LiamKhoaLe commited on
Commit
fc4a3e4
·
1 Parent(s): 458cb55

Upd gitignore

Browse files
.dockerignore CHANGED
@@ -4,3 +4,4 @@
4
  .venv
5
  __pycache__
6
  *.pyc
 
 
4
  .venv
5
  __pycache__
6
  *.pyc
7
+ key
.gitignore CHANGED
@@ -9,4 +9,5 @@
9
  **/.env
10
  **.DS_STORE
11
 
12
- review_vi.txt
 
 
9
  **/.env
10
  **.DS_STORE
11
 
12
+ review_vi.txt
13
+ key
src/emr/repositories/emr.py CHANGED
@@ -24,8 +24,12 @@ def create():
24
  collection.create_index("patient_id")
25
  collection.create_index("doctor_id")
26
  collection.create_index("session_id")
 
27
  collection.create_index("created_at")
28
  collection.create_index([("patient_id", ASCENDING), ("created_at", DESCENDING)])
 
 
 
29
  logger().info("EMR collection created successfully with indexes")
30
  except Exception as e:
31
  logger().error(f"Error creating EMR collection: {e}")
@@ -36,6 +40,13 @@ def create_emr_entry(emr_data: EMRCreateRequest, embeddings: List[float]) -> str
36
  """Create a new EMR entry in the database."""
37
  try:
38
  collection = get_collection(EMR_COLLECTION)
 
 
 
 
 
 
 
39
  now = datetime.now(timezone.utc)
40
 
41
  doc = {
@@ -223,6 +234,17 @@ def delete_emr_entry(emr_id: str) -> bool:
223
  return False
224
 
225
 
 
 
 
 
 
 
 
 
 
 
 
226
  def get_emr_statistics(patient_id: str) -> Dict[str, Any]:
227
  """Get EMR statistics for a patient."""
228
  try:
 
24
  collection.create_index("patient_id")
25
  collection.create_index("doctor_id")
26
  collection.create_index("session_id")
27
+ collection.create_index("message_id")
28
  collection.create_index("created_at")
29
  collection.create_index([("patient_id", ASCENDING), ("created_at", DESCENDING)])
30
+ collection.create_index([("patient_id", ASCENDING), ("doctor_id", ASCENDING)])
31
+ collection.create_index([("session_id", ASCENDING), ("created_at", DESCENDING)])
32
+ collection.create_index("confidence_score")
33
  logger().info("EMR collection created successfully with indexes")
34
  except Exception as e:
35
  logger().error(f"Error creating EMR collection: {e}")
 
40
  """Create a new EMR entry in the database."""
41
  try:
42
  collection = get_collection(EMR_COLLECTION)
43
+
44
+ # Check if EMR entry already exists for this message
45
+ existing = collection.find_one({"message_id": emr_data.message_id})
46
+ if existing:
47
+ logger().warning(f"EMR entry already exists for message {emr_data.message_id}")
48
+ return str(existing["_id"])
49
+
50
  now = datetime.now(timezone.utc)
51
 
52
  doc = {
 
234
  return False
235
 
236
 
237
+ def check_emr_exists(message_id: str) -> bool:
238
+ """Check if an EMR entry already exists for a message."""
239
+ try:
240
+ collection = get_collection(EMR_COLLECTION)
241
+ existing = collection.find_one({"message_id": message_id})
242
+ return existing is not None
243
+ except Exception as e:
244
+ logger().error(f"Error checking EMR existence: {e}")
245
+ return False
246
+
247
+
248
  def get_emr_statistics(patient_id: str) -> Dict[str, Any]:
249
  """Get EMR statistics for a patient."""
250
  try:
src/emr/routes/emr.py CHANGED
@@ -1,5 +1,6 @@
1
  # emr/routes/emr.py
2
 
 
3
  from typing import List, Optional
4
 
5
  from fastapi import APIRouter, HTTPException, Depends
@@ -12,6 +13,44 @@ from src.utils.logger import logger
12
  router = APIRouter(prefix="/emr", tags=["EMR"])
13
 
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  def get_emr_service(state: MedicalState = Depends(get_state)) -> EMRService:
16
  """Get EMR service instance."""
17
  return EMRService(state.gemini_rotator, state.embedding_client)
@@ -28,6 +67,18 @@ async def extract_emr_from_message(
28
  ):
29
  """Extract and store EMR data from a chat message."""
30
  try:
 
 
 
 
 
 
 
 
 
 
 
 
31
  logger().info(f"EMR extraction requested for patient {patient_id}, message {message_id}")
32
 
33
  # Get patient context if available
@@ -192,3 +243,84 @@ async def get_emr_statistics(
192
  except Exception as e:
193
  logger().error(f"Error getting EMR statistics: {e}")
194
  raise HTTPException(status_code=500, detail=str(e))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # emr/routes/emr.py
2
 
3
+ from datetime import datetime, timezone
4
  from typing import List, Optional
5
 
6
  from fastapi import APIRouter, HTTPException, Depends
 
13
  router = APIRouter(prefix="/emr", tags=["EMR"])
14
 
15
 
16
+ @router.get("/check/{message_id}", response_model=dict)
17
+ async def check_emr_exists(message_id: str):
18
+ """Check if EMR extraction has already been done for a message."""
19
+ try:
20
+ from src.emr.repositories.emr import check_emr_exists
21
+ exists = check_emr_exists(message_id)
22
+ return {
23
+ "message_id": message_id,
24
+ "emr_exists": exists
25
+ }
26
+ except Exception as e:
27
+ logger().error(f"Error checking EMR existence: {e}")
28
+ raise HTTPException(status_code=500, detail=str(e))
29
+
30
+
31
+ @router.get("/health", response_model=dict)
32
+ async def emr_health_check():
33
+ """Health check endpoint for EMR service."""
34
+ try:
35
+ from src.data.connection import get_collection
36
+ collection = get_collection("emr")
37
+ # Try to count documents to verify collection exists and is accessible
38
+ count = collection.count_documents({})
39
+ return {
40
+ "status": "healthy",
41
+ "collection": "emr",
42
+ "document_count": count,
43
+ "timestamp": datetime.now(timezone.utc).isoformat()
44
+ }
45
+ except Exception as e:
46
+ logger().error(f"EMR health check failed: {e}")
47
+ return {
48
+ "status": "unhealthy",
49
+ "error": str(e),
50
+ "timestamp": datetime.now(timezone.utc).isoformat()
51
+ }
52
+
53
+
54
  def get_emr_service(state: MedicalState = Depends(get_state)) -> EMRService:
55
  """Get EMR service instance."""
56
  return EMRService(state.gemini_rotator, state.embedding_client)
 
67
  ):
68
  """Extract and store EMR data from a chat message."""
69
  try:
70
+ # Input validation
71
+ if not patient_id or not patient_id.strip():
72
+ raise HTTPException(status_code=400, detail="Patient ID is required")
73
+ if not doctor_id or not doctor_id.strip():
74
+ raise HTTPException(status_code=400, detail="Doctor ID is required")
75
+ if not message_id or not message_id.strip():
76
+ raise HTTPException(status_code=400, detail="Message ID is required")
77
+ if not session_id or not session_id.strip():
78
+ raise HTTPException(status_code=400, detail="Session ID is required")
79
+ if not message or not message.strip():
80
+ raise HTTPException(status_code=400, detail="Message content is required")
81
+
82
  logger().info(f"EMR extraction requested for patient {patient_id}, message {message_id}")
83
 
84
  # Get patient context if available
 
243
  except Exception as e:
244
  logger().error(f"Error getting EMR statistics: {e}")
245
  raise HTTPException(status_code=500, detail=str(e))
246
+
247
+
248
+ @router.post("/bulk-extract", response_model=dict)
249
+ async def bulk_extract_emr(
250
+ extractions: List[dict],
251
+ emr_service: EMRService = Depends(get_emr_service)
252
+ ):
253
+ """Extract EMR data from multiple messages in bulk."""
254
+ try:
255
+ if not extractions or len(extractions) == 0:
256
+ raise HTTPException(status_code=400, detail="No extractions provided")
257
+
258
+ if len(extractions) > 10:
259
+ raise HTTPException(status_code=400, detail="Maximum 10 extractions allowed per request")
260
+
261
+ logger().info(f"Bulk EMR extraction requested for {len(extractions)} messages")
262
+
263
+ results = []
264
+ errors = []
265
+
266
+ for i, extraction in enumerate(extractions):
267
+ try:
268
+ # Validate required fields
269
+ required_fields = ['patient_id', 'doctor_id', 'message_id', 'session_id', 'message']
270
+ for field in required_fields:
271
+ if field not in extraction or not extraction[field]:
272
+ raise ValueError(f"Missing or empty {field}")
273
+
274
+ # Get patient context
275
+ patient_context = None
276
+ try:
277
+ from src.data.repositories.patient import get_patient_by_id
278
+ patient = get_patient_by_id(extraction['patient_id'])
279
+ if patient:
280
+ patient_context = {
281
+ "name": patient.get("name"),
282
+ "age": patient.get("age"),
283
+ "sex": patient.get("sex"),
284
+ "medications": patient.get("medications", []),
285
+ "past_assessment_summary": patient.get("past_assessment_summary")
286
+ }
287
+ except Exception as e:
288
+ logger().warning(f"Could not fetch patient context for extraction {i}: {e}")
289
+
290
+ # Extract and store EMR data
291
+ emr_id = await emr_service.extract_and_store_emr(
292
+ patient_id=extraction['patient_id'],
293
+ doctor_id=extraction['doctor_id'],
294
+ message_id=extraction['message_id'],
295
+ session_id=extraction['session_id'],
296
+ message=extraction['message'],
297
+ patient_context=patient_context
298
+ )
299
+
300
+ results.append({
301
+ "index": i,
302
+ "message_id": extraction['message_id'],
303
+ "emr_id": emr_id,
304
+ "status": "success"
305
+ })
306
+
307
+ except Exception as e:
308
+ logger().error(f"Error in bulk extraction {i}: {e}")
309
+ errors.append({
310
+ "index": i,
311
+ "message_id": extraction.get('message_id', 'unknown'),
312
+ "error": str(e),
313
+ "status": "failed"
314
+ })
315
+
316
+ return {
317
+ "message": f"Bulk extraction completed. {len(results)} successful, {len(errors)} failed.",
318
+ "results": results,
319
+ "errors": errors
320
+ }
321
+
322
+ except HTTPException:
323
+ raise
324
+ except Exception as e:
325
+ logger().error(f"Error in bulk EMR extraction: {e}")
326
+ raise HTTPException(status_code=500, detail=str(e))
src/main.py CHANGED
@@ -86,7 +86,7 @@ def startup_event(state: MedicalState):
86
  #session_repo.create()
87
  #message_repo.create()
88
  #medical_repo.create()
89
- #emr_repo.create()
90
 
91
  def shutdown_event():
92
  """Cleanup on shutdown"""
 
86
  #session_repo.create()
87
  #message_repo.create()
88
  #medical_repo.create()
89
+ emr_repo.create()
90
 
91
  def shutdown_event():
92
  """Cleanup on shutdown"""
static/js/app.js CHANGED
@@ -1816,7 +1816,7 @@ How can I assist you today?`;
1816
  // Add EMR icon for assistant messages (system-generated)
1817
  const emrIcon = message.role === 'assistant' ?
1818
  `<div class="message-actions">
1819
- <button class="emr-extract-btn" onclick="app.extractEMR('${message.id}')" title="Extract to EMR">
1820
  <i class="fas fa-file-medical"></i>
1821
  </button>
1822
  </div>` : '';
@@ -1831,6 +1831,11 @@ How can I assist you today?`;
1831
  chatMessages.appendChild(messageElement);
1832
  chatMessages.scrollTop = chatMessages.scrollHeight;
1833
  if (this.currentSession) this.currentSession.lastActivity = new Date().toISOString();
 
 
 
 
 
1834
  }
1835
 
1836
  formatMessageContent(content) {
@@ -1925,7 +1930,9 @@ How can I assist you today?`;
1925
  // Show notification
1926
  this.showNotification('EMR data extracted successfully!', 'success');
1927
  } else {
1928
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
 
 
1929
  }
1930
 
1931
  } catch (error) {
@@ -1943,6 +1950,24 @@ How can I assist you today?`;
1943
  }
1944
  }
1945
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1946
  showNotification(message, type = 'info') {
1947
  // Create notification element
1948
  const notification = document.createElement('div');
 
1816
  // Add EMR icon for assistant messages (system-generated)
1817
  const emrIcon = message.role === 'assistant' ?
1818
  `<div class="message-actions">
1819
+ <button class="emr-extract-btn" onclick="app.extractEMR('${message.id}')" title="Extract to EMR" data-message-id="${message.id}">
1820
  <i class="fas fa-file-medical"></i>
1821
  </button>
1822
  </div>` : '';
 
1831
  chatMessages.appendChild(messageElement);
1832
  chatMessages.scrollTop = chatMessages.scrollHeight;
1833
  if (this.currentSession) this.currentSession.lastActivity = new Date().toISOString();
1834
+
1835
+ // Check EMR status for assistant messages
1836
+ if (message.role === 'assistant' && this.currentPatientId) {
1837
+ this.checkEMRStatus(message.id);
1838
+ }
1839
  }
1840
 
1841
  formatMessageContent(content) {
 
1930
  // Show notification
1931
  this.showNotification('EMR data extracted successfully!', 'success');
1932
  } else {
1933
+ const errorData = await response.json().catch(() => ({ detail: 'Unknown error' }));
1934
+ const errorMessage = errorData.detail || `HTTP ${response.status}: ${response.statusText}`;
1935
+ throw new Error(errorMessage);
1936
  }
1937
 
1938
  } catch (error) {
 
1950
  }
1951
  }
1952
 
1953
+ async checkEMRStatus(messageId) {
1954
+ try {
1955
+ const response = await fetch(`/emr/check/${messageId}`);
1956
+ if (response.ok) {
1957
+ const result = await response.json();
1958
+ const button = document.querySelector(`[data-message-id="${messageId}"]`);
1959
+ if (button && result.emr_exists) {
1960
+ button.innerHTML = '<i class="fas fa-check"></i>';
1961
+ button.style.color = 'var(--success-color)';
1962
+ button.title = 'EMR data already extracted';
1963
+ button.disabled = true;
1964
+ }
1965
+ }
1966
+ } catch (error) {
1967
+ console.warn('Could not check EMR status:', error);
1968
+ }
1969
+ }
1970
+
1971
  showNotification(message, type = 'info') {
1972
  // Create notification element
1973
  const notification = document.createElement('div');
static/js/emr.js CHANGED
@@ -9,7 +9,7 @@ class EMRPage {
9
  this.filteredEntries = [];
10
  this.currentPage = 1;
11
  this.entriesPerPage = 20;
12
-
13
  this.init();
14
  }
15
 
@@ -182,7 +182,7 @@ class EMRPage {
182
  if (!this.currentPatientId) return;
183
 
184
  this.showLoading(true);
185
-
186
  try {
187
  const response = await fetch(`/emr/patient/${this.currentPatientId}?limit=100`);
188
  if (response.ok) {
@@ -195,7 +195,7 @@ class EMRPage {
195
  }
196
  } catch (error) {
197
  console.error('Error loading EMR data:', error);
198
- this.showEmptyState();
199
  } finally {
200
  this.showLoading(false);
201
  }
@@ -232,7 +232,7 @@ class EMRPage {
232
  </button>
233
  <button class="action-btn danger" onclick="emrPage.deleteEMREntry('${entry.emr_id}')" title="Delete">
234
  <i class="fas fa-trash"></i>
235
- </button>
236
  </div>
237
  </td>
238
  </tr>
@@ -480,7 +480,7 @@ class EMRPage {
480
  }
481
 
482
  this.showLoading(true);
483
-
484
  try {
485
  let searchResults = [];
486
 
@@ -507,7 +507,7 @@ class EMRPage {
507
  if (semanticQuery) {
508
  const exactIds = new Set(exactResults.map(r => r.emr_id));
509
  searchResults = searchResults.concat(exactResults.filter(r => !exactIds.has(r.emr_id)));
510
- } else {
511
  searchResults = exactResults;
512
  }
513
  }
@@ -543,6 +543,19 @@ class EMRPage {
543
  emptyState.style.display = 'block';
544
  tableContainer.style.display = 'none';
545
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
546
  }
547
 
548
  // Initialize the EMR page when DOM is loaded
 
9
  this.filteredEntries = [];
10
  this.currentPage = 1;
11
  this.entriesPerPage = 20;
12
+
13
  this.init();
14
  }
15
 
 
182
  if (!this.currentPatientId) return;
183
 
184
  this.showLoading(true);
185
+
186
  try {
187
  const response = await fetch(`/emr/patient/${this.currentPatientId}?limit=100`);
188
  if (response.ok) {
 
195
  }
196
  } catch (error) {
197
  console.error('Error loading EMR data:', error);
198
+ this.showErrorState('Failed to load EMR data. Please try again.');
199
  } finally {
200
  this.showLoading(false);
201
  }
 
232
  </button>
233
  <button class="action-btn danger" onclick="emrPage.deleteEMREntry('${entry.emr_id}')" title="Delete">
234
  <i class="fas fa-trash"></i>
235
+ </button>
236
  </div>
237
  </td>
238
  </tr>
 
480
  }
481
 
482
  this.showLoading(true);
483
+
484
  try {
485
  let searchResults = [];
486
 
 
507
  if (semanticQuery) {
508
  const exactIds = new Set(exactResults.map(r => r.emr_id));
509
  searchResults = searchResults.concat(exactResults.filter(r => !exactIds.has(r.emr_id)));
510
+ } else {
511
  searchResults = exactResults;
512
  }
513
  }
 
543
  emptyState.style.display = 'block';
544
  tableContainer.style.display = 'none';
545
  }
546
+
547
+ showErrorState(message) {
548
+ const emptyState = document.getElementById('emptyState');
549
+ const tableContainer = document.querySelector('.emr-table-container');
550
+
551
+ // Update the empty state to show error message
552
+ emptyState.querySelector('h3').textContent = 'Error Loading EMR Data';
553
+ emptyState.querySelector('p').textContent = message;
554
+ emptyState.querySelector('.btn').style.display = 'none';
555
+
556
+ emptyState.style.display = 'block';
557
+ tableContainer.style.display = 'none';
558
+ }
559
  }
560
 
561
  // Initialize the EMR page when DOM is loaded