Files
lcbp3/specs/300-others/302-ai-model-revision/tasks.md
T
admin 6cb3ae10ee
CI / CD Pipeline / build (push) Failing after 5m36s
CI / CD Pipeline / deploy (push) Has been skipped
feat(ai): unify AI architecture, implement RAG and legacy migration
2026-05-15 11:10:44 +07:00

20 KiB
Raw Blame History

Tasks: AI Model Revision (ADR-023A)

Input: specs/300-others/302-ai-model-revision/ (spec.md, plan.md, data-model.md, contracts/) Feature: 302-ai-model-revision | Date: 2026-05-15 Prerequisites: plan.md | spec.md | research.md | data-model.md | contracts/


Format: [ID] [P?] [Story] Description

  • [P]: สามารถรันพร้อมกัน (คนละไฟล์, ไม่มี dependency กัน)
  • [US1/US2/US3/US4]: User Story ที่ task นี้ satisfy
  • ทุก task ต้องระบุ file path ที่แน่ชัด

Phase 1: Setup & Cleanup (Tier 1 Critical First)

Purpose: ลบ Typhoon ออก, ตั้ง BullMQ 2-queue, สร้าง Schema Delta

⚠️ CRITICAL: Phase นี้ต้องทำเสร็จก่อน Phase ถัดไปทั้งหมด — Typhoon removal เป็น Tier 1 blocking

  • T001 Delete Typhoon Cloud API service: rm backend/src/modules/ai/rag/typhoon.service.ts และลบ reference ทั้งหมดออกจาก backend/src/modules/ai/ai.module.ts, backend/src/modules/ai/rag/rag.service.ts
  • T002 [P] สร้าง SQL delta #14: specs/03-Data-and-Storage/deltas/14-add-migration-review-queue.sql ตาม schema ใน data-model.md (ADR-009 — ห้ามใช้ TypeORM migration)
  • T002B [P] สร้าง SQL delta #15: specs/03-Data-and-Storage/deltas/15-add-ai-processing-status.sqlALTER TABLE documents ADD COLUMN ai_processing_status ENUM('PENDING','PROCESSING','DONE','FAILED') NOT NULL DEFAULT 'PENDING'; ADD INDEX idx_ai_status (ai_processing_status) (FR-018, ADR-009)
  • T003 [P] อัปเดต backend/src/config/bullmq.config.ts — เพิ่ม ai-batch queue config (concurrency=1, defaultJobOptions: retry 3, backoff exponential)
  • T004 อัปเดต backend/.env.example — เพิ่ม OLLAMA_MODEL_MAIN, OLLAMA_MODEL_EMBED, QDRANT_HOST, QDRANT_COLLECTION, OCR_CHAR_THRESHOLD, OCR_API_URL
  • T005 ตรวจสอบว่าไม่มี Typhoon reference เหลือ: grep -r "typhoon" backend/src --include="*.ts" ต้องไม่มีผล

Checkpoint: grep -r "typhoon" → 0 results; bullmq.config.ts มี 2 queues; delta file สร้างแล้ว


Phase 2: Foundational (Blocking Prerequisites)

Purpose: Core AI infrastructure ที่ทุก User Story ต้องการ

⚠️ CRITICAL: ต้องทำเสร็จก่อนทุก US

  • T006 สร้าง backend/src/modules/ai/processors/ai-realtime.processor.ts — BullMQ @Processor('ai-realtime') รองรับ jobType: 'ai-suggest' และ 'rag-query'; ใส่ logic pause ai-batch เมื่อ job active (event: active); resume ai-batch เมื่อ job completed/failed (events: completed, failed)
  • T006A เพิ่ม onModuleInit() ใน backend/src/modules/ai/ai.module.ts (implements OnModuleInit) — startup check: const isPaused = await aiBatchQueue.isPaused() AND const activeCount = await aiRealtimeQueue.getActiveCount() → ถ้า isPaused && activeCount === 0await aiBatchQueue.resume(); this.logger.warn('ai-batch auto-resumed on startup (stale paused state)') (FR-016)
  • T007 สร้าง backend/src/modules/ai/processors/ai-batch.processor.ts — BullMQ @Processor('ai-batch') รองรับ jobType: 'ocr', 'extract-metadata', 'embed-document'
  • T008 [P] อัปเดต backend/src/modules/ai/services/ollama.service.ts — เปลี่ยน model จาก gemma4:9b เป็น process.env.OLLAMA_MODEL_MAIN (default: gemma4:e4b); เพิ่ม generateEmbedding() ที่ใช้ process.env.OLLAMA_MODEL_EMBED
  • T009 [P] อัปเดต backend/src/modules/ai/services/qdrant.service.ts — เปลี่ยน search() signature ให้ projectPublicId: string เป็น required param แรก; เพิ่ม must: [{ key: 'project_public_id', match: { value: projectPublicId } }] filter ใน payload; ลบ rawSearch() ออก
  • T010 สร้าง backend/src/modules/ai/services/ocr.service.ts — auto-detect: ถ้า extractedChars > OCR_CHAR_THRESHOLD → Fast Path (return text); else → call PaddleOCR sidecar ที่ OCR_API_URL; return { text: string; ocrUsed: boolean }
  • T011 อัปเดต backend/src/modules/ai/ai.module.ts — register BullModule ทั้ง 2 queues; provide processors ทั้งคู่; ลบ Typhoon import; register entity MigrationReviewQueueEntity
  • T012 [P] สร้าง backend/src/modules/ai/entities/migration-review-queue.entity.ts — TypeORM entity ตาม schema ใน data-model.md (column: publicId, batchId, idempotencyKey, aiMetadataJson, confidenceScore, ocrUsed, status, reviewedBy, reviewedAt, rejectionReason)

Checkpoint: NestJS compile สำเร็จ ไม่มี TypeScript error; QdrantService ไม่มี method ที่ไม่รับ projectPublicId


Phase 3: User Story 1 — AI-Assisted Document Classification (Priority: P1) 🎯 MVP

Goal: Digital/Scanned PDF detection + AI Suggest metadata + frontend display

Independent Test: อัปโหลด PDF → AI Suggestion ปรากฏบนฟอร์มภายใน 30s

Implementation

  • T013 [US1] สร้าง backend/src/modules/ai/dto/create-ai-job.dto.ts — field: documentPublicId: string (IsUUID), projectPublicId: string (IsUUID), jobType: 'ai-suggest' | 'rag-query' | 'ocr' | 'extract-metadata' | 'embed-document', idempotencyKey: string
  • T014 [US1] อัปเดต backend/src/modules/ai/ai.service.ts — method queueSuggestJob(): ตรวจสอบ Idempotency-Key, ส่ง job ไป ai-realtime queue พร้อม payload; method queueEmbedJob(): ส่ง job ไป ai-batch queue (ทั้งสองเรียกพร้อมกันหลัง commit)
  • T015 [US1] อัปเดต AI-Suggest logic ใน ai-realtime.processor.ts — ดึงไฟล์จาก storage, เรียก OcrService.detectAndExtract() (3 หน้าแรก), ส่ง text ไป OllamaService; validate categorical fields กับ MasterDataService.getCategories() (FR-014): ถ้า value ไม่รู้จัก → set is_unknown: true ใน suggestion JSON; บันทึก raw AI output ใน ai_audit_logs.ai_suggestion_json (รวมค่าที่ไม่รู้จัก — FR-015); return suggestion JSON พร้อม is_unknown flag ให้ frontend แสดง badge (FR-015)
  • T016 [US1] อัปเดต backend/src/modules/ai/ai.controller.ts — endpoint POST /api/ai/suggest รับ CreateAiJobDto, ตรวจสอบ Idempotency-Key header, เรียก AiService.queueSuggestJob(); endpoint GET /api/ai/jobs/:jobId/status สำหรับ polling; CASL guard: ai.manage
  • T017 [P] [US1] อัปเดต frontend/components/ai/ai-suggestion-field.tsx — แสดง confidence badge: ≥0.85 → สีเขียว "AI แนะนำ", 0.60-0.84 → สีเหลือง "⚠️ ตรวจสอบก่อนยืนยัน", <0.60 → ว่าง; polling GET /api/ai/jobs/:jobId/status ทุก 3s จนกว่า completed/failed
  • T018 [P] [US1] อัปเดต frontend/components/ai/AiStatusBanner.tsx — แสดง AI service status (online/offline/queue-paused); ถ้า offline → banner "AI ไม่พร้อม กรอก Metadata เอง" แทน spinner
  • T019 [US1] Trigger dual-queue จาก Document commit flow — หาจุดใน backend/src/modules/documents/document.service.ts (หรือ rfa.service.ts) ที่ commit document แล้ว: wrap Promise.all([queueSuggestJob(), queueEmbedJob()]) ใน try/catch (FR-017) — on success: ไม่ await result (fire-and-forget); on catch: Logger.error('AI job queue failed', { documentPublicId, error }) ไม่ throw เพื่อไม่ทำลาย commit flow; set ai_processing_status = 'FAILED' ของ document record
  • T019B [US1] อัปเดต ai-realtime.processor.ts + ai-batch.processor.ts — เมื่อ dequeue: set document.ai_processing_status = 'PROCESSING'; เมื่อ ทั้ง ai-suggest + embed-document สำเร็จ: set ai_processing_status = 'DONE'; เมื่อ dead-letter: set ai_processing_status = 'FAILED' (FR-018)
  • T020 [US1] ทดสอบ fallback: ปิด OLLAMA_HOST → อัปโหลดเอกสาร → ตรวจสอบว่า document บันทึกได้ปกติและ UI แสดง warning ไม่ใช่ error 500

Checkpoint: อัปโหลด Digital PDF → AI Suggestion ใน 30s; อัปโหลด Scanned PDF → Suggestion ใน 90s; ai_audit_logs มี record ใหม่


Phase 4: User Story 2 — RAG-based Document Q&A (Priority: P2)

Goal: Full-document chunked embedding + projectPublicId isolation + RAG query endpoint

Independent Test: embed 2 docs ใน Project A, query → ได้เฉพาะ Project A; latency < 10s

Implementation

  • T021 [US2] สร้าง backend/src/modules/ai/services/embedding.service.tsembedDocument(pdfPath, documentPublicId, projectPublicId): ดึงข้อความ full-doc ด้วย PyMuPDF, chunk 512 tokens / 64 overlap, เรียก OllamaService.generateEmbedding() ต่อ chunk, upsert ไป Qdrant ผ่าน QdrantService.upsert(projectPublicId, points) (ต้องส่ง projectPublicId เสมอ)
  • T022 [US2] อัปเดต embed-document logic ใน ai-batch.processor.ts — เรียก EmbeddingService.embedDocument() พร้อมรับ retries; ถ้า fail 3 ครั้ง → dead-letter; อัปเดต ai_audit_logs status
  • T023 [US2] สร้าง backend/src/modules/ai/dto/rag-query.dto.ts — field: projectPublicId: string (IsUUID, Required), question: string (MaxLength 500), topK: number (Min 1, Max 20, Default 5)
  • T024 [US2] อัปเดต backend/src/modules/ai/rag/rag.service.ts — method query(dto: RagQueryDto): embed คำถามด้วย nomic-embed-text, call QdrantService.search(dto.projectPublicId, embedding, dto.topK), ส่ง context ไป OllamaService, return { answer, sources: [{documentPublicId, chunkText, pageNumber}] }
  • T025 [US2] เพิ่ม endpoint POST /api/ai/rag/query ใน ai.controller.ts — รับ RagQueryDto, queue ไป ai-realtime (rag-query), return jobId; CASL guard: ai.query
  • T026 [P] [US2] อัปเดต frontend/components/ai/RagChatWidget.tsx — ส่ง projectPublicId ใน request body; แสดง sources citation (document name + page); แสดง "เอกสารใหม่อาจยังไม่อยู่ใน index" ถ้า document < 5 นาที
  • T027 [US2] ทดสอบ multi-tenancy: embed doc ใน Project A และ Project B → query ด้วย projectPublicId ของ A → ต้องไม่เห็น doc ของ B ในผล (ตรวจสอบใน Qdrant query log)
  • T028 [US2] เพิ่ม QdrantService.deleteByDocument(projectPublicId, documentPublicId) — ใช้เมื่อ document ถูกลบออกจาก DMS; hook เข้า document.service.ts soft-delete flow

Checkpoint: RAG query ตอบกลับ < 10s; ผล isolate ตาม projectPublicId; Qdrant ไม่มีข้อมูลข้ามโครงการ


Phase 5: User Story 3 — Legacy Migration Pipeline (Priority: P3)

Goal: n8n → DMS API → migration_review_queue → Admin Review UI

Independent Test: POST /api/ai/migration/queue → queue item PENDING → Admin Approve → document imported + embed queued

Implementation

  • T029 [US3] สร้าง backend/src/modules/ai/dto/migration-queue-item.dto.ts — field: batchId: string, filename: string, tempPath: string; idempotencyKey ดึงจาก header
  • T030 [US3] สร้าง backend/src/modules/ai/services/migration.service.ts — method queueForReview(dto, idempotencyKey): สร้าง MigrationReviewQueue record (status=PENDING), queue ai-batch: ocr + extract-metadata; method approve(publicId, reviewedBy): import document, queue embed-document; method reject(publicId, reason); method findAll(filters) pagination
  • T031 [US3] เพิ่ม endpoint ใน ai.controller.ts: POST /api/ai/migration/queue (Idempotency-Key header required), GET /api/ai/migration/queue, POST /api/ai/migration/queue/:publicId/approve, POST /api/ai/migration/queue/:publicId/reject; CASL guard: ai.manage (SYSTEM_ADMIN only)
  • T032 [P] [US3] สร้าง frontend/components/ai/migration-queue-table.tsx — แสดง list ของ migration_review_queue; column: filename, confidenceScore (badge), status, ocrUsed; ปุ่ม Approve/Reject ต่อ row; filter by status/batchId
  • T033 [P] [US3] สร้าง frontend/app/(dashboard)/ai-staging/migration-review/page.tsx — ใช้ MigrationQueueTable component; TanStack Query สำหรับ data fetching + optimistic update เมื่อ approve/reject
  • T034 [US3] อัปเดต frontend/app/(dashboard)/ai-staging/page.tsx — เพิ่ม tab "Migration Queue" link ไปยัง /ai-staging/migration-review
  • T035 [US3] ทดสอบ Idempotency: POST migration/queue 2 ครั้งด้วย Idempotency-Key เดิม → ตรวจสอบว่า record ไม่ถูกสร้างซ้ำ (HTTP 409 ครั้งที่สอง)

Checkpoint: n8n สามารถ POST และได้ 202; Admin เห็น queue ใน UI; Approve → document import + embed queued


Phase 6: User Story 4 — AI Monitoring & Threshold Management (Priority: P4)

Goal: Admin dashboard AI metrics + threshold recalibration + ai_audit_logs delete permission

Independent Test: Admin ดู dashboard → เห็น confidence distribution; Admin ลบ test log → audit_logs บันทึก

Implementation

  • T036 [US4] เพิ่ม endpoint GET /api/ai/analytics/summary ใน ai.controller.ts — query ai_audit_logs GROUP BY document_type, status; return: avgConfidence, overrideRate, rejectedRate per type; CASL: ai.read_analytics
  • T037 [US4] เพิ่ม endpoint DELETE /api/ai/audit-logs/:publicId — CASL: ai.delete_audit (SYSTEM_ADMIN only); บันทึกใน audit_logs (action: 'AI_AUDIT_LOG_DELETED', targetId: publicId)
  • T038 [P] [US4] อัปเดต frontend/app/(dashboard)/ai-staging/page.tsx — เพิ่ม tab "AI Analytics"; แสดง: confidence distribution bar chart (TBD: ใช้ recharts หรือ shadcn chart), override rate, rejected rate แยกตาม document_type
  • T039 [P] [US4] เพิ่ม Threshold Recalibration UI ใน ai-staging page — แสดง current threshold (HIGH=0.85, MID=0.60 จาก ENV), แสดงคำแนะนำ "ถ้า override rate > 40% → ลด threshold เป็น X", link ไปที่ ENV documentation; ไม่ใช่ปุ่มอัตโนมัติ — Admin ปรับ ENV เอง
  • T040 [US4] ทดสอบ delete permission: STAFF role พยายาม DELETE → 403; SYSTEM_ADMIN DELETE → 200; audit_logs มี record ใหม่ action='AI_AUDIT_LOG_DELETED'

Checkpoint: Admin dashboard แสดง metrics; delete audit log บันทึกใน audit_logs; threshold guidance แสดงถูกต้อง


Phase 7: Polish & Cross-Cutting Concerns

Purpose: i18n, error messages, documentation

  • T041 [P] เพิ่ม i18n keys สำหรับ AI module ใน public/locales/th/ai.json และ public/locales/en/ai.json — รวม: ai suggestion labels, migration queue statuses, error messages (ไม่ hardcode text ใน component)
  • T042 [P] เพิ่ม i18n key สำหรับ fallback messages: ai.service_unavailable, ai.new_doc_not_indexed, ai.no_results_found
  • T043 ตรวจสอบ backend-tsc.txt และ frontend-tsc.txt — ต้องไม่มี TypeScript error จาก files ที่แก้
  • T044 รัน grep -r "console.log" backend/src/modules/ai --include="*.ts" → ต้องไม่มีผล (ใช้ Logger แทน)
  • T045 รัน quickstart.md Verification Scenarios ทั้ง 6 scenarios และ document ผล
  • T046 อัปเดต specs/03-Data-and-Storage/deltas/14-add-migration-review-queue.sql ให้สมบูรณ์และ run ใน dev DB
  • T047 [P] อัปเดต CHANGELOG.md — เพิ่ม entry สำหรับ ADR-023A implementation

Dependencies & Execution Order

Phase Dependencies

  • Phase 1 (Setup): ไม่มี dependency — เริ่มได้ทันที; MUST เสร็จก่อนทุก Phase
  • Phase 2 (Foundation): ขึ้นอยู่กับ Phase 1 — BLOCKS ทุก User Story
  • Phase 3 (US1 - P1): ขึ้นอยู่กับ Phase 2 — MVP สำคัญที่สุด
  • Phase 4 (US2 - P2): ขึ้นอยู่กับ Phase 2; ใช้ EmbeddingService และ QdrantService จาก Phase 2
  • Phase 5 (US3 - P3): ขึ้นอยู่กับ Phase 2; อาจรันพร้อม Phase 4 ได้ (คนละ service)
  • Phase 6 (US4 - P4): ขึ้นอยู่กับ Phase 3 (ต้องมี ai_audit_logs data)
  • Phase 7 (Polish): ขึ้นอยู่กับทุก Phase ก่อนหน้า

User Story Dependencies (Internal)

  • US1: T013 → T014 → T015, T016 (parallel); T019 ต้องหลัง T014
  • US2: T021 → T022; T023 → T024 → T025; T026, T027 parallel กับ T025
  • US3: T029 → T030 → T031; T032, T033 parallel กับ T031
  • US4: T036, T037 parallel; T038, T039 parallel กับ T036

Parallel Opportunities per Phase

Phase 1: T002 ‖ T003 ‖ T004 (ทำพร้อมกันได้) Phase 2: T008 ‖ T009 ‖ T010 ‖ T012 (ทำพร้อมกันได้หลัง T006, T007, T011) Phase 3: T017 ‖ T018 พร้อมกัน; T019 ต้องหลัง T014 Phase 4: T021 ‖ T023 พร้อมกัน (คนละ service); T026 ‖ T027 พร้อมกัน Phase 5: T032 ‖ T033 ‖ T034 พร้อมกัน (frontend tasks)


Implementation Strategy

MVP Scope (Phase 1 + 2 + 3 เท่านั้น)

  1. Phase 1: ลบ Typhoon, ตั้ง 2-queue, สร้าง delta → Tier 1 Critical
  2. Phase 2: Foundation AI infrastructure → Core engine ready
  3. Phase 3: US1 - Document Classification → User value delivered
  4. STOP และ VALIDATE: ทดสอบ AI Suggestion flow end-to-end
  5. Deploy MVP ถ้าพร้อม

Incremental Delivery

  • Phase 3 done → MVP: Classification on Upload
  • Phase 4 done → RAG Q&A
  • Phase 5 done → Migration Pipeline (Pre-launch)
  • Phase 6 done → Admin Monitoring
  • Phase 7 done → Complete

Metrics

  • Total Tasks: 47 tasks (T001T047)
  • Phase 1 (Setup): 5 tasks
  • Phase 2 (Foundation): 7 tasks
  • Phase 3 (US1 P1): 8 tasks
  • Phase 4 (US2 P2): 8 tasks
  • Phase 5 (US3 P3): 7 tasks
  • Phase 6 (US4 P4): 5 tasks
  • Phase 7 (Polish): 7 tasks
  • Parallel [P] tasks: 22 tasks (47%)
  • MVP Scope: 20 tasks (Phase 1+2+3)