From aa789b3431f72de2bf0f60ecd6d5a8c96ad54e86 Mon Sep 17 00:00:00 2001 From: Raika Furude Date: Sat, 18 Oct 2025 16:48:05 -0400 Subject: [PATCH] working server for real mock data --- vitallink/backend/server.py | 69 +++++---- vitallink/simulator/wristband_simulator.py | 160 +++++++++++++-------- 2 files changed, 138 insertions(+), 91 deletions(-) diff --git a/vitallink/backend/server.py b/vitallink/backend/server.py index f199af6..2ddbf0e 100644 --- a/vitallink/backend/server.py +++ b/vitallink/backend/server.py @@ -8,11 +8,35 @@ from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel from typing import List, Dict, Optional from datetime import datetime, timedelta +from contextlib import asynccontextmanager import asyncio import json 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 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") async def check_in_patient(data: PatientCheckIn): """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") -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 + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/vitallink/simulator/wristband_simulator.py b/vitallink/simulator/wristband_simulator.py index ae9d17c..9892dcd 100644 --- a/vitallink/simulator/wristband_simulator.py +++ b/vitallink/simulator/wristband_simulator.py @@ -7,6 +7,7 @@ import asyncio import struct import random import time +import aiohttp from dataclasses import dataclass from typing import Dict, List, Optional from enum import IntFlag @@ -347,6 +348,17 @@ class BaseStationSimulator: "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): """Simulate continuous transmission from one wristband""" band = self.wristbands[band_id] @@ -369,6 +381,9 @@ class BaseStationSimulator: # Log packet self.packet_log.append(decoded) + # Send to backend API + await self.send_to_backend(decoded) + # Print to console tier_symbol = ( "🔴" @@ -384,9 +399,6 @@ class BaseStationSimulator: f"Seq={decoded['seq']}" ) - # Send to backend API (in production) - # await self.send_to_api(decoded) - await asyncio.sleep(interval) async def run(self): @@ -402,7 +414,7 @@ class BaseStationSimulator: ] # Run until stopped - await asyncio.gather(*tasks) + await asyncio.gather(*tasks, return_exceptions=True) def stop(self): """Stop the simulation""" @@ -428,68 +440,101 @@ class BaseStationSimulator: # ============================================================================ -# DEMO / TEST SCENARIOS +# AUTO CHECK-IN FUNCTION # ============================================================================ -async def demo_scenario_1(): - """Demo: Mix of stable and deteriorating patients""" - print("\n" + "=" * 80) - print("DEMO SCENARIO 1: Mixed Patient Population") - print("=" * 80 + "\n") +async def auto_checkin_patients(): + """Automatically check in patients via API""" + patients = [ + { + "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() - - # 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 + assigned = [] try: - await asyncio.wait_for(base.run(), timeout=30.0) - except asyncio.TimeoutError: - base.stop() + async with aiohttp.ClientSession() as session: + for patient_data in patients: + 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("SUMMARY:") - 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("CONTINUOUS MODE: Sending data to backend at http://localhost:8000") 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() - # Start with stable patients - base.add_wristband("VitalLink-1111", "stable", "P200001") - base.add_wristband("VitalLink-2222", "stable", "P200002") + # Add wristbands using the assigned IDs + profiles = ["stable", "mild_anxiety", "deteriorating"] + 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 - 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("\nPress Ctrl+C to stop\n") 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__": print(""" @@ -504,16 +549,7 @@ Available Patient Profiles: - deteriorating: Gradually worsening condition - critical: Severe vitals, triggers emergency tier - 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 - print("\nRunning Demo Scenario 1...\n") - asyncio.run(demo_scenario_1()) - - # Uncomment to run other scenarios: - # asyncio.run(demo_scenario_2()) + # Run continuous mode + asyncio.run(continuous_mode())