Files
AIclinicalresearch/docs/03-业务模块/DC-数据清洗整理/02-技术设计/数据库设计文档-DC模块(完整版).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

517 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 数据库设计文档 - 工具B病历结构化机器人
> **模块**: DC数据清洗整理 - 工具B
> **版本**: V2.0 (MVP)
> **Schema**: `dc_schema`
> **更新日期**: 2025-12-03
> **状态**: ✅ MVP完成已验证可用真实数据测试通过
---
## 📋 目录
- [一、概述](#一概述)
- [二、Schema设计原则](#二schema设计原则)
- [三、数据表设计](#三数据表设计)
- [四、索引设计](#四索引设计)
- [五、外键约束](#五外键约束)
- [六、数据生命周期](#六数据生命周期)
---
## 一、概述
### 1.1 设计目标
工具B的数据库设计旨在支持
- ✅ 双大模型交叉验证的文本结构化
- ✅ 大规模异步任务处理1000+条记录)
- ✅ 冲突检测与人工裁决
- ✅ 预设模板管理与复用
- ✅ 健康检查缓存优化
### 1.2 表关系总览
```
dc_schema ✅ 已创建并运行中
├── dc_health_checks [健康检查缓存] ✅ 运行正常
├── dc_templates [预设模板] ✅ 3个预设模板可用
├── dc_extraction_tasks [提取任务] ✅ 已完成多个任务
│ └── dc_extraction_items [提取记录] (1:N) ✅ 双模型结果正常保存
```
**✅ MVP完成状态2025-12-03**
- 所有表正常工作,已处理多个真实任务
- 3个预设模板肺癌病理报告、糖尿病入院记录、高血压门诊病历
- 真实测试9条病理数据提取成功100%成功率
- 双模型结果resultA、resultB、finalResult字段正常保存
- Token统计totalTokens字段正常累加
- 冲突检测conflictFields数组正常工作
- 验证脚本:`backend/scripts/check-task-progress.mjs`
### 1.3 技术栈
- **数据库**: PostgreSQL 15
- **ORM**: Prisma 6
- **Schema隔离**: `dc_schema`(独立命名空间)
- **JSON字段**: 使用JSONB类型高性能查询
---
## 二、Schema设计原则
### 2.1 Schema隔离
```sql
-- 所有表使用dc_schema命名空间
CREATE TABLE "dc_schema"."dc_health_checks" (...);
CREATE TABLE "dc_schema"."dc_extraction_tasks" (...);
```
**优势**
- ✅ 与其他模块完全隔离platform_schema、asl_schema等
- ✅ 数据安全,避免误操作
- ✅ 便于模块化管理和迁移
### 2.2 命名规范
| 规则 | 说明 | 示例 |
|------|------|------|
| **表名前缀** | `dc_` | `dc_extraction_tasks` |
| **字段命名** | snake_case | `user_id`, `source_file_key` |
| **时间戳** | 统一后缀 | `created_at`, `started_at` |
| **外键** | 实体名_id | `task_id`, `user_id` |
### 2.3 JSONB字段使用场景
| 字段 | 类型 | 原因 |
|------|------|------|
| `target_fields` | JSONB | 灵活的字段配置 |
| `result_a/result_b` | JSONB | 动态提取结果 |
| `final_result` | JSONB | 最终裁决结果 |
---
## 三、数据表设计
### 3.1 dc_health_checks健康检查缓存表
**用途**: 缓存健康检查结果,避免重复计算
```sql
CREATE TABLE "dc_schema"."dc_health_checks" (
"id" TEXT NOT NULL PRIMARY KEY,
"user_id" TEXT NOT NULL,
"file_name" TEXT NOT NULL,
"column_name" TEXT NOT NULL,
-- 统计指标
"empty_rate" DOUBLE PRECISION NOT NULL,
"avg_length" DOUBLE PRECISION NOT NULL,
"total_rows" INTEGER NOT NULL,
"estimated_tokens" INTEGER NOT NULL,
-- 检查结果
"status" TEXT NOT NULL, -- 'good' | 'bad'
"message" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
);
```
**字段说明**
| 字段 | 类型 | 说明 | 示例 |
|------|------|------|------|
| `id` | TEXT | UUID主键 | `uuid()` |
| `user_id` | TEXT | 用户ID | `user-123` |
| `file_name` | TEXT | 文件名 | `患者数据.xlsx` |
| `column_name` | TEXT | 检查的列名 | `病历文本` |
| `empty_rate` | DOUBLE | 空值率 (0-1) | 0.15 (15%) |
| `avg_length` | DOUBLE | 平均文本长度 | 256.8 |
| `total_rows` | INT | 总行数 | 500 |
| `estimated_tokens` | INT | 预估Token数 | 150000 |
| `status` | TEXT | 健康状态 | `good` / `bad` |
| `message` | TEXT | 提示信息 | `健康度良好` |
**索引**
```sql
CREATE INDEX "dc_health_checks_user_id_file_name_idx"
ON "dc_schema"."dc_health_checks"("user_id", "file_name");
```
**业务规则**
- 空值率 > 80% → `status = 'bad'`
- 平均长度 < 10 → `status = 'bad'`
- 缓存有效期24小时应用层实现
---
### 3.2 dc_templates预设模板表
**用途**: 存储疾病类型的预设提取模板
```sql
CREATE TABLE "dc_schema"."dc_templates" (
"id" TEXT NOT NULL PRIMARY KEY,
"disease_type" TEXT NOT NULL, -- 'lung_cancer', 'diabetes', 'hypertension'
"report_type" TEXT NOT NULL, -- 'pathology', 'admission', 'outpatient'
"display_name" TEXT NOT NULL, -- '肺癌病理报告'
"fields" JSONB NOT NULL, -- [{name, desc, width}]
"prompt_template" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "dc_templates_disease_type_report_type_key"
UNIQUE ("disease_type", "report_type")
);
```
**字段说明**
| 字段 | 类型 | 说明 | 示例 |
|------|------|------|------|
| `disease_type` | TEXT | 疾病类型 | `lung_cancer` |
| `report_type` | TEXT | 报告类型 | `pathology` |
| `display_name` | TEXT | 显示名称 | `肺癌病理报告` |
| `fields` | JSONB | 提取字段配置 | 见下方示例 |
| `prompt_template` | TEXT | Prompt模板 | `请从以下病理报告中提取...` |
**fields字段结构**
```json
[
{
"name": "病理类型",
"desc": "如:浸润性腺癌、鳞状细胞癌",
"width": "w-40"
},
{
"name": "分化程度",
"desc": "高/中/低分化",
"width": "w-32"
}
]
```
**唯一约束**
```sql
UNIQUE ("disease_type", "report_type")
```
同一疾病+报告类型组合只能有一个模板
---
### 3.3 dc_extraction_tasks提取任务表
**用途**: 管理批量提取任务,追踪进度和成本
```sql
CREATE TABLE "dc_schema"."dc_extraction_tasks" (
"id" TEXT NOT NULL PRIMARY KEY,
"user_id" TEXT NOT NULL,
"project_name" TEXT NOT NULL,
"source_file_key" TEXT NOT NULL, -- Storage中的路径
"text_column" TEXT NOT NULL,
-- 模板配置
"disease_type" TEXT NOT NULL,
"report_type" TEXT NOT NULL,
"target_fields" JSONB NOT NULL,
-- 双模型配置
"model_a" TEXT NOT NULL DEFAULT 'deepseek-v3',
"model_b" TEXT NOT NULL DEFAULT 'qwen3-72b',
-- 任务状态
"status" TEXT NOT NULL DEFAULT 'pending',
"total_count" INTEGER NOT NULL DEFAULT 0,
"processed_count" INTEGER NOT NULL DEFAULT 0,
"clean_count" INTEGER NOT NULL DEFAULT 0,
"conflict_count" INTEGER NOT NULL DEFAULT 0,
"failed_count" INTEGER NOT NULL DEFAULT 0,
-- 成本统计
"total_tokens" INTEGER NOT NULL DEFAULT 0,
"total_cost" DOUBLE PRECISION NOT NULL DEFAULT 0,
-- 错误信息
"error" TEXT,
-- 时间戳
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"started_at" TIMESTAMP(3),
"completed_at" TIMESTAMP(3)
);
```
**字段说明**
| 字段 | 类型 | 说明 | 示例 |
|------|------|------|------|
| `source_file_key` | TEXT | Storage路径 | `uploads/user123/data.xlsx` |
| `text_column` | TEXT | 文本列名 | `病历文本` |
| `target_fields` | JSONB | 提取字段 | `[{name, desc}]` |
| `status` | TEXT | 任务状态 | `pending/processing/completed/failed` |
| `total_count` | INT | 总记录数 | 500 |
| `processed_count` | INT | 已处理数 | 250 |
| `clean_count` | INT | 一致数 | 200 |
| `conflict_count` | INT | 冲突数 | 45 |
| `failed_count` | INT | 失败数 | 5 |
| `total_tokens` | INT | 总Token数 | 150000 |
| `total_cost` | DOUBLE | 总成本($) | 0.27 |
**状态流转**
```
pending → processing → completed
→ failed
```
**索引**
```sql
CREATE INDEX "dc_extraction_tasks_user_id_status_idx"
ON "dc_schema"."dc_extraction_tasks"("user_id", "status");
```
---
### 3.4 dc_extraction_items提取记录表
**用途**: 存储每条记录的双模型提取结果和冲突状态
```sql
CREATE TABLE "dc_schema"."dc_extraction_items" (
"id" TEXT NOT NULL PRIMARY KEY,
"task_id" TEXT NOT NULL,
-- 原始数据
"row_index" INTEGER NOT NULL,
"original_text" TEXT NOT NULL,
-- 双模型结果
"result_a" JSONB,
"result_b" JSONB,
-- 冲突检测
"status" TEXT NOT NULL DEFAULT 'pending',
"conflict_fields" TEXT[] DEFAULT ARRAY[]::TEXT[],
-- 最终结果
"final_result" JSONB,
-- Token统计
"tokens_a" INTEGER NOT NULL DEFAULT 0,
"tokens_b" INTEGER NOT NULL DEFAULT 0,
-- 错误信息
"error" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"resolved_at" TIMESTAMP(3),
CONSTRAINT "dc_extraction_items_task_id_fkey"
FOREIGN KEY ("task_id")
REFERENCES "dc_schema"."dc_extraction_tasks"("id")
ON DELETE CASCADE
);
```
**字段说明**
| 字段 | 类型 | 说明 | 示例 |
|------|------|------|------|
| `row_index` | INT | Excel行号 | 5 |
| `original_text` | TEXT | 原始病历文本 | `患者45岁...` |
| `result_a` | JSONB | DeepSeek结果 | `{"肿瘤大小": "3cm"}` |
| `result_b` | JSONB | Qwen结果 | `{"肿瘤大小": "3.0cm"}` |
| `status` | TEXT | 处理状态 | `clean/conflict/resolved/failed` |
| `conflict_fields` | TEXT[] | 冲突字段列表 | `["肿瘤大小"]` |
| `final_result` | JSONB | 最终裁决结果 | `{"肿瘤大小": "3cm"}` |
**result_a/result_b结构示例**
```json
{
"病理类型": "浸润性腺癌",
"分化程度": "中分化",
"肿瘤大小": "3cm",
"淋巴结转移": "无"
}
```
**状态说明**
- `pending`: 等待处理
- `clean`: 双模型结果一致
- `conflict`: 存在冲突,需人工裁决
- `resolved`: 冲突已解决
- `failed`: 提取失败
**索引**
```sql
CREATE INDEX "dc_extraction_items_task_id_status_idx"
ON "dc_schema"."dc_extraction_items"("task_id", "status");
```
**外键约束**
- `ON DELETE CASCADE`: 删除任务时自动删除所有记录
---
## 四、索引设计
### 4.1 索引列表
| 表名 | 索引字段 | 类型 | 用途 |
|------|---------|------|------|
| `dc_health_checks` | `(user_id, file_name)` | 复合 | 查询用户的历史检查 |
| `dc_templates` | `(disease_type, report_type)` | 唯一 | 防止重复模板 |
| `dc_extraction_tasks` | `(user_id, status)` | 复合 | 查询用户的任务列表 |
| `dc_extraction_items` | `(task_id, status)` | 复合 | 查询任务的记录列表 |
### 4.2 性能考虑
**查询优化**
```sql
-- 高效查询:利用索引
SELECT * FROM dc_extraction_tasks
WHERE user_id = 'user123' AND status = 'processing';
-- 高效查询:利用索引
SELECT * FROM dc_extraction_items
WHERE task_id = 'task456' AND status = 'conflict';
```
**避免全表扫描**
- ✅ 始终在WHERE子句中包含索引字段
- ✅ 使用`status`字段过滤可以显著减少扫描行数
---
## 五、外键约束
### 5.1 级联删除
```sql
ALTER TABLE "dc_schema"."dc_extraction_items"
ADD CONSTRAINT "dc_extraction_items_task_id_fkey"
FOREIGN KEY ("task_id")
REFERENCES "dc_schema"."dc_extraction_tasks"("id")
ON DELETE CASCADE;
```
**行为**
- 删除任务 → 自动删除所有关联的提取记录
- 保证数据一致性
### 5.2 无外键的表
- `dc_health_checks`: 独立表,无外键
- `dc_templates`: 独立表,无外键
- `dc_extraction_tasks`: 无外键user_id仅为标识不强制关联
**原因**
- ✅ 减少跨Schema依赖
- ✅ 提高模块独立性
- ✅ 简化迁移和回滚
---
## 六、数据生命周期
### 6.1 数据保留策略
| 表名 | 保留时间 | 清理策略 |
|------|---------|---------|
| `dc_health_checks` | 7天 | 定期清理旧记录 |
| `dc_templates` | 永久 | 手动管理 |
| `dc_extraction_tasks` | 90天 | 归档后删除 |
| `dc_extraction_items` | 90天 | 随任务删除 |
### 6.2 归档策略
**大任务归档** (> 1000条记录)
1. 任务完成后导出结果到CSV/Excel
2. 上传到Storage永久保存
3. 删除数据库记录(释放空间)
### 6.3 清理脚本(示例)
```typescript
// 清理7天前的健康检查记录
await prisma.dCHealthCheck.deleteMany({
where: {
createdAt: {
lt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
}
}
});
// 归档90天前的已完成任务
const oldTasks = await prisma.dCExtractionTask.findMany({
where: {
status: 'completed',
completedAt: {
lt: new Date(Date.now() - 90 * 24 * 60 * 60 * 1000)
}
},
include: { items: true }
});
// 导出后删除
for (const task of oldTasks) {
await exportTaskToStorage(task);
await prisma.dCExtractionTask.delete({ where: { id: task.id } });
}
```
---
## 七、数据安全
### 7.1 PII保护
**敏感字段**
- `original_text`: 可能包含患者姓名、身份证号
- `result_a/result_b/final_result`: 可能包含结构化的敏感信息
**保护措施**
- ✅ 发送LLM前自动脱敏PIIMaskUtil
- ✅ 数据库加密PostgreSQL SSL
- ✅ 定期清理历史数据
### 7.2 用户隔离
**机制**
- 所有表包含`user_id`字段
- 应用层强制过滤:`WHERE user_id = currentUserId`
- 永不跨用户查询
---
## 八、附录
### 8.1 完整Schema DDL
完整的Schema创建脚本位于
```
backend/prisma/migrations/20251127_add_dc_tool_b_tables/migration.sql
```
### 8.2 Prisma模型定义
完整的Prisma模型定义位于
```
backend/prisma/schema.prisma
```
搜索 `dc_schema` 查看所有模型。
### 8.3 变更历史
| 版本 | 日期 | 变更内容 |
|------|------|---------|
| V1.0 | 2025-11-27 | 初始版本4个表 |
---
**文档结束**