Validasi adalah gerbang pertama pipeline: lebih baik menolak baris yang tidak dapat dipercaya daripada membiarkannya mencemari tabel yang dipakai pengambilan keputusan. Pada bagian ini kita membaca berkas CSV, membandingkan header dengan kontrak kolom, lalu memeriksa setiap baris terhadap aturan yang diturunkan dari audit Bagian 1. Modul src/validate.py ditulis sebagai kumpulan fungsi murni tanpa efek samping, sehingga perilakunya dapat diuji tanpa menyentuh berkas lain.
Kontrak Kolom #
Dua fungsi pertama pada modul menjaga bentuk berkas. Fungsi baca_csv memakai csv.DictReader agar setiap baris berupa dictionary nama kolom -> nilai, bukan daftar posisi:
def baca_csv(path: str) -> tuple[list[str], list[dict]]:
"""Baca satu berkas CSV; kembalikan (header, daftar baris)."""
with open(path, newline="", encoding="utf-8") as f:
pembaca = csv.DictReader(f)
header = [k for k in (pembaca.fieldnames or []) if k]
baris = [dict(r) for r in pembaca]
return header, barisFungsi cek_kolom membandingkan header dengan KOLOM_WAJIB. Kedua arah diperiksa: kolom wajib yang hilang, dan kolom asing yang tidak dikenal:
def cek_kolom(header: list[str]) -> list[str]:
"""Bandingkan header berkas dengan KOLOM_WAJIB.
Kembalikan daftar keterangan; daftar kosong berarti header sesuai.
"""
keterangan: list[str] = []
for kolom in KOLOM_WAJIB:
if kolom not in header:
keterangan.append(f"kolom wajib hilang: {kolom}")
for kolom in header:
if kolom and kolom not in KOLOM_WAJIB:
keterangan.append(f"kolom tak dikenal: {kolom}")
return keteranganBila header tidak sesuai, pipeline berhenti sebelum memproses satu baris pun. Header yang salah berarti berkas bukan yang kita sangka, dan melanjutkannya hanya menyembunyikan galat.
Aturan Per Kolom #
Setiap kolom punya aturan yang dapat ditunjuk asal-usulnya dari audit. Hasilnya dirangkum dalam tabel berikut:
| Kolom | Aturan | Kode masalah bila dilanggar |
|---|---|---|
order_id | cocok pola ORD-YYYY-NNNN | order_id_tidak_valid |
tanggal | tanggal nyata berformat YYYY-MM-DD | tanggal_tidak_valid |
email | cocok pola [email protected] | email_tidak_valid |
kategori | salah satu dari lima kategori | kategori_tidak_dikenal |
jumlah | angka bulat minimal 1 | jumlah_tidak_positif |
harga | angka >= 0, boleh berpemisah ribuan | harga_tidak_valid |
status | salah satu dari empat status | status_tidak_dikenal |
pelanggan, produk | tidak kosong | pelanggan_kosong, produk_kosong |
Perhatikan dua keputusan yang tampak kecil tetapi berdampak besar. Pertama, pola order_id dicocokkan dengan re.IGNORECASE, sehingga ord-2024-0006 lolos validasi dan cukup dibesarkan pada tahap pembersihan. Kedua, harga menerima 210.000 karena format ribuan adalah kebiasaan penulisan yang sah, bukan kebohongan data; tugas menormalkannya diberikan ke Bagian 4.
Validasi Satu Baris #
Satu baris diperiksa oleh validasi_baris, yang mengembalikan daftar objek Masalah:
@dataclass
class Masalah:
"""Satu temuan validasi pada satu baris."""
baris: int # nomor baris di berkas, 1-based termasuk baris header
kolom: str
jenis: str # kode masalah yang stabil untuk pelaporan
pesan: strCuplikan berikut menunjukkan tiga aturan pertama di dalam validasi_baris; aturan lainnya mengikuti pola yang sama:
order_id = teks("order_id")
if not order_id:
galat("order_id", "order_id_kosong", "order_id tidak boleh kosong")
elif not POLA_ORDER_ID.match(order_id):
galat("order_id", "order_id_tidak_valid", "pola order_id harus ORD-YYYY-NNNN")
tanggal = teks("tanggal")
if not tanggal:
galat("tanggal", "tanggal_kosong", "tanggal tidak boleh kosong")
else:
try:
datetime.strptime(tanggal, "%Y-%m-%d")
except ValueError:
galat(
"tanggal",
"tanggal_tidak_valid",
"tanggal harus tanggal nyata berformat YYYY-MM-DD",
)
email = teks("email")
if not email:
galat("email", "email_kosong", "email tidak boleh kosong")
elif not POLA_EMAIL.match(email):
galat("email", "email_tidak_valid", "email harus berformat [email protected]")Mari kita telaah maksudnya:
teks(kolom): fungsi kecil yang membaca kolom lalu memangkas spasi tepi. Nilai" 2 "tetap valid karena spasi bukan kebohongan data.datetime.strptime(...): tanggal2024-06-31lolos pola teksYYYY-MM-DD, tetapistrptimemenolaknya karena 31 Juni tidak ada di kalender. Pemeriksaan kalender inilah yang membedakan validasi tanggal sungguhan dari pencocokan pola.galat(kolom, jenis, pesan): setiap pelanggaran membawa nomor baris, kolom, kode stabil, dan kalimat yang dapat dibaca operator. kodejenismenjadi kunci pelaporan dan pengujian.
Menjalankan Validasi pada Data Asli #
Ringkasan berikut dijalankan nyata terhadap berkas Juni. Seluruh temuan lalu dihitung per jenis dengan ringkas_masalah:
header, baris = validate.baca_csv("data/pesanan_2024-06.csv")
temuan = []
ditolak = 0
for nomor, b in enumerate(baris, start=2):
t = validate.validasi_baris(b, nomor)
if t:
ditolak += 1
temuan += tOutput yang Dihasilkan:
berkas : data/pesanan_2024-06.csv
baris : 107 (16 ditolak, 91 lolos)
temuan : 16
ringkasan per jenis:
email_kosong 1
email_tidak_valid 1
harga_kosong 1
harga_negatif 1
harga_tidak_valid 1
jumlah_tidak_positif 2
kategori_tidak_dikenal 1
order_id_kosong 1
order_id_tidak_valid 1
pelanggan_kosong 1
produk_kosong 1
status_tidak_dikenal 1
tanggal_kosong 1
tanggal_tidak_valid 2
contoh temuan:
baris 9 | email | email_tidak_valid | email harus berformat [email protected]
baris 14 | jumlah | jumlah_tidak_positif | jumlah tidak boleh negatif
baris 20 | harga | harga_kosong | harga tidak boleh kosong
baris 25 | tanggal | tanggal_tidak_valid | tanggal harus tanggal nyata berformat YYYY-MM-DD
baris 32 | order_id | order_id_tidak_valid | pola order_id harus ORD-YYYY-NNNN
baris 38 | kategori | kategori_tidak_dikenal | kategori harus salah satu dari: buku, elektronik, fashion, makanan, olahragaAngka pada ringkasan persis seperti rancangan audit: 16 baris ditolak, masing-masing dengan satu temuan. Setiap jenis muncul dari contoh nyata yang kita tanam di berkas, bukan dari aturan yang ditebak.
Rujukan teknis untuk bagian ini: csv.DictReader dan modul re .
Validasi menolak baris yang tidak dapat diselamatkan. Tetapi 91 baris yang lolos belum berarti siap dipakai: kapitalisasi, spasi, dan pemisah ribuan masih melekat. Tugas itulah yang kita kerjakan berikutnya. Lanjutkan ke Bagian 4: Membersihkan Data.