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

19 KiB
Raw Blame History

<EFBFBD>𡁶鍂<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>箏𦛚皜<EFBFBD><EFBFBD>

*<EFBFBD><EFBFBD><EFBFBD><EFBFBD>𧋦嚗? v1.0
*<EFBFBD>𥕦遣<EFBFBD><EFBFBD>嚗? 2025-12-22
蝏湔擪<EFBFBD><EFBFBD><EFBFBD> 撟喳蝱<E596B3><EFBFBD><E59786><EFBFBD>
*<EFBFBD><EFBFBD><EFBFBD><EFBFBD>嚗? 霈啣<E99C88><E595A3>𡁶鍂<F0A181B6><EFBFBD><EFBFBD><E69285>隡睃<E99AA1>憿對<E686BF><E5B08D><EFBFBD><EFBFBD>芣䔉餈凋誨


<EFBFBD><EFBFBD><>

<EFBFBD><EFBFBD><EFBFBD>抅鈭?DC Tool C 撘<><EFBFBD><EFBFBD>摰噼殿嚗?025-12-22嚗㚁<E59A97><E39A81><EFBFBD><E9A48C>𤑳緵<F0A491B3><E7B7B5>虾隞交𡂝鞊∩蛹<E288A9>𡁶鍂<F0A181B6><EFBFBD><E8B3A2><EFBFBD>芋撘譌<E69298>?

**<2A><EFBFBD><E8A9A8><EFBFBD>**嚗?

  • <EFBFBD>?敶枏<EFBFBD><EFBFBD><EFBFBD><EFBFBD>撌脩<EFBFBD><EFBFBD><EFBFBD><EFBFBD>𡁶鍂<EFBFBD><EFBFBD><EFBFBD><EFBFBD>靘𥕦<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>霈暹鴌嚗?
  • <EFBFBD>?*銝𡁜𦛚璅<EFBFBD><EFBFBD>雿輻鍂鈭<EFBFBD><EFBFBD>鈭𥡝<EFBFBD><EFBFBD>?
  • <EFBFBD><EFBFBD> **<2A>芣䔉<E88AA3>臭誑餈𥕢<E9A488>甇交𡂝鞊?*嚗<>釺銝𦠜溶<F0A6A09C><EFBFBD><E6A2A7>𧼮<EFBFBD><F0A7BCAE><EFBFBD>嚗?

<EFBFBD><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>

<EFBFBD><EFBFBD><EFBFBD>讛膩

**敶枏<E695B6><E69E8F><EFBFBD>?*嚗?

  • Tool C 摰䂿緵嚗䫤dc/tool-c/hooks/useSessionStatus.ts`
  • ASL 摰䂿緵嚗䫤asl/hooks/useScreeningTask.ts`
  • <EFBFBD><EFBFBD><EFBFBD><EFBFBD>摨佗<EFBFBD>70%

**<2A><EFBFBD><E6BB9A><EFBFBD><EFBFBD><EFBFBD>**嚗?

// 瘥譍葵璅<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,
});

<EFBFBD><EFBFBD><EFBFBD><EFBFBD>

<EFBFBD>質情銝粹<EFBFBD>𡁶鍂Hook嚗?

// 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>**嚗?

// 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),
});

敶勗<EFBFBD><EFBFBD><EFBFBD>

  • Tool C: useSessionStatus.ts <20><EFBFBD><E88890>?
  • ASL: useScreeningTask.ts <20><EFBFBD><E88890>?
  • Tool B: <20>芣䔉<E88AA3>滨垢<E6BBA8>舐凒<E88890>乩蝙<E4B9A9>?
  • <EFBFBD><EFBFBD><EFBFBD>: <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>?

<EFBFBD><EFBFBD><EFBFBD>讛膩

**敶枏<E695B6><E69E8F><EFBFBD>?*嚗?

  • Tool C 摰䂿緵嚗帋<E59A97>摮?${fileKey}_clean.json
  • ASL<EFBFBD><EFBFBD>ool B嚗𡁏𧊋摰䂿緵嚗䔶<E59A97><E494B6><EFBFBD>甈⊿<E79488><E28ABF>啗圾<E59597>?

**<2A><EFBFBD>霈∠<E99C88><E288A0><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>嚗?

<EFBFBD><EFBFBD><EFBFBD><EFBFBD>

<EFBFBD>質情銝粹<EFBFBD>𡁶鍂<EFBFBD>滚𦛚嚗?

// 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>**嚗?

// 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);

敶勗<EFBFBD><EFBFBD><EFBFBD>

  • Tool C: 蝞<><E89D9E>𣇉緵<F0A38789>劐誨<E58A90>?
  • ASL: <20><>讃閫<E8AE83><E996AB>蝏𤘪<E89D8F>蝻枏<E89DBB><EFBFBD><E59A97><EFBFBD>?9%嚗?
  • Tool B: <20>唳旿<E594B3>𣂼<EFBFBD>蝏𤘪<E89D8F>蝻枏<E89DBB>
  • <EFBFBD><EFBFBD><EFBFBD>㗇芋<EFBFBD>? 蝏煺<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>摰䂿緵

<EFBFBD><EFBFBD><EFBFBD>讛膩

**敶枏<E695B6><E69E8F><EFBFBD>?*嚗?

  • Tool C 摰䂿緵嚗䫤intelligentCleanData`嚗<><EFBFBD><EFBFBD>瘚?摰匧<E691B0><E58CA7><EFBFBD>嚗?
  • <EFBFBD><EFBFBD><EFBFBD>嚗𡁏𧊋摰䂿緵蝐颱撮<EFBFBD><EFBFBD>

**<2A>臭誑<E887AD>𡁶鍂<F0A181B6>𣇉<EFBFBD>蝞埈<E89D9E>**嚗?

  1. 撟賜<EFBFBD><EFBFBD><EFBFBD>瘚页<EFBFBD>颲寧<EFBFBD><EFBFBD>瘚页<EFBFBD>
  2. 撟賜<EFBFBD>銵諹<EFBFBD>皛?
  3. 摰匧<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>憭批<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>

<EFBFBD><EFBFBD><EFBFBD><EFBFBD>

// 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>?
  // ...
}

敶勗<EFBFBD><EFBFBD><EFBFBD>

  • Tool C: 憭滨鍂<E6BBA8>𡁶鍂摰䂿緵
  • <EFBFBD><EFBFBD>銝𠹺<EFBFBD>Excel<EFBFBD><EFBFBD><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>

<EFBFBD><EFBFBD><EFBFBD>讛膩

**敶枏<E695B6><E69E8F><EFBFBD>?*嚗?

  • 瘥譍葵璅<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Worker 瘜典<E7989C><EFBFBD><E99A9E>
  • <EFBFBD>躰秤憭<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>

<EFBFBD><EFBFBD><EFBFBD><EFBFBD>

// 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>**嚗?

// 蝞<><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(`撘<>憪见<E686AA><E8A781>? ${job.id}`),
  onComplete: (job, result) => console.log(`摰峕<E691B0>: ${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>?

<EFBFBD><EFBFBD><EFBFBD>讛膩

**敶枏<E695B6><E69E8F>𣂼<EFBFBD>**嚗?

  • <EFBFBD><EFBFBD>銝齿糓蝝舐妖<EFBFBD><EFBFBD><EFBFBD>瘥𤩺活<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>唳旿嚗?
  • <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>銝芣<EFBFBD>雿𨅯<EFBFBD><EFBFBD><EFBFBD>𠶖<EFBFBD>?
  • <EFBFBD><EFBFBD>撖澆枂銝剝𡢿<EFBFBD><EFBFBD>𧋦<EFBFBD><EFBFBD><EFBFBD>?

**<2A><EFBFBD><E586BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>極雿𨀣<E99BBF>**嚗?

銝𠹺<EFBFBD>嚗ǒ0: 100銵䕘<E98AB5>
  <20>?
蝑偦<E89D91><EFBFBD>v1: 50銵䕘<E98AB5><E49598>?<3F>臭誑<E887AD><EFBFBD><E9AE8B><EFBFBD><EFBFBD><E59597>?
  <20>?
<0A><EFBFBD><EFBFBD><EFBFBD><E692A0>v2: 50銵䕘<E98AB5><E49598><EFBFBD><EFBFBD><E692A0><EFBFBD>?<3F>臭誑<E887AD><EFBFBD><E9AE8B><EFBFBD><EFBFBD><E59597>?
  <20>?
Pivot嚗ǒ3: 銝滚<E98A9D>蝏𤘪<E89D8F>嚗?
  <20>?
撖澆枂嚗𡁜虾隞亙紡<E4BA99>?v0<76><30>1<EFBFBD><31>2<EFBFBD><32>3 隞餅<E99A9E><E9A485><EFBFBD>𧋦

<EFBFBD><EFBFBD><EFBFBD><EFBFBD>

Prisma Schema霈曇恣嚗?

// <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摰䂿緵嚗?

// 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>**嚗?

// 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 }
});

敶勗<EFBFBD><EFBFBD><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>?
  • <EFBFBD><EFBFBD><EFBFBD>㗇芋<EFBFBD>? 蝏煺<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>

<EFBFBD><EFBFBD><EFBFBD>讛膩

**敶枏<E695B6><E69E8F><EFBFBD>?*嚗?

  • Tool C 摰䂿緵嚗朞器<E69C9E><EFBFBD>瘚讠<E7989A>瘜?
  • <EFBFBD><EFBFBD><EFBFBD>嚗𡁏𧊋摰䂿緵

**Excel<65><EFBFBD>瘙⊥<E79899><E28AA5><EFBFBD>**嚗?

  • <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>16384<EFBFBD>?<3F>?閫<><E996AB><EFBFBD>?6384<38><EFBFBD>摰鮋<E691B0><E9AE8B><EFBFBD>151<35><EFBFBD><E59F88><EFBFBD><EFBFBD>
  • <EFBFBD><EFBFBD><EFBFBD>𣳇膄<EFBFBD>唳旿<EFBFBD><EFBFBD><EFBFBD>?<3F>?閫<><E996AB><EFBFBD>箏之<E7AE8F>讐征銵?

<EFBFBD><EFBFBD><EFBFBD><EFBFBD>

// 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>?

<EFBFBD><EFBFBD><EFBFBD>讛膩

**敶枏<E695B6><E69E8F><EFBFBD>?*嚗?

  • Tool C 摰䂿緵嚗𡁜<E59A97><F0A1819C><EFBFBD>摨行辺嚗<E8BEBA><E59A97><EFBFBD><EFBFBD>Header銝𧢲䲮嚗?
  • <EFBFBD><EFBFBD><EFBFBD>嚗𡁏𧊋摰䂿緵<EFBFBD><EFBFBD>蝏煺<EFBFBD>

<EFBFBD><EFBFBD><EFBFBD><EFBFBD>

// 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>
  );
};

<EFBFBD><EFBFBD> 摰墧鴌霈<E99C88>

<EFBFBD><EFBFBD><EFBFBD><EFBFBD>箏𦛚 隡睃<EFBFBD>蝥? 撌乩<EFBFBD><EFBFBD>? 撱箄悅<EFBFBD>嗆㦤 <EFBFBD><EFBFBD>
TD-COMMON-001 <20>滨垢頧株砭Hook P2 0.5憭? 銝𧢲活餈凋誨 <EFBFBD><EFBFBD>蝏煺<EFBFBD>
TD-COMMON-002 Clean Data<74>滚𦛚 P1 1憭? 銝𧢲活餈凋誨 <EFBFBD><EFBFBD><EFBFBD>𣂼<EFBFBD>99%
TD-COMMON-003 <20><EFBFBD><EFBFBD><E79A9C>蝞埈<E89D9E> P2 0.5憭? <EFBFBD><EFBFBD><EFBFBD>𧒄 韐券<EFBFBD><EFBFBD>𣂼<EFBFBD>
TD-COMMON-004 Worker瘜典<E7989C><EFBFBD>𨭌 P3 0.3憭? <EFBFBD><EFBFBD>? <EFBFBD><EFBFBD><EFBFBD><EFBFBD>?
TD-COMMON-005 <20>唳旿<E594B3><E697BF>𧋦蝞∠<E89D9E> P1 3憭? 銝𧢲活餈凋誨 <EFBFBD><EFBFBD><EFBFBD><EFBFBD>
TD-COMMON-006 撟賜<E6929F><E8B39C>?銵峕<E98AB5>瘚? P2 0.5憭? <EFBFBD><EFBFBD><EFBFBD>𧒄 韐券<EFBFBD><EFBFBD>𣂼<EFBFBD>
TD-COMMON-007 餈𥕦漲<F0A595A6><EFBFBD>隞? P2 0.3憭? <EFBFBD><EFBFBD><EFBFBD>𧒄 UI蝏煺<EFBFBD>

**<2A><EFBFBD>隡睃<E99AA1>蝥?*嚗?

  1. 潃鐥<EFBFBD>潃鐥<EFBFBD>潃?TD-COMMON-002嚗㇃lean Data<74>滚𦛚嚗?
  2. 潃鐥<EFBFBD>潃鐥<EFBFBD>潃?TD-COMMON-005嚗<35><EFBFBD><EFBFBD><E6A183>祉恣<E7A589><E681A3><EFBFBD>
  3. 潃鐥<EFBFBD>潃?TD-COMMON-001嚗<31><E59A97>蝡航蔭霂ook嚗?

<EFBFBD><EFBFBD> 敶枏<E695B6><E69E8F><EFBFBD><EFBFBD>

<EFBFBD>?撌脩<E6928C><EFBFBD><EFBFBD><E69CAB><EFBFBD><EFBFBD>?

  1. <EFBFBD>?<EFBFBD>𡁶鍂<EFBFBD><EFBFBD><EFBFBD>抅蝖<EFBFBD>霈暹鴌摰峕㟲

    • jobQueue嚗īg-boss<73><EFBFBD>嚗?
    • CheckpointService嚗<EFBFBD><EFBFBD>寧賒隡𩤃<EFBFBD>
    • 隞餃𦛚<EFBFBD><EFBFBD><EFBFBD>撌亙<EFBFBD>
    • storage<EFBFBD><EFBFBD>ogger<EFBFBD><EFBFBD>ache
  2. <EFBFBD>?銝𡁜𦛚璅<EFBFBD><EFBFBD>雿輻鍂<EFBFBD>𡁶鍂<EFBFBD><EFBFBD>

    • Tool C<><43>SL<53><4C>ool B 蝏煺<E89D8F>雿輻鍂 jobQueue
    • Platform-Only璅<E79285>
    • 銝滚銁銝𡁜𦛚銵其葉摮睃<EFBFBD>隞餃𦛚蝞∠<EFBFBD>靽⊥<EFBFBD>
  3. <EFBFBD>?**<2A><><EFBFBD><EFBFBD>苊嚗諹<E59A97><EFBFBD><E99F90>蝖?*

    • <EFBFBD>𡁶鍂<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>霈暹鴌
    • 銝𡁜𦛚璅<EFBFBD>嚗𡁜<EFBFBD>雿枏<EFBFBD><EFBFBD>?

<EFBFBD><EFBFBD> <20>臭誑<E887AD><EFBFBD><E5AFA1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>𧼮<EFBFBD><F0A7BCAE><EFBFBD>嚗?

  1. <EFBFBD><EFBFBD> <20>滨垢頧株砭璅<E79285><E288AA>臭誑<E887AD><EFBFBD><EFBFBD>
  2. <EFBFBD><EFBFBD> clean data蝻枏<E89DBB><E69E8F>臭誑<E887AD><EFBFBD>𡁶鍂
  3. <EFBFBD><EFBFBD> <20>唳旿<E594B3><E697BF>𧋦蝞∠<E89D9E><EFBFBD>遣蝡?

蝏栞捏嚗𡁜<EFBFBD><EFBFBD>齿沲<EFBFBD><EFBFBD>歇蝏讐泵<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>芣䔉隡睃<EFBFBD><EFBFBD>舫釺銝𦠜溶<EFBFBD><EFBFBD>


<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>獢?

  • Postgres-Only撘<79>郊隞餃𦛚憭<F0A69B9A><E686AD><EFBFBD><EFBFBD><EFBFBD> - 摰峕㟲摰噼殿
  • [鈭穃<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>
**<2A><><EFBFBD>擧凒<E693A7>?
: 2025-12-22
**<2A><><EFBFBD><EFBFBD>?*: <20>?<3F><EFBFBD><E598A5><EFBFBD>𧋦