Back to Quality Engineering Toolkit
capstone-quality-engineering-toolkit/src/analysis.ts
import { ToolkitError } from "./errors.js";
import type { ExecutionRecord, QualitySummary } from "./models.js";
function countByKey(records: readonly ExecutionRecord[], selectKey: (record: ExecutionRecord) => string): Record<string, number> {
const counts = new Map<string, number>();
for (const record of records) {
const key = selectKey(record);
counts.set(key, (counts.get(key) ?? 0) + 1);
}
return Object.fromEntries([...counts.entries()].sort(([left], [right]) => left.localeCompare(right)));
}
function assertSlowThreshold(slowThresholdMs: number): void {
if (!Number.isFinite(slowThresholdMs) || slowThresholdMs < 0) {
throw new ToolkitError(
"invalid-input",
"slowThresholdMs must be a non-negative finite number.",
{ operation: "summarise-executions", expected: "slowThresholdMs: non-negative finite number" },
);
}
}
/** Produces decision-oriented counts from a trusted, de-duplicated population. */
export function summariseExecutions(
records: readonly ExecutionRecord[],
slowThresholdMs: number,
): QualitySummary {
assertSlowThreshold(slowThresholdMs);
const failed = records.filter((record) => record.outcome === "failed");
const slow = records.filter((record) => record.durationMs >= slowThresholdMs);
return {
executionCount: records.length,
passedExecutionCount: records.filter((record) => record.outcome === "passed").length,
failedExecutionCount: failed.length,
skippedExecutionCount: records.filter((record) => record.outcome === "skipped").length,
slowExecutionCount: slow.length,
failedOperationCount: countByKey(failed, (record) => record.operation),
executionCountByEnvironment: countByKey(records, (record) => record.environment),
slowOperations: [...new Set(slow.map((record) => record.operation))].sort((left, right) =>
left.localeCompare(right),
),
};
}