feat: separate confidence from reasoning completeness
This commit is contained in:
+170
-14
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 ?? [],
|
||||
|
||||
Reference in New Issue
Block a user