
Introduction
One-year probability of default (PD) can be seen as a snapshot of lifetime PD. Two main reasons suggest treating one-year and lifetime separately. Firstly, banks have been developing one-year PD models over the last two decades for Basel II regulatory requirements. Secondly, a building-block-structure split in one-year and lifetime PD facilitates the learning process.
IFRS 9 and CECL accounting requirements lead banks to develop models by focusing on: point-intime (PIT), unbiased and forward-looking lifetime estimates. These requirements puzzle banks relying on the Basel II regulatory capital framework (BIS, 2006). Indeed, a one-year through-the-cycle (TTC) perspective informs Basel II capital requirement framework. Therefore new methodologies and information technology (IT) infrastructures are compulsory to align with new accounting requirements.
This chapter provides the foundation for PD model development and validation. A lifetime perspective is at the very heart of FASB (2016) accounting principle. On the other hand, IASB (2014) relies on one-year or lifetime expected losses, based on a significant increase in credit risk criteria. One may focus on one-year PDs as a snapshot of the lifetime PD term structure. Then, taking into account banks’ appetite to rely on a framework already put in place for Basel II and stress testing purposes, a split between one-year and lifetime PDs is considered beneficial for the entire learning journey.
In recent years, growing attention has been devoted to big data. Machine learning (ML) algorithms play a key role in this field. Classification and regression trees (CARTs), bagging, random forest, and boosting are studied both to challenge existing models, and explore new PD modelling solutions:
Examples and case studies are explored throughout the chapter by means of R software
# Load packages for modelling:
rm(list = ls())
library(lubridate)
library(tidyverse)
library(caret)
library(scorecard)
library(corrplot)
library(broom)
library(pROC)
#=================================
# Stage 1: Default Definition
#=================================
# Import data and create default_flag by definition described in section 2.2.1:
oneypd <- read.table("C:\\Users\\Zbook\\Desktop\\ifrs9_Bellini\\chap2\\oneyear.txt", sep = ",", header = TRUE) %>%
mutate(origination_date = ymd(origination_date), maturity_date = ymd(maturity_date), recent_arrears_date = ymd(recent_arrears_date)) %>%
mutate(default_flag = case_when(arrears_event == 1 | term_expiry_event == 1 | bankrupt_event == 1 ~ 1, TRUE ~ 0)) %>%
mutate(arrears_event = NULL, term_expiry_event = NULL, bankrupt_event = NULL, X = NULL, id = NULL)
# Some predictors for modelling:
# my_vars <- c("bureau_score", "cc_util", "num_ccj", "max_arrears_12m", "max_arrears_bal_6m",
# "emp_length", "months_since_recent_cc_delinq", "annual_income", "default_flag")
my_vars <- c("cc_util", "num_ccj", "max_arrears_12m", "max_arrears_bal_6m",
"emp_length", "months_since_recent_cc_delinq", "annual_income", "default_flag")
# Select predictors selected:
oneypd <- oneypd %>% select(my_vars)
# Split data:
set.seed(2122)
id <- createDataPartition(y = oneypd$default_flag, p = 0.7, list = FALSE)
train <- oneypd[id, ]
test <- oneypd[-id, ]
#==================================
# Stage 2: Univariate Analysis
#==================================
# WOE binning:
bins <- woebin(train, y = "default_flag", positive = "default_flag|1")
## [INFO] creating woe binning ...
## [INFO] converting into woe values ...

(Intercept) |
-2.920 |
0.053 |
-55.209 |
0.000 |
cc_util_woe |
-0.969 |
0.038 |
-25.447 |
0.000 |
max_arrears_12m_woe |
-0.772 |
0.075 |
-10.272 |
0.000 |
max_arrears_bal_6m_woe |
-0.277 |
0.082 |
-3.393 |
0.001 |
emp_length_woe |
-0.332 |
0.098 |
-3.395 |
0.001 |
months_since_recent_cc_delinq_woe |
-0.206 |
0.082 |
-2.505 |
0.012 |
annual_income_woe |
-0.879 |
0.059 |
-14.925 |
0.000 |
## [INFO] converting into woe values ...
pd_test <- predict(logit_stepwise, test_woe, type = "response") %>% as.vector()
# Function for scoring based on PD predicted:
scaled_score <- function(pd_selected) {
odds <- 72
my_offset <- 500
pdo <- 20
b <- pdo / log(2)
a <- my_offset - b*log(odds)
scores <- a + b*log((1 - pd_selected) / pd_selected)
return(round(scores, 0))
}
# Assign scorecard point:
scores <- scaled_score(pd_test)
#===========================================
# Stage 5: Evaluate Discriminatory Power
#===========================================
df_scored_test <- test %>%
select(default_flag) %>%
mutate(SCORE = scores) %>%
mutate(default_flag = case_when(default_flag == 1 ~ "Default", TRUE ~ "NonDefault"))
df_scored_test %>%
group_by(default_flag) %>%
summarise_each(funs(min, max, median, mean, n()), SCORE) %>%
mutate_if(is.numeric, function(x) {round(x, 0)}) %>%
knitr::kable(caption = "Table 1: Scorecad Points by Group for Test Data (Stepwise Logistic)")
Table 1: Scorecad Points by Group for Test Data (Stepwise Logistic)
Default |
295 |
572 |
406 |
405 |
416 |
NonDefault |
295 |
586 |
539 |
520 |
7355 |
df_scored_test %>%
group_by(default_flag) %>%
summarise(tb = mean(SCORE)) %>%
ungroup() -> mean_score_test
theme_set(theme_minimal())
g1 <- df_scored_test %>%
ggplot(aes(SCORE, color = default_flag, fill = default_flag)) +
geom_density(alpha = 0.3) +
geom_vline(aes(xintercept = mean_score_test$tb[1]), linetype = "dashed", color = "red") +
geom_vline(aes(xintercept = mean_score_test$tb[2]), linetype = "dashed", color = "blue") +
geom_text(aes(x = 390, y = 0.008, label = mean_score_test$tb[1] %>% round(0)), color = "red", size = 4) +
geom_text(aes(x = 545, y = 0.008, label = mean_score_test$tb[2] %>% round(0)), color = "blue", size = 4) +
theme(legend.title = element_blank()) +
theme(legend.position = c(0.2, 0.8)) +
theme(panel.grid = element_blank()) +
theme(axis.text.y = element_blank()) +
theme(plot.margin = unit(c(1.3, 1.3, 1.3, 1.3), "cm")) +
labs(x = "Scorecard Point", y = NULL,
title = "Figure 1: Scorecard Point Distribution by Group for Test Data, Stepwise Logistic Model",
subtitle = "The scorecard point is a numeric expression measuring creditworthiness. Commercial Banks\nusually utilize it as a method to support the decision-making about credit applications.")
g1

# Function calculates AUC:
auc_for_test <- function(pd) {
actual <- test$default_flag
return(roc(actual, pd))
}
# Use this function:
my_auc_logit <- auc_for_test(pd_test)
# Function for presenting AUC/ROC curve:
my_ROC_curve <- function(auc_object) {
sen_spec_df <- data_frame(TPR = auc_object$sensitivities,
FPR = 1 - auc_object$specificities)
sen_spec_df %>%
ggplot(aes(x = FPR, ymin = 0, ymax = TPR))+
geom_polygon(aes(y = TPR), fill = "red", alpha = 0.3)+
geom_path(aes(y = TPR), col = "firebrick", size = 1.2) +
geom_abline(intercept = 0, slope = 1, color = "gray37", size = 1, linetype = "dashed") +
scale_y_continuous(labels = scales::percent) +
scale_x_continuous(labels = scales::percent) +
theme_bw() +
coord_equal() %>%
return()
}
# ROC curve for Stepwise Logistic:
my_auc_logit %>%
my_ROC_curve() +
labs(x = "FPR (1 - Specificity)",
y = "TPR (Sensitivity)",
title = "Figure 2: Model Performance Based on Test Data",
subtitle = paste0("AUC Value for Stepwise Logistic: ", my_auc_logit$auc %>% round(3))) -> g2
g2

#========================================
# Compare with Random Forest Approach
#========================================
# Precessing Data for modelling:
df_train_ml <- train %>%
mutate_all(function(x) {replace_na(x, median(x, na.rm = TRUE))}) %>%
mutate(default_flag = case_when(default_flag == 1 ~ "Default", TRUE ~ "NonDefault") %>% as.factor())
df_test_ml <- test %>% mutate_all(function(x) {replace_na(x, median(x, na.rm = TRUE))})
# Conditions for training and evaluating RF model:
number <- 5
repeats <- 5
n <- number*repeats
set.seed(1)
train.control <- trainControl(method = "repeatedcv",
number = number,
repeats = repeats,
classProbs = TRUE,
allowParallel = TRUE,
summaryFunction = multiClassSummary)
# Train RF model:
set.seed(1)
my_rf <- train(default_flag ~.,
data = df_train_ml,
method = "ranger",
metric = "Accuracy",
trControl = train.control)
# PD from RF model:
pd_rf <- predict(my_rf, df_test_ml, type = "prob") %>% pull(Default)
# Assign scores:
scores_rf <- scaled_score(pd_rf)
scores_rf <- case_when(scores_rf == Inf ~ max(scores_rf[!is.infinite(scores_rf)]), TRUE ~ scores_rf)
df_scored_rf <- test %>%
select(default_flag) %>%
mutate(SCORE = scores_rf) %>%
mutate(default_flag = case_when(default_flag == 1 ~ "Default", TRUE ~ "NonDefault"))
df_scored_rf %>%
group_by(default_flag) %>%
summarise_each(funs(min, max, median, mean, n()), SCORE) %>%
mutate_if(is.numeric, function(x) {round(x, 0)}) %>%
knitr::kable(caption = "Table 2: Scorecad Points by Group for Test Data (RF Approach)")
Table 2: Scorecad Points by Group for Test Data (RF Approach)
Default |
282 |
566 |
396 |
401 |
416 |
NonDefault |
305 |
773 |
541 |
557 |
7355 |
df_scored_rf %>%
group_by(default_flag) %>%
summarise(tb = mean(SCORE)) %>%
ungroup() -> mean_score_test
df_scored_rf %>%
ggplot(aes(SCORE, color = default_flag, fill = default_flag)) +
geom_density(alpha = 0.3) +
geom_vline(aes(xintercept = mean_score_test$tb[1]), linetype = "dashed", color = "red") +
geom_vline(aes(xintercept = mean_score_test$tb[2]), linetype = "dashed", color = "blue") +
geom_text(aes(x = 415, y = 0.008, label = mean_score_test$tb[1] %>% round(0)), color = "red", size = 4) +
geom_text(aes(x = 535, y = 0.008, label = mean_score_test$tb[2] %>% round(0)), color = "blue", size = 4) +
theme(legend.title = element_blank()) +
theme(legend.position = c(0.7, 0.8)) +
theme(panel.grid = element_blank()) +
theme(axis.text.y = element_blank()) +
theme(plot.margin = unit(c(1.3, 1.3, 1.3, 1.3), "cm")) +
labs(x = "Scorecard Point", y = NULL,
title = "Figure 3: Scorecard Point Distribution by Group for Test Data, Random Forest Model",
subtitle = "The scorecard point is a numeric expression measuring creditworthiness. Commercial Banks\nusually utilize it as a method to support the decision-making about credit applications.")


LS0tDQp0aXRsZTogIklGUlMgOSBhbmQgQ0VDTCBDcmVkaXQgUmlzayBNb2RlbGxpbmcgYW5kIFZhbGlkYXRpb24gKENoYXB0ZXIgMikiIA0Kc3VidGl0bGU6ICJSIGZvciBQbGVhc3VyZSINCmF1dGhvcjogIk5ndXllbiBDaGkgRHVuZyINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgICMgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgaGlnaGxpZ2h0OiBweWdtZW50cw0KICAgICMgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0aGVtZTogImZsYXRseSINCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCi0tLQ0KDQpgYGB7ciBzZXR1cCxpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSkNCmBgYA0KDQoNCiFbXShDOlxcVXNlcnNcXFpib29rXFxEZXNrdG9wXFxpZnJzOV9CZWxsaW5pXFxjaGFwMl9wMS5KUEcpDQoNCiMgSW50cm9kdWN0aW9uDQoNCk9uZS15ZWFyIHByb2JhYmlsaXR5IG9mIGRlZmF1bHQgKFBEKSBjYW4gYmUgc2VlbiBhcyBhIHNuYXBzaG90IG9mIGxpZmV0aW1lIFBELiBUd28gbWFpbiByZWFzb25zIHN1Z2dlc3QgdHJlYXRpbmcgb25lLXllYXIgYW5kIGxpZmV0aW1lIHNlcGFyYXRlbHkuIEZpcnN0bHksIGJhbmtzIGhhdmUgYmVlbiBkZXZlbG9waW5nIG9uZS15ZWFyIFBEIG1vZGVscyBvdmVyIHRoZSBsYXN0IHR3byBkZWNhZGVzIGZvciBCYXNlbCBJSSByZWd1bGF0b3J5IHJlcXVpcmVtZW50cy4gU2Vjb25kbHksIGEgYnVpbGRpbmctYmxvY2stc3RydWN0dXJlIHNwbGl0IGluIG9uZS15ZWFyIGFuZCBsaWZldGltZSBQRCBmYWNpbGl0YXRlcyB0aGUgbGVhcm5pbmcgcHJvY2Vzcy4NCg0KSUZSUyA5IGFuZCBDRUNMIGFjY291bnRpbmcgcmVxdWlyZW1lbnRzIGxlYWQgYmFua3MgdG8gZGV2ZWxvcCBtb2RlbHMgYnkgZm9jdXNpbmcgb246IHBvaW50LWludGltZSAoUElUKSwgdW5iaWFzZWQgYW5kIGZvcndhcmQtbG9va2luZyBsaWZldGltZSBlc3RpbWF0ZXMuIFRoZXNlIHJlcXVpcmVtZW50cyBwdXp6bGUgYmFua3MgcmVseWluZyBvbiB0aGUgQmFzZWwgSUkgcmVndWxhdG9yeSBjYXBpdGFsIGZyYW1ld29yayAoQklTLCAyMDA2KS4gSW5kZWVkLCBhIG9uZS15ZWFyIHRocm91Z2gtdGhlLWN5Y2xlIChUVEMpIHBlcnNwZWN0aXZlIGluZm9ybXMgQmFzZWwgSUkgY2FwaXRhbCByZXF1aXJlbWVudCBmcmFtZXdvcmsuIFRoZXJlZm9yZSBuZXcgbWV0aG9kb2xvZ2llcyBhbmQgaW5mb3JtYXRpb24gdGVjaG5vbG9neSAoSVQpIGluZnJhc3RydWN0dXJlcyBhcmUgY29tcHVsc29yeSB0byBhbGlnbiB3aXRoIG5ldyBhY2NvdW50aW5nIHJlcXVpcmVtZW50cy4NCg0KVGhpcyBjaGFwdGVyIHByb3ZpZGVzIHRoZSBmb3VuZGF0aW9uIGZvciBQRCBtb2RlbCBkZXZlbG9wbWVudCBhbmQgdmFsaWRhdGlvbi4gQSBsaWZldGltZSBwZXJzcGVjdGl2ZSBpcyBhdCB0aGUgdmVyeSBoZWFydCBvZiBGQVNCICgyMDE2KSBhY2NvdW50aW5nIHByaW5jaXBsZS4gT24gdGhlIG90aGVyIGhhbmQsIElBU0IgKDIwMTQpIHJlbGllcyBvbiBvbmUteWVhciBvciBsaWZldGltZSBleHBlY3RlZCBsb3NzZXMsIGJhc2VkIG9uIGEgc2lnbmlmaWNhbnQgaW5jcmVhc2UgaW4gY3JlZGl0IHJpc2sgY3JpdGVyaWEuIE9uZSBtYXkgZm9jdXMgb24gb25lLXllYXIgUERzIGFzIGEgc25hcHNob3Qgb2YgdGhlIGxpZmV0aW1lIFBEIHRlcm0gc3RydWN0dXJlLiBUaGVuLCB0YWtpbmcgaW50byBhY2NvdW50IGJhbmtz4oCZIGFwcGV0aXRlIHRvIHJlbHkgb24gYSBmcmFtZXdvcmsgYWxyZWFkeSBwdXQgaW4gcGxhY2UgZm9yIEJhc2VsIElJIGFuZCBzdHJlc3MgdGVzdGluZyBwdXJwb3NlcywgYSBzcGxpdCBiZXR3ZWVuIG9uZS15ZWFyIGFuZCBsaWZldGltZSBQRHMgaXMgY29uc2lkZXJlZCBiZW5lZmljaWFsIGZvciB0aGUgZW50aXJlIGxlYXJuaW5nIGpvdXJuZXkuDQoNCkluIHJlY2VudCB5ZWFycywgZ3Jvd2luZyBhdHRlbnRpb24gaGFzIGJlZW4gZGV2b3RlZCB0byBiaWcgZGF0YS4gTWFjaGluZSBsZWFybmluZyAoTUwpIGFsZ29yaXRobXMgcGxheSBhIGtleSByb2xlIGluIHRoaXMgZmllbGQuIENsYXNzaWZpY2F0aW9uIGFuZCByZWdyZXNzaW9uIHRyZWVzIChDQVJUcyksIGJhZ2dpbmcsIHJhbmRvbSBmb3Jlc3QsIGFuZCBib29zdGluZyBhcmUgc3R1ZGllZCBib3RoIHRvIGNoYWxsZW5nZSBleGlzdGluZyBtb2RlbHMsIGFuZCBleHBsb3JlIG5ldyBQRCBtb2RlbGxpbmcgc29sdXRpb25zOiANCg0KIVtdKEM6XFxVc2Vyc1xcWmJvb2tcXERlc2t0b3BcXGlmcnM5X0JlbGxpbmlcXGNoYXAyX3AyLnBuZykNCkV4YW1wbGVzIGFuZCBjYXNlIHN0dWRpZXMgYXJlIGV4cGxvcmVkIHRocm91Z2hvdXQgdGhlIGNoYXB0ZXIgYnkgbWVhbnMgb2YgUiBzb2Z0d2FyZQ0KDQoNCmBgYHtyfQ0KDQojIExvYWQgcGFja2FnZXMgZm9yIG1vZGVsbGluZzogDQoNCnJtKGxpc3QgPSBscygpKQ0KbGlicmFyeShsdWJyaWRhdGUpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KHNjb3JlY2FyZCkNCmxpYnJhcnkoY29ycnBsb3QpDQpsaWJyYXJ5KGJyb29tKQ0KbGlicmFyeShwUk9DKSANCg0KDQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQojICAgU3RhZ2UgMTogRGVmYXVsdCBEZWZpbml0aW9uDQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCiMgSW1wb3J0IGRhdGEgYW5kIGNyZWF0ZSBkZWZhdWx0X2ZsYWcgYnkgZGVmaW5pdGlvbiBkZXNjcmliZWQgaW4gc2VjdGlvbiAyLjIuMTogDQpvbmV5cGQgPC0gcmVhZC50YWJsZSgiQzpcXFVzZXJzXFxaYm9va1xcRGVza3RvcFxcaWZyczlfQmVsbGluaVxcY2hhcDJcXG9uZXllYXIudHh0Iiwgc2VwID0gIiwiLCBoZWFkZXIgPSBUUlVFKSAlPiUgDQogIG11dGF0ZShvcmlnaW5hdGlvbl9kYXRlID0geW1kKG9yaWdpbmF0aW9uX2RhdGUpLCBtYXR1cml0eV9kYXRlID0geW1kKG1hdHVyaXR5X2RhdGUpLCByZWNlbnRfYXJyZWFyc19kYXRlID0geW1kKHJlY2VudF9hcnJlYXJzX2RhdGUpKSAlPiUgDQogIG11dGF0ZShkZWZhdWx0X2ZsYWcgPSBjYXNlX3doZW4oYXJyZWFyc19ldmVudCA9PSAxIHwgdGVybV9leHBpcnlfZXZlbnQgPT0gMSB8IGJhbmtydXB0X2V2ZW50ID09IDEgfiAxLCBUUlVFIH4gMCkpICU+JSANCiAgbXV0YXRlKGFycmVhcnNfZXZlbnQgPSBOVUxMLCB0ZXJtX2V4cGlyeV9ldmVudCA9IE5VTEwsIGJhbmtydXB0X2V2ZW50ID0gTlVMTCwgWCA9IE5VTEwsIGlkID0gTlVMTCkNCg0KIyBTb21lIHByZWRpY3RvcnMgZm9yIG1vZGVsbGluZzogDQoNCiMgbXlfdmFycyA8LSBjKCJidXJlYXVfc2NvcmUiLCAiY2NfdXRpbCIsICJudW1fY2NqIiwgIm1heF9hcnJlYXJzXzEybSIsICJtYXhfYXJyZWFyc19iYWxfNm0iLCANCiMgICAgICAgICAgICAgICJlbXBfbGVuZ3RoIiwgIm1vbnRoc19zaW5jZV9yZWNlbnRfY2NfZGVsaW5xIiwgImFubnVhbF9pbmNvbWUiLCAiZGVmYXVsdF9mbGFnIikNCg0KDQpteV92YXJzIDwtIGMoImNjX3V0aWwiLCAibnVtX2NjaiIsICJtYXhfYXJyZWFyc18xMm0iLCAibWF4X2FycmVhcnNfYmFsXzZtIiwgDQogICAgICAgICAgICAgImVtcF9sZW5ndGgiLCAibW9udGhzX3NpbmNlX3JlY2VudF9jY19kZWxpbnEiLCAiYW5udWFsX2luY29tZSIsICJkZWZhdWx0X2ZsYWciKQ0KDQojIFNlbGVjdCBwcmVkaWN0b3JzIHNlbGVjdGVkOiANCm9uZXlwZCA8LSBvbmV5cGQgJT4lIHNlbGVjdChteV92YXJzKQ0KDQojIFNwbGl0IGRhdGE6IA0Kc2V0LnNlZWQoMjEyMikNCmlkIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oeSA9IG9uZXlwZCRkZWZhdWx0X2ZsYWcsIHAgPSAwLjcsIGxpc3QgPSBGQUxTRSkNCnRyYWluIDwtIG9uZXlwZFtpZCwgXQ0KdGVzdCA8LSBvbmV5cGRbLWlkLCBdDQoNCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQojICBTdGFnZSAyOiBVbml2YXJpYXRlIEFuYWx5c2lzDQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KDQojIFdPRSBiaW5uaW5nOiANCg0KDQpiaW5zIDwtIHdvZWJpbih0cmFpbiwgeSA9ICJkZWZhdWx0X2ZsYWciLCAgcG9zaXRpdmUgPSAiZGVmYXVsdF9mbGFnfDEiKQ0KDQpkby5jYWxsKCJyYmluZCIsIGJpbnMpICU+JSANCiAgZmlsdGVyKCFkdXBsaWNhdGVkKHZhcmlhYmxlKSkgJT4lIA0KICBmaWx0ZXIodG90YWxfaXYgPj0gMC4xKSAlPiUgDQogIHB1bGwodmFyaWFibGUpIC0+IHZhcl9JVl8wMQ0KDQojIFdPRSB0cmFuc2Zvcm1hdGlvbjogDQoNCnRyYWluX3dvZSA8LSB3b2ViaW5fcGx5KHRyYWluLCBiaW5zID0gYmlucykgJT4lIA0KICBhcy5kYXRhLmZyYW1lKCkgJT4lIA0KICBzZWxlY3QoZGVmYXVsdF9mbGFnLCBwYXN0ZSh2YXJfSVZfMDEsICJ3b2UiLCBzZXAgPSAiXyIpKQ0KDQpkZWZhdWx0X2ZsYWcgPC0gdHJhaW5fd29lJGRlZmF1bHRfZmxhZyANCg0KIyBDb3JyZWxhdGlvbnM6IA0KDQpteV9jb3JyIDwtIGNvcih0cmFpbl93b2UgJT4lIHNlbGVjdChjb250YWlucygid29lIikpKQ0KDQpteV9jb3JyICU+JQ0KICBjb3JycGxvdCguLCBtZXRob2QgPSAibnVtYmVyIiwgdGwuY2V4ID0gMC43KQ0KDQoNCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQojICBTdGFnZSAzOiBMb2dpc3RpYyBTdGVwd2lzZSBSZWdyZXNzaW9uDQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KDQp0cmFpbl93b2UgPC0gdHJhaW5fd29lICU+JSANCiAgc2VsZWN0KC1kZWZhdWx0X2ZsYWcpICU+JSANCiAgbXV0YXRlX2FsbChmdW5jdGlvbih4KSB7LXh9KSAlPiUgDQogIG11dGF0ZShkZWZhdWx0X2ZsYWcgPSBkZWZhdWx0X2ZsYWcpDQoNCmxvZ2l0X3N0ZXB3aXNlIDwtIGdsbShkZWZhdWx0X2ZsYWcgfi4sIGZhbWlseSA9ICJiaW5vbWlhbCIsIGRhdGEgPSB0cmFpbl93b2UpICU+JSANCiAgc3RlcCh0cmFjZSA9IDApDQoNCg0KdGlkeShsb2dpdF9zdGVwd2lzZSkgJT4lIA0KICBtdXRhdGVfaWYoaXMubnVtZXJpYywgZnVuY3Rpb24oeCkge3JvdW5kKHgsIDMpfSkgJT4lIA0KICBrbml0cjo6a2FibGUoKQ0KDQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQojICAgICBTdGFnZSA0OiBTY29yZSBmb3IgdGVzdCBkYXRhDQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCnRlc3Rfd29lIDwtIHdvZWJpbl9wbHkodGVzdCwgYmlucyA9IGJpbnMpICU+JSANCiAgYXMuZGF0YS5mcmFtZSgpICU+JSANCiAgc2VsZWN0KHBhc3RlKHZhcl9JVl8wMSwgIndvZSIsIHNlcCA9ICJfIikpICU+JSANCiAgbXV0YXRlX2FsbChmdW5jdGlvbih4KSB7LXh9KQ0KDQpwZF90ZXN0IDwtIHByZWRpY3QobG9naXRfc3RlcHdpc2UsIHRlc3Rfd29lLCB0eXBlID0gInJlc3BvbnNlIikgJT4lIGFzLnZlY3RvcigpDQoNCg0KIyBGdW5jdGlvbiBmb3Igc2NvcmluZyBiYXNlZCBvbiBQRCBwcmVkaWN0ZWQ6IA0KDQpzY2FsZWRfc2NvcmUgPC0gZnVuY3Rpb24ocGRfc2VsZWN0ZWQpIHsNCiAgDQogIG9kZHMgPC0gNzINCiAgbXlfb2Zmc2V0IDwtIDUwMA0KICBwZG8gPC0gMjANCiAgYiA8LSBwZG8gLyBsb2coMikNCiAgYSA8LSBteV9vZmZzZXQgLSBiKmxvZyhvZGRzKQ0KICANCiAgc2NvcmVzIDwtIGEgKyBiKmxvZygoMSAtIHBkX3NlbGVjdGVkKSAvIHBkX3NlbGVjdGVkKQ0KICByZXR1cm4ocm91bmQoc2NvcmVzLCAwKSkNCiAgDQp9DQoNCiMgQXNzaWduIHNjb3JlY2FyZCBwb2ludDogDQoNCnNjb3JlcyA8LSBzY2FsZWRfc2NvcmUocGRfdGVzdCkNCg0KDQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KIyAgU3RhZ2UgNTogRXZhbHVhdGUgRGlzY3JpbWluYXRvcnkgUG93ZXINCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCmRmX3Njb3JlZF90ZXN0IDwtIHRlc3QgJT4lDQogIHNlbGVjdChkZWZhdWx0X2ZsYWcpICU+JSANCiAgbXV0YXRlKFNDT1JFID0gc2NvcmVzKSAlPiUgDQogIG11dGF0ZShkZWZhdWx0X2ZsYWcgPSBjYXNlX3doZW4oZGVmYXVsdF9mbGFnID09IDEgfiAiRGVmYXVsdCIsIFRSVUUgfiAiTm9uRGVmYXVsdCIpKSANCg0KZGZfc2NvcmVkX3Rlc3QgJT4lIA0KICBncm91cF9ieShkZWZhdWx0X2ZsYWcpICU+JSANCiAgc3VtbWFyaXNlX2VhY2goZnVucyhtaW4sIG1heCwgbWVkaWFuLCBtZWFuLCBuKCkpLCBTQ09SRSkgJT4lIA0KICBtdXRhdGVfaWYoaXMubnVtZXJpYywgZnVuY3Rpb24oeCkge3JvdW5kKHgsIDApfSkgJT4lIA0KICBrbml0cjo6a2FibGUoY2FwdGlvbiA9ICJUYWJsZSAxOiBTY29yZWNhZCBQb2ludHMgYnkgR3JvdXAgZm9yIFRlc3QgRGF0YSAoU3RlcHdpc2UgTG9naXN0aWMpIikNCg0KDQpkZl9zY29yZWRfdGVzdCAlPiUgDQogIGdyb3VwX2J5KGRlZmF1bHRfZmxhZykgJT4lIA0KICBzdW1tYXJpc2UodGIgPSBtZWFuKFNDT1JFKSkgJT4lIA0KICB1bmdyb3VwKCkgLT4gbWVhbl9zY29yZV90ZXN0DQoNCnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKCkpDQoNCmcxIDwtIGRmX3Njb3JlZF90ZXN0ICU+JSANCiAgZ2dwbG90KGFlcyhTQ09SRSwgY29sb3IgPSBkZWZhdWx0X2ZsYWcsIGZpbGwgPSBkZWZhdWx0X2ZsYWcpKSArIA0KICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjMpICsgDQogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSBtZWFuX3Njb3JlX3Rlc3QkdGJbMV0pLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJyZWQiKSArIA0KICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gbWVhbl9zY29yZV90ZXN0JHRiWzJdKSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiYmx1ZSIpICsgDQogIGdlb21fdGV4dChhZXMoeCA9IDM5MCwgeSA9IDAuMDA4LCBsYWJlbCA9IG1lYW5fc2NvcmVfdGVzdCR0YlsxXSAlPiUgcm91bmQoMCkpLCBjb2xvciA9ICJyZWQiLCBzaXplID0gNCkgKyANCiAgZ2VvbV90ZXh0KGFlcyh4ID0gNTQ1LCB5ID0gMC4wMDgsIGxhYmVsID0gbWVhbl9zY29yZV90ZXN0JHRiWzJdICU+JSByb3VuZCgwKSksIGNvbG9yID0gImJsdWUiLCBzaXplID0gNCkgKyANCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArIA0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuMiwgMC44KSkgKyANCiAgdGhlbWUocGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSkgKw0KICB0aGVtZShheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSkgKyANCiAgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMS4zLCAxLjMsIDEuMywgMS4zKSwgImNtIikpICsgDQogIGxhYnMoeCA9ICJTY29yZWNhcmQgUG9pbnQiLCB5ID0gTlVMTCwgDQogICAgICAgdGl0bGUgPSAiRmlndXJlIDE6IFNjb3JlY2FyZCBQb2ludCBEaXN0cmlidXRpb24gYnkgR3JvdXAgZm9yIFRlc3QgRGF0YSwgU3RlcHdpc2UgTG9naXN0aWMgTW9kZWwiLCANCiAgICAgICBzdWJ0aXRsZSA9ICJUaGUgc2NvcmVjYXJkIHBvaW50IGlzIGEgbnVtZXJpYyBleHByZXNzaW9uIG1lYXN1cmluZyBjcmVkaXR3b3J0aGluZXNzLiBDb21tZXJjaWFsIEJhbmtzXG51c3VhbGx5IHV0aWxpemUgaXQgYXMgYSBtZXRob2QgdG8gc3VwcG9ydCB0aGUgZGVjaXNpb24tbWFraW5nIGFib3V0IGNyZWRpdCBhcHBsaWNhdGlvbnMuIikNCg0KDQpnMQ0KDQojIEZ1bmN0aW9uIGNhbGN1bGF0ZXMgQVVDOiANCg0KYXVjX2Zvcl90ZXN0IDwtIGZ1bmN0aW9uKHBkKSB7DQogIGFjdHVhbCA8LSB0ZXN0JGRlZmF1bHRfZmxhZw0KICByZXR1cm4ocm9jKGFjdHVhbCwgcGQpKQ0KfQ0KDQojIFVzZSB0aGlzIGZ1bmN0aW9uOiANCm15X2F1Y19sb2dpdCA8LSBhdWNfZm9yX3Rlc3QocGRfdGVzdCkNCg0KIyBGdW5jdGlvbiBmb3IgcHJlc2VudGluZyBBVUMvUk9DIGN1cnZlOiANCg0KbXlfUk9DX2N1cnZlIDwtIGZ1bmN0aW9uKGF1Y19vYmplY3QpIHsNCiAgDQogIHNlbl9zcGVjX2RmIDwtIGRhdGFfZnJhbWUoVFBSID0gYXVjX29iamVjdCRzZW5zaXRpdml0aWVzLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBGUFIgPSAxIC0gYXVjX29iamVjdCRzcGVjaWZpY2l0aWVzKQ0KICANCiAgc2VuX3NwZWNfZGYgJT4lIA0KICAgIGdncGxvdChhZXMoeCA9IEZQUiwgeW1pbiA9IDAsIHltYXggPSBUUFIpKSsNCiAgICBnZW9tX3BvbHlnb24oYWVzKHkgPSBUUFIpLCBmaWxsID0gInJlZCIsIGFscGhhID0gMC4zKSsNCiAgICBnZW9tX3BhdGgoYWVzKHkgPSBUUFIpLCBjb2wgPSAiZmlyZWJyaWNrIiwgc2l6ZSA9IDEuMikgKw0KICAgIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgY29sb3IgPSAiZ3JheTM3Iiwgc2l6ZSA9IDEsIGxpbmV0eXBlID0gImRhc2hlZCIpICsNCiAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KSArIA0KICAgIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsgDQogICAgdGhlbWVfYncoKSArDQogICAgY29vcmRfZXF1YWwoKSAlPiUgDQogICAgcmV0dXJuKCkNCn0NCg0KDQojIFJPQyBjdXJ2ZSBmb3IgU3RlcHdpc2UgTG9naXN0aWM6IA0KDQpteV9hdWNfbG9naXQgJT4lIA0KICBteV9ST0NfY3VydmUoKSArIA0KICBsYWJzKHggPSAiRlBSICgxIC0gU3BlY2lmaWNpdHkpIiwgDQogICAgICAgeSA9ICJUUFIgKFNlbnNpdGl2aXR5KSIsIA0KICAgICAgIHRpdGxlID0gIkZpZ3VyZSAyOiBNb2RlbCBQZXJmb3JtYW5jZSBCYXNlZCBvbiBUZXN0IERhdGEiLCANCiAgICAgICBzdWJ0aXRsZSA9IHBhc3RlMCgiQVVDIFZhbHVlIGZvciBTdGVwd2lzZSBMb2dpc3RpYzogIiwgbXlfYXVjX2xvZ2l0JGF1YyAlPiUgcm91bmQoMykpKSAtPiBnMiANCg0KZzINCg0KDQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KIyAgQ29tcGFyZSB3aXRoIFJhbmRvbSBGb3Jlc3QgQXBwcm9hY2gNCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCiMgUHJlY2Vzc2luZyBEYXRhIGZvciBtb2RlbGxpbmc6IA0KDQpkZl90cmFpbl9tbCA8LSB0cmFpbiAlPiUgDQogIG11dGF0ZV9hbGwoZnVuY3Rpb24oeCkge3JlcGxhY2VfbmEoeCwgbWVkaWFuKHgsIG5hLnJtID0gVFJVRSkpfSkgJT4lIA0KICBtdXRhdGUoZGVmYXVsdF9mbGFnID0gY2FzZV93aGVuKGRlZmF1bHRfZmxhZyA9PSAxIH4gIkRlZmF1bHQiLCBUUlVFIH4gIk5vbkRlZmF1bHQiKSAlPiUgYXMuZmFjdG9yKCkpDQoNCmRmX3Rlc3RfbWwgPC0gdGVzdCAlPiUgbXV0YXRlX2FsbChmdW5jdGlvbih4KSB7cmVwbGFjZV9uYSh4LCBtZWRpYW4oeCwgbmEucm0gPSBUUlVFKSl9KQ0KDQojIENvbmRpdGlvbnMgZm9yIHRyYWluaW5nIGFuZCBldmFsdWF0aW5nIFJGIG1vZGVsOiANCm51bWJlciA8LSA1DQpyZXBlYXRzIDwtIDUNCm4gPC0gbnVtYmVyKnJlcGVhdHMNCg0Kc2V0LnNlZWQoMSkNCnRyYWluLmNvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1iZXIgPSBudW1iZXIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXBlYXRzID0gcmVwZWF0cywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFzc1Byb2JzID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsbG93UGFyYWxsZWwgPSBUUlVFLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1bW1hcnlGdW5jdGlvbiA9IG11bHRpQ2xhc3NTdW1tYXJ5KQ0KDQoNCiMgVHJhaW4gUkYgbW9kZWw6IA0Kc2V0LnNlZWQoMSkNCm15X3JmIDwtIHRyYWluKGRlZmF1bHRfZmxhZyB+LiwgDQogICAgICAgICAgICAgICBkYXRhID0gZGZfdHJhaW5fbWwsIA0KICAgICAgICAgICAgICAgbWV0aG9kID0gInJhbmdlciIsIA0KICAgICAgICAgICAgICAgbWV0cmljID0gIkFjY3VyYWN5IiwgDQogICAgICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbi5jb250cm9sKQ0KDQoNCiMgUEQgZnJvbSBSRiBtb2RlbDogDQoNCnBkX3JmIDwtIHByZWRpY3QobXlfcmYsIGRmX3Rlc3RfbWwsIHR5cGUgPSAicHJvYiIpICU+JSBwdWxsKERlZmF1bHQpDQoNCg0KIyBBc3NpZ24gc2NvcmVzOiANCnNjb3Jlc19yZiA8LSBzY2FsZWRfc2NvcmUocGRfcmYpDQpzY29yZXNfcmYgPC0gY2FzZV93aGVuKHNjb3Jlc19yZiA9PSBJbmYgfiBtYXgoc2NvcmVzX3JmWyFpcy5pbmZpbml0ZShzY29yZXNfcmYpXSksIFRSVUUgfiBzY29yZXNfcmYpDQoNCg0KZGZfc2NvcmVkX3JmIDwtIHRlc3QgJT4lDQogIHNlbGVjdChkZWZhdWx0X2ZsYWcpICU+JSANCiAgbXV0YXRlKFNDT1JFID0gc2NvcmVzX3JmKSAlPiUgDQogIG11dGF0ZShkZWZhdWx0X2ZsYWcgPSBjYXNlX3doZW4oZGVmYXVsdF9mbGFnID09IDEgfiAiRGVmYXVsdCIsIFRSVUUgfiAiTm9uRGVmYXVsdCIpKSANCg0KZGZfc2NvcmVkX3JmICU+JSANCiAgZ3JvdXBfYnkoZGVmYXVsdF9mbGFnKSAlPiUgDQogIHN1bW1hcmlzZV9lYWNoKGZ1bnMobWluLCBtYXgsIG1lZGlhbiwgbWVhbiwgbigpKSwgU0NPUkUpICU+JSANCiAgbXV0YXRlX2lmKGlzLm51bWVyaWMsIGZ1bmN0aW9uKHgpIHtyb3VuZCh4LCAwKX0pICU+JSANCiAga25pdHI6OmthYmxlKGNhcHRpb24gPSAiVGFibGUgMjogU2NvcmVjYWQgUG9pbnRzIGJ5IEdyb3VwIGZvciBUZXN0IERhdGEgKFJGIEFwcHJvYWNoKSIpDQoNCg0KZGZfc2NvcmVkX3JmICU+JSANCiAgZ3JvdXBfYnkoZGVmYXVsdF9mbGFnKSAlPiUgDQogIHN1bW1hcmlzZSh0YiA9IG1lYW4oU0NPUkUpKSAlPiUgDQogIHVuZ3JvdXAoKSAtPiBtZWFuX3Njb3JlX3Rlc3QNCg0KDQoNCmRmX3Njb3JlZF9yZiAlPiUgDQogIGdncGxvdChhZXMoU0NPUkUsIGNvbG9yID0gZGVmYXVsdF9mbGFnLCBmaWxsID0gZGVmYXVsdF9mbGFnKSkgKyANCiAgZ2VvbV9kZW5zaXR5KGFscGhhID0gMC4zKSArIA0KICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gbWVhbl9zY29yZV90ZXN0JHRiWzFdKSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAicmVkIikgKyANCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdCA9IG1lYW5fc2NvcmVfdGVzdCR0YlsyXSksIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImJsdWUiKSArIA0KICBnZW9tX3RleHQoYWVzKHggPSA0MTUsIHkgPSAwLjAwOCwgbGFiZWwgPSBtZWFuX3Njb3JlX3Rlc3QkdGJbMV0gJT4lIHJvdW5kKDApKSwgY29sb3IgPSAicmVkIiwgc2l6ZSA9IDQpICsgDQogIGdlb21fdGV4dChhZXMoeCA9IDUzNSwgeSA9IDAuMDA4LCBsYWJlbCA9IG1lYW5fc2NvcmVfdGVzdCR0YlsyXSAlPiUgcm91bmQoMCkpLCBjb2xvciA9ICJibHVlIiwgc2l6ZSA9IDQpICsgDQogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKyANCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjcsIDAuOCkpICsgDQogIHRoZW1lKHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCkpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCkpICsgDQogIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKDEuMywgMS4zLCAxLjMsIDEuMyksICJjbSIpKSArIA0KICBsYWJzKHggPSAiU2NvcmVjYXJkIFBvaW50IiwgeSA9IE5VTEwsIA0KICAgICAgIHRpdGxlID0gIkZpZ3VyZSAzOiBTY29yZWNhcmQgUG9pbnQgRGlzdHJpYnV0aW9uIGJ5IEdyb3VwIGZvciBUZXN0IERhdGEsIFJhbmRvbSBGb3Jlc3QgTW9kZWwiLCANCiAgICAgICBzdWJ0aXRsZSA9ICJUaGUgc2NvcmVjYXJkIHBvaW50IGlzIGEgbnVtZXJpYyBleHByZXNzaW9uIG1lYXN1cmluZyBjcmVkaXR3b3J0aGluZXNzLiBDb21tZXJjaWFsIEJhbmtzXG51c3VhbGx5IHV0aWxpemUgaXQgYXMgYSBtZXRob2QgdG8gc3VwcG9ydCB0aGUgZGVjaXNpb24tbWFraW5nIGFib3V0IGNyZWRpdCBhcHBsaWNhdGlvbnMuIikNCg0KDQoNCnBkX3JmICU+JSANCiAgYXVjX2Zvcl90ZXN0KCkgLT4gbXlfYXVjX3JmDQoNCg0KbXlfYXVjX3JmICU+JSANCiAgbXlfUk9DX2N1cnZlKCkgKyANCiAgbGFicyh4ID0gIkZQUiAoMSAtIFNwZWNpZmljaXR5KSIsIA0KICAgICAgIHkgPSAiVFBSIChTZW5zaXRpdml0eSkiLCANCiAgICAgICB0aXRsZSA9ICJGaWd1cmUgNDogUk9DIEN1cnZlIEJhc2VkIG9uIFRlc3QgRGF0YSIsIA0KICAgICAgIHN1YnRpdGxlID0gcGFzdGUwKCJBVUMgVmFsdWUgZm9yIFJhbmRvbSBGb3Jlc3Q6ICIsIG15X2F1Y19yZiRhdWMgJT4lIHJvdW5kKDMpKSkNCg0KDQpgYGANCg0K