feature: Explainable Alerts
This commit is contained in:
@@ -29,6 +29,20 @@ describe('AlertCard', () => {
|
||||
expect(wrapper.text()).toContain('Sepsis Warning (SIRS — Legacy)')
|
||||
})
|
||||
|
||||
it('showsNarrativeSummaryWhenExplanationPresent', () => {
|
||||
const wrapper = mount(AlertCard, {
|
||||
props: {
|
||||
alert: {
|
||||
...openAlert,
|
||||
explanation: {
|
||||
narrativeSummary: 'NEWS2 8 — respiratory rate (+3), SpO2 (+2).',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(wrapper.text()).toContain('NEWS2 8 — respiratory rate (+3), SpO2 (+2).')
|
||||
})
|
||||
|
||||
it('acknowledgeButtonEmitsEvent', async () => {
|
||||
const wrapper = mount(AlertCard, { props: { alert: openAlert } })
|
||||
const ackButton = wrapper.findAll('button').find(b => b.text() === 'Acknowledge')
|
||||
|
||||
@@ -50,4 +50,41 @@ describe('AlertReasoning', () => {
|
||||
expect(wrapper.text()).toContain('CustomUnknown')
|
||||
expect(wrapper.text()).toContain('Something unusual happened')
|
||||
})
|
||||
|
||||
it('showsStructuredExplanationWhenPresent', () => {
|
||||
const news2Alert = {
|
||||
id: 'alert-4',
|
||||
alertType: 'News2Emergency',
|
||||
severity: 'Critical',
|
||||
details: 'NEWS2 score 8 (HIGH): RESP_RATE=3, SPO2=2',
|
||||
triggeredAt: '2026-06-19T14:00:00Z',
|
||||
explanation: {
|
||||
narrativeSummary: 'NEWS2 8 — respiratory rate (+3), SpO2 (+2).',
|
||||
scoreContributors: [
|
||||
{ parameter: 'Respiratory Rate', points: 3, rawValue: '28', normalRange: '12-20' },
|
||||
{ parameter: 'SpO2', points: 2, rawValue: '92%', normalRange: '96-100%' },
|
||||
],
|
||||
trend: {
|
||||
parameter: 'Respiratory Rate',
|
||||
percentChange: 37,
|
||||
duration: '02:00:00',
|
||||
direction: 'Increasing',
|
||||
},
|
||||
medicationContext: {
|
||||
drugName: 'acetaminophen',
|
||||
dose: '1000mg',
|
||||
route: 'PO',
|
||||
administeredAt: '2026-06-19T13:30:00Z',
|
||||
relevanceNote: 'Antipyretic — may affect temperature trend',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const wrapper = mount(AlertReasoning, { props: { alert: news2Alert } })
|
||||
expect(wrapper.text()).toContain('NEWS2 8 — respiratory rate (+3), SpO2 (+2).')
|
||||
expect(wrapper.text()).toContain('Respiratory Rate')
|
||||
expect(wrapper.text()).toContain('Correlated medication')
|
||||
expect(wrapper.text()).toContain('acetaminophen')
|
||||
expect(wrapper.text()).toContain('increasing 37% over 2 hours')
|
||||
})
|
||||
})
|
||||
@@ -22,11 +22,14 @@ describe('CriticalAlertBanner', () => {
|
||||
severity: 'Critical',
|
||||
status: 'Open',
|
||||
details: 'SOFA delta >= 2',
|
||||
explanation: {
|
||||
narrativeSummary: 'SOFA 6 — respiratory (+2), renal (+2).',
|
||||
},
|
||||
}]
|
||||
|
||||
const wrapper = mount(CriticalAlertBanner)
|
||||
expect(wrapper.text()).toContain('Sepsis Alert (SOFA)')
|
||||
expect(wrapper.text()).toContain('SOFA delta >= 2')
|
||||
expect(wrapper.text()).toContain('SOFA 6 — respiratory (+2), renal (+2).')
|
||||
|
||||
const dismissButton = wrapper.findAll('button').find(button => button.text() === 'Dismiss')
|
||||
await dismissButton.trigger('click')
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import {
|
||||
alertSummaryText,
|
||||
elevatedContributors,
|
||||
formatDuration,
|
||||
formatMedicationContext,
|
||||
formatTrendLine,
|
||||
hasExplanation,
|
||||
} from '@/composables/alertExplanation'
|
||||
|
||||
describe('alertExplanation', () => {
|
||||
it('prefersNarrativeSummaryOverDetails', () => {
|
||||
const alert = {
|
||||
details: 'NEWS2 score 8 (HIGH): ...',
|
||||
explanation: {
|
||||
narrativeSummary: 'NEWS2 8 — respiratory rate (+3), SpO2 (+2).',
|
||||
},
|
||||
}
|
||||
expect(alertSummaryText(alert)).toBe('NEWS2 8 — respiratory rate (+3), SpO2 (+2).')
|
||||
})
|
||||
|
||||
it('fallsBackToDetailsWhenExplanationMissing', () => {
|
||||
expect(alertSummaryText({ details: 'Legacy details' })).toBe('Legacy details')
|
||||
expect(alertSummaryText({})).toBe('')
|
||||
})
|
||||
|
||||
it('filtersElevatedContributors', () => {
|
||||
const explanation = {
|
||||
scoreContributors: [
|
||||
{ parameter: 'Respiratory Rate', points: 3, rawValue: '28', normalRange: '12-20' },
|
||||
{ parameter: 'Heart Rate', points: 0, rawValue: '72', normalRange: '51-90' },
|
||||
],
|
||||
}
|
||||
expect(elevatedContributors(explanation)).toHaveLength(1)
|
||||
expect(elevatedContributors(explanation)[0].parameter).toBe('Respiratory Rate')
|
||||
})
|
||||
|
||||
it('formatsTrendAndMedicationContext', () => {
|
||||
expect(formatDuration('02:00:00')).toBe('2 hours')
|
||||
expect(formatTrendLine({
|
||||
parameter: 'Respiratory Rate',
|
||||
percentChange: 37,
|
||||
duration: '02:00:00',
|
||||
direction: 'Increasing',
|
||||
})).toContain('Respiratory Rate')
|
||||
expect(formatMedicationContext({
|
||||
drugName: 'acetaminophen',
|
||||
dose: '1000mg',
|
||||
route: 'PO',
|
||||
administeredAt: '2026-06-22T10:15:00Z',
|
||||
relevanceNote: 'Antipyretic — may affect temperature trend',
|
||||
})).toContain('acetaminophen')
|
||||
})
|
||||
|
||||
it('detectsStructuredExplanation', () => {
|
||||
expect(hasExplanation({ explanation: { narrativeSummary: 'x' } })).toBe(true)
|
||||
expect(hasExplanation({ details: 'only details' })).toBe(false)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user