-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcdss_engine.py
More file actions
179 lines (152 loc) · 10.9 KB
/
cdss_engine.py
File metadata and controls
179 lines (152 loc) · 10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import pandas as pd
import numpy as np
import os
class CDSSCoreEngine:
def __init__(self, data_path: str):
"""
Initializes the CDSS Engine, ingests raw clinical CSV files,
and normalizes naming schemas.
"""
if not os.path.exists(data_path):
raise FileNotFoundError(f"Target data file at {data_path} not found.")
self.df = pd.read_csv(data_path)
self._sanitize_headers()
def _sanitize_headers(self):
"""Normalizes column names to standard snake_case formatting."""
self.df.columns = [str(col).strip().replace(" ", "_").replace("/", "_") for col in self.df.columns]
def process_clinical_audit(self, output_report_path: str):
"""
Iterates across patient data entries, evaluates the 12 input features,
calculates compliance metrics, and generates a structured audit report.
"""
audit_records = []
for idx, row in self.df.iterrows():
# Extract identifiers and metadata safely
patient_id = row.get('Patient_ID', f"PID_VAL_{idx + 1001}")
# Phase 1: Input Variable Extraction (Fields 1-12 Logic)
dx = row.get('Diagnosis') # 1=STEMI, 2=NSTEMI, 3=UA
age_flag = row.get('Age_Group', 0) # 0=<75, 1==>75
spo2 = row.get('SpO2', 95) # Continuous numeric percentage
# Additional continuous and medical history variables
sbp = row.get('SBP', 120)
hr = row.get('HR', 75)
troponin = row.get('Troponin', 1 if dx in [1, 2] else 0)
ecg = row.get('ECG', 0)
# Phase 2: Treatment & Management Variable Extraction
reperfusion = row.get('Reperfusion_Strategy', 4) # 1=PPCI, 2=Fibrinolytics, 3=CABG, 4=MedMgmt
p2y12 = row.get('P2Y12_Inhibitor', 0) # 0=None, 1=Clopidogrel, 2=Ticagrelor, 3=Prasugrel
hbr_status = row.get('Bleeding_Risk_HBR', 0) # 0=Low, 1=High Bleeding Risk
statin = row.get('Statin_Intensity', 0) # 0=None, 1=Low/Mod, 2=High
ldl = row.get('Follow_Up_LDL', 85) # Continuous follow-up value
ppi_status = row.get('PPI_Prescribed', 0) # Binary status field
oac_status = row.get('Concurrent_OAC', 0) # Binary concurrent oral anticoagulant field
# Specific ischemic and bleeding indicators (reconstructed from patient data for fine-grained rules)
ischemicInstability = 1 if (sbp < 90 or hr > 100) else 0
ischemicHeartFailure = 1 if (row.get('ischemicHeartFailure') == 1) else 0
ischemicAngina = 1 if (row.get('ischemicAngina') == 1) else 0
ischemicArrhythmia = 1 if (row.get('ischemicArrhythmia') == 1) else 0
is_high_ischemic = (troponin == 1 or ischemicInstability == 1 or
ischemicAngina == 1 or ischemicHeartFailure == 1 or
ischemicArrhythmia == 1)
# ARC-HBR components
bleedingRenal = row.get('bleedingRenal', 0)
bleedingAnemia = row.get('bleedingAnemia', 0)
bleedingAF = row.get('bleedingAF', 0)
bleedingStroke = row.get('bleedingStroke', 0)
is_high_bleeding = (age_flag == 1 or hbr_status == 1 or bleedingRenal == 1 or
bleedingAnemia == 1 or bleedingAF == 1 or bleedingStroke == 1)
# Evaluation containers
system_deviations = []
predicted_best_regimen = []
safety_alerts = []
# --- SYSTEM VALIDATION MODULES ---
# 1. Oxygenation Check
if pd.notna(spo2) and spo2 < 90:
safety_alerts.append("HYPOXEMIA ALERT: Patient SpO2 < 90%. Administer supplementary oxygen immediately.")
# 2. Reperfusion Strategy vs Antiplatelet Mapping Rules
if dx == 1: # STEMI Primary Classification
if reperfusion == 2: # Fibrinolytic Therapy Path
if age_flag == 1 or is_high_bleeding:
predicted_best_regimen.append("Aspirin (162-325mg load) + Clopidogrel (75mg daily, no load) [Guideline Preferred]")
else:
predicted_best_regimen.append("Aspirin (162-325mg load) + Clopidogrel (300mg load) [Guideline Preferred]")
if p2y12 in [2, 3]:
system_deviations.append("CRITICAL MISMATCH: Potent antiplatelets (Prasugrel/Ticagrelor) run concurrently with Fibrinolytics.")
safety_alerts.append("HEMORRHAGE RISK HIGH: Discontinue Prasugrel/Ticagrelor immediately to mitigate intracranial bleeding risk.")
elif reperfusion == 1: # Primary PCI Path
predicted_best_regimen.append("Aspirin (162-325mg load) + Prasugrel (60mg load) OR Ticagrelor (180mg load)")
if p2y12 == 1 and not is_high_bleeding:
system_deviations.append("SUBOPTIMAL PROTECTION: Clopidogrel used in standard risk PPCI path.")
safety_alerts.append("EFFICACY WARNING: Upgrade to Ticagrelor or Prasugrel for superior protection against stent thrombosis.")
elif p2y12 == 0:
system_deviations.append("CRITICAL OMISSION: A P2Y12 inhibitor is required for all STEMI patients undergoing Primary PCI.")
elif dx in [2, 3]: # NSTE-ACS Protocols (NSTEMI / Unstable Angina)
if reperfusion == 4: # Conservative Medical Management
predicted_best_regimen.append("Aspirin (81mg daily) + Ticagrelor (90mg BID for 12 months)")
if p2y12 == 1 and not is_high_bleeding:
system_deviations.append("NON-PREFERENTIAL REGIMEN: Clopidogrel selected for standard risk medical management.")
elif reperfusion == 1: # Invasive Strategy
predicted_best_regimen.append("Aspirin + Ticagrelor (or Prasugrel administered at angiography)")
if p2y12 == 1 and not is_high_bleeding:
system_deviations.append("SUBOPTIMAL EFFICACY: Clopidogrel is less effective than Ticagrelor/Prasugrel. Switch to a high-potency agent unless contraindicated or high bleeding risk exists.")
# 3. NSTE-ACS Invasive Strategy Timing
if dx in [2, 3]:
if is_high_ischemic:
safety_alerts.append("CRITICAL: Immediate Invasive Strategy Required (Angiography within 2 hours) due to presence of High Ischemic Risk Indicators (cardiogenic shock, refractory angina, heart failure, or sustained VT/VF).")
else:
predicted_best_regimen.append("Guideline Compliant: Early Invasive Strategy (Angiography within 24 hours) is indicated for standard-risk NSTE-ACS.")
# 4. High Bleeding Risk (HBR) Custom Safe-Overrides
if is_high_bleeding:
if p2y12 == 3: # Prasugrel assigned to HBR
system_deviations.append("CONTRAINDICATION OVERRIDE: Prasugrel allocated within high bleeding risk profile.")
safety_alerts.append("STOP SIGN: Immediately discontinue Prasugrel. Substitute with Clopidogrel 75mg daily.")
# Check for protective PPI therapy (HBR is a gastric protection trigger)
if ppi_status != 1:
safety_alerts.append("GASTRIC GAP: Co-prescribe a protective PPI (e.g., Pantoprazole) to manage GI bleeding margins.")
# HBR Rules A, B, C
if oac_status == 1 or bleedingAF == 1: # Rule C
predicted_best_regimen.append("Convert to Dual Therapy (OAC + Clopidogrel 75mg) after 1-4 weeks; completely drop Aspirin.")
elif row.get('minorBleeding') == 1 or row.get('minor_bleeding') == 1: # Rule B
predicted_best_regimen.append("P2Y12 Down-Titration: Transition from Ticagrelor/Prasugrel to Clopidogrel 75mg daily due to bleeding/Hb drop.")
else: # Rule A
predicted_best_regimen.append("Short-Course DAPT: Transition to Ticagrelor Monotherapy after 1-3 months, completely dropping Aspirin.")
else:
# If not high bleeding, check other GI triggers (peptic ulcer, concurrent NSAIDs)
has_gi_trigger = (row.get('pepticUlcer') == 1 or row.get('nsaid') == 1)
if has_gi_trigger and ppi_status != 1:
safety_alerts.append("GASTRIC GAP: Co-prescribe a protective PPI (e.g., Pantoprazole) to manage GI bleeding margins.")
# PPI Metabolic Optimization Check
if p2y12 == 1 and ppi_status == 1:
safety_alerts.append("PPI Metabolic Optimization: If Clopidogrel is prescribed, Pantoprazole is preferred over Omeprazole (prevents CYP2C19 inhibition).")
# 5. Secondary Lipid Prevention Engine (Ref: 2025 Guideline Fig 5)
if statin != 2:
system_deviations.append("OMISSION FAULT: Patient not initiated on baseline High-Intensity Statin therapy.")
predicted_best_regimen.append("Initiate High-Intensity Atorvastatin 40-80mg or Rosuvastatin 20-40mg.")
if pd.notna(ldl):
if ldl >= 70:
system_deviations.append("LIPID GOAL MISSED: Follow-up LDL tracking >= 70 mg/dL.")
predicted_best_regimen.append("Add Non-Statin Therapy: Append Ezetimibe 10mg daily immediately (Class 1).")
elif 55 <= ldl < 70:
predicted_best_regimen.append("Therapeutic Optimization: Evaluate addition of Ezetimibe to reach target < 55 mg/dL (Class 2a).")
# Final aggregation formatting
audit_records.append({
"Patient_ID": patient_id,
"Audit_Status": "FAIL (Deviations Found)" if system_deviations else "PASS (Guideline Compliant)",
"Identified_Deviations": " | ".join(system_deviations) if system_deviations else "None Identified",
"Safety_Alert_Overrides": " | ".join(safety_alerts) if safety_alerts else "Patient Stable",
"Predicted_Optimal_Management": " | ".join(predicted_best_regimen) if predicted_best_regimen else "Maintain Current Schema"
})
# Save the audit report
output_df = pd.DataFrame(audit_records)
output_df.to_csv(output_report_path, index=False)
return output_df
if __name__ == "__main__":
source_csv = "IHD data entry.xlsx - IHD.csv"
target_report = "CDSS_Final_Clinical_Audit.csv"
try:
engine = CDSSCoreEngine(data_path=source_csv)
results = engine.process_clinical_audit(output_report_path=target_report)
print(f"CDSS Engine successfully processed {len(results)} client records.")
print(f"Audit matrix written to: {target_report}")
except Exception as e:
print(f"Engine execution halted. Parameter Exception: {str(e)}")