frabbani commited on
Commit
dc3f8a9
·
1 Parent(s): 6b833a6

Fix fact extraction - pass raw data for simple tools......

Browse files
Files changed (3) hide show
  1. report_generator.py +103 -6
  2. server.py +77 -5
  3. static/index.html +45 -0
report_generator.py CHANGED
@@ -51,6 +51,12 @@ class PreVisitReport:
51
  current_medications: List[str] = field(default_factory=list)
52
  recent_vitals: Dict[str, str] = field(default_factory=dict)
53
 
 
 
 
 
 
 
54
  # Attachments
55
  attachments: List[Dict] = field(default_factory=list)
56
 
@@ -69,6 +75,10 @@ class PreVisitReport:
69
  "relevant_conditions": self.relevant_conditions,
70
  "current_medications": self.current_medications,
71
  "recent_vitals": self.recent_vitals,
 
 
 
 
72
  "attachments": self.attachments,
73
  "key_quotes": self.key_quotes
74
  }
@@ -97,7 +107,11 @@ async def generate_report(
97
  patient_info: Dict,
98
  conversation_history: List[Dict],
99
  tool_results: List[Dict],
100
- attachments: List[Dict] = None
 
 
 
 
101
  ) -> PreVisitReport:
102
  """
103
  Generate a pre-visit report from conversation and medical data.
@@ -107,6 +121,10 @@ async def generate_report(
107
  conversation_history: List of {"role": "user"|"assistant", "content": "..."}
108
  tool_results: List of {"tool": "...", "facts": "..."}
109
  attachments: List of {"type": "audio"|"chart", "title": "...", "summary": "..."}
 
 
 
 
110
 
111
  Returns:
112
  PreVisitReport object
@@ -207,6 +225,10 @@ Output ONLY the JSON, no other text.
207
  relevant_conditions=report_data.get('relevant_conditions', []),
208
  current_medications=report_data.get('current_medications', []),
209
  recent_vitals=report_data.get('recent_vitals', {}),
 
 
 
 
210
  attachments=attachments or [],
211
  key_quotes=report_data.get('key_quotes', [])
212
  )
@@ -242,10 +264,64 @@ def format_report_html(report: PreVisitReport) -> str:
242
  </div>
243
  """
244
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
  html = f"""
246
  <div class="previsit-report">
247
  <div class="report-header">
248
- <h2>Pre-Visit Summary</h2>
249
  <div class="report-meta">
250
  <div class="patient-info">
251
  <strong>{report.patient_name}</strong> · {report.patient_age}y · {report.patient_gender}
@@ -255,7 +331,7 @@ def format_report_html(report: PreVisitReport) -> str:
255
  </div>
256
 
257
  <div class="report-section">
258
- <h3>📋 Chief Concerns</h3>
259
  <ul class="concerns-list">{concerns_html}</ul>
260
  </div>
261
 
@@ -271,20 +347,41 @@ def format_report_html(report: PreVisitReport) -> str:
271
 
272
  <div class="report-columns">
273
  <div class="report-section half">
274
- <h3>📁 Relevant Conditions</h3>
275
  <ul>{conditions_html}</ul>
276
  </div>
277
  <div class="report-section half">
278
- <h3>💊 Current Medications</h3>
279
- <ul>{medications_html}</ul>
280
  </div>
281
  </div>
282
 
 
 
 
 
 
283
  {f'''<div class="report-section">
284
  <h3>📊 Recent Vitals</h3>
285
  <div class="vitals-grid">{vitals_html}</div>
286
  </div>''' if report.recent_vitals else ""}
287
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  {f'''<div class="report-section">
289
  <h3>💬 Patient Quotes</h3>
290
  {quotes_html}
 
51
  current_medications: List[str] = field(default_factory=list)
52
  recent_vitals: Dict[str, str] = field(default_factory=dict)
53
 
54
+ # NEW: Additional medical history
55
+ immunizations: List[Dict] = field(default_factory=list)
56
+ procedures: List[Dict] = field(default_factory=list)
57
+ recent_encounters: List[Dict] = field(default_factory=list)
58
+ allergies: List[Dict] = field(default_factory=list)
59
+
60
  # Attachments
61
  attachments: List[Dict] = field(default_factory=list)
62
 
 
75
  "relevant_conditions": self.relevant_conditions,
76
  "current_medications": self.current_medications,
77
  "recent_vitals": self.recent_vitals,
78
+ "immunizations": self.immunizations,
79
+ "procedures": self.procedures,
80
+ "recent_encounters": self.recent_encounters,
81
+ "allergies": self.allergies,
82
  "attachments": self.attachments,
83
  "key_quotes": self.key_quotes
84
  }
 
107
  patient_info: Dict,
108
  conversation_history: List[Dict],
109
  tool_results: List[Dict],
110
+ attachments: List[Dict] = None,
111
+ immunizations: List[Dict] = None,
112
+ procedures: List[Dict] = None,
113
+ encounters: List[Dict] = None,
114
+ allergies: List[Dict] = None
115
  ) -> PreVisitReport:
116
  """
117
  Generate a pre-visit report from conversation and medical data.
 
121
  conversation_history: List of {"role": "user"|"assistant", "content": "..."}
122
  tool_results: List of {"tool": "...", "facts": "..."}
123
  attachments: List of {"type": "audio"|"chart", "title": "...", "summary": "..."}
124
+ immunizations: List of immunization records from DB
125
+ procedures: List of procedure records from DB
126
+ encounters: List of encounter records from DB
127
+ allergies: List of allergy records from DB
128
 
129
  Returns:
130
  PreVisitReport object
 
225
  relevant_conditions=report_data.get('relevant_conditions', []),
226
  current_medications=report_data.get('current_medications', []),
227
  recent_vitals=report_data.get('recent_vitals', {}),
228
+ immunizations=immunizations or [],
229
+ procedures=procedures or [],
230
+ recent_encounters=encounters or [],
231
+ allergies=allergies or [],
232
  attachments=attachments or [],
233
  key_quotes=report_data.get('key_quotes', [])
234
  )
 
264
  </div>
265
  """
266
 
267
+ # NEW: Format immunizations
268
+ immunizations_html = ""
269
+ if report.immunizations:
270
+ for imm in report.immunizations[:8]: # Limit to 8 most recent
271
+ vaccine_name = imm.get('vaccine_display', 'Unknown vaccine')
272
+ # Truncate long vaccine names
273
+ if len(vaccine_name) > 40:
274
+ vaccine_name = vaccine_name[:37] + "..."
275
+ date = imm.get('occurrence_date', '')[:10] if imm.get('occurrence_date') else 'Unknown'
276
+ immunizations_html += f"<li>{vaccine_name} <span class='date-badge'>({date})</span></li>"
277
+ else:
278
+ immunizations_html = "<li>No immunization records</li>"
279
+
280
+ # NEW: Format procedures (surgical history)
281
+ procedures_html = ""
282
+ if report.procedures:
283
+ for proc in report.procedures[:6]: # Limit to 6 most recent
284
+ proc_name = proc.get('display', 'Unknown procedure')
285
+ if len(proc_name) > 45:
286
+ proc_name = proc_name[:42] + "..."
287
+ date = proc.get('performed_date', '')[:10] if proc.get('performed_date') else 'Unknown'
288
+ procedures_html += f"<li>{proc_name} <span class='date-badge'>({date})</span></li>"
289
+ else:
290
+ procedures_html = "<li>No surgical history</li>"
291
+
292
+ # NEW: Format recent encounters
293
+ encounters_html = ""
294
+ if report.recent_encounters:
295
+ for enc in report.recent_encounters[:5]: # Limit to 5 most recent
296
+ enc_type = enc.get('type_display', enc.get('class_display', 'Visit'))
297
+ if len(enc_type) > 35:
298
+ enc_type = enc_type[:32] + "..."
299
+ reason = enc.get('reason_display', '')
300
+ if reason and len(reason) > 30:
301
+ reason = reason[:27] + "..."
302
+ date = enc.get('period_start', '')[:10] if enc.get('period_start') else 'Unknown'
303
+ reason_text = f" - {reason}" if reason else ""
304
+ encounters_html += f"<li>{enc_type}{reason_text} <span class='date-badge'>({date})</span></li>"
305
+ else:
306
+ encounters_html = "<li>No recent encounters</li>"
307
+
308
+ # NEW: Format allergies with severity
309
+ allergies_html = ""
310
+ if report.allergies:
311
+ for allergy in report.allergies:
312
+ substance = allergy.get('substance', 'Unknown')
313
+ reaction = allergy.get('reaction', allergy.get('reaction_display', ''))
314
+ criticality = allergy.get('criticality', '')
315
+ severity_class = 'allergy-high' if criticality == 'high' else 'allergy-low'
316
+ reaction_text = f" → {reaction}" if reaction else ""
317
+ allergies_html += f"<li class='{severity_class}'><strong>{substance}</strong>{reaction_text}</li>"
318
+ else:
319
+ allergies_html = "<li>No known allergies</li>"
320
+
321
  html = f"""
322
  <div class="previsit-report">
323
  <div class="report-header">
324
+ <h2>📋 Pre-Visit Summary</h2>
325
  <div class="report-meta">
326
  <div class="patient-info">
327
  <strong>{report.patient_name}</strong> · {report.patient_age}y · {report.patient_gender}
 
331
  </div>
332
 
333
  <div class="report-section">
334
+ <h3>🎯 Chief Concerns</h3>
335
  <ul class="concerns-list">{concerns_html}</ul>
336
  </div>
337
 
 
347
 
348
  <div class="report-columns">
349
  <div class="report-section half">
350
+ <h3>📁 Active Conditions</h3>
351
  <ul>{conditions_html}</ul>
352
  </div>
353
  <div class="report-section half">
354
+ <h3>⚠️ Allergies</h3>
355
+ <ul class="allergies-list">{allergies_html}</ul>
356
  </div>
357
  </div>
358
 
359
+ <div class="report-section">
360
+ <h3>💊 Current Medications</h3>
361
+ <ul>{medications_html}</ul>
362
+ </div>
363
+
364
  {f'''<div class="report-section">
365
  <h3>📊 Recent Vitals</h3>
366
  <div class="vitals-grid">{vitals_html}</div>
367
  </div>''' if report.recent_vitals else ""}
368
 
369
+ <div class="report-columns">
370
+ <div class="report-section half">
371
+ <h3>💉 Immunizations</h3>
372
+ <ul class="compact-list">{immunizations_html}</ul>
373
+ </div>
374
+ <div class="report-section half">
375
+ <h3>🏥 Surgical History</h3>
376
+ <ul class="compact-list">{procedures_html}</ul>
377
+ </div>
378
+ </div>
379
+
380
+ <div class="report-section">
381
+ <h3>📅 Recent Encounters</h3>
382
+ <ul class="compact-list">{encounters_html}</ul>
383
+ </div>
384
+
385
  {f'''<div class="report-section">
386
  <h3>💬 Patient Quotes</h3>
387
  {quotes_html}
server.py CHANGED
@@ -167,13 +167,43 @@ async def get_encounters(patient_id: str, limit: int = 10):
167
  conn = get_db()
168
  try:
169
  cursor = conn.execute("""
170
- SELECT * FROM encounters WHERE patient_id = ?
171
- ORDER BY start_date DESC LIMIT ?
 
 
172
  """, (patient_id, limit))
173
  encounters = [dict_from_row(row) for row in cursor.fetchall()]
174
  return {"encounters": encounters}
175
  finally:
176
  conn.close()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  # ============================================================================
178
  # LLM Integration
179
  # ============================================================================
@@ -543,9 +573,11 @@ async def generate_report_endpoint(request: ReportRequest):
543
  conn = get_db()
544
  cursor = conn.execute("SELECT * FROM patients WHERE id = ?", (request.patient_id,))
545
  patient = cursor.fetchone()
546
- conn.close()
547
  if not patient:
 
548
  raise HTTPException(status_code=404, detail="Patient not found")
 
549
  from datetime import datetime
550
  birth = datetime.strptime(patient["birth_date"], "%Y-%m-%d")
551
  age = (datetime.now() - birth).days // 365
@@ -554,13 +586,53 @@ async def generate_report_endpoint(request: ReportRequest):
554
  "age": age,
555
  "gender": patient['gender']
556
  }
557
- # Generate report
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
558
  report = await generate_report(
559
  patient_info=patient_info,
560
  conversation_history=request.conversation,
561
  tool_results=request.tool_results,
562
- attachments=request.attachments
 
 
 
 
563
  )
 
564
  # Return both structured data and HTML
565
  return {
566
  "success": True,
 
167
  conn = get_db()
168
  try:
169
  cursor = conn.execute("""
170
+ SELECT id, status, class_code, class_display, type_code, type_display,
171
+ reason_code, reason_display, period_start, period_end
172
+ FROM encounters WHERE patient_id = ?
173
+ ORDER BY period_start DESC LIMIT ?
174
  """, (patient_id, limit))
175
  encounters = [dict_from_row(row) for row in cursor.fetchall()]
176
  return {"encounters": encounters}
177
  finally:
178
  conn.close()
179
+
180
+ @app.get("/api/patients/{patient_id}/immunizations")
181
+ async def get_immunizations(patient_id: str):
182
+ conn = get_db()
183
+ try:
184
+ cursor = conn.execute("""
185
+ SELECT id, vaccine_code, vaccine_display, status, occurrence_date
186
+ FROM immunizations WHERE patient_id = ?
187
+ ORDER BY occurrence_date DESC
188
+ """, (patient_id,))
189
+ immunizations = [dict_from_row(row) for row in cursor.fetchall()]
190
+ return {"immunizations": immunizations}
191
+ finally:
192
+ conn.close()
193
+
194
+ @app.get("/api/patients/{patient_id}/procedures")
195
+ async def get_procedures(patient_id: str):
196
+ conn = get_db()
197
+ try:
198
+ cursor = conn.execute("""
199
+ SELECT id, code, display, status, performed_date
200
+ FROM procedures WHERE patient_id = ?
201
+ ORDER BY performed_date DESC
202
+ """, (patient_id,))
203
+ procedures = [dict_from_row(row) for row in cursor.fetchall()]
204
+ return {"procedures": procedures}
205
+ finally:
206
+ conn.close()
207
  # ============================================================================
208
  # LLM Integration
209
  # ============================================================================
 
573
  conn = get_db()
574
  cursor = conn.execute("SELECT * FROM patients WHERE id = ?", (request.patient_id,))
575
  patient = cursor.fetchone()
576
+
577
  if not patient:
578
+ conn.close()
579
  raise HTTPException(status_code=404, detail="Patient not found")
580
+
581
  from datetime import datetime
582
  birth = datetime.strptime(patient["birth_date"], "%Y-%m-%d")
583
  age = (datetime.now() - birth).days // 365
 
586
  "age": age,
587
  "gender": patient['gender']
588
  }
589
+
590
+ # Fetch immunizations
591
+ cursor = conn.execute("""
592
+ SELECT id, vaccine_code, vaccine_display, status, occurrence_date
593
+ FROM immunizations WHERE patient_id = ?
594
+ ORDER BY occurrence_date DESC
595
+ """, (request.patient_id,))
596
+ immunizations = [dict_from_row(row) for row in cursor.fetchall()]
597
+
598
+ # Fetch procedures (surgical history)
599
+ cursor = conn.execute("""
600
+ SELECT id, code, display, status, performed_date
601
+ FROM procedures WHERE patient_id = ?
602
+ ORDER BY performed_date DESC
603
+ """, (request.patient_id,))
604
+ procedures = [dict_from_row(row) for row in cursor.fetchall()]
605
+
606
+ # Fetch recent encounters
607
+ cursor = conn.execute("""
608
+ SELECT id, status, class_code, class_display, type_code, type_display,
609
+ reason_code, reason_display, period_start, period_end
610
+ FROM encounters WHERE patient_id = ?
611
+ ORDER BY period_start DESC LIMIT 10
612
+ """, (request.patient_id,))
613
+ encounters = [dict_from_row(row) for row in cursor.fetchall()]
614
+
615
+ # Fetch allergies
616
+ cursor = conn.execute("""
617
+ SELECT id, substance, reaction_display as reaction, criticality, category
618
+ FROM allergies WHERE patient_id = ?
619
+ """, (request.patient_id,))
620
+ allergies = [dict_from_row(row) for row in cursor.fetchall()]
621
+
622
+ conn.close()
623
+
624
+ # Generate report with all data
625
  report = await generate_report(
626
  patient_info=patient_info,
627
  conversation_history=request.conversation,
628
  tool_results=request.tool_results,
629
+ attachments=request.attachments,
630
+ immunizations=immunizations,
631
+ procedures=procedures,
632
+ encounters=encounters,
633
+ allergies=allergies
634
  )
635
+
636
  # Return both structured data and HTML
637
  return {
638
  "success": True,
static/index.html CHANGED
@@ -929,6 +929,45 @@
929
  color: var(--text-muted);
930
  text-align: center;
931
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
932
  </style>
933
  </head>
934
  <body>
@@ -1966,6 +2005,12 @@
1966
  .pdf-report .report-section.half { flex: 1; }
1967
  .pdf-report .patient-info { color: #000 !important; }
1968
  .pdf-report .report-date { color: #333 !important; }
 
 
 
 
 
 
1969
  </style>
1970
  <div class="pdf-report">
1971
  ${currentReport.html}
 
929
  color: var(--text-muted);
930
  text-align: center;
931
  }
932
+
933
+ /* NEW: Compact list styling for immunizations/procedures/encounters */
934
+ .previsit-report .compact-list {
935
+ font-size: 12px;
936
+ margin: 0;
937
+ padding-left: 16px;
938
+ }
939
+
940
+ .previsit-report .compact-list li {
941
+ margin: 3px 0;
942
+ line-height: 1.4;
943
+ }
944
+
945
+ .previsit-report .date-badge {
946
+ color: var(--text-muted);
947
+ font-size: 11px;
948
+ }
949
+
950
+ /* Allergy styling with severity indicators */
951
+ .previsit-report .allergies-list {
952
+ margin: 0;
953
+ padding-left: 16px;
954
+ }
955
+
956
+ .previsit-report .allergies-list li {
957
+ margin: 4px 0;
958
+ }
959
+
960
+ .previsit-report .allergies-list .allergy-high {
961
+ color: #ff6b6b;
962
+ }
963
+
964
+ .previsit-report .allergies-list .allergy-high strong {
965
+ color: #ff4757;
966
+ }
967
+
968
+ .previsit-report .allergies-list .allergy-low {
969
+ color: var(--text-main);
970
+ }
971
  </style>
972
  </head>
973
  <body>
 
2005
  .pdf-report .report-section.half { flex: 1; }
2006
  .pdf-report .patient-info { color: #000 !important; }
2007
  .pdf-report .report-date { color: #333 !important; }
2008
+ .pdf-report .compact-list { font-size: 12px; padding-left: 16px; }
2009
+ .pdf-report .compact-list li { margin: 3px 0; }
2010
+ .pdf-report .date-badge { color: #666 !important; font-size: 11px; }
2011
+ .pdf-report .allergies-list .allergy-high { color: #c0392b !important; }
2012
+ .pdf-report .allergies-list .allergy-high strong { color: #c0392b !important; }
2013
+ .pdf-report .allergies-list .allergy-low { color: #000 !important; }
2014
  </style>
2015
  <div class="pdf-report">
2016
  ${currentReport.html}