Kursus Python / Proyek laporan CSV dengan validasi dan record ditolak
Alat laporan CSV perlu memeriksa header, kolom wajib, jumlah, dan ID ganda sebelum menjumlahkan data per kategori. Record yang ditolak harus disertai alasan dan status keluar yang membedakan hasil lengkap dari hasil sebagian. Latihan ini menghasilkan laporan untuk record valid tanpa mengubah berkas masukan.
Menetapkan kontrak masukan dan penolakan
Setiap pengeluaran memiliki id, category, dan amount. Jumlah harus berupa digit desimal ASCII nonnegatif; pecahan, tanda minus, label mata uang, dan pemisah ribuan tidak diterima. Spasi di awal atau akhir nilai dibuang sebelum pemeriksaan. ID pertama yang valid dicatat; kemunculan berikutnya dengan ID yang sama ditolak, bukan dijumlahkan lagi.
Header harus tepat id,category,amount dalam urutan tersebut. Program menggunakan DictReader untuk membaca kolom berdasarkan nama, tetapi sengaja menerapkan kontrak header yang ketat. Nama kolom berbeda, urutan terbalik, atau berkas kosong tanpa header menyebabkan penghentian sebelum laporan dibuat. Kunci atau kategori kosong dan kolom berlebih ditolak pada tingkat record.
Gunakan Python 3.12 atau lebih baru, lingkungan virtual proyek, dan folder latihan baru. Semua modul yang dipakai tersedia pada pustaka standar. Program menolak berjalan jika report.csv atau rejected.csv sudah ada. Jangan menghapus laporan lama secara otomatis untuk melewati pemeriksaan itu; gunakan folder baru untuk setiap percobaan.
Dasar format, tanda kutip, dan newline="" dijelaskan dalam membaca dan menulis CSV. utf-8-sig pada masukan memungkinkan pembacaan UTF-8 dengan atau tanpa byte order mark, sedangkan keluaran ditulis sebagai UTF-8.
Menyiapkan data latihan lengkap
Simpan seluruh isi berikut sebagai expenses-input.csv dengan UTF-8 pada folder latihan:
id,category,amount
e1,food,3500
e2,food,8000
e3,transport,2500
e2,food,8000
e4,books,-1
ID e2 sengaja diulang, sedangkan pengeluaran books memiliki jumlah negatif. Kedua cacat itu digunakan untuk memeriksa jalur penolakan. Jangan memperbaikinya sebelum mencoba contoh pertama, karena hasil yang diharapkan memang mencakup dua record ditolak.
Program menomori record logis dengan header sebagai nomor 1. Dengan data ini, duplikat adalah record 5 dan nilai negatif adalah record 6. Jika suatu kolom CSV memiliki baris baru di dalam tanda kutip, satu record dapat mencakup beberapa baris fisik, sehingga nomor tersebut bukan nomor baris editor.
Menjalankan program laporan dan memeriksa kedua hasil
Simpan seluruh program berikut sebagai csv_report.py di samping berkas masukan:
import csv
import re
from collections import defaultdict
from pathlib import Path
source = Path("expenses-input.csv")
report = Path("report.csv")
rejected = Path("rejected.csv")
if report.exists() or rejected.exists():
raise FileExistsError("Use a new output directory")
totals = defaultdict(int)
seen = set()
errors = []
accepted = 0
with source.open(encoding="utf-8-sig", newline="") as f:
reader = csv.DictReader(f)
if reader.fieldnames != ["id", "category", "amount"]:
raise ValueError("unexpected header")
for number, row in enumerate(reader, start=2):
key = (row.get("id") or "").strip()
category = (row.get("category") or "").strip()
amount_text = (row.get("amount") or "").strip()
reason = ""
if None in row or not key or not category:
reason = "invalid fields"
elif key in seen:
reason = "duplicate id"
elif not re.fullmatch(r"[0-9]+", amount_text):
reason = "invalid amount"
if reason:
errors.append({"record": number, "reason": reason})
continue
seen.add(key)
totals[category] += int(amount_text)
accepted += 1
with report.open("x", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerow(["category", "total"])
writer.writerows(sorted(totals.items()))
with rejected.open("x", encoding="utf-8", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["record", "reason"])
writer.writeheader()
writer.writerows(errors)
print("accepted:", accepted, "rejected:", len(errors))
print("total:", sum(totals.values()))
raise SystemExit(1 if errors else 0)
Dari terminal pada folder latihan, pilih interpreter proyek lalu jalankan:
python csv_report.py
Program mencetak accepted: 3 rejected: 2 dan total: 14000. Status keluar adalah 1 karena ada record ditolak. Ini bukan pemrosesan penuh yang berhasil walaupun laporan kategori telah terbentuk. Pada PowerShell, periksa $LASTEXITCODE langsung setelah perintah; pada shell macOS/Ubuntu, periksa echo $?.
Isi lengkap report.csv yang diharapkan:
category,total
food,11500
transport,2500
Isi lengkap rejected.csv yang diharapkan:
record,reason
5,duplicate id
6,invalid amount
Food berjumlah 3500 + 8000 = 11500 dan transport berjumlah 2500; record e2 kedua tidak dihitung. seen baru diperbarui setelah record lolos validasi. Karena itu, ID pada record tidak valid belum mengambil hak sebagai kemunculan pertama yang diterima. Urutan pemeriksaan juga menentukan alasan tunggal yang dicatat jika sebuah record melanggar beberapa aturan sekaligus.
Mode tulis "x" meminta pembuatan file baru dan menolak menimpa file yang sudah ada. Pengaman ini melengkapi pemeriksaan awal exists(), tetapi tidak menjadikan pembuatan dua file satu transaksi. Penulisan laporan juga memakai urutan kategori yang diurutkan agar hasil lebih mudah dibandingkan.
Menguji pemulihan, pengulangan, dan batas hasil sebagian
- Cocokkan total laporan dengan record masukan yang diterima dan periksa dua alasan penolakan. Pastikan berkas masukan tidak berubah.
- Jalankan kembali pada folder yang sama. Program harus berhenti karena file keluaran sudah ada, dan isi laporan lama tetap sama.
- Siapkan folder baru dengan salinan program. Ubah ID duplikat menjadi ID unik dan ubah jumlah negatif menjadi nol atau nilai nonnegatif lain. Jalankan dari folder baru dan pastikan tidak ada penolakan serta status keluar 0.
- Pada folder uji lain, ubah urutan header dan periksa pesan
unexpected header. Gunakan data terpisah agar percobaan gagal tidak mencampur hasil sebelumnya.
Untuk pemeriksaan tambahan, bandingkan jumlah nol dengan string pecahan, kategori kosong, kolom berlebih, dan ID yang baru valid pada kemunculan kedua. Masukan salah tidak boleh menghilang tanpa jejak. Pemeriksaan integrasi otomatis dapat menjalankan program di folder sementara dan memeriksa status serta kedua berkas hasil.
Jika file pertama berhasil dibuat tetapi penulisan file kedua gagal karena masalah penyimpanan atau izin, laporan pertama dapat tertinggal. Perlakukan folder itu sebagai hasil belum lengkap. Untuk alur kerja yang lebih kuat, tulis ke direktori sementara, validasi seluruh hasil, lalu publikasikan folder atau tanda selesai melalui prosedur yang sesuai. Jangan meneruskan laporan ke proses berikutnya hanya karena nama filenya sudah muncul.
Alat ini adalah contoh lokal dengan volume data yang terkendali. Ia menyimpan daftar alasan penolakan di memori dan tidak menyelesaikan seluruh masalah distribusi CSV. Jika kategori berasal dari pihak luar dan hasil dibuka pada spreadsheet, tentukan kebijakan penanganan string yang dapat ditafsirkan sebagai rumus oleh aplikasi tujuan. Rincian pembacaan record tersedia dalam dokumentasi csv; perilaku file mengikuti Path.open.




