Import the committed KnowledgeFS snapshot dc4072ee302317145612087ce7440851dc329fd0 under knowledge-fs/ without its Git history, local IDE settings, or build artifacts.
187 lines
5.6 KiB
TypeScript
187 lines
5.6 KiB
TypeScript
export interface EvaluationDashboardRun {
|
|
readonly citationAccuracy: number;
|
|
readonly costUsd: number;
|
|
readonly failedQuestions: number;
|
|
readonly faithfulnessScore: number;
|
|
readonly id: string;
|
|
readonly latencyMs: number;
|
|
readonly recallAtK: number;
|
|
readonly runAt: string;
|
|
readonly totalQuestions: number;
|
|
}
|
|
|
|
export interface EvaluationDashboardTrendPoint {
|
|
readonly label: string;
|
|
readonly runId: string;
|
|
readonly value: number;
|
|
readonly valueLabel: string;
|
|
}
|
|
|
|
export interface EvaluationDashboardSummary {
|
|
readonly citationTrend: readonly EvaluationDashboardTrendPoint[];
|
|
readonly costLatency: {
|
|
readonly averageCostLabel: string;
|
|
readonly averageLatencyLabel: string;
|
|
readonly latestCostLabel: string;
|
|
readonly latestLatencyLabel: string;
|
|
};
|
|
readonly latest: {
|
|
readonly citationAccuracyLabel: string;
|
|
readonly faithfulnessLabel: string;
|
|
readonly passRateLabel: string;
|
|
readonly recallLabel: string;
|
|
readonly runId: string;
|
|
};
|
|
readonly recallTrend: readonly EvaluationDashboardTrendPoint[];
|
|
}
|
|
|
|
export interface EvaluationDashboardSummaryInput {
|
|
readonly maxRuns?: number | undefined;
|
|
readonly maxTrendPoints?: number | undefined;
|
|
readonly runs: readonly EvaluationDashboardRun[];
|
|
}
|
|
|
|
const defaultMaxRuns = 20;
|
|
const defaultMaxTrendPoints = 12;
|
|
|
|
export function createEvaluationDashboardSummary({
|
|
maxRuns = defaultMaxRuns,
|
|
maxTrendPoints = defaultMaxTrendPoints,
|
|
runs,
|
|
}: EvaluationDashboardSummaryInput): EvaluationDashboardSummary {
|
|
validatePositiveInteger(maxRuns, "Evaluation dashboard maxRuns");
|
|
validatePositiveInteger(maxTrendPoints, "Evaluation dashboard maxTrendPoints");
|
|
|
|
if (runs.length > maxRuns) {
|
|
throw new Error(`Evaluation dashboard runs exceeds maxRuns=${maxRuns}`);
|
|
}
|
|
|
|
const sortedRuns = [...runs].sort((left, right) => left.runAt.localeCompare(right.runAt));
|
|
|
|
for (const run of sortedRuns) {
|
|
validateRun(run);
|
|
}
|
|
|
|
const latest = sortedRuns.at(-1) ?? emptyRun();
|
|
const trendRuns = sortedRuns.slice(-maxTrendPoints);
|
|
const averageCost =
|
|
sortedRuns.length === 0
|
|
? 0
|
|
: sortedRuns.reduce((total, run) => total + run.costUsd, 0) / sortedRuns.length;
|
|
const averageLatency =
|
|
sortedRuns.length === 0
|
|
? 0
|
|
: sortedRuns.reduce((total, run) => total + run.latencyMs, 0) / sortedRuns.length;
|
|
|
|
return {
|
|
citationTrend: trendRuns.map((run) => trendPoint(run, run.citationAccuracy)),
|
|
costLatency: {
|
|
averageCostLabel: formatUsd(averageCost),
|
|
averageLatencyLabel: formatMs(averageLatency),
|
|
latestCostLabel: formatUsd(latest.costUsd),
|
|
latestLatencyLabel: formatMs(latest.latencyMs),
|
|
},
|
|
latest: {
|
|
citationAccuracyLabel: formatPercent(latest.citationAccuracy),
|
|
faithfulnessLabel: formatPercent(latest.faithfulnessScore),
|
|
passRateLabel: formatPercent(passRate(latest)),
|
|
recallLabel: formatPercent(latest.recallAtK),
|
|
runId: latest.id,
|
|
},
|
|
recallTrend: trendRuns.map((run) => trendPoint(run, run.recallAtK)),
|
|
};
|
|
}
|
|
|
|
function validateRun(run: EvaluationDashboardRun): void {
|
|
if (!run.id.trim()) {
|
|
throw new Error("Evaluation dashboard run id is required");
|
|
}
|
|
|
|
if (Number.isNaN(Date.parse(run.runAt))) {
|
|
throw new Error("Evaluation dashboard runAt must be a valid timestamp");
|
|
}
|
|
|
|
validateZeroToOne(run.recallAtK, "Evaluation dashboard recallAtK");
|
|
validateZeroToOne(run.citationAccuracy, "Evaluation dashboard citationAccuracy");
|
|
validateZeroToOne(run.faithfulnessScore, "Evaluation dashboard faithfulnessScore");
|
|
|
|
if (!Number.isInteger(run.totalQuestions) || run.totalQuestions < 1) {
|
|
throw new Error("Evaluation dashboard totalQuestions must be at least 1");
|
|
}
|
|
|
|
if (!Number.isInteger(run.failedQuestions) || run.failedQuestions < 0) {
|
|
throw new Error("Evaluation dashboard failedQuestions must be non-negative");
|
|
}
|
|
|
|
if (run.failedQuestions > run.totalQuestions) {
|
|
throw new Error("Evaluation dashboard failedQuestions cannot exceed totalQuestions");
|
|
}
|
|
|
|
if (!Number.isFinite(run.costUsd) || run.costUsd < 0) {
|
|
throw new Error("Evaluation dashboard costUsd must be non-negative");
|
|
}
|
|
|
|
if (!Number.isFinite(run.latencyMs) || run.latencyMs < 0) {
|
|
throw new Error("Evaluation dashboard latencyMs must be non-negative");
|
|
}
|
|
}
|
|
|
|
function validatePositiveInteger(value: number, label: string): void {
|
|
if (!Number.isInteger(value) || value < 1) {
|
|
throw new Error(`${label} must be at least 1`);
|
|
}
|
|
}
|
|
|
|
function validateZeroToOne(value: number, label: string): void {
|
|
if (!Number.isFinite(value) || value < 0 || value > 1) {
|
|
throw new Error(`${label} must be between 0 and 1`);
|
|
}
|
|
}
|
|
|
|
function trendPoint(run: EvaluationDashboardRun, value: number): EvaluationDashboardTrendPoint {
|
|
return {
|
|
label: formatHourMinute(run.runAt),
|
|
runId: run.id,
|
|
value,
|
|
valueLabel: formatPercent(value),
|
|
};
|
|
}
|
|
|
|
function passRate(run: EvaluationDashboardRun): number {
|
|
return (run.totalQuestions - run.failedQuestions) / run.totalQuestions;
|
|
}
|
|
|
|
function formatPercent(value: number): string {
|
|
return `${Math.round(value * 100)}%`;
|
|
}
|
|
|
|
function formatUsd(value: number): string {
|
|
return `$${value.toFixed(2)}`;
|
|
}
|
|
|
|
function formatMs(value: number): string {
|
|
return `${Math.round(value).toLocaleString("en-US")} ms`;
|
|
}
|
|
|
|
function formatHourMinute(timestamp: string): string {
|
|
const date = new Date(timestamp);
|
|
return `${String(date.getUTCHours()).padStart(2, "0")}:${String(date.getUTCMinutes()).padStart(
|
|
2,
|
|
"0",
|
|
)}`;
|
|
}
|
|
|
|
function emptyRun(): EvaluationDashboardRun {
|
|
return {
|
|
citationAccuracy: 0,
|
|
costUsd: 0,
|
|
failedQuestions: 0,
|
|
faithfulnessScore: 0,
|
|
id: "",
|
|
latencyMs: 0,
|
|
recallAtK: 0,
|
|
runAt: "1970-01-01T00:00:00.000Z",
|
|
totalQuestions: 1,
|
|
};
|
|
}
|