Phase 1 MVP: synchronous OCR + regex header extraction

Implements the foundation of the OCR Sprint service:
- FastAPI app with /api/v1/health and /api/v1/documents (sync upload)
- Pydantic v2 schemas for documents, extraction result, personnel
- Pipeline: PDF/image ingest (PyMuPDF), preprocessing (resize, deskew,
  denoise, optional adaptive threshold), PaddleOCR wrapper, regex-based
  header extraction (nomor sprint, tanggal, satuan, perihal, dasar),
  signatory NRP, master-pangkat validation, confidence scoring + routing.
- Tests: 61 unit tests covering regex rules, validators, preprocess,
  ingest, confidence, and API contract (PaddleOCR mocked).
- Tooling: pyproject (setuptools), ruff, mypy strict, pytest, pre-commit,
  Dockerfile, docker-compose, Makefile.
- Docs: README + docs/architecture.md (full hybrid stack rationale and
  6-phase roadmap).

Co-authored-by: adrian kuman firmansah <adriancuman@gmail.com>
This commit is contained in:
Devin AI
2026-04-25 14:58:50 +00:00
commit ca0c0a0428
45 changed files with 2457 additions and 0 deletions

259
docs/architecture.md Normal file
View File

@@ -0,0 +1,259 @@
# Plan & Arsitektur — OCR Service Surat Sprint Kepolisian
## 1. Penilaian Jujur Tech Stack yang Diusulkan
Tech stack Anda (FastAPI + PaddleOCR + OpenCV/Pillow + Regex) **sudah bagus dan layak produksi**, tapi **belum tentu paling optimal akurasinya** untuk kasus surat sprint. Ada beberapa gap yang perlu diisi sebelum bisa disebut "terbaik".
### Yang sudah tepat
| Komponen | Alasan |
|---|---|
| **FastAPI** | Async native, Pydantic validation, OpenAPI docs otomatis, ideal untuk ML serving. |
| **PaddleOCR (PP-OCRv4/v5)** | Salah satu OCR open-source terbaik untuk dokumen campuran teks + tabel, mendukung Latin (cocok untuk Bahasa Indonesia), bisa jalan on-premise (penting untuk dokumen kepolisian yang sensitif — **cloud OCR seperti Google Vision/AWS Textract sebaiknya dihindari** karena masalah kerahasiaan). |
| **OpenCV + Pillow** | Standar industri untuk preprocessing. |
| **Regex/rule-based** | Cocok untuk dokumen terstruktur seperti sprint yang format-nya relatif baku. |
### Yang masih kurang / perlu ditambah
1. **Table extraction belum tertangani**
Daftar personel di surat sprint hampir selalu berbentuk **tabel** (No, Pangkat, NRP, Nama, Jabatan, Keterangan). Regex pada teks linear dari OCR biasa **akan kacau** ketika baris tabel pecah atau kolom bergeser. Solusi: gunakan **PaddleOCR PP-Structure** (modul table recognition bawaan Paddle) atau model khusus seperti **TableTransformer (Microsoft)**.
2. **Document detection & dewarping untuk foto HP belum eksplisit**
Foto HP bermasalah karena: perspektif miring, lipatan, bayangan, lighting tidak rata, fokus tidak merata. OpenCV crop + perspective transform manual saja sering gagal. Tambahkan:
- **Document corner detection**: `DocTR` / `MobileSAM` / model edge-based, atau heuristik kontur OpenCV sebagai fallback.
- **Dewarping**: `DocTr` / `DewarpNet` untuk halaman yang melengkung (lipatan).
- **Shadow removal**: algoritma background division atau model spesialis.
3. **Strategi ekstraksi 100% regex itu rapuh**
Surat sprint dari satuan berbeda (Polda, Polres, Polsek, Mabes) punya **variasi format**: header berbeda, urutan field berbeda, kadang pangkat disingkat (`AKP`, `IPDA`) kadang ditulis penuh. Regex murni akan butuh ratusan rule dan tetap miss kasus baru.
**Rekomendasi pendekatan hybrid**:
- **Layer 1 — Regex/rule** untuk field deterministik (Nomor sprint, tanggal, dasar hukum) yang format-nya baku.
- **Layer 2 — Schema-aware extraction** menggunakan **LLM lokal** (Llama 3.1 8B / Qwen2.5 7B via Ollama atau vLLM) dengan structured output (JSON schema / Pydantic) untuk field yang variatif (jabatan, keterangan tugas).
- **Layer 3 — Validation** terhadap master data (daftar pangkat valid, format NRP 8 digit, dll).
4. **Tidak ada confidence scoring & human-in-the-loop**
Untuk dokumen kepolisian, **akurasi 100% otomatis itu mitos**. Sistem harus:
- Mengeluarkan confidence score per field.
- Otomatis flag dokumen low-confidence untuk review manusia.
- Sediakan UI/endpoint koreksi yang feedback-nya bisa dipakai retraining.
5. **Alternatif end-to-end yang patut dipertimbangkan**
Jika nanti volume dokumen besar dan format relatif stabil, fine-tuning model **Document Understanding** end-to-end bisa lebih akurat:
- **Donut** (OCR-free, langsung image → JSON).
- **LayoutLMv3** (kombinasi teks + layout + visual).
- **Surya OCR** (newer, sangat bagus untuk dokumen).
Untuk MVP, tetap pakai PaddleOCR. Donut/LayoutLM adalah opsi V2 setelah ada labeled dataset cukup (~5001000 dokumen).
### Verdict
Stack Anda **bisa mencapai ~8592% akurasi field-level** untuk surat sprint dengan kualitas scan baik, dan **~7080%** untuk foto HP, **kalau ditambah** komponen di atas. Tanpa table extraction + dewarping + hybrid extraction, akurasinya akan jatuh di kondisi nyata.
---
## 2. Arsitektur yang Direkomendasikan
### 2.1 Diagram Logis
```
┌────────────────────────────────────────────────────────────────────┐
│ Client (Web/Mobile) │
└──────────────────────────────┬─────────────────────────────────────┘
│ HTTPS (multipart upload)
┌────────────────────────────────────────────────────────────────────┐
│ FastAPI Gateway (stateless) │
│ - Auth (JWT/API key) - Rate limit - Request validation │
└──────────────────────────────┬─────────────────────────────────────┘
│ enqueue job
┌────────────────────────────────────────────────────────────────────┐
│ Job Queue (Redis + Celery / RQ / Dramatiq) │
└──────────────────────────────┬─────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────┐
│ OCR Worker Pipeline (GPU/CPU) │
│ ┌────────────┐ ┌──────────────┐ ┌───────────┐ ┌────────────┐ │
│ │ 1. Ingest │→ │ 2. Preproc │→ │ 3. OCR + │→ │ 4. Extract │ │
│ │ & detect │ │ (deskew, │ │ Layout │ │ (regex + │ │
│ │ PDF/IMG │ │ dewarp, │ │ PP-Struct│ │ LLM + │ │
│ │ │ │ denoise) │ │ + Table) │ │ validate) │ │
│ └────────────┘ └──────────────┘ └───────────┘ └─────┬──────┘ │
│ │ │
│ ┌──────────────────────────────┘ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 5. Confidence│ → low conf? flag for review │
│ │ scoring │ │
│ └──────┬───────┘ │
└──────────────────────────┼─────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────┐
│ Storage: PostgreSQL (metadata) + MinIO/S3 (file) │
│ + Vector store opsional (untuk dedup / search) │
└────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────┐
│ Review UI (optional) — koreksi manual + audit trail │
└────────────────────────────────────────────────────────────────────┘
```
### 2.2 Pipeline Detail per Tahap
**Tahap 1 — Ingest & Document Detection**
- PDF: render setiap halaman jadi image @ 300 DPI (`pdf2image` / `PyMuPDF`).
- Image (foto HP): deteksi sudut dokumen → crop → perspective transform.
- Library: OpenCV `findContours` (cepat) sebagai fallback, **DocTR document detector** (lebih akurat) sebagai utama.
**Tahap 2 — Preprocessing**
- Deskew (rotation correction) — Hough transform atau model.
- Dewarp (untuk foto buku/lipatan) — `DewarpNet` atau model RNN.
- Adaptive thresholding (untuk foto dengan lighting tidak rata).
- Shadow removal (background division).
- Denoise (Non-Local Means).
- Resize ke ukuran optimal OCR (~15002500 px sisi panjang).
**Tahap 3 — OCR + Layout Analysis**
- **PaddleOCR PP-Structure** dijalankan sekali → menghasilkan:
- Bounding boxes + teks + confidence per word/line.
- Table region detection + table-to-HTML/JSON.
- Layout type per region (title, paragraph, table, figure).
- Output ditampung sebagai struktur intermediate (mirip hOCR / ALTO XML).
**Tahap 4 — Information Extraction**
- **4a. Header parsing (regex)**: Nomor sprint, tanggal, satuan penerbit, dasar hukum, perihal. Format relatif baku → regex sangat cocok.
- **4b. Personnel table extraction**: ambil dari hasil PP-Structure table → mapping kolom (Pangkat, NRP, Nama, Jabatan, Keterangan).
- **4c. LLM fallback**: untuk field yang regex/table miss, kirim chunk teks + JSON schema ke LLM lokal (Ollama / vLLM) dengan **structured output** (Pydantic via `outlines` / `instructor`).
- **4d. Validation layer**:
- NRP: 8 digit numerik.
- Pangkat: harus ada di daftar master pangkat Polri.
- Tanggal: parse + sanity check.
- Cross-check: jumlah personel di body = jumlah baris tabel.
**Tahap 5 — Confidence Scoring & Routing**
- Aggregate confidence: weighted average dari OCR confidence + validation pass/fail + LLM logprob (kalau pakai).
- Threshold (mis. < 0.85) status `NEEDS_REVIEW`.
- Threshold tinggi (≥ 0.95) + semua validasi pass status `AUTO_APPROVED`.
### 2.3 API Endpoint (FastAPI)
```
POST /api/v1/documents # upload, kembalikan job_id
GET /api/v1/documents/{job_id} # poll status + hasil
GET /api/v1/documents/{job_id}/raw # raw OCR output (debug)
PATCH /api/v1/documents/{job_id} # koreksi manual (HITL)
GET /api/v1/health # liveness
GET /api/v1/metrics # Prometheus
```
Response shape (contoh):
```json
{
"job_id": "uuid",
"status": "completed | processing | needs_review | failed",
"confidence": 0.92,
"data": {
"nomor_sprint": "Sprin/123/IV/2025",
"tanggal": "2025-04-21",
"satuan_penerbit": "Polres Bandung",
"dasar": ["...", "..."],
"perihal": "...",
"personel": [
{"no": 1, "pangkat": "AKP", "nrp": "12345678", "nama": "...", "jabatan": "Kasat Reskrim", "confidence": 0.97},
...
],
"ttd": {"pejabat": "...", "pangkat": "...", "nrp": "..."}
},
"review_flags": []
}
```
### 2.4 Tech Stack Final yang Direkomendasikan
| Layer | Pilihan | Catatan |
|---|---|---|
| API | **FastAPI** + Uvicorn/Gunicorn | sesuai usulan |
| Validation | **Pydantic v2** | wajib |
| Queue | **Redis + Celery** atau **Dramatiq** | OCR berat, jangan blocking request |
| OCR | **PaddleOCR PP-OCRv4 + PP-Structure** | tambah PP-Structure untuk tabel |
| Preprocessing | **OpenCV + Pillow** + **DocTR** (detection) | DocTR untuk foto HP |
| Extraction | **Regex + Ollama (Llama 3.1 8B / Qwen2.5 7B)** + **instructor/outlines** | hybrid |
| Storage | **PostgreSQL** (metadata) + **MinIO** (file blob) | self-hosted, sesuai compliance |
| Observability | **Prometheus + Grafana + Loki** | wajib produksi |
| Container | **Docker + docker-compose** (dev) **Kubernetes** (prod) | |
| GPU | NVIDIA T4/A10 (1× cukup untuk MVP) | PaddleOCR jauh lebih cepat di GPU |
---
## 3. Roadmap Pengembangan (Bertahap)
### Fase 0 — Persiapan (1 minggu)
- Kumpulkan **dataset sampel**: minimal 50 surat sprint (campur PDF scan + foto HP) dari beragam satuan.
- Buat **ground truth labelling** untuk 20 dokumen (untuk evaluasi).
- Definisikan **schema output final** (JSON) bersama stakeholder.
### Fase 1 — MVP Pipeline Sinkron (2 minggu)
- Setup FastAPI skeleton + Pydantic schemas.
- Integrasi PaddleOCR PP-OCRv4 (CPU dulu, GPU menyusul).
- Preprocessing dasar: deskew + denoise + resize.
- Regex extraction untuk field header.
- Endpoint sinkron `POST /documents` (untuk dev/testing saja).
- **Evaluasi akurasi** terhadap 20 ground truth.
### Fase 2 — Robustness untuk Foto HP (2 minggu)
- Integrasi document detection (DocTR atau OpenCV contour).
- Perspective transform + dewarping.
- Shadow removal.
- Re-evaluasi akurasi pada subset foto HP.
### Fase 3 — Table Extraction (1.5 minggu)
- Integrasi PP-Structure untuk personnel table.
- Mapping kolom + validation (NRP, pangkat).
- Master data tabel pangkat Polri.
### Fase 4 — Async + Production Ready (1.5 minggu)
- Pindahkan ke arsitektur async dengan Celery + Redis.
- Storage MinIO + PostgreSQL.
- Auth, rate limit, logging, metrics.
- Docker compose untuk deployment.
### Fase 5 — LLM Hybrid Extraction (2 minggu)
- Setup Ollama / vLLM dengan model lokal.
- Structured output via `instructor`.
- Confidence scoring + routing ke review.
### Fase 6 — HITL Review UI (opsional, 2 minggu)
- Endpoint koreksi.
- Simple web UI (Next.js) untuk reviewer.
- Audit trail & feedback loop.
### Fase 7 — Optimasi Lanjutan (ongoing)
- Fine-tune PaddleOCR detection/recognition pada dataset internal.
- Eksplorasi Donut/LayoutLMv3 jika dataset sudah cukup.
- Batch processing & GPU optimization.
**Total estimasi MVP fungsional (Fase 14): ~7 minggu** dengan 1 backend engineer + 1 ML engineer.
---
## 4. Risiko & Mitigasi
| Risiko | Mitigasi |
|---|---|
| Data sensitif (kepolisian) bocor | Wajib on-prem; tidak ada cloud OCR; enkripsi at-rest (LUKS/pgcrypto) + in-transit (mTLS); audit log lengkap. |
| Variasi format antar satuan | Hybrid extraction (regex + LLM); kumpulkan sample dari banyak satuan sejak awal. |
| Foto HP kualitas buruk | Validasi kualitas image di client (resolusi minimal, blur detection) sebelum upload. |
| Akurasi tidak sampai target | HITL review wajib untuk dokumen low-confidence; jangan deploy fully-automatic. |
| Tanggung jawab hukum atas hasil OCR | Selalu simpan original document + flag bahwa hasil ekstraksi adalah "draft, perlu verifikasi manusia". |
---
## 5. Pertanyaan Sebelum Implementasi
Sebelum saya lanjut ke implementasi, mohon konfirmasi:
1. **Volume**: berapa dokumen/hari yang ditargetkan? (mempengaruhi pilihan async vs sync, GPU vs CPU)
2. **Deployment target**: on-prem mutlak, atau private cloud (GovCloud) boleh?
3. **Source dokumen**: apakah ada akses ke 2050 sample surat sprint untuk dijadikan dataset awal?
4. **Integrasi**: service ini akan dipanggil sistem apa? (mempengaruhi auth & API contract)
5. **HITL**: apakah ada SDM untuk review manual dokumen low-confidence?
6. **Hardware**: sudah ada server GPU, atau perlu sizing rekomendasi?
7. **Format output final**: ada schema yang sudah dipakai sistem downstream?