feature: Sepsis Engine Refactor: Remove SIRS, Rewire qSOFA + Bundle
Frontend: GCS Entry, SOFA Display, Sepsis UI Refactor
This commit is contained in:
@@ -26,7 +26,7 @@ describe('AlertCard', () => {
|
||||
it('showsAlertTypeAndSeverity', () => {
|
||||
const wrapper = mount(AlertCard, { props: { alert: openAlert } })
|
||||
expect(wrapper.text()).toContain('Critical')
|
||||
expect(wrapper.text()).toContain('SEPSIS_WARNING')
|
||||
expect(wrapper.text()).toContain('Sepsis Warning (SIRS — Legacy)')
|
||||
})
|
||||
|
||||
it('acknowledgeButtonEmitsEvent', async () => {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { alertTypeLabel, bundleTriggerLabel } from '@/api/normalize'
|
||||
|
||||
describe('AlertLabels', () => {
|
||||
it('sofaSepsisLabel', () => {
|
||||
expect(alertTypeLabel('SofaSepsis')).toBe('Sepsis Alert (SOFA)')
|
||||
})
|
||||
|
||||
it('legacySepsisLabel', () => {
|
||||
expect(alertTypeLabel('SepsisWarning')).toBe('Sepsis Warning (SIRS — Legacy)')
|
||||
})
|
||||
|
||||
it('qsofaScreenLabel', () => {
|
||||
expect(alertTypeLabel('QsofaScreen')).toBe('qSOFA Screen')
|
||||
})
|
||||
|
||||
it('gcsCriticalLabel', () => {
|
||||
expect(alertTypeLabel('GcsCritical')).toBe('GCS Critical (≤ 8)')
|
||||
})
|
||||
|
||||
it('bundleTriggerSofa', () => {
|
||||
expect(bundleTriggerLabel('SOFA_SEPSIS')).toBe('SOFA delta ≥ 2')
|
||||
})
|
||||
|
||||
it('bundleTriggerLegacy', () => {
|
||||
expect(bundleTriggerLabel('SEPSIS_WARNING')).toBe('SIRS criteria (Legacy)')
|
||||
})
|
||||
})
|
||||
@@ -11,11 +11,11 @@ const sepsisAlert = {
|
||||
triggeredAt: '2026-06-19T12:00:00Z',
|
||||
}
|
||||
|
||||
const qsofaAlert = {
|
||||
const qsofaScreenAlert = {
|
||||
id: 'alert-2',
|
||||
alertType: 'QsofaWarning',
|
||||
alertType: 'QsofaScreen',
|
||||
severity: 'Warning',
|
||||
details: 'qSOFA score elevated',
|
||||
details: 'qSOFA screen positive',
|
||||
triggeredAt: '2026-06-19T12:30:00Z',
|
||||
}
|
||||
|
||||
@@ -24,16 +24,18 @@ describe('AlertReasoning', () => {
|
||||
setActivePinia(createPinia())
|
||||
})
|
||||
|
||||
it('showsExplanationForSepsisWarning', () => {
|
||||
it('showsExplanationForLegacySepsisWarning', () => {
|
||||
const wrapper = mount(AlertReasoning, { props: { alert: sepsisAlert } })
|
||||
expect(wrapper.text()).toContain('SIRS / Sepsis Alert')
|
||||
expect(wrapper.text()).toContain('≥2 of 4 SIRS criteria met')
|
||||
expect(wrapper.text()).toContain('SIRS / Sepsis Alert (Legacy)')
|
||||
expect(wrapper.text()).toContain('Historical alert')
|
||||
expect(wrapper.text()).toContain('SOFA delta')
|
||||
})
|
||||
|
||||
it('showsExplanationForQsofa', () => {
|
||||
const wrapper = mount(AlertReasoning, { props: { alert: qsofaAlert } })
|
||||
expect(wrapper.text()).toContain('qSOFA Alert')
|
||||
expect(wrapper.text()).toContain('≥2 of 3 qSOFA criteria met')
|
||||
it('showsExplanationForQsofaScreen', () => {
|
||||
const wrapper = mount(AlertReasoning, { props: { alert: qsofaScreenAlert } })
|
||||
expect(wrapper.text()).toContain('qSOFA Screen')
|
||||
expect(wrapper.text()).toContain('Bedside screen positive')
|
||||
expect(wrapper.text()).toContain('Recommend ordering SOFA labs (PaO₂')
|
||||
})
|
||||
|
||||
it('showsRawDetailsForUnknownType', () => {
|
||||
@@ -48,4 +50,4 @@ describe('AlertReasoning', () => {
|
||||
expect(wrapper.text()).toContain('CustomUnknown')
|
||||
expect(wrapper.text()).toContain('Something unusual happened')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -28,8 +28,8 @@ describe('FeedbackSummary', () => {
|
||||
it('showsPerAlertTypeBreakdown', () => {
|
||||
const wrapper = mount(FeedbackSummary)
|
||||
|
||||
expect(wrapper.text()).toContain('SEPSIS_WARNING')
|
||||
expect(wrapper.text()).toContain('WARNING_HEART_RATE')
|
||||
expect(wrapper.text()).toContain('Sepsis Warning (SIRS — Legacy)')
|
||||
expect(wrapper.text()).toContain('Warning Heart Rate')
|
||||
expect(wrapper.text()).toContain('2 ratings')
|
||||
expect(wrapper.text()).toContain('1 ratings')
|
||||
})
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import GcsEntryForm from '@/components/patient/GcsEntryForm.vue'
|
||||
|
||||
describe('GcsEntryForm', () => {
|
||||
it('rendersThreeDropdowns', () => {
|
||||
const wrapper = mount(GcsEntryForm)
|
||||
expect(wrapper.findAll('select')).toHaveLength(3)
|
||||
expect(wrapper.text()).toContain('Eye (E)')
|
||||
expect(wrapper.text()).toContain('Verbal (V)')
|
||||
expect(wrapper.text()).toContain('Motor (M)')
|
||||
})
|
||||
|
||||
it('computesTotalCorrectly', async () => {
|
||||
const wrapper = mount(GcsEntryForm)
|
||||
await wrapper.findAll('select')[0].setValue(2)
|
||||
await wrapper.findAll('select')[1].setValue(3)
|
||||
await wrapper.findAll('select')[2].setValue(4)
|
||||
expect(wrapper.text()).toContain('GCS 9/15')
|
||||
})
|
||||
|
||||
it('showsClassification', async () => {
|
||||
const wrapper = mount(GcsEntryForm)
|
||||
await wrapper.findAll('select')[0].setValue(2)
|
||||
await wrapper.findAll('select')[1].setValue(3)
|
||||
await wrapper.findAll('select')[2].setValue(3)
|
||||
expect(wrapper.text()).toContain('Severe')
|
||||
|
||||
await wrapper.findAll('select')[0].setValue(4)
|
||||
await wrapper.findAll('select')[1].setValue(4)
|
||||
await wrapper.findAll('select')[2].setValue(4)
|
||||
expect(wrapper.text()).toContain('Moderate')
|
||||
|
||||
await wrapper.findAll('select')[0].setValue(4)
|
||||
await wrapper.findAll('select')[1].setValue(5)
|
||||
await wrapper.findAll('select')[2].setValue(6)
|
||||
expect(wrapper.text()).toContain('Mild')
|
||||
})
|
||||
|
||||
it('emitsSubmitWithComponents', async () => {
|
||||
const wrapper = mount(GcsEntryForm)
|
||||
await wrapper.findAll('select')[0].setValue(3)
|
||||
await wrapper.findAll('select')[1].setValue(4)
|
||||
await wrapper.findAll('select')[2].setValue(5)
|
||||
await wrapper.find('button').trigger('click')
|
||||
expect(wrapper.emitted('submit')?.[0]?.[0]).toEqual({ eye: 3, verbal: 4, motor: 5 })
|
||||
})
|
||||
|
||||
it('defaultsToNormalValues', () => {
|
||||
const wrapper = mount(GcsEntryForm)
|
||||
expect(wrapper.text()).toContain('GCS 15/15')
|
||||
expect(wrapper.text()).toContain('E4 V5 M6')
|
||||
})
|
||||
|
||||
it('usesResponsiveLayout', () => {
|
||||
const wrapper = mount(GcsEntryForm)
|
||||
const grid = wrapper.find('.grid')
|
||||
expect(grid.classes()).toContain('grid-cols-1')
|
||||
expect(grid.classes()).toContain('sm:grid-cols-3')
|
||||
expect(wrapper.find('button').classes()).toContain('w-full')
|
||||
expect(wrapper.find('button').classes()).toContain('sm:w-auto')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,49 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import ScoresPanel from '@/components/patient/ScoresPanel.vue'
|
||||
import { useScoringStore } from '@/stores/scoring'
|
||||
|
||||
describe('ScoresPanel', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
const store = useScoringStore()
|
||||
store.$patch({
|
||||
news2: { totalScore: 5, riskLevel: 'Medium' },
|
||||
gcs: { eyeScore: 4, verbalScore: 5, motorScore: 6, totalScore: 15 },
|
||||
sofa: { totalScore: 4, deltaFromBaseline: 0, respiratoryScore: 1, coagulationScore: 0, liverScore: 0, cardiovascularScore: 1, cnsScore: 1, renalScore: 1 },
|
||||
qsofa: { activeCriteria: 2 },
|
||||
})
|
||||
})
|
||||
|
||||
it('showsGcsSection', () => {
|
||||
const wrapper = mount(ScoresPanel)
|
||||
expect(wrapper.text()).toContain('GCS')
|
||||
expect(wrapper.text()).toContain('15/15')
|
||||
expect(wrapper.text()).toContain('E4 V5 M6')
|
||||
})
|
||||
|
||||
it('showsSofaSection', () => {
|
||||
const wrapper = mount(ScoresPanel)
|
||||
expect(wrapper.text()).toContain('SOFA')
|
||||
expect(wrapper.text()).toContain('4/24')
|
||||
})
|
||||
|
||||
it('noSirsReference', () => {
|
||||
const wrapper = mount(ScoresPanel)
|
||||
expect(wrapper.text().toUpperCase()).not.toContain('SIRS')
|
||||
})
|
||||
|
||||
it('qsofaLabeledAsScreen', () => {
|
||||
const wrapper = mount(ScoresPanel)
|
||||
expect(wrapper.text()).toContain('qSOFA Screen')
|
||||
expect(wrapper.text()).toContain('Bedside screening')
|
||||
})
|
||||
|
||||
it('sofaOrganBadgesUseResponsiveGrid', () => {
|
||||
const wrapper = mount(ScoresPanel)
|
||||
const grid = wrapper.find('.grid.grid-cols-2')
|
||||
expect(grid.exists()).toBe(true)
|
||||
expect(grid.classes()).toContain('sm:flex')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,110 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import SofaScorePanel from '@/components/patient/SofaScorePanel.vue'
|
||||
|
||||
const mockFetch = vi.fn()
|
||||
const mockState = {
|
||||
total: null,
|
||||
delta: null,
|
||||
organs: [],
|
||||
hasStaleData: false,
|
||||
staleness: null,
|
||||
loading: false,
|
||||
fetch: mockFetch,
|
||||
}
|
||||
|
||||
vi.mock('@/composables/useSofa', () => ({
|
||||
useSofa: () => mockState,
|
||||
}))
|
||||
|
||||
describe('SofaScorePanel', () => {
|
||||
beforeEach(() => {
|
||||
mockState.total = null
|
||||
mockState.delta = null
|
||||
mockState.organs = []
|
||||
mockState.hasStaleData = false
|
||||
mockState.staleness = null
|
||||
mockState.loading = false
|
||||
mockFetch.mockClear()
|
||||
})
|
||||
|
||||
it('showsLabsPending_WhenNoScore', () => {
|
||||
const wrapper = mount(SofaScorePanel, {
|
||||
props: { encounterId: 'enc-1' },
|
||||
})
|
||||
expect(wrapper.text()).toContain('Labs pending')
|
||||
})
|
||||
|
||||
it('showsOrganBreakdown', () => {
|
||||
mockState.total = 8
|
||||
mockState.organs = [
|
||||
{ name: 'Respiratory', score: 2, max: 4 },
|
||||
{ name: 'Coagulation', score: 1, max: 4 },
|
||||
{ name: 'Liver', score: 0, max: 4 },
|
||||
{ name: 'Cardiovascular', score: 3, max: 4 },
|
||||
{ name: 'CNS', score: 1, max: 4 },
|
||||
{ name: 'Renal', score: 1, max: 4 },
|
||||
]
|
||||
const wrapper = mount(SofaScorePanel, {
|
||||
props: { encounterId: 'enc-1' },
|
||||
})
|
||||
expect(wrapper.text()).toContain('Respiratory')
|
||||
expect(wrapper.text()).toContain('Renal')
|
||||
expect(wrapper.text()).toContain('8/24')
|
||||
})
|
||||
|
||||
it('showsDeltaBadge_WhenDeltaGe2', () => {
|
||||
mockState.total = 10
|
||||
mockState.delta = 2
|
||||
mockState.organs = [{ name: 'CNS', score: 2, max: 4 }]
|
||||
const wrapper = mount(SofaScorePanel, {
|
||||
props: { encounterId: 'enc-1' },
|
||||
})
|
||||
expect(wrapper.text()).toContain('+2 from baseline')
|
||||
})
|
||||
|
||||
it('showsStalenessWarning', () => {
|
||||
mockState.total = 6
|
||||
mockState.hasStaleData = true
|
||||
mockState.staleness = {
|
||||
staleComponents: ['Platelets'],
|
||||
missingComponents: ['PaO2'],
|
||||
usedSpO2Fallback: true,
|
||||
}
|
||||
mockState.organs = [{ name: 'Coagulation', score: 1, max: 4 }]
|
||||
const wrapper = mount(SofaScorePanel, {
|
||||
props: { encounterId: 'enc-1' },
|
||||
})
|
||||
expect(wrapper.text()).toContain('Missing: PaO2')
|
||||
expect(wrapper.text()).toContain('Stale: Platelets')
|
||||
expect(wrapper.text()).toContain('SpO₂/FiO₂ proxy')
|
||||
})
|
||||
|
||||
it('organScoreColors', () => {
|
||||
mockState.total = 3
|
||||
mockState.organs = [
|
||||
{ name: 'Liver', score: 0, max: 4 },
|
||||
{ name: 'CNS', score: 2, max: 4 },
|
||||
{ name: 'Renal', score: 4, max: 4 },
|
||||
]
|
||||
const wrapper = mount(SofaScorePanel, {
|
||||
props: { encounterId: 'enc-1' },
|
||||
})
|
||||
const badges = wrapper.findAll('span')
|
||||
expect(badges.some(b => b.text().includes('0/4'))).toBe(true)
|
||||
expect(badges.some(b => b.text().includes('2/4'))).toBe(true)
|
||||
expect(badges.some(b => b.text().includes('4/4'))).toBe(true)
|
||||
})
|
||||
|
||||
it('usesResponsiveOrganGrid', () => {
|
||||
mockState.total = 4
|
||||
mockState.organs = [{ name: 'CNS', score: 1, max: 4 }]
|
||||
const wrapper = mount(SofaScorePanel, {
|
||||
props: { encounterId: 'enc-1' },
|
||||
})
|
||||
const grid = wrapper.find('.grid')
|
||||
expect(grid.classes()).toContain('grid-cols-2')
|
||||
expect(grid.classes()).toContain('sm:grid-cols-3')
|
||||
expect(grid.classes()).toContain('lg:grid-cols-6')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user