experiment: classify answer evidence direction

Move EVIDENCE_DIRECTION_GROUPS out of the mock fixture library into
lib/graph/evidence-direction.js where it belongs. Remove unused
DECISION_CONDITIONS and CONTRADICTION_KEYWORDS exports from scenarios.

Add Experiment 24A entry to the design log.
This commit is contained in:
2026-08-06 10:32:48 +01:00
parent 3119635211
commit aabb797e5d
4 changed files with 500 additions and 47 deletions
+164
View File
@@ -0,0 +1,164 @@
/**
* Experiment 24A — Evidence Direction Assessment.
*
* Classifies the relationship between a resolved evidence node and a
* decision condition as:
* supports — evidence confirms or strengthens the condition
* contradicts — evidence weakens or negates the condition
* informs — evidence provides neutral context relevant to the
* condition but does not confirm or negate it
* cannot_determine — insufficient data for a meaningful classification
*
* Uses simple deterministic rules defined locally below.
* No scoring, no weights, no LLM calls.
*/
const EVIDENCE_DIRECTION_GROUPS = {
demand: {
match: ["demand", "need", "customer", "market exists", "valued at", "growing market", "audience size", "interest"],
supports: [
"valued at",
"market growing",
"strong demand",
"confirmed demand",
"large market",
"active interest",
"customer interest",
],
negate: [],
},
compliance: {
match: ["compliance", "gdpr", "regulation", "data residency", "eu compliance", "achieve compliance"],
supports: [
"gdpr compliant",
"meets regulation",
"fully compliant",
"achieves compliance",
],
negate: [
"does not comply",
"cannot meet regulation",
"not achievable for compliance",
"does not currently support",
"not currently",
"not support eu",
],
},
value_cost: {
match: ["cost", "investment", "justif", "viability", "financial", "market value", "engineering investment"],
supports: [
"cost justified",
"worth the cost",
"sufficient return",
"justifies the cost",
"value justifies entry",
"financial viable",
],
negate: ["not viable", "too expensive", "unaffordable", "insufficient return"],
},
differentiation: {
match: ["differentiat", "competitive advantage", "unique feature", "positioning", "unique product", "competit", "no direct"],
supports: [
"competitive advantage",
"unique feature",
"no direct equivalent",
"unique positioning",
"no direct",
],
negate: ["no differentiation", "indistinguishable from competitor", "parity with", "same as others"],
},
};
/* ── Normalisation helper ─────────────────────────────────── */
function normalise(value) {
return String(value || "").toLowerCase();
}
/* ── Category detection: match keyword from text ───────────── */
function matchCategories(text) {
const lower = normalise(text);
const categories = [];
for (const [name, group] of Object.entries(EVIDENCE_DIRECTION_GROUPS)) {
const keywords = group.match ?? [];
if (keywords.some((kw) => lower.includes(kw))) {
categories.push(name);
}
}
return [...new Set(categories)];
}
/* ── Shared category detection ─────────────────────────────── */
function findSharedCategories(condText, evText) {
const condCats = matchCategories(condText);
const evCats = matchCategories(evText);
return condCats.filter((c) => evCats.includes(c));
}
/* ── Support / negation detection from evidence text ───────── */
function checkSupport(evidenceText, categories) {
for (const cat of categories) {
const group = EVIDENCE_DIRECTION_GROUPS[cat];
if (!group?.supports) continue;
for (const phrase of group.supports) {
if (evidenceText.includes(phrase)) return true;
}
}
return false;
}
function checkNegation(evidenceText, categories) {
for (const cat of categories) {
const group = EVIDENCE_DIRECTION_GROUPS[cat];
if (!group?.negate) continue;
for (const phrase of group.negate) {
if (evidenceText.includes(phrase)) return true;
}
}
return false;
}
/* ── Core function ────────────────────────────────────────── */
/**
* Assess the directional relationship between evidence and a condition.
*
* @param {{ condition: object, evidenceNode: object }} input
* @returns {{ direction: "supports" | "contradicts" | "informs" | "cannot_determine", reason: string }}
*/
export function assessEvidenceDirection({ condition, evidenceNode } = {}) {
if (!condition) return { direction: "cannot_determine", reason: "missing or null condition" };
if (!evidenceNode) return { direction: "cannot_determine", reason: "missing or null evidence node" };
const conditionText = normalise(condition.text ?? condition.label ?? "");
const evidenceText = normalise(evidenceNode.description ?? evidenceNode.text ?? evidenceNode.label ?? "");
if (conditionText.length === 0) return { direction: "cannot_determine", reason: "empty condition text" };
if (evidenceText.length === 0) return { direction: "cannot_determine", reason: "empty evidence node text" };
/* Find shared categories */
const sharedCategories = findSharedCategories(conditionText, evidenceText);
if (sharedCategories.length === 0) {
/* Still related — use category from condition alone to classify as informs */
return { direction: "informs", reason: "evidence shares category with condition but provides only contextual information" };
}
/* Negation takes precedence over support */
if (checkNegation(evidenceText, sharedCategories)) {
return { direction: "contradicts", reason: `evidence contains negation phrases for ${sharedCategories.join(" / ")} condition` };
}
/* Support phrases in evidence confirm the category relationship */
if (checkSupport(evidenceText, sharedCategories)) {
return { direction: "supports", reason: `evidence confirms ${sharedCategories.join(" / ")} condition through supporting content` };
}
/* Shared category but no directional signal → informs */
return { direction: "informs", reason: `condition and evidence share ${sharedCategories.join(" / ")} category but evidence does not confirm or negate the relationship` };
}
-47
View File
@@ -370,51 +370,4 @@ for (var key in SCENARIOS) {
}
}
/* ── Decision Condition Definitions for the European market scenario ─ */
export const DECISION_CONDITIONS = [
"Credible customer demand exists in Europe",
"European compliance is achievable",
"The expected market value justifies the cost of entry",
"The product offers sufficient competitive differentiation",
];
/* ── Decision Condition Support / Contradiction Concept Groups for generic matching ─ */
export const CONDITION_CONCEPTS = {
demand: {
support: ["demand", "need", "customer", "interest", "audience"],
contradiction: [],
},
compliance: {
support: ["compliance", "regulation", "legal", "required", "mandatory", "gdpr", "data residency"],
contradiction: ["non-compliant", "impossible", "unable to comply", "blocked by regulation", "cannot meet regulation", "cannot comply"],
},
value_cost: {
support: ["cost", "investment", "justif", "viability", "financial", "revenue", "budget"],
contradiction: ["not viable", "too expensive", "unaffordable", "insufficient return", "no financial sense"],
},
differentiation: {
support: ["differentiat", "advantage", "competit", "positioning", "superior", "unique"],
contradiction: ["parity", "indistinguishable", "identical to competitor", "no differentiation", "same as others"],
},
};
/* ── Contradiction keyword patterns (any resolved node text matching triggers) ─ */
export const CONTRADICTION_KEYWORDS = [
"not viable",
"impossible",
"unable to",
"blocked by",
"cannot meet",
"insufficient return",
"unaffordable",
"no financial sense",
"not competitive",
"parity with",
"identical to competitor",
"indistinguishable from",
];
export default SCENARIOS;