Files
deviation-log/test/deviation-log.test.ts
2026-09-28 14:49:00 -04:00

140 lines
5.4 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { overall, validateReport } from '@extant2000/evidence-record'
import { assessLog, formatLog, repeatOffenders, type Deviation } from '../src/index.js'
const dev = (p: Partial<Deviation> = {}): Deviation => ({
id: 'd1',
procedure: 'septic-install',
step: 'step-7 backfill compaction',
operator: 'crew-a',
what: 'compacted in one lift instead of three',
where: 'work order 4412',
...p,
})
describe('a logged deviation is not a handled one', () => {
it('reports an unadjudicated deviation as not-assessed', () => {
const r = assessLog([dev()])
expect(r.findings[0]!.determination).toBe('not-assessed')
expect(r.findings[0]!.detail).toContain('an entry in a list, not a finding')
expect(overall(r)).toBe('not-assessed')
})
it('fails an adjudicated deviation with no action — the open loop', () => {
const r = assessLog([dev({ cause: 'procedure-defect' })])
expect(r.findings[0]!.determination).toBe('fail')
expect(r.findings[0]!.severity).toBe('critical')
expect(r.findings[0]!.detail).toContain('looks handled because someone wrote it down')
})
it('fails while the action is still open', () => {
const r = assessLog([dev({
cause: 'procedure-defect',
action: { description: 'rewrite step 7 for single-lift equipment', status: 'open' },
})])
expect(r.findings[0]!.determination).toBe('fail')
expect(r.findings[0]!.detail).toContain('the next crew meets the same step unchanged')
})
it('passes only when the action is applied', () => {
const r = assessLog([dev({
cause: 'procedure-defect',
action: { description: 'rewrite step 7', appliedTo: 'SOP-114 rev C', status: 'applied' },
})])
expect(overall(r)).toBe('pass')
expect(formatLog([dev({
cause: 'procedure-defect',
action: { description: 'rewrite step 7', appliedTo: 'SOP-114 rev C', status: 'applied' },
})], { evidence: 'full' })).toContain('SOP-114 rev C')
})
it('records a rejection as not-applicable and still prints it', () => {
// Printed so a rejection can be reviewed when the same step recurs.
const r = assessLog([dev({
cause: 'environment',
action: { description: 'no change — frozen ground is seasonal', status: 'rejected' },
})])
expect(r.findings[0]!.determination).toBe('not-applicable')
expect(formatLog([dev({
cause: 'environment',
action: { description: 'no change — frozen ground is seasonal', status: 'rejected' },
})])).toContain('frozen ground is seasonal')
})
})
describe('repeat deviations indict the procedure, not the operator', () => {
const closedAsError = (id: string, operator: string): Deviation => dev({
id,
operator,
cause: 'execution-error',
action: { description: 'retrained operator', status: 'applied' },
})
const repeated = [
closedAsError('d1', 'crew-a'),
closedAsError('d2', 'crew-b'),
closedAsError('d3', 'crew-c'),
]
it('counts DISTINCT operators, not incidents', () => {
// One person five times is a conversation. Five people once each is the step.
const sameGuy = [
closedAsError('d1', 'crew-a'),
closedAsError('d2', 'crew-a'),
closedAsError('d3', 'crew-a'),
]
expect(repeatOffenders(sameGuy)).toEqual([])
expect(repeatOffenders(repeated)).toHaveLength(1)
expect(repeatOffenders(repeated)[0]!.operators).toEqual(['crew-a', 'crew-b', 'crew-c'])
})
it('THE POINT: every incident closed clean, and the procedure is still indicted', () => {
// Each entry says "execution error, retrained, applied" — individually a
// pass. The aggregate is the only thing that sees the defect.
const r = assessLog(repeated)
expect(r.findings.filter(f => f.id.startsWith('repeat/'))).toHaveLength(1)
expect(overall(r)).toBe('fail')
const repeat = r.findings.find(f => f.id.startsWith('repeat/'))!
expect(repeat.severity).toBe('critical')
expect(repeat.detail).toContain('the mechanism by which a defective procedure survives')
})
it('leads with the aggregate, above the individual entries', () => {
const text = formatLog(repeated)
expect(text.indexOf('deviated from by 3 different operators'))
.toBeLessThan(text.indexOf('closed: retrained operator'))
})
it('does not group across different steps of the same procedure', () => {
expect(repeatOffenders([
dev({ id: 'd1', operator: 'a', step: 'step-7' }),
dev({ id: 'd2', operator: 'b', step: 'step-9' }),
])).toEqual([])
})
it('honours a custom operator threshold', () => {
expect(repeatOffenders(repeated, { minOperators: 4 })).toEqual([])
expect(repeatOffenders(repeated, { minOperators: 3 })).toHaveLength(1)
})
})
describe('conformance with the evidence-record standard', () => {
it('an empty log is not-assessed, and says what empty means', () => {
const r = assessLog([])
expect(overall(r)).toBe('not-assessed')
expect(formatLog([])).toContain('evidence that none were written down')
})
it('every conclusion carries a citation', () => {
expect(validateReport(assessLog([
dev({ id: 'd1', cause: 'procedure-defect' }),
dev({ id: 'd2', operator: 'crew-b', cause: 'execution-error', action: { description: 'x', status: 'applied' } }),
]))).toEqual([])
})
it('cites the work order behind each deviation', () => {
expect(formatLog([dev({ cause: 'procedure-defect' })], { evidence: 'full' }))
.toContain('deviations#d1')
})
})