From 456d8669f8f792e8dc7f923641d037a160b3c2b7 Mon Sep 17 00:00:00 2001 From: Telemark Codex Agent Date: Sat, 15 Aug 2026 22:25:05 +0200 Subject: [PATCH] Add causal claim validity guard --- causal-claim-validity-guard/README.md | 48 +++++ .../artifacts/causal-claim-review.json | 129 ++++++++++++ .../artifacts/causal-claim-review.md | 46 +++++ .../artifacts/causal-claim-summary.svg | 14 ++ .../artifacts/demo.mp4 | Bin 0 -> 49252 bytes .../data/sample_review_packets.json | 85 ++++++++ causal-claim-validity-guard/package.json | 13 ++ causal-claim-validity-guard/scripts/demo.js | 77 +++++++ .../scripts/render-demo-video.js | 52 +++++ causal-claim-validity-guard/src/index.js | 195 ++++++++++++++++++ .../test/causal-claim-validity-guard.test.js | 80 +++++++ 11 files changed, 739 insertions(+) create mode 100644 causal-claim-validity-guard/README.md create mode 100644 causal-claim-validity-guard/artifacts/causal-claim-review.json create mode 100644 causal-claim-validity-guard/artifacts/causal-claim-review.md create mode 100644 causal-claim-validity-guard/artifacts/causal-claim-summary.svg create mode 100644 causal-claim-validity-guard/artifacts/demo.mp4 create mode 100644 causal-claim-validity-guard/data/sample_review_packets.json create mode 100644 causal-claim-validity-guard/package.json create mode 100644 causal-claim-validity-guard/scripts/demo.js create mode 100644 causal-claim-validity-guard/scripts/render-demo-video.js create mode 100644 causal-claim-validity-guard/src/index.js create mode 100644 causal-claim-validity-guard/test/causal-claim-validity-guard.test.js diff --git a/causal-claim-validity-guard/README.md b/causal-claim-validity-guard/README.md new file mode 100644 index 00000000..fbe4195c --- /dev/null +++ b/causal-claim-validity-guard/README.md @@ -0,0 +1,48 @@ +# Causal Claim Validity Guard + +This module adds a focused causal-claim review gate for the AI-Powered Research Assistant Suite in issue `#16`. + +Scientific manuscripts often drift from careful associational language into causal conclusions. The guard inspects synthetic review packets and determines whether AI-generated peer-review output should be released, revised, or held until the causal claim is supported. + +## Scope + +- Detects causal-language overreach in manuscript claims and conclusions. +- Checks whether the study design can support causal language. +- Flags missing comparator/control support, temporal-order gaps, confounder handling gaps, and effect-size uncertainty. +- Produces evidence-linked remediation items suitable for authors, internal reviewers, or automated pre-submission checks. +- Uses synthetic data only. +- Uses no external AI APIs, credentials, private manuscripts, payment systems, or network calls. + +## Requirement Map + +| Issue #16 capability | Implementation | +| --- | --- | +| Auto peer review reports | Emits structured reviewer findings, severity, decision, and remediation. | +| Methodological red flags | Detects design/evidence mismatch, missing controls, confounder gaps, and uncertainty gaps. | +| Claims vs evidence alignment | Compares claim intent, causal wording, study design, temporal evidence, and comparator evidence. | +| Adaptive templates per domain | Supports packet-level `domain` and domain-specific reviewer note generation. | +| Reproducible deterministic output | Demo and tests use fixed synthetic packets and deterministic scoring. | + +## Run + +```bash +npm test +npm run demo +npm run check +``` + +Demo artifacts are written to `artifacts/`: + +- `causal-claim-review.json` +- `causal-claim-review.md` +- `causal-claim-summary.svg` + +## Decisions + +- `RELEASE`: evidence supports the causal wording. +- `REVISE`: the manuscript can likely be repaired with clearer language or additional evidence. +- `HOLD`: the causal claim is not safe to release without material methodological support. + +## AI Assistance Disclosure + +Implemented with Codex assistance and locally verified with deterministic tests and generated artifacts. diff --git a/causal-claim-validity-guard/artifacts/causal-claim-review.json b/causal-claim-validity-guard/artifacts/causal-claim-review.json new file mode 100644 index 00000000..6d2a7c8a --- /dev/null +++ b/causal-claim-validity-guard/artifacts/causal-claim-review.json @@ -0,0 +1,129 @@ +{ + "generatedAt": "2026-08-15T20:24:14.343Z", + "packetCount": 4, + "decisionCounts": { + "RELEASE": 2, + "REVISE": 1, + "HOLD": 1 + }, + "reviews": [ + { + "id": "cardio-rct-001", + "title": "Remote coaching reduces systolic blood pressure in high-risk adults", + "domain": "clinical trials", + "decision": "RELEASE", + "confidence": 0.95, + "causalLanguageDetected": true, + "findingCount": 0, + "findings": [], + "reviewerNote": "clinical trials: causal wording is aligned with the study design and evidence packet." + }, + { + "id": "education-cross-sectional-002", + "title": "Daily tutoring app usage improves mathematics scores", + "domain": "education", + "decision": "HOLD", + "confidence": 0.15, + "causalLanguageDetected": true, + "findingCount": 5, + "findings": [ + { + "code": "DESIGN_CANNOT_SUPPORT_CAUSAL_LANGUAGE", + "severity": "critical", + "message": "The manuscript uses causal language, but the stated design does not independently support causal inference.", + "evidence": { + "design": "cross_sectional", + "claim": "Daily tutoring app usage improves mathematics scores." + }, + "remediation": "Replace causal verbs with associational language or add a causal identification strategy with assumptions and diagnostics." + }, + { + "code": "MISSING_COMPARATOR", + "severity": "major", + "message": "Causal review requires a comparator, control, counterfactual, or explicit quasi-experimental contrast.", + "evidence": { + "hasComparator": false + }, + "remediation": "Add a comparator/control description and explain why it approximates the counterfactual." + }, + { + "code": "TEMPORAL_ORDER_GAP", + "severity": "major", + "message": "The evidence packet does not show that exposure or intervention preceded the outcome.", + "evidence": { + "temporalOrder": false + }, + "remediation": "Document timing of exposure, intervention, measurement, and outcome windows." + }, + { + "code": "CONFOUNDER_STRATEGY_MISSING", + "severity": "major", + "message": "No confounder strategy is described for a causal claim.", + "evidence": { + "confounderStrategy": null + }, + "remediation": "Describe randomization, matching, adjustment, stratification, sensitivity analysis, or a defensible natural experiment." + }, + { + "code": "UNCERTAINTY_NOT_REPORTED", + "severity": "minor", + "message": "The packet reports an effect size without an uncertainty interval.", + "evidence": { + "effectSize": "0.31 SD", + "confidenceInterval": null + }, + "remediation": "Add confidence, credible, or bootstrap intervals and specify the estimation method." + } + ], + "reviewerNote": "education: HOLD before release because the manuscript uses causal language, but the stated design does not independently support causal inference." + }, + { + "id": "ecology-longitudinal-003", + "title": "Wetland restoration is associated with higher amphibian diversity", + "domain": "ecology", + "decision": "RELEASE", + "confidence": 0.95, + "causalLanguageDetected": false, + "findingCount": 0, + "findings": [], + "reviewerNote": "ecology: causal wording is aligned with the study design and evidence packet." + }, + { + "id": "oncology-subgroup-004", + "title": "Dose timing prevents relapse in a small oncology subgroup", + "domain": "oncology", + "decision": "REVISE", + "confidence": 0.79, + "causalLanguageDetected": true, + "findingCount": 2, + "findings": [ + { + "code": "UNCERTAINTY_NOT_REPORTED", + "severity": "minor", + "message": "The packet reports an effect size without an uncertainty interval.", + "evidence": { + "effectSize": "hazard ratio 0.72", + "confidenceInterval": null + }, + "remediation": "Add confidence, credible, or bootstrap intervals and specify the estimation method." + }, + { + "code": "UNDERPOWERED_SUBGROUP_CAUSAL_CLAIM", + "severity": "minor", + "message": "At least one subgroup claim uses causal language without a powered subgroup analysis.", + "evidence": { + "subgroups": [ + { + "name": "biomarker-positive subgroup", + "claim": "dose timing prevents relapse", + "powered": false + } + ] + }, + "remediation": "Mark subgroup findings exploratory unless power and multiplicity controls are documented." + } + ], + "reviewerNote": "oncology: REVISE before release because the packet reports an effect size without an uncertainty interval." + } + ] +} diff --git a/causal-claim-validity-guard/artifacts/causal-claim-review.md b/causal-claim-validity-guard/artifacts/causal-claim-review.md new file mode 100644 index 00000000..87285146 --- /dev/null +++ b/causal-claim-validity-guard/artifacts/causal-claim-review.md @@ -0,0 +1,46 @@ +# Causal Claim Validity Review + +Generated: 2026-08-15T20:24:14.343Z + +## Decision Counts + +- RELEASE: 2 +- REVISE: 1 +- HOLD: 1 + +## Packet Reviews + +### Remote coaching reduces systolic blood pressure in high-risk adults +Decision: RELEASE +Confidence: 0.95 +Reviewer note: clinical trials: causal wording is aligned with the study design and evidence packet. + +### Daily tutoring app usage improves mathematics scores +Decision: HOLD +Confidence: 0.15 +Reviewer note: education: HOLD before release because the manuscript uses causal language, but the stated design does not independently support causal inference. +- CRITICAL DESIGN_CANNOT_SUPPORT_CAUSAL_LANGUAGE: The manuscript uses causal language, but the stated design does not independently support causal inference. + Remediation: Replace causal verbs with associational language or add a causal identification strategy with assumptions and diagnostics. +- MAJOR MISSING_COMPARATOR: Causal review requires a comparator, control, counterfactual, or explicit quasi-experimental contrast. + Remediation: Add a comparator/control description and explain why it approximates the counterfactual. +- MAJOR TEMPORAL_ORDER_GAP: The evidence packet does not show that exposure or intervention preceded the outcome. + Remediation: Document timing of exposure, intervention, measurement, and outcome windows. +- MAJOR CONFOUNDER_STRATEGY_MISSING: No confounder strategy is described for a causal claim. + Remediation: Describe randomization, matching, adjustment, stratification, sensitivity analysis, or a defensible natural experiment. +- MINOR UNCERTAINTY_NOT_REPORTED: The packet reports an effect size without an uncertainty interval. + Remediation: Add confidence, credible, or bootstrap intervals and specify the estimation method. + +### Wetland restoration is associated with higher amphibian diversity +Decision: RELEASE +Confidence: 0.95 +Reviewer note: ecology: causal wording is aligned with the study design and evidence packet. + +### Dose timing prevents relapse in a small oncology subgroup +Decision: REVISE +Confidence: 0.79 +Reviewer note: oncology: REVISE before release because the packet reports an effect size without an uncertainty interval. +- MINOR UNCERTAINTY_NOT_REPORTED: The packet reports an effect size without an uncertainty interval. + Remediation: Add confidence, credible, or bootstrap intervals and specify the estimation method. +- MINOR UNDERPOWERED_SUBGROUP_CAUSAL_CLAIM: At least one subgroup claim uses causal language without a powered subgroup analysis. + Remediation: Mark subgroup findings exploratory unless power and multiplicity controls are documented. + diff --git a/causal-claim-validity-guard/artifacts/causal-claim-summary.svg b/causal-claim-validity-guard/artifacts/causal-claim-summary.svg new file mode 100644 index 00000000..3ea5aa3e --- /dev/null +++ b/causal-claim-validity-guard/artifacts/causal-claim-summary.svg @@ -0,0 +1,14 @@ + + + Causal Claim Validity Guard + AI peer-review gate for causal language, design support, comparator evidence, and uncertainty. + RELEASE: 2 + + + REVISE: 1 + + + HOLD: 1 + + + \ No newline at end of file diff --git a/causal-claim-validity-guard/artifacts/demo.mp4 b/causal-claim-validity-guard/artifacts/demo.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..3e19b58d8c79feec8f1dcf93ba3b0f84187a4e6f GIT binary patch literal 49252 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 literal 0 HcmV?d00001 diff --git a/causal-claim-validity-guard/data/sample_review_packets.json b/causal-claim-validity-guard/data/sample_review_packets.json new file mode 100644 index 00000000..d19271ca --- /dev/null +++ b/causal-claim-validity-guard/data/sample_review_packets.json @@ -0,0 +1,85 @@ +[ + { + "id": "cardio-rct-001", + "title": "Remote coaching reduces systolic blood pressure in high-risk adults", + "domain": "clinical trials", + "claim": { + "intent": "causal", + "text": "Remote coaching reduces systolic blood pressure after twelve weeks." + }, + "evidence": { + "design": "randomized_controlled_trial", + "hasComparator": true, + "temporalOrder": true, + "confounderStrategy": "blocked randomization with prespecified covariate balance checks", + "effectSize": "-7.4 mmHg", + "confidenceInterval": "95% CI -10.1 to -4.9", + "subgroups": [ + { + "name": "participants over 65", + "claim": "coaching may reduce systolic blood pressure", + "powered": true + } + ] + }, + "conclusion": "Remote coaching reduces systolic blood pressure in the randomized study population." + }, + { + "id": "education-cross-sectional-002", + "title": "Daily tutoring app usage improves mathematics scores", + "domain": "education", + "claim": { + "intent": "causal", + "text": "Daily tutoring app usage improves mathematics scores." + }, + "evidence": { + "design": "cross_sectional", + "hasComparator": false, + "temporalOrder": false, + "effectSize": "0.31 SD" + }, + "conclusion": "The tutoring app drives higher scores across schools." + }, + { + "id": "ecology-longitudinal-003", + "title": "Wetland restoration is associated with higher amphibian diversity", + "domain": "ecology", + "claim": { + "intent": "associational", + "text": "Wetland restoration is associated with higher amphibian diversity." + }, + "evidence": { + "design": "longitudinal_observational", + "hasComparator": true, + "temporalOrder": true, + "confounderStrategy": "matched watersheds and rainfall adjustment", + "effectSize": "18% higher species richness", + "confidenceInterval": "95% CI 6% to 29%" + }, + "conclusion": "Wetland restoration is associated with higher observed amphibian diversity after adjustment." + }, + { + "id": "oncology-subgroup-004", + "title": "Dose timing prevents relapse in a small oncology subgroup", + "domain": "oncology", + "claim": { + "intent": "causal", + "text": "Dose timing prevents relapse in the biomarker-positive subgroup." + }, + "evidence": { + "design": "quasi_experimental", + "hasComparator": true, + "temporalOrder": true, + "confounderStrategy": "propensity score weighting", + "effectSize": "hazard ratio 0.72", + "subgroups": [ + { + "name": "biomarker-positive subgroup", + "claim": "dose timing prevents relapse", + "powered": false + } + ] + }, + "conclusion": "Dose timing prevents relapse for biomarker-positive patients." + } +] diff --git a/causal-claim-validity-guard/package.json b/causal-claim-validity-guard/package.json new file mode 100644 index 00000000..90b5945f --- /dev/null +++ b/causal-claim-validity-guard/package.json @@ -0,0 +1,13 @@ +{ + "name": "causal-claim-validity-guard", + "version": "1.0.0", + "description": "Dependency-free causal claim validity guard for AI research assistant review packets.", + "main": "src/index.js", + "scripts": { + "test": "node --test", + "demo": "node scripts/demo.js", + "video": "node scripts/render-demo-video.js", + "check": "node --check src/index.js && node --check scripts/demo.js" + }, + "license": "MIT" +} diff --git a/causal-claim-validity-guard/scripts/demo.js b/causal-claim-validity-guard/scripts/demo.js new file mode 100644 index 00000000..afedd27e --- /dev/null +++ b/causal-claim-validity-guard/scripts/demo.js @@ -0,0 +1,77 @@ +"use strict"; + +const fs = require("node:fs"); +const path = require("node:path"); +const { analyzePackets } = require("../src"); + +const root = path.resolve(__dirname, ".."); +const packets = JSON.parse(fs.readFileSync(path.join(root, "data", "sample_review_packets.json"), "utf8")); +const report = analyzePackets(packets); +const outDir = path.join(root, "artifacts"); + +fs.mkdirSync(outDir, { recursive: true }); +fs.writeFileSync(path.join(outDir, "causal-claim-review.json"), `${JSON.stringify(report, null, 2)}\n`); +fs.writeFileSync(path.join(outDir, "causal-claim-review.md"), renderMarkdown(report)); +fs.writeFileSync(path.join(outDir, "causal-claim-summary.svg"), renderSvg(report)); + +console.log(JSON.stringify({ + packetCount: report.packetCount, + decisionCounts: report.decisionCounts, + artifacts: [ + "artifacts/causal-claim-review.json", + "artifacts/causal-claim-review.md", + "artifacts/causal-claim-summary.svg" + ] +}, null, 2)); + +function renderMarkdown(report) { + const lines = [ + "# Causal Claim Validity Review", + "", + `Generated: ${report.generatedAt}`, + "", + "## Decision Counts", + "", + `- RELEASE: ${report.decisionCounts.RELEASE}`, + `- REVISE: ${report.decisionCounts.REVISE}`, + `- HOLD: ${report.decisionCounts.HOLD}`, + "", + "## Packet Reviews", + "" + ]; + + for (const review of report.reviews) { + lines.push(`### ${review.title}`); + lines.push(`Decision: ${review.decision}`); + lines.push(`Confidence: ${review.confidence}`); + lines.push(`Reviewer note: ${review.reviewerNote}`); + for (const item of review.findings) { + lines.push(`- ${item.severity.toUpperCase()} ${item.code}: ${item.message}`); + lines.push(` Remediation: ${item.remediation}`); + } + lines.push(""); + } + + return `${lines.join("\n")}\n`; +} + +function renderSvg(report) { + const hold = report.decisionCounts.HOLD; + const revise = report.decisionCounts.REVISE; + const release = report.decisionCounts.RELEASE; + return ` + + Causal Claim Validity Guard + AI peer-review gate for causal language, design support, comparator evidence, and uncertainty. + ${bar("RELEASE", release, 48, 148, "#4fbf9f")} + ${bar("REVISE", revise, 48, 214, "#d6a137")} + ${bar("HOLD", hold, 48, 280, "#d85f73")} +`; +} + +function bar(label, count, x, y, color) { + const width = 96 + count * 110; + return `${label}: ${count} + + `; +} diff --git a/causal-claim-validity-guard/scripts/render-demo-video.js b/causal-claim-validity-guard/scripts/render-demo-video.js new file mode 100644 index 00000000..630d3393 --- /dev/null +++ b/causal-claim-validity-guard/scripts/render-demo-video.js @@ -0,0 +1,52 @@ +"use strict"; + +const path = require("node:path"); +const { spawnSync } = require("node:child_process"); + +const root = path.resolve(__dirname, ".."); +const ffmpeg = path.resolve( + root, + "..", + "..", + "tool_downloads", + "video_tools", + "node_modules", + "ffmpeg-static", + "ffmpeg.exe" +); +const out = path.join(root, "artifacts", "demo.mp4"); +const font = "C\\:/Windows/Fonts/arial.ttf"; + +const draw = [ + `drawtext=fontfile=${font}:text='Causal Claim Validity Guard':x=64:y=56:fontsize=46:fontcolor=black`, + `drawtext=fontfile=${font}:text='AI peer-review gate for causal overclaiming':x=64:y=122:fontsize=25:fontcolor=0x34413b`, + `drawtext=fontfile=${font}:text='Synthetic packets analyzed 4':x=64:y=212:fontsize=34:fontcolor=black`, + `drawtext=fontfile=${font}:text='RELEASE 2 REVISE 1 HOLD 1':x=64:y=270:fontsize=38:fontcolor=0x245fd6`, + `drawtext=fontfile=${font}:text='Checks design support comparator evidence temporal order confounders uncertainty':x=64:y=360:fontsize=26:fontcolor=0x34413b`, + `drawtext=fontfile=${font}:text='Validation passed npm test plus demo plus syntax check':x=64:y=424:fontsize=26:fontcolor=0x34413b`, + `drawtext=fontfile=${font}:text='No external APIs credentials private manuscripts or live data':x=64:y=488:fontsize=26:fontcolor=0x34413b` +].join(","); + +const result = spawnSync(ffmpeg, [ + "-y", + "-f", + "lavfi", + "-i", + "color=c=0xf8faf7:s=1280x720:d=8:r=30", + "-vf", + draw, + "-c:v", + "libx264", + "-pix_fmt", + "yuv420p", + "-movflags", + "+faststart", + out +], { encoding: "utf8" }); + +if (result.status !== 0) { + process.stderr.write(result.stderr || result.stdout); + process.exit(result.status || 1); +} + +console.log(JSON.stringify({ out, bytes: require("node:fs").statSync(out).size }, null, 2)); diff --git a/causal-claim-validity-guard/src/index.js b/causal-claim-validity-guard/src/index.js new file mode 100644 index 00000000..6bba8742 --- /dev/null +++ b/causal-claim-validity-guard/src/index.js @@ -0,0 +1,195 @@ +"use strict"; + +const CAUSAL_TERMS = [ + "cause", + "causes", + "caused", + "causal", + "effect of", + "leads to", + "resulted in", + "prevents", + "reduces", + "increases", + "drives", + "improves", + "protects against" +]; + +const CAUSAL_DESIGNS = new Set([ + "randomized_controlled_trial", + "quasi_experimental", + "instrumental_variable", + "regression_discontinuity", + "difference_in_differences", + "natural_experiment" +]); + +function includesCausalLanguage(value) { + const text = String(value || "").toLowerCase(); + return CAUSAL_TERMS.some((term) => text.includes(term)); +} + +function finding(code, severity, message, evidence, remediation) { + return { code, severity, message, evidence, remediation }; +} + +function severityWeight(severity) { + if (severity === "critical") return 5; + if (severity === "major") return 3; + return 1; +} + +function decisionFromFindings(findings) { + const score = findings.reduce((total, item) => total + severityWeight(item.severity), 0); + const critical = findings.some((item) => item.severity === "critical"); + const major = findings.filter((item) => item.severity === "major").length; + if (critical || major >= 2 || score >= 7) return "HOLD"; + if (major >= 1 || score >= 2) return "REVISE"; + return "RELEASE"; +} + +function confidenceFromFindings(findings) { + const penalty = findings.reduce((total, item) => total + severityWeight(item.severity), 0); + return Math.max(0.15, Number((0.95 - penalty * 0.08).toFixed(2))); +} + +function normalizePacket(packet) { + return { + id: packet.id, + title: packet.title, + domain: packet.domain || "general science", + claim: packet.claim || {}, + evidence: packet.evidence || {}, + conclusion: packet.conclusion || "", + reviewerContext: packet.reviewerContext || {} + }; +} + +function analyzePacket(input) { + const packet = normalizePacket(input); + const claimText = packet.claim.text || ""; + const conclusion = packet.conclusion || ""; + const evidence = packet.evidence; + const design = evidence.design || "unspecified"; + const causalIntent = packet.claim.intent === "causal" || includesCausalLanguage(claimText); + const conclusionCausal = includesCausalLanguage(conclusion); + const findings = []; + + if (causalIntent && !CAUSAL_DESIGNS.has(design)) { + findings.push(finding( + "DESIGN_CANNOT_SUPPORT_CAUSAL_LANGUAGE", + design === "cross_sectional" || design === "case_series" ? "critical" : "major", + "The manuscript uses causal language, but the stated design does not independently support causal inference.", + { design, claim: claimText }, + "Replace causal verbs with associational language or add a causal identification strategy with assumptions and diagnostics." + )); + } + + if ((causalIntent || conclusionCausal) && evidence.hasComparator !== true) { + findings.push(finding( + "MISSING_COMPARATOR", + "major", + "Causal review requires a comparator, control, counterfactual, or explicit quasi-experimental contrast.", + { hasComparator: evidence.hasComparator }, + "Add a comparator/control description and explain why it approximates the counterfactual." + )); + } + + if ((causalIntent || conclusionCausal) && evidence.temporalOrder !== true) { + findings.push(finding( + "TEMPORAL_ORDER_GAP", + "major", + "The evidence packet does not show that exposure or intervention preceded the outcome.", + { temporalOrder: evidence.temporalOrder }, + "Document timing of exposure, intervention, measurement, and outcome windows." + )); + } + + if ((causalIntent || conclusionCausal) && !evidence.confounderStrategy) { + findings.push(finding( + "CONFOUNDER_STRATEGY_MISSING", + "major", + "No confounder strategy is described for a causal claim.", + { confounderStrategy: evidence.confounderStrategy || null }, + "Describe randomization, matching, adjustment, stratification, sensitivity analysis, or a defensible natural experiment." + )); + } + + if ((causalIntent || conclusionCausal) && evidence.effectSize && !evidence.confidenceInterval) { + findings.push(finding( + "UNCERTAINTY_NOT_REPORTED", + "minor", + "The packet reports an effect size without an uncertainty interval.", + { effectSize: evidence.effectSize, confidenceInterval: evidence.confidenceInterval || null }, + "Add confidence, credible, or bootstrap intervals and specify the estimation method." + )); + } + + if (packet.claim.intent === "associational" && conclusionCausal) { + findings.push(finding( + "CONCLUSION_OVERSTATES_ASSOCIATION", + "major", + "The claim is labeled associational but the conclusion uses causal wording.", + { intent: packet.claim.intent, conclusion }, + "Align the conclusion with the claim intent or provide a causal design and evidence upgrade." + )); + } + + if (Array.isArray(evidence.subgroups) && evidence.subgroups.some((group) => group.powered !== true && includesCausalLanguage(group.claim))) { + findings.push(finding( + "UNDERPOWERED_SUBGROUP_CAUSAL_CLAIM", + "minor", + "At least one subgroup claim uses causal language without a powered subgroup analysis.", + { subgroups: evidence.subgroups }, + "Mark subgroup findings exploratory unless power and multiplicity controls are documented." + )); + } + + const decision = decisionFromFindings(findings); + return { + id: packet.id, + title: packet.title, + domain: packet.domain, + decision, + confidence: confidenceFromFindings(findings), + causalLanguageDetected: causalIntent || conclusionCausal, + findingCount: findings.length, + findings, + reviewerNote: reviewerNote(packet, decision, findings) + }; +} + +function reviewerNote(packet, decision, findings) { + if (decision === "RELEASE") { + return `${packet.domain}: causal wording is aligned with the study design and evidence packet.`; + } + const first = findings[0]; + return `${packet.domain}: ${decision} before release because ${first.message.toLowerCase()}`; +} + +function analyzePackets(packets) { + const reviews = packets.map(analyzePacket); + const counts = reviews.reduce((acc, review) => { + acc[review.decision] = (acc[review.decision] || 0) + 1; + return acc; + }, {}); + return { + generatedAt: new Date().toISOString(), + packetCount: reviews.length, + decisionCounts: { + RELEASE: counts.RELEASE || 0, + REVISE: counts.REVISE || 0, + HOLD: counts.HOLD || 0 + }, + reviews + }; +} + +module.exports = { + CAUSAL_TERMS, + CAUSAL_DESIGNS, + analyzePacket, + analyzePackets, + includesCausalLanguage +}; diff --git a/causal-claim-validity-guard/test/causal-claim-validity-guard.test.js b/causal-claim-validity-guard/test/causal-claim-validity-guard.test.js new file mode 100644 index 00000000..012e5f89 --- /dev/null +++ b/causal-claim-validity-guard/test/causal-claim-validity-guard.test.js @@ -0,0 +1,80 @@ +"use strict"; + +const assert = require("node:assert/strict"); +const test = require("node:test"); +const { analyzePacket, analyzePackets, includesCausalLanguage } = require("../src"); + +test("detects causal language in common manuscript wording", () => { + assert.equal(includesCausalLanguage("The intervention reduces risk."), true); + assert.equal(includesCausalLanguage("The marker is associated with risk."), false); +}); + +test("holds causal claims from cross-sectional evidence without comparator or temporal order", () => { + const review = analyzePacket({ + id: "bad-causal", + title: "App usage improves outcomes", + claim: { + intent: "causal", + text: "The app improves outcomes." + }, + evidence: { + design: "cross_sectional", + hasComparator: false, + temporalOrder: false, + effectSize: "0.4 SD" + }, + conclusion: "The app causes better outcomes." + }); + + assert.equal(review.decision, "HOLD"); + assert.ok(review.findings.some((item) => item.code === "DESIGN_CANNOT_SUPPORT_CAUSAL_LANGUAGE")); + assert.ok(review.findings.some((item) => item.code === "MISSING_COMPARATOR")); + assert.ok(review.findings.some((item) => item.code === "TEMPORAL_ORDER_GAP")); +}); + +test("releases supported randomized causal claims", () => { + const review = analyzePacket({ + id: "good-rct", + title: "Coaching reduces blood pressure", + domain: "clinical trials", + claim: { + intent: "causal", + text: "Coaching reduces blood pressure." + }, + evidence: { + design: "randomized_controlled_trial", + hasComparator: true, + temporalOrder: true, + confounderStrategy: "blocked randomization", + effectSize: "-7 mmHg", + confidenceInterval: "95% CI -10 to -4" + }, + conclusion: "Coaching reduces blood pressure in this randomized trial." + }); + + assert.equal(review.decision, "RELEASE"); + assert.equal(review.findingCount, 0); +}); + +test("summarizes packet decisions deterministically", () => { + const report = analyzePackets([ + { + id: "a", + title: "A", + claim: { intent: "associational", text: "A is associated with B." }, + evidence: { design: "cross_sectional" }, + conclusion: "A is associated with B." + }, + { + id: "b", + title: "B", + claim: { intent: "causal", text: "A prevents B." }, + evidence: { design: "case_series", hasComparator: false, temporalOrder: false }, + conclusion: "A prevents B." + } + ]); + + assert.equal(report.packetCount, 2); + assert.equal(report.decisionCounts.RELEASE, 1); + assert.equal(report.decisionCounts.HOLD, 1); +});