/**
 * Recall eval harness.
 *
 * Given a corpus of (query, expected_memory_ids) cases, run recall against
 * the store and report ranking-quality metrics: MRR, Recall@K, NDCG@K.
 *
 * The goal is to make recall quality measurable so MMR lambda, embedding
 * weights, and future scoring tweaks can be tuned against evidence instead
 * of intuition.
 */
import type { MemoryEntry } from './memory.js';
export interface EvalCase {
    /** Free-form ID for humans to reference the case. */
    id: string;
    /** The query text to run through recall. */
    query: string;
    /** Memory IDs considered relevant. At least one required. */
    expectedIds: string[];
    /** Optional short description so a failure report is self-explaining. */
    description?: string;
}
export interface EvalCaseResult {
    case: EvalCase;
    returnedIds: string[];
    /** 1 / rank of the first expected id, else 0. */
    mrr: number;
    /** |expected ∩ returned[0..K]| / |expected|, 0 when expected is empty. */
    recallAt5: number;
    recallAt10: number;
    /** Normalized DCG at 10 using binary relevance (expected = 1, else 0). */
    ndcgAt10: number;
}
export interface EvalSummary {
    cases: EvalCaseResult[];
    /** Simple arithmetic means across cases. */
    meanMrr: number;
    meanRecallAt5: number;
    meanRecallAt10: number;
    meanNdcgAt10: number;
    /** Wall-clock runtime in ms for the whole eval. */
    durationMs: number;
}
export interface RunEvalOptions {
    mmr?: boolean;
    mmrLambda?: number;
    embeddingWeight?: number;
    /** Max returned results per case. Larger than K-at-10 so metrics stay honest. */
    budget?: number;
    hippoRoot?: string;
    /** When set and initialized, eval runs through searchBothHybrid so global
     *  memories are in scope. Otherwise only the `entries` list is searched. */
    globalRoot?: string;
    /** Multiplier for local-over-global results when globalRoot is in play. */
    localBump?: number;
    now?: Date;
}
/** Mean Reciprocal Rank for a single ranking given expected ids. */
export declare function mrr(returned: string[], expected: string[]): number;
/** Recall@K — fraction of expected items found in the top-K. */
export declare function recallAtK(returned: string[], expected: string[], k: number): number;
/**
 * Normalized Discounted Cumulative Gain at K with binary relevance.
 * gain_i = 1 if returned[i] ∈ expected else 0. discount = log2(i + 2).
 */
export declare function ndcgAtK(returned: string[], expected: string[], k: number): number;
export declare function runEval(cases: EvalCase[], entries: MemoryEntry[], options?: RunEvalOptions): Promise<EvalSummary>;
export interface EvalDelta {
    mrr: number;
    recallAt5: number;
    recallAt10: number;
    ndcgAt10: number;
}
export interface CaseDelta {
    id: string;
    query: string;
    mrrBefore: number;
    mrrAfter: number;
    r10Before: number;
    r10After: number;
    ndcgBefore: number;
    ndcgAfter: number;
}
export interface EvalComparison {
    aggregate: EvalDelta;
    improved: CaseDelta[];
    regressed: CaseDelta[];
    unchanged: number;
    onlyInBaseline: string[];
    onlyInCurrent: string[];
}
/**
 * Compute pairwise deltas between a baseline and a current eval summary.
 * Cases are matched by EvalCase.id; any mismatch is surfaced in the onlyIn
 * arrays so the caller knows the corpora diverged.
 */
export declare function compareSummaries(baseline: EvalSummary, current: EvalSummary): EvalComparison;
/**
 * For each memory, take its first 8 content words as a trivial query and
 * expect that memory back. Useful as a smoke test: if recall can't find a
 * memory by its own opening words, something is broken.
 */
export declare function bootstrapCorpus(entries: MemoryEntry[], maxCases?: number): EvalCase[];
//# sourceMappingURL=eval.d.ts.map