/** * Experiment 22 — Question Relevance Against Decision Conditions. * * Classifies an unresolved unknown against explicit decision conditions * that define what must be true for a specific decision to be sensible. * * Classification categories: * tests_deciding_condition — The question directly tests something * required for the decision's justification. * adds_supporting_evidence — The answer would strengthen confidence * but doesn't test a required condition. * outside_decision_conditions — Not meaningfully connected to any condition. * cannot_determine — Inputs are missing, empty, or too unclear. * * No LLM calls. No new graph fields. Pure function. No engine mutation. */ function normalise(value) { return String(value || "").toLowerCase().replace(/[^a-z0-9]+/g, " ").trim(); } /* ── Primary concept groups (substring-based detection) ─*/ const DEMAND_CONCEPTS = ["demand","need","interest","customers","audience"]; const COMPLIANCE_CONCEPTS = ["compliance","regulation","legal","required","mandatory","gdpr","data residency"]; const VALUE_COST_CONCEPTS = ["cost","investment","justif","return","viability","financial"]; const DIFFERENTIATION_CONCEPTS = ["differentiat","advantage","competit","positioning","superior","unique"]; /* ── Supporting evidence concept groups (broader, indirect terms) ─*/ const DEMAND_SUPPORT_CONCEPTS = ["geography","region","country","territory","area","locale","segment","target","entry","expansion","penetration"]; const COMPLIANCE_SUPPORT_CONCEPTS = ["privacy","certification","standards"]; const VALUE_COST_SUPPORT_CONCEPTS = ["budget","price","revenue","pricing","resource","structure","subsidiary"]; const DIFFERENTIATION_SUPPORT_CONCEPTS = ["edge","distinct","feature","benefit"]; const CATEGORY_GROUPS = { demand: DEMAND_CONCEPTS, compliance: COMPLIANCE_CONCEPTS, value_cost: VALUE_COST_CONCEPTS, differentiation: DIFFERENTIATION_CONCEPTS, }; const SUPPORT_MAP = { demand: DEMAND_SUPPORT_CONCEPTS, compliance: COMPLIANCE_SUPPORT_CONCEPTS, value_cost: VALUE_COST_SUPPORT_CONCEPTS, differentiation: DIFFERENTIATION_SUPPORT_CONCEPTS, }; /* ── Which primary concept categories does a condition mention? ─*/ function getConditionCategories(condition) { const lower = condition.toLowerCase(); const cats = []; for (const [name, concepts] of Object.entries(CATEGORY_GROUPS)) { if (concepts.some((kw) => lower.includes(normalise(kw)))) cats.push(name); } return cats; } /* ── Primary categories a question draws from (substring matching) ─*/ function getPrimaryCategories(text) { const lower = text.toLowerCase(); const cats = []; for (const [name, concepts] of Object.entries(CATEGORY_GROUPS)) { let hasMatch = false; for (const concept of concepts) { if (lower.includes(normalise(concept))) { hasMatch = true; break; } } if (hasMatch) cats.push(name); } return cats; } /* ── Support categories a question draws from ─*/ function getSupportCategories(text) { const lower = text.toLowerCase(); const cats = []; for (const [name, concepts] of Object.entries(SUPPORT_MAP)) { let hasMatch = false; for (const concept of concepts) { if (lower.includes(normalise(concept))) { hasMatch = true; break; } } if (hasMatch) cats.push(name); } return cats; } /* ── Rule 1: Test a deciding condition directly ─*/ function testsCondition(conditions, primaryCategories, text) { let bestMatch = null; let bestScore = -1; let bestCoverage = 0; for (const cond of conditions) { const condCats = getConditionCategories(cond); if (condCats.length === 0) continue; let score = 0; let coverage = 0; for (const pCat of primaryCategories) { if (!condCats.includes(pCat)) continue; const group = CATEGORY_GROUPS[pCat]; const qLower = text.toLowerCase(); const cLower = cond.toLowerCase(); // Count distinct concepts from this category that appear in question or condition let distinctConcepts = 0; for (const c of group) { const normC = normalise(c); if (qLower.includes(normC) || cLower.includes(normC)) { distinctConcepts++; } } score += Math.min(distinctConcepts, group.length); if (distinctConcepts > coverage) coverage = distinctConcepts; } // Update: strict better score wins. Tied score: more concept coverage wins. if (score > bestScore || (score === bestScore && coverage > bestCoverage)) { bestMatch = cond; bestScore = score; bestCoverage = coverage; } } return bestMatch; } /* ── Rule 2: Add supporting evidence to a condition ─*/ function supportsCondition(conditions, supportCategories, primaryCategories, text) { let bestMatch = null; let bestScore = -1; for (const cond of conditions) { const condCats = getConditionCategories(cond); let score = 0; for (const sCat of supportCategories) { // Direct primary concept hits in this category's group const directHits = countPrimaryHits(text, sCat); // Support-only concept hits let supportOnlyHits = 0; for (const c of SUPPORT_MAP[sCat] || []) { if (text.toLowerCase().includes(normalise(c))) supportOnlyHits++; } score += directHits * 0.5 + supportOnlyHits * 0.3; } if (score > bestScore) { bestMatch = cond; bestScore = score; } } return bestMatch; } function countPrimaryHits(text, categoryName) { const group = CATEGORY_GROUPS[categoryName]; if (!group) return 0; const qLower = text.toLowerCase(); let count = 0; for (const c of group) { if (qLower.includes(normalise(c))) count++; } return count; } /* ── Core classification function ─────────────────────────── */ export function assessQuestionAgainstDecisionConditions(input) { const { decisionTarget, decisionConditions, unknown: node, graph } = input || {}; if (!decisionTarget || !node || typeof node.kind !== "string") { return { relevance: "cannot_determine", reason: "missing_input" }; } if (!decisionConditions || !Array.isArray(decisionConditions) || decisionConditions.length === 0) { return { relevance: "cannot_determine", reason: "missing_or_empty_conditions" }; } const text = `${node?.label || ""} ${node?.description || ""}`.trim(); if (!text) { return { relevance: "cannot_determine", reason: "empty_node_text" }; } const decisionText = normalise(decisionTarget); if (!decisionText) { return { relevance: "cannot_determine", reason: "empty_decision_target" }; } const validConditions = decisionConditions.filter((c) => c && normalise(c).length > 0); if (validConditions.length === 0) { return { relevance: "cannot_determine", reason: "all_conditions_empty" }; } const unknownNormalized = normalise(text); const dtWords = new Set(decisionText.split(/\s+/)); if (dtWords.size < 3) { return { relevance: "cannot_determine", reason: "decision_target_too_short" }; } const primaryCategories = getPrimaryCategories(unknownNormalized); const supportCategories = getSupportCategories(unknownNormalized); // No connection to any condition if (primaryCategories.length === 0 && supportCategories.length === 0) { return { relevance: "outside_decision_conditions", reason: "question does not relate to any stated decision condition" }; } // Rule 1: Direct test of a deciding condition — requires primary category matches if (primaryCategories.length > 0) { const matchedDirect = testsCondition(validConditions, primaryCategories, unknownNormalized); if (matchedDirect) { return { relevance: "tests_deciding_condition", matchedCondition: matchedDirect, reason: `question directly tests a condition required for the decision: "${matchedDirect}"`, }; } } // Rule 2: Supports a condition via indirect match const matchedSupport = supportsCondition(validConditions, supportCategories, primaryCategories, unknownNormalized); if (matchedSupport) { return { relevance: "adds_supporting_evidence", matchedCondition: matchedSupport, reason: `question provides evidence related to a decision condition rather than testing it directly: "${matchedSupport}"`, }; } // Rule 3: Outside (safety net) return { relevance: "outside_decision_conditions", reason: "question does not clearly connect to any stated decision condition" }; }