medsci-skills 5.8.0 → 5.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +4 -2
  2. package/installers/install.py +14 -1
  3. package/metadata/distribution_files.json +101 -26
  4. package/metadata/distribution_manifest.json +1 -1
  5. package/package.json +1 -1
  6. package/skills/analyze-stats/SKILL.md +14 -3
  7. package/skills/calc-sample-size/SKILL.md +47 -2
  8. package/skills/calc-sample-size/references/prediction_model_sample_size.md +97 -0
  9. package/skills/check-reporting/scripts/check_framework_naming.py +19 -9
  10. package/skills/check-reporting/tests/test_framework_naming.sh +16 -0
  11. package/skills/design-study/SKILL.md +2 -1
  12. package/skills/design-study/references/dag_adjustment.md +93 -0
  13. package/skills/design-study/references/target_trial_emulation.md +94 -0
  14. package/skills/design-study/scripts/adjustment_set_challenge/fixture/confounder.json +1 -0
  15. package/skills/design-study/scripts/adjustment_set_challenge/fixture/instrument.json +1 -0
  16. package/skills/design-study/scripts/adjustment_set_challenge/fixture/mbias.json +1 -0
  17. package/skills/design-study/scripts/adjustment_set_challenge/fixture/mediator.json +1 -0
  18. package/skills/design-study/scripts/adjustment_set_challenge/problem.md +35 -0
  19. package/skills/design-study/scripts/adjustment_set_challenge/verify.sh +45 -0
  20. package/skills/design-study/scripts/adjustment_set_helper.py +163 -0
  21. package/skills/design-study/skill.yml +1 -0
  22. package/skills/make-figures/references/exemplar_plots/README.md +14 -0
  23. package/skills/make-figures/scripts/render_core_figures.py +295 -0
  24. package/skills/make-figures/scripts/render_core_figures_challenge/fixture/synthetic_inputs.json +42 -0
  25. package/skills/make-figures/scripts/render_core_figures_challenge/problem.md +42 -0
  26. package/skills/make-figures/scripts/render_core_figures_challenge/verify.sh +37 -0
  27. package/skills/make-figures/skill.yml +1 -0
  28. package/skills/model-validation/SKILL.md +14 -1
  29. package/skills/model-validation/references/validation_design.md +32 -1
  30. package/skills/self-review/SKILL.md +37 -87
  31. package/skills/self-review/references/panel_review_template.md +5 -2
  32. package/skills/self-review/references/phases/confounding_completeness.md +90 -0
  33. package/skills/self-review/scripts/check_claim_artifact.py +42 -6
  34. package/skills/self-review/tests/test_claim_artifact.sh +24 -0
package/README.md CHANGED
@@ -51,7 +51,9 @@ integrity gates** for submission-grade biomedical research. As of **v5.0** it ad
51
51
  **model-engineering lane**: choose a paper-grounded architecture, scaffold a
52
52
  reproducible, leakage-safe PyTorch training repo, and validate, document, and
53
53
  evaluate a medical-imaging or LLM/MLLM model so the work reaches a paper — it
54
- **integrates** MONAI / nnU-Net, never reimplements them. Clinical AI model research
54
+ ships a minimal runnable default model for a forward-pass smoke test and
55
+ **integrates** MONAI / nnU-Net / timm / torchvision for production-grade models,
56
+ rather than reimplementing the ecosystem. Clinical AI model research
55
57
  engineering is in scope; it is **not** a diagnostic tool, an autonomous author, or a
56
58
  general AI-scientist platform, and every output requires human-expert verification.
57
59
  New here? See the [3 workflows below](#start-here-3-workflows), the
@@ -459,7 +461,7 @@ ma-scout -> search-lit -> fulltext-retrieval -> design-study ──> write-proto
459
461
  | **design-study** | Study design review: identifies analysis unit, cohort logic, data leakage risks, comparator design, validation strategy, and reporting guideline fit. |
460
462
  | **design-ai-benchmarking** | Design and validity review for benchmarking AI system(s) against a human-expert panel: evaluation-question and arm definition, decoupled multi-dimensional rubrics with anchors, planted calibration probes (positive-control / known-bad / instability / mechanism-contradiction), reviewer-panel construction with per-reviewer randomization, inter-rater reliability targets with separate control-item reliability, LLM-as-judge vs human-as-judge adjudication, construct-independence guards, and a structured JSON rating-export schema. Locks the rubric before data collection. |
461
463
  | **model-validation** | Design or audit the clinical-validation study for an engineer-built medical-imaging model (segmentation / classification / detection): patient-level split disjointness and the data-leakage taxonomy, tuning-on-test, internal vs genuine external validation, comparator design, single-run vs multi-seed variance, task-correct metric selection (Metrics Reloaded), test-set sizing, and CLAIM 2024 / TRIPOD+AI / STARD-AI reporting fit. Ships a deterministic split-leakage gate that proves patient disjointness by set arithmetic on the emitted split table. Integrates with MONAI / nnU-Net — does not replace them. |
462
- | **model-scaffold** | Generate a reproducible, runnable PyTorch training repo for a medical-imaging task — segmentation (U-Net), classification, detection, image-to-image synthesis, or self-supervised pretraining — the missing middle link between choosing an architecture and validating a trained model. Emits a patient-level seed-locked split as an auditable artifact, a task-appropriate model, train/evaluate scripts that seed every RNG and infer under eval mode, a config, requirements, a reproducibility record, and a Methods stub with VERIFY placeholders (no fabricated numbers). Reproducibility holds by construction; ships a `check_training_hygiene` AST gate + a network-free build→validate challenge. Integrates with MONAI / nnU-Net / TorchIO / timm / torchvision does not reimplement them. |
464
+ | **model-scaffold** | Generate a reproducible, runnable PyTorch training repo for a medical-imaging task — segmentation (U-Net), classification, detection, image-to-image synthesis, or self-supervised pretraining — the missing middle link between choosing an architecture and validating a trained model. Emits a patient-level seed-locked split as an auditable artifact, a task-appropriate model, train/evaluate scripts that seed every RNG and infer under eval mode, a config, requirements, a reproducibility record, and a Methods stub with VERIFY placeholders (no fabricated numbers). Reproducibility holds by construction; ships a `check_training_hygiene` AST gate + a network-free build→validate challenge. Emits a minimal runnable default model for a forward-pass smoke test, and integrates with MONAI / nnU-Net / TorchIO / timm / torchvision for production-grade models. |
463
465
  | **architecture-zoo** | "Which architecture for which research question" decision tool: maps task (classification / segmentation / detection / transfer), modality, data scale, and class imbalance to a paper-grounded architecture shortlist. Curates the foundational curriculum (ResNet / DenseNet / EfficientNet / ViT / Swin; U-Net / 3-D U-Net / Attention & Residual U-Net / nnU-Net / Mask R-CNN; SAM/MedSAM / TotalSegmentator / BiomedCLIP / DINO / MAE / SimCLR) — each with core idea, when-to-use, medical-imaging use, reference implementation, validation setup, and the matching model-scaffold template. Advisory; teaches archetypes, not a live SOTA leaderboard. |
464
466
  | **model-card** | Generate the documentation an engineer-built medical-imaging model must carry — a Model Card (Mitchell et al. 2019), a Datasheet for its dataset (Gebru et al. 2021), and a METRIC-informed data-quality pass — filled from user-supplied facts (never fabricated), then verify every required section is present and non-empty with a deterministic completeness gate (`check_model_card_complete`). Model Card / Datasheet are documentation standards vendored as templates, not counted reporting checklists. |
465
467
  | **model-evaluation** | Compute and report task-correct held-out metrics for a trained medical-imaging model — segmentation (Dice + a boundary metric HD95/NSD, per structure), classification (AUROC + AUPRC + sensitivity/specificity with bootstrap CIs at the deployment prevalence), or detection (FROC/mAP with a stated IoU criterion) — plus calibration and subgroup slices. Emits a per-case table for analyze-stats and gates the metric choice against Metrics Reloaded / CLAIM 2024 (`check_metric_reporting`). Numbers come only from executed code. |
@@ -167,10 +167,23 @@ def run_self_test() -> int:
167
167
  return 0
168
168
 
169
169
 
170
+ LOG_DIR = REPO_ROOT / "installers" / ".logs"
171
+ LOG_KEEP = 10 # retain the most recent N install logs; prune older
172
+
173
+
170
174
  def write_log(log_lines: list[str]) -> Path:
175
+ """Write the timestamped install log to installers/.logs/ (gitignored) and keep only
176
+ the most recent LOG_KEEP — logs used to accumulate in the repo root."""
177
+ LOG_DIR.mkdir(parents=True, exist_ok=True)
171
178
  stamp = dt.datetime.now().strftime("%Y%m%d-%H%M%S")
172
- log_path = REPO_ROOT / f"{stamp}-{LOG_NAME}"
179
+ log_path = LOG_DIR / f"{stamp}-{LOG_NAME}"
173
180
  log_path.write_text("\n".join(log_lines) + "\n", encoding="utf-8")
181
+ old = sorted(LOG_DIR.glob(f"*-{LOG_NAME}"))
182
+ for stale in old[:-LOG_KEEP]:
183
+ try:
184
+ stale.unlink()
185
+ except OSError:
186
+ pass
174
187
  return log_path
175
188
 
176
189
 
@@ -23,8 +23,8 @@
23
23
  },
24
24
  {
25
25
  "path": "installers/install.py",
26
- "size": 14230,
27
- "sha256": "f1a092bbc5ea676a3bfca3dcb724e9e64a07dc1e1e2414ff696fc21cf4f7f8c0"
26
+ "size": 14720,
27
+ "sha256": "b22e5d5be62ba1f71a81ad3214aed2cbfefbf97fd52179b21a8452a11ae95fcf"
28
28
  },
29
29
  {
30
30
  "path": "installers/medsci_txn.py",
@@ -153,8 +153,8 @@
153
153
  },
154
154
  {
155
155
  "path": "skills/analyze-stats/SKILL.md",
156
- "size": 54473,
157
- "sha256": "6f7a194db60588e84ddf8e62e80d6e143d14265e21bdac35efa6efbbdd5d5ffc"
156
+ "size": 55302,
157
+ "sha256": "3809b6a55e8a79b7dc48f617e3be0a7faf2a96a6f0ba03d58977a39f267cce81"
158
158
  },
159
159
  {
160
160
  "path": "skills/analyze-stats/references/analysis_guides/health_economic_evaluation.md",
@@ -513,8 +513,8 @@
513
513
  },
514
514
  {
515
515
  "path": "skills/calc-sample-size/SKILL.md",
516
- "size": 20967,
517
- "sha256": "da3427c78bf251def5433b5ffad106e9069770be347668e6b14bb3ce8e925d40"
516
+ "size": 23904,
517
+ "sha256": "0bed1ee5d43f044a490d77fac181900f9e513dfd379831d0446b118b4768b798"
518
518
  },
519
519
  {
520
520
  "path": "skills/calc-sample-size/references/formulas.md",
@@ -531,6 +531,11 @@
531
531
  "size": 2142,
532
532
  "sha256": "97062f5aa63ce8f5b5e340e6b65683720b3ce1d94e34d201977d9a2601293038"
533
533
  },
534
+ {
535
+ "path": "skills/calc-sample-size/references/prediction_model_sample_size.md",
536
+ "size": 5074,
537
+ "sha256": "bf3d42ad59d2a3b3abc2f00db9559de671514b7b64bd4cf44ec53717966dc635"
538
+ },
534
539
  {
535
540
  "path": "skills/calc-sample-size/skill.yml",
536
541
  "size": 2958,
@@ -808,8 +813,8 @@
808
813
  },
809
814
  {
810
815
  "path": "skills/check-reporting/scripts/check_framework_naming.py",
811
- "size": 7719,
812
- "sha256": "bb71f1628375779e00438e6ec317c9637cd7bb9c82d71db516097c991f520d58"
816
+ "size": 8455,
817
+ "sha256": "bd44ffb3f55d6539f972cd4bf50f1d9301e865da41e05c8d279776e94fb6d3d7"
813
818
  },
814
819
  {
815
820
  "path": "skills/check-reporting/scripts/check_prisma_figure.py",
@@ -1003,13 +1008,58 @@
1003
1008
  },
1004
1009
  {
1005
1010
  "path": "skills/design-study/SKILL.md",
1006
- "size": 18282,
1007
- "sha256": "dfd73871102af6beab3d21f60f650db41ec2e4025d6ee19eae8de61b17851fd4"
1011
+ "size": 19220,
1012
+ "sha256": "d4a05f6dd2530620bd1ec3e1c64ba0625aae25def12892247e61a77724132cd6"
1013
+ },
1014
+ {
1015
+ "path": "skills/design-study/references/dag_adjustment.md",
1016
+ "size": 4390,
1017
+ "sha256": "8b6c9d2b2d4e79652b355cf30f0d56f4232619cad8589489707107f85bef9de3"
1018
+ },
1019
+ {
1020
+ "path": "skills/design-study/references/target_trial_emulation.md",
1021
+ "size": 6025,
1022
+ "sha256": "3f3b8aa8256689a2109e06b0eaa92f0e793869838711c19f0b2d9e9e01dcb0ff"
1023
+ },
1024
+ {
1025
+ "path": "skills/design-study/scripts/adjustment_set_challenge/fixture/confounder.json",
1026
+ "size": 111,
1027
+ "sha256": "3d1c43f40a41e91f674cca1d47fd9bb8566e9d74f9ebb18aa097a41fbf0b11ff"
1028
+ },
1029
+ {
1030
+ "path": "skills/design-study/scripts/adjustment_set_challenge/fixture/instrument.json",
1031
+ "size": 199,
1032
+ "sha256": "5a99731963404b122cb3e233cc5c850885fe42cc49c9b1cfbac0206ea936703b"
1033
+ },
1034
+ {
1035
+ "path": "skills/design-study/scripts/adjustment_set_challenge/fixture/mbias.json",
1036
+ "size": 182,
1037
+ "sha256": "b05f7d537b9a25841172d4325fba858933aedab20718ac78e58547d11f8b6561"
1038
+ },
1039
+ {
1040
+ "path": "skills/design-study/scripts/adjustment_set_challenge/fixture/mediator.json",
1041
+ "size": 106,
1042
+ "sha256": "9e3fe1ed554a115058e9d7916856eaeca4d666c00741282960320cb87e1cfe91"
1043
+ },
1044
+ {
1045
+ "path": "skills/design-study/scripts/adjustment_set_challenge/problem.md",
1046
+ "size": 2264,
1047
+ "sha256": "79cba6ed5f3259a9e633c88d7a4d6d08994fff5f4f8461482dd86bcd2c7239ab"
1048
+ },
1049
+ {
1050
+ "path": "skills/design-study/scripts/adjustment_set_challenge/verify.sh",
1051
+ "size": 2672,
1052
+ "sha256": "87349a1eb69b1c5e1b6120c6e5b1269bc830ef91803e7f09219da2285451bb76"
1053
+ },
1054
+ {
1055
+ "path": "skills/design-study/scripts/adjustment_set_helper.py",
1056
+ "size": 8231,
1057
+ "sha256": "239948c31d513f95f834fdf2d48e9938f7e585a2ebb12387a5abf3b06305dca0"
1008
1058
  },
1009
1059
  {
1010
1060
  "path": "skills/design-study/skill.yml",
1011
- "size": 1357,
1012
- "sha256": "6d90940e8eb13c65c6db08bfd4abdf5cf6f5cb01972ef205e19e81810ae9eb49"
1061
+ "size": 1411,
1062
+ "sha256": "3981d5da7734b90e6905ac329e996b145af82bd6a28ec2a8d42918e9ce077ee5"
1013
1063
  },
1014
1064
  {
1015
1065
  "path": "skills/fill-icmje-coi/SKILL.md",
@@ -1953,8 +2003,8 @@
1953
2003
  },
1954
2004
  {
1955
2005
  "path": "skills/make-figures/references/exemplar_plots/README.md",
1956
- "size": 4913,
1957
- "sha256": "a5a320c785154ec73e41863302c2b625c9a24aeb213cfac0f195414aa1570f01"
2006
+ "size": 5872,
2007
+ "sha256": "32f93f0c65b2fe353b17dd1345267c85d5646fcf7de7adb9f784be551fc900af"
1958
2008
  },
1959
2009
  {
1960
2010
  "path": "skills/make-figures/references/exemplar_plots/bland_altman.md",
@@ -2116,6 +2166,26 @@
2116
2166
  "size": 17167,
2117
2167
  "sha256": "88c5e0829bb7c6d1fb6d6e1f45e1ce61bccdd91a7ed9a153623e51d8bc30b651"
2118
2168
  },
2169
+ {
2170
+ "path": "skills/make-figures/scripts/render_core_figures.py",
2171
+ "size": 14436,
2172
+ "sha256": "35f91147d51dd73b75dc116be9e0a80588fe1daf502966e82468d3c62b12f25d"
2173
+ },
2174
+ {
2175
+ "path": "skills/make-figures/scripts/render_core_figures_challenge/fixture/synthetic_inputs.json",
2176
+ "size": 1429,
2177
+ "sha256": "24c0944fcb1384f073988d0f6e28b8f375ba4a7eff62fb4e21bc7066726d5ebc"
2178
+ },
2179
+ {
2180
+ "path": "skills/make-figures/scripts/render_core_figures_challenge/problem.md",
2181
+ "size": 2485,
2182
+ "sha256": "a4e62161806d95b637d9ce984541190e5e9e213e2522d1f8740d847ee10c1a30"
2183
+ },
2184
+ {
2185
+ "path": "skills/make-figures/scripts/render_core_figures_challenge/verify.sh",
2186
+ "size": 1907,
2187
+ "sha256": "7f05fd9cd4e2bfdf6c5b7e8e2a61bee5f8fd44d7d2746e29e2ae77adec84b9c0"
2188
+ },
2119
2189
  {
2120
2190
  "path": "skills/make-figures/scripts/validate_pptx_mac_compat.py",
2121
2191
  "size": 8662,
@@ -2123,8 +2193,8 @@
2123
2193
  },
2124
2194
  {
2125
2195
  "path": "skills/make-figures/skill.yml",
2126
- "size": 2146,
2127
- "sha256": "bc6f4a4d47ea7a53b733733ee356337edb8803a76fc0d2deb1d889f5931a7b1d"
2196
+ "size": 2205,
2197
+ "sha256": "b973c376998b15cefe9bffdf580889f905b60ddc2dead00cc5cbc900a6fd50f5"
2128
2198
  },
2129
2199
  {
2130
2200
  "path": "skills/make-figures/templates/official/NOTES.md",
@@ -2773,13 +2843,13 @@
2773
2843
  },
2774
2844
  {
2775
2845
  "path": "skills/model-validation/SKILL.md",
2776
- "size": 9810,
2777
- "sha256": "1a75a5a1f21f5a8778d0b77db2e99574bf37edda2a291d8dde6aafea4a207ff0"
2846
+ "size": 10836,
2847
+ "sha256": "5cfd4ac6d75b3a474408b10d8eead3fdf7be8a44e1578dcac332cc2203ae75fe"
2778
2848
  },
2779
2849
  {
2780
2850
  "path": "skills/model-validation/references/validation_design.md",
2781
- "size": 11427,
2782
- "sha256": "16d43b688ea63745174c7ca8fafd78a7342b26c34ad1e10e1fdbc117cceafb2e"
2851
+ "size": 13780,
2852
+ "sha256": "c7ad02c2679b241d94af70c05f22740a6ad75c61c0c70d71ba2e48c4f3d64bf5"
2783
2853
  },
2784
2854
  {
2785
2855
  "path": "skills/model-validation/scripts/check_split_leakage.py",
@@ -3418,8 +3488,8 @@
3418
3488
  },
3419
3489
  {
3420
3490
  "path": "skills/self-review/SKILL.md",
3421
- "size": 105186,
3422
- "sha256": "202bf201cbe1b5dfc3703cddaaf2dfc025af5456d5446656132b1545243b0098"
3491
+ "size": 102413,
3492
+ "sha256": "97219c57684d4eaced9e35b0968ca09da7731095c186a4e97757b4039f43f2f6"
3423
3493
  },
3424
3494
  {
3425
3495
  "path": "skills/self-review/references/domain-probes/ai_overclaiming.md",
@@ -3573,8 +3643,13 @@
3573
3643
  },
3574
3644
  {
3575
3645
  "path": "skills/self-review/references/panel_review_template.md",
3576
- "size": 14863,
3577
- "sha256": "a44567b5ea0c47971bcc75b0e6c46f3846d687240b0607ded07183d6fbaad86f"
3646
+ "size": 15095,
3647
+ "sha256": "f9c084e950602ec88f7465dc953ed2309148cab37a3bef5f1b4c80deca2707a4"
3648
+ },
3649
+ {
3650
+ "path": "skills/self-review/references/phases/confounding_completeness.md",
3651
+ "size": 5344,
3652
+ "sha256": "051f2b066d2d708b161c1e97d666d9e214d128f1413a9c4822f0333e748bda14"
3578
3653
  },
3579
3654
  {
3580
3655
  "path": "skills/self-review/scripts/check_artifact_coverage.py",
@@ -3593,8 +3668,8 @@
3593
3668
  },
3594
3669
  {
3595
3670
  "path": "skills/self-review/scripts/check_claim_artifact.py",
3596
- "size": 10757,
3597
- "sha256": "2cc6690c917ecb064c9e6834fce6162787881599ba845f8cb6ecddd4d65520b6"
3671
+ "size": 13065,
3672
+ "sha256": "ac29a7e8145152e6641fce8eaf98766cfc943a034db72252cc38538d7ab59db2"
3598
3673
  },
3599
3674
  {
3600
3675
  "path": "skills/self-review/scripts/check_classical_style.py",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schema_version": 1,
3
- "version": "5.8.0",
3
+ "version": "5.9.0",
4
4
  "owned_skills": [
5
5
  "academic-aio",
6
6
  "add-journal",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "medsci-skills",
3
- "version": "5.8.0",
3
+ "version": "5.9.0",
4
4
  "description": "MedSci Skills — a medical/scientific research skill suite for AI coding agents (Claude Code, Codex, Cursor, Copilot). The npm package is a terminal-friendly installer shortcut; the canonical distribution remains the GitHub repository and the Claude Code plugin marketplace.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://github.com/Aperivue/medsci-skills#readme",
@@ -283,9 +283,20 @@ rho = 0.39 between a fasting marker (IQR 0.6 to 3.5 units, SD 3.05) and an index
283
283
  marker is associated with about 0.8 index units higher on average (monotonic-linear approximation;
284
284
  crude, unadjusted)."
285
285
 
286
- **Output contract**: add a "Real-world translation" line beneath each primary effect size in the
287
- results table or its footnote. For OR/HR/RR primary outcomes, add an NNT/NNH line with the baseline
288
- risk stated.
286
+ **Output contract (clinical-utility is a default, not an optional add-on).** Report every
287
+ primary effect in units a clinician acts on, by default do not leave these as prose to be
288
+ added later:
289
+ - **OR/HR/RR primary outcomes** → report the relative measure **and** the absolute risk at
290
+ a stated baseline + absolute risk difference + **NNT** (or NNH = 1/ARI), baseline risk
291
+ explicit. A relative-only headline is incomplete.
292
+ - **Continuous outcomes** → add the IQR/clinically-anchored "Real-world translation" line
293
+ beneath the effect size.
294
+ - **Prediction / classification (incl. medical-AI) models** → a **decision-curve /
295
+ net-benefit** pass at the relevant threshold is standard output, not just AUC +
296
+ calibration. An incremental claim reports added **net benefit / NRI / IDI over the
297
+ established clinical model**, not the new model's AUC alone. See
298
+ `references/table-standards/table-types/incremental_value.md` and the `make-figures`
299
+ `decision_curve` exemplar (and `render_core_figures.py` for the rendered curve).
289
300
 
290
301
  ## Error Handling
291
302
 
@@ -20,6 +20,7 @@ Python (alternative), interpret effect sizes clinically, and produce IRB-ready j
20
20
 
21
21
  - **Formulas**: `${CLAUDE_SKILL_DIR}/references/formulas.md` -- mathematical formulas, R/Python functions, effect size conventions
22
22
  - **Observational cohort precision branch**: `${CLAUDE_SKILL_DIR}/references/observational_cohort.md`
23
+ - **Prediction-model / medical-AI sample size (Riley)**: `${CLAUDE_SKILL_DIR}/references/prediction_model_sample_size.md` -- the current TRIPOD+AI-aligned standard for a clinical prediction/classification model (development via `pmsampsize`, external validation via `pmvalsampsize`, net-benefit precision). Use this instead of EPV-10 whenever the goal is risk prediction for use rather than a single-predictor hypothesis test (Tests 12-13).
23
24
  - **Justification prose exemplars**: `${CLAUDE_SKILL_DIR}/references/justification_examples.md` -- reviewer-safe IRB/Methods justification paragraphs per design (proportions, means, DTA precision, survival/log-rank, ICC agreement, non-inferiority), each stating the five required elements; load when producing the justification text
24
25
  - **Existing R template**: See `analyze-stats` skill at `references/templates/sample_size.R` for the 7 original tests
25
26
 
@@ -49,7 +50,10 @@ What is your primary outcome?
49
50
  | | +-- NO --> How many groups?
50
51
  | | +-- 2 groups, superiority --> [4] Two-proportion comparison (chi-square)
51
52
  | | +-- 2 groups, non-inferiority --> [10] Non-inferiority / equivalence
52
- | | +-- Multivariable model --> [9] Logistic regression
53
+ | | +-- Multivariable model --> single-predictor hypothesis test? --> [9] Logistic regression
54
+ | | --> clinical prediction / AI model for use?
55
+ | | +-- developing the model --> [12] Prediction-model development (Riley)
56
+ | | +-- externally validating --> [13] External-validation (Riley)
53
57
  | |
54
58
  +-- Continuous (measurement, score)
55
59
  | |
@@ -242,7 +246,10 @@ What is your primary outcome?
242
246
  **Approach A: Peduzzi Rule of Thumb (EPV >= 10)**
243
247
  - N_events = 10 * n_predictors
244
248
  - N_total = N_events / event_rate
245
- - Simple, widely cited, conservative. Use as a minimum baseline.
249
+ - Simple, widely cited, conservative. Use as a minimum baseline **for a single-predictor
250
+ hypothesis test only**. For a **clinical prediction / medical-AI model intended for use**,
251
+ EPV-10 is outdated and reviewer-vulnerable — use the Riley criteria in Test 12
252
+ (development) / Test 13 (validation) instead.
246
253
 
247
254
  **Approach B: Hsieh (1989) Formula**
248
255
  - Uses the OR of interest for the primary predictor to calculate a more precise sample size.
@@ -320,6 +327,44 @@ N_adj = N_total / (1 - attrition_rate)
320
327
 
321
328
  ---
322
329
 
330
+ ### Test 12: Prediction-Model Development (Riley)
331
+
332
+ **When to use**: developing a clinical prediction / classification model (including a
333
+ medical-AI model evaluated as one) — the goal is risk prediction *for use*, not a single
334
+ predictor's hypothesis test. EPV-10 (Tests 9/11) is outdated here.
335
+
336
+ **Approach**: the minimum N is the largest satisfying all four Riley criteria
337
+ simultaneously — global shrinkage ≥ 0.9, apparent–adjusted R² gap ≤ 0.05, precise overall
338
+ risk estimate, and (time-to-event) precise baseline survival. Implemented in R `pmsampsize`.
339
+
340
+ **Required parameters**: number of **candidate predictor parameters** (count dummy/non-linear
341
+ terms), a conservative expected **C-statistic or Cox-Snell R²** (with its literature source),
342
+ and outcome **prevalence** (binary) or **event rate + mean follow-up** (time-to-event).
343
+
344
+ Read `${CLAUDE_SKILL_DIR}/references/prediction_model_sample_size.md` for the criteria, the
345
+ `pmsampsize` code, and the reporting requirements. Report N + required events + the binding
346
+ criterion + the assumed C/R² and its source.
347
+
348
+ ---
349
+
350
+ ### Test 13: External-Validation Sample Size (Riley)
351
+
352
+ **When to use**: sizing an **external validation** of an existing prediction/AI model.
353
+
354
+ **Approach**: size to estimate the key validation metrics *precisely enough to be
355
+ conclusive* — target the CI width of the **C-statistic**, the **calibration slope**, the
356
+ **calibration-in-the-large / O:E ratio**, and (if a utility claim) **net benefit**.
357
+ Implemented in R `pmvalsampsize`. A floor of ≥ 100 events and ≥ 100 non-events applies, but
358
+ the precise target is usually larger.
359
+
360
+ **Required parameters**: expected **prevalence**, anticipated **C-statistic**, and the
361
+ target CI widths.
362
+
363
+ Read `${CLAUDE_SKILL_DIR}/references/prediction_model_sample_size.md` for the `pmvalsampsize`
364
+ code. Report the targeted CI widths and the resulting events / non-events.
365
+
366
+ ---
367
+
323
368
  ## Scope Limitations
324
369
 
325
370
  ### Supported
@@ -0,0 +1,97 @@
1
+ # Prediction-model sample size (Riley) — development & external validation
2
+
3
+ For a **clinical prediction model** (or a medical-AI model evaluated as one), the
4
+ events-per-variable rule of thumb (EPV ≥ 10) is **outdated and reviewer-vulnerable**. EPV
5
+ does not target the things that determine whether a developed model is trustworthy
6
+ (overfitting, precise risk estimates) or whether a validation is conclusive (precise
7
+ discrimination and calibration). Use the **Riley criteria** instead — they are the current
8
+ TRIPOD+AI-aligned standard, and they are implemented in the R packages `pmsampsize`
9
+ (development) and `pmvalsampsize` (external validation).
10
+
11
+ Apply this whenever the goal is **risk prediction / classification for use**, not a single
12
+ predictor's hypothesis test. (For a single-predictor association test, the Hsieh/logistic
13
+ power calculation in Test 9 is appropriate.)
14
+
15
+ ## Development sample size — `pmsampsize` (Riley et al.)
16
+
17
+ The minimum development sample size is the largest N satisfying four criteria
18
+ simultaneously:
19
+
20
+ 1. **Small overfitting** — global shrinkage factor ≥ 0.9.
21
+ 2. **Small optimism in apparent fit** — absolute difference between apparent and adjusted
22
+ Nagelkerke R² ≤ 0.05.
23
+ 3. **Precise estimate of overall risk** — margin of error of the mean predicted outcome
24
+ (the intercept) ≤ 0.05.
25
+ 4. **(time-to-event)** precise estimate of the baseline survival at a key time point.
26
+
27
+ Inputs you must specify (and justify from prior literature, not guess):
28
+ - number of **candidate predictor parameters** (count dummy variables and non-linear terms,
29
+ not just variables);
30
+ - the anticipated model performance — a conservative **Cox-Snell R²** (derivable from an
31
+ expected **C-statistic** + outcome prevalence) or expected R²;
32
+ - the **outcome prevalence** (binary) or **event rate + mean follow-up** (time-to-event),
33
+ or outcome mean/SD (continuous).
34
+
35
+ ```r
36
+ library(pmsampsize)
37
+ # Binary outcome: prevalence 0.20, 30 candidate parameters, expected C ≈ 0.78
38
+ # (convert C to a conservative Cox-Snell R²; pmsampsize accepts csrsquared or cstatistic)
39
+ pmsampsize(type = "b", cstatistic = 0.78, parameters = 30, prevalence = 0.20)
40
+
41
+ # Time-to-event: rate per person-year, mean follow-up, timepoint of interest
42
+ pmsampsize(type = "s", csrsquared = 0.05, parameters = 30,
43
+ rate = 0.08, timepoint = 5, meanfup = 4.2)
44
+ ```
45
+
46
+ Report the **minimum N and the required number of events**, the assumed C-statistic / R²
47
+ and its source, and the binding criterion. A development set below this is a standard
48
+ high-impact-journal rejection.
49
+
50
+ ## External-validation sample size — `pmvalsampsize` (Riley et al.)
51
+
52
+ Size the validation set to estimate the key performance metrics **precisely enough to be
53
+ conclusive** — target the confidence-interval width of each:
54
+
55
+ - **discrimination** — C-statistic (target CI width, e.g. ±0.05);
56
+ - **calibration slope** (target SE / CI width);
57
+ - **calibration-in-the-large / O:E ratio**;
58
+ - **(if reporting clinical utility)** the standardized **net benefit** at the decision
59
+ threshold.
60
+
61
+ ```r
62
+ library(pmvalsampsize)
63
+ # Validate a model with expected prevalence 0.20, anticipated C ≈ 0.78,
64
+ # targeting a C-statistic CI half-width of 0.05 and a precise calibration slope
65
+ pmvalsampsize(type = "b", prevalence = 0.20, cstatistic = 0.78,
66
+ cslope = 1, oe = 1, lpnormal = c(-0.5, 0.5),
67
+ cstatci = 0.10) # target CI *width* for the C-statistic
68
+ ```
69
+
70
+ A common **floor** is ≥ 100 events and ≥ 100 non-events (Vergouwe; Collins), but
71
+ `pmvalsampsize` gives the precise target for your metrics and usually requires more.
72
+
73
+ ## Sample size for net benefit / decision-curve
74
+
75
+ If the model's value claim rests on **net benefit** (decision-curve analysis), size the
76
+ study to estimate net benefit at the relevant threshold with adequate precision —
77
+ `pmvalsampsize` supports a net-benefit target, and the development criteria should be met
78
+ so the model is not overfit before its utility is judged. Do not claim clinical utility
79
+ from a decision curve estimated on an underpowered validation set.
80
+
81
+ ## Reporting
82
+
83
+ - State that sizing followed the **Riley criteria** (cite the originals) — not EPV-10.
84
+ - Report the assumed C-statistic / R² / prevalence and **where they came from**.
85
+ - For development, report N + events + the binding criterion; for validation, report the
86
+ targeted CI widths and the resulting N (events and non-events).
87
+
88
+ ## References (cite the originals)
89
+
90
+ - Riley RD, Snell KIE, Ensor J, et al. Minimum sample size for developing a multivariable
91
+ prediction model: PART II — binary and time-to-event outcomes. *Stat Med* 2019.
92
+ - Riley RD, Ensor J, Snell KIE, et al. Calculating the sample size required for developing
93
+ a clinical prediction model. *BMJ* 2020.
94
+ - Riley RD, Debray TPA, Collins GS, et al. Minimum sample size for external validation of a
95
+ clinical prediction model with a binary outcome. *Stat Med* 2021.
96
+ - Collins GS, Ogundimu EO, Altman DG. Sample size considerations for the external
97
+ validation of a multivariable prognostic model. *Stat Med* 2016.
@@ -65,6 +65,11 @@ VAGUE = re.compile(
65
65
  r"\b(?:recent|current|emerging|latest|evolving)\b\s+"
66
66
  r"(?:best[- ]practice|guidance|practice|recommendations?|standards?|guidelines?)",
67
67
  re.I)
68
+ # VAGUE only counts inside a reporting-framework context; otherwise "recent
69
+ # best-practice recommendations" about a method is a false positive (F05).
70
+ REPORTING_CUE = re.compile(
71
+ r"\b(?:report(?:ing|ed)?|checklist|EQUATOR|reporting\s+standard|"
72
+ r"reporting\s+framework)\b", re.I)
68
73
 
69
74
 
70
75
  def _sentences(text: str) -> list[str]:
@@ -128,15 +133,20 @@ def check(text: str) -> list[dict]:
128
133
  "where": m,
129
134
  })
130
135
 
131
- # VAGUE_GUIDANCE
132
- for m in VAGUE.finditer(text):
133
- claims.append({
134
- "verdict": "VAGUE_GUIDANCE",
135
- "severity": "Minor",
136
- "detail": (f"vague wording '{m.group(0).strip()}' — name the specific framework "
137
- f"and cite it"),
138
- "where": m.group(0).strip()[:80],
139
- })
136
+ # VAGUE_GUIDANCE — only when the sentence is clearly about REPORTING (a reporting
137
+ # guideline / checklist) yet names no specific framework. Gating on a reporting cue
138
+ # prevents firing on method-level wording like "external validation following recent
139
+ # best-practice recommendations", which is not a reporting-framework claim at all.
140
+ for s in _sentences(text):
141
+ m = VAGUE.search(s)
142
+ if m and REPORTING_CUE.search(s):
143
+ claims.append({
144
+ "verdict": "VAGUE_GUIDANCE",
145
+ "severity": "Minor",
146
+ "detail": (f"vague wording '{m.group(0).strip()}' in a reporting context — name "
147
+ f"the specific framework and cite it"),
148
+ "where": m.group(0).strip()[:80],
149
+ })
140
150
 
141
151
  return claims
142
152
 
@@ -41,5 +41,21 @@ check "SELF_COINED_LABEL detected (12-AI)" has_verdict SELF_COINED_LABEL
41
41
  python3 "$SCRIPT" --manuscript "$CLEAN" --strict --quiet >/dev/null 2>&1
42
42
  check "exit 0 on clean manuscript (base named + cited)" test "$?" -eq 0
43
43
 
44
+ # (3) FP guard (less-defensive, F05): method-level "recent best-practice recommendations"
45
+ # with NO reporting context must NOT trigger VAGUE_GUIDANCE.
46
+ NOFP="$(mktemp -t fw_nofp_XXXX).md"
47
+ trap 'rm -f "$OUT" "$NOFP"' EXIT
48
+ cat > "$NOFP" <<'EOF'
49
+ # Methods
50
+ We performed external validation following recent best-practice recommendations.
51
+ The imputation strategy was aligned with current guidance for handling missingness.
52
+ EOF
53
+ python3 "$SCRIPT" --manuscript "$NOFP" --out "$OUT" --quiet >/dev/null 2>&1
54
+ check "no VAGUE_GUIDANCE on method-level recommendations (no reporting cue)" python3 -c "
55
+ import json
56
+ d=json.load(open('$OUT'))
57
+ raise SystemExit(0 if not any(c['verdict']=='VAGUE_GUIDANCE' for c in d['claims']) else 1)
58
+ "
59
+
44
60
  echo "fail=$fail"; [[ "$fail" -eq 0 ]] && echo "ALL PASS" || echo "FAILURES: $fail"
45
61
  exit "$fail"
@@ -275,7 +275,8 @@ Recommend one primary guideline:
275
275
  - healthy volunteer bias unacknowledged → inflated external validity claims
276
276
  - surveillance bias from unequal follow-up frequency between groups
277
277
  - **3 bias classification (Hernan/Robins)**: selection bias (who enters), information bias (how measured), confounding (what else differs) — explicitly map each threat
278
- - **confounding completeness**: pre-specify the adjustment set from a DAG (not a Table-1 p < 0.05 rule), and plan to report whether any measured covariate that turns out imbalanced by exposure but outside the adjustment set leaves the primary estimate robust (an extended-adjustment sensitivity model). At review time `/self-review` Phase 2.5e + the O1–O12 probes in `observational_confounding.md` check this against Table 1 (including O7 over-adjustment, O10 overlapping-subset-gradient discipline, for complex-survey data O11 design-based weighting and O12 data-driven-threshold mining, O13 a cross-sectional mediation claim cannot order X→M→Y, and O14 a synergy/joint-effect claim needs the additive interaction scale (RERI/AP/S), not a multiplicative-only test).
278
+ - **comparative / causal question emulate a target trial.** For a treatment-vs-treatment, screening-vs-no-screening, or drug-A-vs-drug-B question on routinely-collected data, specify the seven target-trial components (eligibility, strategies, assignment, **time zero**, outcome, causal contrast, analysis plan) before extraction this is what prevents the immortal-time / prevalent-user / confounding-by-indication trio above and turns an association into a defensible causal contrast. New-user + active-comparator design, grace-period clone-censor-weight, and negative controls are in `references/target_trial_emulation.md`.
279
+ - **confounding completeness**: pre-specify the adjustment set from a DAG (not a Table-1 p < 0.05 rule), and plan to report whether any measured covariate that turns out imbalanced by exposure but outside the adjustment set leaves the primary estimate robust (an extended-adjustment sensitivity model). Build the DAG and pre-screen the proposed covariates with `scripts/adjustment_set_helper.py` (flags mediator / collider / descendant adjustment and omitted confounders, and proposes a candidate backdoor set), then derive the **minimal** sufficient set with dagitty — see `references/dag_adjustment.md`. At review time `/self-review` Phase 2.5e + the O1–O12 probes in `observational_confounding.md` check this against Table 1 (including O7 over-adjustment, O10 overlapping-subset-gradient discipline, for complex-survey data O11 design-based weighting and O12 data-driven-threshold mining, O13 — a cross-sectional mediation claim cannot order X→M→Y, and O14 — a synergy/joint-effect claim needs the additive interaction scale (RERI/AP/S), not a multiplicative-only test).
279
280
 
280
281
  ### Multimodal LLM / report generation
281
282
  - no clear rubric for clinical correctness