reasoning: prevent unsupported comparison decomposition
This commit is contained in:
@@ -278,6 +278,70 @@ function makeRiskClarificationFixture() {
|
||||
return { graph, riskUnknownId: riskUnknown.id };
|
||||
}
|
||||
|
||||
function makeGenericExplanationFixture() {
|
||||
const explanationUnknown = makeNode({
|
||||
id: "n-generic-explanation",
|
||||
label:
|
||||
"Explanation for why Should I relocate my engineering team from London to Manchester",
|
||||
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 observationA = makeNode({
|
||||
id: "n-london-team-state",
|
||||
label: "Engineering team is currently operational in London.",
|
||||
description: "Engineering team is currently operational in London.",
|
||||
kind: "observation",
|
||||
status: "supported",
|
||||
confidence: "high",
|
||||
});
|
||||
|
||||
const observationB = makeNode({
|
||||
id: "n-manchester-relocation-eval",
|
||||
label:
|
||||
"Relocation to Manchester is actively being evaluated by the decision-maker.",
|
||||
description:
|
||||
"Relocation to Manchester is actively being evaluated by the decision-maker.",
|
||||
kind: "observation",
|
||||
status: "supported",
|
||||
confidence: "high",
|
||||
});
|
||||
|
||||
const graph = makeGraph({
|
||||
centralStatement:
|
||||
"Should I relocate my engineering team from London to Manchester?",
|
||||
nodes: [explanationUnknown, observationA, observationB],
|
||||
edges: [
|
||||
makeEdge({
|
||||
id: "e-london-explanation",
|
||||
fromNodeId: observationA.id,
|
||||
toNodeId: explanationUnknown.id,
|
||||
relationship: "supports",
|
||||
confidence: "medium",
|
||||
description:
|
||||
"The current London operating state contributes to the broad explanation unknown.",
|
||||
}),
|
||||
makeEdge({
|
||||
id: "e-manchester-explanation",
|
||||
fromNodeId: observationB.id,
|
||||
toNodeId: explanationUnknown.id,
|
||||
relationship: "supports",
|
||||
confidence: "medium",
|
||||
description:
|
||||
"The Manchester relocation evaluation contributes to the broad explanation unknown.",
|
||||
}),
|
||||
],
|
||||
activeUnknownNodeId: explanationUnknown.id,
|
||||
resolvedNodeIds: [],
|
||||
currentSummary: "Generic explanation fixture",
|
||||
});
|
||||
|
||||
return { graph, explanationUnknownId: explanationUnknown.id };
|
||||
}
|
||||
|
||||
function makeMeaningfulNoOpProposal() {
|
||||
return {
|
||||
addedNodes: [
|
||||
@@ -2114,86 +2178,38 @@ describe("applyValidatedProposal", () => {
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("decomposes a composite selected unknown before asking the next question", () => {
|
||||
const { graph, proposal } = makeComparabilityUpdateFixture();
|
||||
it("does not decompose a generic explanation unknown into unsupported comparison children", () => {
|
||||
const { graph, explanationUnknownId } = makeGenericExplanationFixture();
|
||||
|
||||
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.",
|
||||
proposal: makeMeaningfulNoOpProposal(),
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.atomicityAssessment).toBe("composite");
|
||||
expect(result.decompositionPerformed).toBe(true);
|
||||
expect(result.childUnknownCount).toBe(5);
|
||||
expect(result.childNodeIds).toHaveLength(5);
|
||||
expect(result.atomicityReason).toBeTruthy();
|
||||
expect(result.selectedQuestion?.nodeId).toBe(result.newActiveUnknownNodeId);
|
||||
expect(result.selectedQuestion?.nodeId).not.toBe(
|
||||
result.emergentReasoningNodeId,
|
||||
);
|
||||
expect(result.selectedQuestion?.question.toLowerCase()).not.toMatch(
|
||||
/dso|working capital|receivables|capex/,
|
||||
);
|
||||
|
||||
const parentNode = result.updatedSituationGraph.nodes.find(
|
||||
(node) => node.id === result.emergentReasoningNodeId,
|
||||
);
|
||||
expect(parentNode?.status).toBe("unknown");
|
||||
|
||||
const childNodes = result.updatedSituationGraph.nodes.filter((node) =>
|
||||
result.childNodeIds.includes(node.id),
|
||||
);
|
||||
expect(childNodes).toHaveLength(5);
|
||||
expect(childNodes.every((node) => node.parentId === parentNode.id)).toBe(
|
||||
true,
|
||||
);
|
||||
expect(result.decompositionPerformed).toBe(false);
|
||||
expect(result.childUnknownCount).toBe(0);
|
||||
expect(result.childNodeIds).toHaveLength(0);
|
||||
expect(
|
||||
result.updatedSituationGraph.edges.filter(
|
||||
(edge) =>
|
||||
result.childNodeIds.includes(edge.fromNodeId) &&
|
||||
edge.toNodeId === parentNode.id &&
|
||||
edge.relationship === "depends_on",
|
||||
result.updatedSituationGraph.nodes.some(
|
||||
(node) => node.label === "How the two observations were measured",
|
||||
),
|
||||
).toHaveLength(5);
|
||||
});
|
||||
|
||||
it("reuses existing decomposition children instead of duplicating them", () => {
|
||||
const { graph, proposal } = makeComparabilityUpdateFixture();
|
||||
|
||||
const firstResult = 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(firstResult.success).toBe(true);
|
||||
|
||||
const secondResult = 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(secondResult.success).toBe(true);
|
||||
expect(secondResult.atomicityAssessment).toBe("composite");
|
||||
const uniqueChildIds = new Set(firstResult.childNodeIds);
|
||||
expect(uniqueChildIds.size).toBe(firstResult.childNodeIds.length);
|
||||
).toBe(false);
|
||||
expect(
|
||||
secondResult.updatedSituationGraph.nodes.filter((node) =>
|
||||
firstResult.childNodeIds.includes(node.id),
|
||||
result.updatedSituationGraph.nodes.some(
|
||||
(node) =>
|
||||
node.label ===
|
||||
"Whether the two observations reflect different timing",
|
||||
),
|
||||
).toHaveLength(firstResult.childNodeIds.length);
|
||||
).toBe(false);
|
||||
expect(result.selectedQuestion?.question || "").not.toContain(
|
||||
"two observations",
|
||||
);
|
||||
expect(result.decompositionStoppedReason).toBe(
|
||||
"Decomposition stopped because no meaning-preserving child family was justified for this parent.",
|
||||
);
|
||||
expect(result.selectedUnknownBefore).toBe(explanationUnknownId);
|
||||
});
|
||||
|
||||
it("reselects a remaining commercial sibling after resolving the first child", () => {
|
||||
|
||||
Reference in New Issue
Block a user