diff --git a/plugins/hetaoBackend/scientific-research-workflows/LICENSE b/plugins/hetaoBackend/scientific-research-workflows/LICENSE new file mode 100644 index 0000000..7ed50c7 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 K-Dense Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/hetaoBackend/scientific-research-workflows/README.md b/plugins/hetaoBackend/scientific-research-workflows/README.md new file mode 100644 index 0000000..beb914a --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/README.md @@ -0,0 +1,86 @@ +# Scientific Research Workflows + +This MiniMax Code Plugin packages nine complementary, local-first Skills from +[K-Dense Scientific Agent Skills](https://github.com/K-Dense-AI/scientific-agent-skills). +It helps researchers move from an early idea to a defensible study plan, analysis, manuscript, and +review without installing the upstream repository's unrelated, network-dependent, or +separately-licensed Skills. + +## Included Skills + +| Skill | Upstream version | Use it for | +| --- | ---: | --- | +| `scientific-brainstorming` | 1.1 | Generate and challenge candidate research directions. | +| `hypothesis-generation` | 2.1 | Turn observations into rival, testable hypotheses and predictions. | +| `experimental-design` | 1.1 | Plan randomization, blocking, controls, factorial designs, and study layouts. | +| `statistical-power` | 1.0 | Estimate sample sizes, detectable effects, and simulation-based power. | +| `statistical-analysis` | 1.1 | Select tests, check assumptions, estimate effects, and report results. | +| `uncertainty-and-units` | 1.0 | Check dimensions and propagate measurement uncertainty. | +| `scientific-writing` | 2.0 | Draft and audit evidence-traceable manuscripts and reports. | +| `peer-review` | 2.1 | Prepare authorized, constructive, evidence-bounded review drafts. | +| `scholar-evaluation` | 2.1 | Give qualitative developmental feedback on scholarly work. | + +The Skills, references, assets, and scripts are sourced from upstream commit +[`1dd0fccf46fc3c9855c4a0c313a0c57fe4319883`](https://github.com/K-Dense-AI/scientific-agent-skills/commit/1dd0fccf46fc3c9855c4a0c313a0c57fe4319883). +To satisfy this repository's hosted-package validator, the scientific-writing templates use the +`[[DRAFT:...]]` label for unfinished fields; its local linter and scaffold helper were updated +consistently, with no change to the marker's meaning. +The bundle deliberately contains only Skills that declare the MIT license. The upstream repository +contains other Skills with different terms; those are not included here. + +## Try it + +```text +I am planning a two-site experiment comparing three treatments, but samples arrive in four weekly +batches. Help me define competing hypotheses, design randomization and blocking, estimate the sample +size for 80% power, and produce a preregistration-ready analysis outline. State every assumption and +do not invent pilot data. +``` + +Expected result: MiniMax Code separates hypotheses from established evidence, identifies the +experimental unit and likely batch/site confounders, proposes a reproducible blocked design, asks for +the effect-size inputs needed for power, and creates an analysis plan with explicit uncertainty and +reporting boundaries. + +## Requirements + +- MiniMax Code with Agent Skills support. +- Core guidance is platform-neutral and works without installing packages. +- Optional bundled command-line helpers require Python: + - Python 3.11+ standard library for the brainstorming, hypothesis, writing, peer-review, and + scholar-evaluation helpers. + - Python 3.10+ plus `numpy`, `pandas`, and `pyDOE3` for experimental-design helpers. + - Python 3.10+ plus `numpy`, `pandas`, `scipy`, `statsmodels`, `pingouin`, and `matplotlib` for + statistical power and analysis helpers. `pymc`, `arviz`, and `lifelines` are optional for the + corresponding Bayesian and survival workflows. + - Python 3.12+ plus `pint`, `uncertainties`, `numpy`, and `scipy` for uncertainty-and-units numeric + helpers. +- Supported on macOS, Windows, and Linux when the selected optional Python dependencies are available. +- No account or paid service is required. + +## Data and network + +- The Plugin defines no MCP server, telemetry, lifecycle hook, installer, or background process. +- Bundled scripts process bounded JSON, CSV, Markdown, or research data locally and do not make + network requests. +- The Skills may read user-selected manuscripts, study plans, or datasets from the active workspace + and may write requested local reports or intermediate artifacts. +- Confidential, unpublished, personal, clinical, or controlled material must remain local unless the + user has authorization and explicitly chooses an external destination. The peer-review and writing + Skills include stricter venue and confidentiality checks. +- No credentials required. + +Disabling or uninstalling the Plugin removes these Skills from discovery. It does not remove Python +packages the user installed independently or delete files the user asked MiniMax Code to create. + +## Attribution and maintenance + +- Upstream project: +- Upstream snapshot: `1dd0fccf46fc3c9855c4a0c313a0c57fe4319883` (2026-08-31) +- Original author: K-Dense Inc. +- MiniMax Code package maintainer: [hetaoBackend](https://github.com/hetaoBackend) +- License: MIT; see [`LICENSE`](LICENSE). + +This community package is an independently maintained selection, not an endorsement by K-Dense or +MiniMax. Future upstream updates require an explicit review of source changes, dependency behavior, +and each included Skill's license before the pinned snapshot is advanced. diff --git a/plugins/hetaoBackend/scientific-research-workflows/plugin.json b/plugins/hetaoBackend/scientific-research-workflows/plugin.json new file mode 100644 index 0000000..0faea12 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/plugin.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "name": "scientific-research-workflows", + "version": "1.0.0", + "description": "Nine local-first scientific research Skills for ideation, hypotheses, study design, statistics, uncertainty, writing, peer review, and scholarly evaluation.", + "author": { + "name": "K-Dense Inc.; MiniMax Code packaging by hetaoBackend", + "url": "https://k-dense.ai" + }, + "homepage": "https://github.com/K-Dense-AI/scientific-agent-skills", + "repository": "https://github.com/K-Dense-AI/scientific-agent-skills", + "license": "MIT", + "keywords": [ + "minimax-code", + "science", + "research", + "statistics", + "peer-review", + "scientific-writing" + ] +} diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/experimental-design/SKILL.md b/plugins/hetaoBackend/scientific-research-workflows/skills/experimental-design/SKILL.md new file mode 100644 index 0000000..44c17b7 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/experimental-design/SKILL.md @@ -0,0 +1,234 @@ +--- +name: experimental-design +description: Design experiments and studies BEFORE data is collected — choosing a design, randomizing, blocking, and laying out treatment combinations so results are interpretable. Use whenever someone is planning a study, asks how to assign subjects/samples to groups, mentions randomization, blocking, stratification, controls, factorial or fractional-factorial designs, design of experiments (DOE), screening many factors, response-surface optimization, crossover or repeated-measures or split-plot designs, cluster/group randomization, Latin squares, plate layouts, batch/run-order effects, replication vs. pseudoreplication, or sequential/adaptive/group-sequential designs. Trigger even for informal phrasings like "how should I set up this experiment", "how do I avoid confounding", "what's the best way to test these 6 factors", or "assign these mice to conditions". For computing the sample size or power once the design is chosen, use statistical-power; for analyzing data already collected, use statistical-analysis. +allowed-tools: Read Write Edit Bash +compatibility: Requires Python >=3.10. Scripts use numpy, pandas, and pyDOE3 (DOE matrices). Install with uv as shown below. +license: MIT license +metadata: + version: "1.1" + skill-author: K-Dense Inc. +--- + +# Experimental Design + +## Overview + +The design of a study — how units are assigned to conditions, what is held constant, what is varied, and in what structure — determines what questions the data can answer. No analysis can rescue a confounded or pseudoreplicated design after the fact. This skill is about the decisions made *before* data collection: picking a design that isolates the effect of interest, randomizing to license causal claims, blocking to remove known nuisance variation, and structuring multi-factor experiments so effects are estimable rather than tangled together. + +The three ideas behind almost every good design (Fisher's principles): +- **Randomization** — assign treatments at random so that confounders, known and unknown, are balanced in expectation. This is what turns a comparison into a causal claim. +- **Replication** — independent repetition at the right level, so you can estimate variability and your effects aren't artifacts of a single unit. The most common fatal error is **pseudoreplication**: counting repeated measurements on the same unit as independent replicates. +- **Blocking / local control** — group similar units (by batch, day, site, litter) and randomize within blocks, removing that nuisance variation from the error term instead of letting it inflate noise. + +This skill helps you choose among design types, generate the actual randomization or DOE layout (with reproducible scripts), and avoid the structural mistakes that make data uninterpretable. + +## When to Use This Skill + +- Planning any comparative experiment or trial and deciding how to assign units +- Randomizing subjects/samples to arms (simple, blocked, stratified, or cluster) +- Removing nuisance variation by blocking or stratification +- Designing multi-factor experiments: full or fractional factorial, screening designs +- Optimizing a response over continuous factors (response-surface designs) +- Within-subject / repeated-measures, crossover, split-plot, or Latin-square designs +- Cluster- or group-randomized designs (sites, clinics, classrooms, litters) +- Deciding the number and level of replicates and avoiding pseudoreplication +- Sequential, group-sequential, or adaptive designs with interim analyses +- Laying out plates/batches and randomizing run order to defeat drift + +## Installation + +```bash +uv pip install "numpy>=1.26" "pandas>=2.0" pyDOE3 +``` + +`pyDOE3` is the maintained successor to pyDOE/pyDOE2 and supplies factorial, +fractional-factorial, Plackett-Burman, central-composite, Box-Behnken, and +Latin-hypercube generators. The bundled scripts wrap it to return designs in real +factor units with named columns and randomized run order. + +--- + +## Choosing a design + +Start from the question and the structure of your units, not from a favorite design. + +``` +What are you trying to learn? +│ +├─ Compare a few predefined conditions (A vs B vs C)? +│ ├─ Units independent, possibly with a known nuisance factor (day, batch, site)? +│ │ → Completely randomized (no nuisance) or RANDOMIZED BLOCK design. +│ ├─ Each unit can receive every condition in sequence (washout possible)? +│ │ → CROSSOVER / repeated-measures design (more power, watch carry-over). +│ └─ You can only randomize groups, not individuals (schools, clinics)? +│ → CLUSTER-randomized design (analyze at the cluster level; see pseudoreplication). +│ +├─ Screen MANY factors (5+) to find the few that matter? +│ → FRACTIONAL FACTORIAL or PLACKETT-BURMAN screening design. +│ +├─ Quantify main effects AND interactions among a handful of factors? +│ → FULL 2^k FACTORIAL design. +│ +├─ Find the settings that OPTIMIZE a response (curvature matters)? +│ → RESPONSE-SURFACE design: central composite or Box-Behnken. +│ +└─ Explore a simulation/computer model over a continuous space? + → SPACE-FILLING design: Latin hypercube. +``` + +Detailed guidance per branch: +- **Randomization, blocking, stratification, controls** → `references/randomization_and_blocking.md` +- **Factorial, fractional-factorial, screening, response-surface, DOE concepts (aliasing, resolution)** → `references/factorial_and_doe.md` +- **Crossover, repeated-measures, split-plot, Latin-square, cluster, nested designs** → `references/design_types.md` +- **Sequential, group-sequential, and adaptive designs (interim analyses)** → `references/sequential_and_adaptive.md` + +--- + +## Generating the design + +Two scripts produce ready-to-use, reproducible layouts. Run them from the skill's +`scripts/` directory or add it to `sys.path`. Everything is seeded so the exact +schedule can be archived and regenerated — a requirement for trial registration +and good lab practice. + +### Randomization / allocation schedules — `scripts/randomization.py` + +```python +from randomization import ( + simple_randomization, block_randomization, + stratified_block_randomization, cluster_randomization, + assign_factorial_runs, arm_balance, +) + +# Permuted blocks keep the arms balanced throughout enrollment (use for n < ~100 +# or sequential intake — simple randomization can drift out of balance with small n) +sched = block_randomization(n=60, arms=["treatment", "control"], seed=42) + +# Balance a prognostic variable across arms by randomizing within each stratum +sched = stratified_block_randomization({"siteA": 30, "siteB": 30}, + arms=["drug", "placebo"], ratio=(2, 1), seed=42) + +# Randomize whole clusters, not individuals (the cluster is the unit) +sched = cluster_randomization(["clinic1", "clinic2", "clinic3", "clinic4"], seed=42) + +arm_balance(sched) # sanity-check the counts per arm +sched.to_csv("allocation_schedule.csv", index=False) +``` + +Choosing among them: **simple** is fine for large n but can produce imbalance with +small n; **block** guarantees balance throughout; **stratified block** additionally +balances a known prognostic factor; **cluster** is mandatory when the intervention +is delivered at a group level. See `references/randomization_and_blocking.md`. + +### DOE matrices — `scripts/doe_designs.py` + +```python +from doe_designs import ( + full_factorial, two_level_factorial, fractional_factorial, + plackett_burman, central_composite, box_behnken, latin_hypercube, +) + +# Factors as real-world (low, high) ranges -> design comes back in real units +factors = {"temp_C": (20, 60), "conc_mM": (1, 10), "pH": (6, 8)} + +# Full 2^3: all main effects + all interactions (8 runs), run order randomized +design = two_level_factorial(factors, seed=42) + +# Screen 7 factors cheaply (main effects only) +many = {f"factor_{i}": (0, 1) for i in range(7)} +design = plackett_burman(many, seed=42) + +# Optimize over 2 factors with curvature (response-surface) +design = central_composite({"temp_C": (20, 60), "conc_mM": (1, 10)}, seed=42) + +design.to_csv("experimental_runs.csv", index=False) +``` + +Run order is randomized by default so factors aren't confounded with time/drift +(machine warm-up, reagent aging). See `references/factorial_and_doe.md` for picking +generators, reading the alias structure, and choosing resolution. + +--- + +## The mistakes that ruin studies + +These are structural — they can't be fixed in analysis, only in design. + +1. **Pseudoreplication.** Treating repeated measurements of one unit as independent + replicates: 3 mice with 100 cells each is n = 3 (mice), not n = 300 (cells), for + any treatment applied to the mouse. The replicate must be at the level the + treatment is randomized. This single error invalidates a large share of published + experiments. Randomize and replicate at the right level; analyze with the nesting + respected (mixed model). See `references/design_types.md`. +2. **Confounding by a nuisance variable.** Running all treatment samples on Monday + and all controls on Tuesday confounds treatment with day. Randomize across, or + block on, every nuisance factor you can name (batch, day, plate, technician, + instrument, position). +3. **No or broken randomization.** Convenience assignment (first-come → treatment) + lets confounders sneak in. Use a seeded schedule and follow it. +4. **No proper control.** Without a concurrent control (and, where relevant, a + vehicle/sham and blinding), you can't separate the treatment effect from time, + placebo, or handling effects. +5. **Batch effects mistaken for biology.** In omics especially, process samples in a + randomized/blocked order across batches; never let batch align with the condition. +6. **Edge/position effects on plates.** Evaporation and thermal gradients make plate + edges differ. Randomize or block sample positions; don't put all controls in + column 1. +7. **Aliasing ignored in fractional designs.** A low-resolution fractional factorial + confounds main effects with interactions; know your alias structure before + concluding a factor "has no effect." +8. **Optimizing without curvature.** A two-level factorial can't detect a curved + response; you'll miss an interior optimum. Use a response-surface design. + +--- + +## Workflow + +1. **State the question, the unit, and the response.** What is randomized? What is + measured? At what level is a true independent replicate? This determines everything. +2. **List nuisance factors** (batch, day, site, operator, position) — plan to block, + stratify, or randomize across each. +3. **Pick the design** using the decision tree and reference files. +4. **Decide replication** at the correct level (and get n from the + **statistical-power** skill for the chosen design). +5. **Generate the layout** with `randomization.py` / `doe_designs.py`, seeded. +6. **Randomize run/processing order** and plate/batch positions. +7. **Document** the design, seed, and schedule (pre-register if possible) so the + analysis is confirmatory and the layout is auditable. +8. **Match the analysis to the design** — blocks, strata, clusters, and nesting must + appear in the model (hand off to **statistical-analysis** / **statsmodels**). + +--- + +## Resources + +### Scripts +- `scripts/randomization.py` — seeded allocation schedules: `simple_randomization`, + `block_randomization`, `stratified_block_randomization`, `cluster_randomization`, + `assign_factorial_runs`, `arm_balance`. +- `scripts/doe_designs.py` — DOE matrices in real units: `full_factorial`, + `two_level_factorial`, `fractional_factorial`, `plackett_burman`, + `central_composite`, `box_behnken`, `latin_hypercube`. + +### References +- `references/randomization_and_blocking.md` — randomization methods, blocking, + stratification, controls, blinding, batch/plate layout. +- `references/factorial_and_doe.md` — factorial and fractional designs, resolution + and aliasing, screening, and response-surface methodology. +- `references/design_types.md` — completely randomized, randomized block, crossover, + repeated-measures, split-plot, Latin-square, cluster, and nested designs; the + pseudoreplication problem in depth. +- `references/sequential_and_adaptive.md` — group-sequential designs, alpha spending, + interim stopping, and adaptive sample-size re-estimation. + +### Related skills +- **statistical-power** — required sample size / power for the design you've chosen. +- **statistical-analysis** — running and reporting the analysis after collection. +- **statsmodels** / **pymc** — fitting the models the design implies. + +### Key references +- Fisher, R. A. (1935). *The Design of Experiments*. +- Montgomery, D. C. (2019). *Design and Analysis of Experiments* (10th ed.). +- Hurlbert, S. H. (1984). Pseudoreplication and the design of ecological field + experiments. *Ecological Monographs*, 54(2), 187–211. +- Lazic, S. E. (2016). *Experimental Design for Laboratory Biologists*. diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/experimental-design/references/design_types.md b/plugins/hetaoBackend/scientific-research-workflows/skills/experimental-design/references/design_types.md new file mode 100644 index 0000000..f52ebc8 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/experimental-design/references/design_types.md @@ -0,0 +1,129 @@ +# Design Types and the Replication Structure + +Choosing the right design structure is mostly about matching the *unit of +randomization* and the *unit of replication* to your question, and respecting any +nesting in the analysis. This file walks through the standard structures and then +treats the single most common fatal error — pseudoreplication — in depth. + +## Table of contents +- [Completely randomized design](#completely-randomized-design) +- [Randomized complete block design](#randomized-complete-block-design) +- [Latin square](#latin-square) +- [Repeated-measures and crossover](#repeated-measures-and-crossover) +- [Split-plot designs](#split-plot-designs) +- [Cluster / group-randomized designs](#cluster--group-randomized-designs) +- [Nested designs and pseudoreplication](#nested-designs-and-pseudoreplication) + +## Completely randomized design + +Units are assigned to treatments purely at random, no blocking. Simplest design; +appropriate when units are homogeneous and there's no identifiable nuisance factor. +Analyze with one-way ANOVA / regression. If units are *not* homogeneous, the +nuisance variation inflates error — block instead. + +## Randomized complete block design + +Group units into **blocks** of similar units (day, batch, litter), and randomize all +treatments *within* each block. Every treatment appears once per block. The +between-block variation is removed from the error term, sharply increasing precision +when blocks differ. Analyze with `treatment + block` in the model. This is the +default upgrade over a completely randomized design whenever a nuisance factor exists. + +## Latin square + +Controls **two** nuisance factors simultaneously with a square layout: each treatment +appears exactly once in every row and every column. Classic uses: row = day, column = +position/order, cell = treatment. Requires #treatments = #rows = #columns, and assumes +no interactions between the blocking factors and treatment. Efficient when both +nuisance dimensions matter and runs are limited. (Graeco-Latin squares extend this to +three nuisance factors.) + +## Repeated-measures and crossover + +Each subject receives more than one condition, serving as its own control. This +removes between-subject variation — usually the largest noise source — so these +designs are far more powerful per subject. + +- **Repeated measures:** the same units measured under several conditions or over + time. +- **Crossover:** each subject receives each treatment in sequence, with **washout** + periods between to clear carry-over. Subjects are randomized to treatment *orders* + (e.g. an AB/BA crossover; or a Williams square for ≥3 treatments to balance order). + +Watch for: +- **Carry-over / residual effects** — an effect of the previous treatment persisting + into the next period. Adequate washout is essential; otherwise the design is biased. +- **Period effects** — systematic change over time (learning, fatigue, disease + progression). Balanced orders let you separate period from treatment. +- **Correlation within subject** — the repeated observations are not independent; the + analysis must model it (mixed model / repeated-measures ANOVA). Sample-size/power + for these depends on the within-subject correlation — use simulation in the + **statistical-power** skill. + +## Split-plot designs + +Arises when some factors are **hard to change** (applied to large units) and others +are **easy to change** (applied to sub-units). The hard-to-change factor is randomized +to whole plots; the easy factor is randomized to subplots within each whole plot. +Example: oven temperature (whole plot — you can't re-set it per sample) × coating type +(subplot — applied per sample). Crucially there are **two different error terms** — one +for whole-plot factors, one for subplot factors — and the analysis must use both. +Treating a split-plot as a completely randomized factorial gives wrong (usually +anticonservative) tests for the whole-plot factor. Industrial DOE and agricultural +trials are full of accidental split-plots; recognize when a factor can't be reset per +run. + +## Cluster / group-randomized designs + +When the intervention is delivered to a *group* (a clinic's protocol, a classroom +curriculum, a village water supply), you can only randomize at the group level. The +**cluster is the unit of randomization**, and because members of a cluster are +correlated, it is effectively the unit of replication too. + +- Power depends on the number of **clusters** far more than the number of individuals, + and on the **intraclass correlation (ICC)**. Adding people to existing clusters + helps much less than adding clusters. +- The **design effect** `DEFF = 1 + (m − 1)·ICC` (m = cluster size) quantifies how + much the effective sample size shrinks; even a small ICC with large clusters costs + dearly. Power these by simulation (see **statistical-power**). +- Analyze with a method that accounts for clustering (mixed model with a cluster + random effect, or GEE). Analyzing individuals as independent is pseudoreplication. + +## Nested designs and pseudoreplication + +**Pseudoreplication** is treating non-independent measurements as independent +replicates. It is the most common and most damaging design error in experimental +biology, and it cannot be fixed after data collection — only by designing and +analyzing at the correct level. + +The principle: **the replicate is whatever the treatment is independently applied +and randomized to.** Measurements taken below that level are *technical replicates* — +they improve the precision of a single unit's value but do **not** add degrees of +freedom for testing the treatment. + +Worked examples: +- **One dish per treatment, 50 cells imaged.** Treatment applied to the dish ⇒ n = 1 + per treatment. The 50 cells describe that one dish; they are not 50 independent + tests of the treatment. You need multiple independently treated dishes. +- **3 mice per group, 100 cells each.** n = 3 (mice) for a treatment given to the + mouse, not 300 (cells). Average within mouse, or use a mixed model with mouse as a + random effect. +- **One tank of fish given a diet, every fish measured.** The tank is the unit (the + diet was randomized to the tank) ⇒ n = number of tanks, not number of fish. Shared + tank water, temperature, and social effects make fish within a tank correlated. +- **Repeated measurements over time on the same subject** are nested within subject; + the subject is the replicate. + +How to avoid it: +1. **Identify the experimental unit** = the smallest physical entity to which a + treatment level is independently and randomly assigned. +2. **Replicate at that level** — more independently treated units, not more + measurements per unit (though technical replicates can reduce measurement noise). +3. **Analyze with the nesting respected** — average to the unit level, or fit a mixed + model with random effects for the nesting (cells in mice, fish in tanks, time in + subjects). The fixed-effect treatment test then uses the correct, larger error and + correct degrees of freedom. + +Technical replicates are still worth taking — they sharpen each unit's estimate — but +report and analyze them as what they are, never as independent biological replicates. +For sample size of nested/clustered designs, use simulation in **statistical-power**. diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/experimental-design/references/factorial_and_doe.md b/plugins/hetaoBackend/scientific-research-workflows/skills/experimental-design/references/factorial_and_doe.md new file mode 100644 index 0000000..5f0574b --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/experimental-design/references/factorial_and_doe.md @@ -0,0 +1,130 @@ +# Factorial and Design-of-Experiments (DOE) + +When several factors might affect a response, testing them **one factor at a time +(OFAT)** is both wasteful and blind to interactions. Factorial designs vary factors +*together*, so you estimate every main effect and interaction from the same runs, +with better precision per run. This file covers the family of DOE designs and the +concepts (resolution, aliasing) needed to read them. Generate them with +`scripts/doe_designs.py`. + +## Table of contents +- [Why factorial beats OFAT](#why-factorial-beats-ofat) +- [Full factorial (2^k)](#full-factorial) +- [Fractional factorial (2^(k-p))](#fractional-factorial) +- [Resolution and aliasing](#resolution-and-aliasing) +- [Screening designs (Plackett-Burman)](#screening-designs) +- [Response-surface designs](#response-surface-designs) +- [Space-filling designs](#space-filling-designs) +- [Choosing a design](#choosing-a-design) + +## Why factorial beats OFAT + +Vary one factor while holding others fixed and you (1) spend runs inefficiently and +(2) can never see **interactions** — cases where the effect of A depends on the level +of B, which are the rule, not the exception, in real systems. A factorial varies all +factors simultaneously across runs; each effect is estimated using *all* the data, so +a 2^k factorial is more precise than k separate OFAT studies of the same size. + +## Full factorial + +A **2^k** design runs every combination of k factors at two levels (low/high, coded +−1/+1). It estimates all k main effects and all 2^k − k − 1 interactions. + +- Runs = 2^k: 8 for 3 factors, 16 for 4, 32 for 5. Practical to ~5 factors. +- Use when you have a handful of factors and want a full picture including + interactions. +- `two_level_factorial({"temp": (20,60), "conc": (1,10), "pH": (6,8)})` → 8 runs. +- For factors with more than two levels, use `full_factorial` with explicit level + lists (runs = product of level counts — grows fast). + +Add **center points** (all factors at their midpoint) to a two-level design to get a +cheap check for curvature: if the center response departs from the factorial average, +a linear model is inadequate and you need a response-surface design. + +## Fractional factorial + +When k is large, 2^k is too many runs — but most high-order interactions are +negligible (the *sparsity-of-effects* principle). A **2^(k−p)** fractional factorial +runs a carefully chosen fraction (1/2, 1/4, ...) of the full design, trading the +ability to estimate some interactions for far fewer runs. + +- `fractional_factorial(factors, generator="a b c abc")` builds a half-fraction of 4 + factors in 8 runs. The generator string (Yates notation) assigns each factor to a + column; a multi-letter token aliases that factor with an interaction. +- The price is **aliasing**: some effects become indistinguishable. You must know + which. + +## Resolution and aliasing + +**Aliasing** (confounding) means two effects are estimated by the same contrast — the +data cannot separate them. Which effects are aliased is summarized by the design's +**resolution**: + +| Resolution | Aliasing | Interpretation | +|------------|----------|----------------| +| **III** | main effects aliased with 2-factor interactions | Screening only; a "significant" main effect might be an interaction | +| **IV** | main effects clear of 2FI, but 2FIs aliased with each other | Good for screening; main effects trustworthy | +| **V** | main effects and 2FIs all clear of each other (aliased with 3FI+) | Can model main effects and 2-factor interactions confidently | + +Always state the resolution and inspect the alias structure before interpreting a +fractional design. Concluding "factor C has no effect" is unsafe if C is aliased with +a real interaction (it could cancel out). When in doubt, choose a higher-resolution +generator (more runs) or add runs to **de-alias** (fold-over / augment the design). + +## Screening designs + +When the goal is to **find the vital few** factors out of many (5, 10, 20+), use a +screening design that estimates main effects only, as cheaply as possible: +- **Plackett-Burman** (`plackett_burman`): runs = the next multiple of 4 above k + (e.g. 12 runs for up to 11 factors). Resolution III — two-factor interactions are + heavily confounded with main effects. Perfect for triage: run it, keep the few + factors with large effects, then study those with a full or higher-resolution + factorial. +- Resolution III fractional factorials serve the same purpose. + +Screen first, optimize later — don't try to learn interactions and find the optimum +in one cheap design. + +## Response-surface designs + +Two-level designs fit only a flat (linear + interaction) model; they cannot locate an +interior optimum or describe **curvature**. To fit a quadratic and optimize, use a +response-surface methodology (RSM) design over continuous factors: + +- **Central composite design (CCD)** (`central_composite`): a 2^k factorial + center + points + axial ("star") points. The axial points add the levels needed to estimate + quadratic terms. With `face="circumscribed"` (default) the axial points sit + *outside* the factorial box (so actual factor levels exceed your stated low/high); + use `face="inscribed"` or `"faced"` to keep everything within the original range. +- **Box-Behnken** (`box_behnken`, needs ≥3 factors): a quadratic design that avoids + the extreme all-low/all-high corners — useful when those corners are unsafe, + expensive, or infeasible. More economical than a CCD for 3–5 factors. + +Workflow: screen → factorial (find important factors & rough region) → response +surface (model curvature, locate optimum), often moving the experimental region +between steps (path of steepest ascent). + +## Space-filling designs + +For **computer experiments / simulations** (deterministic or expensive models) where +classical replication and blocking don't apply, you want even coverage of a +high-dimensional input space: +- **Latin hypercube** (`latin_hypercube`): each factor's range is divided into + n_samples equal bins, sampled once each, arranged to spread points apart + (`criterion="maximin"`). Gives good coverage with relatively few points and is the + standard input design for surrogate/emulator modeling and sensitivity analysis. + +## Choosing a design + +| Goal | Factors | Design | Script function | +|------|---------|--------|-----------------| +| Screen many factors | 5–20+ | Plackett-Burman / Res III | `plackett_burman` | +| Main effects, some interactions, few runs | 4–8 | Res IV/V fractional | `fractional_factorial` | +| All effects + interactions | 2–5 | Full 2^k factorial | `two_level_factorial` | +| Multi-level categorical | few | Full factorial | `full_factorial` | +| Optimize a response (curvature) | 2–5 | Central composite / Box-Behnken | `central_composite`, `box_behnken` | +| Cover a simulation input space | any | Latin hypercube | `latin_hypercube` | + +In all cases, **randomize run order** (the scripts do by default) so factors aren't +confounded with time-related drift, and add center points to two-level designs as a +curvature check. diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/experimental-design/references/randomization_and_blocking.md b/plugins/hetaoBackend/scientific-research-workflows/skills/experimental-design/references/randomization_and_blocking.md new file mode 100644 index 0000000..9c9e15e --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/experimental-design/references/randomization_and_blocking.md @@ -0,0 +1,116 @@ +# Randomization, Blocking, Stratification, and Controls + +These are the tools of *local control*: removing or balancing nuisance variation so +the comparison you care about is clean. Randomization handles the unknown +confounders; blocking and stratification handle the known ones; controls and +blinding handle the systematic biases. + +## Randomization — why and how + +Randomization assigns treatments to units by chance, so that in expectation every +confounder (measured or not, known or unknown) is balanced across arms. This is the +foundation of causal inference: without it, an observed difference could always be +due to some variable that happened to track the grouping. + +Use a **seeded, reproducible** schedule (see `scripts/randomization.py`) and follow +it exactly. Record the seed. "I randomized somehow" is neither auditable nor +reproducible. + +### Methods (and when each is right) + +| Method | What it does | Use when | +|--------|--------------|----------| +| **Simple** | Independent random assignment per unit | n is large (≳100); simplicity matters; imbalance is tolerable | +| **Permuted block** | Within each block, arms appear in fixed ratio; order shuffled | You need balance throughout enrollment, or n is small/moderate, or intake is sequential | +| **Stratified block** | Separate blocks within each level of a prognostic factor | A known covariate (site, sex, stage) must be balanced across arms | +| **Cluster** | Whole groups (clinics, classes) assigned to arms | The intervention is delivered at a group level | +| **Minimization** | Adaptively assign to minimize imbalance across several covariates | Many prognostic factors and small n (specialized; not in the script) | + +**Simple randomization caveat:** with small n it behaves like flipping a few coins — +you can easily get 12 vs. 8 instead of 10 vs. 10, and worse for subgroups. Blocking +fixes this. + +**Block size:** must be a multiple of the ratio unit (e.g. for 1:1, sizes 2, 4, 6). +Smaller blocks balance more tightly but are more predictable in unblinded trials +(a clinician who knows the block size can guess the last allocation). Vary block +size or keep it concealed when predictability is a concern. + +## Blocking — removing known nuisance variation + +A **block** is a group of units expected to be similar (same day, batch, litter, +plate, instrument run). You randomize treatments *within* each block. The nuisance +variation between blocks is then removed from the error term, so the treatment +comparison is more precise — often dramatically so. + +Block on anything that (a) you can identify before the experiment and (b) you +expect to affect the response but isn't of interest itself: +- **Time:** day, week, session, processing batch. +- **Space:** plate, plate position/edge, shelf, cage rack, field plot. +- **Material:** reagent lot, animal litter, cell passage, donor. +- **People/instruments:** technician, machine, sequencing run. + +Rule of thumb: *"Block what you can, randomize what you cannot."* If you suspect a +factor matters but can't block it, at least randomize across it and record it as a +covariate. + +**Randomized complete block design (RCBD):** every treatment appears once in every +block. This is the workhorse design — analyze with treatment + block in the model. + +## Stratification vs. blocking vs. covariate adjustment + +These overlap; the distinction is about *when* you control the variable: +- **Stratify / block at design time** when the factor is known before assignment and + you want guaranteed balance (the safest, since it doesn't rely on a model). +- **Adjust as a covariate at analysis time** (ANCOVA, regression) when the factor is + continuous or measured after assignment. Often you do both: stratify on the big + ones, adjust for the rest. + +A few strata are better than many: stratifying on too many factors at once leaves +strata with too few units to block effectively. For many covariates and small n, +minimization is the alternative. + +## Controls + +A comparison needs a concurrent baseline. Match the control to the threat you're +ruling out: +- **Untreated / standard-of-care control** — isolates the treatment effect from time. +- **Vehicle / sham control** — isolates the active ingredient from the delivery + (injection stress, vehicle solvent, sham surgery). +- **Positive control** — a treatment known to produce the effect, to confirm the + assay can detect one at all. +- **Concurrent, not historical** — controls run at the same time as the treatment; + historical controls reintroduce time confounding. + +## Blinding + +Blinding prevents expectation from biasing measurement and behavior: +- **Single-blind:** the subject doesn't know the assignment. +- **Double-blind:** neither subject nor experimenter/assessor knows. +- **Blinded outcome assessment:** at minimum, whoever measures the outcome shouldn't + know the group — cheap and high-value even in animal/bench work. +Allocation concealment (the person enrolling can't foresee the next assignment) is +distinct from blinding and just as important; a sealed seeded schedule provides it. + +## Batch effects and plate layout (especially omics / HTS) + +Batch effects are systematic technical differences between processing groups and are +a leading cause of irreproducible high-throughput results. +- **Never let batch align with the biological condition.** If all cases are in batch + 1 and all controls in batch 2, condition and batch are perfectly confounded and + no normalization can separate them. +- **Randomize or block sample-to-batch and position-within-plate.** Spread each + condition across all batches and across plate positions. +- **Avoid edge effects:** evaporation and thermal gradients make outer wells differ; + don't load all controls into edge columns. Randomize positions, or include + replicates spanning edge and interior. +- **Include anchor/reference samples** in every batch to estimate and correct batch + shifts. +- Use `assign_factorial_runs()` / the randomization functions to generate a + randomized processing order and position map. + +## Documentation + +Record, and ideally pre-register: the randomization method, the seed, block sizes, +stratification factors, the schedule itself, and the planned analysis (which must +include block/stratum/cluster terms). This is what makes the study auditable and the +primary analysis confirmatory rather than exploratory. diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/experimental-design/references/sequential_and_adaptive.md b/plugins/hetaoBackend/scientific-research-workflows/skills/experimental-design/references/sequential_and_adaptive.md new file mode 100644 index 0000000..7574996 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/experimental-design/references/sequential_and_adaptive.md @@ -0,0 +1,97 @@ +# Sequential and Adaptive Designs + +A fixed design commits to a single sample size and one analysis at the end. +**Sequential** and **adaptive** designs allow looks at the data *during* the study and +let you stop early (for benefit, harm, or futility) or modify the design — saving +participants, time, and money. The catch: every interim look at the data is another +chance to cross the significance threshold by luck, so the error rate must be +controlled explicitly. Peeking at accumulating data and stopping the first time +p < 0.05 inflates the Type I error rate badly (to ~0.20+ with a few looks) — this is +the core problem these methods solve. + +## Why naive peeking fails + +If you test at α = 0.05 at each of K interim analyses and stop at the first +significant result, the *overall* false-positive rate is far above 0.05 — roughly +0.08 for 2 looks, ~0.14 for 5, ~0.20 for 10. The fix is to spend your total α across +the looks so the *cumulative* Type I error stays at 0.05. + +## Group-sequential designs + +Pre-plan a fixed number of interim analyses (e.g. after 25%, 50%, 75%, 100% of data) +and use **adjusted, more stringent boundaries** at each look so the overall α is +preserved. Common boundary families: + +- **Pocock:** constant (equally stringent) nominal significance level at every look. + Easier to stop early, but pays a larger penalty at the final analysis. +- **O'Brien–Fleming:** very stringent early (hard to stop in the first looks), relaxing + toward the planned final α. Most popular in confirmatory trials because the final + boundary is close to the unadjusted 0.05 and early stopping is reserved for dramatic + effects. +- **Alpha-spending functions (Lan–DeMets):** generalize the above by defining how much + α is "spent" as a function of information accrued, so the number and timing of looks + need not be fixed in advance — only the spending function is. + +You can stop for: +- **Efficacy** — the effect crosses the upper boundary. +- **Futility** — the effect is so small that continuing is unlikely to ever reach + significance (a non-binding or binding lower boundary / conditional power threshold). +- **Harm** — safety boundary crossed. + +Group-sequential designs require a modestly larger maximum sample size than a fixed +design (to pay for the looks), but the *expected* sample size is usually smaller +because many trials stop early. + +### Tooling + +Python support is thinner than for fixed designs; common options: +- **statsmodels** has limited sequential utilities; for full boundary computation, + most practitioners call R packages via `rpy2` or a subprocess: + - R `gsDesign` — the standard for group-sequential boundaries and spending functions. + - R `rpact` — confirmatory adaptive and group-sequential designs. +- For custom rules, **simulate** the whole sequential procedure (generate data, apply + the boundaries look by look, repeat) to confirm the realized Type I error and to + estimate expected sample size and power. This mirrors the simulation approach in the + **statistical-power** skill and is the most flexible route. + +## Adaptive designs + +Broader than group-sequential: the design itself can change at an interim based on +accumulating data, within a pre-specified plan that still controls error. Main types: + +- **Sample-size re-estimation:** recompute the required n at an interim using the + observed nuisance parameter (e.g. the variance or control-arm rate), without + unblinding the treatment effect. Protects against a misjudged variance at planning. +- **Adaptive randomization:** shift allocation probabilities toward the better- + performing arm as data accrue (response-adaptive), or to improve covariate balance. +- **Drop-the-loser / arm selection:** start with several arms or doses and drop + inferior ones at interims (seamless phase II/III). +- **Adaptive enrichment:** narrow enrollment to a subgroup that appears to benefit. + +Adaptive designs are powerful but easy to get wrong: any adaptation that uses the +unblinded treatment effect can inflate Type I error and bias the final effect estimate +unless the method explicitly corrects for it. Two non-negotiables: +1. **Pre-specify** the adaptation rule and the error-control method before the study. +2. **Validate by simulation** that the *entire* procedure preserves the Type I error + rate and yields acceptable power and unbiased-enough estimates. + +## When to use them + +- **Confirmatory trials, expensive or risky enrollment** — group-sequential with + O'Brien–Fleming boundaries to allow ethical early stopping. +- **Uncertain nuisance parameters at planning** — blinded sample-size re-estimation. +- **Many candidate doses/arms** — adaptive arm selection / seamless designs. +- **Pure exploration / fixed cheap data** — usually not worth the overhead; a fixed + design is simpler and the analysis is unambiguous. + +## Practical checklist + +- Decide the **number and timing** of interim analyses (or the spending function). +- Choose a **boundary family** matched to how eager you are to stop early. +- Specify **futility** rules if you want to stop for lack of effect. +- Inflate the **maximum** sample size to cover the looks; report the **expected** + sample size too. +- Pre-register the full sequential/adaptive plan, including the stopping rules. +- Have an independent **data monitoring committee** look at unblinded interims in + human trials, not the study team. +- **Simulate** the design end to end to confirm error control before running it. diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/experimental-design/scripts/doe_designs.py b/plugins/hetaoBackend/scientific-research-workflows/skills/experimental-design/scripts/doe_designs.py new file mode 100644 index 0000000..036fa43 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/experimental-design/scripts/doe_designs.py @@ -0,0 +1,183 @@ +"""Design-of-experiments (DOE) matrices as labeled, decoded pandas DataFrames. + +pyDOE3 returns designs in *coded* units (-1/+1, or 0..k-1). Researchers want the +design in *real* factor units (temperature in C, concentration in mM) with named +columns, randomized run order, and a clear sense of what each design is for. This +module wraps pyDOE3 to do exactly that. + +A `factors` spec maps factor names to their real-world levels: + - two-level / continuous: {"temp": (20, 60), "conc": (1, 10)} # (low, high) + - multi-level categorical: {"catalyst": ["A", "B", "C"]} + +Functions: + full_factorial every combination of given levels (cost grows fast) + two_level_factorial 2^k full factorial (screening + interactions) + fractional_factorial 2^(k-p) fraction (screening many factors cheaply) + plackett_burman very economical main-effects-only screening + central_composite response-surface design (curvature / optimization) + box_behnken response-surface design, no extreme corners + latin_hypercube space-filling sample for simulation / computer experiments + +Each returns a DataFrame in real units; pass randomize=True (default) to also get +a randomized 'run_order'. Requires: pyDOE3, numpy, pandas. +""" + +from __future__ import annotations + +import numpy as np +import pandas as pd + + +def _decode_two_level(coded, factors): + """Map a -1/+1 coded matrix to real (low/high) units per factor.""" + names = list(factors) + out = {} + for j, name in enumerate(names): + lvl = factors[name] + low, high = lvl[0], lvl[1] + mid, half = (high + low) / 2.0, (high - low) / 2.0 + out[name] = mid + coded[:, j] * half + return pd.DataFrame(out) + + +def _randomize(df, randomize, seed): + if not randomize: + return df.reset_index(drop=True) + rng = np.random.default_rng(seed) + order = rng.permutation(len(df)) + 1 + df = df.copy() + df.insert(0, "run_order", order) + return df.sort_values("run_order").reset_index(drop=True) + + +def full_factorial(factors, randomize=True, seed=0): + """Every combination of the listed levels. + + factors values are explicit level lists, e.g. + {"temp": [20, 40, 60], "catalyst": ["A", "B"]} -> 3*2 = 6 runs. + Runs = product of level counts, so this explodes quickly with many factors. + """ + from pyDOE3 import fullfact + names = list(factors) + levels = [list(factors[n]) for n in names] + counts = [len(l) for l in levels] + coded = fullfact(counts).astype(int) + data = {n: [levels[j][coded[i, j]] for i in range(len(coded))] + for j, n in enumerate(names)} + return _randomize(pd.DataFrame(data), randomize, seed) + + +def two_level_factorial(factors, randomize=True, seed=0): + """Full 2^k factorial: all main effects and all interactions, estimable. + + Each factor needs a (low, high) pair. Use for k up to ~5; beyond that the + run count (2^k) gets expensive — switch to fractional_factorial or + plackett_burman for screening. + """ + from pyDOE3 import ff2n + coded = ff2n(len(factors)) + return _randomize(_decode_two_level(coded, factors), randomize, seed) + + +def fractional_factorial(factors, generator, randomize=True, seed=0): + """2^(k-p) fractional factorial from a generator string. + + `generator` is pyDOE3's Yates notation, e.g. for 4 factors in 8 runs (one of + them aliased): "a b c abc". Each token defines a column; multi-letter tokens + alias a factor with an interaction (this is the tradeoff — fewer runs, some + effects confounded). Choose a higher-resolution generator if you need to + separate main effects from two-factor interactions. + """ + from pyDOE3 import fracfact + coded = fracfact(generator) + if coded.shape[1] != len(factors): + raise ValueError(f"generator defines {coded.shape[1]} factors but " + f"{len(factors)} were named") + return _randomize(_decode_two_level(coded, factors), randomize, seed) + + +def plackett_burman(factors, randomize=True, seed=0): + """Plackett-Burman screening design: main effects only, very few runs. + + Ideal for screening many factors (run count is the next multiple of 4 above k) + to find the vital few. Two-factor interactions are heavily confounded with main + effects, so use it to screen, not to model interactions. + """ + from pyDOE3 import pbdesign + coded = pbdesign(len(factors)) # may include extra dummy columns + coded = coded[:, :len(factors)] + return _randomize(_decode_two_level(coded, factors), randomize, seed) + + +def central_composite(factors, center=(0, 1), alpha="orthogonal", + face="circumscribed", randomize=True, seed=0): + """Central composite design (CCD) for response-surface / optimization work. + + Adds axial ("star") points and center points to a 2^k factorial so you can fit + a quadratic model and locate an optimum. With face='circumscribed' the axial + points sit OUTSIDE the (low, high) box (so real levels exceed your stated + range); use face='inscribed' or 'faced' to keep everything within range. + `center` = (n center pts in factorial block, n in axial block). + """ + from pyDOE3 import ccdesign + coded = ccdesign(len(factors), center=center, alpha=alpha, face=face) + return _randomize(_decode_two_level(coded, factors), randomize, seed) + + +def box_behnken(factors, center=1, randomize=True, seed=0): + """Box-Behnken response-surface design (needs >= 3 factors). + + Like a CCD it fits a quadratic, but it never uses the extreme corner + combinations (all-low or all-high), which is useful when those corners are + unsafe or infeasible. More economical than a CCD for 3-5 factors. + """ + from pyDOE3 import bbdesign + if len(factors) < 3: + raise ValueError("box_behnken requires at least 3 factors") + coded = bbdesign(len(factors), center=center) + return _randomize(_decode_two_level(coded, factors), randomize, seed) + + +def latin_hypercube(factors, n_samples, criterion="maximin", seed=0, + randomize=False): + """Space-filling Latin hypercube sample over continuous factor ranges. + + For computer experiments / simulations where you want even coverage of a + high-dimensional space with relatively few points. Each factor needs a + (low, high) range. `criterion`: 'maximin' spreads points apart; + 'center'/'centermaximin'/'correlation' are alternatives. + """ + from pyDOE3 import lhs + # pyDOE3 draws from its own default_rng, so seeding numpy's global RNG has + # no effect -- the seed has to be handed to lhs itself. + names = list(factors) + unit = lhs( # in [0,1] + len(names), samples=n_samples, criterion=criterion, seed=int(seed) + ) + out = {} + for j, n in enumerate(names): + low, high = factors[n][0], factors[n][1] + out[n] = low + unit[:, j] * (high - low) + df = pd.DataFrame(out) + return _randomize(df, randomize, seed) + + +if __name__ == "__main__": + f2 = {"temp": (20, 60), "conc": (1, 10), "ph": (6, 8)} + + print("== 2^3 full factorial ==") + print(two_level_factorial(f2, seed=1).to_string(index=False)) + + print("\n== fractional 2^(4-1), generator 'a b c abc' ==") + f4 = {"A": (-1, 1), "B": (-1, 1), "C": (-1, 1), "D": (-1, 1)} + print(fractional_factorial(f4, "a b c abc", seed=1).to_string(index=False)) + + print("\n== Plackett-Burman screening, 5 factors ==") + f5 = {f"x{i}": (0, 1) for i in range(1, 6)} + print(f"runs = {len(plackett_burman(f5, randomize=False))}") + + print("\n== central composite (2 factors) ==") + print(central_composite({"temp": (20, 60), "conc": (1, 10)}, seed=1).round(2).to_string(index=False)) + + print("\n== Latin hypercube, 8 samples over 3 factors ==") + print(latin_hypercube(f2, 8, seed=1).round(2).to_string(index=False)) diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/experimental-design/scripts/randomization.py b/plugins/hetaoBackend/scientific-research-workflows/skills/experimental-design/scripts/randomization.py new file mode 100644 index 0000000..8f340ee --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/experimental-design/scripts/randomization.py @@ -0,0 +1,171 @@ +"""Reproducible randomization / allocation schedules for experiments and trials. + +Randomization is what licenses causal inference: it breaks the link between +treatment assignment and any confounder, measured or not. But "I shuffled it" +is not enough — the *method* matters (simple vs. blocked vs. stratified) and the +schedule must be reproducible (seeded) and auditable. This module produces +allocation tables as pandas DataFrames, with a fixed seed so the exact schedule +can be regenerated and archived. + +Functions: + simple_randomization independent coin-flip per unit (can yield imbalance) + block_randomization permuted blocks -> balance throughout enrollment + stratified_block_randomization blocks within strata -> balance per subgroup + cluster_randomization randomize whole clusters (sites/classes), not units + assign_factorial_runs randomize the RUN ORDER of a list of design rows + +Requires: numpy, pandas. +""" + +from __future__ import annotations + +import numpy as np +import pandas as pd + + +def _normalize_ratio(arms, ratio): + """Turn arms + integer ratio into a block template list, e.g. + arms=['A','B'], ratio=(2,1) -> ['A','A','B'].""" + if ratio is None: + ratio = [1] * len(arms) + if len(ratio) != len(arms): + raise ValueError("ratio must have one entry per arm") + if any(r <= 0 for r in ratio): + raise ValueError("ratio entries must be positive integers") + template = [] + for arm, r in zip(arms, ratio): + template += [arm] * int(r) + return template + + +def simple_randomization(n, arms=("treatment", "control"), ratio=None, seed=0): + """Independent random assignment per unit. + + Simplest method; with small n it can produce noticeable arm-size imbalance + (like flipping few coins). Fine for large n. Use block_randomization when you + need balance, especially for n < ~100 or sequential enrollment. + """ + rng = np.random.default_rng(seed) + arms = list(arms) + template = _normalize_ratio(arms, ratio) + probs = np.array([template.count(a) for a in arms], dtype=float) + probs /= probs.sum() + assign = rng.choice(arms, size=n, p=probs) + return pd.DataFrame({"unit_id": np.arange(1, n + 1), "arm": assign}) + + +def block_randomization(n, arms=("treatment", "control"), block_size=None, + ratio=None, seed=0): + """Permuted-block randomization: balance is maintained throughout enrollment. + + Within each block every arm appears in the specified ratio; the order inside + a block is shuffled. block_size must be a multiple of sum(ratio). Leaving it + None picks a small valid size. Mild caveat: fixed small blocks are slightly + predictable in unblinded trials — vary block size if that matters. + """ + rng = np.random.default_rng(seed) + arms = list(arms) + template = _normalize_ratio(arms, ratio) + unit = len(template) + if block_size is None: + block_size = unit * 2 # two of each ratio-unit per block + if block_size % unit != 0: + raise ValueError(f"block_size ({block_size}) must be a multiple of " + f"sum(ratio)={unit}") + reps = block_size // unit + + out = [] + block_id = 0 + while len(out) < n: + block = template * reps + rng.shuffle(block) + for a in block: + out.append((len(out) + 1, block_id, a)) + block_id += 1 + df = pd.DataFrame(out[:n], columns=["unit_id", "block", "arm"]) + return df + + +def stratified_block_randomization(strata, arms=("treatment", "control"), + block_size=None, ratio=None, seed=0): + """Block-randomize independently within each stratum. + + Use when a prognostic variable (site, sex, disease stage) must be balanced + across arms. `strata` is a dict {stratum_label: n_in_that_stratum} or a + sequence of stratum labels (one per unit). Each stratum gets its own permuted + blocks, guaranteeing balance within every subgroup. + """ + if isinstance(strata, dict): + items = list(strata.items()) + else: # sequence of labels + s = pd.Series(list(strata)) + items = list(s.value_counts().sort_index().items()) + + frames = [] + for i, (label, count) in enumerate(items): + df = block_randomization(count, arms=arms, block_size=block_size, + ratio=ratio, seed=seed + 1 + i) + df.insert(1, "stratum", label) + frames.append(df) + out = pd.concat(frames, ignore_index=True) + out["unit_id"] = np.arange(1, len(out) + 1) + return out + + +def cluster_randomization(clusters, arms=("treatment", "control"), ratio=None, + block_size=None, seed=0): + """Randomize whole clusters (clinics, schools, litters) to arms. + + The cluster — not the individual — is the unit of randomization AND the unit + of analysis-level independence. `clusters` is a list of cluster IDs (or an int + count). Returns one row per cluster. Analyze with a method that accounts for + clustering (mixed model / GEE); treating members as independent is + pseudoreplication. Uses blocking across clusters for arm balance. + """ + if isinstance(clusters, int): + clusters = [f"cluster_{i+1}" for i in range(clusters)] + clusters = list(clusters) + df = block_randomization(len(clusters), arms=arms, ratio=ratio, + block_size=block_size, seed=seed) + df = df.drop(columns=["unit_id"]) + df.insert(0, "cluster_id", clusters) + return df + + +def assign_factorial_runs(design_df, seed=0): + """Randomize the execution order of a set of design runs (e.g. a DOE matrix). + + Run order matters: executing a factorial design in a systematic order + confounds the factors with time/drift (the machine warms up, the reagent + degrades). Randomizing run order protects against that. Returns the design + with a 'run_order' column and rows sorted by it. + """ + rng = np.random.default_rng(seed) + df = design_df.copy().reset_index(drop=True) + order = rng.permutation(len(df)) + 1 + df["run_order"] = order + return df.sort_values("run_order").reset_index(drop=True) + + +def arm_balance(df, arm_col="arm", by=None): + """Quick check: counts per arm (optionally within each stratum/block).""" + if by: + return df.groupby([by, arm_col]).size().unstack(fill_value=0) + return df[arm_col].value_counts() + + +if __name__ == "__main__": + print("== simple (n=10) ==") + print(arm_balance(simple_randomization(10, seed=1)).to_dict()) + + print("\n== permuted blocks, 2:1 treatment:control, n=12 ==") + d = block_randomization(12, arms=["treatment", "control"], ratio=(2, 1), seed=1) + print(d.to_string(index=False)) + print("balance:", arm_balance(d).to_dict()) + + print("\n== stratified by site (A=8, B=6) ==") + d = stratified_block_randomization({"siteA": 8, "siteB": 6}, seed=1) + print(arm_balance(d, by="stratum").to_string()) + + print("\n== cluster randomization (6 clinics) ==") + print(cluster_randomization(6, seed=1).to_string(index=False)) diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/SKILL.md b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/SKILL.md new file mode 100644 index 0000000..01045c8 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/SKILL.md @@ -0,0 +1,264 @@ +--- +name: hypothesis-generation +description: Formulate evidence-bounded scientific questions, candidate hypotheses, rival explanations, causal or associational claims, discriminating predictions, measurements, and preregistration-ready analysis plans. Use when turning observations or preliminary findings into transparent, testable research plans without treating hypotheses as facts. +license: MIT +compatibility: Python 3.11+ standard library. Bundled CLIs are deterministic and local-only; they accept bounded JSON, CSV, or Markdown and require no network, credentials, models, image services, or external packages. +metadata: + version: "2.1" + skill-author: K-Dense Inc. + last-reviewed: "2026-07-23" +--- + +# Scientific Hypothesis Generation + +Turn an observation into a transparent set of candidate explanations and tests. A hypothesis is a proposal to be challenged, not a finding, fact, diagnosis, or recommendation. + +## Non-negotiable boundaries + +Before using unpublished, sensitive, controlled, personal, proprietary, export-controlled, or security-relevant material: + +1. Confirm authorization and the applicable institutional, funder, publisher, data-use, privacy, and AI policies. +2. Keep the material local unless an authorized human explicitly approves a named external destination and data scope. +3. Minimize inputs. Do not place sensitive or unpublished data in web searches or external AI systems without authorization. +4. Stop at the appropriate human, animal, biosafety, dual-use, data-governance, or regulatory gate. + +Never: + +- present a hypothesis, mechanism, causal effect, citation, or apparent pattern as established evidence; +- claim novelty because a quick search found nothing; +- infer causation from association, temporal order alone, predictive accuracy, or model output; +- supply patient-specific diagnosis, treatment, dose, prognosis, or other clinical advice; +- provide harmful experimental optimization or operational detail for pathogens, toxins, weapons, evasion, or other misuse; +- bypass IRB/REC, IACUC, IBC, biosafety, dual-use, privacy, legal, or regulatory review; +- fabricate sources, identifiers, search coverage, data, results, approvals, or preregistration; +- automatically score, rank, select, accept, or reject scientific hypotheses. + +If a request crosses a safety gate, produce only a high-level risk/oversight note and route it to the qualified local authority. Do not continue with operational detail. + +## Keep the objects distinct + +| Object | Meaning | +|---|---| +| **Observation** | What was measured, noticed, or reported, with provenance and uncertainty | +| **Research question** | The answerable question that defines scope | +| **Hypothesis** | A candidate explanatory or relational proposition | +| **Mechanism** | The proposed process connecting conditions to an outcome | +| **Causal estimand** | The precisely defined causal contrast to estimate | +| **Prediction** | An observable implication derived before checking the target result | +| **Alternative explanation** | A rival account, including bias or non-causal explanations | +| **Null hypothesis** | A specified no-effect/no-difference model used by an analysis | +| **Negative control** | A control expected not to operate through the proposed mechanism | +| **Operationalization** | How a construct becomes a variable, measurement, intervention, or category | +| **Analysis plan** | Prespecified transformations, models, contrasts, uncertainty, and decision rules | +| **Evidence** | Observations or sources that bear on a claim; never the claim itself | + +Do not collapse these labels. A mechanistic story is not a prediction; a prediction is not evidence; rejection of one null does not prove a mechanism; support for one candidate does not eliminate unconsidered rivals. + +## Workflow + +### 1. Run the scope and safety gate + +Record: + +- accountable human owner and intended use; +- data sensitivity, authorization, retention, and permitted processing; +- affected people, animals, ecosystems, communities, or security interests; +- required ethics, feasibility, biosafety, dual-use, and regulatory reviews; +- unresolved blocks and domain expertise needed. + +No script approval is an ethics, safety, regulatory, or scientific approval. + +### 2. Freeze the observation + +Write the observation before interpretation: + +- measurement or source; +- population, system, place, and time; +- unit of observation and unit of analysis; +- uncertainty, missingness, exclusions, and preprocessing; +- whether the pattern was expected, exploratory, or selected after viewing results. + +Use “reported,” “observed,” or “associated,” not causal language, unless a causal design and estimand justify it. + +### 3. Frame the research question + +Choose a framework only when it fits: + +- **PICO/PICOT** for intervention/effectiveness questions: population, intervention, comparator, outcome, and optionally time. +- **PECO** for exposure questions. +- **Population–index test–reference standard–target condition** for diagnostic accuracy. +- **Population–prognostic factor–outcome–time** for prognosis. +- A domain-specific construct–context–outcome frame for qualitative, descriptive, mechanistic, or theoretical work. + +PICO is not a universal template. Define stakeholders, context, boundaries, feasibility, and what answer would change knowledge or practice. FINER is a question-refinement mnemonic—Feasible, Interesting, Novel, Ethical, Relevant—not a scoring system. Treat “Novel” as unresolved until a documented, fit-for-purpose search and expert review support it. + +### 4. Establish a dated evidence boundary + +Search before making literature-dependent statements. Prefer primary research, official policies, primary methods papers, current reporting guidelines, and systematic reviews used for orientation. + +Record: + +- search date and cutoff; +- databases/indexes, queries, filters, and screening boundary; +- included and excluded source types; +- sources supporting, challenging, or contextualizing each claim; +- known access, language, database, and time limitations. + +A search can establish what was searched, not universal absence. Say “not located within the documented search boundary,” never “no prior work exists.” Use `assets/search_boundary_template.json`, `assets/evidence_ledger_template.csv`, and `references/literature_search_strategies.md`. + +### 5. Generate rivals before choosing tests + +Create multiple candidates from genuinely different explanatory classes when plausible: + +- proposed mechanism; +- measurement or processing artifact; +- confounding or common cause; +- selection or attrition; +- conditioning on a collider; +- reverse causation; +- temporal, contextual, or boundary-condition differences; +- stochastic variation; +- competing mechanisms at another scale. + +Generate an initial rival set independently before AI-assisted expansion to reduce anchoring and homogenization. Do not force a fixed number or false symmetry. Keep every candidate labeled `candidate`. + +Platt’s strong-inference pattern motivates alternative hypotheses and crucial tests, but failed alternatives do not make the survivor true. Unknown alternatives, auxiliary assumptions, measurement error, and mixed mechanisms remain possible. + +### 6. Declare the claim type and estimand + +Classify each target as: + +- descriptive; +- associational; +- predictive; +- causal; +- mechanistic. + +For a causal target, define before analysis: + +- target population or system; +- intervention/exposure and comparator; +- outcome and time horizon; +- population-level summary; +- treatment versions and intercurrent-event handling where relevant; +- identification assumptions and target-trial/design analogue. + +Document confounding, selection, collider, measurement, and reverse-causation risks separately. An observational causal estimate remains assumption-dependent. Use `references/causal_inference_and_claims.md`. + +### 7. Derive discriminating predictions + +For every candidate: + +1. State conditions and boundary conditions. +2. Name the observable and measurement. +3. State the expected pattern and uncertainty. +4. State a result incompatible with the candidate under declared assumptions. +5. Contrast the expected result with at least one rival. +6. Define indeterminate outcomes and what would be learned from them. + +Prefer tests where rivals predict meaningfully different outcomes. Add positive, procedural, and negative controls when scientifically appropriate. A negative control must be incapable of operating through the target mechanism while sharing relevant bias pathways; it is not a decorative untreated group. + +Use `assets/prediction_rival_matrix_template.csv` and `assets/falsification_controls_template.json`. + +### 8. Operationalize and validate measurement + +For every construct record: + +- variable role and operational definition; +- population/system, unit, timing, and conditions; +- instrument/method, calibration, quality control, and masking; +- reliability/repeatability; +- validity evidence and applicability; +- missingness, detection limits, transformations, cut points, and their rationales; +- measurement invariance or cross-group comparability when relevant; +- foreseeable measurement bias and limitations. + +Do not treat a convenient proxy as the construct itself. Validate with: + +```bash +python3 scripts/check_operationalization.py local-operationalization.json +``` + +### 9. Match design and analysis to the claim + +Specify: + +- sampling, experimental unit, allocation, randomization, masking, and controls; +- inclusion/exclusion and stopping rules; +- sample-size, precision, or information rationale based on declared assumptions; +- outcomes, contrasts, estimands, models, effect measures, and uncertainty; +- missing-data and intercurrent-event handling; +- multiplicity across outcomes, models, subgroups, looks, and hypotheses; +- assumptions, diagnostics, robustness, and sensitivity analyses; +- replication or independent validation plan; +- what is confirmatory versus exploratory. + +Do not use universal sample-size minima. Do not interpret a thresholded p-value as the probability a hypothesis is true or as effect importance. See `references/experimental_design_patterns.md`. + +For intervention trials, use the current SPIRIT 2025 protocol guidance and CONSORT 2025 reporting guidance where applicable. These improve completeness; they do not certify design quality, ethics, or regulatory compliance. + +### 10. Prevent HARKing and expose deviations + +Before accessing the target outcomes, timestamp the question, candidates, predictions, outcomes, exclusions, transformations, analysis, multiplicity, missing-data plan, and stopping rule when feasible. + +Afterward: + +- label data-dependent ideas and analyses exploratory; +- preserve and report planned analyses; +- list deviations with date, rationale, who decided, and expected impact; +- never rewrite an observed pattern as an a priori prediction. + +Preregistration is a transparent plan, not a ban on adaptation. Registered Reports add results-blind peer review and in-principle acceptance under journal policy. See `references/preregistration_and_open_science.md`. + +### 11. Plan replication and updating + +Distinguish: + +- **reproducibility:** consistent computational results from the same data/code/conditions; +- **replicability:** consistency across studies collecting new data for the same question. + +Preserve provenance, versions, code, materials, and decision logs when sharing is authorized. Plan independent replication or transport tests across relevant boundaries. Update candidate status when contrary, null, or replication evidence arrives; do not hide negative results. + +### 12. Apply human accountability + +The accountable human must verify: + +- every citation and source-to-claim link; +- domain plausibility and measurement validity; +- causal assumptions and statistical design; +- ethics, feasibility, safety, privacy, and regulatory status; +- all AI-assisted text, ideas, and citations; +- whether broader expertise or community input is required. + +AI can confabulate citations, anchor reasoning, and homogenize candidate sets. Record permitted AI use and material influence. Keep independent human ideation and rival generation in the process. + +## Local tool index + +All CLIs are bounded, dependency-free, local, deterministic, and non-scoring: + +| Task | Asset | Command | +|---|---|---| +| Hypothesis-record schema | `assets/hypothesis_record_template.json` | `python3 scripts/validate_hypothesis_schema.py record.json` | +| Measurement checklist | `assets/operationalization_template.json` | `python3 scripts/check_operationalization.py checklist.json` | +| Prediction/rival matrix | `assets/prediction_rival_matrix_template.csv` | `python3 scripts/validate_prediction_matrix.py matrix.csv` | +| Claim-language lint | Annotated Markdown | `python3 scripts/lint_causal_claims.py draft.md` | +| Falsification/controls | `assets/falsification_controls_template.json` | `python3 scripts/check_falsification_controls.py controls.json` | +| Evidence/source audit | `assets/evidence_ledger_template.csv` + `assets/search_boundary_template.json` | `python3 scripts/audit_evidence_ledger.py ledger.csv boundary.json` | +| Preregistration scaffold | `assets/preregistration_scaffold_template.md` | `python3 scripts/generate_preregistration_scaffold.py record.json -o preregistration.md` | + +Exit codes are `0` for structurally valid output, `1` for completed validation with errors, and `2` for malformed/unsafe input. Reports validate declarations and internal consistency only; they do not verify scientific truth or choose a hypothesis. Full schemas are in `references/tool_reference.md`. + +## References + +- `references/concepts_and_workflow.md` — object model, strong inference, uncertainty, and candidate lifecycle +- `references/hypothesis_quality_criteria.md` — non-scoring human review criteria +- `references/literature_search_strategies.md` — traceable, bounded evidence search +- `references/causal_inference_and_claims.md` — estimands and causal-bias risks +- `references/experimental_design_patterns.md` — design, controls, measurement, multiplicity, and replication +- `references/preregistration_and_open_science.md` — preregistration, Registered Reports, deviations, and open science +- `references/ethics_safety_and_ai.md` — oversight gates, dual use, data handling, and responsible AI +- `references/tool_reference.md` — CLI schemas, limits, and examples +- `references/source_ledger.md` — dated authoritative source notes +- `references/security_validation.md` — baseline findings and validation record + +The bundled source ledger is `assets/source_ledger.csv`, verified through **2026-07-23**. Recheck time-sensitive policy and guidance before a later or jurisdiction-specific use. diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/assets/evidence_ledger_template.csv b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/assets/evidence_ledger_template.csv new file mode 100644 index 0000000..e91799a --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/assets/evidence_ledger_template.csv @@ -0,0 +1,2 @@ +source_id,claim_ids,title,authors_or_organization,publication_date,source_type,identifier,url,accessed_on,relation,study_design_or_document_type,limitations,notes +SRC-SYN-001,OBS1;H1;H2;P1;P2,"Synthetic source record for schema demonstration","Synthetic Example Group",2026-07-23,other,SYNTHETIC:HG-001,https://example.invalid/hypothesis-generation/synthetic-source,2026-07-23,contextual,"Synthetic documentation record","Not scientific evidence; does not support a real-world claim","Replace this row with verified sources and preserve challenging as well as supportive evidence" diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/assets/falsification_controls_template.json b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/assets/falsification_controls_template.json new file mode 100644 index 0000000..258c493 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/assets/falsification_controls_template.json @@ -0,0 +1,116 @@ +{ + "schema_version": "2.0", + "checklist_id": "FC-SYN-001", + "record_id": "HG-SYN-001", + "hypotheses": [ + { + "hypothesis_id": "H1", + "candidate_status": "candidate", + "assumptions": [ + "Synthetic assignment, manipulation, and measurements operate as declared." + ], + "boundary_conditions": [ + "Synthetic eligible units over one prespecified interval." + ], + "falsifier": { + "prediction_id": "P1", + "conditions": "The manipulation and measurement checks pass and uncertainty is small enough to distinguish the patterns.", + "observable": "Intermediate M and outcome Y measured at prespecified times.", + "incompatible_result": "Outcome Y changes without the prespecified change in intermediate M.", + "assumption_failure_checks": [ + "Verify assignment, manipulation, calibration, missingness, and temporal ordering before interpreting incompatibility." + ] + }, + "discriminating_tests": [ + { + "test_id": "DT1", + "rival_hypothesis_id": "H2", + "focal_expected": "Intermediate M changes before outcome Y with no negative-control reference shift.", + "rival_expected": "Outcome Y and the negative-control reference shift together without the prespecified intermediate change.", + "indeterminate_result": "Both intermediate M and the negative-control reference change, so mixed mechanisms or bias remain possible." + } + ], + "nulls": [ + { + "null_id": "N1", + "statement": "The population mean of outcome Y is equal under X and comparator at the prespecified horizon.", + "analysis_id": "A1", + "interpretation_limit": "Failure to reject is not equivalence, absence of effect, or proof of the rival." + } + ], + "controls": [ + { + "control_id": "NC1", + "control_type": "negative_outcome", + "rationale": "The synthetic reference cannot be changed by process M but shares the acquisition workflow.", + "expected_result": "No assignment-linked reference difference after the prespecified acquisition adjustment.", + "failure_implication": "Investigate acquisition, selection, or analysis bias before a causal interpretation." + }, + { + "control_id": "PC1", + "control_type": "positive_control", + "rationale": "A known synthetic calibration input checks whether the outcome procedure detects a declared change.", + "expected_result": "The calibration input is recovered within the locked tolerance.", + "failure_implication": "The target test is not interpretable until measurement performance is restored." + } + ], + "outcome_interpretation": { + "consistent_with_candidate": "The focal pattern is observed, the rival pattern is not, controls pass, and uncertainty is compatible with the prespecified target.", + "challenges_candidate": "The incompatible result is observed while manipulation, measurements, and declared assumptions remain credible.", + "supports_neither_or_mixed": "The result matches neither pattern, both patterns occur, or an assumption/control fails." + }, + "human_review_status": "pending" + }, + { + "hypothesis_id": "H2", + "candidate_status": "candidate", + "assumptions": [ + "Any synthetic acquisition drift affects the outcome and reference through a shared pathway." + ], + "boundary_conditions": [ + "Synthetic acquisition periods covered by the declared reference process." + ], + "falsifier": { + "prediction_id": "P2", + "conditions": "Independent calibration periods overlap both assigned conditions and reference measurement is valid.", + "observable": "Synthetic negative-control reference and outcome Y by acquisition period.", + "incompatible_result": "The reference remains stable while outcome Y changes reproducibly by assignment.", + "assumption_failure_checks": [ + "Verify reference sensitivity, batch overlap, calibration, and missing acquisition records." + ] + }, + "discriminating_tests": [ + { + "test_id": "DT2", + "rival_hypothesis_id": "H1", + "focal_expected": "Reference and outcome shift together by acquisition period.", + "rival_expected": "Outcome changes by assignment without a reference shift and after intermediate M changes.", + "indeterminate_result": "Acquisition period is confounded with assignment, so the rivals cannot be separated." + } + ], + "nulls": [ + { + "null_id": "N2", + "statement": "The negative-control reference mean is equal across acquisition periods.", + "analysis_id": "A2", + "interpretation_limit": "Failure to reject does not prove absence of drift when precision or reference sensitivity is insufficient." + } + ], + "controls": [ + { + "control_id": "NC1", + "control_type": "negative_outcome", + "rationale": "The reference is outside process M while sharing the acquisition pathway.", + "expected_result": "A drift explanation predicts a shared acquisition-linked shift.", + "failure_implication": "A stable, sensitive reference challenges the drift candidate." + } + ], + "outcome_interpretation": { + "consistent_with_candidate": "Outcome and reference shifts align by acquisition period under valid measurement.", + "challenges_candidate": "The stable reference and independent calibration are incompatible with the declared shared-drift process.", + "supports_neither_or_mixed": "Assignment and acquisition cannot be separated or both mechanism and drift patterns occur." + }, + "human_review_status": "pending" + } + ] +} diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/assets/hypothesis_record_template.json b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/assets/hypothesis_record_template.json new file mode 100644 index 0000000..78ab50f --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/assets/hypothesis_record_template.json @@ -0,0 +1,331 @@ +{ + "schema_version": "2.0", + "project_id": "HG-SYN-001", + "status": "draft", + "updated_on": "2026-07-23", + "human_owner": "Accountable human investigator", + "observation": { + "statement": "In a synthetic demonstration dataset, units measured after condition X had a different mean value of outcome Y than units measured after the comparator condition.", + "provenance": "Synthetic demonstration only; no human, animal, clinical, proprietary, or security-sensitive data.", + "source_ids": [ + "SRC-SYN-001" + ], + "uncertainties": [ + "The synthetic pattern could reflect the generating process, selection, measurement, or random variation." + ] + }, + "research_question": { + "statement": "In the synthetic target system, what is the effect of assigning condition X rather than the comparator on outcome Y after one measurement interval?", + "framework": "PICO", + "question_type": "causal", + "population_or_system": "Synthetic independent units meeting the declared eligibility rule", + "intervention_or_exposure": "Assignment to synthetic condition X", + "comparator": "Assignment to the synthetic comparator condition", + "outcome": "Validated synthetic outcome Y", + "timeframe": "One prespecified measurement interval" + }, + "hypotheses": [ + { + "hypothesis_id": "H1", + "statement": "Condition X changes outcome Y through the proposed synthetic process M.", + "mechanism": "Condition X changes intermediate M before outcome Y is measured.", + "status": "candidate", + "source_ids": [ + "SRC-SYN-001" + ], + "assumptions": [ + "The synthetic assignment and measurement procedures operate as declared." + ], + "boundary_conditions": [ + "The candidate is limited to the declared synthetic units and one-interval horizon." + ], + "uncertainties": [ + "The intermediate may be a marker rather than a mediator." + ], + "prediction_ids": [ + "P1" + ], + "rival_hypothesis_ids": [ + "H2" + ] + }, + { + "hypothesis_id": "H2", + "statement": "The observed difference in outcome Y is produced by a synthetic measurement drift rather than process M.", + "mechanism": "A time-linked offset changes the recorded outcome without changing the target construct.", + "status": "candidate", + "source_ids": [ + "SRC-SYN-001" + ], + "assumptions": [ + "The synthetic measurement offset is not removed by calibration." + ], + "boundary_conditions": [ + "The candidate applies only to measurements from the affected synthetic acquisition period." + ], + "uncertainties": [ + "The size and stability of any offset are unknown." + ], + "prediction_ids": [ + "P2" + ], + "rival_hypothesis_ids": [ + "H1" + ] + } + ], + "causal_estimands": [ + { + "estimand_id": "E1", + "linked_hypothesis_ids": [ + "H1" + ], + "population": "Synthetic eligible units", + "intervention_or_exposure": "Assignment to condition X", + "comparator": "Assignment to comparator", + "outcome": "Outcome Y measured with method M-OUTCOME", + "time_horizon": "One measurement interval", + "population_summary": "Difference in population mean outcome Y", + "intercurrent_event_strategy": "Not applicable in the synthetic demonstration", + "identification_assumptions": [ + "Random assignment, no interference, consistent treatment versions, and valid outcome measurement." + ] + } + ], + "predictions": [ + { + "prediction_id": "P1", + "hypothesis_id": "H1", + "statement": "Under matched acquisition conditions, assignment to X will precede a change in intermediate M and then outcome Y.", + "observable": "Ordered changes in intermediate M and outcome Y", + "conditions": "Synthetic units are randomly assigned and measured with calibrated instruments.", + "expected_pattern": "Intermediate M changes before outcome Y in X but not comparator units.", + "falsifier": "Outcome Y changes without the prespecified change in intermediate M under a successful manipulation and valid measurements.", + "rival_hypothesis_ids": [ + "H2" + ], + "measurement_ids": [ + "M-INTERMEDIATE", + "M-OUTCOME" + ], + "analysis_ids": [ + "A1" + ] + }, + { + "prediction_id": "P2", + "hypothesis_id": "H2", + "statement": "A reference quantity measured in the same acquisition period will show a similar offset even though process M cannot affect it.", + "observable": "Offset in the negative-control reference quantity", + "conditions": "The same acquisition and calibration workflow is used.", + "expected_pattern": "The reference quantity and outcome Y shift together by acquisition period.", + "falsifier": "The reference quantity remains stable while outcome Y changes reproducibly across independently calibrated periods.", + "rival_hypothesis_ids": [ + "H1" + ], + "measurement_ids": [ + "M-NEGATIVE-CONTROL", + "M-OUTCOME" + ], + "analysis_ids": [ + "A2" + ] + } + ], + "alternative_explanations": [ + { + "alternative_id": "ALT1", + "statement": "Synthetic selection into the analyzed set creates the observed difference.", + "linked_hypothesis_ids": [ + "H1", + "H2" + ], + "risk_types": [ + "selection_bias" + ], + "discriminating_prediction_ids": [ + "P1", + "P2" + ] + } + ], + "null_hypotheses": [ + { + "null_id": "N1", + "statement": "The population mean of outcome Y is equal under assignment to X and comparator at the prespecified horizon.", + "linked_prediction_ids": [ + "P1" + ], + "rejection_or_compatibility_rule": "Use the prespecified interval estimate and decision rule in analysis A1; do not equate failure to reject with equivalence." + }, + { + "null_id": "N2", + "statement": "The synthetic negative-control reference mean is equal across acquisition periods.", + "linked_prediction_ids": [ + "P2" + ], + "rejection_or_compatibility_rule": "Use analysis A2 as a bias diagnostic; failure to reject does not prove absence of drift when precision or reference sensitivity is insufficient." + } + ], + "negative_controls": [ + { + "control_id": "NC1", + "control_type": "negative_outcome", + "rationale": "The reference quantity cannot be changed by process M but shares the synthetic acquisition workflow.", + "expected_result": "No difference by assigned condition after accounting for acquisition period.", + "failure_implication": "A non-null control result raises concern about acquisition, selection, or analysis bias.", + "linked_prediction_ids": [ + "P2" + ] + } + ], + "operationalizations": [ + { + "measurement_id": "M-INTERMEDIATE", + "construct": "Synthetic intermediate M", + "variable": "m_value", + "role": "mediator", + "operational_definition": "Calibrated continuous synthetic measurement at the prespecified early time.", + "instrument_or_method": "Deterministic synthetic measurement procedure", + "unit": "synthetic units", + "timing": "Early prespecified time", + "population_or_system": "Synthetic eligible units", + "validity_evidence_source_ids": [ + "SRC-SYN-001" + ], + "reliability_plan": "Repeat the deterministic check on an independent synthetic batch.", + "missingness_plan": "Report missingness by assignment and use the prespecified sensitivity analysis.", + "blinding_or_masking": "Synthetic analyst label is masked until validation completes.", + "threshold_rationale": "No threshold; analyze the continuous measurement." + }, + { + "measurement_id": "M-OUTCOME", + "construct": "Synthetic outcome Y", + "variable": "y_value", + "role": "outcome", + "operational_definition": "Calibrated continuous synthetic outcome at one interval.", + "instrument_or_method": "Deterministic synthetic measurement procedure", + "unit": "synthetic units", + "timing": "One prespecified measurement interval", + "population_or_system": "Synthetic eligible units", + "validity_evidence_source_ids": [ + "SRC-SYN-001" + ], + "reliability_plan": "Verify calibration and repeatability before unmasking assignment.", + "missingness_plan": "Report reasons and analyze under the prespecified primary and sensitivity assumptions.", + "blinding_or_masking": "Outcome acquisition and validation are masked to assignment.", + "threshold_rationale": "No threshold; estimate the continuous mean contrast." + }, + { + "measurement_id": "M-NEGATIVE-CONTROL", + "construct": "Synthetic reference quantity", + "variable": "reference_value", + "role": "negative_control", + "operational_definition": "Stable synthetic reference measured in the same acquisition workflow.", + "instrument_or_method": "Deterministic synthetic reference procedure", + "unit": "synthetic reference units", + "timing": "Concurrent with outcome Y", + "population_or_system": "Synthetic acquisition batches", + "validity_evidence_source_ids": [ + "SRC-SYN-001" + ], + "reliability_plan": "Check repeatability across synthetic batches.", + "missingness_plan": "Treat a missing reference as an acquisition-quality failure.", + "blinding_or_masking": "Reference processing is independent of assignment labels.", + "threshold_rationale": "Use the prespecified calibration tolerance." + } + ], + "analysis_plan": { + "analyses": [ + { + "analysis_id": "A1", + "prediction_ids": [ + "P1" + ], + "estimand_ids": [ + "E1" + ], + "analysis_population": "All randomized synthetic eligible units under the declared estimand strategy", + "method": "Estimate the prespecified mean contrast with its uncertainty interval.", + "effect_or_summary_measure": "Difference in population means", + "uncertainty_method": "Prespecified interval estimate under the declared independent-unit assumptions", + "missing_data_plan": "Primary declared assumption plus a bounded sensitivity analysis", + "multiplicity_plan": "A1 is the sole confirmatory contrast; all additional contrasts are exploratory.", + "sensitivity_analyses": [ + "Repeat under the prespecified alternative missingness assumption." + ], + "decision_rule": "Interpret compatibility with predicted patterns and rivals; do not select a hypothesis automatically.", + "exploratory_or_confirmatory": "confirmatory" + }, + { + "analysis_id": "A2", + "prediction_ids": [ + "P2" + ], + "estimand_ids": [], + "analysis_population": "All synthetic acquisition batches with valid reference measurements", + "method": "Estimate reference shifts by acquisition period and assigned condition.", + "effect_or_summary_measure": "Difference in reference means", + "uncertainty_method": "Prespecified interval estimate", + "missing_data_plan": "Report and exclude acquisition failures according to the locked quality rule.", + "multiplicity_plan": "Negative-control analysis is interpreted as a bias diagnostic, not a candidate-selection score.", + "sensitivity_analyses": [ + "Repeat after excluding the prespecified failed-calibration batch category." + ], + "decision_rule": "A non-null reference shift triggers investigation and limits causal interpretation.", + "exploratory_or_confirmatory": "confirmatory" + } + ], + "harking_control": "Freeze this record before viewing the target synthetic outcomes and label later candidates or analyses exploratory.", + "deviation_reporting": "Preserve the original plan and log each deviation with date, rationale, decision owner, result-awareness status, and interpretive impact." + }, + "evidence": { + "ledger_path": "local-evidence-ledger.csv", + "source_ids": [ + "SRC-SYN-001" + ], + "search_boundary_id": "SEARCH-SYN-001", + "evidence_limitations": [ + "The bundled row is synthetic and demonstrates structure only." + ] + }, + "risk_register": { + "confounding": [ + "Synthetic assignment failure or implementation differences could act as common causes." + ], + "selection_bias": [ + "Post-assignment exclusion could make analyzed groups non-comparable." + ], + "collider_bias": [ + "Restricting analysis to a jointly caused quality indicator could open bias." + ], + "reverse_causation": [ + "Temporal ordering is built into the synthetic design but must be verified." + ], + "measurement_bias": [ + "Acquisition-period drift could change recorded outcome Y." + ], + "other": [ + "Random variation and model misspecification remain possible." + ] + }, + "ethics_and_feasibility": { + "human_subjects_gate": "not_applicable", + "animal_research_gate": "not_applicable", + "biosafety_gate": "not_applicable", + "dual_use_gate": "not_applicable", + "regulatory_gate": "not_applicable", + "data_governance_gate": "approved", + "feasibility_status": "feasible_for_planning", + "required_reviews": [], + "unresolved_blocks": [] + }, + "ai_use": { + "used": false, + "sensitive_or_unpublished_data_sent_externally": false, + "local_policy_checked": true, + "citation_verification_required": true, + "human_accountable": true, + "diversity_mitigation": "Generate independent rivals before any authorized AI-assisted expansion and retain human-origin candidates." + } +} diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/assets/operationalization_template.json b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/assets/operationalization_template.json new file mode 100644 index 0000000..6343b55 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/assets/operationalization_template.json @@ -0,0 +1,56 @@ +{ + "schema_version": "2.0", + "checklist_id": "OP-SYN-001", + "record_id": "HG-SYN-001", + "human_reviewer": "Qualified measurement reviewer pending", + "items": [ + { + "measurement_id": "M-OUTCOME", + "applicability": "applicable", + "construct_defined": true, + "variable_role": "outcome", + "operational_definition_recorded": true, + "population_scope_recorded": true, + "unit_or_categories_recorded": true, + "timing_recorded": true, + "instrument_or_method_recorded": true, + "validity_evidence_source_ids": [ + "SRC-SYN-001" + ], + "validity_applicability_reviewed": false, + "reliability_or_repeatability_plan_recorded": true, + "calibration_or_quality_control_status": "planned", + "measurement_invariance_or_comparability_status": "unresolved", + "masking_status": "planned", + "missingness_plan_recorded": true, + "threshold_or_cutpoint_status": "not_applicable", + "limitations_recorded": true, + "human_review_status": "pending", + "note": "Synthetic example. A qualified human must determine whether the validity evidence applies to the target system." + }, + { + "measurement_id": "M-NEGATIVE-CONTROL", + "applicability": "applicable", + "construct_defined": true, + "variable_role": "negative_control", + "operational_definition_recorded": true, + "population_scope_recorded": true, + "unit_or_categories_recorded": true, + "timing_recorded": true, + "instrument_or_method_recorded": true, + "validity_evidence_source_ids": [ + "SRC-SYN-001" + ], + "validity_applicability_reviewed": false, + "reliability_or_repeatability_plan_recorded": true, + "calibration_or_quality_control_status": "planned", + "measurement_invariance_or_comparability_status": "not_applicable", + "masking_status": "not_applicable", + "missingness_plan_recorded": true, + "threshold_or_cutpoint_status": "planned", + "limitations_recorded": true, + "human_review_status": "pending", + "note": "Synthetic example. Confirm that the target mechanism cannot affect this control and that relevant bias pathways are shared." + } + ] +} diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/assets/prediction_rival_matrix_template.csv b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/assets/prediction_rival_matrix_template.csv new file mode 100644 index 0000000..d9d0b23 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/assets/prediction_rival_matrix_template.csv @@ -0,0 +1,3 @@ +prediction_id,hypothesis_id,rival_hypothesis_ids,conditions,observable,expected_if_focal,expected_if_rivals,falsifier,indeterminate_result,boundary_conditions,measurement_ids,negative_control_ids,analysis_ids,uncertainty +P1,H1,H2,"Random assignment, successful manipulation, calibrated measurements, and masked validation","Ordering of synthetic intermediate M and outcome Y","Intermediate M changes before outcome Y in condition X","H2 predicts no condition-specific intermediate change and a shift shared with the reference quantity","Outcome Y changes without the prespecified intermediate change after successful manipulation and valid measurement","Manipulation or measurement check fails, or uncertainty is too large to distinguish the patterns","Synthetic eligible units over one prespecified interval",M-INTERMEDIATE;M-OUTCOME,NC1,A1,"Magnitude is not assumed; the planned interval and measurement resolution determine whether patterns are distinguishable" +P2,H2,H1,"Matched synthetic acquisition and calibration workflow","Shift in the synthetic negative-control reference quantity","Reference quantity and outcome Y shift together by acquisition period","H1 predicts outcome Y changes without a corresponding reference shift","Reference quantity is stable across independently calibrated periods while outcome Y changes reproducibly by assignment","Reference measurement fails or acquisition periods do not overlap assigned conditions","Synthetic acquisition batches covered by the declared calibration process",M-NEGATIVE-CONTROL;M-OUTCOME,NC1,A2,"A control shift can have causes other than the stated measurement-drift rival" diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/assets/preregistration_scaffold_template.md b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/assets/preregistration_scaffold_template.md new file mode 100644 index 0000000..b40aacd --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/assets/preregistration_scaffold_template.md @@ -0,0 +1,137 @@ +# Preregistration scaffold: {{PROJECT_ID}} + +> **UNREGISTERED DRAFT — NOT AN APPROVAL OR SCIENTIFIC ENDORSEMENT** +> +> Generated locally on {{GENERATED_ON}} from a validated structural record. Complete repository-specific fields, obtain required human/ethics/safety/regulatory review, and verify every statement before registration. + +## 1. Administrative record + +- Project ID: {{PROJECT_ID}} +- Accountable human owner: {{HUMAN_OWNER}} +- Record status: {{RECORD_STATUS}} +- Record updated: {{UPDATED_ON}} +- Registration repository and identifier: [TO COMPLETE] +- Registration timestamp: [TO COMPLETE] +- Study status and prior access to target data: [TO COMPLETE] +- Roles, funding, conflicts, sponsor role: [TO COMPLETE] + +## 2. Authorization and oversight gates + +{{ETHICS_AND_FEASIBILITY}} + +- Human/animal/biosafety/dual-use/data/regulatory determinations and identifiers: [TO COMPLETE] +- Local policy and jurisdiction checked on: [TO COMPLETE] +- Unresolved work must not begin until the responsible authority clears it. + +## 3. Search boundary and evidence + +{{EVIDENCE_BOUNDARY}} + +- Attach the completed search-boundary record and evidence ledger. +- “Not located within this boundary” does not establish universal absence or novelty. +- Verify every citation and claim-to-source link against the primary source. + +## 4. Frozen observation + +{{OBSERVATION}} + +- Units, preprocessing, exclusions, missingness, and uncertainty: [TO COMPLETE] +- Whether expected or selected after inspection: [TO COMPLETE] + +## 5. Research question and claim type + +{{RESEARCH_QUESTION}} + +## 6. Candidate hypotheses and mechanisms + +{{HYPOTHESES}} + +All listed hypotheses remain candidates. This scaffold does not rank or select one. + +## 7. Causal estimand(s), if applicable + +{{ESTIMANDS}} + +- Target-trial/design analogue and identification logic: [TO COMPLETE] +- Confounding, selection, collider, measurement, reverse-causation, positivity, and interference assumptions: [TO COMPLETE] + +## 8. Predictions, rivals, and falsification + +{{PREDICTIONS}} + +- Indeterminate and mixed-mechanism outcomes: [TO COMPLETE] +- Assumption/manipulation checks required before interpreting a challenge: [TO COMPLETE] + +## 9. Null hypotheses and controls + +{{NULLS_AND_CONTROLS}} + +- Positive/procedural-control details: [TO COMPLETE] +- Why each negative control cannot operate through the target mechanism and which bias pathways it shares: [TO COMPLETE] + +## 10. Operationalization and measurement validity + +{{OPERATIONALIZATIONS}} + +- Validity applicability, reliability/repeatability, calibration, detection limits, masking, missingness, invariance/comparability, and limitations: [TO COMPLETE] + +## 11. Design + +- Target population/system and sampling frame: [TO COMPLETE] +- Experimental/observational unit and analysis unit: [TO COMPLETE] +- Eligibility, recruitment/selection, and exclusions: [TO COMPLETE] +- Intervention/exposure and comparator versions: [TO COMPLETE] +- Allocation, randomization, concealment, and masking: [TO COMPLETE] +- Measurement schedule and quality control: [TO COMPLETE] +- Sample-size, precision, or information rationale with sensitivity to assumptions: [TO COMPLETE] +- Stopping, attrition, and missing-outcome plan: [TO COMPLETE] +- Replication, transport, and external-validation plan: [TO COMPLETE] + +## 12. Analysis plan + +{{ANALYSES}} + +- Software, environment, versions, and seeds where applicable: [TO COMPLETE] +- Assumption diagnostics and model checks: [TO COMPLETE] +- Full multiplicity family across hypotheses, outcomes, timepoints, subgroups, models, and looks: [TO COMPLETE] +- Confirmatory versus exploratory outputs: [TO COMPLETE] + +## 13. Data, code, materials, and retention + +- Provenance and versioning: [TO COMPLETE] +- Data/code/material availability or justified restriction: [TO COMPLETE] +- Consent, privacy, community governance, intellectual property, export-control, biosafety, and dual-use limits: [TO COMPLETE] +- Approved storage, access, retention, and deletion: [TO COMPLETE] + +## 14. AI and tool assistance + +{{AI_USE}} + +- Named tools/versions and material influence: [TO COMPLETE IF PERMITTED] +- Verification and disclosure plan: [TO COMPLETE] +- Sensitive or unpublished information must remain local unless explicitly authorized for a named service and scope. + +## 15. Deviations and amendments + +{{DEVIATION_PLAN}} + +For each deviation record: + +- date; +- affected section and IDs; +- original plan; +- change and rationale; +- decision owner; +- whether target results were known; +- expected interpretive impact; +- whether the original analysis will still be reported. + +## 16. Human sign-off + +- Domain expert: [NAME / DATE / STATUS] +- Measurement expert: [NAME / DATE / STATUS] +- Methodologist/statistician: [NAME / DATE / STATUS] +- Ethics/safety/data/regulatory authorities as applicable: [NAME / DATE / STATUS] +- Accountable investigator: [NAME / DATE / STATUS] + +No signature turns a hypothesis into fact. Interpret results with uncertainty, rivals, boundary conditions, controls, and replication evidence. diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/assets/search_boundary_template.json b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/assets/search_boundary_template.json new file mode 100644 index 0000000..0e6194a --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/assets/search_boundary_template.json @@ -0,0 +1,23 @@ +{ + "schema_version": "2.0", + "search_boundary_id": "SEARCH-SYN-001", + "searched_on": "2026-07-23", + "searched_by": "Accountable human or authorized research assistant", + "purpose": "Demonstrate a bounded, traceable search record for a synthetic hypothesis package.", + "databases_or_indexes": [ + "Synthetic index for format demonstration" + ], + "queries": [ + "synthetic condition X outcome Y mechanism", + "synthetic condition X alternative explanation measurement drift" + ], + "date_limits": "No date restriction in the synthetic demonstration.", + "language_limits": "Synthetic English records only.", + "inclusion_scope": "Sources bearing on the observation, candidate process, measurement, rivals, and methods.", + "exclusion_scope": "No real scientific sources were screened in this synthetic template.", + "known_limitations": [ + "This is a schema example and cannot support novelty, priority, or an evidence claim." + ], + "last_result_screened_or_stop_rule": "Replace with the actual result depth, saturation rule, or protocol completion statement.", + "novelty_status": "not_assessed" +} diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/assets/source_ledger.csv b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/assets/source_ledger.csv new file mode 100644 index 0000000..a891f6a --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/assets/source_ledger.csv @@ -0,0 +1,37 @@ +source_id,organization,title,version_or_date,source_type,url,verified_on,status,notes +SRC-NIH-RIGOR,National Institutes of Health,Guidance: Rigor and Reproducibility in Grant Applications,Page updated 2024-10-16,official guidance,https://grants.nih.gov/policy-and-compliance/policy-topics/reproducibility/guidance,2026-07-23,current_official,"Scientific premise, rigorous design, biological variables, resource authentication, and transparency" +SRC-NIH-REPLICATION,National Institutes of Health,Strengthening Replication and Reproducibility of NIH-funded Research,Page reviewed 2026-06-22,official initiative,https://www.nih.gov/replicationandreproducibility,2026-07-23,current_official,"Agency-wide replication and reproducibility initiative; policy implementation may continue to evolve" +SRC-COCHRANE-PICO,Cochrane,Handbook Chapter 2: Determining the scope of the review and the questions it will address,Current handbook chapter,official handbook,https://www.cochrane.org/authors/handbooks-and-manuals/handbook/current/chapter-02,2026-07-23,current_official,"PICO for intervention reviews, protocol-stage definition, synthesis-level distinctions, and stakeholder input" +SRC-PICO-ORIGINAL,Richardson et al.,The well-built clinical question: a key to evidence-based decisions,Published 1995-11-01,primary methods article,https://www.acpjournals.org/doi/10.7326/ACPJC-1995-123-3-A12,2026-07-23,foundational_primary,"Original four-part well-built clinical-question article; DOI 10.7326/ACPJC-1995-123-3-A12" +SRC-FINER-1988,Hulley and Cummings,Designing Clinical Research: An Epidemiologic Approach,First edition 1988,foundational book metadata,https://books.google.com/books/about/Designing_Clinical_Research.html?id=hKdpAAAAMAAJ,2026-07-23,historical_metadata_limited,"Earliest FINER-attributed source located in this search; bibliographic preview confirms edition metadata but the search did not prove first printed use or coinage" +SRC-FINER-CURRENT,Werner and Willis,Back to the basics: guidance for formulating good research questions,Published 2023-10-12,current methods article,https://pmc.ncbi.nlm.nih.gov/articles/PMC11129835/,2026-07-23,current_primary,"Current discussion of FINER as a practical question-appraisal framework; novelty requires literature review" +SRC-PLATT-1964,John R. Platt,Strong Inference,Published 1964-10-16,primary methods essay,https://www.science.org/doi/10.1126/science.146.3642.347,2026-07-23,foundational_primary,"Multiple alternatives, crucial experiments, and iterative exclusion; DOI 10.1126/science.146.3642.347" +SRC-COS-PREREG,Center for Open Science,Preregistration,Current official page,official guidance,https://www.cos.io/initiatives/prereg,2026-07-23,current_official,"Separates planned from unplanned work and requires transparent changes; exploration remains valid" +SRC-OSF-REG,Center for Open Science / OSF,Welcome to Registrations and Preregistrations,Current 2026 help page,official implementation guidance,https://help.osf.io/article/330-welcome-to-registrations,2026-07-23,current_official,"Timestamped plans, template guidance, analysis details, and anticipated deviations" +SRC-COS-RR,Center for Open Science,Registered Reports,Current official initiative page,official guidance,https://www.cos.io/initiatives/registered-reports,2026-07-23,current_official,"Results-blind protocol review and in-principle acceptance; verify the target journal policy" +SRC-SPIRIT-2025,SPIRIT-CONSORT Group,SPIRIT 2025 statement: updated guideline for protocols of randomised trials,Published 2025-04-28,primary reporting guideline,https://www.bmj.com/content/389/bmj-2024-081477,2026-07-23,current_primary,"34 minimum protocol items; supersedes SPIRIT 2013; DOI 10.1136/bmj-2024-081477" +SRC-CONSORT-2025,SPIRIT-CONSORT Group,CONSORT 2025 statement: updated guideline for reporting randomised trials,Published 2025-04-14,primary reporting guideline,https://www.bmj.com/content/389/bmj-2024-081123,2026-07-23,current_primary,"30-item result-reporting guideline with open-science, harms, outcomes, intervention, and change-reporting updates" +SRC-ICH-E9R1,International Council for Harmonisation,ICH E9(R1) Addendum on Estimands and Sensitivity Analysis in Clinical Trials,Step 4 2019; implemented 2020,official statistical guidance,https://database.ich.org/sites/default/files/E9-R1_Step4_Guideline_2019_1203.pdf,2026-07-23,current_official,"Defines estimand as the precise treatment-effect target and aligns planning, design, analysis, sensitivity, and interpretation" +SRC-WHATIF,Hernán and Robins,Causal Inference: What If,2020 citation; latest author-hosted revision linked 2025-11-21,foundational methods book,https://miguelhernan.org/whatifbook,2026-07-23,current_author_source,"Causal questions, counterfactuals, interventions, confounding, selection, causal diagrams, measurement, and target-trial emulation" +SRC-NEG-CONTROL,Lipsitch Tchetgen Tchetgen and Cohen,Negative controls: a tool for detecting confounding and bias in observational studies,Published 2010-05,primary methods article,https://pubmed.ncbi.nlm.nih.gov/20335814/,2026-07-23,foundational_primary,"Negative exposure and outcome controls; DOI 10.1097/EDE.0b013e3181d61eeb" +SRC-HARKING,Norbert L. Kerr,HARKing: hypothesizing after the results are known,Published 1998,primary methods article,https://pubmed.ncbi.nlm.nih.gov/15647155/,2026-07-23,foundational_primary,"Defines presenting a post hoc hypothesis as if a priori; DOI 10.1207/s15327957pspr0203_4" +SRC-ASA-PVALUE,American Statistical Association,ASA Statement on Statistical Significance and P-Values,Released 2016-03-07,professional statistical statement,https://www.amstat.org/asa/files/pdfs/P-ValueStatement.pdf,2026-07-23,current_official,"Thresholds alone do not support scientific conclusions; p-values do not measure hypothesis truth or effect importance; full reporting is required" +SRC-NASEM-RR,National Academies of Sciences Engineering and Medicine,Reproducibility and Replicability in Science,Published 2019-05-07,consensus report,https://www.nationalacademies.org/read/25303,2026-07-23,current_authoritative,"Defines reproducibility using same data/code and replicability using new data; DOI 10.17226/25303" +SRC-TOP,Center for Open Science collaborators,Promoting an open research culture,Published 2015-06-26,primary transparency framework,https://www.science.org/doi/10.1126/science.aab2374,2026-07-23,current_primary,"Transparency and Openness Promotion guidelines; apply with privacy, consent, governance, and security limits" +SRC-NIH-DMS,National Institutes of Health,Data Management and Sharing Policy,Effective 2023-01-25,official policy,https://sharing.nih.gov/data-management-and-sharing-policy,2026-07-23,current_official,"Prospective data-management and sharing planning for covered NIH-funded research; sharing remains subject to justified limits" +SRC-NIST-GENAI,National Institute of Standards and Technology,Artificial Intelligence Risk Management Framework: Generative Artificial Intelligence Profile,NIST AI 600-1 July 2024; page updated 2026-04-08,official risk framework,https://www.nist.gov/publications/artificial-intelligence-risk-management-framework-generative-artificial-intelligence,2026-07-23,current_official,"Confabulation, privacy, harmful bias/homogenization, information integrity, dangerous recommendations, and human-AI configuration" +SRC-DOSHI-HAUSER,Doshi and Hauser,Generative AI enhances individual creativity but reduces the collective diversity of novel content,Published 2024-07-12,primary research,https://www.science.org/doi/10.1126/sciadv.adn5290,2026-07-23,current_primary,"AI-assisted stories were more similar in this experiment; do not generalize beyond the studied task without evidence" +SRC-UNESCO-AI,UNESCO,Recommendation on the Ethics of Artificial Intelligence,Adopted 2021; official page current 2026,international recommendation,https://www.unesco.org/en/artificial-intelligence/recommendation-ethics,2026-07-23,current_official,"Human rights, privacy, accountability, transparency, diversity, and human oversight" +SRC-UNESCO-OPEN,UNESCO,Recommendation on Open Science,Adopted 2021,international recommendation,https://unesdoc.unesco.org/ark:/48223/pf0000379949,2026-07-23,current_official,"Open-science values and practices with responsibility, inclusiveness, and governance" +SRC-HHS-COMMON-RULE,U.S. Department of Health and Human Services,Federal Policy for the Protection of Human Subjects (Common Rule),Official page updated 2026-01-15,regulation and policy index,https://www.hhs.gov/ohrp/regulations-and-policy/regulations/common-rule/index.html,2026-07-23,current_official,"45 CFR 46 applicability and agency implementation; obtain an authorized local determination" +SRC-BELMONT,U.S. Department of Health and Human Services,The Belmont Report,Published 1979,ethical principles report,https://www.hhs.gov/ohrp/regulations-and-policy/belmont-report/index.html,2026-07-23,current_foundational,"Respect for persons, beneficence, and justice" +SRC-HELSINKI,World Medical Association,WMA Declaration of Helsinki: Ethical Principles for Medical Research Involving Human Participants,Revised October 2024,international ethical declaration,https://www.wma.net/policies-post/wma-declaration-of-helsinki/,2026-07-23,current_official,"Current international medical-research ethics statement; local law and review remain controlling" +SRC-OLAW-PHS,Office of Laboratory Animal Welfare,Public Health Service Policy on Humane Care and Use of Laboratory Animals,Current official policy page,official animal-welfare policy,https://grants.nih.gov/policy-and-compliance/policy-topics/animal-welfare/laws-regulations/phs-policy,2026-07-23,current_official,"Institutional assurance and IACUC review for covered PHS-conducted or supported animal activities" +SRC-ARRIVE,NC3Rs / ARRIVE Group,ARRIVE Guidelines 2.0,Published 2020,animal reporting guideline,https://arriveguidelines.org/arrive-guidelines,2026-07-23,current_official,"Reporting guidance for in vivo animal research; not an ethics approval or design-quality certificate" +SRC-NIH-RSNA,National Institutes of Health,NIH Guidelines for Research Involving Recombinant or Synthetic Nucleic Acid Molecules,April 2024 or latest revision,official biosafety policy,https://osp.od.nih.gov/wp-content/uploads/NIH_Guidelines.htm,2026-07-23,current_official,"Institutional biosafety oversight and covered research categories; verify the latest revision and local applicability" +SRC-NIH-BIOSEC,NIH Office of Science Policy,Biosafety and Biosecurity Policy,Official page updated 2026-04-09,official policy portal,https://osp.od.nih.gov/policies/biosafety-and-biosecurity-policy,2026-07-23,current_official,"Current NIH biosafety, biosecurity, IBC, transparency, and Executive Order implementation links" +SRC-BMBL,Centers for Disease Control and Prevention and National Institutes of Health,Biosafety in Microbiological and Biomedical Laboratories,6th edition June 2020,official biosafety guidance,https://www.cdc.gov/labs/bmbl/index.html,2026-07-23,current_official,"Risk assessment and biosafety guidance; use qualified institutional review rather than inferring procedures from this skill" +SRC-WHO-LIFE,World Health Organization,Global Guidance Framework for the Responsible Use of the Life Sciences,Published 2022-09-13,international guidance,https://www.who.int/publications/i/item/9789240056107,2026-07-23,current_official,"Values, principles, tools, and governance for biorisks and dual-use throughout the research lifecycle" +SRC-EO-14292,The White House,Executive Order 14292: Improving the Safety and Security of Biological Research,Signed 2025-05-05,executive order,https://www.whitehouse.gov/presidential-actions/2025/05/improving-the-safety-and-security-of-biological-research,2026-07-23,current_official,"Directed revision/replacement of 2024 DURC/PEPP policy and pause/termination actions for covered dangerous gain-of-function research" +SRC-NIH-NOT-25-112,National Institutes of Health,NOT-OD-25-112: Implementation Update—Improving the Safety and Security of Biological Research,Issued 2025-05,official funding notice,https://grants.nih.gov/grants/guide/notice-files/NOT-OD-25-112.html,2026-07-23,current_official,"States Executive Order definitions superseded NIH's 2024 DURC/PEPP implementation and rescinds NOT-OD-25-061" +SRC-ASPR-DURC,U.S. Department of Health and Human Services ASPR,Dual Use Research of Concern Oversight Policy Framework,Official page current at verification,official policy status page,https://aspr.hhs.gov/S3/Pages/Dual-Use-Research-of-Concern-Oversight-Policy-Framework.aspx,2026-07-23,current_transition_notice,"At verification, states the 2024 DURC/PEPP policy will be revised or replaced and page will update when revised policy is available" diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/causal_inference_and_claims.md b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/causal_inference_and_claims.md new file mode 100644 index 0000000..518c0db --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/causal_inference_and_claims.md @@ -0,0 +1,190 @@ +# Causal Inference and Claim Discipline + +## Start with the scientific target + +Association, prediction, intervention effects, and mechanisms answer different questions: + +- **Descriptive:** What is the distribution or pattern? +- **Associational:** How do measured variables co-vary in the observed data? +- **Predictive:** How well does information predict outcomes in a target setting? +- **Causal:** What would differ under specified interventions or exposure conditions? +- **Mechanistic:** Through which process would the causal change occur? + +A model can predict accurately without identifying a causal effect. A randomized effect estimate can identify an intervention contrast without establishing the complete mechanism. + +## Define a causal estimand + +Following the causal-question and ICH E9(R1) principles where applicable, specify: + +1. **Population/system:** To whom or what does the contrast apply? +2. **Intervention/exposure:** What condition is set, assigned, or contrasted? +3. **Comparator:** What alternative condition is compared? +4. **Outcome:** What variable is affected and how is it measured? +5. **Time horizon:** When is the outcome assessed? +6. **Population summary:** Mean difference, risk ratio, quantile contrast, survival summary, or another target. +7. **Intercurrent events:** How are post-assignment events handled when they affect interpretation or measurement? +8. **Treatment versions:** Are interventions sufficiently well defined? + +The estimand should exist before choosing an estimator or model. + +## Counterfactual contrast + +Causal effects compare outcomes under different conditions for the same target units, although both conditions cannot usually be observed for one unit. Identification therefore depends on design and assumptions. + +For observational data, state: + +- target-trial analogue or other design logic; +- consistency/well-defined intervention assumptions; +- exchangeability/no-unmeasured-confounding assumptions; +- positivity/overlap; +- interference assumptions; +- measurement and missingness assumptions; +- model assumptions introduced by estimation. + +Do not write “controlled for confounding” as if adjustment proves exchangeability. + +## Bias pathways + +### Confounding + +A common cause of exposure/intervention and outcome can create or obscure an association. Address through design, randomization where ethical/feasible, restriction, matching, measurement and adjustment of justified common causes, negative controls, sensitivity analysis, or triangulation. + +Risks: + +- unmeasured or poorly measured common causes; +- time-varying confounders affected by prior treatment; +- inappropriate adjustment for instruments, mediators, or colliders; +- residual confounding after coarse categorization. + +### Selection bias + +Selection into the sample, analysis, follow-up, or observed outcome can depend on causes of exposure and outcome. Record: + +- sampling and eligibility; +- participation and consent; +- exclusions; +- attrition and censoring; +- complete-case restrictions; +- availability of measurements; +- conditioning introduced by data linkage. + +### Collider bias + +A collider is a common effect of two variables. Conditioning on it or its descendant can open a non-causal path. Common sources include: + +- selection into a study or subgroup; +- restricting to diagnosed, hospitalized, tested, or surviving participants; +- adjusting for a post-exposure variable affected by another cause of the outcome; +- using complete cases when missingness is jointly caused. + +More covariates are not automatically better. + +### Reverse causation + +The outcome or its precursors may influence the exposure or measurement. Cross-sectional order is especially weak evidence of direction. Use temporal design, lagged measurements, incident outcomes, intervention, negative controls, or explicit bidirectional candidates where appropriate. + +### Measurement bias + +Measurement error can: + +- attenuate or inflate estimates; +- differ by exposure or outcome; +- induce apparent interactions; +- distort covariate adjustment; +- affect selection into analysis. + +Operationalization and validation are part of causal design, not a later documentation task. + +## Mediators and effect modifiers + +- A **mediator** lies on a causal pathway. Adjusting for it changes the target from total to a direct or controlled effect and introduces additional assumptions. +- An **effect modifier** describes variation in a causal contrast across strata. It is not synonymous with statistical interaction in every scale. +- A **confounder** is defined relative to a target causal contrast and design, not simply by association with the outcome. + +Label the intended role before analysis and justify it with domain knowledge and a causal structure. + +## Negative controls + +Lipsitch, Tchetgen Tchetgen, and Cohen distinguish negative-control exposures and outcomes: + +- A negative-control exposure should not cause the target outcome through the proposed mechanism but should share relevant confounding/bias pathways. +- A negative-control outcome should not be caused by the target exposure through the proposed mechanism but should share relevant bias pathways. + +Specify: + +- why the target mechanism cannot operate; +- which biases should be shared; +- expected result; +- implication of control failure; +- alternative reasons for a non-null control result. + +Negative controls detect some biases under assumptions; they do not prove absence of bias. + +## Claim-language rules + +### Associational + +Use: + +- “was associated with”; +- “co-varied with”; +- “predicted in the evaluated dataset”; +- “the adjusted association.” + +State design, population, timing, effect/summary measure, uncertainty, and limitations. + +### Causal + +Use causal verbs only when: + +- the causal estimand is explicit; +- design/identification logic is stated; +- assumptions and sensitivity are visible; +- confounding, selection, collider, reverse-causation, and measurement risks are addressed; +- language is calibrated to the evidence. + +For observational work, “estimated causal effect under the stated assumptions” is often more accurate than an unqualified causal declaration. + +### Mechanistic + +Distinguish: + +- direct evidence for process steps; +- mediation or intermediate measurements; +- perturbation/rescue evidence; +- temporal ordering; +- analogy or plausibility only. + +A causal intervention effect does not by itself verify the proposed pathway. + +## Markdown claim annotations + +The bundled linter recognizes line-level annotations: + +```markdown +[claim:associational] Exposure X was associated with outcome Y in the observed cohort. + +[claim:causal][estimand:E1][identification:observational_assumption_dependent][confounding:unresolved][selection:assessed][collider:assessed][reverse-causation:assessed] Under the stated assumptions, intervention X would reduce outcome Y over 12 months. +``` + +Allowed risk states are `assessed`, `unresolved`, and `not_applicable`. “Assessed” records that a human evaluation exists; it does not mean the risk is absent. + +Run: + +```bash +python3 scripts/lint_causal_claims.py local-draft.md +``` + +The linter is lexical. It can miss causal language, flag benign phrases, and cannot judge whether a design identifies an effect. + +## Intervention-trial context + +For intervention hypotheses: + +- align objectives, estimands, outcomes, timing, harms, and analysis; +- use SPIRIT 2025 for protocol reporting and CONSORT 2025 for trial-result reporting; +- preserve access to protocol and statistical analysis plan; +- report important post-start changes and non-prespecified outcomes/analyses; +- include harms and participant/public involvement where applicable. + +Reporting completeness is not proof of ethical approval, design validity, regulatory compliance, or treatment efficacy. diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/concepts_and_workflow.md b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/concepts_and_workflow.md new file mode 100644 index 0000000..af46781 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/concepts_and_workflow.md @@ -0,0 +1,173 @@ +# Concepts and Candidate Lifecycle + +## Purpose + +This reference prevents common category errors in hypothesis work. It is a vocabulary and workflow guide, not a theory of confirmation and not an automatic ranking method. + +## Object model + +### Observation + +A bounded account of what was detected or reported: + +- source or measurement; +- population/system, place, and time; +- unit of observation; +- preprocessing, exclusions, missingness, and uncertainty; +- whether the observation was expected or selected after inspection. + +An observation can be mistaken, biased, or unrepresentative. It does not explain itself. + +### Research question + +An answerable question that fixes the scope of inquiry. It should identify the target population/system, variables or interventions, comparator where meaningful, outcome, timeframe, context, and claim type. + +PICO/PICOT is appropriate for many intervention-effect questions. It is not a universal ontology. Use a framework matched to the question and involve affected stakeholders where appropriate. + +### Hypothesis + +A candidate proposition that could explain or relate observations and yield testable implications. Keep its status as `candidate` until evidence changes the state. Avoid “validated hypothesis,” “proven mechanism,” and similar language unless the statement is being used only to quote a source accurately. + +### Mechanism + +A proposed process connecting antecedent conditions to an outcome. A mechanism should identify entities, activities, ordering, and boundary conditions where the domain permits. A plausible narrative without discriminating predictions remains a story. + +### Causal estimand + +A precise target causal contrast. At minimum, state: + +- target population/system; +- intervention/exposure and comparator; +- outcome and time horizon; +- population-level summary; +- treatment versions and intercurrent-event strategy where relevant; +- identification assumptions. + +The estimand is the target, the estimator is the method, and the estimate is the numerical result. + +### Prediction + +An observable implication derived from a candidate before checking the target result. A useful prediction specifies conditions, measurement, expected pattern, uncertainty, and an incompatible result. It should distinguish at least one rival when possible. + +### Alternative explanation + +A rival account that could produce the same observation. Rivals include: + +- distinct mechanisms; +- measurement or processing artifacts; +- confounding/common causes; +- selection or attrition; +- collider conditioning; +- reverse causation; +- contextual or temporal heterogeneity; +- stochastic variation. + +Rivals can coexist. Do not force mutual exclusivity when a mixed explanation is scientifically plausible. + +### Null hypothesis + +A defined no-effect/no-difference model used in an analysis. It is not “nothing happened,” and failure to reject it does not establish equivalence or absence. Define compatibility, equivalence, or non-inferiority rules separately when those are the scientific targets. + +### Negative control + +A control in which the target mechanism should not operate but relevant bias pathways should remain. Negative exposure and negative outcome controls can reveal confounding, selection, measurement, or analytic bias when their assumptions are credible. A negative control does not repair bias automatically. + +### Operationalization + +The mapping from a construct to a measurement, category, intervention, or variable. Record instrument/method, unit, timing, population/system, validity, reliability, calibration, missingness, transformations, cut points, and limitations. + +### Analysis plan + +The planned mapping from data to estimand, prediction, or descriptive target. It includes units, populations, transformations, models, contrasts, effect measures, uncertainty, missingness, multiplicity, diagnostics, sensitivity analyses, and decision rules. + +### Evidence + +Empirical observations or documented sources that bear on claims. Record whether a source supports, challenges, contextualizes, or supplies a method. Citation presence does not prove claim support; a human must inspect the source. + +## Candidate lifecycle + +Use explicit states: + +1. **Draft candidate** — generated but not yet searched or operationalized. +2. **Evidence-bounded candidate** — linked to a dated search and source ledger. +3. **Test-ready candidate** — has measurements, rivals, falsifiers, controls, and analysis links. +4. **Preregistered candidate** — time-stamped before the relevant outcome was inspected. +5. **Tested candidate** — results and deviations are available. +6. **Retained, revised, challenged, or unresolved** — human interpretation with uncertainty. + +Never use `true`, `proven`, or `selected_winner` as a machine-generated state. + +## Multiple hypotheses and strong inference + +Platt’s 1964 strong-inference essay advocates: + +1. devising alternative hypotheses; +2. devising a crucial experiment with alternative possible outcomes that exclude candidates; +3. performing the experiment cleanly; +4. recycling the process with subhypotheses. + +Use this as a discipline for contrast, not as a guarantee of truth. In practice: + +- alternatives may be incomplete; +- candidates may not be mutually exclusive; +- auxiliary assumptions can fail; +- measurements may not distinguish the intended mechanisms; +- a “crucial” result may be indeterminate; +- exclusions remain provisional. + +Always include an “unknown or mixed explanation” path in interpretation. + +## Exploratory and confirmatory modes + +### Exploratory + +- Generates observations, candidates, variables, and models. +- Can be data-dependent. +- Must record that dependence. +- Produces hypotheses for future tests rather than relabeling the same-data analysis as confirmation. + +### Confirmatory + +- Defines hypotheses, outcomes, exclusions, transformations, models, and decision rules before inspecting the target result. +- Preserves the planned analysis. +- Reports deviations and additional analyses transparently. + +Both modes are scientifically valuable. The integrity failure is not exploration; it is presenting exploration as if it were prespecified. + +## Uncertainty vocabulary + +Prefer: + +- “candidate explanation”; +- “consistent with under the stated assumptions”; +- “challenges this candidate if measurement and design assumptions hold”; +- “not distinguished by this result”; +- “not located within the documented search boundary”; +- “requires replication or external validation.” + +Avoid: + +- “proved” or “disproved” for ordinary empirical results; +- “novel” based only on no quick search hit; +- “no effect” from a non-significant result; +- “causes” from an unqualified association; +- “the mechanism” when several remain plausible. + +## Minimum handoff + +A hypothesis package should contain: + +- frozen observation; +- framed question and claim type; +- dated search boundary and source ledger; +- candidate hypotheses and mechanisms; +- rivals and bias explanations; +- causal estimand if applicable; +- discriminating predictions and falsifiers; +- operationalization and measurement-validity record; +- nulls and controls; +- design and analysis plan; +- uncertainty and boundary conditions; +- ethics/safety/regulatory gates; +- preregistration/deviation plan; +- accountable human review. diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/ethics_safety_and_ai.md b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/ethics_safety_and_ai.md new file mode 100644 index 0000000..46252aa --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/ethics_safety_and_ai.md @@ -0,0 +1,216 @@ +# Ethics, Safety, Feasibility, and Responsible AI + +## This is a routing guide + +This reference helps identify gates. It is not legal, medical, regulatory, biosafety, biosecurity, export-control, ethics, or institutional advice. Requirements vary by jurisdiction, sponsor, institution, organism, material, and intended use. + +When applicability is uncertain, mark the gate `undetermined`, stop operational planning, and obtain a determination from the qualified local authority. + +## Universal intake + +Record: + +- accountable owner and institution; +- intended purpose and foreseeable misuse; +- affected people, animals, communities, ecosystems, infrastructure, or security interests; +- data and material sensitivity; +- funding, jurisdiction, and collaborating sites; +- required expertise; +- conflicts and incentives; +- approvals, determinations, and unresolved blocks; +- less risky ways to answer the question. + +Feasibility never overrides ethics or safety. + +## Human-participant gate + +Potential triggers include: + +- intervention or interaction with living people; +- identifiable private information or biospecimens; +- secondary use, linkage, re-identification, recruitment, or contact; +- vulnerable populations or sensitive topics; +- international or community-governed data. + +Required action: + +- obtain an IRB/REC or other authorized determination before research starts; +- do not self-declare exemption; +- address consent or authorized waiver, privacy, security, equitable selection, risk/benefit, compensation, return of results, and community governance as applicable; +- use additional protections required by law or policy. + +In the United States, HHS 45 CFR 46 includes the Common Rule and additional subparts. Local and non-U.S. rules can differ. The 2024 revision of the World Medical Association Declaration of Helsinki is a relevant international ethical statement for medical research involving human participants. + +This skill does not provide clinical advice or authorize an intervention. + +## Animal-research gate + +Potential triggers include live vertebrate animals, field capture, breeding, procedures, tissues tied to ongoing animal activities, or covered training/testing. + +Required action: + +- obtain the applicable IACUC or equivalent approval before work; +- establish institutional assurance and veterinary oversight where required; +- apply replacement, reduction, and refinement; +- justify species/model, numbers, endpoints, welfare monitoring, analgesia/anesthesia, and humane endpoints through the authorized process; +- use current reporting guidance such as ARRIVE when applicable. + +The bundled tools do not calculate animal numbers or approve protocols. + +## Biosafety and biosecurity gate + +Potential triggers include: + +- recombinant or synthetic nucleic acids; +- infectious agents, toxins, biological materials, gene transfer, or modified organisms; +- environmental release; +- select agents or regulated materials; +- procedures that could alter hazard, host range, pathogenicity, transmissibility, resistance, or detection; +- work beyond established institutional containment and training. + +Required action: + +- stop before operational detail; +- route to the biosafety officer, Institutional Biosafety Committee, and other required authority; +- use the current NIH Guidelines, CDC/NIH *Biosafety in Microbiological and Biomedical Laboratories*, local biosafety manual, and applicable regulations; +- document containment and occupational-health decisions only after authorized review. + +Do not infer a containment level or operating procedure from this skill. + +## Dual-use and harmful-use gate + +Potential triggers include research, data, models, or protocols that could reasonably enable: + +- increased biological harm or spread; +- evasion of detection, treatment, control, or safeguards; +- scalable production or dissemination of harmful agents; +- weaponization; +- exploitation of critical vulnerabilities; +- transfer of restricted technical information. + +Required action: + +1. Do not provide optimization, stepwise procedures, parameter choices, sequences, acquisition pathways, or troubleshooting that increase harmful capability. +2. Preserve only a high-level scientific question, benefit rationale, and risk statement. +3. Route to institutional dual-use/biosecurity review, funder, legal/export-control, and other required authorities. +4. Follow current policy, award terms, and jurisdiction-specific controls. + +### U.S. policy status checked 2026-07-23 + +- Executive Order 14292 of May 5, 2025 directed revision/replacement of the 2024 U.S. Government DURC/PEPP policy and paused federally funded research meeting its “dangerous gain-of-function” definition pending the replacement policy. +- NIH Notice NOT-OD-25-112 stated that the Executive Order superseded NIH implementation of the 2024 DURC/PEPP policy and rescinded NOT-OD-25-061. +- The HHS/ASPR policy page still stated at the verification date that federal departments and agencies would revise or replace the 2024 policy and that the page would be updated when the revised policy became available. + +Do not use the superseded 2024 implementation as current clearance. Recheck the official policy and award terms for every project because this status is time-sensitive. + +WHO’s *Global Guidance Framework for the Responsible Use of the Life Sciences* provides an international risk-governance framework; it does not replace national or local rules. + +## Data-governance gate + +Before using data: + +- confirm authority, consent, license, data-use agreement, and purpose limitation; +- classify sensitivity and re-identification risk; +- minimize fields and access; +- use approved storage, retention, deletion, audit, and sharing controls; +- address community and Indigenous governance; +- separate public, controlled, confidential, proprietary, and export-controlled materials. + +Passing a local schema check is not de-identification, anonymization, HIPAA compliance, GDPR compliance, or authorization to share. + +## Regulatory gate + +Potential triggers include: + +- human interventions or clinical investigations; +- drugs, biologics, devices, diagnostics, or software intended for clinical use; +- environmental release; +- genetically modified organisms; +- regulated laboratory, animal, agricultural, or chemical activities; +- claims intended for product labeling, approval, or public-health action. + +Record: + +- intended use; +- jurisdiction; +- product/activity classification; +- sponsor and responsible regulatory owner; +- applicable quality system or submission route; +- current determination and source/date. + +Do not infer regulatory status from a research label, reporting checklist, or generated artifact. + +## Feasibility gate + +Assess: + +- scientific and technical capability; +- validated measurement; +- statistical information/precision; +- qualified personnel and facilities; +- time and resources; +- access to population/system; +- approvals and material/data access; +- foreseeable failure and stopping criteria. + +If infeasible, revise the question or conduct a bounded feasibility study. Do not weaken protections or invent optimistic assumptions. + +## Responsible AI policy + +### Local-first rule + +Default to local processing. Do not send sensitive, unpublished, confidential, personal, proprietary, controlled, or security-relevant information to an external AI system without: + +- explicit authorization; +- a named approved service and account; +- a defined minimum data scope; +- contract, retention, training-use, location, and access review; +- applicable publisher, funder, institutional, and participant permission. + +The bundled scripts make no network, model, image, or external-service calls and read no environment credentials. + +### Human accountability + +An accountable human must: + +- own the question, candidate set, and final scientific decisions; +- verify every citation, identifier, quotation, and source-to-claim link; +- verify calculations and scientific plausibility; +- inspect omitted rivals and boundary conditions; +- review ethics, safety, privacy, dual-use, and regulatory implications; +- disclose AI assistance where policy requires; +- retain or delete records under the controlling policy. + +AI output is not evidence and cannot grant approval. + +### Known AI risks + +NIST AI 600-1 identifies generative-AI risks including confabulation, data privacy, harmful bias/homogenization, information integrity, human–AI configuration, and dangerous recommendations. Mitigate by: + +- independent human ideation before AI expansion; +- generating rivals from different disciplinary perspectives; +- separating source retrieval from claim synthesis; +- checking primary sources directly; +- recording prompts/tool versions when authorized and scientifically relevant; +- challenging convergent, polished, or overly confident output; +- using multiple human reviewers for high-consequence work. + +Doshi and Hauser’s 2024 experiment found AI-assisted stories were more similar to one another even while some individual creativity measures improved. Do not generalize that one study to all scientific ideation; treat homogenization as a plausible risk and preserve independent candidate generation. + +UNESCO’s AI ethics recommendation emphasizes human rights, privacy/data protection, responsibility/accountability, transparency, and human oversight. Ultimate responsibility remains human. + +## Stop conditions + +Stop and escalate when: + +- authorization is absent or ambiguous; +- a required review is missing; +- data or material classification is unknown; +- harmful-use potential cannot be bounded; +- a request seeks operational harmful detail; +- patient-specific advice is requested; +- a regulatory or legal determination is needed; +- the proposed measurement cannot validly bear on the construct; +- qualified expertise is unavailable. + +Record the block without copying sensitive details into a general-purpose artifact. diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/experimental_design_patterns.md b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/experimental_design_patterns.md new file mode 100644 index 0000000..0367eca --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/experimental_design_patterns.md @@ -0,0 +1,301 @@ +# Design Patterns for Discriminating Tests + +## Design starts from the prediction + +For each test, link: + +`candidate → mechanism → prediction → observable → operationalization → design → analysis → interpretation` + +Choose the design that can distinguish candidates under realistic uncertainty. Do not select a design merely because it is familiar or available. + +## NIH-aligned rigor questions + +Where applicable, address: + +- rigor of the prior research forming the scientific premise; +- unbiased and well-controlled design; +- relevant biological variables such as sex, age, weight, or health condition; +- authentication and validity of key biological/chemical resources; +- transparent methods, analysis, interpretation, and reporting. + +Apply only the elements relevant to the science and explain omissions. + +## Core design record + +Every design should state: + +- study system and target population; +- experimental/observational unit and analysis unit; +- sampling frame and recruitment/selection; +- interventions/exposures and comparator versions; +- allocation, randomization, concealment, and masking; +- outcomes, timing, measurement IDs, and quality control; +- positive, negative, vehicle/sham, procedural, or reference controls as applicable; +- inclusion, exclusion, attrition, and stopping; +- sample-size, precision, or information rationale; +- analysis IDs and estimands; +- safety, ethics, feasibility, data, and regulatory gates; +- replication, transport, and external-validation plan. + +## Experimental designs + +### Randomized intervention + +Useful for causal contrasts when intervention, allocation, and ethics permit. + +Check: + +- allocation sequence and concealment; +- intervention versions, adherence, contamination, and co-interventions; +- masking of participants, providers, outcome assessors, and analysts where feasible; +- primary estimand and intercurrent events; +- intention-to-treat or other analysis population aligned to the estimand; +- harms, stopping, missing outcomes, and protocol deviations. + +Randomization does not solve measurement bias, nonadherence, post-randomization selection, interference, or poor external validity. + +### Factorial design + +Useful for multiple interventions and interactions. + +Check: + +- scientific meaning and scale of interaction; +- power/precision for interactions, not only main effects; +- compatibility and safety of combined conditions; +- multiplicity and hierarchy; +- whether sparse combinations undermine interpretation. + +### Within-unit or crossover design + +Useful when effects are reversible and carryover can be controlled. + +Check: + +- period and sequence effects; +- washout rationale; +- time trends and learning; +- missing periods; +- whether the condition is stable and intervention reversible. + +### Perturbation and rescue + +Useful for mechanistic candidates when ethically and technically appropriate. + +Check: + +- perturbation specificity and off-target effects; +- manipulation check; +- rescue interpretation and overexpression artifacts; +- temporal order; +- orthogonal perturbations and measurements; +- relevant negative and positive controls. + +A rescue can still be explained by compensatory or non-specific effects. + +### Time-course + +Useful when candidates predict different ordering or dynamics. + +Check: + +- sampling times justified by expected process; +- independent versus repeated units; +- baseline and pre-trend; +- measurement stability across time; +- multiple looks and timepoint multiplicity; +- lag, feedback, and reverse causation. + +## Observational designs + +### Cross-sectional + +Can estimate prevalence and associations at a defined time. It usually cannot establish temporal direction. Explicitly consider selection, reverse causation, survival/prevalence bias, and common-method measurement. + +### Cohort/longitudinal + +Can establish measured temporal ordering and incidence. It does not eliminate confounding. + +Check: + +- time zero and eligibility; +- exposure updates and time-varying confounding; +- loss to follow-up and informative censoring; +- competing events; +- immortal-time and delayed-entry risks; +- outcome ascertainment changes. + +### Case-control + +Efficient for some rare outcomes. + +Check: + +- source population and control sampling; +- matching implications; +- exposure measurement and recall; +- selection mechanisms; +- correct effect measure and sampling analysis. + +### Natural/quasi-experimental + +Can strengthen causal identification when an assignment mechanism or discontinuity is credible. + +Check: + +- assignment mechanism and manipulation; +- continuity, parallel trends, exclusion, or instrument assumptions as applicable; +- anticipation and spillovers; +- bandwidth/window choices; +- placebo/negative-control tests; +- sensitivity to specification and clustering. + +The design label alone does not establish identification. + +## Computational and theoretical designs + +### Simulation + +Use to test implications of assumptions, estimator behavior, or model dynamics. + +Record: + +- data-generating process and parameter ranges; +- rationale for scenarios; +- seeds and software versions; +- performance targets and uncertainty; +- failure cases and sensitivity; +- separation between simulated truth and empirical validity. + +Simulation can show consequences within a model, not that the model describes nature. + +### Predictive model evaluation + +Separate prediction from causation. + +Check: + +- target population, outcome, time origin, and horizon; +- leakage and preprocessing; +- train/tune/test independence; +- calibration and discrimination; +- uncertainty and subgroup performance; +- temporal/geographic/external validation; +- dataset shift and update policy. + +### Secondary-data analysis + +Record provenance, data-generating process, inclusion, missingness, transformations, version, and prior analysis exposure. Avoid using the same data to generate and confirm a hypothesis without transparent separation or independent validation. + +## Controls + +### Positive control + +A condition expected to produce a known response. It checks whether the system and measurement can detect a relevant effect. + +### Procedural control + +Matches handling, timing, delivery, or processing without the target active component. + +### Negative control + +Should not operate through the target mechanism but should share relevant bias pathways. State assumptions and failure interpretation. + +### Null comparator + +A comparator representing no intervention or no difference may be useful, but it is not equivalent to a negative control and may not isolate placebo, handling, expectancy, or background trends. + +## Measurement validity + +Before collecting target outcomes: + +- define constructs and proxies; +- verify instrument validity in the target context; +- assess reliability/repeatability; +- calibrate and authenticate resources; +- prespecify detection limits and quality failures; +- plan masking and standardized acquisition; +- define missing/invalid values; +- test cross-site, cross-device, cross-group, or longitudinal comparability where relevant. + +A precise measure can be precisely wrong. Technical replicates do not replace independent biological, participant, site, or experimental units. + +## Sample size and precision + +Do not use universal minima. + +Base planning on: + +- primary estimand and effect/precision target; +- expected variability and dependence; +- allocation ratio; +- attrition/missingness; +- multiplicity or sequential design; +- model complexity; +- feasibility and ethical burden; +- uncertainty in planning values. + +Report assumptions and sensitivity to them. Pilot data may be too unstable for definitive effect-size planning; use external evidence, conservative ranges, or precision-based goals where appropriate. + +## Multiplicity + +Inventory: + +- candidate hypotheses; +- outcomes and timepoints; +- subgroups and interactions; +- model specifications and transformations; +- interim looks and stopping; +- repeated datasets or cohorts. + +Prespecify a strategy appropriate to the inferential goal, such as: + +- family-wise error control; +- false-discovery-rate control; +- hierarchical/gatekeeping testing; +- multilevel estimation; +- clearly labeled exploratory analysis without confirmatory claims. + +Do not report only favorable analyses. Threshold crossing is not a quality score or probability that a candidate is true. + +## Missing data and deviations + +Define: + +- missingness by variable, time, and group; +- reasons and data-collection process; +- primary handling; +- assumptions; +- sensitivity analyses; +- protocol and analysis deviations. + +Complete-case analysis is not automatically unbiased. Record deviations without overwriting the original plan. + +## Replication and open materials + +Following the National Academies terminology: + +- reproducibility uses the same data/code/conditions; +- replicability collects new data to address the same question. + +Plan: + +- code, environment, seeds, and workflow capture; +- provenance and versioning; +- shareable materials and justified restrictions; +- independent replication; +- boundary-condition and transport tests; +- reporting of null and contrary results. + +Open sharing remains subject to consent, privacy, community governance, intellectual property, biosecurity, and other controls. + +## Intervention reporting + +For randomized intervention work: + +- use SPIRIT 2025 and its explanation/elaboration for protocol completeness; +- use CONSORT 2025 and its explanation/elaboration for result reporting; +- include trial registration, protocol and statistical-analysis-plan access, outcomes, harms, intervention/comparator details, analysis populations, missing data, and important changes; +- use applicable extensions. + +These are reporting guidelines. They do not replace ethics review, trial registration rules, statistical expertise, or regulatory requirements. diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/hypothesis_quality_criteria.md b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/hypothesis_quality_criteria.md new file mode 100644 index 0000000..528014c --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/hypothesis_quality_criteria.md @@ -0,0 +1,203 @@ +# Human Review Criteria for Candidate Hypotheses + +## No automatic quality score + +These criteria structure expert review. Do not sum them, assign weights, calculate a “quality score,” rank candidates automatically, or select a winner. Trade-offs and domain assumptions are not commensurable numbers. + +For each criterion record: + +- evidence or rationale; +- uncertainty and missing information; +- source IDs; +- reviewer role and date; +- revision or test needed. + +## Question-level review + +### Feasibility + +- Are required data, samples, methods, expertise, time, and resources available? +- Is the unit of analysis attainable without pseudoreplication? +- Can the needed precision or information be achieved? +- Are approvals and governance pathways realistically available? +- Would a pilot answer feasibility rather than the scientific hypothesis? + +### Interest and relevance + +- Which scientific, stakeholder, policy, or practical decision could the answer inform? +- Were affected groups or domain experts involved where appropriate? +- Is the burden of the work proportionate to its expected informational value? + +### Novelty + +Treat novelty as a separate evidence claim: + +- What databases, indexes, registries, patents, repositories, and grey literature were searched? +- What queries, dates, languages, and screening limits were used? +- Was prior work examined for conceptually equivalent terminology? +- Did a domain expert assess near neighbors and historical literature? + +Use “not located within the documented search boundary” when that is all the evidence supports. Absence from a quick search is not evidence of novelty. + +### Ethics + +- Are human, animal, environmental, privacy, community, biosafety, dual-use, and regulatory implications assessed? +- Is there a less burdensome way to answer the question? +- Are harms, benefits, fairness, consent, and stewardship addressed? +- Are required reviews complete before work begins? + +FINER—Feasible, Interesting, Novel, Ethical, Relevant—is a mnemonic for refining a question, not a pass/fail instrument. The earliest source located in this refresh is the first edition of *Designing Clinical Research* (Hulley and Cummings, 1988); later editions and current methodological articles present the mnemonic. The dated search did not establish that the 1988 edition was the first printed use, so do not claim coinage without checking the primary text. + +## Hypothesis-level review + +### Clarity + +- Is the statement a candidate proposition rather than an observation or question? +- Are population/system, conditions, variables, direction, and timeframe explicit? +- Is the mechanism separate from the hypothesis statement? +- Are undefined terms and escape clauses removed? + +### Testability + +- Are observables and measurements available? +- Does the candidate generate at least one prospective prediction? +- Can a feasible design bear on the prediction? +- Are assumptions needed to connect result to candidate stated? + +### Falsifiability and vulnerability + +- What result would be incompatible under the stated assumptions? +- Could the candidate explain every possible outcome after the fact? +- Are indeterminate outcomes acknowledged? +- Does the proposed test risk only “confirming” the preferred candidate? + +A null result can be uninformative because of low precision, failed manipulation, insensitive measurement, missingness, or assumption failure. Record these possibilities before calling a result falsifying. + +### Discriminability + +- Which rival predicts a different observable pattern? +- Is the difference larger than expected measurement uncertainty? +- Can the test distinguish mixed mechanisms? +- Are positive, procedural, and negative controls informative? +- What result supports neither candidate? + +### Mechanistic adequacy + +- Does the mechanism specify entities, activities, ordering, and context? +- Does it respect established constraints or explicitly identify where it departs? +- Are intermediate steps measurable? +- Could a simpler bias or measurement explanation produce the observation? + +Mechanistic detail is not evidence. A more elaborate story can be less testable. + +### Boundary conditions and transport + +- Where, when, and for whom should the candidate apply? +- What exposure/intervention versions matter? +- What effect modifiers or contextual dependencies are plausible? +- Which populations, species, platforms, or scales are outside scope? +- What independent replication or external-validation test is planned? + +### Assumption transparency + +Separate: + +- scientific assumptions; +- measurement assumptions; +- design/identification assumptions; +- statistical/model assumptions; +- implementation assumptions. + +State which assumptions are testable, partially diagnosable, or fundamentally untestable with available data. + +### Evidence alignment + +For every source: + +- identify the exact claim it bears on; +- distinguish direct from indirect or analogous evidence; +- note design, population/system, and limitations; +- include challenging and null evidence; +- avoid venue prestige, citation count, or author reputation as a substitute for appraisal. + +### Uncertainty + +- Are direction, magnitude, and interval uncertainty separated? +- Is model or structural uncertainty acknowledged? +- Is measurement uncertainty propagated or discussed? +- Are unknown alternatives and residual confounding visible? +- Are conclusions calibrated to the evidence? + +## Prediction-level review + +A prediction should identify: + +- prediction ID and parent candidate; +- conditions and boundary conditions; +- observable and measurement ID; +- expected pattern, direction, magnitude/range if justified, and timing; +- rival and rival-expected pattern; +- falsifier/incompatible result; +- indeterminate outcome; +- linked analysis ID; +- assumptions and uncertainty. + +Do not invent numerical effect sizes merely to appear specific. If magnitude is unknown, prespecify the direction, smallest effect of scientific interest, precision target, or a range of plausible values with rationale. + +## Operationalization review + +For each construct ask: + +- Does the variable actually represent the construct? +- Is the instrument validated in the target context? +- Are reliability, calibration, detection limits, and quality control addressed? +- Are timing and aggregation aligned with the mechanism? +- Are cut points prespecified and justified? +- Are missingness and measurement error mechanisms considered? +- Is comparability across groups, time, sites, species, or devices established? +- Could the measurement itself be affected by exposure, outcome, or selection? + +## Causal-claim review + +Require: + +- a well-defined intervention/exposure contrast; +- causal estimand; +- target population and horizon; +- design/target-trial analogue; +- identification assumptions; +- confounding, selection, collider, measurement, and reverse-causation assessment; +- positivity/overlap and interference considerations where applicable; +- sensitivity analyses and negative controls where scientifically defensible. + +Predictive performance does not identify a causal effect. Adjustment does not guarantee exchangeability. Conditioning on a mediator or collider can introduce bias. + +## Analysis-plan review + +Check: + +- unit and dependence structure; +- sample-size/precision rationale; +- exclusions and stopping; +- outcome and analysis populations; +- transformations and model specification; +- effect/summary measures and uncertainty; +- missing data and intercurrent events; +- multiplicity across hypotheses, outcomes, subgroups, models, and looks; +- assumptions and diagnostics; +- robustness and sensitivity analyses; +- confirmatory/exploratory labels; +- deviation-reporting process. + +## Decision record + +End human review with one of: + +- `revise_before_test`; +- `ready_for_preregistration_review`; +- `blocked_by_safety_or_ethics_gate`; +- `blocked_by_measurement_or_feasibility`; +- `retain_as_exploratory_candidate`; +- `requires_specialist_review`. + +These are workflow states, not scientific truth judgments and not outputs of a score. diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/literature_search_strategies.md b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/literature_search_strategies.md new file mode 100644 index 0000000..c3853bb --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/literature_search_strategies.md @@ -0,0 +1,208 @@ +# Evidence Search and Source Traceability + +## Scope + +This workflow supports hypothesis formulation. It is not automatically a systematic review, evidence-grade, patentability search, regulatory determination, or proof of novelty. + +## Data and confidentiality gate + +Before searching: + +- remove confidential identifiers, unpublished results, proprietary sequences, exact vulnerabilities, and controlled operational details from queries; +- confirm that external search is authorized; +- translate sensitive observations into the minimum non-sensitive concepts needed; +- use approved local or institutional search tools when policy requires; +- record what was withheld and how that limits the search. + +Do not send unpublished or sensitive data to an external search engine, AI service, citation service, or model without explicit authorization. + +## Frame the search + +Separate search objectives: + +1. **Phenomenon:** Has the observation or a close analogue been reported? +2. **Mechanism:** Which processes could explain it? +3. **Rivals:** Which alternative mechanisms, artifacts, or biases are documented? +4. **Measurement:** How have the constructs been operationalized and validated? +5. **Design:** Which tests discriminate the candidates? +6. **Contrary evidence:** What findings challenge each candidate? +7. **Safety and governance:** Which ethical, biosafety, dual-use, data, or regulatory rules apply? +8. **Priority/novelty:** What prior work, registrations, preprints, patents, and grey literature address the same claim? + +Use separate queries so a mechanism search is not mistaken for a novelty search. + +## Question structures + +Choose a structure matched to the question: + +- PICO/PICOT: population, intervention, comparator, outcome, optional time; +- PECO: population, exposure, comparator, outcome; +- diagnostic: population, index test, reference standard, target condition; +- prognostic: population, prognostic factor, outcome, time; +- qualitative: population/sample, phenomenon, context; +- mechanistic: system, perturbation/condition, mediator/process, observable outcome; +- computational/theoretical: model class, assumptions, parameter regime, predicted observable. + +Cochrane uses PICO for intervention-effect review questions and distinguishes review PICO, PICO for each synthesis, and PICO of included studies. Do not force PICO onto every domain. + +## Source priority + +Prefer the source closest to the claim: + +1. law, regulation, official policy, or current institutional guidance for governance claims; +2. original paper, protocol, dataset, standard, or methods source for primary claims; +3. current reporting guideline and explanation/elaboration for reporting expectations; +4. systematic review or consensus report for landscape orientation; +5. narrative review for terminology and citation mining; +6. preprint or conference abstract, clearly labeled, for recent unreviewed work; +7. secondary webpages only when they point to a primary source or document current implementation. + +Authority does not replace critical appraisal. A primary study may be weak, and a current official policy may be jurisdiction-specific. + +## Search sequence + +### 1. Broad orientation + +- Search the phenomenon and field terminology. +- Locate one or more current reviews or consensus documents. +- Extract synonyms, controlled vocabulary, candidate mechanisms, and landmark sources. + +### 2. Primary evidence + +- Search each candidate mechanism separately. +- Search the original observation and closest analogues. +- Search prospective, experimental, longitudinal, and replication evidence where relevant. +- Search measurement-validation papers for each operationalization. + +### 3. Rival and falsification search + +For every candidate, run terms such as: + +- alternative explanation; +- confounding; +- selection bias; +- collider bias; +- reverse causation; +- measurement error/artifact; +- negative control; +- failed replication; +- null or contradictory result; +- boundary condition/effect modification. + +Record whether a source challenges the mechanism, the measurement, the design, or only its generalizability. + +### 4. Foundational and historical search + +Use backward citation tracing from methods and review papers. Search original titles, authors, books, standards, and DOI/PMID records. Recency is not a proxy for relevance. + +### 5. Forward citation and registration search + +Use citation indexes, trial registries, preregistration repositories, preprint servers, data/code repositories, and correction/retraction records as appropriate. + +### 6. Policy search + +Use current official sites and record: + +- jurisdiction and applicability; +- effective or revision date; +- superseded documents; +- local implementation requirements; +- date checked. + +For high-consequence work, a search result is not legal, regulatory, ethics, biosafety, or dual-use clearance. + +## Search documentation + +Complete `assets/search_boundary_template.json`. At minimum include: + +- `search_boundary_id`; +- `searched_on`; +- purpose; +- databases/indexes; +- exact queries or reproducible query descriptions; +- date/language/source-type limits; +- inclusion and exclusion scope; +- stop rule or last result screened; +- known access and coverage limitations; +- novelty status. + +Use `novelty_status: not_assessed` unless a qualified human has reviewed a fit-for-purpose search. Even a comprehensive search supports only a bounded statement. + +## Evidence ledger + +Complete `assets/evidence_ledger_template.csv` with one row per source: + +- stable source ID; +- linked claim IDs; +- title and author/organization; +- publication date; +- source type and identifier; +- canonical HTTPS URL; +- access date; +- relation: supportive, challenging, contextual, method, safety, or mixed; +- design/document type; +- limitations and notes. + +The audit validates structure and links only: + +```bash +python3 scripts/audit_evidence_ledger.py \ + local-evidence-ledger.csv \ + local-search-boundary.json \ + --record local-hypothesis-record.json +``` + +It does not visit URLs, verify existence, appraise evidence, or decide whether a citation supports a claim. + +## Claim-to-source notes + +For each consequential claim, record: + +- the exact source location (section, table, figure, page, or quoted sentence); +- whether the evidence is direct, indirect, analogous, or contradictory; +- study design and target population/system; +- magnitude and uncertainty actually reported; +- key limitations and conflicts; +- applicability to the candidate. + +Verify every identifier and claim against the source. Do not rely on search snippets or AI-generated citations. + +## Search stopping + +Use a documented stop rule, such as: + +- all prespecified databases searched; +- a fixed result depth screened per query; +- forward/backward citation tracing completed for named seed sources; +- predefined date and language boundary reached; +- saturation documented for terminology or mechanisms. + +Do not stop merely because: + +- preferred evidence was found; +- new results seem repetitive; +- a citation count is high; +- a source appears in a prestigious venue. + +## Reporting bounded conclusions + +Use: + +> Searches were conducted on YYYY-MM-DD in [indexes] using [queries/strategy]. Within the documented date, language, access, and screening limits, we located [scope]. This does not establish universal absence, priority, or novelty. + +For gaps: + +> No directly matching source was located within the documented search boundary. Related work was found on [near neighbors]. A broader specialist search is required before making a novelty claim. + +## Common failures + +- Treating impact factor or citation count as study validity +- Searching only for support +- Using only one database +- Ignoring terminology changes or historical work +- Citing a review for a claim that should cite the primary study +- Treating preprints as peer-reviewed +- Omitting corrections, retractions, or protocol/registration records +- Searching confidential text verbatim +- Claiming an exhaustive search without a reproducible protocol +- Conflating “not found” with “does not exist” diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/preregistration_and_open_science.md b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/preregistration_and_open_science.md new file mode 100644 index 0000000..6dab0a7 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/preregistration_and_open_science.md @@ -0,0 +1,205 @@ +# Preregistration, Registered Reports, and Open Science + +## Purpose + +Preregistration records a time-stamped plan before the relevant data are collected or analyzed. Its main value is making planned and data-dependent work distinguishable. + +Preregistration does not: + +- guarantee a valid design or analysis; +- prevent all researcher degrees of freedom; +- make a hypothesis true; +- forbid exploration or justified adaptation; +- replace ethics, safety, data, or regulatory review; +- require public release of restricted information. + +## What to preregister + +### Administrative + +- title and project ID; +- accountable owner and roles; +- registration date and repository; +- study status and prior access to relevant data; +- conflicts, funding, and sponsor roles. + +### Question and candidates + +- observation and provenance; +- research question and claim type; +- candidate hypotheses and mechanisms; +- rivals and alternative explanations; +- causal estimands where applicable; +- boundary conditions and uncertainty. + +### Predictions and controls + +- prediction IDs and parent candidates; +- conditions, measurements, expected patterns, and timing; +- falsifiers and indeterminate outcomes; +- discriminating expectations for rivals; +- null hypotheses; +- positive, procedural, and negative controls. + +### Design + +- population/system and sampling; +- experimental and analysis units; +- allocation, randomization, concealment, and masking; +- interventions/exposures and comparators; +- inclusion, exclusion, attrition, and stopping; +- sample-size or precision rationale; +- outcomes and measurement timing; +- ethics, safety, data, and regulatory status. + +### Analysis + +- analysis populations; +- transformations and data exclusions; +- models, contrasts, estimators, and effect/summary measures; +- uncertainty intervals or other inferential summaries; +- missing-data and intercurrent-event handling; +- multiplicity; +- assumptions and diagnostics; +- sensitivity and robustness analyses; +- rules for interpreting support, challenge, and indeterminacy. + +### Transparency + +- data, code, materials, and metadata plans; +- restrictions and controlled-access process; +- software/environment versions; +- AI/tool use; +- deviation log and reporting plan. + +## Timing and prior access + +State what had already occurred: + +- no data collected; +- data collected but target outcomes unseen; +- data available but analyst blinded; +- summary statistics viewed; +- exploratory analysis already performed; +- existing dataset reused. + +When data have already informed the plan, label the work transparently and use independent data, a held-out set, or a new replication for confirmatory testing where feasible. + +## Confirmatory versus exploratory + +### Confirmatory + +- planned before checking the target result; +- tied to specified outcomes and analyses; +- reported whether favorable, unfavorable, or null. + +### Exploratory + +- generated after or while viewing data; +- useful for discovery; +- labeled as data-dependent; +- treated as a source of future predictions. + +Do not call exploratory work “post hoc confirmation.” + +## HARKing + +Kerr defined HARKing as presenting a post hoc hypothesis as if it were a priori. Prevent it by: + +- preserving dated versions; +- separating planned and unplanned analyses; +- reporting all prespecified outcomes and tests; +- documenting when each candidate was generated; +- not rewriting unexpected results as predictions; +- seeking independent replication. + +## Deviations + +Preregistration is a plan, not a prison. For every material deviation record: + +- date; +- affected section and IDs; +- original plan; +- change; +- reason; +- who made the decision; +- whether the target result was known; +- likely effect on bias or interpretation; +- whether the original analysis is still reported. + +Do not silently replace the registration. Preserve the original and append amendments. + +## Registered Reports + +Registered Reports add journal peer review before results are known: + +1. Stage 1 protocol submission; +2. review of question, methods, and analysis; +3. in-principle acceptance under the journal’s conditions; +4. study conduct; +5. Stage 2 review focused on adherence, justified deviations, and interpretation. + +Check the current journal policy. In-principle acceptance is not ethics approval, funding, regulatory authorization, or assurance of a favorable result. + +## Intervention trials + +For randomized intervention hypotheses: + +- use current registration requirements for the applicable jurisdiction, funder, and venue; +- use SPIRIT 2025 for protocol reporting; +- align objectives, estimands, outcomes, harms, intervention details, statistical methods, and data sharing; +- use CONSORT 2025 for completed-trial reporting; +- report important changes, including non-prespecified outcomes or analyses. + +The preregistration scaffold in this skill is generic and is not a trial-registry submission, SPIRIT checklist, protocol, statistical analysis plan, or regulatory document. + +## Reproducibility and replicability + +Use the National Academies definitions: + +- **reproducibility:** obtaining consistent computational results with the same data, code, methods, and analysis conditions; +- **replicability:** obtaining consistent results in a new study addressing the same question with new data. + +Plan for: + +- stable identifiers and version control; +- code and environment capture; +- provenance and decision logs; +- independent replication; +- exact and conceptual replication; +- boundary-condition and transport tests; +- publication of null and challenging results. + +Non-replication does not automatically imply misconduct or that the original study was invalid. Differences can reveal heterogeneity, measurement limitations, context, or sampling variation. + +## Open-science limits + +“Open” does not override: + +- participant consent and privacy; +- Indigenous or community data governance; +- contractual or intellectual-property restrictions; +- export controls; +- biosafety and dual-use review; +- endangered-species or sensitive-location protections; +- security-sensitive vulnerabilities. + +Share the maximum responsibly permitted, not the maximum technically possible. Use metadata, synthetic examples, controlled access, or redacted protocols when full release is unsafe. + +## Scaffold generation + +Generate a local draft only after the hypothesis record validates: + +```bash +python3 scripts/generate_preregistration_scaffold.py \ + local-hypothesis-record.json \ + -o local-preregistration.md +``` + +The result: + +- is marked as an unregistered draft; +- includes every candidate without ranking; +- carries unresolved placeholders; +- requires human review and repository-specific completion; +- does not submit, register, upload, or transmit anything. diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/security_validation.md b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/security_validation.md new file mode 100644 index 0000000..9624806 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/security_validation.md @@ -0,0 +1,74 @@ +# Security Validation Record + +Validation date: **2026-07-23** (local project date). + +## Baseline + +The repository `SECURITY.md` section recorded **10 findings** with maximum severity **CRITICAL**: + +- cross-file environment-variable/network exfiltration; +- a multi-file collection/transmission chain; +- environment harvesting in both schematic scripts; +- credential transmission and local `.env` loading; +- subprocess delegation and full-environment propagation; +- fabricated or unverified model identifiers; +- unpinned external dependencies. + +The affected files were: + +- deleted: scripts/generate_schematic.py; +- deleted: scripts/generate_schematic_ai.py; +- the former credential and mandatory-figure instructions in `SKILL.md`. + +## Remediation + +- Deleted both external schematic scripts and all former LaTeX/figure assets. +- Removed credential declarations, environment access, `.env` loading, subprocesses, HTTP requests, external models, image generation, mandatory figures, and cross-skill behavior. +- Replaced the workflow with bounded deterministic local JSON/CSV/Markdown validators and a preregistration scaffold generator. +- Added strict duplicate-key/header detection, size/row/cell/list limits, symlink and URL-path rejection, private atomic outputs, and no implicit overwrite. +- Added AST tests rejecting network libraries, subprocesses, executable serialization, dynamic code execution, and environment credential access. +- Added local-first confidentiality, source verification, human accountability, ethics, biosafety, dual-use, data, clinical-scope, and regulatory gates. +- Tools report declarations and cross-links only; they do not score or select hypotheses. + +## Validation results + +- Agent Skills reference validator: **PASS** +- Dependency-free CLI help checks: **PASS** for all 7 public CLIs +- Synthetic standard-library tests: **27 passed** +- Explicit AST parse with bytecode disabled: **8 scripts parsed** +- Bytecode artifacts: **0** +- IDE lints: **0** +- Documented local-path link test: **PASS** +- Authoritative-source link extraction: **36/36 reachable, 0 errors** +- Direct behavioral security scan: **SAFE, 0 findings** +- Pull-request gate with `--fail-on HIGH`: **PASS** + - CRITICAL: 0 + - HIGH: 0 + - LOW: 2 + +## Residual LOW findings + +The LLM-assisted pull-request scan reported: + +1. **Missing `allowed-tools` declaration** — informational. The Agent Skills specification does not require this optional field. The compatibility declaration and body constrain bundled tools to bounded local standard-library processing with no network, models, images, credentials, or environment access. +2. **Missing referenced files** — analyzer false positive. It invented paths under `templates/` and mismatched existing `assets/` and `references/` files. The deterministic local-path test resolved every documented bundled path and passed. No script implements network fallback or substitute retrieval. + +Neither LOW finding permits data transmission, credential access, scientific scoring, or automatic hypothesis selection. No CRITICAL or HIGH issue remains. + +## Reproduction + +```bash +PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover \ + -s tests/hypothesis-generation -p "test_*.py" -v + +uv run skills-ref validate skills/hypothesis-generation + +uv run skill-scanner scan skills/hypothesis-generation --use-behavioral + +uv run python scan_pr_skills.py \ + --fail-on HIGH \ + --output /tmp/hypothesis-generation-pr-scan.md \ + skills/hypothesis-generation +``` + +The repository-level `SECURITY.md` is intentionally not edited in this scoped refresh; its generated snapshot updates through the repository’s normal scan process. diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/source_ledger.md b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/source_ledger.md new file mode 100644 index 0000000..2a4baa9 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/source_ledger.md @@ -0,0 +1,116 @@ +# Dated Source Ledger + +Verification and research cutoff: **2026-07-23**. + +Machine-readable ledger: `assets/source_ledger.csv`. + +## Search boundary + +Research used `parallel-cli` search against official or primary domains with focused queries for: + +- NIH rigor, reproducibility, and the 2026 replication initiative; +- Cochrane PICO and the original well-built clinical-question article; +- FINER’s attributed historical source and current interpretation; +- Platt’s original strong-inference essay; +- COS/OSF preregistration and Registered Reports; +- SPIRIT 2025 and CONSORT 2025; +- causal questions, counterfactuals, estimands, and bias; +- negative controls, HARKing, multiplicity, reproducibility, and replication; +- NIST/UNESCO responsible AI and primary evidence on output homogenization; +- human, animal, biosafety, biosecurity, dual-use, data, and regulatory gates. + +Searches prioritized official domains and primary publications. Full-text extraction or search excerpts were used to verify titles, dates, versions, and relevant passages. This was a targeted skill refresh, not a systematic review, patent search, or proof of scientific novelty. + +## Core methodology sources + +### Question formulation + +- `SRC-COCHRANE-PICO` — current Cochrane Handbook Chapter 2. PICO is used for intervention-effect review questions, with objectives defined in advance and stakeholder input where appropriate. +- `SRC-PICO-ORIGINAL` — Richardson et al., 1995, *The well-built clinical question*. Foundational four-part clinical-question article. +- `SRC-FINER-1988` — Hulley and Cummings, *Designing Clinical Research*, first edition metadata (1988). It is the earliest FINER-attributed source located in this refresh. +- `SRC-FINER-CURRENT` — Werner and Willis, 2023, current FINER interpretation. + +**Historical limitation:** the available targeted search confirmed the 1988 book’s bibliographic metadata and later attribution but did not establish the exact first printed use or coinage of the FINER mnemonic. The skill therefore does not claim that provenance as proven. + +### Multiple hypotheses and falsification + +- `SRC-PLATT-1964` — John R. Platt, “Strong Inference,” *Science* 146:347–353, DOI `10.1126/science.146.3642.347`. +- `SRC-NEG-CONTROL` — Lipsitch, Tchetgen Tchetgen, and Cohen, 2010, negative controls for confounding and bias, DOI `10.1097/EDE.0b013e3181d61eeb`. +- `SRC-HARKING` — Kerr, 1998, HARKing, DOI `10.1207/s15327957pspr0203_4`. +- `SRC-ASA-PVALUE` — ASA statement: thresholds alone do not support scientific conclusions; p-values do not measure hypothesis truth or effect importance; full reporting is required. + +### Rigor, reproducibility, and replication + +- `SRC-NIH-RIGOR` — NIH guidance on scientific premise, rigorous design, relevant biological variables, authentication, and transparency. +- `SRC-NIH-REPLICATION` — NIH’s agency-wide replication and reproducibility initiative, page reviewed June 22, 2026. +- `SRC-NASEM-RR` — National Academies 2019 consensus report defining computational reproducibility and replicability with new data. +- `SRC-TOP` — Transparency and Openness Promotion guidelines. +- `SRC-NIH-DMS` — NIH Data Management and Sharing Policy. + +Open practices remain subject to consent, privacy, community governance, intellectual property, export control, and security restrictions. + +## Preregistration and intervention trials + +- `SRC-COS-PREREG` — preregistration separates planned from unplanned work; transparent exploration remains valuable. +- `SRC-OSF-REG` — current OSF registration/preregistration implementation guidance. +- `SRC-COS-RR` — Registered Reports and results-blind protocol review. +- `SRC-SPIRIT-2025` — current 34-item randomized-trial protocol guideline; supersedes SPIRIT 2013. +- `SRC-CONSORT-2025` — current 30-item randomized-trial result-reporting guideline, including open science, harms, outcomes, intervention details, and important changes. + +SPIRIT and CONSORT are reporting guidelines, not design-quality, ethics, regulatory, or efficacy certifications. + +## Causal inference and estimands + +- `SRC-WHATIF` — Hernán and Robins, *Causal Inference: What If*. The author page linked the latest revision found during verification. +- `SRC-ICH-E9R1` — ICH E9(R1), defining the estimand as the precise treatment-effect target and aligning planning, design, analysis, sensitivity analysis, and interpretation. + +These sources ground the distinctions among target causal contrast, estimator, and estimate, and the explicit treatment of confounding, selection, collider, measurement, and intervention-definition assumptions. + +## Responsible AI + +- `SRC-NIST-GENAI` — NIST AI 600-1, covering confabulation, privacy, harmful bias/homogenization, information integrity, dangerous recommendations, and human–AI configuration. +- `SRC-UNESCO-AI` — human rights, privacy, accountability, transparency, diversity, and human oversight. +- `SRC-DOSHI-HAUSER` — Doshi and Hauser, 2024, DOI `10.1126/sciadv.adn5290`. In the studied story-writing task, AI-assisted outputs were more similar to one another while some individual creativity measures improved. + +The primary homogenization result is task-specific. The skill treats idea homogenization as a plausible risk, not a universal measured effect across scientific domains. + +## Ethics and oversight + +### Humans + +- `SRC-HHS-COMMON-RULE` — U.S. Common Rule/45 CFR 46 portal. +- `SRC-BELMONT` — respect for persons, beneficence, and justice. +- `SRC-HELSINKI` — World Medical Association Declaration of Helsinki, revised October 2024. + +An authorized IRB/REC or equivalent must determine applicability; the skill does not self-declare exemption. + +### Animals + +- `SRC-OLAW-PHS` — PHS Policy and IACUC/Assurance requirements for covered work. +- `SRC-ARRIVE` — ARRIVE 2.0 reporting guidance. + +### Biosafety and dual use + +- `SRC-NIH-RSNA` — NIH Guidelines for research involving recombinant or synthetic nucleic acid molecules. +- `SRC-NIH-BIOSEC` — current NIH biosafety/biosecurity portal. +- `SRC-BMBL` — CDC/NIH BMBL sixth edition. +- `SRC-WHO-LIFE` — WHO Global Guidance Framework for the Responsible Use of the Life Sciences. + +### U.S. dual-use transition status + +At the verification date: + +- `SRC-EO-14292` directed revision/replacement of the 2024 DURC/PEPP policy and pause/termination actions for covered dangerous gain-of-function research. +- `SRC-NIH-NOT-25-112` stated the Executive Order superseded NIH’s 2024 implementation and rescinded NOT-OD-25-061. +- `SRC-ASPR-DURC` still described the 2024 policy as awaiting revision or replacement and promised an update when the revised policy became available. + +This status is time-sensitive. Recheck current federal, funder, award, institutional, and jurisdiction-specific rules before any related work. Do not use this ledger as clearance. + +## Source-use rules + +1. Verify each citation and identifier against the live primary source before publication or registration. +2. Recheck time-sensitive policy after the cutoff date. +3. Link every scientific claim to evidence in `assets/evidence_ledger_template.csv`. +4. Include challenging, null, and limitation evidence, not only support. +5. Do not infer novelty from this source ledger; it documents the skill refresh, not a user’s research topic. +6. Do not expose a sensitive research question in an external search query without authorization. diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/tool_reference.md b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/tool_reference.md new file mode 100644 index 0000000..28cba62 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/references/tool_reference.md @@ -0,0 +1,246 @@ +# Local Tool Reference + +## Runtime and safety model + +All bundled CLIs: + +- require Python 3.11+ standard library only; +- read explicit local JSON, CSV, or Markdown paths; +- reject URL-like paths, symlinks, wrong suffixes, oversized inputs, invalid UTF-8, and NUL bytes; +- cap inputs at 2 MiB, CSV data at 1,000 rows, cells/Markdown lines at 8,000 characters, and JSON collections at bounded sizes; +- reject duplicate JSON keys and require exact ordered CSV headers; +- make no network, model, image, subprocess, credential, or environment-variable calls; +- write only to an explicit existing local directory; +- refuse implicit overwrite unless `--force` is given; +- use private mode (`0600`) and atomic replacement for generated output. + +Reports contain identifiers, counts, rule codes, and line numbers rather than copying scientific prose where practical. + +## Exit codes + +- `0`: input was structurally valid; warnings or human-review gaps may remain. +- `1`: input was parsed, but consistency or required-control errors were found. +- `2`: malformed, unsafe, missing, oversized, or wrong-type input. + +No exit code means a hypothesis is true, novel, ethical, safe, feasible, supported, or selected. + +## 1. Hypothesis schema validator + +Asset: `assets/hypothesis_record_template.json` + +```bash +python3 scripts/validate_hypothesis_schema.py local-record.json +python3 scripts/validate_hypothesis_schema.py \ + local-record.json -o local-validation.json +``` + +The exact top-level objects are: + +- observation; +- research question; +- hypotheses; +- causal estimands; +- predictions; +- alternative explanations; +- null hypotheses; +- negative controls; +- operationalizations; +- analysis plan; +- evidence/search link; +- causal-bias risk register; +- ethics/feasibility gates; +- AI-use record. + +All hypothesis statuses must be `candidate`. Causal questions require at least one estimand. Cross-links among candidate, prediction, rival, measurement, analysis, control, and source IDs are checked. + +The validator does not read the evidence ledger, test measurements, appraise sources, or interpret results. + +## 2. Operationalization and measurement checklist + +Asset: `assets/operationalization_template.json` + +```bash +python3 scripts/check_operationalization.py local-operationalization.json +``` + +Each measurement item records: + +- construct, operational definition, population/system, unit/categories, timing, and method completion; +- variable role; +- validity source IDs and applicability review; +- reliability/repeatability; +- calibration/quality control; +- invariance/comparability; +- masking; +- missingness; +- threshold/cut-point status; +- limitations and human review. + +`planned`, `unresolved`, and `pending` states are reported as gaps, not converted to scores. A checklist cannot establish measurement validity. + +## 3. Prediction/rival matrix validator + +Asset: `assets/prediction_rival_matrix_template.csv` + +Exact header: + +```text +prediction_id,hypothesis_id,rival_hypothesis_ids,conditions,observable,expected_if_focal,expected_if_rivals,falsifier,indeterminate_result,boundary_conditions,measurement_ids,negative_control_ids,analysis_ids,uncertainty +``` + +Use semicolons inside ID-list cells: + +```bash +python3 scripts/validate_prediction_matrix.py local-matrix.csv +python3 scripts/validate_prediction_matrix.py \ + local-matrix.csv --record local-record.json +``` + +Checks include: + +- unique prediction IDs; +- focal candidate not listed as its own rival; +- declared focal and rival expectations are not lexically identical; +- required falsifier, indeterminate result, boundary, measurement, control, analysis, and uncertainty fields; +- optional cross-links to the hypothesis record. + +The validator cannot determine whether two predictions are scientifically distinguishable. + +## 4. Causal-versus-associational Markdown lint + +```bash +python3 scripts/lint_causal_claims.py local-draft.md +``` + +Use one claim per annotated line: + +```markdown +[claim:associational] Exposure X was associated with outcome Y in the observed sample. + +[claim:causal][estimand:E1][identification:observational_assumption_dependent][confounding:unresolved][selection:assessed][collider:assessed][reverse-causation:assessed] Under the stated assumptions, intervention X would reduce outcome Y. +``` + +Claim types: + +- `causal` +- `associational` +- `descriptive` +- `predictive` +- `mechanistic` + +Identification values: + +- `randomized` +- `quasi_experimental` +- `observational_assumption_dependent` +- `mechanistic_experiment` +- `other_assumption_dependent` + +Risk values: + +- `assessed` +- `unresolved` +- `not_applicable` + +The linter flags a bounded causal lexicon and annotation consistency. It is not a semantic classifier and will have false positives and false negatives. + +## 5. Falsification and negative-control checklist + +Asset: `assets/falsification_controls_template.json` + +```bash +python3 scripts/check_falsification_controls.py local-controls.json +python3 scripts/check_falsification_controls.py \ + local-controls.json --record local-record.json +``` + +For each candidate it requires: + +- assumptions and boundary conditions; +- a prediction-linked falsifier and assumption-failure checks; +- at least one discriminating test with focal, rival, and indeterminate outcomes; +- a linked null and interpretation limit; +- controls including at least one negative-control type; +- outcome paths for consistency, challenge, and neither/mixed; +- human-review status. + +The checker does not validate that a negative control is biologically or causally appropriate. + +## 6. Evidence ledger and search-boundary audit + +Assets: + +- `assets/evidence_ledger_template.csv` +- `assets/search_boundary_template.json` + +Exact evidence-ledger header: + +```text +source_id,claim_ids,title,authors_or_organization,publication_date,source_type,identifier,url,accessed_on,relation,study_design_or_document_type,limitations,notes +``` + +```bash +python3 scripts/audit_evidence_ledger.py \ + local-evidence.csv local-search-boundary.json + +python3 scripts/audit_evidence_ledger.py \ + local-evidence.csv local-search-boundary.json \ + --record local-record.json +``` + +The audit checks: + +- exact schema and date/HTTPS/identifier formats; +- unique source IDs; +- source-to-claim links; +- source-type and relation declarations; +- dated search boundary, queries, limits, stop rule, and novelty status; +- optional source, claim, and boundary links to a hypothesis record. + +It deliberately performs no network access. It cannot verify that a URL exists, a source says what is claimed, evidence is complete, or an idea is novel. + +Allowed novelty states: + +- `not_assessed` +- `requires_specialist_review` +- `supported_by_documented_comprehensive_search` + +The final state still requires a qualified human and supports only a bounded statement. + +## 7. Preregistration scaffold generator + +Asset: `assets/preregistration_scaffold_template.md` + +```bash +python3 scripts/generate_preregistration_scaffold.py \ + local-record.json -o local-preregistration.md +``` + +The generator: + +- first runs record validation; +- refuses unresolved ethics/safety/feasibility gates; +- renders all candidates and rivals without ranking; +- escapes inserted text for inert Markdown; +- marks the output as an unregistered draft; +- leaves repository-, design-, oversight-, and sign-off fields for humans. + +It never uploads, registers, timestamps externally, or submits the result. + +## Suggested local sequence + +```bash +python3 scripts/validate_hypothesis_schema.py local-record.json +python3 scripts/check_operationalization.py local-operationalization.json +python3 scripts/validate_prediction_matrix.py \ + local-predictions.csv --record local-record.json +python3 scripts/check_falsification_controls.py \ + local-controls.json --record local-record.json +python3 scripts/audit_evidence_ledger.py \ + local-evidence.csv local-search-boundary.json --record local-record.json +python3 scripts/lint_causal_claims.py local-draft.md +python3 scripts/generate_preregistration_scaffold.py \ + local-record.json -o local-preregistration.md +``` + +Qualified human review remains mandatory after every command. diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/scripts/_common.py b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/scripts/_common.py new file mode 100644 index 0000000..cf68f33 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/scripts/_common.py @@ -0,0 +1,412 @@ +#!/usr/bin/env python3 +"""Shared, dependency-free safety helpers for local hypothesis CLIs.""" + +from __future__ import annotations + +import csv +import json +import os +import re +import sys +import tempfile +from datetime import date +from pathlib import Path +from typing import Any, Iterable + +MAX_INPUT_BYTES = 2 * 1024 * 1024 +MAX_ROWS = 1_000 +MAX_CELL_CHARS = 8_000 +MAX_TEXT_CHARS = 20_000 +MAX_LIST_ITEMS = 500 + +IDENTIFIER_RE = re.compile(r"^[A-Za-z][A-Za-z0-9._:-]{0,95}$") +URL_SCHEME_RE = re.compile(r"^[A-Za-z][A-Za-z0-9+.-]*://") +HTTPS_URL_RE = re.compile(r"^https://[^\s]+$", re.IGNORECASE) +PARTIAL_DATE_RE = re.compile(r"^\d{4}(?:-\d{2}(?:-\d{2})?)?$") + + +class ValidationError(ValueError): + """A deterministic, user-correctable validation failure.""" + + +def _duplicate_safe_object(pairs: list[tuple[str, Any]]) -> dict[str, Any]: + result: dict[str, Any] = {} + for key, value in pairs: + if key in result: + raise ValidationError(f"JSON object contains duplicate key: {key}") + result[key] = value + return result + + +def _reject_url_like_path(raw_path: str | Path, context: str) -> None: + if URL_SCHEME_RE.match(str(raw_path).strip()): + raise ValidationError(f"{context} must be a local file path") + + +def safe_input_path(raw_path: str | Path, suffixes: Iterable[str]) -> Path: + """Resolve a bounded regular local file and reject symlink inputs.""" + _reject_url_like_path(raw_path, "input") + path = Path(raw_path).expanduser() + if path.is_symlink(): + raise ValidationError(f"symlink inputs are not allowed: {path}") + try: + resolved = path.resolve(strict=True) + except FileNotFoundError as exc: + raise ValidationError(f"input file does not exist: {path}") from exc + if not resolved.is_file(): + raise ValidationError(f"input path is not a regular file: {resolved}") + allowed = {suffix.lower() for suffix in suffixes} + if resolved.suffix.lower() not in allowed: + choices = ", ".join(sorted(allowed)) + raise ValidationError(f"expected one of [{choices}]: {resolved}") + size = resolved.stat().st_size + if size > MAX_INPUT_BYTES: + raise ValidationError( + f"input exceeds {MAX_INPUT_BYTES} bytes: {resolved} ({size} bytes)" + ) + return resolved + + +def safe_output_path( + raw_path: str | Path, suffix: str, *, force: bool = False +) -> Path: + """Resolve an output in an existing directory without implicit overwrite.""" + _reject_url_like_path(raw_path, "output") + path = Path(raw_path).expanduser() + if path.suffix.lower() != suffix.lower(): + raise ValidationError(f"output must use {suffix}: {path}") + try: + parent = path.parent.resolve(strict=True) + except FileNotFoundError as exc: + raise ValidationError(f"output parent does not exist: {path.parent}") from exc + if not parent.is_dir(): + raise ValidationError(f"output parent is not a directory: {parent}") + resolved = parent / path.name + if resolved.is_symlink(): + raise ValidationError(f"symlink outputs are not allowed: {resolved}") + if resolved.exists(): + if not resolved.is_file(): + raise ValidationError(f"output is not a regular file: {resolved}") + if not force: + raise ValidationError( + f"output already exists; pass --force to replace it: {resolved}" + ) + return resolved + + +def read_json(raw_path: str | Path) -> Any: + """Read strict UTF-8 JSON with duplicate-key detection.""" + path = safe_input_path(raw_path, {".json"}) + try: + text = path.read_text(encoding="utf-8") + except UnicodeDecodeError as exc: + raise ValidationError(f"JSON must be UTF-8: {path}") from exc + if "\x00" in text: + raise ValidationError(f"JSON contains a NUL byte: {path}") + try: + return json.loads(text, object_pairs_hook=_duplicate_safe_object) + except json.JSONDecodeError as exc: + raise ValidationError( + f"invalid JSON at line {exc.lineno}, column {exc.colno}: {exc.msg}" + ) from exc + + +def read_markdown(raw_path: str | Path) -> str: + """Read one bounded UTF-8 Markdown file.""" + path = safe_input_path(raw_path, {".md", ".markdown"}) + try: + text = path.read_text(encoding="utf-8") + except UnicodeDecodeError as exc: + raise ValidationError(f"Markdown must be UTF-8: {path}") from exc + if "\x00" in text: + raise ValidationError(f"Markdown contains a NUL byte: {path}") + if any(len(line) > MAX_CELL_CHARS for line in text.splitlines()): + raise ValidationError( + f"Markdown contains a line longer than {MAX_CELL_CHARS} characters" + ) + return text + + +def read_csv_records( + raw_path: str | Path, + *, + fields: Iterable[str], + max_rows: int = MAX_ROWS, +) -> list[dict[str, str]]: + """Read strict UTF-8 CSV with an exact ordered header and bounded cells.""" + path = safe_input_path(raw_path, {".csv"}) + expected = tuple(fields) + try: + with path.open("r", encoding="utf-8-sig", newline="") as handle: + reader = csv.DictReader(handle) + headers = reader.fieldnames + if not headers: + raise ValidationError(f"CSV has no header: {path}") + normalized = tuple(header.strip() for header in headers) + if any(not header for header in normalized): + raise ValidationError("CSV headers must not be blank") + if len(normalized) != len(set(normalized)): + raise ValidationError("CSV headers must be unique") + if normalized != expected: + raise ValidationError( + "CSV header must exactly match: " + ",".join(expected) + ) + reader.fieldnames = list(normalized) + records: list[dict[str, str]] = [] + for line_number, row in enumerate(reader, start=2): + if line_number - 1 > max_rows: + raise ValidationError(f"CSV exceeds {max_rows} data rows") + if None in row: + raise ValidationError( + f"row {line_number} has more cells than the header" + ) + cleaned: dict[str, str] = {} + for key, value in row.items(): + cell = "" if value is None else value.strip() + if "\x00" in cell: + raise ValidationError( + f"row {line_number}, column {key} contains a NUL byte" + ) + if len(cell) > MAX_CELL_CHARS: + raise ValidationError( + f"row {line_number}, column {key} exceeds " + f"{MAX_CELL_CHARS} characters" + ) + cleaned[key] = cell + if any(cleaned.values()): + records.append(cleaned) + except UnicodeDecodeError as exc: + raise ValidationError(f"CSV must be UTF-8: {path}") from exc + except csv.Error as exc: + raise ValidationError(f"invalid CSV: {exc}") from exc + if not records: + raise ValidationError("CSV must contain at least one data row") + return records + + +def require_object(value: Any, context: str) -> dict[str, Any]: + if not isinstance(value, dict): + raise ValidationError(f"{context} must be a JSON object") + return value + + +def require_exact_keys( + value: dict[str, Any], + *, + required: Iterable[str], + optional: Iterable[str] = (), + context: str, +) -> None: + required_set = set(required) + allowed = required_set | set(optional) + missing = sorted(required_set - set(value)) + unknown = sorted(set(value) - allowed) + if missing: + raise ValidationError(f"{context} is missing fields: {', '.join(missing)}") + if unknown: + raise ValidationError(f"{context} has unknown fields: {', '.join(unknown)}") + + +def require_list( + value: Any, + context: str, + *, + minimum: int = 0, + maximum: int = MAX_LIST_ITEMS, +) -> list[Any]: + if not isinstance(value, list): + raise ValidationError(f"{context} must be a JSON array") + if not minimum <= len(value) <= maximum: + raise ValidationError( + f"{context} must contain between {minimum} and {maximum} items" + ) + return value + + +def require_text( + value: Any, + context: str, + *, + allow_empty: bool = False, + minimum: int = 1, + maximum: int = MAX_TEXT_CHARS, +) -> str: + if not isinstance(value, str): + raise ValidationError(f"{context} must be a string") + text = value.strip() + if not allow_empty and not text: + raise ValidationError(f"{context} must not be empty") + if text and len(text) < minimum: + raise ValidationError(f"{context} must contain at least {minimum} characters") + if len(text) > maximum: + raise ValidationError(f"{context} exceeds {maximum} characters") + if "\x00" in text: + raise ValidationError(f"{context} contains a NUL byte") + return text + + +def require_bool(value: Any, context: str) -> bool: + if type(value) is not bool: + raise ValidationError(f"{context} must be true or false") + return value + + +def require_enum(value: Any, choices: Iterable[str], context: str) -> str: + text = require_text(value, context, maximum=96) + allowed = set(choices) + if text not in allowed: + raise ValidationError( + f"{context} must be one of: {', '.join(sorted(allowed))}" + ) + return text + + +def require_identifier(value: Any, context: str) -> str: + identifier = require_text(value, context, maximum=96) + if not IDENTIFIER_RE.fullmatch(identifier): + raise ValidationError(f"{context} has an invalid identifier format") + return identifier + + +def require_unique(values: Iterable[str], context: str) -> None: + seen: set[str] = set() + duplicates: set[str] = set() + for value in values: + if value in seen: + duplicates.add(value) + seen.add(value) + if duplicates: + raise ValidationError( + f"{context} contains duplicate IDs: {', '.join(sorted(duplicates))}" + ) + + +def require_text_list( + value: Any, + context: str, + *, + minimum: int = 0, + maximum: int = 100, + item_maximum: int = 1_000, +) -> list[str]: + items = require_list(value, context, minimum=minimum, maximum=maximum) + return [ + require_text(item, f"{context}[{index}]", maximum=item_maximum) + for index, item in enumerate(items) + ] + + +def require_identifier_list( + value: Any, + context: str, + *, + minimum: int = 0, + maximum: int = 100, +) -> list[str]: + items = require_list(value, context, minimum=minimum, maximum=maximum) + parsed = [ + require_identifier(item, f"{context}[{index}]") + for index, item in enumerate(items) + ] + require_unique(parsed, context) + return parsed + + +def split_identifiers( + value: Any, context: str, *, allow_empty: bool = False +) -> list[str]: + text = require_text(value, context, allow_empty=allow_empty, maximum=MAX_CELL_CHARS) + if not text: + return [] + parts = [part.strip() for part in text.split(";")] + if any(not part for part in parts): + raise ValidationError(f"{context} contains an empty identifier") + parsed = [require_identifier(part, context) for part in parts] + require_unique(parsed, context) + return parsed + + +def require_iso_date(value: Any, context: str) -> str: + text = require_text(value, context, maximum=10) + try: + date.fromisoformat(text) + except ValueError as exc: + raise ValidationError(f"{context} must be an ISO date (YYYY-MM-DD)") from exc + return text + + +def require_partial_date(value: Any, context: str) -> str: + text = require_text(value, context, maximum=10) + if not PARTIAL_DATE_RE.fullmatch(text): + raise ValidationError(f"{context} must be YYYY, YYYY-MM, or YYYY-MM-DD") + try: + if len(text) == 4: + date(int(text), 1, 1) + elif len(text) == 7: + date.fromisoformat(f"{text}-01") + else: + date.fromisoformat(text) + except ValueError as exc: + raise ValidationError(f"{context} contains an invalid date") from exc + return text + + +def require_https_url(value: Any, context: str) -> str: + text = require_text(value, context, maximum=2_000) + if not HTTPS_URL_RE.fullmatch(text): + raise ValidationError(f"{context} must be an https URL") + return text + + +def atomic_write_text( + destination: Path, text: str, *, mode: int = 0o600 +) -> Path: + """Atomically write private local text to a validated destination.""" + temporary_name: str | None = None + try: + with tempfile.NamedTemporaryFile( + "w", + encoding="utf-8", + dir=destination.parent, + prefix=f".{destination.name}.", + suffix=".tmp", + delete=False, + ) as handle: + handle.write(text) + temporary_name = handle.name + os.chmod(temporary_name, mode) + os.replace(temporary_name, destination) + os.chmod(destination, mode) + finally: + if temporary_name and Path(temporary_name).exists(): + Path(temporary_name).unlink() + return destination + + +def write_json_report( + data: Any, output: str | Path | None, *, force: bool = False +) -> None: + """Print JSON or atomically write it to an explicitly selected local file.""" + serialized = json.dumps(data, indent=2, sort_keys=True, ensure_ascii=False) + "\n" + if output is None: + print(serialized, end="") + return + destination = safe_output_path(output, ".json", force=force) + atomic_write_text(destination, serialized) + + +def write_markdown( + text: str, output: str | Path, *, force: bool = False +) -> Path: + destination = safe_output_path(output, ".md", force=force) + return atomic_write_text(destination, text) + + +def issue(code: str, field: str) -> dict[str, str]: + """Create a content-free finding safe for reports.""" + return {"code": code, "field": field} + + +def error_exit(exc: ValidationError) -> int: + print(f"ERROR: {exc}", file=sys.stderr) + return 2 diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/scripts/audit_evidence_ledger.py b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/scripts/audit_evidence_ledger.py new file mode 100644 index 0000000..5f67334 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/scripts/audit_evidence_ledger.py @@ -0,0 +1,337 @@ +#!/usr/bin/env python3 +"""Audit a local evidence ledger and dated search boundary without networking.""" + +from __future__ import annotations + +import argparse +from collections import Counter +from datetime import date +from typing import Any + +from _common import ( + ValidationError, + error_exit, + issue, + read_csv_records, + read_json, + require_enum, + require_exact_keys, + require_https_url, + require_identifier, + require_iso_date, + require_object, + require_partial_date, + require_text, + require_text_list, + require_unique, + split_identifiers, + write_json_report, +) +from validate_hypothesis_schema import load_hypothesis_record, validate_record + +FIELDS = ( + "source_id", + "claim_ids", + "title", + "authors_or_organization", + "publication_date", + "source_type", + "identifier", + "url", + "accessed_on", + "relation", + "study_design_or_document_type", + "limitations", + "notes", +) +SOURCE_TYPES = { + "official_guidance", + "regulation_policy", + "reporting_guideline", + "primary_research", + "primary_method", + "systematic_review", + "consensus_report", + "book", + "dataset_or_registry", + "preprint", + "other", +} +RELATIONS = {"supportive", "challenging", "contextual", "method", "safety", "mixed"} +NOVELTY_STATES = { + "not_assessed", + "requires_specialist_review", + "supported_by_documented_comprehensive_search", +} + + +def load_ledger(raw_path: str) -> list[dict[str, Any]]: + rows = read_csv_records(raw_path, fields=FIELDS) + parsed: list[dict[str, Any]] = [] + source_ids: list[str] = [] + for line_number, row in enumerate(rows, start=2): + context = f"ledger row {line_number}" + source_id = require_identifier(row["source_id"], f"{context}.source_id") + source_ids.append(source_id) + parsed.append( + { + "source_id": source_id, + "claim_ids": split_identifiers( + row["claim_ids"], f"{context}.claim_ids" + ), + "title": require_text( + row["title"], f"{context}.title", minimum=5, maximum=2_000 + ), + "authors_or_organization": require_text( + row["authors_or_organization"], + f"{context}.authors_or_organization", + minimum=2, + maximum=1_000, + ), + "publication_date": require_partial_date( + row["publication_date"], f"{context}.publication_date" + ), + "source_type": require_enum( + row["source_type"], SOURCE_TYPES, f"{context}.source_type" + ), + "identifier": require_text( + row["identifier"], + f"{context}.identifier", + minimum=3, + maximum=1_000, + ), + "url": require_https_url(row["url"], f"{context}.url"), + "accessed_on": require_iso_date( + row["accessed_on"], f"{context}.accessed_on" + ), + "relation": require_enum( + row["relation"], RELATIONS, f"{context}.relation" + ), + "study_design_or_document_type": require_text( + row["study_design_or_document_type"], + f"{context}.study_design_or_document_type", + minimum=3, + maximum=1_000, + ), + "limitations": require_text( + row["limitations"], + f"{context}.limitations", + minimum=3, + maximum=2_000, + ), + "notes": require_text( + row["notes"], + f"{context}.notes", + allow_empty=True, + maximum=2_000, + ), + } + ) + require_unique(source_ids, "evidence ledger") + return parsed + + +def load_search_boundary(payload: Any) -> dict[str, Any]: + root = require_object(payload, "search_boundary") + fields = { + "schema_version", + "search_boundary_id", + "searched_on", + "searched_by", + "purpose", + "databases_or_indexes", + "queries", + "date_limits", + "language_limits", + "inclusion_scope", + "exclusion_scope", + "known_limitations", + "last_result_screened_or_stop_rule", + "novelty_status", + } + require_exact_keys(root, required=fields, context="search_boundary") + return { + "schema_version": require_enum( + root["schema_version"], {"2.0"}, "search_boundary.schema_version" + ), + "search_boundary_id": require_identifier( + root["search_boundary_id"], "search_boundary.search_boundary_id" + ), + "searched_on": require_iso_date( + root["searched_on"], "search_boundary.searched_on" + ), + "searched_by": require_text( + root["searched_by"], "search_boundary.searched_by", minimum=3 + ), + "purpose": require_text( + root["purpose"], "search_boundary.purpose", minimum=10 + ), + "databases_or_indexes": require_text_list( + root["databases_or_indexes"], + "search_boundary.databases_or_indexes", + minimum=1, + maximum=100, + ), + "queries": require_text_list( + root["queries"], "search_boundary.queries", minimum=1, maximum=200 + ), + "date_limits": require_text( + root["date_limits"], "search_boundary.date_limits", minimum=3 + ), + "language_limits": require_text( + root["language_limits"], "search_boundary.language_limits", minimum=3 + ), + "inclusion_scope": require_text( + root["inclusion_scope"], "search_boundary.inclusion_scope", minimum=10 + ), + "exclusion_scope": require_text( + root["exclusion_scope"], "search_boundary.exclusion_scope", minimum=10 + ), + "known_limitations": require_text_list( + root["known_limitations"], + "search_boundary.known_limitations", + minimum=1, + maximum=100, + ), + "last_result_screened_or_stop_rule": require_text( + root["last_result_screened_or_stop_rule"], + "search_boundary.last_result_screened_or_stop_rule", + minimum=10, + ), + "novelty_status": require_enum( + root["novelty_status"], + NOVELTY_STATES, + "search_boundary.novelty_status", + ), + } + + +def _record_claim_ids(record: dict[str, Any]) -> set[str]: + identifiers = {"OBS1", record["project_id"]} + identifiers.update(item["hypothesis_id"] for item in record["hypotheses"]) + identifiers.update(item["estimand_id"] for item in record["causal_estimands"]) + identifiers.update(item["prediction_id"] for item in record["predictions"]) + identifiers.update( + item["alternative_id"] for item in record["alternative_explanations"] + ) + identifiers.update(item["null_id"] for item in record["null_hypotheses"]) + identifiers.update(item["control_id"] for item in record["negative_controls"]) + identifiers.update( + item["measurement_id"] for item in record["operationalizations"] + ) + identifiers.update( + item["analysis_id"] for item in record["analysis_plan"]["analyses"] + ) + return identifiers + + +def audit( + ledger: list[dict[str, Any]], + boundary: dict[str, Any], + record: dict[str, Any] | None = None, +) -> dict[str, Any]: + errors: list[dict[str, str]] = [] + warnings: list[dict[str, str]] = [] + source_type_counts: Counter[str] = Counter() + relation_counts: Counter[str] = Counter() + ledger_source_ids = {row["source_id"] for row in ledger} + linked_claim_ids: set[str] = set() + + searched_on = date.fromisoformat(boundary["searched_on"]) + for row in ledger: + source_type_counts[row["source_type"]] += 1 + relation_counts[row["relation"]] += 1 + linked_claim_ids.update(row["claim_ids"]) + if date.fromisoformat(row["accessed_on"]) > searched_on: + warnings.append( + issue("SOURCE_ACCESSED_AFTER_SEARCH_BOUNDARY", row["source_id"]) + ) + + if not relation_counts["challenging"]: + warnings.append(issue("NO_CHALLENGING_SOURCE_DECLARED", "ledger")) + if boundary["novelty_status"] == "supported_by_documented_comprehensive_search": + warnings.append( + issue("NOVELTY_STATUS_REQUIRES_QUALIFIED_HUMAN_REVIEW", "search_boundary") + ) + + record_cross_check = record is not None + if record is not None: + record_report = validate_record(record) + if not record_report["valid"]: + raise ValidationError( + "optional hypothesis record must pass schema validation first" + ) + if record["evidence"]["search_boundary_id"] != boundary["search_boundary_id"]: + errors.append( + issue("SEARCH_BOUNDARY_ID_MISMATCH", boundary["search_boundary_id"]) + ) + record_source_ids = set(record["evidence"]["source_ids"]) + for source_id in sorted(record_source_ids - ledger_source_ids): + errors.append(issue("RECORD_SOURCE_MISSING_FROM_LEDGER", source_id)) + for source_id in sorted(ledger_source_ids - record_source_ids): + warnings.append(issue("LEDGER_SOURCE_NOT_DECLARED_IN_RECORD", source_id)) + allowed_claim_ids = _record_claim_ids(record) + for claim_id in sorted(linked_claim_ids - allowed_claim_ids): + errors.append(issue("UNKNOWN_CLAIM_ID", claim_id)) + + return { + "schema_version": "2.0", + "search_boundary_id": boundary["search_boundary_id"], + "searched_on": boundary["searched_on"], + "novelty_status": boundary["novelty_status"], + "valid": not errors, + "status": "INVALID_LEDGER" if errors else "VALID_FOR_HUMAN_SOURCE_REVIEW", + "errors": errors, + "warnings": warnings, + "source_count": len(ledger), + "source_type_counts": dict(sorted(source_type_counts.items())), + "relation_counts": dict(sorted(relation_counts.items())), + "source_ids": sorted(ledger_source_ids), + "linked_claim_ids": sorted(linked_claim_ids), + "record_cross_check_performed": record_cross_check, + "notice": ( + "This local audit does not visit URLs, verify source existence or " + "content, appraise evidence, establish novelty, or determine whether " + "a source supports a claim. Verify every source and link manually." + ), + } + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description=( + "Audit a bounded local evidence CSV and search-boundary JSON without " + "network access or scientific scoring." + ) + ) + parser.add_argument("ledger", help="Local evidence ledger CSV") + parser.add_argument("search_boundary", help="Local search-boundary JSON") + parser.add_argument( + "--record", help="Optional local hypothesis record JSON for cross-checks" + ) + parser.add_argument("-o", "--output", help="Optional local JSON report") + parser.add_argument( + "--force", action="store_true", help="Replace an existing output file" + ) + return parser + + +def main() -> int: + args = build_parser().parse_args() + try: + record = ( + load_hypothesis_record(read_json(args.record)) if args.record else None + ) + report = audit( + load_ledger(args.ledger), + load_search_boundary(read_json(args.search_boundary)), + record, + ) + write_json_report(report, args.output, force=args.force) + return 0 if report["valid"] else 1 + except ValidationError as exc: + return error_exit(exc) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/scripts/check_falsification_controls.py b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/scripts/check_falsification_controls.py new file mode 100644 index 0000000..7b3bd8d --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/scripts/check_falsification_controls.py @@ -0,0 +1,455 @@ +#!/usr/bin/env python3 +"""Audit falsifiers, discriminating tests, nulls, and controls without scoring.""" + +from __future__ import annotations + +import argparse +import re +from collections import Counter +from typing import Any + +from _common import ( + ValidationError, + error_exit, + issue, + read_json, + require_enum, + require_exact_keys, + require_identifier, + require_list, + require_object, + require_text, + require_text_list, + require_unique, + write_json_report, +) +from validate_hypothesis_schema import load_hypothesis_record, validate_record + +CONTROL_TYPES = { + "negative_exposure", + "negative_outcome", + "procedural_negative", + "positive_control", + "vehicle_or_sham", + "other", +} +NEGATIVE_TYPES = {"negative_exposure", "negative_outcome", "procedural_negative"} + + +def _parse_falsifier(raw: Any, context: str) -> dict[str, Any]: + value = require_object(raw, context) + require_exact_keys( + value, + required={ + "prediction_id", + "conditions", + "observable", + "incompatible_result", + "assumption_failure_checks", + }, + context=context, + ) + return { + "prediction_id": require_identifier( + value["prediction_id"], f"{context}.prediction_id" + ), + "conditions": require_text( + value["conditions"], f"{context}.conditions", minimum=10 + ), + "observable": require_text( + value["observable"], f"{context}.observable", minimum=5 + ), + "incompatible_result": require_text( + value["incompatible_result"], + f"{context}.incompatible_result", + minimum=10, + ), + "assumption_failure_checks": require_text_list( + value["assumption_failure_checks"], + f"{context}.assumption_failure_checks", + minimum=1, + ), + } + + +def _parse_discriminating_tests(raw: Any, context: str) -> list[dict[str, str]]: + values = require_list(raw, context, minimum=1, maximum=100) + parsed: list[dict[str, str]] = [] + identifiers: list[str] = [] + fields = { + "test_id", + "rival_hypothesis_id", + "focal_expected", + "rival_expected", + "indeterminate_result", + } + for index, raw_value in enumerate(values): + item_context = f"{context}[{index}]" + value = require_object(raw_value, item_context) + require_exact_keys(value, required=fields, context=item_context) + test_id = require_identifier(value["test_id"], f"{item_context}.test_id") + identifiers.append(test_id) + parsed.append( + { + "test_id": test_id, + "rival_hypothesis_id": require_identifier( + value["rival_hypothesis_id"], + f"{item_context}.rival_hypothesis_id", + ), + "focal_expected": require_text( + value["focal_expected"], + f"{item_context}.focal_expected", + minimum=10, + ), + "rival_expected": require_text( + value["rival_expected"], + f"{item_context}.rival_expected", + minimum=10, + ), + "indeterminate_result": require_text( + value["indeterminate_result"], + f"{item_context}.indeterminate_result", + minimum=10, + ), + } + ) + require_unique(identifiers, context) + return parsed + + +def _parse_nulls(raw: Any, context: str) -> list[dict[str, str]]: + values = require_list(raw, context, minimum=1, maximum=100) + parsed: list[dict[str, str]] = [] + identifiers: list[str] = [] + fields = {"null_id", "statement", "analysis_id", "interpretation_limit"} + for index, raw_value in enumerate(values): + item_context = f"{context}[{index}]" + value = require_object(raw_value, item_context) + require_exact_keys(value, required=fields, context=item_context) + null_id = require_identifier(value["null_id"], f"{item_context}.null_id") + identifiers.append(null_id) + parsed.append( + { + "null_id": null_id, + "statement": require_text( + value["statement"], f"{item_context}.statement", minimum=10 + ), + "analysis_id": require_identifier( + value["analysis_id"], f"{item_context}.analysis_id" + ), + "interpretation_limit": require_text( + value["interpretation_limit"], + f"{item_context}.interpretation_limit", + minimum=10, + ), + } + ) + require_unique(identifiers, context) + return parsed + + +def _parse_controls(raw: Any, context: str) -> list[dict[str, str]]: + values = require_list(raw, context, minimum=1, maximum=100) + parsed: list[dict[str, str]] = [] + identifiers: list[str] = [] + fields = { + "control_id", + "control_type", + "rationale", + "expected_result", + "failure_implication", + } + for index, raw_value in enumerate(values): + item_context = f"{context}[{index}]" + value = require_object(raw_value, item_context) + require_exact_keys(value, required=fields, context=item_context) + control_id = require_identifier( + value["control_id"], f"{item_context}.control_id" + ) + identifiers.append(control_id) + parsed.append( + { + "control_id": control_id, + "control_type": require_enum( + value["control_type"], + CONTROL_TYPES, + f"{item_context}.control_type", + ), + "rationale": require_text( + value["rationale"], f"{item_context}.rationale", minimum=10 + ), + "expected_result": require_text( + value["expected_result"], + f"{item_context}.expected_result", + minimum=5, + ), + "failure_implication": require_text( + value["failure_implication"], + f"{item_context}.failure_implication", + minimum=10, + ), + } + ) + require_unique(identifiers, context) + return parsed + + +def _parse_outcome_interpretation(raw: Any, context: str) -> dict[str, str]: + value = require_object(raw, context) + fields = { + "consistent_with_candidate", + "challenges_candidate", + "supports_neither_or_mixed", + } + require_exact_keys(value, required=fields, context=context) + return { + field: require_text(value[field], f"{context}.{field}", minimum=10) + for field in fields + } + + +def load_checklist(payload: Any) -> dict[str, Any]: + root = require_object(payload, "checklist") + require_exact_keys( + root, + required={"schema_version", "checklist_id", "record_id", "hypotheses"}, + context="checklist", + ) + raw_hypotheses = require_list( + root["hypotheses"], "checklist.hypotheses", minimum=1, maximum=50 + ) + parsed_hypotheses: list[dict[str, Any]] = [] + hypothesis_ids: list[str] = [] + fields = { + "hypothesis_id", + "candidate_status", + "assumptions", + "boundary_conditions", + "falsifier", + "discriminating_tests", + "nulls", + "controls", + "outcome_interpretation", + "human_review_status", + } + for index, raw_hypothesis in enumerate(raw_hypotheses): + context = f"checklist.hypotheses[{index}]" + hypothesis = require_object(raw_hypothesis, context) + require_exact_keys(hypothesis, required=fields, context=context) + hypothesis_id = require_identifier( + hypothesis["hypothesis_id"], f"{context}.hypothesis_id" + ) + hypothesis_ids.append(hypothesis_id) + parsed_hypotheses.append( + { + "hypothesis_id": hypothesis_id, + "candidate_status": require_enum( + hypothesis["candidate_status"], + {"candidate"}, + f"{context}.candidate_status", + ), + "assumptions": require_text_list( + hypothesis["assumptions"], + f"{context}.assumptions", + minimum=1, + ), + "boundary_conditions": require_text_list( + hypothesis["boundary_conditions"], + f"{context}.boundary_conditions", + minimum=1, + ), + "falsifier": _parse_falsifier( + hypothesis["falsifier"], f"{context}.falsifier" + ), + "discriminating_tests": _parse_discriminating_tests( + hypothesis["discriminating_tests"], + f"{context}.discriminating_tests", + ), + "nulls": _parse_nulls( + hypothesis["nulls"], f"{context}.nulls" + ), + "controls": _parse_controls( + hypothesis["controls"], f"{context}.controls" + ), + "outcome_interpretation": _parse_outcome_interpretation( + hypothesis["outcome_interpretation"], + f"{context}.outcome_interpretation", + ), + "human_review_status": require_enum( + hypothesis["human_review_status"], + {"pending", "complete", "specialist_required"}, + f"{context}.human_review_status", + ), + } + ) + require_unique(hypothesis_ids, "checklist.hypotheses") + return { + "schema_version": require_enum( + root["schema_version"], {"2.0"}, "checklist.schema_version" + ), + "checklist_id": require_identifier( + root["checklist_id"], "checklist.checklist_id" + ), + "record_id": require_identifier(root["record_id"], "checklist.record_id"), + "hypotheses": parsed_hypotheses, + } + + +def _normalize(value: str) -> str: + return re.sub(r"[^a-z0-9]+", " ", value.casefold()).strip() + + +def audit( + checklist: dict[str, Any], record: dict[str, Any] | None = None +) -> dict[str, Any]: + errors: list[dict[str, str]] = [] + warnings: list[dict[str, str]] = [] + review_counts: Counter[str] = Counter() + test_ids: list[str] = [] + null_ids: list[str] = [] + + record_ids: dict[str, set[str]] | None = None + if record is not None: + record_report = validate_record(record) + if not record_report["valid"]: + raise ValidationError( + "optional hypothesis record must pass schema validation first" + ) + record_ids = { + "hypotheses": { + item["hypothesis_id"] for item in record["hypotheses"] + }, + "predictions": { + item["prediction_id"] for item in record["predictions"] + }, + "analyses": { + item["analysis_id"] for item in record["analysis_plan"]["analyses"] + }, + "controls": { + item["control_id"] for item in record["negative_controls"] + }, + "nulls": {item["null_id"] for item in record["null_hypotheses"]}, + } + + for hypothesis in checklist["hypotheses"]: + hypothesis_id = hypothesis["hypothesis_id"] + review_counts[hypothesis["human_review_status"]] += 1 + if hypothesis["human_review_status"] != "complete": + warnings.append(issue("HUMAN_REVIEW_INCOMPLETE", hypothesis_id)) + + falsifier = hypothesis["falsifier"] + if record_ids is not None: + if hypothesis_id not in record_ids["hypotheses"]: + errors.append(issue("UNKNOWN_HYPOTHESIS_ID", hypothesis_id)) + if falsifier["prediction_id"] not in record_ids["predictions"]: + errors.append( + issue("UNKNOWN_PREDICTION_ID", falsifier["prediction_id"]) + ) + + for test in hypothesis["discriminating_tests"]: + test_ids.append(test["test_id"]) + if test["rival_hypothesis_id"] == hypothesis_id: + errors.append(issue("FOCAL_HYPOTHESIS_LISTED_AS_RIVAL", test["test_id"])) + if _normalize(test["focal_expected"]) == _normalize( + test["rival_expected"] + ): + errors.append( + issue("FOCAL_AND_RIVAL_EXPECTATIONS_IDENTICAL", test["test_id"]) + ) + if ( + record_ids is not None + and test["rival_hypothesis_id"] not in record_ids["hypotheses"] + ): + errors.append( + issue("UNKNOWN_RIVAL_HYPOTHESIS_ID", test["test_id"]) + ) + + for null in hypothesis["nulls"]: + null_ids.append(null["null_id"]) + if record_ids is not None: + if null["null_id"] not in record_ids["nulls"]: + errors.append(issue("UNKNOWN_NULL_ID", null["null_id"])) + if null["analysis_id"] not in record_ids["analyses"]: + errors.append( + issue("UNKNOWN_ANALYSIS_ID", null["analysis_id"]) + ) + + negative_controls = [ + control + for control in hypothesis["controls"] + if control["control_type"] in NEGATIVE_TYPES + ] + if not negative_controls: + errors.append(issue("NEGATIVE_CONTROL_REQUIRED", hypothesis_id)) + if record_ids is not None: + for control in negative_controls: + if control["control_id"] not in record_ids["controls"]: + errors.append( + issue("UNKNOWN_NEGATIVE_CONTROL_ID", control["control_id"]) + ) + + require_unique(test_ids, "checklist.discriminating_tests") + require_unique(null_ids, "checklist.nulls") + + return { + "schema_version": "2.0", + "checklist_id": checklist["checklist_id"], + "record_id": checklist["record_id"], + "valid": not errors, + "status": ( + "INVALID_CHECKLIST" + if errors + else "VALID_PENDING_HUMAN_REVIEW" + if any(item["human_review_status"] != "complete" for item in checklist["hypotheses"]) + else "VALID_HUMAN_REVIEW_DECLARED_COMPLETE" + ), + "errors": errors, + "warnings": warnings, + "hypothesis_ids": sorted( + item["hypothesis_id"] for item in checklist["hypotheses"] + ), + "discriminating_test_ids": sorted(test_ids), + "null_ids": sorted(null_ids), + "human_review_status_counts": dict(sorted(review_counts.items())), + "record_cross_check_performed": record is not None, + "notice": ( + "This audit checks declared falsifiers, rival contrasts, nulls, " + "controls, and links. It does not prove falsifiability, validate " + "control assumptions, interpret results, or select a hypothesis." + ), + } + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description=( + "Audit a bounded local falsification/control JSON checklist without " + "scientific scoring or candidate selection." + ) + ) + parser.add_argument("checklist", help="Local falsification/control checklist JSON") + parser.add_argument( + "--record", help="Optional local hypothesis record JSON for cross-checks" + ) + parser.add_argument("-o", "--output", help="Optional local JSON report") + parser.add_argument( + "--force", action="store_true", help="Replace an existing output file" + ) + return parser + + +def main() -> int: + args = build_parser().parse_args() + try: + record = ( + load_hypothesis_record(read_json(args.record)) if args.record else None + ) + report = audit(load_checklist(read_json(args.checklist)), record) + write_json_report(report, args.output, force=args.force) + return 0 if report["valid"] else 1 + except ValidationError as exc: + return error_exit(exc) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/scripts/check_operationalization.py b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/scripts/check_operationalization.py new file mode 100644 index 0000000..1ece30a --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/scripts/check_operationalization.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python3 +"""Audit a local operationalization and measurement checklist without scoring.""" + +from __future__ import annotations + +import argparse +from collections import Counter +from typing import Any + +from _common import ( + ValidationError, + error_exit, + issue, + read_json, + require_bool, + require_enum, + require_exact_keys, + require_identifier, + require_identifier_list, + require_list, + require_object, + require_text, + require_unique, + write_json_report, +) + +ROLES = { + "intervention", + "exposure", + "outcome", + "mediator", + "confounder", + "selection", + "effect_modifier", + "negative_control", + "positive_control", + "other", +} +PLANNING_STATUSES = {"planned", "complete", "not_applicable", "unresolved"} +REVIEW_STATUSES = {"pending", "complete", "specialist_required"} +BOOLEAN_FIELDS = ( + "construct_defined", + "operational_definition_recorded", + "population_scope_recorded", + "unit_or_categories_recorded", + "timing_recorded", + "instrument_or_method_recorded", + "validity_applicability_reviewed", + "reliability_or_repeatability_plan_recorded", + "missingness_plan_recorded", + "limitations_recorded", +) +STATUS_FIELDS = ( + "calibration_or_quality_control_status", + "measurement_invariance_or_comparability_status", + "masking_status", + "threshold_or_cutpoint_status", +) + + +def load_checklist(payload: Any) -> dict[str, Any]: + root = require_object(payload, "checklist") + require_exact_keys( + root, + required={ + "schema_version", + "checklist_id", + "record_id", + "human_reviewer", + "items", + }, + context="checklist", + ) + raw_items = require_list(root["items"], "checklist.items", minimum=1, maximum=200) + parsed_items: list[dict[str, Any]] = [] + identifiers: list[str] = [] + item_fields = { + "measurement_id", + "applicability", + "variable_role", + "validity_evidence_source_ids", + "human_review_status", + "note", + *BOOLEAN_FIELDS, + *STATUS_FIELDS, + } + for index, raw_item in enumerate(raw_items): + context = f"checklist.items[{index}]" + item = require_object(raw_item, context) + require_exact_keys(item, required=item_fields, context=context) + measurement_id = require_identifier( + item["measurement_id"], f"{context}.measurement_id" + ) + identifiers.append(measurement_id) + parsed: dict[str, Any] = { + "measurement_id": measurement_id, + "applicability": require_enum( + item["applicability"], + {"applicable", "not_applicable"}, + f"{context}.applicability", + ), + "variable_role": require_enum( + item["variable_role"], ROLES, f"{context}.variable_role" + ), + "validity_evidence_source_ids": require_identifier_list( + item["validity_evidence_source_ids"], + f"{context}.validity_evidence_source_ids", + maximum=100, + ), + "human_review_status": require_enum( + item["human_review_status"], + REVIEW_STATUSES, + f"{context}.human_review_status", + ), + "note": require_text( + item["note"], f"{context}.note", allow_empty=True, maximum=2_000 + ), + } + for field in BOOLEAN_FIELDS: + parsed[field] = require_bool(item[field], f"{context}.{field}") + for field in STATUS_FIELDS: + parsed[field] = require_enum( + item[field], PLANNING_STATUSES, f"{context}.{field}" + ) + parsed_items.append(parsed) + require_unique(identifiers, "checklist.items") + return { + "schema_version": require_enum( + root["schema_version"], {"2.0"}, "checklist.schema_version" + ), + "checklist_id": require_identifier( + root["checklist_id"], "checklist.checklist_id" + ), + "record_id": require_identifier(root["record_id"], "checklist.record_id"), + "human_reviewer": require_text( + root["human_reviewer"], "checklist.human_reviewer", minimum=3 + ), + "items": parsed_items, + } + + +def audit(checklist: dict[str, Any]) -> dict[str, Any]: + errors: list[dict[str, str]] = [] + warnings: list[dict[str, str]] = [] + gaps_by_measurement: dict[str, list[str]] = {} + role_counts: Counter[str] = Counter() + review_counts: Counter[str] = Counter() + + for item in checklist["items"]: + measurement_id = item["measurement_id"] + role_counts[item["variable_role"]] += 1 + review_counts[item["human_review_status"]] += 1 + gaps: list[str] = [] + + if item["applicability"] == "not_applicable": + if not item["note"]: + errors.append(issue("NOT_APPLICABLE_RATIONALE_REQUIRED", measurement_id)) + continue + + for field in BOOLEAN_FIELDS: + if not item[field]: + gaps.append(field) + if not item["validity_evidence_source_ids"]: + errors.append(issue("VALIDITY_SOURCE_REQUIRED", measurement_id)) + for field in STATUS_FIELDS: + status = item[field] + if status in {"planned", "unresolved"}: + gaps.append(field) + if status == "not_applicable" and not item["note"]: + errors.append( + issue("NOT_APPLICABLE_STATUS_NEEDS_RATIONALE", f"{measurement_id}:{field}") + ) + if item["human_review_status"] != "complete": + gaps.append("human_review_status") + if not item["note"]: + warnings.append(issue("MEASUREMENT_NOTE_EMPTY", measurement_id)) + if gaps: + gaps_by_measurement[measurement_id] = sorted(set(gaps)) + + if not role_counts["outcome"]: + warnings.append(issue("NO_OUTCOME_MEASUREMENT_DECLARED", "items")) + if not (role_counts["intervention"] or role_counts["exposure"]): + warnings.append(issue("NO_INTERVENTION_OR_EXPOSURE_DECLARED", "items")) + + return { + "schema_version": "2.0", + "checklist_id": checklist["checklist_id"], + "record_id": checklist["record_id"], + "valid": not errors, + "status": ( + "INVALID_CHECKLIST" + if errors + else "VALID_WITH_MEASUREMENT_GAPS" + if gaps_by_measurement + else "VALID_HUMAN_REVIEW_COMPLETE" + ), + "errors": errors, + "warnings": warnings, + "measurement_count": len(checklist["items"]), + "role_counts": dict(sorted(role_counts.items())), + "human_review_status_counts": dict(sorted(review_counts.items())), + "gap_fields_by_measurement_id": gaps_by_measurement, + "notice": ( + "This checklist records declared measurement work. It does not test " + "an instrument, establish construct validity, certify comparability, " + "or score measurement quality. Qualified human review is required." + ), + } + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description=( + "Audit a bounded local JSON operationalization checklist and report " + "measurement IDs and unresolved fields without scientific scoring." + ) + ) + parser.add_argument("checklist", help="Local operationalization checklist JSON") + parser.add_argument("-o", "--output", help="Optional local JSON report") + parser.add_argument( + "--force", action="store_true", help="Replace an existing output file" + ) + return parser + + +def main() -> int: + args = build_parser().parse_args() + try: + report = audit(load_checklist(read_json(args.checklist))) + write_json_report(report, args.output, force=args.force) + return 0 if report["valid"] else 1 + except ValidationError as exc: + return error_exit(exc) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/scripts/generate_preregistration_scaffold.py b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/scripts/generate_preregistration_scaffold.py new file mode 100644 index 0000000..04d172e --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/scripts/generate_preregistration_scaffold.py @@ -0,0 +1,384 @@ +#!/usr/bin/env python3 +"""Generate a deterministic local Markdown preregistration scaffold.""" + +from __future__ import annotations + +import argparse +import html +import re +from pathlib import Path +from typing import Any + +from _common import ( + ValidationError, + error_exit, + read_json, + read_markdown, + write_markdown, +) +from validate_hypothesis_schema import load_hypothesis_record, validate_record + +TEMPLATE_PATH = ( + Path(__file__).resolve().parents[1] + / "assets" + / "preregistration_scaffold_template.md" +) +PLACEHOLDER_RE = re.compile(r"\{\{[A-Z0-9_]+\}\}") +MARKDOWN_SPECIAL_RE = re.compile(r"([\\`*_[\]{}#+|])") + + +def _safe_text(value: Any) -> str: + """Render bounded input as inert one-line Markdown text.""" + text = " ".join(str(value).split()) + text = html.escape(text, quote=False) + return MARKDOWN_SPECIAL_RE.sub(r"\\\1", text) + + +def _bullet(label: str, value: Any) -> str: + return f"- **{_safe_text(label)}:** {_safe_text(value)}" + + +def _render_ethics(record: dict[str, Any]) -> str: + ethics = record["ethics_and_feasibility"] + lines = [ + _bullet(field.replace("_", " "), ethics[field]) + for field in ( + "human_subjects_gate", + "animal_research_gate", + "biosafety_gate", + "dual_use_gate", + "regulatory_gate", + "data_governance_gate", + "feasibility_status", + ) + ] + reviews = ethics["required_reviews"] or ["None declared"] + blocks = ethics["unresolved_blocks"] or ["None declared"] + lines.append(_bullet("required reviews", "; ".join(reviews))) + lines.append(_bullet("unresolved blocks", "; ".join(blocks))) + return "\n".join(lines) + + +def _render_evidence(record: dict[str, Any]) -> str: + evidence = record["evidence"] + lines = [ + _bullet("search boundary ID", evidence["search_boundary_id"]), + _bullet("evidence ledger path", evidence["ledger_path"]), + _bullet("declared source IDs", "; ".join(evidence["source_ids"])), + _bullet("evidence limitations", "; ".join(evidence["evidence_limitations"])), + ] + return "\n".join(lines) + + +def _render_observation(record: dict[str, Any]) -> str: + observation = record["observation"] + return "\n".join( + [ + _bullet("statement", observation["statement"]), + _bullet("provenance", observation["provenance"]), + _bullet("source IDs", "; ".join(observation["source_ids"])), + _bullet("uncertainties", "; ".join(observation["uncertainties"])), + ] + ) + + +def _render_question(record: dict[str, Any]) -> str: + question = record["research_question"] + return "\n".join( + _bullet(field.replace("_", " "), question[field]) + for field in ( + "statement", + "framework", + "question_type", + "population_or_system", + "intervention_or_exposure", + "comparator", + "outcome", + "timeframe", + ) + ) + + +def _render_hypotheses(record: dict[str, Any]) -> str: + sections: list[str] = [] + for hypothesis in record["hypotheses"]: + sections.extend( + [ + f"### {_safe_text(hypothesis['hypothesis_id'])}", + "", + _bullet("status", hypothesis["status"]), + _bullet("candidate statement", hypothesis["statement"]), + _bullet("proposed mechanism", hypothesis["mechanism"]), + _bullet("rival IDs", "; ".join(hypothesis["rival_hypothesis_ids"])), + _bullet("assumptions", "; ".join(hypothesis["assumptions"])), + _bullet( + "boundary conditions", "; ".join(hypothesis["boundary_conditions"]) + ), + _bullet("uncertainties", "; ".join(hypothesis["uncertainties"])), + _bullet("source IDs", "; ".join(hypothesis["source_ids"])), + "", + ] + ) + return "\n".join(sections).rstrip() + + +def _render_estimands(record: dict[str, Any]) -> str: + if not record["causal_estimands"]: + return "- Not applicable to the declared non-causal question." + sections: list[str] = [] + for estimand in record["causal_estimands"]: + sections.extend( + [ + f"### {_safe_text(estimand['estimand_id'])}", + "", + _bullet( + "linked hypothesis IDs", + "; ".join(estimand["linked_hypothesis_ids"]), + ), + _bullet("population", estimand["population"]), + _bullet( + "intervention or exposure", estimand["intervention_or_exposure"] + ), + _bullet("comparator", estimand["comparator"]), + _bullet("outcome", estimand["outcome"]), + _bullet("time horizon", estimand["time_horizon"]), + _bullet("population summary", estimand["population_summary"]), + _bullet( + "intercurrent-event strategy", + estimand["intercurrent_event_strategy"], + ), + _bullet( + "identification assumptions", + "; ".join(estimand["identification_assumptions"]), + ), + "", + ] + ) + return "\n".join(sections).rstrip() + + +def _render_predictions(record: dict[str, Any]) -> str: + sections: list[str] = [] + for prediction in record["predictions"]: + sections.extend( + [ + f"### {_safe_text(prediction['prediction_id'])} " + f"({_safe_text(prediction['hypothesis_id'])})", + "", + _bullet("statement", prediction["statement"]), + _bullet("conditions", prediction["conditions"]), + _bullet("observable", prediction["observable"]), + _bullet("expected pattern", prediction["expected_pattern"]), + _bullet("falsifier", prediction["falsifier"]), + _bullet( + "rival hypothesis IDs", + "; ".join(prediction["rival_hypothesis_ids"]), + ), + _bullet("measurement IDs", "; ".join(prediction["measurement_ids"])), + _bullet("analysis IDs", "; ".join(prediction["analysis_ids"])), + "", + ] + ) + return "\n".join(sections).rstrip() + + +def _render_nulls_and_controls(record: dict[str, Any]) -> str: + sections = ["### Null hypotheses", ""] + for null in record["null_hypotheses"]: + sections.extend( + [ + _bullet( + null["null_id"], + f"{null['statement']} | Rule: " + f"{null['rejection_or_compatibility_rule']}", + ), + "", + ] + ) + sections.extend(["### Negative controls", ""]) + for control in record["negative_controls"]: + sections.extend( + [ + _bullet( + control["control_id"], + f"{control['control_type']} | Rationale: {control['rationale']} " + f"| Expected: {control['expected_result']} | Failure: " + f"{control['failure_implication']}", + ), + "", + ] + ) + return "\n".join(sections).rstrip() + + +def _render_operationalizations(record: dict[str, Any]) -> str: + sections: list[str] = [] + for measurement in record["operationalizations"]: + sections.extend( + [ + f"### {_safe_text(measurement['measurement_id'])}", + "", + _bullet("construct", measurement["construct"]), + _bullet("variable and role", f"{measurement['variable']} / {measurement['role']}"), + _bullet( + "operational definition", measurement["operational_definition"] + ), + _bullet( + "instrument or method", measurement["instrument_or_method"] + ), + _bullet("unit", measurement["unit"]), + _bullet("timing", measurement["timing"]), + _bullet("population or system", measurement["population_or_system"]), + _bullet( + "validity evidence source IDs", + "; ".join(measurement["validity_evidence_source_ids"]), + ), + _bullet("reliability plan", measurement["reliability_plan"]), + _bullet("missingness plan", measurement["missingness_plan"]), + _bullet( + "blinding or masking", measurement["blinding_or_masking"] + ), + _bullet("threshold rationale", measurement["threshold_rationale"]), + "", + ] + ) + return "\n".join(sections).rstrip() + + +def _render_analyses(record: dict[str, Any]) -> str: + sections: list[str] = [] + for analysis in record["analysis_plan"]["analyses"]: + sections.extend( + [ + f"### {_safe_text(analysis['analysis_id'])}", + "", + _bullet("mode", analysis["exploratory_or_confirmatory"]), + _bullet("prediction IDs", "; ".join(analysis["prediction_ids"])), + _bullet("estimand IDs", "; ".join(analysis["estimand_ids"]) or "None"), + _bullet("analysis population", analysis["analysis_population"]), + _bullet("method", analysis["method"]), + _bullet( + "effect or summary measure", + analysis["effect_or_summary_measure"], + ), + _bullet("uncertainty method", analysis["uncertainty_method"]), + _bullet("missing-data plan", analysis["missing_data_plan"]), + _bullet("multiplicity plan", analysis["multiplicity_plan"]), + _bullet( + "sensitivity analyses", + "; ".join(analysis["sensitivity_analyses"]), + ), + _bullet("decision rule", analysis["decision_rule"]), + "", + ] + ) + return "\n".join(sections).rstrip() + + +def _render_ai_use(record: dict[str, Any]) -> str: + ai_use = record["ai_use"] + return "\n".join( + [ + _bullet("AI used", str(ai_use["used"]).lower()), + _bullet( + "sensitive or unpublished data sent externally", + str( + ai_use["sensitive_or_unpublished_data_sent_externally"] + ).lower(), + ), + _bullet( + "local policy checked", str(ai_use["local_policy_checked"]).lower() + ), + _bullet( + "citation verification required", + str(ai_use["citation_verification_required"]).lower(), + ), + _bullet( + "human accountable", str(ai_use["human_accountable"]).lower() + ), + _bullet("diversity mitigation", ai_use["diversity_mitigation"]), + ] + ) + + +def generate( + record: dict[str, Any], template_path: Path = TEMPLATE_PATH +) -> str: + """Render all candidates into a local draft without ranking or selection.""" + validation = validate_record(record) + if not validation["valid"]: + codes = sorted({item["code"] for item in validation["errors"]}) + raise ValidationError( + "hypothesis record is invalid; resolve these controls first: " + + ", ".join(codes) + ) + if validation["unresolved_gate_fields"]: + raise ValidationError( + "hypothesis record has unresolved safety or feasibility gates: " + + ", ".join(validation["unresolved_gate_fields"]) + ) + + template = read_markdown(template_path) + replacements = { + "{{PROJECT_ID}}": _safe_text(record["project_id"]), + "{{GENERATED_ON}}": _safe_text(record["updated_on"]), + "{{HUMAN_OWNER}}": _safe_text(record["human_owner"]), + "{{RECORD_STATUS}}": _safe_text(record["status"]), + "{{UPDATED_ON}}": _safe_text(record["updated_on"]), + "{{ETHICS_AND_FEASIBILITY}}": _render_ethics(record), + "{{EVIDENCE_BOUNDARY}}": _render_evidence(record), + "{{OBSERVATION}}": _render_observation(record), + "{{RESEARCH_QUESTION}}": _render_question(record), + "{{HYPOTHESES}}": _render_hypotheses(record), + "{{ESTIMANDS}}": _render_estimands(record), + "{{PREDICTIONS}}": _render_predictions(record), + "{{NULLS_AND_CONTROLS}}": _render_nulls_and_controls(record), + "{{OPERATIONALIZATIONS}}": _render_operationalizations(record), + "{{ANALYSES}}": _render_analyses(record), + "{{AI_USE}}": _render_ai_use(record), + "{{DEVIATION_PLAN}}": _safe_text( + record["analysis_plan"]["deviation_reporting"] + ), + } + rendered = template + for placeholder, value in replacements.items(): + rendered = rendered.replace(placeholder, value) + unresolved = sorted(set(PLACEHOLDER_RE.findall(rendered))) + if unresolved: + raise ValidationError( + "scaffold template contains unresolved placeholders: " + + ", ".join(unresolved) + ) + return rendered.rstrip() + "\n" + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description=( + "Generate a private local Markdown preregistration scaffold from a " + "valid hypothesis record; no upload or registration occurs." + ) + ) + parser.add_argument("record", help="Local hypothesis record JSON") + parser.add_argument("-o", "--output", required=True, help="Output Markdown path") + parser.add_argument( + "--force", action="store_true", help="Replace an existing output file" + ) + return parser + + +def main() -> int: + args = build_parser().parse_args() + try: + record = load_hypothesis_record(read_json(args.record)) + destination = write_markdown( + generate(record), args.output, force=args.force + ) + print(f"Created local preregistration scaffold: {destination}") + return 0 + except ValidationError as exc: + return error_exit(exc) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/scripts/lint_causal_claims.py b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/scripts/lint_causal_claims.py new file mode 100644 index 0000000..4c0bfc3 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/scripts/lint_causal_claims.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +"""Lexically lint causal versus associational claims in bounded Markdown.""" + +from __future__ import annotations + +import argparse +import re +from collections import Counter +from typing import Any + +from _common import ( + ValidationError, + error_exit, + issue, + read_markdown, + write_json_report, +) + +TAG_RE = re.compile( + r"\[(claim|estimand|identification|confounding|selection|collider|" + r"reverse-causation):([A-Za-z0-9_.:-]+)\]", + re.IGNORECASE, +) +CAUSAL_RE = re.compile( + r"\b(?:causes?|caused|causal\s+(?:effect|impact)|effect\s+of|" + r"leads?\s+to|results?\s+in|increases?|reduces?|prevents?|improves?|" + r"worsens?|drives?|mediates?|produces?)\b", + re.IGNORECASE, +) +ASSOCIATION_RE = re.compile( + r"\b(?:associated\s+with|association|correlates?\s+with|correlation|" + r"co-var(?:y|ies|ied)\s+with|predicts?|linked\s+to)\b", + re.IGNORECASE, +) +CLAIM_TYPES = {"causal", "associational", "descriptive", "predictive", "mechanistic"} +IDENTIFICATION_TYPES = { + "randomized", + "quasi_experimental", + "observational_assumption_dependent", + "mechanistic_experiment", + "other_assumption_dependent", +} +RISK_STATES = {"assessed", "unresolved", "not_applicable"} +REQUIRED_CAUSAL_TAGS = ( + "estimand", + "identification", + "confounding", + "selection", + "collider", + "reverse-causation", +) + + +def _finding(code: str, line_number: int) -> dict[str, Any]: + return {"code": code, "line": line_number} + + +def lint(markdown: str) -> dict[str, Any]: + errors: list[dict[str, Any]] = [] + warnings: list[dict[str, Any]] = [] + claim_counts: Counter[str] = Counter() + causal_trigger_lines = 0 + association_trigger_lines = 0 + in_fence = False + + for line_number, raw_line in enumerate(markdown.splitlines(), start=1): + stripped = raw_line.strip() + if stripped.startswith("```") or stripped.startswith("~~~"): + in_fence = not in_fence + continue + if in_fence or not stripped or stripped.startswith("#"): + continue + + matches = [(key.casefold(), value) for key, value in TAG_RE.findall(stripped)] + key_counts = Counter(key for key, _ in matches) + for key, count in key_counts.items(): + if count > 1: + errors.append(_finding(f"DUPLICATE_{key.upper()}_TAG", line_number)) + tags = {key: value for key, value in matches} + claim_type = tags.get("claim", "").casefold() + has_causal_language = CAUSAL_RE.search(stripped) is not None + has_association_language = ASSOCIATION_RE.search(stripped) is not None + + if claim_type: + if claim_type not in CLAIM_TYPES: + errors.append(_finding("INVALID_CLAIM_TYPE", line_number)) + else: + claim_counts[claim_type] += 1 + + if has_causal_language: + causal_trigger_lines += 1 + if not claim_type: + errors.append(_finding("UNMARKED_CAUSAL_LANGUAGE", line_number)) + elif claim_type not in {"causal", "mechanistic"}: + errors.append( + _finding("CAUSAL_LANGUAGE_IN_NONCAUSAL_CLAIM", line_number) + ) + + if has_association_language: + association_trigger_lines += 1 + if not claim_type: + warnings.append( + _finding("UNMARKED_ASSOCIATIONAL_LANGUAGE", line_number) + ) + elif claim_type == "causal" and not has_causal_language: + warnings.append( + _finding("CAUSAL_TAG_WITH_ASSOCIATION_ONLY_LANGUAGE", line_number) + ) + + if claim_type == "causal": + for required_tag in REQUIRED_CAUSAL_TAGS: + if required_tag not in tags: + code_tag = required_tag.replace("-", "_").upper() + errors.append( + _finding( + f"CAUSAL_CLAIM_MISSING_{code_tag}_TAG", + line_number, + ) + ) + identification = tags.get("identification", "").casefold() + if identification and identification not in IDENTIFICATION_TYPES: + errors.append(_finding("INVALID_IDENTIFICATION_TAG", line_number)) + for risk_tag in ( + "confounding", + "selection", + "collider", + "reverse-causation", + ): + state = tags.get(risk_tag, "").casefold() + code_tag = risk_tag.replace("-", "_").upper() + if state and state not in RISK_STATES: + errors.append( + _finding(f"INVALID_{code_tag}_STATE", line_number) + ) + if state == "unresolved": + warnings.append( + _finding(f"UNRESOLVED_{code_tag}_RISK", line_number) + ) + elif claim_type and "estimand" in tags: + warnings.append(_finding("ESTIMAND_TAG_ON_NONCAUSAL_CLAIM", line_number)) + + if in_fence: + errors.append(issue("UNCLOSED_MARKDOWN_FENCE", "document")) + + return { + "schema_version": "2.0", + "valid": not errors, + "status": "INVALID_CLAIM_MARKUP" if errors else "VALID_LEXICAL_LINT", + "errors": errors, + "warnings": warnings, + "claim_type_counts": dict(sorted(claim_counts.items())), + "causal_trigger_line_count": causal_trigger_lines, + "associational_trigger_line_count": association_trigger_lines, + "notice": ( + "This deterministic lexical lint does not determine whether language " + "is scientifically causal, whether an estimand is well defined, or " + "whether identification assumptions hold. Review every flagged and " + "unflagged claim manually." + ), + } + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description=( + "Lint bounded local Markdown for causal/associational claim annotations " + "and emit only rule codes and line numbers." + ) + ) + parser.add_argument("document", help="Local Markdown document") + parser.add_argument("-o", "--output", help="Optional local JSON report") + parser.add_argument( + "--force", action="store_true", help="Replace an existing output file" + ) + return parser + + +def main() -> int: + args = build_parser().parse_args() + try: + report = lint(read_markdown(args.document)) + write_json_report(report, args.output, force=args.force) + return 0 if report["valid"] else 1 + except ValidationError as exc: + return error_exit(exc) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/scripts/validate_hypothesis_schema.py b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/scripts/validate_hypothesis_schema.py new file mode 100644 index 0000000..036c39d --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/scripts/validate_hypothesis_schema.py @@ -0,0 +1,1078 @@ +#!/usr/bin/env python3 +"""Validate a structured hypothesis record without judging scientific merit.""" + +from __future__ import annotations + +import argparse +from collections import Counter +from pathlib import PurePosixPath +from typing import Any + +from _common import ( + ValidationError, + error_exit, + issue, + read_json, + require_bool, + require_enum, + require_exact_keys, + require_identifier, + require_identifier_list, + require_iso_date, + require_list, + require_object, + require_text, + require_text_list, + require_unique, + write_json_report, +) + +QUESTION_TYPES = {"descriptive", "associational", "predictive", "causal", "mechanistic"} +HYPOTHESIS_STATUS = {"candidate"} +MEASUREMENT_ROLES = { + "intervention", + "exposure", + "outcome", + "mediator", + "confounder", + "selection", + "negative_control", + "positive_control", + "effect_modifier", + "other", +} +RISK_TYPES = { + "confounding", + "selection_bias", + "collider_bias", + "reverse_causation", + "measurement_bias", + "other", +} +CONTROL_TYPES = { + "negative_exposure", + "negative_outcome", + "procedural_negative", + "positive_control", + "vehicle_or_sham", + "other", +} +PLAN_MODES = {"confirmatory", "exploratory"} +GATE_STATES = {"not_applicable", "undetermined", "requires_review", "approved", "blocked"} +FEASIBILITY_STATES = { + "undetermined", + "feasible_for_planning", + "requires_pilot", + "infeasible", + "blocked", +} + + +def _parse_observation(raw: Any) -> dict[str, Any]: + value = require_object(raw, "record.observation") + require_exact_keys( + value, + required={"statement", "provenance", "source_ids", "uncertainties"}, + context="record.observation", + ) + return { + "statement": require_text( + value["statement"], "record.observation.statement", minimum=10 + ), + "provenance": require_text( + value["provenance"], "record.observation.provenance", minimum=10 + ), + "source_ids": require_identifier_list( + value["source_ids"], "record.observation.source_ids", minimum=1 + ), + "uncertainties": require_text_list( + value["uncertainties"], + "record.observation.uncertainties", + minimum=1, + ), + } + + +def _parse_question(raw: Any) -> dict[str, str]: + value = require_object(raw, "record.research_question") + fields = { + "statement", + "framework", + "question_type", + "population_or_system", + "intervention_or_exposure", + "comparator", + "outcome", + "timeframe", + } + require_exact_keys(value, required=fields, context="record.research_question") + parsed = { + field: require_text( + value[field], + f"record.research_question.{field}", + minimum=2 if field == "framework" else 5, + ) + for field in fields + if field != "question_type" + } + parsed["question_type"] = require_enum( + value["question_type"], + QUESTION_TYPES, + "record.research_question.question_type", + ) + return parsed + + +def _parse_hypotheses(raw: Any) -> list[dict[str, Any]]: + entries = require_list(raw, "record.hypotheses", minimum=1, maximum=50) + parsed: list[dict[str, Any]] = [] + identifiers: list[str] = [] + fields = { + "hypothesis_id", + "statement", + "mechanism", + "status", + "source_ids", + "assumptions", + "boundary_conditions", + "uncertainties", + "prediction_ids", + "rival_hypothesis_ids", + } + for index, raw_entry in enumerate(entries): + context = f"record.hypotheses[{index}]" + entry = require_object(raw_entry, context) + require_exact_keys(entry, required=fields, context=context) + identifier = require_identifier(entry["hypothesis_id"], f"{context}.hypothesis_id") + identifiers.append(identifier) + parsed.append( + { + "hypothesis_id": identifier, + "statement": require_text( + entry["statement"], f"{context}.statement", minimum=10 + ), + "mechanism": require_text( + entry["mechanism"], f"{context}.mechanism", minimum=10 + ), + "status": require_enum( + entry["status"], HYPOTHESIS_STATUS, f"{context}.status" + ), + "source_ids": require_identifier_list( + entry["source_ids"], f"{context}.source_ids", minimum=1 + ), + "assumptions": require_text_list( + entry["assumptions"], f"{context}.assumptions", minimum=1 + ), + "boundary_conditions": require_text_list( + entry["boundary_conditions"], + f"{context}.boundary_conditions", + minimum=1, + ), + "uncertainties": require_text_list( + entry["uncertainties"], f"{context}.uncertainties", minimum=1 + ), + "prediction_ids": require_identifier_list( + entry["prediction_ids"], f"{context}.prediction_ids", minimum=1 + ), + "rival_hypothesis_ids": require_identifier_list( + entry["rival_hypothesis_ids"], + f"{context}.rival_hypothesis_ids", + ), + } + ) + require_unique(identifiers, "record.hypotheses") + return parsed + + +def _parse_estimands(raw: Any) -> list[dict[str, Any]]: + entries = require_list(raw, "record.causal_estimands", maximum=50) + parsed: list[dict[str, Any]] = [] + identifiers: list[str] = [] + fields = { + "estimand_id", + "linked_hypothesis_ids", + "population", + "intervention_or_exposure", + "comparator", + "outcome", + "time_horizon", + "population_summary", + "intercurrent_event_strategy", + "identification_assumptions", + } + for index, raw_entry in enumerate(entries): + context = f"record.causal_estimands[{index}]" + entry = require_object(raw_entry, context) + require_exact_keys(entry, required=fields, context=context) + identifier = require_identifier(entry["estimand_id"], f"{context}.estimand_id") + identifiers.append(identifier) + parsed.append( + { + "estimand_id": identifier, + "linked_hypothesis_ids": require_identifier_list( + entry["linked_hypothesis_ids"], + f"{context}.linked_hypothesis_ids", + minimum=1, + ), + "population": require_text(entry["population"], f"{context}.population"), + "intervention_or_exposure": require_text( + entry["intervention_or_exposure"], + f"{context}.intervention_or_exposure", + ), + "comparator": require_text( + entry["comparator"], f"{context}.comparator" + ), + "outcome": require_text(entry["outcome"], f"{context}.outcome"), + "time_horizon": require_text( + entry["time_horizon"], f"{context}.time_horizon" + ), + "population_summary": require_text( + entry["population_summary"], f"{context}.population_summary" + ), + "intercurrent_event_strategy": require_text( + entry["intercurrent_event_strategy"], + f"{context}.intercurrent_event_strategy", + ), + "identification_assumptions": require_text_list( + entry["identification_assumptions"], + f"{context}.identification_assumptions", + minimum=1, + ), + } + ) + require_unique(identifiers, "record.causal_estimands") + return parsed + + +def _parse_predictions(raw: Any) -> list[dict[str, Any]]: + entries = require_list(raw, "record.predictions", minimum=1, maximum=200) + parsed: list[dict[str, Any]] = [] + identifiers: list[str] = [] + fields = { + "prediction_id", + "hypothesis_id", + "statement", + "observable", + "conditions", + "expected_pattern", + "falsifier", + "rival_hypothesis_ids", + "measurement_ids", + "analysis_ids", + } + for index, raw_entry in enumerate(entries): + context = f"record.predictions[{index}]" + entry = require_object(raw_entry, context) + require_exact_keys(entry, required=fields, context=context) + identifier = require_identifier(entry["prediction_id"], f"{context}.prediction_id") + identifiers.append(identifier) + parsed.append( + { + "prediction_id": identifier, + "hypothesis_id": require_identifier( + entry["hypothesis_id"], f"{context}.hypothesis_id" + ), + "statement": require_text( + entry["statement"], f"{context}.statement", minimum=10 + ), + "observable": require_text( + entry["observable"], f"{context}.observable", minimum=5 + ), + "conditions": require_text( + entry["conditions"], f"{context}.conditions", minimum=5 + ), + "expected_pattern": require_text( + entry["expected_pattern"], f"{context}.expected_pattern", minimum=5 + ), + "falsifier": require_text( + entry["falsifier"], f"{context}.falsifier", minimum=10 + ), + "rival_hypothesis_ids": require_identifier_list( + entry["rival_hypothesis_ids"], + f"{context}.rival_hypothesis_ids", + minimum=1, + ), + "measurement_ids": require_identifier_list( + entry["measurement_ids"], + f"{context}.measurement_ids", + minimum=1, + ), + "analysis_ids": require_identifier_list( + entry["analysis_ids"], f"{context}.analysis_ids", minimum=1 + ), + } + ) + require_unique(identifiers, "record.predictions") + return parsed + + +def _parse_alternatives(raw: Any) -> list[dict[str, Any]]: + entries = require_list(raw, "record.alternative_explanations", minimum=1, maximum=100) + parsed: list[dict[str, Any]] = [] + identifiers: list[str] = [] + fields = { + "alternative_id", + "statement", + "linked_hypothesis_ids", + "risk_types", + "discriminating_prediction_ids", + } + for index, raw_entry in enumerate(entries): + context = f"record.alternative_explanations[{index}]" + entry = require_object(raw_entry, context) + require_exact_keys(entry, required=fields, context=context) + identifier = require_identifier(entry["alternative_id"], f"{context}.alternative_id") + identifiers.append(identifier) + risk_types = require_text_list( + entry["risk_types"], f"{context}.risk_types", minimum=1, maximum=10 + ) + for risk_index, risk_type in enumerate(risk_types): + require_enum(risk_type, RISK_TYPES, f"{context}.risk_types[{risk_index}]") + require_unique(risk_types, f"{context}.risk_types") + parsed.append( + { + "alternative_id": identifier, + "statement": require_text( + entry["statement"], f"{context}.statement", minimum=10 + ), + "linked_hypothesis_ids": require_identifier_list( + entry["linked_hypothesis_ids"], + f"{context}.linked_hypothesis_ids", + minimum=1, + ), + "risk_types": risk_types, + "discriminating_prediction_ids": require_identifier_list( + entry["discriminating_prediction_ids"], + f"{context}.discriminating_prediction_ids", + minimum=1, + ), + } + ) + require_unique(identifiers, "record.alternative_explanations") + return parsed + + +def _parse_nulls(raw: Any) -> list[dict[str, Any]]: + entries = require_list(raw, "record.null_hypotheses", minimum=1, maximum=100) + parsed: list[dict[str, Any]] = [] + identifiers: list[str] = [] + fields = { + "null_id", + "statement", + "linked_prediction_ids", + "rejection_or_compatibility_rule", + } + for index, raw_entry in enumerate(entries): + context = f"record.null_hypotheses[{index}]" + entry = require_object(raw_entry, context) + require_exact_keys(entry, required=fields, context=context) + identifier = require_identifier(entry["null_id"], f"{context}.null_id") + identifiers.append(identifier) + parsed.append( + { + "null_id": identifier, + "statement": require_text( + entry["statement"], f"{context}.statement", minimum=10 + ), + "linked_prediction_ids": require_identifier_list( + entry["linked_prediction_ids"], + f"{context}.linked_prediction_ids", + minimum=1, + ), + "rejection_or_compatibility_rule": require_text( + entry["rejection_or_compatibility_rule"], + f"{context}.rejection_or_compatibility_rule", + minimum=10, + ), + } + ) + require_unique(identifiers, "record.null_hypotheses") + return parsed + + +def _parse_controls(raw: Any) -> list[dict[str, Any]]: + entries = require_list(raw, "record.negative_controls", minimum=1, maximum=100) + parsed: list[dict[str, Any]] = [] + identifiers: list[str] = [] + fields = { + "control_id", + "control_type", + "rationale", + "expected_result", + "failure_implication", + "linked_prediction_ids", + } + for index, raw_entry in enumerate(entries): + context = f"record.negative_controls[{index}]" + entry = require_object(raw_entry, context) + require_exact_keys(entry, required=fields, context=context) + identifier = require_identifier(entry["control_id"], f"{context}.control_id") + identifiers.append(identifier) + parsed.append( + { + "control_id": identifier, + "control_type": require_enum( + entry["control_type"], CONTROL_TYPES, f"{context}.control_type" + ), + "rationale": require_text( + entry["rationale"], f"{context}.rationale", minimum=10 + ), + "expected_result": require_text( + entry["expected_result"], f"{context}.expected_result", minimum=5 + ), + "failure_implication": require_text( + entry["failure_implication"], + f"{context}.failure_implication", + minimum=10, + ), + "linked_prediction_ids": require_identifier_list( + entry["linked_prediction_ids"], + f"{context}.linked_prediction_ids", + minimum=1, + ), + } + ) + require_unique(identifiers, "record.negative_controls") + return parsed + + +def _parse_operationalizations(raw: Any) -> list[dict[str, Any]]: + entries = require_list(raw, "record.operationalizations", minimum=1, maximum=200) + parsed: list[dict[str, Any]] = [] + identifiers: list[str] = [] + fields = { + "measurement_id", + "construct", + "variable", + "role", + "operational_definition", + "instrument_or_method", + "unit", + "timing", + "population_or_system", + "validity_evidence_source_ids", + "reliability_plan", + "missingness_plan", + "blinding_or_masking", + "threshold_rationale", + } + for index, raw_entry in enumerate(entries): + context = f"record.operationalizations[{index}]" + entry = require_object(raw_entry, context) + require_exact_keys(entry, required=fields, context=context) + identifier = require_identifier(entry["measurement_id"], f"{context}.measurement_id") + identifiers.append(identifier) + text_fields = fields - { + "measurement_id", + "role", + "validity_evidence_source_ids", + } + parsed_entry: dict[str, Any] = { + "measurement_id": identifier, + "role": require_enum(entry["role"], MEASUREMENT_ROLES, f"{context}.role"), + "validity_evidence_source_ids": require_identifier_list( + entry["validity_evidence_source_ids"], + f"{context}.validity_evidence_source_ids", + minimum=1, + ), + } + for field in text_fields: + parsed_entry[field] = require_text( + entry[field], + f"{context}.{field}", + minimum=2 if field in {"unit", "variable"} else 5, + ) + parsed.append(parsed_entry) + require_unique(identifiers, "record.operationalizations") + return parsed + + +def _parse_analysis_plan(raw: Any) -> dict[str, Any]: + plan = require_object(raw, "record.analysis_plan") + require_exact_keys( + plan, + required={"analyses", "harking_control", "deviation_reporting"}, + context="record.analysis_plan", + ) + entries = require_list( + plan["analyses"], "record.analysis_plan.analyses", minimum=1, maximum=200 + ) + parsed: list[dict[str, Any]] = [] + identifiers: list[str] = [] + fields = { + "analysis_id", + "prediction_ids", + "estimand_ids", + "analysis_population", + "method", + "effect_or_summary_measure", + "uncertainty_method", + "missing_data_plan", + "multiplicity_plan", + "sensitivity_analyses", + "decision_rule", + "exploratory_or_confirmatory", + } + for index, raw_entry in enumerate(entries): + context = f"record.analysis_plan.analyses[{index}]" + entry = require_object(raw_entry, context) + require_exact_keys(entry, required=fields, context=context) + identifier = require_identifier(entry["analysis_id"], f"{context}.analysis_id") + identifiers.append(identifier) + parsed.append( + { + "analysis_id": identifier, + "prediction_ids": require_identifier_list( + entry["prediction_ids"], f"{context}.prediction_ids", minimum=1 + ), + "estimand_ids": require_identifier_list( + entry["estimand_ids"], f"{context}.estimand_ids" + ), + "analysis_population": require_text( + entry["analysis_population"], + f"{context}.analysis_population", + minimum=5, + ), + "method": require_text(entry["method"], f"{context}.method", minimum=5), + "effect_or_summary_measure": require_text( + entry["effect_or_summary_measure"], + f"{context}.effect_or_summary_measure", + minimum=3, + ), + "uncertainty_method": require_text( + entry["uncertainty_method"], + f"{context}.uncertainty_method", + minimum=5, + ), + "missing_data_plan": require_text( + entry["missing_data_plan"], + f"{context}.missing_data_plan", + minimum=5, + ), + "multiplicity_plan": require_text( + entry["multiplicity_plan"], + f"{context}.multiplicity_plan", + minimum=5, + ), + "sensitivity_analyses": require_text_list( + entry["sensitivity_analyses"], + f"{context}.sensitivity_analyses", + minimum=1, + ), + "decision_rule": require_text( + entry["decision_rule"], f"{context}.decision_rule", minimum=10 + ), + "exploratory_or_confirmatory": require_enum( + entry["exploratory_or_confirmatory"], + PLAN_MODES, + f"{context}.exploratory_or_confirmatory", + ), + } + ) + require_unique(identifiers, "record.analysis_plan.analyses") + return { + "analyses": parsed, + "harking_control": require_text( + plan["harking_control"], + "record.analysis_plan.harking_control", + minimum=10, + ), + "deviation_reporting": require_text( + plan["deviation_reporting"], + "record.analysis_plan.deviation_reporting", + minimum=10, + ), + } + + +def _parse_evidence(raw: Any) -> dict[str, Any]: + value = require_object(raw, "record.evidence") + require_exact_keys( + value, + required={ + "ledger_path", + "source_ids", + "search_boundary_id", + "evidence_limitations", + }, + context="record.evidence", + ) + ledger_path = require_text( + value["ledger_path"], "record.evidence.ledger_path", maximum=500 + ) + pure_path = PurePosixPath(ledger_path) + if pure_path.is_absolute() or ".." in pure_path.parts or "://" in ledger_path: + raise ValidationError("record.evidence.ledger_path must be a safe relative path") + if pure_path.suffix.lower() != ".csv": + raise ValidationError("record.evidence.ledger_path must end in .csv") + return { + "ledger_path": ledger_path, + "source_ids": require_identifier_list( + value["source_ids"], "record.evidence.source_ids", minimum=1 + ), + "search_boundary_id": require_identifier( + value["search_boundary_id"], "record.evidence.search_boundary_id" + ), + "evidence_limitations": require_text_list( + value["evidence_limitations"], + "record.evidence.evidence_limitations", + minimum=1, + ), + } + + +def _parse_risk_register(raw: Any) -> dict[str, list[str]]: + value = require_object(raw, "record.risk_register") + fields = { + "confounding", + "selection_bias", + "collider_bias", + "reverse_causation", + "measurement_bias", + "other", + } + require_exact_keys(value, required=fields, context="record.risk_register") + return { + field: require_text_list( + value[field], f"record.risk_register.{field}", maximum=50 + ) + for field in fields + } + + +def _parse_ethics(raw: Any) -> dict[str, Any]: + value = require_object(raw, "record.ethics_and_feasibility") + gate_fields = { + "human_subjects_gate", + "animal_research_gate", + "biosafety_gate", + "dual_use_gate", + "regulatory_gate", + "data_governance_gate", + } + require_exact_keys( + value, + required=gate_fields + | {"feasibility_status", "required_reviews", "unresolved_blocks"}, + context="record.ethics_and_feasibility", + ) + parsed: dict[str, Any] = { + field: require_enum( + value[field], GATE_STATES, f"record.ethics_and_feasibility.{field}" + ) + for field in gate_fields + } + parsed["feasibility_status"] = require_enum( + value["feasibility_status"], + FEASIBILITY_STATES, + "record.ethics_and_feasibility.feasibility_status", + ) + parsed["required_reviews"] = require_text_list( + value["required_reviews"], + "record.ethics_and_feasibility.required_reviews", + maximum=50, + ) + parsed["unresolved_blocks"] = require_text_list( + value["unresolved_blocks"], + "record.ethics_and_feasibility.unresolved_blocks", + maximum=50, + ) + return parsed + + +def _parse_ai_use(raw: Any) -> dict[str, Any]: + value = require_object(raw, "record.ai_use") + fields = { + "used", + "sensitive_or_unpublished_data_sent_externally", + "local_policy_checked", + "citation_verification_required", + "human_accountable", + "diversity_mitigation", + } + require_exact_keys(value, required=fields, context="record.ai_use") + return { + "used": require_bool(value["used"], "record.ai_use.used"), + "sensitive_or_unpublished_data_sent_externally": require_bool( + value["sensitive_or_unpublished_data_sent_externally"], + "record.ai_use.sensitive_or_unpublished_data_sent_externally", + ), + "local_policy_checked": require_bool( + value["local_policy_checked"], "record.ai_use.local_policy_checked" + ), + "citation_verification_required": require_bool( + value["citation_verification_required"], + "record.ai_use.citation_verification_required", + ), + "human_accountable": require_bool( + value["human_accountable"], "record.ai_use.human_accountable" + ), + "diversity_mitigation": require_text( + value["diversity_mitigation"], + "record.ai_use.diversity_mitigation", + minimum=10, + ), + } + + +def load_hypothesis_record(payload: Any) -> dict[str, Any]: + """Parse the exact v2 record schema and normalize bounded values.""" + root = require_object(payload, "record") + fields = { + "schema_version", + "project_id", + "status", + "updated_on", + "human_owner", + "observation", + "research_question", + "hypotheses", + "causal_estimands", + "predictions", + "alternative_explanations", + "null_hypotheses", + "negative_controls", + "operationalizations", + "analysis_plan", + "evidence", + "risk_register", + "ethics_and_feasibility", + "ai_use", + } + require_exact_keys(root, required=fields, context="record") + return { + "schema_version": require_enum( + root["schema_version"], {"2.0"}, "record.schema_version" + ), + "project_id": require_identifier(root["project_id"], "record.project_id"), + "status": require_enum( + root["status"], {"draft", "preregistered", "archived"}, "record.status" + ), + "updated_on": require_iso_date(root["updated_on"], "record.updated_on"), + "human_owner": require_text( + root["human_owner"], "record.human_owner", minimum=3 + ), + "observation": _parse_observation(root["observation"]), + "research_question": _parse_question(root["research_question"]), + "hypotheses": _parse_hypotheses(root["hypotheses"]), + "causal_estimands": _parse_estimands(root["causal_estimands"]), + "predictions": _parse_predictions(root["predictions"]), + "alternative_explanations": _parse_alternatives( + root["alternative_explanations"] + ), + "null_hypotheses": _parse_nulls(root["null_hypotheses"]), + "negative_controls": _parse_controls(root["negative_controls"]), + "operationalizations": _parse_operationalizations(root["operationalizations"]), + "analysis_plan": _parse_analysis_plan(root["analysis_plan"]), + "evidence": _parse_evidence(root["evidence"]), + "risk_register": _parse_risk_register(root["risk_register"]), + "ethics_and_feasibility": _parse_ethics(root["ethics_and_feasibility"]), + "ai_use": _parse_ai_use(root["ai_use"]), + } + + +def _missing_references( + values: list[str], valid_values: set[str], code: str, field: str +) -> list[dict[str, str]]: + return [ + issue(code, f"{field}:{value}") + for value in sorted(set(values) - valid_values) + ] + + +def validate_record(record: dict[str, Any]) -> dict[str, Any]: + """Check cross-links and safety declarations without scoring candidates.""" + errors: list[dict[str, str]] = [] + warnings: list[dict[str, str]] = [] + + hypothesis_ids = {item["hypothesis_id"] for item in record["hypotheses"]} + estimand_ids = {item["estimand_id"] for item in record["causal_estimands"]} + prediction_ids = {item["prediction_id"] for item in record["predictions"]} + measurement_ids = { + item["measurement_id"] for item in record["operationalizations"] + } + analysis_ids = { + item["analysis_id"] for item in record["analysis_plan"]["analyses"] + } + control_ids = {item["control_id"] for item in record["negative_controls"]} + source_ids = set(record["evidence"]["source_ids"]) + + used_sources = set(record["observation"]["source_ids"]) + for hypothesis in record["hypotheses"]: + used_sources.update(hypothesis["source_ids"]) + errors.extend( + _missing_references( + hypothesis["prediction_ids"], + prediction_ids, + "UNKNOWN_PREDICTION_ID", + hypothesis["hypothesis_id"], + ) + ) + errors.extend( + _missing_references( + hypothesis["rival_hypothesis_ids"], + hypothesis_ids, + "UNKNOWN_RIVAL_HYPOTHESIS_ID", + hypothesis["hypothesis_id"], + ) + ) + if hypothesis["hypothesis_id"] in hypothesis["rival_hypothesis_ids"]: + errors.append( + issue("HYPOTHESIS_CANNOT_RIVAL_ITSELF", hypothesis["hypothesis_id"]) + ) + if len(hypothesis_ids) > 1 and not hypothesis["rival_hypothesis_ids"]: + warnings.append( + issue("RIVAL_HYPOTHESIS_LINK_MISSING", hypothesis["hypothesis_id"]) + ) + for operationalization in record["operationalizations"]: + used_sources.update(operationalization["validity_evidence_source_ids"]) + errors.extend( + _missing_references( + sorted(used_sources), + source_ids, + "SOURCE_NOT_DECLARED_IN_EVIDENCE", + "record.evidence.source_ids", + ) + ) + + if len(hypothesis_ids) == 1: + warnings.append(issue("SINGLE_CANDIDATE_REQUIRES_RIVAL_REVIEW", "hypotheses")) + + for estimand in record["causal_estimands"]: + errors.extend( + _missing_references( + estimand["linked_hypothesis_ids"], + hypothesis_ids, + "UNKNOWN_HYPOTHESIS_ID", + estimand["estimand_id"], + ) + ) + if ( + record["research_question"]["question_type"] == "causal" + and not record["causal_estimands"] + ): + errors.append(issue("CAUSAL_QUESTION_REQUIRES_ESTIMAND", "causal_estimands")) + + predictions_by_hypothesis: Counter[str] = Counter() + for prediction in record["predictions"]: + prediction_id = prediction["prediction_id"] + predictions_by_hypothesis[prediction["hypothesis_id"]] += 1 + errors.extend( + _missing_references( + [prediction["hypothesis_id"]], + hypothesis_ids, + "UNKNOWN_HYPOTHESIS_ID", + prediction_id, + ) + ) + errors.extend( + _missing_references( + prediction["rival_hypothesis_ids"], + hypothesis_ids, + "UNKNOWN_RIVAL_HYPOTHESIS_ID", + prediction_id, + ) + ) + errors.extend( + _missing_references( + prediction["measurement_ids"], + measurement_ids, + "UNKNOWN_MEASUREMENT_ID", + prediction_id, + ) + ) + errors.extend( + _missing_references( + prediction["analysis_ids"], + analysis_ids, + "UNKNOWN_ANALYSIS_ID", + prediction_id, + ) + ) + if prediction["hypothesis_id"] in prediction["rival_hypothesis_ids"]: + errors.append(issue("PREDICTION_RIVAL_IS_FOCAL", prediction_id)) + for hypothesis_id in sorted(hypothesis_ids): + if predictions_by_hypothesis[hypothesis_id] == 0: + errors.append(issue("HYPOTHESIS_HAS_NO_PREDICTION", hypothesis_id)) + + for alternative in record["alternative_explanations"]: + errors.extend( + _missing_references( + alternative["linked_hypothesis_ids"], + hypothesis_ids, + "UNKNOWN_HYPOTHESIS_ID", + alternative["alternative_id"], + ) + ) + errors.extend( + _missing_references( + alternative["discriminating_prediction_ids"], + prediction_ids, + "UNKNOWN_PREDICTION_ID", + alternative["alternative_id"], + ) + ) + + for null in record["null_hypotheses"]: + errors.extend( + _missing_references( + null["linked_prediction_ids"], + prediction_ids, + "UNKNOWN_PREDICTION_ID", + null["null_id"], + ) + ) + + for control in record["negative_controls"]: + errors.extend( + _missing_references( + control["linked_prediction_ids"], + prediction_ids, + "UNKNOWN_PREDICTION_ID", + control["control_id"], + ) + ) + if not any( + control["control_type"] + in {"negative_exposure", "negative_outcome", "procedural_negative"} + for control in record["negative_controls"] + ): + warnings.append(issue("NEGATIVE_CONTROL_TYPE_REVIEW_REQUIRED", "controls")) + + for analysis in record["analysis_plan"]["analyses"]: + errors.extend( + _missing_references( + analysis["prediction_ids"], + prediction_ids, + "UNKNOWN_PREDICTION_ID", + analysis["analysis_id"], + ) + ) + errors.extend( + _missing_references( + analysis["estimand_ids"], + estimand_ids, + "UNKNOWN_ESTIMAND_ID", + analysis["analysis_id"], + ) + ) + + for category, entries in record["risk_register"].items(): + if not entries: + warnings.append(issue("RISK_CATEGORY_EMPTY_REQUIRES_RATIONALE", category)) + + ethics = record["ethics_and_feasibility"] + gate_values = { + key: value for key, value in ethics.items() if key.endswith("_gate") + } + unresolved_gate_names = sorted( + key + for key, value in gate_values.items() + if value in {"undetermined", "requires_review", "blocked"} + ) + if unresolved_gate_names and not ethics["unresolved_blocks"]: + errors.append( + issue("UNRESOLVED_GATE_REQUIRES_BLOCK_RECORD", "ethics_and_feasibility") + ) + if ( + any(value == "requires_review" for value in gate_values.values()) + and not ethics["required_reviews"] + ): + errors.append( + issue("REQUIRED_REVIEW_LIST_MISSING", "ethics_and_feasibility") + ) + if ethics["feasibility_status"] in {"undetermined", "infeasible", "blocked"}: + unresolved_gate_names.append("feasibility_status") + + ai_use = record["ai_use"] + if ai_use["sensitive_or_unpublished_data_sent_externally"]: + errors.append( + issue( + "EXTERNAL_SENSITIVE_DATA_NOT_SUPPORTED", + "ai_use.sensitive_or_unpublished_data_sent_externally", + ) + ) + if not ai_use["local_policy_checked"]: + errors.append(issue("LOCAL_AI_POLICY_NOT_CHECKED", "ai_use.local_policy_checked")) + if not ai_use["citation_verification_required"]: + errors.append( + issue( + "CITATION_VERIFICATION_MUST_BE_REQUIRED", + "ai_use.citation_verification_required", + ) + ) + if not ai_use["human_accountable"]: + errors.append( + issue("HUMAN_ACCOUNTABILITY_REQUIRED", "ai_use.human_accountable") + ) + + return { + "schema_version": "2.0", + "project_id": record["project_id"], + "valid": not errors, + "status": ( + "INVALID_RECORD" + if errors + else "VALID_BLOCKED_BY_GATES" + if unresolved_gate_names + else "VALID_FOR_HUMAN_REVIEW" + ), + "errors": errors, + "warnings": warnings, + "counts": { + "hypotheses": len(hypothesis_ids), + "causal_estimands": len(estimand_ids), + "predictions": len(prediction_ids), + "alternative_explanations": len(record["alternative_explanations"]), + "null_hypotheses": len(record["null_hypotheses"]), + "negative_controls": len(control_ids), + "operationalizations": len(measurement_ids), + "analyses": len(analysis_ids), + "declared_sources": len(source_ids), + }, + "identifiers": { + "hypothesis_ids": sorted(hypothesis_ids), + "estimand_ids": sorted(estimand_ids), + "prediction_ids": sorted(prediction_ids), + "measurement_ids": sorted(measurement_ids), + "analysis_ids": sorted(analysis_ids), + "control_ids": sorted(control_ids), + }, + "unresolved_gate_fields": sorted(set(unresolved_gate_names)), + "notice": ( + "This report validates schema, cross-references, and declared safety " + "controls only. It does not verify evidence, scientific validity, " + "novelty, ethics approval, causal identification, or hypothesis merit, " + "and it does not rank or select candidates." + ), + } + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description=( + "Validate a bounded local hypothesis JSON record and emit identifiers, " + "counts, and rule codes without scientific scoring." + ) + ) + parser.add_argument("record", help="Local hypothesis record JSON") + parser.add_argument("-o", "--output", help="Optional local JSON report") + parser.add_argument( + "--force", action="store_true", help="Replace an existing output file" + ) + return parser + + +def main() -> int: + args = build_parser().parse_args() + try: + record = load_hypothesis_record(read_json(args.record)) + report = validate_record(record) + write_json_report(report, args.output, force=args.force) + return 0 if report["valid"] else 1 + except ValidationError as exc: + return error_exit(exc) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/scripts/validate_prediction_matrix.py b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/scripts/validate_prediction_matrix.py new file mode 100644 index 0000000..7b0dd7a --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/hypothesis-generation/scripts/validate_prediction_matrix.py @@ -0,0 +1,286 @@ +#!/usr/bin/env python3 +"""Validate a prediction/rival-hypothesis CSV without ranking candidates.""" + +from __future__ import annotations + +import argparse +import re +from collections import Counter +from typing import Any + +from _common import ( + ValidationError, + error_exit, + issue, + read_csv_records, + read_json, + require_identifier, + require_text, + require_unique, + split_identifiers, + write_json_report, +) +from validate_hypothesis_schema import load_hypothesis_record, validate_record + +FIELDS = ( + "prediction_id", + "hypothesis_id", + "rival_hypothesis_ids", + "conditions", + "observable", + "expected_if_focal", + "expected_if_rivals", + "falsifier", + "indeterminate_result", + "boundary_conditions", + "measurement_ids", + "negative_control_ids", + "analysis_ids", + "uncertainty", +) + + +def load_matrix(raw_path: str) -> list[dict[str, Any]]: + rows = read_csv_records(raw_path, fields=FIELDS) + parsed: list[dict[str, Any]] = [] + prediction_ids: list[str] = [] + for line_number, row in enumerate(rows, start=2): + context = f"matrix row {line_number}" + prediction_id = require_identifier( + row["prediction_id"], f"{context}.prediction_id" + ) + prediction_ids.append(prediction_id) + parsed.append( + { + "prediction_id": prediction_id, + "hypothesis_id": require_identifier( + row["hypothesis_id"], f"{context}.hypothesis_id" + ), + "rival_hypothesis_ids": split_identifiers( + row["rival_hypothesis_ids"], + f"{context}.rival_hypothesis_ids", + ), + "conditions": require_text( + row["conditions"], f"{context}.conditions", minimum=5 + ), + "observable": require_text( + row["observable"], f"{context}.observable", minimum=5 + ), + "expected_if_focal": require_text( + row["expected_if_focal"], + f"{context}.expected_if_focal", + minimum=5, + ), + "expected_if_rivals": require_text( + row["expected_if_rivals"], + f"{context}.expected_if_rivals", + minimum=5, + ), + "falsifier": require_text( + row["falsifier"], f"{context}.falsifier", minimum=10 + ), + "indeterminate_result": require_text( + row["indeterminate_result"], + f"{context}.indeterminate_result", + minimum=10, + ), + "boundary_conditions": require_text( + row["boundary_conditions"], + f"{context}.boundary_conditions", + minimum=5, + ), + "measurement_ids": split_identifiers( + row["measurement_ids"], f"{context}.measurement_ids" + ), + "negative_control_ids": split_identifiers( + row["negative_control_ids"], + f"{context}.negative_control_ids", + ), + "analysis_ids": split_identifiers( + row["analysis_ids"], f"{context}.analysis_ids" + ), + "uncertainty": require_text( + row["uncertainty"], f"{context}.uncertainty", minimum=10 + ), + } + ) + require_unique(prediction_ids, "prediction matrix") + return parsed + + +def _normalized_expectation(value: str) -> str: + return re.sub(r"[^a-z0-9]+", " ", value.casefold()).strip() + + +def _unknown( + values: list[str], allowed: set[str], code: str, field: str +) -> list[dict[str, str]]: + return [ + issue(code, f"{field}:{value}") + for value in sorted(set(values) - allowed) + ] + + +def validate_matrix( + rows: list[dict[str, Any]], record: dict[str, Any] | None = None +) -> dict[str, Any]: + errors: list[dict[str, str]] = [] + warnings: list[dict[str, str]] = [] + hypothesis_counts: Counter[str] = Counter() + + record_ids: dict[str, set[str]] | None = None + if record is not None: + record_report = validate_record(record) + if not record_report["valid"]: + raise ValidationError( + "optional hypothesis record must pass schema validation first" + ) + record_ids = { + "hypotheses": { + item["hypothesis_id"] for item in record["hypotheses"] + }, + "predictions": { + item["prediction_id"] for item in record["predictions"] + }, + "measurements": { + item["measurement_id"] for item in record["operationalizations"] + }, + "controls": { + item["control_id"] for item in record["negative_controls"] + }, + "analyses": { + item["analysis_id"] for item in record["analysis_plan"]["analyses"] + }, + } + + for row in rows: + prediction_id = row["prediction_id"] + hypothesis_id = row["hypothesis_id"] + hypothesis_counts[hypothesis_id] += 1 + if hypothesis_id in row["rival_hypothesis_ids"]: + errors.append(issue("FOCAL_HYPOTHESIS_LISTED_AS_RIVAL", prediction_id)) + if ( + _normalized_expectation(row["expected_if_focal"]) + == _normalized_expectation(row["expected_if_rivals"]) + ): + errors.append(issue("FOCAL_AND_RIVAL_EXPECTATIONS_IDENTICAL", prediction_id)) + if not row["negative_control_ids"]: + warnings.append(issue("NEGATIVE_CONTROL_LINK_MISSING", prediction_id)) + + if record_ids is not None: + errors.extend( + _unknown( + [hypothesis_id], + record_ids["hypotheses"], + "UNKNOWN_HYPOTHESIS_ID", + prediction_id, + ) + ) + errors.extend( + _unknown( + row["rival_hypothesis_ids"], + record_ids["hypotheses"], + "UNKNOWN_RIVAL_HYPOTHESIS_ID", + prediction_id, + ) + ) + errors.extend( + _unknown( + [prediction_id], + record_ids["predictions"], + "UNKNOWN_PREDICTION_ID", + prediction_id, + ) + ) + errors.extend( + _unknown( + row["measurement_ids"], + record_ids["measurements"], + "UNKNOWN_MEASUREMENT_ID", + prediction_id, + ) + ) + errors.extend( + _unknown( + row["negative_control_ids"], + record_ids["controls"], + "UNKNOWN_CONTROL_ID", + prediction_id, + ) + ) + errors.extend( + _unknown( + row["analysis_ids"], + record_ids["analyses"], + "UNKNOWN_ANALYSIS_ID", + prediction_id, + ) + ) + record_prediction = next( + ( + item + for item in record["predictions"] + if item["prediction_id"] == prediction_id + ), + None, + ) + if ( + record_prediction is not None + and record_prediction["hypothesis_id"] != hypothesis_id + ): + errors.append( + issue("PREDICTION_HYPOTHESIS_MISMATCH", prediction_id) + ) + + return { + "schema_version": "2.0", + "valid": not errors, + "status": "INVALID_MATRIX" if errors else "VALID_FOR_HUMAN_REVIEW", + "errors": errors, + "warnings": warnings, + "prediction_count": len(rows), + "hypothesis_prediction_counts": dict(sorted(hypothesis_counts.items())), + "prediction_ids": sorted(row["prediction_id"] for row in rows), + "record_cross_check_performed": record is not None, + "notice": ( + "This report checks CSV structure, identifiers, declared contrasts, " + "and optional cross-links. It cannot determine whether a prediction " + "is scientifically discriminating, sufficiently precise, or likely, " + "and it never ranks or selects a hypothesis." + ), + } + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description=( + "Validate a bounded local prediction/rival CSV and emit identifiers " + "and rule codes without scientific scoring." + ) + ) + parser.add_argument("matrix", help="Local prediction/rival matrix CSV") + parser.add_argument( + "--record", help="Optional local hypothesis record JSON for cross-checks" + ) + parser.add_argument("-o", "--output", help="Optional local JSON report") + parser.add_argument( + "--force", action="store_true", help="Replace an existing output file" + ) + return parser + + +def main() -> int: + args = build_parser().parse_args() + try: + record = ( + load_hypothesis_record(read_json(args.record)) if args.record else None + ) + report = validate_matrix(load_matrix(args.matrix), record) + write_json_report(report, args.output, force=args.force) + return 0 if report["valid"] else 1 + except ValidationError as exc: + return error_exit(exc) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/SKILL.md b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/SKILL.md new file mode 100644 index 0000000..2826f85 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/SKILL.md @@ -0,0 +1,288 @@ +--- +name: peer-review +description: Prepare evidence-bounded, constructive peer-review drafts and structured manuscript assessments. Use for authorized review of scientific manuscripts, protocols, preprints, or research proposals; reporting-guideline selection; claim–evidence checks; methods, statistics, reproducibility, ethics, figure/table, and citation critique; or revision-response planning. +license: MIT +compatibility: Python 3.11+ standard library. Bundled CLIs are deterministic and local-only; they accept bounded JSON, CSV, or Markdown and make no network, model, image, or external-service calls. +metadata: + version: "2.1" + skill-author: K-Dense Inc. +--- + +# Peer Review + +Support an accountable human reviewer with a rigorous, fair, actionable assessment. Treat every unpublished submission and review as confidential. + +## Mandatory safety boundary + +Before reading or analyzing unpublished content: + +1. Confirm the user is authorized by the publisher, editor, author, or other material owner. +2. Check the target venue’s review, confidentiality, co-review, retention, and AI/tool policies. +3. Record conflicts, competence limits, requested scope, and specialist-review needs. +4. Default to local-only processing. + +If authorization is unclear, do not inspect or quote the manuscript. Ask for confirmation or use only the bundled local CLIs, whose reports do not echo manuscript text. + +Never: + +- Send unpublished manuscript, supplement, review, or editorial text to an external service without specific publisher/author authorization and venue permission +- Upload confidential content to a public model, search engine, citation service, grammar tool, plagiarism checker, or image service +- Reuse content for training, benchmarking, product improvement, or unrelated research +- Read broad environment state, `.env` files, API keys, or credentials +- Call a network, LLM, or image API from bundled tools +- Invoke another skill or a PDF/image pipeline automatically +- Impersonate an assigned reviewer, editor, journal, funder, or author +- Fabricate manuscript details, review findings, citations, analyses, experiments, reproduction, or an editorial outcome +- Announce a decision that belongs to an editor or panel + +Delete local copies and derivatives when policy requires; otherwise retain only what the controlling policy authorizes. Record deletion or retention without copying confidential content into the record. + +Read `references/ethical_review_practice.md` before handling confidential material. + +## Human accountability + +Label generated text as a working draft. The accountable human must: + +- Read the complete authorized submission and relevant supplements +- Verify every factual statement, calculation, citation, and manuscript location +- Resolve conflicts and disclose assistance as required +- Rewrite comments in their own expert judgment +- Submit through the authorized channel + +Automated coverage, consistency, or lint results are not peer review and do not establish manuscript merit. + +## Intake gate + +Copy and complete `assets/review_intake_template.json`, then run: + +```bash +python3 scripts/validate_review_intake.py completed-intake.json +``` + +Proceed only when status is `READY_FOR_LOCAL_REVIEW`. + +The validator blocks: + +- Undocumented authorization +- Missing human accountability +- Unassessed or unresolved conflicts +- Unknown review model or unchecked venue policy +- Unauthorized AI assistance +- External service use +- Data reuse +- Missing deletion/retention planning + +It validates declarations, not their truth. + +## Review workflow + +### 1. Establish scope and available evidence + +Record: + +- Submission type and stage +- Review question and requested focus +- Target venue and review model +- Materials actually available: manuscript, supplements, protocol, registration, analysis plan, data/code statement, prior decision, or response letter +- Competence areas and limits +- Missing material that prevents assessment + +Do not infer absent content. Use “not reported” or “not available for review.” + +### 2. Orient without deciding + +Create a short neutral map: + +- Research question +- Population or system +- Design and unit +- Intervention, exposure, test, or model +- Comparator/reference +- Outcomes and timing +- Principal claims + +Do not write an acceptance/rejection recommendation. Identify what evidence would be needed to evaluate each claim. + +### 3. Select reporting guidance + +Copy `assets/study_profile_template.json` and run: + +```bash +python3 scripts/select_reporting_guidelines.py local-profile.json +``` + +For checklist coverage: + +```bash +python3 scripts/select_reporting_guidelines.py \ + local-profile.json \ + --coverage local-coverage.csv +``` + +Use the current base guideline, explanation/elaboration, applicable extensions, and target venue policy. See `references/reporting_standards.md`. + +**Critical distinction:** reporting completeness is not design quality, risk of bias, validity, or merit. Never convert missing items into an automatic score or publication judgment. + +### 4. Map claims to evidence + +Prioritize central, causal, mechanistic, safety, diagnostic, prediction, and generalization claims. + +For each claim, record: + +- Location and claim ID +- Supporting result, figure, table, analysis, or citation IDs +- Direction, magnitude, population, outcome, timepoint, and uncertainty alignment +- Limitation or alternative explanation +- Bounded requested action + +Run: + +```bash +python3 scripts/validate_claim_evidence.py local-claim-matrix.csv +``` + +Start from `assets/claim_evidence_matrix_template.csv`. The report emits IDs and counts, not claim text. + +### 5. Review methods and statistics + +Assess in this order: + +1. Question and target quantity +2. Design and unit of inference +3. Sampling, allocation, controls, masking, and timing +4. Sample-size or precision rationale +5. Inclusion, exclusion, attrition, and missingness +6. Analysis–design alignment and assumptions +7. Multiplicity and prespecification +8. Effect estimates, uncertainty, denominators, and harms +9. Interpretation, causality, and generalizability + +Use `references/common_issues.md` and `references/statistical_reproducibility.md`. + +For a structured local audit: + +```bash +python3 scripts/audit_statistics_reproducibility.py \ + local-statistics-reproducibility.json +``` + +Start from `assets/statistical_reproducibility_template.json`. Request specialist review when a central method exceeds competence; do not hide uncertainty behind a generic critique. + +### 6. Review reproducibility and transparency + +Check, as applicable: + +- Protocol, registration, amendments, and analysis-plan consistency +- Data provenance, exclusions, transformations, and accession IDs +- Software, package, model, and parameter versions +- Code, environment, seeds, run instructions, and tests +- Data, code, materials, and model availability or justified restrictions +- Domain metadata standards + +Do not claim reproduction unless authorized inputs were actually run with documented commands, environment, and outputs. + +### 7. Review ethics and integrity + +Check applicable approvals, consent, welfare, privacy, community governance, funding, sponsor role, conflicts, authorship/contribution, registration, biosafety, and dual-use concerns. + +Describe observable evidence and uncertainty. Do not accuse authors or investigate them. Route credible concerns through the confidential editor channel under venue policy. + +### 8. Review figures, tables, and citations + +For figures and tables, assess: + +- Consistency with text and supplements +- Denominators, units, axes, scales, uncertainty, and legends +- Accessible encoding and sufficient context +- Image acquisition/processing disclosure and source-data policy + +This skill has no image-generation or PDF-conversion workflow. Use only user-authorized local artifacts and tools. + +For Pandoc-style citations such as `[@ref-id]`: + +```bash +python3 scripts/audit_citations.py local-manuscript.md local-references.csv +``` + +Start from `assets/citation_references_template.csv`. This checks key consistency and identifier format only; it does not verify that a source exists or supports a claim. + +### 9. Draft actionable comments + +Generate a private scaffold only after intake passes: + +```bash +python3 scripts/generate_review_scaffold.py \ + completed-intake.json \ + -o private-review.md +``` + +Every major/minor comment should include: + +- **Location** +- **Observation** +- **Evidence or criterion** +- **Why it matters** +- **Requested action** + +Prioritize: + +- Claim–evidence alignment +- Methods and statistical validity +- Reproducibility and transparency +- Ethics and participant/animal protection +- Reporting needed for appraisal +- Figures, tables, limitations, and citations + +Requests for new work must be necessary to support a central claim and proportionate to scope. Offer narrowing, clarification, sensitivity analysis, correction, or limitation language when that is sufficient. + +### 10. Keep channels separate + +**Comments to authors** contain the scientific review, strengths, major/minor comments, and limitations. + +**Confidential comments to editor** contain only policy-appropriate conflicts, competence limits, assistance disclosure, specialist requests, or substantiated integrity/process concerns that require a separate route. + +Do not place ordinary criticism only in confidential notes. Do not reveal reviewer identity under an anonymized process. + +### 11. Lint and finalize + +```bash +python3 scripts/lint_review.py private-review.md +``` + +The linter checks channel separation, unresolved placeholders, a narrow abusive-language lexicon, role/decision phrases, and required actionability fields. It emits line numbers and rule IDs, not review text. Human tone and scientific review remain mandatory. + +Before handoff: + +- Verify all locations and evidence. +- Remove unsupported or speculative criticism. +- Confirm professional, non-abusive language. +- State review limits and specialist needs. +- Disclose permitted assistance. +- Remove all placeholders. +- Ensure no invented citation, experiment, reanalysis, or outcome. +- Follow the documented deletion/retention rule. + +## Local tool index + +- `scripts/validate_review_intake.py` — scope, authorization, conflicts, policy, handling +- `scripts/select_reporting_guidelines.py` — dated selector and non-scoring coverage audit +- `scripts/validate_claim_evidence.py` — claim/evidence alignment matrix +- `scripts/audit_statistics_reproducibility.py` — methods/statistics/reproducibility checklist +- `scripts/audit_citations.py` — local citation/reference consistency +- `scripts/generate_review_scaffold.py` — separated private Markdown scaffold +- `scripts/lint_review.py` — tone, channel, and actionability lint + +Full schemas and exit codes: `references/tool_reference.md`. + +## References and assets + +- `references/ethical_review_practice.md` — COPE/ICMJE duties, confidentiality, AI, channels +- `references/reporting_standards.md` — current major guidelines and verified domain standards +- `references/statistical_reproducibility.md` — methods, statistics, and reproducibility review +- `references/common_issues.md` — contextual issue patterns and constructive responses +- `references/security_validation.md` — baseline remediation and local scan results +- `assets/source_ledger.csv` — authoritative sources verified 2026-07-23 +- `assets/reporting_guidelines.json` — local selector catalog +- `assets/review_scaffold_template.md` — private structured draft + +The source ledger is dated. Recheck live primary sources and the target venue policy for a later review, without exposing confidential manuscript text in search queries. diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/citation_references_template.csv b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/citation_references_template.csv new file mode 100644 index 0000000..ccefdaa --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/citation_references_template.csv @@ -0,0 +1,2 @@ +reference_id,title,authors,year,doi,url,verification_status +ref-synthetic-2026,Synthetic methods note for local testing,Example Research Group,2026,,https://example.invalid/synthetic,not_verified diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/claim_evidence_matrix_template.csv b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/claim_evidence_matrix_template.csv new file mode 100644 index 0000000..df23fb2 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/claim_evidence_matrix_template.csv @@ -0,0 +1,4 @@ +claim_id,location,claim_type,claim_summary,evidence_ids,support_level,alignment_issue,limitation,requested_action +CLAIM-SYN-001,Abstract:results,primary_outcome,Synthetic primary outcome claim for structural testing,FIG-SYN-001;syn-analysis-001,supported,none,, +CLAIM-SYN-002,Discussion:paragraph-2,generalization,Synthetic broad generalization claim for structural testing,TAB-SYN-001,partly_supported,population,Synthetic sample does not represent the stated target population,Narrow the claim to the studied population or provide justified external evidence +CLAIM-SYN-003,Results:paragraph-4,safety,Synthetic safety claim awaiting evidence mapping,,not_assessed,none,,Map the claim to adverse-event denominators and uncertainty before review diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/reporting_checklist_template.csv b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/reporting_checklist_template.csv new file mode 100644 index 0000000..7650575 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/reporting_checklist_template.csv @@ -0,0 +1,31 @@ +guideline_id,item_id,status,location,rationale +CONSORT-2025,1,not_assessed,, +CONSORT-2025,2,not_assessed,, +CONSORT-2025,3,not_assessed,, +CONSORT-2025,4,not_assessed,, +CONSORT-2025,5,not_assessed,, +CONSORT-2025,6,not_assessed,, +CONSORT-2025,7,not_assessed,, +CONSORT-2025,8,not_assessed,, +CONSORT-2025,9,not_assessed,, +CONSORT-2025,10,not_assessed,, +CONSORT-2025,11,not_assessed,, +CONSORT-2025,12,not_assessed,, +CONSORT-2025,13,not_assessed,, +CONSORT-2025,14,not_assessed,, +CONSORT-2025,15,not_assessed,, +CONSORT-2025,16,not_assessed,, +CONSORT-2025,17,not_assessed,, +CONSORT-2025,18,not_assessed,, +CONSORT-2025,19,not_assessed,, +CONSORT-2025,20,not_assessed,, +CONSORT-2025,21,not_assessed,, +CONSORT-2025,22,not_assessed,, +CONSORT-2025,23,not_assessed,, +CONSORT-2025,24,not_assessed,, +CONSORT-2025,25,not_assessed,, +CONSORT-2025,26,not_assessed,, +CONSORT-2025,27,not_assessed,, +CONSORT-2025,28,not_assessed,, +CONSORT-2025,29,not_assessed,, +CONSORT-2025,30,not_assessed,, diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/reporting_guidelines.json b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/reporting_guidelines.json new file mode 100644 index 0000000..1d45f29 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/reporting_guidelines.json @@ -0,0 +1,466 @@ +{ + "schema_version": "2.0", + "reviewed_on": "2026-07-23", + "notice": "This catalog supports reporting-guideline selection only. Verify the official source, applicable extensions, and target venue policy. Checklist coverage is not a quality or merit score.", + "guidelines": [ + { + "id": "ARRIVE-2.0", + "name": "ARRIVE", + "version": "2.0 (2020)", + "status": "current", + "category": "reporting_guideline", + "study_types": [ + "animal_in_vivo" + ], + "report_kinds": [ + "results" + ], + "required_features": [], + "domains": [ + "all" + ], + "main_item_count": 21, + "url": "https://arriveguidelines.org/arrive-guidelines", + "notes": "Use Essential 10 and Recommended Set; reporting supports appraisal but does not by itself establish rigor." + }, + { + "id": "CARE-2013", + "name": "CARE", + "version": "2013 checklist; 2017 explanation", + "status": "current", + "category": "reporting_guideline", + "study_types": [ + "case_report" + ], + "report_kinds": [ + "results" + ], + "required_features": [], + "domains": [ + "health" + ], + "main_item_count": 13, + "url": "https://www.care-statement.org/checklist", + "notes": "Confirm informed consent and privacy requirements with the target venue and applicable law." + }, + { + "id": "CHEERS-2022", + "name": "CHEERS", + "version": "2022", + "status": "current", + "category": "reporting_guideline", + "study_types": [ + "economic_evaluation" + ], + "report_kinds": [ + "results" + ], + "required_features": [], + "domains": [ + "health" + ], + "main_item_count": 28, + "url": "https://www.ispor.org/heor-resources/good-practices/cheers", + "notes": "For economic evaluations of health interventions; use the explanation and elaboration." + }, + { + "id": "CONSORT-2025", + "name": "CONSORT", + "version": "2025", + "status": "current", + "category": "reporting_guideline", + "study_types": [ + "randomized_trial" + ], + "report_kinds": [ + "results" + ], + "required_features": [], + "domains": [ + "health", + "all" + ], + "main_item_count": 30, + "url": "https://www.consort-spirit.org/", + "notes": "Supersedes CONSORT 2010. Check design-specific extensions; CONSORT is not a quality assessment instrument." + }, + { + "id": "CONSORT-AI-2020", + "name": "CONSORT-AI", + "version": "2020 extension", + "status": "current", + "category": "reporting_guideline", + "study_types": [ + "randomized_trial" + ], + "report_kinds": [ + "results" + ], + "required_features": [ + "ai_intervention" + ], + "domains": [ + "health" + ], + "main_item_count": null, + "url": "https://www.equator-network.org/reporting-guidelines/consort-artificial-intelligence/", + "notes": "Use with CONSORT 2025 for randomized trials evaluating an AI intervention; verify whether updated extension guidance applies." + }, + { + "id": "COREQ-2007", + "name": "COREQ", + "version": "2007", + "status": "current", + "category": "reporting_guideline", + "study_types": [ + "qualitative_interview", + "qualitative_focus_group" + ], + "report_kinds": [ + "results" + ], + "required_features": [], + "domains": [ + "health" + ], + "main_item_count": 32, + "url": "https://www.equator-network.org/reporting-guidelines/coreq/", + "notes": "For interviews and focus groups; consider SRQR for broader qualitative study designs." + }, + { + "id": "PRISMA-2020", + "name": "PRISMA", + "version": "2020 (published 2021)", + "status": "current", + "category": "reporting_guideline", + "study_types": [ + "systematic_review" + ], + "report_kinds": [ + "results" + ], + "required_features": [], + "domains": [ + "all" + ], + "main_item_count": 27, + "url": "https://www.prisma-statement.org/prisma-2020", + "notes": "Use the relevant extension for scoping, diagnostic, individual-participant-data, or other review types. PRISMA does not assess methodological quality." + }, + { + "id": "PRISMA-P-2015", + "name": "PRISMA-P", + "version": "2015", + "status": "current", + "category": "reporting_guideline", + "study_types": [ + "systematic_review" + ], + "report_kinds": [ + "protocol" + ], + "required_features": [], + "domains": [ + "all" + ], + "main_item_count": null, + "url": "https://www.equator-network.org/reporting-guidelines/prisma-protocols/", + "notes": "Use for systematic review protocols rather than completed review reports." + }, + { + "id": "SPIRIT-2025", + "name": "SPIRIT", + "version": "2025", + "status": "current", + "category": "reporting_guideline", + "study_types": [ + "randomized_trial" + ], + "report_kinds": [ + "protocol" + ], + "required_features": [], + "domains": [ + "health", + "all" + ], + "main_item_count": 34, + "url": "https://www.consort-spirit.org/", + "notes": "Supersedes SPIRIT 2013. Compare protocol, registry, statistical analysis plan, amendments, and final report for consistency." + }, + { + "id": "SQUIRE-2.0", + "name": "SQUIRE", + "version": "2.0 (2015)", + "status": "current", + "category": "reporting_guideline", + "study_types": [ + "quality_improvement" + ], + "report_kinds": [ + "results" + ], + "required_features": [], + "domains": [ + "health" + ], + "main_item_count": 18, + "url": "https://www.squire-statement.org/index.cfm?fuseaction=page.viewPage&pageID=471&nodeID=1", + "notes": "For system-level work to improve healthcare; not every element is applicable to every manuscript." + }, + { + "id": "SRQR-2014", + "name": "SRQR", + "version": "2014", + "status": "current", + "category": "reporting_guideline", + "study_types": [ + "qualitative_study" + ], + "report_kinds": [ + "results" + ], + "required_features": [], + "domains": [ + "health" + ], + "main_item_count": 21, + "url": "https://www.equator-network.org/reporting-guidelines/srqr", + "notes": "Broad qualitative reporting standard; choose COREQ when interviews or focus groups are the primary methods." + }, + { + "id": "STARD-2015", + "name": "STARD", + "version": "2015", + "status": "current", + "category": "reporting_guideline", + "study_types": [ + "diagnostic_accuracy" + ], + "report_kinds": [ + "results" + ], + "required_features": [], + "domains": [ + "health" + ], + "main_item_count": 30, + "url": "https://www.equator-network.org/reporting-guidelines/stard/", + "notes": "For diagnostic accuracy studies. Use a separate risk-of-bias tool for design/conduct appraisal." + }, + { + "id": "STARD-AI-2025", + "name": "STARD-AI", + "version": "2025", + "status": "current", + "category": "reporting_guideline", + "study_types": [ + "diagnostic_accuracy" + ], + "report_kinds": [ + "results" + ], + "required_features": [ + "ai_based" + ], + "domains": [ + "health" + ], + "main_item_count": 40, + "url": "https://www.nature.com/articles/s41591-025-03953-8", + "notes": "Use for AI-centered diagnostic accuracy studies and alongside the applicable base and modality guidance." + }, + { + "id": "STROBE-2007", + "name": "STROBE", + "version": "2007", + "status": "current", + "category": "reporting_guideline", + "study_types": [ + "observational_study", + "cohort_study", + "case_control_study", + "cross_sectional_study" + ], + "report_kinds": [ + "results" + ], + "required_features": [], + "domains": [ + "health" + ], + "main_item_count": 22, + "url": "https://www.strobe-statement.org/", + "notes": "Select the design-specific checklist and applicable extensions such as RECORD, STREGA, or STROBE-MR." + }, + { + "id": "TRIPOD-AI-2024", + "name": "TRIPOD+AI", + "version": "2024", + "status": "current", + "category": "reporting_guideline", + "study_types": [ + "prediction_model" + ], + "report_kinds": [ + "results" + ], + "required_features": [], + "domains": [ + "health" + ], + "main_item_count": 27, + "url": "https://www.tripod-statement.org/", + "notes": "Replaces TRIPOD 2015 and applies to regression or machine-learning model development and performance evaluation." + }, + { + "id": "TRIPOD-LLM-2025", + "name": "TRIPOD-LLM", + "version": "2025", + "status": "current", + "category": "reporting_guideline", + "study_types": [ + "prediction_model" + ], + "report_kinds": [ + "results" + ], + "required_features": [ + "large_language_model" + ], + "domains": [ + "health" + ], + "main_item_count": 19, + "url": "https://www.equator-network.org/reporting-guidelines/the-tripod-llm-reporting-guideline-for-studies-using-large-language-models/", + "notes": "Use with TRIPOD+AI for studies of large language models in biomedicine and healthcare." + }, + { + "id": "MIAME-2001", + "name": "MIAME", + "version": "2001 content standard; repository guidance current", + "status": "legacy_current_qualified", + "category": "domain_metadata_standard", + "study_types": [ + "microarray" + ], + "report_kinds": [ + "results", + "data_release" + ], + "required_features": [], + "domains": [ + "functional_genomics" + ], + "main_item_count": null, + "url": "https://www.ncbi.nlm.nih.gov/geo/info/MIAME.html", + "notes": "Retained because GEO and ArrayExpress still implement it; verify current repository submission fields." + }, + { + "id": "MIAPE-CURRENT", + "name": "MIAPE", + "version": "modular released components", + "status": "legacy_current_qualified", + "category": "domain_metadata_standard", + "study_types": [ + "proteomics" + ], + "report_kinds": [ + "results", + "data_release" + ], + "required_features": [], + "domains": [ + "proteomics" + ], + "main_item_count": null, + "url": "https://www.psidev.info/miape", + "notes": "Select the current PSI component for the actual workflow; MIAPE is modular, not one universal checklist." + }, + { + "id": "MIAPPE-1.2", + "name": "MIAPPE", + "version": "1.2 (October 2024)", + "status": "current", + "category": "domain_metadata_standard", + "study_types": [ + "plant_phenotyping" + ], + "report_kinds": [ + "results", + "data_release" + ], + "required_features": [], + "domains": [ + "plant_science" + ], + "main_item_count": null, + "url": "https://www.miappe.org/releases", + "notes": "Use version 1.2; version 2.0 was still under early development at the review date." + }, + { + "id": "MIFlowCyt-1.0", + "name": "MIFlowCyt", + "version": "1.0; ISAC recommendation current", + "status": "legacy_current_qualified", + "category": "domain_metadata_standard", + "study_types": [ + "flow_cytometry" + ], + "report_kinds": [ + "results", + "data_release" + ], + "required_features": [], + "domains": [ + "cytometry" + ], + "main_item_count": null, + "url": "https://isac-net.org/miflowcyt-2/", + "notes": "Retained as an active ISAC recommendation; also verify FCS, gating, and repository requirements." + }, + { + "id": "MINSEQE-CURRENT", + "name": "MINSEQE", + "version": "repository guidance current", + "status": "legacy_current_qualified", + "category": "domain_metadata_standard", + "study_types": [ + "high_throughput_sequencing" + ], + "report_kinds": [ + "results", + "data_release" + ], + "required_features": [], + "domains": [ + "functional_genomics" + ], + "main_item_count": null, + "url": "https://www.ncbi.nlm.nih.gov/geo/info/MIAME.html", + "notes": "Retained because GEO and ArrayExpress still implement it; verify platform-specific repository requirements." + }, + { + "id": "MIxS-CURRENT", + "name": "MIxS", + "version": "current GSC release", + "status": "current", + "category": "domain_metadata_standard", + "study_types": [ + "genome_sequence", + "metagenome_sequence" + ], + "report_kinds": [ + "results", + "data_release" + ], + "required_features": [], + "domains": [ + "genomics" + ], + "main_item_count": null, + "url": "https://www.gensc.org/pages/standards-intro.html", + "notes": "Use MIxS as the current umbrella; MIGS and MIMS are legacy checklists within the MIxS framework." + } + ] +} diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/review_intake_template.json b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/review_intake_template.json new file mode 100644 index 0000000..a108884 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/review_intake_template.json @@ -0,0 +1,52 @@ +{ + "schema_version": "2.0", + "review_id": "REVIEW-LOCAL-001", + "material": { + "status": "unpublished_confidential", + "contains_personal_or_sensitive_data": false + }, + "authorization": { + "basis": "journal_invitation", + "documented": false, + "local_processing_authorized": false, + "external_processing_authorized": false + }, + "reviewer": { + "capacity": "assigned_reviewer", + "human_accountable": false, + "competence_areas": [ + "subject_matter" + ], + "competence_limits": [], + "conflict_status": "not_assessed", + "conflicts": [] + }, + "venue_policy": { + "checked": false, + "peer_review_model": "unknown", + "confidential_editor_notes_supported": false + }, + "ai_use": { + "policy": "not_stated", + "planned": "local_deterministic_tools", + "permission_confirmed": false, + "disclosure_planned": false + }, + "handling": { + "local_only": true, + "external_service_use": false, + "data_reuse_permitted": false, + "retention_rule": "delete_after_review", + "deletion_or_retention_record_planned": false + }, + "scope": { + "manuscript_type": "randomized_trial", + "requested_focus": [ + "methods", + "statistics", + "reporting" + ], + "out_of_scope": [], + "specialist_review_needed": [] + } +} diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/review_scaffold_template.md b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/review_scaffold_template.md new file mode 100644 index 0000000..dfbae11 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/review_scaffold_template.md @@ -0,0 +1,68 @@ +# Peer-review working draft — {{REVIEW_ID}} + +> Private working document. Human review, policy checks, and factual verification are required. Do not submit this scaffold with unresolved placeholders. Do not make or announce an editorial decision. + +## Intake record + +- Reviewer capacity: `{{REVIEWER_CAPACITY}}` +- Peer-review model: `{{PEER_REVIEW_MODEL}}` +- Declared processing plan: `{{AI_PLAN}}` +- Manuscript text is not embedded by the generator. +- Reconfirm conflicts, competence limits, tool use, confidentiality, and deletion or retention obligations before submission. + +# Comments to authors + +## Evidence-bounded summary + +[Write a neutral summary of the question, design, and principal claims. Do not invent findings or imply that unperformed analyses were run.] + +## Strengths + +[Identify specific strengths supported by a manuscript location or supplied material.] + +## Major comments + +### Major comment M1 + +- Location: [Section, page, line, figure, table, or claim ID] +- Observation: [State what is reported, missing, inconsistent, or unsupported] +- Evidence or criterion: [Cite manuscript evidence, a method principle, venue policy, or reporting item] +- Why it matters: [Explain the consequence for validity, interpretation, reproducibility, ethics, or reporting] +- Requested action: [Request clarification, correction, analysis, evidence, or a bounded revision] + +## Minor comments + +### Minor comment m1 + +- Location: [Section, page, line, figure, table, or reference ID] +- Observation: [State the local clarity, consistency, citation, figure, or reporting issue] +- Evidence or criterion: [Identify the relevant evidence or criterion] +- Why it matters: [Explain the reader-facing consequence] +- Requested action: [Request a specific correction or clarification] + +## Methods, statistics, and reproducibility + +[Summarize only assessed issues. Distinguish reporting gaps from design or analysis concerns and state where specialist review is needed.] + +## Ethics, transparency, figures, tables, and citations + +[Record specific, evidence-backed issues. Do not allege misconduct; route credible integrity concerns through the journal process.] + +## Limitations of this review + +[State competence limits, unavailable materials, analyses not independently reproduced, and unresolved uncertainty.] + +# Confidential comments to editor + +> Keep this channel separate from comments to authors. Follow the venue policy. Do not place ordinary scientific criticism only here. + +## Reviewer disclosures + +- Conflicts and editor clearance: [State the disclosed status without unnecessary personal detail] +- Competence limits or specialist review needed: [State areas] +- Assistance or tools used and required disclosure: [State policy-compliant details] +- Confidentiality or retention issue: [State any unresolved process concern] + +## Editorial-process or integrity concerns + +[Describe only substantiated process, ethics, confidentiality, or integrity concerns and their evidence locations. Avoid accusations and avoid an editorial outcome.] diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/source_ledger.csv b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/source_ledger.csv new file mode 100644 index 0000000..f27ba6f --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/source_ledger.csv @@ -0,0 +1,32 @@ +source_id,organization,title,version_or_date,source_type,url,verified_on,status,notes +SRC-COPE-ETHICS,COPE,Ethical Guidelines for Peer Reviewers,Version 2 September 2017,ethical guideline,https://publicationethics.org/guidance/guideline/ethical-guidelines-peer-reviewers,2026-07-23,current_official,"Core duties for competence, conflicts, confidentiality, timeliness, conduct, and bounded requests" +SRC-COPE-PEER,COPE,Peer review core practice and resources,Current page; first posted 2018,core practice,https://publicationethics.org/news-opinion/peer-review,2026-07-23,current_official,Use with the detailed ethical guideline and current target-journal policy +SRC-ICMJE-REVIEW,ICMJE,Responsibilities in the Submission and Peer-Review Process,Current 2026 recommendations,professional recommendation,https://www.icmje.org/recommendations/browse/roles-and-responsibilities/responsibilities-in-the-submission-and-peer-peview-process.html,2026-07-23,current_official,"Confidentiality, deletion, assistance disclosure, conflicts, constructive review, and editor responsibility" +SRC-ICMJE-AI,ICMJE,Use of AI by Reviewers,Current 2026 recommendations,professional recommendation,https://www.icmje.org/recommendations/browse/artificial-intelligence/ai-use-by-reviewers.html,2026-07-23,current_official,"Follow journal policy, request permission, preserve confidentiality, disclose use, and validate output" +SRC-EQUATOR,EQUATOR Network,Library for health research reporting,Current database,reporting-guideline registry,https://www.equator-network.org/,2026-07-23,current_official,Primary registry for selecting study-type-specific health reporting guidance +SRC-EQUATOR-SELECT,EQUATOR Network,Selecting the appropriate reporting guideline for your article,Current toolkit,selection guidance,https://www.equator-network.org/toolkits/selecting-the-appropriate-reporting-guideline,2026-07-23,current_official,Selection aid for authors editors and peer reviewers +SRC-CONSORT-2025,CONSORT-SPIRIT Group,CONSORT 2025 statement: updated guideline for reporting randomised trials,Published 2025-04-14,primary guideline,https://www.bmj.com/content/389/bmj-2024-081123,2026-07-23,current_primary,"Thirty main items; supersedes 2010; explicitly not a quality assessment instrument" +SRC-SPIRIT-2025,CONSORT-SPIRIT Group,SPIRIT 2025 statement: updated guideline for protocols of randomised trials,Published 2025-04-28,primary guideline,https://www.bmj.com/content/389/bmj-2024-081477,2026-07-23,current_primary,Thirty-four main items; supersedes SPIRIT 2013 +SRC-PRISMA-2020,PRISMA Group,PRISMA 2020,2020 statement published 2021,official guideline site,https://www.prisma-statement.org/prisma-2020,2026-07-23,current_official,"Statement, expanded checklist, abstract checklist, and flow diagrams" +SRC-PRISMA-QUALITY,PRISMA Group,The PRISMA 2020 statement: an updated guideline for reporting systematic reviews,Published 2021-03-29,primary guideline,https://www.bmj.com/content/372/bmj.n71,2026-07-23,current_primary,Explicitly not intended to assess conduct or methodological quality +SRC-STROBE,STROBE Initiative,Strengthening the Reporting of Observational Studies in Epidemiology,2007 statement; current site,official guideline site,https://www.strobe-statement.org/,2026-07-23,current_official,"Use design-specific checklist and applicable extensions" +SRC-STARD-2015,EQUATOR Network / STARD Group,STARD 2015: updated list for diagnostic accuracy studies,2015,registry record and primary links,https://www.equator-network.org/reporting-guidelines/stard/,2026-07-23,current_official,Thirty main reporting items for diagnostic accuracy studies +SRC-STARD-QUALITY,EQUATOR Network / STARD Group,STARD reporting guideline applicability guidance,STARD 2015 v1.1,implementation guidance,https://resources.equator-network.org/reporting-guidelines/stard,2026-07-23,current_official,Explicitly separates reporting assessment from design-quality appraisal +SRC-STARD-AI,STARD-AI Steering Committee,The STARD-AI reporting guideline for diagnostic accuracy studies using artificial intelligence,Published 2025-09-15,primary guideline,https://www.nature.com/articles/s41591-025-03953-8,2026-07-23,current_primary,Forty items for AI-centered diagnostic accuracy studies +SRC-TRIPOD-AI,TRIPOD Group,TRIPOD+AI statement: updated guidance for clinical prediction models,Published 2024-04-16; corrected 2024-04-18,primary guideline,https://www.bmj.com/content/385/bmj-2023-078378,2026-07-23,current_primary,"Twenty-seven items; replaces TRIPOD 2015 for regression and machine-learning prediction models" +SRC-CARE,CARE Group,CARE Checklist,2013 checklist; 2017 explanation,official guideline site,https://www.care-statement.org/checklist,2026-07-23,current_official,Case reports including patient perspective and informed consent +SRC-ARRIVE,NC3Rs / ARRIVE Group,The ARRIVE Guidelines 2.0,Published 2020,official guideline site,https://arriveguidelines.org/arrive-guidelines,2026-07-23,current_official,Essential 10 plus Recommended Set for in vivo animal research +SRC-SQUIRE,SQUIRE Group,SQUIRE 2.0 Guidelines,Published 2015,official guideline site,https://www.squire-statement.org/index.cfm?fuseaction=page.viewPage&pageID=471&nodeID=1,2026-07-23,current_official,Quality-improvement reporting; not every element applies to every manuscript +SRC-CHEERS,ISPOR,CHEERS 2022,Published 2022,official guideline site,https://www.ispor.org/heor-resources/good-practices/cheers,2026-07-23,current_official,Replaces 2013 guidance for health economic evaluations +SRC-SAMPL,EQUATOR Network / Lang and Altman,SAMPL guidelines for statistical reporting,2013; journal article 2015,statistical reporting guidance,https://www.equator-network.org/2013/02/11/sampl-guidelines-for-statistical-reporting/,2026-07-23,current_guidance,Succinct biomedical statistical reporting guidance for authors editors and reviewers +SRC-ASA-PVALUE,American Statistical Association,Statement on Statistical Significance and P-Values,Released 2016-03-07,statistical principle statement,https://www.amstat.org/asa/files/pdfs/P-ValueStatement.pdf,2026-07-23,current_official,Do not base conclusions on thresholds alone; report transparently and distinguish effect size from significance +SRC-NIH-RIGOR,National Institutes of Health,Guidance: Rigor and Reproducibility in Grant Applications,Current page updated 2024-10-16,funder guidance,https://grants.nih.gov/policy-and-compliance/policy-topics/reproducibility/guidance,2026-07-23,current_official,"Rigor of prior research, robust design, authentication, and transparent reporting" +SRC-ICH-E9R1,International Council for Harmonisation,ICH E9(R1) Addendum on Estimands and Sensitivity Analysis in Clinical Trials,Step 4 2019; effective 2020,statistical guidance,https://database.ich.org/sites/default/files/E9-R1_Step4_Guideline_2019_1203.pdf,2026-07-23,current_official,Trial estimands missing data and sensitivity analysis; use only when applicable +SRC-MIAME-MINSEQE,NCBI GEO,MIAME and MINSEQE guidelines,Page last modified 2026-07-08,repository implementation guidance,https://www.ncbi.nlm.nih.gov/geo/info/MIAME.html,2026-07-23,current_qualified,GEO still implements both standards; verify current repository submission fields +SRC-MIAPE,HUPO Proteomics Standards Initiative,MIAPE reporting guidelines for proteomics,Current page updated 2024-01-16,domain metadata standard,https://www.psidev.info/miape,2026-07-23,current_qualified,Modular released components have separate versions; select the component matching the workflow +SRC-MIFLOWCYT,International Society for Advancement of Cytometry,MIFlowCyt,Version 1.0; current ISAC recommendation,domain metadata standard,https://isac-net.org/miflowcyt-2/,2026-07-23,current_qualified,Retained as active ISAC guidance for flow-cytometry experiment reporting +SRC-MIAPPE,MIAPPE Community,MIAPPE Releases,Version 1.2 October 2024,domain metadata standard,https://www.miappe.org/releases,2026-07-23,current_official,Version 2.0 remained in early development at verification date +SRC-MIXS,Genomic Standards Consortium,Minimum Information about any Sequence,Current framework,domain metadata standard,https://www.gensc.org/pages/standards-intro.html,2026-07-23,current_official,MIGS and MIMS are legacy checklists within the current MIxS umbrella +SRC-NATURE-POLICY,Nature Portfolio,Peer Review,Current policy at verification,journal policy example,https://www.nature.com/nature-portfolio/editorial-policies/peer-review,2026-07-23,illustrative_policy,Example only; prohibits uploading manuscripts to generative AI and requests AI-use declaration +SRC-BMJ-POLICY,BMJ,AI use,Updated 2026-05-21,publisher policy example,https://authors.bmj.com/policies/ai-use,2026-07-23,illustrative_policy,Example only; protects unpublished material and requires declaration for review-language assistance +SRC-JAMA-POLICY,JAMA Network,Guidance for Authors Peer Reviewers and Editors on Use of AI Language Models and Chatbots,Published 2023-07-27,journal policy example,https://jamanetwork.com/journals/jama/fullarticle/2807956,2026-07-23,illustrative_policy,Example only; prohibits entering manuscript or review text into a chatbot and requires disclosure diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/statistical_reproducibility_template.json b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/statistical_reproducibility_template.json new file mode 100644 index 0000000..943a30f --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/statistical_reproducibility_template.json @@ -0,0 +1,210 @@ +{ + "schema_version": "2.0", + "checklist_id": "STAT-REPRO-SYNTHETIC-001", + "study_design": "randomized_trial", + "specialist_review": { + "needed": "undetermined", + "areas": [], + "requested": false + }, + "items": [ + { + "id": "question.estimand_alignment", + "category": "question", + "applicability": "applicable", + "status": "not_assessed", + "evidence_locations": [], + "note": "", + "requested_action": "Check alignment among the question, target quantity, design, analysis, and interpretation" + }, + { + "id": "design.unit_and_independence", + "category": "design", + "applicability": "applicable", + "status": "not_assessed", + "evidence_locations": [], + "note": "", + "requested_action": "Identify the experimental or observational unit and assess dependence" + }, + { + "id": "design.sample_size_precision", + "category": "design", + "applicability": "applicable", + "status": "not_assessed", + "evidence_locations": [], + "note": "", + "requested_action": "Assess prospective sample-size or precision justification and assumptions" + }, + { + "id": "design.allocation_randomization", + "category": "design", + "applicability": "applicable", + "status": "not_assessed", + "evidence_locations": [], + "note": "", + "requested_action": "Assess sequence generation, allocation concealment, or sampling procedures as applicable" + }, + { + "id": "design.blinding", + "category": "design", + "applicability": "applicable", + "status": "not_assessed", + "evidence_locations": [], + "note": "", + "requested_action": "Assess who was blinded or masked and likely consequences when blinding was infeasible" + }, + { + "id": "data.inclusion_exclusion", + "category": "data", + "applicability": "applicable", + "status": "not_assessed", + "evidence_locations": [], + "note": "", + "requested_action": "Trace prespecified inclusion, exclusion, attrition, and analysis populations" + }, + { + "id": "data.missing_data", + "category": "data", + "applicability": "applicable", + "status": "not_assessed", + "evidence_locations": [], + "note": "", + "requested_action": "Assess missingness amounts, reasons, assumptions, methods, and sensitivity analyses" + }, + { + "id": "data.outliers_transformations", + "category": "data", + "applicability": "applicable", + "status": "not_assessed", + "evidence_locations": [], + "note": "", + "requested_action": "Check rules for exclusions, transformations, detection limits, and influential observations" + }, + { + "id": "analysis.prespecification", + "category": "analysis", + "applicability": "applicable", + "status": "not_assessed", + "evidence_locations": [], + "note": "", + "requested_action": "Compare analyses with the protocol, registration, and statistical analysis plan" + }, + { + "id": "analysis.method_design_alignment", + "category": "analysis", + "applicability": "applicable", + "status": "not_assessed", + "evidence_locations": [], + "note": "", + "requested_action": "Assess whether methods match the design, outcome type, estimand, and sampling structure" + }, + { + "id": "analysis.assumptions_diagnostics", + "category": "analysis", + "applicability": "applicable", + "status": "not_assessed", + "evidence_locations": [], + "note": "", + "requested_action": "Assess model assumptions, diagnostics, robustness, and fallback analyses" + }, + { + "id": "analysis.multiplicity", + "category": "analysis", + "applicability": "applicable", + "status": "not_assessed", + "evidence_locations": [], + "note": "", + "requested_action": "Assess outcome hierarchy, repeated looks, multiple comparisons, and error control" + }, + { + "id": "analysis.clustering_repeated_measures", + "category": "analysis", + "applicability": "applicable", + "status": "not_assessed", + "evidence_locations": [], + "note": "", + "requested_action": "Assess clustering, pairing, repeated measures, nesting, and time dependence" + }, + { + "id": "results.effect_sizes_uncertainty", + "category": "results", + "applicability": "applicable", + "status": "not_assessed", + "evidence_locations": [], + "note": "", + "requested_action": "Check effect estimates and compatible uncertainty intervals, not thresholded p-values alone" + }, + { + "id": "results.denominators_flow", + "category": "results", + "applicability": "applicable", + "status": "not_assessed", + "evidence_locations": [], + "note": "", + "requested_action": "Reconcile denominators, participant or sample flow, exclusions, and analysis populations" + }, + { + "id": "results.complete_outcomes_harms", + "category": "results", + "applicability": "applicable", + "status": "not_assessed", + "evidence_locations": [], + "note": "", + "requested_action": "Check all prespecified outcomes, null findings, adverse events, and deviations" + }, + { + "id": "reproducibility.data_materials_access", + "category": "reproducibility", + "applicability": "applicable", + "status": "not_assessed", + "evidence_locations": [], + "note": "", + "requested_action": "Assess data, materials, protocol, repository, and justified access restrictions" + }, + { + "id": "reproducibility.code_environment_parameters", + "category": "reproducibility", + "applicability": "applicable", + "status": "not_assessed", + "evidence_locations": [], + "note": "", + "requested_action": "Assess code, software versions, environments, seeds, parameters, and run instructions" + }, + { + "id": "reproducibility.provenance_versions", + "category": "reproducibility", + "applicability": "applicable", + "status": "not_assessed", + "evidence_locations": [], + "note": "", + "requested_action": "Trace data provenance, transformations, versioned artifacts, and accession identifiers" + }, + { + "id": "ethics.approval_consent_governance", + "category": "ethics", + "applicability": "applicable", + "status": "not_assessed", + "evidence_locations": [], + "note": "", + "requested_action": "Assess approvals, consent, welfare, privacy, governance, and conflicts as applicable" + }, + { + "id": "interpretation.claim_evidence_causality", + "category": "interpretation", + "applicability": "applicable", + "status": "not_assessed", + "evidence_locations": [], + "note": "", + "requested_action": "Check claim strength against design, evidence, uncertainty, alternatives, and generalizability" + }, + { + "id": "integrity.deviations_selective_reporting", + "category": "integrity", + "applicability": "applicable", + "status": "not_assessed", + "evidence_locations": [], + "note": "", + "requested_action": "Check unexplained deviations, outcome switching, selective reporting, and internal inconsistencies" + } + ] +} diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/study_profile_template.json b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/study_profile_template.json new file mode 100644 index 0000000..cce8e5f --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/assets/study_profile_template.json @@ -0,0 +1,12 @@ +{ + "schema_version": "2.0", + "profile_id": "PROFILE-SYNTHETIC-RCT", + "study_types": [ + "randomized_trial" + ], + "report_kind": "results", + "features": [], + "domains": [ + "health" + ] +} diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/references/common_issues.md b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/references/common_issues.md new file mode 100644 index 0000000..f04f8b8 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/references/common_issues.md @@ -0,0 +1,257 @@ +# Common Issues in Manuscript Review + +Use this reference as a prompt for inquiry, not a defect checklist. A possible issue becomes a review comment only when it is relevant to the study and supported by a manuscript location, supplied artifact, or applicable method principle. + +Do not infer misconduct, poor quality, or manuscript merit from a missing reporting item. Separate: + +- **Not reported:** the manuscript does not provide enough information to assess the point. +- **Potential design or analysis problem:** the reported method may not answer the stated question. +- **Demonstrated inconsistency:** two supplied artifacts or manuscript locations conflict. +- **Integrity concern:** credible evidence should be described neutrally and routed through the journal process, normally in confidential editor notes. + +## Claim–evidence alignment + +Check each central claim against the design, analysis, result, and uncertainty that support it. + +Common mismatches: + +- Causal wording from an observational or otherwise non-identifying design +- Mechanistic conclusions supported only by association or prediction +- Conclusions based on a secondary, exploratory, or post hoc outcome without labeling +- Directionally correct claims that overstate magnitude or precision +- Population, setting, intervention, comparator, outcome, or time-horizon extrapolation +- “No effect,” “equivalent,” or “safe” conclusions from imprecise or non-significant results +- Abstract or conclusion claims that omit material harms, uncertainty, subgroup caveats, or null findings +- Novelty claims that are broader than the search or cited literature supports + +Constructive response: + +1. Identify the claim and its location. +2. Identify the relevant result or missing evidence. +3. Explain the alignment problem. +4. Request a bounded remedy: narrow wording, add uncertainty, clarify exploratory status, provide the prespecified analysis, or justify the inference. + +Use `scripts/validate_claim_evidence.py` for a local identifier-based matrix. Its report never echoes claim text. + +## Study question, design, and units + +### Question–design mismatch + +Check whether the population, intervention or exposure, comparator, outcomes, timing, and target quantity align from objectives through interpretation. For trials, identify the estimand when relevant. For prediction, distinguish model development from performance evaluation. For diagnostic studies, distinguish diagnostic accuracy from clinical utility. + +### Experimental or observational unit + +Potential issues include: + +- Technical replicates treated as independent biological units +- Multiple cells, images, lesions, eyes, visits, or samples per subject analyzed as independent +- Cluster assignment analyzed at the individual level without accounting for clustering +- Paired or repeated observations analyzed as unpaired +- Site, operator, batch, family, spatial, or temporal dependence ignored + +Request a clear definition of the unit, nesting, repeated measures, and analysis that reflects dependence. Do not assume a mixed model is always the correct remedy; the model must match the design and question. + +### Selection, allocation, and masking + +Assess, as applicable: + +- Sampling frame, recruitment, eligibility, and exclusions +- Sequence generation and allocation concealment +- Prospective stopping rules +- Blinding or masking of participants, personnel, outcome assessors, and analysts +- Consequences and mitigation when masking is infeasible +- Baseline measurement timing and post-allocation exclusions + +Avoid treating baseline significance tests as proof of successful randomization. Focus on chance imbalance, clinically important imbalance, prespecified adjustment, and departures from the randomized comparison. + +### Confounding and causal identification + +For causal claims, ask: + +- What target causal contrast is intended? +- Which assumptions connect the design and analysis to that contrast? +- Were confounders selected using subject-matter reasoning rather than outcome-driven screening? +- Could adjustment introduce collider or mediator bias? +- Are time-varying treatment, censoring, immortal time, or informative observation processes relevant? +- Are negative controls, sensitivity analyses, or alternative explanations appropriate? + +Do not demand a specific causal method without showing why it fits the data-generating process. + +## Sample size, precision, and replication + +Avoid fixed heuristics such as “n < 30 is too small” or “three replicates are sufficient.” Adequacy depends on the target effect or precision, variability, design effect, event count, model complexity, multiplicity, attrition, and decision context. + +Check: + +- Prospective rationale for sample size or precision +- Inputs, assumptions, software or method, and allowance for attrition or clustering +- Whether the primary outcome and analysis match the calculation +- Event and outcome information relative to model complexity +- Effective sample size after dependence, missingness, weighting, or splitting +- Independent biological replication and validation where the claim requires it +- Precision of estimates, not only nominal power + +Observed or post hoc power calculated from the observed effect generally adds little beyond the estimate and its interval. Request effect estimates and uncertainty rather than “achieved power.” + +## Statistical analysis + +### Analysis–design alignment + +Check whether the analysis respects: + +- Outcome scale and distribution +- Pairing, clustering, repeated measures, censoring, and competing events +- Sampling design, weights, matching, stratification, or blocking +- Outcome hierarchy and prespecified estimand +- Non-inferiority or equivalence margins and analysis populations +- Longitudinal timing and informative dropout + +Do not prescribe “parametric” or “non-parametric” methods from sample size alone. + +### Assumptions and diagnostics + +The relevant assumptions depend on the estimand and model. A standalone normality test is not a universal gatekeeper and can be uninformative in very small or large samples. Look for design-aware diagnostics, residual behavior, influential observations, functional form, calibration, proportional hazards where applicable, and sensitivity to reasonable alternatives. + +Comments should identify the assumption at risk and why it matters. “Check normality” without specifying the modeled quantity or consequence is not actionable. + +### Effect estimates and uncertainty + +Flag: + +- Thresholded interpretation of p-values +- P-values used as effect size, importance, or probability that a hypothesis is true +- “Significant” versus “not significant” used as evidence of a difference between effects +- Missing effect estimates, compatible intervals, denominators, or units +- Excessive precision or inconsistent rounding +- Confidence, credible, or prediction intervals described incorrectly +- Clinical or practical importance conflated with statistical compatibility + +Prefer estimates, uncertainty, assumptions, and context. The ASA p-value principles and SAMPL reporting guidance are indexed in `assets/source_ledger.csv`. + +### Multiplicity and analysis flexibility + +Assess: + +- Number and hierarchy of outcomes, time points, subgroups, contrasts, and models +- Interim looks, adaptive changes, or repeated data inspection +- Family or false-discovery control when required by the inferential aim +- Transparent labeling of confirmatory and exploratory analyses +- Consistency with protocol, registration, and statistical analysis plan +- Complete reporting rather than selective presentation of favorable analyses + +Not every collection of analyses requires the same correction. Ask authors to state the inferential family and rationale instead of automatically demanding Bonferroni adjustment. + +### Missing data and intercurrent events + +Check: + +- Amount and reasons by group and time +- Distinction between intercurrent events and missing observations when relevant +- Assumptions behind complete-case, imputation, weighting, likelihood, or other methods +- Inclusion of variables and uncertainty in multiple imputation +- Sensitivity analyses to plausible departures from assumptions +- Alignment between the target quantity, data collection, and missing-data strategy + +Do not require a test that data are “missing completely at random”; missingness assumptions are not generally established by a single diagnostic test. + +### Outliers, transformations, and limits + +Check whether exclusions, transformations, winsorization, detection-limit handling, and influential-observation rules were prespecified or transparently justified. Request sensitivity analyses when conclusions depend materially on discretionary handling. Do not demand deletion merely because a value is extreme. + +### Subgroups and heterogeneity + +Look for prespecification, adequate interaction analysis, multiplicity, uncertainty, biological or clinical rationale, and consistency of direction. Within-group significance and between-group non-significance do not establish subgroup differences. + +### Prediction and machine learning + +Check: + +- Clear target population, outcome, prediction time, and intended use +- Separation of training, tuning, and evaluation without leakage +- Representative evaluation data and transportability +- Handling of missing values and preprocessing within resampling folds +- Calibration as well as discrimination when relevant +- Uncertainty around performance and decision consequences +- Overfitting, optimism correction, and external evaluation +- Model and preprocessing availability, versioning, and human oversight +- Fairness analyses tied to intended use, not demographic metrics without context + +TRIPOD+AI applies to regression and machine-learning prediction models; STARD-AI applies when diagnostic accuracy is the primary evaluation target. + +## Reproducibility and transparency + +Check whether another qualified researcher could understand and, where permissions allow, repeat the work: + +- Protocol, registration, amendments, and analysis plan +- Data provenance, processing stages, exclusions, and versioned identifiers +- Reagents, materials, instruments, software, package versions, parameters, and seeds +- Code, environment or lock file, run order, and computational resources +- Data, code, model, and material availability statements +- Repository accession numbers and persistent identifiers +- Clear, justified restrictions for privacy, consent, security, licensing, or community governance + +“Available on request” is not automatically invalid, and open release is not always ethical or lawful. Evaluate whether the access route is specific, feasible, and consistent with governance. + +Do not claim to have reproduced an analysis unless it was actually run with documented inputs, environment, commands, and outputs. + +## Figures, tables, and images + +Assess the supplied artifact directly; do not infer manipulation from low-resolution rendering alone. + +Check: + +- Axes, units, denominators, scales, legends, and uncertainty definitions +- Individual data or distribution display when summary graphics conceal relevant structure +- Accessibility and redundant encoding beyond color alone +- Consistency among text, tables, figures, and supplements +- Sample sizes and exclusions for each panel or analysis +- Image acquisition, processing, normalization, scale bars, and representative-image selection +- Disclosed splicing or adjustments and availability of source images when policy requires +- Avoidance of deceptive truncation, area/volume encoding, or dual-axis implication + +Possible duplication or manipulation should be documented neutrally by location and referred to the editor under the journal’s image-integrity process. Do not accuse authors of fabrication. + +## Ethics, welfare, privacy, and integrity + +Check what is applicable: + +- Ethics committee or institutional review and identifiers +- Consent, assent, waiver, or lawful basis +- Trial registration and prospective protocol availability +- Animal welfare, humane endpoints, and relevant ARRIVE items +- Privacy, identifiability, community governance, and controlled access +- Funding, sponsor role, author conflicts, and contributor roles +- Dual-use, biosafety, environmental, or security considerations +- Prior publication, overlapping reports, and transparent secondary analyses + +If a concern cannot safely be raised with authors, use the confidential editor channel. State the evidence and uncertainty; do not investigate people, contact institutions, or reveal the manuscript outside the authorized process. + +## Citations and references + +Check: + +- Every consequential literature claim has an appropriate source +- The cited source supports the stated proposition +- Primary sources are used for methods, data, and policies when available +- Retracted or corrected work is handled appropriately +- Contradictory and relevant evidence is represented fairly +- Self-citation requests are necessary, specific, and not coercive +- Citation identifiers and reference entries are internally consistent + +The local `scripts/audit_citations.py` checks Pandoc-style keys such as `[@ref-id]` against a CSV. It does not verify source existence or support and must not be described as doing so. + +## Writing actionable comments + +For each major or minor comment, include: + +- **Location** +- **Observation** +- **Evidence or criterion** +- **Why it matters** +- **Requested action** + +Prefer: “At Methods, paragraph 3, the experimental unit is unclear. Because three measurements appear to come from each participant, please define the unit and explain how within-participant dependence was handled.” + +Avoid: “The statistics are bad.” + +Requests for new experiments should be necessary to support an existing central claim, ethically and practically proportionate, and distinguished from optional future work. Often the appropriate remedy is to narrow a claim, add a limitation, provide missing analysis detail, or share an existing artifact. diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/references/ethical_review_practice.md b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/references/ethical_review_practice.md new file mode 100644 index 0000000..c0211ca --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/references/ethical_review_practice.md @@ -0,0 +1,233 @@ +# Ethical and Confidential Peer Review + +Verified on **2026-07-23** against COPE, ICMJE, and illustrative publisher policies listed in `assets/source_ledger.csv`. + +COPE identifies peer review as one of its 10 Core Practices and states that the process should be transparently described and well managed, with policies for conflicts, appeals, and disputes. The target journal’s published process controls the individual assignment. + +## Role boundary + +This skill supports an accountable human preparing a review draft or structured assessment. It must not: + +- Claim to be the assigned reviewer, editor, journal, funder, or decision-maker +- Submit a review or contact authors, editors, institutions, or third parties without authorization +- Invent manuscript content, experiments, analyses, citations, reviewer identity, or editorial outcomes +- Present generated text as an independently completed review +- Investigate authors or search unpublished content outside the authorized process + +The editor decides the editorial outcome. The reviewer provides evidence-bounded advice within the requested scope. + +## Before accepting or starting + +COPE’s Ethical Guidelines for Peer Reviewers and ICMJE recommendations require reviewers to consider: + +### Competence + +- Accept only work for which the reviewer can provide a useful assessment. +- State material subject-matter, methods, statistics, ethics, language, or domain limits. +- Ask the editor for a specialist reviewer when a central issue exceeds competence. +- Do not conceal limits by producing confident generic criticism. + +### Conflicts + +Disclose actual, potential, or perceived conflicts before proceeding. They can be: + +- Financial or commercial +- Personal or family +- Institutional +- Recent collaboration, supervision, mentorship, or competition +- Intellectual commitments or directly competing work +- Political, religious, advocacy, or legal interests + +The journal decides whether a disclosed conflict permits review. If unresolved, stop. Do not accept merely to gain access to unpublished work. + +### Capacity and timeliness + +Accept only if the review can be completed within the agreed time. Tell the editor promptly if scope or timing changes. + +### Journal policy + +Record: + +- Review model and anonymity expectations +- Whether co-review is allowed and how contributors are named +- Confidentiality and retention requirements +- Required author-facing and editor-only fields +- AI and tool policy +- Citation, image-integrity, data, ethics, and reporting expectations + +Journal practices differ. A policy from another publisher is an example, not authority for the target venue. + +Use `scripts/validate_review_intake.py` before substantive review. + +## Confidentiality and data handling + +An unpublished manuscript, its supplements, review comments, and editorial correspondence are privileged confidential material. + +### Default rule + +Keep processing local. Do not send, paste, upload, transcribe, summarize, or expose unpublished manuscript or review text to: + +- Public or external generative-AI systems +- Search engines or web research tools +- Citation, plagiarism, grammar, translation, or image services +- Unapproved collaborators +- Cloud storage or telemetry outside the authorized environment + +unless the publisher or author has authorized that specific use, the target venue permits it, and applicable privacy, contract, intellectual-property, and data-governance requirements are satisfied. + +Authorization to review is not automatically authorization to disclose material to a service. A tool’s promise not to train on data is not, by itself, authorization. + +The bundled CLIs: + +- Use only Python’s standard library +- Read bounded local JSON, CSV, or Markdown +- Make no network, model, image, subprocess, environment-variable, or dynamic-code calls +- Emit identifiers, counts, rule codes, and line numbers rather than raw manuscript or review text +- Refuse symlink inputs and implicit output overwrite + +They do not make an external service safe and do not authorize its use. + +### Assistance and co-review + +Obtain journal permission before sharing with a trainee or colleague. Record the contributor and acknowledge the contribution to the editor as required. The invited reviewer remains responsible for confidentiality and the submitted report. + +### No reuse + +Do not: + +- Appropriate ideas, methods, code, data, or language before publication +- Use the material for model training, benchmarking, product improvement, or unrelated research +- Build a private corpus of manuscripts or reviews +- Retain content for convenience beyond policy + +### Retention and deletion + +After submitting or ending the review: + +1. Follow the target venue’s retention rule. +2. Delete local manuscript and review copies when required. +3. Empty derivative exports and temporary files within the authorized workspace. +4. Retain only what policy requires. +5. Record deletion or authorized retention without copying confidential content into the record. + +ICMJE recommends that reviewers not retain manuscripts for personal use and delete copies after review. Local law, publisher policy, or a documented investigation may impose a different rule; follow the controlling requirement. + +## AI and automated assistance + +ICMJE says reviewers must follow the journal’s AI policy or request permission before using AI, maintain confidentiality, disclose use, and remain responsible for output that may be incorrect, incomplete, or biased. + +For any permitted assistance: + +- Identify the tool, version, purpose, and material exposed +- Use only the minimum necessary content +- Keep an accountable human in control +- Verify every statement, citation, calculation, and proposed comment +- Disclose use exactly as the journal requires +- Do not let a model create an autonomous review or editorial outcome + +If permission is absent, the policy is unclear, or confidentiality cannot be assured, do not use AI on the material. Local deterministic checks may still be possible if policy and authorization permit local file processing. + +Illustrative policies, not universal rules: + +- [Nature Portfolio](https://www.nature.com/nature-portfolio/editorial-policies/peer-review) asks reviewers not to upload manuscripts to generative-AI tools and asks for transparent declaration when AI supported claim evaluation. +- [BMJ](https://authors.bmj.com/policies/ai-use) requires declaration of AI used for review-language assistance and prohibits placing unpublished material into publicly available tools when confidentiality cannot be guaranteed. +- [JAMA Network](https://jamanetwork.com/journals/jama/fullarticle/2807956) states that entering manuscript, abstract, or review text into a chatbot or language model violates its confidentiality agreement and requires disclosure of other AI resource use. + +Always check the current target-venue policy. + +## Preparing the report + +COPE and ICMJE emphasize constructive, honest, polite, fair, and timely comments. + +### Evidence and proportionality + +- Anchor every consequential criticism to a location and reason. +- Distinguish missing reporting from demonstrated methodological error. +- Explain why the issue changes validity, interpretation, reproducibility, ethics, or reader understanding. +- Request the least burdensome adequate remedy. +- Label optional suggestions as optional. +- Do not expand the study beyond its stated scope merely to satisfy reviewer preference. +- Do not request citations to benefit the reviewer or associates. + +### Tone + +Critique the work, not the people. Avoid: + +- Insults, sarcasm, ridicule, threats, or speculation about competence or motives +- Language policing unrelated to scientific clarity +- Bias based on identity, institution, location, seniority, language, or reputation +- Accusations when the evidence supports only a question or discrepancy +- Vague commands such as “redo the statistics” or “needs more work” + +Use direct language without hostility: + +> “The analysis appears to treat three observations per participant as independent. Please define the analysis unit and account for within-participant dependence, or explain why independence is justified.” + +### Requests for additional work + +Request a new experiment or analysis only when it is necessary and proportionate to evaluate or support a central claim. State: + +- Which claim depends on it +- Why existing evidence is insufficient +- Whether a narrower claim, correction, sensitivity analysis, or limitation would be an adequate alternative + +Do not turn review into an opportunity to redesign the authors’ research program. + +## Separate communication channels + +### Comments to authors + +Include: + +- Neutral summary of the work actually reviewed +- Specific strengths +- Major comments affecting validity, interpretation, reproducibility, ethics, or central claims +- Minor comments affecting clarity, consistency, figures, tables, citations, or reporting +- Review limitations and unavailable materials when useful + +Do not include: + +- Reviewer identity when policy requires anonymity +- Unnecessary personal information +- Editor-only conflict details +- Accusations or investigative instructions +- An editorial outcome presented as decided + +### Confidential comments to editor + +Use only for matters that require a separate channel: + +- Reviewer conflicts or competence limits +- Permission, confidentiality, or AI-use disclosures +- Credible ethics, integrity, duplicate-publication, image, or security concerns +- Reasons an issue cannot safely be raised directly with authors +- Requests for specialist review + +Ordinary scientific criticism should not appear only in the editor channel. Do not write a harsher private review that contradicts the author-facing report. The bundled scaffold keeps these channels visibly separate. + +## Suspected integrity problems + +Reviewers identify concerns; they do not adjudicate misconduct. + +1. Preserve confidentiality. +2. Record the exact location and observable discrepancy. +3. Describe uncertainty and plausible benign explanations. +4. Notify the editor through the designated confidential route. +5. Do not contact authors, institutions, journals, funders, or media independently. +6. Do not run external similarity, face-recognition, image, or data-search services on confidential material without authorization. +7. Follow editor instructions and retain or delete evidence according to policy. + +Use “Figure 3 appears similar to Figure 5 after rotation; please assess under the journal’s image-integrity process,” not “the authors fabricated the data.” + +## Final ethical check + +- Authorization and role are documented. +- Conflicts are resolved or disclosed. +- Competence limits and specialist needs are stated. +- Target-venue policy and review model are known. +- No unauthorized person or service received confidential material. +- AI or other assistance is permitted and disclosed. +- Author and editor channels are separate. +- Every criticism is specific, evidence-backed, proportionate, and professional. +- No invented citation, analysis, experiment, or editorial outcome appears. +- Deletion or authorized retention is planned. diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/references/reporting_standards.md b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/references/reporting_standards.md new file mode 100644 index 0000000..f155b2d --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/references/reporting_standards.md @@ -0,0 +1,249 @@ +# Reporting Guidelines and Domain Metadata Standards + +Verified against primary or official sources on **2026-07-23**. The dated evidence record is `assets/source_ledger.csv`; the machine-readable selector catalog is `assets/reporting_guidelines.json`. + +## What reporting guidelines do—and do not do + +A reporting guideline identifies information that should be reported so readers can understand and appraise a study. It is not, by itself: + +- A method for designing or conducting the study +- A risk-of-bias tool +- A statistical reanalysis +- A measure of truth, importance, novelty, or manuscript merit +- A publication recommendation + +Checklist completion must never be converted automatically into a quality score. A fully reported study can have serious design problems; an incompletely reported study may be impossible to assess. Record missing information as a reporting gap, then separately assess any design, conduct, analysis, reproducibility, or ethics concern using appropriate evidence and expertise. + +Use the guideline’s current statement together with its explanation and elaboration. Check applicable extensions and the target venue’s instructions. Do not copy checklist wording into a review when a specific, contextual comment is more useful. + +## Selection workflow + +1. Identify the **report kind**: results, protocol, abstract, or data release. +2. Identify the **study design**, not merely the topic or journal section. +3. Add cross-cutting features: AI intervention, diagnostic AI, routinely collected data, clustered design, qualitative interviews, and so on. +4. Select the current base guideline and applicable extensions. +5. Use the official checklist to record `reported`, `partly_reported`, `not_reported`, `not_applicable`, or `not_assessed`. +6. Explain `not_applicable`; do not treat it as a defect. +7. Keep reporting coverage separate from methodological appraisal. + +Run: + +```bash +python3 scripts/select_reporting_guidelines.py \ + assets/study_profile_template.json \ + --coverage assets/reporting_checklist_template.csv +``` + +The bundled catalog is a dated aid, not a live registry. Consult the [EQUATOR Network](https://www.equator-network.org/) and official guideline site when the study type is unclear or a newer extension may apply. + +## Major current health-research guidelines + +### Randomized trial results — CONSORT 2025 + +- Current statement: **CONSORT 2025**, published 14 April 2025 +- Structure: 30 main checklist items and a participant flow diagram +- Supersedes: CONSORT 2010 +- Use for: reports of randomized trials +- Review with: explanation and elaboration plus design/intervention extensions +- Important boundary: the statement explicitly says it is not a quality assessment instrument + +Check registration, protocol and statistical analysis plan consistency, allocation, participant flow, outcomes and harms, effect estimates and uncertainty, protocol changes, data sharing, conflicts, and patient/public involvement where applicable. + +Official sources: [CONSORT–SPIRIT](https://www.consort-spirit.org/) and the [CONSORT 2025 statement](https://www.bmj.com/content/389/bmj-2024-081123). + +### Randomized trial protocols — SPIRIT 2025 + +- Current statement: **SPIRIT 2025**, published 28 April 2025 +- Structure: 34 main checklist items and a participant timeline +- Supersedes: SPIRIT 2013 +- Use for: randomized trial protocols + +Compare the protocol with registration, statistical analysis plan, ethics records, amendments, and any completed-trial report. Explicitly stated non-applicability with rationale is not missing reporting. + +Official sources: [CONSORT–SPIRIT](https://www.consort-spirit.org/) and the [SPIRIT 2025 statement](https://www.bmj.com/content/389/bmj-2024-081477). + +### Systematic reviews — PRISMA 2020 + +- Current statement: **PRISMA 2020** (named 2020; published 2021) +- Structure: 27 main items, expanded checklist, abstract checklist, and flow diagrams +- Use for: completed systematic reviews, primarily reviews of intervention effects +- Protocols: use PRISMA-P +- Extensions: use the appropriate extension for scoping, diagnostic, individual-participant-data, network, equity, harms, or other specialized reviews + +PRISMA explicitly does not assess review conduct or methodological quality. Use appropriate methods and risk-of-bias tools separately. + +Official sources: [PRISMA 2020 resources](https://www.prisma-statement.org/prisma-2020) and the [primary statement](https://www.bmj.com/content/372/bmj.n71). + +### Observational studies — STROBE + +- Current base statement: **STROBE 2007** +- Structure: 22 main items with cohort, case-control, cross-sectional, and combined checklists +- Use for: reports of observational epidemiologic studies +- Extensions: examples include RECORD for routinely collected health data, STREGA for genetic association studies, STROBE-MR, and domain-specific extensions + +STROBE helps identify whether selection, measurement, bias, confounding, missing data, sensitivity analyses, and generalizability are reported. It does not establish that those methods were adequate. + +Official source: [STROBE](https://www.strobe-statement.org/). + +### Diagnostic accuracy — STARD 2015 + +- Current base statement: **STARD 2015** +- Structure: 30 main items and a flow diagram +- Use for: studies estimating diagnostic accuracy against a reference standard + +Separately assess risk of bias and applicability with a suitable tool such as the current QUADAS family when relevant. STARD’s official implementation guidance explicitly says not to use the reporting checklist as a design-quality tool. + +Official source: [STARD 2015](https://www.equator-network.org/reporting-guidelines/stard/). + +### AI-centered diagnostic accuracy — STARD-AI + +- Current statement: **STARD-AI 2025** +- Published: 15 September 2025; an author correction was published 13 July 2026 +- Structure: 40 items, including 18 new or modified items relative to STARD 2015 +- Use for: AI-centered diagnostic accuracy studies, including suitable diagnostic classification tasks + +Check dataset practices, index-test specification, evaluation, algorithmic bias and fairness, applicability, and generalizability. If the primary aim is development or evaluation of a multivariable prediction model, use TRIPOD+AI instead. + +Official source: [STARD-AI](https://www.nature.com/articles/s41591-025-03953-8). + +### Clinical prediction models — TRIPOD+AI + +- Current statement: **TRIPOD+AI 2024** +- Structure: 27 main items plus a 13-item abstract checklist +- Replaces: TRIPOD 2015 +- Use for: development, evaluation, or updating of diagnostic or prognostic prediction models using regression or machine-learning methods + +Do not select it solely because software called “AI” appears in a paper. Select it when the study’s primary object is a prediction model. Relevant extensions include TRIPOD-Cluster, TRIPOD-SRMA, and TRIPOD-LLM. + +Official sources: [TRIPOD](https://www.tripod-statement.org/) and the [TRIPOD+AI statement](https://www.bmj.com/content/385/bmj-2023-078378). + +### Case reports — CARE + +- Current base checklist: **CARE 2013** +- Explanation and elaboration/manual: 2017 +- Structure: 13 main items +- Use for: clinical case reports + +Check timeline, diagnostic reasoning, interventions, outcomes, adverse events, patient perspective where available, informed consent, privacy, and venue requirements. + +Official source: [CARE checklist](https://www.care-statement.org/checklist). + +### In vivo animal research — ARRIVE 2.0 + +- Current statement: **ARRIVE 2.0**, published July 2020 +- Structure: Essential 10 plus 11 Recommended Set items +- Use for: research involving live animals across bioscience disciplines + +The Essential 10 are a minimum reporting set, not a ranking. Review study design, sample size, inclusion/exclusion, randomization, blinding, outcome measures, statistics, animal details, procedures, and results; also assess ethics, welfare, humane endpoints, adverse events, protocol registration, data access, and interests. + +Official source: [ARRIVE 2.0](https://arriveguidelines.org/arrive-guidelines). + +### Quality improvement — SQUIRE 2.0 + +- Current statement: **SQUIRE 2.0**, published 2015 +- Structure: 18 main items +- Use for: system-level work intended to improve healthcare quality, safety, value, or equity where methods seek to relate outcomes to the intervention + +SQUIRE states that every item should be considered, but not every element belongs in every manuscript. Attend to local context, rationale, intervention evolution, measures, analysis, ethics, unintended consequences, and sustainability. + +Official source: [SQUIRE 2.0](https://www.squire-statement.org/index.cfm?fuseaction=page.viewPage&pageID=471&nodeID=1). + +### Health economic evaluations — CHEERS 2022 + +- Current statement: **CHEERS 2022** +- Structure: 28 main items +- Replaces: CHEERS 2013 +- Use for: economic evaluations of health interventions + +Assess perspective, comparators, time horizon, discounting, outcome and cost measurement, model assumptions, heterogeneity, distributional effects where applicable, uncertainty, engagement, funding, and conflicts. Use a separate critical-appraisal framework for methodological quality. + +Official source: [ISPOR CHEERS](https://www.ispor.org/heor-resources/good-practices/cheers). + +### Qualitative research — SRQR and COREQ + +- **SRQR**: broad qualitative research reporting standard +- **COREQ**: 32-item checklist specifically for interviews and focus groups + +Select by methods, not by the presence of quotations. Review researcher reflexivity, sampling, context, data collection, analytic process, credibility, participant voice, ethics, and limitations without imposing one epistemology on all qualitative traditions. + +Official registry records: [SRQR](https://www.equator-network.org/reporting-guidelines/srqr) and [COREQ](https://www.equator-network.org/reporting-guidelines/coreq/). + +## AI extensions and overlap + +Use the guideline that matches the study’s primary design and claim: + +- Randomized trial of an AI intervention: CONSORT 2025 plus current CONSORT-AI guidance +- Protocol for such a trial: SPIRIT 2025 plus current SPIRIT-AI guidance +- AI diagnostic accuracy: STARD-AI +- Prediction model development or performance evaluation: TRIPOD+AI +- Biomedical large-language-model prediction or evaluation: check TRIPOD-LLM and design-specific guidance +- Medical imaging AI: consider current modality guidance in addition to the design-specific base + +Multiple guidelines can apply, but do not create redundant demands. State which base and extension address each concern. + +## Domain metadata standards: verified legacy status + +These standards describe minimum experiment or repository metadata. They complement, rather than replace, study-design reporting and methodological appraisal. + +### MIAME and MINSEQE + +**Retain with qualification.** NCBI GEO’s page was last modified 8 July 2026 and still states that GEO submission procedures implement: + +- MIAME for microarray experiments +- MINSEQE for next-generation/high-throughput sequencing experiments + +ArrayExpress/Annotare also continues to reference these standards. Verify the current repository’s fields, file formats, raw/processed data expectations, and accession requirements; do not rely on an old static project page alone. + +Official implementation source: [GEO and MIAME/MINSEQE](https://www.ncbi.nlm.nih.gov/geo/info/MIAME.html). + +### MIAPE + +**Retain as a modular current-qualified standard.** The HUPO Proteomics Standards Initiative lists released components with separate versions, including mass spectrometry, mass-spectrometry informatics, quantification, gel electrophoresis, gel informatics, chromatography, and capillary electrophoresis. + +Select only components relevant to the actual workflow and verify current repository expectations. Do not present “MIAPE” as one unversioned universal checklist. + +Official source: [HUPO-PSI MIAPE](https://www.psidev.info/miape). + +### MIFlowCyt + +**Retain with qualification.** ISAC continues to identify MIFlowCyt 1.0 as an ISAC recommendation for experiment overview, samples, instrumentation, and data analysis. Also check current FCS, gating, panel, controls, and FlowRepository requirements. + +Official source: [ISAC MIFlowCyt](https://isac-net.org/miflowcyt-2/). + +### MIAPPE + +**Use MIAPPE 1.2**, released October 2024, for plant phenotyping metadata. It remains compatible with 1.1; version 2.0 was still in early development on the verification date. + +Official source: [MIAPPE releases](https://www.miappe.org/releases). + +### MIGS and MIMS + +**Do not present standalone MIGS/MIMS as the current umbrella.** The Genomic Standards Consortium now organizes these legacy checklists within **MIxS** (Minimum Information about any Sequence), alongside newer checklists and environmental packages. Select the current MIxS release and applicable checklist/package. + +Official source: [GSC standards](https://www.gensc.org/pages/standards-intro.html). + +## Other study types + +The EQUATOR database contains hundreds of guidelines. Common additional choices include: + +- Protocols: design-specific protocol guidance +- Routinely collected health data: RECORD +- Clinical practice guidelines: RIGHT and AGREE reporting guidance +- Surveys: design-appropriate survey reporting guidance +- Implementation studies: current implementation-reporting guidance +- Mixed methods: current mixed-methods guidance +- Laboratory and omics studies: study-design reporting plus current repository metadata standards + +If no suitable guideline exists, say so. Do not force the nearest checklist or invent one. + +## Coverage language for reviews + +Use: + +> “Item 12 is not reported clearly enough to determine the analysis population. Please identify the included participants and reconcile this denominator with Figure 1.” + +Avoid: + +> “The manuscript scores 18/30 on CONSORT and is therefore low quality.” + +Report counts or item identifiers only as navigation aids. The local selector deliberately emits no percentage or merit score. diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/references/security_validation.md b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/references/security_validation.md new file mode 100644 index 0000000..928c04f --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/references/security_validation.md @@ -0,0 +1,75 @@ +# Security Validation Record + +Validation date: **2026-07-23** (local project date). + +## Baseline + +The repository `SECURITY.md` entry recorded **10 findings** with maximum severity **CRITICAL**: + +- Cross-file environment-variable and network exfiltration +- A multi-file collection/transmission chain +- Environment harvesting in both schematic scripts +- API-key transmission to an external model service +- Full environment propagation to a subprocess +- Repeated costly model/image operations +- Mandatory external schematic/cross-skill behavior + +The affected files were: + +- Deleted: scripts/generate_schematic.py +- Deleted: scripts/generate_schematic_ai.py +- The former `SKILL.md` + +## Remediation + +- Deleted both external schematic scripts. +- Removed credentials, environment access, `.env` loading, subprocess chaining, network requests, model calls, image generation, mandatory figures, and cross-skill calls. +- Replaced them with bounded deterministic local JSON/CSV/Markdown validators and generators. +- Added strict schemas, duplicate-key/header detection, size/row/cell limits, symlink rejection, private atomic output, and no implicit overwrite. +- Added report minimization: IDs, counts, rule codes, and line numbers instead of manuscript/review prose. +- Added AST tests that reject network libraries, executable serialization, dynamic code execution, and environment credential access. +- Added confidentiality, no-reuse, authorization, conflict, competence, AI-policy, disclosure, and deletion/retention gates. + +## Validation results + +- Agent Skills reference validator: **PASS** +- Dependency-free CLI help checks: **PASS** +- Synthetic standard-library tests: **26 passed** +- Explicit AST parse with bytecode disabled: **8 scripts parsed** +- Bytecode artifacts: **0** +- IDE lints: **0** +- Documented local-path link test: **PASS** +- Markdown link check: **PASS** (access-controlled HTTP 403 treated as reachable) +- Direct behavioral security scan: **SAFE, 0 findings** +- Pull-request gate with `--fail-on HIGH`: **PASS** + - CRITICAL: 0 + - HIGH: 0 + - LOW: 3 + +## Residual LOW findings + +The final LLM-assisted pull-request scan reported: + +1. **Missing `allowed-tools` declaration** — informational. This field is optional under the Agent Skills specification. The compatibility and body explicitly constrain bundled tools to local standard-library processing with no network, model, image, credential, or environment access. +2. **Broad description** — accepted as a scoped capability description. The mandatory authorization and venue-policy gate applies before confidential content is read, and the body limits all functions to peer-review assessment. +3. **Bounded CSV/JSON processing** — defensive observation with “no action required” in the scanner output. Inputs are already limited to 4 MiB, 5,000 CSV rows, 12,000 characters per cell, and finite list sizes; tests cover oversize rejection. + +None of the LOW findings permits data transmission or credential access. No CRITICAL or HIGH issue remains. + +## Reproduction + +```bash +PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover \ + -s tests/peer-review -p "test_*.py" -v + +uv run skills-ref validate skills/peer-review + +uv run skill-scanner scan skills/peer-review --use-behavioral + +uv run python scan_pr_skills.py \ + --fail-on HIGH \ + --output /tmp/peer-review-pr-scan.md \ + skills/peer-review +``` + +The repository-level `SECURITY.md` was intentionally not edited in this scoped refresh; its generated snapshot will update through the repository’s normal scan process. diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/references/statistical_reproducibility.md b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/references/statistical_reproducibility.md new file mode 100644 index 0000000..17f73cf --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/references/statistical_reproducibility.md @@ -0,0 +1,329 @@ +# Statistical, Methods, and Reproducibility Review + +This guide supports structured questions; it does not replace a statistician, methodologist, domain expert, or independent reanalysis. Sources verified on **2026-07-23** are recorded in `assets/source_ledger.csv`. + +## Evidence hierarchy for the review + +Use, in order: + +1. The stated research question and target population +2. Protocol, registration, analysis plan, and amendments +3. Reported design and data-generating process +4. Methods, code, tables, figures, supplements, and repository records +5. Applicable primary method or regulatory guidance +6. Current reporting guidance +7. Target venue policy + +Do not reject a method merely because another method is more familiar. Explain the estimand, assumption, error, or interpretation at stake. + +## Core review sequence + +### 1. Define what is being estimated + +Write down: + +- Unit of inference +- Population +- Intervention, exposure, test, or predictors +- Comparator or reference condition +- Outcome and time horizon +- Target effect, association, accuracy, or predictive performance +- Intercurrent events, censoring, and missing observations where relevant + +Then trace whether design, data collection, analysis, result, and claim target the same quantity. + +For applicable clinical trials, ICH E9(R1) provides a framework for estimands and sensitivity analyses. It is not a universal rule for every study. + +### 2. Reconstruct the design + +Identify: + +- Prospective, retrospective, cross-sectional, longitudinal, experimental, or observational structure +- Recruitment or sampling frame +- Experimental/observational unit +- Pairing, nesting, clustering, repeated measures, sites, batches, and time +- Allocation, concealment, blinding, matching, or weighting +- Primary and secondary outcomes +- Prespecified versus exploratory analyses + +If the design cannot be reconstructed, first request missing reporting. Do not label the design invalid solely because details are absent. + +### 3. Trace every denominator + +Reconcile: + +- Eligible, enrolled, assigned, treated/exposed, followed, measured, and analyzed units +- Outcome-specific denominators +- Exclusions before and after allocation or measurement +- Missing values and reasons +- Complete-case, imputed, weighted, or model-based analysis populations +- Figure, table, abstract, text, and supplement totals + +Report the exact mismatch and locations; do not infer why counts differ. + +### 4. Assess analysis–design alignment + +Ask whether the method accounts for: + +- Outcome scale and distribution +- Pairing and repeated measures +- Clustering and multilevel structure +- Unequal follow-up, censoring, or competing events +- Sampling weights or matched designs +- Baseline adjustment and prespecified covariates +- Multiplicity and outcome hierarchy +- Model tuning and validation +- Missingness assumptions + +The name of a statistical test is not enough. The report should state inputs, model form, uncertainty method, software/version, and relevant diagnostics. + +### 5. Assess estimates and interpretation + +Prefer: + +- Effect or performance estimates with units +- Compatible uncertainty intervals +- Absolute as well as relative quantities when decision-relevant +- Exact denominators and analysis sets +- Assumption and sensitivity context +- Clinical, biological, policy, or practical relevance distinct from statistical compatibility + +The ASA’s six p-value principles include: + +- A p-value is about incompatibility with a specified model, not the probability a hypothesis is true. +- Threshold crossing alone should not determine scientific conclusions. +- Transparent reporting of all relevant analyses is required. +- Statistical significance does not measure effect size or importance. +- A p-value alone is not a good measure of evidence. + +SAMPL provides concise biomedical statistical reporting guidance. Apply it as reporting guidance, not a universal analysis recipe. + +## Topic-specific checks + +### Sample size and precision + +Look for: + +- Prospective calculation or precision rationale +- Target effect or interval width +- Variance, event rate, prevalence, or accuracy assumptions +- Type I error, power, sidedness, and multiplicity when applicable +- Design effect, clustering, attrition, noncompliance, and missingness +- Model complexity and effective sample size +- Simulation details for complex designs + +Do not request observed/post hoc power as a remedy for an imprecise result. Examine the estimate and uncertainty. + +### Randomized trials + +Check: + +- Allocation sequence and concealment +- Prespecified estimand and analysis population +- Protocol/registry/outcome consistency +- Baseline adjustment and stratification factors +- Intercurrent events, adherence, treatment switching, and missing data +- Harms and unintended effects +- Sensitivity and supplementary analyses +- Non-inferiority/equivalence margin and interpretation if relevant + +Use CONSORT 2025 and SPIRIT 2025 for reporting. Use ICH E9/E9(R1) only when its scope and decision context fit. + +### Observational causal analyses + +Check: + +- Causal question and target contrast +- Time zero, eligibility, treatment/exposure assignment, follow-up, and outcome timing +- Confounder rationale and measurement timing +- Positivity/overlap +- Exchangeability and consistency assumptions +- Missingness, censoring, selection, and measurement error +- Model specification and balance diagnostics +- Sensitivity to unmeasured confounding or alternative specifications + +Avoid judging causal identification from adjusted versus unadjusted p-values. + +### Diagnostic accuracy + +Check: + +- Intended use, setting, and participant spectrum +- Index test and reference standard +- Threshold prespecification +- Blinding and timing +- Indeterminate/missing results +- Verification and incorporation bias +- Two-by-two denominators and uncertainty +- External applicability + +STARD/STARD-AI describe reporting; use an appropriate risk-of-bias framework separately. + +### Prediction models + +Check: + +- Intended use, prediction time, outcome, and target population +- Data source and participant flow +- Predictor availability at intended use +- Missing-data and preprocessing leakage +- Sample size relative to outcome information and complexity +- Internal validation and optimism correction +- Independent evaluation and dataset shift +- Calibration, discrimination, decision utility, and uncertainty +- Hyperparameter tuning separated from evaluation +- Reproducible model specification and preprocessing +- Subgroup performance tied to plausible use and harms + +TRIPOD+AI replaces TRIPOD 2015 for regression and machine-learning prediction model reporting. STARD-AI is more appropriate when diagnostic accuracy of an index test is the primary aim. + +### Systematic reviews and meta-analyses + +Check: + +- Protocol and registration +- Eligibility criteria and information sources +- Reproducible search dates and strategies +- Duplicate screening/extraction processes or justified alternatives +- Risk-of-bias assessment +- Effect measure and synthesis model +- Heterogeneity and prediction intervals when appropriate +- Dependence among estimates +- Small-study and reporting biases +- Certainty assessment, if claimed +- Transparent deviations and unavailable data + +PRISMA 2020 assesses reporting. Do not substitute PRISMA coverage for review-conduct appraisal. + +### Clustered and longitudinal data + +Check: + +- Level of assignment, measurement, and inference +- Within-cluster/subject correlation +- Number and distribution of clusters +- Small-cluster corrections where needed +- Time structure, nonlinear change, and irregular measurement +- Informative visit, dropout, or censoring processes +- Cluster-level versus individual-level covariates + +Repeated observations do not increase independent sample size one-for-one. + +### Multiplicity + +Identify the inferential family before recommending adjustment: + +- Multiple primary outcomes +- Multiple intervention arms or contrasts +- Repeated time points +- Subgroups and interactions +- Interim analyses +- High-dimensional features +- Model selection + +Possible responses include hierarchical testing, family-wise control, false-discovery control, multilevel modeling, transparent exploratory labeling, or emphasis on estimates and uncertainty. The remedy depends on the claim and decision rule. + +### Missing data + +Check: + +- Missingness by group, variable, outcome, and time +- Reasons and relation to intercurrent events +- Information used by imputation or weighting +- Number of imputations and pooling when applicable +- Compatibility of imputation and analysis models +- Uncertainty propagation +- Sensitivity to plausible departures from assumptions + +Avoid demanding one preferred technique without considering the estimand and missingness process. + +## Reproducibility review + +### Materials and provenance + +Check: + +- Stable identifiers for datasets, samples, models, protocols, and materials +- Raw-to-processed provenance +- Exclusion and transformation records +- Versioned analysis inputs and outputs +- Repository accession numbers +- Data dictionary, units, and coding +- Domain metadata standard where applicable + +Legacy domain standards and their current status are summarized in `references/reporting_standards.md`. + +### Code and computational environment + +Check: + +- Executable code for central analyses when sharing is permitted +- Dependency versions or lock/environment file +- Operating-system or hardware requirements that affect results +- Random seeds and nondeterminism +- Parameter, configuration, and model checkpoints +- Run order and instructions +- Tests or validation of custom code +- License and access restrictions + +Code availability does not prove that the code generated the reported result. Provenance and a reproducible run record are separate evidence. + +### Data and access + +Open sharing may be limited by consent, privacy, indigenous/community governance, security, contracts, or licensing. A useful statement should identify: + +- What exists +- Where it is held +- Who can request access +- Criteria and process +- Expected timeline +- Restrictions and rationale +- Whether code or synthetic/aggregate alternatives are available + +Do not request disclosure that would violate ethics, law, consent, or governance. + +### Independent reproduction + +Claim independent reproduction only if the reviewer actually: + +1. Obtained authorized inputs. +2. Recorded versions and environment. +3. Ran documented commands. +4. Preserved content hashes or equivalent provenance. +5. Compared prespecified outputs. +6. Recorded deviations and failures. + +A static consistency audit is not reproduction. + +## When to request specialist review + +Escalate when a central conclusion depends on methods outside competence, including: + +- Complex adaptive, Bayesian, causal, survival, multilevel, spatial, or longitudinal methods +- High-dimensional omics or multiple-testing procedures +- Diagnostic, prediction, or AI evaluation +- Survey weighting or complex sampling +- Economic modeling +- Meta-analysis with dependent effects or network structure +- Unfamiliar qualitative or mixed-methods methodology +- Image forensics, biosecurity, privacy, or domain-specific ethics + +Say what expertise is needed and which claim depends on it. Do not mask uncertainty with an automated score. + +## Using the local checklist + +Copy `assets/statistical_reproducibility_template.json`, record evidence locations without pasting manuscript prose into report fields, and run: + +```bash +python3 scripts/audit_statistics_reproducibility.py local-checklist.json +``` + +Statuses: + +- `verified_present` +- `partly_documented` +- `missing` +- `not_assessed` +- `not_applicable` with rationale + +The tool reports item IDs and counts. It does not calculate merit, rerun analyses, or certify reproducibility. diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/references/tool_reference.md b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/references/tool_reference.md new file mode 100644 index 0000000..d9b156b --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/references/tool_reference.md @@ -0,0 +1,253 @@ +# Local Tool Contracts + +All bundled tools are deterministic Python 3.11+ standard-library CLIs. They make no network, model, image, subprocess, environment-variable, dynamic-code, or pickle calls. + +## Shared safety behavior + +- Inputs: local JSON, CSV, or Markdown only +- Maximum input size: 4 MiB +- Maximum CSV rows: 5,000 +- UTF-8 only; NUL bytes rejected +- Symlink inputs and outputs rejected +- Duplicate JSON keys and CSV headers rejected +- Unknown schema fields rejected by JSON validators +- Existing outputs are not replaced unless `--force` is explicit +- JSON and Markdown outputs are written atomically with owner-only permissions where supported +- Reports contain IDs, counts, rule codes, and line numbers—not raw manuscript, review, claim, title, author, or reference prose + +Exit codes: + +- `0`: structurally valid or completed +- `1`: a validly parsed audit is blocked or has rule errors +- `2`: malformed input, unsafe path, unsupported field, or CLI validation error + +Run tools from the skill directory or use absolute script paths. + +## Intake validator + +```bash +python3 scripts/validate_review_intake.py \ + assets/review_intake_template.json +``` + +Purpose: + +- Confirm documented authorization and human accountability +- Record role, competence areas and limits, conflicts, target-venue policy, and review model +- Enforce local-only processing, no external service use, no data reuse, and a deletion/retention record +- Gate approved AI assistance on venue policy, permission, and disclosure + +The bundled template is intentionally blocked until the human completes the controls. + +Top-level JSON fields: + +- `schema_version`: `2.0` +- `review_id`: safe local identifier, not a manuscript title +- `material`: status and sensitive-data flag +- `authorization`: basis and documented permissions +- `reviewer`: capacity, accountability, competence, and conflicts +- `venue_policy`: checked status, review model, confidential-note channel +- `ai_use`: policy, plan, permission, disclosure +- `handling`: local-only, external-service, reuse, retention controls +- `scope`: manuscript type, requested focus, limits, specialist needs + +The report validates declarations, not their truth. + +## Reporting-guideline selector and coverage audit + +Selection only: + +```bash +python3 scripts/select_reporting_guidelines.py \ + assets/study_profile_template.json +``` + +Selection plus coverage: + +```bash +python3 scripts/select_reporting_guidelines.py \ + assets/study_profile_template.json \ + --coverage assets/reporting_checklist_template.csv +``` + +Profile fields: + +- `schema_version`: `2.0` +- `profile_id` +- `study_types`: identifiers such as `randomized_trial` +- `report_kind`: `results`, `protocol`, `abstract`, or `data_release` +- `features`: for example `ai_based`, `ai_intervention`, `large_language_model` +- `domains`: for example `health`, `genomics`, `proteomics` + +Coverage columns: + +- `guideline_id` +- `item_id`: aggregate main item number for guidelines with a known main count +- `status`: `reported`, `partly_reported`, `not_reported`, `not_applicable`, `not_assessed` +- `location`: required for reported or partly reported items +- `rationale`: required for not-applicable items + +The catalog is `assets/reporting_guidelines.json`, verified on the date embedded in that file. It does not fetch live updates. The output deliberately has no percentage or quality score. + +## Claim–evidence matrix validator + +```bash +python3 scripts/validate_claim_evidence.py \ + assets/claim_evidence_matrix_template.csv +``` + +Columns: + +- `claim_id` +- `location` +- `claim_type` +- `claim_summary`: input-only; never echoed +- `evidence_ids`: semicolon-delimited local IDs +- `support_level`: `supported`, `partly_supported`, `unsupported`, `not_assessed` +- `alignment_issue`: direction, magnitude, population, outcome, timepoint, causal language, scope, uncertainty, selective reporting, other, or none +- `limitation`: input-only; never echoed +- `requested_action`: input-only; never echoed + +Rules include: + +- Supported claims need evidence IDs and no declared alignment issue. +- Partly supported claims need evidence, an issue code, and a requested action. +- Unsupported claims need an issue code. +- Claim IDs must be unique. + +The tool does not determine whether evidence is true or sufficient. + +## Statistics and reproducibility checklist + +```bash +python3 scripts/audit_statistics_reproducibility.py \ + assets/statistical_reproducibility_template.json +``` + +The JSON contains: + +- Checklist and study-design IDs +- Specialist-review declaration +- Core item records with category, applicability, status, evidence locations, note, and requested action + +Core areas: + +- Question/estimand alignment +- Unit, independence, sample size, allocation, and blinding +- Inclusion/exclusion, missing data, and data handling +- Prespecification, method alignment, assumptions, multiplicity, and dependence +- Effect estimates, uncertainty, denominators, outcomes, and harms +- Data/material access, code/environment, and provenance +- Ethics/governance, claim interpretation, and selective reporting + +The tool requires the core item IDs but permits additional safe IDs. It reports gaps and specialist-review triggers without a score. + +## Citation/reference consistency audit + +The Markdown must use Pandoc-style citation keys: + +```markdown +The synthetic method is described elsewhere [@ref-synthetic-2026]. +Several sources may be grouped [@ref-one; @ref-two]. +``` + +Run: + +```bash +python3 scripts/audit_citations.py \ + local-manuscript.md \ + assets/citation_references_template.csv +``` + +Reference CSV columns: + +- `reference_id` +- `title` +- `authors` +- `year` +- `doi` +- `url` +- `verification_status`: `verified_primary`, `verified_secondary`, or `not_verified` + +The audit finds: + +- Citation keys without reference rows +- Reference rows not cited +- Cited references not marked verified +- References without DOI or URL +- Malformed citation syntax + +It validates DOI/URL shape only. It does not resolve identifiers, search the web, verify existence, or determine whether a reference supports a claim. + +## Review scaffold generator + +The intake must pass first: + +```bash +python3 scripts/generate_review_scaffold.py \ + completed-intake.json \ + -o private-review.md +``` + +The generator: + +- Reads `assets/review_scaffold_template.md` +- Interpolates only safe intake identifiers +- Never reads or embeds manuscript text +- Separates comments to authors from confidential editor notes +- Provides structured major/minor comment fields +- Includes human-accountability and no-editorial-decision warnings + +It refuses unresolved intake controls and implicit overwrite. + +## Tone and actionability lint + +```bash +python3 scripts/lint_review.py private-review.md +``` + +Required headings: + +- `# Comments to authors` +- `# Confidential comments to editor` + +Structured comment headings: + +- `### Major comment M1` +- `### Minor comment m1` + +Each comment must contain non-placeholder values for: + +- `Location` +- `Observation` +- `Evidence or criterion` +- `Why it matters` +- `Requested action` + +The linter flags: + +- Missing or reversed author/editor channels +- Editor-only markers in the author channel +- Unresolved scaffold placeholders +- A narrow lexicon of abusive or personal language +- Role impersonation and editorial-decision phrases +- Missing actionability fields +- Claims of executed analysis that need provenance + +Lexical lint has false positives and false negatives. Human review remains required. + +## Private output examples + +All JSON-reporting CLIs accept: + +```bash +-o local-report.json +``` + +To replace an existing output deliberately: + +```bash +--force +``` + +Do not place outputs in a synced or shared directory unless the authorization and venue policy permit it. Delete or retain inputs, drafts, and reports according to the documented review policy. diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/scripts/_common.py b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/scripts/_common.py new file mode 100644 index 0000000..3f8aa21 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/scripts/_common.py @@ -0,0 +1,398 @@ +#!/usr/bin/env python3 +"""Shared, dependency-free safety helpers for local peer-review CLIs.""" + +from __future__ import annotations + +import csv +import json +import os +import re +import tempfile +from datetime import date +from pathlib import Path +from typing import Any, Iterable + +MAX_INPUT_BYTES = 4 * 1024 * 1024 +MAX_ROWS = 5_000 +MAX_CELL_CHARS = 12_000 +MAX_TEXT_CHARS = 50_000 +MAX_LIST_ITEMS = 2_000 + +IDENTIFIER_RE = re.compile(r"^[A-Za-z][A-Za-z0-9._:-]{0,95}$") +DOI_RE = re.compile(r"^10\.\d{4,9}/\S+$", re.IGNORECASE) +URL_RE = re.compile(r"^https?://[^\s]+$", re.IGNORECASE) + + +class ValidationError(ValueError): + """A deterministic, user-correctable validation failure.""" + + +def _duplicate_safe_object(pairs: list[tuple[str, Any]]) -> dict[str, Any]: + result: dict[str, Any] = {} + for key, value in pairs: + if key in result: + raise ValidationError(f"JSON object contains duplicate key: {key}") + result[key] = value + return result + + +def safe_input_path(raw_path: str | Path, suffixes: Iterable[str]) -> Path: + """Resolve a bounded regular local file and reject symlink inputs.""" + path = Path(raw_path).expanduser() + if path.is_symlink(): + raise ValidationError(f"symlink inputs are not allowed: {path}") + try: + resolved = path.resolve(strict=True) + except FileNotFoundError as exc: + raise ValidationError(f"input file does not exist: {path}") from exc + if not resolved.is_file(): + raise ValidationError(f"input path is not a regular file: {resolved}") + allowed = {suffix.lower() for suffix in suffixes} + if resolved.suffix.lower() not in allowed: + choices = ", ".join(sorted(allowed)) + raise ValidationError(f"expected one of [{choices}]: {resolved}") + size = resolved.stat().st_size + if size > MAX_INPUT_BYTES: + raise ValidationError( + f"input exceeds {MAX_INPUT_BYTES} bytes: {resolved} ({size} bytes)" + ) + return resolved + + +def safe_output_path( + raw_path: str | Path, suffix: str, *, force: bool = False +) -> Path: + """Resolve an output in an existing directory without implicit overwrite.""" + path = Path(raw_path).expanduser() + if path.suffix.lower() != suffix.lower(): + raise ValidationError(f"output must use {suffix}: {path}") + try: + parent = path.parent.resolve(strict=True) + except FileNotFoundError as exc: + raise ValidationError(f"output parent does not exist: {path.parent}") from exc + if not parent.is_dir(): + raise ValidationError(f"output parent is not a directory: {parent}") + resolved = parent / path.name + if resolved.is_symlink(): + raise ValidationError(f"symlink outputs are not allowed: {resolved}") + if resolved.exists(): + if not resolved.is_file(): + raise ValidationError(f"output is not a regular file: {resolved}") + if not force: + raise ValidationError( + f"output already exists; pass --force to replace it: {resolved}" + ) + return resolved + + +def read_json(raw_path: str | Path) -> Any: + """Read strict UTF-8 JSON with duplicate-key detection.""" + path = safe_input_path(raw_path, {".json"}) + try: + text = path.read_text(encoding="utf-8") + except UnicodeDecodeError as exc: + raise ValidationError(f"JSON must be UTF-8: {path}") from exc + if "\x00" in text: + raise ValidationError(f"JSON contains a NUL byte: {path}") + try: + return json.loads(text, object_pairs_hook=_duplicate_safe_object) + except json.JSONDecodeError as exc: + raise ValidationError( + f"invalid JSON at line {exc.lineno}, column {exc.colno}: {exc.msg}" + ) from exc + + +def read_markdown(raw_path: str | Path) -> str: + """Read one bounded UTF-8 Markdown file.""" + path = safe_input_path(raw_path, {".md", ".markdown"}) + try: + text = path.read_text(encoding="utf-8") + except UnicodeDecodeError as exc: + raise ValidationError(f"Markdown must be UTF-8: {path}") from exc + if "\x00" in text: + raise ValidationError(f"Markdown contains a NUL byte: {path}") + return text + + +def read_csv_records( + raw_path: str | Path, + *, + required_fields: Iterable[str], + allowed_fields: Iterable[str] | None = None, + max_rows: int = MAX_ROWS, +) -> list[dict[str, str]]: + """Read strict UTF-8 CSV records with bounded, unique cells and headers.""" + path = safe_input_path(raw_path, {".csv"}) + required = tuple(required_fields) + allowed = set(allowed_fields) if allowed_fields is not None else None + try: + with path.open("r", encoding="utf-8-sig", newline="") as handle: + reader = csv.DictReader(handle) + headers = reader.fieldnames + if not headers: + raise ValidationError(f"CSV has no header: {path}") + normalized = [header.strip() for header in headers] + if any(not header for header in normalized): + raise ValidationError("CSV headers must not be blank") + if len(normalized) != len(set(normalized)): + raise ValidationError("CSV headers must be unique") + missing = sorted(set(required) - set(normalized)) + if missing: + raise ValidationError( + f"CSV is missing required columns: {', '.join(missing)}" + ) + if allowed is not None: + unknown = sorted(set(normalized) - allowed) + if unknown: + raise ValidationError( + f"CSV contains unknown columns: {', '.join(unknown)}" + ) + reader.fieldnames = normalized + records: list[dict[str, str]] = [] + for line_number, row in enumerate(reader, start=2): + if line_number - 1 > max_rows: + raise ValidationError(f"CSV exceeds {max_rows} data rows") + if None in row: + raise ValidationError( + f"row {line_number} has more cells than the header" + ) + cleaned: dict[str, str] = {} + for key, value in row.items(): + cell = "" if value is None else value.strip() + if "\x00" in cell: + raise ValidationError( + f"row {line_number}, column {key} contains a NUL byte" + ) + if len(cell) > MAX_CELL_CHARS: + raise ValidationError( + f"row {line_number}, column {key} exceeds " + f"{MAX_CELL_CHARS} characters" + ) + cleaned[key] = cell + if any(cleaned.values()): + records.append(cleaned) + except UnicodeDecodeError as exc: + raise ValidationError(f"CSV must be UTF-8: {path}") from exc + except csv.Error as exc: + raise ValidationError(f"invalid CSV: {exc}") from exc + if not records: + raise ValidationError("CSV must contain at least one data row") + return records + + +def require_object(value: Any, context: str) -> dict[str, Any]: + if not isinstance(value, dict): + raise ValidationError(f"{context} must be a JSON object") + return value + + +def require_exact_keys( + value: dict[str, Any], + *, + required: Iterable[str], + optional: Iterable[str] = (), + context: str, +) -> None: + required_set = set(required) + allowed = required_set | set(optional) + missing = sorted(required_set - set(value)) + unknown = sorted(set(value) - allowed) + if missing: + raise ValidationError(f"{context} is missing fields: {', '.join(missing)}") + if unknown: + raise ValidationError(f"{context} has unknown fields: {', '.join(unknown)}") + + +def require_list( + value: Any, + context: str, + *, + minimum: int = 0, + maximum: int = MAX_LIST_ITEMS, +) -> list[Any]: + if not isinstance(value, list): + raise ValidationError(f"{context} must be a JSON array") + if not minimum <= len(value) <= maximum: + raise ValidationError( + f"{context} must contain between {minimum} and {maximum} items" + ) + return value + + +def require_text( + value: Any, + context: str, + *, + allow_empty: bool = False, + minimum: int = 1, + maximum: int = MAX_TEXT_CHARS, +) -> str: + if not isinstance(value, str): + raise ValidationError(f"{context} must be a string") + text = value.strip() + if not allow_empty and not text: + raise ValidationError(f"{context} must not be empty") + if text and len(text) < minimum: + raise ValidationError(f"{context} must contain at least {minimum} characters") + if len(text) > maximum: + raise ValidationError(f"{context} exceeds {maximum} characters") + if "\x00" in text: + raise ValidationError(f"{context} contains a NUL byte") + return text + + +def require_bool(value: Any, context: str) -> bool: + if type(value) is not bool: + raise ValidationError(f"{context} must be true or false") + return value + + +def require_enum(value: Any, choices: Iterable[str], context: str) -> str: + text = require_text(value, context, maximum=96) + allowed = set(choices) + if text not in allowed: + raise ValidationError( + f"{context} must be one of: {', '.join(sorted(allowed))}" + ) + return text + + +def require_identifier(value: Any, context: str) -> str: + identifier = require_text(value, context, maximum=96) + if not IDENTIFIER_RE.fullmatch(identifier): + raise ValidationError(f"{context} has an invalid identifier format") + return identifier + + +def require_unique(values: Iterable[str], context: str) -> None: + seen: set[str] = set() + duplicates: set[str] = set() + for value in values: + if value in seen: + duplicates.add(value) + seen.add(value) + if duplicates: + raise ValidationError( + f"{context} contains duplicate IDs: {', '.join(sorted(duplicates))}" + ) + + +def require_text_list( + value: Any, + context: str, + *, + minimum: int = 0, + maximum: int = 100, +) -> list[str]: + items = require_list(value, context, minimum=minimum, maximum=maximum) + return [ + require_text(item, f"{context}[{index}]", maximum=500) + for index, item in enumerate(items) + ] + + +def require_identifier_list( + value: Any, + context: str, + *, + minimum: int = 0, + maximum: int = 100, +) -> list[str]: + items = require_list(value, context, minimum=minimum, maximum=maximum) + parsed = [ + require_identifier(item, f"{context}[{index}]") + for index, item in enumerate(items) + ] + require_unique(parsed, context) + return parsed + + +def split_identifiers( + value: Any, context: str, *, allow_empty: bool = False +) -> list[str]: + text = require_text(value, context, allow_empty=allow_empty, maximum=MAX_CELL_CHARS) + if not text: + return [] + parts = [part.strip() for part in text.split(";")] + if any(not part for part in parts): + raise ValidationError(f"{context} contains an empty identifier") + parsed = [require_identifier(part, context) for part in parts] + require_unique(parsed, context) + return parsed + + +def require_iso_date(value: Any, context: str) -> str: + text = require_text(value, context, maximum=10) + try: + date.fromisoformat(text) + except ValueError as exc: + raise ValidationError(f"{context} must be an ISO date (YYYY-MM-DD)") from exc + return text + + +def require_url(value: Any, context: str, *, allow_empty: bool = False) -> str: + text = require_text(value, context, allow_empty=allow_empty, maximum=2_000) + if text and not URL_RE.fullmatch(text): + raise ValidationError(f"{context} must be an http or https URL") + return text + + +def require_doi(value: Any, context: str, *, allow_empty: bool = False) -> str: + text = require_text(value, context, allow_empty=allow_empty, maximum=500) + if text and not DOI_RE.fullmatch(text): + raise ValidationError(f"{context} has an invalid DOI format") + return text + + +def atomic_write_text( + destination: Path, text: str, *, mode: int = 0o600 +) -> Path: + """Atomically write private local text to a validated destination.""" + temporary_name: str | None = None + try: + with tempfile.NamedTemporaryFile( + "w", + encoding="utf-8", + dir=destination.parent, + prefix=f".{destination.name}.", + suffix=".tmp", + delete=False, + ) as handle: + handle.write(text) + temporary_name = handle.name + os.chmod(temporary_name, mode) + os.replace(temporary_name, destination) + os.chmod(destination, mode) + finally: + if temporary_name and Path(temporary_name).exists(): + Path(temporary_name).unlink() + return destination + + +def write_json_report( + data: Any, output: str | Path | None, *, force: bool = False +) -> None: + """Print JSON or atomically write it to an explicitly selected local file.""" + serialized = json.dumps(data, indent=2, sort_keys=True, ensure_ascii=False) + "\n" + if output is None: + print(serialized, end="") + return + destination = safe_output_path(output, ".json", force=force) + atomic_write_text(destination, serialized) + + +def write_markdown( + text: str, output: str | Path, *, force: bool = False +) -> Path: + destination = safe_output_path(output, ".md", force=force) + return atomic_write_text(destination, text) + + +def issue(code: str, field: str) -> dict[str, str]: + """Create a content-free finding safe for confidential reports.""" + return {"code": code, "field": field} + + +def error_exit(exc: ValidationError) -> int: + print(f"ERROR: {exc}", file=os.sys.stderr) + return 2 diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/scripts/audit_citations.py b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/scripts/audit_citations.py new file mode 100644 index 0000000..1a7d3ec --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/scripts/audit_citations.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +"""Audit Markdown citation keys against a local reference CSV without network use.""" + +from __future__ import annotations + +import argparse +import re +from collections import Counter +from typing import Any + +from _common import ( + ValidationError, + error_exit, + issue, + read_csv_records, + read_markdown, + require_doi, + require_enum, + require_identifier, + require_text, + require_unique, + require_url, + write_json_report, +) + +REFERENCE_FIELDS = ( + "reference_id", + "title", + "authors", + "year", + "doi", + "url", + "verification_status", +) +VERIFICATION_STATUSES = { + "verified_primary", + "verified_secondary", + "not_verified", +} +CITATION_GROUP_RE = re.compile(r"\[([^\]\n]{1,1000})\]") +CITATION_KEY_RE = re.compile(r"@([A-Za-z][A-Za-z0-9._:-]{0,95})") +YEAR_RE = re.compile(r"^(?:1[5-9]\d{2}|20\d{2}|2100)$") + + +def load_references(raw_path: str) -> list[dict[str, str]]: + rows = read_csv_records( + raw_path, + required_fields=REFERENCE_FIELDS, + allowed_fields=REFERENCE_FIELDS, + ) + parsed: list[dict[str, str]] = [] + ids: list[str] = [] + for line_number, row in enumerate(rows, start=2): + context = f"references row {line_number}" + reference_id = require_identifier( + row["reference_id"], f"{context}.reference_id" + ) + ids.append(reference_id) + year = require_text(row["year"], f"{context}.year", maximum=4) + if not YEAR_RE.fullmatch(year): + raise ValidationError(f"{context}.year must be a four-digit year") + parsed.append( + { + "reference_id": reference_id, + "title": require_text( + row["title"], f"{context}.title", minimum=3, maximum=2_000 + ), + "authors": require_text( + row["authors"], f"{context}.authors", minimum=2, maximum=2_000 + ), + "year": year, + "doi": require_doi( + row["doi"], f"{context}.doi", allow_empty=True + ), + "url": require_url( + row["url"], f"{context}.url", allow_empty=True + ), + "verification_status": require_enum( + row["verification_status"], + VERIFICATION_STATUSES, + f"{context}.verification_status", + ), + } + ) + require_unique(ids, "reference IDs") + return parsed + + +def extract_citations(markdown: str) -> tuple[dict[str, list[int]], list[int]]: + citations: dict[str, list[int]] = {} + malformed_lines: list[int] = [] + for line_number, line in enumerate(markdown.splitlines(), start=1): + matched_starts = 0 + for group in CITATION_GROUP_RE.finditer(line): + content = group.group(1) + if "@" not in content: + continue + matched_starts += content.count("@") + keys = CITATION_KEY_RE.findall(content) + if not keys: + malformed_lines.append(line_number) + continue + for key in keys: + citations.setdefault(key, []).append(line_number) + if line.count("[@") > matched_starts: + malformed_lines.append(line_number) + return citations, sorted(set(malformed_lines)) + + +def audit(markdown: str, references: list[dict[str, str]]) -> dict[str, Any]: + citations, malformed_lines = extract_citations(markdown) + by_id = {row["reference_id"]: row for row in references} + cited_ids = set(citations) + reference_ids = set(by_id) + missing_ids = sorted(cited_ids - reference_ids) + uncited_ids = sorted(reference_ids - cited_ids) + unverified_ids = sorted( + reference_id + for reference_id in cited_ids.intersection(reference_ids) + if by_id[reference_id]["verification_status"] == "not_verified" + ) + no_locator_ids = sorted( + row["reference_id"] + for row in references + if not row["doi"] and not row["url"] + ) + errors: list[dict[str, str]] = [ + issue("CITATION_WITHOUT_REFERENCE", reference_id) + for reference_id in missing_ids + ] + errors.extend( + issue("MALFORMED_CITATION_SYNTAX", f"line:{line_number}") + for line_number in malformed_lines + ) + warnings: list[dict[str, str]] = [ + issue("REFERENCE_NOT_CITED", reference_id) for reference_id in uncited_ids + ] + warnings.extend( + issue("CITED_REFERENCE_NOT_VERIFIED", reference_id) + for reference_id in unverified_ids + ) + warnings.extend( + issue("REFERENCE_HAS_NO_PERSISTENT_LOCATOR", reference_id) + for reference_id in no_locator_ids + ) + verification_counts = Counter( + row["verification_status"] for row in references + ) + return { + "schema_version": "2.0", + "valid": not errors, + "status": "VALID" if not errors else "CITATION_INCONSISTENCIES", + "errors": errors, + "warnings": warnings, + "citation_occurrence_count": sum(len(lines) for lines in citations.values()), + "cited_reference_count": len(cited_ids), + "reference_count": len(references), + "missing_reference_ids": missing_ids, + "missing_reference_line_numbers": { + reference_id: sorted(set(citations[reference_id])) + for reference_id in missing_ids + }, + "uncited_reference_ids": uncited_ids, + "unverified_cited_reference_ids": unverified_ids, + "references_without_persistent_locator": no_locator_ids, + "verification_counts": dict(sorted(verification_counts.items())), + "malformed_citation_line_numbers": malformed_lines, + "notice": ( + "This local audit checks structured citation-key consistency and " + "identifier format only. It does not query registries, verify that a " + "source exists, confirm that a citation supports a claim, or echo " + "manuscript/reference prose." + ), + } + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description=( + "Audit Pandoc-style Markdown citations such as [@ref-id] against a " + "strict local reference CSV. No network calls are made." + ) + ) + parser.add_argument("manuscript", help="Local Markdown file") + parser.add_argument("references", help="Local reference CSV") + parser.add_argument("-o", "--output", help="Optional local JSON report") + parser.add_argument( + "--force", action="store_true", help="Replace an existing output file" + ) + return parser + + +def main() -> int: + args = build_parser().parse_args() + try: + report = audit( + read_markdown(args.manuscript), + load_references(args.references), + ) + write_json_report(report, args.output, force=args.force) + return 0 if report["valid"] else 1 + except ValidationError as exc: + return error_exit(exc) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/scripts/audit_statistics_reproducibility.py b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/scripts/audit_statistics_reproducibility.py new file mode 100644 index 0000000..931d214 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/scripts/audit_statistics_reproducibility.py @@ -0,0 +1,305 @@ +#!/usr/bin/env python3 +"""Audit a structured statistics and reproducibility checklist locally.""" + +from __future__ import annotations + +import argparse +from collections import Counter +from typing import Any + +from _common import ( + ValidationError, + error_exit, + issue, + read_json, + require_bool, + require_enum, + require_exact_keys, + require_identifier, + require_identifier_list, + require_list, + require_object, + require_text, + require_text_list, + require_unique, + write_json_report, +) + +CORE_ITEM_IDS = ( + "question.estimand_alignment", + "design.unit_and_independence", + "design.sample_size_precision", + "design.allocation_randomization", + "design.blinding", + "data.inclusion_exclusion", + "data.missing_data", + "data.outliers_transformations", + "analysis.prespecification", + "analysis.method_design_alignment", + "analysis.assumptions_diagnostics", + "analysis.multiplicity", + "analysis.clustering_repeated_measures", + "results.effect_sizes_uncertainty", + "results.denominators_flow", + "results.complete_outcomes_harms", + "reproducibility.data_materials_access", + "reproducibility.code_environment_parameters", + "reproducibility.provenance_versions", + "ethics.approval_consent_governance", + "interpretation.claim_evidence_causality", + "integrity.deviations_selective_reporting", +) +CATEGORIES = { + "question", + "design", + "data", + "analysis", + "results", + "reproducibility", + "ethics", + "interpretation", + "integrity", +} +APPLICABILITY = {"applicable", "not_applicable"} +STATUSES = { + "verified_present", + "partly_documented", + "missing", + "not_assessed", + "not_applicable", +} +SPECIALIST_TRIGGER_IDS = { + "question.estimand_alignment", + "data.missing_data", + "analysis.method_design_alignment", + "analysis.assumptions_diagnostics", + "analysis.multiplicity", + "analysis.clustering_repeated_measures", + "results.effect_sizes_uncertainty", + "interpretation.claim_evidence_causality", +} + + +def load_checklist(payload: Any) -> dict[str, Any]: + root = require_object(payload, "checklist") + require_exact_keys( + root, + required={ + "schema_version", + "checklist_id", + "study_design", + "specialist_review", + "items", + }, + context="checklist", + ) + schema_version = require_enum( + root["schema_version"], {"2.0"}, "checklist.schema_version" + ) + checklist_id = require_identifier(root["checklist_id"], "checklist.checklist_id") + study_design = require_identifier(root["study_design"], "checklist.study_design") + + specialist = require_object( + root["specialist_review"], "checklist.specialist_review" + ) + require_exact_keys( + specialist, + required={"needed", "areas", "requested"}, + context="checklist.specialist_review", + ) + specialist_review = { + "needed": require_enum( + specialist["needed"], + {"yes", "no", "undetermined"}, + "checklist.specialist_review.needed", + ), + "areas": require_identifier_list( + specialist["areas"], "checklist.specialist_review.areas" + ), + "requested": require_bool( + specialist["requested"], "checklist.specialist_review.requested" + ), + } + + raw_items = require_list(root["items"], "checklist.items", minimum=1, maximum=200) + items: list[dict[str, Any]] = [] + item_ids: list[str] = [] + for index, raw_item in enumerate(raw_items): + context = f"checklist.items[{index}]" + item = require_object(raw_item, context) + require_exact_keys( + item, + required={ + "id", + "category", + "applicability", + "status", + "evidence_locations", + "note", + "requested_action", + }, + context=context, + ) + item_id = require_identifier(item["id"], f"{context}.id") + item_ids.append(item_id) + items.append( + { + "id": item_id, + "category": require_enum( + item["category"], CATEGORIES, f"{context}.category" + ), + "applicability": require_enum( + item["applicability"], APPLICABILITY, f"{context}.applicability" + ), + "status": require_enum( + item["status"], STATUSES, f"{context}.status" + ), + "evidence_locations": require_text_list( + item["evidence_locations"], + f"{context}.evidence_locations", + maximum=50, + ), + "note": require_text( + item["note"], + f"{context}.note", + allow_empty=True, + maximum=2_000, + ), + "requested_action": require_text( + item["requested_action"], + f"{context}.requested_action", + allow_empty=True, + maximum=2_000, + ), + } + ) + require_unique(item_ids, "checklist.items") + return { + "schema_version": schema_version, + "checklist_id": checklist_id, + "study_design": study_design, + "specialist_review": specialist_review, + "items": items, + } + + +def audit(checklist: dict[str, Any]) -> dict[str, Any]: + errors: list[dict[str, str]] = [] + warnings: list[dict[str, str]] = [] + items_by_id = {item["id"]: item for item in checklist["items"]} + missing_core = sorted(set(CORE_ITEM_IDS) - set(items_by_id)) + if missing_core: + errors.extend(issue("CORE_ITEM_MISSING", item_id) for item_id in missing_core) + + status_counts = Counter() + gaps_by_category: dict[str, list[str]] = {} + action_missing: list[str] = [] + specialist_triggers: list[str] = [] + + for item in checklist["items"]: + item_id = item["id"] + status = item["status"] + status_counts[status] += 1 + if item["applicability"] == "not_applicable": + if status != "not_applicable": + errors.append(issue("APPLICABILITY_STATUS_MISMATCH", item_id)) + if not item["note"]: + errors.append(issue("NOT_APPLICABLE_RATIONALE_REQUIRED", item_id)) + continue + if status == "not_applicable": + errors.append(issue("APPLICABILITY_STATUS_MISMATCH", item_id)) + continue + if status == "verified_present" and not item["evidence_locations"]: + errors.append(issue("EVIDENCE_LOCATION_REQUIRED", item_id)) + if status == "partly_documented": + if not item["evidence_locations"]: + errors.append(issue("PARTIAL_ITEM_NEEDS_EVIDENCE_LOCATION", item_id)) + if not item["requested_action"]: + errors.append(issue("PARTIAL_ITEM_NEEDS_REQUESTED_ACTION", item_id)) + if status in {"partly_documented", "missing", "not_assessed"}: + gaps_by_category.setdefault(item["category"], []).append(item_id) + if not item["requested_action"]: + action_missing.append(item_id) + warnings.append(issue("REQUESTED_ACTION_MISSING", item_id)) + if item_id in SPECIALIST_TRIGGER_IDS: + specialist_triggers.append(item_id) + + specialist = checklist["specialist_review"] + specialist_recommended = bool(specialist_triggers) + if specialist["needed"] == "yes" and not specialist["requested"]: + warnings.append( + issue("SPECIALIST_REVIEW_NOT_REQUESTED", "specialist_review.requested") + ) + if specialist["needed"] == "undetermined": + warnings.append( + issue("SPECIALIST_REVIEW_UNDETERMINED", "specialist_review.needed") + ) + if specialist_recommended and specialist["needed"] == "no": + warnings.append( + issue("SPECIALIST_REVIEW_DECLARATION_RECHECK", "specialist_review.needed") + ) + + return { + "schema_version": "2.0", + "checklist_id": checklist["checklist_id"], + "valid": not errors, + "status": ( + "INVALID_CHECKLIST" + if errors + else "VALID_WITH_REVIEW_GAPS" + if gaps_by_category + else "VALID_NO_RECORDED_GAPS" + ), + "errors": errors, + "warnings": warnings, + "study_design": checklist["study_design"], + "item_count": len(checklist["items"]), + "status_counts": dict(sorted(status_counts.items())), + "gap_item_ids_by_category": { + category: sorted(item_ids) + for category, item_ids in sorted(gaps_by_category.items()) + }, + "item_ids_missing_requested_action": sorted(action_missing), + "specialist_review": { + "declared_needed": specialist["needed"], + "declared_areas": specialist["areas"], + "declared_requested": specialist["requested"], + "trigger_item_ids": sorted(specialist_triggers), + "recheck_recommended": specialist_recommended, + }, + "notice": ( + "This is a structured completeness and consistency audit, not a " + "statistical reanalysis, reproducibility claim, quality score, or " + "publication recommendation. A qualified specialist must evaluate " + "methods outside the reviewer's competence." + ), + } + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description=( + "Audit a local statistics/reproducibility checklist and emit only " + "item identifiers and counts." + ) + ) + parser.add_argument("checklist", help="Local checklist JSON") + parser.add_argument("-o", "--output", help="Optional local JSON report") + parser.add_argument( + "--force", action="store_true", help="Replace an existing output file" + ) + return parser + + +def main() -> int: + args = build_parser().parse_args() + try: + report = audit(load_checklist(read_json(args.checklist))) + write_json_report(report, args.output, force=args.force) + return 0 if report["valid"] else 1 + except ValidationError as exc: + return error_exit(exc) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/scripts/generate_review_scaffold.py b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/scripts/generate_review_scaffold.py new file mode 100644 index 0000000..0951a39 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/scripts/generate_review_scaffold.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +"""Generate a local structured peer-review draft scaffold from validated intake.""" + +from __future__ import annotations + +import argparse +import re +from pathlib import Path +from typing import Any + +from _common import ( + ValidationError, + error_exit, + read_markdown, + write_markdown, +) +from validate_review_intake import validate_intake + +TEMPLATE_PATH = ( + Path(__file__).resolve().parents[1] / "assets" / "review_scaffold_template.md" +) +PLACEHOLDER_RE = re.compile(r"\{\{[A-Z0-9_]+\}\}") + + +def generate(payload: Any, template_path: Path = TEMPLATE_PATH) -> str: + """Render only safe intake identifiers; never interpolate manuscript prose.""" + report = validate_intake(payload) + if not report["valid"]: + codes = sorted({item["code"] for item in report["errors"]}) + raise ValidationError( + "intake is blocked; resolve these controls first: " + ", ".join(codes) + ) + template = read_markdown(template_path) + replacements = { + "{{REVIEW_ID}}": report["review_id"], + "{{REVIEWER_CAPACITY}}": report["normalized_scope"]["capacity"], + "{{PEER_REVIEW_MODEL}}": report["normalized_scope"]["peer_review_model"], + "{{AI_PLAN}}": report["normalized_scope"]["ai_plan"], + } + rendered = template + for placeholder, value in replacements.items(): + rendered = rendered.replace(placeholder, value) + unresolved = sorted(set(PLACEHOLDER_RE.findall(rendered))) + if unresolved: + raise ValidationError( + "scaffold template contains unresolved placeholders: " + + ", ".join(unresolved) + ) + return rendered + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description=( + "Generate a private local Markdown review scaffold after intake " + "authorization and conflict controls pass." + ) + ) + parser.add_argument("intake", help="Local review intake JSON") + parser.add_argument("-o", "--output", required=True, help="Output Markdown path") + parser.add_argument( + "--force", action="store_true", help="Replace an existing output file" + ) + return parser + + +def main() -> int: + args = build_parser().parse_args() + try: + from _common import read_json + + rendered = generate(read_json(args.intake)) + destination = write_markdown(rendered, args.output, force=args.force) + print(f"Created local review scaffold: {destination}") + return 0 + except ValidationError as exc: + return error_exit(exc) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/scripts/lint_review.py b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/scripts/lint_review.py new file mode 100644 index 0000000..ff2fe89 --- /dev/null +++ b/plugins/hetaoBackend/scientific-research-workflows/skills/peer-review/scripts/lint_review.py @@ -0,0 +1,254 @@ +#!/usr/bin/env python3 +"""Lint a structured review for channel separation, tone, and actionability.""" + +from __future__ import annotations + +import argparse +import re +from typing import Any + +from _common import ( + ValidationError, + error_exit, + issue, + read_markdown, + write_json_report, +) + +AUTHOR_HEADING = "# Comments to authors" +EDITOR_HEADING = "# Confidential comments to editor" +COMMENT_HEADING_RE = re.compile( + r"^###\s+(?PMajor|Minor)\s+comment(?:\s+(?P[A-Za-z0-9._:-]+))?\s*$", + re.IGNORECASE, +) +FIELD_RE = re.compile( + r"^-\s*(?P