experiment: test question relevance against decision target

This commit is contained in:
2026-08-06 06:19:09 +01:00
parent 7c3d1e7355
commit 89650b44df
3 changed files with 482 additions and 0 deletions
+115
View File
@@ -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" };
}