feat: update backend OCR parser, web app, mobile app camera/preview UI, tests, and documentation with sample images
This commit is contained in:
1 parent
aa3233e411
commit
bdb3a49742
71 files changed
+3360
-764
No files matched your search
@@ -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.
|
||||
Reference in new issue
Block a user