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

351 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# **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 一份数据,开始开发“执行路径树”和“代码下载”组件。