fix(metrics): сохранить нулевые медианы процесса (#637)

Issue: #637
User-Visible: no
This commit is contained in:
Claude
2026-09-23 11:46:16 +03:00
parent 5df8cfc0c1
commit 44aa358060
2 changed files with 31 additions and 4 deletions
+8 -4
View File
@@ -32,6 +32,10 @@ const median = (values) => {
const mid = Math.floor(sorted.length / 2);
return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
};
const medianHours = (values) => {
const value = median(values);
return value === null ? null : hours(value);
};
const mean = (values) => {
const finite = values.filter(Number.isFinite);
return finite.length ? finite.reduce((a, b) => a + b, 0) / finite.length : null;
@@ -159,10 +163,10 @@ export function buildReport({ since, until, issues = [], timelines = new Map(),
closed: perIssue.length,
completed: completed.length,
notPlanned: perIssue.filter((issue) => issue.stateReason === 'not_planned').length,
medianLeadToS7Hours: hours(median(completed.map((i) => i.leadToS7Ms)) ?? NaN) || null,
medianReviewToMergeHours: hours(median(completed.map((i) => i.reviewToMergeMs)) ?? NaN) || null,
medianLeadToS8Hours: hours(median(completed.map((i) => i.leadToS8Ms)) ?? NaN) || null,
medianSpecLeadHours: hours(median(completed.map((i) => i.specLeadMs)) ?? NaN) || null,
medianLeadToS7Hours: medianHours(completed.map((i) => i.leadToS7Ms)),
medianReviewToMergeHours: medianHours(completed.map((i) => i.reviewToMergeMs)),
medianLeadToS8Hours: medianHours(completed.map((i) => i.leadToS8Ms)),
medianSpecLeadHours: medianHours(completed.map((i) => i.specLeadMs)),
codeReviewRounds: { mean: mean(codeRounds), distribution: dist(codeRounds), issues: codeRounds.length },
specReviewRounds: { mean: mean(specRounds), distribution: dist(specRounds), issues: specRounds.length },
s7RepeatRequests: completed.filter((i) => i.s7Requests > 1).length,
+23
View File
@@ -105,6 +105,29 @@ test('#637 buildReport + renderMarkdown: сводка воспроизводит
assert.equal(report.jobs, null, 'без jobs job-минуты не выдумываются');
});
test('#637 buildReport: нулевая медиана не смешивается с отсутствием данных', () => {
const issue = { number: 637, title: 'fast', closed_at: T(4 / 60), state_reason: 'completed' };
const timelines = new Map([[637, [
labeled('S1-new', 0),
labeled('S4-spec-review', 0),
labeled('S5-ready', 1 / 60),
labeled('S7-code-review', 2 / 60),
labeled('S8-merged', 3 / 60),
]] ]);
const report = buildReport({ since: T(0), until: T(1), issues: [issue], timelines });
assert.deepEqual([
report.issues.medianLeadToS7Hours,
report.issues.medianReviewToMergeHours,
report.issues.medianLeadToS8Hours,
report.issues.medianSpecLeadHours,
], [0, 0, 0.1, 0]);
assert.match(renderMarkdown(report), /Медиана вход → S7 \| 0 ч/);
const empty = buildReport({ since: T(0), until: T(1) });
assert.equal(empty.issues.medianLeadToS7Hours, null);
assert.match(renderMarkdown(empty), /Медиана вход → S7 \| —/);
});
test('#637 workflow: еженедельный запуск читает только, публикует summary и artifact', () => {
const wf = readFileSync(new URL('../.github/workflows/process-metrics.yml', import.meta.url), 'utf8');
assert.match(wf, /schedule:\n(?:\s+#[^\n]*\n)*\s+- cron: '/);