Files
AIclinicalresearch/docs/03-业务模块/DC-数据清洗整理/02-技术设计/数据库设计文档-DC模块(完整版).md
HaHafeng 66255368b7 feat(admin): Add user management and upgrade to module permission system
Features - User Management (Phase 4.1):
- Database: Add user_modules table for fine-grained module permissions
- Database: Add 4 user permissions (view/create/edit/delete) to role_permissions
- Backend: UserService (780 lines) - CRUD with tenant isolation
- Backend: UserController + UserRoutes (648 lines) - 13 API endpoints
- Backend: Batch import users from Excel
- Frontend: UserListPage (412 lines) - list/filter/search/pagination
- Frontend: UserFormPage (341 lines) - create/edit with module config
- Frontend: UserDetailPage (393 lines) - details/tenant/module management
- Frontend: 3 modal components (592 lines) - import/assign/configure
- API: GET/POST/PUT/DELETE /api/admin/users/* endpoints

Architecture Upgrade - Module Permission System:
- Backend: Add getUserModules() method in auth.service
- Backend: Login API returns modules array in user object
- Frontend: AuthContext adds hasModule() method
- Frontend: Navigation filters modules based on user.modules
- Frontend: RouteGuard checks requiredModule instead of requiredVersion
- Frontend: Remove deprecated version-based permission system
- UX: Only show accessible modules in navigation (clean UI)
- UX: Smart redirect after login (avoid 403 for regular users)

Fixes:
- Fix UTF-8 encoding corruption in ~100 docs files
- Fix pageSize type conversion in userService (String to Number)
- Fix authUser undefined error in TopNavigation
- Fix login redirect logic with role-based access check
- Update Git commit guidelines v1.2 with UTF-8 safety rules

Database Changes:
- CREATE TABLE user_modules (user_id, tenant_id, module_code, is_enabled)
- ADD UNIQUE CONSTRAINT (user_id, tenant_id, module_code)
- INSERT 4 permissions + role assignments
- UPDATE PUBLIC tenant with 8 module subscriptions

Technical:
- Backend: 5 new files (~2400 lines)
- Frontend: 10 new files (~2500 lines)
- Docs: 1 development record + 2 status updates + 1 guideline update
- Total: ~4900 lines of code

Status: User management 100% complete, module permission system operational
2026-01-16 13:42:10 +08:00

14 KiB
Raw Permalink Blame History

数据库设计文档 - 工具B病历结构化机器人

模块: DC数据清洗整理 - 工具B
版本: V2.0 (MVP)
Schema: dc_schema
更新日期: 2025-12-03
状态: MVP完成已验证可用真实数据测试通过


📋 目录


一、概述

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隔离

-- 所有表使用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健康检查缓存表

用途: 缓存健康检查结果,避免重复计算

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 提示信息 健康度良好

索引

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预设模板表

用途: 存储疾病类型的预设提取模板

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字段结构

[
  {
    "name": "病理类型",
    "desc": "如:浸润性腺癌、鳞状细胞癌",
    "width": "w-40"
  },
  {
    "name": "分化程度",
    "desc": "高/中/低分化",
    "width": "w-32"
  }
]

唯一约束

UNIQUE ("disease_type", "report_type")

同一疾病+报告类型组合只能有一个模板


3.3 dc_extraction_tasks提取任务表

用途: 管理批量提取任务,追踪进度和成本

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

索引

CREATE INDEX "dc_extraction_tasks_user_id_status_idx" 
ON "dc_schema"."dc_extraction_tasks"("user_id", "status");

3.4 dc_extraction_items提取记录表

用途: 存储每条记录的双模型提取结果和冲突状态

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结构示例

{
  "病理类型": "浸润性腺癌",
  "分化程度": "中分化",
  "肿瘤大小": "3cm",
  "淋巴结转移": "无"
}

状态说明

  • pending: 等待处理
  • clean: 双模型结果一致
  • conflict: 存在冲突,需人工裁决
  • resolved: 冲突已解决
  • failed: 提取失败

索引

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 性能考虑

查询优化

-- 高效查询:利用索引
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 级联删除

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 清理脚本(示例)

// 清理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个表

文档结束