wip(confidence-engine): checkpoint open-question presentation work
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildInitialGraph } from "@/lib/graph/builder.js";
|
||||
import { formulateQuestionForTarget } from "@/lib/graph/focused-investigation.js";
|
||||
import { makeGraph, makeNodeId } from "@/lib/graph/schema.js";
|
||||
import { buildFacilitatorViewModel } from "@/lib/presentation/facilitator-view-adapter.js";
|
||||
import { buildPrompt } from "@/lib/reconstruction/prompt.js";
|
||||
import { validateReconstructionOpenQuestions } from "@/lib/reconstruction/open-question-presentation.js";
|
||||
import { reconstructionV2Schema } from "@/lib/reconstruction/schema.js";
|
||||
|
||||
function reconstructionWithUnknowns(importantUnknowns) {
|
||||
return {
|
||||
inputClassification: {
|
||||
primaryType: "other",
|
||||
secondaryTypes: [],
|
||||
reasoningModes: ["identify_missing_information"],
|
||||
classificationReason: "The scenario contains unresolved factors.",
|
||||
confidence: "medium",
|
||||
},
|
||||
reconstruction: {
|
||||
summary: "A situation with unresolved factors.",
|
||||
actors: [],
|
||||
systemsOrObjects: [],
|
||||
expectedStates: [],
|
||||
observedStates: [],
|
||||
differences: [],
|
||||
knownTransitions: [],
|
||||
unexplainedTransitions: [],
|
||||
contradictions: [],
|
||||
importantUnknowns,
|
||||
plausibleInterpretations: [],
|
||||
relationships: [],
|
||||
},
|
||||
evidence: [],
|
||||
nextQuestion: {
|
||||
id: "next-question",
|
||||
question: "What information would clarify the situation?",
|
||||
targets: [],
|
||||
reason: "The situation has unresolved factors.",
|
||||
expectedInformationValue: "medium",
|
||||
reasoningMode: "identify_missing_information",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const CASES = [
|
||||
{
|
||||
name: "recruitment uncertainty",
|
||||
description: "Whether skilled production staff can be recruited within the required timeframe",
|
||||
question: "Can skilled production staff be recruited within the required timeframe?",
|
||||
terms: ["skilled production staff", "required timeframe"],
|
||||
},
|
||||
{
|
||||
name: "demand uncertainty",
|
||||
description: "The level of demand in the North West market needed to justify the expansion cost",
|
||||
question: "What level of demand in the North West market is needed to justify the expansion cost?",
|
||||
terms: ["North West market", "justify the expansion cost"],
|
||||
},
|
||||
{
|
||||
name: "historical uncertainty",
|
||||
description: "Whether the prior staffing level was sufficient before the expansion",
|
||||
question: "Was the prior staffing level sufficient before the expansion?",
|
||||
terms: ["prior staffing level", "before the expansion"],
|
||||
},
|
||||
{
|
||||
name: "non-business uncertainty",
|
||||
description: "The time at which the missing hiker last used the eastern trail",
|
||||
question: "When did the missing hiker last use the eastern trail?",
|
||||
terms: ["missing hiker", "eastern trail"],
|
||||
},
|
||||
];
|
||||
|
||||
describe("Open Question presentation", () => {
|
||||
it.each(["v0.2", "v0.3", "v0.4", "v0.5"])("instructs reconstruction prompt %s to produce a direct Open Question", async (version) => {
|
||||
const { prompt } = await buildPrompt("A scenario with an unresolved condition.", version);
|
||||
|
||||
expect(prompt).toContain("importantUnknown");
|
||||
expect(prompt).toContain("natural, direct interrogative");
|
||||
expect(prompt).toContain('"question"');
|
||||
expect(prompt).toContain('"Whether …"');
|
||||
});
|
||||
|
||||
it.each(CASES)("preserves $name meaning while displaying a direct interrogative", ({ description, question, terms }) => {
|
||||
const parsed = reconstructionV2Schema.parse(
|
||||
reconstructionWithUnknowns([{ id: "unknown", description, question, confidence: "medium" }]),
|
||||
);
|
||||
const graph = buildInitialGraph({ reconstruction: parsed.reconstruction, evidence: [] });
|
||||
const unknown = graph.nodes.find((node) => node.kind === "unknown");
|
||||
const viewModel = buildFacilitatorViewModel({ nodes: graph.nodes, resolvedIds: new Set() });
|
||||
|
||||
expect(unknown.label).toBe(description);
|
||||
expect(unknown.description).toBe(description);
|
||||
expect(unknown.openQuestion).toBe(question);
|
||||
expect(viewModel.investigating.items).toContain(question);
|
||||
expect(question).toMatch(/\?$/);
|
||||
expect(question).not.toMatch(/^whether\b/i);
|
||||
expect(question).not.toMatch(/^before whether\b/i);
|
||||
for (const term of terms) expect(question.toLowerCase()).toContain(term.toLowerCase());
|
||||
});
|
||||
|
||||
it("leaves canonical identity, explicit target selection, and focused formulation unchanged", () => {
|
||||
const description = CASES[0].description;
|
||||
const question = CASES[0].question;
|
||||
const graphData = buildInitialGraph({
|
||||
reconstruction: reconstructionWithUnknowns([
|
||||
{ id: "recruitment", description, question, confidence: "medium" },
|
||||
]).reconstruction,
|
||||
evidence: [],
|
||||
});
|
||||
const target = graphData.nodes.find((node) => node.kind === "unknown");
|
||||
const graph = makeGraph({
|
||||
centralStatement: "A manufacturer is considering expansion.",
|
||||
nodes: graphData.nodes,
|
||||
edges: graphData.edges,
|
||||
activeUnknownNodeId: null,
|
||||
resolvedNodeIds: [],
|
||||
currentSummary: "Expansion depends on recruitment capacity.",
|
||||
});
|
||||
const before = JSON.stringify(graph);
|
||||
const result = formulateQuestionForTarget({ situationGraph: graph, targetNodeId: target.id });
|
||||
|
||||
expect(target.id).toBe(makeNodeId(description));
|
||||
expect(target.id).not.toBe(makeNodeId(question));
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.targetNodeId).toBe(target.id);
|
||||
expect(result.question).toMatch(/\?$/);
|
||||
expect(result.question.toLowerCase()).toContain("staff");
|
||||
expect(JSON.stringify(graph)).toBe(before);
|
||||
});
|
||||
|
||||
it("keeps persisted legacy unknowns valid and falls back to canonical text when no display question exists", () => {
|
||||
const legacyDescription = "Whether the figures are comparable";
|
||||
const legacyNode = makeGraph({
|
||||
centralStatement: "Compare two figures.",
|
||||
nodes: [
|
||||
{
|
||||
id: makeNodeId(legacyDescription),
|
||||
label: legacyDescription,
|
||||
description: legacyDescription,
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
evidenceIds: [],
|
||||
dependsOn: [],
|
||||
affects: [],
|
||||
parentId: null,
|
||||
childIds: [],
|
||||
},
|
||||
],
|
||||
edges: [],
|
||||
activeUnknownNodeId: null,
|
||||
resolvedNodeIds: [],
|
||||
currentSummary: "The comparison remains unresolved.",
|
||||
});
|
||||
const viewModel = buildFacilitatorViewModel({ nodes: legacyNode.nodes, resolvedIds: new Set() });
|
||||
|
||||
expect(viewModel.investigating.items).toContain(legacyDescription);
|
||||
expect(legacyNode.nodes[0]).not.toHaveProperty("openQuestion");
|
||||
});
|
||||
|
||||
it.each([
|
||||
["missing", undefined, false],
|
||||
["empty", " ", true],
|
||||
["whether fragment", "Whether the facility can recruit workers in time", true],
|
||||
["whether fragment with punctuation", "Whether the facility can recruit workers in time?", true],
|
||||
["statement", "The facility can recruit workers in time.", true],
|
||||
])(
|
||||
"keeps an invalid %s presentation canonically selectable and investigable",
|
||||
(_name, question, includeQuestion) => {
|
||||
const description =
|
||||
"Whether the facility can recruit skilled workers within the launch timeframe.";
|
||||
const unknown = {
|
||||
id: "unknown",
|
||||
description,
|
||||
confidence: "medium",
|
||||
};
|
||||
if (includeQuestion) unknown.question = question;
|
||||
const reconstruction = reconstructionWithUnknowns([unknown]).reconstruction;
|
||||
const validated = validateReconstructionOpenQuestions(reconstruction);
|
||||
const graphParts = buildInitialGraph({
|
||||
reconstruction: validated.reconstruction,
|
||||
evidence: [],
|
||||
});
|
||||
const graph = makeGraph({
|
||||
centralStatement: "The facility launch depends on workforce availability.",
|
||||
nodes: graphParts.nodes,
|
||||
edges: graphParts.edges,
|
||||
currentSummary: "Workforce availability remains unresolved.",
|
||||
});
|
||||
const graphUnknown = graph.nodes.find((node) => node.kind === "unknown");
|
||||
const focused = formulateQuestionForTarget({
|
||||
situationGraph: graph,
|
||||
targetNodeId: graphUnknown.id,
|
||||
});
|
||||
|
||||
expect(validated.issues).toHaveLength(1);
|
||||
expect(graphUnknown.label).toBe(description);
|
||||
expect(graphUnknown.description).toBe(description);
|
||||
expect(graphUnknown).not.toHaveProperty("openQuestion");
|
||||
expect(graphUnknown.id).toBe(makeNodeId(description));
|
||||
expect(focused.success).toBe(true);
|
||||
expect(focused.targetNodeId).toBe(graphUnknown.id);
|
||||
},
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user