feat: multi-point patient verification (MRN, Name, DOB) via URL parameters

This commit is contained in:
2026-09-15 10:07:14 +05:30
parent 5773656b01
commit 60da868a3a
4 changed files with 266 additions and 150 deletions
+16 -10
View File
@@ -112,26 +112,30 @@ def get_latest_readings(
if device_id:
query = query.filter(VitalReading.device_id == device_id)
patient_filters = []
patient_criteria = []
if mrn:
mrn_clean = mrn.strip()
patient_filters.append(Patient.mrn == mrn_clean)
patient_filters.append(Patient.patient_id == mrn_clean)
patient_criteria.append(or_(Patient.mrn == mrn_clean, Patient.patient_id == mrn_clean))
if name:
name_clean = name.strip()
patient_filters.append(Patient.name.ilike(f"%{name_clean}%"))
patient_criteria.append(Patient.name.ilike(f"%{name_clean}%"))
if dob:
dob_clean = dob.strip()
patient_filters.append(Patient.dob.ilike(f"%{dob_clean}%"))
dob_clean = dob.strip().replace("-", "").replace("/", "")
patient_criteria.append(or_(
Patient.dob.ilike(f"%{dob.strip()}%"),
Patient.dob.ilike(f"%{dob_clean}%"),
Patient.dob.ilike(f"%{dob.strip()[:4]}%") if len(dob.strip()) >= 4 else False
))
if patient_filters:
matching_patients = db.query(Patient).filter(or_(*patient_filters)).all()
if patient_criteria:
from sqlalchemy import and_
matching_patients = db.query(Patient).filter(and_(*patient_criteria)).all()
if not matching_patients:
return []
matching_dev_ids = [p.device_id for p in matching_patients if p.device_id]
matching_mrns = [p.mrn for p in matching_patients if p.mrn] + [p.patient_id for p in matching_patients if p.patient_id]
conds = []
if mrn:
conds.append(VitalReading.patient_id == mrn.strip())
if matching_mrns:
conds.append(VitalReading.patient_id.in_(matching_mrns))
if matching_dev_ids:
@@ -139,6 +143,8 @@ def get_latest_readings(
if conds:
query = query.filter(or_(*conds))
else:
return []
readings = query.order_by(VitalReading.timestamp.desc()).limit(limit).all()
return readings
+117 -65
View File
@@ -1425,13 +1425,14 @@
<!-- Single Machine Detailed View Wrapper -->
<div id="singleViewWrapper" style="display: none; flex-direction: column; flex: 1;">
<!-- Targeted Patient MRN Waiting State (shown when filtered patient hasn't connected yet) -->
<div id="singleTargetWaitingState" style="display: none; flex-direction: column; align-items: center; justify-content: center; padding: 60px 20px; text-align: center; background: var(--bg-card); border: 1px dashed rgba(6, 182, 212, 0.4); border-radius: 16px; margin: 20px 0;">
<!-- Targeted Patient Demographics Waiting State (shown when filtered patient hasn't connected yet) -->
<div id="singleTargetWaitingState" style="display: none; flex-direction: column; align-items: center; justify-content: center; padding: 50px 20px; text-align: center; background: var(--bg-card); border: 1px dashed rgba(6, 182, 212, 0.4); border-radius: 16px; margin: 20px 0;">
<div style="font-size: 40px; margin-bottom: 12px;">⏳</div>
<div style="font-size: 19px; font-weight: 700; color: var(--text-primary);">Waiting for Contec Monitor Data</div>
<div style="font-size: 15px; color: var(--spo2-color); font-weight: 600; margin-top: 6px;" id="targetWaitingMrnBadge">Patient MRN: ---</div>
<div style="font-size: 13px; color: var(--text-secondary); max-width: 520px; margin-top: 10px; line-height: 1.5;">
Filtered to show exclusively this patient's teleconsult telemetry. Once the Contec CMS7000PLUS monitor connects or transmits data, live waveforms and vitals will stream automatically.
<div style="font-size: 13px; color: var(--text-secondary); margin-top: 6px;">Verifying incoming monitor telemetry against required teleconsult patient:</div>
<div style="margin-top: 12px; display: flex; gap: 8px; justify-content: center; flex-wrap: wrap;" id="targetWaitingMrnBadge">Patient Demographics: ---</div>
<div style="font-size: 13px; color: var(--text-secondary); max-width: 540px; margin-top: 14px; line-height: 1.5;">
Live waveforms and telemetry stream automatically once the Contec CMS7000PLUS monitor connects and transmits telemetry matching all requested demographics (MRN, Name, DOB).
</div>
<div style="margin-top: 18px; display: inline-flex; align-items: center; gap: 8px; padding: 6px 14px; background: rgba(6, 182, 212, 0.1); border: 1px solid rgba(6, 182, 212, 0.25); border-radius: 20px; font-size: 12px; color: var(--spo2-color);">
<span style="width: 8px; height: 8px; border-radius: 50%; background: var(--spo2-color); display: inline-block;"></span> Listening for live connection...
@@ -1774,65 +1775,110 @@
function getTargetPatientBadge() {
const items = [];
if (targetMrn) items.push(`MRN: <strong>${targetMrn}</strong>`);
if (targetName) items.push(`Name: <strong>${targetName}</strong>`);
if (targetDob) items.push(`DOB: <strong>${targetDob}</strong>`);
if (targetGender) items.push(`Gender: <strong>${targetGender}</strong>`);
return items.length > 0 ? items.join(' &nbsp;•&nbsp; ') : 'Target Patient';
if (targetMrn) items.push(`<span class="patient-mrn-badge" style="padding: 4px 10px; font-size: 12px;">📋 MRN: <strong>${targetMrn}</strong></span>`);
if (targetName) items.push(`<span class="patient-name-badge" style="padding: 4px 10px; background: rgba(14, 165, 233, 0.15); border: 1px solid rgba(14, 165, 233, 0.3); border-radius: 6px; font-size: 12px; color: #38bdf8;">👤 Name: <strong>${targetName}</strong></span>`);
if (targetDob) items.push(`<span class="patient-dob-badge" style="padding: 4px 10px; font-size: 12px;">🎂 DOB: <strong>${targetDob}</strong></span>`);
if (targetGender) items.push(`<span class="patient-gender-badge" style="padding: 4px 10px; font-size: 12px;">⚧ Gender: <strong>${targetGender}</strong></span>`);
return items.length > 0 ? items.join('') : '<span>Target Patient</span>';
}
// Helper: Extract 4-digit year and digits for robust DOB matching
function normalizeDob(str) {
if (!str) return { raw: '', digits: '', year: null };
const clean = String(str).trim();
const yearMatch = clean.match(/\b(19\d\d|20[0-2]\d)\b/);
const digits = clean.replace(/\D/g, '');
return {
raw: clean.toLowerCase(),
digits: digits,
year: yearMatch ? yearMatch[1] : null
};
}
// Helper: Extract individual words for flexible name matching
function normalizeNameTokens(str) {
if (!str) return [];
return String(str)
.toLowerCase()
.replace(/[^a-z0-9\s]/g, '')
.trim()
.split(/\s+/)
.filter(w => w.length >= 2);
}
// Check verification for each requested parameter (MRN, Name, DOB, Gender)
function checkPatientVerification(dev) {
const res = {
mrnMatched: !targetMrn,
nameMatched: !targetName,
dobMatched: !targetDob,
genderMatched: !targetGender,
allMatched: false
};
if (!dev) return res;
const pMrn = String(dev?.mrn || dev?.patient_id || '').toLowerCase().trim();
const pName = String(dev?.patient_name || dev?.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))) {
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")
if (targetName) {
const tn = targetName.toLowerCase().trim();
const targetTokens = normalizeNameTokens(targetName);
const patientTokens = normalizeNameTokens(pName);
if (pName && (pName.includes(tn) || tn.includes(pName))) {
res.nameMatched = true;
} else if (targetTokens.length > 0 && targetTokens.some(t => patientTokens.includes(t))) {
res.nameMatched = true;
}
}
// 3. DOB verification (supports 1960, 1960-06-22, 22/06/1960, 19600622)
if (targetDob) {
const td = targetDob.toLowerCase().trim();
const tNorm = normalizeDob(targetDob);
const pNorm = normalizeDob(pDob);
if (pDob && (pDob.includes(td) || td.includes(pDob))) {
res.dobMatched = true;
} else if (tNorm.year && pNorm.year && tNorm.year === pNorm.year) {
res.dobMatched = true;
} else if (tNorm.digits && pNorm.digits && (tNorm.digits.includes(pNorm.digits) || pNorm.digits.includes(tNorm.digits))) {
res.dobMatched = true;
}
}
// 4. Gender verification
if (targetGender) {
const tg = targetGender.toLowerCase().trim();
if (pGender && (pGender.startsWith(tg[0]) || pGender.includes(tg))) {
res.genderMatched = true;
}
}
res.allMatched = res.mrnMatched && res.nameMatched && res.dobMatched && res.genderMatched;
return res;
}
// Helper function: Check if a device or patient matches the target demographics (MRN, Name, DOB, Gender)
function matchesTargetPatient(dev, devId) {
if (!hasTargetFilter) return true;
if (!dev && !devId) return false;
const pMrn = String(dev?.mrn || dev?.patient_id || '').toLowerCase().trim();
const pName = String(dev?.patient_name || dev?.name || '').toLowerCase().trim();
const pDob = String(dev?.patient_dob || dev?.dob || '').toLowerCase().trim();
const pGender = String(dev?.patient_gender || dev?.gender || '').toLowerCase().trim();
const pBed = String(dev?.bed_id || dev?.bedLabel || '').toLowerCase().trim();
const dId = String(devId || dev?.device_id || '').toLowerCase().trim();
// 1. If targetName is explicitly specified: must match patient name
if (targetName) {
const tn = targetName.toLowerCase().trim();
const nameMatch = pName && (pName.includes(tn) || tn.includes(pName));
if (!nameMatch) return false;
}
// 2. If targetDob is explicitly specified: must match patient DOB / birth year
if (targetDob) {
const td = targetDob.toLowerCase().trim();
const dobMatch = pDob && (pDob.includes(td) || td.includes(pDob));
if (!dobMatch) return false;
}
// 3. If targetGender is explicitly specified: must match gender
if (targetGender) {
const tg = targetGender.toLowerCase().trim();
const genderMatch = pGender && (pGender.startsWith(tg[0]) || pGender.includes(tg));
if (!genderMatch) return false;
}
// 4. If targetMrn is specified: strictly match patient MRN only
if (targetMrn) {
const tm = targetMrn.toLowerCase().trim();
let mrnMatch = false;
if (pMrn) {
if (pMrn === tm) mrnMatch = true;
// Also support numeric equivalence (e.g. "09091102" vs "9091102") with minimum length 3
const numT = parseInt(tm, 10);
const numMrn = parseInt(pMrn, 10);
if (!isNaN(numT) && !isNaN(numMrn) && numT === numMrn && tm.length >= 3 && pMrn.length >= 3) {
mrnMatch = true;
}
}
if (!mrnMatch) return false;
}
return true;
return checkPatientVerification(dev).allMatched;
}
// Backward-compatible alias
@@ -2121,8 +2167,13 @@
}
// 2. Load latest readings to backfill vitals for each device
const backfillUrl = hasTargetFilter && targetMrn
? `${API_BASE}/latest-readings?limit=50&mrn=${encodeURIComponent(targetMrn)}`
const backfillParams = new URLSearchParams();
backfillParams.set('limit', '50');
if (targetMrn) backfillParams.set('mrn', targetMrn);
if (targetName) backfillParams.set('name', targetName);
if (targetDob) backfillParams.set('dob', targetDob);
const backfillUrl = hasTargetFilter
? `${API_BASE}/latest-readings?${backfillParams.toString()}`
: `${API_BASE}/latest-readings?limit=100`;
const readResp = await fetch(backfillUrl);
if (readResp.ok) {
@@ -2560,7 +2611,7 @@
if (waitingState) {
waitingState.style.display = 'flex';
const badge = document.getElementById('targetWaitingMrnBadge');
if (badge) badge.innerHTML = `Target Patient: ${getTargetPatientBadge()}`;
if (badge) badge.innerHTML = getTargetPatientBadge();
}
if (patientBanner) patientBanner.style.display = 'none';
if (vitalsGrid) vitalsGrid.style.display = 'none';
@@ -2584,10 +2635,11 @@
const bannerEl = document.getElementById('singlePatientBanner');
if (bannerEl) {
document.getElementById('bannerPatientName').textContent = pName;
document.getElementById('bannerMrn').textContent = `MRN: ${pMrn}`;
document.getElementById('bannerGender').textContent = `Gender: ${pGender}`;
document.getElementById('bannerDob').textContent = `DOB: ${pDob}`;
const verifiedTag = '<span style="display:inline-flex;align-items:center;gap:3px;padding:2px 6px;background:rgba(16,185,129,0.15);border:1px solid rgba(16,185,129,0.35);border-radius:4px;color:#10b981;font-size:10px;font-weight:700;margin-left:6px;">✓ Verified</span>';
document.getElementById('bannerPatientName').innerHTML = `${pName} ${targetName ? verifiedTag : ''}`;
document.getElementById('bannerMrn').innerHTML = `MRN: ${pMrn} ${targetMrn ? verifiedTag : ''}`;
document.getElementById('bannerGender').innerHTML = `Gender: ${pGender} ${targetGender ? verifiedTag : ''}`;
document.getElementById('bannerDob').innerHTML = `DOB: ${pDob} ${targetDob ? verifiedTag : ''}`;
document.getElementById('bannerBed').textContent = `Location: ${pBed}`;
}
+16 -10
View File
@@ -112,26 +112,30 @@ def get_latest_readings(
if device_id:
query = query.filter(VitalReading.device_id == device_id)
patient_filters = []
patient_criteria = []
if mrn:
mrn_clean = mrn.strip()
patient_filters.append(Patient.mrn == mrn_clean)
patient_filters.append(Patient.patient_id == mrn_clean)
patient_criteria.append(or_(Patient.mrn == mrn_clean, Patient.patient_id == mrn_clean))
if name:
name_clean = name.strip()
patient_filters.append(Patient.name.ilike(f"%{name_clean}%"))
patient_criteria.append(Patient.name.ilike(f"%{name_clean}%"))
if dob:
dob_clean = dob.strip()
patient_filters.append(Patient.dob.ilike(f"%{dob_clean}%"))
dob_clean = dob.strip().replace("-", "").replace("/", "")
patient_criteria.append(or_(
Patient.dob.ilike(f"%{dob.strip()}%"),
Patient.dob.ilike(f"%{dob_clean}%"),
Patient.dob.ilike(f"%{dob.strip()[:4]}%") if len(dob.strip()) >= 4 else False
))
if patient_filters:
matching_patients = db.query(Patient).filter(or_(*patient_filters)).all()
if patient_criteria:
from sqlalchemy import and_
matching_patients = db.query(Patient).filter(and_(*patient_criteria)).all()
if not matching_patients:
return []
matching_dev_ids = [p.device_id for p in matching_patients if p.device_id]
matching_mrns = [p.mrn for p in matching_patients if p.mrn] + [p.patient_id for p in matching_patients if p.patient_id]
conds = []
if mrn:
conds.append(VitalReading.patient_id == mrn.strip())
if matching_mrns:
conds.append(VitalReading.patient_id.in_(matching_mrns))
if matching_dev_ids:
@@ -139,6 +143,8 @@ def get_latest_readings(
if conds:
query = query.filter(or_(*conds))
else:
return []
readings = query.order_by(VitalReading.timestamp.desc()).limit(limit).all()
return readings
+117 -65
View File
@@ -1425,13 +1425,14 @@
<!-- Single Machine Detailed View Wrapper -->
<div id="singleViewWrapper" style="display: none; flex-direction: column; flex: 1;">
<!-- Targeted Patient MRN Waiting State (shown when filtered patient hasn't connected yet) -->
<div id="singleTargetWaitingState" style="display: none; flex-direction: column; align-items: center; justify-content: center; padding: 60px 20px; text-align: center; background: var(--bg-card); border: 1px dashed rgba(6, 182, 212, 0.4); border-radius: 16px; margin: 20px 0;">
<!-- Targeted Patient Demographics Waiting State (shown when filtered patient hasn't connected yet) -->
<div id="singleTargetWaitingState" style="display: none; flex-direction: column; align-items: center; justify-content: center; padding: 50px 20px; text-align: center; background: var(--bg-card); border: 1px dashed rgba(6, 182, 212, 0.4); border-radius: 16px; margin: 20px 0;">
<div style="font-size: 40px; margin-bottom: 12px;">⏳</div>
<div style="font-size: 19px; font-weight: 700; color: var(--text-primary);">Waiting for Contec Monitor Data</div>
<div style="font-size: 15px; color: var(--spo2-color); font-weight: 600; margin-top: 6px;" id="targetWaitingMrnBadge">Patient MRN: ---</div>
<div style="font-size: 13px; color: var(--text-secondary); max-width: 520px; margin-top: 10px; line-height: 1.5;">
Filtered to show exclusively this patient's teleconsult telemetry. Once the Contec CMS7000PLUS monitor connects or transmits data, live waveforms and vitals will stream automatically.
<div style="font-size: 13px; color: var(--text-secondary); margin-top: 6px;">Verifying incoming monitor telemetry against required teleconsult patient:</div>
<div style="margin-top: 12px; display: flex; gap: 8px; justify-content: center; flex-wrap: wrap;" id="targetWaitingMrnBadge">Patient Demographics: ---</div>
<div style="font-size: 13px; color: var(--text-secondary); max-width: 540px; margin-top: 14px; line-height: 1.5;">
Live waveforms and telemetry stream automatically once the Contec CMS7000PLUS monitor connects and transmits telemetry matching all requested demographics (MRN, Name, DOB).
</div>
<div style="margin-top: 18px; display: inline-flex; align-items: center; gap: 8px; padding: 6px 14px; background: rgba(6, 182, 212, 0.1); border: 1px solid rgba(6, 182, 212, 0.25); border-radius: 20px; font-size: 12px; color: var(--spo2-color);">
<span style="width: 8px; height: 8px; border-radius: 50%; background: var(--spo2-color); display: inline-block;"></span> Listening for live connection...
@@ -1774,65 +1775,110 @@
function getTargetPatientBadge() {
const items = [];
if (targetMrn) items.push(`MRN: <strong>${targetMrn}</strong>`);
if (targetName) items.push(`Name: <strong>${targetName}</strong>`);
if (targetDob) items.push(`DOB: <strong>${targetDob}</strong>`);
if (targetGender) items.push(`Gender: <strong>${targetGender}</strong>`);
return items.length > 0 ? items.join(' &nbsp;•&nbsp; ') : 'Target Patient';
if (targetMrn) items.push(`<span class="patient-mrn-badge" style="padding: 4px 10px; font-size: 12px;">📋 MRN: <strong>${targetMrn}</strong></span>`);
if (targetName) items.push(`<span class="patient-name-badge" style="padding: 4px 10px; background: rgba(14, 165, 233, 0.15); border: 1px solid rgba(14, 165, 233, 0.3); border-radius: 6px; font-size: 12px; color: #38bdf8;">👤 Name: <strong>${targetName}</strong></span>`);
if (targetDob) items.push(`<span class="patient-dob-badge" style="padding: 4px 10px; font-size: 12px;">🎂 DOB: <strong>${targetDob}</strong></span>`);
if (targetGender) items.push(`<span class="patient-gender-badge" style="padding: 4px 10px; font-size: 12px;">⚧ Gender: <strong>${targetGender}</strong></span>`);
return items.length > 0 ? items.join('') : '<span>Target Patient</span>';
}
// Helper: Extract 4-digit year and digits for robust DOB matching
function normalizeDob(str) {
if (!str) return { raw: '', digits: '', year: null };
const clean = String(str).trim();
const yearMatch = clean.match(/\b(19\d\d|20[0-2]\d)\b/);
const digits = clean.replace(/\D/g, '');
return {
raw: clean.toLowerCase(),
digits: digits,
year: yearMatch ? yearMatch[1] : null
};
}
// Helper: Extract individual words for flexible name matching
function normalizeNameTokens(str) {
if (!str) return [];
return String(str)
.toLowerCase()
.replace(/[^a-z0-9\s]/g, '')
.trim()
.split(/\s+/)
.filter(w => w.length >= 2);
}
// Check verification for each requested parameter (MRN, Name, DOB, Gender)
function checkPatientVerification(dev) {
const res = {
mrnMatched: !targetMrn,
nameMatched: !targetName,
dobMatched: !targetDob,
genderMatched: !targetGender,
allMatched: false
};
if (!dev) return res;
const pMrn = String(dev?.mrn || dev?.patient_id || '').toLowerCase().trim();
const pName = String(dev?.patient_name || dev?.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))) {
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")
if (targetName) {
const tn = targetName.toLowerCase().trim();
const targetTokens = normalizeNameTokens(targetName);
const patientTokens = normalizeNameTokens(pName);
if (pName && (pName.includes(tn) || tn.includes(pName))) {
res.nameMatched = true;
} else if (targetTokens.length > 0 && targetTokens.some(t => patientTokens.includes(t))) {
res.nameMatched = true;
}
}
// 3. DOB verification (supports 1960, 1960-06-22, 22/06/1960, 19600622)
if (targetDob) {
const td = targetDob.toLowerCase().trim();
const tNorm = normalizeDob(targetDob);
const pNorm = normalizeDob(pDob);
if (pDob && (pDob.includes(td) || td.includes(pDob))) {
res.dobMatched = true;
} else if (tNorm.year && pNorm.year && tNorm.year === pNorm.year) {
res.dobMatched = true;
} else if (tNorm.digits && pNorm.digits && (tNorm.digits.includes(pNorm.digits) || pNorm.digits.includes(tNorm.digits))) {
res.dobMatched = true;
}
}
// 4. Gender verification
if (targetGender) {
const tg = targetGender.toLowerCase().trim();
if (pGender && (pGender.startsWith(tg[0]) || pGender.includes(tg))) {
res.genderMatched = true;
}
}
res.allMatched = res.mrnMatched && res.nameMatched && res.dobMatched && res.genderMatched;
return res;
}
// Helper function: Check if a device or patient matches the target demographics (MRN, Name, DOB, Gender)
function matchesTargetPatient(dev, devId) {
if (!hasTargetFilter) return true;
if (!dev && !devId) return false;
const pMrn = String(dev?.mrn || dev?.patient_id || '').toLowerCase().trim();
const pName = String(dev?.patient_name || dev?.name || '').toLowerCase().trim();
const pDob = String(dev?.patient_dob || dev?.dob || '').toLowerCase().trim();
const pGender = String(dev?.patient_gender || dev?.gender || '').toLowerCase().trim();
const pBed = String(dev?.bed_id || dev?.bedLabel || '').toLowerCase().trim();
const dId = String(devId || dev?.device_id || '').toLowerCase().trim();
// 1. If targetName is explicitly specified: must match patient name
if (targetName) {
const tn = targetName.toLowerCase().trim();
const nameMatch = pName && (pName.includes(tn) || tn.includes(pName));
if (!nameMatch) return false;
}
// 2. If targetDob is explicitly specified: must match patient DOB / birth year
if (targetDob) {
const td = targetDob.toLowerCase().trim();
const dobMatch = pDob && (pDob.includes(td) || td.includes(pDob));
if (!dobMatch) return false;
}
// 3. If targetGender is explicitly specified: must match gender
if (targetGender) {
const tg = targetGender.toLowerCase().trim();
const genderMatch = pGender && (pGender.startsWith(tg[0]) || pGender.includes(tg));
if (!genderMatch) return false;
}
// 4. If targetMrn is specified: strictly match patient MRN only
if (targetMrn) {
const tm = targetMrn.toLowerCase().trim();
let mrnMatch = false;
if (pMrn) {
if (pMrn === tm) mrnMatch = true;
// Also support numeric equivalence (e.g. "09091102" vs "9091102") with minimum length 3
const numT = parseInt(tm, 10);
const numMrn = parseInt(pMrn, 10);
if (!isNaN(numT) && !isNaN(numMrn) && numT === numMrn && tm.length >= 3 && pMrn.length >= 3) {
mrnMatch = true;
}
}
if (!mrnMatch) return false;
}
return true;
return checkPatientVerification(dev).allMatched;
}
// Backward-compatible alias
@@ -2121,8 +2167,13 @@
}
// 2. Load latest readings to backfill vitals for each device
const backfillUrl = hasTargetFilter && targetMrn
? `${API_BASE}/latest-readings?limit=50&mrn=${encodeURIComponent(targetMrn)}`
const backfillParams = new URLSearchParams();
backfillParams.set('limit', '50');
if (targetMrn) backfillParams.set('mrn', targetMrn);
if (targetName) backfillParams.set('name', targetName);
if (targetDob) backfillParams.set('dob', targetDob);
const backfillUrl = hasTargetFilter
? `${API_BASE}/latest-readings?${backfillParams.toString()}`
: `${API_BASE}/latest-readings?limit=100`;
const readResp = await fetch(backfillUrl);
if (readResp.ok) {
@@ -2560,7 +2611,7 @@
if (waitingState) {
waitingState.style.display = 'flex';
const badge = document.getElementById('targetWaitingMrnBadge');
if (badge) badge.innerHTML = `Target Patient: ${getTargetPatientBadge()}`;
if (badge) badge.innerHTML = getTargetPatientBadge();
}
if (patientBanner) patientBanner.style.display = 'none';
if (vitalsGrid) vitalsGrid.style.display = 'none';
@@ -2584,10 +2635,11 @@
const bannerEl = document.getElementById('singlePatientBanner');
if (bannerEl) {
document.getElementById('bannerPatientName').textContent = pName;
document.getElementById('bannerMrn').textContent = `MRN: ${pMrn}`;
document.getElementById('bannerGender').textContent = `Gender: ${pGender}`;
document.getElementById('bannerDob').textContent = `DOB: ${pDob}`;
const verifiedTag = '<span style="display:inline-flex;align-items:center;gap:3px;padding:2px 6px;background:rgba(16,185,129,0.15);border:1px solid rgba(16,185,129,0.35);border-radius:4px;color:#10b981;font-size:10px;font-weight:700;margin-left:6px;">✓ Verified</span>';
document.getElementById('bannerPatientName').innerHTML = `${pName} ${targetName ? verifiedTag : ''}`;
document.getElementById('bannerMrn').innerHTML = `MRN: ${pMrn} ${targetMrn ? verifiedTag : ''}`;
document.getElementById('bannerGender').innerHTML = `Gender: ${pGender} ${targetGender ? verifiedTag : ''}`;
document.getElementById('bannerDob').innerHTML = `DOB: ${pDob} ${targetDob ? verifiedTag : ''}`;
document.getElementById('bannerBed').textContent = `Location: ${pBed}`;
}