Phase 2: Recovery state components (ProviderUnavailableCard, MalformedResponseCard, UnexpectedStateCard, ContinueLaterBanner) with automatic error detection for provider/network/malformed/unexpected states. Phase 3: Session persistence via sessionStorage — save after each successful turn, restore on mount, clear on restart/reset. Continuelater banner shown when session is restored. Phase 4: InvestigationSummaryPanel component displaying current status, understanding summary, questions answered/remaining, investigation timestamps. Phase 5: docs/reasoning-contract-backlog.md documenting all mocked fields (60+ rows across 7 categories) with feature/UI need/mock/desired output/stage/notes columns. Also: wired onRestart through ReasoningWorkspace → ScenarioForm, fixed getErrorType scope issues, removed broken window.__restartInvestigation.
137 lines
8.0 KiB
JavaScript
137 lines
8.0 KiB
JavaScript
/**
|
|
* 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 Promise.resolve({ success:true, data:buildErrorFixture() });
|
|
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, 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 };
|