Spaces:
Sleeping
Sleeping
teest
Browse files- api/routes/patients.py +20 -91
- models/entities.py +19 -36
- routes/patients.py +28 -83
api/routes/patients.py
CHANGED
|
@@ -3,7 +3,7 @@ from db.mongo import patients_collection, db # Added db import
|
|
| 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
|
| 7 |
from models.schemas import PatientListResponse # Fixed import
|
| 8 |
from api.services.fhir_integration import HAPIFHIRIntegrationService
|
| 9 |
from api.services.synthea_integration import SyntheaIntegrationService
|
|
@@ -58,36 +58,6 @@ def get_synthea_data_dir():
|
|
| 58 |
SYNTHEA_DATA_DIR = get_synthea_data_dir()
|
| 59 |
|
| 60 |
# Pydantic models for update validation
|
| 61 |
-
class ConditionUpdate(BaseModel):
|
| 62 |
-
id: Optional[str] = None
|
| 63 |
-
code: Optional[str] = None
|
| 64 |
-
status: Optional[str] = None
|
| 65 |
-
onset_date: Optional[str] = None
|
| 66 |
-
recorded_date: Optional[str] = None
|
| 67 |
-
verification_status: Optional[str] = None
|
| 68 |
-
notes: Optional[str] = None
|
| 69 |
-
|
| 70 |
-
class MedicationUpdate(BaseModel):
|
| 71 |
-
id: Optional[str] = None
|
| 72 |
-
name: Optional[str] = None
|
| 73 |
-
status: Optional[str] = None
|
| 74 |
-
prescribed_date: Optional[str] = None
|
| 75 |
-
requester: Optional[str] = None
|
| 76 |
-
dosage: Optional[str] = None
|
| 77 |
-
|
| 78 |
-
class EncounterUpdate(BaseModel):
|
| 79 |
-
id: Optional[str] = None
|
| 80 |
-
type: Optional[str] = None
|
| 81 |
-
status: Optional[str] = None
|
| 82 |
-
period: Optional[Dict[str, str]] = None
|
| 83 |
-
service_provider: Optional[str] = None
|
| 84 |
-
|
| 85 |
-
class NoteUpdate(BaseModel):
|
| 86 |
-
id: Optional[str] = None
|
| 87 |
-
title: Optional[str] = None
|
| 88 |
-
date: Optional[str] = None
|
| 89 |
-
author: Optional[str] = None
|
| 90 |
-
content: Optional[str] = None
|
| 91 |
|
| 92 |
class PatientUpdate(BaseModel):
|
| 93 |
full_name: Optional[str] = None
|
|
@@ -98,12 +68,24 @@ class PatientUpdate(BaseModel):
|
|
| 98 |
state: Optional[str] = None
|
| 99 |
postal_code: Optional[str] = None
|
| 100 |
country: Optional[str] = None
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
|
| 108 |
@router.get("/debug/count")
|
| 109 |
async def debug_patient_count():
|
|
@@ -958,60 +940,7 @@ async def get_patient(patient_id: str):
|
|
| 958 |
detail=f"Failed to retrieve patient: {str(e)}"
|
| 959 |
)
|
| 960 |
|
| 961 |
-
|
| 962 |
-
async def add_note(
|
| 963 |
-
patient_id: str,
|
| 964 |
-
note: Note,
|
| 965 |
-
current_user: dict = Depends(get_current_user)
|
| 966 |
-
):
|
| 967 |
-
logger.info(f"Adding note for patient {patient_id} by user {current_user.get('email')}")
|
| 968 |
-
if not any(role in current_user.get('roles', []) for role in ['doctor', 'admin']):
|
| 969 |
-
logger.warning(f"Unauthorized note addition attempt by {current_user.get('email')}")
|
| 970 |
-
raise HTTPException(
|
| 971 |
-
status_code=status.HTTP_403_FORBIDDEN,
|
| 972 |
-
detail="Only clinicians can add notes"
|
| 973 |
-
)
|
| 974 |
-
|
| 975 |
-
try:
|
| 976 |
-
note_data = note.dict()
|
| 977 |
-
note_data.update({
|
| 978 |
-
"author": current_user.get('full_name', 'System'),
|
| 979 |
-
"timestamp": datetime.utcnow().isoformat()
|
| 980 |
-
})
|
| 981 |
-
|
| 982 |
-
result = await patients_collection.update_one(
|
| 983 |
-
{"$or": [
|
| 984 |
-
{"_id": ObjectId(patient_id)},
|
| 985 |
-
{"fhir_id": patient_id}
|
| 986 |
-
]},
|
| 987 |
-
{
|
| 988 |
-
"$push": {"notes": note_data},
|
| 989 |
-
"$set": {"last_updated": datetime.utcnow().isoformat()}
|
| 990 |
-
}
|
| 991 |
-
)
|
| 992 |
-
|
| 993 |
-
if result.modified_count == 0:
|
| 994 |
-
logger.warning(f"Patient not found for note addition: {patient_id}")
|
| 995 |
-
raise HTTPException(
|
| 996 |
-
status_code=status.HTTP_404_NOT_FOUND,
|
| 997 |
-
detail="Patient not found"
|
| 998 |
-
)
|
| 999 |
-
|
| 1000 |
-
logger.info(f"Note added successfully for patient {patient_id}")
|
| 1001 |
-
return {"status": "success", "message": "Note added"}
|
| 1002 |
-
|
| 1003 |
-
except ValueError as ve:
|
| 1004 |
-
logger.error(f"Invalid patient ID format: {patient_id}, error: {str(ve)}")
|
| 1005 |
-
raise HTTPException(
|
| 1006 |
-
status_code=status.HTTP_400_BAD_REQUEST,
|
| 1007 |
-
detail="Invalid patient ID format"
|
| 1008 |
-
)
|
| 1009 |
-
except Exception as e:
|
| 1010 |
-
logger.error(f"Failed to add note for patient {patient_id}: {str(e)}")
|
| 1011 |
-
raise HTTPException(
|
| 1012 |
-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 1013 |
-
detail=f"Failed to add note: {str(e)}"
|
| 1014 |
-
)
|
| 1015 |
|
| 1016 |
@router.put("/patients/{patient_id}", status_code=status.HTTP_200_OK)
|
| 1017 |
async def update_patient(
|
|
|
|
| 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 PatientCreate
|
| 7 |
from models.schemas import PatientListResponse # Fixed import
|
| 8 |
from api.services.fhir_integration import HAPIFHIRIntegrationService
|
| 9 |
from api.services.synthea_integration import SyntheaIntegrationService
|
|
|
|
| 58 |
SYNTHEA_DATA_DIR = get_synthea_data_dir()
|
| 59 |
|
| 60 |
# Pydantic models for update validation
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
class PatientUpdate(BaseModel):
|
| 63 |
full_name: Optional[str] = None
|
|
|
|
| 68 |
state: Optional[str] = None
|
| 69 |
postal_code: Optional[str] = None
|
| 70 |
country: Optional[str] = None
|
| 71 |
+
national_id: Optional[str] = None
|
| 72 |
+
blood_type: Optional[str] = None
|
| 73 |
+
allergies: Optional[List[str]] = None
|
| 74 |
+
chronic_conditions: Optional[List[str]] = None
|
| 75 |
+
medications: Optional[List[str]] = None
|
| 76 |
+
emergency_contact_name: Optional[str] = None
|
| 77 |
+
emergency_contact_phone: Optional[str] = None
|
| 78 |
+
insurance_provider: Optional[str] = None
|
| 79 |
+
insurance_policy_number: Optional[str] = None
|
| 80 |
+
medical_notes: Optional[str] = None
|
| 81 |
+
symptoms: Optional[List[str]] = None
|
| 82 |
+
vital_signs: Optional[dict] = None
|
| 83 |
+
source: Optional[str] = None
|
| 84 |
+
status: Optional[str] = None
|
| 85 |
+
assigned_doctor_id: Optional[str] = None
|
| 86 |
+
registration_date: Optional[str] = None
|
| 87 |
+
created_at: Optional[datetime] = None
|
| 88 |
+
updated_at: Optional[datetime] = None
|
| 89 |
|
| 90 |
@router.get("/debug/count")
|
| 91 |
async def debug_patient_count():
|
|
|
|
| 940 |
detail=f"Failed to retrieve patient: {str(e)}"
|
| 941 |
)
|
| 942 |
|
| 943 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 944 |
|
| 945 |
@router.put("/patients/{patient_id}", status_code=status.HTTP_200_OK)
|
| 946 |
async def update_patient(
|
models/entities.py
CHANGED
|
@@ -2,36 +2,6 @@ from pydantic import BaseModel
|
|
| 2 |
from typing import List, Optional
|
| 3 |
from datetime import datetime
|
| 4 |
|
| 5 |
-
class Condition(BaseModel):
|
| 6 |
-
id: str
|
| 7 |
-
code: str
|
| 8 |
-
status: Optional[str] = ""
|
| 9 |
-
onset_date: Optional[str] = None
|
| 10 |
-
recorded_date: Optional[str] = None
|
| 11 |
-
verification_status: Optional[str] = ""
|
| 12 |
-
|
| 13 |
-
class Medication(BaseModel):
|
| 14 |
-
id: str
|
| 15 |
-
name: str
|
| 16 |
-
status: str
|
| 17 |
-
prescribed_date: Optional[str] = None
|
| 18 |
-
requester: Optional[str] = ""
|
| 19 |
-
dosage: Optional[str] = ""
|
| 20 |
-
|
| 21 |
-
class Encounter(BaseModel):
|
| 22 |
-
id: str
|
| 23 |
-
type: str
|
| 24 |
-
status: str
|
| 25 |
-
period: dict
|
| 26 |
-
service_provider: Optional[str] = ""
|
| 27 |
-
|
| 28 |
-
class Note(BaseModel):
|
| 29 |
-
date: str
|
| 30 |
-
type: str
|
| 31 |
-
text: str
|
| 32 |
-
context: Optional[str] = ""
|
| 33 |
-
author: Optional[str] = "System"
|
| 34 |
-
|
| 35 |
class PatientCreate(BaseModel):
|
| 36 |
full_name: str
|
| 37 |
gender: str
|
|
@@ -41,9 +11,22 @@ class PatientCreate(BaseModel):
|
|
| 41 |
state: Optional[str] = ""
|
| 42 |
postal_code: Optional[str] = ""
|
| 43 |
country: Optional[str] = "US"
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
from typing import List, Optional
|
| 3 |
from datetime import datetime
|
| 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
class PatientCreate(BaseModel):
|
| 6 |
full_name: str
|
| 7 |
gender: str
|
|
|
|
| 11 |
state: Optional[str] = ""
|
| 12 |
postal_code: Optional[str] = ""
|
| 13 |
country: Optional[str] = "US"
|
| 14 |
+
national_id: Optional[str] = ""
|
| 15 |
+
blood_type: Optional[str] = ""
|
| 16 |
+
allergies: Optional[List[str]] = []
|
| 17 |
+
chronic_conditions: Optional[List[str]] = []
|
| 18 |
+
medications: Optional[List[str]] = []
|
| 19 |
+
emergency_contact_name: Optional[str] = ""
|
| 20 |
+
emergency_contact_phone: Optional[str] = ""
|
| 21 |
+
insurance_provider: Optional[str] = ""
|
| 22 |
+
insurance_policy_number: Optional[str] = ""
|
| 23 |
+
medical_notes: Optional[str] = ""
|
| 24 |
+
symptoms: Optional[List[str]] = []
|
| 25 |
+
vital_signs: Optional[dict] = {
|
| 26 |
+
"blood_pressure": "",
|
| 27 |
+
"temperature": "",
|
| 28 |
+
"heart_rate": "",
|
| 29 |
+
"respiratory_rate": "",
|
| 30 |
+
"weight": "",
|
| 31 |
+
"height": ""
|
| 32 |
+
}
|
routes/patients.py
CHANGED
|
@@ -3,7 +3,7 @@ from db.mongo import patients_collection, db # Added db import
|
|
| 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
|
| 7 |
from models.schemas import PatientListResponse # Fixed import
|
| 8 |
from api.services.fhir_integration import HAPIFHIRIntegrationService
|
| 9 |
from datetime import datetime
|
|
@@ -713,34 +713,32 @@ async def get_patient(patient_id: str):
|
|
| 713 |
)
|
| 714 |
|
| 715 |
response = {
|
| 716 |
-
"
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
"
|
| 734 |
-
|
| 735 |
-
|
| 736 |
-
|
| 737 |
-
|
| 738 |
-
|
| 739 |
-
"
|
| 740 |
-
|
| 741 |
-
|
| 742 |
-
"last_updated": patient.get("last_updated")
|
| 743 |
-
}
|
| 744 |
}
|
| 745 |
|
| 746 |
logger.info(f"Successfully retrieved patient: {patient_id}")
|
|
@@ -759,60 +757,7 @@ async def get_patient(patient_id: str):
|
|
| 759 |
detail=f"Failed to retrieve patient: {str(e)}"
|
| 760 |
)
|
| 761 |
|
| 762 |
-
|
| 763 |
-
async def add_note(
|
| 764 |
-
patient_id: str,
|
| 765 |
-
note: Note,
|
| 766 |
-
current_user: dict = Depends(get_current_user)
|
| 767 |
-
):
|
| 768 |
-
logger.info(f"Adding note for patient {patient_id} by user {current_user.get('email')}")
|
| 769 |
-
if current_user.get('role') not in ['doctor', 'admin']:
|
| 770 |
-
logger.warning(f"Unauthorized note addition attempt by {current_user.get('email')}")
|
| 771 |
-
raise HTTPException(
|
| 772 |
-
status_code=status.HTTP_403_FORBIDDEN,
|
| 773 |
-
detail="Only clinicians can add notes"
|
| 774 |
-
)
|
| 775 |
-
|
| 776 |
-
try:
|
| 777 |
-
note_data = note.dict()
|
| 778 |
-
note_data.update({
|
| 779 |
-
"author": current_user.get('full_name', 'System'),
|
| 780 |
-
"timestamp": datetime.utcnow().isoformat()
|
| 781 |
-
})
|
| 782 |
-
|
| 783 |
-
result = await patients_collection.update_one(
|
| 784 |
-
{"$or": [
|
| 785 |
-
{"_id": ObjectId(patient_id)},
|
| 786 |
-
{"fhir_id": patient_id}
|
| 787 |
-
]},
|
| 788 |
-
{
|
| 789 |
-
"$push": {"notes": note_data},
|
| 790 |
-
"$set": {"last_updated": datetime.utcnow().isoformat()}
|
| 791 |
-
}
|
| 792 |
-
)
|
| 793 |
-
|
| 794 |
-
if result.modified_count == 0:
|
| 795 |
-
logger.warning(f"Patient not found for note addition: {patient_id}")
|
| 796 |
-
raise HTTPException(
|
| 797 |
-
status_code=status.HTTP_404_NOT_FOUND,
|
| 798 |
-
detail="Patient not found"
|
| 799 |
-
)
|
| 800 |
-
|
| 801 |
-
logger.info(f"Note added successfully for patient {patient_id}")
|
| 802 |
-
return {"status": "success", "message": "Note added"}
|
| 803 |
-
|
| 804 |
-
except ValueError as ve:
|
| 805 |
-
logger.error(f"Invalid patient ID format: {patient_id}, error: {str(ve)}")
|
| 806 |
-
raise HTTPException(
|
| 807 |
-
status_code=status.HTTP_400_BAD_REQUEST,
|
| 808 |
-
detail="Invalid patient ID format"
|
| 809 |
-
)
|
| 810 |
-
except Exception as e:
|
| 811 |
-
logger.error(f"Failed to add note for patient {patient_id}: {str(e)}")
|
| 812 |
-
raise HTTPException(
|
| 813 |
-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 814 |
-
detail=f"Failed to add note: {str(e)}"
|
| 815 |
-
)
|
| 816 |
|
| 817 |
@router.put("/patients/{patient_id}", status_code=status.HTTP_200_OK)
|
| 818 |
async def update_patient(
|
|
|
|
| 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 PatientCreate
|
| 7 |
from models.schemas import PatientListResponse # Fixed import
|
| 8 |
from api.services.fhir_integration import HAPIFHIRIntegrationService
|
| 9 |
from datetime import datetime
|
|
|
|
| 713 |
)
|
| 714 |
|
| 715 |
response = {
|
| 716 |
+
"id": str(patient["_id"]),
|
| 717 |
+
"full_name": patient.get("full_name"),
|
| 718 |
+
"gender": patient.get("gender"),
|
| 719 |
+
"date_of_birth": patient.get("date_of_birth"),
|
| 720 |
+
"address": patient.get("address"),
|
| 721 |
+
"city": patient.get("city"),
|
| 722 |
+
"state": patient.get("state"),
|
| 723 |
+
"postal_code": patient.get("postal_code"),
|
| 724 |
+
"country": patient.get("country"),
|
| 725 |
+
"national_id": patient.get("national_id"),
|
| 726 |
+
"blood_type": patient.get("blood_type"),
|
| 727 |
+
"allergies": patient.get("allergies", []),
|
| 728 |
+
"chronic_conditions": patient.get("chronic_conditions", []),
|
| 729 |
+
"medications": patient.get("medications", []),
|
| 730 |
+
"emergency_contact_name": patient.get("emergency_contact_name"),
|
| 731 |
+
"emergency_contact_phone": patient.get("emergency_contact_phone"),
|
| 732 |
+
"insurance_provider": patient.get("insurance_provider"),
|
| 733 |
+
"insurance_policy_number": patient.get("insurance_policy_number"),
|
| 734 |
+
"medical_notes": patient.get("medical_notes"),
|
| 735 |
+
"symptoms": patient.get("symptoms", []),
|
| 736 |
+
"vital_signs": patient.get("vital_signs", {}),
|
| 737 |
+
"source": patient.get("source"),
|
| 738 |
+
"status": patient.get("status"),
|
| 739 |
+
"assigned_doctor_id": patient.get("assigned_doctor_id"),
|
| 740 |
+
"created_at": patient.get("created_at"),
|
| 741 |
+
"updated_at": patient.get("updated_at")
|
|
|
|
|
|
|
| 742 |
}
|
| 743 |
|
| 744 |
logger.info(f"Successfully retrieved patient: {patient_id}")
|
|
|
|
| 757 |
detail=f"Failed to retrieve patient: {str(e)}"
|
| 758 |
)
|
| 759 |
|
| 760 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 761 |
|
| 762 |
@router.put("/patients/{patient_id}", status_code=status.HTTP_200_OK)
|
| 763 |
async def update_patient(
|