Files
confidence-engine/tests/graph/question-priority-generalisation.test.js
T

156 lines
5.1 KiB
JavaScript

import { describe, expect, it } from "vitest";
import { applyValidatedProposal } from "@/lib/graph/apply-proposal.js";
import { formulateQuestion } from "@/lib/graph/question-formulator.js";
import { selectActiveUnknownCandidate } from "@/lib/graph/utils.js";
import { questionPriorityGeneralisationFixtures } from "@/tests/fixtures/question-priority-generalisation.js";
function clone(value) {
return JSON.parse(JSON.stringify(value));
}
function buildResolutionProposal(graph) {
const activeNode = graph.nodes.find(
(node) => node.id === graph.activeUnknownNodeId,
);
const placeholderCandidate = graph.nodes.find(
(node) => node.kind === "unknown" && node.id !== activeNode.id,
);
return {
addedNodes: [],
updatedNodes: [
{
nodeId: activeNode.id,
previousStatus: activeNode.status,
newStatus: "resolved",
previousValue: activeNode.value ?? null,
newValue: activeNode.value ?? "Resolved context answer",
reason:
"The resolved context unknown is treated as answered for fixture progression.",
},
],
addedEdges: [],
removedEdgeIds: [],
resolvedUnknownNodeIds: [activeNode.id],
affectedNodeIds: [],
selectedQuestion: {
nodeId: placeholderCandidate?.id,
question: "Placeholder candidate question?",
reason: "Candidate only; deterministic selector should override it.",
},
};
}
function assertQuestionStructure(question) {
expect(question.match(/\?/g) || []).toHaveLength(1);
expect(question).not.toMatch(/\?\s*(and|or)\b/i);
expect(question).not.toMatch(/^What is\s+/i);
expect(question).toMatch(/^(What|Who|When)\b/);
}
describe("question priority generalisation", () => {
for (const fixture of questionPriorityGeneralisationFixtures) {
it(`${fixture.scenario} selects a foundational unknown and singular answerable strategy`, () => {
const originalGraph = clone(fixture.graph);
const deterministicSelection = selectActiveUnknownCandidate(
fixture.graph,
[fixture.graph.activeUnknownNodeId],
);
const result = applyValidatedProposal({
situationGraph: fixture.graph,
proposal: buildResolutionProposal(fixture.graph),
});
expect(result.success).toBe(true);
expect(fixture.graph).toEqual(originalGraph);
expect(result.graphUpdate.selectedQuestion?.question).toBe(
"Placeholder candidate question?",
);
expect(deterministicSelection.nodeId).toBe(
result.selectedQuestion.nodeId,
);
expect(fixture.acceptableFoundationalUnknownNodeIds).toContain(
result.selectedQuestion.nodeId,
);
expect(result.selectedQuestion.nodeId).not.toBe(
fixture.graph.nodes[fixture.graph.nodes.length - 1].id,
);
expect(fixture.acceptableQuestionStrategies).toContain(
result.selectedQuestion.strategy,
);
assertQuestionStructure(result.selectedQuestion.question);
const lowerQuestion = result.selectedQuestion.question.toLowerCase();
for (const topic of fixture.prohibitedFirstTopics) {
expect(lowerQuestion).not.toContain(topic.toLowerCase());
}
const selectedNode = result.updatedSituationGraph.nodes.find(
(node) => node.id === result.selectedQuestion.nodeId,
);
const reformulated = formulateQuestion({
node: selectedNode,
graph: result.updatedSituationGraph,
context: {
resolvedValues: ["Resolved context answer"],
},
});
expect(reformulated.question).toBe(result.selectedQuestion.question);
expect(clone(result.updatedSituationGraph)).toEqual(
result.updatedSituationGraph,
);
});
}
it("reports all five selected unknowns and strategies", () => {
const summary = questionPriorityGeneralisationFixtures.map((fixture) => {
const result = applyValidatedProposal({
situationGraph: fixture.graph,
proposal: buildResolutionProposal(fixture.graph),
});
expect(result.success).toBe(true);
return {
scenario: fixture.scenario,
nodeId: result.selectedQuestion.nodeId,
strategy: result.selectedQuestion.strategy,
};
});
expect(summary).toMatchInlineSnapshot(`
[
{
"nodeId": "hire-success-criteria",
"scenario": "Should we hire another engineer?",
"strategy": "decision_threshold",
},
{
"nodeId": "van-reliability-threshold",
"scenario": "Should we replace the delivery vans?",
"strategy": "decision_threshold",
},
{
"nodeId": "country-value-threshold",
"scenario": "Should we launch in another country?",
"strategy": "decision_threshold",
},
{
"nodeId": "project-benefit-threshold",
"scenario": "Should we continue a project that is over budget?",
"strategy": "decision_threshold",
},
{
"nodeId": "support-value-threshold",
"scenario": "Should we introduce a paid support tier?",
"strategy": "baseline_reconstruction",
},
]
`);
});
});