Phase I - Session Blackboard + READ Layer: - SessionBlackboardService with Postgres-Only cache - DataProfileService for data overview generation - PicoInferenceService for LLM-driven PICO extraction - Frontend DataContextCard and VariableDictionaryPanel - E2E tests: 31/31 passed Phase II - Conversation Layer LLM + Intent Router: - ConversationService with SSE streaming - IntentRouterService (rule-first + LLM fallback, 6 intents) - SystemPromptService with 6-segment dynamic assembly - TokenTruncationService for context management - ChatHandlerService as unified chat entry - Frontend SSAChatPane and useSSAChat hook - E2E tests: 38/38 passed Phase III - Method Consultation + AskUser Standardization: - ToolRegistryService with Repository Pattern - MethodConsultService with DecisionTable + LLM enhancement - AskUserService with global interrupt handling - Frontend AskUserCard component - E2E tests: 13/13 passed Phase IV - Dialogue-Driven Analysis + QPER Integration: - ToolOrchestratorService (plan/execute/report) - analysis_plan SSE event for WorkflowPlan transmission - Dual-channel confirmation (ask_user card + workspace button) - PICO as optional hint for LLM parsing - E2E tests: 25/25 passed R Statistics Service: - 5 new R tools: anova_one, baseline_table, fisher, linear_reg, wilcoxon - Enhanced guardrails and block helpers - Comprehensive test suite (run_all_tools_test.js) Documentation: - Updated system status document (v5.9) - Updated SSA module status and development plan (v1.8) Total E2E: 107/107 passed (Phase I: 31, Phase II: 38, Phase III: 13, Phase IV: 25) Co-authored-by: Cursor <cursoragent@cursor.com>
1245 lines
36 KiB
Markdown
1245 lines
36 KiB
Markdown
# R 统计引擎架构与部署指南
|
||
|
||
> **版本:** v1.3
|
||
> **更新日期:** 2026-02-22
|
||
> **维护者:** SSA-Pro 开发团队
|
||
> **状态:** ✅ 生产就绪(Phase Deploy 完成 — 12 工具 + Block-based 标准化输出)
|
||
|
||
---
|
||
|
||
## 📋 目录
|
||
|
||
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 │ │ 其他 │ │ 其他 │ │
|
||
│ │ 智能统计 │ │ 模块 │ │ 模块 │ │
|
||
│ └────┬────┘ └─────────┘ └─────────┘ │
|
||
├───────┼─────────────────────────────────────────────────────┤
|
||
│ ▼ 通用能力层 │
|
||
│ ┌─────────────────────────────────────────────────────┐ │
|
||
│ │ R 统计引擎 (Docker) │ │
|
||
│ │ • /health 健康检查 │ │
|
||
│ │ • /api/v1/tools 工具列表 │ │
|
||
│ │ • /api/v1/skills 技能执行 │ │
|
||
│ └─────────────────────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 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 新增) |
|
||
| 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 新增依赖)
|
||
RUN R -e "install.packages(c( \
|
||
'plumber', \
|
||
'jsonlite', \
|
||
'ggplot2', \
|
||
'glue', \
|
||
'dplyr', \
|
||
'tidyr', \
|
||
'base64enc', \
|
||
'yaml', \
|
||
'car', \
|
||
'httr', \
|
||
'scales', \
|
||
'gridExtra', \
|
||
'gtsummary', \
|
||
'gt', \
|
||
'broom' \
|
||
), 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 包安装(15 个包含 gtsummary/gt) | ~10 分钟 |
|
||
| **总计** | **~13 分钟** |
|
||
|
||
---
|
||
|
||
## 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": 1
|
||
}
|
||
```
|
||
|
||
### 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",
|
||
"t_test_ind",
|
||
"t_test_paired",
|
||
"wilcoxon"
|
||
],
|
||
"count": 12
|
||
}
|
||
```
|
||
|
||
#### 已实现的统计工具(12 个)
|
||
|
||
**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 的一键式基线表生成 |
|
||
|
||
### 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 复合工具示例:基线特征表(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 合并行)。
|
||
|
||
---
|
||
|
||
## 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` (数组,不传则自动全选) |
|
||
|
||
### 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
|
||
```
|
||
|
||
### 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 工具集中测试脚本(12 工具 + JIT)
|
||
|
||
项目提供了 R 统计引擎的全工具测试脚本:
|
||
|
||
```bash
|
||
# 仅测试 R 服务层(12 工具 + JIT 护栏 + report_blocks 校验)
|
||
node r-statistics-service/tests/run_all_tools_test.js
|
||
```
|
||
|
||
测试覆盖:
|
||
- 12 个统计工具(Phase 2A × 7 + Phase Deploy × 5)
|
||
- 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 服务(12 个统计工具 + JIT 护栏)
|
||
- Layer 2: Python DataProfile API
|
||
- Layer 3: Node.js 后端 API(登录 → 会话 → 规划 → 执行)
|
||
|
||
---
|
||
|
||
## 附录:文件结构
|
||
|
||
```
|
||
r-statistics-service/
|
||
├── Dockerfile # 生产镜像定义(含 gtsummary/gt/broom/scales/gridExtra)
|
||
├── docker-compose.yml # 开发环境编排(含 volume 挂载)
|
||
├── renv.lock # R 包版本锁定(备用)
|
||
├── .Rprofile # R 启动配置(备用)
|
||
├── plumber.R # API 入口(含 JIT 护栏端点,自动发现 tools/ 目录)
|
||
├── utils/
|
||
│ ├── data_loader.R # 数据加载(支持行格式/列格式)
|
||
│ ├── guardrails.R # 统计护栏 + JIT 检查(12 工具全覆盖)
|
||
│ ├── error_codes.R # 错误映射
|
||
│ ├── result_formatter.R # 结果格式化
|
||
│ └── block_helpers.R # Block-based 输出辅助函数(Phase E+ 协议)
|
||
├── tools/ # 统计工具(12 个)
|
||
│ ├── 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)
|
||
├── 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.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 检验工具 |
|
||
|
||
---
|
||
|
||
**文档结束**
|