feat: introduce comparability assessment before contradiction reasoning

This commit is contained in:
2026-08-02 16:28:11 +01:00
parent b84989b96a
commit 0c7558d31f
5 changed files with 399 additions and 1 deletions
+118
View File
@@ -123,6 +123,105 @@ function buildEvidenceFallbackQuestion(meaning) {
return `What evidence would confirm or rule out ${stripTrailingPunctuation(meaning)}?`;
}
function collectObservationNodes(graph) {
return (graph?.nodes || []).filter(
(node) => node.kind === "observation" && node.status === "supported",
);
}
function analyseObservationText(text) {
const normalised = normaliseText(text);
return {
text,
normalised,
isMeasurementLike:
/\b(increase|increased|decrease|decreased|fell|rose|doubled|halved|remained|average|score|scores|rate|time|traffic|sales|output|defects|complaints|production|revenue|cash|temperature|quality)\b/.test(
normalised,
) || /%|percent/.test(String(text || "")),
timeframeMentioned:
/\b(period|timeframe|quarter|month|week|year|day|annual|daily|weekly|monthly|same period)\b/.test(
normalised,
),
scaleMentioned: /\b(average|rate|score|scores|per|percent|%)\b/.test(
normalised,
),
unitMentioned:
/\b(celsius|fahrenheit|minutes|minute|hours|hour|days|day|units|sales|traffic|cash|revenue|complaints|defects)\b/.test(
normalised,
),
};
}
export function assessComparability(graph) {
const observations = collectObservationNodes(graph);
const centralText = normaliseText(graph?.centralStatement || "");
const profiles = observations.map((node) =>
analyseObservationText(`${node.label} ${node.description}`),
);
if (profiles.length < 2) {
return {
comparabilityStatus: "confirmed",
reason: "Fewer than two supported observations need comparison.",
contradictionReasoningAllowed: true,
};
}
if (
profiles.every((profile) => profile.normalised === profiles[0].normalised)
) {
return {
comparabilityStatus: "confirmed",
reason: "The observations restate the same measurement.",
contradictionReasoningAllowed: false,
};
}
if (profiles.some((profile) => !profile.isMeasurementLike)) {
return {
comparabilityStatus: "confirmed",
reason: "The observations are not competing like-for-like measurements.",
contradictionReasoningAllowed: true,
};
}
const hasExplicitTimeframe =
/\b(period|timeframe|quarter|month|week|year|day|same period)\b/.test(
centralText,
) || profiles.every((profile) => profile.timeframeMentioned);
const hasSharedScale = profiles.every((profile) => profile.scaleMentioned);
const hasSharedUnits = profiles.every((profile) => profile.unitMentioned);
if (!hasExplicitTimeframe || !hasSharedScale || !hasSharedUnits) {
return {
comparabilityStatus: "uncertain",
reason:
"Comparability between the observations is not yet established across period, scale, or measurement basis.",
contradictionReasoningAllowed: false,
};
}
return {
comparabilityStatus: "uncertain",
reason:
"The observations appear comparable in form, but the basis for comparing them is still not established.",
contradictionReasoningAllowed: false,
};
}
function buildComparabilityQuestion(graph, assessment) {
const centralText = normaliseText(graph?.centralStatement || "");
const mentionsPeriod =
/\b(period|timeframe|quarter|month|week|year|day)\b/.test(centralText);
if (mentionsPeriod) {
return "Were these figures measured on the same basis and at the same scale?";
}
return "Were these figures measured over the same period and at the same scale?";
}
function detectContradictionContext(graph) {
const central = stripTrailingPunctuation(
graph?.centralStatement || "this situation",
@@ -146,6 +245,22 @@ function detectContradictionContext(graph) {
}
export function formulateTieResolutionQuestion({ graph }) {
const comparability = assessComparability(graph);
if (comparability.comparabilityStatus === "uncertain") {
return {
question: buildComparabilityQuestion(graph, comparability),
reason:
"Formulated to confirm whether the observations are comparable before exploring competing explanations.",
strategy: null,
investigationStrategy: null,
selectionStatus: "ambiguous",
comparabilityStatus: comparability.comparabilityStatus,
comparabilityReason: comparability.reason,
contradictionReasoningAllowed:
comparability.contradictionReasoningAllowed,
};
}
const { centralStatement, contradictionLabel } =
detectContradictionContext(graph);
const focus =
@@ -161,6 +276,9 @@ export function formulateTieResolutionQuestion({ graph }) {
strategy: null,
investigationStrategy: null,
selectionStatus: "ambiguous",
comparabilityStatus: comparability.comparabilityStatus,
comparabilityReason: comparability.reason,
contradictionReasoningAllowed: comparability.contradictionReasoningAllowed,
};
}