feat: separate confidence from reasoning completeness

This commit is contained in:
2026-08-03 07:05:20 +01:00
parent 49765e95a0
commit 1d64144e01
8 changed files with 489 additions and 19 deletions
+179
View File
@@ -0,0 +1,179 @@
import { describe, expect, it } from "vitest";
import { applyValidatedProposal } from "@/lib/graph/apply-proposal.js";
import { makeEdge, makeGraph, makeNode } from "@/lib/graph/schema.js";
function makeFixture() {
const parent = makeNode({
id: "n-parent",
label: "Explanation for why revenue increased while cash fell",
description:
"Need to understand what change or event could explain why these observations differ, because that is needed to investigate their relationship.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const children = [
makeNode({
id: "n-child-1",
label: "How the two observations were measured",
description: "Need evidence about the measure used for each observation.",
kind: "unknown",
status: "unknown",
confidence: "medium",
parentId: parent.id,
}),
makeNode({
id: "n-child-2",
label: "Whether the two observations reflect different timing",
description:
"Need to know whether the two observations reflect different timing.",
kind: "unknown",
status: "unknown",
confidence: "medium",
parentId: parent.id,
}),
makeNode({
id: "n-child-3",
label: "Possible change mainly affecting revenue",
description:
"Need to know whether a possible change mainly affected revenue.",
kind: "unknown",
status: "unknown",
confidence: "medium",
parentId: parent.id,
}),
makeNode({
id: "n-child-4",
label: "Possible one-off event during the period",
description:
"Need to know whether a possible one-off event happened during the period.",
kind: "unknown",
status: "unknown",
confidence: "medium",
parentId: parent.id,
}),
];
return makeGraph({
centralStatement: "Revenue increased while cash fell.",
nodes: [parent, ...children],
edges: children.map((child, index) =>
makeEdge({
id: `e-${index + 1}`,
fromNodeId: child.id,
toNodeId: parent.id,
relationship: "depends_on",
description: `${child.label} feeds the parent.`,
}),
),
activeUnknownNodeId: "n-child-1",
resolvedNodeIds: [],
currentSummary: "confidence propagation fixture",
});
}
function makeProposal({ resolvedIds, contradictedIds = [] }) {
return {
addedNodes: [
makeNode({
id: "n-anchor",
label: "Update anchor",
description:
"Anchor state introduced by the answer because the update must contain a meaningful change.",
kind: "state",
status: "known",
confidence: "low",
}),
],
updatedNodes: [
...resolvedIds.map((id) => ({
nodeId: id,
previousStatus: "unknown",
newStatus: "resolved",
previousValue: null,
newValue: `answer:${id}`,
reason: "resolved child",
})),
...contradictedIds.map((id) => ({
nodeId: id,
previousStatus: "unknown",
newStatus: "contradicted",
previousValue: null,
newValue: `contradiction:${id}`,
reason: "contradictory child evidence",
})),
],
addedEdges: [],
removedEdgeIds: [],
resolvedUnknownNodeIds: resolvedIds,
affectedNodeIds: [],
selectedQuestion: null,
};
}
describe("confidence propagation", () => {
it("one of four children resolved does not yield high conclusion confidence", () => {
const result = applyValidatedProposal({
situationGraph: makeFixture(),
proposal: makeProposal({ resolvedIds: ["n-child-1"] }),
previousQuestion:
"What evidence would clarify how the two observations were measured?",
answer: "Same accounting period and same management accounts.",
});
const parent = result.updatedSituationGraph.nodes.find(
(n) => n.id === "n-parent",
);
expect(parent.status).toBe("provisional");
expect(parent.confidence).toBe("medium");
expect(parent.confidenceAssessment).toEqual({
evidenceConfidence: "high",
completenessStatus: "partial",
conclusionConfidence: "medium",
});
expect(result.confidenceCapReason).toBe(
"unresolved_direct_children_cap_conclusion",
);
});
it("all children resolved with coherent evidence may yield high confidence", () => {
const result = applyValidatedProposal({
situationGraph: makeFixture(),
proposal: makeProposal({
resolvedIds: ["n-child-1", "n-child-2", "n-child-3", "n-child-4"],
}),
previousQuestion:
"What evidence would clarify how the two observations were measured?",
answer: "All direct child questions are answered.",
});
const parent = result.updatedSituationGraph.nodes.find(
(n) => n.id === "n-parent",
);
expect(parent.status).toBe("resolved");
expect(parent.confidenceAssessment).toEqual({
evidenceConfidence: "high",
completenessStatus: "complete",
conclusionConfidence: "high",
});
});
it("contradictory child evidence prevents high confidence", () => {
const result = applyValidatedProposal({
situationGraph: makeFixture(),
proposal: makeProposal({
resolvedIds: ["n-child-1"],
contradictedIds: ["n-child-2"],
}),
previousQuestion:
"What evidence would clarify how the two observations were measured?",
answer: "One child resolved, another contradicted.",
});
const parent = result.updatedSituationGraph.nodes.find(
(n) => n.id === "n-parent",
);
expect(parent.confidenceAssessment.conclusionConfidence).toBe("low");
expect(result.confidenceCapReason).toBe("contradictory_direct_children");
});
});
+31 -4
View File
@@ -255,7 +255,16 @@ describe("upward propagation", () => {
expect(result.parentStatusBefore).toBe("unknown");
expect(result.parentStatusAfter).toBe("provisional");
expect(result.parentConfidenceBefore).toBe("medium");
expect(result.parentConfidenceAfter).toBe("high");
expect(result.parentConfidenceAfter).toBe("medium");
expect(result.evidenceConfidenceBefore).toBe("medium");
expect(result.evidenceConfidenceAfter).toBe("high");
expect(result.completenessBefore).toBe("empty");
expect(result.completenessAfter).toBe("partial");
expect(result.conclusionConfidenceBefore).toBe("low");
expect(result.conclusionConfidenceAfter).toBe("medium");
expect(result.confidenceCapReason).toBe(
"unresolved_direct_children_cap_conclusion",
);
expect(result.parentResolved).toBe(false);
expect(result.affectedAncestorIds).toContain(ids.parent);
expect(result.affectedAncestorIds).toContain(ids.ancestor);
@@ -276,7 +285,12 @@ describe("upward propagation", () => {
);
expect(parentNode).toMatchObject({
status: "provisional",
confidence: "high",
confidence: "medium",
confidenceAssessment: {
evidenceConfidence: "high",
completenessStatus: "partial",
conclusionConfidence: "medium",
},
});
const ancestorNode = result.updatedSituationGraph.nodes.find(
@@ -284,7 +298,12 @@ describe("upward propagation", () => {
);
expect(ancestorNode).toMatchObject({
status: "provisional",
confidence: "high",
confidence: "low",
confidenceAssessment: {
evidenceConfidence: "low",
completenessStatus: "empty",
conclusionConfidence: "low",
},
});
const resolvedChild = result.updatedSituationGraph.nodes.find(
@@ -387,6 +406,14 @@ describe("upward propagation", () => {
expect(result.resolvedUnknownNodeIds).toContain(ids.parent);
expect(
result.updatedSituationGraph.nodes.find((node) => node.id === ids.parent),
).toMatchObject({ status: "resolved", confidence: "high" });
).toMatchObject({
status: "resolved",
confidence: "high",
confidenceAssessment: {
evidenceConfidence: "high",
completenessStatus: "complete",
conclusionConfidence: "high",
},
});
});
});