/* * 시뮬 명세 자격 판정 — **"이 예측으로 무엇을 말할 수 있나" 를 숫자로 가른다.** * * 배경: 공정 소요가 커널 상수였을 때와 현장 명세로 굴렸을 때가 응답에서 똑같이 "p50 168" 로 보였다. * 그 둘을 구별하지 못하면 상대 비교용 숫자가 절대 예측처럼 읽힌다. */ import { test } from 'node:test' import assert from 'node:assert/strict' import { classifySpecCoverage, readSpecCoverage } from '../server/engine/spec-coverage.ts' test('전부 기본값이면 상대 비교만 — 절대 시각을 주장할 근거가 없다', () => { const r = classifySpecCoverage({ operations: [ { kind: 'putaway', duration: 'default' }, { kind: 'pick', duration: 'default' } ] }) assert.equal(r!.qualification, 'relative') assert.equal(r!.declaredDurations, 0) assert.equal(r!.defaultDurations, 2) }) test('전부 선언되면 절대 예측 시도 가능 — 단 정확도는 별개(이름이 capable 이다)', () => { const r = classifySpecCoverage({ operations: [ { kind: 'cut', duration: 'declared', variability: 'triangular', parameters: ['yield'] }, { kind: 'weld', duration: 'declared', variability: 'uniform' } ] }) assert.equal(r!.qualification, 'absolute-capable') assert.equal(r!.varyingDurations, 2) }) test('섞이면 partial — 한쪽만 계산하지된 사실을 감추지 않는다', () => { const r = classifySpecCoverage({ operations: [ { kind: 'cut', duration: 'declared' }, { kind: 'paint', duration: 'default' } ] }) assert.equal(r!.qualification, 'partial') assert.equal(r!.declaredDurations, 1) assert.equal(r!.defaultDurations, 1) }) test('변동 없는 선언은 varying 0 — 고정값이면 대기·병목이 과소평가된다는 신호', () => { const r = classifySpecCoverage({ operations: [{ kind: 'cut', duration: 'declared' }] }) assert.equal(r!.varyingDurations, 0) const c = classifySpecCoverage({ operations: [{ kind: 'cut', duration: 'declared', variability: 'constant' }] }) assert.equal(c!.varyingDurations, 0, 'constant 는 변동이 아니다') }) test('관측된 작업이 없으면 판정하지 않는다 — 모름을 relative 로 단정하지 않는다', () => { assert.equal(classifySpecCoverage({ operations: [] }), undefined) assert.equal(classifySpecCoverage({} as any), undefined) }) test('커널이 보고 API 를 갖지 않으면 undefined — "전부 기본값" 이라 단정하지 않는다', () => { assert.equal(readSpecCoverage({}), undefined) assert.equal(readSpecCoverage(undefined), undefined) }) test('커널 보고가 오류를 내어도 예측을 깨지 않는다', () => { assert.equal(readSpecCoverage({ specCoverage: () => { throw new Error('boom') } }), undefined) }) test('커널 보고를 그대로 분류한다 — 카운트를 다시 세지 않고 준 값을 존중', () => { const r = readSpecCoverage({ specCoverage: () => ({ operations: [{ kind: 'cut', duration: 'declared', parameters: ['yield'] }], declaredDurations: 1, defaultDurations: 0 }) }) assert.equal(r!.qualification, 'absolute-capable') assert.deepEqual(r!.operations[0].parameters, ['yield']) }) /* ── 실측이 들어오면 자격이 한 칸 올라간다 ─────────────────────────────── */ test('전부 이력에서 온 소요면 calibrated — 선언은 사람이 적은 값, 이것은 일어난 값', () => { const r = classifySpecCoverage({ operations: [ { kind: 'putaway', duration: 'measured' }, { kind: 'pick', duration: 'measured' } ] }) assert.equal(r!.qualification, 'calibrated') assert.equal(r!.measuredDurations, 2) }) test('실측과 선언이 섞이면 absolute-capable — 상수가 없으면 시각을 말할 수 있다', () => { const r = classifySpecCoverage({ operations: [ { kind: 'putaway', duration: 'measured' }, { kind: 'cut', duration: 'declared' } ] }) assert.equal(r!.qualification, 'absolute-capable') }) test('상수가 하나라도 섞이면 partial — 실측이 있어도 감추지 않는다', () => { const r = classifySpecCoverage({ operations: [ { kind: 'putaway', duration: 'measured' }, { kind: 'pick', duration: 'default' } ] }) assert.equal(r!.qualification, 'partial') assert.equal(r!.measuredDurations, 1) assert.equal(r!.defaultDurations, 1) })