experiment: qualify condition status by evidence scope

This commit is contained in:
2026-08-06 12:40:07 +01:00
parent 1a9a9a94fe
commit eb12a9ce49
+95 -15
View File
@@ -84,7 +84,9 @@ describe("assessDecisionConditionStatus — established", () => {
expect(result.evidenceNodeIds).toHaveLength(0);
});
it("returns established when resolved unique feature evidence supports differentiation", () => {
it("returns unresolved when evidence mentions a unique feature but scope is not direct_match", () => {
// Evidence contains support keywords but does not share the core "differentiat" keyword
// required by CONCEPT_FAMILIES for differentiation category. Scope = unrelated → unresolved.
const graph = makeGraph(
[makeNode("d-1", "Our platform offers unique real-time collaboration with no European equivalent", { status: "resolved" })],
["d-1"],
@@ -95,8 +97,24 @@ describe("assessDecisionConditionStatus — established", () => {
graph,
});
expect(result.status).toBe("unresolved");
expect(result.evidenceNodeIds).toHaveLength(0);
});
it("returns established when evidence has direct_match scope and supports differentiation", () => {
// Evidence contains core keyword "differentiat" → direct_match + supports → established
const graph = makeGraph(
[makeNode("d-3", "Our product offers a clear competitive differentiation against existing players", { status: "resolved" })],
["d-3"],
);
const result = assessDecisionConditionStatus({
condition: CONDITIONS[3],
graph,
});
expect(result.status).toBe("established");
expect(result.evidenceNodeIds).toContain("d-1");
expect(result.evidenceNodeIds).toContain("d-3");
});
it("returns established for compliance with GDPR match", () => {
@@ -148,7 +166,10 @@ describe("assessDecisionConditionStatus — contradicted", () => {
expect(result.evidenceNodeIds).toContain("x-1");
});
it("contradiction takes precedence over support when both exist", () => {
it("contradiction takes precedence over support only within direct scope", () => {
// s-1 gets direct_match scope (present-state evidence about present compliance)
// c-1 gets unrelated scope (evidence text does not match compliance category)
// With scope-aware logic: only s-1 contributes → established, contradicted status = unresolved
const graph = makeGraph(
[
makeNode("s-1", "Our platform meets GDPR requirements for compliance", { status: "resolved" }),
@@ -162,6 +183,25 @@ describe("assessDecisionConditionStatus — contradicted", () => {
graph,
});
// Only direct-scope evidence counts; s-1 supports, c-1 is unrelated → established
expect(result.status).toBe("established");
});
it("contradiction takes precedence over support within the same direct scope", () => {
// Both evidence nodes have direct_match scope for compliance
const graph = makeGraph(
[
makeNode("p-1", "Our platform meets compliance requirements", { status: "resolved" }),
makeNode("p-2", "But it does not support EU data residency mandates", { status: "resolved" }),
],
["p-1", "p-2"],
);
const result = assessDecisionConditionStatus({
condition: CONDITIONS[1],
graph,
});
expect(result.status).toBe("contradicted");
});
@@ -196,14 +236,51 @@ describe("assessDecisionConditionStatus — contradicted", () => {
expect(result.status).toBe("established");
});
it("returns contradicted when node text contains 'not achievable' phrase", () => {
it("returns unresolved when evidence contains 'not achievable' but scope is unrelated", () => {
// Evidence mentions "not achievable" (a contradiction phrase) but is about market share,
// not value_cost. Scope check correctly classifies as unrelated → contradiction ignored.
const graph = makeGraph(
[makeNode("na-1", "Target market share is not achievable given incumbent dominance", { status: "resolved" })],
["na-1"],
);
const result = assessDecisionConditionStatus({
condition: CONDITIONS[2], // value_cost — "achievable" matches CONTRADICTION_PHRASES via substring
condition: CONDITIONS[2], // value_cost — unrelated subject → scope=unrelated
graph,
});
expect(result.status).toBe("unresolved");
});
it("returns unresolved when evidence scope is different_timeframe for future-feasibility condition", () => {
// A condition using "can be achieved" triggers the different_timeframe scope check
// because the scope module recognises this as a future-feasibility phrase.
const graph = makeGraph(
[makeNode("c-3", "Our platform does not currently support EU data residency requirements", { status: "resolved" })],
["c-3"],
);
// This condition text triggers different_timeframe scope detection (contains "can be achieved"),
// so present-state evidence cannot establish or contradict it.
const result = assessDecisionConditionStatus({
condition: "European compliance can be achieved",
graph,
});
expect(result.status).toBe("unresolved");
});
it("present-state compliance condition with contradictory evidence returns contradicted", () => {
// Focused test: a genuinely present-state compliance condition should be contradicted
// when evidence directly negates the claim. Requires a separate unknown node for category
// matching and an observation-type node as contradicting evidence.
const cs1 = makeNode("cs-1", "Our platform does not support EU data residency requirements", { kind: "observation", status: "resolved" });
const ux = makeNode("u-x", "Whether our product meets EU compliance requirements", { kind: "unknown", status: "resolved" });
const graph = makeGraph([cs1, ux], ["cs-1", "u-x"]);
const result = assessDecisionConditionStatus({
condition: "The platform currently supports EU data residency requirements",
graph,
});
@@ -442,23 +519,26 @@ describe("assessDecisionConditionStatus — long investigation sequence", () =>
expect(valueCostResult.status).toBe("unresolved");
});
it("turn 4 has two established and one contradicted", () => {
it("turn 4 has demand established, compliance contradicted, value_cost unresolved, differentiation unresolved", () => {
// Turn 4 resolved=[u-1,u-2,u-3,u-4]
// demand=established, compliance=contradicted, value_cost=unresolved, differentiation=established
// demand=established (obs-2 direct_match + supports)
// compliance=contradicted (obs-3 direct_match + contradicts)
// value_cost=unresolved (obs-4 informs — no directional signal)
// differentiation=unresolved (obs-5 unrelated scope for CONDITIONS[3] keywords)
const turn4 = allResults.filter((r) => r.turn === 4);
const establishedCount = turn4.filter((r) => r.status === "established").length;
expect(establishedCount).toBeGreaterThanOrEqual(2);
const contradictedCount = turn4.filter((r) => r.status === "contradicted").length;
expect(contradictedCount).toBeGreaterThanOrEqual(1);
// Demand is established
const demandResult = turn4.find((r) => r.condition.includes("demand"));
expect(demandResult.status).toBe("established");
// Differentiation is established (no direct European equivalent)
const complianceResult = turn4.find((r) => r.condition.includes("compliance"));
expect(complianceResult.status).toBe("contradicted");
const valueCostResult = turn4.find((r) => r.condition.includes("value"));
expect(valueCostResult.status).toBe("unresolved");
const diffResult = turn4.find((r) => r.condition.includes("competitive"));
expect(diffResult.status).toBe("established");
// obs-5 "unrelated" scope for CONDITIONS[3] → unresolved
expect(diffResult.status).toBe("unresolved");
});
it("status transitions make sense: unresolved → determined over turns", () => {