Spaces:
Sleeping
Sleeping
Update api/routes.py
Browse files- api/routes.py +77 -37
api/routes.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
from fastapi import APIRouter, HTTPException, Depends, Body
|
| 2 |
from fastapi.security import OAuth2PasswordRequestForm
|
| 3 |
from models.schemas import SignupForm, TokenResponse, PatientCreate, DoctorCreate, AppointmentCreate
|
| 4 |
from db.mongo import users_collection, patients_collection, appointments_collection
|
|
@@ -6,8 +6,12 @@ from core.security import hash_password, verify_password, create_access_token, g
|
|
| 6 |
from datetime import datetime
|
| 7 |
from bson import ObjectId
|
| 8 |
from bson.errors import InvalidId
|
| 9 |
-
from typing import Optional
|
| 10 |
from pydantic import BaseModel
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
router = APIRouter()
|
| 13 |
|
|
@@ -88,7 +92,7 @@ async def get_me(current_user: dict = Depends(get_current_user)):
|
|
| 88 |
raise HTTPException(status_code=404, detail="User not found")
|
| 89 |
|
| 90 |
return {
|
| 91 |
-
"id": str(user["_id"]),
|
| 92 |
"email": user["email"],
|
| 93 |
"full_name": user.get("full_name", ""),
|
| 94 |
"role": user.get("role", "unknown"),
|
|
@@ -118,15 +122,15 @@ async def list_patients(current_user: dict = Depends(get_current_user)):
|
|
| 118 |
if current_user.get("role") != "doctor":
|
| 119 |
raise HTTPException(status_code=403, detail="Only doctors can view patients")
|
| 120 |
|
| 121 |
-
|
| 122 |
patients = []
|
| 123 |
-
async for
|
| 124 |
patients.append({
|
| 125 |
-
"id": str(
|
| 126 |
-
"full_name":
|
| 127 |
-
"date_of_birth":
|
| 128 |
-
"gender":
|
| 129 |
-
"notes":
|
| 130 |
})
|
| 131 |
return patients
|
| 132 |
|
|
@@ -144,16 +148,14 @@ async def create_appointment(data: AppointmentCreate, current_user: dict = Depen
|
|
| 144 |
if current_user.get("role") != "patient":
|
| 145 |
raise HTTPException(status_code=403, detail="Only patients can book appointments")
|
| 146 |
|
| 147 |
-
# Get patient user info
|
| 148 |
patient_user = await users_collection.find_one({"email": current_user["email"]})
|
| 149 |
if not patient_user:
|
| 150 |
raise HTTPException(status_code=404, detail="Patient user not found")
|
| 151 |
|
| 152 |
-
# Insert appointment
|
| 153 |
appointment_doc = {
|
| 154 |
"patient_id": patient_user["_id"],
|
| 155 |
"doctor_id": ObjectId(data.doctor_id),
|
| 156 |
-
"date":
|
| 157 |
"time": data.time.strftime("%H:%M:%S"),
|
| 158 |
"reason": data.reason,
|
| 159 |
"created_by": current_user["email"],
|
|
@@ -161,30 +163,8 @@ async def create_appointment(data: AppointmentCreate, current_user: dict = Depen
|
|
| 161 |
}
|
| 162 |
await appointments_collection.insert_one(appointment_doc)
|
| 163 |
|
| 164 |
-
# Auto-add to doctor's patient list if not already
|
| 165 |
-
existing = await patients_collection.find_one({
|
| 166 |
-
"user_email": current_user["email"],
|
| 167 |
-
"created_by": await get_doctor_email_by_id(data.doctor_id)
|
| 168 |
-
})
|
| 169 |
-
if not existing:
|
| 170 |
-
await patients_collection.insert_one({
|
| 171 |
-
"full_name": patient_user.get("full_name", ""),
|
| 172 |
-
"user_email": patient_user["email"],
|
| 173 |
-
"gender": "",
|
| 174 |
-
"created_by": await get_doctor_email_by_id(data.doctor_id),
|
| 175 |
-
"created_at": datetime.utcnow()
|
| 176 |
-
})
|
| 177 |
-
|
| 178 |
return {"message": "Appointment booked successfully"}
|
| 179 |
|
| 180 |
-
# --- Helper function ---
|
| 181 |
-
async def get_doctor_email_by_id(doctor_id: str) -> Optional[str]:
|
| 182 |
-
try:
|
| 183 |
-
doc = await users_collection.find_one({"_id": ObjectId(doctor_id), "role": "doctor"})
|
| 184 |
-
return doc["email"] if doc else None
|
| 185 |
-
except Exception:
|
| 186 |
-
return None
|
| 187 |
-
|
| 188 |
# --- LIST DOCTOR'S APPOINTMENTS ---
|
| 189 |
@router.get("/appointments/doctor")
|
| 190 |
async def list_doctor_appointments(current_user: dict = Depends(get_current_user)):
|
|
@@ -193,13 +173,11 @@ async def list_doctor_appointments(current_user: dict = Depends(get_current_user
|
|
| 193 |
|
| 194 |
cursor = appointments_collection.find({"doctor_id": ObjectId(current_user["_id"])})
|
| 195 |
appointments = []
|
| 196 |
-
|
| 197 |
async for a in cursor:
|
| 198 |
try:
|
| 199 |
patient_id = a.get("patient_id")
|
| 200 |
if not isinstance(patient_id, ObjectId):
|
| 201 |
patient_id = ObjectId(patient_id)
|
| 202 |
-
|
| 203 |
patient = await users_collection.find_one({"_id": patient_id})
|
| 204 |
except Exception:
|
| 205 |
patient = None
|
|
@@ -231,3 +209,65 @@ async def list_my_appointments(current_user: dict = Depends(get_current_user)):
|
|
| 231 |
patient_id = user.get("_id")
|
| 232 |
cursor = appointments_collection.find({"patient_id": patient_id})
|
| 233 |
return [{**a, "_id": str(a["_id"]), "patient_id": str(a["patient_id"]), "doctor_id": str(a["doctor_id"])} async for a in cursor]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter, HTTPException, Depends, Body, UploadFile, File
|
| 2 |
from fastapi.security import OAuth2PasswordRequestForm
|
| 3 |
from models.schemas import SignupForm, TokenResponse, PatientCreate, DoctorCreate, AppointmentCreate
|
| 4 |
from db.mongo import users_collection, patients_collection, appointments_collection
|
|
|
|
| 6 |
from datetime import datetime
|
| 7 |
from bson import ObjectId
|
| 8 |
from bson.errors import InvalidId
|
| 9 |
+
from typing import Optional, List
|
| 10 |
from pydantic import BaseModel
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
import csv
|
| 13 |
+
import io
|
| 14 |
+
import json
|
| 15 |
|
| 16 |
router = APIRouter()
|
| 17 |
|
|
|
|
| 92 |
raise HTTPException(status_code=404, detail="User not found")
|
| 93 |
|
| 94 |
return {
|
| 95 |
+
"id": str(user["_id"]),
|
| 96 |
"email": user["email"],
|
| 97 |
"full_name": user.get("full_name", ""),
|
| 98 |
"role": user.get("role", "unknown"),
|
|
|
|
| 122 |
if current_user.get("role") != "doctor":
|
| 123 |
raise HTTPException(status_code=403, detail="Only doctors can view patients")
|
| 124 |
|
| 125 |
+
cursor = patients_collection.find({"created_by": current_user["email"]})
|
| 126 |
patients = []
|
| 127 |
+
async for p in cursor:
|
| 128 |
patients.append({
|
| 129 |
+
"id": str(p["_id"]),
|
| 130 |
+
"full_name": p.get("full_name", ""),
|
| 131 |
+
"date_of_birth": p.get("date_of_birth"),
|
| 132 |
+
"gender": p.get("gender", ""),
|
| 133 |
+
"notes": p.get("notes", "")
|
| 134 |
})
|
| 135 |
return patients
|
| 136 |
|
|
|
|
| 148 |
if current_user.get("role") != "patient":
|
| 149 |
raise HTTPException(status_code=403, detail="Only patients can book appointments")
|
| 150 |
|
|
|
|
| 151 |
patient_user = await users_collection.find_one({"email": current_user["email"]})
|
| 152 |
if not patient_user:
|
| 153 |
raise HTTPException(status_code=404, detail="Patient user not found")
|
| 154 |
|
|
|
|
| 155 |
appointment_doc = {
|
| 156 |
"patient_id": patient_user["_id"],
|
| 157 |
"doctor_id": ObjectId(data.doctor_id),
|
| 158 |
+
"date": datetime.combine(data.date, datetime.min.time()),
|
| 159 |
"time": data.time.strftime("%H:%M:%S"),
|
| 160 |
"reason": data.reason,
|
| 161 |
"created_by": current_user["email"],
|
|
|
|
| 163 |
}
|
| 164 |
await appointments_collection.insert_one(appointment_doc)
|
| 165 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
return {"message": "Appointment booked successfully"}
|
| 167 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
# --- LIST DOCTOR'S APPOINTMENTS ---
|
| 169 |
@router.get("/appointments/doctor")
|
| 170 |
async def list_doctor_appointments(current_user: dict = Depends(get_current_user)):
|
|
|
|
| 173 |
|
| 174 |
cursor = appointments_collection.find({"doctor_id": ObjectId(current_user["_id"])})
|
| 175 |
appointments = []
|
|
|
|
| 176 |
async for a in cursor:
|
| 177 |
try:
|
| 178 |
patient_id = a.get("patient_id")
|
| 179 |
if not isinstance(patient_id, ObjectId):
|
| 180 |
patient_id = ObjectId(patient_id)
|
|
|
|
| 181 |
patient = await users_collection.find_one({"_id": patient_id})
|
| 182 |
except Exception:
|
| 183 |
patient = None
|
|
|
|
| 209 |
patient_id = user.get("_id")
|
| 210 |
cursor = appointments_collection.find({"patient_id": patient_id})
|
| 211 |
return [{**a, "_id": str(a["_id"]), "patient_id": str(a["patient_id"]), "doctor_id": str(a["doctor_id"])} async for a in cursor]
|
| 212 |
+
|
| 213 |
+
# === UTIL: Parse Synthea CSV ===
|
| 214 |
+
def parse_synthea_patient_csv(file: UploadFile) -> List[dict]:
|
| 215 |
+
decoded = file.file.read().decode("utf-8")
|
| 216 |
+
reader = csv.DictReader(io.StringIO(decoded))
|
| 217 |
+
patients = []
|
| 218 |
+
|
| 219 |
+
for row in reader:
|
| 220 |
+
patients.append({
|
| 221 |
+
"synthea_id": row.get("Id"),
|
| 222 |
+
"full_name": f"{row.get('FIRST', '')} {row.get('LAST', '')}".strip(),
|
| 223 |
+
"gender": row.get("GENDER"),
|
| 224 |
+
"date_of_birth": datetime.strptime(row.get("BIRTHDATE"), "%Y-%m-%dT%H:%M:%SZ"),
|
| 225 |
+
"address": row.get("ADDRESS"),
|
| 226 |
+
"city": row.get("CITY"),
|
| 227 |
+
"state": row.get("STATE"),
|
| 228 |
+
"zip": row.get("ZIP"),
|
| 229 |
+
"created_at": datetime.utcnow()
|
| 230 |
+
})
|
| 231 |
+
|
| 232 |
+
return patients
|
| 233 |
+
|
| 234 |
+
# === API: Upload and import patients.csv ===
|
| 235 |
+
@router.post("/ehr/import-patients")
|
| 236 |
+
async def import_synthea_patients(file: UploadFile = File(...)):
|
| 237 |
+
if not file.filename.endswith(".csv"):
|
| 238 |
+
raise HTTPException(status_code=400, detail="Only CSV files are supported")
|
| 239 |
+
|
| 240 |
+
try:
|
| 241 |
+
patients = parse_synthea_patient_csv(file)
|
| 242 |
+
result = await patients_collection.insert_many(patients)
|
| 243 |
+
return {"inserted_count": len(result.inserted_ids)}
|
| 244 |
+
except Exception as e:
|
| 245 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 246 |
+
|
| 247 |
+
# === GET all Synthea patients ===
|
| 248 |
+
@router.get("/ehr/synthea-patients")
|
| 249 |
+
async def list_synthea_patients():
|
| 250 |
+
cursor = patients_collection.find({"synthea_id": {"$exists": True}})
|
| 251 |
+
patients = []
|
| 252 |
+
async for p in cursor:
|
| 253 |
+
patients.append({
|
| 254 |
+
"id": str(p["_id"]),
|
| 255 |
+
"synthea_id": p.get("synthea_id"),
|
| 256 |
+
"full_name": p.get("full_name"),
|
| 257 |
+
"gender": p.get("gender"),
|
| 258 |
+
"date_of_birth": p.get("date_of_birth"),
|
| 259 |
+
})
|
| 260 |
+
return patients
|
| 261 |
+
|
| 262 |
+
# === GET individual Synthea patient ===
|
| 263 |
+
@router.get("/ehr/synthea-patient/{id}")
|
| 264 |
+
async def get_synthea_patient(id: str):
|
| 265 |
+
try:
|
| 266 |
+
patient = await patients_collection.find_one({"_id": ObjectId(id)})
|
| 267 |
+
if not patient:
|
| 268 |
+
raise HTTPException(status_code=404, detail="Patient not found")
|
| 269 |
+
patient["id"] = str(patient["_id"])
|
| 270 |
+
del patient["_id"]
|
| 271 |
+
return patient
|
| 272 |
+
except Exception:
|
| 273 |
+
raise HTTPException(status_code=400, detail="Invalid patient ID")
|