feat: introduce reasoning pattern selection

This commit is contained in:
2026-08-03 10:25:26 +01:00
parent 42d4da3496
commit b00928d6fb
7 changed files with 986 additions and 19 deletions
+18
View File
@@ -1766,6 +1766,15 @@ export function determineGraphBackedQuestion({ situationGraph }) {
reason: formulatedQuestion.reason,
strategy: formulatedQuestion.strategy,
investigationStrategy: formulatedQuestion.investigationStrategy,
reasoningPattern: formulatedQuestion.reasoningPattern,
reasoningPatternReason: formulatedQuestion.reasoningPatternReason,
questionFamily: formulatedQuestion.questionFamily,
allowedQuestionFamilies:
formulatedQuestion.allowedQuestionFamilies,
rejectedQuestionFamilies:
formulatedQuestion.rejectedQuestionFamilies,
selectedQuestionTemplate:
formulatedQuestion.selectedQuestionTemplate,
questionComplexity: formulatedQuestion.questionComplexity,
plainLanguageNormalisations:
formulatedQuestion.plainLanguageNormalisations,
@@ -2469,6 +2478,15 @@ export function applyValidatedProposal({
reason: formulatedQuestion?.reason || deterministicSelection.reason,
strategy: formulatedQuestion?.strategy,
investigationStrategy: formulatedQuestion?.investigationStrategy,
reasoningPattern: formulatedQuestion?.reasoningPattern,
reasoningPatternReason: formulatedQuestion?.reasoningPatternReason,
questionFamily: formulatedQuestion?.questionFamily,
allowedQuestionFamilies:
formulatedQuestion?.allowedQuestionFamilies,
rejectedQuestionFamilies:
formulatedQuestion?.rejectedQuestionFamilies,
selectedQuestionTemplate:
formulatedQuestion?.selectedQuestionTemplate,
questionComplexity,
plainLanguageNormalisations,
}
+70
View File
@@ -59,6 +59,12 @@ function buildDiagnostics({
decompositionReason,
selectedContainerUnknown,
selectedChildUnknown,
reasoningPattern,
questionFamily,
allowedQuestionFamilies,
rejectedQuestionFamilies,
selectedQuestionTemplate,
reasoningPatternReason,
}) {
return {
promptVersion: analysis?.promptVersion ?? null,
@@ -88,6 +94,12 @@ function buildDiagnostics({
decompositionReason: decompositionReason ?? null,
selectedContainerUnknown: selectedContainerUnknown ?? null,
selectedChildUnknown: selectedChildUnknown ?? null,
reasoningPattern: reasoningPattern ?? null,
questionFamily: questionFamily ?? null,
allowedQuestionFamilies: allowedQuestionFamilies ?? [],
rejectedQuestionFamilies: rejectedQuestionFamilies ?? [],
selectedQuestionTemplate: selectedQuestionTemplate ?? null,
reasoningPatternReason: reasoningPatternReason ?? null,
};
}
@@ -177,6 +189,12 @@ function buildUpdateDiagnostics({
selectedUnknownBefore,
selectedUnknownAfter,
plainLanguageNormalisations,
reasoningPattern,
questionFamily,
allowedQuestionFamilies,
rejectedQuestionFamilies,
selectedQuestionTemplate,
reasoningPatternReason,
}) {
return {
promptVersion: promptVersion ?? "v0.4",
@@ -257,6 +275,12 @@ function buildUpdateDiagnostics({
selectedUnknownBefore: selectedUnknownBefore ?? null,
selectedUnknownAfter: selectedUnknownAfter ?? null,
plainLanguageNormalisations: plainLanguageNormalisations ?? [],
reasoningPattern: reasoningPattern ?? null,
questionFamily: questionFamily ?? null,
allowedQuestionFamilies: allowedQuestionFamilies ?? [],
rejectedQuestionFamilies: rejectedQuestionFamilies ?? [],
selectedQuestionTemplate: selectedQuestionTemplate ?? null,
reasoningPatternReason: reasoningPatternReason ?? null,
unknownSelectionExplanation: unknownSelectionExplanation ?? null,
};
}
@@ -370,6 +394,21 @@ export async function startCase(body) {
initialQuestionResult.selectedContainerUnknown ?? null,
selectedChildUnknown:
initialQuestionResult.selectedChildUnknown ?? null,
reasoningPattern:
initialQuestionResult.selectedQuestion?.reasoningPattern ?? null,
questionFamily:
initialQuestionResult.selectedQuestion?.questionFamily ?? null,
allowedQuestionFamilies:
initialQuestionResult.selectedQuestion?.allowedQuestionFamilies ?? [],
rejectedQuestionFamilies:
initialQuestionResult.selectedQuestion?.rejectedQuestionFamilies ??
[],
selectedQuestionTemplate:
initialQuestionResult.selectedQuestion?.selectedQuestionTemplate ??
null,
reasoningPatternReason:
initialQuestionResult.selectedQuestion?.reasoningPatternReason ??
null,
}),
validationErrors: graphReferenceValidation.errors,
statusCode: 500,
@@ -413,6 +452,19 @@ export async function startCase(body) {
selectedContainerUnknown:
initialQuestionResult.selectedContainerUnknown ?? null,
selectedChildUnknown: initialQuestionResult.selectedChildUnknown ?? null,
reasoningPattern:
initialQuestionResult.selectedQuestion?.reasoningPattern ?? null,
questionFamily:
initialQuestionResult.selectedQuestion?.questionFamily ?? null,
allowedQuestionFamilies:
initialQuestionResult.selectedQuestion?.allowedQuestionFamilies ?? [],
rejectedQuestionFamilies:
initialQuestionResult.selectedQuestion?.rejectedQuestionFamilies ?? [],
selectedQuestionTemplate:
initialQuestionResult.selectedQuestion?.selectedQuestionTemplate ??
null,
reasoningPatternReason:
initialQuestionResult.selectedQuestion?.reasoningPatternReason ?? null,
}),
};
}
@@ -709,6 +761,18 @@ async function updateCaseWithDependencies(body, dependencies = {}) {
selectedUnknownAfter: applicationResult.selectedUnknownAfter,
plainLanguageNormalisations:
applicationResult.plainLanguageNormalisations,
reasoningPattern:
applicationResult.selectedQuestion?.reasoningPattern ?? null,
questionFamily:
applicationResult.selectedQuestion?.questionFamily ?? null,
allowedQuestionFamilies:
applicationResult.selectedQuestion?.allowedQuestionFamilies ?? [],
rejectedQuestionFamilies:
applicationResult.selectedQuestion?.rejectedQuestionFamilies ?? [],
selectedQuestionTemplate:
applicationResult.selectedQuestion?.selectedQuestionTemplate ?? null,
reasoningPatternReason:
applicationResult.selectedQuestion?.reasoningPatternReason ?? null,
unknownSelectionExplanation: buildUnknownSelectionDiagnostics(
applicationResult.updatedSituationGraph,
applicationResult.updatedSituationGraph.resolvedNodeIds || [],
@@ -787,6 +851,12 @@ async function updateCaseWithDependencies(body, dependencies = {}) {
selectedUnknownBefore: null,
selectedUnknownAfter: null,
plainLanguageNormalisations: [],
reasoningPattern: null,
questionFamily: null,
allowedQuestionFamilies: [],
rejectedQuestionFamilies: [],
selectedQuestionTemplate: null,
reasoningPatternReason: null,
unknownSelectionExplanation: buildUnknownSelectionDiagnostics(
situationGraph,
situationGraph.resolvedNodeIds || [],
+538 -19
View File
@@ -527,6 +527,10 @@ function buildFoundationalDirectQuestion(node) {
function isRelationshipExplanationUnknown(node, graph) {
const text = normaliseText(`${node?.label || ""} ${node?.description || ""}`);
if (isDirectlyAnswerableObservationChildText(text)) {
return false;
}
return (
collectObservationNodes(graph).length >= 2 &&
/\b(explain|explanation|divergence|moved differently|difference between|change or event|what changed|why the observations)/.test(
@@ -729,6 +733,433 @@ export function assessUnknownAtomicity({ node, graph }) {
};
}
const ALL_REASONING_PATTERNS = [
"decision",
"explanation",
"contradiction",
"definition",
"diagnosis",
"comparison",
"prioritisation",
];
const QUESTION_FAMILIES_BY_PATTERN = {
decision: [
"decision_foundation",
"decision_evidence",
"decision_threshold",
"definition",
],
explanation: ["explanation", "comparison"],
contradiction: ["contradiction", "comparison", "explanation"],
definition: ["definition"],
diagnosis: ["diagnosis", "comparison"],
comparison: ["comparison"],
prioritisation: ["prioritisation", "decision_threshold"],
};
const STRATEGIES_BY_PATTERN = {
decision: [
"decision_threshold",
"evidence_gathering",
"definition",
"baseline_reconstruction",
],
explanation: ["evidence_gathering", "baseline_reconstruction"],
contradiction: [
"contradiction_resolution",
"baseline_reconstruction",
"evidence_gathering",
],
definition: ["definition"],
diagnosis: ["evidence_gathering", "baseline_reconstruction"],
comparison: ["baseline_reconstruction", "evidence_gathering"],
prioritisation: ["decision_threshold", "evidence_gathering"],
};
function buildParentChain(node, graph) {
const nodesById = buildNodeMap(graph);
const chain = [];
let current = node?.parentId ? nodesById.get(node.parentId) : null;
while (current) {
chain.push(current);
current = current.parentId ? nodesById.get(current.parentId) : null;
}
return chain;
}
function hasDecisionContext(node, graph, relatedNodes = []) {
const ancestry = buildParentChain(node, graph);
const contextText = normaliseText(
[
graph?.centralStatement,
node?.label,
node?.description,
...relatedNodes.map((relatedNode) => relatedNode.label),
...relatedNodes.map((relatedNode) => relatedNode.description),
...ancestry.map((ancestor) => ancestor.label),
...ancestry.map((ancestor) => ancestor.description),
...collectResolvedContextValues(graph),
]
.filter(Boolean)
.join(" "),
);
return /\b(whether to|build|launch|continue|proceed|invest|commercially justified|commercial justification|commercial value|business case|viability)\b/.test(
contextText,
);
}
function hasObservationRelationshipTopology(node, graph, relatedNodes = []) {
const ancestry = buildParentChain(node, graph);
const topologyNodes = [node, ...relatedNodes, ...ancestry].filter(Boolean);
return (
collectObservationNodes(graph).length >= 2 &&
topologyNodes.some(
(candidate) =>
candidate.kind === "observation" || candidate.kind === "relationship",
)
);
}
function isDefinitionPatternCandidate(node, graph) {
const text = normaliseText(`${node?.label || ""} ${node?.description || ""}`);
return isDefinitionLikeUnknown(
text,
`${text} ${graph?.centralStatement || ""}`,
);
}
function isContradictionPatternCandidate(node, graph, relatedNodes = []) {
const relationshipStatus = graph?.reasoningState?.relationshipStatus ?? null;
const text = normaliseText(
[
node?.label,
node?.description,
...relatedNodes.map((relatedNode) => relatedNode.label),
...relatedNodes.map((relatedNode) => relatedNode.description),
]
.filter(Boolean)
.join(" "),
);
return (
relationshipStatus === "contradictory" ||
/\b(contradiction|contradict|conflict|inconsistent|mismatch|opposing)\b/.test(
text,
)
);
}
function isComparisonPatternCandidate(node, graph, relatedNodes = []) {
if (isRelationshipExplanationUnknown(node, graph)) {
return false;
}
const text = normaliseText(
[
node?.label,
node?.description,
...relatedNodes.map((relatedNode) => relatedNode.label),
...relatedNodes.map((relatedNode) => relatedNode.description),
]
.filter(Boolean)
.join(" "),
);
return (
(hasObservationRelationshipTopology(node, graph, relatedNodes) ||
collectObservationNodes(graph).length >= 2 ||
graph?.reasoningState?.comparabilityStatus === "uncertain") &&
/\b(compare|comparison|different timing|measured|measurement|basis|scale|period|alternative|alternatives|better than)\b/.test(
text,
)
);
}
function isExplanationPatternCandidate(node, graph, relatedNodes = []) {
if (isRelationshipExplanationUnknown(node, graph)) return true;
const text = normaliseText(
[
node?.label,
node?.description,
...relatedNodes.map((relatedNode) => relatedNode.label),
...relatedNodes.map((relatedNode) => relatedNode.description),
]
.filter(Boolean)
.join(" "),
);
return (
hasObservationRelationshipTopology(node, graph, relatedNodes) &&
/\b(explain|explanation|why .* but|difference between|divergence|what changed|moved differently)\b/.test(
text,
)
);
}
function isPrioritisationPatternCandidate(node, graph, relatedNodes = []) {
const text = normaliseText(
[
node?.label,
node?.description,
graph?.centralStatement,
...relatedNodes.map((relatedNode) => relatedNode.label),
]
.filter(Boolean)
.join(" "),
);
return /\b(prioritise|prioritize|priority|rank|ranking|trade off|tradeoff|which first)\b/.test(
text,
);
}
export function selectReasoningPattern({ node, graph, context = {} }) {
const relatedNodes = collectRelatedNodes(node, graph);
const patternContext = {
hasDecisionContext: hasDecisionContext(node, graph, relatedNodes),
hasObservationRelationshipTopology: hasObservationRelationshipTopology(
node,
graph,
relatedNodes,
),
};
if (isDefinitionPatternCandidate(node, graph)) {
return {
pattern: "definition",
reason:
"Selected definition because the active unknown is about meaning, scope, or term boundaries.",
context: patternContext,
};
}
if (isContradictionPatternCandidate(node, graph, relatedNodes)) {
return {
pattern: "contradiction",
reason:
"Selected contradiction because the graph indicates opposing claims or incompatible observations.",
context: patternContext,
};
}
if (isComparisonPatternCandidate(node, graph, relatedNodes)) {
return {
pattern: "comparison",
reason:
"Selected comparison because the active unknown is about distinguishing measurements, timing, basis, or alternatives.",
context: patternContext,
};
}
if (isExplanationPatternCandidate(node, graph, relatedNodes)) {
return {
pattern: "explanation",
reason:
"Selected explanation because the active unknown is about accounting for a relationship between observations.",
context: patternContext,
};
}
if (patternContext.hasDecisionContext) {
return {
pattern: "decision",
reason:
"Selected decision because the active unknown sits inside a build, continue, invest, or commercial-justification decision context.",
context: patternContext,
};
}
if (isPrioritisationPatternCandidate(node, graph, relatedNodes)) {
return {
pattern: "prioritisation",
reason:
"Selected prioritisation because the active unknown is about ordering options or trade-offs.",
context: patternContext,
};
}
return {
pattern: "diagnosis",
reason:
"Selected diagnosis as the default because the active unknown needs clarifying evidence or mechanism-level investigation.",
context: patternContext,
};
}
function allowedQuestionFamiliesForPattern(pattern) {
return QUESTION_FAMILIES_BY_PATTERN[pattern] || [pattern];
}
function rejectedQuestionFamiliesForPattern(pattern) {
const allowed = new Set(allowedQuestionFamiliesForPattern(pattern));
return ALL_REASONING_PATTERNS.flatMap((candidatePattern) =>
(
QUESTION_FAMILIES_BY_PATTERN[candidatePattern] || [candidatePattern]
).filter((family) => !allowed.has(family)),
).filter((family, index, list) => list.indexOf(family) === index);
}
function constrainStrategyToReasoningPattern(strategy, reasoningPattern) {
if (!strategy) return null;
const allowedStrategies = STRATEGIES_BY_PATTERN[reasoningPattern] || [];
return allowedStrategies.includes(strategy.key) ? strategy : null;
}
function selectQuestionFamily({
node,
graph,
reasoningPattern,
investigationStrategy,
}) {
const text = normaliseText(`${node?.label || ""} ${node?.description || ""}`);
if (reasoningPattern === "definition") {
return { family: "definition", template: "definition_meaning" };
}
if (reasoningPattern === "contradiction") {
if (/\b(period|timing|basis|scale|measure|measured)\b/.test(text)) {
return {
family: "comparison",
template: "comparison_reconcile_measurement",
};
}
return {
family: "contradiction",
template: "contradiction_resolve_opposition",
};
}
if (reasoningPattern === "explanation") {
return {
family: "explanation",
template: "explanation_broad_investigation",
};
}
if (reasoningPattern === "comparison") {
return /\b(period|timing)\b/.test(text) &&
!/\bhow the two observations were measured|measurement\b/.test(text)
? { family: "comparison", template: "comparison_timing_basis" }
: { family: "comparison", template: "comparison_measurement_basis" };
}
if (reasoningPattern === "decision") {
if (
/\b(audience|customer|user|buyer|stakeholder|recipient|who experiences)\b/.test(
text,
)
) {
return { family: "decision_foundation", template: "decision_audience" };
}
if (
/\b(alternative|alternatives|better than|different from|deal with)\b/.test(
text,
)
) {
return {
family: "decision_foundation",
template: "decision_current_alternatives",
};
}
if (/\b(problem|need|demand)\b/.test(text)) {
return {
family: "decision_foundation",
template: "decision_problem_existence",
};
}
if (investigationStrategy?.key === "decision_threshold") {
return {
family: "decision_threshold",
template: "decision_threshold_outcome",
};
}
return {
family: "decision_evidence",
template: "decision_evidence_clarification",
};
}
if (reasoningPattern === "prioritisation") {
return { family: "prioritisation", template: "prioritisation_tradeoff" };
}
return investigationStrategy?.key === "baseline_reconstruction"
? { family: "comparison", template: "diagnosis_baseline_comparison" }
: { family: "diagnosis", template: "diagnosis_evidence" };
}
function buildQuestionFromFamily({
node,
graph,
reasoningPattern,
questionFamily,
selectedQuestionTemplate,
investigationStrategy,
}) {
const meaning = extractMeaning(node);
if (reasoningPattern === "decision") {
if (selectedQuestionTemplate === "decision_audience") {
return "Who experiences this problem?";
}
if (selectedQuestionTemplate === "decision_current_alternatives") {
return "How do people deal with this today?";
}
if (selectedQuestionTemplate === "decision_problem_existence") {
return "What makes you think this is a real problem?";
}
if (selectedQuestionTemplate === "decision_threshold_outcome") {
return buildQuestionFromStrategy(
investigationStrategy || {
key: "decision_threshold",
meaning,
actionPhrase: null,
},
);
}
return `What evidence would clarify ${stripTrailingPunctuation(meaning)}?`;
}
if (questionFamily === "definition") {
return `What does ${meaning} mean in this situation?`;
}
if (reasoningPattern === "comparison") {
if (selectedQuestionTemplate === "comparison_timing_basis") {
return `What evidence would clarify whether ${stripTrailingPunctuation(meaning)}?`;
}
if (selectedQuestionTemplate === "comparison_measurement_basis") {
return `What evidence would clarify ${stripTrailingPunctuation(meaning)}?`;
}
return `What evidence would clarify ${stripTrailingPunctuation(meaning)}?`;
}
if (reasoningPattern === "contradiction") {
return investigationStrategy?.key === "contradiction_resolution"
? buildQuestionFromStrategy(investigationStrategy)
: `What fact would resolve the contradiction about ${stripTrailingPunctuation(meaning)}?`;
}
if (reasoningPattern === "explanation") {
return buildBroadInvestigationQuestion(graph);
}
if (reasoningPattern === "prioritisation") {
return `Which option should be investigated first, and why?`;
}
return investigationStrategy
? buildQuestionFromStrategy(investigationStrategy)
: buildNeutralClarificationQuestion(meaning);
}
export function formulateTieResolutionQuestion({ graph }) {
const comparability = assessComparability(graph);
if (comparability.comparabilityStatus === "uncertain") {
@@ -747,6 +1178,14 @@ export function formulateTieResolutionQuestion({ graph }) {
relationshipStatus: deferredRelationship.relationshipStatus,
relationshipReason: deferredRelationship.reason,
relationshipAssessed: deferredRelationship.relationshipAssessed,
reasoningPattern: "comparison",
reasoningPatternReason:
"Tie resolution is using the comparison family because comparability is still unresolved.",
allowedQuestionFamilies: allowedQuestionFamiliesForPattern("comparison"),
rejectedQuestionFamilies:
rejectedQuestionFamiliesForPattern("comparison"),
questionFamily: "comparison",
selectedQuestionTemplate: "comparison_tie_resolution",
questionRequired: true,
reasoningStages: deferredRelationship.reasoningStages,
};
@@ -766,6 +1205,14 @@ export function formulateTieResolutionQuestion({ graph }) {
relationshipReason: relationship.reason,
relationshipAssessed: relationship.relationshipAssessed,
contradictionReasoningAllowed: relationship.contradictionReasoningAllowed,
reasoningPattern: "comparison",
reasoningPatternReason:
"Tie resolution remains in the comparison family because no distinct winning unknown exists.",
allowedQuestionFamilies: allowedQuestionFamiliesForPattern("comparison"),
rejectedQuestionFamilies:
rejectedQuestionFamiliesForPattern("comparison"),
questionFamily: "comparison",
selectedQuestionTemplate: "comparison_no_question_required",
questionRequired: relationship.questionRequired,
questionSuppressedReason: relationship.questionSuppressedReason,
reasoningStages: relationship.reasoningStages,
@@ -786,6 +1233,14 @@ export function formulateTieResolutionQuestion({ graph }) {
relationshipReason: relationship.reason,
relationshipAssessed: relationship.relationshipAssessed,
contradictionReasoningAllowed: relationship.contradictionReasoningAllowed,
reasoningPattern: "explanation",
reasoningPatternReason:
"Tie resolution is using the explanation family because the observations appear related and need a neutral explanation question.",
allowedQuestionFamilies: allowedQuestionFamiliesForPattern("explanation"),
rejectedQuestionFamilies:
rejectedQuestionFamiliesForPattern("explanation"),
questionFamily: "explanation",
selectedQuestionTemplate: "explanation_broad_investigation",
questionRequired: relationship.questionRequired,
reasoningStages: relationship.reasoningStages,
};
@@ -812,6 +1267,14 @@ export function formulateTieResolutionQuestion({ graph }) {
relationshipReason: relationship.reason,
relationshipAssessed: relationship.relationshipAssessed,
contradictionReasoningAllowed: relationship.contradictionReasoningAllowed,
reasoningPattern: "contradiction",
reasoningPatternReason:
"Tie resolution is using the contradiction family because the graph is trying to distinguish incompatible explanations.",
allowedQuestionFamilies: allowedQuestionFamiliesForPattern("contradiction"),
rejectedQuestionFamilies:
rejectedQuestionFamiliesForPattern("contradiction"),
questionFamily: "contradiction",
selectedQuestionTemplate: "contradiction_distinguishing_change",
questionRequired: relationship.questionRequired,
reasoningStages: relationship.reasoningStages,
};
@@ -899,6 +1362,9 @@ function buildInvestigationStrategy({
}
export function selectInvestigationStrategy({ node, graph, context = {} }) {
const effectiveReasoningPattern =
context.reasoningPattern ||
selectReasoningPattern({ node, graph, context }).pattern;
const relatedNodes = collectRelatedNodes(node, graph);
const meaning = extractMeaning(node);
const combinedText = [
@@ -949,8 +1415,10 @@ export function selectInvestigationStrategy({ node, graph, context = {} }) {
const hasPrimaryBaselineLanguage =
/\b(before|previous|baseline|prior|comparable state)\b/.test(nodeText);
let selectedStrategy = null;
if (hasBaselineLanguage && hasPrimaryBaselineLanguage) {
return buildInvestigationStrategy({
selectedStrategy = buildInvestigationStrategy({
key: "baseline_reconstruction",
reason:
"Selected because the unknown explicitly references a missing previous or baseline state.",
@@ -991,8 +1459,11 @@ export function selectInvestigationStrategy({ node, graph, context = {} }) {
relatedNode.kind === "conclusion",
);
if (hasPrimaryDefinitionLanguage || hasDefinitionLanguage) {
return buildInvestigationStrategy({
if (
!selectedStrategy &&
(hasPrimaryDefinitionLanguage || hasDefinitionLanguage)
) {
selectedStrategy = buildInvestigationStrategy({
key: "definition",
reason:
"Selected because the unknown is primarily about clarifying what a term means in this case.",
@@ -1004,8 +1475,8 @@ export function selectInvestigationStrategy({ node, graph, context = {} }) {
});
}
if (hasDecisionValueLanguage || hasCriteriaLanguage) {
return buildInvestigationStrategy({
if (!selectedStrategy && (hasDecisionValueLanguage || hasCriteriaLanguage)) {
selectedStrategy = buildInvestigationStrategy({
key: "decision_threshold",
reason:
"Selected because the unknown determines the threshold for making or justifying a decision.",
@@ -1017,8 +1488,11 @@ export function selectInvestigationStrategy({ node, graph, context = {} }) {
});
}
if (hasPrimaryBaselineLanguage || hasBaselineLanguage) {
return buildInvestigationStrategy({
if (
!selectedStrategy &&
(hasPrimaryBaselineLanguage || hasBaselineLanguage)
) {
selectedStrategy = buildInvestigationStrategy({
key: "baseline_reconstruction",
reason:
"Selected because reconstructing the prior state is the most direct way to resolve the unknown.",
@@ -1030,8 +1504,8 @@ export function selectInvestigationStrategy({ node, graph, context = {} }) {
});
}
if (hasContradictionLanguage) {
return buildInvestigationStrategy({
if (!selectedStrategy && hasContradictionLanguage) {
selectedStrategy = buildInvestigationStrategy({
key: "contradiction_resolution",
reason:
"Selected because the graph context indicates conflicting claims or inconsistent states that must be reconciled.",
@@ -1043,8 +1517,11 @@ export function selectInvestigationStrategy({ node, graph, context = {} }) {
});
}
if (hasEvidenceLanguage || hasMeasurementLanguage || hasConstraintLanguage) {
return buildInvestigationStrategy({
if (
!selectedStrategy &&
(hasEvidenceLanguage || hasMeasurementLanguage || hasConstraintLanguage)
) {
selectedStrategy = buildInvestigationStrategy({
key: "evidence_gathering",
reason:
hasConstraintLanguage && hasPrimaryConstraintLanguage
@@ -1058,7 +1535,10 @@ export function selectInvestigationStrategy({ node, graph, context = {} }) {
});
}
return null;
return constrainStrategyToReasoningPattern(
selectedStrategy,
effectiveReasoningPattern,
);
}
function buildQuestionFromStrategy(strategy) {
@@ -1232,8 +1712,22 @@ export function formulateQuestion({ node, graph, context = {} }) {
return formulateTieResolutionQuestion({ graph });
}
const reasoningPatternSelection = selectReasoningPattern({
node,
graph,
context,
});
const allowedQuestionFamilies = allowedQuestionFamiliesForPattern(
reasoningPatternSelection.pattern,
);
const rejectedQuestionFamilies = rejectedQuestionFamiliesForPattern(
reasoningPatternSelection.pattern,
);
const foundationalDirectQuestion = buildFoundationalDirectQuestion(node);
if (foundationalDirectQuestion) {
if (
foundationalDirectQuestion &&
reasoningPatternSelection.pattern === "decision"
) {
const plainLanguage = applyPlainLanguageNormalisations(
sanitizeQuestionText(foundationalDirectQuestion),
);
@@ -1249,6 +1743,12 @@ export function formulateQuestion({ node, graph, context = {} }) {
"Formulated as a direct foundational question because this child unknown should be answered one step at a time.",
strategy: null,
investigationStrategy: null,
reasoningPattern: reasoningPatternSelection.pattern,
reasoningPatternReason: reasoningPatternSelection.reason,
questionFamily: "decision_foundation",
allowedQuestionFamilies,
rejectedQuestionFamilies,
selectedQuestionTemplate: "decision_foundation_direct_child",
questionComplexity,
plainLanguageNormalisations: plainLanguage.normalisations,
};
@@ -1257,14 +1757,27 @@ export function formulateQuestion({ node, graph, context = {} }) {
const investigationStrategy = selectInvestigationStrategy({
node,
graph,
context,
context: {
...context,
reasoningPattern: reasoningPatternSelection.pattern,
},
});
let question = investigationStrategy
? buildQuestionFromStrategy(investigationStrategy)
: isRelationshipExplanationUnknown(node, graph)
? buildBroadInvestigationQuestion(graph)
: buildNeutralClarificationQuestion(extractMeaning(node));
const questionFamilySelection = selectQuestionFamily({
node,
graph,
reasoningPattern: reasoningPatternSelection.pattern,
investigationStrategy,
});
let question = buildQuestionFromFamily({
node,
graph,
reasoningPattern: reasoningPatternSelection.pattern,
questionFamily: questionFamilySelection.family,
selectedQuestionTemplate: questionFamilySelection.template,
investigationStrategy,
});
question = sanitizeQuestionText(question);
const plainLanguage = applyPlainLanguageNormalisations(question);
@@ -1310,6 +1823,12 @@ export function formulateQuestion({ node, graph, context = {} }) {
: "Formulated as a neutral clarification question because no narrower investigation strategy clearly applied.",
strategy: investigationStrategy?.key ?? null,
investigationStrategy,
reasoningPattern: reasoningPatternSelection.pattern,
reasoningPatternReason: reasoningPatternSelection.reason,
questionFamily: questionFamilySelection.family,
allowedQuestionFamilies,
rejectedQuestionFamilies,
selectedQuestionTemplate: questionFamilySelection.template,
questionComplexity,
plainLanguageNormalisations: plainLanguage.normalisations,
};