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 Setelah paparan, kami siap menerima pertanyaan dan masukan dari Bapak/Ibu penguji.
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
Invoice896barisrekap 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

  • 896 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 104 hari berdasarkan aturan IQR 42 baris dibuang (5,0%)
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[896 baris dari<br/>5 CSV] --> D
    P1[Pra-pemrosesan<br/>→ 793 baris<br/>12 fitur] --> P
    M1[Random Forest<br/>300 pohon<br/>Pipeline sklearn] --> M
    E1[CV 5-lipat<br/>R²=0,5312<br/>MAE=7,92 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.csv202422021899%
rekap_invoice_2025.csv202531630998%
rekap_invoice_2026.csv20261099486%
Total setelah deduplikasi2019–202689283694%
Data disegarkan sampai 22 Juli 2026 Rekap 2025–2026 kini sudah terisi tanggal pembayarannya — 94% invoice memiliki target yang dapat dihitung. Pemutakhiran ini menaikkan data layak latih dari 446 menjadi 793 baris, hampir dua kali lipat.
apps/ml/scripts/fase1_load.py — dihitung ulang saat penyusunan presentasi
[BAB III] funnel-penyaringan

Funnel Penyaringan: 896 → 793 Baris

Penyusutan terbesar kini: −55 baris non-PAID Setelah data disegarkan, hampir semua invoice PAID sudah punya tanggal bayar (837 → 836). Pengurangan berarti tinggal penyaringan status dan cap outlier.
[BAB III] cap-outlier

Tahap Terakhir Funnel: Cap Outlier

\[ \text{batas} = Q_3 + 3\,(Q_3 - Q_1) \]
Q₁12harikuartil 1
Q₃35harikuartil 3
Batas104hari35 + 3 × 23
Dibuang42baris5,0% dari 835
apps/ml/scripts/fase2_4_model.py:46–48 — dihitung ulang
Mengapa pengali 3, bukan 1,5 Aturan Tukey memiliki dua ambang: \(1{,}5\times\mathrm{IQR}\) untuk outlier ringan dan \(3\times\mathrm{IQR}\) untuk outlier ekstrem. Kami memilih kriteria yang lebih longgar agar hanya membuang kasus yang benar-benar ekstrem.
  • Yang dibuang: invoice dengan lama pembayaran hingga 380 hari — umumnya sengketa penagihan atau kesalahan pencatatan tanggal
  • Bila dipertahankan, kasus ekstrem menarik prediksi seluruh invoice ke atas dan merusak ketelitian pada mayoritas data

Dengan ambang \(1{,}5\times\mathrm{IQR}\) batasnya menjadi 69 hari — padahal invoice bertermin 60 hari lazim di perusahaan ini.

[BAB III] split-data

Pembagian Data Latih dan Data Uji

Bersih793baris 100%
Latih634baris 80%
Uji159baris 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

159 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] data-understanding

Seperti Apa Sebaran Lama Pembayaran?

Median17hari
Rerata23,6hari
Maks104hari
Distribusi miring ke kanan Mayoritas invoice lunas dalam tiga minggu pertama, dengan ekor panjang ke arah pembayaran lambat. Karena rerata (23,6) melebihi median (17), MAE lebih representatif daripada RMSE untuk menggambarkan galat tipikal.

Dihitung ulang dari 793 baris final.

[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: 6 numerik + 30 kolom one-hot + 2 biner.
[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 634 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. Galat tiap pohon saling meniadakan saat dirata-ratakan.
[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 634 data latih (tiap lipatan: 507–508 latih / 126–127 validasi)
  2. Pengujian akhir pada 159 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 159 Data Uji

0,5312 55,6%
RMSE12,90hari akar rata-rata kuadrat galat — menghukum kesalahan besar
MAE7,92hari 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,5312model 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 (12,90 vs 7,92) menunjukkan mayoritas prediksi cukup dekat, namun ada segelintir invoice dari klien berpola tidak teratur yang meleset jauh. Untuk menyusun urutan prioritas, ketelitian ini memadai.

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

Prediksi vs Aktual — 159 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,5222
Simpangan baku0,0690
Rentang0,4307–0,5954
RMSE rata-rata12,58 hr
MAE rata-rata8,47 hr
Mengapa ini bukti tidak overfit Kinerja stabil di kelima lipatan — simpangan baku 0,069. Model yang overfit berkinerja sangat baik pada satu lipatan lalu anjlok pada lipatan lain.
Perhatikan arah selisihnya R² data uji (0,5312) justru lebih tinggi daripada rata-rata validasi silang (0,5222) — pola yang berlawanan dengan overfitting, karena model final dilatih dengan lebih banyak data (634 baris) daripada tiap lipatan validasi silang (507–508 baris).
[BAB IV] feature-importance

Fitur Paling Berpengaruh

Menjawab rumusan masalah 03 Dua fitur teratas — klien (23,76%) dan top_days (22,89%) — menyumbang lebih dari 46% seluruh kekuatan prediksi. Nilai importance kolom hasil one-hot telah dijumlahkan kembali ke fitur asalnya; agar tidak terdilusi dan dapat dibandingkan adil dengan fitur numerik.
[BAB IV] interpretasi-bisnis

Mengapa Temuan Ini Masuk Akal

23,76%  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 — dan kini menjadi penentu terkuat.

22,89%  top_days — termin pembayaran

TOP adalah kesepakatan kontraktual yang langsung membentuk ekspektasi kapan klien membayar. Invoice bertermin 60 hari secara alamiah dibayar lebih lambat daripada yang bertermin 14 hari. Selisihnya dengan klien tipis (23,76% vs 22,89%) — keduanya praktis sekuat.

Temuan yang berlawanan dengan praktik saat ini Fitur bernilai uang (TOTAL 5,36%, Budget 5,43%) 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
34,3 hari
Estimasi lama pembayaran

prediksi 34,3>termin 30 hari
putusanBerisiko telat
Aturan kategorisasi \(\hat{y} > \texttt{top\_days}\) → berisiko telat; selain itu → tepat waktu. Pada 159 data uji: 125 berisiko telat, 34 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
01Cakupan tahun berjalan Data sudah disegarkan sampai 22 Juli 2026, sehingga pola terkini kini terwakili. Yang tersisa: invoice yang terbit menjelang tanggal pengambilan data belum sempat jatuh tempo, jadi tahun berjalan tetap kurang terwakili dibanding tahun penuh.
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 793 baris yang layak dilatih. Untuk metode ensemble, jumlah ini tergolong kecil sehingga membatasi kompleksitas pola yang dapat dipelajari.
04Pembanding terbatas Sudah ada pembanding baseline (rata-rata) yang dikalahkan 31,5% pada RMSE, tetapi algoritma lain (XGBoost, SVR, regresi linear) belum dijalankan sebagai pembanding.
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 793 data invoice dengan 12 fitur prediktor, mengikuti kerangka CRISP-DM
02 Kinerja memadai: R² 0,5312, RMSE 12,90 hari, MAE 7,92 hari; validasi silang konsisten (rata-rata R² 0,5222) menunjukkan model tidak overfit
03 Fitur paling berpengaruh: klien (23,76%) dan top_days (22,89%)

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.
Teknik Informatika — Fakultas Teknologi Informasi, Universitas Budi Luhur
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,5312 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 104 hari
  • A8 Mengapa 300 pohon dan max_depth 12?
  • A9 Detail lengkap 12 fitur
  • A10 Rincian penyusutan 896 → 793
  • 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 sedang (793 baris)CukupCukupRawan overfitBaik
Menyediakan feature importanceKoefisienTidakYaYa
Sensitivitas terhadap tuningRendahTinggiTinggiRendah
Sejauh mana sudah dibandingkan Kami sudah menguji terhadap baseline penebak rata-rata (DummyRegressor): Random Forest mengungguli RMSE-nya 31,5% (12,90 vs 18,84 hari). Namun kami belum membandingkan dengan XGBoost, SVR, atau regresi linear — pemilihan Random Forest untuk itu masih berdasar pertimbangan teoretis di atas dan rujukan Breiman (2001), dan perbandingannya tercantum sebagai saran 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,5312 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{,}92}{30} \;\approx\; 26\% \]

Terhadap termin paling umum 30 hari, galat tipikal ±26%. 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 74,1% meski galat absolutnya hanya 7,92 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 \(12{,}90 > 7{,}92\), 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 104 Hari

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

Dihitung pada 835 baris

Q₁10harikuartil 1
Q₃30harikuartil 3
IQR23hariQ₃ − Q₁
Batas90hari30 + 3 × 20
Dibuang42baris5,0% dari 835
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 \(35 + 1{,}5 \times 23 \approx 69\) 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 634 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 634 sampel latih. Artinya batas kedalaman memang aktif membatasi, bukan angka hiasan. Bandingkan: pohon tanpa batas pada 634 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 896 → 793 Baris

NoTahap penyaringan SisaDibuang Alasan metodologis
00Gabungan 5 berkas CSV896Data mentah awal
01Deduplikasi892−4Duplikat pada kunci (Client, C.E No., Inv. Date, TOTAL, tahun)
02Saring status PAID837−55Invoice belum lunas tidak punya nilai target
03Target y dapat dihitung836−1Hanya satu baris tersisa tanpa tanggal pembayaran
04Saring y ≥ 0835−1Anomali tanggal (pembayaran tercatat mendahului invoice)
05Cap outlier IQR793−42y > 104 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 793 cukup? Untuk Random Forest dengan 12 fitur, 793 baris sudah memadai — dibuktikan validasi silang yang stabil di kelima lipatan (simpangan baku R² 0,069). Penambahan data tetap menjadi saran utama kami.
A11 selisih-cv-uji

Mengapa R² Validasi Silang Berbeda dari Data Uji?

Validasi silangrata-rata R² 0,5222 · tiap model dilatih dengan ±507 baris
Data uji0,5312 · model final dilatih dengan 634 baris

01 · Jumlah data latih berbeda

Pada validasi silang, tiap model hanya dilatih dengan 507–508 baris (4/5 dari 634). Model final dilatih dengan seluruh 634 baris — cukup untuk menjelaskan selisih setipis 0,009.

02 · Variasi acak pada data kecil

Data uji hanya 159 baris. Pada ukuran sekecil ini R² wajar berfluktuasi. Rentang antar lipatan CV sendiri sudah lebar — 0,4307–0,5954 — dan 0,5312 justru berada di dalam rentang itu.

Selisihnya kini sangat tipis Rata-rata validasi silang 0,5222 dan data uji 0,5312 — hanya berbeda 0,009. Gejala overfitting adalah kinerja tinggi saat latih lalu anjlok saat uji; di sini keduanya praktis berimpit, yang menunjukkan model generalisasi dengan baik.
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 (793 Baris Final)

Statistik target

Rerata23,6hari
Median17harinilai tengah
Std18,4hari
Min–Maks0–104hari
Distribusi miring ke kanan Mayoritas invoice dibayar dalam tiga minggu pertama (median 17 hari), dengan ekor panjang ke arah pembayaran lambat. Karena rerata (23,6) > median (17), MAE lebih representatif daripada RMSE untuk menggambarkan galat tipikal.
docs/figures/metrics.json — sumber yang sama dengan laporan
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 159 Data Uji

\[ \text{risiko}(x) = \begin{cases} \text{Berisiko telat}, & \hat{y}(x) > \texttt{top\_days}(x) \\ \text{Tepat waktu}, & \text{selainnya} \end{cases} \]
125 invoiceberisiko telat (78,6%)
34 invoicetepat waktu (21,4%)
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 (78,6%) 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.