diff --git a/backend/src/modules/ai/services/ocr.service.ts b/backend/src/modules/ai/services/ocr.service.ts index 0984fe75..a2263377 100644 --- a/backend/src/modules/ai/services/ocr.service.ts +++ b/backend/src/modules/ai/services/ocr.service.ts @@ -3,7 +3,8 @@ // - 2026-05-15: เพิ่ม OCR auto-detection service สำหรับ ADR-023A. // - 2026-05-25: แก้ไข AggregateError (empty message) จาก axios โดย wrap เป็น Error พร้อม context ที่ชัดเจน. // - 2026-05-25: เพิ่ม path remapping (OCR_UPLOAD_BASE_PATH) เพื่อแปลง local upload path เป็น path ที่ sidecar เห็นผ่าน CIFS. -// - 2026-05-29: เพิ่ม checkHealth() เพื่อตรวจสอบสุขภาพของ PaddleOCR sidecar สำหรับ getSystemHealth() (ADR-027) +// - 2026-05-29: เพิ่ม checkHealth() เพื่อตรวจสอบสุขภาพของ OCR sidecar สำหรับ getSystemHealth() (ADR-027) +// - 2026-05-30: เปลี่ยนจาก PaddleOCR เป็น Tesseract OCR เพื่อความเข้ากันได้กับ CPU เก่า import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; @@ -20,7 +21,7 @@ export interface OcrDetectionResult { ocrUsed: boolean; } -interface PaddleOcrResponse { +interface OcrSidecarResponse { text?: string; } @@ -31,7 +32,7 @@ export interface OcrHealthResult { error?: string; } -/** บริการเลือก fast path หรือ PaddleOCR sidecar ตามจำนวนตัวอักษรที่ extract ได้ */ +/** บริการเลือก fast path หรือ OCR sidecar (Tesseract) ตามจำนวนตัวอักษรที่ extract ได้ */ @Injectable() export class OcrService { private readonly logger = new Logger(OcrService.name); @@ -64,7 +65,7 @@ export class OcrService { return localPath; } - /** ตรวจสอบสุขภาพและ latency ของ PaddleOCR sidecar ผ่าน GET /health */ + /** ตรวจสอบสุขภาพและ latency ของ OCR sidecar (Tesseract) ผ่าน GET /health */ async checkHealth(): Promise { const startTime = Date.now(); try { @@ -105,7 +106,7 @@ export class OcrService { try { const sidecarPath = this.remapPath(input.pdfPath); this.logger.debug(`OCR path remap: ${input.pdfPath} → ${sidecarPath}`); - const response = await axios.post( + const response = await axios.post( `${this.ocrApiUrl}/ocr`, { pdfPath: sidecarPath }, { timeout: 90000 } @@ -124,7 +125,7 @@ export class OcrService { ? err.message : String(err); throw new Error( - `PaddleOCR sidecar unreachable at ${this.ocrApiUrl} — ${cause}` + `OCR sidecar (Tesseract) unreachable at ${this.ocrApiUrl} — ${cause}` ); } } diff --git a/frontend/components/admin/ai/OcrSandboxPromptManager.tsx b/frontend/components/admin/ai/OcrSandboxPromptManager.tsx index 4166acf6..295fba29 100644 --- a/frontend/components/admin/ai/OcrSandboxPromptManager.tsx +++ b/frontend/components/admin/ai/OcrSandboxPromptManager.tsx @@ -508,7 +508,7 @@ export default function OcrSandboxPromptManager() { OCR Raw Text (Step 1 Result) - {ocrResult.ocrUsed ? 'PaddleOCR' : 'Fast Path (Text Layer)'} + {ocrResult.ocrUsed ? 'Tesseract' : 'Fast Path (Text Layer)'} diff --git a/memory/agent-memory.md b/memory/agent-memory.md index 1a1b3c83..afb94448 100644 --- a/memory/agent-memory.md +++ b/memory/agent-memory.md @@ -10,6 +10,7 @@ - 2026-05-25 (Session 7): PaddleOCR Sidecar setup บน Desk-5439 — สร้าง FastAPI sidecar (port 8765) รองรับ `/ocr` + `/normalize`, แก้ AggregateError ใน ocr.service.ts, เพิ่ม path remapping (`OCR_SIDECAR_UPLOAD_BASE`), CIFS volume mount จาก QNAP. - 2026-05-26: เพิ่ม system memories ที่หายไป — QNAP SSH Key Authentication, TransformInterceptor double registration, ADR-021 Transmittals/Circulation integration, Correspondence detail fixes, Playwright E2E setup, Tag/Contract UUID fixes. - 2026-05-27: Context-Aware Prompts & DB CC Typo Cleanup (ADR-030) — นำเสนอการผูก Master Data เข้ากับ Prompt Extraction, ออกแบบ JSON Context-Aware configuration, อัปเดต Entity/DTOs, ออกแบบ JSON format ผู้รับเป็น Object Array ป้องกันบัค และแก้ whitespace typo 'CC ' ในฐานข้อมูล +- 2026-05-30 (Session 8): OCR Engine Migration — เปลี่ยนจาก PaddleOCR เป็น Tesseract OCR เพื่อแก้ปัญหา SIGILL (Illegal Instruction) บน CPU เก่าที่ไม่รองรับ AVX: อัปเดต requirements.txt (ลบ paddlepaddle/paddleocr, เพิ่ม pytesseract), app.py (เปลี่ยนใช้ pytesseract, OCR_LANG=tha+eng), Dockerfile (ติดตั้ง tesseract-ocr + ภาษาไทย/อังกฤษ), docker-compose.yml (OCR_LANG=tha+eng, ลบ paddleocr_models volume), backend ocr.service.ts (เปลี่ยน comment/error message), frontend OcrSandboxPromptManager.tsx (เปลี่ยน Badge text) --> # 🧠 Agent Long-term Project Memory @@ -184,18 +185,18 @@ docker compose ps # Check status ## 🌐 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 | -| **n8n** | `http://localhost:5678` | QNAP `192.168.10.8:5678` | Migration orchestrator only | -| **Ollama** | `http://192.168.10.100:11434` | Admin Desktop (Desk-5439) | gemma4:e2b + nomic-embed-text | -| **Qdrant** | `http://localhost:6333` | Admin Desktop (Desk-5439) | Vector DB — requires projectPublicId | -| **PaddleOCR** | `http://192.168.10.100:8765` | Admin Desktop (Desk-5439) | `/ocr` + `/normalize` (FastAPI) | -| **Gitea** | `https://git.np-dms.work` | QNAP `192.168.10.8` | Source + CI/CD | -| **Gitea Runner** | ASUSTOR `192.168.10.9` | — | CI runner | +| 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 | +| **n8n** | `http://localhost:5678` | QNAP `192.168.10.8:5678` | Migration orchestrator only | +| **Ollama** | `http://192.168.10.100:11434` | Admin Desktop (Desk-5439) | gemma4:e2b + nomic-embed-text | +| **Qdrant** | `http://localhost:6333` | Admin Desktop (Desk-5439) | Vector DB — requires projectPublicId | +| **Tesseract OCR** | `http://192.168.10.100:8765` | Admin Desktop (Desk-5439) | `/ocr` + `/normalize` (FastAPI) | +| **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`) @@ -211,15 +212,16 @@ 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) | +| วันที่ | 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) | --- @@ -450,7 +452,51 @@ OCR_SIDECAR_UPLOAD_BASE=/mnt/uploads (env var) --- -### Session 8 — 2026-05-26 (System Memories Consolidation) +### 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 @@ -649,6 +695,7 @@ npx playwright show-report # Generate report **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**: @@ -662,14 +709,17 @@ npx playwright show-report # Generate report - แก้ไข 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 diff --git a/specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/Dockerfile b/specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/Dockerfile index 7254bb41..8858010a 100644 --- a/specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/Dockerfile +++ b/specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/Dockerfile @@ -4,10 +4,11 @@ # Change Log: # - 2026-05-25: Initial Dockerfile สำหรับ PaddleOCR sidecar (port 8765) # - 2026-05-30: เปลี่ยนจาก PaddleOCR เป็น Tesseract OCR เพื่อความเข้ากันได้กับ CPU เก่า +# - 2026-05-30: เพิ่ม system dependencies สำหรับ OpenCV (libsm6, libxext6, libxrender1, libfontconfig1, libx11-6) FROM python:3.10-slim -# ติดตั้ง system dependencies สำหรับ PDF processing, Tesseract OCR และภาษาไทย +# ติดตั้ง system dependencies สำหรับ PDF processing, Tesseract OCR, ภาษาไทย และ OpenCV RUN apt-get update && apt-get install -y --no-install-recommends \ libglib2.0-0 \ libgl1 \ @@ -16,6 +17,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ tesseract-ocr \ tesseract-ocr-tha \ tesseract-ocr-eng \ + libsm6 \ + libxext6 \ + libxrender1 \ + libfontconfig1 \ + libx11-6 \ && rm -rf /var/lib/apt/lists/* WORKDIR /app diff --git a/specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/app.py b/specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/app.py index d17c7ca1..3bc1e007 100644 --- a/specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/app.py +++ b/specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/app.py @@ -5,6 +5,7 @@ # - 2026-05-25: Initial FastAPI server สำหรับ PaddleOCR sidecar # - 2026-05-30: เปลี่ยน lang='en' เป็น lang='ch' (CTJK) เพื่อรองรับภาษาไทย # - 2026-05-30: เปลี่ยนจาก PaddleOCR เป็น Tesseract OCR เพื่อความเข้ากันได้กับ CPU เก่า +# - 2026-05-30: เพิ่ม OpenCV preprocessing (threshold, denoise) และ DPI 300 เพื่อเพิ่มความแม่นยำ import os import logging @@ -15,6 +16,8 @@ from typing import Optional from PIL import Image import pytesseract import io +import cv2 +import numpy as np from fastapi import FastAPI, HTTPException from pydantic import BaseModel @@ -34,6 +37,26 @@ OCR_LANG = os.getenv("OCR_LANG", "tha+eng") # Tesseract language code (tha+eng logger.info(f"Tesseract OCR Sidecar initialized (lang={OCR_LANG})") +def preprocess_image(pil_image: Image.Image) -> Image.Image: + """Preprocess image ด้วย OpenCV เพื่อเพิ่มความแม่นยำ OCR""" + # แปลง PIL Image เป็น numpy array (OpenCV format) + img_array = np.array(pil_image) + + # แปลงเป็น grayscale + gray = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY) + + # Denoise ด้วย median blur + denoised = cv2.medianBlur(gray, 3) + + # Adaptive threshold เพื่อแยก background จาก text + thresh = cv2.adaptiveThreshold( + denoised, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2 + ) + + # แปลงกลับเป็น PIL Image + return Image.fromarray(thresh) + + class OcrRequest(BaseModel): pdfPath: str maxPages: Optional[int] = None @@ -89,10 +112,14 @@ def ocr_extract(req: OcrRequest): ocr_text_parts = [] for i in pages_to_process: page = doc[i] - pix = page.get_pixmap(dpi=200) + pix = page.get_pixmap(dpi=300) # เพิ่ม DPI เป็น 300 เพื่อความชัด img_bytes = pix.tobytes("png") img = Image.open(io.BytesIO(img_bytes)) - text = pytesseract.image_to_string(img, lang=OCR_LANG) + + # Preprocess ด้วย OpenCV เพื่อเพิ่มความแม่นยำ + processed_img = preprocess_image(img) + + text = pytesseract.image_to_string(processed_img, lang=OCR_LANG) ocr_text_parts.append(text.strip()) ocr_text = "\n".join(ocr_text_parts).strip() diff --git a/specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/requirements.txt b/specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/requirements.txt index 37794f8b..a244e125 100644 --- a/specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/requirements.txt +++ b/specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/requirements.txt @@ -2,6 +2,7 @@ # Change Log: # - 2026-05-30: เปลี่ยนจาก PaddleOCR เป็น Tesseract OCR เพื่อความเข้ากันได้กับ CPU เก่า (ไม่ต้องการ AVX) # - 2026-05-30: ลบ paddlepaddle/paddleocr dependencies เนื่องจาก SIGILL บน CPU ที่ไม่รองรับ AVX +# - 2026-05-30: เพิ่ม opencv-python สำหรับ image preprocessing (threshold, denoise) เพื่อเพิ่มความแม่นยำ OCR numpy<2.0 PyMuPDF==1.24.0 @@ -12,3 +13,4 @@ python-multipart==0.0.9 pythainlp==5.0.4 httpx==0.27.0 Pillow==10.0.0 +opencv-python==4.8.1.78