204 lines
8.3 KiB
JavaScript
204 lines
8.3 KiB
JavaScript
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);
|
|
},
|
|
);
|
|
}); |