import { describe, expect, it } from "vitest"; import { calibrateUseCase } from "../../../../src/application/routing/calibrate.js"; import type { OutcomeSource } from "../../../../src/domain/ports/outcome-source.port.js"; import type { OutcomeWriter } from "../../../../src/domain/ports/outcome-writer.port.js"; import type { RoutingDecision } from "../../src/shared/value-objects/routing-decision.js"; import { RoutingOutcome } from "@tff/core"; const D1 = "00000000-0000-4000-8000-000000000001"; const decision: RoutingDecision = { decision_id: D1, agent: "reviewer", confidence: 0.9, signals: { complexity: "medium", risk: { level: "low", tags: ["auth"] } }, fallback_used: false, enriched: false, }; const outcomes: RoutingOutcome[] = Array.from({ length: 5 }, (_, i) => RoutingOutcome.create({ outcomeId: `00000000-0000-4000-8000-00000000010${i}`, decisionId: D1, dimension: "tier", verdict: "too-low", source: "model-judge", sliceId: "M01-S01", workflowId: "tff:ship", emittedAt: "2026-04-20T10:00:00.000Z", }), ); const emptySource: OutcomeSource = { async *readOutcomes() {} }; const outcomesSource: OutcomeSource = { async *readOutcomes() { for (const o of outcomes) yield o; }, }; const writer: OutcomeWriter = { append: async () => {} }; describe("calibrateUseCase — source_weights", () => { it("uses source_weights when provided", async () => { const report = await calibrateUseCase({ decisions: [decision], implicitSource: emptySource, outcomesSource, writer, config: { n_min: 2, source_weights: { manual: 1.0, "debug-join": 0.5, "model-judge": 0.5 }, }, now: () => "2026-04-20T10:00:00.000Z", }); // 5 model-judge outcomes × weight 0.5 = 2.5 effective_total ≥ n_min=2 → cell passes expect(report.cells.length).toBeGreaterThanOrEqual(1); const agentCell = report.cells.find((c) => c.key.kind === "agent"); expect(agentCell?.effective_total).toBeCloseTo(2.5); }); it("merges partial source_weights over defaults instead of zeroing untouched sources", async () => { const manualOutcome = RoutingOutcome.create({ outcomeId: "00000000-0000-4000-8000-0000000000b1", decisionId: D1, dimension: "tier", verdict: "ok", source: "manual", sliceId: "M01-S01", workflowId: "tff:ship", emittedAt: "2026-04-20T10:00:00.000Z", }); const modelJudgeOutcome = RoutingOutcome.create({ outcomeId: "00000000-0000-4000-8000-0000000000b2", decisionId: D1, dimension: "tier", verdict: "too-low", source: "model-judge", sliceId: "M01-S01", workflowId: "tff:ship", emittedAt: "2026-04-20T10:00:00.000Z", }); const mixedSource: OutcomeSource = { async *readOutcomes() { yield manualOutcome; yield modelJudgeOutcome; }, }; const report = await calibrateUseCase({ decisions: [decision], implicitSource: emptySource, outcomesSource: mixedSource, writer, config: { n_min: 1, // Partial: only model-judge specified. Manual must NOT be silently zeroed. source_weights: { "model-judge": 0.5 }, }, now: () => "2026-04-20T10:00:00.000Z", }); expect(report.source_weights).toEqual({ manual: 1.0, // from DEFAULT_WEIGHTS "debug-join": 0.5, // from DEFAULT_WEIGHTS "model-judge": 0.5, // overridden by user }); }); });