From c3423ebc35c3b4d9a81655472ae487acc57d5ecd Mon Sep 17 00:00:00 2001 From: Keon Kim Date: Sun, 31 May 2026 16:14:15 +0900 Subject: [PATCH] Add organism strain boundary guard --- organism-strain-boundary-guard/README.md | 57 ++ organism-strain-boundary-guard/demo.js | 27 + organism-strain-boundary-guard/index.js | 556 ++++++++++++++++++ .../render-video.js | 50 ++ .../reports/demo.mp4 | Bin 0 -> 55290 bytes .../reports/organism-boundary-review.json | 315 ++++++++++ .../reports/organism-boundary-review.md | 93 +++ .../reports/organism-boundary-review.svg | 35 ++ organism-strain-boundary-guard/sample-data.js | 153 +++++ organism-strain-boundary-guard/test.js | 67 +++ 10 files changed, 1353 insertions(+) create mode 100644 organism-strain-boundary-guard/README.md create mode 100644 organism-strain-boundary-guard/demo.js create mode 100644 organism-strain-boundary-guard/index.js create mode 100644 organism-strain-boundary-guard/render-video.js create mode 100644 organism-strain-boundary-guard/reports/demo.mp4 create mode 100644 organism-strain-boundary-guard/reports/organism-boundary-review.json create mode 100644 organism-strain-boundary-guard/reports/organism-boundary-review.md create mode 100644 organism-strain-boundary-guard/reports/organism-boundary-review.svg create mode 100644 organism-strain-boundary-guard/sample-data.js create mode 100644 organism-strain-boundary-guard/test.js diff --git a/organism-strain-boundary-guard/README.md b/organism-strain-boundary-guard/README.md new file mode 100644 index 00000000..db40d3a0 --- /dev/null +++ b/organism-strain-boundary-guard/README.md @@ -0,0 +1,57 @@ +# Organism Strain Boundary Guard + +This module adds a focused organism, strain, cell-line, and host-species +boundary guard for Scientific Knowledge Graph Integration. It protects entity +pages, graph navigation, and AI recommendation paths from unsafe biological +node relationships before they are published. + +## Scope + +The guard validates synthetic knowledge graph packets for: + +- sample-to-organism taxon consistency +- strain-to-parent-species boundaries +- pathogen-to-host role direction and supported host taxon evidence +- cell-line species provenance and culture collection evidence +- mixed-organism sample edges that need contamination or co-culture review +- recommendation paths that depend on suppressed or review-only biological + graph edges + +It emits deterministic edge decisions, recommendation decisions, curator +actions, JSON-LD review packets, Markdown reports, SVG summaries, and a short +H.264 demo video. No credentials, live APIs, private data, or external services +are used. + +## Issue #17 Requirement Mapping + +- Entity extraction and linked data: consumes typed organism, strain, + cell-line, pathogen, sample, and dataset graph nodes and exports a JSON-LD + review packet. +- Knowledge navigation: prevents graph search paths from crossing unsupported + organism or strain boundaries. +- AI research recommendations: suppresses recommendations that rely on + taxon-mismatched, role-inverted, or unresolved mixed-organism graph paths. +- Entity pages and curator workflows: emits per-edge evidence reasons and + remediation actions for graph stewards. + +## Non-Overlap + +This is not a biological accession crosswalk, geospatial sample provenance +guard, measurement harmonization module, temporal-validity guard, evidence +freshness guard, multilingual alias guard, ontology drift migration, or broad +graph ingestion module. It focuses specifically on biological boundary safety +for organism, strain, host, cell-line, and sample graph edges. + +## Validation + +```bash +node organism-strain-boundary-guard/test.js +node organism-strain-boundary-guard/demo.js +node organism-strain-boundary-guard/render-video.js +node --check organism-strain-boundary-guard/index.js +node --check organism-strain-boundary-guard/sample-data.js +node --check organism-strain-boundary-guard/test.js +node --check organism-strain-boundary-guard/demo.js +node --check organism-strain-boundary-guard/render-video.js +git diff --check +``` diff --git a/organism-strain-boundary-guard/demo.js b/organism-strain-boundary-guard/demo.js new file mode 100644 index 00000000..94c79bd8 --- /dev/null +++ b/organism-strain-boundary-guard/demo.js @@ -0,0 +1,27 @@ +const fs = require("fs"); +const path = require("path"); +const graph = require("./sample-data"); +const { + inspectOrganismBoundaryGraph, + renderMarkdownReport, + renderSvgReport, +} = require("./index"); + +const reportDir = path.join(__dirname, "reports"); +fs.mkdirSync(reportDir, { recursive: true }); + +const report = inspectOrganismBoundaryGraph(graph); +const jsonPath = path.join(reportDir, "organism-boundary-review.json"); +const markdownPath = path.join(reportDir, "organism-boundary-review.md"); +const svgPath = path.join(reportDir, "organism-boundary-review.svg"); + +fs.writeFileSync(jsonPath, `${JSON.stringify(report, null, 2)}\n`); +fs.writeFileSync(markdownPath, renderMarkdownReport(report)); +fs.writeFileSync(svgPath, renderSvgReport(report)); + +console.log(`wrote ${path.relative(process.cwd(), jsonPath)}`); +console.log(`wrote ${path.relative(process.cwd(), markdownPath)}`); +console.log(`wrote ${path.relative(process.cwd(), svgPath)}`); +console.log( + `summary: ${report.summary.edgeSuppress} suppressed edges, ${report.summary.edgeReview} review edge` +); diff --git a/organism-strain-boundary-guard/index.js b/organism-strain-boundary-guard/index.js new file mode 100644 index 00000000..45de689a --- /dev/null +++ b/organism-strain-boundary-guard/index.js @@ -0,0 +1,556 @@ +const crypto = require("crypto"); + +function buildLookup(items) { + return new Map((items || []).map((item) => [item.id, item])); +} + +function reason(code, message, evidence) { + return { code, message, evidence }; +} + +function normalizeList(values) { + return Array.isArray(values) ? values.filter(Boolean) : []; +} + +function evidenceItems(edge, evidenceLookup) { + return normalizeList(edge.evidenceIds).map((id) => evidenceLookup.get(id)); +} + +function evidenceHasValue(items, key, value) { + return items.some((item) => item && item[key] === value); +} + +function evidenceListContains(items, key, value) { + return items.some((item) => item && normalizeList(item[key]).includes(value)); +} + +function evaluateSampleOrganism(edge, source, target, evidence) { + const blockers = []; + const warnings = []; + + if (source.type !== "sample" || target.type !== "organism") { + blockers.push( + reason("invalid_sample_organism_roles", "sample_of_organism requires sample to organism", { + sourceType: source.type, + targetType: target.type, + }) + ); + return { blockers, warnings }; + } + + if (source.taxonId !== target.taxonId) { + blockers.push( + reason("sample_taxon_mismatch", "sample taxon does not match organism node", { + sampleTaxonId: source.taxonId, + organismTaxonId: target.taxonId, + }) + ); + } + + if (!source.voucherId || !evidenceHasValue(evidence, "taxonId", target.taxonId)) { + warnings.push( + reason("voucher_evidence_incomplete", "sample organism edge needs voucher evidence", { + voucherId: source.voucherId || "missing", + targetTaxonId: target.taxonId, + }) + ); + } + + return { blockers, warnings }; +} + +function evaluateStrainSpecies(edge, source, target, evidence) { + const blockers = []; + const warnings = []; + + if (source.type !== "strain" || target.type !== "organism") { + blockers.push( + reason("invalid_strain_species_roles", "strain_of_species requires strain to organism", { + sourceType: source.type, + targetType: target.type, + }) + ); + return { blockers, warnings }; + } + + if (source.parentTaxonId !== target.taxonId) { + blockers.push( + reason("strain_parent_taxon_mismatch", "strain parent taxon does not match organism node", { + strainParentTaxonId: source.parentTaxonId, + organismTaxonId: target.taxonId, + }) + ); + } + + if (!evidenceHasValue(evidence, "parentTaxonId", source.parentTaxonId)) { + warnings.push( + reason("strain_registry_evidence_missing", "strain parent evidence is missing", { + strainId: source.id, + }) + ); + } + + return { blockers, warnings }; +} + +function evaluatePathogenHost(edge, source, target, evidence) { + const blockers = []; + const warnings = []; + + if (source.type !== "pathogen" || target.type !== "organism") { + blockers.push( + reason("invalid_pathogen_host_roles", "pathogen_in_host requires pathogen to host organism", { + sourceType: source.type, + targetType: target.type, + }) + ); + return { blockers, warnings }; + } + + const supportedHosts = normalizeList(source.supportedHostTaxonIds); + if (!supportedHosts.includes(target.taxonId)) { + blockers.push( + reason("host_taxon_not_supported", "host organism is not supported by pathogen host evidence", { + pathogenId: source.id, + targetTaxonId: target.taxonId, + supportedHostTaxonIds: supportedHosts, + }) + ); + } + + if (!evidenceListContains(evidence, "supportedHostTaxonIds", target.taxonId)) { + warnings.push( + reason("host_range_evidence_missing", "host edge lacks matching host-range evidence", { + targetTaxonId: target.taxonId, + }) + ); + } + + return { blockers, warnings }; +} + +function evaluateCellLineSpecies(edge, source, target, evidence) { + const blockers = []; + const warnings = []; + + if (source.type !== "cell_line" || target.type !== "organism") { + blockers.push( + reason("invalid_cell_line_species_roles", "cell_line_from_species requires cell line to organism", { + sourceType: source.type, + targetType: target.type, + }) + ); + return { blockers, warnings }; + } + + if (source.speciesTaxonId !== target.taxonId) { + blockers.push( + reason("cell_line_species_mismatch", "cell-line species does not match organism node", { + speciesTaxonId: source.speciesTaxonId, + organismTaxonId: target.taxonId, + }) + ); + } + + if (!source.cultureCollectionId || !evidenceHasValue(evidence, "cultureCollectionId", source.cultureCollectionId)) { + warnings.push( + reason("cell_line_registry_missing", "cell-line edge needs culture collection evidence", { + cultureCollectionId: source.cultureCollectionId || "missing", + }) + ); + } + + return { blockers, warnings }; +} + +function evaluateDatasetSample(edge, source, target, evidence) { + const blockers = []; + const warnings = []; + + if (source.type !== "dataset" || target.type !== "sample") { + blockers.push( + reason("invalid_dataset_sample_roles", "dataset_contains_sample requires dataset to sample", { + sourceType: source.type, + targetType: target.type, + }) + ); + return { blockers, warnings }; + } + + if (!source.doi || !evidenceHasValue(evidence, "doi", source.doi)) { + warnings.push( + reason("dataset_doi_evidence_missing", "dataset sample edge needs DOI evidence", { + doi: source.doi || "missing", + }) + ); + } + + const observedTaxa = normalizeList(target.observedTaxonIds); + if (observedTaxa.length > 1 && !target.contaminationReview) { + warnings.push( + reason("mixed_organism_review_missing", "mixed-organism sample needs contamination or co-culture review", { + sampleId: target.id, + observedTaxonIds: observedTaxa, + }) + ); + } + + return { blockers, warnings }; +} + +function evaluateTypedEdge(edge, source, target, evidence) { + if (edge.type === "sample_of_organism") { + return evaluateSampleOrganism(edge, source, target, evidence); + } + if (edge.type === "strain_of_species") { + return evaluateStrainSpecies(edge, source, target, evidence); + } + if (edge.type === "pathogen_in_host") { + return evaluatePathogenHost(edge, source, target, evidence); + } + if (edge.type === "cell_line_from_species") { + return evaluateCellLineSpecies(edge, source, target, evidence); + } + if (edge.type === "dataset_contains_sample") { + return evaluateDatasetSample(edge, source, target, evidence); + } + return { + blockers: [], + warnings: [ + reason("unhandled_edge_type", "edge type is not handled by this biological boundary guard", { + edgeType: edge.type, + }), + ], + }; +} + +function actionsFor(decision, blockers, warnings) { + if (decision === "allow") { + return ["publish biological graph edge", "allow recommendation paths to use this edge"]; + } + + const codes = new Set([...blockers, ...warnings].map((item) => item.code)); + const actions = []; + + if (codes.has("strain_parent_taxon_mismatch")) { + actions.push("suppress strain edge until parent organism is corrected"); + actions.push("request strain registry evidence from curator"); + } + if (codes.has("host_taxon_not_supported")) { + actions.push("suppress host recommendation path"); + actions.push("request host range evidence or mark as experimental model"); + } + if (codes.has("mixed_organism_review_missing")) { + actions.push("show dataset path only with contamination review notice"); + actions.push("request co-culture or contamination review evidence"); + } + if (codes.has("sample_taxon_mismatch") || codes.has("cell_line_species_mismatch")) { + actions.push("quarantine biological entity page edge"); + } + if (actions.length === 0) { + actions.push("route edge to graph curator review"); + } + + return [...new Set(actions)]; +} + +function evaluateEdge(edge, context) { + const blockers = []; + const warnings = []; + const source = context.entities.get(edge.source); + const target = context.entities.get(edge.target); + const evidence = evidenceItems(edge, context.evidence); + + if (!source) { + blockers.push(reason("missing_source", "edge source node is missing", { source: edge.source })); + } + if (!target) { + blockers.push(reason("missing_target", "edge target node is missing", { target: edge.target })); + } + if (normalizeList(edge.evidenceIds).length === 0) { + warnings.push(reason("missing_evidence_ids", "edge has no evidence identifiers", { edgeId: edge.id })); + } + + for (const id of normalizeList(edge.evidenceIds)) { + if (!context.evidence.has(id)) { + blockers.push(reason("unknown_evidence", "edge references unknown evidence", { evidenceId: id })); + } + } + + if (source && target) { + const typed = evaluateTypedEdge(edge, source, target, evidence); + blockers.push(...typed.blockers); + warnings.push(...typed.warnings); + } + + const decision = + blockers.length > 0 ? "suppress" : warnings.length > 0 ? "curator_review" : "allow"; + + const result = { + edgeId: edge.id, + type: edge.type, + source: edge.source, + target: edge.target, + decision, + blockers, + warnings, + actions: actionsFor(decision, blockers, warnings), + }; + + return { + ...result, + auditDigest: digest(result), + }; +} + +function evaluateRecommendation(recommendation, edgeLookup) { + const blockers = []; + const warnings = []; + const path = []; + + for (const edgeId of normalizeList(recommendation.pathEdgeIds)) { + const edgeDecision = edgeLookup.get(edgeId); + if (!edgeDecision) { + blockers.push(reason("missing_path_edge", "recommendation references missing edge", { edgeId })); + continue; + } + path.push({ + edgeId, + decision: edgeDecision.decision, + auditDigest: edgeDecision.auditDigest, + }); + if (edgeDecision.decision === "suppress") { + blockers.push( + reason("path_contains_suppressed_edge", "recommendation uses a suppressed biological edge", { + edgeId, + }) + ); + } + if (edgeDecision.decision === "curator_review") { + warnings.push( + reason("path_contains_review_edge", "recommendation uses an edge requiring curator review", { + edgeId, + }) + ); + } + } + + const decision = + blockers.length > 0 ? "suppress" : warnings.length > 0 ? "show_with_notice" : "show"; + + const result = { + recommendationId: recommendation.id, + label: recommendation.label, + decision, + path, + blockers, + warnings, + actions: + decision === "show" + ? ["show biological graph recommendation"] + : ["suppress or annotate recommendation until biological boundary review completes"], + }; + + return { + ...result, + auditDigest: digest(result), + }; +} + +function inspectOrganismBoundaryGraph(graph) { + const context = { + entities: buildLookup(graph.entities), + evidence: buildLookup(graph.evidence), + }; + const edgeDecisions = normalizeList(graph.edges).map((edge) => evaluateEdge(edge, context)); + const edgeLookup = new Map(edgeDecisions.map((item) => [item.edgeId, item])); + const recommendationDecisions = normalizeList(graph.recommendations).map((recommendation) => + evaluateRecommendation(recommendation, edgeLookup) + ); + + const summary = { + graphId: graph.graphId, + generatedAt: graph.generatedAt, + entityCount: normalizeList(graph.entities).length, + edgeCount: edgeDecisions.length, + edgeAllow: edgeDecisions.filter((item) => item.decision === "allow").length, + edgeReview: edgeDecisions.filter((item) => item.decision === "curator_review").length, + edgeSuppress: edgeDecisions.filter((item) => item.decision === "suppress").length, + recommendationCount: recommendationDecisions.length, + recommendationShow: recommendationDecisions.filter((item) => item.decision === "show").length, + recommendationNotice: recommendationDecisions.filter((item) => item.decision === "show_with_notice").length, + recommendationSuppress: recommendationDecisions.filter((item) => item.decision === "suppress").length, + }; + + const packet = { + generatedAt: graph.generatedAt, + graphId: graph.graphId, + summary, + edgeDecisions, + recommendationDecisions, + }; + + return { + ...packet, + jsonLd: renderJsonLd(packet), + auditDigest: digest(packet), + }; +} + +function renderJsonLd(report) { + return { + "@context": { + scibase: "https://scibase.ai/schema#", + taxon: "https://www.ncbi.nlm.nih.gov/taxonomy/", + decision: "scibase:decision", + blocker: "scibase:blocker", + warning: "scibase:warning", + }, + "@type": "scibase:OrganismBoundaryReviewPacket", + "@id": `scibase:organism-boundary:${report.graphId}`, + graphId: report.graphId, + generatedAt: report.generatedAt, + summary: report.summary, + edgeDecisions: report.edgeDecisions.map((item) => ({ + "@type": "scibase:OrganismBoundaryEdgeDecision", + edgeId: item.edgeId, + decision: item.decision, + blockers: item.blockers.map((entry) => entry.code), + warnings: item.warnings.map((entry) => entry.code), + auditDigest: item.auditDigest, + })), + recommendationDecisions: report.recommendationDecisions.map((item) => ({ + "@type": "scibase:OrganismBoundaryRecommendationDecision", + recommendationId: item.recommendationId, + decision: item.decision, + blockers: item.blockers.map((entry) => entry.code), + warnings: item.warnings.map((entry) => entry.code), + auditDigest: item.auditDigest, + })), + }; +} + +function renderMarkdownReport(report) { + const lines = [ + "# Organism Strain Boundary Report", + "", + `Generated: ${report.generatedAt}`, + `Graph: ${report.graphId}`, + `Audit digest: ${report.auditDigest}`, + "", + "## Summary", + "", + `- Entities: ${report.summary.entityCount}`, + `- Edges: ${report.summary.edgeCount}`, + `- Edge allow: ${report.summary.edgeAllow}`, + `- Edge curator review: ${report.summary.edgeReview}`, + `- Edge suppress: ${report.summary.edgeSuppress}`, + `- Recommendations: ${report.summary.recommendationCount}`, + `- Recommendation show: ${report.summary.recommendationShow}`, + `- Recommendation show with notice: ${report.summary.recommendationNotice}`, + `- Recommendation suppress: ${report.summary.recommendationSuppress}`, + "", + "## Edge Decisions", + "", + ]; + + for (const item of report.edgeDecisions) { + lines.push(`### ${item.edgeId}`); + lines.push(""); + lines.push(`- Type: ${item.type}`); + lines.push(`- Decision: ${item.decision}`); + lines.push(`- Blockers: ${item.blockers.map((entry) => entry.code).join(", ") || "none"}`); + lines.push(`- Warnings: ${item.warnings.map((entry) => entry.code).join(", ") || "none"}`); + lines.push(`- Actions: ${item.actions.join("; ")}`); + lines.push(`- Digest: ${item.auditDigest}`); + lines.push(""); + } + + lines.push("## Recommendation Decisions"); + lines.push(""); + + for (const item of report.recommendationDecisions) { + lines.push(`### ${item.recommendationId}`); + lines.push(""); + lines.push(`- Label: ${item.label}`); + lines.push(`- Decision: ${item.decision}`); + lines.push(`- Path: ${item.path.map((entry) => `${entry.edgeId}:${entry.decision}`).join(", ")}`); + lines.push(`- Blockers: ${item.blockers.map((entry) => entry.code).join(", ") || "none"}`); + lines.push(`- Warnings: ${item.warnings.map((entry) => entry.code).join(", ") || "none"}`); + lines.push(`- Digest: ${item.auditDigest}`); + lines.push(""); + } + + while (lines[lines.length - 1] === "") { + lines.pop(); + } + + return `${lines.join("\n")}\n`; +} + +function escapeXml(value) { + return String(value) + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """); +} + +function renderSvgReport(report) { + const cards = [ + ["Entities", report.summary.entityCount, "#355c7d"], + ["Edges allowed", report.summary.edgeAllow, "#2a9d8f"], + ["Edges reviewed", report.summary.edgeReview, "#e9c46a"], + ["Edges suppressed", report.summary.edgeSuppress, "#e76f51"], + ["Recommendations shown", report.summary.recommendationShow, "#457b9d"], + ["Recommendations blocked", report.summary.recommendationSuppress, "#b56576"], + ]; + + const cardSvg = cards + .map(([label, value, color], index) => { + const x = 72 + (index % 3) * 380; + const y = 188 + Math.floor(index / 3) * 150; + return [ + ``, + ``, + `${escapeXml(label)}`, + `${value}`, + ].join("\n"); + }) + .join("\n"); + + const curatorRows = report.edgeDecisions + .filter((item) => item.decision !== "allow") + .slice(0, 3) + .map((item, index) => { + const y = 545 + index * 32; + const codes = [...item.blockers, ...item.warnings].map((entry) => entry.code).join(", "); + return `${escapeXml(item.edgeId)} - ${escapeXml(item.decision)} - ${escapeXml(codes)}`; + }) + .join("\n"); + + return ` + + + Organism Strain Boundary Guard + Graph ${escapeXml(report.graphId)} reviewed at ${escapeXml(report.generatedAt)} + ${cardSvg} + Curator queue + ${curatorRows} + Audit digest ${escapeXml(report.auditDigest.slice(0, 32))} +`; +} + +function digest(value) { + return crypto.createHash("sha256").update(JSON.stringify(value)).digest("hex"); +} + +module.exports = { + inspectOrganismBoundaryGraph, + evaluateEdge, + evaluateRecommendation, + renderJsonLd, + renderMarkdownReport, + renderSvgReport, + digest, +}; diff --git a/organism-strain-boundary-guard/render-video.js b/organism-strain-boundary-guard/render-video.js new file mode 100644 index 00000000..23242cf2 --- /dev/null +++ b/organism-strain-boundary-guard/render-video.js @@ -0,0 +1,50 @@ +const fs = require("fs"); +const path = require("path"); +const { execFileSync } = require("child_process"); + +const reportDir = path.join(__dirname, "reports"); +const jsonPath = path.join(reportDir, "organism-boundary-review.json"); +const svgPath = path.join(reportDir, "organism-boundary-review.svg"); +const framePath = path.join(reportDir, "organism-boundary-review.png"); +const outputPath = path.join(reportDir, "demo.mp4"); + +if (!fs.existsSync(jsonPath) || !fs.existsSync(svgPath)) { + require("./demo"); +} + +const report = JSON.parse(fs.readFileSync(jsonPath, "utf8")); + +execFileSync( + "rsvg-convert", + ["--width", "1280", "--height", "720", "--output", framePath, svgPath], + { stdio: "inherit" } +); + +execFileSync( + "ffmpeg", + [ + "-y", + "-loop", + "1", + "-framerate", + "25", + "-i", + framePath, + "-t", + "4", + "-pix_fmt", + "yuv420p", + "-vf", + "scale=1280:720", + "-movflags", + "+faststart", + outputPath, + ], + { stdio: "inherit" } +); + +fs.unlinkSync(framePath); + +console.log( + `wrote ${path.relative(process.cwd(), outputPath)} for ${report.summary.edgeCount} biological edges` +); diff --git a/organism-strain-boundary-guard/reports/demo.mp4 b/organism-strain-boundary-guard/reports/demo.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..f18b45f71a03090d07d78242e5e8e76bef575983 GIT binary patch literal 55290 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 + + + Organism Strain Boundary Guard + Graph scibase-organism-boundary-demo reviewed at 2026-05-31T00:00:00Z + + +Entities +8 + + +Edges allowed +2 + + +Edges reviewed +1 + + +Edges suppressed +2 + + +Recommendations shown +1 + + +Recommendations blocked +1 + Curator queue + edge:balbc-strain-human-parent - suppress - strain_parent_taxon_mismatch +edge:pathogen-mouse-host - suppress - host_taxon_not_supported, host_range_evidence_missing +edge:mixed-sample-dataset - curator_review - mixed_organism_review_missing + Audit digest 56e2969b5599c79308f6540e2f8ca092 + \ No newline at end of file diff --git a/organism-strain-boundary-guard/sample-data.js b/organism-strain-boundary-guard/sample-data.js new file mode 100644 index 00000000..7ca0552f --- /dev/null +++ b/organism-strain-boundary-guard/sample-data.js @@ -0,0 +1,153 @@ +const graph = { + graphId: "scibase-organism-boundary-demo", + generatedAt: "2026-05-31T00:00:00Z", + entities: [ + { + id: "organism:homo-sapiens", + type: "organism", + label: "Homo sapiens", + taxonId: "NCBITaxon:9606", + }, + { + id: "organism:mus-musculus", + type: "organism", + label: "Mus musculus", + taxonId: "NCBITaxon:10090", + }, + { + id: "strain:balb-c", + type: "strain", + label: "BALB/c mouse", + taxonId: "NCBITaxon:10090-BALBC", + parentTaxonId: "NCBITaxon:10090", + aliasEvidenceIds: ["ev:jax-strain-registry"], + }, + { + id: "pathogen:sars-cov-2", + type: "pathogen", + label: "SARS-CoV-2 isolate SCB-31", + taxonId: "NCBITaxon:2697049", + supportedHostTaxonIds: ["NCBITaxon:9606", "NCBITaxon:9544"], + }, + { + id: "cell-line:hela", + type: "cell_line", + label: "HeLa", + speciesTaxonId: "NCBITaxon:9606", + cultureCollectionId: "ATCC:CCL-2", + }, + { + id: "sample:human-lung-01", + type: "sample", + label: "Human lung biopsy 01", + taxonId: "NCBITaxon:9606", + voucherId: "SCB-VCH-001", + observedTaxonIds: ["NCBITaxon:9606"], + }, + { + id: "sample:mixed-airway-02", + type: "sample", + label: "Mixed airway culture 02", + taxonId: "NCBITaxon:9606", + voucherId: "SCB-VCH-044", + observedTaxonIds: ["NCBITaxon:9606", "NCBITaxon:2697049"], + contaminationReview: false, + }, + { + id: "dataset:airway-atlas", + type: "dataset", + label: "Airway infection atlas", + doi: "10.5555/scibase.airway", + }, + ], + evidence: [ + { + id: "ev:human-voucher", + type: "voucher", + label: "Human specimen voucher SCB-VCH-001", + taxonId: "NCBITaxon:9606", + }, + { + id: "ev:jax-strain-registry", + type: "strain_registry", + label: "JAX BALB/c strain registry", + taxonId: "NCBITaxon:10090-BALBC", + parentTaxonId: "NCBITaxon:10090", + }, + { + id: "ev:host-range-review", + type: "host_range", + label: "Curated SARS-CoV-2 host range", + supportedHostTaxonIds: ["NCBITaxon:9606", "NCBITaxon:9544"], + }, + { + id: "ev:hela-atcc", + type: "cell_line_registry", + label: "ATCC HeLa registry", + speciesTaxonId: "NCBITaxon:9606", + cultureCollectionId: "ATCC:CCL-2", + }, + { + id: "ev:dataset-doi", + type: "dataset_doi", + label: "Dataset DOI landing page", + doi: "10.5555/scibase.airway", + }, + ], + edges: [ + { + id: "edge:human-sample-organism", + type: "sample_of_organism", + source: "sample:human-lung-01", + target: "organism:homo-sapiens", + evidenceIds: ["ev:human-voucher"], + }, + { + id: "edge:balbc-strain-human-parent", + type: "strain_of_species", + source: "strain:balb-c", + target: "organism:homo-sapiens", + evidenceIds: ["ev:jax-strain-registry"], + }, + { + id: "edge:pathogen-mouse-host", + type: "pathogen_in_host", + source: "pathogen:sars-cov-2", + target: "organism:mus-musculus", + evidenceIds: ["ev:host-range-review"], + }, + { + id: "edge:hela-human-cell-line", + type: "cell_line_from_species", + source: "cell-line:hela", + target: "organism:homo-sapiens", + evidenceIds: ["ev:hela-atcc"], + }, + { + id: "edge:mixed-sample-dataset", + type: "dataset_contains_sample", + source: "dataset:airway-atlas", + target: "sample:mixed-airway-02", + evidenceIds: ["ev:dataset-doi"], + }, + ], + recommendations: [ + { + id: "rec:human-cell-line-context", + label: "Show human sample and HeLa context on entity page", + pathEdgeIds: ["edge:human-sample-organism", "edge:hela-human-cell-line"], + }, + { + id: "rec:bad-strain-parent", + label: "Recommend BALB/c as human organism evidence", + pathEdgeIds: ["edge:balbc-strain-human-parent"], + }, + { + id: "rec:mixed-sample-notice", + label: "Recommend mixed airway culture with contamination notice", + pathEdgeIds: ["edge:mixed-sample-dataset"], + }, + ], +}; + +module.exports = graph; diff --git a/organism-strain-boundary-guard/test.js b/organism-strain-boundary-guard/test.js new file mode 100644 index 00000000..acf0e691 --- /dev/null +++ b/organism-strain-boundary-guard/test.js @@ -0,0 +1,67 @@ +const assert = require("assert"); +const graph = require("./sample-data"); +const { inspectOrganismBoundaryGraph } = require("./index"); + +const report = inspectOrganismBoundaryGraph(graph); + +function edge(id) { + return report.edgeDecisions.find((item) => item.edgeId === id); +} + +function recommendation(id) { + return report.recommendationDecisions.find((item) => item.recommendationId === id); +} + +assert.strictEqual(report.summary.edgeCount, 5); +assert.strictEqual(report.summary.edgeAllow, 2); +assert.strictEqual(report.summary.edgeReview, 1); +assert.strictEqual(report.summary.edgeSuppress, 2); +assert.strictEqual(report.summary.recommendationCount, 3); +assert.strictEqual(report.summary.recommendationShow, 1); +assert.strictEqual(report.summary.recommendationNotice, 1); +assert.strictEqual(report.summary.recommendationSuppress, 1); + +assert.strictEqual(edge("edge:human-sample-organism").decision, "allow"); +assert.strictEqual(edge("edge:hela-human-cell-line").decision, "allow"); + +const strainEdge = edge("edge:balbc-strain-human-parent"); +assert.strictEqual(strainEdge.decision, "suppress"); +assert( + strainEdge.blockers.some((item) => item.code === "strain_parent_taxon_mismatch"), + "BALB/c strain must not map to human parent organism" +); + +const hostEdge = edge("edge:pathogen-mouse-host"); +assert.strictEqual(hostEdge.decision, "suppress"); +assert( + hostEdge.blockers.some((item) => item.code === "host_taxon_not_supported"), + "unsupported pathogen host taxon must suppress the edge" +); + +const mixedEdge = edge("edge:mixed-sample-dataset"); +assert.strictEqual(mixedEdge.decision, "curator_review"); +assert( + mixedEdge.warnings.some((item) => item.code === "mixed_organism_review_missing"), + "mixed-organism sample should require contamination or co-culture review" +); + +assert.strictEqual(recommendation("rec:human-cell-line-context").decision, "show"); +assert.strictEqual(recommendation("rec:bad-strain-parent").decision, "suppress"); +assert( + recommendation("rec:bad-strain-parent").blockers.some( + (item) => item.code === "path_contains_suppressed_edge" + ), + "recommendations using suppressed biological edges should be blocked" +); +assert.strictEqual(recommendation("rec:mixed-sample-notice").decision, "show_with_notice"); +assert( + recommendation("rec:mixed-sample-notice").warnings.some( + (item) => item.code === "path_contains_review_edge" + ), + "review-only biological edges should surface recommendation notices" +); + +assert.strictEqual(report.jsonLd["@type"], "scibase:OrganismBoundaryReviewPacket"); +assert.match(report.auditDigest, /^[a-f0-9]{64}$/); + +console.log("organism strain boundary guard tests passed");