From 333a75ec9292ae456fbb58360b3a1189a2b7858d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szab=C3=B3=20D=C3=A1vid?= Date: Sat, 27 Jun 2026 10:30:11 +0200 Subject: [PATCH] Add data fabrication anomaly assistant --- data-fabrication-anomaly-assistant/README.md | 23 ++ data-fabrication-anomaly-assistant/demo.js | 31 ++ .../make-demo-video.js | 50 ++++ .../package.json | 13 + .../reports/clean-review.json | 9 + .../reports/demo.mp4 | Bin 0 -> 23155 bytes .../reports/risky-review.json | 45 +++ .../reports/risky-review.md | 22 ++ .../reports/summary.svg | 11 + .../src/assistant.js | 278 ++++++++++++++++++ .../src/samplePackets.js | 39 +++ data-fabrication-anomaly-assistant/test.js | 31 ++ 12 files changed, 552 insertions(+) create mode 100644 data-fabrication-anomaly-assistant/README.md create mode 100644 data-fabrication-anomaly-assistant/demo.js create mode 100644 data-fabrication-anomaly-assistant/make-demo-video.js create mode 100644 data-fabrication-anomaly-assistant/package.json create mode 100644 data-fabrication-anomaly-assistant/reports/clean-review.json create mode 100644 data-fabrication-anomaly-assistant/reports/demo.mp4 create mode 100644 data-fabrication-anomaly-assistant/reports/risky-review.json create mode 100644 data-fabrication-anomaly-assistant/reports/risky-review.md create mode 100644 data-fabrication-anomaly-assistant/reports/summary.svg create mode 100644 data-fabrication-anomaly-assistant/src/assistant.js create mode 100644 data-fabrication-anomaly-assistant/src/samplePackets.js create mode 100644 data-fabrication-anomaly-assistant/test.js diff --git a/data-fabrication-anomaly-assistant/README.md b/data-fabrication-anomaly-assistant/README.md new file mode 100644 index 00000000..4f147908 --- /dev/null +++ b/data-fabrication-anomaly-assistant/README.md @@ -0,0 +1,23 @@ +# Data Fabrication Anomaly Assistant + +This is a focused AI-Powered Research Assistant Suite slice for issue #16. It reviews synthetic manuscript data packets before AI peer-review output is trusted and emits release, review, or hold decisions for data-forensics red flags. + +The assistant checks: + +- repeated measurement rows that need raw-data verification +- invalid collection timestamps that cannot be audited +- terminal-digit preference that suggests rounding or synthetic entry +- unusually smooth measurement series +- perfect group separation that needs preregistered exclusion and raw-row provenance + +All fixtures are synthetic. The module does not call external AI services, publisher APIs, private datasets, credential stores, payment systems, or live manuscript systems. + +## Run + +```sh +npm run check +npm test +npm run demo +``` + +The demo writes reviewer artifacts under `reports/`. diff --git a/data-fabrication-anomaly-assistant/demo.js b/data-fabrication-anomaly-assistant/demo.js new file mode 100644 index 00000000..bbbf04a9 --- /dev/null +++ b/data-fabrication-anomaly-assistant/demo.js @@ -0,0 +1,31 @@ +import fs from "node:fs"; +import path from "node:path"; +import { + evaluateFabricationAnomalyPacket, + renderSummarySvg, + summarizeReview, +} from "./src/assistant.js"; +import { cleanPacket, riskyPacket } from "./src/samplePackets.js"; + +const reportsDir = path.join(process.cwd(), "reports"); +fs.mkdirSync(reportsDir, { recursive: true }); + +const clean = evaluateFabricationAnomalyPacket(cleanPacket); +const risky = evaluateFabricationAnomalyPacket(riskyPacket); + +fs.writeFileSync( + path.join(reportsDir, "clean-review.json"), + `${JSON.stringify(clean, null, 2)}\n`, +); +fs.writeFileSync( + path.join(reportsDir, "risky-review.json"), + `${JSON.stringify(risky, null, 2)}\n`, +); +fs.writeFileSync(path.join(reportsDir, "risky-review.md"), summarizeReview(risky)); +fs.writeFileSync(path.join(reportsDir, "summary.svg"), renderSummarySvg(risky)); + +console.log("Wrote data fabrication anomaly assistant reports:"); +console.log("- reports/clean-review.json"); +console.log("- reports/risky-review.json"); +console.log("- reports/risky-review.md"); +console.log("- reports/summary.svg"); diff --git a/data-fabrication-anomaly-assistant/make-demo-video.js b/data-fabrication-anomaly-assistant/make-demo-video.js new file mode 100644 index 00000000..0d1752be --- /dev/null +++ b/data-fabrication-anomaly-assistant/make-demo-video.js @@ -0,0 +1,50 @@ +import { spawnSync } from "node:child_process"; +import path from "node:path"; +import { riskyPacket } from "./src/samplePackets.js"; +import { evaluateFabricationAnomalyPacket } from "./src/assistant.js"; + +const reportsDir = path.join(process.cwd(), "reports"); +const demoMp4 = path.join(reportsDir, "demo.mp4"); +const resultPacket = evaluateFabricationAnomalyPacket(riskyPacket); + +function escapeDrawtext(text) { + return text.replaceAll("\\", "\\\\").replaceAll(":", "\\:").replaceAll("'", "\\'"); +} + +const font = "C\\:/Windows/Fonts/arial.ttf"; +const lines = [ + "Data Fabrication Anomaly Assistant", + `Decision ${resultPacket.decision} | Findings ${resultPacket.findingCount}`, + "Flags duplicate rows, invalid timestamps, digit preference, smooth series, and perfect separation.", + `Audit digest ${resultPacket.auditDigest}`, +]; +const drawText = lines + .map( + (line, index) => + `drawtext=fontfile='${font}':text='${escapeDrawtext(line)}':x=48:y=${64 + index * 72}:fontsize=${index === 0 ? 34 : 24}:fontcolor=${index === 1 ? "0xffdddd" : "white"}`, + ) + .join(","); + +const result = spawnSync( + "ffmpeg", + [ + "-y", + "-f", + "lavfi", + "-i", + "color=c=0x111827:s=960x540:r=12", + "-t", + "4", + "-vf", + `${drawText},format=yuv420p`, + "-an", + demoMp4, + ], + { encoding: "utf8" }, +); + +if (result.status !== 0) { + throw new Error(result.stderr || "ffmpeg failed to render demo.mp4"); +} + +console.log(`Wrote ${path.relative(process.cwd(), demoMp4)}`); diff --git a/data-fabrication-anomaly-assistant/package.json b/data-fabrication-anomaly-assistant/package.json new file mode 100644 index 00000000..6f386f49 --- /dev/null +++ b/data-fabrication-anomaly-assistant/package.json @@ -0,0 +1,13 @@ +{ + "name": "data-fabrication-anomaly-assistant", + "version": "1.0.0", + "private": true, + "description": "Synthetic data-forensics review assistant for SCIBASE AI research review packets.", + "type": "module", + "scripts": { + "check": "node --check src/assistant.js && node --check src/samplePackets.js && node --check test.js && node --check demo.js && node --check make-demo-video.js", + "test": "node test.js", + "demo": "node demo.js", + "demo:video": "node make-demo-video.js" + } +} diff --git a/data-fabrication-anomaly-assistant/reports/clean-review.json b/data-fabrication-anomaly-assistant/reports/clean-review.json new file mode 100644 index 00000000..a73db814 --- /dev/null +++ b/data-fabrication-anomaly-assistant/reports/clean-review.json @@ -0,0 +1,9 @@ +{ + "packetId": "clean-growth-study", + "manuscriptTitle": "Enzyme response in replicated plant growth chambers", + "decision": "RELEASE", + "findingCount": 0, + "findings": [], + "auditDigest": "5db12124295e2085", + "assistantScope": "Synthetic data-fabrication anomaly red flags before AI peer-review output is trusted." +} diff --git a/data-fabrication-anomaly-assistant/reports/demo.mp4 b/data-fabrication-anomaly-assistant/reports/demo.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..afae3d2de9f91ad1e1245bad682afbcc3c537f9b GIT binary patch literal 23155 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 literal 0 HcmV?d00001 diff --git a/data-fabrication-anomaly-assistant/reports/risky-review.json b/data-fabrication-anomaly-assistant/reports/risky-review.json new file mode 100644 index 00000000..c6167afe --- /dev/null +++ b/data-fabrication-anomaly-assistant/reports/risky-review.json @@ -0,0 +1,45 @@ +{ + "packetId": "risky-cytokine-study", + "manuscriptTitle": "Cytokine response claims from a disputed assay batch", + "decision": "HOLD", + "findingCount": 5, + "findings": [ + { + "id": "duplicate-measurement-rows", + "severity": "high", + "title": "Repeated measurement rows need raw-data verification", + "evidence": "1 duplicate row pair(s): #12/#13", + "action": "Hold AI review output until source instruments, audit trail, and import logs explain the repeated rows." + }, + { + "id": "invalid-collection-timestamps", + "severity": "high", + "title": "Collection timestamps are not machine-auditable", + "evidence": "Invalid timestamp rows: #14", + "action": "Require normalized ISO-8601 collection timestamps before the assistant cites this dataset." + }, + { + "id": "terminal-digit-preference", + "severity": "medium", + "title": "Terminal digit distribution is unusually concentrated", + "evidence": "50% of numeric measurements end in 0", + "action": "Ask for raw instrument exports or an explanation of rounding rules before trusting fine-grained effects." + }, + { + "id": "over-smooth-measurement-series", + "severity": "medium", + "title": "Measurement series is smoother than expected for independent observations", + "evidence": "control delta sd/value sd=0", + "action": "Route to statistical reviewer for instrument drift, interpolation, or synthetic-row checks." + }, + { + "id": "perfect-between-group-separation", + "severity": "medium", + "title": "Groups separate perfectly without overlap", + "evidence": "control: n=6, range=10-12.5; treated: n=8, range=20-23", + "action": "Require preregistered exclusion rules and raw row provenance before strong causal or classification claims are released." + } + ], + "auditDigest": "6466f6cc655b23a7", + "assistantScope": "Synthetic data-fabrication anomaly red flags before AI peer-review output is trusted." +} diff --git a/data-fabrication-anomaly-assistant/reports/risky-review.md b/data-fabrication-anomaly-assistant/reports/risky-review.md new file mode 100644 index 00000000..cf5d36fd --- /dev/null +++ b/data-fabrication-anomaly-assistant/reports/risky-review.md @@ -0,0 +1,22 @@ +# Data fabrication anomaly review: Cytokine response claims from a disputed assay batch + +Decision: **HOLD** +Audit digest: `6466f6cc655b23a7` + +## Findings + +- **HIGH** Repeated measurement rows need raw-data verification (duplicate-measurement-rows) + Evidence: 1 duplicate row pair(s): #12/#13 + Action: Hold AI review output until source instruments, audit trail, and import logs explain the repeated rows. +- **HIGH** Collection timestamps are not machine-auditable (invalid-collection-timestamps) + Evidence: Invalid timestamp rows: #14 + Action: Require normalized ISO-8601 collection timestamps before the assistant cites this dataset. +- **MEDIUM** Terminal digit distribution is unusually concentrated (terminal-digit-preference) + Evidence: 50% of numeric measurements end in 0 + Action: Ask for raw instrument exports or an explanation of rounding rules before trusting fine-grained effects. +- **MEDIUM** Measurement series is smoother than expected for independent observations (over-smooth-measurement-series) + Evidence: control delta sd/value sd=0 + Action: Route to statistical reviewer for instrument drift, interpolation, or synthetic-row checks. +- **MEDIUM** Groups separate perfectly without overlap (perfect-between-group-separation) + Evidence: control: n=6, range=10-12.5; treated: n=8, range=20-23 + Action: Require preregistered exclusion rules and raw row provenance before strong causal or classification claims are released. diff --git a/data-fabrication-anomaly-assistant/reports/summary.svg b/data-fabrication-anomaly-assistant/reports/summary.svg new file mode 100644 index 00000000..fd82864f --- /dev/null +++ b/data-fabrication-anomaly-assistant/reports/summary.svg @@ -0,0 +1,11 @@ + + + + Data Fabrication Anomaly Assistant + Cytokine response claims from a disputed assay batch + + HOLD + Findings: 5 | Digest: 6466f6cc655b23a7 + HIGH - duplicate-measurement-rowsHIGH - invalid-collection-timestampsMEDIUM - terminal-digit-preferenceMEDIUM - over-smooth-measurement-series + Synthetic data only. No live manuscripts, private datasets, credentials, or external APIs. + diff --git a/data-fabrication-anomaly-assistant/src/assistant.js b/data-fabrication-anomaly-assistant/src/assistant.js new file mode 100644 index 00000000..13f61ef5 --- /dev/null +++ b/data-fabrication-anomaly-assistant/src/assistant.js @@ -0,0 +1,278 @@ +import crypto from "node:crypto"; + +const DECISION_RANK = { + RELEASE: 0, + REVIEW: 1, + HOLD: 2, +}; + +function strongerDecision(current, next) { + return DECISION_RANK[next] > DECISION_RANK[current] ? next : current; +} + +function round(value, places = 3) { + return Number(value.toFixed(places)); +} + +function median(values) { + const sorted = [...values].sort((a, b) => a - b); + const middle = Math.floor(sorted.length / 2); + return sorted.length % 2 + ? sorted[middle] + : (sorted[middle - 1] + sorted[middle]) / 2; +} + +function stddev(values) { + if (values.length < 2) return 0; + const mean = values.reduce((sum, value) => sum + value, 0) / values.length; + const variance = + values.reduce((sum, value) => sum + (value - mean) ** 2, 0) / + (values.length - 1); + return Math.sqrt(variance); +} + +function finding(id, severity, title, evidence, action) { + return { id, severity, title, evidence, action }; +} + +function digestPacket(packet) { + const stable = JSON.stringify(packet, Object.keys(packet).sort()); + return crypto.createHash("sha256").update(stable).digest("hex").slice(0, 16); +} + +function normalizeRecords(records = []) { + return records.map((record, index) => ({ + index, + participantId: String(record.participantId ?? `row-${index + 1}`), + group: String(record.group ?? "unknown"), + value: Number(record.value), + collectedAt: record.collectedAt, + operatorId: String(record.operatorId ?? "unknown"), + })); +} + +function findDuplicateRows(records) { + const seen = new Map(); + const duplicates = []; + for (const record of records) { + const key = [ + record.group, + record.value, + record.collectedAt, + record.operatorId, + ].join("|"); + if (seen.has(key)) { + duplicates.push([seen.get(key), record.index]); + } else { + seen.set(key, record.index); + } + } + + if (duplicates.length === 0) return null; + return finding( + "duplicate-measurement-rows", + "high", + "Repeated measurement rows need raw-data verification", + `${duplicates.length} duplicate row pair(s): ${duplicates + .map(([a, b]) => `#${a + 1}/#${b + 1}`) + .join(", ")}`, + "Hold AI review output until source instruments, audit trail, and import logs explain the repeated rows.", + ); +} + +function findImpossibleTimestamps(records) { + const invalidRows = records.filter((record) => { + const parsed = new Date(record.collectedAt); + return Number.isNaN(parsed.getTime()); + }); + + if (invalidRows.length === 0) return null; + return finding( + "invalid-collection-timestamps", + "high", + "Collection timestamps are not machine-auditable", + `Invalid timestamp rows: ${invalidRows.map((row) => `#${row.index + 1}`).join(", ")}`, + "Require normalized ISO-8601 collection timestamps before the assistant cites this dataset.", + ); +} + +function findDigitPreference(records) { + const numericRows = records.filter((record) => Number.isFinite(record.value)); + if (numericRows.length < 10) return null; + + const terminalCounts = new Map(); + for (const record of numericRows) { + const terminal = Math.abs(Math.round(record.value * 10)) % 10; + terminalCounts.set(terminal, (terminalCounts.get(terminal) ?? 0) + 1); + } + + const [digit, count] = [...terminalCounts.entries()].sort( + (a, b) => b[1] - a[1], + )[0]; + const share = count / numericRows.length; + + if (share < 0.45) return null; + return finding( + "terminal-digit-preference", + "medium", + "Terminal digit distribution is unusually concentrated", + `${round(share * 100, 1)}% of numeric measurements end in ${digit}`, + "Ask for raw instrument exports or an explanation of rounding rules before trusting fine-grained effects.", + ); +} + +function findTooSmoothSequence(records) { + const byGroup = Map.groupBy( + records.filter((record) => Number.isFinite(record.value)), + (record) => record.group, + ); + const suspicious = []; + + for (const [group, groupRecords] of byGroup.entries()) { + if (groupRecords.length < 6) continue; + const sorted = [...groupRecords].sort( + (a, b) => new Date(a.collectedAt) - new Date(b.collectedAt), + ); + const deltas = sorted + .slice(1) + .map((record, index) => round(record.value - sorted[index].value, 4)); + const deltaSpread = stddev(deltas); + const valueSpread = stddev(sorted.map((record) => record.value)); + if (valueSpread > 0 && deltaSpread / valueSpread < 0.045) { + suspicious.push(`${group} delta sd/value sd=${round(deltaSpread / valueSpread, 4)}`); + } + } + + if (suspicious.length === 0) return null; + return finding( + "over-smooth-measurement-series", + "medium", + "Measurement series is smoother than expected for independent observations", + suspicious.join("; "), + "Route to statistical reviewer for instrument drift, interpolation, or synthetic-row checks.", + ); +} + +function findPerfectSeparation(records) { + const byGroup = Map.groupBy( + records.filter((record) => Number.isFinite(record.value)), + (record) => record.group, + ); + if (byGroup.size < 2) return null; + const summaries = [...byGroup.entries()].map(([group, groupRecords]) => { + const values = groupRecords.map((record) => record.value); + return { + group, + n: values.length, + min: Math.min(...values), + max: Math.max(...values), + median: median(values), + }; + }); + + const ordered = summaries.sort((a, b) => a.median - b.median); + const gaps = ordered + .slice(1) + .map((summary, index) => summary.min - ordered[index].max); + const separated = gaps.some((gap) => gap > 0); + const allGroupsLargeEnough = summaries.every((summary) => summary.n >= 6); + + if (!separated || !allGroupsLargeEnough) return null; + return finding( + "perfect-between-group-separation", + "medium", + "Groups separate perfectly without overlap", + summaries + .map( + (summary) => + `${summary.group}: n=${summary.n}, range=${round(summary.min)}-${round(summary.max)}`, + ) + .join("; "), + "Require preregistered exclusion rules and raw row provenance before strong causal or classification claims are released.", + ); +} + +export function evaluateFabricationAnomalyPacket(packet) { + const records = normalizeRecords(packet.records); + const findings = [ + findDuplicateRows(records), + findImpossibleTimestamps(records), + findDigitPreference(records), + findTooSmoothSequence(records), + findPerfectSeparation(records), + ].filter(Boolean); + + let decision = "RELEASE"; + for (const item of findings) { + decision = strongerDecision( + decision, + item.severity === "high" ? "HOLD" : "REVIEW", + ); + } + + return { + packetId: packet.id, + manuscriptTitle: packet.manuscriptTitle, + decision, + findingCount: findings.length, + findings, + auditDigest: digestPacket({ + id: packet.id, + records, + findings: findings.map((item) => item.id), + }), + assistantScope: + "Synthetic data-fabrication anomaly red flags before AI peer-review output is trusted.", + }; +} + +export function summarizeReview(result) { + const lines = [ + `# Data fabrication anomaly review: ${result.manuscriptTitle}`, + "", + `Decision: **${result.decision}**`, + `Audit digest: \`${result.auditDigest}\``, + "", + ]; + + if (result.findings.length === 0) { + lines.push("No fabrication-anomaly red flags were detected in the synthetic packet."); + } else { + lines.push("## Findings", ""); + for (const item of result.findings) { + lines.push( + `- **${item.severity.toUpperCase()}** ${item.title} (${item.id})`, + ` Evidence: ${item.evidence}`, + ` Action: ${item.action}`, + ); + } + } + + return `${lines.join("\n")}\n`; +} + +export function renderSummarySvg(result) { + const hold = result.decision === "HOLD"; + const review = result.decision === "REVIEW"; + const accent = hold ? "#b91c1c" : review ? "#b45309" : "#047857"; + const rows = result.findings.slice(0, 4); + const findingRows = rows + .map( + (item, index) => + `${item.severity.toUpperCase()} - ${item.id}`, + ) + .join(""); + + return ` + + + Data Fabrication Anomaly Assistant + ${result.manuscriptTitle} + + ${result.decision} + Findings: ${result.findingCount} | Digest: ${result.auditDigest} + ${findingRows || 'No red flags detected.'} + Synthetic data only. No live manuscripts, private datasets, credentials, or external APIs. + +`; +} diff --git a/data-fabrication-anomaly-assistant/src/samplePackets.js b/data-fabrication-anomaly-assistant/src/samplePackets.js new file mode 100644 index 00000000..8af499a1 --- /dev/null +++ b/data-fabrication-anomaly-assistant/src/samplePackets.js @@ -0,0 +1,39 @@ +export const cleanPacket = { + id: "clean-growth-study", + manuscriptTitle: "Enzyme response in replicated plant growth chambers", + records: [ + { participantId: "P001", group: "control", value: 9.7, collectedAt: "2026-02-01T09:00:00Z", operatorId: "op-a" }, + { participantId: "P002", group: "control", value: 10.4, collectedAt: "2026-02-01T09:06:00Z", operatorId: "op-b" }, + { participantId: "P003", group: "control", value: 11.1, collectedAt: "2026-02-01T09:12:00Z", operatorId: "op-a" }, + { participantId: "P004", group: "control", value: 10.8, collectedAt: "2026-02-01T09:18:00Z", operatorId: "op-b" }, + { participantId: "P005", group: "control", value: 9.9, collectedAt: "2026-02-01T09:24:00Z", operatorId: "op-a" }, + { participantId: "P006", group: "control", value: 10.6, collectedAt: "2026-02-01T09:30:00Z", operatorId: "op-b" }, + { participantId: "P007", group: "treated", value: 11.3, collectedAt: "2026-02-01T10:00:00Z", operatorId: "op-a" }, + { participantId: "P008", group: "treated", value: 12.1, collectedAt: "2026-02-01T10:06:00Z", operatorId: "op-b" }, + { participantId: "P009", group: "treated", value: 10.9, collectedAt: "2026-02-01T10:12:00Z", operatorId: "op-a" }, + { participantId: "P010", group: "treated", value: 12.7, collectedAt: "2026-02-01T10:18:00Z", operatorId: "op-b" }, + { participantId: "P011", group: "treated", value: 11.8, collectedAt: "2026-02-01T10:24:00Z", operatorId: "op-a" }, + { participantId: "P012", group: "treated", value: 12.4, collectedAt: "2026-02-01T10:30:00Z", operatorId: "op-b" } + ] +}; + +export const riskyPacket = { + id: "risky-cytokine-study", + manuscriptTitle: "Cytokine response claims from a disputed assay batch", + records: [ + { participantId: "C001", group: "control", value: 10.0, collectedAt: "2026-03-01T09:00:00Z", operatorId: "op-a" }, + { participantId: "C002", group: "control", value: 10.5, collectedAt: "2026-03-01T09:05:00Z", operatorId: "op-a" }, + { participantId: "C003", group: "control", value: 11.0, collectedAt: "2026-03-01T09:10:00Z", operatorId: "op-a" }, + { participantId: "C004", group: "control", value: 11.5, collectedAt: "2026-03-01T09:15:00Z", operatorId: "op-a" }, + { participantId: "C005", group: "control", value: 12.0, collectedAt: "2026-03-01T09:20:00Z", operatorId: "op-a" }, + { participantId: "C006", group: "control", value: 12.5, collectedAt: "2026-03-01T09:25:00Z", operatorId: "op-a" }, + { participantId: "T001", group: "treated", value: 20.0, collectedAt: "2026-03-01T10:00:00Z", operatorId: "op-b" }, + { participantId: "T002", group: "treated", value: 20.5, collectedAt: "2026-03-01T10:05:00Z", operatorId: "op-b" }, + { participantId: "T003", group: "treated", value: 21.0, collectedAt: "2026-03-01T10:10:00Z", operatorId: "op-b" }, + { participantId: "T004", group: "treated", value: 21.5, collectedAt: "2026-03-01T10:15:00Z", operatorId: "op-b" }, + { participantId: "T005", group: "treated", value: 22.0, collectedAt: "2026-03-01T10:20:00Z", operatorId: "op-b" }, + { participantId: "T006", group: "treated", value: 22.5, collectedAt: "2026-03-01T10:25:00Z", operatorId: "op-b" }, + { participantId: "T007", group: "treated", value: 22.5, collectedAt: "2026-03-01T10:25:00Z", operatorId: "op-b" }, + { participantId: "T008", group: "treated", value: 23.0, collectedAt: "not-a-date", operatorId: "op-b" } + ] +}; diff --git a/data-fabrication-anomaly-assistant/test.js b/data-fabrication-anomaly-assistant/test.js new file mode 100644 index 00000000..42ba1b5e --- /dev/null +++ b/data-fabrication-anomaly-assistant/test.js @@ -0,0 +1,31 @@ +import assert from "node:assert/strict"; +import { + evaluateFabricationAnomalyPacket, + renderSummarySvg, + summarizeReview, +} from "./src/assistant.js"; +import { cleanPacket, riskyPacket } from "./src/samplePackets.js"; + +const clean = evaluateFabricationAnomalyPacket(cleanPacket); +assert.equal(clean.decision, "RELEASE"); +assert.equal(clean.findingCount, 0); +assert.match(clean.auditDigest, /^[0-9a-f]{16}$/); + +const risky = evaluateFabricationAnomalyPacket(riskyPacket); +assert.equal(risky.decision, "HOLD"); +assert.ok(risky.findings.some((item) => item.id === "duplicate-measurement-rows")); +assert.ok(risky.findings.some((item) => item.id === "invalid-collection-timestamps")); +assert.ok(risky.findings.some((item) => item.id === "terminal-digit-preference")); +assert.ok(risky.findings.some((item) => item.id === "over-smooth-measurement-series")); +assert.ok(risky.findings.some((item) => item.id === "perfect-between-group-separation")); + +const markdown = summarizeReview(risky); +assert.match(markdown, /Data fabrication anomaly review/); +assert.match(markdown, /HOLD/); +assert.match(markdown, /raw-data verification/); + +const svg = renderSummarySvg(risky); +assert.match(svg, /