added wristband viewing inside of the control software for real and mock wristbands

This commit is contained in:
2025-10-18 18:06:37 -04:00
parent 8d04eb5594
commit e6aba90ee5
15 changed files with 567 additions and 575 deletions
+29 -56
View File
@@ -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)