wip(confidence-engine): checkpoint open-question presentation work

This commit is contained in:
2026-10-09 07:46:19 +01:00
parent f74fe6de10
commit 6bc8be6edb
19 changed files with 812 additions and 22 deletions
+110 -4
View File
@@ -1,5 +1,6 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { normaliseAnalysisResponse } from "@/lib/reconstruction/compatibility.js";
import { reconstructionGenerationJsonSchema } from "@/lib/reconstruction/schema.js";
const mockGenerateReconstruction = vi.fn();
@@ -17,6 +18,7 @@ vi.mock("@/lib/llm/provider.js", () => ({
getProvider: () => ({
generateReconstruction: (...args) => mockGenerateReconstruction(...args),
}),
getProviderModelName: () => "test-model",
}));
vi.mock("@/lib/reconstruction/prompt.js", () => ({
@@ -134,13 +136,117 @@ describe("analyseScenario compatibility", () => {
});
expect(result.success).toBe(true);
expect(injectedProvider.generateReconstruction).toHaveBeenCalledWith(
"prompt",
"experiment-model",
);
expect(injectedProvider.generateReconstruction).toHaveBeenCalledTimes(1);
const [prompt, modelName, outputSchema, options] =
injectedProvider.generateReconstruction.mock.calls[0];
expect(prompt).toBe("prompt");
expect(modelName).toBe("experiment-model");
expect(outputSchema).toEqual(reconstructionGenerationJsonSchema);
expect(options.outputZodSchema.safeParse).toBeTypeOf("function");
expect(
options.outputZodSchema.safeParse({
inputClassification: {
primaryType: "other",
secondaryTypes: [],
reasoningModes: [],
classificationReason: "reason",
confidence: "medium",
},
reconstruction: {
summary: "summary",
actors: [],
systemsOrObjects: [],
expectedStates: [],
observedStates: [],
differences: [],
knownTransitions: [],
unexplainedTransitions: [],
contradictions: [],
importantUnknowns: [],
plausibleInterpretations: [],
relationships: [],
},
evidence: [],
nextQuestion: {
id: "q1",
question: "What next?",
targets: [],
reason: "reason",
expectedInformationValue: "low",
reasoningMode: "other",
},
}).success,
).toBe(true);
expect(mockGenerateReconstruction).not.toHaveBeenCalled();
});
it("preserves canonical reconstruction and reports invalid presentation nonfatally", async () => {
const response = {
inputClassification: {
primaryType: "other",
secondaryTypes: [],
reasoningModes: [],
classificationReason: "reason",
confidence: "medium",
},
reconstruction: {
summary: "summary",
actors: [],
systemsOrObjects: [],
expectedStates: [],
observedStates: [],
differences: [],
knownTransitions: [],
unexplainedTransitions: [],
contradictions: [],
importantUnknowns: [
{
id: "unknown-1",
description: "Whether workers can be recruited in time.",
question: "Whether workers can be recruited in time?",
confidence: "high",
},
],
plausibleInterpretations: [],
relationships: [],
},
evidence: [],
nextQuestion: {
id: "q1",
question: "What recruitment evidence exists?",
targets: ["unknown-1"],
reason: "reason",
expectedInformationValue: "high",
reasoningMode: "other",
},
};
const injectedProvider = {
generateReconstruction: vi.fn().mockResolvedValue(response),
};
const { analyseScenario } = await import("@/lib/analysis.js");
const result = await analyseScenario("Scenario text", {
reconstructionProvider: injectedProvider,
reconstructionModelName: "experiment-model",
});
expect(result.success).toBe(true);
expect(result.validationStatus).toBe("valid");
expect(result.presentationValidationStatus).toBe("partial");
expect(result.reconstruction.importantUnknowns[0]).toEqual({
id: "unknown-1",
description: "Whether workers can be recruited in time.",
confidence: "high",
});
expect(result.presentationValidationIssues).toEqual([
expect.objectContaining({
category: "question_indirect_proposition",
unknownId: "unknown-1",
severity: "nonfatal",
}),
]);
});
it("preserves an attempted provider API path on provider failure", async () => {
const providerError = new Error("Provider failed");
providerError.providerApiPath = "/api/chat";