Files
lcbp3/specs/200-fullstacks/238-ocr-ai-prompt-separation/plan.md
T
admin 09e304de84
CI / CD Pipeline / build (push) Successful in 7m5s
CI / CD Pipeline / deploy (push) Failing after 20m14s
690618:1444 237 #02
2026-06-18 14:44:46 +07:00

12 KiB

Implementation Plan: OCR & AI Extraction Prompt Management

Branch: [238-ocr-ai-prompt-separation] | Date: 2026-06-17 | Spec: /specs/200-fullstacks/238-ocr-ai-prompt-separation/spec.md Input: Feature specification from /specs/200-fullstacks/238-ocr-ai-prompt-separation/spec.md

Summary

แยกการจัดการ OCR system prompt และ AI Extraction prompt ให้ชัดเจนใน AI Admin Console พร้อม Full 3-Step Pipeline (OCR → AI Extract → RAG Prep) ตาม ADR-037 โดย:

  1. สร้าง prompt_type ใหม่ 'ocr_system' สำหรับเก็บ OCR system prompt (Step 1)
  2. รองรับ 'ocr_extraction' สำหรับ AI metadata extraction (Step 2)
  3. รองรับ 'rag_prep_prompt' สำหรับ semantic chunking (Step 3)
  4. แก้ไข sidecar (app.py) ให้รับ system prompt และเพิ่ม /embed endpoint
  5. สร้าง UI แยก tab พร้อม 3-Step Sandbox ที่แสดง vector preview
  6. รองรับ versioning และ optimistic locking สำหรับ concurrent edits

Technical Context

Language/Version: TypeScript 5.x (Frontend), NestJS 10.x + TypeScript 5.x (Backend), Python 3.11 (Sidecar)

Primary Dependencies:

  • Frontend: Next.js 14, React Hook Form, Zod, TanStack Query, shadcn/ui
  • Backend: NestJS, TypeORM, BullMQ, class-validator
  • Sidecar: FastAPI, typhoon_ocr (SCB10X), httpx

Storage: MariaDB (ai_prompts table — มีอยู่แล้ว), Redis (cache TTL 60s)

Testing: Jest (backend), Vitest (frontend), pytest (sidecar)

Package manager: pnpm workspace (pnpm --filter backend, pnpm --filter lcbp3-frontend) — ห้ามใช้ npm/yarn (ดู package.jsonpackageManager: pnpm@10.33.0)

Target Platform: On-premises (QNAP NAS + Admin Desktop)

Project Type: Web application (backend + frontend + sidecar)

Performance Goals:

  • Save new prompt version: <500ms p95
  • Load active prompt: <200ms p95
  • Sandbox OCR with custom prompt: ไม่ช้ากว่า prompt default เกิน 10%

Constraints:

  • Sidecar ต้องอยู่บน Admin Desktop (Desk-5439) ตาม ADR-023
  • AI prompt validation ต้องทำที่ backend (ไม่ trust frontend)
  • รองรับ optimistic locking สำหรับ concurrent edits

Scale/Scope:

  • 10-20 prompt versions ต่อ prompt_type
  • 5-10 admin users ที่อาจแก้ไขพร้อมกัน

Constitution Check

GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.

Gate Status Notes
2 projects max PASS backend, frontend
Language aligned PASS TypeScript, Python
Storage aligned PASS MariaDB (existing)
Test coverage PASS Jest/Vitest/pytest

Project Structure

Documentation (this feature)

specs/200-fullstacks/238-ocr-ai-prompt-separation/
├── plan.md              # This file
├── spec.md              # Feature specification
├── checklists/
│   └── requirements.md  # Quality checklist
├── research.md          # Phase 0 (research findings)
├── data-model.md        # Phase 1 (entity design)
├── quickstart.md        # Phase 1 (setup guide)
├── contracts/
│   └── api.yaml         # OpenAPI contracts
└── tasks.md             # Phase 2 (generated by speckit-tasks)

Source Code (repository root)

สำคัญ: โมดูลนี้ มีอยู่แล้ว ที่ backend/src/modules/ai/prompts/ (ADR-029) — งาน 238 ต้อง ขยายของเดิม ไม่สร้างไฟล์ controller/service/entity ชุดใหม่ที่ map ตาราง ai_prompts ซ้ำ

backend/
├── src/modules/ai/prompts/                  # (มีอยู่แล้ว — แก้ไขที่นี่)
│   ├── ai-prompts.controller.ts             # @Controller('ai/prompts') — เพิ่ม route ถ้าจำเป็น
│   ├── ai-prompts.service.ts                # CRUD + versioning + validation (เพิ่ม case 'ocr_system')
│   ├── ai-prompts.entity.ts                 # AiPrompt (มี @VersionColumn แล้ว)
│   ├── ai-prompts.service.spec.ts           # unit tests (เพิ่ม test 'ocr_system')
│   └── dto/
│       ├── create-ai-prompt.dto.ts          # body = { template, contextConfig } (ไม่มี promptType)
│       ├── update-prompt-note.dto.ts
│       └── ai-prompt-response.dto.ts
├── src/modules/ai/services/
│   └── sandbox-ocr-engine.service.ts        # ส่ง systemPrompt ไป sidecar (Step 1)
└── src/modules/ai/dto/
    └── sandbox-rag-prep.dto.ts              # (มีอยู่แล้ว — Step 3 RAG Prep)

frontend/
├── components/admin/ai/
│   ├── PromptManagementTabs.tsx            # Two-tab layout + Sandbox tab
│   ├── OcrPromptTab.tsx                    # OCR system prompt editor (textarea, no placeholders)
│   ├── AiExtractionPromptTab.tsx           # AI extraction template editor (with {{ocr_text}} validation)
│   ├── SystemPromptEditor.tsx              # Reusable textarea for system prompts (no placeholder validation)
│   ├── PromptVersionHistory.tsx            # Version list + rollback
│   ├── SandboxStepIndicator.tsx            # 3-step pipeline status (OCR → Extract → RAG Prep)
│   ├── RagPrepResultPanel.tsx              # Chunk list + vector preview (5 dimensions)
│   └── SandboxWorkflow.tsx                 # Full 3-step workflow container
├── lib/services/
│   └── admin-ai-prompt.service.ts          # API client for prompt endpoints
└── tests/
    └── prompt-management.spec.ts

specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/
└── app.py                                  # Modified: accept systemPrompt parameter ใน /ocr-upload
                                            # (NOTE: /embed + /rerank มีอยู่แล้วตั้งแต่ 2026-06-11)

Structure Decision: Web application with backend (NestJS), frontend (Next.js), and sidecar (FastAPI). Sidecar modifications:

  1. Add systemPrompt parameter to /ocr-upload endpoint (Step 1)
    • ยืนยันแล้ว (จาก app.py): inject โดย append text item เข้า messages[0]["content"] (pattern เดียวกับ DMS-tags injection ที่ใช้งานได้จริงแล้ว app.py:194-203) — ไม่ insert {"role":"system"} แยก (typhoon OCR = single-message format)
    • ต้อง thread systemPrompt ผ่าน _process_pdf_doc()process_ocr(..., system_prompt=...)
  2. /embed endpoint มีอยู่แล้ว (Step 3 - RAG Prep) — ไม่ใช่งานใหม่

Complexity Tracking

No complexity violations detected. Feature fits within standard project boundaries.

Phase 0: Research

Research Findings (research.md)

Decision: ใช้ optimistic locking ด้วย version/timestamp field ใน ai_prompts table Rationale: ลด lock contention ใน database, user experience ดีกว่า (แจ้งเตือนแทน block), ง่ายต่อการ implement กับ TypeORM @VersionColumn Alternatives considered:

  • Pessimistic locking: ไม่เลือกเพราะอาจ block admin คนอื่นนาน
  • Last-write-wins: ไม่เลือกเพราะเสี่ยงสูญเสียการแก้ไข

Decision: Sidecar รับ systemPrompt ผ่าน multipart/form-data field 'systemPrompt' Rationale: สอดคล้องกับรูปแบบที่ sidecar รับ file upload อยู่แล้ว, ไม่ต้องเปลี่ยน content-type Alternatives considered:

  • JSON payload: ไม่เลือกเพราะต้องเปลี่ยน endpoint structure มาก

Decision: Hardcoded default OCR system prompt ใช้ข้อความ minimal เช่น "Extract all text from this PDF page accurately." Rationale: Simple, language-agnostic, ทำงานได้กับทุกประเภทเอกสาร Alternatives considered:

  • ไม่มี default (fail): ไม่เลือกเพราะจะทำให้ OCR ใช้ไม่ได้ถ้าลืมสร้าง prompt

Phase 1: Design

Data Model (data-model.md)

Entity: AiPrompt (มีอยู่แล้วที่ backend/src/modules/ai/prompts/ai-prompts.entity.ts — ดูโครงสร้างจริงครบใน data-model.md)

จุดที่ต้องระวัง (ของจริง ต่างจากร่างเดิม):

  • PK = @PrimaryGeneratedColumn() INT (id, @Exclude)
  • createdBy: number (INT FK → users.user_id) — ไม่ใช่ createdByPublicId
  • มี fieldSchema, testResultJson, manualNote, lastTestedAt, activatedAt ที่ร่างเดิมตกหล่น
  • มี @VersionColumn({ name: 'version' }) แล้ว (delta 2026-06-15)

prompt_type values:

  • ocr_system: OCR system prompt สำหรับ np-dms-ocr model (ใหม่ — งาน 238)
  • ocr_extraction, rag_query_prompt, rag_prep_prompt, classification_prompt: มี validation ใน create() อยู่แล้ว

Validation rules:

  • ocr_extraction template ต้องมี placeholder {{ocr_text}}
  • ocr_extraction template อาจมี placeholder {{master_data_context}} (optional)
  • ocr_system template ไม่มี required placeholders (free-form system prompt)

API Contracts (contracts/api.yaml)

Endpoints (route จริง — @Controller('ai/prompts') + global prefix /api):

GET    /api/ai/prompts/{promptType}                                # List versions ของ type
POST   /api/ai/prompts/{promptType}                                # Create version (header Idempotency-Key)
DELETE /api/ai/prompts/{promptType}/{versionNumber}                # Delete version (ห้ามลบ active)
POST   /api/ai/prompts/{promptType}/{versionNumber}/activate       # Activate (header Idempotency-Key)
PATCH  /api/ai/prompts/{promptType}/{versionNumber}/note           # Update manual note
GET    /api/ai/prompts/{promptType}/{versionNumber}/context-config # Get context config
PUT    /api/ai/prompts/{promptType}/{versionNumber}/context-config # Update context config

# Sandbox (มีอยู่แล้วใน AiController @Controller('ai'))
POST   /api/ai/admin/sandbox/ocr                                   # Step 1 OCR
POST   /api/ai/admin/sandbox/extract                               # Step 2 Extract
POST   /api/ai/admin/sandbox/rag-prep                              # Step 3 RAG Prep (มีอยู่แล้ว)

Request/Response DTOs (ตรงกับของจริง):

// CreateAiPromptDto (promptType เป็น path param ไม่ใช่ body)
{
  template: string;        // @MaxLength(4000)
  contextConfig?: object;
}

// activate(): ของจริงใช้ pessimistic_write lock — ไม่รับ expectedVersion และยังไม่มี 409 flow
// ถ้าจะทำ optimistic locking (expectedVersion + HTTP 409) ตาม spec ต้องแก้ signature activate() เพิ่ม

Quick Start (quickstart.md)

Setup Steps:

  1. Database delta (ADR-009: edit SQL directly, ไม่ใช้ TypeORM migration): seed default ocr_system prompt — คอลัมน์ version มีอยู่แล้ว
  2. Backend: ขยาย ai-prompts.service.ts/ai-prompts.controller.ts ที่มีอยู่ (ไม่สร้างไฟล์ใหม่)
  3. Frontend: เพิ่ม PromptManagementTabs component แล้ว build ด้วย pnpm --filter lcbp3-frontend build
  4. Sidecar: Deploy updated app.py with systemPrompt parameter support (ต้อง spike ยืนยันก่อน)
  5. Seed data: Insert default OCR system prompt if table empty (created_by = user_id ของ superadmin)

Next Steps

Run /speckit-tasks เพื่อ generate tasks.md จาก plan นี้