snikhilesh commited on
Commit
c3eaf50
·
verified ·
1 Parent(s): 3e68886

Deploy test_synthesis_service.py to backend/ directory

Browse files
Files changed (1) hide show
  1. backend/test_synthesis_service.py +515 -0
backend/test_synthesis_service.py ADDED
@@ -0,0 +1,515 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test Suite for Clinical Synthesis Service
3
+ Tests MedGemma prompt templates and synthesis functionality
4
+
5
+ Author: MiniMax Agent
6
+ Date: 2025-10-29
7
+ """
8
+
9
+ import sys
10
+ import asyncio
11
+ from datetime import datetime
12
+ from typing import Dict, Any
13
+
14
+ # Add backend to path
15
+ sys.path.insert(0, '/workspace/medical-ai-platform/backend')
16
+
17
+ from clinical_synthesis_service import get_synthesis_service
18
+ from medical_schemas import ECGAnalysis, RadiologyAnalysis, LaboratoryResults, ClinicalNotesAnalysis
19
+
20
+
21
+ def create_sample_ecg_data() -> Dict[str, Any]:
22
+ """Create sample ECG structured data for testing"""
23
+ return {
24
+ "metadata": {
25
+ "document_id": "ecg-test-001",
26
+ "source_type": "ECG",
27
+ "document_date": "2025-10-29T10:00:00Z",
28
+ "facility": "Test Medical Center",
29
+ "data_completeness": 0.95
30
+ },
31
+ "signal_data": {
32
+ "lead_names": ["I", "II", "III", "aVR", "aVL", "aVF", "V1", "V2", "V3", "V4", "V5", "V6"],
33
+ "sampling_rate_hz": 500,
34
+ "signal_arrays": {
35
+ "I": [0.5] * 5000,
36
+ "II": [0.8] * 5000,
37
+ "III": [0.3] * 5000,
38
+ "aVR": [-0.6] * 5000,
39
+ "aVL": [0.4] * 5000,
40
+ "aVF": [0.6] * 5000,
41
+ "V1": [0.2] * 5000,
42
+ "V2": [0.4] * 5000,
43
+ "V3": [0.6] * 5000,
44
+ "V4": [0.8] * 5000,
45
+ "V5": [0.9] * 5000,
46
+ "V6": [0.8] * 5000
47
+ },
48
+ "duration_seconds": 10.0,
49
+ "num_samples": 5000
50
+ },
51
+ "intervals": {
52
+ "pr_ms": 165.0,
53
+ "qrs_ms": 92.0,
54
+ "qt_ms": 390.0,
55
+ "qtc_ms": 425.0,
56
+ "rr_ms": 850.0
57
+ },
58
+ "rhythm_classification": {
59
+ "primary_rhythm": "Normal Sinus Rhythm",
60
+ "rhythm_confidence": 0.92,
61
+ "arrhythmia_types": [],
62
+ "heart_rate_bpm": 71,
63
+ "heart_rate_regularity": "regular"
64
+ },
65
+ "arrhythmia_probabilities": {
66
+ "normal_rhythm": 0.92,
67
+ "atrial_fibrillation": 0.02,
68
+ "atrial_flutter": 0.01,
69
+ "ventricular_tachycardia": 0.01,
70
+ "heart_block": 0.01,
71
+ "premature_beats": 0.03
72
+ },
73
+ "derived_features": {
74
+ "st_elevation_mm": {},
75
+ "st_depression_mm": {},
76
+ "t_wave_abnormalities": [],
77
+ "q_wave_indicators": [],
78
+ "axis_deviation": "normal"
79
+ },
80
+ "confidence": {
81
+ "extraction_confidence": 0.94,
82
+ "model_confidence": 0.89,
83
+ "data_quality": 0.95
84
+ }
85
+ }
86
+
87
+
88
+ def create_sample_radiology_data() -> Dict[str, Any]:
89
+ """Create sample radiology structured data for testing"""
90
+ return {
91
+ "metadata": {
92
+ "document_id": "rad-test-001",
93
+ "source_type": "radiology",
94
+ "document_date": "2025-10-29T11:00:00Z",
95
+ "facility": "Imaging Center",
96
+ "data_completeness": 0.90
97
+ },
98
+ "image_references": [
99
+ {
100
+ "image_id": "img-001",
101
+ "modality": "CT",
102
+ "body_part": "Chest",
103
+ "view_orientation": "Axial",
104
+ "slice_thickness_mm": 2.5,
105
+ "resolution": {"width": 512, "height": 512}
106
+ }
107
+ ],
108
+ "findings": {
109
+ "findings_text": "Chest CT shows clear lungs bilaterally. No pleural effusion. Heart size within normal limits. No mediastinal lymphadenopathy. Bones appear intact without acute fracture.",
110
+ "impression_text": "No acute cardiopulmonary abnormality. Unremarkable chest CT.",
111
+ "critical_findings": [],
112
+ "incidental_findings": ["Mild degenerative changes in thoracic spine"],
113
+ "comparison_prior": "None available",
114
+ "technique_description": "Contrast-enhanced CT chest with IV contrast"
115
+ },
116
+ "segmentations": [],
117
+ "metrics": {
118
+ "organ_volumes": {"lung_left": 2800, "lung_right": 2950, "heart": 680},
119
+ "lesion_measurements": [],
120
+ "enhancement_patterns": [],
121
+ "calcification_scores": {},
122
+ "tissue_density": {}
123
+ },
124
+ "confidence": {
125
+ "extraction_confidence": 0.88,
126
+ "model_confidence": 0.85,
127
+ "data_quality": 0.92
128
+ },
129
+ "criticality_level": "routine",
130
+ "follow_up_recommendations": []
131
+ }
132
+
133
+
134
+ def create_sample_laboratory_data() -> Dict[str, Any]:
135
+ """Create sample laboratory results for testing"""
136
+ return {
137
+ "metadata": {
138
+ "document_id": "lab-test-001",
139
+ "source_type": "laboratory",
140
+ "document_date": "2025-10-29T09:00:00Z",
141
+ "facility": "Test Lab",
142
+ "data_completeness": 0.98
143
+ },
144
+ "tests": [
145
+ {
146
+ "test_name": "Glucose",
147
+ "test_code": "2345-7",
148
+ "value": 105.0,
149
+ "unit": "mg/dL",
150
+ "reference_range_low": 70.0,
151
+ "reference_range_high": 99.0,
152
+ "flags": ["H"]
153
+ },
154
+ {
155
+ "test_name": "Hemoglobin",
156
+ "test_code": "718-7",
157
+ "value": 14.5,
158
+ "unit": "g/dL",
159
+ "reference_range_low": 13.5,
160
+ "reference_range_high": 17.5,
161
+ "flags": []
162
+ },
163
+ {
164
+ "test_name": "Creatinine",
165
+ "test_code": "2160-0",
166
+ "value": 1.1,
167
+ "unit": "mg/dL",
168
+ "reference_range_low": 0.7,
169
+ "reference_range_high": 1.3,
170
+ "flags": []
171
+ },
172
+ {
173
+ "test_name": "Total Cholesterol",
174
+ "test_code": "2093-3",
175
+ "value": 215.0,
176
+ "unit": "mg/dL",
177
+ "reference_range_low": 0.0,
178
+ "reference_range_high": 200.0,
179
+ "flags": ["H"]
180
+ }
181
+ ],
182
+ "critical_values": [],
183
+ "panel_name": "Basic Metabolic Panel + Lipids",
184
+ "fasting_status": "fasting",
185
+ "collection_date": "2025-10-29T09:00:00Z",
186
+ "confidence": {
187
+ "extraction_confidence": 0.96,
188
+ "model_confidence": 0.92,
189
+ "data_quality": 0.98
190
+ },
191
+ "abnormal_count": 2,
192
+ "critical_count": 0
193
+ }
194
+
195
+
196
+ def create_sample_model_outputs() -> list:
197
+ """Create sample model outputs for testing"""
198
+ return [
199
+ {
200
+ "model_name": "Bio_ClinicalBERT",
201
+ "domain": "clinical_notes",
202
+ "result": {
203
+ "summary": "Analysis suggests normal baseline clinical parameters with minor metabolic considerations",
204
+ "confidence": 0.87
205
+ }
206
+ },
207
+ {
208
+ "model_name": "MedGemma 27B",
209
+ "domain": "general",
210
+ "result": {
211
+ "analysis": "Comprehensive medical review indicates overall satisfactory health status with attention to glucose and lipid management",
212
+ "confidence": 0.85
213
+ }
214
+ }
215
+ ]
216
+
217
+
218
+ async def test_ecg_synthesis():
219
+ """Test ECG synthesis - clinician and patient summaries"""
220
+ print("\n" + "="*80)
221
+ print("TEST 1: ECG SYNTHESIS")
222
+ print("="*80)
223
+
224
+ synthesis_service = get_synthesis_service()
225
+ ecg_data = create_sample_ecg_data()
226
+ model_outputs = create_sample_model_outputs()
227
+
228
+ # Test clinician summary
229
+ print("\n[1A] Clinician Summary - ECG")
230
+ print("-" * 80)
231
+ result = await synthesis_service.synthesize_clinical_summary(
232
+ modality="ECG",
233
+ structured_data=ecg_data,
234
+ model_outputs=model_outputs,
235
+ summary_type="clinician",
236
+ user_id="test-user-001"
237
+ )
238
+
239
+ print(f"Synthesis ID: {result['synthesis_id']}")
240
+ print(f"Risk Level: {result['risk_level']}")
241
+ print(f"Requires Review: {result['requires_review']}")
242
+ print(f"Overall Confidence: {result['confidence_scores']['overall_confidence']*100:.1f}%")
243
+ print(f"\nNarrative:\n{result['narrative'][:500]}...")
244
+ print(f"\nRecommendations: {len(result['recommendations'])} items")
245
+ for rec in result['recommendations'][:3]:
246
+ print(f" - [{rec['priority']}] {rec['recommendation']}")
247
+
248
+ # Test patient summary
249
+ print("\n[1B] Patient Summary - ECG")
250
+ print("-" * 80)
251
+ result_patient = await synthesis_service.synthesize_clinical_summary(
252
+ modality="ECG",
253
+ structured_data=ecg_data,
254
+ model_outputs=model_outputs,
255
+ summary_type="patient",
256
+ user_id="test-user-001"
257
+ )
258
+
259
+ print(f"Narrative:\n{result_patient['narrative'][:500]}...")
260
+
261
+ return True
262
+
263
+
264
+ async def test_radiology_synthesis():
265
+ """Test radiology synthesis"""
266
+ print("\n" + "="*80)
267
+ print("TEST 2: RADIOLOGY SYNTHESIS")
268
+ print("="*80)
269
+
270
+ synthesis_service = get_synthesis_service()
271
+ rad_data = create_sample_radiology_data()
272
+ model_outputs = create_sample_model_outputs()
273
+
274
+ # Test clinician summary
275
+ print("\n[2A] Clinician Summary - Radiology")
276
+ print("-" * 80)
277
+ result = await synthesis_service.synthesize_clinical_summary(
278
+ modality="radiology",
279
+ structured_data=rad_data,
280
+ model_outputs=model_outputs,
281
+ summary_type="clinician",
282
+ user_id="test-user-002"
283
+ )
284
+
285
+ print(f"Synthesis ID: {result['synthesis_id']}")
286
+ print(f"Risk Level: {result['risk_level']}")
287
+ print(f"Overall Confidence: {result['confidence_scores']['overall_confidence']*100:.1f}%")
288
+ print(f"\nNarrative:\n{result['narrative'][:500]}...")
289
+
290
+ return True
291
+
292
+
293
+ async def test_laboratory_synthesis():
294
+ """Test laboratory results synthesis"""
295
+ print("\n" + "="*80)
296
+ print("TEST 3: LABORATORY SYNTHESIS")
297
+ print("="*80)
298
+
299
+ synthesis_service = get_synthesis_service()
300
+ lab_data = create_sample_laboratory_data()
301
+ model_outputs = create_sample_model_outputs()
302
+
303
+ # Test clinician summary
304
+ print("\n[3A] Clinician Summary - Laboratory")
305
+ print("-" * 80)
306
+ result = await synthesis_service.synthesize_clinical_summary(
307
+ modality="laboratory",
308
+ structured_data=lab_data,
309
+ model_outputs=model_outputs,
310
+ summary_type="clinician",
311
+ user_id="test-user-003"
312
+ )
313
+
314
+ print(f"Synthesis ID: {result['synthesis_id']}")
315
+ print(f"Risk Level: {result['risk_level']}")
316
+ print(f"Abnormal Tests: {lab_data['abnormal_count']}")
317
+ print(f"Overall Confidence: {result['confidence_scores']['overall_confidence']*100:.1f}%")
318
+ print(f"\nNarrative:\n{result['narrative'][:500]}...")
319
+
320
+ # Test patient summary
321
+ print("\n[3B] Patient Summary - Laboratory")
322
+ print("-" * 80)
323
+ result_patient = await synthesis_service.synthesize_clinical_summary(
324
+ modality="laboratory",
325
+ structured_data=lab_data,
326
+ model_outputs=model_outputs,
327
+ summary_type="patient",
328
+ user_id="test-user-003"
329
+ )
330
+
331
+ print(f"Narrative:\n{result_patient['narrative'][:500]}...")
332
+
333
+ return True
334
+
335
+
336
+ async def test_multi_modal_synthesis():
337
+ """Test multi-modal synthesis combining multiple modalities"""
338
+ print("\n" + "="*80)
339
+ print("TEST 4: MULTI-MODAL SYNTHESIS")
340
+ print("="*80)
341
+
342
+ synthesis_service = get_synthesis_service()
343
+
344
+ modalities_data = {
345
+ "ECG": create_sample_ecg_data(),
346
+ "radiology": create_sample_radiology_data(),
347
+ "laboratory": create_sample_laboratory_data()
348
+ }
349
+
350
+ print("\n[4A] Multi-Modal Clinician Summary")
351
+ print("-" * 80)
352
+ result = await synthesis_service.synthesize_multi_modal(
353
+ modalities_data=modalities_data,
354
+ summary_type="clinician",
355
+ user_id="test-user-004"
356
+ )
357
+
358
+ print(f"Modalities Combined: {', '.join(result['modalities'])}")
359
+ print(f"Overall Confidence: {result['overall_confidence']*100:.1f}%")
360
+ print(f"Risk Level: {result['risk_level']}")
361
+ print(f"\nNarrative:\n{result['narrative'][:500]}...")
362
+ print(f"\nRecommendations: {len(result['recommendations'])} items")
363
+
364
+ return True
365
+
366
+
367
+ async def test_confidence_thresholds():
368
+ """Test confidence-based review requirements"""
369
+ print("\n" + "="*80)
370
+ print("TEST 5: CONFIDENCE THRESHOLD TESTING")
371
+ print("="*80)
372
+
373
+ synthesis_service = get_synthesis_service()
374
+
375
+ # Test high confidence (auto-approve)
376
+ high_conf_data = create_sample_ecg_data()
377
+ high_conf_data['confidence'] = {
378
+ "extraction_confidence": 0.95,
379
+ "model_confidence": 0.92,
380
+ "data_quality": 0.94
381
+ }
382
+
383
+ print("\n[5A] High Confidence Case (≥0.85)")
384
+ print("-" * 80)
385
+ result_high = await synthesis_service.synthesize_clinical_summary(
386
+ modality="ECG",
387
+ structured_data=high_conf_data,
388
+ model_outputs=[],
389
+ summary_type="clinician",
390
+ user_id="test-user-005"
391
+ )
392
+ print(f"Overall Confidence: {result_high['confidence_scores']['overall_confidence']*100:.1f}%")
393
+ print(f"Requires Review: {result_high['requires_review']}")
394
+ print(f"Expected: False (auto-approved)")
395
+
396
+ # Test moderate confidence (review required)
397
+ mod_conf_data = create_sample_ecg_data()
398
+ mod_conf_data['confidence'] = {
399
+ "extraction_confidence": 0.75,
400
+ "model_confidence": 0.72,
401
+ "data_quality": 0.78
402
+ }
403
+
404
+ print("\n[5B] Moderate Confidence Case (0.60-0.85)")
405
+ print("-" * 80)
406
+ result_mod = await synthesis_service.synthesize_clinical_summary(
407
+ modality="ECG",
408
+ structured_data=mod_conf_data,
409
+ model_outputs=[],
410
+ summary_type="clinician",
411
+ user_id="test-user-005"
412
+ )
413
+ print(f"Overall Confidence: {result_mod['confidence_scores']['overall_confidence']*100:.1f}%")
414
+ print(f"Requires Review: {result_mod['requires_review']}")
415
+ print(f"Expected: True (review required)")
416
+
417
+ # Test low confidence (manual review required)
418
+ low_conf_data = create_sample_ecg_data()
419
+ low_conf_data['confidence'] = {
420
+ "extraction_confidence": 0.55,
421
+ "model_confidence": 0.50,
422
+ "data_quality": 0.58
423
+ }
424
+
425
+ print("\n[5C] Low Confidence Case (<0.60)")
426
+ print("-" * 80)
427
+ result_low = await synthesis_service.synthesize_clinical_summary(
428
+ modality="ECG",
429
+ structured_data=low_conf_data,
430
+ model_outputs=[],
431
+ summary_type="clinician",
432
+ user_id="test-user-005"
433
+ )
434
+ print(f"Overall Confidence: {result_low['confidence_scores']['overall_confidence']*100:.1f}%")
435
+ print(f"Requires Review: {result_low['requires_review']}")
436
+ print(f"Risk Level: {result_low['risk_level']}")
437
+ print(f"Expected: True (manual review required), Risk: high")
438
+
439
+ return True
440
+
441
+
442
+ async def test_synthesis_statistics():
443
+ """Test synthesis service statistics tracking"""
444
+ print("\n" + "="*80)
445
+ print("TEST 6: SYNTHESIS STATISTICS")
446
+ print("="*80)
447
+
448
+ synthesis_service = get_synthesis_service()
449
+
450
+ stats = synthesis_service.get_synthesis_statistics()
451
+
452
+ print(f"\nTotal Syntheses: {stats['total_syntheses']}")
453
+ print(f"Average Confidence: {stats['average_confidence']*100:.1f}%")
454
+ print(f"Review Required: {stats['review_required_percentage']:.1f}%")
455
+ print(f"Average Generation Time: {stats['average_generation_time']:.2f} seconds")
456
+
457
+ if stats['by_modality']:
458
+ print(f"\nBy Modality:")
459
+ for modality, count in stats['by_modality'].items():
460
+ print(f" - {modality}: {count}")
461
+
462
+ if stats['by_risk_level']:
463
+ print(f"\nBy Risk Level:")
464
+ for risk, count in stats['by_risk_level'].items():
465
+ print(f" - {risk}: {count}")
466
+
467
+ return True
468
+
469
+
470
+ async def run_all_tests():
471
+ """Run all synthesis service tests"""
472
+ print("\n" + "="*80)
473
+ print("MEDICAL SYNTHESIS SERVICE - COMPREHENSIVE TEST SUITE")
474
+ print("Testing MedGemma Prompt Templates & Clinical Synthesis")
475
+ print("="*80)
476
+ print(f"Start Time: {datetime.utcnow().isoformat()}")
477
+
478
+ tests = [
479
+ ("ECG Synthesis", test_ecg_synthesis),
480
+ ("Radiology Synthesis", test_radiology_synthesis),
481
+ ("Laboratory Synthesis", test_laboratory_synthesis),
482
+ ("Multi-Modal Synthesis", test_multi_modal_synthesis),
483
+ ("Confidence Thresholds", test_confidence_thresholds),
484
+ ("Synthesis Statistics", test_synthesis_statistics)
485
+ ]
486
+
487
+ results = []
488
+
489
+ for test_name, test_func in tests:
490
+ try:
491
+ success = await test_func()
492
+ results.append((test_name, "PASS" if success else "FAIL"))
493
+ except Exception as e:
494
+ print(f"\n[ERROR] {test_name} failed: {str(e)}")
495
+ import traceback
496
+ traceback.print_exc()
497
+ results.append((test_name, "FAIL"))
498
+
499
+ # Print summary
500
+ print("\n" + "="*80)
501
+ print("TEST SUMMARY")
502
+ print("="*80)
503
+ for test_name, status in results:
504
+ status_symbol = "✓" if status == "PASS" else "✗"
505
+ print(f"{status_symbol} {test_name}: {status}")
506
+
507
+ passed = sum(1 for _, status in results if status == "PASS")
508
+ total = len(results)
509
+ print(f"\nTotal: {passed}/{total} tests passed ({passed/total*100:.1f}%)")
510
+ print(f"End Time: {datetime.utcnow().isoformat()}")
511
+ print("="*80)
512
+
513
+
514
+ if __name__ == "__main__":
515
+ asyncio.run(run_all_tests())