Modul Skema Jaringan adalah inti (*core engine*) visual dan kalkulasi spasial-topologis dalam sistem SIPASI (Sistem Informasi Pengelolaan Air dan Skema Irigasi). Modul ini bertanggung jawab atas:
Authoring / Editor Graf Jaringan Irigasi (`/cms/37/`): Kanvas interaktif berbasis SVG untuk mendesain topologi fisik bendung, bangunan bagi, bangunan sadap, saluran (ruas), dan petak tersier dengan penataan koordinat visual (*snap-to-grid*) dan pembentukan konektivitas (*parent-child directed acyclic graph*).
Operational Execution & Decision Support System (`/cms/38/`): Visualisasi skema operasi pembagian air secara real-time, monitoring debit sensor AWLR/SCADA vs debit manual, pembagian alokasi air hilir-ke-hulu (*bottom-up*), kalkulasi Faktor K, dan penjadwalan rotasi air (giliran).
Sistem navigasi SIPASI menggunakan arsitektur *Dynamic Database-Driven Content Dispatching*. Ketika user mengakses URL `/cms/37/`, berikut rantai eksekusi lengkapnya:
[User Browser]
| GET /cms/37/
v
[Config/Routes.php] ---> $routes->add('/cms/(:any)', 'Cms::index/');
|
v
[Controllers/Cms.php::index()]
|
+---> head_navigation() (Render menu navbar)
+---> body_content() (Menentukan isi konten utama)
|
|-- urinext('cms') == 'mdl'? TIDAK ('37')
|-- urinext('cms') == 'sip'? TIDAK ('37')
v
[Models/Cms/CmsContent.php::home()]
|
|-- Query `cms_navigation` WHERE id = '37'
| Row: {
| id: 37,
| title: 'Skema Jaringan',
| view_mode: 'model',
| val: 'Sipasi\\Mdl_skema_jaringan(membuat)'
Method `Mdl_skema_jaringan::home()` membaca `urinext('skema_jaringan')` dan mendispatch ke method yang bersesuaian (`simpan`, `pembagian`, `set_routing`, `debit_ruas_harian`, `simpan_giliran`, dll.).
---
3. Pemetaan Lengkap Method Mdl_skema_jaringan.php
File `app_sipasi/Models/Sipasi/Mdl_skema_jaringan.php` terdiri dari 1376 baris kode dengan total 17 method. Berikut adalah tabel matriks pemetaan lengkap:
Tabel Referensi Method
No
Nama Method
Visibilitas
Trigger / Route
Input Parameter
Output / Return
Tabel DB yang Disentuh (CRUD)
1
`__construct()`
`public`
Instansiasi Model
Sesi CI4, Environment `id_di`
Instance Object
`dd_pt_profil` (R via `polatanam()`)
2
`home()`
`public`
`/cms/sip/skema_jaringan/`
`urinext('skema_jaringan')`
Hasil delegasi method
Tergantung sub-method
3
`simpan_giliran()`
`public`
AJAX POST `/cms/sip/skema_jaringan/simpan_giliran`
`name` (String): ID elemen DOM SVG (`bpengambil_0`, `bangunan_1`, `petak_104`).
`x`, `y` (Float/Int): Koordinat absolut di dalam SVG ViewBox 9600x9600 (di-snap ke grid kelipatan 20px).
`tx`, `ty` (Float/Int): Posisi koordinat offset label teks nomenklatur.
`sumber` (Array of Int): Daftar ID parent node (hulu/upstream).
`goto` (Array of Int): Daftar ID child node (hilir/downstream).
`link` (Integer): Foreign Key ke tabel master:
Jika `bb != 'ter'` $\rightarrow$ merujuk ke `dts_bangunan_irigasi.id`.
Jika `bb == 'ter'` $\rightarrow$ merujuk ke `dts_petak_tersier.id`.
`nomen` (String): Nama resmi nomenklatur (misal: `BD.OR`, `B.Or.1`, `B.Or.1 Ki`).
`z` (Integer): Tingkat Kedalaman Routing (Topological Level / Depth) dari intake ($z=1$). Digunakan sebagai urutan komputasi hidrolika *bottom-up*.
`p` (String, Optional): Gaya lengkungan saluran (`path_right`, `path_left`, `path_line`).
Atribut Identifikasi Ruas Saluran (`id_ruas` di `dts_ruas_saluran`):
Format Standar Saluran Pembagi: `bxb` (Contoh: `b1xb2` = Ruas dari Bangunan ID 1 ke Bangunan ID 2).
Format Saluran Tersier: `bxt` (Contoh: `b2xt1` = Sadap dari Bangunan ID 2 ke Petak Tersier ID 1).
---
6. Alur Kerja Pengguna End-to-End (User Workflows)
Skenario A: Membuat Skema Jaringan Baru (Authoring Workflow)
[1. User Buka /cms/37/]
|
v
[2. Drop Objek Intake & Bangunan Bagi]
|-- Klik palet kiri (bpm, bin, bba, bsa, dll.)
|-- Drag ke posisi kanvas SVG (GSAP LiveSnap Grid 20px)
v
[3. Hubungkan Saluran Antar Node]
|-- Klik tombol Link [fa-link-simple] pada Bangunan Hulu
|-- Tarik garis merah (#garis) dan klik Bangunan Hilir
|-- Array `sumber` dan `goto` otomatis terisi di kedua objek
v
[4. Koneksikan Node ke Database Master (Linking)]
|-- Klik node bangunan di kanvas
|-- Ketik nama di input "Bangunan Irigasi:" -> Pilih dari #palet_nomen
|-- Property `link` = ID master dan label `nomen` otomatis terpasang
v
[5. Hubungkan Petak Tersier]
|-- Klik tombol Petak [fa-square] pada Bangunan Sadap/Bagi
|-- Muncul kotak hijau pastel petak tersier
|-- Ketik nama di "Petak Tersier:" -> Pilih dari #palet_petak
v
[6. Generate Routing Topologi]
|-- Klik node Intake -> Klik tombol "Set Intake"
|-- JS menjalankan fungsi rekursif `rooting(id, level)`
|-- Menghitung depth level `z` dari hulu ke hilir
|-- AJAX POST `/cms/sip/skema_jaringan/set_routing`
|-- Backend membuat/mengupdate record di tabel `dts_ruas_saluran`
v
[7. Simpan Skema Jaringan]
|-- Klik tombol "Simpan"
|-- JS men-serialize objek `bangunan` & `textTitle`
|-- AJAX POST `/cms/sip/skema_jaringan/simpan`
|-- Tersimpan ke tabel `dts_skema_jaringan`
Skenario B: Eksekusi Alokasi Air & Perhitungan Real-Time (Operational Workflow)
[1. User Buka /cms/38/ (Skema Pelaksanaan)]
|
v
[2. Client-Side Init]
|-- Load data dasar (BB, PT, RPT, Debit Real, Alokasi)
|-- Panggil `SkemaDataService.fetchSkemaData()`
v
[3. AJAX Request: GET /cms/sip/skema_jaringan/pembagian]
|
v
[4. Backend (Mdl_skema_jaringan::pembagian)]
|-- Load `data_skema` dari `dts_skema_jaringan`
|-- Parse JSON / Regex Archaic
|-- Cek Rotasi Giliran Aktif via `_find_active_rotation()`
|-- Ambil kebutuhan air petak (ttl_KAS) dari `Mdl_musim_tanam`
|-- Kelompokkan node berdasarkan level z ($bgOnLevel[$z])
|-- Jalankan `kebutuhan_air()`:
| * Iterasi mundur dari $maxz s/d 1 (Bottom-Up)
| * Akumulasi kebutuhan air + faktor kehilangan saluran (losses)
| * Akumulasi luas komoditas (padi, palawija, tebu, luas total)
|-- Hitung rekomendasi Faktor K
|-- Return JSON lengkap
v
[5. Frontend SvgRenderer]
|-- Render ulang seluruh node, garis saluran, dan label angka debit
|-- Tampilkan nilai debit kebutuhan di setiap titik bagi
|-- Update panel info DSS saat bangunan diklik
Perhitungan kebutuhan air dilakukan dari petak tersier paling hilir merambat naik ke bangunan utama (Bendung/Intake) dengan memperhitungkan kehilangan air di setiap ruas saluran (*conveyance losses*).
Level Max (Z = 4) [Petak Tersier A] (KAS = 100 lt/dt)
[Petak Tersier B] (KAS = 150 lt/dt)
|
| Losses Ruas Saluran = 5%
v
Level 3 (Z = 3) [Bangunan Sadap 2]
Kebutuhan = (100 + 150) + Loss = 250 + (250 / (100 - 5) * 5) = 263.15 lt/dt
|
| Losses Ruas Saluran = 2.5%
v
Level 2 (Z = 2) [Bangunan Bagi 1]
Kebutuhan = Kebutuhan B.Sadap 2 + Cabang Lain + Loss
|
v
Level 1 (Z = 1) [Bangunan Intake / Pengambil]
Total Kebutuhan Sistem = Σ Kebutuhan Seluruh Jaringan
Rumus Hidrolika di `kebutuhan_air()`:
Untuk setiap cabang tujuan ($idbg \in goto$):
$$\text{Losses} = lossesSaluran[idRuas]$$
$$Q_{bangunan} = Q_{tujuan} + \left( \frac{Q_{tujuan}}{100 - \text{Losses}} \times \text{Losses} \right)$$
$$Q_{total\_level} = \sum Q_{bangunan}$$
B. Rumus Perhitungan Faktor K
Faktor K adalah rasio antara ketersediaan debit air di intake dengan total kebutuhan air seluruh daerah irigasi:
$$K = \frac{Q_{\text{intake}}}{Q_{\text{kebutuhan}}}$$
Aturan penentuan debit intake ($Q_{\text{intake}}$):
Jika $Q_{\text{alokasi}} > 0$ dan $Q_{\text{alokasi}} > Q_{\text{sungai}}$, maka $Q_{\text{intake}} = Q_{\text{sungai}}$.
Jika $Q_{\text{alokasi}} = 0$ dan $Q_{\text{sungai}} > Q_{\text{kebutuhan}}$, maka $Q_{\text{intake}} = Q_{\text{kebutuhan}}$.
Jika $Q_{\text{alokasi}} > 0$, $Q_{\text{sungai}} > Q_{\text{alokasi}}$, dan $Q_{\text{alokasi}} < Q_{\text{kebutuhan}}$, maka $Q_{\text{intake}} = Q_{\text{alokasi}}$.
Batasan: $K \le 1.0$. Jika $K > 1.0$, maka nilai $K$ dipatok menjadi $1.0$.
Filtering Debit: Saat rotasi aktif, seluruh petak tersier yang `giliran != Current Turn` otomatis diberikan nilai kebutuhan $Q = 0$, sehingga alokasi air 100% terkonsentrasi hanya pada petak dalam giliran yang sedang bertugas.
Masalah: Method ini mem-parse string format lama dari database dengan mengeksekusinya via `eval()`. Jika isi kolom `dts_skema_jaringan.data_skema` disisipi kode PHP berbahaya oleh pihak ketiga, server rentan mengalami Remote Code Execution (RCE).
Rekomendasi: Wajib diganti 100% menggunakan `json_decode()` standar seperti pada `pembagian()`.