Spaces:
Sleeping
Sleeping
device token
Browse files- api/routes/patients.py +131 -63
- models/schemas.py +1 -1
api/routes/patients.py
CHANGED
|
@@ -491,83 +491,137 @@ async def import_patients(
|
|
| 491 |
async def import_ehr_patients(
|
| 492 |
ehr_data: List[dict],
|
| 493 |
ehr_system: str = Query(..., description="Name of the EHR system"),
|
| 494 |
-
current_user: dict = Depends(get_current_user)
|
| 495 |
-
db_client: AsyncIOMotorClient = Depends(lambda: db)
|
| 496 |
):
|
| 497 |
-
"""
|
|
|
|
|
|
|
| 498 |
logger.info(f"Importing {len(ehr_data)} patients from EHR system: {ehr_system}")
|
| 499 |
|
| 500 |
if not any(role in current_user.get('roles', []) for role in ['admin', 'doctor']):
|
| 501 |
logger.warning(f"Unauthorized EHR import attempt by {current_user.get('email')}")
|
| 502 |
raise HTTPException(
|
| 503 |
status_code=status.HTTP_403_FORBIDDEN,
|
| 504 |
-
detail="Only administrators and doctors can import
|
| 505 |
)
|
| 506 |
|
| 507 |
try:
|
|
|
|
|
|
|
| 508 |
imported_patients = []
|
| 509 |
skipped_patients = []
|
|
|
|
| 510 |
|
| 511 |
for patient_data in ehr_data:
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
|
| 531 |
-
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
|
| 538 |
-
|
| 539 |
-
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
|
| 549 |
-
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 557 |
|
| 558 |
return {
|
| 559 |
-
"message": f"
|
| 560 |
-
"imported_count":
|
| 561 |
-
"skipped_count":
|
|
|
|
| 562 |
"imported_patients": imported_patients,
|
| 563 |
-
"skipped_patients": skipped_patients
|
|
|
|
|
|
|
| 564 |
}
|
| 565 |
|
| 566 |
except Exception as e:
|
| 567 |
-
logger.error(f"
|
| 568 |
raise HTTPException(
|
| 569 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 570 |
-
detail=f"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 571 |
)
|
| 572 |
|
| 573 |
@router.get("/patients/sources", response_model=List[dict])
|
|
@@ -704,18 +758,32 @@ async def get_patients(
|
|
| 704 |
# Add assigned doctor name
|
| 705 |
patient["assigned_doctor_name"] = assigned_doctor_name
|
| 706 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 707 |
processed_patients.append(patient)
|
| 708 |
|
| 709 |
logger.info(f"✅ Returning {len(processed_patients)} processed patients")
|
| 710 |
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 719 |
|
| 720 |
except Exception as e:
|
| 721 |
logger.error(f"❌ Error fetching patients: {str(e)}")
|
|
|
|
| 491 |
async def import_ehr_patients(
|
| 492 |
ehr_data: List[dict],
|
| 493 |
ehr_system: str = Query(..., description="Name of the EHR system"),
|
| 494 |
+
current_user: dict = Depends(get_current_user)
|
|
|
|
| 495 |
):
|
| 496 |
+
"""
|
| 497 |
+
Import patients from external EHR system
|
| 498 |
+
"""
|
| 499 |
logger.info(f"Importing {len(ehr_data)} patients from EHR system: {ehr_system}")
|
| 500 |
|
| 501 |
if not any(role in current_user.get('roles', []) for role in ['admin', 'doctor']):
|
| 502 |
logger.warning(f"Unauthorized EHR import attempt by {current_user.get('email')}")
|
| 503 |
raise HTTPException(
|
| 504 |
status_code=status.HTTP_403_FORBIDDEN,
|
| 505 |
+
detail="Only administrators and doctors can import patients"
|
| 506 |
)
|
| 507 |
|
| 508 |
try:
|
| 509 |
+
imported_count = 0
|
| 510 |
+
skipped_count = 0
|
| 511 |
imported_patients = []
|
| 512 |
skipped_patients = []
|
| 513 |
+
errors = []
|
| 514 |
|
| 515 |
for patient_data in ehr_data:
|
| 516 |
+
try:
|
| 517 |
+
# Validate required fields
|
| 518 |
+
if not patient_data.get('ehr_id') or not patient_data.get('full_name'):
|
| 519 |
+
errors.append(f"Patient missing required fields (ehr_id, full_name): {patient_data.get('full_name', 'Unknown')}")
|
| 520 |
+
continue
|
| 521 |
+
|
| 522 |
+
# Check for existing patient by EHR ID and system
|
| 523 |
+
existing_patient = await patients_collection.find_one({
|
| 524 |
+
"ehr_id": patient_data['ehr_id'],
|
| 525 |
+
"ehr_system": ehr_system
|
| 526 |
+
})
|
| 527 |
+
|
| 528 |
+
if existing_patient:
|
| 529 |
+
skipped_count += 1
|
| 530 |
+
skipped_patients.append(patient_data['full_name'])
|
| 531 |
+
continue
|
| 532 |
+
|
| 533 |
+
# Prepare patient document
|
| 534 |
+
patient_doc = {
|
| 535 |
+
"fhir_id": str(uuid.uuid4()),
|
| 536 |
+
"full_name": patient_data['full_name'],
|
| 537 |
+
"date_of_birth": patient_data.get('date_of_birth', ''),
|
| 538 |
+
"gender": patient_data.get('gender', 'unknown'),
|
| 539 |
+
"address": patient_data.get('address', ''),
|
| 540 |
+
"national_id": patient_data.get('national_id', ''),
|
| 541 |
+
"blood_type": patient_data.get('blood_type', ''),
|
| 542 |
+
"allergies": patient_data.get('allergies', []),
|
| 543 |
+
"chronic_conditions": patient_data.get('chronic_conditions', []),
|
| 544 |
+
"medications": patient_data.get('medications', []),
|
| 545 |
+
"emergency_contact_name": patient_data.get('emergency_contact_name', ''),
|
| 546 |
+
"emergency_contact_phone": patient_data.get('emergency_contact_phone', ''),
|
| 547 |
+
"insurance_provider": patient_data.get('insurance_provider', ''),
|
| 548 |
+
"insurance_policy_number": patient_data.get('insurance_policy_number', ''),
|
| 549 |
+
"source": "ehr_import",
|
| 550 |
+
"ehr_id": patient_data['ehr_id'],
|
| 551 |
+
"ehr_system": ehr_system,
|
| 552 |
+
"status": "active",
|
| 553 |
+
"created_by": current_user.get('email'),
|
| 554 |
+
"created_at": datetime.utcnow().isoformat(),
|
| 555 |
+
"updated_at": datetime.utcnow().isoformat()
|
| 556 |
+
}
|
| 557 |
+
|
| 558 |
+
# Insert patient
|
| 559 |
+
result = await patients_collection.insert_one(patient_doc)
|
| 560 |
+
imported_count += 1
|
| 561 |
+
imported_patients.append(patient_data['full_name'])
|
| 562 |
+
|
| 563 |
+
logger.info(f"Imported patient {patient_data['full_name']} with EHR ID {patient_data['ehr_id']}")
|
| 564 |
+
|
| 565 |
+
except Exception as e:
|
| 566 |
+
error_msg = f"Error importing patient {patient_data.get('full_name', 'Unknown')}: {str(e)}"
|
| 567 |
+
errors.append(error_msg)
|
| 568 |
+
logger.error(error_msg)
|
| 569 |
|
| 570 |
return {
|
| 571 |
+
"message": f"Import completed: {imported_count} imported, {skipped_count} skipped, {len(errors)} errors",
|
| 572 |
+
"imported_count": imported_count,
|
| 573 |
+
"skipped_count": skipped_count,
|
| 574 |
+
"error_count": len(errors),
|
| 575 |
"imported_patients": imported_patients,
|
| 576 |
+
"skipped_patients": skipped_patients,
|
| 577 |
+
"errors": errors,
|
| 578 |
+
"ehr_system": ehr_system
|
| 579 |
}
|
| 580 |
|
| 581 |
except Exception as e:
|
| 582 |
+
logger.error(f"Failed to import EHR patients: {str(e)}")
|
| 583 |
raise HTTPException(
|
| 584 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 585 |
+
detail=f"Failed to import EHR patients: {str(e)}"
|
| 586 |
+
)
|
| 587 |
+
|
| 588 |
+
@router.post("/patients/import-hapi-fhir", status_code=status.HTTP_201_CREATED)
|
| 589 |
+
async def import_hapi_fhir_patients(
|
| 590 |
+
limit: int = Query(20, ge=1, le=100, description="Number of patients to import"),
|
| 591 |
+
current_user: dict = Depends(get_current_user)
|
| 592 |
+
):
|
| 593 |
+
"""
|
| 594 |
+
Import patients from HAPI FHIR Test Server
|
| 595 |
+
"""
|
| 596 |
+
logger.info(f"Importing {limit} patients from HAPI FHIR by user {current_user.get('email')}")
|
| 597 |
+
|
| 598 |
+
if not any(role in current_user.get('roles', []) for role in ['admin', 'doctor']):
|
| 599 |
+
logger.warning(f"Unauthorized HAPI FHIR import attempt by {current_user.get('email')}")
|
| 600 |
+
raise HTTPException(
|
| 601 |
+
status_code=status.HTTP_403_FORBIDDEN,
|
| 602 |
+
detail="Only administrators and doctors can import patients"
|
| 603 |
+
)
|
| 604 |
+
|
| 605 |
+
try:
|
| 606 |
+
service = HAPIFHIRIntegrationService()
|
| 607 |
+
result = await service.import_patients_from_hapi(limit=limit)
|
| 608 |
+
|
| 609 |
+
return {
|
| 610 |
+
"message": f"Successfully imported {result.get('imported_count', 0)} patients from HAPI FHIR",
|
| 611 |
+
"imported_count": result.get('imported_count', 0),
|
| 612 |
+
"skipped_count": result.get('skipped_count', 0),
|
| 613 |
+
"error_count": result.get('error_count', 0),
|
| 614 |
+
"imported_patients": result.get('imported_patients', []),
|
| 615 |
+
"skipped_patients": result.get('skipped_patients', []),
|
| 616 |
+
"errors": result.get('errors', []),
|
| 617 |
+
"source": "hapi_fhir"
|
| 618 |
+
}
|
| 619 |
+
|
| 620 |
+
except Exception as e:
|
| 621 |
+
logger.error(f"Failed to import HAPI FHIR patients: {str(e)}")
|
| 622 |
+
raise HTTPException(
|
| 623 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 624 |
+
detail=f"Failed to import HAPI FHIR patients: {str(e)}"
|
| 625 |
)
|
| 626 |
|
| 627 |
@router.get("/patients/sources", response_model=List[dict])
|
|
|
|
| 758 |
# Add assigned doctor name
|
| 759 |
patient["assigned_doctor_name"] = assigned_doctor_name
|
| 760 |
|
| 761 |
+
# Clean date_of_birth field - convert empty strings to None
|
| 762 |
+
if patient.get("date_of_birth") == "":
|
| 763 |
+
patient["date_of_birth"] = None
|
| 764 |
+
|
| 765 |
processed_patients.append(patient)
|
| 766 |
|
| 767 |
logger.info(f"✅ Returning {len(processed_patients)} processed patients")
|
| 768 |
|
| 769 |
+
try:
|
| 770 |
+
return PatientListResponse(
|
| 771 |
+
patients=processed_patients,
|
| 772 |
+
total=total,
|
| 773 |
+
page=page,
|
| 774 |
+
limit=limit,
|
| 775 |
+
source_filter=source,
|
| 776 |
+
status_filter=patient_status
|
| 777 |
+
)
|
| 778 |
+
except Exception as validation_error:
|
| 779 |
+
logger.error(f"❌ Validation error for PatientListResponse: {str(validation_error)}")
|
| 780 |
+
# Log the problematic data for debugging
|
| 781 |
+
for i, patient in enumerate(processed_patients):
|
| 782 |
+
logger.error(f"Patient {i}: {patient.get('full_name', 'Unknown')} - date_of_birth: '{patient.get('date_of_birth')}'")
|
| 783 |
+
raise HTTPException(
|
| 784 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 785 |
+
detail=f"Data validation error: {str(validation_error)}"
|
| 786 |
+
)
|
| 787 |
|
| 788 |
except Exception as e:
|
| 789 |
logger.error(f"❌ Error fetching patients: {str(e)}")
|
models/schemas.py
CHANGED
|
@@ -140,7 +140,7 @@ class PatientUpdate(BaseModel):
|
|
| 140 |
class PatientResponse(BaseModel):
|
| 141 |
id: str
|
| 142 |
full_name: str
|
| 143 |
-
date_of_birth: date
|
| 144 |
gender: str
|
| 145 |
notes: Optional[Union[str, List[dict]]] = [] # Can be string or list of dicts
|
| 146 |
address: Optional[str] = None
|
|
|
|
| 140 |
class PatientResponse(BaseModel):
|
| 141 |
id: str
|
| 142 |
full_name: str
|
| 143 |
+
date_of_birth: Optional[date] = None
|
| 144 |
gender: str
|
| 145 |
notes: Optional[Union[str, List[dict]]] = [] # Can be string or list of dicts
|
| 146 |
address: Optional[str] = None
|