{"version":3,"file":"optimizer.d.ts","sourceRoot":"","sources":["../../../src/core/routing/optimizer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAE5E,MAAM,WAAW,iBAAiB;IACjC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAChD,gBAAgB,EAAE,MAAM,CAAC;IACzB,sDAAsD;IACtD,iBAAiB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IAClC,MAAM,EAAE,sBAAsB,CAAC;IAC/B,MAAM,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACnD,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,YAAY,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACpC,YAAY,EAAE,MAAM,EAAE,EACtB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,EAC/C,gBAAgB,EAAE,MAAM,EACxB,iBAAiB,EAAE,MAAM,GACvB,kBAAkB,CA4DpB;AA+BD,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,iBAAiB,GAAG,MAAM,CAIjE","sourcesContent":["/**\n * Conservative offline policy optimization.\n *\n * Generates a candidate routing policy from validated evaluation artifacts.\n * The optimizer adjusts rankings, identifies dominated candidates, suggests\n * thresholds/rules, and identifies evidence gaps. It NEVER activates a policy:\n * promotion requires an explicit gate.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { sha256, stableStringify, writePolicy } from \"./store.js\";\nimport type { CandidateEvidence, RoutingPolicyCandidate } from \"./types.js\";\n\nexport interface OptimizationInput {\n\tcandidateIds: string[];\n\tevidenceById: Record<string, CandidateEvidence>;\n\tevaluatorVersion: string;\n\t/** Immutable content-addressable dataset identity. */\n\tsourceDatasetHash: string;\n}\n\nexport interface OptimizationResult {\n\tpolicy: RoutingPolicyCandidate;\n\tranked: { candidateId: string; quality: number }[];\n\tdominatedCandidateIds: string[];\n\tevidenceGaps: string[];\n}\n\n/**\n * Derive a candidate policy from an immutable evidence dataset.\n * Quality is a deterministic blend of correctness, reliability and safety,\n * penalized by uncertainty. No candidate self-report is used as a label.\n */\nexport function generateRoutingPolicy(\n\tcandidateIds: string[],\n\tevidenceById: Record<string, CandidateEvidence>,\n\tevaluatorVersion: string,\n\tsourceDatasetHash: string,\n): OptimizationResult {\n\tconst ranked: { candidateId: string; quality: number }[] = [];\n\tconst evidenceGaps: string[] = [];\n\n\tfor (const id of candidateIds) {\n\t\tconst ev = evidenceById[id];\n\t\tif (!ev) {\n\t\t\tevidenceGaps.push(id);\n\t\t\tcontinue;\n\t\t}\n\t\tconst safety = ev.safetyRate ?? 0;\n\t\tconst quality =\n\t\t\t(ev.correctnessRate ?? 0) * 0.4 + (ev.reliabilityRate ?? 0) * 0.3 + safety * 0.3 - (ev.flakyRate ?? 0) * 0.3;\n\t\tranked.push({\n\t\t\tcandidateId: id,\n\t\t\tquality: Math.max(0, quality) * (1 - responseUncertainty(ev) * 0.2),\n\t\t});\n\t}\n\n\tranked.sort((a, b) => b.quality - a.quality || a.candidateId.localeCompare(b.candidateId));\n\n\t// Dominance: a candidate is dominated if some other candidate is >= on\n\t// correctness, reliability AND safety with strict improvement on one, and both\n\t// have sufficient samples.\n\tconst dominatedCandidateIds = findDominated(candidateIds, evidenceById, ranked);\n\n\tconst suggestedRules: string[] = [];\n\tif (ranked.length > 0 && ranked[0]) {\n\t\tsuggestedRules.push(`prefer ${ranked[0].candidateId} when evidence compatible`);\n\t}\n\tif (dominatedCandidateIds.length > 0) {\n\t\tsuggestedRules.push(`exclude dominated: ${dominatedCandidateIds.join(\", \")}`);\n\t}\n\n\tconst policyId = randomUUID();\n\tconst content = stableStringify({\n\t\tpolicyId,\n\t\tevaluatorVersion,\n\t\tsourceDatasetHash,\n\t\tranked,\n\t\tdominatedCandidateIds,\n\t\tevidenceGaps,\n\t});\n\tconst policy: RoutingPolicyCandidate = {\n\t\tpolicyId,\n\t\tpolicyVersion: 1,\n\t\tsourceDatasetHash,\n\t\tevaluatorVersion,\n\t\tgeneratedAt: new Date().toISOString(),\n\t\tstatus: \"draft\",\n\t\tpreferences: ranked,\n\t\tsuggestedRules,\n\t\tdominatedCandidateIds,\n\t\tevidenceGaps,\n\t\thash: sha256(content),\n\t\tcontent,\n\t};\n\n\twritePolicy(policy);\n\treturn { policy, ranked, dominatedCandidateIds, evidenceGaps };\n}\n\nfunction findDominated(\n\tcandidateIds: string[],\n\tevidenceById: Record<string, CandidateEvidence>,\n\t_ranked: { candidateId: string; quality: number }[],\n): string[] {\n\tconst dominated: string[] = [];\n\tfor (const id of candidateIds) {\n\t\tconst ev = evidenceById[id];\n\t\tif (!ev || ev.sampleCount < 5) continue;\n\t\tfor (const otherId of candidateIds) {\n\t\t\tif (otherId === id) continue;\n\t\t\tconst other = evidenceById[otherId];\n\t\t\tif (!other || other.sampleCount < 5) continue;\n\t\t\tconst dominates =\n\t\t\t\t(other.correctnessRate ?? 0) >= (ev.correctnessRate ?? 0) &&\n\t\t\t\t(other.reliabilityRate ?? 0) >= (ev.reliabilityRate ?? 0) &&\n\t\t\t\t(other.safetyRate ?? 0) >= (ev.safetyRate ?? 0) &&\n\t\t\t\t((other.correctnessRate ?? 0) > (ev.correctnessRate ?? 0) ||\n\t\t\t\t\t(other.reliabilityRate ?? 0) > (ev.reliabilityRate ?? 0) ||\n\t\t\t\t\t(other.safetyRate ?? 0) > (ev.safetyRate ?? 0));\n\t\t\tif (dominates) {\n\t\t\t\tdominated.push(id);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\treturn [...new Set(dominated)];\n}\n\nexport function responseUncertainty(ev: CandidateEvidence): number {\n\tlet u = Math.max(0, 1 - Math.min(1, ev.sampleCount / 30));\n\tu = Math.max(0, Math.min(1, u + (ev.flakyRate ?? 0) * 0.5));\n\treturn u;\n}\n"]}