feat: update backend OCR parser, web app, mobile app camera/preview UI, tests, and documentation with sample images

This commit is contained in:
Rafhan Mazaya Fathurrahman committed 2026-07-02 13:33:57 +07:00
1 parent aa3233e411
commit bdb3a49742
71 files changed
+3360 -764

No files matched your search

+234
View File
@@ -0,0 +1,234 @@
# Dokumentasi Detail Alur Kerja PFM OCR (Prima Fresh Mart)
Dokumen ini memuat penjelasan lengkap mengenai alur kerja (workflow) pengolahan gambar dokumen Delivery Order (DO) hingga data tersimpan di database dan divisualisasikan pada dashboard web.
---
## Ringkasan Alur Kerja
Berikut adalah flowchart lengkap yang menggambarkan aliran data dan pemrosesan dari aplikasi mobile, gateway API, pipeline OCR, hingga penyimpanan database:
```mermaid
graph TD
%% Sisi Client (Flutter)
subgraph Client [1. Client-Side: Flutter App]
Start([Mulai: Supir Scan DO]) --> Capture[Ambil Foto via Camera API]
Capture --> GPS[Dapatkan Koordinat via Geolocator]
GPS --> BlurCheck{Cek Kualitas via BlurDetector}
BlurCheck -- Blur/Jelek --> Capture
BlurCheck -- Tajam/Baik --> Upload[Kirim Form Data via Dio HTTP Client]
end
%% Web Gateway (Nginx & Next.js)
subgraph Gateway [2. Gateway & API: Next.js & Nginx]
Upload --> Proxy[Nginx Proxy: Terima Request di Port 8000]
Proxy --> UploadAPI[Next.js Upload API: Simpan Gambar ke /uploads]
UploadAPI --> SQLInit[pg Driver: Simpan Dokumen ke DB - parsed=false]
SQLInit --> TriggerParse[Panggil API /api/parse & Kirim Base64]
end
%% Pipeline OCR (FastAPI)
subgraph OCR [3. Layout Parsing & OCR Pipeline: FastAPI]
TriggerParse --> OriCheck[Deteksi Orientasi via PP-LCNet]
OriCheck --> TiltCalc[Hitung Sudut Kemiringan via block_polygon_points]
TiltCalc --> TiltCheck{Kemiringan > 1.0 deg?}
TiltCheck -- Ya --> Unwarp[Lakukan Unwarping via UVDoc]
Unwarp --> ReRun[Proses Ulang Gambar Lurus]
TiltCheck -- Tidak/Sudah Lurus --> LayoutDet[Segmentasi Tata Letak via PP-DocLayoutV3]
ReRun --> LayoutDet
LayoutDet --> TextRec[Deteksi & Pengenalan Teks via PP-OCRv6_medium]
TextRec --> MarkdownGen[Susun Output Menjadi Teks Markdown]
end
%% VLM Server
subgraph VLM [4. Vision-Language Server: vLLM]
MarkdownGen --> QueryVLM[Query VLM Lokal via Port 8118]
QueryVLM --> ModelVLM[Proses Model PaddleOCR-VL-1.6-0.9B]
ModelVLM --> ResVLM[Kembalikan Hasil Teks Terstruktur]
end
%% Next.js Data Processing
subgraph Processing [5. Pengolahan Data & Validasi: Next.js]
ResVLM --> RegEx[Ekstraksi Regex PO/SO/DO via parseDOMetadata]
RegEx --> StoreMatch[Pencocokan Toko via resolveStoreFromText ke DB]
StoreMatch --> Sanitize[Sanitasi & Standardisasi Data via sanitizeParsedMetadata]
Sanitize --> SQLSave[Update Dokumen & ocr_items ke DB - parsed=true]
end
%% Polling & Selesai
subgraph Polling [6. Sinkronisasi Akhir: Flutter App]
SQLSave --> PollRequest[Dio Polling GET /documents tiap 2s]
PollRequest --> PollCheck{Status parsed = true?}
PollCheck -- Belum --> PollRequest
PollCheck -- Ya --> StateUpdate[Riverpod: Perbarui State UI & Tampilkan Data]
StateUpdate --> PrintPDF[Cetak Label PDF via pdf & printing lib]
PrintPDF --> End([Selesai])
end
```
---
## Tech Stack (Teknologi yang Digunakan)
Sistem ini dibangun menggunakan arsitektur modern yang membagi tugas antara aplikasi mobile client, API gateway, database, dan engine AI/OCR khusus:
### 1. Client-Side (Aplikasi Mobile)
* **Framework**: Flutter (Dart SDK `>=3.2.0`)
* **State Management & DI**: Riverpod (`flutter_riverpod`)
* **Routing**: GoRouter (`go_router`)
* **HTTP Client**: Dio (`dio`)
* **Local Storage / Cache**: Hive (`hive`, `hive_flutter`) & SharedPreferences
* **Sensors & Utilities**: Camera API (`camera`), Exif Data (`exif`), Location Service (`geolocator`), dan Blur Detection (`sensors_plus`)
* **PDF & Printing**: PDF Generator & Printing (`pdf`, `printing`)
### 2. Backend Gateway & Dashboard Web Admin
* **Framework**: Next.js 16 (React 19 & TypeScript)
* **Styling (CSS)**: TailwindCSS v4
* **Database Driver**: Node-postgres (`pg`)
* **Deployment/Runtime**: Node.js & Docker
### 3. Database
* **Engine**: PostgreSQL 15 (di-deploy via Docker)
### 4. Mesin AI & Pipeline OCR (Python & Docker)
* **Framework OCR**: PaddleX / PaddleOCR (menggunakan GPU CUDA 12.6 devel base image)
* **Model Deteksi Tata Letak**: `PP-DocLayoutV3` (untuk segmentasi blok teks/tabel)
* **Model Koreksi Kemiringan**: `PP-LCNet_x1_0_doc_ori` & `UVDoc` (untuk *auto-deskew* & *unwarping*)
* **Vision-Language Model (VLM)**: `PaddleOCR-VL-1.6-0.9B` (VLM 0.9B lokal)
* **VLM Inference Engine**: `vllm` server (berfungsi sebagai back-end inference manager untuk akselerasi GPU)
* **Python Web Framework**: FastAPI & Uvicorn (untuk expose `/layout-parsing` API endpoint)
### 5. Web Proxy / Server Gateway
* **Engine**: Nginx (Alpine) sebagai reverse proxy untuk routing port internal kontainer Next.js, FastAPI (Pipeline-API), dan vLLM.
---
## Interaksi Alur Kerja & Tech Stack
Di bawah ini adalah pemetaan langkah per langkah bagaimana komponen-komponen tech stack di atas berinteraksi secara real-time:
```mermaid
sequenceDiagram
autonumber
actor User as Pengguna
participant Mobile as Flutter App (Dio & Riverpod)
participant Nginx as Nginx Reverse Proxy
participant Gateway as Next.js Gateway (TypeScript & pg)
participant DB as PostgreSQL 15
participant Pipeline as FastAPI OCR (PaddleX & Python)
participant VLM as vLLM Server (PaddleOCR-VL-1.6)
User->>Mobile: Ambil foto DO
Note over Mobile: Kamera & GPS diakses via camera & geolocator.<br/>Pemeriksaan blur via BlurDetector.
Mobile->>Nginx: POST /api/v1/documents/upload (Base64/Form Data via Dio)
Nginx->>Gateway: Forward ke Next.js (port 3000)
Gateway->>DB: Simpan data awal dokumen (pg driver)
Gateway->>Pipeline: POST /layout-parsing (Base64)
Note over Pipeline: 1. Koreksi kemiringan (PP-LCNet & UVDoc)<br/>2. Deteksi layout tabel & teks (PP-DocLayoutV3)<br/>3. Ekstraksi teks (PP-OCRv6)<br/>4. Hasil akhir diubah ke Markdown.
Pipeline->>VLM: Query VLM lokal (port 8118 via vLLM)
VLM-->>Pipeline: Respons teks terstruktur dari model VL-1.6-0.9B
Pipeline-->>Gateway: Kembalikan Markdown lengkap & koordinat polygon
Note over Gateway: 1. parseDOMetadata() mengurai PO, SO, DO via Regex.<br/>2. resolveStoreFromText() mencocokkan toko ke DB.<br/>3. sanitizeParsedMetadata() menstandardisasi tanggal & plat.
Gateway->>DB: INSERT/UPDATE metadata & ocr_items (parsed = true)
loop Polling (tiap 2 detik)
Mobile->>Gateway: GET /documents (Status check via Dio)
Gateway-->>Mobile: Kirim data final (di-render via Riverpod)
end
User->>Mobile: Cetak Label
Note over Mobile: Generate & cetak dokumen PDF via pdf & printing lib.
```
### Penjelasan Alur Data & Teknologi:
1. **Pengambilan Gambar (Flutter Client)**: Pengguna mengambil foto Delivery Order (DO). Aplikasi Flutter mengakses kamera via **Camera API** dan mengambil GPS via **Geolocator**. Setelah lolos uji kualitas blur via `BlurDetector`, data dikirim menggunakan **Dio HTTP Client** via request `POST` Multipart Form ke Nginx pada port **`8000` (eksternal)** ke endpoint **`/api/v1/documents/upload`** (untuk supir/mobile) atau **`/api/upload`** (untuk dashboard web admin).
2. **Reverse Proxy (Nginx)**: Kontainer **`paddleocr-nginx`** mendengarkan di port **`80` (internal) / `8000` (eksternal)**. Nginx merutekan request dari client eksternal secara internal di dalam Docker network:
* Request ke `/api/*` diteruskan ke kontainer **`paddleocr-pfm-web-app`** pada port **`3000`**.
* Request langsung ke `/layout-parsing` diteruskan ke kontainer **`paddleocr-pipeline-api-v10`** pada port **`8090`**.
* Request langsung ke `/v1` diteruskan ke kontainer **`paddleocr-vllm-server`** pada port **`8118`**.
3. **Penyimpanan Dokumen Awal (Next.js Gateway)**: Kontainer **`paddleocr-pfm-web-app`** menerima request upload di port **`3000`**, menulis gambar fisik ke folder `/uploads`, dan menyimpannya ke database **`paddleocr-db`** pada port **`5432`** dengan query `INSERT` (status `parsed = false`) menggunakan driver **pg (node-postgres)**. Setelah itu, Next.js mengirimkan data Base64 via request `POST` secara internal ke kontainer **`paddleocr-pipeline-api-v10`** pada port **`8090`** ke endpoint **`/layout-parsing`**.
4. **Layout-Parsing (FastAPI OCR Pipeline)**: Kontainer **`paddleocr-pipeline-api-v10`** menerima request di port **`8090`** pada endpoint **`/layout-parsing`**. Menjalankan kode Python dengan model-model:
* Klasifikasi orientasi & unwarping kemiringan via **PP-LCNet_x1_0_doc_ori** & **UVDoc**.
* Deteksi tata letak via **PP-DocLayoutV3**.
* Pengenalan teks via **PP-OCRv6_medium** dengan akselerasi GPU CUDA 12.6.
* Hasil parsing ditransformasi menjadi teks Markdown mentah.
5. **Pemahaman Konten Dokumen (vLLM Server)**: FastAPI (`pipeline-api`) meneruskan request secara internal ke kontainer **`paddleocr-vllm-server`** di port **`8118`** pada endpoint **`/v1/chat/completions`** dengan menggunakan Vision-Language Model **`PaddleOCR-VL-1.6-0.9B`** untuk menganalisis teks secara kontekstual.
6. **Ekstraksi Metadata & Fuzzy Matching (Next.js)**: Next.js menerima respons balik, mengekstrak data PO, SO, DO via Regex (`parseDOMetadata`), mencocokkan toko penerima ke database PostgreSQL port **`5432`** (`resolveStoreFromText`), menstandardisasi format data (`sanitizeParsedMetadata`), lalu memperbarui status dokumen di PostgreSQL menjadi `parsed = true` dan menyimpan item barang ke tabel `ocr_items`.
7. **Sinkronisasi (Flutter Polling)**: Aplikasi Flutter mengirim request `GET` secara berkala setiap 2 detik menggunakan **Dio** ke port **`8000`** (eksternal) ke endpoint **`/api/documents`** (atau `/api/documents/[id]`) untuk memantau status parsing. Begitu status berubah menjadi `true`, **Riverpod** memperbarui UI secara real-time pada perangkat supir. Pengguna juga dapat mengekspor data ke format PDF menggunakan pustaka **pdf** & **printing** Flutter.
---
## Detail Langkah Per Langkah
### Langkah 1: Client-Side (Flutter App)
1. **Layar Konfirmasi**: Setelah memotret dokumen, pengguna diarahkan ke [image_preview_screen.dart](file:///d:/Client/Data%20Bisnis%20Solusi/app-pfm-ocr-v2/lib/features/camera/image_preview_screen.dart). Di layar ini, sistem mendeteksi apakah gambar blur menggunakan kelas `BlurDetector`.
2. **Unggah Dokumen**: Pengguna menekan tombol "Unggah Dokumen". Ini memicu aksi pada `PendingDocumentsNotifier` di [pending_documents_provider.dart](file:///d:/Client/Data%20Bisnis%20Solusi/app-pfm-ocr-v2/lib/features/documents/pending_documents_provider.dart):
* Status dokumen diatur ke `PendingDocumentStatus.uploading`.
* Koordinat GPS (latitude & longitude) diambil menggunakan `LocationService`.
* Gambar dikirim menggunakan form data ke backend (endpoint `/documents/upload`).
* Halaman otomatis beralih ke daftar dokumen (`/documents`) untuk memulai *background polling* setiap 2 detik.
---
### Langkah 2: Gateway Penerimaan (Next.js Upload)
Ada dua gerbang masuk upload:
1. **API Web** ([route.ts (api/upload)](file:///d:/Client/Data%20Bisnis%20Solusi/app-pfm-ocr-v2/backend/pfm-web-app/src/app/api/upload/route.ts)): Digunakan oleh dashboard website admin.
2. **API Mobile v1** ([route.ts (api/v1/documents/upload)](file:///d:/Client/Data%20Bisnis%20Solusi/app-pfm-ocr-v2/backend/pfm-web-app/src/app/api/v1/documents/upload/route.ts)): Digunakan oleh aplikasi Flutter.
**Mekanisme Penyimpanan:**
* Berkas disimpan ke folder `/uploads` dengan nama unik (`Date.now()-filename`).
* Koordinat GPS disimpan.
* Memicu pemanggilan API parse sinkron `/api/parse` dengan menyertakan nama file.
---
### Langkah 3: Mesin Ekstraksi & OCR Pipeline (`/api/parse`)
Di dalam file [route.ts (api/parse)](file:///d:/Client/Data%20Bisnis%20Solusi/app-pfm-ocr-v2/backend/pfm-web-app/src/app/api/parse/route.ts):
#### 1. Layout-Parsing (OCR Pipeline)
* Berkas dikonversi menjadi format Base64 dan diposkan ke API Pipeline (`http://localhost:7871/layout-parsing`).
* Pipeline membedah tata letak halaman (tabel, teks, paragraf) dan menghasilkan format terstruktur (Markdown) beserta informasi polygon koordinat tiap blok teks.
* Konfigurasi `markdown_ignore_labels` disetel kosong (`[]`) pada berkas `pipeline_config_vllm.yaml` agar elemen tata letak seperti `header`, `footer`, `footnote`, `number`, dan `aside_text` tidak di-filter oleh detektor layout. Hal ini memastikan informasi administratif penting (seperti nomor PO/SO/Tanggal) yang terletak di bagian atas atau bawah halaman tetap terekstrak secara akurat.
#### 2. Auto-Deskew & Unwarping (Rotasi Gambar Otomatis)
* Sudut kemiringan dihitung secara trigonometris melalui `calculateAverageTilt(data)` dengan melihat titik polygon `block_polygon_points`:
$$\text{Angle} = \arctan\left(\frac{dy}{dx}\right) \times \frac{180}{\pi}$$
* Jika kemiringan rata-rata melampaui **1.0 derajat**, backend otomatis memicu pemrosesan ulang dengan menyalakan flag:
* `useDocUnwarping: true`
* `useDocOrientationClassify: true`
* Hal ini menjamin pembacaan teks lurus dan akurat walaupun dokumen difoto miring.
#### 3. Ekstraksi Metadata Lapis 1 (Regex & Alinyemen Layout)
Fungsi `parseDOMetadata(markdownText)` mengurai konten Markdown hasil OCR:
* **Vendor & Customer**: Mencari baris teks berawalan `PT.` (Vendor dibedakan dengan Customer/Primafood).
* **Nomor Dokumen**:
* **No PO**: Pola `PO/YY/XXXX` (dipaksa tahun berjalan).
* **No SO & DO**: Pola numerik 10 digit (misal: dimulai dengan `16`).
* **Koreksi Layout Tergeser (*Shifted Layout*)**: Jika hasil OCR tidak menyejajarkan teks label dengan nilainya (misal, nilai nomor SO tergeser di bawah label DO), parser memindai baris terdekat untuk melakukan pemetaan ulang berdasarkan kedekatan indeks baris.
#### 4. Sanitasi Lapis 2 (Validator Sanity Check)
Fungsi `sanitizeParsedMetadata()` menjamin kepatuhan data sebelum disimpan ke database:
* **Tanggal**: Wajib berformat `"dd Month yyyy"` (Januari s.d. Desember Inggris). Jika tidak valid atau di luar rentang (2010 s.d. tahun depan), nilainya diganti `"Not Found"`.
* **SO & DO**: Wajib berupa digit angka murni sepanjang 7 s.d. 12 karakter.
* **Plat Nomor**: Wajib memiliki format plat nomor Indonesia yang valid (misalnya: `B 1234 ABC`).
#### 5. Pencocokan Toko (Fuzzy Database Match)
Fungsi `resolveStoreFromText()` mencocokkan teks penerima dengan data toko aktif di database (`store_master`):
* Kata umum seperti *"dan", "dki", "jalan", "raya", "kelurahan"* dihapus dari token OCR.
* Dilakukan penghitungan rasio irisan token (*token intersection score*) dengan seluruh kombinasi `nama_toko` dan `alamat` di database.
* Toko dengan kecocokan minimal 2 token dan skor tertinggi dipilih untuk mengisi kolom `orderUntuk` dan `alamat`.
#### 6. Parsing Tabel Item SKU
* Backend memindai tag `<table>` di Markdown.
* Kolom kode barang dibersihkan dan difilter ketat untuk hanya menerima format **8 digit angka murni** (sesuai standar SKU item internal).
* Jika kolom kuantitas (`banyak`) tidak terbaca satuannya (misal hanya angka `"10"`), backend melengkapinya berdasarkan pola SKU (contoh: SKU `11310024` dilengkapi menjadi `"10 KRG"`).
---
### Langkah 4: Penyimpanan Database (PostgreSQL)
1. Metadata dokumen disimpan atau diperbarui pada tabel `documents`:
* Kolom status parsing diubah menjadi `parsed = true`.
* Kolom JSONB `metadata` menyimpan struktur PO, SO, DO, Plat Truk, Tanggal, dll.
* Kolom JSONB `layout_parsing_result` menyimpan mentahan respons Pipeline OCR (termasuk data tilt).
* Kolom JSONB `processing_logs` menyimpan log terperinci tiap tahapan ekstraksi.
2. Item lama yang terkait dengan dokumen di tabel `ocr_items` dihapus untuk menghindari data usang, kemudian data item hasil ekstraksi baru dimasukkan kembali via bulk-insert.