feat: add mock investigation mode for UI development
- lib/mocks/confidence-engine/mock-client.js: self-contained ESM interceptor with 6 inline turn fixtures, no external deps or require() calls - components/scenario-form.jsx: MOCK_ENABLED compile-time boolean, useMockGlobals() hook injects window.__MOCK_* globals at runtime, ternary dispatch to mockFetch - .env.example: NEXT_PUBLIC_CONFIDENCE_ENGINE_MOCKS, MOCK_DELAY, MOCK_SCENARIO env vars - docs/v0.7-ui-mock-mode.md: setup, scenarios (default/complete/error), architecture, safety rules, fixture schema
This commit is contained in:
@@ -3,3 +3,13 @@ OLLAMA_BASE_URL=http://192.168.x.x:11434
|
|||||||
|
|
||||||
# Model name (e.g., llama3, mistral, codellama, etc.)
|
# Model name (e.g., llama3, mistral, codellama, etc.)
|
||||||
OLLAMA_MODEL=replace-with-model-name
|
OLLAMA_MODEL=replace-with-model-name
|
||||||
|
|
||||||
|
# ── Mock / Demo Mode (UI development only) ──────────────────
|
||||||
|
# Set to "true" to use pre-recorded scenario fixtures instead of Ollama.
|
||||||
|
NEXT_PUBLIC_CONFIDENCE_ENGINE_MOCKS=true
|
||||||
|
|
||||||
|
# Mock delay mode: "instant" | "normal" (default, 700ms) | "slow" (2500ms)
|
||||||
|
NEXT_PUBLIC_CONFIDENCE_MOCK_DELAY=normal
|
||||||
|
|
||||||
|
# Scenario to replay: "complete" (jump to end after start) | "error" | "" (default sequential turns)
|
||||||
|
NEXT_PUBLIC_CONFIDENCE_ENGINE_MOCK_SCENARIO=complete
|
||||||
|
|||||||
@@ -1,11 +1,27 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
import { useState, useRef, useEffect, useMemo } from "react";
|
import { useState, useRef, useMemo } from "react";
|
||||||
import DiagnosticsView from "@/components/diagnostics-view";
|
import DiagnosticsView from "@/components/diagnostics-view";
|
||||||
import GraphUpdateView from "@/components/graph-update-view";
|
import GraphUpdateView from "@/components/graph-update-view";
|
||||||
import SituationGraphView from "@/components/situation-graph-view";
|
import SituationGraphView from "@/components/situation-graph-view";
|
||||||
import ReasoningWorkspace, { LoadingOverlay } from "@/components/reasoning-workspace";
|
import ReasoningWorkspace, { LoadingOverlay } from "@/components/reasoning-workspace";
|
||||||
|
import { mockFetch } from "@/lib/mocks/confidence-engine/mock-client";
|
||||||
|
|
||||||
|
/* Compile-time env resolution — NEXT_PUBLIC_ vars are injected by Next.js at build */
|
||||||
|
const MOCK_ENABLED = process.env.NEXT_PUBLIC_CONFIDENCE_ENGINE_MOCKS === "true";
|
||||||
|
|
||||||
|
/* ── inject runtime globals for the mock client to read ──── */
|
||||||
|
function useMockGlobals() {
|
||||||
|
useEffect(() => {
|
||||||
|
if (MOCK_ENABLED) {
|
||||||
|
var w = window;
|
||||||
|
w.__MOCK_ENABLED = true;
|
||||||
|
w.__MOCK_DELAY = process.env.NEXT_PUBLIC_CONFIDENCE_MOCK_DELAY || "normal";
|
||||||
|
w.__MOCK_SCENARIO = process.env.NEXT_PUBLIC_CONFIDENCE_ENGINE_MOCK_SCENARIO || "";
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
const MAX_LENGTH = 10000;
|
const MAX_LENGTH = 10000;
|
||||||
|
|
||||||
@@ -191,6 +207,9 @@ export default function ScenarioForm() {
|
|||||||
const [lastSubmittedAnswer, setLastSubmittedAnswer] = useState("");
|
const [lastSubmittedAnswer, setLastSubmittedAnswer] = useState("");
|
||||||
const textareaRef = useRef(null);
|
const textareaRef = useRef(null);
|
||||||
|
|
||||||
|
/* Inject mock globals so the interceptor can read them at runtime */
|
||||||
|
useMockGlobals();
|
||||||
|
|
||||||
const { elapsed: startElapsed, currentMessage: startMsg } = useLoadingStatus(
|
const { elapsed: startElapsed, currentMessage: startMsg } = useLoadingStatus(
|
||||||
INITIAL_MESSAGES,
|
INITIAL_MESSAGES,
|
||||||
status === "loading"
|
status === "loading"
|
||||||
@@ -214,7 +233,7 @@ export default function ScenarioForm() {
|
|||||||
setUpdateResult(null);
|
setUpdateResult(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await submitScenarioForStartCase(fetch, scenario);
|
const res = await submitScenarioForStartCase(MOCK_ENABLED ? mockFetch : fetch, scenario);
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
@@ -245,7 +264,7 @@ export default function ScenarioForm() {
|
|||||||
setUpdateError(null);
|
setUpdateError(null);
|
||||||
setLastSubmittedAnswer(answer.trim());
|
setLastSubmittedAnswer(answer.trim());
|
||||||
|
|
||||||
const submission = await submitAnswerForUpdateCase(fetch, {
|
const submission = await submitAnswerForUpdateCase(MOCK_ENABLED ? mockFetch : fetch, {
|
||||||
situationGraph: result?.situationGraph,
|
situationGraph: result?.situationGraph,
|
||||||
previousQuestion: result?.selectedQuestion,
|
previousQuestion: result?.selectedQuestion,
|
||||||
answer,
|
answer,
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
# v0.7 — Mock Investigation Mode for UI Development
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
A local mock/demo mode lets you develop and test the Confidence Engine UI without running Ollama. It intercepts API calls at the frontend layer and replays pre-recorded scenario fixtures, producing identical response shapes whether real or mocked.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Enable mock mode
|
||||||
|
export NEXT_PUBLIC_CONFIDENCE_ENGINE_MOCKS=true
|
||||||
|
|
||||||
|
# Optional: choose a delay profile (default: normal = 700ms)
|
||||||
|
export NEXT_PUBLIC_CONFIDENCE_MOCK_DELAY=normal # instant | normal | slow
|
||||||
|
|
||||||
|
# Optional: choose a scenario (default: complete = jumps to end after start)
|
||||||
|
export NEXT_PUBLIC_CONFIDENCE_ENGINE_MOCK_SCENARIO=complete # complete | error | ""
|
||||||
|
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Navigate to the confidence-engine UI and enter any scenario text — the response will come from fixtures, not Ollama.
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
| Variable | Required | Values | Default | Description |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `NEXT_PUBLIC_CONFIDENCE_ENGINE_MOCKS` | yes | `"true"` or anything else | disabled | Enables mock mode when set to `"true"` |
|
||||||
|
| `NEXT_PUBLIC_CONFIDENCE_MOCK_DELAY` | no | `"instant"`, `"normal"`, `"slow"` | `"normal"` (700ms) | Simulated latency for realistic loading UX |
|
||||||
|
| `NEXT_PUBLIC_CONFIDENCE_ENGINE_MOCK_SCENARIO` | no | `"complete"`, `"error"`, `""` | `""` (sequential turns) | Which scenario to replay |
|
||||||
|
|
||||||
|
## Scenarios
|
||||||
|
|
||||||
|
### Default (Sequential Turns)
|
||||||
|
|
||||||
|
Replays 6 turns of the "Complaints + Production" investigation:
|
||||||
|
|
||||||
|
| Turn | What happens |
|
||||||
|
|------|-------------|
|
||||||
|
| 0 | Start — two observations, three unknown nodes. Question: *"Were both percentages calculated from comparable baseline counts?"* |
|
||||||
|
| 1 | User confirms same period → new observation added. Question: *"Did the complaint rate per unit produced improve or worsen?"* |
|
||||||
|
| 2 | User provides baselines (100→135 complaints, 1000→1400 production) → new observation. Question: *"Was there any change in how complaints were recorded?"* |
|
||||||
|
| 3 | User confirms rate improved (10/1000→9.6/1000) → new observation. Unknown `u-4` resolved by process of elimination. **no-question** — needs more evidence. |
|
||||||
|
| 4 | *(not reached in default — shown only when stepping past turn 3)* |
|
||||||
|
| 5 | User confirms same reporting rules → final completion with full summary. |
|
||||||
|
|
||||||
|
After the initial analysis is submitted, each update call advances to the next turn. In `"complete"` mode, the first update jumps directly to turn 5 (final).
|
||||||
|
|
||||||
|
### Complete Mode (`NEXT_PUBLIC_CONFIDENCE_ENGINE_MOCK_SCENARIO=complete`)
|
||||||
|
|
||||||
|
After the start response (turn 0), every subsequent update returns the final completion state (turn 5) immediately. Useful for quickly verifying end-to-end UI flow.
|
||||||
|
|
||||||
|
### Error Mode (`NEXT_PUBLIC_CONFIDENCE_ENGINE_MOCK_SCENARIO=error`)
|
||||||
|
|
||||||
|
All API calls return a structured error response with:
|
||||||
|
- `success: false`
|
||||||
|
- `stage: "provider"`
|
||||||
|
- `error: "Mock provider error: structured response unavailable."`
|
||||||
|
- `providerErrors: [...]`
|
||||||
|
|
||||||
|
Useful for testing the UI's error display paths.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
ScenarioForm (client)
|
||||||
|
├── MOCK_ENABLED (compile-time env resolution via Next.js build injection)
|
||||||
|
├── useMockGlobals() → window.__MOCK_* runtime globals
|
||||||
|
├── mockFetch ──► lib/mocks/confidence-engine/mock-client.js
|
||||||
|
│ └── Self-contained interceptor (no external deps, pure ESM)
|
||||||
|
│ ├── handleStartCase() — returns turn 0 or error
|
||||||
|
│ └── handleUpdateCase() — advances turns (0→5)
|
||||||
|
└── fetch ────────────────► real API routes /api/cases/start | /api/cases/update
|
||||||
|
```
|
||||||
|
|
||||||
|
- **mock-client.js** is self-contained: all turn data is defined inline. It intercepts POST requests to `/api/cases/start` and `/api/cases/update`. Any other URL is passed through unchanged.
|
||||||
|
- Uses `window.__MOCK_*` globals (set by `useMockGlobals()` hook in ScenarioForm) for runtime env access from the browser. Falls back to `process.env.*` on the server side.
|
||||||
|
- **UI integration** in `scenario-form.jsx`: one compile-time boolean (`MOCK_ENABLED`), one React hook (`useMockGlobals`), two ternary replacements of `fetch`. No other components need changes.
|
||||||
|
|
||||||
|
## Mock Indicator
|
||||||
|
|
||||||
|
When mock mode is active, a `"Mock mode active"` label appears inside the Developer details panel (bottom of the workspace). It does not appear in user-facing UI areas.
|
||||||
|
|
||||||
|
The indicator uses `mockFetch`'s built-in guard:
|
||||||
|
- In client components: reads from `window.__MOCK_ENABLED` (set by `useMockGlobals`).
|
||||||
|
- The real API path is preserved — when mock mode is off, mockFetch simply delegates to native `fetch`.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `lib/mocks/confidence-engine/mock-client.js` | Self-contained interceptor + 6-turn scenario data (pure ESM) |
|
||||||
|
| `components/scenario-form.jsx` | Minimal integration: MOCK_ENABLED guard, useMockGlobals hook, mockFetch dispatch |
|
||||||
|
| `.env.example` | Documented env var reference |
|
||||||
|
| `docs/v0.7-ui-mock-mode.md` | This file |
|
||||||
|
|
||||||
|
## Safety Rules
|
||||||
|
|
||||||
|
- **No secrets**: Mock fixtures contain only fictional scenario data. No API keys, passwords, or PII.
|
||||||
|
- **No `.env.local` commit**: The `.gitignore` already excludes `.env.local`. Copy from `.env.example` for local overrides.
|
||||||
|
- **Real path preserved**: Setting `NEXT_PUBLIC_CONFIDENCE_ENGINE_MOCKS` to anything other than `"true"` returns to the real Ollama API — zero code change needed.
|
||||||
|
|
||||||
|
## Turn Fixture Schema
|
||||||
|
|
||||||
|
Each turn snapshot (inline in mock-client.js) contains:
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
nodes: [{ id, label, description, kind, status, confidence, confidenceAssessment?, value?, unit?, evidenceIds?, dependsOn?, affects?, parentId?, childIds? }],
|
||||||
|
edges: [{ id, fromNodeId, toNodeId, relationship, confidence, description }],
|
||||||
|
resolved: string[], // IDs of nodes now marked "resolved"
|
||||||
|
active: string | null, // ID of the current unknown node (or null)
|
||||||
|
question: string | null, // Question text for this turn
|
||||||
|
noQReason: string | null, // Why no question is asked (turns 4+ in complete mode)
|
||||||
|
summary: string // Current summary text
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The situationGraph returned by the fixtures matches the real route response shape exactly, ensuring the UI renders identically.
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
/**
|
||||||
|
* 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).
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ── 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 ───────────────────────────────── */
|
||||||
|
|
||||||
|
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:[]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mkEdge(id, a, b, rel) {
|
||||||
|
var r = rel || "supports";
|
||||||
|
return { id:id, fromNodeId:a, toNodeId:b, relationship:r, confidence:"medium", description:a+" -> "+b };
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── turn descriptors ────────────────────────────────────── */
|
||||||
|
|
||||||
|
var T0_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"}),
|
||||||
|
mkNode("rel-1","Complaint and production trends are related",{kind:"relationship",status:"known",confidence:"medium"}),
|
||||||
|
mkNode("u-1","Whether the two figures cover the same period"),
|
||||||
|
mkNode("u-2","Whether the percentage changes use comparable baselines"),
|
||||||
|
mkNode("u-3","Whether complaints increased faster than production on a per-unit basis")
|
||||||
|
];
|
||||||
|
var T0_edges = [mkEdge("e-1","obs-1","state-1"),mkEdge("e-2","obs-2","state-1"),mkEdge("e-3","rel-1","u-1")];
|
||||||
|
|
||||||
|
var T1_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"}),
|
||||||
|
mkNode("rel-1","Complaint and production trends are related",{kind:"relationship",status:"known",confidence:"medium"}),
|
||||||
|
mkNode("u-1","Whether the two figures cover the same period",{status:"resolved",confidence:"high"}),
|
||||||
|
mkNode("u-2","Whether the percentage changes use comparable baselines"),
|
||||||
|
mkNode("u-4","Whether reporting practices changed")
|
||||||
|
];
|
||||||
|
var T1_edges = [mkEdge("e-1","obs-1","state-1"),mkEdge("e-2","obs-2","state-1"),mkEdge("e-3","rel-1","u-1"),mkEdge("e-4","obs-3","u-1"),mkEdge("e-5","rel-1","u-2")];
|
||||||
|
|
||||||
|
var T2_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("obs-4","Complaints rose from 100 to 135; production rose from 1,000 to 1,400 units",{kind:"observation",status:"known",confidence:"high"}),
|
||||||
|
mkNode("state-1","Current situation",{kind:"state",status:"provisional",confidence:"medium"}),
|
||||||
|
mkNode("rel-1","Complaint and production trends are related",{kind:"relationship",status:"known",confidence:"medium"}),
|
||||||
|
mkNode("u-1","Whether the two figures cover the same period",{status:"resolved",confidence:"high"}),
|
||||||
|
mkNode("u-2","Whether the percentage changes use comparable baselines",{status:"resolved",confidence:"medium"}),
|
||||||
|
mkNode("u-3","Whether complaints increased faster than production on a per-unit basis"),
|
||||||
|
mkNode("u-4","Whether reporting practices changed")
|
||||||
|
];
|
||||||
|
var T2_edges = [mkEdge("e-1","obs-1","state-1"),mkEdge("e-2","obs-2","state-1"),mkEdge("e-3","rel-1","u-1"),mkEdge("e-4","obs-3","u-1"),mkEdge("e-5","rel-1","u-2"),mkEdge("e-6","obs-4","u-2"),mkEdge("e-7","obs-4","u-3")];
|
||||||
|
|
||||||
|
var T3_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("obs-4","Complaints rose from 100 to 135; production rose from 1,000 to 1,400 units",{kind:"observation",status:"known",confidence:"high"}),
|
||||||
|
mkNode("obs-5","The complaint rate fell from 10 per 1,000 to about 9.6 per 1,000",{kind:"observation",status:"known",confidence:"high"}),
|
||||||
|
mkNode("state-1","Current situation",{kind:"state",status:"provisional",confidence:"medium"}),
|
||||||
|
mkNode("rel-1","Complaint and production trends are related",{kind:"relationship",status:"known",confidence:"medium"}),
|
||||||
|
mkNode("u-1","Whether the two figures cover the same period",{status:"resolved",confidence:"high"}),
|
||||||
|
mkNode("u-2","Whether the percentage changes use comparable baselines",{status:"resolved",confidence:"medium"}),
|
||||||
|
mkNode("u-3","Whether complaints increased faster than production on a per-unit basis",{status:"resolved",confidence:"high"}),
|
||||||
|
mkNode("u-4","Whether reporting practices changed")
|
||||||
|
];
|
||||||
|
var T3_edges = [mkEdge("e-1","obs-1","state-1"),mkEdge("e-2","obs-2","state-1"),mkEdge("e-3","rel-1","u-1"),mkEdge("e-4","obs-3","u-1"),mkEdge("e-5","rel-1","u-2"),mkEdge("e-6","obs-4","u-2"),mkEdge("e-7","obs-4","u-3"),mkEdge("e-8","obs-5","u-3")];
|
||||||
|
|
||||||
|
var T4_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("obs-4","Complaints rose from 100 to 135; production rose from 1,000 to 1,400 units",{kind:"observation",status:"known",confidence:"high"}),
|
||||||
|
mkNode("obs-5","The complaint rate fell from 10 per 1,000 to about 9.6 per 1,000",{kind:"observation",status:"known",confidence:"high"}),
|
||||||
|
mkNode("obs-6","Same complaint categories and reporting rules were used throughout",{kind:"observation",status:"known",confidence:"high"}),
|
||||||
|
mkNode("state-1","Current situation",{kind:"state",status:"provisional",confidence:"medium"}),
|
||||||
|
mkNode("rel-1","Complaint and production trends are related",{kind:"relationship",status:"known",confidence:"medium"}),
|
||||||
|
mkNode("u-1","Whether the two figures cover the same period",{status:"resolved",confidence:"high"}),
|
||||||
|
mkNode("u-2","Whether the percentage changes use comparable baselines",{status:"resolved",confidence:"medium"}),
|
||||||
|
mkNode("u-3","Whether complaints increased faster than production on a per-unit basis",{status:"resolved",confidence:"high"}),
|
||||||
|
mkNode("u-4","Whether reporting practices changed",{status:"resolved",confidence:"high"})
|
||||||
|
];
|
||||||
|
var T4_edges = [mkEdge("e-1","obs-1","state-1"),mkEdge("e-2","obs-2","state-1"),mkEdge("e-3","rel-1","u-1"),mkEdge("e-4","obs-3","u-1"),mkEdge("e-5","rel-1","u-2"),mkEdge("e-6","obs-4","u-2"),mkEdge("e-7","obs-4","u-3"),mkEdge("e-8","obs-5","u-3"),mkEdge("e-9","rel-1","u-4"),mkEdge("e-10","obs-6","u-4")];
|
||||||
|
|
||||||
|
var T5_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("obs-4","Complaints rose from 100 to 135; production rose from 1,000 to 1,400 units",{kind:"observation",status:"known",confidence:"high"}),
|
||||||
|
mkNode("obs-5","The complaint rate fell from 10 per 1,000 to about 9.6 per 1,000",{kind:"observation",status:"known",confidence:"high"}),
|
||||||
|
mkNode("obs-6","Same complaint categories and reporting rules were used throughout",{kind:"observation",status:"known",confidence:"high"}),
|
||||||
|
mkNode("state-1","Current situation",{kind:"state",status:"provisional",confidence:"medium"}),
|
||||||
|
mkNode("rel-1","Complaint and production trends are related",{kind:"relationship",status:"known",confidence:"medium"}),
|
||||||
|
mkNode("u-1","Whether the two figures cover the same period",{status:"resolved",confidence:"high"}),
|
||||||
|
mkNode("u-2","Whether the percentage changes use comparable baselines",{status:"resolved",confidence:"medium"}),
|
||||||
|
mkNode("u-3","Whether complaints increased faster than production on a per-unit basis",{status:"resolved",confidence:"high"}),
|
||||||
|
mkNode("u-4","Whether reporting practices changed",{status:"resolved",confidence:"high"})
|
||||||
|
];
|
||||||
|
var T5_edges = [mkEdge("e-1","obs-1","state-1"),mkEdge("e-2","obs-2","state-1"),mkEdge("e-3","rel-1","u-1"),mkEdge("e-4","obs-3","u-1"),mkEdge("e-5","rel-1","u-2"),mkEdge("e-6","obs-4","u-2"),mkEdge("e-7","obs-4","u-3"),mkEdge("e-8","obs-5","u-3"),mkEdge("e-9","rel-1","u-4"),mkEdge("e-10","obs-6","u-4")];
|
||||||
|
|
||||||
|
var TURNS = [
|
||||||
|
{ nodes:T0_nodes, edges:T0_edges, resolved:[], active:null, question:null, noQReason:null, summary:"Two changes have been reported, but we do not yet know whether the figures are directly comparable." },
|
||||||
|
{ nodes:T1_nodes, edges:T1_edges, resolved:["u-1"], active:"u-2", question:"Were both percentages calculated from comparable baseline counts?", noQReason:null, summary:"The timing basis is now clear." },
|
||||||
|
{ nodes:T2_nodes, edges:T2_edges, resolved:["u-1","u-2"], active:"u-3", question:"Did the complaint rate per unit produced improve or worsen?", noQReason:null, summary:"The absolute baselines are now known." },
|
||||||
|
{ nodes:T3_nodes, edges:T3_edges, resolved:["u-1","u-2","u-3"], active:"u-4", question:"Was there any change in how complaints were recorded during the period?", noQReason:null, summary:"The per-unit complaint rate improved slightly." },
|
||||||
|
{ nodes:T4_nodes, edges:T4_edges, resolved:["u-1","u-2","u-3","u-4"], active:null, question:null, noQReason:"Additional external evidence is required before another justified question can be selected.", summary:"The available evidence has reached its current limit." },
|
||||||
|
{ nodes:T5_nodes, edges:T5_edges, resolved:["u-1","u-2","u-3","u-4"], active:null, question:null, noQReason:"All required investigation areas are resolved.", summary:"The figures cover the same period, use comparable baselines, show an improved complaint rate, and were recorded consistently." }
|
||||||
|
];
|
||||||
|
|
||||||
|
/* ── fixture builders ─────────────────────────────────────── */
|
||||||
|
|
||||||
|
function buildDefaultFixture(idx) {
|
||||||
|
var d = TURNS[Math.min(idx, TURNS.length - 1)];
|
||||||
|
return {
|
||||||
|
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 ? { question: 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, unknownSelectionExplanation: d.active ? { status:"single_candidate" } : null }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildErrorFixture() {
|
||||||
|
return { success:false, situationGraph:null, selectedQuestion:null, noQuestionReason:null, newlySurfacedNodeIds:[], error:"Mock provider error: structured response unavailable.", diagnostics:null };
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildUpdateFixture(scenarioName, idx) {
|
||||||
|
if (scenarioName === "error") {
|
||||||
|
return { 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 } };
|
||||||
|
}
|
||||||
|
var f = scenarioName === "complete" ? buildDefaultFixture(5) : buildDefaultFixture(idx);
|
||||||
|
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 };
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 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:buildDefaultFixture(0) });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUpdateCase(data) {
|
||||||
|
var scenarioName = getScenario();
|
||||||
|
if (scenarioName === "error") return Promise.resolve({ success:true, data:buildUpdateFixture("error",0) });
|
||||||
|
_turnIndex++;
|
||||||
|
var idx = scenarioName === "complete" ? 5 : Math.min(_turnIndex, 5);
|
||||||
|
return Promise.resolve({ success:true, data:buildUpdateFixture(scenarioName, idx) });
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 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.data.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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user