reorganize application code into fiveparaminte-main directory structure

This commit is contained in:
2026-09-17 14:30:02 +05:30
parent 1b1b382f3e
commit 68bda2519f
16 changed files with 1080 additions and 379 deletions
+33
View File
@@ -79,3 +79,36 @@ async def forward_vitals_to_api(vitals: NormalizedVitals) -> bool:
logger.warning(f"Error forwarding to Render Cloud: {type(e).__name__} ({err_msg})")
return primary_success or render_success
async def sync_patient_assignment_to_cloud(patient_data: dict, device_id: str) -> bool:
"""
Synchronizes patient demographics assignment to Render Cloud Server.
Ensures local and remote dashboards always show the exact same patient.
"""
import os
is_on_render = os.getenv("RENDER") is not None or os.getenv("PORT") == "10000" or "render.local" in os.getenv("HOSTNAME", "")
if is_on_render:
return True
client = get_http_client()
render_assign_url = "https://contectfiveparamonitor.onrender.com/api/patient/assign"
payload = {
"device_id": device_id,
"mrn": patient_data.get("mrn") or patient_data.get("patient_id") or "",
"name": patient_data.get("name") or patient_data.get("patient_name") or "Unknown",
"dob": patient_data.get("dob") or patient_data.get("patient_dob"),
"gender": patient_data.get("gender") or patient_data.get("patient_gender"),
"bed_id": patient_data.get("bed_id"),
}
try:
url_with_key = render_assign_url
if settings.api_token:
url_with_key = f"{render_assign_url}?api_key={settings.api_token}"
resp = await client.post(url_with_key, json=payload, headers={"Content-Type": "application/json"})
resp.raise_for_status()
logger.info(f"Successfully synced patient {payload['mrn']} ({payload['name']}) to Render Cloud for {device_id}")
return True
except Exception as e:
logger.warning(f"Could not sync patient assignment to Render Cloud: {type(e).__name__} ({e})")
return False
+22 -31
View File
@@ -165,6 +165,10 @@ def parse_contec_hl7_text(raw_text: str, client_ip: str) -> Optional[NormalizedV
if len(fields) > 8 and fields[8]:
vitals_dict["patient_gender"] = fields[8].strip().upper()
# PID-35: Patient Category (ADULT / PEDIATRIC / NEONATAL)
if len(fields) > 35 and fields[35]:
vitals_dict["patient_type"] = fields[35].strip()
elif seg_name == "PV1":
# PV1-3: Assigned Patient Location (Room^Bed^...)
if len(fields) > 3 and fields[3]:
@@ -174,6 +178,10 @@ def parse_contec_hl7_text(raw_text: str, client_ip: str) -> Optional[NormalizedV
if bed:
vitals_dict["bed_id"] = bed
# PV1-18: Patient Type
if len(fields) > 18 and fields[18]:
vitals_dict["patient_type"] = fields[18].strip()
elif seg_name == "OBX":
if len(fields) > 5:
obs_val = fields[5].strip()
@@ -279,6 +287,7 @@ def _parse_45_byte_packet(data: bytes, vitals_dict: dict) -> bool:
if "present_fields" not in vitals_dict:
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_dia = read_u16_le(data, 17)
nibp_map = read_u16_le(data, 19)
@@ -363,42 +372,26 @@ def _parse_spo2_waveform_packet(data: bytes, vitals_dict: dict) -> bool:
if "pleth_wave" not in vitals_dict["present_fields"]:
vitals_dict["present_fields"].append("pleth_wave")
found = False
# Check SpO2 at standard offset 264
spo2_val = read_u16_le(data, 264)
# If 264 is disconnected sentinel, probe is NOT connected / off-finger
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:
if spo2_val is not None and 50 <= spo2_val <= 100:
vitals_dict["spo2"] = float(spo2_val)
if "spo2" not in vitals_dict["present_fields"]:
vitals_dict["present_fields"].append("spo2")
found = True
# ECG HR is at offset 266 in the 286-byte SpO2 waveform packet footer.
# Offset 268 is the SpO2 HIGH ALARM LIMIT (always 100) — NOT pulse rate!
# So we read ECG HR at 266 only.
# Pulse Rate / ECG HR is at offset 266 in the footer
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:
vitals_dict["heart_rate"] = float(ecg_hr)
if "heart_rate" not in vitals_dict["present_fields"]:
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:
@@ -434,13 +427,13 @@ def _parse_989_byte_packet(data: bytes, vitals_dict: dict) -> bool:
if "ecg_wave" not in vitals_dict["present_fields"]:
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)
if hr_val is not None and hr_val not in (65535, 9999, 0) and 20 <= hr_val <= 300:
# Only use ECG HR if SpO2-probe PR has NOT already set heart_rate.
# SpO2 PR (from the pleth packet) matches the monitor display and wins.
if "heart_rate" not in vitals_dict.get("present_fields", []):
if hr_val is not None and hr_val not in (65535, 32767, 9999, 0) and 20 <= hr_val <= 300:
if vitals_dict.get("heart_rate") is None:
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
@@ -478,9 +471,7 @@ def _find_vitals_block_in_stream(data: bytes, client_ip: str) -> Optional[Normal
elif pkt_len == 989:
if _parse_989_byte_packet(pkt_data, vitals_dict):
found_any = True
elif 100 < pkt_len < 600:
if _parse_spo2_waveform_packet(pkt_data, vitals_dict):
found_any = True
# Handled specifically by known packet sizes above
i += max(pkt_len, 4)
else:
+100 -29
View File
@@ -58,25 +58,58 @@ TELEMETRY_WORDS = {
}
def is_telemetry_word(s_lower: str) -> bool:
"""Check if string matches hardware telemetry or alarm words."""
if s_lower in TELEMETRY_WORDS:
return True
tokens = set(re.findall(r'[a-zA-Z0-9]+', s_lower))
alarm_tokens = {
'sensor', 'alarm', 'lead', 'leads', 'searching', 'pulse', 'cuff', 'probe',
'off', 'fall', 'leak', 'motion', 'weak', 'arr', 'asystole', 'vfib', 'vtac',
'pvc', 'brady', 'tachy', 'error', 'ecg', 'spo2', 'nibp', 'resp', 'temp',
'mv', 'bpm', 'mmhg'
}
if any(tok in alarm_tokens for tok in tokens):
return True
return False
def extract_demographics_from_packet(data: bytes, client_ip: str) -> dict:
"""
Extracts real patient demographics from Contec CMS8500 / CMS7000 binary packets.
Extracts real patient demographics from Contec packets.
Only extracts clean, legitimate patient fields — strictly rejects sensor alarms,
waveform noise, and hardware telemetry.
"""
patient_type = None
data_upper = data.upper()
if b'PEDIATRIC' in data_upper or b'PED' in data_upper:
patient_type = 'Pediatric'
elif b'NEONATE' in data_upper or b'NEONATAL' in data_upper or b'NEO' in data_upper:
patient_type = 'Neonatal'
elif b'ADULT' in data_upper or b'ADU' in data_upper:
patient_type = 'Adult'
# Strictly reject binary waveform/measurement packets from generating false patient records.
# Patient MRN/Name extraction requires an authentic HL7 message with PID or MSH.
if b'PID|' not in data and b'MSH|' not in data:
if patient_type:
return {"patient_type": patient_type}
return None
clean = []
for m in re.finditer(rb'[a-zA-Z0-9_\-\.]{2,}', data):
# Match strings of printable alphanumeric and reasonable characters, allowing single spaces
for m in re.finditer(rb'[a-zA-Z0-9_\-\.]{2,}(?:\s+[a-zA-Z0-9_\-\.]{2,})*', data):
s = m.group(0).decode('latin1', errors='ignore').strip()
if not s or len(s) < 2:
continue
s_lower = s.lower()
if any(kw in s_lower for kw in TELEMETRY_WORDS):
if is_telemetry_word(s_lower):
continue
if len(set(s)) == 1:
continue
if set(s).issubset({'.', '-', '_', '/', '\\'}):
continue
if len(s) > 8 and re.match(r'^[A-Za-z]+$', s):
if len(s) > 6 and re.match(r'^[A-Za-z0-9]+$', s):
diffs = [abs(ord(s[i]) - ord(s[i-1])) for i in range(1, len(s))]
if sum(1 for d in diffs if d <= 2) / len(diffs) > 0.6:
continue
@@ -84,7 +117,7 @@ def extract_demographics_from_packet(data: bytes, client_ip: str) -> dict:
continue
clean.append(s)
if not clean:
if not clean and not patient_type:
return None
name = None
@@ -95,9 +128,8 @@ def extract_demographics_from_packet(data: bytes, client_ip: str) -> dict:
for s in clean:
s_up = s.upper()
s_low = s.lower()
if s_low in TELEMETRY_WORDS:
continue
# Check gender first
if s_up in ('MALE', 'FEMALE') and not gender:
gender = 'Male' if s_up == 'MALE' else 'Female'
continue
@@ -105,52 +137,75 @@ def extract_demographics_from_packet(data: bytes, client_ip: str) -> dict:
gender = 'Male' if s_up == 'M' else 'Female'
continue
# Date of birth: 4-digit birth year between 1920 and 2024, or YYYY-MM-DD
if re.match(r'^(19\d\d|20[01]\d|202[0-4])$', s) and not dob:
if is_telemetry_word(s_low):
continue
# Date of birth: 4-digit birth year between 1920 and 2026, or YYYY-MM-DD
if re.match(r'^(19\d\d|20[01]\d|202[0-6])$', s) and not dob:
dob = s
continue
elif re.match(r'^\d{4}[-/]\d{2}[-/]\d{2}$', s) and not dob:
dob = s
continue
# Name candidate: strictly at least 3 letters, has a vowel, not telemetry
if re.search(r'^[A-Za-z\s\.\-]{3,30}$', s) and re.search(r'[aeiouyAEIOUY]', s) and not name:
if s_low not in TELEMETRY_WORDS:
# Name candidate: contains letters and a vowel, length between 3 and 40
if re.search(r'^[A-Za-z\s\.\-]{3,40}$', s) and re.search(r'[aeiouyAEIOUY]', s) and not name:
if not is_telemetry_word(s_low):
name = s
continue
# MRN candidate: numeric or alphanumeric ID, at least 2 chars, not a year, not telemetry
if not mrn and re.match(r'^[A-Za-z0-9\-]{2,15}$', s) and s_low not in TELEMETRY_WORDS:
# MRN candidate: numeric or alphanumeric ID, 2..15 chars, not a birth year
if not mrn and re.match(r'^[A-Za-z0-9\-]{2,15}$', s) and not is_telemetry_word(s_low):
if not (s.isdigit() and 1920 <= int(s) <= 2030):
if s.isdigit() and len(s) > 10:
continue
mrn = s
continue
# Only accept when BOTH real name and real MRN/DOB are present together
if name and (mrn or dob):
# Accept if valid MRN or Name was detected
if mrn or name:
clean_mrn = mrn or name
clean_name = name or f"Patient {clean_mrn}"
return {
"name": name,
"mrn": mrn or name,
"patient_id": mrn or name,
"name": clean_name,
"mrn": clean_mrn,
"patient_id": clean_mrn,
"dob": dob,
"gender": gender,
"patient_type": patient_type,
}
elif patient_type:
return {
"patient_type": patient_type,
}
return None
async def apply_auto_demographics(client_ip: str, demo: dict):
from config import settings
from api_client import sync_patient_assignment_to_cloud
model = settings.device_models.get(client_ip, settings.monitor_model)
device_id = f"{model}_{client_ip}"
name = demo.get("name")
mrn = demo.get("mrn")
dob = demo.get("dob")
gender = demo.get("gender")
patient_type = demo.get("patient_type")
current = device_patient_map.get(device_id, {})
if current.get("name") == name and current.get("mrn") == mrn and current.get("dob") == dob and current.get("gender") == gender:
# If only patient_type updated
if not mrn and not name:
if patient_type and current.get("patient_type") != patient_type:
current["patient_type"] = patient_type
device_patient_map[device_id] = current
return
logger.info(f"*** REAL PATIENT DEMOGRAPHICS DETECTED FROM MACHINE ({client_ip}): Name={name}, MRN={mrn}, DOB={dob}, Gender={gender} ***")
if (current.get("name") == name and current.get("mrn") == mrn and
current.get("dob") == dob and current.get("gender") == gender and
current.get("patient_type") == patient_type):
return
logger.info(f"*** REAL PATIENT DEMOGRAPHICS DETECTED FROM MACHINE ({client_ip}): Name={name}, MRN={mrn}, DOB={dob}, Gender={gender}, Type={patient_type} ***")
bed_id = "1" if "192.168.100.106" in client_ip else "2"
p_data = {
@@ -159,7 +214,8 @@ async def apply_auto_demographics(client_ip: str, demo: dict):
"name": name,
"gender": gender,
"dob": dob,
"bed_id": bed_id
"bed_id": bed_id,
"patient_type": patient_type or current.get("patient_type"),
}
device_patient_map[device_id] = p_data
@@ -218,11 +274,15 @@ async def apply_auto_demographics(client_ip: str, demo: dict):
"gender": gender,
"patient_dob": dob,
"dob": dob,
"bed_id": bed_id
"bed_id": bed_id,
"patient_type": p_data.get("patient_type"),
})
except Exception as e:
logger.debug(f"Failed to broadcast auto patient: {e}")
# Forward to Render Cloud Server so remote is kept in sync
asyncio.create_task(sync_patient_assignment_to_cloud(p_data, device_id))
async def handle_contec_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter, port: int):
"""
@@ -251,8 +311,13 @@ async def handle_contec_client(reader: asyncio.StreamReader, writer: asyncio.Str
logger.info(f"Contec client {client_ip}:{client_port} on port {port} sent EOF (0 bytes)")
break
if client_ip != "127.0.0.1":
logger.info(f"TCP READ from {client_ip}: {len(data)} bytes, hex: {data[:24].hex()}")
# Dynamically extract and apply patient demographics sent from the machine
try:
demo = extract_demographics_from_packet(data, client_ip)
if demo:
await apply_auto_demographics(client_ip, demo)
except Exception as ex:
logger.debug(f"Demographics extraction error: {ex}")
buffer += data
@@ -376,6 +441,7 @@ def get_assigned_patient(device_id: str):
"gender": patient.gender,
"dob": patient.dob,
"bed_id": patient.bed_id,
"patient_type": getattr(patient, "patient_type", None),
}
device_patient_map[device_id] = p_data
return p_data
@@ -396,6 +462,7 @@ def set_assigned_patient(device_id: str, patient_data: dict):
cached.patient_gender = patient_data.get("gender")
cached.patient_dob = patient_data.get("dob")
cached.bed_id = patient_data.get("bed_id")
cached.patient_type = patient_data.get("patient_type")
async def process_vitals(vitals):
@@ -416,6 +483,7 @@ async def process_vitals(vitals):
vitals.patient_gender = assigned.get("gender")
vitals.patient_dob = assigned.get("dob")
vitals.bed_id = assigned.get("bed_id")
vitals.patient_type = assigned.get("patient_type")
elif vitals.patient_name and vitals.patient_name != "Unknown":
# Monitor sent fresh demographics via HL7 PID segment!
device_patient_map[device_id] = {
@@ -425,6 +493,7 @@ async def process_vitals(vitals):
"gender": vitals.patient_gender,
"dob": vitals.patient_dob,
"bed_id": vitals.bed_id,
"patient_type": getattr(vitals, "patient_type", None),
}
# Check if incoming packet has fresh waveforms
@@ -453,14 +522,12 @@ async def process_vitals(vitals):
if value is not None:
active_fields.append(field)
# Update cache & record timestamps
# Update cache & record timestamps for present values
for field in active_fields:
val = getattr(vitals, field, None)
setattr(cached, field, val)
if val is not None:
setattr(cached, field, val)
device_field_timestamps[device_id][field] = now
else:
device_field_timestamps[device_id].pop(field, None)
# Clean up stale fields:
# Live vitals (HR, SpO2, Temp, RR) have a 60-second window because monitor cycles take ~24s
@@ -506,6 +573,7 @@ async def process_vitals(vitals):
vitals_dict["gender"] = vitals.patient_gender
vitals_dict["dob"] = vitals.patient_dob
vitals_dict["bed_id"] = vitals.bed_id
vitals_dict["patient_type"] = getattr(vitals, "patient_type", None)
await ws_manager.broadcast_vitals(vitals_dict)
except Exception as e:
logger.debug(f"WebSocket broadcast failed: {e}")
@@ -569,6 +637,7 @@ async def _persist_and_forward_bg(vitals, should_persist):
gender=vitals.patient_gender,
dob=vitals.patient_dob,
bed_id=vitals.bed_id,
patient_type=getattr(vitals, "patient_type", None),
device_id=vitals.device_id
)
db.add(patient)
@@ -584,6 +653,8 @@ async def _persist_and_forward_bg(vitals, should_persist):
patient.dob = vitals.patient_dob
if vitals.bed_id:
patient.bed_id = vitals.bed_id
if getattr(vitals, "patient_type", None):
patient.patient_type = vitals.patient_type
if vitals.device_id:
patient.device_id = vitals.device_id
+33
View File
@@ -79,3 +79,36 @@ async def forward_vitals_to_api(vitals: NormalizedVitals) -> bool:
logger.warning(f"Error forwarding to Render Cloud: {type(e).__name__} ({err_msg})")
return primary_success or render_success
async def sync_patient_assignment_to_cloud(patient_data: dict, device_id: str) -> bool:
"""
Synchronizes patient demographics assignment to Render Cloud Server.
Ensures local and remote dashboards always show the exact same patient.
"""
import os
is_on_render = os.getenv("RENDER") is not None or os.getenv("PORT") == "10000" or "render.local" in os.getenv("HOSTNAME", "")
if is_on_render:
return True
client = get_http_client()
render_assign_url = "https://contectfiveparamonitor.onrender.com/api/patient/assign"
payload = {
"device_id": device_id,
"mrn": patient_data.get("mrn") or patient_data.get("patient_id") or "",
"name": patient_data.get("name") or patient_data.get("patient_name") or "Unknown",
"dob": patient_data.get("dob") or patient_data.get("patient_dob"),
"gender": patient_data.get("gender") or patient_data.get("patient_gender"),
"bed_id": patient_data.get("bed_id"),
}
try:
url_with_key = render_assign_url
if settings.api_token:
url_with_key = f"{render_assign_url}?api_key={settings.api_token}"
resp = await client.post(url_with_key, json=payload, headers={"Content-Type": "application/json"})
resp.raise_for_status()
logger.info(f"Successfully synced patient {payload['mrn']} ({payload['name']}) to Render Cloud for {device_id}")
return True
except Exception as e:
logger.warning(f"Could not sync patient assignment to Render Cloud: {type(e).__name__} ({e})")
return False
+8
View File
@@ -165,6 +165,10 @@ def parse_contec_hl7_text(raw_text: str, client_ip: str) -> Optional[NormalizedV
if len(fields) > 8 and fields[8]:
vitals_dict["patient_gender"] = fields[8].strip().upper()
# PID-35: Patient Category (ADULT / PEDIATRIC / NEONATAL)
if len(fields) > 35 and fields[35]:
vitals_dict["patient_type"] = fields[35].strip()
elif seg_name == "PV1":
# PV1-3: Assigned Patient Location (Room^Bed^...)
if len(fields) > 3 and fields[3]:
@@ -174,6 +178,10 @@ def parse_contec_hl7_text(raw_text: str, client_ip: str) -> Optional[NormalizedV
if bed:
vitals_dict["bed_id"] = bed
# PV1-18: Patient Type
if len(fields) > 18 and fields[18]:
vitals_dict["patient_type"] = fields[18].strip()
elif seg_name == "OBX":
if len(fields) > 5:
obs_val = fields[5].strip()
+98 -25
View File
@@ -58,25 +58,58 @@ TELEMETRY_WORDS = {
}
def is_telemetry_word(s_lower: str) -> bool:
"""Check if string matches hardware telemetry or alarm words."""
if s_lower in TELEMETRY_WORDS:
return True
tokens = set(re.findall(r'[a-zA-Z0-9]+', s_lower))
alarm_tokens = {
'sensor', 'alarm', 'lead', 'leads', 'searching', 'pulse', 'cuff', 'probe',
'off', 'fall', 'leak', 'motion', 'weak', 'arr', 'asystole', 'vfib', 'vtac',
'pvc', 'brady', 'tachy', 'error', 'ecg', 'spo2', 'nibp', 'resp', 'temp',
'mv', 'bpm', 'mmhg'
}
if any(tok in alarm_tokens for tok in tokens):
return True
return False
def extract_demographics_from_packet(data: bytes, client_ip: str) -> dict:
"""
Extracts real patient demographics from Contec CMS8500 / CMS7000 binary packets.
Extracts real patient demographics from Contec packets.
Only extracts clean, legitimate patient fields — strictly rejects sensor alarms,
waveform noise, and hardware telemetry.
"""
patient_type = None
data_upper = data.upper()
if b'PEDIATRIC' in data_upper or b'PED' in data_upper:
patient_type = 'Pediatric'
elif b'NEONATE' in data_upper or b'NEONATAL' in data_upper or b'NEO' in data_upper:
patient_type = 'Neonatal'
elif b'ADULT' in data_upper or b'ADU' in data_upper:
patient_type = 'Adult'
# Strictly reject binary waveform/measurement packets from generating false patient records.
# Patient MRN/Name extraction requires an authentic HL7 message with PID or MSH.
if b'PID|' not in data and b'MSH|' not in data:
if patient_type:
return {"patient_type": patient_type}
return None
clean = []
for m in re.finditer(rb'[a-zA-Z0-9_\-\.]{2,}', data):
# Match strings of printable alphanumeric and reasonable characters, allowing single spaces
for m in re.finditer(rb'[a-zA-Z0-9_\-\.]{2,}(?:\s+[a-zA-Z0-9_\-\.]{2,})*', data):
s = m.group(0).decode('latin1', errors='ignore').strip()
if not s or len(s) < 2:
continue
s_lower = s.lower()
if any(kw in s_lower for kw in TELEMETRY_WORDS):
if is_telemetry_word(s_lower):
continue
if len(set(s)) == 1:
continue
if set(s).issubset({'.', '-', '_', '/', '\\'}):
continue
if len(s) > 8 and re.match(r'^[A-Za-z]+$', s):
if len(s) > 6 and re.match(r'^[A-Za-z0-9]+$', s):
diffs = [abs(ord(s[i]) - ord(s[i-1])) for i in range(1, len(s))]
if sum(1 for d in diffs if d <= 2) / len(diffs) > 0.6:
continue
@@ -84,7 +117,7 @@ def extract_demographics_from_packet(data: bytes, client_ip: str) -> dict:
continue
clean.append(s)
if not clean:
if not clean and not patient_type:
return None
name = None
@@ -95,9 +128,8 @@ def extract_demographics_from_packet(data: bytes, client_ip: str) -> dict:
for s in clean:
s_up = s.upper()
s_low = s.lower()
if s_low in TELEMETRY_WORDS:
continue
# Check gender first
if s_up in ('MALE', 'FEMALE') and not gender:
gender = 'Male' if s_up == 'MALE' else 'Female'
continue
@@ -105,52 +137,75 @@ def extract_demographics_from_packet(data: bytes, client_ip: str) -> dict:
gender = 'Male' if s_up == 'M' else 'Female'
continue
# Date of birth: 4-digit birth year between 1920 and 2024, or YYYY-MM-DD
if re.match(r'^(19\d\d|20[01]\d|202[0-4])$', s) and not dob:
if is_telemetry_word(s_low):
continue
# Date of birth: 4-digit birth year between 1920 and 2026, or YYYY-MM-DD
if re.match(r'^(19\d\d|20[01]\d|202[0-6])$', s) and not dob:
dob = s
continue
elif re.match(r'^\d{4}[-/]\d{2}[-/]\d{2}$', s) and not dob:
dob = s
continue
# Name candidate: strictly at least 3 letters, has a vowel, not telemetry
if re.search(r'^[A-Za-z\s\.\-]{3,30}$', s) and re.search(r'[aeiouyAEIOUY]', s) and not name:
if s_low not in TELEMETRY_WORDS:
# Name candidate: contains letters and a vowel, length between 3 and 40
if re.search(r'^[A-Za-z\s\.\-]{3,40}$', s) and re.search(r'[aeiouyAEIOUY]', s) and not name:
if not is_telemetry_word(s_low):
name = s
continue
# MRN candidate: numeric or alphanumeric ID, at least 2 chars, not a year, not telemetry
if not mrn and re.match(r'^[A-Za-z0-9\-]{2,15}$', s) and s_low not in TELEMETRY_WORDS:
# MRN candidate: numeric or alphanumeric ID, 2..15 chars, not a birth year
if not mrn and re.match(r'^[A-Za-z0-9\-]{2,15}$', s) and not is_telemetry_word(s_low):
if not (s.isdigit() and 1920 <= int(s) <= 2030):
if s.isdigit() and len(s) > 10:
continue
mrn = s
continue
# Only accept when BOTH real name and real MRN/DOB are present together
if name and (mrn or dob):
# Accept if valid MRN or Name was detected
if mrn or name:
clean_mrn = mrn or name
clean_name = name or f"Patient {clean_mrn}"
return {
"name": name,
"mrn": mrn or name,
"patient_id": mrn or name,
"name": clean_name,
"mrn": clean_mrn,
"patient_id": clean_mrn,
"dob": dob,
"gender": gender,
"patient_type": patient_type,
}
elif patient_type:
return {
"patient_type": patient_type,
}
return None
async def apply_auto_demographics(client_ip: str, demo: dict):
from config import settings
from api_client import sync_patient_assignment_to_cloud
model = settings.device_models.get(client_ip, settings.monitor_model)
device_id = f"{model}_{client_ip}"
name = demo.get("name")
mrn = demo.get("mrn")
dob = demo.get("dob")
gender = demo.get("gender")
patient_type = demo.get("patient_type")
current = device_patient_map.get(device_id, {})
if current.get("name") == name and current.get("mrn") == mrn and current.get("dob") == dob and current.get("gender") == gender:
# If only patient_type updated
if not mrn and not name:
if patient_type and current.get("patient_type") != patient_type:
current["patient_type"] = patient_type
device_patient_map[device_id] = current
return
logger.info(f"*** REAL PATIENT DEMOGRAPHICS DETECTED FROM MACHINE ({client_ip}): Name={name}, MRN={mrn}, DOB={dob}, Gender={gender} ***")
if (current.get("name") == name and current.get("mrn") == mrn and
current.get("dob") == dob and current.get("gender") == gender and
current.get("patient_type") == patient_type):
return
logger.info(f"*** REAL PATIENT DEMOGRAPHICS DETECTED FROM MACHINE ({client_ip}): Name={name}, MRN={mrn}, DOB={dob}, Gender={gender}, Type={patient_type} ***")
bed_id = "1" if "192.168.100.106" in client_ip else "2"
p_data = {
@@ -159,7 +214,8 @@ async def apply_auto_demographics(client_ip: str, demo: dict):
"name": name,
"gender": gender,
"dob": dob,
"bed_id": bed_id
"bed_id": bed_id,
"patient_type": patient_type or current.get("patient_type"),
}
device_patient_map[device_id] = p_data
@@ -218,11 +274,15 @@ async def apply_auto_demographics(client_ip: str, demo: dict):
"gender": gender,
"patient_dob": dob,
"dob": dob,
"bed_id": bed_id
"bed_id": bed_id,
"patient_type": p_data.get("patient_type"),
})
except Exception as e:
logger.debug(f"Failed to broadcast auto patient: {e}")
# Forward to Render Cloud Server so remote is kept in sync
asyncio.create_task(sync_patient_assignment_to_cloud(p_data, device_id))
async def handle_contec_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter, port: int):
"""
@@ -251,8 +311,13 @@ async def handle_contec_client(reader: asyncio.StreamReader, writer: asyncio.Str
logger.info(f"Contec client {client_ip}:{client_port} on port {port} sent EOF (0 bytes)")
break
if client_ip != "127.0.0.1":
logger.info(f"TCP READ from {client_ip}: {len(data)} bytes, hex: {data[:24].hex()}")
# Dynamically extract and apply patient demographics sent from the machine
try:
demo = extract_demographics_from_packet(data, client_ip)
if demo:
await apply_auto_demographics(client_ip, demo)
except Exception as ex:
logger.debug(f"Demographics extraction error: {ex}")
buffer += data
@@ -376,6 +441,7 @@ def get_assigned_patient(device_id: str):
"gender": patient.gender,
"dob": patient.dob,
"bed_id": patient.bed_id,
"patient_type": getattr(patient, "patient_type", None),
}
device_patient_map[device_id] = p_data
return p_data
@@ -396,6 +462,7 @@ def set_assigned_patient(device_id: str, patient_data: dict):
cached.patient_gender = patient_data.get("gender")
cached.patient_dob = patient_data.get("dob")
cached.bed_id = patient_data.get("bed_id")
cached.patient_type = patient_data.get("patient_type")
async def process_vitals(vitals):
@@ -416,6 +483,7 @@ async def process_vitals(vitals):
vitals.patient_gender = assigned.get("gender")
vitals.patient_dob = assigned.get("dob")
vitals.bed_id = assigned.get("bed_id")
vitals.patient_type = assigned.get("patient_type")
elif vitals.patient_name and vitals.patient_name != "Unknown":
# Monitor sent fresh demographics via HL7 PID segment!
device_patient_map[device_id] = {
@@ -425,6 +493,7 @@ async def process_vitals(vitals):
"gender": vitals.patient_gender,
"dob": vitals.patient_dob,
"bed_id": vitals.bed_id,
"patient_type": getattr(vitals, "patient_type", None),
}
# Check if incoming packet has fresh waveforms
@@ -506,6 +575,7 @@ async def process_vitals(vitals):
vitals_dict["gender"] = vitals.patient_gender
vitals_dict["dob"] = vitals.patient_dob
vitals_dict["bed_id"] = vitals.bed_id
vitals_dict["patient_type"] = getattr(vitals, "patient_type", None)
await ws_manager.broadcast_vitals(vitals_dict)
except Exception as e:
logger.debug(f"WebSocket broadcast failed: {e}")
@@ -569,6 +639,7 @@ async def _persist_and_forward_bg(vitals, should_persist):
gender=vitals.patient_gender,
dob=vitals.patient_dob,
bed_id=vitals.bed_id,
patient_type=getattr(vitals, "patient_type", None),
device_id=vitals.device_id
)
db.add(patient)
@@ -584,6 +655,8 @@ async def _persist_and_forward_bg(vitals, should_persist):
patient.dob = vitals.patient_dob
if vitals.bed_id:
patient.bed_id = vitals.bed_id
if getattr(vitals, "patient_type", None):
patient.patient_type = vitals.patient_type
if vitals.device_id:
patient.device_id = vitals.device_id
+16
View File
@@ -62,6 +62,22 @@ def _get_local_ips():
@asynccontextmanager
async def lifespan(app: FastAPI):
# Protect against Windows IOCP Proactor crashes (WinError 64 / 121 / 10054 on accept)
import sys
if sys.platform == "win32":
try:
loop = asyncio.get_running_loop()
def silence_windows_iocp_errors(loop, context):
msg = str(context.get("message", ""))
exc = context.get("exception")
winerr = getattr(exc, "winerror", None)
if winerr in (64, 121, 10054) or "Accept failed on a socket" in msg or "The specified network name is no longer available" in msg:
return
loop.default_exception_handler(context)
loop.set_exception_handler(silence_windows_iocp_errors)
except Exception:
pass
servers = []
# === Start Contec CMS7000PLUS TCP Servers ===
+1
View File
@@ -19,6 +19,7 @@ class Patient(Base):
gender = Column(String, nullable=True) # M/F/U from PID-8
dob = Column(String, nullable=True) # YYYYMMDD from PID-7
bed_id = Column(String, nullable=True) # bed from PV1-3
patient_type = Column(String, nullable=True) # Adult / Pediatric / Neonatal
device_id = Column(String, nullable=True, index=True) # assigned machine / device_id
class VitalReading(Base):
+68 -15
View File
@@ -24,6 +24,7 @@ class PatientAssignRequest(BaseModel):
dob: Optional[str] = None
gender: Optional[str] = None
bed_id: Optional[str] = None
patient_type: Optional[str] = None
# === WebSocket Manager for real-time vitals push ===
class VitalsWebSocketManager:
@@ -164,8 +165,8 @@ def list_patients(db: Session = Depends(get_db)):
patients = db.query(Patient).filter(Patient.patient_id != "UNKNOWN", Patient.mrn != "UNKNOWN").all()
results = []
for p in patients:
# Find latest vitals for this patient or this patient's assigned device
latest_reading = (
# Find latest non-null vitals across recent readings for this patient or device
recent_readings = (
db.query(VitalReading)
.filter(
or_(
@@ -175,9 +176,60 @@ def list_patients(db: Session = Depends(get_db)):
)
)
.order_by(VitalReading.timestamp.desc())
.first()
.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
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]
device = db.query(Device).filter(Device.device_id == p.device_id).first() if p.device_id else None
results.append({
@@ -191,18 +243,7 @@ def list_patients(db: Session = Depends(get_db)):
"device_id": p.device_id,
"device_status": device.status if device else "unassigned",
"device_ip": device.ip_address if device else None,
"latest_vitals": {
"heart_rate": latest_reading.heart_rate if latest_reading else None,
"spo2": latest_reading.spo2 if latest_reading else None,
"systolic_bp": latest_reading.systolic_bp if latest_reading else None,
"diastolic_bp": latest_reading.diastolic_bp if latest_reading else None,
"map_bp": latest_reading.map_bp if latest_reading else None,
"respiratory_rate": latest_reading.respiratory_rate if latest_reading else None,
"temperature": latest_reading.temperature if latest_reading else None,
"temperature2": getattr(latest_reading, 'temperature2', None) if latest_reading else None,
"temperature_diff": getattr(latest_reading, 'temperature_diff', None) if latest_reading else None,
"timestamp": latest_reading.timestamp.isoformat() if (latest_reading and latest_reading.timestamp) else None,
} if latest_reading else None
"latest_vitals": merged_vitals
})
return results
@@ -250,6 +291,8 @@ async def assign_patient(req: PatientAssignRequest, db: Session = Depends(get_db
})
except Exception:
pass
from api_client import sync_patient_assignment_to_cloud
asyncio.create_task(sync_patient_assignment_to_cloud({"mrn": "UNKNOWN", "name": "Unassigned Patient"}, device_id))
return {
"status": "success",
"message": f"Device {device_id} unassigned successfully"
@@ -271,6 +314,7 @@ async def assign_patient(req: PatientAssignRequest, db: Session = Depends(get_db
gender=req.gender,
dob=req.dob,
bed_id=req.bed_id,
patient_type=req.patient_type,
device_id=device_id
)
db.add(patient)
@@ -285,6 +329,8 @@ async def assign_patient(req: PatientAssignRequest, db: Session = Depends(get_db
patient.dob = req.dob
if req.bed_id is not None:
patient.bed_id = req.bed_id
if req.patient_type is not None:
patient.patient_type = req.patient_type
patient.device_id = device_id
# 3. Associate recent readings for this device that were marked "UNKNOWN"
@@ -304,6 +350,7 @@ async def assign_patient(req: PatientAssignRequest, db: Session = Depends(get_db
"gender": patient.gender,
"dob": patient.dob,
"bed_id": patient.bed_id,
"patient_type": patient.patient_type,
}
set_assigned_patient(device_id, p_data)
@@ -321,10 +368,14 @@ async def assign_patient(req: PatientAssignRequest, db: Session = Depends(get_db
"patient_dob": patient.dob,
"dob": patient.dob,
"bed_id": patient.bed_id,
"patient_type": patient.patient_type,
})
except Exception as e:
logger.debug(f"Broadcast of patient_assigned failed: {e}")
from api_client import sync_patient_assignment_to_cloud
asyncio.create_task(sync_patient_assignment_to_cloud(p_data, device_id))
return {
"status": "success",
"message": f"Patient {patient.name} ({mrn}) assigned to device {device_id}",
@@ -460,6 +511,7 @@ def get_network_info():
primary_ip = None
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(0.5)
s.connect(('8.8.8.8', 80))
primary_ip = s.getsockname()[0]
s.close()
@@ -578,6 +630,7 @@ async def push_vitals(vitals: NormalizedVitals, api_key: Optional[str] = None, d
vitals_dict["dob"] = vitals.patient_dob or (patient.dob if patient else None)
vitals_dict["patient_dob"] = vitals_dict["dob"]
vitals_dict["bed_id"] = vitals.bed_id or (patient.bed_id if patient else None)
vitals_dict["patient_type"] = getattr(vitals, "patient_type", None)
await ws_manager.broadcast_vitals(vitals_dict)
+1
View File
@@ -25,6 +25,7 @@ class NormalizedVitals(BaseModel):
patient_gender: Optional[str] = None # PID-8: M / F / U
patient_dob: Optional[str] = None # PID-7: YYYYMMDD
bed_id: Optional[str] = None # PV1-3: bed / room ID
patient_type: Optional[str] = None # Patient category: Adult / Pediatric / Neonatal
class DeviceStatus(BaseModel):
device_id: str
+291 -116
View File
@@ -2192,10 +2192,10 @@
<div class="machine-top-bar">
<div style="display: flex; align-items: center; gap: 10px; flex-wrap: wrap;">
<span class="machine-brand">🏥 CONTEC CMS7000PLUS</span>
<span class="machine-pill" id="machBedPill">BED 01</span>
<span class="machine-pill">ADULT</span>
<span style="font-weight: 800; color: #ffffff;" id="machPatientName">SHAHIL</span>
<span class="machine-pill" id="machMrnPill">MRN: 09091102</span>
<span class="machine-pill" id="machBedPill">BED --</span>
<span class="machine-pill" id="machCategoryPill">ADULT</span>
<span style="font-weight: 800; color: #ffffff;" id="machPatientName">--</span>
<span class="machine-pill" id="machMrnPill">MRN: --</span>
<span style="color: #64748b; font-size: 11px;">PACE: NO</span>
<span class="machine-alarm-normal">● TELEMETRY NORMAL</span>
</div>
@@ -2592,26 +2592,30 @@
};
if (!dev) return res;
const pMrn = String(dev?.mrn || dev?.patient_id || '').toLowerCase().trim();
const pName = String(dev?.patient_name || dev?.name || '').toLowerCase().trim();
let pMrn = String(dev?.mrn || dev?.patient_id || '').toLowerCase().trim();
if ((!pMrn || pMrn === 'unknown') && dev?.device_id) {
const pat = Object.values(patientRegistry).find(p => p.device_id === dev.device_id);
if (pat) pMrn = String(pat.mrn || pat.patient_id || '').toLowerCase().trim();
}
let pName = String(dev?.patient_name || dev?.name || '').toLowerCase().trim();
if ((!pName || pName === 'unknown') && dev?.device_id) {
const pat = Object.values(patientRegistry).find(p => p.device_id === dev.device_id);
if (pat) pName = String(pat.name || '').toLowerCase().trim();
}
const pDob = String(dev?.patient_dob || dev?.dob || '').toLowerCase().trim();
const pGender = String(dev?.patient_gender || dev?.gender || '').toLowerCase().trim();
// 1. MRN verification
if (targetMrn) {
const tm = targetMrn.toLowerCase().trim();
if (pMrn && (pMrn === tm || pMrn.includes(tm) || tm.includes(pMrn))) {
const cleanTm = tm.replace(/^0+/, '');
const cleanMrn = pMrn.replace(/^0+/, '');
if (pMrn && (pMrn === tm || (cleanTm.length >= 2 && cleanTm === cleanMrn))) {
res.mrnMatched = true;
} else {
const numT = parseInt(tm, 10);
const numMrn = parseInt(pMrn, 10);
if (!isNaN(numT) && !isNaN(numMrn) && numT === numMrn && tm.length >= 3 && pMrn.length >= 3) {
res.mrnMatched = true;
}
}
}
// 2. Name verification (supports "Shahil", "Shahil Kumar", "Kumar Shahil")
// 2. Name verification (supports exact, partial or token-based matching)
if (targetName) {
const tn = targetName.toLowerCase().trim();
const targetTokens = normalizeNameTokens(targetName);
@@ -2645,7 +2649,16 @@
}
}
res.allMatched = res.mrnMatched && res.nameMatched && res.dobMatched && res.genderMatched;
// Match positively on MRN, or on Name (with DOB if supplied)
if (targetMrn && res.mrnMatched) {
res.allMatched = true;
} else if (targetName && res.nameMatched && (!targetDob || res.dobMatched)) {
res.allMatched = true;
} else if (!targetMrn && !targetName) {
res.allMatched = res.dobMatched && res.genderMatched;
} else {
res.allMatched = false;
}
return res;
}
@@ -2682,6 +2695,32 @@
bed_id: null
};
function clearMachineVitals() {
machVitals.heart_rate = null;
machVitals.spo2 = null;
machVitals.systolic_bp = null;
machVitals.diastolic_bp = null;
machVitals.map_bp = null;
machVitals.respiratory_rate = null;
machVitals.temperature = null;
machVitals.temperature2 = null;
machVitals.temperature_diff = null;
machVitals.device_id = null;
machVitals.patient_name = targetName || null;
machVitals.patient_id = targetMrn || null;
machVitals.bed_id = null;
machVitals.patient_type = null;
const elName = document.getElementById('machPatientName');
if (elName) elName.textContent = machVitals.patient_name ? machVitals.patient_name.toUpperCase() : '--';
const elMrn = document.getElementById('machMrnPill');
if (elMrn) elMrn.textContent = machVitals.patient_id ? `MRN: ${machVitals.patient_id}` : 'MRN: --';
const elBed = document.getElementById('machBedPill');
if (elBed) elBed.textContent = machVitals.bed_id ? `BED ${machVitals.bed_id}` : 'BED --';
updateMachineDisplay();
}
let machSweepX = 0;
let machPrevY_ecg = null;
let machPrevY_spo2 = null;
@@ -2711,13 +2750,16 @@
window.addEventListener('resize', resizeMachineCanvases);
if (targetMrn) {
machVitals.patient_id = targetMrn;
const elMrn = document.getElementById('machMrnPill');
if (elMrn) elMrn.textContent = `MRN: ${targetMrn}`;
}
if (targetName) {
machVitals.patient_name = targetName;
const elName = document.getElementById('machPatientName');
if (elName) elName.textContent = targetName.toUpperCase();
}
updateMachineDisplay();
// Load real patient telemetry from DB
fetchMachineTelemetry();
@@ -3021,45 +3063,136 @@
// === Load Real Machine Telemetry from Database & API ===
async function fetchMachineTelemetry() {
try {
const targetId = targetMrn || targetName || '09091102';
// 1. Demographics from registered patient list
if (hasTargetFilter) {
// Strict Target Patient Mode: NEVER fallback to another patient or transmitting device!
let p = null;
const patResp = await fetch(`${API_BASE}/patients`);
if (patResp.ok) {
const patients = await patResp.json();
const p = patients.find(pt =>
(targetMrn && (pt.mrn === targetMrn || pt.patient_id === targetMrn)) ||
(targetName && pt.name && pt.name.toLowerCase().includes(targetName.toLowerCase()))
) || (targetMrn ? null : patients[0]);
if (targetMrn) {
const cleanTm = targetMrn.toLowerCase().trim().replace(/^0+/, '');
p = patients.find(pt => {
const m = String(pt.mrn || pt.patient_id || '').toLowerCase().trim();
return m === targetMrn.toLowerCase().trim() || (cleanTm.length >= 2 && m.replace(/^0+/, '') === cleanTm);
});
} else if (targetName) {
p = patients.find(pt => pt.name && pt.name.toLowerCase().includes(targetName.toLowerCase()));
}
}
if (p) {
machVitals.patient_name = p.name || targetName || 'SHAHIL';
machVitals.patient_id = p.mrn || p.patient_id || targetMrn || '09091102';
machVitals.bed_id = p.bed_id || '1';
machVitals.device_id = p.device_id || 'CMS8500_192.168.100.106';
machVitals.patient_name = p.name || targetName || null;
machVitals.patient_id = p.mrn || p.patient_id || targetMrn || null;
machVitals.bed_id = p.bed_id || null;
machVitals.device_id = p.device_id || '';
const elName = document.getElementById('machPatientName');
if (elName) elName.textContent = machVitals.patient_name.toUpperCase();
if (elName) elName.textContent = machVitals.patient_name ? machVitals.patient_name.toUpperCase() : '--';
const elMrn = document.getElementById('machMrnPill');
if (elMrn) elMrn.textContent = `MRN: ${machVitals.patient_id}`;
if (elMrn) elMrn.textContent = machVitals.patient_id ? `MRN: ${machVitals.patient_id}` : 'MRN: --';
const elBed = document.getElementById('machBedPill');
if (elBed) elBed.textContent = `BED ${machVitals.bed_id}`;
if (elBed) elBed.textContent = machVitals.bed_id ? `BED ${machVitals.bed_id}` : 'BED --';
if (p.latest_vitals) {
applyVitalsToMachine(p.latest_vitals);
return;
}
}
}
// 2. Fallback: Fetch latest single reading directly from DB (limit=1: NO FAKE DATA)
const readResp = await fetch(`${API_BASE}/latest-readings?mrn=${encodeURIComponent(targetId)}&limit=1`);
// Query latest readings directly for target patient
const targetId = targetMrn || targetName;
if (targetId) {
const readResp = await fetch(`${API_BASE}/latest-readings?mrn=${encodeURIComponent(targetId)}&limit=10`);
if (readResp.ok) {
const readings = await readResp.json();
if (readings && readings.length > 0) {
applyVitalsToMachine(readings[0]);
} else {
applyVitalsToMachine(null);
const merged = {};
for (let i = readings.length - 1; i >= 0; i--) {
const r = readings[i];
for (const k in r) {
if (r[k] !== null && r[k] !== undefined) merged[k] = r[k];
}
}
applyVitalsToMachine(merged);
return;
}
}
}
// If no readings or machine data exists for target patient, strictly clear all vitals to '--' / LEAD OFF / flat line
clearMachineVitals();
return;
}
// General mode (no target filter):
// Determine target patient: active device patient
let targetId = null;
if (activeDeviceId && connectedDevices[activeDeviceId]) {
const activeDev = connectedDevices[activeDeviceId];
targetId = activeDev.patient_id || activeDev.mrn;
}
// 1. Demographics from registered patient list
const patResp = await fetch(`${API_BASE}/patients`);
if (patResp.ok) {
const patients = await patResp.json();
let p = null;
if (activeDeviceId) {
p = patients.find(pt => pt.device_id === activeDeviceId);
}
if (!p && patients.length > 0) {
// Find patient corresponding to a live transmitting device
p = patients.find(pt => pt.device_id && connectedDevices[pt.device_id]?._liveData) ||
patients.find(pt => pt.device_id && pt.device_id === activeDeviceId);
}
if (p) {
machVitals.patient_name = p.name || null;
machVitals.patient_id = p.mrn || p.patient_id || null;
machVitals.bed_id = p.bed_id || null;
machVitals.device_id = p.device_id || activeDeviceId || '';
const elName = document.getElementById('machPatientName');
if (elName) elName.textContent = machVitals.patient_name ? machVitals.patient_name.toUpperCase() : '--';
const elMrn = document.getElementById('machMrnPill');
if (elMrn) elMrn.textContent = machVitals.patient_id ? `MRN: ${machVitals.patient_id}` : 'MRN: --';
const elBed = document.getElementById('machBedPill');
if (elBed) elBed.textContent = machVitals.bed_id ? `BED ${machVitals.bed_id}` : 'BED --';
if (p.latest_vitals) {
applyVitalsToMachine(p.latest_vitals);
return;
}
} else if (activeDeviceId && connectedDevices[activeDeviceId]) {
const dev = connectedDevices[activeDeviceId];
if (dev.patient_name || dev.name) machVitals.patient_name = dev.patient_name || dev.name;
if (dev.mrn || dev.patient_id) machVitals.patient_id = dev.mrn || dev.patient_id;
if (dev.bed_id || dev.bedLabel) machVitals.bed_id = dev.bed_id || dev.bedLabel;
const elName = document.getElementById('machPatientName');
if (elName) elName.textContent = machVitals.patient_name ? machVitals.patient_name.toUpperCase() : '--';
const elMrn = document.getElementById('machMrnPill');
if (elMrn) elMrn.textContent = machVitals.patient_id ? `MRN: ${machVitals.patient_id}` : 'MRN: --';
const elBed = document.getElementById('machBedPill');
if (elBed) elBed.textContent = machVitals.bed_id ? `BED ${machVitals.bed_id}` : 'BED --';
}
}
// 2. Fallback: Fetch latest readings directly from DB (merge up to 10 recent readings so all active parameters display)
if (targetId) {
const readResp = await fetch(`${API_BASE}/latest-readings?mrn=${encodeURIComponent(targetId)}&limit=10`);
if (readResp.ok) {
const readings = await readResp.json();
if (readings && readings.length > 0) {
const merged = {};
for (let i = readings.length - 1; i >= 0; i--) {
const r = readings[i];
for (const k in r) {
if (r[k] !== null && r[k] !== undefined) merged[k] = r[k];
}
}
applyVitalsToMachine(merged);
}
}
}
} catch(e) {
@@ -3071,39 +3204,39 @@
if (!data) return;
// Heart Rate
if (data.heart_rate !== undefined) {
machVitals.heart_rate = (data.heart_rate != null && data.heart_rate > 0) ? Math.round(data.heart_rate) : null;
if (data.heart_rate !== undefined && data.heart_rate !== null) {
machVitals.heart_rate = (data.heart_rate > 0) ? Math.round(data.heart_rate) : null;
}
// SpO2
if (data.spo2 !== undefined) {
machVitals.spo2 = (data.spo2 != null && data.spo2 > 0) ? Math.round(data.spo2) : null;
if (data.spo2 !== undefined && data.spo2 !== null) {
machVitals.spo2 = (data.spo2 > 0) ? Math.round(data.spo2) : null;
}
// Respiration Rate
if (data.respiratory_rate !== undefined) {
machVitals.respiratory_rate = (data.respiratory_rate != null && data.respiratory_rate > 0) ? Math.round(data.respiratory_rate) : null;
if (data.respiratory_rate !== undefined && data.respiratory_rate !== null) {
machVitals.respiratory_rate = (data.respiratory_rate > 0) ? Math.round(data.respiratory_rate) : null;
}
// NIBP
if (data.systolic_bp !== undefined) {
machVitals.systolic_bp = (data.systolic_bp != null && data.systolic_bp > 0) ? Math.round(data.systolic_bp) : null;
if (data.systolic_bp !== undefined && data.systolic_bp !== null) {
machVitals.systolic_bp = (data.systolic_bp > 0) ? Math.round(data.systolic_bp) : null;
}
if (data.diastolic_bp !== undefined) {
machVitals.diastolic_bp = (data.diastolic_bp != null && data.diastolic_bp > 0) ? Math.round(data.diastolic_bp) : null;
if (data.diastolic_bp !== undefined && data.diastolic_bp !== null) {
machVitals.diastolic_bp = (data.diastolic_bp > 0) ? Math.round(data.diastolic_bp) : null;
}
if (data.map_bp !== undefined) {
machVitals.map_bp = (data.map_bp != null && data.map_bp > 0) ? Math.round(data.map_bp) : null;
if (data.map_bp !== undefined && data.map_bp !== null) {
machVitals.map_bp = (data.map_bp > 0) ? Math.round(data.map_bp) : null;
}
// Temperature 1 (T1)
if (data.temperature !== undefined) {
machVitals.temperature = (data.temperature != null && data.temperature > 0) ? Number(data.temperature) : null;
if (data.temperature !== undefined && data.temperature !== null) {
machVitals.temperature = (data.temperature > 0) ? Number(data.temperature) : null;
}
// Temperature 2 (T2)
if (data.temperature2 !== undefined) {
machVitals.temperature2 = (data.temperature2 != null && data.temperature2 > 0) ? Number(data.temperature2) : null;
if (data.temperature2 !== undefined && data.temperature2 !== null) {
machVitals.temperature2 = (data.temperature2 > 0) ? Number(data.temperature2) : null;
}
// Temperature Difference (TD)
@@ -3111,16 +3244,25 @@
machVitals.temperature_diff = Number(data.temperature_diff);
} else if (machVitals.temperature != null && machVitals.temperature2 != null) {
machVitals.temperature_diff = Math.round(Math.abs(machVitals.temperature - machVitals.temperature2) * 10) / 10;
} else {
machVitals.temperature_diff = null;
}
if (data.patient_name && data.patient_name !== 'Unknown') machVitals.patient_name = data.patient_name;
if (data.name && data.name !== 'Unknown') machVitals.patient_name = data.name;
if (data.mrn && data.mrn !== 'UNKNOWN') machVitals.patient_id = data.mrn;
if (data.patient_id && data.patient_id !== 'UNKNOWN') machVitals.patient_id = data.patient_id;
if (data.patient_name && data.patient_name !== 'Unknown' && data.patient_name !== 'UNKNOWN') machVitals.patient_name = data.patient_name;
if (data.name && data.name !== 'Unknown' && data.name !== 'UNKNOWN') machVitals.patient_name = data.name;
if (data.mrn && data.mrn !== 'UNKNOWN' && data.mrn !== 'UNASSIGNED') machVitals.patient_id = data.mrn;
if (data.patient_id && data.patient_id !== 'UNKNOWN' && data.patient_id !== 'UNASSIGNED') machVitals.patient_id = data.patient_id;
if (data.bed_id) machVitals.bed_id = data.bed_id;
if (data.device_id) machVitals.device_id = data.device_id;
if (data.patient_type) machVitals.patient_type = data.patient_type;
// Ensure top machine pills reflect current patient directly from machine
const elName = document.getElementById('machPatientName');
if (elName) elName.textContent = machVitals.patient_name ? machVitals.patient_name.toUpperCase() : '--';
const elMrn = document.getElementById('machMrnPill');
if (elMrn) elMrn.textContent = machVitals.patient_id ? `MRN: ${machVitals.patient_id}` : 'MRN: --';
const elBed = document.getElementById('machBedPill');
if (elBed) elBed.textContent = machVitals.bed_id ? `BED ${machVitals.bed_id}` : 'BED --';
const elCat = document.getElementById('machCategoryPill');
if (elCat && machVitals.patient_type) elCat.textContent = machVitals.patient_type.toUpperCase();
updateMachineDisplay();
}
@@ -3638,6 +3780,22 @@
function selectDevice(devId) {
activeDeviceId = devId;
setViewMode('single');
if (devId && connectedDevices[devId]) {
const dev = connectedDevices[devId];
machVitals.patient_name = (dev.patient_name && dev.patient_name !== 'Unknown' && dev.patient_name !== 'UNKNOWN') ? dev.patient_name : (dev.name && dev.name !== 'Unknown' && dev.name !== 'UNKNOWN' ? dev.name : null);
machVitals.patient_id = (dev.patient_id && dev.patient_id !== 'UNKNOWN' && dev.patient_id !== 'UNASSIGNED') ? dev.patient_id : ((dev.mrn && dev.mrn !== 'UNKNOWN' && dev.mrn !== 'UNASSIGNED') ? dev.mrn : null);
machVitals.bed_id = dev.bed_id || dev.bedLabel || null;
machVitals.device_id = devId;
const elName = document.getElementById('machPatientName');
if (elName) elName.textContent = machVitals.patient_name ? machVitals.patient_name.toUpperCase() : '--';
const elMrn = document.getElementById('machMrnPill');
if (elMrn) elMrn.textContent = machVitals.patient_id ? `MRN: ${machVitals.patient_id}` : 'MRN: --';
const elBed = document.getElementById('machBedPill');
if (elBed) elBed.textContent = machVitals.bed_id ? `BED ${machVitals.bed_id}` : 'BED --';
applyVitalsToMachine(dev);
}
}
function updateDeviceTabs() {
@@ -3865,6 +4023,22 @@
bed_id: data.bed_id,
bedLabel: data.bed_id || connectedDevices[devId]?.bedLabel
};
if (activeDeviceId === devId || !activeDeviceId) {
machVitals.patient_name = (data.patient_name && data.patient_name !== 'Unknown' && data.patient_name !== 'UNKNOWN') ? data.patient_name : (data.name && data.name !== 'Unknown' && data.name !== 'UNKNOWN' ? data.name : null);
machVitals.patient_id = (data.mrn && data.mrn !== 'UNKNOWN' && data.mrn !== 'UNASSIGNED') ? data.mrn : ((data.patient_id && data.patient_id !== 'UNKNOWN' && data.patient_id !== 'UNASSIGNED') ? data.patient_id : null);
machVitals.bed_id = data.bed_id || null;
machVitals.device_id = devId;
if (data.patient_type) machVitals.patient_type = data.patient_type;
const elName = document.getElementById('machPatientName');
if (elName) elName.textContent = machVitals.patient_name ? machVitals.patient_name.toUpperCase() : '--';
const elMrn = document.getElementById('machMrnPill');
if (elMrn) elMrn.textContent = machVitals.patient_id ? `MRN: ${machVitals.patient_id}` : 'MRN: --';
const elBed = document.getElementById('machBedPill');
if (elBed) elBed.textContent = machVitals.bed_id ? `BED ${machVitals.bed_id}` : 'BED --';
const elCat = document.getElementById('machCategoryPill');
if (elCat && machVitals.patient_type) elCat.textContent = machVitals.patient_type.toUpperCase();
}
updateDeviceTabs();
if (hasTargetFilter) {
if (matchesTargetPatient(connectedDevices[devId], devId)) {
@@ -3929,16 +4103,13 @@
};
// Real-time sync with Contec Machine Display (Strictly live telemetry, zero fake values)
if (devId) {
const isMatch = !hasTargetFilter || matchesTargetPatient(connectedDevices[devId], devId) ||
(devId.includes('192.168.100.106') && (targetMrn === '09091102' || (targetName && targetName.toLowerCase().includes('shahil'))));
if (isMatch) {
applyVitalsToMachine(data);
}
}
const isTargetMatch = !hasTargetFilter || matchesTargetPatient(connectedDevices[devId], devId);
if (devId && isTargetMatch) {
if (!activeDeviceId) activeDeviceId = devId;
applyVitalsToMachine(connectedDevices[devId]);
}
if (isTargetMatch) {
if (!activeDeviceId || hasTargetFilter) {
activeDeviceId = devId;
@@ -3949,21 +4120,23 @@
if (isTargetMatch && hasTargetFilter) {
// Target patient matched — force single view and render immediately
// (don't wait for fetchInitialDevicesAndPatients async race)
if (viewMode !== 'single') setViewMode('single');
renderSingleDevice(connectedDevices[devId]);
} else if (hasTargetFilter && !isTargetMatch) {
// Incoming data is for ANOTHER patient/device - do NOT show it on target patient view!
if (viewMode === 'single') {
renderSingleDevice(null);
}
} else if (viewMode === 'grid') {
renderMultiBedGrid();
} else if (viewMode === 'single') {
if (isTargetMatch && activeDeviceId === devId) {
if (activeDeviceId === devId) {
renderSingleDevice(connectedDevices[devId]);
} else if (hasTargetFilter && !activeDeviceId) {
renderSingleDevice(null);
}
}
}
function renderSingleDevice(data) {
function renderSingleDevice(dev) {
const waitingState = document.getElementById('singleTargetWaitingState');
const patientBanner = document.getElementById('singlePatientBanner');
const vitalsGrid = document.getElementById('singleVitalsGrid');
@@ -3972,67 +4145,52 @@
// Always ensure the ICU Live Monitor is visible in single view
if (icuMon) icuMon.style.display = 'flex';
if (!data && hasTargetFilter) {
data = {
patient_name: targetName || machVitals.patient_name || 'Shahilfaah',
name: targetName || machVitals.patient_name || 'Shahilfaah',
patient_id: targetMrn || machVitals.patient_id || '09091102',
mrn: targetMrn || machVitals.patient_id || '09091102',
device_id: machVitals.device_id || 'CMS8500_192.168.100.106',
bed_id: machVitals.bed_id || 'Bed 01',
bedLabel: machVitals.bed_id || 'Bed 01',
heart_rate: null,
spo2: null,
systolic_bp: null,
diastolic_bp: null,
map_bp: null,
respiratory_rate: null,
temperature: null
};
let data = dev;
if (hasTargetFilter) {
if (data && !matchesTargetPatient(data, data.device_id)) {
data = null;
}
if (!data) {
const matchDev = Object.values(connectedDevices).find(d => matchesTargetPatient(d, d.device_id));
if (matchDev) {
data = matchDev;
}
}
}
if (hasTargetFilter && !isMinimal) {
const isMatch = data && matchesTargetPatient(data, data.device_id);
if (!isMatch) {
if (waitingState) {
if (hasTargetFilter && !data) {
// Target patient has NO connected machine or matching readings
clearMachineVitals();
// Clear standard vital cards
updateVitalCard('hr', null, 'heart_rate');
updateVitalCard('spo2', null, 'spo2');
updateVitalCard('bp', null, 'systolic_bp');
updateVitalCard('rr', null, 'respiratory_rate');
updateVitalCard('temp', null, 'temperature');
updateVitalCard('map', null, 'map_bp');
if (!isMinimal && waitingState) {
waitingState.style.display = 'flex';
const badge = document.getElementById('targetWaitingMrnBadge');
if (badge) badge.innerHTML = getTargetPatientBadge();
}
} else {
if (waitingState) waitingState.style.display = 'none';
if (!isMinimal) {
if (patientBanner) patientBanner.style.display = 'none';
if (vitalsGrid) vitalsGrid.style.display = 'none';
}
} else {
if (waitingState) waitingState.style.display = 'none';
return;
}
if (waitingState) waitingState.style.display = 'none';
if (!isMinimal) {
if (patientBanner) patientBanner.style.display = 'flex';
if (vitalsGrid) vitalsGrid.style.display = 'grid';
}
if (!data) return;
// Sync with ICU Live Monitor
if (data) {
if (data.heart_rate != null) icuVitals.heart_rate = data.heart_rate;
if (data.spo2 != null) icuVitals.spo2 = data.spo2;
if (data.systolic_bp != null) icuVitals.systolic_bp = data.systolic_bp;
if (data.diastolic_bp != null) icuVitals.diastolic_bp = data.diastolic_bp;
if (data.map_bp != null) icuVitals.map_bp = data.map_bp;
if (data.respiratory_rate != null) icuVitals.respiratory_rate = data.respiratory_rate;
if (data.temperature != null) icuVitals.temperature = data.temperature;
if (data.patient_name && data.patient_name !== 'Unknown') icuVitals.patient_name = data.patient_name;
if (data.mrn || data.patient_id) icuVitals.patient_id = data.mrn || data.patient_id;
if (data.device_id) icuVitals.device_id = data.device_id;
if (data.bed_id || data.bedLabel) icuVitals.bed_id = data.bed_id || data.bedLabel;
document.getElementById('icuPatientNameText').textContent = icuVitals.patient_name;
document.getElementById('icuAvatar').textContent = icuVitals.patient_name.charAt(0).toUpperCase();
document.getElementById('icuMrnTag').textContent = `MRN: ${icuVitals.patient_id}`;
document.getElementById('icuBedTag').textContent = `BED: ${icuVitals.bed_id}`;
document.getElementById('icuDevTag').textContent = `DEV: ${icuVitals.device_id}`;
updateIcuMonitorReadouts();
}
// Sync with Contec Machine Screen
applyVitalsToMachine(data);
// 1. Update Patient Banner
const hasAssigned = data.patient_name && data.patient_name !== 'Unknown' && data.patient_name !== 'UNKNOWN' && data.patient_name !== 'Unassigned Patient';
@@ -4182,6 +4340,17 @@
};
closeAssignPatientModal();
if (activeDeviceId === device_id || !activeDeviceId) {
machVitals.patient_name = name;
machVitals.patient_id = mrn;
machVitals.bed_id = bed_id;
const elName = document.getElementById('machPatientName');
if (elName) elName.textContent = name ? name.toUpperCase() : '--';
const elMrn = document.getElementById('machMrnPill');
if (elMrn) elMrn.textContent = mrn ? `MRN: ${mrn}` : 'MRN: --';
const elBed = document.getElementById('machBedPill');
if (elBed) elBed.textContent = bed_id ? `BED ${bed_id}` : 'BED --';
}
updateDeviceTabs();
if (viewMode === 'grid') renderMultiBedGrid();
else if (viewMode === 'single' && activeDeviceId === device_id) {
@@ -4343,12 +4512,18 @@
const updateEl = document.getElementById(`update-${cardKey}`);
const cardEl = document.getElementById(`card-${cardKey}`);
if (!valueEl) return;
if (value === null || value === undefined) {
valueEl.innerHTML = '---';
valueEl.className = 'vital-value no-data';
if (statusEl) {
statusEl.textContent = 'No Data';
statusEl.className = 'status-badge offline';
}
if (cardEl) {
cardEl.classList.remove('has-value');
}
return;
}
+16
View File
@@ -62,6 +62,22 @@ def _get_local_ips():
@asynccontextmanager
async def lifespan(app: FastAPI):
# Protect against Windows IOCP Proactor crashes (WinError 64 / 121 / 10054 on accept)
import sys
if sys.platform == "win32":
try:
loop = asyncio.get_running_loop()
def silence_windows_iocp_errors(loop, context):
msg = str(context.get("message", ""))
exc = context.get("exception")
winerr = getattr(exc, "winerror", None)
if winerr in (64, 121, 10054) or "Accept failed on a socket" in msg or "The specified network name is no longer available" in msg:
return
loop.default_exception_handler(context)
loop.set_exception_handler(silence_windows_iocp_errors)
except Exception:
pass
servers = []
# === Start Contec CMS7000PLUS TCP Servers ===
+1
View File
@@ -19,6 +19,7 @@ class Patient(Base):
gender = Column(String, nullable=True) # M/F/U from PID-8
dob = Column(String, nullable=True) # YYYYMMDD from PID-7
bed_id = Column(String, nullable=True) # bed from PV1-3
patient_type = Column(String, nullable=True) # Adult / Pediatric / Neonatal
device_id = Column(String, nullable=True, index=True) # assigned machine / device_id
class VitalReading(Base):
+68 -15
View File
@@ -24,6 +24,7 @@ class PatientAssignRequest(BaseModel):
dob: Optional[str] = None
gender: Optional[str] = None
bed_id: Optional[str] = None
patient_type: Optional[str] = None
# === WebSocket Manager for real-time vitals push ===
class VitalsWebSocketManager:
@@ -164,8 +165,8 @@ def list_patients(db: Session = Depends(get_db)):
patients = db.query(Patient).filter(Patient.patient_id != "UNKNOWN", Patient.mrn != "UNKNOWN").all()
results = []
for p in patients:
# Find latest vitals for this patient or this patient's assigned device
latest_reading = (
# Find latest non-null vitals across recent readings for this patient or device
recent_readings = (
db.query(VitalReading)
.filter(
or_(
@@ -175,9 +176,60 @@ def list_patients(db: Session = Depends(get_db)):
)
)
.order_by(VitalReading.timestamp.desc())
.first()
.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
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]
device = db.query(Device).filter(Device.device_id == p.device_id).first() if p.device_id else None
results.append({
@@ -191,18 +243,7 @@ def list_patients(db: Session = Depends(get_db)):
"device_id": p.device_id,
"device_status": device.status if device else "unassigned",
"device_ip": device.ip_address if device else None,
"latest_vitals": {
"heart_rate": latest_reading.heart_rate if latest_reading else None,
"spo2": latest_reading.spo2 if latest_reading else None,
"systolic_bp": latest_reading.systolic_bp if latest_reading else None,
"diastolic_bp": latest_reading.diastolic_bp if latest_reading else None,
"map_bp": latest_reading.map_bp if latest_reading else None,
"respiratory_rate": latest_reading.respiratory_rate if latest_reading else None,
"temperature": latest_reading.temperature if latest_reading else None,
"temperature2": getattr(latest_reading, 'temperature2', None) if latest_reading else None,
"temperature_diff": getattr(latest_reading, 'temperature_diff', None) if latest_reading else None,
"timestamp": latest_reading.timestamp.isoformat() if (latest_reading and latest_reading.timestamp) else None,
} if latest_reading else None
"latest_vitals": merged_vitals
})
return results
@@ -250,6 +291,8 @@ async def assign_patient(req: PatientAssignRequest, db: Session = Depends(get_db
})
except Exception:
pass
from api_client import sync_patient_assignment_to_cloud
asyncio.create_task(sync_patient_assignment_to_cloud({"mrn": "UNKNOWN", "name": "Unassigned Patient"}, device_id))
return {
"status": "success",
"message": f"Device {device_id} unassigned successfully"
@@ -271,6 +314,7 @@ async def assign_patient(req: PatientAssignRequest, db: Session = Depends(get_db
gender=req.gender,
dob=req.dob,
bed_id=req.bed_id,
patient_type=req.patient_type,
device_id=device_id
)
db.add(patient)
@@ -285,6 +329,8 @@ async def assign_patient(req: PatientAssignRequest, db: Session = Depends(get_db
patient.dob = req.dob
if req.bed_id is not None:
patient.bed_id = req.bed_id
if req.patient_type is not None:
patient.patient_type = req.patient_type
patient.device_id = device_id
# 3. Associate recent readings for this device that were marked "UNKNOWN"
@@ -304,6 +350,7 @@ async def assign_patient(req: PatientAssignRequest, db: Session = Depends(get_db
"gender": patient.gender,
"dob": patient.dob,
"bed_id": patient.bed_id,
"patient_type": patient.patient_type,
}
set_assigned_patient(device_id, p_data)
@@ -321,10 +368,14 @@ async def assign_patient(req: PatientAssignRequest, db: Session = Depends(get_db
"patient_dob": patient.dob,
"dob": patient.dob,
"bed_id": patient.bed_id,
"patient_type": patient.patient_type,
})
except Exception as e:
logger.debug(f"Broadcast of patient_assigned failed: {e}")
from api_client import sync_patient_assignment_to_cloud
asyncio.create_task(sync_patient_assignment_to_cloud(p_data, device_id))
return {
"status": "success",
"message": f"Patient {patient.name} ({mrn}) assigned to device {device_id}",
@@ -460,6 +511,7 @@ def get_network_info():
primary_ip = None
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(0.5)
s.connect(('8.8.8.8', 80))
primary_ip = s.getsockname()[0]
s.close()
@@ -578,6 +630,7 @@ async def push_vitals(vitals: NormalizedVitals, api_key: Optional[str] = None, d
vitals_dict["dob"] = vitals.patient_dob or (patient.dob if patient else None)
vitals_dict["patient_dob"] = vitals_dict["dob"]
vitals_dict["bed_id"] = vitals.bed_id or (patient.bed_id if patient else None)
vitals_dict["patient_type"] = getattr(vitals, "patient_type", None)
await ws_manager.broadcast_vitals(vitals_dict)
+1
View File
@@ -25,6 +25,7 @@ class NormalizedVitals(BaseModel):
patient_gender: Optional[str] = None # PID-8: M / F / U
patient_dob: Optional[str] = None # PID-7: YYYYMMDD
bed_id: Optional[str] = None # PV1-3: bed / room ID
patient_type: Optional[str] = None # Patient category: Adult / Pediatric / Neonatal
class DeviceStatus(BaseModel):
device_id: str
+291 -116
View File
@@ -2192,10 +2192,10 @@
<div class="machine-top-bar">
<div style="display: flex; align-items: center; gap: 10px; flex-wrap: wrap;">
<span class="machine-brand">🏥 CONTEC CMS7000PLUS</span>
<span class="machine-pill" id="machBedPill">BED 01</span>
<span class="machine-pill">ADULT</span>
<span style="font-weight: 800; color: #ffffff;" id="machPatientName">SHAHIL</span>
<span class="machine-pill" id="machMrnPill">MRN: 09091102</span>
<span class="machine-pill" id="machBedPill">BED --</span>
<span class="machine-pill" id="machCategoryPill">ADULT</span>
<span style="font-weight: 800; color: #ffffff;" id="machPatientName">--</span>
<span class="machine-pill" id="machMrnPill">MRN: --</span>
<span style="color: #64748b; font-size: 11px;">PACE: NO</span>
<span class="machine-alarm-normal">● TELEMETRY NORMAL</span>
</div>
@@ -2592,26 +2592,30 @@
};
if (!dev) return res;
const pMrn = String(dev?.mrn || dev?.patient_id || '').toLowerCase().trim();
const pName = String(dev?.patient_name || dev?.name || '').toLowerCase().trim();
let pMrn = String(dev?.mrn || dev?.patient_id || '').toLowerCase().trim();
if ((!pMrn || pMrn === 'unknown') && dev?.device_id) {
const pat = Object.values(patientRegistry).find(p => p.device_id === dev.device_id);
if (pat) pMrn = String(pat.mrn || pat.patient_id || '').toLowerCase().trim();
}
let pName = String(dev?.patient_name || dev?.name || '').toLowerCase().trim();
if ((!pName || pName === 'unknown') && dev?.device_id) {
const pat = Object.values(patientRegistry).find(p => p.device_id === dev.device_id);
if (pat) pName = String(pat.name || '').toLowerCase().trim();
}
const pDob = String(dev?.patient_dob || dev?.dob || '').toLowerCase().trim();
const pGender = String(dev?.patient_gender || dev?.gender || '').toLowerCase().trim();
// 1. MRN verification
if (targetMrn) {
const tm = targetMrn.toLowerCase().trim();
if (pMrn && (pMrn === tm || pMrn.includes(tm) || tm.includes(pMrn))) {
const cleanTm = tm.replace(/^0+/, '');
const cleanMrn = pMrn.replace(/^0+/, '');
if (pMrn && (pMrn === tm || (cleanTm.length >= 2 && cleanTm === cleanMrn))) {
res.mrnMatched = true;
} else {
const numT = parseInt(tm, 10);
const numMrn = parseInt(pMrn, 10);
if (!isNaN(numT) && !isNaN(numMrn) && numT === numMrn && tm.length >= 3 && pMrn.length >= 3) {
res.mrnMatched = true;
}
}
}
// 2. Name verification (supports "Shahil", "Shahil Kumar", "Kumar Shahil")
// 2. Name verification (supports exact, partial or token-based matching)
if (targetName) {
const tn = targetName.toLowerCase().trim();
const targetTokens = normalizeNameTokens(targetName);
@@ -2645,7 +2649,16 @@
}
}
res.allMatched = res.mrnMatched && res.nameMatched && res.dobMatched && res.genderMatched;
// Match positively on MRN, or on Name (with DOB if supplied)
if (targetMrn && res.mrnMatched) {
res.allMatched = true;
} else if (targetName && res.nameMatched && (!targetDob || res.dobMatched)) {
res.allMatched = true;
} else if (!targetMrn && !targetName) {
res.allMatched = res.dobMatched && res.genderMatched;
} else {
res.allMatched = false;
}
return res;
}
@@ -2682,6 +2695,32 @@
bed_id: null
};
function clearMachineVitals() {
machVitals.heart_rate = null;
machVitals.spo2 = null;
machVitals.systolic_bp = null;
machVitals.diastolic_bp = null;
machVitals.map_bp = null;
machVitals.respiratory_rate = null;
machVitals.temperature = null;
machVitals.temperature2 = null;
machVitals.temperature_diff = null;
machVitals.device_id = null;
machVitals.patient_name = targetName || null;
machVitals.patient_id = targetMrn || null;
machVitals.bed_id = null;
machVitals.patient_type = null;
const elName = document.getElementById('machPatientName');
if (elName) elName.textContent = machVitals.patient_name ? machVitals.patient_name.toUpperCase() : '--';
const elMrn = document.getElementById('machMrnPill');
if (elMrn) elMrn.textContent = machVitals.patient_id ? `MRN: ${machVitals.patient_id}` : 'MRN: --';
const elBed = document.getElementById('machBedPill');
if (elBed) elBed.textContent = machVitals.bed_id ? `BED ${machVitals.bed_id}` : 'BED --';
updateMachineDisplay();
}
let machSweepX = 0;
let machPrevY_ecg = null;
let machPrevY_spo2 = null;
@@ -2711,13 +2750,16 @@
window.addEventListener('resize', resizeMachineCanvases);
if (targetMrn) {
machVitals.patient_id = targetMrn;
const elMrn = document.getElementById('machMrnPill');
if (elMrn) elMrn.textContent = `MRN: ${targetMrn}`;
}
if (targetName) {
machVitals.patient_name = targetName;
const elName = document.getElementById('machPatientName');
if (elName) elName.textContent = targetName.toUpperCase();
}
updateMachineDisplay();
// Load real patient telemetry from DB
fetchMachineTelemetry();
@@ -3021,45 +3063,136 @@
// === Load Real Machine Telemetry from Database & API ===
async function fetchMachineTelemetry() {
try {
const targetId = targetMrn || targetName || '09091102';
// 1. Demographics from registered patient list
if (hasTargetFilter) {
// Strict Target Patient Mode: NEVER fallback to another patient or transmitting device!
let p = null;
const patResp = await fetch(`${API_BASE}/patients`);
if (patResp.ok) {
const patients = await patResp.json();
const p = patients.find(pt =>
(targetMrn && (pt.mrn === targetMrn || pt.patient_id === targetMrn)) ||
(targetName && pt.name && pt.name.toLowerCase().includes(targetName.toLowerCase()))
) || (targetMrn ? null : patients[0]);
if (targetMrn) {
const cleanTm = targetMrn.toLowerCase().trim().replace(/^0+/, '');
p = patients.find(pt => {
const m = String(pt.mrn || pt.patient_id || '').toLowerCase().trim();
return m === targetMrn.toLowerCase().trim() || (cleanTm.length >= 2 && m.replace(/^0+/, '') === cleanTm);
});
} else if (targetName) {
p = patients.find(pt => pt.name && pt.name.toLowerCase().includes(targetName.toLowerCase()));
}
}
if (p) {
machVitals.patient_name = p.name || targetName || 'SHAHIL';
machVitals.patient_id = p.mrn || p.patient_id || targetMrn || '09091102';
machVitals.bed_id = p.bed_id || '1';
machVitals.device_id = p.device_id || 'CMS8500_192.168.100.106';
machVitals.patient_name = p.name || targetName || null;
machVitals.patient_id = p.mrn || p.patient_id || targetMrn || null;
machVitals.bed_id = p.bed_id || null;
machVitals.device_id = p.device_id || '';
const elName = document.getElementById('machPatientName');
if (elName) elName.textContent = machVitals.patient_name.toUpperCase();
if (elName) elName.textContent = machVitals.patient_name ? machVitals.patient_name.toUpperCase() : '--';
const elMrn = document.getElementById('machMrnPill');
if (elMrn) elMrn.textContent = `MRN: ${machVitals.patient_id}`;
if (elMrn) elMrn.textContent = machVitals.patient_id ? `MRN: ${machVitals.patient_id}` : 'MRN: --';
const elBed = document.getElementById('machBedPill');
if (elBed) elBed.textContent = `BED ${machVitals.bed_id}`;
if (elBed) elBed.textContent = machVitals.bed_id ? `BED ${machVitals.bed_id}` : 'BED --';
if (p.latest_vitals) {
applyVitalsToMachine(p.latest_vitals);
return;
}
}
}
// 2. Fallback: Fetch latest single reading directly from DB (limit=1: NO FAKE DATA)
const readResp = await fetch(`${API_BASE}/latest-readings?mrn=${encodeURIComponent(targetId)}&limit=1`);
// Query latest readings directly for target patient
const targetId = targetMrn || targetName;
if (targetId) {
const readResp = await fetch(`${API_BASE}/latest-readings?mrn=${encodeURIComponent(targetId)}&limit=10`);
if (readResp.ok) {
const readings = await readResp.json();
if (readings && readings.length > 0) {
applyVitalsToMachine(readings[0]);
} else {
applyVitalsToMachine(null);
const merged = {};
for (let i = readings.length - 1; i >= 0; i--) {
const r = readings[i];
for (const k in r) {
if (r[k] !== null && r[k] !== undefined) merged[k] = r[k];
}
}
applyVitalsToMachine(merged);
return;
}
}
}
// If no readings or machine data exists for target patient, strictly clear all vitals to '--' / LEAD OFF / flat line
clearMachineVitals();
return;
}
// General mode (no target filter):
// Determine target patient: active device patient
let targetId = null;
if (activeDeviceId && connectedDevices[activeDeviceId]) {
const activeDev = connectedDevices[activeDeviceId];
targetId = activeDev.patient_id || activeDev.mrn;
}
// 1. Demographics from registered patient list
const patResp = await fetch(`${API_BASE}/patients`);
if (patResp.ok) {
const patients = await patResp.json();
let p = null;
if (activeDeviceId) {
p = patients.find(pt => pt.device_id === activeDeviceId);
}
if (!p && patients.length > 0) {
// Find patient corresponding to a live transmitting device
p = patients.find(pt => pt.device_id && connectedDevices[pt.device_id]?._liveData) ||
patients.find(pt => pt.device_id && pt.device_id === activeDeviceId);
}
if (p) {
machVitals.patient_name = p.name || null;
machVitals.patient_id = p.mrn || p.patient_id || null;
machVitals.bed_id = p.bed_id || null;
machVitals.device_id = p.device_id || activeDeviceId || '';
const elName = document.getElementById('machPatientName');
if (elName) elName.textContent = machVitals.patient_name ? machVitals.patient_name.toUpperCase() : '--';
const elMrn = document.getElementById('machMrnPill');
if (elMrn) elMrn.textContent = machVitals.patient_id ? `MRN: ${machVitals.patient_id}` : 'MRN: --';
const elBed = document.getElementById('machBedPill');
if (elBed) elBed.textContent = machVitals.bed_id ? `BED ${machVitals.bed_id}` : 'BED --';
if (p.latest_vitals) {
applyVitalsToMachine(p.latest_vitals);
return;
}
} else if (activeDeviceId && connectedDevices[activeDeviceId]) {
const dev = connectedDevices[activeDeviceId];
if (dev.patient_name || dev.name) machVitals.patient_name = dev.patient_name || dev.name;
if (dev.mrn || dev.patient_id) machVitals.patient_id = dev.mrn || dev.patient_id;
if (dev.bed_id || dev.bedLabel) machVitals.bed_id = dev.bed_id || dev.bedLabel;
const elName = document.getElementById('machPatientName');
if (elName) elName.textContent = machVitals.patient_name ? machVitals.patient_name.toUpperCase() : '--';
const elMrn = document.getElementById('machMrnPill');
if (elMrn) elMrn.textContent = machVitals.patient_id ? `MRN: ${machVitals.patient_id}` : 'MRN: --';
const elBed = document.getElementById('machBedPill');
if (elBed) elBed.textContent = machVitals.bed_id ? `BED ${machVitals.bed_id}` : 'BED --';
}
}
// 2. Fallback: Fetch latest readings directly from DB (merge up to 10 recent readings so all active parameters display)
if (targetId) {
const readResp = await fetch(`${API_BASE}/latest-readings?mrn=${encodeURIComponent(targetId)}&limit=10`);
if (readResp.ok) {
const readings = await readResp.json();
if (readings && readings.length > 0) {
const merged = {};
for (let i = readings.length - 1; i >= 0; i--) {
const r = readings[i];
for (const k in r) {
if (r[k] !== null && r[k] !== undefined) merged[k] = r[k];
}
}
applyVitalsToMachine(merged);
}
}
}
} catch(e) {
@@ -3071,39 +3204,39 @@
if (!data) return;
// Heart Rate
if (data.heart_rate !== undefined) {
machVitals.heart_rate = (data.heart_rate != null && data.heart_rate > 0) ? Math.round(data.heart_rate) : null;
if (data.heart_rate !== undefined && data.heart_rate !== null) {
machVitals.heart_rate = (data.heart_rate > 0) ? Math.round(data.heart_rate) : null;
}
// SpO2
if (data.spo2 !== undefined) {
machVitals.spo2 = (data.spo2 != null && data.spo2 > 0) ? Math.round(data.spo2) : null;
if (data.spo2 !== undefined && data.spo2 !== null) {
machVitals.spo2 = (data.spo2 > 0) ? Math.round(data.spo2) : null;
}
// Respiration Rate
if (data.respiratory_rate !== undefined) {
machVitals.respiratory_rate = (data.respiratory_rate != null && data.respiratory_rate > 0) ? Math.round(data.respiratory_rate) : null;
if (data.respiratory_rate !== undefined && data.respiratory_rate !== null) {
machVitals.respiratory_rate = (data.respiratory_rate > 0) ? Math.round(data.respiratory_rate) : null;
}
// NIBP
if (data.systolic_bp !== undefined) {
machVitals.systolic_bp = (data.systolic_bp != null && data.systolic_bp > 0) ? Math.round(data.systolic_bp) : null;
if (data.systolic_bp !== undefined && data.systolic_bp !== null) {
machVitals.systolic_bp = (data.systolic_bp > 0) ? Math.round(data.systolic_bp) : null;
}
if (data.diastolic_bp !== undefined) {
machVitals.diastolic_bp = (data.diastolic_bp != null && data.diastolic_bp > 0) ? Math.round(data.diastolic_bp) : null;
if (data.diastolic_bp !== undefined && data.diastolic_bp !== null) {
machVitals.diastolic_bp = (data.diastolic_bp > 0) ? Math.round(data.diastolic_bp) : null;
}
if (data.map_bp !== undefined) {
machVitals.map_bp = (data.map_bp != null && data.map_bp > 0) ? Math.round(data.map_bp) : null;
if (data.map_bp !== undefined && data.map_bp !== null) {
machVitals.map_bp = (data.map_bp > 0) ? Math.round(data.map_bp) : null;
}
// Temperature 1 (T1)
if (data.temperature !== undefined) {
machVitals.temperature = (data.temperature != null && data.temperature > 0) ? Number(data.temperature) : null;
if (data.temperature !== undefined && data.temperature !== null) {
machVitals.temperature = (data.temperature > 0) ? Number(data.temperature) : null;
}
// Temperature 2 (T2)
if (data.temperature2 !== undefined) {
machVitals.temperature2 = (data.temperature2 != null && data.temperature2 > 0) ? Number(data.temperature2) : null;
if (data.temperature2 !== undefined && data.temperature2 !== null) {
machVitals.temperature2 = (data.temperature2 > 0) ? Number(data.temperature2) : null;
}
// Temperature Difference (TD)
@@ -3111,16 +3244,25 @@
machVitals.temperature_diff = Number(data.temperature_diff);
} else if (machVitals.temperature != null && machVitals.temperature2 != null) {
machVitals.temperature_diff = Math.round(Math.abs(machVitals.temperature - machVitals.temperature2) * 10) / 10;
} else {
machVitals.temperature_diff = null;
}
if (data.patient_name && data.patient_name !== 'Unknown') machVitals.patient_name = data.patient_name;
if (data.name && data.name !== 'Unknown') machVitals.patient_name = data.name;
if (data.mrn && data.mrn !== 'UNKNOWN') machVitals.patient_id = data.mrn;
if (data.patient_id && data.patient_id !== 'UNKNOWN') machVitals.patient_id = data.patient_id;
if (data.patient_name && data.patient_name !== 'Unknown' && data.patient_name !== 'UNKNOWN') machVitals.patient_name = data.patient_name;
if (data.name && data.name !== 'Unknown' && data.name !== 'UNKNOWN') machVitals.patient_name = data.name;
if (data.mrn && data.mrn !== 'UNKNOWN' && data.mrn !== 'UNASSIGNED') machVitals.patient_id = data.mrn;
if (data.patient_id && data.patient_id !== 'UNKNOWN' && data.patient_id !== 'UNASSIGNED') machVitals.patient_id = data.patient_id;
if (data.bed_id) machVitals.bed_id = data.bed_id;
if (data.device_id) machVitals.device_id = data.device_id;
if (data.patient_type) machVitals.patient_type = data.patient_type;
// Ensure top machine pills reflect current patient directly from machine
const elName = document.getElementById('machPatientName');
if (elName) elName.textContent = machVitals.patient_name ? machVitals.patient_name.toUpperCase() : '--';
const elMrn = document.getElementById('machMrnPill');
if (elMrn) elMrn.textContent = machVitals.patient_id ? `MRN: ${machVitals.patient_id}` : 'MRN: --';
const elBed = document.getElementById('machBedPill');
if (elBed) elBed.textContent = machVitals.bed_id ? `BED ${machVitals.bed_id}` : 'BED --';
const elCat = document.getElementById('machCategoryPill');
if (elCat && machVitals.patient_type) elCat.textContent = machVitals.patient_type.toUpperCase();
updateMachineDisplay();
}
@@ -3638,6 +3780,22 @@
function selectDevice(devId) {
activeDeviceId = devId;
setViewMode('single');
if (devId && connectedDevices[devId]) {
const dev = connectedDevices[devId];
machVitals.patient_name = (dev.patient_name && dev.patient_name !== 'Unknown' && dev.patient_name !== 'UNKNOWN') ? dev.patient_name : (dev.name && dev.name !== 'Unknown' && dev.name !== 'UNKNOWN' ? dev.name : null);
machVitals.patient_id = (dev.patient_id && dev.patient_id !== 'UNKNOWN' && dev.patient_id !== 'UNASSIGNED') ? dev.patient_id : ((dev.mrn && dev.mrn !== 'UNKNOWN' && dev.mrn !== 'UNASSIGNED') ? dev.mrn : null);
machVitals.bed_id = dev.bed_id || dev.bedLabel || null;
machVitals.device_id = devId;
const elName = document.getElementById('machPatientName');
if (elName) elName.textContent = machVitals.patient_name ? machVitals.patient_name.toUpperCase() : '--';
const elMrn = document.getElementById('machMrnPill');
if (elMrn) elMrn.textContent = machVitals.patient_id ? `MRN: ${machVitals.patient_id}` : 'MRN: --';
const elBed = document.getElementById('machBedPill');
if (elBed) elBed.textContent = machVitals.bed_id ? `BED ${machVitals.bed_id}` : 'BED --';
applyVitalsToMachine(dev);
}
}
function updateDeviceTabs() {
@@ -3865,6 +4023,22 @@
bed_id: data.bed_id,
bedLabel: data.bed_id || connectedDevices[devId]?.bedLabel
};
if (activeDeviceId === devId || !activeDeviceId) {
machVitals.patient_name = (data.patient_name && data.patient_name !== 'Unknown' && data.patient_name !== 'UNKNOWN') ? data.patient_name : (data.name && data.name !== 'Unknown' && data.name !== 'UNKNOWN' ? data.name : null);
machVitals.patient_id = (data.mrn && data.mrn !== 'UNKNOWN' && data.mrn !== 'UNASSIGNED') ? data.mrn : ((data.patient_id && data.patient_id !== 'UNKNOWN' && data.patient_id !== 'UNASSIGNED') ? data.patient_id : null);
machVitals.bed_id = data.bed_id || null;
machVitals.device_id = devId;
if (data.patient_type) machVitals.patient_type = data.patient_type;
const elName = document.getElementById('machPatientName');
if (elName) elName.textContent = machVitals.patient_name ? machVitals.patient_name.toUpperCase() : '--';
const elMrn = document.getElementById('machMrnPill');
if (elMrn) elMrn.textContent = machVitals.patient_id ? `MRN: ${machVitals.patient_id}` : 'MRN: --';
const elBed = document.getElementById('machBedPill');
if (elBed) elBed.textContent = machVitals.bed_id ? `BED ${machVitals.bed_id}` : 'BED --';
const elCat = document.getElementById('machCategoryPill');
if (elCat && machVitals.patient_type) elCat.textContent = machVitals.patient_type.toUpperCase();
}
updateDeviceTabs();
if (hasTargetFilter) {
if (matchesTargetPatient(connectedDevices[devId], devId)) {
@@ -3929,16 +4103,13 @@
};
// Real-time sync with Contec Machine Display (Strictly live telemetry, zero fake values)
if (devId) {
const isMatch = !hasTargetFilter || matchesTargetPatient(connectedDevices[devId], devId) ||
(devId.includes('192.168.100.106') && (targetMrn === '09091102' || (targetName && targetName.toLowerCase().includes('shahil'))));
if (isMatch) {
applyVitalsToMachine(data);
}
}
const isTargetMatch = !hasTargetFilter || matchesTargetPatient(connectedDevices[devId], devId);
if (devId && isTargetMatch) {
if (!activeDeviceId) activeDeviceId = devId;
applyVitalsToMachine(connectedDevices[devId]);
}
if (isTargetMatch) {
if (!activeDeviceId || hasTargetFilter) {
activeDeviceId = devId;
@@ -3949,21 +4120,23 @@
if (isTargetMatch && hasTargetFilter) {
// Target patient matched — force single view and render immediately
// (don't wait for fetchInitialDevicesAndPatients async race)
if (viewMode !== 'single') setViewMode('single');
renderSingleDevice(connectedDevices[devId]);
} else if (hasTargetFilter && !isTargetMatch) {
// Incoming data is for ANOTHER patient/device - do NOT show it on target patient view!
if (viewMode === 'single') {
renderSingleDevice(null);
}
} else if (viewMode === 'grid') {
renderMultiBedGrid();
} else if (viewMode === 'single') {
if (isTargetMatch && activeDeviceId === devId) {
if (activeDeviceId === devId) {
renderSingleDevice(connectedDevices[devId]);
} else if (hasTargetFilter && !activeDeviceId) {
renderSingleDevice(null);
}
}
}
function renderSingleDevice(data) {
function renderSingleDevice(dev) {
const waitingState = document.getElementById('singleTargetWaitingState');
const patientBanner = document.getElementById('singlePatientBanner');
const vitalsGrid = document.getElementById('singleVitalsGrid');
@@ -3972,67 +4145,52 @@
// Always ensure the ICU Live Monitor is visible in single view
if (icuMon) icuMon.style.display = 'flex';
if (!data && hasTargetFilter) {
data = {
patient_name: targetName || machVitals.patient_name || 'Shahilfaah',
name: targetName || machVitals.patient_name || 'Shahilfaah',
patient_id: targetMrn || machVitals.patient_id || '09091102',
mrn: targetMrn || machVitals.patient_id || '09091102',
device_id: machVitals.device_id || 'CMS8500_192.168.100.106',
bed_id: machVitals.bed_id || 'Bed 01',
bedLabel: machVitals.bed_id || 'Bed 01',
heart_rate: null,
spo2: null,
systolic_bp: null,
diastolic_bp: null,
map_bp: null,
respiratory_rate: null,
temperature: null
};
let data = dev;
if (hasTargetFilter) {
if (data && !matchesTargetPatient(data, data.device_id)) {
data = null;
}
if (!data) {
const matchDev = Object.values(connectedDevices).find(d => matchesTargetPatient(d, d.device_id));
if (matchDev) {
data = matchDev;
}
}
}
if (hasTargetFilter && !isMinimal) {
const isMatch = data && matchesTargetPatient(data, data.device_id);
if (!isMatch) {
if (waitingState) {
if (hasTargetFilter && !data) {
// Target patient has NO connected machine or matching readings
clearMachineVitals();
// Clear standard vital cards
updateVitalCard('hr', null, 'heart_rate');
updateVitalCard('spo2', null, 'spo2');
updateVitalCard('bp', null, 'systolic_bp');
updateVitalCard('rr', null, 'respiratory_rate');
updateVitalCard('temp', null, 'temperature');
updateVitalCard('map', null, 'map_bp');
if (!isMinimal && waitingState) {
waitingState.style.display = 'flex';
const badge = document.getElementById('targetWaitingMrnBadge');
if (badge) badge.innerHTML = getTargetPatientBadge();
}
} else {
if (waitingState) waitingState.style.display = 'none';
if (!isMinimal) {
if (patientBanner) patientBanner.style.display = 'none';
if (vitalsGrid) vitalsGrid.style.display = 'none';
}
} else {
if (waitingState) waitingState.style.display = 'none';
return;
}
if (waitingState) waitingState.style.display = 'none';
if (!isMinimal) {
if (patientBanner) patientBanner.style.display = 'flex';
if (vitalsGrid) vitalsGrid.style.display = 'grid';
}
if (!data) return;
// Sync with ICU Live Monitor
if (data) {
if (data.heart_rate != null) icuVitals.heart_rate = data.heart_rate;
if (data.spo2 != null) icuVitals.spo2 = data.spo2;
if (data.systolic_bp != null) icuVitals.systolic_bp = data.systolic_bp;
if (data.diastolic_bp != null) icuVitals.diastolic_bp = data.diastolic_bp;
if (data.map_bp != null) icuVitals.map_bp = data.map_bp;
if (data.respiratory_rate != null) icuVitals.respiratory_rate = data.respiratory_rate;
if (data.temperature != null) icuVitals.temperature = data.temperature;
if (data.patient_name && data.patient_name !== 'Unknown') icuVitals.patient_name = data.patient_name;
if (data.mrn || data.patient_id) icuVitals.patient_id = data.mrn || data.patient_id;
if (data.device_id) icuVitals.device_id = data.device_id;
if (data.bed_id || data.bedLabel) icuVitals.bed_id = data.bed_id || data.bedLabel;
document.getElementById('icuPatientNameText').textContent = icuVitals.patient_name;
document.getElementById('icuAvatar').textContent = icuVitals.patient_name.charAt(0).toUpperCase();
document.getElementById('icuMrnTag').textContent = `MRN: ${icuVitals.patient_id}`;
document.getElementById('icuBedTag').textContent = `BED: ${icuVitals.bed_id}`;
document.getElementById('icuDevTag').textContent = `DEV: ${icuVitals.device_id}`;
updateIcuMonitorReadouts();
}
// Sync with Contec Machine Screen
applyVitalsToMachine(data);
// 1. Update Patient Banner
const hasAssigned = data.patient_name && data.patient_name !== 'Unknown' && data.patient_name !== 'UNKNOWN' && data.patient_name !== 'Unassigned Patient';
@@ -4182,6 +4340,17 @@
};
closeAssignPatientModal();
if (activeDeviceId === device_id || !activeDeviceId) {
machVitals.patient_name = name;
machVitals.patient_id = mrn;
machVitals.bed_id = bed_id;
const elName = document.getElementById('machPatientName');
if (elName) elName.textContent = name ? name.toUpperCase() : '--';
const elMrn = document.getElementById('machMrnPill');
if (elMrn) elMrn.textContent = mrn ? `MRN: ${mrn}` : 'MRN: --';
const elBed = document.getElementById('machBedPill');
if (elBed) elBed.textContent = bed_id ? `BED ${bed_id}` : 'BED --';
}
updateDeviceTabs();
if (viewMode === 'grid') renderMultiBedGrid();
else if (viewMode === 'single' && activeDeviceId === device_id) {
@@ -4343,12 +4512,18 @@
const updateEl = document.getElementById(`update-${cardKey}`);
const cardEl = document.getElementById(`card-${cardKey}`);
if (!valueEl) return;
if (value === null || value === undefined) {
valueEl.innerHTML = '---';
valueEl.className = 'vital-value no-data';
if (statusEl) {
statusEl.textContent = 'No Data';
statusEl.className = 'status-badge offline';
}
if (cardEl) {
cardEl.classList.remove('has-value');
}
return;
}