Spaces:
Sleeping
Sleeping
dylanglenister
commited on
Commit
·
521a291
1
Parent(s):
56e2429
Updating patient routes
Browse files- src/api/routes/patient.py +101 -91
- src/core/memory_manager.py +43 -0
- src/models/patient.py +26 -0
- src/models/user.py +0 -28
src/api/routes/patient.py
CHANGED
|
@@ -1,97 +1,107 @@
|
|
| 1 |
-
# api/routes/patient.py
|
| 2 |
|
| 3 |
-
from
|
| 4 |
-
from bson.errors import InvalidId
|
| 5 |
-
from fastapi import APIRouter, HTTPException
|
| 6 |
|
| 7 |
-
from src.
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
from src.
|
| 11 |
-
from src.models.user import PatientCreateRequest, PatientUpdateRequest
|
| 12 |
from src.utils.logger import logger
|
| 13 |
|
| 14 |
router = APIRouter(prefix="/patient", tags=["Patient"])
|
| 15 |
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# src/api/routes/patient.py
|
| 2 |
|
| 3 |
+
from fastapi import APIRouter, Depends, HTTPException, status
|
|
|
|
|
|
|
| 4 |
|
| 5 |
+
from src.core.state import AppState, get_state
|
| 6 |
+
from src.models.patient import (Patient, PatientCreateRequest,
|
| 7 |
+
PatientUpdateRequest)
|
| 8 |
+
from src.models.session import Session
|
|
|
|
| 9 |
from src.utils.logger import logger
|
| 10 |
|
| 11 |
router = APIRouter(prefix="/patient", tags=["Patient"])
|
| 12 |
|
| 13 |
+
|
| 14 |
+
@router.get("", response_model=list[Patient])
|
| 15 |
+
async def search_or_get_all_patients(
|
| 16 |
+
q: str | None = None,
|
| 17 |
+
limit: int = 20,
|
| 18 |
+
state: AppState = Depends(get_state)
|
| 19 |
+
):
|
| 20 |
+
"""
|
| 21 |
+
Searches for patients by name if a query 'q' is provided.
|
| 22 |
+
Otherwise, this endpoint would list all patients (functionality not yet in repo).
|
| 23 |
+
"""
|
| 24 |
+
logger().info(f"GET /patient?q='{q}' limit={limit}")
|
| 25 |
+
if not q:
|
| 26 |
+
# In the future, you could add a get_all_patients method here.
|
| 27 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="A search query 'q' is required.")
|
| 28 |
+
|
| 29 |
+
patients = state.memory_manager.search_patients(q, limit=limit)
|
| 30 |
+
logger().info(f"Search returned {len(patients)} results")
|
| 31 |
+
return patients
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
@router.post("", response_model=Patient, status_code=status.HTTP_201_CREATED)
|
| 35 |
+
async def create_patient_profile(
|
| 36 |
+
req: PatientCreateRequest,
|
| 37 |
+
state: AppState = Depends(get_state)
|
| 38 |
+
):
|
| 39 |
+
"""Creates a new patient profile."""
|
| 40 |
+
logger().info(f"POST /patient name={req.name}")
|
| 41 |
+
patient_id = state.memory_manager.create_patient(**req.model_dump())
|
| 42 |
+
|
| 43 |
+
if not patient_id:
|
| 44 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Patient could not be created due to invalid data.")
|
| 45 |
+
|
| 46 |
+
new_patient = state.memory_manager.get_patient_by_id(patient_id)
|
| 47 |
+
if not new_patient:
|
| 48 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Could not find newly created patient.")
|
| 49 |
+
|
| 50 |
+
logger().info(f"Created patient {req.name} id={patient_id}")
|
| 51 |
+
return new_patient
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
@router.get("/{patient_id}", response_model=Patient)
|
| 55 |
+
async def get_patient_by_id(
|
| 56 |
+
patient_id: str,
|
| 57 |
+
state: AppState = Depends(get_state)
|
| 58 |
+
):
|
| 59 |
+
"""Retrieves a single patient by their unique ID."""
|
| 60 |
+
logger().info(f"GET /patient/{patient_id}")
|
| 61 |
+
patient = state.memory_manager.get_patient_by_id(patient_id)
|
| 62 |
+
if not patient:
|
| 63 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Patient not found")
|
| 64 |
+
return patient
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
@router.patch("/{patient_id}", response_model=Patient)
|
| 68 |
+
async def update_patient(
|
| 69 |
+
patient_id: str,
|
| 70 |
+
req: PatientUpdateRequest,
|
| 71 |
+
state: AppState = Depends(get_state)
|
| 72 |
+
):
|
| 73 |
+
"""Updates a patient's profile with the provided fields."""
|
| 74 |
+
# Get only the fields that the client actually sent
|
| 75 |
+
updates = req.model_dump(exclude_unset=True)
|
| 76 |
+
if not updates:
|
| 77 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="No update fields provided.")
|
| 78 |
+
|
| 79 |
+
logger().info(f"PATCH /patient/{patient_id} fields={list(updates.keys())}")
|
| 80 |
+
modified_count = state.memory_manager.update_patient_profile(patient_id, updates)
|
| 81 |
+
|
| 82 |
+
if modified_count == 0:
|
| 83 |
+
# Check if the patient exists to differentiate between "not found" and "no changes made"
|
| 84 |
+
if not state.memory_manager.get_patient_by_id(patient_id):
|
| 85 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Patient not found")
|
| 86 |
+
|
| 87 |
+
# Return the full, updated patient object
|
| 88 |
+
updated_patient = state.memory_manager.get_patient_by_id(patient_id)
|
| 89 |
+
if not updated_patient:
|
| 90 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Could not find patient after update.")
|
| 91 |
+
|
| 92 |
+
return updated_patient
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
@router.get("/{patient_id}/session", response_model=list[Session])
|
| 96 |
+
async def list_sessions_for_patient(
|
| 97 |
+
patient_id: str,
|
| 98 |
+
state: AppState = Depends(get_state)
|
| 99 |
+
):
|
| 100 |
+
"""Lists all chat sessions associated with a specific patient."""
|
| 101 |
+
logger().info(f"GET /patient/{patient_id}/session")
|
| 102 |
+
# First, verify the patient exists
|
| 103 |
+
if not state.memory_manager.get_patient_by_id(patient_id):
|
| 104 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Patient not found")
|
| 105 |
+
|
| 106 |
+
sessions = state.memory_manager.list_patient_sessions(patient_id)
|
| 107 |
+
return sessions
|
src/core/memory_manager.py
CHANGED
|
@@ -3,8 +3,10 @@
|
|
| 3 |
from src.data.connection import ActionFailed
|
| 4 |
from src.data.repositories import account as account_repo
|
| 5 |
from src.data.repositories import medical_memory as memory_repo
|
|
|
|
| 6 |
from src.data.repositories import session as session_repo
|
| 7 |
from src.models.account import Account
|
|
|
|
| 8 |
from src.models.session import Session
|
| 9 |
from src.services import summariser
|
| 10 |
from src.services.nvidia import nvidia_chat
|
|
@@ -60,6 +62,39 @@ class MemoryManager:
|
|
| 60 |
except ActionFailed as e:
|
| 61 |
logger().error(f"Failed to search accounts in MemoryManager: {e}")
|
| 62 |
return []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
# --- Session Management Facade ---
|
| 65 |
|
|
@@ -95,6 +130,14 @@ class MemoryManager:
|
|
| 95 |
logger().error(f"Failed to update title for session '{session_id}': {e}")
|
| 96 |
return False
|
| 97 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
# --- Core Business Logic ---
|
| 99 |
|
| 100 |
async def process_medical_exchange(
|
|
|
|
| 3 |
from src.data.connection import ActionFailed
|
| 4 |
from src.data.repositories import account as account_repo
|
| 5 |
from src.data.repositories import medical_memory as memory_repo
|
| 6 |
+
from src.data.repositories import patient as patient_repo
|
| 7 |
from src.data.repositories import session as session_repo
|
| 8 |
from src.models.account import Account
|
| 9 |
+
from src.models.patient import Patient
|
| 10 |
from src.models.session import Session
|
| 11 |
from src.services import summariser
|
| 12 |
from src.services.nvidia import nvidia_chat
|
|
|
|
| 62 |
except ActionFailed as e:
|
| 63 |
logger().error(f"Failed to search accounts in MemoryManager: {e}")
|
| 64 |
return []
|
| 65 |
+
# --- Patient Management Facade ---
|
| 66 |
+
|
| 67 |
+
def create_patient(self, **kwargs) -> str | None:
|
| 68 |
+
"""Creates a new patient record."""
|
| 69 |
+
try:
|
| 70 |
+
return patient_repo.create_patient(**kwargs)
|
| 71 |
+
except ActionFailed as e:
|
| 72 |
+
logger().error(f"Failed to create patient in MemoryManager: {e}")
|
| 73 |
+
return None
|
| 74 |
+
|
| 75 |
+
def get_patient_by_id(self, patient_id: str) -> Patient | None:
|
| 76 |
+
"""Retrieves a patient by their unique ID."""
|
| 77 |
+
try:
|
| 78 |
+
return patient_repo.get_patient_by_id(patient_id)
|
| 79 |
+
except ActionFailed as e:
|
| 80 |
+
logger().error(f"Failed to get patient '{patient_id}' in MemoryManager: {e}")
|
| 81 |
+
return None
|
| 82 |
+
|
| 83 |
+
def update_patient_profile(self, patient_id: str, updates: dict) -> int:
|
| 84 |
+
"""Updates a patient's profile."""
|
| 85 |
+
try:
|
| 86 |
+
return patient_repo.update_patient_profile(patient_id, updates)
|
| 87 |
+
except ActionFailed as e:
|
| 88 |
+
logger().error(f"Failed to update patient '{patient_id}' in MemoryManager: {e}")
|
| 89 |
+
return 0
|
| 90 |
+
|
| 91 |
+
def search_patients(self, query: str, limit: int = 10) -> list[Patient]:
|
| 92 |
+
"""Searches for patients by name."""
|
| 93 |
+
try:
|
| 94 |
+
return patient_repo.search_patients(query, limit=limit)
|
| 95 |
+
except ActionFailed as e:
|
| 96 |
+
logger().error(f"Failed to search patients in MemoryManager: {e}")
|
| 97 |
+
return []
|
| 98 |
|
| 99 |
# --- Session Management Facade ---
|
| 100 |
|
|
|
|
| 130 |
logger().error(f"Failed to update title for session '{session_id}': {e}")
|
| 131 |
return False
|
| 132 |
|
| 133 |
+
def list_patient_sessions(self, patient_id: str) -> list[Session]:
|
| 134 |
+
"""Retrieves all sessions for a specific patient."""
|
| 135 |
+
try:
|
| 136 |
+
return session_repo.list_patient_sessions(patient_id, limit=self.max_sessions_per_user)
|
| 137 |
+
except ActionFailed as e:
|
| 138 |
+
logger().error(f"Failed to get sessions for patient '{patient_id}': {e}")
|
| 139 |
+
return []
|
| 140 |
+
|
| 141 |
# --- Core Business Logic ---
|
| 142 |
|
| 143 |
async def process_medical_exchange(
|
src/models/patient.py
CHANGED
|
@@ -2,6 +2,8 @@
|
|
| 2 |
|
| 3 |
from datetime import datetime
|
| 4 |
|
|
|
|
|
|
|
| 5 |
from src.models.common import BaseMongoModel, PyObjectId
|
| 6 |
|
| 7 |
|
|
@@ -19,3 +21,27 @@ class Patient(BaseMongoModel):
|
|
| 19 |
medications: list[str] | None = None
|
| 20 |
past_assessment_summary: str | None = None
|
| 21 |
assigned_doctor_id: PyObjectId | None = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
from datetime import datetime
|
| 4 |
|
| 5 |
+
from pydantic import BaseModel
|
| 6 |
+
|
| 7 |
from src.models.common import BaseMongoModel, PyObjectId
|
| 8 |
|
| 9 |
|
|
|
|
| 21 |
medications: list[str] | None = None
|
| 22 |
past_assessment_summary: str | None = None
|
| 23 |
assigned_doctor_id: PyObjectId | None = None
|
| 24 |
+
|
| 25 |
+
class PatientCreateRequest(BaseModel):
|
| 26 |
+
name: str
|
| 27 |
+
age: int
|
| 28 |
+
sex: str
|
| 29 |
+
ethnicity: str
|
| 30 |
+
address: str | None = None
|
| 31 |
+
phone: str | None = None
|
| 32 |
+
email: str | None = None
|
| 33 |
+
medications: list[str] | None = None
|
| 34 |
+
past_assessment_summary: str | None = None
|
| 35 |
+
assigned_doctor_id: str | None = None
|
| 36 |
+
|
| 37 |
+
class PatientUpdateRequest(BaseModel):
|
| 38 |
+
name: str | None = None
|
| 39 |
+
age: int | None = None
|
| 40 |
+
sex: str | None = None
|
| 41 |
+
ethnicity: str | None = None
|
| 42 |
+
address: str | None = None
|
| 43 |
+
phone: str | None = None
|
| 44 |
+
email: str | None = None
|
| 45 |
+
medications: list[str] | None = None
|
| 46 |
+
past_assessment_summary: str | None = None
|
| 47 |
+
assigned_doctor_id: str | None = None
|
src/models/user.py
DELETED
|
@@ -1,28 +0,0 @@
|
|
| 1 |
-
# src/model/user.py
|
| 2 |
-
|
| 3 |
-
from pydantic import BaseModel
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
class PatientCreateRequest(BaseModel):
|
| 7 |
-
name: str
|
| 8 |
-
age: int
|
| 9 |
-
sex: str
|
| 10 |
-
ethnicity: str
|
| 11 |
-
address: str | None = None
|
| 12 |
-
phone: str | None = None
|
| 13 |
-
email: str | None = None
|
| 14 |
-
medications: list[str] | None = None
|
| 15 |
-
past_assessment_summary: str | None = None
|
| 16 |
-
assigned_doctor_id: str | None = None
|
| 17 |
-
|
| 18 |
-
class PatientUpdateRequest(BaseModel):
|
| 19 |
-
name: str | None = None
|
| 20 |
-
age: int | None = None
|
| 21 |
-
sex: str | None = None
|
| 22 |
-
ethnicity: str | None = None
|
| 23 |
-
address: str | None = None
|
| 24 |
-
phone: str | None = None
|
| 25 |
-
email: str | None = None
|
| 26 |
-
medications: list[str] | None = None
|
| 27 |
-
past_assessment_summary: str | None = None
|
| 28 |
-
assigned_doctor_id: str | None = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|