# 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. |