From 8137e3cde2f6e115de0e922334ff1d196dc9d502 Mon Sep 17 00:00:00 2001 From: HaHafeng Date: Wed, 18 Feb 2026 21:58:37 +0800 Subject: [PATCH] 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 --- .../00-系统当前状态与开发指南.md | 5 +- .../SSA-智能统计分析/00-模块当前状态与开发指南.md | 319 ++++++ .../00-系统设计/PRD SSA-Pro 严谨型智能统计分析模块.md | 162 +++ .../00-系统设计/SSA-Pro (V4.1) 统计技能中心架构规范.md | 217 ++++ .../SSA-Pro 严谨型智能统计分析架构设计方案V4.md | 224 ++++ .../SSA-01 R工具封装标准与前后端数据协议技术规范.md | 351 ++++++ .../SSA-02 数据库与向量库 Schema 设计规范.md | 167 +++ .../SSA-03 Agent Prompt 与编排流程设计规范.md | 200 ++++ .../02-技术设计/项目开发管理与实施计划.md | 130 +++ .../03-UI设计/智能统计分析V2.html | 478 ++++++++ .../04-开发计划/00-MVP开发计划总览.md | 209 ++++ .../04-开发计划/01-任务清单与进度追踪.md | 195 ++++ .../04-开发计划/02-R服务开发指南.md | 1018 +++++++++++++++++ .../04-开发计划/03-后端开发指南.md | 825 +++++++++++++ .../04-开发计划/04-前端开发指南.md | 897 +++++++++++++++ .../06-开发记录/SSA-Pro V1.2 终极审查与发令报告V3.0.md | 118 ++ .../SSA-Pro 方案深度审查与风险评估报告 V2.0.md | 124 ++ .../06-开发记录/SSA-Pro 方案深度审查与风险评估报告.md | 119 ++ docs/03-业务模块/SSA-智能统计分析/README.md | 96 -- 19 files changed, 5756 insertions(+), 98 deletions(-) create mode 100644 docs/03-业务模块/SSA-智能统计分析/00-模块当前状态与开发指南.md create mode 100644 docs/03-业务模块/SSA-智能统计分析/00-系统设计/PRD SSA-Pro 严谨型智能统计分析模块.md create mode 100644 docs/03-业务模块/SSA-智能统计分析/00-系统设计/SSA-Pro (V4.1) 统计技能中心架构规范.md create mode 100644 docs/03-业务模块/SSA-智能统计分析/00-系统设计/SSA-Pro 严谨型智能统计分析架构设计方案V4.md create mode 100644 docs/03-业务模块/SSA-智能统计分析/02-技术设计/SSA-01 R工具封装标准与前后端数据协议技术规范.md create mode 100644 docs/03-业务模块/SSA-智能统计分析/02-技术设计/SSA-02 数据库与向量库 Schema 设计规范.md create mode 100644 docs/03-业务模块/SSA-智能统计分析/02-技术设计/SSA-03 Agent Prompt 与编排流程设计规范.md create mode 100644 docs/03-业务模块/SSA-智能统计分析/02-技术设计/项目开发管理与实施计划.md create mode 100644 docs/03-业务模块/SSA-智能统计分析/03-UI设计/智能统计分析V2.html create mode 100644 docs/03-业务模块/SSA-智能统计分析/04-开发计划/00-MVP开发计划总览.md create mode 100644 docs/03-业务模块/SSA-智能统计分析/04-开发计划/01-任务清单与进度追踪.md create mode 100644 docs/03-业务模块/SSA-智能统计分析/04-开发计划/02-R服务开发指南.md create mode 100644 docs/03-业务模块/SSA-智能统计分析/04-开发计划/03-后端开发指南.md create mode 100644 docs/03-业务模块/SSA-智能统计分析/04-开发计划/04-前端开发指南.md create mode 100644 docs/03-业务模块/SSA-智能统计分析/06-开发记录/SSA-Pro V1.2 终极审查与发令报告V3.0.md create mode 100644 docs/03-业务模块/SSA-智能统计分析/06-开发记录/SSA-Pro 方案深度审查与风险评估报告 V2.0.md create mode 100644 docs/03-业务模块/SSA-智能统计分析/06-开发记录/SSA-Pro 方案深度审查与风险评估报告.md delete mode 100644 docs/03-业务模块/SSA-智能统计分析/README.md diff --git a/docs/00-系统总体设计/00-系统当前状态与开发指南.md b/docs/00-系统总体设计/00-系统当前状态与开发指南.md index 3476a37f..3caca33d 100644 --- a/docs/00-系统总体设计/00-系统当前状态与开发指南.md +++ b/docs/00-系统总体设计/00-系统当前状态与开发指南.md @@ -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** | diff --git a/docs/03-业务模块/SSA-智能统计分析/00-模块当前状态与开发指南.md b/docs/03-业务模块/SSA-智能统计分析/00-模块当前状态与开发指南.md new file mode 100644 index 00000000..000a478c --- /dev/null +++ b/docs/03-业务模块/SSA-智能统计分析/00-模块当前状态与开发指南.md @@ -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 骨架搭建 diff --git a/docs/03-业务模块/SSA-智能统计分析/00-系统设计/PRD SSA-Pro 严谨型智能统计分析模块.md b/docs/03-业务模块/SSA-智能统计分析/00-系统设计/PRD SSA-Pro 严谨型智能统计分析模块.md new file mode 100644 index 00000000..ef48005b --- /dev/null +++ b/docs/03-业务模块/SSA-智能统计分析/00-系统设计/PRD SSA-Pro 严谨型智能统计分析模块.md @@ -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) \ No newline at end of file diff --git a/docs/03-业务模块/SSA-智能统计分析/00-系统设计/SSA-Pro (V4.1) 统计技能中心架构规范.md b/docs/03-业务模块/SSA-智能统计分析/00-系统设计/SSA-Pro (V4.1) 统计技能中心架构规范.md new file mode 100644 index 00000000..417ccc0c --- /dev/null +++ b/docs/03-业务模块/SSA-智能统计分析/00-系统设计/SSA-Pro (V4.1) 统计技能中心架构规范.md @@ -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\pgvector)\] + Orchestrator\[Skill Orchestrator\Node.js\] + Planner\[Planner / Router\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\; // 符合 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 即可无缝调用,系统扩展性无限。 + diff --git a/docs/03-业务模块/SSA-智能统计分析/00-系统设计/SSA-Pro 严谨型智能统计分析架构设计方案V4.md b/docs/03-业务模块/SSA-智能统计分析/00-系统设计/SSA-Pro 严谨型智能统计分析架构设计方案V4.md new file mode 100644 index 00000000..ec35de1d --- /dev/null +++ b/docs/03-业务模块/SSA-智能统计分析/00-系统设计/SSA-Pro 严谨型智能统计分析架构设计方案V4.md @@ -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 资产,工程边界清晰。 +* **对于平台**:它建立了极高的数据隐私壁垒和学术严谨性壁垒。 + +**这是目前行业内最务实、最可落地的智能统计解决方案。** + diff --git a/docs/03-业务模块/SSA-智能统计分析/02-技术设计/SSA-01 R工具封装标准与前后端数据协议技术规范.md b/docs/03-业务模块/SSA-智能统计分析/02-技术设计/SSA-01 R工具封装标准与前后端数据协议技术规范.md new file mode 100644 index 00000000..9dbc737f --- /dev/null +++ b/docs/03-业务模块/SSA-智能统计分析/02-技术设计/SSA-01 R工具封装标准与前后端数据协议技术规范.md @@ -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 一份数据,开始开发“执行路径树”和“代码下载”组件。 \ No newline at end of file diff --git a/docs/03-业务模块/SSA-智能统计分析/02-技术设计/SSA-02 数据库与向量库 Schema 设计规范.md b/docs/03-业务模块/SSA-智能统计分析/02-技术设计/SSA-02 数据库与向量库 Schema 设计规范.md new file mode 100644 index 00000000..b578b28c --- /dev/null +++ b/docs/03-业务模块/SSA-智能统计分析/02-技术设计/SSA-02 数据库与向量库 Schema 设计规范.md @@ -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. **可追溯**:详尽的审计日志为未来的“自我进化”(根据用户反馈优化工具推荐)提供了数据基础。 \ No newline at end of file diff --git a/docs/03-业务模块/SSA-智能统计分析/02-技术设计/SSA-03 Agent Prompt 与编排流程设计规范.md b/docs/03-业务模块/SSA-智能统计分析/02-技术设计/SSA-03 Agent Prompt 与编排流程设计规范.md new file mode 100644 index 00000000..7be51433 --- /dev/null +++ b/docs/03-业务模块/SSA-智能统计分析/02-技术设计/SSA-03 Agent Prompt 与编排流程设计规范.md @@ -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\`(正态性检查)。 + +\#\#\# 输出要求 +请先在 \ 标签中进行推理,分析变量类型和工具适用性。 +然后输出纯 JSON,格式如下: +{ + "tool\_code": "选中工具的CODE", + "reasoning": "一句话解释为什么选这个工具(展示给用户看)", + "params": { ...根据工具定义的 params\_schema 填写... }, + "guardrails": { "check\_normality": true, "auto\_fix": true } +} + +**User Input Example:** + +"比较性别(Gender)对血压(SBP)的影响。" + +**Model Output Example:** + +\ +用户想要比较 "Gender" (分类变量,通常2组) 对 "SBP" (数值变量) 的影响。 +候选工具中有:ST\_T\_TEST\_IND (独立样本T检验) 和 ST\_CORRELATION (相关性)。 +T检验适用于 分类(2组) vs 数值。相关性适用于 数值 vs 数值。 +因此选择 ST\_T\_TEST\_IND。 +参数映射:group\_col="Gender", val\_col="SBP"。 +需要开启正态性检查。 +\ + +{ + "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 流式输出,减少用户等待的焦虑感。 \ No newline at end of file diff --git a/docs/03-业务模块/SSA-智能统计分析/02-技术设计/项目开发管理与实施计划.md b/docs/03-业务模块/SSA-智能统计分析/02-技术设计/项目开发管理与实施计划.md new file mode 100644 index 00000000..232c3a1b --- /dev/null +++ b/docs/03-业务模块/SSA-智能统计分析/02-技术设计/项目开发管理与实施计划.md @@ -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. **每日站会**:演示昨天封装的工具是否能跑通闭环。 \ No newline at end of file diff --git a/docs/03-业务模块/SSA-智能统计分析/03-UI设计/智能统计分析V2.html b/docs/03-业务模块/SSA-智能统计分析/03-UI设计/智能统计分析V2.html new file mode 100644 index 00000000..4db22226 --- /dev/null +++ b/docs/03-业务模块/SSA-智能统计分析/03-UI设计/智能统计分析V2.html @@ -0,0 +1,478 @@ + + + + + + SSA-Pro 智能统计分析原型 V2.0 + + + + + + + + + + + + + + + +
+ +
+

+ 会话 / 血糖性别差异分析 +

+
+ + +
+
+ + +
+ + +
+
+ +
+
+
+

你好!我是 SSA 智能统计助手。

+

请先上传您的研究数据(Excel 或 CSV),我将为您自动解析数据结构并规划分析方案。

+ + +
+
+ +
+

点击上传数据文件

+

支持 .xlsx, .csv (最大 20MB)

+
+ + + +
+
+
+ + + + + + + + + + + + + +
+ + +
+
+ + +
+
+ + SSA-Pro Generate | 仅供科研参考 + +
+
+
+ + + + \ No newline at end of file diff --git a/docs/03-业务模块/SSA-智能统计分析/04-开发计划/00-MVP开发计划总览.md b/docs/03-业务模块/SSA-智能统计分析/04-开发计划/00-MVP开发计划总览.md new file mode 100644 index 00000000..f28bf356 --- /dev/null +++ b/docs/03-业务模块/SSA-智能统计分析/04-开发计划/00-MVP开发计划总览.md @@ -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` | 技术架构 | diff --git a/docs/03-业务模块/SSA-智能统计分析/04-开发计划/01-任务清单与进度追踪.md b/docs/03-业务模块/SSA-智能统计分析/04-开发计划/01-任务清单与进度追踪.md new file mode 100644 index 00000000..83ac6d55 --- /dev/null +++ b/docs/03-业务模块/SSA-智能统计分析/04-开发计划/01-任务清单与进度追踪.md @@ -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 + +**昨日完成:** +- + +**今日计划:** +- + +**阻塞问题:** +- diff --git a/docs/03-业务模块/SSA-智能统计分析/04-开发计划/02-R服务开发指南.md b/docs/03-业务模块/SSA-智能统计分析/04-开发计划/02-R服务开发指南.md new file mode 100644 index 00000000..81e7654c --- /dev/null +++ b/docs/03-业务模块/SSA-智能统计分析/04-开发计划/02-R服务开发指南.md @@ -0,0 +1,1018 @@ +# SSA-Pro R 服务开发指南 + +> **文档版本:** v1.3 +> **创建日期:** 2026-02-18 +> **最后更新:** 2026-02-18(纳入 V3.0 终极审查建议) +> **目标读者:** R 统计工程师 + +--- + +## 1. 项目结构 + +``` +r-statistics-service/ +├── Dockerfile +├── renv.lock # 📌 包版本锁定文件 +├── .Rprofile # renv 初始化 +├── plumber.R # Plumber 入口 +├── tools/ # 统计工具目录 +│ ├── ST_T_TEST_IND.R +│ ├── ST_T_TEST_PAIRED.R +│ ├── ST_ANOVA_ONE.R +│ └── ... +├── templates/ # 📌 代码模板目录(glue) +│ ├── t_test.R.template +│ ├── anova.R.template +│ └── ... +├── utils/ +│ ├── data_loader.R # 🆕 混合数据协议加载器 +│ ├── guardrails.R # 护栏函数库 +│ ├── code_generator.R # 代码生成工具(使用 glue) +│ ├── result_formatter.R # 🆕 结果格式化(p_value_fmt) +│ └── error_codes.R # 📌 错误码定义 +├── metadata/ # 工具元数据 +│ └── tools.yaml # 所有工具定义 +└── tests/ + ├── test_tools.R # 单元测试 + └── fixtures/ # 🆕 标准测试数据集 + ├── normal_data.csv + ├── skewed_data.csv + └── missing_data.csv +``` + +--- + +## 2. Dockerfile 模板 + +```dockerfile +FROM rocker/r-ver:4.3 + +LABEL maintainer="your-team@example.com" +LABEL version="1.0.0" +LABEL description="SSA-Pro R Statistics Service" + +# 安装系统依赖 +RUN apt-get update && apt-get install -y \ + libcurl4-openssl-dev \ + libssl-dev \ + libxml2-dev \ + && rm -rf /var/lib/apt/lists/* + +# 📌 安装 renv(包管理工具) +RUN R -e "install.packages('renv', repos='https://cloud.r-project.org/')" + +WORKDIR /app + +# 📌 先复制 renv.lock,利用 Docker 缓存 +COPY renv.lock renv.lock +COPY .Rprofile .Rprofile + +# 📌 使用 renv 恢复依赖(版本锁定) +RUN R -e "renv::restore()" + +# 复制应用代码 +COPY . . + +EXPOSE 8080 + +# 🆕 OSS 配置通过环境变量注入(开发/生产环境不同) +ENV OSS_ENDPOINT="" +ENV OSS_ACCESS_KEY_ID="" +ENV OSS_ACCESS_KEY_SECRET="" +ENV OSS_BUCKET="" + +# 📌 启动前清理临时文件 +CMD ["R", "-e", "unlink(list.files('/tmp', full.names=TRUE), recursive=TRUE); plumber::plumb('plumber.R')$run(host='0.0.0.0', port=8080)"] +``` + +### 2.2 环境变量配置(🆕 开发/生产差异) + +```yaml +# docker-compose.yml (本地开发) +services: + ssa-r-service: + build: . + ports: + - "8080:8080" + environment: + - OSS_ENDPOINT=oss-cn-beijing.aliyuncs.com # 公网 + - OSS_ACCESS_KEY_ID=${OSS_ACCESS_KEY_ID} + - OSS_ACCESS_KEY_SECRET=${OSS_ACCESS_KEY_SECRET} + - OSS_BUCKET=ssa-data-bucket +``` + +```yaml +# SAE 环境变量 (生产) +OSS_ENDPOINT: oss-cn-beijing-internal.aliyuncs.com # 🆕 VPC 内网 +OSS_ACCESS_KEY_ID: ****** +OSS_ACCESS_KEY_SECRET: ****** +OSS_BUCKET: ssa-data-bucket +``` + +> **重要**:OSS Endpoint 绝不能硬编码,必须通过环境变量注入。本地开发用公网,SAE 生产用内网。 + +### 2.1 renv.lock 示例 + +```json +{ + "R": { + "Version": "4.3.0", + "Repositories": [ + { + "Name": "CRAN", + "URL": "https://cloud.r-project.org" + } + ] + }, + "Packages": { + "plumber": { "Package": "plumber", "Version": "1.2.1", "Source": "Repository" }, + "jsonlite": { "Package": "jsonlite", "Version": "1.8.8", "Source": "Repository" }, + "ggplot2": { "Package": "ggplot2", "Version": "3.4.4", "Source": "Repository" }, + "glue": { "Package": "glue", "Version": "1.7.0", "Source": "Repository" }, + "styler": { "Package": "styler", "Version": "1.10.2", "Source": "Repository" }, + "dplyr": { "Package": "dplyr", "Version": "1.1.4", "Source": "Repository" }, + "tidyr": { "Package": "tidyr", "Version": "1.3.0", "Source": "Repository" }, + "base64enc": { "Package": "base64enc", "Version": "0.1-3", "Source": "Repository" }, + "yaml": { "Package": "yaml", "Version": "2.3.8", "Source": "Repository" }, + "car": { "Package": "car", "Version": "3.1-2", "Source": "Repository" } + } +} +``` + +> **重要**:每次添加新依赖后,执行 `renv::snapshot()` 更新 lock 文件。 + +--- + +## 3. Plumber 入口文件 + +```r +# plumber.R +library(plumber) +library(jsonlite) + +# 加载工具模块 +tools_dir <- "tools" +tool_files <- list.files(tools_dir, pattern = "\\.R$", full.names = TRUE) +for (f in tool_files) source(f) + +# 加载公共函数 +source("utils/data_loader.R") # 🆕 混合数据协议 +source("utils/guardrails.R") +source("utils/code_generator.R") +source("utils/result_formatter.R") # 🆕 结果格式化 + +#* @apiTitle SSA-Pro R Statistics Service +#* @apiDescription 严谨型统计分析 R 引擎 + +#* 健康检查 +#* @get /health +function() { + list( + status = "ok", + timestamp = Sys.time(), + version = "1.0.0" + ) +} + +#* 执行统计工具 +#* @post /api/v1/skills/ +#* @param tool_code:str 工具代码 +#* @serializer unboxedJSON +function(req, tool_code) { + tryCatch({ + # 解析请求体 + input <- jsonlite::fromJSON(req$postBody, simplifyVector = FALSE) + + # 🆕 Debug 模式:保留临时文件用于排查 + debug_mode <- isTRUE(input$debug) + + # 动态调用工具函数 + func_name <- paste0("run_", tolower(tool_code)) + + if (!exists(func_name, mode = "function")) { + return(list( + status = "error", + message = paste("Unknown tool:", tool_code) + )) + } + + result <- do.call(func_name, list(input)) + + # 🆕 Debug 模式:附加临时文件路径 + if (debug_mode && !is.null(result$tmp_files)) { + result$debug_files <- result$tmp_files + message("[DEBUG] 临时文件已保留: ", paste(result$tmp_files, collapse = ", ")) + } + + return(result) + + }, error = function(e) { + # 🆕 使用友好错误映射 + return(map_r_error(e$message)) + }) +} +``` + +--- + +## 4. 错误码定义 + +```r +# utils/error_codes.R +# 📌 结构化错误码,便于 LLM 自愈 + +ERROR_CODES <- list( + # 业务错误(可被 Planner 修复) + E001_COLUMN_NOT_FOUND = list( + code = "E001", + type = "business", + message_template = "列名 '{col}' 在数据中不存在", + user_hint = "请检查变量名是否拼写正确" # 🆕 用户友好提示 + ), + E002_TYPE_MISMATCH = list( + code = "E002", + type = "business", + message_template = "列 '{col}' 类型应为 {expected},实际为 {actual}", + user_hint = "该列包含非数值数据,请检查数据格式" + ), + E003_INSUFFICIENT_GROUPS = list( + code = "E003", + type = "business", + message_template = "分组变量 '{col}' 应有 {expected} 个水平,实际有 {actual} 个", + user_hint = "分组变量的取值个数不符合要求" + ), + E004_SAMPLE_TOO_SMALL = list( + code = "E004", + type = "business", + message_template = "样本量 {n} 不足,至少需要 {min_required}", + user_hint = "数据量太少,无法进行统计分析" + ), + + # 🆕 统计计算错误(用户友好映射) + E005_SINGULAR_MATRIX = list( + code = "E005", + type = "business", + message_template = "矩阵计算异常: {details}", + user_hint = "变量之间可能存在多重共线性,建议移除高度相关的变量" + ), + E006_CONVERGENCE_FAILED = list( + code = "E006", + type = "business", + message_template = "模型未能收敛: {details}", + user_hint = "算法未能找到稳定解,可能需要调整参数或检查数据" + ), + E007_VARIANCE_ZERO = list( + code = "E007", + type = "business", + message_template = "变量 '{col}' 方差为零", + user_hint = "该列的所有值都相同,无法进行比较" + ), + + # 系统错误(需人工介入) + E100_INTERNAL_ERROR = list( + code = "E100", + type = "system", + message_template = "内部错误: {details}", + user_hint = "系统繁忙,请稍后重试" + ), + E101_PACKAGE_MISSING = list( + code = "E101", + type = "system", + message_template = "缺少依赖包: {package}", + user_hint = "请联系管理员" + ) +) + +# 🆕 R 原始错误到错误码的映射字典 +R_ERROR_MAPPING <- list( + "system is computationally singular" = "E005_SINGULAR_MATRIX", + "did not converge" = "E006_CONVERGENCE_FAILED", + "constant" = "E007_VARIANCE_ZERO" +) + +# 构造错误响应(🆕 含用户友好提示) +make_error <- function(error_def, ...) { + params <- list(...) + msg <- error_def$message_template + for (name in names(params)) { + msg <- gsub(paste0("\\{", name, "\\}"), as.character(params[[name]]), msg) + } + return(list( + status = "error", + error_code = error_def$code, + error_type = error_def$type, + message = msg, + user_hint = error_def$user_hint # 🆕 前端可直接展示 + )) +} + +# 🆕 尝试将 R 原始错误映射为友好错误码 +map_r_error <- function(raw_error_msg) { + for (pattern in names(R_ERROR_MAPPING)) { + if (grepl(pattern, raw_error_msg, ignore.case = TRUE)) { + error_key <- R_ERROR_MAPPING[[pattern]] + return(make_error(ERROR_CODES[[error_key]], details = raw_error_msg)) + } + } + # 无法映射,返回通用内部错误 + return(make_error(ERROR_CODES$E100_INTERNAL_ERROR, details = raw_error_msg)) +} +``` + +--- + +## 5. 🆕 混合数据协议加载器 + +> **核心问题**:Node.js 发送的 Payload 可能是 inline JSON(小数据)或 OSS Key(大数据),R 服务必须统一处理。 + +```r +# utils/data_loader.R +# 🆕 混合数据协议:自动识别 inline 数据 vs OSS key + +library(httr) +library(jsonlite) + +# 🆕 开发模式开关(本地无法访问 OSS 时启用) +DEV_MODE <- Sys.getenv("DEV_MODE", "false") == "true" + +# 从环境变量获取 OSS 配置(开发/生产差异化) +get_oss_config <- function() { + list( + endpoint = Sys.getenv("OSS_ENDPOINT", ""), + access_key_id = Sys.getenv("OSS_ACCESS_KEY_ID", ""), + access_key_secret = Sys.getenv("OSS_ACCESS_KEY_SECRET", ""), + bucket = Sys.getenv("OSS_BUCKET", ""), + mock_data_dir = Sys.getenv("OSS_MOCK_DIR", "tests/fixtures") # 🆕 Mock 目录 + ) +} + +# 统一数据加载入口 +load_input_data <- function(input) { + # 检查输入结构 + if (is.null(input$data_source)) { + stop(make_error(ERROR_CODES$E100_INTERNAL_ERROR, + details = "请求缺少 data_source 字段")) + } + + source_type <- input$data_source$type # "inline" | "oss" + + if (source_type == "inline") { + # 📌 方式1:内联 JSON 数据(< 2MB) + return(as.data.frame(input$data_source$data)) + + } else if (source_type == "oss") { + # 📌 方式2:从 OSS 下载(2MB - 20MB) + return(load_from_oss(input$data_source$oss_key)) + + } else { + stop(make_error(ERROR_CODES$E100_INTERNAL_ERROR, + details = paste("未知的 data_source.type:", source_type))) + } +} + +# 从 OSS 下载数据(🆕 支持 DEV_MODE Mock) +load_from_oss <- function(oss_key) { + config <- get_oss_config() + + # 🆕 开发模式:从本地 fixtures 读取 Mock 数据 + if (DEV_MODE) { + mock_file <- file.path(config$mock_data_dir, basename(oss_key)) + if (file.exists(mock_file)) { + message(glue("[DEV_MODE] 使用本地 Mock 文件: {mock_file}")) + return(read.csv(mock_file, stringsAsFactors = FALSE)) + } else { + # 回退到 normal_data.csv + fallback <- file.path(config$mock_data_dir, "normal_data.csv") + message(glue("[DEV_MODE] Mock 文件不存在,使用默认: {fallback}")) + return(read.csv(fallback, stringsAsFactors = FALSE)) + } + } + + if (config$endpoint == "") { + stop(make_error(ERROR_CODES$E100_INTERNAL_ERROR, + details = "OSS_ENDPOINT 环境变量未配置")) + } + + # 构造签名 URL(简化版,生产应使用 SDK) + url <- sprintf("https://%s.%s/%s", + config$bucket, config$endpoint, oss_key) + + # 下载到临时文件 + temp_file <- tempfile(fileext = ".csv") + on.exit(unlink(temp_file)) # 确保清理 + + tryCatch({ + response <- GET(url, + add_headers( + Authorization = generate_oss_signature(config, "GET", oss_key) + ), + write_disk(temp_file, overwrite = TRUE)) + + if (status_code(response) != 200) { + stop(make_error(ERROR_CODES$E100_INTERNAL_ERROR, + details = paste("OSS 下载失败:", status_code(response)))) + } + + return(read.csv(temp_file, stringsAsFactors = FALSE)) + + }, error = function(e) { + stop(make_error(ERROR_CODES$E100_INTERNAL_ERROR, + details = paste("OSS 网络错误:", e$message))) + }) +} + +# OSS 签名生成(简化版) +generate_oss_signature <- function(config, method, object_key) { + # TODO: 完整 OSS V4 签名实现 + # MVP 阶段可使用阿里云 R SDK 或预签名 URL + paste0("OSS ", config$access_key_id, ":", "SIGNATURE_PLACEHOLDER") +} +``` + +### 5.1 后端 Payload 格式规范 + +Node.js `RClientService` 发送给 R 的 Payload 格式: + +```typescript +// 小数据(< 2MB):inline 模式 +{ + "data_source": { + "type": "inline", + "data": [ + { "group": "A", "value": 10.5 }, + { "group": "B", "value": 12.3 } + ] + }, + "params": { + "group_var": "group", + "value_var": "value" + } +} + +// 大数据(2MB - 20MB):OSS 模式 +{ + "data_source": { + "type": "oss", + "oss_key": "sessions/abc123/data.csv" + }, + "params": { + "group_var": "group", + "value_var": "value" + } +} +``` + +--- + +## 6. 🆕 结果格式化工具 + +```r +# utils/result_formatter.R +# 🆕 统计结果格式化,确保 p 值显示规范 + +# 格式化 p 值(符合 APA 规范) +format_p_value <- function(p) { + if (is.na(p)) return(NA) + + if (p < 0.001) { + return("< 0.001") + } else { + return(sprintf("%.3f", p)) + } +} + +# 构建标准化结果(包含 p_value_fmt) +make_result <- function(p_value, statistic, method, ...) { + list( + p_value = p_value, + p_value_fmt = format_p_value(p_value), # 🆕 前端直接展示 + statistic = statistic, + method = method, + ... + ) +} + +# 格式化置信区间 +format_ci <- function(lower, upper, digits = 2) { + sprintf("[%.${digits}f, %.${digits}f]", lower, upper) +} + +# 格式化效应量 +format_effect_size <- function(value, type = "d") { + interpretation <- "" + if (type == "d") { # Cohen's d + if (abs(value) < 0.2) interpretation <- "微小" + else if (abs(value) < 0.5) interpretation <- "小" + else if (abs(value) < 0.8) interpretation <- "中等" + else interpretation <- "大" + } + list( + value = round(value, 3), + interpretation = interpretation + ) +} +``` + +--- + +## 7. 工具 Wrapper 标准模板(使用 glue) + +### 5.1 代码模板文件 + +```r +# templates/t_test.R.template +# SSA-Pro 自动生成代码 +# 工具: {tool_name} +# 时间: {timestamp} +# ================================ + +# 🆕 自动安装依赖(用户本地运行时自动检测) +required_packages <- c("ggplot2", "car") +new_packages <- required_packages[!(required_packages %in% installed.packages()[,"Package"])] +if(length(new_packages)) {{ + message("正在安装缺失的依赖包: ", paste(new_packages, collapse = ", ")) + install.packages(new_packages, repos = "https://cloud.r-project.org") +}} + +library(ggplot2) + +# 数据准备 +df <- read.csv("your_data.csv") +group_var <- "{group_var}" +value_var <- "{value_var}" + +# 正态性检验 +{normality_code} + +# {method_name} +result <- {test_code} +print(result) + +# 可视化 +ggplot(df, aes(x = {group_var}, y = {value_var})) + + geom_boxplot(fill = "#3b82f6", alpha = 0.6) + + theme_minimal() + + labs(title = "Distribution of {value_var} by {group_var}") +``` + +### 5.2 Wrapper 实现(使用 glue) + +```r +# tools/ST_T_TEST_IND.R +# 独立样本 T 检验 + +library(glue) +source("utils/error_codes.R") + +run_st_t_test_ind <- function(input) { + # ===== 初始化 ===== + logs <- c() + log_add <- function(msg) { logs <<- c(logs, paste0("[", Sys.time(), "] ", msg)) } + tmp_files <- c() # 📌 追踪临时文件 + + # 📌 确保退出时清理临时文件 + on.exit({ + if (length(tmp_files) > 0) { + unlink(tmp_files) + } + }, add = TRUE) + + # ===== 🆕 数据加载(混合协议) ===== + log_add("开始加载输入数据") + df <- tryCatch( + load_input_data(input), # 🆕 统一入口,自动处理 inline/OSS + error = function(e) { + log_add(paste("数据加载失败:", e$message)) + return(NULL) + } + ) + + if (is.null(df)) { + return(make_error(ERROR_CODES$E100_INTERNAL_ERROR, details = "数据加载失败")) + } + log_add(glue("数据加载成功: {nrow(df)} 行, {ncol(df)} 列")) + + p <- input$params + guardrails_cfg <- input$guardrails + + group_var <- p$group_variable + value_var <- p$value_variable + + # 📌 业务错误检查 + if (!(group_var %in% names(df))) { + return(make_error(ERROR_CODES$E001_COLUMN_NOT_FOUND, col = group_var)) + } + if (!(value_var %in% names(df))) { + return(make_error(ERROR_CODES$E001_COLUMN_NOT_FOUND, col = value_var)) + } + + groups <- unique(df[[group_var]]) + if (length(groups) != 2) { + return(make_error(ERROR_CODES$E003_INSUFFICIENT_GROUPS, + col = group_var, expected = 2, actual = length(groups))) + } + + # ===== 护栏检查 ===== + normality_code <- "" + method_used <- "t.test" + + if (isTRUE(guardrails_cfg$check_normality)) { + log_add("执行正态性检验") + use_nonparam <- FALSE + + for (g in groups) { + vals <- df[df[[group_var]] == g, value_var] + if (length(vals) >= 3 && length(vals) <= 5000) { + sw_test <- shapiro.test(vals) + normality_code <- paste0(normality_code, + glue("shapiro.test(df[df${group_var} == '{g}', '{value_var}'])\n")) + if (sw_test$p.value < 0.05) { + use_nonparam <- TRUE + log_add(glue("组[{g}] Shapiro-Wilk P = {round(sw_test$p.value, 4)} < 0.05, 拒绝正态性")) + } + } + } + + if (use_nonparam && isTRUE(guardrails_cfg$auto_fix)) { + log_add("触发降级: T-Test -> Wilcoxon") + return(run_st_wilcoxon(input)) + } + } + + # ===== 核心计算 ===== + log_add("执行 T 检验") + g1_vals <- df[df[[group_var]] == groups[1], value_var] + g2_vals <- df[df[[group_var]] == groups[2], value_var] + + result <- t.test(g1_vals, g2_vals, var.equal = FALSE) + + # ===== 生成图表 ===== + log_add("生成箱线图") + plot_base64 <- generate_boxplot(df, group_var, value_var, tmp_files) + + # ===== 📌 使用 glue 生成代码 ===== + template <- readLines("templates/t_test.R.template", warn = FALSE) + template_str <- paste(template, collapse = "\n") + + reproducible_code <- glue(template_str, + tool_name = "独立样本 T 检验", + timestamp = Sys.time(), + group_var = group_var, + value_var = value_var, + normality_code = if (nchar(normality_code) > 0) normality_code else "# 未执行正态性检验", + method_name = result$method, + test_code = glue("t.test(df[df${group_var} == '{groups[1]}', '{value_var}'], + df[df${group_var} == '{groups[2]}', '{value_var}'], + var.equal = FALSE)") + ) + + # 📌 使用 styler 格式化代码(可选) + # reproducible_code <- styler::style_text(reproducible_code) + + # ===== 返回结果 ===== + log_add("分析完成") + + return(list( + status = "success", + message = "分析完成", + results = list( + method = result$method, + statistic = unbox(as.numeric(result$statistic)), + p_value = unbox(as.numeric(result$p.value)), + p_value_fmt = format_p_value(result$p.value), # 🆕 格式化 p 值 + conf_int = as.numeric(result$conf.int), + estimate = as.numeric(result$estimate), + group_stats = list( + list(group = groups[1], n = length(g1_vals), mean = mean(g1_vals), sd = sd(g1_vals)), + list(group = groups[2], n = length(g2_vals), mean = mean(g2_vals), sd = sd(g2_vals)) + ) + ), + plots = list(plot_base64), + trace_log = logs, + reproducible_code = as.character(reproducible_code) + )) +} + +# 📌 辅助函数(带临时文件追踪) +generate_boxplot <- function(df, group_var, value_var, tmp_files_ref) { + library(ggplot2) + library(base64enc) + + p <- ggplot(df, aes_string(x = group_var, y = value_var)) + + geom_boxplot(fill = "#3b82f6", alpha = 0.6) + + theme_minimal() + + labs(title = paste("Distribution of", value_var, "by", group_var)) + + tmp_file <- tempfile(fileext = ".png") + tmp_files_ref <- c(tmp_files_ref, tmp_file) # 追踪 + + ggsave(tmp_file, p, width = 6, height = 4, dpi = 100) + + base64_str <- base64encode(tmp_file) + unlink(tmp_file) # 立即清理 + + return(paste0("data:image/png;base64,", base64_str)) +} +``` + +--- + +## 8. 护栏函数库 + +```r +# utils/guardrails.R + +# 🆕 大样本优化阈值 +LARGE_SAMPLE_THRESHOLD <- 5000 + +# 正态性检验(🆕 大样本优化) +check_normality <- function(values, alpha = 0.05) { + n <- length(values) + + # 样本量过小 + if (n < 3) { + return(list(passed = TRUE, reason = "样本量过小,跳过正态性检验", skipped = TRUE)) + } + + # 🆕 大样本优化:N > 5000 时使用抽样检验 + if (n > LARGE_SAMPLE_THRESHOLD) { + # 抽取 1000 个样本进行检验 + set.seed(42) # 保证可重复性 + sampled_values <- sample(values, 1000) + test <- shapiro.test(sampled_values) + passed <- test$p.value >= alpha + + return(list( + passed = passed, + p_value = test$p.value, + reason = glue("大样本(N={n})抽样检验,{if (passed) '满足正态性' else '不满足正态性'}"), + sampled = TRUE, + sample_size = 1000 + )) + } + + # 常规检验 + test <- shapiro.test(values) + passed <- test$p.value >= alpha + + return(list( + passed = passed, + p_value = test$p.value, + reason = if (passed) "满足正态性" else "不满足正态性", + sampled = FALSE + )) +} + +# 方差齐性检验 (Levene) +check_homogeneity <- function(df, group_var, value_var, alpha = 0.05) { + library(car) + + formula <- as.formula(paste(value_var, "~", group_var)) + test <- leveneTest(formula, data = df) + p_val <- test$`Pr(>F)`[1] + passed <- p_val >= alpha + + return(list( + passed = passed, + p_value = p_val, + reason = if (passed) "方差齐性满足" else "方差不齐性" + )) +} + +# 样本量检验 +check_sample_size <- function(n, min_required = 30) { + passed <- n >= min_required + return(list( + passed = passed, + n = n, + reason = if (passed) "样本量充足" else paste0("样本量不足, 需要至少 ", min_required) + )) +} +``` + +--- + +## 9. API 请求/响应规范 + +### 9.1 🆕 请求格式(混合协议) + +**方式 1:Inline 数据(< 2MB)** +```json +{ + "data_source": { + "type": "inline", + "data": [ + {"Gender": "Male", "GLU": 5.8, "Age": 45}, + {"Gender": "Female", "GLU": 5.1, "Age": 38} + ] + }, + "params": { + "group_variable": "Gender", + "value_variable": "GLU" + }, + "guardrails": { + "check_normality": true, + "auto_fix": true + } +} +``` + +**方式 2:OSS 数据(2MB - 20MB)** +```json +{ + "data_source": { + "type": "oss", + "oss_key": "sessions/abc123/data.csv" + }, + "params": { + "group_variable": "Gender", + "value_variable": "GLU" + }, + "guardrails": { + "check_normality": true, + "auto_fix": true + } +} +``` + +### 9.2 成功响应(🆕 含 p_value_fmt) + +```json +{ + "status": "success", + "message": "分析完成", + "results": { + "method": "Welch Two Sample t-test", + "statistic": 2.345, + "p_value": 0.021, + "p_value_fmt": "0.021", + "conf_int": [0.12, 1.28], + "group_stats": [ + {"group": "Male", "n": 78, "mean": 5.8, "sd": 0.9}, + {"group": "Female", "n": 72, "mean": 5.1, "sd": 0.7} + ] + }, + "plots": ["data:image/png;base64,..."], + "trace_log": [ + "[2026-02-18 10:30:01] 数据加载成功: 150 行, 5 列", + "[2026-02-18 10:30:01] 执行正态性检验", + "[2026-02-18 10:30:02] 执行 T 检验", + "[2026-02-18 10:30:02] 分析完成" + ], + "reproducible_code": "# SSA-Pro 自动生成代码\n..." +} +``` + +> **p_value_fmt 说明**: +> - p >= 0.001: 保留 3 位小数,如 "0.021" +> - p < 0.001: 显示 "< 0.001" +> - 前端应直接使用 `p_value_fmt` 展示,避免重复格式化 + +### 7.3 错误响应(📌 含结构化错误码) + +```json +{ + "status": "error", + "error_code": "E001", + "error_type": "business", + "message": "列名 'invalid_col' 在数据中不存在", + "trace_log": [ + "[2026-02-18 10:30:01] 开始解析输入数据", + "[2026-02-18 10:30:01] 错误: 列名 'invalid_col' 在数据中不存在" + ] +} +``` + +> **错误类型说明**: +> - `business`:业务错误,Planner 可尝试自动修复参数后重试 +> - `system`:系统错误,需人工介入 + +--- + +## 8. MVP 10 个工具清单 + +| 序号 | 工具代码 | 文件名 | 主要函数 | 护栏 | +|------|---------|--------|---------|------| +| 1 | ST_T_TEST_IND | ST_T_TEST_IND.R | `run_st_t_test_ind()` | 正态性 | +| 2 | ST_T_TEST_PAIRED | ST_T_TEST_PAIRED.R | `run_st_t_test_paired()` | 正态性 | +| 3 | ST_ANOVA_ONE | ST_ANOVA_ONE.R | `run_st_anova_one()` | 正态性+方差齐性 | +| 4 | ST_CHI_SQUARE | ST_CHI_SQUARE.R | `run_st_chi_square()` | 期望频数 | +| 5 | ST_FISHER | ST_FISHER.R | `run_st_fisher()` | 无 | +| 6 | ST_WILCOXON | ST_WILCOXON.R | `run_st_wilcoxon()` | 无 | +| 7 | ST_MANN_WHITNEY | ST_MANN_WHITNEY.R | `run_st_mann_whitney()` | 无 | +| 8 | ST_CORRELATION | ST_CORRELATION.R | `run_st_correlation()` | 正态性(决定Pearson/Spearman) | +| 9 | ST_LINEAR_REG | ST_LINEAR_REG.R | `run_st_linear_reg()` | 残差正态性 | +| 10 | ST_DESCRIPTIVE | ST_DESCRIPTIVE.R | `run_st_descriptive()` | 无 | + +--- + +## 9. 本地开发流程 + +### 8.1 构建镜像 + +```bash +cd r-statistics-service +docker build -t ssa-r-service:dev . +``` + +### 8.2 运行容器 + +```bash +docker run -d -p 8080:8080 --name ssa-r-dev ssa-r-service:dev +``` + +### 8.3 测试健康检查 + +```bash +curl http://localhost:8080/health +``` + +### 8.4 测试工具调用 + +```bash +curl -X POST http://localhost:8080/api/v1/skills/ST_T_TEST_IND \ + -H "Content-Type: application/json" \ + -d '{ + "data": [ + {"Gender": "Male", "GLU": 5.8}, + {"Gender": "Male", "GLU": 6.1}, + {"Gender": "Female", "GLU": 5.0}, + {"Gender": "Female", "GLU": 5.2} + ], + "params": { + "group_variable": "Gender", + "value_variable": "GLU" + }, + "guardrails": { + "check_normality": true, + "auto_fix": true + } + }' +``` + +--- + +## 10. 工具元数据格式 + +```yaml +# metadata/tools.yaml +tools: + - code: ST_T_TEST_IND + name: 独立样本 T 检验 + version: "1.0.0" + category: 假设检验 + description: | + 用于比较两个独立组的均值是否存在显著差异。 + 适用场景:比较男性vs女性的血糖水平、实验组vs对照组的疗效等。 + usage_context: | + - 两组独立样本比较 + - 连续型数值变量 + - 样本量建议 >= 30 + params_schema: + type: object + required: + - group_variable + - value_variable + properties: + group_variable: + type: string + description: 分组变量名(应为分类变量,仅含两个水平) + value_variable: + type: string + description: 检验变量名(应为数值型) + guardrails: + - check_normality + - check_homogeneity +``` + +--- + +## 11. 常见问题 + +### Q1: 护栏检查失败后如何处理? + +如果 `auto_fix = true`,R 服务会自动降级到适当的非参数方法。如果 `auto_fix = false`,则返回警告但仍执行原方法。 + +### Q2: 如何添加新工具? + +1. 在 `tools/` 目录创建 `ST_NEW_TOOL.R` +2. 实现 `run_st_new_tool(input)` 函数 +3. 在 `metadata/tools.yaml` 添加元数据 +4. 执行后端脚本导入到 pgvector + +### Q3: 图表生成失败怎么办? + +检查 `plots` 字段是否为空数组。R 服务不会因图表失败而中断整个分析,但会在 `trace_log` 中记录错误。 + +### Q4: 如何添加新的 R 包依赖? + +1. 在 R 控制台执行 `install.packages("new_package")` +2. 执行 `renv::snapshot()` 更新 `renv.lock` +3. 提交 `renv.lock` 到版本控制 +4. 重新构建 Docker 镜像 + +### Q5: 临时文件清理策略是什么? + +- **代码层面**:使用 `on.exit(unlink(tmp_files))` 确保函数退出时清理 +- **容器层面**:Docker 启动时清理 `/tmp` +- **运维层面**:SAE 配置定时任务,每日清理 24 小时前的临时文件 diff --git a/docs/03-业务模块/SSA-智能统计分析/04-开发计划/03-后端开发指南.md b/docs/03-业务模块/SSA-智能统计分析/04-开发计划/03-后端开发指南.md new file mode 100644 index 00000000..38f8eced --- /dev/null +++ b/docs/03-业务模块/SSA-智能统计分析/04-开发计划/03-后端开发指南.md @@ -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; + guardrails: Record; + }) { + 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 { + 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(); + + 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; +``` + +--- + +## 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(); + 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。 + +### 输出要求 +请先在 标签中进行推理,分析变量类型和工具适用性。 +然后输出纯 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" + } +} +``` diff --git a/docs/03-业务模块/SSA-智能统计分析/04-开发计划/04-前端开发指南.md b/docs/03-业务模块/SSA-智能统计分析/04-开发计划/04-前端开发指南.md new file mode 100644 index 00000000..4de5ccc3 --- /dev/null +++ b/docs/03-业务模块/SSA-智能统计分析/04-开发计划/04-前端开发指南.md @@ -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; + 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 = ({ + 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 ( + + 分析方案确认 + {plan.tool_name} + + } + styles={{ header: { background: '#f8fafc' } }} + > + {/* 变量映射 */} +
+ {Object.entries(plan.params).map(([key, value]) => ( +
+
+ {key.replace(/_/g, ' ')} +
+
+ {String(value)} + + {getColumnInfo(String(value))} + +
+
+ ))} +
+ + {/* 护栏提示 */} + {plan.guardrails.check_normality && ( + } + showIcon + message="统计护栏 (自动执行)" + description={ +
    +
  • Shapiro-Wilk 正态性检验
  • +
  • Levene 方差齐性检验
  • + {plan.guardrails.auto_fix && ( +
  • + ⚠️ 若正态性检验失败,将自动降级为 Wilcoxon 秩和检验 +
  • + )} +
+ } + className="mb-4" + /> + )} + + {/* 操作按钮 */} +
+ + +
+
+ ); +}; +``` + +### 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 = ({ steps }) => { + const getIcon = (status: TraceStep['status']) => { + switch (status) { + case 'success': + return ; + case 'warning': + return ; + case 'error': + return ; + case 'running': + return ; + default: + return
; + } + }; + + return ( +
+
+ 执行路径 + {steps.every(s => s.status === 'success') && ( + + 完成 + + )} +
+ +
+ {/* 连接线 */} +
+ + {steps.map((step, idx) => ( +
+
+ {getIcon(step.status)} +
+
+ + {step.label} + + {step.detail && ( +
+ + {step.detail} + + {step.subLabel && ( + {step.subLabel} + )} +
+ )} +
+
+ ))} +
+
+ ); +}; + +// 使用示例 +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 = ({ + 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 ( +
+
+ + 正在执行统计分析 +
+ + + + + {PROGRESS_MESSAGES[messageIndex]} + + + + 复杂计算可能需要 10-30 秒,请耐心等待... + +
+ ); +}; +``` + +### 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 = ({ + 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: () => ( + + {/* 🆕 直接使用 R 服务返回的格式化值 */} + {result.p_value_fmt} + {result.p_value < 0.01 ? ' **' : result.p_value < 0.05 ? ' *' : ''} + + ), + rowSpan: tableData.length + }, + ]; + + return ( +
+ {/* Header */} +
+ 分析结果报告 + + 基于 {result.method} + +
+ + {/* 1. 统计表格 */} +
+
+
+ 表 1. 组间差异比较 (三线表) +
+ + + + + Note: {result.method.includes('Wilcoxon') ? 'IQR = Interquartile Range' : 'SD = Standard Deviation'}; + * P < 0.05, ** P < 0.01. + +
+ + {/* 2. 图表 */} + {plots.length > 0 && ( +
+
+
+ 图 1. 可视化结果 +
+ +
+ )} + + {/* 3. 方法与解读 */} + {interpretation && ( +
+
+
+ 方法与结果解读 +
+
+
+ )} + + {/* 4. 资产交付 */} +
+ + 资产交付 + + + + + +
+
+ ); +}; +``` + +### 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[]; +} + +export const APATable: React.FC = ({ columns, data }) => { + return ( +
+ + + + {columns.map(col => ( + + ))} + + + + {data.map((row, 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 ( + + ); + })} + + ))} + +
+ {col.title} +
+ {value} +
+
+ ); +}; +``` + +```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) => void; + setPlanning: (planning: boolean) => void; + setExecuting: (executing: boolean) => void; + setCurrentPlan: (plan: object | null) => void; + reset: () => void; +} + +export const useSSAStore = create((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: , + 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 +``` diff --git a/docs/03-业务模块/SSA-智能统计分析/06-开发记录/SSA-Pro V1.2 终极审查与发令报告V3.0.md b/docs/03-业务模块/SSA-智能统计分析/06-开发记录/SSA-Pro V1.2 终极审查与发令报告V3.0.md new file mode 100644 index 00000000..d46283ab --- /dev/null +++ b/docs/03-业务模块/SSA-智能统计分析/06-开发记录/SSA-Pro V1.2 终极审查与发令报告V3.0.md @@ -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` 脚本,一键安装所有依赖包。** + diff --git a/docs/03-业务模块/SSA-智能统计分析/06-开发记录/SSA-Pro 方案深度审查与风险评估报告 V2.0.md b/docs/03-业务模块/SSA-智能统计分析/06-开发记录/SSA-Pro 方案深度审查与风险评估报告 V2.0.md new file mode 100644 index 00000000..4a34c005 --- /dev/null +++ b/docs/03-业务模块/SSA-智能统计分析/06-开发记录/SSA-Pro 方案深度审查与风险评估报告 V2.0.md @@ -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: 骨架搭建" 的首要任务。** \ No newline at end of file diff --git a/docs/03-业务模块/SSA-智能统计分析/06-开发记录/SSA-Pro 方案深度审查与风险评估报告.md b/docs/03-业务模块/SSA-智能统计分析/06-开发记录/SSA-Pro 方案深度审查与风险评估报告.md new file mode 100644 index 00000000..b3b9039a --- /dev/null +++ b/docs/03-业务模块/SSA-智能统计分析/06-开发记录/SSA-Pro 方案深度审查与风险评估报告.md @@ -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 将具备极高的鲁棒性。 \ No newline at end of file diff --git a/docs/03-业务模块/SSA-智能统计分析/README.md b/docs/03-业务模块/SSA-智能统计分析/README.md deleted file mode 100644 index dc13b8a9..00000000 --- a/docs/03-业务模块/SSA-智能统计分析/README.md +++ /dev/null @@ -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 -**维护人:** 技术架构师 - - - - - - - - - - - - - - - - - - - - - - - - - -