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>
351 lines
12 KiB
Markdown
351 lines
12 KiB
Markdown
# **SSA-01: R工具封装标准与前后端数据协议技术规范**
|
||
|
||
**文档状态:** v1.0 (Draft)
|
||
|
||
**创建日期:** 2026-02-06
|
||
|
||
**适用对象:** R 工程师、后端工程师
|
||
|
||
**目标:** 定义 SSA 模块中 100+ R 工具的标准化封装规范,以及前后端交互的数据契约。
|
||
|
||
## **1\. 核心设计理念**
|
||
|
||
为了实现 **SSA-Pro V4.0** 的“同步调用”、“统计护栏”和“白盒交付”,我们需要对现有的 R 脚本进行 **"Wrapper 改造"**。
|
||
|
||
* **原则 1:JSON 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 一份数据,开始开发“执行路径树”和“代码下载”组件。 |