Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

© 2026 Quality Measurement Group


Counterfactual Method Explorer for Prehospital Care and Treatment Quality Improvement

June 2, 2026 | drafted by David Balkcom; acknowledgments: Andy Wilson, Brian Knaeble

Open Notebook in Colab

For repo collaborators only: accept the GitHub invite before opening in Colab.


Background

To deliver semantic interoperability and quality improvement across the full patient story, we begin with patient prehospital-emergency care and treatment. Clinical-outcomes data solvency is a principal barrier for administrators looking to improve their program capability and team knowledge.

Across the continuum of care, extending vanguard innovations in Health IT to edge partners such as EMS potentially improves:

  • Care quality
  • Patient outcomes
  • Provider ratings
  • Provider satisfaction

Patching corporate blind spots with clinical data interoperability and solvency is the first step.

Emergency medical services (EMS) prehospital care and treatment providers need closed-loop patient outcomes feedback from emergency care centers after the care transition and handoff occurs. Outcome feedback remains uncommon, and status-quo standard operating procedures (SOPs) guide prehospital providers to the best-possible care path in the field through evidence, experience, and widely adopted care and treatment protocols. By providing quality data solvency and counterfactual intelligence, we hope to enhance that knowledge base.

This project aims to:

  1. Build a compliant-electronic data exchange path with integrated continuous quality improvement (CQI) for EMS providers.
  2. Deliver each differential diagnosis and other pre-defined patient outcome measures from the hospital to the EMS provider agency.
  3. Deliver coupled counterfactual predictions for prehospital care and treatment events to inform provider CQI and wider protocol development.

EMS agencies need closed-loop outcome feedback after patient care transition/handoff. Our "eOutcomes for CQI workbench" already models the core task and objective:

NEMSIS v3.5.1 (ePCR for EMS based records)
  eSituation.11  Provider primary impression
  eSituation.12  Provider secondary impressions
        |
        v
Hospital outcome feedback (EMR based records)
  eOutcome.10    Emergency department diagnosis
  eOutcome.13    Hospital diagnosis
        |
        v
CQI review signal
  alignment score, gaps, case review notes

Proposed Method

Use Diverse Counterfactual Explanations (DiCE) to generate multiple feasible "what-if" alternatives for quality improvement. “For example, if a loan application is rejected, DiCE can provide scenarios such as "Your loan would be approved if your income were $10,000 higher.” DiCE is appropriate here because it can constrain which variables are allowed to change and can return several distinct counterfactual paths instead of one brittle nearest path.

DICE incrementally extends the quality review from "what matched?" to "what field-controllable variables could plausibly improve the modeled outcome in similar cases?"

For this initial draft, the working use case is an ankle injury requiring prehospital splinting during transport. The model asks whether field-controllable variables, such as scene time, transport time, splint type, splint brand, and padding, are associated with a favorable synthetic outcome. Reassessed pain remains a model covariate, not a DiCE-varying action.

Why Variables Must Be Locked

Downstream eOutcome fields are not actionable at the scene. A crew cannot alter an emergency department diagnosis, hospital diagnosis, or disposition after handoff. Therefore:

  • eOutcome.* fields stay in the source data for provenance and labels.
  • eOutcome.* fields are excluded from model predictors to reduce leakage.
  • Non-actionable case-mix fields, such as age and injury severity, may remain model predictors.
  • Only field-controllable variables are passed to DiCE through features_to_vary.
field-controllable inputs  ->  model  ->  favorable outcome label
        |                                  ^
        |                                  |
        +---- DiCE may vary these          |

downstream eOutcome facts  ->  locked provenance / labels only

Synthetic Training Data

The synthetic ankle splint data is stored separately at:

data/synthetic/eoutcomes_ankle_splint_training.json

The JSON file is intentionally synthetic, contains no PHI, and uses normalized training values rather than official NEMSIS code values. It is wired into the Python example below through DATA_PATH. The Colab notebook embeds the same synthetic payload and writes this path automatically when only the notebook file is uploaded.

Python Implementation

Install the local requirements first:

pip install -r requirements.txt

This installs pandas, DiCE, scikit-learn, and matplotlib from the repo requirements file so the example can load data, train a simple model, visualize training-set descriptives, and generate counterfactuals.

from pathlib import Path
import json

import dice_ml
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder


DATA_PATH = Path("data/synthetic/eoutcomes_ankle_splint_training.json")

payload = json.loads(DATA_PATH.read_text(encoding="utf-8"))
schema = payload["schema"]
raw_df = pd.DataFrame(payload["records"])

target = schema["target"]
locked_prefixes = tuple(schema["locked_prefixes"])
locked_exact = set(schema["locked_features"])

locked_features = {
    column
    for column in raw_df.columns
    if column.startswith(locked_prefixes) or column in locked_exact
}

model_feature_columns = [
    column
    for column in schema["model_feature_columns"]
    if column in raw_df.columns and not column.startswith(locked_prefixes)
]

actionable_features = [
    column
    for column in schema["actionable_feature_columns"]
    if column in model_feature_columns and column not in locked_features
]

model_df = raw_df[model_feature_columns + [target]].copy()
continuous_features = [column for column in schema["continuous_features"] if column in model_feature_columns]
categorical_features = [column for column in model_feature_columns if column not in continuous_features]
permitted_range = {
    column: bounds
    for column, bounds in schema["permitted_range"].items()
    if column in actionable_features
}

X = model_df[model_feature_columns]
y = model_df[target]

preprocess = ColumnTransformer(
    transformers=[
        ("categorical", OneHotEncoder(handle_unknown="ignore"), categorical_features),
        ("numeric", "passthrough", continuous_features),
    ]
)

model = Pipeline(
    steps=[
        ("preprocess", preprocess),
        (
            "classifier",
            RandomForestClassifier(
                n_estimators=100,
                random_state=7,
                class_weight="balanced",
            ),
        ),
    ]
)
model.fit(X, y)

dice_data = dice_ml.Data(
    dataframe=model_df,
    continuous_features=continuous_features,
    outcome_name=target,
)
dice_model = dice_ml.Model(model=model, backend="sklearn")
explainer = dice_ml.Dice(dice_data, dice_model, method="random")

query_instance = X.iloc[[0]]

counterfactuals = explainer.generate_counterfactuals(
    query_instance,
    total_CFs=3,
    desired_class=1,
    features_to_vary=actionable_features,
    permitted_range=permitted_range,
)

counterfactuals.visualize_as_dataframe(show_only_changes=True)

End-User Mapping

Do not show raw DiCE dataframes to field crews or CQI reviewers. Map changes back into a small operational review statement:

Raw counterfactual change Review-language output
scene_time_minutes decreases by 4 Similar favorable cases had shorter scene time. Review packaging, assessment, and splint workflow.
splint_type changes from foam to rigid Similar favorable cases used a rigid splint. Route to protocol and supply review, not real-time treatment advice.
padding_used changes from 0 to 1 Similar favorable cases documented padding. Route to documentation and training review.

Sources And Local Provenance

  • Local context: /qmg/skunkworks-signal-three/skunkworks-eoutcomes/README.md
  • Local algorithm: /qmg/skunkworks-signal-three/skunkworks-eoutcomes/eoutcomes_ddx/algorithm.py
  • Local API parity: /qmg/skunkworks-signal-three/skunkworks-eoutcomes/web/functions/api/compare.ts
  • DiCE project: https://www.microsoft.com/en-us/research/project/dice/
  • DiCE documentation: https://interpret.ml/DiCE/index.html

About

diverse counterfactual explanations for EMS quality improvement

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages