From 49ac6958d7ffba449b9bf01a1a683203e7edd559 Mon Sep 17 00:00:00 2001 From: Test User Date: Sat, 23 May 2026 02:57:09 +0200 Subject: [PATCH] Add literature freshness review assistant --- README.md | 4 + .../README.md | 33 ++ literature-freshness-review-assistant/demo.js | 28 ++ .../index.js | 322 ++++++++++++++++++ .../package.json | 13 + .../reports/demo.json | 217 ++++++++++++ .../reports/demo.md | 46 +++ .../reports/demo.mp4 | Bin 0 -> 43703 bytes .../reports/demo.svg | 34 ++ .../sample-data.js | 144 ++++++++ .../scripts/render-demo-video.js | 89 +++++ literature-freshness-review-assistant/test.js | 47 +++ 12 files changed, 977 insertions(+) create mode 100644 literature-freshness-review-assistant/README.md create mode 100644 literature-freshness-review-assistant/demo.js create mode 100644 literature-freshness-review-assistant/index.js create mode 100644 literature-freshness-review-assistant/package.json create mode 100644 literature-freshness-review-assistant/reports/demo.json create mode 100644 literature-freshness-review-assistant/reports/demo.md create mode 100644 literature-freshness-review-assistant/reports/demo.mp4 create mode 100644 literature-freshness-review-assistant/reports/demo.svg create mode 100644 literature-freshness-review-assistant/sample-data.js create mode 100644 literature-freshness-review-assistant/scripts/render-demo-video.js create mode 100644 literature-freshness-review-assistant/test.js diff --git a/README.md b/README.md index d338cf68..450f344b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # deepevents.ai deepevents.ai main codebase + +## Research Assistant Bounty Slices + +- [Literature freshness review assistant](literature-freshness-review-assistant/README.md) diff --git a/literature-freshness-review-assistant/README.md b/literature-freshness-review-assistant/README.md new file mode 100644 index 00000000..33fcbbe2 --- /dev/null +++ b/literature-freshness-review-assistant/README.md @@ -0,0 +1,33 @@ +# Literature Freshness Review Assistant + +This is a focused AI-Powered Research Assistant Suite slice for SCIBASE issue #16. It checks whether reviewer-facing manuscript claims rely on current evidence before the assistant marks a packet ready. + +## Scope + +- Flags stale support citations outside a configurable freshness window. +- Detects newer systematic reviews, reporting guidelines, dataset releases, and benchmark updates that are missing from a claim. +- Escalates newer contradictory evidence as a review hold. +- Detects dataset and benchmark version drift for claims using "current" or "state-of-the-art" wording. +- Emits reviewer actions, research-gap prompts, stable audit digests, and deterministic JSON/Markdown/SVG/MP4 artifacts. + +This is intentionally separate from broad assistant suites, citation-context reconciliation, retraction notices, statistics review, benchmark-leakage checks, reporting-guideline compliance, figure/table checks, prompt-safety guards, and assay control/calibration completeness. + +## Files + +- `index.js` - freshness evaluator and report renderers +- `sample-data.js` - synthetic evidence ledger and manuscript packets +- `test.js` - dependency-free assertion tests +- `demo.js` - deterministic JSON/Markdown/SVG report generator +- `scripts/render-demo-video.js` - optional ffmpeg MP4 renderer +- `reports/` - generated reviewer artifacts + +## Validate + +```bash +npm run check +npm test +npm run demo +npm run demo:video +``` + +Synthetic data only. The module does not call external APIs, scan private manuscripts, use credentials, or contact payment services. diff --git a/literature-freshness-review-assistant/demo.js b/literature-freshness-review-assistant/demo.js new file mode 100644 index 00000000..4c437104 --- /dev/null +++ b/literature-freshness-review-assistant/demo.js @@ -0,0 +1,28 @@ +const fs = require("fs"); +const path = require("path"); + +const { + evaluateFreshnessReview, + renderMarkdown, + renderSvg +} = require("./index"); +const { + freshnessPolicy, + evidenceLedger, + manuscriptPackets +} = require("./sample-data"); + +const reportsDir = path.join(__dirname, "reports"); +fs.mkdirSync(reportsDir, { recursive: true }); + +const report = evaluateFreshnessReview(manuscriptPackets, evidenceLedger, freshnessPolicy); +fs.writeFileSync(path.join(reportsDir, "demo.json"), `${JSON.stringify(report, null, 2)}\n`); +fs.writeFileSync(path.join(reportsDir, "demo.md"), renderMarkdown(report)); +fs.writeFileSync(path.join(reportsDir, "demo.svg"), renderSvg(report)); + +console.log(`status=${report.manuscripts[0].summary.status}`); +console.log(`reviewed=${report.aggregate.reviewedManuscripts}`); +console.log(`held=${report.aggregate.heldManuscripts}`); +console.log(`critical=${report.aggregate.criticalFindings}`); +console.log(`major=${report.aggregate.majorFindings}`); +console.log(`digest=${report.auditDigest}`); diff --git a/literature-freshness-review-assistant/index.js b/literature-freshness-review-assistant/index.js new file mode 100644 index 00000000..c0f3d6b4 --- /dev/null +++ b/literature-freshness-review-assistant/index.js @@ -0,0 +1,322 @@ +const crypto = require("crypto"); + +const SEVERITY_RANK = { + info: 0, + warning: 1, + major: 2, + critical: 3 +}; + +function yearsBetween(olderDate, newerDate) { + const older = new Date(`${olderDate}T00:00:00Z`); + const newer = new Date(`${newerDate}T00:00:00Z`); + return (newer.getTime() - older.getTime()) / (365.25 * 24 * 60 * 60 * 1000); +} + +function latestDate(dates) { + const validDates = dates.filter(Boolean).sort(); + return validDates[validDates.length - 1] || null; +} + +function evidenceForTopic(ledger, topic) { + return ledger.signals.filter((signal) => signal.topic === topic); +} + +function addFinding(findings, finding) { + findings.push({ + severity: finding.severity, + code: finding.code, + claimId: finding.claimId, + topic: finding.topic, + detail: finding.detail, + requiredAction: finding.requiredAction, + evidenceSignalId: finding.evidenceSignalId || null + }); +} + +function evaluateClaimFreshness(claim, ledger, policy) { + const findings = []; + const signals = evidenceForTopic(ledger, claim.topic); + const latestCitation = latestDate(claim.citationDates || []); + const reviewDate = policy.reviewDate; + + if (!latestCitation) { + addFinding(findings, { + severity: "critical", + code: "NO_SUPPORT_DATES", + claimId: claim.id, + topic: claim.topic, + detail: "Claim has no dated supporting citations for freshness review.", + requiredAction: "Attach dated evidence before the assistant marks this claim reviewer-ready." + }); + } else if (yearsBetween(latestCitation, reviewDate) > policy.staleAfterYears) { + addFinding(findings, { + severity: "major", + code: "STALE_SUPPORT_WINDOW", + claimId: claim.id, + topic: claim.topic, + detail: `Latest support is ${latestCitation}, older than the ${policy.staleAfterYears}-year freshness window.`, + requiredAction: "Add recent evidence or downgrade the claim wording." + }); + } + + for (const signal of signals) { + const cited = (claim.citedCurrentSignalIds || []).includes(signal.id); + const signalIsNewer = !latestCitation || signal.published > latestCitation; + + if (!cited && signalIsNewer) { + const severity = signal.relation === "contradicts" ? policy.contradictionSeverity : "major"; + addFinding(findings, { + severity, + code: signal.relation === "contradicts" ? "NEWER_CONTRADICTORY_EVIDENCE" : "MISSING_NEWER_EVIDENCE", + claimId: claim.id, + topic: claim.topic, + detail: `${signal.title} (${signal.published}) ${signal.relation} older support but is not cited.`, + requiredAction: signal.requirement, + evidenceSignalId: signal.id + }); + } + + if (signal.currentVersion && claim.datasetVersion && claim.datasetVersion !== signal.currentVersion) { + addFinding(findings, { + severity: "major", + code: "DATASET_VERSION_DRIFT", + claimId: claim.id, + topic: claim.topic, + detail: `Claim uses dataset ${claim.datasetVersion}; current ledger version is ${signal.currentVersion}.`, + requiredAction: signal.requirement, + evidenceSignalId: signal.id + }); + } + + if (signal.currentVersion && claim.benchmarkVersion && claim.benchmarkVersion !== signal.currentVersion) { + addFinding(findings, { + severity: "major", + code: "BENCHMARK_VERSION_DRIFT", + claimId: claim.id, + topic: claim.topic, + detail: `Claim uses benchmark ${claim.benchmarkVersion}; current ledger version is ${signal.currentVersion}.`, + requiredAction: signal.requirement, + evidenceSignalId: signal.id + }); + } + } + + if (findings.length === 0) { + addFinding(findings, { + severity: "info", + code: "FRESHNESS_READY", + claimId: claim.id, + topic: claim.topic, + detail: "Claim cites the current evidence signal for its topic.", + requiredAction: "No freshness hold required." + }); + } + + return findings; +} + +function summarizeFindings(findings, policy) { + const actionable = findings.filter((finding) => finding.severity !== "info"); + const critical = findings.filter((finding) => finding.severity === "critical"); + const major = findings.filter((finding) => finding.severity === "major"); + const warnings = findings.filter((finding) => finding.severity === "warning"); + const maxSeverity = findings.reduce((max, finding) => { + return SEVERITY_RANK[finding.severity] > SEVERITY_RANK[max] ? finding.severity : max; + }, "info"); + const status = SEVERITY_RANK[maxSeverity] >= SEVERITY_RANK[policy.holdSeverityCutoff] + ? "hold_freshness_review" + : actionable.length > 0 + ? "revise_freshness_context" + : "ready_for_review"; + + return { + status, + totalFindings: findings.length, + actionableFindings: actionable.length, + criticalFindings: critical.length, + majorFindings: major.length, + warningFindings: warnings.length, + readyFindings: findings.length - actionable.length + }; +} + +function buildReviewerActions(findings) { + return findings + .filter((finding) => finding.severity !== "info") + .map((finding) => ({ + claimId: finding.claimId, + severity: finding.severity, + action: finding.requiredAction, + reason: finding.detail + })); +} + +function buildResearchGapPrompts(findings) { + const topics = new Map(); + for (const finding of findings) { + if (finding.severity === "info") { + continue; + } + if (!topics.has(finding.topic)) { + topics.set( + finding.topic, + `Freshness gap: update the evidence base for ${finding.topic} before treating the claim as current.` + ); + } + } + return [...topics.values()]; +} + +function stableDigest(value) { + return crypto.createHash("sha256").update(JSON.stringify(value)).digest("hex"); +} + +function evaluateManuscript(packet, ledger, policy) { + const findings = packet.claims.flatMap((claim) => evaluateClaimFreshness(claim, ledger, policy)); + const summary = summarizeFindings(findings, policy); + const reviewerActions = buildReviewerActions(findings); + const researchGapPrompts = buildResearchGapPrompts(findings); + const result = { + packetId: packet.id, + title: packet.title, + domain: packet.domain, + reviewDate: policy.reviewDate, + summary, + findings, + reviewerActions, + researchGapPrompts + }; + return { + ...result, + auditDigest: stableDigest(result) + }; +} + +function evaluateFreshnessReview(packets, ledger, policy) { + const manuscripts = packets.map((packet) => evaluateManuscript(packet, ledger, policy)); + const aggregate = { + reviewedManuscripts: manuscripts.length, + heldManuscripts: manuscripts.filter((entry) => entry.summary.status === "hold_freshness_review").length, + revisionManuscripts: manuscripts.filter((entry) => entry.summary.status === "revise_freshness_context").length, + readyManuscripts: manuscripts.filter((entry) => entry.summary.status === "ready_for_review").length, + criticalFindings: manuscripts.reduce((sum, entry) => sum + entry.summary.criticalFindings, 0), + majorFindings: manuscripts.reduce((sum, entry) => sum + entry.summary.majorFindings, 0), + actionableFindings: manuscripts.reduce((sum, entry) => sum + entry.summary.actionableFindings, 0) + }; + const result = { + assistant: "literature-freshness-review-assistant", + policy, + aggregate, + manuscripts + }; + return { + ...result, + auditDigest: stableDigest(result) + }; +} + +function renderMarkdown(report) { + const lines = [ + "# Literature Freshness Review Assistant", + "", + `Review date: ${report.policy.reviewDate}`, + `Overall digest: ${report.auditDigest}`, + "", + "## Aggregate", + "", + `- Reviewed manuscripts: ${report.aggregate.reviewedManuscripts}`, + `- Held manuscripts: ${report.aggregate.heldManuscripts}`, + `- Revision manuscripts: ${report.aggregate.revisionManuscripts}`, + `- Ready manuscripts: ${report.aggregate.readyManuscripts}`, + `- Critical findings: ${report.aggregate.criticalFindings}`, + `- Major findings: ${report.aggregate.majorFindings}`, + `- Actionable findings: ${report.aggregate.actionableFindings}`, + "" + ]; + + for (const manuscript of report.manuscripts) { + lines.push(`## ${manuscript.title}`); + lines.push(""); + lines.push(`Status: ${manuscript.summary.status}`); + lines.push(`Digest: ${manuscript.auditDigest}`); + lines.push(""); + lines.push("| Severity | Code | Claim | Detail | Required action |"); + lines.push("| --- | --- | --- | --- | --- |"); + for (const finding of manuscript.findings) { + lines.push( + `| ${finding.severity} | ${finding.code} | ${finding.claimId} | ${finding.detail} | ${finding.requiredAction} |` + ); + } + lines.push(""); + if (manuscript.researchGapPrompts.length > 0) { + lines.push("Research gap prompts:"); + for (const prompt of manuscript.researchGapPrompts) { + lines.push(`- ${prompt}`); + } + lines.push(""); + } + } + + return `${lines.join("\n").trimEnd()}\n`; +} + +function escapeXml(value) { + return String(value) + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """); +} + +function renderSvg(report) { + const held = report.aggregate.heldManuscripts; + const ready = report.aggregate.readyManuscripts; + const critical = report.aggregate.criticalFindings; + const major = report.aggregate.majorFindings; + const bars = [ + { label: "Held", value: held, color: "#b91c1c" }, + { label: "Ready", value: ready, color: "#047857" }, + { label: "Critical", value: critical, color: "#dc2626" }, + { label: "Major", value: major, color: "#d97706" } + ]; + const maxValue = Math.max(1, ...bars.map((bar) => bar.value)); + const rows = bars.map((bar, index) => { + const y = 170 + index * 70; + const width = Math.round((bar.value / maxValue) * 560); + return ` ${escapeXml(bar.label)} + + + ${bar.value}`; + }).join("\n"); + + return ` + Literature freshness review assistant summary + Summary of held manuscripts, ready manuscripts, and freshness findings. + + + + Literature freshness review assistant + Stale citations, missing newer evidence, and temporal drift before AI review release +${rows} + Audit digest: ${escapeXml(report.auditDigest.slice(0, 24))} + +`; +} + +module.exports = { + evaluateClaimFreshness, + evaluateManuscript, + evaluateFreshnessReview, + renderMarkdown, + renderSvg, + stableDigest +}; diff --git a/literature-freshness-review-assistant/package.json b/literature-freshness-review-assistant/package.json new file mode 100644 index 00000000..c867f4ce --- /dev/null +++ b/literature-freshness-review-assistant/package.json @@ -0,0 +1,13 @@ +{ + "name": "literature-freshness-review-assistant", + "version": "1.0.0", + "description": "Freshness-focused AI research assistant review slice for SCIBASE issue #16.", + "main": "index.js", + "scripts": { + "check": "node --check index.js && node --check sample-data.js && node --check test.js && node --check demo.js && node --check scripts/render-demo-video.js", + "test": "node test.js", + "demo": "node demo.js", + "demo:video": "node scripts/render-demo-video.js" + }, + "license": "MIT" +} diff --git a/literature-freshness-review-assistant/reports/demo.json b/literature-freshness-review-assistant/reports/demo.json new file mode 100644 index 00000000..917be5be --- /dev/null +++ b/literature-freshness-review-assistant/reports/demo.json @@ -0,0 +1,217 @@ +{ + "assistant": "literature-freshness-review-assistant", + "policy": { + "reviewDate": "2026-05-23", + "staleAfterYears": 4, + "contradictionSeverity": "critical", + "requiredEvidenceTypes": [ + "systematic_review", + "guideline", + "dataset_release", + "benchmark_update" + ], + "holdSeverityCutoff": "critical" + }, + "aggregate": { + "reviewedManuscripts": 2, + "heldManuscripts": 1, + "revisionManuscripts": 0, + "readyManuscripts": 1, + "criticalFindings": 1, + "majorFindings": 8, + "actionableFindings": 9 + }, + "manuscripts": [ + { + "packetId": "ms-hold-senolytic-review", + "title": "Senolytic cardiac safety manuscript", + "domain": "translational cardiometabolic biology", + "reviewDate": "2026-05-23", + "summary": { + "status": "hold_freshness_review", + "totalFindings": 9, + "actionableFindings": 9, + "criticalFindings": 1, + "majorFindings": 8, + "warningFindings": 0, + "readyFindings": 0 + }, + "findings": [ + { + "severity": "major", + "code": "STALE_SUPPORT_WINDOW", + "claimId": "claim-tolerability", + "topic": "senolytic cardiometabolic safety", + "detail": "Latest support is 2021-06-01, older than the 4-year freshness window.", + "requiredAction": "Add recent evidence or downgrade the claim wording.", + "evidenceSignalId": null + }, + { + "severity": "critical", + "code": "NEWER_CONTRADICTORY_EVIDENCE", + "claimId": "claim-tolerability", + "topic": "senolytic cardiometabolic safety", + "detail": "2025 systematic review of senolytic cardiometabolic safety (2025-11-18) contradicts older support but is not cited.", + "requiredAction": "Discuss the larger safety signal before claiming broad tolerability.", + "evidenceSignalId": "ev-sr-2025-senolytic-cardiometabolic" + }, + { + "severity": "major", + "code": "STALE_SUPPORT_WINDOW", + "claimId": "claim-organoid-methods", + "topic": "organoid assay reporting", + "detail": "Latest support is 2022-02-10, older than the 4-year freshness window.", + "requiredAction": "Add recent evidence or downgrade the claim wording.", + "evidenceSignalId": null + }, + { + "severity": "major", + "code": "MISSING_NEWER_EVIDENCE", + "claimId": "claim-organoid-methods", + "topic": "organoid assay reporting", + "detail": "SCIBASE organoid reporting guidance 2026.1 (2026-02-04) updates older support but is not cited.", + "requiredAction": "Use the 2026.1 organoid reporting checklist for reviewer packets.", + "evidenceSignalId": "ev-guide-2026-organoid-reporting" + }, + { + "severity": "major", + "code": "STALE_SUPPORT_WINDOW", + "claimId": "claim-atlas-normalization", + "topic": "single-cell atlas normalization", + "detail": "Latest support is 2021-12-12, older than the 4-year freshness window.", + "requiredAction": "Add recent evidence or downgrade the claim wording.", + "evidenceSignalId": null + }, + { + "severity": "major", + "code": "MISSING_NEWER_EVIDENCE", + "claimId": "claim-atlas-normalization", + "topic": "single-cell atlas normalization", + "detail": "Single Cell Atlas v4.0 release notes (2026-03-14) supersedes older support but is not cited.", + "requiredAction": "Reconcile claims trained on atlas v2.x or v3.x against v4.0 annotations.", + "evidenceSignalId": "ev-data-2026-single-cell-atlas-v4" + }, + { + "severity": "major", + "code": "DATASET_VERSION_DRIFT", + "claimId": "claim-atlas-normalization", + "topic": "single-cell atlas normalization", + "detail": "Claim uses dataset v2.1; current ledger version is v4.0.", + "requiredAction": "Reconcile claims trained on atlas v2.x or v3.x against v4.0 annotations.", + "evidenceSignalId": "ev-data-2026-single-cell-atlas-v4" + }, + { + "severity": "major", + "code": "MISSING_NEWER_EVIDENCE", + "claimId": "claim-protein-benchmark", + "topic": "protein structure benchmark", + "detail": "Protein structure benchmark leaderboard recalibration (2026-01-22) updates older support but is not cited.", + "requiredAction": "Refresh benchmark claims against the recalibrated 2026.01 leaderboard.", + "evidenceSignalId": "ev-bench-2026-protein-leaderboard" + }, + { + "severity": "major", + "code": "BENCHMARK_VERSION_DRIFT", + "claimId": "claim-protein-benchmark", + "topic": "protein structure benchmark", + "detail": "Claim uses benchmark 2024.10; current ledger version is 2026.01.", + "requiredAction": "Refresh benchmark claims against the recalibrated 2026.01 leaderboard.", + "evidenceSignalId": "ev-bench-2026-protein-leaderboard" + } + ], + "reviewerActions": [ + { + "claimId": "claim-tolerability", + "severity": "major", + "action": "Add recent evidence or downgrade the claim wording.", + "reason": "Latest support is 2021-06-01, older than the 4-year freshness window." + }, + { + "claimId": "claim-tolerability", + "severity": "critical", + "action": "Discuss the larger safety signal before claiming broad tolerability.", + "reason": "2025 systematic review of senolytic cardiometabolic safety (2025-11-18) contradicts older support but is not cited." + }, + { + "claimId": "claim-organoid-methods", + "severity": "major", + "action": "Add recent evidence or downgrade the claim wording.", + "reason": "Latest support is 2022-02-10, older than the 4-year freshness window." + }, + { + "claimId": "claim-organoid-methods", + "severity": "major", + "action": "Use the 2026.1 organoid reporting checklist for reviewer packets.", + "reason": "SCIBASE organoid reporting guidance 2026.1 (2026-02-04) updates older support but is not cited." + }, + { + "claimId": "claim-atlas-normalization", + "severity": "major", + "action": "Add recent evidence or downgrade the claim wording.", + "reason": "Latest support is 2021-12-12, older than the 4-year freshness window." + }, + { + "claimId": "claim-atlas-normalization", + "severity": "major", + "action": "Reconcile claims trained on atlas v2.x or v3.x against v4.0 annotations.", + "reason": "Single Cell Atlas v4.0 release notes (2026-03-14) supersedes older support but is not cited." + }, + { + "claimId": "claim-atlas-normalization", + "severity": "major", + "action": "Reconcile claims trained on atlas v2.x or v3.x against v4.0 annotations.", + "reason": "Claim uses dataset v2.1; current ledger version is v4.0." + }, + { + "claimId": "claim-protein-benchmark", + "severity": "major", + "action": "Refresh benchmark claims against the recalibrated 2026.01 leaderboard.", + "reason": "Protein structure benchmark leaderboard recalibration (2026-01-22) updates older support but is not cited." + }, + { + "claimId": "claim-protein-benchmark", + "severity": "major", + "action": "Refresh benchmark claims against the recalibrated 2026.01 leaderboard.", + "reason": "Claim uses benchmark 2024.10; current ledger version is 2026.01." + } + ], + "researchGapPrompts": [ + "Freshness gap: update the evidence base for senolytic cardiometabolic safety before treating the claim as current.", + "Freshness gap: update the evidence base for organoid assay reporting before treating the claim as current.", + "Freshness gap: update the evidence base for single-cell atlas normalization before treating the claim as current.", + "Freshness gap: update the evidence base for protein structure benchmark before treating the claim as current." + ], + "auditDigest": "2b28dfa1b6fc17166e45aa06df339015e0dec9da568f3a185015dab564cad637" + }, + { + "packetId": "ms-ready-telemetry-review", + "title": "Telemetry implant arrhythmia replication note", + "domain": "clinical devices", + "reviewDate": "2026-05-23", + "summary": { + "status": "ready_for_review", + "totalFindings": 1, + "actionableFindings": 0, + "criticalFindings": 0, + "majorFindings": 0, + "warningFindings": 0, + "readyFindings": 1 + }, + "findings": [ + { + "severity": "info", + "code": "FRESHNESS_READY", + "claimId": "claim-telemetry-risk", + "topic": "telemetry implant arrhythmia", + "detail": "Claim cites the current evidence signal for its topic.", + "requiredAction": "No freshness hold required.", + "evidenceSignalId": null + } + ], + "reviewerActions": [], + "researchGapPrompts": [], + "auditDigest": "30b9621c84e89262d698c368e40fcd6fd32bde83bb05cf9be0e70ad5823c4287" + } + ], + "auditDigest": "45a5a7683b859d96101b72cc8442e889cd630ad2736fcebe4dd6b85302cd0cd8" +} diff --git a/literature-freshness-review-assistant/reports/demo.md b/literature-freshness-review-assistant/reports/demo.md new file mode 100644 index 00000000..3e2ac752 --- /dev/null +++ b/literature-freshness-review-assistant/reports/demo.md @@ -0,0 +1,46 @@ +# Literature Freshness Review Assistant + +Review date: 2026-05-23 +Overall digest: 45a5a7683b859d96101b72cc8442e889cd630ad2736fcebe4dd6b85302cd0cd8 + +## Aggregate + +- Reviewed manuscripts: 2 +- Held manuscripts: 1 +- Revision manuscripts: 0 +- Ready manuscripts: 1 +- Critical findings: 1 +- Major findings: 8 +- Actionable findings: 9 + +## Senolytic cardiac safety manuscript + +Status: hold_freshness_review +Digest: 2b28dfa1b6fc17166e45aa06df339015e0dec9da568f3a185015dab564cad637 + +| Severity | Code | Claim | Detail | Required action | +| --- | --- | --- | --- | --- | +| major | STALE_SUPPORT_WINDOW | claim-tolerability | Latest support is 2021-06-01, older than the 4-year freshness window. | Add recent evidence or downgrade the claim wording. | +| critical | NEWER_CONTRADICTORY_EVIDENCE | claim-tolerability | 2025 systematic review of senolytic cardiometabolic safety (2025-11-18) contradicts older support but is not cited. | Discuss the larger safety signal before claiming broad tolerability. | +| major | STALE_SUPPORT_WINDOW | claim-organoid-methods | Latest support is 2022-02-10, older than the 4-year freshness window. | Add recent evidence or downgrade the claim wording. | +| major | MISSING_NEWER_EVIDENCE | claim-organoid-methods | SCIBASE organoid reporting guidance 2026.1 (2026-02-04) updates older support but is not cited. | Use the 2026.1 organoid reporting checklist for reviewer packets. | +| major | STALE_SUPPORT_WINDOW | claim-atlas-normalization | Latest support is 2021-12-12, older than the 4-year freshness window. | Add recent evidence or downgrade the claim wording. | +| major | MISSING_NEWER_EVIDENCE | claim-atlas-normalization | Single Cell Atlas v4.0 release notes (2026-03-14) supersedes older support but is not cited. | Reconcile claims trained on atlas v2.x or v3.x against v4.0 annotations. | +| major | DATASET_VERSION_DRIFT | claim-atlas-normalization | Claim uses dataset v2.1; current ledger version is v4.0. | Reconcile claims trained on atlas v2.x or v3.x against v4.0 annotations. | +| major | MISSING_NEWER_EVIDENCE | claim-protein-benchmark | Protein structure benchmark leaderboard recalibration (2026-01-22) updates older support but is not cited. | Refresh benchmark claims against the recalibrated 2026.01 leaderboard. | +| major | BENCHMARK_VERSION_DRIFT | claim-protein-benchmark | Claim uses benchmark 2024.10; current ledger version is 2026.01. | Refresh benchmark claims against the recalibrated 2026.01 leaderboard. | + +Research gap prompts: +- Freshness gap: update the evidence base for senolytic cardiometabolic safety before treating the claim as current. +- Freshness gap: update the evidence base for organoid assay reporting before treating the claim as current. +- Freshness gap: update the evidence base for single-cell atlas normalization before treating the claim as current. +- Freshness gap: update the evidence base for protein structure benchmark before treating the claim as current. + +## Telemetry implant arrhythmia replication note + +Status: ready_for_review +Digest: 30b9621c84e89262d698c368e40fcd6fd32bde83bb05cf9be0e70ad5823c4287 + +| Severity | Code | Claim | Detail | Required action | +| --- | --- | --- | --- | --- | +| info | FRESHNESS_READY | claim-telemetry-risk | Claim cites the current evidence signal for its topic. | No freshness hold required. | diff --git a/literature-freshness-review-assistant/reports/demo.mp4 b/literature-freshness-review-assistant/reports/demo.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..31227d38c4c3c6ac82c5aeb1329b6265bd307b53 GIT binary patch literal 43703 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﨏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 literal 0 HcmV?d00001 diff --git a/literature-freshness-review-assistant/reports/demo.svg b/literature-freshness-review-assistant/reports/demo.svg new file mode 100644 index 00000000..5081e22b --- /dev/null +++ b/literature-freshness-review-assistant/reports/demo.svg @@ -0,0 +1,34 @@ + + Literature freshness review assistant summary + Summary of held manuscripts, ready manuscripts, and freshness findings. + + + + Literature freshness review assistant + Stale citations, missing newer evidence, and temporal drift before AI review release + Held + + + 1 + Ready + + + 1 + Critical + + + 1 + Major + + + 8 + Audit digest: 45a5a7683b859d96101b72cc + diff --git a/literature-freshness-review-assistant/sample-data.js b/literature-freshness-review-assistant/sample-data.js new file mode 100644 index 00000000..1506269c --- /dev/null +++ b/literature-freshness-review-assistant/sample-data.js @@ -0,0 +1,144 @@ +const freshnessPolicy = { + reviewDate: "2026-05-23", + staleAfterYears: 4, + contradictionSeverity: "critical", + requiredEvidenceTypes: [ + "systematic_review", + "guideline", + "dataset_release", + "benchmark_update" + ], + holdSeverityCutoff: "critical" +}; + +const evidenceLedger = { + signals: [ + { + id: "ev-sr-2025-senolytic-cardiometabolic", + topic: "senolytic cardiometabolic safety", + evidenceType: "systematic_review", + title: "2025 systematic review of senolytic cardiometabolic safety", + published: "2025-11-18", + relation: "contradicts", + requirement: "Discuss the larger safety signal before claiming broad tolerability.", + minCitationDate: "2025-11-18" + }, + { + id: "ev-guide-2026-organoid-reporting", + topic: "organoid assay reporting", + evidenceType: "guideline", + title: "SCIBASE organoid reporting guidance 2026.1", + published: "2026-02-04", + relation: "updates", + requirement: "Use the 2026.1 organoid reporting checklist for reviewer packets.", + minCitationDate: "2026-02-04" + }, + { + id: "ev-data-2026-single-cell-atlas-v4", + topic: "single-cell atlas normalization", + evidenceType: "dataset_release", + title: "Single Cell Atlas v4.0 release notes", + published: "2026-03-14", + relation: "supersedes", + currentVersion: "v4.0", + requirement: "Reconcile claims trained on atlas v2.x or v3.x against v4.0 annotations.", + minCitationDate: "2026-03-14" + }, + { + id: "ev-bench-2026-protein-leaderboard", + topic: "protein structure benchmark", + evidenceType: "benchmark_update", + title: "Protein structure benchmark leaderboard recalibration", + published: "2026-01-22", + relation: "updates", + currentVersion: "2026.01", + requirement: "Refresh benchmark claims against the recalibrated 2026.01 leaderboard.", + minCitationDate: "2026-01-22" + }, + { + id: "ev-sr-2024-telemetry-implant", + topic: "telemetry implant arrhythmia", + evidenceType: "systematic_review", + title: "2024 telemetry implant arrhythmia meta-analysis", + published: "2024-09-20", + relation: "supports", + requirement: "Current support is acceptable if cited with the 2024 meta-analysis.", + minCitationDate: "2024-09-20" + } + ] +}; + +const manuscriptPackets = [ + { + id: "ms-hold-senolytic-review", + title: "Senolytic cardiac safety manuscript", + domain: "translational cardiometabolic biology", + claims: [ + { + id: "claim-tolerability", + text: "The senolytic cocktail is broadly well tolerated across cardiometabolic cohorts.", + topic: "senolytic cardiometabolic safety", + supportCitationIds: ["trial-2019-small", "review-2021-safety"], + citationYears: [2019, 2021], + citationDates: ["2019-08-15", "2021-06-01"], + citedCurrentSignalIds: [], + tone: "broad" + }, + { + id: "claim-organoid-methods", + text: "The organoid assay follows current field reporting guidance.", + topic: "organoid assay reporting", + supportCitationIds: ["organoid-checklist-2022"], + citationYears: [2022], + citationDates: ["2022-02-10"], + citedCurrentSignalIds: [], + tone: "current" + }, + { + id: "claim-atlas-normalization", + text: "The single-cell normalization pipeline reflects current atlas annotations.", + topic: "single-cell atlas normalization", + supportCitationIds: ["atlas-v2-release"], + citationYears: [2021], + citationDates: ["2021-12-12"], + citedCurrentSignalIds: [], + datasetVersion: "v2.1", + tone: "current" + }, + { + id: "claim-protein-benchmark", + text: "The model remains state-of-the-art on the protein structure benchmark.", + topic: "protein structure benchmark", + supportCitationIds: ["leaderboard-2024-10"], + citationYears: [2024], + citationDates: ["2024-10-05"], + citedCurrentSignalIds: [], + benchmarkVersion: "2024.10", + tone: "state-of-the-art" + } + ] + }, + { + id: "ms-ready-telemetry-review", + title: "Telemetry implant arrhythmia replication note", + domain: "clinical devices", + claims: [ + { + id: "claim-telemetry-risk", + text: "Telemetry implant arrhythmia risk is consistent with the latest meta-analysis.", + topic: "telemetry implant arrhythmia", + supportCitationIds: ["telemetry-meta-2024"], + citationYears: [2024], + citationDates: ["2024-09-20"], + citedCurrentSignalIds: ["ev-sr-2024-telemetry-implant"], + tone: "calibrated" + } + ] + } +]; + +module.exports = { + freshnessPolicy, + evidenceLedger, + manuscriptPackets +}; diff --git a/literature-freshness-review-assistant/scripts/render-demo-video.js b/literature-freshness-review-assistant/scripts/render-demo-video.js new file mode 100644 index 00000000..c53297c4 --- /dev/null +++ b/literature-freshness-review-assistant/scripts/render-demo-video.js @@ -0,0 +1,89 @@ +const fs = require("fs"); +const os = require("os"); +const path = require("path"); +const { spawnSync } = require("child_process"); + +const { + evaluateFreshnessReview +} = require("../index"); +const { + freshnessPolicy, + evidenceLedger, + manuscriptPackets +} = require("../sample-data"); + +const moduleDir = path.join(__dirname, ".."); +const reportsDir = path.join(moduleDir, "reports"); +fs.mkdirSync(reportsDir, { recursive: true }); + +function candidateFonts() { + return [ + "C:\\Windows\\Fonts\\arial.ttf", + "C:\\Windows\\Fonts\\segoeui.ttf", + "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", + "/usr/share/fonts/truetype/liberation2/LiberationSans-Regular.ttf", + "/System/Library/Fonts/Supplemental/Arial.ttf", + "/Library/Fonts/Arial.ttf" + ]; +} + +function resolveFontFile() { + const font = candidateFonts().find((candidate) => fs.existsSync(candidate)); + if (!font) { + throw new Error("No supported TrueType font file found for ffmpeg drawtext rendering."); + } + return font; +} + +function escapeDrawtext(value) { + return String(value) + .replaceAll("\\", "\\\\") + .replaceAll(":", "\\:") + .replaceAll("'", "\\'") + .replaceAll(",", "\\,"); +} + +function drawText(fontFile, text, x, y, size, color) { + return `drawtext=fontfile='${escapeDrawtext(fontFile)}':text='${escapeDrawtext(text)}':x=${x}:y=${y}:fontsize=${size}:fontcolor=${color}`; +} + +const ffmpegPath = process.env.FFMPEG_PATH || "ffmpeg"; +const fontFile = resolveFontFile(); +const report = evaluateFreshnessReview(manuscriptPackets, evidenceLedger, freshnessPolicy); +const output = path.join(reportsDir, "demo.mp4"); +const filter = [ + "color=c=0xf8fafc:s=1280x720:d=5", + drawText(fontFile, "Literature Freshness Review Assistant", 70, 70, 42, "0x0f172a"), + drawText(fontFile, `Status: ${report.manuscripts[0].summary.status}`, 70, 145, 32, "0xb91c1c"), + drawText(fontFile, `Reviewed manuscripts: ${report.aggregate.reviewedManuscripts}`, 70, 220, 28, "0x0f172a"), + drawText(fontFile, `Held: ${report.aggregate.heldManuscripts} Ready: ${report.aggregate.readyManuscripts}`, 70, 270, 28, "0x0f172a"), + drawText(fontFile, `Critical freshness findings: ${report.aggregate.criticalFindings}`, 70, 340, 30, "0xdc2626"), + drawText(fontFile, `Major freshness findings: ${report.aggregate.majorFindings}`, 70, 390, 30, "0xd97706"), + drawText(fontFile, "Checks stale support, missing newer evidence, dataset drift, and benchmark drift.", 70, 475, 25, "0x334155"), + drawText(fontFile, `Audit ${report.auditDigest.slice(0, 24)}`, 70, 635, 22, "0x475569") +].join(","); + +const args = [ + "-y", + "-f", "lavfi", + "-i", filter, + "-t", "5", + "-pix_fmt", "yuv420p", + "-c:v", "libx264", + "-movflags", "+faststart", + output +]; + +const result = spawnSync(ffmpegPath, args, { + encoding: "utf8", + stdio: "pipe", + windowsHide: true +}); + +if (result.status !== 0) { + process.stderr.write(result.stderr || result.stdout); + throw new Error(`ffmpeg failed with status ${result.status}`); +} + +const stats = fs.statSync(output); +console.log(`wrote ${path.relative(process.cwd(), output)} (${stats.size} bytes) using ${path.basename(ffmpegPath)} on ${os.platform()}`); diff --git a/literature-freshness-review-assistant/test.js b/literature-freshness-review-assistant/test.js new file mode 100644 index 00000000..05d879bc --- /dev/null +++ b/literature-freshness-review-assistant/test.js @@ -0,0 +1,47 @@ +const assert = require("assert"); + +const { + evaluateFreshnessReview, + renderMarkdown, + renderSvg +} = require("./index"); +const { + freshnessPolicy, + evidenceLedger, + manuscriptPackets +} = require("./sample-data"); + +const report = evaluateFreshnessReview(manuscriptPackets, evidenceLedger, freshnessPolicy); + +assert.strictEqual(report.aggregate.reviewedManuscripts, 2); +assert.strictEqual(report.aggregate.heldManuscripts, 1); +assert.strictEqual(report.aggregate.readyManuscripts, 1); +assert.strictEqual(report.aggregate.criticalFindings, 1); +assert.ok(report.aggregate.majorFindings >= 4); + +const held = report.manuscripts.find((entry) => entry.packetId === "ms-hold-senolytic-review"); +assert.ok(held); +assert.strictEqual(held.summary.status, "hold_freshness_review"); +assert.ok(held.findings.some((finding) => finding.code === "NEWER_CONTRADICTORY_EVIDENCE")); +assert.ok(held.findings.some((finding) => finding.code === "MISSING_NEWER_EVIDENCE")); +assert.ok(held.findings.some((finding) => finding.code === "DATASET_VERSION_DRIFT")); +assert.ok(held.findings.some((finding) => finding.code === "BENCHMARK_VERSION_DRIFT")); +assert.ok(held.reviewerActions.length >= 4); + +const ready = report.manuscripts.find((entry) => entry.packetId === "ms-ready-telemetry-review"); +assert.ok(ready); +assert.strictEqual(ready.summary.status, "ready_for_review"); +assert.deepStrictEqual(ready.findings.map((finding) => finding.code), ["FRESHNESS_READY"]); + +const secondPass = evaluateFreshnessReview(manuscriptPackets, evidenceLedger, freshnessPolicy); +assert.strictEqual(secondPass.auditDigest, report.auditDigest); + +const markdown = renderMarkdown(report); +assert.ok(markdown.includes("Literature Freshness Review Assistant")); +assert.ok(markdown.includes("NEWER_CONTRADICTORY_EVIDENCE")); + +const svg = renderSvg(report); +assert.ok(svg.includes("