# 统一结果来源与复现 v2.2 · 第 3 步

本步把 24 例模拟、96 例模拟和一篇 NHANES 论文的限定模型组织为同一结果契约。第 2 步的研究卡片仍保留。版本为 `2.2.0-dev.3`，完整 v2.2 尚未发布。

## 一个数字从哪里来

[机器可读对象](assets/分析结果v2.2.json)是页面、基线表、效应图、图注和结果段落的共同来源。[结果报告](assets/统一结果报告v2.2.md)、[产物散列](assets/产物来源v2.2.json)和[基线表](assets/基线表v2.2.html)由生成器派生，不能单独手改数字。打开离线网页只展示上次保存产物，不会重新拟合模型。

旧的教学示意图及 Q–Q / CRP 分布图保留为冻结图像，本轮没有重新绘制。分布图的分母说明和数值诊断从新对象生成，但不声称所有历史图片已统一生成或实际重新绘制。

`cases` 中每个案例至少包含：

| 字段 | 记录什么 |
|---|---|
| `execution` | `snapshot-import` 或 `recomputed`、该次时间、实际复算范围及数值对照状态 |
| `data` / `code` | 文件、版本化路径、SHA-256；公共源 URL；实现与作者代码的区别 |
| `population` / `denominators` | 分析单位、纳排、观测与总人数、变量级已知分母；调查未加权人数不冒充加权总体 |
| `estimand` / `direction` | 回答的量及比较方向；均值差、OR 和屈光度系数分开 |
| `estimates` | 估计、单位、CI 水平 / 界限 / 算法及模型指标；论文两种 CI 分别保留 |
| `statistics` | 长格式的变量、组、上下文、统计名称、数值与该项分母，可用于派生 Table 1 |
| `missing` | 实际纳排和缺失处理，不从一个数字推断机制 |
| `diagnostics` / `warnings` | 执行了什么、未执行什么、警告；收敛或 P 值不是方法适用性的认证 |
| `environment` | R / Python、包、系统与库隔离信息；历史未记录字段明确保留未知 |

组织方式借鉴 [pharmaverse/cards](https://github.com/pharmaverse/cards) 的 ARD 思路：先保存可识别的统计量，再派生报告。cards 由 Daniel D. Sjoberg 等维护，[官方 DESCRIPTION 声明 Apache 2.0 许可](https://raw.githubusercontent.com/pharmaverse/cards/main/DESCRIPTION)，核查日期为 2026-10-09。本项目未复制其代码，也没有安装 cards 或声称实现完整 CDISC ARS；这是服务三个既有案例的原创轻量契约。

## 哪一种检查真的重新算了

| 入口 | 做什么 | 不代表什么 |
|---|---|---|
| `npm run check:offline` | 当前方法 / 教学 / 结果契约测试、保存对象 / 视图 / 散列与六列状态表、论文聚合快照及本地链接 / 命名核对；无需 R 包恢复或下载公共个体文件 | 未拟合模型，未重算整篇论文，未读取公共个体缓存 |
| `python3 scripts/统一结果v2.2.py snapshot --output build/结果快照v2.2.json` | 把冻结的旧 JSON 转为新契约，执行状态保留为快照导入 | 不能把重新格式化写成实际复现；不会更新冻结目标 |
| `Rscript --vanilla scripts/运行流水线v2.2.R ...` | 由 targets 调度，实际执行 Python / base R 教学计算、读取公共 XPT 拟合限定 NHANES 模型、生成统一对象与报告，再检查产物 | 不是三篇论文全部重跑、原作者代码运行或统计专家批准 |

完整重跑的 `runtime` 目标每次执行并更新该次清单；其下游计算真正重做。其他文件目标由 targets 追踪。任何中途源码或输入散列漂移会使合并失败，不能用新的来源标签套在旧数值上。失败保留日志，不更改冻结期望值。

## 从项目根目录恢复，再重跑

需要已有 R 和 Python；renv 不安装 R、本机系统、BLAS 或编译工具。当前锁定 R **4.5.2**，R 包 **45** 个，包括 targets 1.12.0、renv 1.3.1、survey 4.5、jsonlite 2.0.0、foreign 0.8-90。包原始版本号使用 DESCRIPTION 字段，避免把 `0.8-90` 与 packageVersion 的规范化显示 `0.8.90` 误认为版本差异。

```sh
Rscript --vanilla scripts/恢复环境v2.2.R restore
python3 -m venv build/Python恢复环境v2.2
build/Python恢复环境v2.2/bin/python -m pip install -r config/Python环境锁v2.2.txt

# 只在缺少冻结公共输入时显式获取这 15 个 CDC 文件。
# 已缓存也会检查旧 SHA-256；不会换成新数据凑齐目标。
python3 scripts/fetch-nhanesv2.1.py --download

CLINICAL_PYTHON="$PWD/build/Python恢复环境v2.2/bin/python" \
  Rscript --vanilla scripts/运行流水线v2.2.R
```

三个可选位置参数分别是：R 库、产物目录、targets 状态目录。例如本轮验收实际运行：

```sh
CLINICAL_PYTHON="$PWD/build/Python恢复环境v2.2/bin/python" \
  Rscript --vanilla scripts/运行流水线v2.2.R \
  build/R验收环境v2.2 build/隔离完整重跑v2.2 build/隔离目标状态v2.2
```

库必须先存在。恢复到自定义库时，使用 `Rscript --vanilla scripts/恢复环境v2.2.R restore build/R验收环境v2.2`。第6步修复了 bootstrap 目录未存在时被 `.libPaths()` 丢弃的问题；先建库再设置路径，并在安装后重新核查。恢复脚本第三个位置参数可指定全新的 bootstrap 库，例如 `restore build/首次恢复核查v2.2/分析库v2.2 build/首次恢复核查v2.2/包源v2.2`。bootstrap 只从官方 CRAN 安装 renv，随后按版本锁恢复项目包；不会改用户启动文件或全局包目录。普通恢复不会自动刷新锁文件。

### 自定义配置路径

已核查并使用官方接口：

- [targets::tar_make](https://docs.ropensci.org/targets/reference/tar_make.html) 的 `script` 与 `store`：使用 `scripts/分析流水线v2.2.R` 与 `build/目标状态v2.2`，无需 `_targets.R` / `_targets/` 或 YAML 默认配置。
- [renv::snapshot](https://rstudio.github.io/renv/reference/snapshot.html) 的 `packages`、`library`、`lockfile` 和 [renv::restore](https://rstudio.github.io/renv/reference/restore.html) 的 `library`、`lockfile`：使用 [R 版本锁](../config/R环境锁v2.2.json)，不生成默认 `renv.lock`。
- [renv paths](https://rstudio.github.io/renv/reference/paths.html)：项目内 build 目录保存 renv 状态、缓存和隔离库，不生成根目录 `.Rprofile` 或激活脚本。

维护者需要更新 R 锁时，须显式运行 `恢复环境v2.2.R snapshot`；这与恢复和重跑是不同动作。Python 完整安装版本保存在 [Python 版本锁](../config/Python环境锁v2.2.txt)，原有 requirements.txt 三个主要计算依赖不改动。R / venv / targets 管理的内部元数据有工具固定名称，均留在忽略的 build 目录；自主命名的源码、配置、报告和产物文件带版本且不含下划线。

## 更新本地网页产物

完整重跑默认只写 `build/`，保留旧结果、作者代码、论文 / 补充材料和冻结目标。通过核查后，可用实际重跑对象生成本地离线网页资产：

```sh
build/Python恢复环境v2.2/bin/python scripts/统一结果v2.2.py render \
  --bundle build/隔离完整重跑v2.2/分析结果v2.2.json --output docs/assets
npm run check:offline
```

这是本地资产生成，不是 GitHub 发布。旧 v2.0 / v2.1 JSON、图表和比较记录仍保留；新的主要展示使用 v2.2 对象。

## 本轮证据与限制

本轮在同一 macOS / arm64 主机上创建新的隔离 R 包目录和 Python venv，恢复版本后执行完整目标图；R 与 Python 教学计算独立核对，NHANES 15 个缓存文件先核查散列，主模型实际读取其中 10 个 DEMO / VIX 文件。ECQ 是另一个既有公共案例的输入，保留并验散列，但没有冒称用于近视模型。

NHANES 保持四个模型、16 个效应、选定流程 / 描述的 **72 / 72** 原文约定核对；当前默认 CI 仍有 **16 / 32** 端点与原文舍入范围一致。两种算法、domain 敏感性和版本差异均保留。模型收敛、ID、权重和设计字段的部分检查有执行记录，但残差形状、非线性、影响点、调整集及缺失机制仍需完整评价。

这是**同主机包与工作目录隔离验收**，不是全新 OS、容器或跨平台证明；系统 R 和基础库仍由该主机提供。Python 锁固定安装版本，未固定各平台二进制 wheel 的散列。没有重跑另外两篇论文、原作者未许可代码、所有补充模型或全篇图表，也没有 AI 测评、独立方法学评审或真人试用。[本轮实施记录](../验证记录v2.2.md)分开记载这些边界。

当前实际检查次数、两个首次恢复隔离库及重跑证据见[综合核查](综合核查v2.2.md)。这不是新操作系统或作者历史环境的验收。
