added wristband viewing inside of the control software for real and mock wristbands
This commit is contained in:
+29
-56
@@ -14,7 +14,7 @@ import json
|
||||
from collections import defaultdict
|
||||
|
||||
# ============================================================================
|
||||
# LIFESPAN MANAGEMENT (Modern FastAPI Way)
|
||||
# LIFESPAN MANAGEMENT
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@@ -38,10 +38,9 @@ async def lifespan(app: FastAPI):
|
||||
|
||||
app = FastAPI(title="VitalLink API", version="1.0.0", lifespan=lifespan)
|
||||
|
||||
# CORS middleware for frontend
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"], # In production, specify your frontend domain
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
@@ -100,7 +99,7 @@ class QueuePosition(BaseModel):
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# IN-MEMORY STORAGE (Replace with database in production)
|
||||
# IN-MEMORY STORAGE
|
||||
# ============================================================================
|
||||
|
||||
patients_db: Dict[str, Patient] = {}
|
||||
@@ -110,58 +109,44 @@ available_bands = [
|
||||
]
|
||||
active_websockets: List[WebSocket] = []
|
||||
|
||||
# Wristband details cache
|
||||
wristband_details_cache = {}
|
||||
|
||||
# ============================================================================
|
||||
# PRIORITY ALGORITHM
|
||||
# ============================================================================
|
||||
|
||||
|
||||
def calculate_priority_score(patient: Patient) -> float:
|
||||
"""
|
||||
Calculate dynamic priority score for queue ordering
|
||||
Higher score = higher priority
|
||||
|
||||
Factors:
|
||||
- Tier (Emergency=100, Alert=50, Normal=0)
|
||||
- Vital sign trends (worsening = higher)
|
||||
- Wait time (exponential increase after threshold)
|
||||
- Initial severity
|
||||
"""
|
||||
score = 0.0
|
||||
|
||||
# Tier contribution (largest factor)
|
||||
tier_scores = {"EMERGENCY": 100, "ALERT": 50, "NORMAL": 0}
|
||||
score += tier_scores.get(patient.current_tier, 0)
|
||||
|
||||
# Wait time contribution (increases exponentially after 30 min)
|
||||
wait_minutes = (datetime.now() - patient.check_in_time).total_seconds() / 60
|
||||
if wait_minutes > 30:
|
||||
score += (wait_minutes - 30) * 0.5 # 0.5 points per minute over 30
|
||||
score += (wait_minutes - 30) * 0.5
|
||||
elif wait_minutes > 60:
|
||||
score += (wait_minutes - 60) * 1.0 # Accelerate after 1 hour
|
||||
score += (wait_minutes - 60) * 1.0
|
||||
|
||||
# Initial severity contribution
|
||||
severity_scores = {"severe": 20, "moderate": 10, "mild": 5}
|
||||
score += severity_scores.get(patient.severity, 0)
|
||||
|
||||
# Vital signs contribution (if available)
|
||||
if patient.last_vitals:
|
||||
hr = patient.last_vitals.get("hr_bpm", 75)
|
||||
spo2 = patient.last_vitals.get("spo2", 98)
|
||||
temp = patient.last_vitals.get("temp_c", 37.0)
|
||||
|
||||
# Abnormal HR
|
||||
if hr > 110 or hr < 50:
|
||||
score += 10
|
||||
if hr > 140 or hr < 40:
|
||||
score += 30
|
||||
|
||||
# Low SpO2 (critical)
|
||||
if spo2 < 92:
|
||||
score += 15
|
||||
if spo2 < 88:
|
||||
score += 40
|
||||
|
||||
# Fever
|
||||
if temp > 38.5:
|
||||
score += 15
|
||||
if temp > 39.5:
|
||||
@@ -177,7 +162,6 @@ def calculate_priority_score(patient: Patient) -> float:
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
"""Root endpoint"""
|
||||
return {
|
||||
"message": "VitalLink Backend API",
|
||||
"version": "1.0.0",
|
||||
@@ -188,16 +172,12 @@ async def root():
|
||||
|
||||
@app.post("/api/checkin")
|
||||
async def check_in_patient(data: PatientCheckIn):
|
||||
"""Register a new patient and assign wristband"""
|
||||
|
||||
if not available_bands:
|
||||
raise HTTPException(status_code=503, detail="No wristbands available")
|
||||
|
||||
# Assign IDs
|
||||
patient_id = f"P{len(patients_db) + 100001}"
|
||||
band_id = available_bands.pop(0)
|
||||
|
||||
# Create patient record
|
||||
patient = Patient(
|
||||
patient_id=patient_id,
|
||||
band_id=band_id,
|
||||
@@ -212,7 +192,6 @@ async def check_in_patient(data: PatientCheckIn):
|
||||
|
||||
patients_db[patient_id] = patient
|
||||
|
||||
# Notify connected clients
|
||||
await broadcast_update({"type": "patient_added", "patient": patient.dict()})
|
||||
|
||||
return {
|
||||
@@ -224,24 +203,19 @@ async def check_in_patient(data: PatientCheckIn):
|
||||
|
||||
@app.post("/api/vitals")
|
||||
async def receive_vitals(data: VitalsData):
|
||||
"""Receive vitals data from base station"""
|
||||
|
||||
patient_id = data.patient_id
|
||||
|
||||
if patient_id not in patients_db:
|
||||
raise HTTPException(status_code=404, detail="Patient not found")
|
||||
|
||||
# Update patient record
|
||||
patient = patients_db[patient_id]
|
||||
patient.current_tier = data.tier
|
||||
patient.last_vitals = data.dict()
|
||||
|
||||
# Store in history (keep last 1000 readings)
|
||||
vitals_history[patient_id].append(data)
|
||||
if len(vitals_history[patient_id]) > 1000:
|
||||
vitals_history[patient_id] = vitals_history[patient_id][-1000:]
|
||||
|
||||
# Broadcast to connected clients
|
||||
await broadcast_update(
|
||||
{"type": "vitals_update", "patient_id": patient_id, "vitals": data.dict()}
|
||||
)
|
||||
@@ -251,11 +225,8 @@ async def receive_vitals(data: VitalsData):
|
||||
|
||||
@app.get("/api/queue")
|
||||
async def get_queue():
|
||||
"""Get prioritized queue of active patients"""
|
||||
|
||||
active_patients = [p for p in patients_db.values() if p.is_active]
|
||||
|
||||
# Calculate priority and sort
|
||||
queue = []
|
||||
for patient in active_patients:
|
||||
priority_score = calculate_priority_score(patient)
|
||||
@@ -283,7 +254,6 @@ async def get_queue():
|
||||
)
|
||||
)
|
||||
|
||||
# Sort by priority (highest first)
|
||||
queue.sort(key=lambda x: x.priority_score, reverse=True)
|
||||
|
||||
return queue
|
||||
@@ -291,8 +261,6 @@ async def get_queue():
|
||||
|
||||
@app.get("/api/patients/{patient_id}")
|
||||
async def get_patient_details(patient_id: str):
|
||||
"""Get detailed information about a specific patient"""
|
||||
|
||||
if patient_id not in patients_db:
|
||||
raise HTTPException(status_code=404, detail="Patient not found")
|
||||
|
||||
@@ -301,25 +269,21 @@ async def get_patient_details(patient_id: str):
|
||||
|
||||
return {
|
||||
"patient": patient.dict(),
|
||||
"vitals_history": [v.dict() for v in history[-50:]], # Last 50 readings
|
||||
"vitals_history": [v.dict() for v in history[-50:]],
|
||||
"priority_score": calculate_priority_score(patient),
|
||||
}
|
||||
|
||||
|
||||
@app.post("/api/patients/{patient_id}/discharge")
|
||||
async def discharge_patient(patient_id: str):
|
||||
"""Discharge a patient and return wristband to pool"""
|
||||
|
||||
if patient_id not in patients_db:
|
||||
raise HTTPException(status_code=404, detail="Patient not found")
|
||||
|
||||
patient = patients_db[patient_id]
|
||||
patient.is_active = False
|
||||
|
||||
# Return band to pool
|
||||
available_bands.append(patient.band_id)
|
||||
|
||||
# Notify clients
|
||||
await broadcast_update({"type": "patient_discharged", "patient_id": patient_id})
|
||||
|
||||
return {"message": "Patient discharged", "band_returned": patient.band_id}
|
||||
@@ -327,8 +291,6 @@ async def discharge_patient(patient_id: str):
|
||||
|
||||
@app.get("/api/stats")
|
||||
async def get_statistics():
|
||||
"""Get overall ER statistics"""
|
||||
|
||||
active_patients = [p for p in patients_db.values() if p.is_active]
|
||||
|
||||
tier_counts = {"EMERGENCY": 0, "ALERT": 0, "NORMAL": 0}
|
||||
@@ -356,34 +318,46 @@ async def get_statistics():
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# WEBSOCKET FOR REAL-TIME UPDATES
|
||||
# WRISTBAND ENDPOINTS
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@app.post("/api/wristband-details")
|
||||
async def update_wristband_details(data: dict):
|
||||
"""Receive wristband details from wristband system"""
|
||||
global wristband_details_cache
|
||||
wristband_details_cache = data
|
||||
return {"status": "updated"}
|
||||
|
||||
|
||||
@app.get("/api/wristband-details")
|
||||
async def get_cached_wristband_details():
|
||||
"""Get cached wristband details"""
|
||||
return wristband_details_cache
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# WEBSOCKET
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@app.websocket("/ws")
|
||||
async def websocket_endpoint(websocket: WebSocket):
|
||||
"""WebSocket connection for real-time updates to frontend"""
|
||||
|
||||
await websocket.accept()
|
||||
active_websockets.append(websocket)
|
||||
|
||||
# Send initial data
|
||||
await websocket.send_json(
|
||||
{"type": "connected", "message": "Connected to VitalLink server"}
|
||||
)
|
||||
|
||||
try:
|
||||
while True:
|
||||
# Keep connection alive and listen for client messages
|
||||
data = await websocket.receive_text()
|
||||
# Could handle client commands here
|
||||
except:
|
||||
active_websockets.remove(websocket)
|
||||
|
||||
|
||||
async def broadcast_update(message: dict):
|
||||
"""Broadcast update to all connected WebSocket clients"""
|
||||
|
||||
disconnected = []
|
||||
for websocket in active_websockets:
|
||||
try:
|
||||
@@ -391,7 +365,6 @@ async def broadcast_update(message: dict):
|
||||
except:
|
||||
disconnected.append(websocket)
|
||||
|
||||
# Remove disconnected clients
|
||||
for ws in disconnected:
|
||||
active_websockets.remove(ws)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user