import { describe, expect, it } from "vitest"; import { formulateQuestion } from "@/lib/graph/question-formulator.js"; import { makeEdge, makeGraph, makeNode } from "@/lib/graph/schema.js"; import { explainUnknownSelection } from "@/lib/graph/utils.js"; function buildLiveShapedGraph() { const summary = makeNode({ id: "nnvog0y", label: "Revenue grew by 18% while corporate cash reserves declined over an identical time frame.", description: "Summary of the situation from the scenario text", kind: "state", status: "provisional", confidence: "medium", }); const revenueObservation = makeNode({ id: "nri36w9", label: "Revenue increased by 18%.", description: "Revenue increased by 18%.", kind: "observation", status: "supported", confidence: "high", evidenceIds: ["obs_rev"], }); const cashObservation = makeNode({ id: "nnfc48j", label: "Cash in the bank decreased over the same period.", description: "Cash in the bank decreased over the same period.", kind: "observation", status: "supported", confidence: "high", evidenceIds: ["obs_cash"], }); const revenueMetric = makeNode({ id: "nhsd6d5", label: "Revenue metric (typically accrual-based income statement figure)", description: "Revenue metric (typically accrual-based income statement figure)", kind: "metric", status: "known", confidence: "high", }); const cashMetric = makeNode({ id: "neh5m6m", label: "Cash balance (liquidity measure on the balance sheet or cash flow statement)", description: "Cash balance (liquidity measure on the balance sheet or cash flow statement)", kind: "metric", status: "known", confidence: "high", }); const directionalRelationship = makeNode({ id: "nwo6070", label: "Divergent directional movement between top-line revenue growth and net cash position contraction.", description: "Divergent directional movement between top-line revenue growth and net cash position contraction.", kind: "relationship", status: "supported", confidence: "high", }); const contradictionRelationship = makeNode({ id: "nuiab02", label: "Apparent contradiction between profitability/revenue expansion and liquidity reduction.", description: "Apparent contradiction between profitability/revenue expansion and liquidity reduction.", kind: "relationship", status: "supported", confidence: "medium", }); const cashTiming = makeNode({ id: "niewza", label: "Whether revenue recognition timing differs from cash collection timing.", description: "Whether revenue recognition timing differs from cash collection timing.", kind: "unknown", status: "unknown", confidence: "high", }); const cashOutflows = makeNode({ id: "nqdzobz", label: "Magnitude and nature of cash outflows (operating expenses, debt repayments, capex, or working capital shifts).", description: "Magnitude and nature of cash outflows (operating expenses, debt repayments, capex, or working capital shifts).", kind: "unknown", status: "unknown", confidence: "high", }); const edges = [ makeEdge({ id: "e-revenue-summary", fromNodeId: revenueObservation.id, toNodeId: summary.id, relationship: "supports", description: "Revenue increase supports the scenario summary.", }), makeEdge({ id: "e-cash-summary", fromNodeId: cashObservation.id, toNodeId: summary.id, relationship: "supports", description: "Cash decline supports the scenario summary.", }), makeEdge({ id: "e-unk-niewza", fromNodeId: cashTiming.id, toNodeId: summary.id, relationship: "depends_on", description: "Whether revenue recognition timing differs from cash collection timing. is an unresolved factor for this situation", }), makeEdge({ id: "e-unk-nqdzobz", fromNodeId: cashOutflows.id, toNodeId: summary.id, relationship: "depends_on", description: "Magnitude and nature of cash outflows (operating expenses, debt repayments, capex, or working capital shifts). is an unresolved factor for this situation", }), ]; return makeGraph({ centralStatement: "Revenue increased by 18%, but cash in the bank fell over the same period.", nodes: [ summary, revenueObservation, cashObservation, revenueMetric, cashMetric, directionalRelationship, contradictionRelationship, cashTiming, cashOutflows, ], edges, activeUnknownNodeId: null, resolvedNodeIds: [], currentSummary: "Diagnostic selection influence fixture", }); } function orderCandidates(explanation) { return explanation.candidates.map((candidate) => ({ nodeId: candidate.nodeId, label: candidate.label, score: candidate.score, downstreamCount: candidate.downstreamCount, unresolvedParentUnknownCount: candidate.unresolvedParentUnknownCount, })); } function removeDependencyLinks(graph) { const nodes = graph.nodes.map((node) => ({ ...node, dependsOn: [], affects: [], parentId: null, childIds: [], })); const edges = (graph.edges || []).filter( (edge) => edge.relationship !== "depends_on", ); return makeGraph({ ...graph, nodes, edges, activeUnknownNodeId: null }); } function neutraliseUnknownWording(graph) { let counter = 0; const nodes = graph.nodes.map((node) => { if (node.kind !== "unknown") return { ...node }; counter += 1; return { ...node, label: `Unknown ${String.fromCharCode(64 + counter)}`, description: `Unknown factor ${counter} relevant to the scenario.`, }; }); return makeGraph({ ...graph, nodes, activeUnknownNodeId: null }); } describe("selection influence diagnostic", () => { it("records ordering changes for live-shaped, structure-only, and wording-neutralised fixtures", () => { const liveGraph = buildLiveShapedGraph(); const liveExplanation = explainUnknownSelection(liveGraph, []); const liveWinner = liveGraph.nodes.find( (node) => node.id === liveExplanation.selectedNodeId, ); const liveQuestion = formulateQuestion({ node: liveWinner, graph: liveGraph, }); const noLinksExplanation = explainUnknownSelection( removeDependencyLinks(liveGraph), [], ); const neutralWordingExplanation = explainUnknownSelection( neutraliseUnknownWording(liveGraph), [], ); const diagnosticRecord = { liveShapedCandidateOrdering: orderCandidates(liveExplanation), noLinksCandidateOrdering: orderCandidates(noLinksExplanation), neutralWordingCandidateOrdering: orderCandidates( neutralWordingExplanation, ), selectedExplanationContributions: liveExplanation.selected?.contributions ?? [], selectedInvestigationStrategy: liveQuestion.strategy, }; expect(diagnosticRecord.liveShapedCandidateOrdering).toEqual([ { nodeId: "nqdzobz", label: "Magnitude and nature of cash outflows (operating expenses, debt repayments, capex, or working capital shifts).", score: 0, downstreamCount: 0, unresolvedParentUnknownCount: 0, }, { nodeId: "niewza", label: "Whether revenue recognition timing differs from cash collection timing.", score: 0, downstreamCount: 0, unresolvedParentUnknownCount: 0, }, ]); expect(diagnosticRecord.noLinksCandidateOrdering).toEqual( diagnosticRecord.liveShapedCandidateOrdering, ); expect(diagnosticRecord.neutralWordingCandidateOrdering).toEqual([ { nodeId: "niewza", label: "Unknown A", score: 0, downstreamCount: 0, unresolvedParentUnknownCount: 0, }, { nodeId: "nqdzobz", label: "Unknown B", score: 0, downstreamCount: 0, unresolvedParentUnknownCount: 0, }, ]); expect(diagnosticRecord.selectedExplanationContributions).toEqual([ { rule: "downstream_dependencies", value: 0, weight: 4, delta: 0, }, ]); expect(diagnosticRecord.selectedInvestigationStrategy).toBe("definition"); }); });