Files
lcbp3/specs/88-logs/session-2026-06-20-ocr-sidecar-refactor-phase6-9.md
T
admin a80ebef285
CI / CD Pipeline / build (push) Successful in 7m37s
CI / CD Pipeline / deploy (push) Failing after 20m15s
refactor(ai): OCR sidecar canonical naming cleanup — typhoon→np-dms, remove hardcoded keys, asyncio.to_thread, ADR-040/041
2026-06-20 16:37:04 +07:00

4.9 KiB

Session — 2026-06-20 (OCR Sidecar Refactor Phase 6-9 Implementation)

Summary

Implemented Phases 6, 8, and 9 of the OCR Sidecar Refactor (Speckit-140) following ADR-040. Phase 6 refactored the sidecar to async I/O with lifespan context manager. Phase 8 removed the unused /normalize endpoint. Phase 9 polished Dockerfile, docker-compose.yml, created README.md, and validated quickstart.md. All 19 Python tests pass.

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

ไม่มี bug ใน session นี้ — เป็นการ implement feature ใหม่ตาม ADR-040:

  • Sync I/O bottleneck: process_ocr ใช้ httpx.Client แบบ sync ทำให้ block FastAPI event loop
  • Stale startup pattern: @app.on_event("startup") deprecate แล้วใน FastAPI 0.111+
  • Unused /normalize endpoint: ไม่มี consumers ใน backend codebase
  • Stale Docker config: OCR_LANG, USE_GPU เป็น Tesseract config ที่ไม่ใช้แล้ว
  • Missing curl: Dockerfile ไม่มี curl ทำให้ HEALTHCHECK ล้มเหลว

การแก้ไข (Fix)

Phase 6: Async I/O Performance (T041-T046)

ไฟล์ การเปลี่ยนแปลง
specs/04-Infrastructure-OPS/.../ocr-sidecar/app.py process_ocrasync def, _process_pdf_docasync def, /ocr + /ocr-uploadasync def
app.py เพิ่ม ollama_client global (httpx.AsyncClient) สร้างใน lifespan context manager
app.py แทน @app.on_event("startup") ด้วย @asynccontextmanager lifespan
app.py Model loading ผ่าน asyncio.to_thread(_load_bge_models)
app.py แทน httpx.Client ด้วย await client.post() (AsyncClient)
tests/integration/ocr-sidecar/test_async_performance.py New file: 6 tests (coroutine check, lifespan, ollama_client global, /normalize removed, concurrent requests)
tests/unit/ocr-sidecar/test_residency_wiring.py Updated: FakeClientFakeAsyncClient, sync → asyncio.run()
tests/integration/ocr-sidecar/test_parameter_governance.py Updated: async mock, patch ollama_client
tests/integration/ocr-sidecar/test_active_prompt.py Updated: async mock, patch ollama_client

Phase 8: Remove /normalize (T054-T055)

ไฟล์ การเปลี่ยนแปลง
app.py ลบ NormalizeRequest, NormalizeResponse, /normalize endpoint, pythainlp imports
requirements.txt ลบ pythainlp==5.0.4 และ Pillow==10.0.0
(grep verified) ไม่มี /normalize หรือ THAI_PREPROCESS_URL consumers ใน backend

Phase 9: Polish (T056-T063)

ไฟล์ การเปลี่ยนแปลง
Dockerfile เพิ่ม curl สำหรับ HEALTHCHECK, change log entry
docker-compose.yml ลบ OCR_LANG, USE_GPU; เพิ่ม OCR_SIDECAR_API_KEY, OCR_ACTIVE_PROFILE
README.md New file: architecture, endpoints, env vars, deploy guide, test coverage
quickstart.md แก้ stale requirements (ลบ pythainlp/Pillow), แก้ TYPHOON_OCR_MODELOCR_MODEL
tasks.md Mark T041-T046, T054-T063 as [x]

กฎที่ Lock แล้ว

  • Async I/O pattern: process_ocr ต้องเป็น async def และใช้ httpx.AsyncClient ผ่าน ollama_client global (สร้างใน lifespan)
  • Lifespan over startup event: ใช้ @asynccontextmanager lifespan แทน @app.on_event("startup") — deprecate แล้วใน FastAPI 0.111+
  • Model loading non-blocking: ใช้ asyncio.to_thread() สำหรับ model loading ใน lifespan เพื่อไม่ block startup
  • No /normalize: endpoint ถูกลบแล้ว — ไม่มี consumers ใน backend
  • Test mock pattern: ใช้ FakeAsyncClient (async post() + aclose()) แทน FakeClient (sync) สำหรับทุก test ที่ mock Ollama API

Verification

  • python -m pytest tests/ -v19/19 tests passed in 4.81s
  • test_process_ocr_is_coroutine_function — process_ocr เป็น async
  • test_process_pdf_doc_is_coroutine_function — _process_pdf_doc เป็น async
  • test_app_uses_lifespan_not_startup_event — ใช้ lifespan ไม่ใช่ on_event
  • test_app_has_async_client_global — ollama_client global มีอยู่
  • test_normalize_endpoint_removed — /normalize ถูกลบแล้ว
  • test_concurrent_ocr_requests_dont_block — 3 concurrent requests สำเร็จ
  • Existing tests (path traversal, API key, residency, CPU fallback, parameter governance, active prompt) — ทั้งหมดผ่าน