feat(ssa): Complete SSA-Pro MVP development plan v1.3

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>
This commit is contained in:
2026-02-18 21:58:37 +08:00
parent f9ed0c2528
commit 8137e3cde2
19 changed files with 5756 additions and 98 deletions

View File

@@ -1,10 +1,11 @@
# AIclinicalresearch 系统当前状态与开发指南
> **文档版本:** v5.2
> **文档版本:** v5.3
> **创建日期:** 2025-11-28
> **维护者:** 开发团队
> **最后更新:** 2026-02-18
> **🎉 重大里程碑:**
> - **2026-02-18SSA MVP 开发计划 v1.3 完成!** Brain-Hand架构 + 统计护栏 + HITL + 10工具MVP准予启动开发
> - **2026-02-18RVW V2.0 Week 3 完成!** 统计验证扩展 + 负号归一化 + 文件格式提示 + 用户体验优化
> - **2026-02-18RVW V2.0 Skills 架构完成!** Skills 核心框架 + 3个 Skill 实现 + ReviewWorker 改造
> - **2026-02-17RVW V2.0 "数据侦探" Day 6 完成!** L2统计验证器 + L2.5一致性取证SE三角验证、SD>Mean
@@ -67,7 +68,7 @@
| **ASL** | AI智能文献 | 文献筛选、Meta分析、证据图谱 | ⭐⭐⭐⭐⭐ | 🎉 **智能检索MVP完成60%** - DeepSearch集成 | **P0** |
| **DC** | 数据清洗整理 | ETL + 医学NER百万行级数据 | ⭐⭐⭐⭐⭐ | ✅ **Tool B完成 + Tool C 99%(异步架构+性能优化-99%+多指标转换+7大功能** | **P0** |
| **IIT** | IIT Manager Agent | AI驱动IIT研究助手 - 双脑架构+REDCap集成 | ⭐⭐⭐⭐⭐ | 🎉 **事件级质控V3.1完成设计100%代码60%** | **P0** |
| **SSA** | 智能统计分析 | 队列/预测模型/RCT分析 | ⭐⭐⭐⭐⭐ | 📋 规划中 | P2 |
| **SSA** | 智能统计分析 | Brain-Hand架构 + 统计护栏 + HITL + 10工具MVP | ⭐⭐⭐⭐⭐ | 🚀 **MVP计划v1.3完成设计100%开发0%** - 准予启动开发 | **P1** |
| **ST** | 统计分析工具 | 100+轻量化统计工具 | ⭐⭐⭐⭐ | 📋 规划中 | P2 |
| **RVW** | 稿件审查系统 | 方法学评估 + 🆕数据侦探L1/L2/L2.5验证)+ Skills架构 + Word导出 | ⭐⭐⭐⭐ | 🚀 **V2.0 Week3完成85%** - 统计验证扩展+负号归一化+文件格式提示+用户体验优化 | P1 |
| **ADMIN** | 运营管理端 | Prompt管理、租户管理、用户管理、运营监控、系统知识库 | ⭐⭐⭐⭐⭐ | 🎉 **Phase 4.6完成88%** - Prompt知识库集成+动态注入 | **P0** |

View File

@@ -0,0 +1,319 @@
# SSA智能统计分析模块 - 当前状态与开发指南
> **文档版本:** v1.0
> **创建日期:** 2026-02-18
> **最后更新:** 2026-02-18
> **维护者:** 开发团队
> **当前状态:** 📋 **MVP 开发计划 v1.3 完成,准予启动开发**
> **文档目的:** 快速了解SSA模块状态为新AI助手提供上下文
>
> **🎉 里程碑2026-02-18**
> - ✅ **PRD 完成**SSA-Pro 严谨型智能统计分析模块需求定义
> - ✅ **架构设计 V4 完成**Brain-Hand 双层架构 + 统计护栏 + HITL 人机协同
> - ✅ **MVP 开发计划 v1.3 完成**:通过 3 轮团队评审,准予启动开发
> - ✅ **5 份开发文档完成**总览、任务清单、R服务指南、后端指南、前端指南
---
## 📊 模块概览
### 基本信息
| 项目 | 信息 |
|------|------|
| **模块名称** | SSA - 智能统计分析 (Smart Statistical Analysis) |
| **模块定位** | AI驱动的"白盒"统计分析系统 |
| **商业价值** | ⭐⭐⭐⭐⭐ 极高 |
| **独立性** | ⭐⭐⭐⭐ 高(可独立使用,也可与其他模块协同) |
| **目标用户** | 临床研究人员、生物统计师 |
| **开发状态** | 📋 **MVP 开发计划完成,准备启动开发** |
### 核心目标
> 打造一个 **"白盒"、"严谨"、"可交付"** 的智能统计分析系统。
>
> **核心差异化**
> 1. **白盒**:用户完全理解 AI 做了什么,为什么这样做
> 2. **严谨**:统计护栏自动检测前提条件,违规时自动降级
> 3. **可交付**:生成可在本地运行的 R 代码,支持审计复现
### 功能规格
#### 核心AI能力规划中
1. **智能规划Planner**
- RAG 工具检索:根据用户意图召回最适合的统计方法
- 参数映射:将自然语言映射为统计参数
- 统计分析计划SAP生成
2. **统计护栏Guardrails**
- 正态性检验Shapiro-Wilk
- 方差齐性检验Levene
- 样本量检验
- 大样本优化N > 5000 抽样检验)
3. **人机协同HITL**
- Plan Card用户确认/修改分析计划
- Execution Trace实时展示执行路径
- Result Card结构化结果 + AI 解读
4. **代码交付**
- 生成可复现的 R 代码
- 自动注入依赖安装脚本
- APA 格式化输出p_value_fmt
#### MVP 工具清单10个
| 工具代码 | 工具名称 | 适用场景 |
|---------|---------|---------|
| ST_T_TEST_IND | 独立样本T检验 | 两组连续变量比较 |
| ST_T_TEST_PAIRED | 配对样本T检验 | 配对设计 |
| ST_WILCOXON | Wilcoxon秩和检验 | T检验的非参数替代 |
| ST_ANOVA_ONE | 单因素方差分析 | 多组连续变量比较 |
| ST_CHI_SQUARE | 卡方检验 | 分类变量关联 |
| ST_FISHER | Fisher精确检验 | 小样本分类变量 |
| ST_CORRELATION | Pearson/Spearman相关 | 连续变量相关性 |
| ST_REGRESSION_LINEAR | 线性回归 | 预测建模 |
| ST_REGRESSION_LOGISTIC | Logistic回归 | 二分类预测 |
| ST_DESCRIBE | 描述性统计 | 数据概览 |
---
## 🏗️ 架构设计
### Brain-Hand 双层架构
```
┌─────────────────────────────────────────────────────────┐
│ 用户界面 (Frontend) │
│ DataUploader | PlanCard | ExecutionTrace | ResultCard │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Brain Layer (Node.js) │
│ ┌─────────────────────────────────────────────────────┐│
│ │ DataParserService: 数据解析 → Schema 提取 ││
│ │ ToolRetrievalService: RAG 工具检索 ││
│ │ PlannerService: LLM 规划 + 参数映射 ││
│ │ RClientService: R 服务调用(混合数据协议) ││
│ │ CriticService: 结果解读(流式) ││
│ └─────────────────────────────────────────────────────┘│
│ 📌 只看 Schema无真实数据
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Hand Layer (R Docker) │
│ ┌─────────────────────────────────────────────────────┐│
│ │ data_loader.R: 混合数据协议inline/OSS ││
│ │ guardrails.R: 统计护栏(正态/方差齐性/样本量) ││
│ │ tools/*.R: 统计工具 Wrapperglue 模板) ││
│ │ result_formatter.R: 结果格式化p_value_fmt ││
│ │ error_codes.R: 结构化错误码 ││
│ └─────────────────────────────────────────────────────┘│
│ 📌 操作真实数据 + 生成可复现代码 │
└─────────────────────────────────────────────────────────┘
```
### 关键技术决策
| 决策点 | 方案 | 理由 |
|--------|------|------|
| **R 服务部署** | SAE Docker固定 2 实例) | 避免冷启动延迟 |
| **数据传输** | 混合协议(<2MB inline, 2-20MB OSS | 平衡性能与内存 |
| **代码生成** | glue 模板 | 可维护性好于 paste0 |
| **LLM 输出** | jsonrepair + Zod | 容错 JSON 解析 |
| **隐私保护** | Schema 脱敏 + 分类变量稀有值隐藏 | 防止 LLM 泄露数据 |
---
## 📋 开发进度
| Phase | 任务 | 状态 | 计划日期 |
|-------|------|------|---------|
| Phase 0 | 需求分析与架构设计 | ✅ 已完成 | 2026-02-18 |
| Phase 0 | MVP 开发计划 v1.0 → v1.3 | ✅ 已完成 | 2026-02-18 |
| Phase 1 | 骨架搭建T检验端到端 | 📋 待开始 | - |
| Phase 2 | 智能与交互RAG + HITL | 📋 待开始 | - |
| Phase 3 | 打磨与调试 | 📋 待开始 | - |
| **总计** | - | **设计 100%,开发 0%** | - |
### 开发计划文档
| 文档 | 路径 | 说明 |
|------|------|------|
| **MVP总览** | `04-开发计划/00-MVP开发计划总览.md` | 范围、架构、里程碑 |
| **任务清单** | `04-开发计划/01-任务清单与进度追踪.md` | 可追踪的 TODO 列表 |
| **R服务指南** | `04-开发计划/02-R服务开发指南.md` | R 统计工程师专用 |
| **后端指南** | `04-开发计划/03-后端开发指南.md` | Node.js 工程师专用 |
| **前端指南** | `04-开发计划/04-前端开发指南.md` | 前端工程师专用 |
### 评审记录
| 版本 | 评审报告 | 结论 | 日期 |
|------|---------|------|------|
| v1.0 | `06-开发记录/SSA-Pro 方案深度审查与风险评估报告.md` | 需修订 | 2026-02-18 |
| v1.1 | `06-开发记录/SSA-Pro 方案深度审查与风险评估报告 V2.0.md` | 需修订 | 2026-02-18 |
| v1.2 | `06-开发记录/SSA-Pro V1.2 终极审查与发令报告V3.0.md` | 🟢 通过 | 2026-02-18 |
---
## 🔧 技术依赖
### 复用的平台能力
| 能力 | 位置 | 用途 |
|------|------|------|
| **LLM网关** | `@/common/llm/LLMFactory` | Planner + Critic |
| **RAG引擎** | `@/common/rag` | 工具检索 |
| **存储** | `@/common/storage` | OSS 数据传输 |
| **日志** | `@/common/logging` | 结构化日志 |
| **流式响应** | `@/common/streaming` | Critic 流式输出 |
### LLM模型
| 模型 | 用途 | 说明 |
|------|------|------|
| DeepSeek-V3 | Planner + Query Rewriter | 性价比高 |
| Qwen3-rerank | 工具重排序 | 中文理解好 |
| GPT-5-Pro | Critic 结果解读 | 深度推理 |
### R 依赖
| 包 | 版本 | 用途 |
|-----|------|------|
| plumber | 1.2.1 | Web API 框架 |
| jsonlite | 1.8.8 | JSON 处理 |
| ggplot2 | 3.4.4 | 可视化 |
| glue | 1.7.0 | 模板代码生成 |
| car | 3.1-2 | Levene 检验 |
| httr | 1.4.7 | OSS 下载 |
---
## 📚 相关文档
### 需求文档
- [PRD SSA-Pro 严谨型智能统计分析模块](./00-系统设计/PRD%20SSA-Pro%20严谨型智能统计分析模块.md)
### 设计文档
- [SSA-Pro 严谨型智能统计分析架构设计方案V4](./00-系统设计/SSA-Pro%20严谨型智能统计分析架构设计方案V4.md) ⬅️ **核心架构文档**
- [SSA-Pro (V4.1) 统计技能中心架构规范](./00-系统设计/SSA-Pro%20(V4.1)%20统计技能中心架构规范.md)
### 原型文件
- [智能统计分析V2.html](./03-UI设计/智能统计分析V2.html) - 可直接浏览器打开
### 参考文档
- [云原生开发规范](../../04-开发规范/08-云原生开发规范.md)
- [系统架构分层设计](../../00-系统总体设计/01-系统架构分层设计.md)
---
## 🎯 快速开始
### 目录结构
```
docs/03-业务模块/SSA-智能统计分析/
├── 00-系统设计/ # PRD + 架构设计
├── 01-需求分析/ # 用户故事、用例
├── 02-技术设计/ # 详细技术方案
├── 03-UI设计/ # 原型图
├── 04-开发计划/ # MVP 开发文档5份
├── 05-测试用例/ # 测试方案
└── 06-开发记录/ # 评审报告、开发日志
```
### 开发环境准备
1. **R 服务环境**
```bash
cd r-statistics-service
docker build -t ssa-r-service .
docker run -p 8080:8080 -e DEV_MODE=true ssa-r-service
```
2. **后端开发**
```bash
cd backend
npm run dev
```
3. **前端开发**
```bash
cd frontend-v2
npm run dev
```
### API 接口预览
```http
### 创建会话
POST http://localhost:3001/api/v1/ssa/sessions
Content-Type: multipart/form-data
# file: 数据文件
### 生成分析计划
POST http://localhost:3001/api/v1/ssa/sessions/{sessionId}/plan
Content-Type: application/json
{"query": "比较两组GLU是否有显著差异"}
### 执行分析
POST http://localhost:3001/api/v1/ssa/sessions/{sessionId}/execute
Content-Type: application/json
{"plan": {...}, "debug": false}
### 获取结果
GET http://localhost:3001/api/v1/ssa/sessions/{sessionId}/result
```
---
## ⚠️ 注意事项
### 对新AI助手
1.**设计文档已完成**开发前请先阅读架构设计V4
2.**开发计划v1.3已审批**遵循5份开发文档进行开发
3. ⚠️ **Brain-Hand 隔离**Node.js 只看 SchemaR 操作真实数据
4. ⚠️ **混合数据协议**< 2MB inline2-20MB OSS
5. ⚠️ **代码模板同步**:修改 Wrapper 逻辑时必须同步更新 templates/
### 风险与应对
| 风险 | 概率 | 应对策略 |
|------|------|---------|
| R 工具封装进度慢 | 高 | 先做 5 个核心工具glue 模板化开发 |
| LLM 输出 JSON 格式错误 | 中 | jsonrepair + Zod 强校验 |
| R 服务并发阻塞 | 中 | SAE 固定 2 实例 |
| Node.js xlsx 内存刺客 | 中 | SAE 内存上限 2GB+ |
| R 服务 Segfault 崩溃 | 低 | Liveness Probe + 502/504 友好提示 |
| 本地开发 OSS 不通 | 中 | DEV_MODE 读取本地 fixtures |
| 用户代码缺依赖 | 高 | 模板头部自动安装脚本 |
---
## 🚀 未来规划
### MVP 阶段(当前)
- [ ] Phase 1骨架搭建T检验端到端跑通
- [ ] Phase 2智能与交互RAG + HITL + 10工具
- [ ] Phase 3打磨与调试性能优化 + Bug修复
### V2.0 阶段(规划中)
- [ ] 更多统计方法Meta分析、生存分析、倾向性评分
- [ ] 自定义工具上传
- [ ] 批量分析
- [ ] 报告导出Word/PDF
---
**文档版本:** v1.0
**最后更新:** 2026-02-18
**当前状态:** 📋 MVP 开发计划 v1.3 完成,准予启动开发
**下一步:** Phase 1 骨架搭建

View File

@@ -0,0 +1,162 @@
# **PRD: SSA-Pro 严谨型智能统计分析模块 (V1.0)**
**文档状态:** v1.0 (Final)
**创建日期:** 2026-02-18
**关联架构:** [SSA-Pro\_智能统计分析架构设计方案\_V4.md](https://www.google.com/search?q=../09-SSA-Pro_%E6%99%BA%E8%83%BD%E7%BB%9F%E8%AE%A1%E5%88%86%E6%9E%90%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1%E6%96%B9%E6%A1%88_V4.md)
**关联规范:** [SSA-Pro\_Skills架构规范\_V4.1.md](https://www.google.com/search?q=../16-SSA-Pro_Skills%E6%9E%B6%E6%9E%84%E8%A7%84%E8%8C%83_V4.1.md)
## **1\. 研发背景与业务价值**
### **1.1 背景 (Why Now?)**
目前平台的 **AIA (问答)****DC (清洗)** 模块已趋于成熟,但在**核心统计分析**环节仍存在断层:
1. **用户痛点**临床医生普遍缺乏统计学能力SPSS/SAS 操作复杂,且容易误用统计方法(如数据不符合正态分布却强行用 T 检验)。
2. **竞品缺陷**:通用的 AI如 ChatGPT虽然能写代码但经常产生“幻觉”生成的代码在本地无法运行且存在严重的数据隐私泄露风险。
3. **资产闲置**:公司内部积累了 100+ 高质量的 R 语言统计脚本,目前处于“沉睡”状态,未能转化为 SaaS 服务能力。
### **1.2 产品目标 (Product Goal)**
构建一个 **“白盒化、严谨型、可交付”** 的智能统计分析 Agent (SSA-Pro)。
* **白盒化**:分析过程透明,用户可见(执行路径、护栏检查)。
* **严谨型**强制执行统计假设检验Guardrails防止学术谬误。
* **可交付**:不仅提供结果,还提供**可复现的 R 源代码**,支持本地二次运行。
## **2\. 核心能力与功能列表**
### **2.1 核心流程 (The Core Loop)**
交互模式采用 **"Retrieve-Plan-Confirm-Execute"** 闭环:
| 步骤 | 功能模块 | 关键动作 | 交付物 |
| :---- | :---- | :---- | :---- |
| **1** | **智能规划 (Planner)** | 意图识别 \+ RAG 检索 \+ 参数映射 | **分析预习卡片 (Plan Card)** |
| **2** | **人机确认 (HITL)** | 用户检查参数,点击确认 | 用户授权指令 |
| **3** | **透明执行 (Execution)** | 混合数据传输 \+ 统计护栏 \+ 核心计算 | **执行路径树 (Execution Trace)** |
| **4** | **资产交付 (Delivery)** | 结果解释 \+ 代码生成 \+ 报告导出 | **分析结果包 (Result \+ Code)** |
### **2.2 功能详细说明**
#### **F1. 智能工具检索 (Tool RAG)**
* **需求**:系统需从 100+ 工具中,根据用户自然语言(如“看两组差异”)精准推荐最合适的工具。
* **技术支撑**:基于 pgvector 的语义检索 \+ pg\_bigm 关键词匹配。
* **输入**:用户 Query \+ 数据 Schema列名/类型)。
* **输出**Top-5 候选工具的 JSON Schema。
#### **F2. 统计分析计划生成 (SAP Generation)**
* **需求**AI 不直接跑代码,而是先像人类统计师一样,写一份 SAP统计分析计划
* **内容包含**分析目标、变量映射X/Y、前置假设条件如正态性、降级策略。
* **表现形式**:前端渲染为 **"待确认卡片"**,用户可修改参数。
#### **F3. 统计护栏与自动降级 (Guardrails)**
* **需求**:在执行核心检验前,必须强制检查数据质量与统计假设。
* **逻辑示例**(以 T 检验为例):
1. 检查样本量是否 \> 3。
2. 执行 Shapiro-Wilk 正态性检验。
3. **决策点**:若 P \< 0.05(非正态),自动切换为 **Wilcoxon 秩和检验**,并在前端亮黄灯提示。
* **价值**:这是本产品区别于 ChatGPT 的核心护城河。
#### **F4. 混合数据传输 (Hybrid Data Protocol)**
* **需求**:支持不同大小的数据集高效传输,规避 HTTP JSON 瓶颈。
* **策略**
* **\< 1MB**:直接嵌入 API 请求体Inline JSON
* **1MB \- 20MB**:前端先传 OSS仅向 R 服务传递 OSS File KeyR 服务内网下载。
#### **F5. 代码资产交付 (Reproducible Code)**
* **需求**:用户下载的 R 代码必须能在其本地 RStudio 中直接运行。
* **实现**R Wrapper 动态拼接代码字符串,数据读取路径替换为占位符 read.csv("your\_data.csv")。
## **3\. 技术路线与架构 (Technical Specifications)**
### **3.1 总体架构Brain-Hand 模型**
本模块严格遵循公司 **V4.1 架构标准**
* **Brain (Node.js)**负责认知、规划、检索、Prompt 组装。**绝不处理真实数据内容**,只看 Schema。
* **Hand (R Docker)**:负责执行、计算、绘图。**运行在隔离容器中**,处理真实数据。
### **3.2 关键技术栈**
* **后端**Node.js (Fastify) \+ Prisma
* **统计引擎**Docker \+ R 4.3 \+ Plumber (API 服务)
* **向量库**RDS PostgreSQL \+ pgvector
* **大模型**DeepSeek-V3 (Planner/Critic)
* **前端**React 19 \+ Ant Design X
### **3.3 数据库设计摘要 (Schema)**
需在 capability\_schema 中建立全局统一的技能注册表:
\-- 核心表:统计技能注册表
CREATE TABLE capability\_schema.global\_skills (
skill\_code VARCHAR(50) PRIMARY KEY, \-- e.g. ST\_T\_TEST
provider VARCHAR(50), \-- 'SSA-R-SERVICE'
input\_schema JSONB, \-- OpenAI Function Schema
embedding vector(1024) \-- 用于 RAG 检索
);
## **4\. 数据隐私与安全 (Safety & Privacy)**
### **4.1 数据隔离原则**
* **原则****LLM 永远不可见真实患者数据。**
* **实现**:前端提取 Header 发送给 LLM 做规划;前端将 CSV 发送给 R 服务做计算。两者物理隔离。
### **4.2 R 容器安全**
* **网络阻断**:生产环境 SAE 容器配置 Egress Deny禁止 R 脚本主动发起外网请求。
* **只读文件系统**R 脚本目录设为 Read-Only防止代码篡改。
## **5\. 开发里程碑 (Roadmap)**
### **Phase 1: 骨架搭建 (Week 1-2)**
* **目标**:跑通 T 检验的 "Hello World"。
* **产出**
* R Docker 基础镜像 (含 Plumber)。
* 第 1 个标准化 Wrapper (T-Test)。
* Node.js \-\> R 的同步 API 调通。
### **Phase 2: 交互 MVP (Week 3-5)**
* **目标**:用户可用,体验完整。
* **产出**
* 集成 RAG 检索AI 能听懂“做个差异分析”。
* 前端“确认卡片”与“执行树”组件上线。
* 上线 Top 10 高频统计工具。
### **Phase 3: 量产与 Skills 化 (Week 6-8)**
* **目标**:工具丰富,能力开放。
* **产出**
* 覆盖 50+ 常用工具。
* 完成 **Global Skill Registry** 注册,允许 IIT Manager 模块调用 SSA 能力。
## **6\. 验收标准 (Acceptance Criteria)**
1. **准确性**:对于非正态数据,系统**必须**自动降级为非参数检验,并给出提示。
2. **性能**20MB 数据文件的 T 检验,端到端耗时(含网络传输)不超过 **5秒**
3. **复现性**:下载的 R 代码包,在干净的本地 R 环境中安装依赖后,**必须**能跑通并产出相同结果。
4. **隐私**:审计日志中**严禁**出现具体的患者隐私数据Row Data
## **7\. 附录:工具列表 (MVP Top 10\)**
1. 独立样本 T 检验 (Independent T-Test)
2. 配对样本 T 检验 (Paired T-Test)
3. 单因素方差分析 (One-way ANOVA)
4. 卡方检验 (Chi-square Test)
5. Fisher 精确检验
6. Mann-Whitney U 检验 (Wilcoxon Rank Sum)
7. Pearson/Spearman 相关性分析
8. 单因素线性回归 (Simple Linear Regression)
9. 生存分析 (Kaplan-Meier Curve)
10. Cox 比例风险回归 (Cox Regression)

View File

@@ -0,0 +1,217 @@
# **SSA-Pro (V4.1): 统计技能中心架构规范 (Statistical Skills Architecture)**
**文档版本:** v4.1 (Skills Edition)
**创建日期:** 2026-02-18
**核心理念:** **能力即技能 (Capabilities as Skills)**。将 100+ R 统计工具标准化为平台级 Skills实现“认知依赖注入”与跨模块复用。
**适用范围:** SSA 模块、IIT Manager、Protocol Agent、DC 模块
## **1\. 架构愿景:从“专用工具”到“通用技能”**
在 SSA-Pro V4.1 原有架构(同步 API \+ 护栏 \+ 白盒)的基础上,我们引入 **Skills 范式**,旨在解决以下问题:
1. **消除孤岛**:统计能力不应局限于 SSA 聊天窗口。IIT Manager 需要做质控监控Protocol Agent 需要计算样本量,它们都应该能直接调用 SSA 的 R 代码。
2. **统一治理**:将 R 脚本的元数据、参数定义、权限控制统一在 **Global Skill Registry** 中管理。
3. **认知解耦**LLM大脑不需要知道 R 代码怎么写只需要知道“技能手册Schema”。
## **2\. 核心定义:什么是 "Statistical Skill"**
在我们的系统中,一个 **统计技能 (Statistical Skill)** 由三部分组成:
### **2.1 语义接口 (Semantic Interface)**
* **定义**:自然语言描述,用于 RAG 检索和 LLM 理解。
* **内容**
* **Name**: st\_t\_test\_ind (独立样本 T 检验)
* **Description**: "比较两组独立连续变量的均值差异。"
* **Usage Context**: "当因变量为数值型,自变量为二分类,且数据独立时使用。"
### **2.2 数据契约 (JSON Schema)**
* **定义**:严格的输入输出规范,兼容 OpenAI Function Calling 标准。
* **内容**
{
"type": "object",
"properties": {
"data\_source": { "type": "object", "description": "OSS路径或内联数据" },
"params": {
"group\_col": { "type": "string", "description": "分组列名" },
"val\_col": { "type": "string", "description": "数值列名" }
}
},
"required": \["data\_source", "params"\]
}
### **2.3 原生函数 (Native Function)**
* **定义**:执行逻辑的实体。
* **实现**:运行在 R Docker 容器中的 Plumber API 端点。它包含**统计护栏 (Guardrails)** 和 **代码生成 (Code Gen)** 逻辑。
## **3\. 系统架构蓝图 (Skills Perspective)**
我们将架构划分为 **技能注册层**、**技能路由层** 和 **技能执行层**
graph TD
subgraph "Clients (技能消费者)"
User\[用户 (SSA Chat)\]
IIT\[IIT Manager Agent\]
Proto\[Protocol Agent\]
end
subgraph "Platform Capability Layer (通用能力层)"
Registry\[(Global Skill Registry\<br\>pgvector)\]
Orchestrator\[Skill Orchestrator\<br\>Node.js\]
Planner\[Planner / Router\<br\>DeepSeek-V3\]
end
subgraph "Skill Provider: Statistics (技能提供者)"
R\_Gateway\[R Service API Gateway\]
subgraph "R Docker Container"
Skill\_A\[Skill: T-Test\]
Skill\_B\[Skill: ANOVA\]
Skill\_C\[Skill: Regression\]
Guard\[Guardrails Engine\]
end
end
%% 注册流程
Skill\_A \-.-\>|注册元数据| Registry
%% 调用流程
User \--\>|1. 自然语言需求| Orchestrator
IIT \--\>|1. 监控触发| Orchestrator
Orchestrator \--\>|2. 语义检索| Registry
Registry \--\>|3. 返回 Top-K Skills| Planner
Planner \--\>|4. 生成调用参数 (JSON)| Orchestrator
Orchestrator \--\>|5. 路由执行 (Sync HTTP)| R\_Gateway
R\_Gateway \--\>|6. 执行与护栏| Skill\_A
Skill\_A \--\>|7. 结果 \+ 代码| Orchestrator
## **4\. 数据库设计Global Skill Registry**
我们将元数据存储从 ssa\_schema 提升至全局通用的 capability\_schema (或 common\_schema)。
### **表结构capability\_schema.global\_skills**
CREATE TABLE capability\_schema.global\_skills (
id UUID PRIMARY KEY DEFAULT gen\_random\_uuid(),
\-- 标识信息
skill\_code VARCHAR(50) NOT NULL UNIQUE, \-- e.g., 'ST\_T\_TEST\_IND'
name VARCHAR(100) NOT NULL,
provider VARCHAR(50) NOT NULL, \-- e.g., 'SSA-R-SERVICE'
category VARCHAR(50), \-- e.g., 'STATISTICS', 'DATA\_CLEANING'
\-- 语义信息 (用于 RAG)
description TEXT NOT NULL,
usage\_context TEXT,
search\_text TEXT, \-- 合成检索字段
embedding vector(1024), \-- 向量索引
\-- 契约定义
input\_schema JSONB NOT NULL, \-- JSON Schema for LLM
output\_schema JSONB, \-- 预期输出格式
\-- 执行配置
endpoint VARCHAR(200), \-- R 服务的内部路由路径
is\_active BOOLEAN DEFAULT TRUE,
created\_at TIMESTAMP DEFAULT NOW()
);
\-- 索引
CREATE INDEX idx\_skills\_embedding ON capability\_schema.global\_skills USING hnsw (embedding vector\_cosine\_ops);
CREATE INDEX idx\_skills\_provider ON capability\_schema.global\_skills(provider);
## **5\. 接口协议:标准技能调用**
为了支持跨模块调用,我们需要定义统一的 **Skill Invocation Protocol**
### **5.1 通用调用入口 (Node.js)**
任何 Agent (SSA/IIT/ASL) 都可以通过此方法调用统计能力。
// common/skills/skillExecutor.ts
interface SkillExecutionRequest {
skillCode: string; // 目标技能
payload: Record\<string, any\>; // 符合 input\_schema 的参数
context?: { // 上下文信息
userId: string;
traceId: string;
};
}
async function executeSkill(req: SkillExecutionRequest) {
// 1\. 从 Registry 获取技能配置
const skillDef \= await skillRegistry.get(req.skillCode);
// 2\. 路由分发 (如果是 SSA-R-SERVICE转发给 R Docker)
if (skillDef.provider \=== 'SSA-R-SERVICE') {
return await rClient.post(skillDef.endpoint, req.payload);
}
// ... 处理其他 Provider
}
### **5.2 R 服务端点 (Wrapper 实现)**
R Docker 中的 Plumber API 保持不变,但其角色明确为 **"Native Function Host"**。
* **Endpoint**: `/api/v1/skills/{skill_code}`
* **Behavior**:
1. 接收 JSON Payload。
2. 执行 **Guardrails** (正态性检查等)。
3. 执行核心统计逻辑。
4. 返回标准结果包 (Result \+ Trace \+ Code)。
---
## **6\. 场景演练:跨模块能力复用**
### **场景 ASSA 模块 (标准流程)**
1. **用户**:在 SSA 界面输入“比较两组血压差异”。
2. **Planner**:检索 Registry命中 `ST_T_TEST_IND`,生成 JSON 参数。
3. **Executor**:调用 R 接口,返回图表和报告。
### **场景 BIIT Manager (主动监控)**
1. **背景**IIT Agent 每天扫描 EDC 数据,监控不良事件 (AE)。
2. **触发**Agent 发现实验组 AE 发生率似乎高于对照组。
3. **决策**Agent 决定调用“统计技能”来验证差异是否显著。
4. **调用**
* IIT Agent 构造数据:`{ "group": [...], "ae_flag": [...] }`
* IIT Agent 调用 `executeSkill('ST_CHI_SQUARE', payload)`
5. **结果**R 服务返回 `p_value = 0.03`
6. **行动**IIT Agent 基于 P \< 0.05,向研究者发送企业微信预警:“检测到实验组不良事件显著升高 (P=0.03),请关注。”
---
## **7\. 实施路线调整**
在原有的 SSA-Pro 开发计划上,增加 **"Skills 标准化"** 的工作项:
1. **数据库迁移**
* 原计划:`ssa_schema.tools_library`
* 新计划:`capability_schema.global_skills` (包含 `provider` 字段)
2. **元数据提取脚本 (R)**
* 生成的 JSON 需要包含符合 OpenAI 标准的 `input_schema`
3. **通用调用层 (Node.js)**
* 开发 `SkillService`,作为所有 Agent 调用工具的统一网关。
---
## **8\. 总结**
采用 **SSA-Pro (V4.1) Skills 架构**,我们不仅仅是在开发一个统计工具箱,而是在构建平台的 **"左脑逻辑中心"**。
* **技术上**:保留了 R Docker 的同步高性能和严谨性。
* **架构上**:打通了业务壁垒,实现了统计能力的资产化和服务化。
* **未来**:当我们需要引入 Python 机器学习技能时,只需注册新的 `provider: 'PYTHON-ML-SERVICE'`Planner 即可无缝调用,系统扩展性无限。

View File

@@ -0,0 +1,224 @@
# **SSA-Pro: 严谨型智能统计分析架构设计方案 (V4.0)**
**文档版本:** v4.0 (Final Comprehensive Edition)
**创建日期:** 2026-02-06
**最后更新:** 2026-02-06
**核心理念:** **白盒化 (White-box)**、**严谨性 (Rigor)**、**透明交付 (Transparency)**
**架构特征:** 同步 HTTP 微服务 \+ RAG 工具检索 \+ 统计护栏 \+ 人机回环 (HITL) \+ 代码交付
## **1\. 执行摘要 (Executive Summary)**
### **1.1 项目背景**
壹证循科技拥有近百种经久考验的 R 语言统计工具ST模块这是我们的核心资产。随着 AI Agent 技术的发展,我们致力于将这些“静态工具”升级为“智能统计伙伴 (SSA)”,以降低临床医生的科研门槛。
### **1.2 核心决策演进**
经过多轮技术选型与研报论证,我们的架构经历了三次关键迭代:
1. **SSA-Lite (V1)**: 追求极致速度,采用同步 HTTP API摒弃了重型的异步队列架构针对 \<2MB 小数据场景)。
2. **SSA-Pro (V2)**: 引入行业研报建议,增加 **RAG 工具检索**(解决百种工具调度难)和 **统计护栏**(防止滥用统计方法)。
3. **SSA-WhiteBox (V3/V4)**: 引入 **人机回环 (HITL)****代码交付**,将“黑盒 AI”转变为“透明导师”解决医疗场景下的信任与合规问题。
### **1.3 最终方案价值主张**
SSA-Pro V4 不是一个简单的“代码生成器”,而是一个\*\*“懂统计、守规矩、乐于分享”的智能分析师\*\*
* **懂统计**:先写计划 (SAP),再做分析,就像真正的统计师一样。
* **守规矩**:强制执行正态性、方差齐性等前置检查,不满足条件自动警告。
* **乐于分享**:不仅给结果,还给**可复现的 R 代码**,支持医生本地二次运行。
## **2\. 为什么选择这套架构?(Architecture Rationale)**
在确定技术路线前,我们对三种主流方案进行了深度对比:
| 维度 | 方案 A: 异步队列 (pg-boss) | 方案 B: 纯 LLM 代码生成 (Open Interpreter) | 方案 C: SSA-Pro V4 (本方案) |
| :---- | :---- | :---- | :---- |
| **核心机制** | 提交任务 \-\> 排队 \-\> 轮询结果 | LLM 现场写 Python/R 代码 \-\> 沙箱执行 | **LLM 调参 \-\> R 标准库执行** |
| **交互体验** | 🐢 慢 (异步断裂感) | 🚀 快 (但不可控) | **⚡ 极速 (同步对话感)** |
| **准确性** | ✅ 高 (使用固定脚本) | ❌ 低 (容易幻觉、语法错误) | **✅ 极高 (护栏 \+ 预验证)** |
| **数据隐私** | 🟡 数据需落地 OSS | 🔴 数据需传给 LLM (高风险) | **🟢 数据/认知分离 (最高级)** |
| **落地难度** | 🔴 重 (需维护队列/状态) | 🟡 中 (需维护复杂沙箱) | **🟢 中 (需封装 R 接口)** |
**决策结论:**
针对医疗科研中 90% 的 **探索性数据分析 (EDA)** 场景(数据量 \< 2MB**SSA-Pro V4 (同步微服务 \+ 工具调用模式)** 是平衡体验、安全与成本的最优解。
## **3\. 系统架构蓝图 (System Blueprint)**
本架构严格遵循 **"Brain-Hand"** 分离原则,并增加了 **"User Check"** 环节。
graph TD
User\[用户 (React Chat)\] \--\>|1. 上传 Data Schema (无敏感数据)| Node\[Node.js 后端 (Brain)\]
User \--\>|1b. 上传真实 CSV (仅用于执行)| R\_Service\[R Plumber 服务 (Hand)\]
subgraph "Phase 1: 认知与规划 (Cognitive Layer)"
Node \--\>|2. 意图识别| LLM\_Plan\[DeepSeek-V3 (Planner)\]
Node \--\>|3. 语义检索 (pgvector)| VectorDB\[(Tools Knowledge Base)\]
VectorDB \--\>|4. 返回 Top-5 工具定义| Node
Node \--\>|5. 生成 SAP & 推荐参数| LLM\_Plan
Node \--\>|6. 返回 \[分析预习卡片\]| User
end
subgraph "Phase 2: 确认与执行 (Execution Layer)"
User \--\>|7. 点击 \[确认执行\]| Node
Node \--\>|8. HTTP POST /run (Params Only)| R\_Service
R\_Service \--\>|9. 路由分发| Wrapper\[工具适配层\]
subgraph "R Runtime (Network Isolated)"
Wrapper \--\>|10. 记录执行路径 (Trace)| TraceLog
Wrapper \--\>|11. 统计护栏 (Guardrails)| CoreTools
CoreTools \--\>|12. 核心计算| CoreTools
CoreTools \--\>|13. 生成复现代码| CodeGen
end
Wrapper \--\>|14. 结果包 (Result+Trace+Code)| R\_Service
end
subgraph "Phase 3: 反思与交付 (Reflection Layer)"
R\_Service \--\>|15. 原始结果| Node
Node \--\>|16. 结果解释 (Conservative)| LLM\_Critic\[DeepSeek-V3\]
LLM\_Critic \--\>|17. 最终报告 \+ R代码下载| User
end
## **4\. 核心业务流程详解**
### **4.1 步骤一:智能规划 (The Planner)**
**解决痛点:** 100 个工具怎么选?
* **动作**
1. 用户输入自然语言需求。
2. 系统提取关键词,在 tools\_library 向量库中检索 Top-5 相关工具。
3. LLM 基于检索结果,生成 **统计分析计划 (SAP)**
* **产出**:不仅是选工具,而是生成一份严谨的计划书(含分析目标、方法选择理由、假设验证条件)。
### **4.2 步骤二:人机回环确认 (HITL Confirmation)**
**解决痛点:** AI 瞎跑,用户不敢信。
* **动作**:在执行代码前,系统弹出一个 **“分析预习卡片”**。
* **卡片内容****📊 分析方案预览:独立样本 T 检验**
* **目标**:比较吸烟组与非吸烟组的 BMI 差异
* **参数**Group=Smoke, Value=BMI
* **前置检查**:将自动执行 Shapiro-Wilk 正态性检验。
* **降级策略**:若正态性不满足,自动切换为 Wilcoxon 检验。
\[ 🛠️ 修改参数 \] **\[ ✅ 确认并执行 \]**
### **4.3 步骤三:透明化执行与护栏 (Transparent Execution)**
**解决痛点:** 统计滥用 (P-hacking)。
* **动作**
1. **统计护栏 (Guardrails)**R 代码强制执行前置检查(如正态性、方差齐性)。如果检查失败,代码会自动记录日志并可能切换方法(降级)。
2. **路径可视化 (Trace)**:前端展示执行树。
├─ 🔍 正态性检验: P=0.23 (通过)
├─ 🔍 方差齐性检验: P=0.45 (通过)
└─ 🚀 执行 T 检验: t=2.5, P=0.012
### **4.4 步骤四:代码交付与解释 (Handover & Critic)**
**解决痛点:** 结果不可复现P 值过度解读。
* **动作**
1. **代码生成**R Wrapper 根据本次执行的参数,拼接出一份**可独立运行**的 .R 脚本,供用户下载。
2. **保守解释**Critic Agent (DeepSeek) 将结果翻译成人话,严禁使用“证明了”等绝对词汇,强调置信区间。
## **5\. 关键技术实现细节**
### **5.1 元数据工程 (Metadata Engineering)**
这是连接 R 和 AI 的桥梁。我们不手动写 JSON而是从 R 代码注释中自动提取。
**R 代码规范 (roxygen2 风格):**
\#' @title 执行独立样本T检验
\#' @description 比较两组独立正态分布数据的均值差异。
\#' @usage\_context 当因变量为连续变量,且在两组间独立时使用。
\#' @param data 数据框
\#' @param group\_col 分组列名 (Character)
\#' @param val\_col 数值列名 (Numeric)
\#' @guardrails check\_normality=TRUE, check\_variance=TRUE
st\_t\_test\_ind \<- function(data, group\_col, val\_col) { ... }
* **Pipeline**:编写脚本扫描 R 目录 \-\> 提取注释 \-\> 生成 tools\_library.json \-\> 存入 pgvector。
### **5.2 R 服务接口设计**
所有工具通过**统一入口**调用,简化后端逻辑。
* **API**: POST /api/v1/ssa/execute
* **Request Payload**:
{
"tool\_code": "ST\_T\_TEST\_IND",
"data": \[ ...rows... \],
"params": { "group\_col": "group", "val\_col": "bmi" },
"options": { "return\_code": true }
}
* **Response Payload**:
{
"status": "success",
"results": { "p\_value": 0.012, "conf\_int": \[0.5, 2.1\] },
"plots": \["base64\_string..."\],
"trace\_log": \["Checking normality... Passed."\],
"reproducible\_code": "data \<- read.csv('data.csv'); t.test(data$bmi \~ data$group)..."
}
### **5.3 安全与隐私设计**
1. **数据本地化 (Data Residency)**
* **原则**LLM (Brain) 永远只看 **Schema (列名)**,不看 **Data (行内容)**
* **流程**:前端提取 Schema \-\> 发给 LLM 规划 \-\> LLM 返回参数 \-\> 前端将 参数+Data 发给 R 服务。
* **结果**:敏感的患者数据只在内网 R 服务中流转,从未触达公网 LLM。
2. **网络隔离**
* SAE 部署 R 容器时,配置 **Egress Policy (出站策略)** 为 Deny All。防止 R 脚本恶意上传数据。
---
## **6\. 实施路线图 (Roadmap)**
### **Phase 1: 地基建设 (2周)**
* **目标**:跑通链路,实现 T 检验的自动化。
* **任务**
1. **Metadata Pipeline**:编写 R 注释提取脚本。
2. **R Wrapper v1**:实现支持 Trace 和 CodeGen 的通用 Wrapper。
3. **R Docker**:集成 Plumber 和 10 个核心工具。
### **Phase 2: 智能体与交互 (3周)**
* **目标**:实现“对话 \-\> 确认 \-\> 执行”闭环。
* **任务**
1. **Planner Agent**:接入 RAG 检索,优化 SAP Prompt。
2. **前端组件**:开发 `ConfirmationCard` (确认卡片) 和 `ExecutionTrace` (执行路径图)。
3. **统计护栏**:在 T 检验中实装正态性检查。
### **Phase 3: 全量迁移与打磨 (持续)**
* **目标**:覆盖 100+ 工具。
* **任务**
1. 批量为旧 R 脚本添加 roxygen2 注释。
2. 全量更新向量库。
3. 收集用户反馈,微调护栏阈值。
---
## **7\. 总结**
**SSA-Pro V4.0** 是一套\*\*“为了医疗科研而生”\*\*的架构。
它没有盲目追求“全自动写代码”的噱头,而是选择了\*\*“同步微服务 \+ 统计护栏 \+ 白盒交付”\*\*这条最难走但最稳健的路。
* **对于用户**:它像一位耐心的导师,教你方法,帮你检查,最后把代码送给你。
* **对于开发团队**:它复用了现有的 R 资产,工程边界清晰。
* **对于平台**:它建立了极高的数据隐私壁垒和学术严谨性壁垒。
**这是目前行业内最务实、最可落地的智能统计解决方案。**

View File

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

View File

@@ -0,0 +1,167 @@
# **SSA-02: 数据库与向量库 Schema 设计规范**
**文档状态:** v1.0 (Draft)
**创建日期:** 2026-02-06
**关联模块:** SSA, Common RAG
**目标:** 定义存储 R 工具元数据、向量索引及执行审计日志的数据库结构。
## **1\. 设计原则**
### **1.1 复用平台能力**
* **向量引擎**:严格使用 pgvector (0.8.1),向量维度固定为 **1024** (对应 text-embedding-v4)。
* **全文检索**:使用 pg\_bigm (1.2) 实现中英文混合的高性能关键词检索。
* **Schema 隔离**:所有表必须位于 ssa\_schema 命名空间下。
### **1.2 "工具即文档" (Tool-as-Document)**
与 PKB 模块将长文切分为 Chunk 不同SSA 模块中**一个 R 工具就是一个原子检索单位**。我们不进行切分,而是进行**特征合成**。
## **2\. 核心表结构设计 (DDL)**
### **2.1 工具库表 (tools\_library) —— 核心资产**
这张表存储了 100+ 个 R 工具的定义、参数结构以及用于检索的向量数据。
\-- 确保扩展已启用 (通常在 platform\_schema 或 public)
\-- CREATE EXTENSION IF NOT EXISTS vector;
\-- CREATE EXTENSION IF NOT EXISTS pg\_bigm;
CREATE TABLE ssa\_schema.tools\_library (
id UUID PRIMARY KEY DEFAULT gen\_random\_uuid(),
\-- 1\. 业务标识
tool\_code VARCHAR(50) NOT NULL UNIQUE, \-- 例如: 'ST\_T\_TEST\_IND'
name VARCHAR(100) NOT NULL, \-- 例如: '独立样本 T 检验'
version VARCHAR(20) DEFAULT '1.0.0', \-- R 脚本版本
\-- 2\. 结构化元数据 (用于 LLM 生成参数)
description TEXT NOT NULL, \-- 简短描述
usage\_context TEXT, \-- 适用场景 (什么时候用?)
params\_schema JSONB NOT NULL, \-- 参数定义 (JSON Schema)
guardrails JSONB, \-- 护栏配置 (支持哪些检查?)
\-- 3\. 检索专用字段 (Search Optimization)
\-- 将 name \+ description \+ usage\_context \+ keywords 拼接
search\_text TEXT NOT NULL,
\-- 4\. 向量索引 (1024维)
embedding vector(1024),
\-- 5\. 审计字段
created\_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated\_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
is\_active BOOLEAN DEFAULT TRUE
);
\-- 索引策略 (Hybrid Search Ready)
\-- 1\. 向量索引 (IVFFlat 或 HNSW) \- 用于语义检索
CREATE INDEX idx\_ssa\_tools\_embedding ON ssa\_schema.tools\_library
USING hnsw (embedding vector\_cosine\_ops);
\-- 2\. 全文索引 (pg\_bigm) \- 用于关键词精确匹配 (如 "T检验", "ANOVA")
CREATE INDEX idx\_ssa\_tools\_search\_text\_bigm ON ssa\_schema.tools\_library
USING gin (search\_text gin\_bigm\_ops);
\-- 3\. 业务索引
CREATE INDEX idx\_ssa\_tools\_code ON ssa\_schema.tools\_library(tool\_code);
### **2.2 执行审计日志表 (execution\_logs) —— 优化之源**
这张表是 SSA-Pro **"白盒化"** 的证据。它完整记录了从用户提问到最终代码交付的全过程。
CREATE TABLE ssa\_schema.execution\_logs (
id UUID PRIMARY KEY DEFAULT gen\_random\_uuid(),
\-- 1\. 归属信息
user\_id VARCHAR(255) NOT NULL,
session\_id VARCHAR(255), \-- 关联的会话 ID
\-- 2\. 输入 (User Intent)
user\_query TEXT NOT NULL, \-- 用户原始提问
data\_summary JSONB, \-- 数据摘要 (Schema, 非原始数据)
\-- 3\. 规划 (AI Plan)
sap\_content TEXT, \-- AI 生成的统计分析计划 (Markdown)
selected\_tool VARCHAR(50), \-- 最终选择的 tool\_code
generated\_params JSONB, \-- AI 生成的参数
\-- 4\. 执行 (R Runtime)
execution\_status VARCHAR(20), \-- 'success', 'error', 'warning'
trace\_log JSONB, \-- R 返回的执行路径日志
error\_message TEXT, \-- 如果失败,记录报错
\-- 5\. 性能指标
duration\_ms INTEGER, \-- 总耗时
tokens\_used INTEGER, \-- LLM 消耗 Token
created\_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
\-- 索引
CREATE INDEX idx\_ssa\_logs\_user ON ssa\_schema.execution\_logs(user\_id);
CREATE INDEX idx\_ssa\_logs\_tool ON ssa\_schema.execution\_logs(selected\_tool);
CREATE INDEX idx\_ssa\_logs\_time ON ssa\_schema.execution\_logs(created\_at DESC);
## **3\. 向量检索策略 (Retrieval Strategy)**
### **3.1 检索字段合成策略 (The "Synthetic Document")**
为了保持高准确率,我们不能只 Embedding 描述字段。我们需要构造一个**富含语义的虚拟文档**。
**search\_text 字段的内容模板:**
工具名称: {name}
功能描述: {description}
适用场景: {usage\_context}
关键词: {keywords} (如: 差异, 比较, 连续变量, 正态分布)
模拟提问: {synthetic\_queries} (如: "怎么比较两组数据的均值?", "两组病人的血糖有区别吗?")
**策略说明:**
* **模拟提问 (Synthetic Queries)**:这是一个高级技巧。在录入工具时,利用 LLM 生成 3-5 个“用户可能会问的问题”,并入索引。这能极大缩短“用户口语”与“专业术语”之间的语义距离。
### **3.2 混合检索流程 (Hybrid Search Workflow)**
复用平台的 VectorSearchService采用 **Brain-Hand** 模式。
1. **查询理解 (Brain Layer)**
* 用户输入:"看看这两组病人的 BMI 也没有差异"
* Query Rewriter (DeepSeek):生成关键词 \["独立样本T检验", "两组差异", "连续变量"\]。
2. **双路召回 (Engine Layer)**
* **路 A (Semantic)**:使用 embedding 字段进行向量相似度搜索 (Top 10)。
* **路 B (Keyword)**:使用 search\_text 字段进行 pg\_bigm 模糊匹配 (Top 10)。
3. **融合与重排 (Rerank)**
* 使用 Reciprocal Rank Fusion (RRF) 合并两路结果。
* 使用 qwen3-rerank 模型,基于用户的 Data Schema (例如:数据是分类的还是数值的) 对候选工具进行重排。
* **截断**:取 Top 5 返回给 Planner。
## **4\. 数据流与写入流程**
### **4.1 工具注册流程 (DevOps Pipeline)**
1. **R 工程师**:在 R 脚本中编写 roxygen2 注释。
2. **CI/CD 脚本**
* 解析 R 脚本,提取元数据。
* 调用 LLM 生成 synthetic\_queries (模拟提问)。
* 拼接 search\_text。
* 调用 EmbeddingService 生成向量。
* Upsert 到 ssa\_schema.tools\_library 表。
### **4.2 运行时流程**
1. **Node.js** 接收用户请求。
2. 调用 ssa\_schema.tools\_library 进行混合检索。
3. 将命中的工具 params\_schema 注入到 Planner 的 Prompt 中。
4. 执行结束后,将全过程写入 ssa\_schema.execution\_logs。
## **5\. 总结**
这套设计方案充分利用了我们现有的 **Postgres-Only** 架构优势:
1. **不需要额外的向量数据库**:直接用 RDS PostgreSQL维护成本最低。
2. **高准确率保证**:通过 search\_text 的精心构造(特别是加入模拟提问)和 pg\_bigm 的关键词辅助,解决了“工具选错”的最大痛点。
3. **可追溯**:详尽的审计日志为未来的“自我进化”(根据用户反馈优化工具推荐)提供了数据基础。

View File

@@ -0,0 +1,200 @@
# **SSA-03: Agent Prompt 与编排流程设计规范**
**文档状态:** v1.0 (Draft)
**创建日期:** 2026-02-06
**关联模块:** SSA, AIA (LLM Gateway)
**目标:** 定义 SSA 模块的认知层逻辑,包括 Prompt 模板、上下文组装策略及错误自愈流程。
## **1\. 认知层架构总览**
SSA 的认知层不是一个简单的 Chatbot而是一个 **"Retrieve-Plan-Execute-Critic"** 的流水线。
### **核心智能节点 (AI Nodes)**
| 节点名称 | 对应模型 | 职责 | 输入 | 输出 |
| :---- | :---- | :---- | :---- | :---- |
| **Query Rewriter** | DeepSeek-V3 | **查询翻译与扩展**。将用户口语转化为统计学术语,用于向量检索。 | 用户提问 | 关键词列表 (JSON) |
| **SSA Planner** | DeepSeek-V3 | **分析规划**。阅读候选工具文档,生成执行参数。 | 提问 \+ 数据元数据 \+ Top-5工具 | SAP 计划书 (JSON) |
| **SSA Critic** | DeepSeek-V3 | **结果解释**。基于统计原则解读 R 输出,警示风险。 | R 执行结果 \+ 路径日志 | Markdown 报告 |
| **Synthetic Gen** | Qwen-Max | **离线数据增强**。为工具生成模拟提问,用于建库。 | 工具描述 | 模拟问答对 (List) |
## **2\. 编排流程逻辑 (Orchestration Logic)**
Node.js 后端将按照以下伪代码逻辑调度各个节点:
async function runAnalysisFlow(userQuery, dataSchema) {
// 1\. \[Rewriter\] 查询理解
const keywords \= await rewriter.extractKeywords(userQuery);
// 2\. \[RAG\] 检索候选工具 (混合检索)
const candidateTools \= await vectorDB.searchTools(keywords, { limit: 5 });
// 3\. \[Planner\] 生成计划
const sap \= await planner.generatePlan(userQuery, dataSchema, candidateTools);
// 4\. \[Frontend\] 用户确认 (HITL)
// ... 等待用户点击 "确认执行" ...
// 5\. \[Executor\] 调用 R 服务
// 此时带有用户确认过的 params
let rResult \= await rService.execute(sap.tool\_code, sap.params);
// 6\. \[Self-Healing\] 简单自愈 (可选)
if (rResult.status \=== 'error' && rResult.is\_fixable) {
const fixedParams \= await planner.fixParams(rResult.error, sap.params);
rResult \= await rService.execute(sap.tool\_code, fixedParams);
}
// 7\. \[Critic\] 生成报告
const finalReport \= await critic.interpret(userQuery, rResult);
return finalReport;
}
## **3\. 详细 Prompt 设计**
### **3.1 Query Rewriter (查询重写器)**
* **目标**解决“用户搜看差异库里存T检验”的语义鸿沟。
* **Model**DeepSeek-V3 (Temperature: 0.3)
**System Prompt:**
你是一个精通统计学的检索助手。你的任务是将用户的自然语言需求转化为精准的检索关键词。
输入规则:
1\. 提取核心统计意图(如:差异、相关、预测、降维)。
2\. 识别变量类型(如:连续变量、分类变量)。
3\. 生成 3-5 个同义专业术语(如:用户说"看两个组不一样",你生成 "独立样本T检验", "差异性分析", "Wilcoxon检验")。
输出格式JSON 字符串列表
示例输入:"我想看看吸烟和不吸烟的人,肺活量有没有区别"
示例输出:\["独立样本T检验", "两组均值比较", "差异分析", "连续变量", "二分类自变量"\]
### **3.2 SSA Planner (核心规划师) ⭐ 最关键**
* **目标**:精准选择工具并填对参数。
* **Model**DeepSeek-V3 (Temperature: 0.1)
* **Context 组装**:动态插入 candidate\_tools 的 JSON Schema。
**System Prompt Template:**
你是一名资深的生物统计学家。你面前有一份数据摘要Metadata和一组可用的统计工具箱。
请根据用户的需求选择最合适的一个工具并生成详细的执行计划SAP
\#\#\# 数据摘要
{{data\_schema\_json}}
\#\#\# 可用工具箱 (Candidates)
{{candidate\_tools\_json}}
\#\#\# 决策规则 (Guardrails)
1\. \*\*类型匹配\*\*:严格检查变量类型。不要把分类变量(如 "男/女"填入要求数值型Numeric的参数中。
2\. \*\*工具匹配\*\*:如果用户要做 "预测",优先选 "回归" 类工具;如果做 "差异",选 "检验" 类工具。
3\. \*\*护栏配置\*\*:对于 T 检验、ANOVA 等参数检验,必须开启 \`check\_normality\`(正态性检查)。
\#\#\# 输出要求
请先在 \<thinking\> 标签中进行推理,分析变量类型和工具适用性。
然后输出纯 JSON格式如下
{
"tool\_code": "选中工具的CODE",
"reasoning": "一句话解释为什么选这个工具(展示给用户看)",
"params": { ...根据工具定义的 params\_schema 填写... },
"guardrails": { "check\_normality": true, "auto\_fix": true }
}
**User Input Example:**
"比较性别Gender对血压SBP的影响。"
**Model Output Example:**
\<thinking\>
用户想要比较 "Gender" (分类变量通常2组) 对 "SBP" (数值变量) 的影响。
候选工具中有ST\_T\_TEST\_IND (独立样本T检验) 和 ST\_CORRELATION (相关性)。
T检验适用于 分类(2组) vs 数值。相关性适用于 数值 vs 数值。
因此选择 ST\_T\_TEST\_IND。
参数映射group\_col="Gender", val\_col="SBP"。
需要开启正态性检查。
\</thinking\>
{
"tool\_code": "ST\_T\_TEST\_IND",
"reasoning": "目标变量'SBP'为数值型,分组变量'Gender'为二分类,适合使用独立样本 T 检验比较均值差异。",
"params": { "group\_col": "Gender", "val\_col": "SBP" },
"guardrails": { "check\_normality": true, "auto\_fix": true }
}
### **3.3 SSA Critic (结果审查员)**
* **目标**:将 R 的输出翻译成人话,并遏制学术不端表述。
* **Model**DeepSeek-V3 (Temperature: 0.5)
**System Prompt:**
你是一名严谨的期刊审稿人。请根据 R 语言的统计结果,为用户撰写一段简短的分析结论。
\#\#\# 输入信息
1\. 用户问题:{{user\_query}}
2\. 统计方法:{{method\_name}}
3\. 执行结果:{{r\_result\_json}}
4\. 执行路径日志:{{trace\_log}}
\#\#\# 撰写原则 (Strict Rules)
1\. \*\*拒绝绝对化\*\*:严禁使用 "证明了" (proved)、"确信" 等词。请使用 "差异具有统计学意义" (statistically significant) 或 "未发现显著差异"。
2\. \*\*关注前提\*\*:如果 trace\_log 显示 "正态性检验失败,切换为非参数检验",必须在报告中指出这一点。
3\. \*\*完整性\*\*:不仅要报告 P 值,还要报告统计量(如 t值、F值和置信区间如果有
4\. \*\*业务关联\*\*:尝试结合用户问题中的变量名进行解释,而不是只说 "X 和 Y"。
\#\#\# 输出格式
Markdown 段落。
## **4\. 离线数据增强 (Offline Synthetic Generation)**
为了让 RAG 检索更准,我们需要在工具入库时,用 AI 生成“模拟用户提问”。这是 **"SSA-02 数据库设计"** 中 search\_text 字段的数据来源。
**Worker Prompt (Qwen-Max):**
我有一个 R 语言统计工具,定义如下:
名称:{{tool\_name}}
描述:{{tool\_desc}}
参数:{{params\_schema}}
请模拟 5 个临床医生可能会问的自然语言问题,这些问题应该触发这个工具的使用。
问题要包含:
1\. 极其口语化的表达(如 "看看这两组有没有区别")。
2\. 带有具体医学场景的表达(如 "比较吃药和不吃药的血糖差异")。
3\. 包含统计术语的表达(如 "做个T检验")。
输出格式:纯文本,每行一个问题。
## **5\. 错误自愈流程 (Self-Healing Flow)**
当 R 服务返回 status: "error" 时Node.js 不会直接把报错甩给用户,而是尝试一次自愈。
**Trigger Condition**: R 返回错误,且错误信息包含 ParameterError 或 ColumnNotFound。
**Fixer Prompt:**
你生成的参数导致 R 代码运行报错。请修正参数。
\#\#\# 原始计划
工具:{{tool\_code}}
参数:{{original\_params}}
\#\#\# 报错信息
{{error\_message}} (例如Error: Column 'age' not found in data)
\#\#\# 数据摘要
{{data\_schema}}
请输出修正后的 params JSON。
## **6\. 开发建议**
1. **Prompt 版本管理**:所有的 Prompt 必须存入 capability\_schema.prompt\_templates 表(复用 ADMIN 模块的能力),不要硬编码在代码里。这样运营人员可以在后台动态调整 Prompt。
2. **Context Window 优化**candidate\_tools 的 JSON Schema 可能会很长。如果超出 Token 限制,仅保留 tool\_code, name, desc 和 params\_schema去除无关字段。
3. **流式输出**Critic 生成报告时,建议使用 SSE 流式输出,减少用户等待的焦虑感。

View File

@@ -0,0 +1,130 @@
# **SSA-Pro: 智能统计分析项目开发管理与实施计划**
**文档版本:** v1.0
**创建日期:** 2026-02-07
**项目代号:** SSA (Smart Statistical Analysis)
**技术架构:** SSA-Pro V4.0 (Sync API \+ Guardrails \+ White-box)
**产品目标:** 打造医疗科研领域最严谨、透明、可交付代码的智能统计助手。
## **1\. 总体里程碑规划 (Milestones Map)**
我们将项目划分为三个阶段,采取 **"走通链路 \-\> 完善体验 \-\> 批量量产"** 的策略。
| 阶段 | 周期 | 核心目标 (OKR) | 交付物 |
| :---- | :---- | :---- | :---- |
| **Phase 1: 骨架与地基** | **Week 1-2** | **跑通 "Hello World"**。 打通 Node \-\> R 的调用链路,完成数据库与 Docker 环境搭建。 | ✅ R Docker 基础镜像 ✅ 核心数据库表 (tools\_library) ✅ 第 1 个工具 (T检验) 跑通 API |
| **Phase 2: 智能与交互 MVP** | **Week 3-5** | **实现 "V2 原型体验"**。 完成 RAG 检索、AI 规划、前端交互卡片,集成 Top 10 核心工具。 | ✅ 完整 Chat 交互界面 ✅ 智能规划 (Planner) 上线 ✅ 10 个高频统计工具可用 |
| **Phase 3: 量产与交付** | **Week 6-8** | **覆盖 80% 场景**。 批量迁移剩余工具,完善结果解释 (Critic) 与代码下载功能。 | ✅ 50+ 工具上线 ✅ 代码下载/Word导出功能 ✅ 生产环境部署与压测 |
## **2\. 详细任务清单 (To-Do List by Role)**
### **🏁 Phase 1: 骨架与地基 (Week 1-2)**
**目标:** 不追求 UI 美观,只追求 API 通畅。实现 POST /execute 能返回 T 检验的 JSON 结果。
#### **🐹 后端工程 (Node.js)**
* \[ \] **DB 设计**:根据 SSA-02 创建 ssa\_schema 及 tools\_library, execution\_logs 表。
* \[ \] **API 存根**:实现 POST /api/v1/ssa/execute 接口目前仅做转发Proxy到 R 服务。
* \[ \] **RAG 基础**:复用 common/rag创建 ToolSearchService支持对工具描述的向量检索。
#### **📐 R 工程 (Statistics)**
* \[ \] **环境构建**:编写 Dockerfile基于 rocker/r-ver安装 plumber, jsonlite, ggplot2 等基础包。
* \[ \] **Wrapper 规范**:根据 SSA-01编写标准 wrapper\_template.R含输入解析、错误捕获、Base64输出
* \[ \] **首个工具**:迁移并封装 ST\_T\_TEST\_IND (独立样本 T 检验),实现正态性检查逻辑。
* \[ \] **服务启动**:编写 plumber.R 入口文件,挂载 /run 端点。
#### **🎨 前端工程 (React)**
* \[ \] **Mock 数据**:根据原型图,构造 PlanCard 和 ResultCard 的 Mock JSON 数据。
* \[ \] **组件开发**:开发基础的 ChatContainer复用 AIA 模块)和侧边栏框架。
### **🚀 Phase 2: 智能与交互 MVP (Week 3-5)**
**目标:** 用户可以上传数据AI 能听懂并规划,前端能展示“执行树”和“三线表”。
#### **🧠 AI 策略 (Prompt Engineering)**
* \[ \] **Query Rewriter**:调试 Prompt将用户口语"看差异")转化为术语("T检验")。
* \[ \] **Planner Agent**:根据 SSA-03调试 DeepSeek-V3 的 Prompt确保稳定输出符合 Schema 的 JSON 计划。
* \[ \] **元数据注入**:实现动态 Context 组装逻辑(只注入 Top-5 工具定义)。
#### **📐 R 工程 (Statistics)**
* \[ \] **Top 10 工具迁移**完成卡方、ANOVA、相关性、Mann-Whitney、Wilcoxon 等高频工具的封装。
* \[ \] **Trace Log 实现**:在 R 代码中埋点,记录 check\_normality, check\_variance 等步骤的状态。
* \[ \] **代码生成器**:实现 generate\_code() 函数,拼接可独立运行的 R 脚本字符串。
#### **🎨 前端工程 (React)**
* \[ \] **卡片组件**:开发 ConfirmationCard (确认卡片) 和 ExecutionTree (执行路径树,支持动画)。
* \[ \] **结果渲染**:开发 ResultCard支持 Tab 切换 (解读/图表/表格)。
* \[ \] **数据上传**:实现侧边栏文件上传,调用 DC 模块解析 Schema不传数据给 LLM
#### **🐹 后端工程 (Node.js)**
* \[ \] **编排逻辑**:实现 Retrieve \-\> Plan \-\> Wait \-\> Execute 的完整业务流。
* \[ \] **数据隐私**:确保传给 LLM 的只有 Schema传给 R 的才有 Data。
### **📦 Phase 3: 量产与交付 (Week 6-8)**
**目标:** 工具库丰富,报告专业,具备生产环境稳定性。
#### **📐 R 工程 (Statistics)**
* \[ \] **批量工厂**:编写脚本扫描旧 R 代码注释,自动生成 tools\_library.json 元数据Metadata Engineering
* \[ \] **长尾工具迁移**:按优先级迁移剩余工具(回归、生存分析、降维等)。
* \[ \] **错误处理**:增强 tryCatch对常见统计错误返回友好的 Message。
#### **🧠 AI 策略 (Critic)**
* \[ \] **Critic Agent**:调试“结果解释” Prompt确保输出严谨的学术结论P值解读、置信区间
* \[ \] **模拟提问生成**:使用 Qwen-Max 离线生成 500+ 条模拟用户提问,更新向量库,提升检索准确率。
#### **🎨 前端工程 (React)**
* \[ \] **三线表组件**:优化表格样式,符合 APA 格式。
* \[ \] **资产交付**:实现 R 代码下载(.zip和 Word 报告导出(调用 Pandoc 服务)。
* \[ \] **图表预览**:集成 react-photo-view 实现图表点击放大。
#### **🛡️ 运维与测试**
* \[ \] **网络隔离**:配置 SAE 网络策略,禁止 R 容器访问外网。
* \[ \] **并发测试**:测试 10 个并发请求下的 R 服务内存占用。
* \[ \] **端到端测试**:验证 20 个典型统计场景的准确性。
## **3\. 关键依赖检查 (Dependencies)**
在项目启动前,请确认以下资源已就绪:
1. **基础设施**
* \[x\] SAE 环境 (Node.js \+ Python 已就绪,需新增 R 应用)
* \[x\] RDS PostgreSQL (pgvector 插件已安装)
* \[x\] OSS 存储 (用于存临时图表或大文件)
2. **技术资产**
* \[x\] DC 模块 (Tool C):用于前置数据清洗和 Excel 解析。
* \[x\] 通用 Chat 组件AIA 模块已沉淀,可直接复用。
* \[x\] LLM 网关DeepSeek-V3 接口已调通。
## **4\. 风险管理 (Risk Management)**
| 风险点 | 概率 | 影响 | 应对策略 |
| :---- | :---- | :---- | :---- |
| **R 工具封装进度慢** | 高 | 阻塞上线 | 优先封装 Top 10编写代码生成脚本辅助封装。 |
| **LLM 规划参数错误** | 中 | 运行报错 | 在 R 入口处做强类型校验;前端卡片允许用户人工修正参数。 |
| **大文件导致 R 崩溃** | 中 | 服务不可用 | 严格限制上传文件 \< 2MBSAE 配置内存监控和自动重启。 |
| **统计结果解释幻觉** | 低 | 学术误导 | Critic Prompt 强制要求基于 R 输出的 JSON 说话,禁止脑补。 |
## **5\. 每日工作流建议 (Daily Workflow)**
建议采用 **"R 优先,前后端跟进"** 的模式:
1. **R 工程师**:每天封装 2-3 个工具,提交到代码库,并更新 tools\_metadata.json。
2. **后端工程师**:更新向量库数据,确保 LLM 能检索到新工具。
3. **前端工程师**:针对新工具的特殊参数(如生存分析的时间列),微调卡片展示逻辑。
4. **每日站会**:演示昨天封装的工具是否能跑通闭环。

View File

@@ -0,0 +1,478 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SSA-Pro 智能统计分析原型 V2.0</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
body { font-family: 'Inter', sans-serif; background-color: #f8fafc; }
/* 自定义滚动条 */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
/* 动画类 */
.fade-in { animation: fadeIn 0.4s ease-out forwards; opacity: 0; }
.slide-up { animation: slideUp 0.4s ease-out forwards; opacity: 0; transform: translateY(10px); }
.pulse-border { animation: pulseBorder 2s infinite; }
@keyframes fadeIn { to { opacity: 1; } }
@keyframes slideUp { to { transform: translateY(0); opacity: 1; } }
@keyframes pulseBorder { 0% { border-color: #3b82f6; box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4); } 70% { border-color: #3b82f6; box-shadow: 0 0 0 6px rgba(59, 130, 246, 0); } 100% { border-color: #3b82f6; box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); } }
/* 三线表样式 (APA Style) */
.apa-table { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }
.apa-table thead th { border-top: 2px solid #1e293b; border-bottom: 1px solid #1e293b; padding: 8px; text-align: left; font-weight: 600; }
.apa-table tbody td { padding: 8px; border-bottom: 1px solid #e2e8f0; }
.apa-table tbody tr:last-child td { border-bottom: 2px solid #1e293b; }
/* 树状结构连接线 */
.tree-line { position: absolute; left: 14px; top: 24px; bottom: -16px; width: 1px; background-color: #e2e8f0; }
.tree-item:last-child .tree-line { display: none; }
</style>
</head>
<body class="h-screen flex overflow-hidden text-slate-800">
<!-- 左侧侧边栏 -->
<aside class="w-64 bg-white border-r border-slate-200 flex flex-col z-20 flex-shrink-0 shadow-sm">
<!-- Logo -->
<div class="h-16 flex items-center px-5 border-b border-slate-100">
<div class="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center text-white mr-3 shadow-sm">
<i class="fa-solid fa-dna"></i>
</div>
<div>
<div class="font-bold text-slate-800 leading-none">SSA-Pro</div>
<div class="text-[10px] text-slate-400 mt-1">智能统计分析 V2.0</div>
</div>
</div>
<!-- Action Buttons (New) -->
<div class="p-4 space-y-3">
<button onclick="triggerUpload()" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2.5 rounded-lg text-sm font-medium transition flex items-center justify-center gap-2 shadow-md shadow-blue-100">
<i class="fa-solid fa-upload"></i> 导入数据 (Excel/CSV)
</button>
<button onclick="location.reload()" class="w-full bg-white border border-slate-200 text-slate-600 hover:bg-slate-50 py-2.5 rounded-lg text-sm font-medium transition flex items-center justify-center gap-2">
<i class="fa-solid fa-plus"></i> 新建会话
</button>
</div>
<!-- History -->
<div class="flex-1 overflow-y-auto px-3 py-2 space-y-1">
<div class="px-3 text-xs font-semibold text-slate-400 uppercase tracking-wider mb-2">最近分析</div>
<div class="group flex items-center gap-3 px-3 py-2 bg-blue-50 text-blue-700 rounded-md cursor-pointer border border-blue-100">
<i class="fa-regular fa-chart-bar text-blue-500"></i>
<div class="flex-1 truncate text-xs font-medium">血糖性别差异分析</div>
</div>
<div class="group flex items-center gap-3 px-3 py-2 hover:bg-slate-50 text-slate-600 rounded-md cursor-pointer transition">
<i class="fa-regular fa-clock text-slate-400"></i>
<div class="flex-1 truncate text-xs">肺癌生存期 Cox 回归</div>
</div>
</div>
<!-- Current Data Status -->
<div id="sidebar-data-status" class="p-4 border-t border-slate-100 bg-slate-50 transition-all duration-500 opacity-50 grayscale pointer-events-none">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2 text-xs font-bold text-slate-700">
<i class="fa-solid fa-database text-blue-500"></i> 当前数据集
</div>
<span class="w-2 h-2 bg-green-500 rounded-full animate-pulse"></span>
</div>
<div class="bg-white border border-slate-200 rounded p-2.5 shadow-sm">
<div class="flex items-center gap-2 mb-1">
<i class="fa-solid fa-file-csv text-green-600 text-lg"></i>
<span class="text-xs font-medium truncate text-slate-700">diabetes_data.csv</span>
</div>
<div class="text-[10px] text-slate-400 flex gap-3 mt-1">
<span>150 行</span>
<span>12 列</span>
<span>24KB</span>
</div>
</div>
</div>
</aside>
<!-- 主工作区 -->
<main class="flex-1 flex flex-col bg-[#f8fafc] relative">
<!-- Header -->
<header class="h-16 border-b border-slate-200 flex items-center justify-between px-8 bg-white shadow-sm z-10">
<h2 class="font-medium text-slate-700 flex items-center gap-2">
<span class="text-slate-400">会话 /</span> 血糖性别差异分析
</h2>
<div class="flex items-center gap-3">
<button class="w-8 h-8 rounded-full hover:bg-slate-100 text-slate-500 flex items-center justify-center transition"><i class="fa-regular fa-bell"></i></button>
<button class="w-8 h-8 rounded-full bg-slate-200 flex items-center justify-center text-slate-600 font-bold text-xs border-2 border-white shadow-sm">DR</button>
</div>
</header>
<!-- Chat Flow -->
<div id="chat-container" class="flex-1 overflow-y-auto p-8 space-y-8 scroll-smooth pb-32">
<!-- 1. System Welcome (Upload Guide) -->
<div class="flex gap-4 max-w-4xl mx-auto">
<div class="w-10 h-10 rounded-full bg-blue-600 flex items-center justify-center text-white flex-shrink-0 shadow-md">
<i class="fa-solid fa-robot"></i>
</div>
<div class="space-y-4 flex-1">
<div class="bg-white p-5 rounded-2xl rounded-tl-none shadow-sm border border-slate-100 max-w-2xl text-slate-600 text-sm leading-relaxed">
<p class="mb-3 font-medium text-slate-800">你好!我是 SSA 智能统计助手。</p>
<p>请先上传您的研究数据Excel 或 CSV我将为您自动解析数据结构并规划分析方案。</p>
<!-- Upload Area Inside Chat -->
<div id="upload-zone" onclick="triggerUpload()" class="mt-4 border-2 border-dashed border-blue-200 bg-blue-50/50 rounded-xl p-6 text-center cursor-pointer hover:border-blue-400 hover:bg-blue-50 transition group">
<div class="w-12 h-12 bg-white rounded-full flex items-center justify-center text-blue-500 mx-auto mb-3 shadow-sm group-hover:scale-110 transition">
<i class="fa-solid fa-cloud-arrow-up text-xl"></i>
</div>
<p class="text-blue-700 font-medium">点击上传数据文件</p>
<p class="text-xs text-slate-400 mt-1">支持 .xlsx, .csv (最大 20MB)</p>
</div>
<!-- Upload Progress (Hidden Initially) -->
<div id="upload-progress" class="hidden mt-4 bg-slate-50 rounded-xl p-4 border border-slate-200">
<div class="flex items-center gap-3 mb-2">
<i class="fa-solid fa-file-csv text-green-500 text-xl"></i>
<div class="flex-1">
<div class="flex justify-between text-xs mb-1">
<span class="font-medium text-slate-700">diabetes_data.csv</span>
<span class="text-blue-600">上传中...</span>
</div>
<div class="h-1.5 bg-slate-200 rounded-full overflow-hidden">
<div class="h-full bg-blue-500 rounded-full w-2/3 animate-pulse"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 2. System Confirmation (Hidden Initially) -->
<div id="data-loaded-msg" class="hidden flex gap-4 max-w-4xl mx-auto fade-in">
<div class="w-10 h-10 rounded-full bg-blue-600 flex items-center justify-center text-white flex-shrink-0 shadow-md">
<i class="fa-solid fa-robot"></i>
</div>
<div class="bg-white p-4 rounded-2xl rounded-tl-none shadow-sm border border-slate-100 max-w-2xl text-slate-600 text-sm">
<div class="flex items-center gap-2 text-green-600 font-medium mb-2">
<i class="fa-solid fa-circle-check"></i> 数据加载成功
</div>
<p>已解析 <strong>150 条记录</strong>,包含 Gender, Age, GLU, BMI 等 12 个变量。</p>
<p class="mt-2 text-slate-500">您可以直接告诉我您的分析目标,例如:<br>“分析性别对血糖的影响” 或 “做一下年龄和血压的相关性”。</p>
</div>
</div>
<!-- 3. User Intent (Hidden Initially) -->
<div id="user-msg" class="hidden flex gap-4 max-w-4xl mx-auto flex-row-reverse fade-in">
<div class="w-10 h-10 rounded-full bg-slate-200 flex items-center justify-center text-slate-500 flex-shrink-0">
<i class="fa-solid fa-user"></i>
</div>
<div class="bg-blue-600 px-5 py-3 rounded-2xl rounded-tr-none max-w-2xl text-white shadow-md text-sm">
比较不同性别组的血糖GLU是否有差异。
</div>
</div>
<!-- 4. Plan Card (Hidden Initially) -->
<div id="plan-card-container" class="hidden flex gap-4 max-w-4xl mx-auto fade-in">
<div class="w-10 h-10 rounded-full bg-blue-600 flex items-center justify-center text-white flex-shrink-0 shadow-md">
<i class="fa-solid fa-wand-magic-sparkles"></i>
</div>
<div class="flex-1 space-y-3 max-w-2xl">
<p class="text-sm text-slate-600 bg-white p-3 rounded-xl rounded-tl-none shadow-sm border border-slate-100 inline-block">
收到,我为您规划了以下分析方案,请确认:
</p>
<!-- Plan Card -->
<div class="border border-slate-200 bg-white rounded-xl shadow-sm overflow-hidden">
<div class="bg-slate-50 px-4 py-3 border-b border-slate-200 flex justify-between items-center">
<span class="font-semibold text-slate-800 text-sm"><i class="fa-solid fa-list-check text-blue-500 mr-2"></i>分析方案确认</span>
<span class="text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded font-medium">T-Test</span>
</div>
<div class="p-5 space-y-4">
<!-- Variables -->
<div class="flex gap-4">
<div class="flex-1 bg-slate-50 p-3 rounded border border-slate-100">
<div class="text-[10px] text-slate-400 uppercase font-bold mb-1">分组变量 (Group)</div>
<div class="text-sm font-medium text-slate-800">Gender <span class="text-xs text-slate-400 font-normal">(分类: Male/Female)</span></div>
</div>
<div class="flex-1 bg-slate-50 p-3 rounded border border-slate-100">
<div class="text-[10px] text-slate-400 uppercase font-bold mb-1">检验变量 (Value)</div>
<div class="text-sm font-medium text-slate-800">GLU <span class="text-xs text-slate-400 font-normal">(数值型)</span></div>
</div>
</div>
<!-- Guardrails -->
<div class="text-xs text-amber-700 bg-amber-50 p-3 rounded border border-amber-100">
<div class="font-bold mb-1"><i class="fa-solid fa-shield-halved mr-1"></i> 统计护栏 (自动执行)</div>
<ul class="list-disc list-inside space-y-0.5 opacity-80">
<li>Shapiro-Wilk 正态性检验</li>
<li>Levene 方差齐性检验</li>
</ul>
<div class="mt-2 pt-2 border-t border-amber-200 font-medium">
⚠️ 若正态性检验失败,将自动降级为 Wilcoxon 秩和检验。
</div>
</div>
</div>
<!-- Actions -->
<div class="px-5 py-3 border-t border-slate-100 flex justify-end gap-3 bg-slate-50">
<button class="text-xs text-slate-500 hover:text-slate-700 font-medium px-3 py-2 rounded hover:bg-slate-200 transition">修改参数</button>
<button onclick="executeAnalysis()" id="btn-execute" class="text-xs bg-blue-600 text-white px-4 py-2 rounded font-medium hover:bg-blue-700 transition shadow-sm flex items-center gap-2">
<i class="fa-solid fa-play"></i> 确认并执行
</button>
</div>
</div>
</div>
</div>
<!-- 5. Execution & Result (Dynamic) -->
<div id="execution-result-container" class="hidden flex-col gap-6 max-w-4xl mx-auto">
<!-- Trace -->
<div class="flex gap-4">
<div class="w-10 h-10 rounded-full bg-white border border-slate-200 flex items-center justify-center text-slate-400 flex-shrink-0 mt-1">
<i class="fa-solid fa-terminal text-sm"></i>
</div>
<div class="bg-white border border-slate-200 rounded-xl p-4 shadow-sm max-w-2xl w-full slide-up">
<div class="text-xs font-bold text-slate-400 uppercase mb-3 tracking-wider flex justify-between">
<span>执行路径</span>
<span class="text-green-600"><i class="fa-solid fa-check"></i> 完成</span>
</div>
<div class="space-y-3 font-mono text-xs relative pl-2">
<!-- Line -->
<div class="absolute left-[19px] top-2 bottom-4 w-px bg-slate-200"></div>
<!-- Items -->
<div class="flex items-center gap-3 relative z-10">
<div class="w-4 h-4 rounded-full bg-green-100 border border-green-300 flex items-center justify-center text-[8px] text-green-600"><i class="fa-solid fa-check"></i></div>
<span class="text-slate-600">加载数据 (n=150)</span>
</div>
<div class="flex items-start gap-3 relative z-10">
<div class="w-4 h-4 rounded-full bg-blue-100 border border-blue-300 flex items-center justify-center text-[8px] text-blue-600 mt-0.5"><i class="fa-solid fa-shield"></i></div>
<div>
<span class="text-slate-800 font-medium">正态性检验 (Shapiro-Wilk)</span>
<div class="mt-1">
<span class="bg-red-50 text-red-600 px-1.5 py-0.5 rounded border border-red-100 font-bold">P = 0.002 (< 0.05) </span>
<span class="ml-2 text-slate-400">-> 拒绝正态假设</span>
</div>
</div>
</div>
<div class="flex items-center gap-3 relative z-10">
<div class="w-4 h-4 rounded-full bg-amber-100 border border-amber-300 flex items-center justify-center text-[8px] text-amber-600"><i class="fa-solid fa-arrow-right-arrow-left"></i></div>
<span class="text-amber-700 font-medium">策略切换: T-Test -> Wilcoxon Test</span>
</div>
<div class="flex items-center gap-3 relative z-10">
<div class="w-4 h-4 rounded-full bg-indigo-100 border border-indigo-300 flex items-center justify-center text-[8px] text-indigo-600"><i class="fa-solid fa-calculator"></i></div>
<span class="text-slate-800 font-bold">计算完成</span>
</div>
</div>
</div>
</div>
<!-- Final Result Card (V2 Major Update) -->
<div class="flex gap-4 slide-up" style="animation-delay: 0.2s;">
<div class="w-10 h-10 rounded-full bg-gradient-to-br from-blue-600 to-indigo-600 flex items-center justify-center text-white flex-shrink-0 shadow-lg mt-1">
<i class="fa-solid fa-file-medical-alt"></i>
</div>
<div class="bg-white border border-slate-200 rounded-xl shadow-md overflow-hidden max-w-3xl w-full">
<!-- Header -->
<div class="bg-slate-50 px-6 py-4 border-b border-slate-200">
<h3 class="font-bold text-slate-800">分析结果报告</h3>
<p class="text-xs text-slate-500 mt-1">基于非参数检验 (Wilcoxon rank sum test)</p>
</div>
<!-- 1. Statistical Tables & Plots -->
<div class="p-6 border-b border-slate-100">
<div class="flex items-center gap-2 mb-4">
<div class="w-1 h-4 bg-blue-600 rounded-full"></div>
<h4 class="font-semibold text-slate-800 text-sm">表 1. 组间差异比较 (三线表)</h4>
</div>
<!-- APA Table -->
<div class="overflow-x-auto mb-6">
<table class="apa-table text-sm text-slate-700">
<thead>
<tr>
<th class="w-32">Group</th>
<th>N</th>
<th>GLU (Median [IQR])</th>
<th>Statistic (W)</th>
<th>P-Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>Male</td>
<td>78</td>
<td>5.80 [5.20 - 6.50]</td>
<td rowspan="2" class="align-middle border-l border-slate-100">1254.5</td>
<td rowspan="2" class="align-middle border-l border-slate-100 font-bold text-slate-900">0.002 **</td>
</tr>
<tr>
<td>Female</td>
<td>72</td>
<td>5.10 [4.80 - 5.50]</td>
</tr>
</tbody>
</table>
<p class="text-[10px] text-slate-400 mt-2 italic">Note: IQR = Interquartile Range; ** P < 0.01.</p>
</div>
<!-- Plot Area -->
<div class="flex items-center gap-2 mb-3">
<div class="w-1 h-4 bg-blue-600 rounded-full"></div>
<h4 class="font-semibold text-slate-800 text-sm">图 1. 可视化结果</h4>
</div>
<div class="bg-slate-50 border border-slate-100 rounded-lg p-4 flex justify-center">
<!-- Mock Plot -->
<div class="relative w-64 h-40 bg-white border border-slate-200 shadow-sm rounded flex items-end justify-around px-8 pb-6">
<!-- Bars -->
<div class="w-8 h-24 bg-blue-400 border border-blue-600 relative opacity-80"></div>
<div class="w-8 h-16 bg-red-400 border border-red-600 relative opacity-80"></div>
<!-- Axes -->
<div class="absolute bottom-6 left-4 right-4 h-px bg-slate-800"></div>
<div class="absolute bottom-6 left-4 top-4 w-px bg-slate-800"></div>
<!-- Labels -->
<div class="absolute bottom-2 left-10 text-[8px]">Male</div>
<div class="absolute bottom-2 right-10 text-[8px]">Female</div>
<!-- Significance -->
<div class="absolute top-6 left-10 right-10 h-2 border-t border-l border-r border-slate-800"></div>
<div class="absolute top-2 left-0 right-0 text-center text-[8px] font-bold">P < 0.01</div>
</div>
</div>
</div>
<!-- 2. Methodology & Interpretation -->
<div class="p-6 bg-slate-50/50 border-b border-slate-100">
<div class="flex items-center gap-2 mb-3">
<div class="w-1 h-4 bg-indigo-600 rounded-full"></div>
<h4 class="font-semibold text-slate-800 text-sm">方法与结果解读</h4>
</div>
<div class="prose prose-sm max-w-none text-slate-600 text-sm leading-relaxed">
<p><strong>统计方法:</strong>首先对数据进行正态性检验Shapiro-Wilk test。由于 GLU 数据不满足正态分布P < 0.05故采用非参数的 <strong>Wilcoxon 秩和检验</strong> (Mann-Whitney U test) 比较两组差异。</p>
<p class="mt-2"><strong>结果解读:</strong>结果表明,男性组和女性组的血糖水平存在显著差异 (W=1254.5, P=0.002)。男性组的中位血糖水平 (5.80) 显著高于女性组 (5.10)。</p>
</div>
</div>
<!-- 3. Assets Delivery (New) -->
<div class="bg-slate-100 p-4 flex items-center justify-between">
<div class="flex items-center gap-2 text-slate-500 text-xs font-semibold uppercase tracking-wide">
<i class="fa-solid fa-box-archive"></i> 资产交付
</div>
<div class="flex gap-3">
<button class="bg-white border border-slate-300 text-slate-700 px-3 py-1.5 rounded-lg text-xs font-medium hover:bg-slate-50 hover:text-blue-600 hover:border-blue-300 transition shadow-sm flex items-center gap-2">
<i class="fa-brands fa-r-project text-blue-600"></i> 下载 R 代码
</button>
<button class="bg-blue-600 border border-blue-600 text-white px-3 py-1.5 rounded-lg text-xs font-medium hover:bg-blue-700 transition shadow-sm flex items-center gap-2">
<i class="fa-regular fa-file-word"></i> 导出分析报告 (Word)
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Input Area -->
<div class="p-6 bg-white border-t border-slate-200 z-20">
<div class="max-w-4xl mx-auto relative">
<textarea id="user-input" class="w-full bg-slate-50 border border-slate-200 rounded-xl px-4 py-3.5 pr-14 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 resize-none text-sm shadow-inner transition placeholder-slate-400" rows="1" placeholder="在此输入新的分析需求..."></textarea>
<button onclick="handleSend()" class="absolute right-2 bottom-2 w-9 h-9 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition flex items-center justify-center shadow-md">
<i class="fa-solid fa-paper-plane text-xs"></i>
</button>
</div>
<div class="text-center mt-2.5">
<span class="text-[10px] text-slate-400 flex items-center justify-center gap-1">
<i class="fa-solid fa-shield-halved text-slate-300"></i> SSA-Pro Generate | 仅供科研参考
</span>
</div>
</div>
</main>
<script>
// --- 交互逻辑 ---
// 1. 触发上传
function triggerUpload() {
// 隐藏上传区域,显示进度
const uploadZone = document.getElementById('upload-zone');
const progress = document.getElementById('upload-progress');
if(uploadZone) uploadZone.classList.add('hidden');
if(progress) progress.classList.remove('hidden');
// 模拟延迟
setTimeout(() => {
// 更新侧边栏状态
const sbStatus = document.getElementById('sidebar-data-status');
sbStatus.classList.remove('opacity-50', 'grayscale', 'pointer-events-none');
// 显示系统确认消息
document.getElementById('data-loaded-msg').classList.remove('hidden');
// 滚动到底部
scrollToBottom();
}, 1500);
}
// 2. 发送消息
function handleSend() {
const input = document.getElementById('user-input');
if (!input.value && !document.getElementById('user-msg').classList.contains('hidden')) return;
// 如果是第一次点击发送 (Demo模式)
if (document.getElementById('user-msg').classList.contains('hidden')) {
// 显示用户消息
document.getElementById('user-msg').classList.remove('hidden');
input.value = '';
scrollToBottom();
// 延迟显示 Plan Card
setTimeout(() => {
document.getElementById('plan-card-container').classList.remove('hidden');
scrollToBottom();
}, 1000);
}
}
// 3. 执行分析
function executeAnalysis() {
const btn = document.getElementById('btn-execute');
btn.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin"></i> 计算中...';
btn.classList.add('opacity-75', 'cursor-not-allowed');
// 延迟显示结果
setTimeout(() => {
document.getElementById('execution-result-container').classList.remove('hidden');
document.getElementById('execution-result-container').classList.add('flex');
// 恢复按钮状态
btn.innerHTML = '<i class="fa-solid fa-check"></i> 已执行';
btn.className = "text-xs bg-green-100 text-green-700 border border-green-200 px-4 py-2 rounded font-medium cursor-default";
scrollToBottom();
}, 1500);
}
function scrollToBottom() {
const container = document.getElementById('chat-container');
container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' });
}
// 回车发送
document.getElementById('user-input').addEventListener('keypress', function (e) {
if (e.key === 'Enter') handleSend();
});
</script>
</body>
</html>

View File

@@ -0,0 +1,209 @@
# SSA-Pro MVP 开发计划总览
> **文档版本:** v1.3
> **创建日期:** 2026-02-18
> **最后更新:** 2026-02-18纳入 V3.0 终极审查建议)
> **项目代号:** SSA (Smart Statistical Analysis)
> **MVP 目标:** 打通完整闭环,上线 10 个核心统计工具
---
## 1. MVP 范围定义
### 1.1 包含内容 ✅
| 类别 | 内容 |
|------|------|
| **统计工具** | 10 个高频工具T检验、ANOVA、卡方、相关性等 |
| **核心流程** | 上传数据 → AI规划 → 用户确认 → R执行 → 结果交付 |
| **交互能力** | 计划确认卡片、执行路径树、结果展示、代码下载 |
| **智能能力** | RAG工具检索、Planner规划、Critic结果解读 |
| **数据安全** | LLM只看SchemaR服务处理真实数据 |
### 1.2 不包含内容 ❌
| 类别 | 说明 | 后续阶段 |
|------|------|---------|
| 50+ 工具量产 | MVP只做10个核心工具 | Phase 3 |
| 跨模块 Skills 化 | 不实现 Global Skill Registry | V2.0 |
| Word 报告导出 | 先实现代码下载 | Phase 3 |
| 大文件 OSS 传输 | MVP 限制 2MB 以内 | Phase 3 |
### 1.3 MVP 工具清单10个
| 序号 | 工具代码 | 名称 | 类别 |
|------|---------|------|------|
| 1 | ST_T_TEST_IND | 独立样本 T 检验 | 假设检验 |
| 2 | ST_T_TEST_PAIRED | 配对样本 T 检验 | 假设检验 |
| 3 | ST_ANOVA_ONE | 单因素方差分析 | 假设检验 |
| 4 | ST_CHI_SQUARE | 卡方检验 | 假设检验 |
| 5 | ST_FISHER | Fisher 精确检验 | 假设检验 |
| 6 | ST_WILCOXON | Wilcoxon 秩和检验 | 非参数检验 |
| 7 | ST_MANN_WHITNEY | Mann-Whitney U 检验 | 非参数检验 |
| 8 | ST_CORRELATION | Pearson/Spearman 相关 | 相关分析 |
| 9 | ST_LINEAR_REG | 简单线性回归 | 回归分析 |
| 10 | ST_DESCRIPTIVE | 描述性统计 | 基础统计 |
---
## 2. 整体架构
```
┌─────────────────────────────────────────────────────────────────┐
│ 前端 (React 19) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 数据上传 │ │ 计划卡片 │ │ 执行路径 │ │ 结果展示 │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
└────────────────────────────┬────────────────────────────────────┘
│ HTTP API
┌────────────────────────────┴────────────────────────────────────┐
│ Node.js 后端 (Brain) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ SSA Orchestrator (编排服务) │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Rewriter│→ │ RAG检索 │→ │ Planner │→ │ Critic │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ 只看 Schema不看真实数据 │
└────────────────────────────┬────────────────────────────────────┘
│ HTTP (内网)
┌────────────────────────────┴────────────────────────────────────┐
│ R 统计服务 (Hand) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Plumber API Gateway │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ 护栏检查 │→ │ 核心计算 │→ │ 代码生成 │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ 处理真实数据,网络隔离 │
└─────────────────────────────────────────────────────────────────┘
```
---
## 3. 里程碑与时间线
### Phase 1骨架搭建Week 1-2
**目标:** 跑通 T 检验的 "Hello World"
| 交付物 | 验收标准 |
|--------|---------|
| R Docker 镜像 | 本地可运行,健康检查通过 |
| Plumber API | POST /api/v1/skills/ST_T_TEST_IND 返回 JSON |
| Node.js 转发 | POST /api/v1/ssa/execute 调用 R 成功 |
| 数据库 Schema | tools_library, sessions, messages 表创建 |
| 前端骨架 | 基础页面框架,可上传文件 |
### Phase 2智能规划与交互Week 3-4
**目标:** 用户可与 AI 对话,确认后执行
| 交付物 | 验收标准 |
|--------|---------|
| RAG 检索 | 输入"两组差异"能返回 T 检验 |
| Planner | 生成正确的参数映射 JSON |
| 计划确认卡片 | 前端展示,用户可修改参数 |
| 执行路径树 | 显示护栏检查步骤 |
| 5 个工具 | T检验、配对T、ANOVA、卡方、相关性 |
### Phase 3完善与联调Week 5-6
**目标:** MVP 功能完整,可演示
| 交付物 | 验收标准 |
|--------|---------|
| Critic 解读 | 生成严谨的统计结论 |
| 代码下载 | 用户可下载 .R 文件 |
| 10 个工具 | 全部上线并测试通过 |
| 端到端测试 | 10 个典型场景通过 |
| SAE 部署 | R 服务部署成功 |
---
## 4. 核心 API 设计
```
POST /api/v1/ssa/sessions # 创建会话
POST /api/v1/ssa/sessions/:id/upload # 上传数据
POST /api/v1/ssa/sessions/:id/plan # 生成计划(不执行)
POST /api/v1/ssa/sessions/:id/execute # 确认执行
GET /api/v1/ssa/sessions/:id/messages # 获取消息历史
GET /api/v1/ssa/sessions/:id/download-code # 下载代码
```
---
## 5. 依赖与集成
### 5.1 平台能力复用
| 能力 | 使用方式 | 状态 |
|------|---------|------|
| LLM 网关 | `LLMFactory.getAdapter('deepseek-v3')` | ✅ 可用 |
| RAG 引擎 | `VectorSearchService` | ✅ 可用 |
| 流式响应 | `StreamingService` (Critic) | ✅ 可用 |
| Prompt 管理 | `capability_schema.prompt_templates` | ✅ 可用 |
| 认证授权 | `authenticate` 中间件 | ✅ 可用 |
| OSS 存储 | `storage.upload()` (图片/代码) | ✅ 可用 |
### 5.2 新增组件
| 组件 | 说明 |
|------|------|
| R Docker 镜像 | 基于 rocker/r-ver:4.3,含 Plumber + renv |
| R 统计服务 | SAE 新应用,**VPC 内网通信** |
| SSA 前端模块 | `frontend-v2/src/modules/ssa/` |
| SSA 后端模块 | `backend/src/modules/ssa/` |
### 5.3 关键配置要求
| 配置项 | 要求 | 原因 |
|-------|------|------|
| OSS Endpoint | 使用 VPC 内网地址 `oss-cn-xxx-internal.aliyuncs.com` | R 服务网络隔离需求 |
| SAE 扩容策略 | CPU > 60% 或并发 > 5 时自动扩容 | R 单线程,需水平扩展 |
| R 服务出站策略 | Deny Public Internet, Allow VPC | 防止数据外泄 |
---
## 6. 风险与应对
| 风险 | 概率 | 应对策略 |
|------|------|---------|
| R 工具封装进度慢 | 高 | 先做 5 个核心工具glue 模板化开发 |
| LLM 参数映射错误 | 中 | R 服务强类型校验 + 前端允许修改 |
| LLM 输出 JSON 格式错误 | 中 | json-repair 库修复 + Zod Schema 强校验 |
| R 服务并发阻塞 | 中 | **SAE 固定 2 实例**(避免冷启动 30s+ |
| 大文件导致内存溢出 | 中 | **混合协议**< 2MB inline2-20MB OSS |
| R 临时文件堆积 | 中 | on.exit() 清理 + 定时 CronJob |
| SAE 部署 R 失败 | 低 | 提前测试 Docker 镜像 |
| OSS 网络不通 | 低 | **ENV 注入 Endpoint** + DEV_MODE Mock |
| 大样本护栏超时 | 中 | **N > 5000 抽样检验**,避免 Shapiro-Wilk 超时 |
| 🆕 Node.js xlsx 内存刺客 | 中 | **SAE 内存上限 2GB+** |
| 🆕 R 服务 Segfault 崩溃 | 低 | **Liveness Probe** + 502/504 友好提示 |
| 🆕 本地开发 OSS 不通 | 中 | **DEV_MODE** 读取本地 fixtures |
| 🆕 用户代码缺依赖 | 高 | **模板头部自动安装脚本** |
| 🆕 分类变量隐私泄露 | 中 | **稀有值 < 5 隐藏** |
| 🆕 R 错误信息黑盒 | 中 | **map_r_error 友好映射** |
---
## 7. 验收标准(来自 PRD
1. **准确性**:非正态数据自动降级为非参数检验
2. **性能**2MB 数据 T 检验端到端 < 5 秒
3. **复现性**:下载的 R 代码本地可运行
4. **隐私**:审计日志不含真实患者数据
---
## 8. 相关文档索引
| 文档 | 路径 | 说明 |
|------|------|------|
| 任务清单 | `04-开发计划/01-任务清单与进度追踪.md` | 可追踪的 TODO |
| R 服务指南 | `04-开发计划/02-R服务开发指南.md` | R 工程师专用 |
| 后端指南 | `04-开发计划/03-后端开发指南.md` | Node.js 工程师专用 |
| 前端指南 | `04-开发计划/04-前端开发指南.md` | 前端工程师专用 |
| PRD | `00-系统设计/PRD SSA-Pro 严谨型智能统计分析模块.md` | 产品需求 |
| 架构设计 | `00-系统设计/SSA-Pro 严谨型智能统计分析架构设计方案V4.md` | 技术架构 |

View File

@@ -0,0 +1,195 @@
# SSA-Pro MVP 任务清单与进度追踪
> **文档版本:** v1.3
> **创建日期:** 2026-02-18
> **最后更新:** 2026-02-18纳入 V3.0 终极审查建议)
> **更新频率:** 每日站会后更新
---
## 状态图例
| 状态 | 含义 |
|------|------|
| ⬜ | 未开始 |
| 🔄 | 进行中 |
| ✅ | 已完成 |
| ⏸️ | 暂停/阻塞 |
---
## Phase 1骨架搭建Week 1-2
**里程碑目标:** T 检验 API 端到端跑通
### R 服务任务
| 状态 | 任务 | 预估 | 备注 |
|------|------|------|------|
| ⬜ | 创建 `r-statistics-service/` 目录结构 | 2h | 含 templates/, fixtures/ 目录 |
| ⬜ | 初始化 renv 并生成 `renv.lock` | 1h | **锁定包版本** |
| ⬜ | 编写 Dockerfile基于 rocker/r-ver:4.3 | 2h | 使用 renv::restore() |
| ⬜ | 🆕 Dockerfile 配置 OSS 环境变量 | 1h | **ENV 注入,非硬编码** |
| ⬜ | 安装 glue 包,创建代码模板文件 | 2h | **替代 paste0 拼接** |
| ⬜ | 🆕 实现 `data_loader.R`(混合协议) | 3h | **支持 inline/OSS/DEV_MODE** |
| ⬜ | 🆕 实现 `result_formatter.R`p_value_fmt | 1h | **APA 格式化** |
| ⬜ | 实现 `plumber.R` 入口文件 | 2h | 健康检查 + 动态路由 |
| ⬜ | 🆕 plumber.R 添加 Debug 模式支持 | 1h | **保留临时文件排查** |
| ⬜ | 定义错误码枚举error_codes.R | 1h | **业务/系统错误分离** |
| ⬜ | 🆕 扩展错误码映射表map_r_error | 1h | **R 错误 → 用户友好提示** |
| ⬜ | 🆕 代码模板头部添加依赖安装脚本 | 0.5h | **用户本地可运行** |
| ⬜ | 🆕 创建 `tests/fixtures/` 标准测试数据 | 2h | **normal/skewed/missing** |
| ⬜ | 实现 T 检验 WrapperST_T_TEST_IND | 4h | 含护栏 + glue + 大样本优化 |
| ⬜ | 本地 Docker 测试通过 | 2h | |
### 后端任务
| 状态 | 任务 | 预估 | 备注 |
|------|------|------|------|
| ⬜ | 创建 `backend/src/modules/ssa/` 目录结构 | 1h | |
| ⬜ | 设计并创建数据库 SchemaPrisma | 3h | 4张表 |
| ⬜ | 执行 `prisma migrate dev` | 0.5h | |
| ⬜ | 安装 json-repair 和 zod 依赖 | 0.5h | **LLM 输出容错** |
| ⬜ | 实现 `RClientService`(调用 R 服务) | 3h | 超时 120s |
| ⬜ | 🆕 RClientService 添加 502/504 友好处理 | 0.5h | **R 崩溃用户提示** |
| ⬜ | 🆕 DataParserService 分类变量隐私保护 | 1h | **稀有值 < 5 隐藏** |
| ⬜ | 实现 `POST /api/v1/ssa/execute` 存根 | 2h | 先做转发 |
| ⬜ | 注册路由到 `index.ts` | 0.5h | |
### 前端任务
| 状态 | 任务 | 预估 | 备注 |
|------|------|------|------|
| ⬜ | 创建 `frontend-v2/src/modules/ssa/` 目录结构 | 1h | |
| ⬜ | 注册到 `moduleRegistry.ts` | 0.5h | |
| ⬜ | 实现基础页面框架SSAWorkspace | 3h | 参考原型图 |
| ⬜ | 实现左侧边栏组件 | 2h | |
| ⬜ | 实现数据上传组件DataUploader | 3h | |
| ⬜ | 构造 Mock 数据用于组件开发 | 1h | |
---
## Phase 2智能规划与交互Week 3-4
**里程碑目标:** 用户可与 AI 对话,确认后执行
### R 服务任务
| 状态 | 任务 | 预估 | 备注 |
|------|------|------|------|
| ⬜ | 实现配对 T 检验ST_T_TEST_PAIRED | 3h | |
| ⬜ | 实现单因素 ANOVAST_ANOVA_ONE | 3h | |
| ⬜ | 实现卡方检验ST_CHI_SQUARE | 3h | |
| ⬜ | 实现相关性分析ST_CORRELATION | 3h | |
| ⬜ | 实现通用护栏函数utils/guardrails.R | 2h | |
| ⬜ | 为 5 个工具编写元数据 YAML | 2h | |
### 后端任务
| 状态 | 任务 | 预估 | 备注 |
|------|------|------|------|
| ⬜ | 实现 `ToolRetrievalService`RAG 检索) | 4h | 复用 VectorSearchService |
| ⬜ | 导入 5 个工具元数据到 pgvector | 2h | |
| ⬜ | 注册 Prompt 到 capability_schema | 2h | 4 个 Prompt |
| ⬜ | 实现 `PlannerService`LLM 调用) | 4h | 含 json-repair + Zod 校验 |
| ⬜ | 实现 `POST /api/v1/ssa/sessions/:id/plan` | 3h | |
| ⬜ | 实现会话管理 APICRUD | 3h | |
| ⬜ | 实现 Brain-Hand 数据隔离逻辑 | 2h | Schema 给 LLMData 给 R |
| ⬜ | DataParserService 增加小样本隐私保护 | 1h | N<10 时模糊化 Min/Max |
### 前端任务
| 状态 | 任务 | 预估 | 备注 |
|------|------|------|------|
| ⬜ | 实现 Chat 消息流组件 | 4h | 复用 AIStreamChat |
| ⬜ | 实现计划确认卡片PlanCard | 4h | 参考原型图 |
| ⬜ | 实现执行路径树ExecutionTrace | 3h | 动画效果 |
| ⬜ | 实现 API 对接api.ts | 2h | |
| ⬜ | 实现 Zustand Store | 2h | |
---
## Phase 3完善与联调Week 5-6
**里程碑目标:** MVP 功能完整,可演示
### R 服务任务
| 状态 | 任务 | 预估 | 备注 |
|------|------|------|------|
| ⬜ | 实现 Fisher 精确检验ST_FISHER | 2h | |
| ⬜ | 实现 Wilcoxon 检验ST_WILCOXON | 2h | |
| ⬜ | 实现 Mann-Whitney UST_MANN_WHITNEY | 2h | |
| ⬜ | 实现简单线性回归ST_LINEAR_REG | 3h | |
| ⬜ | 实现描述性统计ST_DESCRIPTIVE | 2h | |
| ⬜ | 完善代码生成器(所有工具) | 3h | |
| ⬜ | 补充错误处理tryCatch | 2h | |
### 后端任务
| 状态 | 任务 | 预估 | 备注 |
|------|------|------|------|
| ⬜ | 实现 `CriticService`(结果解读) | 3h | 流式输出 |
| ⬜ | 实现代码下载 API | 2h | |
| ⬜ | 导入剩余 5 个工具元数据 | 1h | |
| ⬜ | 实现执行日志记录execution_logs | 2h | |
| ⬜ | 端到端集成测试 | 4h | |
### 前端任务
| 状态 | 任务 | 预估 | 备注 |
|------|------|------|------|
| ⬜ | 实现结果展示卡片ResultCard | 4h | 三线表 + 图表 |
| ⬜ | 实现代码下载功能 | 2h | |
| ⬜ | 实现消息历史加载 | 2h | |
| ⬜ | UI 样式精调(对齐原型图) | 3h | |
| ⬜ | 端到端联调测试 | 4h | |
### 部署任务
| 状态 | 任务 | 预估 | 备注 |
|------|------|------|------|
| ⬜ | R 服务 Docker 镜像推送 ACR | 1h | |
| ⬜ | SAE 创建 R 服务应用 | 2h | |
| ⬜ | 🆕 **配置 SAE 固定 2 实例** | 1h | **避免冷启动 30s+ 延迟** |
| ⬜ | 🆕 **配置 R 服务 Liveness Probe** | 0.5h | **检测僵尸进程,自动重启** |
| ⬜ | 🆕 **配置 Node.js 内存上限 2GB+** | 0.5h | **xlsx 全量读取防 OOM** |
| ⬜ | 🆕 **配置 OSS Endpoint 环境变量** | 0.5h | **开发公网/生产内网** |
| ⬜ | **配置 R 服务出站策略** | 0.5h | Deny Public, Allow VPC |
| ⬜ | 配置内网通信Node.js → R | 1h | |
| ⬜ | **创建临时文件清理 CronJob** | 1h | 每日清理 /tmp |
| ⬜ | 生产环境验证 | 2h | |
---
## 进度统计
| Phase | 任务总数 | 已完成 | 进度 |
|-------|---------|--------|------|
| Phase 1 | 21 | 0 | 0% |
| Phase 2 | 20 | 0 | 0% |
| Phase 3 | 21 | 0 | 0% |
| **总计** | **62** | **0** | **0%** |
---
## 风险与阻塞项
| 日期 | 问题描述 | 影响 | 解决方案 | 状态 |
|------|---------|------|---------|------|
| | | | | |
---
## 每日站会记录
### 2026-02-xx
**昨日完成:**
-
**今日计划:**
-
**阻塞问题:**
-

View File

@@ -0,0 +1,825 @@
# SSA-Pro 后端开发指南
> **文档版本:** v1.3
> **创建日期:** 2026-02-18
> **最后更新:** 2026-02-18纳入 V3.0 终极审查建议)
> **目标读者:** Node.js 后端工程师
---
## 1. 模块目录结构
```
backend/src/modules/ssa/
├── index.ts # 模块入口,注册路由
├── routes/
│ ├── session.routes.ts # 会话管理路由
│ └── analysis.routes.ts # 分析执行路由
├── services/
│ ├── SessionService.ts # 会话 CRUD
│ ├── PlannerService.ts # AI 规划LLM 调用)
│ ├── CriticService.ts # 结果解读(流式)
│ ├── ToolRetrievalService.ts # RAG 工具检索
│ ├── RClientService.ts # R 服务调用
│ └── DataParserService.ts # 数据解析 + Schema 提取
├── validators/
│ └── planSchema.ts # 📌 Zod Schema 定义
├── dto/
│ ├── CreateSessionDto.ts
│ ├── UploadDataDto.ts
│ └── ExecuteAnalysisDto.ts
└── types/
└── index.ts # 类型定义
```
---
## 2. 数据库 SchemaPrisma
```prisma
// schema.prisma - SSA 模块部分
// 分析会话
model SsaSession {
id String @id @default(uuid())
userId String @map("user_id")
title String?
dataSchema Json? @map("data_schema") // 数据结构LLM可见
dataPayload Json? @map("data_payload") // 真实数据仅R可见
status String @default("active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
messages SsaMessage[]
@@map("ssa_sessions")
@@schema("ssa_schema")
}
// 消息记录
model SsaMessage {
id String @id @default(uuid())
sessionId String @map("session_id")
role String // user | assistant | system
contentType String @map("content_type") // text | plan | result
content Json
createdAt DateTime @default(now()) @map("created_at")
session SsaSession @relation(fields: [sessionId], references: [id])
@@map("ssa_messages")
@@schema("ssa_schema")
}
// 工具库
model SsaTool {
id String @id @default(uuid())
toolCode String @unique @map("tool_code")
name String
version String @default("1.0.0")
description String
usageContext String? @map("usage_context")
paramsSchema Json @map("params_schema")
guardrails Json?
searchText String @map("search_text")
embedding Unsupported("vector(1024)")?
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("tools_library")
@@schema("ssa_schema")
}
// 执行日志
model SsaExecutionLog {
id String @id @default(uuid())
sessionId String @map("session_id")
messageId String? @map("message_id")
toolCode String @map("tool_code")
inputParams Json @map("input_params")
outputStatus String @map("output_status")
outputResult Json? @map("output_result")
traceLog String[] @map("trace_log")
executionMs Int? @map("execution_ms")
createdAt DateTime @default(now()) @map("created_at")
@@map("execution_logs")
@@schema("ssa_schema")
}
```
---
## 3. API 路由设计
### 3.1 路由注册
```typescript
// index.ts
import { FastifyInstance } from 'fastify';
import sessionRoutes from './routes/session.routes';
import analysisRoutes from './routes/analysis.routes';
export default async function ssaModule(app: FastifyInstance) {
// 注册认证中间件
app.addHook('preHandler', app.authenticate);
// 注册子路由
app.register(sessionRoutes, { prefix: '/sessions' });
app.register(analysisRoutes, { prefix: '/sessions' });
}
```
### 3.2 会话路由
```typescript
// routes/session.routes.ts
import { FastifyInstance } from 'fastify';
import { SessionService } from '../services/SessionService';
export default async function sessionRoutes(app: FastifyInstance) {
const sessionService = new SessionService();
// 创建会话
app.post('/', async (req, reply) => {
const userId = req.user.id;
const session = await sessionService.create(userId);
return reply.send(session);
});
// 获取会话列表
app.get('/', async (req, reply) => {
const userId = req.user.id;
const sessions = await sessionService.listByUser(userId);
return reply.send(sessions);
});
// 获取单个会话(含消息历史)
app.get('/:id', async (req, reply) => {
const { id } = req.params as { id: string };
const session = await sessionService.getById(id, req.user.id);
return reply.send(session);
});
// 上传数据
app.post('/:id/upload', async (req, reply) => {
const { id } = req.params as { id: string };
// 解析 Excel/CSV提取 Schema 和 Data
const result = await sessionService.uploadData(id, req);
return reply.send(result);
});
}
```
### 3.3 分析路由
```typescript
// routes/analysis.routes.ts
import { FastifyInstance } from 'fastify';
import { PlannerService } from '../services/PlannerService';
import { RClientService } from '../services/RClientService';
import { CriticService } from '../services/CriticService';
export default async function analysisRoutes(app: FastifyInstance) {
const plannerService = new PlannerService();
const rClientService = new RClientService();
const criticService = new CriticService();
// 生成分析计划(不执行)
app.post('/:id/plan', async (req, reply) => {
const { id } = req.params as { id: string };
const { query } = req.body as { query: string };
// 1. RAG 检索工具
// 2. LLM 生成计划
const plan = await plannerService.generatePlan(id, query);
return reply.send({
type: 'plan',
plan
});
});
// 确认执行
app.post('/:id/execute', async (req, reply) => {
const { id } = req.params as { id: string };
const { plan } = req.body as { plan: object };
// 1. 调用 R 服务执行
const result = await rClientService.execute(id, plan);
// 2. 保存执行日志
// 3. 保存结果到消息
return reply.send({
type: 'result',
result
});
});
// 获取结果解读(流式)
app.get('/:id/interpret/:messageId', async (req, reply) => {
const { id, messageId } = req.params as { id: string; messageId: string };
// 流式返回 Critic 解读
reply.raw.setHeader('Content-Type', 'text/event-stream');
await criticService.streamInterpret(id, messageId, reply.raw);
});
// 下载代码
app.get('/:id/download-code/:messageId', async (req, reply) => {
const { id, messageId } = req.params as { id: string; messageId: string };
const code = await sessionService.getReproducibleCode(messageId);
reply.header('Content-Type', 'text/plain');
reply.header('Content-Disposition', 'attachment; filename="analysis.R"');
return reply.send(code);
});
}
```
---
## 4. 核心服务实现
### 4.1 RClientService调用 R 服务)
```typescript
// services/RClientService.ts
import axios, { AxiosInstance } from 'axios';
import { prisma } from '@/common/db';
import { logger } from '@/common/logging';
export class RClientService {
private client: AxiosInstance;
constructor() {
this.client = axios.create({
baseURL: process.env.R_SERVICE_URL || 'http://localhost:8080',
timeout: 120000, // 📌 120s 超时(应对复杂计算)
headers: { 'Content-Type': 'application/json' }
});
}
async execute(sessionId: string, plan: {
tool_code: string;
params: Record<string, any>;
guardrails: Record<string, boolean>;
}) {
const startTime = Date.now();
// 1. 获取会话的真实数据
const session = await prisma.ssaSession.findUniqueOrThrow({
where: { id: sessionId }
});
// 🆕 2. 构造 R 服务请求(混合数据协议)
const dataSource = this.buildDataSource(session);
const requestBody = {
data_source: dataSource, // 🆕 统一数据源字段
params: plan.params,
guardrails: plan.guardrails
};
/**
* 🆕 根据数据大小选择传输方式
* - < 2MB: inline JSON
* - >= 2MB: OSS key
*/
private buildDataSource(session: any): { type: string; data?: any; oss_key?: string } {
const payload = session.dataPayload;
const payloadSize = JSON.stringify(payload).length;
const SIZE_THRESHOLD = 2 * 1024 * 1024; // 2MB
if (payloadSize < SIZE_THRESHOLD) {
// 小数据:直接内联
return {
type: 'inline',
data: payload
};
} else {
// 大数据:上传 OSS传递 key
// 注意:此处假设 session 创建时已上传 OSS
const ossKey = session.dataOssKey || `sessions/${session.id}/data.json`;
return {
type: 'oss',
oss_key: ossKey
};
}
}
// 3. 调用 R 服务
try {
const response = await this.client.post(
`/api/v1/skills/${plan.tool_code}`,
requestBody
);
const executionMs = Date.now() - startTime;
// 4. 记录执行日志(不含真实数据)
await prisma.ssaExecutionLog.create({
data: {
sessionId,
toolCode: plan.tool_code,
inputParams: plan.params, // 只记录参数,不记录数据
outputStatus: response.data.status,
outputResult: response.data.results,
traceLog: response.data.trace_log || [],
executionMs
}
});
return response.data;
} catch (error: any) {
logger.error('R service call failed', { sessionId, toolCode: plan.tool_code, error });
// 🆕 502/504 特殊处理R 服务崩溃或超时)
const statusCode = error.response?.status;
if (statusCode === 502 || statusCode === 504) {
throw new Error('统计服务繁忙或数据异常,请稍后重试');
}
// 🆕 提取 R 服务返回的用户友好提示
const userHint = error.response?.data?.user_hint;
if (userHint) {
throw new Error(userHint);
}
throw new Error(`R service error: ${error.message}`);
}
}
async healthCheck(): Promise<boolean> {
try {
const res = await this.client.get('/health');
return res.data.status === 'ok';
} catch {
return false;
}
}
}
```
### 4.2 ToolRetrievalServiceRAG 检索)
```typescript
// services/ToolRetrievalService.ts
import { VectorSearchService } from '@/common/rag';
import { LLMFactory } from '@/common/llm/adapters/LLMFactory';
import { prisma } from '@/common/db';
export class ToolRetrievalService {
private vectorSearch: VectorSearchService;
constructor() {
this.vectorSearch = new VectorSearchService({
schema: 'ssa_schema',
table: 'tools_library',
embeddingColumn: 'embedding',
textColumn: 'search_text'
});
}
async retrieveTools(query: string, dataSchema: object, topK = 5) {
// 1. Query Rewrite可选提升召回
const rewriter = LLMFactory.getAdapter('deepseek-v3');
const rewritePrompt = `
将用户的统计分析需求改写为更适合检索统计工具的查询:
用户需求: ${query}
数据结构: ${JSON.stringify(dataSchema)}
输出改写后的查询(一句话):
`.trim();
const rewrittenQuery = await rewriter.chat([
{ role: 'user', content: rewritePrompt }
]);
// 2. 向量检索
const vectorResults = await this.vectorSearch.search(rewrittenQuery, topK);
// 3. 关键词检索 (pg_bigm)
const keywordResults = await prisma.$queryRaw`
SELECT id, tool_code, name, description, params_schema, guardrails
FROM ssa_schema.tools_library
WHERE search_text LIKE '%' || ${query} || '%'
AND is_active = true
LIMIT 5
`;
// 4. RRF 融合
const merged = this.rrfMerge(vectorResults, keywordResults);
// 5. Rerank可选
// const reranked = await this.rerank(merged, query);
return merged.slice(0, topK);
}
private rrfMerge(vectorResults: any[], keywordResults: any[], k = 60) {
const scores = new Map<string, number>();
vectorResults.forEach((item, idx) => {
const rrf = 1 / (k + idx + 1);
scores.set(item.id, (scores.get(item.id) || 0) + rrf);
});
keywordResults.forEach((item, idx) => {
const rrf = 1 / (k + idx + 1);
scores.set(item.id, (scores.get(item.id) || 0) + rrf);
});
// 合并并排序
const allItems = [...vectorResults, ...keywordResults];
const unique = [...new Map(allItems.map(i => [i.id, i])).values()];
return unique.sort((a, b) =>
(scores.get(b.id) || 0) - (scores.get(a.id) || 0)
);
}
}
```
### 4.3 PlannerServiceAI 规划 + JSON 容错)
```typescript
// services/PlannerService.ts
import { LLMFactory } from '@/common/llm/adapters/LLMFactory';
import { PromptService } from '@/common/prompts';
import { ToolRetrievalService } from './ToolRetrievalService';
import { prisma } from '@/common/db';
import { jsonrepair } from 'jsonrepair'; // 📌 JSON 修复库
import { planSchema } from '../validators/planSchema'; // 📌 Zod Schema
export class PlannerService {
private retrieval: ToolRetrievalService;
constructor() {
this.retrieval = new ToolRetrievalService();
}
async generatePlan(sessionId: string, userQuery: string) {
// 1. 获取会话的数据 Schema不含真实数据
const session = await prisma.ssaSession.findUniqueOrThrow({
where: { id: sessionId },
select: { dataSchema: true }
});
// 2. RAG 检索候选工具
const candidateTools = await this.retrieval.retrieveTools(
userQuery,
session.dataSchema,
5
);
// 3. 获取 Planner Prompt
const promptTemplate = await PromptService.get('SSA_PLANNER');
// 4. 构造 Prompt
const systemPrompt = promptTemplate
.replace('{{data_schema_json}}', JSON.stringify(session.dataSchema, null, 2))
.replace('{{candidate_tools_json}}', JSON.stringify(candidateTools, null, 2));
// 5. 调用 LLM
const llm = LLMFactory.getAdapter('deepseek-v3');
const response = await llm.chat([
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userQuery }
]);
// 6. 📌 解析 + 修复 + 校验 JSON
const plan = this.parseAndValidateJson(response, candidateTools);
// 7. 保存用户消息和计划消息
await prisma.ssaMessage.createMany({
data: [
{
sessionId,
role: 'user',
contentType: 'text',
content: { text: userQuery }
},
{
sessionId,
role: 'assistant',
contentType: 'plan',
content: plan
}
]
});
return plan;
}
// 📌 增强的 JSON 解析(含修复和校验)
private parseAndValidateJson(text: string, candidateTools: any[]): object {
// Step 1: 提取 JSON 块
const jsonMatch = text.match(/```json\n?([\s\S]*?)\n?```/) ||
text.match(/\{[\s\S]*\}/);
if (!jsonMatch) {
throw new Error('LLM response does not contain valid JSON');
}
let jsonStr = jsonMatch[1] || jsonMatch[0];
// Step 2: 使用 jsonrepair 修复常见问题(末尾逗号、缺少引号等)
try {
jsonStr = jsonrepair(jsonStr);
} catch (repairError) {
// 修复失败,继续尝试原始解析
}
// Step 3: 解析 JSON
let parsed: any;
try {
parsed = JSON.parse(jsonStr);
} catch (parseError) {
throw new Error(`JSON parse failed: ${parseError.message}`);
}
// Step 4: 使用 Zod 校验结构
const validatedPlan = planSchema.safeParse(parsed);
if (!validatedPlan.success) {
throw new Error(`Plan validation failed: ${validatedPlan.error.message}`);
}
// Step 5: 校验 tool_code 是否在候选列表中
const validToolCodes = candidateTools.map(t => t.tool_code);
if (!validToolCodes.includes(validatedPlan.data.tool_code)) {
throw new Error(`Invalid tool_code: ${validatedPlan.data.tool_code}`);
}
return validatedPlan.data;
}
}
```
### 4.4 Zod Schema 定义
```typescript
// validators/planSchema.ts
import { z } from 'zod';
export const planSchema = z.object({
tool_code: z.string().min(1),
reasoning: z.string().optional(),
params: z.record(z.any()),
guardrails: z.object({
check_normality: z.boolean().optional(),
check_homogeneity: z.boolean().optional(),
auto_fix: z.boolean().optional()
}).optional()
});
export type PlanType = z.infer<typeof planSchema>;
```
---
## 5. Brain-Hand 数据隔离
**核心原则LLM 只看 SchemaR 服务处理真实数据**
```
┌─────────────────────────────────────────────────────────────┐
│ 数据上传流程 │
│ │
│ Excel/CSV ──────┬────────────────────────────────────────│
│ │ │
│ ┌──────▼──────┐ │
│ │ 数据解析器 │ │
│ └──────┬──────┘ │
│ │ │
│ ┌─────────┴─────────┐ │
│ │ │ │
│ dataSchema dataPayload │
│ (结构/类型/统计) (真实数据) │
│ │ │ │
│ ▼ ▼ │
│ LLM (Planner) R (Executor) │
│ │
└─────────────────────────────────────────────────────────────┘
```
### 5.1 数据解析实现
```typescript
// services/DataParserService.ts
import * as XLSX from 'xlsx';
export class DataParserService {
static parse(buffer: Buffer, filename: string) {
const workbook = XLSX.read(buffer, { type: 'buffer' });
const sheetName = workbook.SheetNames[0];
const sheet = workbook.Sheets[sheetName];
// 转为 JSON 数组
const data = XLSX.utils.sheet_to_json(sheet);
// 提取 Schema
const schema = this.extractSchema(data);
return {
dataSchema: schema, // 给 LLM
dataPayload: data // 给 R
};
}
private static extractSchema(data: any[]) {
if (data.length === 0) return { columns: [], rowCount: 0 };
const columns = Object.keys(data[0]).map(colName => {
const values = data.map(row => row[colName]).filter(v => v != null);
const type = this.inferType(values);
return {
name: colName,
type,
...this.computeStats(values, type, data.length) // 📌 传入行数用于隐私保护
};
});
return {
rowCount: data.length,
columns
};
}
private static inferType(values: any[]): 'numeric' | 'categorical' | 'datetime' {
const sample = values.slice(0, 100);
const numericCount = sample.filter(v => typeof v === 'number' || !isNaN(Number(v))).length;
if (numericCount / sample.length > 0.9) return 'numeric';
return 'categorical';
}
private static computeStats(values: any[], type: string, rowCount: number) {
if (type === 'numeric') {
const nums = values.map(Number).filter(n => !isNaN(n));
let min = Math.min(...nums);
let max = Math.max(...nums);
// 📌 小样本隐私保护N < 10 时模糊化极值
if (rowCount < 10) {
min = Math.floor(min / 10) * 10; // 向下取整到十位
max = Math.ceil(max / 10) * 10; // 向上取整到十位
}
return {
min,
max,
mean: nums.reduce((a, b) => a + b, 0) / nums.length,
missing: values.length - nums.length,
privacyProtected: rowCount < 10 // 📌 标记是否已模糊化
};
}
// categorical
const counts = new Map<string, number>();
values.forEach(v => {
const key = String(v);
counts.set(key, (counts.get(key) || 0) + 1);
});
// 🆕 分类变量隐私保护:
// 如果某个取值的计数 < 5 且总行数 > 10则隐藏具体值
const uniqueValues: string[] = [];
let maskedCount = 0;
for (const [value, count] of counts.entries()) {
if (count < 5 && rowCount > 10) {
maskedCount++;
} else {
uniqueValues.push(value);
}
}
// 最多展示 10 个非敏感值
const safeValues = uniqueValues.slice(0, 10);
if (maskedCount > 0) {
safeValues.push(`[${maskedCount} 个稀有值已隐藏]`);
}
return {
uniqueValues: safeValues,
uniqueCount: counts.size,
missing: values.filter(v => v == null || v === '').length,
privacyProtected: maskedCount > 0 // 🆕 标记
};
}
}
```
---
## 6. Prompt 注册
```sql
-- 注册 Planner Prompt
INSERT INTO capability_schema.prompt_templates (code, name, content, model, temperature)
VALUES (
'SSA_PLANNER',
'SSA 统计规划器',
'你是一名资深的生物统计学家。你面前有一份数据摘要Metadata和一组可用的统计工具箱。
请根据用户的需求选择最合适的一个工具并生成详细的执行计划SAP
### 数据摘要
{{data_schema_json}}
### 可用工具箱 (Candidates)
{{candidate_tools_json}}
### 决策规则 (Guardrails)
1. **类型匹配**:严格检查变量类型。不要把分类变量填入要求数值型的参数中。
2. **工具匹配**:如果用户要做 "预测",优先选 "回归" 类工具;如果做 "差异",选 "检验" 类工具。
3. **护栏配置**:对于 T 检验、ANOVA 等参数检验,必须开启 check_normality。
### 输出要求
请先在 <thinking> 标签中进行推理,分析变量类型和工具适用性。
然后输出纯 JSON格式如下
{
"tool_code": "选中工具的CODE",
"reasoning": "一句话解释为什么选这个工具",
"params": { ...根据工具定义的 params_schema 填写... },
"guardrails": { "check_normality": true, "auto_fix": true }
}',
'deepseek-v3',
0.3
);
```
---
## 7. 与主应用集成
```typescript
// backend/src/index.ts
import ssaModule from './modules/ssa';
// 在 Fastify 注册
app.register(ssaModule, { prefix: '/api/v1/ssa' });
```
---
## 8. 环境变量
```env
# .env
# R 服务配置
R_SERVICE_URL=http://ssa-r-service:8080 # SAE VPC 内网地址
R_SERVICE_TIMEOUT=120000 # 📌 超时 120s
# 📌 OSS 配置(必须使用 VPC 内网 Endpoint
OSS_ENDPOINT=oss-cn-beijing-internal.aliyuncs.com # 内网地址
OSS_BUCKET=ssa-data-bucket
OSS_ACCESS_KEY_ID=your-access-key
OSS_ACCESS_KEY_SECRET=your-secret
# LLM 配置
LLM_DEFAULT_MODEL=deepseek-v3
```
> **重要**OSS Endpoint 必须使用 `-internal` 后缀的 VPC 内网地址,否则 R 服务的网络隔离策略会导致文件下载失败。
---
## 9. 测试检查清单
| 测试场景 | 预期结果 |
|----------|---------|
| POST /sessions 创建会话 | 返回 sessionId |
| POST /sessions/:id/upload (CSV) | 返回 dataSchema |
| POST /sessions/:id/upload (N<10) | dataSchema.privacyProtected = true |
| POST /sessions/:id/plan (T检验意图) | 返回包含 tool_code 的 plan |
| POST /sessions/:id/plan (LLM 返回格式错误 JSON) | json-repair 修复成功 |
| POST /sessions/:id/plan (参数不合法) | Zod 校验失败,返回错误 |
| POST /sessions/:id/execute | R 服务返回 success |
| POST /sessions/:id/execute (超过 60s) | 不超时,等待 120s |
| GET /sessions/:id/download-code | 下载 .R 文件 |
| R 服务宕机时 execute | 返回友好错误 |
---
## 10. 依赖包清单
```json
{
"dependencies": {
"jsonrepair": "^3.6.0",
"zod": "^3.22.4",
"xlsx": "^0.18.5",
"axios": "^1.6.0"
}
}
```

View File

@@ -0,0 +1,897 @@
# SSA-Pro 前端开发指南
> **文档版本:** v1.3
> **创建日期:** 2026-02-18
> **最后更新:** 2026-02-18纳入 V3.0 终极审查建议)
> **目标读者:** 前端工程师
> **原型参考:** `03-UI设计/智能统计分析V2.html`
---
## 1. 模块目录结构
```
frontend-v2/src/modules/ssa/
├── index.ts # 模块入口,导出路由
├── pages/
│ └── SSAWorkspace.tsx # 主页面(工作区)
├── components/
│ ├── layout/
│ │ ├── SSASidebar.tsx # 左侧边栏
│ │ ├── SSAHeader.tsx # 顶部标题栏
│ │ └── SSAInputArea.tsx # 底部输入区
│ ├── chat/
│ │ ├── MessageList.tsx # 消息流容器
│ │ ├── SystemMessage.tsx # 系统消息气泡
│ │ ├── UserMessage.tsx # 用户消息气泡
│ │ └── AssistantMessage.tsx # AI 消息(含卡片)
│ ├── cards/
│ │ ├── DataUploader.tsx # 数据上传区
│ │ ├── DataStatus.tsx # 数据集状态卡片
│ │ ├── PlanCard.tsx # 分析计划确认卡片 ⭐
│ │ ├── ExecutionTrace.tsx # 执行路径树 ⭐
│ │ ├── ExecutionProgress.tsx# 📌 执行进度动画 ⭐
│ │ └── ResultCard.tsx # 结果报告卡片 ⭐
│ └── common/
│ ├── APATable.tsx # 三线表组件
│ └── PlotViewer.tsx # 图表查看器
├── hooks/
│ ├── useSSASession.ts # 会话管理 Hook
│ └── useSSAExecution.ts # 执行控制 Hook
├── store/
│ └── ssaStore.ts # Zustand Store
├── api/
│ └── ssaApi.ts # API 封装
├── types/
│ └── index.ts # 类型定义
└── styles/
└── ssa.css # 模块样式
```
---
## 2. 原型图核心元素解析
根据 `智能统计分析V2.html` 原型,需实现以下核心 UI
### 2.1 整体布局
```
┌─────────────────────────────────────────────────────────────────┐
│ ┌───────────┐ ┌─────────────────────────────────────────────┐ │
│ │ │ │ Header (会话标题) │ │
│ │ Sidebar │ ├─────────────────────────────────────────────┤ │
│ │ │ │ │ │
│ │ - 导入数据│ │ Chat Flow (消息流) │ │
│ │ - 新会话 │ │ │ │
│ │ - 历史 │ │ - SystemMessage (欢迎/上传引导) │ │
│ │ │ │ - UserMessage (用户输入) │ │
│ │ │ │ - PlanCard (计划确认) │ │
│ │ ─────── │ │ - ExecutionTrace (执行路径) │ │
│ │ 数据状态 │ │ - ResultCard (结果报告) │ │
│ │ │ │ │ │
│ │ │ ├─────────────────────────────────────────────┤ │
│ │ │ │ InputArea (输入框 + 发送按钮) │ │
│ └───────────┘ └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
### 2.2 核心组件设计规范
| 组件 | 原型特征 | 实现要点 |
|------|---------|---------|
| **SSASidebar** | 宽 256px白色背景阴影分隔 | Logo + 按钮 + 历史列表 + 数据状态 |
| **PlanCard** | 圆角卡片,分组/检验变量展示,护栏警告 | 支持参数编辑、确认/修改按钮 |
| **ExecutionTrace** | 竖向树状结构,带状态图标和连接线 | 动画展开,步骤状态(成功/警告/进行中) |
| **ResultCard** | 多区块:三线表 + 图表 + 解读 + 下载 | APA 格式表格Base64 图片渲染 |
| **APATable** | 顶线(2px) + 表头下线(1px) + 底线(2px) | 数字右对齐,等宽字体 |
---
## 3. 核心组件实现
### 3.1 PlanCard计划确认卡片
```tsx
// components/cards/PlanCard.tsx
import React from 'react';
import { Card, Button, Tag, Alert, Space, Descriptions } from 'antd';
import { PlayCircleOutlined, EditOutlined, SafetyOutlined } from '@ant-design/icons';
interface PlanCardProps {
plan: {
tool_code: string;
tool_name: string;
reasoning: string;
params: Record<string, any>;
guardrails: {
check_normality?: boolean;
auto_fix?: boolean;
};
};
dataSchema: {
columns: Array<{ name: string; type: string; uniqueValues?: string[] }>;
};
onConfirm: () => void;
onEdit: () => void;
loading?: boolean;
}
export const PlanCard: React.FC<PlanCardProps> = ({
plan,
dataSchema,
onConfirm,
onEdit,
loading = false
}) => {
// 查找变量类型信息
const getColumnInfo = (colName: string) => {
const col = dataSchema.columns.find(c => c.name === colName);
if (!col) return '';
if (col.type === 'categorical' && col.uniqueValues) {
return `(分类: ${col.uniqueValues.slice(0, 3).join('/')})`;
}
return `(${col.type === 'numeric' ? '数值型' : '分类型'})`;
};
return (
<Card
className="plan-card"
title={
<Space>
<span></span>
<Tag color="blue">{plan.tool_name}</Tag>
</Space>
}
styles={{ header: { background: '#f8fafc' } }}
>
{/* 变量映射 */}
<div className="grid grid-cols-2 gap-4 mb-4">
{Object.entries(plan.params).map(([key, value]) => (
<div key={key} className="bg-slate-50 p-3 rounded border border-slate-100">
<div className="text-xs text-slate-400 uppercase font-bold mb-1">
{key.replace(/_/g, ' ')}
</div>
<div className="text-sm font-medium text-slate-800">
{String(value)}
<span className="text-xs text-slate-400 font-normal ml-1">
{getColumnInfo(String(value))}
</span>
</div>
</div>
))}
</div>
{/* 护栏提示 */}
{plan.guardrails.check_normality && (
<Alert
type="warning"
icon={<SafetyOutlined />}
showIcon
message="统计护栏 (自动执行)"
description={
<ul className="list-disc list-inside text-xs mt-1 space-y-1">
<li>Shapiro-Wilk </li>
<li>Levene </li>
{plan.guardrails.auto_fix && (
<li className="font-medium">
Wilcoxon
</li>
)}
</ul>
}
className="mb-4"
/>
)}
{/* 操作按钮 */}
<div className="flex justify-end gap-3 pt-3 border-t border-slate-100">
<Button icon={<EditOutlined />} onClick={onEdit}>
</Button>
<Button
type="primary"
icon={<PlayCircleOutlined />}
onClick={onConfirm}
loading={loading}
>
</Button>
</div>
</Card>
);
};
```
### 3.2 ExecutionTrace执行路径树
```tsx
// components/cards/ExecutionTrace.tsx
import React from 'react';
import { CheckCircleFilled, ExclamationCircleFilled,
SwapOutlined, CalculatorOutlined, LoadingOutlined } from '@ant-design/icons';
interface TraceStep {
id: string;
label: string;
status: 'success' | 'warning' | 'error' | 'running' | 'pending';
detail?: string;
subLabel?: string;
}
interface ExecutionTraceProps {
steps: TraceStep[];
}
export const ExecutionTrace: React.FC<ExecutionTraceProps> = ({ steps }) => {
const getIcon = (status: TraceStep['status']) => {
switch (status) {
case 'success':
return <CheckCircleFilled className="text-green-500" />;
case 'warning':
return <ExclamationCircleFilled className="text-amber-500" />;
case 'error':
return <ExclamationCircleFilled className="text-red-500" />;
case 'running':
return <LoadingOutlined className="text-blue-500" spin />;
default:
return <div className="w-4 h-4 rounded-full bg-slate-200" />;
}
};
return (
<div className="bg-white border border-slate-200 rounded-xl p-4 shadow-sm">
<div className="text-xs font-bold text-slate-400 uppercase mb-3 tracking-wider flex justify-between">
<span></span>
{steps.every(s => s.status === 'success') && (
<span className="text-green-600">
<CheckCircleFilled />
</span>
)}
</div>
<div className="space-y-3 font-mono text-xs relative pl-2">
{/* 连接线 */}
<div className="absolute left-[19px] top-2 bottom-4 w-px bg-slate-200" />
{steps.map((step, idx) => (
<div key={step.id} className="flex items-start gap-3 relative z-10">
<div className="w-4 h-4 flex items-center justify-center mt-0.5">
{getIcon(step.status)}
</div>
<div>
<span className={`
${step.status === 'warning' ? 'text-amber-700 font-medium' : ''}
${step.status === 'success' && step.detail ? 'text-slate-800 font-medium' : 'text-slate-600'}
`}>
{step.label}
</span>
{step.detail && (
<div className="mt-1">
<span className={`
px-1.5 py-0.5 rounded border font-bold
${step.status === 'error'
? 'bg-red-50 text-red-600 border-red-100'
: 'bg-green-50 text-green-600 border-green-100'}
`}>
{step.detail}
</span>
{step.subLabel && (
<span className="ml-2 text-slate-400">{step.subLabel}</span>
)}
</div>
)}
</div>
</div>
))}
</div>
</div>
);
};
// 使用示例
const mockSteps: TraceStep[] = [
{ id: '1', label: '加载数据 (n=150)', status: 'success' },
{
id: '2',
label: '正态性检验 (Shapiro-Wilk)',
status: 'error',
detail: 'P = 0.002 (< 0.05) ❌',
subLabel: '-> 拒绝正态假设'
},
{ id: '3', label: '策略切换: T-Test -> Wilcoxon Test', status: 'warning' },
{ id: '4', label: '计算完成', status: 'success' },
];
```
### 3.3 ExecutionProgress📌 执行进度动画)
```tsx
// components/cards/ExecutionProgress.tsx
import React, { useState, useEffect } from 'react';
import { LoadingOutlined, CheckCircleFilled } from '@ant-design/icons';
import { Progress, Typography } from 'antd';
const { Text } = Typography;
interface ExecutionProgressProps {
isExecuting: boolean;
onComplete?: () => void;
}
// 📌 模拟进度文案(缓解用户等待焦虑)
const PROGRESS_MESSAGES = [
'正在加载数据...',
'执行统计护栏检验...',
'进行核心计算...',
'生成可视化图表...',
'格式化结果...',
'即将完成...'
];
export const ExecutionProgress: React.FC<ExecutionProgressProps> = ({
isExecuting,
onComplete
}) => {
const [progress, setProgress] = useState(0);
const [messageIndex, setMessageIndex] = useState(0);
useEffect(() => {
if (!isExecuting) {
setProgress(0);
setMessageIndex(0);
return;
}
// 📌 模拟进度(实际进度由后端控制)
const progressInterval = setInterval(() => {
setProgress(prev => {
if (prev >= 90) return prev; // 卡在 90%,等待真正完成
return prev + Math.random() * 10;
});
}, 500);
const messageInterval = setInterval(() => {
setMessageIndex(prev =>
prev < PROGRESS_MESSAGES.length - 1 ? prev + 1 : prev
);
}, 2000);
return () => {
clearInterval(progressInterval);
clearInterval(messageInterval);
};
}, [isExecuting]);
if (!isExecuting) return null;
return (
<div className="bg-white border border-blue-200 rounded-xl p-6 shadow-sm animate-pulse">
<div className="flex items-center gap-3 mb-4">
<LoadingOutlined className="text-blue-500 text-xl" spin />
<Text strong className="text-blue-700"></Text>
</div>
<Progress
percent={Math.round(progress)}
status="active"
strokeColor={{
'0%': '#3b82f6',
'100%': '#10b981'
}}
/>
<Text type="secondary" className="text-sm mt-2 block">
{PROGRESS_MESSAGES[messageIndex]}
</Text>
<Text type="secondary" className="text-xs mt-4 block opacity-60">
10-30 ...
</Text>
</div>
);
};
```
### 3.4 ResultCard结果报告卡片
```tsx
// components/cards/ResultCard.tsx
import React from 'react';
import { Button, Divider, Space, Typography } from 'antd';
import { DownloadOutlined, FileWordOutlined } from '@ant-design/icons';
import { APATable } from '../common/APATable';
import { PlotViewer } from '../common/PlotViewer';
const { Title, Paragraph, Text } = Typography;
interface ResultCardProps {
result: {
method: string;
statistic: number;
p_value: number;
p_value_fmt: string; // 🆕 R 服务返回的格式化 p 值
group_stats: Array<{
group: string;
n: number;
mean?: number;
median?: number;
sd?: number;
iqr?: [number, number];
}>;
};
plots: string[]; // Base64 图片
interpretation?: string; // Critic 解读
reproducibleCode: string;
onDownloadCode: () => void;
}
export const ResultCard: React.FC<ResultCardProps> = ({
result,
plots,
interpretation,
reproducibleCode,
onDownloadCode
}) => {
// 构造表格数据
const tableData = result.group_stats.map(g => ({
group: g.group,
n: g.n,
value: g.median
? `${g.median.toFixed(2)} [${g.iqr?.[0].toFixed(2)} - ${g.iqr?.[1].toFixed(2)}]`
: `${g.mean?.toFixed(2)} ± ${g.sd?.toFixed(2)}`
}));
const columns = [
{ key: 'group', title: 'Group', width: 120 },
{ key: 'n', title: 'N', width: 60, align: 'right' as const },
{ key: 'value', title: result.method.includes('Wilcoxon') ? 'Median [IQR]' : 'Mean ± SD' },
{
key: 'statistic',
title: 'Statistic',
render: () => result.statistic.toFixed(2),
rowSpan: tableData.length
},
{
key: 'pValue',
title: 'P-Value',
render: () => (
<Text strong>
{/* 🆕 直接使用 R 服务返回的格式化值 */}
{result.p_value_fmt}
{result.p_value < 0.01 ? ' **' : result.p_value < 0.05 ? ' *' : ''}
</Text>
),
rowSpan: tableData.length
},
];
return (
<div className="bg-white border border-slate-200 rounded-xl shadow-md overflow-hidden">
{/* Header */}
<div className="bg-slate-50 px-6 py-4 border-b border-slate-200">
<Title level={5} className="mb-0"></Title>
<Text type="secondary" className="text-xs">
{result.method}
</Text>
</div>
{/* 1. 统计表格 */}
<div className="p-6 border-b border-slate-100">
<div className="flex items-center gap-2 mb-4">
<div className="w-1 h-4 bg-blue-600 rounded-full" />
<Text strong> 1. (线)</Text>
</div>
<APATable columns={columns} data={tableData} />
<Text type="secondary" className="text-xs mt-2 block italic">
Note: {result.method.includes('Wilcoxon') ? 'IQR = Interquartile Range' : 'SD = Standard Deviation'};
* P &lt; 0.05, ** P &lt; 0.01.
</Text>
</div>
{/* 2. 图表 */}
{plots.length > 0 && (
<div className="p-6 border-b border-slate-100">
<div className="flex items-center gap-2 mb-3">
<div className="w-1 h-4 bg-blue-600 rounded-full" />
<Text strong> 1. </Text>
</div>
<PlotViewer src={plots[0]} alt="Statistical Plot" />
</div>
)}
{/* 3. 方法与解读 */}
{interpretation && (
<div className="p-6 bg-slate-50/50 border-b border-slate-100">
<div className="flex items-center gap-2 mb-3">
<div className="w-1 h-4 bg-indigo-600 rounded-full" />
<Text strong></Text>
</div>
<div
className="prose prose-sm max-w-none text-slate-600"
dangerouslySetInnerHTML={{ __html: interpretation }}
/>
</div>
)}
{/* 4. 资产交付 */}
<div className="bg-slate-100 p-4 flex items-center justify-between">
<Text type="secondary" className="text-xs font-semibold uppercase tracking-wide">
</Text>
<Space>
<Button
icon={<DownloadOutlined />}
onClick={onDownloadCode}
>
R
</Button>
<Button
type="primary"
icon={<FileWordOutlined />}
disabled // MVP 阶段禁用
>
(Word)
</Button>
</Space>
</div>
</div>
);
};
```
### 3.5 APATable三线表
```tsx
// components/common/APATable.tsx
import React from 'react';
import './APATable.css';
interface Column {
key: string;
title: string;
width?: number;
align?: 'left' | 'center' | 'right';
render?: (value: any, record: any, index: number) => React.ReactNode;
rowSpan?: number;
}
interface APATableProps {
columns: Column[];
data: Record<string, any>[];
}
export const APATable: React.FC<APATableProps> = ({ columns, data }) => {
return (
<div className="overflow-x-auto">
<table className="apa-table">
<thead>
<tr>
{columns.map(col => (
<th
key={col.key}
style={{ width: col.width, textAlign: col.align || 'left' }}
>
{col.title}
</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, rowIdx) => (
<tr key={rowIdx}>
{columns.map((col, colIdx) => {
// 处理 rowSpan
if (col.rowSpan && rowIdx > 0) return null;
const value = col.render
? col.render(row[col.key], row, rowIdx)
: row[col.key];
return (
<td
key={col.key}
rowSpan={col.rowSpan}
style={{ textAlign: col.align || 'left' }}
className={col.rowSpan ? 'align-middle border-l border-slate-100' : ''}
>
{value}
</td>
);
})}
</tr>
))}
</tbody>
</table>
</div>
);
};
```
```css
/* components/common/APATable.css */
.apa-table {
width: 100%;
border-collapse: collapse;
font-variant-numeric: tabular-nums;
font-size: 14px;
}
.apa-table thead th {
border-top: 2px solid #1e293b;
border-bottom: 1px solid #1e293b;
padding: 8px 12px;
text-align: left;
font-weight: 600;
color: #334155;
}
.apa-table tbody td {
padding: 8px 12px;
border-bottom: 1px solid #e2e8f0;
color: #475569;
}
.apa-table tbody tr:last-child td {
border-bottom: 2px solid #1e293b;
}
```
---
## 4. Zustand Store
```typescript
// store/ssaStore.ts
import { create } from 'zustand';
interface Message {
id: string;
role: 'user' | 'assistant' | 'system';
contentType: 'text' | 'plan' | 'result' | 'trace';
content: any;
createdAt: string;
}
interface SSAState {
// 会话
sessionId: string | null;
sessionTitle: string;
// 数据
dataLoaded: boolean;
dataSchema: object | null;
dataFileName: string;
dataRowCount: number;
// 消息
messages: Message[];
// 执行状态
isPlanning: boolean;
isExecuting: boolean;
currentPlan: object | null;
// Actions
setSession: (id: string, title?: string) => void;
setDataLoaded: (schema: object, fileName: string, rowCount: number) => void;
addMessage: (message: Omit<Message, 'id' | 'createdAt'>) => void;
setPlanning: (planning: boolean) => void;
setExecuting: (executing: boolean) => void;
setCurrentPlan: (plan: object | null) => void;
reset: () => void;
}
export const useSSAStore = create<SSAState>((set, get) => ({
sessionId: null,
sessionTitle: '新会话',
dataLoaded: false,
dataSchema: null,
dataFileName: '',
dataRowCount: 0,
messages: [],
isPlanning: false,
isExecuting: false,
currentPlan: null,
setSession: (id, title = '新会话') => set({ sessionId: id, sessionTitle: title }),
setDataLoaded: (schema, fileName, rowCount) => set({
dataLoaded: true,
dataSchema: schema,
dataFileName: fileName,
dataRowCount: rowCount
}),
addMessage: (message) => set(state => ({
messages: [
...state.messages,
{
...message,
id: crypto.randomUUID(),
createdAt: new Date().toISOString()
}
]
})),
setPlanning: (planning) => set({ isPlanning: planning }),
setExecuting: (executing) => set({ isExecuting: executing }),
setCurrentPlan: (plan) => set({ currentPlan: plan }),
reset: () => set({
sessionId: null,
sessionTitle: '新会话',
dataLoaded: false,
dataSchema: null,
dataFileName: '',
dataRowCount: 0,
messages: [],
isPlanning: false,
isExecuting: false,
currentPlan: null
})
}));
```
---
## 5. API 封装
```typescript
// api/ssaApi.ts
import { apiClient } from '@/common/api/client';
const BASE = '/api/v1/ssa';
export const ssaApi = {
// 会话
createSession: () =>
apiClient.post<{ id: string }>(`${BASE}/sessions`),
getSession: (id: string) =>
apiClient.get(`${BASE}/sessions/${id}`),
listSessions: () =>
apiClient.get(`${BASE}/sessions`),
// 数据上传
uploadData: (sessionId: string, file: File) => {
const formData = new FormData();
formData.append('file', file);
return apiClient.post(`${BASE}/sessions/${sessionId}/upload`, formData, {
headers: { 'Content-Type': 'multipart/form-data' }
});
},
// 生成计划
generatePlan: (sessionId: string, query: string) =>
apiClient.post(`${BASE}/sessions/${sessionId}/plan`, { query }),
// 执行分析(📌 超时 120s
executeAnalysis: (sessionId: string, plan: object) =>
apiClient.post(`${BASE}/sessions/${sessionId}/execute`, { plan }, {
timeout: 120000 // 📌 120s 超时,应对复杂计算
}),
// 下载代码
downloadCode: (sessionId: string, messageId: string) =>
apiClient.get(`${BASE}/sessions/${sessionId}/download-code/${messageId}`, {
responseType: 'blob'
}),
};
```
---
## 6. 模块注册
```typescript
// index.ts
import { lazy } from 'react';
const SSAWorkspace = lazy(() => import('./pages/SSAWorkspace'));
export const ssaRoutes = [
{
path: '/ssa',
element: <SSAWorkspace />,
meta: {
title: '智能统计分析',
icon: 'BarChartOutlined',
requireAuth: true
}
}
];
// 在 moduleRegistry.ts 中注册
// import { ssaRoutes } from './modules/ssa';
// registerModule('ssa', ssaRoutes);
```
---
## 7. 样式规范
### 7.1 颜色系统(与原型对齐)
```css
/* styles/ssa.css */
:root {
--ssa-primary: #3b82f6; /* blue-500 */
--ssa-primary-hover: #2563eb; /* blue-600 */
--ssa-bg: #f8fafc; /* slate-50 */
--ssa-card-bg: #ffffff;
--ssa-border: #e2e8f0; /* slate-200 */
--ssa-text: #334155; /* slate-700 */
--ssa-text-muted: #94a3b8; /* slate-400 */
--ssa-success: #22c55e; /* green-500 */
--ssa-warning: #f59e0b; /* amber-500 */
--ssa-error: #ef4444; /* red-500 */
}
```
### 7.2 动画
```css
/* 渐入动画 */
.ssa-fade-in {
animation: ssaFadeIn 0.4s ease-out forwards;
opacity: 0;
}
/* 上滑动画 */
.ssa-slide-up {
animation: ssaSlideUp 0.4s ease-out forwards;
opacity: 0;
transform: translateY(10px);
}
@keyframes ssaFadeIn {
to { opacity: 1; }
}
@keyframes ssaSlideUp {
to { transform: translateY(0); opacity: 1; }
}
```
---
## 8. 开发检查清单
| 组件 | 功能 | 状态 |
|------|------|------|
| SSASidebar | 导入数据、新建会话、历史列表、数据状态 | ⬜ |
| DataUploader | 拖拽/点击上传,进度显示 | ⬜ |
| MessageList | 消息流滚动,自动滚底 | ⬜ |
| PlanCard | 参数展示、护栏提示、确认/修改按钮 | ⬜ |
| ExecutionTrace | 步骤树、状态图标、连接线 | ⬜ |
| **ExecutionProgress** | **📌 执行中进度动画,缓解等待焦虑** | ⬜ |
| ResultCard | 三线表、图表、解读、下载按钮 | ⬜ |
| APATable | APA 格式表格样式 | ⬜ |
| Zustand Store | 状态管理 | ⬜ |
| API 对接 | 所有接口联调,**超时 120s** | ⬜ |
---
## 9. 关键配置
### 9.1 Axios 全局超时配置
```typescript
// api/client.ts
import axios from 'axios';
export const apiClient = axios.create({
baseURL: '/api',
timeout: 60000, // 默认 60s
headers: {
'Content-Type': 'application/json'
}
});
// 📌 对于 SSA 执行接口,单独设置 120s 超时
// 见 ssaApi.executeAnalysis
```

View File

@@ -0,0 +1,118 @@
# **SSA-Pro V1.2 终极审查与发令报告**
**审查对象:** SSA-Pro MVP 开发计划套件 (v1.2)
**审查时间:** 2026-02-18
**审查结论:** 🟢 **通过 (Green Light)** \- 准予启动开发
**风险等级:** 低 (Low)
## **1\. 核心改进验证 (Verification of Fixes)**
针对上一轮V1.1指出的致命问题V1.2 已完成修复:
| 检查项 | 状态 | 证据 |
| :---- | :---- | :---- |
| **混合数据协议一致性** | ✅ **已修复** | **R指南**: utils/data\_loader.R 增加了 load\_input\_data 函数,支持 OSS 下载。 **后端指南**: RClientService 实现了 buildDataSource 逻辑,自动判断 \<2MB 和 \>2MB。 |
| **OSS 内网穿透** | ✅ **已修复** | **R指南**: Dockerfile 引入了 OSS\_ENDPOINT 环境变量,不再硬编码。 |
| **代码生成可维护性** | ✅ **已修复** | **R指南**: 引入 glue 包和 templates/ 目录,告别了 paste0 硬拼接。 |
| **大样本护栏误杀** | ✅ **已修复** | **R指南**: guardrails.R 增加了 LARGE\_SAMPLE\_THRESHOLD (5000),大样本启用抽样检验。 |
**评价:** 团队响应迅速,核心架构隐患已消除。
## **2\. 深度风险挖掘 (Deep Dive Risks)**
虽然逻辑闭环,但在**高并发**和**运维**层面仍有 4 个隐形深坑:
### **2.1 Node.js 的“内存刺客” (Memory Spike)**
* **位置**DataParserService.ts
* **问题**:使用 xlsx 库读取 Excel。该库会将整个文件读入内存。如果用户并发上传 10 个 20MB 的 ExcelNode.js 内存可能瞬间飙升 500MB+,导致 OOM (Out of Memory)。
* **建议**
* **MVP 阶段**:在 SAE 设置 Node.js 内存上限为 2GB+。
* **长期方案**:改用 exceljs 的流式读取 (Stream Reader),或将解析任务卸载给 Python (Tool C) 处理。
### **2.2 R 服务的“僵尸进程” (Zombie Process)**
* **位置**R Docker
* **问题**tryCatch 虽然捕获了错误,但如果底层的 C++ 库(如 read.csv 读取畸形文件)导致 Segmentation FaultR 进程会直接 Crash 退出。SAE 虽然会重启容器,但会导致该请求直接 502 Bad Gateway且没有明确的错误日志。
* **建议**
* 配置 SAE 的 **Liveness Probe** (存活检查),确保挂掉的容器能被立刻重启。
* 后端 RClientService 增加对 **HTTP 502/504** 的特殊处理,返回给用户:“服务繁忙或数据异常,请稍后重试”。
### **2.3 开发环境的“网络孤岛”**
* **位置**:本地 Docker
* **问题**:开发人员在本地跑 R Docker 时,试图下载阿里云 OSS 的文件。如果公司的 VPN 或网络环境不稳定OSS 下载会超时,导致本地开发受阻。
* **建议**
* 在 tests/fixtures 中,除了 CSV**增加一个模拟的 OSS Downloader**Mock
* 在 data\_loader.R 中增加一个 DEV\_MODE 开关,如果是开发环境且 OSS 不通,允许直接读取本地文件模拟下载。
### **2.4 代码交付的“环境依赖”**
* **位置**:用户下载的 .R 代码
* **问题**:用户下载代码后在自己的电脑运行,大概率会报错 Error: there is no package called 'ggplot2'。
* **建议**
* 在生成的代码头部,自动注入一段 **“依赖检查与安装脚本”**
\# 自动安装依赖
required\_packages \<- c("jsonlite", "ggplot2", "car")
new\_packages \<- required\_packages\[\!(required\_packages %in% installed.packages()\[,"Package"\])\]
if(length(new\_packages)) install.packages(new\_packages)
## **3\. "最后一公里" 优化建议**
### **3.1 增加 "Debug 模式"**
在 POST /execute 接口增加一个 debug: true 参数。
* **效果**R 服务不删除 /tmp 下的中间文件CSV、Plot并返回这些文件的 OSS 地址。
* **价值**:极大方便 QA 和开发人员排查“为什么这张图画不出来”。
### **3.2 强化 P 值展示 (APA 格式)**
目前的 p\_value\_fmt 很好了,但建议补充 **显著性星号**
* **建议**R 返回结果增加 significance\_stars 字段 (\*\*\*, \*\*, \*, ns),前端直接展示,显得更专业。
### **3.3 预埋 "用户反馈" 埋点**
在结果卡片下方增加 👍 / 👎 按钮。
* **价值**:收集用户对 AI 解释Critic的满意度用于后续微调 Prompt。
### **4.1 R 代码生成的维护成本 (High)**
* **现状:使用 `glue` 模板技术。**
* **挑战:你需要维护 两套逻辑 —— 一套是 R Wrapper 里的真实执行逻辑,另一套是 `templates/*.R` 里的字符串模板。**
* **风险:如果修改了 Wrapper 的逻辑(比如换了 `leveneTest` 包),但忘了改模板,用户下载的代码就跑不通了。这需要极强的开发纪律。**
### **4.2 护栏机制的调试 (Medium)**
* **挑战:如何设定正态性检验的阈值?**
* **细节:对于 N=5000 的数据Shapiro 检验极易显著P\<0.05),导致 T 检验被误杀。**
* **对策:需要在 `guardrails.R` 中加入基于样本量的动态策略例如N \> 3000 时跳过正态性检查,直接使用 T 检验,依据中心极限定理)。**
### **4.3 异步交互的体验优化 (Medium)**
* **挑战:后端虽然是同步 API但如果计算耗时 30秒前端用户会焦虑。**
* **对策:`ExecutionProgress` 组件虽然做了模拟进度条,但最好能让后端支持 Server-Sent Events (SSE) 推送真实进度(如“正在进行第 500 次置换...”。MVP 阶段可以暂缓,但 Phase 3 建议补上。**
## **4\. 潜在的其他问题 (Remaining Issues)**
### **5.1 隐私保护的漏网之鱼**
* **问题:`DataParserService` 在提取 Schema 时计算了 Min/Max。**
* **场景如果某列数据是“HIV感染状态”虽然是分类变量但如果某一类只有 1 个人,`uniqueValues` 也会暴露隐私。**
* **建议:在 `DataParserService` 中增加逻辑:如果某列的唯一值计数 \< 5 且总行数 \> 10仅返回 "Masked" 或不返回具体值给 LLM。**
### **5.2 错误信息的“黑盒化”**
* **问题R 报错信息(如 `system is computationally singular`)对用户极其不友好。**
* **建议:建立一个 R 错误码字典。Wrapper 捕获错误后,尽量映射为 `E00x` 业务错误码。前端根据错误码显示“多重共线性警告”等人话,而不是原始报错。**
### **5.3 代码运行环境的一致性**
* **问题:用户下载代码在本地跑,本地没有装 R 包怎么办?**
* **建议:在下载的代码包里,除了 `.R` 文件,最好附带一个 `install_dependencies.R` 脚本,一键安装所有依赖包。**

View File

@@ -0,0 +1,124 @@
# **SSA-Pro 方案深度审查与风险评估报告**
**审查对象:** SSA-Pro MVP 开发计划套件 (v1.1)
**包含文档:** MVP计划总览、任务清单、R服务指南、后端指南、前端指南
**审查时间:** 2026-02-18
**总体评级:** **A- (优秀,但存在关键一致性漏洞)**
## **1\. 🚨 关键一致性漏洞 (Critical Inconsistency)**
**这是当前计划中最大的风险点,必须在开工前修复。**
### **漏洞:混合数据传输协议在 R 指南中缺失**
* **架构定义 (V4.1)**:明确定义了“混合数据传输协议”,即 \<1MB 走 Inline JSON1-20MB 走 OSS 引用。
* **后端指南 (Doc 03\)**:正确实现了 data\_source 逻辑,会发送 OSS Key。
* **R 服务指南 (Doc 02\)**:❌ **严重缺失**
* 在 5.2 Wrapper 实现 示例代码中,依然假设 input$data 直接就是数据对象:
\# 错误代码 (Doc 02\)
df \<- tryCatch(as.data.frame(input$data), ...)
* **后果**:如果后端发来的是 OSS KeyR 服务会直接报错或崩溃,导致 1MB 以上文件无法处理。
### **🛠️ 修正建议**
在 R 服务开发指南中,必须增加一个标准化的 **Data Loader** 工具函数,并在所有 Wrapper 入口处调用:
\# utils/data\_loader.R
load\_input\_data \<- function(input\_json) {
\# 兼容旧协议纯数据和新协议data\_source 对象)
if (\!is.null(input\_json$data\_source)) {
source \<- input\_json$data\_source
if (source$type \== "inline") {
return(as.data.frame(source$content))
} else if (source$type \== "oss") {
\# ⬇️ 必须实现 OSS 下载逻辑
return(download\_and\_read\_oss(source$key))
}
}
\# Fallback
return(as.data.frame(input\_json$data))
}
## **2\. 架构层面的潜在风险 (Architecture Risks)**
### **2.1 R 服务的“单线程阻塞”风险**
* **现状**R 语言是单线程的。Plumber 默认也是同步处理。
* **场景**:假设 SAE 分配了 1 个实例。用户 A 提交了一个需要跑 10 秒的 Bootstrap 任务。此时用户 B 提交了一个简单的 T 检验。
* **后果**:用户 B 的请求会被阻塞 10 秒甚至超时。HTTP 接口没有排队机制,体验极差。
* **建议**
1. **SAE 层面**:设置较激进的扩容策略(并发数 \> 2 即扩容)。
2. **应用层面**:考虑在 Plumber 中使用 future 包将长任务丢到后台 Promise 中运行(虽然这会增加复杂度),或者简单粗暴地在 Docker 容器中启动多个 R 进程(利用 PM2 管理 Rscript。**MVP 阶段建议至少部署 2 个 SAE 实例做负载均衡。**
### **2.2 OSS 内网穿透的配置陷阱**
* **现状**文档强调了使用“VPC 内网 Endpoint”。
* **风险**
* 开发环境(本地 Docker通常不在 VPC 内,无法访问内网 Endpoint。
* 生产环境SAE在 VPC 内,必须访问内网 Endpoint。
* **建议**
* R 代码中的 OSS Endpoint **必须通过环境变量注入**,绝不能硬编码。
* docker-compose.yml (开发) 注入公网 Endpoint。
* SAE 环境变量 (生产) 注入内网 Endpoint。
## **3\. 工程细节的优化建议 (Engineering Improvements)**
### **3.1 代码生成的“可维护性”问题**
* **现状**:在 R 代码中使用 glue 拼接字符串。
* **问题**:将大量的 R 代码(如下载逻辑、绘图逻辑)以字符串形式写在 Wrapper 里,不仅难写,而且**没有语法高亮,容易拼错**。
* **建议**
* 采用 **"模板文件分离"** 策略。
* 在 templates/ 目录下存放完整的 .R 文件(如 t\_test\_template.R在其中用 {{variable}} 占位。
* Wrapper 只负责读取文件并替换变量,不要在代码里写大段字符串。
### **3.2 统计结果的“精度陷阱”**
* **现状**R 返回的 p\_value 是浮点数。
* **问题**JSON 序列化时,极小的 P 值(如 1.23e-16在前端 JavaScript 中可能会显示不友好,或者精度丢失。
* **建议**
* R 服务在返回 JSON 前,建议增加一个格式化后的字段,如 p\_value\_fmt: "\< 0.001",由 R 来决定如何科学计数显示,而不是让前端去猜。
### **3.3 护栏Guardrails的“一刀切”风险**
* **现状**:正态性检验失败 \-\> 自动降级。
* **问题**对于大样本N \> 5000Shapiro-Wilk 检验过于敏感,哪怕极微小的偏态也会 P \< 0.05。但实际上大样本下 T 检验是鲁棒的(中心极限定理)。
* **建议**
* 优化 guardrails.R 逻辑:当 N \> 500 时,放宽正态性检验的阈值,或者直接跳过 Shapiro 检验,改用 Q-Q 图的峰度/偏度判断(虽然这很难自动化)。
* **MVP 修正**:至少加上 if (N \> 5000\) return PASS 的逻辑,避免大样本被错误降级。
## **4\. 测试数据的缺失 (Missing Test Data)**
文档中提到了“端到端测试”,但缺少\*\*“标准测试数据集 (Golden Datasets)”\*\*。
* **问题**:怎么证明你的 T 检验算得是对的?
* **建议**
* 建立一个 tests/fixtures 目录。
* 准备几组 **“标准答案”**
1. normal\_data.csv (R 算出来应该是 P=0.042)
2. skewed\_data.csv (应该触发护栏降级)
3. missing\_data.csv (应该报错或自动剔除)
* 在 CI/CD 流程中,自动跑这几组数据,比对结果是否与标准答案一致。
## **5\. 结论与修正计划**
### **✅ 总体评价**
方案架构设计合理技术栈选型Node+R+Plumber成熟。只要修复数据协议的不一致MVP 成功率极高。
### **📝 需立即执行的修正 (Must-Do)**
1. **修正 Doc 02 (R 开发指南)**
* \[ \] 增加 utils/data\_loader.R 模块,实现混合协议解析。
* \[ \] 修改 Dockerfile 和 Env 配置,支持 OSS\_ENDPOINT 动态注入。
* \[ \] 修改 Wrapper 模板,使用 load\_input\_data() 替代直接读取。
2. **修正 Doc 03 (后端指南)**
* \[ \] 确认 RClientService 发送给 R 的 Payload 结构与 R 端的解析逻辑完全匹配。
3. **补充测试资源**
* \[ \] 准备 3-5 个涵盖边缘情况(空值、单行、非正态)的标准 CSV 文件。
**建议项目经理PM将上述 3 点加入 "Phase 1: 骨架搭建" 的首要任务。**

View File

@@ -0,0 +1,119 @@
# **SSA-Pro 方案深度审查与风险评估报告**
**审查对象:** SSA-Pro V4.1 架构及配套开发计划 (v1.0)
**审查视角:** 系统架构、工程落地、安全性、可维护性
**审查结论:** **总体优秀 (A-)**,架构逻辑清晰,但 R 服务工程化细节存在隐患,需在开发前修正。
## **1\. 架构层面的潜在风险 (Architecture Risks)**
### **1.1 R 服务并发瓶颈 (The Single-Threaded Bottleneck)**
* **现状**:设计采用同步 HTTP (Plumber) 调用 R。
* **问题**R 是**单线程**的。虽然 MVP 限制了数据量,但如果 SAE 实例配置为单进程,当一个 T 检验正在计算(耗时 2秒第二个请求会被阻塞。如果并发量稍大如 20 人同时点击),请求会排队导致超时。
* **建议**
1. **SAE 弹性策略**:必须配置 SAE 的自动扩容策略(例如 CPU \> 60% 或 并发数 \> 5 时扩容)。
2. **Plumber 配置**:在生产环境,建议使用 future 包或在 Docker 中配置多个 R Worker 进程(利用 pm2 或类似工具管理多个 R 进程端口,前端 Nginx 做负载均衡),而不仅仅依赖 SAE 的容器级扩容。
### **1.2 "网络隔离"与"OSS下载"的矛盾**
* **现状**
* 架构文档提到R 容器配置 Egress Deny禁止外网
* 数据协议提到R 服务需要从 OSS 下载大于 1MB 的文件。
* **问题**:如果 OSS 是公网 Endpoint配置 Egress Deny 后 R 服务将**无法下载数据**,导致系统瘫痪。
* **建议**
* 务必使用阿里云 OSS 的 **VPC 内网 Endpoint**oss-cn-beijing-internal.aliyuncs.com
* 网络策略应配置为:**Deny All Public Internet**,但 **Allow VPC Internal Traffic**
### **1.3 临时文件堆积 (Storage Leak)**
* **现状**R 服务会生成临时图片 (tempfile) 和下载 OSS 数据。
* **问题**:文档中未提及**清理策略**。长期运行后,/tmp 目录会爆满,导致 Docker 容器崩溃。
* **建议**
* 在 plumber.R 中添加 on.exit(unlink(tmp\_files)) 逻辑。
* 或者在 Docker 启动脚本中添加定时清理任务Cron
## **2\. R 工程化细节审查 (R Engineering Review)**
### **2.1 代码生成方式过于脆弱 (Fragile Code Gen)**
* **现状**02-R服务开发指南.md 中使用 code\_lines \<- c(..., paste0(...)) 进行字符串拼接。
* **问题**
* 这种硬编码方式非常**难以维护**。一旦需要修改代码模板,很容易拼错引号或括号。
* 生成的代码缺乏缩进和格式化,用户下载后可读性差。
* **建议**
* 引入 **模板引擎**(如 R 的 glue 包或 whisker
* 将代码模板存为独立的 .R 模板文件(如 templates/t\_test.R.template运行时读取并填充参数。
* **强力推荐**:使用 styler 包在生成代码后自动美化格式。
### **2.2 依赖包管理的隐患 (Dependency Hell)**
* **现状**Dockerfile 中通过 install.packages 安装了一堆包。
* **问题**100 个工具可能依赖 50+ 个包。如果不锁定版本,下个月重新构建镜像时,某个包升级(如 ggplot2 API 变动)可能导致整个服务挂掉。
* **建议**
* 使用 **renv** 进行包管理。生成 renv.lock 文件,确保生产环境安装的包版本与开发环境完全一致。
* 或者使用 RStudio Public Package Manager (P3M) 的**快照日期源**(已在指南中提及,需严格执行)。
### **2.3 错误处理的颗粒度**
* **现状**Wrapper 使用 tryCatch 捕获所有错误并返回 message。
* **问题**R 的报错信息(如 object 'x' not found对 LLM 来说可能不够明确,或者包含了系统路径等敏感信息。
* **建议**
* 在 Wrapper 中区分 **"业务错误"**(如列名不存在、数据类型不对)和 **"系统错误"**。
* 对于业务错误,返回特定的 error\_code方便 Planner 进行针对性的参数自愈。
## **3\. 后端逻辑审查 (Backend Review)**
### **3.1 LLM 生成 JSON 的稳定性**
* **现状**:依赖 Prompt 让 LLM 输出 JSON。
* **问题**DeepSeek 虽然强,但偶尔也会输出 Markdown 包裹的 JSON或者 JSON 格式错误(如末尾多逗号)。单纯的 JSON.parse 很容易挂。
* **建议**
* 引入 json-repair 库或使用正则表达式提取 JSON 块。
* 或者使用 **Structured Output (JSON Mode)** 如果模型支持。
* 增加一层 **Schema Validation (Zod)**:在收到 LLM 的 Plan 后,先用 Zod 校验参数结构是否符合 tools\_library 中的定义,如果不符合,直接触发重试,不要发给 R。
### **3.2 数据 Schema 提取的隐私风险**
* **现状**DataParserService 计算数值列的 Min/Max/Mean。
* **问题**对于罕见病或小样本数据N\<10极值Min/Max可能导致患者身份泄露年龄=102岁
* **建议**
* 增加 **隐私阈值**:如果行数 \< 10不计算 Min/Max或者进行模糊化处理如向下取整
## **4\. 前端交互审查 (Frontend Review)**
### **4.1 大图与多图渲染**
* **现状**R 返回 Base64 图片。
* **问题**如果图片很大高DPI或者一次返回 10 张图如相关性矩阵拆解Base64 字符串会极大,导致 HTTP 响应慢,且前端卡顿。
* **建议**
* 对于复杂图表R 服务应将图片上传到 OSS返回 **URL** 而不是 Base64。
* Base64 仅限于 MVP 阶段的小图预览。
### **4.2 长连接与超时**
* **现状**:同步 HTTP超时 60s。
* **问题**:虽然大部分 T 检验很快,但如果是 Bootstrap 或 MCMC很容易超过 60s。前端 Nginx 或浏览器可能会提前断开连接。
* **建议**
* 前端 axios 请求需显式设置 timeout: 120000 (2分钟)。
* 增加 **"正在计算..."** 的动态文案(如:正在进行第 500 次置换检验...),这需要 R 支持流式日志输出SSE但在同步架构下较难实现。MVP 阶段至少要有一个假的进度条动画,缓解焦虑。
## **5\. 补充的任务清单 (Missing Items)**
在当前的开发计划中,缺少以下关键任务,建议补充进 Phase 2 或 3
1. **🔍 审计日志查看器**后台管理端ADMIN需要一个界面查看 execution\_logs方便运营人员排查 AI 为什么规划错了。
2. **🧹 自动清理任务**:编写 CronJob定期清理 R 容器的 /tmp 和 OSS 中的临时数据文件24小时过期
3. **📊 资源监控**:在运营看板中增加 R 服务的内存/CPU 监控,防止内存泄漏导致的 OOM。
4. **📚 帮助文档生成**:不仅是 R 代码生成 JSON还需要一个脚本将 R 的注释自动生成前端可见的“工具说明书”,展示在 Chat 的侧边栏或悬停提示中。
## **6\. 最终修订建议**
**请研发团队在开工前,重点落实以下 3 点变更:**
1. **R 代码生成**:放弃 paste0 拼接,改用 glue 模板技术。
2. **网络配置**:确认 SAE 与 OSS 的内网连通性,配置精确的 ACL。
3. **JSON 容错**:后端增加 LLM 输出的 JSON 修复与 Zod 强校验层。
**总评:** 方案非常扎实以上问题属于“精益求精”的工程细节。解决这些问题后SSA-Pro 将具备极高的鲁棒性。

View File

@@ -1,96 +0,0 @@
# SSA - 智能统计分析
> **模块代号:** SSA (Smart Statistical Analysis)
> **开发状态:** ⏳ 规划中
> **商业价值:** ⭐⭐⭐⭐⭐ 刚需
> **独立性:** ⭐⭐⭐⭐
> **优先级:** P2
---
## 📋 模块概述
智能统计分析模块提供3条核心分析路径实现从数据上传到报告导出的完整流程。
---
## 🎯 核心功能3条路径
### 1. 队列研究分析
- 基线特征分析
- 生存分析Kaplan-Meier
- Cox回归
### 2. 预测模型构建
- 变量筛选
- 模型构建Logistic回归、随机森林
- 模型验证ROC曲线
### 3. RCT研究分析
- 随机化检查
- 疗效分析
- 亚组分析
---
## 📂 文档结构
```
SSA-智能统计分析/
├── [AI对接] SSA快速上下文.md # ⏳ 待创建
├── 00-项目概述/
│ └── 01-产品需求文档(PRD).md # ⏳ 待创建
└── README.md # ✅ 当前文档
```
---
## 🔗 依赖的通用能力
- **文档处理引擎** - 数据导入
- **ETL引擎** - 数据预处理
---
## 🏗️ 技术栈
- **R语言** - 统计分析核心
- **Plumber** - R暴露为API
- **Node.js** - 粘合层
---
## 🎯 商业模式
**与ST模块协同售卖**
---
**最后更新:** 2025-11-06
**维护人:** 技术架构师