Files
AIclinicalresearch/docs/02-通用能力层/通用能力层技术债务清单.md
HaHafeng 1b53ab9d52 feat(aia): Complete AIA V2.0 with universal streaming capabilities
Major Changes:
- Add StreamingService with OpenAI Compatible format
- Upgrade Chat component V2 with Ant Design X integration
- Implement AIA module with 12 intelligent agents
- Update API routes to unified /api/v1 prefix
- Update system documentation

Backend (~1300 lines):
- common/streaming: OpenAI Compatible adapter
- modules/aia: 12 agents, conversation service, streaming integration
- Update route versions (RVW, PKB to v1)

Frontend (~3500 lines):
- modules/aia: AgentHub + ChatWorkspace (100% prototype restoration)
- shared/Chat: AIStreamChat, ThinkingBlock, useAIStream Hook
- Update API endpoints to v1

Documentation:
- AIA module status guide
- Universal capabilities catalog
- System overview updates
- All module documentation sync

Tested: Stream response verified, authentication working
Status: AIA V2.0 core completed (85%)
2026-01-14 19:15:01 +08:00

808 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.
# <20>𡁶鍂<F0A181B6><EFBFBD><EFBFBD><E69285><EFBFBD><EFBFBD>箏𦛚皜<F0A69B9A><E79A9C>
> **<2A><><EFBFBD><EFB99D>𧋦嚗?* v1.0
> **<2A>𥕦遣<F0A595A6><EFBFBD>嚗?* 2025-12-22
> **蝏湔擪<E6B994><E693AA><EFBFBD>** 撟喳蝱<E596B3><EFBFBD><E59786><EFBFBD>
> **<2A><><EFBFBD><EFBFBD>嚗?* 霈啣<E99C88><E595A3>𡁶鍂<F0A181B6><EFBFBD><EFBFBD><E69285>隡睃<E99AA1>憿對<E686BF><E5B08D><EFBFBD><EFBFBD>芣䔉餈凋誨
---
## <20><><>
<EFBFBD><EFBFBD><EFBFBD>抅鈭?**DC Tool C 撘<><EFBFBD><EFBFBD>摰噼殿**嚗?025-12-22嚗㚁<E59A97><E39A81><EFBFBD><E9A48C>𤑳緵<F0A491B3><E7B7B5>虾隞交𡂝鞊∩蛹<E288A9>𡁶鍂<F0A181B6><EFBFBD><E8B3A2><EFBFBD>芋撘譌<E69298>?
**<EFBFBD><EFBFBD><EFBFBD><EFBFBD>**嚗?
- <20>?**敶枏<E695B6><E69E8F><EFBFBD><EFBFBD>撌脩<E6928C><EFBFBD>末**嚗<><E59A97>𡁶鍂<F0A181B6><EFBFBD><EFBFBD><E69285>靘𥕦<E99D98><F0A595A6><EFBFBD><E6B8A1><EFBFBD>霈暹鴌嚗?
- <20>?**銝𡁜𦛚璅<E79285><EFBFBD>雿輻鍂鈭<E98D82><E988AD>鈭𥡝<E988AD><F0A5A19D>?*
- <20><EFBFBD> **<EFBFBD>芣䔉<EFBFBD>臭誑餈𥕢<EFBFBD>甇交𡂝鞊?*嚗<>釺銝𦠜溶<F0A6A09C><EFBFBD><E6A2A7>𧼮<EFBFBD><F0A7BCAE><EFBFBD>嚗?
---
## <20><20><><EFBFBD><EFBFBD>箏𦛚皜<F0A69B9A><E79A9C>
### TD-COMMON-001: <20>滨垢<E6BBA8>𡁶鍂頧株砭Hook
**隡睃<E99AA1>蝥?*嚗尠<E59A97>潃鐥<E6BD83> P2嚗<32>葉嚗?
**撌乩<E6928C><E4B9A9>?*嚗?.5憭?
**憸<><E686B8><EFBFBD><EFBFBD>**嚗帋誨<E5B88B><E8AAA8><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E689B3><EFBFBD><EFBFBD><E79285><E288AA><EFBFBD><EFBFBD>
#### <20><EFBFBD><E6A185>讛膩
**敶枏<E695B6><E69E8F><EFBFBD>?*嚗?
- Tool C 摰䂿緵嚗䫤dc/tool-c/hooks/useSessionStatus.ts`
- ASL 摰䂿緵嚗䫤asl/hooks/useScreeningTask.ts`
-<><E99A9E><EFBFBD><EFBFBD>摨佗<E691A8>70%
**<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>**嚗?
```typescript
// 瘥譍葵璅<E79285><E288AA><EFBFBD><E8B3AA>嗵掩隡潛<E99AA1><EFBFBD><E99A9E>
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,
});
```
#### 閫<><E996AB><EFBFBD><EFBFBD>
**<EFBFBD>質情銝粹<EFBFBD>𡁶鍂Hook**嚗?
```typescript
// frontend-v2/src/common/hooks/useAsyncTaskPolling.ts嚗<73>鰵撱綽<E692B1>
import { useQuery } from '@tanstack/react-query';
interface UseAsyncTaskPollingOptions<T> {
/** 隞餃𦛚ID */
taskId: string | null;
/** <20><EFBFBD><E59786>䰻霂PI<50>賣㺭 */
queryFn: (taskId: string) => Promise<T>;
/** <20><EFBFBD><E59786><EFBFBD><EFBFBD>硋遆<E7A18B>?*/
getStatus: (data: T) => 'pending' | 'processing' | 'ready' | 'error' | string;
/** 餈𥕦漲<F0A595A6>𣂼<EFBFBD><F0A382BC>賣㺭嚗<E3BAAD><EFBFBD><EFBFBD> */
getProgress?: (data: T) => number;
/** <20>臬炏<E887AC>舐鍂 */
enabled?: boolean;
/** 頧株砭<E6A0AA><EFBFBD><EFBFBD>神蝘𡜐<E89D98>嚗屸<E59A97>霈?000 */
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);
// 摰峕<E691B0><E5B395>硋仃韐交𧒄<E4BAA4>𨀣迫頧株砭
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,
};
}
```
**雿輻鍂蝷箔<E89DB7>**嚗?
```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),
});
```
#### 敶勗<E695B6><E58B97><EFBFBD>
- Tool C: `useSessionStatus.ts` <20><EFBFBD><E88890>?
- ASL: `useScreeningTask.ts` <20><EFBFBD><E88890>?
- Tool B: <20>芣䔉<E88AA3>滨垢<E6BBA8>舐凒<E88890>乩蝙<E4B9A9>?
- <20><EFBFBD><E79285>: <20>湔𦻖憭滨鍂
---
### TD-COMMON-002: Clean Data 蝻枏<E89DBB><E69E8F>滚𦛚
**隡睃<E99AA1>蝥?*嚗尠<E59A97>潃鐥<E6BD83>潃?P1嚗<31><E59A97>嚗?
**撌乩<E6928C><E4B9A9>?*嚗?憭?
**憸<><E686B8><EFBFBD><EFBFBD>**嚗𡁏<E59A97><EFBFBD><E689AF>𣂼<EFBFBD>99%嚗峕<E59A97><E5B395>㗇芋<E39787><EFBFBD><E5A092>?
#### <20><EFBFBD><E6A185>讛膩
**敶枏<E695B6><E69E8F><EFBFBD>?*嚗?
- Tool C 摰䂿緵嚗帋<E59A97>摮?`${fileKey}_clean.json`
- ASL<53><4C>ool B嚗𡁏𧊋摰䂿緵嚗䔶<E59A97><E494B6><EFBFBD>甈⊿<E79488><E28ABF>啗圾<E59597>?
**<EFBFBD><EFBFBD>霈∠<EFBFBD><EFBFBD><EFBFBD>**嚗?
- ASL <20><>讃蝑偦<E89D91><EFBFBD>瘥𤩺活隞?OSS 銝贝蝸 PDF嚗屸<E59A97><E5B1B8>啗圾<E59597><EFBFBD>5-10蝘?蝭<><E89DAD>
- Tool B <20>唳旿<E594B3>𣂼<EFBFBD>嚗𡁏<E59A97>甈⊿<E79488><E28ABF>啗粉<E59597>?Excel
- Tool C <20><EFBFBD>嚗𡁜歇隡睃<E99AA1>嚗Ếlean data蝻枏<E89DBB>嚗?
#### 閫<><E996AB><EFBFBD><EFBFBD>
**<EFBFBD>質情銝粹<EFBFBD>𡁶鍂<EFBFBD>滚𦛚**嚗?
```typescript
// backend/src/common/services/DataCacheService.ts嚗<73>鰵撱綽<E692B1>
import { storage } from '../storage';
import { prisma } from '../../config/database';
import { logger } from '../logging';
/**
* <20>唳旿蝻枏<E89DBB><E69E8F>滚𦛚嚗<F0A69B9A><E59A97>𡁶鍂嚗?
*
* <20><EFBFBD><EFBFBD>
* - Worker 閫<><E996AB><EFBFBD>𦒘<EFBFBD>摮睃<E691AE><E79D83><EFBFBD><EFBFBD><EFBFBD>?
* - Service 隡睃<E99AA1>霂餃<E99C82>蝻枏<E89DBB>嚗屸<E59A97><E5B1B8><EFBFBD>憭滩恣蝞?
* - <20><EFBFBD><E6BBA2>𤾸<EFBFBD>甇交凒<E4BAA4><EFBFBD>摮?
*/
export class DataCacheService {
/**
* 靽嘥<E99DBD><EFBFBD><E79A9C>/憭<><E686AD><EFBFBD>𡒊<EFBFBD><F0A1928A>唳旿
*
* @param originalKey <20><EFBFBD><E7AC94><EFBFBD>辣key
* @param cleanData 皜<><E79A9C><EFBFBD>𡒊<EFBFBD><F0A1928A>唳旿
* @param suffix <20>𡒊<EFBFBD><EFBFBD><E59A97>霈?'_clean.json'嚗?
* @returns clean data <20>?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',
});
// 摨誩<E691A8><E8AAA9>硋僎銝𠹺<E98A9D>
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;
}
/**
* 霂餃<E99C82><EFBFBD><E79A9C><EFBFBD>𡒊<EFBFBD><F0A1928A>唳旿
*
* @param cleanDataKey clean data <20>?OSS key
* @returns 皜<><E79A9C><EFBFBD>𡒊<EFBFBD><F0A1928A>唳旿
*/
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;
}
/**
* <20>湔鰵皜<E9B0B5><E79A9C><EFBFBD>𡒊<EFBFBD><F0A1928A>唳旿
*
* @param cleanDataKey clean data <20>?OSS key
* @param newData <20>唳㺭<E594B3>?
*/
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');
}
/**
* <20>𣳇膄皜<E88684><E79A9C><EFBFBD>𡒊<EFBFBD><F0A1928A>唳旿
*
* @param cleanDataKey clean data <20>?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();
```
**雿輻鍂蝷箔<E89DB7>**嚗?
```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);
}
// <20><EFBFBD><E6BBA2>擧凒<E693A7>?
await dataCacheService.updateCleanData(record.cleanDataKey, newData);
```
#### 敶勗<E695B6><E58B97><EFBFBD>
- Tool C: 蝞<><E89D9E>𣇉緵<F0A38789>劐誨<E58A90>?
- ASL: <20><>讃閫<E8AE83><E996AB>蝏𤘪<E89D8F>蝻枏<E89DBB><EFBFBD><E59A97><EFBFBD>?9%嚗?
- Tool B: <20>唳旿<E594B3>𣂼<EFBFBD>蝏𤘪<E89D8F>蝻枏<E89DBB>
- <20><><EFBFBD>㗇芋<E39787>? 蝏煺<E89D8F><E785BA><EFBFBD><EFBFBD>摮䀹㦤<E480B9>?
---
### TD-COMMON-003: <20><EFBFBD><EFBFBD><E79A9C>蝞埈<E89D9E><E59F88>𡁶鍂<F0A181B6>?
**隡睃<E99AA1>蝥?*嚗尠<E59A97>潃?P2嚗<32>葉嚗?
**撌乩<E6928C><E4B9A9>?*嚗?.5憭?
**憸<><E686B8><EFBFBD><EFBFBD>**嚗帋誨<E5B88B><E8AAA8><EFBFBD><EFBFBD><EFBFBD><E58981><EFBFBD><E8B8B9><EFBFBD>摰䂿緵
#### <20><EFBFBD><E6A185>讛膩
**敶枏<E695B6><E69E8F><EFBFBD>?*嚗?
- Tool C 摰䂿緵嚗䫤intelligentCleanData`嚗<><EFBFBD><EFBFBD>瘚?摰匧<E691B0><E58CA7><EFBFBD>嚗?
- <20><EFBFBD><E79285>嚗𡁏𧊋摰䂿緵蝐颱撮<E9A2B1><EFBFBD>
**<EFBFBD>臭誑<EFBFBD>𡁶鍂<EFBFBD>𣇉<EFBFBD>蝞埈<EFBFBD>**嚗?
1. 撟賜<E6929F><E8B39C><EFBFBD>瘚页<E7989A>颲寧<E9A2B2><EFBFBD>瘚页<E7989A>
2. 撟賜<E6929F>銵諹<E98AB5>皛?
3. 摰匧<E691B0><E58CA7><EFBFBD><EFBFBD><E59A97>憭批<E686AD><E689B9><EFBFBD><E5959C><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E59C88><EFBFBD>
#### 閫<><E996AB><EFBFBD><EFBFBD>
```typescript
// backend/src/common/utils/dataCleaningUtils.ts嚗<73>鰵撱綽<E692B1>
export interface CleaningOptions {
maxCols?: number; // <20><>憭批<E686AD><E689B9><EFBFBD>暺䁅恕3000
maxCells?: number; // <20><>憭批<E686AD><E689B9><EFBFBD><EFBFBD><EFBFBD>暺䁅恕500銝?
removeEmptyRows?: boolean; // <20>臬炏<E887AC>𣳇膄蝛箄<E89D9B>嚗屸<E59A97>霈川rue
removeEmptyCols?: boolean; // <20>臬炏<E887AC>𣳇膄蝛箏<E89D9B>嚗屸<E59A97>霈川rue
}
export function intelligentCleanData(
data: any[],
options: CleaningOptions = {}
): any[] {
// 摰䂿緵颲寧<E9A2B2><EFBFBD>瘚卝<E7989A><E58D9D><EFBFBD><EFBFBD>/銵峕<E98AB5>瘣𨰜<E798A3><F0A8B09C><EFBFBD><EFBFBD><EFBFBD>
// ...
}
export function isValidValue(value: any): boolean {
// 蝏煺<E89D8F><E785BA><EFBFBD><EFBFBD>澆ế<E6BE86>?
// ...
}
```
#### 敶勗<E695B6><E58B97><EFBFBD>
- Tool C: 憭滨鍂<E6BBA8>𡁶鍂摰䂿緵
- <20><EFBFBD>銝𠹺<E98A9D>Excel<65><6C><EFBFBD><EFBFBD>? <20>湔𦻖雿輻鍂
---
### TD-COMMON-004: Worker瘜典<E7989C><EFBFBD>𨭌撌亙<E6928C>
**隡睃<E99AA1>蝥?*嚗尠<E59A97> P3嚗<33><E59A97>嚗?
**撌乩<E6928C><E4B9A9>?*嚗?.3憭?
**憸<><E686B8><EFBFBD><EFBFBD>**嚗𡁻<E59A97>雿竝orker瘜典<E7989C><EFBFBD><E99A9E><EFBFBD><EFBFBD>
#### <20><EFBFBD><E6A185>讛膩
**敶枏<E695B6><E69E8F><EFBFBD>?*嚗?
- 瘥譍葵璅<E79285><E288AA><EFBFBD><E8B3AA><EFBFBD> Worker 瘜典<E7989C><EFBFBD><E99A9E>
- <20>躰秤憭<E7A7A4><E686AD><EFBFBD><EFBFBD><E9A489><EFBFBD>
#### 閫<><E996AB><EFBFBD><EFBFBD>
```typescript
// backend/src/common/jobs/WorkerHelper.ts嚗<73>鰵撱綽<E692B1>
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] <20>?Worker registered: ${queueName}`);
}
```
**雿輻鍂蝷箔<E89DB7>**嚗?
```typescript
// 蝞<><E89D9E>𣇉<EFBFBD>瘜典<E7989C><EFBFBD><E99A9E>
registerWorker({
queueName: 'dc_toolc_parse_excel',
description: 'Excel閫<6C><E996AB>Worker',
handler: async (job) => {
// 銝𡁜𦛚<F0A1819C><EFBFBD>
return result;
},
onStart: (job) => console.log(`<EFBFBD>憪见<EFBFBD><EFBFBD>? ${job.id}`),
onComplete: (job, result) => console.log(`摰峕<EFBFBD>: ${result}`),
onError: (job, error) => console.error(`憭梯揖: ${error}`),
});
```
---
### TD-COMMON-005: <20>唳旿<E594B3><E697BF>𧋦蝞∠<E89D9E>蝟餌<E89D9F>
**隡睃<E99AA1>蝥?*嚗尠<E59A97>潃鐥<E6BD83>潃?P1嚗<31><E59A97>嚗?
**撌乩<E6928C><E4B9A9>?*嚗?憭?
**憸<><E686B8><EFBFBD><EFBFBD>**嚗𡁏𣈲<F0A1818F><F0A388B2>曎撘𤩺<E69298>雿栶<E99BBF><E6A0B6>ndo<64><EFBFBD><E8A098><EFBFBD><EFBFBD><EFBFBD><E7AE8F><EFBFBD><E884A9>?
#### <20><EFBFBD><E6A185>讛膩
**敶枏<E695B6><E69E8F>𣂼<EFBFBD>**嚗?
- <20><EFBFBD>銝齿糓蝝舐妖<E88890><E5A696><EFBFBD>瘥𤩺活<F0A4A9BA><EFBFBD><E7AE94><EFBFBD><E7AC94>唳旿嚗?
- <20><EFBFBD><E4ADBE><EFBFBD><E9AE8B><EFBFBD><EFBFBD>銝芣<E98A9D>雿𨅯<E99BBF><F0A885AF><EFBFBD>𠶖<EFBFBD>?
- <20><EFBFBD>撖澆枂銝剝𡢿<E5899D><F0A1A2BF>𧋦<EFBFBD><F0A78BA6><EFBFBD>?
**<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>極雿𨀣<EFBFBD>**嚗?
```
銝𠹺<EFBFBD>嚗ǒ0: 100銵䕘<E98AB5>
<20>?
蝑偦<EFBFBD><EFBFBD>v1: 50銵䕘<E98AB5><E49598>?<3F>臭誑<E887AD><EFBFBD><E9AE8B><EFBFBD><EFBFBD><E59597>?
<20>?
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>v2: 50銵䕘<E98AB5><E49598><EFBFBD><EFBFBD><E692A0><EFBFBD>?<3F>臭誑<E887AD><EFBFBD><E9AE8B><EFBFBD><EFBFBD><E59597>?
<20>?
Pivot嚗ǒ3: 銝滚<E98A9D>蝏𤘪<E89D8F>嚗?
<20>?
撖澆枂嚗𡁜虾隞亙紡<EFBFBD>?v0<76><30>1<EFBFBD><31>2<EFBFBD><32>3 隞餅<E99A9E><E9A485><EFBFBD>𧋦
```
#### 閫<><E996AB><EFBFBD><EFBFBD>
**Prisma Schema霈曇恣**嚗?
```prisma
// <20><>𧋦蝞∠<E89D9E>銵剁<E98AB5><E58981>𡁶鍂嚗?
model DataVersion {
id String @id @default(uuid())
// <20><EFBFBD>靽⊥<E99DBD><EFBFBD><E59A97><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
entityType String // 'dc_toolc_session' | 'asl_project' | ...
entityId String // Session ID | Project ID | ...
// <20><>𧋦靽⊥<E99DBD>
versionNumber Int // 0=<3D><EFBFBD>, 1=蝚?甈⊥<E79488>雿𨅯<E99BBF>, 2=蝚?甈⊥<E79488>雿𨅯<E99BBF>...
dataKey String // OSS銝剔<E98A9D><E58994>唳旿<E594B3><E697BF>辣key
// <20><EFBFBD>霈啣<E99C88>
operation String? // 'upload' | 'filter' | 'pivot' | 'recode' ...
operationParams Json? // <20><EFBFBD><E6BBA2><EFBFBD>
// <20><><EFBFBD>?
totalRows Int
totalCols Int
columns Json
// <20>園𡢿<E59C92>?
createdAt DateTime @default(now())
createdBy String // <20><EFBFBD>ID
@@unique([entityType, entityId, versionNumber])
@@index([entityType, entityId])
@@map("data_versions")
@@schema("platform_schema")
}
// 銝𡁜𦛚銵冽溶<E586BD><EFBFBD>畾?
model DcToolCSession {
// ...<2E><EFBFBD>摮埈挾
currentVersion Int @default(0) // 敶枏<E695B6><E69E8F><EFBFBD>𧋦<EFBFBD>?
}
```
**Service摰䂿緵**嚗?
```typescript
// backend/src/common/services/DataVersionService.ts嚗<73>鰵撱綽<E692B1>
export class DataVersionService {
/**
* <20>𥕦遣<F0A595A6><EFBFBD><E59581>?
*/
async createVersion(
entityType: string,
entityId: string,
versionNumber: number,
data: any[],
operation?: string,
params?: any
): Promise<string> {
// 靽嘥<E99DBD><E598A5>唳旿<E594B3>?OSS
const dataKey = `versions/${entityType}/${entityId}/v${versionNumber}.json`;
await storage.upload(dataKey, JSON.stringify(data));
// <20>𥕦遣<F0A595A6><E981A3>𧋦霈啣<E99C88>
await prisma.dataVersion.create({
data: {
entityType,
entityId,
versionNumber,
dataKey,
operation,
operationParams: params,
totalRows: data.length,
columns: Object.keys(data[0] || {}),
createdBy: userId,
}
});
return dataKey;
}
/**
* 霂餃<E99C82><E9A483><EFBFBD><EFBFBD><EFBFBD><EFBFBD>𧋦
*/
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('<27><>𧋦銝滚<E98A9D><E6BB9A>?);
const buffer = await storage.download(version.dataKey);
return JSON.parse(buffer.toString('utf-8'));
}
/**
* <20><EFBFBD><E9AE8B><EFBFBD><EFBFBD>摰𡁶<E691B0><F0A181B6>?
*/
async rollbackToVersion(
entityType: string,
entityId: string,
versionNumber: number
): Promise<void> {
// <20>湔鰵敶枏<E695B6><E69E8F><EFBFBD>𧋦<EFBFBD>?
await this.updateCurrentVersion(entityType, entityId, versionNumber);
}
/**
* <20><EFBFBD><E79195><EFBFBD>𧋦<EFBFBD><F0A78BA6>
*/
async listVersions(entityType: string, entityId: string) {
return await prisma.dataVersion.findMany({
where: { entityType, entityId },
orderBy: { versionNumber: 'asc' }
});
}
}
```
**QuickAction <20><><EFBFBD>**嚗?
```typescript
// QuickAction <20><EFBFBD><E689AF>舘䌊<E88898><EFBFBD>撱箸鰵<E7AEB8><E9B0B5>𧋦
const result = await python.execute(fullData, params);
// 霂餃<E99C82>敶枏<E695B6><E69E8F><EFBFBD>𧋦<EFBFBD>?
const currentVersion = session.currentVersion || 0;
// <20>𥕦遣<F0A595A6><EFBFBD><E59581>?
await dataVersionService.createVersion(
'dc_toolc_session',
sessionId,
currentVersion + 1,
result.data,
action, // 'filter' | 'pivot' | ...
params
);
// <20>湔鰵敶枏<E695B6><E69E8F><EFBFBD>𧋦<EFBFBD>?
await prisma.update({
where: { id: sessionId },
data: { currentVersion: currentVersion + 1 }
});
```
#### 敶勗<E695B6><E58B97><EFBFBD>
- Tool C: <20><EFBFBD><E88880><EFBFBD><E69B89><EFBFBD><E6BBA2><EFBFBD>ndo<64><6F><EFBFBD><EFBFBD><E7AE8F>?
- ASL: <20><>讃蝑偦<E89D91><EFBFBD>憭𡁻𧫴畾萇<E795BE><E89087>𦦵恣<F0A6A6B5>?
- Tool B: <20>唳旿<E594B3>𣂼<EFBFBD><F0A382BC><EFBFBD><EFBFBD><EFBFBD>祉恣<E7A589>?
- <20><><EFBFBD>㗇芋<E39787>? 蝏煺<E89D8F><E785BA><EFBFBD><EFBFBD><EFBFBD>祉恣<E7A589><E681A3><EFBFBD><EFBFBD>?
---
### TD-COMMON-006: 撟賜<E6929F><E8B39C>?銵峕<E98AB5>瘚讠<E7989A>瘜訫<E7989C>
**隡睃<E99AA1>蝥?*嚗尠<E59A97>潃?P2嚗<32>葉嚗?
**撌乩<E6928C><E4B9A9>?*嚗?.5憭?
**憸<><E686B8><EFBFBD><EFBFBD>**嚗鍃xcel憭<6C><E686AD>韐券<E99F90><E588B8>𣂼<EFBFBD>
#### <20><EFBFBD><E6A185>讛膩
**敶枏<E695B6><E69E8F><EFBFBD>?*嚗?
- Tool C 摰䂿緵嚗朞器<E69C9E><EFBFBD>瘚讠<E7989A>瘜?
- <20><EFBFBD><E79285>嚗𡁏𧊋摰䂿緵
**Excel<65><EFBFBD>瘙⊥<E79899><E28AA5><EFBFBD>**嚗?
- <20><EFBFBD><E586BD><EFBFBD><E790BF><EFBFBD>16384<38>?<3F>?閫<><E996AB><EFBFBD>?6384<38><EFBFBD>摰鮋<E691B0><E9AE8B><EFBFBD>151<35><EFBFBD><E59F88><EFBFBD><EFBFBD>
- <20><EFBFBD><E586BD>𣳇膄<F0A3B387>唳旿<E594B3><EFBFBD><E88AA3>?<3F>?閫<><E996AB><EFBFBD>箏之<E7AE8F>讐征銵?
#### 閫<><E996AB><EFBFBD><EFBFBD>
```typescript
// backend/src/common/utils/excelCleaning.ts嚗<73>鰵撱綽<E692B1>
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 {
// 摰䂿緵<E482BF>𡁶鍂<F0A181B6><E98D82><EFBFBD>瘣㛖<E798A3>瘜?
// ...
}
```
---
### TD-COMMON-007: <20>滨垢餈𥕦漲<F0A595A6><EFBFBD>隞?
**隡睃<E99AA1>蝥?*嚗尠<E59A97>潃?P2嚗<32>葉嚗?
**撌乩<E6928C><E4B9A9>?*嚗?.3憭?
**憸<><E686B8><EFBFBD><EFBFBD>**嚗䦧I蝏煺<E89D8F>嚗𣬚鍂<F0A3AC9A><EFBFBD>撉峕<E69289><E5B395>?
#### <20><EFBFBD><E6A185>讛膩
**敶枏<E695B6><E69E8F><EFBFBD>?*嚗?
- Tool C 摰䂿緵嚗𡁜<E59A97><F0A1819C><EFBFBD>摨行辺嚗<E8BEBA><E59A97><EFBFBD><EFBFBD>Header銝𧢲䲮嚗?
- <20><EFBFBD><E79285>嚗𡁏𧊋摰䂿緵<E482BF><EFBFBD>蝏煺<E89D8F>
#### 閫<><E996AB><EFBFBD><EFBFBD>
```typescript
// frontend-v2/src/common/components/AsyncProgressBar.tsx嚗<78>鰵撱綽<E692B1>
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>
);
};
```
---
## <20><> 摰墧鴌霈<E99C88>
| <20><><EFBFBD><EFBFBD>箏𦛚 | 隡睃<E99AA1>蝥?| 撌乩<E6928C><E4B9A9>?| 撱箄悅<E7AE84>嗆㦤 | <20><EFBFBD> |
|---------|--------|--------|---------|------|
| TD-COMMON-001 <20>滨垢頧株砭Hook | P2 | 0.5憭?| 銝𧢲活餈凋誨 | 隞<><E99A9E>蝏煺<E89D8F> |
| TD-COMMON-002 Clean Data<74>滚𦛚 | **P1** | 1憭?| **銝𧢲活餈凋誨** | **<EFBFBD><EFBFBD><EFBFBD>𣂼<EFBFBD>99%** |
| TD-COMMON-003 <20><EFBFBD><EFBFBD><E79A9C>蝞埈<E89D9E> | P2 | 0.5憭?| <20><><EFBFBD>𧒄 | 韐券<E99F90><E588B8>𣂼<EFBFBD> |
| TD-COMMON-004 Worker瘜典<E7989C><EFBFBD>𨭌 | P3 | 0.3憭?| <20><EFBFBD>?| 隞<><E99A9E><EFBFBD><E89D9E>?|
| TD-COMMON-005 <20>唳旿<E594B3><E697BF>𧋦蝞∠<E89D9E> | **P1** | 3憭?| **銝𧢲活餈凋誨** | **<EFBFBD><EFBFBD><EFBFBD><EFBFBD>** |
| TD-COMMON-006 撟賜<E6929F><E8B39C>?銵峕<E98AB5>瘚?| P2 | 0.5憭?| <20><><EFBFBD>𧒄 | 韐券<E99F90><E588B8>𣂼<EFBFBD> |
| TD-COMMON-007 餈𥕦漲<F0A595A6><EFBFBD>隞?| P2 | 0.3憭?| <20><><EFBFBD>𧒄 | UI蝏煺<E89D8F> |
**<EFBFBD><EFBFBD>隡睃<EFBFBD>蝥?*嚗?
1. 潃鐥<E6BD83>潃鐥<E6BD83>潃?TD-COMMON-002嚗㇃lean Data<74>滚𦛚嚗?
2. 潃鐥<E6BD83>潃鐥<E6BD83>潃?TD-COMMON-005嚗<35><EFBFBD><EFBFBD><E6A183>祉恣<E7A589><E681A3><EFBFBD>
3. 潃鐥<E6BD83>潃?TD-COMMON-001嚗<31><E59A97>蝡航蔭霂ook嚗?
---
## <20><> 敶枏<E695B6><E69E8F><EFBFBD><EFBFBD>
### <20>?撌脩<E6928C><EFBFBD><EFBFBD><E69CAB><EFBFBD><EFBFBD>?
1. <20>?**<2A>𡁶鍂<F0A181B6><EFBFBD><EFBFBD>抅蝖<E68A85>霈暹鴌摰峕㟲**
- jobQueue嚗īg-boss<73><EFBFBD>嚗?
- CheckpointService嚗<65><EFBFBD>寧賒隡𩤃<E99AA1>
- 隞餃𦛚<E9A483><F0A69B9A><EFBFBD>撌亙<E6928C>
- storage<67><65>ogger<65><72>ache
2. <20>?**銝𡁜𦛚璅<E79285><EFBFBD>雿輻鍂<E8BCBB>𡁶鍂<F0A181B6><EFBFBD>**
- Tool C<><43>SL<53><4C>ool B 蝏煺<E89D8F>雿輻鍂 jobQueue
- Platform-Only璅<E79285>
- 銝滚銁銝𡁜𦛚銵其葉摮睃<E691AE>隞餃𦛚蝞∠<E89D9E>靽⊥<E99DBD>
3. <20>?**<2A><><EFBFBD><EFBFBD>苊嚗諹<E59A97><EFBFBD><E99F90>蝖?*
- <20>𡁶鍂<F0A181B6><EFBFBD><EFBFBD><E69285><EFBFBD><EFBFBD>霈暹鴌
- 銝𡁜𦛚璅<E79285>嚗𡁜<E59A97>雿枏<E99BBF><E69E8F>?
### <20><EFBFBD> <20>臭誑<E887AD><EFBFBD><E5AFA1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>𧼮<EFBFBD><F0A7BCAE><EFBFBD>嚗?
1. <20><EFBFBD> <20>滨垢頧株砭璅<E79285><E288AA>臭誑<E887AD><EFBFBD><EFBFBD>
2. <20><EFBFBD> clean data蝻枏<E89DBB><E69E8F>臭誑<E887AD><EFBFBD>𡁶鍂
3. <20><EFBFBD> <20>唳旿<E594B3><E697BF>𧋦蝞∠<E89D9E><EFBFBD>遣蝡?
**蝏栞捏**嚗𡁜<E59A97><F0A1819C>齿沲<E9BDBF><E6B2B2>歇蝏讐泵<E8AE90><E6B3B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>芣䔉隡睃<E99AA1><E79D83>舫釺銝𦠜溶<F0A6A09C><EFBFBD>
---
## <20><> <20><><EFBFBD><EFBFBD><EFBFBD>獢?
- [Postgres-Only撘<79>郊隞餃𦛚憭<F0A69B9A><E686AD><EFBFBD><EFBFBD><EFBFBD>](./Postgres-Only撘<79>郊隞餃𦛚憭<F0A69B9A><E686AD><EFBFBD><EFBFBD><EFBFBD>.md) - 摰峕㟲摰噼殿
- [鈭穃<E988AD><E7A983><EFBFBD><E7AC94>𤏸<EFBFBD><F0A48FB8><EFBFBD>(../04-撘<><E69298>𤏸<EFBFBD><F0A48FB8>?08-鈭穃<E988AD><E7A983><EFBFBD><E7AC94>𤏸<EFBFBD><F0A48FB8>?md) - 撘<><E69298>𤏸<EFBFBD><F0A48FB8>?
- [DC Tool C<><EFBFBD><E59786>(../03-銝𡁜𦛚璅<E79285>/DC-<2D>唳旿皜<E697BF><E79A9C><EFBFBD><EFBFBD>/00-撌亙<E6928C>C敶枏<E695B6><E69E8F><EFBFBD><E59786><EFBFBD><EFBFBD><E69298><EFBFBD><E78390>?md) - Day 10摰噼殿
---
**蝏湔擪<E6B994>?*: 撟喳蝱<E596B3><EFBFBD><E59786><EFBFBD>
**<EFBFBD><EFBFBD><EFBFBD>擧凒<EFBFBD>?*: 2025-12-22
**<EFBFBD><EFBFBD><EFBFBD><EFBFBD>?*: <20>?<3F><EFBFBD><E598A5><EFBFBD>𧋦