Files
AIclinicalresearch/docs/03-业务模块/DC-数据清洗整理/04-开发计划/DC模块Tool-B开发计划.md
HaHafeng 8a17369138 feat(dc): Complete Tool B MVP with full API integration and bug fixes
Phase 5: Export Feature
- Add Excel export API endpoint (GET /tasks/:id/export)
- Fix Content-Disposition header encoding for Chinese filenames
- Fix export field order to match template definition
- Export finalResult or resultA as fallback

API Integration Fixes (Phase 1-5):
- Fix API response parsing (return result.data consistently)
- Fix field name mismatch (fileKey -> sourceFileKey)
- Fix Excel parsing bug (range:99 -> slice(0,100))
- Add file upload with Excel parsing (columns, totalRows)
- Add detailed error logging for debugging

LLM Integration Fixes:
- Fix LLM call method: LLMFactory.createLLM -> getAdapter
- Fix adapter interface: generateText -> chat([messages])
- Fix response fields: text -> content, tokensUsed -> usage.totalTokens
- Fix model names: qwen-max -> qwen3-72b

React Infinite Loop Fixes:
- Step2: Remove updateState from useEffect deps
- Step3: Add useRef to prevent Strict Mode double execution
- Step3: Clear interval on API failure (max 3 retries)
- Step4: Add useRef to prevent infinite data loading
- Add cleanup functions to all useEffect hooks

Frontend Enhancements:
- Add comprehensive error handling with user-friendly messages
- Remove debug console.logs (production ready)
- Fix TypeScript type definitions (TaskProgress, ExtractionItem)
- Improve Step4Verify data transformation logic

Backend Enhancements:
- Add detailed logging at each step for debugging
- Add parameter validation in controllers
- Improve error messages with stack traces (dev mode)
- Add export field ordering by template definition

Documentation Updates:
- Update module status: Tool B MVP completed
- Create MVP completion summary (06-开发记录)
- Create technical debt document (07-技术债务)
- Update API documentation with test status
- Update database documentation with verified status
- Update system overview with DC module status
- Document 4 known issues (Excel preprocessing, progress display, etc.)

Testing Results:
- File upload: 9 rows parsed successfully
- Health check: Column validation working
- Dual model extraction: DeepSeek-V3 + Qwen-Max both working
- Processing time: ~49s for 9 records (~5s per record)
- Token usage: ~10k tokens total (~1.1k per record)
- Conflict detection: 1 clean, 8 conflicts (88.9% conflict rate)
- Excel export: Working with proper encoding

Files Changed:
Backend (~500 lines):
- ExtractionController.ts: Add upload endpoint, improve logging
- DualModelExtractionService.ts: Fix LLM call methods, add detailed logs
- HealthCheckService.ts: Fix Excel range parsing
- routes/index.ts: Add upload route

Frontend (~200 lines):
- toolB.ts: Fix API response parsing, add error handling
- Step1Upload.tsx: Integrate upload and health check APIs
- Step2Schema.tsx: Fix infinite loop, load templates from API
- Step3Processing.tsx: Fix infinite loop, integrate progress polling
- Step4Verify.tsx: Fix infinite loop, transform backend data correctly
- Step5Result.tsx: Integrate export API
- index.tsx: Add file metadata to state

Scripts:
- check-task-progress.mjs: Database inspection utility

Docs (~8 files):
- 00-模块当前状态与开发指南.md: Update to v2.0
- API设计文档.md: Mark all endpoints as tested
- 数据库设计文档.md: Update verification status
- DC模块Tool-B开发计划.md: Add MVP completion notice
- DC模块Tool-B开发任务清单.md: Update progress to 100%
- Tool-B-MVP完成总结.md: New completion summary
- Tool-B技术债务清单.md: New technical debt document
- 00-系统当前状态与开发指南.md: Update DC module status

Status: Tool B MVP complete and production ready
2025-12-03 15:07:39 +08:00

43 KiB
Raw Blame History

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)
  • 有测试数据可用于开发调试

验证脚本

cd backend
node scripts/check-dc-tables.mjs  # 执行数据库表检查脚本

结论 数据库完全准备就绪,可以开始前端开发!


1.3 前端代码状态 0%仅Placeholder

代码位置frontend-v2/src/modules/dc/

frontend-v2/src/modules/dc/
├── index.tsx           # ❌ 仅Placeholder14行
├── components/         # 📁 空
├── pages/              # 📁 空文件夹结构
│   ├── tool-a/         # 📁 空
│   ├── tool-b/         # 📁 空
│   └── tool-c/         # 📁 空
└── types/              # 📁 空

当前内容

// frontend-v2/src/modules/dc/index.tsx
import Placeholder from '@/shared/components/Placeholder'

const DCModule = () => {
  return (
    <Placeholder 
      title="数据清洗模块"
      description="功能规划中,将提供智能数据清洗和整理工具"
      moduleName="DC - Data Cleaning"
    />
  )
}
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

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

APIGET /api/v1/dc/tasks/recent(需后端新增)

数据结构

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

APIGET /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 路由配置

// 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 (
    <Routes>
      <Route path="" element={<Portal />} />
      <Route path="tool-b/*" element={<ToolBModule />} />
      {/* 未来扩展 */}
      <Route path="tool-a/*" element={<ToolAPlaceholder />} />
      <Route path="tool-c/*" element={<ToolCPlaceholder />} />
    </Routes>
  );
};

export default DCModule;

3.4.3 API对接

需要后端新增的API

// 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&2Day 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文件上传

// 使用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. 列选择与健康检查

const [columns, setColumns] = useState<string[]>([]);
const [selectedColumn, setSelectedColumn] = useState('');
const [healthResult, setHealthResult] = useState<HealthCheckResult | null>(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. 健康度显示

// 根据status显示不同样式
{healthResult?.status === 'good' && (
  <Alert
    type="success"
    icon={<CheckCircle2 />}
    message="健康度优秀,适合提取"
    description={
      <div className="mt-2">
        <span>平均字符: {healthResult.avgLength}</span>
        <span className="ml-4">空值率: {(healthResult.emptyRate * 100).toFixed(1)}%</span>
        <span className="ml-4 text-purple-600 font-bold">
          预计Token: {healthResult.estimatedTokens.toLocaleString()}
        </span>
      </div>
    }
  />
)}

{healthResult?.status === 'bad' && (
  <Alert
    type="error"
    icon={<AlertTriangle />}
    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. 获取模板列表

const [templates, setTemplates] = useState<Template[]>([]);
const [diseaseType, setDiseaseType] = useState('');
const [reportType, setReportType] = useState('');
const [fields, setFields] = useState<Field[]>([]);

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. 字段编辑

// 添加字段
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预览

// 动态生成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
<SyntaxHighlighter language="javascript" style={atomOneDark}>
  {generatePrompt()}
</SyntaxHighlighter>

4.3 验收标准

Step 1:

  • Excel上传成功获取fileKey
  • 列名列表正确显示
  • 健康检查API调用成功
  • 健康度卡片根据结果正确显示(绿色/红色)
  • Token预估数值正确
  • 空值率>50%时禁止进入下一步

Step 2:

  • 模板列表加载成功
  • 疾病类型和报告类型选择框正常
  • 选择模板后字段自动加载
  • 字段支持添加/删除/编辑
  • Prompt预览实时更新
  • 代码高亮正确显示

⚙️ 五、Phase 3: Tool B - Step 3Day 3上午

5.1 处理进度监控3小时

5.1.1 页面设计

组件pages/tool-b/Step3Processing.tsx

UI参考原型V4第375-400行

布局

  1. 圆形进度环(双模型动画)
  2. 进度百分比和文本
  3. 日志滚动区域

5.1.2 功能实现

1. 创建任务

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. 进度轮询

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. 进度动画

// 使用Ant Design Progress组件或自定义SVG
<Progress
  type="circle"
  percent={progress}
  format={percent => (
    <div>
      <div className="text-2xl font-bold">{percent}%</div>
      <div className="text-sm text-gray-500">双盲提取中</div>
    </div>
  )}
  strokeColor={{
    '0%': '#9333ea',
    '100%': '#4f46e5'
  }}
/>

// 双模型动画(两个小球跳动)
<div className="flex gap-2">
  <div className="w-3 h-3 bg-blue-500 rounded-full animate-bounce" />
  <div className="w-3 h-3 bg-orange-500 rounded-full animate-bounce delay-200" />
</div>

4. 日志显示

<div className="h-40 overflow-y-auto bg-slate-50 p-4 rounded font-mono text-xs">
  {logs.map((log, i) => (
    <div key={i} className="mb-1 text-slate-600">
      <span className="text-slate-400">[{log.timestamp}]</span> {log.message}
    </div>
  ))}
  <div className="animate-pulse text-purple-500">_</div>
</div>

5.2 验收标准

  • 点击"开始提取"后成功创建任务
  • 获取到taskId
  • 进度轮询正常每5秒
  • 进度条实时更新
  • 日志滚动区域正常显示
  • 任务完成后自动跳转到Step 4
  • 任务失败时显示错误提示

🎯 六、Phase 4: Tool B - Step 4Day 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 核心数据结构

interface VerifyRow {
  id: string;
  rowIndex: number;
  originalText: string; // 原文摘要前50字
  fullText: string; // 原文全文(侧边栏显示)
  results: Record<string, {
    A: string; // DeepSeek结果
    B: string; // Qwen结果
    chosen: string | null; // 用户采纳的值null=未解决)
  }>;
  status: 'clean' | 'conflict' | 'resolved';
  conflictFields: string[]; // 冲突字段名称列表
}

6.1.4 功能实现

1. 获取验证数据

const [rows, setRows] = useState<VerifyRow[]>([]);
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. 表格列配置

const columns: ColumnsType<VerifyRow> = [
  {
    title: '#',
    dataIndex: 'rowIndex',
    width: 60,
    fixed: 'left',
  },
  {
    title: '原文摘要',
    dataIndex: 'originalText',
    width: 200,
    render: (text, record) => (
      <div className="flex items-center gap-2">
        <FileText size={14} className="text-slate-300" />
        <Tooltip title={text}>
          <span className="truncate w-40">{text}</span>
        </Tooltip>
      </div>
    ),
  },
  // 动态字段列(根据模板生成)
  ...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 <ConflictCell 
          fieldName={field.name}
          valueA={cellData.A}
          valueB={cellData.B}
          onAdopt={(value) => handleAdopt(record.id, field.name, value)}
        />;
      }
      
      return <CleanCell value={cellData.chosen || cellData.A} />;
    },
  })),
  {
    title: '状态',
    dataIndex: 'status',
    width: 100,
    fixed: 'right',
    render: (status) => {
      if (status === 'clean' || status === 'resolved') {
        return <Tag color="success">通过</Tag>;
      }
      return <Tag color="warning" className="animate-pulse">待裁决</Tag>;
    },
  },
];

3. 冲突单元格组件 核心

// components/ConflictCell.tsx
interface ConflictCellProps {
  fieldName: string;
  valueA: string;
  valueB: string;
  onAdopt: (value: string) => void;
}

const ConflictCell: React.FC<ConflictCellProps> = ({ fieldName, valueA, valueB, onAdopt }) => {
  return (
    <div className="flex flex-col gap-1.5 bg-orange-50 p-2 rounded">
      {/* 选项A - DeepSeek */}
      <button
        className="text-left text-xs px-2 py-1.5 rounded border border-blue-200 bg-white hover:bg-blue-50 hover:border-blue-400 transition-all flex justify-between group"
        onClick={() => onAdopt(valueA)}
      >
        <Tooltip title={valueA}>
          <span className="truncate max-w-[100px]">{valueA}</span>
        </Tooltip>
        <Badge className="text-[10px] text-blue-400 group-hover:text-blue-600">DS</Badge>
      </button>
      
      {/* 选项B - Qwen */}
      <button
        className="text-left text-xs px-2 py-1.5 rounded border border-orange-200 bg-white hover:bg-orange-50 hover:border-orange-400 transition-all flex justify-between group"
        onClick={() => onAdopt(valueB)}
      >
        <Tooltip title={valueB}>
          <span className="truncate max-w-[100px]">{valueB}</span>
        </Tooltip>
        <Badge className="text-[10px] text-orange-400 group-hover:text-orange-600">QW</Badge>
      </button>
    </div>
  );
};

4. 裁决逻辑

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. 侧边栏原文显示

const [selectedRowId, setSelectedRowId] = useState<string | null>(null);

// 点击行时打开侧边栏
const onRow = (record: VerifyRow) => ({
  onClick: () => setSelectedRowId(record.id),
});

// Drawer组件
<Drawer
  title="病历原文详情"
  placement="right"
  width={400}
  open={!!selectedRowId}
  onClose={() => setSelectedRowId(null)}
>
  {(() => {
    const row = rows.find(r => r.id === selectedRowId);
    if (!row) return null;
    
    return (
      <div className="prose">
        <p className="whitespace-pre-wrap font-serif leading-7">
          {row.fullText}
        </p>
        
        <Divider />
        
        <div className="flex flex-wrap gap-2">
          {Object.keys(row.results).map(fieldName => (
            <Tag
              key={fieldName}
              color={row.conflictFields.includes(fieldName) ? 'orange' : 'default'}
            >
              {fieldName}
            </Tag>
          ))}
        </div>
      </div>
    );
  })()}
</Drawer>

6. Toolbar统计

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;

<div className="flex items-center gap-4 mb-4">
  <div className="bg-slate-100 px-3 py-1.5 rounded">
    总数据: <strong>{rows.length}</strong>
  </div>
  
  {conflictCount > 0 ? (
    <div className="bg-orange-50 px-3 py-1.5 rounded text-orange-700 animate-pulse">
      <AlertTriangle size={16} className="inline mr-1" />
      <strong>{conflictCount}</strong> 条冲突待裁决
    </div>
  ) : (
    <div className="bg-emerald-50 px-3 py-1.5 rounded text-emerald-700">
      <CheckCircle2 size={16} className="inline mr-1" />
      所有冲突已解决
    </div>
  )}
  
  <Button onClick={handleExport}>导出当前结果</Button>
  <Button type="primary" onClick={handleComplete}>完成并入库</Button>
</div>

6.2 验收标准

数据加载

  • 成功获取验证数据
  • 表格列根据模板动态生成
  • 分页功能正常

冲突单元格

  • 冲突单元格显示A/B两个按钮
  • 按钮显示正确的值和模型标识DS/QW
  • 点击按钮后采纳值
  • UI乐观更新立即生效
  • API调用成功

侧边栏

  • 点击行时侧边栏滑出
  • 显示完整病历原文
  • 显示字段标签(冲突字段高亮)
  • 点击关闭按钮或外部区域关闭侧边栏

Toolbar

  • 冲突数统计正确
  • 实时更新(裁决后减少)
  • 导出按钮可点击
  • 完成按钮跳转到Step 5

📦 七、Phase 5: Tool B - Step 5Day 5上午

7.1 结果导出3小时

7.1.1 页面设计

组件pages/tool-b/Step5Result.tsx

UI参考原型V4第572-607行

布局

  1. 完成图标和标题
  2. 统计卡片4个
  3. 操作按钮(下载、流转)

7.1.2 功能实现

1. 统计数据获取

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导出

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

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. 常见问题FAQdocs/03-业务模块/DC-数据清洗整理/07-用户手册/FAQ.md

🎯 十、关键技术要点

10.1 必须遵守的规范

云原生规范 强制

参考:docs/04-开发规范/08-云原生开发规范.md

禁止

  • 使用fs.writeFile()等本地文件操作
  • 使用全局变量缓存数据
  • 硬编码配置IP、密钥等
  • 同步长任务(>10秒
  • 重复实现平台能力

必须

  • 使用storage服务存储文件
  • 使用cache服务缓存数据
  • 使用环境变量配置
  • 异步任务 + 进度轮询
  • 复用平台基础设施

10.2 复用策略

后端复用(参考)

// ✅ 正确:复用平台能力
import { storage } from '@/common/storage';
import { logger } from '@/common/logging';
import { cache } from '@/common/cache';
import { LLMFactory } from '@/common/llm/adapters/LLMFactory';

前端复用(需实现)

// 复用ASL模块的组件和Hook
import { usePolling } from '@/shared/hooks/usePolling';
import { FileUploader } from '@/shared/components/FileUploader';
import { ProgressBar } from '@/shared/components/ProgressBar';

10.3 状态管理

推荐方案React QueryASL模块使用

// 使用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

// 健康检查结果
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<string, {
    A: string;
    B: string;
    chosen: string | null;
  }>;
  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. 验证数据库表

cd AIclinicalresearch/backend
npx prisma db push --skip-generate
npx prisma studio  # 可视化验证

2. 测试后端API

# 启动后端服务
npm run dev

# 使用REST Client测试6个API端点
# 或使用Postman/Insomnia

3. Git提交计划

# 建议每完成一个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页面骨架

cd frontend-v2/src/modules/dc
mkdir -p pages components hooks services types

# 创建Portal.tsx
touch pages/Portal.tsx

# 创建路由配置
# 修改 index.tsx

任务2实现3个工具卡片

# 创建ToolCard组件
touch components/ToolCard.tsx

任务3实现任务列表使用Mock数据

# 创建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 1Portal页面开发

预计完成时间5-6个工作日

祝开发顺利! 🚀