Nhu cầu về các mô hình phân loại ở các tổ chức tài chính - tín dụng

Xếp hạng tín dụng đóng một vai trò quan trọng đối với lợi nhuận và phát triển bền vững của ngân hàng nói riêng cũng như các tổ chức tài chính khác. Hiện nay cách tiếp cận theo phương pháp học máy (Machine Learning) đã chứng tỏ nhiều ưu thế về mức độ chính xác cũng như tin cậy so với một số mô hình phân loại truyền thống.

Sự kiện cuộc khủng hoảng tài chính dẫn đến sự sụp đổ của một loạt các định chế tài chính nói chung và ngân hàng nói riêng đã thức tỉnh các tổ chức này chú trọng hơn đến vai trò của thẩm định tín dụng trong hoạt động của mình. Hầu hết lợi nhuận của các ngân hàng đến từ hoạt động cấp tín dụng và cho vay. Cấp tín dụng là một trong những hoạt động tạo ra một tỉ trọng lớn về doanh thu và lợi nhuận cho ngân hàng nhưng cũng tiềm ẩn rất nhiều rủi ro (Zakrzewska, 2007). Rủi ro chính của ngân hàng là khả năng khách hàng không có khả năng hoàn trả lại khoản vay mà ngân hàng đã cấp. Mặt khác, quyết định có hay không cung cấp một khoản vay cho khách hàng thường phụ thuộc nhiều vào trình độ cũng như kinh nghiệm của cán nhân viên thẩm định tín dụng (Thomas, 2000). Ngoài ra, căn cứ để cấp tín dụng cho một khách hàng còn căn cứ vào một số tiêu chí xếp hạng mà một số trong số đó là rất khó đo lường, hoặc khó có thể đo lường chính xác. Chẳng hạn tiêu chuẩn 5C khi cấp tín dụng là dựa trên những đánh giá của ngân hàng về tư cách, năng lực, vốn, tài sản thế chấp, và điều kiện của người xin vay (Abrahams & Zhang, 2008). Rõ ràng một số tiêu chí, chẳng hạn như tư cách và năng lực của người vay là một nhân tố khó đánh giá và do vậy có thể dẫn đến các sai sót khi quyết định cho vay. Ngoài ra phương pháp đánh giá xếp hạng tín dụng dựa trên tiêu chuẩn 5C là có chi phí cao và có thể xẩy ra sự không nhất quán về việc cho vay hay không giữa những nhân viên thẩm định tín dụng khác nhau đối với cùng một hồ sơ xin vay. Vì những hạn chế này, các ngân hàng cũng như các tổ chức tài chính cần sử dụng các phương pháp thẩm định và xếp hạng tín dụng tin cậy, khách quan và chi phí thấp nhằm giúp những tổ chức này quyết định có hay không cấp tín dụng cho các hồ sơ xin vay (Akhavein, Frame, & White, 2005; Chye, Chin, & Peng, 2004). Hơn nữa, theo Thomas và ctg (2002), các ngân hàng cần một phương pháp xếp hạng tín dụng mà thỏa mãn những đòi hỏi sau: (1) chi phí rẻ và dễ vận hành, (2) nhanh chóng và ổn định, (3) đưa ra những quyết định nhất quán dựa trên các thông tin khách quan không phụ thuộc vào cảm xúc và tình cảm chủ quan của con người, và (4) hiệu quả của hệ phương pháp xếp hạng tín dụng có thể dễ dàng kiểm tra, điều chỉnh ở bất kì thời điểm nào nhằm điều chỉnh kịp thời với những thay đổi về chính sách hoặc điều kiện của nền kinh tế.

Đối với vấn đề phân loại tín dụng, cách tiếp cận truyền thống là dựa vào các phương pháp thống kê thuần túy như hồi quy tuyến tính đa biến (Meyer & Pifer, 1970), phân tích khác biệt (Altman, 1968; Banasik, Crook, & Thomas, 2003), và hồi quy Logistic (Desai, Crook, & Overstreet, 1996; Dimitras, Zanakis, & Zopounidis, 1996; Elliott & Filinkov, 2008; Lee, Chiu, Lu, & Chen, 2002). Tuy nhiên những yêu cầu của hội đồng Basel về giám sát hoạt động ngân hàng (the Basel Committee on Banking Supervision) ban hành năm 2004 đòi hỏi các ngân hàng cũng như các tổ chức tài chính phải sử dụng những mô hình phân loại tín dụng tin cậy hơn nhằm nâng cao hiệu quả của việc phân bổ vốn. Nhằm đáp ứng những đòi hỏi trên, trong những năm gần đây đã xuất hiện một số mô hình phân loại tín dụng mới theo cách tiếp cận của học máy (Machine Learning) và trí thông minh nhân tạo (Artificial Intelligence). Không giống như các tiếp cận trước đây, các phương pháp mới này không đưa ra bất kì giả thiết chặt chẽ nào như đòi hỏi của các cách tiếp cận theo phương pháp thống kê. Thay vào đó, các tiếp cận mới này cố gắng khai thác và đưa ra các kiến thức, các thông tin đầu ra chỉ dựa vào các thông tin đầu vào là các quan sát, các thông tin trong quá khứ. Với bài toán phân loại tín dụng, một số mô hình thuộc học máy như mạng trí tuệ nhân tạo ANN (Artificial Neural Network), Máy Hỗ Trợ Véctơ SVM (Support Vector Machines), K láng giềng gần nhất KNN (K-Nearest Neighbors), rừng ngẫu nhiên RF (Random Forest), cây quyết định DT (Decision Tree) chẳng hạn đã chứng tỏ nhiều ưu thế về mức độ chính xác cũng như tin cậy so với một số mô hình phân loại truyền thống (Chi & Hsu, 2012; Huang et.all, 2004; Huang, Chen, & Wang, 2007; Ince & Aktan, 2009; Martens và et al., 2010).

Kết quả nghiên cứu của một số tác giả với bộ dữ liệu German Credit

German Credit là bộ số liệu được sử dụng bởi nhiều nghiên cứu trên thế giới được cung cấp bởi giáo sư Hans Hofmann. Bộ dữ liệu này có thể download (cũng như tham khảo các thông tin khác) ở đây.

Nghiên cứu có tên Deep Learning for Credit Scoring in the Era of Big Data của nhóm tác giả đến từ Học Viện Ngân Hàng sử dụng bộ dữ liệu German Credit và mô hình có mức độ chính xác khi phân loại cao nhất là 76% thuộc về Convolutional Neural Network.Nhóm tác giả sử dụng gói Keras (gói này cũng sử dụng được với R và Python) và con số 76% đó là con số trung bình của 10 lần chạy mẫu bằng kiểm tra chéo (10-fold cross validation).

Một nghiên cứu khác cũng của nhóm tác giả của Học Viện Ngân Hàng cho một kết quả Accuracy cao hơn đáng kể là 81.42% (dù chưa rõ con số này là thử nghiệm trên một mẫu hay k-fold cross validatio) và AUC lớn nhất là 77.37%.

Nghiên cứu của nhóm tác giả đến từ University of Tasmania có tên Investigation and improvement of multi-layer perceptron neural networks for credit scoring cũng sử dụng cùng cách tiếp cận (Neural Networks) và cùng bộ số liệu German Credit thì mức độ chính xác đạt được là 87%.

Ngoài ra mức độ chính xác khi phân loại của một số cách tiếp cận khác cho bộ số liệu German Credit của một số tác giả khác là như sau:

Như vậy kết quả 76% của nhóm nghiên cứu đến từ Học Viện Ngân Hàng là một con số khiêm tốn và cách tiếp cận cho nghiên cứu cũng chưa có điểm gì mới. Ngoài ra nghiên cứu này chưa giải quyết hai vấn đề quan trọng sau khi lựa chọn và sử dụng mô hình phân loại cho bối cảnh đặc thù là các ngân hàng thương mại cũng như các tổ chức tài chính:

  1. Nhóm tác giả sử dụng đồng thời 6 cách tiếp cận (mô hình) cho phân loại nhưng có tới 4 trong số chúng có mức độ chính xác thấp hơn 70%. Vì thực tế chúng ta chẳng cần mô hình gì, bằng cách đoán ngẫu nhiên chúng ta cũng đạt mức chính xác 70% - chính là nhãn có tần suất xuất hiện cao nhất của cột biến cần phân loại (tỉ lệ Good/Bad là 70/30). Vì rằng không cần mô hình gì, bằng cách dán nhãn cho tất cả các cases là “Good” thì chúng ta đã có một “mô hình không gì cả” có độ chính xác 70%. Một mô hình gọi là có thể sử dụng được cho mục đích phân loại thì Accuracy của nó ít nhất cũng nên cao hơn tỉ lệ của nhãn chiếm ưu thế.

  2. Một tổ chức hoạt động vì lợi nhuận như ngân hàng thì mức độ chính xác không phải là căn cứ để chọn mô hình. Mà chúng căn cứ, trước hết, vào hệ quả kinh tế (lãi hay lỗ, và bao nhiêu) của việc sử dụng mô hình. Điều này đã được đề cập trong bài viết sau.

Deep Learning Model

Sử dụng 10-fold cross validatio như là một kĩ thuật kiểm định và đánh giá moo hình, chúng ta có thể xây dựng một mô hình có mức độ chính xác là 80.61% như sau:

# Load dữ liệu: 

rm(list = ls())
library(tidyverse)
library(magrittr)
library(caret)
data("GermanCredit")

sub_data <- GermanCredit %>% 
  mutate_if(is.numeric, function(x) {(x - min(x)) / (max(x) - min(x))})

# Load package H2o: 
library(h2o)
h2o.init(nthreads = 6, max_mem_size = "8g")
## 
## H2O is not running yet, starting it now...
## 
## Note:  In case of errors look at the following log files:
##     C:\Users\Zbook\AppData\Local\Temp\RtmpMjNC4Q/h2o_Zbook_started_from_r.out
##     C:\Users\Zbook\AppData\Local\Temp\RtmpMjNC4Q/h2o_Zbook_started_from_r.err
## 
## 
## Starting H2O JVM and connecting: . Connection successful!
## 
## R is connected to the H2O cluster: 
##     H2O cluster uptime:         2 seconds 728 milliseconds 
##     H2O cluster timezone:       Asia/Bangkok 
##     H2O data parsing timezone:  UTC 
##     H2O cluster version:        3.22.1.1 
##     H2O cluster version age:    3 months and 20 days !!! 
##     H2O cluster name:           H2O_started_from_R_Zbook_lik256 
##     H2O cluster total nodes:    1 
##     H2O cluster total memory:   7.11 GB 
##     H2O cluster total cores:    8 
##     H2O cluster allowed cores:  6 
##     H2O cluster healthy:        TRUE 
##     H2O Connection ip:          localhost 
##     H2O Connection port:        54321 
##     H2O Connection proxy:       NA 
##     H2O Internal Security:      FALSE 
##     H2O API Extensions:         Algos, AutoML, Core V3, Core V4 
##     R Version:                  R version 3.5.2 (2018-12-20)
h2o.no_progress()

# Chuẩn  bị dữ liệu: 
y <- "Class" 
x <- setdiff(colnames(sub_data), y)

# Phân chưa dữ liệu (chú ý em này là list) theo tỉ lệ 70 - 30:  
sub_data %<>% as.h2o()
id <- h2o.splitFrame(sub_data, 
                     ratios = 0.7, 
                     seed = 29)


# Tách ra dữ liệu  huấn luyện và kiếm định: 
train <- id[[1]]
test <- id[[2]]

dep_ln <- h2o.deeplearning (x,
                            y, 
                            model_id = "Deep_learning", 
                            training_frame = train, 
                            nfolds = 10, 
                            hidden = c(400, 400, 400, 400), 
                            balance_classes = TRUE,
                            stopping_metric = "AUC", 
                            replicate_training_data = TRUE, 
                            stopping_tolerance = 0.001, 
                            stopping_rounds = 5, 
                            overwrite_with_best_model = TRUE, 
                            fold_assignment = "Stratified", 
                            epochs = 500, 
                            activation = "TanhWithDropout", 
                            keep_cross_validation_fold_assignment = TRUE,
                            keep_cross_validation_predictions = FALSE, 
                            score_each_iteration = TRUE, 
                            variable_importances = TRUE, 
                            reproducible = TRUE,
                            seed = 123)


# Viết hàm lấy ra các kết quả chủ yếu cho lớp mô hình phân loại: 

results_df <- function(h2o_model) {
  h2o_model@model$cross_validation_metrics_summary %>% 
    as.data.frame() %>% 
    select(-mean, -sd) %>% 
    t() %>% 
    as.data.frame() %>% 
    mutate_all(as.character) %>% 
    mutate_all(as.numeric) -> k
  
  k %>% 
    select(Accuracy = accuracy, 
           AUC = auc, 
           Precision = precision, 
           Specificity = specificity, 
           Recall = recall, 
           Logloss = logloss) %>% 
    return()
}

# Sử dụng hàm: 
results_df(dep_ln) -> ket_qua


# Các thống kê về những tiêu chí này: 
ket_qua %>% 
  gather(Metrics, Values) %>% 
  group_by(Metrics) %>% 
  summarise_each(funs(mean, median, min, max, sd, n()), Values) %>% 
  mutate_if(is.numeric, function(x) {round(100*x, 2)}) %>% 
  mutate(n = n / 100) %>% 
  knitr::kable(col.names = c("Criterion", "Mean", "Median", "Min", "Max", "SD", "N"), 
               caption = "Table 1: Model Performance by some Criteria")
Table 1: Model Performance by some Criteria
Criterion Mean Median Min Max SD N
Accuracy 80.61 81.99 72.97 85.45 4.35 10
AUC 82.37 82.26 76.09 87.67 3.49 10
Logloss 54.83 54.32 42.64 74.04 10.57 10
Precision 82.01 84.04 69.49 88.89 6.89 10
Recall 93.18 94.01 84.48 100.00 4.55 10
Specificity 52.09 51.08 35.71 73.08 11.23 10

Các kết quả trên có thể được hình ảnh hóa để đánh giá chi tiết hơn nữa (như phân phối):

theme_set(theme_minimal())

ket_qua %>% 
  gather(Metrics, Values) %>% 
  ggplot(aes(Metrics, Values, fill = Metrics, color = Metrics)) +
  geom_boxplot(alpha = 0.3, show.legend = FALSE) + 
  facet_wrap(~ Metrics, scales = "free") + 
  scale_y_continuous(labels = scales::percent) + 
  labs(title = "Figure 1: Model Performance by Some Criteria", 
       subtitle = "Data Used: German Credit provided by Center for Machine Learning and Intelligent Systems", 
       x = NULL, y = NULL)

Lựa chọn và sử dụng mô hình dựa trên tiêu chuẩn lợi nhuận

Sử dụng lợi nhuận để lựa chọn mô hình với các giả thiết sau:

  1. Lãi là 30% trên số tiền cho vay.

  2. Phân phối của số tiền cho vay (đối với các hồ sơ được duyệt vay) dựa trên dữ liệu lịch sử cho vay của ngân hàng.

  3. Khi cho các hồ sơ vốn là hồ sơ xấu (Bad) nhưng mô hình phân loại sai thành tốt (Good) thì ngân hàng sẽ mất vốn hoàn toàn.

Ngoài ra, để nghiên cứu chi tiết hơn nữa về hậu quả kinh tế cũng như có thể đưa ra bằng chứng thông kê thuyết phục hơn thì kết quả sẽ được kiểm tra cho 1000 lần chọn mẫu.

Trước hết huấn luyện và so sánh sơ bộ một loạt các mô hình Machine Learning. Để thuận lợi cho việc so sánh chúng ta nên sử dụng gói caretEnsemble cho việc so sánh và đánh giá:

# Chuẩn hóa dữ liệu và loại một số cột biến không cần thiết: 

df_for_ml <- GermanCredit %>% 
  mutate_if(is.numeric, function(x) {(x - min(x)) / (max(x) - min(x))}) %>% 
  select(-Personal.Female.Single, -Purpose.Vacation)

# Split data: 

set.seed(1)
id <- createDataPartition(y = df_for_ml$Class, p = 0.7, list = FALSE)
df_train_ml <- df_for_ml[id, ]
df_test_ml <- df_for_ml[-id, ]

# Set conditions for training model and cross-validation: 

set.seed(1)
number <- 5
repeats <- 5
control <- trainControl(method = "repeatedcv", 
                        number = number , 
                        repeats = repeats, 
                        classProbs = TRUE, 
                        savePredictions = "final", 
                        index = createResample(df_train_ml$Class, repeats*number), 
                        summaryFunction = multiClassSummary, 
                        allowParallel = TRUE)

# Use Parallel computing: 
library(doParallel)
registerDoParallel(cores = detectCores() - 1)

# 7 models selected: 

my_models <- c("glm", "rf", "adaboost", "svmRadial", "knn", "xgbTree", "C5.0")

# Train these ML Models: 
library(caretEnsemble)
set.seed(1)
system.time(model_list1 <- caretList(Class ~., 
                                     data = df_train_ml,
                                     trControl = control,
                                     metric = "Accuracy", 
                                     methodList = my_models))
##    user  system elapsed 
##   32.80    1.39  538.74
# Extract results for comparing: 

list_of_results <- lapply(my_models, function(x) {model_list1[[x]]$resample})

# Convert to data frame: 
total_df <- do.call("bind_rows", list_of_results)
total_df %<>% mutate(Model = lapply(my_models, 
                                    function(x) {rep(x, number*repeats)}) %>% unlist())

# Average Accuracy based on 25 samples for these models: 

total_df %>% 
  group_by(Model) %>% 
  summarise_each(funs(mean), Accuracy, AUC, Sensitivity, Specificity, F1, Kappa) %>% 
  ungroup() %>% 
  arrange(-Accuracy) %>% 
  mutate_if(is.numeric, function(x) {round(100*x, 2)}) %>% 
  knitr::kable(caption = "Table 2: A Coparision of Model Performance by some Criteria")
Table 2: A Coparision of Model Performance by some Criteria
Model Accuracy AUC Sensitivity Specificity F1 Kappa
xgbTree 75.67 79.99 52.32 85.66 55.88 39.33
svmRadial 75.28 79.87 53.06 84.74 55.93 38.90
rf 75.15 78.74 45.91 87.71 52.09 35.98
adaboost 74.92 53.77 30.19 94.01 41.37 28.60
glm 74.23 78.14 51.33 84.00 54.07 36.34
C5.0 73.79 78.05 54.86 81.90 55.26 36.85
knn 71.18 70.19 37.10 85.81 42.94 24.77

Table 2 mang lại ấn tượng rằng XgbTree là mô hình nên được lựa chọn vì đây là cách tiếp cận có Accuracy cao nhất. Tuy nhiên, với các tổ chức vì lợi nhuận thì mô hình có mức độ chính xác cao nhất chưa chắc đã là mô hình được lựa chọn để phân loại hồ sơ. Cụ thể hơn, XgbTre không phải là mô hình mang lại nhiều lợi nhuận nhất mà là C5.0 như chúng ta có thể thấy ngay sau đây dù mô hình này không phải có Accuracy cao nhất:

# Viết hàm đánh giá kết quả với đầu vào là: (1) tỉ lệ mẫu được chọn, 
# (2) số lần lặp, và (3)  model được chọn + bộ dữ liệu mà từ đó
# chúng ta chọn mẫu: 


get_result <- function(ti_le, N, model_selected, df_test_selected) {
  my_vec <- c()
  
  for (i in 1:N) {
    set.seed(i) 
    df_test <- df_test_selected %>% 
      group_by(Class) %>% 
      sample_frac(ti_le) %>% 
      ungroup()
    
    pred <- predict(model_selected, df_test %>% select(-Class))
    cm <- confusionMatrix(df_test$Class, pred)
    cm$table %>% 
      as.vector() -> u
    my_vec <- c(my_vec, u)
  }
  
  my_vec %>% 
    matrix(ncol = 4, byrow = TRUE) %>% 
    as.data.frame() %>% 
    rename(BB = V1, 
           GB = V2,
           BG = V3,
           GG = V4) %>% 
    return()
}


# Kết quả phân loại của các mô hình này: 

loan_xgbtree <- get_result(0.5, 100, model_list1$xgbTree, df_test_ml)
loan_svm <- get_result(0.5, 100, model_list1$svmRadial, df_test_ml)
loan_rf <- get_result(0.5, 100, model_list1$rf, df_test_ml)
loan_adaboost <- get_result(0.5, 100, model_list1$adaboost, df_test_ml)
loan_logit <- get_result(0.5, 100, model_list1$glm, df_test_ml)
loan_c50 <- get_result(0.5, 100, model_list1$C5.0, df_test_ml)
loan_knn <- get_result(0.5, 100, model_list1$knn, df_test_ml)


# Tổng hợp các Data Frame này và tạo ra cột Accuracy: 

total_df_result <- bind_rows(loan_xgbtree %>% mutate(Model = "XgbTree"), 
                             loan_svm %>% mutate(Model = "SVM"), 
                             loan_rf %>% mutate(Model = "RF"), 
                             loan_knn %>% mutate(Model = "KNN"), 
                             loan_logit %>% mutate(Model = "Logistic"), 
                             loan_c50 %>% mutate(Model = "C5.0"), 
                             loan_adaboost %>% mutate(Model = "AdaBoost")) %>% 
  mutate(Accuracy = (BB + GG) / (BB + GG + GB + BG))

# Accuracy trung bình trên 100 mẫu được thử nghiệm: 

total_df_result %>% 
  group_by(Model) %>% 
  summarise_each(funs(mean), Accuracy, GG, BB, BG) %>% 
  ungroup() %>% 
  arrange(-Accuracy) %>% 
  mutate_at(.vars = c("Accuracy"), function(x) {round(100*x, 2)}) %>% 
  knitr::kable(caption = "Table 3: A Coparision of Model Performance by some Criteria, 100 samples")
Table 3: A Coparision of Model Performance by some Criteria, 100 samples
Model Accuracy GG BB BG
RF 76.96 97.06 18.38 26.62
AdaBoost 76.61 99.45 15.47 29.53
KNN 74.55 95.05 16.78 28.22
C5.0 73.07 86.96 22.64 22.36
Logistic 72.15 87.70 20.52 24.48
XgbTree 72.03 88.98 19.07 25.93
SVM 71.83 87.26 20.48 24.52
# Viết hàm mô phỏng lợi nhuận với các giả thiết đã nêu ở trên: 

profit_simu <- function(df_result, rate, N) {

  khoan_vay <- GermanCredit$Amount
  so_khoan_vay_tot <- sum(df_result$GG)
  so_khoan_vay_xau <- sum(df_result$BG)
  
  my_prof <- c()
  
  for (i in 1:N) {
    set.seed(i)
    prof <- rate*sample(khoan_vay, size = so_khoan_vay_tot, replace = TRUE) %>% sum() - 
      sum(sample(khoan_vay, size = so_khoan_vay_xau, replace = TRUE))
    my_prof <- c(prof, my_prof)
  }
  return(my_prof)
}

# Lợi nhuận của tổ chức tài chính tương ứng với việc
# sử dụng các mô hình dựa trên mô phỏng 1000 lần: 


profit <- c(loan_xgbtree %>% profit_simu(0.3, 1000), 
            loan_svm %>% profit_simu(0.3, 1000), 
            loan_rf %>% profit_simu(0.3, 1000), 
            loan_knn %>% profit_simu(0.3, 1000), 
            loan_logit %>% profit_simu(0.3, 1000), 
            loan_c50 %>% profit_simu(0.3, 1000), 
            loan_adaboost %>% profit_simu(0.3, 1000))


profit_df <- data.frame(Profit = profit, 
                        Model = c(rep("XgbTree", 1000), 
                                  rep("SVM", 1000), 
                                  rep("RF", 1000), 
                                  rep("KNN", 1000), 
                                  rep("Logistic", 1000),
                                  rep("C5.0", 1000), 
                                  rep("AdaBoost", 1000)))


# Các thống kê chi tiết về lợi nhuận này tương ứng với các mô hình: 

profit_df %>% 
  group_by(Model) %>% 
  summarise_each(funs(mean, median, min, max, sd), Profit) %>% 
  ungroup() %>% 
  arrange(-mean) %>% 
  mutate_if(is.numeric, function(x) {round(x, 0)}) %>% 
  knitr::kable(caption = "Table 4: Simulated Profit Based Monte Carlo Method\nwith Interest Rate is 30% for Test Data Sets", 
               col.names = c("Model", "Mean", "Median", "Min", "Max", "SD"))
Table 4: Simulated Profit Based Monte Carlo Method with Interest Rate is 30% for Test Data Sets
Model Mean Median Min Max SD
C5.0 1220940 1224864 777290 1682442 152549
RF 821424 824390 302273 1303387 165424
Logistic 600134 601024 94491 1146187 157444
SVM 544602 547606 74599 1065523 158888
XgbTree 251178 254245 -252344 706301 160084
AdaBoost 105167 108447 -471154 717252 168513
KNN 101694 103692 -433281 583161 166045

Chúng ta có thể hình ảnh hóa phân phối của lợi nhuận tương ứng với các mô hình được lựa chọn:

profit_df %>% 
  mutate(Profit = Profit / 1000000) %>% 
  ggplot(aes(Profit)) + 
  geom_density(fill = "red", color = "red", alpha = 0.3) + 
  geom_histogram(aes(y = ..density..), color = "blue", fill = "blue", alpha = 0.3) + 
  facet_wrap(~ Model, scales = "free") + 
  labs(x = NULL, y = NULL, 
       title = "Figure 2: Simulated Profit Based on Monte Carlo Method\nwith Interest Rate of 30% and 1000 Samples (unit: millions)", 
       caption = "Data Used: German Credit provided by Center for Machine Learning and Intelligent Systems")

Với dữ liệu ở Figure 2 chúng ta cũng có thể tính toán xác suất mà ngân hàng có lợi nhuận âm với một ngưỡng xác suất chọn trước nào đó bằng VaR (Value at Risk).

Vài kết luận

Trong thực tế thì những giả thiết sau đây cần phải được nới lỏng:

  1. Không phải mọi hồ sơ tốt khi được vay đều có mức độ hoàn vốn và lãi cho ngân hàng với xác suất 100%. ĐIều đó cũng đúng với các hồ sơ xấu (nhưng theo hướng ngược lại). Xác suất của các sự kiện này có thể được ước lượng từ dữ liệu lịch sử của ngân hàng hoặc sử dụng các phương pháp mô phỏng.

  2. Không chỉ Profit mà còn nhiều khía cạnh khác mà tổ chức sử dụng mô hình phân loại quan tâm. Chẳng hạn mức độ ổn định khi phân loại của mô hình.

  3. Khả năng phân loại chính xác các lớp hồ sơ (nhãn Bad phân loại đúng thành Bad - BB, nhãn Good phân loại đúng thành Good - GG) phụ thuộc vào ngưỡng được lựa chọn (cutoff) cho Probability of Default khi phân loại và do đó ngưỡng này có ảnh hưởng đến Profit. Hiện tại khảo sát ảnh hưởng của ngưỡng lên Profit chưa được khảo sát và đánh giá trong bài viết này.

References

  1. Martens, D., B. Baesens, T. Van Gestel, and J. Vanthienen. 2007. “Comprehensible Credit Scoring Models Using Rule Extraction from Support Vector Machines.” European Journal of Operational Research 183:1466–1476.

  2. Baesens, B., Roesch, D., & Scheule, H. (2016). Credit risk analytics: Measurement techniques, applications, and examples in SAS. John Wiley & Sons.

  3. Siddiqi, N. (2012). Credit risk scorecards: developing and implementing intelligent credit scoring. John Wiley & Sons.

  4. Anderson R (2007): The Credit Scoring Toolkit: Theory and Practice for Retail Credit Risk Management and Decision Automation. Oxford, Oxford University Press.

  5. Hand DJ, Henley WE (1997): Statistical Classification Methods in Consumer Credit Scoring: a review. Journal. of the Royal Statistical Society, Series A, 160(3):523–541.

  6. Thomas LC (2000): A survey of credit and behavioural scoring: forecasting financial risk of lending to consumers. International Journal of Forecasting, 16(2):149–172 .

  7. Thomas LC (2009): Consumer Credit Models: Pricing, Profit, and Portfolio. Oxford, Oxford University Press.

  8. Crook JN, Edelman DB, Thomas LC (2007): Recent developments in consumer credit risk assessment. European Journal of Operational Research, 183(3):1447–1465.

  9. Van Gestel, T., B. Baesens, P. Van Dijcke, J. Suykens, J. Garcia, and T. Alderweireld. 2005. “Linear and Nonlinear Credit Scoring by Combining Logistic Regression and Support Vector Machines.” Journal of Credit Risk 1, no. 4.

  10. Ben-David, A., & Frank, E. (2009). Accuracy of machine learning models versus “hand crafted” expert systems–a credit scoring case study. Expert Systems with Applications, 36(3), 5264-5271.

  11. Molinaro A (2005). “Prediction Error Estimation: A Comparison of Resampling Methods.” Bioinformatics, 21(15), 3301–3307.

  12. Kim JH (2009). “Estimating Classification Error Rate: Repeated Cross– Validation, Repeated Hold–Out and Bootstrap.” Computational Statistics & Data Analysis, 53(11), 3735–3745.

LS0tDQp0aXRsZTogIkRlZXAgTGVhcm5pbmcgZm9yIENyZWRpdCBTY29yaW5nIGluIHRoZSBFcmEgb2YgQmlnIERhdGEgKEFkYXB0ZWQgZnJvbSBhIHJlc2VhcmNoIGNvbmR1Y3RlZCBieSBNSVMsIEJhbmtpbmcgQWNhZGVteSBvZiBWaWV0bmFtKSIgDQpzdWJ0aXRsZTogIlIgZm9yIFBsZWFzdXJlIg0KYXV0aG9yOiAiTmd1eWVuIENoaSBEdW5nIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50OiANCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgaGlnaGxpZ2h0OiBweWdtZW50cw0KICAgICMgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0aGVtZTogImZsYXRseSINCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCi0tLQ0KDQpgYGB7ciBzZXR1cCxpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSkNCmBgYA0KDQoNCiMgTmh1IGPhuqd1IHbhu4EgY8OhYyBtw7QgaMOsbmggcGjDom4gbG/huqFpIOG7nyBjw6FjIHThu5UgY2jhu6ljIHTDoGkgY2jDrW5oIC0gdMOtbiBk4bulbmcNCg0KDQpY4bq/cCBo4bqhbmcgdMOtbiBk4bulbmcgxJHDs25nIG3hu5l0IHZhaSB0csOyIHF1YW4gdHLhu41uZyDEkeG7kWkgduG7m2kgbOG7o2kgbmh14bqtbiB2w6AgcGjDoXQgdHJp4buDbiBi4buBbiB24buvbmcgY+G7p2EgbmfDom4gaMOgbmcgbsOzaSByacOqbmcgY8WpbmcgbmjGsCBjw6FjIHThu5UgY2jhu6ljIHTDoGkgY2jDrW5oIGtow6FjLiBIaeG7h24gbmF5IGPDoWNoIHRp4bq/cCBj4bqtbiB0aGVvIHBoxrDGoW5nIHBow6FwIGjhu41jIG3DoXkgKE1hY2hpbmUgTGVhcm5pbmcpIMSRw6MgY2jhu6luZyB04buPIG5oaeG7gXUgxrB1IHRo4bq/IHbhu4EgbeG7qWMgxJHhu5kgY2jDrW5oIHjDoWMgY8WpbmcgbmjGsCB0aW4gY+G6rXkgc28gduG7m2kgbeG7mXQgc+G7kSBtw7QgaMOsbmggcGjDom4gbG/huqFpIHRydXnhu4FuIHRo4buRbmcuDQoNClPhu7Ega2nhu4duIGN14buZYyBraOG7p25nIGhv4bqjbmcgdMOgaSBjaMOtbmggZOG6q24gxJHhur9uIHPhu7Egc+G7pXAgxJHhu5UgY+G7p2EgbeG7mXQgbG/huqF0IGPDoWMgxJHhu4tuaCBjaOG6vyB0w6BpIGNow61uaCBuw7NpIGNodW5nIHbDoCBuZ8OibiBow6BuZyBuw7NpIHJpw6puZyDEkcOjIHRo4bupYyB04buJbmggY8OhYyB04buVIGNo4bupYyBuw6B5IGNow7ogdHLhu41uZyBoxqFuIMSR4bq/biB2YWkgdHLDsiBj4bunYSB0aOG6qW0gxJHhu4tuaCB0w61uIGThu6VuZyB0cm9uZyBob+G6oXQgxJHhu5luZyBj4bunYSBtw6xuaC4gSOG6p3UgaOG6v3QgbOG7o2kgbmh14bqtbiBj4bunYSBjw6FjIG5nw6JuIGjDoG5nIMSR4bq/biB04burIGhv4bqhdCDEkeG7mW5nIGPhuqVwIHTDrW4gZOG7pW5nIHbDoCBjaG8gdmF5LiBD4bqlcCB0w61uIGThu6VuZyBsw6AgbeG7mXQgdHJvbmcgbmjhu69uZyBob+G6oXQgxJHhu5luZyB04bqhbyByYSBt4buZdCB04buJIHRy4buNbmcgbOG7m24gduG7gSBkb2FuaCB0aHUgdsOgIGzhu6NpIG5odeG6rW4gY2hvIG5nw6JuIGjDoG5nIG5oxrBuZyBjxaluZyB0aeG7gW0g4bqpbiBy4bqldCBuaGnhu4F1IHLhu6dpIHJvIChaYWtyemV3c2thLCAyMDA3KS4gUuG7p2kgcm8gY2jDrW5oIGPhu6dhIG5nw6JuIGjDoG5nIGzDoCBraOG6oyBuxINuZyBraMOhY2ggaMOgbmcga2jDtG5nIGPDsyBraOG6oyBuxINuZyBob8OgbiB0cuG6oyBs4bqhaSBraG/huqNuIHZheSBtw6AgbmfDom4gaMOgbmcgxJHDoyBj4bqlcC4gTeG6t3Qga2jDoWMsIHF1eeG6v3QgxJHhu4tuaCBjw7MgaGF5IGtow7RuZyBjdW5nIGPhuqVwIG3hu5l0IGtob+G6o24gdmF5IGNobyBraMOhY2ggaMOgbmcgdGjGsOG7nW5nIHBo4bulIHRodeG7mWMgbmhp4buBdSB2w6BvIHRyw6xuaCDEkeG7mSBjxaluZyBuaMawIGtpbmggbmdoaeG7h20gY+G7p2EgY8OhbiBuaMOibiB2acOqbiB0aOG6qW0gxJHhu4tuaCB0w61uIGThu6VuZyAoVGhvbWFzLCAyMDAwKS4gTmdvw6BpIHJhLCBjxINuIGPhu6kgxJHhu4MgY+G6pXAgdMOtbiBk4bulbmcgY2hvIG3hu5l0IGtow6FjaCBow6BuZyBjw7JuIGPEg24gY+G7qSB2w6BvIG3hu5l0IHPhu5EgdGnDqnUgY2jDrSB44bq/cCBo4bqhbmcgbcOgIG3hu5l0IHPhu5EgdHJvbmcgc+G7kSDEkcOzIGzDoCBy4bqldCBraMOzIMSRbyBsxrDhu51uZywgaG/hurdjIGtow7MgY8OzIHRo4buDIMSRbyBsxrDhu51uZyBjaMOtbmggeMOhYy4gQ2jhurNuZyBo4bqhbiB0acOqdSBjaHXhuqluIDVDIGtoaSBj4bqlcCB0w61uIGThu6VuZyBsw6AgZOG7sWEgdHLDqm4gbmjhu69uZyDEkcOhbmggZ2nDoSBj4bunYSBuZ8OibiBow6BuZyB24buBIHTGsCBjw6FjaCwgbsSDbmcgbOG7sWMsIHbhu5FuLCB0w6BpIHPhuqNuIHRo4bq/IGNo4bqlcCwgdsOgIMSRaeG7gXUga2nhu4duIGPhu6dhIG5nxrDhu51pIHhpbiB2YXkgKEFicmFoYW1zICYgWmhhbmcsIDIwMDgpLiBSw7UgcsOgbmcgbeG7mXQgc+G7kSB0acOqdSBjaMOtLCBjaOG6s25nIGjhuqFuIG5oxrAgdMawIGPDoWNoIHbDoCBuxINuZyBs4buxYyBj4bunYSBuZ8aw4budaSB2YXkgbMOgIG3hu5l0IG5ow6JuIHThu5Ega2jDsyDEkcOhbmggZ2nDoSB2w6AgZG8gduG6rXkgY8OzIHRo4buDIGThuqtuIMSR4bq/biBjw6FjIHNhaSBzw7N0IGtoaSBxdXnhur90IMSR4buLbmggY2hvIHZheS4gTmdvw6BpIHJhIHBoxrDGoW5nIHBow6FwIMSRw6FuaCBnacOhIHjhur9wIGjhuqFuZyB0w61uIGThu6VuZyBk4buxYSB0csOqbiB0acOqdSBjaHXhuqluIDVDIGzDoCBjw7MgY2hpIHBow60gY2FvIHbDoCBjw7MgdGjhu4MgeOG6qXkgcmEgc+G7sSBraMO0bmcgbmjhuqV0IHF1w6FuIHbhu4Egdmnhu4djIGNobyB2YXkgaGF5IGtow7RuZyBnaeG7r2Egbmjhu69uZyBuaMOibiB2acOqbiB0aOG6qW0gxJHhu4tuaCB0w61uIGThu6VuZyBraMOhYyBuaGF1IMSR4buRaSB24bubaSBjw7luZyBt4buZdCBo4buTIHPGoSB4aW4gdmF5LiBWw6wgbmjhu69uZyBo4bqhbiBjaOG6vyBuw6B5LCBjw6FjIG5nw6JuIGjDoG5nIGPFqW5nIG5oxrAgY8OhYyB04buVIGNo4bupYyB0w6BpIGNow61uaCBj4bqnbiBz4butIGThu6VuZyBjw6FjIHBoxrDGoW5nIHBow6FwIHRo4bqpbSDEkeG7i25oIHbDoCB44bq/cCBo4bqhbmcgdMOtbiBk4bulbmcgdGluIGPhuq15LCBraMOhY2ggcXVhbiB2w6AgY2hpIHBow60gdGjhuqVwIG5o4bqxbSBnacO6cCBuaOG7r25nIHThu5UgY2jhu6ljIG7DoHkgcXV54bq/dCDEkeG7i25oIGPDsyBoYXkga2jDtG5nIGPhuqVwIHTDrW4gZOG7pW5nIGNobyBjw6FjIGjhu5Mgc8ahIHhpbiB2YXkgKEFraGF2ZWluLCBGcmFtZSwgJiBXaGl0ZSwgMjAwNTsgQ2h5ZSwgQ2hpbiwgJiBQZW5nLCAyMDA0KS4gSMahbiBu4buvYSwgdGhlbyBUaG9tYXMgdsOgIGN0ZyAoMjAwMiksIGPDoWMgbmfDom4gaMOgbmcgY+G6p24gbeG7mXQgcGjGsMahbmcgcGjDoXAgeOG6v3AgaOG6oW5nIHTDrW4gZOG7pW5nIG3DoCB0aOG7j2EgbcOjbiBuaOG7r25nIMSRw7JpIGjhu49pIHNhdTogKDEpIGNoaSBwaMOtIHLhursgdsOgIGThu4UgduG6rW4gaMOgbmgsICgyKSBuaGFuaCBjaMOzbmcgdsOgIOG7lW4gxJHhu4tuaCwgKDMpIMSRxrBhIHJhIG5o4buvbmcgcXV54bq/dCDEkeG7i25oIG5o4bqldCBxdcOhbiBk4buxYSB0csOqbiBjw6FjIHRow7RuZyB0aW4ga2jDoWNoIHF1YW4ga2jDtG5nIHBo4bulIHRodeG7mWMgdsOgbyBj4bqjbSB4w7pjIHbDoCB0w6xuaCBj4bqjbSBjaOG7pyBxdWFuIGPhu6dhIGNvbiBuZ8aw4budaSwgdsOgICg0KSBoaeG7h3UgcXXhuqMgY+G7p2EgaOG7hyBwaMawxqFuZyBwaMOhcCB44bq/cCBo4bqhbmcgdMOtbiBk4bulbmcgY8OzIHRo4buDIGThu4UgZMOgbmcga2nhu4NtIHRyYSwgxJFp4buBdSBjaOG7iW5oIOG7nyBi4bqldCBrw6wgdGjhu51pIMSRaeG7g20gbsOgbyBuaOG6sW0gxJFp4buBdSBjaOG7iW5oIGvhu4twIHRo4budaSB24bubaSBuaOG7r25nIHRoYXkgxJHhu5VpIHbhu4EgY2jDrW5oIHPDoWNoIGhv4bq3YyDEkWnhu4F1IGtp4buHbiBj4bunYSBu4buBbiBraW5oIHThur8uDQoNCsSQ4buRaSB24bubaSB24bqlbiDEkeG7gSBwaMOibiBsb+G6oWkgdMOtbiBk4bulbmcsIGPDoWNoIHRp4bq/cCBj4bqtbiB0cnV54buBbiB0aOG7kW5nIGzDoCBk4buxYSB2w6BvIGPDoWMgcGjGsMahbmcgcGjDoXAgdGjhu5FuZyBrw6ogdGh14bqnbiB0w7p5IG5oxrAgaOG7k2kgcXV5IHR1eeG6v24gdMOtbmggxJFhIGJp4bq/biAoTWV5ZXIgJiBQaWZlciwgMTk3MCksIHBow6JuIHTDrWNoIGtow6FjIGJp4buHdCAoQWx0bWFuLCAxOTY4OyBCYW5hc2lrLCBDcm9vaywgJiBUaG9tYXMsIDIwMDMpLCB2w6AgaOG7k2kgcXV5IExvZ2lzdGljIChEZXNhaSwgQ3Jvb2ssICYgT3ZlcnN0cmVldCwgMTk5NjsgRGltaXRyYXMsIFphbmFraXMsICYgWm9wb3VuaWRpcywgMTk5NjsgRWxsaW90dCAmIEZpbGlua292LCAyMDA4OyBMZWUsIENoaXUsIEx1LCAmIENoZW4sIDIwMDIpLiBUdXkgbmhpw6puIG5o4buvbmcgecOqdSBj4bqndSBj4bunYSBo4buZaSDEkeG7k25nIEJhc2VsIHbhu4EgZ2nDoW0gc8OhdCBob+G6oXQgxJHhu5luZyBuZ8OibiBow6BuZyAodGhlIEJhc2VsIENvbW1pdHRlZSBvbiBCYW5raW5nIFN1cGVydmlzaW9uKSBiYW4gaMOgbmggbsSDbSAyMDA0IMSRw7JpIGjhu49pIGPDoWMgbmfDom4gaMOgbmcgY8WpbmcgbmjGsCBjw6FjIHThu5UgY2jhu6ljIHTDoGkgY2jDrW5oIHBo4bqjaSBz4butIGThu6VuZyBuaOG7r25nIG3DtCBow6xuaCBwaMOibiBsb+G6oWkgdMOtbiBk4bulbmcgdGluIGPhuq15IGjGoW4gbmjhurFtIG7Dom5nIGNhbyBoaeG7h3UgcXXhuqMgY+G7p2Egdmnhu4djIHBow6JuIGLhu5UgduG7kW4uIE5o4bqxbSDEkcOhcCDhu6luZyBuaOG7r25nIMSRw7JpIGjhu49pIHRyw6puLCB0cm9uZyBuaOG7r25nIG7Eg20gZ+G6p24gxJHDonkgxJHDoyB4deG6pXQgaGnhu4duIG3hu5l0IHPhu5EgbcO0IGjDrG5oIHBow6JuIGxv4bqhaSB0w61uIGThu6VuZyBt4bubaSB0aGVvIGPDoWNoIHRp4bq/cCBj4bqtbiBj4bunYSBo4buNYyBtw6F5IChNYWNoaW5lIExlYXJuaW5nKSB2w6AgdHLDrSB0aMO0bmcgbWluaCBuaMOibiB04bqhbyAoQXJ0aWZpY2lhbCBJbnRlbGxpZ2VuY2UpLiBLaMO0bmcgZ2nhu5FuZyBuaMawIGPDoWMgdGnhur9wIGPhuq1uIHRyxrDhu5tjIMSRw6J5LCBjw6FjIHBoxrDGoW5nIHBow6FwIG3hu5tpIG7DoHkga2jDtG5nIMSRxrBhIHJhIGLhuqV0IGvDrCBnaeG6oyB0aGnhur90IGNo4bq3dCBjaOG6vSBuw6BvIG5oxrAgxJHDsmkgaOG7j2kgY+G7p2EgY8OhYyBjw6FjaCB0aeG6v3AgY+G6rW4gdGhlbyBwaMawxqFuZyBwaMOhcCB0aOG7kW5nIGvDqi4gVGhheSB2w6BvIMSRw7MsIGPDoWMgdGnhur9wIGPhuq1uIG3hu5tpIG7DoHkgY+G7kSBn4bqvbmcga2hhaSB0aMOhYyB2w6AgxJHGsGEgcmEgY8OhYyBraeG6v24gdGjhu6ljLCBjw6FjIHRow7RuZyB0aW4gxJHhuqd1IHJhIGNo4buJIGThu7FhIHbDoG8gY8OhYyB0aMO0bmcgdGluIMSR4bqndSB2w6BvIGzDoCBjw6FjIHF1YW4gc8OhdCwgY8OhYyB0aMO0bmcgdGluIHRyb25nIHF1w6Ega2jhu6kuIFbhu5tpIGLDoGkgdG/DoW4gcGjDom4gbG/huqFpIHTDrW4gZOG7pW5nLCBt4buZdCBz4buRIG3DtCBow6xuaCB0aHXhu5ljIGjhu41jIG3DoXkgbmjGsCBt4bqhbmcgdHLDrSB0deG7hyBuaMOibiB04bqhbyBBTk4gKEFydGlmaWNpYWwgTmV1cmFsIE5ldHdvcmspLCBNw6F5IEjhu5cgVHLhu6MgVsOpY3TGoSBTVk0gKFN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzKSwgSyBsw6FuZyBnaeG7gW5nIGfhuqduIG5o4bqldCBLTk4gKEstTmVhcmVzdCBOZWlnaGJvcnMpLCBy4burbmcgbmfhuqt1IG5oacOqbiBSRiAoUmFuZG9tIEZvcmVzdCksIGPDonkgcXV54bq/dCDEkeG7i25oIERUIChEZWNpc2lvbiBUcmVlKSBjaOG6s25nIGjhuqFuIMSRw6MgY2jhu6luZyB04buPIG5oaeG7gXUgxrB1IHRo4bq/IHbhu4EgbeG7qWMgxJHhu5kgY2jDrW5oIHjDoWMgY8WpbmcgbmjGsCB0aW4gY+G6rXkgc28gduG7m2kgbeG7mXQgc+G7kSBtw7QgaMOsbmggcGjDom4gbG/huqFpIHRydXnhu4FuIHRo4buRbmcgKENoaSAmIEhzdSwgMjAxMjsgSHVhbmcgZXQuYWxsLCAyMDA0OyBIdWFuZywgQ2hlbiwgJiBXYW5nLCAyMDA3OyBJbmNlICYgQWt0YW4sIDIwMDk7IE1hcnRlbnMgdsOgIGV0IGFsLiwgMjAxMCkuDQoNCiMgS+G6v3QgcXXhuqMgbmdoacOqbiBj4bupdSBj4bunYSBt4buZdCBz4buRIHTDoWMgZ2nhuqMgduG7m2kgYuG7mSBk4buvIGxp4buHdSBHZXJtYW4gQ3JlZGl0DQoNCkdlcm1hbiBDcmVkaXQgbMOgIGLhu5kgc+G7kSBsaeG7h3UgxJHGsOG7o2Mgc+G7rSBk4bulbmcgYuG7n2kgbmhp4buBdSBuZ2hpw6puIGPhu6l1IHRyw6puIHRo4bq/IGdp4bubaSDEkcaw4bujYyBjdW5nIGPhuqVwIGLhu59pIGdpw6FvIHPGsCBIYW5zIEhvZm1hbm4uIELhu5kgZOG7ryBsaeG7h3UgbsOgeSBjw7MgdGjhu4MgZG93bmxvYWQgKGPFqW5nIG5oxrAgdGhhbSBraOG6o28gY8OhYyB0aMO0bmcgdGluIGtow6FjKSBb4bufIMSRw6J5XShodHRwOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9tbC9kYXRhc2V0cy9zdGF0bG9nKyhnZXJtYW4rY3JlZGl0K2RhdGEpKS4gDQoNCk5naGnDqm4gY+G7qXUgY8OzIHTDqm4gW0RlZXAgTGVhcm5pbmcgZm9yIENyZWRpdCBTY29yaW5nIGluIHRoZSBFcmEgb2YgQmlnIERhdGFdKGh0dHBzOi8vYnVzaW5lc3Nkb2Nib3guY29tL0J1c2luZXNzX1NvZnR3YXJlLzY4ODc4NzMzLVN0YXRlLWJhbmstb2YtdmlldG5hbS1iYW5raW5nLWFjYWRlbXktaW50ZXJuYXRpb25hbC1jb25mZXJlbmNlLWJkYmYtMjAxNy1oYW5vaS12aWV0bmFtLWp1bmUtMTUtdGgtMjAxNy1wcm9jZWVkaW5ncy5odG1sKSBj4bunYSBuaMOzbSB0w6FjIGdp4bqjIMSR4bq/biB04burIEjhu41jIFZp4buHbiBOZ8OibiBIw6BuZyBz4butIGThu6VuZyBi4buZIGThu68gbGnhu4d1IEdlcm1hbiBDcmVkaXQgdsOgIG3DtCBow6xuaCBjw7MgbeG7qWMgxJHhu5kgY2jDrW5oIHjDoWMga2hpIHBow6JuIGxv4bqhaSBjYW8gbmjhuqV0IGzDoCA3NiUgdGh14buZYyB24buBIENvbnZvbHV0aW9uYWwgTmV1cmFsIE5ldHdvcmsuTmjDs20gdMOhYyBnaeG6oyBz4butIGThu6VuZyBnw7NpIEtlcmFzIChnw7NpIG7DoHkgY8Wpbmcgc+G7rSBk4bulbmcgxJHGsOG7o2MgduG7m2kgUiB2w6AgUHl0aG9uKSB2w6AgY29uIHPhu5EgIDc2JSDEkcOzIGzDoCBjb24gc+G7kSB0cnVuZyBiw6xuaCBj4bunYSAxMCBs4bqnbiBjaOG6oXkgbeG6q3UgYuG6sW5nIGtp4buDbSB0cmEgY2jDqW8gKDEwLWZvbGQgY3Jvc3MgdmFsaWRhdGlvbikuDQoNCk3hu5l0IFtuZ2hpw6puIGPhu6l1IGtow6FjXShodHRwOi8vbWlzLmh2bmguZWR1LnZuL3VwbG9hZC8xNzAvMjAxNzAxMDMvQXAlMjBkdW5nJTIwQUklMjB0cm9uZyUyMGNoYW0lMjBkaWVtJTIwdGluJTIwZHVuZy1IdWUmVGh1eS10YXBjaGlUaW5ob2NOSC5wZGYpIGPFqW5nIGPhu6dhIG5ow7NtIHTDoWMgZ2nhuqMgY+G7p2EgSOG7jWMgVmnhu4duIE5nw6JuIEjDoG5nIGNobyBt4buZdCBr4bq/dCBxdeG6oyAgQWNjdXJhY3kgY2FvIGjGoW4gxJHDoW5nIGvhu4MgbMOgIDgxLjQyJSAoZMO5IGNoxrBhIHLDtSBjb24gc+G7kSBuw6B5IGzDoCB0aOG7rSBuZ2hp4buHbSB0csOqbiAqKm3hu5l0KiogbeG6q3UgaGF5IGstZm9sZCAgY3Jvc3MgdmFsaWRhdGlvKSB2w6AgQVVDIGzhu5tuIG5o4bqldCBsw6AgNzcuMzclLiANCg0KTmdoacOqbiBj4bupdSBj4bunYSBuaMOzbSB0w6FjIGdp4bqjIMSR4bq/biB04burIFVuaXZlcnNpdHkgb2YgVGFzbWFuaWEgY8OzIHTDqm4gW0ludmVzdGlnYXRpb24gYW5kIGltcHJvdmVtZW50IG9mIG11bHRpLWxheWVyIHBlcmNlcHRyb24gbmV1cmFsIG5ldHdvcmtzIGZvciBjcmVkaXQgc2NvcmluZ10oaHR0cHM6Ly93d3cuc2NpZW5jZWRpcmVjdC5jb20vc2NpZW5jZS9hcnRpY2xlL3BpaS9TMDk1NzQxNzQxNDAwNzcyNikgY8Wpbmcgc+G7rSBk4bulbmcgY8O5bmcgY8OhY2ggdGnhur9wIGPhuq1uIChOZXVyYWwgTmV0d29ya3MpIHbDoCBjw7luZyBi4buZIHPhu5EgbGnhu4d1IEdlcm1hbiBDcmVkaXQgdGjDrCBt4bupYyDEkeG7mSBjaMOtbmggeMOhYyDEkeG6oXQgxJHGsOG7o2MgbMOgIDg3JS4NCg0KTmdvw6BpIHJhIG3hu6ljIMSR4buZIGNow61uaCB4w6FjIGtoaSBwaMOibiBsb+G6oWkgY+G7p2EgbeG7mXQgc+G7kSBjw6FjaCB0aeG6v3AgY+G6rW4ga2jDoWMgY2hvIGLhu5kgc+G7kSBsaeG7h3UgR2VybWFuIENyZWRpdCBj4bunYSBt4buZdCBz4buRIHTDoWMgZ2nhuqMga2jDoWMgbMOgIG5oxrAgc2F1OiANCg0KIVtdKEM6XFxVc2Vyc1xcWmJvb2tcXERlc2t0b3BcXHBpY1xccGljMi5wbmcpDQoNCk5oxrAgduG6rXkga+G6v3QgcXXhuqMgNzYlIGPhu6dhIG5ow7NtIG5naGnDqm4gY+G7qXUgxJHhur9uIHThu6sgSOG7jWMgVmnhu4duIE5nw6JuIEjDoG5nIGzDoCBt4buZdCBjb24gc+G7kSBraGnDqm0gdOG7kW4gdsOgIGPDoWNoIHRp4bq/cCBj4bqtbiBjaG8gbmdoacOqbiBj4bupdSBjxaluZyBjaMawYSBjw7MgxJFp4buDbSBnw6wgbeG7m2kuIE5nb8OgaSByYSBuZ2hpw6puIGPhu6l1IG7DoHkgY2jGsGEgZ2nhuqNpIHF1eeG6v3QgaGFpIHbhuqVuIMSR4buBIHF1YW4gdHLhu41uZyBzYXUga2hpIGzhu7FhIGNo4buNbiB2w6Agc+G7rSBk4bulbmcgbcO0IGjDrG5oIHBow6JuIGxv4bqhaSBjaG8gYuG7kWkgY+G6o25oIMSR4bq3YyB0aMO5IGzDoCBjw6FjIG5nw6JuIGjDoG5nIHRoxrDGoW5nIG3huqFpIGPFqW5nIG5oxrAgY8OhYyB04buVIGNo4bupYyB0w6BpIGNow61uaDogDQoNCjEuIE5ow7NtIHTDoWMgZ2nhuqMgc+G7rSBk4bulbmcgxJHhu5NuZyB0aOG7nWkgNiBjw6FjaCB0aeG6v3AgY+G6rW4gKG3DtCBow6xuaCkgY2hvIHBow6JuIGxv4bqhaSBuaMawbmcgY8OzIHThu5tpIDQgdHJvbmcgc+G7kSBjaMO6bmcgY8OzIG3hu6ljIMSR4buZIGNow61uaCB4w6FjIHRo4bqlcCBoxqFuIDcwJS4gVsOsIHRo4buxYyB04bq/IGNow7puZyB0YSBjaOG6s25nIGPhuqduIG3DtCBow6xuaCBnw6wsIGLhurFuZyBjw6FjaCDEkW/DoW4gbmfhuqt1IG5oacOqbiBjaMO6bmcgdGEgY8WpbmcgxJHhuqF0IG3hu6ljIGNow61uaCB4w6FjIDcwJSAtIGNow61uaCBsw6AgbmjDo24gY8OzIHThuqduIHN14bqldCB4deG6pXQgaGnhu4duIGNhbyBuaOG6pXQgY+G7p2EgY+G7mXQgYmnhur9uIGPhuqduIHBow6JuIGxv4bqhaSAodOG7iSBs4buHIEdvb2QvQmFkIGzDoCA3MC8zMCkuIFbDrCBy4bqxbmcga2jDtG5nIGPhuqduIG3DtCBow6xuaCBnw6wsIGLhurFuZyBjw6FjaCBkw6FuIG5ow6NuIGNobyB04bqldCBj4bqjIGPDoWMgY2FzZXMgbMOgICJHb29kIiB0aMOsIGNow7puZyB0YSDEkcOjIGPDsyBt4buZdCAibcO0IGjDrG5oIGtow7RuZyBnw6wgY+G6oyIgY8OzIMSR4buZIGNow61uaCB4w6FjIDcwJS4gTeG7mXQgbcO0IGjDrG5oIGfhu41pIGzDoCBjw7MgdGjhu4Mgc+G7rSBk4bulbmcgxJHGsOG7o2MgY2hvIG3hu6VjIMSRw61jaCBwaMOibiBsb+G6oWkgdGjDrCBBY2N1cmFjeSBj4bunYSBuw7Mgw610IG5o4bqldCBjxaluZyBuw6puIGNhbyBoxqFuIHThu4kgbOG7hyBj4bunYSBuaMOjbiBjaGnhur9tIMawdSB0aOG6vy4gDQoNCjIuIE3hu5l0IHThu5UgY2jhu6ljIGhv4bqhdCDEkeG7mW5nIHbDrCBs4bujaSBuaHXhuq1uIG5oxrAgbmfDom4gaMOgbmcgdGjDrCBt4bupYyDEkeG7mSBjaMOtbmggeMOhYyBraMO0bmcgcGjhuqNpIGzDoCBjxINuIGPhu6kgxJHhu4MgY2jhu41uIG3DtCBow6xuaC4gTcOgIGNow7puZyBjxINuIGPhu6ksIHRyxrDhu5tjIGjhur90LCB2w6BvIGjhu4cgcXXhuqMga2luaCB04bq/IChsw6NpIGhheSBs4buXLCB2w6AgYmFvIG5oacOqdSkgY+G7p2Egdmnhu4djIHPhu60gZOG7pW5nIG3DtCBow6xuaC4gxJBp4buBdSBuw6B5IMSRw6MgxJHGsOG7o2MgxJHhu4EgY+G6rXAgdHJvbmcgW2LDoGkgdmnhur90IHNhdV0oaHR0cDovL3JwdWJzLmNvbS9jaGlkdW5na3QvMjk3ODI1KS4gDQoNCg0KIyBEZWVwIExlYXJuaW5nIE1vZGVsDQoNClPhu60gZOG7pW5nIDEwLWZvbGQgY3Jvc3MgdmFsaWRhdGlvIG5oxrAgbMOgIG3hu5l0IGvEqSB0aHXhuq10IGtp4buDbSDEkeG7i25oIHbDoCDEkcOhbmggZ2nDoSBtb28gaMOsbmgsIGNow7puZyB0YSBjw7MgdGjhu4MgeMOieSBk4buxbmcgbeG7mXQgbcO0IGjDrG5oIGPDsyBt4bupYyDEkeG7mSBjaMOtbmggeMOhYyBsw6AgODAuNjElIG5oxrAgc2F1OiANCg0KDQpgYGB7cn0NCiMgTG9hZCBk4buvIGxp4buHdTogDQoNCnJtKGxpc3QgPSBscygpKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KG1hZ3JpdHRyKQ0KbGlicmFyeShjYXJldCkNCmRhdGEoIkdlcm1hbkNyZWRpdCIpDQoNCnN1Yl9kYXRhIDwtIEdlcm1hbkNyZWRpdCAlPiUgDQogIG11dGF0ZV9pZihpcy5udW1lcmljLCBmdW5jdGlvbih4KSB7KHggLSBtaW4oeCkpIC8gKG1heCh4KSAtIG1pbih4KSl9KQ0KDQojIExvYWQgcGFja2FnZSBIMm86IA0KbGlicmFyeShoMm8pDQpoMm8uaW5pdChudGhyZWFkcyA9IDYsIG1heF9tZW1fc2l6ZSA9ICI4ZyIpDQpoMm8ubm9fcHJvZ3Jlc3MoKQ0KDQojIENodeG6qW4gIGLhu4sgZOG7ryBsaeG7h3U6IA0KeSA8LSAiQ2xhc3MiIA0KeCA8LSBzZXRkaWZmKGNvbG5hbWVzKHN1Yl9kYXRhKSwgeSkNCg0KIyBQaMOibiBjaMawYSBk4buvIGxp4buHdSAoY2jDuiDDvSBlbSBuw6B5IGzDoCBsaXN0KSB0aGVvIHThu4kgbOG7hyA3MCAtIDMwOiAgDQpzdWJfZGF0YSAlPD4lIGFzLmgybygpDQppZCA8LSBoMm8uc3BsaXRGcmFtZShzdWJfZGF0YSwgDQogICAgICAgICAgICAgICAgICAgICByYXRpb3MgPSAwLjcsIA0KICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDI5KQ0KDQoNCiMgVMOhY2ggcmEgZOG7ryBsaeG7h3UgIGh14bqlbiBsdXnhu4duIHbDoCBraeG6v20gxJHhu4tuaDogDQp0cmFpbiA8LSBpZFtbMV1dDQp0ZXN0IDwtIGlkW1syXV0NCg0KZGVwX2xuIDwtIGgyby5kZWVwbGVhcm5pbmcgKHgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgeSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgPSAiRGVlcF9sZWFybmluZyIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluaW5nX2ZyYW1lID0gdHJhaW4sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5mb2xkcyA9IDEwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBoaWRkZW4gPSBjKDQwMCwgNDAwLCA0MDAsIDQwMCksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJhbGFuY2VfY2xhc3NlcyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RvcHBpbmdfbWV0cmljID0gIkFVQyIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcGxpY2F0ZV90cmFpbmluZ19kYXRhID0gVFJVRSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RvcHBpbmdfdG9sZXJhbmNlID0gMC4wMDEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0b3BwaW5nX3JvdW5kcyA9IDUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIG92ZXJ3cml0ZV93aXRoX2Jlc3RfbW9kZWwgPSBUUlVFLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb2xkX2Fzc2lnbm1lbnQgPSAiU3RyYXRpZmllZCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVwb2NocyA9IDUwMCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgYWN0aXZhdGlvbiA9ICJUYW5oV2l0aERyb3BvdXQiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZWVwX2Nyb3NzX3ZhbGlkYXRpb25fZm9sZF9hc3NpZ25tZW50ID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZWVwX2Nyb3NzX3ZhbGlkYXRpb25fcHJlZGljdGlvbnMgPSBGQUxTRSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NvcmVfZWFjaF9pdGVyYXRpb24gPSBUUlVFLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YXJpYWJsZV9pbXBvcnRhbmNlcyA9IFRSVUUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcHJvZHVjaWJsZSA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDEyMykNCg0KDQojIFZp4bq/dCBow6BtIGzhuqV5IHJhIGPDoWMga+G6v3QgcXXhuqMgY2jhu6cgeeG6v3UgY2hvIGzhu5twIG3DtCBow6xuaCBwaMOibiBsb+G6oWk6IA0KDQpyZXN1bHRzX2RmIDwtIGZ1bmN0aW9uKGgyb19tb2RlbCkgew0KICBoMm9fbW9kZWxAbW9kZWwkY3Jvc3NfdmFsaWRhdGlvbl9tZXRyaWNzX3N1bW1hcnkgJT4lIA0KICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUgDQogICAgc2VsZWN0KC1tZWFuLCAtc2QpICU+JSANCiAgICB0KCkgJT4lIA0KICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUgDQogICAgbXV0YXRlX2FsbChhcy5jaGFyYWN0ZXIpICU+JSANCiAgICBtdXRhdGVfYWxsKGFzLm51bWVyaWMpIC0+IGsNCiAgDQogIGsgJT4lIA0KICAgIHNlbGVjdChBY2N1cmFjeSA9IGFjY3VyYWN5LCANCiAgICAgICAgICAgQVVDID0gYXVjLCANCiAgICAgICAgICAgUHJlY2lzaW9uID0gcHJlY2lzaW9uLCANCiAgICAgICAgICAgU3BlY2lmaWNpdHkgPSBzcGVjaWZpY2l0eSwgDQogICAgICAgICAgIFJlY2FsbCA9IHJlY2FsbCwgDQogICAgICAgICAgIExvZ2xvc3MgPSBsb2dsb3NzKSAlPiUgDQogICAgcmV0dXJuKCkNCn0NCg0KIyBT4butIGThu6VuZyBow6BtOiANCnJlc3VsdHNfZGYoZGVwX2xuKSAtPiBrZXRfcXVhDQoNCg0KIyBDw6FjIHRo4buRbmcga8OqIHbhu4Egbmjhu69uZyB0acOqdSBjaMOtIG7DoHk6IA0Ka2V0X3F1YSAlPiUgDQogIGdhdGhlcihNZXRyaWNzLCBWYWx1ZXMpICU+JSANCiAgZ3JvdXBfYnkoTWV0cmljcykgJT4lIA0KICBzdW1tYXJpc2VfZWFjaChmdW5zKG1lYW4sIG1lZGlhbiwgbWluLCBtYXgsIHNkLCBuKCkpLCBWYWx1ZXMpICU+JSANCiAgbXV0YXRlX2lmKGlzLm51bWVyaWMsIGZ1bmN0aW9uKHgpIHtyb3VuZCgxMDAqeCwgMil9KSAlPiUgDQogIG11dGF0ZShuID0gbiAvIDEwMCkgJT4lIA0KICBrbml0cjo6a2FibGUoY29sLm5hbWVzID0gYygiQ3JpdGVyaW9uIiwgIk1lYW4iLCAiTWVkaWFuIiwgIk1pbiIsICJNYXgiLCAiU0QiLCAiTiIpLCANCiAgICAgICAgICAgICAgIGNhcHRpb24gPSAiVGFibGUgMTogTW9kZWwgUGVyZm9ybWFuY2UgYnkgc29tZSBDcml0ZXJpYSIpDQoNCg0KYGBgDQoNCkPDoWMga+G6v3QgcXXhuqMgdHLDqm4gY8OzIHRo4buDIMSRxrDhu6NjIGjDrG5oIOG6o25oIGjDs2EgxJHhu4MgxJHDoW5oIGdpw6EgY2hpIHRp4bq/dCBoxqFuIG7hu69hIChuaMawIHBow6JuIHBo4buRaSk6IA0KDQpgYGB7cn0NCnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKCkpDQoNCmtldF9xdWEgJT4lIA0KICBnYXRoZXIoTWV0cmljcywgVmFsdWVzKSAlPiUgDQogIGdncGxvdChhZXMoTWV0cmljcywgVmFsdWVzLCBmaWxsID0gTWV0cmljcywgY29sb3IgPSBNZXRyaWNzKSkgKw0KICBnZW9tX2JveHBsb3QoYWxwaGEgPSAwLjMsIHNob3cubGVnZW5kID0gRkFMU0UpICsgDQogIGZhY2V0X3dyYXAofiBNZXRyaWNzLCBzY2FsZXMgPSAiZnJlZSIpICsgDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsgDQogIGxhYnModGl0bGUgPSAiRmlndXJlIDE6IE1vZGVsIFBlcmZvcm1hbmNlIGJ5IFNvbWUgQ3JpdGVyaWEiLCANCiAgICAgICBzdWJ0aXRsZSA9ICJEYXRhIFVzZWQ6IEdlcm1hbiBDcmVkaXQgcHJvdmlkZWQgYnkgQ2VudGVyIGZvciBNYWNoaW5lIExlYXJuaW5nIGFuZCBJbnRlbGxpZ2VudCBTeXN0ZW1zIiwgDQogICAgICAgeCA9IE5VTEwsIHkgPSBOVUxMKQ0KYGBgDQoNCg0KIyBM4buxYSBjaOG7jW4gdsOgIHPhu60gZOG7pW5nIG3DtCBow6xuaCBk4buxYSB0csOqbiB0acOqdSBjaHXhuqluIGzhu6NpIG5odeG6rW4NCg0KDQpT4butIGThu6VuZyBs4bujaSBuaHXhuq1uIMSR4buDIGzhu7FhIGNo4buNbiBtw7QgaMOsbmggduG7m2kgY8OhYyBnaeG6oyB0aGnhur90IHNhdToNCg0KMS4gTMOjaSBsw6AgMzAlIHRyw6puIHPhu5EgdGnhu4FuIGNobyB2YXkuIA0KDQoyLiBQaMOibiBwaOG7kWkgY+G7p2Egc+G7kSB0aeG7gW4gY2hvIHZheSAoxJHhu5FpIHbhu5tpIGPDoWMgaOG7kyBzxqEgxJHGsOG7o2MgIGR1eeG7h3QgdmF5KSBk4buxYSB0csOqbiBk4buvIGxp4buHdSBs4buLY2ggc+G7rSBjaG8gdmF5IGPhu6dhIG5nw6JuIGjDoG5nLg0KDQozLiBLaGkgY2hvIGPDoWMgaOG7kyBzxqEgduG7kW4gbMOgIGjhu5Mgc8ahIHjhuqV1IChCYWQpIG5oxrBuZyBtw7QgaMOsbmggcGjDom4gbG/huqFpIHNhaSB0aMOgbmggdOG7kXQgKEdvb2QpIHRow6wgbmfDom4gaMOgbmcgc+G6vSBt4bqldCB24buRbiBob8OgbiB0b8Ogbi4gDQoNCk5nb8OgaSByYSwgxJHhu4MgbmdoacOqbiBj4bupdSBjaGkgdGnhur90IGjGoW4gbuG7r2EgduG7gSBo4bqtdSBxdeG6oyBraW5oIHThur8gY8WpbmcgbmjGsCBjw7MgdGjhu4MgxJHGsGEgcmEgYuG6sW5nIGNo4bupbmcgdGjDtG5nIGvDqiB0aHV54bq/dCBwaOG7pWMgaMahbiB0aMOsIGvhur90IHF14bqjIHPhur0gxJHGsOG7o2Mga2nhu4NtIHRyYSBjaG8gMTAwMCBs4bqnbiBjaOG7jW4gbeG6q3UuIA0KDQpUcsaw4bubYyBo4bq/dCBodeG6pW4gbHV54buHbiB2w6Agc28gc8Ohbmggc8ahIGLhu5kgbeG7mXQgbG/huqF0IGPDoWMgbcO0IGjDrG5oIE1hY2hpbmUgTGVhcm5pbmcuIMSQ4buDIHRodeG6rW4gbOG7o2kgY2hvIHZp4buHYyBzbyBzw6FuaCBjaMO6bmcgdGEgbsOqbiBz4butIGThu6VuZyBnw7NpICoqY2FyZXRFbnNlbWJsZSoqIGNobyB2aeG7h2Mgc28gc8OhbmggdsOgIMSRw6FuaCBnacOhOiANCg0KYGBge3J9DQoNCiMgQ2h14bqpbiBow7NhIGThu68gbGnhu4d1IHbDoCBsb+G6oWkgbeG7mXQgc+G7kSBj4buZdCBiaeG6v24ga2jDtG5nIGPhuqduIHRoaeG6v3Q6IA0KDQpkZl9mb3JfbWwgPC0gR2VybWFuQ3JlZGl0ICU+JSANCiAgbXV0YXRlX2lmKGlzLm51bWVyaWMsIGZ1bmN0aW9uKHgpIHsoeCAtIG1pbih4KSkgLyAobWF4KHgpIC0gbWluKHgpKX0pICU+JSANCiAgc2VsZWN0KC1QZXJzb25hbC5GZW1hbGUuU2luZ2xlLCAtUHVycG9zZS5WYWNhdGlvbikNCg0KIyBTcGxpdCBkYXRhOiANCg0Kc2V0LnNlZWQoMSkNCmlkIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oeSA9IGRmX2Zvcl9tbCRDbGFzcywgcCA9IDAuNywgbGlzdCA9IEZBTFNFKQ0KZGZfdHJhaW5fbWwgPC0gZGZfZm9yX21sW2lkLCBdDQpkZl90ZXN0X21sIDwtIGRmX2Zvcl9tbFstaWQsIF0NCg0KIyBTZXQgY29uZGl0aW9ucyBmb3IgdHJhaW5pbmcgbW9kZWwgYW5kIGNyb3NzLXZhbGlkYXRpb246IA0KDQpzZXQuc2VlZCgxKQ0KbnVtYmVyIDwtIDUNCnJlcGVhdHMgPC0gNQ0KY29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gInJlcGVhdGVkY3YiLCANCiAgICAgICAgICAgICAgICAgICAgICAgIG51bWJlciA9IG51bWJlciAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgcmVwZWF0cyA9IHJlcGVhdHMsIA0KICAgICAgICAgICAgICAgICAgICAgICAgY2xhc3NQcm9icyA9IFRSVUUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgc2F2ZVByZWRpY3Rpb25zID0gImZpbmFsIiwgDQogICAgICAgICAgICAgICAgICAgICAgICBpbmRleCA9IGNyZWF0ZVJlc2FtcGxlKGRmX3RyYWluX21sJENsYXNzLCByZXBlYXRzKm51bWJlciksIA0KICAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyeUZ1bmN0aW9uID0gbXVsdGlDbGFzc1N1bW1hcnksIA0KICAgICAgICAgICAgICAgICAgICAgICAgYWxsb3dQYXJhbGxlbCA9IFRSVUUpDQoNCiMgVXNlIFBhcmFsbGVsIGNvbXB1dGluZzogDQpsaWJyYXJ5KGRvUGFyYWxsZWwpDQpyZWdpc3RlckRvUGFyYWxsZWwoY29yZXMgPSBkZXRlY3RDb3JlcygpIC0gMSkNCg0KIyA3IG1vZGVscyBzZWxlY3RlZDogDQoNCm15X21vZGVscyA8LSBjKCJnbG0iLCAicmYiLCAiYWRhYm9vc3QiLCAic3ZtUmFkaWFsIiwgImtubiIsICJ4Z2JUcmVlIiwgIkM1LjAiKQ0KDQojIFRyYWluIHRoZXNlIE1MIE1vZGVsczogDQpsaWJyYXJ5KGNhcmV0RW5zZW1ibGUpDQpzZXQuc2VlZCgxKQ0Kc3lzdGVtLnRpbWUobW9kZWxfbGlzdDEgPC0gY2FyZXRMaXN0KENsYXNzIH4uLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGZfdHJhaW5fbWwsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gY29udHJvbCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRyaWMgPSAiQWNjdXJhY3kiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2RMaXN0ID0gbXlfbW9kZWxzKSkNCg0KIyBFeHRyYWN0IHJlc3VsdHMgZm9yIGNvbXBhcmluZzogDQoNCmxpc3Rfb2ZfcmVzdWx0cyA8LSBsYXBwbHkobXlfbW9kZWxzLCBmdW5jdGlvbih4KSB7bW9kZWxfbGlzdDFbW3hdXSRyZXNhbXBsZX0pDQoNCiMgQ29udmVydCB0byBkYXRhIGZyYW1lOiANCnRvdGFsX2RmIDwtIGRvLmNhbGwoImJpbmRfcm93cyIsIGxpc3Rfb2ZfcmVzdWx0cykNCnRvdGFsX2RmICU8PiUgbXV0YXRlKE1vZGVsID0gbGFwcGx5KG15X21vZGVscywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbih4KSB7cmVwKHgsIG51bWJlcipyZXBlYXRzKX0pICU+JSB1bmxpc3QoKSkNCg0KIyBBdmVyYWdlIEFjY3VyYWN5IGJhc2VkIG9uIDI1IHNhbXBsZXMgZm9yIHRoZXNlIG1vZGVsczogDQoNCnRvdGFsX2RmICU+JSANCiAgZ3JvdXBfYnkoTW9kZWwpICU+JSANCiAgc3VtbWFyaXNlX2VhY2goZnVucyhtZWFuKSwgQWNjdXJhY3ksIEFVQywgU2Vuc2l0aXZpdHksIFNwZWNpZmljaXR5LCBGMSwgS2FwcGEpICU+JSANCiAgdW5ncm91cCgpICU+JSANCiAgYXJyYW5nZSgtQWNjdXJhY3kpICU+JSANCiAgbXV0YXRlX2lmKGlzLm51bWVyaWMsIGZ1bmN0aW9uKHgpIHtyb3VuZCgxMDAqeCwgMil9KSAlPiUgDQogIGtuaXRyOjprYWJsZShjYXB0aW9uID0gIlRhYmxlIDI6IEEgQ29wYXJpc2lvbiBvZiBNb2RlbCBQZXJmb3JtYW5jZSBieSBzb21lIENyaXRlcmlhIikNCg0KYGBgDQoNCg0KVGFibGUgMiBtYW5nIGzhuqFpIOG6pW4gdMaw4bujbmcgcuG6sW5nIFhnYlRyZWUgbMOgIG3DtCBow6xuaCBuw6puIMSRxrDhu6NjIGzhu7FhIGNo4buNbiB2w6wgxJHDonkgbMOgIGPDoWNoIHRp4bq/cCBj4bqtbiBjw7MgQWNjdXJhY3kgY2FvIG5o4bqldC4gVHV5IG5oacOqbiwgduG7m2kgY8OhYyB04buVIGNo4bupYyB2w6wgbOG7o2kgbmh14bqtbiB0aMOsIG3DtCBow6xuaCBjw7MgbeG7qWMgxJHhu5kgY2jDrW5oIHjDoWMgY2FvIG5o4bqldCBjaMawYSBjaOG6r2MgxJHDoyBsw6AgbcO0IGjDrG5oIMSRxrDhu6NjIGzhu7FhIGNo4buNbiDEkeG7gyBwaMOibiBsb+G6oWkgaOG7kyBzxqEuIEPhu6UgdGjhu4MgaMahbiwgWGdiVHJlIGtow7RuZyBwaOG6o2kgbMOgIG3DtCBow6xuaCBtYW5nIGzhuqFpIG5oaeG7gXUgbOG7o2kgbmh14bqtbiBuaOG6pXQgbcOgIGzDoCBDNS4wICBuaMawIGNow7puZyB0YSBjw7MgdGjhu4MgdGjhuqV5IG5nYXkgc2F1IMSRw6J5IGTDuSBtw7QgaMOsbmggbsOgeSBraMO0bmcgcGjhuqNpIGPDsyBBY2N1cmFjeSBjYW8gbmjhuqV0OiANCg0KDQpgYGB7cn0NCiMgVmnhur90IGjDoG0gxJHDoW5oIGdpw6Ega+G6v3QgcXXhuqMgduG7m2kgxJHhuqd1IHbDoG8gbMOgOiAoMSkgdOG7iSBs4buHIG3huqt1IMSRxrDhu6NjIGNo4buNbiwgDQojICgyKSBz4buRIGzhuqduIGzhurdwLCB2w6AgKDMpICBtb2RlbCDEkcaw4bujYyBjaOG7jW4gKyBi4buZIGThu68gbGnhu4d1IG3DoCB04burIMSRw7MNCiMgY2jDum5nIHRhIGNo4buNbiBt4bqrdTogDQoNCg0KZ2V0X3Jlc3VsdCA8LSBmdW5jdGlvbih0aV9sZSwgTiwgbW9kZWxfc2VsZWN0ZWQsIGRmX3Rlc3Rfc2VsZWN0ZWQpIHsNCiAgbXlfdmVjIDwtIGMoKQ0KICANCiAgZm9yIChpIGluIDE6Tikgew0KICAgIHNldC5zZWVkKGkpIA0KICAgIGRmX3Rlc3QgPC0gZGZfdGVzdF9zZWxlY3RlZCAlPiUgDQogICAgICBncm91cF9ieShDbGFzcykgJT4lIA0KICAgICAgc2FtcGxlX2ZyYWModGlfbGUpICU+JSANCiAgICAgIHVuZ3JvdXAoKQ0KICAgIA0KICAgIHByZWQgPC0gcHJlZGljdChtb2RlbF9zZWxlY3RlZCwgZGZfdGVzdCAlPiUgc2VsZWN0KC1DbGFzcykpDQogICAgY20gPC0gY29uZnVzaW9uTWF0cml4KGRmX3Rlc3QkQ2xhc3MsIHByZWQpDQogICAgY20kdGFibGUgJT4lIA0KICAgICAgYXMudmVjdG9yKCkgLT4gdQ0KICAgIG15X3ZlYyA8LSBjKG15X3ZlYywgdSkNCiAgfQ0KICANCiAgbXlfdmVjICU+JSANCiAgICBtYXRyaXgobmNvbCA9IDQsIGJ5cm93ID0gVFJVRSkgJT4lIA0KICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUgDQogICAgcmVuYW1lKEJCID0gVjEsIA0KICAgICAgICAgICBHQiA9IFYyLA0KICAgICAgICAgICBCRyA9IFYzLA0KICAgICAgICAgICBHRyA9IFY0KSAlPiUgDQogICAgcmV0dXJuKCkNCn0NCg0KDQojIEvhur90IHF14bqjIHBow6JuIGxv4bqhaSBj4bunYSBjw6FjIG3DtCBow6xuaCBuw6B5OiANCg0KbG9hbl94Z2J0cmVlIDwtIGdldF9yZXN1bHQoMC41LCAxMDAsIG1vZGVsX2xpc3QxJHhnYlRyZWUsIGRmX3Rlc3RfbWwpDQpsb2FuX3N2bSA8LSBnZXRfcmVzdWx0KDAuNSwgMTAwLCBtb2RlbF9saXN0MSRzdm1SYWRpYWwsIGRmX3Rlc3RfbWwpDQpsb2FuX3JmIDwtIGdldF9yZXN1bHQoMC41LCAxMDAsIG1vZGVsX2xpc3QxJHJmLCBkZl90ZXN0X21sKQ0KbG9hbl9hZGFib29zdCA8LSBnZXRfcmVzdWx0KDAuNSwgMTAwLCBtb2RlbF9saXN0MSRhZGFib29zdCwgZGZfdGVzdF9tbCkNCmxvYW5fbG9naXQgPC0gZ2V0X3Jlc3VsdCgwLjUsIDEwMCwgbW9kZWxfbGlzdDEkZ2xtLCBkZl90ZXN0X21sKQ0KbG9hbl9jNTAgPC0gZ2V0X3Jlc3VsdCgwLjUsIDEwMCwgbW9kZWxfbGlzdDEkQzUuMCwgZGZfdGVzdF9tbCkNCmxvYW5fa25uIDwtIGdldF9yZXN1bHQoMC41LCAxMDAsIG1vZGVsX2xpc3QxJGtubiwgZGZfdGVzdF9tbCkNCg0KDQojIFThu5VuZyBo4bujcCBjw6FjIERhdGEgRnJhbWUgbsOgeSB2w6AgdOG6oW8gcmEgY+G7mXQgQWNjdXJhY3k6IA0KDQp0b3RhbF9kZl9yZXN1bHQgPC0gYmluZF9yb3dzKGxvYW5feGdidHJlZSAlPiUgbXV0YXRlKE1vZGVsID0gIlhnYlRyZWUiKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvYW5fc3ZtICU+JSBtdXRhdGUoTW9kZWwgPSAiU1ZNIiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsb2FuX3JmICU+JSBtdXRhdGUoTW9kZWwgPSAiUkYiKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvYW5fa25uICU+JSBtdXRhdGUoTW9kZWwgPSAiS05OIiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsb2FuX2xvZ2l0ICU+JSBtdXRhdGUoTW9kZWwgPSAiTG9naXN0aWMiKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvYW5fYzUwICU+JSBtdXRhdGUoTW9kZWwgPSAiQzUuMCIpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbG9hbl9hZGFib29zdCAlPiUgbXV0YXRlKE1vZGVsID0gIkFkYUJvb3N0IikpICU+JSANCiAgbXV0YXRlKEFjY3VyYWN5ID0gKEJCICsgR0cpIC8gKEJCICsgR0cgKyBHQiArIEJHKSkNCg0KIyBBY2N1cmFjeSB0cnVuZyBiw6xuaCB0csOqbiAxMDAgbeG6q3UgxJHGsOG7o2MgdGjhu60gbmdoaeG7h206IA0KDQp0b3RhbF9kZl9yZXN1bHQgJT4lIA0KICBncm91cF9ieShNb2RlbCkgJT4lIA0KICBzdW1tYXJpc2VfZWFjaChmdW5zKG1lYW4pLCBBY2N1cmFjeSwgR0csIEJCLCBCRykgJT4lIA0KICB1bmdyb3VwKCkgJT4lIA0KICBhcnJhbmdlKC1BY2N1cmFjeSkgJT4lIA0KICBtdXRhdGVfYXQoLnZhcnMgPSBjKCJBY2N1cmFjeSIpLCBmdW5jdGlvbih4KSB7cm91bmQoMTAwKngsIDIpfSkgJT4lIA0KICBrbml0cjo6a2FibGUoY2FwdGlvbiA9ICJUYWJsZSAzOiBBIENvcGFyaXNpb24gb2YgTW9kZWwgUGVyZm9ybWFuY2UgYnkgc29tZSBDcml0ZXJpYSwgMTAwIHNhbXBsZXMiKQ0KDQoNCmBgYA0KDQoNCmBgYHtyfQ0KIyBWaeG6v3QgaMOgbSBtw7QgcGjhu49uZyBs4bujaSBuaHXhuq1uIHbhu5tpIGPDoWMgZ2nhuqMgdGhp4bq/dCDEkcOjIG7DqnUg4bufIHRyw6puOiANCg0KcHJvZml0X3NpbXUgPC0gZnVuY3Rpb24oZGZfcmVzdWx0LCByYXRlLCBOKSB7DQoNCiAga2hvYW5fdmF5IDwtIEdlcm1hbkNyZWRpdCRBbW91bnQNCiAgc29fa2hvYW5fdmF5X3RvdCA8LSBzdW0oZGZfcmVzdWx0JEdHKQ0KICBzb19raG9hbl92YXlfeGF1IDwtIHN1bShkZl9yZXN1bHQkQkcpDQogIA0KICBteV9wcm9mIDwtIGMoKQ0KICANCiAgZm9yIChpIGluIDE6Tikgew0KICAgIHNldC5zZWVkKGkpDQogICAgcHJvZiA8LSByYXRlKnNhbXBsZShraG9hbl92YXksIHNpemUgPSBzb19raG9hbl92YXlfdG90LCByZXBsYWNlID0gVFJVRSkgJT4lIHN1bSgpIC0gDQogICAgICBzdW0oc2FtcGxlKGtob2FuX3ZheSwgc2l6ZSA9IHNvX2tob2FuX3ZheV94YXUsIHJlcGxhY2UgPSBUUlVFKSkNCiAgICBteV9wcm9mIDwtIGMocHJvZiwgbXlfcHJvZikNCiAgfQ0KICByZXR1cm4obXlfcHJvZikNCn0NCg0KIyBM4bujaSBuaHXhuq1uIGPhu6dhIHThu5UgY2jhu6ljIHTDoGkgY2jDrW5oIHTGsMahbmcg4bupbmcgduG7m2kgdmnhu4djDQojIHPhu60gZOG7pW5nIGPDoWMgbcO0IGjDrG5oIGThu7FhIHRyw6puIG3DtCBwaOG7j25nIDEwMDAgbOG6p246IA0KDQoNCnByb2ZpdCA8LSBjKGxvYW5feGdidHJlZSAlPiUgcHJvZml0X3NpbXUoMC4zLCAxMDAwKSwgDQogICAgICAgICAgICBsb2FuX3N2bSAlPiUgcHJvZml0X3NpbXUoMC4zLCAxMDAwKSwgDQogICAgICAgICAgICBsb2FuX3JmICU+JSBwcm9maXRfc2ltdSgwLjMsIDEwMDApLCANCiAgICAgICAgICAgIGxvYW5fa25uICU+JSBwcm9maXRfc2ltdSgwLjMsIDEwMDApLCANCiAgICAgICAgICAgIGxvYW5fbG9naXQgJT4lIHByb2ZpdF9zaW11KDAuMywgMTAwMCksIA0KICAgICAgICAgICAgbG9hbl9jNTAgJT4lIHByb2ZpdF9zaW11KDAuMywgMTAwMCksIA0KICAgICAgICAgICAgbG9hbl9hZGFib29zdCAlPiUgcHJvZml0X3NpbXUoMC4zLCAxMDAwKSkNCg0KDQpwcm9maXRfZGYgPC0gZGF0YS5mcmFtZShQcm9maXQgPSBwcm9maXQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgTW9kZWwgPSBjKHJlcCgiWGdiVHJlZSIsIDEwMDApLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXAoIlNWTSIsIDEwMDApLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXAoIlJGIiwgMTAwMCksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcCgiS05OIiwgMTAwMCksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcCgiTG9naXN0aWMiLCAxMDAwKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXAoIkM1LjAiLCAxMDAwKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwKCJBZGFCb29zdCIsIDEwMDApKSkNCg0KDQojIEPDoWMgdGjhu5FuZyBrw6ogY2hpIHRp4bq/dCB24buBIGzhu6NpIG5odeG6rW4gbsOgeSB0xrDGoW5nIOG7qW5nIHbhu5tpIGPDoWMgbcO0IGjDrG5oOiANCg0KcHJvZml0X2RmICU+JSANCiAgZ3JvdXBfYnkoTW9kZWwpICU+JSANCiAgc3VtbWFyaXNlX2VhY2goZnVucyhtZWFuLCBtZWRpYW4sIG1pbiwgbWF4LCBzZCksIFByb2ZpdCkgJT4lIA0KICB1bmdyb3VwKCkgJT4lIA0KICBhcnJhbmdlKC1tZWFuKSAlPiUgDQogIG11dGF0ZV9pZihpcy5udW1lcmljLCBmdW5jdGlvbih4KSB7cm91bmQoeCwgMCl9KSAlPiUgDQogIGtuaXRyOjprYWJsZShjYXB0aW9uID0gIlRhYmxlIDQ6IFNpbXVsYXRlZCBQcm9maXQgQmFzZWQgTW9udGUgQ2FybG8gTWV0aG9kXG53aXRoIEludGVyZXN0IFJhdGUgaXMgMzAlIGZvciBUZXN0IERhdGEgU2V0cyIsIA0KICAgICAgICAgICAgICAgY29sLm5hbWVzID0gYygiTW9kZWwiLCAiTWVhbiIsICJNZWRpYW4iLCAiTWluIiwgIk1heCIsICJTRCIpKQ0KYGBgDQoNCkNow7puZyB0YSBjw7MgdGjhu4MgaMOsbmgg4bqjbmggaMOzYSBwaMOibiBwaOG7kWkgY+G7p2EgbOG7o2kgbmh14bqtbiB0xrDGoW5nIOG7qW5nIHbhu5tpIGPDoWMgbcO0IGjDrG5oIMSRxrDhu6NjIGzhu7FhIGNo4buNbjogDQoNCmBgYHtyfQ0KcHJvZml0X2RmICU+JSANCiAgbXV0YXRlKFByb2ZpdCA9IFByb2ZpdCAvIDEwMDAwMDApICU+JSANCiAgZ2dwbG90KGFlcyhQcm9maXQpKSArIA0KICBnZW9tX2RlbnNpdHkoZmlsbCA9ICJyZWQiLCBjb2xvciA9ICJyZWQiLCBhbHBoYSA9IDAuMykgKyANCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSAuLmRlbnNpdHkuLiksIGNvbG9yID0gImJsdWUiLCBmaWxsID0gImJsdWUiLCBhbHBoYSA9IDAuMykgKyANCiAgZmFjZXRfd3JhcCh+IE1vZGVsLCBzY2FsZXMgPSAiZnJlZSIpICsgDQogIGxhYnMoeCA9IE5VTEwsIHkgPSBOVUxMLCANCiAgICAgICB0aXRsZSA9ICJGaWd1cmUgMjogU2ltdWxhdGVkIFByb2ZpdCBCYXNlZCBvbiBNb250ZSBDYXJsbyBNZXRob2RcbndpdGggSW50ZXJlc3QgUmF0ZSBvZiAzMCUgYW5kIDEwMDAgU2FtcGxlcyAodW5pdDogbWlsbGlvbnMpIiwgDQogICAgICAgY2FwdGlvbiA9ICJEYXRhIFVzZWQ6IEdlcm1hbiBDcmVkaXQgcHJvdmlkZWQgYnkgQ2VudGVyIGZvciBNYWNoaW5lIExlYXJuaW5nIGFuZCBJbnRlbGxpZ2VudCBTeXN0ZW1zIikNCmBgYA0KDQpW4bubaSBk4buvIGxp4buHdSDhu58gRmlndXJlIDIgY2jDum5nIHRhIGPFqW5nIGPDsyB0aOG7gyB0w61uaCB0b8OhbiB4w6FjIHN14bqldCBtw6AgbmfDom4gaMOgbmcgY8OzIGzhu6NpIG5odeG6rW4gw6JtIHbhu5tpIG3hu5l0IG5nxrDhu6FuZyB4w6FjIHN14bqldCBjaOG7jW4gdHLGsOG7m2MgbsOgbyDEkcOzIGLhurFuZyBbVmFSIChWYWx1ZSBhdCBSaXNrKV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvVmFsdWVfYXRfcmlzaykuIA0KDQojIFbDoGkga+G6v3QgbHXhuq1uDQoNClRyb25nIHRo4buxYyB04bq/IHRow6wgbmjhu69uZyBnaeG6oyB0aGnhur90IHNhdSDEkcOieSBj4bqnbiBwaOG6o2kgxJHGsOG7o2MgbuG7m2kgbOG7j25nOiANCg0KMS4gS2jDtG5nIHBo4bqjaSBt4buNaSBo4buTIHPGoSB04buRdCBraGkgxJHGsOG7o2MgdmF5IMSR4buBdSBjw7MgbeG7qWMgxJHhu5kgaG/DoG4gduG7kW4gdsOgIGzDo2kgY2hvIG5nw6JuIGjDoG5nIHbhu5tpIHjDoWMgc3XhuqV0IDEwMCUuIMSQSeG7gXUgxJHDsyBjxaluZyDEkcO6bmcgduG7m2kgY8OhYyBo4buTIHPGoSB44bqldSAobmjGsG5nIHRoZW8gaMaw4bubbmcgbmfGsOG7o2MgbOG6oWkpLiBYw6FjIHN14bqldCBj4bunYSBjw6FjIHPhu7Ega2nhu4duIG7DoHkgY8OzIHRo4buDIMSRxrDhu6NjIMaw4bubYyBsxrDhu6NuZyB04burIGThu68gbGnhu4d1IGzhu4tjaCBz4butIGPhu6dhIG5nw6JuIGjDoG5nIGhv4bq3YyBz4butIGThu6VuZyBjw6FjIHBoxrDGoW5nIHBow6FwIG3DtCBwaOG7j25nLg0KDQoyLiBLaMO0bmcgY2jhu4kgUHJvZml0IG3DoCBjw7JuIG5oaeG7gXUga2jDrWEgY+G6oW5oIGtow6FjIG3DoCB04buVIGNo4bupYyBz4butIGThu6VuZyBtw7QgaMOsbmggcGjDom4gbG/huqFpIHF1YW4gdMOibS4gQ2jhurNuZyBo4bqhbiBt4bupYyDEkeG7mSDhu5VuIMSR4buLbmgga2hpIHBow6JuIGxv4bqhaSBj4bunYSBtw7QgaMOsbmguDQoNCjMuIEto4bqjIG7Eg25nIHBow6JuIGxv4bqhaSBjaMOtbmggeMOhYyBjw6FjIGzhu5twIGjhu5Mgc8ahIChuaMOjbiBCYWQgcGjDom4gbG/huqFpIMSRw7puZyB0aMOgbmggQmFkIC0gQkIsIG5ow6NuIEdvb2QgcGjDom4gbG/huqFpIMSRw7puZyB0aMOgbmggR29vZCAtIEdHKSBwaOG7pSB0aHXhu5ljIHbDoG8gbmfGsOG7oW5nIMSRxrDhu6NjIGzhu7FhIGNo4buNbiAoY3V0b2ZmKSBjaG8gUHJvYmFiaWxpdHkgb2YgRGVmYXVsdCBraGkgcGjDom4gbG/huqFpIHbDoCBkbyDEkcOzIG5nxrDhu6FuZyBuw6B5IGPDsyDhuqNuaCBoxrDhu59uZyDEkeG6v24gUHJvZml0LiBIaeG7h24gdOG6oWkga2jhuqNvIHPDoXQg4bqjbmggaMaw4bufbmcgY+G7p2EgbmfGsOG7oW5nIGzDqm4gUHJvZml0IGNoxrBhIMSRxrDhu6NjIGto4bqjbyBzw6F0IHbDoCDEkcOhbmggZ2nDoSB0cm9uZyBiw6BpIHZp4bq/dCBuw6B5LiANCg0KIyBSZWZlcmVuY2VzDQoNCjEuIE1hcnRlbnMsIEQuLCBCLiBCYWVzZW5zLCBULiBWYW4gR2VzdGVsLCBhbmQgSi4gVmFudGhpZW5lbi4gMjAwNy4g4oCcQ29tcHJlaGVuc2libGUgQ3JlZGl0IFNjb3JpbmcgTW9kZWxzIFVzaW5nIFJ1bGUgRXh0cmFjdGlvbiBmcm9tIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzLuKAnSBFdXJvcGVhbiBKb3VybmFsIG9mIE9wZXJhdGlvbmFsIFJlc2VhcmNoIDE4MzoxNDY24oCTMTQ3Ni4NCg0KMi4gQmFlc2VucywgQi4sIFJvZXNjaCwgRC4sICYgU2NoZXVsZSwgSC4gKDIwMTYpLiBDcmVkaXQgcmlzayBhbmFseXRpY3M6IE1lYXN1cmVtZW50IHRlY2huaXF1ZXMsIGFwcGxpY2F0aW9ucywgYW5kIGV4YW1wbGVzIGluIFNBUy4gSm9obiBXaWxleSAmIFNvbnMuDQoNCjMuIFNpZGRpcWksIE4uICgyMDEyKS4gQ3JlZGl0IHJpc2sgc2NvcmVjYXJkczogZGV2ZWxvcGluZyBhbmQgaW1wbGVtZW50aW5nIGludGVsbGlnZW50IGNyZWRpdCBzY29yaW5nLiBKb2huIFdpbGV5ICYgU29ucy4NCg0KNC4gQW5kZXJzb24gUiAoMjAwNyk6IFRoZSBDcmVkaXQgU2NvcmluZyBUb29sa2l0OiBUaGVvcnkgYW5kIFByYWN0aWNlIGZvciBSZXRhaWwgQ3JlZGl0IFJpc2sgTWFuYWdlbWVudCBhbmQgRGVjaXNpb24gQXV0b21hdGlvbi4gT3hmb3JkLCBPeGZvcmQgVW5pdmVyc2l0eSBQcmVzcy4NCg0KNS4gSGFuZCBESiwgSGVubGV5IFdFICgxOTk3KTogU3RhdGlzdGljYWwgQ2xhc3NpZmljYXRpb24gTWV0aG9kcyBpbiBDb25zdW1lciBDcmVkaXQgU2NvcmluZzogYSByZXZpZXcuIEpvdXJuYWwuIG9mIHRoZSBSb3lhbCBTdGF0aXN0aWNhbCBTb2NpZXR5LCBTZXJpZXMgQSwgMTYwKDMpOjUyM+KAkzU0MS4NCg0KNi4gVGhvbWFzIExDICgyMDAwKTogQSBzdXJ2ZXkgb2YgY3JlZGl0IGFuZCBiZWhhdmlvdXJhbCBzY29yaW5nOiBmb3JlY2FzdGluZyBmaW5hbmNpYWwgcmlzayBvZiBsZW5kaW5nIHRvIGNvbnN1bWVycy4gSW50ZXJuYXRpb25hbCBKb3VybmFsIG9mIEZvcmVjYXN0aW5nLCAxNigyKToxNDnigJMxNzIgLg0KDQo3LiBUaG9tYXMgTEMgKDIwMDkpOiBDb25zdW1lciBDcmVkaXQgTW9kZWxzOiBQcmljaW5nLCBQcm9maXQsIGFuZCBQb3J0Zm9saW8uIE94Zm9yZCwgT3hmb3JkIFVuaXZlcnNpdHkgUHJlc3MuDQoNCjguIENyb29rIEpOLCBFZGVsbWFuIERCLCBUaG9tYXMgTEMgKDIwMDcpOiBSZWNlbnQgZGV2ZWxvcG1lbnRzIGluIGNvbnN1bWVyIGNyZWRpdCByaXNrIGFzc2Vzc21lbnQuIEV1cm9wZWFuIEpvdXJuYWwgb2YgT3BlcmF0aW9uYWwgUmVzZWFyY2gsIDE4MygzKToxNDQ34oCTMTQ2NS4NCg0KOS4gVmFuIEdlc3RlbCwgVC4sIEIuIEJhZXNlbnMsIFAuIFZhbiBEaWpja2UsIEouIFN1eWtlbnMsIEouIEdhcmNpYSwgYW5kIFQuIEFsZGVyd2VpcmVsZC4gMjAwNS4g4oCcTGluZWFyIGFuZCBOb25saW5lYXIgQ3JlZGl0IFNjb3JpbmcgYnkgQ29tYmluaW5nIExvZ2lzdGljIFJlZ3Jlc3Npb24gYW5kIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzLuKAnSBKb3VybmFsIG9mIENyZWRpdCBSaXNrIDEsIG5vLiA0Lg0KDQoxMC4gQmVuLURhdmlkLCBBLiwgJiBGcmFuaywgRS4gKDIwMDkpLiBBY2N1cmFjeSBvZiBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscyB2ZXJzdXMg4oCcaGFuZCBjcmFmdGVk4oCdIGV4cGVydCBzeXN0ZW1z4oCTYSBjcmVkaXQgc2NvcmluZyBjYXNlIHN0dWR5LiBFeHBlcnQgU3lzdGVtcyB3aXRoIEFwcGxpY2F0aW9ucywgMzYoMyksIDUyNjQtNTI3MS4NCg0KMTEuIE1vbGluYXJvIEEgKDIwMDUpLiDigJxQcmVkaWN0aW9uIEVycm9yIEVzdGltYXRpb246IEEgQ29tcGFyaXNvbiBvZiBSZXNhbXBsaW5nIE1ldGhvZHMu4oCdIEJpb2luZm9ybWF0aWNzLCAyMSgxNSksIDMzMDHigJMzMzA3Lg0KDQoxMi4gS2ltIEpIICgyMDA5KS4g4oCcRXN0aW1hdGluZyBDbGFzc2lmaWNhdGlvbiBFcnJvciBSYXRlOiBSZXBlYXRlZCBDcm9zc+KAkyBWYWxpZGF0aW9uLCBSZXBlYXRlZCBIb2xk4oCTT3V0IGFuZCBCb290c3RyYXAu4oCdIENvbXB1dGF0aW9uYWwgU3RhdGlzdGljcyAmIERhdGEgQW5hbHlzaXMsIDUzKDExKSwgMzczNeKAkzM3NDUuDQoNCg0KDQoNCg0KDQoNCg0K