/**
 * pipeline/grader-comparison.ts
 *
 * Pure computation module for inter-grader comparison.
 *
 * Takes score data from multiple grader models (each having graded the same
 * set of responses) and produces a comparison matrix with correlations,
 * bias measurements, and per-area deltas between every grader pair.
 *
 * 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 3
 */
import { compare } from "./compare.js";
import { pearsonCorrelation } from "./grader-validation.js";
// ---------------------------------------------------------------------------
// Pure computation
// ---------------------------------------------------------------------------
/**
 * Compare graders at the ScoreSummary level using the existing compare() primitive.
 *
 * This extends a basic GraderComparison with full ComparisonReport objects,
 * showing per-area and per-dimension deltas at the aggregate score level.
 *
 * @param comparison Basic grader comparison (from compareGraders)
 * @param baselineSummary ScoreSummary produced using the baseline grader
 * @param candidateSummaries ScoreSummaries produced using each candidate grader
 * @returns The comparison object augmented with scoreSummaryComparisons
 */
export function attachScoreSummaryComparisons(comparison, baselineSummary, candidateSummaries) {
    const scoreSummaryComparisons = candidateSummaries.map((candidateSummary) => compare(baselineSummary, candidateSummary));
    return {
        ...comparison,
        scoreSummaryComparisons,
    };
}
/**
 * Compare multiple grader models against a baseline grader.
 *
 * This is the main entry point — a pure function with no side effects.
 *
 * @param baseline Score data from the baseline grader
 * @param candidates Score data from candidate grader(s)
 * @returns GraderComparison with pairwise metrics and recommendations
 */
export function compareGraders(baseline, candidates) {
    const pairwise = candidates.map((candidate) => comparePair(baseline, candidate));
    const recommendations = pairwise.map((pair) => makeRecommendation(pair));
    return {
        baselineGrader: baseline.modelId,
        candidateGraders: candidates.map((c) => c.modelId),
        generatedAt: new Date().toISOString(),
        pairwise,
        recommendations,
    };
}
// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------
/** Compare a single pair of graders on their raw judgment scores */
function comparePair(a, b) {
    // Build a lookup for grader B scores by (taskId, dimension) key
    const bScoreMap = new Map();
    for (const s of b.scores) {
        bScoreMap.set(`${s.taskId}::${s.dimension}`, s.score);
    }
    // Find paired observations (present in both graders)
    const pairedA = [];
    const pairedB = [];
    // Group by dimension dynamically — works with any dimension names
    const dimPairsA = {};
    const dimPairsB = {};
    for (const sA of a.scores) {
        const key = `${sA.taskId}::${sA.dimension}`;
        const scoreB = bScoreMap.get(key);
        if (scoreB === undefined)
            continue;
        pairedA.push(sA.score);
        pairedB.push(scoreB);
        (dimPairsA[sA.dimension] ??= []).push(sA.score);
        (dimPairsB[sA.dimension] ??= []).push(scoreB);
    }
    // Build perDimension from all dimensions observed in paired data
    const perDimension = {};
    for (const dim of Object.keys(dimPairsA)) {
        perDimension[dim] = computeDimensionPair(dimPairsA[dim], dimPairsB[dim]);
    }
    return {
        bias: computeBias(pairedA, pairedB),
        correlation: safeCorrelation(pairedA, pairedB),
        graderA: a.modelId,
        graderB: b.modelId,
        meanAbsDiff: computeMeanAbsDiff(pairedA, pairedB),
        perDimension,
    };
}
/** Mean signed difference (B - A). Positive = B scores higher. */
function computeBias(a, b) {
    if (a.length === 0)
        return 0;
    const sum = b.reduce((s, v, i) => s + (v - a[i]), 0);
    return Math.round((sum / a.length) * 10) / 10;
}
/** Compute dimension-level pair comparison */
function computeDimensionPair(scoresA, scoresB) {
    return {
        bias: computeBias(scoresA, scoresB),
        correlation: safeCorrelation(scoresA, scoresB),
        count: scoresA.length,
        meanAbsDiff: computeMeanAbsDiff(scoresA, scoresB),
    };
}
/** Mean absolute difference between paired scores */
function computeMeanAbsDiff(a, b) {
    if (a.length === 0)
        return 0;
    const sum = b.reduce((s, v, i) => s + Math.abs(v - a[i]), 0);
    return Math.round((sum / a.length) * 10) / 10;
}
/** Classify a grader pair as comparable, divergent, or strongly-divergent */
function makeRecommendation(pair) {
    const r = pair.correlation;
    const absBias = Math.abs(pair.bias);
    const mad = pair.meanAbsDiff;
    // Thresholds for recommendation
    // comparable: high correlation, low bias and MAD
    // divergent: moderate correlation or noticeable bias
    // strongly-divergent: low correlation or large bias
    if (r >= 0.9 && absBias <= 5 && mad <= 8) {
        return {
            modelId: pair.graderB,
            reason: `High correlation (r=${r}), low bias (${pair.bias}), low MAD (${mad})`,
            recommendation: "comparable",
        };
    }
    if (r >= 0.7 && absBias <= 10 && mad <= 15) {
        return {
            modelId: pair.graderB,
            reason: `Moderate correlation (r=${r}), bias=${pair.bias}, MAD=${mad}`,
            recommendation: "divergent",
        };
    }
    return {
        modelId: pair.graderB,
        reason: `Low correlation (r=${r}), bias=${pair.bias}, MAD=${mad}`,
        recommendation: "strongly-divergent",
    };
}
/** Safe Pearson correlation that handles edge cases */
function safeCorrelation(a, b) {
    if (a.length < 2)
        return 0;
    return Math.round(pearsonCorrelation(a, b) * 100) / 100;
}
