Skip to content

Menyusun Pilot Ingest Sinyal Harian dari Registri Publik

Adityo Guni Waluyo

Dua registri publik lolos probe: susun scraper atas satu seam, paksa idempotensi lewat content hash, dan jadwalkan ingest harian pukul lima pagi.

Ringkasan

Jadi intinya pilot ini ngambil data harian dari daftar hitam LKPP sama daftar perkara pailit/PKPU pengadilan, semua scraper lewat satu antarmuka yang sama. Biar nggak dobel, tiap sinyal dikasih hash, database nolak kalau udah masuk, jadi aman dijalan ulang kapan aja. Ingestnya sekali sehari jam lima pagi, sopan dikit, dikasih jeda antar request biar servernya nggak keberatan.

Dokumen rencana pilot menargetkan ingest harian dari dua registri publik: daftar hitam pengadaan pemerintah dan daftar perkara pailit serta PKPU dari pengadilan negeri. Satu keputusan di awal menentukan bentuk seluruh sistem: pengambilan data yang sama akan berjalan setiap pagi, berulang, tanpa boleh menduplikasi sinyal yang sudah masuk.

Cara tercepat yang terlintas adalah cron biasa yang menyisipkan setiap baris hasil ke satu tabel. Rencana itu bertahan sampai hitungan pertama: halaman daftar hitam memuat lusinan baris sanksi yang bergeser setiap hari, dan baris yang sama akan terbaca lagi besok pagi. Tanpa mekanisme khusus, tabel penuh duplikat dalam seminggu.

Jadi prinsipnya satu: setiap pengambilan data harus aman dijalankan ulang kapan pun. Istilahnya idempoten, dan seluruh rancangan di bawah tunduk pada prinsip itu.

Pilih dua sumber yang lolos probe

Sumber pertama adalah Daftar Hitam Nasional milik LKPP, sistem informasi yang memuat identitas penyedia barang/jasa yang dikenakan sanksi daftar hitam oleh pejabat pengadaan, dengan dasar hukum Perpres 16/2018 [1]. Sumber kedua adalah halaman daftar perkara SIPP dari tiga pengadilan yang masih terbuka untuk akses terukur. SIPP secara resmi adalah aplikasi web per-satuan-kerja pengadilan, bukan satu portal pusat [5], sehingga tiap pengadilan dikonfigurasi sendiri-sendiri.

Karakter kedua sumber berbeda. Halaman pertama daftar hitam dirender di sisi server dengan sanksi terbaru di urutan atas, sementara halaman di bawahnya digerakkan JavaScript; snapshot harian halaman pertama saja sudah cukup untuk pilot. Daftar perkara pengadilan berbentuk tabel dengan kolom Nomor Perkara, Klasifikasi Perkara, Para Pihak, dan Status Perkara. Menu filter per jenis perkara ternyata tidak benar-benar menyaring, maka penyaringan dilakukan lokal: pola nomor perkara Pdt.Sus-Pailit dan Pdt.Sus-PKPU menandai sinyal kepailitan dan PKPU, sementara nama perusahaan diambil dari kolom Para Pihak.

Susun semua scraper di atas satu seam

Kedua sumber diimplementasikan sebagai scraper yang memenuhi satu antarmuka yang sama. Antarmuka itulah SignalSource: komponen lain tidak perlu tahu apakah sinyal berasal dari daftar hitam atau dari pengadilan. Hasil normalisasi masuk ke dua tabel:

class SignalSource:
    source_id: str

    def fetch(self) -> list[dict]: ...

# signals: id, source_id, source_ref, company_name_raw,
#   company_name_normalized, signal_type, event_date,
#   details (JSONB), content_hash
# ingest_runs: id, source_id, started_at, finished_at,
#   status, pages, items_seen, items_new, error

Idempotensi dipaksa di tingkat basis data, bukan hanya di naskah desain. Kolom content_hash dihitung dari isi sinyal, lalu constraint unik pada pasangan source_id dan content_hash menolak baris yang sudah pernah masuk. Ingest hari kedua membaca halaman yang sama, menghitung hash yang sama, dan basis data yang menolaknya. Jalankan ulang tiga kali sehari pun hasilnya tetap satu baris per sinyal.

Kegagalan parsial juga bagian dari desain. Satu pengadilan yang down tidak boleh menggagalkan seluruh run: proses berlanjut ke sumber berikutnya, dan status kegagalan tercatat di ingest_runs. Untuk membaca hasilnya, tersedia API terbatas per tenant serta ringkasan harian yang mengelompokkan sinyal per perusahaan.

Jadwalkan sekali sehari, beri jeda pada tiap permintaan

Jadwal ingest adalah sekali sehari sekitar pukul 05.00 WIB, dijalankan dari dalam container aplikasi lewat penjadwal Python, bukan cron sistem. Pustaka APScheduler menyediakan CronTrigger dengan parameter zona waktu dan jitter [2]:

from apscheduler.triggers.cron import CronTrigger

trigger = CronTrigger(
    hour=5, minute=0,
    timezone="Asia/Jakarta",
    jitter=60,
)
# setara crontab: 0 5 * * * (Asia/Jakarta)

Di dalam satu run, kesopanan dijalankan sebagai aturan keras: User-Agent memuat nama layanan, jeda 2 sampai 5 detik antar permintaan, batas keras 12 permintaan per run, HTML mentah di-cache sebelum diurai, dan satu lintasan per hari. RFC 9309 menyatakan aturan robots bukan merupakan bentuk otorisasi akses [3]; kesopanan menjadi bagian desain sejak awal, bukan menunggu peringatan.

Sumber yang tergerbang dicatat, bukan dilawan. Tidak ada pemecahan captcha, tidak ada pengelakan WAF; pengadilan yang menolak dilepas dari konfigurasi sampai suatu saat bisa diakses kembali. Satu catatan soal data pribadi ikut menyertai: baris perkara bisa membawa nama direktur, dan UU 27/2022 tentang Pelindungan Data Pribadi mengatur pemrosesan data pribadi [4], sehingga anonimisasi masuk daftar pertimbangan desain sejak fase pilot.

Pilot berakhir bila satu kalimat ini bisa didemonstrasikan dengan data nyata: tunjukkan sinyal seminggu terakhir untuk satu perusahaan. Dua scraper, satu seam, satu jadwal yang sama. Selebihnya hanya eksekusi.

Sumber

  1. PPID LKPP: Aplikasi Daftar Hitam Nasional
  2. Dokumentasi APScheduler: apscheduler.triggers.cron
  3. RFC 9309: Robots Exclusion Protocol
  4. JDIH BPK: UU Nomor 27 Tahun 2022 tentang Pelindungan Data Pribadi
  5. Dokumentasi Mahkamah Agung: Pendahuluan Pencadangan SIPP

Artikel terkait