// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------
/** Build the bucket key from scope + type. */
function bucketKey(scope, type) {
    return `${scope}:${type}`;
}
/** Determine confidence level from sample count. */
function confidenceFor(n) {
    if (n >= 10) {
        return 'high';
    }
    if (n >= 3) {
        return 'medium';
    }
    return 'low';
}
/** Compute the average bias ratio (actualHours / estimatedHours) from records. */
function computeBias(records) {
    /* v8 ignore next 3 */
    if (records.length === 0) {
        return 1;
    }
    const ratios = records.map((r) => (r.estimatedHours > 0 ? r.actualHours / r.estimatedHours : 1));
    return ratios.reduce((sum, v) => sum + v, 0) / ratios.length;
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Compute correction buckets from a list of historical actuals.
 * Groups records by `scope:type` and returns one PredictionBucket per group.
 * Callers are responsible for loading and merging records from one or more projects.
 */
export function computeCorrectionFactors(records) {
    if (records.length === 0) {
        return [];
    }
    const groups = new Map();
    for (const r of records) {
        const key = bucketKey(r.specScope, r.specType);
        const existing = groups.get(key) ?? [];
        existing.push(r);
        groups.set(key, existing);
    }
    const buckets = [];
    for (const [key, recs] of groups) {
        const avgBias = computeBias(recs);
        buckets.push({
            key,
            sampleSize: recs.length,
            avgBias: Math.round(avgBias * 1000) / 1000,
            correctionFactor: Math.round(avgBias * 1000) / 1000,
            confidence: confidenceFor(recs.length),
        });
    }
    return buckets.sort((a, b) => a.key.localeCompare(b.key));
}
/**
 * Apply a correction bucket to a raw estimation.
 * When bucket is undefined, returns the original hours with a wide interval.
 *
 * Confidence interval width:
 *   high   → ±10%
 *   medium → ±25%
 *   low    → ±40%
 */
export function applyCorrection(estimatedHours, bucket) {
    if (bucket === undefined || bucket.sampleSize === 0) {
        return {
            correctedHours: Math.round(estimatedHours * 10) / 10,
            confidenceInterval: [
                Math.round(estimatedHours * 0.6 * 10) / 10,
                Math.round(estimatedHours * 1.4 * 10) / 10,
            ],
            calibrationBasis: 'no historical data — using raw estimate',
        };
    }
    const correctedHours = Math.round(estimatedHours * bucket.correctionFactor * 10) / 10;
    const spreadPct = bucket.confidence === 'high' ? 0.1 : bucket.confidence === 'medium' ? 0.25 : 0.4;
    const lo = Math.round(correctedHours * (1 - spreadPct) * 10) / 10;
    const hi = Math.round(correctedHours * (1 + spreadPct) * 10) / 10;
    const factorLabel = bucket.correctionFactor.toFixed(2);
    const calibrationBasis = `${String(bucket.sampleSize)} ${bucket.confidence}-confidence samples (${bucket.key}), factor ${factorLabel}x`;
    return {
        correctedHours,
        confidenceInterval: [lo, hi],
        calibrationBasis,
    };
}
// ---------------------------------------------------------------------------
// Calibration Trend helpers (used by estimation_accuracy_report)
// ---------------------------------------------------------------------------
/**
 * Group actuals into quarters and compute avgBias per quarter.
 * Returns quarters sorted chronologically.
 * Useful for detecting whether estimates improve over time (trending toward 1.0).
 */
export function computeCalibrationTrend(records) {
    if (records.length === 0) {
        return [];
    }
    const quarterGroups = new Map();
    for (const r of records) {
        const date = new Date(r.recordedAt);
        const year = date.getUTCFullYear();
        const quarter = Math.floor(date.getUTCMonth() / 3) + 1;
        const key = `${String(year)}-Q${String(quarter)}`;
        const existing = quarterGroups.get(key) ?? [];
        existing.push(r);
        quarterGroups.set(key, existing);
    }
    const trend = [];
    for (const [quarter, recs] of quarterGroups) {
        trend.push({
            quarter,
            sampleSize: recs.length,
            avgBias: Math.round(computeBias(recs) * 1000) / 1000,
        });
    }
    return trend.sort((a, b) => a.quarter.localeCompare(b.quarter));
}
//# sourceMappingURL=estimation-predictor.js.map