138 lines
7.8 KiB
Markdown
138 lines
7.8 KiB
Markdown
# API Prediksi Harga Cabai
|
|
|
|
API ini menggunakan Flask, application factory, dan Blueprint pada prefix `/api/v1`. Model LSTM tidak dilatih ulang dan artifact di `models/` hanya dimuat satu kali saat startup.
|
|
|
|
## Menjalankan di Windows
|
|
|
|
Dari folder `api` dengan environment Python proyek:
|
|
|
|
```powershell
|
|
pip install -r requirements.txt
|
|
python main.py
|
|
```
|
|
|
|
Untuk menghentikan proses backend lama dan menjalankannya kembali dengan auto-reloader:
|
|
|
|
```powershell
|
|
cd C:\PrediksiHargaCabe\api
|
|
powershell -ExecutionPolicy Bypass -File .\restart_backend.ps1
|
|
```
|
|
|
|
Skrip tersebut menghentikan proses backend proyek pada port `8000`, lalu menjalankan Flask dengan
|
|
`FLASK_DEBUG=1`. Perubahan file Python akan dimuat otomatis selama proses tetap berjalan.
|
|
|
|
Environment yang tersedia:
|
|
|
|
- `MODELS_DIR`: lokasi folder model, default `api/models`.
|
|
- `DATA_DIR`: lokasi data CSV historis, default folder `api` karena file aktual berada di sana.
|
|
- `DATA_SOURCE`: sumber data aktif, `mysql` (default) atau `csv`.
|
|
- `MYSQL_HOST`, `MYSQL_PORT`, `MYSQL_DATABASE`, `MYSQL_USER`, `MYSQL_PASSWORD`: koneksi database MySQL melalui PyMySQL.
|
|
- `MYSQL_CONNECT_TIMEOUT`: batas waktu koneksi database dalam detik.
|
|
- `PRICE_UNIT_SCALE`: pengali nilai CSV ke Rupiah penuh, default `1000`; sumber CSV penelitian menyimpan `12.000` sebagai 12 ribu Rupiah.
|
|
- `CORS_ORIGINS`: daftar origin dipisahkan koma; default `http://127.0.0.1:5173,http://localhost:5173` untuk development dengan session login.
|
|
- `ALLOWED_HORIZONS`: default `1,3,7`.
|
|
- `MAX_HORIZON`: default `7`.
|
|
|
|
## Endpoint
|
|
|
|
- `GET /api/v1/health`
|
|
- `GET /api/v1/commodities`
|
|
- `GET /api/v1/markets?commodity=rawit`
|
|
- `GET /api/v1/history?commodity=rawit&market=pasar_tanjung&days=90`
|
|
- `GET /api/v1/predict?commodity=rawit&market=pasar_tanjung&horizon=7`
|
|
- `GET /api/v1/predict/map?commodity=rawit&horizon=7`
|
|
|
|
Semua error memakai format `{ "error": { "code": "...", "message": "...", "details": {} } }`.
|
|
|
|
## Format data historis
|
|
|
|
Dengan `DATA_SOURCE=mysql`, loader membaca tabel `price_history` dan melakukan join ke `commodities` serta `markets` menggunakan `slug`. Dengan `DATA_SOURCE=csv`, loader membaca file aktual `harga_cabai_rawit_*.csv`, `harga_cabe_merah_besar_*.csv`, dan `harga_cabai_merah_keriting_*.csv` dari `DATA_DIR`. Pada mode CSV, kolom tanggal dapat bernama `Tanggal`, dan kolom pasar memakai nama `Pasar_Tanjung`, `Pasar_Kreongan`, `Pasar_Tegal_Besar`, `Pasar_Mangli`, serta `Pasar_Wirolegi`. Harga bernilai `0`, `N/A`, atau kosong diperlakukan sebagai `NaN`; hanya gap internal sampai dua titik yang diinterpolasi linear.
|
|
|
|
## Menghubungkan ke MySQL phpMyAdmin
|
|
|
|
1. Pastikan service MySQL pada XAMPP/Laragon/WAMP sedang berjalan.
|
|
2. Pastikan database yang dipakai adalah `prediksicabe`.
|
|
3. Pastikan tabel `commodities`, `markets`, dan `price_history` ada.
|
|
4. Isi data harga ke `price_history`; seed SQL hanya mengisi master komoditas dan pasar.
|
|
5. Set environment variable pada terminal; file `.env.example` hanya template dan belum dibaca otomatis oleh aplikasi.
|
|
6. Isi `MYSQL_USER` dan `MYSQL_PASSWORD` sesuai akun MySQL lokal.
|
|
7. Jalankan API dari folder `api`.
|
|
|
|
Untuk membuat akun admin, daftar dahulu melalui endpoint register atau halaman register website, lalu ubah role akun tersebut melalui phpMyAdmin:
|
|
|
|
```sql
|
|
UPDATE users
|
|
SET role = 'admin'
|
|
WHERE email = 'email-admin-anda@example.com';
|
|
```
|
|
|
|
Password tetap disimpan dalam bentuk hash oleh API; jangan memasukkan password ke query SQL.
|
|
|
|
Endpoint admin yang sudah tersedia:
|
|
|
|
- `GET /api/v1/admin/overview`
|
|
- `GET /api/v1/admin/models`
|
|
- `GET /api/v1/admin/scraping/logs`
|
|
- `GET /api/v1/admin/scraping/logs/<log_id>/records`
|
|
- `POST /api/v1/admin/scraping/jobs`
|
|
- `GET /api/v1/admin/scraping/jobs/<job_id>`
|
|
- `POST /api/v1/admin/preprocessing/jobs` dengan `{"scraping_log_id": 123}` untuk memproses satu riwayat scraping yang dipilih.
|
|
- `GET /api/v1/admin/preprocessing/jobs/<job_id>`
|
|
- `DELETE /api/v1/admin/scraping/history`
|
|
- `DELETE /api/v1/admin/scraping/logs/<log_id>`
|
|
- `POST /api/v1/admin/scraping/refresh`
|
|
- `GET /api/v1/prices?page=1&limit=25` (admin)
|
|
|
|
Panel admin sekarang memiliki menu dataset dan pembaruan data manual. Endpoint refresh mengambil tabel SISKAPERBAPO, menyimpan observasi baru atau perubahan ke `price_history`, dan mencatat proses ke `scraping_logs`. Endpoint records menampilkan tanggal, komoditas, pasar, dan harga dari hasil scraping terakhir. Data baru tidak melatih ulang model; model deployment yang sudah aktif tetap digunakan untuk inference. Scheduler, preprocessing aktif, dan training aktif tetap tidak dijalankan.
|
|
|
|
Endpoint jobs menerima opsi `commodities` (array slug), `start_date`, dan `end_date`. Jika rentang tanggal dipilih, jumlah observasi dihitung otomatis sebagai hasil tanggal yang tersedia dikalikan pasar yang berhasil dibaca. Proses berjalan di background dan dapat dipantau melalui endpoint status job; panel admin menampilkan progress bar. Penghapusan history menghapus log scraping dan baris `price_history` dengan `source = 'siskaperbapo'` saja, sehingga data hasil import CSV tetap aman.
|
|
Setiap observasi scraping memiliki `scraping_log_id`, sehingga tombol trash pada baris log menghapus hanya log tersebut dan data yang dihasilkan proses itu. Jika database lama digunakan, application factory otomatis menambahkan kolom dan mengaitkan observasi SISKAPERBAPO lama ke log terakhir.
|
|
|
|
Konfigurasi scraper:
|
|
|
|
```powershell
|
|
$env:SISKAPERBAPO_URL="https://siskaperbapo.jatimprov.go.id/harga-komoditas"
|
|
$env:SISKAPERBAPO_COMMODITY_PARAM="komoditas"
|
|
$env:SISKAPERBAPO_TIMEOUT="30"
|
|
$env:SISKAPERBAPO_COMMODITIES="rawit"
|
|
```
|
|
|
|
Tahap awal scraper dibatasi ke `rawit` agar tidak mengisi komoditas lain dengan data yang salah apabila halaman sumber mengabaikan parameter komoditas. Tambahkan slug lain setelah URL/parameter sumber sudah diverifikasi. Jika struktur halaman SISKAPERBAPO berubah atau parameter komoditas berbeda, endpoint akan mengembalikan error dan mencatat detailnya sebagai scraping gagal; sistem tidak membuat data sukses palsu.
|
|
|
|
Untuk mengisi tabel `price_history` dari CSV penelitian secara manual satu kali:
|
|
|
|
```powershell
|
|
cd C:\PrediksiHargaCabe\api
|
|
C:/Users/alfia/anaconda3/envs/tf_gpu39/python.exe scripts/import_csv_to_mysql.py
|
|
```
|
|
|
|
Script ini hanya melakukan import manual dan **tidak membuat scheduler atau auto-scraping**. Jika dijalankan lagi, constraint unik tanggal/komoditas/pasar mencegah duplikat dan nilai akan diperbarui.
|
|
|
|
Contoh PowerShell:
|
|
|
|
```powershell
|
|
cd C:\PrediksiHargaCabe\api
|
|
$env:DATA_SOURCE="mysql"
|
|
$env:MYSQL_DATABASE="prediksicabe"
|
|
$env:MYSQL_USER="root"
|
|
$env:MYSQL_PASSWORD=""
|
|
python main.py
|
|
```
|
|
|
|
Uji koneksi melalui endpoint:
|
|
|
|
```text
|
|
http://127.0.0.1:8000/api/v1/history?commodity=rawit&market=pasar_tanjung&days=30
|
|
```
|
|
|
|
Jika response menyatakan tidak ada data, koneksi berhasil tetapi baris harga belum ada untuk kombinasi tersebut. Jika muncul `Gagal membaca database MySQL`, periksa service MySQL, port, username, password, dan nama database.
|
|
|
|
## Asumsi dan keterbatasan
|
|
|
|
- Koordinat pada `app/data/markets.json` berasal dari spesifikasi dan perlu diverifikasi manual sebelum dipakai sebagai titik final peta.
|
|
- `keriting` memakai `time_step=14`. Config dan H5 yang tersedia untuk Kreongan, Mangli, dan Wirolegi sekarang sudah memiliki input 14 langkah dan dapat dipakai oleh registry.
|
|
- Folder model Keriting untuk Tanjung dan Tegal Besar tidak lengkap dan dilaporkan pada `/api/v1/health`.
|
|
- Evaluasi model yang tersedia adalah satu hari ke depan. Prediksi 7, 24, dan 30 hari memakai recursive forecasting, sehingga error dapat menumpuk dan tidak boleh dianggap memiliki akurasi satu hari.
|
|
- Harga API adalah Rupiah penuh. Scaler/model yang dilatih dalam ribuan Rupiah harus disesuaikan saat artifact inference divalidasi; CSV historis juga harus mengikuti unit yang sama dengan proses training.
|
|
- Semua nilai positif yang disimpan ke `price_history` menggunakan Rupiah penuh. Scraper menormalkan nilai sumber yang masih dalam ribuan sebelum menyimpannya; nilai `0` diperlakukan sebagai data kosong.
|