Major changes: - Organized Python v1 implementation into v1/ subdirectory - Created Rust workspace with 9 modular crates: - wifi-densepose-core: Core types, traits, errors - wifi-densepose-signal: CSI processing, phase sanitization, FFT - wifi-densepose-nn: Neural network inference (ONNX/Candle/tch) - wifi-densepose-api: Axum-based REST/WebSocket API - wifi-densepose-db: SQLx database layer - wifi-densepose-config: Configuration management - wifi-densepose-hardware: Hardware abstraction - wifi-densepose-wasm: WebAssembly bindings - wifi-densepose-cli: Command-line interface Documentation: - ADR-001: Workspace structure - ADR-002: Signal processing library selection - ADR-003: Neural network inference strategy - DDD domain model with bounded contexts Testing: - 69 tests passing across all crates - Signal processing: 45 tests - Neural networks: 21 tests - Core: 3 doc tests Performance targets: - 10x faster CSI processing (~0.5ms vs ~5ms) - 5x lower memory usage (~100MB vs ~500MB) - WASM support for browser deployment
736 lines
28 KiB
Python
736 lines
28 KiB
Python
"""
|
|
End-to-end tests for healthcare fall detection scenario.
|
|
|
|
Tests complete workflow from CSI data collection to fall alert generation.
|
|
"""
|
|
|
|
import pytest
|
|
import asyncio
|
|
import numpy as np
|
|
from datetime import datetime, timedelta
|
|
from typing import Dict, Any, List, Optional
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
import json
|
|
from dataclasses import dataclass
|
|
from enum import Enum
|
|
|
|
|
|
class AlertSeverity(Enum):
|
|
"""Alert severity levels."""
|
|
LOW = "low"
|
|
MEDIUM = "medium"
|
|
HIGH = "high"
|
|
CRITICAL = "critical"
|
|
|
|
|
|
@dataclass
|
|
class HealthcareAlert:
|
|
"""Healthcare alert data structure."""
|
|
alert_id: str
|
|
timestamp: datetime
|
|
alert_type: str
|
|
severity: AlertSeverity
|
|
patient_id: str
|
|
location: str
|
|
confidence: float
|
|
description: str
|
|
metadata: Dict[str, Any]
|
|
|
|
|
|
class MockPatientMonitor:
|
|
"""Mock patient monitoring system."""
|
|
|
|
def __init__(self, patient_id: str, room_id: str):
|
|
self.patient_id = patient_id
|
|
self.room_id = room_id
|
|
self.is_monitoring = False
|
|
self.baseline_activity = None
|
|
self.activity_history = []
|
|
self.alerts_generated = []
|
|
self.fall_detection_enabled = True
|
|
self.sensitivity_level = "medium"
|
|
|
|
async def start_monitoring(self) -> bool:
|
|
"""Start patient monitoring."""
|
|
if self.is_monitoring:
|
|
return False
|
|
|
|
self.is_monitoring = True
|
|
return True
|
|
|
|
async def stop_monitoring(self) -> bool:
|
|
"""Stop patient monitoring."""
|
|
if not self.is_monitoring:
|
|
return False
|
|
|
|
self.is_monitoring = False
|
|
return True
|
|
|
|
async def process_pose_data(self, pose_data: Dict[str, Any]) -> Optional[HealthcareAlert]:
|
|
"""Process pose data and detect potential issues."""
|
|
if not self.is_monitoring:
|
|
return None
|
|
|
|
# Extract activity metrics
|
|
activity_metrics = self._extract_activity_metrics(pose_data)
|
|
self.activity_history.append(activity_metrics)
|
|
|
|
# Keep only recent history
|
|
if len(self.activity_history) > 100:
|
|
self.activity_history = self.activity_history[-100:]
|
|
|
|
# Detect anomalies
|
|
alert = await self._detect_anomalies(activity_metrics, pose_data)
|
|
|
|
if alert:
|
|
self.alerts_generated.append(alert)
|
|
|
|
return alert
|
|
|
|
def _extract_activity_metrics(self, pose_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Extract activity metrics from pose data."""
|
|
persons = pose_data.get("persons", [])
|
|
|
|
if not persons:
|
|
return {
|
|
"person_count": 0,
|
|
"activity_level": 0.0,
|
|
"posture": "unknown",
|
|
"movement_speed": 0.0,
|
|
"stability_score": 1.0
|
|
}
|
|
|
|
# Analyze first person (primary patient)
|
|
person = persons[0]
|
|
|
|
# Extract posture from activity field or bounding box analysis
|
|
posture = person.get("activity", "standing")
|
|
|
|
# If no activity specified, analyze bounding box for fall detection
|
|
if posture == "standing" and "bounding_box" in person:
|
|
bbox = person["bounding_box"]
|
|
width = bbox.get("width", 80)
|
|
height = bbox.get("height", 180)
|
|
|
|
# Fall detection: if width > height, likely fallen
|
|
if width > height * 1.5:
|
|
posture = "fallen"
|
|
|
|
# Calculate activity metrics based on posture
|
|
if posture == "fallen":
|
|
activity_level = 0.1
|
|
movement_speed = 0.0
|
|
stability_score = 0.2
|
|
elif posture == "walking":
|
|
activity_level = 0.8
|
|
movement_speed = 1.5
|
|
stability_score = 0.7
|
|
elif posture == "sitting":
|
|
activity_level = 0.3
|
|
movement_speed = 0.1
|
|
stability_score = 0.9
|
|
else: # standing or other
|
|
activity_level = 0.5
|
|
movement_speed = 0.2
|
|
stability_score = 0.8
|
|
|
|
return {
|
|
"person_count": len(persons),
|
|
"activity_level": activity_level,
|
|
"posture": posture,
|
|
"movement_speed": movement_speed,
|
|
"stability_score": stability_score,
|
|
"confidence": person.get("confidence", 0.0)
|
|
}
|
|
|
|
async def _detect_anomalies(self, current_metrics: Dict[str, Any], pose_data: Dict[str, Any]) -> Optional[HealthcareAlert]:
|
|
"""Detect health-related anomalies."""
|
|
# Fall detection
|
|
if current_metrics["posture"] == "fallen":
|
|
return await self._generate_fall_alert(current_metrics, pose_data)
|
|
|
|
# Prolonged inactivity detection
|
|
if len(self.activity_history) >= 10:
|
|
recent_activity = [m["activity_level"] for m in self.activity_history[-10:]]
|
|
avg_activity = np.mean(recent_activity)
|
|
|
|
if avg_activity < 0.1: # Very low activity
|
|
return await self._generate_inactivity_alert(current_metrics, pose_data)
|
|
|
|
# Unusual movement patterns
|
|
if current_metrics["stability_score"] < 0.4:
|
|
return await self._generate_instability_alert(current_metrics, pose_data)
|
|
|
|
return None
|
|
|
|
async def _generate_fall_alert(self, metrics: Dict[str, Any], pose_data: Dict[str, Any]) -> HealthcareAlert:
|
|
"""Generate fall detection alert."""
|
|
return HealthcareAlert(
|
|
alert_id=f"fall_{self.patient_id}_{int(datetime.utcnow().timestamp())}",
|
|
timestamp=datetime.utcnow(),
|
|
alert_type="fall_detected",
|
|
severity=AlertSeverity.CRITICAL,
|
|
patient_id=self.patient_id,
|
|
location=self.room_id,
|
|
confidence=metrics["confidence"],
|
|
description=f"Fall detected for patient {self.patient_id} in {self.room_id}",
|
|
metadata={
|
|
"posture": metrics["posture"],
|
|
"stability_score": metrics["stability_score"],
|
|
"pose_data": pose_data
|
|
}
|
|
)
|
|
|
|
async def _generate_inactivity_alert(self, metrics: Dict[str, Any], pose_data: Dict[str, Any]) -> HealthcareAlert:
|
|
"""Generate prolonged inactivity alert."""
|
|
return HealthcareAlert(
|
|
alert_id=f"inactivity_{self.patient_id}_{int(datetime.utcnow().timestamp())}",
|
|
timestamp=datetime.utcnow(),
|
|
alert_type="prolonged_inactivity",
|
|
severity=AlertSeverity.MEDIUM,
|
|
patient_id=self.patient_id,
|
|
location=self.room_id,
|
|
confidence=metrics["confidence"],
|
|
description=f"Prolonged inactivity detected for patient {self.patient_id}",
|
|
metadata={
|
|
"activity_level": metrics["activity_level"],
|
|
"duration_minutes": 10,
|
|
"pose_data": pose_data
|
|
}
|
|
)
|
|
|
|
async def _generate_instability_alert(self, metrics: Dict[str, Any], pose_data: Dict[str, Any]) -> HealthcareAlert:
|
|
"""Generate movement instability alert."""
|
|
return HealthcareAlert(
|
|
alert_id=f"instability_{self.patient_id}_{int(datetime.utcnow().timestamp())}",
|
|
timestamp=datetime.utcnow(),
|
|
alert_type="movement_instability",
|
|
severity=AlertSeverity.HIGH,
|
|
patient_id=self.patient_id,
|
|
location=self.room_id,
|
|
confidence=metrics["confidence"],
|
|
description=f"Movement instability detected for patient {self.patient_id}",
|
|
metadata={
|
|
"stability_score": metrics["stability_score"],
|
|
"movement_speed": metrics["movement_speed"],
|
|
"pose_data": pose_data
|
|
}
|
|
)
|
|
|
|
def get_monitoring_stats(self) -> Dict[str, Any]:
|
|
"""Get monitoring statistics."""
|
|
return {
|
|
"patient_id": self.patient_id,
|
|
"room_id": self.room_id,
|
|
"is_monitoring": self.is_monitoring,
|
|
"total_alerts": len(self.alerts_generated),
|
|
"alert_types": {
|
|
alert.alert_type: len([a for a in self.alerts_generated if a.alert_type == alert.alert_type])
|
|
for alert in self.alerts_generated
|
|
},
|
|
"activity_samples": len(self.activity_history),
|
|
"fall_detection_enabled": self.fall_detection_enabled
|
|
}
|
|
|
|
|
|
class MockHealthcareNotificationSystem:
|
|
"""Mock healthcare notification system."""
|
|
|
|
def __init__(self):
|
|
self.notifications_sent = []
|
|
self.notification_channels = {
|
|
"nurse_station": True,
|
|
"mobile_app": True,
|
|
"email": True,
|
|
"sms": False
|
|
}
|
|
self.escalation_rules = {
|
|
AlertSeverity.CRITICAL: ["nurse_station", "mobile_app", "sms"],
|
|
AlertSeverity.HIGH: ["nurse_station", "mobile_app"],
|
|
AlertSeverity.MEDIUM: ["nurse_station"],
|
|
AlertSeverity.LOW: ["mobile_app"]
|
|
}
|
|
|
|
async def send_alert_notification(self, alert: HealthcareAlert) -> Dict[str, bool]:
|
|
"""Send alert notification through appropriate channels."""
|
|
channels_to_notify = self.escalation_rules.get(alert.severity, ["nurse_station"])
|
|
results = {}
|
|
|
|
for channel in channels_to_notify:
|
|
if self.notification_channels.get(channel, False):
|
|
success = await self._send_to_channel(channel, alert)
|
|
results[channel] = success
|
|
|
|
if success:
|
|
self.notifications_sent.append({
|
|
"alert_id": alert.alert_id,
|
|
"channel": channel,
|
|
"timestamp": datetime.utcnow(),
|
|
"severity": alert.severity.value
|
|
})
|
|
|
|
return results
|
|
|
|
async def _send_to_channel(self, channel: str, alert: HealthcareAlert) -> bool:
|
|
"""Send notification to specific channel."""
|
|
# Simulate network delay
|
|
await asyncio.sleep(0.01)
|
|
|
|
# Simulate occasional failures
|
|
if np.random.random() < 0.05: # 5% failure rate
|
|
return False
|
|
|
|
return True
|
|
|
|
def get_notification_stats(self) -> Dict[str, Any]:
|
|
"""Get notification statistics."""
|
|
return {
|
|
"total_notifications": len(self.notifications_sent),
|
|
"notifications_by_channel": {
|
|
channel: len([n for n in self.notifications_sent if n["channel"] == channel])
|
|
for channel in self.notification_channels.keys()
|
|
},
|
|
"notifications_by_severity": {
|
|
severity.value: len([n for n in self.notifications_sent if n["severity"] == severity.value])
|
|
for severity in AlertSeverity
|
|
}
|
|
}
|
|
|
|
|
|
class TestHealthcareFallDetection:
|
|
"""Test healthcare fall detection workflow."""
|
|
|
|
@pytest.fixture
|
|
def patient_monitor(self):
|
|
"""Create patient monitor."""
|
|
return MockPatientMonitor("patient_001", "room_101")
|
|
|
|
@pytest.fixture
|
|
def notification_system(self):
|
|
"""Create notification system."""
|
|
return MockHealthcareNotificationSystem()
|
|
|
|
@pytest.fixture
|
|
def fall_pose_data(self):
|
|
"""Create pose data indicating a fall."""
|
|
return {
|
|
"persons": [
|
|
{
|
|
"person_id": "patient_001",
|
|
"confidence": 0.92,
|
|
"bounding_box": {"x": 200, "y": 400, "width": 150, "height": 80}, # Horizontal position
|
|
"activity": "fallen",
|
|
"keypoints": [[x, y, 0.8] for x, y in zip(range(17), range(17))]
|
|
}
|
|
],
|
|
"zone_summary": {"room_101": 1},
|
|
"timestamp": datetime.utcnow().isoformat()
|
|
}
|
|
|
|
@pytest.fixture
|
|
def normal_pose_data(self):
|
|
"""Create normal pose data."""
|
|
return {
|
|
"persons": [
|
|
{
|
|
"person_id": "patient_001",
|
|
"confidence": 0.88,
|
|
"bounding_box": {"x": 200, "y": 150, "width": 80, "height": 180},
|
|
"activity": "standing",
|
|
"keypoints": [[x, y, 0.9] for x, y in zip(range(17), range(17))]
|
|
}
|
|
],
|
|
"zone_summary": {"room_101": 1},
|
|
"timestamp": datetime.utcnow().isoformat()
|
|
}
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fall_detection_workflow_should_fail_initially(self, patient_monitor, notification_system, fall_pose_data):
|
|
"""Test fall detection workflow - should fail initially."""
|
|
# Start monitoring
|
|
result = await patient_monitor.start_monitoring()
|
|
|
|
# This will fail initially
|
|
assert result is True
|
|
assert patient_monitor.is_monitoring is True
|
|
|
|
# Process fall pose data
|
|
alert = await patient_monitor.process_pose_data(fall_pose_data)
|
|
|
|
# Should generate fall alert
|
|
assert alert is not None
|
|
assert alert.alert_type == "fall_detected"
|
|
assert alert.severity == AlertSeverity.CRITICAL
|
|
assert alert.patient_id == "patient_001"
|
|
|
|
# Send notification
|
|
notification_results = await notification_system.send_alert_notification(alert)
|
|
|
|
# Should notify appropriate channels
|
|
assert len(notification_results) > 0
|
|
assert any(notification_results.values()) # At least one channel should succeed
|
|
|
|
# Check statistics
|
|
monitor_stats = patient_monitor.get_monitoring_stats()
|
|
assert monitor_stats["total_alerts"] == 1
|
|
|
|
notification_stats = notification_system.get_notification_stats()
|
|
assert notification_stats["total_notifications"] > 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_normal_activity_monitoring_should_fail_initially(self, patient_monitor, normal_pose_data):
|
|
"""Test normal activity monitoring - should fail initially."""
|
|
await patient_monitor.start_monitoring()
|
|
|
|
# Process multiple normal pose data samples
|
|
alerts_generated = []
|
|
|
|
for i in range(10):
|
|
alert = await patient_monitor.process_pose_data(normal_pose_data)
|
|
if alert:
|
|
alerts_generated.append(alert)
|
|
|
|
# This will fail initially
|
|
# Should not generate alerts for normal activity
|
|
assert len(alerts_generated) == 0
|
|
|
|
# Should have activity history
|
|
stats = patient_monitor.get_monitoring_stats()
|
|
assert stats["activity_samples"] == 10
|
|
assert stats["is_monitoring"] is True
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_prolonged_inactivity_detection_should_fail_initially(self, patient_monitor):
|
|
"""Test prolonged inactivity detection - should fail initially."""
|
|
await patient_monitor.start_monitoring()
|
|
|
|
# Simulate prolonged inactivity
|
|
inactive_pose_data = {
|
|
"persons": [], # No person detected
|
|
"zone_summary": {"room_101": 0},
|
|
"timestamp": datetime.utcnow().isoformat()
|
|
}
|
|
|
|
alerts_generated = []
|
|
|
|
# Process multiple inactive samples
|
|
for i in range(15):
|
|
alert = await patient_monitor.process_pose_data(inactive_pose_data)
|
|
if alert:
|
|
alerts_generated.append(alert)
|
|
|
|
# This will fail initially
|
|
# Should generate inactivity alert after sufficient samples
|
|
inactivity_alerts = [a for a in alerts_generated if a.alert_type == "prolonged_inactivity"]
|
|
assert len(inactivity_alerts) > 0
|
|
|
|
# Check alert properties
|
|
alert = inactivity_alerts[0]
|
|
assert alert.severity == AlertSeverity.MEDIUM
|
|
assert alert.patient_id == "patient_001"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_movement_instability_detection_should_fail_initially(self, patient_monitor):
|
|
"""Test movement instability detection - should fail initially."""
|
|
await patient_monitor.start_monitoring()
|
|
|
|
# Simulate unstable movement
|
|
unstable_pose_data = {
|
|
"persons": [
|
|
{
|
|
"person_id": "patient_001",
|
|
"confidence": 0.65, # Lower confidence indicates instability
|
|
"bounding_box": {"x": 200, "y": 150, "width": 80, "height": 180},
|
|
"activity": "walking",
|
|
"keypoints": [[x, y, 0.5] for x, y in zip(range(17), range(17))] # Low keypoint confidence
|
|
}
|
|
],
|
|
"zone_summary": {"room_101": 1},
|
|
"timestamp": datetime.utcnow().isoformat()
|
|
}
|
|
|
|
# Process unstable pose data
|
|
alert = await patient_monitor.process_pose_data(unstable_pose_data)
|
|
|
|
# This will fail initially
|
|
# May generate instability alert based on stability score
|
|
if alert and alert.alert_type == "movement_instability":
|
|
assert alert.severity == AlertSeverity.HIGH
|
|
assert alert.patient_id == "patient_001"
|
|
assert "stability_score" in alert.metadata
|
|
|
|
|
|
class TestHealthcareMultiPatientMonitoring:
|
|
"""Test multi-patient monitoring scenarios."""
|
|
|
|
@pytest.fixture
|
|
def multi_patient_setup(self):
|
|
"""Create multi-patient monitoring setup."""
|
|
patients = {
|
|
"patient_001": MockPatientMonitor("patient_001", "room_101"),
|
|
"patient_002": MockPatientMonitor("patient_002", "room_102"),
|
|
"patient_003": MockPatientMonitor("patient_003", "room_103")
|
|
}
|
|
|
|
notification_system = MockHealthcareNotificationSystem()
|
|
|
|
return patients, notification_system
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_concurrent_patient_monitoring_should_fail_initially(self, multi_patient_setup):
|
|
"""Test concurrent patient monitoring - should fail initially."""
|
|
patients, notification_system = multi_patient_setup
|
|
|
|
# Start monitoring for all patients
|
|
start_results = []
|
|
for patient_id, monitor in patients.items():
|
|
result = await monitor.start_monitoring()
|
|
start_results.append(result)
|
|
|
|
# This will fail initially
|
|
assert all(start_results)
|
|
assert all(monitor.is_monitoring for monitor in patients.values())
|
|
|
|
# Simulate concurrent pose data processing
|
|
pose_data_samples = [
|
|
{
|
|
"persons": [
|
|
{
|
|
"person_id": patient_id,
|
|
"confidence": 0.85,
|
|
"bounding_box": {"x": 200, "y": 150, "width": 80, "height": 180},
|
|
"activity": "standing"
|
|
}
|
|
],
|
|
"zone_summary": {f"room_{101 + i}": 1},
|
|
"timestamp": datetime.utcnow().isoformat()
|
|
}
|
|
for i, patient_id in enumerate(patients.keys())
|
|
]
|
|
|
|
# Process data for all patients concurrently
|
|
tasks = []
|
|
for (patient_id, monitor), pose_data in zip(patients.items(), pose_data_samples):
|
|
task = asyncio.create_task(monitor.process_pose_data(pose_data))
|
|
tasks.append(task)
|
|
|
|
alerts = await asyncio.gather(*tasks)
|
|
|
|
# Check results
|
|
assert len(alerts) == len(patients)
|
|
|
|
# Get statistics for all patients
|
|
all_stats = {}
|
|
for patient_id, monitor in patients.items():
|
|
all_stats[patient_id] = monitor.get_monitoring_stats()
|
|
|
|
assert len(all_stats) == 3
|
|
assert all(stats["is_monitoring"] for stats in all_stats.values())
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_alert_prioritization_should_fail_initially(self, multi_patient_setup):
|
|
"""Test alert prioritization across patients - should fail initially."""
|
|
patients, notification_system = multi_patient_setup
|
|
|
|
# Start monitoring
|
|
for monitor in patients.values():
|
|
await monitor.start_monitoring()
|
|
|
|
# Generate different severity alerts
|
|
alert_scenarios = [
|
|
("patient_001", "fall_detected", AlertSeverity.CRITICAL),
|
|
("patient_002", "prolonged_inactivity", AlertSeverity.MEDIUM),
|
|
("patient_003", "movement_instability", AlertSeverity.HIGH)
|
|
]
|
|
|
|
generated_alerts = []
|
|
|
|
for patient_id, alert_type, expected_severity in alert_scenarios:
|
|
# Create appropriate pose data for each scenario
|
|
if alert_type == "fall_detected":
|
|
pose_data = {
|
|
"persons": [{"person_id": patient_id, "confidence": 0.9, "activity": "fallen"}],
|
|
"zone_summary": {f"room_{patients[patient_id].room_id}": 1}
|
|
}
|
|
else:
|
|
pose_data = {
|
|
"persons": [{"person_id": patient_id, "confidence": 0.7, "activity": "standing"}],
|
|
"zone_summary": {f"room_{patients[patient_id].room_id}": 1}
|
|
}
|
|
|
|
alert = await patients[patient_id].process_pose_data(pose_data)
|
|
if alert:
|
|
generated_alerts.append(alert)
|
|
|
|
# This will fail initially
|
|
# Should have generated alerts
|
|
assert len(generated_alerts) > 0
|
|
|
|
# Send notifications for all alerts
|
|
notification_tasks = [
|
|
notification_system.send_alert_notification(alert)
|
|
for alert in generated_alerts
|
|
]
|
|
|
|
notification_results = await asyncio.gather(*notification_tasks)
|
|
|
|
# Check notification prioritization
|
|
notification_stats = notification_system.get_notification_stats()
|
|
assert notification_stats["total_notifications"] > 0
|
|
|
|
# Critical alerts should use more channels
|
|
critical_notifications = [
|
|
n for n in notification_system.notifications_sent
|
|
if n["severity"] == "critical"
|
|
]
|
|
|
|
if critical_notifications:
|
|
# Critical alerts should be sent to multiple channels
|
|
critical_channels = set(n["channel"] for n in critical_notifications)
|
|
assert len(critical_channels) >= 1
|
|
|
|
|
|
class TestHealthcareSystemIntegration:
|
|
"""Test healthcare system integration scenarios."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_end_to_end_healthcare_workflow_should_fail_initially(self):
|
|
"""Test complete end-to-end healthcare workflow - should fail initially."""
|
|
# Setup complete healthcare monitoring system
|
|
class HealthcareMonitoringSystem:
|
|
def __init__(self):
|
|
self.patient_monitors = {}
|
|
self.notification_system = MockHealthcareNotificationSystem()
|
|
self.alert_history = []
|
|
self.system_status = "operational"
|
|
|
|
async def add_patient(self, patient_id: str, room_id: str) -> bool:
|
|
"""Add patient to monitoring system."""
|
|
if patient_id in self.patient_monitors:
|
|
return False
|
|
|
|
monitor = MockPatientMonitor(patient_id, room_id)
|
|
self.patient_monitors[patient_id] = monitor
|
|
return await monitor.start_monitoring()
|
|
|
|
async def process_pose_update(self, room_id: str, pose_data: Dict[str, Any]) -> List[HealthcareAlert]:
|
|
"""Process pose update for room."""
|
|
alerts = []
|
|
|
|
# Find patients in this room
|
|
room_patients = [
|
|
(patient_id, monitor) for patient_id, monitor in self.patient_monitors.items()
|
|
if monitor.room_id == room_id
|
|
]
|
|
|
|
for patient_id, monitor in room_patients:
|
|
alert = await monitor.process_pose_data(pose_data)
|
|
if alert:
|
|
alerts.append(alert)
|
|
self.alert_history.append(alert)
|
|
|
|
# Send notification
|
|
await self.notification_system.send_alert_notification(alert)
|
|
|
|
return alerts
|
|
|
|
def get_system_status(self) -> Dict[str, Any]:
|
|
"""Get overall system status."""
|
|
return {
|
|
"system_status": self.system_status,
|
|
"total_patients": len(self.patient_monitors),
|
|
"active_monitors": sum(1 for m in self.patient_monitors.values() if m.is_monitoring),
|
|
"total_alerts": len(self.alert_history),
|
|
"notification_stats": self.notification_system.get_notification_stats()
|
|
}
|
|
|
|
healthcare_system = HealthcareMonitoringSystem()
|
|
|
|
# Add patients to system
|
|
patients = [
|
|
("patient_001", "room_101"),
|
|
("patient_002", "room_102"),
|
|
("patient_003", "room_103")
|
|
]
|
|
|
|
for patient_id, room_id in patients:
|
|
result = await healthcare_system.add_patient(patient_id, room_id)
|
|
assert result is True
|
|
|
|
# Simulate pose data updates for different rooms
|
|
pose_updates = [
|
|
("room_101", {
|
|
"persons": [{"person_id": "patient_001", "confidence": 0.9, "activity": "fallen"}],
|
|
"zone_summary": {"room_101": 1}
|
|
}),
|
|
("room_102", {
|
|
"persons": [{"person_id": "patient_002", "confidence": 0.8, "activity": "standing"}],
|
|
"zone_summary": {"room_102": 1}
|
|
}),
|
|
("room_103", {
|
|
"persons": [], # No person detected
|
|
"zone_summary": {"room_103": 0}
|
|
})
|
|
]
|
|
|
|
all_alerts = []
|
|
for room_id, pose_data in pose_updates:
|
|
alerts = await healthcare_system.process_pose_update(room_id, pose_data)
|
|
all_alerts.extend(alerts)
|
|
|
|
# This will fail initially
|
|
# Should have processed all updates
|
|
assert len(pose_updates) == 3
|
|
|
|
# Check system status
|
|
system_status = healthcare_system.get_system_status()
|
|
assert system_status["total_patients"] == 3
|
|
assert system_status["active_monitors"] == 3
|
|
assert system_status["system_status"] == "operational"
|
|
|
|
# Should have generated some alerts
|
|
if all_alerts:
|
|
assert len(all_alerts) > 0
|
|
assert system_status["total_alerts"] > 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_healthcare_system_resilience_should_fail_initially(self):
|
|
"""Test healthcare system resilience - should fail initially."""
|
|
patient_monitor = MockPatientMonitor("patient_001", "room_101")
|
|
notification_system = MockHealthcareNotificationSystem()
|
|
|
|
await patient_monitor.start_monitoring()
|
|
|
|
# Simulate system stress with rapid pose updates
|
|
rapid_updates = 50
|
|
alerts_generated = []
|
|
|
|
for i in range(rapid_updates):
|
|
# Alternate between normal and concerning pose data
|
|
if i % 10 == 0: # Every 10th update is concerning
|
|
pose_data = {
|
|
"persons": [{"person_id": "patient_001", "confidence": 0.9, "activity": "fallen"}],
|
|
"zone_summary": {"room_101": 1}
|
|
}
|
|
else:
|
|
pose_data = {
|
|
"persons": [{"person_id": "patient_001", "confidence": 0.85, "activity": "standing"}],
|
|
"zone_summary": {"room_101": 1}
|
|
}
|
|
|
|
alert = await patient_monitor.process_pose_data(pose_data)
|
|
if alert:
|
|
alerts_generated.append(alert)
|
|
await notification_system.send_alert_notification(alert)
|
|
|
|
# This will fail initially
|
|
# System should handle rapid updates gracefully
|
|
stats = patient_monitor.get_monitoring_stats()
|
|
assert stats["activity_samples"] == rapid_updates
|
|
assert stats["is_monitoring"] is True
|
|
|
|
# Should have generated some alerts but not excessive
|
|
assert len(alerts_generated) <= rapid_updates / 5 # At most 20% alert rate
|
|
|
|
notification_stats = notification_system.get_notification_stats()
|
|
assert notification_stats["total_notifications"] >= len(alerts_generated) |