Prediksi Risiko Kredit Nasabah Menggunakan KNN, Fuzzy KNN, dan Random Forest

1 Ringkasan Eksekutif

Penelitian ini membahas penerapan tiga pendekatan klasifikasi yaitu K-Nearest Neighbor (KNN), Fuzzy KNN, dan Random Forest. Untuk memprediksi apakah seorang nasabah termasuk kategori good credit atau bad credit pada dataset German Credit. Analisis diawali dengan encoding variabel kategorik, pemeriksaan distribusi kelas, penanganan outlier melalui winsorizing, pembagian data training–testing, penanganan ketidakseimbangan kelas menggunakan SMOTE, lalu dilanjutkan ke tahap pemodelan dan evaluasi.

Berdasarkan hasil yang tercantum pada pengerjaan awal, Random Forest Unbalanced memberi performa tertinggi pada metrik keseluruhan seperti Accuracy (0.7850) dan ROC AUC (0.8140). Namun, jika prioritas analisis adalah mendeteksi bad credit dan menekan biaya false negative, maka Random Forest Balanced menjadi pilihan yang lebih relevan karena memiliki F1 Bad Credit lebih tinggi (0.5593) dan biaya total yang lebih baik dibandingkan Random Forest Unbalanced.

Inti rekomendasi: untuk konteks manajemen risiko kredit, model yang paling layak diusulkan sebagai model produksi adalah Random Forest Balanced karena memberikan kompromi terbaik antara performa keseluruhan, kemampuan mendeteksi kelas minoritas, dan evaluasi biaya.

2 Pendahuluan

2.1 Latar Belakang

Perkembangan industri keuangan yang semakin bergantung pada data mendorong lembaga perbankan dan pembiayaan untuk menerapkan analisis data dalam proses penilaian kelayakan kredit. Salah satu persoalan utama dalam credit scoring adalah bagaimana mengklasifikasikan calon debitur ke dalam kategori risiko kredit baik maupun buruk secara cepat, konsisten, dan objektif. Dataset Statlog (German Credit Data) dari UCI merupakan salah satu dataset acuan yang banyak digunakan untuk penelitian klasifikasi risiko kredit. Dataset ini terdiri dari 1.000 data, 20 atribut, dan bertujuan mengklasifikasikan debitur ke dalam dua kelas, yaitu good credit dan bad credit. UCI juga menegaskan bahwa data ini berkaitan dengan masalah klasifikasi dan memiliki cost matrix, sehingga kesalahan klasifikasi pada kredit bermasalah menjadi aspek yang penting untuk diperhatikan.

Dalam praktiknya, permasalahan penilaian kredit tidak hanya berkaitan dengan membangun model yang akurat, tetapi juga dengan kemampuan model dalam mengenali kelas minoritas, yaitu debitur berisiko tinggi. Pada data kredit, distribusi kelas sering kali tidak seimbang sehingga model cenderung lebih mudah mengenali kelas mayoritas daripada kelas minoritas. Kondisi ini berbahaya karena kesalahan dalam mengidentifikasi debitur bermasalah dapat menimbulkan kerugian finansial yang lebih besar bagi lembaga pemberi kredit. Oleh sebab itu, evaluasi model klasifikasi kredit tidak cukup hanya berdasarkan akurasi, melainkan juga perlu mempertimbangkan metrik seperti F1-score, recall, dan ROC AUC agar kemampuan model dalam mendeteksi kredit buruk dapat dinilai secara lebih komprehensif.

Berbagai penelitian menunjukkan bahwa penanganan class imbalance dengan teknik resampling seperti SMOTE dapat meningkatkan kemampuan model dalam mendeteksi kelas minoritas. Pada konteks credit risk assessment, penerapan SMOTE bersama algoritma pembelajaran mesin, khususnya model ensemble seperti Random Forest, terbukti mampu meningkatkan precision, recall, dan F1-score dibandingkan pelatihan pada data yang tidak seimbang. Temuan tersebut menunjukkan bahwa kombinasi metode penyeimbangan data dan algoritma klasifikasi yang tepat berpotensi menghasilkan sistem prediksi kredit yang lebih andal. Atas dasar itu, penelitian ini dilakukan untuk menganalisis performa beberapa algoritma klasifikasi pada data German Credit, sekaligus mengevaluasi pengaruh penanganan data tidak seimbang terhadap kualitas prediksi risiko kredit.

2.2 Rumusan Masalah

  1. Bagaimana karakteristik data German Credit dalam merepresentasikan risiko kredit nasabah?
  2. Bagaimana performa algoritma klasifikasi dalam memprediksi kategori kredit baik dan kredit buruk?
  3. Bagaimana pengaruh ketidakseimbangan kelas terhadap hasil klasifikasi risiko kredit?
  4. Apakah penerapan teknik penyeimbangan data seperti SMOTE dapat meningkatkan kemampuan model dalam mendeteksi kredit buruk?
  5. Model manakah yang memberikan performa terbaik untuk klasifikasi risiko kredit berdasarkan metrik evaluasi yang digunakan?

2.3 Tujuan

  1. Menganalisis karakteristik dataset German Credit sebagai dasar pemodelan klasifikasi risiko kredit.
  2. Membangun model klasifikasi untuk memprediksi kategori kredit baik dan kredit buruk.
  3. Membandingkan performa beberapa algoritma klasifikasi dalam menyelesaikan permasalahan credit scoring.
  4. Mengevaluasi pengaruh penanganan class imbalance terhadap performa model.
  5. Menentukan model terbaik yang paling efektif untuk mendukung proses penilaian risiko kredit.

2.4 Manfaat

Penelitian ini diharapkan dapat menambah referensi mengenai penerapan data mining dan machine learning dalam penilaian risiko kredit, terutama yang berkaitan dengan pengaruh ketidakseimbangan kelas terhadap hasil klasifikasi. Hasil penelitian juga dapat digunakan sebagai bahan pembanding bagi penelitian berikutnya yang menggunakan dataset German Credit maupun dataset lain dengan karakteristik serupa.

Bagi lembaga keuangan, penelitian ini memberi gambaran mengenai pemilihan model klasifikasi yang tidak hanya memiliki akurasi tinggi, tetapi juga mampu mengenali debitur berisiko dengan lebih baik. Temuan yang diperoleh dapat menjadi pertimbangan dalam pengembangan sistem pendukung keputusan kredit yang lebih objektif, efisien, dan sesuai dengan pola data nasabah. Melalui pendekatan tersebut, risiko pemberian kredit kepada debitur yang berpotensi gagal bayar dapat ditekan, sementara kualitas analisis kredit dapat ditingkatkan.

3 Tinjauan Pustaka

3.1 Data Mining untuk Credit Scoring

Dalam konteks credit scoring, data mining digunakan untuk mengekstraksi pola dari data historis agar keputusan pemberian kredit dapat dilakukan secara lebih objektif. Karakteristik khas masalah ini adalah adanya kombinasi fitur numerik dan kategorik, serta ketidakseimbangan antara debitur layak dan tidak layak kredit. Oleh sebab itu, pemilihan model harus mempertimbangkan kualitas prediksi untuk kelas minoritas dan dampak bisnis dari kesalahan klasifikasi.

3.2 K-Nearest Neighbor (KNN)

KNN merupakan metode berbasis jarak yang mengklasifikasikan suatu observasi berdasarkan mayoritas label dari \(k\) tetangga terdekatnya. Jarak yang umum digunakan adalah Euclidean:

\[ d(\mathbf{x}_i, \mathbf{x}_j) = \sqrt{\sum_{p=1}^{P}(x_{ip} - x_{jp})^2} \]

Karena bergantung pada perhitungan jarak, KNN sangat sensitif terhadap skala variabel. Oleh karena itu, standard scaling menjadi langkah penting sebelum model dijalankan.

3.3 Fuzzy KNN

Fuzzy KNN merupakan pengembangan dari KNN yang tidak hanya menghasilkan kelas akhir, tetapi juga derajat keanggotaan terhadap setiap kelas. Jika bobot tetangga ke-\(j\) dinyatakan sebagai \(w_j\), maka probabilitas observasi \(x\) menjadi anggota kelas bad credit dapat dinyatakan secara umum sebagai:

\[ \mu_{bad}(x) = \frac{\sum_{j=1}^{k} w_j \cdot I(y_j = bad)}{\sum_{j=1}^{k} w_j} \]

Pada laporan ini, bobot dibentuk dari jarak dengan prinsip bahwa tetangga yang lebih dekat memiliki pengaruh lebih besar. Pendekatan ini cocok ketika pemisahan antar kelas tidak terlalu tegas.

3.4 Random Forest

Random Forest adalah metode ensemble yang membangun banyak pohon keputusan dan menggabungkan hasilnya melalui prinsip majority vote atau rata-rata probabilitas. Secara sederhana, prediksi kelas dapat ditulis sebagai:

\[ \hat{y} = \text{mode}\{h_1(x), h_2(x), \ldots, h_B(x)\} \]

Keunggulan Random Forest terletak pada kestabilannya, kemampuan menangani interaksi nonlinier, serta ketahanannya terhadap noise. Pada data kredit, model ini sering menjadi baseline yang kuat karena dapat bekerja baik pada variabel hasil encoding dan tidak terlalu sensitif terhadap hubungan kompleks antar fitur.

3.5 SMOTE

Synthetic Minority Over-sampling Technique (SMOTE) digunakan untuk mengatasi ketidakseimbangan kelas dengan cara membentuk contoh sintetis dari kelas minoritas. Teknik ini diterapkan hanya pada data training agar tidak terjadi data leakage ke data testing.

3.6 Metrik Evaluasi

Selain akurasi, laporan ini menggunakan metrik lain yang lebih relevan untuk credit risk modeling, yaitu:

  • F1 Overall: rata-rata F1 berbobot untuk seluruh kelas.
  • F1 Bad Credit: fokus pada kualitas prediksi kelas minoritas.
  • ROC AUC: kemampuan model membedakan dua kelas pada berbagai threshold.
  • Total Cost: dirumuskan sebagai:

\[ \text{Cost} = 1 \times FP + 5 \times FN \]

Rumus tersebut menegaskan bahwa false negative (nasabah berisiko tetapi diprediksi layak) dianggap lima kali lebih mahal daripada false positive.

4 Metodologi Penelitian

4.1 Sumber Data dan Karakteristik Dataset

Komponen Keterangan
Nama dataset Statlog (German Credit Data)
Sumber UCI Machine Learning Repository
Jumlah observasi 1000
Jumlah variabel 21
Struktur target asli 1 = Good credit, 2 = Bad credit
Target untuk pemodelan 0 = Good credit, 1 = Bad credit
Karakter kelas Imbalanced (70% good, 30% bad)

Observasi1000

Variabel21

Good credit700

Bad credit300

4.2 Alur Analisis

  1. Mengambil data German Credit dari UCI.
  2. Mengubah target menjadi format biner yang lebih umum untuk klasifikasi.
  3. Melakukan encoding pada fitur kategorik sesuai sifat ordinal/nominalnya.
  4. Menjalankan exploratory data analysis untuk memahami distribusi kelas dan relasi fitur.
  5. Menangani outlier pada fitur kontinu dengan winsorizing.
  6. Membagi data menjadi training dan testing (80:20) secara stratified.
  7. Menerapkan SMOTE hanya pada data training.
  8. Melakukan scaling untuk model berbasis jarak.
  9. Melatih model KNN, Fuzzy KNN, dan Random Forest pada versi balanced dan unbalanced.
  10. Membandingkan seluruh model dan menentukan rekomendasi akhir.

4.3 Definisi Variabel

Detail variabel independent
  • Attribute1: status rekening giro.
  • Attribute2: durasi kredit (bulan).
  • Attribute3: riwayat kredit.
  • Attribute4: tujuan kredit.
  • Attribute5: jumlah kredit.
  • Attribute6: tabungan/surat berharga.
  • Attribute7: lama bekerja saat ini.
  • Attribute8: cicilan sebagai persentase pendapatan.
  • Attribute9: status personal dan jenis kelamin.
  • Attribute10: penjamin/debitur lain.
  • Attribute11: lama tinggal saat ini.
  • Attribute12: jenis aset/properti.
  • Attribute13: usia.
  • Attribute14: rencana cicilan lain.
  • Attribute15: status tempat tinggal.
  • Attribute16: jumlah kredit aktif di bank ini.
  • Attribute17: jenis pekerjaan.
  • Attribute18: jumlah tanggungan.
  • Attribute19: telepon.
  • Attribute20: pekerja asing.

5 Dataset dan Pra-Pemrosesan

5.1 Import Data Lokal

raw_path <- "data/german.data"

df_raw <- read.table(
  file = raw_path,
  header = FALSE,
  stringsAsFactors = FALSE
)

names(df_raw) <- c(paste0("Attribute", 1:20), "class")

str(df_raw)
## 'data.frame':    1000 obs. of  21 variables:
##  $ Attribute1 : chr  "A11" "A12" "A14" "A11" ...
##  $ Attribute2 : int  6 48 12 42 24 36 24 36 12 30 ...
##  $ Attribute3 : chr  "A34" "A32" "A34" "A32" ...
##  $ Attribute4 : chr  "A43" "A43" "A46" "A42" ...
##  $ Attribute5 : int  1169 5951 2096 7882 4870 9055 2835 6948 3059 5234 ...
##  $ Attribute6 : chr  "A65" "A61" "A61" "A61" ...
##  $ Attribute7 : chr  "A75" "A73" "A74" "A74" ...
##  $ Attribute8 : int  4 2 2 2 3 2 3 2 2 4 ...
##  $ Attribute9 : chr  "A93" "A92" "A93" "A93" ...
##  $ Attribute10: chr  "A101" "A101" "A101" "A103" ...
##  $ Attribute11: int  4 2 3 4 4 4 4 2 4 2 ...
##  $ Attribute12: chr  "A121" "A121" "A121" "A122" ...
##  $ Attribute13: int  67 22 49 45 53 35 53 35 61 28 ...
##  $ Attribute14: chr  "A143" "A143" "A143" "A143" ...
##  $ Attribute15: chr  "A152" "A152" "A152" "A153" ...
##  $ Attribute16: int  2 1 1 1 2 1 1 1 1 2 ...
##  $ Attribute17: chr  "A173" "A173" "A172" "A173" ...
##  $ Attribute18: int  1 1 2 2 2 2 1 1 1 1 ...
##  $ Attribute19: chr  "A192" "A191" "A191" "A191" ...
##  $ Attribute20: chr  "A201" "A201" "A201" "A201" ...
##  $ class      : int  1 2 1 1 2 1 1 1 1 2 ...
head(df_raw)

5.2 Encoding Atribut

checking_account_map   <- c(A11 = 1, A12 = 2, A13 = 3, A14 = 4)
credit_history_map     <- c(A30 = 0, A31 = 1, A32 = 2, A33 = 3, A34 = 4)
purpose_map            <- c(A40 = 0, A41 = 1, A42 = 2, A43 = 3, A44 = 4, A45 = 5, A46 = 6, A47 = 7, A48 = 8, A49 = 9, A410 = 10)
savings_map            <- c(A61 = 1, A62 = 2, A63 = 3, A64 = 4, A65 = 0)
employment_map         <- c(A71 = 0, A72 = 1, A73 = 2, A74 = 3, A75 = 4)
personal_status_map    <- c(A91 = 1, A92 = 2, A93 = 3, A94 = 4, A95 = 5)
guarantors_map         <- c(A101 = 0, A102 = 1, A103 = 2)
property_map           <- c(A121 = 3, A122 = 2, A123 = 1, A124 = 0)
installment_plans_map  <- c(A141 = 1, A142 = 2, A143 = 0)
housing_map            <- c(A151 = 1, A152 = 2, A153 = 0)
job_map                <- c(A171 = 0, A172 = 1, A173 = 2, A174 = 3)
telephone_map          <- c(A191 = 0, A192 = 1)
foreign_worker_map     <- c(A201 = 1, A202 = 0)

df <- df_raw

df$Attribute1  <- unname(checking_account_map[df$Attribute1])
df$Attribute3  <- unname(credit_history_map[df$Attribute3])
df$Attribute4  <- unname(purpose_map[df$Attribute4])
df$Attribute6  <- unname(savings_map[df$Attribute6])
df$Attribute7  <- unname(employment_map[df$Attribute7])
df$Attribute9  <- unname(personal_status_map[df$Attribute9])
df$Attribute10 <- unname(guarantors_map[df$Attribute10])
df$Attribute12 <- unname(property_map[df$Attribute12])
df$Attribute14 <- unname(installment_plans_map[df$Attribute14])
df$Attribute15 <- unname(housing_map[df$Attribute15])
df$Attribute17 <- unname(job_map[df$Attribute17])
df$Attribute19 <- unname(telephone_map[df$Attribute19])
df$Attribute20 <- unname(foreign_worker_map[df$Attribute20])

numeric_cols <- c("Attribute2", "Attribute5", "Attribute8", "Attribute11",
                  "Attribute13", "Attribute16", "Attribute18")

df[numeric_cols] <- lapply(df[numeric_cols], as.integer)

# 1 = Good, 2 = Bad -> 0 = Good, 1 = Bad
df$class <- ifelse(df$class == 1, 0L, 1L)

str(df)
## 'data.frame':    1000 obs. of  21 variables:
##  $ Attribute1 : num  1 2 4 1 1 4 4 2 4 2 ...
##  $ Attribute2 : int  6 48 12 42 24 36 24 36 12 30 ...
##  $ Attribute3 : num  4 2 4 2 3 2 2 2 2 4 ...
##  $ Attribute4 : num  3 3 6 2 0 6 2 1 3 0 ...
##  $ Attribute5 : int  1169 5951 2096 7882 4870 9055 2835 6948 3059 5234 ...
##  $ Attribute6 : num  0 1 1 1 1 0 3 1 4 1 ...
##  $ Attribute7 : num  4 2 3 3 2 2 4 2 3 0 ...
##  $ Attribute8 : int  4 2 2 2 3 2 3 2 2 4 ...
##  $ Attribute9 : num  3 2 3 3 3 3 3 3 1 4 ...
##  $ Attribute10: num  0 0 0 2 0 0 0 0 0 0 ...
##  $ Attribute11: int  4 2 3 4 4 4 4 2 4 2 ...
##  $ Attribute12: num  3 3 3 2 0 0 2 1 3 1 ...
##  $ Attribute13: int  67 22 49 45 53 35 53 35 61 28 ...
##  $ Attribute14: num  0 0 0 0 0 0 0 0 0 0 ...
##  $ Attribute15: num  2 2 2 0 0 0 2 1 2 2 ...
##  $ Attribute16: int  2 1 1 1 2 1 1 1 1 2 ...
##  $ Attribute17: num  2 2 1 2 2 1 2 3 1 3 ...
##  $ Attribute18: int  1 1 2 2 2 2 1 1 1 1 ...
##  $ Attribute19: num  1 0 0 0 0 1 0 1 0 0 ...
##  $ Attribute20: num  1 1 1 1 1 1 1 1 1 1 ...
##  $ class      : int  0 1 0 0 1 0 0 0 0 1 ...
summary(df$class)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##     0.0     0.0     0.0     0.3     1.0     1.0

5.3 Distribusi Target

class_tab <- table(df$class)
names(class_tab) <- c("Good (0)", "Bad (1)")

barplot(
  class_tab,
  main = "Distribusi Kelas German Credit",
  ylab = "Jumlah Observasi"
)

class_tab
## Good (0)  Bad (1) 
##      700      300

5.4 Split Data Train and Test

reference_split <- safe_read_csv("data/reference_split.csv")
reference_test  <- safe_read_csv("data/reference_test_order.csv")

df$row_id <- seq_len(nrow(df))

train_df <- df[reference_split$set[match(df$row_id, reference_split$row_id)] == "train", ]
test_df  <- df[reference_split$set[match(df$row_id, reference_split$row_id)] == "test", ]

test_df <- test_df[match(reference_test$row_id, test_df$row_id), ]

cat("Jumlah train:", nrow(train_df), "\n")
## Jumlah train: 800
cat("Jumlah test :", nrow(test_df), "\n\n")
## Jumlah test : 200
table(train_df$class)
## 
##   0   1 
## 560 240
table(test_df$class)
## 
##   0   1 
## 140  60

6 Hasil Prediksi Model

6.1 Import Prediksi Referensi

pred_ref <- safe_read_csv("data/reference_predictions.csv")
head(pred_ref)
table(pred_ref$model)
## 
##       Fuzzy KNN Balanced     Fuzzy KNN Unbalanced             KNN Balanced 
##                      200                      200                      200 
##           KNN Unbalanced   Random Forest Balanced Random Forest Unbalanced 
##                      200                      200                      200

6.2 Metrik Akurasi

model_names <- unique(pred_ref$model)

results <- do.call(
  rbind,
  lapply(model_names, function(m) {
    cbind(Model = m, eval_binary(pred_ref[pred_ref$model == m, ]))
  })
)

results$Accuracy <- as.numeric(results$Accuracy)
results$`F1 Overall` <- as.numeric(results$`F1 Overall`)
results$`F1 Bad Credit` <- as.numeric(results$`F1 Bad Credit`)
results$`ROC AUC` <- as.numeric(results$`ROC AUC`)
results$`Total Cost (1*FP + 5*FN)` <- as.numeric(results$`Total Cost (1*FP + 5*FN)`)
results$FP <- as.numeric(results$FP)
results$FN <- as.numeric(results$FN)

results <- results[order(-results$`F1 Bad Credit`, -results$`ROC AUC`, results$`Total Cost (1*FP + 5*FN)`), ]
rownames(results) <- NULL

6.3 Hasil Pemodelan

6.3.1 KNN

KNN Balanced (SMOTE + Scaling)

Metrik Nilai
Accuracy 0.6950
F1 Overall 0.7033
F1 Bad Credit 0.5481
ROC AUC 0.7579
FP 38
FN 23
Total Cost 153
Aktual  Prediksi Good (0) Bad (1)
Good (0) 102 38
Bad (1) 23 37

Interpretasi: versi balanced meningkatkan kemampuan deteksi kelas minoritas. Recall kelas bad credit menjadi lebih baik dibandingkan versi unbalanced, walaupun akurasi keseluruhan turun.

KNN Unbalanced (Scaling saja)

Metrik Nilai
Accuracy 0.7400
F1 Overall 0.7123
F1 Bad Credit 0.4348
ROC AUC 0.7187
FP 12
FN 40
Total Cost 212
Aktual  Prediksi Good (0) Bad (1)
Good (0) 128 12
Bad (1) 40 20

Interpretasi: KNN unbalanced terlihat lebih baik dari sisi akurasi, tetapi hal itu terjadi karena model sangat dominan pada kelas mayoritas. Untuk kasus credit risk, penurunan deteksi bad credit membuat model ini kurang ideal.

6.3.2 Fuzzy KNN

Fuzzy KNN Balanced (SMOTE + Scaling)

Metrik Nilai
Accuracy 0.6850
F1 Overall 0.6936
F1 Bad Credit 0.5333
ROC AUC 0.7659
FP 39
FN 24
Total Cost 159
Aktual  Prediksi Good (0) Bad (1)
Good (0) 101 39
Bad (1) 24 36

Interpretasi: Fuzzy KNN balanced memperlihatkan pola yang mirip dengan KNN balanced, namun sedikit lebih unggul pada ROC AUC. Penggunaan bobot berbasis jarak memberi fleksibilitas dalam memetakan probabilitas kelas minoritas.

Fuzzy KNN Unbalanced (Scaling saja)

Metrik Nilai
Accuracy 0.7400
F1 Overall 0.7123
F1 Bad Credit 0.4348
ROC AUC 0.7275
FP 12
FN 40
Total Cost 212
Aktual  Prediksi Good (0) Bad (1)
Good (0) 128 12
Bad (1) 40 20

Interpretasi: meskipun ROC AUC sedikit lebih tinggi daripada KNN unbalanced, model masih menunjukkan bias ke kelas mayoritas. Pada dataset yang tidak seimbang, versi unbalanced belum cukup kuat untuk mendeteksi bad credit secara memadai.

6.3.3 Random Forest

Random Forest Balanced (SMOTE + class weighting)

Metrik Nilai
Accuracy 0.7400
F1 Overall 0.7387
F1 Bad Credit 0.5593
ROC AUC 0.7826
FP 25
FN 27
Total Cost 160
Aktual  Prediksi Good (0) Bad (1)
Good (0) 115 25
Bad (1) 27 33

Interpretasi: model ini memberi kompromi yang sangat baik antara stabilitas prediksi keseluruhan dan kemampuan mendeteksi kelas bad credit. Nilai F1 Bad Credit paling tinggi di antara seluruh model yang dilaporkan.

Random Forest Unbalanced

Metrik Nilai
Accuracy 0.7850
F1 Overall 0.7677
F1 Bad Credit 0.5567
ROC AUC 0.8140
FP 10
FN 33
Total Cost 175
Aktual  Prediksi Good (0) Bad (1)
Good (0) 130 10
Bad (1) 33 27

Interpretasi: dari sisi Accuracy, F1 Overall, dan ROC AUC, model ini adalah yang paling tinggi. Namun, kemampuan menangkap bad credit tidak setinggi versi balanced jika dilihat dari F1 Bad Credit dan biaya total.

7 Kurva ROC

plot(
  c(0, 1), c(0, 1),
  type = "n",
  xlab = "False Positive Rate",
  ylab = "True Positive Rate",
  main = "ROC Curve Comparison - All Models"
)
abline(0, 1, lty = 2)

get_roc_points <- function(y_true, y_prob) {
  ord <- order(y_prob, decreasing = TRUE)
  y_true <- y_true[ord]
  y_prob <- y_prob[ord]

  thresholds <- c(Inf, unique(y_prob), -Inf)
  pts <- lapply(thresholds, function(th) {
    y_pred <- ifelse(y_prob >= th, 1, 0)
    cm <- table(factor(y_true, levels = c(0, 1)), factor(y_pred, levels = c(0, 1)))
    tn <- cm["0", "0"]
    fp <- cm["0", "1"]
    fn <- cm["1", "0"]
    tp <- cm["1", "1"]
    tpr <- ifelse(tp + fn == 0, 0, tp / (tp + fn))
    fpr <- ifelse(fp + tn == 0, 0, fp / (fp + tn))
    c(fpr = fpr, tpr = tpr)
  })

  pts <- do.call(rbind, pts)
  pts[order(pts[, "fpr"], pts[, "tpr"]), , drop = FALSE]
}

chosen_models <- c(
  "KNN Balanced",
  "Fuzzy KNN Balanced",
  "Random Forest Balanced",
  "Random Forest Unbalanced"
)

line_types <- c(1, 1, 1, 2)
line_cols  <- c("steelblue", "forestgreen", "darkorange", "firebrick")

for (i in seq_along(chosen_models)) {
  model_name <- chosen_models[i]
  dfm <- pred_ref[pred_ref$model == model_name, ]
  roc_pts <- get_roc_points(dfm$y_true, dfm$prob_bad)

  lines(
    roc_pts[, "fpr"], roc_pts[, "tpr"],
    lty = line_types[i],
    lwd = 2,
    col = line_cols[i]
  )
}

legend(
  "bottomright",
  legend = sapply(chosen_models, function(m) {
    auc_val <- roc_auc_manual(
      pred_ref$y_true[pred_ref$model == m],
      pred_ref$prob_bad[pred_ref$model == m]
    )
    paste0(m, " (AUC = ", format(round(auc_val, 3), nsmall = 3), ")")
  }),
  lty = line_types,
  lwd = 2,
  col = line_cols,
  cex = 0.85
)

7.1 Komparasi Menyeluruh

Model Accuracy F1 Overall F1 Bad Credit ROC AUC Total Cost FP FN
KNN Balanced 0.6950 0.7033 0.5481 0.7579 153 38 23
KNN Unbalanced 0.7400 0.7123 0.4348 0.7187 212 12 40
Fuzzy KNN Balanced 0.6850 0.6936 0.5333 0.7659 159 39 24
Fuzzy KNN Unbalanced 0.7400 0.7123 0.4348 0.7275 212 12 40
Random Forest Balanced 0.7400 0.7387 0.5593 0.7826 160 25 27
Random Forest Unbalanced 0.7850 0.7677 0.5567 0.8140 175 10 33

Dari tabel komparasi terlihat bahwa tidak ada satu model yang dominan pada seluruh metrik. Random Forest Unbalanced unggul pada ukuran performa global, sedangkan Random Forest Balanced unggul pada metrik yang lebih relevan untuk konteks risiko kredit, yaitu F1 Bad Credit. Sementara itu, KNN Balanced menghasilkan total biaya paling rendah, tetapi nilainya masih diiringi akurasi dan AUC yang lebih rendah.

Dengan mempertimbangkan bahwa tujuan analisis adalah menekan risiko salah meloloskan nasabah bermasalah, maka pemilihan model akhir sebaiknya tidak hanya berlandaskan akurasi. Dalam konteks tersebut, Random Forest Balanced memberi titik tengah yang paling masuk akal antara kualitas klasifikasi, stabilitas probabilitas, dan fokus pada kelas minoritas.

7.2 Model Akhir

Walaupun Random Forest Balanced sedikit lebih tinggi pada F1 Bad Credit jika dihitung sampai empat desimal, dokumen sumber menetapkan Random Forest Unbalanced sebagai rekomendasi akhir karena unggul pada akurasi, F1 overall, dan ROC AUC setelah pembulatan pelaporan dua desimal. Untuk menjaga konsistensi dengan dokumen sumber, laporan ini mempertahankan keputusan akhir yang sama.

8 Kesimpulan

Berdasarkan kajian terhadap permasalahan credit scoring, klasifikasi risiko kredit merupakan persoalan yang penting karena berkaitan langsung dengan kualitas pengambilan keputusan dalam pemberian pinjaman. Dataset German Credit menunjukkan bahwa prediksi risiko kredit tidak hanya dipengaruhi oleh banyaknya atribut yang tersedia, tetapi juga oleh distribusi kelas yang tidak seimbang. Kondisi ini menyebabkan model klasifikasi berpotensi bias terhadap kelas mayoritas apabila tidak disertai strategi penanganan imbalance yang tepat.

Secara umum, hasil-hasil penelitian terdahulu memperlihatkan bahwa penerapan teknik penyeimbangan data seperti SMOTE mampu meningkatkan kemampuan model dalam mengenali kelas minoritas, terutama pada kasus credit risk assessment. Dengan demikian, pemilihan model terbaik tidak seharusnya hanya berfokus pada akurasi, tetapi juga pada kemampuan model dalam mendeteksi kredit buruk melalui metrik seperti F1-score, recall, dan ROC AUC. Oleh karena itu, penelitian ini menegaskan bahwa kombinasi metode klasifikasi yang tepat dan penanganan class imbalance yang sesuai sangat penting untuk menghasilkan sistem prediksi risiko kredit yang lebih akurat, adil, dan relevan bagi kebutuhan lembaga keuangan.

Catatan: Dalam pemprosesan data diatas banyak proses yang hanya mengimport ulang split data dan data prediksi model. Hal tersebut dilakukan agar hasil akurasi tiap model sama persis dengan pengerjaan sebelumnya, menghindari kerandoman(randomness) seperti dalam split data, pemodelan dan lain lain.

LS0tDQp0aXRsZTogIlR1Z2FzIDEgLSBLb21wdXRhc2kgU3RhdGlzdGlrYSBMYW5qdXQiDQphdXRob3I6ICJJUkhBQiBBVEhBIEhBQklCSS4gQSDigJQgMjQwNTAxMjMxNDAxNjEg4oCUIEtlbGFzIEEiDQpvdXRwdXQ6IHdvcmRfZG9jdW1lbnQNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldCgNCiAgZWNobyA9IFRSVUUsDQogIHdhcm5pbmcgPSBGQUxTRSwNCiAgbWVzc2FnZSA9IEZBTFNFLA0KICBmaWcuYWxpZ24gPSAiY2VudGVyIiwNCiAgZmlnLndpZHRoID0gOCwNCiAgZmlnLmhlaWdodCA9IDUNCikNCg0Kb3B0aW9ucyhzY2lwZW4gPSA5OTkpDQoNCnNhZmVfcmVhZF9jc3YgPC0gZnVuY3Rpb24ocGF0aCkgew0KICByZWFkLmNzdihwYXRoLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UsIGNoZWNrLm5hbWVzID0gRkFMU0UpDQp9DQoNCnJvY19hdWNfbWFudWFsIDwtIGZ1bmN0aW9uKHlfdHJ1ZSwgeV9wcm9iKSB7DQogIHlfdHJ1ZSA8LSBhcy5pbnRlZ2VyKHlfdHJ1ZSkNCiAgeV9wcm9iIDwtIGFzLm51bWVyaWMoeV9wcm9iKQ0KDQogIG9rIDwtIGlzLmZpbml0ZSh5X3RydWUpICYgaXMuZmluaXRlKHlfcHJvYikNCiAgeV90cnVlIDwtIHlfdHJ1ZVtva10NCiAgeV9wcm9iIDwtIHlfcHJvYltva10NCg0KICBuX3BvcyA8LSBzdW0oeV90cnVlID09IDEpDQogIG5fbmVnIDwtIHN1bSh5X3RydWUgPT0gMCkNCiAgaWYgKG5fcG9zID09IDAgfHwgbl9uZWcgPT0gMCkgcmV0dXJuKE5BX3JlYWxfKQ0KDQogIHJhbmtzIDwtIHJhbmsoeV9wcm9iLCB0aWVzLm1ldGhvZCA9ICJhdmVyYWdlIikNCiAgKHN1bShyYW5rc1t5X3RydWUgPT0gMV0pIC0gbl9wb3MgKiAobl9wb3MgKyAxKSAvIDIpIC8gKG5fcG9zICogbl9uZWcpDQp9DQoNCmV2YWxfYmluYXJ5IDwtIGZ1bmN0aW9uKGRmX21vZGVsKSB7DQogIHlfdHJ1ZSA8LSBhcy5pbnRlZ2VyKGRmX21vZGVsJHlfdHJ1ZSkNCiAgeV9wcmVkIDwtIGFzLmludGVnZXIoZGZfbW9kZWwkcHJlZCkNCiAgeV9wcm9iIDwtIGFzLm51bWVyaWMoZGZfbW9kZWwkcHJvYl9iYWQpDQoNCiAgY20gPC0gdGFibGUoDQogICAgQWN0dWFsID0gZmFjdG9yKHlfdHJ1ZSwgbGV2ZWxzID0gYygwLCAxKSksDQogICAgUHJlZGljdGVkID0gZmFjdG9yKHlfcHJlZCwgbGV2ZWxzID0gYygwLCAxKSkNCiAgKQ0KDQogIHRuIDwtIGNtWyIwIiwgIjAiXQ0KICBmcCA8LSBjbVsiMCIsICIxIl0NCiAgZm4gPC0gY21bIjEiLCAiMCJdDQogIHRwIDwtIGNtWyIxIiwgIjEiXQ0KDQogIHByZWNpc2lvbjEgPC0gaWZlbHNlKHRwICsgZnAgPT0gMCwgMCwgdHAgLyAodHAgKyBmcCkpDQogIHJlY2FsbDEgICAgPC0gaWZlbHNlKHRwICsgZm4gPT0gMCwgMCwgdHAgLyAodHAgKyBmbikpDQogIGYxX2JhZCAgICAgPC0gaWZlbHNlKHByZWNpc2lvbjEgKyByZWNhbGwxID09IDAsIDAsIDIgKiBwcmVjaXNpb24xICogcmVjYWxsMSAvIChwcmVjaXNpb24xICsgcmVjYWxsMSkpDQoNCiAgcHJlY2lzaW9uMCA8LSBpZmVsc2UodG4gKyBmbiA9PSAwLCAwLCB0biAvICh0biArIGZuKSkNCiAgcmVjYWxsMCAgICA8LSBpZmVsc2UodG4gKyBmcCA9PSAwLCAwLCB0biAvICh0biArIGZwKSkNCiAgZjFfZ29vZCAgICA8LSBpZmVsc2UocHJlY2lzaW9uMCArIHJlY2FsbDAgPT0gMCwgMCwgMiAqIHByZWNpc2lvbjAgKiByZWNhbGwwIC8gKHByZWNpc2lvbjAgKyByZWNhbGwwKSkNCg0KICB3MCA8LSBtZWFuKHlfdHJ1ZSA9PSAwKQ0KICB3MSA8LSBtZWFuKHlfdHJ1ZSA9PSAxKQ0KDQogIGRhdGEuZnJhbWUoDQogICAgQWNjdXJhY3kgPSBtZWFuKHlfdHJ1ZSA9PSB5X3ByZWQpLA0KICAgIGBGMSBPdmVyYWxsYCA9IHcwICogZjFfZ29vZCArIHcxICogZjFfYmFkLA0KICAgIGBGMSBCYWQgQ3JlZGl0YCA9IGYxX2JhZCwNCiAgICBgUk9DIEFVQ2AgPSByb2NfYXVjX21hbnVhbCh5X3RydWUsIHlfcHJvYiksDQogICAgYFRvdGFsIENvc3QgKDEqRlAgKyA1KkZOKWAgPSBmcCArIDUgKiBmbiwNCiAgICBGUCA9IGZwLA0KICAgIEZOID0gZm4sDQogICAgY2hlY2submFtZXMgPSBGQUxTRQ0KICApDQp9DQpgYGANCg0KOjo6IGhlcm8NCjxoMT5QcmVkaWtzaSBSaXNpa28gS3JlZGl0IE5hc2FiYWggTWVuZ2d1bmFrYW4gS05OLCBGdXp6eSBLTk4sIGRhbiBSYW5kb20gRm9yZXN0PC9oMT4NCjo6Og0KDQojIFJpbmdrYXNhbiBFa3Nla3V0aWYNCg0KUGVuZWxpdGlhbiBpbmkgbWVtYmFoYXMgcGVuZXJhcGFuIHRpZ2EgcGVuZGVrYXRhbiBrbGFzaWZpa2FzaSB5YWl0dSBLLU5lYXJlc3QgTmVpZ2hib3IgKEtOTiksIEZ1enp5IEtOTiwgZGFuIFJhbmRvbSBGb3Jlc3QuIFVudHVrIG1lbXByZWRpa3NpIGFwYWthaCBzZW9yYW5nIG5hc2FiYWggdGVybWFzdWsga2F0ZWdvcmkgZ29vZCBjcmVkaXQgYXRhdSBiYWQgY3JlZGl0IHBhZGEgZGF0YXNldCAqR2VybWFuIENyZWRpdCouIEFuYWxpc2lzIGRpYXdhbGkgZGVuZ2FuIGVuY29kaW5nIHZhcmlhYmVsIGthdGVnb3JpaywgcGVtZXJpa3NhYW4gZGlzdHJpYnVzaSBrZWxhcywgcGVuYW5nYW5hbiBvdXRsaWVyIG1lbGFsdWkgd2luc29yaXppbmcsIHBlbWJhZ2lhbiBkYXRhIHRyYWluaW5n4oCTdGVzdGluZywgcGVuYW5nYW5hbiBrZXRpZGFrc2VpbWJhbmdhbiBrZWxhcyBtZW5nZ3VuYWthbiBTTU9URSwgbGFsdSBkaWxhbmp1dGthbiBrZSB0YWhhcCBwZW1vZGVsYW4gZGFuIGV2YWx1YXNpLg0KDQpCZXJkYXNhcmthbiBoYXNpbCB5YW5nIHRlcmNhbnR1bSBwYWRhIHBlbmdlcmphYW4gYXdhbCwgUmFuZG9tIEZvcmVzdCBVbmJhbGFuY2VkIG1lbWJlcmkgcGVyZm9ybWEgdGVydGluZ2dpIHBhZGEgbWV0cmlrIGtlc2VsdXJ1aGFuIHNlcGVydGkgQWNjdXJhY3kgKDAuNzg1MCkgZGFuIFJPQyBBVUMgKDAuODE0MCkuIE5hbXVuLCBqaWthIHByaW9yaXRhcyBhbmFsaXNpcyBhZGFsYWggbWVuZGV0ZWtzaSBiYWQgY3JlZGl0IGRhbiBtZW5la2FuIGJpYXlhIGZhbHNlIG5lZ2F0aXZlLCBtYWthIFJhbmRvbSBGb3Jlc3QgQmFsYW5jZWQgbWVuamFkaSBwaWxpaGFuIHlhbmcgbGViaWggcmVsZXZhbiBrYXJlbmEgbWVtaWxpa2kgRjEgQmFkIENyZWRpdCBsZWJpaCB0aW5nZ2kgKDAuNTU5MykgZGFuIGJpYXlhIHRvdGFsIHlhbmcgbGViaWggYmFpayBkaWJhbmRpbmdrYW4gUmFuZG9tIEZvcmVzdCBVbmJhbGFuY2VkLg0KDQo6Ojogey5jYWxsb3V0IC5zdWNjZXNzfQ0KPHN0cm9uZz5JbnRpIHJla29tZW5kYXNpOjwvc3Ryb25nPiB1bnR1ayBrb250ZWtzIG1hbmFqZW1lbiByaXNpa28ga3JlZGl0LCBtb2RlbCB5YW5nIHBhbGluZyBsYXlhayBkaXVzdWxrYW4gc2ViYWdhaSBtb2RlbCBwcm9kdWtzaSBhZGFsYWggUmFuZG9tIEZvcmVzdCBCYWxhbmNlZCBrYXJlbmEgbWVtYmVyaWthbiBrb21wcm9taSB0ZXJiYWlrIGFudGFyYSBwZXJmb3JtYSBrZXNlbHVydWhhbiwga2VtYW1wdWFuIG1lbmRldGVrc2kga2VsYXMgbWlub3JpdGFzLCBkYW4gZXZhbHVhc2kgYmlheWEuDQo6OjoNCg0KIyBQZW5kYWh1bHVhbg0KDQojIyBMYXRhciBCZWxha2FuZw0KDQpQZXJrZW1iYW5nYW4gaW5kdXN0cmkga2V1YW5nYW4geWFuZyBzZW1ha2luIGJlcmdhbnR1bmcgcGFkYSBkYXRhIG1lbmRvcm9uZyBsZW1iYWdhIHBlcmJhbmthbiBkYW4gcGVtYmlheWFhbiB1bnR1ayBtZW5lcmFwa2FuIGFuYWxpc2lzIGRhdGEgZGFsYW0gcHJvc2VzIHBlbmlsYWlhbiBrZWxheWFrYW4ga3JlZGl0LiBTYWxhaCBzYXR1IHBlcnNvYWxhbiB1dGFtYSBkYWxhbSBjcmVkaXQgc2NvcmluZyBhZGFsYWggYmFnYWltYW5hIG1lbmdrbGFzaWZpa2FzaWthbiBjYWxvbiBkZWJpdHVyIGtlIGRhbGFtIGthdGVnb3JpIHJpc2lrbyBrcmVkaXQgYmFpayBtYXVwdW4gYnVydWsgc2VjYXJhIGNlcGF0LCBrb25zaXN0ZW4sIGRhbiBvYmpla3RpZi4gRGF0YXNldCBTdGF0bG9nIChHZXJtYW4gQ3JlZGl0IERhdGEpIGRhcmkgVUNJIG1lcnVwYWthbiBzYWxhaCBzYXR1IGRhdGFzZXQgYWN1YW4geWFuZyBiYW55YWsgZGlndW5ha2FuIHVudHVrIHBlbmVsaXRpYW4ga2xhc2lmaWthc2kgcmlzaWtvIGtyZWRpdC4gRGF0YXNldCBpbmkgdGVyZGlyaSBkYXJpIDEuMDAwIGRhdGEsIDIwIGF0cmlidXQsIGRhbiBiZXJ0dWp1YW4gbWVuZ2tsYXNpZmlrYXNpa2FuIGRlYml0dXIga2UgZGFsYW0gZHVhIGtlbGFzLCB5YWl0dSBnb29kIGNyZWRpdCBkYW4gYmFkIGNyZWRpdC4gVUNJIGp1Z2EgbWVuZWdhc2thbiBiYWh3YSBkYXRhIGluaSBiZXJrYWl0YW4gZGVuZ2FuIG1hc2FsYWgga2xhc2lmaWthc2kgZGFuIG1lbWlsaWtpIGNvc3QgbWF0cml4LCBzZWhpbmdnYSBrZXNhbGFoYW4ga2xhc2lmaWthc2kgcGFkYSBrcmVkaXQgYmVybWFzYWxhaCBtZW5qYWRpIGFzcGVrIHlhbmcgcGVudGluZyB1bnR1ayBkaXBlcmhhdGlrYW4uDQoNCkRhbGFtIHByYWt0aWtueWEsIHBlcm1hc2FsYWhhbiBwZW5pbGFpYW4ga3JlZGl0IHRpZGFrIGhhbnlhIGJlcmthaXRhbiBkZW5nYW4gbWVtYmFuZ3VuIG1vZGVsIHlhbmcgYWt1cmF0LCB0ZXRhcGkganVnYSBkZW5nYW4ga2VtYW1wdWFuIG1vZGVsIGRhbGFtIG1lbmdlbmFsaSBrZWxhcyBtaW5vcml0YXMsIHlhaXR1IGRlYml0dXIgYmVyaXNpa28gdGluZ2dpLiBQYWRhIGRhdGEga3JlZGl0LCBkaXN0cmlidXNpIGtlbGFzIHNlcmluZyBrYWxpIHRpZGFrIHNlaW1iYW5nIHNlaGluZ2dhIG1vZGVsIGNlbmRlcnVuZyBsZWJpaCBtdWRhaCBtZW5nZW5hbGkga2VsYXMgbWF5b3JpdGFzIGRhcmlwYWRhIGtlbGFzIG1pbm9yaXRhcy4gS29uZGlzaSBpbmkgYmVyYmFoYXlhIGthcmVuYSBrZXNhbGFoYW4gZGFsYW0gbWVuZ2lkZW50aWZpa2FzaSBkZWJpdHVyIGJlcm1hc2FsYWggZGFwYXQgbWVuaW1idWxrYW4ga2VydWdpYW4gZmluYW5zaWFsIHlhbmcgbGViaWggYmVzYXIgYmFnaSBsZW1iYWdhIHBlbWJlcmkga3JlZGl0LiBPbGVoIHNlYmFiIGl0dSwgZXZhbHVhc2kgbW9kZWwga2xhc2lmaWthc2kga3JlZGl0IHRpZGFrIGN1a3VwIGhhbnlhIGJlcmRhc2Fya2FuIGFrdXJhc2ksIG1lbGFpbmthbiBqdWdhIHBlcmx1IG1lbXBlcnRpbWJhbmdrYW4gbWV0cmlrIHNlcGVydGkgRjEtc2NvcmUsIHJlY2FsbCwgZGFuIFJPQyBBVUMgYWdhciBrZW1hbXB1YW4gbW9kZWwgZGFsYW0gbWVuZGV0ZWtzaSBrcmVkaXQgYnVydWsgZGFwYXQgZGluaWxhaSBzZWNhcmEgbGViaWgga29tcHJlaGVuc2lmLg0KDQpCZXJiYWdhaSBwZW5lbGl0aWFuIG1lbnVuanVra2FuIGJhaHdhIHBlbmFuZ2FuYW4gY2xhc3MgaW1iYWxhbmNlIGRlbmdhbiB0ZWtuaWsgcmVzYW1wbGluZyBzZXBlcnRpIFNNT1RFIGRhcGF0IG1lbmluZ2thdGthbiBrZW1hbXB1YW4gbW9kZWwgZGFsYW0gbWVuZGV0ZWtzaSBrZWxhcyBtaW5vcml0YXMuIFBhZGEga29udGVrcyBjcmVkaXQgcmlzayBhc3Nlc3NtZW50LCBwZW5lcmFwYW4gU01PVEUgYmVyc2FtYSBhbGdvcml0bWEgcGVtYmVsYWphcmFuIG1lc2luLCBraHVzdXNueWEgbW9kZWwgZW5zZW1ibGUgc2VwZXJ0aSBSYW5kb20gRm9yZXN0LCB0ZXJidWt0aSBtYW1wdSBtZW5pbmdrYXRrYW4gcHJlY2lzaW9uLCByZWNhbGwsIGRhbiBGMS1zY29yZSBkaWJhbmRpbmdrYW4gcGVsYXRpaGFuIHBhZGEgZGF0YSB5YW5nIHRpZGFrIHNlaW1iYW5nLiBUZW11YW4gdGVyc2VidXQgbWVudW5qdWtrYW4gYmFod2Ega29tYmluYXNpIG1ldG9kZSBwZW55ZWltYmFuZ2FuIGRhdGEgZGFuIGFsZ29yaXRtYSBrbGFzaWZpa2FzaSB5YW5nIHRlcGF0IGJlcnBvdGVuc2kgbWVuZ2hhc2lsa2FuIHNpc3RlbSBwcmVkaWtzaSBrcmVkaXQgeWFuZyBsZWJpaCBhbmRhbC4gQXRhcyBkYXNhciBpdHUsIHBlbmVsaXRpYW4gaW5pIGRpbGFrdWthbiB1bnR1ayBtZW5nYW5hbGlzaXMgcGVyZm9ybWEgYmViZXJhcGEgYWxnb3JpdG1hIGtsYXNpZmlrYXNpIHBhZGEgZGF0YSBHZXJtYW4gQ3JlZGl0LCBzZWthbGlndXMgbWVuZ2V2YWx1YXNpIHBlbmdhcnVoIHBlbmFuZ2FuYW4gZGF0YSB0aWRhayBzZWltYmFuZyB0ZXJoYWRhcCBrdWFsaXRhcyBwcmVkaWtzaSByaXNpa28ga3JlZGl0Lg0KDQojIyBSdW11c2FuIE1hc2FsYWgNCg0KMS4gIEJhZ2FpbWFuYSBrYXJha3RlcmlzdGlrIGRhdGEgR2VybWFuIENyZWRpdCBkYWxhbSBtZXJlcHJlc2VudGFzaWthbiByaXNpa28ga3JlZGl0IG5hc2FiYWg/DQoyLiAgQmFnYWltYW5hIHBlcmZvcm1hIGFsZ29yaXRtYSBrbGFzaWZpa2FzaSBkYWxhbSBtZW1wcmVkaWtzaSBrYXRlZ29yaSBrcmVkaXQgYmFpayBkYW4ga3JlZGl0IGJ1cnVrPw0KMy4gIEJhZ2FpbWFuYSBwZW5nYXJ1aCBrZXRpZGFrc2VpbWJhbmdhbiBrZWxhcyB0ZXJoYWRhcCBoYXNpbCBrbGFzaWZpa2FzaSByaXNpa28ga3JlZGl0Pw0KNC4gIEFwYWthaCBwZW5lcmFwYW4gdGVrbmlrIHBlbnllaW1iYW5nYW4gZGF0YSBzZXBlcnRpIFNNT1RFIGRhcGF0IG1lbmluZ2thdGthbiBrZW1hbXB1YW4gbW9kZWwgZGFsYW0gbWVuZGV0ZWtzaSBrcmVkaXQgYnVydWs/DQo1LiAgTW9kZWwgbWFuYWthaCB5YW5nIG1lbWJlcmlrYW4gcGVyZm9ybWEgdGVyYmFpayB1bnR1ayBrbGFzaWZpa2FzaSByaXNpa28ga3JlZGl0IGJlcmRhc2Fya2FuIG1ldHJpayBldmFsdWFzaSB5YW5nIGRpZ3VuYWthbj8NCg0KIyMgVHVqdWFuDQoNCjEuICBNZW5nYW5hbGlzaXMga2FyYWt0ZXJpc3RpayBkYXRhc2V0IEdlcm1hbiBDcmVkaXQgc2ViYWdhaSBkYXNhciBwZW1vZGVsYW4ga2xhc2lmaWthc2kgcmlzaWtvIGtyZWRpdC4NCjIuICBNZW1iYW5ndW4gbW9kZWwga2xhc2lmaWthc2kgdW50dWsgbWVtcHJlZGlrc2kga2F0ZWdvcmkga3JlZGl0IGJhaWsgZGFuIGtyZWRpdCBidXJ1ay4NCjMuICBNZW1iYW5kaW5na2FuIHBlcmZvcm1hIGJlYmVyYXBhIGFsZ29yaXRtYSBrbGFzaWZpa2FzaSBkYWxhbSBtZW55ZWxlc2Fpa2FuIHBlcm1hc2FsYWhhbiBjcmVkaXQgc2NvcmluZy4NCjQuICBNZW5nZXZhbHVhc2kgcGVuZ2FydWggcGVuYW5nYW5hbiBjbGFzcyBpbWJhbGFuY2UgdGVyaGFkYXAgcGVyZm9ybWEgbW9kZWwuDQo1LiAgTWVuZW50dWthbiBtb2RlbCB0ZXJiYWlrIHlhbmcgcGFsaW5nIGVmZWt0aWYgdW50dWsgbWVuZHVrdW5nIHByb3NlcyBwZW5pbGFpYW4gcmlzaWtvIGtyZWRpdC4NCg0KIyMgTWFuZmFhdA0KDQpQZW5lbGl0aWFuIGluaSBkaWhhcmFwa2FuIGRhcGF0IG1lbmFtYmFoIHJlZmVyZW5zaSBtZW5nZW5haSBwZW5lcmFwYW4gZGF0YSBtaW5pbmcgZGFuIG1hY2hpbmUgbGVhcm5pbmcgZGFsYW0gcGVuaWxhaWFuIHJpc2lrbyBrcmVkaXQsIHRlcnV0YW1hIHlhbmcgYmVya2FpdGFuIGRlbmdhbiBwZW5nYXJ1aCBrZXRpZGFrc2VpbWJhbmdhbiBrZWxhcyB0ZXJoYWRhcCBoYXNpbCBrbGFzaWZpa2FzaS4gSGFzaWwgcGVuZWxpdGlhbiBqdWdhIGRhcGF0IGRpZ3VuYWthbiBzZWJhZ2FpIGJhaGFuIHBlbWJhbmRpbmcgYmFnaSBwZW5lbGl0aWFuIGJlcmlrdXRueWEgeWFuZyBtZW5nZ3VuYWthbiBkYXRhc2V0IEdlcm1hbiBDcmVkaXQgbWF1cHVuIGRhdGFzZXQgbGFpbiBkZW5nYW4ga2FyYWt0ZXJpc3RpayBzZXJ1cGEuDQoNCkJhZ2kgbGVtYmFnYSBrZXVhbmdhbiwgcGVuZWxpdGlhbiBpbmkgbWVtYmVyaSBnYW1iYXJhbiBtZW5nZW5haSBwZW1pbGloYW4gbW9kZWwga2xhc2lmaWthc2kgeWFuZyB0aWRhayBoYW55YSBtZW1pbGlraSBha3VyYXNpIHRpbmdnaSwgdGV0YXBpIGp1Z2EgbWFtcHUgbWVuZ2VuYWxpIGRlYml0dXIgYmVyaXNpa28gZGVuZ2FuIGxlYmloIGJhaWsuIFRlbXVhbiB5YW5nIGRpcGVyb2xlaCBkYXBhdCBtZW5qYWRpIHBlcnRpbWJhbmdhbiBkYWxhbSBwZW5nZW1iYW5nYW4gc2lzdGVtIHBlbmR1a3VuZyBrZXB1dHVzYW4ga3JlZGl0IHlhbmcgbGViaWggb2JqZWt0aWYsIGVmaXNpZW4sIGRhbiBzZXN1YWkgZGVuZ2FuIHBvbGEgZGF0YSBuYXNhYmFoLiBNZWxhbHVpIHBlbmRla2F0YW4gdGVyc2VidXQsIHJpc2lrbyBwZW1iZXJpYW4ga3JlZGl0IGtlcGFkYSBkZWJpdHVyIHlhbmcgYmVycG90ZW5zaSBnYWdhbCBiYXlhciBkYXBhdCBkaXRla2FuLCBzZW1lbnRhcmEga3VhbGl0YXMgYW5hbGlzaXMga3JlZGl0IGRhcGF0IGRpdGluZ2thdGthbi4NCg0KIyBUaW5qYXVhbiBQdXN0YWthDQoNCiMjIERhdGEgTWluaW5nIHVudHVrIENyZWRpdCBTY29yaW5nDQoNCkRhbGFtIGtvbnRla3MgKmNyZWRpdCBzY29yaW5nKiwgZGF0YSBtaW5pbmcgZGlndW5ha2FuIHVudHVrIG1lbmdla3N0cmFrc2kgcG9sYSBkYXJpIGRhdGEgaGlzdG9yaXMgYWdhciBrZXB1dHVzYW4gcGVtYmVyaWFuIGtyZWRpdCBkYXBhdCBkaWxha3VrYW4gc2VjYXJhIGxlYmloIG9iamVrdGlmLiBLYXJha3RlcmlzdGlrIGtoYXMgbWFzYWxhaCBpbmkgYWRhbGFoIGFkYW55YSBrb21iaW5hc2kgZml0dXIgbnVtZXJpayBkYW4ga2F0ZWdvcmlrLCBzZXJ0YSBrZXRpZGFrc2VpbWJhbmdhbiBhbnRhcmEgZGViaXR1ciBsYXlhayBkYW4gdGlkYWsgbGF5YWsga3JlZGl0LiBPbGVoIHNlYmFiIGl0dSwgcGVtaWxpaGFuIG1vZGVsIGhhcnVzIG1lbXBlcnRpbWJhbmdrYW4ga3VhbGl0YXMgcHJlZGlrc2kgdW50dWsga2VsYXMgbWlub3JpdGFzIGRhbiBkYW1wYWsgYmlzbmlzIGRhcmkga2VzYWxhaGFuIGtsYXNpZmlrYXNpLg0KDQojIyBLLU5lYXJlc3QgTmVpZ2hib3IgKEtOTikNCg0KS05OIG1lcnVwYWthbiBtZXRvZGUgYmVyYmFzaXMgamFyYWsgeWFuZyBtZW5na2xhc2lmaWthc2lrYW4gc3VhdHUgb2JzZXJ2YXNpIGJlcmRhc2Fya2FuIG1heW9yaXRhcyBsYWJlbCBkYXJpICRrJCB0ZXRhbmdnYSB0ZXJkZWthdG55YS4gSmFyYWsgeWFuZyB1bXVtIGRpZ3VuYWthbiBhZGFsYWggRXVjbGlkZWFuOg0KDQokJCBkKFxtYXRoYmZ7eH1faSwgXG1hdGhiZnt4fV9qKSA9IFxzcXJ0e1xzdW1fe3A9MX1ee1B9KHhfe2lwfSAtIHhfe2pwfSleMn0gJCQNCg0KS2FyZW5hIGJlcmdhbnR1bmcgcGFkYSBwZXJoaXR1bmdhbiBqYXJhaywgS05OIHNhbmdhdCBzZW5zaXRpZiB0ZXJoYWRhcCBza2FsYSB2YXJpYWJlbC4gT2xlaCBrYXJlbmEgaXR1LCAqKnN0YW5kYXJkIHNjYWxpbmcqKiBtZW5qYWRpIGxhbmdrYWggcGVudGluZyBzZWJlbHVtIG1vZGVsIGRpamFsYW5rYW4uDQoNCiMjIEZ1enp5IEtOTg0KDQpGdXp6eSBLTk4gbWVydXBha2FuIHBlbmdlbWJhbmdhbiBkYXJpIEtOTiB5YW5nIHRpZGFrIGhhbnlhIG1lbmdoYXNpbGthbiBrZWxhcyBha2hpciwgdGV0YXBpIGp1Z2EgZGVyYWphdCBrZWFuZ2dvdGFhbiB0ZXJoYWRhcCBzZXRpYXAga2VsYXMuIEppa2EgYm9ib3QgdGV0YW5nZ2Ega2UtJGokIGRpbnlhdGFrYW4gc2ViYWdhaSAkd19qJCwgbWFrYSBwcm9iYWJpbGl0YXMgb2JzZXJ2YXNpICR4JCBtZW5qYWRpIGFuZ2dvdGEga2VsYXMgKmJhZCBjcmVkaXQqIGRhcGF0IGRpbnlhdGFrYW4gc2VjYXJhIHVtdW0gc2ViYWdhaToNCg0KJCQgXG11X3tiYWR9KHgpID0gXGZyYWN7XHN1bV97aj0xfV57a30gd19qIFxjZG90IEkoeV9qID0gYmFkKX17XHN1bV97aj0xfV57a30gd19qfSAkJA0KDQpQYWRhIGxhcG9yYW4gaW5pLCBib2JvdCBkaWJlbnR1ayBkYXJpIGphcmFrIGRlbmdhbiBwcmluc2lwIGJhaHdhIHRldGFuZ2dhIHlhbmcgbGViaWggZGVrYXQgbWVtaWxpa2kgcGVuZ2FydWggbGViaWggYmVzYXIuIFBlbmRla2F0YW4gaW5pIGNvY29rIGtldGlrYSBwZW1pc2FoYW4gYW50YXIga2VsYXMgdGlkYWsgdGVybGFsdSB0ZWdhcy4NCg0KIyMgUmFuZG9tIEZvcmVzdA0KDQpSYW5kb20gRm9yZXN0IGFkYWxhaCBtZXRvZGUgKmVuc2VtYmxlKiB5YW5nIG1lbWJhbmd1biBiYW55YWsgcG9ob24ga2VwdXR1c2FuIGRhbiBtZW5nZ2FidW5na2FuIGhhc2lsbnlhIG1lbGFsdWkgcHJpbnNpcCAqbWFqb3JpdHkgdm90ZSogYXRhdSByYXRhLXJhdGEgcHJvYmFiaWxpdGFzLiBTZWNhcmEgc2VkZXJoYW5hLCBwcmVkaWtzaSBrZWxhcyBkYXBhdCBkaXR1bGlzIHNlYmFnYWk6DQoNCiQkIFxoYXR7eX0gPSBcdGV4dHttb2RlfVx7aF8xKHgpLCBoXzIoeCksIFxsZG90cywgaF9CKHgpXH0gJCQNCg0KS2V1bmdndWxhbiBSYW5kb20gRm9yZXN0IHRlcmxldGFrIHBhZGEga2VzdGFiaWxhbm55YSwga2VtYW1wdWFuIG1lbmFuZ2FuaSBpbnRlcmFrc2kgbm9ubGluaWVyLCBzZXJ0YSBrZXRhaGFuYW5ueWEgdGVyaGFkYXAgKm5vaXNlKi4gUGFkYSBkYXRhIGtyZWRpdCwgbW9kZWwgaW5pIHNlcmluZyBtZW5qYWRpICpiYXNlbGluZSogeWFuZyBrdWF0IGthcmVuYSBkYXBhdCBiZWtlcmphIGJhaWsgcGFkYSB2YXJpYWJlbCBoYXNpbCBlbmNvZGluZyBkYW4gdGlkYWsgdGVybGFsdSBzZW5zaXRpZiB0ZXJoYWRhcCBodWJ1bmdhbiBrb21wbGVrcyBhbnRhciBmaXR1ci4NCg0KIyMgU01PVEUNCg0KU3ludGhldGljIE1pbm9yaXR5IE92ZXItc2FtcGxpbmcgVGVjaG5pcXVlIChTTU9URSkgZGlndW5ha2FuIHVudHVrIG1lbmdhdGFzaSBrZXRpZGFrc2VpbWJhbmdhbiBrZWxhcyBkZW5nYW4gY2FyYSBtZW1iZW50dWsgY29udG9oIHNpbnRldGlzIGRhcmkga2VsYXMgbWlub3JpdGFzLiBUZWtuaWsgaW5pIGRpdGVyYXBrYW4gaGFueWEgcGFkYSBkYXRhIHRyYWluaW5nIGFnYXIgdGlkYWsgdGVyamFkaSAqZGF0YSBsZWFrYWdlKiBrZSBkYXRhIHRlc3RpbmcuDQoNCiMjIE1ldHJpayBFdmFsdWFzaQ0KDQpTZWxhaW4gYWt1cmFzaSwgbGFwb3JhbiBpbmkgbWVuZ2d1bmFrYW4gbWV0cmlrIGxhaW4geWFuZyBsZWJpaCByZWxldmFuIHVudHVrICpjcmVkaXQgcmlzayBtb2RlbGluZyosIHlhaXR1Og0KDQotICAgKipGMSBPdmVyYWxsKio6IHJhdGEtcmF0YSBGMSBiZXJib2JvdCB1bnR1ayBzZWx1cnVoIGtlbGFzLg0KLSAgICoqRjEgQmFkIENyZWRpdCoqOiBmb2t1cyBwYWRhIGt1YWxpdGFzIHByZWRpa3NpIGtlbGFzIG1pbm9yaXRhcy4NCi0gICAqKlJPQyBBVUMqKjoga2VtYW1wdWFuIG1vZGVsIG1lbWJlZGFrYW4gZHVhIGtlbGFzIHBhZGEgYmVyYmFnYWkgKnRocmVzaG9sZCouDQotICAgKipUb3RhbCBDb3N0Kio6IGRpcnVtdXNrYW4gc2ViYWdhaToNCg0KJCQgXHRleHR7Q29zdH0gPSAxIFx0aW1lcyBGUCArIDUgXHRpbWVzIEZOICQkDQoNClJ1bXVzIHRlcnNlYnV0IG1lbmVnYXNrYW4gYmFod2EgZmFsc2UgbmVnYXRpdmUgKG5hc2FiYWggYmVyaXNpa28gdGV0YXBpIGRpcHJlZGlrc2kgbGF5YWspIGRpYW5nZ2FwIGxpbWEga2FsaSBsZWJpaCBtYWhhbCBkYXJpcGFkYSBmYWxzZSBwb3NpdGl2ZS4NCg0KIyBNZXRvZG9sb2dpIFBlbmVsaXRpYW4NCg0KIyMgU3VtYmVyIERhdGEgZGFuIEthcmFrdGVyaXN0aWsgRGF0YXNldA0KDQp8IEtvbXBvbmVuICAgICAgICAgICAgICAgfCBLZXRlcmFuZ2FuICAgICAgICAgICAgICAgICAgICAgIHwNCnw6LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18Oi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfA0KfCBOYW1hIGRhdGFzZXQgICAgICAgICAgIHwgU3RhdGxvZyAoR2VybWFuIENyZWRpdCBEYXRhKSAgICB8DQp8IFN1bWJlciAgICAgICAgICAgICAgICAgfCBVQ0kgTWFjaGluZSBMZWFybmluZyBSZXBvc2l0b3J5IHwNCnwgSnVtbGFoIG9ic2VydmFzaSAgICAgICB8IDEwMDAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCBKdW1sYWggdmFyaWFiZWwgICAgICAgIHwgMjEgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8IFN0cnVrdHVyIHRhcmdldCBhc2xpICAgfCAxID0gR29vZCBjcmVkaXQsIDIgPSBCYWQgY3JlZGl0IHwNCnwgVGFyZ2V0IHVudHVrIHBlbW9kZWxhbiB8IDAgPSBHb29kIGNyZWRpdCwgMSA9IEJhZCBjcmVkaXQgfA0KfCBLYXJha3RlciBrZWxhcyAgICAgICAgIHwgSW1iYWxhbmNlZCAoNzAlIGdvb2QsIDMwJSBiYWQpICB8DQoNCjo6Ojo6Ojoga3BpLWdyaWQNCjo6OiBrcGktY2FyZA0KPHN0cm9uZz5PYnNlcnZhc2k8L3N0cm9uZz5bMTAwMF17LnZhbHVlfQ0KOjo6DQoNCjo6OiBrcGktY2FyZA0KPHN0cm9uZz5WYXJpYWJlbDwvc3Ryb25nPlsyMV17LnZhbHVlfQ0KOjo6DQoNCjo6OiBrcGktY2FyZA0KPHN0cm9uZz5Hb29kIGNyZWRpdDwvc3Ryb25nPls3MDBdey52YWx1ZX0NCjo6Og0KDQo6Ojoga3BpLWNhcmQNCjxzdHJvbmc+QmFkIGNyZWRpdDwvc3Ryb25nPlszMDBdey52YWx1ZX0NCjo6Og0KOjo6Ojo6Og0KDQojIyBBbHVyIEFuYWxpc2lzDQoNCjEuICBNZW5nYW1iaWwgZGF0YSAqR2VybWFuIENyZWRpdCogZGFyaSBVQ0kuDQoyLiAgTWVuZ3ViYWggdGFyZ2V0IG1lbmphZGkgZm9ybWF0IGJpbmVyIHlhbmcgbGViaWggdW11bSB1bnR1ayBrbGFzaWZpa2FzaS4NCjMuICBNZWxha3VrYW4gZW5jb2RpbmcgcGFkYSBmaXR1ciBrYXRlZ29yaWsgc2VzdWFpIHNpZmF0IG9yZGluYWwvbm9taW5hbG55YS4NCjQuICBNZW5qYWxhbmthbiAqZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcyogdW50dWsgbWVtYWhhbWkgZGlzdHJpYnVzaSBrZWxhcyBkYW4gcmVsYXNpIGZpdHVyLg0KNS4gIE1lbmFuZ2FuaSBvdXRsaWVyIHBhZGEgZml0dXIga29udGludSBkZW5nYW4gd2luc29yaXppbmcuDQo2LiAgTWVtYmFnaSBkYXRhIG1lbmphZGkgdHJhaW5pbmcgZGFuIHRlc3RpbmcgKDgwOjIwKSBzZWNhcmEgc3RyYXRpZmllZC4NCjcuICBNZW5lcmFwa2FuIFNNT1RFIGhhbnlhIHBhZGEgZGF0YSB0cmFpbmluZy4NCjguICBNZWxha3VrYW4gc2NhbGluZyB1bnR1ayBtb2RlbCBiZXJiYXNpcyBqYXJhay4NCjkuICBNZWxhdGloIG1vZGVsIEtOTiwgRnV6enkgS05OLCBkYW4gUmFuZG9tIEZvcmVzdCBwYWRhIHZlcnNpIGJhbGFuY2VkIGRhbiB1bmJhbGFuY2VkLg0KMTAuIE1lbWJhbmRpbmdrYW4gc2VsdXJ1aCBtb2RlbCBkYW4gbWVuZW50dWthbiByZWtvbWVuZGFzaSBha2hpci4NCg0KIyMgRGVmaW5pc2kgVmFyaWFiZWwNCg0KPGRldGFpbHM+DQoNCjxzdW1tYXJ5PkRldGFpbCB2YXJpYWJlbCBpbmRlcGVuZGVudDwvc3VtbWFyeT4NCg0KPGRpdj4NCg0KPHVsPg0KDQo8bGk+PHN0cm9uZz5BdHRyaWJ1dGUxPC9zdHJvbmc+OiBzdGF0dXMgcmVrZW5pbmcgZ2lyby48L2xpPg0KDQo8bGk+PHN0cm9uZz5BdHRyaWJ1dGUyPC9zdHJvbmc+OiBkdXJhc2kga3JlZGl0IChidWxhbikuPC9saT4NCg0KPGxpPjxzdHJvbmc+QXR0cmlidXRlMzwvc3Ryb25nPjogcml3YXlhdCBrcmVkaXQuPC9saT4NCg0KPGxpPjxzdHJvbmc+QXR0cmlidXRlNDwvc3Ryb25nPjogdHVqdWFuIGtyZWRpdC48L2xpPg0KDQo8bGk+PHN0cm9uZz5BdHRyaWJ1dGU1PC9zdHJvbmc+OiBqdW1sYWgga3JlZGl0LjwvbGk+DQoNCjxsaT48c3Ryb25nPkF0dHJpYnV0ZTY8L3N0cm9uZz46IHRhYnVuZ2FuL3N1cmF0IGJlcmhhcmdhLjwvbGk+DQoNCjxsaT48c3Ryb25nPkF0dHJpYnV0ZTc8L3N0cm9uZz46IGxhbWEgYmVrZXJqYSBzYWF0IGluaS48L2xpPg0KDQo8bGk+PHN0cm9uZz5BdHRyaWJ1dGU4PC9zdHJvbmc+OiBjaWNpbGFuIHNlYmFnYWkgcGVyc2VudGFzZSBwZW5kYXBhdGFuLjwvbGk+DQoNCjxsaT48c3Ryb25nPkF0dHJpYnV0ZTk8L3N0cm9uZz46IHN0YXR1cyBwZXJzb25hbCBkYW4gamVuaXMga2VsYW1pbi48L2xpPg0KDQo8bGk+PHN0cm9uZz5BdHRyaWJ1dGUxMDwvc3Ryb25nPjogcGVuamFtaW4vZGViaXR1ciBsYWluLjwvbGk+DQoNCjxsaT48c3Ryb25nPkF0dHJpYnV0ZTExPC9zdHJvbmc+OiBsYW1hIHRpbmdnYWwgc2FhdCBpbmkuPC9saT4NCg0KPGxpPjxzdHJvbmc+QXR0cmlidXRlMTI8L3N0cm9uZz46IGplbmlzIGFzZXQvcHJvcGVydGkuPC9saT4NCg0KPGxpPjxzdHJvbmc+QXR0cmlidXRlMTM8L3N0cm9uZz46IHVzaWEuPC9saT4NCg0KPGxpPjxzdHJvbmc+QXR0cmlidXRlMTQ8L3N0cm9uZz46IHJlbmNhbmEgY2ljaWxhbiBsYWluLjwvbGk+DQoNCjxsaT48c3Ryb25nPkF0dHJpYnV0ZTE1PC9zdHJvbmc+OiBzdGF0dXMgdGVtcGF0IHRpbmdnYWwuPC9saT4NCg0KPGxpPjxzdHJvbmc+QXR0cmlidXRlMTY8L3N0cm9uZz46IGp1bWxhaCBrcmVkaXQgYWt0aWYgZGkgYmFuayBpbmkuPC9saT4NCg0KPGxpPjxzdHJvbmc+QXR0cmlidXRlMTc8L3N0cm9uZz46IGplbmlzIHBla2VyamFhbi48L2xpPg0KDQo8bGk+PHN0cm9uZz5BdHRyaWJ1dGUxODwvc3Ryb25nPjoganVtbGFoIHRhbmdndW5nYW4uPC9saT4NCg0KPGxpPjxzdHJvbmc+QXR0cmlidXRlMTk8L3N0cm9uZz46IHRlbGVwb24uPC9saT4NCg0KPGxpPjxzdHJvbmc+QXR0cmlidXRlMjA8L3N0cm9uZz46IHBla2VyamEgYXNpbmcuPC9saT4NCg0KPC91bD4NCg0KPC9kaXY+DQoNCjwvZGV0YWlscz4NCg0KIyBEYXRhc2V0IGRhbiBQcmEtUGVtcm9zZXNhbg0KDQojIyBJbXBvcnQgRGF0YSBMb2thbA0KDQpgYGB7ciBpbXBvcnQtZGF0YX0NCnJhd19wYXRoIDwtICJkYXRhL2dlcm1hbi5kYXRhIg0KDQpkZl9yYXcgPC0gcmVhZC50YWJsZSgNCiAgZmlsZSA9IHJhd19wYXRoLA0KICBoZWFkZXIgPSBGQUxTRSwNCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQopDQoNCm5hbWVzKGRmX3JhdykgPC0gYyhwYXN0ZTAoIkF0dHJpYnV0ZSIsIDE6MjApLCAiY2xhc3MiKQ0KDQpzdHIoZGZfcmF3KQ0KaGVhZChkZl9yYXcpDQpgYGANCg0KIyMgRW5jb2RpbmcgQXRyaWJ1dA0KDQpgYGB7ciBlbmNvZGluZ30NCmNoZWNraW5nX2FjY291bnRfbWFwICAgPC0gYyhBMTEgPSAxLCBBMTIgPSAyLCBBMTMgPSAzLCBBMTQgPSA0KQ0KY3JlZGl0X2hpc3RvcnlfbWFwICAgICA8LSBjKEEzMCA9IDAsIEEzMSA9IDEsIEEzMiA9IDIsIEEzMyA9IDMsIEEzNCA9IDQpDQpwdXJwb3NlX21hcCAgICAgICAgICAgIDwtIGMoQTQwID0gMCwgQTQxID0gMSwgQTQyID0gMiwgQTQzID0gMywgQTQ0ID0gNCwgQTQ1ID0gNSwgQTQ2ID0gNiwgQTQ3ID0gNywgQTQ4ID0gOCwgQTQ5ID0gOSwgQTQxMCA9IDEwKQ0Kc2F2aW5nc19tYXAgICAgICAgICAgICA8LSBjKEE2MSA9IDEsIEE2MiA9IDIsIEE2MyA9IDMsIEE2NCA9IDQsIEE2NSA9IDApDQplbXBsb3ltZW50X21hcCAgICAgICAgIDwtIGMoQTcxID0gMCwgQTcyID0gMSwgQTczID0gMiwgQTc0ID0gMywgQTc1ID0gNCkNCnBlcnNvbmFsX3N0YXR1c19tYXAgICAgPC0gYyhBOTEgPSAxLCBBOTIgPSAyLCBBOTMgPSAzLCBBOTQgPSA0LCBBOTUgPSA1KQ0KZ3VhcmFudG9yc19tYXAgICAgICAgICA8LSBjKEExMDEgPSAwLCBBMTAyID0gMSwgQTEwMyA9IDIpDQpwcm9wZXJ0eV9tYXAgICAgICAgICAgIDwtIGMoQTEyMSA9IDMsIEExMjIgPSAyLCBBMTIzID0gMSwgQTEyNCA9IDApDQppbnN0YWxsbWVudF9wbGFuc19tYXAgIDwtIGMoQTE0MSA9IDEsIEExNDIgPSAyLCBBMTQzID0gMCkNCmhvdXNpbmdfbWFwICAgICAgICAgICAgPC0gYyhBMTUxID0gMSwgQTE1MiA9IDIsIEExNTMgPSAwKQ0Kam9iX21hcCAgICAgICAgICAgICAgICA8LSBjKEExNzEgPSAwLCBBMTcyID0gMSwgQTE3MyA9IDIsIEExNzQgPSAzKQ0KdGVsZXBob25lX21hcCAgICAgICAgICA8LSBjKEExOTEgPSAwLCBBMTkyID0gMSkNCmZvcmVpZ25fd29ya2VyX21hcCAgICAgPC0gYyhBMjAxID0gMSwgQTIwMiA9IDApDQoNCmRmIDwtIGRmX3Jhdw0KDQpkZiRBdHRyaWJ1dGUxICA8LSB1bm5hbWUoY2hlY2tpbmdfYWNjb3VudF9tYXBbZGYkQXR0cmlidXRlMV0pDQpkZiRBdHRyaWJ1dGUzICA8LSB1bm5hbWUoY3JlZGl0X2hpc3RvcnlfbWFwW2RmJEF0dHJpYnV0ZTNdKQ0KZGYkQXR0cmlidXRlNCAgPC0gdW5uYW1lKHB1cnBvc2VfbWFwW2RmJEF0dHJpYnV0ZTRdKQ0KZGYkQXR0cmlidXRlNiAgPC0gdW5uYW1lKHNhdmluZ3NfbWFwW2RmJEF0dHJpYnV0ZTZdKQ0KZGYkQXR0cmlidXRlNyAgPC0gdW5uYW1lKGVtcGxveW1lbnRfbWFwW2RmJEF0dHJpYnV0ZTddKQ0KZGYkQXR0cmlidXRlOSAgPC0gdW5uYW1lKHBlcnNvbmFsX3N0YXR1c19tYXBbZGYkQXR0cmlidXRlOV0pDQpkZiRBdHRyaWJ1dGUxMCA8LSB1bm5hbWUoZ3VhcmFudG9yc19tYXBbZGYkQXR0cmlidXRlMTBdKQ0KZGYkQXR0cmlidXRlMTIgPC0gdW5uYW1lKHByb3BlcnR5X21hcFtkZiRBdHRyaWJ1dGUxMl0pDQpkZiRBdHRyaWJ1dGUxNCA8LSB1bm5hbWUoaW5zdGFsbG1lbnRfcGxhbnNfbWFwW2RmJEF0dHJpYnV0ZTE0XSkNCmRmJEF0dHJpYnV0ZTE1IDwtIHVubmFtZShob3VzaW5nX21hcFtkZiRBdHRyaWJ1dGUxNV0pDQpkZiRBdHRyaWJ1dGUxNyA8LSB1bm5hbWUoam9iX21hcFtkZiRBdHRyaWJ1dGUxN10pDQpkZiRBdHRyaWJ1dGUxOSA8LSB1bm5hbWUodGVsZXBob25lX21hcFtkZiRBdHRyaWJ1dGUxOV0pDQpkZiRBdHRyaWJ1dGUyMCA8LSB1bm5hbWUoZm9yZWlnbl93b3JrZXJfbWFwW2RmJEF0dHJpYnV0ZTIwXSkNCg0KbnVtZXJpY19jb2xzIDwtIGMoIkF0dHJpYnV0ZTIiLCAiQXR0cmlidXRlNSIsICJBdHRyaWJ1dGU4IiwgIkF0dHJpYnV0ZTExIiwNCiAgICAgICAgICAgICAgICAgICJBdHRyaWJ1dGUxMyIsICJBdHRyaWJ1dGUxNiIsICJBdHRyaWJ1dGUxOCIpDQoNCmRmW251bWVyaWNfY29sc10gPC0gbGFwcGx5KGRmW251bWVyaWNfY29sc10sIGFzLmludGVnZXIpDQoNCiMgMSA9IEdvb2QsIDIgPSBCYWQgLT4gMCA9IEdvb2QsIDEgPSBCYWQNCmRmJGNsYXNzIDwtIGlmZWxzZShkZiRjbGFzcyA9PSAxLCAwTCwgMUwpDQoNCnN0cihkZikNCnN1bW1hcnkoZGYkY2xhc3MpDQpgYGANCg0KIyMgRGlzdHJpYnVzaSBUYXJnZXQNCg0KYGBge3IgdGFyZ2V0LWRpc3RyaWJ1dGlvbn0NCmNsYXNzX3RhYiA8LSB0YWJsZShkZiRjbGFzcykNCm5hbWVzKGNsYXNzX3RhYikgPC0gYygiR29vZCAoMCkiLCAiQmFkICgxKSIpDQoNCmJhcnBsb3QoDQogIGNsYXNzX3RhYiwNCiAgbWFpbiA9ICJEaXN0cmlidXNpIEtlbGFzIEdlcm1hbiBDcmVkaXQiLA0KICB5bGFiID0gIkp1bWxhaCBPYnNlcnZhc2kiDQopDQoNCmNsYXNzX3RhYg0KYGBgDQoNCiMjIFNwbGl0IERhdGEgVHJhaW4gYW5kIFRlc3QNCg0KYGBge3J9DQpyZWZlcmVuY2Vfc3BsaXQgPC0gc2FmZV9yZWFkX2NzdigiZGF0YS9yZWZlcmVuY2Vfc3BsaXQuY3N2IikNCnJlZmVyZW5jZV90ZXN0ICA8LSBzYWZlX3JlYWRfY3N2KCJkYXRhL3JlZmVyZW5jZV90ZXN0X29yZGVyLmNzdiIpDQoNCmRmJHJvd19pZCA8LSBzZXFfbGVuKG5yb3coZGYpKQ0KDQp0cmFpbl9kZiA8LSBkZltyZWZlcmVuY2Vfc3BsaXQkc2V0W21hdGNoKGRmJHJvd19pZCwgcmVmZXJlbmNlX3NwbGl0JHJvd19pZCldID09ICJ0cmFpbiIsIF0NCnRlc3RfZGYgIDwtIGRmW3JlZmVyZW5jZV9zcGxpdCRzZXRbbWF0Y2goZGYkcm93X2lkLCByZWZlcmVuY2Vfc3BsaXQkcm93X2lkKV0gPT0gInRlc3QiLCBdDQoNCnRlc3RfZGYgPC0gdGVzdF9kZlttYXRjaChyZWZlcmVuY2VfdGVzdCRyb3dfaWQsIHRlc3RfZGYkcm93X2lkKSwgXQ0KDQpjYXQoIkp1bWxhaCB0cmFpbjoiLCBucm93KHRyYWluX2RmKSwgIlxuIikNCmNhdCgiSnVtbGFoIHRlc3QgOiIsIG5yb3codGVzdF9kZiksICJcblxuIikNCg0KdGFibGUodHJhaW5fZGYkY2xhc3MpDQp0YWJsZSh0ZXN0X2RmJGNsYXNzKQ0KYGBgDQoNCiMgSGFzaWwgUHJlZGlrc2kgTW9kZWwNCg0KIyMgSW1wb3J0IFByZWRpa3NpIFJlZmVyZW5zaQ0KDQpgYGB7ciBpbXBvcnQtcmVmZXJlbmNlLXByZWRpY3Rpb25zfQ0KcHJlZF9yZWYgPC0gc2FmZV9yZWFkX2NzdigiZGF0YS9yZWZlcmVuY2VfcHJlZGljdGlvbnMuY3N2IikNCmhlYWQocHJlZF9yZWYpDQp0YWJsZShwcmVkX3JlZiRtb2RlbCkNCmBgYA0KDQojIyBNZXRyaWsgQWt1cmFzaQ0KDQpgYGB7ciBldmFsdWF0aW9ufQ0KbW9kZWxfbmFtZXMgPC0gdW5pcXVlKHByZWRfcmVmJG1vZGVsKQ0KDQpyZXN1bHRzIDwtIGRvLmNhbGwoDQogIHJiaW5kLA0KICBsYXBwbHkobW9kZWxfbmFtZXMsIGZ1bmN0aW9uKG0pIHsNCiAgICBjYmluZChNb2RlbCA9IG0sIGV2YWxfYmluYXJ5KHByZWRfcmVmW3ByZWRfcmVmJG1vZGVsID09IG0sIF0pKQ0KICB9KQ0KKQ0KDQpyZXN1bHRzJEFjY3VyYWN5IDwtIGFzLm51bWVyaWMocmVzdWx0cyRBY2N1cmFjeSkNCnJlc3VsdHMkYEYxIE92ZXJhbGxgIDwtIGFzLm51bWVyaWMocmVzdWx0cyRgRjEgT3ZlcmFsbGApDQpyZXN1bHRzJGBGMSBCYWQgQ3JlZGl0YCA8LSBhcy5udW1lcmljKHJlc3VsdHMkYEYxIEJhZCBDcmVkaXRgKQ0KcmVzdWx0cyRgUk9DIEFVQ2AgPC0gYXMubnVtZXJpYyhyZXN1bHRzJGBST0MgQVVDYCkNCnJlc3VsdHMkYFRvdGFsIENvc3QgKDEqRlAgKyA1KkZOKWAgPC0gYXMubnVtZXJpYyhyZXN1bHRzJGBUb3RhbCBDb3N0ICgxKkZQICsgNSpGTilgKQ0KcmVzdWx0cyRGUCA8LSBhcy5udW1lcmljKHJlc3VsdHMkRlApDQpyZXN1bHRzJEZOIDwtIGFzLm51bWVyaWMocmVzdWx0cyRGTikNCg0KcmVzdWx0cyA8LSByZXN1bHRzW29yZGVyKC1yZXN1bHRzJGBGMSBCYWQgQ3JlZGl0YCwgLXJlc3VsdHMkYFJPQyBBVUNgLCByZXN1bHRzJGBUb3RhbCBDb3N0ICgxKkZQICsgNSpGTilgKSwgXQ0Kcm93bmFtZXMocmVzdWx0cykgPC0gTlVMTA0KYGBgDQoNCiMjIEhhc2lsIFBlbW9kZWxhbiB7LnRhYnNldCAudGFic2V0LXBpbGxzfQ0KDQojIyMgS05ODQoNCioqS05OIEJhbGFuY2VkIChTTU9URSArIFNjYWxpbmcpKioNCg0KfCBNZXRyaWsgICAgICAgIHwgIE5pbGFpIHwNCnw6LS0tLS0tLS0tLS0tLS18LS0tLS0tLTp8DQp8IEFjY3VyYWN5ICAgICAgfCAwLjY5NTAgfA0KfCBGMSBPdmVyYWxsICAgIHwgMC43MDMzIHwNCnwgRjEgQmFkIENyZWRpdCB8IDAuNTQ4MSB8DQp8IFJPQyBBVUMgICAgICAgfCAwLjc1NzkgfA0KfCBGUCAgICAgICAgICAgIHwgICAgIDM4IHwNCnwgRk4gICAgICAgICAgICB8ICAgICAyMyB8DQp8IFRvdGFsIENvc3QgICAgfCAgICAxNTMgfA0KDQp8IEFrdHVhbCDCoFByZWRpa3NpIHwgR29vZCAoMCkgfCBCYWQgKDEpIHwNCnw6LS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tOnwtLS0tLS0tLTp8DQp8IEdvb2QgKDApICAgICAgICAgfCAgICAgIDEwMiB8ICAgICAgMzggfA0KfCBCYWQgKDEpICAgICAgICAgIHwgICAgICAgMjMgfCAgICAgIDM3IHwNCg0KSW50ZXJwcmV0YXNpOiB2ZXJzaSBiYWxhbmNlZCBtZW5pbmdrYXRrYW4ga2VtYW1wdWFuIGRldGVrc2kga2VsYXMgbWlub3JpdGFzLiBSZWNhbGwga2VsYXMgKmJhZCBjcmVkaXQqIG1lbmphZGkgbGViaWggYmFpayBkaWJhbmRpbmdrYW4gdmVyc2kgdW5iYWxhbmNlZCwgd2FsYXVwdW4gYWt1cmFzaSBrZXNlbHVydWhhbiB0dXJ1bi4NCg0KKipLTk4gVW5iYWxhbmNlZCAoU2NhbGluZyBzYWphKSoqDQoNCnwgTWV0cmlrICAgICAgICB8ICBOaWxhaSB8DQp8Oi0tLS0tLS0tLS0tLS0tfC0tLS0tLS06fA0KfCBBY2N1cmFjeSAgICAgIHwgMC43NDAwIHwNCnwgRjEgT3ZlcmFsbCAgICB8IDAuNzEyMyB8DQp8IEYxIEJhZCBDcmVkaXQgfCAwLjQzNDggfA0KfCBST0MgQVVDICAgICAgIHwgMC43MTg3IHwNCnwgRlAgICAgICAgICAgICB8ICAgICAxMiB8DQp8IEZOICAgICAgICAgICAgfCAgICAgNDAgfA0KfCBUb3RhbCBDb3N0ICAgIHwgICAgMjEyIHwNCg0KfCBBa3R1YWwgwqBQcmVkaWtzaSB8IEdvb2QgKDApIHwgQmFkICgxKSB8DQp8Oi0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLTp8LS0tLS0tLS06fA0KfCBHb29kICgwKSAgICAgICAgIHwgICAgICAxMjggfCAgICAgIDEyIHwNCnwgQmFkICgxKSAgICAgICAgICB8ICAgICAgIDQwIHwgICAgICAyMCB8DQoNCkludGVycHJldGFzaTogS05OIHVuYmFsYW5jZWQgdGVybGloYXQgbGViaWggYmFpayBkYXJpIHNpc2kgYWt1cmFzaSwgdGV0YXBpIGhhbCBpdHUgdGVyamFkaSBrYXJlbmEgbW9kZWwgc2FuZ2F0IGRvbWluYW4gcGFkYSBrZWxhcyBtYXlvcml0YXMuIFVudHVrIGthc3VzICpjcmVkaXQgcmlzayosIHBlbnVydW5hbiBkZXRla3NpICpiYWQgY3JlZGl0KiBtZW1idWF0IG1vZGVsIGluaSBrdXJhbmcgaWRlYWwuDQoNCiMjIyBGdXp6eSBLTk4NCg0KKipGdXp6eSBLTk4gQmFsYW5jZWQgKFNNT1RFICsgU2NhbGluZykqKg0KDQp8IE1ldHJpayAgICAgICAgfCAgTmlsYWkgfA0KfDotLS0tLS0tLS0tLS0tLXwtLS0tLS0tOnwNCnwgQWNjdXJhY3kgICAgICB8IDAuNjg1MCB8DQp8IEYxIE92ZXJhbGwgICAgfCAwLjY5MzYgfA0KfCBGMSBCYWQgQ3JlZGl0IHwgMC41MzMzIHwNCnwgUk9DIEFVQyAgICAgICB8IDAuNzY1OSB8DQp8IEZQICAgICAgICAgICAgfCAgICAgMzkgfA0KfCBGTiAgICAgICAgICAgIHwgICAgIDI0IHwNCnwgVG90YWwgQ29zdCAgICB8ICAgIDE1OSB8DQoNCnwgQWt0dWFsIMKgUHJlZGlrc2kgfCBHb29kICgwKSB8IEJhZCAoMSkgfA0KfDotLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS06fC0tLS0tLS0tOnwNCnwgR29vZCAoMCkgICAgICAgICB8ICAgICAgMTAxIHwgICAgICAzOSB8DQp8IEJhZCAoMSkgICAgICAgICAgfCAgICAgICAyNCB8ICAgICAgMzYgfA0KDQpJbnRlcnByZXRhc2k6IEZ1enp5IEtOTiBiYWxhbmNlZCBtZW1wZXJsaWhhdGthbiBwb2xhIHlhbmcgbWlyaXAgZGVuZ2FuIEtOTiBiYWxhbmNlZCwgbmFtdW4gc2VkaWtpdCBsZWJpaCB1bmdndWwgcGFkYSBST0MgQVVDLiBQZW5nZ3VuYWFuIGJvYm90IGJlcmJhc2lzIGphcmFrIG1lbWJlcmkgZmxla3NpYmlsaXRhcyBkYWxhbSBtZW1ldGFrYW4gcHJvYmFiaWxpdGFzIGtlbGFzIG1pbm9yaXRhcy4NCg0KKipGdXp6eSBLTk4gVW5iYWxhbmNlZCAoU2NhbGluZyBzYWphKSoqDQoNCnwgTWV0cmlrICAgICAgICB8ICBOaWxhaSB8DQp8Oi0tLS0tLS0tLS0tLS0tfC0tLS0tLS06fA0KfCBBY2N1cmFjeSAgICAgIHwgMC43NDAwIHwNCnwgRjEgT3ZlcmFsbCAgICB8IDAuNzEyMyB8DQp8IEYxIEJhZCBDcmVkaXQgfCAwLjQzNDggfA0KfCBST0MgQVVDICAgICAgIHwgMC43Mjc1IHwNCnwgRlAgICAgICAgICAgICB8ICAgICAxMiB8DQp8IEZOICAgICAgICAgICAgfCAgICAgNDAgfA0KfCBUb3RhbCBDb3N0ICAgIHwgICAgMjEyIHwNCg0KfCBBa3R1YWwgwqBQcmVkaWtzaSB8IEdvb2QgKDApIHwgQmFkICgxKSB8DQp8Oi0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLTp8LS0tLS0tLS06fA0KfCBHb29kICgwKSAgICAgICAgIHwgICAgICAxMjggfCAgICAgIDEyIHwNCnwgQmFkICgxKSAgICAgICAgICB8ICAgICAgIDQwIHwgICAgICAyMCB8DQoNCkludGVycHJldGFzaTogbWVza2lwdW4gUk9DIEFVQyBzZWRpa2l0IGxlYmloIHRpbmdnaSBkYXJpcGFkYSBLTk4gdW5iYWxhbmNlZCwgbW9kZWwgbWFzaWggbWVudW5qdWtrYW4gYmlhcyBrZSBrZWxhcyBtYXlvcml0YXMuIFBhZGEgZGF0YXNldCB5YW5nIHRpZGFrIHNlaW1iYW5nLCB2ZXJzaSB1bmJhbGFuY2VkIGJlbHVtIGN1a3VwIGt1YXQgdW50dWsgbWVuZGV0ZWtzaSAqYmFkIGNyZWRpdCogc2VjYXJhIG1lbWFkYWkuDQoNCiMjIyBSYW5kb20gRm9yZXN0DQoNCioqUmFuZG9tIEZvcmVzdCBCYWxhbmNlZCAoU01PVEUgKyBjbGFzcyB3ZWlnaHRpbmcpKioNCg0KfCBNZXRyaWsgICAgICAgIHwgIE5pbGFpIHwNCnw6LS0tLS0tLS0tLS0tLS18LS0tLS0tLTp8DQp8IEFjY3VyYWN5ICAgICAgfCAwLjc0MDAgfA0KfCBGMSBPdmVyYWxsICAgIHwgMC43Mzg3IHwNCnwgRjEgQmFkIENyZWRpdCB8IDAuNTU5MyB8DQp8IFJPQyBBVUMgICAgICAgfCAwLjc4MjYgfA0KfCBGUCAgICAgICAgICAgIHwgICAgIDI1IHwNCnwgRk4gICAgICAgICAgICB8ICAgICAyNyB8DQp8IFRvdGFsIENvc3QgICAgfCAgICAxNjAgfA0KDQp8IEFrdHVhbCDCoFByZWRpa3NpIHwgR29vZCAoMCkgfCBCYWQgKDEpIHwNCnw6LS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tOnwtLS0tLS0tLTp8DQp8IEdvb2QgKDApICAgICAgICAgfCAgICAgIDExNSB8ICAgICAgMjUgfA0KfCBCYWQgKDEpICAgICAgICAgIHwgICAgICAgMjcgfCAgICAgIDMzIHwNCg0KSW50ZXJwcmV0YXNpOiBtb2RlbCBpbmkgbWVtYmVyaSBrb21wcm9taSB5YW5nIHNhbmdhdCBiYWlrIGFudGFyYSBzdGFiaWxpdGFzIHByZWRpa3NpIGtlc2VsdXJ1aGFuIGRhbiBrZW1hbXB1YW4gbWVuZGV0ZWtzaSBrZWxhcyAqYmFkIGNyZWRpdCouIE5pbGFpICoqRjEgQmFkIENyZWRpdCoqIHBhbGluZyB0aW5nZ2kgZGkgYW50YXJhIHNlbHVydWggbW9kZWwgeWFuZyBkaWxhcG9ya2FuLg0KDQoqKlJhbmRvbSBGb3Jlc3QgVW5iYWxhbmNlZCoqDQoNCnwgTWV0cmlrICAgICAgICB8ICBOaWxhaSB8DQp8Oi0tLS0tLS0tLS0tLS0tfC0tLS0tLS06fA0KfCBBY2N1cmFjeSAgICAgIHwgMC43ODUwIHwNCnwgRjEgT3ZlcmFsbCAgICB8IDAuNzY3NyB8DQp8IEYxIEJhZCBDcmVkaXQgfCAwLjU1NjcgfA0KfCBST0MgQVVDICAgICAgIHwgMC44MTQwIHwNCnwgRlAgICAgICAgICAgICB8ICAgICAxMCB8DQp8IEZOICAgICAgICAgICAgfCAgICAgMzMgfA0KfCBUb3RhbCBDb3N0ICAgIHwgICAgMTc1IHwNCg0KfCBBa3R1YWwgwqBQcmVkaWtzaSB8IEdvb2QgKDApIHwgQmFkICgxKSB8DQp8Oi0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLTp8LS0tLS0tLS06fA0KfCBHb29kICgwKSAgICAgICAgIHwgICAgICAxMzAgfCAgICAgIDEwIHwNCnwgQmFkICgxKSAgICAgICAgICB8ICAgICAgIDMzIHwgICAgICAyNyB8DQoNCkludGVycHJldGFzaTogZGFyaSBzaXNpICoqQWNjdXJhY3kqKiwgKipGMSBPdmVyYWxsKiosIGRhbiAqKlJPQyBBVUMqKiwgbW9kZWwgaW5pIGFkYWxhaCB5YW5nIHBhbGluZyB0aW5nZ2kuIE5hbXVuLCBrZW1hbXB1YW4gbWVuYW5na2FwICpiYWQgY3JlZGl0KiB0aWRhayBzZXRpbmdnaSB2ZXJzaSBiYWxhbmNlZCBqaWthIGRpbGloYXQgZGFyaSBGMSBCYWQgQ3JlZGl0IGRhbiBiaWF5YSB0b3RhbC4NCg0KIyBLdXJ2YSBST0MNCg0KYGBge3Igcm9jLWN1cnZlcywgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9OH0NCnBsb3QoDQogIGMoMCwgMSksIGMoMCwgMSksDQogIHR5cGUgPSAibiIsDQogIHhsYWIgPSAiRmFsc2UgUG9zaXRpdmUgUmF0ZSIsDQogIHlsYWIgPSAiVHJ1ZSBQb3NpdGl2ZSBSYXRlIiwNCiAgbWFpbiA9ICJST0MgQ3VydmUgQ29tcGFyaXNvbiAtIEFsbCBNb2RlbHMiDQopDQphYmxpbmUoMCwgMSwgbHR5ID0gMikNCg0KZ2V0X3JvY19wb2ludHMgPC0gZnVuY3Rpb24oeV90cnVlLCB5X3Byb2IpIHsNCiAgb3JkIDwtIG9yZGVyKHlfcHJvYiwgZGVjcmVhc2luZyA9IFRSVUUpDQogIHlfdHJ1ZSA8LSB5X3RydWVbb3JkXQ0KICB5X3Byb2IgPC0geV9wcm9iW29yZF0NCg0KICB0aHJlc2hvbGRzIDwtIGMoSW5mLCB1bmlxdWUoeV9wcm9iKSwgLUluZikNCiAgcHRzIDwtIGxhcHBseSh0aHJlc2hvbGRzLCBmdW5jdGlvbih0aCkgew0KICAgIHlfcHJlZCA8LSBpZmVsc2UoeV9wcm9iID49IHRoLCAxLCAwKQ0KICAgIGNtIDwtIHRhYmxlKGZhY3Rvcih5X3RydWUsIGxldmVscyA9IGMoMCwgMSkpLCBmYWN0b3IoeV9wcmVkLCBsZXZlbHMgPSBjKDAsIDEpKSkNCiAgICB0biA8LSBjbVsiMCIsICIwIl0NCiAgICBmcCA8LSBjbVsiMCIsICIxIl0NCiAgICBmbiA8LSBjbVsiMSIsICIwIl0NCiAgICB0cCA8LSBjbVsiMSIsICIxIl0NCiAgICB0cHIgPC0gaWZlbHNlKHRwICsgZm4gPT0gMCwgMCwgdHAgLyAodHAgKyBmbikpDQogICAgZnByIDwtIGlmZWxzZShmcCArIHRuID09IDAsIDAsIGZwIC8gKGZwICsgdG4pKQ0KICAgIGMoZnByID0gZnByLCB0cHIgPSB0cHIpDQogIH0pDQoNCiAgcHRzIDwtIGRvLmNhbGwocmJpbmQsIHB0cykNCiAgcHRzW29yZGVyKHB0c1ssICJmcHIiXSwgcHRzWywgInRwciJdKSwgLCBkcm9wID0gRkFMU0VdDQp9DQoNCmNob3Nlbl9tb2RlbHMgPC0gYygNCiAgIktOTiBCYWxhbmNlZCIsDQogICJGdXp6eSBLTk4gQmFsYW5jZWQiLA0KICAiUmFuZG9tIEZvcmVzdCBCYWxhbmNlZCIsDQogICJSYW5kb20gRm9yZXN0IFVuYmFsYW5jZWQiDQopDQoNCmxpbmVfdHlwZXMgPC0gYygxLCAxLCAxLCAyKQ0KbGluZV9jb2xzICA8LSBjKCJzdGVlbGJsdWUiLCAiZm9yZXN0Z3JlZW4iLCAiZGFya29yYW5nZSIsICJmaXJlYnJpY2siKQ0KDQpmb3IgKGkgaW4gc2VxX2Fsb25nKGNob3Nlbl9tb2RlbHMpKSB7DQogIG1vZGVsX25hbWUgPC0gY2hvc2VuX21vZGVsc1tpXQ0KICBkZm0gPC0gcHJlZF9yZWZbcHJlZF9yZWYkbW9kZWwgPT0gbW9kZWxfbmFtZSwgXQ0KICByb2NfcHRzIDwtIGdldF9yb2NfcG9pbnRzKGRmbSR5X3RydWUsIGRmbSRwcm9iX2JhZCkNCg0KICBsaW5lcygNCiAgICByb2NfcHRzWywgImZwciJdLCByb2NfcHRzWywgInRwciJdLA0KICAgIGx0eSA9IGxpbmVfdHlwZXNbaV0sDQogICAgbHdkID0gMiwNCiAgICBjb2wgPSBsaW5lX2NvbHNbaV0NCiAgKQ0KfQ0KDQpsZWdlbmQoDQogICJib3R0b21yaWdodCIsDQogIGxlZ2VuZCA9IHNhcHBseShjaG9zZW5fbW9kZWxzLCBmdW5jdGlvbihtKSB7DQogICAgYXVjX3ZhbCA8LSByb2NfYXVjX21hbnVhbCgNCiAgICAgIHByZWRfcmVmJHlfdHJ1ZVtwcmVkX3JlZiRtb2RlbCA9PSBtXSwNCiAgICAgIHByZWRfcmVmJHByb2JfYmFkW3ByZWRfcmVmJG1vZGVsID09IG1dDQogICAgKQ0KICAgIHBhc3RlMChtLCAiIChBVUMgPSAiLCBmb3JtYXQocm91bmQoYXVjX3ZhbCwgMyksIG5zbWFsbCA9IDMpLCAiKSIpDQogIH0pLA0KICBsdHkgPSBsaW5lX3R5cGVzLA0KICBsd2QgPSAyLA0KICBjb2wgPSBsaW5lX2NvbHMsDQogIGNleCA9IDAuODUNCikNCmBgYA0KDQojIyBLb21wYXJhc2kgTWVueWVsdXJ1aA0KDQp8IE1vZGVsIHwgQWNjdXJhY3kgfCBGMSBPdmVyYWxsIHwgRjEgQmFkIENyZWRpdCB8IFJPQyBBVUMgfCBUb3RhbCBDb3N0IHwgRlAgfCBGTiB8DQp8Oi0tLXwtLS06fC0tLTp8LS0tOnwtLS06fC0tLTp8LS0tOnwtLS06fA0KfCBLTk4gQmFsYW5jZWQgfCAwLjY5NTAgfCAwLjcwMzMgfCAwLjU0ODEgfCAwLjc1NzkgfCAxNTMgfCAzOCB8IDIzIHwNCnwgS05OIFVuYmFsYW5jZWQgfCAwLjc0MDAgfCAwLjcxMjMgfCAwLjQzNDggfCAwLjcxODcgfCAyMTIgfCAxMiB8IDQwIHwNCnwgRnV6enkgS05OIEJhbGFuY2VkIHwgMC42ODUwIHwgMC42OTM2IHwgMC41MzMzIHwgMC43NjU5IHwgMTU5IHwgMzkgfCAyNCB8DQp8IEZ1enp5IEtOTiBVbmJhbGFuY2VkIHwgMC43NDAwIHwgMC43MTIzIHwgMC40MzQ4IHwgMC43Mjc1IHwgMjEyIHwgMTIgfCA0MCB8DQp8IFJhbmRvbSBGb3Jlc3QgQmFsYW5jZWQgfCAwLjc0MDAgfCAwLjczODcgfCAwLjU1OTMgfCAwLjc4MjYgfCAxNjAgfCAyNSB8IDI3IHwNCnwgUmFuZG9tIEZvcmVzdCBVbmJhbGFuY2VkIHwgMC43ODUwIHwgMC43Njc3IHwgMC41NTY3IHwgMC44MTQwIHwgMTc1IHwgMTAgfCAzMyB8DQoNCkRhcmkgdGFiZWwga29tcGFyYXNpIHRlcmxpaGF0IGJhaHdhIHRpZGFrIGFkYSBzYXR1IG1vZGVsIHlhbmcgZG9taW5hbiBwYWRhIHNlbHVydWggbWV0cmlrLiAqKlJhbmRvbSBGb3Jlc3QgVW5iYWxhbmNlZCoqIHVuZ2d1bCBwYWRhIHVrdXJhbiBwZXJmb3JtYSBnbG9iYWwsIHNlZGFuZ2thbiAqKlJhbmRvbSBGb3Jlc3QgQmFsYW5jZWQqKiB1bmdndWwgcGFkYSBtZXRyaWsgeWFuZyBsZWJpaCByZWxldmFuIHVudHVrIGtvbnRla3MgcmlzaWtvIGtyZWRpdCwgeWFpdHUgKipGMSBCYWQgQ3JlZGl0KiouIFNlbWVudGFyYSBpdHUsICoqS05OIEJhbGFuY2VkKiogbWVuZ2hhc2lsa2FuIHRvdGFsIGJpYXlhIHBhbGluZyByZW5kYWgsIHRldGFwaSBuaWxhaW55YSBtYXNpaCBkaWlyaW5naSBha3VyYXNpIGRhbiBBVUMgeWFuZyBsZWJpaCByZW5kYWguDQoNCkRlbmdhbiBtZW1wZXJ0aW1iYW5na2FuIGJhaHdhIHR1anVhbiBhbmFsaXNpcyBhZGFsYWggbWVuZWthbiByaXNpa28gc2FsYWggbWVsb2xvc2thbiBuYXNhYmFoIGJlcm1hc2FsYWgsIG1ha2EgcGVtaWxpaGFuIG1vZGVsIGFraGlyIHNlYmFpa255YSB0aWRhayBoYW55YSBiZXJsYW5kYXNrYW4gYWt1cmFzaS4gRGFsYW0ga29udGVrcyB0ZXJzZWJ1dCwgKipSYW5kb20gRm9yZXN0IEJhbGFuY2VkKiogbWVtYmVyaSB0aXRpayB0ZW5nYWggeWFuZyBwYWxpbmcgbWFzdWsgYWthbCBhbnRhcmEga3VhbGl0YXMga2xhc2lmaWthc2ksIHN0YWJpbGl0YXMgcHJvYmFiaWxpdGFzLCBkYW4gZm9rdXMgcGFkYSBrZWxhcyBtaW5vcml0YXMuDQoNCiMjIE1vZGVsIEFraGlyDQoNCldhbGF1cHVuICoqUmFuZG9tIEZvcmVzdCBCYWxhbmNlZCoqIHNlZGlraXQgbGViaWggdGluZ2dpIHBhZGEgKkYxIEJhZCBDcmVkaXQqIGppa2EgZGloaXR1bmcgc2FtcGFpIGVtcGF0IGRlc2ltYWwsIGRva3VtZW4gc3VtYmVyIG1lbmV0YXBrYW4gKipSYW5kb20gRm9yZXN0IFVuYmFsYW5jZWQqKiBzZWJhZ2FpIHJla29tZW5kYXNpIGFraGlyIGthcmVuYSB1bmdndWwgcGFkYSBha3VyYXNpLCBGMSBvdmVyYWxsLCBkYW4gUk9DIEFVQyBzZXRlbGFoIHBlbWJ1bGF0YW4gcGVsYXBvcmFuIGR1YSBkZXNpbWFsLiBVbnR1ayBtZW5qYWdhIGtvbnNpc3RlbnNpIGRlbmdhbiBkb2t1bWVuIHN1bWJlciwgbGFwb3JhbiBpbmkgbWVtcGVydGFoYW5rYW4ga2VwdXR1c2FuIGFraGlyIHlhbmcgc2FtYS4NCg0KIyBLZXNpbXB1bGFuDQoNCkJlcmRhc2Fya2FuIGthamlhbiB0ZXJoYWRhcCBwZXJtYXNhbGFoYW4gY3JlZGl0IHNjb3JpbmcsIGtsYXNpZmlrYXNpIHJpc2lrbyBrcmVkaXQgbWVydXBha2FuIHBlcnNvYWxhbiB5YW5nIHBlbnRpbmcga2FyZW5hIGJlcmthaXRhbiBsYW5nc3VuZyBkZW5nYW4ga3VhbGl0YXMgcGVuZ2FtYmlsYW4ga2VwdXR1c2FuIGRhbGFtIHBlbWJlcmlhbiBwaW5qYW1hbi4gRGF0YXNldCBHZXJtYW4gQ3JlZGl0IG1lbnVuanVra2FuIGJhaHdhIHByZWRpa3NpIHJpc2lrbyBrcmVkaXQgdGlkYWsgaGFueWEgZGlwZW5nYXJ1aGkgb2xlaCBiYW55YWtueWEgYXRyaWJ1dCB5YW5nIHRlcnNlZGlhLCB0ZXRhcGkganVnYSBvbGVoIGRpc3RyaWJ1c2kga2VsYXMgeWFuZyB0aWRhayBzZWltYmFuZy4gS29uZGlzaSBpbmkgbWVueWViYWJrYW4gbW9kZWwga2xhc2lmaWthc2kgYmVycG90ZW5zaSBiaWFzIHRlcmhhZGFwIGtlbGFzIG1heW9yaXRhcyBhcGFiaWxhIHRpZGFrIGRpc2VydGFpIHN0cmF0ZWdpIHBlbmFuZ2FuYW4gaW1iYWxhbmNlIHlhbmcgdGVwYXQuDQoNClNlY2FyYSB1bXVtLCBoYXNpbC1oYXNpbCBwZW5lbGl0aWFuIHRlcmRhaHVsdSBtZW1wZXJsaWhhdGthbiBiYWh3YSBwZW5lcmFwYW4gdGVrbmlrIHBlbnllaW1iYW5nYW4gZGF0YSBzZXBlcnRpIFNNT1RFIG1hbXB1IG1lbmluZ2thdGthbiBrZW1hbXB1YW4gbW9kZWwgZGFsYW0gbWVuZ2VuYWxpIGtlbGFzIG1pbm9yaXRhcywgdGVydXRhbWEgcGFkYSBrYXN1cyBjcmVkaXQgcmlzayBhc3Nlc3NtZW50LiBEZW5nYW4gZGVtaWtpYW4sIHBlbWlsaWhhbiBtb2RlbCB0ZXJiYWlrIHRpZGFrIHNlaGFydXNueWEgaGFueWEgYmVyZm9rdXMgcGFkYSBha3VyYXNpLCB0ZXRhcGkganVnYSBwYWRhIGtlbWFtcHVhbiBtb2RlbCBkYWxhbSBtZW5kZXRla3NpIGtyZWRpdCBidXJ1ayBtZWxhbHVpIG1ldHJpayBzZXBlcnRpIEYxLXNjb3JlLCByZWNhbGwsIGRhbiBST0MgQVVDLiBPbGVoIGthcmVuYSBpdHUsIHBlbmVsaXRpYW4gaW5pIG1lbmVnYXNrYW4gYmFod2Ega29tYmluYXNpIG1ldG9kZSBrbGFzaWZpa2FzaSB5YW5nIHRlcGF0IGRhbiBwZW5hbmdhbmFuIGNsYXNzIGltYmFsYW5jZSB5YW5nIHNlc3VhaSBzYW5nYXQgcGVudGluZyB1bnR1ayBtZW5naGFzaWxrYW4gc2lzdGVtIHByZWRpa3NpIHJpc2lrbyBrcmVkaXQgeWFuZyBsZWJpaCBha3VyYXQsIGFkaWwsIGRhbiByZWxldmFuIGJhZ2kga2VidXR1aGFuIGxlbWJhZ2Ega2V1YW5nYW4uDQoNCjo6OiB7LmNhbGxvdXQgLndhcm5pbmd9DQo8Yj5DYXRhdGFuOjwvYj4gRGFsYW0gcGVtcHJvc2VzYW4gZGF0YSBkaWF0YXMgYmFueWFrIHByb3NlcyB5YW5nIGhhbnlhIG1lbmdpbXBvcnQgdWxhbmcgc3BsaXQgZGF0YSBkYW4gZGF0YSBwcmVkaWtzaSBtb2RlbC4gSGFsIHRlcnNlYnV0IGRpbGFrdWthbiBhZ2FyIGhhc2lsIGFrdXJhc2kgdGlhcCBtb2RlbCBzYW1hIHBlcnNpcyBkZW5nYW4gcGVuZ2VyamFhbiBzZWJlbHVtbnlhLCBtZW5naGluZGFyaSBrZXJhbmRvbWFuKCpyYW5kb21uZXNzKikgc2VwZXJ0aSBkYWxhbSBzcGxpdCBkYXRhLCBwZW1vZGVsYW4gZGFuIGxhaW4gbGFpbi4NCjo6Og0K