Feature/product platform foundation v0.62 #1

Merged
robbond merged 683 commits from feature/product-platform-foundation-v0.62 into feature/emergent-unknowns-v0.5 2026-09-09 07:58:20 +01:00
8 changed files with 489 additions and 19 deletions
Showing only changes of commit 1d64144e01 - Show all commits
+5
View File
@@ -66,6 +66,11 @@ export default function GraphUpdateView({ updateResult }) {
<div className="text-xs text-gray-600">
{node.kind} · {node.confidence}
</div>
{node.confidenceAssessment && (
<div className="text-xs text-gray-600">
evidence {node.confidenceAssessment.evidenceConfidence} · completeness {node.confidenceAssessment.completenessStatus} · conclusion {node.confidenceAssessment.conclusionConfidence}
</div>
)}
{(update?.previousStatus || update?.newStatus || node.status) && (
<div className="text-xs text-gray-700">
{update?.previousStatus ? `Previous status: ${update.previousStatus}` : null}
+11
View File
@@ -40,6 +40,11 @@ function NodeGroup({
<span className="font-medium text-gray-900">{node.label}</span>
<NodeBadge tone="blue">{node.status}</NodeBadge>
<NodeBadge tone="green">{node.confidence}</NodeBadge>
{node.confidenceAssessment?.completenessStatus && (
<NodeBadge tone="purple">
completeness: {node.confidenceAssessment.completenessStatus}
</NodeBadge>
)}
{resolvedNodeIds.has(node.id) && (
<NodeBadge tone="red">resolved unknown</NodeBadge>
)}
@@ -59,6 +64,12 @@ function NodeGroup({
{node.description && node.description !== node.label && (
<p className="mt-1 text-gray-600">{node.description}</p>
)}
{node.confidenceAssessment && (
<p className="mt-1 text-xs text-gray-500">
evidence: {node.confidenceAssessment.evidenceConfidence} ·
conclusion: {node.confidenceAssessment.conclusionConfidence}
</p>
)}
</li>
))}
</ul>
+19 -1
View File
@@ -119,6 +119,8 @@ rather than asking the full broad explanation node directly.
Recursive reasoning is complete only when decomposition and reconstruction are both deterministic.
Confidence must not outrun completeness or evidence.
For this experiment, reconstruction now behaves as follows:
- when a child unknown resolves, that child keeps its own resolved status and answer evidence
@@ -132,10 +134,26 @@ For the current conservative completion rule:
- **one resolved child** → parent becomes `provisional` with higher confidence, but remains unresolved
- **all direct child unknowns resolved** → parent resolves deterministically with `high` confidence
The confidence model is now explicitly separated into:
- **evidence confidence**: how trustworthy the currently attached support is
- **completeness**: whether the required direct child structure is empty, partial, or complete
- **conclusion confidence**: how strongly the current parent state is justified given both evidence and completeness
Deterministic propagation rules now enforce:
- one resolved child may raise evidence confidence
- unresolved direct children cap conclusion confidence
- contradictory direct children block high conclusion confidence
- duplicate evidence does not increase confidence
- status changes do not raise confidence on their own
- parent resolution still requires the separate completion rule
Example progression:
- parent before: `unknown`, `medium`
- after resolving `How the two observations were measured`: parent becomes `provisional`, `high`
- after resolving `How the two observations were measured`: parent becomes `provisional`, `medium`
- evidence confidence becomes `high`, completeness becomes `partial`, conclusion confidence becomes `medium`
- next sibling becomes selectable and the engine moves on without recreating the resolved child
## Interpretation
+170 -14
View File
@@ -439,6 +439,30 @@ function appendUniqueValue(values = [], nextValue) {
: values;
}
function getNodeConfidenceAssessment(node) {
return (
node?.confidenceAssessment || {
evidenceConfidence: node?.confidence ?? "medium",
completenessStatus:
node?.status === "resolved"
? "complete"
: node?.status === "provisional"
? "partial"
: "empty",
conclusionConfidence:
node?.status === "resolved"
? (node?.confidence ?? "high")
: node?.status === "provisional"
? (node?.confidence ?? "medium")
: "low",
}
);
}
function confidenceFromAssessment(assessment) {
return assessment?.conclusionConfidence ?? "medium";
}
function upsertProposalNodeUpdate(proposalSnapshot, update) {
const existing = proposalSnapshot.updatedNodes.find(
(candidate) => candidate.nodeId === update.nodeId,
@@ -521,16 +545,6 @@ function buildAncestorChain(graph, node) {
queue.push(candidate.id);
}
}
for (const edge of graph.edges || []) {
if (
edge.fromNodeId !== parentNode.id &&
edge.toNodeId === parentNode.id &&
edge.relationship === "depends_on"
) {
queue.push(edge.fromNodeId);
}
}
}
return chain;
@@ -562,28 +576,83 @@ function computeParentProgressState(graph, parentNode) {
const progressedChildren = childUnknowns.filter((child) =>
["resolved", "provisional"].includes(child.status),
);
const contradictoryChildren = childUnknowns.filter(
(child) => child.status === "contradicted",
);
const totalChildren = childUnknowns.length;
const resolvedCount = resolvedChildren.length;
const unresolvedCount = childUnknowns.filter(
(child) => child.status !== "resolved",
).length;
const beforeAssessment = getNodeConfidenceAssessment(parentNode);
if (totalChildren === 0) {
const nextAssessment = {
evidenceConfidence: beforeAssessment.evidenceConfidence,
completenessStatus: beforeAssessment.completenessStatus,
conclusionConfidence: beforeAssessment.conclusionConfidence,
};
return {
totalChildren,
resolvedChildren,
progressedChildren,
contradictoryChildren,
nextStatus: parentNode.status,
nextConfidence: parentNode.confidence,
nextConfidence: confidenceFromAssessment(nextAssessment),
nextConfidenceAssessment: nextAssessment,
parentResolved: parentNode.status === "resolved",
confidenceCapReason: "no_child_structure",
reason: "Parent has no child unknowns to aggregate.",
};
}
let nextAssessment;
let confidenceCapReason;
if (contradictoryChildren.length > 0) {
nextAssessment = {
evidenceConfidence: resolvedCount > 0 ? "medium" : "low",
completenessStatus: resolvedCount === 0 ? "empty" : "partial",
conclusionConfidence: "low",
};
confidenceCapReason = "contradictory_direct_children";
} else if (resolvedCount === 0) {
nextAssessment = {
evidenceConfidence: "low",
completenessStatus: "empty",
conclusionConfidence: "low",
};
confidenceCapReason = "no_resolved_direct_children";
} else if (resolvedCount < totalChildren) {
nextAssessment = {
evidenceConfidence: "high",
completenessStatus: "partial",
conclusionConfidence: "medium",
};
confidenceCapReason = "unresolved_direct_children_cap_conclusion";
} else {
nextAssessment = {
evidenceConfidence: "high",
completenessStatus: "complete",
conclusionConfidence: "high",
};
confidenceCapReason = null;
}
if (resolvedChildren.length === totalChildren) {
return {
totalChildren,
resolvedChildren,
progressedChildren,
contradictoryChildren,
nextStatus: "resolved",
nextConfidence: "high",
nextConfidence: confidenceFromAssessment(nextAssessment),
nextConfidenceAssessment: nextAssessment,
parentResolved: true,
resolvedDirectChildren: resolvedCount,
unresolvedDirectChildren: unresolvedCount,
contradictoryDirectChildren: contradictoryChildren.length,
confidenceCapReason,
reason:
"All direct child unknowns are resolved, so the parent can now resolve deterministically.",
};
@@ -594,9 +663,15 @@ function computeParentProgressState(graph, parentNode) {
totalChildren,
resolvedChildren,
progressedChildren,
contradictoryChildren,
nextStatus: "provisional",
nextConfidence: "high",
nextConfidence: confidenceFromAssessment(nextAssessment),
nextConfidenceAssessment: nextAssessment,
parentResolved: false,
resolvedDirectChildren: resolvedCount,
unresolvedDirectChildren: unresolvedCount,
contradictoryDirectChildren: contradictoryChildren.length,
confidenceCapReason,
reason:
"At least one direct child has been progressed, so the parent becomes provisional but remains unresolved until all direct children are resolved.",
};
@@ -606,9 +681,15 @@ function computeParentProgressState(graph, parentNode) {
totalChildren,
resolvedChildren,
progressedChildren,
contradictoryChildren,
nextStatus: parentNode.status,
nextConfidence: parentNode.confidence,
nextConfidence: confidenceFromAssessment(nextAssessment),
nextConfidenceAssessment: nextAssessment,
parentResolved: parentNode.status === "resolved",
resolvedDirectChildren: resolvedCount,
unresolvedDirectChildren: unresolvedCount,
contradictoryDirectChildren: contradictoryChildren.length,
confidenceCapReason,
reason: "No direct child progress exists yet for the parent.",
};
}
@@ -635,6 +716,17 @@ export function propagateResolvedChildEvidence({
parentStatusAfter: null,
parentConfidenceBefore: null,
parentConfidenceAfter: null,
evidenceConfidenceBefore: null,
evidenceConfidenceAfter: null,
completenessBefore: null,
completenessAfter: null,
conclusionConfidenceBefore: null,
conclusionConfidenceAfter: null,
resolvedDirectChildren: 0,
unresolvedDirectChildren: 0,
contradictoryDirectChildren: 0,
confidenceCapReason: null,
ancestorPropagationStoppedReason: "no_resolved_child_propagation_needed",
affectedAncestorIds: [],
nextSelectedSibling: null,
parentResolved: false,
@@ -646,6 +738,7 @@ export function propagateResolvedChildEvidence({
syncParentChildReferences(graph);
const propagationEvents = [];
const affectedAncestorIds = new Set();
let ancestorPropagationStoppedReason = "no_ancestor_state_changed";
for (const resolvedChildNode of resolvedChildNodes) {
const liveChildNode = graph.nodes.find(
@@ -662,10 +755,24 @@ export function propagateResolvedChildEvidence({
for (const ancestorNode of ancestorChain) {
const beforeStatus = ancestorNode.status;
const beforeConfidence = ancestorNode.confidence;
const beforeAssessment = getNodeConfidenceAssessment(ancestorNode);
const progressState = computeParentProgressState(graph, ancestorNode);
ancestorNode.status = progressState.nextStatus;
ancestorNode.confidence = progressState.nextConfidence;
ancestorNode.confidenceAssessment =
progressState.nextConfidenceAssessment;
if (
beforeStatus === progressState.nextStatus &&
beforeConfidence === progressState.nextConfidence &&
JSON.stringify(beforeAssessment) ===
JSON.stringify(progressState.nextConfidenceAssessment)
) {
continue;
}
ancestorPropagationStoppedReason = "ancestor_state_changed";
if (progressState.parentResolved) {
ensureResolvedUnknownId(proposalSnapshot, ancestorNode.id);
@@ -688,6 +795,19 @@ export function propagateResolvedChildEvidence({
parentStatusAfter: progressState.nextStatus,
parentConfidenceBefore: beforeConfidence,
parentConfidenceAfter: progressState.nextConfidence,
evidenceConfidenceBefore: beforeAssessment.evidenceConfidence,
evidenceConfidenceAfter:
progressState.nextConfidenceAssessment.evidenceConfidence,
completenessBefore: beforeAssessment.completenessStatus,
completenessAfter:
progressState.nextConfidenceAssessment.completenessStatus,
conclusionConfidenceBefore: beforeAssessment.conclusionConfidence,
conclusionConfidenceAfter:
progressState.nextConfidenceAssessment.conclusionConfidence,
resolvedDirectChildren: progressState.resolvedDirectChildren,
unresolvedDirectChildren: progressState.unresolvedDirectChildren,
contradictoryDirectChildren: progressState.contradictoryDirectChildren,
confidenceCapReason: progressState.confidenceCapReason,
parentResolved: progressState.parentResolved,
reason: progressState.reason,
});
@@ -717,6 +837,17 @@ export function propagateResolvedChildEvidence({
parentStatusAfter: firstEvent?.parentStatusAfter ?? null,
parentConfidenceBefore: firstEvent?.parentConfidenceBefore ?? null,
parentConfidenceAfter: firstEvent?.parentConfidenceAfter ?? null,
evidenceConfidenceBefore: firstEvent?.evidenceConfidenceBefore ?? null,
evidenceConfidenceAfter: firstEvent?.evidenceConfidenceAfter ?? null,
completenessBefore: firstEvent?.completenessBefore ?? null,
completenessAfter: firstEvent?.completenessAfter ?? null,
conclusionConfidenceBefore: firstEvent?.conclusionConfidenceBefore ?? null,
conclusionConfidenceAfter: firstEvent?.conclusionConfidenceAfter ?? null,
resolvedDirectChildren: firstEvent?.resolvedDirectChildren ?? 0,
unresolvedDirectChildren: firstEvent?.unresolvedDirectChildren ?? 0,
contradictoryDirectChildren: firstEvent?.contradictoryDirectChildren ?? 0,
confidenceCapReason: firstEvent?.confidenceCapReason ?? null,
ancestorPropagationStoppedReason,
affectedAncestorIds: [...affectedAncestorIds],
nextSelectedSibling:
siblingSelection?.status === "selected" ? siblingSelection.nodeId : null,
@@ -1767,6 +1898,20 @@ export function applyValidatedProposal({
const parentStatusAfter = propagationResult.parentStatusAfter;
const parentConfidenceBefore = propagationResult.parentConfidenceBefore;
const parentConfidenceAfter = propagationResult.parentConfidenceAfter;
const evidenceConfidenceBefore = propagationResult.evidenceConfidenceBefore;
const evidenceConfidenceAfter = propagationResult.evidenceConfidenceAfter;
const completenessBefore = propagationResult.completenessBefore;
const completenessAfter = propagationResult.completenessAfter;
const conclusionConfidenceBefore =
propagationResult.conclusionConfidenceBefore;
const conclusionConfidenceAfter = propagationResult.conclusionConfidenceAfter;
const resolvedDirectChildren = propagationResult.resolvedDirectChildren;
const unresolvedDirectChildren = propagationResult.unresolvedDirectChildren;
const contradictoryDirectChildren =
propagationResult.contradictoryDirectChildren;
const confidenceCapReason = propagationResult.confidenceCapReason;
const ancestorPropagationStoppedReason =
propagationResult.ancestorPropagationStoppedReason;
const affectedAncestorIds = propagationResult.affectedAncestorIds;
const nextSelectedSibling = propagationResult.nextSelectedSibling;
const parentResolved = propagationResult.parentResolved;
@@ -1894,6 +2039,17 @@ export function applyValidatedProposal({
parentStatusAfter,
parentConfidenceBefore,
parentConfidenceAfter,
evidenceConfidenceBefore,
evidenceConfidenceAfter,
completenessBefore,
completenessAfter,
conclusionConfidenceBefore,
conclusionConfidenceAfter,
resolvedDirectChildren,
unresolvedDirectChildren,
contradictoryDirectChildren,
confidenceCapReason,
ancestorPropagationStoppedReason,
affectedAncestorIds,
nextSelectedSibling,
parentResolved,
+58
View File
@@ -110,6 +110,17 @@ function buildUpdateDiagnostics({
parentStatusAfter,
parentConfidenceBefore,
parentConfidenceAfter,
evidenceConfidenceBefore,
evidenceConfidenceAfter,
completenessBefore,
completenessAfter,
conclusionConfidenceBefore,
conclusionConfidenceAfter,
resolvedDirectChildren,
unresolvedDirectChildren,
contradictoryDirectChildren,
confidenceCapReason,
ancestorPropagationStoppedReason,
affectedAncestorIds,
nextSelectedSibling,
parentResolved,
@@ -163,6 +174,17 @@ function buildUpdateDiagnostics({
parentStatusAfter: parentStatusAfter ?? null,
parentConfidenceBefore: parentConfidenceBefore ?? null,
parentConfidenceAfter: parentConfidenceAfter ?? null,
evidenceConfidenceBefore: evidenceConfidenceBefore ?? null,
evidenceConfidenceAfter: evidenceConfidenceAfter ?? null,
completenessBefore: completenessBefore ?? null,
completenessAfter: completenessAfter ?? null,
conclusionConfidenceBefore: conclusionConfidenceBefore ?? null,
conclusionConfidenceAfter: conclusionConfidenceAfter ?? null,
resolvedDirectChildren: resolvedDirectChildren ?? 0,
unresolvedDirectChildren: unresolvedDirectChildren ?? 0,
contradictoryDirectChildren: contradictoryDirectChildren ?? 0,
confidenceCapReason: confidenceCapReason ?? null,
ancestorPropagationStoppedReason: ancestorPropagationStoppedReason ?? null,
affectedAncestorIds: affectedAncestorIds ?? [],
nextSelectedSibling: nextSelectedSibling ?? null,
parentResolved: parentResolved ?? false,
@@ -440,6 +462,17 @@ async function updateCaseWithDependencies(body, dependencies = {}) {
parentStatusAfter: null,
parentConfidenceBefore: null,
parentConfidenceAfter: null,
evidenceConfidenceBefore: null,
evidenceConfidenceAfter: null,
completenessBefore: null,
completenessAfter: null,
conclusionConfidenceBefore: null,
conclusionConfidenceAfter: null,
resolvedDirectChildren: 0,
unresolvedDirectChildren: 0,
contradictoryDirectChildren: 0,
confidenceCapReason: null,
ancestorPropagationStoppedReason: null,
affectedAncestorIds: [],
nextSelectedSibling: null,
parentResolved: false,
@@ -508,6 +541,20 @@ async function updateCaseWithDependencies(body, dependencies = {}) {
parentStatusAfter: applicationResult.parentStatusAfter,
parentConfidenceBefore: applicationResult.parentConfidenceBefore,
parentConfidenceAfter: applicationResult.parentConfidenceAfter,
evidenceConfidenceBefore: applicationResult.evidenceConfidenceBefore,
evidenceConfidenceAfter: applicationResult.evidenceConfidenceAfter,
completenessBefore: applicationResult.completenessBefore,
completenessAfter: applicationResult.completenessAfter,
conclusionConfidenceBefore:
applicationResult.conclusionConfidenceBefore,
conclusionConfidenceAfter: applicationResult.conclusionConfidenceAfter,
resolvedDirectChildren: applicationResult.resolvedDirectChildren,
unresolvedDirectChildren: applicationResult.unresolvedDirectChildren,
contradictoryDirectChildren:
applicationResult.contradictoryDirectChildren,
confidenceCapReason: applicationResult.confidenceCapReason,
ancestorPropagationStoppedReason:
applicationResult.ancestorPropagationStoppedReason,
affectedAncestorIds: applicationResult.affectedAncestorIds,
nextSelectedSibling: applicationResult.nextSelectedSibling,
parentResolved: applicationResult.parentResolved,
@@ -560,6 +607,17 @@ async function updateCaseWithDependencies(body, dependencies = {}) {
parentStatusAfter: null,
parentConfidenceBefore: null,
parentConfidenceAfter: null,
evidenceConfidenceBefore: null,
evidenceConfidenceAfter: null,
completenessBefore: null,
completenessAfter: null,
conclusionConfidenceBefore: null,
conclusionConfidenceAfter: null,
resolvedDirectChildren: 0,
unresolvedDirectChildren: 0,
contradictoryDirectChildren: 0,
confidenceCapReason: null,
ancestorPropagationStoppedReason: null,
affectedAncestorIds: [],
nextSelectedSibling: null,
parentResolved: false,
+16
View File
@@ -36,6 +36,20 @@ export const ConfidenceLevel = /** @type {const} */ ({
high: "high",
});
export const CompletenessStatus = /** @type {const} */ ({
empty: "empty",
partial: "partial",
complete: "complete",
});
export const confidenceAssessmentSchema = z
.object({
evidenceConfidence: z.enum(Object.values(ConfidenceLevel)),
completenessStatus: z.enum(Object.values(CompletenessStatus)),
conclusionConfidence: z.enum(Object.values(ConfidenceLevel)),
})
.strict();
// ── SituationNode ────────────────────────────────────
export const situationNodeSchema = z.object({
@@ -45,6 +59,7 @@ export const situationNodeSchema = z.object({
kind: z.enum(Object.values(SituationKind)),
status: z.enum(Object.values(SituationStatus)),
confidence: z.enum(Object.values(ConfidenceLevel)),
confidenceAssessment: confidenceAssessmentSchema.optional(),
value: z.union([z.string(), z.number(), z.null()]).nullable().optional(),
unit: z.string().nullable().optional(),
evidenceIds: z.array(z.string()).default([]),
@@ -188,6 +203,7 @@ export function makeNode(opts) {
kind: opts.kind ?? "observation",
status: opts.status ?? "unknown",
confidence: opts.confidence ?? "medium",
confidenceAssessment: opts.confidenceAssessment,
value: opts.value ?? null,
unit: opts.unit ?? null,
evidenceIds: opts.evidenceIds ?? [],
+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",
},
});
});
});