/** * Question Relevance to Decision Target — passive classifier for Experiment 21. * * Classifies an unresolved unknown's relevance against an explicit decision target. * Importance is not an isolated property of a question; it is a relationship between * the question and the decision the investigation is trying to support. * * Classification categories: * could_change_decision — Answering could reasonably reverse the proposed action. * supports_decision — Answer improves confidence/evidence, less likely to reverse alone. * unlikely_to_change_decision — Answer may be interesting but unlikely to materially affect the decision. * cannot_determine — Decision target or unknown is missing / empty / too unclear. * * No LLM calls. No new graph fields. Pure function. No engine mutation. */ /* ── Helper: normalise text for matching ─────────────────────── */ function normalise(value) { return String(value || "").toLowerCase().replace(/[^a-z0-9]+/g, " ").trim(); } /* ── Rule 1: Direct action — question asks whether the decision's core action should happen ─ */ const DECISION_REVERSAL_PATTERNS = [ // Direct: "whether to enter/launch/build/proceed..." /whether to (proceed|enter|launch|build|stop|abandon|drop|cancel|shelve)/i, // Direct: "we should/must/can [action]..." /\bwe\s+(should|must|can|need)\s+(to\s+)?(enter|launch|build|stop|proceed|pursue)\b/i, // Fundamental existence check: "whether there is/are [any words] demand/market/need..." /whether there (is|are).*\b(demand|market|need|interest|customers|audience|users)\b/i, ]; /* ── Rule 2: Necessary precondition — must be true for the decision to proceed ─ */ const PRECONDITION_PATTERNS = [ // "whether our product is/has ..." /\bour product (is|has|supports|meets|handles)\b.*\b(compliance|regulation|legal|required|mandatory|suitable)\b/i, ]; /* ── Rule 3a: Feasibility / cost-justify — supports but not decisive alone ─ */ const FEASIBILITY_PATTERNS = [ /\b(cost (of|versus|vs|and)\s+(\w+)|justified\s+(by|with|through))/i, ]; /* ── Rule 3: Supporting context — informative but not decisive ─ */ const SUPPORTING_CONTEXT_PATTERNS = [ /\bdifferentiat.*\b(against|versus|existing)\b/i, /\bcompetitive (differentiation|advantage|landscape|position)\b/i, /\boption|approach|path|way\b/i, ]; /* ── Rule 4: Incidental — background or comparative detail ─ */ const INCIDENTAL_PATTERNS = [ /\b(history|background|general|typically|usually|generally)\b/i, /\bcustomer (segment|base|profile|persona)\b/i, /\bbenchmark(s)?|standard\s+(reference|example|case\s*study)\b/i, ]; /* ── Core classification function ───────────────────────────── */ export function assessQuestionRelevanceToDecision(input) { const { decisionTarget, unknown: node, graph } = input || {}; // Missing or invalid inputs → cannot_determine if (!decisionTarget || !node || typeof node.kind !== "string") { return { relevance: "cannot_determine", reason: "missing_input" }; } const text = `${normalise(node.label)} ${normalise(node.description)}`.trim(); if (!text) { return { relevance: "cannot_determine", reason: "empty_node_text" }; } const decisionText = normalise(decisionTarget); // Rule 1: Direct action — question mirrors the decision's core action const hasActionKeyword = /\b(enter|launch|build|stop|abandon)\b/.test(decisionText); const directMatch = DECISION_REVERSAL_PATTERNS.some((p) => p.test(text)); if (directMatch && hasActionKeyword) { return { relevance: "could_change_decision", reason: "question directly mirrors the decision's core action" }; } // Rule 2: Necessary precondition — tests a condition that must be true for the decision const precondMatch = PRECONDITION_PATTERNS.some((p) => p.test(text)); if (precondMatch) { return { relevance: "supports_decision", reason: "question tests a necessary precondition for the decision" }; } // Rule 3a: Feasibility / cost-justify — supports but not decisive alone const feasibilityMatch = FEASIBILITY_PATTERNS.some((p) => p.test(text)); if (feasibilityMatch) { return { relevance: "supports_decision", reason: "question assesses feasibility or cost justification of the decision" }; } // Rule 3: Supporting context — informative but not decisive on its own const supportingMatch = SUPPORTING_CONTEXT_PATTERNS.some((p) => p.test(text)); if (supportingMatch) { return { relevance: "supports_decision", reason: "question provides supporting context rather than a go/no-go condition" }; } // Rule 4: Unlikely to change the decision — background detail const incidentalMatch = INCIDENTAL_PATTERNS.some((p) => p.test(text)); if (incidentalMatch) { return { relevance: "unlikely_to_change_decision", reason: "question concerns background detail rather than decision conditions" }; } // Fallback — text is too generic to judge against the decision target return { relevance: "cannot_determine", reason: "text does not clearly relate to or contrast with the decision target" }; }