Following on from the exploratory data analysis of reviews of coffee quality from different suppliers, we will now attempt to model the main quality metric, Total.Cup.Points, based on the characteristics of the beans used to make the coffee.
We have 1338 rows of data, of which the first 10 rows are given below.
We would like to develop a random forest model to describe how Total.Cup.Points depends on the input variables of this data. To choose the optimal parameters to use with our model, we would like to crossvalidate using training data, before testing the chosen parameters using test data.
Feature Importance
To estimate the relative importance of the features, we fit an unoptimised random forest model to all the data and determine variable importance from the model.

The feature variables with greatest influence on quality appear to be:
- Altitude
- Category.Two.Defects
- Owner
- Moisture
- Weight of Sample Bag
- Grading Month
- Variety
- Category.One.Defects
- Harvest and Grading Year
Processing method, Quakers, Species and Color appear to not be as important.
We have difficulties with this data set in that some of the categorical variables have low frequency values within them. These factor levels may not appear in all folds of the crossvalidation data, and may not be present in both the training and test data. If we use our workflow to just create dummies for these variables as is, we will encounter errors during prediction steps when the training and testing variables don’t match.
To compensate for this, we will identify our top 14 countries of origin, top 6 Owners, top 5 varieties and the top 3 processing methods and change the other values in these categories to “Other”. We are reducing the number of distinct categories for each of these variables.
After removing rows with missing data for Owner, Country or Variety, a random 20% will be removed to act as the test set. We will also randomly define the 4-fold crossvalidation splits for the training data.
Training data rows = 890
Testing data rows = 222
Set up Pre-treatment Recipes
We now define the pretreatment recipes. We are going to impute some data into missing values for crossvalidation. We will impute the mean altitude, the median Harvest.Year, and the mode for the categorical variables processing method and Color. We will also normalize numeric variables and remove those that are highly correlated. We will create variables for grading month and year and create dummy variables for the remaining categorical variables.
recipe_1 <-
train %>%
recipe(Total.Cup.Points ~ .) %>%
step_meanimpute(mean_alt) %>%
step_medianimpute(Harvest.Year) %>%
step_modeimpute(pm, Color) %>%
step_normalize(all_numeric(), -all_outcomes(), -Harvest.Year) %>%
step_date(new_date, features = c("month", "year")) %>%
step_rm(new_date) %>%
step_dummy(Owner, Species, Color, coo, var, pm, new_date_month) %>%
step_corr(all_numeric(), -all_outcomes())
What does the subsequent training frame look like?
Random Forest Model
We will create a tuning workflow in order to optimise hyperparameters for a random forest regression model.
We will be optimising trees and mtry values for our ranger model, using crossvalidation results of our training data. Mean absolute error (MAE) will be evaluated for each set of predictions.
rf_model <-
rand_forest(trees = tune(), mtry = tune()) %>%
set_engine("ranger", importance = "impurity") %>%
set_mode("regression")
grid_rf <-
grid_max_entropy(
trees(range = c(100, 2000)),
mtry(range = c(3, 15)),
size = 30)
my_metrics <- metric_set(mae)
After crossvalidating for a range of trees and mtry values, let’s plot values of MAE and list the best models.

We will pick the values of trees and mtry with the lowest error. Now we will choose these values to train a model using the training data and fit the model to the test data. Let’s see how the predictions compare with reality for the test set and calculate the test MAE. The line on the plot is the line of equality.

Mean Absolute Error for Test set = 1.421002
Our test set error is better than our best crossvalidation error.
Linear Model
Let’s compare this with a linear regression model. We will be optimising mixture and penalty values for our glmnet model, using crossvalidation results of our training data. Mean absolute error (MAE) will be evaluated for each set of predictions.
model_glm <-
linear_reg(penalty = tune(), mixture = tune()) %>%
set_engine("glmnet")
grid_glm <-
grid_max_entropy(
penalty(range = c(0.01, 0.3), trans = NULL),
mixture(range = c(0, 1)),
size = 30)
my_metrics <- metric_set(mae)
After crossvalidating for a range of mixture and penalty values, let’s plot values of MAE and list the best models.

Again we will pick the values of mixture and penalty with the lowest error. Now we will choose these values to train a model using the training data and fit the model to the test data. Again let’s see how the predictions compare with reality for the test set and calculate the test MAE. The line on the plot is the line of equality.

Mean Absolute Error for Test set = 1.479737
Again, the error on the test set is better than that of the crossvalidation results.
Overall, it appears that the random forest model is better at predicting coffee quality than the linear regression model. It may be possible to reduce the prediction error further by combining the two model predictions.
End
LS0tDQp0aXRsZTogIkNvZmZlZSBRdWFsaXR5IFNjb3JlIE1vZGVsbGluZyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCkZvbGxvd2luZyBvbiBmcm9tIHRoZSBleHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzIG9mIHJldmlld3Mgb2YgY29mZmVlIHF1YWxpdHkgZnJvbSBkaWZmZXJlbnQgc3VwcGxpZXJzLCB3ZSB3aWxsIG5vdyBhdHRlbXB0IHRvIG1vZGVsIHRoZSBtYWluIHF1YWxpdHkgbWV0cmljLCBUb3RhbC5DdXAuUG9pbnRzLCBiYXNlZCBvbiB0aGUgY2hhcmFjdGVyaXN0aWNzIG9mIHRoZSBiZWFucyB1c2VkIHRvIG1ha2UgdGhlIGNvZmZlZS4gIA0KDQpgYGB7ciBJbml0aWFsaXNlIGFuZCBsb2FkIGRhdGEsIGluY2x1ZGU9RkFMU0V9DQpybShsaXN0ID0gbHMoKSkNCg0KbGlicmFyeShoZXJlKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHRpZHltb2RlbHMpDQpsaWJyYXJ5KHR1bmUpDQpsaWJyYXJ5KHdvcmtmbG93cykNCg0Kb3B0aW9ucyh0aWR5bW9kZWxzLmRhcmsgPSBUUlVFKQ0KDQpsb2FkKGhlcmUoImRhdGEvaW50ZXJpbS9iZWFuX2RhdGFfY2xlYW4uUkRhdGEiKSkNCg0KYmVhbl9kYXRhIDwtIGJlYW5fZGF0YSAlPiUNCiAgcmVuYW1lKGNvbyA9IENvdW50cnkub2YuT3JpZ2luLA0KICAgICAgICAgdmFyID0gVmFyaWV0eSwNCiAgICAgICAgIHBtID0gUHJvY2Vzc2luZy5NZXRob2QpICU+JQ0KICBtdXRhdGUoY29vID0gc3RyX3JlcGxhY2VfYWxsKGNvbywgIiAiLCAiXyIpLA0KICAgICAgICAgY29vID0gc3RyX3JlcGxhY2VfYWxsKGNvbywgIlxcKCIsICIiKSwNCiAgICAgICAgIGNvbyA9IHN0cl9yZXBsYWNlX2FsbChjb28sICJcXCkiLCAiIiksDQogICAgICAgICB2YXIgPSBzdHJfcmVwbGFjZV9hbGwodmFyLCAiICIsICJfIiksDQogICAgICAgICBwbSA9IHN0cl9yZXBsYWNlX2FsbChwbSwgIiAiLCAiXyIpLA0KICAgICAgICAgcG0gPSBzdHJfcmVwbGFjZV9hbGwocG0sICJfXFwvIiwgIiIpLA0KICAgICAgICAgcG0gPSBzdHJfcmVwbGFjZV9hbGwocG0sICItIiwgIiIpKQ0KDQptb2RlbF9kYXRhIDwtIGJlYW5fZGF0YSAlPiUNCiAgZmlsdGVyKGlzLm5hKGNvbykgPT0gRkFMU0UsDQogICAgICAgICBpcy5uYSh2YXIpID09IEZBTFNFLA0KICAgICAgICAgaXMubmEoT3duZXIpID09IEZBTFNFKQ0KDQpgYGANCg0KYGBge3IgRnVuY3Rpb25zLCBpbmNsdWRlPUZBTFNFfQ0KbWV0cmljc19wbG90IDwtIGZ1bmN0aW9uKG1vZGVsX2ZpdCwgbWV0cmljKXsNCiAgDQogIGRhdGEgPC0gbW9kZWxfZml0JC5tZXRyaWNzDQogIHBhcmFtcyA8LSBuYW1lcyhkYXRhW1sxXV0pWyEobmFtZXMoZGF0YVtbMV1dKSAlaW4lIG5hbWVzKGRhdGFbWzFdXSlbZ3JlcCgiXlxcLiIsIG5hbWVzKGRhdGFbWzFdXSkpXSldDQogIA0KICBmaXRfbWV0cmljcyA8LSB1bmlxdWUoZGF0YVtbMV1dJC5tZXRyaWMpDQogIA0KICBpZiAoIShtZXRyaWMgJWluJSBmaXRfbWV0cmljcykpew0KICAgIHJldHVybigibm8gbWV0cmljIGJ5IHRoYXQgbmFtZSIpDQogIH0NCiAgDQogIG1lYW5fZGF0YSA8LSBkYXRhW1sxXV0gJT4lIHNlbGVjdCgtLmVzdGltYXRlKQ0KICANCiAgbWVhbl9kYXRhJC5lc3RpbWF0ZSA8LSBkYXRhICU+JQ0KICAgIG1hcCgiLmVzdGltYXRlIikgJT4lDQogICAgYXNfdGliYmxlKC5uYW1lX3JlcGFpciA9ICJ1bmlxdWUiKSAlPiUgDQogICAgcm93TWVhbnMoKQ0KICANCiAgcGxvdF9kYXRhIDwtIG1lYW5fZGF0YSAlPiUNCiAgICBmaWx0ZXIoLm1ldHJpYyA9PSBtZXRyaWMpICU+JQ0KICAgIHBpdm90X2xvbmdlcihjb2xzID0gYWxsX29mKHBhcmFtcyksIG5hbWVzX3RvID0gInBhcmFtIiwgdmFsdWVzX3RvID0gInZhbHVlIikNCiAgDQogIHBsb3QgPC0gZ2dwbG90KHBsb3RfZGF0YSwgYWVzKHZhbHVlLCAuZXN0aW1hdGUpKSArIGdlb21fcG9pbnQoKSArDQogICAgdGhlbWVfYncoKSArIGZhY2V0X3dyYXAofiBwYXJhbSwgc2NhbGVzID0gImZyZWVfeCIpICsgbGFicyh5ID0gbWV0cmljKQ0KICANCiAgcmV0dXJuKHBsb3QpDQp9DQoNCmNoZWNrX2R1bW15IDwtIGZ1bmN0aW9uKGRmLCBzdGFydHMsIGtlZXApew0KICANCiAgdGVtcF9kZiA8LSBkZiAlPiUNCiAgICBzZWxlY3Qoc3RhcnRzX3dpdGgoc3RhcnRzKSkNCiAgDQogIHZhcl9zdW0gPC0gY29sU3Vtcyh0ZW1wX2RmLCBuYS5ybSA9IFRSVUUpDQogIA0KICBrZWVwX2NvbHMgPC0gd2hpY2godmFyX3N1bSA+PSBrZWVwKQ0KICANCiAgdGVtcF9kZiA8LSB0ZW1wX2RmICU+JQ0KICAgIHNlbGVjdChhbGxfb2Yoa2VlcF9jb2xzKSkNCiAgDQogIHJldHVybl9kZiA8LSBkZiAlPiUNCiAgICBzZWxlY3QoLXN0YXJ0c193aXRoKHN0YXJ0cykpICU+JQ0KICAgIGJpbmRfY29scyh0ZW1wX2RmKQ0KICANCiAgcmV0dXJuKHJldHVybl9kZikNCn0NCg0KDQpgYGANCg0KV2UgaGF2ZSBgciBucm93KGJlYW5fZGF0YSlgIHJvd3Mgb2YgZGF0YSwgb2Ygd2hpY2ggdGhlIGZpcnN0IDEwIHJvd3MgYXJlIGdpdmVuIGJlbG93LiAgDQoNCmBgYHtyIERhdGEgZXhhbXBsZSwgZWNobz1GQUxTRX0NCmhlYWQoYmVhbl9kYXRhLCAxMCkNCmBgYA0KDQpXZSB3b3VsZCBsaWtlIHRvIGRldmVsb3AgYSByYW5kb20gZm9yZXN0IG1vZGVsIHRvIGRlc2NyaWJlIGhvdyBUb3RhbC5DdXAuUG9pbnRzIGRlcGVuZHMgb24gdGhlIGlucHV0IHZhcmlhYmxlcyBvZiB0aGlzIGRhdGEuIFRvIGNob29zZSB0aGUgb3B0aW1hbCBwYXJhbWV0ZXJzIHRvIHVzZSB3aXRoIG91ciBtb2RlbCwgd2Ugd291bGQgbGlrZSB0byBjcm9zc3ZhbGlkYXRlIHVzaW5nIHRyYWluaW5nIGRhdGEsIGJlZm9yZSB0ZXN0aW5nIHRoZSBjaG9zZW4gcGFyYW1ldGVycyB1c2luZyB0ZXN0IGRhdGEuICANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnRlYWw7Ij5GZWF0dXJlIEltcG9ydGFuY2U8L3NwYW4+ICANCg0KVG8gZXN0aW1hdGUgdGhlIHJlbGF0aXZlIGltcG9ydGFuY2Ugb2YgdGhlIGZlYXR1cmVzLCB3ZSBmaXQgYW4gdW5vcHRpbWlzZWQgcmFuZG9tIGZvcmVzdCBtb2RlbCB0byBhbGwgdGhlIGRhdGEgYW5kIGRldGVybWluZSB2YXJpYWJsZSBpbXBvcnRhbmNlIGZyb20gdGhlIG1vZGVsLiAgDQoNCmBgYHtyIERlZmluZSB2YXJzLCBpbmNsdWRlPUZBTFNFfQ0KdmFyX3NldF9pbXAgPC0gYygiT3duZXIiLCAiU3BlY2llcyIsICJDb2xvciIsICJjb28iLCAidmFyIiwgInBtIiwgIm1lYW5fYWx0IiwgIkhhcnZlc3QuWWVhciIsICJuZXdfd3QiLCAibmV3X2RhdGUiLCANCiAgICAgICAgICAgICAgICAgIk1vaXN0dXJlIiwgIkNhdGVnb3J5Lk9uZS5EZWZlY3RzIiwgIkNhdGVnb3J5LlR3by5EZWZlY3RzIiwgIlF1YWtlcnMiKQ0KDQppbXBfbW9kZWxfZGF0YSA8LSBiZWFuX2RhdGEgJT4lDQogIHNlbGVjdChUb3RhbC5DdXAuUG9pbnRzLCB2YXJfc2V0X2ltcCkNCmBgYA0KDQpgYGB7ciBWYXJpYWJsZSBJbXBvcnRhbmNlLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KcmVjaXBlX2FsbCA8LQ0KICBpbXBfbW9kZWxfZGF0YSAlPiUNCiAgcmVjaXBlKFRvdGFsLkN1cC5Qb2ludHMgfiAuKSAlPiUNCiAgc3RlcF9uYW9taXQodmFyLCBwbSwgQ29sb3IpICU+JQ0KICBzdGVwX21lYW5pbXB1dGUobWVhbl9hbHQpICU+JQ0KICBzdGVwX21lZGlhbmltcHV0ZShIYXJ2ZXN0LlllYXIpICU+JQ0KICBzdGVwX2RhdGUobmV3X2RhdGUsIGZlYXR1cmVzID0gYygibW9udGgiLCAieWVhciIpKSAlPiUNCiAgc3RlcF9ybShuZXdfZGF0ZSkgJT4lDQogIHN0ZXBfenYoYWxsX3ByZWRpY3RvcnMoKSkgJT4lDQogIHN0ZXBfbm9ybWFsaXplKG1lYW5fYWx0LCBuZXdfd3QsIE1vaXN0dXJlLCBDYXRlZ29yeS5PbmUuRGVmZWN0cywgQ2F0ZWdvcnkuVHdvLkRlZmVjdHMsIFF1YWtlcnMpDQoNCmFsbF9wcmVwIDwtIHJlY2lwZV9hbGwgJT4lIHByZXAoKSAlPiUganVpY2UoKQ0KDQpzaW1wbGVfcmZfbW9kZWwgPC0gDQogIHJhbmRfZm9yZXN0KHRyZWVzID0gMjAwKSAlPiUgDQogIHNldF9lbmdpbmUoInJhbmdlciIsIGltcG9ydGFuY2UgPSAiaW1wdXJpdHkiKSAlPiUgDQogIHNldF9tb2RlKCJyZWdyZXNzaW9uIikNCg0KcmZfbW9kZWxfZml0IDwtIHNpbXBsZV9yZl9tb2RlbCAlPiUNCiAgZml0KFRvdGFsLkN1cC5Qb2ludHMgfiAuLCBkYXRhID0gYWxsX3ByZXApDQoNCmltcF90ZXJtcyA8LSByZl9tb2RlbF9maXQkZml0JHZhcmlhYmxlLmltcG9ydGFuY2UNCg0KaW1wX3Bsb3RfZGYgPC0gZGF0YS5mcmFtZSh2YXJpYWJsZSA9IG5hbWVzKGltcF90ZXJtcyksIGltcG9ydGFuY2UgPSBpbXBfdGVybXMpDQppbXBfcGxvdF9kZiR2YXJpYWJsZSA8LSBhcy5jaGFyYWN0ZXIoaW1wX3Bsb3RfZGYkdmFyaWFibGUpDQoNCmltcF9wbG90IDwtIGdncGxvdChpbXBfcGxvdF9kZiwgYWVzKHJlb3JkZXIodmFyaWFibGUsIGltcG9ydGFuY2UpLCBpbXBvcnRhbmNlKSkgKyBnZW9tX3BvaW50KCkgKyBjb29yZF9mbGlwKCkgKw0KICB0aGVtZV9idygpICsgZ2d0aXRsZSgiUmVsYXRpdmUgSW1wb3J0YW5jZSBvZiBWYXJpYWJsZXMiLCBzdWJ0aXRsZSA9ICJTaW1wbGUgUmFuZG9tIEZvcmVzdCBNb2RlbCIpICsNCiAgbGFicyh4ID0gIlZhcmlhYmxlIikNCmdnc2F2ZShoZXJlKCJwbG90cy9pbXBfcGxvdC5wbmciKSwgaGVpZ2h0ID0gNiwgZHBpID0gMjAwKQ0KDQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlKCJwbG90cy9pbXBfcGxvdC5wbmciKSkNCmBgYA0KDQpUaGUgZmVhdHVyZSB2YXJpYWJsZXMgd2l0aCBncmVhdGVzdCBpbmZsdWVuY2Ugb24gcXVhbGl0eSBhcHBlYXIgdG8gYmU6DQoNCiogQWx0aXR1ZGUNCiogQ2F0ZWdvcnkuVHdvLkRlZmVjdHMNCiogT3duZXINCiogTW9pc3R1cmUNCiogV2VpZ2h0IG9mIFNhbXBsZSBCYWcNCiogR3JhZGluZyBNb250aA0KKiBWYXJpZXR5DQoqIENhdGVnb3J5Lk9uZS5EZWZlY3RzDQoqIEhhcnZlc3QgYW5kIEdyYWRpbmcgWWVhcg0KDQpQcm9jZXNzaW5nIG1ldGhvZCwgUXVha2VycywgU3BlY2llcyBhbmQgQ29sb3IgYXBwZWFyIHRvIG5vdCBiZSBhcyBpbXBvcnRhbnQuICANCg0KV2UgaGF2ZSBkaWZmaWN1bHRpZXMgd2l0aCB0aGlzIGRhdGEgc2V0IGluIHRoYXQgc29tZSBvZiB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGhhdmUgbG93IGZyZXF1ZW5jeSB2YWx1ZXMgd2l0aGluIHRoZW0uICBUaGVzZSBmYWN0b3IgbGV2ZWxzIG1heSBub3QgYXBwZWFyIGluIGFsbCBmb2xkcyBvZiB0aGUgY3Jvc3N2YWxpZGF0aW9uIGRhdGEsIGFuZCBtYXkgbm90IGJlIHByZXNlbnQgaW4gYm90aCB0aGUgdHJhaW5pbmcgYW5kIHRlc3QgZGF0YS4gSWYgd2UgdXNlIG91ciB3b3JrZmxvdyB0byBqdXN0IGNyZWF0ZSBkdW1taWVzIGZvciB0aGVzZSB2YXJpYWJsZXMgYXMgaXMsIHdlIHdpbGwgZW5jb3VudGVyIGVycm9ycyBkdXJpbmcgcHJlZGljdGlvbiBzdGVwcyB3aGVuIHRoZSB0cmFpbmluZyBhbmQgdGVzdGluZyB2YXJpYWJsZXMgZG9uJ3QgbWF0Y2guICANCg0KVG8gY29tcGVuc2F0ZSBmb3IgdGhpcywgd2Ugd2lsbCBpZGVudGlmeSBvdXIgdG9wIDE0IGNvdW50cmllcyBvZiBvcmlnaW4sIHRvcCA2IE93bmVycywgdG9wIDUgdmFyaWV0aWVzIGFuZCB0aGUgdG9wIDMgcHJvY2Vzc2luZyBtZXRob2RzIGFuZCBjaGFuZ2UgdGhlIG90aGVyIHZhbHVlcyBpbiB0aGVzZSBjYXRlZ29yaWVzIHRvICJPdGhlciIuIFdlIGFyZSByZWR1Y2luZyB0aGUgbnVtYmVyIG9mIGRpc3RpbmN0IGNhdGVnb3JpZXMgZm9yIGVhY2ggb2YgdGhlc2UgdmFyaWFibGVzLiAgDQoNCg0KYGBge3IgQ3JlYXRlIGR1bW1pZXMsIGluY2x1ZGU9RkFMU0V9DQpvd25fbGlzdCA8LSBjKCJqdWFuIGx1aXMgYWx2YXJhZG8gcm9tZXJvIiwgInJhY2FmZSAgY2lhIHNjYSIsICJleHBvcnRhZG9yYSBkZSBjYWZlIGNvbmRvciBzYSIsIA0KICAgICAgICAgICAgICAia29uYSBwYWNpZmljIGZhcm1lcnMgY29vcGVyYXRpdmUiLCAiaXBhbmVtYSBjb2ZmZWVzIiwgImNxaSB0YWl3YW4gaWNwIGNxaSIpDQpjb29fbGlzdCA8LSBjKCJNZXhpY28iLCAiQ29sb21iaWEiLCAiR3VhdGVtYWxhIiwgIkJyYXppbCIsICJUYWl3YW4iLCAiVW5pdGVkX1N0YXRlc19IYXdhaWkiLCAiSG9uZHVyYXMiLA0KICAgICAgICAgICAgICAiQ29zdGEgUmljYSIsICJFdGhpb3BpYSIsICJUYW56YW5pYSIsICJVZ2FuZGEiLCAiVGhhaWxhbmQiLCAiTmljYXJhZ3VhIiwgIktlbnlhIikNCnZhcl9saXN0IDwtIGMoIkNhdHVycmEiLCAiQm91cmJvbiIsICJUeXBpY2EiLCAiQ2F0dWFpIiwgIkhhd2FpaWFuIEtvbmEiKQ0KcG1fbGlzdCA8LSBjKCJXYXNoZWRfV2V0IiwgIk5hdHVyYWxfRHJ5IiwgIlNlbWl3YXNoZWRfU2VtaXB1bHBlZCIpDQoNCm93bl9ub3ROQSA8LSB3aGljaChpcy5uYShtb2RlbF9kYXRhJE93bmVyKSA9PSBGQUxTRSkNCm93bl9pbmxpc3QgPC0gd2hpY2gobW9kZWxfZGF0YSRPd25lciAlaW4lIG93bl9saXN0KQ0KbW9kZWxfZGF0YSRPd25lcltzZXRkaWZmKG93bl9ub3ROQSwgb3duX2lubGlzdCldIDwtICJPdGhlciINCg0KY29vX25vdE5BIDwtIHdoaWNoKGlzLm5hKG1vZGVsX2RhdGEkY29vKSA9PSBGQUxTRSkNCmNvb19pbmxpc3QgPC0gd2hpY2gobW9kZWxfZGF0YSRjb28gJWluJSBjb29fbGlzdCkNCm1vZGVsX2RhdGEkY29vW3NldGRpZmYoY29vX25vdE5BLCBjb29faW5saXN0KV0gPC0gIk90aGVyIg0KDQp2YXJfbm90TkEgPC0gd2hpY2goaXMubmEobW9kZWxfZGF0YSR2YXIpID09IEZBTFNFKQ0KdmFyX2lubGlzdCA8LSB3aGljaChtb2RlbF9kYXRhJHZhciAlaW4lIHZhcl9saXN0KQ0KbW9kZWxfZGF0YSR2YXJbc2V0ZGlmZih2YXJfbm90TkEsIHZhcl9pbmxpc3QpXSA8LSAiT3RoZXIiDQoNCnBtX25vdE5BIDwtIHdoaWNoKGlzLm5hKG1vZGVsX2RhdGEkcG0pID09IEZBTFNFKQ0KcG1faW5saXN0IDwtIHdoaWNoKG1vZGVsX2RhdGEkcG0gJWluJSBwbV9saXN0KQ0KbW9kZWxfZGF0YSRwbVtzZXRkaWZmKHBtX25vdE5BLCBwbV9pbmxpc3QpXSA8LSAiT3RoZXIiDQoNCmBgYA0KDQoNCmBgYHtyIERlZmluZSBjcm9zc3ZhbCB2YXJzLCBpbmNsdWRlPUZBTFNFfQ0KdmFyX3NldDEgPC0gImNvbyINCnZhcl9zZXQyIDwtICJ2YXIiDQp2YXJfc2V0MyA8LSAicG0iDQoNCnZhcl9zZXQ0IDwtIGMoIk93bmVyIiwgIlNwZWNpZXMiLCAiQ29sb3IiLCAibWVhbl9hbHQiLCAiSGFydmVzdC5ZZWFyIiwgIm5ld193dCIsICJuZXdfZGF0ZSIsICJNb2lzdHVyZSIsDQogICAgICAgICAgICAgICJDYXRlZ29yeS5PbmUuRGVmZWN0cyIsICJDYXRlZ29yeS5Ud28uRGVmZWN0cyIsICJRdWFrZXJzIikNCg0KdmFyX3NldCA8LSBjKHZhcl9zZXQxLCB2YXJfc2V0MiwgdmFyX3NldDMsIHZhcl9zZXQ0KQ0KDQptb2RlbF9kYXRhIDwtIG1vZGVsX2RhdGEgJT4lDQogIHNlbGVjdChUb3RhbC5DdXAuUG9pbnRzLCB2YXJfc2V0KQ0KYGBgDQoNCg0KQWZ0ZXIgcmVtb3Zpbmcgcm93cyB3aXRoIG1pc3NpbmcgZGF0YSBmb3IgT3duZXIsIENvdW50cnkgb3IgVmFyaWV0eSwgYSByYW5kb20gMjAlIHdpbGwgYmUgcmVtb3ZlZCB0byBhY3QgYXMgdGhlIHRlc3Qgc2V0LiBXZSB3aWxsIGFsc28gcmFuZG9tbHkgZGVmaW5lIHRoZSA0LWZvbGQgY3Jvc3N2YWxpZGF0aW9uIHNwbGl0cyBmb3IgdGhlIHRyYWluaW5nIGRhdGEuDQoNCg0KYGBge3IgRGF0YSBzcGxpdCBhbmQgY3Ygc3BsaXQsIGVjaG89RkFMU0V9DQpkYXRhX3NwbGl0IDwtIG1vZGVsX2RhdGEgJT4lDQogIGluaXRpYWxfc3BsaXQocHJvcCA9IDQvNSkNCg0KdGVzdCA8LSB0ZXN0aW5nKGRhdGFfc3BsaXQpDQp0cmFpbiA8LSB0cmFpbmluZyhkYXRhX3NwbGl0KQ0KY3YgPC0gdmZvbGRfY3YodHJhaW4sIHYgPSA0KQ0KDQpjYXQoIlRyYWluaW5nIGRhdGEgcm93cyA9IiwgbnJvdyh0cmFpbiksICJcbiIpDQpjYXQoIlRlc3RpbmcgZGF0YSByb3dzID0iLCBucm93KHRlc3QpLCAiXG4iKQ0KDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnRlYWw7Ij5TZXQgdXAgUHJlLXRyZWF0bWVudCBSZWNpcGVzPC9zcGFuPiAgDQoNCldlIG5vdyBkZWZpbmUgdGhlIHByZXRyZWF0bWVudCByZWNpcGVzLiAgV2UgYXJlIGdvaW5nIHRvIGltcHV0ZSBzb21lIGRhdGEgaW50byBtaXNzaW5nIHZhbHVlcyBmb3IgY3Jvc3N2YWxpZGF0aW9uLiBXZSB3aWxsIGltcHV0ZSB0aGUgbWVhbiBhbHRpdHVkZSwgdGhlIG1lZGlhbiBIYXJ2ZXN0LlllYXIsIGFuZCB0aGUgbW9kZSBmb3IgdGhlIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyBwcm9jZXNzaW5nIG1ldGhvZCBhbmQgQ29sb3IuIFdlIHdpbGwgYWxzbyBub3JtYWxpemUgbnVtZXJpYyB2YXJpYWJsZXMgYW5kIHJlbW92ZSB0aG9zZSB0aGF0IGFyZSBoaWdobHkgY29ycmVsYXRlZC4gV2Ugd2lsbCBjcmVhdGUgdmFyaWFibGVzIGZvciBncmFkaW5nIG1vbnRoIGFuZCB5ZWFyIGFuZCBjcmVhdGUgZHVtbXkgdmFyaWFibGVzIGZvciB0aGUgcmVtYWluaW5nIGNhdGVnb3JpY2FsIHZhcmlhYmxlcy4gIA0KDQpgYGB7ciBQcmV0cmVhdCAxfQ0KcmVjaXBlXzEgPC0NCiAgdHJhaW4gJT4lDQogIHJlY2lwZShUb3RhbC5DdXAuUG9pbnRzIH4gLikgJT4lDQogIHN0ZXBfbWVhbmltcHV0ZShtZWFuX2FsdCkgJT4lDQogIHN0ZXBfbWVkaWFuaW1wdXRlKEhhcnZlc3QuWWVhcikgJT4lDQogIHN0ZXBfbW9kZWltcHV0ZShwbSwgQ29sb3IpICU+JQ0KICBzdGVwX25vcm1hbGl6ZShhbGxfbnVtZXJpYygpLCAtYWxsX291dGNvbWVzKCksIC1IYXJ2ZXN0LlllYXIpICU+JQ0KICBzdGVwX2RhdGUobmV3X2RhdGUsIGZlYXR1cmVzID0gYygibW9udGgiLCAieWVhciIpKSAlPiUNCiAgc3RlcF9ybShuZXdfZGF0ZSkgJT4lIA0KICBzdGVwX2R1bW15KE93bmVyLCBTcGVjaWVzLCBDb2xvciwgY29vLCB2YXIsIHBtLCBuZXdfZGF0ZV9tb250aCkgJT4lIA0KICBzdGVwX2NvcnIoYWxsX251bWVyaWMoKSwgLWFsbF9vdXRjb21lcygpKQ0KDQoNCmBgYA0KDQpXaGF0IGRvZXMgdGhlIHN1YnNlcXVlbnQgdHJhaW5pbmcgZnJhbWUgbG9vayBsaWtlPyAgDQoNCmBgYHtyIFRyYWluaW5nIGZyYW1lLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdHJhaW5fcHJlcCA8LSByZWNpcGVfMSAlPiUgcHJlcCgpICU+JSBqdWljZSgpDQpoZWFkKHRyYWluX3ByZXAsIDEwKQ0KYGBgDQoNCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnRlYWw7Ij5SYW5kb20gRm9yZXN0IE1vZGVsPC9zcGFuPiAgDQoNCldlIHdpbGwgY3JlYXRlIGEgdHVuaW5nIHdvcmtmbG93IGluIG9yZGVyIHRvIG9wdGltaXNlIGh5cGVycGFyYW1ldGVycyBmb3IgYSByYW5kb20gZm9yZXN0IHJlZ3Jlc3Npb24gbW9kZWwuICANCg0KV2Ugd2lsbCBiZSBvcHRpbWlzaW5nICoqdHJlZXMqKiBhbmQgKiptdHJ5KiogdmFsdWVzIGZvciBvdXIgcmFuZ2VyIG1vZGVsLCB1c2luZyBjcm9zc3ZhbGlkYXRpb24gcmVzdWx0cyBvZiBvdXIgdHJhaW5pbmcgZGF0YS4gTWVhbiBhYnNvbHV0ZSBlcnJvciAoTUFFKSB3aWxsIGJlIGV2YWx1YXRlZCBmb3IgZWFjaCBzZXQgb2YgcHJlZGljdGlvbnMuICANCg0KDQpgYGB7ciBXb3JrZmxvdyBSRn0NCnJmX21vZGVsIDwtIA0KICByYW5kX2ZvcmVzdCh0cmVlcyA9IHR1bmUoKSwgbXRyeSA9IHR1bmUoKSkgJT4lIA0KICBzZXRfZW5naW5lKCJyYW5nZXIiLCBpbXBvcnRhbmNlID0gImltcHVyaXR5IikgJT4lIA0KICBzZXRfbW9kZSgicmVncmVzc2lvbiIpDQoNCmdyaWRfcmYgPC0gDQogIGdyaWRfbWF4X2VudHJvcHkoDQogICAgdHJlZXMocmFuZ2UgPSBjKDEwMCwgMjAwMCkpLA0KICAgIG10cnkocmFuZ2UgPSBjKDMsIDE1KSksDQogICAgc2l6ZSA9IDMwKQ0KDQpteV9tZXRyaWNzIDwtIG1ldHJpY19zZXQobWFlKQ0KDQpgYGANCg0KDQpgYGB7ciBUdW5lIFJGLCBpbmNsdWRlPUZBTFNFfQ0Kd29ya2Zsb3dfcmYgPC0gDQogIHdvcmtmbG93KCkgJT4lIA0KICBhZGRfcmVjaXBlKHJlY2lwZV8xKSAlPiUNCiAgYWRkX21vZGVsKHJmX21vZGVsKQ0KDQpyZl9maXQgPC0gdHVuZV9ncmlkKA0KICB3b3JrZmxvd19yZiwNCiAgcmVzYW1wbGVzID0gY3YsDQogIGdyaWQgPSBncmlkX3JmLA0KICBtZXRyaWNzID0gbXlfbWV0cmljcywNCiAgY29udHJvbCA9IGNvbnRyb2xfZ3JpZCh2ZXJib3NlID0gVFJVRSkNCikNCmBgYA0KDQpBZnRlciBjcm9zc3ZhbGlkYXRpbmcgZm9yIGEgcmFuZ2Ugb2YgKip0cmVlcyoqIGFuZCAqKm10cnkqKiB2YWx1ZXMsIGxldCdzIHBsb3QgdmFsdWVzIG9mIE1BRSBhbmQgbGlzdCB0aGUgYmVzdCBtb2RlbHMuDQoNCmBgYHtyIFBsb3QgbWV0cmljcyBSRiwgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCm1hZV9wbG90IDwtIG1ldHJpY3NfcGxvdChyZl9maXQsIG1ldHJpYyA9ICJtYWUiKQ0KZ2dzYXZlKGhlcmU6OmhlcmUoInBsb3RzL21ldHJpY3NfMS5wbmciKSwgaGVpZ2h0ID0gNSwgZHBpID0gMjAwKQ0KDQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlOjpoZXJlKCJwbG90cy9tZXRyaWNzXzEucG5nIikpDQoNCnNob3dfYmVzdChyZl9maXQsIG1ldHJpYyA9ICJtYWUiKQ0KDQpgYGANCg0KV2Ugd2lsbCBwaWNrIHRoZSB2YWx1ZXMgb2YgKip0cmVlcyoqIGFuZCAqKm10cnkqKiB3aXRoIHRoZSBsb3dlc3QgZXJyb3IuIE5vdyB3ZSB3aWxsIGNob29zZSB0aGVzZSB2YWx1ZXMgdG8gdHJhaW4gYSBtb2RlbCB1c2luZyB0aGUgdHJhaW5pbmcgZGF0YSBhbmQgZml0IHRoZSBtb2RlbCB0byB0aGUgdGVzdCBkYXRhLiBMZXQncyBzZWUgaG93IHRoZSBwcmVkaWN0aW9ucyBjb21wYXJlIHdpdGggcmVhbGl0eSBmb3IgdGhlIHRlc3Qgc2V0IGFuZCBjYWxjdWxhdGUgdGhlIHRlc3QgTUFFLiBUaGUgbGluZSBvbiB0aGUgcGxvdCBpcyB0aGUgbGluZSBvZiBlcXVhbGl0eS4gIA0KDQoNCmBgYHtyIFRlc3QgcHJlZGljdGlvbiBSRiwgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCnR1bmVkX3JmIDwtDQogIHdvcmtmbG93X3JmICU+JSANCiAgZmluYWxpemVfd29ya2Zsb3coc2VsZWN0X2Jlc3QocmZfZml0LCBtZXRyaWMgPSAibWFlIikpICU+JSANCiAgZml0KGRhdGEgPSB0cmFpbikNCg0KcHJlZF9yZiA8LSBwcmVkaWN0KHR1bmVkX3JmLCB0ZXN0KSAlPiUNCiAgYmluZF9jb2xzKHRlc3QgJT4lIHNlbGVjdChUb3RhbC5DdXAuUG9pbnRzKSkNCg0KcHJlZF9wbG90IDwtIGdncGxvdChwcmVkX3JmLCBhZXMoLnByZWQsIFRvdGFsLkN1cC5Qb2ludHMpKSArIGdlb21fcG9pbnQoY29sb3VyID0gInRhbjMiKSArIA0KICBnZW9tX2FibGluZShzbG9wZSA9IDEsIGludGVyY2VwdCA9IDApICsNCiAgdGhlbWVfYncoKSArIGdndGl0bGUoIlRlc3QgU2V0IFByZWRpY3Rpb25zIGZvciBUb3RhbC5DdXAuUG9pbnRzIiwgDQogICAgICAgICAgICAgICAgICAgICAgIHN1YnRpdGxlID0gIkZpbmFsIFJhbmRvbSBGb3Jlc3QgTW9kZWwiKSArIA0KICBsYWJzKHggPSAiUHJlZGljdGlvbiIsIHkgPSAiQWN0dWFsIikNCmdnc2F2ZShoZXJlKCJwbG90cy9wcmVkX3Bsb3QucG5nIiksIGhlaWdodCA9IDUsIGRwaSA9IDIwMCkNCg0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZSgicGxvdHMvcHJlZF9wbG90LnBuZyIpKQ0KDQpjYXQoIk1lYW4gQWJzb2x1dGUgRXJyb3IgZm9yIFRlc3Qgc2V0ID0iLCBtYWVfdmVjKHRydXRoID0gcHJlZF9yZiRUb3RhbC5DdXAuUG9pbnRzLCBlc3RpbWF0ZSA9IHByZWRfcmYkLnByZWQpKQ0KYGBgDQpPdXIgdGVzdCBzZXQgZXJyb3IgaXMgYmV0dGVyIHRoYW4gb3VyIGJlc3QgY3Jvc3N2YWxpZGF0aW9uIGVycm9yLiAgDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjp0ZWFsOyI+TGluZWFyIE1vZGVsPC9zcGFuPiAgDQoNCkxldCdzIGNvbXBhcmUgdGhpcyB3aXRoIGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwuIFdlIHdpbGwgYmUgb3B0aW1pc2luZyAqKm1peHR1cmUqKiBhbmQgKipwZW5hbHR5KiogdmFsdWVzIGZvciBvdXIgZ2xtbmV0IG1vZGVsLCB1c2luZyBjcm9zc3ZhbGlkYXRpb24gcmVzdWx0cyBvZiBvdXIgdHJhaW5pbmcgZGF0YS4gTWVhbiBhYnNvbHV0ZSBlcnJvciAoTUFFKSB3aWxsIGJlIGV2YWx1YXRlZCBmb3IgZWFjaCBzZXQgb2YgcHJlZGljdGlvbnMuICANCg0KDQpgYGB7ciBXb3JrZmxvdyBHTE19DQptb2RlbF9nbG0gPC0gDQogIGxpbmVhcl9yZWcocGVuYWx0eSA9IHR1bmUoKSwgbWl4dHVyZSA9IHR1bmUoKSkgJT4lIA0KICBzZXRfZW5naW5lKCJnbG1uZXQiKQ0KDQpncmlkX2dsbSA8LSANCiAgZ3JpZF9tYXhfZW50cm9weSgNCiAgICBwZW5hbHR5KHJhbmdlID0gYygwLjAxLCAwLjMpLCB0cmFucyA9IE5VTEwpLA0KICAgIG1peHR1cmUocmFuZ2UgPSBjKDAsIDEpKSwNCiAgICBzaXplID0gMzApDQoNCm15X21ldHJpY3MgPC0gbWV0cmljX3NldChtYWUpDQoNCmBgYA0KDQoNCmBgYHtyIFR1bmUgR0xNLCBpbmNsdWRlPUZBTFNFfQ0Kd29ya2Zsb3dfZ2xtIDwtIA0KICB3b3JrZmxvdygpICU+JSANCiAgYWRkX3JlY2lwZShyZWNpcGVfMSkgJT4lDQogIGFkZF9tb2RlbChtb2RlbF9nbG0pDQoNCmdsbV9maXQgPC0gdHVuZV9ncmlkKA0KICB3b3JrZmxvd19nbG0sDQogIHJlc2FtcGxlcyA9IGN2LA0KICBncmlkID0gZ3JpZF9nbG0sDQogIG1ldHJpY3MgPSBteV9tZXRyaWNzLA0KICBjb250cm9sID0gY29udHJvbF9ncmlkKHZlcmJvc2UgPSBUUlVFKQ0KKQ0KYGBgDQoNCkFmdGVyIGNyb3NzdmFsaWRhdGluZyBmb3IgYSByYW5nZSBvZiAqKm1peHR1cmUqKiBhbmQgKipwZW5hbHR5KiogdmFsdWVzLCBsZXQncyBwbG90IHZhbHVlcyBvZiBNQUUgYW5kIGxpc3QgdGhlIGJlc3QgbW9kZWxzLg0KDQpgYGB7ciBQbG90IG1ldHJpY3MgR0xNLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KbWFlX3Bsb3QgPC0gbWV0cmljc19wbG90KGdsbV9maXQsIG1ldHJpYyA9ICJtYWUiKQ0KZ2dzYXZlKGhlcmU6OmhlcmUoInBsb3RzL21ldHJpY3NfMi5wbmciKSwgaGVpZ2h0ID0gNSwgZHBpID0gMjAwKQ0KDQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlOjpoZXJlKCJwbG90cy9tZXRyaWNzXzIucG5nIikpDQoNCnNob3dfYmVzdChnbG1fZml0LCBtZXRyaWMgPSAibWFlIikNCg0KYGBgDQoNCkFnYWluIHdlIHdpbGwgcGljayB0aGUgdmFsdWVzIG9mICoqbWl4dHVyZSoqIGFuZCAqKnBlbmFsdHkqKiB3aXRoIHRoZSBsb3dlc3QgZXJyb3IuIE5vdyB3ZSB3aWxsIGNob29zZSB0aGVzZSB2YWx1ZXMgdG8gdHJhaW4gYSBtb2RlbCB1c2luZyB0aGUgdHJhaW5pbmcgZGF0YSBhbmQgZml0IHRoZSBtb2RlbCB0byB0aGUgdGVzdCBkYXRhLiBBZ2FpbiBsZXQncyBzZWUgaG93IHRoZSBwcmVkaWN0aW9ucyBjb21wYXJlIHdpdGggcmVhbGl0eSBmb3IgdGhlIHRlc3Qgc2V0IGFuZCBjYWxjdWxhdGUgdGhlIHRlc3QgTUFFLiBUaGUgbGluZSBvbiB0aGUgcGxvdCBpcyB0aGUgbGluZSBvZiBlcXVhbGl0eS4gIA0KDQoNCmBgYHtyIFRlc3QgcHJlZGljdGlvbiBHTE0sIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQp0dW5lZF9nbG0gPC0NCiAgd29ya2Zsb3dfZ2xtICU+JSANCiAgZmluYWxpemVfd29ya2Zsb3coc2VsZWN0X2Jlc3QoZ2xtX2ZpdCwgbWV0cmljID0gIm1hZSIpKSAlPiUgDQogIGZpdChkYXRhID0gdHJhaW4pDQoNCnByZWRfZ2xtIDwtIHByZWRpY3QodHVuZWRfZ2xtLCB0ZXN0KSAlPiUNCiAgYmluZF9jb2xzKHRlc3QgJT4lIHNlbGVjdChUb3RhbC5DdXAuUG9pbnRzKSkNCg0KcHJlZF9wbG90IDwtIGdncGxvdChwcmVkX2dsbSwgYWVzKC5wcmVkLCBUb3RhbC5DdXAuUG9pbnRzKSkgKyBnZW9tX3BvaW50KGNvbG91ciA9ICJ0YW40IikgKyANCiAgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBpbnRlcmNlcHQgPSAwKSArDQogIHRoZW1lX2J3KCkgKyBnZ3RpdGxlKCJUZXN0IFNldCBQcmVkaWN0aW9ucyBmb3IgVG90YWwuQ3VwLlBvaW50cyIsIA0KICAgICAgICAgICAgICAgICAgICAgICBzdWJ0aXRsZSA9ICJGaW5hbCBMaW5lYXIgTW9kZWwiKSArIA0KICBsYWJzKHggPSAiUHJlZGljdGlvbiIsIHkgPSAiQWN0dWFsIikNCmdnc2F2ZShoZXJlKCJwbG90cy9wcmVkX3Bsb3QucG5nIiksIGhlaWdodCA9IDUsIGRwaSA9IDIwMCkNCg0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZSgicGxvdHMvcHJlZF9wbG90LnBuZyIpKQ0KDQpjYXQoIk1lYW4gQWJzb2x1dGUgRXJyb3IgZm9yIFRlc3Qgc2V0ID0iLCBtYWVfdmVjKHRydXRoID0gcHJlZF9nbG0kVG90YWwuQ3VwLlBvaW50cywgZXN0aW1hdGUgPSBwcmVkX2dsbSQucHJlZCkpDQpgYGANCg0KQWdhaW4sIHRoZSBlcnJvciBvbiB0aGUgdGVzdCBzZXQgaXMgYmV0dGVyIHRoYW4gdGhhdCBvZiB0aGUgY3Jvc3N2YWxpZGF0aW9uIHJlc3VsdHMuICANCg0KT3ZlcmFsbCwgaXQgYXBwZWFycyB0aGF0IHRoZSByYW5kb20gZm9yZXN0IG1vZGVsIGlzIGJldHRlciBhdCBwcmVkaWN0aW5nIGNvZmZlZSBxdWFsaXR5IHRoYW4gdGhlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsLiBJdCBtYXkgYmUgcG9zc2libGUgdG8gcmVkdWNlIHRoZSBwcmVkaWN0aW9uIGVycm9yIGZ1cnRoZXIgYnkgY29tYmluaW5nIHRoZSB0d28gbW9kZWwgcHJlZGljdGlvbnMuICAgDQoNCioqKg0KDQpFbmQNCg0KDQoNCg0KDQo=