# DC模块 Tool-B 开发计划 > **文档版本:** V2.0 (MVP完成) > **创建日期:** 2025-12-02 > **完成日期:** 2025-12-03 > **实际周期:** 2个工作日 > **状态:** ✅ MVP完成 --- ## 🎉 MVP完成通告(2025-12-03) **Tool B病历结构化机器人MVP版本已完成!** - ✅ 前端5步工作流完整实现(~1400行) - ✅ 8个API端点全部对接并测试通过 - ✅ LLM双模型提取验证成功(DeepSeek-V3 + Qwen-Max) - ✅ 真实数据测试:9条病理报告提取成功 - ✅ Excel导出功能可用 - ⚠️ 4个技术债务待处理(见`07-技术债务/Tool-B技术债务清单.md`) **完成详情:** 参见 `06-开发记录/Tool-B-MVP完成总结-2025-12-03.md` --- ## 原始开发计划 > **目标:** 完成Tool-B(病历结构化机器人)的前端开发和完整功能集成 --- ## 📋 一、项目当前状态调研总结 ### 1.1 后端代码状态 ✅ 100%完成 #### 通用能力平台(可复用) 基于 `backend/src/common/` 平台基础设施,已完整实现: | 能力模块 | 导入路径 | 功能说明 | 状态 | |---------|---------|---------|------| | **存储服务** | `@/common/storage` | 文件上传/下载(Local ↔ OSS) | ✅ 完成 | | **日志系统** | `@/common/logging` | 结构化日志(Winston) | ✅ 完成 | | **缓存服务** | `@/common/cache` | 缓存(Memory ↔ Redis) | ✅ 完成 | | **异步任务** | `@/common/jobs` | 长时间任务队列 | ✅ 完成 | | **LLM工厂** | `@/common/llm/adapters/LLMFactory` | 统一LLM调用(DeepSeek/Qwen/GPT/Claude) | ✅ 完成 | | **数据库** | `@/config/database` | Prisma连接池 | ✅ 完成 | **复用策略**: - ✅ Tool B后端代码已100%复用平台能力 - ✅ 前端开发应参考后端模式,复用前端共享组件 --- #### Tool B后端实现状态 **代码位置**:`backend/src/modules/dc/tool-b/` | 文件 | 功能 | 代码量 | 状态 | 复用能力 | |-----|------|--------|------|---------| | **services/HealthCheckService.ts** | Excel健康检查 | ~190行 | ✅ 完成 | storage, logger, cache, prisma | | **services/TemplateService.ts** | 预设模板管理 | ~243行 | ✅ 完成 | logger, prisma | | **services/DualModelExtractionService.ts** | 双模型提取 | ~390行 | ✅ 完成 | LLMFactory, logger, prisma | | **services/ConflictDetectionService.ts** | 冲突检测算法 | ~215行 | ✅ 完成 | logger | | **controllers/ExtractionController.ts** | API控制器 | ~388行 | ✅ 完成 | 全部服务 | | **routes/index.ts** | 路由配置 | ~115行 | ✅ 完成 | - | | **index.ts** | 模块入口 | ~117行 | ✅ 完成 | - | **总计**:约1,658行,7个文件,100%完成 --- #### API端点清单 **Base URL**: `/api/v1/dc/tool-b` | 方法 | 路径 | 功能 | 请求体 | 响应 | 状态 | |------|------|------|--------|------|------| | **POST** | `/health-check` | Excel列健康检查 | `{fileKey, columnName}` | 健康度报告 | ✅ 完成 | | **GET** | `/templates` | 获取预设模板列表 | - | 模板数组 | ✅ 完成 | | **POST** | `/tasks` | 创建提取任务 | `{projectName, fileKey, textColumn, diseaseType, reportType}` | `{taskId}` | ✅ 完成 | | **GET** | `/tasks/:taskId/progress` | 查询任务进度 | - | 进度详情 | ✅ 完成 | | **GET** | `/tasks/:taskId/items` | 获取验证网格数据 | `?status=conflict&page=1` | 分页数据 | ✅ 完成 | | **POST** | `/items/:itemId/resolve` | 裁决冲突 | `{resolvedData}` | 成功状态 | ✅ 完成 | **预设模板**(3个): 1. 肺癌病理报告(`lung_cancer/pathology`)- 5个字段 2. 糖尿病入院记录(`diabetes/admission`)- 5个字段 3. 高血压门诊病历(`hypertension/outpatient`)- 5个字段 --- ### 1.2 数据库状态 ✅ 已验证完成(2025-12-02) **Schema**: `dc_schema`(独立隔离) | 表名 | 用途 | 字段数 | 关键字段 | 状态 | 数据量 | |------|------|--------|---------|------|--------| | **dc_health_checks** | 健康检查记录 | 10 | status, emptyRate, avgLength | ✅ 已创建 | 2条 | | **dc_templates** | 预设模板 | 7 | diseaseType, reportType, fields | ✅ 已创建 | **3条** | | **dc_extraction_tasks** | 提取任务 | 21 | status, totalCount, processedCount | ✅ 已创建 | 1条 | | **dc_extraction_items** | 提取明细 | 15 | resultA, resultB, conflictFields | ✅ 已创建 | 4条 | **✅ 验证结果(2025-12-02)**: - ✅ **dc_schema已存在** - ✅ **4个表全部创建成功** - ✅ **3个预设模板已初始化**: 1. 肺癌病理报告 (lung_cancer/pathology) 2. 糖尿病入院记录 (diabetes/admission) 3. 高血压门诊病历 (hypertension/outpatient) - ✅ **有测试数据可用于开发调试** **验证脚本**: ```bash cd backend node scripts/check-dc-tables.mjs # 执行数据库表检查脚本 ``` **结论**:✅ 数据库完全准备就绪,可以开始前端开发! --- ### 1.3 前端代码状态 ❌ 0%(仅Placeholder) **代码位置**:`frontend-v2/src/modules/dc/` ``` frontend-v2/src/modules/dc/ ├── index.tsx # ❌ 仅Placeholder(14行) ├── components/ # 📁 空 ├── pages/ # 📁 空文件夹结构 │ ├── tool-a/ # 📁 空 │ ├── tool-b/ # 📁 空 │ └── tool-c/ # 📁 空 └── types/ # 📁 空 ``` **当前内容**: ```typescript // frontend-v2/src/modules/dc/index.tsx import Placeholder from '@/shared/components/Placeholder' const DCModule = () => { return ( ) } export default DCModule ``` --- ### 1.4 前端架构设计 ✅ 已明确 #### 系统级架构 - **导航模式**:顶部导航 - **路由路径**:`/data-cleaning` - **技术栈**:React 19 + TypeScript + Vite + Ant Design 5 #### DC模块架构方案 **选择:方案A - 独立Portal页面**(推荐) ``` /data-cleaning (Portal工作台 - 总览页) ├── 快速启动区(3个工具卡片) │ ├── [超级合并器] → /data-cleaning/tool-a │ ├── [病历结构化] → /data-cleaning/tool-b │ └── [数据编辑器] → /data-cleaning/tool-c ├── 最近任务列表(实时进度) └── 数据资产库(文件管理) /data-cleaning/tool-b (Tool B - 全屏5步流程) ├── Step 1: 上传与健康检查 ├── Step 2: 智能模板配置 ├── Step 3: 双盲提取进度 ├── Step 4: 冲突验证网格 ⭐ 核心 └── Step 5: 结果导出 ``` **参考模块**: - ASL模块(`frontend-v2/src/modules/asl/`) - 有完整的布局、页面、组件结构 - 使用React Router嵌套路由 - 左侧导航 + 右侧内容区 --- ## 🎯 二、开发计划总览 ### 2.1 开发阶段划分 | 阶段 | 任务 | 预计工时 | 优先级 | 目标 | |------|------|---------|--------|------| | **Phase 1** | Portal工作台页面 | 4-6h | P0 | DC模块入口 | | **Phase 2** | Tool B - Step 1&2 | 6h | P0 | 上传+配置 | | **Phase 3** | Tool B - Step 3 | 3h | P0 | 进度监控 | | **Phase 4** | Tool B - Step 4 | 9h | P0 | 冲突验证网格⭐ | | **Phase 5** | Tool B - Step 5 | 3h | P0 | 结果导出 | | **Phase 6** | 集成测试 | 4h | P1 | 端到端验证 | **总计**:约29-31小时(4-5个工作日) --- ### 2.2 里程碑时间表 | 里程碑 | 完成标志 | 预计完成 | |--------|---------|---------| | **M1: Portal上线** | 用户可访问DC模块入口 | Day 1 | | **M2: Tool B可用** | Step1-5全部完成 | Day 4 | | **M3: 集成测试通过** | 端到端流程测试通过 | Day 5 | | **M4: 文档完善** | 开发文档和用户文档 | Day 6 | --- ## 📐 三、Phase 1: Portal工作台开发(Day 1) ### 3.1 目标 创建DC模块的入口页面,适配系统顶部导航,提供工具启动、任务监控和文件管理功能。 ### 3.2 设计参考 - **原型文件**:`docs/03-业务模块/DC-数据清洗整理/03-UI设计/智能数据清洗工作台V2.html` - **PRD文档**:`docs/03-业务模块/DC-数据清洗整理/01-需求分析/PRD:智能数据清洗工作台 (The Data Cleaning Portal).md` ### 3.3 功能清单 #### 3.3.1 快速启动区(3个工具卡片) **组件**:`components/ToolCard.tsx` ```typescript interface ToolCardProps { id: 'tool-a' | 'tool-b' | 'tool-c'; title: string; description: string; icon: ReactNode; color: 'blue' | 'purple' | 'emerald'; status: 'ready' | 'disabled'; onClick: () => void; } ``` **卡片内容**: 1. **Tool A - 超级合并器** - 图标:FileSpreadsheet(蓝色) - 描述:"解决多源数据时间轴对齐难题" - 状态:disabled(暂未开发) 2. **Tool B - 病历结构化机器人** ⭐ 本次开发 - 图标:Bot(紫色) - 描述:"利用大模型提取非结构化文本" - 状态:ready - 点击跳转:`/data-cleaning/tool-b` 3. **Tool C - 科研数据编辑器** - 图标:Table2(绿色) - 描述:"Excel风格的在线清洗工具" - 状态:disabled(暂未开发) --- #### 3.3.2 最近任务列表 **组件**:`components/TaskList.tsx` **API**:`GET /api/v1/dc/tasks/recent`(需后端新增) **数据结构**: ```typescript interface Task { id: string; name: string; tool: 'tool-a' | 'tool-b' | 'tool-c'; status: 'pending' | 'processing' | 'completed' | 'failed'; progress: number; // 0-100 createdAt: string; completedAt?: string; } ``` **功能**: - 显示最近10条任务 - 实时轮询进度(processing状态时,每5秒轮询) - 智能流转按钮: - Tool A完成 → [下载] + [去AI提取](跳Tool B) - Tool B完成 → [下载] + [去清洗](跳Tool C) - Tool C完成 → [下载] --- #### 3.3.3 数据资产库 **组件**:`components/AssetLibrary.tsx` **API**:`GET /api/v1/dc/assets`(需后端新增) **Tab分类**: - **全部**:所有文件 - **处理结果**:工具A/B/C生成的文件(绿色/蓝色图标) - **原始上传**:用户直接上传的底表(灰色图标) **功能**: - 文件卡片显示(文件名、行数、标签、修改时间) - 快捷操作:[下载] [去处理] [分析] - 底部固定按钮:[+ 上传原始文件到库] --- ### 3.4 技术实现 #### 3.4.1 目录结构 ``` frontend-v2/src/modules/dc/ ├── pages/ │ └── Portal.tsx # ⭐ Portal主页面 ├── components/ │ ├── ToolCard.tsx # 工具卡片 │ ├── TaskList.tsx # 任务列表 │ └── AssetLibrary.tsx # 数据资产库 ├── hooks/ │ ├── useRecentTasks.ts # 任务列表Hook │ └── useAssets.ts # 资产列表Hook ├── services/ │ └── portalApi.ts # Portal API封装 └── types/ └── portal.ts # Portal类型定义 ``` --- #### 3.4.2 路由配置 ```typescript // frontend-v2/src/modules/dc/index.tsx import { Routes, Route } from 'react-router-dom'; import Portal from './pages/Portal'; import ToolBModule from './pages/tool-b'; const DCModule = () => { return ( } /> } /> {/* 未来扩展 */} } /> } /> ); }; export default DCModule; ``` --- #### 3.4.3 API对接 **需要后端新增的API**: ```typescript // GET /api/v1/dc/tasks/recent interface GetRecentTasksResponse { tasks: Task[]; } // GET /api/v1/dc/assets interface GetAssetsResponse { assets: Asset[]; } ``` **临时方案**(后端API未开发时): - 使用Mock数据 - 后续替换为真实API --- ### 3.5 验收标准 - [ ] Portal页面可访问(`http://localhost:3000/data-cleaning`) - [ ] 3个工具卡片正确显示 - [ ] Tool B卡片可点击跳转(其他两个显示disabled) - [ ] 任务列表显示Mock数据 - [ ] 数据资产库Tab切换正常 - [ ] 整体样式符合系统设计规范 --- ## 🛠️ 四、Phase 2: Tool B - Step 1&2(Day 2) ### 4.1 Step 1: 文件上传与健康检查(3小时) #### 4.1.1 页面设计 **组件**:`pages/tool-b/Step1Upload.tsx` **UI参考**:原型V4第260-310行 **布局**: 1. 文件信息卡片(显示已上传文件) 2. 列选择下拉框 3. 健康检查结果卡片(动态显示) --- #### 4.1.2 功能实现 **1. Excel文件上传** ```typescript // 使用Ant Design Upload组件 import { Upload } from 'antd'; // 上传到Storage const handleUpload = async (file: File) => { const formData = new FormData(); formData.append('file', file); const response = await fetch('/api/v1/storage/upload', { method: 'POST', body: formData }); const { fileKey } = await response.json(); setFileKey(fileKey); // 保存fileKey用于后续步骤 }; ``` **2. 列选择与健康检查** ```typescript const [columns, setColumns] = useState([]); const [selectedColumn, setSelectedColumn] = useState(''); const [healthResult, setHealthResult] = useState(null); // 自动检测列名(可选:从后端获取) useEffect(() => { if (fileKey) { // TODO: 调用API获取Excel列名 // 或前端解析Excel(使用xlsx库) } }, [fileKey]); // 健康检查 const handleHealthCheck = async (columnName: string) => { setIsChecking(true); try { const response = await fetch('/api/v1/dc/tool-b/health-check', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fileKey, columnName }) }); const result = await response.json(); setHealthResult(result.data); } finally { setIsChecking(false); } }; ``` **3. 健康度显示** ```typescript // 根据status显示不同样式 {healthResult?.status === 'good' && ( } message="健康度优秀,适合提取" description={ 平均字符: {healthResult.avgLength} 空值率: {(healthResult.emptyRate * 100).toFixed(1)}% 预计Token: {healthResult.estimatedTokens.toLocaleString()} } /> )} {healthResult?.status === 'bad' && ( } message="警告:该列不适合AI处理" description={healthResult.message} /> )} ``` --- ### 4.2 Step 2: 智能模板配置(3小时) #### 4.2.1 页面设计 **组件**:`pages/tool-b/Step2Schema.tsx` **UI参考**:原型V4第313-372行 **布局**: 1. 疾病类型与报告类型选择(级联) 2. 字段列表(左侧,可编辑) 3. Prompt预览(右侧,代码高亮) --- #### 4.2.2 功能实现 **1. 获取模板列表** ```typescript const [templates, setTemplates] = useState([]); const [diseaseType, setDiseaseType] = useState(''); const [reportType, setReportType] = useState(''); const [fields, setFields] = useState([]); useEffect(() => { // 获取所有模板 fetch('/api/v1/dc/tool-b/templates') .then(res => res.json()) .then(data => setTemplates(data.data.templates)); }, []); // 当选择疾病类型和报告类型时,自动加载字段 useEffect(() => { if (diseaseType && reportType) { const template = templates.find( t => t.diseaseType === diseaseType && t.reportType === reportType ); if (template) { setFields(template.fields); } } }, [diseaseType, reportType, templates]); ``` **2. 字段编辑** ```typescript // 添加字段 const handleAddField = () => { setFields([...fields, { id: `custom_${Date.now()}`, name: '新字段', desc: '字段描述', width: 'w-32' }]); }; // 删除字段 const handleDeleteField = (id: string) => { setFields(fields.filter(f => f.id !== id)); }; // 编辑字段 const handleEditField = (id: string, key: 'name' | 'desc', value: string) => { setFields(fields.map(f => f.id === id ? { ...f, [key]: value } : f )); }; ``` **3. Prompt预览** ```typescript // 动态生成Prompt预览 const generatePrompt = () => { return `You are an expert in ${diseaseType.replace('_', ' ')} pathology. Extract fields in JSON format: { ${fields.map(f => ` "${f.name}": "string", // ${f.desc}`).join('\n')} } Original text: {originalText} Output JSON only.`; }; // 使用代码高亮库(如react-syntax-highlighter) {generatePrompt()} ``` --- ### 4.3 验收标准 **Step 1:** - [ ] Excel上传成功,获取fileKey - [ ] 列名列表正确显示 - [ ] 健康检查API调用成功 - [ ] 健康度卡片根据结果正确显示(绿色/红色) - [ ] Token预估数值正确 - [ ] 空值率>50%时禁止进入下一步 **Step 2:** - [ ] 模板列表加载成功 - [ ] 疾病类型和报告类型选择框正常 - [ ] 选择模板后字段自动加载 - [ ] 字段支持添加/删除/编辑 - [ ] Prompt预览实时更新 - [ ] 代码高亮正确显示 --- ## ⚙️ 五、Phase 3: Tool B - Step 3(Day 3上午) ### 5.1 处理进度监控(3小时) #### 5.1.1 页面设计 **组件**:`pages/tool-b/Step3Processing.tsx` **UI参考**:原型V4第375-400行 **布局**: 1. 圆形进度环(双模型动画) 2. 进度百分比和文本 3. 日志滚动区域 --- #### 5.1.2 功能实现 **1. 创建任务** ```typescript const handleStartExtraction = async () => { const response = await fetch('/api/v1/dc/tool-b/tasks', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ projectName: 'Test Project', sourceFileKey: fileKey, textColumn: selectedColumn, diseaseType, reportType, modelA: 'deepseek-v3', modelB: 'qwen-max' }) }); const { taskId } = await response.json(); setTaskId(taskId); // 开始轮询 startPolling(taskId); }; ``` **2. 进度轮询** ```typescript const startPolling = (taskId: string) => { const interval = setInterval(async () => { const response = await fetch(`/api/v1/dc/tool-b/tasks/${taskId}/progress`); const data = await response.json(); setProgress(data.progress); // 百分比 setLogs(prevLogs => [...prevLogs, data.latestLog]); // 新增日志 if (data.status === 'completed') { clearInterval(interval); // 跳转到Step 4 setTimeout(() => navigate(`/data-cleaning/tool-b/verify/${taskId}`), 800); } else if (data.status === 'failed') { clearInterval(interval); message.error('提取任务失败'); } }, 5000); // 每5秒轮询 return () => clearInterval(interval); }; ``` **3. 进度动画** ```typescript // 使用Ant Design Progress组件或自定义SVG ( {percent}% 双盲提取中 )} strokeColor={{ '0%': '#9333ea', '100%': '#4f46e5' }} /> // 双模型动画(两个小球跳动) ``` **4. 日志显示** ```typescript {logs.map((log, i) => ( [{log.timestamp}] {log.message} ))} _ ``` --- ### 5.2 验收标准 - [ ] 点击"开始提取"后成功创建任务 - [ ] 获取到taskId - [ ] 进度轮询正常(每5秒) - [ ] 进度条实时更新 - [ ] 日志滚动区域正常显示 - [ ] 任务完成后自动跳转到Step 4 - [ ] 任务失败时显示错误提示 --- ## 🎯 六、Phase 4: Tool B - Step 4(Day 3下午+Day 4)⭐ 核心 ### 6.1 冲突验证网格(9小时) 这是整个Tool B最复杂、最核心的页面! #### 6.1.1 页面设计 **组件**:`pages/tool-b/Step4Verify.tsx` **UI参考**:原型V4第402-569行 **布局**: ``` ┌─────────────────────────────────────────────────┐ │ Toolbar: 统计信息 + [导出][完成] │ ├─────────────────────────────────────────────────┤ │ │ │ 全景验证网格(表格) │ │ ┌────┬────────────┬────────┬────────┬──────┐ │ │ │ # │ 原文摘要 │ 字段1 │ 字段2 │ 状态 │ │ │ ├────┼────────────┼────────┼────────┼──────┤ │ │ │ 1 │ 病理诊断.. │ 冲突单元格(A/B按钮)│待裁决│ │ │ │ 2 │ 送检组织.. │ 绿色(一致)│ 绿色 │ 通过 │ │ │ └────┴────────────┴────────┴────────┴──────┘ │ │ │ └─────────────────────────────────────────────────┘ ↓ 点击行 ┌──────────────────────────────────┐ │ 侧边栏(Drawer) │ │ ─────────────────────────────── │ │ 病历原文详情 │ │ │ │ 病理诊断:(右肺上叶)浸润性腺癌... │ │ 肿瘤大小 3.2*2.5*2.0cm... │ │ ... │ │ │ │ [关闭] │ └──────────────────────────────────┘ ``` --- #### 6.1.2 技术选型 **方案选择**: **Option 1: Ant Design Table**(推荐,数据量<1000行) - 优点:开箱即用,API友好,样式统一 - 缺点:数据量大时可能卡顿 - 适用场景:大部分场景(预计数据量<500行) **Option 2: TanStack Table**(高性能,数据量>1000行) - 优点:虚拟滚动,性能极佳 - 缺点:Headless需自己实现UI - 适用场景:处理大数据集 **决策**:先用Ant Design Table,如性能不佳再迁移到TanStack Table --- #### 6.1.3 核心数据结构 ```typescript interface VerifyRow { id: string; rowIndex: number; originalText: string; // 原文摘要(前50字) fullText: string; // 原文全文(侧边栏显示) results: Record; status: 'clean' | 'conflict' | 'resolved'; conflictFields: string[]; // 冲突字段名称列表 } ``` --- #### 6.1.4 功能实现 **1. 获取验证数据** ```typescript const [rows, setRows] = useState([]); const [pagination, setPagination] = useState({ page: 1, pageSize: 20, total: 0 }); useEffect(() => { fetch(`/api/v1/dc/tool-b/tasks/${taskId}/items?status=conflict&page=${pagination.page}`) .then(res => res.json()) .then(data => { setRows(data.data.items); setPagination({ ...pagination, total: data.data.pagination.total }); }); }, [taskId, pagination.page]); ``` **2. 表格列配置** ```typescript const columns: ColumnsType = [ { title: '#', dataIndex: 'rowIndex', width: 60, fixed: 'left', }, { title: '原文摘要', dataIndex: 'originalText', width: 200, render: (text, record) => ( {text} ), }, // 动态字段列(根据模板生成) ...fields.map(field => ({ title: field.name, dataIndex: ['results', field.name], width: 180, render: (cellData: { A: string; B: string; chosen: string | null }, record: VerifyRow) => { const isConflict = cellData.A !== cellData.B && cellData.chosen === null; if (isConflict) { return handleAdopt(record.id, field.name, value)} />; } return ; }, })), { title: '状态', dataIndex: 'status', width: 100, fixed: 'right', render: (status) => { if (status === 'clean' || status === 'resolved') { return 通过; } return 待裁决; }, }, ]; ``` **3. 冲突单元格组件** ⭐ 核心 ```typescript // components/ConflictCell.tsx interface ConflictCellProps { fieldName: string; valueA: string; valueB: string; onAdopt: (value: string) => void; } const ConflictCell: React.FC = ({ fieldName, valueA, valueB, onAdopt }) => { return ( {/* 选项A - DeepSeek */} onAdopt(valueA)} > {valueA} DS {/* 选项B - Qwen */} onAdopt(valueB)} > {valueB} QW ); }; ``` **4. 裁决逻辑** ```typescript const handleAdopt = async (itemId: string, fieldName: string, value: string) => { // 乐观更新UI setRows(prevRows => prevRows.map(row => { if (row.id !== itemId) return row; const newResults = { ...row.results }; newResults[fieldName].chosen = value; // 检查该行是否还有未解决的冲突 const hasConflict = Object.values(newResults).some( cell => cell.chosen === null && cell.A !== cell.B ); return { ...row, results: newResults, status: hasConflict ? 'conflict' : 'resolved', }; }) ); // 调用API try { await fetch(`/api/v1/dc/tool-b/items/${itemId}/resolve`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ resolvedData: { [fieldName]: value } }) }); } catch (error) { message.error('裁决失败'); // 回滚UI // TODO: 实现回滚逻辑 } }; ``` **5. 侧边栏原文显示** ```typescript const [selectedRowId, setSelectedRowId] = useState(null); // 点击行时打开侧边栏 const onRow = (record: VerifyRow) => ({ onClick: () => setSelectedRowId(record.id), }); // Drawer组件 setSelectedRowId(null)} > {(() => { const row = rows.find(r => r.id === selectedRowId); if (!row) return null; return ( {row.fullText} {Object.keys(row.results).map(fieldName => ( {fieldName} ))} ); })()} ``` **6. Toolbar统计** ```typescript const conflictCount = rows.filter(r => r.status === 'conflict').length; const resolvedCount = rows.filter(r => r.status === 'resolved').length; const cleanCount = rows.filter(r => r.status === 'clean').length; 总数据: {rows.length} {conflictCount > 0 ? ( {conflictCount} 条冲突待裁决 ) : ( 所有冲突已解决 )} 导出当前结果 完成并入库 ``` --- ### 6.2 验收标准 **数据加载**: - [ ] 成功获取验证数据 - [ ] 表格列根据模板动态生成 - [ ] 分页功能正常 **冲突单元格**: - [ ] 冲突单元格显示A/B两个按钮 - [ ] 按钮显示正确的值和模型标识(DS/QW) - [ ] 点击按钮后采纳值 - [ ] UI乐观更新(立即生效) - [ ] API调用成功 **侧边栏**: - [ ] 点击行时侧边栏滑出 - [ ] 显示完整病历原文 - [ ] 显示字段标签(冲突字段高亮) - [ ] 点击关闭按钮或外部区域关闭侧边栏 **Toolbar**: - [ ] 冲突数统计正确 - [ ] 实时更新(裁决后减少) - [ ] 导出按钮可点击 - [ ] 完成按钮跳转到Step 5 --- ## 📦 七、Phase 5: Tool B - Step 5(Day 5上午) ### 7.1 结果导出(3小时) #### 7.1.1 页面设计 **组件**:`pages/tool-b/Step5Result.tsx` **UI参考**:原型V4第572-607行 **布局**: 1. 完成图标和标题 2. 统计卡片(4个) 3. 操作按钮(下载、流转) --- #### 7.1.2 功能实现 **1. 统计数据获取** ```typescript const [stats, setStats] = useState({ totalCount: 0, cleanCount: 0, conflictCount: 0, failedCount: 0, totalTokens: 0, totalCost: 0, }); useEffect(() => { fetch(`/api/v1/dc/tool-b/tasks/${taskId}/progress`) .then(res => res.json()) .then(data => setStats(data.data)); }, [taskId]); ``` **2. Excel导出** ```typescript const handleExport = async () => { try { const response = await fetch(`/api/v1/dc/tool-b/tasks/${taskId}/export`, { method: 'POST' }); const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `extraction_result_${taskId}.xlsx`; a.click(); message.success('导出成功'); } catch (error) { message.error('导出失败'); } }; ``` **3. 流转到工具C** ```typescript const handleGoToToolC = () => { // 跳转到工具C,传递taskId navigate(`/data-cleaning/tool-c?sourceTaskId=${taskId}`); }; ``` --- ### 7.2 验收标准 - [ ] 统计卡片数据正确显示 - [ ] Token消耗和成本计算正确 - [ ] 下载按钮触发Excel导出 - [ ] Excel文件包含4个Sheet(完整结果、无冲突、冲突项、失败项) - [ ] 流转到工具C按钮可点击(提示暂未开发) - [ ] 返回Portal按钮可点击 --- ## 🧪 八、Phase 6: 集成测试(Day 5下午) ### 8.1 端到端测试清单 #### 8.1.1 完整流程测试 **测试场景**:肺癌病理报告提取 **测试步骤**: 1. [ ] 访问Portal页面(`/data-cleaning`) 2. [ ] 点击Tool B卡片,跳转到Step 1 3. [ ] 上传测试Excel文件(包含病理报告列) 4. [ ] 选择"病理报告"列,触发健康检查 5. [ ] 验证健康度显示为"绿色 - 优秀" 6. [ ] 进入Step 2,选择"肺癌 + 病理报告" 7. [ ] 验证字段自动加载(5个字段) 8. [ ] 编辑一个字段(修改描述) 9. [ ] 进入Step 3,点击"开始提取" 10. [ ] 验证进度条开始更新 11. [ ] 验证日志滚动显示 12. [ ] 等待任务完成(或模拟完成) 13. [ ] 自动跳转到Step 4 14. [ ] 验证验证网格加载数据 15. [ ] 找到一个冲突单元格,点击"DS"按钮采纳 16. [ ] 验证冲突数减1 17. [ ] 点击某行,验证侧边栏显示原文 18. [ ] 点击"完成并入库",跳转到Step 5 19. [ ] 验证统计卡片数据正确 20. [ ] 点击"下载",验证Excel下载成功 --- #### 8.1.2 异常场景测试 **测试场景1:健康检查失败** - [ ] 上传Excel,选择空值率>50%的列 - [ ] 验证健康度显示为"红色 - 警告" - [ ] 验证"下一步"按钮被禁用 **测试场景2:任务失败** - [ ] 模拟API返回失败状态 - [ ] 验证错误提示显示 - [ ] 验证可以重新尝试 **测试场景3:网络错误** - [ ] 断网后点击"下一步" - [ ] 验证错误提示显示 - [ ] 重新联网后可继续 --- ### 8.2 性能测试 **测试场景**:大数据量验证网格 **测试步骤**: 1. [ ] 加载500行数据 2. [ ] 验证表格滚动流畅(无卡顿) 3. [ ] 验证分页功能正常 4. [ ] 验证排序/筛选功能(如实现) **性能指标**: - [ ] 首屏加载时间 < 2秒 - [ ] 表格滚动帧率 > 30fps - [ ] 裁决操作响应时间 < 500ms --- ### 8.3 兼容性测试 **浏览器**: - [ ] Chrome 120+ - [ ] Edge 120+ - [ ] Firefox 120+ - [ ] Safari 17+(Mac) **分辨率**: - [ ] 1920x1080(常规) - [ ] 1440x900(笔记本) - [ ] 2560x1440(高分屏) --- ## 📝 九、文档完善(Day 6) ### 9.1 开发文档 **需要补充的文档**: 1. [ ] **前端代码结构说明**(`docs/03-业务模块/DC-数据清洗整理/02-技术设计/前端架构设计.md`) 2. [ ] **组件使用文档**(`frontend-v2/src/modules/dc/README.md`) 3. [ ] **API对接文档**(补充到现有API设计文档) 4. [ ] **本开发计划的完成总结**(`docs/03-业务模块/DC-数据清洗整理/06-开发记录/Tool-B开发完成总结.md`) --- ### 9.2 用户文档(可选) **需要创建的文档**: 1. [ ] **Tool B使用指南**(`docs/03-业务模块/DC-数据清洗整理/07-用户手册/Tool-B使用指南.md`) 2. [ ] **常见问题FAQ**(`docs/03-业务模块/DC-数据清洗整理/07-用户手册/FAQ.md`) --- ## 🎯 十、关键技术要点 ### 10.1 必须遵守的规范 #### 云原生规范 ⭐ 强制 参考:`docs/04-开发规范/08-云原生开发规范.md` **禁止**: - ❌ 使用`fs.writeFile()`等本地文件操作 - ❌ 使用全局变量缓存数据 - ❌ 硬编码配置(IP、密钥等) - ❌ 同步长任务(>10秒) - ❌ 重复实现平台能力 **必须**: - ✅ 使用`storage`服务存储文件 - ✅ 使用`cache`服务缓存数据 - ✅ 使用环境变量配置 - ✅ 异步任务 + 进度轮询 - ✅ 复用平台基础设施 --- ### 10.2 复用策略 #### 后端复用(参考) ```typescript // ✅ 正确:复用平台能力 import { storage } from '@/common/storage'; import { logger } from '@/common/logging'; import { cache } from '@/common/cache'; import { LLMFactory } from '@/common/llm/adapters/LLMFactory'; ``` #### 前端复用(需实现) ```typescript // 复用ASL模块的组件和Hook import { usePolling } from '@/shared/hooks/usePolling'; import { FileUploader } from '@/shared/components/FileUploader'; import { ProgressBar } from '@/shared/components/ProgressBar'; ``` --- ### 10.3 状态管理 **推荐方案**:React Query(ASL模块使用) ```typescript // 使用React Query管理API调用 import { useQuery, useMutation } from '@tanstack/react-query'; // 获取任务进度 const { data: progress } = useQuery({ queryKey: ['task-progress', taskId], queryFn: () => fetch(`/api/v1/dc/tool-b/tasks/${taskId}/progress`).then(res => res.json()), refetchInterval: 5000, // 每5秒轮询 enabled: !!taskId && status === 'processing', }); // 裁决冲突 const resolveMutation = useMutation({ mutationFn: ({ itemId, resolvedData }) => fetch(`/api/v1/dc/tool-b/items/${itemId}/resolve`, { method: 'POST', body: JSON.stringify({ resolvedData }), }), onSuccess: () => { queryClient.invalidateQueries(['task-items', taskId]); message.success('裁决成功'); }, }); ``` --- ### 10.4 TypeScript类型定义 **统一类型文件**:`frontend-v2/src/modules/dc/types/toolB.ts` ```typescript // 健康检查结果 export interface HealthCheckResult { status: 'good' | 'bad'; emptyRate: number; avgLength: number; totalRows: number; estimatedTokens: number; message: string; } // 模板定义 export interface Template { id: string; diseaseType: string; reportType: string; displayName: string; fields: Field[]; } export interface Field { id: string; name: string; desc: string; width?: string; } // 任务状态 export interface Task { id: string; projectName: string; status: 'pending' | 'processing' | 'completed' | 'failed'; totalCount: number; processedCount: number; cleanCount: number; conflictCount: number; failedCount: number; totalTokens: number; totalCost: number; } // 验证行 export interface VerifyRow { id: string; rowIndex: number; originalText: string; fullText: string; results: Record; status: 'clean' | 'conflict' | 'resolved'; conflictFields: string[]; } ``` --- ## 📊 十一、风险评估与应对 ### 11.1 技术风险 | 风险 | 概率 | 影响 | 应对措施 | |------|------|------|---------| | **数据库表未创建** | 中 | 高 | 立即执行`npx prisma db push`验证 | | **后端API未测试** | 中 | 高 | 开发前先用REST Client测试6个端点 | | **性能问题(大数据量)** | 低 | 中 | 预留TanStack Table迁移方案 | | **Excel解析失败** | 低 | 中 | 使用xlsx库,增加错误处理 | | **LLM调用超时** | 低 | 中 | 后端已实现重试机制 | --- ### 11.2 时间风险 | 风险 | 概率 | 影响 | 应对措施 | |------|------|------|---------| | **Step 4开发超时** | 中 | 高 | 预留2天(9小时),可拆分子任务 | | **集成测试发现问题** | 中 | 中 | 预留1天缓冲时间 | | **原型与需求不符** | 低 | 高 | 开发前与产品确认原型 | --- ### 11.3 依赖风险 | 风险 | 概率 | 影响 | 应对措施 | |------|------|------|---------| | **后端API变更** | 低 | 高 | 使用TypeScript类型,编译时检查 | | **平台能力Bug** | 低 | 中 | 已完整测试,风险低 | | **设计变更** | 低 | 中 | 组件化设计,易于修改 | --- ## 🎉 十二、预期成果 ### 12.1 交付物清单 **代码**: - [ ] Portal工作台页面(完整) - [ ] Tool B 5个Step页面(完整) - [ ] 共享组件库(TaskList、AssetLibrary、ConflictCell等) - [ ] API服务封装(toolBApi.ts、portalApi.ts) - [ ] TypeScript类型定义(完整) - [ ] 样式文件(TailwindCSS + Ant Design) **文档**: - [ ] 前端架构设计文档 - [ ] 组件使用文档 - [ ] API对接文档 - [ ] 开发完成总结 - [ ] 用户使用指南(可选) **测试**: - [ ] 端到端测试报告 - [ ] 性能测试报告 - [ ] 兼容性测试报告 --- ### 12.2 功能完整性 **Portal工作台**: - ✅ 3个工具卡片(Tool B可用) - ✅ 最近任务列表(实时轮询) - ✅ 数据资产库(Tab切换) **Tool B - 病历结构化机器人**: - ✅ Step 1: Excel上传 + 健康检查 - ✅ Step 2: 智能模板配置(3个预设模板) - ✅ Step 3: 双盲提取进度监控 - ✅ Step 4: 冲突验证网格(支持裁决) - ✅ Step 5: 结果导出(Excel + 流转) --- ### 12.3 代码质量 **代码规范**: - ✅ 遵守云原生开发规范 - ✅ TypeScript类型安全 - ✅ ESLint无错误 - ✅ 代码注释完善 **性能指标**: - ✅ 首屏加载 < 2s - ✅ 表格滚动流畅 - ✅ 裁决响应 < 500ms - ✅ 内存占用 < 200MB **用户体验**: - ✅ 流畅的5步流程 - ✅ 实时进度反馈 - ✅ 直观的冲突验证界面 - ✅ 友好的错误提示 --- ## 🚀 十三、下一步行动 ### 13.1 立即执行(Day 0) **1. 验证数据库表** ```bash cd AIclinicalresearch/backend npx prisma db push --skip-generate npx prisma studio # 可视化验证 ``` **2. 测试后端API** ```bash # 启动后端服务 npm run dev # 使用REST Client测试6个API端点 # 或使用Postman/Insomnia ``` **3. Git提交计划** ```bash # 建议每完成一个Phase就提交 git commit -m "feat(dc): Complete Portal page (Phase 1)" git commit -m "feat(dc/tool-b): Complete Step1&2 (Phase 2)" git commit -m "feat(dc/tool-b): Complete Step4 conflict grid (Phase 4)" ``` --- ### 13.2 开发启动(Day 1) **任务1:创建Portal页面骨架** ```bash cd frontend-v2/src/modules/dc mkdir -p pages components hooks services types # 创建Portal.tsx touch pages/Portal.tsx # 创建路由配置 # 修改 index.tsx ``` **任务2:实现3个工具卡片** ```bash # 创建ToolCard组件 touch components/ToolCard.tsx ``` **任务3:实现任务列表(使用Mock数据)** ```bash # 创建TaskList组件 touch components/TaskList.tsx # 创建useRecentTasks Hook touch hooks/useRecentTasks.ts ``` --- ### 13.3 持续跟进 **每日检查点**: - [ ] 代码是否Git提交 - [ ] Todo列表是否更新 - [ ] 遇到的问题是否记录 - [ ] API调用是否正常 **每周检查点**: - [ ] 功能完成度是否符合预期 - [ ] 是否需要调整计划 - [ ] 是否需要额外资源 --- ## 📞 十四、联系与支持 ### 14.1 技术支持 **遇到问题时**: 1. 查阅平台基础设施文档(`backend/src/common/README.md`) 2. 查阅云原生开发规范(`docs/04-开发规范/08-云原生开发规范.md`) 3. 参考ASL模块代码(`frontend-v2/src/modules/asl/`) 4. 查看后端API代码(`backend/src/modules/dc/tool-b/`) ### 14.2 代码审查 **提交前自检**: - [ ] 是否遵守云原生规范 - [ ] 是否复用平台能力 - [ ] TypeScript类型是否完整 - [ ] 是否有TODO/FIXME注释 - [ ] 是否有console.log(应使用logger) --- ## 🎯 十五、总结 ### 核心目标 完成DC模块Tool B的前端开发,实现从文件上传到结果导出的完整5步流程,特别是**冲突验证网格**这一核心功能。 ### 关键成功因素 1. ✅ **后端已100%完成**,前端可专注UI和交互 2. ✅ **平台能力完善**,可直接复用,无需重复开发 3. ✅ **设计文档齐全**,有清晰的原型和需求 4. ✅ **参考模块成熟**,ASL模块可作为参考 5. ✅ **架构方案明确**,采用方案A(独立Portal) ### 预期收益 - **用户价值**:提供AI驱动的病历结构化能力,双模型交叉验证提高数据质量 - **技术价值**:完整的前端模块化架构,可复用组件库 - **商业价值**:Tool B可独立销售,满足医疗数据处理需求 --- **开发计划制定完成!** ✅ **下一步**:验证数据库表 → 测试后端API → 开始Phase 1(Portal页面开发) **预计完成时间**:5-6个工作日 **祝开发顺利!** 🚀
{row.fullText}