feat: surface new unknowns after graph updates
This commit is contained in:
@@ -99,6 +99,7 @@ function makeApplicationFixture() {
|
||||
removedEdgeIds: [],
|
||||
resolvedUnknownNodeIds: [complaintRateUnknown.id],
|
||||
affectedNodeIds: [qualityDeterioration.id],
|
||||
selectedQuestion: null,
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -444,6 +445,7 @@ describe("applyValidatedProposal", () => {
|
||||
removedEdgeIds: [],
|
||||
resolvedUnknownNodeIds: [],
|
||||
affectedNodeIds: [],
|
||||
selectedQuestion: null,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -455,4 +457,246 @@ describe("applyValidatedProposal", () => {
|
||||
expect.arrayContaining([expect.stringContaining("no meaningful change")]),
|
||||
);
|
||||
});
|
||||
|
||||
it("resolves one unknown and adds consequential unknowns with one selected question", () => {
|
||||
const { graph, ids } = makeApplicationFixture();
|
||||
|
||||
const proposal = {
|
||||
addedNodes: [
|
||||
makeNode({
|
||||
id: "n-commercial-value",
|
||||
label: "Commercial value definition",
|
||||
description:
|
||||
"Need a concrete definition of commercial value because the decision depends on it.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "high",
|
||||
}),
|
||||
makeNode({
|
||||
id: "n-demand-evidence",
|
||||
label: "Evidence of demand",
|
||||
description:
|
||||
"Need evidence of demand because it matters to the build decision.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
}),
|
||||
makeNode({
|
||||
id: "n-build-decision",
|
||||
label: "Build Confidence Engine decision",
|
||||
description: "Decision situation introduced by the answer.",
|
||||
kind: "state",
|
||||
status: "supported",
|
||||
confidence: "medium",
|
||||
}),
|
||||
],
|
||||
updatedNodes: [
|
||||
{
|
||||
nodeId: ids.complaintRateUnknown,
|
||||
previousStatus: "unknown",
|
||||
newStatus: "resolved",
|
||||
previousValue: null,
|
||||
newValue: "Decision whether to build Confidence Engine",
|
||||
reason: "The answer resolves the original context unknown.",
|
||||
},
|
||||
],
|
||||
addedEdges: [
|
||||
makeEdge({
|
||||
id: "e-build-commercial-value",
|
||||
fromNodeId: "n-build-decision",
|
||||
toNodeId: "n-commercial-value",
|
||||
relationship: "depends_on",
|
||||
confidence: "medium",
|
||||
description: "The decision depends on defining commercial value.",
|
||||
}),
|
||||
makeEdge({
|
||||
id: "e-build-demand-evidence",
|
||||
fromNodeId: "n-build-decision",
|
||||
toNodeId: "n-demand-evidence",
|
||||
relationship: "depends_on",
|
||||
confidence: "medium",
|
||||
description: "The decision depends on evidence of demand.",
|
||||
}),
|
||||
],
|
||||
removedEdgeIds: [],
|
||||
resolvedUnknownNodeIds: [ids.complaintRateUnknown],
|
||||
affectedNodeIds: [],
|
||||
selectedQuestion: {
|
||||
nodeId: "n-commercial-value",
|
||||
question: "How should commercial value be defined for this decision?",
|
||||
reason:
|
||||
"This is the most consequential unresolved unknown introduced by the answer.",
|
||||
},
|
||||
};
|
||||
|
||||
const result = applyValidatedProposal({ situationGraph: graph, proposal });
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.updatedSituationGraph.resolvedNodeIds).toContain(
|
||||
ids.complaintRateUnknown,
|
||||
);
|
||||
expect(
|
||||
result.updatedSituationGraph.nodes.some(
|
||||
(node) => node.id === "n-commercial-value",
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
result.updatedSituationGraph.nodes.some(
|
||||
(node) => node.id === "n-demand-evidence",
|
||||
),
|
||||
).toBe(true);
|
||||
expect(result.newActiveUnknownNodeId).toBe("n-commercial-value");
|
||||
expect(result.selectedQuestion).toEqual(proposal.selectedQuestion);
|
||||
});
|
||||
|
||||
it("rejects more than 3 added unknowns", () => {
|
||||
const { graph, proposal, ids } = makeApplicationFixture();
|
||||
|
||||
const result = applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
proposal: {
|
||||
...proposal,
|
||||
addedNodes: [1, 2, 3, 4].map((index) =>
|
||||
makeNode({
|
||||
id: `n-unknown-${index}`,
|
||||
label: `Unknown ${index}`,
|
||||
description: `Need unknown ${index} because it matters to the decision.`,
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
}),
|
||||
),
|
||||
addedEdges: [1, 2, 3, 4].map((index) =>
|
||||
makeEdge({
|
||||
id: `e-unknown-${index}`,
|
||||
fromNodeId: ids.complaintRateUnknown,
|
||||
toNodeId: `n-unknown-${index}`,
|
||||
relationship: "depends_on",
|
||||
confidence: "medium",
|
||||
description: `Links unknown ${index}`,
|
||||
}),
|
||||
),
|
||||
selectedQuestion: {
|
||||
nodeId: "n-unknown-1",
|
||||
question: "What is unknown 1?",
|
||||
reason: "Follow-up required.",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.errors.join(" ")).toContain("too many unknown nodes");
|
||||
});
|
||||
|
||||
it("rejects unrelated added unknowns", () => {
|
||||
const { graph, proposal } = makeApplicationFixture();
|
||||
|
||||
const result = applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
proposal: {
|
||||
...proposal,
|
||||
addedNodes: [
|
||||
makeNode({
|
||||
id: "n-unrelated",
|
||||
label: "Office rent",
|
||||
description:
|
||||
"Need office rent because it matters to a different branch.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "low",
|
||||
}),
|
||||
],
|
||||
selectedQuestion: {
|
||||
nodeId: "n-unrelated",
|
||||
question: "What is the office rent?",
|
||||
reason: "Unrelated test.",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.errors.join(" ")).toContain(
|
||||
"explicitly related to an answer-derived node",
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects selected question referencing resolved node", () => {
|
||||
const { graph, proposal, ids } = makeApplicationFixture();
|
||||
|
||||
const result = applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
proposal: {
|
||||
...proposal,
|
||||
selectedQuestion: {
|
||||
nodeId: ids.complaintRateUnknown,
|
||||
question: "What is the complaint rate?",
|
||||
reason: "Invalid reselection.",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.errors.join(" ")).toContain(
|
||||
"selectedQuestion must reference an unresolved node",
|
||||
);
|
||||
});
|
||||
|
||||
it("active unknown matches selected question node", () => {
|
||||
const { graph, ids } = makeApplicationFixture();
|
||||
|
||||
const proposal = {
|
||||
addedNodes: [
|
||||
makeNode({
|
||||
id: "n-success-threshold",
|
||||
label: "Success threshold",
|
||||
description:
|
||||
"Need a success threshold because the decision depends on it.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "high",
|
||||
}),
|
||||
makeNode({
|
||||
id: "n-build-decision",
|
||||
label: "Build Confidence Engine decision",
|
||||
description: "Decision introduced by the answer.",
|
||||
kind: "state",
|
||||
status: "supported",
|
||||
confidence: "medium",
|
||||
}),
|
||||
],
|
||||
updatedNodes: [
|
||||
{
|
||||
nodeId: ids.complaintRateUnknown,
|
||||
previousStatus: "unknown",
|
||||
newStatus: "resolved",
|
||||
previousValue: null,
|
||||
newValue: "Decision whether to build Confidence Engine",
|
||||
reason: "The answer resolves the original unknown.",
|
||||
},
|
||||
],
|
||||
addedEdges: [
|
||||
makeEdge({
|
||||
id: "e-build-success-threshold",
|
||||
fromNodeId: "n-build-decision",
|
||||
toNodeId: "n-success-threshold",
|
||||
relationship: "depends_on",
|
||||
confidence: "medium",
|
||||
description: "The decision depends on a success threshold.",
|
||||
}),
|
||||
],
|
||||
removedEdgeIds: [],
|
||||
resolvedUnknownNodeIds: [ids.complaintRateUnknown],
|
||||
affectedNodeIds: [],
|
||||
selectedQuestion: {
|
||||
nodeId: "n-success-threshold",
|
||||
question: "What success threshold would justify building it?",
|
||||
reason: "One consequential unknown remains.",
|
||||
},
|
||||
};
|
||||
|
||||
const result = applyValidatedProposal({ situationGraph: graph, proposal });
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.newActiveUnknownNodeId).toBe(result.selectedQuestion?.nodeId);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user