Question 1

A company is considering using RFM segments to target its rollout. The mailing under consideration costs €0.50 to send; if customers respond, they spend on average €50, of which €15 is margin.

It conducted a test of customers in the file, ebeer_test.csv; the customers not in the test can be found in ebeer_rollout.csv. For this analysis, create 3 RFM groups (instead of 5 like we did in class). 

What is the average recency of the group that is most recent, i.e., the customers who purchased most recently? 

Provide your answer with one decimal separated by a dot, not a comma (e.g. 0.1).

ebeer_test$Rgroup <- quantcut(ebeer_test$R, q = 3)

Alternatively:

# Create Quantiles 
ntiles <- function(x, bins) {
  quantiles = seq(from=0, to = 1, length.out=bins+1)
  cut(ecdf(x)(x), breaks = quantiles, labels=F)
}
ebeer_test$Rgroup <- ntiles(ebeer_test$R, bins=3)  
recency_stats <- ebeer_test %>% 
  group_by(Rgroup) %>% 
  summarise(n = n(), 
            mean_R = mean(R), sd_R = sd(R), 
            resp_p = mean(respmail, na.rm=TRUE))
recency_stats %>% 
  kbl() %>%
  kable_styling()
Rgroup n mean_R sd_R resp_p
1 756 5.01 2.27 0.122
2 926 11.94 1.66 0.132
3 847 22.64 6.94 0.131
## The Average Recency of Group [2:10] is 5

Question 2

What is the response probability of that group? 

Provide your answer with two decimals separated by a dot, not a comma (e.g. 0.17).

## The Response Rate of Group [2:10] is 0.12

Question 3

Now do the full RFM analysis. 

How many RFM groups are there?

Provide your answer with zero decimals (e.g. 17).

The Code:

ntiles <- function(x, bins) {
  quantiles = seq(from=0, to = 1, length.out=bins+1)
  cut(ecdf(x)(x),breaks=quantiles, labels=F)
}

ebeer_test$Rgroup <- ntiles(ebeer_test$R, bins=3)  


dt = data.table(ebeer_test)
nbins = 3
dt[, RFgroup := paste0(as.character(Rgroup), as.character(ntiles(F, bins = nbins))), by = c('Rgroup')]
dt[, RFMgroup := paste0(as.character(RFgroup), as.character(ntiles(M, bins = nbins))), by = c('RFgroup')]

# put it back to data.frame
ebeer_test = data.frame(dt)

# change it to a factor variable
ebeer_test$RFMgroup<-as.factor(ebeer_test$RFMgroup)

Number of RFM Groups:

length(unique(ebeer_test$RFMgroup))
## [1] 27

Question 4

How many of these segments are profitable to send to? 

Provide your answer with zero decimals (e.g. 12).

Let’s calculate the response rates for each segment:

respRFM <- ebeer_test %>% 
  group_by(RFMgroup) %>% 
  summarise(n_resp= sum(respmail, na.rm = TRUE), 
            n_mail= sum(mailing, na.rm = TRUE)) %>% 
  mutate(resp_rate = n_resp/n_mail) %>% 
  arrange(desc(resp_rate))
head(respRFM) %>% 
  kbl() %>%
  kable_styling()
RFMgroup n_resp n_mail resp_rate
331 37 101 0.366
232 40 111 0.360
231 33 111 0.297
132 28 96 0.292
333 30 103 0.291
332 28 103 0.272

Breakeven Point:

c <- 0.5
m <- 15
brk <- c/m

How many to target?:

sum(respRFM$resp_rate >= brk)
## [1] 17
respRFM <- as.data.frame(respRFM)

bp <- barplot(respRFM[,4], 
              main="response by RFM group", 
              xlab="RFM Group", ylab="average response", xaxt="n")
axis(1, at = bp[,1], labels=respRFM[,1], cex.axis=0.7, las=2)

abline(h=brk)
text(85, brk, "breakeven", cex=1, pos=3, col="black")

Question 5

What fraction of total variation in responses is explained by the RFM model? 

Provide your answer with three decimals separated by a dot, not a comma (e.g. 0.127)

RFM_model <- lm(respmail ~ RFMgroup, data = ebeer_test)
round(summary(RFM_model)$r.squared,3)
## [1] 0.153

Question 6

How many customers in the roll-out sample should be targeted? 

Provide your answer with zero decimals (e.g. 12).

Let’s consider now the roll-out data. We need first to create the segments:

ntiles <- function(x, bins) {
  quantiles = seq(from=0, to = 1, length.out=bins+1)
  cut(ecdf(x)(x),breaks=quantiles, labels=F)
}

ebeer_rollout$Rgroup <- ntiles(ebeer_rollout$R, bins=3)  


dt = data.table(ebeer_rollout)
nbins = 3
dt[, RFgroup := paste0(as.character(Rgroup), as.character(ntiles(F, bins = nbins))), by = c('Rgroup')]
dt[, RFMgroup := paste0(as.character(RFgroup), as.character(ntiles(M, bins = nbins))), by = c('RFgroup')]

# put it back to data.frame
ebeer_rollout = data.frame(dt)

# change it to a factor variable
ebeer_rollout$RFMgroup<-as.factor(ebeer_rollout$RFMgroup)

We want to predict their response rates:

ebeer_rollout$RFMpred <- predict(RFM_model, ebeer_rollout)
sum(ebeer_rollout$RFMpred >= brk)
## [1] 1564
rollout_respRFM <- ebeer_rollout %>% 
  group_by(RFMgroup) %>% 
  summarise(n_resp= sum(respmail, na.rm = TRUE), 
            n_mail= sum(mailing, na.rm = TRUE)) %>% 
  mutate(resp_rate = n_resp/n_mail) %>% 
  arrange(desc(resp_rate))
rollout_respRFM <- as.data.frame(rollout_respRFM)

bp <- barplot(rollout_respRFM[,4], 
              main="response by RFM group", 
              xlab="RFM Group", ylab="average response", xaxt="n")
axis(1, at = bp[,1], labels=rollout_respRFM[,1], cex.axis=0.7, las=2)

abline(h=brk)
text(85, brk, "breakeven", cex=1, pos=3, col="black")

Question 7

What is the return on investment of rolling out to those segments? 

Provide your answer with zero decimals without the percent sign (e.g. 120).

Profit per Consumer:

ebeer_rollout <- ebeer_rollout %>% 
    mutate(RFMprofit = case_when(RFMpred >= brk ~ RFMpred*m-c, TRUE ~ 0))

Sum of Profits:

sum_profit <- sum(ebeer_rollout$RFMprofit)
sum_profit
## [1] 3592

Costs per Consumer:

ebeer_rollout <- ebeer_rollout %>% 
    mutate(RFMcost = case_when(RFMpred >= brk ~ c, TRUE ~ 0))

Sum of Costs:

sum_cost <- sum(ebeer_rollout$RFMcost)
sum_cost 
## [1] 782

ROI:

## The ROI is 459 %

Question 8

Use a beta-binomial model to shrink the segment estimates in the test data.  

How many extra segments would you target using this method that you wouldn’t otherwise target?

Provide your answer with zero decimals (e.g. 12).

First, we need the non-response data:

respRFM <- respRFM %>% 
  mutate(n_nonresp = n_mail-n_resp) %>% 
  relocate(n_nonresp, .after=n_resp)

Likelihood Function to fit the data and find prior response rate:

fit <- vglm(cbind(respRFM$n_resp,respRFM$n_nonresp) ~ 1, betabinomialff, trace=TRUE)
## VGLM    linear loop  1 :  loglikelihood = -87.958729
## VGLM    linear loop  2 :  loglikelihood = -87.944974
## VGLM    linear loop  3 :  loglikelihood = -87.944815
## VGLM    linear loop  4 :  loglikelihood = -87.944812
## VGLM    linear loop  5 :  loglikelihood = -87.944812
a <- Coef(fit)[[1]]
b <- Coef(fit)[[2]]
## (a,b)=( 0.388 , 3.11 )

We know can estimate the posterior response rates:

post_mean_resp <- (a+respRFM$n_resp)/(a+b+respRFM$n_mail)
respRFM <- cbind(respRFM, post_mean_resp)
plot(respRFM$resp_rate, xaxt="n",col="red",xlab="RFM segments",ylab="response rate and posterior mean response rate")
points(respRFM$post_mean_resp, col='blue')
legend('topleft',legend=c("estimate response rate", "posterior expected response rate"),col=c("red","blue"), pch=1)
axis(1, at = 1:27, labels=respRFM$RFMgroup, cex.axis=0.7, las=2)
abline(h=brk)
text(25, brk, "breakeven", cex=1, pos=3, col="black")

In the end, how many extra segments should we target?

sum(respRFM$post_mean_resp >= brk) - sum(respRFM$resp_rate >= brk)
## [1] 1
LS0tDQp0aXRsZTogIlF1aXogMjogUkZNIEFuYWx5c2lzIg0KYXV0aG9yOiAiRGFuaWVsIFJlZGVsIg0KZGF0ZTogIjIwMjMtMDEtMjQiDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KICAgIGNvZGVfZG93bmxvYWQ6IFRSVUUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCnJtKGxpc3QgPSBscygpKQ0KDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoZGF0YS50YWJsZSkNCmxpYnJhcnkoa2FibGVFeHRyYSkNCmxpYnJhcnkoZ3Rvb2xzKQ0KbGlicmFyeShWR0FNKQ0KbGlicmFyeShyZWFkcikNCmBgYA0KDQpgYGB7ciwgaW5jbHVkZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmViZWVyX3Rlc3QgPC0gcmVhZF9jc3YoIkM6L1VzZXJzL2Rhbm55L09uZURyaXZlL0Fuw6FsaXNpcyBDdWFudGl0YXRpdm8geSBFY29ub21ldHLDrWEvTWFya2V0aW5nIEFuYWx5dGljcy9DdXN0b21lciBBbmFseXRpY3MvUXVpemVzLzIgUkZNL2ViZWVyX3Rlc3QuY3N2IikNCg0KZWJlZXJfcm9sbG91dCA8LSByZWFkX2NzdigiQzovVXNlcnMvZGFubnkvT25lRHJpdmUvQW7DoWxpc2lzIEN1YW50aXRhdGl2byB5IEVjb25vbWV0csOtYS9NYXJrZXRpbmcgQW5hbHl0aWNzL0N1c3RvbWVyIEFuYWx5dGljcy9RdWl6ZXMvMiBSRk0vZWJlZXJfcm9sbG91dC5jc3YiKQ0KYGBgDQoNCiMgUXVlc3Rpb24gMQ0KDQpBIGNvbXBhbnkgaXMgY29uc2lkZXJpbmcgdXNpbmcgUkZNIHNlZ21lbnRzIHRvIHRhcmdldCBpdHMgcm9sbG91dC4gVGhlIG1haWxpbmcgdW5kZXIgY29uc2lkZXJhdGlvbiBjb3N0cyDigqwwLjUwIHRvIHNlbmQ7IGlmIGN1c3RvbWVycyByZXNwb25kLCB0aGV5IHNwZW5kIG9uIGF2ZXJhZ2Ug4oKsNTAsIG9mIHdoaWNoIOKCrDE1IGlzIG1hcmdpbi4NCg0KSXQgY29uZHVjdGVkIGEgdGVzdCBvZiBjdXN0b21lcnMgaW4gdGhlIGZpbGUswqBbZWJlZXJfdGVzdC5jc3ZdKGh0dHBzOi8vdGlsYnVyZ3VuaXZlcnNpdHkuaW5zdHJ1Y3R1cmUuY29tL2NvdXJzZXMvMTA5MTkvZmlsZXMvMTk0ODk3NT93cmFwPTEpOyB0aGUgY3VzdG9tZXJzIG5vdCBpbiB0aGUgdGVzdCBjYW4gYmUgZm91bmQgaW7CoFtlYmVlcl9yb2xsb3V0LmNzdl0oaHR0cHM6Ly90aWxidXJndW5pdmVyc2l0eS5pbnN0cnVjdHVyZS5jb20vY291cnNlcy8xMDkxOS9maWxlcy8xOTQ4OTc3P3dyYXA9MSkuIEZvciB0aGlzIGFuYWx5c2lzLCBjcmVhdGUgMyBSRk0gZ3JvdXBzIChpbnN0ZWFkIG9mIDUgbGlrZSB3ZSBkaWQgaW4gY2xhc3MpLsKgDQoNCioqV2hhdCBpcyB0aGUgYXZlcmFnZSByZWNlbmN5IG9mIHRoZSBncm91cCB0aGF0IGlzIG1vc3QgcmVjZW50LCBpLmUuLCB0aGUgY3VzdG9tZXJzIHdobyBwdXJjaGFzZWQgbW9zdCByZWNlbnRseT8qKsKgDQoNCipQcm92aWRlIHlvdXIgYW5zd2VyIHdpdGjCoCoqb25lKiogZGVjaW1hbCBzZXBhcmF0ZWQgYnkgYSBkb3QsIG5vdCBhIGNvbW1hIChlLmcuIDAuMSkuKg0KDQpgYGB7cn0NCmViZWVyX3Rlc3QkUmdyb3VwIDwtIHF1YW50Y3V0KGViZWVyX3Rlc3QkUiwgcSA9IDMpDQpgYGANCg0KQWx0ZXJuYXRpdmVseToNCg0KYGBge3J9DQojIENyZWF0ZSBRdWFudGlsZXMgDQpudGlsZXMgPC0gZnVuY3Rpb24oeCwgYmlucykgew0KICBxdWFudGlsZXMgPSBzZXEoZnJvbT0wLCB0byA9IDEsIGxlbmd0aC5vdXQ9YmlucysxKQ0KICBjdXQoZWNkZih4KSh4KSwgYnJlYWtzID0gcXVhbnRpbGVzLCBsYWJlbHM9RikNCn0NCmViZWVyX3Rlc3QkUmdyb3VwIDwtIG50aWxlcyhlYmVlcl90ZXN0JFIsIGJpbnM9MykgIA0KYGBgDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0KcmVjZW5jeV9zdGF0cyA8LSBlYmVlcl90ZXN0ICU+JSANCiAgZ3JvdXBfYnkoUmdyb3VwKSAlPiUgDQogIHN1bW1hcmlzZShuID0gbigpLCANCiAgICAgICAgICAgIG1lYW5fUiA9IG1lYW4oUiksIHNkX1IgPSBzZChSKSwgDQogICAgICAgICAgICByZXNwX3AgPSBtZWFuKHJlc3BtYWlsLCBuYS5ybT1UUlVFKSkNCnJlY2VuY3lfc3RhdHMgJT4lIA0KICBrYmwoKSAlPiUNCiAga2FibGVfc3R5bGluZygpDQpgYGANCg0KYGBge3IsIGVjaG89RkFMU0V9DQpjYXQoIlRoZSBBdmVyYWdlIFJlY2VuY3kgb2YgR3JvdXAgWzI6MTBdIGlzIiwgcm91bmQocmVjZW5jeV9zdGF0cyRtZWFuX1JbMV0sMSkgKQ0KYGBgDQoNCiMgUXVlc3Rpb24gMg0KDQoqKldoYXQgaXMgdGhlIHJlc3BvbnNlIHByb2JhYmlsaXR5IG9mIHRoYXQgZ3JvdXA/KirCoA0KDQoqUHJvdmlkZSB5b3VyIGFuc3dlciB3aXRowqAqKnR3byoqwqBkZWNpbWFscyBzZXBhcmF0ZWQgYnkgYSBkb3QsIG5vdCBhIGNvbW1hIChlLmcuIDAuMTcpLioNCg0KYGBge3IsIGVjaG89RkFMU0V9DQpjYXQoIlRoZSBSZXNwb25zZSBSYXRlIG9mIEdyb3VwIFsyOjEwXSBpcyIsIHJvdW5kKHJlY2VuY3lfc3RhdHMkcmVzcF9wWzFdLDIpICkNCmBgYA0KDQojIFF1ZXN0aW9uIDMNCg0KTm93IGRvIHRoZSBmdWxsIFJGTSBhbmFseXNpcy7CoA0KDQoqKkhvdyBtYW55IFJGTSBncm91cHMgYXJlIHRoZXJlKio/DQoNCipQcm92aWRlIHlvdXIgYW5zd2VyIHdpdGjCoCoqemVybyoqwqBkZWNpbWFscyAoZS5nLiAxNykuKg0KDQpUaGUgQ29kZToNCg0KYGBge3J9DQpudGlsZXMgPC0gZnVuY3Rpb24oeCwgYmlucykgew0KICBxdWFudGlsZXMgPSBzZXEoZnJvbT0wLCB0byA9IDEsIGxlbmd0aC5vdXQ9YmlucysxKQ0KICBjdXQoZWNkZih4KSh4KSxicmVha3M9cXVhbnRpbGVzLCBsYWJlbHM9RikNCn0NCg0KZWJlZXJfdGVzdCRSZ3JvdXAgPC0gbnRpbGVzKGViZWVyX3Rlc3QkUiwgYmlucz0zKSAgDQoNCg0KZHQgPSBkYXRhLnRhYmxlKGViZWVyX3Rlc3QpDQpuYmlucyA9IDMNCmR0WywgUkZncm91cCA6PSBwYXN0ZTAoYXMuY2hhcmFjdGVyKFJncm91cCksIGFzLmNoYXJhY3RlcihudGlsZXMoRiwgYmlucyA9IG5iaW5zKSkpLCBieSA9IGMoJ1Jncm91cCcpXQ0KZHRbLCBSRk1ncm91cCA6PSBwYXN0ZTAoYXMuY2hhcmFjdGVyKFJGZ3JvdXApLCBhcy5jaGFyYWN0ZXIobnRpbGVzKE0sIGJpbnMgPSBuYmlucykpKSwgYnkgPSBjKCdSRmdyb3VwJyldDQoNCiMgcHV0IGl0IGJhY2sgdG8gZGF0YS5mcmFtZQ0KZWJlZXJfdGVzdCA9IGRhdGEuZnJhbWUoZHQpDQoNCiMgY2hhbmdlIGl0IHRvIGEgZmFjdG9yIHZhcmlhYmxlDQplYmVlcl90ZXN0JFJGTWdyb3VwPC1hcy5mYWN0b3IoZWJlZXJfdGVzdCRSRk1ncm91cCkNCmBgYA0KDQoqKk51bWJlciBvZiBSRk0gR3JvdXBzOioqDQoNCmBgYHtyfQ0KbGVuZ3RoKHVuaXF1ZShlYmVlcl90ZXN0JFJGTWdyb3VwKSkNCmBgYA0KDQojIFF1ZXN0aW9uIDQNCg0KKipIb3cgbWFueSBvZiB0aGVzZSBzZWdtZW50cyBhcmUgcHJvZml0YWJsZSB0byBzZW5kIHRvPyoqwqANCg0KKlByb3ZpZGUgeW91ciBhbnN3ZXIgd2l0aMKgKip6ZXJvKirCoGRlY2ltYWxzIChlLmcuIDEyKS4qDQoNCkxldCdzIGNhbGN1bGF0ZSB0aGUgcmVzcG9uc2UgcmF0ZXMgZm9yIGVhY2ggc2VnbWVudDoNCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQpyZXNwUkZNIDwtIGViZWVyX3Rlc3QgJT4lIA0KICBncm91cF9ieShSRk1ncm91cCkgJT4lIA0KICBzdW1tYXJpc2Uobl9yZXNwPSBzdW0ocmVzcG1haWwsIG5hLnJtID0gVFJVRSksIA0KICAgICAgICAgICAgbl9tYWlsPSBzdW0obWFpbGluZywgbmEucm0gPSBUUlVFKSkgJT4lIA0KICBtdXRhdGUocmVzcF9yYXRlID0gbl9yZXNwL25fbWFpbCkgJT4lIA0KICBhcnJhbmdlKGRlc2MocmVzcF9yYXRlKSkNCmhlYWQocmVzcFJGTSkgJT4lIA0KICBrYmwoKSAlPiUNCiAga2FibGVfc3R5bGluZygpDQpgYGANCg0KKipCcmVha2V2ZW4gUG9pbnQqKjoNCg0KYGBge3J9DQpjIDwtIDAuNQ0KbSA8LSAxNQ0KYnJrIDwtIGMvbQ0KYGBgDQoNClsqKkhvdyBtYW55IHRvIHRhcmdldD8qKl17LnVuZGVybGluZX06DQoNCmBgYHtyfQ0Kc3VtKHJlc3BSRk0kcmVzcF9yYXRlID49IGJyaykNCmBgYA0KDQpgYGB7cn0NCnJlc3BSRk0gPC0gYXMuZGF0YS5mcmFtZShyZXNwUkZNKQ0KDQpicCA8LSBiYXJwbG90KHJlc3BSRk1bLDRdLCANCiAgICAgICAgICAgICAgbWFpbj0icmVzcG9uc2UgYnkgUkZNIGdyb3VwIiwgDQogICAgICAgICAgICAgIHhsYWI9IlJGTSBHcm91cCIsIHlsYWI9ImF2ZXJhZ2UgcmVzcG9uc2UiLCB4YXh0PSJuIikNCmF4aXMoMSwgYXQgPSBicFssMV0sIGxhYmVscz1yZXNwUkZNWywxXSwgY2V4LmF4aXM9MC43LCBsYXM9MikNCg0KYWJsaW5lKGg9YnJrKQ0KdGV4dCg4NSwgYnJrLCAiYnJlYWtldmVuIiwgY2V4PTEsIHBvcz0zLCBjb2w9ImJsYWNrIikNCmBgYA0KDQojIFF1ZXN0aW9uIDUNCg0KKipXaGF0IGZyYWN0aW9uIG9mIHRvdGFsIHZhcmlhdGlvbiBpbiByZXNwb25zZXMgaXMgZXhwbGFpbmVkIGJ5IHRoZSBSRk0gbW9kZWw/KirCoA0KDQoqUHJvdmlkZSB5b3VyIGFuc3dlciB3aXRowqAqKnRocmVlKiogZGVjaW1hbHMgc2VwYXJhdGVkIGJ5IGEgZG90LCBub3QgYSBjb21tYSAoZS5nLiAwLjEyNykqDQoNCmBgYHtyfQ0KUkZNX21vZGVsIDwtIGxtKHJlc3BtYWlsIH4gUkZNZ3JvdXAsIGRhdGEgPSBlYmVlcl90ZXN0KQ0Kcm91bmQoc3VtbWFyeShSRk1fbW9kZWwpJHIuc3F1YXJlZCwzKQ0KYGBgDQoNCiMgUXVlc3Rpb24gNg0KDQoqKkhvdyBtYW55IGN1c3RvbWVycyBpbiB0aGUgcm9sbC1vdXQgc2FtcGxlIHNob3VsZCBiZSB0YXJnZXRlZD8qKsKgDQoNCipQcm92aWRlIHlvdXIgYW5zd2VyIHdpdGjCoCoqemVybyoqwqBkZWNpbWFscyAoZS5nLiAxMikuKg0KDQpMZXQncyBjb25zaWRlciBub3cgdGhlIHJvbGwtb3V0IGRhdGEuIFdlIG5lZWQgZmlyc3QgdG8gKmNyZWF0ZSB0aGUgc2VnbWVudHMqOg0KDQpgYGB7cn0NCm50aWxlcyA8LSBmdW5jdGlvbih4LCBiaW5zKSB7DQogIHF1YW50aWxlcyA9IHNlcShmcm9tPTAsIHRvID0gMSwgbGVuZ3RoLm91dD1iaW5zKzEpDQogIGN1dChlY2RmKHgpKHgpLGJyZWFrcz1xdWFudGlsZXMsIGxhYmVscz1GKQ0KfQ0KDQplYmVlcl9yb2xsb3V0JFJncm91cCA8LSBudGlsZXMoZWJlZXJfcm9sbG91dCRSLCBiaW5zPTMpICANCg0KDQpkdCA9IGRhdGEudGFibGUoZWJlZXJfcm9sbG91dCkNCm5iaW5zID0gMw0KZHRbLCBSRmdyb3VwIDo9IHBhc3RlMChhcy5jaGFyYWN0ZXIoUmdyb3VwKSwgYXMuY2hhcmFjdGVyKG50aWxlcyhGLCBiaW5zID0gbmJpbnMpKSksIGJ5ID0gYygnUmdyb3VwJyldDQpkdFssIFJGTWdyb3VwIDo9IHBhc3RlMChhcy5jaGFyYWN0ZXIoUkZncm91cCksIGFzLmNoYXJhY3RlcihudGlsZXMoTSwgYmlucyA9IG5iaW5zKSkpLCBieSA9IGMoJ1JGZ3JvdXAnKV0NCg0KIyBwdXQgaXQgYmFjayB0byBkYXRhLmZyYW1lDQplYmVlcl9yb2xsb3V0ID0gZGF0YS5mcmFtZShkdCkNCg0KIyBjaGFuZ2UgaXQgdG8gYSBmYWN0b3IgdmFyaWFibGUNCmViZWVyX3JvbGxvdXQkUkZNZ3JvdXA8LWFzLmZhY3RvcihlYmVlcl9yb2xsb3V0JFJGTWdyb3VwKQ0KYGBgDQoNCldlIHdhbnQgdG8gKnByZWRpY3QgdGhlaXIgcmVzcG9uc2UgcmF0ZXMqOg0KDQpgYGB7cn0NCmViZWVyX3JvbGxvdXQkUkZNcHJlZCA8LSBwcmVkaWN0KFJGTV9tb2RlbCwgZWJlZXJfcm9sbG91dCkNCnN1bShlYmVlcl9yb2xsb3V0JFJGTXByZWQgPj0gYnJrKQ0KYGBgDQoNCmBgYHtyfQ0Kcm9sbG91dF9yZXNwUkZNIDwtIGViZWVyX3JvbGxvdXQgJT4lIA0KICBncm91cF9ieShSRk1ncm91cCkgJT4lIA0KICBzdW1tYXJpc2Uobl9yZXNwPSBzdW0ocmVzcG1haWwsIG5hLnJtID0gVFJVRSksIA0KICAgICAgICAgICAgbl9tYWlsPSBzdW0obWFpbGluZywgbmEucm0gPSBUUlVFKSkgJT4lIA0KICBtdXRhdGUocmVzcF9yYXRlID0gbl9yZXNwL25fbWFpbCkgJT4lIA0KICBhcnJhbmdlKGRlc2MocmVzcF9yYXRlKSkNCmBgYA0KDQpgYGB7cn0NCnJvbGxvdXRfcmVzcFJGTSA8LSBhcy5kYXRhLmZyYW1lKHJvbGxvdXRfcmVzcFJGTSkNCg0KYnAgPC0gYmFycGxvdChyb2xsb3V0X3Jlc3BSRk1bLDRdLCANCiAgICAgICAgICAgICAgbWFpbj0icmVzcG9uc2UgYnkgUkZNIGdyb3VwIiwgDQogICAgICAgICAgICAgIHhsYWI9IlJGTSBHcm91cCIsIHlsYWI9ImF2ZXJhZ2UgcmVzcG9uc2UiLCB4YXh0PSJuIikNCmF4aXMoMSwgYXQgPSBicFssMV0sIGxhYmVscz1yb2xsb3V0X3Jlc3BSRk1bLDFdLCBjZXguYXhpcz0wLjcsIGxhcz0yKQ0KDQphYmxpbmUoaD1icmspDQp0ZXh0KDg1LCBicmssICJicmVha2V2ZW4iLCBjZXg9MSwgcG9zPTMsIGNvbD0iYmxhY2siKQ0KYGBgDQoNCiMgUXVlc3Rpb24gNw0KDQoqKldoYXQgaXMgdGhlIHJldHVybiBvbiBpbnZlc3RtZW50IG9mIHJvbGxpbmcgb3V0IHRvIHRob3NlIHNlZ21lbnRzPyoqwqANCg0KKlByb3ZpZGUgeW91ciBhbnN3ZXIgd2l0aMKgKip6ZXJvKiogZGVjaW1hbHPCoCoqd2l0aG91dCoqwqB0aGUgcGVyY2VudCBzaWduIChlLmcuIDEyMCkuKg0KDQoqKlByb2ZpdCBwZXIgQ29uc3VtZXIqKjoNCg0KYGBge3J9DQplYmVlcl9yb2xsb3V0IDwtIGViZWVyX3JvbGxvdXQgJT4lIA0KCW11dGF0ZShSRk1wcm9maXQgPSBjYXNlX3doZW4oUkZNcHJlZCA+PSBicmsgfiBSRk1wcmVkKm0tYywgVFJVRSB+IDApKQ0KYGBgDQoNCioqU3VtIG9mIFByb2ZpdHMqKjoNCg0KYGBge3J9DQpzdW1fcHJvZml0IDwtIHN1bShlYmVlcl9yb2xsb3V0JFJGTXByb2ZpdCkNCnN1bV9wcm9maXQNCmBgYA0KDQoqKkNvc3RzIHBlciBDb25zdW1lcioqOg0KDQpgYGB7cn0NCmViZWVyX3JvbGxvdXQgPC0gZWJlZXJfcm9sbG91dCAlPiUgDQoJbXV0YXRlKFJGTWNvc3QgPSBjYXNlX3doZW4oUkZNcHJlZCA+PSBicmsgfiBjLCBUUlVFIH4gMCkpDQpgYGANCg0KKipTdW0gb2YgQ29zdHMqKjoNCg0KYGBge3J9DQpzdW1fY29zdCA8LSBzdW0oZWJlZXJfcm9sbG91dCRSRk1jb3N0KQ0Kc3VtX2Nvc3QgDQpgYGANCg0KKipST0kqKjoNCg0KYGBge3IsIGVjaG89RkFMU0V9DQpjYXQoIlRoZSBST0kgaXMiLCByb3VuZChzdW1fcHJvZml0IC8gc3VtX2Nvc3QqMTAwLCAwKSwgIiUiKQ0KYGBgDQoNCiMgUXVlc3Rpb24gOA0KDQpVc2UgYSBiZXRhLWJpbm9taWFsIG1vZGVsIHRvIHNocmluayB0aGUgc2VnbWVudCBlc3RpbWF0ZXMgaW4gdGhlIHRlc3QgZGF0YS4gwqANCg0KKipIb3cgbWFueSBleHRyYSBzZWdtZW50cyB3b3VsZCB5b3UgdGFyZ2V0IHVzaW5nIHRoaXMgbWV0aG9kIHRoYXQgeW91IHdvdWxkbid0IG90aGVyd2lzZSB0YXJnZXQ/KioNCg0KKlByb3ZpZGUgeW91ciBhbnN3ZXIgd2l0aMKgKip6ZXJvKiogZGVjaW1hbHMgKGUuZy4gMTIpLioNCg0KRmlyc3QsIHdlIG5lZWQgdGhlIG5vbi1yZXNwb25zZSBkYXRhOg0KDQpgYGB7cn0NCnJlc3BSRk0gPC0gcmVzcFJGTSAlPiUgDQogIG11dGF0ZShuX25vbnJlc3AgPSBuX21haWwtbl9yZXNwKSAlPiUgDQogIHJlbG9jYXRlKG5fbm9ucmVzcCwgLmFmdGVyPW5fcmVzcCkNCmBgYA0KDQpMaWtlbGlob29kIEZ1bmN0aW9uIHRvIGZpdCB0aGUgZGF0YSBhbmQgZmluZCAqKnByaW9yIHJlc3BvbnNlIHJhdGUqKjoNCg0KYGBge3J9DQpmaXQgPC0gdmdsbShjYmluZChyZXNwUkZNJG5fcmVzcCxyZXNwUkZNJG5fbm9ucmVzcCkgfiAxLCBiZXRhYmlub21pYWxmZiwgdHJhY2U9VFJVRSkNCmEgPC0gQ29lZihmaXQpW1sxXV0NCmIgPC0gQ29lZihmaXQpW1syXV0NCmBgYA0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCmNhdCgiKGEsYik9KCIsYSwiLCIsIGIsICIpIikNCmBgYA0KDQpXZSBrbm93IGNhbiBlc3RpbWF0ZSB0aGUgcG9zdGVyaW9yIHJlc3BvbnNlIHJhdGVzOg0KDQpgYGB7cn0NCnBvc3RfbWVhbl9yZXNwIDwtIChhK3Jlc3BSRk0kbl9yZXNwKS8oYStiK3Jlc3BSRk0kbl9tYWlsKQ0KcmVzcFJGTSA8LSBjYmluZChyZXNwUkZNLCBwb3N0X21lYW5fcmVzcCkNCmBgYA0KDQpgYGB7cn0NCnBsb3QocmVzcFJGTSRyZXNwX3JhdGUsIHhheHQ9Im4iLGNvbD0icmVkIix4bGFiPSJSRk0gc2VnbWVudHMiLHlsYWI9InJlc3BvbnNlIHJhdGUgYW5kIHBvc3RlcmlvciBtZWFuIHJlc3BvbnNlIHJhdGUiKQ0KcG9pbnRzKHJlc3BSRk0kcG9zdF9tZWFuX3Jlc3AsIGNvbD0nYmx1ZScpDQpsZWdlbmQoJ3RvcGxlZnQnLGxlZ2VuZD1jKCJlc3RpbWF0ZSByZXNwb25zZSByYXRlIiwgInBvc3RlcmlvciBleHBlY3RlZCByZXNwb25zZSByYXRlIiksY29sPWMoInJlZCIsImJsdWUiKSwgcGNoPTEpDQpheGlzKDEsIGF0ID0gMToyNywgbGFiZWxzPXJlc3BSRk0kUkZNZ3JvdXAsIGNleC5heGlzPTAuNywgbGFzPTIpDQphYmxpbmUoaD1icmspDQp0ZXh0KDI1LCBicmssICJicmVha2V2ZW4iLCBjZXg9MSwgcG9zPTMsIGNvbD0iYmxhY2siKQ0KYGBgDQoNCkluIHRoZSBlbmQsICoqKmhvdyBtYW55IGV4dHJhIHNlZ21lbnRzIHNob3VsZCB3ZSB0YXJnZXQ/KioqDQoNCmBgYHtyfQ0Kc3VtKHJlc3BSRk0kcG9zdF9tZWFuX3Jlc3AgPj0gYnJrKSAtIHN1bShyZXNwUkZNJHJlc3BfcmF0ZSA+PSBicmspDQpgYGANCg==