// engine/spec-effectiveness/calculator.ts — Per-spec effectiveness score (SPEC-611)
// ── Constants ────────────────────────────────────────────────────────────────
const WEIGHTS = {
    adherenceRate: 0.3,
    firstPassSuccess: 0.2,
    reworkRatioInverse: 0.2,
    estimationAccuracy: 0.2,
    driftPenaltyInverse: 0.1,
};
/** reworkRatio above this threshold triggers a "high rework" flag. */
const HIGH_REWORK_THRESHOLD = 40;
/** Drift events above this are capped for penalty calculation. */
const MAX_DRIFT_EVENTS = 10;
// ── Helpers ──────────────────────────────────────────────────────────────────
function clamp(value, min, max) {
    return Math.max(min, Math.min(max, value));
}
function calcEstimationAccuracy(estimated, actual) {
    if (estimated <= 0 || actual <= 0) {
        return 50; // neutral when data is missing
    }
    const ratio = actual / estimated;
    // ratio=1 → perfect (100), ratio=0.5 or ratio=2 → 50, ratio=0 or ∞ → 0
    const accuracy = Math.max(0, 100 - Math.abs(ratio - 1) * 100);
    return clamp(accuracy, 0, 100);
}
function calcDriftPenalty(spec) {
    // driftEvents is not part of the canonical Spec type yet; read defensively.
    const raw = spec;
    const driftEvents = Array.isArray(raw.driftEvents) ? raw.driftEvents.length : 0;
    const capped = Math.min(driftEvents, MAX_DRIFT_EVENTS);
    return (capped / MAX_DRIFT_EVENTS) * 100;
}
function calcValidateRuns(spec) {
    // validateRuns may be stored in statusHistory or as a field.
    const raw = spec;
    if (typeof raw.validateRuns === 'number') {
        return raw.validateRuns;
    }
    // Count status transitions to/from 'review' as a proxy for validate runs.
    const history = spec.statusHistory ?? [];
    return history.filter((e) => e.status === 'review').length;
}
// ── Main export ──────────────────────────────────────────────────────────────
/**
 * Calculate the Spec Effectiveness Score for a single spec.
 * Does NOT perform any I/O — all data comes from the spec object itself.
 */
export function calculateSpecEffectivenessScore(spec) {
    const warnings = [];
    const suggestions = [];
    const validateRuns = calcValidateRuns(spec);
    const actuals = spec.actuals;
    // ── Adherence Rate ────────────────────────────────────────────────────────
    let adherenceRate;
    if (validateRuns === 0) {
        adherenceRate = 0;
        warnings.push('No validate runs recorded before marking done. Run `validate` before transitioning to done to get an accurate adherence rate.');
    }
    else {
        // If validateRuns exist use a simple proxy: first-run pass = 100, else decay.
        const raw = spec;
        const passedCriteria = typeof raw.passedCriteria === 'number' ? raw.passedCriteria : null;
        const totalCriteria = typeof raw.totalCriteria === 'number' ? raw.totalCriteria : null;
        if (passedCriteria !== null && totalCriteria !== null && totalCriteria > 0) {
            adherenceRate = clamp((passedCriteria / totalCriteria) * 100, 0, 100);
        }
        else {
            // Default: assume good adherence when validate was run but no detailed data.
            adherenceRate = 80;
        }
    }
    // ── First-Pass Success ───────────────────────────────────────────────────
    const firstPassSuccess = validateRuns === 1 ? 100 : validateRuns > 1 ? 0 : 0;
    // ── Rework Ratio ─────────────────────────────────────────────────────────
    const raw = spec;
    let reworkRatio;
    if (typeof raw.reworkRatio === 'number') {
        reworkRatio = clamp(raw.reworkRatio, 0, 100);
    }
    else {
        // Default to 0 — no rework data = assume clean
        reworkRatio = 0;
    }
    if (reworkRatio > HIGH_REWORK_THRESHOLD) {
        suggestions.push(`High rework ratio (${reworkRatio.toFixed(0)}%): Consider adding more specific GIVEN/WHEN/THEN criteria before implementation. Use \`check_readiness\` to evaluate criteria completeness.`);
        suggestions.push('Split large specs into smaller ones with a single responsibility to reduce rework scope.');
    }
    // ── Estimation Accuracy ───────────────────────────────────────────────────
    const estimationAccuracy = actuals !== null ? calcEstimationAccuracy(spec.estimation.devHours, actuals.devHours) : 50; // neutral when no actuals
    // ── Drift Penalty ─────────────────────────────────────────────────────────
    const driftPenalty = calcDriftPenalty(spec);
    const components = {
        adherenceRate,
        firstPassSuccess,
        reworkRatio,
        estimationAccuracy,
        driftPenalty,
    };
    // ── Composite Score ───────────────────────────────────────────────────────
    const rawScore = adherenceRate * WEIGHTS.adherenceRate +
        firstPassSuccess * WEIGHTS.firstPassSuccess +
        (100 - reworkRatio) * WEIGHTS.reworkRatioInverse +
        estimationAccuracy * WEIGHTS.estimationAccuracy +
        (100 - driftPenalty) * WEIGHTS.driftPenaltyInverse;
    const score = clamp(Math.round(rawScore), 0, 100);
    return {
        specId: spec.id,
        score,
        components,
        warnings,
        suggestions,
        calculatedAt: new Date().toISOString(),
    };
}
//# sourceMappingURL=calculator.js.map