feature: Clinical Review Mode: Charts, Replay Controls & Alert Reasoning
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import Badge from '@/components/ui/Badge.vue'
|
||||
import Card from '@/components/ui/Card.vue'
|
||||
|
||||
const props = defineProps({
|
||||
alert: { type: Object, required: true },
|
||||
medications: { type: Array, default: () => [] },
|
||||
})
|
||||
|
||||
const CORRELATION_WINDOW_MS = 90 * 60 * 1000
|
||||
|
||||
const reasoningMap = {
|
||||
WarningHeartRate: { label: 'Heart Rate Warning', explain: (a) => `Heart rate ${extractValue(a)} is in the warning range (91–110 bpm or 41–50 bpm).` },
|
||||
WarningSystolicBp: { label: 'Systolic BP Warning', explain: (a) => `Systolic BP ${extractValue(a)} is in the warning range (101–110 mmHg).` },
|
||||
WarningTempC: { label: 'Temperature Warning', explain: (a) => `Temperature ${extractValue(a)} is in the warning range.` },
|
||||
SepsisWarning: { label: 'SIRS / Sepsis Alert', explain: () => '≥2 of 4 SIRS criteria met: temperature, heart rate, respiratory rate, WBC.' },
|
||||
QsofaWarning: { label: 'qSOFA Alert', explain: () => '≥2 of 3 qSOFA criteria met: RR ≥22, SBP ≤100, altered mentation (AVPU ≥1).' },
|
||||
News2Warning: { label: 'NEWS2 Medium Risk', explain: () => 'NEWS2 composite score 5–6 indicating medium clinical risk.' },
|
||||
News2Emergency: { label: 'NEWS2 High Risk', explain: () => 'NEWS2 composite score ≥7 or any single parameter scored 3.' },
|
||||
RapidDeterioration: { label: 'Rapid Deterioration', explain: () => 'Vital sign trajectory shows rapid change within the sliding window.' },
|
||||
}
|
||||
|
||||
const recentMedications = computed(() => {
|
||||
if (!props.medications.length || !props.alert.triggeredAt) return []
|
||||
const alertTime = new Date(props.alert.triggeredAt).getTime()
|
||||
return props.medications.filter(m => {
|
||||
const administeredAt = new Date(m.administeredAt).getTime()
|
||||
const delta = alertTime - administeredAt
|
||||
return delta >= 0 && delta <= CORRELATION_WINDOW_MS
|
||||
})
|
||||
})
|
||||
|
||||
function extractValue(alert) {
|
||||
const match = alert.details?.match(/(\d+\.?\d*)/)
|
||||
return match ? match[1] : '—'
|
||||
}
|
||||
|
||||
function formatMed(med) {
|
||||
return `${med.drugName} ${med.dose}${med.doseUnit} (${med.route})`
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card>
|
||||
<div class="space-y-4">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<Badge :variant="alert.severity === 'Critical' ? 'critical' : 'warning'">
|
||||
{{ alert.severity }}
|
||||
</Badge>
|
||||
<h3 class="font-medium dark:text-white">
|
||||
{{ reasoningMap[alert.alertType]?.label ?? alert.alertType }}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ reasoningMap[alert.alertType]?.explain(alert) ?? alert.details }}
|
||||
</p>
|
||||
|
||||
<div
|
||||
v-if="recentMedications.length"
|
||||
class="rounded bg-amber-50 p-4 text-sm text-amber-900 dark:bg-amber-950/40 dark:text-amber-200"
|
||||
>
|
||||
<p class="mb-2 font-medium">Recent medications (within 90 min)</p>
|
||||
<ul class="space-y-2">
|
||||
<li v-for="med in recentMedications" :key="med.id">
|
||||
{{ formatMed(med) }} — {{ new Date(med.administeredAt).toLocaleString() }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div v-if="alert.details" class="rounded bg-gray-50 p-4 text-xs font-mono text-gray-700 dark:bg-gray-800 dark:text-gray-300">
|
||||
{{ alert.details }}
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-gray-500 dark:text-gray-500">
|
||||
Triggered at {{ new Date(alert.triggeredAt).toLocaleString() }}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
Reference in New Issue
Block a user