From ef6592cd9199976a5655b919990cfd44033f13d9 Mon Sep 17 00:00:00 2001 From: robbond Date: Wed, 30 Sep 2026 10:55:13 +0100 Subject: [PATCH] test(confidence-engine): add progressed investigation experiment fixture Co-Authored-By: Claude Code --- .../progressed-investigation-fixture.test.js | 189 ++++++++++++++ ...rogressed-investigation-manufacturing.json | 241 ++++++++++++++++++ 2 files changed, 430 insertions(+) create mode 100644 tests/experimental/progressed-investigation-fixture.test.js create mode 100644 tests/fixtures/progressed-investigation-manufacturing.json diff --git a/tests/experimental/progressed-investigation-fixture.test.js b/tests/experimental/progressed-investigation-fixture.test.js new file mode 100644 index 0000000..71c0835 --- /dev/null +++ b/tests/experimental/progressed-investigation-fixture.test.js @@ -0,0 +1,189 @@ +/** + * Progressed investigation fixture apparatus verification. + * + * Proves that the canonical progressed-investigation fixture: + * 1. loads successfully as valid JSON + * 2. contains >= 1 admitted known/supported evidence item + * 3. contains >= 1 unresolved Open Question (kind=unknown, status=unknown) + * 4. the unresolved Open Question is not admitted evidence + * 5. at least one eligible Finding exists (null disposition) + * 6. unresolved Open Question is not represented as an eligible Finding + * 7. resolvedNodeIds does NOT include any unknown node id + */ + +import { describe, it, expect } from "vitest"; +import fixture from "../fixtures/progressed-investigation-manufacturing.json"; + +describe("Progressed investigation fixture apparatus", () => { + /* ── 1. Fixture loads successfully ─────────────────── */ + + it("loads as valid JSON with required top-level keys", () => { + expect(fixture).toHaveProperty("success", true); + expect(fixture).toHaveProperty("stage", "update_applied"); + expect(fixture).toHaveProperty("situationGraph"); + expect(fixture.situationGraph).toHaveProperty("nodes"); + expect(fixture.situationGraph).toHaveProperty("edges"); + expect(fixture.situationGraph).toHaveProperty("activeUnknownNodeId"); + expect(fixture.situationGraph).toHaveProperty("resolvedNodeIds"); + expect(fixture.situationGraph).toHaveProperty("centralStatement"); + }); + + /* ── 2. Contains admitted evidence (>= 1 supported/known node) ─── */ + + it("contains at least one admitted known or supported evidence node", () => { + const admitted = fixture.situationGraph.nodes.filter( + (n) => n.status === "supported" || n.status === "known" + ); + expect(admitted.length).toBeGreaterThan(0); + }); + + it("admitted nodes are not unknown kind", () => { + const admitted = fixture.situationGraph.nodes.filter( + (n) => ["supported", "known"].includes(n.status) + ); + for (const node of admitted) { + expect(node.kind).not.toBe("unknown"); + } + }); + + /* ── 3. Contains unresolved Open Question (>= 1) ─────── */ + + it("contains at least one unresolved Open Question node", () => { + const unresolved = fixture.situationGraph.nodes.filter( + (n) => n.kind === "unknown" && n.status === "unknown" + ); + expect(unresolved.length).toBeGreaterThan(0); + }); + + /* ── 4. Unresolved Open Question is not admitted evidence ─── */ + + it("no node is simultaneously unresolved unknown AND admitted evidence", () => { + const allNodes = fixture.situationGraph.nodes; + for (const node of allNodes) { + if (node.kind === "unknown" && node.status === "unknown") { + // An unresolved unknown must not have admitted status + expect(["supported", "known"]).not.toContain(node.status); + } + } + }); + + it("unresolved and admitted node ID sets are disjoint", () => { + const unresolvedIds = new Set( + fixture.situationGraph.nodes + .filter((n) => n.kind === "unknown" && n.status === "unknown") + .map((n) => n.id) + ); + const admittedIds = new Set( + fixture.situationGraph.nodes + .filter((n) => ["supported", "known"].includes(n.status)) + .map((n) => n.id) + ); + for (const id of unresolvedIds) { + expect(admittedIds.has(id)).toBe(false); + } + }); + + it("unresolved unknown nodes are distinct from admitted evidence", () => { + const unresolvedIds = new Set( + fixture.situationGraph.nodes + .filter((n) => n.kind === "unknown" && n.status === "unknown") + .map((n) => n.id) + ); + const admittedIds = new Set( + fixture.situationGraph.nodes + .filter((n) => ["supported", "known"].includes(n.status)) + .map((n) => n.id) + ); + for (const id of unresolvedIds) { + expect(admittedIds.has(id)).toBe(false); + } + }); + + /* ── 5. Unresolved question excluded from resolvedNodeIds ─── */ + + it("resolvedNodeIds does not contain any unresolved unknown node id", () => { + const unresolvedIds = fixture.situationGraph.nodes + .filter((n) => n.kind === "unknown" && n.status === "unknown") + .map((n) => n.id); + for (const nodeId of unresolvedIds) { + expect(fixture.situationGraph.resolvedNodeIds).not.toContain(nodeId); + } + }); + + /* ── 6. Eligible Finding exists ─────────────────────── */ + + it("findings array exists and has eligible items", () => { + expect(fixture.findings).toBeDefined(); + expect(Array.isArray(fixture.findings)).toBe(true); + const eligible = fixture.findings.filter( + (f) => f.status === "provisional" && f.userDisposition === null + ); + expect(eligible.length).toBeGreaterThan(0); + }); + + /* ── 7. Unresolved Open Question is not represented as a Finding ─── */ + + it("unresolved unknown nodes are not mapped to findings propositions", () => { + const unresolvedNodeIds = new Set( + fixture.situationGraph.nodes + .filter((n) => n.kind === "unknown" && n.status === "unknown") + .map((n) => n.id) + ); + // originTargetNodeId links findings to unknown nodes + // A finding whose originatingTargetNodeId is an unresolved unknown is fine + // but the proposition should not BE the same as the node label + const eligibleFindings = fixture.findings.filter( + (f) => f.status === "provisional" && f.userDisposition === null + ); + const unresolvedNodes = fixture.situationGraph.nodes.filter( + (n) => n.kind === "unknown" && n.status === "unknown" + ); + + for (const finding of eligibleFindings) { + // The proposition is an admitted evidence item, not the unknown itself + expect(finding.proposition).not.toContain("Whether"); + // Verify the originating node is NOT resolved — it remains open + const targetNode = unresolvedNodes.find( + (n) => n.id === finding.originatingTargetNodeId + ); + if (targetNode) { + expect(targetNode.status).toBe("unknown"); + expect(targetNode.kind).toBe("unknown"); + } + } + }); + + /* ── 8. Canonical shape integrity ──────────────────── */ + + it("activeUnknownNodeId references a valid unresolved unknown node", () => { + const unresolvedIds = fixture.situationGraph.nodes + .filter((n) => n.kind === "unknown" && n.status === "unknown") + .map((n) => n.id); + expect(unresolvedIds).toContain(fixture.situationGraph.activeUnknownNodeId); + }); + + it("all nodes pass the canonical situationNode schema invariants", () => { + for (const node of fixture.situationGraph.nodes) { + expect(node).toHaveProperty("id"); + expect(node).toHaveProperty("label"); + expect(node).toHaveProperty("description"); + expect(["observation","reported_claim","metric","state","transition","relationship","assumption","unknown","conclusion","option"]).toContain(node.kind); + expect(["known","unknown","provisional","supported","weakened","contradicted","resolved"]).toContain(node.status); + expect(["low","medium","high"]).toContain(node.confidence); + expect(Array.isArray(node.evidenceIds)).toBe(true); + expect(Array.isArray(node.dependsOn)).toBe(true); + expect(Array.isArray(node.affects)).toBe(true); + } + }); + + it("all edges reference valid nodes", () => { + const nodeIds = new Set(fixture.situationGraph.nodes.map((n) => n.id)); + for (const edge of fixture.situationGraph.edges) { + expect(edge).toHaveProperty("id"); + expect(nodeIds.has(edge.fromNodeId)).toBe(true); + expect(nodeIds.has(edge.toNodeId)).toBe(true); + expect(edge).toHaveProperty("relationship"); + expect(edge).toHaveProperty("confidence"); + } + }); +}); diff --git a/tests/fixtures/progressed-investigation-manufacturing.json b/tests/fixtures/progressed-investigation-manufacturing.json new file mode 100644 index 0000000..5efd740 --- /dev/null +++ b/tests/fixtures/progressed-investigation-manufacturing.json @@ -0,0 +1,241 @@ +{ + "success": true, + "stage": "update_applied", + "situationGraph": { + "centralStatement": "Complaint counts increased while production also increased.", + "nodes": [ + { + "id": "n-obs-1", + "label": "Customer complaints rose by 35% over the last six months", + "description": "Customer complaints rose by 35% over the last six months", + "kind": "observation", + "status": "supported", + "confidence": "high", + "value": null, + "unit": null, + "evidenceIds": [ + "complaint-data" + ], + "dependsOn": [], + "affects": [], + "parentId": null, + "childIds": [] + }, + { + "id": "n-obs-2", + "label": "Production volume increased by 40% in the same period", + "description": "Production volume increased by 40% in the same period", + "kind": "observation", + "status": "supported", + "confidence": "high", + "value": null, + "unit": null, + "evidenceIds": [ + "production-data" + ], + "dependsOn": [], + "affects": [], + "parentId": null, + "childIds": [] + }, + { + "id": "n-obs-3", + "label": "A new CRM tagging system was introduced three months ago", + "description": "A new CRM tagging system was introduced three months ago, coinciding with the complaint category changes", + "kind": "observation", + "status": "known", + "confidence": "high", + "value": null, + "unit": null, + "evidenceIds": [ + "crm-audit" + ], + "dependsOn": [], + "affects": [], + "parentId": null, + "childIds": [] + }, + { + "id": "n-unknown-1", + "label": "Whether complaints increased per-unit or proportionally with production volume", + "description": "Need to know the complaint rate normalized by production volume to determine if quality deteriorated or if growth explains the rise", + "kind": "unknown", + "status": "unknown", + "confidence": "high", + "value": null, + "unit": null, + "evidenceIds": [], + "dependsOn": [], + "affects": [ + "n-conclusion" + ], + "parentId": null, + "childIds": [] + }, + { + "id": "n-unknown-2", + "label": "Whether the new CRM tagging system changed what gets counted as a complaint", + "description": "The complaint category changes may be an artifact of the new CRM system rather than genuine quality change", + "kind": "unknown", + "status": "unknown", + "confidence": "medium", + "value": null, + "unit": null, + "evidenceIds": [], + "dependsOn": [], + "affects": [ + "n-unknown-1" + ], + "parentId": null, + "childIds": [] + }, + { + "id": "n-conclusion", + "label": "Whether automated quality inspection investment is justified", + "description": "Decision node for whether to spend about £120,000 on automated quality inspection", + "kind": "state", + "status": "provisional", + "confidence": "medium", + "value": null, + "unit": null, + "evidenceIds": [], + "dependsOn": [ + "n-unknown-1" + ], + "affects": [], + "parentId": null, + "childIds": [] + }, + { + "id": "nz92pkx-summary", + "label": "Complaint counts increased while production also increased.", + "description": "Summary of the situation from the scenario text", + "kind": "state", + "status": "provisional", + "confidence": "medium", + "value": null, + "unit": null, + "evidenceIds": [], + "dependsOn": [ + "n-obs-1", + "n-obs-2", + "n-obs-3" + ], + "affects": [ + "n-conclusion" + ], + "parentId": null, + "childIds": [] + } + ], + "edges": [ + { + "id": "e-evidence-n-obs-1", + "fromNodeId": "n-obs-1", + "toNodeId": "nz92pkx-summary", + "relationship": "supports", + "confidence": "high", + "description": "Customer complaints rose by 35% over the last six months supports the summary" + }, + { + "id": "e-evidence-n-obs-2", + "fromNodeId": "n-obs-2", + "toNodeId": "nz92pkx-summary", + "relationship": "supports", + "confidence": "high", + "description": "Production volume increased by 40% in the same period supports the summary" + }, + { + "id": "e-evidence-n-obs-3", + "fromNodeId": "n-obs-3", + "toNodeId": "nz92pkx-summary", + "relationship": "supports", + "confidence": "high", + "description": "A new CRM tagging system was introduced three months ago supports the summary" + }, + { + "id": "e-unk-n-unknown-1", + "fromNodeId": "n-unknown-1", + "toNodeId": "nz92pkx-summary", + "relationship": "depends_on", + "confidence": "high", + "description": "Whether complaints increased per-unit or proportionally with production volume is an unresolved factor for this situation" + }, + { + "id": "e-unk-n-unknown-2", + "fromNodeId": "n-unknown-2", + "toNodeId": "nz92pkx-summary", + "relationship": "depends_on", + "confidence": "medium", + "description": "Whether the new CRM tagging system changed what gets counted as a complaint is an unresolved factor for this situation" + }, + { + "id": "e-unk-conclusion", + "fromNodeId": "n-unknown-1", + "toNodeId": "n-conclusion", + "relationship": "depends_on", + "confidence": "medium", + "description": "Whether complaints increased per-unit or proportionally with production volume affects the investment decision" + } + ], + "activeUnknownNodeId": "n-unknown-1", + "resolvedNodeIds": [], + "currentSummary": "Nodes: 4 observation, 2 unknown, 2 state | Edges: 6 total | Unknowns: 2 unresolved" + }, + "findings": [ + { + "id": "finding-complaint-rise", + "proposition": "Customer complaints have increased by 35% over the last six months while production volume grew by 40%.", + "status": "provisional", + "userDisposition": null, + "originatingTargetNodeId": "n-unknown-1", + "contributionId": "contrib-admission-1", + "sourceObservation": "user_input" + }, + { + "id": "finding-crm-change", + "proposition": "A new CRM tagging system was introduced three months ago, coinciding with complaint category changes.", + "status": "provisional", + "userDisposition": null, + "originatingTargetNodeId": "n-unknown-2", + "contributionId": "contrib-admission-2", + "sourceObservation": "user_input" + } + ], + "selectedQuestion": { + "nodeId": "n-unknown-1", + "question": "What is the complaint rate normalized by production volume, and has it increased per unit?", + "reason": "The admitted evidence shows absolute increases but does not establish whether quality deteriorated or growth explains the rise.", + "strategy": "evidence_gathering", + "investigationStrategy": { + "key": "evidence_gathering", + "reason": "Resolving the unknown requires normalized rate data.", + "nodeId": "n-unknown-1", + "nodeLabel": "Whether complaints increased per-unit or proportionally with production volume", + "meaning": "whether complaints increased per-unit or proportionally with production volume", + "actionPhrase": "deciding whether to spend about £120,000 on automated quality inspection now", + "relatedNodeIds": [ + "n-conclusion" + ], + "centralStatement": "Complaint counts increased while production also increased." + }, + "reasoningPattern": "decision", + "reasoningPatternReason": "Selected decision because the active unknown sits inside an investment justification context." + }, + "diagnostics": { + "promptVersion": "v0.4", + "modelName": "configured-model", + "validationStatus": "valid", + "nodeCount": 6, + "edgeCount": 6, + "graphReferenceValidation": { + "valid": true, + "errors": [] + }, + "unknownSelectionExplanation": { + "selectedNodeId": "n-unknown-1", + "status": "selected", + "tieType": "none" + } + } +} \ No newline at end of file