Problem statement
Predicting Coupon Redemption
Setup
library(plyr);library(dplyr)
library(caret)
library(tidyverse)
library(tidymodels)
library(lubridate)
library(skimr)
library(doParallel)
library(cluster)
library(factoextra)
source("functions.R")
ERD

Import
campaign <- read_csv('data/campaign_data.csv') %>%
mutate(start_date = as_date(x=start_date, format="%d/%m/%y", tz=""),
end_date = as_date(x=end_date, format="%d/%m/%y", tz="")) %>%
arrange(start_date) %>%
mutate(camp_duration = as.numeric(end_date - start_date),
camp_days_from_first_campaign = as.numeric(start_date - as_date("2012-08-12")),
dataset = ifelse(between(campaign_id,1,13) | between(campaign_id,26,30),"train","test"),
campaign_type = as_factor(campaign_type))
coupon_item_mapping <- read_csv('data/coupon_item_mapping.csv')
customer_demographics <- read_csv("data/customer_demographics.csv") %>%
mutate_at(c("age_range","marital_status","rented","family_size","no_of_children","income_bracket"), ~as.character(.)) %>%
mutate_if(is.character, ~replace(., is.na(.), 'Na'))
customer_transaction <- read_csv("data/customer_transaction_data.csv")
item <- read_csv("data/item_data.csv")
item <- recipe(~. , item) %>%
step_mutate(category = str_replace_all(category,"([[:punct:]]|\\s)","")) %>%
step_mutate(brand_orig = brand) %>%
step_other(brand, threshold = 0.005) %>%
step_mutate(cat_brand = paste(category, brand, sep="_")) %>%
step_string2factor(brand_type, category, brand) %>%
prep(item) %>%
bake(item)
train <- read_csv("data/train.csv")
test <- read_csv("data/test_QyjYwdj.csv")
profiles
customer_profile <- Fn_customer_profile(customer_transaction, "2012-08-12", "2013-07-03")
customer_profile <- customer_profile %>%
left_join(Fn_customer_tenure(customer_transaction), by="customer_id") %>%
left_join(Fn_custering(customer_profile), by="customer_id")
customer_coupon_fav <- Fn_customer_items_fav(customer_transaction, "2012-08-12", "2013-07-03")
item_profile <- Fn_item_profile(customer_transaction, "2012-08-12", "2013-07-03")
coupon <- Fn_coupon(item_profile)
coupon_items_cat_brand <- Fn_coupon_item_cat_brand(coupon)
coupon_top_items_by_spend <- Fn_coupon_top_items_by_spend(coupon)
coupon_top_items_by_cust <- Fn_coupon_top_items_by_cust(coupon)
coupon_top_items_by_visit <- Fn_coupon_top_items_by_visit(coupon)
coupon_top_items_by_price <- Fn_coupon_top_items_by_price(coupon)
Build train
train <- read_csv("data/train.csv") %>%
mutate(redemption_status = as_factor(if_else(redemption_status==1, "Yes", "No"))) %>%
#..campaign
left_join(campaign, by="campaign_id") %>%
left_join(Fn_campaign_profile(train), by="campaign_id") %>%
#..customer
left_join(customer_profile, by=c("customer_id")) %>%
left_join(customer_demographics, by="customer_id") %>%
left_join(customer_coupon_fav, by=c("customer_id", "coupon_id")) %>%
#..coupon
left_join(coupon_top_items_by_spend, by=c("coupon_id")) %>%
left_join(coupon_top_items_by_cust, by=c("coupon_id")) %>%
left_join(coupon_top_items_by_visit, by=c("coupon_id")) %>%
left_join(coupon_items_cat_brand, by=c("coupon_id")) %>%
left_join(coupon_top_items_by_price, by=c("coupon_id")) %>%
#..impute
mutate_if(is_character, ~replace(., is.na(.), 'Na')) %>%
mutate_if(is_character, ~as_factor(.)) %>%
mutate_if(is.numeric, ~replace(., is.na(.), 0))
Build test
test <- read_csv("data/test_QyjYwdj.csv") %>%
#..campaign
left_join(campaign, by="campaign_id") %>%
left_join(Fn_campaign_profile(test), by="campaign_id") %>%
#..customer
left_join(customer_profile, by=c("customer_id")) %>%
left_join(customer_demographics, by="customer_id") %>%
left_join(customer_coupon_fav, by=c("customer_id", "coupon_id")) %>%
#..coupon
left_join(coupon_top_items_by_spend, by=c("coupon_id")) %>%
left_join(coupon_top_items_by_cust, by=c("coupon_id")) %>%
left_join(coupon_top_items_by_visit, by=c("coupon_id")) %>%
left_join(coupon_items_cat_brand, by=c("coupon_id")) %>%
left_join(coupon_top_items_by_price, by=c("coupon_id")) %>%
#..impute
mutate_if(is_character, ~replace(., is.na(.), 'Na')) %>%
mutate_if(is_character, ~as_factor(.)) %>%
mutate_if(is.numeric, ~replace(., is.na(.), 0))
Outcome distribution
ggplot(train, aes(x=redemption_status)) +
geom_bar() +
geom_text(aes(label=scales::percent(..count../sum(..count..))),
stat="count",position=position_stack(),vjust=1, color="orange")

Preprocessing
train_recipe <- recipe(redemption_status ~ ., train) %>%
update_role(id, campaign_id, coupon_id, customer_id, new_role = "id variable") %>%
step_rm(start_date, end_date, dataset) %>%
step_num2factor(starts_with("cc_")) %>%
step_dummy(age_range, campaign_type, family_size, income_bracket, marital_status, no_of_children, rented, cluster, starts_with("cc_"), one_hot=TRUE) %>%
step_nzv(all_predictors()) %>%
step_center(all_numeric(),-all_predictors()) %>%
step_scale(all_numeric(),-all_predictors()) %>%
step_downsample(redemption_status, ratio = 5)
Modeling
fitControl <- trainControl(method = "cv",
number = 5,
classProbs = TRUE,
summaryFunction = twoClassSummary,
verboseIter = TRUE,
search = "random")
cl <- makeCluster(detectCores())
registerDoParallel(cl)
Modeling XGB
library(xgboost)
Attaching package: 㤼㸱xgboost㤼㸲
The following object is masked from 㤼㸱package:dplyr㤼㸲:
slice
parametersGrid <- expand.grid(eta = c(0.1),
colsample_bytree=c(1),
max_depth=c(10),
nrounds=c(500),
gamma=c(1),
min_child_weight=5,
subsample=1)
set.seed(1968)
xgb.fit <- train(train_recipe,
data=train,
method="xgbTree",
# tuneLength = 5,
tuneGrid=parametersGrid,
metric="ROC",
trControl=fitControl)
Preparing recipe
There are new levels in a factor: NA
Aggregating results
Fitting final model on full training set
There are new levels in a factor: NA
xgb.fit
eXtreme Gradient Boosting
78369 samples
137 predictor
2 classes: 'No', 'Yes'
Recipe steps: rm, num2factor, dummy, nzv, center, scale, downsample
Resampling: Cross-Validated (5 fold)
Summary of sample sizes: 62695, 62695, 62695, 62695, 62696
Resampling results:
ROC Sens Spec
0.979255 0.9570711 0.8546339
Tuning parameter 'nrounds' was held constant at a value of 500
Tuning parameter
parameter 'min_child_weight' was held constant at a value of 5
Tuning parameter
'subsample' was held constant at a value of 1
confusionMatrix(xgb.fit, norm = "none")
Cross-Validated (5 fold) Confusion Matrix
(entries are un-normalized aggregated counts)
Reference
Prediction No Yes
No 74307 106
Yes 3333 623
Accuracy (average) : 0.9561
Fn_var_imp(xgb.fit)
Modeling RF
library(ranger)
parametersGrid <- expand.grid(mtry = c(43), splitrule = c('extratrees'), min.node.size = c(6))
set.seed(1968)
rf.fit <- train(train_recipe,
data=train,
method="ranger",
importance="impurity",
# tuneLength = 10,
tuneGrid=parametersGrid,
metric="ROC",
trControl=fitControl)
rf.fit
confusionMatrix.train(rf.fit, norm="none")
Fn_var_imp(rf.fit)
Submission
test.pred.prob <- predict(xgb.fit, test, type = "prob")
submission <- bind_cols(id=test$id, redemption_status=test.pred.prob$Yes)
write_csv(submission,'sub7-xgb.csv')
LS0tDQp0aXRsZTogIkFuYWx5dGljcyBWaWRoeWEgfCBBbUV4cGVydCAyMDE5IOKAkyBNYWNoaW5lIExlYXJuaW5nIEhhY2thdGhvbiAtIENvdXBvbiByZWRlbXB0aW9uIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQphdXRob3I6IFJvbmVuIENvaGVuIC0gaHR0cDovL2xpbmtlZGluLmNvbS9pbi9yb25lbmNvemVuDQotLS0NCg0KIyMjIFByb2JsZW0gc3RhdGVtZW50DQpQcmVkaWN0aW5nIENvdXBvbiBSZWRlbXB0aW9uDQoNCiMjIyBTZXR1cA0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkocGx5cik7bGlicmFyeShkcGx5cikNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkodGlkeW1vZGVscykNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeShza2ltcikNCmxpYnJhcnkoZG9QYXJhbGxlbCkNCmxpYnJhcnkoY2x1c3RlcikNCmxpYnJhcnkoZmFjdG9leHRyYSkNCg0Kc291cmNlKCJmdW5jdGlvbnMuUiIpDQpgYGANCg0KIyMjIEVSRA0KYGBge3IgRVJELCBlY2hvPUZBTFNFLCBvdXQud2lkdGggPSAnMTAwJSd9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiZXJkLnBuZyIpDQpgYGANCg0KIyMjIEltcG9ydCANCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpjYW1wYWlnbiA8LSByZWFkX2NzdignZGF0YS9jYW1wYWlnbl9kYXRhLmNzdicpICU+JSANCiAgbXV0YXRlKHN0YXJ0X2RhdGUgPSBhc19kYXRlKHg9c3RhcnRfZGF0ZSwgZm9ybWF0PSIlZC8lbS8leSIsIHR6PSIiKSwNCiAgICAgICAgIGVuZF9kYXRlID0gYXNfZGF0ZSh4PWVuZF9kYXRlLCBmb3JtYXQ9IiVkLyVtLyV5IiwgdHo9IiIpKSAlPiUgDQogICAgICAgICBhcnJhbmdlKHN0YXJ0X2RhdGUpICU+JSANCiAgbXV0YXRlKGNhbXBfZHVyYXRpb24gPSBhcy5udW1lcmljKGVuZF9kYXRlIC0gc3RhcnRfZGF0ZSksDQogICAgICAgICBjYW1wX2RheXNfZnJvbV9maXJzdF9jYW1wYWlnbiA9IGFzLm51bWVyaWMoc3RhcnRfZGF0ZSAtIGFzX2RhdGUoIjIwMTItMDgtMTIiKSksDQogICAgICAgICBkYXRhc2V0ID0gaWZlbHNlKGJldHdlZW4oY2FtcGFpZ25faWQsMSwxMykgfCBiZXR3ZWVuKGNhbXBhaWduX2lkLDI2LDMwKSwidHJhaW4iLCJ0ZXN0IiksDQogICAgICAgICBjYW1wYWlnbl90eXBlID0gYXNfZmFjdG9yKGNhbXBhaWduX3R5cGUpKQ0KDQpjb3Vwb25faXRlbV9tYXBwaW5nIDwtIHJlYWRfY3N2KCdkYXRhL2NvdXBvbl9pdGVtX21hcHBpbmcuY3N2JykNCg0KY3VzdG9tZXJfZGVtb2dyYXBoaWNzIDwtIHJlYWRfY3N2KCJkYXRhL2N1c3RvbWVyX2RlbW9ncmFwaGljcy5jc3YiKSAlPiUgDQogIG11dGF0ZV9hdChjKCJhZ2VfcmFuZ2UiLCJtYXJpdGFsX3N0YXR1cyIsInJlbnRlZCIsImZhbWlseV9zaXplIiwibm9fb2ZfY2hpbGRyZW4iLCJpbmNvbWVfYnJhY2tldCIpLCB+YXMuY2hhcmFjdGVyKC4pKSAlPiUNCiAgbXV0YXRlX2lmKGlzLmNoYXJhY3RlciwgfnJlcGxhY2UoLiwgaXMubmEoLiksICdOYScpKQ0KDQpjdXN0b21lcl90cmFuc2FjdGlvbiA8LSByZWFkX2NzdigiZGF0YS9jdXN0b21lcl90cmFuc2FjdGlvbl9kYXRhLmNzdiIpDQoNCml0ZW0gPC0gcmVhZF9jc3YoImRhdGEvaXRlbV9kYXRhLmNzdiIpIA0KaXRlbSA8LSByZWNpcGUofi4gLCBpdGVtKSAlPiUgDQogIHN0ZXBfbXV0YXRlKGNhdGVnb3J5ID0gc3RyX3JlcGxhY2VfYWxsKGNhdGVnb3J5LCIoW1s6cHVuY3Q6XV18XFxzKSIsIiIpKSAlPiUgDQogIHN0ZXBfbXV0YXRlKGJyYW5kX29yaWcgPSBicmFuZCkgJT4lIA0KICBzdGVwX290aGVyKGJyYW5kLCB0aHJlc2hvbGQgPSAwLjAwNSkgJT4lDQogIHN0ZXBfbXV0YXRlKGNhdF9icmFuZCA9IHBhc3RlKGNhdGVnb3J5LCBicmFuZCwgc2VwPSJfIikpICU+JSANCiAgc3RlcF9zdHJpbmcyZmFjdG9yKGJyYW5kX3R5cGUsIGNhdGVnb3J5LCBicmFuZCkgJT4lIA0KICBwcmVwKGl0ZW0pICU+JSANCiAgYmFrZShpdGVtKQ0KDQp0cmFpbiA8LSByZWFkX2NzdigiZGF0YS90cmFpbi5jc3YiKQ0KdGVzdCA8LSByZWFkX2NzdigiZGF0YS90ZXN0X1F5all3ZGouY3N2IikNCmBgYA0KDQojIyMgcHJvZmlsZXMNCmBgYHtyfQ0KY3VzdG9tZXJfcHJvZmlsZSA8LSBGbl9jdXN0b21lcl9wcm9maWxlKGN1c3RvbWVyX3RyYW5zYWN0aW9uLCAiMjAxMi0wOC0xMiIsICIyMDEzLTA3LTAzIikNCmN1c3RvbWVyX3Byb2ZpbGUgPC0gY3VzdG9tZXJfcHJvZmlsZSAlPiUgDQogIGxlZnRfam9pbihGbl9jdXN0b21lcl90ZW51cmUoY3VzdG9tZXJfdHJhbnNhY3Rpb24pLCBieT0iY3VzdG9tZXJfaWQiKSAlPiUgDQogIGxlZnRfam9pbihGbl9jdXN0ZXJpbmcoY3VzdG9tZXJfcHJvZmlsZSksIGJ5PSJjdXN0b21lcl9pZCIpDQoNCmN1c3RvbWVyX2NvdXBvbl9mYXYgPC0gRm5fY3VzdG9tZXJfaXRlbXNfZmF2KGN1c3RvbWVyX3RyYW5zYWN0aW9uLCAiMjAxMi0wOC0xMiIsICIyMDEzLTA3LTAzIikNCg0KaXRlbV9wcm9maWxlIDwtIEZuX2l0ZW1fcHJvZmlsZShjdXN0b21lcl90cmFuc2FjdGlvbiwgIjIwMTItMDgtMTIiLCAiMjAxMy0wNy0wMyIpDQpjb3Vwb24gPC0gRm5fY291cG9uKGl0ZW1fcHJvZmlsZSkNCmNvdXBvbl9pdGVtc19jYXRfYnJhbmQgPC0gRm5fY291cG9uX2l0ZW1fY2F0X2JyYW5kKGNvdXBvbikNCmNvdXBvbl90b3BfaXRlbXNfYnlfc3BlbmQgPC0gRm5fY291cG9uX3RvcF9pdGVtc19ieV9zcGVuZChjb3Vwb24pDQpjb3Vwb25fdG9wX2l0ZW1zX2J5X2N1c3QgPC0gRm5fY291cG9uX3RvcF9pdGVtc19ieV9jdXN0KGNvdXBvbikNCmNvdXBvbl90b3BfaXRlbXNfYnlfdmlzaXQgPC0gRm5fY291cG9uX3RvcF9pdGVtc19ieV92aXNpdChjb3Vwb24pDQpjb3Vwb25fdG9wX2l0ZW1zX2J5X3ByaWNlIDwtIEZuX2NvdXBvbl90b3BfaXRlbXNfYnlfcHJpY2UoY291cG9uKQ0KYGBgDQoNCiMjIyBCdWlsZCB0cmFpbg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCnRyYWluIDwtIHJlYWRfY3N2KCJkYXRhL3RyYWluLmNzdiIpICU+JSANCiAgbXV0YXRlKHJlZGVtcHRpb25fc3RhdHVzID0gYXNfZmFjdG9yKGlmX2Vsc2UocmVkZW1wdGlvbl9zdGF0dXM9PTEsICJZZXMiLCAiTm8iKSkpICU+JSAgDQogICMuLmNhbXBhaWduDQogIGxlZnRfam9pbihjYW1wYWlnbiwgYnk9ImNhbXBhaWduX2lkIikgJT4lDQogIGxlZnRfam9pbihGbl9jYW1wYWlnbl9wcm9maWxlKHRyYWluKSwgYnk9ImNhbXBhaWduX2lkIikgJT4lDQogICMuLmN1c3RvbWVyDQogIGxlZnRfam9pbihjdXN0b21lcl9wcm9maWxlLCBieT1jKCJjdXN0b21lcl9pZCIpKSAlPiUgDQogIGxlZnRfam9pbihjdXN0b21lcl9kZW1vZ3JhcGhpY3MsIGJ5PSJjdXN0b21lcl9pZCIpICU+JQ0KICBsZWZ0X2pvaW4oY3VzdG9tZXJfY291cG9uX2ZhdiwgYnk9YygiY3VzdG9tZXJfaWQiLCAiY291cG9uX2lkIikpICU+JQ0KICAjLi5jb3Vwb24NCiAgbGVmdF9qb2luKGNvdXBvbl90b3BfaXRlbXNfYnlfc3BlbmQsIGJ5PWMoImNvdXBvbl9pZCIpKSAlPiUgDQogIGxlZnRfam9pbihjb3Vwb25fdG9wX2l0ZW1zX2J5X2N1c3QsIGJ5PWMoImNvdXBvbl9pZCIpKSAlPiUNCiAgbGVmdF9qb2luKGNvdXBvbl90b3BfaXRlbXNfYnlfdmlzaXQsIGJ5PWMoImNvdXBvbl9pZCIpKSAlPiUgDQogIGxlZnRfam9pbihjb3Vwb25faXRlbXNfY2F0X2JyYW5kLCAgYnk9YygiY291cG9uX2lkIikpICU+JQ0KICBsZWZ0X2pvaW4oY291cG9uX3RvcF9pdGVtc19ieV9wcmljZSwgIGJ5PWMoImNvdXBvbl9pZCIpKSAlPiUgDQogICMuLmltcHV0ZQ0KICBtdXRhdGVfaWYoaXNfY2hhcmFjdGVyLCB+cmVwbGFjZSguLCBpcy5uYSguKSwgJ05hJykpICU+JSANCiAgbXV0YXRlX2lmKGlzX2NoYXJhY3RlciwgfmFzX2ZhY3RvciguKSkgJT4lIA0KICBtdXRhdGVfaWYoaXMubnVtZXJpYywgfnJlcGxhY2UoLiwgaXMubmEoLiksIDApKQ0KYGBgDQoNCiMjIyBCdWlsZCB0ZXN0DQpgYGB7ciBtZXNzYWdlPUZBTFNFfQ0KdGVzdCA8LSByZWFkX2NzdigiZGF0YS90ZXN0X1F5all3ZGouY3N2IikgJT4lIA0KICAjLi5jYW1wYWlnbg0KICBsZWZ0X2pvaW4oY2FtcGFpZ24sIGJ5PSJjYW1wYWlnbl9pZCIpICU+JQ0KICBsZWZ0X2pvaW4oRm5fY2FtcGFpZ25fcHJvZmlsZSh0ZXN0KSwgYnk9ImNhbXBhaWduX2lkIikgJT4lDQogICMuLmN1c3RvbWVyDQogIGxlZnRfam9pbihjdXN0b21lcl9wcm9maWxlLCBieT1jKCJjdXN0b21lcl9pZCIpKSAlPiUgDQogIGxlZnRfam9pbihjdXN0b21lcl9kZW1vZ3JhcGhpY3MsIGJ5PSJjdXN0b21lcl9pZCIpICU+JQ0KICBsZWZ0X2pvaW4oY3VzdG9tZXJfY291cG9uX2ZhdiwgYnk9YygiY3VzdG9tZXJfaWQiLCAiY291cG9uX2lkIikpICU+JSANCiAgIy4uY291cG9uDQogIGxlZnRfam9pbihjb3Vwb25fdG9wX2l0ZW1zX2J5X3NwZW5kLCBieT1jKCJjb3Vwb25faWQiKSkgJT4lIA0KICBsZWZ0X2pvaW4oY291cG9uX3RvcF9pdGVtc19ieV9jdXN0LCBieT1jKCJjb3Vwb25faWQiKSkgJT4lDQogIGxlZnRfam9pbihjb3Vwb25fdG9wX2l0ZW1zX2J5X3Zpc2l0LCBieT1jKCJjb3Vwb25faWQiKSkgJT4lIA0KICBsZWZ0X2pvaW4oY291cG9uX2l0ZW1zX2NhdF9icmFuZCwgIGJ5PWMoImNvdXBvbl9pZCIpKSAlPiUNCiAgbGVmdF9qb2luKGNvdXBvbl90b3BfaXRlbXNfYnlfcHJpY2UsICBieT1jKCJjb3Vwb25faWQiKSkgJT4lIA0KICAjLi5pbXB1dGUNCiAgbXV0YXRlX2lmKGlzX2NoYXJhY3RlciwgfnJlcGxhY2UoLiwgaXMubmEoLiksICdOYScpKSAlPiUgDQogIG11dGF0ZV9pZihpc19jaGFyYWN0ZXIsIH5hc19mYWN0b3IoLikpICU+JSANCiAgbXV0YXRlX2lmKGlzLm51bWVyaWMsIH5yZXBsYWNlKC4sIGlzLm5hKC4pLCAwKSkNCmBgYA0KDQojIyMgT3V0Y29tZSBkaXN0cmlidXRpb24NCmBgYHtyfQ0KZ2dwbG90KHRyYWluLCBhZXMoeD1yZWRlbXB0aW9uX3N0YXR1cykpICsNCiAgZ2VvbV9iYXIoKSArDQogIGdlb21fdGV4dChhZXMobGFiZWw9c2NhbGVzOjpwZXJjZW50KC4uY291bnQuLi9zdW0oLi5jb3VudC4uKSkpLA0KICAgICAgICAgICAgc3RhdD0iY291bnQiLHBvc2l0aW9uPXBvc2l0aW9uX3N0YWNrKCksdmp1c3Q9MSwgY29sb3I9Im9yYW5nZSIpDQpgYGANCiMjIyBQcmVwcm9jZXNzaW5nDQpgYGB7cn0NCnRyYWluX3JlY2lwZSA8LSByZWNpcGUocmVkZW1wdGlvbl9zdGF0dXMgfiAuLCB0cmFpbikgJT4lIA0KICB1cGRhdGVfcm9sZShpZCwgY2FtcGFpZ25faWQsIGNvdXBvbl9pZCwgY3VzdG9tZXJfaWQsIG5ld19yb2xlID0gImlkIHZhcmlhYmxlIikgJT4lDQogIHN0ZXBfcm0oc3RhcnRfZGF0ZSwgZW5kX2RhdGUsIGRhdGFzZXQpICU+JQ0KICBzdGVwX251bTJmYWN0b3Ioc3RhcnRzX3dpdGgoImNjXyIpKSAlPiUgDQogIHN0ZXBfZHVtbXkoYWdlX3JhbmdlLCBjYW1wYWlnbl90eXBlLCBmYW1pbHlfc2l6ZSwgaW5jb21lX2JyYWNrZXQsIG1hcml0YWxfc3RhdHVzLCBub19vZl9jaGlsZHJlbiwgcmVudGVkLCBjbHVzdGVyLCBzdGFydHNfd2l0aCgiY2NfIiksIG9uZV9ob3Q9VFJVRSkgJT4lIA0KICBzdGVwX256dihhbGxfcHJlZGljdG9ycygpKSAlPiUgDQogIHN0ZXBfY2VudGVyKGFsbF9udW1lcmljKCksLWFsbF9wcmVkaWN0b3JzKCkpICU+JQ0KICBzdGVwX3NjYWxlKGFsbF9udW1lcmljKCksLWFsbF9wcmVkaWN0b3JzKCkpICU+JSANCiAgc3RlcF9kb3duc2FtcGxlKHJlZGVtcHRpb25fc3RhdHVzLCByYXRpbyA9IDUpDQpgYGANCg0KIyMjIE1vZGVsaW5nDQpgYGB7cn0NCmZpdENvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBudW1iZXIgPSA1LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgY2xhc3NQcm9icyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBzdW1tYXJ5RnVuY3Rpb24gPSB0d29DbGFzc1N1bW1hcnksDQogICAgICAgICAgICAgICAgICAgICAgICAgICB2ZXJib3NlSXRlciA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBzZWFyY2ggPSAicmFuZG9tIikNCg0KY2wgPC0gbWFrZUNsdXN0ZXIoZGV0ZWN0Q29yZXMoKSkNCnJlZ2lzdGVyRG9QYXJhbGxlbChjbCkNCmBgYA0KDQojIyMgTW9kZWxpbmcgWEdCDQpgYGB7cn0NCmxpYnJhcnkoeGdib29zdCkNCg0KcGFyYW1ldGVyc0dyaWQgPC0gIGV4cGFuZC5ncmlkKGV0YSA9IGMoMC4xKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xzYW1wbGVfYnl0cmVlPWMoMSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2RlcHRoPWMoMTApLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3VuZHM9Yyg1MDApLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdhbW1hPWMoMSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluX2NoaWxkX3dlaWdodD01LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1YnNhbXBsZT0xKQ0KDQpzZXQuc2VlZCgxOTY4KQ0KDQp4Z2IuZml0IDwtIHRyYWluKHRyYWluX3JlY2lwZSwNCiAgICAgICAgICAgICAgICBkYXRhPXRyYWluLA0KICAgICAgICAgICAgICAgIG1ldGhvZD0ieGdiVHJlZSIsIA0KICAgICAgICAgICAgICAgICMgdHVuZUxlbmd0aCA9IDUsDQogICAgICAgICAgICAgICAgdHVuZUdyaWQ9cGFyYW1ldGVyc0dyaWQsDQogICAgICAgICAgICAgICAgbWV0cmljPSJST0MiLA0KICAgICAgICAgICAgICAgIHRyQ29udHJvbD1maXRDb250cm9sKQ0KDQp4Z2IuZml0DQoNCmNvbmZ1c2lvbk1hdHJpeCh4Z2IuZml0LCBub3JtID0gIm5vbmUiKQ0KDQpGbl92YXJfaW1wKHhnYi5maXQpDQpgYGANCg0KIyMjIE1vZGVsaW5nIFJGDQpgYGB7cn0NCmxpYnJhcnkocmFuZ2VyKQ0KDQpwYXJhbWV0ZXJzR3JpZCA8LSAgZXhwYW5kLmdyaWQobXRyeSA9IGMoNDMpLCBzcGxpdHJ1bGUgPSBjKCdleHRyYXRyZWVzJyksIG1pbi5ub2RlLnNpemUgPSBjKDYpKQ0KDQpzZXQuc2VlZCgxOTY4KQ0KDQpyZi5maXQgPC0gdHJhaW4odHJhaW5fcmVjaXBlLCAgDQogICAgICAgICAgICAgICAgZGF0YT10cmFpbiwgDQogICAgICAgICAgICAgICAgbWV0aG9kPSJyYW5nZXIiLCANCiAgICAgICAgICAgICAgICBpbXBvcnRhbmNlPSJpbXB1cml0eSIsDQogICAgICAgICAgICAgICAgIyB0dW5lTGVuZ3RoID0gMTAsDQogICAgICAgICAgICAgICAgdHVuZUdyaWQ9cGFyYW1ldGVyc0dyaWQsDQogICAgICAgICAgICAgICAgbWV0cmljPSJST0MiLA0KICAgICAgICAgICAgICAgIHRyQ29udHJvbD1maXRDb250cm9sKQ0KDQpyZi5maXQNCg0KY29uZnVzaW9uTWF0cml4LnRyYWluKHJmLmZpdCwgbm9ybT0ibm9uZSIpDQoNCkZuX3Zhcl9pbXAocmYuZml0KQ0KYGBgDQoNCg0KIyMjIFN1Ym1pc3Npb24NCmBgYHtyfQ0KdGVzdC5wcmVkLnByb2IgPC0gcHJlZGljdCh4Z2IuZml0LCB0ZXN0LCB0eXBlID0gInByb2IiKQ0Kc3VibWlzc2lvbiA8LSBiaW5kX2NvbHMoaWQ9dGVzdCRpZCwgcmVkZW1wdGlvbl9zdGF0dXM9dGVzdC5wcmVkLnByb2IkWWVzKQ0Kd3JpdGVfY3N2KHN1Ym1pc3Npb24sJ3N1YjcteGdiLmNzdicpDQoNCmBgYA0KDQo=