Ali2206 commited on
Commit
f39f16a
·
verified ·
1 Parent(s): 52904be

Update api/routes/patients.py

Browse files
Files changed (1) hide show
  1. api/routes/patients.py +84 -400
api/routes/patients.py CHANGED
@@ -1,439 +1,135 @@
1
  from fastapi import APIRouter, HTTPException, Depends, Query, status
2
  from db.mongo import patients_collection
3
  from core.security import get_current_user
4
- from utils.db import create_indexes
5
- from utils.helpers import calculate_age, standardize_language
6
- from models.entities import Note
7
  from datetime import datetime
8
  from bson import ObjectId
9
  from bson.errors import InvalidId
10
- from typing import Optional, List, Dict
11
- from pymongo import UpdateOne
12
- from pymongo.errors import BulkWriteError
13
- import json
14
- from pathlib import Path
15
- import glob
16
  import uuid
17
- import re
18
  import logging
19
- import time
20
- import os
21
 
22
- # Configure logging
23
- logging.basicConfig(
24
- level=logging.INFO,
25
- format='%(asctime)s - %(levelname)s - %(name)s - %(message)s'
26
- )
27
  logger = logging.getLogger(__name__)
28
-
29
  router = APIRouter()
30
 
31
- # Configuration
32
- BASE_DIR = Path(__file__).resolve().parent.parent.parent
33
- SYNTHEA_DATA_DIR = BASE_DIR / "output" / "fhir"
34
- os.makedirs(SYNTHEA_DATA_DIR, exist_ok=True)
35
-
36
- async def process_synthea_patient(bundle: dict, file_path: str) -> Optional[dict]:
37
- logger.debug(f"Processing patient from file: {file_path}")
38
- patient_data = {}
39
- notes = []
40
- conditions = []
41
- medications = []
42
- encounters = []
43
-
44
- # Validate bundle structure
45
- if not isinstance(bundle, dict) or 'entry' not in bundle:
46
- logger.error(f"Invalid FHIR bundle structure in {file_path}")
47
- return None
48
-
49
- for entry in bundle.get('entry', []):
50
- resource = entry.get('resource', {})
51
- resource_type = resource.get('resourceType')
52
-
53
- if not resource_type:
54
- logger.warning(f"Skipping entry with missing resourceType in {file_path}")
55
- continue
56
-
57
- try:
58
- if resource_type == 'Patient':
59
- name = resource.get('name', [{}])[0]
60
- address = resource.get('address', [{}])[0]
61
-
62
- # Construct full name and remove numbers
63
- raw_full_name = f"{' '.join(name.get('given', ['']))} {name.get('family', '')}".strip()
64
- clean_full_name = re.sub(r'\d+', '', raw_full_name).strip()
65
-
66
- patient_data = {
67
- 'fhir_id': resource.get('id'),
68
- 'full_name': clean_full_name,
69
- 'gender': resource.get('gender', 'unknown'),
70
- 'date_of_birth': resource.get('birthDate', ''),
71
- 'address': ' '.join(address.get('line', [''])),
72
- 'city': address.get('city', ''),
73
- 'state': address.get('state', ''),
74
- 'postal_code': address.get('postalCode', ''),
75
- 'country': address.get('country', ''),
76
- 'marital_status': resource.get('maritalStatus', {}).get('text', ''),
77
- 'language': standardize_language(resource.get('communication', [{}])[0].get('language', {}).get('text', '')),
78
- 'source': 'synthea',
79
- 'last_updated': datetime.utcnow().isoformat()
80
- }
81
-
82
- elif resource_type == 'Encounter':
83
- encounter = {
84
- 'id': resource.get('id'),
85
- 'type': resource.get('type', [{}])[0].get('text', ''),
86
- 'status': resource.get('status'),
87
- 'period': resource.get('period', {}),
88
- 'service_provider': resource.get('serviceProvider', {}).get('display', '')
89
- }
90
- encounters.append(encounter)
91
-
92
- for note in resource.get('note', []):
93
- if note.get('text'):
94
- notes.append({
95
- 'date': resource.get('period', {}).get('start', datetime.utcnow().isoformat()),
96
- 'type': resource.get('type', [{}])[0].get('text', 'Encounter Note'),
97
- 'text': note.get('text'),
98
- 'context': f"Encounter: {encounter.get('type')}",
99
- 'author': 'System Generated'
100
- })
101
-
102
- elif resource_type == 'Condition':
103
- conditions.append({
104
- 'id': resource.get('id'),
105
- 'code': resource.get('code', {}).get('text', ''),
106
- 'status': resource.get('clinicalStatus', {}).get('text', ''),
107
- 'onset_date': resource.get('onsetDateTime'),
108
- 'recorded_date': resource.get('recordedDate'),
109
- 'verification_status': resource.get('verificationStatus', {}).get('text', '')
110
- })
111
-
112
- elif resource_type == 'MedicationRequest':
113
- medications.append({
114
- 'id': resource.get('id'),
115
- 'name': resource.get('medicationCodeableConcept', {}).get('text', ''),
116
- 'status': resource.get('status'),
117
- 'prescribed_date': resource.get('authoredOn'),
118
- 'requester': resource.get('requester', {}).get('display', ''),
119
- 'dosage': resource.get('dosageInstruction', [{}])[0].get('text', '')
120
- })
121
-
122
- except Exception as e:
123
- logger.error(f"Error processing {resource_type} in {file_path}: {str(e)}")
124
- continue
125
-
126
- if patient_data:
127
- patient_data.update({
128
- 'notes': notes,
129
- 'conditions': conditions,
130
- 'medications': medications,
131
- 'encounters': encounters,
132
- 'import_date': datetime.utcnow().isoformat()
133
- })
134
- logger.info(f"Successfully processed patient {patient_data.get('fhir_id')} from {file_path}")
135
- return patient_data
136
- logger.warning(f"No valid patient data found in {file_path}")
137
- return None
138
-
139
- @router.post("/import", status_code=status.HTTP_201_CREATED)
140
- async def import_patients(
141
- limit: int = Query(100, ge=1, le=1000),
142
  current_user: dict = Depends(get_current_user)
143
  ):
144
- request_id = str(uuid.uuid4())
145
- logger.info(f"Starting import request {request_id} by user {current_user.get('email')}")
146
- start_time = time.time()
147
-
148
  if current_user.get('role') not in ['admin', 'doctor']:
149
- logger.warning(f"Unauthorized import attempt by {current_user.get('email')}")
150
  raise HTTPException(
151
  status_code=status.HTTP_403_FORBIDDEN,
152
- detail="Only administrators and doctors can import data"
153
  )
154
-
155
  try:
156
- await create_indexes()
157
-
158
- if not SYNTHEA_DATA_DIR.exists():
159
- logger.error(f"Synthea data directory not found: {SYNTHEA_DATA_DIR}")
160
- raise HTTPException(
161
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
162
- detail="Data directory not found"
163
- )
164
-
165
- # Filter out non-patient files
166
- files = [
167
- f for f in glob.glob(str(SYNTHEA_DATA_DIR / "*.json"))
168
- if not re.search(r'(hospitalInformation|practitionerInformation)\d+\.json$', f)
169
- ]
170
- if not files:
171
- logger.warning("No valid patient JSON files found in synthea data directory")
172
- return {
173
- "status": "success",
174
- "message": "No patient data files found",
175
- "imported": 0,
176
- "request_id": request_id
177
- }
178
-
179
- operations = []
180
- imported = 0
181
- errors = []
182
-
183
- for file_path in files[:limit]:
184
- try:
185
- logger.debug(f"Processing file: {file_path}")
186
-
187
- # Check file accessibility
188
- if not os.path.exists(file_path):
189
- logger.error(f"File not found: {file_path}")
190
- errors.append(f"File not found: {file_path}")
191
- continue
192
-
193
- # Check file size
194
- file_size = os.path.getsize(file_path)
195
- if file_size == 0:
196
- logger.warning(f"Empty file: {file_path}")
197
- errors.append(f"Empty file: {file_path}")
198
- continue
199
-
200
- with open(file_path, 'r', encoding='utf-8') as f:
201
- try:
202
- bundle = json.load(f)
203
- except json.JSONDecodeError as je:
204
- logger.error(f"Invalid JSON in {file_path}: {str(je)}")
205
- errors.append(f"Invalid JSON in {file_path}: {str(je)}")
206
- continue
207
-
208
- patient = await process_synthea_patient(bundle, file_path)
209
- if patient:
210
- if not patient.get('fhir_id'):
211
- logger.warning(f"Missing FHIR ID in patient data from {file_path}")
212
- errors.append(f"Missing FHIR ID in {file_path}")
213
- continue
214
-
215
- operations.append(UpdateOne(
216
- {"fhir_id": patient['fhir_id']},
217
- {"$setOnInsert": patient},
218
- upsert=True
219
- ))
220
- imported += 1
221
- else:
222
- logger.warning(f"No valid patient data in {file_path}")
223
- errors.append(f"No valid patient data in {file_path}")
224
-
225
- except Exception as e:
226
- logger.error(f"Error processing {file_path}: {str(e)}")
227
- errors.append(f"Error in {file_path}: {str(e)}")
228
- continue
229
 
230
- response = {
231
- "status": "success",
232
- "imported": imported,
233
- "errors": errors,
234
- "request_id": request_id,
235
- "duration_seconds": time.time() - start_time
236
  }
237
-
238
- if operations:
239
- try:
240
- result = await patients_collection.bulk_write(operations, ordered=False)
241
- response.update({
242
- "upserted": result.upserted_count,
243
- "existing": len(operations) - result.upserted_count
244
- })
245
- logger.info(f"Import request {request_id} completed: {imported} patients processed, "
246
- f"{result.upserted_count} upserted, {len(errors)} errors")
247
- except BulkWriteError as bwe:
248
- logger.error(f"Partial bulk write failure for request {request_id}: {str(bwe.details)}")
249
- response.update({
250
- "upserted": bwe.details.get('nUpserted', 0),
251
- "existing": len(operations) - bwe.details.get('nUpserted', 0),
252
- "write_errors": [
253
- f"Index {err['index']}: {err['errmsg']}" for err in bwe.details.get('writeErrors', [])
254
- ]
255
- })
256
- logger.info(f"Import request {request_id} partially completed: {imported} patients processed, "
257
- f"{response['upserted']} upserted, {len(errors)} errors")
258
- except Exception as e:
259
- logger.error(f"Bulk write failed for request {request_id}: {str(e)}")
260
- raise HTTPException(
261
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
262
- detail=f"Database operation failed: {str(e)}"
263
- )
264
- else:
265
- logger.info(f"Import request {request_id} completed: No new patients to import, {len(errors)} errors")
266
- response["message"] = "No new patients found to import"
267
-
268
- return response
269
-
270
- except HTTPException:
271
- raise
272
  except Exception as e:
273
- logger.error(f"Import request {request_id} failed: {str(e)}", exc_info=True)
274
  raise HTTPException(
275
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
276
- detail=f"Import failed: {str(e)}"
277
  )
278
 
279
- @router.get("/patients", response_model=List[dict])
280
  async def list_patients(
281
- search: Optional[str] = Query(None),
282
- min_notes: int = Query(0, ge=0),
283
- min_conditions: int = Query(0, ge=0),
284
- limit: int = Query(100, ge=1, le=500),
285
- skip: int = Query(0, ge=0)
286
  ):
287
- logger.info(f"Listing patients with search: {search}, limit: {limit}, skip: {skip}")
288
- query = {"source": "synthea"}
289
-
290
- if search:
291
- query["$or"] = [
292
- {"full_name": {"$regex": search, "$options": "i"}},
293
- {"fhir_id": search}
294
- ]
295
-
296
- if min_notes > 0:
297
- query[f"notes.{min_notes-1}"] = {"$exists": True}
298
-
299
- if min_conditions > 0:
300
- query[f"conditions.{min_conditions-1}"] = {"$exists": True}
301
-
302
- # Removed $slice to return full arrays for the frontend
303
- projection = {
304
- "fhir_id": 1,
305
- "full_name": 1,
306
- "gender": 1,
307
- "date_of_birth": 1,
308
- "city": 1,
309
- "state": 1,
310
- "conditions": 1,
311
- "medications": 1,
312
- "encounters": 1,
313
- "notes": 1
314
- }
315
-
316
  try:
317
- cursor = patients_collection.find(query, projection).skip(skip).limit(limit)
 
 
 
 
 
 
 
318
  patients = []
319
 
320
- async for patient in cursor:
321
- patients.append({
322
- "id": str(patient["_id"]),
323
- "fhir_id": patient.get("fhir_id"),
324
- "full_name": patient.get("full_name"),
325
- "gender": patient.get("gender"),
326
- "date_of_birth": patient.get("date_of_birth"),
327
- "city": patient.get("city"),
328
- "state": patient.get("state"),
329
- "conditions": patient.get("conditions", []),
330
- "medications": patient.get("medications", []),
331
- "encounters": patient.get("encounters", []),
332
- "notes": patient.get("notes", []),
333
- "age": calculate_age(patient.get("date_of_birth")),
334
- "stats": {
335
- "notes": len(patient.get("notes", [])),
336
- "conditions": len(patient.get("conditions", [])),
337
- "medications": len(patient.get("medications", []))
338
- }
339
- })
340
 
341
- logger.info(f"Retrieved {len(patients)} patients")
342
  return patients
343
-
344
  except Exception as e:
345
- logger.error(f"Failed to list patients: {str(e)}")
346
  raise HTTPException(
347
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
348
- detail=f"Failed to retrieve patients: {str(e)}"
349
  )
350
 
351
- @router.get("/patients/{patient_id}", response_model=dict)
352
- async def get_patient(patient_id: str):
353
- logger.info(f"Retrieving patient: {patient_id}")
 
 
 
354
  try:
355
- patient = await patients_collection.find_one({
356
- "$or": [
357
- {"_id": ObjectId(patient_id)},
358
- {"fhir_id": patient_id}
359
- ]
360
- })
361
-
362
  if not patient:
363
- logger.warning(f"Patient not found: {patient_id}")
364
- raise HTTPException(
365
- status_code=status.HTTP_404_NOT_FOUND,
366
- detail="Patient not found"
367
- )
368
-
369
- response = {
370
- "demographics": {
371
- "id": str(patient["_id"]),
372
- "fhir_id": patient.get("fhir_id"),
373
- "full_name": patient.get("full_name"),
374
- "gender": patient.get("gender"),
375
- "date_of_birth": patient.get("date_of_birth"),
376
- "age": calculate_age(patient.get("date_of_birth")),
377
- "address": {
378
- "line": patient.get("address"),
379
- "city": patient.get("city"),
380
- "state": patient.get("state"),
381
- "postal_code": patient.get("postal_code"),
382
- "country": patient.get("country")
383
- },
384
- "marital_status": patient.get("marital_status"),
385
- "language": patient.get("language")
386
- },
387
- "clinical_data": {
388
- "notes": patient.get("notes", []),
389
- "conditions": patient.get("conditions", []),
390
- "medications": patient.get("medications", []),
391
- "encounters": patient.get("encounters", [])
392
- },
393
- "metadata": {
394
- "source": patient.get("source"),
395
- "import_date": patient.get("import_date"),
396
- "last_updated": patient.get("last_updated")
397
- }
398
- }
399
-
400
- logger.info(f"Successfully retrieved patient: {patient_id}")
401
- return response
402
-
403
- except ValueError as ve:
404
- logger.error(f"Invalid patient ID format: {patient_id}, error: {str(ve)}")
405
- raise HTTPException(
406
- status_code=status.HTTP_400_BAD_REQUEST,
407
- detail="Invalid patient ID format"
408
- )
409
  except Exception as e:
410
- logger.error(f"Failed to retrieve patient {patient_id}: {str(e)}")
411
  raise HTTPException(
412
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
413
- detail=f"Failed to retrieve patient: {str(e)}"
414
  )
415
 
416
- @router.post("/patients/{patient_id}/notes", status_code=status.HTTP_201_CREATED)
417
- async def add_note(
418
  patient_id: str,
419
  note: Note,
420
  current_user: dict = Depends(get_current_user)
421
  ):
422
- logger.info(f"Adding note for patient {patient_id} by user {current_user.get('email')}")
423
  if current_user.get('role') not in ['doctor', 'admin']:
424
- logger.warning(f"Unauthorized note addition attempt by {current_user.get('email')}")
425
- raise HTTPException(
426
- status_code=status.HTTP_403_FORBIDDEN,
427
- detail="Only clinicians can add notes"
428
- )
429
-
430
  try:
431
  note_data = note.dict()
432
  note_data.update({
433
- "author": current_user.get('full_name', 'System'),
434
  "timestamp": datetime.utcnow().isoformat()
435
  })
436
-
437
  result = await patients_collection.update_one(
438
  {"$or": [
439
  {"_id": ObjectId(patient_id)},
@@ -444,29 +140,17 @@ async def add_note(
444
  "$set": {"last_updated": datetime.utcnow().isoformat()}
445
  }
446
  )
447
-
448
  if result.modified_count == 0:
449
- logger.warning(f"Patient not found for note addition: {patient_id}")
450
- raise HTTPException(
451
- status_code=status.HTTP_404_NOT_FOUND,
452
- detail="Patient not found"
453
- )
454
-
455
- logger.info(f"Note added successfully for patient {patient_id}")
456
- return {"status": "success", "message": "Note added"}
457
-
458
- except ValueError as ve:
459
- logger.error(f"Invalid patient ID format: {patient_id}, error: {str(ve)}")
460
- raise HTTPException(
461
- status_code=status.HTTP_400_BAD_REQUEST,
462
- detail="Invalid patient ID format"
463
- )
464
  except Exception as e:
465
- logger.error(f"Failed to add note for patient {patient_id}: {str(e)}")
466
  raise HTTPException(
467
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
468
- detail=f"Failed to add note: {str(e)}"
469
  )
470
 
471
- # Export the router as 'patients' for api.__init__.py
472
  patients = router
 
1
  from fastapi import APIRouter, HTTPException, Depends, Query, status
2
  from db.mongo import patients_collection
3
  from core.security import get_current_user
4
+ from utils.helpers import calculate_age
5
+ from models.entities import PatientCreate, Note
 
6
  from datetime import datetime
7
  from bson import ObjectId
8
  from bson.errors import InvalidId
9
+ from typing import Optional, List
 
 
 
 
 
10
  import uuid
 
11
  import logging
 
 
12
 
 
 
 
 
 
13
  logger = logging.getLogger(__name__)
 
14
  router = APIRouter()
15
 
16
+ @router.post("/", status_code=status.HTTP_201_CREATED)
17
+ async def create_patient(
18
+ patient_data: PatientCreate,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  current_user: dict = Depends(get_current_user)
20
  ):
21
+ """Create a new patient"""
 
 
 
22
  if current_user.get('role') not in ['admin', 'doctor']:
 
23
  raise HTTPException(
24
  status_code=status.HTTP_403_FORBIDDEN,
25
+ detail="Only admins and doctors can create patients"
26
  )
27
+
28
  try:
29
+ patient = patient_data.dict()
30
+ now = datetime.utcnow().isoformat()
31
+
32
+ patient.update({
33
+ "fhir_id": str(uuid.uuid4()),
34
+ "import_date": now,
35
+ "last_updated": now,
36
+ "source": "manual",
37
+ "created_by": current_user.get('email')
38
+ })
39
+
40
+ result = await patients_collection.insert_one(patient)
41
+ created = await patients_collection.find_one({"_id": result.inserted_id})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
+ return {
44
+ "id": str(created["_id"]),
45
+ **{k: v for k, v in created.items() if k != "_id"}
 
 
 
46
  }
47
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  except Exception as e:
49
+ logger.error(f"Create patient error: {str(e)}")
50
  raise HTTPException(
51
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
52
+ detail="Failed to create patient"
53
  )
54
 
55
+ @router.get("/", response_model=List[dict])
56
  async def list_patients(
57
+ search: Optional[str] = None,
58
+ skip: int = 0,
59
+ limit: int = 100,
60
+ current_user: dict = Depends(get_current_user)
 
61
  ):
62
+ """List patients with pagination and search"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  try:
64
+ query = {}
65
+ if search:
66
+ query["$or"] = [
67
+ {"full_name": {"$regex": search, "$options": "i"}},
68
+ {"fhir_id": search}
69
+ ]
70
+
71
+ cursor = patients_collection.find(query).skip(skip).limit(limit)
72
  patients = []
73
 
74
+ async for doc in cursor:
75
+ doc["id"] = str(doc["_id"])
76
+ doc["age"] = calculate_age(doc.get("date_of_birth"))
77
+ patients.append(doc)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
 
79
  return patients
80
+
81
  except Exception as e:
82
+ logger.error(f"List patients error: {str(e)}")
83
  raise HTTPException(
84
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
85
+ detail="Failed to fetch patients"
86
  )
87
 
88
+ @router.get("/{patient_id}", response_model=dict)
89
+ async def get_patient(
90
+ patient_id: str,
91
+ current_user: dict = Depends(get_current_user)
92
+ ):
93
+ """Get patient by ID or FHIR ID"""
94
  try:
95
+ # Try as ObjectId first
96
+ try:
97
+ patient = await patients_collection.find_one({"_id": ObjectId(patient_id)})
98
+ except (InvalidId, ValueError):
99
+ # Fall back to FHIR ID
100
+ patient = await patients_collection.find_one({"fhir_id": patient_id})
101
+
102
  if not patient:
103
+ raise HTTPException(status_code=404, detail="Patient not found")
104
+
105
+ patient["id"] = str(patient["_id"])
106
+ patient["age"] = calculate_age(patient.get("date_of_birth"))
107
+ return patient
108
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  except Exception as e:
110
+ logger.error(f"Get patient error: {str(e)}")
111
  raise HTTPException(
112
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
113
+ detail="Failed to fetch patient"
114
  )
115
 
116
+ @router.post("/{patient_id}/notes", status_code=status.HTTP_201_CREATED)
117
+ async def add_patient_note(
118
  patient_id: str,
119
  note: Note,
120
  current_user: dict = Depends(get_current_user)
121
  ):
122
+ """Add a note to patient record"""
123
  if current_user.get('role') not in ['doctor', 'admin']:
124
+ raise HTTPException(status_code=403, detail="Only clinicians can add notes")
125
+
 
 
 
 
126
  try:
127
  note_data = note.dict()
128
  note_data.update({
129
+ "author": current_user.get('email'),
130
  "timestamp": datetime.utcnow().isoformat()
131
  })
132
+
133
  result = await patients_collection.update_one(
134
  {"$or": [
135
  {"_id": ObjectId(patient_id)},
 
140
  "$set": {"last_updated": datetime.utcnow().isoformat()}
141
  }
142
  )
143
+
144
  if result.modified_count == 0:
145
+ raise HTTPException(status_code=404, detail="Patient not found")
146
+
147
+ return {"status": "success"}
148
+
 
 
 
 
 
 
 
 
 
 
 
149
  except Exception as e:
150
+ logger.error(f"Add note error: {str(e)}")
151
  raise HTTPException(
152
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
153
+ detail="Failed to add note"
154
  )
155
 
 
156
  patients = router