feat: formulate follow-up questions from graph context
This commit is contained in:
@@ -547,8 +547,13 @@ describe("applyValidatedProposal", () => {
|
||||
).toBe(true);
|
||||
expect(result.newActiveUnknownNodeId).toBe("n-commercial-value");
|
||||
expect(result.selectedQuestion?.nodeId).toBe("n-commercial-value");
|
||||
expect(result.selectedQuestion?.question).toContain(
|
||||
"Commercial value definition",
|
||||
expect(result.selectedQuestion?.question).toMatch(/\?$/);
|
||||
expect(result.selectedQuestion?.question.length).toBeGreaterThan(20);
|
||||
expect(result.selectedQuestion?.question.toLowerCase()).not.toContain(
|
||||
"price",
|
||||
);
|
||||
expect(result.selectedQuestion?.question.toLowerCase()).not.toContain(
|
||||
"how should uncertainty regarding",
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -584,6 +584,12 @@ describe("lib/graph/orchestrator startCase", () => {
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.selectedQuestion?.nodeId).toBe("n-commercial-value");
|
||||
expect(result.newActiveUnknownNodeId).toBe("n-commercial-value");
|
||||
expect(result.selectedQuestion?.question).not.toBe(
|
||||
"How should commercial value be defined for this decision?",
|
||||
);
|
||||
expect(result.selectedQuestion?.question.toLowerCase()).not.toContain(
|
||||
"how should uncertainty regarding",
|
||||
);
|
||||
});
|
||||
|
||||
it("deterministically prioritises customer value over pricing follow-up", async () => {
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { formulateQuestion } from "@/lib/graph/question-formulator.js";
|
||||
import { makeEdge, makeGraph, makeNode } from "@/lib/graph/schema.js";
|
||||
|
||||
function makeGraphFor(node, extra = {}) {
|
||||
return makeGraph({
|
||||
centralStatement: extra.centralStatement || "Decision context",
|
||||
nodes: [node, ...(extra.nodes || [])],
|
||||
edges: extra.edges || [],
|
||||
activeUnknownNodeId: node.id,
|
||||
resolvedNodeIds: extra.resolvedNodeIds || [],
|
||||
currentSummary: "Test summary",
|
||||
});
|
||||
}
|
||||
|
||||
describe("formulateQuestion", () => {
|
||||
it("commercial viability plus build decision produces a decision-criterion question", () => {
|
||||
const unknown = makeNode({
|
||||
id: "n-commercial",
|
||||
label: "Uncertainty regarding the commercial value of the product",
|
||||
description:
|
||||
"Commercial justification remains unclear because the decision depends on it.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "high",
|
||||
parentId: "n-decision",
|
||||
});
|
||||
const decision = makeNode({
|
||||
id: "n-decision",
|
||||
label: "Build decision",
|
||||
description: "Decision introduced by the answer.",
|
||||
kind: "state",
|
||||
status: "known",
|
||||
confidence: "medium",
|
||||
childIds: [unknown.id],
|
||||
value: "Deciding whether to build the product",
|
||||
});
|
||||
const graph = makeGraphFor(unknown, {
|
||||
nodes: [decision],
|
||||
resolvedNodeIds: [decision.id],
|
||||
});
|
||||
|
||||
const result = formulateQuestion({ node: unknown, graph });
|
||||
|
||||
expect(result.strategy).toBe("decision criterion");
|
||||
expect(result.question).toContain("What outcome");
|
||||
expect(result.question.toLowerCase()).toContain("justify");
|
||||
});
|
||||
|
||||
it("commercial viability does not produce a pricing-first question", () => {
|
||||
const unknown = makeNode({
|
||||
id: "n-commercial",
|
||||
label: "Commercial viability",
|
||||
description:
|
||||
"Commercial viability remains unresolved because the decision depends on it.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "high",
|
||||
});
|
||||
const graph = makeGraphFor(unknown);
|
||||
|
||||
const result = formulateQuestion({ node: unknown, graph });
|
||||
|
||||
expect(result.question.toLowerCase()).not.toContain("price");
|
||||
expect(result.question.toLowerCase()).not.toContain("pricing");
|
||||
});
|
||||
|
||||
it("undefined term produces a definition question", () => {
|
||||
const unknown = makeNode({
|
||||
id: "n-term",
|
||||
label: "Success criteria definition",
|
||||
description:
|
||||
"Need a definition of the term because the team uses it inconsistently.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
});
|
||||
|
||||
const result = formulateQuestion({
|
||||
node: unknown,
|
||||
graph: makeGraphFor(unknown),
|
||||
});
|
||||
|
||||
expect(result.strategy).toBe("definition");
|
||||
expect(result.question).toMatch(/^What does /);
|
||||
});
|
||||
|
||||
it("unsupported claim produces an evidence question", () => {
|
||||
const unknown = makeNode({
|
||||
id: "n-claim",
|
||||
label: "Demand claim",
|
||||
description: "Need evidence because the claim has not been validated.",
|
||||
kind: "reported_claim",
|
||||
status: "provisional",
|
||||
confidence: "low",
|
||||
});
|
||||
|
||||
const result = formulateQuestion({
|
||||
node: unknown,
|
||||
graph: makeGraphFor(unknown),
|
||||
});
|
||||
|
||||
expect(result.strategy).toBe("evidence");
|
||||
expect(result.question).toContain("What evidence");
|
||||
});
|
||||
|
||||
it("missing previous state produces a baseline question", () => {
|
||||
const unknown = makeNode({
|
||||
id: "n-baseline",
|
||||
label: "Baseline conversion rate",
|
||||
description:
|
||||
"Need the previous baseline because the change cannot be assessed without it.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
});
|
||||
|
||||
const result = formulateQuestion({
|
||||
node: unknown,
|
||||
graph: makeGraphFor(unknown),
|
||||
});
|
||||
|
||||
expect(result.strategy).toBe("baseline");
|
||||
expect(result.question).toContain("What was the comparable state before");
|
||||
});
|
||||
|
||||
it("unknown customer produces an actor/customer question", () => {
|
||||
const unknown = makeNode({
|
||||
id: "n-customer",
|
||||
label: "Target customer",
|
||||
description:
|
||||
"Need to know the customer because value depends on who receives it.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "high",
|
||||
});
|
||||
|
||||
const result = formulateQuestion({
|
||||
node: unknown,
|
||||
graph: makeGraphFor(unknown),
|
||||
});
|
||||
|
||||
expect(result.strategy).toBe("actor/customer");
|
||||
expect(result.question).toContain(
|
||||
"Who experiences the problem or receives the value",
|
||||
);
|
||||
});
|
||||
|
||||
it("constraint unknown produces a constraint question", () => {
|
||||
const unknown = makeNode({
|
||||
id: "n-constraint",
|
||||
label: "Budget constraint",
|
||||
description:
|
||||
"Need the main budget constraint because it limits the available options.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
});
|
||||
|
||||
const result = formulateQuestion({
|
||||
node: unknown,
|
||||
graph: makeGraphFor(unknown),
|
||||
});
|
||||
|
||||
expect(result.strategy).toBe("constraint");
|
||||
expect(result.question).toContain("What constraint most limits");
|
||||
});
|
||||
|
||||
it("question is singular and answerable", () => {
|
||||
const unknown = makeNode({
|
||||
id: "n-evidence",
|
||||
label: "Evidence of demand",
|
||||
description:
|
||||
"Need evidence of demand because the decision depends on it.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
});
|
||||
|
||||
const result = formulateQuestion({
|
||||
node: unknown,
|
||||
graph: makeGraphFor(unknown),
|
||||
});
|
||||
|
||||
expect(result.question.match(/\?/g) || []).toHaveLength(1);
|
||||
expect(result.question.toLowerCase()).not.toContain(" and ");
|
||||
});
|
||||
|
||||
it("awkward uncertainty phrasing is rejected via fallback", () => {
|
||||
const unknown = makeNode({
|
||||
id: "n-weird",
|
||||
label: "Uncertainty regarding service reliability",
|
||||
description: "Unknown service reliability.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
});
|
||||
|
||||
const result = formulateQuestion({
|
||||
node: unknown,
|
||||
graph: makeGraphFor(unknown),
|
||||
});
|
||||
|
||||
expect(result.question).not.toContain("How should uncertainty regarding");
|
||||
expect(result.question).not.toContain(
|
||||
"What would resolve uncertainty regarding",
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user