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