Files
vigilcare-clinical/vigilcare-dashboard/src/components/alerts/AlertReasoning.vue
T

82 lines
3.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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 (91110 bpm or 4150 bpm).` },
WarningSystolicBp: { label: 'Systolic BP Warning', explain: (a) => `Systolic BP ${extractValue(a)} is in the warning range (101110 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 56 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>