working server for real mock data
This commit is contained in:
parent
e178eba0af
commit
aa789b3431
@ -8,11 +8,35 @@ from fastapi.middleware.cors import CORSMiddleware
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from typing import List, Dict, Optional
|
from typing import List, Dict, Optional
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
app = FastAPI(title="VitalLink API", version="1.0.0")
|
# ============================================================================
|
||||||
|
# LIFESPAN MANAGEMENT (Modern FastAPI Way)
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def lifespan(app: FastAPI):
|
||||||
|
# Startup
|
||||||
|
print("=" * 80)
|
||||||
|
print("VitalLink Backend API Started")
|
||||||
|
print("=" * 80)
|
||||||
|
print("API Documentation: http://localhost:8000/docs")
|
||||||
|
print("WebSocket Endpoint: ws://localhost:8000/ws")
|
||||||
|
print("=" * 80)
|
||||||
|
yield
|
||||||
|
# Shutdown
|
||||||
|
print("\nVitalLink Backend API Shutting Down")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# APP INITIALIZATION
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
app = FastAPI(title="VitalLink API", version="1.0.0", lifespan=lifespan)
|
||||||
|
|
||||||
# CORS middleware for frontend
|
# CORS middleware for frontend
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
@ -151,6 +175,17 @@ def calculate_priority_score(patient: Patient) -> float:
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
async def root():
|
||||||
|
"""Root endpoint"""
|
||||||
|
return {
|
||||||
|
"message": "VitalLink Backend API",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"docs": "/docs",
|
||||||
|
"status": "running",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/checkin")
|
@app.post("/api/checkin")
|
||||||
async def check_in_patient(data: PatientCheckIn):
|
async def check_in_patient(data: PatientCheckIn):
|
||||||
"""Register a new patient and assign wristband"""
|
"""Register a new patient and assign wristband"""
|
||||||
@ -362,34 +397,10 @@ async def broadcast_update(message: dict):
|
|||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# STARTUP / SHUTDOWN
|
# RUN SERVER
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
@app.on_event("startup")
|
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||||
async def startup_event():
|
|
||||||
print("=" * 80)
|
|
||||||
print("VitalLink Backend API Started")
|
|
||||||
print("=" * 80)
|
|
||||||
print("API Documentation: http://localhost:8000/docs")
|
|
||||||
print("WebSocket Endpoint: ws://localhost:8000/ws")
|
|
||||||
print("=" * 80)
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# INTEGRATION WITH SIMULATOR
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
|
|
||||||
async def simulator_integration_task():
|
|
||||||
"""
|
|
||||||
Background task to integrate with wristband simulator
|
|
||||||
In production, this receives data from actual base station
|
|
||||||
"""
|
|
||||||
# This would be replaced with actual base station connection
|
|
||||||
# For now, shows how to integrate the simulator
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# Run with: uvicorn vitalink_backend:app --reload
|
|
||||||
# Then access at http://localhost:8000
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import asyncio
|
|||||||
import struct
|
import struct
|
||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
|
import aiohttp
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
from enum import IntFlag
|
from enum import IntFlag
|
||||||
@ -347,6 +348,17 @@ class BaseStationSimulator:
|
|||||||
"checksum": f"0x{checksum:02X}",
|
"checksum": f"0x{checksum:02X}",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async def send_to_backend(self, decoded_data: dict):
|
||||||
|
"""Send vitals data to backend API"""
|
||||||
|
try:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
await session.post(
|
||||||
|
"http://localhost:8000/api/vitals", json=decoded_data
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
# Silently fail if backend not available
|
||||||
|
pass
|
||||||
|
|
||||||
async def simulate_band_transmission(self, band_id: str):
|
async def simulate_band_transmission(self, band_id: str):
|
||||||
"""Simulate continuous transmission from one wristband"""
|
"""Simulate continuous transmission from one wristband"""
|
||||||
band = self.wristbands[band_id]
|
band = self.wristbands[band_id]
|
||||||
@ -369,6 +381,9 @@ class BaseStationSimulator:
|
|||||||
# Log packet
|
# Log packet
|
||||||
self.packet_log.append(decoded)
|
self.packet_log.append(decoded)
|
||||||
|
|
||||||
|
# Send to backend API
|
||||||
|
await self.send_to_backend(decoded)
|
||||||
|
|
||||||
# Print to console
|
# Print to console
|
||||||
tier_symbol = (
|
tier_symbol = (
|
||||||
"🔴"
|
"🔴"
|
||||||
@ -384,9 +399,6 @@ class BaseStationSimulator:
|
|||||||
f"Seq={decoded['seq']}"
|
f"Seq={decoded['seq']}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Send to backend API (in production)
|
|
||||||
# await self.send_to_api(decoded)
|
|
||||||
|
|
||||||
await asyncio.sleep(interval)
|
await asyncio.sleep(interval)
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
@ -402,7 +414,7 @@ class BaseStationSimulator:
|
|||||||
]
|
]
|
||||||
|
|
||||||
# Run until stopped
|
# Run until stopped
|
||||||
await asyncio.gather(*tasks)
|
await asyncio.gather(*tasks, return_exceptions=True)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""Stop the simulation"""
|
"""Stop the simulation"""
|
||||||
@ -428,68 +440,101 @@ class BaseStationSimulator:
|
|||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# DEMO / TEST SCENARIOS
|
# AUTO CHECK-IN FUNCTION
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
async def demo_scenario_1():
|
async def auto_checkin_patients():
|
||||||
"""Demo: Mix of stable and deteriorating patients"""
|
"""Automatically check in patients via API"""
|
||||||
print("\n" + "=" * 80)
|
patients = [
|
||||||
print("DEMO SCENARIO 1: Mixed Patient Population")
|
{
|
||||||
print("=" * 80 + "\n")
|
"firstName": "John",
|
||||||
|
"lastName": "Smith",
|
||||||
|
"dob": "1985-03-15",
|
||||||
|
"symptoms": ["Chest Pain"],
|
||||||
|
"severity": "mild",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"firstName": "Sarah",
|
||||||
|
"lastName": "Johnson",
|
||||||
|
"dob": "1990-07-22",
|
||||||
|
"symptoms": ["Fever", "Difficulty Breathing"],
|
||||||
|
"severity": "moderate",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"firstName": "Michael",
|
||||||
|
"lastName": "Chen",
|
||||||
|
"dob": "1978-11-05",
|
||||||
|
"symptoms": ["Severe Headache"],
|
||||||
|
"severity": "severe",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
base = BaseStationSimulator()
|
assigned = []
|
||||||
|
|
||||||
# Add various patients
|
|
||||||
base.add_wristband("VitalLink-A1B2", "stable", "P100001")
|
|
||||||
base.add_wristband("VitalLink-C3D4", "mild_anxiety", "P100002")
|
|
||||||
base.add_wristband("VitalLink-E5F6", "deteriorating", "P100003")
|
|
||||||
|
|
||||||
# Run for 30 seconds
|
|
||||||
try:
|
try:
|
||||||
await asyncio.wait_for(base.run(), timeout=30.0)
|
async with aiohttp.ClientSession() as session:
|
||||||
except asyncio.TimeoutError:
|
for patient_data in patients:
|
||||||
base.stop()
|
async with session.post(
|
||||||
|
"http://localhost:8000/api/checkin", json=patient_data
|
||||||
|
) as resp:
|
||||||
|
if resp.status == 200:
|
||||||
|
data = await resp.json()
|
||||||
|
assigned.append(data)
|
||||||
|
print(
|
||||||
|
f"[CHECKIN] {patient_data['firstName']} {patient_data['lastName']} → {data['patient_id']} / {data['band_id']}"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ERROR] Could not check in patients: {e}")
|
||||||
|
print("[INFO] Make sure backend is running at http://localhost:8000")
|
||||||
|
return []
|
||||||
|
|
||||||
|
return assigned
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# MAIN - CONTINUOUS MODE
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
async def continuous_mode():
|
||||||
|
"""Run simulator continuously, sending data to backend"""
|
||||||
print("\n" + "=" * 80)
|
print("\n" + "=" * 80)
|
||||||
print("SUMMARY:")
|
print("CONTINUOUS MODE: Sending data to backend at http://localhost:8000")
|
||||||
print(base.get_summary())
|
|
||||||
print("=" * 80)
|
|
||||||
|
|
||||||
|
|
||||||
async def demo_scenario_2():
|
|
||||||
"""Demo: Critical patient arrival"""
|
|
||||||
print("\n" + "=" * 80)
|
|
||||||
print("DEMO SCENARIO 2: Critical Patient Emergency")
|
|
||||||
print("=" * 80 + "\n")
|
print("=" * 80 + "\n")
|
||||||
|
|
||||||
|
# Auto check-in patients first
|
||||||
|
print("Checking in patients...")
|
||||||
|
assigned = await auto_checkin_patients()
|
||||||
|
|
||||||
|
if not assigned:
|
||||||
|
print("\n[ERROR] No patients checked in. Is the backend running?")
|
||||||
|
print("[INFO] Start backend with: python backend/server.py")
|
||||||
|
return
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
base = BaseStationSimulator()
|
base = BaseStationSimulator()
|
||||||
|
|
||||||
# Start with stable patients
|
# Add wristbands using the assigned IDs
|
||||||
base.add_wristband("VitalLink-1111", "stable", "P200001")
|
profiles = ["stable", "mild_anxiety", "deteriorating"]
|
||||||
base.add_wristband("VitalLink-2222", "stable", "P200002")
|
for i, assignment in enumerate(assigned):
|
||||||
|
profile = profiles[i] if i < len(profiles) else "stable"
|
||||||
|
base.add_wristband(assignment["band_id"], profile, assignment["patient_id"])
|
||||||
|
|
||||||
# Simulate for 10 seconds
|
print("\nPress Ctrl+C to stop\n")
|
||||||
async def add_critical_patient():
|
|
||||||
await asyncio.sleep(10)
|
|
||||||
print("\n⚠️ CRITICAL PATIENT ARRIVED ⚠️\n")
|
|
||||||
base.add_wristband("VitalLink-9999", "critical", "P200999")
|
|
||||||
|
|
||||||
# Run both tasks
|
|
||||||
await asyncio.gather(
|
|
||||||
add_critical_patient(), asyncio.wait_for(base.run(), timeout=25.0)
|
|
||||||
)
|
|
||||||
|
|
||||||
base.stop()
|
|
||||||
print("\n" + "=" * 80)
|
|
||||||
print("SUMMARY:")
|
|
||||||
print(base.get_summary())
|
|
||||||
print("=" * 80)
|
print("=" * 80)
|
||||||
|
|
||||||
|
# Run indefinitely
|
||||||
|
try:
|
||||||
|
await base.run()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
base.stop()
|
||||||
|
print("\n" + "=" * 80)
|
||||||
|
print("SUMMARY:")
|
||||||
|
print(base.get_summary())
|
||||||
|
print("=" * 80)
|
||||||
|
print("\n[BASE] Simulator stopped by user")
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# MAIN
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print("""
|
print("""
|
||||||
@ -504,16 +549,7 @@ Available Patient Profiles:
|
|||||||
- deteriorating: Gradually worsening condition
|
- deteriorating: Gradually worsening condition
|
||||||
- critical: Severe vitals, triggers emergency tier
|
- critical: Severe vitals, triggers emergency tier
|
||||||
- sepsis: Rapid deterioration pattern
|
- sepsis: Rapid deterioration pattern
|
||||||
|
|
||||||
Usage Examples:
|
|
||||||
1. Run demo scenarios (below)
|
|
||||||
2. Create custom scenarios using BaseStationSimulator
|
|
||||||
3. Integrate with FastAPI backend for web portal
|
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# Choose a demo to run
|
# Run continuous mode
|
||||||
print("\nRunning Demo Scenario 1...\n")
|
asyncio.run(continuous_mode())
|
||||||
asyncio.run(demo_scenario_1())
|
|
||||||
|
|
||||||
# Uncomment to run other scenarios:
|
|
||||||
# asyncio.run(demo_scenario_2())
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user