/**
 * pipeline/grader-sensitivity.ts
 *
 * Pure computation module for measuring grader sensitivity (discrimination power).
 *
 * Takes paired grading results — original (good) and degraded (bad) versions
 * of the same response graded by the same grader — and computes:
 * - Concordance rate: % of pairs where the grader ranked the original higher
 * - Score separation: average score difference between good and bad
 * - Per-dimension sensitivity
 *
 * This module has NO side effects — no file I/O, no API calls.
 *
 * @see docs/archive/exec-plans/grader-reliability.md — Phase 4
 */
// ---------------------------------------------------------------------------
// Pure computation
// ---------------------------------------------------------------------------
/**
 * Analyze sensitivity from a set of paired grading results.
 *
 * @param pairs Array of paired comparisons (original vs. degraded)
 * @param graderModel Grader model ID for the report
 * @returns Full sensitivity analysis with concordance, separation, and per-dimension metrics
 */
export function analyzeSensitivity(pairs, graderModel) {
    if (pairs.length === 0) {
        return emptyResult(graderModel);
    }
    // Overall concordance and separation
    const { avgSeparation, concordanceRate, tiedRate: _tiedRate, } = computeMetrics(pairs);
    // Per-dimension (based on the grading dimension, not the target dimension)
    const dimGroups = {};
    for (const p of pairs) {
        ;
        (dimGroups[p.dimension] ??= []).push(p);
    }
    const perDimension = {};
    for (const [dim, dimPairs] of Object.entries(dimGroups)) {
        perDimension[dim] = computeMetrics(dimPairs);
    }
    // Cross-dimension: on-target (dimension matches targetDimension) vs off-target
    const onTargetPairs = pairs.filter((p) => p.dimension === p.targetDimension);
    const offTargetPairs = pairs.filter((p) => p.dimension !== p.targetDimension);
    const crossDimension = {
        offTarget: computeMetrics(offTargetPairs),
        onTarget: computeMetrics(onTargetPairs),
    };
    // Per-degradation type
    const byDegradation = computeByDegradation(pairs);
    // Failed pairs (grader ranked degraded higher)
    const failedPairs = pairs
        .filter((p) => p.degradedScore > p.originalScore)
        .sort((a, b) => {
        const sepA = a.degradedScore - a.originalScore;
        const sepB = b.degradedScore - b.originalScore;
        return sepB - sepA; // worst failures first
    });
    return {
        avgSeparation,
        byDegradation,
        concordanceRate,
        crossDimension,
        failedPairs,
        generatedAt: new Date().toISOString(),
        graderModel,
        perDimension,
        totalPairs: pairs.length,
    };
}
// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------
/** Group pairs by degradation description and compute per-group metrics */
function computeByDegradation(pairs) {
    const groups = new Map();
    for (const p of pairs) {
        const key = p.degradationDescription;
        const group = groups.get(key) ?? [];
        group.push(p);
        groups.set(key, group);
    }
    const results = [];
    for (const [description, group] of groups) {
        const metrics = computeMetrics(group);
        results.push({
            avgSeparation: metrics.avgSeparation,
            concordanceRate: metrics.concordanceRate,
            description,
            pairCount: metrics.pairCount,
            targetDimension: group[0].targetDimension,
        });
    }
    // Sort by concordance rate ascending (worst discrimination first)
    results.sort((a, b) => a.concordanceRate - b.concordanceRate);
    return results;
}
/** Compute concordance, separation, and tied rate from a set of pairs */
function computeMetrics(pairs) {
    if (pairs.length === 0) {
        return { avgSeparation: 0, concordanceRate: 0, pairCount: 0, tiedRate: 0 };
    }
    let concordant = 0;
    let tied = 0;
    let totalSeparation = 0;
    for (const p of pairs) {
        const sep = p.originalScore - p.degradedScore;
        totalSeparation += sep;
        if (p.originalScore > p.degradedScore)
            concordant++;
        if (p.originalScore === p.degradedScore)
            tied++;
    }
    return {
        avgSeparation: round(totalSeparation / pairs.length),
        concordanceRate: round((concordant / pairs.length) * 100),
        pairCount: pairs.length,
        tiedRate: round((tied / pairs.length) * 100),
    };
}
/** Empty result for when there are no pairs */
function emptyResult(graderModel) {
    const emptyDim = {
        avgSeparation: 0,
        concordanceRate: 0,
        pairCount: 0,
        tiedRate: 0,
    };
    return {
        avgSeparation: 0,
        byDegradation: [],
        concordanceRate: 0,
        crossDimension: { offTarget: emptyDim, onTarget: emptyDim },
        failedPairs: [],
        generatedAt: new Date().toISOString(),
        graderModel,
        perDimension: {},
        totalPairs: 0,
    };
}
/** Round to 1 decimal place */
function round(n) {
    return Math.round(n * 10) / 10;
}
