Files
brain/tools/judge-evaluator.mjs
T

42 lines
2.2 KiB
JavaScript

#!/usr/bin/env node
/**
* judge-evaluator (D31, §9.4) — оценщик ПОСТФАКТУМ. Отдельный (не сам судья):
* сверяет прошлые вердикты судьи И роутера с тем, что вылезло НИЖЕ (на Гейте-3 / у
* владельца). Промах = проблема, которую пропустил GO-вердикт. Систематическую халтуру
* ловит по СЛЕДУ (частота промахов), а не по одному вердикту. Чистая функция над данными;
* источник (лог вердиктов J8 + всплывшие проблемы) подаётся снаружи (4-G/живой прогон).
*/
const DEFAULT_THRESHOLD = 0.2;
function rate(misses, totalGo) {
return totalGo > 0 ? misses / totalGo : 0;
}
/**
* @param {object} a
* @param {Array<{verdict_id,decision,source?}>} a.verdicts - прошлые вердикты (GO/NO-GO)
* @param {Array<{missed_by,where?}>} a.surfaced - проблемы, вылезшие ниже (missed_by → id вердикта)
* @param {number} [a.threshold]
*/
export function evaluatePostfactum({ verdicts = [], surfaced = [], threshold = DEFAULT_THRESHOLD } = {}) {
const goById = new Map();
for (const v of verdicts) {
if (v && v.decision === 'GO') goById.set(v.verdict_id, v.source || 'unknown');
}
// Промах = всплывшая проблема, пропущенная GO-вердиктом (NO-GO-вердикт её «поймал» — не промах).
const missList = (surfaced || []).filter((s) => s && goById.has(s.missed_by));
const totalGo = goById.size;
const misses = missList.length;
const bySource = {};
const sources = new Set([...goById.values()]);
for (const src of sources) {
const srcGo = [...goById.values()].filter((x) => x === src).length;
const srcMiss = missList.filter((s) => goById.get(s.missed_by) === src).length;
bySource[src] = { totalGo: srcGo, misses: srcMiss, missRate: rate(srcMiss, srcGo) };
}
const missRate = rate(misses, totalGo);
return { totalGo, misses, missRate, flag: totalGo > 0 && missRate > threshold, bySource };
}