Files
confidence-engine/tests/graph/selection-influence-diagnostic.test.js
T

266 lines
8.2 KiB
JavaScript

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");
});
});