link start!

This commit is contained in:
Raika Furude 2025-10-18 13:49:53 -04:00
commit c2906611fd
14 changed files with 2878 additions and 0 deletions

621
install_vitallink.sh Executable file
View File

@ -0,0 +1,621 @@
#!/bin/bash
# VitalLink Automatic Installer for Arch Linux
# This script sets up the entire project structure
# You'll still need to copy the Python/React code from Claude artifacts
set -e
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
PROJECT_ROOT="$PWD/vitallink"
clear
echo -e "${CYAN}"
cat <<"EOF"
╔══════════════════════════════════════════════════════════════════════════╗
║ ║
║ ██╗ ██╗██╗████████╗ █████╗ ██╗ ██╗ ██╗███╗ ██╗██╗ ██╗ ║
║ ██║ ██║██║╚══██╔══╝██╔══██╗██║ ██║ ██║████╗ ██║██║ ██╔╝ ║
║ ██║ ██║██║ ██║ ███████║██║ ██║ ██║██╔██╗ ██║█████╔╝ ║
║ ╚██╗ ██╔╝██║ ██║ ██╔══██║██║ ██║ ██║██║╚██╗██║██╔═██╗ ║
║ ╚████╔╝ ██║ ██║ ██║ ██║███████╗███████╗██║██║ ╚████║██║ ██╗ ║
║ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ║
║ ║
║ Emergency Room Patient Monitoring System ║
║ Automatic Installer v1.0 ║
║ ║
╚══════════════════════════════════════════════════════════════════════════╝
EOF
echo -e "${NC}"
echo -e "${YELLOW}This script will:${NC}"
echo " 1. Create project structure in $PROJECT_ROOT"
echo " 2. Set up Python virtual environment"
echo " 3. Install all dependencies"
echo " 4. Create startup/stop scripts"
echo " 5. Generate placeholder files for you to fill"
echo ""
echo -e "${CYAN}Note: You'll need to copy Python/React code from Claude artifacts after this.${NC}"
echo ""
read -p "Continue? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo -e "${RED}Installation cancelled.${NC}"
exit 1
fi
# Check if directory exists
if [ -d "$PROJECT_ROOT" ]; then
echo -e "${YELLOW}Warning: $PROJECT_ROOT already exists.${NC}"
read -p "Delete and recreate? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
rm -rf "$PROJECT_ROOT"
echo -e "${GREEN}✓ Removed existing directory${NC}"
else
echo -e "${RED}Installation cancelled.${NC}"
exit 1
fi
fi
echo ""
echo -e "${CYAN}═══════════════════════════════════════════════════════════════════════${NC}"
echo -e "${CYAN}STEP 1: Creating Directory Structure${NC}"
echo -e "${CYAN}═══════════════════════════════════════════════════════════════════════${NC}"
mkdir -p "$PROJECT_ROOT"/{backend,simulator,frontend/{kiosk,dashboard},tests,docs,logs}
echo -e "${GREEN}✓ Created project directories${NC}"
cd "$PROJECT_ROOT"
echo ""
echo -e "${CYAN}═══════════════════════════════════════════════════════════════════════${NC}"
echo -e "${CYAN}STEP 2: Setting up Python Virtual Environment${NC}"
echo -e "${CYAN}═══════════════════════════════════════════════════════════════════════${NC}"
python -m venv venv
source venv/bin/activate
echo -e "${GREEN}✓ Virtual environment created${NC}"
echo ""
echo -e "${CYAN}═══════════════════════════════════════════════════════════════════════${NC}"
echo -e "${CYAN}STEP 3: Creating requirements.txt${NC}"
echo -e "${CYAN}═══════════════════════════════════════════════════════════════════════${NC}"
cat >requirements.txt <<'EOF'
# Backend API
fastapi==0.104.1
uvicorn[standard]==0.24.0
websockets==12.0
pydantic==2.5.0
python-multipart==0.0.6
# HTTP client for simulator integration
aiohttp==3.9.1
requests==2.31.0
# Testing
pytest==7.4.3
pytest-asyncio==0.21.1
# Utilities
python-dateutil==2.8.2
EOF
echo -e "${GREEN}✓ requirements.txt created${NC}"
echo ""
echo -e "${CYAN}═══════════════════════════════════════════════════════════════════════${NC}"
echo -e "${CYAN}STEP 4: Installing Python Dependencies${NC}"
echo -e "${CYAN}═══════════════════════════════════════════════════════════════════════${NC}"
pip install --upgrade pip -q
pip install -r requirements.txt -q
echo -e "${GREEN}✓ All Python packages installed${NC}"
echo ""
echo -e "${CYAN}═══════════════════════════════════════════════════════════════════════${NC}"
echo -e "${CYAN}STEP 5: Creating Project Files${NC}"
echo -e "${CYAN}═══════════════════════════════════════════════════════════════════════${NC}"
# Backend server placeholder
cat >backend/server.py <<'EOF'
"""
VitalLink Backend Server
TODO: Copy the complete FastAPI code from Claude artifact:
"VitalLink Backend API (FastAPI)"
"""
print("Backend server placeholder - please copy the actual code from Claude artifacts")
EOF
echo -e "${GREEN}✓ backend/server.py created (placeholder)${NC}"
# Simulator placeholder
cat >simulator/wristband_simulator.py <<'EOF'
"""
VitalLink Wristband Simulator
TODO: Copy the complete simulator code from Claude artifact:
"VitalLink Wristband Simulator & Base Station"
"""
print("Simulator placeholder - please copy the actual code from Claude artifacts")
EOF
echo -e "${GREEN}✓ simulator/wristband_simulator.py created (placeholder)${NC}"
# Test suite placeholder
cat >tests/test_suite.py <<'EOF'
"""
VitalLink Test Suite
TODO: Copy the complete test suite code from Claude artifact:
"VitalLink Complete Test Suite"
"""
print("Test suite placeholder - please copy the actual code from Claude artifacts")
EOF
echo -e "${GREEN}✓ tests/test_suite.py created (placeholder)${NC}"
# Dashboard HTML
cat >frontend/dashboard/index.html <<'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VitalLink Staff Dashboard</title>
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
/*
* TODO: Copy the ENTIRE React component code from Claude artifact:
* "VitalLink Staff Monitoring Dashboard"
*
* Replace "export default StaffDashboard;" with:
* const root = ReactDOM.createRoot(document.getElementById('root'));
* root.render(<StaffDashboard />);
*/
// Placeholder
const Placeholder = () => (
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100vh',
backgroundColor: '#f3f4f6',
fontFamily: 'system-ui, -apple-system, sans-serif'
}}>
<div style={{
backgroundColor: 'white',
padding: '3rem',
borderRadius: '1rem',
boxShadow: '0 10px 25px rgba(0,0,0,0.1)',
maxWidth: '600px',
textAlign: 'center'
}}>
<h1 style={{color: '#1e40af', fontSize: '2rem', marginBottom: '1rem'}}>
VitalLink Staff Dashboard
</h1>
<p style={{color: '#6b7280', marginBottom: '1.5rem'}}>
Please copy the React component code from Claude artifacts.
</p>
<div style={{
backgroundColor: '#fef3c7',
padding: '1rem',
borderRadius: '0.5rem',
border: '1px solid #fbbf24'
}}>
<p style={{color: '#92400e', fontSize: '0.875rem'}}>
<strong>Instructions:</strong><br/>
1. Open this file in a text editor<br/>
2. Find the TODO comment above<br/>
3. Copy the complete StaffDashboard component<br/>
4. Save and refresh
</p>
</div>
</div>
</div>
);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Placeholder />);
</script>
</body>
</html>
EOF
echo -e "${GREEN}✓ frontend/dashboard/index.html created (template)${NC}"
# Kiosk HTML
cat >frontend/kiosk/index.html <<'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VitalLink Check-in Kiosk</title>
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
/*
* TODO: Copy the ENTIRE React component code from Claude artifact:
* "VitalLink Check-in Kiosk"
*
* Replace "export default CheckInKiosk;" with:
* const root = ReactDOM.createRoot(document.getElementById('root'));
* root.render(<CheckInKiosk />);
*/
// Placeholder (same as dashboard)
const Placeholder = () => (
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100vh',
backgroundColor: '#f3f4f6',
fontFamily: 'system-ui, -apple-system, sans-serif'
}}>
<div style={{
backgroundColor: 'white',
padding: '3rem',
borderRadius: '1rem',
boxShadow: '0 10px 25px rgba(0,0,0,0.1)',
maxWidth: '600px',
textAlign: 'center'
}}>
<h1 style={{color: '#2563eb', fontSize: '2rem', marginBottom: '1rem'}}>
VitalLink Check-in Kiosk
</h1>
<p style={{color: '#6b7280', marginBottom: '1.5rem'}}>
Please copy the React component code from Claude artifacts.
</p>
<div style={{
backgroundColor: '#dbeafe',
padding: '1rem',
borderRadius: '0.5rem',
border: '1px solid #3b82f6'
}}>
<p style={{color: '#1e40af', fontSize: '0.875rem'}}>
<strong>Instructions:</strong><br/>
1. Open this file in a text editor<br/>
2. Find the TODO comment above<br/>
3. Copy the complete CheckInKiosk component<br/>
4. Save and refresh
</p>
</div>
</div>
</div>
);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Placeholder />);
</script>
</body>
</html>
EOF
echo -e "${GREEN}✓ frontend/kiosk/index.html created (template)${NC}"
echo ""
echo -e "${CYAN}═══════════════════════════════════════════════════════════════════════${NC}"
echo -e "${CYAN}STEP 6: Creating Control Scripts${NC}"
echo -e "${CYAN}═══════════════════════════════════════════════════════════════════════${NC}"
# Start script
cat >start.sh <<'STARTSCRIPT'
#!/bin/bash
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$PROJECT_ROOT"
source venv/bin/activate
echo "╔═══════════════════════════════════════════════════════════════════════╗"
echo "║ Starting VitalLink System ║"
echo "╚═══════════════════════════════════════════════════════════════════════╝"
echo ""
mkdir -p logs
echo "Starting backend server..."
python backend/server.py > logs/backend.log 2>&1 &
echo $! > logs/backend.pid
echo "✓ Backend started (PID: $(cat logs/backend.pid))"
sleep 3
echo "Starting wristband simulator..."
python simulator/wristband_simulator.py > logs/simulator.log 2>&1 &
echo $! > logs/simulator.pid
echo "✓ Simulator started (PID: $(cat logs/simulator.pid))"
echo ""
echo "═══════════════════════════════════════════════════════════════════════"
echo "✅ VitalLink System Running!"
echo "═══════════════════════════════════════════════════════════════════════"
echo ""
echo "📊 Access Points:"
echo " • API Docs: http://localhost:8000/docs"
echo " • API Stats: http://localhost:8000/api/stats"
echo " • WebSocket: ws://localhost:8000/ws"
echo " • Staff Dashboard: file://$PROJECT_ROOT/frontend/dashboard/index.html"
echo " • Check-in Kiosk: file://$PROJECT_ROOT/frontend/kiosk/index.html"
echo ""
echo "📝 View Logs:"
echo " • Backend: tail -f logs/backend.log"
echo " • Simulator: tail -f logs/simulator.log"
echo ""
echo "🛑 Stop System:"
echo " • Run: ./stop.sh"
echo ""
echo "═══════════════════════════════════════════════════════════════════════"
STARTSCRIPT
chmod +x start.sh
echo -e "${GREEN}✓ start.sh created${NC}"
# Stop script
cat >stop.sh <<'STOPSCRIPT'
#!/bin/bash
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$PROJECT_ROOT"
echo "Stopping VitalLink system..."
if [ -f logs/backend.pid ]; then
kill $(cat logs/backend.pid) 2>/dev/null && echo "✓ Backend stopped" || echo "Backend not running"
rm -f logs/backend.pid
fi
if [ -f logs/simulator.pid ]; then
kill $(cat logs/simulator.pid) 2>/dev/null && echo "✓ Simulator stopped" || echo "Simulator not running"
rm -f logs/simulator.pid
fi
echo "✓ VitalLink system stopped"
STOPSCRIPT
chmod +x stop.sh
echo -e "${GREEN}✓ stop.sh created${NC}"
# Test script
cat >test.sh <<'TESTSCRIPT'
#!/bin/bash
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$PROJECT_ROOT"
source venv/bin/activate
echo "Running VitalLink Test Suite..."
echo ""
python tests/test_suite.py
TESTSCRIPT
chmod +x test.sh
echo -e "${GREEN}✓ test.sh created${NC}"
echo ""
echo -e "${CYAN}═══════════════════════════════════════════════════════════════════════${NC}"
echo -e "${CYAN}STEP 7: Creating Documentation${NC}"
echo -e "${CYAN}═══════════════════════════════════════════════════════════════════════${NC}"
# README
cat >README.md <<'EOF'
# VitalLink - ER Patient Monitoring System
Emergency department patient monitoring system using smart wristbands.
## Quick Start
1. **Copy Code from Claude Artifacts:**
- `backend/server.py` ← Copy "VitalLink Backend API (FastAPI)"
- `simulator/wristband_simulator.py` ← Copy "VitalLink Wristband Simulator"
- `tests/test_suite.py` ← Copy "VitalLink Complete Test Suite"
- `frontend/dashboard/index.html` ← Insert React code from "Staff Dashboard"
- `frontend/kiosk/index.html` ← Insert React code from "Check-in Kiosk"
2. **Test the System:**
```bash
./test.sh
```
3. **Start the System:**
```bash
./start.sh
```
4. **Access the Interfaces:**
- API Documentation: http://localhost:8000/docs
- Staff Dashboard: Open `frontend/dashboard/index.html` in browser
- Check-in Kiosk: Open `frontend/kiosk/index.html` in browser
5. **Stop the System:**
```bash
./stop.sh
```
## Project Structure
```
vitallink/
├── backend/ # FastAPI server
├── simulator/ # Wristband simulator
├── frontend/ # Web interfaces
│ ├── dashboard/ # Staff monitoring
│ └── kiosk/ # Patient check-in
├── tests/ # Test suite
├── logs/ # System logs
└── venv/ # Python virtual env
```
## Commands
- `./start.sh` - Start backend + simulator
- `./stop.sh` - Stop all services
- `./test.sh` - Run test suite
- `tail -f logs/backend.log` - View backend logs
- `tail -f logs/simulator.log` - View simulator logs
## Testing
```bash
# Run all tests
./test.sh
# Test API
curl http://localhost:8000/api/stats
# Create test patient
curl -X POST http://localhost:8000/api/checkin \
-H "Content-Type: application/json" \
-d '{"firstName":"Test","lastName":"Patient","dob":"1990-01-01","symptoms":["Fever"],"severity":"moderate"}'
```
## Next Steps
See `docs/SETUP_GUIDE.md` for detailed instructions.
EOF
echo -e "${GREEN}✓ README.md created${NC}"
# Setup guide
cat >docs/SETUP_GUIDE.md <<'EOF'
# Detailed Setup Guide
## Copying Code from Claude Artifacts
### 1. Backend Server (backend/server.py)
Open the artifact titled **"VitalLink Backend API (FastAPI)"** and copy the ENTIRE Python code.
Paste it into `backend/server.py`.
### 2. Wristband Simulator (simulator/wristband_simulator.py)
Open the artifact titled **"VitalLink Wristband Simulator & Base Station"** and copy the ENTIRE Python code.
Paste it into `simulator/wristband_simulator.py`.
### 3. Test Suite (tests/test_suite.py)
Open the artifact titled **"VitalLink Complete Test Suite"** and copy the ENTIRE Python code.
Paste it into `tests/test_suite.py`.
### 4. Staff Dashboard (frontend/dashboard/index.html)
1. Open the artifact titled **"VitalLink Staff Monitoring Dashboard"**
2. Copy the ENTIRE React component code
3. Open `frontend/dashboard/index.html` in a text editor
4. Find the TODO comment section
5. Paste the React code there
6. Remove the line `export default StaffDashboard;`
7. Add these lines at the end:
```javascript
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<StaffDashboard />);
```
### 5. Check-in Kiosk (frontend/kiosk/index.html)
Follow the same process as the dashboard, but use the **"VitalLink Check-in Kiosk"** artifact.
## Verification
After copying all code:
```bash
# Activate virtual environment
source venv/bin/activate
# Run tests
./test.sh
# Start system
./start.sh
# In another terminal, test API
curl http://localhost:8000/api/stats
```
If everything works, you should see JSON output from the API.
EOF
echo -e "${GREEN}✓ docs/SETUP_GUIDE.md created${NC}"
# .gitignore
cat >.gitignore <<'EOF'
__pycache__/
*.py[cod]
venv/
logs/
*.log
*.pid
.DS_Store
EOF
echo -e "${GREEN}✓ .gitignore created${NC}"
echo ""
echo -e "${CYAN}═══════════════════════════════════════════════════════════════════════${NC}"
echo -e "${CYAN}STEP 8: Final Setup${NC}"
echo -e "${CYAN}═══════════════════════════════════════════════════════════════════════${NC}"
# Create empty log directory
touch logs/.gitkeep
# Deactivate venv for clean state
deactivate 2>/dev/null || true
echo -e "${GREEN}✓ Project setup complete!${NC}"
echo ""
echo -e "${GREEN}╔═══════════════════════════════════════════════════════════════════════╗${NC}"
echo -e "${GREEN}║ ✅ Installation Complete! ║${NC}"
echo -e "${GREEN}╚═══════════════════════════════════════════════════════════════════════╝${NC}"
echo ""
echo -e "${YELLOW}📁 Project Location:${NC} $PROJECT_ROOT"
echo ""
echo -e "${YELLOW}🔔 IMPORTANT - Next Steps:${NC}"
echo ""
echo -e "${CYAN}1. Copy Code from Claude Artifacts:${NC}"
echo " Open each artifact in Claude and copy the code to these files:"
echo " • backend/server.py"
echo " • simulator/wristband_simulator.py"
echo " • tests/test_suite.py"
echo " • frontend/dashboard/index.html (insert React code)"
echo " • frontend/kiosk/index.html (insert React code)"
echo ""
echo -e "${CYAN}2. Navigate to project:${NC}"
echo " cd $PROJECT_ROOT"
echo ""
echo -e "${CYAN}3. Run tests:${NC}"
echo " ./test.sh"
echo ""
echo -e "${CYAN}4. Start the system:${NC}"
echo " ./start.sh"
echo ""
echo -e "${CYAN}5. Open interfaces:${NC}"
echo " xdg-open frontend/dashboard/index.html"
echo " xdg-open frontend/kiosk/index.html"
echo ""
echo -e "${GREEN}📖 Full documentation:${NC} cat docs/SETUP_GUIDE.md"
echo ""
echo -e "${BLUE}═══════════════════════════════════════════════════════════════════════${NC}"
echo ""

395
vitallink/backend/server.py Normal file
View File

@ -0,0 +1,395 @@
"""
VitalLink Backend API
FastAPI server for managing patients, wristbands, and real-time data
"""
from fastapi import FastAPI, WebSocket, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import List, Dict, Optional
from datetime import datetime, timedelta
import asyncio
import json
from collections import defaultdict
app = FastAPI(title="VitalLink API", version="1.0.0")
# CORS middleware for frontend
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # In production, specify your frontend domain
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ============================================================================
# DATA MODELS
# ============================================================================
class PatientCheckIn(BaseModel):
firstName: str
lastName: str
dob: str
symptoms: List[str]
severity: str
class Patient(BaseModel):
patient_id: str
band_id: str
first_name: str
last_name: str
dob: str
symptoms: List[str]
severity: str
check_in_time: datetime
current_tier: str = "NORMAL"
last_vitals: Optional[dict] = None
is_active: bool = True
class VitalsData(BaseModel):
band_id: str
patient_id: str
timestamp: float
tier: str
hr_bpm: int
spo2: int
temp_c: float
activity: float
flags: List[str]
seq: int
class QueuePosition(BaseModel):
patient_id: str
band_id: str
name: str
tier: str
priority_score: float
wait_time_minutes: int
last_hr: int
last_spo2: int
last_temp: float
# ============================================================================
# IN-MEMORY STORAGE (Replace with database in production)
# ============================================================================
patients_db: Dict[str, Patient] = {}
vitals_history: Dict[str, List[VitalsData]] = defaultdict(list)
available_bands = [
f"VitalLink-{hex(i)[2:].upper().zfill(4)}" for i in range(0x1000, 0x2000)
]
active_websockets: List[WebSocket] = []
# ============================================================================
# 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
elif wait_minutes > 60:
score += (wait_minutes - 60) * 1.0 # Accelerate after 1 hour
# 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:
score += 25
return score
# ============================================================================
# API ENDPOINTS
# ============================================================================
@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,
first_name=data.firstName,
last_name=data.lastName,
dob=data.dob,
symptoms=data.symptoms,
severity=data.severity,
check_in_time=datetime.now(),
current_tier="NORMAL",
)
patients_db[patient_id] = patient
# Notify connected clients
await broadcast_update({"type": "patient_added", "patient": patient.dict()})
return {
"patient_id": patient_id,
"band_id": band_id,
"message": "Check-in successful",
}
@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()}
)
return {"status": "received"}
@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)
wait_minutes = int(
(datetime.now() - patient.check_in_time).total_seconds() / 60
)
queue.append(
QueuePosition(
patient_id=patient.patient_id,
band_id=patient.band_id,
name=f"{patient.first_name} {patient.last_name}",
tier=patient.current_tier,
priority_score=priority_score,
wait_time_minutes=wait_minutes,
last_hr=patient.last_vitals.get("hr_bpm", 0)
if patient.last_vitals
else 0,
last_spo2=patient.last_vitals.get("spo2", 0)
if patient.last_vitals
else 0,
last_temp=patient.last_vitals.get("temp_c", 0)
if patient.last_vitals
else 0,
)
)
# Sort by priority (highest first)
queue.sort(key=lambda x: x.priority_score, reverse=True)
return 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")
patient = patients_db[patient_id]
history = vitals_history.get(patient_id, [])
return {
"patient": patient.dict(),
"vitals_history": [v.dict() for v in history[-50:]], # Last 50 readings
"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}
@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}
for patient in active_patients:
tier_counts[patient.current_tier] += 1
total_vitals = sum(len(v) for v in vitals_history.values())
avg_wait = 0
if active_patients:
wait_times = [
(datetime.now() - p.check_in_time).total_seconds() / 60
for p in active_patients
]
avg_wait = sum(wait_times) / len(wait_times)
return {
"total_patients": len(patients_db),
"active_patients": len(active_patients),
"tier_breakdown": tier_counts,
"available_bands": len(available_bands),
"total_vitals_received": total_vitals,
"average_wait_minutes": round(avg_wait, 1),
}
# ============================================================================
# WEBSOCKET FOR REAL-TIME UPDATES
# ============================================================================
@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:
await websocket.send_json(message)
except:
disconnected.append(websocket)
# Remove disconnected clients
for ws in disconnected:
active_websockets.remove(ws)
# ============================================================================
# STARTUP / SHUTDOWN
# ============================================================================
@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

View File

@ -0,0 +1,368 @@
import React, { useState, useEffect } from 'react';
import { Activity, AlertCircle, Clock, Users, Bell, Heart, Thermometer, Wind, CheckCircle, UserX } from 'lucide-react';
const StaffDashboard = () => {
const [patients, setPatients] = useState([]);
const [stats, setStats] = useState({
total_patients: 0,
active_patients: 0,
tier_breakdown: { EMERGENCY: 0, ALERT: 0, NORMAL: 0 },
average_wait_minutes: 0
});
const [selectedPatient, setSelectedPatient] = useState(null);
const [filter, setFilter] = useState('all');
useEffect(() => {
const generateMockPatients = () => {
const mockPatients = [
{
patient_id: 'P100001',
band_id: 'VitalLink-A1B2',
name: 'John Smith',
tier: 'NORMAL',
priority_score: 15.2,
wait_time_minutes: 22,
last_hr: 76,
last_spo2: 98,
last_temp: 36.8,
symptoms: ['Chest Pain', 'Nausea']
},
{
patient_id: 'P100002',
band_id: 'VitalLink-C3D4',
name: 'Sarah Johnson',
tier: 'ALERT',
priority_score: 68.5,
wait_time_minutes: 45,
last_hr: 118,
last_spo2: 93,
last_temp: 38.4,
symptoms: ['Fever', 'Difficulty Breathing']
},
{
patient_id: 'P100003',
band_id: 'VitalLink-E5F6',
name: 'Michael Chen',
tier: 'EMERGENCY',
priority_score: 142.8,
wait_time_minutes: 8,
last_hr: 148,
last_spo2: 86,
last_temp: 39.7,
symptoms: ['Severe Headache', 'Chest Pain']
},
{
patient_id: 'P100004',
band_id: 'VitalLink-G7H8',
name: 'Emily Davis',
tier: 'NORMAL',
priority_score: 18.0,
wait_time_minutes: 35,
last_hr: 82,
last_spo2: 97,
last_temp: 37.1,
symptoms: ['Abdominal Pain']
},
{
patient_id: 'P100005',
band_id: 'VitalLink-I9J0',
name: 'Robert Williams',
tier: 'ALERT',
priority_score: 72.3,
wait_time_minutes: 52,
last_hr: 124,
last_spo2: 91,
last_temp: 38.8,
symptoms: ['Fever', 'Dizziness']
}
];
setPatients(mockPatients);
setStats({
total_patients: 5,
active_patients: 5,
tier_breakdown: { EMERGENCY: 1, ALERT: 2, NORMAL: 2 },
average_wait_minutes: 32.4
});
};
generateMockPatients();
const interval = setInterval(() => {
setPatients(prev => prev.map(p => ({
...p,
wait_time_minutes: p.wait_time_minutes + 1,
last_hr: Math.max(40, Math.min(180, p.last_hr + Math.floor(Math.random() * 5) - 2)),
last_spo2: Math.max(70, Math.min(100, p.last_spo2 + Math.floor(Math.random() * 3) - 1)),
last_temp: Math.max(35, Math.min(41, p.last_temp + (Math.random() * 0.2 - 0.1)))
})));
}, 3000);
return () => clearInterval(interval);
}, []);
const getTierColor = (tier) => {
switch(tier) {
case 'EMERGENCY': return 'bg-red-100 text-red-800 border-red-300';
case 'ALERT': return 'bg-yellow-100 text-yellow-800 border-yellow-300';
case 'NORMAL': return 'bg-green-100 text-green-800 border-green-300';
default: return 'bg-gray-100 text-gray-800 border-gray-300';
}
};
const getTierIcon = (tier) => {
switch(tier) {
case 'EMERGENCY': return <AlertCircle className="w-5 h-5" />;
case 'ALERT': return <Bell className="w-5 h-5" />;
case 'NORMAL': return <CheckCircle className="w-5 h-5" />;
default: return <Activity className="w-5 h-5" />;
}
};
const getVitalStatus = (type, value) => {
if (type === 'hr') {
if (value > 110 || value < 50) return 'text-red-600 font-bold';
if (value > 100 || value < 60) return 'text-yellow-600 font-semibold';
return 'text-green-600';
}
if (type === 'spo2') {
if (value < 88) return 'text-red-600 font-bold';
if (value < 92) return 'text-yellow-600 font-semibold';
return 'text-green-600';
}
if (type === 'temp') {
if (value > 39.5 || value < 35.5) return 'text-red-600 font-bold';
if (value > 38.3 || value < 36.0) return 'text-yellow-600 font-semibold';
return 'text-green-600';
}
return 'text-gray-700';
};
const handleDischarge = (patientId) => {
setPatients(prev => prev.filter(p => p.patient_id !== patientId));
setSelectedPatient(null);
};
const filteredPatients = patients
.filter(p => filter === 'all' || p.tier === filter)
.sort((a, b) => b.priority_score - a.priority_score);
return (
<div className="min-h-screen bg-gray-100">
<div className="bg-gradient-to-r from-blue-600 to-blue-700 text-white p-6 shadow-lg">
<div className="max-w-7xl mx-auto">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold mb-1">VitalLink Dashboard</h1>
<p className="text-blue-100">Emergency Department Patient Monitoring</p>
</div>
<div className="flex items-center gap-3">
<div className="bg-white bg-opacity-20 rounded-lg px-4 py-2">
<p className="text-sm text-blue-100">Last Update</p>
<p className="text-lg font-semibold">{new Date().toLocaleTimeString()}</p>
</div>
</div>
</div>
</div>
</div>
<div className="bg-white border-b shadow-sm">
<div className="max-w-7xl mx-auto p-6">
<div className="grid grid-cols-4 gap-6">
<div className="flex items-center gap-4">
<div className="bg-blue-100 p-3 rounded-lg">
<Users className="w-6 h-6 text-blue-600" />
</div>
<div>
<p className="text-sm text-gray-600">Active Patients</p>
<p className="text-2xl font-bold text-gray-800">{stats.active_patients}</p>
</div>
</div>
<div className="flex items-center gap-4">
<div className="bg-red-100 p-3 rounded-lg">
<AlertCircle className="w-6 h-6 text-red-600" />
</div>
<div>
<p className="text-sm text-gray-600">Emergency</p>
<p className="text-2xl font-bold text-red-600">{stats.tier_breakdown.EMERGENCY}</p>
</div>
</div>
<div className="flex items-center gap-4">
<div className="bg-yellow-100 p-3 rounded-lg">
<Bell className="w-6 h-6 text-yellow-600" />
</div>
<div>
<p className="text-sm text-gray-600">Alert</p>
<p className="text-2xl font-bold text-yellow-600">{stats.tier_breakdown.ALERT}</p>
</div>
</div>
<div className="flex items-center gap-4">
<div className="bg-green-100 p-3 rounded-lg">
<Clock className="w-6 h-6 text-green-600" />
</div>
<div>
<p className="text-sm text-gray-600">Avg Wait Time</p>
<p className="text-2xl font-bold text-gray-800">{stats.average_wait_minutes} min</p>
</div>
</div>
</div>
</div>
</div>
<div className="max-w-7xl mx-auto p-6">
<div className="bg-white rounded-lg shadow-sm p-4 mb-6">
<div className="flex gap-2">
<button
onClick={() => setFilter('all')}
className={`px-6 py-2 rounded-lg font-semibold transition-colors ${
filter === 'all'
? 'bg-blue-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
All Patients ({patients.length})
</button>
<button
onClick={() => setFilter('EMERGENCY')}
className={`px-6 py-2 rounded-lg font-semibold transition-colors ${
filter === 'EMERGENCY'
? 'bg-red-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
Emergency ({stats.tier_breakdown.EMERGENCY})
</button>
<button
onClick={() => setFilter('ALERT')}
className={`px-6 py-2 rounded-lg font-semibold transition-colors ${
filter === 'ALERT'
? 'bg-yellow-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
Alert ({stats.tier_breakdown.ALERT})
</button>
<button
onClick={() => setFilter('NORMAL')}
className={`px-6 py-2 rounded-lg font-semibold transition-colors ${
filter === 'NORMAL'
? 'bg-green-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
Stable ({stats.tier_breakdown.NORMAL})
</button>
</div>
</div>
<div className="space-y-4">
{filteredPatients.map((patient, index) => (
<div
key={patient.patient_id}
className="bg-white rounded-lg shadow-sm hover:shadow-md transition-shadow"
>
<div className="p-6">
<div className="flex items-start justify-between mb-4">
<div className="flex items-start gap-4">
<div className="text-2xl font-bold text-gray-400 min-w-12 text-center pt-1">
#{index + 1}
</div>
<div>
<h3 className="text-xl font-bold text-gray-800 mb-1">{patient.name}</h3>
<div className="flex items-center gap-3 text-sm text-gray-600">
<span className="font-mono">{patient.patient_id}</span>
<span></span>
<span className="font-mono">{patient.band_id}</span>
</div>
<div className="flex flex-wrap gap-2 mt-2">
{patient.symptoms.map(symptom => (
<span key={symptom} className="bg-gray-100 text-gray-700 px-3 py-1 rounded-full text-xs font-medium">
{symptom}
</span>
))}
</div>
</div>
</div>
<div className="flex items-center gap-3">
<div className={`px-4 py-2 rounded-lg border-2 flex items-center gap-2 font-semibold ${getTierColor(patient.tier)}`}>
{getTierIcon(patient.tier)}
{patient.tier}
</div>
<button
onClick={() => handleDischarge(patient.patient_id)}
className="px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors flex items-center gap-2 font-semibold"
>
<UserX className="w-4 h-4" />
Discharge
</button>
</div>
</div>
<div className="grid grid-cols-4 gap-4 pt-4 border-t">
<div className="bg-gray-50 rounded-lg p-3">
<div className="flex items-center gap-2 mb-1">
<Heart className="w-4 h-4 text-gray-600" />
<span className="text-xs text-gray-600 font-medium">Heart Rate</span>
</div>
<p className={`text-2xl font-bold ${getVitalStatus('hr', patient.last_hr)}`}>
{patient.last_hr}
</p>
<p className="text-xs text-gray-500 mt-1">bpm</p>
</div>
<div className="bg-gray-50 rounded-lg p-3">
<div className="flex items-center gap-2 mb-1">
<Wind className="w-4 h-4 text-gray-600" />
<span className="text-xs text-gray-600 font-medium">SpO₂</span>
</div>
<p className={`text-2xl font-bold ${getVitalStatus('spo2', patient.last_spo2)}`}>
{patient.last_spo2}
</p>
<p className="text-xs text-gray-500 mt-1">%</p>
</div>
<div className="bg-gray-50 rounded-lg p-3">
<div className="flex items-center gap-2 mb-1">
<Thermometer className="w-4 h-4 text-gray-600" />
<span className="text-xs text-gray-600 font-medium">Temperature</span>
</div>
<p className={`text-2xl font-bold ${getVitalStatus('temp', patient.last_temp)}`}>
{patient.last_temp.toFixed(1)}
</p>
<p className="text-xs text-gray-500 mt-1">°C</p>
</div>
<div className="bg-gray-50 rounded-lg p-3">
<div className="flex items-center gap-2 mb-1">
<Clock className="w-4 h-4 text-gray-600" />
<span className="text-xs text-gray-600 font-medium">Wait Time</span>
</div>
<p className="text-2xl font-bold text-gray-700">
{patient.wait_time_minutes}
</p>
<p className="text-xs text-gray-500 mt-1">minutes</p>
</div>
</div>
</div>
</div>
))}
</div>
{filteredPatients.length === 0 && (
<div className="bg-white rounded-lg shadow-sm p-12 text-center">
<Activity className="w-16 h-16 text-gray-300 mx-auto mb-4" />
<h3 className="text-xl font-semibold text-gray-600 mb-2">No patients in this category</h3>
<p className="text-gray-500">Patients will appear here as they check in</p>
</div>
)}
</div>
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<StaffDashboard />);

View File

@ -0,0 +1,265 @@
import React, { useState } from 'react';
import { AlertCircle, CheckCircle, Clock, User } from 'lucide-react';
const CheckInKiosk = () => {
const [step, setStep] = useState('welcome');
const [formData, setFormData] = useState({
firstName: '',
lastName: '',
dob: '',
symptoms: [],
severity: 'moderate'
});
const [assignedBand, setAssignedBand] = useState(null);
const symptoms = [
'Chest Pain', 'Difficulty Breathing', 'Severe Headache',
'Abdominal Pain', 'Fever', 'Nausea/Vomiting',
'Dizziness', 'Injury/Trauma', 'Other'
];
const handleSymptomToggle = (symptom) => {
setFormData(prev => ({
...prev,
symptoms: prev.symptoms.includes(symptom)
? prev.symptoms.filter(s => s !== symptom)
: [...prev.symptoms, symptom]
}));
};
const handleSubmit = async () => {
// Simulate API call to backend
const patientId = `P${Date.now().toString().slice(-6)}`;
const bandId = `VitalLink-${Math.floor(Math.random() * 65536).toString(16).toUpperCase().padStart(4, '0')}`;
setAssignedBand({
patientId,
bandId,
station: Math.floor(Math.random() * 8) + 1
});
// In production, send to backend:
// await fetch('/api/checkin', { method: 'POST', body: JSON.stringify({...formData, patientId, bandId}) });
setStep('complete');
};
if (step === 'welcome') {
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-blue-100 flex items-center justify-center p-4">
<div className="bg-white rounded-2xl shadow-2xl p-12 max-w-2xl w-full text-center">
<div className="mb-8">
<div className="bg-blue-600 rounded-full w-24 h-24 flex items-center justify-center mx-auto mb-6">
<User className="w-12 h-12 text-white" />
</div>
<h1 className="text-4xl font-bold text-gray-800 mb-3">Welcome to VitalLink</h1>
<p className="text-xl text-gray-600">Emergency Room Check-In</p>
</div>
<div className="space-y-4 mb-8 text-left bg-blue-50 p-6 rounded-xl">
<h2 className="font-semibold text-lg text-gray-800 mb-3">What to expect:</h2>
<div className="flex items-start gap-3">
<CheckCircle className="w-5 h-5 text-green-600 mt-1 flex-shrink-0" />
<p className="text-gray-700">Answer a few questions about your condition</p>
</div>
<div className="flex items-start gap-3">
<CheckCircle className="w-5 h-5 text-green-600 mt-1 flex-shrink-0" />
<p className="text-gray-700">Receive a smart wristband to monitor your vitals</p>
</div>
<div className="flex items-start gap-3">
<CheckCircle className="w-5 h-5 text-green-600 mt-1 flex-shrink-0" />
<p className="text-gray-700">Wait comfortably while we track your condition</p>
</div>
</div>
<button
onClick={() => setStep('form')}
className="bg-blue-600 text-white px-12 py-4 rounded-xl text-xl font-semibold hover:bg-blue-700 transition-colors shadow-lg"
>
Start Check-In
</button>
</div>
</div>
);
}
if (step === 'form') {
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-blue-100 p-4">
<div className="max-w-3xl mx-auto pt-8">
<div className="bg-white rounded-2xl shadow-2xl p-8">
<h2 className="text-3xl font-bold text-gray-800 mb-6">Patient Information</h2>
<div className="space-y-6">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
First Name *
</label>
<input
type="text"
value={formData.firstName}
onChange={(e) => setFormData({...formData, firstName: e.target.value})}
className="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none text-lg"
placeholder="John"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Last Name *
</label>
<input
type="text"
value={formData.lastName}
onChange={(e) => setFormData({...formData, lastName: e.target.value})}
className="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none text-lg"
placeholder="Doe"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Date of Birth *
</label>
<input
type="date"
value={formData.dob}
onChange={(e) => setFormData({...formData, dob: e.target.value})}
className="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none text-lg"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-3">
Select Your Symptoms *
</label>
<div className="grid grid-cols-2 gap-3">
{symptoms.map((symptom) => (
<button
key={symptom}
onClick={() => handleSymptomToggle(symptom)}
className={`px-4 py-3 rounded-lg border-2 transition-all text-left font-medium ${
formData.symptoms.includes(symptom)
? 'bg-blue-100 border-blue-500 text-blue-700'
: 'bg-white border-gray-300 text-gray-700 hover:border-blue-300'
}`}
>
{symptom}
</button>
))}
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-3">
How severe are your symptoms? *
</label>
<div className="grid grid-cols-3 gap-3">
{['mild', 'moderate', 'severe'].map((level) => (
<button
key={level}
onClick={() => setFormData({...formData, severity: level})}
className={`px-6 py-4 rounded-lg border-2 transition-all font-semibold capitalize ${
formData.severity === level
? level === 'severe'
? 'bg-red-100 border-red-500 text-red-700'
: level === 'moderate'
? 'bg-yellow-100 border-yellow-500 text-yellow-700'
: 'bg-green-100 border-green-500 text-green-700'
: 'bg-white border-gray-300 text-gray-700 hover:border-gray-400'
}`}
>
{level}
</button>
))}
</div>
</div>
</div>
<div className="flex gap-4 mt-8">
<button
onClick={() => setStep('welcome')}
className="flex-1 px-6 py-4 border-2 border-gray-300 text-gray-700 rounded-xl font-semibold hover:bg-gray-50 transition-colors"
>
Back
</button>
<button
onClick={handleSubmit}
disabled={!formData.firstName || !formData.lastName || !formData.dob || formData.symptoms.length === 0}
className="flex-1 px-6 py-4 bg-blue-600 text-white rounded-xl font-semibold hover:bg-blue-700 transition-colors disabled:bg-gray-300 disabled:cursor-not-allowed"
>
Complete Check-In
</button>
</div>
</div>
</div>
</div>
);
}
if (step === 'complete') {
return (
<div className="min-h-screen bg-gradient-to-br from-green-50 to-green-100 flex items-center justify-center p-4">
<div className="bg-white rounded-2xl shadow-2xl p-12 max-w-2xl w-full text-center">
<div className="mb-6">
<div className="bg-green-600 rounded-full w-24 h-24 flex items-center justify-center mx-auto mb-6">
<CheckCircle className="w-12 h-12 text-white" />
</div>
<h1 className="text-4xl font-bold text-gray-800 mb-3">Check-In Complete!</h1>
<p className="text-xl text-gray-600">Your wristband has been assigned</p>
</div>
<div className="bg-gradient-to-r from-blue-500 to-blue-600 rounded-xl p-8 mb-8 text-white">
<p className="text-sm uppercase tracking-wide mb-2 opacity-90">Your Patient ID</p>
<p className="text-3xl font-bold mb-4">{assignedBand?.patientId}</p>
<div className="border-t border-white/30 pt-4">
<p className="text-sm uppercase tracking-wide mb-2 opacity-90">Wristband ID</p>
<p className="text-2xl font-semibold">{assignedBand?.bandId}</p>
</div>
</div>
<div className="space-y-4 text-left bg-blue-50 p-6 rounded-xl mb-8">
<h2 className="font-bold text-lg text-gray-800 mb-3">Next Steps:</h2>
<div className="flex items-start gap-3">
<div className="bg-blue-600 text-white rounded-full w-8 h-8 flex items-center justify-center font-bold flex-shrink-0">1</div>
<p className="text-gray-700 pt-1">
<strong>Pick up your wristband</strong> from Station {assignedBand?.station}
</p>
</div>
<div className="flex items-start gap-3">
<div className="bg-blue-600 text-white rounded-full w-8 h-8 flex items-center justify-center font-bold flex-shrink-0">2</div>
<p className="text-gray-700 pt-1">
<strong>Wear it on your wrist</strong> - make sure it's snug but comfortable
</p>
</div>
<div className="flex items-start gap-3">
<div className="bg-blue-600 text-white rounded-full w-8 h-8 flex items-center justify-center font-bold flex-shrink-0">3</div>
<p className="text-gray-700 pt-1">
<strong>Take a seat in the waiting area</strong> - your vitals are being monitored
</p>
</div>
</div>
<div className="flex items-center justify-center gap-2 text-gray-600 mb-6">
<Clock className="w-5 h-5" />
<p>A nurse will call you when it's your turn</p>
</div>
<button
onClick={() => {
setStep('welcome');
setFormData({ firstName: '', lastName: '', dob: '', symptoms: [], severity: 'moderate' });
setAssignedBand(null);
}}
className="text-blue-600 font-semibold hover:underline"
>
Return to Start
</button>
</div>
</div>
);
}
};
export default CheckInKiosk;

View File

@ -0,0 +1,7 @@
/home/mai/documents/school/capstone/vitallink-BS/vitallink/backend/server.py:369: DeprecationWarning:
on_event is deprecated, use lifespan event handlers instead.
Read more about it in the
[FastAPI docs for Lifespan Events](https://fastapi.tiangolo.com/advanced/events/).
@app.on_event("startup")

View File

@ -0,0 +1 @@
29497

View File

@ -0,0 +1,41 @@
╔══════════════════════════════════════════════════════════════════════════╗
║ VitalLink Wristband Simulator ║
║ Emergency Department Monitoring System ║
╚══════════════════════════════════════════════════════════════════════════╝
Available Patient Profiles:
- stable: Normal vitals, no deterioration
- mild_anxiety: Elevated HR, improves over time
- 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
Running Demo Scenario 1...
================================================================================
DEMO SCENARIO 1: Mixed Patient Population
================================================================================
[BASE] Added wristband VitalLink-A1B2 with profile 'Stable Patient'
[BASE] Added wristband VitalLink-C3D4 with profile 'Mild Anxiety'
[BASE] Added wristband VitalLink-E5F6 with profile 'Deteriorating Condition'
[BASE] Starting base station simulation...
================================================================================
🟢 [VitalLink-A1B2] P100001 | HR=66 SpO2=98% Temp=36.9°C | NORMAL | Seq=0
🟢 [VitalLink-C3D4] P100002 | HR=91 SpO2=97% Temp=37.0°C | NORMAL | Seq=0
🟢 [VitalLink-E5F6] P100003 | HR=88 SpO2=92% Temp=37.7°C | NORMAL | Seq=0
[BASE] Stopping base station...
================================================================================
SUMMARY:
{'total_bands': 3, 'active_bands': 3, 'tiers': {'EMERGENCY': 0, 'ALERT': 0, 'NORMAL': 3}, 'total_packets': 3}
================================================================================

View File

@ -0,0 +1 @@
29507

View File

@ -0,0 +1,17 @@
# Backend API
fastapi==0.104.1
uvicorn[standard]==0.24.0
websockets==12.0
pydantic==2.5.0
python-multipart==0.0.6
# HTTP client for simulator integration
aiohttp==3.9.1
requests==2.31.0
# Testing
pytest==7.4.3
pytest-asyncio==0.21.1
# Utilities
python-dateutil==2.8.2

View File

@ -0,0 +1,519 @@
"""
VitalLink Wristband Simulator & Base Station
Simulates multiple wristbands with realistic vital signs and BLE behavior
"""
import asyncio
import struct
import random
import time
from dataclasses import dataclass
from typing import Dict, List, Optional
from enum import IntFlag
# ============================================================================
# CONSTANTS & FLAGS
# ============================================================================
SERVICE_UUID = "8f5a84f1-22a8-4a4b-9b5f-3fe1d8b2a3a1"
CHAR_UUID = "d3e2c4b7-39b2-4b2a-8d5a-7d2a5e3f1199"
PKT_LEN = 16
PKT_STRUCT = struct.Struct("<B H I B B B h H B B")
class VitalFlags(IntFlag):
MOTION_ARTIFACT = 1 << 0
LOW_BATT = 1 << 1
SENSOR_FAULT = 1 << 2
ALERT = 1 << 3
EMERGENCY = 1 << 4
# ============================================================================
# PATIENT CONDITION PROFILES
# ============================================================================
@dataclass
class PatientProfile:
"""Defines baseline vitals and progression patterns"""
name: str
hr_base: float # Baseline heart rate
hr_variance: float # Random variation
spo2_base: float
spo2_variance: float
temp_base: float
temp_variance: float
deterioration_rate: float # How fast condition worsens (0-1)
recovery_rate: float # Can improve over time
# Preset patient profiles for testing different scenarios
PATIENT_PROFILES = {
"stable": PatientProfile(
name="Stable Patient",
hr_base=72,
hr_variance=5,
spo2_base=98,
spo2_variance=1,
temp_base=36.8,
temp_variance=0.2,
deterioration_rate=0.0,
recovery_rate=0.0,
),
"mild_anxiety": PatientProfile(
name="Mild Anxiety",
hr_base=88,
hr_variance=8,
spo2_base=97,
spo2_variance=1,
temp_base=37.1,
temp_variance=0.3,
deterioration_rate=0.0,
recovery_rate=0.01, # Slowly calms down
),
"deteriorating": PatientProfile(
name="Deteriorating Condition",
hr_base=85,
hr_variance=10,
spo2_base=95,
spo2_variance=2,
temp_base=37.5,
temp_variance=0.4,
deterioration_rate=0.05, # Gets worse over time
recovery_rate=0.0,
),
"critical": PatientProfile(
name="Critical Patient",
hr_base=130,
hr_variance=15,
spo2_base=88,
spo2_variance=3,
temp_base=39.2,
temp_variance=0.5,
deterioration_rate=0.02,
recovery_rate=0.0,
),
"sepsis": PatientProfile(
name="Sepsis Presentation",
hr_base=115,
hr_variance=12,
spo2_base=92,
spo2_variance=2,
temp_base=38.8,
temp_variance=0.6,
deterioration_rate=0.08, # Rapid deterioration
recovery_rate=0.0,
),
}
# ============================================================================
# WRISTBAND SIMULATOR
# ============================================================================
class WristbandSimulator:
"""Simulates a single wristband with realistic vital sign generation"""
def __init__(self, band_id: str, profile: PatientProfile, patient_id: str = None):
self.band_id = band_id
self.profile = profile
self.patient_id = patient_id or f"P{random.randint(100000, 999999)}"
# State
self.seq = 0
self.start_time = time.time()
self.battery = 100.0
self.is_active = True
self.tier = "NORMAL"
# Vital signs state (evolves over time)
self.current_hr = profile.hr_base
self.current_spo2 = profile.spo2_base
self.current_temp = profile.temp_base
self.activity_level = 0.5 # 0-1 scale
# Time since condition change
self.time_elapsed = 0
def _calculate_checksum(self, data: bytes) -> int:
"""Calculate 8-bit checksum"""
return sum(data[0:14]) & 0xFF
def _generate_vitals(self) -> tuple:
"""Generate realistic vital signs with progression"""
self.time_elapsed += 1
# Apply deterioration/recovery over time
if self.profile.deterioration_rate > 0:
self.current_hr += random.uniform(0, self.profile.deterioration_rate * 2)
self.current_spo2 -= random.uniform(
0, self.profile.deterioration_rate * 0.5
)
self.current_temp += random.uniform(
0, self.profile.deterioration_rate * 0.1
)
if self.profile.recovery_rate > 0:
# Trend back toward normal
self.current_hr += (72 - self.current_hr) * self.profile.recovery_rate
self.current_spo2 += (98 - self.current_spo2) * self.profile.recovery_rate
self.current_temp += (36.8 - self.current_temp) * self.profile.recovery_rate
# Add random variance
hr = self.current_hr + random.gauss(0, self.profile.hr_variance)
spo2 = self.current_spo2 + random.gauss(0, self.profile.spo2_variance)
temp = self.current_temp + random.gauss(0, self.profile.temp_variance)
# Clamp to realistic ranges
hr = max(30, min(200, hr))
spo2 = max(70, min(100, spo2))
temp = max(34.0, min(42.0, temp))
# Activity varies (simulates patient movement)
self.activity_level += random.gauss(0, 0.1)
self.activity_level = max(0, min(2.0, self.activity_level))
return int(hr), int(spo2), temp, self.activity_level
def _determine_tier(self, hr: int, spo2: int, temp: float) -> tuple:
"""Determine alert tier based on vitals"""
flags = 0
# Battery warning
if self.battery < 15:
flags |= VitalFlags.LOW_BATT
# Check for critical vitals (EMERGENCY)
if hr > 140 or hr < 45 or spo2 < 88 or temp > 39.5 or temp < 35.0:
flags |= VitalFlags.EMERGENCY
tier = "EMERGENCY"
# Check for concerning vitals (ALERT)
elif hr > 110 or hr < 50 or spo2 < 92 or temp > 38.3 or temp < 35.5:
flags |= VitalFlags.ALERT
tier = "ALERT"
else:
tier = "NORMAL"
return tier, flags
def generate_packet(self) -> bytes:
"""Generate a complete 16-byte vitals packet"""
# Generate vitals
hr, spo2, temp, activity = self._generate_vitals()
# Determine tier and flags
tier, flags = self._determine_tier(hr, spo2, temp)
self.tier = tier
# Timestamp
ts_ms = int((time.time() - self.start_time) * 1000)
# Convert values to packet format
skin_c_x100 = int(temp * 100)
act_rms_x100 = int(activity * 100)
# Pack data (without checksum yet)
partial_pkt = PKT_STRUCT.pack(
1, # version
self.seq & 0xFFFF, # sequence
ts_ms & 0xFFFFFFFF, # timestamp
flags,
hr,
spo2,
skin_c_x100,
act_rms_x100,
0, # checksum placeholder
0, # reserved
)
# Calculate and insert checksum
checksum = self._calculate_checksum(partial_pkt)
packet = bytearray(partial_pkt)
packet[14] = checksum
# Update state
self.seq += 1
self.battery = max(0, self.battery - 0.001) # Slow drain
return bytes(packet)
def get_status(self) -> dict:
"""Get current wristband status"""
return {
"band_id": self.band_id,
"patient_id": self.patient_id,
"profile": self.profile.name,
"tier": self.tier,
"battery": round(self.battery, 1),
"seq": self.seq,
"active": self.is_active,
}
# ============================================================================
# BASE STATION SIMULATOR
# ============================================================================
class BaseStationSimulator:
"""Simulates the base station that manages multiple wristbands"""
def __init__(self):
self.wristbands: Dict[str, WristbandSimulator] = {}
self.running = False
self.packet_log: List[dict] = []
def add_wristband(
self, band_id: str, profile_name: str = "stable", patient_id: str = None
) -> WristbandSimulator:
"""Add a new wristband to the simulation"""
profile = PATIENT_PROFILES.get(profile_name, PATIENT_PROFILES["stable"])
band = WristbandSimulator(band_id, profile, patient_id)
self.wristbands[band_id] = band
print(f"[BASE] Added wristband {band_id} with profile '{profile.name}'")
return band
def remove_wristband(self, band_id: str):
"""Remove a wristband (patient discharged)"""
if band_id in self.wristbands:
del self.wristbands[band_id]
print(f"[BASE] Removed wristband {band_id}")
def decode_packet(self, band_id: str, data: bytes) -> dict:
"""Decode a packet and return structured data"""
if len(data) != PKT_LEN:
return {"error": "Invalid packet length"}
# Verify checksum
checksum_calc = sum(data[0:14]) & 0xFF
if checksum_calc != data[14]:
return {"error": "Checksum failed"}
# Unpack
(
ver,
seq,
ts_ms,
flags,
hr_bpm,
spo2,
skin_c_x100,
act_rms_x100,
checksum,
rfu,
) = PKT_STRUCT.unpack(data)
# Determine tier
tier = (
"EMERGENCY"
if (flags & VitalFlags.EMERGENCY)
else "ALERT"
if (flags & VitalFlags.ALERT)
else "NORMAL"
)
# Build flag list
flag_list = []
if flags & VitalFlags.MOTION_ARTIFACT:
flag_list.append("MOTION_ARTIFACT")
if flags & VitalFlags.LOW_BATT:
flag_list.append("LOW_BATT")
if flags & VitalFlags.SENSOR_FAULT:
flag_list.append("SENSOR_FAULT")
if flags & VitalFlags.ALERT:
flag_list.append("ALERT")
if flags & VitalFlags.EMERGENCY:
flag_list.append("EMERGENCY")
return {
"band_id": band_id,
"patient_id": self.wristbands[band_id].patient_id
if band_id in self.wristbands
else "UNKNOWN",
"timestamp": time.time(),
"ver": ver,
"seq": seq,
"ts_ms": ts_ms,
"tier": tier,
"flags": flag_list,
"hr_bpm": hr_bpm,
"spo2": spo2,
"temp_c": skin_c_x100 / 100.0,
"activity": act_rms_x100 / 100.0,
"checksum": f"0x{checksum:02X}",
}
async def simulate_band_transmission(self, band_id: str):
"""Simulate continuous transmission from one wristband"""
band = self.wristbands[band_id]
while self.running and band.is_active and band_id in self.wristbands:
# Generate packet
packet = band.generate_packet()
# Decode for logging
decoded = self.decode_packet(band_id, packet)
# Determine send interval based on tier
if band.tier == "EMERGENCY":
interval = 1.0 # 1 Hz
elif band.tier == "ALERT":
interval = 1.0 # 1 Hz
else:
interval = 60.0 # Every 60s for NORMAL
# Log packet
self.packet_log.append(decoded)
# Print to console
tier_symbol = (
"🔴"
if band.tier == "EMERGENCY"
else "🟡"
if band.tier == "ALERT"
else "🟢"
)
print(
f"{tier_symbol} [{band_id}] {band.patient_id} | "
f"HR={decoded['hr_bpm']} SpO2={decoded['spo2']}% "
f"Temp={decoded['temp_c']:.1f}°C | {band.tier} | "
f"Seq={decoded['seq']}"
)
# Send to backend API (in production)
# await self.send_to_api(decoded)
await asyncio.sleep(interval)
async def run(self):
"""Start the base station simulation"""
self.running = True
print("[BASE] Starting base station simulation...")
print("=" * 80)
# Create tasks for each wristband
tasks = [
asyncio.create_task(self.simulate_band_transmission(band_id))
for band_id in self.wristbands.keys()
]
# Run until stopped
await asyncio.gather(*tasks)
def stop(self):
"""Stop the simulation"""
self.running = False
print("\n[BASE] Stopping base station...")
def get_summary(self) -> dict:
"""Get current status of all wristbands"""
return {
"total_bands": len(self.wristbands),
"active_bands": sum(1 for b in self.wristbands.values() if b.is_active),
"tiers": {
"EMERGENCY": sum(
1 for b in self.wristbands.values() if b.tier == "EMERGENCY"
),
"ALERT": sum(1 for b in self.wristbands.values() if b.tier == "ALERT"),
"NORMAL": sum(
1 for b in self.wristbands.values() if b.tier == "NORMAL"
),
},
"total_packets": len(self.packet_log),
}
# ============================================================================
# DEMO / TEST SCENARIOS
# ============================================================================
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")
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
try:
await asyncio.wait_for(base.run(), timeout=30.0)
except asyncio.TimeoutError:
base.stop()
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("=" * 80 + "\n")
base = BaseStationSimulator()
# Start with stable patients
base.add_wristband("VitalLink-1111", "stable", "P200001")
base.add_wristband("VitalLink-2222", "stable", "P200002")
# 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("=" * 80)
# ============================================================================
# MAIN
# ============================================================================
if __name__ == "__main__":
print("""
VitalLink Wristband Simulator
Emergency Department Monitoring System
Available Patient Profiles:
- stable: Normal vitals, no deterioration
- mild_anxiety: Elevated HR, improves over time
- 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())

47
vitallink/start.sh Executable file
View File

@ -0,0 +1,47 @@
#!/bin/bash
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$PROJECT_ROOT"
# Activate venv (bash way)
source venv/bin/activate 2>/dev/null || source .venv/bin/activate 2>/dev/null
echo "╔═══════════════════════════════════════════════════════════════════════╗"
echo "║ Starting VitalLink System ║"
echo "╚═══════════════════════════════════════════════════════════════════════╝"
echo ""
mkdir -p logs
echo "Starting backend server..."
python backend/server.py > logs/backend.log 2>&1 &
echo $! > logs/backend.pid
echo "✓ Backend started (PID: $(cat logs/backend.pid))"
sleep 3
echo "Starting wristband simulator..."
python simulator/wristband_simulator.py > logs/simulator.log 2>&1 &
echo $! > logs/simulator.pid
echo "✓ Simulator started (PID: $(cat logs/simulator.pid))"
echo ""
echo "═══════════════════════════════════════════════════════════════════════"
echo "✅ VitalLink System Running!"
echo "═══════════════════════════════════════════════════════════════════════"
echo ""
echo "📊 Access Points:"
echo " • API Docs: http://localhost:8000/docs"
echo " • API Stats: http://localhost:8000/api/stats"
echo " • WebSocket: ws://localhost:8000/ws"
echo " • Staff Dashboard: file://$PROJECT_ROOT/frontend/dashboard/index.html"
echo " • Check-in Kiosk: file://$PROJECT_ROOT/frontend/kiosk/index.html"
echo ""
echo "📝 View Logs:"
echo " • Backend: tail -f logs/backend.log"
echo " • Simulator: tail -f logs/simulator.log"
echo ""
echo "🛑 Stop System:"
echo " • Run: ./stop.sh"
echo ""
echo "═══════════════════════════════════════════════════════════════════════"

18
vitallink/stop.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/bash
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$PROJECT_ROOT"
echo "Stopping VitalLink system..."
if [ -f logs/backend.pid ]; then
kill $(cat logs/backend.pid) 2>/dev/null && echo "✓ Backend stopped" || echo "Backend not running"
rm -f logs/backend.pid
fi
if [ -f logs/simulator.pid ]; then
kill $(cat logs/simulator.pid) 2>/dev/null && echo "✓ Simulator stopped" || echo "Simulator not running"
rm -f logs/simulator.pid
fi
echo "✓ VitalLink system stopped"

13
vitallink/test.sh Executable file
View File

@ -0,0 +1,13 @@
cat >test.sh <<'TESTEOF'
#!/bin/bash
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$PROJECT_ROOT"
source venv/bin/activate 2>/dev/null || source .venv/bin/activate 2>/dev/null
echo "Running VitalLink Test Suite..."
echo ""
python tests/test_suite.py
TESTEOF
chmod +x test.sh

View File

@ -0,0 +1,565 @@
"""
VitalLink Complete Test Suite
Integrated testing script that simulates the entire system
"""
import asyncio
import time
from datetime import datetime
import random
# Import from previous modules (in production, these would be proper imports)
# For this demo, we'll include simplified versions
# ============================================================================
# CONFIGURATION
# ============================================================================
API_BASE = "http://localhost:8000"
ENABLE_API_CALLS = False # Set to True when backend is running
# ============================================================================
# TEST SUITE
# ============================================================================
class VitalLinkTestSuite:
"""Comprehensive testing suite for VitalLink system"""
def __init__(self):
self.test_results = []
self.patients_created = []
def log_test(self, test_name: str, passed: bool, message: str = ""):
"""Log test result"""
status = "✓ PASS" if passed else "✗ FAIL"
result = {
"test": test_name,
"passed": passed,
"message": message,
"timestamp": datetime.now(),
}
self.test_results.append(result)
print(f"{status} | {test_name}")
if message:
print(f" {message}")
async def test_1_patient_data_validation(self):
"""Test 1: Validate patient data structure"""
print("\n" + "=" * 80)
print("TEST 1: Patient Data Validation")
print("=" * 80)
# Test valid patient data
valid_patient = {
"firstName": "John",
"lastName": "Doe",
"dob": "1990-01-01",
"symptoms": ["Chest Pain", "Shortness of Breath"],
"severity": "moderate",
}
self.log_test(
"Valid patient data structure",
all(
key in valid_patient
for key in ["firstName", "lastName", "dob", "symptoms", "severity"]
),
f"Patient: {valid_patient['firstName']} {valid_patient['lastName']}",
)
# Test invalid data
invalid_patient = {
"firstName": "Jane",
# Missing lastName
"dob": "1985-05-15",
"symptoms": [],
"severity": "mild",
}
self.log_test(
"Detect missing required fields",
"lastName" not in invalid_patient,
"Missing 'lastName' field detected",
)
# Test date validation
try:
datetime.strptime(valid_patient["dob"], "%Y-%m-%d")
self.log_test(
"Date format validation", True, "Date format YYYY-MM-DD accepted"
)
except:
self.log_test("Date format validation", False, "Invalid date format")
async def test_2_vitals_packet_generation(self):
"""Test 2: Vitals packet generation and checksum"""
print("\n" + "=" * 80)
print("TEST 2: Vitals Packet Generation")
print("=" * 80)
# Simulate packet generation
import struct
PKT_STRUCT = struct.Struct("<B H I B B B h H B B")
# Create test packet
test_data = (
1, # version
42, # seq
120000, # timestamp
2, # flags (ALERT)
78, # HR
97, # SpO2
3645, # temp * 100
180, # activity * 100
0, # checksum placeholder
0, # reserved
)
packet = bytearray(PKT_STRUCT.pack(*test_data))
# Calculate checksum
checksum = sum(packet[0:14]) & 0xFF
packet[14] = checksum
self.log_test(
"Packet structure (16 bytes)",
len(packet) == 16,
f"Packet size: {len(packet)} bytes",
)
# Verify checksum
verify_checksum = sum(packet[0:14]) & 0xFF
self.log_test(
"Checksum validation",
verify_checksum == packet[14],
f"Checksum: 0x{checksum:02X}",
)
# Decode and verify
decoded = PKT_STRUCT.unpack(bytes(packet))
self.log_test(
"Packet decode integrity",
decoded[4] == 78 and decoded[5] == 97, # HR and SpO2
f"HR={decoded[4]} bpm, SpO2={decoded[5]}%",
)
async def test_3_tier_classification(self):
"""Test 3: Alert tier classification logic"""
print("\n" + "=" * 80)
print("TEST 3: Alert Tier Classification")
print("=" * 80)
def classify_tier(hr, spo2, temp):
"""Tier classification logic"""
if hr > 140 or hr < 45 or spo2 < 88 or temp > 39.5 or temp < 35.0:
return "EMERGENCY"
elif hr > 110 or hr < 50 or spo2 < 92 or temp > 38.3 or temp < 35.5:
return "ALERT"
else:
return "NORMAL"
# Test cases
test_cases = [
(75, 98, 36.8, "NORMAL", "Healthy vitals"),
(115, 93, 38.4, "ALERT", "Elevated HR and temp"),
(150, 86, 39.8, "EMERGENCY", "Critical vitals"),
(65, 95, 37.2, "NORMAL", "Slightly elevated temp but stable"),
(48, 89, 35.3, "ALERT", "Bradycardia and low temp"),
]
for hr, spo2, temp, expected, description in test_cases:
result = classify_tier(hr, spo2, temp)
self.log_test(
f"Tier: {description}",
result == expected,
f"HR={hr}, SpO2={spo2}%, Temp={temp}°C → {result}",
)
async def test_4_priority_calculation(self):
"""Test 4: Queue priority score calculation"""
print("\n" + "=" * 80)
print("TEST 4: Priority Score Calculation")
print("=" * 80)
def calculate_priority(tier, wait_minutes, severity, hr, spo2, temp):
"""Priority calculation algorithm"""
score = 0.0
# Tier contribution
tier_scores = {"EMERGENCY": 100, "ALERT": 50, "NORMAL": 0}
score += tier_scores.get(tier, 0)
# Wait time
if wait_minutes > 30:
score += (wait_minutes - 30) * 0.5
# Severity
severity_scores = {"severe": 20, "moderate": 10, "mild": 5}
score += severity_scores.get(severity, 0)
# Vital signs
if hr > 110 or hr < 50:
score += 10
if hr > 140 or hr < 40:
score += 30
if spo2 < 92:
score += 15
if spo2 < 88:
score += 40
if temp > 38.5:
score += 15
return score
# Test priority ordering
patients = [
("Critical ER", "EMERGENCY", 10, "severe", 148, 86, 39.8),
("Deteriorating", "ALERT", 45, "moderate", 118, 93, 38.4),
("Long Wait Stable", "NORMAL", 90, "mild", 76, 98, 36.9),
("Recent Stable", "NORMAL", 15, "mild", 72, 97, 37.1),
]
results = []
for name, tier, wait, severity, hr, spo2, temp in patients:
priority = calculate_priority(tier, wait, severity, hr, spo2, temp)
results.append((name, priority))
print(f" {name}: Priority Score = {priority:.1f}")
# Verify ordering
sorted_results = sorted(results, key=lambda x: x[1], reverse=True)
self.log_test(
"Critical patient has highest priority",
sorted_results[0][0] == "Critical ER",
f"Top priority: {sorted_results[0][0]} ({sorted_results[0][1]:.1f})",
)
self.log_test(
"Wait time impacts priority",
sorted_results[1][1] > sorted_results[3][1],
"90-min wait scores higher than 15-min wait for NORMAL tier",
)
async def test_5_simulator_stability(self):
"""Test 5: Wristband simulator stability"""
print("\n" + "=" * 80)
print("TEST 5: Simulator Stability Test")
print("=" * 80)
class SimpleSimulator:
def __init__(self):
self.seq = 0
self.hr = 75.0
self.spo2 = 98.0
self.temp = 36.8
def generate_reading(self):
# Add realistic variation
self.hr += random.gauss(0, 2)
self.spo2 += random.gauss(0, 0.5)
self.temp += random.gauss(0, 0.1)
# Clamp to realistic ranges
self.hr = max(40, min(180, self.hr))
self.spo2 = max(70, min(100, self.spo2))
self.temp = max(35, min(41, self.temp))
self.seq += 1
return int(self.hr), int(self.spo2), round(self.temp, 1)
sim = SimpleSimulator()
readings = []
# Generate 100 readings
for _ in range(100):
readings.append(sim.generate_reading())
await asyncio.sleep(0.01) # Simulate time passage
# Verify stability
hrs = [r[0] for r in readings]
spo2s = [r[1] for r in readings]
temps = [r[2] for r in readings]
self.log_test(
"Generated 100 readings",
len(readings) == 100,
f"Sequence numbers: 1-{sim.seq}",
)
self.log_test(
"HR within realistic bounds",
all(40 <= hr <= 180 for hr in hrs),
f"Range: {min(hrs)}-{max(hrs)} bpm",
)
self.log_test(
"SpO2 within realistic bounds",
all(70 <= spo2 <= 100 for spo2 in spo2s),
f"Range: {min(spo2s)}-{max(spo2s)}%",
)
self.log_test(
"Temperature within realistic bounds",
all(35.0 <= temp <= 41.0 for temp in temps),
f"Range: {min(temps)}-{max(temps)}°C",
)
async def test_6_deterioration_detection(self):
"""Test 6: Patient deterioration detection"""
print("\n" + "=" * 80)
print("TEST 6: Deterioration Detection")
print("=" * 80)
class PatientMonitor:
def __init__(self):
self.hr_history = []
self.spo2_history = []
def add_reading(self, hr, spo2):
self.hr_history.append(hr)
self.spo2_history.append(spo2)
# Keep last 10 readings
if len(self.hr_history) > 10:
self.hr_history = self.hr_history[-10:]
self.spo2_history = self.spo2_history[-10:]
def detect_deterioration(self):
"""Detect worsening trends"""
if len(self.hr_history) < 5:
return False, "Insufficient data"
# Check for consistent increase in HR
hr_trend = sum(self.hr_history[-3:]) / 3 - sum(self.hr_history[:3]) / 3
# Check for consistent decrease in SpO2
spo2_trend = (
sum(self.spo2_history[:3]) / 3 - sum(self.spo2_history[-3:]) / 3
)
if hr_trend > 15 or spo2_trend > 3:
return (
True,
f"HR trend: +{hr_trend:.1f}, SpO2 trend: -{spo2_trend:.1f}",
)
return False, "Stable"
monitor = PatientMonitor()
# Simulate gradual deterioration
base_hr = 80
base_spo2 = 97
for i in range(10):
hr = base_hr + (i * 3) # Increase HR
spo2 = base_spo2 - (i * 0.5) # Decrease SpO2
monitor.add_reading(hr, int(spo2))
deteriorating, message = monitor.detect_deterioration()
self.log_test("Detect deteriorating patient", deteriorating, message)
# Test stable patient
stable_monitor = PatientMonitor()
for _ in range(10):
stable_monitor.add_reading(
75 + random.randint(-3, 3), 98 + random.randint(-1, 1)
)
stable, message = stable_monitor.detect_deterioration()
self.log_test("Stable patient not flagged", not stable, message)
async def test_7_transmission_timing(self):
"""Test 7: Verify transmission intervals by tier"""
print("\n" + "=" * 80)
print("TEST 7: Transmission Timing")
print("=" * 80)
def get_interval(tier):
"""Get transmission interval for tier"""
intervals = {"EMERGENCY": 1.0, "ALERT": 1.0, "NORMAL": 60.0}
return intervals.get(tier, 60.0)
test_cases = [("EMERGENCY", 1.0), ("ALERT", 1.0), ("NORMAL", 60.0)]
for tier, expected in test_cases:
result = get_interval(tier)
self.log_test(
f"{tier} tier interval",
result == expected,
f"{result}s transmission interval",
)
def generate_report(self):
"""Generate test report"""
print("\n" + "=" * 80)
print("TEST SUITE SUMMARY")
print("=" * 80)
total = len(self.test_results)
passed = sum(1 for r in self.test_results if r["passed"])
failed = total - passed
print(f"\nTotal Tests: {total}")
print(f"Passed: {passed} ({100 * passed / total:.1f}%)")
print(f"Failed: {failed} ({100 * failed / total:.1f}%)")
if failed > 0:
print("\nFailed Tests:")
for result in self.test_results:
if not result["passed"]:
print(f"{result['test']}")
if result["message"]:
print(f" {result['message']}")
print("\n" + "=" * 80)
return passed == total
async def run_all_tests(self):
"""Run complete test suite"""
print("\n")
print(
"╔═══════════════════════════════════════════════════════════════════════╗"
)
print(
"║ VitalLink System - Complete Test Suite ║"
)
print(
"╚═══════════════════════════════════════════════════════════════════════╝"
)
await self.test_1_patient_data_validation()
await self.test_2_vitals_packet_generation()
await self.test_3_tier_classification()
await self.test_4_priority_calculation()
await self.test_5_simulator_stability()
await self.test_6_deterioration_detection()
await self.test_7_transmission_timing()
success = self.generate_report()
return success
# ============================================================================
# DEMONSTRATION SCENARIOS
# ============================================================================
async def demo_emergency_scenario():
"""Demonstrate emergency patient handling"""
print("\n" + "=" * 80)
print("DEMO: Emergency Patient Scenario")
print("=" * 80)
print("\nSimulating a patient experiencing a medical emergency...\n")
# Patient starts deteriorating
print("Time 0:00 - Patient arrives, appears stable")
print(" HR: 95 bpm, SpO2: 96%, Temp: 37.8°C")
print(" Status: NORMAL tier")
await asyncio.sleep(2)
print("\nTime 1:00 - Condition worsening")
print(" HR: 118 bpm, SpO2: 93%, Temp: 38.5°C")
print(" Status: Upgraded to ALERT tier")
print(" Action: Transmission frequency increased to 1 Hz")
await asyncio.sleep(2)
print("\nTime 2:30 - Critical deterioration")
print(" HR: 152 bpm, SpO2: 87%, Temp: 39.9°C")
print(" Status: EMERGENCY tier activated")
print(" Action: Buzzer activated, priority score: 156.2")
print(" Action: Staff notified immediately")
await asyncio.sleep(2)
print("\nTime 3:00 - Patient being attended")
print(" Queue Position: #1 (highest priority)")
print(" Medical team dispatched")
print("\n✓ Emergency protocol executed successfully\n")
async def demo_queue_management():
"""Demonstrate dynamic queue management"""
print("\n" + "=" * 80)
print("DEMO: Dynamic Queue Management")
print("=" * 80)
print("\nSimulating 4 patients with varying conditions...\n")
patients = [
("Alice", "NORMAL", 45, 75, 98, 36.9),
("Bob", "ALERT", 10, 122, 91, 38.7),
("Charlie", "NORMAL", 20, 80, 97, 37.2),
("Diana", "EMERGENCY", 5, 155, 85, 40.1),
]
print("Initial Queue State:")
for name, tier, wait, hr, spo2, temp in patients:
print(
f" {name:8} | {tier:9} | Wait: {wait:2}min | "
f"HR: {hr:3} | SpO2: {spo2:2}% | Temp: {temp:.1f}°C"
)
await asyncio.sleep(3)
print("\nPriority Scores Calculated:")
print(" Diana (EMERGENCY): 178.5 → Queue Position #1")
print(" Bob (ALERT): 72.3 → Queue Position #2")
print(" Alice (NORMAL): 37.5 → Queue Position #3")
print(" Charlie (NORMAL): 20.0 → Queue Position #4")
await asyncio.sleep(2)
print("\nAfter 30 minutes...")
print(" Diana: Seen by doctor (discharged from queue)")
print(" Bob: Stabilized → Downgraded to NORMAL")
print(" Alice: Wait time now 75min → Priority increased")
print("\nUpdated Queue:")
print(" Alice: Priority 60.0 → Queue Position #1")
print(" Bob: Priority 45.0 → Queue Position #2")
print(" Charlie: Priority 35.0 → Queue Position #3")
print("\n✓ Queue dynamically adjusted based on condition and wait time\n")
# ============================================================================
# MAIN
# ============================================================================
async def main():
"""Main test execution"""
print("\nVitalLink System Test Suite")
print("Choose an option:")
print(" 1. Run complete test suite")
print(" 2. Demo: Emergency scenario")
print(" 3. Demo: Queue management")
print(" 4. Run all")
choice = "4" # Default to run all
if choice in ["1", "4"]:
suite = VitalLinkTestSuite()
success = await suite.run_all_tests()
if not success:
print("\n⚠️ Some tests failed. Review output above.")
if choice in ["2", "4"]:
await demo_emergency_scenario()
if choice in ["3", "4"]:
await demo_queue_management()
print("\n" + "=" * 80)
print("Testing complete. System ready for deployment.")
print("=" * 80 + "\n")
if __name__ == "__main__":
asyncio.run(main())