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:
@@ -1,10 +1,11 @@
|
||||
# AIclinicalresearch 系统当前状态与开发指南
|
||||
|
||||
> **文档版本:** v5.2
|
||||
> **文档版本:** v5.3
|
||||
> **创建日期:** 2025-11-28
|
||||
> **维护者:** 开发团队
|
||||
> **最后更新:** 2026-02-18
|
||||
> **🎉 重大里程碑:**
|
||||
> - **2026-02-18:SSA MVP 开发计划 v1.3 完成!** Brain-Hand架构 + 统计护栏 + HITL + 10工具MVP,准予启动开发
|
||||
> - **2026-02-18:RVW V2.0 Week 3 完成!** 统计验证扩展 + 负号归一化 + 文件格式提示 + 用户体验优化
|
||||
> - **2026-02-18:RVW V2.0 Skills 架构完成!** Skills 核心框架 + 3个 Skill 实现 + ReviewWorker 改造
|
||||
> - **2026-02-17:RVW 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** |
|
||||
|
||||
319
docs/03-业务模块/SSA-智能统计分析/00-模块当前状态与开发指南.md
Normal file
319
docs/03-业务模块/SSA-智能统计分析/00-模块当前状态与开发指南.md
Normal 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: 统计工具 Wrapper(glue 模板) ││
|
||||
│ │ 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 只看 Schema,R 操作真实数据
|
||||
4. ⚠️ **混合数据协议**:< 2MB inline,2-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 骨架搭建
|
||||
162
docs/03-业务模块/SSA-智能统计分析/00-系统设计/PRD SSA-Pro 严谨型智能统计分析模块.md
Normal file
162
docs/03-业务模块/SSA-智能统计分析/00-系统设计/PRD SSA-Pro 严谨型智能统计分析模块.md
Normal 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 Key,R 服务内网下载。
|
||||
|
||||
#### **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)
|
||||
217
docs/03-业务模块/SSA-智能统计分析/00-系统设计/SSA-Pro (V4.1) 统计技能中心架构规范.md
Normal file
217
docs/03-业务模块/SSA-智能统计分析/00-系统设计/SSA-Pro (V4.1) 统计技能中心架构规范.md
Normal 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\. 场景演练:跨模块能力复用**
|
||||
|
||||
### **场景 A:SSA 模块 (标准流程)**
|
||||
|
||||
1. **用户**:在 SSA 界面输入“比较两组血压差异”。
|
||||
2. **Planner**:检索 Registry,命中 `ST_T_TEST_IND`,生成 JSON 参数。
|
||||
3. **Executor**:调用 R 接口,返回图表和报告。
|
||||
|
||||
### **场景 B:IIT 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 即可无缝调用,系统扩展性无限。
|
||||
|
||||
224
docs/03-业务模块/SSA-智能统计分析/00-系统设计/SSA-Pro 严谨型智能统计分析架构设计方案V4.md
Normal file
224
docs/03-业务模块/SSA-智能统计分析/00-系统设计/SSA-Pro 严谨型智能统计分析架构设计方案V4.md
Normal 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 资产,工程边界清晰。
|
||||
* **对于平台**:它建立了极高的数据隐私壁垒和学术严谨性壁垒。
|
||||
|
||||
**这是目前行业内最务实、最可落地的智能统计解决方案。**
|
||||
|
||||
351
docs/03-业务模块/SSA-智能统计分析/02-技术设计/SSA-01 R工具封装标准与前后端数据协议技术规范.md
Normal file
351
docs/03-业务模块/SSA-智能统计分析/02-技术设计/SSA-01 R工具封装标准与前后端数据协议技术规范.md
Normal 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 改造"**。
|
||||
|
||||
* **原则 1:JSON In, JSON Out**。所有工具统一使用 JSON 格式进行输入输出,严禁直接读写本地文件路径(除非是临时的)。
|
||||
* **原则 2:护栏内嵌 (Guardrails Inside)**。统计假设检验(如正态性)必须在 R 代码内部完成,而不是依赖 LLM。
|
||||
* **原则 3:代码自生成 (Self-Generating)**。每个工具必须能“吐出”一份可独立运行的 R 代码片段,用于交付给用户。
|
||||
|
||||
## **2\. R 工具封装标准 (The Wrapper Spec)**
|
||||
|
||||
所有纳入 SSA 体系的 R 工具,必须遵循以下函数签名和文件结构。
|
||||
|
||||
### **2.1 标准函数签名**
|
||||
|
||||
\#' @title SSA Standard Wrapper Interface
|
||||
\#' @param input\_json JSON字符串或列表,包含 data, params, guardrails 等
|
||||
\#' @return JSON字符串,包含 status, results, plots, trace\_log, reproducible\_code
|
||||
run\_ssa\_tool \<- function(input\_json) {
|
||||
\# ...
|
||||
}
|
||||
|
||||
### **2.2 输入结构定义 (Input Schema)**
|
||||
|
||||
后端调用 R API 时,Request Body 将被解析为以下 R List 结构:
|
||||
|
||||
list(
|
||||
\# 1\. 核心数据 (必需)
|
||||
\# 前端/DC模块清洗后的干净数据,通常为数据框的列列表格式
|
||||
data \= list(
|
||||
col\_group \= c("A", "A", "B", "B", ...),
|
||||
col\_val \= c(1.2, 1.3, 2.1, 2.2, ...)
|
||||
),
|
||||
|
||||
\# 2\. 统计参数 (必需)
|
||||
\# 由 LLM 生成,用于控制统计行为
|
||||
params \= list(
|
||||
group\_col \= "col\_group",
|
||||
val\_col \= "col\_val",
|
||||
conf\_level \= 0.95,
|
||||
paired \= FALSE,
|
||||
alternative \= "two.sided"
|
||||
),
|
||||
|
||||
\# 3\. 护栏配置 (可选)
|
||||
\# 控制是否开启强制检查
|
||||
guardrails \= list(
|
||||
check\_normality \= TRUE,
|
||||
check\_variance \= TRUE,
|
||||
auto\_fix \= TRUE \# 若检查失败,是否允许自动降级(如T检验转Wilcoxon)
|
||||
),
|
||||
|
||||
\# 4\. 元信息 (可选)
|
||||
meta \= list(
|
||||
tool\_code \= "ST\_T\_TEST\_IND",
|
||||
user\_id \= "u123",
|
||||
session\_id \= "s456"
|
||||
)
|
||||
)
|
||||
|
||||
### **2.3 输出结构定义 (Output Schema)**
|
||||
|
||||
R 函数必须返回以下结构的 List (最终被 Plumber 序列化为 JSON):
|
||||
|
||||
list(
|
||||
\# 1\. 执行状态
|
||||
status \= "success", \# "success" | "warning" | "error"
|
||||
message \= "执行成功,数据满足正态分布。", \# 给用户看的简短提示
|
||||
|
||||
\# 2\. 统计结果 (结构化)
|
||||
\# 用于前端渲染表格或 LLM 解读
|
||||
results \= list(
|
||||
method \= "Two Sample t-test",
|
||||
statistic \= 2.34, \# t值
|
||||
p\_value \= 0.023,
|
||||
conf\_int \= c(0.5, 2.1),
|
||||
estimate \= c(mean\_x \= 5.1, mean\_y \= 4.2),
|
||||
df \= 18
|
||||
),
|
||||
|
||||
\# 3\. 可视化 (Base64)
|
||||
\# 推荐返回 1-2 张核心图表
|
||||
plots \= list(
|
||||
"data:image/png;base64,iVBORw0K...", \# 图1
|
||||
"data:image/png;base64,..." \# 图2
|
||||
),
|
||||
|
||||
\# 4\. 执行路径日志 (Trace Log)
|
||||
\# 用于前端展示 "执行树"
|
||||
trace\_log \= list(
|
||||
list(step \= "check\_normality", status \= "pass", msg \= "Shapiro-Wilk P=0.23 \> 0.05"),
|
||||
list(step \= "check\_variance", status \= "pass", msg \= "Levene P=0.45 \> 0.05"),
|
||||
list(step \= "main\_test", status \= "done", msg \= "t.test executed")
|
||||
),
|
||||
|
||||
\# 5\. 可复现代码 (Reproducible Code)
|
||||
\# 用户下载的 R 脚本内容
|
||||
reproducible\_code \= "library(ggplot2)\\ndata \<- read.csv('your\_data.csv')..."
|
||||
)
|
||||
|
||||
## **3\. R 脚本开发模板 (Developer Guide)**
|
||||
|
||||
R 工程师请直接复制此模板开发新工具。以 **"独立样本 T 检验"** 为例。
|
||||
|
||||
### **文件名:tools/st\_t\_test\_ind.R**
|
||||
|
||||
library(jsonlite)
|
||||
library(ggplot2)
|
||||
library(car) \# for leveneTest
|
||||
|
||||
\#' @title 独立样本T检验 (Independent Samples T-Test)
|
||||
\#' @description 用于比较两组独立正态分布数据的均值是否存在显著差异。
|
||||
\#' @usage\_context 适用于数值型因变量(Y)和二分类自变量(X)。需满足正态性和方差齐性。
|
||||
\#' @param input\_json 标准输入对象
|
||||
\#' @export
|
||||
run\_tool \<- function(input\_json) {
|
||||
|
||||
\# \--- 0\. 初始化 \---
|
||||
logs \<- list()
|
||||
log\_step \<- function(step, status, msg) {
|
||||
logs \<\<- c(logs, list(list(step=step, status=status, msg=msg)))
|
||||
}
|
||||
|
||||
\# 解析数据
|
||||
df \<- as.data.frame(input\_json$data)
|
||||
p \<- input\_json$params
|
||||
|
||||
\# 构造复现代码 (Header)
|
||||
code\_lines \<- c(
|
||||
"\# \------------------------------------------------",
|
||||
"\# SSA 生成代码: 独立样本 T 检验",
|
||||
"\# \------------------------------------------------",
|
||||
"library(ggplot2)",
|
||||
"library(car)",
|
||||
"",
|
||||
"\# 1\. 加载数据 (请替换为您的本地文件路径)",
|
||||
"df \<- read.csv('your\_data.csv')",
|
||||
""
|
||||
)
|
||||
|
||||
tryCatch({
|
||||
|
||||
\# \--- 1\. 数据预处理 (Statistical Prep) \---
|
||||
\# 强制类型转换
|
||||
df\[\[p$group\_col\]\] \<- as.factor(df\[\[p$group\_col\]\])
|
||||
df\[\[p$val\_col\]\] \<- as.numeric(df\[\[p$val\_col\]\])
|
||||
|
||||
code\_lines \<- c(code\_lines,
|
||||
"\# 2\. 数据预处理",
|
||||
sprintf("df\[\['%s'\]\] \<- as.factor(df\[\['%s'\]\])", p$group\_col, p$group\_col),
|
||||
sprintf("df\[\['%s'\]\] \<- as.numeric(df\[\['%s'\]\])", p$val\_col, p$val\_col)
|
||||
)
|
||||
|
||||
\# \--- 2\. 护栏检查 (Guardrails) \---
|
||||
run\_test \<- "t.test" \# 默认方法
|
||||
|
||||
if (isTRUE(input\_json$guardrails$check\_normality)) {
|
||||
\# 简化的正态性检查 (对每组进行 Shapiro 检验)
|
||||
groups \<- unique(df\[\[p$group\_col\]\])
|
||||
is\_normal \<- TRUE
|
||||
|
||||
for (g in groups) {
|
||||
sub\_data \<- df\[df\[\[p$group\_col\]\] \== g, p$val\_col\]
|
||||
\# 样本量 \< 3 或 \> 5000 不做 shapiro
|
||||
if (length(sub\_data) \>= 3 && length(sub\_data) \<= 5000\) {
|
||||
pval \<- shapiro.test(sub\_data)$p.value
|
||||
if (pval \< 0.05) is\_normal \<- FALSE
|
||||
}
|
||||
}
|
||||
|
||||
if (\!is\_normal) {
|
||||
log\_step("check\_normality", "fail", "数据不满足正态分布 (P\<0.05)")
|
||||
if (isTRUE(input\_json$guardrails$auto\_fix)) {
|
||||
run\_test \<- "wilcox.test"
|
||||
log\_step("auto\_fix", "switch", "自动降级为 Wilcoxon 秩和检验")
|
||||
code\_lines \<- c(code\_lines, "\# 注意:由于数据不满足正态分布,已切换为非参数检验")
|
||||
} else {
|
||||
return(list(status="error", message="数据不满足正态分布,请尝试非参数检验。", trace\_log=logs))
|
||||
}
|
||||
} else {
|
||||
log\_step("check\_normality", "pass", "正态性检验通过")
|
||||
}
|
||||
}
|
||||
|
||||
\# \--- 3\. 核心计算 \---
|
||||
f \<- as.formula(paste(p$val\_col, "\~", p$group\_col))
|
||||
|
||||
if (run\_test \== "t.test") {
|
||||
\# 方差齐性检查
|
||||
var\_pval \<- leveneTest(f, data=df)$\`Pr(\>F)\`\[1\]
|
||||
var\_equal \<- var\_pval \> 0.05
|
||||
res \<- t.test(f, data=df, var.equal=var\_equal)
|
||||
|
||||
log\_step("main\_test", "done", sprintf("T-Test (var.equal=%s)", var\_equal))
|
||||
|
||||
\# 添加代码
|
||||
code\_lines \<- c(code\_lines,
|
||||
"", "\# 3\. 执行 T 检验",
|
||||
sprintf("res \<- t.test(%s \~ %s, data=df, var.equal=%s)", p$val\_col, p$group\_col, var\_equal),
|
||||
"print(res)"
|
||||
)
|
||||
|
||||
} else {
|
||||
res \<- wilcox.test(f, data=df)
|
||||
log\_step("main\_test", "done", "Wilcoxon Test")
|
||||
|
||||
\# 添加代码
|
||||
code\_lines \<- c(code\_lines,
|
||||
"", "\# 3\. 执行 Wilcoxon 检验",
|
||||
sprintf("res \<- wilcox.test(%s \~ %s, data=df)", p$val\_col, p$group\_col),
|
||||
"print(res)"
|
||||
)
|
||||
}
|
||||
|
||||
\# \--- 4\. 绘图 \---
|
||||
plot\_file \<- tempfile(fileext \= ".png")
|
||||
png(plot\_file, width=800, height=600)
|
||||
|
||||
p\_plot \<- ggplot(df, aes\_string(x=p$group\_col, y=p$val\_col, fill=p$group\_col)) \+
|
||||
geom\_boxplot() \+
|
||||
theme\_minimal() \+
|
||||
labs(title="Boxplot Comparison")
|
||||
print(p\_plot)
|
||||
|
||||
dev.off()
|
||||
|
||||
\# 转 Base64
|
||||
plot\_base64 \<- base64enc::base64encode(plot\_file)
|
||||
plot\_base64 \<- paste0("data:image/png;base64,", plot\_base64)
|
||||
|
||||
\# 添加绘图代码
|
||||
code\_lines \<- c(code\_lines,
|
||||
"", "\# 4\. 绘图",
|
||||
"library(ggplot2)",
|
||||
sprintf("ggplot(df, aes(x=%s, y=%s, fill=%s)) \+ geom\_boxplot() \+ theme\_minimal()",
|
||||
p$group\_col, p$val\_col, p$group\_col)
|
||||
)
|
||||
|
||||
\# \--- 5\. 返回结果 \---
|
||||
return(list(
|
||||
status \= "success",
|
||||
message \= "分析完成",
|
||||
results \= list(
|
||||
method \= res$method,
|
||||
statistic \= as.numeric(res$statistic),
|
||||
p\_value \= as.numeric(res$p.value),
|
||||
conf\_int \= if(\!is.null(res$conf.int)) as.numeric(res$conf.int) else NULL
|
||||
),
|
||||
plots \= list(plot\_base64),
|
||||
trace\_log \= logs,
|
||||
reproducible\_code \= paste(code\_lines, collapse="\\n")
|
||||
))
|
||||
|
||||
}, error \= function(e) {
|
||||
return(list(status="error", message=e$message, trace\_log=logs))
|
||||
})
|
||||
}
|
||||
|
||||
## **4\. 前后端通信 API (API Contract)**
|
||||
|
||||
### **4.1 执行统计分析**
|
||||
|
||||
* **URL**: POST /api/v1/ssa/execute
|
||||
* **Content-Type**: application/json
|
||||
* **发起方**: Frontend (用户点击“确认并执行”后)
|
||||
|
||||
**Request Body:**
|
||||
|
||||
{
|
||||
"tool\_code": "ST\_T\_TEST\_IND",
|
||||
"data": {
|
||||
"group": \["A", "A", "B", "B"\],
|
||||
"bmi": \[21.5, 22.1, 25.4, 26.8\]
|
||||
},
|
||||
"params": {
|
||||
"group\_col": "group",
|
||||
"val\_col": "bmi",
|
||||
"conf\_level": 0.95
|
||||
},
|
||||
"guardrails": {
|
||||
"check\_normality": true,
|
||||
"auto\_fix": true
|
||||
}
|
||||
}
|
||||
|
||||
**Response Body (200 OK):**
|
||||
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"status": "success",
|
||||
"message": "分析完成",
|
||||
"results": {
|
||||
"method": "Welch Two Sample t-test",
|
||||
"p\_value": 0.042,
|
||||
"statistic": \-2.31
|
||||
},
|
||||
"plots": \["data:image/png;base64,..."\],
|
||||
"trace\_log": \[
|
||||
{"step": "check\_normality", "status": "pass", "msg": "正态性检验通过"},
|
||||
{"step": "main\_test", "status": "done", "msg": "T-Test executed"}
|
||||
\],
|
||||
"reproducible\_code": "\# SSA 生成代码...\\nlibrary(ggplot2)..."
|
||||
}
|
||||
}
|
||||
|
||||
## **5\. 元数据注册规范 (Metadata Spec)**
|
||||
|
||||
为了让 SSA-Planner (DeepSeek) 能够检索到这个工具,我们需要提取以下 JSON 元数据,存入 pgvector。
|
||||
|
||||
**JSON 结构示例:**
|
||||
|
||||
{
|
||||
"tool\_code": "ST\_T\_TEST\_IND",
|
||||
"name": "独立样本 T 检验",
|
||||
"description": "用于比较两组独立样本的均值差异。基于 t 分布理论。",
|
||||
"usage\_context": "适用于:1. 因变量为连续数值型;2. 自变量为二分类(如性别、分组);3. 数据满足正态分布和方差齐性。",
|
||||
"params\_schema": {
|
||||
"group\_col": {
|
||||
"type": "string",
|
||||
"desc": "分组变量列名,必须只有2个水平"
|
||||
},
|
||||
"val\_col": {
|
||||
"type": "string",
|
||||
"desc": "数值变量列名"
|
||||
}
|
||||
},
|
||||
"guardrails\_supported": \["check\_normality", "check\_variance"\]
|
||||
}
|
||||
|
||||
## **6\. 总结与行动指南**
|
||||
|
||||
1. **R 工程师**:
|
||||
* 请按照 **第 3 节 (R 脚本开发模板)**,先试着封装 1 个工具(如 T 检验)。
|
||||
* 确保 reproducible\_code 生成的代码可以在干净的 RStudio 环境中跑通。
|
||||
* 确保所有 library() 调用都在函数内部或头部声明。
|
||||
2. **后端工程师**:
|
||||
* 在 Node.js 中实现 POST /api/v1/ssa/execute 接口。
|
||||
* 该接口的核心逻辑是:将前端 JSON \-\> 转发给 R Plumber 服务 \-\> 接收 R 响应 \-\> 存入数据库日志 \-\> 返回前端。
|
||||
* **不要在 Node.js 里写任何统计逻辑**,只做“二传手”。
|
||||
3. **前端工程师**:
|
||||
* 根据 API 定义,Mock 一份数据,开始开发“执行路径树”和“代码下载”组件。
|
||||
167
docs/03-业务模块/SSA-智能统计分析/02-技术设计/SSA-02 数据库与向量库 Schema 设计规范.md
Normal file
167
docs/03-业务模块/SSA-智能统计分析/02-技术设计/SSA-02 数据库与向量库 Schema 设计规范.md
Normal 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. **可追溯**:详尽的审计日志为未来的“自我进化”(根据用户反馈优化工具推荐)提供了数据基础。
|
||||
200
docs/03-业务模块/SSA-智能统计分析/02-技术设计/SSA-03 Agent Prompt 与编排流程设计规范.md
Normal file
200
docs/03-业务模块/SSA-智能统计分析/02-技术设计/SSA-03 Agent Prompt 与编排流程设计规范.md
Normal 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 流式输出,减少用户等待的焦虑感。
|
||||
130
docs/03-业务模块/SSA-智能统计分析/02-技术设计/项目开发管理与实施计划.md
Normal file
130
docs/03-业务模块/SSA-智能统计分析/02-技术设计/项目开发管理与实施计划.md
Normal 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 崩溃** | 中 | 服务不可用 | 严格限制上传文件 \< 2MB;SAE 配置内存监控和自动重启。 |
|
||||
| **统计结果解释幻觉** | 低 | 学术误导 | Critic Prompt 强制要求基于 R 输出的 JSON 说话,禁止脑补。 |
|
||||
|
||||
## **5\. 每日工作流建议 (Daily Workflow)**
|
||||
|
||||
建议采用 **"R 优先,前后端跟进"** 的模式:
|
||||
|
||||
1. **R 工程师**:每天封装 2-3 个工具,提交到代码库,并更新 tools\_metadata.json。
|
||||
2. **后端工程师**:更新向量库数据,确保 LLM 能检索到新工具。
|
||||
3. **前端工程师**:针对新工具的特殊参数(如生存分析的时间列),微调卡片展示逻辑。
|
||||
4. **每日站会**:演示昨天封装的工具是否能跑通闭环。
|
||||
478
docs/03-业务模块/SSA-智能统计分析/03-UI设计/智能统计分析V2.html
Normal file
478
docs/03-业务模块/SSA-智能统计分析/03-UI设计/智能统计分析V2.html
Normal 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>
|
||||
209
docs/03-业务模块/SSA-智能统计分析/04-开发计划/00-MVP开发计划总览.md
Normal file
209
docs/03-业务模块/SSA-智能统计分析/04-开发计划/00-MVP开发计划总览.md
Normal 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只看Schema,R服务处理真实数据 |
|
||||
|
||||
### 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 inline,2-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` | 技术架构 |
|
||||
195
docs/03-业务模块/SSA-智能统计分析/04-开发计划/01-任务清单与进度追踪.md
Normal file
195
docs/03-业务模块/SSA-智能统计分析/04-开发计划/01-任务清单与进度追踪.md
Normal 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 检验 Wrapper(ST_T_TEST_IND) | 4h | 含护栏 + glue + 大样本优化 |
|
||||
| ⬜ | 本地 Docker 测试通过 | 2h | |
|
||||
|
||||
### 后端任务
|
||||
|
||||
| 状态 | 任务 | 预估 | 备注 |
|
||||
|------|------|------|------|
|
||||
| ⬜ | 创建 `backend/src/modules/ssa/` 目录结构 | 1h | |
|
||||
| ⬜ | 设计并创建数据库 Schema(Prisma) | 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 | |
|
||||
| ⬜ | 实现单因素 ANOVA(ST_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 | |
|
||||
| ⬜ | 实现会话管理 API(CRUD) | 3h | |
|
||||
| ⬜ | 实现 Brain-Hand 数据隔离逻辑 | 2h | Schema 给 LLM,Data 给 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 U(ST_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
|
||||
|
||||
**昨日完成:**
|
||||
-
|
||||
|
||||
**今日计划:**
|
||||
-
|
||||
|
||||
**阻塞问题:**
|
||||
-
|
||||
1018
docs/03-业务模块/SSA-智能统计分析/04-开发计划/02-R服务开发指南.md
Normal file
1018
docs/03-业务模块/SSA-智能统计分析/04-开发计划/02-R服务开发指南.md
Normal file
File diff suppressed because it is too large
Load Diff
825
docs/03-业务模块/SSA-智能统计分析/04-开发计划/03-后端开发指南.md
Normal file
825
docs/03-业务模块/SSA-智能统计分析/04-开发计划/03-后端开发指南.md
Normal 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. 数据库 Schema(Prisma)
|
||||
|
||||
```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 ToolRetrievalService(RAG 检索)
|
||||
|
||||
```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 PlannerService(AI 规划 + 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 只看 Schema,R 服务处理真实数据**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 数据上传流程 │
|
||||
│ │
|
||||
│ 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"
|
||||
}
|
||||
}
|
||||
```
|
||||
897
docs/03-业务模块/SSA-智能统计分析/04-开发计划/04-前端开发指南.md
Normal file
897
docs/03-业务模块/SSA-智能统计分析/04-开发计划/04-前端开发指南.md
Normal 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 < 0.05, ** P < 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
|
||||
```
|
||||
118
docs/03-业务模块/SSA-智能统计分析/06-开发记录/SSA-Pro V1.2 终极审查与发令报告V3.0.md
Normal file
118
docs/03-业务模块/SSA-智能统计分析/06-开发记录/SSA-Pro V1.2 终极审查与发令报告V3.0.md
Normal 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 的 Excel,Node.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 Fault,R 进程会直接 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` 脚本,一键安装所有依赖包。**
|
||||
|
||||
124
docs/03-业务模块/SSA-智能统计分析/06-开发记录/SSA-Pro 方案深度审查与风险评估报告 V2.0.md
Normal file
124
docs/03-业务模块/SSA-智能统计分析/06-开发记录/SSA-Pro 方案深度审查与风险评估报告 V2.0.md
Normal 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 JSON,1-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 Key,R 服务会直接报错或崩溃,导致 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 \> 5000),Shapiro-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: 骨架搭建" 的首要任务。**
|
||||
119
docs/03-业务模块/SSA-智能统计分析/06-开发记录/SSA-Pro 方案深度审查与风险评估报告.md
Normal file
119
docs/03-业务模块/SSA-智能统计分析/06-开发记录/SSA-Pro 方案深度审查与风险评估报告.md
Normal 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 将具备极高的鲁棒性。
|
||||
@@ -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
|
||||
**维护人:** 技术架构师
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user