Files
pfm-ocr/docs/workflow_detail_aplikasi.md

315 lines
24 KiB
Markdown

# Analisis Detail Alur Kerja Aplikasi PFM OCR (Mobile & Backend API)
Dokumen ini memuat analisis mendalam mengenai seluruh alur kerja (*workflow*) sistem **Prima Fresh Mart (PFM) Scanner OCR v2**, dengan memfokuskan analisis secara eksklusif pada interaksi antara **Aplikasi Mobile Flutter** dan **Layanan Backend API** (API Gateway Next.js, Database PostgreSQL, Pipeline OCR FastAPI, vLLM Server, dan Server Klasifikasi YOLO).
Asumsi analisis ini didasarkan sepenuhnya pada peniadaan antarmuka web (*web interface/dashboard*), sehingga sistem diposisikan murni sebagai aplikasi mobile tersinkronisasi dengan backend OCR.
---
## 1. Arsitektur Aliran Data (Tanpa Web Interface)
Berikut adalah diagram alur kerja yang menggambarkan interaksi ujung-ke-ujung (*end-to-end*) antara aplikasi mobile Flutter dan komponen backend:
```mermaid
graph TD
%% Sisi Client (Flutter)
subgraph Client [Aplikasi Mobile Flutter]
Start([Mulai: Supir/Operator Scan DO]) --> Capture[Ambil Foto via Camera API]
Capture --> GPS[Ambil Geoposition via Geolocator]
GPS --> BlurCheck{Cek Kualitas via BlurDetector}
BlurCheck -- Blurry / Skor < 80 --> Capture
BlurCheck -- Tajam / Skor >= 80 --> Queue[Tambahkan ke Pending Queue]
Queue --> Upload[Kirim Multipart POST ke /api/v1/documents/upload]
%% Polling
Upload --> StartPoll[Mulai Polling GET /api/v1/documents]
StartPoll --> PollRequest{Cek Dokumen di List?}
PollRequest -- Belum Parsed / Timeout --> StartPoll
PollRequest -- Berhasil Parsed --> StateUpdate[Perbarui UI via Riverpod]
%% Editor & PDF
StateUpdate --> Editor[Manual Correction & Validation]
Editor --> SaveEdit[Kirim PUT ke /api/v1/documents/:id]
SaveEdit --> PrintPDF[Cetak Label / A4 PDF Natively]
PrintPDF --> End([Selesai])
end
%% Sisi Backend (Docker Stack)
subgraph Backend [Backend API & AI Engine]
%% Nginx & API Gateway
Upload --> Nginx[Nginx Router :8000]
Nginx --> UploadAPI[Next.js API Gateway :3000]
UploadAPI --> DBInsert[SQL: Simpan Dokumen Awal - parsed=false]
DBInsert --> TriggerParse[Panggil Internal POST /api/parse]
%% Pipeline API
TriggerParse --> Pipeline[FastAPI Pipeline API :8090]
Pipeline --> Deskew{Hitung Sudut Rata-rata - Tilt > 1.0 deg?}
Deskew -- Ya --> Unwarp[Unwarping via UVDoc & Re-Run]
Deskew -- Tidak --> LayoutDet[Layout Detection via PP-DocLayoutV3]
Unwarp --> LayoutDet
%% Text Extraction & LLM
LayoutDet --> TextOCR[PaddleOCR Text Recognition]
TextOCR --> FormatMD[Format Hasil ke Markdown]
FormatMD --> VllmProxy[vLLM Server: PaddleOCR-VL-1.6 :8118]
VllmProxy --> SendMD[Kembalikan Hasil Teks Terstruktur]
%% Next.js Post-processing
SendMD --> ParserTS[parseDOMetadata - Regex Extraction]
ParserTS --> StoreMatch[resolveStoreFromText - Fuzzy Toko]
StoreMatch --> TripleCheck[Intelligent Triple-Check SKU / Nama / Unit]
TripleCheck --> DBSave[SQL: UPDATE documents - parsed=true & ocr_items]
DBSave --> PollRequest
SaveEdit --> Nginx
end
```
---
## 2. Analisis Alur Kerja Sisi Client (Aplikasi Mobile Flutter)
Aplikasi mobile Flutter bertindak sebagai gerbang pertama pengumpulan data di lapangan oleh supir/operator pengiriman.
### A. Pengambilan Gambar & Geolokasi
1. Operator menggunakan modul kamera bawaan aplikasi Flutter untuk memotret kertas dokumen *Delivery Order* (DO).
2. Bersamaan dengan pengambilan foto, aplikasi mengakses layanan lokasi perangkat menggunakan paket `geolocator` untuk mengambil titik koordinat GPS (`latitude` dan `longitude`). Koordinat ini akan disematkan sebagai penanda lokasi pengunggahan (*geolocation tags*).
### B. Pendeteksian Blur (*Blur Detection*) secara Real-time
Untuk memastikan gambar yang dikirim memiliki kualitas yang layak dibaca oleh mesin OCR, aplikasi menjalankan deteksi ketajaman gambar via kelas [BlurDetector](file:///d:/Client/Data%20Bisnis%20Solusi/app-pfm-ocr-v2/lib/features/camera/blur_detector.dart) sebelum diunggah:
* **Isolate Execution**: Agar antarmuka (UI) tidak mengalami patah-patah (*stuttering*), pemrosesan gambar dipindahkan ke Dart Isolate terpisah menggunakan fungsi `compute`.
* **Preprocessing Gambar**:
1. Gambar asli diperkecil skalanya (*resized*) menjadi lebar **300 piksel** untuk mempercepat kalkulasi.
2. Dilakukan pemotongan area tengah sebesar **60%** (*center crop*), dengan tujuan mengabaikan latar belakang (meja, lantai) dan fokus sepenuhnya pada teks dokumen.
3. Mengaplikasikan *Gaussian Blur* dengan radius `1` untuk menyaring noise sensor kamera.
4. Mengonversi gambar hasil crop ke format skala abu-abu (*grayscale*).
* **Algoritma Varians Laplacian**:
* Aplikasi memindai piksel gambar dan menerapkan matriks konvolusi Laplacian 3x3 untuk mendeteksi tepi (*edge detection*):
$$\text{Laplacian} = 4 \times \text{Center} - \text{Left} - \text{Right} - \text{Top} - \text{Bottom}$$
* Menghitung nilai varians dari seluruh piksel Laplacian. Nilai varians yang tinggi mengindikasikan banyaknya perubahan intensitas piksel yang tajam (tepi teks jelas), sedangkan varians rendah berarti transisi piksel halus (gambar blur).
* **Penentuan Batas Kelolosan (Thresholding)**:
* Varians mentah dipetakan ke skala 0 hingga 100 dengan batas skor kelolosan sebesar **80.0** (setara dengan nilai varians mentah sebesar **400.0** dari batas maksimum **500.0**).
* Jika gambar di bawah skor 80.0, aplikasi memblokir proses unggah dan meminta pengguna mengambil ulang foto.
### C. Antrean Unggah & Sinkronisasi Latar Belakang (*Background Polling*)
Setelah lolos uji blur, berkas gambar didaftarkan ke state management Riverpod melalui kelas `PendingDocumentsNotifier`:
1. Status dokumen diatur menjadi `PendingDocumentStatus.uploading`.
2. Aplikasi mengirimkan berkas gambar bersama koordinat GPS menggunakan objek `FormData` melalui pustaka `Dio` HTTP Client ke endpoint backend:
`POST /api/v1/documents/upload`
3. Begitu API mengonfirmasi bahwa gambar sukses diunggah ke server (HTTP status `201`), aplikasi mengubah status menjadi `PendingDocumentStatus.processing`.
4. **Mekanisme Polling**:
Aplikasi Flutter memulai loop polling otomatis di latar belakang dengan melakukan request `GET /api/v1/documents` setiap **2 detik**.
* Aplikasi membandingkan ID dokumen yang sedang diunggah dengan daftar dokumen yang dikembalikan oleh server.
* Server hanya akan menampilkan dokumen pada daftar tersebut jika kolom `parsed` di database sudah bernilai `true`.
* Loop polling dibatasi maksimal **60 kali percobaan** (timeout 2 menit). Begitu terdeteksi di server, status diubah menjadi `PendingDocumentStatus.success` dan data terisi ke UI.
### D. Koreksi Manual & Standardisasi Sisi Editor
Jika terdapat karakter OCR yang tidak sempurna, operator dapat melakukan penyelarasan langsung pada aplikasi mobile (`editor_screen.dart`):
* Menyesuaikan nomor dokumen (PO, SO, DO) atau detail pengiriman (Plat truk, nama driver, nama penerima).
* Melakukan penyuntingan baris item barang (SKU, kuantitas/banyak, jumlah unit).
* Memberi tanda bendera (`is_flagged`) dan catatan khusus (`remark`) jika ada ketidaksesuaian barang.
* Menyimpan seluruh perubahan kembali ke database backend melalui endpoint:
`PUT /api/v1/documents/[id]`
### E. Ekspor PDF & Cetak Struk Nativel
Setelah data terverifikasi bersih, operator dapat langsung mencetak tanda terima fisik:
1. Pustaka `pdf` menyusun tata letak lembaran A4 yang memuat logo PFM, ringkasan PO/SO/DO, informasi pengiriman (Driver, plat nomor, alamat toko), tabel barang hasil OCR yang sudah bersih, serta kolom tanda tangan basah untuk supir dan penjaga toko.
2. Pustaka `printing` memicu manajer cetak bawaan sistem operasi (*Native OS Print Spooler*) untuk mencetak dokumen langsung ke printer thermal/portable via Bluetooth/Wi-Fi atau menyimpannya sebagai berkas PDF lokal.
---
## 3. Analisis Alur Kerja Sisi Backend (Gateway API & Database)
Backend bertindak sebagai API gateway dan orkestrator yang mengarahkan gambar dokumen ke unit pemrosesan AI, melakukan sanitasi, dan menyimpannya ke database relasional.
### A. Penerimaan Berkas & Inisialisasi Database (`/api/v1/documents/upload`)
Ketika file diunggah dari aplikasi mobile, Next.js API Gateway memprosesnya sebagai berikut:
1. Menyimpan berkas gambar fisik ke direktori `/uploads` dengan nama unik terstempel waktu (`Date.now() - safe_name.jpg`).
2. Menghitung nilai hash SHA-256 dari berkas biner untuk melacak integritas data dan mencegah duplikasi konten.
3. Melakukan insert record ke tabel `documents` dengan kolom `parsed = false` dan menyimpan metadata awal beserta koordinat GPS.
4. Memicu eksekusi parsing secara sinkron dan cepat dengan menembak API internal `/api/parse` membawa payload nama file.
### B. Alur Ekstraksi & Pipeline OCR (`/api/parse`)
Pada endpoint ini, Next.js meneruskan base64 gambar ke kontainer Python `paddleocr-pipeline-api` (FastAPI pada port `8090`) yang menjalankan pipeline pengolahan gambar berbasis GPU:
#### 1. Deteksi Sudut & Koreksi Kemiringan Otomatis (*Auto-Deskew & Unwarping*)
Dokumen yang difoto secara manual oleh supir sering kali miring atau melengkung.
* Sistem mendeteksi poligon koordinat blok teks menggunakan model **PP-LCNet_x1_0_doc_ori** untuk klasifikasi arah orientasi dokumen.
* Sistem menghitung kemiringan rata-rata teks secara trigonometri dari koordinat sudut poligon blok teks (`block_polygon_points`):
$$\text{Kemiringan Rata-rata} = \frac{1}{N} \sum_{i=1}^{N} \arctan\left(\frac{y_1 - y_0}{x_1 - x_0}\right) \times \frac{180}{\pi}$$
* Jika rata-rata kemiringan melampaui batas toleransi **1.0 derajat**, backend akan menolak hasil bacaan awal dan memicu pemrosesan ulang dengan mengaktifkan modul unwarping dokumen **UVDoc** (`useDocUnwarping: true`). Modul ini merentangkan kembali kertas yang melengkung/miring agar teks sejajar horizontal sebelum dibaca ulang.
#### 2. Segmentasi Tata Letak & Deteksi Teks
* Model **PP-DocLayoutV3** membagi area dokumen ke dalam beberapa tipe tata letak (Tabel, Paragraf, Judul, Header, Footer).
* Untuk memastikan informasi penting di bagian tepi kertas (seperti Nomor PO, SO, dan Tanggal pengiriman) tidak dibuang oleh OCR, parameter `markdown_ignore_labels` pada `pipeline_config_vllm.yaml` disetel kosong (`[]`).
* Teks dibaca per wilayah menggunakan engine **PaddleOCR v6** dan disusun kembali menjadi representasi teks terstruktur berformat Markdown mentah.
* Pipeline FastAPI mengirimkan teks Markdown tersebut ke kontainer **vLLM Server** (port `8118`) yang memuat model lokal **PaddleOCR-VL-1.6-0.9B** untuk interpretasi kontekstual lanjutan guna menyusun kembali kalimat-kalimat yang terpecah akibat deteksi tabel.
---
## 4. Pengolahan Data Lanjutan & Validasi Lapis Dua (Next.js)
Setelah menerima respons Markdown terstruktur dari pipeline OCR, Next.js Gateway melakukan serangkaian pembersihan data menggunakan aturan logika bisnis yang ketat:
### A. Ekstraksi Informasi Utama (Regex Matching)
Fungsi [parser.ts](file:///d:/Client/Data%20Bisnis%20Solusi/app-pfm-ocr-v2/backend/pfm-web-app/src/utils/parser.ts) menggunakan ekspresi reguler (Regex) untuk menarik data dari teks mentah:
* **No PO (Purchase Order)**:
* Mengekstrak kode PO menggunakan pola regex `/No\.?\s*PO\s*[:\-]\s*([A-Z0-9\-\/]+)/i`.
* Mengoreksi kegagalan bacaan OCR yang sering menyatukan karakter (*fused digits*) seperti `PO1207000019` dengan memisahkannya kembali menjadi format standar `PO/YY/NNNN+` (di mana `YY` disamakan dengan tahun pada tanggal dokumen).
* **No SO & DO**:
* Mencari pola 10 digit angka numerik (yang biasanya diawali angka `16` atau `10`).
* **Plat Nomor Kendaraan**:
* Mencari format plat nomor Indonesia dengan pola `[KODE_WILAYAH] [NOMOR_POLISI] [SERI_AKHIR]`. Huruf depan divalidasi ke daftar kode plat resmi (seperti `B`, `D`, `F`, `DK`, dll.).
* **Koreksi Layout Tergeser (*Shifted Layout Correction*)**:
* Kadang-kadang teks label ("Tanggal :", "No. SO :") tercetak terpisah jauh di atas nilainya karena tata letak visual bergeser. Jika parser mendeteksi nilai-nilai kunci kosong tetapi menemukan deretan angka 10-digit berurutan di baris-baris bawahnya, parser otomatis melakukan realinyasi indeks baris untuk menarik nilai ke kunci yang sesuai.
### B. Sanitasi & Standardisasi Data (`sanitizeParsedMetadata`)
Semua metadata yang berhasil ditarik disaring kembali untuk menjamin kepatuhan data sebelum masuk ke database:
* **Tanggal**: Harus lolos pola `"dd Month yyyy"` dengan nama bulan bahasa Inggris lengkap (contoh: `"02 February 2026"`). Jika OCR menghasilkan tanggal yang salah format atau di luar rentang wajar (2010 s.d. tahun depan), nilainya diganti menjadi `"Not Found"` agar memaksa operator melakukan koreksi manual, alih-alih melakukan *auto-fallback* ke tanggal hari ini yang berpotensi memalsukan tanggal dokumen asli.
* **SO & DO**: Wajib berupa digit angka murni sepanjang 7 s.d. 12 karakter. Sistem otomatis mengoreksi kesalahan visual karakter OCR yang umum terjadi (*visual digit correction*):
* Huruf `O` atau `o` di tengah angka $\rightarrow$ `0`
* Huruf `I`, `i`, `l`, atau simbol `|` di tengah angka $\rightarrow$ `1`
* Huruf `S` atau `s` $\rightarrow$ `5`
* Huruf `Z` atau `z` $\rightarrow$ `2`
* Huruf `B` atau `b` $\rightarrow$ `8`
* Angka bulan salah seperti `012` atau `112` diubah menjadi `02` (Februari).
### C. Pencocokan Toko Penerima (*Fuzzy Store Matching*)
Toko penerima ditentukan secara cerdas dari teks area customer (`customerInfo`):
1. Sistem mengambil teks mentah customer, melakukan normalisasi ke huruf kecil, membuang tanda baca, dan memotongnya menjadi token kata-kata unik (*tokenization*).
2. Sistem menyaring kata umum atau kata henti (*stop words*) seperti *"dan", "dki", "area", "yth", "kepada", "jalan", "raya", "kelurahan", "kecamatan"*.
3. Token tersebut kemudian diiriskan (*intersection*) dengan seluruh data toko yang terdaftar di database `store_master`.
4. Toko yang memiliki kecocokan minimal **2 token** dengan skor bobot tertinggi dipilih secara otomatis. Alamat resmi dan nama toko dari database kemudian disematkan pada kolom `orderUntuk` dan `alamat`.
### D. Sistem Validasi Barang Pintar (*Intelligent Triple-Check Matcher*)
Baris barang pada tabel DO rentan mengalami kesalahan pembacaan kode SKU maupun nama item. Next.js mengatasinya melalui mekanisme **Triple-Check Matcher**:
1. Sistem memuat seluruh master barang dari database `sku_master` (meliputi nomor SKU, nama item, standar jumlah inner, dan jenis kemasan outer).
2. Setiap baris barang hasil pembacaan tabel OCR dibandingkan dengan master menggunakan bobot gabungan (*Weighted Combined Score*):
* **Bobot SKU (50%)**: Skor kemiripan antara kode SKU hasil pembacaan dan SKU master menggunakan algoritma Levenshtein Distance. Jika ditemukan kecocokan SKU 100% presisi, sistem langsung menetapkan item tersebut sebagai kecocokan mutlak.
* **Bobot Nama Barang (40%)**: Skor kemiripan string nama barang hasil pembacaan dengan nama barang master.
* **Bobot Kemasan & Satuan (10%)**: Bonus kecocokan jika satuan kuantitas yang dibaca (misal: `KRG`, `BOX`, `BAG` sebagai kemasan luar/outer) atau harga unit (`PAC`, `PC`, `KG` sebagai kemasan dalam/inner) sesuai dengan definisi jenis kemasan item tersebut di database master.
3. **Penyelarasan & Pembersihan**:
* Jika skor kecocokan gabungan mencapai **$\ge$ 0.6**, baris tersebut dianggap valid. Sistem otomatis mengganti nomor SKU dan nama barang dengan data master yang resmi, serta menstandardisasi satuan kuantitasnya (contoh: kuantitas angka murni `"15"` diautocomplete menjadi `"15 KRG"` berdasarkan jenis kemasan luar item bebek/ayam).
* Jika skor kecocokan di bawah 0.6 tetapi nomor SKU yang terbaca merupakan angka valid **8 digit**, baris item tetap dipertahaman (untuk mendukung pengenalan SKU baru yang belum terdaftar di database master).
* Jika tidak ada kecocokan dan SKU tidak valid 8-digit, baris tersebut langsung **dibuang** karena diidentifikasi sebagai kebisingan layout tabel (*table noise* seperti garis pembatas tabel, tanda tangan supir, atau coretan kupon).
---
## 5. Alur Kerja Deteksi & Klasifikasi Produk (Scan Produk Tunggal)
Selain mengolah dokumen DO utuh, aplikasi mobile juga mendukung pemindaian produk/kemasan secara individual untuk memverifikasi kecocokan barang dan mendeteksi tanggal kedaluwarsa (*expiry date*).
```mermaid
graph TD
StartScan([Mulai: Scan Produk Tunggal]) --> SendImg[POST /api/scan-pfm dengan Base64 Gambar]
SendImg --> ForwardAPI[Next.js Meneruskan ke Python FastAPI :8120]
subgraph PyServer [FastAPI Classify-OCR Server]
ForwardAPI --> YOLO[1. YOLO Classification: Prediksi Kelas Produk]
YOLO --> Paddle[2. PaddleOCR: Deteksi & Ekstraksi Seluruh Baris Teks]
Paddle --> ExpExtract[3. extract_expired_date: Regex Pencarian Tanggal]
ExpExtract --> VisualDraw[4. Menggambar Bounding Box: Teal=Teks, Amber=EXP]
VisualDraw --> CropExp[5. Crop Region Tanggal Exp sebagai Base64]
CropExp --> SpotAPI[6. Call Layout-Parsing untuk Spotting Image]
end
PyServer --> ReturnData[Kembalikan Hasil OCR & Visualisasi ke Next.js]
ReturnData --> FuzzyMatch[Levenshtein Match: Hasil YOLO vs sku_master]
FuzzyMatch --> Top5[Ambil Top 5 Hasil dengan Skor Sim > 0.1]
Top5 --> RespondMobile[Kirim Hasil Akhir JSON ke Flutter Client]
RespondMobile --> EndScan([Selesai])
```
### A. Klasifikasi YOLO & Deteksi OCR
1. Aplikasi Flutter mengambil foto produk tunggal dan mengirimkannya ke endpoint Next.js `/api/scan-pfm`, yang diteruskan ke server FastAPI khusus (`classify_ocr_server.py` pada port `8120`).
2. **YOLO Classification**: Server memuat bobot model YOLO yang telah dilatih (`produk-pfm-classifier-26n-*e-*.pt`) untuk memprediksi kelas produk (nama produk) beserta probabilitas keyakinannya (*confidence score*).
3. **PaddleOCR Text Extraction**: Server menjalankan deteksi teks untuk mengambil seluruh baris tulisan yang tercetak pada label/kemasan produk.
### B. Ekstraksi & Pembersihan Tanggal Kedaluwarsa (*Expiry Date Spotting*)
Pendeteksian tanggal kedaluwarsa pada kemasan produk sangat menantang karena kualitas cetakan dot-matrix pada plastik kemasan sering kali buruk. Server FastAPI menggunakan serangkaian aturanRegex berlapis:
* **Pencarian Kata Kunci EXP**: Mencari kata kunci kedaluwarsa seperti `EXP`, `EXPIRY`, `TGL EXP`, `BBD`, `BEST BEFORE`, atau `BB`.
* **Regex Format Tanggal**:
* Pola tanggal rapat tanpa spasi/karakter pembatas (contoh: `05032027` via `DDMMYYYY_RE`).
* Pola tanggal dengan pembatas spasi, titik, hubung, atau garis miring (contoh: `05-03-2027` via `DD_MM_YYYY_RE`).
* Pola tanggal langsung setelah kata kunci (contoh: `BB05032027` via `BB_ATTACHED_DATE_RE`).
* **Pembersihan Noise Karakter Khusus**:
* Mengganti noise cetakan tanda kurung `()` atau karakter tidak sengaja `1)` menjadi angka `0`.
* Memperbaiki kesalahan interpretasi huruf/angka kembar seperti `B8`, `8B`, atau `88` menjadi `BB` (Best Before) jika diikuti oleh digit angka.
* Mengoreksi kegagalan bacaan bulan cetak rapat seperti `012` atau `112` di belakang tanggal menjadi bulan `02` (Februari) (contoh: `020122027` dikoreksi menjadi `02/02/2027`).
* **Visualisasi & Pemotongan Area (*Visual Cropping*)**:
* Server memotong poligon area koordinat teks kedaluwarsa yang berhasil diidentifikasi dan mengembalikannya sebagai gambar thumbnail terpotong berformat Base64 (`expired_date_crop_base64`). Hal ini berguna untuk validasi visual oleh pengguna pada aplikasi mobile.
* Menggambar kotak pembatas (*bounding box*) pada gambar produk: kotak **Teal** untuk tulisan biasa, dan kotak **Amber (oranye)** dengan label "EXP" untuk menyoroti letak teks tanggal kedaluwarsa.
### C. Fuzzy Similarity Matching (Next.js)
Begitu Next.js menerima hasil klasifikasi YOLO dan ekstraksi OCR:
1. Sistem menghitung kemiripan string menggunakan jarak Levenshtein antara prediksi nama produk YOLO dengan data `nama_item` pada tabel `sku_master`.
2. Mengembalikan **Top 5 kecocokan terbaik** yang memiliki skor kemiripan $>0.1$, dengan menetapkan item teratas sebagai `isBestMatch = true`.
3. Seluruh hasil klasifikasi produk, kecocokan master SKU, teks kedaluwarsa yang dideteksi, beserta visualisasi gambar kotak pembatas dikirimkan kembali ke aplikasi mobile Flutter.
---
## 6. Spesifikasi Skema Database PostgreSQL
Berikut adalah spesifikasi detail tabel-tabel PostgreSQL yang digunakan dalam workflow aplikasi mobile dan backend:
### A. Tabel `documents`
Menyimpan berkas informasi utama dokumen DO yang diunggah.
| Nama Kolom | Tipe Data | Deskripsi |
| :--- | :--- | :--- |
| `id` | SERIAL (PK) | ID unik dokumen (Auto-increment). |
| `filename` | VARCHAR(255) | Nama file unik yang tersimpan di `/uploads` (Unique). |
| `upload_time` | TIMESTAMP | Tanggal dan waktu pengunggahan dokumen. |
| `size` | INTEGER | Ukuran berkas gambar dalam satuan bytes. |
| `parsed` | BOOLEAN | Penanda status apakah dokumen telah selesai di-OCR (`true`/`false`). |
| `metadata` | JSONB | Menyimpan hasil ekstraksi terstruktur (PO, SO, DO, Tanggal, Plat nomor, Driver). |
| `layout_parsing_result` | JSONB | Menyimpan mentahan respons JSON dari kontainer Pipeline OCR FastAPI. |
| `is_sample` | BOOLEAN | Menandai apakah dokumen merupakan data contoh bawaan sistem (`true`/`false`). |
| `file_hash` | VARCHAR(64) | Hash SHA-256 berkas gambar untuk mencegah duplikasi data biner. |
| `processing_logs` | JSONB | Log jejak audit detail dari seluruh tahapan pemrosesan sistem OCR. |
| `latitude` | NUMERIC | Titik koordinat garis lintang (GPS) lokasi unggah dari perangkat mobile. |
| `longitude` | NUMERIC | Titik koordinat garis bujur (GPS) lokasi unggah dari perangkat mobile. |
### B. Tabel `ocr_items`
Menyimpan baris item produk yang tertera pada tabel dokumen DO. Terikat relasi *One-to-Many* terhadap tabel `documents`.
| Nama Kolom | Tipe Data | Deskripsi |
| :--- | :--- | :--- |
| `id` | SERIAL (PK) | ID unik baris item (Auto-increment). |
| `document_id` | INTEGER (FK) | Relasi kunci asing merujuk ke `documents(id)` dengan aksi `ON DELETE CASCADE`. |
| `row_index` | INTEGER | Nomor urut baris tabel item dalam satu dokumen. |
| `kode_barang_original` | VARCHAR(255) | Kode SKU barang asli hasil pembacaan awal mesin OCR sebelum dicocokkan. |
| `kode_barang` | VARCHAR(255) | Kode SKU barang hasil verifikasi akhir / standardisasi master. |
| `nama_barang` | VARCHAR(255) | Nama/deskripsi barang yang dikirim. |
| `banyak_original` | VARCHAR(255) | Kuantitas barang asli hasil pembacaan awal OCR. |
| `banyak` | VARCHAR(255) | Kuantitas barang hasil standardisasi (misalnya: `"15 KRG"`). |
| `jumlah_original` | VARCHAR(255) | Nilai total barang asli hasil pembacaan awal OCR. |
| `jumlah` | VARCHAR(255) | Nilai total barang hasil standardisasi (misalnya: `"450 PAC"`). |
| `is_flagged` | BOOLEAN | Penanda bendera untuk perhatian khusus / manual check (`true`/`false`). |
| `remark` | VARCHAR(1000) | Catatan koreksi atau alasan penandaan bendera pada baris item. |
*Catatan: Terdapat constraint `UNIQUE(document_id, row_index)` untuk mencegah tumpang tindih indeks baris.*
### C. Tabel `sku_master`
Menyimpan daftar master barang PFM untuk validasi Triple-Check.
| Nama Kolom | Tipe Data | Deskripsi |
| :--- | :--- | :--- |
| `id` | SERIAL (PK) | ID unik master SKU. |
| `no_sku` | VARCHAR(255) | Nomor SKU barang sepanjang 8 digit angka unik (Unique). |
| `nama_item` | VARCHAR(255) | Deskripsi nama resmi barang. |
| `standar_jumlah` | VARCHAR(255) | Satuan kemasan dalam/inner (contoh: `PAC`, `PC`, `KG`). |
| `jenis_outer` | VARCHAR(255) | Satuan kemasan luar/outer (contoh: `Karung`, `Box`, `Bag`). |
| `created_at` | TIMESTAMP | Tanggal pendaftaran SKU ke database. |
### D. Tabel `store_master`
Menyimpan data master toko Prima Fresh Mart untuk pencocokan tujuan pengiriman DO.
| Nama Kolom | Tipe Data | Deskripsi |
| :--- | :--- | :--- |
| `id` | SERIAL (PK) | ID unik master toko. |
| `nama_toko` | VARCHAR(255) | Nama resmi outlet/toko PFM. |
| `kode_toko` | VARCHAR(255) | Kode unik toko (Unique, contoh: `02-ANC2`). |
| `alamat` | TEXT | Alamat lengkap toko PFM untuk tujuan pengiriman. |
| `created_at` | TIMESTAMP | Tanggal pendaftaran toko ke database. |