Files
AIclinicalresearch/docs/03-业务模块/SSA-智能统计分析/06-开发记录/J技术报告审核评估与建议/SSA-Pro 动态结果渲染与通信协议规范.md
HaHafeng 371e1c069c feat(ssa): Complete QPER architecture - Query, Planner, Execute, Reflection layers
Implement the full QPER intelligent analysis pipeline:

- Phase E+: Block-based standardization for all 7 R tools, DynamicReport renderer, Word export enhancement

- Phase Q: LLM intent parsing with dynamic Zod validation against real column names, ClarificationCard component, DataProfile is_id_like tagging

- Phase P: ConfigLoader with Zod schema validation and hot-reload API, DecisionTableService (4-dimension matching), FlowTemplateService with EPV protection, PlannedTrace audit output

- Phase R: ReflectionService with statistical slot injection, sensitivity analysis conflict rules, ConclusionReport with section reveal animation, conclusion caching API, graceful R error classification

End-to-end test: 40/40 passed across two complete analysis scenarios.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-21 18:15:53 +08:00

172 lines
6.1 KiB
Markdown
Raw 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-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.jsNode.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 }) \=\> \<ReactMarkdown\>{content}\</ReactMarkdown\>;
const SciTableBlock \= ({ title, data, footer }) \=\> (
\<div\>
\<h4\>{title}\</h4\>
\<table className="sci-table"\>
\<thead\>\<tr\>{data.headers.map(h \=\> \<th\>{h}\</th\>)}\</tr\>\</thead\>
\<tbody\>{data.rows.map(row \=\> \<tr\>{row.map(cell \=\> \<td\>{cell}\</td\>)}\</tr\>)}\</tbody\>
\</table\>
{footer && \<p\>{footer}\</p\>}
\</div\>
);
const ImageBlock \= ({ title, src, caption }) \=\> (
\<div\>
\<h4\>{title}\</h4\>
\<img src={\`data:image/png;base64,${src}\`} alt={title} /\>
\<p\>{caption}\</p\>
\</div\>
);
// 2\. 核心动态渲染器
export const DynamicReport \= ({ blocks }) \=\> {
return (
\<div className="report-container space-y-8"\>
{blocks.map((block, index) \=\> {
switch (block.type) {
case 'markdown':
return \<MarkdownBlock key={index} {...block} /\>;
case 'table':
return \<SciTableBlock key={index} {...block} /\>;
case 'image':
return \<ImageBlock key={index} {...block} /\>;
case 'key\_value':
return \<KeyValueBlock key={index} {...block} /\>;
default:
return \<div key={index}\>未知的区块类型: {block.type}\</div\>;
}
})}
\</div\>
);
};
## **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 团队极大的排版自由度,同时彻底保护了前端和后端的架构稳定性。