Files
lcbp3/memory/agent-memory.md
T
admin 548dba6476
CI / CD Pipeline / build (push) Successful in 6m5s
CI / CD Pipeline / deploy (push) Successful in 6m30s
690605:1247 ADR-034-134 #11 fix Step 2
2026-06-05 12:47:38 +07:00

64 KiB

🧠 Agent Long-term Project Memory

Project: NAP-DMS (LCBP3) — Laem Chabang Port Phase 3 Document Management System Version: 1.9.9 (Last Synced: 2026-06-03) Stack: NestJS 11 + Next.js 16 + TypeScript + MariaDB 11.8 + Redis + BullMQ + Elasticsearch + Ollama (on-prem AI)

Important

Project memory นี้ต้องใช้งานภายใต้ AGENTS.md เสมอ

  • ให้ใช้ AGENTS.md เป็นกฎหลักก่อน memory ทุกครั้ง
  • ถ้า memory เก่าหรือ session note ขัดกับ AGENTS.md ให้ยึด AGENTS.md
  • งาน schema ต้องทำตาม ADR-009 ผ่าน SQL/delta เท่านั้น
  • งาน UUID/Public API ต้องทำตาม ADR-019 โดยใช้ publicId และห้าม parseInt() บน UUID
  • งาน n8n / AI migration ต้องอยู่ในขอบเขต ADR-023A และ mutation ต้องมี Idempotency-Key

🧭 1. กฎการรันคำสั่งและการทำงานบนระบบ (OS Rules & Sandbox Constraints)

Important

ระบบรันอยู่บน Windows OS

  • ห้ามใช้คำสั่ง bash หรือคำสั่งของ Linux โดยเด็ดขาด
  • คำสั่งทุกประเภทที่จะส่งให้ผู้ใช้รันหรือรันผ่าน Terminal ต้องเป็น PowerShell หรือ CMD เท่านั้น
  • ห้ามใช้คำสั่ง cd ในการสลับ Directory ให้ระบุพารามิเตอร์ Cwd ใน Tool ตรง ๆ

🔴 2. กฎเหล็กระดับ Tier 1 (CI Blocker - ห้ามละเมิดเด็ดขาด)

🆔 2.1 กลยุทธ์ UUID (ADR-019)

  • คีย์หลักในฐานข้อมูล (Internal PK) เป็น INT AUTO_INCREMENT ส่วนคีย์สาธารณะใน API/URL (Public API) จะใช้ UUIDv7 เท่านั้น
  • ฟิลด์คีย์สาธารณะชื่อ publicId: string จะถูกส่งออกไปใน API โดยตรง
  • ห้ามใช้ parseInt(), Number(), หรือเครื่องหมายบวก + บน UUID (เช่น parseInt(projectId) จะคำนวณผิดพลาดทันที)
  • ห้ามใช้ fallback ในลักษณะ id ?? '' ในฝั่ง Frontend ให้ใช้ publicId เท่านั้น

🛡️ 2.2 สิทธิ์การใช้งานและความปลอดภัย (RBAC & Auth)

  • API ทุกตัวที่เป็นการปรับปรุงข้อมูล (Mutation: POST/PUT/PATCH) ต้องใช้ CASL Guard ตรวจสอบสิทธิ์ 4 ระดับ (RBAC Matrix) เสมอ
  • API สำหรับการแก้ไขข้อมูล (POST/PUT/PATCH) ต้องตรวจสอบและยืนยัน Idempotency-Key ใน HTTP Header ทุกครั้ง
  • การเข้ารหัสรหัสผ่านใช้ bcrypt ที่ระดับ 12 salt rounds เสมอ

💾 2.3 การปรับแต่งและเปลี่ยนแปลง Schema (ADR-009)

  • ห้ามใช้ TypeORM migrations ในการอัปเดต Schema บนสภาพแวดล้อม Production
  • ให้ใช้วิธี แก้ไขไฟล์ SQL โครงสร้างหลักตรง ๆ ใน specs/03-Data-and-Storage/lcbp3-v1.9.0-schema-02-tables.sql หรือเพิ่มการทำงานในโฟลเดอร์ deltas/*.sql เท่านั้น

🏎️ 2.4 ป้องกัน Race Conditions ในเลขเอกสาร (ADR-002)

  • การจัดสรรและจองเลขที่เอกสารใหม่ (Document Numbering) ต้องใช้ Redis Redlock ควบคู่กับ TypeORM @VersionColumn เสมอ ห้ามเขียนตัวนับ (Counter) ในฝั่ง Application เดี่ยว ๆ

🤖 2.5 ขอบเขตและการแยกส่วน AI (ADR-023/023A)

  • Ollama (AI Inference) ต้องทำงานบน Admin Desktop เท่านั้น ห้ามรันบน Server หรือ Docker ใน Production
  • AI ห้ามเชื่อมต่อและเข้าถึง Database หรือ Storage โดยตรง (ต้องผ่าน DMS API เท่านั้น)
  • โมเดลที่ใช้: typhoon2.5-np-dms:latest (Main LLM, ADR-034) + typhoon-np-dms-ocr:latest (OCR, keep_alive:0) + nomic-embed-text (Embeddings)
  • การทำงานแบบ Background Job หรือ Inference ที่ใช้เวลานานต้องสั่งงานผ่าน BullMQ (คิว ai-realtime และ ai-batch)
  • ข้อมูลผลลัพธ์จาก AI ทั้งหมดต้องผ่านการตรวจสอบความถูกต้องโดยมนุษย์ (Human-in-the-loop) เสมอ

🏷️ 3. Domain Terminology (คำศัพท์เฉพาะของระบบ DMS)

ห้ามใช้คำศัพท์ทั่วไป ให้ใช้คำศัพท์ตามสารบัญหลักของโปรเจกต์เสมอ:

คำศัพท์ที่ถูกต้อง ( Use) คำศัพท์ที่ห้ามใช้ ( Don't Use) คำอธิบายภาษาไทย
Correspondence Letter, Communication จดหมาย/เอกสารติดต่อสื่อสาร (ครอบคลุมทุกประเภท)
RFA Approval Request เอกสารขออนุมัติ (Request for Approval)
Transmittal Delivery Note, Cover Letter เอกสารนำส่งแบบและเอกสาร
Circulation Distribution, Routing ใบเวียนเอกสารภายในหน่วยงาน
Shop Drawing Construction Drawing แบบก่อสร้างจริง
Contract Drawing Design Drawing, Blueprint แบบคู่สัญญา
Workflow Engine Approval Flow, Process Engine ระบบควบคุมและเปลี่ยนสถานะเอกสาร
Document Numbering Document ID, Auto Number ระบบออกเลขที่เอกสารอัตโนมัติ

⌨️ 4. Known Commands (PowerShell — Windows Only)

Note

ห้ามใช้ bash/Linux commands ทุกอย่างต้องเป็น PowerShell หรือ CMD เท่านั้น

Dev Servers

# Backend (NestJS) — port 3001
npm run start:dev                        # รันจาก e:\np-dms\lcbp3\backend

# Frontend (Next.js) — port 3000
npm run dev                              # รันจาก e:\np-dms\lcbp3\frontend

Build & Type Check

# Backend
npm run build                            # tsc compile
npm run lint                             # ESLint

# Frontend
npm run build                            # Next.js build
npx tsc --noEmit                         # Type check only

Tests

# Backend Unit Tests
npm run test                             # Jest all
npm run test -- --testPathPattern=<name> # เฉพาะ file
npm run test:cov                         # Coverage report

# Frontend Unit Tests
npm run test                             # Vitest

# E2E
npx playwright test                      # รันจาก e:\np-dms\lcbp3\frontend

Database & Services

# Docker (รันจาก root หรือ backend)
docker compose up -d                     # Start all services
docker compose logs -f backend           # Tail backend logs
docker compose ps                        # Check status

🏛️ 5. Current Decisions (Locked)

การตัดสินใจเหล่านี้ ไม่สามารถเปลี่ยนแปลงได้ โดยไม่ได้รับ Explicit Approval

ID Decision ADR
D1 n8n = Migration Phase orchestrator เท่านั้น — ห้ามทำ New Correspondence pipeline ผ่าน n8n ADR-023A
D2 New Correspondence → BullMQ ai-realtime queue โดยตรง (ไม่ผ่าน n8n) ADR-023A
D3 n8n ต้อง call POST /api/ai/jobs (DMS Backend) เท่านั้น — ห้าม call Ollama/Qdrant โดยตรง ADR-023A
D4 Excel metadata ส่งไปพร้อม AI job เป็น context (docNumber, title, sender ฯลฯ) Session 2
D5 Tag suggestion ใช้ทาง C: แนะนำ existing tags + สร้างใหม่ได้ถ้าไม่มี (isNew: true flag) Session 2
D6 Editable Review Form: AI pre-fill → user approve/edit → submit (human-in-the-loop ทุกครั้ง) ADR-023
D7 UUID Strategy: publicId (UUIDv7) เท่านั้นสำหรับ Public API — INT PK ต้อง @Exclude() ADR-019
D8 Schema changes: แก้ SQL โดยตรง + เพิ่ม deltas/*.sql — ห้ามใช้ TypeORM migration files ADR-009
D9 Qdrant search ต้องส่ง projectPublicId เป็น mandatory parameter ทุกครั้ง (compile-time) ADR-023A
D10 AI model stack: typhoon2.5-np-dms:latest (Main LLM) + typhoon-np-dms-ocr:latest (OCR, keep_alive:0) + nomic-embed-text (Embeddings) on Admin Desktop (ADR-034, supersedes ADR-023A §2.1) ADR-034

6. Do / Don't Quick Reference

Do Don't
ใช้ publicId (UUID string) ใน API/URL parseInt() / Number() บน UUID
ใช้ RequestWithUser ใน NestJS controller req: any ใน controller
ส่ง notification/email ผ่าน BullMQ ส่ง email แบบ inline ใน service
เขียน schema changes ใน deltas/*.sql สร้าง TypeORM migration files
ใช้ NestJS Logger แทน console.log console.log ใน committed code
ตรวจสอบ table/column ใน schema-02-tables.sql ก่อนเขียน query คาดเดาชื่อ column โดยไม่ตรวจสอบ schema
ใช้ CASL Guard กับทุก mutation endpoint สร้าง API ที่ไม่มี auth guard
ผ่าน StorageService ทุกครั้งที่จัดการไฟล์ ทำ file operation โดยตรงโดยไม่ผ่าน StorageService
ใช้คำสั่ง PowerShell/CMD บน Windows ใช้ bash/Linux commands บน Windows
Human-in-the-loop validate ก่อน apply AI output ใช้ AI output โดยตรงโดยไม่ผ่าน human review
เขียน comment ภาษาไทย, code identifier ภาษาอังกฤษ คำ comment ภาษาอังกฤษ หรือ identifier ภาษาไทย
ใส่ file header // File: path/filename ทุกไฟล์ TypeScript ไฟล์ที่ไม่มี file header

🌐 7. Environment & Services

Service Local URL / Port Production Notes
Backend API http://localhost:3001 QNAP 192.168.10.8 NestJS — /api prefix
Frontend http://localhost:3000 QNAP 192.168.10.8 Next.js
MariaDB localhost:3307 QNAP internal DB: lcbp3, root via docker
Redis localhost:6379 QNAP internal BullMQ + session store
Ollama http://192.168.10.100:11434 Admin Desktop (Desk-5439) typhoon2.5-np-dms:latest (main) + typhoon-np-dms-ocr:latest (OCR) + nomic-embed-text
Qdrant http://localhost:6333 Admin Desktop (Desk-5439) Vector DB — requires projectPublicId
OCR Sidecar http://192.168.10.100:8765 Admin Desktop (Desk-5439) Dynamic (Tesseract tha+eng / Typhoon OCR-3B)
Gitea https://git.np-dms.work QNAP 192.168.10.8 Source + CI/CD
Gitea Runner ASUSTOR 192.168.10.9 CI runner

Key Environment Variables (ตรวจสอบใน docker-compose.yml)

DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME
REDIS_HOST, REDIS_PORT
JWT_SECRET, JWT_EXPIRES_IN
OLLAMA_BASE_URL (ชี้ไป Admin Desktop)
QDRANT_URL

🚀 8. Recent Rollouts

วันที่ Version รายการ สถานะ
2026-05-23 v1.9.6 Specs reorganization (100/200/300-* folders), AGENTS.md v1.9.6 update Complete
2026-05-23 v1.9.6 N8N Workflow v2 (n8n.workflow.v2.json) — ADR-023A compliant, ลบ Ollama direct Pending import to n8n UI
2026-05-24 v1.9.6 AGENTS.md Project Memory Override rule (Windsurf / Antigravity / Codex) Complete
2026-05-25 v1.9.6 Migration Queue attachment UUID fix — DTO + Service + n8n.workflow.v2.json (Session 3) Complete (tsc verified)
2026-05-25 v1.9.6 Migration error normalization + job_id logging — workflow + backend + SQL/delta (Session 4) Complete
2026-05-25 v1.9.6 PaddleOCR Sidecar บน Desk-5439 — FastAPI /ocr+/normalize, CIFS mount, path remapping (Session 7) Running
2026-05-27 v1.9.7 Context-Aware Prompt Templates & DB CC Whitespace Cleanup (ADR-030) (Session 9) Complete (v1.9.7 main)
2026-05-30 v1.9.7 OCR Engine Migration — PaddleOCR → Tesseract (Session 8) Complete (pending rebuild)

🔄 9. สถานะและประวัติการทำงาน (Latest Session Progress)

Session 1 — 2026-05-23 (Specs Reorganization)

  • Reorganize โครงสร้างโฟลเดอร์ specs/ สำเร็จ (100-Infrastructures, 200-fullstacks, 300-others)
  • อัปเดตกฎ AGENTS.md และ GEMINI.md ให้ตรงกับมาตรฐานใหม่
  • ริเริ่มระบบ memory/agent-memory.md

Session 2 — 2026-05-23 (N8N Workflow Refactor)

Decisions ที่ Lock แล้ว (จาก QuizMe Session)

# Decision
S1 n8n = Migration only — New Correspondence ใช้ Backend pipeline (BullMQ)
S2 New Correspondence → BullMQ ai-realtime (ไม่ผ่าน n8n)
S3 n8n call POST /api/ai/jobs (Backend) แทน Ollama direct (ADR-023A)
PA Excel metadata (docNumber, title, sender, etc.) ส่งไปพร้อม AI job เป็น context
PB-Tags Tag suggestion ทาง C — แนะนำ existing + สร้าง tag ใหม่ได้ถ้าไม่มี (isNew flag)
PB-UX Editable form pre-filled ด้วย AI suggestions — user approve/edit ก่อน submit

Endpoint ที่ Verified แล้ว

  • POST /api/ai/jobs (type: migrate-document) — พร้อมใช้งานแล้ว (verified 2026-05-23)
  • GET /api/ai/jobs/:jobId — polling endpoint พร้อม
  • POST /api/storage/upload — two-phase upload พร้อม

ไฟล์ที่สร้าง/แก้ไข

ไฟล์ การเปลี่ยนแปลง
specs/03-Data-and-Storage/CONTEXT-N8N-Refactor.md สร้างใหม่ — context doc สำหรับ implement
specs/03-Data-and-Storage/n8n.workflow.v2.json สร้างใหม่ — ADR-023A compliant workflow
specs/03-Data-and-Storage/03-05-n8n-migration-setup-guide.md อัพเดต v1.9.0 — ลบ Ollama direct, เพิ่มขั้นตอน update token
specs/03-Data-and-Storage/03-06-migration-business-scope.md อัพเดต — Gate #1 blocker → Verified 2026-05-23

สาระสำคัญของ n8n.workflow.v2.json

Nodes ที่ลบ (ADR-023A violations): Ollama AI Analysis, Build AI Prompt, Extract PDF Text (Tika), Check/Update Fallback State, Import to Backend, Upsert Tags, Link Tags

Nodes ใหม่: Validate TokenUpload PDF to BackendBuild AI Job PayloadSubmit AI JobPoll AI Job Status

Flow สรุป:

Form Trigger → Set Config → Health/Token Check → Fetch Master Data
→ File Mount Check → Read Excel → Read Checkpoint
→ [Per Record: File Validate → Upload PDF → Submit AI Job → Poll → Parse/Route]
    → Auto/Flagged → migration_review_queue
    → Rejected → CSV Log
    → Error → CSV + DB Log
→ Save Checkpoint → Delay → Loop

Session 3 — 2026-05-24 (Migration Queue Attachment UUID Bug Fix)

ปัญหาที่พบ (Root Cause)

ไฟล์ n8n.workflow.v2.json (โหนด Insert Review Queue) ส่งค่า tempAttachmentId โดยใช้ {{parseInt($json.attachmentId)}} ซึ่งพยายามแปลง UUID string เป็นตัวเลข ผลลัพธ์คือค่า NaN หรือตัวเลขที่ผิดพลาด (เช่น "0195..."19) ทำให้คอลัมน์ temp_attachment_id ใน migration_review_queue เป็น NULL เสมอ — ละเมิด ADR-019 Tier 1 Blocker

การแก้ไข (Fix)

ไฟล์ การเปลี่ยนแปลง
backend/src/modules/ai/dto/migration-checkpoint.dto.ts ปรับ tempAttachmentId เป็น @IsOptional() รองรับทั้ง UUID string และ Integer PK
backend/src/modules/ai/ai-migration-checkpoint.service.ts เพิ่ม UUID→INT resolution: SELECT id FROM attachments WHERE uuid = ? LIMIT 1
specs/03-Data-and-Storage/n8n.workflow.v2.json เปลี่ยนส่ง temp_attachment_public_id (UUID string) แทน parseInt(...) ที่ผิด

Pattern ที่ตกลง (Locked)

n8n ส่ง: { tempAttachmentId: "019505a1-7c3e-7000-..." }  ← UUID string
Backend รับ: ตรวจสอบประเภท → ถ้าเป็น string → query DB → ได้ INT id จริง
DB บันทึก: migration_review_queue.temp_attachment_id = <INT>  ← ถูกต้อง

Verification

  • npx tsc --noEmit ผ่าน ไม่มี type error
  • ตรวจสอบ logic ใน Service แล้ว ไม่มีการเขียนทับ tempAttachmentId ด้วย undefined (guard check แล้ว)

Session 4 — 2026-05-25 (Migration Error Normalization ตาม AGENTS.md) ← ล่าสุด

ปัญหาที่พบ (Root Cause)

  • Log Error to CSV และ Log Error to DB ใน n8n.workflow.v2.json ส่ง error_type บางค่าไม่ตรง enum ของ migration_errors
  • ค่าที่พบจริงและต้อง normalize: AI_JOB_FAILED, PARSE_ERROR, TOKEN_EXPIRED
  • backend AiMigrationCheckpointService.logError() เดิม insert ค่า dto.errorType ตรง ๆ ทำให้เสี่ยง DB enum reject
  • ตาราง migration_errors เดิมไม่มี job_id แม้ workflow/DTO จะมี jobId อยู่แล้ว ทำให้ trace กลับไป BullMQ job ไม่ครบ

การแก้ไข (Fix)

ไฟล์ การเปลี่ยนแปลง
specs/03-Data-and-Storage/n8n.workflow.v2.json normalize error_type, document_number, error, job_id ก่อนเขียน CSV/DB
backend/src/modules/ai/ai-migration-checkpoint.service.ts map/validate errorType ซ้ำก่อน insert และเพิ่ม job_id ใน SQL insert
backend/src/modules/migration/entities/migration-error.entity.ts เพิ่ม field jobId?: string
specs/03-Data-and-Storage/lcbp3-v1.9.0-migration.sql เพิ่มคอลัมน์ job_id VARCHAR(100) NULL และ index
specs/03-Data-and-Storage/deltas/2026-05-22-drop-migration-tables.rollback.sql อัปเดต table definition ของ migration_errors ให้มี job_id
specs/03-Data-and-Storage/deltas/2026-05-24-add-migration-errors-job-id.sql เพิ่ม delta สำหรับ add job_id
specs/03-Data-and-Storage/deltas/2026-05-24-add-migration-errors-job-id.rollback.sql เพิ่ม rollback สำหรับ drop job_id
backend/src/modules/ai/ai-migration-checkpoint.service.spec.ts เพิ่ม regression tests สำหรับ error normalization + job_id

Mapping ที่ Lock แล้ว

AI_JOB_FAILED -> API_ERROR
PARSE_ERROR -> AI_PARSE_ERROR
TOKEN_EXPIRED -> API_ERROR
unsupported value -> UNKNOWN

กฎใช้งานต่อไป

  • ให้ถือ enum ของ migration_errors.error_type เป็น source of truth เสมอ
  • workflow ต้อง normalize ก่อนส่งเข้า backend และ backend ต้อง normalize ซ้ำอีกชั้น
  • ห้ามพึ่ง DB enum reject เป็น validation mechanism
  • การเพิ่มคอลัมน์ job_id ต้องทำผ่าน SQL/delta ตาม ADR-009 เท่านั้น

Verification

  • workflow normalization assertion — ผ่าน
  • pnpm --filter backend build ผ่าน
  • pnpm --filter backend test -- --runTestsByPath src/modules/ai/ai-migration-checkpoint.service.spec.ts ผ่าน
  • regression seam ที่เพิ่มยืนยัน:
    • AI_JOB_FAILED map เป็น API_ERROR
    • unsupported error type fallback เป็น UNKNOWN

Session 5 — 2026-05-25 (N8N Submit AI Job Debug + Upload Dedup)

ปัญหาที่พบ (Root Cause)

Bug 1: Submit AI Job → 400 Bad Request

  • n8n HTTP Request node typeVersion: 4.1 เมื่อ specifyBody: "json" และ jsonBody เป็น expression ที่ return object → n8n ส่ง body เป็น "[object Object]" แทน JSON string
  • แก้ด้วย JSON.stringify($json.submit_payload)

Bug 2: Submit AI Job → 403 Forbidden

  • migration_bot (user_id=5, role_id=1/Superadmin) ไม่มี ai.suggest ใน role_permissions
  • Root cause: Seed script INSERT INTO role_permissions SELECT 1, permission_id FROM permissions WHERE is_active = 1 รันก่อน ai.* permissions (id 181-186) ถูก insert เข้า permissions table
  • แก้ด้วย delta SQL grant ai.* ให้ role_id=1

Bug 3: Upload ซ้ำเมื่อ n8n retry

  • FileStorageService.upload() เดิมไม่มี dedup → ทุก retry สร้าง orphan temp attachment ใหม่
  • แก้ด้วย checksum-based dedup: query หา temp record ที่มี checksum+userId เดิมและยังไม่หมดอายุ → คืน record เดิมแทน

การแก้ไข (Fix)

ไฟล์ การเปลี่ยนแปลง
specs/03-Data-and-Storage/n8n.workflow.v2.json jsonBody เปลี่ยนเป็น JSON.stringify($json.submit_payload)
specs/03-Data-and-Storage/deltas/2026-05-25-grant-ai-permissions-to-superadmin.sql INSERT IGNORE ai.* permissions สำหรับ role_id=1 (Superadmin)
specs/03-Data-and-Storage/deltas/2026-05-25-grant-ai-permissions-to-superadmin.rollback.sql Rollback DELETE สำหรับ delta ข้างบน
backend/src/common/file-storage/file-storage.service.ts เพิ่ม checksum dedup ใน upload() ก่อน write file

กฎที่ Lock แล้ว

  • jsonBody ใน n8n HTTP Request typeVersion >= 4.1 ต้องใช้ JSON.stringify(...) เมื่อ specifyBody: "json" และค่าเป็น object
  • ทุกครั้งที่เพิ่ม permission ใหม่ใน permissions table ต้อง grant ให้ Superadmin (role_id=1) ด้วยทันที — ห้ามปล่อยให้ขาดหาย
  • FileStorageService.upload() เป็น idempotent ผ่าน SHA-256 checksum + userId + expiresAt

Verification ที่ยังต้องทำ

  • รัน delta SQL ใน MariaDB (ถ้ายังไม่รัน): 2026-05-25-grant-ai-permissions-to-superadmin.sql
  • Import n8n.workflow.v2.json ใหม่เข้า n8n UI
  • pnpm --filter backend test -- file-storage — ยืนยัน checksum dedup

Session 7 — 2026-05-25 (PaddleOCR Sidecar Setup) ← ล่าสุด

สิ่งที่ทำ

  • แก้ AggregateError (empty message) ใน ocr.service.ts — wrap เป็น Error พร้อม context ที่ชัดเจน
  • สร้าง PaddleOCR + PyThaiNLP FastAPI sidecar รันบน Desk-5439 (Windows 10/11, Docker Desktop WSL2)
  • เพิ่ม path remapping remapPath() ใน OcrService — แปลง /app/uploads/.../mnt/uploads/...

ไฟล์ที่สร้าง/แก้ไข

ไฟล์ การเปลี่ยนแปลง
specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/Dockerfile สร้างใหม่ — Python 3.10 slim, ลบ pre-download step (segfault)
specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/app.py สร้างใหม่ — FastAPI: /health, /ocr (PaddleOCR), /normalize (PyThaiNLP)
specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/requirements.txt สร้างใหม่ — numpy<2.0 ต้องอยู่ก่อน paddlepaddle (ABI fix)
specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/docker-compose.yml สร้างใหม่ — CIFS volume mount + named volume สำหรับ model cache
specs/04-Infrastructure-OPS/04-00-docker-compose/QNAP/app/docker-compose-app.yml เพิ่ม OCR_API_URL, OCR_CHAR_THRESHOLD, OCR_SIDECAR_UPLOAD_BASE
specs/04-Infrastructure-OPS/04-00-docker-compose/QNAP/app/.env.example เพิ่ม OCR_API_URL, OCR_CHAR_THRESHOLD, OCR_SIDECAR_UPLOAD_BASE
backend/src/modules/ai/services/ocr.service.ts เพิ่ม remapPath(), AggregateError fix

Known Issues / Fixes

  • numpy<2.0 ต้องอยู่ก่อน paddlepaddle — ABI mismatch กับ cv2 (numpy 2.x)
  • Docker Desktop WSL2 ไม่รองรับ bind mount จาก network drive (Z:) → ใช้ CIFS volume แทน
  • Pre-download model ใน Dockerfile ทำให้ segfault (exit 139) → ลบออก download ตอน runtime
  • OLLAMA_RAG_MODEL → เปลี่ยนเป็น OLLAMA_MODEL_MAIN=gemma4:e2b ตาม ADR-023A

CIFS Volume Config

volumes:
  qnap_uploads:
    driver: local
    driver_opts:
      type: cifs
      o: 'username=${QNAP_USER},password=${QNAP_PASS},vers=3.0,uid=0,gid=0'
      device: '//192.168.10.8/np-dms-as/data/uploads'

Path Remapping

backend: /app/uploads/temp/xxx.pdf → sidecar: /mnt/uploads/temp/xxx.pdf
OCR_SIDECAR_UPLOAD_BASE=/mnt/uploads (env var)

Verification

  • curl http://localhost:8765/health{"status":"ok","engine":"paddleocr"}
  • POST /ocr ทดสอบกับไฟล์จริงใน /mnt/uploads/temp/ → ได้ text กลับ
  • 78 test suites, 672 tests ผ่านทั้งหมด

Session 8 — 2026-05-30 (OCR Engine Migration — PaddleOCR → Tesseract)

ปัญหาที่พบ (Root Cause)

Bug 1: PaddleOCR SIGILL (Illegal Instruction)

  • PaddleOCR 2.6.2 ต้องการ AVX instruction set ซึ่ง CPU บน Desk-5439 ไม่รองรับ
  • ลองลดรุ่นเป็น 2.5.2 → ยังมี dependency conflict กับ paddleocr 2.7.3
  • ลองใช้ paddlepaddle-cpu → ยังคงมีปัญหา dependency

Bug 2: OCR ภาษาไทยผิด

  • PaddleOCR ตั้งค่า lang="en" ทำให้ข้อความภาษาไทยถูกแปลงเป็นอักษรละตินผิดๆ
  • เช่น: "10 กุมภาพันธ์ 2568" → "10 qunnwus 2568"

การแก้ไข (Fix)

เปลี่ยนจาก PaddleOCR เป็น Tesseract OCR เพื่อ:

  1. แก้ปัญหา SIGILL บน CPU เก่าที่ไม่รองรับ AVX
  2. รองรับภาษาไทยด้วย tha+eng language code
ไฟล์ การเปลี่ยนแปลง
specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/requirements.txt ลบ paddlepaddle/paddleocr, เพิ่ม pytesseract, Pillow
specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/app.py เปลี่ยนใช้ pytesseract, OCR_LANG เป็น tha+eng, ลบ PaddleOCR initialization
specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/Dockerfile ติดตั้ง tesseract-ocr, tesseract-ocr-tha, tesseract-ocr-eng
specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/docker-compose.yml OCR_LANG เป็น tha+eng, ลบ paddleocr_models volume
backend/src/modules/ai/services/ocr.service.ts เปลี่ยน comment/error message จาก PaddleOCR เป็น Tesseract
frontend/components/admin/ai/OcrSandboxPromptManager.tsx เปลี่ยน Badge text จาก PaddleOCR เป็น Tesseract

กฎที่ Lock แล้ว

  • Tesseract OCR ไม่ต้องการ AVX instruction set → ทำงานได้บน CPU ทุกรุ่น
  • Tesseract รองรับภาษาไทยด้วย tha+eng language code
  • API contract ยังเหมือนเดิม (POST /ocr คืน { text, ocrUsed }) → backend/frontend ไม่ต้องเปลี่ยน logic

Verification ที่ต้องทำ

  • Rebuild container บน Desk-5439: docker compose down && docker compose up -d --build
  • ทดสอบ OCR ภาษาไทย: "10 กุมภาพันธ์ 2568" ควรออกมาถูกต้อง

Session 9 — 2026-05-26 (System Memories Consolidation)

QNAP SSH Key Authentication & CI/CD Deployment

Infrastructure:

  • QNAP 192.168.10.8 — target deploy server (runs Gitea + app containers)
  • ASUSTOR 192.168.10.9 — Gitea runner

SSH Key Setup (Persistent):

  • Private key: /etc/config/ssh/gitea-runner
  • Public key: /etc/config/ssh/gitea-runner.pub
  • Fingerprint: SHA256:OhPbRe9vi4aWTyzBqCQ6T3MLl+JK9lFtH5bPrx+ICPw
  • Authorized keys: /etc/config/ssh/authorized_keys (symlinked from /root/.ssh/)
  • QNAP SSH config: /etc/config/ssh/sshd_config (persistent — ใช้อันนี้เท่านั้น ไม่ใช้ /etc/ssh/sshd_config)

Critical Fix: AuthorizedKeysFile

AuthorizedKeysFile /etc/config/ssh/authorized_keys

ต้องใช้ absolute path — ถ้าใช้ .ssh/authorized_keys จะ resolve ไปที่ /share/homes/admin/.ssh/ ซึ่งผิด (admin home = /share/homes/admin แต่ symlink อยู่ที่ /root/.ssh)

Reload QNAP SSH daemon

kill -HUP $(ps | grep "/usr/sbin/sshd -f /etc/config" | grep -v grep | awk '{print $1}')

ไม่มี pgrep และไม่มี systemctl บน QNAP

Gitea Secrets:

Secret Value
HOST 192.168.10.8
PORT 22
USERNAME admin
SSH_KEY private key content from /etc/config/ssh/gitea-runner

deploy.sh Fix:

# scripts/deploy.sh line 10 — correct path:
COMPOSE_FILE="$SOURCE_DIR/specs/04-Infrastructure-OPS/04-00-docker-compose/QNAP/app/docker-compose-app.yml"

ไม่ใช่ ...04-00-docker-compose/docker-compose-app.yml (ขาด QNAP/app/)

Root Causes (ทั้งหมด):

  1. authorized_keys เสียหาย — 2 keys บรรทัดเดียว
  2. SSH key pair หายหลัง reboot — QNAP / เป็น RAM, ต้องเก็บใน /etc/config/
  3. AuthorizedKeysFile ใช้ relative path — resolve ผิด directory
  4. HOST secret ชี้ไปผิด server (Go SSH) — แก้เป็น 192.168.10.8:22
  5. deploy.sh COMPOSE_FILE path ผิด — ขาด QNAP/app/ subdirectory

Backend TransformInterceptor Double Registration Bug

Issue: API responses were double-wrapped { data: { data: actualData } } causing frontend detail pages to fail loading data.

Root Cause: TransformInterceptor registered in TWO places:

  1. backend/src/main.ts: app.useGlobalInterceptors(new TransformInterceptor())
  2. backend/src/common/common.module.ts: { provide: APP_INTERCEPTOR, useClass: TransformInterceptor }

Fix: Removed duplicate registration from main.ts (keep only APP_INTERCEPTOR in CommonModule).

Why list page still worked: Paginated responses were re-detected as paginated by second interceptor, preventing double-nesting. Non-paginated (detail) endpoints were affected.

Verification: curl http://localhost:3001/api/correspondences/{uuid} now returns single-wrapped { data: {...} } instead of double-wrapped.

Pattern to Avoid: Never register global interceptors/filters in both main.ts AND via APP_INTERCEPTOR/APP_FILTER providers.

ADR-021 Integration: Transmittals & Circulation

Summary: Successfully integrated ADR-021 (Integrated Workflow Context & Step-specific Attachments) into Transmittals and Circulation modules. All backend services, frontend pages, and tests are wired to the Unified Workflow Engine.

Backend Changes (B1-B9):

  • WorkflowEngineService: Added getInstanceByEntity(entityType, entityId) for polymorphic workflow instance lookup
  • TransmittalService:
    • Expose workflowInstanceId, workflowState, availableActions in findOneByUuid()
    • Added purpose filter to findAll()
    • Added submit() with EC-RFA-004 validation (prevents submission if any item correspondence is DRAFT)
    • Starts workflow instance TRANSMITTAL_FLOW_V1 and updates CorrespondenceRevision status
  • TransmittalController: Added POST /:uuid/submit endpoint with RBAC and Audit
  • TransmittalModule: Imported WorkflowEngineModule and CorrespondenceRevision
  • CirculationService:
    • Expose workflow fields in findOneByUuid()
    • Added reassignRouting() (EC-CIRC-001) for PENDING routing reassignment
    • Added forceClose() (EC-CIRC-002) with transactional rollback and reason validation
  • CirculationController: Added PATCH /:uuid/routing/:routingId/reassign and POST /:uuid/force-close
  • Circulation Entity: Added deadlineDate column for EC-CIRC-003 Overdue badge
  • Schema Delta: 05-add-circulation-deadline.sql per ADR-009 (no migrations)

Frontend Changes (F1-F7):

  • Types: Extended Transmittal and Circulation interfaces with workflow fields; added deadlineDate to Circulation
  • Hooks: Created useTransmittal() and extended useCirculation() hooks with TanStack Query
  • Detail Pages:
    • Both wired with IntegratedBanner and WorkflowLifecycle using live workflow data
    • Circulation page includes EC-CIRC-003 Overdue badge logic (isOverdue())
  • List Page: Added purpose filter dropdown to transmittals/page.tsx

Tests (T1-T2): 19/19 Passing

  • TransmittalService: 7 tests covering EC-RFA-004 validation, workflow instance creation, and error cases
  • CirculationService: 12 tests covering EC-CIRC-001 (reassign), EC-CIRC-002 (forceClose), EC-CIRC-003 (deadlineDate exposure)

Key Technical Decisions:

  • Followed ADR-019 UUID handling (no parseInt, use string UUIDs)
  • Used ADR-009 direct schema edits (no TypeORM migrations)
  • Enforced RBAC with CASL guards and Audit decorators
  • Implemented transactional force-close with proper rollback
  • Maintained existing patterns for error handling and service architecture

Remaining Work:

  • I1: i18n keys for new workflow actions (low priority)

Correspondence Detail Display Fixes

Issue: /correspondences/[uuid] detail display inconsistency

Fix: Made backend findOneByUuid query deterministic with explicit relation joins and revision ordering (rev.revisionNumber DESC, rev.createdAt DESC), and normalized recipient_type values in frontend detail page before TO/CC filtering to handle whitespace variants per schema (e.g., 'CC ').

Files Modified:

  • backend/src/modules/correspondence/correspondence.service.ts
  • frontend/components/correspondences/detail.tsx

Correspondence Create Permission Bypass

Issue: Users without primaryOrganizationId could not create documents even with system.manage_all permission

Fix: In backend CorrespondenceService.create flow, users without primaryOrganizationId can still create when they have system.manage_all and provide originatorId. Validation now resolves originator organization under that permission instead of immediately throwing 'User must belong to an organization to create documents'. Added regression test in correspondence.service.spec.ts.

Extension: Applied same pattern to RFA, Transmittal, and Circulation create endpoints — they now accept optional originatorId and allow creation for users with system.manage_all even when primaryOrganizationId is null. Added permission-gated impersonation checks in their services to prevent unauthorized cross-organization creation.

Playwright E2E Testing Setup

Test Stack:

  • Backend: Jest (Unit + Integration + E2E)
  • Frontend: Vitest (Unit) + Playwright (E2E)

MCP Server Setup (Windsurf):

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": ["-y", "@playwright/mcp@latest"]
    }
  }
}

Windsurf Cascade Tools:

  • browser_navigate - เปิด URL
  • browser_click - คลิก element
  • browser_type - พิมพ์ข้อความ
  • browser_take_screenshot - ถ่าย screenshot
  • browser_evaluate - รัน JavaScript

Run E2E Tests:

cd frontend
npx playwright test                    # Run all
npx playwright test --ui               # Debug mode
npx playwright test --headed           # See browser
npx playwright show-report             # Generate report

E2E Script Location: frontend/e2e/workflow-adr021.spec.ts

Tag Creation and Contract UUID Fixes

Issue 1: /admin/doc-control/reference/tags needed a list-level Project dropdown filter and Tag creation could fail due to TypeORM Tag entity column-name mismatches.

Fix: Added selectedProjectId filter in frontend tags page and mapped backend Tag entity fields to schema names (project_id, tag_name, color_code, created_by, created_at, updated_at, deleted_at).

Issue 2: Frontend contract detail page typecheck failure — contract.project?.id vs contract.project?.publicId

Fix: In frontend/app/(admin)/admin/doc-control/contracts/page.tsx, handleEdit must read nested project UUID from contract.project?.id (not project?.publicId) because Contract.project is typed and returned as { id: string; projectCode; projectName }.


Session 9 — 2026-05-27 (Context-Aware Prompt Templates & Database Typo CC Cleanup) ← ล่าสุด

Summary: ดำเนินการอิมพลีเมนต์ ADR-030 (Context-Aware Prompt Templates สำหรับการสกัดข้อมูลเอกสาร) และทำการแก้ไขบัคช่องว่างประเภทผู้รับ 'CC ' ในฐานข้อมูล

Backend Changes (B1-B6):

  • AiPrompt Entity: เพิ่มการแมปคอลัมน์ contextConfig ไปยัง JSON ฟิลด์ context_config ในฐานข้อมูลเพื่อควบคุม master data resolution
  • CreateAiPromptDto / Response DTO: ปรับแต่งให้รองรับการรับและส่งออกคอลัมน์ contextConfig
  • AiPromptsService:
    • อิมพลีเมนต์เมธอด resolveContext() สำหรับการดึงข้อมูล Master Data ดำเนินการคัดกรองข้อมูลอ้างอิงโครงการ (Projects, Organizations, Disciplines, CorrespondenceTypes, Tags) สอดคล้องกับ dynamic config filter
    • ติดตั้ง Gatekeeper Rule (ตัวกรองความปลอดภัย) โยน ForbiddenException ทันทีเมื่อมีการร้องขอ override project UUID ข้ามอาณาเขตโครงการที่กำหนดใน template เพื่อป้องกัน Cross-project data leak
  • AiBatchProcessor:
    • ปรับปรุงโครงสร้าง MigrateDocumentMetadata interface, sandbox extraction, และ migration process ให้ดึงข้อมูลและแมป master data context-aware
    • สกัดและจำแนกผู้รับเอกสาร (recipients) ภายใต้โครงสร้าง JSON แบบใหม่ในรูป Object Array: recipients: Array<{ organizationPublicId: string, recipientType: 'TO' | 'CC' }> เพื่อความเสถียรและทนทานของข้อมูล
  • Unit Tests:
    • เพิ่มชุดการทดสอบ resolveContext ใน ai-prompts.service.spec.ts ครอบคลุมการจำลอง master data resolution, การโยน NotFoundException และการล็อคสิทธิ์ความปลอดภัยด้วย ForbiddenException เมื่อ override โครงการข้าม boundary
    • แก้ไข mock dependencies ของ AiPromptsService ใน ai-batch.processor.spec.ts ป้องกันปัญหา TypeError: getActive is not a function ทำให้ผ่าน unit tests 100%

Database & Schema Changes (ADR-009):

  • schema-02-tables.sql: แก้ไข line 338 ปรับปรุง ENUM('TO', 'CC ') เป็น ENUM('TO', 'CC')
  • SQL Delta: สร้าง 2026-05-27-add-context-aware-prompts-and-cleanup.sql ดำเนินการ UPDATE ข้อมูลเก่าที่เป็น 'CC ' ให้เป็น 'CC' เพื่อล้างช่องว่าง จากนั้นสั่ง ALTER TABLE ปรับปรุงฟิลด์ enum และ Seed template ภาษาไทยเวอร์ชัน 2
  • Rollback SQL: สร้างไฟล์ย้อนกลับ 2026-05-27-add-context-aware-prompts-and-cleanup.rollback.sql เรียบร้อย

Frontend Changes:

  • detail.tsx: ตรวจสอบการใช้งาน normalizeRecipientType ซึ่งครอบคลุมการล้างช่องว่างและการกรองผู้รับ TO/CC ได้อย่างทนทาน

Verification:

  • pnpm --filter backend build Compile ผ่านแบบ Strict Mode
  • unit tests AI module & backend suites — ผ่านทั้งหมด 60 suites / 521 tests

Session 10 — 2026-05-30 (OCR Sandbox Two-Step Flow) ← ล่าสุด

Summary: แยก OCR Sandbox เป็น 2 steps ตาม spec 231: Step 1 OCR-only → Step 2 AI Extraction เพื่อให้ admin ตรวจคุณภาพ OCR ก่อนทดสอบ AI prompt

Backend Changes (B1-B5):

  • AiBatchJobType: เพิ่ม sandbox-ocr-only และ sandbox-ai-extract job types
  • AiBatchProcessor:
    • เพิ่ม processSandboxOcrOnly() — รัน OCR เท่านั้น, cache OCR text ใน Redis key ai:sandbox:ocr:{idempotencyKey} (TTL 3600s)
    • เพิ่ม processSandboxAiExtract() — ดึง OCR text จาก cache, resolve prompt version (active หรือ version ที่ระบุ), replace {{ocr_text}} และ {{master_data_context}}, run LLM
  • AiPromptsService: เพิ่ม findByVersion(promptType, versionNumber) method สำหรับดึง prompt version ที่ระบุ
  • AiController:
    • เพิ่ม POST /ai/admin/sandbox/ocr — Step 1 endpoint (รับ file multipart/form-data)
    • เพิ่ม POST /ai/admin/sandbox/ai-extract — Step 2 endpoint (รับ requestPublicId + optional promptVersion)
  • AiQueueService: อัปเดต enqueueSandboxJob() รองรับ job types ใหม่และ extraPayload สำหรับส่ง promptVersion

Frontend Changes (F1-F3):

  • adminAiService: เพิ่ม submitSandboxOcr(file) และ submitSandboxAiExtract(requestPublicId, promptVersion) methods
  • OcrSandboxPromptManager.tsx:
    • เพิ่ม states: sandboxStep ('ocr'|'ai'), ocrResult (requestPublicId, ocrText, ocrUsed), selectedPromptVersion
    • เพิ่ม handlers: handleStep1Ocr() (poll OCR result), handleStep2AiExtract() (poll AI result), handleResetSandbox()
    • Refactor UI: Step 1 (upload + Run OCR button) → Step 2 (prompt version dropdown + Run AI Extraction button + Reset button)
    • แสดง OCR Raw Text card หลัง Step 1 เสร็จ (สีน้ำเงิน, badge บอก PaddleOCR/Fast Path)
    • แสดง AI Extraction result หลัง Step 2 เสร็จ (สีเขียว, badge บอก promptVersionUsed)

Schema Fix (ADR-009 + ADR-019):

  • Delta SQL: สร้าง 2026-05-30-add-ai-prompts-publicId.sql เพิ่ม publicId CHAR(36) UNIQUE column ใน ai_prompts table
  • Root Cause: Entity มี publicId field แต่ DB table ไม่มี column นี้ → QueryFailedError: "Unknown column 'AiPrompt.publicId' in 'SELECT'"
  • Fix: ใช้ CHAR(36) แทน UUID type (MariaDB compatible), generate UUID สำหรับ existing records ด้วย UUID() function

Verification:

  • Backend TypeScript: ผ่าน (npx tsc --noEmit)
  • Frontend TypeScript: ผ่าน (npx tsc --noEmit)
  • ESLint: ผ่าน (แก้ unused user parameter ใน submitSandboxAiExtract)

Data Flow:

Step 1: Upload PDF → POST /ai/admin/sandbox/ocr
  ↓
  BullMQ: sandbox-ocr-only job
  ↓
  OCR Service → Cache OCR text (ai:sandbox:ocr:{id})
  ↓
  Frontend displays OCR Raw Text

Step 2: Select prompt version → POST /ai/admin/sandbox/ai-extract
  ↓
  BullMQ: sandbox-ai-extract job
  ↓
  Retrieve OCR text from cache (ai:sandbox:ocr:{id})
  ↓
  Replace {{ocr_text}} → LLM → JSON result
  ↓
  Frontend displays AI Extraction result

Pending:

  • Run delta SQL 2026-05-30-add-ai-prompts-publicId.sql ใน production database
  • Restart backend service หลัง apply delta
  • Test 2-step flow จริงใน production environment

Session 11 — 2026-05-30 (Typhoon OCR & LLM Integration) ← ล่าสุด

Summary: ออกแบบและพัฒนาการใช้งานโมเดลภาษาไทยผสมอังกฤษ Typhoon OCR-3B ร่วมกับ Tesseract OCR แบบ Dynamic พร้อมระบบ caching 24 ชม., VRAM Monitor ป้องกัน GPU OOM และระบบ fallback 5s เมื่อโมเดลมีปัญหา และการสลับและบริหารจัดการ LLM โมเดลหลักแบบ Dynamic ในระบบ AI Model Management ของ Next.js frontend ตามข้อกำหนด ADR-032

Backend Changes (B1-B5):

  • OcrService:
    • เพิ่ม dynamic OCR engine selection (getOcrEngines(), selectOcrEngine(), getActiveEngineId()) จัดเก็บสถานะหลักใน DB system_settings (OCR_ACTIVE_ENGINE) พร้อม cache ใน Redis 30s ป้องกันคิวรีซ้ำซ้อน
    • ปรับปรุง detectAndExtract() ให้เลือกใช้ engine ที่เหมาะสม หากผู้ใช้เลือก Typhoon OCR-3B จะทำงานผ่าน processWithTyphoon() ร่วมกับ OcrCacheService (24-hour Redis caching) และ VramMonitorService (ตรวจสอบ VRAM capacity > 4GB ก่อนโหลดโมเดล)
    • พัฒนาระบบ Graceful Fallback ไปยัง Tesseract OCR ในเวลา 5 วินาทีหาก Typhoon ขัดข้องหรือ VRAM ไม่เพียงพอ โดยมีการบันทึกรายละเอียดและ error ลง ai_audit_logs
  • AiService:
    • เพิ่ม endpoints สำหรับ AI Model Management: GET /models, POST /models (Superadmin), PATCH /models/:modelId/activate และ GET /vram/status (Used/Free VRAM และ Active models บน GPU) ร่วมกับ AiSettingsService และ VramMonitorService
    • ตรวจสอบความปลอดภัย VRAM ก่อนอนุญาตให้สลับโมเดลหลัก หากเหลือพื้นที่หน่วยความจำ GPU ไม่พอ จะโยน BusinessException แจ้งเตือนภาษาไทยพร้อมบันทึกลง Audit Log และระงับการเปลี่ยนโมเดลทันที
    • แก้ไขข้อผิดพลาด build error ใน ai.service.ts โดยการนำเข้า OllamaService และ AiQdrantService ที่ขาดหายไปใน constructor

Frontend Changes (F1-F3):

  • admin-ai.service.ts: เพิ่ม interface LoadedModelInfo และ VramStatusResponse และเพิ่ม methods getVramStatus(), getAvailableModels(), setActiveModel(), และ addModel() โดยใช้ dynamic path ที่อ้างอิง UUIDv7 (modelId) และส่ง idempotency headers ตาม ADR-019/ADR-016
  • admin/ai/page.tsx: อัปเดตหน้า AI Admin page โดยเพิ่ม VRAM GPU Monitor Card แบบ realtime (ดึงและสลับรีเฟรชผ่าน React Query ทุก 15s) แสดง Free/Used VRAM และ active models บน GPU และปรับปรุง UI ส่วน AI Model Management ให้สลับโมเดลหลักผ่าน UUIDv7 และแสดง VRAM requirements ของแต่ละโมเดลอย่างสวยงามพรีเมียม

ADRs Update (ADR-023/023A):

  • อัปเดต ADR-023-unified-ai-architecture.md (v1.2) และ ADR-023A-unified-ai-architecture.md (v1.3) เพื่อรับรองสถาปัตยกรรม dynamic Thai specialized models (Typhoon OCR & LLM) ภายใต้การควบคุมของ VRAM Monitor

Verification:

  • Backend NestJS Build: Compile สำเร็จ 100% ปราศจาก Error (npm run build ใน backend)
  • Frontend Next.js Build: Compile สำเร็จ 100% ปราศจาก Error (npm run build ใน frontend)

🎯 11. แผนงานขั้นต่อไป (Next Session Focus)

N8N Migration & E2E Testing (งานหลักที่เหลือ)

  • Import n8n.workflow.v2.json เข้า n8n UI และทดสอบ End-to-End (มี fix จาก Session 3, 4, 5 แล้ว)
  • ทดสอบ End-to-End จริง — รัน n8n กับ Excel ตัวอย่าง → ตรวจสอบว่า Submit AI Job ผ่าน, migration_review_queue มีข้อมูล, migration_errors.job_id ถูกบันทึก
  • Frontend Editable Review Form (Pipeline B) — pre-fill AI suggestions + tag suggestion UI
  • Dry Run กับ Excel จริงก่อน Production Migration

งานทั่วไป

  • ดำเนินการรัน SQL delta script ใน MariaDB เมื่อขึ้นสภาพแวดล้อมจริง
  • เพิ่ม unit test สำหรับ upsertQueueRecord ใน ai-migration-checkpoint.service.spec.ts (UUID→INT path)
  • เพิ่ม unit test สำหรับ checksum dedup ใน file-storage.service.spec.ts