/** * Mock client — intercepts fetch calls when mock mode is enabled. * Replaces the real Ollama-powered API with pre-recorded scenario fixtures. * Pure ESM + browser-compatible (no require(), no Node-only APIs). */ import { buildScenarioFixture, AVAILABLE_SCENARIOS } from "@/lib/mocks/scenarios.js"; /* ── helpers ─────────────────────────────────────────────── */ function getMockFlag() { if (typeof window !== "undefined") return !!window.__MOCK_ENABLED; return process.env.NEXT_PUBLIC_CONFIDENCE_ENGINE_MOCKS === "true"; } function getDelay() { var d = typeof window !== "undefined" ? window.__MOCK_DELAY : process.env.NEXT_PUBLIC_CONFIDENCE_MOCK_DELAY; if (d === "instant") return 0; if (d === "slow") return 2500; return 700; } function getScenario() { var s = typeof window !== "undefined" ? window.__MOCK_SCENARIO : process.env.NEXT_PUBLIC_CONFIDENCE_ENGINE_MOCK_SCENARIO; return s || ""; } /* ── node / edge factories (re-exported for scenario files) ─ */ export function mkNode(id, label, opts) { var kind = (opts && opts.kind) || "unknown"; var status = (opts && opts.status) || (kind === "unknown" ? "unknown" : "known"); var confidence = (opts && opts.confidence) || "low"; return { id:id, label:label, description:label, kind:kind, status:status, confidence:confidence, confidenceAssessment:{ evidenceConfidence:confidence, completenessStatus:"partial", conclusionConfidence:confidence }, value:(opts && opts.value !== undefined) ? opts.value : null, unit:(opts && opts.unit) || null, evidenceIds:[], dependsOn:[], affects:[], childIds:[] }; } export function mkEdge(id, a, b, rel) { var r = rel || "supports"; return { id:id, fromNodeId:a, toNodeId:b, relationship:r, confidence:"medium", description:a+" -> "+b }; } /* ── fixture builders ─────────────────────────────────────── */ function buildErrorFixture() { return { success:false, situationGraph:null, selectedQuestion:null, noQuestionReason:null, newlySurfacedNodeIds:[], error:"Mock provider error: structured response unavailable.", diagnostics:null }; } /* ── delay shim (browser only) ──────────────────────────── */ function delay(ms) { return new Promise(function(r) { if (typeof setTimeout === "function") setTimeout(r, ms); else r(); // server fallback — skip wait }); } /* ── case handlers ──────────────────────────────────────── */ var _turnIndex = 0; function handleStartCase(scenario) { _turnIndex = 0; var scenarioName = getScenario(); if (scenarioName === "error") return delay(getDelay()).then(function() { return Promise.resolve({ success:true, data:buildErrorFixture() }); }); return delay(getDelay()).then(function() { return Promise.resolve({ success:true, data: buildScenarioFixture(scenarioName, 0) || buildDefaultFallback(0) }); }); } function handleUpdateCase(data) { var scenarioName = getScenario(); if (scenarioName === "error") return delay(getDelay()).then(function() { return Promise.resolve({ success:true, data:{ success:false, stage:"provider", error:"Mock provider error: structured response unavailable.", providerErrors:["Mock provider error: structured response unavailable."], updatedSituationGraph:null, selectedQuestion:null, affectedNodeIds:[], resolvedUnknownNodeIds:[], changesApplied:null, summary:null, diagnostics:{ promptVersion:"v0.4", modelName:"mock-ollama", responseDurationMs:0 } } }); }); _turnIndex++; return delay(getDelay()).then(function() { var fixture = buildScenarioFixture(scenarioName, _turnIndex); if (fixture) { return Promise.resolve({ success:true, stage:"update_applied", updatedSituationGraph:fixture.situationGraph, selectedQuestion:fixture.selectedQuestion, affectedNodeIds:[], resolvedUnknownNodeIds:(fixture.situationGraph.resolvedNodeIds||[]).slice(), changesApplied:{ addedNodeCount:0, updatedNodeCount:0, addedEdgeCount:0, removedEdgeCount:0 }, summary:fixture.situationGraph.currentSummary||null, diagnostics:fixture.diagnostics }); } // Fallback to original default if scenario not found return Promise.resolve({ success:true, data:buildUpdateFallback(scenarioName) }); }); } /* ── fallback for when scenarios.js is not available ─────── */ var _fallbackTurns = [ { nodes:[mkNode("obs-1","Complaints increased by 35%",{kind:"observation",status:"known",confidence:"high"}),mkNode("obs-2","Production increased by 40%",{kind:"observation",status:"known",confidence:"high"}),mkNode("state-1","Current situation",{kind:"state",status:"provisional",confidence:"medium"})], edges:[mkEdge("e-1","obs-1","state-1"),mkEdge("e-2","obs-2","state-1")], resolved:[], active:"u-1", question:{ nodeId:"u-1", question:"Were the complaint and production figures measured over the same period?", reason:"If different periods, comparing movement could be misleading.", reasoningPattern:"comparability_check" }, noQReason:null, summary:"Two changes have been reported, but we do not yet know whether the figures are directly comparable." }, { nodes:[mkNode("obs-1","Complaints increased by 35%",{kind:"observation",status:"known",confidence:"high"}),mkNode("obs-2","Production increased by 40%",{kind:"observation",status:"known",confidence:"high"}),mkNode("obs-3","Both figures cover the same three-month period",{kind:"observation",status:"known",confidence:"high"}),mkNode("state-1","Current situation",{kind:"state",status:"provisional",confidence:"medium"})], edges:[mkEdge("e-1","obs-1","state-1"),mkEdge("e-2","obs-2","state-1"),mkEdge("e-3","obs-3","u-1")], resolved:["u-1"], active:"u-2", question:{ nodeId:"u-2", question:"Were both percentages calculated from comparable baseline counts?", reason:"Establishing the reference point is essential.", reasoningPattern:"baseline_comparability" }, noQReason:null, summary:"The timing basis is now clear." } ]; function buildDefaultFallback(idx) { var d = _fallbackTurns[Math.min(idx, _fallbackTurns.length - 1)]; return { success:true, summary:d.summary || null, situationGraph:{ centralStatement:"Complaints increased by 35% while production increased by 40%.", currentSummary:d.summary, nodes:d.nodes, edges:d.edges, activeUnknownNodeId:d.active, resolvedNodeIds:d.resolved }, selectedQuestion:d.question||null, noQuestionReason:d.noQReason, newlySurfacedNodeIds:[], diagnostics:{ promptVersion:"v0.4", modelName:"mock-ollama", responseDurationMs:0, validationStatus:"valid", nodeCount:d.nodes.length, edgeCount:d.edges.length } }; } function buildUpdateFallback(scenarioName) { var f = buildDefaultFallback(Math.min(_turnIndex, _fallbackTurns.length - 1)); return { success:true, stage:"update_applied", updatedSituationGraph:f.situationGraph, selectedQuestion:f.selectedQuestion, affectedNodeIds:[], resolvedUnknownNodeIds:(f.situationGraph.resolvedNodeIds||[]).slice(), changesApplied:{ addedNodeCount:0, updatedNodeCount:0, addedEdgeCount:0, removedEdgeCount:0 }, summary:f.situationGraph.currentSummary||null, diagnostics:f.diagnostics }; } /* ── public intercept function ──────────────────────────── */ export async function mockFetch(url, options) { if (!getMockFlag()) return fetch(url, options); var body = null; if (options && options.body) { try { body = JSON.parse(options.body); } catch(_) {} } if (url.indexOf("/api/cases/start") === 0 && options && options.method === "POST") { var r1 = await handleStartCase(body && body.scenario); return new Response(JSON.stringify(r1.data), { status: r1.success ? 200 : 500, headers:{ "Content-Type":"application/json" } }); } if (url.indexOf("/api/cases/update") === 0 && options && options.method === "POST") { var r2 = await handleUpdateCase(body); return new Response(JSON.stringify(r2.data), { status: r2.data.success ? 200 : 500, headers:{ "Content-Type":"application/json" } }); } return fetch(url, options); } export { AVAILABLE_SCENARIOS };