/** * Experiment 24A — Tests for assessEvidenceDirection. * * Validates evidence direction classification (supports, contradicts, * informs, cannot_determine) against explicit conditions and the * long-investigation scenario fixture. */ import { describe, expect, it } from "vitest"; import { assessEvidenceDirection } from "@/lib/graph/evidence-direction.js"; import { buildScenarioFixture } from "@/lib/mocks/scenarios.js"; /* ── Helpers ─────────────────────────────────────────────── */ const DECISION_CONDITIONS = [ "Credible customer demand exists in Europe", "European compliance is achievable", "The expected market value justifies the cost of entry", "The product offers sufficient competitive differentiation", ]; function makeNode(id, label, opts = {}) { return { id, label, description: opts.description || label, kind: opts.kind || "observation", status: opts.status || "resolved", }; } /* ── supports: evidence confirms the condition ───────────── */ describe("assessEvidenceDirection — supports", () => { it("returns supports when observation text contains supporting keywords for demand", () => { const evidence = makeNode("obs-2", "European analytics SaaS market valued at approximately €8B and growing 15% annually"); const result = assessEvidenceDirection({ condition: { text: DECISION_CONDITIONS[0] }, evidenceNode: evidence, }); expect(result.direction).toBe("supports"); expect(typeof result.reason).toBe("string"); expect(result.reason.length).toBeGreaterThan(0); }); it("returns supports when observation contains a clear supporting keyword for differentiation", () => { const evidence = makeNode("obs-5", "Our real-time collaboration feature has no direct European equivalent"); const result = assessEvidenceDirection({ condition: { text: DECISION_CONDITIONS[3] }, evidenceNode: evidence, }); expect(result.direction).toBe("supports"); }); }); /* ── contradicts: evidence directly weakens the condition ─ */ describe("assessEvidenceDirection — contradicts", () => { it("returns contradicts when observation contains a contradiction phrase for compliance", () => { const evidence = makeNode("obs-3", "Our platform does not support EU data residency requirements"); const result = assessEvidenceDirection({ condition: { text: DECISION_CONDITIONS[1] }, evidenceNode: evidence, }); expect(result.direction).toBe("contradicts"); }); }); /* ── informs: evidence provides relevant context only ───── */ describe("assessEvidenceDirection — informs", () => { it("returns informs when observation is relevant but contains only neutral keywords", () => { const evidence = makeNode("obs-4", "Achieving compliance would require approximately 6 months and $500K engineering investment"); const result = assessEvidenceDirection({ condition: { text: DECISION_CONDITIONS[2] }, evidenceNode: evidence, }); expect(result.direction).toBe("informs"); }); it("returns informs when observation is related but does not strongly support or contradict", () => { const evidence = makeNode("obs-1", "Current revenue is $2M ARR in the US market only"); const result = assessEvidenceDirection({ condition: { text: "cost" }, evidenceNode: evidence, }); expect(result.direction).toBe("informs"); }); }); /* ── cannot_determine: insufficient data ─────────────────── */ describe("assessEvidenceDirection — cannot_determine", () => { it("returns cannot_determine when condition is missing", () => { const evidence = makeNode("obs-2", "European analytics SaaS market valued at €8B"); const result = assessEvidenceDirection({ evidenceNode: evidence }); expect(result.direction).toBe("cannot_determine"); }); it("returns cannot_determine when condition is null", () => { const result = assessEvidenceDirection({ condition: null, evidenceNode: makeNode("t-1", "test") }); expect(result.direction).toBe("cannot_determine"); }); it("returns cannot_determine when evidenceNode is missing", () => { const result = assessEvidenceDirection({ condition: { text: "demand" } }); expect(result.direction).toBe("cannot_determine"); }); it("returns cannot_determine when evidenceNode is null", () => { const result = assessEvidenceDirection({ condition: { text: "demand" }, evidenceNode: null, }); expect(result.direction).toBe("cannot_determine"); }); it("returns cannot_determine when condition has empty text", () => { const result = assessEvidenceDirection({ condition: { text: "" }, evidenceNode: makeNode("t-1", "test"), }); expect(result.direction).toBe("cannot_determine"); }); it("returns cannot_determine when evidenceNode has empty description", () => { const result = assessEvidenceDirection({ condition: { text: DECISION_CONDITIONS[0] }, evidenceNode: makeNode("t-1", ""), }); expect(result.direction).toBe("cannot_determine"); }); it("returns cannot_determine when neither input is provided", () => { const result = assessEvidenceDirection({}); expect(result.direction).toBe("cannot_determine"); }); }); /* ── Deterministic output ───────────────────────────────── */ describe("assessEvidenceDirection — determinism", () => { it("returns identical results for identical inputs across multiple calls", () => { const evidence = makeNode("det-1", "European analytics market growing 15% annually"); const input = { condition: { text: DECISION_CONDITIONS[0] }, evidenceNode: evidence, }; const r1 = assessEvidenceDirection(input); const r2 = assessEvidenceDirection(input); expect(r1).toEqual(r2); }); it("deterministic across 5 calls with long-investigation data", () => { const fixture = buildScenarioFixture("long", 2); const obs3Node = makeNode("obs-3", "Our platform does not support EU data residency requirements"); const input = { condition: { text: DECISION_CONDITIONS[1] }, evidenceNode: obs3Node, }; const results = [1, 2, 3, 4, 5].map(() => assessEvidenceDirection(input)); for (let i = 1; i < results.length; i++) { expect(results[i]).toEqual(results[0]); } }); }); /* ── Input immutability ─────────────────────────────────── */ describe("assessEvidenceDirection — input immutability", () => { it("does not mutate the condition object", () => { const cond = { text: DECISION_CONDITIONS[0] }; const originalText = cond.text; const evidence = makeNode("imm-1", "European market valued at €8B"); assessEvidenceDirection({ condition: cond, evidenceNode: evidence }); expect(cond.text).toBe(originalText); }); it("does not mutate the evidenceNode object", () => { const node = makeNode("imm-2", "test description"); const originalDesc = node.description; const condition = { text: DECISION_CONDITIONS[3] }; assessEvidenceDirection({ condition, evidenceNode: node }); expect(node.description).toBe(originalDesc); }); }); /* ── Four long-investigation examples ───────────────────── */ describe("assessEvidenceDirection — long investigation examples", () => { it("demand example: supports (obs-2)", () => { const fixture = buildScenarioFixture("long", 1); const graphNodes = fixture.situationGraph.nodes; const obs2Node = graphNodes.find((n) => n.id === "obs-2"); expect(obs2Node).toBeDefined(); const result = assessEvidenceDirection({ condition: { text: DECISION_CONDITIONS[0] }, evidenceNode: obs2Node, }); expect(result.direction).toBe("supports"); }); it("compliance example: contradicts (obs-3)", () => { const fixture = buildScenarioFixture("long", 2); const graphNodes = fixture.situationGraph.nodes; const obs3Node = graphNodes.find((n) => n.id === "obs-3"); expect(obs3Node).toBeDefined(); const result = assessEvidenceDirection({ condition: { text: DECISION_CONDITIONS[1] }, evidenceNode: obs3Node, }); expect(result.direction).toBe("contradicts"); }); it("value versus cost example: informs (obs-4)", () => { const fixture = buildScenarioFixture("long", 3); const graphNodes = fixture.situationGraph.nodes; const obs4Node = graphNodes.find((n) => n.id === "obs-4"); expect(obs4Node).toBeDefined(); const result = assessEvidenceDirection({ condition: { text: DECISION_CONDITIONS[2] }, evidenceNode: obs4Node, }); expect(result.direction).toBe("informs"); }); it("differentiation example: supports (obs-5)", () => { const fixture = buildScenarioFixture("long", 4); const graphNodes = fixture.situationGraph.nodes; const obs5Node = graphNodes.find((n) => n.id === "obs-5"); expect(obs5Node).toBeDefined(); const result = assessEvidenceDirection({ condition: { text: DECISION_CONDITIONS[3] }, evidenceNode: obs5Node, }); expect(result.direction).toBe("supports"); }); }); /* ── Unrelated evidence ─────────────────────────────────── */ describe("assessEvidenceDirection — unrelated evidence", () => { it("returns informs when evidence is about a different concept area", () => { const evidence = makeNode("unrel-1", "Our team has 15 engineers in London"); const result = assessEvidenceDirection({ condition: { text: DECISION_CONDITIONS[0] }, evidenceNode: evidence, }); expect(result.direction).toBe("informs"); }); it("returns informs when observation contains no category-matching keywords", () => { const evidence = makeNode("unrel-2", "The office lease expires in December 2026"); const result = assessEvidenceDirection({ condition: { text: DECISION_CONDITIONS[1] }, evidenceNode: evidence, }); expect(result.direction).toBe("informs"); }); });