working check in
This commit is contained in:
parent
9a50ce6f2a
commit
b28b0d2fac
Binary file not shown.
@ -1,14 +1,12 @@
|
||||
"""
|
||||
VitalLink Database Layer
|
||||
SQLite persistence for patients, vitals, and audit trail
|
||||
Enables replay, analysis, and incident investigation
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import List, Dict, Optional
|
||||
from contextlib import asynccontextmanager
|
||||
import aiosqlite
|
||||
|
||||
# ============================================================================
|
||||
@ -16,23 +14,21 @@ import aiosqlite
|
||||
# ============================================================================
|
||||
|
||||
SCHEMA_SQL = """
|
||||
-- Patients table
|
||||
CREATE TABLE IF NOT EXISTS patients (
|
||||
patient_id TEXT PRIMARY KEY,
|
||||
band_id TEXT NOT NULL,
|
||||
first_name TEXT NOT NULL,
|
||||
last_name TEXT NOT NULL,
|
||||
dob TEXT NOT NULL,
|
||||
symptoms TEXT, -- JSON array
|
||||
symptoms TEXT,
|
||||
severity TEXT,
|
||||
check_in_time TIMESTAMP NOT NULL,
|
||||
discharge_time TIMESTAMP,
|
||||
check_in_time TEXT NOT NULL,
|
||||
discharge_time TEXT,
|
||||
current_tier TEXT DEFAULT 'NORMAL',
|
||||
is_active BOOLEAN DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
is_active INTEGER DEFAULT 1,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Vitals readings table (time-series data)
|
||||
CREATE TABLE IF NOT EXISTS vitals_readings (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
patient_id TEXT NOT NULL,
|
||||
@ -44,63 +40,33 @@ CREATE TABLE IF NOT EXISTS vitals_readings (
|
||||
temp_c REAL,
|
||||
activity REAL,
|
||||
tier TEXT,
|
||||
flags TEXT, -- JSON array
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
flags TEXT,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (patient_id) REFERENCES patients(patient_id)
|
||||
);
|
||||
|
||||
-- Triage assessments (audit trail)
|
||||
CREATE TABLE IF NOT EXISTS triage_assessments (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
patient_id TEXT NOT NULL,
|
||||
assessment_time TIMESTAMP NOT NULL,
|
||||
triage_level INTEGER,
|
||||
tier_name TEXT,
|
||||
priority_score REAL,
|
||||
reasoning TEXT,
|
||||
abnormalities TEXT, -- JSON array
|
||||
wait_time_minutes INTEGER,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (patient_id) REFERENCES patients(patient_id)
|
||||
);
|
||||
|
||||
-- Tier changes (for incident investigation)
|
||||
CREATE TABLE IF NOT EXISTS tier_changes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
patient_id TEXT NOT NULL,
|
||||
change_time TIMESTAMP NOT NULL,
|
||||
change_time TEXT NOT NULL,
|
||||
old_tier TEXT,
|
||||
new_tier TEXT,
|
||||
trigger_reason TEXT,
|
||||
vitals_snapshot TEXT, -- JSON
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
vitals_snapshot TEXT,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (patient_id) REFERENCES patients(patient_id)
|
||||
);
|
||||
|
||||
-- System events (audit log)
|
||||
CREATE TABLE IF NOT EXISTS system_events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
event_time TIMESTAMP NOT NULL,
|
||||
event_type TEXT NOT NULL, -- 'patient_checkin', 'discharge', 'tier_change', 'alert', etc.
|
||||
event_time TEXT NOT NULL,
|
||||
event_type TEXT NOT NULL,
|
||||
patient_id TEXT,
|
||||
band_id TEXT,
|
||||
details TEXT, -- JSON
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
details TEXT,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Wristband assignments (inventory tracking)
|
||||
CREATE TABLE IF NOT EXISTS wristband_assignments (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
band_id TEXT NOT NULL,
|
||||
patient_id TEXT,
|
||||
assigned_at TIMESTAMP,
|
||||
released_at TIMESTAMP,
|
||||
packet_count INTEGER DEFAULT 0,
|
||||
band_type TEXT, -- 'real' or 'simulated'
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Indexes for performance
|
||||
CREATE INDEX IF NOT EXISTS idx_vitals_patient ON vitals_readings(patient_id, timestamp);
|
||||
CREATE INDEX IF NOT EXISTS idx_vitals_timestamp ON vitals_readings(timestamp);
|
||||
CREATE INDEX IF NOT EXISTS idx_patients_active ON patients(is_active);
|
||||
@ -131,84 +97,69 @@ class VitalLinkDatabase:
|
||||
if self.conn:
|
||||
await self.conn.close()
|
||||
|
||||
# ========================================================================
|
||||
# PATIENT OPERATIONS
|
||||
# ========================================================================
|
||||
async def save_patient(self, patient_data: Dict):
|
||||
"""Save new patient to database"""
|
||||
check_in_time = patient_data["check_in_time"]
|
||||
if isinstance(check_in_time, datetime):
|
||||
check_in_time = check_in_time.isoformat()
|
||||
|
||||
|
||||
async def save_patient(self, patient_data: Dict):
|
||||
"""Save new patient to database"""
|
||||
# Convert datetime to ISO string if needed
|
||||
check_in_time = patient_data["check_in_time"]
|
||||
if isinstance(check_in_time, datetime):
|
||||
check_in_time = check_in_time.isoformat()
|
||||
|
||||
await self.conn.execute(
|
||||
"""
|
||||
INSERT INTO patients (
|
||||
patient_id, band_id, first_name, last_name, dob,
|
||||
symptoms, severity, check_in_time, current_tier
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
patient_data["patient_id"],
|
||||
patient_data["band_id"],
|
||||
patient_data["first_name"],
|
||||
patient_data["last_name"],
|
||||
patient_data["dob"],
|
||||
json.dumps(patient_data["symptoms"]),
|
||||
patient_data["severity"],
|
||||
check_in_time, # Now a string
|
||||
patient_data.get("current_tier", "NORMAL"),
|
||||
),
|
||||
)
|
||||
await self.conn.commit()
|
||||
|
||||
# Log event with serializable data
|
||||
await self.log_event(
|
||||
"patient_checkin",
|
||||
patient_data["patient_id"],
|
||||
patient_data["band_id"],
|
||||
{
|
||||
"first_name": patient_data["first_name"],
|
||||
"last_name": patient_data["last_name"],
|
||||
"symptoms": patient_data["symptoms"],
|
||||
"severity": patient_data["severity"],
|
||||
},
|
||||
)
|
||||
|
||||
async def update_patient_tier(self, patient_id: str, new_tier: str):
|
||||
"""Update patient's current tier"""
|
||||
await self.conn.execute(
|
||||
"""
|
||||
UPDATE patients SET current_tier = ? WHERE patient_id = ?
|
||||
""",
|
||||
(new_tier, patient_id),
|
||||
# Check if patient already exists
|
||||
cursor = await self.conn.execute(
|
||||
"SELECT patient_id FROM patients WHERE patient_id = ?",
|
||||
(patient_data["patient_id"],),
|
||||
)
|
||||
existing = await cursor.fetchone()
|
||||
|
||||
if existing:
|
||||
# Update instead of insert
|
||||
await self.conn.execute(
|
||||
"""
|
||||
UPDATE patients SET
|
||||
band_id = ?, current_tier = ?
|
||||
WHERE patient_id = ?
|
||||
""",
|
||||
(
|
||||
patient_data["band_id"],
|
||||
patient_data.get("current_tier", "NORMAL"),
|
||||
patient_data["patient_id"],
|
||||
),
|
||||
)
|
||||
else:
|
||||
# Insert new patient
|
||||
await self.conn.execute(
|
||||
"""
|
||||
INSERT INTO patients (
|
||||
patient_id, band_id, first_name, last_name, dob,
|
||||
symptoms, severity, check_in_time, current_tier
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
patient_data["patient_id"],
|
||||
patient_data["band_id"],
|
||||
patient_data["first_name"],
|
||||
patient_data["last_name"],
|
||||
patient_data["dob"],
|
||||
json.dumps(patient_data["symptoms"]),
|
||||
patient_data["severity"],
|
||||
check_in_time,
|
||||
patient_data.get("current_tier", "NORMAL"),
|
||||
),
|
||||
)
|
||||
|
||||
await self.conn.commit()
|
||||
|
||||
async def discharge_patient(self, patient_id: str):
|
||||
"""Mark patient as discharged"""
|
||||
await self.conn.execute(
|
||||
"""
|
||||
UPDATE patients
|
||||
SET is_active = 0, discharge_time = ?
|
||||
WHERE patient_id = ?
|
||||
""",
|
||||
(datetime.now(), patient_id),
|
||||
)
|
||||
await self.conn.commit()
|
||||
|
||||
await self.log_event(
|
||||
"discharge",
|
||||
patient_id,
|
||||
None,
|
||||
{"discharge_time": datetime.now().isoformat()},
|
||||
)
|
||||
|
||||
# ========================================================================
|
||||
# VITALS OPERATIONS
|
||||
# ========================================================================
|
||||
if not existing:
|
||||
await self.log_event(
|
||||
"patient_checkin",
|
||||
patient_data["patient_id"],
|
||||
patient_data["band_id"],
|
||||
{
|
||||
"first_name": patient_data["first_name"],
|
||||
"last_name": patient_data["last_name"],
|
||||
"symptoms": patient_data["symptoms"],
|
||||
"severity": patient_data["severity"],
|
||||
},
|
||||
)
|
||||
|
||||
async def save_vitals(self, vitals_data: Dict):
|
||||
"""Save vital signs reading"""
|
||||
@ -264,10 +215,6 @@ async def save_patient(self, patient_data: Dict):
|
||||
for row in rows
|
||||
]
|
||||
|
||||
# ========================================================================
|
||||
# TIER CHANGE TRACKING
|
||||
# ========================================================================
|
||||
|
||||
async def log_tier_change(
|
||||
self, patient_id: str, old_tier: str, new_tier: str, reason: str, vitals: Dict
|
||||
):
|
||||
@ -281,7 +228,7 @@ async def save_patient(self, patient_data: Dict):
|
||||
""",
|
||||
(
|
||||
patient_id,
|
||||
datetime.now(),
|
||||
datetime.now().isoformat(),
|
||||
old_tier,
|
||||
new_tier,
|
||||
reason,
|
||||
@ -298,7 +245,7 @@ async def save_patient(self, patient_data: Dict):
|
||||
)
|
||||
|
||||
async def get_tier_history(self, patient_id: str) -> List[Dict]:
|
||||
"""Get tier change history for incident review"""
|
||||
"""Get tier change history"""
|
||||
cursor = await self.conn.execute(
|
||||
"""
|
||||
SELECT change_time, old_tier, new_tier, trigger_reason, vitals_snapshot
|
||||
@ -322,329 +269,52 @@ async def save_patient(self, patient_data: Dict):
|
||||
for row in rows
|
||||
]
|
||||
|
||||
# ========================================================================
|
||||
# SYSTEM EVENTS (Audit Trail)
|
||||
# ========================================================================
|
||||
|
||||
|
||||
async def log_event(
|
||||
self,
|
||||
event_type: str,
|
||||
patient_id: Optional[str],
|
||||
band_id: Optional[str],
|
||||
details: Dict,
|
||||
):
|
||||
"""Log system event for audit trail"""
|
||||
|
||||
# Ensure details is JSON serializable
|
||||
serializable_details = {}
|
||||
for key, value in details.items():
|
||||
if isinstance(value, datetime):
|
||||
serializable_details[key] = value.isoformat()
|
||||
else:
|
||||
serializable_details[key] = value
|
||||
|
||||
await self.conn.execute(
|
||||
"""
|
||||
INSERT INTO system_events (
|
||||
event_time, event_type, patient_id, band_id, details
|
||||
) VALUES (?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
datetime.now().isoformat(), # Convert to string
|
||||
event_type,
|
||||
patient_id,
|
||||
band_id,
|
||||
json.dumps(serializable_details),
|
||||
),
|
||||
)
|
||||
await self.conn.commit()
|
||||
|
||||
async def get_events(
|
||||
async def log_event(
|
||||
self,
|
||||
event_type: Optional[str] = None,
|
||||
patient_id: Optional[str] = None,
|
||||
hours: int = 24,
|
||||
) -> List[Dict]:
|
||||
"""Get system events for analysis"""
|
||||
event_type: str,
|
||||
patient_id: Optional[str],
|
||||
band_id: Optional[str],
|
||||
details: Dict,
|
||||
):
|
||||
"""Log system event for audit trail"""
|
||||
serializable_details = {}
|
||||
for key, value in details.items():
|
||||
if isinstance(value, datetime):
|
||||
serializable_details[key] = value.isoformat()
|
||||
else:
|
||||
serializable_details[key] = value
|
||||
|
||||
query = "SELECT event_time, event_type, patient_id, band_id, details FROM system_events WHERE 1=1"
|
||||
params = []
|
||||
|
||||
if event_type:
|
||||
query += " AND event_type = ?"
|
||||
params.append(event_type)
|
||||
|
||||
if patient_id:
|
||||
query += " AND patient_id = ?"
|
||||
params.append(patient_id)
|
||||
|
||||
query += " AND event_time > datetime('now', '-' || ? || ' hours')"
|
||||
params.append(hours)
|
||||
|
||||
query += " ORDER BY event_time DESC LIMIT 1000"
|
||||
|
||||
cursor = await self.conn.execute(query, params)
|
||||
rows = await cursor.fetchall()
|
||||
|
||||
return [
|
||||
{
|
||||
"event_time": row[0],
|
||||
"event_type": row[1],
|
||||
"patient_id": row[2],
|
||||
"band_id": row[3],
|
||||
"details": json.loads(row[4]) if row[4] else {},
|
||||
}
|
||||
for row in rows
|
||||
]
|
||||
|
||||
# ========================================================================
|
||||
# ANALYTICS & REPLAY
|
||||
# ========================================================================
|
||||
|
||||
async def get_session_summary(
|
||||
self, start_time: datetime, end_time: datetime
|
||||
) -> Dict:
|
||||
"""Get summary statistics for a session (for incident review)"""
|
||||
|
||||
cursor = await self.conn.execute(
|
||||
await self.conn.execute(
|
||||
"""
|
||||
SELECT
|
||||
COUNT(DISTINCT patient_id) as total_patients,
|
||||
COUNT(*) as total_vitals,
|
||||
AVG(hr_bpm) as avg_hr,
|
||||
AVG(spo2) as avg_spo2,
|
||||
AVG(temp_c) as avg_temp
|
||||
FROM vitals_readings
|
||||
WHERE timestamp BETWEEN ? AND ?
|
||||
INSERT INTO system_events (
|
||||
event_time, event_type, patient_id, band_id, details
|
||||
) VALUES (?, ?, ?, ?, ?)
|
||||
""",
|
||||
(start_time.timestamp(), end_time.timestamp()),
|
||||
(
|
||||
datetime.now().isoformat(),
|
||||
event_type,
|
||||
patient_id,
|
||||
band_id,
|
||||
json.dumps(serializable_details),
|
||||
),
|
||||
)
|
||||
await self.conn.commit()
|
||||
|
||||
row = await cursor.fetchone()
|
||||
|
||||
return {
|
||||
"total_patients": row[0],
|
||||
"total_vitals_recorded": row[1],
|
||||
"average_hr": round(row[2], 1) if row[2] else 0,
|
||||
"average_spo2": round(row[3], 1) if row[3] else 0,
|
||||
"average_temp": round(row[4], 2) if row[4] else 0,
|
||||
}
|
||||
|
||||
async def export_patient_data(self, patient_id: str) -> Dict:
|
||||
"""Export complete patient data for incident investigation"""
|
||||
|
||||
# Get patient info
|
||||
cursor = await self.conn.execute(
|
||||
async def discharge_patient(self, patient_id: str):
|
||||
"""Mark patient as discharged"""
|
||||
await self.conn.execute(
|
||||
"""
|
||||
SELECT * FROM patients WHERE patient_id = ?
|
||||
UPDATE patients
|
||||
SET is_active = 0, discharge_time = ?
|
||||
WHERE patient_id = ?
|
||||
""",
|
||||
(patient_id,),
|
||||
(datetime.now().isoformat(), patient_id),
|
||||
)
|
||||
patient_row = await cursor.fetchone()
|
||||
await self.conn.commit()
|
||||
|
||||
if not patient_row:
|
||||
return None
|
||||
|
||||
# Get all vitals
|
||||
vitals = await self.get_patient_vitals_history(patient_id, limit=10000)
|
||||
|
||||
# Get tier changes
|
||||
tier_changes = await self.get_tier_history(patient_id)
|
||||
|
||||
# Get related events
|
||||
events = await self.get_events(patient_id=patient_id, hours=24)
|
||||
|
||||
return {
|
||||
"patient_id": patient_id,
|
||||
"name": f"{patient_row[2]} {patient_row[3]}",
|
||||
"dob": patient_row[4],
|
||||
"symptoms": json.loads(patient_row[5]) if patient_row[5] else [],
|
||||
"severity": patient_row[6],
|
||||
"check_in_time": patient_row[7],
|
||||
"discharge_time": patient_row[8],
|
||||
"total_vitals": len(vitals),
|
||||
"vitals_timeline": vitals,
|
||||
"tier_changes": tier_changes,
|
||||
"events": events,
|
||||
"export_time": datetime.now().isoformat(),
|
||||
}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# REPLAY SYSTEM
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class VitalsReplaySystem:
|
||||
"""Replay historical vitals data for analysis"""
|
||||
|
||||
def __init__(self, db: VitalLinkDatabase):
|
||||
self.db = db
|
||||
|
||||
async def replay_patient_session(self, patient_id: str, speed: float = 1.0):
|
||||
"""
|
||||
Replay a patient's entire session
|
||||
speed: 1.0 = real-time, 10.0 = 10x faster, 0.1 = slow motion
|
||||
"""
|
||||
|
||||
vitals = await self.db.get_patient_vitals_history(patient_id, limit=10000)
|
||||
vitals.reverse() # Chronological order
|
||||
|
||||
if not vitals:
|
||||
print(f"No data found for patient {patient_id}")
|
||||
return
|
||||
|
||||
print(f"\n{'=' * 80}")
|
||||
print(f"REPLAYING SESSION: {patient_id} ({len(vitals)} readings)")
|
||||
print(f"Speed: {speed}x | Press Ctrl+C to stop")
|
||||
print(f"{'=' * 80}\n")
|
||||
|
||||
start_time = vitals[0]["timestamp"]
|
||||
|
||||
for i, reading in enumerate(vitals):
|
||||
# Calculate delay
|
||||
if i > 0:
|
||||
time_diff = reading["timestamp"] - vitals[i - 1]["timestamp"]
|
||||
await asyncio.sleep(time_diff / speed)
|
||||
|
||||
# Display reading
|
||||
elapsed = reading["timestamp"] - start_time
|
||||
tier_symbol = (
|
||||
"🔴"
|
||||
if reading["tier"] == "EMERGENCY"
|
||||
else "🟡"
|
||||
if reading["tier"] == "ALERT"
|
||||
else "🟢"
|
||||
)
|
||||
|
||||
print(
|
||||
f"[{elapsed:7.1f}s] {tier_symbol} Seq={reading['seq']:3d} | "
|
||||
f"HR={reading['hr_bpm']:3d} SpO2={reading['spo2']:2d}% "
|
||||
f"Temp={reading['temp_c']:.1f}°C | {reading['tier']}"
|
||||
)
|
||||
|
||||
print(f"\n{'=' * 80}")
|
||||
print(f"Replay complete: {len(vitals)} readings")
|
||||
print(f"{'=' * 80}\n")
|
||||
|
||||
async def analyze_critical_events(self, patient_id: str):
|
||||
"""Analyze critical tier changes and deterioration events"""
|
||||
|
||||
tier_changes = await self.db.get_tier_history(patient_id)
|
||||
|
||||
print(f"\n{'=' * 80}")
|
||||
print(f"CRITICAL EVENT ANALYSIS: {patient_id}")
|
||||
print(f"{'=' * 80}\n")
|
||||
|
||||
for change in tier_changes:
|
||||
print(f"[{change['change_time']}]")
|
||||
print(f" {change['old_tier']} → {change['new_tier']}")
|
||||
print(f" Reason: {change['reason']}")
|
||||
print(f" Vitals: {change['vitals']}")
|
||||
print()
|
||||
|
||||
print(f"{'=' * 80}\n")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# INTEGRATION HELPERS
|
||||
# ============================================================================
|
||||
|
||||
|
||||
async def init_database(db_path: str = "vitallink.db") -> VitalLinkDatabase:
|
||||
"""Initialize database for use in FastAPI"""
|
||||
db = VitalLinkDatabase(db_path)
|
||||
await db.initialize()
|
||||
return db
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# CLI TOOLS
|
||||
# ============================================================================
|
||||
|
||||
|
||||
async def cli_export_patient(patient_id: str, output_file: str = None):
|
||||
"""Export patient data to JSON file"""
|
||||
db = VitalLinkDatabase()
|
||||
await db.initialize()
|
||||
|
||||
data = await db.export_patient_data(patient_id)
|
||||
|
||||
if not data:
|
||||
print(f"Patient {patient_id} not found")
|
||||
return
|
||||
|
||||
if output_file:
|
||||
with open(output_file, "w") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
print(f"✓ Exported to {output_file}")
|
||||
else:
|
||||
print(json.dumps(data, indent=2))
|
||||
|
||||
await db.close()
|
||||
|
||||
|
||||
async def cli_replay_session(patient_id: str, speed: float = 1.0):
|
||||
"""Replay a patient session"""
|
||||
db = VitalLinkDatabase()
|
||||
await db.initialize()
|
||||
|
||||
replay = VitalsReplaySystem(db)
|
||||
await replay.replay_patient_session(patient_id, speed)
|
||||
|
||||
await db.close()
|
||||
|
||||
|
||||
async def cli_analyze_incident(patient_id: str):
|
||||
"""Analyze critical events for a patient"""
|
||||
db = VitalLinkDatabase()
|
||||
await db.initialize()
|
||||
|
||||
replay = VitalsReplaySystem(db)
|
||||
await replay.analyze_critical_events(patient_id)
|
||||
|
||||
# Also show vital trends
|
||||
vitals = await db.get_patient_vitals_history(patient_id, limit=1000)
|
||||
|
||||
if vitals:
|
||||
print("VITAL SIGN TRENDS:")
|
||||
print(
|
||||
f" HR range: {min(v['hr_bpm'] for v in vitals)} - {max(v['hr_bpm'] for v in vitals)} bpm"
|
||||
await self.log_event(
|
||||
"discharge",
|
||||
patient_id,
|
||||
None,
|
||||
{"discharge_time": datetime.now().isoformat()},
|
||||
)
|
||||
print(
|
||||
f" SpO2 range: {min(v['spo2'] for v in vitals)} - {max(v['spo2'] for v in vitals)}%"
|
||||
)
|
||||
print(
|
||||
f" Temp range: {min(v['temp_c'] for v in vitals):.1f} - {max(v['temp_c'] for v in vitals):.1f}°C"
|
||||
)
|
||||
print()
|
||||
|
||||
await db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
import asyncio
|
||||
|
||||
parser = argparse.ArgumentParser(description="VitalLink Database Tools")
|
||||
parser.add_argument("--export", metavar="PATIENT_ID", help="Export patient data")
|
||||
parser.add_argument("--replay", metavar="PATIENT_ID", help="Replay patient session")
|
||||
parser.add_argument(
|
||||
"--analyze", metavar="PATIENT_ID", help="Analyze critical events"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--speed", type=float, default=1.0, help="Replay speed multiplier"
|
||||
)
|
||||
parser.add_argument("--output", "-o", help="Output file for export")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.export:
|
||||
asyncio.run(cli_export_patient(args.export, args.output))
|
||||
elif args.replay:
|
||||
asyncio.run(cli_replay_session(args.replay, args.speed))
|
||||
elif args.analyze:
|
||||
asyncio.run(cli_analyze_incident(args.analyze))
|
||||
else:
|
||||
parser.print_help()
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
68221
|
||||
68237
|
||||
68245
|
||||
68281
|
||||
70573
|
||||
70588
|
||||
70601
|
||||
70667
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1 +1 @@
|
||||
68221
|
||||
70573
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
> vite
|
||||
|
||||
|
||||
VITE v7.1.10 ready in 219 ms
|
||||
VITE v7.1.10 ready in 246 ms
|
||||
|
||||
➜ Local: http://localhost:5173/
|
||||
➜ Network: use --host to expose
|
||||
|
||||
@ -1 +1 @@
|
||||
68245
|
||||
70601
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
Port 5173 is in use, trying another one...
|
||||
|
||||
VITE v7.1.10 ready in 250 ms
|
||||
VITE v7.1.10 ready in 228 ms
|
||||
|
||||
➜ Local: http://localhost:5174/
|
||||
➜ Network: use --host to expose
|
||||
|
||||
@ -1 +1 @@
|
||||
68281
|
||||
70667
|
||||
|
||||
@ -1 +1 @@
|
||||
68237
|
||||
70588
|
||||
|
||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user