/** * Retrieval-class routing (Task 308). * * Consumes the `retrievalClass` field produced by Task 304's * gateway-classifier (landed) and returns a per-class index plan: which * labels to filter to, which fusion weights to favour, and whether to * skip the lookup entirely. * * No new Haiku call here — this module is a pure deterministic mapping * over the gateway's existing classification. */ export type RetrievalClass = | "entity" | "temporal" | "event" | "general" | "none"; export interface IndexPlan { /** When true, the caller short-circuits and returns no results. */ skip: boolean; /** Restrict vector + BM25 to these labels. Undefined means no filter. */ labelFilter?: string[]; /** Weight on the vector half of the (weighted-sum) fusion. */ vectorWeight: number; /** Weight on the BM25 half of the (weighted-sum) fusion. */ bm25Weight: number; } /** * Return the index plan for a retrieval class. Unknown values fall back * to `general` rather than throwing — the routing is a soft hint and the * caller's old behaviour is the safe default. */ export function pickIndexMix(retrievalClass: string | undefined): IndexPlan { switch (retrievalClass) { case "entity": return { skip: false, labelFilter: ["Person", "Company", "Concept"], vectorWeight: 0.85, bm25Weight: 0.15, }; case "temporal": return { skip: false, labelFilter: ["Event"], vectorWeight: 0.2, bm25Weight: 0.8, }; case "event": return { skip: false, labelFilter: ["Event"], vectorWeight: 0, bm25Weight: 1, }; case "none": return { skip: true, vectorWeight: 0, bm25Weight: 0 }; case "general": default: if (retrievalClass && retrievalClass !== "general") { process.stderr.write( `[graph-search:unknown-class] rc=${retrievalClass} fallback=general\n`, ); } return { skip: false, vectorWeight: 0.7, bm25Weight: 0.3 }; } }