Files
AIclinicalresearch/docs/02-通用能力层/通用能力层技术债务清单.md
HaHafeng dfc472810b feat(iit-manager): Integrate Dify knowledge base for hybrid retrieval
Completed features:
- Created Dify dataset (Dify_test0102) with 2 processed documents
- Linked test0102 project with Dify dataset ID
- Extended intent detection to recognize query_protocol intent
- Implemented queryDifyKnowledge method (semantic search Top 5)
- Integrated hybrid retrieval (REDCap data + Dify documents)
- Fixed AI hallucination bugs (intent detection + API field path)
- Developed debugging scripts
- Completed end-to-end testing (5 scenarios passed)
- Generated comprehensive documentation (600+ lines)
- Updated development plans and module status

Technical highlights:
- Single project single knowledge base architecture
- Smart routing based on user intent
- Prevent AI hallucination by injecting real data/documents
- Session memory for multi-turn conversations
- Reused LLMFactory for DeepSeek-V3 integration

Bug fixes:
- Fixed intent detection missing keywords
- Fixed Dify API response field path error

Testing: All scenarios verified in WeChat production environment

Status: Fully tested and deployed
2026-01-04 15:44:11 +08:00

791 lines
19 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.
# 通用能力层技术债务清单
> **文档版本:** 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%
**重复的逻辑**
```typescript
// 每个模块都要写类似的代码
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**
```typescript
// 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,
};
}
```
**使用示例**
```typescript
// 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缓存
#### 解决方案
**抽象为通用服务**
```typescript
// 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();
```
**使用示例**
```typescript
// 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. 安全阀(最大列数、单元格数限制)
#### 解决方案
```typescript
// 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 注册代码
- 错误处理逻辑重复
#### 解决方案
```typescript
// 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}`);
}
```
**使用示例**
```typescript
// 简化的注册代码
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设计**
```prisma
// 版本管理表(通用)
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实现**
```typescript
// 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 集成**
```typescript
// 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列有效
- 用户删除数据未清理 → 解析出大量空行
#### 解决方案
```typescript
// 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下方
- 其他模块:未实现或不统一
#### 解决方案
```typescript
// 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. ⏭️ 数据版本管理待建立
**结论**:当前架构已经符合规范,未来优化是锦上添花!
---
## 📚 参考文档
- [Postgres-Only异步任务处理指南](./Postgres-Only异步任务处理指南.md) - 完整实践
- [云原生开发规范](../04-开发规范/08-云原生开发规范.md) - 开发规范
- [DC Tool C状态](../03-业务模块/DC-数据清洗整理/00-工具C当前状态与开发指南.md) - Day 10实践
---
**维护者**: 平台架构团队
**最后更新**: 2025-12-22
**文档状态**: ✅ 初始版本