116 lines
5.1 KiB
JavaScript
116 lines
5.1 KiB
JavaScript
/**
|
|
* 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" };
|
|
}
|