Repository navigation
Expand file tree
/
Copy pathtest_rules.py
More file actions
366 lines (319 loc) · 19.7 KB
/
Copy pathtest_rules.py
File metadata and controls
366 lines (319 loc) · 19.7 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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
import json
def calculate_ischemic_risk(p):
return (p['troponin'] == 1 or
p['ischemicInstability'] == 1 or
p['ischemicAngina'] == 1 or
p['ischemicHeartFailure'] == 1 or
p['ischemicArrhythmia'] == 1)
def calculate_bleeding_risk(p):
return (p['age'] >= 75 or
p['bleedingRenal'] == 1 or
p['bleedingAnemia'] == 1 or
p['bleedingAF'] == 1 or
p['bleedingStroke'] == 1)
def evaluate_rules(p):
is_high_ischemic = calculate_ischemic_risk(p)
is_high_bleeding = calculate_bleeding_risk(p)
results = {
'deviations': [],
'alerts': [],
'regimen': []
}
# 1. Oxygenation Check
if 'spo2' in p and p['spo2'] < 90:
results['alerts'].append("HYPOXEMIA ALERT: Patient SpO2 < 90%. Administer supplementary oxygen immediately.")
# 2. Reperfusion Strategy vs Antiplatelet Mapping Rules
dx = p['diagnosis']
reperfusion = p['reperfusion']
p2y12 = p['p2y12']
age_flag = 1 if p['age'] >= 75 else 0
sbp = p.get('sbp', 120)
# Calculate Table 7 antiplatelet recommendations
asp_ld = "162–325 mg orally (chewed)"
asp_md = "75–100 mg orally once daily"
aspirin_rec = f"Aspirin [Loading: {asp_ld}, Maintenance: {asp_md}]"
p2y12_name = "None Selected"
p2y12_ld = "N/A"
p2y12_md = "N/A"
p2y12_adj = "None"
p2y12_warn = ""
age = p.get('age', 60)
weight = p.get('weight', 70)
is_stemi = (dx == 1)
is_fibrinolysis = (reperfusion == 2)
is_pci = (reperfusion == 1)
if p2y12 == 1:
p2y12_name = "Clopidogrel"
if is_stemi and is_fibrinolysis:
if age < 75:
p2y12_ld = "300 mg orally"
p2y12_md = "75 mg orally daily"
else:
p2y12_ld = "No loading dose (initial dose 75 mg orally)"
p2y12_md = "75 mg orally daily"
p2y12_adj = "Loading dose is omitted because the patient is older than 75 years."
else:
p2y12_ld = "300–600 mg orally"
p2y12_md = "75 mg orally daily"
elif p2y12 == 2:
p2y12_name = "Ticagrelor"
p2y12_ld = "180 mg orally"
p2y12_md = "90 mg orally twice daily"
if is_stemi and is_fibrinolysis:
p2y12_warn = "WARNING: Ticagrelor is not preferred post-fibrinolysis according to clinical guidelines."
elif p2y12 == 3:
p2y12_name = "Prasugrel"
p2y12_ld = "60 mg orally"
if is_fibrinolysis:
p2y12_md = "N/A"
p2y12_warn = "WARNING: Prasugrel is contraindicated with Fibrinolytic therapy."
elif not is_pci:
p2y12_md = "10 mg orally daily (Not recommended for non-PCI)"
p2y12_warn = "WARNING: Prasugrel should only be used in patients undergoing PCI."
else:
if weight >= 60 and age < 75:
p2y12_md = "10 mg orally daily"
else:
p2y12_md = "5 mg orally daily"
p2y12_adj = f"Maintenance dose is reduced to 5 mg daily due to {'age ≥ 75' if age >= 75 else ''}{' and ' if age >= 75 and weight < 60 else ''}{'weight < 60 kg' if weight < 60 else ''}."
p2y12_warn = f"CAUTION: Maintenance dose is reduced to 5 mg daily because of {'age ≥ 75' if age >= 75 else ''}{' or ' if age >= 75 or weight < 60 else ''}{'body weight < 60 kg' if weight < 60 else ''}."
p2y12_rec = f"{p2y12_name} [Loading: {p2y12_ld}, Maintenance: {p2y12_md}"
if p2y12_adj != "None":
p2y12_rec += f", Adjustment: {p2y12_adj}"
p2y12_rec += "]"
if p2y12_warn:
results['alerts'].append(p2y12_warn)
if dx == 1: # STEMI
if reperfusion == 2: # Fibrinolytics
# Fibrinolytic contraindications (Table 14)
if sbp > 180:
results['alerts'].append("CRITICAL: Absolute contraindication for Fibrinolysis (Severe uncontrolled SBP > 180 mmHg).")
results['deviations'].append("CONTRAINDICATION FAULT: Fibrinolysis selected despite SBP > 180 mmHg.")
if p.get('bleedingStroke', 0) == 1 or p.get('priorStroke', 0) == 1:
results['alerts'].append("CRITICAL: Absolute contraindication for Fibrinolysis (History of Stroke/TIA).")
results['deviations'].append("CONTRAINDICATION FAULT: Fibrinolysis selected in patient with prior Stroke/TIA.")
if p.get('bleedingAF', 0) == 1 or p.get('concurrentOac', 0) == 1:
results['alerts'].append("WARNING: Relative contraindication for Fibrinolysis (Oral Anticoagulant therapy).")
if p.get('pepticUlcer', 0) == 1:
results['alerts'].append("WARNING: Relative contraindication for Fibrinolysis (Active peptic ulcer).")
if p.get('dementia', 0) == 1:
results['alerts'].append("WARNING: Relative contraindication for Fibrinolysis (Dementia).")
results['regimen'].append(aspirin_rec)
results['regimen'].append(p2y12_rec)
if p2y12 in [2, 3]:
results['deviations'].append("CRITICAL MISMATCH: Potent antiplatelets (Prasugrel/Ticagrelor) run concurrently with Fibrinolytics.")
results['alerts'].append("HEMORRHAGE RISK HIGH: Discontinue Prasugrel/Ticagrelor immediately to mitigate intracranial bleeding risk.")
elif reperfusion == 1: # Primary PCI
results['regimen'].append(aspirin_rec)
results['regimen'].append(p2y12_rec)
if p2y12 == 1 and not is_high_bleeding:
results['deviations'].append("SUBOPTIMAL PROTECTION: Clopidogrel used in standard risk PPCI path.")
results['alerts'].append("EFFICACY WARNING: Upgrade to Ticagrelor or Prasugrel for superior protection against stent thrombosis.")
elif p2y12 == 0:
results['deviations'].append("CRITICAL OMISSION: A P2Y12 inhibitor is required for all STEMI patients undergoing Primary PCI.")
elif dx in [2, 3]: # NSTE-ACS
# Relative contraindications to routine invasive strategy (Table 15)
has_invasive_contra = (is_high_bleeding or p.get('thrombocytopenia', 0) == 1 or p.get('bleedingRenal', 0) == 1 or p.get('arf', 0) == 1 or p.get('expectancy', 0) == 1 or p.get('dementia', 0) == 1)
if has_invasive_contra:
results['alerts'].append("RELATIVE CONTRAINDICATION: Routine invasive procedure has contraindications (due to HBR, thrombocytopenia, CKD/ARF, limited expectancy, or dementia). Route to Conservative Medical Management instead.")
if reperfusion == 1:
results['deviations'].append("CONTRAINDICATION FAULT: Routine invasive procedure selected despite relative contraindications.")
if reperfusion == 4: # Conservative Medical Management
results['regimen'].append(aspirin_rec)
results['regimen'].append(p2y12_rec)
if p2y12 == 1 and not is_high_bleeding:
results['deviations'].append("NON-PREFERENTIAL REGIMEN: Clopidogrel selected for standard risk medical management.")
elif reperfusion == 1: # Invasive Strategy
results['regimen'].append(aspirin_rec)
results['regimen'].append(p2y12_rec)
if p2y12 == 1 and not is_high_bleeding:
results['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:
results['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:
results['regimen'].append("Guideline Compliant: Early Invasive Strategy (Angiography within 24 hours) is indicated for standard-risk NSTE-ACS.")
# 4. Baseline Pharmacotherapy Checks (Anticoagulation, Beta-Blockers, RAAS)
if p.get('anticoagulation', 0) == 0:
results['deviations'].append("OMISSION FAULT: Parenteral anticoagulation not administered.")
results['regimen'].append("Initiate Parenteral Anticoagulation (UFH, Enoxaparin, Bivalirudin, or Fondaparinux).")
if p.get('betaBlocker', 0) == 0:
results['deviations'].append("OMISSION FAULT: Baseline Beta-Blocker therapy not initiated.")
results['regimen'].append("Initiate Oral Beta-Blocker therapy (e.g., Metoprolol succinate or Carvedilol) within 24 hours.")
if p.get('raasInhibitor', 0) == 0:
results['deviations'].append("OMISSION FAULT: Baseline RAAS Inhibitor (ACEI/ARB) not initiated.")
results['regimen'].append("Initiate ACE Inhibitor (e.g., Ramipril) or ARB (e.g., Valsartan) within 24 hours.")
# 5. RBC Transfusion Protocol
if p.get('bleedingAnemia', 0) == 1 and p.get('minorBleeding', 0) == 0:
if p.get('transfusion', 0) == 0:
results['deviations'].append("OMISSION FAULT: RBC transfusion not administered despite severe anemia without active bleeding.")
results['regimen'].append("Administer RBC Transfusion to maintain hemoglobin target of 10 g/dL (Class 2b).")
else:
results['regimen'].append("Guideline Compliant: RBC Transfusion administered to maintain Hb target (10 g/dL).")
# 6. High Bleeding Risk (HBR) Custom Safe-Overrides
if is_high_bleeding:
if p2y12 == 3: # Prasugrel in HBR
results['deviations'].append("CONTRAINDICATION OVERRIDE: Prasugrel allocated within high bleeding risk profile.")
results['alerts'].append("STOP SIGN: Immediately discontinue Prasugrel. Substitute with Clopidogrel 75mg daily.")
# Rules A, B, C
if p.get('concurrentOac', 0) == 1 or p['bleedingAF'] == 1: # Rule C
results['regimen'].append("Convert to Dual Therapy (OAC + Clopidogrel 75mg) after 1-4 weeks; completely drop Aspirin.")
elif p.get('minorBleeding', 0) == 1: # Rule B
results['regimen'].append("P2Y12 Down-Titration: Transition from Ticagrelor/Prasugrel to Clopidogrel 75mg daily due to bleeding/Hb drop.")
else: # Rule A
results['regimen'].append("Short-Course DAPT: Transition to Ticagrelor Monotherapy after 1-3 months, completely dropping Aspirin.")
# 7. GI Safety Net
has_gi_trigger = is_high_bleeding or p.get('pepticUlcer', 0) == 1 or p['age'] >= 75 or p.get('nsaid', 0) == 1
if has_gi_trigger:
if p.get('ppiPrescribed', 0) != 1:
results['alerts'].append("GASTRIC GAP: Co-prescribe a protective PPI (e.g., Pantoprazole) to manage GI bleeding margins.")
if p2y12 == 1 and p.get('ppiPrescribed', 0) == 1:
results['alerts'].append("PPI Metabolic Optimization: If Clopidogrel is prescribed, Pantoprazole is preferred over Omeprazole (prevents CYP2C19 inhibition).")
# 8. Lipid Secondary Prevention
if p['statin'] != 2:
results['deviations'].append("OMISSION FAULT: Patient not initiated on baseline High-Intensity Statin therapy.")
results['regimen'].append("Initiate High-Intensity Atorvastatin 40-80mg or Rosuvastatin 20-40mg.")
if p['ldlc'] >= 70:
results['deviations'].append("LIPID GOAL MISSED: Follow-up LDL tracking >= 70 mg/dL.")
results['regimen'].append("Add Non-Statin Therapy: Append Ezetimibe 10mg daily immediately (Class 1).")
elif 55 <= p['ldlc'] < 70:
results['regimen'].append("Therapeutic Optimization: Evaluate addition of Ezetimibe to reach target < 55 mg/dL (Class 2a).")
return results
def run_tests():
# Load converted patients
with open('patients.json', 'r', encoding='utf-8') as f:
patients = json.load(f)
print(f"Loaded {len(patients)} patients for rule audit tests.")
# Test cases count
criticals = 0
warnings = 0
for i, p in enumerate(patients):
res = evaluate_rules(p)
criticals += len(res['deviations'])
warnings += len(res['alerts'])
print(f"Audit Summary of Converted Registry Dataset:")
print(f" - Total Patients Audited: {len(patients)}")
print(f" - Total Deviations/Omissions found: {criticals}")
print(f" - Total Safety Alerts triggered: {warnings}")
# Run test profiles
print("\nRunning specific guideline boundary checks:")
# Boundary Test 1: STEMI + Fibrinolytics + Ticagrelor
test1 = {
"patientId": "T1", "name": "Test STEMI Fibrinolytic Ticagrelor",
"age": 60, "gender": "Male", "diagnosis": 1, "troponin": 1, "ecg": 1,
"ischemicInstability": 0, "ischemicAngina": 0, "ischemicHeartFailure": 0, "ischemicArrhythmia": 0,
"bleedingRenal": 0, "bleedingAnemia": 0, "bleedingAF": 0, "bleedingStroke": 0,
"spo2": 95, "sbp": 120, "hr": 80, "cpType": 1,
"diabetes": 0, "ckd": 0, "priorStroke": 0, "pepticUlcer": 0, "nsaid": 0,
"minorBleeding": 0, "concurrentOac": 0, "ppiPrescribed": 0,
"reperfusion": 2, "p2y12": 2, "statin": 2, "ldlc": 65
}
r1 = evaluate_rules(test1)
assert any("CRITICAL MISMATCH" in d for d in r1['deviations']), f"Failed Test 1: {r1}"
print(" [PASS] STEMI post-fibrinolysis on Ticagrelor correctly flagged as CRITICAL MISMATCH.")
# Boundary Test 1b: STEMI + Fibrinolytics + Clopidogrel + Age >= 75
test1b = {
"patientId": "T1b", "name": "Test STEMI Fibrinolytic Clopidogrel >=75",
"age": 75, "gender": "Male", "diagnosis": 1, "troponin": 1, "ecg": 1,
"ischemicInstability": 0, "ischemicAngina": 0, "ischemicHeartFailure": 0, "ischemicArrhythmia": 0,
"bleedingRenal": 0, "bleedingAnemia": 0, "bleedingAF": 0, "bleedingStroke": 0,
"spo2": 95, "sbp": 120, "hr": 80, "cpType": 1,
"diabetes": 0, "ckd": 0, "priorStroke": 0, "pepticUlcer": 0, "nsaid": 0,
"minorBleeding": 0, "concurrentOac": 0, "ppiPrescribed": 1,
"reperfusion": 2, "p2y12": 1, "statin": 2, "ldlc": 65
}
r1b = evaluate_rules(test1b)
assert any("no load" in reg.lower() for reg in r1b['regimen']), f"Failed Test 1b: {r1b}"
print(" [PASS] STEMI post-fibrinolysis Clopidogrel (Age >= 75) correctly flags for no loading dose.")
# Boundary Test 2: NSTE-ACS + Ischemic Indicators (Angiography within 2 hours)
test2 = {
"patientId": "T2", "name": "Test NSTE-ACS High Ischemic",
"age": 60, "gender": "Male", "diagnosis": 2, "troponin": 1, "ecg": 2,
"ischemicInstability": 0, "ischemicAngina": 1, "ischemicHeartFailure": 0, "ischemicArrhythmia": 0,
"bleedingRenal": 0, "bleedingAnemia": 0, "bleedingAF": 0, "bleedingStroke": 0,
"spo2": 95, "sbp": 120, "hr": 80, "cpType": 1,
"diabetes": 0, "ckd": 0, "priorStroke": 0, "pepticUlcer": 0, "nsaid": 0,
"minorBleeding": 0, "concurrentOac": 0, "ppiPrescribed": 0,
"reperfusion": 4, "p2y12": 1, "statin": 2, "ldlc": 65
}
r2 = evaluate_rules(test2)
assert any("Immediate Invasive Strategy Required" in a for a in r2['alerts']), f"Failed Test 2: {r2}"
print(" [PASS] High Ischemic Risk NSTE-ACS patient correctly flagged for Immediate Invasive Strategy.")
# Boundary Test 3: HBR + Prasugrel (Absolute Contraindication)
test3 = {
"patientId": "T3", "name": "Test HBR Prasugrel",
"age": 76, "gender": "Male", "diagnosis": 1, "troponin": 1, "ecg": 1,
"ischemicInstability": 0, "ischemicAngina": 0, "ischemicHeartFailure": 0, "ischemicArrhythmia": 0,
"bleedingRenal": 0, "bleedingAnemia": 0, "bleedingAF": 0, "bleedingStroke": 0,
"spo2": 95, "sbp": 120, "hr": 80, "cpType": 1,
"diabetes": 0, "ckd": 0, "priorStroke": 0, "pepticUlcer": 0, "nsaid": 0,
"minorBleeding": 0, "concurrentOac": 0, "ppiPrescribed": 1,
"reperfusion": 1, "p2y12": 3, "statin": 2, "ldlc": 65
}
r3 = evaluate_rules(test3)
assert any("CONTRAINDICATION OVERRIDE" in d for d in r3['deviations']), f"Failed Test 3: {r3}"
print(" [PASS] HBR patient (Age >= 75) on Prasugrel correctly flagged as CONTRAINDICATION OVERRIDE.")
# Boundary Test 4: PPI Gastric Gap & Metabolic Optimization
test4 = {
"patientId": "T4", "name": "Test Gastric Gap and Metabolic",
"age": 78, "gender": "Male", "diagnosis": 3, "troponin": 0, "ecg": 0,
"ischemicInstability": 0, "ischemicAngina": 0, "ischemicHeartFailure": 0, "ischemicArrhythmia": 0,
"bleedingRenal": 0, "bleedingAnemia": 0, "bleedingAF": 0, "bleedingStroke": 0,
"spo2": 95, "sbp": 120, "hr": 80, "cpType": 2,
"diabetes": 0, "ckd": 0, "priorStroke": 0, "pepticUlcer": 0, "nsaid": 0,
"minorBleeding": 0, "concurrentOac": 0, "ppiPrescribed": 0,
"reperfusion": 4, "p2y12": 1, "statin": 2, "ldlc": 65
}
r4 = evaluate_rules(test4)
assert any("GASTRIC GAP" in a for a in r4['alerts']), f"Failed Test 4 Gastric Gap: {r4}"
test4['ppiPrescribed'] = 1
r4b = evaluate_rules(test4)
assert any("PPI Metabolic Optimization" in a for a in r4b['alerts']), f"Failed Test 4 Metabolic: {r4b}"
print(" [PASS] PPI Gastric Gap and CYP2C19 metabolic drug interaction rules verified.")
# Boundary Test 5: Hypoxemia check
test5 = {
"patientId": "T5", "name": "Test Hypoxemia",
"age": 60, "gender": "Male", "diagnosis": 3, "troponin": 0, "ecg": 0,
"ischemicInstability": 0, "ischemicAngina": 0, "ischemicHeartFailure": 0, "ischemicArrhythmia": 0,
"bleedingRenal": 0, "bleedingAnemia": 0, "bleedingAF": 0, "bleedingStroke": 0,
"spo2": 88, "sbp": 120, "hr": 80, "cpType": 2,
"diabetes": 0, "ckd": 0, "priorStroke": 0, "pepticUlcer": 0, "nsaid": 0,
"minorBleeding": 0, "concurrentOac": 0, "ppiPrescribed": 0,
"reperfusion": 4, "p2y12": 1, "statin": 2, "ldlc": 65
}
r5 = evaluate_rules(test5)
assert any("HYPOXEMIA ALERT" in a for a in r5['alerts']), f"Failed Test 5 Hypoxemia: {r5}"
print(" [PASS] Hypoxemia (SpO2 < 90%) check verified.")
# Boundary Test 6: HBR Rules A/B/C
# Rule B: Minor Bleeding
test6b = {
"patientId": "T6b", "name": "Test Rule B",
"age": 60, "gender": "Male", "diagnosis": 3, "troponin": 0, "ecg": 0,
"ischemicInstability": 0, "ischemicAngina": 0, "ischemicHeartFailure": 0, "ischemicArrhythmia": 0,
"bleedingRenal": 1, "bleedingAnemia": 0, "bleedingAF": 0, "bleedingStroke": 0,
"spo2": 95, "sbp": 120, "hr": 80, "cpType": 2,
"diabetes": 0, "ckd": 0, "priorStroke": 0, "pepticUlcer": 0, "nsaid": 0,
"minorBleeding": 1, "concurrentOac": 0, "ppiPrescribed": 1,
"reperfusion": 4, "p2y12": 2, "statin": 2, "ldlc": 65
}
r6b = evaluate_rules(test6b)
assert any("P2Y12 Down-Titration" in reg for reg in r6b['regimen']), f"Failed Rule B: {r6b}"
print(" [PASS] HBR Rule B (P2Y12 Down-Titration on minor bleed) verified.")
# Rule C: Concurrent OAC
test6c = {
"patientId": "T6c", "name": "Test Rule C",
"age": 60, "gender": "Male", "diagnosis": 3, "troponin": 0, "ecg": 0,
"ischemicInstability": 0, "ischemicAngina": 0, "ischemicHeartFailure": 0, "ischemicArrhythmia": 0,
"bleedingRenal": 1, "bleedingAnemia": 0, "bleedingAF": 1, "bleedingStroke": 0,
"spo2": 95, "sbp": 120, "hr": 80, "cpType": 2,
"diabetes": 0, "ckd": 0, "priorStroke": 0, "pepticUlcer": 0, "nsaid": 0,
"minorBleeding": 0, "concurrentOac": 1, "ppiPrescribed": 1,
"reperfusion": 4, "p2y12": 2, "statin": 2, "ldlc": 65
}
r6c = evaluate_rules(test6c)
assert any("Convert to Dual Therapy" in reg for reg in r6c['regimen']), f"Failed Rule C: {r6c}"
print(" [PASS] HBR Rule C (Triple to Dual therapy conversion for OAC) verified.")
print("\nAll boundary tests PASSED successfully.")
if __name__ == "__main__":
run_tests()