Introduction

I will write something later…

BAD n percent
Bad 300 8.92
Good 3064 91.08
library(tidymodels)

set.seed(123)
members_split <- initial_split(hmeq_full, prop = 0.7, strata = BAD)
members_train <- training(members_split)
members_test <- testing(members_split)

set.seed(123)
members_folds <- vfold_cv(members_train, strata = BAD, v = 5, repeats = 3)

library(recipes)
library(themis)

members_rec <- recipe(BAD ~ ., data = members_train) %>%
  step_dummy(all_nominal(), -BAD) %>%
  step_smote(BAD)

glm_spec <- logistic_reg() %>%
  set_engine("glm")

rf_spec <- rand_forest(trees = 1000) %>%
  set_mode("classification") %>%
  set_engine("ranger")

members_wf <- workflow() %>%
  add_recipe(members_rec)

members_metrics <- metric_set(roc_auc, accuracy, sensitivity, specificity)

glm_rs <- members_wf %>%
  add_model(glm_spec) %>%
  fit_resamples(resamples = members_folds,
                metrics = members_metrics,
                control = control_resamples(save_pred = TRUE))

rf_rs <- members_wf %>%
  add_model(rf_spec) %>%
  fit_resamples(resamples = members_folds,
                metrics = members_metrics,
                control = control_resamples(save_pred = TRUE))

members_final_glm <- members_wf %>%
  add_model(glm_spec) %>%
  last_fit(members_split)

collect_predictions(members_final_glm) -> glm_pd

members_final_rf <- members_wf %>%
  add_model(rf_spec) %>%
  last_fit(members_split)

collect_predictions(members_final_rf) -> rf_pd

calculate_some_metrics <- function(threshold_selected, pd_selected) {
  
  pd_selected %>% 
    mutate(label_predicted_threshold = case_when(.pred_Bad >= threshold_selected ~ "Bad", TRUE ~ "Good")) -> df_result
  
  sum(df_result$label_predicted_threshold == df_result$BAD) / nrow(df_result) -> accuracy_threshold
  
  df_result %>% filter(BAD == "Bad") -> actual_is_died
  
  sum(actual_is_died$label_predicted_threshold == "Bad") / nrow(actual_is_died) -> sensitivity_threshold
  
  df_result %>% filter(BAD != "Bad") -> actual_is_survived
  
  # Calculate Specificity: 
  sum(actual_is_survived$label_predicted_threshold != "Bad") / nrow(actual_is_survived) -> spec 
  
  # Report results in form of data frame: 
  data.frame(Accuracy = accuracy_threshold, 
             Sensitivity = sensitivity_threshold, 
             Specificity = spec, 
             FPR = 1 - spec, 
             # AUC = roc_auc, 
             Threshold = threshold_selected) -> df_reporting
  return(df_reporting)
}

range_threshold <- c(0.5, seq(0, 1, length.out = 99))

lapply(range_threshold, function(x) {calculate_some_metrics(x, pd_selected = glm_pd)}) -> list_glm
lapply(range_threshold, function(x) {calculate_some_metrics(x, pd_selected = rf_pd)}) -> list_rf

do.call("bind_rows", list_glm) -> glm_results_thres
do.call("bind_rows", list_rf) -> rf_results_thres

bind_rows(glm_results_thres %>% mutate(Model = "GLM"), 
          rf_results_thres %>% mutate(Model = "RF")) -> df_compare

df_compare %>% 
  gather(Metric, Value, -Threshold, -Model) -> df_long

library(pROC)
glm_auc <- roc(case_when(members_test$BAD == "Bad" ~ 1, TRUE ~ 0), glm_pd$.pred_Bad)$auc %>% round(3)
rf_auc <- roc(case_when(members_test$BAD == "Bad" ~ 1, TRUE ~ 0), rf_pd$.pred_Bad)$auc %>% round(3)

library(extrafont)
theme_set(theme_minimal())
my_font <- "Roboto Condensed"

df_long %>% 
  ggplot(aes(Threshold, Value, color = Model)) + 
  geom_line() + 
  geom_point(data = df_long %>% filter(Threshold == 0.5)) + 
  facet_wrap(~ Metric) + 
  theme(text = element_text(family = my_font)) + 
  theme(plot.margin = unit(rep(0.8, 4), "cm")) + 
  scale_y_continuous(labels = scales::percent) + 
  labs(subtitle = paste0("AUC for GLM = ", glm_auc, ", AUC for RF = ", rf_auc), 
       title = "Figure 1: Model Performance by Threshold based on Test Data", 
       y = NULL)

#========================

calculate_profit <- function(threshold_selected, pd_selected) {
  
  members_test %>% 
    as_tibble() %>% 
    mutate(pd = pd_selected$.pred_Bad) %>% 
    mutate(predicted = case_when(pd >= threshold_selected ~ "Bad", TRUE ~ "Good")) -> df_selected
  
  # Set conditions for calculating average profit at given threshold: 
  n <- 50
  rate <- 0.07
  profit_space <- NULL
  
  # Calculate net profit for each sample randomly selected from test data:
  
  for (j in 1:n) {
    
    set.seed(j)
    
    df_results <- df_selected %>% 
      sample_frac(0.7)
    
    # Profit from true negative cases: 
    
    df_results %>% 
      filter(predicted == "Good", BAD == "Good") %>% 
      mutate(profit = rate*LOAN) %>% 
      pull(profit) %>% 
      sum() -> total_profit
    
    # Loss causes from false negative cases: 
    
    df_results %>% 
      filter(predicted == "Good", BAD == "Bad") %>% 
      pull(LOAN) %>% 
      sum() -> total_loss
    
    # Net profit: 
    net_profit <- total_profit - total_loss
    profit_space <- c(profit_space, net_profit)
    
  }
  
  # Average net profit at given threshold: 
  data.frame(Profit_avg = mean(profit_space), Threshold = threshold_selected) -> df_prof_thres
  return(df_prof_thres)
  
}

lapply(range_threshold, function(x) {calculate_profit(threshold_selected = x, pd_selected = rf_pd)}) -> list_prof

do.call("bind_rows", list_prof) -> df_prof

df_prof %>% filter(Threshold == 0.5) -> default_prof

df_prof %>% slice(which.max(Profit_avg)) -> max_prof

df_prof %>% 
  ggplot(aes(Threshold, Profit_avg)) + 
  geom_line(color = "#00006E") + 
  geom_point(data = max_prof, color = "red", size = 3) + 
  geom_point(data = default_prof, color = "blue", size = 3) + 
  geom_text(data = max_prof %>% mutate(Profit_avg = 640000), family = my_font,  
            aes(label = "Threshold that\nmaximizes Profit"), size = 3.5) + 
  geom_text(data = default_prof %>% mutate(Profit_avg = 550000),
            aes(label = "Profit at\ndefault threshold"), size = 3.5, family = my_font) + 
  scale_y_continuous(labels = scales::dollar_format()) + 
  theme(text = element_text(family = my_font)) + 
  theme(plot.margin = unit(rep(0.8, 4), "cm")) + 
  labs(y = NULL, title = "Figure 2: Maximum Profit by Threshold for Random Forest")

LS0tDQp0aXRsZTogIkNvbXBhcmUgTWF4aW11bSBQcm9maXQgYmV0d2VlbiBSYW5kb20gRm9yZXN0IGFuZCBMb2dpc3RpYyAodGlkeW1vZGVscykiDQphdXRob3I6ICJOZ3V5ZW4gQ2hpIER1bmciDQpzdWJ0aXRsZTogIlIgTWFjaGluZSBMZWFybmluZyBTZXJpZXMiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICAjIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGhpZ2hsaWdodDogemVuYnVybg0KICAgICMgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0aGVtZTogImZsYXRseSINCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCi0tLQ0KDQpgYGB7ciBzZXR1cCxpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSA2KQ0KYGBgDQoNCg0KDQojIEludHJvZHVjdGlvbg0KDQpJIHdpbGwgd3JpdGUgc29tZXRoaW5nIGxhdGVyLi4uDQoNCg0KYGBge3J9DQojIGh0dHBzOi8vZ2l0aHViLmNvbS90aWR5bW9kZWxzL3RoZW1pcw0KIyBodHRwczovL3d3dy50aWR5bW9kZWxzLm9yZy8NCiMgaHR0cHM6Ly93d3cudG13ci5vcmcvDQojIGh0dHBzOi8vanVsaWFzaWxnZS5jb20vYmxvZy94Z2Jvb3N0LXR1bmUtdm9sbGV5YmFsbC8NCg0KIyBDbGVhciBvdXIgd29ya3NwYWNlOiANCnJtKGxpc3QgPSBscygpKQ0KDQojIExvYWQgaG1lcS5jc3YgZGF0YXNldDogDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmhtZXEgPC0gcmVhZF9jc3YoImh0dHA6Ly93d3cuY3JlZGl0cmlza2FuYWx5dGljcy5uZXQvdXBsb2Fkcy8xLzkvNS8xLzE5NTExNjAxL2htZXEuY3N2IikNCmhtZXEgJT4lIG5hLm9taXQoKSAtPiBobWVxX2Z1bGwNCg0KIyBSZWxhYmVsIGZvciBCQUQgYW5kIGNvbnZlcnQgdG8gZmFjdG9yIGZvciBjaGFyYWN0ZXIgY29sdW1uczogDQpobWVxX2Z1bGwgJT4lIA0KICBtdXRhdGUoQkFEID0gY2FzZV93aGVuKEJBRCA9PSAxIH4gIkJhZCIsIFRSVUUgfiAiR29vZCIpKSAlPiUgDQogIG11dGF0ZV9pZihpcy5jaGFyYWN0ZXIsIGFzLmZhY3RvcikgLT4gaG1lcV9mdWxsDQoNCiMgRGlzdHJpYnV0aW9uIG9mIEJBRDogDQoNCmxpYnJhcnkoa25pdHIpDQoNCmhtZXFfZnVsbCAlPiUgDQogIGdyb3VwX2J5KEJBRCkgJT4lIA0KICBjb3VudCgpICU+JSANCiAgdW5ncm91cCgpICU+JSANCiAgbXV0YXRlKHBlcmNlbnQgPSAxMDAqbiAvIHN1bShuKSkgJT4lIA0KICBtdXRhdGUocGVyY2VudCA9IHJvdW5kKHBlcmNlbnQsIDIpKSAtPiBkZl9kaXMNCg0KZGZfZGlzICU+JSANCiAga2FibGUoKQ0KDQoNCmxpYnJhcnkodGlkeW1vZGVscykNCg0Kc2V0LnNlZWQoMTIzKQ0KbWVtYmVyc19zcGxpdCA8LSBpbml0aWFsX3NwbGl0KGhtZXFfZnVsbCwgcHJvcCA9IDAuNywgc3RyYXRhID0gQkFEKQ0KbWVtYmVyc190cmFpbiA8LSB0cmFpbmluZyhtZW1iZXJzX3NwbGl0KQ0KbWVtYmVyc190ZXN0IDwtIHRlc3RpbmcobWVtYmVyc19zcGxpdCkNCg0Kc2V0LnNlZWQoMTIzKQ0KbWVtYmVyc19mb2xkcyA8LSB2Zm9sZF9jdihtZW1iZXJzX3RyYWluLCBzdHJhdGEgPSBCQUQsIHYgPSA1LCByZXBlYXRzID0gMykNCg0KbGlicmFyeShyZWNpcGVzKQ0KbGlicmFyeSh0aGVtaXMpDQoNCm1lbWJlcnNfcmVjIDwtIHJlY2lwZShCQUQgfiAuLCBkYXRhID0gbWVtYmVyc190cmFpbikgJT4lDQogIHN0ZXBfZHVtbXkoYWxsX25vbWluYWwoKSwgLUJBRCkgJT4lDQogIHN0ZXBfc21vdGUoQkFEKQ0KDQpnbG1fc3BlYyA8LSBsb2dpc3RpY19yZWcoKSAlPiUNCiAgc2V0X2VuZ2luZSgiZ2xtIikNCg0KcmZfc3BlYyA8LSByYW5kX2ZvcmVzdCh0cmVlcyA9IDEwMDApICU+JQ0KICBzZXRfbW9kZSgiY2xhc3NpZmljYXRpb24iKSAlPiUNCiAgc2V0X2VuZ2luZSgicmFuZ2VyIikNCg0KbWVtYmVyc193ZiA8LSB3b3JrZmxvdygpICU+JQ0KICBhZGRfcmVjaXBlKG1lbWJlcnNfcmVjKQ0KDQptZW1iZXJzX21ldHJpY3MgPC0gbWV0cmljX3NldChyb2NfYXVjLCBhY2N1cmFjeSwgc2Vuc2l0aXZpdHksIHNwZWNpZmljaXR5KQ0KDQpnbG1fcnMgPC0gbWVtYmVyc193ZiAlPiUNCiAgYWRkX21vZGVsKGdsbV9zcGVjKSAlPiUNCiAgZml0X3Jlc2FtcGxlcyhyZXNhbXBsZXMgPSBtZW1iZXJzX2ZvbGRzLA0KICAgICAgICAgICAgICAgIG1ldHJpY3MgPSBtZW1iZXJzX21ldHJpY3MsDQogICAgICAgICAgICAgICAgY29udHJvbCA9IGNvbnRyb2xfcmVzYW1wbGVzKHNhdmVfcHJlZCA9IFRSVUUpKQ0KDQpyZl9ycyA8LSBtZW1iZXJzX3dmICU+JQ0KICBhZGRfbW9kZWwocmZfc3BlYykgJT4lDQogIGZpdF9yZXNhbXBsZXMocmVzYW1wbGVzID0gbWVtYmVyc19mb2xkcywNCiAgICAgICAgICAgICAgICBtZXRyaWNzID0gbWVtYmVyc19tZXRyaWNzLA0KICAgICAgICAgICAgICAgIGNvbnRyb2wgPSBjb250cm9sX3Jlc2FtcGxlcyhzYXZlX3ByZWQgPSBUUlVFKSkNCg0KbWVtYmVyc19maW5hbF9nbG0gPC0gbWVtYmVyc193ZiAlPiUNCiAgYWRkX21vZGVsKGdsbV9zcGVjKSAlPiUNCiAgbGFzdF9maXQobWVtYmVyc19zcGxpdCkNCg0KY29sbGVjdF9wcmVkaWN0aW9ucyhtZW1iZXJzX2ZpbmFsX2dsbSkgLT4gZ2xtX3BkDQoNCm1lbWJlcnNfZmluYWxfcmYgPC0gbWVtYmVyc193ZiAlPiUNCiAgYWRkX21vZGVsKHJmX3NwZWMpICU+JQ0KICBsYXN0X2ZpdChtZW1iZXJzX3NwbGl0KQ0KDQpjb2xsZWN0X3ByZWRpY3Rpb25zKG1lbWJlcnNfZmluYWxfcmYpIC0+IHJmX3BkDQoNCmNhbGN1bGF0ZV9zb21lX21ldHJpY3MgPC0gZnVuY3Rpb24odGhyZXNob2xkX3NlbGVjdGVkLCBwZF9zZWxlY3RlZCkgew0KICANCiAgcGRfc2VsZWN0ZWQgJT4lIA0KICAgIG11dGF0ZShsYWJlbF9wcmVkaWN0ZWRfdGhyZXNob2xkID0gY2FzZV93aGVuKC5wcmVkX0JhZCA+PSB0aHJlc2hvbGRfc2VsZWN0ZWQgfiAiQmFkIiwgVFJVRSB+ICJHb29kIikpIC0+IGRmX3Jlc3VsdA0KICANCiAgc3VtKGRmX3Jlc3VsdCRsYWJlbF9wcmVkaWN0ZWRfdGhyZXNob2xkID09IGRmX3Jlc3VsdCRCQUQpIC8gbnJvdyhkZl9yZXN1bHQpIC0+IGFjY3VyYWN5X3RocmVzaG9sZA0KICANCiAgZGZfcmVzdWx0ICU+JSBmaWx0ZXIoQkFEID09ICJCYWQiKSAtPiBhY3R1YWxfaXNfZGllZA0KICANCiAgc3VtKGFjdHVhbF9pc19kaWVkJGxhYmVsX3ByZWRpY3RlZF90aHJlc2hvbGQgPT0gIkJhZCIpIC8gbnJvdyhhY3R1YWxfaXNfZGllZCkgLT4gc2Vuc2l0aXZpdHlfdGhyZXNob2xkDQogIA0KICBkZl9yZXN1bHQgJT4lIGZpbHRlcihCQUQgIT0gIkJhZCIpIC0+IGFjdHVhbF9pc19zdXJ2aXZlZA0KICANCiAgIyBDYWxjdWxhdGUgU3BlY2lmaWNpdHk6IA0KICBzdW0oYWN0dWFsX2lzX3N1cnZpdmVkJGxhYmVsX3ByZWRpY3RlZF90aHJlc2hvbGQgIT0gIkJhZCIpIC8gbnJvdyhhY3R1YWxfaXNfc3Vydml2ZWQpIC0+IHNwZWMgDQogIA0KICAjIFJlcG9ydCByZXN1bHRzIGluIGZvcm0gb2YgZGF0YSBmcmFtZTogDQogIGRhdGEuZnJhbWUoQWNjdXJhY3kgPSBhY2N1cmFjeV90aHJlc2hvbGQsIA0KICAgICAgICAgICAgIFNlbnNpdGl2aXR5ID0gc2Vuc2l0aXZpdHlfdGhyZXNob2xkLCANCiAgICAgICAgICAgICBTcGVjaWZpY2l0eSA9IHNwZWMsIA0KICAgICAgICAgICAgIEZQUiA9IDEgLSBzcGVjLCANCiAgICAgICAgICAgICAjIEFVQyA9IHJvY19hdWMsIA0KICAgICAgICAgICAgIFRocmVzaG9sZCA9IHRocmVzaG9sZF9zZWxlY3RlZCkgLT4gZGZfcmVwb3J0aW5nDQogIHJldHVybihkZl9yZXBvcnRpbmcpDQp9DQoNCnJhbmdlX3RocmVzaG9sZCA8LSBjKDAuNSwgc2VxKDAsIDEsIGxlbmd0aC5vdXQgPSA5OSkpDQoNCmxhcHBseShyYW5nZV90aHJlc2hvbGQsIGZ1bmN0aW9uKHgpIHtjYWxjdWxhdGVfc29tZV9tZXRyaWNzKHgsIHBkX3NlbGVjdGVkID0gZ2xtX3BkKX0pIC0+IGxpc3RfZ2xtDQpsYXBwbHkocmFuZ2VfdGhyZXNob2xkLCBmdW5jdGlvbih4KSB7Y2FsY3VsYXRlX3NvbWVfbWV0cmljcyh4LCBwZF9zZWxlY3RlZCA9IHJmX3BkKX0pIC0+IGxpc3RfcmYNCg0KZG8uY2FsbCgiYmluZF9yb3dzIiwgbGlzdF9nbG0pIC0+IGdsbV9yZXN1bHRzX3RocmVzDQpkby5jYWxsKCJiaW5kX3Jvd3MiLCBsaXN0X3JmKSAtPiByZl9yZXN1bHRzX3RocmVzDQoNCmJpbmRfcm93cyhnbG1fcmVzdWx0c190aHJlcyAlPiUgbXV0YXRlKE1vZGVsID0gIkdMTSIpLCANCiAgICAgICAgICByZl9yZXN1bHRzX3RocmVzICU+JSBtdXRhdGUoTW9kZWwgPSAiUkYiKSkgLT4gZGZfY29tcGFyZQ0KDQpkZl9jb21wYXJlICU+JSANCiAgZ2F0aGVyKE1ldHJpYywgVmFsdWUsIC1UaHJlc2hvbGQsIC1Nb2RlbCkgLT4gZGZfbG9uZw0KDQpsaWJyYXJ5KHBST0MpDQpnbG1fYXVjIDwtIHJvYyhjYXNlX3doZW4obWVtYmVyc190ZXN0JEJBRCA9PSAiQmFkIiB+IDEsIFRSVUUgfiAwKSwgZ2xtX3BkJC5wcmVkX0JhZCkkYXVjICU+JSByb3VuZCgzKQ0KcmZfYXVjIDwtIHJvYyhjYXNlX3doZW4obWVtYmVyc190ZXN0JEJBRCA9PSAiQmFkIiB+IDEsIFRSVUUgfiAwKSwgcmZfcGQkLnByZWRfQmFkKSRhdWMgJT4lIHJvdW5kKDMpDQoNCmxpYnJhcnkoZXh0cmFmb250KQ0KdGhlbWVfc2V0KHRoZW1lX21pbmltYWwoKSkNCm15X2ZvbnQgPC0gIlJvYm90byBDb25kZW5zZWQiDQoNCmRmX2xvbmcgJT4lIA0KICBnZ3Bsb3QoYWVzKFRocmVzaG9sZCwgVmFsdWUsIGNvbG9yID0gTW9kZWwpKSArIA0KICBnZW9tX2xpbmUoKSArIA0KICBnZW9tX3BvaW50KGRhdGEgPSBkZl9sb25nICU+JSBmaWx0ZXIoVGhyZXNob2xkID09IDAuNSkpICsgDQogIGZhY2V0X3dyYXAofiBNZXRyaWMpICsgDQogIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gbXlfZm9udCkpICsgDQogIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChyZXAoMC44LCA0KSwgImNtIikpICsgDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsgDQogIGxhYnMoc3VidGl0bGUgPSBwYXN0ZTAoIkFVQyBmb3IgR0xNID0gIiwgZ2xtX2F1YywgIiwgQVVDIGZvciBSRiA9ICIsIHJmX2F1YyksIA0KICAgICAgIHRpdGxlID0gIkZpZ3VyZSAxOiBNb2RlbCBQZXJmb3JtYW5jZSBieSBUaHJlc2hvbGQgYmFzZWQgb24gVGVzdCBEYXRhIiwgDQogICAgICAgeSA9IE5VTEwpDQoNCg0KIz09PT09PT09PT09PT09PT09PT09PT09PQ0KDQpjYWxjdWxhdGVfcHJvZml0IDwtIGZ1bmN0aW9uKHRocmVzaG9sZF9zZWxlY3RlZCwgcGRfc2VsZWN0ZWQpIHsNCiAgDQogIG1lbWJlcnNfdGVzdCAlPiUgDQogICAgYXNfdGliYmxlKCkgJT4lIA0KICAgIG11dGF0ZShwZCA9IHBkX3NlbGVjdGVkJC5wcmVkX0JhZCkgJT4lIA0KICAgIG11dGF0ZShwcmVkaWN0ZWQgPSBjYXNlX3doZW4ocGQgPj0gdGhyZXNob2xkX3NlbGVjdGVkIH4gIkJhZCIsIFRSVUUgfiAiR29vZCIpKSAtPiBkZl9zZWxlY3RlZA0KICANCiAgIyBTZXQgY29uZGl0aW9ucyBmb3IgY2FsY3VsYXRpbmcgYXZlcmFnZSBwcm9maXQgYXQgZ2l2ZW4gdGhyZXNob2xkOiANCiAgbiA8LSA1MA0KICByYXRlIDwtIDAuMDcNCiAgcHJvZml0X3NwYWNlIDwtIE5VTEwNCiAgDQogICMgQ2FsY3VsYXRlIG5ldCBwcm9maXQgZm9yIGVhY2ggc2FtcGxlIHJhbmRvbWx5IHNlbGVjdGVkIGZyb20gdGVzdCBkYXRhOg0KICANCiAgZm9yIChqIGluIDE6bikgew0KICAgIA0KICAgIHNldC5zZWVkKGopDQogICAgDQogICAgZGZfcmVzdWx0cyA8LSBkZl9zZWxlY3RlZCAlPiUgDQogICAgICBzYW1wbGVfZnJhYygwLjcpDQogICAgDQogICAgIyBQcm9maXQgZnJvbSB0cnVlIG5lZ2F0aXZlIGNhc2VzOiANCiAgICANCiAgICBkZl9yZXN1bHRzICU+JSANCiAgICAgIGZpbHRlcihwcmVkaWN0ZWQgPT0gIkdvb2QiLCBCQUQgPT0gIkdvb2QiKSAlPiUgDQogICAgICBtdXRhdGUocHJvZml0ID0gcmF0ZSpMT0FOKSAlPiUgDQogICAgICBwdWxsKHByb2ZpdCkgJT4lIA0KICAgICAgc3VtKCkgLT4gdG90YWxfcHJvZml0DQogICAgDQogICAgIyBMb3NzIGNhdXNlcyBmcm9tIGZhbHNlIG5lZ2F0aXZlIGNhc2VzOiANCiAgICANCiAgICBkZl9yZXN1bHRzICU+JSANCiAgICAgIGZpbHRlcihwcmVkaWN0ZWQgPT0gIkdvb2QiLCBCQUQgPT0gIkJhZCIpICU+JSANCiAgICAgIHB1bGwoTE9BTikgJT4lIA0KICAgICAgc3VtKCkgLT4gdG90YWxfbG9zcw0KICAgIA0KICAgICMgTmV0IHByb2ZpdDogDQogICAgbmV0X3Byb2ZpdCA8LSB0b3RhbF9wcm9maXQgLSB0b3RhbF9sb3NzDQogICAgcHJvZml0X3NwYWNlIDwtIGMocHJvZml0X3NwYWNlLCBuZXRfcHJvZml0KQ0KICAgIA0KICB9DQogIA0KICAjIEF2ZXJhZ2UgbmV0IHByb2ZpdCBhdCBnaXZlbiB0aHJlc2hvbGQ6IA0KICBkYXRhLmZyYW1lKFByb2ZpdF9hdmcgPSBtZWFuKHByb2ZpdF9zcGFjZSksIFRocmVzaG9sZCA9IHRocmVzaG9sZF9zZWxlY3RlZCkgLT4gZGZfcHJvZl90aHJlcw0KICByZXR1cm4oZGZfcHJvZl90aHJlcykNCiAgDQp9DQoNCmxhcHBseShyYW5nZV90aHJlc2hvbGQsIGZ1bmN0aW9uKHgpIHtjYWxjdWxhdGVfcHJvZml0KHRocmVzaG9sZF9zZWxlY3RlZCA9IHgsIHBkX3NlbGVjdGVkID0gcmZfcGQpfSkgLT4gbGlzdF9wcm9mDQoNCmRvLmNhbGwoImJpbmRfcm93cyIsIGxpc3RfcHJvZikgLT4gZGZfcHJvZg0KDQpkZl9wcm9mICU+JSBmaWx0ZXIoVGhyZXNob2xkID09IDAuNSkgLT4gZGVmYXVsdF9wcm9mDQoNCmRmX3Byb2YgJT4lIHNsaWNlKHdoaWNoLm1heChQcm9maXRfYXZnKSkgLT4gbWF4X3Byb2YNCg0KZGZfcHJvZiAlPiUgDQogIGdncGxvdChhZXMoVGhyZXNob2xkLCBQcm9maXRfYXZnKSkgKyANCiAgZ2VvbV9saW5lKGNvbG9yID0gIiMwMDAwNkUiKSArIA0KICBnZW9tX3BvaW50KGRhdGEgPSBtYXhfcHJvZiwgY29sb3IgPSAicmVkIiwgc2l6ZSA9IDMpICsgDQogIGdlb21fcG9pbnQoZGF0YSA9IGRlZmF1bHRfcHJvZiwgY29sb3IgPSAiYmx1ZSIsIHNpemUgPSAzKSArIA0KICBnZW9tX3RleHQoZGF0YSA9IG1heF9wcm9mICU+JSBtdXRhdGUoUHJvZml0X2F2ZyA9IDY0MDAwMCksIGZhbWlseSA9IG15X2ZvbnQsICANCiAgICAgICAgICAgIGFlcyhsYWJlbCA9ICJUaHJlc2hvbGQgdGhhdFxubWF4aW1pemVzIFByb2ZpdCIpLCBzaXplID0gMy41KSArIA0KICBnZW9tX3RleHQoZGF0YSA9IGRlZmF1bHRfcHJvZiAlPiUgbXV0YXRlKFByb2ZpdF9hdmcgPSA1NTAwMDApLA0KICAgICAgICAgICAgYWVzKGxhYmVsID0gIlByb2ZpdCBhdFxuZGVmYXVsdCB0aHJlc2hvbGQiKSwgc2l6ZSA9IDMuNSwgZmFtaWx5ID0gbXlfZm9udCkgKyANCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6ZG9sbGFyX2Zvcm1hdCgpKSArIA0KICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9IG15X2ZvbnQpKSArIA0KICB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQocmVwKDAuOCwgNCksICJjbSIpKSArIA0KICBsYWJzKHkgPSBOVUxMLCB0aXRsZSA9ICJGaWd1cmUgMjogTWF4aW11bSBQcm9maXQgYnkgVGhyZXNob2xkIGZvciBSYW5kb20gRm9yZXN0IikNCg0KbGFwcGx5KHJhbmdlX3RocmVzaG9sZCwgZnVuY3Rpb24oeCkge2NhbGN1bGF0ZV9wcm9maXQodGhyZXNob2xkX3NlbGVjdGVkID0geCwgcGRfc2VsZWN0ZWQgPSBnbG1fcGQpfSkgLT4gbGlzdF9wcm9mX2dsbQ0KDQpkby5jYWxsKCJiaW5kX3Jvd3MiLCBsaXN0X3Byb2ZfZ2xtKSAtPiBkZl9wcm9mX2dsbQ0KDQpkZl9wcm9mX2dsbSAlPiUgZmlsdGVyKFRocmVzaG9sZCA9PSAwLjUpIC0+IGRlZmF1bHRfcHJvZl9nbG0NCg0KZGZfcHJvZl9nbG0gJT4lIHNsaWNlKHdoaWNoLm1heChQcm9maXRfYXZnKSkgLT4gbWF4X3Byb2ZfZ2xtDQoNCmRmX3Byb2ZfZ2xtICU+JSANCiAgZ2dwbG90KGFlcyhUaHJlc2hvbGQsIFByb2ZpdF9hdmcpKSArIA0KICBnZW9tX2xpbmUoY29sb3IgPSAiIzAwMDA2RSIpICsgDQogIGdlb21fcG9pbnQoZGF0YSA9IG1heF9wcm9mX2dsbSwgY29sb3IgPSAicmVkIiwgc2l6ZSA9IDMpICsgDQogIGdlb21fcG9pbnQoZGF0YSA9IGRlZmF1bHRfcHJvZl9nbG0sIGNvbG9yID0gImJsdWUiLCBzaXplID0gMykgKyANCiAgZ2VvbV90ZXh0KGRhdGEgPSBtYXhfcHJvZl9nbG0gJT4lIG11dGF0ZShUaHJlc2hvbGQgPSAwLjUpLCBmYW1pbHkgPSBteV9mb250LCAgDQogICAgICAgICAgICBhZXMobGFiZWwgPSAiVGhyZXNob2xkIHRoYXRcbm1heGltaXplcyBQcm9maXQiKSwgc2l6ZSA9IDMuNSkgKyANCiAgZ2VvbV90ZXh0KGRhdGEgPSBkZWZhdWx0X3Byb2ZfZ2xtICU+JSBtdXRhdGUoUHJvZml0X2F2ZyA9IDI5MDAwMCksDQogICAgICAgICAgICBhZXMobGFiZWwgPSAiUHJvZml0IGF0XG5kZWZhdWx0IHRocmVzaG9sZCIpLCBzaXplID0gMy41LCBmYW1pbHkgPSBteV9mb250KSArIA0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpkb2xsYXJfZm9ybWF0KCkpICsgDQogIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gbXlfZm9udCkpICsgDQogIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChyZXAoMC44LCA0KSwgImNtIikpICsgDQogIGxhYnMoeSA9IE5VTEwsIHRpdGxlID0gIkZpZ3VyZSAzOiBNYXhpbXVtIFByb2ZpdCBieSBUaHJlc2hvbGQgZm9yIExvZ2lzdGljIikNCg0KDQpgYGANCg0K