import { groupOutcomes, runAllRules, type GroupOutcomesInput, type RoutingDecision, } from "@tff/core"; import type { OutcomeSource } from "../../domain/ports/outcome-source.port.js"; import type { OutcomeWriter } from "../../domain/ports/outcome-writer.port.js"; import type { CalibrationCell, CalibrationRecommendation, CalibrationReport, SkippedCell, } from "../../shared/value-objects/calibration-report.js"; import { computeImplicitOutcomesUseCase } from "./compute-outcomes.js"; export interface CalibrateConfig { n_min: number; /** Per-source weights. Missing sources fall back to defaults (manual: 1.0, debug-join: 0.5, model-judge: 1.0). */ source_weights?: Record; } export interface CalibrateDeps { decisions: RoutingDecision[]; implicitSource: OutcomeSource; outcomesSource: OutcomeSource; writer: OutcomeWriter; config: CalibrateConfig; now: () => string; } const DEFAULT_WEIGHTS: Record = { manual: 1.0, "debug-join": 0.5, "model-judge": 1.0, }; const resolveWeights = (config: CalibrateConfig): Record => { if (config.source_weights) { // Merge over defaults so partial maps don't silently zero out other sources. return { ...DEFAULT_WEIGHTS, ...config.source_weights }; } return DEFAULT_WEIGHTS; }; export const calibrateUseCase = async (deps: CalibrateDeps): Promise => { await computeImplicitOutcomesUseCase({ implicitSource: deps.implicitSource, existingOutcomesSource: deps.outcomesSource, writer: deps.writer, }); const outcomes: GroupOutcomesInput["outcomes"] = []; for await (const o of deps.outcomesSource.readOutcomes({})) { outcomes.push({ decision_id: o.decisionId, verdict: o.verdict, source: o.source, }); } const weights = resolveWeights(deps.config); const { byAgent, byTag } = groupOutcomes({ decisions: deps.decisions, outcomes, weights, }); const cells: CalibrationCell[] = []; const skipped: SkippedCell[] = []; const recommendations: CalibrationRecommendation[] = []; const collectCells = (cellsByKey: Map) => { for (const cell of cellsByKey.values()) { if (cell.effective_total < deps.config.n_min) { skipped.push({ key: cell.key, total: cell.total, reason: `insufficient evidence (effective_N=${cell.effective_total}, need N_min=${deps.config.n_min})`, }); } else { cells.push(cell); recommendations.push(...runAllRules(cell, { n_min: deps.config.n_min })); } } }; collectCells(byAgent); collectCells(byTag); return { generated_at: deps.now(), n_min: deps.config.n_min, source_weights: weights, decisions_scanned: deps.decisions.length, outcomes_scanned: outcomes.length, cells, recommendations, skipped_cells: skipped, }; };