updated features to reduce "Bouncing" from in and out of warning zones

This commit is contained in:
Raika Furude 2025-11-01 15:28:16 -04:00
parent 7cf5510401
commit 4cd0ac3fee
9 changed files with 1150 additions and 1515 deletions

View File

@ -11,6 +11,7 @@ from datetime import datetime, timedelta
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
import asyncio import asyncio
import json import json
import time
from collections import defaultdict from collections import defaultdict
from triage_engine import TriageEngine, VitalSigns, TriageLevel, triage_from_vitals from triage_engine import TriageEngine, VitalSigns, TriageLevel, triage_from_vitals
@ -105,6 +106,8 @@ class QueuePosition(BaseModel):
patients_db: Dict[str, Patient] = {} patients_db: Dict[str, Patient] = {}
vitals_history: Dict[str, List[VitalsData]] = defaultdict(list) vitals_history: Dict[str, List[VitalsData]] = defaultdict(list)
# Tier stability tracking
tier_trackers: Dict[str, Dict] = {} # patient_id -> tracker info
available_bands = [ available_bands = [
f"VitalLink-{hex(i)[2:].upper().zfill(4)}" for i in range(0x1000, 0x2000) f"VitalLink-{hex(i)[2:].upper().zfill(4)}" for i in range(0x1000, 0x2000)
] ]
@ -203,7 +206,7 @@ async def check_in_patient(data: PatientCheckIn):
@app.post("/api/vitals") @app.post("/api/vitals")
async def receive_vitals(data: VitalsData): async def receive_vitals(data: VitalsData):
"""Receive vitals data from base station""" """Receive vitals data from base station with tier stability"""
patient_id = data.patient_id patient_id = data.patient_id
@ -212,7 +215,19 @@ async def receive_vitals(data: VitalsData):
patient = patients_db[patient_id] patient = patients_db[patient_id]
# Use triage engine to determine tier from vitals # Initialize tier tracker if needed
if patient_id not in tier_trackers:
tier_trackers[patient_id] = {
"current_tier": "NORMAL",
"tier_since": time.time(),
"consecutive_readings": 0,
"pending_tier": None,
"pending_count": 0,
}
tracker = tier_trackers[patient_id]
# Use triage engine to determine what tier vitals suggest
vitals = VitalSigns( vitals = VitalSigns(
heart_rate=data.hr_bpm, heart_rate=data.hr_bpm,
spo2=data.spo2, spo2=data.spo2,
@ -220,10 +235,73 @@ async def receive_vitals(data: VitalsData):
activity=data.activity, activity=data.activity,
) )
# Quick tier assessment # Determine suggested tier
tier = triage_from_vitals(data.hr_bpm, data.spo2, data.temp_c) suggested_tier = triage_from_vitals(data.hr_bpm, data.spo2, data.temp_c)
patient.current_tier = tier # Apply tier stability logic
current_tier = tracker["current_tier"]
time_in_tier = time.time() - tracker["tier_since"]
# Determine if tier should change
should_change = False
if suggested_tier == current_tier:
# Same tier - reset pending change
tracker["consecutive_readings"] += 1
tracker["pending_tier"] = None
tracker["pending_count"] = 0
else:
# Different tier suggested
is_upgrade = (
suggested_tier == "EMERGENCY" and current_tier in ["ALERT", "NORMAL"]
) or (suggested_tier == "ALERT" and current_tier == "NORMAL")
is_downgrade = (
suggested_tier == "NORMAL" and current_tier in ["ALERT", "EMERGENCY"]
) or (suggested_tier == "ALERT" and current_tier == "EMERGENCY")
# Track pending change
if tracker["pending_tier"] == suggested_tier:
tracker["pending_count"] += 1
else:
tracker["pending_tier"] = suggested_tier
tracker["pending_count"] = 1
# Determine if we have enough confirmations
if is_upgrade:
# Upgrade to higher tier - need 2 consecutive readings
required_confirmations = 2
min_time_required = 10.0 # 10 seconds minimum
else:
# Downgrade to lower tier - need 5 consecutive readings
required_confirmations = 5
min_time_required = 60.0 # 60 seconds minimum
# Check if we should change tier
if (
tracker["pending_count"] >= required_confirmations
and time_in_tier >= min_time_required
):
should_change = True
# Apply tier change if confirmed
if should_change:
old_tier = tracker["current_tier"]
new_tier = suggested_tier
tracker["current_tier"] = new_tier
tracker["tier_since"] = time.time()
tracker["consecutive_readings"] = 0
tracker["pending_tier"] = None
tracker["pending_count"] = 0
print(
f"🔄 TIER CHANGE: {patient_id} {old_tier}{new_tier} (confirmed after {tracker['pending_count']} readings)"
)
# Use confirmed tier
final_tier = tracker["current_tier"]
patient.current_tier = final_tier
patient.last_vitals = data.dict() patient.last_vitals = data.dict()
# Store in history # Store in history
@ -243,17 +321,28 @@ async def receive_vitals(data: VitalsData):
print(f"⚠️ DETERIORATION DETECTED: {patient_id}") print(f"⚠️ DETERIORATION DETECTED: {patient_id}")
print(f" Concerns: {', '.join(deterioration['concerns'])}") print(f" Concerns: {', '.join(deterioration['concerns'])}")
# If deteriorating, force upgrade to at least ALERT
if final_tier == "NORMAL" and tracker["pending_tier"] != "ALERT":
tracker["pending_tier"] = "ALERT"
tracker["pending_count"] = 1
print(f" ⬆️ Escalation initiated due to deterioration")
# Broadcast update # Broadcast update
await broadcast_update( await broadcast_update(
{ {
"type": "vitals_update", "type": "vitals_update",
"patient_id": patient_id, "patient_id": patient_id,
"vitals": data.dict(), "vitals": data.dict(),
"tier": tier, "tier": final_tier,
} }
) )
return {"status": "received", "tier": tier} return {
"status": "received",
"tier": final_tier,
"suggested_tier": suggested_tier,
"confirmed": suggested_tier == final_tier,
}
@app.get("/api/queue") @app.get("/api/queue")

View File

@ -0,0 +1,4 @@
215078
215139
215153
215223

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
215078

View File

@ -3,7 +3,7 @@
> vite > vite
VITE v7.1.10 ready in 107 ms VITE v7.1.10 ready in 104 ms
➜ Local: http://localhost:5173/ ➜ Local: http://localhost:5173/
➜ Network: use --host to expose ➜ Network: use --host to expose

View File

@ -0,0 +1 @@
215153

1
vitallink/logs/kiosk.pid Normal file
View File

@ -0,0 +1 @@
215223

View File

@ -0,0 +1 @@
215139