Skip to content

Tiga Celah Mapper yang Hanya Terlihat di Data Asli

Adityo Guni Waluyo

Fixture hijau bukan jaminan: nested object, camelCase, dan datetime penuh baru ketahuan saat 5330 baris asli masuk ke ingest.

Ringkasan

Dump JSON 5330 baris masuk mulus tanpa error, tapi kolom nama perusahaan sama tanggalnya kosong semua, ternyata mapper salah asumsi bentuk data. Field penyedia ternyata objek bersarang, kuncinya camelCase, plus tanggalnya datetime penuh yang ditolak date.fromisoformat. Setelah dibenerin, semua baris kebaca dan idempotensi jalan, pelajarannya mulai dari data asli, bukan asumsi.

Baris pertama dari dump JSON berukuran 7,7 MB berisi 5330 entri daftar hitam baru saja tiba di pipeline ingest DemandScope. Proses pemetaan berjalan tanpa error, tetapi kolom nama perusahaan dan kolom tanggal peristiwa kembali kosong. Inspeksi pada baris ke-0 menunjukkan tiga kejutan bentuk data sekaligus.

Dugaan pertama menyalahkan fixture yang kurang lengkap. Tebakan itu keliru. Seluruh pengujian hijau justru karena fixture dibangun dari asumsi yang sama persis dengan mapper, sehingga cacat asumsi tidak pernah terpapar data sintetis. Skema GraphQL memang mendeskripsikan penuh data yang bisa diminta [2], tetapi asumsi tentang bentuk data hidup di kode klien, bukan di skema.

Tiga Celah pada Bentuk Data

Pertama, soal shape: field penyedia bukan string datar melainkan objek bersarang yang memuat nama, NPWP, dan alamat. Kedua, soal naming: kunci tanggal memakai startDate dan expiredDate, bukan gaya snake_case yang lazim di Python. Ketiga, soal precision: nilai tanggalnya datetime penuh seperti 2026-10-06T06:15:52Z, bukan tanggal saja.

Ketiganya masuk akal kalau dilihat dari sisi penyedia data. Konvensi penamaan GraphQL memang menyarankan camelCase agar selaras dengan variabel JavaScript [6]. Format yang dikirim adalah datetime RFC 3339, profil dari ISO 8601 [3]. Sementara itu, date.fromisoformat menerima string ISO 8601 dengan pengecualian yang terdokumentasi, dan datetime penuh dengan penanda zona waktu termasuk yang ditolaknya [5]. Perilaku itu mudah dibuktikan sendiri:

from datetime import date

# Format tanggal dasar: berhasil diurai
date.fromisoformat("2026-10-06")

# Format dasar tanpa pemisah: berhasil diurai
date.fromisoformat("20261006")

# Datetime penuh RFC 3339: memicu ValueError
date.fromisoformat("2026-10-06T06:15:52Z")

Perbaikan dan Jaring Pengaman

Perbaikannya tiga lapis. Resolver membaca objek bersarang lebih dulu, lalu melanjutkan ke rantai field datar yang sudah ada sehingga jalur lama tetap hidup. Nama kunci camelCase ditambahkan ke satu tupel registri field, satu sumber kebenaran yang dikonsumsi parser sekaligus detail-stripper; drift di tupel itu selama ini berarti kolom tanggal hilang diam-diam karena dua konsumen membaca daftar yang berbeda. Ekstraksi tanggal memakai regex yang mengambil komponen tanggalnya saja sebelum parsing, toleran terhadap pemisah spasi maupun detik opsional. Jalur SIPP tidak tersentuh oleh perubahan ini: ketika field datar tersedia, rantai lama tetap menang, sehingga dua sumber data dengan bentuk berbeda hidup damai di satu mapper tanpa cabang khusus per sumber. Urutan pemeriksaan juga penting; resolver nested ditaruh sebelum rantai datar supaya objek bersarang tidak pernah salah perlakukan sebagai teks mentah.

def resolve_provider_name(row: dict) -> str:
    # Nested lebih dulu, lalu rantai field datar
    provider = row.get("provider")
    if isinstance(provider, dict):
        return provider.get("name", "").strip()
    return str(provider).strip()

Verifikasi pascaperbaikan berjalan pada data yang sama: 5330 dari 5330 baris terbentuk dengan kolom perusahaan ternormalisasi dan tanggal peristiwa terisi [1]. Pengiriman ulang sesaat kemudian menghasilkan 5330 baris berstatus unchanged, revisi tetap 0, hanya penanda waktu terakhir yang bergeser; idempotensi berbasis content hash bekerja seperti dirancang. Lima puluh lima pengujian tetap hijau, dan 178 run ingest tercatat normal selama proses itu. Dari 4260 nama penyedia mentah, tersisa 4141 perusahaan unik setelah normalisasi, dua angka untuk dua lapis yang berbeda. Seluruh dump ditaruh masuk hanya dengan satu permintaan per detik, pola paling sopan yang tetap selesai dalam hitungan menit.

Prinsip lama RFC 1122 menutup pelajaran ini: bersikap liberal dalam menerima, konservatif dalam mengirim [4]. Mapper menerima variasi bentuk, penamaan, dan presisi di tepi sistem, lalu memancarkan satu bentuk kanonik ke hilir. Ada satu baris berisi tahun 1905 akibat salah ketik di sumbernya, dan ia dipertahankan apa adanya alih-alih dijepit diam-diam; keputusan soal data sumber yang rusak bukan milik mapper. Jalur tanggal berbahasa Indonesia pernah menunjukkan pola serupa pada kolom event_date yang selalu NULL, dan pelajarannya konsisten: mulai dari baris pertama data asli, bukan dari asumsi.

Sumber

  1. Portal daftar hitam (GraphQL endpoint, header dataset)
  2. GraphQL Learn: Schemas and Types
  3. RFC 3339: Date and Time on the Internet
  4. RFC 1122: Requirements for Internet Hosts
  5. Python docs: datetime
  6. GraphQL Learn: Naming Conventions

Artikel terkait