Overview

Executive Summary

Initial Findings

For my analysis, I first examined differences between our customers who bought organics and those who did not. Across store spending, region, and months as loyalty card holders, there was little to no discrepancy between our organic and non-organic customers. Outside of these three variables, however, we see some key differences between these two customer groups.

Firstly, those who bought more organics were generally wealthier. This is to be expected-those with more money have more disposable income to spend on healthier produce. Secondly, our organics customers tend to be younger. As younger generations have become more health conscious about how their groceries are sourced, this also is not surprising. In terms of gender, our primary shoppers are women. Over 70% of all our sales are to females. Women also have a higher tendency to buy organics, as can be seen by the mosaic plot in the Gender tab under EDA.
Across the pre-calculated cluster groups, we see that cluster group F has a higher tendency to buy organics. Cluster group A is the least likely to buy our organics. More information on how these cluster groups were generated can help in interpreting this output. Most importantly, however, I noted some differences across our loyalty status categories. The higher a customer’s loyalty status, the lower their tendency to purchase organics. This relationship holds true at every loyalty status level. Delving further into this issue might enable us as a company to boost our organics sales.

Predictive Modeling

From here I ran some predictive modeling to predict the probability that our customers will purchase organics. Backing out these models, I was able to determine which independent variables were most significantly impacting our customers’ choice to buy organics. Based on the independent variables, my final gradient boosting model can accurately predict which customers will and will not purchase our organics with only a misclassification rate of 19.35%. I analyzed the output of my model via a decision tree on its predictions - this showed that the variables most impacting the model, and therefore customers’ probabilities of purchasing organics, are age, affluence, and gender. This model will enable our marketing team to assess which of our customers on an individual basis are most likely to purchase our organics. As a grocer, we can use these probabilities to better spend our advertising dollars to boost organics sales.

Customer Clustering

After performing predictive modeling, I also chose to implement clustering techniques to split our customers into different segments. I ultimately generated twelve customer segment clusters, with one specific customer segment having a high tendency to purchase our organics. I then chose to delve further into the characteristics of this cluster.

This cluster was both the wealthiest and the second youngest. This falls in line with the initial exploratory analysis. The cluster was also mostly female, although it did contain some males as well. This cluster did not consist of our most loyal customers; most of the customers in this segment had either silver or tin loyalty status.

The most worrying statistic about this cluster is the amount that it spends annually in our stores. This cluster of customers had the third lowest average annual expenditures at our stores. Our high-spending customers, on the other hand, only buy organics at a rate of 15%. This means that we as a grocer need to reposition our brand if we really want to sell more organics. Those who shop at our store the most are not buying organics, indicating that organic produce is not something that people come to our store most often to buy.

Going Forward

We should send more targeted messaging to our high-spending customer cluster. Doing so, hopefully we can convince this cluster to buy more organics. If we can successfully convert this segment, then we as a brand position ourselves to sell more organics to our customers and improve their health. We need to seize this opportunity and convince those who shop with us the most frequently to buy more organics.

If we can effectively engage with these cluster and leverage our predictive model, then we can better spend our advertising dollars and disburse our coupon. This should boost our organic sales across the board, boosting our public image, and make us the healthy grocer of tomorrow.

Data Dictionary

data_dictionary <- read.csv("Data Dictionary.csv")
colnames(data_dictionary) <- c("Variable", "Description")
data_dictionary %>%
  datatable(rownames = FALSE)

Required Packages

library(plyr)            # For caret package
library(tidyverse)       # For data manipulation
library(missRanger)      # For missing value imputation
library(leaps)           # For gradient boosting
library(rpart)           # For decision tree
library(randomForest)    # For random forest model
library(e1071)           # For svm model
library(caret)           # For regression analysis
library(rattle)          # For ploting decision tree
library(factoextra)      # For cluster analysis
library(cluster)         # For cluster analysis
library(NbClust)         # For cluster analysis
library(DT)              # For datatable visualization

Import and Format

df <- read.csv("organics.csv")
df <- df %>%
  select(-DemCluster)
colnames(df)[1] <- "ID"

The initial dataset consisted of 11 variables and 22,223 observations. I dropped the DemCluster variable because of the high number of levels and since this variable had already been used to generate the DemClusterGroup variable.

The initial dataset also contained some “U” values and some “” values, indicating that these values were unknown. I therefore changed these values to NA.

df$DemClusterGroup[df$DemClusterGroup %in% c("U","")] <- NA
df$DemGender[df$DemGender %in% c("U","")] <- NA
df$DemReg[df$DemReg == ""] <- NA
df$DemTVReg[df$DemTVReg == ""] <- NA

I converted the PromTime variable to an ordered factor, assuming the ordering that “tin”<“silver”<“gold”<“platinum”. I also converted the binary target variable to a factor. After doing so, I dropped all unused factor levels from the data.

df$PromClass <- factor(df$PromClass, ordered = TRUE, 
                       levels = c("Tin", "Silver", "Gold", "Platinum"))
df$TargetBuy <- factor(df$TargetBuy)
df <- droplevels(df)

At this point there were still 8791 missing values in the dataset. Before imputing these values, I first performed some exploratory data analysis.

EDA

I chose to first determine if there were any noticeable differences between customers that purchase organics versus those who do not. I did this across each of the explanatory attributes.

Dependent Variables

Affluence

df %>%
  group_by(TargetBuy) %>%
  ggplot(aes(x = TargetBuy, y = DemAffl)) +
  geom_boxplot() +
  xlab("Organics") +
  scale_x_discrete(labels = c("Didn't Buy", "Bought")) +
  ylab("Affluence (1-30)") +
  ggtitle("Wealthier People Buy More Organics")

Age

df %>%
  group_by(TargetBuy) %>%
  ggplot(aes(x = TargetBuy, y = DemAge)) +
  geom_boxplot() +
  xlab("Organics") +
  scale_x_discrete(labels = c("Didn't Buy", "Bought")) +
  ylab("Age") +
  ggtitle("Younger People Buy More Organics")

Gender

table(df$DemGender, df$TargetBuy) %>%
  prop.table() %>%
  print() %>%
  mosaicplot(main = "Organic Purchases and Genders", xlab = "Gender", 
             ylab = "Whether They Purchase Organics")
   
             0          1
  F 0.44221777 0.23407927
  M 0.26992875 0.05377422

Cluster Group

table(df$DemClusterGroup, df$TargetBuy) %>%
  prop.table() %>%
  print() %>%
  mosaicplot(main = "Organic Purchases and Cluster Group", 
             xlab = "Cluster Group", ylab = "Whether They Purchase Organics")
   
             0          1
  A 0.06894627 0.01712026
  B 0.15189579 0.04089323
  C 0.15961852 0.05280298
  D 0.15231449 0.05136078
  E 0.09025355 0.03107699
  F 0.13035590 0.05336125

We see that among our cluster groups, the F group tends to buy more organics.

Region

table(df$DemReg, df$TargetBuy) %>%
  prop.table() %>%
  print() %>%
  mosaicplot(main = "Organic Purchases and Region", xlab = "Region",
             ylab = "Whether They Purchase Organics")
            
                       0           1
  Midlands   0.231363177 0.078453902
  North      0.152219873 0.046511628
  Scottish   0.047982351 0.014891075
  South East 0.296442688 0.100376873
  South West 0.024450777 0.007307657

We see that most of our shoppers are from the South East, followed by the Midlands and the North. Across all regions, we don’t see major discrepancies in the proportion of organics sold.

Loyalty Status

table(df$PromClass, df$TargetBuy) %>%
  prop.table() %>%
  print() %>%
  mosaicplot(main = "Organic Purchases and Loyalty Status", 
             xlab = "Loyalty Status", ylab = "Whether They Purchase Organics")
          
                     0           1
  Tin      0.200602979 0.091301804
  Silver   0.290689826 0.095036674
  Gold     0.228951987 0.055618053
  Platinum 0.032038879 0.005759798

Across loyalty status, we see that our loyal customers buy fewer organics.

Store Spending

df %>%
  group_by(TargetBuy) %>%
  ggplot(aes(x = TargetBuy, y = PromSpend)) +
  geom_boxplot() +
  xlab("Organics") +
  scale_x_discrete(labels = c("Didn't Buy", "Bought")) +
  ylab("Store Spending") +
  scale_y_continuous(labels = scales::dollar) +
  ggtitle("Wealthier People Buy More Organics")

We see that the distribution of grocery spending is highly skewed right based on these boxplots. We see that there are three people who are spending over $200,000 on groceries. Given that this seems fairly extreme, I decided to dig more into these observations.

df[c(2057, 3483, 11476),]
            ID DemAffl DemAge DemClusterGroup DemGender     DemReg  DemTVReg PromClass PromSpend
2057   4558448      15     76               C         F      North Yorkshire  Platinum  239542.1
3483   7932499      10     67               C         M South East    London  Platinum  296313.8
11476 29207630      15     71               A      <NA> South East    London  Platinum  201000.0
      PromTime TargetBuy TargetAmt
2057         4         1         1
3483         8         0         0
11476        7         0         0

Given that these three outliers have average affluence levels, I am operating under the assumption that their spending was not above $200,000–rather, they misrecorded by two decimal places. I am imputing them as such.

df[c(2057, 3483, 11476), "PromSpend"] <- df[c(2057, 3483, 11476), 
                                            "PromSpend"] / 100

Months as Loyalty Member

df %>%
  group_by(TargetBuy) %>%
  ggplot(aes(x = TargetBuy, y = PromTime)) +
  geom_boxplot() +
  xlab("Organics") +
  scale_x_discrete(labels = c("Didn't Buy", "Bought")) +
  ylab("Months as Loyalty Card Member") +
  ggtitle("Comparing Months as Member to Organics Purchases")

We don’t see a difference in the distribution of months as loyalty card members between those who bought organics and who didn’t.

Imputation

I operated under the assumption that the remaining 8791 missing values were missing at random. Therefore I chose to use the missRanger function to impute the missing values. This function imputes mixed-type data sets by chaining tree ensembles.

df[1:10] <- missRanger(df[1:10], seed = 42)

Missing value imputation by chained tree ensembles

iter 1: .......
iter 2: .......
iter 3: .......

Predictive Modeling

I first split my data into training and testing data. I dropped the TargetAmt variable because the purpose of my models was to predict whether someone purchased organics, not how many organics they bought. I also dropped the ID variable since this variable has no impact on my target variable.

set.seed(42)
df <- df %>%
  select(-TargetAmt, -ID)
sample <- sample(c(TRUE, FALSE), nrow(df), replace = TRUE, 
                 prob = c(0.7, 0.3))
train <- df[sample, ]
test <- df[!sample, ]

I used a logistic regression, decision tree, random forest, gradient boosting model, and support vector machine to determine which model did the best job predicting the TargetBuy variable.

Logistic Regression

logit_null <- glm(TargetBuy ~ 1, data = train, 
                  family = binomial(link = "logit"))
logit_full <- glm(TargetBuy ~ ., data = train, 
                  family = binomial(link = "logit"))
logit_fit <- step(logit_null,scope=formula(logit_full),
                    direction="both",k=2)
logit_predictions <- predict(logit_fit, newdata = test, type = "response")
logit_conf_matrix <- table(test$TargetBuy, logit_predictions > 0.5) %>%
  prop.table()

I used both forward and backward selection to determine my final logistic regression model. The final statistically significant predictor variables were DemAffl, DemAge, and DemGender, all of which were significant at the 99% confidence level. Customer affluence increased the probability of a customer purchasing organics, which increased age and being male both decreased the probability of a customer purchasing organics.

summary(logit_fit)

Call:
glm(formula = TargetBuy ~ DemAffl + DemAge + DemGender, family = binomial(link = "logit"), 
    data = train)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.0625  -0.7073  -0.4655  -0.1603   2.8973  

Coefficients:
             Estimate Std. Error z value Pr(>|z|)    
(Intercept) -0.397150   0.108143  -3.672  0.00024 ***
DemAffl      0.248395   0.006934  35.822  < 2e-16 ***
DemAge      -0.055316   0.001734 -31.896  < 2e-16 ***
DemGenderM  -0.713361   0.051673 -13.805  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 17451  on 15570  degrees of freedom
Residual deviance: 14182  on 15567  degrees of freedom
AIC: 14190

Number of Fisher Scoring iterations: 5

Decision Tree

tree_fit <- rpart(TargetBuy ~ ., data = train, method = "class")
tree_predictions <- predict(tree_fit, newdata = test, type = "class")
tree_conf_matrix <- table(test$TargetBuy, tree_predictions) %>% 
  prop.table()
fancyRpartPlot(tree_fit)

The output of my tree method showed that age was the single most important variable in determining whether or not someone purchased organics, with those age 45 or over not buying organic. The next most significant split was on affluence, with those having more money tending to purchase more organics.

Random Forest

forest_fit <- randomForest(TargetBuy ~ ., data = train, ntree = 500)
forest_predictions <- predict(forest_fit, newdata = test, type = "class")
forest_conf_matrix <- table(test$TargetBuy, forest_predictions) %>%
  prop.table()

Gradient Boosting

fit_control <- trainControl(method = "cv", number = 10)
tune_grid <- expand.grid(interaction.depth = 2, n.trees = 500, shrinkage = 0.1,
                         n.minobsinnode = 10)                            
gbm_fit <- train(TargetBuy ~ ., data = train, method = "gbm", 
                   trControl = fit_control, verbose = FALSE, 
                   tuneGrid = tune_grid)
gbm_predictions <- predict(gbm_fit, newdata = test, type = "prob")
gbm_conf_matrix <- table(test$TargetBuy, gbm_predictions[, 2] > 0.5) %>%
  prop.table()

SVM

svm_fit <- svm(TargetBuy ~ ., data = train)
svm_predictions <- predict(svm_fit, newdata = test, type = "prob")
svm_conf_matrix <- table(test$TargetBuy, svm_predictions) %>%
  prop.table()

Model Comparison

models <- list(
  logit = logit_conf_matrix,
  tree = tree_conf_matrix,
  forest = forest_conf_matrix,
  boosting = gbm_conf_matrix,
  svm = svm_conf_matrix
)
for (i in 1:length(models)){
  rownames(models[[i]]) <- c("Bought", "Didn't Buy")
  colnames(models[[i]]) <- c("Predicted No Buy", "Predicted Buy")
}
misclassification_rates <- list(
  logit = paste("Misclassification Rate =",
                mean(test$TargetBuy != round(logit_predictions, 0))),
  tree = paste("Misclassification Rate =", 
               mean(test$TargetBuy != tree_predictions)),
  forest = paste("Misclassification Rate =", 
                 mean(test$TargetBuy != forest_predictions)),
  gbm = paste("Misclassification Rate =",
                    mean(test$TargetBuy != round(gbm_predictions[, 2], 0))),
  svm = paste("Misclassification Rate =", 
              mean(test$TargetBuy != svm_predictions))
)
print(models)
$`logit`
            
             Predicted No Buy Predicted Buy
  Bought           0.71602526    0.03743235
  Didn't Buy       0.16551413    0.08102826

$tree
            tree_predictions
             Predicted No Buy Predicted Buy
  Bought           0.72113650    0.03232111
  Didn't Buy       0.16791942    0.07862297

$forest
            forest_predictions
             Predicted No Buy Predicted Buy
  Bought           0.69678292    0.05667468
  Didn't Buy       0.14762477    0.09891762

$boosting
            
             Predicted No Buy Predicted Buy
  Bought           0.70625376    0.04720385
  Didn't Buy       0.14657246    0.09996993

$svm
            svm_predictions
             Predicted No Buy Predicted Buy
  Bought           0.73677090    0.01668671
  Didn't Buy       0.18656043    0.05998196
print(misclassification_rates)
$`logit`
[1] "Misclassification Rate = 0.202946482260974"

$tree
[1] "Misclassification Rate = 0.200240529164161"

$forest
[1] "Misclassification Rate = 0.204299458809381"

$gbm
[1] "Misclassification Rate = 0.19377630787733"

$svm
[1] "Misclassification Rate = 0.203247143716176"

The final model with the lowest misclassification rate was the gradient boosting model. I am therefore choosing to proceed with the gradient boosting model for predicting whether or not a customer will purchase organics. I implemented a decision tree trying to predict my gradient boosting model predictions based on the x inputs to the model to discern which variables had the biggest impact on my gradient boosting model predictions.

train_predictions <- predict(gbm_fit, newdata = train, type = "prob")
train_df_w_predictions <- train %>%
  mutate(Prediction = round(train_predictions[, 2], 0)) %>%
  select(-TargetBuy)
model_assessment_tree <- rpart(Prediction ~ ., data = train_df_w_predictions,
                               method = "class")
fancyRpartPlot(model_assessment_tree)

Based on the output of the decision tree, the variables most impacting my final model’s predictions were age followed by affluence followed by gender. These three variables were the only attributes represented in my decision tree modeling of my gbm predictions.

Scored Set

new_data <- read.csv("New_organics.csv")
new_data <- new_data %>%
  select(-DemCluster)
new_data$PromSpend <- 0
new_data$DemClusterGroup[new_data$DemClusterGroup %in% c("U","")] <- NA
new_data$DemGender[new_data$DemGender %in% c("U","")] <- NA
new_data$DemReg[new_data$DemReg == ""] <- NA
new_data$DemTVReg[new_data$DemTVReg == ""] <- NA
new_data$PromClass <- factor(new_data$PromClass, ordered = TRUE, 
                       levels = c("Tin", "Silver", "Gold", "Platinum"))
new_data <- droplevels(new_data)
new_data <- missRanger(new_data, seed = 42)

Missing value imputation by chained tree ensembles

iter 1: .
iter 2: .
iter 3: .
## Creating gbm_fit on all data
gbm_fit <- train(TargetBuy ~ ., data = df, method = "gbm", 
                 trControl = fit_control, verbose = FALSE, 
                 tuneGrid = tune_grid)
gbm_predictions <- predict(gbm_fit, newdata = new_data, type = "prob")
scored_set <- cbind(new_data$ID,
                            predict(gbm_fit,
                                    newdata = new_data, type = "prob")[, 2]) %>%
  data.frame() %>%
  arrange(desc(X2))
colnames(scored_set) <- c("ID", "Prob of Purchasing")
print(scored_set)
     ID Prob of Purchasing
1  2014         0.87826575
2  2018         0.53859139
3  2006         0.35826681
4  2000         0.32818577
5  2004         0.32805737
6  2017         0.31163555
7  2011         0.28386556
8  2021         0.27353447
9  2009         0.24883738
10 2005         0.22013667
11 2020         0.18217325
12 2007         0.16587082
13 2012         0.14600402
14 2010         0.10950861
15 2008         0.09411691
16 2016         0.09113999
17 2003         0.08954738
18 2002         0.07898911
19 2013         0.06980488
20 2022         0.06952756
21 2015         0.06437703
22 2023         0.05295740
23 2019         0.04909787
24 2001         0.03230183

I applied the same data cleaning and imputation on the new set as I did on the old set. When creating my gbm model, however, I trained it on the full initial dataset rather than just the training partition. The final output shows the customer ID in the left column and the probability that that customer buys organics in the right column. This final model only predicted two of the ten people as likely to purchase organics.

Customer Clustering

I clustered the customers to generate customer segments. The intent of these clusters is so the grocery chain can better target its users with its marketing messages. For these clusters, I chose to leave out the target variable TargetBuy since the intent of these clusters is to eventually also target a customer segment with a high probability of purchasing our organics. I ran my clusters across two separate dataframes, where I either:

  1. Removed unordered multilevel factor variables DemClusterGroup, DemReg, and DemTVReg.

  2. Generated dummy variables for the multilevel factor variables.

Clustering on the dataframe containing the dummy variables failed to converge across Elbow, Silhouette, and Gap Statistics methods. Therefore I chose to continue with the first dataframe, where I left the unordered multilevel factor variables entirely out of my clustering analysis.

Preparation

clus_vars <- df %>%
  select(-TargetBuy)
clus_vars <- clus_vars %>%
  select(-c(DemClusterGroup, DemReg, DemTVReg))
clus_vars <- lapply(clus_vars, as.integer) %>%
  data.frame()

I scaled the dataframe to keep any one variable from having too large an impact on my final output clusters.

clus_vars_scaled <- scale(clus_vars) %>%
  data.frame()

Results

I first tried to determine the optimal cluster numbers through both the elbow and silhouette method.

Elbow Method

fviz_nbclust(clus_vars_scaled, kmeans, method = "wss")
Error: cannot allocate vector of size 1.8 Gb

The elbow method, while not completely definitive, suggested that optimum cluster sizes might be 2, 4, or 6.

Silhouette Method

fviz_nbclust(clus_vars_scaled, kmeans, method = "silhouette")

The average silhouette method demonstrates optimal cluster number at 4.

Seeing that both methods disagreed on ideal cluster size, I chose to use the NbClust function to determine which cluster count was optimal across the majority of the 26 non-computationally expensive methods embedded in NbClust. Note that I used argument index = “all” to iterate only on the non-computationally expensive metrics. Since this procedure is only being used to determine optimal number of clusters, not the final clusters for my model, I am choosing to subset my data to cut down on computational time. Therefore I am only running NbClust on 30% of my data.

NbClust

cluster_sample <- sample(c(TRUE, FALSE), nrow(clus_vars_scaled), replace = TRUE, 
                         prob = c(0.3, 0.7))
cluster_subset <- clus_vars_scaled[cluster_sample,]
nb <- NbClust(cluster_subset, distance = "euclidean", min.nc = 2,
        max.nc = 15, method = "complete", index ="all")
fviz_nbclust(nb)

The resuls of this clustering on my subsetted data demonstrated an ideal cluster count of 2. Given that this is not necessarily practical from a business perspective, I am choosing to operate with k = 12 clusters since this was the second-based cluster number.

k12 <- kmeans(clus_vars_scaled, centers = 12, nstart = 50, iter.max = 50)
k12

Each of these clusters was of a substantial size, the smallest of which being of size 141 68.3% of the total variability was accounted for by between-cluster variance.

fviz_cluster(k12, data = clus_vars_scaled)

Note that around 50% of the variability in the customer clusters is explained by Dim1 and Dim2. Also note that 52.2% of the variability in the data can be explained by the two eigenvectors Dim1 and Dim2.

I then chose to analyze which among these clusters had the highest proportion of organic purchases.

## Adding back target variable:
clus_vars <- cbind(clus_vars, TargetBuy = as.integer(df$TargetBuy),
                   Cluster = k12$cluster)

cluster_means <- clus_vars %>%
  group_by(Cluster) %>%
  summarize_all("mean") %>%
  select(TargetBuy, everything()) %>%
  arrange(desc(TargetBuy))

datatable(cluster_means)

Exploring Final Clusters

ggplot(cluster_means, aes(x = reorder(Cluster, -TargetBuy), y = TargetBuy - 1, 
                          fill = TargetBuy - 1)) +
  geom_bar(stat = "identity") +
  scale_fill_gradient(name = "Proportion \nPurchasing \nOrganics") +
  xlab("Cluster") +
  ylab("Organics Purchased") +
  ggtitle("One of Our Segments Purchases Organics More Often")

Note that one cluster by far has the highest probability of purchasing organics. For the following graphs, I will continue to have the bar fill represent organic purchases.

Explanatory Variable Plots

Affluence
ggplot(cluster_means, aes(x = reorder(Cluster, -DemAffl), y = DemAffl, 
                          fill = TargetBuy - 1)) +
  geom_bar(stat = "identity") +
  scale_fill_gradient(name = "Proportion \nPurchasing \nOrganics") +
  xlab("Cluster") +
  ylab("Affluence") +
  ggtitle("The Organics Cluster has more Money")
Age
ggplot(cluster_means, aes(x = reorder(Cluster, -DemAge), y = DemAge, 
                          fill = TargetBuy - 1)) +
  geom_bar(stat = "identity") +
  scale_fill_gradient(name = "Proportion \nPurchasing \nOrganics") +
  xlab("Cluster") +
  ylab("Age") +
  ggtitle("The Organics Cluster is Fairly Young")
Gender
ggplot(cluster_means, aes(x = reorder(Cluster, -DemGender), y = 1.5 - DemGender, 
                          fill = TargetBuy - 1)) +
  geom_bar(stat = "identity") +
  scale_fill_gradient(name = "Proportion \nPurchasing \nOrganics") +
  xlab("Cluster") +
  ylab("Gender") +
  scale_y_continuous(breaks = c(-0.5, 0, 0.5), 
                     labels = c("Male", "Even", "Female")) +
  ggtitle("The Organics Cluster is Mostly Female")
Loyalty Status
ggplot(cluster_means, aes(x = reorder(Cluster, -PromClass), y = PromClass, 
                          fill = TargetBuy - 1)) +
  geom_bar(stat = "identity") +
  scale_y_continuous(breaks = c(1,2,3,4), labels = c("tin", "silver", "gold",
                                                     "platinum")) +
  scale_fill_gradient(name = "Proportion \nPurchasing \nOrganics") +
  ylab("Loyalty Status") +
  xlab("Cluster") +
  ggtitle("Our Most Loyal Customers are not buying Organics")
Annual Store Spending
ggplot(cluster_means, aes(x = reorder(Cluster, -PromSpend), y = PromSpend, 
                          fill = TargetBuy - 1)) +
  geom_bar(stat = "identity") +
  scale_y_continuous(labels = scales::dollar) +
  scale_fill_gradient(name = "Proportion \nPurchasing \nOrganics") +
  ylab("Total Store Spending") +
  xlab("Cluster") +
  ggtitle("Our Highest-Spending Customers are not buying Organics")
Time as Loyalty Card Member
ggplot(cluster_means, aes(x = reorder(Cluster, -PromTime), y = PromTime, 
                          fill = TargetBuy - 1)) +
  geom_bar(stat = "identity") +
  scale_fill_gradient(name = "Proportion \nPurchasing \nOrganics") +
  xlab("Cluster") +
  ylab("Months as Loyalty Card Member") +
  ggtitle("Our Oldest Loyalty Cards Customers are not buying Organics")

Analysis of Explanatory Variables

The primary takeaways from these clusters is that affluence is most tied to organic produce purchases. These clusters also indicate that those who mostly purchase organics tend to not spend much in the store, indicating that the store is not attracting organics-savvy consumers as much as it could be.

Outside of analyzing organics sales, we see that one cluster’s primary feature is those who have had loyalty cards longest. Another cluster spends the most and also consists mostly of platinum members. Based on these features, we know that this cluster is our highest value cluster. Therefore we should target this customer segment to purchase more organics.

LS0tDQp0aXRsZTogIk9yZ2FuaWMgUHJvZHVjZSBBbmFseXNpcyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCiMgT3ZlcnZpZXcgey50YWJzZXQgLnRhYnNldC1mYWRlfQ0KDQojIyBFeGVjdXRpdmUgU3VtbWFyeQ0KDQohW10ob3JnYW5pY3NfcGhvdG8uanBnKQ0KDQojIyMgSW5pdGlhbCBGaW5kaW5ncw0KDQpGb3IgbXkgYW5hbHlzaXMsIEkgZmlyc3QgZXhhbWluZWQgZGlmZmVyZW5jZXMgYmV0d2VlbiBvdXIgY3VzdG9tZXJzIHdobyBib3VnaHQgb3JnYW5pY3MgYW5kIHRob3NlIHdobyBkaWQgbm90LiAgQWNyb3NzIHN0b3JlIHNwZW5kaW5nLCByZWdpb24sIGFuZCBtb250aHMgYXMgbG95YWx0eSBjYXJkIGhvbGRlcnMsIHRoZXJlIHdhcyBsaXR0bGUgdG8gbm8gZGlzY3JlcGFuY3kgYmV0d2VlbiBvdXIgb3JnYW5pYyBhbmQgbm9uLW9yZ2FuaWMgY3VzdG9tZXJzLiAgT3V0c2lkZSBvZiB0aGVzZSB0aHJlZSB2YXJpYWJsZXMsIGhvd2V2ZXIsIHdlIHNlZSBzb21lIGtleSBkaWZmZXJlbmNlcyBiZXR3ZWVuIHRoZXNlIHR3byBjdXN0b21lciBncm91cHMuDQoJDQpGaXJzdGx5LCB0aG9zZSB3aG8gYm91Z2h0IG1vcmUgb3JnYW5pY3Mgd2VyZSBnZW5lcmFsbHkgd2VhbHRoaWVyLiAgVGhpcyBpcyB0byBiZSBleHBlY3RlZC10aG9zZSB3aXRoIG1vcmUgbW9uZXkgaGF2ZSBtb3JlIGRpc3Bvc2FibGUgaW5jb21lIHRvIHNwZW5kIG9uIGhlYWx0aGllciBwcm9kdWNlLiAgU2Vjb25kbHksIG91ciBvcmdhbmljcyBjdXN0b21lcnMgdGVuZCB0byBiZSB5b3VuZ2VyLiAgQXMgeW91bmdlciBnZW5lcmF0aW9ucyBoYXZlIGJlY29tZSBtb3JlIGhlYWx0aCBjb25zY2lvdXMgYWJvdXQgaG93IHRoZWlyIGdyb2NlcmllcyBhcmUgc291cmNlZCwgdGhpcyBhbHNvIGlzIG5vdCBzdXJwcmlzaW5nLiAgSW4gdGVybXMgb2YgZ2VuZGVyLCBvdXIgcHJpbWFyeSBzaG9wcGVycyBhcmUgd29tZW4uICBPdmVyIDcwJSBvZiBhbGwgb3VyIHNhbGVzIGFyZSB0byBmZW1hbGVzLiAgV29tZW4gYWxzbyBoYXZlIGEgaGlnaGVyIHRlbmRlbmN5IHRvIGJ1eSBvcmdhbmljcywgYXMgY2FuIGJlIHNlZW4gYnkgdGhlIG1vc2FpYyBwbG90IGluIHRoZSBHZW5kZXIgdGFiIHVuZGVyIEVEQS4gIA0KQWNyb3NzIHRoZSBwcmUtY2FsY3VsYXRlZCBjbHVzdGVyIGdyb3Vwcywgd2Ugc2VlIHRoYXQgY2x1c3RlciBncm91cCBGIGhhcyBhIGhpZ2hlciB0ZW5kZW5jeSB0byBidXkgb3JnYW5pY3MuICBDbHVzdGVyIGdyb3VwIEEgaXMgdGhlIGxlYXN0IGxpa2VseSB0byBidXkgb3VyIG9yZ2FuaWNzLiAgTW9yZSBpbmZvcm1hdGlvbiBvbiBob3cgdGhlc2UgY2x1c3RlciBncm91cHMgd2VyZSBnZW5lcmF0ZWQgY2FuIGhlbHAgaW4gaW50ZXJwcmV0aW5nIHRoaXMgb3V0cHV0Lg0KTW9zdCBpbXBvcnRhbnRseSwgaG93ZXZlciwgSSBub3RlZCBzb21lIGRpZmZlcmVuY2VzIGFjcm9zcyBvdXIgbG95YWx0eSBzdGF0dXMgY2F0ZWdvcmllcy4gIFRoZSBoaWdoZXIgYSBjdXN0b21lcidzIGxveWFsdHkgc3RhdHVzLCB0aGUgbG93ZXIgdGhlaXIgdGVuZGVuY3kgdG8gcHVyY2hhc2Ugb3JnYW5pY3MuICBUaGlzIHJlbGF0aW9uc2hpcCBob2xkcyB0cnVlIGF0IGV2ZXJ5IGxveWFsdHkgc3RhdHVzIGxldmVsLiAgRGVsdmluZyBmdXJ0aGVyIGludG8gdGhpcyBpc3N1ZSBtaWdodCBlbmFibGUgdXMgYXMgYSBjb21wYW55IHRvIGJvb3N0IG91ciBvcmdhbmljcyBzYWxlcy4NCgkNCiMjIyBQcmVkaWN0aXZlIE1vZGVsaW5nDQoNCkZyb20gaGVyZSBJIHJhbiBzb21lIHByZWRpY3RpdmUgbW9kZWxpbmcgdG8gcHJlZGljdCB0aGUgcHJvYmFiaWxpdHkgdGhhdCBvdXIgY3VzdG9tZXJzIHdpbGwgcHVyY2hhc2Ugb3JnYW5pY3MuICBCYWNraW5nIG91dCB0aGVzZSBtb2RlbHMsIEkgd2FzIGFibGUgdG8gZGV0ZXJtaW5lIHdoaWNoIGluZGVwZW5kZW50IHZhcmlhYmxlcyB3ZXJlIG1vc3Qgc2lnbmlmaWNhbnRseSBpbXBhY3Rpbmcgb3VyIGN1c3RvbWVycycgY2hvaWNlIHRvIGJ1eSBvcmdhbmljcy4gIEJhc2VkIG9uIHRoZSBpbmRlcGVuZGVudCB2YXJpYWJsZXMsIG15IGZpbmFsIGdyYWRpZW50IGJvb3N0aW5nIG1vZGVsIGNhbiBhY2N1cmF0ZWx5IHByZWRpY3Qgd2hpY2ggY3VzdG9tZXJzIHdpbGwgYW5kIHdpbGwgbm90IHB1cmNoYXNlIG91ciBvcmdhbmljcyB3aXRoIG9ubHkgYSBtaXNjbGFzc2lmaWNhdGlvbiByYXRlIG9mIDE5LjM1JS4gIEkgYW5hbHl6ZWQgdGhlIG91dHB1dCBvZiBteSBtb2RlbCB2aWEgYSBkZWNpc2lvbiB0cmVlIG9uIGl0cyBwcmVkaWN0aW9ucyAtIHRoaXMgc2hvd2VkIHRoYXQgdGhlIHZhcmlhYmxlcyBtb3N0IGltcGFjdGluZyB0aGUgbW9kZWwsIGFuZCB0aGVyZWZvcmUgY3VzdG9tZXJzJyBwcm9iYWJpbGl0aWVzIG9mIHB1cmNoYXNpbmcgb3JnYW5pY3MsIGFyZSBhZ2UsIGFmZmx1ZW5jZSwgYW5kIGdlbmRlci4gIFRoaXMgbW9kZWwgd2lsbCBlbmFibGUgb3VyIG1hcmtldGluZyB0ZWFtIHRvIGFzc2VzcyB3aGljaCBvZiBvdXIgY3VzdG9tZXJzIG9uIGFuIGluZGl2aWR1YWwgYmFzaXMgYXJlIG1vc3QgbGlrZWx5IHRvIHB1cmNoYXNlIG91ciBvcmdhbmljcy4gIEFzIGEgZ3JvY2VyLCB3ZSBjYW4gdXNlIHRoZXNlIHByb2JhYmlsaXRpZXMgdG8gYmV0dGVyIHNwZW5kIG91ciBhZHZlcnRpc2luZyBkb2xsYXJzIHRvIGJvb3N0IG9yZ2FuaWNzIHNhbGVzLg0KDQoNCiMjIyBDdXN0b21lciBDbHVzdGVyaW5nDQoNCkFmdGVyIHBlcmZvcm1pbmcgcHJlZGljdGl2ZSBtb2RlbGluZywgSSBhbHNvIGNob3NlIHRvIGltcGxlbWVudCBjbHVzdGVyaW5nIHRlY2huaXF1ZXMgdG8gc3BsaXQgb3VyIGN1c3RvbWVycyBpbnRvIGRpZmZlcmVudCBzZWdtZW50cy4gIEkgdWx0aW1hdGVseSBnZW5lcmF0ZWQgdHdlbHZlIGN1c3RvbWVyIHNlZ21lbnQgY2x1c3RlcnMsIHdpdGggb25lIHNwZWNpZmljIGN1c3RvbWVyIHNlZ21lbnQgaGF2aW5nIGEgaGlnaCB0ZW5kZW5jeSB0byBwdXJjaGFzZSBvdXIgb3JnYW5pY3MuICBJIHRoZW4gY2hvc2UgdG8gZGVsdmUgZnVydGhlciBpbnRvIHRoZSBjaGFyYWN0ZXJpc3RpY3Mgb2YgdGhpcyBjbHVzdGVyLg0KCSANClRoaXMgY2x1c3RlciB3YXMgYm90aCB0aGUgd2VhbHRoaWVzdCBhbmQgdGhlIHNlY29uZCB5b3VuZ2VzdC4gIFRoaXMgZmFsbHMgaW4gbGluZSB3aXRoIHRoZSBpbml0aWFsIGV4cGxvcmF0b3J5IGFuYWx5c2lzLiAgVGhlIGNsdXN0ZXIgd2FzIGFsc28gbW9zdGx5IGZlbWFsZSwgYWx0aG91Z2ggaXQgZGlkIGNvbnRhaW4gc29tZSBtYWxlcyBhcyB3ZWxsLiAgVGhpcyBjbHVzdGVyIGRpZCBub3QgY29uc2lzdCBvZiBvdXIgbW9zdCBsb3lhbCBjdXN0b21lcnM7IG1vc3Qgb2YgdGhlIGN1c3RvbWVycyBpbiB0aGlzIHNlZ21lbnQgaGFkIGVpdGhlciBzaWx2ZXIgb3IgdGluIGxveWFsdHkgc3RhdHVzLg0KCQ0KVGhlIG1vc3Qgd29ycnlpbmcgc3RhdGlzdGljIGFib3V0IHRoaXMgY2x1c3RlciBpcyB0aGUgYW1vdW50IHRoYXQgaXQgc3BlbmRzIGFubnVhbGx5IGluIG91ciBzdG9yZXMuICBUaGlzIGNsdXN0ZXIgb2YgY3VzdG9tZXJzIGhhZCB0aGUgdGhpcmQgbG93ZXN0IGF2ZXJhZ2UgYW5udWFsIGV4cGVuZGl0dXJlcyBhdCBvdXIgc3RvcmVzLiAgT3VyIGhpZ2gtc3BlbmRpbmcgY3VzdG9tZXJzLCBvbiB0aGUgb3RoZXIgaGFuZCwgb25seSBidXkgb3JnYW5pY3MgYXQgYSByYXRlIG9mIDE1JS4gIFRoaXMgbWVhbnMgdGhhdCB3ZSBhcyBhIGdyb2NlciBuZWVkIHRvIHJlcG9zaXRpb24gb3VyIGJyYW5kIGlmIHdlIHJlYWxseSB3YW50IHRvIHNlbGwgbW9yZSBvcmdhbmljcy4gIFRob3NlIHdobyBzaG9wIGF0IG91ciBzdG9yZSB0aGUgbW9zdCBhcmUgbm90IGJ1eWluZyBvcmdhbmljcywgaW5kaWNhdGluZyB0aGF0IG9yZ2FuaWMgcHJvZHVjZSBpcyBub3Qgc29tZXRoaW5nIHRoYXQgcGVvcGxlIGNvbWUgdG8gb3VyIHN0b3JlIG1vc3Qgb2Z0ZW4gdG8gYnV5LiANCg0KIyMjIEdvaW5nIEZvcndhcmQNCg0KV2Ugc2hvdWxkIHNlbmQgbW9yZSB0YXJnZXRlZCBtZXNzYWdpbmcgdG8gb3VyIGhpZ2gtc3BlbmRpbmcgY3VzdG9tZXIgY2x1c3Rlci4gIERvaW5nIHNvLCBob3BlZnVsbHkgd2UgY2FuIGNvbnZpbmNlIHRoaXMgY2x1c3RlciB0byBidXkgbW9yZSBvcmdhbmljcy4gIElmIHdlIGNhbiBzdWNjZXNzZnVsbHkgY29udmVydCB0aGlzIHNlZ21lbnQsIHRoZW4gd2UgYXMgYSBicmFuZCBwb3NpdGlvbiBvdXJzZWx2ZXMgdG8gc2VsbCBtb3JlIG9yZ2FuaWNzIHRvIG91ciBjdXN0b21lcnMgYW5kIGltcHJvdmUgdGhlaXIgaGVhbHRoLiAgV2UgbmVlZCB0byBzZWl6ZSB0aGlzIG9wcG9ydHVuaXR5IGFuZCBjb252aW5jZSB0aG9zZSB3aG8gc2hvcCB3aXRoIHVzIHRoZSBtb3N0IGZyZXF1ZW50bHkgdG8gYnV5IG1vcmUgb3JnYW5pY3MuDQoJDQpJZiB3ZSBjYW4gZWZmZWN0aXZlbHkgZW5nYWdlIHdpdGggdGhlc2UgY2x1c3RlciBhbmQgbGV2ZXJhZ2Ugb3VyIHByZWRpY3RpdmUgbW9kZWwsIHRoZW4gd2UgY2FuIGJldHRlciBzcGVuZCBvdXIgYWR2ZXJ0aXNpbmcgZG9sbGFycyBhbmQgZGlzYnVyc2Ugb3VyIGNvdXBvbi4gIFRoaXMgc2hvdWxkIGJvb3N0IG91ciBvcmdhbmljIHNhbGVzIGFjcm9zcyB0aGUgYm9hcmQsIGJvb3N0aW5nIG91ciBwdWJsaWMgaW1hZ2UsIGFuZCBtYWtlIHVzIHRoZSBoZWFsdGh5IGdyb2NlciBvZiB0b21vcnJvdy4NCg0KIyMgRGF0YSBEaWN0aW9uYXJ5DQoNCmBgYHtyfQ0KZGF0YV9kaWN0aW9uYXJ5IDwtIHJlYWQuY3N2KCJEYXRhIERpY3Rpb25hcnkuY3N2IikNCmNvbG5hbWVzKGRhdGFfZGljdGlvbmFyeSkgPC0gYygiVmFyaWFibGUiLCAiRGVzY3JpcHRpb24iKQ0KZGF0YV9kaWN0aW9uYXJ5ICU+JQ0KICBkYXRhdGFibGUocm93bmFtZXMgPSBGQUxTRSkNCmBgYA0KDQojIyBSZXF1aXJlZCBQYWNrYWdlcw0KDQpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQ0KbGlicmFyeShwbHlyKSAgICAgICAgICAgICMgRm9yIGNhcmV0IHBhY2thZ2UNCmxpYnJhcnkodGlkeXZlcnNlKSAgICAgICAjIEZvciBkYXRhIG1hbmlwdWxhdGlvbg0KbGlicmFyeShtaXNzUmFuZ2VyKSAgICAgICMgRm9yIG1pc3NpbmcgdmFsdWUgaW1wdXRhdGlvbg0KbGlicmFyeShsZWFwcykgICAgICAgICAgICMgRm9yIGdyYWRpZW50IGJvb3N0aW5nDQpsaWJyYXJ5KHJwYXJ0KSAgICAgICAgICAgIyBGb3IgZGVjaXNpb24gdHJlZQ0KbGlicmFyeShyYW5kb21Gb3Jlc3QpICAgICMgRm9yIHJhbmRvbSBmb3Jlc3QgbW9kZWwNCmxpYnJhcnkoZTEwNzEpICAgICAgICAgICAjIEZvciBzdm0gbW9kZWwNCmxpYnJhcnkoY2FyZXQpICAgICAgICAgICAjIEZvciByZWdyZXNzaW9uIGFuYWx5c2lzDQpsaWJyYXJ5KHJhdHRsZSkgICAgICAgICAgIyBGb3IgcGxvdGluZyBkZWNpc2lvbiB0cmVlDQpsaWJyYXJ5KGZhY3RvZXh0cmEpICAgICAgIyBGb3IgY2x1c3RlciBhbmFseXNpcw0KbGlicmFyeShjbHVzdGVyKSAgICAgICAgICMgRm9yIGNsdXN0ZXIgYW5hbHlzaXMNCmxpYnJhcnkoTmJDbHVzdCkgICAgICAgICAjIEZvciBjbHVzdGVyIGFuYWx5c2lzDQpsaWJyYXJ5KERUKSAgICAgICAgICAgICAgIyBGb3IgZGF0YXRhYmxlIHZpc3VhbGl6YXRpb24NCmBgYA0KDQojIyBJbXBvcnQgYW5kIEZvcm1hdA0KDQpgYGB7cn0NCmRmIDwtIHJlYWQuY3N2KCJvcmdhbmljcy5jc3YiKQ0KDQpkZiA8LSBkZiAlPiUNCiAgc2VsZWN0KC1EZW1DbHVzdGVyKQ0KDQpjb2xuYW1lcyhkZilbMV0gPC0gIklEIg0KYGBgDQoNClRoZSBpbml0aWFsIGRhdGFzZXQgY29uc2lzdGVkIG9mIDExIHZhcmlhYmxlcyBhbmQgMjIsMjIzIG9ic2VydmF0aW9ucy4gIEkgZHJvcHBlZCB0aGUgPGNvZGU+RGVtQ2x1c3RlcjwvY29kZT4gdmFyaWFibGUgYmVjYXVzZSBvZiB0aGUgaGlnaCBudW1iZXIgb2YgbGV2ZWxzIGFuZCBzaW5jZSB0aGlzIHZhcmlhYmxlIGhhZCBhbHJlYWR5IGJlZW4gdXNlZCB0byBnZW5lcmF0ZSB0aGUgPGNvZGU+RGVtQ2x1c3Rlckdyb3VwPC9jb2RlPiB2YXJpYWJsZS4NCg0KVGhlIGluaXRpYWwgZGF0YXNldCBhbHNvIGNvbnRhaW5lZCBzb21lICJVIiB2YWx1ZXMgYW5kIHNvbWUgIiIgdmFsdWVzLCBpbmRpY2F0aW5nIHRoYXQgdGhlc2UgdmFsdWVzIHdlcmUgdW5rbm93bi4gIEkgdGhlcmVmb3JlIGNoYW5nZWQgdGhlc2UgdmFsdWVzIHRvIE5BLg0KDQpgYGB7cn0NCmRmJERlbUNsdXN0ZXJHcm91cFtkZiREZW1DbHVzdGVyR3JvdXAgJWluJSBjKCJVIiwiIildIDwtIE5BDQpkZiREZW1HZW5kZXJbZGYkRGVtR2VuZGVyICVpbiUgYygiVSIsIiIpXSA8LSBOQQ0KZGYkRGVtUmVnW2RmJERlbVJlZyA9PSAiIl0gPC0gTkENCmRmJERlbVRWUmVnW2RmJERlbVRWUmVnID09ICIiXSA8LSBOQQ0KYGBgDQoNCkkgY29udmVydGVkIHRoZSA8Y29kZT5Qcm9tVGltZTwvY29kZT4gdmFyaWFibGUgdG8gYW4gb3JkZXJlZCBmYWN0b3IsIGFzc3VtaW5nIHRoZSBvcmRlcmluZyB0aGF0ICJ0aW4iPCJzaWx2ZXIiPCJnb2xkIjwicGxhdGludW0iLiAgSSBhbHNvIGNvbnZlcnRlZCB0aGUgYmluYXJ5IHRhcmdldCB2YXJpYWJsZSB0byBhIGZhY3Rvci4gIEFmdGVyIGRvaW5nIHNvLCBJIGRyb3BwZWQgYWxsIHVudXNlZCBmYWN0b3IgbGV2ZWxzIGZyb20gdGhlIGRhdGEuDQoNCmBgYHtyfQ0KZGYkUHJvbUNsYXNzIDwtIGZhY3RvcihkZiRQcm9tQ2xhc3MsIG9yZGVyZWQgPSBUUlVFLCANCiAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygiVGluIiwgIlNpbHZlciIsICJHb2xkIiwgIlBsYXRpbnVtIikpDQoNCmRmJFRhcmdldEJ1eSA8LSBmYWN0b3IoZGYkVGFyZ2V0QnV5KQ0KDQpkZiA8LSBkcm9wbGV2ZWxzKGRmKQ0KYGBgDQoNCkF0IHRoaXMgcG9pbnQgdGhlcmUgd2VyZSBzdGlsbCA4NzkxIG1pc3NpbmcgdmFsdWVzIGluIHRoZSBkYXRhc2V0LiAgQmVmb3JlIGltcHV0aW5nIHRoZXNlIHZhbHVlcywgSSBmaXJzdCBwZXJmb3JtZWQgc29tZSBleHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzLg0KDQojIyBFREENCg0KSSBjaG9zZSB0byBmaXJzdCBkZXRlcm1pbmUgaWYgdGhlcmUgd2VyZSBhbnkgbm90aWNlYWJsZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIGN1c3RvbWVycyB0aGF0IHB1cmNoYXNlIG9yZ2FuaWNzIHZlcnN1cyB0aG9zZSB3aG8gZG8gbm90LiAgSSBkaWQgdGhpcyBhY3Jvc3MgZWFjaCBvZiB0aGUgZXhwbGFuYXRvcnkgYXR0cmlidXRlcy4NCg0KIyMjIERlcGVuZGVudCBWYXJpYWJsZXMgey50YWJzZXQgLnRhYnNldC1mYWRlfQ0KDQojIyMjIEFmZmx1ZW5jZQ0KDQpgYGB7ciwgd2FybmluZyA9IEZBTFNFfQ0KZGYgJT4lDQogIGdyb3VwX2J5KFRhcmdldEJ1eSkgJT4lDQogIGdncGxvdChhZXMoeCA9IFRhcmdldEJ1eSwgeSA9IERlbUFmZmwpKSArDQogIGdlb21fYm94cGxvdCgpICsNCiAgeGxhYigiT3JnYW5pY3MiKSArDQogIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gYygiRGlkbid0IEJ1eSIsICJCb3VnaHQiKSkgKw0KICB5bGFiKCJBZmZsdWVuY2UgKDEtMzApIikgKw0KICBnZ3RpdGxlKCJXZWFsdGhpZXIgUGVvcGxlIEJ1eSBNb3JlIE9yZ2FuaWNzIikNCmBgYA0KDQoNCiMjIyMgQWdlDQoNCmBgYHtyLCB3YXJuaW5nID0gRkFMU0V9DQpkZiAlPiUNCiAgZ3JvdXBfYnkoVGFyZ2V0QnV5KSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gVGFyZ2V0QnV5LCB5ID0gRGVtQWdlKSkgKw0KICBnZW9tX2JveHBsb3QoKSArDQogIHhsYWIoIk9yZ2FuaWNzIikgKw0KICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IGMoIkRpZG4ndCBCdXkiLCAiQm91Z2h0IikpICsNCiAgeWxhYigiQWdlIikgKw0KICBnZ3RpdGxlKCJZb3VuZ2VyIFBlb3BsZSBCdXkgTW9yZSBPcmdhbmljcyIpDQpgYGANCg0KDQojIyMjIEdlbmRlcg0KDQpgYGB7cn0NCnRhYmxlKGRmJERlbUdlbmRlciwgZGYkVGFyZ2V0QnV5KSAlPiUNCiAgcHJvcC50YWJsZSgpICU+JQ0KICBwcmludCgpICU+JQ0KICBtb3NhaWNwbG90KG1haW4gPSAiT3JnYW5pYyBQdXJjaGFzZXMgYW5kIEdlbmRlcnMiLCB4bGFiID0gIkdlbmRlciIsIA0KICAgICAgICAgICAgIHlsYWIgPSAiV2hldGhlciBUaGV5IFB1cmNoYXNlIE9yZ2FuaWNzIikNCg0KYGBgDQoNCiMjIyMgQ2x1c3RlciBHcm91cA0KDQpgYGB7cn0NCnRhYmxlKGRmJERlbUNsdXN0ZXJHcm91cCwgZGYkVGFyZ2V0QnV5KSAlPiUNCiAgcHJvcC50YWJsZSgpICU+JQ0KICBwcmludCgpICU+JQ0KICBtb3NhaWNwbG90KG1haW4gPSAiT3JnYW5pYyBQdXJjaGFzZXMgYW5kIENsdXN0ZXIgR3JvdXAiLCANCiAgICAgICAgICAgICB4bGFiID0gIkNsdXN0ZXIgR3JvdXAiLCB5bGFiID0gIldoZXRoZXIgVGhleSBQdXJjaGFzZSBPcmdhbmljcyIpDQpgYGANCg0KIFdlIHNlZSB0aGF0IGFtb25nIG91ciBjbHVzdGVyIGdyb3VwcywgdGhlIEYgZ3JvdXAgdGVuZHMgdG8gYnV5IG1vcmUgb3JnYW5pY3MuDQoNCiMjIyMgUmVnaW9uDQoNCmBgYHtyfQ0KdGFibGUoZGYkRGVtUmVnLCBkZiRUYXJnZXRCdXkpICU+JQ0KICBwcm9wLnRhYmxlKCkgJT4lDQogIHByaW50KCkgJT4lDQogIG1vc2FpY3Bsb3QobWFpbiA9ICJPcmdhbmljIFB1cmNoYXNlcyBhbmQgUmVnaW9uIiwgeGxhYiA9ICJSZWdpb24iLA0KICAgICAgICAgICAgIHlsYWIgPSAiV2hldGhlciBUaGV5IFB1cmNoYXNlIE9yZ2FuaWNzIikNCmBgYA0KDQpXZSBzZWUgdGhhdCBtb3N0IG9mIG91ciBzaG9wcGVycyBhcmUgZnJvbSB0aGUgU291dGggRWFzdCwgZm9sbG93ZWQgYnkgdGhlIE1pZGxhbmRzIGFuZCB0aGUgTm9ydGguICBBY3Jvc3MgYWxsIHJlZ2lvbnMsIHdlIGRvbid0IHNlZSBtYWpvciBkaXNjcmVwYW5jaWVzIGluIHRoZSBwcm9wb3J0aW9uIG9mIG9yZ2FuaWNzIHNvbGQuDQoNCiMjIyMgTG95YWx0eSBTdGF0dXMNCg0KYGBge3J9DQp0YWJsZShkZiRQcm9tQ2xhc3MsIGRmJFRhcmdldEJ1eSkgJT4lDQogIHByb3AudGFibGUoKSAlPiUNCiAgcHJpbnQoKSAlPiUNCiAgbW9zYWljcGxvdChtYWluID0gIk9yZ2FuaWMgUHVyY2hhc2VzIGFuZCBMb3lhbHR5IFN0YXR1cyIsIA0KICAgICAgICAgICAgIHhsYWIgPSAiTG95YWx0eSBTdGF0dXMiLCB5bGFiID0gIldoZXRoZXIgVGhleSBQdXJjaGFzZSBPcmdhbmljcyIpDQoNCmBgYA0KDQpBY3Jvc3MgbG95YWx0eSBzdGF0dXMsIHdlIHNlZSB0aGF0IG91ciBsb3lhbCBjdXN0b21lcnMgYnV5IGZld2VyIG9yZ2FuaWNzLg0KDQojIyMjIFN0b3JlIFNwZW5kaW5nDQoNCmBgYHtyfQ0KZGYgJT4lDQogIGdyb3VwX2J5KFRhcmdldEJ1eSkgJT4lDQogIGdncGxvdChhZXMoeCA9IFRhcmdldEJ1eSwgeSA9IFByb21TcGVuZCkpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICB4bGFiKCJPcmdhbmljcyIpICsNCiAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBjKCJEaWRuJ3QgQnV5IiwgIkJvdWdodCIpKSArDQogIHlsYWIoIlN0b3JlIFNwZW5kaW5nIikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpkb2xsYXIpICsNCiAgZ2d0aXRsZSgiV2VhbHRoaWVyIFBlb3BsZSBCdXkgTW9yZSBPcmdhbmljcyIpDQpgYGANCg0KV2Ugc2VlIHRoYXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiBncm9jZXJ5IHNwZW5kaW5nIGlzIGhpZ2hseSBza2V3ZWQgcmlnaHQgYmFzZWQgb24gdGhlc2UgYm94cGxvdHMuICBXZSBzZWUgdGhhdCB0aGVyZSBhcmUgdGhyZWUgcGVvcGxlIHdobyBhcmUgc3BlbmRpbmcgb3ZlciAkMjAwLDAwMCBvbiBncm9jZXJpZXMuICBHaXZlbiB0aGF0IHRoaXMgc2VlbXMgZmFpcmx5IGV4dHJlbWUsIEkgZGVjaWRlZCB0byBkaWcgbW9yZSBpbnRvIHRoZXNlIG9ic2VydmF0aW9ucy4NCg0KYGBge3J9DQpkZltjKDIwNTcsIDM0ODMsIDExNDc2KSxdDQpgYGANCg0KR2l2ZW4gdGhhdCB0aGVzZSB0aHJlZSBvdXRsaWVycyBoYXZlIGF2ZXJhZ2UgYWZmbHVlbmNlIGxldmVscywgSSBhbSBvcGVyYXRpbmcgdW5kZXIgdGhlIGFzc3VtcHRpb24gdGhhdCB0aGVpciBzcGVuZGluZyB3YXMgbm90IGFib3ZlICQyMDAsMDAwLS1yYXRoZXIsIHRoZXkgbWlzcmVjb3JkZWQgYnkgdHdvIGRlY2ltYWwgcGxhY2VzLiAgSSBhbSBpbXB1dGluZyB0aGVtIGFzIHN1Y2guDQoNCmBgYHtyfQ0KZGZbYygyMDU3LCAzNDgzLCAxMTQ3NiksICJQcm9tU3BlbmQiXSA8LSBkZltjKDIwNTcsIDM0ODMsIDExNDc2KSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJQcm9tU3BlbmQiXSAvIDEwMA0KYGBgDQoNCg0KIyMjIyBNb250aHMgYXMgTG95YWx0eSBNZW1iZXINCg0KYGBge3IsIHdhcm5pbmcgPSBGQUxTRX0NCmRmICU+JQ0KICBncm91cF9ieShUYXJnZXRCdXkpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBUYXJnZXRCdXksIHkgPSBQcm9tVGltZSkpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICB4bGFiKCJPcmdhbmljcyIpICsNCiAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBjKCJEaWRuJ3QgQnV5IiwgIkJvdWdodCIpKSArDQogIHlsYWIoIk1vbnRocyBhcyBMb3lhbHR5IENhcmQgTWVtYmVyIikgKw0KICBnZ3RpdGxlKCJDb21wYXJpbmcgTW9udGhzIGFzIE1lbWJlciB0byBPcmdhbmljcyBQdXJjaGFzZXMiKQ0KYGBgDQoNCldlIGRvbid0IHNlZSBhIGRpZmZlcmVuY2UgaW4gdGhlIGRpc3RyaWJ1dGlvbiBvZiBtb250aHMgYXMgbG95YWx0eSBjYXJkIG1lbWJlcnMgYmV0d2VlbiB0aG9zZSB3aG8gYm91Z2h0IG9yZ2FuaWNzIGFuZCB3aG8gZGlkbid0Lg0KDQoNCiMjIEltcHV0YXRpb24NCg0KSSBvcGVyYXRlZCB1bmRlciB0aGUgYXNzdW1wdGlvbiB0aGF0IHRoZSByZW1haW5pbmcgODc5MSBtaXNzaW5nIHZhbHVlcyB3ZXJlIG1pc3NpbmcgYXQgcmFuZG9tLiAgVGhlcmVmb3JlIEkgY2hvc2UgdG8gdXNlIHRoZSA8Y29kZT5taXNzUmFuZ2VyPC9jb2RlPiBmdW5jdGlvbiB0byBpbXB1dGUgdGhlIG1pc3NpbmcgdmFsdWVzLiAgVGhpcyBmdW5jdGlvbiBpbXB1dGVzIG1peGVkLXR5cGUgZGF0YSBzZXRzIGJ5IGNoYWluaW5nIHRyZWUgZW5zZW1ibGVzLg0KDQpgYGB7cn0NCmRmWzE6MTBdIDwtIG1pc3NSYW5nZXIoZGZbMToxMF0sIHNlZWQgPSA0MikNCmBgYA0KDQojIyBQcmVkaWN0aXZlIE1vZGVsaW5nIHsudGFic2V0IC50YWJzZXQtZmFkZX0NCkkgZmlyc3Qgc3BsaXQgbXkgZGF0YSBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0aW5nIGRhdGEuICBJIGRyb3BwZWQgdGhlIDxjb2RlPlRhcmdldEFtdDwvY29kZT4gdmFyaWFibGUgYmVjYXVzZSB0aGUgcHVycG9zZSBvZiBteSBtb2RlbHMgd2FzIHRvIHByZWRpY3Qgd2hldGhlciBzb21lb25lIHB1cmNoYXNlZCBvcmdhbmljcywgbm90IGhvdyBtYW55IG9yZ2FuaWNzIHRoZXkgYm91Z2h0LiAgSSBhbHNvIGRyb3BwZWQgdGhlIDxjb2RlPklEPC9jb2RlPiB2YXJpYWJsZSBzaW5jZSB0aGlzIHZhcmlhYmxlIGhhcyBubyBpbXBhY3Qgb24gbXkgdGFyZ2V0IHZhcmlhYmxlLg0KDQpgYGB7cn0NCnNldC5zZWVkKDQyKQ0KZGYgPC0gZGYgJT4lDQogIHNlbGVjdCgtVGFyZ2V0QW10LCAtSUQpDQoNCnNhbXBsZSA8LSBzYW1wbGUoYyhUUlVFLCBGQUxTRSksIG5yb3coZGYpLCByZXBsYWNlID0gVFJVRSwgDQogICAgICAgICAgICAgICAgIHByb2IgPSBjKDAuNywgMC4zKSkNCnRyYWluIDwtIGRmW3NhbXBsZSwgXQ0KdGVzdCA8LSBkZlshc2FtcGxlLCBdDQpgYGANCg0KSSB1c2VkIGEgbG9naXN0aWMgcmVncmVzc2lvbiwgZGVjaXNpb24gdHJlZSwgcmFuZG9tIGZvcmVzdCwgZ3JhZGllbnQgYm9vc3RpbmcgbW9kZWwsIGFuZCBzdXBwb3J0IHZlY3RvciBtYWNoaW5lIHRvIGRldGVybWluZSB3aGljaCBtb2RlbCBkaWQgdGhlIGJlc3Qgam9iIHByZWRpY3RpbmcgdGhlIDxjb2RlPlRhcmdldEJ1eTwvY29kZT4gdmFyaWFibGUuDQoNCiMjIyBMb2dpc3RpYyBSZWdyZXNzaW9uDQoNCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHJlc3VsdHMgPSAnaGlkZSd9DQpsb2dpdF9udWxsIDwtIGdsbShUYXJnZXRCdXkgfiAxLCBkYXRhID0gdHJhaW4sIA0KICAgICAgICAgICAgICAgICAgZmFtaWx5ID0gYmlub21pYWwobGluayA9ICJsb2dpdCIpKQ0KbG9naXRfZnVsbCA8LSBnbG0oVGFyZ2V0QnV5IH4gLiwgZGF0YSA9IHRyYWluLCANCiAgICAgICAgICAgICAgICAgIGZhbWlseSA9IGJpbm9taWFsKGxpbmsgPSAibG9naXQiKSkNCg0KbG9naXRfZml0IDwtIHN0ZXAobG9naXRfbnVsbCxzY29wZT1mb3JtdWxhKGxvZ2l0X2Z1bGwpLA0KICAgICAgICAgICAgICAgICAgICBkaXJlY3Rpb249ImJvdGgiLGs9MikNCg0KbG9naXRfcHJlZGljdGlvbnMgPC0gcHJlZGljdChsb2dpdF9maXQsIG5ld2RhdGEgPSB0ZXN0LCB0eXBlID0gInJlc3BvbnNlIikNCg0KbG9naXRfY29uZl9tYXRyaXggPC0gdGFibGUodGVzdCRUYXJnZXRCdXksIGxvZ2l0X3ByZWRpY3Rpb25zID4gMC41KSAlPiUNCiAgcHJvcC50YWJsZSgpDQpgYGANCg0KSSB1c2VkIGJvdGggZm9yd2FyZCBhbmQgYmFja3dhcmQgc2VsZWN0aW9uIHRvIGRldGVybWluZSBteSBmaW5hbCBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsLiAgVGhlIGZpbmFsIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgcHJlZGljdG9yIHZhcmlhYmxlcyB3ZXJlIDxjb2RlPkRlbUFmZmw8L2NvZGU+LCA8Y29kZT5EZW1BZ2U8L2NvZGU+LCBhbmQgPGNvZGU+RGVtR2VuZGVyPC9jb2RlPiwgYWxsIG9mIHdoaWNoIHdlcmUgc2lnbmlmaWNhbnQgYXQgdGhlIDk5JSBjb25maWRlbmNlIGxldmVsLiAgQ3VzdG9tZXIgYWZmbHVlbmNlIGluY3JlYXNlZCB0aGUgcHJvYmFiaWxpdHkgb2YgYSBjdXN0b21lciBwdXJjaGFzaW5nIG9yZ2FuaWNzLCB3aGljaCBpbmNyZWFzZWQgYWdlIGFuZCBiZWluZyBtYWxlIGJvdGggZGVjcmVhc2VkIHRoZSBwcm9iYWJpbGl0eSBvZiBhIGN1c3RvbWVyIHB1cmNoYXNpbmcgb3JnYW5pY3MuDQoNCmBgYHtyfQ0Kc3VtbWFyeShsb2dpdF9maXQpDQpgYGANCg0KDQojIyMgRGVjaXNpb24gVHJlZQ0KDQpgYGB7cn0NCnRyZWVfZml0IDwtIHJwYXJ0KFRhcmdldEJ1eSB+IC4sIGRhdGEgPSB0cmFpbiwgbWV0aG9kID0gImNsYXNzIikNCg0KdHJlZV9wcmVkaWN0aW9ucyA8LSBwcmVkaWN0KHRyZWVfZml0LCBuZXdkYXRhID0gdGVzdCwgdHlwZSA9ICJjbGFzcyIpDQoNCnRyZWVfY29uZl9tYXRyaXggPC0gdGFibGUodGVzdCRUYXJnZXRCdXksIHRyZWVfcHJlZGljdGlvbnMpICU+JSANCiAgcHJvcC50YWJsZSgpDQoNCmZhbmN5UnBhcnRQbG90KHRyZWVfZml0KQ0KYGBgDQpUaGUgb3V0cHV0IG9mIG15IHRyZWUgbWV0aG9kIHNob3dlZCB0aGF0IGFnZSB3YXMgdGhlIHNpbmdsZSBtb3N0IGltcG9ydGFudCB2YXJpYWJsZSBpbiBkZXRlcm1pbmluZyB3aGV0aGVyIG9yIG5vdCBzb21lb25lIHB1cmNoYXNlZCBvcmdhbmljcywgd2l0aCB0aG9zZSBhZ2UgNDUgb3Igb3ZlciBub3QgYnV5aW5nIG9yZ2FuaWMuICBUaGUgbmV4dCBtb3N0IHNpZ25pZmljYW50IHNwbGl0IHdhcyBvbiBhZmZsdWVuY2UsIHdpdGggdGhvc2UgaGF2aW5nIG1vcmUgbW9uZXkgdGVuZGluZyB0byBwdXJjaGFzZSBtb3JlIG9yZ2FuaWNzLg0KDQojIyMgUmFuZG9tIEZvcmVzdA0KDQpgYGB7cn0NCmZvcmVzdF9maXQgPC0gcmFuZG9tRm9yZXN0KFRhcmdldEJ1eSB+IC4sIGRhdGEgPSB0cmFpbiwgbnRyZWUgPSA1MDApDQoNCmZvcmVzdF9wcmVkaWN0aW9ucyA8LSBwcmVkaWN0KGZvcmVzdF9maXQsIG5ld2RhdGEgPSB0ZXN0LCB0eXBlID0gImNsYXNzIikNCg0KZm9yZXN0X2NvbmZfbWF0cml4IDwtIHRhYmxlKHRlc3QkVGFyZ2V0QnV5LCBmb3Jlc3RfcHJlZGljdGlvbnMpICU+JQ0KICBwcm9wLnRhYmxlKCkNCmBgYA0KDQojIyMgR3JhZGllbnQgQm9vc3RpbmcNCg0KYGBge3IsIG1lc3NhZ2UgPSBGQUxTRX0NCmZpdF9jb250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMCkNCnR1bmVfZ3JpZCA8LSBleHBhbmQuZ3JpZChpbnRlcmFjdGlvbi5kZXB0aCA9IDIsIG4udHJlZXMgPSA1MDAsIHNocmlua2FnZSA9IDAuMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBuLm1pbm9ic2lubm9kZSA9IDEwKSAgICAgICAgICAgICAgICAgICAgICAgICAgICANCmdibV9maXQgPC0gdHJhaW4oVGFyZ2V0QnV5IH4gLiwgZGF0YSA9IHRyYWluLCBtZXRob2QgPSAiZ2JtIiwgDQogICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gZml0X2NvbnRyb2wsIHZlcmJvc2UgPSBGQUxTRSwgDQogICAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSB0dW5lX2dyaWQpDQoNCmdibV9wcmVkaWN0aW9ucyA8LSBwcmVkaWN0KGdibV9maXQsIG5ld2RhdGEgPSB0ZXN0LCB0eXBlID0gInByb2IiKQ0KDQpnYm1fY29uZl9tYXRyaXggPC0gdGFibGUodGVzdCRUYXJnZXRCdXksIGdibV9wcmVkaWN0aW9uc1ssIDJdID4gMC41KSAlPiUNCiAgcHJvcC50YWJsZSgpDQpgYGANCg0KIyMjIFNWTQ0KDQpgYGB7cn0NCnN2bV9maXQgPC0gc3ZtKFRhcmdldEJ1eSB+IC4sIGRhdGEgPSB0cmFpbikNCg0Kc3ZtX3ByZWRpY3Rpb25zIDwtIHByZWRpY3Qoc3ZtX2ZpdCwgbmV3ZGF0YSA9IHRlc3QsIHR5cGUgPSAicHJvYiIpDQoNCnN2bV9jb25mX21hdHJpeCA8LSB0YWJsZSh0ZXN0JFRhcmdldEJ1eSwgc3ZtX3ByZWRpY3Rpb25zKSAlPiUNCiAgcHJvcC50YWJsZSgpDQpgYGANCg0KIyMgTW9kZWwgQ29tcGFyaXNvbg0KDQpgYGB7cn0NCm1vZGVscyA8LSBsaXN0KA0KICBsb2dpdCA9IGxvZ2l0X2NvbmZfbWF0cml4LA0KICB0cmVlID0gdHJlZV9jb25mX21hdHJpeCwNCiAgZm9yZXN0ID0gZm9yZXN0X2NvbmZfbWF0cml4LA0KICBib29zdGluZyA9IGdibV9jb25mX21hdHJpeCwNCiAgc3ZtID0gc3ZtX2NvbmZfbWF0cml4DQopDQoNCmZvciAoaSBpbiAxOmxlbmd0aChtb2RlbHMpKXsNCiAgcm93bmFtZXMobW9kZWxzW1tpXV0pIDwtIGMoIkJvdWdodCIsICJEaWRuJ3QgQnV5IikNCiAgY29sbmFtZXMobW9kZWxzW1tpXV0pIDwtIGMoIlByZWRpY3RlZCBObyBCdXkiLCAiUHJlZGljdGVkIEJ1eSIpDQp9DQoNCm1pc2NsYXNzaWZpY2F0aW9uX3JhdGVzIDwtIGxpc3QoDQogIGxvZ2l0ID0gcGFzdGUoIk1pc2NsYXNzaWZpY2F0aW9uIFJhdGUgPSIsDQogICAgICAgICAgICAgICAgbWVhbih0ZXN0JFRhcmdldEJ1eSAhPSByb3VuZChsb2dpdF9wcmVkaWN0aW9ucywgMCkpKSwNCiAgdHJlZSA9IHBhc3RlKCJNaXNjbGFzc2lmaWNhdGlvbiBSYXRlID0iLCANCiAgICAgICAgICAgICAgIG1lYW4odGVzdCRUYXJnZXRCdXkgIT0gdHJlZV9wcmVkaWN0aW9ucykpLA0KICBmb3Jlc3QgPSBwYXN0ZSgiTWlzY2xhc3NpZmljYXRpb24gUmF0ZSA9IiwgDQogICAgICAgICAgICAgICAgIG1lYW4odGVzdCRUYXJnZXRCdXkgIT0gZm9yZXN0X3ByZWRpY3Rpb25zKSksDQogIGdibSA9IHBhc3RlKCJNaXNjbGFzc2lmaWNhdGlvbiBSYXRlID0iLA0KICAgICAgICAgICAgICAgICAgICBtZWFuKHRlc3QkVGFyZ2V0QnV5ICE9IHJvdW5kKGdibV9wcmVkaWN0aW9uc1ssIDJdLCAwKSkpLA0KICBzdm0gPSBwYXN0ZSgiTWlzY2xhc3NpZmljYXRpb24gUmF0ZSA9IiwgDQogICAgICAgICAgICAgIG1lYW4odGVzdCRUYXJnZXRCdXkgIT0gc3ZtX3ByZWRpY3Rpb25zKSkNCikNCg0KcHJpbnQobW9kZWxzKQ0KcHJpbnQobWlzY2xhc3NpZmljYXRpb25fcmF0ZXMpDQpgYGANCg0KVGhlIGZpbmFsIG1vZGVsIHdpdGggdGhlIGxvd2VzdCBtaXNjbGFzc2lmaWNhdGlvbiByYXRlIHdhcyB0aGUgZ3JhZGllbnQgYm9vc3RpbmcgbW9kZWwuICBJIGFtIHRoZXJlZm9yZSBjaG9vc2luZyB0byBwcm9jZWVkIHdpdGggdGhlIGdyYWRpZW50IGJvb3N0aW5nIG1vZGVsIGZvciBwcmVkaWN0aW5nIHdoZXRoZXIgb3Igbm90IGEgY3VzdG9tZXIgd2lsbCBwdXJjaGFzZSBvcmdhbmljcy4gIEkgaW1wbGVtZW50ZWQgYSBkZWNpc2lvbiB0cmVlIHRyeWluZyB0byBwcmVkaWN0IG15IGdyYWRpZW50IGJvb3N0aW5nIG1vZGVsIHByZWRpY3Rpb25zIGJhc2VkIG9uIHRoZSB4IGlucHV0cyB0byB0aGUgbW9kZWwgdG8gZGlzY2VybiB3aGljaCB2YXJpYWJsZXMgaGFkIHRoZSBiaWdnZXN0IGltcGFjdCBvbiBteSBncmFkaWVudCBib29zdGluZyBtb2RlbCBwcmVkaWN0aW9ucy4NCg0KYGBge3J9DQp0cmFpbl9wcmVkaWN0aW9ucyA8LSBwcmVkaWN0KGdibV9maXQsIG5ld2RhdGEgPSB0cmFpbiwgdHlwZSA9ICJwcm9iIikNCg0KdHJhaW5fZGZfd19wcmVkaWN0aW9ucyA8LSB0cmFpbiAlPiUNCiAgbXV0YXRlKFByZWRpY3Rpb24gPSByb3VuZCh0cmFpbl9wcmVkaWN0aW9uc1ssIDJdLCAwKSkgJT4lDQogIHNlbGVjdCgtVGFyZ2V0QnV5KQ0KDQptb2RlbF9hc3Nlc3NtZW50X3RyZWUgPC0gcnBhcnQoUHJlZGljdGlvbiB+IC4sIGRhdGEgPSB0cmFpbl9kZl93X3ByZWRpY3Rpb25zLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJjbGFzcyIpDQoNCmZhbmN5UnBhcnRQbG90KG1vZGVsX2Fzc2Vzc21lbnRfdHJlZSkNCmBgYA0KDQpCYXNlZCBvbiB0aGUgb3V0cHV0IG9mIHRoZSBkZWNpc2lvbiB0cmVlLCB0aGUgdmFyaWFibGVzIG1vc3QgaW1wYWN0aW5nIG15IGZpbmFsIG1vZGVsJ3MgcHJlZGljdGlvbnMgd2VyZSBhZ2UgZm9sbG93ZWQgYnkgYWZmbHVlbmNlIGZvbGxvd2VkIGJ5IGdlbmRlci4gIFRoZXNlIHRocmVlIHZhcmlhYmxlcyB3ZXJlIHRoZSBvbmx5IGF0dHJpYnV0ZXMgcmVwcmVzZW50ZWQgaW4gbXkgZGVjaXNpb24gdHJlZSBtb2RlbGluZyBvZiBteSBnYm0gcHJlZGljdGlvbnMuDQoNCiMjIFNjb3JlZCBTZXQNCg0KYGBge3J9DQpuZXdfZGF0YSA8LSByZWFkLmNzdigiTmV3X29yZ2FuaWNzLmNzdiIpDQoNCm5ld19kYXRhIDwtIG5ld19kYXRhICU+JQ0KICBzZWxlY3QoLURlbUNsdXN0ZXIpDQoNCm5ld19kYXRhJFByb21TcGVuZCA8LSAwDQoNCm5ld19kYXRhJERlbUNsdXN0ZXJHcm91cFtuZXdfZGF0YSREZW1DbHVzdGVyR3JvdXAgJWluJSBjKCJVIiwiIildIDwtIE5BDQpuZXdfZGF0YSREZW1HZW5kZXJbbmV3X2RhdGEkRGVtR2VuZGVyICVpbiUgYygiVSIsIiIpXSA8LSBOQQ0KbmV3X2RhdGEkRGVtUmVnW25ld19kYXRhJERlbVJlZyA9PSAiIl0gPC0gTkENCm5ld19kYXRhJERlbVRWUmVnW25ld19kYXRhJERlbVRWUmVnID09ICIiXSA8LSBOQQ0KDQpuZXdfZGF0YSRQcm9tQ2xhc3MgPC0gZmFjdG9yKG5ld19kYXRhJFByb21DbGFzcywgb3JkZXJlZCA9IFRSVUUsIA0KICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKCJUaW4iLCAiU2lsdmVyIiwgIkdvbGQiLCAiUGxhdGludW0iKSkNCg0KbmV3X2RhdGEgPC0gZHJvcGxldmVscyhuZXdfZGF0YSkNCg0KbmV3X2RhdGEgPC0gbWlzc1JhbmdlcihuZXdfZGF0YSwgc2VlZCA9IDQyKQ0KDQojIyBDcmVhdGluZyBnYm1fZml0IG9uIGFsbCBkYXRhDQoNCmdibV9maXQgPC0gdHJhaW4oVGFyZ2V0QnV5IH4gLiwgZGF0YSA9IGRmLCBtZXRob2QgPSAiZ2JtIiwgDQogICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGZpdF9jb250cm9sLCB2ZXJib3NlID0gRkFMU0UsIA0KICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IHR1bmVfZ3JpZCkNCg0KZ2JtX3ByZWRpY3Rpb25zIDwtIHByZWRpY3QoZ2JtX2ZpdCwgbmV3ZGF0YSA9IG5ld19kYXRhLCB0eXBlID0gInByb2IiKQ0KDQpzY29yZWRfc2V0IDwtIGNiaW5kKG5ld19kYXRhJElELA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByZWRpY3QoZ2JtX2ZpdCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5ld2RhdGEgPSBuZXdfZGF0YSwgdHlwZSA9ICJwcm9iIilbLCAyXSkgJT4lDQogIGRhdGEuZnJhbWUoKSAlPiUNCiAgYXJyYW5nZShkZXNjKFgyKSkNCg0KY29sbmFtZXMoc2NvcmVkX3NldCkgPC0gYygiSUQiLCAiUHJvYiBvZiBQdXJjaGFzaW5nIikNCnByaW50KHNjb3JlZF9zZXQpDQpgYGANCg0KSSBhcHBsaWVkIHRoZSBzYW1lIGRhdGEgY2xlYW5pbmcgYW5kIGltcHV0YXRpb24gb24gdGhlIG5ldyBzZXQgYXMgSSBkaWQgb24gdGhlIG9sZCBzZXQuICBXaGVuIGNyZWF0aW5nIG15IGdibSBtb2RlbCwgaG93ZXZlciwgSSB0cmFpbmVkIGl0IG9uIHRoZSBmdWxsIGluaXRpYWwgZGF0YXNldCByYXRoZXIgdGhhbiBqdXN0IHRoZSB0cmFpbmluZyBwYXJ0aXRpb24uICBUaGUgZmluYWwgb3V0cHV0IHNob3dzIHRoZSBjdXN0b21lciBJRCBpbiB0aGUgbGVmdCBjb2x1bW4gYW5kIHRoZSBwcm9iYWJpbGl0eSB0aGF0IHRoYXQgY3VzdG9tZXIgYnV5cyBvcmdhbmljcyBpbiB0aGUgcmlnaHQgY29sdW1uLiAgVGhpcyBmaW5hbCBtb2RlbCBvbmx5IHByZWRpY3RlZCB0d28gb2YgdGhlIHRlbiBwZW9wbGUgYXMgbGlrZWx5IHRvIHB1cmNoYXNlIG9yZ2FuaWNzLg0KDQojIyBDdXN0b21lciBDbHVzdGVyaW5nDQoNCkkgY2x1c3RlcmVkIHRoZSBjdXN0b21lcnMgdG8gZ2VuZXJhdGUgY3VzdG9tZXIgc2VnbWVudHMuICBUaGUgaW50ZW50IG9mIHRoZXNlIGNsdXN0ZXJzIGlzIHNvIHRoZSBncm9jZXJ5IGNoYWluIGNhbiBiZXR0ZXIgdGFyZ2V0IGl0cyB1c2VycyB3aXRoIGl0cyBtYXJrZXRpbmcgbWVzc2FnZXMuICBGb3IgdGhlc2UgY2x1c3RlcnMsIEkgY2hvc2UgdG8gbGVhdmUgb3V0IHRoZSB0YXJnZXQgdmFyaWFibGUgPGNvZGU+VGFyZ2V0QnV5PC9jb2RlPiBzaW5jZSB0aGUgaW50ZW50IG9mIHRoZXNlIGNsdXN0ZXJzIGlzIHRvIGV2ZW50dWFsbHkgYWxzbyB0YXJnZXQgYSBjdXN0b21lciBzZWdtZW50IHdpdGggYSBoaWdoIHByb2JhYmlsaXR5IG9mIHB1cmNoYXNpbmcgb3VyIG9yZ2FuaWNzLiAgSSByYW4gbXkgY2x1c3RlcnMgYWNyb3NzIHR3byBzZXBhcmF0ZSBkYXRhZnJhbWVzLCB3aGVyZSBJIGVpdGhlcjoNCg0KMS4gUmVtb3ZlZCB1bm9yZGVyZWQgbXVsdGlsZXZlbCBmYWN0b3IgdmFyaWFibGVzIDxjb2RlPkRlbUNsdXN0ZXJHcm91cDwvY29kZT4sIDxjb2RlPkRlbVJlZzwvY29kZT4sIGFuZCA8Y29kZT5EZW1UVlJlZzwvY29kZT4uDQoNCjIuIEdlbmVyYXRlZCBkdW1teSB2YXJpYWJsZXMgZm9yIHRoZSBtdWx0aWxldmVsIGZhY3RvciB2YXJpYWJsZXMuDQoNCkNsdXN0ZXJpbmcgb24gdGhlIGRhdGFmcmFtZSBjb250YWluaW5nIHRoZSBkdW1teSB2YXJpYWJsZXMgZmFpbGVkIHRvIGNvbnZlcmdlIGFjcm9zcyBFbGJvdywgU2lsaG91ZXR0ZSwgYW5kIEdhcCBTdGF0aXN0aWNzIG1ldGhvZHMuICBUaGVyZWZvcmUgSSBjaG9zZSB0byBjb250aW51ZSB3aXRoIHRoZSBmaXJzdCBkYXRhZnJhbWUsIHdoZXJlIEkgbGVmdCB0aGUgdW5vcmRlcmVkIG11bHRpbGV2ZWwgZmFjdG9yIHZhcmlhYmxlcyBlbnRpcmVseSBvdXQgb2YgbXkgY2x1c3RlcmluZyBhbmFseXNpcy4NCg0KIyMjIFByZXBhcmF0aW9uDQoNCmBgYHtyfQ0KY2x1c192YXJzIDwtIGRmICU+JQ0KICBzZWxlY3QoLVRhcmdldEJ1eSkNCg0KY2x1c192YXJzIDwtIGNsdXNfdmFycyAlPiUNCiAgc2VsZWN0KC1jKERlbUNsdXN0ZXJHcm91cCwgRGVtUmVnLCBEZW1UVlJlZykpDQoNCmNsdXNfdmFycyA8LSBsYXBwbHkoY2x1c192YXJzLCBhcy5pbnRlZ2VyKSAlPiUNCiAgZGF0YS5mcmFtZSgpDQpgYGANCg0KSSBzY2FsZWQgdGhlIGRhdGFmcmFtZSB0byBrZWVwIGFueSBvbmUgdmFyaWFibGUgZnJvbSBoYXZpbmcgdG9vIGxhcmdlIGFuIGltcGFjdCBvbiBteSBmaW5hbCBvdXRwdXQgY2x1c3RlcnMuDQoNCmBgYHtyfQ0KY2x1c192YXJzX3NjYWxlZCA8LSBzY2FsZShjbHVzX3ZhcnMpICU+JQ0KICBkYXRhLmZyYW1lKCkNCmBgYA0KDQojIyMgUmVzdWx0cw0KDQpJIGZpcnN0IHRyaWVkIHRvIGRldGVybWluZSB0aGUgb3B0aW1hbCBjbHVzdGVyIG51bWJlcnMgdGhyb3VnaCBib3RoIHRoZSBlbGJvdyBhbmQgc2lsaG91ZXR0ZSBtZXRob2QuDQoNCiMjIyMgRWxib3cgTWV0aG9kDQoNCmBgYHtyfQ0KZnZpel9uYmNsdXN0KGNsdXNfdmFyc19zY2FsZWQsIGttZWFucywgbWV0aG9kID0gIndzcyIpDQpgYGANCg0KDQpUaGUgZWxib3cgbWV0aG9kLCB3aGlsZSBub3QgY29tcGxldGVseSBkZWZpbml0aXZlLCBzdWdnZXN0ZWQgdGhhdCBvcHRpbXVtIGNsdXN0ZXIgc2l6ZXMgbWlnaHQgYmUgMiwgNCwgb3IgNi4NCg0KIyMjIyBTaWxob3VldHRlIE1ldGhvZA0KDQpgYGB7cn0NCmZ2aXpfbmJjbHVzdChjbHVzX3ZhcnNfc2NhbGVkLCBrbWVhbnMsIG1ldGhvZCA9ICJzaWxob3VldHRlIikNCmBgYA0KDQpUaGUgYXZlcmFnZSBzaWxob3VldHRlIG1ldGhvZCBkZW1vbnN0cmF0ZXMgb3B0aW1hbCBjbHVzdGVyIG51bWJlciBhdCA0Lg0KDQpTZWVpbmcgdGhhdCBib3RoIG1ldGhvZHMgZGlzYWdyZWVkIG9uIGlkZWFsIGNsdXN0ZXIgc2l6ZSwgSSBjaG9zZSB0byB1c2UgdGhlIDxjb2RlPk5iQ2x1c3Q8L2NvZGU+IGZ1bmN0aW9uIHRvIGRldGVybWluZSB3aGljaCBjbHVzdGVyIGNvdW50IHdhcyBvcHRpbWFsIGFjcm9zcyB0aGUgbWFqb3JpdHkgb2YgdGhlIDI2IG5vbi1jb21wdXRhdGlvbmFsbHkgZXhwZW5zaXZlIG1ldGhvZHMgZW1iZWRkZWQgaW4gPGNvZGU+TmJDbHVzdDwvY29kZT4uICBOb3RlIHRoYXQgSSB1c2VkIGFyZ3VtZW50IDxjb2RlPmluZGV4ID0gImFsbCI8L2NvZGU+IHRvIGl0ZXJhdGUgb25seSBvbiB0aGUgbm9uLWNvbXB1dGF0aW9uYWxseSBleHBlbnNpdmUgbWV0cmljcy4gIFNpbmNlIHRoaXMgcHJvY2VkdXJlIGlzIG9ubHkgYmVpbmcgdXNlZCB0byBkZXRlcm1pbmUgb3B0aW1hbCBudW1iZXIgb2YgY2x1c3RlcnMsIG5vdCB0aGUgZmluYWwgY2x1c3RlcnMgZm9yIG15IG1vZGVsLCBJIGFtIGNob29zaW5nIHRvIHN1YnNldCBteSBkYXRhIHRvIGN1dCBkb3duIG9uIGNvbXB1dGF0aW9uYWwgdGltZS4gIFRoZXJlZm9yZSBJIGFtIG9ubHkgcnVubmluZyA8Y29kZT5OYkNsdXN0PC9jb2RlPiBvbiAzMCUgb2YgbXkgZGF0YS4NCg0KIyMjIyBOYkNsdXN0DQoNCmBgYHtyLCByZXN1bHRzID0gImhpZGUifQ0KY2x1c3Rlcl9zYW1wbGUgPC0gc2FtcGxlKGMoVFJVRSwgRkFMU0UpLCBucm93KGNsdXNfdmFyc19zY2FsZWQpLCByZXBsYWNlID0gVFJVRSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgcHJvYiA9IGMoMC4zLCAwLjcpKQ0KDQpjbHVzdGVyX3N1YnNldCA8LSBjbHVzX3ZhcnNfc2NhbGVkW2NsdXN0ZXJfc2FtcGxlLF0NCg0KbmIgPC0gTmJDbHVzdChjbHVzdGVyX3N1YnNldCwgZGlzdGFuY2UgPSAiZXVjbGlkZWFuIiwgbWluLm5jID0gMiwNCiAgICAgICAgbWF4Lm5jID0gMTUsIG1ldGhvZCA9ICJjb21wbGV0ZSIsIGluZGV4ID0iYWxsIikNCmBgYA0KDQpgYGB7cn0NCmZ2aXpfbmJjbHVzdChuYikNCmBgYA0KDQpUaGUgcmVzdWxzIG9mIHRoaXMgY2x1c3RlcmluZyBvbiBteSBzdWJzZXR0ZWQgZGF0YSBkZW1vbnN0cmF0ZWQgYW4gaWRlYWwgY2x1c3RlciBjb3VudCBvZiAyLiAgR2l2ZW4gdGhhdCB0aGlzIGlzIG5vdCBuZWNlc3NhcmlseSBwcmFjdGljYWwgZnJvbSBhIGJ1c2luZXNzIHBlcnNwZWN0aXZlLCBJIGFtIGNob29zaW5nIHRvIG9wZXJhdGUgd2l0aCA8Y29kZT5rID0gMTI8L2NvZGU+IGNsdXN0ZXJzIHNpbmNlIHRoaXMgd2FzIHRoZSBzZWNvbmQtYmFzZWQgY2x1c3RlciBudW1iZXIuDQoNCmBgYHtyfQ0KazEyIDwtIGttZWFucyhjbHVzX3ZhcnNfc2NhbGVkLCBjZW50ZXJzID0gMTIsIG5zdGFydCA9IDUwLCBpdGVyLm1heCA9IDUwKQ0KazEyDQpgYGANCg0KRWFjaCBvZiB0aGVzZSBjbHVzdGVycyB3YXMgb2YgYSBzdWJzdGFudGlhbCBzaXplLCB0aGUgc21hbGxlc3Qgb2Ygd2hpY2ggYmVpbmcgb2Ygc2l6ZSAxNDEgIDY4LjMlIG9mIHRoZSB0b3RhbCB2YXJpYWJpbGl0eSB3YXMgYWNjb3VudGVkIGZvciBieSBiZXR3ZWVuLWNsdXN0ZXIgdmFyaWFuY2UuDQoNCmBgYHtyfQ0KZnZpel9jbHVzdGVyKGsxMiwgZGF0YSA9IGNsdXNfdmFyc19zY2FsZWQpDQpgYGANCg0KTm90ZSB0aGF0IGFyb3VuZCA1MCUgb2YgdGhlIHZhcmlhYmlsaXR5IGluIHRoZSBjdXN0b21lciBjbHVzdGVycyBpcyBleHBsYWluZWQgYnkgPGNvZGU+RGltMTwvY29kZT4gYW5kIDxjb2RlPkRpbTI8L2NvZGU+LiAgQWxzbyBub3RlIHRoYXQgNTIuMiUgb2YgdGhlIHZhcmlhYmlsaXR5IGluIHRoZSBkYXRhIGNhbiBiZSBleHBsYWluZWQgYnkgdGhlIHR3byBlaWdlbnZlY3RvcnMgPGNvZGU+RGltMTwvY29kZT4gYW5kIDxjb2RlPkRpbTI8L2NvZGU+Lg0KDQpJIHRoZW4gY2hvc2UgdG8gYW5hbHl6ZSB3aGljaCBhbW9uZyB0aGVzZSBjbHVzdGVycyBoYWQgdGhlIGhpZ2hlc3QgcHJvcG9ydGlvbiBvZiBvcmdhbmljIHB1cmNoYXNlcy4NCg0KYGBge3J9DQojIyBBZGRpbmcgYmFjayB0YXJnZXQgdmFyaWFibGU6DQpjbHVzX3ZhcnMgPC0gY2JpbmQoY2x1c192YXJzLCBUYXJnZXRCdXkgPSBhcy5pbnRlZ2VyKGRmJFRhcmdldEJ1eSksDQogICAgICAgICAgICAgICAgICAgQ2x1c3RlciA9IGsxMiRjbHVzdGVyKQ0KDQpjbHVzdGVyX21lYW5zIDwtIGNsdXNfdmFycyAlPiUNCiAgZ3JvdXBfYnkoQ2x1c3RlcikgJT4lDQogIHN1bW1hcml6ZV9hbGwoIm1lYW4iKSAlPiUNCiAgc2VsZWN0KFRhcmdldEJ1eSwgZXZlcnl0aGluZygpKSAlPiUNCiAgYXJyYW5nZShkZXNjKFRhcmdldEJ1eSkpDQoNCmRhdGF0YWJsZShjbHVzdGVyX21lYW5zKQ0KYGBgDQoNCiMjIEV4cGxvcmluZyBGaW5hbCBDbHVzdGVycw0KDQpgYGB7cn0NCmdncGxvdChjbHVzdGVyX21lYW5zLCBhZXMoeCA9IHJlb3JkZXIoQ2x1c3RlciwgLVRhcmdldEJ1eSksIHkgPSBUYXJnZXRCdXkgLSAxLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IFRhcmdldEJ1eSAtIDEpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobmFtZSA9ICJQcm9wb3J0aW9uIFxuUHVyY2hhc2luZyBcbk9yZ2FuaWNzIikgKw0KICB4bGFiKCJDbHVzdGVyIikgKw0KICB5bGFiKCJPcmdhbmljcyBQdXJjaGFzZWQiKSArDQogIGdndGl0bGUoIk9uZSBvZiBPdXIgU2VnbWVudHMgUHVyY2hhc2VzIE9yZ2FuaWNzIE1vcmUgT2Z0ZW4iKQ0KYGBgDQoNCk5vdGUgdGhhdCBvbmUgY2x1c3RlciBieSBmYXIgaGFzIHRoZSBoaWdoZXN0IHByb2JhYmlsaXR5IG9mIHB1cmNoYXNpbmcgb3JnYW5pY3MuICBGb3IgdGhlIGZvbGxvd2luZyBncmFwaHMsIEkgd2lsbCBjb250aW51ZSB0byBoYXZlIHRoZSBiYXIgZmlsbCByZXByZXNlbnQgb3JnYW5pYyBwdXJjaGFzZXMuDQoNCg0KIyMjIyBFeHBsYW5hdG9yeSBWYXJpYWJsZSBQbG90cyB7LnRhYnNldCAudGFic2V0LWZhZGV9DQojIyMjIyBBZmZsdWVuY2UNCmBgYHtyfQ0KZ2dwbG90KGNsdXN0ZXJfbWVhbnMsIGFlcyh4ID0gcmVvcmRlcihDbHVzdGVyLCAtRGVtQWZmbCksIHkgPSBEZW1BZmZsLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IFRhcmdldEJ1eSAtIDEpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobmFtZSA9ICJQcm9wb3J0aW9uIFxuUHVyY2hhc2luZyBcbk9yZ2FuaWNzIikgKw0KICB4bGFiKCJDbHVzdGVyIikgKw0KICB5bGFiKCJBZmZsdWVuY2UiKSArDQogIGdndGl0bGUoIlRoZSBPcmdhbmljcyBDbHVzdGVyIGhhcyBtb3JlIE1vbmV5IikNCmBgYA0KDQojIyMjIyBBZ2UNCmBgYHtyfQ0KZ2dwbG90KGNsdXN0ZXJfbWVhbnMsIGFlcyh4ID0gcmVvcmRlcihDbHVzdGVyLCAtRGVtQWdlKSwgeSA9IERlbUFnZSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBUYXJnZXRCdXkgLSAxKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKw0KICBzY2FsZV9maWxsX2dyYWRpZW50KG5hbWUgPSAiUHJvcG9ydGlvbiBcblB1cmNoYXNpbmcgXG5PcmdhbmljcyIpICsNCiAgeGxhYigiQ2x1c3RlciIpICsNCiAgeWxhYigiQWdlIikgKw0KICBnZ3RpdGxlKCJUaGUgT3JnYW5pY3MgQ2x1c3RlciBpcyBGYWlybHkgWW91bmciKQ0KYGBgDQoNCiMjIyMjIEdlbmRlcg0KYGBge3J9DQpnZ3Bsb3QoY2x1c3Rlcl9tZWFucywgYWVzKHggPSByZW9yZGVyKENsdXN0ZXIsIC1EZW1HZW5kZXIpLCB5ID0gMS41IC0gRGVtR2VuZGVyLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IFRhcmdldEJ1eSAtIDEpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobmFtZSA9ICJQcm9wb3J0aW9uIFxuUHVyY2hhc2luZyBcbk9yZ2FuaWNzIikgKw0KICB4bGFiKCJDbHVzdGVyIikgKw0KICB5bGFiKCJHZW5kZXIiKSArDQogIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBjKC0wLjUsIDAsIDAuNSksIA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiTWFsZSIsICJFdmVuIiwgIkZlbWFsZSIpKSArDQogIGdndGl0bGUoIlRoZSBPcmdhbmljcyBDbHVzdGVyIGlzIE1vc3RseSBGZW1hbGUiKQ0KYGBgDQoNCiMjIyMjIExveWFsdHkgU3RhdHVzDQpgYGB7cn0NCmdncGxvdChjbHVzdGVyX21lYW5zLCBhZXMoeCA9IHJlb3JkZXIoQ2x1c3RlciwgLVByb21DbGFzcyksIHkgPSBQcm9tQ2xhc3MsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gVGFyZ2V0QnV5IC0gMSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IGMoMSwyLDMsNCksIGxhYmVscyA9IGMoInRpbiIsICJzaWx2ZXIiLCAiZ29sZCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJwbGF0aW51bSIpKSArDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobmFtZSA9ICJQcm9wb3J0aW9uIFxuUHVyY2hhc2luZyBcbk9yZ2FuaWNzIikgKw0KICB5bGFiKCJMb3lhbHR5IFN0YXR1cyIpICsNCiAgeGxhYigiQ2x1c3RlciIpICsNCiAgZ2d0aXRsZSgiT3VyIE1vc3QgTG95YWwgQ3VzdG9tZXJzIGFyZSBub3QgYnV5aW5nIE9yZ2FuaWNzIikNCmBgYA0KDQojIyMjIyBBbm51YWwgU3RvcmUgU3BlbmRpbmcNCmBgYHtyfQ0KZ2dwbG90KGNsdXN0ZXJfbWVhbnMsIGFlcyh4ID0gcmVvcmRlcihDbHVzdGVyLCAtUHJvbVNwZW5kKSwgeSA9IFByb21TcGVuZCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBUYXJnZXRCdXkgLSAxKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpkb2xsYXIpICsNCiAgc2NhbGVfZmlsbF9ncmFkaWVudChuYW1lID0gIlByb3BvcnRpb24gXG5QdXJjaGFzaW5nIFxuT3JnYW5pY3MiKSArDQogIHlsYWIoIlRvdGFsIFN0b3JlIFNwZW5kaW5nIikgKw0KICB4bGFiKCJDbHVzdGVyIikgKw0KICBnZ3RpdGxlKCJPdXIgSGlnaGVzdC1TcGVuZGluZyBDdXN0b21lcnMgYXJlIG5vdCBidXlpbmcgT3JnYW5pY3MiKQ0KYGBgDQoNCiMjIyMjIFRpbWUgYXMgTG95YWx0eSBDYXJkIE1lbWJlcg0KYGBge3J9DQpnZ3Bsb3QoY2x1c3Rlcl9tZWFucywgYWVzKHggPSByZW9yZGVyKENsdXN0ZXIsIC1Qcm9tVGltZSksIHkgPSBQcm9tVGltZSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBUYXJnZXRCdXkgLSAxKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKw0KICBzY2FsZV9maWxsX2dyYWRpZW50KG5hbWUgPSAiUHJvcG9ydGlvbiBcblB1cmNoYXNpbmcgXG5PcmdhbmljcyIpICsNCiAgeGxhYigiQ2x1c3RlciIpICsNCiAgeWxhYigiTW9udGhzIGFzIExveWFsdHkgQ2FyZCBNZW1iZXIiKSArDQogIGdndGl0bGUoIk91ciBPbGRlc3QgTG95YWx0eSBDYXJkcyBDdXN0b21lcnMgYXJlIG5vdCBidXlpbmcgT3JnYW5pY3MiKQ0KYGBgDQoNCiMjIyMgQW5hbHlzaXMgb2YgRXhwbGFuYXRvcnkgVmFyaWFibGVzDQoNClRoZSBwcmltYXJ5IHRha2Vhd2F5cyBmcm9tIHRoZXNlIGNsdXN0ZXJzIGlzIHRoYXQgYWZmbHVlbmNlIGlzIG1vc3QgdGllZCB0byBvcmdhbmljIHByb2R1Y2UgcHVyY2hhc2VzLiAgVGhlc2UgY2x1c3RlcnMgYWxzbyBpbmRpY2F0ZSB0aGF0IHRob3NlIHdobyBtb3N0bHkgcHVyY2hhc2Ugb3JnYW5pY3MgdGVuZCB0byBub3Qgc3BlbmQgbXVjaCBpbiB0aGUgc3RvcmUsIGluZGljYXRpbmcgdGhhdCB0aGUgc3RvcmUgaXMgbm90IGF0dHJhY3Rpbmcgb3JnYW5pY3Mtc2F2dnkgY29uc3VtZXJzIGFzIG11Y2ggYXMgaXQgY291bGQgYmUuDQoNCk91dHNpZGUgb2YgYW5hbHl6aW5nIG9yZ2FuaWNzIHNhbGVzLCB3ZSBzZWUgdGhhdCBvbmUgY2x1c3RlcidzIHByaW1hcnkgZmVhdHVyZSBpcyB0aG9zZSB3aG8gaGF2ZSBoYWQgbG95YWx0eSBjYXJkcyBsb25nZXN0LiBBbm90aGVyIGNsdXN0ZXIgc3BlbmRzIHRoZSBtb3N0IGFuZCBhbHNvIGNvbnNpc3RzIG1vc3RseSBvZiBwbGF0aW51bSBtZW1iZXJzLiAgQmFzZWQgb24gdGhlc2UgZmVhdHVyZXMsIHdlIGtub3cgdGhhdCB0aGlzIGNsdXN0ZXIgaXMgb3VyIGhpZ2hlc3QgdmFsdWUgY2x1c3Rlci4gIFRoZXJlZm9yZSB3ZSBzaG91bGQgdGFyZ2V0IHRoaXMgY3VzdG9tZXIgc2VnbWVudCB0byBwdXJjaGFzZSBtb3JlIG9yZ2FuaWNzLg0KDQoNCg==