Files
AIclinicalresearch/docs/03-业务模块/SSA-智能统计分析/02-技术设计/SSA-01 R工具封装标准与前后端数据协议技术规范.md
HaHafeng 8137e3cde2 feat(ssa): Complete SSA-Pro MVP development plan v1.3
Summary:

- Add PRD and architecture design V4 (Brain-Hand model)

- Complete 5 development guide documents

- Pass 3 rounds of team review (v1.0 -> v1.3)

- Add module status guide document

- Update system status document

Key Features:

- Brain-Hand architecture: Node.js + R Docker

- Statistical guardrails with auto degradation

- HITL workflow: PlanCard -> ExecutionTrace -> ResultCard

- Mixed data protocol: inline vs OSS

- Reproducible R code delivery

MVP Scope: 10 statistical tools

Status: Design 100%, ready for development
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-18 21:58:37 +08:00

12 KiB
Raw Blame History

SSA-01: R工具封装标准与前后端数据协议技术规范

文档状态: v1.0 (Draft)

创建日期: 2026-02-06

适用对象: R 工程师、后端工程师

目标: 定义 SSA 模块中 100+ R 工具的标准化封装规范,以及前后端交互的数据契约。

1. 核心设计理念

为了实现 SSA-Pro V4.0 的“同步调用”、“统计护栏”和“白盒交付”,我们需要对现有的 R 脚本进行 "Wrapper 改造"

  • 原则 1JSON In, JSON Out。所有工具统一使用 JSON 格式进行输入输出,严禁直接读写本地文件路径(除非是临时的)。
  • 原则 2护栏内嵌 (Guardrails Inside)。统计假设检验(如正态性)必须在 R 代码内部完成,而不是依赖 LLM。
  • 原则 3代码自生成 (Self-Generating)。每个工具必须能“吐出”一份可独立运行的 R 代码片段,用于交付给用户。

2. R 工具封装标准 (The Wrapper Spec)

所有纳入 SSA 体系的 R 工具,必须遵循以下函数签名和文件结构。

2.1 标准函数签名

#' @title SSA Standard Wrapper Interface
#' @param input_json JSON字符串或列表包含 data, params, guardrails 等
#' @return JSON字符串包含 status, results, plots, trace_log, reproducible_code
run_ssa_tool <- function(input_json) {
# ...
}

2.2 输入结构定义 (Input Schema)

后端调用 R API 时Request Body 将被解析为以下 R List 结构:

list(
# 1. 核心数据 (必需)
# 前端/DC模块清洗后的干净数据通常为数据框的列列表格式
data = list(
col_group = c("A", "A", "B", "B", ...),
col_val = c(1.2, 1.3, 2.1, 2.2, ...)
),

# 2. 统计参数 (必需)
# 由 LLM 生成,用于控制统计行为
params = list(
group_col = "col_group",
val_col = "col_val",
conf_level = 0.95,
paired = FALSE,
alternative = "two.sided"
),

# 3. 护栏配置 (可选)
# 控制是否开启强制检查
guardrails = list(
check_normality = TRUE,
check_variance = TRUE,
auto_fix = TRUE # 若检查失败,是否允许自动降级(如T检验转Wilcoxon)
),

# 4. 元信息 (可选)
meta = list(
tool_code = "ST_T_TEST_IND",
user_id = "u123",
session_id = "s456"
)
)

2.3 输出结构定义 (Output Schema)

R 函数必须返回以下结构的 List (最终被 Plumber 序列化为 JSON)

list(
# 1. 执行状态
status = "success", # "success" | "warning" | "error"
message = "执行成功,数据满足正态分布。", # 给用户看的简短提示

# 2. 统计结果 (结构化)
# 用于前端渲染表格或 LLM 解读
results = list(
method = "Two Sample t-test",
statistic = 2.34, # t值
p_value = 0.023,
conf_int = c(0.5, 2.1),
estimate = c(mean_x = 5.1, mean_y = 4.2),
df = 18
),

# 3. 可视化 (Base64)
# 推荐返回 1-2 张核心图表
plots = list(
"data:image/png;base64,iVBORw0K...", # 图1
"data:image/png;base64,..." # 图2
),

# 4. 执行路径日志 (Trace Log)
# 用于前端展示 "执行树"
trace_log = list(
list(step = "check_normality", status = "pass", msg = "Shapiro-Wilk P=0.23 > 0.05"),
list(step = "check_variance", status = "pass", msg = "Levene P=0.45 > 0.05"),
list(step = "main_test", status = "done", msg = "t.test executed")
),

# 5. 可复现代码 (Reproducible Code)
# 用户下载的 R 脚本内容
reproducible_code = "library(ggplot2)\ndata <- read.csv('your_data.csv')..."
)

3. R 脚本开发模板 (Developer Guide)

R 工程师请直接复制此模板开发新工具。以 "独立样本 T 检验" 为例。

文件名tools/st_t_test_ind.R

library(jsonlite)
library(ggplot2)
library(car) # for leveneTest

#' @title 独立样本T检验 (Independent Samples T-Test)
#' @description 用于比较两组独立正态分布数据的均值是否存在显著差异。
#' @usage_context 适用于数值型因变量(Y)和二分类自变量(X)。需满足正态性和方差齐性。
#' @param input_json 标准输入对象
#' @export
run_tool <- function(input_json) {

# --- 0. 初始化 ---
logs <- list()
log_step <- function(step, status, msg) {
logs <<- c(logs, list(list(step=step, status=status, msg=msg)))
}

# 解析数据
df <- as.data.frame(input_json$data)
p <- input_json$params

# 构造复现代码 (Header)
code_lines <- c(
"# ------------------------------------------------",
"# SSA 生成代码: 独立样本 T 检验",
"# ------------------------------------------------",
"library(ggplot2)",
"library(car)",
"",
"# 1. 加载数据 (请替换为您的本地文件路径)",
"df <- read.csv('your_data.csv')",
""
)

tryCatch({

\# \--- 1\. 数据预处理 (Statistical Prep) \---  
\# 强制类型转换  
df\[\[p$group\_col\]\] \<- as.factor(df\[\[p$group\_col\]\])  
df\[\[p$val\_col\]\] \<- as.numeric(df\[\[p$val\_col\]\])  
  
code\_lines \<- c(code\_lines,   
  "\# 2\. 数据预处理",  
  sprintf("df\[\['%s'\]\] \<- as.factor(df\[\['%s'\]\])", p$group\_col, p$group\_col),  
  sprintf("df\[\['%s'\]\] \<- as.numeric(df\[\['%s'\]\])", p$val\_col, p$val\_col)  
)  
  
\# \--- 2\. 护栏检查 (Guardrails) \---  
run\_test \<- "t.test" \# 默认方法  
  
if (isTRUE(input\_json$guardrails$check\_normality)) {  
  \# 简化的正态性检查 (对每组进行 Shapiro 检验)  
  groups \<- unique(df\[\[p$group\_col\]\])  
  is\_normal \<- TRUE  
    
  for (g in groups) {  
    sub\_data \<- df\[df\[\[p$group\_col\]\] \== g, p$val\_col\]  
    \# 样本量 \< 3 或 \> 5000 不做 shapiro  
    if (length(sub\_data) \>= 3 && length(sub\_data) \<= 5000\) {  
      pval \<- shapiro.test(sub\_data)$p.value  
      if (pval \< 0.05) is\_normal \<- FALSE  
    }  
  }  
    
  if (\!is\_normal) {  
    log\_step("check\_normality", "fail", "数据不满足正态分布 (P\<0.05)")  
    if (isTRUE(input\_json$guardrails$auto\_fix)) {  
      run\_test \<- "wilcox.test"  
      log\_step("auto\_fix", "switch", "自动降级为 Wilcoxon 秩和检验")  
      code\_lines \<- c(code\_lines, "\# 注意:由于数据不满足正态分布,已切换为非参数检验")  
    } else {  
      return(list(status="error", message="数据不满足正态分布,请尝试非参数检验。", trace\_log=logs))  
    }  
  } else {  
    log\_step("check\_normality", "pass", "正态性检验通过")  
  }  
}  
  
\# \--- 3\. 核心计算 \---  
f \<- as.formula(paste(p$val\_col, "\~", p$group\_col))  
  
if (run\_test \== "t.test") {  
  \# 方差齐性检查  
  var\_pval \<- leveneTest(f, data=df)$\`Pr(\>F)\`\[1\]  
  var\_equal \<- var\_pval \> 0.05  
  res \<- t.test(f, data=df, var.equal=var\_equal)  
    
  log\_step("main\_test", "done", sprintf("T-Test (var.equal=%s)", var\_equal))  
    
  \# 添加代码  
  code\_lines \<- c(code\_lines,   
    "", "\# 3\. 执行 T 检验",  
    sprintf("res \<- t.test(%s \~ %s, data=df, var.equal=%s)", p$val\_col, p$group\_col, var\_equal),  
    "print(res)"  
  )  
    
} else {  
  res \<- wilcox.test(f, data=df)  
  log\_step("main\_test", "done", "Wilcoxon Test")  
    
  \# 添加代码  
  code\_lines \<- c(code\_lines,   
    "", "\# 3\. 执行 Wilcoxon 检验",  
    sprintf("res \<- wilcox.test(%s \~ %s, data=df)", p$val\_col, p$group\_col),  
    "print(res)"  
  )  
}  
  
\# \--- 4\. 绘图 \---  
plot\_file \<- tempfile(fileext \= ".png")  
png(plot\_file, width=800, height=600)  
  
p\_plot \<- ggplot(df, aes\_string(x=p$group\_col, y=p$val\_col, fill=p$group\_col)) \+  
  geom\_boxplot() \+  
  theme\_minimal() \+  
  labs(title="Boxplot Comparison")  
print(p\_plot)  
  
dev.off()  
  
\# 转 Base64  
plot\_base64 \<- base64enc::base64encode(plot\_file)  
plot\_base64 \<- paste0("data:image/png;base64,", plot\_base64)  
  
\# 添加绘图代码  
code\_lines \<- c(code\_lines,   
  "", "\# 4\. 绘图",  
  "library(ggplot2)",  
  sprintf("ggplot(df, aes(x=%s, y=%s, fill=%s)) \+ geom\_boxplot() \+ theme\_minimal()",   
          p$group\_col, p$val\_col, p$group\_col)  
)  
  
\# \--- 5\. 返回结果 \---  
return(list(  
  status \= "success",  
  message \= "分析完成",  
  results \= list(  
    method \= res$method,  
    statistic \= as.numeric(res$statistic),  
    p\_value \= as.numeric(res$p.value),  
    conf\_int \= if(\!is.null(res$conf.int)) as.numeric(res$conf.int) else NULL  
  ),  
  plots \= list(plot\_base64),  
  trace\_log \= logs,  
  reproducible\_code \= paste(code\_lines, collapse="\\n")  
))  

}, error = function(e) {
return(list(status="error", message=e$message, trace_log=logs))
})
}

4. 前后端通信 API (API Contract)

4.1 执行统计分析

  • URL: POST /api/v1/ssa/execute
  • Content-Type: application/json
  • 发起方: Frontend (用户点击“确认并执行”后)

Request Body:

{
"tool_code": "ST_T_TEST_IND",
"data": {
"group": ["A", "A", "B", "B"],
"bmi": [21.5, 22.1, 25.4, 26.8]
},
"params": {
"group_col": "group",
"val_col": "bmi",
"conf_level": 0.95
},
"guardrails": {
"check_normality": true,
"auto_fix": true
}
}

Response Body (200 OK):

{
"code": 200,
"data": {
"status": "success",
"message": "分析完成",
"results": {
"method": "Welch Two Sample t-test",
"p_value": 0.042,
"statistic": -2.31
},
"plots": ["data:image/png;base64,..."],
"trace_log": [
{"step": "check_normality", "status": "pass", "msg": "正态性检验通过"},
{"step": "main_test", "status": "done", "msg": "T-Test executed"}
],
"reproducible_code": "# SSA 生成代码...\nlibrary(ggplot2)..."
}
}

5. 元数据注册规范 (Metadata Spec)

为了让 SSA-Planner (DeepSeek) 能够检索到这个工具,我们需要提取以下 JSON 元数据,存入 pgvector。

JSON 结构示例:

{
"tool_code": "ST_T_TEST_IND",
"name": "独立样本 T 检验",
"description": "用于比较两组独立样本的均值差异。基于 t 分布理论。",
"usage_context": "适用于1. 因变量为连续数值型2. 自变量为二分类如性别、分组3. 数据满足正态分布和方差齐性。",
"params_schema": {
"group_col": {
"type": "string",
"desc": "分组变量列名必须只有2个水平"
},
"val_col": {
"type": "string",
"desc": "数值变量列名"
}
},
"guardrails_supported": ["check_normality", "check_variance"]
}

6. 总结与行动指南

  1. R 工程师
    • 请按照 第 3 节 (R 脚本开发模板),先试着封装 1 个工具(如 T 检验)。
    • 确保 reproducible_code 生成的代码可以在干净的 RStudio 环境中跑通。
    • 确保所有 library() 调用都在函数内部或头部声明。
  2. 后端工程师
    • 在 Node.js 中实现 POST /api/v1/ssa/execute 接口。
    • 该接口的核心逻辑是:将前端 JSON -> 转发给 R Plumber 服务 -> 接收 R 响应 -> 存入数据库日志 -> 返回前端。
    • 不要在 Node.js 里写任何统计逻辑,只做“二传手”。
  3. 前端工程师
    • 根据 API 定义Mock 一份数据,开始开发“执行路径树”和“代码下载”组件。