Files
AIclinicalresearch/docs/02-通用能力层/通用能力层技术债务清单.md
HaHafeng 57fdc6ef00 feat(aia): Integrate PromptService for 10 AI agents
Features:
- Migrate 10 agent prompts from hardcoded to database
- Add grayscale preview support (DRAFT/ACTIVE distribution)
- Implement 3-tier fallback (DB -> Cache -> Hardcoded)
- Add version management and rollback capability

Files changed:
- backend/scripts/migrate-aia-prompts.ts (new migration script)
- backend/src/common/prompt/prompt.fallbacks.ts (add AIA fallbacks)
- backend/src/modules/aia/services/agentService.ts (integrate PromptService)
- backend/src/modules/aia/services/conversationService.ts (pass userId)
- backend/src/modules/aia/types/index.ts (fix AgentStage type)

Documentation:
- docs/03-业务模块/AIA-AI智能问答/06-开发记录/2026-01-18-Prompt管理系统集成.md
- docs/02-通用能力层/00-通用能力层清单.md (add FileCard, Prompt management)
- docs/00-系统总体设计/00-系统当前状态与开发指南.md (update to v3.6)

Prompt codes:
- AIA_SCIENTIFIC_QUESTION, AIA_PICO_ANALYSIS, AIA_TOPIC_EVALUATION
- AIA_OUTCOME_DESIGN, AIA_CRF_DESIGN, AIA_SAMPLE_SIZE
- AIA_PROTOCOL_WRITING, AIA_METHODOLOGY_REVIEW
- AIA_PAPER_POLISH, AIA_PAPER_TRANSLATE

Tested: Migration script executed, all 10 prompts inserted successfully
2026-01-18 15:48:53 +08:00

19 KiB
Raw Blame History

通用能力层技术债务清单

文档版本: v1.0
创建日期: 2025-12-22
维护者: 平台架构团队
文档目的: 记录通用能力层待优化项,指导未来迭代


📋 概述

本文档基于 DC Tool C 异步架构实践2025-12-22总结发现的可以抽象为通用能力的模式。

核心思想

  • 当前分层已经很好(通用能力层提供完整的基础设施)
  • 业务模块正确使用了这些能力
  • ⏭️ 未来可以进一步抽象(锦上添花,非必需)

🎯 技术债务清单

TD-COMMON-001: 前端通用轮询Hook

优先级 P2
工作量0.5天
预期收益:代码复用性提升,模块间统一

问题描述

当前状态

  • Tool C 实现:dc/tool-c/hooks/useSessionStatus.ts
  • ASL 实现:asl/hooks/useScreeningTask.ts
  • 代码重复度70%

重复的逻辑

// 每个模块都要写类似的代码
useQuery({
  queryKey: ['taskStatus', id],
  queryFn: () => api.getStatus(id),
  enabled: !!id,
  refetchInterval: (query) => {
    const status = query.state.data?.status;
    if (status === 'ready' || status === 'error') return false;
    return 2000;
  },
  staleTime: 0,
});

解决方案

抽象为通用Hook

// frontend-v2/src/common/hooks/useAsyncTaskPolling.ts新建

import { useQuery } from '@tanstack/react-query';

interface UseAsyncTaskPollingOptions<T> {
  /** 任务ID */
  taskId: string | null;
  
  /** 状态查询API函数 */
  queryFn: (taskId: string) => Promise<T>;
  
  /** 状态提取函数 */
  getStatus: (data: T) => 'pending' | 'processing' | 'ready' | 'error' | string;
  
  /** 进度提取函数(可选) */
  getProgress?: (data: T) => number;
  
  /** 是否启用 */
  enabled?: boolean;
  
  /** 轮询间隔毫秒默认2000 */
  pollingInterval?: number;
}

export function useAsyncTaskPolling<T>({
  taskId,
  queryFn,
  getStatus,
  getProgress,
  enabled = true,
  pollingInterval = 2000,
}: UseAsyncTaskPollingOptions<T>) {
  const { data, isLoading, error } = useQuery({
    queryKey: ['asyncTask', taskId],
    queryFn: () => queryFn(taskId!),
    enabled: enabled && !!taskId,
    refetchInterval: (query) => {
      if (!query.state.data) return false;
      
      const status = getStatus(query.state.data);
      
      // 完成或失败时停止轮询
      if (status === 'ready' || status === 'completed' || status === 'error' || status === 'failed') {
        return false;
      }
      
      return pollingInterval;
    },
    staleTime: 0,
    retry: 1,
  });

  const status = data ? getStatus(data) : 'pending';
  const progress = getProgress && data ? getProgress(data) : 0;

  return {
    data,
    status,
    progress,
    isReady: status === 'ready' || status === 'completed',
    isError: status === 'error' || status === 'failed',
    isProcessing: status === 'processing' || status === 'pending',
    isLoading,
    error,
  };
}

使用示例

// Tool C 使用
const { status, progress, isReady } = useAsyncTaskPolling({
  taskId: sessionId,
  queryFn: (sid) => api.getSessionStatus(sid, jobId),
  getStatus: (res) => res.data.status,
  getProgress: (res) => res.data.progress,
});

// ASL 使用
const { status, isReady } = useAsyncTaskPolling({
  taskId: projectId,
  queryFn: (pid) => aslApi.getScreeningTask(pid),
  getStatus: (res) => res.data.status,
  getProgress: (res) => Math.round((res.data.processedItems / res.data.totalItems) * 100),
});

影响范围

  • Tool C: useSessionStatus.ts 可简化
  • ASL: useScreeningTask.ts 可简化
  • Tool B: 未来前端可直接使用
  • 其他模块: 直接复用

TD-COMMON-002: Clean Data 缓存服务

优先级 P1
工作量1天
预期收益性能提升99%,所有模块受益

问题描述

当前状态

  • Tool C 实现:保存 ${fileKey}_clean.json
  • ASL、Tool B未实现仍然每次重新解析

重复计算问题

  • ASL 文献筛选:每次从 OSS 下载 PDF重新解析5-10秒/篇)
  • Tool B 数据提取:每次重新读取 Excel
  • Tool C 操作已优化clean data缓存

解决方案

抽象为通用服务

// backend/src/common/services/DataCacheService.ts新建

import { storage } from '../storage';
import { prisma } from '../../config/database';
import { logger } from '../logging';

/**
 * 数据缓存服务(通用)
 * 
 * 用途:
 * - Worker 解析后保存处理结果
 * - Service 优先读取缓存,避免重复计算
 * - 操作后同步更新缓存
 */
export class DataCacheService {
  /**
   * 保存清洗/处理后的数据
   * 
   * @param originalKey 原始文件key
   * @param cleanData 清洗后的数据
   * @param suffix 后缀(默认 '_clean.json'
   * @returns clean data 的 OSS key
   */
  async saveCleanData(
    originalKey: string,
    cleanData: any,
    suffix: string = '_clean.json'
  ): Promise<string> {
    const cleanDataKey = `${originalKey}${suffix}`;
    
    logger.info('[DataCacheService] Saving clean data', { 
      originalKey, 
      cleanDataKey,
      rows: Array.isArray(cleanData) ? cleanData.length : 'N/A',
    });
    
    // 序列化并上传
    const buffer = Buffer.from(JSON.stringify(cleanData), 'utf-8');
    await storage.upload(cleanDataKey, buffer);
    
    logger.info('[DataCacheService] Clean data saved', { 
      size: `${(buffer.length / 1024).toFixed(2)} KB` 
    });
    
    return cleanDataKey;
  }

  /**
   * 读取清洗后的数据
   * 
   * @param cleanDataKey clean data 的 OSS key
   * @returns 清洗后的数据
   */
  async getCleanData(cleanDataKey: string): Promise<any> {
    logger.info('[DataCacheService] Loading clean data', { cleanDataKey });
    
    const buffer = await storage.download(cleanDataKey);
    const data = JSON.parse(buffer.toString('utf-8'));
    
    logger.info('[DataCacheService] Clean data loaded', { 
      rows: Array.isArray(data) ? data.length : 'N/A',
    });
    
    return data;
  }

  /**
   * 更新清洗后的数据
   * 
   * @param cleanDataKey clean data 的 OSS key
   * @param newData 新数据
   */
  async updateCleanData(cleanDataKey: string, newData: any): Promise<void> {
    logger.info('[DataCacheService] Updating clean data', { 
      cleanDataKey,
      rows: Array.isArray(newData) ? newData.length : 'N/A',
    });
    
    const buffer = Buffer.from(JSON.stringify(newData), 'utf-8');
    await storage.upload(cleanDataKey, buffer);
    
    logger.info('[DataCacheService] Clean data updated');
  }

  /**
   * 删除清洗后的数据
   * 
   * @param cleanDataKey clean data 的 OSS key
   */
  async deleteCleanData(cleanDataKey: string): Promise<void> {
    try {
      await storage.delete(cleanDataKey);
      logger.info('[DataCacheService] Clean data deleted', { cleanDataKey });
    } catch (error: any) {
      logger.warn('[DataCacheService] Clean data deletion failed', { 
        cleanDataKey, 
        error: error.message 
      });
    }
  }
}

export const dataCacheService = new DataCacheService();

使用示例

// Worker 中
const cleanDataKey = await dataCacheService.saveCleanData(fileKey, cleanedData);
await prisma.update({ where: { id }, data: { cleanDataKey } });

// Service 中
if (record.cleanDataKey) {
  return await dataCacheService.getCleanData(record.cleanDataKey);
}

// 操作后更新
await dataCacheService.updateCleanData(record.cleanDataKey, newData);

影响范围

  • Tool C: 简化现有代码
  • ASL: 文献解析结果缓存提升99%
  • Tool B: 数据提取结果缓存
  • 所有模块: 统一的缓存机制

TD-COMMON-003: 智能清洗算法通用化

优先级 P2
工作量0.5天
预期收益:代码复用,避免重复实现

问题描述

当前状态

  • Tool C 实现:intelligentCleanData(边界检测+安全阀)
  • 其他模块:未实现类似功能

可以通用化的算法

  1. 幽灵列检测(边界检测)
  2. 幽灵行过滤
  3. 安全阀(最大列数、单元格数限制)

解决方案

// backend/src/common/utils/dataCleaningUtils.ts新建

export interface CleaningOptions {
  maxCols?: number;       // 最大列数默认3000
  maxCells?: number;      // 最大单元格数默认500万
  removeEmptyRows?: boolean;  // 是否删除空行默认true
  removeEmptyCols?: boolean;  // 是否删除空列默认true
}

export function intelligentCleanData(
  data: any[],
  options: CleaningOptions = {}
): any[] {
  // 实现边界检测、幽灵列/行清洗、安全阀
  // ...
}

export function isValidValue(value: any): boolean {
  // 统一的空值判断
  // ...
}

影响范围

  • Tool C: 复用通用实现
  • 其他上传Excel的功能: 直接使用

TD-COMMON-004: Worker注册辅助工具

优先级 P3
工作量0.3天
预期收益降低Worker注册代码重复

问题描述

当前状态

  • 每个模块都要手写 Worker 注册代码
  • 错误处理逻辑重复

解决方案

// backend/src/common/jobs/WorkerHelper.ts新建

import { jobQueue } from './index';
import { logger } from '../logging';
import type { JobHandler } from './types';

interface RegisterWorkerOptions {
  queueName: string;
  handler: JobHandler;
  description?: string;
  onStart?: (job: any) => void;
  onComplete?: (job: any, result: any) => void;
  onError?: (job: any, error: any) => void;
}

export function registerWorker(options: RegisterWorkerOptions) {
  const { queueName, handler, description } = options;
  
  logger.info(`[WorkerHelper] Registering worker: ${queueName}`, { description });
  
  jobQueue.process(queueName, async (job) => {
    try {
      options.onStart?.(job);
      
      const result = await handler(job);
      
      options.onComplete?.(job, result);
      
      return result;
    } catch (error) {
      options.onError?.(job, error);
      throw error;
    }
  });
  
  logger.info(`[WorkerHelper] ✅ Worker registered: ${queueName}`);
}

使用示例

// 简化的注册代码
registerWorker({
  queueName: 'dc_toolc_parse_excel',
  description: 'Excel解析Worker',
  handler: async (job) => {
    // 业务逻辑
    return result;
  },
  onStart: (job) => console.log(`开始处理: ${job.id}`),
  onComplete: (job, result) => console.log(`完成: ${result}`),
  onError: (job, error) => console.error(`失败: ${error}`),
});

TD-COMMON-005: 数据版本管理系统

优先级 P1
工作量3天
预期收益支持链式操作、undo功能、导出历史版本

问题描述

当前限制

  • 操作不是累积的(每次基于原始数据)
  • 无法回退到某个操作前的状态
  • 无法导出中间版本的数据

用户期望的工作流

上传v0: 100行
  ↓
筛选v1: 50行← 可以回退到这里
  ↓
数值映射v2: 50行有映射← 可以回退到这里
  ↓
Pivotv3: 不同结构)
  ↓
导出:可以导出 v0、v1、v2、v3 任意版本

解决方案

Prisma Schema设计

// 版本管理表(通用)
model DataVersion {
  id String @id @default(uuid())
  
  // 关联信息(多态关联)
  entityType String  // 'dc_toolc_session' | 'asl_project' | ...
  entityId String    // Session ID | Project ID | ...
  
  // 版本信息
  versionNumber Int  // 0=原始, 1=第1次操作后, 2=第2次操作后...
  dataKey String     // OSS中的数据文件key
  
  // 操作记录
  operation String?  // 'upload' | 'filter' | 'pivot' | 'recode' ...
  operationParams Json?  // 操作参数
  
  // 元数据
  totalRows Int
  totalCols Int
  columns Json
  
  // 时间戳
  createdAt DateTime @default(now())
  createdBy String   // 用户ID
  
  @@unique([entityType, entityId, versionNumber])
  @@index([entityType, entityId])
  @@map("data_versions")
  @@schema("platform_schema")
}

// 业务表添加字段
model DcToolCSession {
  // ...现有字段
  
  currentVersion Int @default(0)  // 当前版本号
}

Service实现

// backend/src/common/services/DataVersionService.ts新建

export class DataVersionService {
  /**
   * 创建新版本
   */
  async createVersion(
    entityType: string,
    entityId: string,
    versionNumber: number,
    data: any[],
    operation?: string,
    params?: any
  ): Promise<string> {
    // 保存数据到 OSS
    const dataKey = `versions/${entityType}/${entityId}/v${versionNumber}.json`;
    await storage.upload(dataKey, JSON.stringify(data));
    
    // 创建版本记录
    await prisma.dataVersion.create({
      data: {
        entityType,
        entityId,
        versionNumber,
        dataKey,
        operation,
        operationParams: params,
        totalRows: data.length,
        columns: Object.keys(data[0] || {}),
        createdBy: userId,
      }
    });
    
    return dataKey;
  }

  /**
   * 读取指定版本
   */
  async getVersion(
    entityType: string,
    entityId: string,
    versionNumber: number
  ): Promise<any[]> {
    const version = await prisma.dataVersion.findUnique({
      where: {
        entityType_entityId_versionNumber: {
          entityType,
          entityId,
          versionNumber,
        }
      }
    });
    
    if (!version) throw new Error('版本不存在');
    
    const buffer = await storage.download(version.dataKey);
    return JSON.parse(buffer.toString('utf-8'));
  }

  /**
   * 回退到指定版本
   */
  async rollbackToVersion(
    entityType: string,
    entityId: string,
    versionNumber: number
  ): Promise<void> {
    // 更新当前版本号
    await this.updateCurrentVersion(entityType, entityId, versionNumber);
  }

  /**
   * 获取版本历史
   */
  async listVersions(entityType: string, entityId: string) {
    return await prisma.dataVersion.findMany({
      where: { entityType, entityId },
      orderBy: { versionNumber: 'asc' }
    });
  }
}

QuickAction 集成

// QuickAction 执行后自动创建新版本
const result = await python.execute(fullData, params);

// 读取当前版本号
const currentVersion = session.currentVersion || 0;

// 创建新版本
await dataVersionService.createVersion(
  'dc_toolc_session',
  sessionId,
  currentVersion + 1,
  result.data,
  action,  // 'filter' | 'pivot' | ...
  params
);

// 更新当前版本号
await prisma.update({
  where: { id: sessionId },
  data: { currentVersion: currentVersion + 1 }
});

影响范围

  • Tool C: 支持链式操作、undo、导出历史
  • ASL: 文献筛选的多阶段结果管理
  • Tool B: 数据提取的版本管理
  • 所有模块: 统一的版本管理能力

TD-COMMON-006: 幽灵列/行检测算法库

优先级 P2
工作量0.5天
预期收益Excel处理质量提升

问题描述

当前状态

  • Tool C 实现:边界检测算法
  • 其他模块:未实现

Excel格式污染问题

  • 用户刷颜色到16384列 → 解析出16384列实际只有151列有效
  • 用户删除数据未清理 → 解析出大量空行

解决方案

// backend/src/common/utils/excelCleaning.ts新建

export interface CleaningResult {
  cleanedData: any[];
  originalRows: number;
  originalCols: number;
  finalRows: number;
  finalCols: number;
  removedRows: number;
  removedCols: number;
  warnings: string[];
}

export function cleanExcelData(
  rawData: any[],
  options?: CleaningOptions
): CleaningResult {
  // 实现通用的清洗算法
  // ...
}

TD-COMMON-007: 前端进度条组件

优先级 P2
工作量0.3天
预期收益UI统一用户体验提升

问题描述

当前状态

  • Tool C 实现内联进度条蓝色Header下方
  • 其他模块:未实现或不统一

解决方案

// frontend-v2/src/common/components/AsyncProgressBar.tsx新建

interface AsyncProgressBarProps {
  visible: boolean;
  progress: number;  // 0-100
  message: string;
  status: 'uploading' | 'processing' | 'completed' | 'error';
}

export const AsyncProgressBar: React.FC<AsyncProgressBarProps> = ({
  visible,
  progress,
  message,
  status,
}) => {
  if (!visible) return null;

  return (
    <div className="bg-blue-50 border-b border-blue-200 px-6 py-3">
      <div className="flex items-center justify-between mb-2">
        <span className="text-sm font-medium text-blue-900">{message}</span>
        <span className="text-sm text-blue-700">{progress}%</span>
      </div>
      <div className="w-full bg-blue-200 rounded-full h-2">
        <div 
          className="bg-blue-600 h-2 rounded-full transition-all duration-300"
          style={{ width: `${progress}%` }}
        />
      </div>
    </div>
  );
};

📅 实施计划

技术债务 优先级 工作量 建议时机 收益
TD-COMMON-001 前端轮询Hook P2 0.5天 下次迭代 代码统一
TD-COMMON-002 Clean Data服务 P1 1天 下次迭代 性能提升99%
TD-COMMON-003 智能清洗算法 P2 0.5天 需要时 质量提升
TD-COMMON-004 Worker注册辅助 P3 0.3天 可选 代码简化
TD-COMMON-005 数据版本管理 P1 3天 下次迭代 链式操作
TD-COMMON-006 幽灵列/行检测 P2 0.5天 需要时 质量提升
TD-COMMON-007 进度条组件 P2 0.3天 需要时 UI统一

推荐优先级

  1. TD-COMMON-002Clean Data服务
  2. TD-COMMON-005数据版本管理
  3. TD-COMMON-001前端轮询Hook

📊 当前架构评价

已经很好的部分

  1. 通用能力层基础设施完整

    • jobQueuepg-boss队列
    • CheckpointService断点续传
    • 任务拆分工具
    • storage、logger、cache
  2. 业务模块正确使用通用能力

    • Tool C、ASL、Tool B 统一使用 jobQueue
    • Platform-Only模式
    • 不在业务表中存储任务管理信息
  3. 分层清晰,职责明确

    • 通用能力层:基础设施
    • 业务模块:具体实现

⏭️ 可以改进的部分(非必需)

  1. ⏭️ 前端轮询模式可以更统一
  2. ⏭️ clean data缓存可以更通用
  3. ⏭️ 数据版本管理待建立

结论:当前架构已经符合规范,未来优化是锦上添花!


📚 参考文档


维护者: 平台架构团队
最后更新: 2025-12-22
文档状态: 初始版本