experiment: test question relevance against decision target
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* 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" };
|
||||
}
|
||||
Reference in New Issue
Block a user