Files
portal/tools/router-accuracy-runner.mjs
T
Дмитрий 7ed72a09f7 feat(router): 20-prompt accuracy runner — Phase A baseline (stage 3 task 5)
Ground truth: tools/router-test-prompts.json (20 промптов).
Runner: tools/router-accuracy-runner.mjs.

Baseline accuracy regex-only (Layer 1, без ANTHROPIC_API_KEY):
type=55.0%, node=55.0%, micro=95.0%.

Overall score: (11+11+19)/(60) = 68.3% — ниже порога 75%.

Систематические разрывы (наблюдения, не фиксы):
1. «опечатка/поправь» → bugfix ожидается, regex не ловит «поправь»
2. «составь email-рассылку» → marketing ожидается, regex не ловит «составь»
3. «проверь ... перед выходом» → #73 go-live ожидается, но #68 ZAP
   перебивает (оба security-узлы, ZAP имеет «проникновение» ≠ «выход»
   — weight tie-breaker в пользу первого найденного узла)
4. domain-узлы (#62, #71, #72) матчат правильно, но taskType не детектится
   («проверь ПДн» → type=unknown, node=#71 верно)
5. «запусти Pest тесты» → type=unknown (нет «баг/fix» в промпте)
6. «удали мёртвый код» → node=#3 (GitHub MCP матчит «issues» в тексте?)

NB: Layer 2 (Sonnet) подняла бы node-accuracy на спорных доменных
промптах — отложена до получения ключа (вариант 2).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 10:40:20 +03:00

56 lines
2.3 KiB
JavaScript

#!/usr/bin/env node
/**
* Accuracy runner — прогоняет 20 промптов через classifier (без LLM, regex only)
* и выдаёт отчёт «правильно/неправильно» по каждому пункту.
*
* Использовать перед регистрацией router-prehook в settings.json.
*/
import { readFileSync } from 'fs';
import { classifyByRegex } from './router-classifier.mjs';
import { loadRegistry } from './registry-load.mjs';
function main() {
const promptsFile = process.argv[2] || 'tools/router-test-prompts.json';
const data = JSON.parse(readFileSync(promptsFile, 'utf-8'));
const registry = loadRegistry({ useCache: false });
let correctType = 0, correctNode = 0, correctMicro = 0, total = data.prompts.length;
const failures = [];
for (const p of data.prompts) {
const r = classifyByRegex(p.text, registry);
const typeOk = r.taskType === p.expectedType;
const nodeOk = r.recommendedNode === p.expectedNode;
const microOk = r.micro === p.expectedMicro;
if (typeOk) correctType++;
if (nodeOk) correctNode++;
if (microOk) correctMicro++;
if (!typeOk || !nodeOk || !microOk) {
failures.push({
prompt: p.text,
expected: { type: p.expectedType, node: p.expectedNode, micro: p.expectedMicro },
actual: { type: r.taskType, node: r.recommendedNode, micro: r.micro },
deltas: { type: !typeOk, node: !nodeOk, micro: !microOk },
});
}
}
console.log('=== Accuracy Report ===');
console.log(`Type accuracy: ${correctType}/${total} = ${(correctType / total * 100).toFixed(1)}%`);
console.log(`Node accuracy: ${correctNode}/${total} = ${(correctNode / total * 100).toFixed(1)}%`);
console.log(`Micro accuracy: ${correctMicro}/${total} = ${(correctMicro / total * 100).toFixed(1)}%`);
console.log('');
console.log(`Failures (${failures.length}):`);
for (const f of failures) {
console.log(` «${f.prompt}»`);
console.log(` expected: type=${f.expected.type}, node=${f.expected.node}, micro=${f.expected.micro}`);
console.log(` actual: type=${f.actual.type}, node=${f.actual.node}, micro=${f.actual.micro}`);
}
const passOverall = (correctType + correctNode + correctMicro) / (total * 3);
process.exit(passOverall >= 0.75 ? 0 : 1);
}
main();