feat: back next questions with explicit graph unknowns
This commit is contained in:
@@ -42,3 +42,7 @@ The repeated pattern appeared in four scenarios, so a small pre-contradiction co
|
|||||||
A comparison question is useful only if its answer advances the reasoning stage rather than merely adding more text.
|
A comparison question is useful only if its answer advances the reasoning stage rather than merely adding more text.
|
||||||
|
|
||||||
In the revenue-versus-cash scenario, the first question now confirms whether the figures are comparable, and the answer resolves that existing uncertainty instead of creating a parallel note. After that update, the engine progresses from comparability assessment to cautious relationship assessment and can select one broad non-expert follow-up question.
|
In the revenue-versus-cash scenario, the first question now confirms whether the figures are comparable, and the answer resolves that existing uncertainty instead of creating a parallel note. After that update, the engine progresses from comparability assessment to cautious relationship assessment and can select one broad non-expert follow-up question.
|
||||||
|
|
||||||
|
Every justified next question should correspond to an explicit unresolved graph node.
|
||||||
|
|
||||||
|
The earlier fallback-only path has now been removed from the normal successful progression. After comparability is resolved and a further investigation question is justified, the engine creates or reuses an explicit unresolved reasoning unknown and lets deterministic selection and question formulation proceed through the standard graph pipeline. A fallback is now only acceptable as an explicit failure case, not as the normal source of the next question.
|
||||||
|
|||||||
+163
-21
@@ -1,11 +1,15 @@
|
|||||||
import { describeGraph } from "./builder.js";
|
import { describeGraph } from "./builder.js";
|
||||||
import {
|
import {
|
||||||
buildReasoningState,
|
buildReasoningState,
|
||||||
|
classifyObservationRelationship,
|
||||||
COMPARABILITY_REASONING_NODE_ID,
|
COMPARABILITY_REASONING_NODE_ID,
|
||||||
formulateQuestion,
|
formulateQuestion,
|
||||||
formulateTieResolutionQuestion,
|
|
||||||
} from "./question-formulator.js";
|
} from "./question-formulator.js";
|
||||||
import { graphUpdateSchema, situationGraphSchema } from "./schema.js";
|
import {
|
||||||
|
graphUpdateSchema,
|
||||||
|
makeNodeId,
|
||||||
|
situationGraphSchema,
|
||||||
|
} from "./schema.js";
|
||||||
import {
|
import {
|
||||||
applyGraphUpdate,
|
applyGraphUpdate,
|
||||||
detectDuplicateNodeIds,
|
detectDuplicateNodeIds,
|
||||||
@@ -428,6 +432,124 @@ function buildChangesApplied(proposal, affectedNodeIds) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildEmergentReasoningUnknownLabel(graph) {
|
||||||
|
const central = String(graph?.centralStatement || "these observations")
|
||||||
|
.trim()
|
||||||
|
.replace(/[.?!:;]+$/g, "");
|
||||||
|
return `Explanation for why ${central}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findEquivalentEmergentUnknown(graph, label, description) {
|
||||||
|
const targetId = makeNodeId(label);
|
||||||
|
const targetTexts = [normaliseText(label), normaliseText(description)].filter(
|
||||||
|
Boolean,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (graph.nodes || []).find((node) => {
|
||||||
|
if (
|
||||||
|
node.kind !== "unknown" ||
|
||||||
|
(graph.resolvedNodeIds || []).includes(node.id)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.id === targetId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeTexts = [
|
||||||
|
normaliseText(node.label),
|
||||||
|
normaliseText(node.description),
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
return targetTexts.some((text) => nodeTexts.includes(text));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildEmergentReasoningUnknown(graph, relationshipAssessment) {
|
||||||
|
if (!relationshipAssessment?.relationshipAssessed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!relationshipAssessment.questionRequired) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
![
|
||||||
|
"potentially_related",
|
||||||
|
"insufficient_information",
|
||||||
|
"contradictory",
|
||||||
|
].includes(relationshipAssessment.relationshipStatus)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const label = buildEmergentReasoningUnknownLabel(graph);
|
||||||
|
const description =
|
||||||
|
"Need to understand what change or event could explain why these observations differ, because that is needed to investigate their relationship.";
|
||||||
|
const existingNode = findEquivalentEmergentUnknown(graph, label, description);
|
||||||
|
if (existingNode) {
|
||||||
|
return {
|
||||||
|
created: false,
|
||||||
|
node: existingNode,
|
||||||
|
edges: [],
|
||||||
|
reason:
|
||||||
|
"Reused an existing unresolved reasoning unknown for the next investigation stage.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const observationNodes = (graph.nodes || []).filter(
|
||||||
|
(node) => node.kind === "observation" && node.status === "supported",
|
||||||
|
);
|
||||||
|
const relationshipNode = (graph.nodes || []).find(
|
||||||
|
(node) => node.kind === "relationship" && node.status === "supported",
|
||||||
|
);
|
||||||
|
const nodeId = makeNodeId(label);
|
||||||
|
const relatedNodeIds = relationshipNode
|
||||||
|
? [relationshipNode.id]
|
||||||
|
: observationNodes.slice(0, 2).map((node) => node.id);
|
||||||
|
|
||||||
|
if (relatedNodeIds.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const node = {
|
||||||
|
id: nodeId,
|
||||||
|
label,
|
||||||
|
description,
|
||||||
|
kind: "unknown",
|
||||||
|
status: "unknown",
|
||||||
|
confidence: "medium",
|
||||||
|
value: null,
|
||||||
|
unit: null,
|
||||||
|
evidenceIds: [],
|
||||||
|
dependsOn: relatedNodeIds,
|
||||||
|
affects: [],
|
||||||
|
parentId: relationshipNode?.id ?? null,
|
||||||
|
childIds: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const edges = relatedNodeIds.map((relatedNodeId) => ({
|
||||||
|
id: `e-${relatedNodeId.slice(0, 6)}-${nodeId.slice(0, 6)}`,
|
||||||
|
fromNodeId: relatedNodeId,
|
||||||
|
toNodeId: nodeId,
|
||||||
|
relationship:
|
||||||
|
relationshipNode?.id === relatedNodeId ? "depends_on" : "other",
|
||||||
|
confidence: "medium",
|
||||||
|
description:
|
||||||
|
"This unresolved explanation arises from the now-assessed relationship between the observations.",
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
created: true,
|
||||||
|
node,
|
||||||
|
edges,
|
||||||
|
reason:
|
||||||
|
"Created a new unresolved reasoning unknown so the next justified question is backed by the graph.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function isComparabilityQuestion(question) {
|
function isComparabilityQuestion(question) {
|
||||||
const text = String(question || "").toLowerCase();
|
const text = String(question || "").toLowerCase();
|
||||||
return (
|
return (
|
||||||
@@ -643,6 +765,36 @@ export function applyValidatedProposal({
|
|||||||
resolvedUnknownNodeIds: validatedProposal.resolvedUnknownNodeIds,
|
resolvedUnknownNodeIds: validatedProposal.resolvedUnknownNodeIds,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const provisionalApplied = applyGraphUpdate(graphSnapshot, proposalSnapshot);
|
||||||
|
if (!provisionalApplied.success) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
stage: "application",
|
||||||
|
errors: provisionalApplied.errors,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const provisionalGraph = {
|
||||||
|
...graphSnapshot,
|
||||||
|
nodes: provisionalApplied.nodes,
|
||||||
|
edges: provisionalApplied.edges,
|
||||||
|
resolvedNodeIds: provisionalApplied.resolvedNodeIds,
|
||||||
|
};
|
||||||
|
provisionalGraph.reasoningState = buildReasoningState(
|
||||||
|
provisionalGraph,
|
||||||
|
reasoningResolution.reasoningStateOverride,
|
||||||
|
);
|
||||||
|
const relationshipAssessment =
|
||||||
|
classifyObservationRelationship(provisionalGraph);
|
||||||
|
const emergentReasoningUnknown = buildEmergentReasoningUnknown(
|
||||||
|
provisionalGraph,
|
||||||
|
relationshipAssessment,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (emergentReasoningUnknown?.created) {
|
||||||
|
proposalSnapshot.addedNodes.push(emergentReasoningUnknown.node);
|
||||||
|
proposalSnapshot.addedEdges.push(...emergentReasoningUnknown.edges);
|
||||||
|
}
|
||||||
|
|
||||||
const applied = applyGraphUpdate(graphSnapshot, proposalSnapshot);
|
const applied = applyGraphUpdate(graphSnapshot, proposalSnapshot);
|
||||||
if (!applied.success) {
|
if (!applied.success) {
|
||||||
return {
|
return {
|
||||||
@@ -738,7 +890,8 @@ export function applyValidatedProposal({
|
|||||||
? {
|
? {
|
||||||
nodeId: null,
|
nodeId: null,
|
||||||
tiedCandidateIds: deterministicSelection.tiedCandidateIds,
|
tiedCandidateIds: deterministicSelection.tiedCandidateIds,
|
||||||
...formulateTieResolutionQuestion({ graph: updatedSituationGraph }),
|
question: null,
|
||||||
|
reason: deterministicSelection.reason,
|
||||||
}
|
}
|
||||||
: deterministicSelection?.status === "selected"
|
: deterministicSelection?.status === "selected"
|
||||||
? {
|
? {
|
||||||
@@ -749,21 +902,7 @@ export function applyValidatedProposal({
|
|||||||
strategy: formulatedQuestion?.strategy,
|
strategy: formulatedQuestion?.strategy,
|
||||||
investigationStrategy: formulatedQuestion?.investigationStrategy,
|
investigationStrategy: formulatedQuestion?.investigationStrategy,
|
||||||
}
|
}
|
||||||
: (() => {
|
: null;
|
||||||
const relationshipFallback = formulateTieResolutionQuestion({
|
|
||||||
graph: updatedSituationGraph,
|
|
||||||
});
|
|
||||||
return relationshipFallback?.question
|
|
||||||
? {
|
|
||||||
nodeId: null,
|
|
||||||
question: relationshipFallback.question,
|
|
||||||
reason: relationshipFallback.reason,
|
|
||||||
strategy: relationshipFallback.strategy,
|
|
||||||
investigationStrategy:
|
|
||||||
relationshipFallback.investigationStrategy,
|
|
||||||
}
|
|
||||||
: null;
|
|
||||||
})();
|
|
||||||
|
|
||||||
const resultGraphValidation = situationGraphSchema.safeParse(
|
const resultGraphValidation = situationGraphSchema.safeParse(
|
||||||
updatedSituationGraph,
|
updatedSituationGraph,
|
||||||
@@ -809,14 +948,17 @@ export function applyValidatedProposal({
|
|||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
updatedSituationGraph,
|
updatedSituationGraph,
|
||||||
graphUpdate: validatedProposal,
|
graphUpdate: proposalSnapshot,
|
||||||
affectedNodeIds,
|
affectedNodeIds,
|
||||||
resolvedUnknownNodeIds: validatedProposal.resolvedUnknownNodeIds,
|
resolvedUnknownNodeIds: proposalSnapshot.resolvedUnknownNodeIds,
|
||||||
resolvedReasoningNodeIds: reasoningResolution.resolvedReasoningNodeIds,
|
resolvedReasoningNodeIds: reasoningResolution.resolvedReasoningNodeIds,
|
||||||
|
emergentReasoningNodeCreated: Boolean(emergentReasoningUnknown?.created),
|
||||||
|
emergentReasoningNodeId: emergentReasoningUnknown?.node?.id ?? null,
|
||||||
|
emergentReasoningNodeReason: emergentReasoningUnknown?.reason ?? null,
|
||||||
previousActiveUnknownNodeId,
|
previousActiveUnknownNodeId,
|
||||||
newActiveUnknownNodeId,
|
newActiveUnknownNodeId,
|
||||||
selectedQuestion: finalSelectedQuestion,
|
selectedQuestion: finalSelectedQuestion,
|
||||||
changesApplied: buildChangesApplied(validatedProposal, affectedNodeIds),
|
changesApplied: buildChangesApplied(proposalSnapshot, affectedNodeIds),
|
||||||
graphReferenceValidation: resultReferenceValidation,
|
graphReferenceValidation: resultReferenceValidation,
|
||||||
previousReasoningState: reasoningResolution.previousReasoningState,
|
previousReasoningState: reasoningResolution.previousReasoningState,
|
||||||
reasoningState: nextReasoningState,
|
reasoningState: nextReasoningState,
|
||||||
|
|||||||
@@ -89,6 +89,9 @@ function buildUpdateDiagnostics({
|
|||||||
previousReasoningState,
|
previousReasoningState,
|
||||||
reasoningState,
|
reasoningState,
|
||||||
resolvedReasoningNodeIds,
|
resolvedReasoningNodeIds,
|
||||||
|
emergentReasoningNodeCreated,
|
||||||
|
emergentReasoningNodeId,
|
||||||
|
emergentReasoningNodeReason,
|
||||||
}) {
|
}) {
|
||||||
return {
|
return {
|
||||||
promptVersion: promptVersion ?? "v0.4",
|
promptVersion: promptVersion ?? "v0.4",
|
||||||
@@ -114,6 +117,9 @@ function buildUpdateDiagnostics({
|
|||||||
reasoningStagesBefore: previousReasoningState?.reasoningStages ?? [],
|
reasoningStagesBefore: previousReasoningState?.reasoningStages ?? [],
|
||||||
reasoningStagesAfter: reasoningState?.reasoningStages ?? [],
|
reasoningStagesAfter: reasoningState?.reasoningStages ?? [],
|
||||||
resolvedReasoningNodeIds: resolvedReasoningNodeIds ?? [],
|
resolvedReasoningNodeIds: resolvedReasoningNodeIds ?? [],
|
||||||
|
emergentReasoningNodeCreated: emergentReasoningNodeCreated ?? false,
|
||||||
|
emergentReasoningNodeId: emergentReasoningNodeId ?? null,
|
||||||
|
emergentReasoningNodeReason: emergentReasoningNodeReason ?? null,
|
||||||
unknownSelectionExplanation: unknownSelectionExplanation ?? null,
|
unknownSelectionExplanation: unknownSelectionExplanation ?? null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -363,6 +369,9 @@ async function updateCaseWithDependencies(body, dependencies = {}) {
|
|||||||
previousReasoningState: buildReasoningState(situationGraph),
|
previousReasoningState: buildReasoningState(situationGraph),
|
||||||
reasoningState: buildReasoningState(situationGraph),
|
reasoningState: buildReasoningState(situationGraph),
|
||||||
resolvedReasoningNodeIds: [],
|
resolvedReasoningNodeIds: [],
|
||||||
|
emergentReasoningNodeCreated: false,
|
||||||
|
emergentReasoningNodeId: null,
|
||||||
|
emergentReasoningNodeReason: null,
|
||||||
unknownSelectionExplanation: explainUnknownSelection(
|
unknownSelectionExplanation: explainUnknownSelection(
|
||||||
situationGraph,
|
situationGraph,
|
||||||
situationGraph.resolvedNodeIds || [],
|
situationGraph.resolvedNodeIds || [],
|
||||||
@@ -400,6 +409,11 @@ async function updateCaseWithDependencies(body, dependencies = {}) {
|
|||||||
previousReasoningState: applicationResult.previousReasoningState,
|
previousReasoningState: applicationResult.previousReasoningState,
|
||||||
reasoningState: applicationResult.reasoningState,
|
reasoningState: applicationResult.reasoningState,
|
||||||
resolvedReasoningNodeIds: applicationResult.resolvedReasoningNodeIds,
|
resolvedReasoningNodeIds: applicationResult.resolvedReasoningNodeIds,
|
||||||
|
emergentReasoningNodeCreated:
|
||||||
|
applicationResult.emergentReasoningNodeCreated,
|
||||||
|
emergentReasoningNodeId: applicationResult.emergentReasoningNodeId,
|
||||||
|
emergentReasoningNodeReason:
|
||||||
|
applicationResult.emergentReasoningNodeReason,
|
||||||
unknownSelectionExplanation: buildUnknownSelectionDiagnostics(
|
unknownSelectionExplanation: buildUnknownSelectionDiagnostics(
|
||||||
applicationResult.updatedSituationGraph,
|
applicationResult.updatedSituationGraph,
|
||||||
applicationResult.updatedSituationGraph.resolvedNodeIds || [],
|
applicationResult.updatedSituationGraph.resolvedNodeIds || [],
|
||||||
@@ -424,6 +438,9 @@ async function updateCaseWithDependencies(body, dependencies = {}) {
|
|||||||
previousReasoningState: buildReasoningState(situationGraph),
|
previousReasoningState: buildReasoningState(situationGraph),
|
||||||
reasoningState: buildReasoningState(situationGraph),
|
reasoningState: buildReasoningState(situationGraph),
|
||||||
resolvedReasoningNodeIds: [],
|
resolvedReasoningNodeIds: [],
|
||||||
|
emergentReasoningNodeCreated: false,
|
||||||
|
emergentReasoningNodeId: null,
|
||||||
|
emergentReasoningNodeReason: null,
|
||||||
unknownSelectionExplanation: buildUnknownSelectionDiagnostics(
|
unknownSelectionExplanation: buildUnknownSelectionDiagnostics(
|
||||||
situationGraph,
|
situationGraph,
|
||||||
situationGraph.resolvedNodeIds || [],
|
situationGraph.resolvedNodeIds || [],
|
||||||
|
|||||||
@@ -498,6 +498,16 @@ function buildBroadInvestigationQuestion(graph) {
|
|||||||
return `What changed during that period that could help explain why ${central}?`;
|
return `What changed during that period that could help explain why ${central}?`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isRelationshipExplanationUnknown(node, graph) {
|
||||||
|
const text = normaliseText(`${node?.label || ""} ${node?.description || ""}`);
|
||||||
|
return (
|
||||||
|
collectObservationNodes(graph).length >= 2 &&
|
||||||
|
/\b(explain|explanation|divergence|moved differently|difference between|change or event|what changed|why the observations)/.test(
|
||||||
|
text,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function formulateTieResolutionQuestion({ graph }) {
|
export function formulateTieResolutionQuestion({ graph }) {
|
||||||
const comparability = assessComparability(graph);
|
const comparability = assessComparability(graph);
|
||||||
if (comparability.comparabilityStatus === "uncertain") {
|
if (comparability.comparabilityStatus === "uncertain") {
|
||||||
@@ -909,7 +919,9 @@ export function formulateQuestion({ node, graph, context = {} }) {
|
|||||||
|
|
||||||
let question = investigationStrategy
|
let question = investigationStrategy
|
||||||
? buildQuestionFromStrategy(investigationStrategy)
|
? buildQuestionFromStrategy(investigationStrategy)
|
||||||
: buildNeutralClarificationQuestion(extractMeaning(node));
|
: isRelationshipExplanationUnknown(node, graph)
|
||||||
|
? buildBroadInvestigationQuestion(graph)
|
||||||
|
: buildNeutralClarificationQuestion(extractMeaning(node));
|
||||||
|
|
||||||
question = sanitizeQuestionText(question);
|
question = sanitizeQuestionText(question);
|
||||||
|
|
||||||
|
|||||||
@@ -1133,6 +1133,9 @@ describe("applyValidatedProposal", () => {
|
|||||||
expect(result.resolvedReasoningNodeIds).toEqual([
|
expect(result.resolvedReasoningNodeIds).toEqual([
|
||||||
"reasoning:comparability",
|
"reasoning:comparability",
|
||||||
]);
|
]);
|
||||||
|
expect(result.emergentReasoningNodeCreated).toBe(true);
|
||||||
|
expect(result.emergentReasoningNodeId).toBeTruthy();
|
||||||
|
expect(result.emergentReasoningNodeReason).toContain("backed by the graph");
|
||||||
expect(result.previousReasoningState.comparabilityStatus).toBe("uncertain");
|
expect(result.previousReasoningState.comparabilityStatus).toBe("uncertain");
|
||||||
expect(result.reasoningState).toMatchObject({
|
expect(result.reasoningState).toMatchObject({
|
||||||
comparabilityStatus: "confirmed",
|
comparabilityStatus: "confirmed",
|
||||||
@@ -1142,6 +1145,7 @@ describe("applyValidatedProposal", () => {
|
|||||||
expect(result.reasoningState.comparabilityEvidence).toEqual([
|
expect(result.reasoningState.comparabilityEvidence).toEqual([
|
||||||
comparabilityUnknownId,
|
comparabilityUnknownId,
|
||||||
]);
|
]);
|
||||||
|
expect(result.selectedQuestion?.nodeId).toBe(result.newActiveUnknownNodeId);
|
||||||
expect(result.selectedQuestion?.question).toMatch(
|
expect(result.selectedQuestion?.question).toMatch(
|
||||||
/^What changed during that period that could help explain why /,
|
/^What changed during that period that could help explain why /,
|
||||||
);
|
);
|
||||||
@@ -1163,6 +1167,27 @@ describe("applyValidatedProposal", () => {
|
|||||||
"The observations concern connected business signals but do not establish a direct contradiction or cause.",
|
"The observations concern connected business signals but do not establish a direct contradiction or cause.",
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
const emergentNode = result.updatedSituationGraph.nodes.find(
|
||||||
|
(node) => node.id === result.emergentReasoningNodeId,
|
||||||
|
);
|
||||||
|
expect(emergentNode).toMatchObject({
|
||||||
|
kind: "unknown",
|
||||||
|
status: "unknown",
|
||||||
|
confidence: "medium",
|
||||||
|
});
|
||||||
|
expect(emergentNode.description.toLowerCase()).toContain("because");
|
||||||
|
expect(
|
||||||
|
result.updatedSituationGraph.edges.filter(
|
||||||
|
(edge) => edge.toNodeId === result.emergentReasoningNodeId,
|
||||||
|
),
|
||||||
|
).not.toEqual([]);
|
||||||
|
expect(
|
||||||
|
result.updatedSituationGraph.edges.some(
|
||||||
|
(edge) =>
|
||||||
|
edge.toNodeId === result.emergentReasoningNodeId &&
|
||||||
|
edge.relationship === "causes",
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
expect(
|
expect(
|
||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
result.updatedSituationGraph.nodes.find(
|
result.updatedSituationGraph.nodes.find(
|
||||||
@@ -1171,4 +1196,40 @@ describe("applyValidatedProposal", () => {
|
|||||||
),
|
),
|
||||||
).toBe(originalUnrelatedNode);
|
).toBe(originalUnrelatedNode);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("reuses an equivalent existing unresolved reasoning unknown instead of creating a duplicate", () => {
|
||||||
|
const { graph, proposal } = makeComparabilityUpdateFixture();
|
||||||
|
graph.nodes.push(
|
||||||
|
makeNode({
|
||||||
|
id: "n-existing-explanation",
|
||||||
|
label:
|
||||||
|
"Explanation for why Revenue increased by 18%, but cash in the bank fell over the same period",
|
||||||
|
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 result = applyValidatedProposal({
|
||||||
|
situationGraph: graph,
|
||||||
|
proposal,
|
||||||
|
previousQuestion:
|
||||||
|
"Were these figures measured on the same basis and at the same scale?",
|
||||||
|
answer:
|
||||||
|
"Yes. Both figures cover the same accounting period and are taken from the same management accounts.",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
expect(result.emergentReasoningNodeCreated).toBe(false);
|
||||||
|
expect(result.emergentReasoningNodeId).toBe("n-existing-explanation");
|
||||||
|
expect(result.newActiveUnknownNodeId).toBe("n-existing-explanation");
|
||||||
|
expect(result.selectedQuestion?.nodeId).toBe("n-existing-explanation");
|
||||||
|
expect(
|
||||||
|
result.updatedSituationGraph.nodes.filter(
|
||||||
|
(node) => node.label === graph.nodes.at(-1).label,
|
||||||
|
),
|
||||||
|
).toHaveLength(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1046,7 +1046,12 @@ describe("lib/graph/orchestrator startCase", () => {
|
|||||||
relationshipStatus: "potentially_related",
|
relationshipStatus: "potentially_related",
|
||||||
relationshipAssessed: true,
|
relationshipAssessed: true,
|
||||||
resolvedReasoningNodeIds: ["reasoning:comparability"],
|
resolvedReasoningNodeIds: ["reasoning:comparability"],
|
||||||
|
emergentReasoningNodeCreated: true,
|
||||||
});
|
});
|
||||||
|
expect(result.diagnostics.emergentReasoningNodeId).toBeTruthy();
|
||||||
|
expect(result.diagnostics.emergentReasoningNodeReason).toContain(
|
||||||
|
"backed by the graph",
|
||||||
|
);
|
||||||
expect(result.diagnostics.reasoningStagesBefore).toEqual([
|
expect(result.diagnostics.reasoningStagesBefore).toEqual([
|
||||||
{
|
{
|
||||||
stage: "comparability",
|
stage: "comparability",
|
||||||
@@ -1074,6 +1079,7 @@ describe("lib/graph/orchestrator startCase", () => {
|
|||||||
"The observations concern connected business signals but do not establish a direct contradiction or cause.",
|
"The observations concern connected business signals but do not establish a direct contradiction or cause.",
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
expect(result.selectedQuestion?.nodeId).toBe(result.newActiveUnknownNodeId);
|
||||||
expect(result.selectedQuestion?.question).toMatch(
|
expect(result.selectedQuestion?.question).toMatch(
|
||||||
/^What changed during that period that could help explain why /,
|
/^What changed during that period that could help explain why /,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -115,32 +115,46 @@ function makeUpdateSuccess(overrides = {}) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "n-next-unknown",
|
id: "n-next-unknown",
|
||||||
label: "Commercial value definition",
|
label:
|
||||||
description: "Need a definition because the decision depends on it.",
|
"Explanation for why revenue increased by 18%, but cash in the bank fell over the same period",
|
||||||
|
description:
|
||||||
|
"Need to understand what change or event could explain why these observations differ, because that is needed to investigate their relationship.",
|
||||||
kind: "unknown",
|
kind: "unknown",
|
||||||
status: "unknown",
|
status: "unknown",
|
||||||
confidence: "high",
|
confidence: "medium",
|
||||||
value: null,
|
value: null,
|
||||||
unit: null,
|
unit: null,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
edges: [],
|
edges: [
|
||||||
|
{
|
||||||
|
id: "e-rel-next",
|
||||||
|
fromNodeId: "n-conclusion",
|
||||||
|
toNodeId: "n-next-unknown",
|
||||||
|
relationship: "depends_on",
|
||||||
|
confidence: "medium",
|
||||||
|
description:
|
||||||
|
"This unresolved explanation arises from the now-assessed relationship between the observations.",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
proposal: {
|
proposal: {
|
||||||
addedNodes: [
|
addedNodes: [
|
||||||
{
|
{
|
||||||
id: "n-next-unknown",
|
id: "n-next-unknown",
|
||||||
label: "Commercial value definition",
|
label:
|
||||||
description: "Need a definition because the decision depends on it.",
|
"Explanation for why revenue increased by 18%, but cash in the bank fell over the same period",
|
||||||
|
description:
|
||||||
|
"Need to understand what change or event could explain why these observations differ, because that is needed to investigate their relationship.",
|
||||||
kind: "unknown",
|
kind: "unknown",
|
||||||
status: "unknown",
|
status: "unknown",
|
||||||
confidence: "high",
|
confidence: "medium",
|
||||||
value: null,
|
value: null,
|
||||||
unit: null,
|
unit: null,
|
||||||
evidenceIds: [],
|
evidenceIds: [],
|
||||||
dependsOn: [],
|
dependsOn: ["n-conclusion"],
|
||||||
affects: [],
|
affects: [],
|
||||||
parentId: null,
|
parentId: "n-conclusion",
|
||||||
childIds: [],
|
childIds: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -153,19 +167,27 @@ function makeUpdateSuccess(overrides = {}) {
|
|||||||
affectedNodeIds: ["n-conclusion"],
|
affectedNodeIds: ["n-conclusion"],
|
||||||
selectedQuestion: {
|
selectedQuestion: {
|
||||||
nodeId: "n-next-unknown",
|
nodeId: "n-next-unknown",
|
||||||
question: "How should commercial value be defined for this decision?",
|
question:
|
||||||
reason: "A narrower consequential uncertainty remains.",
|
"What changed during that period that could help explain why revenue increased by 18%, but cash in the bank fell over the same period?",
|
||||||
|
reason:
|
||||||
|
"Formulated as a neutral clarification question because no narrower investigation strategy clearly applied.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
selectedQuestion: {
|
selectedQuestion: {
|
||||||
nodeId: "n-next-unknown",
|
nodeId: "n-next-unknown",
|
||||||
question: "How should commercial value be defined for this decision?",
|
question:
|
||||||
reason: "A narrower consequential uncertainty remains.",
|
"What changed during that period that could help explain why revenue increased by 18%, but cash in the bank fell over the same period?",
|
||||||
|
reason:
|
||||||
|
"Formulated as a neutral clarification question because no narrower investigation strategy clearly applied.",
|
||||||
},
|
},
|
||||||
affectedNodeIds: ["n-conclusion"],
|
affectedNodeIds: ["n-conclusion"],
|
||||||
resolvedUnknownNodeIds: ["n-unknown"],
|
resolvedUnknownNodeIds: ["n-unknown"],
|
||||||
previousActiveUnknownNodeId: "n-unknown",
|
previousActiveUnknownNodeId: "n-unknown",
|
||||||
newActiveUnknownNodeId: "n-next-unknown",
|
newActiveUnknownNodeId: "n-next-unknown",
|
||||||
|
emergentReasoningNodeCreated: true,
|
||||||
|
emergentReasoningNodeId: "n-next-unknown",
|
||||||
|
emergentReasoningNodeReason:
|
||||||
|
"Created a new unresolved reasoning unknown so the next justified question is backed by the graph.",
|
||||||
previousReasoningState: {
|
previousReasoningState: {
|
||||||
comparabilityStatus: "uncertain",
|
comparabilityStatus: "uncertain",
|
||||||
reasoningStages: [
|
reasoningStages: [
|
||||||
@@ -404,7 +426,9 @@ describe("graph-backed UI rendering", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(html).toContain("Newly surfaced unknowns");
|
expect(html).toContain("Newly surfaced unknowns");
|
||||||
expect(html).toContain("Commercial value definition");
|
expect(html).toContain(
|
||||||
|
"Explanation for why revenue increased by 18%, but cash in the bank fell over the same period",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("affected nodes render", () => {
|
it("affected nodes render", () => {
|
||||||
@@ -432,7 +456,7 @@ describe("graph-backed UI rendering", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(html).toContain(
|
expect(html).toContain(
|
||||||
"How should commercial value be defined for this decision?",
|
"What changed during that period that could help explain why revenue increased by 18%, but cash in the bank fell over the same period?",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -469,7 +493,9 @@ describe("graph-backed UI rendering", () => {
|
|||||||
expect(html).toContain("Previous active unknown");
|
expect(html).toContain("Previous active unknown");
|
||||||
expect(html).toContain("Complaint rate denominator");
|
expect(html).toContain("Complaint rate denominator");
|
||||||
expect(html).toContain("New active unknown");
|
expect(html).toContain("New active unknown");
|
||||||
expect(html).toContain("Commercial value definition");
|
expect(html).toContain(
|
||||||
|
"Explanation for why revenue increased by 18%, but cash in the bank fell over the same period",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("successful update renders prior and new state together", () => {
|
it("successful update renders prior and new state together", () => {
|
||||||
@@ -488,7 +514,7 @@ describe("graph-backed UI rendering", () => {
|
|||||||
expect(html).toContain("New active unknown");
|
expect(html).toContain("New active unknown");
|
||||||
expect(html).toContain("Next question");
|
expect(html).toContain("Next question");
|
||||||
expect(html).toContain(
|
expect(html).toContain(
|
||||||
"How should commercial value be defined for this decision?",
|
"What changed during that period that could help explain why revenue increased by 18%, but cash in the bank fell over the same period?",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -543,7 +569,9 @@ describe("graph-backed UI rendering", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(html).toContain("How should commercial value be defined for this decision?");
|
expect(html).toContain(
|
||||||
|
"What changed during that period that could help explain why revenue increased by 18%, but cash in the bank fell over the same period?",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("raw ids remain only in collapsed proposal details", () => {
|
it("raw ids remain only in collapsed proposal details", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user