Sidang Kuliah Kerja Praktek

Penerapan Data Mining Regresi untuk Prediksi Lama Pembayaran Invoice pada PT Kreasi Pandawa Sakti dengan Algoritma Random Forest

Dosen Pembimbing  Basuki Hari Prasetyo, S.Kom., M.Kom.
Program Studi Teknik Informatika — Peminatan Programming Expert
Fakultas Teknologi Informasi, Universitas Budi Luhur · Semester Genap 2025/2026
[ALUR] peta-presentasi

Yang Akan Kami Sampaikan

Segmen 1 · ±10 menit

Aris Kurniawan — BAB I–II

  • Profil instansi & latar belakang
  • Rumusan masalah, tujuan, manfaat
  • Batasan masalah
  • Penelitian terdahulu
  • Metodologi CRISP-DM

Segmen 2 · ±10 menit

Chaisar Wildan — BAB III

  • Sumber data & funnel penyaringan
  • Target penelitian & 12 fitur
  • Rekayasa fitur & pipeline
  • Cara kerja Random Forest
  • Setup eksperimen

Segmen 3 · ±10 menit

Rilo Pamuji — BAB IV–V

  • Hasil evaluasi model
  • Validasi silang & feature importance
  • Integrasi ke sistem keuangan
  • Demo prediksi runtime
  • Keterbatasan, kesimpulan, saran
Sesi tanya jawab Kami menyiapkan 17 slide cadangan di bagian akhir — termasuk penurunan matematis kriteria pemecahan node, mekanisme bagging, dan definisi feature importance.
01

Pendahuluan & Metodologi

Aris Kurniawan · 2311510438
BAB I Pendahuluan  ·  BAB II Landasan Teori
Profil instansi → latar belakang → rumusan masalah → tujuan & manfaat → batasan → penelitian terdahulu → CRISP-DM
[BAB II] profil-instansi

PT Kreasi Pandawa Sakti

Perusahaan aktivasi merek (brand activation) berlokasi di Jakarta, menangani banyak klien dengan pola pembayaran yang sangat beragam.

Klien utama

  • PT Sayap Mas Utama (Wings Group)
  • PT Energizer Indonesia
  • PT Bital Asia
  • PT Ichi Tan Indonesia
  • PT Amarta Indah Otsuka
Dua entitas legal Operasional melibatkan PT Kreasi Pandawa Sakti (KPS) dan PT Sinergi Unggul Barakarsa (SUB) dalam satu grup usaha — karenanya rekap invoice tersebar di beberapa berkas.

Skala data

Klien24entitasberbeda
Invoice857barisrekap mentah
Periode7tahun2019–2026
Posisi penulis Kerja praktik dilaksanakan di bagian Keuangan, yang bertanggung jawab atas pengelolaan piutang usaha dan pemantauan pembayaran invoice.
[BAB I] latar-belakang

Piutang Usaha yang Dipantau Manual

Rp1,70 miliar
Outstanding piutang belum tertagih

dari total nilai invoice sekitar Rp64,9 miliar sepanjang 2019–2026

Kondisi saat ini

  • 857 baris invoice tersebar di 5 berkas rekap yang formatnya tidak seragam
  • Pemantauan keterlambatan masih manual memakai lembar kerja
  • Tidak ada estimasi kapan sebuah invoice akan dilunasi
  • Prioritas penagihan disusun berdasarkan perkiraan, bukan data
Dampak Keterlambatan pembayaran berpengaruh langsung terhadap arus kas perusahaan: proyeksi kas sulit disusun dan risiko piutang tak tertagih meningkat.
[BAB I] gagasan-solusi

Dari Masalah ke Gagasan Solusi

Pemantauan manual prioritas penagihan tidak terukur, arus kas sulit diproyeksikan
Data historis 2019–2026 tersimpan lengkap, tetapi belum pernah dianalisis untuk memprediksi apa pun
Teknik regresi menggali pola dari data historis untuk memprediksi nilai numerik (Han dkk., 2011)
Random Forest menangani hubungan non-linear, tahan overfitting lewat bagging (Breiman, 2001)
Hipotesis kerja: apabila lama pembayaran sebuah invoice dapat diperkirakan sejak invoice diterbitkan, bagian Keuangan dapat menyusun prioritas penagihan secara lebih tepat dan terukur.
Batas kelayakan Prediksi hanya boleh memakai atribut yang sudah diketahui saat invoice terbit — inilah yang membedakannya dari sekadar melaporkan keterlambatan setelah terjadi.
[BAB I] rumusan-masalah

Tiga Pertanyaan Penelitian

01 Membangun model. Bagaimana membangun model regresi menggunakan algoritma Random Forest untuk memprediksi lama pembayaran invoice berdasarkan atribut yang tersedia saat invoice diterbitkan?
Dijawab pada Segmen 2 — slide data, fitur, dan pemodelan
02 Mengukur kinerja. Seberapa baik kinerja algoritma Random Forest dalam memprediksi lama pembayaran invoice yang diukur menggunakan metrik , RMSE, dan MAE?
Dijawab pada Segmen 3 — slide hasil evaluasi dan validasi silang
03 Mengidentifikasi faktor. Atribut apa yang paling berpengaruh terhadap lama pembayaran invoice berdasarkan analisis feature importance?
Dijawab pada Segmen 3 — slide feature importance
[BAB I] tujuan-manfaat

Tujuan dan Manfaat Penelitian

Tujuan

  1. Menghasilkan model regresi Random Forest untuk memprediksi lama pembayaran invoice
  2. Mengukur akurasi model berdasarkan metrik , RMSE, dan MAE
  3. Mengidentifikasi atribut paling berpengaruh terhadap lama pembayaran

Manfaat praktis

  • Membantu penyusunan prioritas penagihan
  • Mengoptimalkan arus kas perusahaan
  • Mengurangi risiko piutang tak tertagih
Manfaat akademis Menambah penerapan Random Forest pada kasus regresi data piutang usaha perusahaan jasa aktivasi merek — bidang yang masih jarang diteliti, karena penelitian sejenis umumnya merumuskannya sebagai klasifikasi.
[BAB I] batasan-masalah

Batasan Masalah

NoBatasanKonsekuensi
01 Rekap invoice PT Kreasi Pandawa Sakti 2019–2026 dari lima berkas CSV Tidak mencakup data di luar periode tersebut
02 Target bersifat kontinu — lama pembayaran dalam hari, bukan klasifikasi Metrik yang dipakai regresi, bukan akurasi
03 Random Forest Regression: 300 pohon, kedalaman maksimum 12, max_features="sqrt" Algoritma lain tidak dibandingkan secara empiris
04 Terbatas pada invoice berstatus PAID dengan tanggal pembayaran lengkap Model hanya belajar dari invoice yang sudah lunas
05 Outlier di-cap pada 90 hari berdasarkan aturan IQR 15 baris dibuang — lihat A7
Catatan atas batasan 04 Ini konsekuensi metodologis, bukan pilihan: model regresi mustahil belajar dari baris yang belum memiliki nilai target.
[BAB II] metrik-evaluasi

Metrik Evaluasi yang Dipakai

\[ R^{2} \;=\; 1 - \frac{\sum_{i=1}^{n}\bigl(y_i - \hat{y}_i\bigr)^{2}} {\sum_{i=1}^{n}\bigl(y_i - \bar{y}\bigr)^{2}} \]

Proporsi keragaman data yang berhasil dijelaskan model. Bernilai 1 bila prediksi sempurna, 0 bila model tidak lebih baik daripada sekadar menebak rata-rata.

\[ \mathrm{RMSE} \;=\; \sqrt{\tfrac{1}{n}\sum_{i=1}^{n}\bigl(y_i-\hat{y}_i\bigr)^{2}} \]
\[ \mathrm{MAE} \;=\; \tfrac{1}{n}\sum_{i=1}^{n}\bigl|\,y_i-\hat{y}_i\,\bigr| \]

Keterangan lambang

\(y_i\)  — lama pembayaran aktual invoice ke-\(i\) (hari)
\(\hat{y}_i\) — lama pembayaran prediksi model (hari)
\(\bar{y}\)  — rata-rata lama pembayaran pada data uji
\(n\)  — banyaknya data uji, dalam penelitian ini \(n=90\)
Mengapa RMSE dan MAE keduanya dilaporkan RMSE mengkuadratkan galat sehingga menghukum kesalahan besar lebih berat; MAE memperlakukan semua galat setara. Selisih keduanya menunjukkan ada tidaknya segelintir prediksi yang meleset jauh.
Kategori penelitian Data mining → prediksi → teknik regresi (supervised learning, target numerik kontinu) → algoritma Random Forest.
[BAB II] studi-literatur

Penelitian Terdahulu & Kebaruan

PenelitiFokusMetodeHasil
Appel dkk. (2019) Prediksi hasil pembayaran invoice pada perusahaan multinasional Machine learning — klasifikasi Akurasi hingga 77%
Appel dkk. (2020) Prediksi waktu pembayaran piutang usaha pada sebuah bank Model berbasis pohon — klasifikasi Akurasi 81%
Nursyahbani dkk. (2026) Keterlambatan pinjaman online di Indonesia Random Forest vs XGBoost RF unggul sensitivitas, XGBoost unggul presisi
Perhatian saat membandingkan Angka 77% dan 81% di atas adalah akurasi klasifikasi, bukan regresi. Keduanya tidak sebanding langsung — memprediksi berapa hari jauh lebih sulit daripada memprediksi label telat / tidak telat.
Kebaruan penelitian ini: (1) dirumuskan sebagai regresi — memprediksi lama pembayaran dalam hari, bukan klasifikasi status; (2) data piutang perusahaan jasa aktivasi merek; (3) model diintegrasikan langsung ke sistem keuangan yang dipakai sehari-hari, bukan berhenti sebagai notebook.
[BAB I] metodologi

Kerangka Kerja CRISP-DM

flowchart TD
    subgraph "CRISP-DM (Cross-Industry Standard Process for Data Mining)"
        direction LR
        B[Business Understanding<br/>Pemahaman Bisnis] --> D[Data Understanding<br/>Pemahaman Data]
        D --> P[Data Preparation<br/>Persiapan Data]
        P --> M[Modeling<br/>Pemodelan]
        M --> E[Evaluation<br/>Evaluasi]
        E --> Dep[Deployment<br/>Penerapan]
        Dep -.->|umpan balik| B
        M -.->|iteratif| P
        E -.->|iteratif| B
        D -.->|iteratif| B
    end

    B1[Wawancara Finance,<br/>Ops, BOD] --> B
    D1[857 baris dari<br/>5 CSV] --> D
    P1[Pra-pemrosesan<br/>→ 446 baris<br/>12 fitur] --> P
    M1[Random Forest<br/>300 pohon<br/>Pipeline sklearn] --> M
    E1[CV 5-lipat<br/>R²=0,5563<br/>MAE=7,35 hr] --> E
    Dep1[ML Sidecar<br/>FastAPI + Go<br/>Joblib model] --> Dep
[BAB I] tahapan-crisp-dm

Apa yang Dikerjakan di Setiap Tahap

FaseTahapKegiatan konkret dalam penelitian ini
01Business Understanding Wawancara bagian Finance, Operations, dan BOD untuk mengidentifikasi masalah pemantauan piutang
02Data Understanding Eksplorasi struktur 5 berkas CSV, pemeriksaan kelengkapan tanggal dan distribusi target
03Data Preparation Pembersihan, deduplikasi, penyaringan, rekayasa 12 fitur, transformasi kolom
04Modeling Pelatihan Random Forest Regression 300 pohon dengan pipeline scikit-learn
05Evaluation Validasi silang 5-lipat pada data latih, lalu pengujian pada data uji yang tidak pernah dilihat model
06Deployment Integrasi model ke sistem keuangan lewat microservice — bukan berhenti sebagai notebook
Perkakas Seluruh analisis dan pemodelan dikerjakan dengan Python dan pustaka scikit-learn; penerapan memakai FastAPI (sidecar) dan Go (backend).
02

Data, Fitur & Model

Chaisar Wildan · 2311510669
BAB III Analisis Masalah dan Perancangan Solusi
Sumber data → funnel penyaringan → target & 12 fitur → rekayasa fitur → cara kerja Random Forest → setup eksperimen
[BAB III] sumber-data

Lima Berkas Rekap Invoice

Berkas CSVPeriodeInvoice Punya tgl. bayarRasio
rekap_invoice_older.csv2019–202215214293%
rekap_invoice_2023.csv2023959398%
rekap_invoice_2024.csv202422021799%
rekap_invoice_2025.csv2025316268%
rekap_invoice_2026.csv2026711927%
Total setelah deduplikasi2019–202685447856%
Perhatikan kolom rasio Invoice 2025 dan 2026 sebagian besar belum memiliki tanggal pembayaran — dari 316 invoice 2025, hanya 26 yang lunas tercatat. Inilah penyebab utama data menyusut, dan menjadi keterbatasan yang kami akui di BAB V.
apps/ml/scripts/fase1_load.py — dihitung ulang saat penyusunan presentasi
[BAB III] funnel-penyaringan

Funnel Penyaringan: 857 → 446 Baris

Penyusutan terbesar: −314 baris Terjadi karena invoice yang belum dibayar tidak memiliki nilai target — bukan karena data rusak. Rincian tiap tahap ada di slide cadangan A10.
[BAB III] split-data

Pembagian Data Latih dan Data Uji

Bersih446baris 100%
Latih356baris 80%
Uji90baris 20%

Cara pembagian

Pembagian acak dengan train_test_split, test_size=0.2, shuffle=True, dan random_state=42 supaya hasilnya dapat direproduksi oleh siapa pun yang menjalankan ulang skrip.

Mengapa data uji dikunci sejak awal

90 baris data uji tidak dilibatkan sama sekali dalam pelatihan maupun validasi silang. Ini menjamin metrik akhir mencerminkan kinerja pada data yang benar-benar baru bagi model.

[BAB III] variabel-target

Variabel Target

\[ y_i \;=\; \operatorname{tanggal\_bayar}_i \;-\; \operatorname{tanggal\_invoice}_i \qquad \text{(satuan: hari)} \]

Penanganan khusus saat menghitung

  • Format tanggal beragam — diseragamkan parse_date, termasuk nama bulan Bahasa Indonesia
  • Koreksi lintas tahun — bila \(y_i < -180\), tahun pembayaran ditambah satu; kesalahan penulisan tahun lazim terjadi di pergantian tahun
  • Nilai uang — string berformat Rupiah diubah ke bilangan riil oleh parse_money
Mengapa bukan kolom Inv. Aging yang sudah ada? Kolom Inv. Aging di spreadsheet tidak reliabel: kosong pada berkas 2025–2026 dan terpolusi pada berkas lama. Target karena itu dihitung ulang langsung dari selisih tanggal.
Konsekuensi Target dihitung dari sumber primer (tanggal), bukan dari kolom turunan yang sudah ada — sehingga tidak mewarisi kesalahan pencatatan sebelumnya.
[BAB III] variabel-prediktor

Dua Belas Fitur Prediktor

Numerik — 6 fitur

TOTALTotal nilai invoice
BudgetAnggaran proyek
Agency FeeBiaya agensi
VATPajak Pertambahan Nilai
WHT 23PPh Pasal 23
top_daysTermin pembayaran

Kategorikal — 4 fitur

klien8 tersering + Lainnya
bulanBulan terbit (1–12)
kuartalKuartal terbit (1–4)
source_yearTahun berkas sumber

Biner — 2 fitur

ada_poPunya nomor PO
ada_cePunya nomor C.E.
Prinsip pemilihan: cegah kebocoran data Hanya atribut yang sudah diketahui saat invoice diterbitkan yang dipakai.

Kolom Payment, Balance, Status, AR, dan Inv. Aging sengaja dibuang — semuanya baru terisi setelah pembayaran terjadi. Bila dipakai, model akan terlihat sangat akurat padahal sebenarnya sedang membaca jawaban.

Setelah one-hot encoding 12 fitur asli berkembang menjadi 38 kolom masukan model — lihat A15.
[BAB III] rekayasa-fitur

Lima Tahap Rekayasa Fitur

01Konversi uangString Rupiah menjadi bilangan riil lewat parse_money
02Bentuk top_daysDue Date − Inv. Date = lama termin dalam hari
03Ekstrak waktuDari tanggal invoice diambil bulan dan kuartal
04Fitur binerada_po & ada_ce dari kolom terisi / kosong
05Kelompokkan klien8 tersering, sisanya jadi kategori "Lainnya"

Mengapa klien dikelompokkan?

Ada 24 klien berbeda, sebagian hanya muncul satu-dua kali. Tanpa pengelompokan, one-hot encoding menghasilkan kolom yang sangat jarang terisi dan membuat model menghafal, bukan belajar.

Mengapa PO & C.E. dibuat biner?

Nomor PO dan C.E. adalah identitas unik — tidak ada pola yang bisa dipelajari darinya. Yang bermakna secara bisnis adalah ada atau tidaknya dokumen tersebut, karena menandakan kelengkapan administrasi.

[BAB III] pipeline-praproses

Pipeline Transformasi Kolom

ColumnTransformer([
    # 6 fitur numerik: isi kosong dengan median, lalu standardisasi
    ("num", Pipeline([
        ("imp", SimpleImputer(strategy="median")),
        ("sc",  StandardScaler())
    ]), NUM),

    # 4 fitur kategorikal: one-hot encoding
    ("cat", OneHotEncoder(handle_unknown="ignore"), CATE),

    # 2 fitur biner: diteruskan apa adanya
    ("bin", "passthrough", BIN),
])
apps/ml/scripts/fase2_4_model.py:55–59
Mengapa dibungkus Pipeline? Ini bukan sekadar kerapian kode, melainkan soal kebenaran metodologi.

Saat validasi silang, imputer dan scaler hanya belajar dari lipatan latih — bukan dari lipatan validasi. Tanpa pipeline, informasi dari data validasi bocor ke tahap praproses dan membuat metrik terlihat lebih baik daripada kenyataan.

handle_unknown="ignore" Membuat model tetap dapat memprediksi ketika bertemu klien baru yang belum pernah ada saat pelatihan.
[BAB II] arsitektur-random-forest

Cara Kerja Random Forest Regression

flowchart TB
    subgraph Input["Data Masukan"]
        X[12 Fitur Invoice<br/>→ 38 kolom setelah encoding]
    end

    subgraph Bootstrap["Bootstrap Sampling"]
        S1[Sampel Bootstrap 1]
        S2[Sampel Bootstrap 2]
        S3[Sampel Bootstrap ...]
        S300[Sampel Bootstrap 300]
    end

    subgraph Trees["Ensemble 300 Pohon Regresi"]
        T1[Pohon 1<br/>max_depth=12<br/>√38 ≈ 6 fitur/split]
        T2[Pohon 2<br/>max_depth=12<br/>√38 ≈ 6 fitur/split]
        T3[Pohon ...<br/>max_depth=12<br/>√38 ≈ 6 fitur/split]
        T300[Pohon 300<br/>max_depth=12<br/>√38 ≈ 6 fitur/split]
    end

    subgraph Prediction["Prediksi Akhir"]
        Avg[ŷ = rata-rata 300 pohon]
        Risk[Kategorisasi Risiko<br/>ŷ > top_days → Berisiko Telat<br/>ŷ ≤ top_days → Tepat Waktu]
    end

    X --> S1 & S2 & S3 & S300
    S1 --> T1
    S2 --> T2
    S3 --> T3
    S300 --> T300
    T1 & T2 & T3 & T300 --> Avg
    Avg --> Risk
[BAB II] sumber-keberagaman

Mengapa 300 Pohon Tidak Identik?

\[ \hat{y}(x) \;=\; \frac{1}{T}\sum_{t=1}^{T} h_t(x), \qquad T = 300 \]

dengan \(h_t\) pohon regresi ke-\(t\) dan \(x\) satu baris invoice. Agar perata-rataan bermanfaat, tiap \(h_t\) harus berbeda — dua mekanisme berikut yang menjaminnya.

01 · Bootstrap sampling

Setiap pohon dilatih pada sampel acak dengan pengembalian dari 356 data latih. Peluang satu baris tidak terpilih sama sekali:

\[ \Bigl(1-\tfrac{1}{n}\Bigr)^{n} \;\xrightarrow[n\to\infty]{}\; e^{-1} \approx 0{,}368 \]

Jadi rata-rata hanya ≈63,2% baris unik yang masuk ke tiap pohon; sisanya menjadi data out-of-bag.

02 · max_features = sqrt

Pada setiap titik percabangan, pohon hanya boleh mempertimbangkan

\[ m \;=\; \bigl\lfloor \sqrt{p} \bigr\rfloor \;=\; \bigl\lfloor \sqrt{38} \bigr\rfloor \;=\; 6 \]

kolom acak dari \(p=38\) kolom yang tersedia. Tanpa aturan ini semua pohon akan memilih pemecah terbaik yang sama dan menjadi nyaris kembar, sehingga perata-rataan tidak mengurangi galat apa pun.

Verifikasi pada model terlatih Kedalaman pohon rata-rata 12,0 (menyentuh batas max_depth), dengan rata-rata 62 daun per pohon. Penurunan variansinya dijelaskan di A5.
[BAB IV] lingkungan-percobaan

Konfigurasi Model dan Lingkungan

Hyperparameter Random Forest

ParameterNilaiKeterangan
n_estimators300Jumlah pohon
max_depth12Kedalaman maksimum
max_featuressqrt≈ 6 kolom per split
min_samples_split5Minimum untuk memecah node
min_samples_leaf2Minimum sampel per daun
bootstrapTrueSampling dengan pengembalian
random_state42Reprodusibilitas
apps/ml/scripts/fase2_4_model.py:64–66

Lingkungan

BahasaPython
Pustakascikit-learn, pandas, NumPy
BackendGo
Sidecar MLFastAPI
Basis dataPostgreSQL

Protokol evaluasi

  1. Validasi silang 5-lipat pada 356 data latih (tiap lipatan: 284 latih / 72 validasi)
  2. Pengujian akhir pada 90 data uji
  3. Analisis feature importance
Pengendali overfitting tambahan min_samples_leaf=2 dan min_samples_split=5 mencegah pohon tumbuh terlalu spesifik pada dataset yang relatif kecil.
03

Hasil, Integrasi & Penutup

Rilo Pamuji · 2311510461
BAB IV Implementasi dan Uji Coba  ·  BAB V Penutup
Hasil evaluasi → validasi silang → feature importance → integrasi sistem → demo prediksi → keterbatasan → kesimpulan & saran
[BAB IV] hasil-evaluasi

Kinerja Model pada 90 Data Uji

0,5563 55,6%
RMSE11,13hari akar rata-rata kuadrat galat — menghukum kesalahan besar
MAE7,35hari rata-rata galat absolut — meleset ± satu minggu

Cara membacanya secara praktis

prediksi 30 harikenyataan rata-rata 23–37 hari
RMSE > MAEsebagian kecil invoice meleset jauh
R² = 0,5563model menjelaskan 55,6% keragaman
Kesimpulan praktis Untuk keperluan menyusun urutan prioritas penagihan — bukan menjanjikan tanggal pasti kepada klien — ketelitian ini sudah memadai.

Selisih RMSE dan MAE (11,13 vs 7,35) menunjukkan mayoritas prediksi cukup dekat, namun ada segelintir invoice dari klien berpola tidak teratur yang meleset jauh. Justifikasi lengkap di A2.

apps/ml/scripts/fase2_4_model.py — scikit-learn 1.9.0, random_state=42
[BAB IV] prediksi-vs-aktual

Prediksi vs Aktual — 90 Data Uji

Cara membaca Garis putus-putus adalah prediksi sempurna \((\hat{y}=y)\). Semakin dekat titik ke garis, semakin akurat prediksinya.

Yang terlihat

  • Titik mengumpul di sekitar garis pada rentang 0–40 hari, tempat mayoritas invoice berada
  • Model terlalu berhati-hati pada nilai ekstrem — pembayaran sangat cepat diprediksi agak lambat, dan sebaliknya
  • Sifat ini wajar pada model rata-rata ensemble: merata-ratakan 300 pohon menarik prediksi ke arah tengah distribusi
[BAB IV] validasi-silang

Validasi Silang 5-Lipat

Hasil agregat

Rata-rata R²0,4523
Simpangan baku0,0520
Rentang0,4012–0,5365
RMSE rata-rata10,91 hr
MAE rata-rata7,33 hr
Mengapa ini bukti tidak overfit Kinerja stabil di kelima lipatan — simpangan baku hanya 0,05. Model yang overfit berkinerja sangat baik pada satu lipatan lalu anjlok pada lipatan lain.
Perhatikan arah selisihnya R² data uji (0,5563) justru lebih tinggi daripada rata-rata validasi silang (0,4523) — pola yang berlawanan dengan overfitting. Penjelasan lengkap di A11.
[BAB IV] feature-importance

Fitur Paling Berpengaruh

Menjawab rumusan masalah 03 Dua fitur teratas — top_days (23,48%) dan klien (17,40%) — menyumbang lebih dari 40% seluruh kekuatan prediksi. Nilai importance kolom hasil one-hot telah dijumlahkan kembali ke fitur asalnya; definisi formalnya di A6.
[BAB IV] interpretasi-bisnis

Mengapa Temuan Ini Masuk Akal

23,48%  top_days — termin pembayaran

TOP adalah kesepakatan kontraktual yang secara langsung membentuk ekspektasi kapan klien membayar. Invoice bertermin 60 hari secara alamiah dibayar lebih lambat daripada yang bertermin 14 hari. Wajar bila ini menjadi penentu terkuat.

17,40%  klien — identitas klien

Setiap klien memiliki kebiasaan pembayaran berbeda: ada yang taat termin, ada yang konsisten terlambat karena proses internalnya panjang. Pola ini sulit ditangkap regresi linear, tetapi mudah dipelajari pohon keputusan.

Temuan yang berlawanan dengan praktik saat ini Fitur bernilai uang (TOTAL 6,74%, Budget 6,71%) justru berkontribusi kecil. Artinya besar-kecilnya nilai invoice bukan penentu utama cepat-lambatnya pembayaran — padahal selama ini penagihan diurutkan berdasarkan nilai invoice terbesar.
Implikasi praktis: prioritas penagihan sebaiknya disusun berdasarkan kombinasi termin dan karakteristik klien, bukan nilai invoice.
[BAB IV] arsitektur-penerapan

Integrasi Model ke Sistem Keuangan

flowchart TB
    subgraph UI["Lapisan Antarmuka"]
        Dashboard["Dashboard BOD<br/>Panel Risiko Penagihan"]
        ARPage["AR Aging Finance<br/>Kolom Estimasi Lunas"]
    end

    subgraph Backend["Lapisan Aplikasi — Go"]
        Router["chi Router"]
        Client["paymentpred Client<br/>timeout 3 detik"]
        Fallback["Graceful Fallback<br/>Available = false"]
    end

    subgraph Sidecar["Lapisan ML — Python FastAPI"]
        Predict["POST /predict/payment-days<br/>POST /predict/payment-days/batch"]
        Model["Model Joblib<br/>Pipeline + RF 300 pohon"]
    end

    subgraph Storage["Lapisan Data"]
        DB[("PostgreSQL<br/>Data invoice")]
    end

    Dashboard --> Router
    ARPage --> Router
    Router --> DB
    Router --> Client
    Client -->|JSON batch| Predict
    Predict --> Model
    Model -->|prediksi hari| Client
    Client -.->|gagal / timeout| Fallback
    Fallback -.->|estimasi disembunyikan| Router

    style Sidecar fill:#EAF4FB,stroke:#1D4ED8
    style Backend fill:#F4F6F8,stroke:#1E3A5F
    style UI fill:#FFF6EC,stroke:#B45309
    style Storage fill:#EDF7F0,stroke:#16A34A
[BAB IV] demo-runtime

Contoh Prediksi Nyata

Masukan — invoice baru

KlienPT. Energizer Indonesia
TOTALRp233.000.000
top_days30 hari
ada_ce1 (punya nomor C.E.)
01Susun12 fitur jadi satu baris
02Prosespraproses + 300 pohon
03Rata-ratahasil seluruh pohon
37,1 hari
Estimasi lama pembayaran

prediksi 37,1>termin 30 hari
putusanBerisiko telat
Aturan kategorisasi \(\hat{y} > \texttt{top\_days}\) → berisiko telat; selain itu → tepat waktu. Pada 90 data uji: 61 berisiko telat, 29 tepat waktu.
[BAB IV] tampilan-sistem

Hasil Prediksi di Layar Pengguna

Dashboard BOD — panel Risiko Penagihan

Panel Risiko Penagihan pada Dashboard BOD

Menampilkan jumlah invoice yang diprediksi telat dan total outstanding yang berisiko.

AR Aging Finance — kolom Estimasi Lunas

Kolom Estimasi Lunas pada halaman AR Aging

Setiap baris invoice memperoleh estimasi hari dan penanda risiko: Berisiko telat atau Sesuai termin.

Model tidak berhenti sebagai notebook — hasilnya sudah tampil di dua halaman yang dipakai sehari-hari oleh manajemen dan bagian Keuangan.
[BAB IV] keterbatasan

Keterbatasan Penelitian

NoKeterbatasanUraian
01Dominasi data lama Dataset didominasi invoice 2019–2024 karena sebagian besar invoice 2025–2026 belum memiliki tanggal pembayaran lengkap. Pola pembayaran terkini belum sepenuhnya terwakili.
02Fitur terbatas Fitur hanya berasal dari data yang tersedia di spreadsheet. Faktor eksternal seperti kondisi ekonomi makro, siklus anggaran klien, atau riwayat komunikasi penagihan belum tercakup.
03Ukuran data Hanya 446 baris yang layak dilatih. Untuk metode ensemble, jumlah ini tergolong kecil sehingga membatasi kompleksitas pola yang dapat dipelajari.
04Tanpa pembanding empiris Algoritma lain (XGBoost, SVR, regresi linear) tidak dijalankan sebagai pembanding; pemilihan Random Forest berdasar pertimbangan teoretis.
Sikap kami Keempat keterbatasan ini kami sampaikan terbuka, dan semuanya menjadi dasar langsung bagi saran pengembangan pada BAB V.
[BAB V] kesimpulan-saran

Kesimpulan dan Saran

Kesimpulan — menjawab 3 rumusan masalah

01 Model regresi Random Forest berhasil dibangun dari 446 data invoice dengan 12 fitur prediktor, mengikuti kerangka CRISP-DM
02 Kinerja memadai: R² 0,5563, RMSE 11,13 hari, MAE 7,35 hari; validasi silang konsisten (rata-rata R² 0,4523) menunjukkan model tidak overfit
03 Fitur paling berpengaruh: top_days (23,48%) dan klien (17,40%)

Saran pengembangan

  • Menambah data tahun berikutnya, khususnya invoice 2025–2026 yang sudah lunas
  • Menambahkan fitur riwayat perilaku pembayaran klien (rata-rata keterlambatan sebelumnya)
  • Membandingkan secara empiris dengan XGBoost atau LightGBM
  • Optimasi hyperparameter dengan grid search atau random search
  • Pembaruan estimasi secara real-time seiring masuknya data pembayaran baru
Sidang Kuliah Kerja Praktek

Terima Kasih

Kami siap menerima pertanyaan dan masukan dari Bapak/Ibu penguji.

Dosen Pembimbing  Basuki Hari Prasetyo, S.Kom., M.Kom.
17 slide cadangan tersedia — tekan panah bawah (↓) pada slide berikutnya, atau Ctrl + Shift + F untuk mencari
A

Slide Cadangan

Tidak dibawakan dalam paparan — disiapkan untuk sesi tanya jawab.

Tekan panah bawah (↓) untuk menelusuri, atau Ctrl + Shift + F untuk mencari kata kunci seperti "outlier", "XGBoost", atau "bagging".
[LAMPIRAN] daftar-isi

Daftar Slide Cadangan

  • A1 Mengapa Random Forest, bukan XGBoost / linear / SVR?
  • A2 Mengapa R² 0,5563 dianggap memadai?
  • A3 Penurunan metrik evaluasi
  • A4 Kriteria pemecahan node (matematis)
  • A5 Bagging & penurunan variansi (matematis)
  • A6 Definisi feature importance (MDI)
  • A7 Aturan IQR dan cap 90 hari
  • A8 Mengapa 300 pohon dan max_depth 12?
  • A9 Detail lengkap 12 fitur
  • A10 Rincian penyusutan 857 → 446
  • A11 Selisih R² validasi silang vs data uji
  • A12 Potongan kode inti
  • A13 Bila ML sidecar mati — graceful fallback
  • A14 Distribusi target & statistik data
  • A15 Mengapa 12 fitur menjadi 38 kolom?
  • A16 Tepat waktu vs berisiko telat
  • A17 Reprodusibilitas — menjalankan ulang
A1 pemilihan-algoritma

Mengapa Random Forest, Bukan Algoritma Lain?

Kriteria kasus iniRegresi LinearSVR XGBoostRandom Forest
Hubungan non-linear antar variabelLemahBaikBaikBaik
Kategorikal kardinalitas tinggi (klien)LemahLemahBaikBaik
Tahan outlier, tanpa feature scalingLemahPerlu scalingCukupBaik
Stabil pada data kecil (446 baris)CukupCukupRawan overfitBaik
Menyediakan feature importanceKoefisienTidakYaYa
Sensitivitas terhadap tuningRendahTinggiTinggiRendah
Jawaban jujur Pada penelitian ini kami tidak menjalankan perbandingan empiris terhadap XGBoost, SVR, maupun regresi linear. Pemilihan Random Forest didasarkan pada pertimbangan teoretis di atas dan rujukan Breiman (2001). Perbandingan empiris sudah kami cantumkan sebagai saran pengembangan BAB V.

Nursyahbani dkk. (2026) membandingkan RF dan XGBoost pada kasus keterlambatan pinjaman online: RF unggul sensitivitas, XGBoost unggul presisi — keduanya kompetitif, tidak ada yang mutlak lebih baik.

A2 justifikasi-r2

Mengapa R² 0,5563 Dianggap Memadai?

01 · Sifat domain

Yang diprediksi adalah perilaku manusia dan proses administrasi klien — kapan tim keuangan klien memproses pembayaran. Ini bukan proses fisik deterministik. Pada domain perilaku, R² di atas 0,5 umumnya sudah dinilai bermanfaat.

02 · Ukuran galat relatif

\[ \frac{\mathrm{MAE}}{\overline{\texttt{top\_days}}} \;=\; \frac{7{,}35}{30} \;\approx\; 24\% \]

Terhadap termin paling umum 30 hari, galat tipikal ±24%. Untuk menyusun urutan prioritas — bukan menjanjikan tanggal pasti kepada klien — ketelitian ini memadai.

03 · Tujuan penggunaan

Model dipakai untuk memberi peringkat risiko, bukan untuk akuntansi presisi. Yang dibutuhkan Keuangan adalah: invoice mana yang perlu ditagih lebih dulu. Kesalahan beberapa hari tidak mengubah urutan prioritas.

04 · Perbandingan literatur

Appel dkk. melaporkan akurasi 77% (2019) dan 81% (2020) — namun itu metrik klasifikasi, bukan R² regresi. Keduanya tidak dapat dibandingkan langsung: memprediksi angka hari jauh lebih sulit daripada memprediksi label telat / tidak telat.

Yang tidak kami klaim Kami tidak mengklaim model ini sangat akurat. Sisa 44,4% keragaman belum dapat dijelaskan — sebagian besar berasal dari faktor yang tidak ada di data, seperti proses internal klien dan komunikasi penagihan.
A3 metrik-matematis

Penurunan Metrik Evaluasi

\[ R^{2} = 1 - \frac{SS_{\text{res}}}{SS_{\text{tot}}} = 1 - \frac{\sum_i (y_i-\hat{y}_i)^2}{\sum_i (y_i-\bar{y})^2} \]

\(SS_{\text{res}}\) jumlah kuadrat galat model; \(SS_{\text{tot}}\) jumlah kuadrat simpangan terhadap rata-rata. Bila model hanya menebak \(\bar{y}\), maka \(SS_{\text{res}}=SS_{\text{tot}}\) dan \(R^2=0\). \(R^2\) dapat bernilai negatif bila model lebih buruk daripada menebak rata-rata.

\[ \mathrm{RMSE} = \sqrt{\frac{1}{n}\sum_{i=1}^{n}(y_i-\hat{y}_i)^2} \]
\[ \mathrm{MAE} = \frac{1}{n}\sum_{i=1}^{n}\bigl|y_i-\hat{y}_i\bigr| \]
\[ \mathrm{MAPE} = \frac{100\%}{n}\sum_{i=1}^{n} \left| \frac{y_i-\hat{y}_i}{y_i} \right| \]
Mengapa MAPE tidak dipakai sebagai metrik utama MAPE membagi dengan \(y_i\). Banyak invoice dibayar sangat cepat (\(y_i\) kecil, bahkan 0), sehingga penyebutnya mendekati nol dan MAPE meledak — pada data uji nilainya 70,8% meski galat absolutnya hanya 7,35 hari. Metrik ini menyesatkan untuk kasus kami.

Hubungan RMSE dan MAE

\[ \mathrm{MAE} \;\le\; \mathrm{RMSE} \;\le\; \sqrt{n}\cdot \mathrm{MAE} \]

Kesamaan kiri tercapai hanya bila semua galat sama besar. Karena \(11{,}13 > 7{,}35\), distribusi galat kami tidak seragam — ada segelintir prediksi yang meleset jauh.

A4 kriteria-split

Kriteria Pemecahan Node

Pada regresi, tiap node dipecah untuk meminimumkan jumlah kuadrat galat di dalam anak-anaknya. Untuk node \(m\) dengan himpunan sampel \(Q_m\) dan \(N_m=|Q_m|\):

\[ \hat{c}_m = \frac{1}{N_m}\sum_{i \in Q_m} y_i \qquad H(Q_m) = \frac{1}{N_m}\sum_{i \in Q_m}\bigl(y_i - \hat{c}_m\bigr)^{2} \]

\(\hat{c}_m\) adalah nilai prediksi node (rata-rata target di dalamnya) dan \(H(Q_m)\) adalah variansi di node tersebut — inilah ukuran ketidakmurnian untuk kasus regresi.

Suatu kandidat pemecahan \(\theta=(j,t)\) — fitur ke-\(j\) pada ambang \(t\) — membelah \(Q_m\) menjadi \(Q_m^{\text{kiri}}\) dan \(Q_m^{\text{kanan}}\). Biayanya:

\[ G(Q_m,\theta) = \frac{N^{\text{kiri}}_m}{N_m} H\!\left(Q^{\text{kiri}}_m\right) + \frac{N^{\text{kanan}}_m}{N_m} H\!\left(Q^{\text{kanan}}_m\right) \]

Pemecahan terbaik adalah yang meminimumkan biaya tersebut:

\[ \theta^{*} = \arg\min_{\theta \in \Theta_m} G(Q_m,\theta) \]
Di sinilah max_features bekerja \(\Theta_m\) bukan seluruh kemungkinan pemecahan. Pada tiap node, hanya \(m=\lfloor\sqrt{38}\rfloor=6\) kolom yang dipilih acak untuk dievaluasi. Inilah yang membuat 300 pohon menempuh jalur yang berbeda-beda.

Kriteria berhenti pada model kami

  • Kedalaman mencapai max_depth = 12
  • Node berisi < min_samples_split = 5 sampel
  • Pemecahan akan menghasilkan daun < min_samples_leaf = 2 sampel
Kriteria "squared_error" — bawaan RandomForestRegressor scikit-learn
A5 bagging-variansi

Bagging dan Penurunan Variansi

Inilah alasan matematis mengapa ensemble mengungguli pohon tunggal. Misalkan \(T\) pohon, masing-masing berprediksi dengan variansi \(\sigma^{2}\) dan korelasi berpasangan \(\rho\) antar pohon. Variansi rata-ratanya:

\[ \operatorname{Var}\!\left(\frac{1}{T}\sum_{t=1}^{T} h_t(x)\right) \;=\; \rho\,\sigma^{2} \;+\; \frac{1-\rho}{T}\,\sigma^{2} \]

Membaca rumus ini

  • Suku kedua \(\dfrac{1-\rho}{T}\sigma^2\) mengecil saat \(T\) bertambah — inilah manfaat menambah pohon
  • Suku pertama \(\rho\sigma^{2}\) tidak terpengaruh \(T\). Berapa pun pohon ditambahkan, variansi tidak pernah turun di bawah batas ini
  • Karena itu menurunkan \(\rho\) sama pentingnya dengan memperbesar \(T\)
Konsekuensi rancangan Bootstrap dan max_features="sqrt" keduanya bekerja untuk menurunkan \(\rho\) — membuat pohon saling tidak berkorelasi. Tanpa keduanya, \(\rho \to 1\) dan ensemble tidak lebih baik daripada satu pohon.
Menjawab: "kenapa tidak 1000 pohon saja?" Karena suku kedua meluruh sebagai \(1/T\), manfaatnya jenuh. Dari 300 ke 1000 pohon, suku itu hanya turun ~0,23%\(\sigma^2\) — tidak sebanding dengan biaya komputasi 3,3× lipat.

Catatan: menambah \(T\) tidak pernah menyebabkan overfitting pada Random Forest — sifat khas bagging, berbeda dari boosting yang justru dapat memburuk bila iterasi terlalu banyak.

A6 definisi-mdi

Definisi Feature Importance (MDI)

scikit-learn memakai Mean Decrease in Impurity: kontribusi fitur \(j\) pada satu pohon adalah jumlah penurunan ketidakmurnian berbobot pada semua node yang memecah memakai fitur tersebut, lalu dirata-ratakan atas seluruh pohon dan dinormalisasi agar berjumlah satu.

\[ \mathrm{Imp}_t(j) = \!\!\sum_{m \,:\, j_m = j}\!\! \frac{N_m}{N}\Bigl[ H(Q_m) - G(Q_m, \theta_m) \Bigr], \qquad \mathrm{Imp}(j) = \frac{1}{T}\sum_{t=1}^{T}\mathrm{Imp}_t(j) \]
Agregasi kembali ke 12 fitur asli Fitur kategorikal pecah menjadi banyak kolom one-hot. Kami menjumlahkan importance seluruh kolom turunannya kembali ke fitur asal: \(\mathrm{Imp}(\texttt{klien}) = \sum_{k \in \text{one-hot}(\texttt{klien})} \mathrm{Imp}(k)\). Tanpa ini, kontribusi klien akan terdilusi ke 9 kolom dan tampak jauh lebih kecil daripada seharusnya.
Keterbatasan MDI yang kami akui MDI bias terhadap fitur berkardinalitas tinggi — fitur dengan banyak nilai unik punya lebih banyak kesempatan memecah node. top_days dan klien memang termasuk kategori itu. Permutation importance tidak bias demikian, namun tidak kami jalankan pada penelitian ini.

Karena itu kami menafsirkan peringkatnya bersama logika bisnis (slide Interpretasi Bisnis), bukan mengandalkan angka semata.

apps/ml/scripts/fase2_4_model.py:86–99
A7 aturan-iqr

Dasar Aturan IQR dan Cap 90 Hari

\[ \text{batas} = Q_3 + 3\,(Q_3 - Q_1) \]

Dihitung pada 461 baris

Q₁10harikuartil 1
Q₃30harikuartil 3
IQR20hariQ₃ − Q₁
Batas90hari30 + 3 × 20
Dibuang15baris3,3% dari 461
Mengapa pengali 3, bukan 1,5? Aturan Tukey memiliki dua ambang: \(1{,}5\times\mathrm{IQR}\) menandai outlier ringan, dan \(3\times\mathrm{IQR}\) menandai outlier ekstrem. Kami sengaja memilih kriteria yang lebih longgar agar hanya membuang kasus yang benar-benar ekstrem dan mempertahankan sebanyak mungkin data yang sah.

Dengan \(1{,}5\times\mathrm{IQR}\) batasnya menjadi \(30 + 1{,}5 \times 20 = 60\) hari, dan data yang dibuang akan jauh lebih banyak — padahal invoice bertermin 60 hari adalah hal yang lazim di perusahaan ini.

Apa yang dibuang, dan mengapa

  • Invoice dengan lama pembayaran hingga 380 hari — umumnya sengketa penagihan atau kesalahan pencatatan tanggal
  • Kasus semacam ini bukan pola pembayaran normal yang ingin dipelajari model
  • Bila dipertahankan, kasus ekstrem akan menarik prediksi seluruh invoice ke atas dan merusak ketelitian pada mayoritas data
apps/ml/scripts/fase2_4_model.py:46–48 — dihitung ulang
A8 pemilihan-hyperparameter

Mengapa 300 Pohon dan max_depth 12?

n_estimators = 300

  • Variansi ensemble meluruh sebagai \(\tfrac{1-\rho}{T}\sigma^2\) (lihat A5) — menurun cepat lalu jenuh
  • 300 dipilih sebagai titik aman di atas ambang konvergensi namun tetap ringan dijalankan (< 1 detik pada laptop)
  • Menambah pohon tidak menyebabkan overfitting pada Random Forest — hanya menambah waktu komputasi

max_depth = 12

  • Dengan hanya 356 baris latih, pohon tanpa batas kedalaman akan tumbuh sampai tiap daun berisi satu-dua sampel — praktis menghafal data
  • Kedalaman 12 memberi ruang cukup untuk pola bertingkat (klien → termin → bulan) tanpa menghafal
  • Diperkuat min_samples_leaf=2 dan min_samples_split=5
Verifikasi pada model terlatih Kedalaman pohon rata-rata 12,0 (menyentuh batas), rata-rata 62 daun per pohon dari 356 sampel latih. Artinya batas kedalaman memang aktif membatasi, bukan angka hiasan. Bandingkan: pohon tanpa batas pada 356 sampel dengan min_samples_leaf=2 dapat mencapai ~178 daun.
Jujur soal proses penentuan Parameter ini ditetapkan di awal berdasarkan pertimbangan di atas dan praktik umum, bukan hasil grid search. Optimasi hyperparameter sistematis sudah kami tulis sebagai saran nomor 4 BAB V.
A9 detail-fitur

Detail Lengkap Dua Belas Fitur

NoNama fiturJenis AsalCara perolehan / rekayasa
01TOTALNumerikKolom CSVString Rupiah → bilangan riil via parse_money
02BudgetNumerikKolom CSVIdem
03Agency FeeNumerikKolom CSVIdem
04VATNumerikKolom CSVIdem
05WHT 23NumerikKolom CSVIdem
06top_daysNumerikRekayasaDue Date − Inv. Date dalam hari
07klienKategorikalRekayasa8 klien tersering (value_counts().head(8)), sisanya "Lainnya"
08bulanKategorikalRekayasainv_dt.dt.month (1–12)
09kuartalKategorikalRekayasa(bulan − 1) // 3 + 1 (1–4)
10source_yearKategorikalRekayasaTahun berkas CSV asal (2019/2023/2024/2025/2026)
11ada_poBinerRekayasa1 bila kolom PO terisi (bukan kosong/0/−)
12ada_ceBinerRekayasa1 bila kolom C.E No. terisi
Separuh fitur adalah hasil rekayasa Enam fitur diambil langsung dari kolom CSV; enam sisanya kami bentuk sendiri. Dan justru dua fitur rekayasa — top_days dan klien — yang menjadi dua prediktor terkuat.
A10 funnel-rinci

Rincian Penyusutan 857 → 446 Baris

NoTahap penyaringan SisaDibuang Alasan metodologis
00Gabungan 5 berkas CSV857Data mentah awal
01Deduplikasi854−3Duplikat pada kunci (Client, C.E No., Inv. Date, TOTAL, tahun)
02Saring status PAID792−62Invoice belum lunas tidak punya nilai target
03Target y dapat dihitung478−314Tanggal pembayaran kosong — mayoritas invoice 2025–2026
04Saring y ≥ 0461−17Anomali tanggal (pembayaran tercatat mendahului invoice)
05Cap outlier IQR446−15y > 90 hari — kasus ekstrem / sengketa
Penyusutan terbesar ada di tahap 03 Ini bukan data rusak, melainkan invoice yang memang belum dibayar saat data diambil. Model regresi mustahil belajar dari baris tanpa nilai target.
Apakah 446 cukup? Untuk Random Forest dengan 12 fitur, 446 baris tergolong kecil namun memadai — dibuktikan validasi silang yang stabil di kelima lipatan (simpangan baku R² hanya 0,052). Penambahan data tetap menjadi saran utama kami.
A11 selisih-cv-uji

Mengapa R² Validasi Silang Berbeda dari Data Uji?

Validasi silangrata-rata R² 0,4523 · tiap model dilatih dengan 284 baris
Data uji0,5563 · model final dilatih dengan 356 baris

01 · Jumlah data latih berbeda

Pada validasi silang, tiap model hanya dilatih dengan 284 baris (4/5 dari 356). Model final dilatih dengan seluruh 356 baris. Lebih banyak data latih → model sedikit lebih baik.

02 · Variasi acak pada data kecil

Data uji hanya 90 baris. Pada ukuran sekecil ini R² wajar berfluktuasi. Rentang antar lipatan CV sendiri sudah lebar — 0,4012–0,5365 — dan 0,5563 hanya sedikit di atas lipatan terbaik.

Arah selisihnya justru menenangkan Gejala overfitting adalah kinerja tinggi saat latih lalu anjlok saat uji. Yang kami temukan sebaliknya: kinerja pada data uji sedikit lebih tinggi daripada rata-rata validasi silang. Ini menunjukkan model generalisasi dengan baik, bukan menghafal.
Kapan justru perlu khawatir Bila R² uji jauh lebih rendah daripada CV (misalnya 0,45 → 0,20), barulah itu indikasi kuat overfitting atau data uji yang tidak representatif.
Nilai mana yang lebih jujur dilaporkan? Keduanya. Validasi silang menggambarkan stabilitas; data uji menggambarkan kinerja pada data yang belum pernah dilihat. Karena itu kami melaporkan keduanya berdampingan, tidak memilih yang paling bagus saja.
A12 kode-inti

Potongan Kode Inti

Definisi fitur & praproses

NUM  = ["TOTAL","Budget","Agency Fee",
        "VAT","WHT 23","top_days"]
CATE = ["klien","bulan","kuartal","source_year"]
BIN  = ["ada_po","ada_ce"]

pre = ColumnTransformer([
    ("num", Pipeline([
        ("imp", SimpleImputer(strategy="median")),
        ("sc",  StandardScaler())]), NUM),
    ("cat", OneHotEncoder(
        handle_unknown="ignore"), CATE),
    ("bin", "passthrough", BIN),
])

Model & evaluasi

rf = RandomForestRegressor(
    n_estimators=300, max_depth=12,
    max_features="sqrt",
    min_samples_split=5, min_samples_leaf=2,
    bootstrap=True, random_state=42, n_jobs=-1)

pipe = Pipeline([("pre", pre), ("m", rf)])
kf   = KFold(n_splits=5, shuffle=True,
             random_state=42)

cv = cross_validate(pipe, Xtr, ytr, cv=kf,
     scoring={"r2":"r2", ...})
pipe.fit(Xtr, ytr)          # hanya data latih
pred = pipe.predict(Xte)    # data uji
Perhatikan dua baris terakhir pipe.fit hanya dipanggil pada Xtr — data latih. Data uji baru muncul di baris berikutnya untuk prediksi. Tidak ada kebocoran data.
apps/ml/scripts/fase2_4_model.py:50–76 · apps/ml/app/main.py:309–362
A13 graceful-fallback

Bila ML Sidecar Mati — Graceful Fallback

flowchart TD
    A[Pengguna buka<br/>Dashboard BOD / AR Aging] --> B[Go backend ambil<br/>invoice dari PostgreSQL]
    B --> C{Panggil ML sidecar<br/>timeout 3 detik}
    C -->|Berhasil| D[Estimasi hari + penanda<br/>risiko ditampilkan]
    C -->|Timeout / mati /<br/>model tidak ada| E[Available = false]
    E --> F[Kolom estimasi disembunyikan<br/>Halaman tetap tampil normal]
    D --> G[Halaman selesai dirender]
    F --> G

    style D fill:#EDF7F0,stroke:#16A34A
    style E fill:#FFF6EC,stroke:#B45309
    style F fill:#FFF6EC,stroke:#B45309
Prinsip perancangan Dashboard tidak boleh gagal hanya karena komponen ML bermasalah. Prediksi bersifat nice-to-have; data piutang inti tetap harus tampil.

Enam kondisi pemicu fallback

Alamat sidecar tidak dikonfigurasi · gagal menyusun permintaan JSON · timeout 3 detik terlampaui · status HTTP bukan 200 · respons gagal diuraikan · peta prediksi kosong

Keputusan sadar Pada kondisi fallback, sistem tidak menampilkan angka tebakan. Kolom estimasi dikosongkan (—) agar pengguna tidak salah menganggapnya hasil model.
apps/api/internal/service/paymentpred/paymentpred.go
A14 distribusi-target

Distribusi Lama Pembayaran (446 Baris Final)

Statistik target

Rerata19,9hari
Median14harinilai tengah
Std15,2hari
Min–Maks0–85hari
Distribusi miring ke kanan Mayoritas invoice dibayar dalam dua minggu pertama (median 14 hari), dengan ekor panjang ke arah pembayaran lambat. Karena rerata (19,9) > median (14), MAE lebih representatif daripada RMSE untuk menggambarkan galat tipikal.
Dihitung ulang dari 446 baris final — apps/ml/scripts/fase2_4_model.py
A15 dimensi-encoding

Mengapa 12 Fitur Menjadi 38 Kolom?

BlokKolom
Numerik (scaled)6
klien (8 + Lainnya)9
bulan (1–12)12
kuartal (1–4)4
source_year5
Biner (passthrough)2
Total38

One-hot encoding mengubah tiap kategori menjadi satu kolom biner tersendiri, karena algoritma tidak boleh menganggap "bulan 12 lebih besar dari bulan 1" sebagai hubungan numerik.

Catatan koreksi terhadap dokumen proposal Dokumen proposal menyebut ±29 kolom — itu estimasi awal. Perhitungan ulang langsung dari pipeline yang berjalan menghasilkan 38 kolom, dan angka inilah yang kami pakai. Konsekuensinya max_features="sqrt" berarti \(\lfloor\sqrt{38}\rfloor = 6\) kolom acak per percabangan, bukan \(\sqrt{29}\approx 5\).
A16 kategorisasi-risiko

Kategorisasi Risiko pada 90 Data Uji

\[ \text{risiko}(x) = \begin{cases} \text{Berisiko telat}, & \hat{y}(x) > \texttt{top\_days}(x) \\[2pt] \text{Tepat waktu}, & \text{selainnya} \end{cases} \]
61 invoiceberisiko telat (67,8%)
29 invoicetepat waktu (32,2%)
Perlu ditegaskan Ini adalah turunan dari keluaran regresi, bukan model klasifikasi tersendiri. Model tetap memprediksi angka hari; kategorisasi hanya membandingkan angka itu dengan termin yang disepakati.

Proporsi berisiko telat yang tinggi (67,8%) sejalan dengan kondisi nyata — outstanding piutang Rp1,70 miliar memang menunjukkan banyak invoice melewati termin.

A17 reprodusibilitas

Reprodusibilitas — Menjalankan Ulang

# 1. Ekstraksi & penggabungan 5 berkas CSV
python3 apps/ml/scripts/fase1_load.py

# 2. Praproses, pemodelan, evaluasi
python3 apps/ml/scripts/fase2_4_model.py

# 3. Latih ulang pada seluruh data & simpan model
python3 apps/ml/scripts/fase5_train_save.py

# 4. Jalankan sidecar inferensi
uvicorn apps.ml.app.main:app --port 8000

Penjamin reprodusibilitas

  • random_state=42 pada pembagian data, Random Forest, dan KFold
  • Seluruh praproses terbungkus dalam satu Pipeline
  • Berkas CSV sumber tersimpan di repositori
Angka pada presentasi ini Seluruhnya dihasilkan ulang dengan menjalankan skrip di samping — bukan disalin manual dari dokumen. Karena itu dapat diverifikasi kapan saja.