# **SSA-Pro 动态结果渲染与通信协议规范** **文档版本:** v1.0 **创建日期:** 2026-02-20 **解决痛点:** 统一 100+ 种统计工具的输出格式,实现后端免维护、前端动态渲染。 **核心思想:** R 端输出的不是“数据”,而是“UI 渲染区块 (Blocks)”。 ## **1\. 核心架构思想:区块化 (Block-based Architecture)** 借鉴 Notion 和 Jupyter Notebook 的设计思想,我们将所有可能的统计输出抽象为**有限的几种“基础积木(Blocks)”**。 不论是 T 检验、生存分析还是复杂的回归模型,其输出结果最终都可以拆解为以下 4 种基础类型的组合: 1. **text / markdown**: 用于 AI 解读、简单结论、警告提示。 2. **table**: 用于三线表、矩阵、数据框。 3. **image**: 用于所有的可视化图表。 4. **key\_value**: 用于展示核心统计量(如 P 值、t 值等高亮卡片)。 ## **2\. JSON 通信协议定义 (The Universal Protocol)** R 服务最终返回给 Node.js,Node.js 再原封不动透传给前端的 JSON 结构,**必须**是如下的标准协议: { "status": "success", "trace\_log": \[ ...执行日志... \], "reproducible\_code": "library(ggplot2)...", // ⭐ 核心变革:统一的 blocks 数组 "report\_blocks": \[ { "type": "markdown", "content": "\*\*AI 解读:\*\* 结果表明新药组的血压下降幅度显著大于对照组..." }, { "type": "table", "title": "Table 1\. 组间差异比较", "data": { "headers": \["Group", "N", "Median \[IQR\]", "P-Value"\], "rows": \[ \["Drug", "60", "14.5 \[12.1-16.8\]", "\< 0.001 \*\*"\], \["Placebo", "60", "8.2 \[6.5-10.4\]", ""\] \] }, "footer": "Note: IQR \= Interquartile Range; \*\* P \< 0.01" }, { "type": "image", "title": "Figure 1\. 血压下降值分布", "format": "base64", "src": "iVBORw0KGgoAAAANSUhEUgAAAAE...", // base64 字符串 "caption": "箱线图展示了两组的分布情况" }, { "type": "key\_value", "title": "核心指标", "items": \[ {"label": "W Statistic", "value": "2845.5"}, {"label": "Effect Size (r)", "value": "0.45", "status": "warning"} \] } \] } ## **3\. R 端的开发规范 (如何吐出这个协议?)** R 工程师在封装 Wrapper 时,不需要关心前端怎么画图,只需要把结果打包成上述的 list。 **R 代码示例 (以 T 检验为例):** run\_tool \<- function(input) { \# ... 执行计算 res \<- t.test(...) ... \# ... 画图并转 base64 ... \# 统一打包为 Blocks report\_blocks \<- list( list( type \= "markdown", content \= sprintf("独立样本 T 检验结果显示,P值为 %.3f。", res$p.value) ), list( type \= "table", title \= "描述统计与检验结果", data \= list( headers \= c("统计量", "数值"), rows \= list( c("t 值", round(res$statistic, 2)), c("自由度 df", round(res$parameter, 2)), c("P 值", res$p.value) ) ) ), list( type \= "image", title \= "均值差异对比图", format \= "base64", src \= base64\_image\_string ) ) return(list( status \= "success", trace\_log \= logs, report\_blocks \= report\_blocks, \# 直接返回 Blocks 数组 reproducible\_code \= code\_str )) } ## **4\. 前端的动态渲染策略 (Dynamic Renderer)** 前端彻底解放。**前端不需要写 TTestResult.tsx 或 AnovaResult.tsx,只需要写一个 DynamicReport.tsx。** 前端只需遍历 report\_blocks 数组,根据 type 挂载对应的基础组件即可。 **React 伪代码:** // 1\. 准备基础积木组件 const MarkdownBlock \= ({ content }) \=\> \{content}\; const SciTableBlock \= ({ title, data, footer }) \=\> ( \ \{title}\ \ \\{data.headers.map(h \=\> \{h}\)}\\ \{data.rows.map(row \=\> \{row.map(cell \=\> \{cell}\)}\)}\ \ {footer && \{footer}\} \ ); const ImageBlock \= ({ title, src, caption }) \=\> ( \ \{title}\ \{title} \{caption}\ \ ); // 2\. 核心动态渲染器 export const DynamicReport \= ({ blocks }) \=\> { return ( \
{blocks.map((block, index) \=\> { switch (block.type) { case 'markdown': return \; case 'table': return \; case 'image': return \; case 'key\_value': return \; default: return \
未知的区块类型: {block.type}\; } })} \ ); }; ## **5\. Node.js 的角色 (Zero-Maintenance)** 通过这套协议,Node.js 后端变成了绝对的 **“零维护 (Zero-Maintenance)”** 状态。 如果未来 R 团队新增了第 101 个工具(比如一个极度复杂的神经网络模型,返回 5 张表、10 张图),Node.js 的代码 **一行都不需要改**!因为它只负责把 R 返回的 JSON 原样抛给 React。 ## **6\. 总结:多表多图的终极解法** * **问:如何展示多表多图?** * **答:R 脚本往 report\_blocks 数组里不断 push 即可。想展示几张就 push 几个 image block。** 这种“协议化、区块化”的设计,是现代 SaaS 平台(如飞书、Notion、Jupyter)的基石架构。它赋予了 R 团队极大的排版自由度,同时彻底保护了前端和后端的架构稳定性。