132 lines
5.8 KiB
TypeScript
132 lines
5.8 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { overall, validateReport } from '@extant2000/evidence-record'
|
|
import { formatRanking, rank, toReport, type CapabilityStanding } from '../src/index.js'
|
|
|
|
// An example portfolio of four services, scored 0-100.
|
|
const suite: CapabilityStanding[] = [
|
|
{ name: 'docs', score: 90, effort: 'small', bindingConstraint: 'none material', where: 'quarterly scorecard' },
|
|
{ name: 'gateway', score: 85, effort: 'small', bindingConstraint: 'retry fix written but unmerged', nextAction: 'merge the retry fix', where: 'quarterly scorecard' },
|
|
{ name: 'reports', score: 60, effort: 'large', bindingConstraint: 'scheduled exports promised, nothing runs them', nextAction: 'build the scheduler', where: 'quarterly scorecard' },
|
|
{ name: 'billing', score: 80, effort: 'trivial', bindingConstraint: 'debug logging left on', nextAction: 'turn off debug logging', where: 'quarterly scorecard' },
|
|
]
|
|
|
|
describe('unassessed is not the bottom of the list', () => {
|
|
it('ranks unassessed capabilities separately, never interleaved', () => {
|
|
// "Nobody looked at this" and "we looked and it is weak" are opposite
|
|
// situations. One ordered list cannot hold both.
|
|
const r = rank([...suite, { name: 'newservice', score: null }])
|
|
expect(r.ranked.map(c => c.name)).not.toContain('newservice')
|
|
expect(r.unassessed.map(c => c.name)).toEqual(['newservice'])
|
|
})
|
|
|
|
it('reports an unassessed capability as not-assessed and says why it is unranked', () => {
|
|
const r = toReport([...suite, { name: 'newservice', score: null }])
|
|
const f = r.findings.find(x => x.id === 'newservice')!
|
|
expect(f.determination).toBe('not-assessed')
|
|
expect(f.detail).toContain('backwards, as advice')
|
|
// The roll-up is `fail`, not `not-assessed`: a real shortfall takes
|
|
// precedence over an unknown, which is the standard's defined order.
|
|
expect(overall(r)).toBe('fail')
|
|
})
|
|
|
|
it('rolls up to not-assessed when the ONLY entries are unassessed', () => {
|
|
expect(overall(toReport([{ name: 'newservice', score: null }]))).toBe('not-assessed')
|
|
})
|
|
|
|
it('does not let a null score sort as zero', () => {
|
|
const r = rank([{ name: 'unknown', score: null }, { name: 'weak', score: 10, effort: 'small' }])
|
|
expect(r.ranked[0]!.name).toBe('weak')
|
|
expect(r.ranked).toHaveLength(1)
|
|
})
|
|
})
|
|
|
|
describe('leverage, not score', () => {
|
|
it('prefers a cheap fix on a mid capability over an expensive one on a weak capability', () => {
|
|
// billing: (100-80)/1 = 20. reports: (100-60)/8 = 5.
|
|
// Ordering by score alone would recommend the rewrite.
|
|
const r = rank(suite)
|
|
expect(r.ranked[0]!.name).toBe('billing')
|
|
expect(r.ranked[0]!.leverage).toBeGreaterThan(r.ranked.find(c => c.name === 'reports')!.leverage!)
|
|
})
|
|
|
|
it('scales by strategic weight', () => {
|
|
const r = rank([
|
|
{ name: 'minor', score: 50, effort: 'small', weight: 1 },
|
|
{ name: 'major', score: 50, effort: 'small', weight: 3 },
|
|
])
|
|
expect(r.ranked[0]!.name).toBe('major')
|
|
})
|
|
|
|
it('treats unknown effort as medium, not as free', () => {
|
|
// An unestimated task is not a cheap one. Defaulting optimistically is
|
|
// how an unscoped item reaches the top of a plan.
|
|
const r = rank([
|
|
{ name: 'unscoped', score: 50 },
|
|
{ name: 'trivial-known', score: 50, effort: 'trivial' },
|
|
])
|
|
expect(r.ranked[0]!.name).toBe('trivial-known')
|
|
})
|
|
|
|
it('gives a near-finished capability little leverage however cheap the work', () => {
|
|
const r = rank([
|
|
{ name: 'nearly-done', score: 98, effort: 'trivial' },
|
|
{ name: 'midway', score: 60, effort: 'medium' },
|
|
])
|
|
expect(r.ranked[0]!.name).toBe('midway')
|
|
})
|
|
})
|
|
|
|
describe('a rank without a constraint is not advice', () => {
|
|
it('names the binding constraint and next action on each entry', () => {
|
|
const text = formatRanking(suite)
|
|
expect(text).toContain('debug logging left on')
|
|
expect(text).toContain('turn off debug logging')
|
|
expect(text).toContain('best next move: billing')
|
|
})
|
|
|
|
it('says so when no constraint was recorded', () => {
|
|
const f = toReport([{ name: 'x', score: 50 }]).findings[0]!
|
|
expect(f.detail).toContain('a position, not advice')
|
|
})
|
|
|
|
it('only a genuinely finished capability passes', () => {
|
|
expect(toReport([{ name: 'done', score: 95, effort: 'trivial' }]).findings[0]!.determination).toBe('pass')
|
|
expect(toReport([{ name: 'nearly', score: 89, effort: 'trivial' }]).findings[0]!.determination).toBe('fail')
|
|
})
|
|
})
|
|
|
|
describe('conformance with the evidence-record standard', () => {
|
|
it('an empty portfolio is not-assessed, never clean', () => {
|
|
expect(overall(toReport([]))).toBe('not-assessed')
|
|
})
|
|
|
|
it('every conclusion carries a citation', () => {
|
|
expect(validateReport(toReport(suite))).toEqual([])
|
|
})
|
|
|
|
it('carries the score into the citation as a measurement', () => {
|
|
const text = formatRanking(suite, { evidence: 'inline' })
|
|
expect(text).toContain('billing readiness = 80')
|
|
})
|
|
})
|
|
|
|
describe('the ranking prints in rank order', () => {
|
|
it('shows #1 first, even when a lower-ranked entry is more severe', () => {
|
|
// The shared renderer sorts worst-first, which is right for findings and
|
|
// wrong for a ranking. A ranking whose display order contradicts its own
|
|
// numbering is worse than no numbering.
|
|
const text = formatRanking(suite)
|
|
const table = text.split('\n\n')[0]!
|
|
expect(table.indexOf('#1')).toBeLessThan(table.indexOf('#2'))
|
|
expect(table.indexOf('#2')).toBeLessThan(table.indexOf('#3'))
|
|
expect(table.split('\n')[0]).toContain('billing')
|
|
})
|
|
|
|
it('lists unassessed capabilities under the ranked ones, without a number', () => {
|
|
const table = formatRanking([...suite, { name: 'newservice', score: null }]).split('\n\n')[0]!
|
|
expect(table).toContain('newservice')
|
|
expect(table).toContain('no score to rank from')
|
|
expect(table.indexOf('#1')).toBeLessThan(table.indexOf('newservice'))
|
|
})
|
|
})
|