/**
 * pipeline/grader-validation.ts
 *
 * Pure computation module for grader validation against human reference grades.
 *
 * Takes paired (graderScore, humanScore) observations and computes:
 * - Mean Absolute Error (MAE)
 * - Pearson correlation
 * - Per-dimension breakdown
 * - Largest disagreements (for diagnosis)
 *
 * This module has NO side effects — no file I/O, no API calls.
 *
 * @see docs/archive/exec-plans/grader-reliability.md — Phase 2
 */
// ---------------------------------------------------------------------------
// Pure computation
// ---------------------------------------------------------------------------
/** Classify a correlation value into a human-readable quality label */
export function classifyCorrelation(r) {
    const abs = Math.abs(r);
    if (abs >= 0.9)
        return "excellent";
    if (abs >= 0.7)
        return "good";
    if (abs >= 0.5)
        return "moderate";
    if (abs >= 0.3)
        return "poor";
    return "very-poor";
}
/** Compute Pearson correlation coefficient between two arrays of equal length */
export function pearsonCorrelation(x, y) {
    const n = x.length;
    if (n < 2)
        return 0;
    if (x.length !== y.length) {
        throw new Error(`Arrays must have equal length (got ${x.length} and ${y.length})`);
    }
    const meanX = x.reduce((s, v) => s + v, 0) / n;
    const meanY = y.reduce((s, v) => s + v, 0) / n;
    let sumXY = 0;
    let sumX2 = 0;
    let sumY2 = 0;
    for (let i = 0; i < n; i++) {
        const dx = x[i] - meanX;
        const dy = y[i] - meanY;
        sumXY += dx * dy;
        sumX2 += dx * dx;
        sumY2 += dy * dy;
    }
    const denom = Math.sqrt(sumX2 * sumY2);
    if (denom === 0)
        return 0;
    return sumXY / denom;
}
/**
 * Validate grader scores against human reference grades.
 *
 * This is the main entry point — a pure function with no side effects.
 *
 * @param grades Array of paired (grader, human) observations
 * @param graderModel The grader model identifier
 * @param options Optional configuration
 * @returns GraderValidation report
 */
export function validateGrader(grades, graderModel, options) {
    const maeThreshold = options?.maeThreshold ?? 10;
    const maxDisagreements = options?.maxDisagreements ?? 10;
    if (grades.length === 0) {
        return {
            generatedAt: new Date().toISOString(),
            graderModel,
            largestDisagreements: [],
            maeThreshold,
            overallBias: 0,
            overallCorrelation: 0,
            overallMae: 0,
            passesThreshold: true,
            perDimension: {},
            totalObservations: 0,
        };
    }
    // Build paired arrays
    const allPairs = grades.map((g) => ({
        grader: g.graderScore,
        human: g.humanScore,
    }));
    // Group by dimension dynamically
    const byDimension = {};
    for (const g of grades) {
        ;
        (byDimension[g.dimension] ??= []).push({
            grader: g.graderScore,
            human: g.humanScore,
        });
    }
    // Overall metrics
    const overallMae = computeMae(allPairs);
    const overallCorrelation = Math.round(pearsonCorrelation(allPairs.map((p) => p.grader), allPairs.map((p) => p.human)) * 100) / 100;
    const overallBias = computeBias(allPairs);
    // Per-dimension metrics
    const perDimension = {};
    for (const [dim, dimPairs] of Object.entries(byDimension)) {
        perDimension[dim] = computeDimensionValidity(dimPairs);
    }
    // Find largest disagreements
    const disagreements = grades
        .map((g) => ({
        absoluteError: Math.abs(g.graderScore - g.humanScore),
        area: g.area,
        dimension: g.dimension,
        graderScore: g.graderScore,
        humanScore: g.humanScore,
        signedError: g.graderScore - g.humanScore,
        taskId: g.taskId,
        ...(g.notes && { notes: g.notes }),
    }))
        .sort((a, b) => b.absoluteError - a.absoluteError)
        .slice(0, maxDisagreements);
    return {
        generatedAt: new Date().toISOString(),
        graderModel,
        largestDisagreements: disagreements,
        maeThreshold,
        overallBias,
        overallCorrelation,
        overallMae,
        passesThreshold: overallMae < maeThreshold,
        perDimension,
        totalObservations: grades.length,
    };
}
/** Compute mean signed error (bias) */
function computeBias(pairs) {
    if (pairs.length === 0)
        return 0;
    const totalBias = pairs.reduce((sum, p) => sum + (p.grader - p.human), 0);
    return Math.round((totalBias / pairs.length) * 10) / 10;
}
/** Compute validity metrics for a set of paired observations */
function computeDimensionValidity(pairs) {
    if (pairs.length === 0) {
        return { bias: 0, correlation: 0, count: 0, mae: 0 };
    }
    const graderScores = pairs.map((p) => p.grader);
    const humanScores = pairs.map((p) => p.human);
    return {
        bias: computeBias(pairs),
        correlation: Math.round(pearsonCorrelation(graderScores, humanScores) * 100) / 100,
        count: pairs.length,
        mae: computeMae(pairs),
    };
}
/** Compute Mean Absolute Error */
function computeMae(pairs) {
    if (pairs.length === 0)
        return 0;
    const totalError = pairs.reduce((sum, p) => sum + Math.abs(p.grader - p.human), 0);
    return Math.round((totalError / pairs.length) * 10) / 10;
}
