Strictly eliminate fake and stale vitals and enforce machine-to-MRN isolation

This commit is contained in:
2026-09-17 19:36:21 +05:30
parent 37445d2ae9
commit 2342346d39
8 changed files with 360 additions and 292 deletions
+84 -57
View File
@@ -11,6 +11,7 @@ from fastapi import APIRouter, Depends, HTTPException, WebSocket, WebSocketDisco
from fastapi.responses import HTMLResponse
from sqlalchemy.orm import Session
from sqlalchemy import or_
from datetime import datetime, timezone, timedelta
from database import get_db
from models import Device, Patient, VitalReading, TransmissionLog
@@ -161,74 +162,100 @@ def get_latest_readings(
def list_patients(db: Session = Depends(get_db)):
"""
List all patients with their assigned device and latest vitals readings.
Strictly enforce live data timeouts:
- Continuous parameters (HR, SpO2, RR, Temp): max 25 seconds age.
- Intermittent parameters (NIBP): max 15 minutes age.
- Never backfill from stale historical readings.
"""
patients = db.query(Patient).filter(Patient.patient_id != "UNKNOWN", Patient.mrn != "UNKNOWN").all()
results = []
now = datetime.now(timezone.utc)
live_cutoff = now - timedelta(seconds=25)
nibp_cutoff = now - timedelta(minutes=15)
for p in patients:
# Find latest non-null vitals across recent readings for this patient or device
recent_readings = (
db.query(VitalReading)
.filter(
or_(
VitalReading.patient_id == p.mrn,
VitalReading.patient_id == p.patient_id,
(VitalReading.device_id == p.device_id) if p.device_id else False
)
)
.order_by(VitalReading.timestamp.desc())
.limit(100)
.all()
)
merged_vitals = None
if recent_readings:
merged_vitals = {
"heart_rate": None,
"spo2": None,
"systolic_bp": None,
"diastolic_bp": None,
"map_bp": None,
"respiratory_rate": None,
"temperature": None,
"temperature2": None,
"temperature_diff": None,
"timestamp": recent_readings[0].timestamp.isoformat() if recent_readings[0].timestamp else None,
}
for r in reversed(recent_readings):
if r.heart_rate is not None: merged_vitals["heart_rate"] = r.heart_rate
if r.spo2 is not None: merged_vitals["spo2"] = r.spo2
if r.systolic_bp is not None: merged_vitals["systolic_bp"] = r.systolic_bp
if r.diastolic_bp is not None: merged_vitals["diastolic_bp"] = r.diastolic_bp
if r.map_bp is not None: merged_vitals["map_bp"] = r.map_bp
if r.respiratory_rate is not None: merged_vitals["respiratory_rate"] = r.respiratory_rate
if r.temperature is not None: merged_vitals["temperature"] = r.temperature
if getattr(r, 'temperature2', None) is not None: merged_vitals["temperature2"] = r.temperature2
if getattr(r, 'temperature_diff', None) is not None: merged_vitals["temperature_diff"] = r.temperature_diff
# Backfill any parameters (like SpO2 or NIBP) that were measured earlier
# Primary source: in-memory live cache if running locally with active machine stream
try:
from contec_server import device_cache, device_field_timestamps
if p.device_id and p.device_id in device_cache:
cached = device_cache[p.device_id]
field_ts = device_field_timestamps.get(p.device_id, {})
cached_time = cached.timestamp
if cached_time:
if cached_time.tzinfo is None:
cached_time = cached_time.replace(tzinfo=timezone.utc)
if (now - cached_time).total_seconds() <= 25:
merged_vitals = {
"heart_rate": cached.heart_rate if (field_ts.get("heart_rate") and (now - field_ts["heart_rate"]).total_seconds() <= 25) else None,
"spo2": cached.spo2 if (field_ts.get("spo2") and (now - field_ts["spo2"]).total_seconds() <= 25) else None,
"respiratory_rate": cached.respiratory_rate if (field_ts.get("respiratory_rate") and (now - field_ts["respiratory_rate"]).total_seconds() <= 25) else None,
"temperature": cached.temperature if (field_ts.get("temperature") and (now - field_ts["temperature"]).total_seconds() <= 25) else None,
"temperature2": getattr(cached, 'temperature2', None) if (field_ts.get("temperature2") and (now - field_ts["temperature2"]).total_seconds() <= 25) else None,
"temperature_diff": getattr(cached, 'temperature_diff', None) if (field_ts.get("temperature_diff") and (now - field_ts["temperature_diff"]).total_seconds() <= 25) else None,
"systolic_bp": cached.systolic_bp if (field_ts.get("systolic_bp") and (now - field_ts["systolic_bp"]).total_seconds() <= 900) else None,
"diastolic_bp": cached.diastolic_bp if (field_ts.get("diastolic_bp") and (now - field_ts["diastolic_bp"]).total_seconds() <= 900) else None,
"map_bp": cached.map_bp if (field_ts.get("map_bp") and (now - field_ts["map_bp"]).total_seconds() <= 900) else None,
"timestamp": cached_time.isoformat()
}
except Exception as e:
logger.debug(f"Could not read from device_cache: {e}")
# Fallback or cloud/Render mode: query DB strictly within recent time window
if merged_vitals is None:
patient_filters = or_(
VitalReading.patient_id == p.mrn,
VitalReading.patient_id == p.patient_id,
(VitalReading.device_id == p.device_id) if p.device_id else False
)
if merged_vitals["spo2"] is None:
r_spo2 = db.query(VitalReading.spo2).filter(patient_filters, VitalReading.spo2 != None).order_by(VitalReading.timestamp.desc()).first()
if r_spo2: merged_vitals["spo2"] = r_spo2[0]
if merged_vitals["heart_rate"] is None:
r_hr = db.query(VitalReading.heart_rate).filter(patient_filters, VitalReading.heart_rate != None).order_by(VitalReading.timestamp.desc()).first()
if r_hr: merged_vitals["heart_rate"] = r_hr[0]
if merged_vitals["systolic_bp"] is None:
r_bp = db.query(VitalReading.systolic_bp, VitalReading.diastolic_bp, VitalReading.map_bp).filter(patient_filters, VitalReading.systolic_bp != None).order_by(VitalReading.timestamp.desc()).first()
if r_bp:
merged_vitals["systolic_bp"] = r_bp[0]
merged_vitals["diastolic_bp"] = r_bp[1]
merged_vitals["map_bp"] = r_bp[2]
if merged_vitals["respiratory_rate"] is None:
r_rr = db.query(VitalReading.respiratory_rate).filter(patient_filters, VitalReading.respiratory_rate != None).order_by(VitalReading.timestamp.desc()).first()
if r_rr: merged_vitals["respiratory_rate"] = r_rr[0]
if merged_vitals["temperature"] is None:
r_temp = db.query(VitalReading.temperature).filter(patient_filters, VitalReading.temperature != None).order_by(VitalReading.timestamp.desc()).first()
if r_temp: merged_vitals["temperature"] = r_temp[0]
# Find readings within the live cutoff window (last 25 seconds)
recent_readings = (
db.query(VitalReading)
.filter(patient_filters, VitalReading.timestamp >= live_cutoff)
.order_by(VitalReading.timestamp.desc())
.limit(20)
.all()
)
if recent_readings:
merged_vitals = {
"heart_rate": None,
"spo2": None,
"systolic_bp": None,
"diastolic_bp": None,
"map_bp": None,
"respiratory_rate": None,
"temperature": None,
"temperature2": None,
"temperature_diff": None,
"timestamp": recent_readings[0].timestamp.isoformat() if recent_readings[0].timestamp else None,
}
for r in reversed(recent_readings):
if r.heart_rate is not None: merged_vitals["heart_rate"] = r.heart_rate
if r.spo2 is not None: merged_vitals["spo2"] = r.spo2
if r.respiratory_rate is not None: merged_vitals["respiratory_rate"] = r.respiratory_rate
if r.temperature is not None: merged_vitals["temperature"] = r.temperature
if getattr(r, 'temperature2', None) is not None: merged_vitals["temperature2"] = r.temperature2
if getattr(r, 'temperature_diff', None) is not None: merged_vitals["temperature_diff"] = r.temperature_diff
# Check NIBP separately within 15 minute window
r_bp = (
db.query(VitalReading.systolic_bp, VitalReading.diastolic_bp, VitalReading.map_bp, VitalReading.timestamp)
.filter(patient_filters, VitalReading.systolic_bp != None, VitalReading.timestamp >= nibp_cutoff)
.order_by(VitalReading.timestamp.desc())
.first()
)
if r_bp:
if merged_vitals is None:
merged_vitals = {
"heart_rate": None, "spo2": None, "respiratory_rate": None,
"temperature": None, "temperature2": None, "temperature_diff": None,
"timestamp": r_bp[3].isoformat() if r_bp[3] else None
}
merged_vitals["systolic_bp"] = r_bp[0]
merged_vitals["diastolic_bp"] = r_bp[1]
merged_vitals["map_bp"] = r_bp[2]
device = db.query(Device).filter(Device.device_id == p.device_id).first() if p.device_id else None