From 7d408701b578b74336dfd6f30a82d9f06544e0b3 Mon Sep 17 00:00:00 2001 From: robbond Date: Sun, 2 Aug 2026 16:48:10 +0100 Subject: [PATCH] fix: defer relationship classification until comparability is established --- lib/graph/question-formulator.js | 69 ++++++++++++++++++-- tests/graph/comparability-assessment.test.js | 40 ++++++++++-- tests/graph/orchestrator.test.js | 2 + 3 files changed, 103 insertions(+), 8 deletions(-) diff --git a/lib/graph/question-formulator.js b/lib/graph/question-formulator.js index 3e60238..1e3eaf8 100644 --- a/lib/graph/question-formulator.js +++ b/lib/graph/question-formulator.js @@ -210,6 +210,23 @@ export function assessComparability(graph) { }; } +function buildReasoningStages(comparability, relationship, deferred = false) { + return [ + { + stage: "comparability", + status: comparability.comparabilityStatus, + outcome: comparability.reason, + }, + { + stage: "relationship", + status: relationship.relationshipStatus, + outcome: deferred + ? "not assessed until comparability is established" + : relationship.reason, + }, + ]; +} + function extractObservationConcepts(profile) { const concepts = new Set(); const text = profile.normalised; @@ -250,7 +267,7 @@ function extractObservationDirection(profile) { return "unknown"; } -export function classifyObservationRelationship(graph) { +function classifyObservationRelationshipWhenComparable(graph) { const observations = collectObservationNodes(graph); const profiles = observations.map((node) => analyseObservationText(`${node.label} ${node.description}`), @@ -343,6 +360,42 @@ export function classifyObservationRelationship(graph) { }; } +export function classifyObservationRelationship(graph) { + const comparability = assessComparability(graph); + + if (comparability.comparabilityStatus !== "confirmed") { + const deferredRelationship = { + relationshipStatus: "insufficient_information", + reason: + "Relationship classification is deferred until comparability is established.", + contradictionReasoningAllowed: false, + questionRequired: comparability.comparabilityStatus === "uncertain", + questionSuppressedReason: + comparability.comparabilityStatus === "incompatible" + ? "Relationship classification was not attempted because the observations are not yet comparable." + : undefined, + relationshipAssessed: false, + }; + + return { + ...deferredRelationship, + reasoningStages: buildReasoningStages( + comparability, + deferredRelationship, + true, + ), + }; + } + + const classified = classifyObservationRelationshipWhenComparable(graph); + + return { + ...classified, + relationshipAssessed: true, + reasoningStages: buildReasoningStages(comparability, classified, false), + }; +} + function buildComparabilityQuestion(graph, assessment) { const centralText = normaliseText(graph?.centralStatement || ""); const mentionsPeriod = @@ -380,6 +433,7 @@ function detectContradictionContext(graph) { export function formulateTieResolutionQuestion({ graph }) { const comparability = assessComparability(graph); if (comparability.comparabilityStatus === "uncertain") { + const deferredRelationship = classifyObservationRelationship(graph); return { question: buildComparabilityQuestion(graph, comparability), reason: @@ -391,10 +445,11 @@ export function formulateTieResolutionQuestion({ graph }) { comparabilityReason: comparability.reason, contradictionReasoningAllowed: comparability.contradictionReasoningAllowed, - relationshipStatus: "insufficient_information", - relationshipReason: - "Relationship classification is deferred until comparability is established.", + relationshipStatus: deferredRelationship.relationshipStatus, + relationshipReason: deferredRelationship.reason, + relationshipAssessed: deferredRelationship.relationshipAssessed, questionRequired: true, + reasoningStages: deferredRelationship.reasoningStages, }; } @@ -410,9 +465,11 @@ export function formulateTieResolutionQuestion({ graph }) { comparabilityReason: comparability.reason, relationshipStatus: relationship.relationshipStatus, relationshipReason: relationship.reason, + relationshipAssessed: relationship.relationshipAssessed, contradictionReasoningAllowed: relationship.contradictionReasoningAllowed, questionRequired: relationship.questionRequired, questionSuppressedReason: relationship.questionSuppressedReason, + reasoningStages: relationship.reasoningStages, }; } @@ -429,8 +486,10 @@ export function formulateTieResolutionQuestion({ graph }) { comparabilityReason: comparability.reason, relationshipStatus: relationship.relationshipStatus, relationshipReason: relationship.reason, + relationshipAssessed: relationship.relationshipAssessed, contradictionReasoningAllowed: relationship.contradictionReasoningAllowed, questionRequired: relationship.questionRequired, + reasoningStages: relationship.reasoningStages, }; } @@ -453,8 +512,10 @@ export function formulateTieResolutionQuestion({ graph }) { comparabilityReason: comparability.reason, relationshipStatus: relationship.relationshipStatus, relationshipReason: relationship.reason, + relationshipAssessed: relationship.relationshipAssessed, contradictionReasoningAllowed: relationship.contradictionReasoningAllowed, questionRequired: relationship.questionRequired, + reasoningStages: relationship.reasoningStages, }; } diff --git a/tests/graph/comparability-assessment.test.js b/tests/graph/comparability-assessment.test.js index 005d5d7..48b5510 100644 --- a/tests/graph/comparability-assessment.test.js +++ b/tests/graph/comparability-assessment.test.js @@ -39,6 +39,7 @@ describe("comparability assessment", () => { scenario: fixture.scenario, comparabilityStatus: assessment.comparabilityStatus, relationshipStatus: relationship.relationshipStatus, + relationshipAssessed: relationship.relationshipAssessed, contradictionReasoningAllowed: question.contradictionReasoningAllowed, question: question.question, }; @@ -49,7 +50,8 @@ describe("comparability assessment", () => { scenario: "Revenue increased by 18%, but cash in the bank fell over the same period.", comparabilityStatus: "uncertain", - relationshipStatus: "compatible", + relationshipStatus: "insufficient_information", + relationshipAssessed: false, contradictionReasoningAllowed: false, question: "Were these figures measured on the same basis and at the same scale?", @@ -57,7 +59,8 @@ describe("comparability assessment", () => { { scenario: "Complaints increased. Production increased.", comparabilityStatus: "uncertain", - relationshipStatus: "compatible", + relationshipStatus: "insufficient_information", + relationshipAssessed: false, contradictionReasoningAllowed: false, question: "Were these figures measured over the same period and at the same scale?", @@ -66,7 +69,8 @@ describe("comparability assessment", () => { scenario: "Average delivery time decreased by 25%, but order cancellations increased.", comparabilityStatus: "uncertain", - relationshipStatus: "compatible", + relationshipStatus: "insufficient_information", + relationshipAssessed: false, contradictionReasoningAllowed: false, question: "Were these figures measured over the same period and at the same scale?", @@ -74,7 +78,8 @@ describe("comparability assessment", () => { { scenario: "Customer satisfaction increased, but complaints increased.", comparabilityStatus: "uncertain", - relationshipStatus: "compatible", + relationshipStatus: "insufficient_information", + relationshipAssessed: false, contradictionReasoningAllowed: false, question: "Were these figures measured over the same period and at the same scale?", @@ -83,6 +88,7 @@ describe("comparability assessment", () => { scenario: "Temperature increased. Ice melted.", comparabilityStatus: "confirmed", relationshipStatus: "compatible", + relationshipAssessed: true, contradictionReasoningAllowed: false, question: null, }, @@ -90,12 +96,38 @@ describe("comparability assessment", () => { 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("allows contradiction reasoning only for genuine contradictions", () => { const serviceGraph = { centralStatement: diff --git a/tests/graph/orchestrator.test.js b/tests/graph/orchestrator.test.js index 3693a34..ffffd04 100644 --- a/tests/graph/orchestrator.test.js +++ b/tests/graph/orchestrator.test.js @@ -256,6 +256,8 @@ describe("lib/graph/orchestrator startCase", () => { "Were these figures measured on the same basis and at the same scale?", tiedCandidateIds: expect.arrayContaining([expect.any(String)]), comparabilityStatus: "uncertain", + relationshipStatus: "insufficient_information", + relationshipAssessed: false, contradictionReasoningAllowed: false, }); expect(result.diagnostics.unknownSelectionExplanation).toMatchObject({