feat: advance reasoning after comparability is resolved
This commit is contained in:
@@ -3,6 +3,120 @@ import { applyValidatedProposal } from "@/lib/graph/apply-proposal.js";
|
||||
import { makeEdge, makeGraph, makeNode } from "@/lib/graph/schema.js";
|
||||
import { validateGraphReferences } from "@/lib/graph/utils.js";
|
||||
|
||||
function makeComparabilityUpdateFixture() {
|
||||
const comparabilityUnknown = makeNode({
|
||||
id: "n-comparability-unknown",
|
||||
label: "Whether the figures are comparable",
|
||||
description:
|
||||
"Need to know whether the figures use the same period, basis, and scale before comparing them.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "high",
|
||||
});
|
||||
const revenueObservation = makeNode({
|
||||
id: "n-revenue-observation",
|
||||
label: "Revenue increased by 18%.",
|
||||
description: "Revenue increased by 18%.",
|
||||
kind: "observation",
|
||||
status: "supported",
|
||||
confidence: "high",
|
||||
});
|
||||
const cashObservation = makeNode({
|
||||
id: "n-cash-observation",
|
||||
label: "Cash in the bank decreased over the same period.",
|
||||
description: "Cash in the bank decreased over the same period.",
|
||||
kind: "observation",
|
||||
status: "supported",
|
||||
confidence: "high",
|
||||
});
|
||||
const unrelatedNode = makeNode({
|
||||
id: "n-unrelated",
|
||||
label: "Board update",
|
||||
description: "A separate unchanged note.",
|
||||
kind: "state",
|
||||
status: "known",
|
||||
confidence: "low",
|
||||
});
|
||||
|
||||
const graph = makeGraph({
|
||||
centralStatement:
|
||||
"Revenue increased by 18%, but cash in the bank fell over the same period.",
|
||||
nodes: [
|
||||
comparabilityUnknown,
|
||||
revenueObservation,
|
||||
cashObservation,
|
||||
unrelatedNode,
|
||||
],
|
||||
edges: [
|
||||
makeEdge({
|
||||
id: "e-revenue-comparability",
|
||||
fromNodeId: revenueObservation.id,
|
||||
toNodeId: comparabilityUnknown.id,
|
||||
relationship: "supports",
|
||||
confidence: "medium",
|
||||
description: "Revenue observation requires comparability confirmation.",
|
||||
}),
|
||||
makeEdge({
|
||||
id: "e-cash-comparability",
|
||||
fromNodeId: cashObservation.id,
|
||||
toNodeId: comparabilityUnknown.id,
|
||||
relationship: "supports",
|
||||
confidence: "medium",
|
||||
description: "Cash observation requires comparability confirmation.",
|
||||
}),
|
||||
],
|
||||
activeUnknownNodeId: comparabilityUnknown.id,
|
||||
resolvedNodeIds: [],
|
||||
currentSummary: "Initial comparability fixture",
|
||||
reasoningState: {
|
||||
comparabilityStatus: "uncertain",
|
||||
comparabilityReason:
|
||||
"Comparability between the observations is not yet established across period, scale, or measurement basis.",
|
||||
comparabilityEvidence: [],
|
||||
relationshipStatus: "insufficient_information",
|
||||
relationshipReason:
|
||||
"Relationship classification is deferred until comparability is established.",
|
||||
relationshipAssessed: false,
|
||||
contradictionReasoningAllowed: false,
|
||||
reasoningStages: [
|
||||
{
|
||||
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",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const proposal = {
|
||||
addedNodes: [],
|
||||
updatedNodes: [
|
||||
{
|
||||
nodeId: comparabilityUnknown.id,
|
||||
previousStatus: "unknown",
|
||||
newStatus: "resolved",
|
||||
previousValue: null,
|
||||
newValue:
|
||||
"Both figures cover the same accounting period and are taken from the same management accounts.",
|
||||
reason: "The answer confirms the figures are comparable.",
|
||||
},
|
||||
],
|
||||
addedEdges: [],
|
||||
removedEdgeIds: [],
|
||||
resolvedUnknownNodeIds: [comparabilityUnknown.id],
|
||||
affectedNodeIds: [],
|
||||
selectedQuestion: null,
|
||||
};
|
||||
|
||||
return { graph, proposal, comparabilityUnknownId: comparabilityUnknown.id };
|
||||
}
|
||||
|
||||
function makeApplicationFixture() {
|
||||
const complaintRateUnknown = makeNode({
|
||||
id: "n-complaint-rate-unknown",
|
||||
@@ -997,4 +1111,64 @@ describe("applyValidatedProposal", () => {
|
||||
"price",
|
||||
);
|
||||
});
|
||||
|
||||
it("resolves the existing comparability unknown and advances reasoning after the answer", () => {
|
||||
const { graph, proposal, comparabilityUnknownId } =
|
||||
makeComparabilityUpdateFixture();
|
||||
const originalUnrelatedNode = JSON.stringify(
|
||||
graph.nodes.find((node) => node.id === "n-unrelated"),
|
||||
);
|
||||
|
||||
const result = applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
proposal,
|
||||
previousQuestion:
|
||||
"Were these figures measured on the same basis and at the same scale?",
|
||||
answer:
|
||||
"Yes. Both figures cover the same accounting period and are taken from the same management accounts.",
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.resolvedUnknownNodeIds).toContain(comparabilityUnknownId);
|
||||
expect(result.resolvedReasoningNodeIds).toEqual([
|
||||
"reasoning:comparability",
|
||||
]);
|
||||
expect(result.previousReasoningState.comparabilityStatus).toBe("uncertain");
|
||||
expect(result.reasoningState).toMatchObject({
|
||||
comparabilityStatus: "confirmed",
|
||||
relationshipStatus: "potentially_related",
|
||||
relationshipAssessed: true,
|
||||
});
|
||||
expect(result.reasoningState.comparabilityEvidence).toEqual([
|
||||
comparabilityUnknownId,
|
||||
]);
|
||||
expect(result.selectedQuestion?.question).toMatch(
|
||||
/^What changed during that period that could help explain why /,
|
||||
);
|
||||
expect(result.selectedQuestion?.question).not.toContain("same basis");
|
||||
expect(result.selectedQuestion?.question.toLowerCase()).not.toMatch(
|
||||
/dso|debtor days|receivables turnover|working capital|receivables/,
|
||||
);
|
||||
expect(result.reasoningState.reasoningStages).toEqual([
|
||||
{
|
||||
stage: "comparability",
|
||||
status: "confirmed",
|
||||
outcome:
|
||||
"Comparability was confirmed by the user answer covering the same period and source basis.",
|
||||
},
|
||||
{
|
||||
stage: "relationship",
|
||||
status: "potentially_related",
|
||||
outcome:
|
||||
"The observations concern connected business signals but do not establish a direct contradiction or cause.",
|
||||
},
|
||||
]);
|
||||
expect(
|
||||
JSON.stringify(
|
||||
result.updatedSituationGraph.nodes.find(
|
||||
(node) => node.id === "n-unrelated",
|
||||
),
|
||||
),
|
||||
).toBe(originalUnrelatedNode);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -118,6 +118,90 @@ function makeProposal(overrides = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function makeComparabilityScenarioGraph() {
|
||||
return makeGraph({
|
||||
centralStatement:
|
||||
"Revenue increased by 18%, but cash in the bank fell over the same period.",
|
||||
nodes: [
|
||||
makeNode({
|
||||
id: "n-comparability-unknown",
|
||||
label: "Whether the figures are comparable",
|
||||
description:
|
||||
"Need to know whether the figures use the same period, basis, and scale before comparing them.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "high",
|
||||
}),
|
||||
makeNode({
|
||||
id: "n-revenue-observation",
|
||||
label: "Revenue increased by 18%.",
|
||||
description: "Revenue increased by 18%.",
|
||||
kind: "observation",
|
||||
status: "supported",
|
||||
confidence: "high",
|
||||
}),
|
||||
makeNode({
|
||||
id: "n-cash-observation",
|
||||
label: "Cash in the bank decreased over the same period.",
|
||||
description: "Cash in the bank decreased over the same period.",
|
||||
kind: "observation",
|
||||
status: "supported",
|
||||
confidence: "high",
|
||||
}),
|
||||
],
|
||||
edges: [],
|
||||
activeUnknownNodeId: "n-comparability-unknown",
|
||||
resolvedNodeIds: [],
|
||||
currentSummary: "Comparability scenario",
|
||||
reasoningState: {
|
||||
comparabilityStatus: "uncertain",
|
||||
comparabilityReason:
|
||||
"Comparability between the observations is not yet established across period, scale, or measurement basis.",
|
||||
comparabilityEvidence: [],
|
||||
relationshipStatus: "insufficient_information",
|
||||
relationshipReason:
|
||||
"Relationship classification is deferred until comparability is established.",
|
||||
relationshipAssessed: false,
|
||||
contradictionReasoningAllowed: false,
|
||||
reasoningStages: [
|
||||
{
|
||||
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",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function makeComparabilityProposal() {
|
||||
return {
|
||||
addedNodes: [],
|
||||
updatedNodes: [
|
||||
{
|
||||
nodeId: "n-comparability-unknown",
|
||||
previousStatus: "unknown",
|
||||
newStatus: "resolved",
|
||||
previousValue: null,
|
||||
newValue:
|
||||
"Both figures cover the same accounting period and are taken from the same management accounts.",
|
||||
reason: "The answer confirms comparability.",
|
||||
},
|
||||
],
|
||||
addedEdges: [],
|
||||
removedEdgeIds: [],
|
||||
resolvedUnknownNodeIds: ["n-comparability-unknown"],
|
||||
affectedNodeIds: [],
|
||||
selectedQuestion: null,
|
||||
};
|
||||
}
|
||||
|
||||
describe("lib/graph/orchestrator startCase", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
@@ -930,6 +1014,74 @@ describe("lib/graph/orchestrator startCase", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("advances reasoning after comparability is resolved by the update answer", async () => {
|
||||
const { updateCase } = await import("@/lib/graph/orchestrator.js");
|
||||
const provider = {
|
||||
generateReconstruction: vi
|
||||
.fn()
|
||||
.mockResolvedValue(makeComparabilityProposal()),
|
||||
};
|
||||
|
||||
const result = await updateCase(
|
||||
{
|
||||
situationGraph: makeComparabilityScenarioGraph(),
|
||||
previousQuestion:
|
||||
"Were these figures measured on the same basis and at the same scale?",
|
||||
answer:
|
||||
"Yes. Both figures cover the same accounting period and are taken from the same management accounts.",
|
||||
promptVersion: "v0.4",
|
||||
},
|
||||
{
|
||||
provider,
|
||||
config: MOCK_CONFIG,
|
||||
applyProposal: true,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.resolvedUnknownNodeIds).toEqual(["n-comparability-unknown"]);
|
||||
expect(result.diagnostics).toMatchObject({
|
||||
previousComparabilityStatus: "uncertain",
|
||||
comparabilityStatus: "confirmed",
|
||||
relationshipStatus: "potentially_related",
|
||||
relationshipAssessed: true,
|
||||
resolvedReasoningNodeIds: ["reasoning:comparability"],
|
||||
});
|
||||
expect(result.diagnostics.reasoningStagesBefore).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",
|
||||
},
|
||||
]);
|
||||
expect(result.diagnostics.reasoningStagesAfter).toEqual([
|
||||
{
|
||||
stage: "comparability",
|
||||
status: "confirmed",
|
||||
outcome:
|
||||
"Comparability was confirmed by the user answer covering the same period and source basis.",
|
||||
},
|
||||
{
|
||||
stage: "relationship",
|
||||
status: "potentially_related",
|
||||
outcome:
|
||||
"The observations concern connected business signals but do not establish a direct contradiction or cause.",
|
||||
},
|
||||
]);
|
||||
expect(result.selectedQuestion?.question).toMatch(
|
||||
/^What changed during that period that could help explain why /,
|
||||
);
|
||||
expect(result.selectedQuestion?.question.toLowerCase()).not.toMatch(
|
||||
/same basis|dso|receivables|debtor days|working capital/,
|
||||
);
|
||||
});
|
||||
|
||||
it("startCase behaviour remains unchanged", async () => {
|
||||
mockAnalyseScenario.mockResolvedValue(makeAnalysisResult());
|
||||
const { startCase } = await import("@/lib/graph/orchestrator.js");
|
||||
|
||||
Reference in New Issue
Block a user