Ali2206 commited on
Commit
84faa65
·
verified ·
1 Parent(s): 360e197

Update api/routes/patients.py

Browse files
Files changed (1) hide show
  1. api/routes/patients.py +218 -3
api/routes/patients.py CHANGED
@@ -7,7 +7,7 @@ from models.entities import Note, PatientCreate
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, DeleteOne
12
  from pymongo.errors import BulkWriteError
13
  import json
@@ -18,6 +18,7 @@ import re
18
  import logging
19
  import time
20
  import os
 
21
 
22
  # Configure logging
23
  logging.basicConfig(
@@ -33,6 +34,54 @@ 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
  @router.get("/debug/count")
37
  async def debug_patient_count():
38
  """Debug endpoint to verify patient counts"""
@@ -172,6 +221,7 @@ async def delete_patient(
172
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
173
  detail=f"Failed to delete patient: {str(e)}"
174
  )
 
175
  async def process_synthea_patient(bundle: dict, file_path: str) -> Optional[dict]:
176
  logger.debug(f"Processing patient from file: {file_path}")
177
  patient_data = {}
@@ -424,7 +474,7 @@ async def list_patients(
424
  skip: int = Query(0, ge=0)
425
  ):
426
  logger.info(f"Listing patients with search: {search}, limit: {limit}, skip: {skip}")
427
- query = {} # Removed the default "source": "synthea" filter
428
 
429
  if search:
430
  query["$or"] = [
@@ -449,7 +499,7 @@ async def list_patients(
449
  "medications": 1,
450
  "encounters": 1,
451
  "notes": 1,
452
- "source": 1 # Added source to projection
453
  }
454
 
455
  try:
@@ -608,5 +658,170 @@ async def add_note(
608
  detail=f"Failed to add note: {str(e)}"
609
  )
610
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
611
  # Export the router as 'patients' for api.__init__.py
612
  patients = router
 
7
  from datetime import datetime
8
  from bson import ObjectId
9
  from bson.errors import InvalidId
10
+ from typing import Optional, List, Dict, Any
11
  from pymongo import UpdateOne, DeleteOne
12
  from pymongo.errors import BulkWriteError
13
  import json
 
18
  import logging
19
  import time
20
  import os
21
+ from pydantic import BaseModel, Field
22
 
23
  # Configure logging
24
  logging.basicConfig(
 
34
  SYNTHEA_DATA_DIR = BASE_DIR / "output" / "fhir"
35
  os.makedirs(SYNTHEA_DATA_DIR, exist_ok=True)
36
 
37
+ # Pydantic models for update validation
38
+ class ConditionUpdate(BaseModel):
39
+ id: Optional[str] = None
40
+ code: Optional[str] = None
41
+ status: Optional[str] = None
42
+ onset_date: Optional[str] = None
43
+ recorded_date: Optional[str] = None
44
+ verification_status: Optional[str] = None
45
+ notes: Optional[str] = None
46
+
47
+ class MedicationUpdate(BaseModel):
48
+ id: Optional[str] = None
49
+ name: Optional[str] = None
50
+ status: Optional[str] = None
51
+ prescribed_date: Optional[str] = None
52
+ requester: Optional[str] = None
53
+ dosage: Optional[str] = None
54
+
55
+ class EncounterUpdate(BaseModel):
56
+ id: Optional[str] = None
57
+ type: Optional[str] = None
58
+ status: Optional[str] = None
59
+ period: Optional[Dict[str, str]] = None
60
+ service_provider: Optional[str] = None
61
+
62
+ class NoteUpdate(BaseModel):
63
+ id: Optional[str] = None
64
+ title: Optional[str] = None
65
+ date: Optional[str] = None
66
+ author: Optional[str] = None
67
+ content: Optional[str] = None
68
+
69
+ class PatientUpdate(BaseModel):
70
+ full_name: Optional[str] = None
71
+ gender: Optional[str] = None
72
+ date_of_birth: Optional[str] = None
73
+ address: Optional[str] = None
74
+ city: Optional[str] = None
75
+ state: Optional[str] = None
76
+ postal_code: Optional[str] = None
77
+ country: Optional[str] = None
78
+ marital_status: Optional[str] = None
79
+ language: Optional[str] = None
80
+ conditions: Optional[List[ConditionUpdate]] = None
81
+ medications: Optional[List[MedicationUpdate]] = None
82
+ encounters: Optional[List[EncounterUpdate]] = None
83
+ notes: Optional[List[NoteUpdate]] = None
84
+
85
  @router.get("/debug/count")
86
  async def debug_patient_count():
87
  """Debug endpoint to verify patient counts"""
 
221
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
222
  detail=f"Failed to delete patient: {str(e)}"
223
  )
224
+
225
  async def process_synthea_patient(bundle: dict, file_path: str) -> Optional[dict]:
226
  logger.debug(f"Processing patient from file: {file_path}")
227
  patient_data = {}
 
474
  skip: int = Query(0, ge=0)
475
  ):
476
  logger.info(f"Listing patients with search: {search}, limit: {limit}, skip: {skip}")
477
+ query = {}
478
 
479
  if search:
480
  query["$or"] = [
 
499
  "medications": 1,
500
  "encounters": 1,
501
  "notes": 1,
502
+ "source": 1
503
  }
504
 
505
  try:
 
658
  detail=f"Failed to add note: {str(e)}"
659
  )
660
 
661
+ @router.put("/patients/{patient_id}", status_code=status.HTTP_200_OK)
662
+ async def update_patient(
663
+ patient_id: str,
664
+ update_data: PatientUpdate,
665
+ current_user: dict = Depends(get_current_user)
666
+ ):
667
+ """Update a patient's record in the database"""
668
+ logger.info(f"Updating patient {patient_id} by user {current_user.get('email')}")
669
+
670
+ if current_user.get('role') not in ['admin', 'doctor']:
671
+ logger.warning(f"Unauthorized update attempt by {current_user.get('email')}")
672
+ raise HTTPException(
673
+ status_code=status.HTTP_403_FORBIDDEN,
674
+ detail="Only administrators and doctors can update patients"
675
+ )
676
+
677
+ try:
678
+ # Build the query based on whether patient_id is a valid ObjectId
679
+ query = {"fhir_id": patient_id}
680
+ if ObjectId.is_valid(patient_id):
681
+ query = {
682
+ "$or": [
683
+ {"_id": ObjectId(patient_id)},
684
+ {"fhir_id": patient_id}
685
+ ]
686
+ }
687
+
688
+ # Check if patient exists
689
+ patient = await patients_collection.find_one(query)
690
+ if not patient:
691
+ logger.warning(f"Patient not found for update: {patient_id}")
692
+ raise HTTPException(
693
+ status_code=status.HTTP_404_NOT_FOUND,
694
+ detail="Patient not found"
695
+ )
696
+
697
+ # Prepare update operations
698
+ update_ops = {"$set": {"last_updated": datetime.utcnow().isoformat()}}
699
+
700
+ # Handle demographic updates
701
+ demographics = {
702
+ "full_name": update_data.full_name,
703
+ "gender": update_data.gender,
704
+ "date_of_birth": update_data.date_of_birth,
705
+ "address": update_data.address,
706
+ "city": update_data.city,
707
+ "state": update_data.state,
708
+ "postal_code": update_data.postal_code,
709
+ "country": update_data.country,
710
+ "marital_status": update_data.marital_status,
711
+ "language": update_data.language
712
+ }
713
+ for key, value in demographics.items():
714
+ if value is not None:
715
+ update_ops["$set"][key] = value
716
+
717
+ # Handle array updates (conditions, medications, encounters, notes)
718
+ array_fields = {
719
+ "conditions": update_data.conditions,
720
+ "medications": update_data.medications,
721
+ "encounters": update_data.encounters,
722
+ "notes": update_data.notes
723
+ }
724
+
725
+ for field, items in array_fields.items():
726
+ if items is not None:
727
+ # Fetch existing items
728
+ existing_items = patient.get(field, [])
729
+ updated_items = []
730
+
731
+ for item in items:
732
+ item_dict = item.dict(exclude_unset=True)
733
+ if not item_dict:
734
+ continue
735
+
736
+ # Generate ID for new items
737
+ if not item_dict.get("id"):
738
+ item_dict["id"] = str(uuid.uuid4())
739
+
740
+ # Validate required fields
741
+ if field == "conditions" and not item_dict.get("code"):
742
+ raise HTTPException(
743
+ status_code=status.HTTP_400_BAD_REQUEST,
744
+ detail=f"Condition code is required for {field}"
745
+ )
746
+ if field == "medications" and not item_dict.get("name"):
747
+ raise HTTPException(
748
+ status_code=status.HTTP_400_BAD_REQUEST,
749
+ detail=f"Medication name is required for {field}"
750
+ )
751
+ if field == "encounters" and not item_dict.get("type"):
752
+ raise HTTPException(
753
+ status_code=status.HTTP_400_BAD_REQUEST,
754
+ detail=f"Encounter type is required for {field}"
755
+ )
756
+ if field == "notes" and not item_dict.get("content"):
757
+ raise HTTPException(
758
+ status_code=status.HTTP_400_BAD_REQUEST,
759
+ detail=f"Note content is required for {field}"
760
+ )
761
+
762
+ updated_items.append(item_dict)
763
+
764
+ # Replace the entire array
765
+ update_ops["$set"][field] = updated_items
766
+
767
+ # Perform the update
768
+ result = await patients_collection.update_one(query, update_ops)
769
+
770
+ if result.modified_count == 0 and result.matched_count == 0:
771
+ logger.warning(f"Patient not found for update: {patient_id}")
772
+ raise HTTPException(
773
+ status_code=status.HTTP_404_NOT_FOUND,
774
+ detail="Patient not found"
775
+ )
776
+
777
+ # Retrieve and return the updated patient
778
+ updated_patient = await patients_collection.find_one(query)
779
+ if not updated_patient:
780
+ logger.error(f"Failed to retrieve updated patient: {patient_id}")
781
+ raise HTTPException(
782
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
783
+ detail="Failed to retrieve updated patient"
784
+ )
785
+
786
+ response = {
787
+ "id": str(updated_patient["_id"]),
788
+ "fhir_id": updated_patient.get("fhir_id"),
789
+ "full_name": updated_patient.get("full_name"),
790
+ "gender": updated_patient.get("gender"),
791
+ "date_of_birth": updated_patient.get("date_of_birth"),
792
+ "address": updated_patient.get("address"),
793
+ "city": updated_patient.get("city"),
794
+ "state": updated_patient.get("state"),
795
+ "postal_code": updated_patient.get("postal_code"),
796
+ "country": updated_patient.get("country"),
797
+ "marital_status": updated_patient.get("marital_status"),
798
+ "language": updated_patient.get("language"),
799
+ "conditions": updated_patient.get("conditions", []),
800
+ "medications": updated_patient.get("medications", []),
801
+ "encounters": updated_patient.get("encounters", []),
802
+ "notes": updated_patient.get("notes", []),
803
+ "source": updated_patient.get("source"),
804
+ "import_date": updated_patient.get("import_date"),
805
+ "last_updated": updated_patient.get("last_updated")
806
+ }
807
+
808
+ logger.info(f"Successfully updated patient {patient_id}")
809
+ return response
810
+
811
+ except ValueError as ve:
812
+ logger.error(f"Invalid patient ID format: {patient_id}, error: {str(ve)}")
813
+ raise HTTPException(
814
+ status_code=status.HTTP_400_BAD_REQUEST,
815
+ detail="Invalid patient ID format"
816
+ )
817
+ except HTTPException:
818
+ raise
819
+ except Exception as e:
820
+ logger.error(f"Failed to update patient {patient_id}: {str(e)}")
821
+ raise HTTPException(
822
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
823
+ detail=f"Failed to update patient: {str(e)}"
824
+ )
825
+
826
  # Export the router as 'patients' for api.__init__.py
827
  patients = router