/**
 * pipeline/grader-consistency.ts
 *
 * Pure computation module for measuring grader consistency.
 *
 * Takes replicated grading results (the same judgment run N times) and
 * computes standard deviation, per-dimension consistency, and an
 * empirically-derived noise threshold recommendation.
 *
 * This module has NO side effects — no file I/O, no API calls.
 * It operates on pre-collected data only.
 *
 * @see docs/archive/exec-plans/grader-reliability.md — Phase 1
 */
// ---------------------------------------------------------------------------
// Pure computation
// ---------------------------------------------------------------------------
/**
 * Analyze a set of replicated gradings and produce a full consistency report.
 *
 * This is the main entry point — a pure function with no side effects.
 *
 * @param gradings Array of replicated grading judgments (each graded N times)
 * @returns GraderConsistency report with per-dimension and overall statistics
 */
export function analyzeConsistency(gradings) {
    if (gradings.length === 0) {
        return {
            avgRange: 0,
            avgStdDev: 0,
            generatedAt: new Date().toISOString(),
            judgments: [],
            maxStdDev: 0,
            perDimension: {},
            recommendedThreshold: 0,
            replications: 0,
            totalJudgments: 0,
        };
    }
    // Analyze each judgment
    const judgments = gradings.map(analyzeJudgment);
    // Group by dimension dynamically — works with any dimension names
    const byDimension = {};
    for (const j of judgments) {
        ;
        (byDimension[j.dimension] ??= []).push(j);
    }
    const perDimension = {};
    for (const [dim, dimJudgments] of Object.entries(byDimension)) {
        perDimension[dim] = aggregateDimension(dimJudgments);
    }
    // Overall stats
    const allStdDevs = judgments.map((j) => j.stdDev);
    const allRanges = judgments.map((j) => j.range);
    const replications = gradings[0]?.scores.length ?? 0;
    // Recommended threshold: 2× the worst (highest) per-dimension avgStdDev.
    // This means a comparison delta must exceed 2σ of the noisiest dimension
    // to be classified as a real change rather than grader variance.
    const dimAvgStdDevs = Object.values(perDimension).map((d) => d.avgStdDev);
    const maxDimensionAvgStdDev = dimAvgStdDevs.length > 0 ? Math.max(...dimAvgStdDevs) : 0;
    const recommendedThreshold = Math.ceil(maxDimensionAvgStdDev * 2);
    // Sort judgments by stdDev descending (noisiest first)
    const sortedJudgments = [...judgments].sort((a, b) => b.stdDev - a.stdDev);
    return {
        avgRange: Math.round(mean(allRanges) * 10) / 10,
        avgStdDev: Math.round(mean(allStdDevs) * 10) / 10,
        generatedAt: new Date().toISOString(),
        judgments: sortedJudgments,
        maxStdDev: Math.max(...allStdDevs),
        perDimension,
        recommendedThreshold,
        replications,
        totalJudgments: judgments.length,
    };
}
/**
 * Compute consistency statistics for a single replicated judgment.
 */
export function analyzeJudgment(grading) {
    const scores = grading.scores;
    const avg = mean(scores);
    const sd = stdDev(scores);
    const min = Math.min(...scores);
    const max = Math.max(...scores);
    return {
        area: grading.area,
        dimension: grading.dimension,
        max,
        mean: Math.round(avg * 10) / 10,
        min,
        range: max - min,
        replications: scores.length,
        stdDev: Math.round(sd * 10) / 10,
        taskId: grading.taskId,
        ...(grading.providerId && { providerId: grading.providerId }),
    };
}
/** Compute mean of an array of numbers */
export function mean(values) {
    if (values.length === 0)
        return 0;
    return values.reduce((sum, v) => sum + v, 0) / values.length;
}
/** Compute population standard deviation of an array of numbers */
export function stdDev(values) {
    if (values.length < 2)
        return 0;
    const avg = mean(values);
    const squaredDiffs = values.map((v) => (v - avg) ** 2);
    return Math.sqrt(mean(squaredDiffs));
}
/**
 * Aggregate judgment-level stats into a per-dimension summary.
 */
function aggregateDimension(judgments) {
    if (judgments.length === 0) {
        return { avgRange: 0, avgStdDev: 0, judgmentCount: 0, maxStdDev: 0 };
    }
    const stdDevs = judgments.map((j) => j.stdDev);
    const ranges = judgments.map((j) => j.range);
    return {
        avgRange: Math.round(mean(ranges) * 10) / 10,
        avgStdDev: Math.round(mean(stdDevs) * 10) / 10,
        judgmentCount: judgments.length,
        maxStdDev: Math.max(...stdDevs),
    };
}
