Files
AIclinicalresearch/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_MVP开发计划_V1.0.md
HaHafeng 8be741cd52 docs(dc/tool-c): Complete Tool C MVP planning and TODO list
Summary:
- Update Tool C MVP Development Plan (V1.3)
  * Clarify Python execution as core feature
  * Add 15 real medical data cleaning scenarios (basic/medium/advanced)
  * Enhance System Prompt with 10 Few-shot examples
  * Discover existing Python service (extraction_service)
  * Update to extend existing service instead of rebuilding
- Create Tool C MVP Development TODO List
  * 3-week plan with 30 tasks (Day 1-15)
  * 4 core milestones with clear acceptance criteria
  * Daily checklist and risk management
  * Detailed task breakdown for each day

Key Changes:
- Python service: Extend existing extraction_service instead of new setup
- Test scenarios: 15 scenarios (5 basic + 5 medium + 5 advanced)
- Success criteria: Basic >90%, Medium >80%, Advanced >60%, Total >80%
- Development time: Reduced from 3 weeks to 2 weeks (reuse infrastructure)

Status: Planning complete, ready to start Day 1 development
2025-12-06 11:00:44 +08:00

68 KiB
Raw Blame History

工具C - 科研数据编辑器 MVP开发计划

文档版本: V1.0 (务实快速验证版)
创建日期: 2025-12-06
计划周期: 3周15个工作日
核心策略: 用最小成本验证核心假设,快速失败优于完美计划
状态: 待启动


⚠️ 开发前必读:严格遵守现有架构与规范

🔴 不要重复造轮子!复用平台能力

本项目已有完整的3层架构体系和平台基础设施所有代码必须复用现有能力

平台基础层( 已完成,直接使用)

服务 导入方式 用途 文档
存储服务 import { storage } from '@/common/storage' 文件上传下载 必须使用
日志系统 import { logger } from '@/common/logging' 标准化日志 必须使用
缓存服务 import { cache } from '@/common/cache' 分布式缓存 必须使用
异步任务 import { jobQueue } from '@/common/jobs' 长时间任务 必须使用
数据库 import { prisma } from '@/config/database' 数据库操作 必须使用
LLM能力 import { LLMFactory } from '@/common/llm' LLM调用 必须使用

云原生开发规范( 强制执行)

详细文档docs/04-开发规范/08-云原生开发规范.md

核心要求:

  • 文件存储:使用storage.upload(),不要用fs.writeFile()
  • Session管理:存数据库,不要用Map<sessionId, data>
  • 日志输出:使用logger.info(),不要用console.log()
  • 数据库连接:使用全局prisma实例,不要new PrismaClient()
  • LLM调用:使用LLMFactory.getLLM(),不要自己集成
  • 禁止本地文件存储Excel直接从内存解析
  • 禁止内存缓存Map用数据库或cache服务
  • 禁止硬编码配置:使用环境变量

代码文件夹结构( 参考tool-b

后端

backend/src/modules/dc/tool-c/        ← 已存在
├── services/                          ← 业务逻辑
├── controllers/                       ← HTTP控制器
├── routes/                            ← 路由定义
└── utils/                             ← 工具函数

前端

frontend-v2/src/modules/dc/pages/tool-c/  ← 已存在

常见错误示例( 严禁)

// ❌ 错误1自己实现Session管理
const sessions = new Map<string, any>();  // 违反规范!

// ❌ 错误2本地文件存储
fs.writeFileSync('./uploads/file.xlsx', buffer);  // 违反规范!

// ❌ 错误3不用logger
console.log('User uploaded file');  // 违反规范!

// ❌ 错误4新建Prisma实例
const prisma = new PrismaClient();  // 违反规范!

// ❌ 错误5自己集成LLM
import Anthropic from '@anthropic-ai/sdk';  // 违反规范!

正确示例( 必须)参考本文档Day 1-5的代码示例


📋 目录


一、MVP核心目标

1.1 核心假设验证

我们需要验证的3个核心假设

假设 验证方式 成功标准 失败后果
H1: AI能生成高质量Pandas代码并成功执行 15个真实场景测试 总体成功率 > 80% MVP失败改用代码模板库
H2: Python代码执行环境稳定可靠 复杂场景测试 高级场景成功率 > 60% 简化为批处理模式
H3: 左表格+右AI的交互模式好用 用户体验测试 用户能独立完成任务 重新设计UI交互
H4: 性能可接受含Python执行 性能测试 端到端 < 20秒 优化Python执行或改批处理

⚠️ 核心价值主张:

  • AI生成代码 + 真实执行 + 表格刷新是工具C的差异化价值
  • 如果只生成代码不执行用户还不如直接用ChatGPT
  • Python执行环境是MVP的技术核心,不是可选项

1.2 MVP功能范围

MVP必须有的P0

  1. 文件上传10MB限制
  2. 表格展示AG Grid100行预览
  3. AI对话界面右侧侧边栏
  4. AI代码生成DeepSeek-V3
  5. 代码执行Python沙箱
  6. UI锁定机制AI处理时表格只读
  7. AST安全检查
  8. 表格自动刷新
  9. 导出Excel

MVP明确不做的

  • 手动编辑单元格专注AI能力
  • 撤销/回滚(节省内存)
  • Apache Arrow先用JSON
  • Redis会话用进程内存
  • 样式保留(直接覆盖)
  • 操作审计日志
  • 协同编辑

1.3 MVP交付物

最终演示场景:

用户上传 "lung_cancer_patients.xlsx" (5000行 × 20列)
  ↓
左侧显示数据表格右侧AI助手准备就绪
  ↓
用户对AI说"把年龄大于60的患者标记为老年组"
  ↓
AI生成代码 → 展示预操作卡片 → 用户确认 → 执行成功
  ↓
表格自动刷新,新增"age_group"列
  ↓
用户继续说:"删除所有缺失患者ID的行"
  ↓
AI执行 → 表格刷新显示删除了23行
  ↓
用户点击"导出"下载处理后的Excel

成功标准:

  • 整个流程 < 2分钟完成
  • AI代码一次性执行成功
  • 用户无需看文档就能操作

二、技术架构方案(务实版)

2.1 总体架构(简化版)- ⚠️ 重要:复用平台能力

┌─────────────────────────────────────────────────────────┐
│                    前端 (React)                          │
│  frontend-v2/src/modules/dc/pages/tool-c/               │
│  ┌──────────────────────┐  ┌────────────────────────┐  │
│  │ AG Grid (70%)        │  │ AI Chat Sidebar (30%) │  │
│  │ • 展示100行数据      │  │ • 自然语言输入         │  │
│  │ • 只读模式切换       │  │ • 预操作卡片           │  │
│  │ • 锁定遮罩           │  │ • 消息历史             │  │
│  └──────────────────────┘  └────────────────────────┘  │
└─────────────────────────────────────────────────────────┘
              ↕ REST API (JSON)
┌─────────────────────────────────────────────────────────┐
│     Node.js后端 (backend/src/modules/dc/tool-c/)        │
│  • 文件上传复用storage服务✅                         │
│  • 会话管理存数据库不用内存Map✅                   │
│  • LLM集成复用LLMFactory✅                           │
│  • 日志记录复用logger✅                              │
│  • 异步任务复用jobQueue✅                            │
└─────────────────────────────────────────────────────────┘
              ↕ HTTP如需Python执行
┌─────────────────────────────────────────────────────────┐
│    Python微服务可选扩展文档处理引擎                │
│  • DataFrame管理存数据库不用内存                   │
│  • exec()代码执行                                        │
│  • AST静态安全检查                                       │
│  • JSON序列化返回                                        │
└─────────────────────────────────────────────────────────┘

⚠️ 云原生架构强制要求:

  • 复用storage服务import { storage } from '@/common/storage'
  • 复用logger服务import { logger } from '@/common/logging'
  • 复用cache服务import { cache } from '@/common/cache'
  • 复用LLMFactoryimport { LLMFactory } from '@/common/llm'
  • 复用prisma实例import { prisma } from '@/config/database'
  • Session存数据库不用内存Map违反规范
  • 禁止本地文件存储Excel直接从内存解析
  • 禁止新建Prisma实例:使用全局实例

关键决策:

  • 不用Apache ArrowJSON够快100行 ≈ 20KB
  • Session存数据库:符合云原生规范,支持多实例
  • 不做撤销:节省开发时间,用户重新上传即可
  • 不保留样式直接生成新Excel

2.2 技术栈选型( 复用现有技术栈)

前端frontend-v2

{
  "framework": "React 19 + TypeScript 5 (已有)",
  "table": "AG Grid Community (免费版)",
  "ui": "Ant Design 5 (已有)",
  "state": "React Query v5 (已有)",
  "http": "axios (已有)",
  "routing": "React Router DOM v6 (已有)"
}

Node.js后端backend

{
  "framework": "Fastify v4 (已有)",
  "llm": "✅ 复用 LLMFactory (平台通用能力层)",
  "storage": "✅ 复用 storage服务 (平台基础层)",
  "logging": "✅ 复用 logger服务 (平台基础层)",
  "cache": "✅ 复用 cache服务 (平台基础层)",
  "database": "✅ 复用 prisma全局实例 (平台基础层)",
  "session": "✅ PostgreSQL (dc_tool_c_sessions表)",
  "excel": "xlsx 库(内存解析)",
  "validation": "Joi"
}

Python微服务⚠️ 核心功能,扩展现有服务)

决策 系统已有Python微服务FastAPI需扩展代码执行功能

📦 现有Python服务已完成

  • extraction_serviceFastAPI + PyMuPDF + Pandas + openpyxl
  • 端口8000已运行
  • 功能PDF/Docx/Txt文档提取、语言检测
  • 集成Node.js通过ExtractionClient调用
  • 依赖Pandas、openpyxl、chardet、langdetect已安装

🔧 工具C需要的新功能

功能 现有服务 需求 方案
Pandas代码执行 不支持 核心 新增API端点 /api/dc/execute
Excel上传 不支持 需要 复用MultiPart上传
DataFrame管理 不支持 会话 新增Session管理
AST代码检查 不支持 必须 新增AST模块
Excel导出 不支持 需要 使用openpyxl已安装

⚠️ 不需要重复开发:

  • 不需要新建Python项目
  • 不需要重新安装Pandas/openpyxl已安装
  • 不需要重新配置FastAPI已运行
  • 不需要重新写Node.js调用逻辑ExtractionClient已存在

MVP扩展方案

# extraction_service/services/dc_executor.py新增
import pandas as pd
import ast
from typing import Dict, Any

def validate_code(code: str) -> Dict[str, Any]:
    """AST静态检查安全验证"""
    try:
        tree = ast.parse(code)
        # 禁止import os、sys、subprocess等
        for node in ast.walk(tree):
            if isinstance(node, ast.Import):
                for alias in node.names:
                    if alias.name in ['os', 'sys', 'subprocess', 'socket']:
                        return {'valid': False, 'error': f'禁止导入{alias.name}'}
        return {'valid': True}
    except Exception as e:
        return {'valid': False, 'error': str(e)}

def execute_pandas_code(data: list, code: str) -> Dict[str, Any]:
    """执行Pandas代码⭐核心功能"""
    try:
        # 1. 安全检查
        validation = validate_code(code)
        if not validation['valid']:
            return {'success': False, 'error': validation['error']}
        
        # 2. 加载数据
        df = pd.DataFrame(data)
        
        # 3. 执行代码注入df和pd
        exec(code, {'df': df, 'pd': pd})
        
        # 4. 返回结果前100行
        return {
            'success': True,
            'data': df.head(100).to_dict('records'),
            'totalRows': len(df),
            'totalCols': len(df.columns),
            'columns': df.columns.tolist()
        }
    except Exception as e:
        return {'success': False, 'error': str(e)}
# extraction_service/main.py扩展
from services.dc_executor import execute_pandas_code, validate_code

@app.post("/api/dc/execute")
async def execute_code(
    data: List[Dict],
    code: str
):
    """工具C执行Pandas代码"""
    result = execute_pandas_code(data, code)
    return JSONResponse(result)

@app.post("/api/dc/validate")
async def validate_code_endpoint(code: str):
    """工具CAST代码检查"""
    result = validate_code(code)
    return JSONResponse(result)

Node.js调用复用ExtractionClient模式

// backend/src/modules/dc/tool-c/services/PythonExecutorService.ts
import axios from 'axios';

const EXTRACTION_SERVICE_URL = process.env.EXTRACTION_SERVICE_URL || 'http://localhost:8000';

export class PythonExecutorService {
  async executeCode(data: any[], code: string) {
    const response = await axios.post(`${EXTRACTION_SERVICE_URL}/api/dc/execute`, {
      data,
      code
    });
    return response.data;
  }
}

2.3 数据流设计

会话生命周期:

# 1. 用户上传文件
POST /api/tool-c/session/init
{
  file: File,
  userId: string
}

# Node.js: 转发到Python
# Python: 
#   - 读取Excel → DataFrame
#   - 存入内存sessions[sessionId] = df
#   - 返回sessionId + 数据概览

# 2. 用户发送AI指令
POST /api/tool-c/ai/execute
{
  sessionId: string,
  prompt: string
}

# Node.js:
#   - 构建System Prompt包含数据上下文
#   - 调用LLM生成代码
#   - 转发代码到Python

# Python:
#   - AST检查
#   - exec(code)修改df
#   - 返回前100行JSON

# 3. 用户导出
GET /api/tool-c/session/export/{sessionId}

# Python:
#   - df.to_excel(buffer)
#   - 返回二进制流

三、功能优先级矩阵

3.1 P0级必须开发核心闭环

ID 功能 描述 验证目标 工时 负责人
P0-001 文件上传 前端Upload组件 + 10MB限制 能否解析Excel 0.5天 前端
P0-002 Session初始化 后端接收文件Python加载DataFrame 中文列名、GBK编码 1天 后端
P0-003 表格展示 AG Grid展示100行数据 列类型识别、空值高亮 1天 前端
P0-004 AI对话UI 右侧侧边栏,消息列表 聊天交互流畅 0.5天 前端
P0-005 System Prompt构建 包含数据上下文的Prompt AI能理解数据结构 1天 后端
P0-006 AI代码生成 调用DeepSeek-V3生成Pandas代码 成功率>80% 2天 后端
P0-007 AST安全检查 拦截危险代码 import os被拦截 1天 Python
P0-008 代码执行 exec()在沙箱中运行代码 修改DataFrame成功 1天 Python
P0-009 预操作卡片 展示代码,用户确认后执行 用户能看懂代码意图 0.5天 前端
P0-010 表格刷新 执行后自动拉取新数据 前端实时更新 0.5天 前端
P0-011 UI锁定机制 AI处理时表格变灰+遮罩 物理禁止并发操作 0.5天 前端
P0-012 导出Excel 下载处理后的文件 文件完整性 1天 Python

P0小计11.5天

3.2 P1级必须验证可简化实现

ID 功能 MVP简化方案 完整版 工时
P1-001 会话管理 进程内存Map单实例 Redis分布式 0.5天
P1-002 心跳保活 固定10分钟过期不续期 前端心跳续期 0.5天
P1-003 编码检测 chardet自动检测+友好报错 自动转换 1天
P1-004 AI自我修复 失败后重试1次 多次重试+学习 1天
P1-005 快捷模板 3个常用模板年龄分组等 10+模板库 0.5天

P1小计3.5天

3.3 P2级延后或不做

功能 延后原因 何时做
手动编辑单元格 MVP专注AI P2阶段
撤销/回滚 节省内存 P2阶段
Apache Arrow JSON够用 性能不达标时
Redis分布式 单实例够用 横向扩展时
样式保留 复杂度高 P3阶段
操作审计 非核心 P3阶段
多Sheet支持 简化逻辑 P3阶段

四、3周详细开发计划

Week 1基础架构搭建5天

Day 1环境搭建 + 代码结构(⚠️ 参考tool-b结构 + Python环境

任务清单:

  • 创建项目目录结构(参考tool-b

    backend/src/modules/dc/tool-c/           ← 主目录
    ├── services/                             ← 业务逻辑层
    │   ├── SessionService.ts                 ← Session管理存数据库
    │   ├── AICodeService.ts                  ← AI代码生成
    │   ├── PythonExecutorService.ts          ← ⭐ Python代码执行核心
    │   └── DataProcessService.ts             ← 数据处理逻辑
    ├── controllers/                          ← 控制器层
    │   └── ToolCController.ts                ← HTTP请求处理
    ├── routes/                               ← 路由层
    │   └── index.ts                          ← 路由定义
    └── utils/                                ← 工具函数
        ├── codeValidator.ts                  ← AST代码检查
        └── pythonScripts/                    ← ⭐ Python执行脚本
            └── executor.py                   ← Pandas代码执行器
    
    frontend-v2/src/modules/dc/pages/tool-c/  ← 前端(已存在)
    
  • 扩展现有Python服务核心功能

    # 系统已有Python微服务extraction_service只需扩展功能
    cd extraction_service
    
    # 1. 创建DC执行器模块
    cat > services/dc_executor.py << 'EOF'
    import pandas as pd
    import ast
    from typing import Dict, Any, List
    
    def validate_code(code: str) -> Dict[str, Any]:
        """AST静态检查安全验证"""
        try:
            tree = ast.parse(code)
            # 禁止危险导入
            forbidden_modules = ['os', 'sys', 'subprocess', 'socket', 'shutil', 'requests']
            for node in ast.walk(tree):
                if isinstance(node, ast.Import):
                    for alias in node.names:
                        if alias.name in forbidden_modules:
                            return {'valid': False, 'error': f'禁止导入{alias.name}模块'}
                elif isinstance(node, ast.ImportFrom):
                    if node.module in forbidden_modules:
                        return {'valid': False, 'error': f'禁止导入{node.module}模块'}
            return {'valid': True}
        except Exception as e:
            return {'valid': False, 'error': f'代码语法错误: {str(e)}'}
    
    def execute_pandas_code(data: List[Dict], code: str) -> Dict[str, Any]:
        """执行Pandas代码⭐核心功能"""
        try:
            # 1. 安全检查
            validation = validate_code(code)
            if not validation['valid']:
                return {'success': False, 'error': validation['error']}
    
            # 2. 加载数据到DataFrame
            df = pd.DataFrame(data)
    
            # 3. 执行代码注入df和pd隔离环境
            local_env = {'df': df, 'pd': pd}
            exec(code, {'__builtins__': {}}, local_env)
    
            # 4. 获取执行后的df
            df_result = local_env.get('df', df)
    
            # 5. 返回结果前100行避免数据量过大
            return {
                'success': True,
                'data': df_result.head(100).to_dict('records'),
                'totalRows': len(df_result),
                'totalCols': len(df_result.columns),
                'columns': df_result.columns.tolist()
            }
        except Exception as e:
            return {
                'success': False,
                'error': f'代码执行失败: {str(e)}'
            }
    EOF
    
    # 2. 扩展main.py添加DC端点
    # 在main.py末尾添加
    cat >> main.py << 'EOF'
    
    # ==================== DC工具C端点 ====================
    from services.dc_executor import execute_pandas_code, validate_code
    from pydantic import BaseModel
    
    class ExecuteRequest(BaseModel):
        data: List[Dict]
        code: str
    
    class ValidateRequest(BaseModel):
        code: str
    
    @app.post("/api/dc/execute")
    async def dc_execute_code(request: ExecuteRequest):
        """工具C执行Pandas代码"""
        logger.info(f"DC Execute: code length={len(request.code)}, data rows={len(request.data)}")
        result = execute_pandas_code(request.data, request.code)
        return JSONResponse(result)
    
    @app.post("/api/dc/validate")
    async def dc_validate_code(request: ValidateRequest):
        """工具CAST代码检查"""
        logger.info(f"DC Validate: code length={len(request.code)}")
        result = validate_code(request.code)
        return JSONResponse(result)
    EOF
    
  • Node.js调用Python服务PythonExecutorService

    // backend/src/modules/dc/tool-c/services/PythonExecutorService.ts
    import axios from 'axios';
    import { logger } from '@/common/logging';
    
    const EXTRACTION_SERVICE_URL = process.env.EXTRACTION_SERVICE_URL || 'http://localhost:8000';
    
    export interface ExecuteResult {
      success: boolean;
      data?: any[];
      totalRows?: number;
      totalCols?: number;
      columns?: string[];
      error?: string;
    }
    
    export class PythonExecutorService {
      private baseUrl: string;
    
      constructor() {
        this.baseUrl = EXTRACTION_SERVICE_URL;
      }
    
      /**
       * 执行Pandas代码⭐ 核心功能)
       * 复用现有Python微服务添加新端点
       */
      async executeCode(data: any[], code: string): Promise<ExecuteResult> {
        try {
          logger.info('Calling Python executor', { 
            dataRows: data.length, 
            codeLength: code.length 
          });
    
          const response = await axios.post<ExecuteResult>(
            `${this.baseUrl}/api/dc/execute`,
            { data, code },
            { timeout: 30000 } // 30秒超时
          );
    
          logger.info('Python execution success', { 
            totalRows: response.data.totalRows 
          });
    
          return response.data;
        } catch (error) {
          logger.error('Python execution failed', { error });
    
          if (axios.isAxiosError(error) && error.response) {
            throw new Error(error.response.data.error || 'Python execution failed');
          }
    
          throw new Error('无法连接到Python执行服务');
        }
      }
    
      /**
       * AST代码检查执行前验证
       */
      async validateCode(code: string): Promise<{ valid: boolean; error?: string }> {
        try {
          const response = await axios.post(
            `${this.baseUrl}/api/dc/validate`,
            { code },
            { timeout: 5000 }
          );
    
          return response.data;
        } catch (error) {
          logger.error('Code validation failed', { error });
          throw new Error('代码验证失败');
        }
      }
    }
    
    export const pythonExecutorService = new PythonExecutorService();
    
  • 强制检查:确保复用平台服务

    // backend/src/modules/dc/tool-c/services/SessionService.ts
    
    // ✅ 必须导入这些平台服务
    import { storage } from '@/common/storage';
    import { logger } from '@/common/logging';
    import { cache } from '@/common/cache';
    import { prisma } from '@/config/database';
    
    // ❌ 禁止自己实现
    // const sessions = new Map()  ← 违反规范!
    
  • 数据库Schema设计dc_schema

    // prisma/schema.prisma添加到dc_schema
    model DcToolCSession {
      id            String   @id @default(uuid())
      userId        String
      sessionId     String   @unique
      fileName      String
      dataSnapshot  Json     // 存储100行预览数据
      totalRows     Int
      totalCols     Int
      columns       Json     // 列信息
      expiresAt     DateTime // 10分钟过期
      createdAt     DateTime @default(now())
    
      @@schema("dc_schema")
      @@map("dc_tool_c_sessions")
    }
    

验收标准:

  • 文件夹结构与tool-b一致
  • 导入了所有必须的平台服务
  • 没有内存Map、没有本地文件存储

负责人: 后端开发


Day 2Session管理 + 数据加载(⚠️ 存数据库,不用内存)

任务清单:

  • 实现SessionService存数据库,符合云原生规范
    // backend/src/modules/dc/tool-c/services/SessionService.ts
    import { storage } from '@/common/storage';
    import { logger } from '@/common/logging';
    import { prisma } from '@/config/database';
    import * as xlsx from 'xlsx';
    import chardet from 'chardet';
    
    export class SessionService {
      /**
       * 创建Session✅ 存数据库不用内存Map
       */
      async createSession(userId: string, fileBuffer: Buffer, fileName: string): Promise<string> {
        // 1. 检测编码
        const detected = chardet.detect(fileBuffer);
        if (detected.encoding.toLowerCase() !== 'utf-8') {
          throw new Error(`文件编码为${detected.encoding}请转换为UTF-8`);
        }
    
        // 2. 内存解析Excel✅ 云原生:不落盘)
        const workbook = xlsx.read(fileBuffer, { type: 'buffer' });
        const sheet = workbook.Sheets[workbook.SheetNames[0]];
        const data = xlsx.utils.sheet_to_json(sheet);
    
        // 3. 提取列信息
        const columns = Object.keys(data[0] || {}).map(col => ({
          name: col,
          type: typeof data[0][col]
        }));
    
        // 4. Session存数据库✅ 符合云原生规范)
        const sessionId = `session_${Date.now()}_${Math.random().toString(36)}`;
        await prisma.dcToolCSession.create({
          data: {
            sessionId,
            userId,
            fileName,
            dataSnapshot: data.slice(0, 100),  // 只存前100行预览
            totalRows: data.length,
            totalCols: columns.length,
            columns,
            expiresAt: new Date(Date.now() + 10 * 60 * 1000)  // 10分钟过期
          }
        });
    
        // 5. 完整数据上传到OSS✅ 云原生:持久化存储)
        const dataKey = `dc/tool-c/${sessionId}/full-data.json`;
        await storage.uploadBuffer(dataKey, Buffer.from(JSON.stringify(data)));
    
        logger.info('Session created', { sessionId, totalRows: data.length });
    
        return sessionId;
      }
    
      /**
       * 获取Session从数据库读取
       */
      async getSession(sessionId: string) {
        const session = await prisma.dcToolCSession.findUnique({
          where: { sessionId }
        });
    
        if (!session || new Date() > session.expiresAt) {
          throw new Error('Session已过期');
        }
    
        return session;
      }
    }
    

测试用例:

  • 上传UTF-8编码的Excel正常解析
  • 上传GBK编码的Excel被拦截并提示
  • 上传中文列名的Excel无乱码
  • Session存入数据库可查询
  • 10分钟后Session自动过期

验收标准:

  • Session存储在数据库不是内存Map
  • 完整数据存储在OSS
  • 符合云原生开发规范

负责人: Node.js后端


Day 3Node.js BFF + 文件上传

任务清单:

  • 创建Fastify路由
    // backend/src/modules/dc/tool-c/routes/index.ts
    import { FastifyInstance } from 'fastify';
    
    export async function registerToolCRoutes(fastify: FastifyInstance) {
      // 文件上传
      fastify.post('/api/v1/dc/tool-c/session/init', async (req, reply) => {
        const file = await req.file();
    
        // 1. 文件大小检查
        if (file.file.bytesRead > 10 * 1024 * 1024) {
          return reply.code(413).send({
            success: false,
            error: '文件过大请压缩后重试限10MB'
          });
        }
    
        // 2. 转发到Python服务
        const formData = new FormData();
        formData.append('file', file.file, file.filename);
    
        const response = await axios.post('http://localhost:8001/api/python/init', formData);
    
        return {
          success: true,
          sessionId: response.data.sessionId,
          data: response.data
        };
      });
    }
    
  • 集成到主应用
    // backend/src/modules/dc/index.ts
    import { registerToolCRoutes } from './tool-c/routes';
    
    export async function registerDCRoutes(fastify: FastifyInstance) {
      await registerToolBRoutes(fastify);
      await registerToolCRoutes(fastify);  // 新增
    }
    

测试用例:

  • 上传9MB文件成功
  • 上传11MB文件被拦截
  • 并发上传2个文件sessionId不冲突

验收标准:

  • Node.js能正确转发文件到Python
  • 文件大小限制生效

负责人: Node.js后端


Day 4前端框架搭建

任务清单:

  • 创建Tool C页面
    // frontend-v2/src/modules/dc/pages/tool-c/index.tsx
    import { AgGridReact } from 'ag-grid-react';
    import { Upload, Spin } from 'antd';
    
    export default function ToolCEditor() {
      const [sessionId, setSessionId] = useState<string>();
      const [data, setData] = useState<any[]>([]);
      const [columns, setColumns] = useState<any[]>([]);
      const [isLoading, setIsLoading] = useState(false);
    
      const handleUpload = async (file: File) => {
        setIsLoading(true);
        const formData = new FormData();
        formData.append('file', file);
    
        const response = await api.post('/api/v1/dc/tool-c/session/init', formData);
    
        setSessionId(response.data.sessionId);
        setData(response.data.data.preview);
        setColumns(response.data.data.columns);
        setIsLoading(false);
      };
    
      return (
        <div className="h-screen flex">
          {/* 左侧:表格区域 */}
          <div className="flex-1">
            {!sessionId ? (
              <Upload beforeUpload={handleUpload}>
                <Button>上传Excel文件(限10MB</Button>
              </Upload>
            ) : (
              <AgGridReact
                rowData={data}
                columnDefs={columns.map(col => ({
                  field: col.name,
                  headerName: col.name
                }))}
              />
            )}
          </div>
    
          {/* 右侧AI侧边栏占位 */}
          <div className="w-96 border-l bg-white">
            <h3>AI助手</h3>
          </div>
        </div>
      );
    }
    
  • 集成AG Grid
    cd frontend-v2
    npm install ag-grid-react ag-grid-community
    
  • 路由配置
    // frontend-v2/src/modules/dc/routes.tsx
    {
      path: 'tool-c',
      element: <ToolCEditor />
    }
    

测试用例:

  • 上传文件,表格正确显示
  • 中文列名显示正常
  • 空值单元格有视觉提示

验收标准:

  • 用户能看到左右分栏界面
  • 表格能展示100行数据

负责人: 前端开发


Day 5System Prompt构建

任务清单:

  • 创建Prompt模板
    // backend/src/modules/dc/tool-c/prompts/system-prompt.ts
    export function buildSystemPrompt(dataContext: DataContext): string {
      return `# AI医疗数据清洗助手 - 系统角色
    
    

当前数据结构

  • 总行数:${dataContext.totalRows} 行
  • 总列数:${dataContext.totalCols} 列
  • 列名:${dataContext.columns.map(c => c.name).join(', ')}

前5行数据示例

${JSON.stringify(dataContext.headData, null, 2)}

严格规则

  1. 只能使用pandas操作已预导入为pd
  2. 变量名必须是df不要用其他名字
  3. 就地修改df['new'] = ... 或 df.drop(...)
  4. 不要print()、display()等输出
  5. 禁止import os、sys、requests等

Few-shot示例分层难度基础→中等→高级

🟢 基础场景(单步骤操作)

场景1年龄分组

用户:"把年龄大于60的标记为老年组" 代码: df['age_group'] = df['age'].apply(lambda x: '老年组' if pd.notna(x) and x > 60 else '非老年组')

场景2删除缺失

用户:"删除没有患者ID的行" 代码: df.dropna(subset=['patient_id'], inplace=True)

场景3性别编码

用户:"把性别转为数字" 代码: df['gender_code'] = df['gender'].map({'男': 1, '女': 0})

🟡 中等场景(多步骤或跨列逻辑)

场景4计算NLR并分组

用户:"计算中性粒细胞淋巴细胞比值NLR并按2.5分为高低两组" 代码: df['NLR'] = df.apply(lambda row: row['neutrophil'] / row['lymphocyte'] if pd.notna(row['neutrophil']) and pd.notna(row['lymphocyte']) and row['lymphocyte'] > 0 else None, axis=1) df['NLR_group'] = df['NLR'].apply(lambda x: 'High' if pd.notna(x) and x > 2.5 else 'Low')

场景5字符串拆分血压

用户:"把血压列的'120/80'格式拆分成收缩压和舒张压,并判断是否高血压" 代码: df'systolic_bp', 'diastolic_bp' = df['blood_pressure'].str.split('/', expand=True) df['systolic_bp'] = pd.to_numeric(df['systolic_bp'], errors='coerce') df['diastolic_bp'] = pd.to_numeric(df['diastolic_bp'], errors='coerce') df['is_hypertension'] = ((df['systolic_bp'] > 140) | (df['diastolic_bp'] > 90)).astype(int)

场景6时间差计算逻辑验证

用户:"计算住院天数,如果出院日期早于入院日期则标记为异常" 代码: df['admission_date'] = pd.to_datetime(df['admission_date'], errors='coerce') df['discharge_date'] = pd.to_datetime(df['discharge_date'], errors='coerce') df['hospital_days'] = (df['discharge_date'] - df['admission_date']).dt.days df['date_error'] = df['hospital_days'] < 0 df.loc[df['date_error'], 'hospital_days'] = None

🔴 高级场景(分组聚合、时间序列、医学规则)

场景7生存时间计算复杂条件逻辑

用户:"生成生存状态和生存时间如果死亡日期存在则状态为1时间为死亡日期减诊断日期否则状态为0时间为随访截止日期减诊断日期" 代码: df['diagnosis_date'] = pd.to_datetime(df['diagnosis_date'], errors='coerce') df['death_date'] = pd.to_datetime(df['death_date'], errors='coerce') df['followup_end_date'] = pd.to_datetime(df['followup_end_date'], errors='coerce') df['vital_status'] = df['death_date'].notna().astype(int) df['survival_days'] = df.apply(lambda row: (row['death_date'] - row['diagnosis_date']).days if pd.notna(row['death_date']) else (row['followup_end_date'] - row['diagnosis_date']).days, axis=1) df['survival_months'] = (df['survival_days'] / 30.44).round(1)

场景8分组聚合首末记录

用户:"对每个患者找出第一次化疗日期和最后一次化疗日期,计算持续时间" 代码: df['chemo_date'] = pd.to_datetime(df['chemo_date'], errors='coerce') patient_chemo = df.groupby('patient_id')['chemo_date'].agg(['min', 'max']).reset_index() patient_chemo.columns = ['patient_id', 'first_chemo', 'last_chemo'] patient_chemo['chemo_duration_days'] = (patient_chemo['last_chemo'] - patient_chemo['first_chemo']).dt.days df = df.merge(patient_chemo'patient_id', 'first_chemo', 'last_chemo', 'chemo_duration_days', on='patient_id', how='left')

场景9时间序列变化率

用户:"按患者ID分组计算每次随访相比上次的肿瘤大小变化率" 代码: df = df.sort_values(['patient_id', 'followup_date']) df['prev_tumor_size'] = df.groupby('patient_id')['tumor_size'].shift(1) df['tumor_change_rate'] = ((df['tumor_size'] - df['prev_tumor_size']) / df['prev_tumor_size'] * 100).round(2) df['tumor_change_rate'] = df['tumor_change_rate'].fillna(0)

场景10医学规则引擎肝功能分级

用户:"根据ALT、AST、ALP、TBIL判断肝功能分级" 代码: def classify_liver_function(row): abnormal_count = 0 if pd.notna(row.get('ALT')) and row['ALT'] > 40: abnormal_count += 1 if pd.notna(row.get('AST')) and row['AST'] > 40: abnormal_count += 1 if pd.notna(row.get('ALP')) and row['ALP'] > 125: abnormal_count += 1 if pd.notna(row.get('TBIL')) and row['TBIL'] > 20: abnormal_count += 1 if abnormal_count == 0: return '正常' elif abnormal_count == 1: return '轻度异常' elif abnormal_count == 2: return '中度异常' else: return '重度异常' df['liver_function_grade'] = df.apply(classify_liver_function, axis=1)

现在,请准备好接收用户的指令。记住:代码要安全、可靠、易懂,能处理从基础到高级的复杂医疗场景。 `; }

- [ ] 集成LLMFactory✅ 复用平台通用能力层)
```typescript
// backend/src/modules/dc/tool-c/services/AICodeService.ts
import { LLMFactory } from '@/common/llm';  // ✅ 复用平台LLM能力
import { logger } from '@/common/logging';  // ✅ 复用日志

export class AICodeService {
  /**
   * 生成Pandas代码✅ 复用LLMFactory
   */
  async generateCode(prompt: string, dataContext: DataContext): Promise<string> {
    logger.info('Generating Pandas code', { prompt, dataContext });
    
    // ✅ 复用平台LLM服务
    const llm = LLMFactory.getLLM('deepseek-v3');
    
    const systemPrompt = buildSystemPrompt(dataContext);
    
    const response = await llm.chat([
      { role: 'system', content: systemPrompt },
      { role: 'user', content: prompt }
    ]);
    
    // 提取纯代码去除Markdown格式
    const code = this.extractCode(response.content);
    
    logger.info('Code generated successfully', { codeLength: code.length });
    
    return code;
  }
  
  /**
   * AI自我修复失败后重试1次
   */
  async fixCode(originalCode: string, errorMsg: string, dataContext: DataContext): Promise<string> {
    logger.warn('Code execution failed, attempting self-repair', { errorMsg });
    
    const llm = LLMFactory.getLLM('deepseek-v3');
    
    const fixPrompt = `以下代码执行失败,请修复:

错误信息:
${errorMsg}

原始代码:
${originalCode}

数据结构:
${JSON.stringify(dataContext)}

请生成修复后的代码不要有Markdown格式`;
    
    const response = await llm.chat([{ role: 'user', content: fixPrompt }]);
    
    return this.extractCode(response.content);
  }
  
  private extractCode(text: string): string {
    // 去除```python```或```标记
    const match = text.match(/```(?:python)?\n([\s\S]*?)\n```/);
    return match ? match[1] : text;
  }
}

测试用例:

  • 输入"年龄分组",生成正确代码
  • 输入"删除空行",生成正确代码
  • 检查代码中是否包含危险语句

验收标准:

  • AI能生成可执行的Pandas代码
  • 代码符合规范无print、无import

负责人: Node.js后端


Week 2核心功能开发5天

Day 6-7AI代码生成 + AST检查

Day 6任务

  • 实现AST静态检查
    # python-service/code_validator.py
    import ast
    
    DANGEROUS_IMPORTS = {'os', 'sys', 'subprocess', 'requests', 'urllib'}
    DANGEROUS_BUILTINS = {'eval', 'exec', 'compile', 'open', '__import__'}
    
    def validate_code_safety(code: str) -> Tuple[bool, str]:
        try:
            tree = ast.parse(code)
    
            for node in ast.walk(tree):
                # 检查import
                if isinstance(node, ast.Import):
                    for alias in node.names:
                        if alias.name in DANGEROUS_IMPORTS:
                            return False, f"禁止导入:{alias.name}"
    
                # 检查函数调用
                if isinstance(node, ast.Call):
                    if isinstance(node.func, ast.Name):
                        if node.func.id in DANGEROUS_BUILTINS:
                            return False, f"禁止使用:{node.func.id}"
    
            return True, "验证通过"
        except SyntaxError as e:
            return False, f"语法错误:{str(e)}"
    

Day 7任务

  • 实现AI代码生成API
    // Node.js
    fastify.post('/api/v1/dc/tool-c/ai/generate', async (req, reply) => {
      const { sessionId, prompt } = req.body;
    
      // 1. 获取数据上下文
      const context = await pythonService.getDataContext(sessionId);
    
      // 2. 调用AI生成代码
      const code = await aiService.generateCode(prompt, context);
    
      return {
        success: true,
        code,
        summary: `将执行以下操作:${extractSummary(code)}`
      };
    });
    

测试场景15个真实医疗数据清洗场景

🟢 基础场景5个- 单步骤操作:

  1. "把年龄大于60的标记为老年组" - 简单条件判断
  2. "删除所有患者ID为空的行" - 数据完整性清洗
  3. "把性别转为数字男1女0" - 分类变量编码
  4. "计算BMI = 体重 / (身高/100)^2" - 简单公式计算
  5. "删除缺失率超过50%的列" - 列级数据质量控制

🟡 中等场景5个- 多步骤或跨列逻辑: 6. [ ] "把诊断日期和出院日期计算天数差,如果出院日期早于诊断日期则标记为异常" - 逻辑验证 7. [ ] "根据白细胞、中性粒细胞、淋巴细胞三个指标计算NLR中性粒细胞/淋巴细胞并按2.5分为高低两组" - 多步骤计算 8. [ ] "从病理报告列中提取TNM分期生成新列如果没有提取到则标记为'未分期'" - 文本提取(可用正则) 9. [ ] "把血压列中的'120/80'格式拆分成收缩压和舒张压两列,并判断是否高血压(收缩压>140 or 舒张压>90" - 字符串处理+逻辑判断 10. [ ] "删除重复的患者ID保留最新的一条记录根据就诊日期" - 去重+排序

🔴 高级场景5个- 复杂分组、时间序列、医学规则: 11. [ ] "对于每个患者,找出第一次化疗日期和最后一次化疗日期,计算化疗持续时间" - 分组聚合 12. [ ] "生成生存状态变量如果死亡日期存在则为1否则为0生成生存时间如果死亡则为死亡日期-诊断日期),否则为(随访截止日期-诊断日期)" - 复杂条件逻辑 13. [ ] "根据多个实验室指标ALT、AST、ALP、TBIL判断肝功能分级正常、轻度异常、中度异常、重度异常" - 医学规则引擎 14. [ ] "按患者ID分组对每个患者的多次随访记录计算相邻两次之间的指标变化率如肿瘤大小变化率" - 时间序列分析 15. [ ] "根据入院时间,计算患者的季节变量(春夏秋冬),然后统计不同季节的发病人数" - 时间特征提取+统计分析

验收标准(分层要求):

  • 基础场景成功率 > 90%5/5或4/5成功
  • 中等场景成功率 > 80%4/5成功
  • 高级场景成功率 > 60%3/5成功
  • 总体成功率 > 80%12/15场景成功
  • AST能拦截import os等危险代码
  • 如果总体成功率 < 60%9/15失败MVP失败需要Pivot到模板库模式

负责人: Node.js后端 + Python开发


Day 8代码执行 + 表格刷新

任务清单:

  • 实现代码执行API
    # Python
    @app.post("/api/python/execute")
    async def execute_code(request: ExecuteRequest):
        # 1. 获取Session
        df = session_manager.get(request.sessionId)
    
        # 2. AST检查
        is_safe, error_msg = validate_code_safety(request.code)
        if not is_safe:
            return {"success": False, "error": error_msg}
    
        # 3. 执行代码
        try:
            exec(request.code, {'df': df, 'pd': pd, 'np': np})
    
            # 4. 返回预览数据
            preview = df.head(100).to_dict('records')
    
            return {
                "success": True,
                "preview": preview,
                "stats": {
                    "totalRows": len(df),
                    "totalCols": len(df.columns)
                }
            }
        except Exception as e:
            return {
                "success": False,
                "error": str(e),
                "traceback": traceback.format_exc()
            }
    
  • 前端执行流程
    async function executeAICode(code: string) {
      // 1. 展示预操作卡片
      const confirmed = await showActionCard(code);
      if (!confirmed) return;
    
      // 2. 锁定表格
      setIsLocked(true);
    
      try {
        // 3. 执行代码
        const response = await api.post('/api/v1/dc/tool-c/ai/execute', {
          sessionId,
          code
        });
    
        if (response.data.success) {
          // 4. 刷新表格
          setData(response.data.preview);
          message.success('执行成功!');
        } else {
          message.error(`执行失败:${response.data.error}`);
        }
      } finally {
        // 5. 解锁表格
        setIsLocked(false);
      }
    }
    

测试用例:

  • 执行成功,表格正确刷新
  • 执行失败,显示错误信息
  • 执行期间,表格处于锁定状态

验收标准:

  • 代码能正确修改DataFrame
  • 前端能实时看到变化

负责人: Python开发 + 前端


Day 9UI锁定 + 预操作卡片

任务清单:

  • 实现AI对话UI
    function AIChatPanel({ sessionId }: Props) {
      const [messages, setMessages] = useState<Message[]>([]);
      const [input, setInput] = useState('');
      const [isProcessing, setIsProcessing] = useState(false);
    
      const handleSend = async () => {
        if (!input.trim()) return;
    
        // 1. 添加用户消息
        setMessages(prev => [...prev, {
          role: 'user',
          content: input
        }]);
    
        setIsProcessing(true);
    
        // 2. 调用AI生成代码
        const response = await api.post('/api/v1/dc/tool-c/ai/generate', {
          sessionId,
          prompt: input
        });
    
        // 3. 添加AI消息包含代码
        setMessages(prev => [...prev, {
          role: 'assistant',
          content: response.data.summary,
          code: response.data.code
        }]);
    
        setIsProcessing(false);
        setInput('');
      };
    
      return (
        <div className="flex flex-col h-full">
          {/* 消息列表 */}
          <div className="flex-1 overflow-y-auto p-4">
            {messages.map((msg, idx) => (
              <MessageBubble key={idx} message={msg} />
            ))}
          </div>
    
          {/* 输入框 */}
          <div className="p-4 border-t">
            <Input.TextArea
              value={input}
              onChange={e => setInput(e.target.value)}
              placeholder="输入指令例如把年龄大于60的标记为老年组"
              disabled={isProcessing}
            />
            <Button onClick={handleSend} disabled={isProcessing}>
              发送
            </Button>
          </div>
        </div>
      );
    }
    
  • 实现预操作卡片
    function ActionCard({ code, onConfirm, onCancel }: Props) {
      return (
        <Card className="mt-2 bg-slate-900 text-white">
          <div className="mb-2 text-xs text-slate-400">
            AI生成的代码 - 请确认后执行
          </div>
          <pre className="text-sm font-mono text-blue-300">
            {code}
          </pre>
          <div className="mt-3 flex gap-2">
            <Button type="primary" onClick={onConfirm}>
              运行代码
            </Button>
            <Button onClick={onCancel}>
              取消
            </Button>
          </div>
        </Card>
      );
    }
    
  • 实现UI锁定
    <AgGridReact
      rowData={data}
      suppressClickEdit={isLocked}
      readOnlyEdit={isLocked}
    />
    
    {isLocked && (
      <div className="absolute inset-0 bg-black/20 flex items-center justify-center">
        <Spin tip="AI正在处理中请稍候..." />
      </div>
    )}
    

验收标准:

  • 用户能发送消息
  • AI返回代码时展示预操作卡片
  • 执行期间表格锁定

负责人: 前端


Day 10导出 + 端到端测试

任务清单:

  • 实现导出API
    @app.get("/api/python/export/{session_id}")
    async def export_excel(session_id: str):
        df = session_manager.get(session_id)
    
        # 生成Excel
        output = io.BytesIO()
        df.to_excel(output, index=False, engine='openpyxl')
        output.seek(0)
    
        return StreamingResponse(
            output,
            media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            headers={
                'Content-Disposition': f'attachment; filename=cleaned_data_{session_id}.xlsx'
            }
        )
    
  • 前端导出按钮
    const handleExport = async () => {
      const response = await api.get(`/api/v1/dc/tool-c/session/export/${sessionId}`, {
        responseType: 'blob'
      });
    
      const url = window.URL.createObjectURL(response.data);
      const a = document.createElement('a');
      a.href = url;
      a.download = 'cleaned_data.xlsx';
      a.click();
    };
    
  • 端到端测试(完整流程)
    1. 上传 test_data.xlsx (5000行 × 20列)
    2. AI指令"把年龄大于60的标记为老年组"
    3. 确认 → 执行 → 表格刷新(验证新增列)
    4. AI指令"删除患者ID为空的行"
    5. 确认 → 执行 → 表格刷新(验证行数减少)
    6. 点击"导出" → 下载文件
    7. 打开下载的文件,验证数据正确
    

验收标准:

  • 整个流程 < 2分钟
  • 导出的Excel数据正确
  • AI代码执行成功

负责人: 全员


Week 3优化与测试5天

Day 11-12错误处理 + AI自我修复

任务清单:

  • 实现AI自我修复
    async function executeWithRetry(code: string): Promise<ExecuteResult> {
      // 第一次执行
      let result = await pythonService.execute(sessionId, code);
    
      if (!result.success) {
        // 失败让AI修复
        const fixedCode = await aiService.fixCode(code, result.error);
    
        // 重试1次
        result = await pythonService.execute(sessionId, fixedCode);
    
        if (!result.success) {
          // 彻底失败
          return {
            success: false,
            error: '代码执行失败,请尝试更详细地描述您的需求',
            originalError: result.error
          };
        }
      }
    
      return result;
    }
    
  • 优化错误提示
    function showErrorDetail(error: string) {
      const errorMap = {
        "KeyError: 'age'": "列名'age'不存在,请检查列名是否正确",
        "ValueError": "数据类型不匹配,请检查数据格式",
        "MemoryError": "数据量过大,请减少数据行数"
      };
    
      const friendlyMsg = errorMap[error] || error;
    
      Modal.error({
        title: '执行失败',
        content: friendlyMsg
      });
    }
    

测试场景(边界情况):

  • 列名不存在:df['nonexistent']
  • 语法错误:df[df['age'] > 60 and df['age'] < 80]
  • 数据类型错误:df['age'] + '字符串'
  • 内存溢出:处理超大数据

验收标准:

  • 常见错误能自我修复
  • 无法修复时有友好提示

负责人: Node.js后端


Day 13快捷模板 + 编码检测

任务清单:

  • 实现快捷模板
    const QUICK_TEMPLATES = {
      '年龄分组': {
        label: '年龄分组60岁分界',
        code: `df['age_group'] = pd.cut(df['age'], bins=[0,60,150], labels=['非老年','老年'])`
      },
      '删除空行': {
        label: '删除空行',
        code: `df.dropna(how='all', inplace=True)`
      },
      '性别编码': {
        label: '性别编码男1女0',
        code: `df['gender_code'] = df['gender'].map({'男':1, '女':0})`
      }
    };
    
    function QuickActions() {
      return (
        <div className="p-4 border-t">
          <div className="text-xs text-slate-500 mb-2">快捷操作</div>
          {Object.entries(QUICK_TEMPLATES).map(([key, template]) => (
            <Button
              key={key}
              size="small"
              onClick={() => executeTemplate(template.code)}
            >
              {template.label}
            </Button>
          ))}
        </div>
      );
    }
    
  • 实现编码检测
    import chardet
    
    def detect_encoding(file_bytes: bytes) -> str:
        result = chardet.detect(file_bytes)
        encoding = result['encoding']
        confidence = result['confidence']
    
        if confidence < 0.8:
            raise ValueError(f"无法确定文件编码(置信度{confidence:.0%}")
    
        return encoding
    
    @app.post("/api/python/init")
    async def init_session(file: UploadFile):
        content = await file.read()
    
        # 检测编码
        encoding = detect_encoding(content)
    
        if encoding.lower() not in ['utf-8', 'utf-8-sig']:
            return {
                "success": False,
                "error": f"文件编码为{encoding}请转换为UTF-8后重新上传",
                "suggestion": "在Excel中另存为CSV UTF-8格式"
            }
    
        # 正常加载
        # ...
    

验收标准:

  • 快捷模板一键执行
  • GBK文件被友好拦截

负责人: 前端 + Python开发


Day 14性能测试 + 压力测试

测试场景:

  • 性能测试
    测试数据5000行 × 20列约5MB
    
    场景1上传文件
    - 目标:< 5秒
    - 实测____秒
    
    场景2AI生成代码
    - 目标:< 5秒
    - 实测____秒
    
    场景3代码执行+刷新
    - 目标:< 3秒
    - 实测____秒
    
    场景4导出Excel
    - 目标:< 3秒
    - 实测____秒
    
    总计:< 16秒
    
  • 压力测试
    场景1并发用户
    - 5个用户同时上传文件
    - 验证Session隔离互不影响
    
    场景2大数据量
    - 上传50000行 × 50列约10MB
    - 验证:不崩溃,响应时间可接受
    
    场景3连续操作
    - 连续执行10次AI指令
    - 验证:内存不泄漏
    

性能优化(如果不达标):

  • 前端AG Grid虚拟滚动配置
  • 后端DataFrame操作改为惰性计算
  • Python使用Polars替代Pandas如果需要

验收标准:

  • 单次操作 < 16秒
  • 5个并发用户正常工作
  • 无内存泄漏

负责人: 全员


Day 15文档 + 演示准备

任务清单:

  • 编写用户文档
    # 工具C使用指南
    
    ## 快速开始
    1. 点击"上传文件"选择Excel限10MB
    2. 等待3-5秒表格加载完成
    3. 在右侧AI助手输入指令例如"把年龄大于60的标记为老年组"
    4. 点击"运行代码"确认
    5. 查看表格自动刷新
    6. 完成所有操作后,点击"导出"下载结果
    
    ## 常见问题
    Q: 文件上传失败,提示文件过大?
    A: 请将Excel文件压缩到10MB以内或删除不需要的列。
    
    Q: 列名显示乱码?
    A: 请在Excel中另存为"CSV UTF-8"格式后重新上传。
    
  • 准备演示数据
    创建 demo_data.xlsx
    - 100行患者数据
    - 包含patient_id, name, age, gender, admission_date, bmi
    - 故意制造一些问题:空值、异常值、需要清洗的数据
    
  • 准备演示脚本
    演示流程5分钟
    1. 上传demo_data.xlsx
    2. AI指令"把年龄大于60的标记为老年组"
    3. AI指令"删除患者ID为空的行"
    4. AI指令"把性别转为数字男1女0"
    5. 导出结果
    6. 展示导出的Excel文件
    

验收标准:

  • 文档清晰易懂
  • 演示流畅无卡顿
  • 演示数据能体现核心功能

负责人: 全员


五、风险应对策略

5.1 风险清单与应对

风险 概率 影响 应对策略 降级方案
AI代码质量低成功率<60% 致命 1. 优化Prompt工程
2. 增加Few-shot示例
3. 实现自我修复
Pivot改用代码模板库
Apache Arrow集成困难 1. MVP不用Arrow
2. 直接用JSON
已降级MVP用JSON
Redis内存成本高 1. MVP用进程内存
2. 激进的Session过期
已降级用Map缓存
Python内存泄漏 1. 不做历史快照
2. 定期重启进程
SAE自动重启
中文Excel乱码 1. chardet自动检测
2. 友好报错提示
文档说明转UTF-8
SAE冷启动慢 1. 最小实例数=1
2. 异步初始化
Loading优化
前端AG Grid性能差 1. 只展示100行
2. 虚拟滚动
已优化

5.2 快速失败触发器

立即停止开发的条件:

  1. Day 7结束AI代码成功率 < 60%

    • 决策放弃AI Code Interpreter路线
    • Pivot改为"代码模板库 + 参数化配置"
  2. Day 10结束端到端时间 > 30秒

    • 决策:性能无法接受
    • Pivot改为批处理模式上传 → 后台处理 → 下载)
  3. Day 14结束3个用户测试都无法独立完成任务

    • 决策:交互设计失败
    • Pivot重新设计UI增加新手引导

六、验收标准

6.1 功能验收清单

编号 验收项 验收标准 验收方式
F-001 文件上传 能上传10MB以内的Excel 手动测试
F-002 编码检测 GBK文件被友好拦截 手动测试
F-003 表格展示 100行数据正确显示中文无乱码 手动测试
F-004 AI对话 能发送消息,接收回复 手动测试
F-005 代码生成 10个场景8个成功80% 自动化测试
F-006 AST检查 拦截import os等危险代码 单元测试
F-007 代码执行 能正确修改DataFrame 集成测试
F-008 表格刷新 执行后自动更新 手动测试
F-009 UI锁定 AI处理时表格只读+遮罩 手动测试
F-010 导出Excel 下载的文件数据正确 手动测试

6.2 性能验收标准

指标 目标值 验收方式
文件上传到表格显示 < 5秒 性能测试
AI生成代码 < 5秒 性能测试
代码执行+刷新 < 3秒 性能测试
导出Excel < 3秒 性能测试
端到端总时间 < 16秒 性能测试
并发用户数 ≥ 5人 压力测试
内存占用 < 2GB5用户 压力测试

6.3 安全验收标准

指标 验收标准 验收方式
代码沙箱 拦截所有危险代码 渗透测试
Session隔离 用户A看不到用户B数据 并发测试
Session过期 10分钟后自动清理 时间测试

七、快速失败机制

7.1 关键检查点

Checkpoint 1 (Day 5结束)
├─ System Prompt能否让AI理解数据
├─ AI能生成Pandas代码吗
└─ 决策:继续 or 调整Prompt策略

Checkpoint 2 (Day 7结束)
├─ AI代码成功率 ≥ 80%
├─ 如果 < 60%:立即停止,改用模板库
└─ 如果 60-80%继续优化Prompt

Checkpoint 3 (Day 10结束)
├─ 端到端流程能跑通?
├─ 性能 < 16秒
└─ 决策:继续 or 性能优化

Checkpoint 4 (Day 14结束)
├─ 用户测试通过?
├─ 无重大Bug
└─ 决策:发布 or 修复Bug

7.2 Pivot决策树

如果 AI代码成功率 < 60%
  ├─ Pivot 1改用"代码模板库 + 参数化配置"
  │   └─ 用户选择模板 → 填写参数 → 生成代码
  │
  ├─ Pivot 2AI变成"辅助建议"角色
  │   └─ 用户手动写代码AI提供建议
  │
  └─ Pivot 3简化为"批处理模式"
      └─ 上传 → 选择预设操作 → 后台处理 → 下载

如果 性能无法接受(> 30秒
  ├─ 方案1引入Apache Arrow
  ├─ 方案2使用Polars替代Pandas
  └─ 方案3改为批处理+异步通知

如果 用户无法独立完成任务:
  ├─ 方案1增加交互式新手引导
  ├─ 方案2提供示例数据集
  └─ 方案3简化UI减少选项

八、附录

8.1 测试数据准备

创建标准测试数据集:

# create_test_data.py
import pandas as pd
import numpy as np

# 生成5000行测试数据
data = {
    'patient_id': [f'P{i:04d}' for i in range(1, 5001)],
    'name': [f'患者{i}' for i in range(1, 5001)],
    'age': np.random.randint(18, 90, 5000),
    'gender': np.random.choice(['男', '女'], 5000),
    'admission_date': pd.date_range('2023-01-01', periods=5000, freq='H'),
    'weight': np.random.uniform(45, 95, 5000).round(1),
    'height': np.random.uniform(150, 190, 5000).round(0),
    'diagnosis': np.random.choice(['肺癌', '糖尿病', '高血压'], 5000)
}

df = pd.DataFrame(data)

# 故意制造问题
df.loc[np.random.choice(5000, 100, replace=False), 'age'] = None  # 100个缺失
df.loc[np.random.choice(5000, 10, replace=False), 'age'] = 150   # 10个异常值
df.loc[np.random.choice(5000, 50, replace=False), 'patient_id'] = None  # 50个空ID

df.to_excel('test_data.xlsx', index=False)

8.2 10个典型场景测试用例

# test_ai_scenarios.py
test_cases = [
    {
        "id": "TC-001",
        "prompt": "把年龄大于60的患者标记为老年组",
        "expected_code": "df['age_group'] = df['age'].apply(lambda x: '老年组' if pd.notna(x) and x > 60 else '非老年组')",
        "verify": lambda df: 'age_group' in df.columns
    },
    {
        "id": "TC-002",
        "prompt": "删除所有患者ID为空的行",
        "expected_code": "df.dropna(subset=['patient_id'], inplace=True)",
        "verify": lambda df: df['patient_id'].isnull().sum() == 0
    },
    # ... 其余8个场景
]

8.3 环境配置清单

Node.js依赖

{
  "dependencies": {
    "fastify": "^4.0.0",
    "axios": "^1.6.0",
    "joi": "^17.11.0",
    "@fastify/multipart": "^8.0.0"
  }
}

Python依赖

fastapi==0.115.0
uvicorn==0.30.0
pandas==2.2.0
openpyxl==3.1.0
chardet==5.2.0
python-multipart==0.0.9

九、项目管理

9.1 团队配置

角色 人数 职责
前端开发 1人 React + AG Grid + UI
Node.js后端 1人 BFF + LLM集成 + Prompt工程
Python开发 1人 FastAPI + DataFrame + AST检查
总计 3人 3周15天

9.2 沟通机制

  • 每日站会15分钟同步进度和问题
  • 关键检查点Day 5、7、10、14
  • 问题升级阻塞问题1小时内升级
  • 文档更新:每日更新开发记录

9.3 代码管理

分支策略:

main (稳定版)
  └── develop (开发版)
      ├── feature/tool-c-frontend
      ├── feature/tool-c-backend
      └── feature/tool-c-python

提交规范:

feat: 新增AI代码生成功能
fix: 修复表格刷新Bug
test: 增加AST检查测试用例
docs: 更新开发文档

十、总结

核心成功要素

  1. AI代码质量:成功率 > 80%(核心假设)
  2. 性能可接受:端到端 < 16秒
  3. 交互简单:用户无需文档就能用

风险控制

  1. 快速失败7天验证AI能力不行立即Pivot
  2. 务实技术JSON不用Arrow内存不用Redis
  3. 降级方案准备好模板库、批处理等Plan B

时间节点

  • Week 1基础架构5天
  • Week 2核心功能5天
  • Week 3优化测试5天
  • 总计15个工作日

📝 文档修订记录

V1.32025-12-06- 发现现有Python服务 重大发现

修正内容检查系统发现已有Python微服务不需要重复开发

修正1复用现有Python服务不重复造轮

  • 重大发现:系统已有 extraction_service/FastAPI + Pandas + openpyxl
  • 现有功能
    • PDF/Docx/Txt文档提取PyMuPDF + Mammoth
    • Pandas、openpyxl、chardet、langdetect已安装
    • FastAPI框架已运行端口8000
    • Node.js已有ExtractionClient集成
  • 新增方案
    • 扩展现有服务,添加 /api/dc/execute 端点
    • 新增 dc_executor.py 模块AST检查 + 代码执行)
    • 复用ExtractionClient模式调用
  • 避免重复
    • 不需要新建Python项目
    • 不需要重新安装依赖
    • 不需要重新写HTTP调用逻辑

V1.22025-12-06- Python执行环境 + 复杂场景 ⚠️ 核心功能

修正内容根据用户反馈明确Python代码执行是核心功能

修正1Python执行是核心不是可选

  • 原错误:说"MVP阶段可能不需要Python微服务"
  • 已修正Python代码执行是工具C的核心价值必须实现
  • 新增内容
    • Day 1增加Python环境搭建
    • 新增PythonExecutorServiceNode.js ↔ Python通信
    • 新增executor.pyPandas代码执行器
    • 技术方案Node.js child_process调用Python脚本

修正2测试场景从简单到复杂

  • 原问题10个测试场景过于简单
  • 已修正15个真实医疗数据清洗场景
    • 🟢 基础场景5个单步骤操作
    • 🟡 中等场景5个多步骤、跨列逻辑
    • 🔴 高级场景5个分组聚合、时间序列、医学规则
  • 新增场景
    • 复杂条件逻辑(生存时间计算)
    • 医学规则引擎(肝功能分级)
    • 时间序列分析(指标变化率)
    • 文本提取TNM分期
    • 字符串处理(血压拆分)

修正3验收标准分层

  • 原标准:总体成功率 > 80%
  • 新标准
    • 基础场景成功率 > 90%
    • 中等场景成功率 > 80%
    • 高级场景成功率 > 60%
    • 总体成功率 > 80%12/15场景

修正4核心假设验证

  • 新增H2Python代码执行环境稳定可靠
  • 修改H1:从"生成代码"改为"生成代码并成功执行"
  • 强调AI生成代码 + 真实执行 + 表格刷新是差异化价值

V1.12025-12-06- 架构合规性修正 ⚠️ 重要

修正内容:根据用户严肃提醒,修正以下违反规范的问题:

修正1强制复用平台能力

  • 原错误建议自己实现Session管理、存储、日志
  • 已修正:强制使用storage, logger, cache, prisma, LLMFactory
  • 影响章节所有Day 1-15的代码示例

修正2Session存储方式

  • 原错误:建议用Map<sessionId, data>内存缓存
  • 已修正Session存数据库dc_tool_c_sessions表)
  • 原因违反云原生规范第2条禁止内存缓存

修正3文件夹结构

  • 原错误:建议创建python-service/node-service/子文件夹
  • 已修正遵循tool-b结构services/, controllers/, routes/
  • 参考backend/src/modules/dc/tool-b/

修正4技术栈说明

  • 原错误:未强调复用现有能力
  • 已修正:明确标注"已有"、"复用平台能力"
  • 新增:云原生开发规范检查清单

修正5代码示例

  • 原错误Day 1-5的代码示例未体现平台服务
  • 已修正:所有代码示例都使用import { storage } from '@/common/storage'
  • 新增:常见错误示例( 严禁)

新增章节

  • 开发前必读:强调不要重复造轮子
  • 云原生规范强制要求列出6条核心规范
  • 常见错误示例展示5个严禁的错误写法

文档状态: 已完成V1.1 架构合规性修正版)
下一步: 团队Review → 严格按规范开发
负责人: 项目经理
创建日期: 2025-12-06
修订日期: 2025-12-06架构合规性修正

⚠️ 重要提醒

  1. 开发前必须阅读:docs/04-开发规范/08-云原生开发规范.md
  2. 参考现有实现:backend/src/modules/dc/tool-b/
  3. 禁止违反规范:内存缓存、本地文件存储、重复实现平台能力