# **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 一份数据,开始开发“执行路径树”和“代码下载”组件。