Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions systematic-review-screening-drift-assistant/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Systematic Review Screening Drift Assistant

This self-contained module adds a deterministic AI research-assistant guard for systematic review screening packets. It is scoped to SCIBASE issue #16, the AI-Powered Research Assistant Suite, and focuses on whether screening decisions, exclusion rationales, and research-gap prompts are safe to surface to researchers.

The assistant does not call external APIs, payment systems, live review platforms, or private data stores. All fixtures are synthetic and all checks run with Node built-ins.

## What It Checks

- Locked eligibility criteria versions and complete PICO plus study-design fields.
- Dual independent review at title/abstract and full-text stages.
- Unresolved reviewer conflicts before assistant output release.
- Structured exclusion reasons from an approved taxonomy.
- Full-text retrieval evidence, locators, and content hashes.
- Duplicate-cluster canonical record selection.
- AI recommendations limited to assist-only authority with human approval gates.
- Private reviewer notes kept out of assistant context and generated gap prompts.
- Research-gap prompts backed by enough screened-study evidence and limitation signals.

## Local Validation

```sh
npm --prefix systematic-review-screening-drift-assistant run check
npm --prefix systematic-review-screening-drift-assistant test
npm --prefix systematic-review-screening-drift-assistant run demo
npm --prefix systematic-review-screening-drift-assistant run make-demo-video
npm --prefix systematic-review-screening-drift-assistant run verify-video
```

## Generated Artifacts

Running the demo writes:

- `reports/clean-screening-report.json`
- `reports/risky-screening-report.json`
- `reports/risky-screening-handoff.md`
- `reports/screening-dashboard.svg`
- `reports/demo.mp4`

The risky packet intentionally demonstrates release blockers: criteria-version drift, missing criteria fields, stale search evidence, broad AI action authority, missing human approval, missing dual review, unresolved full-text conflict, missing full-text retrieval, invalid exclusion reason, missing exclusion evidence, private note leakage, and under-evidenced gap prompts.

## Issue Fit

This is a distinct AI-powered research assistant slice. It complements the broad assistant suite, evidence binder, structured abstract checker, external-validity transfer assistant, geospatial assistant, prompt-safety guard, omics review assistants, and generic peer-review generators by focusing specifically on systematic review screening integrity and exclusion-rationale drift.
85 changes: 85 additions & 0 deletions systematic-review-screening-drift-assistant/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
const fs = require("node:fs");
const path = require("node:path");
const { evaluateSystematicReviewScreening } = require("./index");
const { cleanPacket, riskyPacket } = require("./sample-data");

const reportsDir = path.join(__dirname, "reports");
fs.mkdirSync(reportsDir, { recursive: true });

const clean = evaluateSystematicReviewScreening(cleanPacket);
const risky = evaluateSystematicReviewScreening(riskyPacket);

function writeJson(name, value) {
fs.writeFileSync(path.join(reportsDir, name), `${JSON.stringify(value, null, 2)}\n`);
}

function escapeXml(value) {
return String(value)
.replaceAll("&", "&")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll("\"", "&quot;");
}

function makeMarkdownReport(report) {
const lines = [
`# Screening Assistant Handoff: ${report.summary.reviewId}`,
"",
`Decision: ${report.summary.decision}`,
`Findings: ${report.summary.findingCount}`,
`High or critical findings: ${report.summary.highOrCriticalFindings}`,
`Audit digest: ${report.summary.auditDigest}`,
"",
"## Required Actions"
];

if (report.recommendations.length === 0) {
lines.push("- No remediation required.");
} else {
for (const recommendation of report.recommendations) {
lines.push(`- ${recommendation}`);
}
}

lines.push("", "## Top Findings");
for (const finding of report.findings.slice(0, 8)) {
lines.push(`- ${finding.severity.toUpperCase()} ${finding.code}: ${finding.message}`);
}

return `${lines.join("\n")}\n`;
}

function makeSvg(cleanReport, riskyReport) {
const cleanWidth = 280;
const riskyWidth = Math.min(560, 36 * riskyReport.summary.findingCount);
const criticalWidth = Math.min(560, 72 * riskyReport.summary.criticalFindings);
return `<svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540">
<rect width="960" height="540" fill="#f8fafc"/>
<rect x="58" y="52" width="844" height="436" rx="6" fill="#ffffff" stroke="#cbd5e1"/>
<rect x="58" y="52" width="844" height="10" fill="#0f172a"/>
<text x="92" y="108" font-family="Arial" font-size="28" fill="#0f172a">Systematic Review Screening Drift Assistant</text>
<text x="92" y="144" font-family="Arial" font-size="15" fill="#475569">Eligibility version locks, dual review, exclusion evidence, AI authority, and gap prompt safety.</text>
<text x="92" y="204" font-family="Arial" font-size="17" fill="#0f172a">Clean packet</text>
<rect x="92" y="222" width="560" height="34" fill="#e2e8f0"/>
<rect x="92" y="222" width="${cleanWidth}" height="34" fill="#10b981"/>
<text x="670" y="245" font-family="Arial" font-size="15" fill="#166534">${escapeXml(cleanReport.summary.decision)}</text>
<text x="92" y="304" font-family="Arial" font-size="17" fill="#0f172a">Risky packet findings</text>
<rect x="92" y="322" width="560" height="34" fill="#e2e8f0"/>
<rect x="92" y="322" width="${riskyWidth}" height="34" fill="#ef4444"/>
<text x="670" y="345" font-family="Arial" font-size="15" fill="#991b1b">${riskyReport.summary.findingCount} findings</text>
<text x="92" y="396" font-family="Arial" font-size="17" fill="#0f172a">Critical release blockers</text>
<rect x="92" y="414" width="560" height="34" fill="#e2e8f0"/>
<rect x="92" y="414" width="${criticalWidth}" height="34" fill="#7f1d1d"/>
<text x="670" y="437" font-family="Arial" font-size="15" fill="#7f1d1d">${riskyReport.summary.criticalFindings} critical</text>
</svg>`;
}

writeJson("clean-screening-report.json", clean);
writeJson("risky-screening-report.json", risky);
fs.writeFileSync(path.join(reportsDir, "risky-screening-handoff.md"), makeMarkdownReport(risky));
fs.writeFileSync(path.join(reportsDir, "screening-dashboard.svg"), makeSvg(clean, risky));

console.log("Clean decision:", clean.summary.decision);
console.log("Risky decision:", risky.summary.decision);
console.log("Risky finding count:", risky.summary.findingCount);
console.log(`Reports written to ${reportsDir}`);
Loading