From 00e23934bf5a3a4bd61e3c72911348d415597d1e Mon Sep 17 00:00:00 2001 From: Feeelix <44130555+FeeeeelixWong@users.noreply.github.com> Date: Fri, 15 May 2026 19:15:54 +0800 Subject: [PATCH] Add knowledge graph conflict arbiter --- knowledge-graph-conflict-arbiter/README.md | 34 +++ .../data/sample-graph.json | 98 +++++++++ .../docs/demo.mp4 | Bin 0 -> 59126 bytes .../docs/demo.svg | 26 +++ .../docs/requirement-map.md | 18 ++ knowledge-graph-conflict-arbiter/package.json | 12 ++ .../scripts/demo.js | 17 ++ .../src/conflict-arbiter.js | 200 ++++++++++++++++++ .../test/conflict-arbiter.test.js | 50 +++++ 9 files changed, 455 insertions(+) create mode 100644 knowledge-graph-conflict-arbiter/README.md create mode 100644 knowledge-graph-conflict-arbiter/data/sample-graph.json create mode 100644 knowledge-graph-conflict-arbiter/docs/demo.mp4 create mode 100644 knowledge-graph-conflict-arbiter/docs/demo.svg create mode 100644 knowledge-graph-conflict-arbiter/docs/requirement-map.md create mode 100644 knowledge-graph-conflict-arbiter/package.json create mode 100644 knowledge-graph-conflict-arbiter/scripts/demo.js create mode 100644 knowledge-graph-conflict-arbiter/src/conflict-arbiter.js create mode 100644 knowledge-graph-conflict-arbiter/test/conflict-arbiter.test.js diff --git a/knowledge-graph-conflict-arbiter/README.md b/knowledge-graph-conflict-arbiter/README.md new file mode 100644 index 0000000..187cc34 --- /dev/null +++ b/knowledge-graph-conflict-arbiter/README.md @@ -0,0 +1,34 @@ +# Knowledge Graph Conflict Arbiter + +This is a self-contained module for SCIBASE issue #17, focused on trust operations for Scientific Knowledge Graph Integration. + +The module arbitrates contradictory scientific knowledge graph relationships before they flow into graph search, entity pages, or AI research recommendations. It groups extracted relationship triples, scores evidence by confidence, evidence type, citation count, and freshness, then decides whether to accept, quarantine, suppress, or send an edge for low-confidence review. + +## What It Covers + +- Relationship grouping for subject/predicate/object graph triples. +- Support/refute polarity handling for contradictory scientific claims. +- Evidence scoring for replicated experiments, replication failures, dataset lineage, curator assertions, and weak text mentions. +- Recommendation suppression when dependent graph edges are quarantined or suppressed. +- Curator action packets for conflict review, relationship suppression, and low-confidence evidence. +- Deterministic audit hashes for relationship evidence and full arbitration packets. + +## Demo + +Run npm run check, npm test, and npm run demo from this directory. + +Expected demo output includes relationship groups, conflicts, quarantined relationships, suppressed recommendations, curator actions, and an audit digest. + +The reviewer-facing visual is in docs/demo.svg; a short demo video is in docs/demo.mp4. + +## Files + +- src/conflict-arbiter.js - deterministic graph arbitration logic. +- data/sample-graph.json - synthetic scientific graph entities, relationships, and recommendations. +- test/conflict-arbiter.test.js - Node assert coverage for conflict quarantine, accepted lineage, weak evidence review, and recommendation suppression. +- scripts/demo.js - terminal demo for reviewers. +- docs/requirement-map.md - maps this module to issue #17 requirements. + +## Safety Notes + +No live ontology, DOI, PubMed, Crossref, LLM, private research, credential, or external service is used. All graph data is synthetic. diff --git a/knowledge-graph-conflict-arbiter/data/sample-graph.json b/knowledge-graph-conflict-arbiter/data/sample-graph.json new file mode 100644 index 0000000..96a43e6 --- /dev/null +++ b/knowledge-graph-conflict-arbiter/data/sample-graph.json @@ -0,0 +1,98 @@ +{ + "graphId": "kg-crispr-neuro-2026", + "freshnessHalfLifeDays": 365, + "entities": [ + { "id": "method-crispr-prime-editing", "type": "method", "label": "CRISPR prime editing" }, + { "id": "disease-parkinsons", "type": "disease", "label": "Parkinsons disease" }, + { "id": "dataset-neuro-organoid-q2", "type": "dataset", "label": "Neuro organoid perturbation Q2" }, + { "id": "protocol-dopamine-neurons", "type": "protocol", "label": "Dopamine neuron differentiation" }, + { "id": "paper-alpha", "type": "paper", "label": "Alpha lab preprint" }, + { "id": "paper-beta", "type": "paper", "label": "Beta lab replication note" } + ], + "relationships": [ + { + "id": "edge-1", + "subject": "method-crispr-prime-editing", + "predicate": "improves-model-for", + "object": "disease-parkinsons", + "polarity": "supports", + "evidenceType": "replicated-experiment", + "confidence": 0.89, + "citationCount": 42, + "sourceDate": "2026-04-20", + "source": "paper-alpha", + "extractor": "pubmed-ner-v2", + "evidenceSpan": "Prime editing improved dopaminergic neuron rescue in three replicated organoid batches." + }, + { + "id": "edge-2", + "subject": "method-crispr-prime-editing", + "predicate": "improves-model-for", + "object": "disease-parkinsons", + "polarity": "refutes", + "evidenceType": "replication-failure", + "confidence": 0.82, + "citationCount": 17, + "sourceDate": "2026-05-08", + "source": "paper-beta", + "extractor": "crossref-abstract-v1", + "evidenceSpan": "Independent replication did not reproduce rescue after batch correction." + }, + { + "id": "edge-3", + "subject": "dataset-neuro-organoid-q2", + "predicate": "reuses-protocol", + "object": "protocol-dopamine-neurons", + "polarity": "supports", + "evidenceType": "dataset-lineage", + "confidence": 0.93, + "citationCount": 9, + "sourceDate": "2026-05-01", + "source": "dataset-neuro-organoid-q2", + "extractor": "metadata-parser-v3", + "evidenceSpan": "Dataset manifest links batch generation to protocol v3.2." + }, + { + "id": "edge-4", + "subject": "dataset-neuro-organoid-q2", + "predicate": "reuses-protocol", + "object": "protocol-dopamine-neurons", + "polarity": "supports", + "evidenceType": "curator-assertion", + "confidence": 0.78, + "citationCount": 3, + "sourceDate": "2026-04-28", + "source": "paper-alpha", + "extractor": "manual-curation", + "evidenceSpan": "Curator confirmed protocol linkage during import review." + }, + { + "id": "edge-5", + "subject": "method-crispr-prime-editing", + "predicate": "requires-dataset", + "object": "dataset-neuro-organoid-q2", + "polarity": "supports", + "evidenceType": "weak-text-mention", + "confidence": 0.47, + "citationCount": 1, + "sourceDate": "2024-09-10", + "source": "paper-alpha", + "extractor": "legacy-pdf-ocr", + "evidenceSpan": "An OCR mention suggests possible use of organoid data." + } + ], + "recommendations": [ + { + "id": "rec-1", + "topic": "prime-editing-parkinsons-collaboration", + "dependsOnEdges": ["edge-1", "edge-2"], + "text": "Recommend collaborators using CRISPR prime editing for Parkinsons disease." + }, + { + "id": "rec-2", + "topic": "organoid-protocol-reuse", + "dependsOnEdges": ["edge-3", "edge-4"], + "text": "Recommend projects that reuse the dopamine neuron differentiation protocol." + } + ] +} diff --git a/knowledge-graph-conflict-arbiter/docs/demo.mp4 b/knowledge-graph-conflict-arbiter/docs/demo.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..cb9653669b6dc503c2b4933c2ed85eaa70aa4d56 GIT binary patch literal 59126 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
7Y-OF($qx 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 Ibr<6Q3;O4dr2qf` literal 0 HcmV?d00001 diff --git a/knowledge-graph-conflict-arbiter/docs/demo.svg b/knowledge-graph-conflict-arbiter/docs/demo.svg new file mode 100644 index 0000000..6c5ad8a --- /dev/null +++ b/knowledge-graph-conflict-arbiter/docs/demo.svg @@ -0,0 +1,26 @@ + + Knowledge graph conflict arbiter demo + A dashboard-style summary of scientific graph relationship arbitration and recommendation suppression. + + + Scientific Knowledge Graph Conflict Arbiter + Contradictory graph edges are quarantined before search, entity pages, or recommendations use them. + + Relationship groups + 3 + + Conflicts + 1 + + Quarantined + 1 + + Suppressed recs + 1 + + CRISPR prime editing -> improves-model-for -> Parkinsons disease + Support and refute evidence are close, so the edge is quarantined for curator review. + Recommendation suppressed until curated + The recommendation engine receives provenance and a reason instead of silently using conflicting evidence. + Audit digest: deterministic SHA-256 arbitration packet hash. + diff --git a/knowledge-graph-conflict-arbiter/docs/requirement-map.md b/knowledge-graph-conflict-arbiter/docs/requirement-map.md new file mode 100644 index 0000000..ee63f82 --- /dev/null +++ b/knowledge-graph-conflict-arbiter/docs/requirement-map.md @@ -0,0 +1,18 @@ +# Requirement Map + +This module targets SCIBASE-AI/SCIBASE.AI issue #17, "Scientific Knowledge Graph Integration". + +| Issue capability | Implementation evidence | +| --- | --- | +| Entity extraction output and linked graph relationships | data/sample-graph.json models scientific entities and extracted relationship edges with evidence spans, extractors, and sources. | +| Knowledge navigation with trustworthy graph search | src/conflict-arbiter.js groups relationship triples and quarantines contradictory edges before graph search or recommendations use them. | +| AI research recommendations with provenance | Recommendation decisions explain whether suggestions are allowed or suppressed based on relationship arbitration. | +| Entity pages with citations and usage contexts | Leading evidence records include source, source date, evidence type, polarity, and audit hash for entity-page conflict notes. | +| Cross-project inference and knowledge gaps | The arbiter prevents inference from mutually contradictory claims and emits curator actions for review gaps. | +| Structured linked data quality | Deterministic audit digests make curation packets reproducible and reviewable. | + +## Non-goals + +- No live PubMed, Crossref, DOI, ontology, or LLM service calls. +- No private research content or external credentials are used. +- This slice is a graph-quality control module, not another broad extractor or graph UI. diff --git a/knowledge-graph-conflict-arbiter/package.json b/knowledge-graph-conflict-arbiter/package.json new file mode 100644 index 0000000..cdb8c08 --- /dev/null +++ b/knowledge-graph-conflict-arbiter/package.json @@ -0,0 +1,12 @@ +{ + "name": "knowledge-graph-conflict-arbiter", + "version": "1.0.0", + "private": true, + "description": "Dependency-free scientific knowledge graph relationship conflict arbiter for SCIBASE.", + "type": "commonjs", + "scripts": { + "check": "node --check src/conflict-arbiter.js && node --check scripts/demo.js && node --check test/conflict-arbiter.test.js", + "test": "node test/conflict-arbiter.test.js", + "demo": "node scripts/demo.js" + } +} diff --git a/knowledge-graph-conflict-arbiter/scripts/demo.js b/knowledge-graph-conflict-arbiter/scripts/demo.js new file mode 100644 index 0000000..66f1b28 --- /dev/null +++ b/knowledge-graph-conflict-arbiter/scripts/demo.js @@ -0,0 +1,17 @@ +"use strict"; + +const fs = require("node:fs"); +const path = require("node:path"); +const { arbitrateKnowledgeGraph } = require("../src/conflict-arbiter"); + +const graph = JSON.parse(fs.readFileSync(path.join(__dirname, "../data/sample-graph.json"), "utf8")); +const packet = arbitrateKnowledgeGraph(graph, { asOfDate: "2026-05-15" }); + +console.log("Graph: " + packet.graphId); +console.log("Relationship groups: " + packet.summary.relationshipGroups); +console.log("Conflicts: " + packet.summary.conflicts); +console.log("Quarantined relationships: " + packet.summary.quarantined); +console.log("Suppressed recommendations: " + packet.summary.suppressedRecommendations); +console.log("Curator actions: " + packet.summary.curatorActions); +console.log("First action: " + packet.actions[0].message); +console.log("Audit digest: " + packet.auditDigest); diff --git a/knowledge-graph-conflict-arbiter/src/conflict-arbiter.js b/knowledge-graph-conflict-arbiter/src/conflict-arbiter.js new file mode 100644 index 0000000..4ed5c69 --- /dev/null +++ b/knowledge-graph-conflict-arbiter/src/conflict-arbiter.js @@ -0,0 +1,200 @@ +"use strict"; + +const crypto = require("node:crypto"); + +const EVIDENCE_WEIGHTS = { + "replicated-experiment": 1.0, + "replication-failure": 0.95, + "dataset-lineage": 0.9, + "curator-assertion": 0.82, + "weak-text-mention": 0.35 +}; + +function stableStringify(value) { + if (Array.isArray(value)) return "[" + value.map(stableStringify).join(",") + "]"; + if (value && typeof value === "object") { + return "{" + Object.keys(value).sort().map((key) => JSON.stringify(key) + ":" + stableStringify(value[key])).join(",") + "}"; + } + return JSON.stringify(value); +} + +function digest(value) { + return crypto.createHash("sha256").update(stableStringify(value)).digest("hex"); +} + +function validateGraph(graph) { + const issues = []; + if (!graph || typeof graph !== "object") issues.push("graph"); + if (!graph.graphId) issues.push("graphId"); + if (!Array.isArray(graph.entities)) issues.push("entities"); + if (!Array.isArray(graph.relationships)) issues.push("relationships"); + if (!Array.isArray(graph.recommendations)) issues.push("recommendations"); + + for (const edge of graph.relationships || []) { + for (const field of ["id", "subject", "predicate", "object", "polarity", "evidenceType", "sourceDate"]) { + if (!edge[field]) issues.push((edge.id || "unknown") + "." + field); + } + if (typeof edge.confidence !== "number") issues.push((edge.id || "unknown") + ".confidence"); + } + + if (issues.length) throw new Error("Invalid graph: " + issues.join(", ")); +} + +function daysBetween(laterIso, earlierIso) { + const later = Date.parse(laterIso + "T00:00:00.000Z"); + const earlier = Date.parse(earlierIso + "T00:00:00.000Z"); + return Math.max(0, Math.round((later - earlier) / 86400000)); +} + +function freshnessScore(edge, asOfDate, halfLifeDays) { + const age = daysBetween(asOfDate, edge.sourceDate); + return Math.pow(0.5, age / halfLifeDays); +} + +function evidenceScore(edge, asOfDate, halfLifeDays) { + const evidenceWeight = EVIDENCE_WEIGHTS[edge.evidenceType] || 0.5; + const citationBoost = Math.min(0.18, Math.log10((edge.citationCount || 0) + 1) * 0.08); + const raw = edge.confidence * evidenceWeight * freshnessScore(edge, asOfDate, halfLifeDays) + citationBoost; + return Math.max(0, Math.min(1, Number(raw.toFixed(4)))); +} + +function relationshipKey(edge) { + return [edge.subject, edge.predicate, edge.object].join("::"); +} + +function groupRelationships(relationships) { + const groups = new Map(); + for (const edge of relationships) { + const key = relationshipKey(edge); + if (!groups.has(key)) groups.set(key, []); + groups.get(key).push(edge); + } + return groups; +} + +function summarizeGroup(key, edges, asOfDate, halfLifeDays) { + const scored = edges.map((edge) => ({ + ...edge, + evidenceScore: evidenceScore(edge, asOfDate, halfLifeDays), + auditHash: digest(edge) + })).sort((a, b) => b.evidenceScore - a.evidenceScore); + const supporting = scored.filter((edge) => edge.polarity === "supports"); + const refuting = scored.filter((edge) => edge.polarity === "refutes"); + const supportScore = Number(supporting.reduce((sum, edge) => sum + edge.evidenceScore, 0).toFixed(4)); + const refuteScore = Number(refuting.reduce((sum, edge) => sum + edge.evidenceScore, 0).toFixed(4)); + const margin = Number(Math.abs(supportScore - refuteScore).toFixed(4)); + const hasConflict = supporting.length > 0 && refuting.length > 0; + const winner = supportScore >= refuteScore ? "supports" : "refutes"; + let decision = "accept"; + if (hasConflict && margin < 0.3) decision = "quarantine-for-curation"; + else if (hasConflict) decision = winner === "supports" ? "accept-with-conflict-note" : "suppress-relationship"; + else if (scored[0].evidenceScore < 0.5) decision = "low-confidence-review"; + + return { + key, + subject: scored[0].subject, + predicate: scored[0].predicate, + object: scored[0].object, + edgeIds: scored.map((edge) => edge.id), + supportScore, + refuteScore, + margin, + hasConflict, + decision, + winner, + leadingEvidence: scored.slice(0, 3).map((edge) => ({ + id: edge.id, + polarity: edge.polarity, + evidenceType: edge.evidenceType, + evidenceScore: edge.evidenceScore, + source: edge.source, + sourceDate: edge.sourceDate, + auditHash: edge.auditHash + })) + }; +} + +function explainRecommendation(recommendation, arbitrationByEdge) { + const related = recommendation.dependsOnEdges.map((edgeId) => arbitrationByEdge.get(edgeId)).filter(Boolean); + const quarantined = related.filter((item) => item.decision === "quarantine-for-curation" || item.decision === "suppress-relationship"); + return { + id: recommendation.id, + topic: recommendation.topic, + action: quarantined.length ? "suppress-until-curated" : "allow-with-provenance", + reasons: quarantined.length + ? quarantined.map((item) => item.key + " -> " + item.decision) + : related.map((item) => item.key + " -> " + item.decision), + text: recommendation.text + }; +} + +function curatorActions(arbitrations) { + const actions = []; + for (const item of arbitrations) { + if (item.decision === "quarantine-for-curation") { + actions.push({ + type: "curator-review", + target: item.key, + message: "Conflicting support/refute evidence is too close; require curator decision before graph search or recommendations use this edge." + }); + } + if (item.decision === "suppress-relationship") { + actions.push({ + type: "relationship-suppression", + target: item.key, + message: "Refuting evidence dominates; suppress relationship and show conflict note on entity pages." + }); + } + if (item.decision === "low-confidence-review") { + actions.push({ + type: "low-confidence-review", + target: item.key, + message: "Only weak or stale evidence is available; request stronger evidence before promotion." + }); + } + } + return actions; +} + +function arbitrateKnowledgeGraph(graph, options = {}) { + validateGraph(graph); + const asOfDate = options.asOfDate || "2026-05-15"; + const halfLifeDays = options.freshnessHalfLifeDays || graph.freshnessHalfLifeDays || 365; + const groups = groupRelationships(graph.relationships); + const arbitrations = Array.from(groups.entries()).map(([key, edges]) => summarizeGroup(key, edges, asOfDate, halfLifeDays)); + const arbitrationByEdge = new Map(); + for (const item of arbitrations) { + for (const edgeId of item.edgeIds) arbitrationByEdge.set(edgeId, item); + } + const recommendationDecisions = graph.recommendations.map((recommendation) => explainRecommendation(recommendation, arbitrationByEdge)); + const actions = curatorActions(arbitrations); + const packet = { + graphId: graph.graphId, + asOfDate, + summary: { + relationshipGroups: arbitrations.length, + conflicts: arbitrations.filter((item) => item.hasConflict).length, + quarantined: arbitrations.filter((item) => item.decision === "quarantine-for-curation").length, + suppressedRecommendations: recommendationDecisions.filter((item) => item.action === "suppress-until-curated").length, + curatorActions: actions.length + }, + arbitrations, + recommendationDecisions, + actions + }; + return { + ...packet, + auditDigest: digest(packet) + }; +} + +module.exports = { + EVIDENCE_WEIGHTS, + arbitrateKnowledgeGraph, + digest, + evidenceScore, + freshnessScore, + relationshipKey, + stableStringify, + validateGraph +}; diff --git a/knowledge-graph-conflict-arbiter/test/conflict-arbiter.test.js b/knowledge-graph-conflict-arbiter/test/conflict-arbiter.test.js new file mode 100644 index 0000000..f907af9 --- /dev/null +++ b/knowledge-graph-conflict-arbiter/test/conflict-arbiter.test.js @@ -0,0 +1,50 @@ +"use strict"; + +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const path = require("node:path"); +const { + arbitrateKnowledgeGraph, + evidenceScore, + relationshipKey, + validateGraph +} = require("../src/conflict-arbiter"); + +const graph = JSON.parse(fs.readFileSync(path.join(__dirname, "../data/sample-graph.json"), "utf8")); +validateGraph(graph); + +const packet = arbitrateKnowledgeGraph(graph, { asOfDate: "2026-05-15" }); +assert.equal(packet.graphId, "kg-crispr-neuro-2026"); +assert.equal(packet.summary.relationshipGroups, 3); +assert.equal(packet.summary.conflicts, 1); +assert.equal(packet.summary.quarantined, 1); +assert.equal(packet.summary.suppressedRecommendations, 1); +assert.equal(packet.summary.curatorActions, 2); +assert.match(packet.auditDigest, /^[a-f0-9]{64}$/); + +const conflict = packet.arbitrations.find((item) => item.predicate === "improves-model-for"); +assert.equal(conflict.hasConflict, true); +assert.equal(conflict.decision, "quarantine-for-curation"); +assert.equal(conflict.winner, "supports"); +assert.ok(conflict.margin < 0.3); +assert.equal(conflict.leadingEvidence.length, 2); +assert.match(conflict.leadingEvidence[0].auditHash, /^[a-f0-9]{64}$/); + +const lineage = packet.arbitrations.find((item) => item.predicate === "reuses-protocol"); +assert.equal(lineage.hasConflict, false); +assert.equal(lineage.decision, "accept"); +assert.ok(lineage.supportScore > 1.4); + +const weak = packet.arbitrations.find((item) => item.predicate === "requires-dataset"); +assert.equal(weak.decision, "low-confidence-review"); +assert.equal(weak.leadingEvidence[0].evidenceType, "weak-text-mention"); + +const rec1 = packet.recommendationDecisions.find((item) => item.id === "rec-1"); +assert.equal(rec1.action, "suppress-until-curated"); +const rec2 = packet.recommendationDecisions.find((item) => item.id === "rec-2"); +assert.equal(rec2.action, "allow-with-provenance"); + +assert.equal(relationshipKey(graph.relationships[0]), "method-crispr-prime-editing::improves-model-for::disease-parkinsons"); +assert.ok(evidenceScore(graph.relationships[0], "2026-05-15", 365) > evidenceScore(graph.relationships[4], "2026-05-15", 365)); + +console.log("knowledge-graph-conflict-arbiter tests passed");