d48cda4081
CI / Test (ubuntu-latest, Node 18.x, bun) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, npm) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, pnpm) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, yarn) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 20.x, bun) (push) Failing after 17m13s
CI / Test (ubuntu-latest, Node 20.x, npm) (push) Failing after 18m42s
CI / Test (ubuntu-latest, Node 20.x, pnpm) (push) Failing after 15m0s
CI / Test (ubuntu-latest, Node 20.x, yarn) (push) Failing after 49m44s
CI / Test (ubuntu-latest, Node 22.x, bun) (push) Failing after 51m55s
CI / Test (ubuntu-latest, Node 22.x, pnpm) (push) Failing after 21m57s
CI / Test (ubuntu-latest, Node 22.x, npm) (push) Failing after 37m39s
CI / Test (ubuntu-latest, Node 22.x, yarn) (push) Failing after 34m7s
CI / Validate Components (push) Failing after 37m15s
CI / Python Tests (push) Failing after 10m1s
CI / Security Scan (push) Failing after 10m1s
CI / Lint (push) Failing after 17m12s
CI / Coverage (push) Failing after 20m19s
CI / Test (macos-latest, Node 18.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, yarn) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, yarn) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, yarn) (push) Has been cancelled
119 lines
3.7 KiB
JavaScript
119 lines
3.7 KiB
JavaScript
'use strict';
|
|
|
|
const HEALTH_SCHEMA_VERSION = 'ecc.skill-health.v1';
|
|
|
|
function roundRate(value) {
|
|
return Math.round(value * 1000) / 1000;
|
|
}
|
|
|
|
function rankCounts(values) {
|
|
return Array.from(values.entries())
|
|
.map(([value, count]) => ({ value, count }))
|
|
.sort((left, right) => right.count - left.count || left.value.localeCompare(right.value));
|
|
}
|
|
|
|
function summarizeVariantRuns(records) {
|
|
return records.reduce((accumulator, record) => {
|
|
const key = record.run && record.run.variant ? record.run.variant : 'baseline';
|
|
if (!accumulator[key]) {
|
|
accumulator[key] = { runs: 0, successes: 0, failures: 0 };
|
|
}
|
|
|
|
accumulator[key].runs += 1;
|
|
if (record.outcome && record.outcome.success) {
|
|
accumulator[key].successes += 1;
|
|
} else {
|
|
accumulator[key].failures += 1;
|
|
}
|
|
|
|
return accumulator;
|
|
}, {});
|
|
}
|
|
|
|
function deriveSkillStatus(skillSummary, options = {}) {
|
|
const minFailureCount = options.minFailureCount || 2;
|
|
if (skillSummary.failures >= minFailureCount) {
|
|
return 'failing';
|
|
}
|
|
|
|
if (skillSummary.failures > 0) {
|
|
return 'watch';
|
|
}
|
|
|
|
return 'healthy';
|
|
}
|
|
|
|
function buildSkillHealthReport(records, options = {}) {
|
|
const filterSkillId = options.skillId || null;
|
|
const filtered = filterSkillId
|
|
? records.filter(record => record.skill && record.skill.id === filterSkillId)
|
|
: records.slice();
|
|
|
|
const grouped = filtered.reduce((accumulator, record) => {
|
|
const skillId = record.skill.id;
|
|
if (!accumulator.has(skillId)) {
|
|
accumulator.set(skillId, []);
|
|
}
|
|
accumulator.get(skillId).push(record);
|
|
return accumulator;
|
|
}, new Map());
|
|
|
|
const skills = Array.from(grouped.entries())
|
|
.map(([skillId, skillRecords]) => {
|
|
const successes = skillRecords.filter(record => record.outcome && record.outcome.success).length;
|
|
const failures = skillRecords.length - successes;
|
|
const recurringErrors = new Map();
|
|
const recurringTasks = new Map();
|
|
const recurringFeedback = new Map();
|
|
|
|
skillRecords.forEach(record => {
|
|
if (!record.outcome || record.outcome.success) {
|
|
return;
|
|
}
|
|
|
|
if (record.outcome.error) {
|
|
recurringErrors.set(record.outcome.error, (recurringErrors.get(record.outcome.error) || 0) + 1);
|
|
}
|
|
if (record.task) {
|
|
recurringTasks.set(record.task, (recurringTasks.get(record.task) || 0) + 1);
|
|
}
|
|
if (record.outcome.feedback) {
|
|
recurringFeedback.set(record.outcome.feedback, (recurringFeedback.get(record.outcome.feedback) || 0) + 1);
|
|
}
|
|
});
|
|
|
|
const summary = {
|
|
skill: {
|
|
id: skillId,
|
|
path: skillRecords[0].skill.path || null
|
|
},
|
|
totalRuns: skillRecords.length,
|
|
successes,
|
|
failures,
|
|
successRate: skillRecords.length > 0 ? roundRate(successes / skillRecords.length) : 0,
|
|
status: 'healthy',
|
|
recurringErrors: rankCounts(recurringErrors).map(entry => ({ error: entry.value, count: entry.count })),
|
|
recurringTasks: rankCounts(recurringTasks).map(entry => ({ task: entry.value, count: entry.count })),
|
|
recurringFeedback: rankCounts(recurringFeedback).map(entry => ({ feedback: entry.value, count: entry.count })),
|
|
variants: summarizeVariantRuns(skillRecords)
|
|
};
|
|
|
|
summary.status = deriveSkillStatus(summary, options);
|
|
return summary;
|
|
})
|
|
.sort((left, right) => right.failures - left.failures || left.skill.id.localeCompare(right.skill.id));
|
|
|
|
return {
|
|
schemaVersion: HEALTH_SCHEMA_VERSION,
|
|
generatedAt: new Date().toISOString(),
|
|
totalObservations: filtered.length,
|
|
skillCount: skills.length,
|
|
skills
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
HEALTH_SCHEMA_VERSION,
|
|
buildSkillHealthReport
|
|
};
|