updates
This commit is contained in:
736
tests/e2e/test_healthcare_scenario.py
Normal file
736
tests/e2e/test_healthcare_scenario.py
Normal file
@@ -0,0 +1,736 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user