fix: synchronize all latest dashboard, Option B, and API updates to fiveparaminte-main for Render deployment
This commit is contained in:
@@ -287,6 +287,7 @@ def _parse_45_byte_packet(data: bytes, vitals_dict: dict) -> bool:
|
|||||||
if "present_fields" not in vitals_dict:
|
if "present_fields" not in vitals_dict:
|
||||||
vitals_dict["present_fields"] = []
|
vitals_dict["present_fields"] = []
|
||||||
|
|
||||||
|
logger.debug("DEBUG_NIBP_PKT len=%d nibp_hex=%s", len(data), data[8:24].hex())
|
||||||
nibp_sys = read_u16_le(data, 15)
|
nibp_sys = read_u16_le(data, 15)
|
||||||
nibp_dia = read_u16_le(data, 17)
|
nibp_dia = read_u16_le(data, 17)
|
||||||
nibp_map = read_u16_le(data, 19)
|
nibp_map = read_u16_le(data, 19)
|
||||||
@@ -371,42 +372,26 @@ def _parse_spo2_waveform_packet(data: bytes, vitals_dict: dict) -> bool:
|
|||||||
if "pleth_wave" not in vitals_dict["present_fields"]:
|
if "pleth_wave" not in vitals_dict["present_fields"]:
|
||||||
vitals_dict["present_fields"].append("pleth_wave")
|
vitals_dict["present_fields"].append("pleth_wave")
|
||||||
|
|
||||||
found = False
|
|
||||||
|
|
||||||
# Check SpO2 at standard offset 264
|
# Check SpO2 at standard offset 264
|
||||||
spo2_val = read_u16_le(data, 264)
|
spo2_val = read_u16_le(data, 264)
|
||||||
|
|
||||||
# If 264 is disconnected sentinel, probe is NOT connected / off-finger
|
if spo2_val is not None and 50 <= spo2_val <= 100:
|
||||||
if spo2_val in (None, 65535, 32767, 255, 9999, 0):
|
|
||||||
vitals_dict["spo2"] = None
|
|
||||||
vitals_dict["heart_rate"] = None
|
|
||||||
if "spo2" not in vitals_dict["present_fields"]:
|
|
||||||
vitals_dict["present_fields"].append("spo2")
|
|
||||||
if "heart_rate" not in vitals_dict["present_fields"]:
|
|
||||||
vitals_dict["present_fields"].append("heart_rate")
|
|
||||||
return True
|
|
||||||
|
|
||||||
if 50 <= spo2_val <= 100:
|
|
||||||
vitals_dict["spo2"] = float(spo2_val)
|
vitals_dict["spo2"] = float(spo2_val)
|
||||||
if "spo2" not in vitals_dict["present_fields"]:
|
if "spo2" not in vitals_dict["present_fields"]:
|
||||||
vitals_dict["present_fields"].append("spo2")
|
vitals_dict["present_fields"].append("spo2")
|
||||||
found = True
|
|
||||||
|
|
||||||
# ECG HR is at offset 266 in the 286-byte SpO2 waveform packet footer.
|
# Pulse Rate / ECG HR is at offset 266 in the footer
|
||||||
# Offset 268 is the SpO2 HIGH ALARM LIMIT (always 100) — NOT pulse rate!
|
|
||||||
# So we read ECG HR at 266 only.
|
|
||||||
ecg_hr = read_u16_le(data, 266)
|
ecg_hr = read_u16_le(data, 266)
|
||||||
|
|
||||||
logger.info(
|
|
||||||
f"SpO2 pkt: SpO2[264]={spo2_val} ECG_HR[266]={ecg_hr} alarm_hi[268]={read_u16_le(data,268)} alarm_lo[270]={read_u16_le(data,270) if len(data)>=272 else 'N/A'}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if ecg_hr is not None and ecg_hr not in (9999, 65535, 32767, 0) and 20 <= ecg_hr <= 300:
|
if ecg_hr is not None and ecg_hr not in (9999, 65535, 32767, 0) and 20 <= ecg_hr <= 300:
|
||||||
vitals_dict["heart_rate"] = float(ecg_hr)
|
vitals_dict["heart_rate"] = float(ecg_hr)
|
||||||
if "heart_rate" not in vitals_dict["present_fields"]:
|
if "heart_rate" not in vitals_dict["present_fields"]:
|
||||||
vitals_dict["present_fields"].append("heart_rate")
|
vitals_dict["present_fields"].append("heart_rate")
|
||||||
|
|
||||||
return found
|
logger.info(
|
||||||
|
f"SpO2 pkt: SpO2[264]={spo2_val} ECG_HR[266]={ecg_hr} alarm_hi[268]={read_u16_le(data,268)} alarm_lo[270]={read_u16_le(data,270) if len(data)>=272 else 'N/A'}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _parse_286_byte_packet(data: bytes, vitals_dict: dict) -> bool:
|
def _parse_286_byte_packet(data: bytes, vitals_dict: dict) -> bool:
|
||||||
@@ -442,13 +427,13 @@ def _parse_989_byte_packet(data: bytes, vitals_dict: dict) -> bool:
|
|||||||
if "ecg_wave" not in vitals_dict["present_fields"]:
|
if "ecg_wave" not in vitals_dict["present_fields"]:
|
||||||
vitals_dict["present_fields"].append("ecg_wave")
|
vitals_dict["present_fields"].append("ecg_wave")
|
||||||
|
|
||||||
|
logger.debug("DEBUG_ECG_PKT len=%d tail_hex=%s", len(data), data[890:989].hex())
|
||||||
hr_val = read_u16_le(data, 904)
|
hr_val = read_u16_le(data, 904)
|
||||||
if hr_val is not None and hr_val not in (65535, 9999, 0) and 20 <= hr_val <= 300:
|
if hr_val is not None and hr_val not in (65535, 32767, 9999, 0) and 20 <= hr_val <= 300:
|
||||||
# Only use ECG HR if SpO2-probe PR has NOT already set heart_rate.
|
if vitals_dict.get("heart_rate") is None:
|
||||||
# SpO2 PR (from the pleth packet) matches the monitor display and wins.
|
|
||||||
if "heart_rate" not in vitals_dict.get("present_fields", []):
|
|
||||||
vitals_dict["heart_rate"] = float(hr_val)
|
vitals_dict["heart_rate"] = float(hr_val)
|
||||||
vitals_dict.setdefault("present_fields", []).append("heart_rate")
|
if "heart_rate" not in vitals_dict.setdefault("present_fields", []):
|
||||||
|
vitals_dict["present_fields"].append("heart_rate")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -486,9 +471,7 @@ def _find_vitals_block_in_stream(data: bytes, client_ip: str) -> Optional[Normal
|
|||||||
elif pkt_len == 989:
|
elif pkt_len == 989:
|
||||||
if _parse_989_byte_packet(pkt_data, vitals_dict):
|
if _parse_989_byte_packet(pkt_data, vitals_dict):
|
||||||
found_any = True
|
found_any = True
|
||||||
elif 100 < pkt_len < 600:
|
# Handled specifically by known packet sizes above
|
||||||
if _parse_spo2_waveform_packet(pkt_data, vitals_dict):
|
|
||||||
found_any = True
|
|
||||||
|
|
||||||
i += max(pkt_len, 4)
|
i += max(pkt_len, 4)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ from sqlalchemy.pool import NullPool
|
|||||||
from sqlalchemy.orm import sessionmaker, declarative_base
|
from sqlalchemy.orm import sessionmaker, declarative_base
|
||||||
from config import settings
|
from config import settings
|
||||||
|
|
||||||
|
connect_args = {"check_same_thread": False} if settings.database_url.startswith("sqlite") else {}
|
||||||
|
|
||||||
engine = create_engine(
|
engine = create_engine(
|
||||||
settings.database_url,
|
settings.database_url,
|
||||||
connect_args={"check_same_thread": False},
|
connect_args=connect_args,
|
||||||
poolclass=NullPool
|
poolclass=NullPool
|
||||||
)
|
)
|
||||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
|||||||
@@ -210,6 +210,13 @@ def serve_dashboard_root():
|
|||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||||
|
|
||||||
|
from fastapi.responses import Response
|
||||||
|
@app.get("/favicon.ico", include_in_schema=False)
|
||||||
|
async def favicon():
|
||||||
|
return Response(status_code=204, media_type="image/x-icon")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# === WebSocket endpoint for live vitals ===
|
# === WebSocket endpoint for live vitals ===
|
||||||
@app.websocket("/ws/vitals")
|
@app.websocket("/ws/vitals")
|
||||||
|
|||||||
@@ -202,14 +202,14 @@ def list_patients(db: Session = Depends(get_db)):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"Could not read from device_cache: {e}")
|
logger.debug(f"Could not read from device_cache: {e}")
|
||||||
|
|
||||||
# Fallback or cloud/Render mode: query DB strictly within recent time window
|
# Fallback or cloud/Render mode: query DB
|
||||||
if merged_vitals is None:
|
if merged_vitals is None:
|
||||||
patient_filters = or_(
|
patient_filters = or_(
|
||||||
VitalReading.patient_id == p.mrn,
|
VitalReading.patient_id == p.mrn,
|
||||||
VitalReading.patient_id == p.patient_id,
|
VitalReading.patient_id == p.patient_id,
|
||||||
(VitalReading.device_id == p.device_id) if p.device_id else False
|
(VitalReading.device_id == p.device_id) if p.device_id else False
|
||||||
)
|
)
|
||||||
# Find readings within the live cutoff window (last 25 seconds)
|
# 1. Try to find readings within the live cutoff window (last 25 seconds)
|
||||||
recent_readings = (
|
recent_readings = (
|
||||||
db.query(VitalReading)
|
db.query(VitalReading)
|
||||||
.filter(patient_filters, VitalReading.timestamp >= live_cutoff)
|
.filter(patient_filters, VitalReading.timestamp >= live_cutoff)
|
||||||
@@ -217,8 +217,20 @@ def list_patients(db: Session = Depends(get_db)):
|
|||||||
.limit(20)
|
.limit(20)
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
|
# 2. Option B: If no live reading in last 25s, retain latest recorded readings from DB
|
||||||
|
if not recent_readings:
|
||||||
|
recent_readings = (
|
||||||
|
db.query(VitalReading)
|
||||||
|
.filter(patient_filters)
|
||||||
|
.order_by(VitalReading.timestamp.desc())
|
||||||
|
.limit(20)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
if recent_readings:
|
if recent_readings:
|
||||||
|
ts_iso = recent_readings[0].timestamp.isoformat() if recent_readings[0].timestamp else None
|
||||||
|
if ts_iso and not ts_iso.endswith('Z') and '+' not in ts_iso:
|
||||||
|
ts_iso += 'Z'
|
||||||
merged_vitals = {
|
merged_vitals = {
|
||||||
"heart_rate": None,
|
"heart_rate": None,
|
||||||
"spo2": None,
|
"spo2": None,
|
||||||
@@ -229,7 +241,7 @@ def list_patients(db: Session = Depends(get_db)):
|
|||||||
"temperature": None,
|
"temperature": None,
|
||||||
"temperature2": None,
|
"temperature2": None,
|
||||||
"temperature_diff": None,
|
"temperature_diff": None,
|
||||||
"timestamp": recent_readings[0].timestamp.isoformat() if recent_readings[0].timestamp else None,
|
"timestamp": ts_iso,
|
||||||
}
|
}
|
||||||
for r in reversed(recent_readings):
|
for r in reversed(recent_readings):
|
||||||
if r.heart_rate is not None: merged_vitals["heart_rate"] = r.heart_rate
|
if r.heart_rate is not None: merged_vitals["heart_rate"] = r.heart_rate
|
||||||
@@ -238,24 +250,32 @@ def list_patients(db: Session = Depends(get_db)):
|
|||||||
if r.temperature is not None: merged_vitals["temperature"] = r.temperature
|
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, '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
|
if getattr(r, 'temperature_diff', None) is not None: merged_vitals["temperature_diff"] = r.temperature_diff
|
||||||
|
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
|
||||||
|
|
||||||
|
# Check NIBP separately if not already populated
|
||||||
|
if not merged_vitals or merged_vitals.get("systolic_bp") is None:
|
||||||
|
r_bp = (
|
||||||
|
db.query(VitalReading.systolic_bp, VitalReading.diastolic_bp, VitalReading.map_bp, VitalReading.timestamp)
|
||||||
|
.filter(patient_filters, VitalReading.systolic_bp != None)
|
||||||
|
.order_by(VitalReading.timestamp.desc())
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
if r_bp:
|
||||||
|
if merged_vitals is None:
|
||||||
|
ts_bp = r_bp[3].isoformat() if r_bp[3] else None
|
||||||
|
if ts_bp and not ts_bp.endswith('Z') and '+' not in ts_bp:
|
||||||
|
ts_bp += 'Z'
|
||||||
|
merged_vitals = {
|
||||||
|
"heart_rate": None, "spo2": None, "respiratory_rate": None,
|
||||||
|
"temperature": None, "temperature2": None, "temperature_diff": None,
|
||||||
|
"timestamp": ts_bp
|
||||||
|
}
|
||||||
|
merged_vitals["systolic_bp"] = r_bp[0]
|
||||||
|
merged_vitals["diastolic_bp"] = r_bp[1]
|
||||||
|
merged_vitals["map_bp"] = r_bp[2]
|
||||||
|
|
||||||
# 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
|
device = db.query(Device).filter(Device.device_id == p.device_id).first() if p.device_id else None
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,17 @@
|
|||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;600;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🩺</text></svg>">
|
||||||
|
<script>
|
||||||
|
// Suppress browser extension message channel port errors inside telemetry embed
|
||||||
|
window.addEventListener('unhandledrejection', function(event) {
|
||||||
|
var msg = (event && event.reason && (event.reason.message || event.reason)) || '';
|
||||||
|
if (typeof msg === 'string' && msg.indexOf('A listener indicated an asynchronous response') !== -1) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* === CSS Reset & Base === */
|
/* === CSS Reset & Base === */
|
||||||
*, *::before, *::after {
|
*, *::before, *::after {
|
||||||
@@ -2548,6 +2559,23 @@
|
|||||||
document.body.classList.add('minimal-mode');
|
document.body.classList.add('minimal-mode');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper: Calculate age in milliseconds robustly against UTC and local timezones
|
||||||
|
function getTimestampAgeMs(ts) {
|
||||||
|
if (!ts) return 999999999;
|
||||||
|
try {
|
||||||
|
let str = String(ts).trim();
|
||||||
|
if (!str.endsWith('Z') && !str.includes('+') && !/[-+]\d{2}:\d{2}$/.test(str)) {
|
||||||
|
str = str + 'Z';
|
||||||
|
}
|
||||||
|
const time = new Date(str).getTime();
|
||||||
|
if (isNaN(time)) return 999999999;
|
||||||
|
return Math.abs(Date.now() - time);
|
||||||
|
} catch (e) {
|
||||||
|
return 999999999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function getTargetPatientBadge() {
|
function getTargetPatientBadge() {
|
||||||
const items = [];
|
const items = [];
|
||||||
if (targetMrn) items.push(`<span class="patient-mrn-badge" style="padding: 4px 10px; font-size: 12px;">📋 MRN: <strong>${targetMrn}</strong></span>`);
|
if (targetMrn) items.push(`<span class="patient-mrn-badge" style="padding: 4px 10px; font-size: 12px;">📋 MRN: <strong>${targetMrn}</strong></span>`);
|
||||||
@@ -2801,6 +2829,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function stepContecMachineSweep(timestamp) {
|
function stepContecMachineSweep(timestamp) {
|
||||||
|
if (document.hidden) {
|
||||||
|
machLastFrameTime = timestamp;
|
||||||
|
requestAnimationFrame(stepContecMachineSweep);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!ctxMachEcg || !canvasMachEcg) {
|
if (!ctxMachEcg || !canvasMachEcg) {
|
||||||
requestAnimationFrame(stepContecMachineSweep);
|
requestAnimationFrame(stepContecMachineSweep);
|
||||||
return;
|
return;
|
||||||
@@ -3110,20 +3144,15 @@
|
|||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
for (let i = readings.length - 1; i >= 0; i--) {
|
for (let i = readings.length - 1; i >= 0; i--) {
|
||||||
const r = readings[i];
|
const r = readings[i];
|
||||||
const rAge = r.timestamp ? (now - new Date(r.timestamp).getTime()) : 9999999;
|
if (r.heart_rate != null) merged.heart_rate = r.heart_rate;
|
||||||
if (rAge <= 25000) {
|
if (r.spo2 != null) merged.spo2 = r.spo2;
|
||||||
if (r.heart_rate != null) merged.heart_rate = r.heart_rate;
|
if (r.respiratory_rate != null) merged.respiratory_rate = r.respiratory_rate;
|
||||||
if (r.spo2 != null) merged.spo2 = r.spo2;
|
if (r.temperature != null) merged.temperature = r.temperature;
|
||||||
if (r.respiratory_rate != null) merged.respiratory_rate = r.respiratory_rate;
|
if (r.temperature2 != null) merged.temperature2 = r.temperature2;
|
||||||
if (r.temperature != null) merged.temperature = r.temperature;
|
if (r.temperature_diff != null) merged.temperature_diff = r.temperature_diff;
|
||||||
if (r.temperature2 != null) merged.temperature2 = r.temperature2;
|
if (r.systolic_bp != null) merged.systolic_bp = r.systolic_bp;
|
||||||
if (r.temperature_diff != null) merged.temperature_diff = r.temperature_diff;
|
if (r.diastolic_bp != null) merged.diastolic_bp = r.diastolic_bp;
|
||||||
}
|
if (r.map_bp != null) merged.map_bp = r.map_bp;
|
||||||
if (rAge <= 900000) {
|
|
||||||
if (r.systolic_bp != null) merged.systolic_bp = r.systolic_bp;
|
|
||||||
if (r.diastolic_bp != null) merged.diastolic_bp = r.diastolic_bp;
|
|
||||||
if (r.map_bp != null) merged.map_bp = r.map_bp;
|
|
||||||
}
|
|
||||||
if (r.patient_name) merged.patient_name = r.patient_name;
|
if (r.patient_name) merged.patient_name = r.patient_name;
|
||||||
if (r.mrn || r.patient_id) merged.mrn = r.mrn || r.patient_id;
|
if (r.mrn || r.patient_id) merged.mrn = r.mrn || r.patient_id;
|
||||||
if (r.bed_id) merged.bed_id = r.bed_id;
|
if (r.bed_id) merged.bed_id = r.bed_id;
|
||||||
@@ -3203,20 +3232,15 @@
|
|||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
for (let i = readings.length - 1; i >= 0; i--) {
|
for (let i = readings.length - 1; i >= 0; i--) {
|
||||||
const r = readings[i];
|
const r = readings[i];
|
||||||
const rAge = r.timestamp ? (now - new Date(r.timestamp).getTime()) : 9999999;
|
if (r.heart_rate != null) merged.heart_rate = r.heart_rate;
|
||||||
if (rAge <= 25000) {
|
if (r.spo2 != null) merged.spo2 = r.spo2;
|
||||||
if (r.heart_rate != null) merged.heart_rate = r.heart_rate;
|
if (r.respiratory_rate != null) merged.respiratory_rate = r.respiratory_rate;
|
||||||
if (r.spo2 != null) merged.spo2 = r.spo2;
|
if (r.temperature != null) merged.temperature = r.temperature;
|
||||||
if (r.respiratory_rate != null) merged.respiratory_rate = r.respiratory_rate;
|
if (r.temperature2 != null) merged.temperature2 = r.temperature2;
|
||||||
if (r.temperature != null) merged.temperature = r.temperature;
|
if (r.temperature_diff != null) merged.temperature_diff = r.temperature_diff;
|
||||||
if (r.temperature2 != null) merged.temperature2 = r.temperature2;
|
if (r.systolic_bp != null) merged.systolic_bp = r.systolic_bp;
|
||||||
if (r.temperature_diff != null) merged.temperature_diff = r.temperature_diff;
|
if (r.diastolic_bp != null) merged.diastolic_bp = r.diastolic_bp;
|
||||||
}
|
if (r.map_bp != null) merged.map_bp = r.map_bp;
|
||||||
if (rAge <= 900000) {
|
|
||||||
if (r.systolic_bp != null) merged.systolic_bp = r.systolic_bp;
|
|
||||||
if (r.diastolic_bp != null) merged.diastolic_bp = r.diastolic_bp;
|
|
||||||
if (r.map_bp != null) merged.map_bp = r.map_bp;
|
|
||||||
}
|
|
||||||
if (r.patient_name) merged.patient_name = r.patient_name;
|
if (r.patient_name) merged.patient_name = r.patient_name;
|
||||||
if (r.mrn || r.patient_id) merged.mrn = r.mrn || r.patient_id;
|
if (r.mrn || r.patient_id) merged.mrn = r.mrn || r.patient_id;
|
||||||
if (r.bed_id) merged.bed_id = r.bed_id;
|
if (r.bed_id) merged.bed_id = r.bed_id;
|
||||||
@@ -3635,20 +3659,6 @@
|
|||||||
if (readings.length > 0) {
|
if (readings.length > 0) {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
readings.forEach(r => {
|
readings.forEach(r => {
|
||||||
const rAge = r.timestamp ? (now - new Date(r.timestamp).getTime()) : 9999999;
|
|
||||||
if (rAge > 25000) {
|
|
||||||
r.heart_rate = null;
|
|
||||||
r.spo2 = null;
|
|
||||||
r.respiratory_rate = null;
|
|
||||||
r.temperature = null;
|
|
||||||
r.temperature2 = null;
|
|
||||||
r.temperature_diff = null;
|
|
||||||
}
|
|
||||||
if (rAge > 900000) {
|
|
||||||
r.systolic_bp = null;
|
|
||||||
r.diastolic_bp = null;
|
|
||||||
r.map_bp = null;
|
|
||||||
}
|
|
||||||
if (r.id !== lastReadingId) {
|
if (r.id !== lastReadingId) {
|
||||||
lastReadingId = r.id;
|
lastReadingId = r.id;
|
||||||
readingCount++;
|
readingCount++;
|
||||||
@@ -3726,18 +3736,14 @@
|
|||||||
const devKey = r.device_id;
|
const devKey = r.device_id;
|
||||||
if (devKey && connectedDevices[devKey]) {
|
if (devKey && connectedDevices[devKey]) {
|
||||||
const dev = connectedDevices[devKey];
|
const dev = connectedDevices[devKey];
|
||||||
const rAge = r.timestamp ? (now - new Date(r.timestamp).getTime()) : 9999999;
|
const rAge = getTimestampAgeMs(r.timestamp);
|
||||||
if (rAge <= 25000) {
|
if ((dev.heart_rate === undefined || dev.heart_rate === null) && r.heart_rate != null) dev.heart_rate = r.heart_rate;
|
||||||
if ((dev.heart_rate === undefined || dev.heart_rate === null) && r.heart_rate != null) dev.heart_rate = r.heart_rate;
|
if ((dev.spo2 === undefined || dev.spo2 === null) && r.spo2 != null) dev.spo2 = r.spo2;
|
||||||
if ((dev.spo2 === undefined || dev.spo2 === null) && r.spo2 != null) dev.spo2 = r.spo2;
|
if ((dev.respiratory_rate === undefined || dev.respiratory_rate === null) && r.respiratory_rate != null) dev.respiratory_rate = r.respiratory_rate;
|
||||||
if ((dev.respiratory_rate === undefined || dev.respiratory_rate === null) && r.respiratory_rate != null) dev.respiratory_rate = r.respiratory_rate;
|
if ((dev.temperature === undefined || dev.temperature === null) && r.temperature != null) dev.temperature = r.temperature;
|
||||||
if ((dev.temperature === undefined || dev.temperature === null) && r.temperature != null) dev.temperature = r.temperature;
|
if ((dev.systolic_bp === undefined || dev.systolic_bp === null) && r.systolic_bp != null) dev.systolic_bp = r.systolic_bp;
|
||||||
}
|
if ((dev.diastolic_bp === undefined || dev.diastolic_bp === null) && r.diastolic_bp != null) dev.diastolic_bp = r.diastolic_bp;
|
||||||
if (rAge <= 900000) {
|
if ((dev.map_bp === undefined || dev.map_bp === null) && r.map_bp != null) dev.map_bp = r.map_bp;
|
||||||
if ((dev.systolic_bp === undefined || dev.systolic_bp === null) && r.systolic_bp != null) dev.systolic_bp = r.systolic_bp;
|
|
||||||
if ((dev.diastolic_bp === undefined || dev.diastolic_bp === null) && r.diastolic_bp != null) dev.diastolic_bp = r.diastolic_bp;
|
|
||||||
if ((dev.map_bp === undefined || dev.map_bp === null) && r.map_bp != null) dev.map_bp = r.map_bp;
|
|
||||||
}
|
|
||||||
if (!dev.lastSeen && r.timestamp) dev.lastSeen = new Date(r.timestamp);
|
if (!dev.lastSeen && r.timestamp) dev.lastSeen = new Date(r.timestamp);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -4787,6 +4793,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function animateWaveforms() {
|
function animateWaveforms() {
|
||||||
|
if (isMinimal || document.hidden) {
|
||||||
|
requestAnimationFrame(animateWaveforms);
|
||||||
|
return;
|
||||||
|
}
|
||||||
waveformPhase += 0.05;
|
waveformPhase += 0.05;
|
||||||
|
|
||||||
// Generate idle waveforms for cards with no data
|
// Generate idle waveforms for cards with no data
|
||||||
|
|||||||
Reference in New Issue
Block a user