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.
165 lines
6.0 KiB
JavaScript
165 lines
6.0 KiB
JavaScript
/**
|
|
* 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` };
|
|
}
|