276 lines
9.5 KiB
JavaScript
276 lines
9.5 KiB
JavaScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
assessComparability,
|
|
classifyObservationRelationship,
|
|
formulateTieResolutionQuestion,
|
|
} from "@/lib/graph/question-formulator.js";
|
|
import { explainUnknownSelection } from "@/lib/graph/utils.js";
|
|
import { comparabilityAssessmentFixtures } from "@/tests/fixtures/comparability-assessment.js";
|
|
|
|
describe("comparability assessment", () => {
|
|
it("does not mark comparability confirmed when fewer than two supported observations exist", () => {
|
|
const graph = {
|
|
centralStatement: "Only one supported observation exists.",
|
|
nodes: [
|
|
{
|
|
id: "obs-single",
|
|
label: "Revenue increased by 18%.",
|
|
description: "Revenue increased by 18%.",
|
|
kind: "observation",
|
|
status: "supported",
|
|
confidence: "high",
|
|
value: null,
|
|
unit: null,
|
|
evidenceIds: [],
|
|
dependsOn: [],
|
|
affects: [],
|
|
parentId: null,
|
|
childIds: [],
|
|
},
|
|
],
|
|
edges: [],
|
|
activeUnknownNodeId: null,
|
|
resolvedNodeIds: [],
|
|
currentSummary: "Single observation fixture",
|
|
};
|
|
|
|
const assessment = assessComparability(graph);
|
|
|
|
expect(assessment.comparabilityStatus).not.toBe("confirmed");
|
|
expect(assessment.contradictionReasoningAllowed).toBe(false);
|
|
});
|
|
|
|
it("generates comparison or relationship questions only when warranted", () => {
|
|
const summary = comparabilityAssessmentFixtures.map((fixture) => {
|
|
const assessment = assessComparability(fixture.graph);
|
|
const relationship = classifyObservationRelationship(fixture.graph);
|
|
const question = formulateTieResolutionQuestion({ graph: fixture.graph });
|
|
const ambiguity = explainUnknownSelection(fixture.graph, []);
|
|
|
|
expect(assessment.comparabilityStatus).toBe(
|
|
fixture.expectedComparabilityStatus,
|
|
);
|
|
expect(question.comparabilityStatus).toBe(
|
|
fixture.expectedComparabilityStatus,
|
|
);
|
|
|
|
if (fixture.expectsComparisonQuestion) {
|
|
expect(question.question.toLowerCase()).toContain("same");
|
|
expect(question.contradictionReasoningAllowed).toBe(false);
|
|
} else {
|
|
expect(question.question?.toLowerCase() || "").not.toContain(
|
|
"same period and at the same scale",
|
|
);
|
|
}
|
|
|
|
if (fixture.key !== "sales-same") {
|
|
expect(["ambiguous", "selected"]).toContain(ambiguity.status);
|
|
}
|
|
|
|
return {
|
|
scenario: fixture.scenario,
|
|
comparabilityStatus: assessment.comparabilityStatus,
|
|
relationshipStatus: relationship.relationshipStatus,
|
|
relationshipAssessed: relationship.relationshipAssessed,
|
|
contradictionReasoningAllowed: question.contradictionReasoningAllowed,
|
|
question: question.question,
|
|
};
|
|
});
|
|
|
|
expect(summary).toEqual([
|
|
{
|
|
scenario:
|
|
"Revenue increased by 18%, but cash in the bank fell over the same period.",
|
|
comparabilityStatus: "uncertain",
|
|
relationshipStatus: "insufficient_information",
|
|
relationshipAssessed: false,
|
|
contradictionReasoningAllowed: false,
|
|
question:
|
|
"Were these figures measured on the same basis and at the same scale?",
|
|
},
|
|
{
|
|
scenario: "Complaints increased. Production increased.",
|
|
comparabilityStatus: "uncertain",
|
|
relationshipStatus: "insufficient_information",
|
|
relationshipAssessed: false,
|
|
contradictionReasoningAllowed: false,
|
|
question:
|
|
"Were these figures measured over the same period and at the same scale?",
|
|
},
|
|
{
|
|
scenario:
|
|
"Average delivery time decreased by 25%, but order cancellations increased.",
|
|
comparabilityStatus: "uncertain",
|
|
relationshipStatus: "insufficient_information",
|
|
relationshipAssessed: false,
|
|
contradictionReasoningAllowed: false,
|
|
question:
|
|
"Were these figures measured over the same period and at the same scale?",
|
|
},
|
|
{
|
|
scenario: "Customer satisfaction increased, but complaints increased.",
|
|
comparabilityStatus: "uncertain",
|
|
relationshipStatus: "insufficient_information",
|
|
relationshipAssessed: false,
|
|
contradictionReasoningAllowed: false,
|
|
question:
|
|
"Were these figures measured over the same period and at the same scale?",
|
|
},
|
|
{
|
|
scenario: "Temperature increased. Ice melted.",
|
|
comparabilityStatus: "confirmed",
|
|
relationshipStatus: "compatible",
|
|
relationshipAssessed: true,
|
|
contradictionReasoningAllowed: false,
|
|
question: null,
|
|
},
|
|
{
|
|
scenario: "Sales doubled. Sales doubled.",
|
|
comparabilityStatus: "confirmed",
|
|
relationshipStatus: "duplicate",
|
|
relationshipAssessed: true,
|
|
contradictionReasoningAllowed: false,
|
|
question: null,
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("defers relationship classification while comparability is uncertain", () => {
|
|
const fixture = comparabilityAssessmentFixtures[0];
|
|
const relationship = classifyObservationRelationship(fixture.graph);
|
|
|
|
expect(relationship).toMatchObject({
|
|
relationshipStatus: "insufficient_information",
|
|
relationshipAssessed: false,
|
|
contradictionReasoningAllowed: false,
|
|
questionRequired: true,
|
|
});
|
|
expect(relationship.reasoningStages).toEqual([
|
|
{
|
|
stage: "comparability",
|
|
status: "uncertain",
|
|
outcome:
|
|
"Comparability between the observations is not yet established across period, scale, or measurement basis.",
|
|
},
|
|
{
|
|
stage: "relationship",
|
|
status: "insufficient_information",
|
|
outcome: "not assessed until comparability is established",
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("returns questionRequired=false for fallback (no meaningful relationship structure)", () => {
|
|
// Two non-measurement-like observations with no shared concept and no known direction.
|
|
// They pass comparability (confirmed) but fall through all classification conditions:
|
|
// - not duplicate, no available+unavailable, no revenue/cash pair, no all-known-directions
|
|
// → hits the fallback which now returns questionRequired=false
|
|
const graph = {
|
|
centralStatement:
|
|
"Temperature was measured. Ice was observed.",
|
|
nodes: [
|
|
{
|
|
id: "obs-temperature",
|
|
label: "Temperature was measured.",
|
|
description: "Temperature was measured.",
|
|
kind: "observation",
|
|
status: "supported",
|
|
confidence: "high",
|
|
value: null,
|
|
unit: null,
|
|
evidenceIds: [],
|
|
dependsOn: [],
|
|
affects: [],
|
|
parentId: null,
|
|
childIds: [],
|
|
},
|
|
{
|
|
id: "obs-ice",
|
|
label: "Ice was observed.",
|
|
description: "Ice was observed.",
|
|
kind: "observation",
|
|
status: "supported",
|
|
confidence: "high",
|
|
value: null,
|
|
unit: null,
|
|
evidenceIds: [],
|
|
dependsOn: [],
|
|
affects: [],
|
|
parentId: null,
|
|
childIds: [],
|
|
},
|
|
],
|
|
edges: [],
|
|
activeUnknownNodeId: null,
|
|
resolvedNodeIds: [],
|
|
currentSummary: "Fallback regression fixture",
|
|
};
|
|
|
|
const relationship = classifyObservationRelationship(graph);
|
|
|
|
expect(relationship.relationshipStatus).toBe("insufficient_information");
|
|
expect(relationship.questionRequired).toBe(false);
|
|
// Verify reasoning stages exist and have correct status
|
|
const stages = relationship.reasoningStages;
|
|
expect(stages.length).toBe(2);
|
|
expect(stages[0].stage).toBe("comparability");
|
|
expect(stages[0].status).toBe("confirmed");
|
|
expect(stages[1].stage).toBe("relationship");
|
|
expect(stages[1].status).toBe("insufficient_information");
|
|
|
|
// questionRequired=false means buildEmergentReasoningUnknown returns early,
|
|
// so no "Explanation for why..." unknown is created — the engine preserves
|
|
// uncertainty rather than fabricating an explanation problem.
|
|
});
|
|
|
|
it("allows contradiction reasoning only for genuine contradictions", () => {
|
|
const serviceGraph = {
|
|
centralStatement:
|
|
"The service was reported as available throughout the hour and unavailable throughout the same hour.",
|
|
nodes: [
|
|
{
|
|
id: "service-available",
|
|
label: "The service was available throughout the hour.",
|
|
description: "The service was available throughout the hour.",
|
|
kind: "observation",
|
|
status: "supported",
|
|
confidence: "high",
|
|
value: null,
|
|
unit: null,
|
|
evidenceIds: [],
|
|
dependsOn: [],
|
|
affects: [],
|
|
parentId: null,
|
|
childIds: [],
|
|
},
|
|
{
|
|
id: "service-unavailable",
|
|
label: "The service was unavailable throughout the same hour.",
|
|
description: "The service was unavailable throughout the same hour.",
|
|
kind: "observation",
|
|
status: "supported",
|
|
confidence: "high",
|
|
value: null,
|
|
unit: null,
|
|
evidenceIds: [],
|
|
dependsOn: [],
|
|
affects: [],
|
|
parentId: null,
|
|
childIds: [],
|
|
},
|
|
],
|
|
edges: [],
|
|
activeUnknownNodeId: null,
|
|
resolvedNodeIds: [],
|
|
currentSummary: "Service contradiction fixture",
|
|
};
|
|
const relationship = classifyObservationRelationship(serviceGraph);
|
|
|
|
expect(relationship).toMatchObject({
|
|
relationshipStatus: "contradictory",
|
|
contradictionReasoningAllowed: true,
|
|
questionRequired: true,
|
|
});
|
|
});
|
|
});
|