Arsitektur Teknis LAPAS (Laboratorium Pesugihan Analisa Saham)
Asal-Usul: dari Obrolan Santai Soal AI ke Laboratorium Analisis Saham
LAPAS bermula dari pertanyaan sederhana: apakah AI bisa membantu analisis saham? Eksplorasi awal menimbang beberapa pendekatan — analisis teknikal berbasis machine learning, sentimen berita, hingga ekstraksi rasio fundamental otomatis — dan berhenti pada satu kesimpulan yang jadi tulang punggung seluruh proyek: AI di sini membantu membaca informasi lebih cepat, bukan meramal harga dengan pasti. Hasil AI adalah salah satu input, bukan keputusan final.
Tiga tahap ambisi dipetakan sejak awal: (1) alat analisis pribadi untuk satu-dua emiten tanpa antarmuka rapi, (2) coverage tool yang mencakup banyak emiten dengan dashboard konsisten, dan (3) equity research platform sungguhan dengan model valuasi dan distribusi lewat API. LAPAS berhenti di tahap kedua yang diperluas secara sadar — ambisinya adalah laboratorium pribadi, bukan platform publik yang memberi rekomendasi ke banyak orang dan bersinggungan dengan pengawasan OJK.
Stack
| Komponen | Pilihan |
|---|---|
| Framework | Laravel 12 (PHP 8.3), Livewire 4, Flux UI, Tailwind CSS |
| Database | MySQL 8 |
| Sumber data | Yahoo Finance (tidak resmi) — harga EOD, fundamental via cookie+crumb, dividen/split via parameter events pada endpoint chart yang sama |
| AI opsional | Claude, ChatGPT, atau Gemini — API key milik masing-masing pengguna |
| Lingkungan | Windows/Uniform Server lokal, rencana pindah ke shared hosting |
| Pengujian | 305 tes PHPUnit, RefreshDatabase + SQLite in-memory, Http::fake() untuk sumber data eksternal |
Isolasi Data Multi-Pengguna: Global Scope, Bukan Policy Satu-per-Satu
Skema awal LAPAS murni tunggal-pengguna: registrasi publik dimatikan, dan tidak ada satu pun kolom user_id di tabel manapun. Mengubah ini tanpa kehilangan data nyata yang sudah ada adalah tantangan struktural terbesar dalam pengembangannya.
Solusinya sebuah trait Eloquent, MilikPengguna, dipasang di empat tabel data pribadi (jurnal, watchlist, simulasi, preset screener). Trait ini otomatis menyaring setiap kueri lewat global scope agar hanya menampilkan milik pengguna yang sedang login, dan otomatis mengisi kepemilikan saat data baru dibuat:
trait MilikPengguna
{
protected static function bootMilikPengguna(): void
{
static::addGlobalScope('milikPengguna', function (Builder $query) {
if (Auth::check()) {
$query->where($query->getModel()->getTable().'.user_id', Auth::id());
}
});
static::creating(function ($model) {
if (! $model->user_id && Auth::check()) {
$model->user_id = Auth::id();
}
});
}
}
Manfaat Tak Terduga: IDOR Tertutup Otomatis
Karena route model binding Laravel ikut memakai scope Eloquent yang sama, celah keamanan IDOR (menebak ID data milik orang lain lewat URL) tertutup otomatis di seluruh aplikasi — tanpa menulis kebijakan otorisasi manual di tiap halaman satu per satu.
Cron yang “Menjadi” Setiap Pengguna
Untuk perintah terjadwal yang memproses banyak pengguna sekaligus (notifikasi, backtest mingguan), dipakai Auth::onceUsingId(): proses “menjadi” tiap pengguna secara bergantian dalam satu putaran, sehingga kode yang sama persis dipakai baik dari halaman web maupun command line tanpa duplikasi logika.
Notifikasi Dua Lapis: Infrastruktur Bersama, Preferensi Pribadi
Bot Telegram dan perangkat Fonnte (WhatsApp) secara alami dimiliki bersama — satu bot, satu nomor. Tapi tujuan pengiriman dan preferensi peristiwa harus jadi milik pribadi begitu ada lebih dari satu pengguna. Notifikasi dipisah menjadi dua lapisan: token bot/perangkat dikelola admin (infrastruktur bersama), sementara tujuan, sakelar, dan preferensi peristiwa dikelola tiap pengguna sendiri.
Satu masalah turunan muncul saat menyambungkan Telegram: karena satu bot dipakai bersama, mengambil “Chat ID” dari pesan /start paling terakhir jadi ambigu ketika dua orang menghubungi bot hampir bersamaan. Solusinya, tiap pengguna diberi kode verifikasi 6 digit yang harus dikirim ke bot, dan sistem hanya mencocokkan pesan yang berisi kode tersebut — bukan sekadar pesan yang paling baru.
Pagar Konten AI: Matriks Integrasi Tanpa Verdict
Permintaan agar AI menyimpulkan sebuah saham “layak/tidak layak” dibeli bertentangan langsung dengan prinsip dasar LAPAS: bukan pemberi sinyal beli/jual. Solusinya adalah “matriks integrasi” yang menyandingkan sinyal empat dimensi (teknikal, fundamental, likuiditas, sentimen berita), masing-masing berlabel positif/negatif/netral/campuran beserta alasannya — tanpa pernah menyimpulkan tindakan.
Yang membedakan pendekatan ini dari sekadar instruksi di dalam prompt: pagar dipasang di level kode, bukan hanya di level permintaan ke AI. Kolom sinyal hanya boleh diisi dari empat kata baku lewat sebuah sanitizer (matriksBersih()), sehingga walau model AI mencoba menjawab “beli sekarang”, jawaban itu otomatis diperhalus menjadi “netral” oleh kode — kata beli/jual secara struktural tidak mungkin lolos lewat kolom ini.
Backup Database: Kredensial yang Tidak Pernah Muncul di Argumen Proses
Backup penuh (mysqldump) berjalan otomatis tiap malam untuk data yang tidak bisa dibangun ulang, terutama jurnal keputusan. Dua detail keamanan dan keandalan yang sengaja diperhatikan:
- Kredensial database dilewatkan lewat berkas
--defaults-extra-filesementara, bukan argumen proses — supaya tidak terlihat di daftar proses sistem operasi (ps/Task Manager). - Nama berkas backup diberi akhiran acak (
bin2hex(random_bytes(2))), bukan hanya presisi detik — ditemukan lewat pengujian bahwa dua backup yang dibuat dalam detik yang sama bisa saling menimpa tanpa akhiran ini.
Bug Nyata yang Ditemukan Saat Audit
Audit menyeluruh setelah retrofit multi-pengguna menemukan beberapa bug produksi nyata, bukan sekadar hipotetis:
- Notifikasi dan AI harian mogok diam-diam: gerbang pemeriksaan (
->when()) di penjadwal masih memanggil fungsi lama dengan argumen yang sudah tidak sesuai setelah model kepemilikan data berubah — satu menyebabkan crash, satu lagi diam-diam selalu bernilai salah sehingga AI harian tidak pernah berjalan otomatis. - PHP
max_execution_time(30 detik) vs panggilan AI (bisa 90 detik lebih): menyebabkan Internal Server Error mentah di halaman Ringkasan AI — diperbaiki dengan menaikkan batas waktu secara eksplisit sebelum tiap panggilan ke API AI. - Migrasi MySQL gagal meski lolos di pengujian: kunci asing (foreign key) di MySQL ternyata butuh indeksnya sendiri sebelum indeks unik lama boleh dihapus — celah yang tidak terlihat di SQLite (basis data pengujian).
Pola yang konsisten: perubahan arsitektur besar berpotensi meninggalkan jejak tersembunyi di bagian kode yang tidak langsung disentuh, dan satu-satunya cara menemukannya secara andal adalah verifikasi nyata setelah setiap perubahan — bukan berasumsi bahwa perubahan lokal tidak berdampak luas.
Menuju Shared Hosting
LAPAS saat ini berjalan di server Windows pribadi (Uniform Server) dan belum pernah diuji penuh di lingkungan shared hosting sungguhan. Panduan pemasangan sudah disiapkan, termasuk catatan bahwa set_time_limit() — yang dipakai untuk mengatasi bug timeout AI di atas — kemungkinan dibatasi oleh sebagian penyedia shared hosting, sehingga perlu diverifikasi ulang begitu pemindahan benar-benar dilakukan.
Referensi Terkait
- LAPAS — Laboratorium Pesugihan Analisa Saham — ringkasan project dan fitur utamanya.
- Arsitektur Teknis PurwaExam — studi kasus arsitektur Laravel lain dengan tantangan deploy shared hosting yang serupa.
- Aplikasi Web & Desktop — layanan Sashindo Project untuk kebutuhan aplikasi kustom serupa.