feat: advance reasoning after comparability is resolved
This commit is contained in:
@@ -28,6 +28,8 @@ export default function GraphUpdateView({ updateResult }) {
|
|||||||
proposal,
|
proposal,
|
||||||
previousSituationGraph,
|
previousSituationGraph,
|
||||||
updatedSituationGraph,
|
updatedSituationGraph,
|
||||||
|
reasoningState,
|
||||||
|
previousReasoningState,
|
||||||
} = updateResult;
|
} = updateResult;
|
||||||
|
|
||||||
const newlySurfacedUnknownNodeIds = (proposal.addedNodes || [])
|
const newlySurfacedUnknownNodeIds = (proposal.addedNodes || [])
|
||||||
@@ -109,6 +111,23 @@ export default function GraphUpdateView({ updateResult }) {
|
|||||||
: null,
|
: null,
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
|
|
||||||
|
const previousComparabilityStatus =
|
||||||
|
previousReasoningState?.comparabilityStatus ||
|
||||||
|
previousSituationGraph?.reasoningState?.comparabilityStatus ||
|
||||||
|
null;
|
||||||
|
const newComparabilityStatus =
|
||||||
|
reasoningState?.comparabilityStatus ||
|
||||||
|
updatedSituationGraph?.reasoningState?.comparabilityStatus ||
|
||||||
|
null;
|
||||||
|
const relationshipStatus =
|
||||||
|
reasoningState?.relationshipStatus ||
|
||||||
|
updatedSituationGraph?.reasoningState?.relationshipStatus ||
|
||||||
|
null;
|
||||||
|
const reasoningStagesAfter =
|
||||||
|
reasoningState?.reasoningStages ||
|
||||||
|
updatedSituationGraph?.reasoningState?.reasoningStages ||
|
||||||
|
[];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<section className="rounded-lg border border-blue-200 bg-blue-50 p-4">
|
<section className="rounded-lg border border-blue-200 bg-blue-50 p-4">
|
||||||
@@ -134,12 +153,32 @@ export default function GraphUpdateView({ updateResult }) {
|
|||||||
{selectedQuestion.question}
|
{selectedQuestion.question}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{previousComparabilityStatus && newComparabilityStatus && (
|
||||||
|
<div>
|
||||||
|
<span className="font-medium">Comparability:</span>{" "}
|
||||||
|
{previousComparabilityStatus} → {newComparabilityStatus}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{relationshipStatus && (
|
||||||
|
<div>
|
||||||
|
<span className="font-medium">Relationship status:</span>{" "}
|
||||||
|
{relationshipStatus}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{!selectedQuestion?.question && !newActiveUnknownNodeId && previousActiveUnknownNodeId && (
|
{!selectedQuestion?.question && !newActiveUnknownNodeId && previousActiveUnknownNodeId && (
|
||||||
<div>
|
<div>
|
||||||
<span className="font-medium">Next question status:</span> No next question selected yet.
|
<span className="font-medium">Next question status:</span> No next question selected yet.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{reasoningStagesAfter.length > 0 && (
|
||||||
|
<div className="mt-3 text-sm text-blue-950">
|
||||||
|
<span className="font-medium">Reasoning stages:</span>{" "}
|
||||||
|
{reasoningStagesAfter
|
||||||
|
.map((stage) => `${stage.stage}: ${stage.status}`)
|
||||||
|
.join(" → ")}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<ListSection
|
<ListSection
|
||||||
|
|||||||
@@ -36,3 +36,9 @@ After comparability assessment, observations now pass through a deterministic re
|
|||||||
Yes, in minimal deterministic form.
|
Yes, in minimal deterministic form.
|
||||||
|
|
||||||
The repeated pattern appeared in four scenarios, so a small pre-contradiction comparability assessment is justified.
|
The repeated pattern appeared in four scenarios, so a small pre-contradiction comparability assessment is justified.
|
||||||
|
|
||||||
|
## Two-step experiment result
|
||||||
|
|
||||||
|
A comparison question is useful only if its answer advances the reasoning stage rather than merely adding more text.
|
||||||
|
|
||||||
|
In the revenue-versus-cash scenario, the first question now confirms whether the figures are comparable, and the answer resolves that existing uncertainty instead of creating a parallel note. After that update, the engine progresses from comparability assessment to cautious relationship assessment and can select one broad non-expert follow-up question.
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { describeGraph } from "./builder.js";
|
import { describeGraph } from "./builder.js";
|
||||||
import {
|
import {
|
||||||
|
buildReasoningState,
|
||||||
|
COMPARABILITY_REASONING_NODE_ID,
|
||||||
formulateQuestion,
|
formulateQuestion,
|
||||||
formulateTieResolutionQuestion,
|
formulateTieResolutionQuestion,
|
||||||
} from "./question-formulator.js";
|
} from "./question-formulator.js";
|
||||||
@@ -426,7 +428,67 @@ function buildChangesApplied(proposal, affectedNodeIds) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyValidatedProposal({ situationGraph, proposal }) {
|
function isComparabilityQuestion(question) {
|
||||||
|
const text = String(question || "").toLowerCase();
|
||||||
|
return (
|
||||||
|
text.includes("same basis") ||
|
||||||
|
text.includes("same scale") ||
|
||||||
|
text.includes("same period")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function answerConfirmsComparability(answer) {
|
||||||
|
const text = String(answer || "").toLowerCase();
|
||||||
|
return (
|
||||||
|
/\byes\b/.test(text) &&
|
||||||
|
(text.includes("same accounting period") ||
|
||||||
|
text.includes("same management accounts") ||
|
||||||
|
text.includes("same basis") ||
|
||||||
|
text.includes("same scale") ||
|
||||||
|
text.includes("both figures cover the same"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deriveReasoningStateOverride({
|
||||||
|
graph,
|
||||||
|
previousQuestion,
|
||||||
|
answer,
|
||||||
|
resolvedUnknownNodeIds,
|
||||||
|
}) {
|
||||||
|
const previousReasoningState = buildReasoningState(graph);
|
||||||
|
const previousComparabilityStatus =
|
||||||
|
previousReasoningState.comparabilityStatus ?? null;
|
||||||
|
|
||||||
|
if (
|
||||||
|
previousComparabilityStatus === "uncertain" &&
|
||||||
|
isComparabilityQuestion(previousQuestion) &&
|
||||||
|
answerConfirmsComparability(answer)
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
reasoningStateOverride: {
|
||||||
|
comparabilityStatus: "confirmed",
|
||||||
|
comparabilityReason:
|
||||||
|
"Comparability was confirmed by the user answer covering the same period and source basis.",
|
||||||
|
comparabilityEvidence: resolvedUnknownNodeIds,
|
||||||
|
},
|
||||||
|
resolvedReasoningNodeIds: [COMPARABILITY_REASONING_NODE_ID],
|
||||||
|
previousReasoningState,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
reasoningStateOverride: {},
|
||||||
|
resolvedReasoningNodeIds: [],
|
||||||
|
previousReasoningState,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyValidatedProposal({
|
||||||
|
situationGraph,
|
||||||
|
proposal,
|
||||||
|
previousQuestion = null,
|
||||||
|
answer = null,
|
||||||
|
}) {
|
||||||
const graphValidation = situationGraphSchema.safeParse(situationGraph);
|
const graphValidation = situationGraphSchema.safeParse(situationGraph);
|
||||||
const proposalValidation = graphUpdateSchema.safeParse(proposal);
|
const proposalValidation = graphUpdateSchema.safeParse(proposal);
|
||||||
|
|
||||||
@@ -574,6 +636,12 @@ export function applyValidatedProposal({ situationGraph, proposal }) {
|
|||||||
const proposalSnapshot = cloneJsonSafe(validatedProposal);
|
const proposalSnapshot = cloneJsonSafe(validatedProposal);
|
||||||
const previousActiveUnknownNodeId = graphSnapshot.activeUnknownNodeId ?? null;
|
const previousActiveUnknownNodeId = graphSnapshot.activeUnknownNodeId ?? null;
|
||||||
const affectedNodeIds = buildAffectedNodeIds(graphSnapshot, proposalSnapshot);
|
const affectedNodeIds = buildAffectedNodeIds(graphSnapshot, proposalSnapshot);
|
||||||
|
const reasoningResolution = deriveReasoningStateOverride({
|
||||||
|
graph: graphSnapshot,
|
||||||
|
previousQuestion,
|
||||||
|
answer,
|
||||||
|
resolvedUnknownNodeIds: validatedProposal.resolvedUnknownNodeIds,
|
||||||
|
});
|
||||||
|
|
||||||
const applied = applyGraphUpdate(graphSnapshot, proposalSnapshot);
|
const applied = applyGraphUpdate(graphSnapshot, proposalSnapshot);
|
||||||
if (!applied.success) {
|
if (!applied.success) {
|
||||||
@@ -590,6 +658,11 @@ export function applyValidatedProposal({ situationGraph, proposal }) {
|
|||||||
edges: applied.edges,
|
edges: applied.edges,
|
||||||
resolvedNodeIds: applied.resolvedNodeIds,
|
resolvedNodeIds: applied.resolvedNodeIds,
|
||||||
};
|
};
|
||||||
|
const nextReasoningState = buildReasoningState(
|
||||||
|
updatedSituationGraph,
|
||||||
|
reasoningResolution.reasoningStateOverride,
|
||||||
|
);
|
||||||
|
updatedSituationGraph.reasoningState = nextReasoningState;
|
||||||
|
|
||||||
const activeUnknownWasResolved =
|
const activeUnknownWasResolved =
|
||||||
previousActiveUnknownNodeId != null &&
|
previousActiveUnknownNodeId != null &&
|
||||||
@@ -676,7 +749,21 @@ export function applyValidatedProposal({ situationGraph, proposal }) {
|
|||||||
strategy: formulatedQuestion?.strategy,
|
strategy: formulatedQuestion?.strategy,
|
||||||
investigationStrategy: formulatedQuestion?.investigationStrategy,
|
investigationStrategy: formulatedQuestion?.investigationStrategy,
|
||||||
}
|
}
|
||||||
: null;
|
: (() => {
|
||||||
|
const relationshipFallback = formulateTieResolutionQuestion({
|
||||||
|
graph: updatedSituationGraph,
|
||||||
|
});
|
||||||
|
return relationshipFallback?.question
|
||||||
|
? {
|
||||||
|
nodeId: null,
|
||||||
|
question: relationshipFallback.question,
|
||||||
|
reason: relationshipFallback.reason,
|
||||||
|
strategy: relationshipFallback.strategy,
|
||||||
|
investigationStrategy:
|
||||||
|
relationshipFallback.investigationStrategy,
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
})();
|
||||||
|
|
||||||
const resultGraphValidation = situationGraphSchema.safeParse(
|
const resultGraphValidation = situationGraphSchema.safeParse(
|
||||||
updatedSituationGraph,
|
updatedSituationGraph,
|
||||||
@@ -725,10 +812,13 @@ export function applyValidatedProposal({ situationGraph, proposal }) {
|
|||||||
graphUpdate: validatedProposal,
|
graphUpdate: validatedProposal,
|
||||||
affectedNodeIds,
|
affectedNodeIds,
|
||||||
resolvedUnknownNodeIds: validatedProposal.resolvedUnknownNodeIds,
|
resolvedUnknownNodeIds: validatedProposal.resolvedUnknownNodeIds,
|
||||||
|
resolvedReasoningNodeIds: reasoningResolution.resolvedReasoningNodeIds,
|
||||||
previousActiveUnknownNodeId,
|
previousActiveUnknownNodeId,
|
||||||
newActiveUnknownNodeId,
|
newActiveUnknownNodeId,
|
||||||
selectedQuestion: finalSelectedQuestion,
|
selectedQuestion: finalSelectedQuestion,
|
||||||
changesApplied: buildChangesApplied(validatedProposal, affectedNodeIds),
|
changesApplied: buildChangesApplied(validatedProposal, affectedNodeIds),
|
||||||
graphReferenceValidation: resultReferenceValidation,
|
graphReferenceValidation: resultReferenceValidation,
|
||||||
|
previousReasoningState: reasoningResolution.previousReasoningState,
|
||||||
|
reasoningState: nextReasoningState,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ import {
|
|||||||
import { buildInitialGraph, describeGraph } from "./builder.js";
|
import { buildInitialGraph, describeGraph } from "./builder.js";
|
||||||
import { applyValidatedProposal } from "./apply-proposal.js";
|
import { applyValidatedProposal } from "./apply-proposal.js";
|
||||||
import { buildGraphUpdatePrompt } from "./prompt-builder.js";
|
import { buildGraphUpdatePrompt } from "./prompt-builder.js";
|
||||||
import { formulateTieResolutionQuestion } from "./question-formulator.js";
|
import {
|
||||||
|
buildReasoningState,
|
||||||
|
formulateTieResolutionQuestion,
|
||||||
|
} from "./question-formulator.js";
|
||||||
import { parseGraphUpdateProposal } from "./update-proposal.js";
|
import { parseGraphUpdateProposal } from "./update-proposal.js";
|
||||||
import {
|
import {
|
||||||
explainUnknownSelection,
|
explainUnknownSelection,
|
||||||
@@ -83,6 +86,9 @@ function buildUpdateDiagnostics({
|
|||||||
graphReferenceValidation,
|
graphReferenceValidation,
|
||||||
selectedQuestion,
|
selectedQuestion,
|
||||||
unknownSelectionExplanation,
|
unknownSelectionExplanation,
|
||||||
|
previousReasoningState,
|
||||||
|
reasoningState,
|
||||||
|
resolvedReasoningNodeIds,
|
||||||
}) {
|
}) {
|
||||||
return {
|
return {
|
||||||
promptVersion: promptVersion ?? "v0.4",
|
promptVersion: promptVersion ?? "v0.4",
|
||||||
@@ -100,6 +106,14 @@ function buildUpdateDiagnostics({
|
|||||||
selectedQuestion?.investigationStrategy ??
|
selectedQuestion?.investigationStrategy ??
|
||||||
selectedQuestion?.strategy ??
|
selectedQuestion?.strategy ??
|
||||||
null,
|
null,
|
||||||
|
previousComparabilityStatus:
|
||||||
|
previousReasoningState?.comparabilityStatus ?? null,
|
||||||
|
comparabilityStatus: reasoningState?.comparabilityStatus ?? null,
|
||||||
|
relationshipStatus: reasoningState?.relationshipStatus ?? null,
|
||||||
|
relationshipAssessed: reasoningState?.relationshipAssessed ?? null,
|
||||||
|
reasoningStagesBefore: previousReasoningState?.reasoningStages ?? [],
|
||||||
|
reasoningStagesAfter: reasoningState?.reasoningStages ?? [],
|
||||||
|
resolvedReasoningNodeIds: resolvedReasoningNodeIds ?? [],
|
||||||
unknownSelectionExplanation: unknownSelectionExplanation ?? null,
|
unknownSelectionExplanation: unknownSelectionExplanation ?? null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -159,6 +173,12 @@ export async function startCase(body) {
|
|||||||
activeUnknownNodeId,
|
activeUnknownNodeId,
|
||||||
resolvedNodeIds: [],
|
resolvedNodeIds: [],
|
||||||
currentSummary,
|
currentSummary,
|
||||||
|
reasoningState: buildReasoningState({
|
||||||
|
centralStatement: scenario,
|
||||||
|
nodes: initialGraph.nodes,
|
||||||
|
edges: initialGraph.edges,
|
||||||
|
resolvedNodeIds: [],
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
situationGraphSchema.parse(situationGraph);
|
situationGraphSchema.parse(situationGraph);
|
||||||
@@ -322,6 +342,8 @@ async function updateCaseWithDependencies(body, dependencies = {}) {
|
|||||||
const applicationResult = applyProposalUpdate({
|
const applicationResult = applyProposalUpdate({
|
||||||
situationGraph,
|
situationGraph,
|
||||||
proposal: parsedProposal.proposal,
|
proposal: parsedProposal.proposal,
|
||||||
|
previousQuestion,
|
||||||
|
answer,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!applicationResult.success) {
|
if (!applicationResult.success) {
|
||||||
@@ -338,6 +360,9 @@ async function updateCaseWithDependencies(body, dependencies = {}) {
|
|||||||
graph: situationGraph,
|
graph: situationGraph,
|
||||||
graphReferenceValidation: graphReferenceValidation,
|
graphReferenceValidation: graphReferenceValidation,
|
||||||
selectedQuestion: null,
|
selectedQuestion: null,
|
||||||
|
previousReasoningState: buildReasoningState(situationGraph),
|
||||||
|
reasoningState: buildReasoningState(situationGraph),
|
||||||
|
resolvedReasoningNodeIds: [],
|
||||||
unknownSelectionExplanation: explainUnknownSelection(
|
unknownSelectionExplanation: explainUnknownSelection(
|
||||||
situationGraph,
|
situationGraph,
|
||||||
situationGraph.resolvedNodeIds || [],
|
situationGraph.resolvedNodeIds || [],
|
||||||
@@ -372,6 +397,9 @@ async function updateCaseWithDependencies(body, dependencies = {}) {
|
|||||||
graph: applicationResult.updatedSituationGraph,
|
graph: applicationResult.updatedSituationGraph,
|
||||||
graphReferenceValidation: applicationResult.graphReferenceValidation,
|
graphReferenceValidation: applicationResult.graphReferenceValidation,
|
||||||
selectedQuestion: applicationResult.selectedQuestion,
|
selectedQuestion: applicationResult.selectedQuestion,
|
||||||
|
previousReasoningState: applicationResult.previousReasoningState,
|
||||||
|
reasoningState: applicationResult.reasoningState,
|
||||||
|
resolvedReasoningNodeIds: applicationResult.resolvedReasoningNodeIds,
|
||||||
unknownSelectionExplanation: buildUnknownSelectionDiagnostics(
|
unknownSelectionExplanation: buildUnknownSelectionDiagnostics(
|
||||||
applicationResult.updatedSituationGraph,
|
applicationResult.updatedSituationGraph,
|
||||||
applicationResult.updatedSituationGraph.resolvedNodeIds || [],
|
applicationResult.updatedSituationGraph.resolvedNodeIds || [],
|
||||||
@@ -393,6 +421,9 @@ async function updateCaseWithDependencies(body, dependencies = {}) {
|
|||||||
graph: situationGraph,
|
graph: situationGraph,
|
||||||
graphReferenceValidation,
|
graphReferenceValidation,
|
||||||
selectedQuestion: null,
|
selectedQuestion: null,
|
||||||
|
previousReasoningState: buildReasoningState(situationGraph),
|
||||||
|
reasoningState: buildReasoningState(situationGraph),
|
||||||
|
resolvedReasoningNodeIds: [],
|
||||||
unknownSelectionExplanation: buildUnknownSelectionDiagnostics(
|
unknownSelectionExplanation: buildUnknownSelectionDiagnostics(
|
||||||
situationGraph,
|
situationGraph,
|
||||||
situationGraph.resolvedNodeIds || [],
|
situationGraph.resolvedNodeIds || [],
|
||||||
|
|||||||
@@ -152,7 +152,28 @@ function analyseObservationText(text) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const COMPARABILITY_REASONING_NODE_ID = "reasoning:comparability";
|
||||||
|
|
||||||
|
function readStoredComparabilityState(graph) {
|
||||||
|
const reasoningState = graph?.reasoningState;
|
||||||
|
if (!reasoningState?.comparabilityStatus) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
comparabilityStatus: reasoningState.comparabilityStatus,
|
||||||
|
reason:
|
||||||
|
reasoningState.comparabilityReason ||
|
||||||
|
"Comparability state was carried forward from earlier reasoning.",
|
||||||
|
contradictionReasoningAllowed:
|
||||||
|
reasoningState.comparabilityStatus === "confirmed",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function assessComparability(graph) {
|
export function assessComparability(graph) {
|
||||||
|
const storedState = readStoredComparabilityState(graph);
|
||||||
|
if (storedState) {
|
||||||
|
return storedState;
|
||||||
|
}
|
||||||
|
|
||||||
const observations = collectObservationNodes(graph);
|
const observations = collectObservationNodes(graph);
|
||||||
const centralText = normaliseText(graph?.centralStatement || "");
|
const centralText = normaliseText(graph?.centralStatement || "");
|
||||||
const profiles = observations.map((node) =>
|
const profiles = observations.map((node) =>
|
||||||
@@ -308,6 +329,9 @@ function classifyObservationRelationshipWhenComparable(graph) {
|
|||||||
const directions = profiles.map((profile) =>
|
const directions = profiles.map((profile) =>
|
||||||
extractObservationDirection(profile),
|
extractObservationDirection(profile),
|
||||||
);
|
);
|
||||||
|
const conceptUnion = new Set(conceptSets.flat());
|
||||||
|
const hasRevenueCashPair =
|
||||||
|
conceptUnion.has("revenue") && conceptUnion.has("cash");
|
||||||
|
|
||||||
if (
|
if (
|
||||||
sharedConcepts.size > 0 &&
|
sharedConcepts.size > 0 &&
|
||||||
@@ -336,6 +360,19 @@ function classifyObservationRelationshipWhenComparable(graph) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
hasRevenueCashPair &&
|
||||||
|
directions.every((direction) => direction !== "unknown")
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
relationshipStatus: "potentially_related",
|
||||||
|
reason:
|
||||||
|
"The observations concern connected business signals but do not establish a direct contradiction or cause.",
|
||||||
|
contradictionReasoningAllowed: false,
|
||||||
|
questionRequired: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
sharedConcepts.size === 0 &&
|
sharedConcepts.size === 0 &&
|
||||||
directions.every((direction) => direction !== "unknown")
|
directions.every((direction) => direction !== "unknown")
|
||||||
@@ -396,6 +433,30 @@ export function classifyObservationRelationship(graph) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildReasoningState(graph, overrides = {}) {
|
||||||
|
const relationship = classifyObservationRelationship({
|
||||||
|
...graph,
|
||||||
|
reasoningState: {
|
||||||
|
...(graph?.reasoningState || {}),
|
||||||
|
...(overrides || {}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
comparabilityStatus: relationship.reasoningStages[0]?.status ?? null,
|
||||||
|
comparabilityReason: relationship.reasoningStages[0]?.outcome ?? null,
|
||||||
|
comparabilityEvidence:
|
||||||
|
overrides.comparabilityEvidence ??
|
||||||
|
graph?.reasoningState?.comparabilityEvidence ??
|
||||||
|
[],
|
||||||
|
relationshipStatus: relationship.relationshipStatus,
|
||||||
|
relationshipReason: relationship.reason,
|
||||||
|
relationshipAssessed: relationship.relationshipAssessed,
|
||||||
|
contradictionReasoningAllowed: relationship.contradictionReasoningAllowed,
|
||||||
|
reasoningStages: relationship.reasoningStages,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function buildComparabilityQuestion(graph, assessment) {
|
function buildComparabilityQuestion(graph, assessment) {
|
||||||
const centralText = normaliseText(graph?.centralStatement || "");
|
const centralText = normaliseText(graph?.centralStatement || "");
|
||||||
const mentionsPeriod =
|
const mentionsPeriod =
|
||||||
@@ -430,6 +491,13 @@ function detectContradictionContext(graph) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildBroadInvestigationQuestion(graph) {
|
||||||
|
const central = sanitizeQuestionText(
|
||||||
|
stripTrailingPunctuation(graph?.centralStatement || "these observations"),
|
||||||
|
);
|
||||||
|
return `What changed during that period that could help explain why ${central}?`;
|
||||||
|
}
|
||||||
|
|
||||||
export function formulateTieResolutionQuestion({ graph }) {
|
export function formulateTieResolutionQuestion({ graph }) {
|
||||||
const comparability = assessComparability(graph);
|
const comparability = assessComparability(graph);
|
||||||
if (comparability.comparabilityStatus === "uncertain") {
|
if (comparability.comparabilityStatus === "uncertain") {
|
||||||
@@ -475,8 +543,7 @@ export function formulateTieResolutionQuestion({ graph }) {
|
|||||||
|
|
||||||
if (relationship.relationshipStatus === "potentially_related") {
|
if (relationship.relationshipStatus === "potentially_related") {
|
||||||
return {
|
return {
|
||||||
question:
|
question: buildBroadInvestigationQuestion(graph),
|
||||||
"What connection, if any, should we check between these observations?",
|
|
||||||
reason:
|
reason:
|
||||||
"Formulated as a neutral relationship question because the observations may be related without being contradictory.",
|
"Formulated as a neutral relationship question because the observations may be related without being contradictory.",
|
||||||
strategy: null,
|
strategy: null,
|
||||||
|
|||||||
@@ -84,6 +84,25 @@ export const situationEdgeSchema = z.object({
|
|||||||
|
|
||||||
// ── SituationGraph ───────────────────────────────────
|
// ── SituationGraph ───────────────────────────────────
|
||||||
|
|
||||||
|
const reasoningStageSchema = z.object({
|
||||||
|
stage: z.string().min(1),
|
||||||
|
status: z.string().min(1),
|
||||||
|
outcome: z.string().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const reasoningStateSchema = z
|
||||||
|
.object({
|
||||||
|
comparabilityStatus: z.string().min(1).nullable().optional(),
|
||||||
|
comparabilityReason: z.string().min(1).nullable().optional(),
|
||||||
|
comparabilityEvidence: z.array(z.string()).default([]),
|
||||||
|
relationshipStatus: z.string().min(1).nullable().optional(),
|
||||||
|
relationshipReason: z.string().min(1).nullable().optional(),
|
||||||
|
relationshipAssessed: z.boolean().optional(),
|
||||||
|
contradictionReasoningAllowed: z.boolean().optional(),
|
||||||
|
reasoningStages: z.array(reasoningStageSchema).default([]),
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export const situationGraphSchema = z.object({
|
export const situationGraphSchema = z.object({
|
||||||
centralStatement: z.string().min(1),
|
centralStatement: z.string().min(1),
|
||||||
nodes: z.array(situationNodeSchema).min(1),
|
nodes: z.array(situationNodeSchema).min(1),
|
||||||
@@ -91,6 +110,7 @@ export const situationGraphSchema = z.object({
|
|||||||
activeUnknownNodeId: z.string().nullable(),
|
activeUnknownNodeId: z.string().nullable(),
|
||||||
resolvedNodeIds: z.array(z.string()).default([]),
|
resolvedNodeIds: z.array(z.string()).default([]),
|
||||||
currentSummary: z.string().min(1),
|
currentSummary: z.string().min(1),
|
||||||
|
reasoningState: reasoningStateSchema.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
/** @typedef {z.infer<typeof situationGraphSchema>} SituationGraph */
|
/** @typedef {z.infer<typeof situationGraphSchema>} SituationGraph */
|
||||||
@@ -201,5 +221,6 @@ export function makeGraph(opts) {
|
|||||||
activeUnknownNodeId: opts.activeUnknownNodeId ?? null,
|
activeUnknownNodeId: opts.activeUnknownNodeId ?? null,
|
||||||
resolvedNodeIds: opts.resolvedNodeIds ?? [],
|
resolvedNodeIds: opts.resolvedNodeIds ?? [],
|
||||||
currentSummary: opts.currentSummary || "",
|
currentSummary: opts.currentSummary || "",
|
||||||
|
reasoningState: opts.reasoningState,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,120 @@ import { applyValidatedProposal } from "@/lib/graph/apply-proposal.js";
|
|||||||
import { makeEdge, makeGraph, makeNode } from "@/lib/graph/schema.js";
|
import { makeEdge, makeGraph, makeNode } from "@/lib/graph/schema.js";
|
||||||
import { validateGraphReferences } from "@/lib/graph/utils.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() {
|
function makeApplicationFixture() {
|
||||||
const complaintRateUnknown = makeNode({
|
const complaintRateUnknown = makeNode({
|
||||||
id: "n-complaint-rate-unknown",
|
id: "n-complaint-rate-unknown",
|
||||||
@@ -997,4 +1111,64 @@ describe("applyValidatedProposal", () => {
|
|||||||
"price",
|
"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", () => {
|
describe("lib/graph/orchestrator startCase", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.resetModules();
|
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 () => {
|
it("startCase behaviour remains unchanged", async () => {
|
||||||
mockAnalyseScenario.mockResolvedValue(makeAnalysisResult());
|
mockAnalyseScenario.mockResolvedValue(makeAnalysisResult());
|
||||||
const { startCase } = await import("@/lib/graph/orchestrator.js");
|
const { startCase } = await import("@/lib/graph/orchestrator.js");
|
||||||
|
|||||||
@@ -166,6 +166,40 @@ function makeUpdateSuccess(overrides = {}) {
|
|||||||
resolvedUnknownNodeIds: ["n-unknown"],
|
resolvedUnknownNodeIds: ["n-unknown"],
|
||||||
previousActiveUnknownNodeId: "n-unknown",
|
previousActiveUnknownNodeId: "n-unknown",
|
||||||
newActiveUnknownNodeId: "n-next-unknown",
|
newActiveUnknownNodeId: "n-next-unknown",
|
||||||
|
previousReasoningState: {
|
||||||
|
comparabilityStatus: "uncertain",
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
reasoningState: {
|
||||||
|
comparabilityStatus: "confirmed",
|
||||||
|
relationshipStatus: "insufficient_information",
|
||||||
|
reasoningStages: [
|
||||||
|
{
|
||||||
|
stage: "comparability",
|
||||||
|
status: "confirmed",
|
||||||
|
outcome:
|
||||||
|
"Comparability was confirmed by the user answer covering the same period and source basis.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stage: "relationship",
|
||||||
|
status: "insufficient_information",
|
||||||
|
outcome:
|
||||||
|
"There is not enough structure to classify the relationship safely.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
changesApplied: {
|
changesApplied: {
|
||||||
updatedNodeCount: 2,
|
updatedNodeCount: 2,
|
||||||
resolvedUnknownCount: 1,
|
resolvedUnknownCount: 1,
|
||||||
@@ -458,6 +492,36 @@ describe("graph-backed UI rendering", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("update view shows comparability progression without raw ids in the normal view", () => {
|
||||||
|
const html = renderToStaticMarkup(
|
||||||
|
<GraphUpdateView
|
||||||
|
updateResult={{
|
||||||
|
...makeUpdateSuccess({
|
||||||
|
selectedQuestion: {
|
||||||
|
nodeId: "n-next-unknown",
|
||||||
|
question:
|
||||||
|
"What changed during that period that could help explain why revenue increased by 18%, but cash in the bank fell over the same period?",
|
||||||
|
reason: "A broad follow-up is now justified.",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
previousSituationGraph: makeGraphResult().situationGraph,
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(html).toContain("Comparability:");
|
||||||
|
expect(html).toContain("uncertain → confirmed");
|
||||||
|
expect(html).toContain("Relationship status:");
|
||||||
|
expect(html).toContain("insufficient_information");
|
||||||
|
expect(html).toContain("Reasoning stages:");
|
||||||
|
expect(html).toContain("comparability: confirmed");
|
||||||
|
expect(html).toContain("relationship: insufficient_information");
|
||||||
|
expect(html).toContain(
|
||||||
|
"What changed during that period that could help explain why revenue increased by 18%, but cash in the bank fell over the same period?",
|
||||||
|
);
|
||||||
|
expect(html).not.toContain("reasoning:comparability");
|
||||||
|
});
|
||||||
|
|
||||||
it("situation graph marks newly surfaced and active unknowns", () => {
|
it("situation graph marks newly surfaced and active unknowns", () => {
|
||||||
const html = renderToStaticMarkup(
|
const html = renderToStaticMarkup(
|
||||||
<SituationGraphView
|
<SituationGraphView
|
||||||
|
|||||||
Reference in New Issue
Block a user