wip(confidence-engine): checkpoint open-question presentation work
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { promises as fs } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import {
|
||||
reconstructionGenerationJsonSchema,
|
||||
reconstructionGenerationSchema,
|
||||
reconstructionV2Schema,
|
||||
} from "@/lib/reconstruction/schema.js";
|
||||
import {
|
||||
validateOpenQuestionCandidate,
|
||||
validateReconstructionOpenQuestions,
|
||||
} from "@/lib/reconstruction/open-question-presentation.js";
|
||||
|
||||
function makeAnalysisResponse(questionValue, includeQuestion = true) {
|
||||
const unknown = {
|
||||
id: "unknown-workforce",
|
||||
description:
|
||||
"Whether necessary skilled production workers can be recruited within the required project timeframe.",
|
||||
confidence: "high",
|
||||
};
|
||||
if (includeQuestion) unknown.question = questionValue;
|
||||
|
||||
return {
|
||||
inputClassification: {
|
||||
primaryType: "other",
|
||||
secondaryTypes: [],
|
||||
reasoningModes: [],
|
||||
classificationReason: "A delivery uncertainty is central to the situation.",
|
||||
confidence: "high",
|
||||
},
|
||||
reconstruction: {
|
||||
summary: "Delivery depends on recruiting skilled production workers.",
|
||||
actors: [],
|
||||
systemsOrObjects: [],
|
||||
expectedStates: [],
|
||||
observedStates: [],
|
||||
differences: [],
|
||||
knownTransitions: [],
|
||||
unexplainedTransitions: [],
|
||||
contradictions: [],
|
||||
importantUnknowns: [unknown],
|
||||
plausibleInterpretations: [],
|
||||
relationships: [],
|
||||
},
|
||||
evidence: [],
|
||||
nextQuestion: {
|
||||
id: "next-1",
|
||||
question: "What recruitment evidence is available?",
|
||||
targets: ["unknown-workforce"],
|
||||
reason: "Recruitment feasibility is unresolved.",
|
||||
expectedInformationValue: "high",
|
||||
reasoningMode: "other",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("new reconstruction generation contract", () => {
|
||||
it("requires a non-empty question for every generated important unknown", () => {
|
||||
expect(
|
||||
reconstructionGenerationSchema.safeParse(
|
||||
makeAnalysisResponse(undefined, false),
|
||||
).success,
|
||||
).toBe(false);
|
||||
expect(
|
||||
reconstructionGenerationSchema.safeParse(makeAnalysisResponse(" ")).success,
|
||||
).toBe(false);
|
||||
expect(
|
||||
reconstructionGenerationSchema.safeParse(
|
||||
makeAnalysisResponse(
|
||||
"Can the necessary skilled production workers be recruited within the required project timeframe?",
|
||||
),
|
||||
).success,
|
||||
).toBe(true);
|
||||
|
||||
const unknownSchema =
|
||||
reconstructionGenerationJsonSchema.properties.reconstruction.properties
|
||||
.importantUnknowns.items;
|
||||
expect(unknownSchema.required).toContain("description");
|
||||
expect(unknownSchema.required).toContain("question");
|
||||
expect(unknownSchema.properties.question.minLength).toBe(1);
|
||||
});
|
||||
|
||||
it("keeps the durable compatibility schema tolerant of a missing question", () => {
|
||||
expect(
|
||||
reconstructionV2Schema.safeParse(makeAnalysisResponse(undefined, false)).success,
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Open Question presentation validation", () => {
|
||||
it("accepts a trimmed direct interrogative", () => {
|
||||
expect(
|
||||
validateOpenQuestionCandidate(
|
||||
" Can the necessary workers be recruited in time? ",
|
||||
),
|
||||
).toEqual({
|
||||
valid: true,
|
||||
question: "Can the necessary workers be recruited in time?",
|
||||
});
|
||||
});
|
||||
|
||||
it("uses canonical UI fallback without a technical explanation", async () => {
|
||||
const source = await fs.readFile(
|
||||
join(process.cwd(), "components/reasoning-workspace.jsx"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
expect(source).toContain("const openQuestion = node.openQuestion || node.label;");
|
||||
expect(source).not.toContain(
|
||||
"Canonical uncertainty — direct question wording unavailable.",
|
||||
);
|
||||
expect(source).toContain("startFocused(n.id);");
|
||||
});
|
||||
|
||||
it.each([
|
||||
["question_missing", undefined, false],
|
||||
["question_empty", " ", true],
|
||||
["question_indirect_proposition", "Whether workers can be recruited in time", true],
|
||||
["question_indirect_proposition", "Whether workers can be recruited in time?", true],
|
||||
["question_not_interrogative", "Workers can be recruited in time.", true],
|
||||
])("classifies %s nonfatally", (category, candidate, includeQuestion) => {
|
||||
const input = makeAnalysisResponse(candidate, includeQuestion).reconstruction;
|
||||
const canonicalBefore = input.importantUnknowns[0].description;
|
||||
const idBefore = input.importantUnknowns[0].id;
|
||||
|
||||
const result = validateReconstructionOpenQuestions(input);
|
||||
|
||||
expect(result.reconstruction.importantUnknowns[0]).not.toHaveProperty("question");
|
||||
expect(result.reconstruction.importantUnknowns[0].description).toBe(
|
||||
canonicalBefore,
|
||||
);
|
||||
expect(result.reconstruction.importantUnknowns[0].id).toBe(idBefore);
|
||||
expect(result.issues).toEqual([
|
||||
expect.objectContaining({
|
||||
category,
|
||||
unknownId: idBefore,
|
||||
severity: "nonfatal",
|
||||
path: ["reconstruction", "importantUnknowns", 0, "question"],
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user