# R 统计引擎架构与部署指南 > **版本:** v1.6 > **更新日期:** 2026-03-07 > **维护者:** SSA-Pro 开发团队 / ASL 循证工具箱团队 > **状态:** ✅ 生产就绪(13 工具 + Block-based 标准化输出 + Agent 代码执行端点 + 结构化错误处理) --- ## 📋 目录 1. [概述](#1-概述) 2. [架构设计](#2-架构设计) 3. [Docker 镜像构建](#3-docker-镜像构建) 4. [部署指南](#4-部署指南) 5. [API 参考](#5-api-参考) 6. [开发指南](#6-开发指南) - 6.1 [添加新工具(含 Block-based 模板)](#61-添加新工具) - 6.5 [各工具参数快速参考](#65-各工具参数快速参考) - 6.6 [R 语言陷阱速查(7 大坑)](#66-r-语言陷阱速查从实际-bug-中总结) - 6.7 [开发环境新增 R 包](#67-开发环境新增-r-包) 7. [运维指南](#7-运维指南) 8. [常见问题](#8-常见问题) 9. [测试指南](#9-测试指南) --- ## 1. 概述 ### 1.1 什么是 R 统计引擎 R 统计引擎是平台的**专用统计计算服务**,基于 Docker 容器化部署,提供: - 🧮 **严谨的统计分析能力**(T 检验、方差分析、回归等) - 🛡️ **统计护栏**(正态性检验、方差齐性检验等) - 📊 **可视化输出**(Base64 编码的图表) - 📝 **可复现代码生成**(APA 格式的 R 脚本) ### 1.2 定位 ``` ┌─────────────────────────────────────────────────────────────┐ │ 业务模块层 │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ SSA-Pro │ │ ASL │ │ 其他 │ │ │ │ 智能统计 │ │Meta分析 │ │ 模块 │ │ │ └────┬────┘ └────┬────┘ └─────────┘ │ ├───────┼──────────┼──────────────────────────────────────────┤ │ ▼ ▼ 通用能力层 │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ R 统计引擎 (Docker) │ │ │ │ • /health 健康检查 │ │ │ │ • /api/v1/tools 工具列表 │ │ │ │ • /api/v1/skills 技能执行(QPER 管线) │ │ │ │ • /api/v1/execute-code 代码执行(Agent 通道) │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ### 1.3 技术栈 | 组件 | 版本 | 说明 | |------|------|------| | R | 4.3.3 | 统计计算核心 | | plumber | 1.2.1 | REST API 框架 | | ggplot2 | 最新 | 数据可视化 | | car | 3.1-2 | 高级统计检验 | | dplyr/tidyr | 最新 | 数据处理 | | gtsummary | 最新 | 基线特征表生成(Phase Deploy 新增) | | gt/broom | 最新 | 表格渲染/模型整理(Phase Deploy 新增) | | scales/gridExtra | 最新 | 坐标轴格式化/多图排版(Phase Deploy 新增) | | meta | 8.2.1 | Meta 分析引擎(ASL 工具 5 新增 — metagen/metabin/metacont) | | Docker | 24+ | 容器化部署 | --- ## 2. 架构设计 ### 2.1 Brain-Hand 模型 R 统计引擎采用 **Brain-Hand 分离架构**: ``` ┌──────────────────┐ ┌──────────────────┐ │ Node.js │ │ R Docker │ │ (Brain) │ │ (Hand) │ ├──────────────────┤ ├──────────────────┤ │ • 业务逻辑 │ HTTP │ • 统计计算 │ │ • 认证鉴权 │ ───────> │ • 数据处理 │ │ • OSS 签名 │ │ • 图表生成 │ │ • 结果解释 │ <─────── │ • 代码生成 │ └──────────────────┘ JSON └──────────────────┘ ``` ### 2.2 数据传输协议 支持两种数据传输方式: | 方式 | 条件 | 字段 | |------|------|------| | **inline** | 数据 < 2MB | `data_source.data` (JSON) | | **oss** | 数据 >= 2MB | `data_source.oss_url` (预签名 URL) | ```json // 方式 1: inline { "data_source": { "type": "inline", "data": [{"group": "A", "value": 10}, ...] } } // 方式 2: oss (预签名 URL) { "data_source": { "type": "oss", "oss_url": "https://bucket.oss.com/data.csv?signature=xxx" } } ``` #### 2.2.1 inline 数据格式详解 R 数据加载器 (`utils/data_loader.R`) 支持两种 JSON 数据格式: | 格式 | 说明 | 示例 | |------|------|------| | **行格式** | JSON 对象数组,每个对象是一行 | `[{"sex": 1, "age": 25}, {"sex": 2, "age": 30}]` | | **列格式** | JSON 对象,每个属性是一列 | `{"sex": [1, 2], "age": [25, 30]}` | > **推荐**:使用**行格式**,与 JavaScript/TypeScript 的数据处理习惯一致。 **Node.js 调用示例:** ```typescript // 推荐:行格式(Array of Objects) const data = [ { sex: 1, age: 25, bmi: 22.5 }, { sex: 2, age: 30, bmi: 24.1 }, // ... ]; const response = await axios.post('http://localhost:8082/api/v1/skills/ST_T_TEST_IND', { data_source: { type: 'inline', data: data // 直接传入数组 }, params: { group_var: 'sex', value_var: 'age' } }); ``` ### 2.3 安全设计 | 安全措施 | 实现方式 | |----------|----------| | 非特权用户 | `USER appuser` | | 路径遍历防护 | `tool_code` 正则白名单 `^[A-Z][A-Z0-9_]*$` | | OSS 密钥隔离 | Node.js 生成预签名 URL,R 无需持有密钥 | | 健康检查 | Docker HEALTHCHECK | --- ## 3. Docker 镜像构建 ### 3.1 完整 Dockerfile ```dockerfile FROM rocker/r-ver:4.3 LABEL maintainer="dev-team@aiclinicalresearch.com" LABEL version="1.0.1" LABEL description="SSA-Pro R Statistics Service" # 安装系统依赖(包括 R 包编译所需的库) RUN apt-get update && apt-get install -y \ libcurl4-openssl-dev \ libssl-dev \ libxml2-dev \ libsodium-dev \ zlib1g-dev \ libnlopt-dev \ liblapack-dev \ libblas-dev \ gfortran \ pkg-config \ cmake \ curl \ && rm -rf /var/lib/apt/lists/* # 直接安装 R 包(含 Phase Deploy + ASL Meta 分析依赖) RUN R -e "install.packages(c( \ 'plumber', \ 'jsonlite', \ 'ggplot2', \ 'glue', \ 'dplyr', \ 'tidyr', \ 'base64enc', \ 'yaml', \ 'car', \ 'httr', \ 'scales', \ 'gridExtra', \ 'gtsummary', \ 'gt', \ 'broom', \ 'meta' \ ), repos='https://cloud.r-project.org/', Ncpus=2)" # 安全加固:创建非特权用户 RUN useradd -m -s /bin/bash appuser WORKDIR /app # 复制应用代码 COPY plumber.R plumber.R COPY utils/ utils/ COPY tools/ tools/ COPY tests/ tests/ # 设置目录权限 RUN chown -R appuser:appuser /app # 切换到非特权用户 USER appuser EXPOSE 8080 # 环境变量 ENV DEV_MODE="false" # 健康检查 HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8080/health || exit 1 # 启动服务 CMD ["R", "-e", "plumber::plumb('plumber.R')$run(host='0.0.0.0', port=8080)"] ``` ### 3.2 系统依赖说明 | 依赖包 | 用途 | |--------|------| | `libcurl4-openssl-dev` | httr 包(HTTP 请求) | | `libssl-dev` | openssl 包(加密) | | `libxml2-dev` | xml2 包(XML 解析) | | `libsodium-dev` | sodium 包(加密) | | `zlib1g-dev` | httpuv 包(Web 服务器) | | `libnlopt-dev` | nloptr 包(优化算法) | | `liblapack-dev` | 线性代数计算 | | `libblas-dev` | 基础线性代数 | | `gfortran` | Fortran 编译器(部分 R 包需要) | | `cmake` | nloptr 包构建 | | `curl` | 健康检查 | ### 3.3 构建命令 ```bash # 本地构建 cd r-statistics-service docker build -t ssa-r-statistics:1.0.1 . # 查看镜像 docker images ssa-r-statistics # 预期输出 REPOSITORY TAG IMAGE ID CREATED SIZE ssa-r-statistics 1.0.1 xxxxxxxxxxxx x minutes ago 1.81GB ``` ### 3.4 构建时间参考 | 阶段 | 耗时 | |------|------| | 基础镜像下载 | ~2 分钟(首次) | | 系统依赖安装 | ~1 分钟 | | R 包安装(16 个,含 gtsummary/gt/meta) | ~15 分钟 | | **总计** | **~17 分钟** | --- ## 4. 部署指南 ### 4.1 开发环境 使用 docker-compose: ```yaml # r-statistics-service/docker-compose.yml services: ssa-r-service: build: . container_name: ssa-r-statistics ports: - "8082:8080" # 主机8082 → 容器8080(REDCap占用8080/8081) environment: - DEV_MODE=true volumes: # 开发环境挂载:支持热重载 - ./plumber.R:/app/plumber.R # ⚠️ 重要:API 入口也需要挂载 - ./tools:/app/tools - ./utils:/app/utils - ./tests:/app/tests restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 30s timeout: 10s retries: 3 start_period: 10s ``` 启动命令: ```bash cd r-statistics-service docker-compose up -d ``` #### 4.1.1 热重载机制详解 | 文件类型 | 热重载支持 | 说明 | |----------|-----------|------| | `tools/*.R` | ✅ 自动 | DEV_MODE=true 时每次请求重新加载 | | `utils/*.R` | ⚠️ 需重启 | 服务启动时加载,修改后需 `docker-compose restart` | | `plumber.R` | ⚠️ 需重启 | API 路由定义,修改后需 `docker-compose restart` | **最佳实践:** - 开发新工具时,只需修改 `tools/` 目录,无需重启 - 修改 `utils/` 或 `plumber.R` 后,执行 `docker-compose restart` - 添加新的 API 端点后,需要 `docker-compose up -d --force-recreate` ### 4.2 生产环境 (SAE) ```yaml # SAE 配置 容器镜像: registry.cn-beijing.aliyuncs.com/aiclinical/ssa-r-statistics:1.0.1 实例规格: 2 vCPU, 4 GB 最小实例: 1 最大实例: 5 端口: 8080 环境变量: DEV_MODE: "false" ``` ### 4.3 环境变量 | 变量 | 默认值 | 说明 | |------|--------|------| | `DEV_MODE` | `false` | 开发模式(启用热重载,每次请求重新加载工具脚本) | > **说明**:开发环境和生产环境都使用真实 OSS,无需 Mock 数据。 > - 开发环境:`ai-clinical-data-dev` bucket > - 生产环境:`ai-clinical-data` bucket ### 4.4 端口配置 | 环境 | 主机端口 | 容器端口 | 说明 | |------|----------|----------|------| | **开发环境** | 8082 | 8080 | 避免与 REDCap 8080/8081 冲突 | | **生产环境 (SAE)** | 8080 | 8080 | 云端无端口冲突 | > **注意**:Node.js 后端通过 `R_SERVICE_URL` 环境变量配置 R 服务地址,默认值为 `http://localhost:8082`。 --- ## 5. API 参考 ### 5.1 健康检查 ```http GET /health ``` **响应:** ```json { "status": "ok", "timestamp": "2026-02-19 08:00:00", "version": "1.0.1", "dev_mode": true, "tools_loaded": 13 } ``` ### 5.2 工具列表 ```http GET /api/v1/tools ``` **响应:** ```json { "status": "ok", "tools": [ "anova_one", "baseline_table", "chi_square", "correlation", "descriptive", "fisher", "linear_reg", "logistic_binary", "mann_whitney", "meta_analysis", "t_test_ind", "t_test_paired", "wilcoxon" ], "count": 13 } ``` #### 已实现的统计工具(13 个) **Phase 2A 基础工具(7 个)** | tool_code | 名称 | 场景 | |-----------|------|------| | `ST_T_TEST_IND` | 独立样本 T 检验 | 两组连续变量比较(正态) | | `ST_MANN_WHITNEY` | Mann-Whitney U | 两组连续变量比较(非参数) | | `ST_T_TEST_PAIRED` | 配对 T 检验 | 前后对比 | | `ST_CHI_SQUARE` | 卡方检验 | 分类变量关联 | | `ST_CORRELATION` | 相关分析 | Pearson/Spearman 相关 | | `ST_LOGISTIC_BINARY` | 二元 Logistic 回归 | 多因素分析(二分类结局) | | `ST_DESCRIPTIVE` | 描述性统计 | 基线表、数据概况 | **Phase Deploy 新增工具(5 个)** | tool_code | 名称 | 场景 | |-----------|------|------| | `ST_FISHER` | Fisher 精确检验 | 小样本/稀疏列联表(卡方替代) | | `ST_ANOVA_ONE` | 单因素方差分析 | 三组及以上均值比较(含 Kruskal-Wallis 降级) | | `ST_WILCOXON` | Wilcoxon 符号秩检验 | 配对非参数检验(配对 T 替代) | | `ST_LINEAR_REG` | 线性回归 | 连续结局多因素分析 | | `ST_BASELINE_TABLE` | 基线特征表(复合工具) | 基于 gtsummary 的一键式基线表生成 | **ASL 循证工具箱新增(1 个)** | tool_code | 名称 | 场景 | 调用方 | |-----------|------|------|--------| | `ST_META_ANALYSIS` | Meta 分析引擎 | HR / 二分类 / 连续型 Meta 分析,生成森林图 + 漏斗图 | ASL 工具 5 | > **跨模块复用说明:** `ST_META_ANALYSIS` 由 ASL 模块(工具 5:Meta 分析引擎)引入,后端代理位于 `backend/src/modules/asl/meta-analysis/`,使用 `inline` 数据协议(Meta 分析通常 5-30 个研究,数据量极小)。该工具同样可被 SSA 或其他模块复用。 ### 5.3 执行技能 ```http POST /api/v1/skills/{tool_code} Content-Type: application/json ``` **请求体:** ```json { "data_source": { "type": "inline", "data": [...] }, "params": { "group_var": "group", "value_var": "value" }, "guardrails": { "check_normality": true } } ``` **成功响应:** ```json { "status": "success", "message": "分析完成", "warnings": null, "results": { "method": "Welch Two Sample t-test", "statistic": -5.196, "df": 5.98, "p_value": 0.002, "p_value_fmt": "p = .002" }, "plots": ["data:image/png;base64,..."], "trace_log": [...], "reproducible_code": "..." } ``` **错误响应:** ```json { "status": "error", "error_code": "E001", "error_type": "business", "message": "列名 'xxx' 在数据中不存在", "user_hint": "请检查变量名是否拼写正确" } ``` ### 5.4 JIT 护栏检查(Phase 2A 新增) 在执行核心统计工具前,调用此端点检验统计假设(正态性、方差齐性等)。 ```http POST /api/v1/guardrails/jit Content-Type: application/json ``` **请求体:** ```json { "data_source": { "type": "inline", "data": [...] }, "tool_code": "ST_T_TEST_IND", "params": { "group_var": "sex", "value_var": "age" } } ``` **响应:** ```json { "status": "success", "checks": [ { "check_name": "正态性检验 (组: 1)", "passed": true, "p_value": 0.234, "recommendation": "满足正态性" }, { "check_name": "方差齐性检验 (Levene)", "passed": false, "p_value": 0.012, "recommendation": "建议使用 Welch 校正" } ], "suggested_tool": "ST_MANN_WHITNEY", "can_proceed": true, "all_checks_passed": false } ``` **使用场景:** - 工作流执行器在调用核心统计方法前,先调用 JIT 护栏 - 根据 `suggested_tool` 自动切换到更合适的方法 - 将 `checks` 结果展示给用户 ### 5.5 Agent 通道:执行任意 R 代码(v1.5 双通道架构新增) ```http POST /api/v1/execute-code Content-Type: application/json ``` **请求体:** ```json { "code": "blocks <- list()\nblocks[[1]] <- make_markdown_block('Hello', title='Test')\nlist(status='success', report_blocks=blocks)", "session_id": "xxx-xxx", "timeout": 120 } ``` | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `code` | string | ✅ | 要执行的 R 代码(可使用 block_helpers.R 的所有辅助函数) | | `session_id` | string | 否 | 会话 ID(用于日志追踪) | | `timeout` | number | 否 | 超时秒数,默认 120,最大 120 | **成功响应:** ```json { "status": "success", "result": { "status": "success", "report_blocks": [ {"type": "markdown", "content": "Hello", "title": "Test"} ] }, "console_output": [], "duration_ms": 42 } ``` **错误响应(v1.6 结构化错误):** ```json { "status": "error", "error_code": "E001", "error_type": "business", "message": "[E001] object 'blood_pressure' not found (约第 12 行)\n[Warnings] NAs introduced by coercion", "user_hint": "请检查变量名是否拼写正确 | object 'blood_pressure' not found", "error_line": 12, "error_context": " 9| df$group <- as.factor(df$group)\n 10| \n 11| # 执行 T 检验\n>>> 12| result <- t.test(blood_pressure ~ group, data = df)\n 13| \n 14| # 构建结果", "console_output": ["Loading required package: ggplot2", "Warning: NAs introduced by coercion"], "duration_ms": 123 } ``` | 错误响应字段 | 类型 | 说明 | |-------------|------|------| | `error_code` | string | 错误分类码(`E001`-`E007` 业务错误,`E100`-`E101` 系统错误,`E_EXEC` 未分类运行时错误) | | `error_type` | string | `business`(可由 LLM 自动修复)或 `system`(需人工介入)或 `runtime`(未分类) | | `message` | string | 结构化友好消息,含错误码 + 原始信息 + 行号 + 警告摘要 | | `user_hint` | string | 面向 LLM 的修复建议 + 原始错误拼接 | | `error_line` | number? | 出错代码行号(从 R 错误信息中提取,可能为 null) | | `error_context` | string? | 出错行前后 3 行的代码上下文,`>>>` 标记出错行(可能为 null) | | `console_output` | string[] | R 控制台完整输出(stdout + warnings + messages 合并) | **错误码速查(`error_codes.R` 定义,20+ 模式匹配):** | 错误码 | 类型 | 典型触发 | LLM 修复建议 | |--------|------|---------|-------------| | E001 | business | `object 'xxx' not found`, `undefined columns` | 检查变量名拼写 | | E002 | business | `non-numeric argument`, `cannot coerce`, `contrasts can be applied only to factors` | 检查数据类型 | | E003 | business | 分组变量水平数不匹配 | 检查分组变量取值 | | E004 | business | `need at least 2 observations`, `sample size must be` | 数据量不足 | | E005 | business | `singular matrix`, `rank deficient`, `singular gradient` | 移除共线变量 | | E006 | business | `did not converge` | 调整模型参数 | | E007 | business | 方差为零 | 该列值全部相同 | | E100 | system | `subscript out of bounds`, `missing value where TRUE/FALSE needed` | 需人工排查 | | E101 | system | `could not find function`, `no package called` | 缺少 R 包 | | E_EXEC | runtime | 未匹配到已知模式 | 返回原始错误信息 | **沙箱安全机制:** - 代码在 `new.env(parent = globalenv())` 隔离环境中执行 - `setTimeLimit` 强制超时(CPU + 挂钟时间 ≤ 120 秒) - `withCallingHandlers` 捕获 warnings 和 messages(不中断执行),错误由外层 `tryCatch` 捕获 - `console_output` 合并三种来源:`capture.output` stdout + 收集的 warnings + 收集的 messages - 错误时调用 `format_agent_error()` 提取行号、代码上下文、错误分类和修复建议 - 可访问 `block_helpers.R` 和 `data_loader.R` 中的所有辅助函数 - 由 Node.js `CodeRunnerService` 自动注入 `input` 和 `df` 数据变量 **调用方:** SSA 模块 Agent 通道(`CodeRunnerService.ts` → `ChatHandlerService.ts`),用于执行 LLM CoderAgent 生成的 R 代码。 > **与 `/api/v1/skills/{tool_code}` 的区别:** `/skills` 端点执行**预制的统计工具**(白名单限制),`/execute-code` 端点执行**任意 R 代码**(由 LLM CoderAgent 生成)。两者的错误处理也不同:`/skills` 使用 `map_r_error()` 做简单映射,`/execute-code` 使用 `format_agent_error()` 返回结构化错误(含行号和上下文),便于 CoderAgent 自动修复重试。 ### 5.6 复合工具示例:基线特征表(Phase Deploy) ```http POST /api/v1/skills/ST_BASELINE_TABLE Content-Type: application/json ``` **请求体:** ```json { "data_source": { "type": "inline", "data": [ {"group": "Drug", "age": 45, "sex": "M", "sbp": 130, "bmi": 24.5, "smoking": "Yes"}, {"group": "Placebo", "age": 47, "sex": "F", "sbp": 128, "bmi": 23.8, "smoking": "No"} ] }, "params": { "group_var": "group", "analyze_vars": ["age", "sex", "sbp", "bmi", "smoking"] } } ``` **成功响应(核心字段):** ```json { "status": "success", "results": { "n_total": 30, "n_groups": 2, "n_variables": 5, "significant_vars": ["sbp"], "method_info": [ {"variable": "age", "method": "Wilcoxon rank sum test"}, {"variable": "sex", "method": "Fisher's exact test"} ] }, "report_blocks": [ { "type": "table", "headers": ["Characteristic", "Drug, N = 15", "Placebo, N = 15", "p-value"], "rows": [["age", "49 (42, 55)", "47 (41, 54)", "0.6"]], "title": "基线特征表 (按 group 分组)", "metadata": { "is_baseline_table": true, "group_var": "group", "has_p_values": true } } ] } ``` > **特点:** `ST_BASELINE_TABLE` 是复合工具,基于 `gtsummary::tbl_summary()` 自动判断变量类型(连续/分类)、选择统计方法(T 检验/Mann-Whitney/卡方/Fisher),输出标准三线表。`report_blocks[0].metadata.is_baseline_table = true` 触发前端特殊渲染(P 值标星、rowspan 合并行)。 ### 5.7 Meta 分析示例(ASL 工具 5 — v1.4 新增) ```http POST /api/v1/skills/ST_META_ANALYSIS Content-Type: application/json ``` **请求体(HR 数据类型):** ```json { "data_source": { "type": "inline", "data": [ {"study_id": "Gandhi 2018", "hr": 0.49, "lower_ci": 0.38, "upper_ci": 0.64}, {"study_id": "Socinski 2018", "hr": 0.56, "lower_ci": 0.45, "upper_ci": 0.70}, {"study_id": "West 2019", "hr": 0.60, "lower_ci": 0.45, "upper_ci": 0.80} ] }, "params": { "data_type": "hr", "model": "random" } } ``` **支持的 3 种数据类型:** | data_type | 必需列 | R 函数 | 效应指标 | |-----------|--------|--------|----------| | `hr` | `study_id`, `hr`, `lower_ci`, `upper_ci` | `meta::metagen()` | HR | | `dichotomous` | `study_id`, `events_e`, `total_e`, `events_c`, `total_c` | `meta::metabin()` | OR / RR / RD | | `continuous` | `study_id`, `mean_e`, `sd_e`, `n_e`, `mean_c`, `sd_c`, `n_c` | `meta::metacont()` | MD | **params 参数:** | 参数 | 类型 | 默认值 | 说明 | |------|------|--------|------| | `data_type` | string | 必填 | `"hr"` / `"dichotomous"` / `"continuous"` | | `model` | string | `"random"` | `"random"` (DerSimonian-Laird) / `"fixed"` | | `effect_measure` | string | 自动 | 仅二分类有效:`"OR"` / `"RR"` / `"RD"` | **成功响应(核心字段):** ```json { "status": "success", "results": { "pooled_effect": 0.5948, "pooled_lower": 0.5255, "pooled_upper": 0.6733, "pooled_pvalue": 0.0, "i_squared": 15.4, "tau_squared": 0.0031, "q_statistic": 4.73, "q_pvalue": 0.316, "k_studies": 3, "effect_measure": "HR", "model": "Random Effects" }, "plots": [ "data:image/png;base64,...(森林图)", "data:image/png;base64,...(漏斗图)" ], "report_blocks": [ {"type": "key_value", "title": "Meta-Analysis Summary", "items": [...]}, {"type": "image", "title": "Forest Plot", "data": "..."}, {"type": "image", "title": "Funnel Plot", "data": "..."}, {"type": "markdown", "title": "Heterogeneity Assessment", "content": "..."} ] } ``` > **重要实现细节 — 对数尺度反变换:** `meta` R 包对比值类效应量(HR / OR / RR)的内部计算在**对数尺度**上进行,即 `TE.random` 返回的是 `log(HR)` 而非 HR。`meta_analysis.R` 在结果提取阶段对比值类指标自动执行 `exp()` 反变换,确保 API 返回临床可读的效应量(如 HR = 0.59 而非 log(HR) = -0.52)。连续型指标(MD)无需反变换。 --- ## 6. 开发指南 ### 6.1 添加新工具 1. 在 `tools/` 目录创建 R 脚本: ```r # tools/my_analysis.R #' @tool_code ST_MY_ANALYSIS #' @name 我的分析工具 #' @version 1.0.0 #' @description 工具描述 #' @author SSA-Pro Team library(glue) library(ggplot2) library(base64enc) run_analysis <- function(input) { # ===== 初始化日志 ===== logs <- c() log_add <- function(msg) { logs <<- c(logs, paste0("[", Sys.time(), "] ", msg)) } # ===== 数据加载 ===== log_add("开始加载输入数据") df <- tryCatch( load_input_data(input), error = function(e) { log_add(paste("数据加载失败:", e$message)) return(NULL) } ) if (is.null(df)) { return(make_error(ERROR_CODES$E100_INTERNAL_ERROR, details = "数据加载失败")) } log_add(glue("数据加载成功: {nrow(df)} 行, {ncol(df)} 列")) # ===== 参数提取 ===== p <- input$params my_var <- p$my_var # ===== 参数校验 ===== if (!(my_var %in% names(df))) { return(make_error(ERROR_CODES$E001_COLUMN_NOT_FOUND, col = my_var)) } # ===== 护栏检查 ===== guardrail_results <- list() warnings_list <- c() sample_check <- check_sample_size(nrow(df), min_required = 10, action = ACTION_WARN) guardrail_results <- c(guardrail_results, list(sample_check)) guardrail_status <- run_guardrail_chain(guardrail_results) if (guardrail_status$status == "blocked") { return(list(status = "blocked", message = guardrail_status$reason, trace_log = logs)) } # ===== 核心计算 ===== log_add("执行分析...") # result <- your_analysis_function(df, ...) # ===== 构建 report_blocks(必须!) ===== blocks <- list() # Block: 检验结果(key_value) blocks[[length(blocks) + 1]] <- make_kv_block( list("方法" = "Your Method", "统计量" = "1.234", "P 值" = format_p_value(0.05)), title = "检验结果" ) # Block: 图表(image) plot_base64 <- tryCatch({ p <- ggplot(df, aes(x = df[[my_var]])) + geom_histogram() + theme_minimal() tmp_file <- tempfile(fileext = ".png") ggsave(tmp_file, p, width = 7, height = 5, dpi = 100) base64_str <- base64encode(tmp_file) unlink(tmp_file) paste0("data:image/png;base64,", base64_str) }, error = function(e) NULL) if (!is.null(plot_base64)) { blocks[[length(blocks) + 1]] <- make_image_block(plot_base64, title = "分析图表") } # Block: 结论(markdown) blocks[[length(blocks) + 1]] <- make_markdown_block("分析结论...", title = "结论摘要") # ===== 生成可复现代码 ===== reproducible_code <- glue(' # SSA-Pro 自动生成代码 # 工具: 我的分析工具 # 时间: {Sys.time()} # ================================ df <- read.csv("data.csv") # 你的分析代码... ') # ===== 返回结果 ===== log_add("分析完成") return(list( status = "success", message = "分析完成", warnings = if (length(warnings_list) > 0) warnings_list else NULL, results = list( # 统计结果(使用 jsonlite::unbox 保证单值不被包装成数组) statistic = jsonlite::unbox(1.234), p_value = jsonlite::unbox(0.05), p_value_fmt = format_p_value(0.05) ), report_blocks = blocks, # ⚠️ 必须!前端 DynamicReport 依赖此字段渲染 plots = if (!is.null(plot_base64)) list(plot_base64) else list(), trace_log = logs, reproducible_code = as.character(reproducible_code) )) } ``` 2. **开发模式**:修改 `tools/` 下的文件后,无需重启,下次请求自动加载 3. 测试: ```bash curl -X POST http://localhost:8082/api/v1/skills/ST_MY_ANALYSIS \ -H "Content-Type: application/json" \ -d '{"data_source": {"type": "inline", "data": [{"x": 1}, {"x": 2}]}, "params": {"my_var": "x"}}' ``` ### 6.2 工具命名规范 | 项目 | 规范 | |------|------| | 文件名 | 小写下划线:`t_test_ind.R` | | tool_code | 大写下划线:`ST_T_TEST_IND` | | 入口函数 | 固定名称:`run_analysis` | ### 6.3 结果格式规范 ```r return(list( status = "success" | "error" | "blocked", message = "...", warnings = c("...") | NULL, results = list( # 统计结果(使用 jsonlite::unbox() 保证单值不被包装成数组) ), report_blocks = list( # Block-based 标准化输出(Phase E+ 协议),前端 DynamicReport.tsx 统一渲染 # 支持 4 种 Block 类型:markdown / table / image / key_value # 通过 utils/block_helpers.R 的辅助函数构建 ), plots = list( "data:image/png;base64,..." ), trace_log = c("..."), reproducible_code = "..." )) ``` ### 6.4 Block-based 输出协议(Phase E+ 标准) 所有工具**必须**通过 `utils/block_helpers.R` 构建 `report_blocks[]`,前端 `DynamicReport.tsx` 根据 `block.type` 统一渲染。 | 辅助函数 | Block 类型 | 用途 | |----------|-----------|------| | `make_markdown_block(content, title)` | `markdown` | 文本结论、方法说明 | | `make_table_block(headers, rows, title, footnote, metadata)` | `table` | 统计结果表、系数表、事后比较表 | | `make_table_block_from_df(df, title, footnote, digits)` | `table` | 从 data.frame 快速构建表格 | | `make_image_block(base64_data, title, alt)` | `image` | 图表(base64 编码 PNG) | | `make_kv_block(items, title)` | `key_value` | 检验统计量、模型拟合指标 | **示例:** ```r blocks <- list() blocks[[length(blocks) + 1]] <- make_kv_block( list("检验方法" = "Welch t-test", "统计量" = "t = -2.35", "P 值" = "p = .021"), title = "检验结果" ) blocks[[length(blocks) + 1]] <- make_image_block(plot_base64, title = "组间比较箱线图") blocks[[length(blocks) + 1]] <- make_markdown_block("两组差异具有统计学意义...", title = "结论") ``` ### 6.5 各工具参数快速参考 > 调用 `POST /api/v1/skills/{tool_code}` 时,`params` 对象需要的字段速查。 | tool_code | 必需参数 | 可选参数 | |-----------|---------|---------| | `ST_T_TEST_IND` | `group_var`, `value_var` | `guardrails.check_normality` | | `ST_MANN_WHITNEY` | `group_var`, `value_var` | — | | `ST_T_TEST_PAIRED` | `before_var`, `after_var` | `guardrails.check_normality` | | `ST_CHI_SQUARE` | `var1`, `var2` | — | | `ST_CORRELATION` | `var_x`, `var_y` | `method` (`"auto"` / `"pearson"` / `"spearman"`) | | `ST_LOGISTIC_BINARY` | `outcome_var`, `predictors` (数组) | — | | `ST_DESCRIPTIVE` | `variables` (数组) | `group_var` | | `ST_FISHER` | `var1`, `var2` | — | | `ST_ANOVA_ONE` | `group_var`, `value_var` | `guardrails.check_normality` | | `ST_WILCOXON` | `before_var`, `after_var` | — | | `ST_LINEAR_REG` | `outcome_var`, `predictors` (数组) | `confounders` (数组) | | `ST_BASELINE_TABLE` | `group_var` | `analyze_vars` (数组,不传则自动全选) | | `ST_META_ANALYSIS` | `data_type` (`hr` / `dichotomous` / `continuous`) | `model` (`random` / `fixed`)、`effect_measure` (二分类: `OR` / `RR` / `RD`) | ### 6.6 R 语言陷阱速查(从实际 Bug 中总结) > **Phase Deploy 开发中实际踩过的坑**,后续开发者必读。每条附真实错误信息和修复方法。 #### 陷阱 1:JSON 数组参数在 R 中是 `list`,不是 `character` 向量 **错误信息:** `invalid subscript type 'list'` **原因:** plumber 解析 JSON `["age", "sex", "bmi"]` 后,R 拿到的是 `list("age", "sex", "bmi")`,不是 `c("age", "sex", "bmi")`。对 list 做 `%in%`、`[` 等操作都会报错。 ```r # ❌ 错误:直接使用 JSON 传入的数组参数 analyze_vars <- p$analyze_vars missing <- analyze_vars[!(analyze_vars %in% names(df))] # 报错! # ✅ 正确:先转换为字符向量 analyze_vars <- as.character(unlist(p$analyze_vars)) missing <- analyze_vars[!(analyze_vars %in% names(df))] # 正常 ``` **影响范围:** 所有接收数组参数的工具(`predictors`、`variables`、`analyze_vars`、`confounders`)。 #### 陷阱 2:`list()` 中不能用表达式做键名 **错误信息:** `unexpected '='` **原因:** R 的 `list()` 构造器只接受**字面量**作为名称,不接受 `paste0()`、`glue()` 等函数调用。 ```r # ❌ 错误:用表达式做键名 items <- list( paste0(var_name, " Median") = "5.2" # 语法错误! ) # ✅ 正确:先创建 list 再用 [[ 赋值 items <- list() items[[paste0(var_name, " Median")]] <- "5.2" ``` #### 陷阱 3:`tryCatch` 会吞掉 warning 导致结果丢失 **错误信息:** 无明确错误,但返回 NULL 或非预期结果 **原因:** `tryCatch(expr, warning = function(w) {...})` 捕获第一个 warning 后**中断 expr 执行**,返回 warning handler 的返回值。gtsummary、car 等包常发 warning,导致主计算被中断。 ```r # ❌ 错误:tryCatch 捕获 warning 会中断执行 tbl <- tryCatch({ tbl_summary(df) %>% add_p() # 如果 add_p() 发 warning,tbl 变成 NULL }, warning = function(w) { invokeRestart("muffleWarning") # 在 tryCatch 中无效! }) # ✅ 正确:withCallingHandlers 处理 warning(不中断执行),tryCatch 只捕获 error tbl <- tryCatch( withCallingHandlers( { tbl_summary(df) %>% add_p() }, warning = function(w) { warnings_list <<- c(warnings_list, w$message) invokeRestart("muffleWarning") } ), error = function(e) { return(NULL) } ) ``` #### 陷阱 4:gtsummary `table_body` 的 p.value 是 list 列 **错误信息:** `invalid subscript type 'list'` **原因:** `gtsummary` 的内部数据结构 `tbl$table_body$p.value` 是 list 列(每个元素可能是 NULL 或 numeric),不能直接用 `<` 比较。 ```r # ❌ 错误:直接对 list 列做比较 p_rows <- body[body$p.value < 0.05, ] # 报错! # ✅ 正确:先 unlist + as.numeric p_vals <- as.numeric(unlist(body$p.value)) sig_idx <- which(!is.na(p_vals) & p_vals < 0.05) ``` #### 陷阱 5:浮点数比较不能用 `==` **错误信息:** 无明确错误,但条件判断逻辑错误 ```r # ❌ 错误:直接比较浮点数 if (sd(values) == 0) { ... } # 可能因精度问题漏判 # ✅ 正确:使用容差比较 if (isTRUE(sd(values) < .Machine$double.eps^0.5)) { ... } ``` #### 陷阱 6:变量可能为 NULL 导致 glue/round 崩溃 **错误信息:** `non-numeric argument to mathematical function` 或 `subscript out of bounds` **原因:** 某些统计结果字段(如 `fstatistic`)在边界条件下为 NULL。 ```r # ❌ 错误:直接使用可能为 NULL 的值 log_add(glue("F = {round(f_stat[1], 2)}")) # f_stat 为 NULL 时崩溃 # ✅ 正确:先检查再使用 if (!is.null(f_stat)) { log_add(glue("F = {round(f_stat[1], 2)}")) } else { log_add("F = NA") } ``` #### 陷阱 7:新增 R 包后 `utils/` 修改需要重启容器 **现象:** `make_table_block()` 新增了 `metadata` 参数,但调用时报 `unused argument` **原因:** `utils/*.R` 在服务启动时一次性加载,不像 `tools/*.R` 有热重载。修改后必须: ```bash cd r-statistics-service docker-compose restart ``` #### 陷阱 8:`meta` 包返回对数尺度效应量(v1.4 新增) **现象:** Meta 分析返回 HR = -0.52,但期望 HR ≈ 0.59 **原因:** `meta` R 包的 `metagen()`/`metabin()` 对比值类效应量(HR、OR、RR)在**对数尺度**上计算。`TE.random`/`TE.fixed` 返回的是 `log(HR)` 或 `log(OR)`,而非原始比值。 ```r # ❌ 错误:直接返回 TE(对数尺度) pooled_effect = ma_result$TE.random # 返回 -0.52(这是 log(HR)) # ✅ 正确:对比值类指标做 exp() 反变换 is_ratio <- sm_label %in% c("HR", "OR", "RR") display_te <- if (is_ratio) exp(pooled_te) else pooled_te # 返回 0.59(真实 HR) ``` **影响范围:** 仅 `meta_analysis.R`,不影响其他统计工具。连续型指标(MD)无需反变换。 ### 6.7 开发环境新增 R 包 当新工具依赖尚未安装的 R 包时,有两种方式: **方式 1:临时安装到运行中的容器(开发测试用)** ```bash # 容器以 appuser 运行,无写权限,需用 root docker exec -u root ssa-r-statistics R -e "install.packages('新包名', repos='https://cloud.r-project.org/', quiet=TRUE)" ``` > 注意:容器重启后丢失,仅用于开发验证。 **方式 2:更新 Dockerfile 并重建镜像(正式方案)** 1. 在 `Dockerfile` 的 `install.packages()` 中添加新包名 2. 重建:`docker-compose up -d --build` --- ## 7. 运维指南 ### 7.1 日志查看 ```bash # 实时日志 docker logs -f ssa-r-statistics # 最近 100 行 docker logs --tail 100 ssa-r-statistics ``` ### 7.2 性能监控 ```bash # 容器资源使用 docker stats ssa-r-statistics ``` ### 7.3 重启服务 ```bash # 开发环境 docker-compose restart # 生产环境 (SAE) 通过 SAE 控制台重启实例 ``` ### 7.4 镜像更新 ```bash # 1. 构建新镜像 docker build -t ssa-r-statistics:1.0.2 . # 2. 推送到镜像仓库 docker tag ssa-r-statistics:1.0.2 registry.cn-beijing.aliyuncs.com/aiclinical/ssa-r-statistics:1.0.2 docker push registry.cn-beijing.aliyuncs.com/aiclinical/ssa-r-statistics:1.0.2 # 3. 更新 SAE 部署 ``` --- ## 8. 常见问题 ### Q1: 构建时 httpuv 安装失败 **错误:** `fatal error: zlib.h: No such file or directory` **解决:** 添加 `zlib1g-dev` 到系统依赖 ### Q2: 构建时 nloptr 安装失败 **错误:** `CMAKE NOT FOUND` **解决:** 添加 `cmake` 到系统依赖 ### Q3: /tmp 权限问题 **错误:** `cannot open file '/tmp/Rtmpxxx': No such file or directory` **解决:** 不要在启动命令中清理 /tmp ### Q4: DEV_MODE 热重载不生效 **原因:** 没有挂载 volumes **解决:** ```yaml volumes: - ./tools:/app/tools ``` ### Q5: 容器启动后无法访问 **检查:** 1. 端口映射是否正确 2. 健康检查是否通过 3. 查看容器日志 ### Q6: 数据加载失败(inline 模式) **错误:** `内部错误: 数据加载失败` **原因:** 数据格式不正确,或数据为空 **解决:** 1. 确保 `data_source.data` 是有效的 JSON 数组 2. 行格式:`[{"col1": val1}, {"col1": val2}]` 3. 检查是否有空数据或全 NA 列 ### Q7: R 脚本语法错误 **错误:** `unexpected symbol` 或 `lexical error` **常见原因:** 1. `glue()` 字符串中使用 `\'` 转义(应直接使用 `'`) 2. 中文注释编码问题 3. 代码块中的花括号不匹配 **解决:** ```r # 错误:glue 中的转义 glue("# Cramer\'s V = ...") # ❌ # 正确:直接使用单引号或避免 glue("# Cramer V = ...") # ✅ ``` ### Q8: JSON 序列化失败 **错误:** `No method asJSON S3 class: table` **原因:** R 的 `table` 对象无法直接序列化为 JSON **解决:** ```r # 错误 observed = as.matrix(contingency_table) # ❌ 可能保留 table 属性 # 正确:显式转换为纯数值矩阵 observed = matrix( as.numeric(contingency_table), nrow = nrow(contingency_table), ncol = ncol(contingency_table) ) # ✅ ``` ### Q9: 新端点返回 404 **原因:** 修改 `plumber.R` 后未重启服务 **解决:** ```bash # 修改 plumber.R 后必须重启 docker-compose restart # 如果修改了 docker-compose.yml(如添加新 volume) docker-compose up -d --force-recreate ``` ### Q10: 变量类型判断错误(missing value where TRUE/FALSE needed) **原因:** 对包含 NA 的数据进行布尔比较 **解决:** ```r # 错误 if (var_type == "numeric") { ... } # var_type 可能是 NA # 正确 if (identical(var_type, "numeric")) { ... } # ✅ 处理 NA ``` ### Q11: 修改 utils/ 后新参数报 `unused argument` **原因:** `utils/*.R`(如 `block_helpers.R`)在服务启动时加载进内存,不支持热重载(与 `tools/*.R` 不同)。 **解决:** ```bash docker-compose restart ``` ### Q12: Docker 已 build 但包仍不存在(`there is no package called 'xxx'`) **原因:** `docker-compose.yml` 中的 `volumes` 挂载会覆盖镜像中的文件,但**不影响已安装的 R 包**。常见场景是更新了 Dockerfile 却只用了 `docker-compose up -d` 而没有加 `--build`。 **解决:** ```bash # 确保 rebuild docker-compose up -d --build # 或临时装包(开发验证) docker exec -u root ssa-r-statistics R -e "install.packages('xxx', repos='https://cloud.r-project.org/', quiet=TRUE)" ``` ### Q13: 工具返回成功但 report_blocks 为空 **原因:** 返回结构中没有 `report_blocks` 字段或 blocks 列表为空。 **检查清单:** 1. 确认使用了 `utils/block_helpers.R` 的辅助函数构建 blocks 2. 确认 return 中包含 `report_blocks = blocks` 3. 确认每个 block 至少包含 `type` 字段 4. 用测试脚本验证:`node r-statistics-service/tests/run_all_tools_test.js` --- ## 9. 测试指南 ### 9.1 单工具测试 ```bash # 测试 T 检验 curl -s -X POST "http://localhost:8082/api/v1/skills/ST_T_TEST_IND" \ -H "Content-Type: application/json" \ -d '{ "data_source": { "type": "inline", "data": [ {"group": "A", "value": 23}, {"group": "A", "value": 25}, {"group": "B", "value": 30}, {"group": "B", "value": 32} ] }, "params": {"group_var": "group", "value_var": "value"} }' ``` ### 9.2 健康检查 ```bash curl -s http://localhost:8082/health | jq ``` ### 9.3 R 工具集中测试脚本(13 工具 + JIT) 项目提供了 R 统计引擎的全工具测试脚本: ```bash # 仅测试 R 服务层(12 工具 + JIT 护栏 + report_blocks 校验) node r-statistics-service/tests/run_all_tools_test.js ``` 测试覆盖: - 13 个统计工具(Phase 2A × 7 + Phase Deploy × 5 + ASL × 1) - JIT 护栏检查(ST_T_TEST_IND / ST_ANOVA_ONE / ST_FISHER / ST_LINEAR_REG) - `report_blocks` 协议校验(类型、必填字段、metadata) ### 9.4 端到端测试脚本(三层联调) 三层联调测试覆盖 R → Python → Node.js: ```bash cd docs/03-业务模块/SSA-智能统计分析/05-测试文档 node run_e2e_test.js ``` 测试覆盖: - Layer 1: R 服务(13 个统计工具 + JIT 护栏) - Layer 2: Python DataProfile API - Layer 3: Node.js 后端 API(登录 → 会话 → 规划 → 执行) ### 9.5 Meta 分析引擎专项 E2E 测试(v1.4 新增) 覆盖 Meta 分析的完整链路(Node.js → R Docker → 森林图/漏斗图生成): ```bash cd backend npx tsx src/modules/asl/meta-analysis/__tests__/meta-e2e-test.ts ``` 测试覆盖(8 个测试项,36 个断言): - R 服务健康检查 + meta_analysis 工具注册验证 - HR 风险比 Meta 分析(5 个研究,随机效应) - 二分类 OR Meta 分析(4 个研究) - 二分类 RR Meta 分析(效应指标切换) - 连续型 MD Meta 分析(5 个研究) - 固定效应模型切换 - 边界条件:仅 1 个研究(应返回错误) - 森林图 + 漏斗图 Base64 PNG 生成验证 最近一次测试结果(2026-02-26): ``` Results: 36 passed, 0 failed (1.3s) HR = 0.5948 [0.5255, 0.6733], I² = 15.4% OR = 0.4530 [0.3328, 0.6166], p < .001 MD = -1.4923 [-1.9098, -1.0749], I² = 70% ``` --- ## 附录:文件结构 ``` r-statistics-service/ ├── Dockerfile # 生产镜像定义(含 gtsummary/gt/broom/scales/gridExtra/meta) ├── docker-compose.yml # 开发环境编排(含 volume 挂载) ├── renv.lock # R 包版本锁定(备用) ├── .Rprofile # R 启动配置(备用) ├── plumber.R # API 入口(含 JIT 护栏端点,自动发现 tools/,Agent withCallingHandlers 错误处理) ├── utils/ │ ├── data_loader.R # 数据加载(支持行格式/列格式) │ ├── guardrails.R # 统计护栏 + JIT 检查(12 工具全覆盖) │ ├── error_codes.R # 错误映射(20+ 模式 + format_agent_error 结构化错误) │ ├── result_formatter.R # 结果格式化 │ └── block_helpers.R # Block-based 输出辅助函数(Phase E+ 协议) ├── tools/ # 统计工具(13 个) │ ├── t_test_ind.R # 独立样本 T 检验 │ ├── t_test_paired.R # 配对 T 检验 │ ├── mann_whitney.R # Mann-Whitney U 检验 │ ├── chi_square.R # 卡方检验 │ ├── correlation.R # 相关分析 │ ├── logistic_binary.R # 二元 Logistic 回归 │ ├── descriptive.R # 描述性统计 │ ├── fisher.R # 🆕 Fisher 精确检验(Phase Deploy) │ ├── anova_one.R # 🆕 单因素方差分析(Phase Deploy) │ ├── wilcoxon.R # 🆕 Wilcoxon 符号秩检验(Phase Deploy) │ ├── linear_reg.R # 🆕 线性回归(Phase Deploy) │ ├── baseline_table.R # 🆕 基线特征表 — 复合工具(Phase Deploy) │ └── meta_analysis.R # 🆕 Meta 分析引擎 — HR/二分类/连续型(ASL 工具 5) ├── tests/ │ ├── run_all_tools_test.js # 🆕 全工具自动化测试(12 工具 + JIT + blocks 校验) │ ├── test_t_test.json # T 检验测试数据 │ ├── test_fisher.json # Fisher 测试数据 │ ├── test_anova_one.json # ANOVA 测试数据 │ ├── test_wilcoxon.json # Wilcoxon 测试数据 │ ├── test_linear_reg.json # 线性回归测试数据 │ ├── test_baseline_table.json # 基线表测试数据 │ └── fixtures/ │ └── normal_data.csv # 测试数据 ├── metadata/ # 工具元数据(预留) └── templates/ # 解释模板(预留) ``` --- ## 更新日志 | 版本 | 日期 | 更新内容 | |------|------|----------| | v1.6 | 2026-03-07 | Agent 通道错误处理增强:`execute-code` 端点新增结构化错误响应(`error_type`/`error_line`/`error_context`/`console_output`),`withCallingHandlers` 捕获 warnings+messages,`format_agent_error()` 从 20+ 模式匹配中提取错误分类+行号+上下文+修复建议(§5.5),便于 CoderAgent 自动修复重试 | | v1.5 | 2026-03-02 | SSA 双通道架构:新增 `POST /api/v1/execute-code` 沙箱端点(§5.5)供 Agent 通道执行 LLM 生成的 R 代码,含超时 + 隔离环境;架构图新增 Agent 通道入口 | | v1.4 | 2026-02-26 | ASL Meta 分析引擎:工具 12→13(+ST_META_ANALYSIS),Dockerfile 新增 `meta` 包,新增 §5.7 Meta 分析 API 示例、陷阱 8(对数尺度反变换)、§9.5 Meta E2E 测试(36 断言全通过),架构图更新 ASL 调用方 | | v1.3 | 2026-02-22 | 开发者体验增强:新工具模板补全 report_blocks(§6.1)、各工具 params 速查表(§6.5)、R 语言 7 大陷阱实录(§6.6)、新增 R 包操作指南(§6.7)、新增 Q11-Q13 常见问题 | | v1.2 | 2026-02-22 | Phase Deploy 完成:工具 7→12(+Fisher/ANOVA/Wilcoxon/线性回归/基线表)、Dockerfile 新增 gtsummary 等 5 包、Block-based 输出协议文档化(§6.4)、全工具测试脚本 | | v1.1 | 2026-02-20 | Phase 2A 完成:7 个统计工具、JIT 护栏、热重载说明、常见问题补充 | | v1.0 | 2026-02-19 | 初始版本:架构设计、部署指南、T 检验工具 | --- **文档结束**