Clifton_Baldwin

Data Science Journal of Clif Baldwin

Predicting Food Inspection Results

16 April ’20

Predicting Food Facility Inspections

I did not get a chance to submit this exploration and model to the MachineHack challenge, but it makes for a good post.

<!DOCTYPE html>

Predicting Food Inspections

Machine Hack Challenge for Food Inspections

March - April 2020

Objective:

Given data from past inspections, build a predictive model that is capable of predicting the outcome of an inspection conducted in a facility based on the given set of features

The Data

Before proceeding, load the required R libraries

library(tidyverse)
Registered S3 method overwritten by 'dplyr':
  method           from
  print.rowwise_df     
── Attaching packages ────────────────────────────────────────────────────────────────────────────────────── tidyverse 1.2.1 ──
✔ ggplot2 3.2.1     ✔ purrr   0.3.2
✔ tibble  2.1.3     ✔ dplyr   0.8.3
✔ tidyr   0.8.3     ✔ stringr 1.4.0
✔ readr   1.3.1     ✔ forcats 0.4.0
── Conflicts ───────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
library(readxl) # used to read the given Excel file
library(lubridate) # used to handle the Date column

Attaching package: ‘lubridate’

The following object is masked from ‘package:base’:

    date

From the provided documentation Features : ID: A unique id for each inspection Date: The date at which the inspection was done in a particular facility LicenseNo: De-identified license number for a particular facility FacilityID: De-identified unique facility id for a facility FacilityName: The encoded name of a facility Type: The type of the facility being inspected Street: The encoded street where the facility is located City: The encoded city where the facility is located State: The encoded state where the facility is located LocationID: An encoded location feature. Reason: The primary reason for the inspection SectionViolations: Laws violated by the facility RiskLevel: The level of risk the facility possesses to the consumers. Geo_Loc: De-identified geo location of the facility

Target : Inspection_Results: The result of the inspection The inspection results can have any of the following values : 0:’FACILITY CHANGED’ 1:’FAIL’ 2:’FURTHER INSPECTION REQUIRED’, 3:’INSPECTION OVERRULED’ 4:’PASS’ 5:’PASS(CONDITIONAL)’ 6:’SHUT-DOWN’

Now load the training dataset as a data frame and take a look

df <- read_excel("Data_Train.xlsx", sheet = 1)
glimpse(df)
Observations: 147,443
Variables: 15
$ ID                 <dbl> 3.110349e+13, 1.008900e+13, 4.014897e+13, 3.715771e+13, 4.747805e+13, 2.523429e+13, 1.764017e+13,…
$ Date               <chr> "26-04-2010", "21-06-2009", "01-05-2013", "28-09-2015", "09-12-2015", "07-02-2016", "04-04-2011",…
$ LicenseNo          <dbl> 4744, 2973, 18223, 20825, 2136, 13009, 9621, 32170, 27027, 7131, 26522, 3350, 15323, 34228, 37390…
$ FacilityID         <dbl> 8123, 12268, 1112, 20007, 16867, 7942, 3791, 24054, 24785, 2892, 5574, 12919, 2017, 24693, 26965,…
$ FacilityName       <dbl> 7715, 11664, 969, 19115, 10409, 7547, 3576, 8766, 23652, 2707, 5307, 12263, 1829, 23563, 5722, 11…
$ Type               <chr> "RESTAURANT", "GROCERY STORE", "RESTAURANT", "RESTAURANT", "RESTAURANT", "RESTAURANT", "RESTAURAN…
$ Street             <dbl> 15522, 3057, 14988, 3661, 7876, 12275, 2133, 5006, 4554, 5899, 16017, 4538, 17881, 14317, 4306, 1…
$ City               <chr> "id-11235901", "id-11235901", "id-11235901", "id-11235901", "id-11235901", "id-11235901", "id-112…
$ State              <chr> "id_1890134", "id_1890134", "id_1890134", "id_1890134", "id_1890134", "id_1890134", "id_1890134",…
$ LocationID         <dbl> 81876, 81862, 81883, 81859, 81886, 81877, 81887, 81851, 81892, 81877, 81864, 81867, 81862, 81905,…
$ Reason             <chr> "CANVASS", "COMPLAINT", "CANVASS", "CANVASS RE-INSPECTION", "COMPLAINT", "CANVASS", "CANVASS RE-I…
$ SectionViolations  <dbl> 33, 33, NA, 31, 30, 18, 32, NA, NA, 32, NA, 33, NA, 16, 2, 32, 33, 18, 21, NA, 32, 32, 33, NA, 32…
$ RiskLevel          <chr> "High", "High", "High", "Medium", "High", "High", "High", "Low", "High", "High", "Medium", "High"…
$ Geo_Loc            <chr> "locid16406", "locid878", "locid3368", "locid11839", "locid12264", "locid3935", "locid8876", "loc…
$ Inspection_Results <chr> "4", "4", "6", "4", "4", "1", "4", "1", "4", "4", "6", "4", "3", "1", "5", "4", "4", "1", "5", "1…

Most of the columns are not in the appropriate format for this analysis. Although I might not need to use ID, I know it should be a factor instead of a number.

# Convert ID to type factor
df$ID <- factor(df$ID)

Change Date from character to date. In doing so, Date fails with parsing errors. Investigate…

x <- dmy(df$Date)
 53 failed to parse.
ids <- df[is.na(x),"ID"]
ids <- ids %>% pull()
df %>% filter(ID %in% ids) %>% select(Date)

There are 53 February 29 dates during non-Leap Years! Since I cannot ask anyone what these dates should be, I guess I will make them March 1?

df <- df %>%
  mutate(Date = case_when(ID %in% ids ~ str_replace(Date, "29-02","01-03"), TRUE ~ Date))

Now Date will convert from character to date

df$Date <- dmy(df$Date)

There are many other categorical variables that are stored as double or character. Convert these remaining appropriate (categorical) features to factors

df$LicenseNo <- factor(df$LicenseNo)
df$FacilityID <- factor(df$FacilityID)
df$FacilityName <- factor(df$FacilityName)
df$Type <- factor(df$Type)
df$City <- factor(df$City)
df$State <- factor(df$State) # State should be unique, but Street may not (same street name in different states?)
df$LocationID <- factor(df$LocationID)
df$Reason <- factor(df$Reason)
df$SectionViolations <- factor(df$SectionViolations)
df$RiskLevel <- factor(df$RiskLevel)
df$Geo_Loc <- factor(df$Geo_Loc)

Inspection_Results needs to be a factor also, but I want to make it an Integer for some histograms first.

Analysis

I wonder if inspections take place throughout the year, and if so is there a seasonal effect? A count of obsevations per month should show if any months are lacking.

sapply(1:12, function(x) { length(which(month(df$Date)==x)) })
 [1] 11267 10543 12863 12455 13708 13596 11073 13372 13708 13522 11471  9865

Ok, at least 10 thousand obsevations per month. Well, December has 9,865 but close enough since it is not unusual that there are less inspections around the holidays. Although inspections do not appear seasonal in the dataset, perhaps time of year has an impact on inspection results. I will create a variable to indicate cold months from warm months.

df$winter <- ifelse((month(df$Date) %in% c(10,11,12,1,2,3)), 1L, 0L)
df$winter <- factor(df$winter)

How many different Type are there

length(levels(df$Type))
[1] 409

Hmm, 499 different factors. How many are NA?

sum(is.na(df$Type))
[1] 3485

There are 3485 without a Type. Investigate what is happening when no Type

nas <- df[is.na(df$Type),]
sum(is.na(nas$SectionViolations))
[1] 3401

Almost all (3401 out of 3485 that have no Type have no SectionViolations. Although interesting, do these NA values have any impact on the Inspection_Results, which is the target variable. Let us look at a histogram of all Inspection_Results

hist(as.integer(df$Inspection_Results), main = "Histogram of All Inspection_Results", xlab = "Inspection Results")

Now let us compare the histogram with Type observations of only NA and SectionViolations of NA

hist(as.integer(nas[is.na(nas$SectionViolations),]$Inspection_Results), main = "Inspection_Results with Type NA and SectionViolations NA", xlab = "Inspection Results")

Interesting! Why are most of the missing SectionViolations the ones that are ShutDown??

sum(which(nas[is.na(nas$SectionViolations),]$Inspection_Results != "6"))
[1] 806390
sum(which(nas[is.na(nas$SectionViolations),]$Inspection_Results == "6"))
[1] 4978711

There are 806,390 observations that are not “ShutDown” and 4,978,711 that are ShutDown. I am thinking I cannot remove these NA obsevations without introducing bias, but I should look at teh data without those observations first.

nas <- df[!is.na(df$Type),]
hist(as.integer(nas$Inspection_Results), main = "Histogram of Inspection_Results without Type = NA", xlab = "Inspection Results")

What if we just remove the SectionViolations that are NA

hist(as.integer(nas[is.na(nas$SectionViolations),]$Inspection_Results), main = "Inspection_Results without Type = NA and with SectionViolations NA", xlab = "Inspection Results")

It appears we lose many of the “PASS” (i.e. “5”). So I am not going to remove the NA values at this time. Of the non-NA Type, how many are unique (i.e. only one obsevarion)

df %>%
  filter(!is.na(Type)) %>%  
  group_by(Type) %>%
  summarise(n = n()) %>% 
  filter(n < 2) %>% nrow()
[1] 71

71 unique observations of Type. How many additional ones have less than 10 observations

df %>%
  filter(!is.na(Type)) %>%  
  group_by(Type) %>%
  summarise(n = n()) %>% 
  filter(n < 10 & n > 1) %>% nrow()
[1] 245

Another 245 Types, which means 316 have less than 10 observations. Going to the other extreme, how many have more than 20 observations per Type

df %>%
  filter(!is.na(Type)) %>%  
  group_by(Type) %>%
  summarise(n = n()) %>% 
  filter(n > 20) %>% nrow()
[1] 53

53 Types have more than 20 observations. How mnany total observations of data is that

df %>%
  filter(!is.na(Type)) %>%  
  group_by(Type) %>%
  summarise(n = n()) %>% 
  filter(n > 20) %>% summarise(sum(n))
NA

There are 142,350 observations with more than 20 observations per Type. So do I keep or exclude the NA’s and the rare Types?? For now, I am keeping all the observations. Moving on to City,

levels(df$City)
[1] "id-11235901" "id-11275913"

Only two cities in the dataset. Are they from different states?

levels(df$State)
[1] "id_1890134" "id_1890135"

Also two factors. Although I am sure there is one city per state, I should verify it.

length(which(df$State[which(df$City == "id-11235901")] == 'id_1890135'))
[1] 19
length(which(df$State[which(df$City == "id-11275913")] == 'id_1890135'))
[1] 22

Wait, what?? What about the two cities in the other state

length(which(df$State[which(df$City == "id-11235901")] == 'id_1890134'))
[1] 147177
length(which(df$State[which(df$City == "id-11275913")] == 'id_1890134'))
[1] 225

It appears there are a total of four, not two, combinations of City and State. Running a chi-square test to determine independence

chisq.test(df$City, df$State, correct = FALSE)
Chi-squared approximation may be incorrect

    Pearson's Chi-squared test

data:  df$City and df$State
X-squared = 7016.5, df = 1, p-value < 2.2e-16

So City and State are independent! How about Reason

levels(df$Reason)
 [1] "CANVASS"                                "CANVASS RE-INSPECTION"                 
 [3] "COMPLAINT"                              "COMPLAINT RE-INSPECTION"               
 [5] "COMPLAINT-FIRE"                         "CONSULTATION"                          
 [7] "LICENSE"                                "LICENSE RE-INSPECTION"                 
 [9] "LICENSE-TASK FORCE"                     "OUT OF BUSINESS"                       
[11] "RECENT INSPECTION"                      "SHORT FORM COMPLAINT"                  
[13] "SHORT FORM FIRE-COMPLAINT"              "SUSPECTED FOOD POISONING"              
[15] "SUSPECTED FOOD POISONING RE-INSPECTION" "TAG REMOVAL"                           
[17] "TASK FORCE LIQUOR 1475"                

17 levels. How many are NA

sum(is.na(df$Reason))
[1] 0

And no NA for Reason. Next, SectionViolations

length(levels(df$SectionViolations))
[1] 61

61 levels of SectionViolations. How many are NA

sum(is.na(df$SectionViolations))
[1] 39068

39,068 are NA (about 26%). If we take out the NA SectionViolations, let us look at the histogram

hist(as.integer(df[!is.na(df$SectionViolations),]$Inspection_Results), main = "Inspection_Results without SectionViolations of NA", xlab = "Inspection Results")

We lose the 2, 6, and most of the 3’s. So the presence of SectionViolations indicates the result is probably 1, 4, or 5 (PASS). So I guess we cannot exclude them.

Look at the RiskLevel

levels(df$RiskLevel)
[1] "High"      "Low"       "Medium"    "Uncertain"

Note 4 levels with one being “Uncertain”. How many are NA

sum(is.na(df$RiskLevel))
[1] 0

No RiskLevel are NA. How many are “Uncertain” though

length(which(df$RiskLevel=="Uncertain"))
[1] 21

Only 21 are “Uncertain”

Finally, let us look at Geo_loc

length(levels(df$Geo_Loc))
[1] 16316

No! Over 16k levels! I think I will use City, Reason, and RiskLevel, and try also Type and SectionViolations.

I need to make sure the target variable is clean

df$Inspection_Results <- factor(df$Inspection_Results)
levels(df$Inspection_Results)
[1] "0" "1" "2" "3" "4" "5" "6"

Validation dataset

I need to break the train dataset into a training and validation datasets

indexes = sample(1:nrow(df), size=0.75*nrow(df))
training = df[indexes,]  
testing = df[-indexes,]  
rm(indexes)

Modeling

Of course there are many options, but an appropriate model needs to be a classification model that accepts categorical features. So I cannot select features using Principal Component Aanalysis since that requires numerical data. The choice I am selecting is a Naive Bayes Classification model.

Load the R library that has a Naive Bayes function

library(e1071)

I will use set.seed so that each model run starts off “the same.” Therefore the only change should be the features input to the model. For the first Naive Bayes model, I will use City + Reason + RiskLevel + Type + SectionViolations + winter as the features.

set.seed(7)
model = naiveBayes(Inspection_Results ~ City + Reason + RiskLevel + Type + SectionViolations + winter, data=training)

Using the test dataset, predict using the model

Predict <- predict(model,newdata = testing )

Create a confusion matrix to check accuracy

cm <- table(Predict, testing$Inspection_Results )
sum(diag(cm)) / sum(cm) # Accuracy
[1] 0.7669352
rm(model, Predict, cm)

76% accurate I am not sure if we prefer a higher sensitivity or higher specificity, where Sensitivity is the true positive rate or probability of detection and Specificity is the true negative rate or proportion of actual negatives that are correctly identified as such. Furthermore, I am not sure if we care more about certain results than others. Therefore I will just use accuracy as the measure of the model.

Let us try the model without the winter feature

set.seed(7)
model = naiveBayes(Inspection_Results ~ City + Reason + RiskLevel + Type + SectionViolations, data=training)

# Predict using the model and the validation dataset
Predict <- predict(model,newdata = testing )

#Confusion matrix to check accuracy
cm <- table(Predict, testing$Inspection_Results )
sum(diag(cm)) / sum(cm) # Accuracy
[1] 0.7663113
rm(model, Predict, cm)

Almost no difference in accuracy. Assuming a simpler model is a better model, I will omit winter. I am skeptical of SectionViolations and will try the model without it.

set.seed(7)
model = naiveBayes(Inspection_Results ~ City + Reason + RiskLevel + Type, data=training)

# Predict using the model and the validation dataset
Predict <- predict(model,newdata = testing )

#Confusion matrix to check accuracy
cm <- table(Predict, testing$Inspection_Results )

(sum(cm) - sum(diag(cm))) / sum(cm) #Accuracy
[1] 0.4508559
rm(model, Predict, cm)

I was wrong!! SectionViolations is important! I will put SectionViolations back in as a feature but try to omit City.

set.seed(7)
model = naiveBayes(Inspection_Results ~ Reason + RiskLevel + Type + SectionViolations, data=training)

# Predict using the model and the validation dataset
Predict <- predict(model,newdata = testing )

#Confusion matrix to check accuracy
cm <- table(Predict, testing$Inspection_Results )
sum(diag(cm)) / sum(cm) # Accuracy
[1] 0.7664198
rm(model, Predict, cm)

It appears City has no impact on the model. Now I will try the model without Type

set.seed(7)
model = naiveBayes(Inspection_Results ~ Reason + RiskLevel + SectionViolations, data=training)

# Predict using the model and the validation dataset
Predict <- predict(model,newdata = testing )

#Confusion matrix to check accuracy
cm <- table(Predict, testing$Inspection_Results )
sum(diag(cm)) / sum(cm) # Accuracy
[1] 0.7748026
rm(model, Predict, cm)

Only marginal, but this model is the best so far. Maybe I need to do something with the Type with NA. Try the model with Type without NA’s

set.seed(7)
model = training %>%
  filter(!is.na(Type)) %>%
  naiveBayes(Inspection_Results ~ Reason + RiskLevel + SectionViolations + Type, data=.)
  
# Predict using the model and the validation dataset
Predict <- predict(model,newdata = testing )

#Confusion matrix to check accuracy
cm <- table(Predict, testing$Inspection_Results )
sum(diag(cm)) / sum(cm) # Accuracy
[1] 0.7483519
rm(model, Predict, cm)

As I suspected earlier, we lose something when Type of NA is omitted. I guess the NA’s are predictive, but Type still not helpful. From what I tried, the best Naive Bayes model gets around 77% accuracy with the model = naiveBayes(Inspection_Results ~ Reason + RiskLevel + SectionViolations, data=training).

Just an aside, this “challenge” was on MachineHack but had ended before I found it. Of course it does not really matter since my results are not as good as the top models submitted to the challenge.

LS0tCnRpdGxlOiAiUHJlZGljdGluZyBGb29kIEluc3BlY3Rpb25zIgphdXRob3I6ICJEci4gQ2xpZnRvbiBCYWxkd2luIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIE1hY2hpbmUgSGFjayBDaGFsbGVuZ2UgZm9yIEZvb2QgSW5zcGVjdGlvbnMKTWFyY2ggLSBBcHJpbCAyMDIwCgojIyBPYmplY3RpdmU6IApHaXZlbiBkYXRhIGZyb20gcGFzdCBpbnNwZWN0aW9ucywgYnVpbGQgYSBwcmVkaWN0aXZlIG1vZGVsIHRoYXQgaXMgY2FwYWJsZSBvZiBwcmVkaWN0aW5nIHRoZSBvdXRjb21lIG9mIGFuIGluc3BlY3Rpb24gY29uZHVjdGVkIGluIGEgZmFjaWxpdHkgYmFzZWQgb24gdGhlIGdpdmVuIHNldCBvZiBmZWF0dXJlcwoKIyMgVGhlIERhdGEKQmVmb3JlIHByb2NlZWRpbmcsIGxvYWQgdGhlIHJlcXVpcmVkIFIgbGlicmFyaWVzCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkocmVhZHhsKSAjIHVzZWQgdG8gcmVhZCB0aGUgZ2l2ZW4gRXhjZWwgZmlsZQpsaWJyYXJ5KGx1YnJpZGF0ZSkgIyB1c2VkIHRvIGhhbmRsZSB0aGUgRGF0ZSBjb2x1bW4KCmBgYAoKRnJvbSB0aGUgcHJvdmlkZWQgZG9jdW1lbnRhdGlvbiAKIEZlYXR1cmVzIDogCiAgICBJRDogQSB1bmlxdWUgaWQgZm9yIGVhY2ggaW5zcGVjdGlvbgogICAgRGF0ZTogVGhlIGRhdGUgYXQgd2hpY2ggdGhlIGluc3BlY3Rpb24gd2FzIGRvbmUgaW4gYSBwYXJ0aWN1bGFyIGZhY2lsaXR5CiAgICBMaWNlbnNlTm86IERlLWlkZW50aWZpZWQgbGljZW5zZSBudW1iZXIgZm9yIGEgcGFydGljdWxhciBmYWNpbGl0eQogICAgRmFjaWxpdHlJRDogRGUtaWRlbnRpZmllZCB1bmlxdWUgZmFjaWxpdHkgaWQgIGZvciBhIGZhY2lsaXR5CiAgICBGYWNpbGl0eU5hbWU6IFRoZSBlbmNvZGVkIG5hbWUgb2YgYSBmYWNpbGl0eQogICAgVHlwZTogVGhlIHR5cGUgb2YgdGhlIGZhY2lsaXR5IGJlaW5nIGluc3BlY3RlZAogICAgU3RyZWV0OiBUaGUgZW5jb2RlZCBzdHJlZXQgd2hlcmUgdGhlIGZhY2lsaXR5IGlzIGxvY2F0ZWQKICAgIENpdHk6IFRoZSBlbmNvZGVkIGNpdHkgd2hlcmUgdGhlIGZhY2lsaXR5IGlzIGxvY2F0ZWQKICAgIFN0YXRlOiBUaGUgZW5jb2RlZCBzdGF0ZSB3aGVyZSB0aGUgZmFjaWxpdHkgaXMgbG9jYXRlZAogICAgTG9jYXRpb25JRDogQW4gZW5jb2RlZCBsb2NhdGlvbiBmZWF0dXJlLgogICAgUmVhc29uOiBUaGUgcHJpbWFyeSByZWFzb24gZm9yIHRoZSBpbnNwZWN0aW9uCiAgICBTZWN0aW9uVmlvbGF0aW9uczogTGF3cyB2aW9sYXRlZCBieSB0aGUgZmFjaWxpdHkKICAgIFJpc2tMZXZlbDogVGhlIGxldmVsIG9mIHJpc2sgdGhlIGZhY2lsaXR5IHBvc3Nlc3NlcyB0byB0aGUgY29uc3VtZXJzLgogICAgR2VvX0xvYzogRGUtaWRlbnRpZmllZCBnZW8gbG9jYXRpb24gb2YgdGhlIGZhY2lsaXR5CgogVGFyZ2V0IDogSW5zcGVjdGlvbl9SZXN1bHRzOiBUaGUgcmVzdWx0IG9mIHRoZSBpbnNwZWN0aW9uCiAgVGhlIGluc3BlY3Rpb24gcmVzdWx0cyBjYW4gaGF2ZSBhbnkgb2YgdGhlIGZvbGxvd2luZyB2YWx1ZXMgOgogICAgMDrigJlGQUNJTElUWSBDSEFOR0VE4oCZCiAgICAxOuKAmUZBSUzigJkKICAgIDI64oCZRlVSVEhFUiBJTlNQRUNUSU9OIFJFUVVJUkVE4oCZLAogICAgMzrigJlJTlNQRUNUSU9OIE9WRVJSVUxFROKAmQogICAgNDrigJlQQVNT4oCZCiAgICA1OuKAmVBBU1MoQ09ORElUSU9OQUwp4oCZCiAgICA2OuKAmVNIVVQtRE9XTuKAmQoKTm93IGxvYWQgdGhlIHRyYWluaW5nIGRhdGFzZXQgYXMgYSBkYXRhIGZyYW1lIGFuZCB0YWtlIGEgbG9vawpgYGB7cn0KZGYgPC0gcmVhZF9leGNlbCgiRGF0YV9UcmFpbi54bHN4Iiwgc2hlZXQgPSAxKQpnbGltcHNlKGRmKQpgYGAKCk1vc3Qgb2YgdGhlIGNvbHVtbnMgYXJlIG5vdCBpbiB0aGUgYXBwcm9wcmlhdGUgZm9ybWF0IGZvciB0aGlzIGFuYWx5c2lzLgpBbHRob3VnaCBJIG1pZ2h0IG5vdCBuZWVkIHRvIHVzZSBJRCwgSSBrbm93IGl0IHNob3VsZCBiZSBhIGZhY3RvciBpbnN0ZWFkIG9mIGEgbnVtYmVyLgpgYGB7cn0KIyBDb252ZXJ0IElEIHRvIHR5cGUgZmFjdG9yCmRmJElEIDwtIGZhY3RvcihkZiRJRCkKYGBgCgpDaGFuZ2UgRGF0ZSBmcm9tIGNoYXJhY3RlciB0byBkYXRlLiBJbiBkb2luZyBzbywgRGF0ZSBmYWlscyB3aXRoIHBhcnNpbmcgZXJyb3JzLiBJbnZlc3RpZ2F0ZS4uLgpgYGB7cn0KeCA8LSBkbXkoZGYkRGF0ZSkKaWRzIDwtIGRmW2lzLm5hKHgpLCJJRCJdCmlkcyA8LSBpZHMgJT4lIHB1bGwoKQpkZiAlPiUgZmlsdGVyKElEICVpbiUgaWRzKSAlPiUgc2VsZWN0KERhdGUpCmBgYAoKVGhlcmUgYXJlIDUzIEZlYnJ1YXJ5IDI5IGRhdGVzIGR1cmluZyBub24tTGVhcCBZZWFycyEgU2luY2UgSSBjYW5ub3QgYXNrIGFueW9uZSB3aGF0IHRoZXNlIGRhdGVzIHNob3VsZCBiZSwgSSBndWVzcyBJIHdpbGwgbWFrZSB0aGVtIE1hcmNoIDE/CmBgYHtyfQpkZiA8LSBkZiAlPiUKICBtdXRhdGUoRGF0ZSA9IGNhc2Vfd2hlbihJRCAlaW4lIGlkcyB+IHN0cl9yZXBsYWNlKERhdGUsICIyOS0wMiIsIjAxLTAzIiksIFRSVUUgfiBEYXRlKSkKYGBgCgpOb3cgRGF0ZSB3aWxsIGNvbnZlcnQgZnJvbSBjaGFyYWN0ZXIgdG8gZGF0ZQpgYGB7cn0KZGYkRGF0ZSA8LSBkbXkoZGYkRGF0ZSkKYGBgCgpUaGVyZSBhcmUgbWFueSBvdGhlciBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgdGhhdCBhcmUgc3RvcmVkIGFzIGRvdWJsZSBvciBjaGFyYWN0ZXIuIENvbnZlcnQgdGhlc2UgcmVtYWluaW5nIGFwcHJvcHJpYXRlIChjYXRlZ29yaWNhbCkgZmVhdHVyZXMgdG8gZmFjdG9ycwpgYGB7cn0KZGYkTGljZW5zZU5vIDwtIGZhY3RvcihkZiRMaWNlbnNlTm8pCmRmJEZhY2lsaXR5SUQgPC0gZmFjdG9yKGRmJEZhY2lsaXR5SUQpCmRmJEZhY2lsaXR5TmFtZSA8LSBmYWN0b3IoZGYkRmFjaWxpdHlOYW1lKQpkZiRUeXBlIDwtIGZhY3RvcihkZiRUeXBlKQpkZiRDaXR5IDwtIGZhY3RvcihkZiRDaXR5KQpkZiRTdGF0ZSA8LSBmYWN0b3IoZGYkU3RhdGUpICMgU3RhdGUgc2hvdWxkIGJlIHVuaXF1ZSwgYnV0IFN0cmVldCBtYXkgbm90IChzYW1lIHN0cmVldCBuYW1lIGluIGRpZmZlcmVudCBzdGF0ZXM/KQpkZiRMb2NhdGlvbklEIDwtIGZhY3RvcihkZiRMb2NhdGlvbklEKQpkZiRSZWFzb24gPC0gZmFjdG9yKGRmJFJlYXNvbikKZGYkU2VjdGlvblZpb2xhdGlvbnMgPC0gZmFjdG9yKGRmJFNlY3Rpb25WaW9sYXRpb25zKQpkZiRSaXNrTGV2ZWwgPC0gZmFjdG9yKGRmJFJpc2tMZXZlbCkKZGYkR2VvX0xvYyA8LSBmYWN0b3IoZGYkR2VvX0xvYykKYGBgCgpJbnNwZWN0aW9uX1Jlc3VsdHMgbmVlZHMgdG8gYmUgYSBmYWN0b3IgYWxzbywgYnV0IEkgd2FudCB0byBtYWtlIGl0IGFuIEludGVnZXIgZm9yIHNvbWUgaGlzdG9ncmFtcyBmaXJzdC4KCiMjIEFuYWx5c2lzCkkgd29uZGVyIGlmIGluc3BlY3Rpb25zIHRha2UgcGxhY2UgdGhyb3VnaG91dCB0aGUgeWVhciwgYW5kIGlmIHNvIGlzIHRoZXJlIGEgc2Vhc29uYWwgZWZmZWN0PyBBIGNvdW50IG9mIG9ic2V2YXRpb25zIHBlciBtb250aCBzaG91bGQgc2hvdyBpZiBhbnkgbW9udGhzIGFyZSBsYWNraW5nLgpgYGB7cn0Kc2FwcGx5KDE6MTIsIGZ1bmN0aW9uKHgpIHsgbGVuZ3RoKHdoaWNoKG1vbnRoKGRmJERhdGUpPT14KSkgfSkKYGBgCgpPaywgYXQgbGVhc3QgMTAgdGhvdXNhbmQgb2JzZXZhdGlvbnMgcGVyIG1vbnRoLiBXZWxsLCBEZWNlbWJlciBoYXMgOSw4NjUgYnV0IGNsb3NlIGVub3VnaCBzaW5jZSBpdCBpcyBub3QgdW51c3VhbCB0aGF0IHRoZXJlIGFyZSBsZXNzIGluc3BlY3Rpb25zIGFyb3VuZCB0aGUgaG9saWRheXMuIEFsdGhvdWdoIGluc3BlY3Rpb25zIGRvIG5vdCBhcHBlYXIgc2Vhc29uYWwgaW4gdGhlIGRhdGFzZXQsIHBlcmhhcHMgdGltZSBvZiB5ZWFyIGhhcyBhbiBpbXBhY3Qgb24gaW5zcGVjdGlvbiByZXN1bHRzLiBJIHdpbGwgY3JlYXRlIGEgdmFyaWFibGUgdG8gaW5kaWNhdGUgY29sZCBtb250aHMgZnJvbSB3YXJtIG1vbnRocy4KYGBge3J9CmRmJHdpbnRlciA8LSBpZmVsc2UoKG1vbnRoKGRmJERhdGUpICVpbiUgYygxMCwxMSwxMiwxLDIsMykpLCAxTCwgMEwpCmRmJHdpbnRlciA8LSBmYWN0b3IoZGYkd2ludGVyKQoKYGBgCgpIb3cgbWFueSBkaWZmZXJlbnQgVHlwZSBhcmUgdGhlcmUKYGBge3J9Cmxlbmd0aChsZXZlbHMoZGYkVHlwZSkpCmBgYAoKSG1tLCA0OTkgZGlmZmVyZW50IGZhY3RvcnMuIEhvdyBtYW55IGFyZSBOQT8KYGBge3J9CnN1bShpcy5uYShkZiRUeXBlKSkKYGBgCgpUaGVyZSBhcmUgMzQ4NSB3aXRob3V0IGEgVHlwZS4gSW52ZXN0aWdhdGUgd2hhdCBpcyBoYXBwZW5pbmcgd2hlbiBubyBUeXBlCmBgYHtyfQpuYXMgPC0gZGZbaXMubmEoZGYkVHlwZSksXQpzdW0oaXMubmEobmFzJFNlY3Rpb25WaW9sYXRpb25zKSkKYGBgCgpBbG1vc3QgYWxsICgzNDAxIG91dCBvZiAzNDg1IHRoYXQgaGF2ZSBubyBUeXBlIGhhdmUgbm8gU2VjdGlvblZpb2xhdGlvbnMuIEFsdGhvdWdoIGludGVyZXN0aW5nLCBkbyB0aGVzZSBOQSB2YWx1ZXMgaGF2ZSBhbnkgaW1wYWN0IG9uIHRoZSBJbnNwZWN0aW9uX1Jlc3VsdHMsIHdoaWNoIGlzIHRoZSB0YXJnZXQgdmFyaWFibGUuIExldCB1cyBsb29rIGF0IGEgaGlzdG9ncmFtIG9mIGFsbCBJbnNwZWN0aW9uX1Jlc3VsdHMKYGBge3J9Cmhpc3QoYXMuaW50ZWdlcihkZiRJbnNwZWN0aW9uX1Jlc3VsdHMpLCBtYWluID0gIkhpc3RvZ3JhbSBvZiBBbGwgSW5zcGVjdGlvbl9SZXN1bHRzIiwgeGxhYiA9ICJJbnNwZWN0aW9uIFJlc3VsdHMiKQpgYGAKCk5vdyBsZXQgdXMgY29tcGFyZSB0aGUgaGlzdG9ncmFtIHdpdGggVHlwZSBvYnNlcnZhdGlvbnMgb2Ygb25seSBOQSBhbmQgU2VjdGlvblZpb2xhdGlvbnMgb2YgTkEgCmBgYHtyfQpoaXN0KGFzLmludGVnZXIobmFzW2lzLm5hKG5hcyRTZWN0aW9uVmlvbGF0aW9ucyksXSRJbnNwZWN0aW9uX1Jlc3VsdHMpLCBtYWluID0gIkluc3BlY3Rpb25fUmVzdWx0cyB3aXRoIFR5cGUgTkEgYW5kIFNlY3Rpb25WaW9sYXRpb25zIE5BIiwgeGxhYiA9ICJJbnNwZWN0aW9uIFJlc3VsdHMiKQpgYGAKCkludGVyZXN0aW5nISBXaHkgYXJlIG1vc3Qgb2YgdGhlIG1pc3NpbmcgU2VjdGlvblZpb2xhdGlvbnMgdGhlIG9uZXMgdGhhdCBhcmUgU2h1dERvd24/PwpgYGB7cn0Kc3VtKHdoaWNoKG5hc1tpcy5uYShuYXMkU2VjdGlvblZpb2xhdGlvbnMpLF0kSW5zcGVjdGlvbl9SZXN1bHRzICE9ICI2IikpCmBgYAoKYGBge3J9CnN1bSh3aGljaChuYXNbaXMubmEobmFzJFNlY3Rpb25WaW9sYXRpb25zKSxdJEluc3BlY3Rpb25fUmVzdWx0cyA9PSAiNiIpKQpgYGAKVGhlcmUgYXJlIDgwNiwzOTAgb2JzZXJ2YXRpb25zIHRoYXQgYXJlIG5vdCAiU2h1dERvd24iIGFuZCA0LDk3OCw3MTEgdGhhdCBhcmUgU2h1dERvd24uIEkgYW0gdGhpbmtpbmcgSSBjYW5ub3QgcmVtb3ZlIHRoZXNlIE5BIG9ic2V2YXRpb25zIHdpdGhvdXQgaW50cm9kdWNpbmcgYmlhcywgYnV0IEkgc2hvdWxkIGxvb2sgYXQgdGVoIGRhdGEgd2l0aG91dCB0aG9zZSBvYnNlcnZhdGlvbnMgZmlyc3QuCgpgYGB7cn0KbmFzIDwtIGRmWyFpcy5uYShkZiRUeXBlKSxdCmhpc3QoYXMuaW50ZWdlcihuYXMkSW5zcGVjdGlvbl9SZXN1bHRzKSwgbWFpbiA9ICJIaXN0b2dyYW0gb2YgSW5zcGVjdGlvbl9SZXN1bHRzIHdpdGhvdXQgVHlwZSA9IE5BIiwgeGxhYiA9ICJJbnNwZWN0aW9uIFJlc3VsdHMiKQoKYGBgCgpXaGF0IGlmIHdlIGp1c3QgcmVtb3ZlIHRoZSBTZWN0aW9uVmlvbGF0aW9ucyB0aGF0IGFyZSBOQQpgYGB7cn0KaGlzdChhcy5pbnRlZ2VyKG5hc1tpcy5uYShuYXMkU2VjdGlvblZpb2xhdGlvbnMpLF0kSW5zcGVjdGlvbl9SZXN1bHRzKSwgbWFpbiA9ICJJbnNwZWN0aW9uX1Jlc3VsdHMgd2l0aG91dCBUeXBlID0gTkEgYW5kIHdpdGggU2VjdGlvblZpb2xhdGlvbnMgTkEiLCB4bGFiID0gIkluc3BlY3Rpb24gUmVzdWx0cyIpCgpgYGAKCkl0IGFwcGVhcnMgd2UgbG9zZSBtYW55IG9mIHRoZSAiUEFTUyIgKGkuZS4gIjUiKS4gU28gSSBhbSBub3QgZ29pbmcgdG8gcmVtb3ZlIHRoZSBOQSB2YWx1ZXMgYXQgdGhpcyB0aW1lLgpPZiB0aGUgbm9uLU5BIFR5cGUsIGhvdyBtYW55IGFyZSB1bmlxdWUgKGkuZS4gb25seSBvbmUgb2JzZXZhcmlvbikKYGBge3J9CmRmICU+JQogIGZpbHRlcighaXMubmEoVHlwZSkpICU+JSAgCiAgZ3JvdXBfYnkoVHlwZSkgJT4lCiAgc3VtbWFyaXNlKG4gPSBuKCkpICU+JSAKICBmaWx0ZXIobiA8IDIpICU+JSBucm93KCkKCmBgYAoKNzEgdW5pcXVlIG9ic2VydmF0aW9ucyBvZiBUeXBlLiBIb3cgbWFueSBhZGRpdGlvbmFsIG9uZXMgaGF2ZSBsZXNzIHRoYW4gMTAgb2JzZXJ2YXRpb25zCmBgYHtyfQpkZiAlPiUKICBmaWx0ZXIoIWlzLm5hKFR5cGUpKSAlPiUgIAogIGdyb3VwX2J5KFR5cGUpICU+JQogIHN1bW1hcmlzZShuID0gbigpKSAlPiUgCiAgZmlsdGVyKG4gPCAxMCAmIG4gPiAxKSAlPiUgbnJvdygpCgpgYGAKCkFub3RoZXIgMjQ1IFR5cGVzLCB3aGljaCBtZWFucyAzMTYgaGF2ZSBsZXNzIHRoYW4gMTAgb2JzZXJ2YXRpb25zLiBHb2luZyB0byB0aGUgb3RoZXIgZXh0cmVtZSwgaG93IG1hbnkgaGF2ZSBtb3JlIHRoYW4gMjAgb2JzZXJ2YXRpb25zIHBlciBUeXBlCmBgYHtyfQpkZiAlPiUKICBmaWx0ZXIoIWlzLm5hKFR5cGUpKSAlPiUgIAogIGdyb3VwX2J5KFR5cGUpICU+JQogIHN1bW1hcmlzZShuID0gbigpKSAlPiUgCiAgZmlsdGVyKG4gPiAyMCkgJT4lIG5yb3coKQoKYGBgCgo1MyBUeXBlcyBoYXZlIG1vcmUgdGhhbiAyMCBvYnNlcnZhdGlvbnMuIEhvdyBtbmFueSB0b3RhbCBvYnNlcnZhdGlvbnMgb2YgZGF0YSBpcyB0aGF0CmBgYHtyfQpkZiAlPiUKICBmaWx0ZXIoIWlzLm5hKFR5cGUpKSAlPiUgIAogIGdyb3VwX2J5KFR5cGUpICU+JQogIHN1bW1hcmlzZShuID0gbigpKSAlPiUgCiAgZmlsdGVyKG4gPiAyMCkgJT4lIHN1bW1hcmlzZShzdW0obikpCgpgYGAKClRoZXJlIGFyZSAxNDIsMzUwIG9ic2VydmF0aW9ucyB3aXRoIG1vcmUgdGhhbiAyMCBvYnNlcnZhdGlvbnMgcGVyIFR5cGUuIFNvIGRvIEkga2VlcCBvciBleGNsdWRlIHRoZSBOQSdzIGFuZCB0aGUgcmFyZSBUeXBlcz8/IEZvciBub3csIEkgYW0ga2VlcGluZyBhbGwgdGhlIG9ic2VydmF0aW9ucy4KTW92aW5nIG9uIHRvIENpdHksCmBgYHtyfQpsZXZlbHMoZGYkQ2l0eSkKYGBgCgpPbmx5IHR3byBjaXRpZXMgaW4gdGhlIGRhdGFzZXQuIEFyZSB0aGV5IGZyb20gZGlmZmVyZW50IHN0YXRlcz8KYGBge3J9CmxldmVscyhkZiRTdGF0ZSkKYGBgCgpBbHNvIHR3byBmYWN0b3JzLiBBbHRob3VnaCBJIGFtIHN1cmUgdGhlcmUgaXMgb25lIGNpdHkgcGVyIHN0YXRlLCBJIHNob3VsZCB2ZXJpZnkgaXQuCmBgYHtyfQpsZW5ndGgod2hpY2goZGYkU3RhdGVbd2hpY2goZGYkQ2l0eSA9PSAiaWQtMTEyMzU5MDEiKV0gPT0gJ2lkXzE4OTAxMzUnKSkKbGVuZ3RoKHdoaWNoKGRmJFN0YXRlW3doaWNoKGRmJENpdHkgPT0gImlkLTExMjc1OTEzIildID09ICdpZF8xODkwMTM1JykpCgpgYGAKCldhaXQsIHdoYXQ/PyBXaGF0IGFib3V0IHRoZSB0d28gY2l0aWVzIGluIHRoZSBvdGhlciBzdGF0ZQpgYGB7cn0KbGVuZ3RoKHdoaWNoKGRmJFN0YXRlW3doaWNoKGRmJENpdHkgPT0gImlkLTExMjM1OTAxIildID09ICdpZF8xODkwMTM0JykpCmxlbmd0aCh3aGljaChkZiRTdGF0ZVt3aGljaChkZiRDaXR5ID09ICJpZC0xMTI3NTkxMyIpXSA9PSAnaWRfMTg5MDEzNCcpKQoKYGBgCgpJdCBhcHBlYXJzIHRoZXJlIGFyZSBhIHRvdGFsIG9mIGZvdXIsIG5vdCB0d28sIGNvbWJpbmF0aW9ucyBvZiBDaXR5IGFuZCBTdGF0ZS4gUnVubmluZyBhIGNoaS1zcXVhcmUgdGVzdCB0byBkZXRlcm1pbmUgaW5kZXBlbmRlbmNlCmBgYHtyfQpjaGlzcS50ZXN0KGRmJENpdHksIGRmJFN0YXRlLCBjb3JyZWN0ID0gRkFMU0UpCmBgYAoKU28gQ2l0eSBhbmQgU3RhdGUgYXJlIGluZGVwZW5kZW50ISAKSG93IGFib3V0IFJlYXNvbgpgYGB7cn0KbGV2ZWxzKGRmJFJlYXNvbikKYGBgCgoxNyBsZXZlbHMuIEhvdyBtYW55IGFyZSBOQQpgYGB7cn0Kc3VtKGlzLm5hKGRmJFJlYXNvbikpCmBgYAoKQW5kIG5vIE5BIGZvciBSZWFzb24uIE5leHQsIFNlY3Rpb25WaW9sYXRpb25zCmBgYHtyfQpsZW5ndGgobGV2ZWxzKGRmJFNlY3Rpb25WaW9sYXRpb25zKSkKYGBgCgo2MSBsZXZlbHMgb2YgU2VjdGlvblZpb2xhdGlvbnMuIEhvdyBtYW55IGFyZSBOQQpgYGB7cn0Kc3VtKGlzLm5hKGRmJFNlY3Rpb25WaW9sYXRpb25zKSkKYGBgCgozOSwwNjggYXJlIE5BIChhYm91dCAyNiUpLiBJZiB3ZSB0YWtlIG91dCB0aGUgTkEgU2VjdGlvblZpb2xhdGlvbnMsIGxldCB1cyBsb29rIGF0IHRoZSBoaXN0b2dyYW0KYGBge3J9Cmhpc3QoYXMuaW50ZWdlcihkZlshaXMubmEoZGYkU2VjdGlvblZpb2xhdGlvbnMpLF0kSW5zcGVjdGlvbl9SZXN1bHRzKSwgbWFpbiA9ICJJbnNwZWN0aW9uX1Jlc3VsdHMgd2l0aG91dCBTZWN0aW9uVmlvbGF0aW9ucyBvZiBOQSIsIHhsYWIgPSAiSW5zcGVjdGlvbiBSZXN1bHRzIikKYGBgCgpXZSBsb3NlIHRoZSAyLCA2LCBhbmQgbW9zdCBvZiB0aGUgMydzLiBTbyB0aGUgcHJlc2VuY2Ugb2YgU2VjdGlvblZpb2xhdGlvbnMgaW5kaWNhdGVzIHRoZSByZXN1bHQgaXMgcHJvYmFibHkgMSwgNCwgb3IgNSAoUEFTUykuIFNvIEkgZ3Vlc3Mgd2UgY2Fubm90IGV4Y2x1ZGUgdGhlbS4KCkxvb2sgYXQgdGhlIFJpc2tMZXZlbApgYGB7cn0KbGV2ZWxzKGRmJFJpc2tMZXZlbCkKYGBgCgpOb3RlIDQgbGV2ZWxzIHdpdGggb25lIGJlaW5nICJVbmNlcnRhaW4iLiBIb3cgbWFueSBhcmUgTkEKYGBge3J9CnN1bShpcy5uYShkZiRSaXNrTGV2ZWwpKQpgYGAKCk5vIFJpc2tMZXZlbCBhcmUgTkEuIEhvdyBtYW55IGFyZSAiVW5jZXJ0YWluIiB0aG91Z2gKYGBge3J9Cmxlbmd0aCh3aGljaChkZiRSaXNrTGV2ZWw9PSJVbmNlcnRhaW4iKSkKYGBgCgpPbmx5IDIxIGFyZSAiVW5jZXJ0YWluIgoKRmluYWxseSwgbGV0IHVzIGxvb2sgYXQgR2VvX2xvYwpgYGB7cn0KbGVuZ3RoKGxldmVscyhkZiRHZW9fTG9jKSkKYGBgCgpObyEgT3ZlciAxNmsgbGV2ZWxzIQpJIHRoaW5rIEkgd2lsbCB1c2UgQ2l0eSwgUmVhc29uLCBhbmQgUmlza0xldmVsLCBhbmQgdHJ5IGFsc28gVHlwZSBhbmQgU2VjdGlvblZpb2xhdGlvbnMuCgpJIG5lZWQgdG8gbWFrZSBzdXJlIHRoZSB0YXJnZXQgdmFyaWFibGUgaXMgY2xlYW4KYGBge3J9CmRmJEluc3BlY3Rpb25fUmVzdWx0cyA8LSBmYWN0b3IoZGYkSW5zcGVjdGlvbl9SZXN1bHRzKQpsZXZlbHMoZGYkSW5zcGVjdGlvbl9SZXN1bHRzKQpgYGAKCgojIyBWYWxpZGF0aW9uIGRhdGFzZXQKSSBuZWVkIHRvIGJyZWFrIHRoZSB0cmFpbiBkYXRhc2V0IGludG8gYSB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiBkYXRhc2V0cwpgYGB7cn0KaW5kZXhlcyA9IHNhbXBsZSgxOm5yb3coZGYpLCBzaXplPTAuNzUqbnJvdyhkZikpCnRyYWluaW5nID0gZGZbaW5kZXhlcyxdICAKdGVzdGluZyA9IGRmWy1pbmRleGVzLF0gIApybShpbmRleGVzKQoKYGBgCgojIyBNb2RlbGluZwpPZiBjb3Vyc2UgdGhlcmUgYXJlIG1hbnkgb3B0aW9ucywgYnV0IGFuIGFwcHJvcHJpYXRlIG1vZGVsIG5lZWRzIHRvIGJlIGEgY2xhc3NpZmljYXRpb24gbW9kZWwgdGhhdCBhY2NlcHRzIGNhdGVnb3JpY2FsIGZlYXR1cmVzLiBTbyBJIGNhbm5vdCBzZWxlY3QgZmVhdHVyZXMgdXNpbmcgUHJpbmNpcGFsIENvbXBvbmVudCBBYW5hbHlzaXMgc2luY2UgdGhhdCByZXF1aXJlcyBudW1lcmljYWwgZGF0YS4gVGhlIGNob2ljZSBJIGFtIHNlbGVjdGluZyBpcyBhIE5haXZlIEJheWVzIENsYXNzaWZpY2F0aW9uIG1vZGVsLiAKCkxvYWQgdGhlIFIgbGlicmFyeSB0aGF0IGhhcyBhIE5haXZlIEJheWVzIGZ1bmN0aW9uCmBgYHtyfQpsaWJyYXJ5KGUxMDcxKQpgYGAKCkkgd2lsbCB1c2Ugc2V0LnNlZWQgc28gdGhhdCBlYWNoIG1vZGVsIHJ1biBzdGFydHMgb2ZmICJ0aGUgc2FtZS4iIFRoZXJlZm9yZSB0aGUgb25seSBjaGFuZ2Ugc2hvdWxkIGJlIHRoZSBmZWF0dXJlcyBpbnB1dCB0byB0aGUgbW9kZWwuIEZvciB0aGUgZmlyc3QgTmFpdmUgQmF5ZXMgbW9kZWwsIEkgd2lsbCB1c2UgQ2l0eSArIFJlYXNvbiArIFJpc2tMZXZlbCArIFR5cGUgKyBTZWN0aW9uVmlvbGF0aW9ucyArIHdpbnRlciBhcyB0aGUgZmVhdHVyZXMuCmBgYHtyfQpzZXQuc2VlZCg3KQptb2RlbCA9IG5haXZlQmF5ZXMoSW5zcGVjdGlvbl9SZXN1bHRzIH4gQ2l0eSArIFJlYXNvbiArIFJpc2tMZXZlbCArIFR5cGUgKyBTZWN0aW9uVmlvbGF0aW9ucyArIHdpbnRlciwgZGF0YT10cmFpbmluZykKCmBgYAoKVXNpbmcgdGhlIHRlc3QgZGF0YXNldCwgcHJlZGljdCB1c2luZyB0aGUgbW9kZWwKYGBge3J9ClByZWRpY3QgPC0gcHJlZGljdChtb2RlbCxuZXdkYXRhID0gdGVzdGluZyApCmBgYAoKQ3JlYXRlIGEgY29uZnVzaW9uIG1hdHJpeCB0byBjaGVjayBhY2N1cmFjeQpgYGB7cn0KY20gPC0gdGFibGUoUHJlZGljdCwgdGVzdGluZyRJbnNwZWN0aW9uX1Jlc3VsdHMgKQpzdW0oZGlhZyhjbSkpIC8gc3VtKGNtKSAjIEFjY3VyYWN5CnJtKG1vZGVsLCBQcmVkaWN0LCBjbSkKYGBgCgo3NiUgYWNjdXJhdGUKSSBhbSBub3Qgc3VyZSBpZiB3ZSBwcmVmZXIgYSBoaWdoZXIgc2Vuc2l0aXZpdHkgb3IgaGlnaGVyIHNwZWNpZmljaXR5LCB3aGVyZSBTZW5zaXRpdml0eSBpcyB0aGUgdHJ1ZSBwb3NpdGl2ZSByYXRlIG9yIHByb2JhYmlsaXR5IG9mIGRldGVjdGlvbiBhbmQgU3BlY2lmaWNpdHkgaXMgdGhlIHRydWUgbmVnYXRpdmUgcmF0ZSBvciBwcm9wb3J0aW9uIG9mIGFjdHVhbCBuZWdhdGl2ZXMgdGhhdCBhcmUgY29ycmVjdGx5IGlkZW50aWZpZWQgYXMgc3VjaC4gRnVydGhlcm1vcmUsIEkgYW0gbm90IHN1cmUgaWYgd2UgY2FyZSBtb3JlIGFib3V0IGNlcnRhaW4gcmVzdWx0cyB0aGFuIG90aGVycy4gVGhlcmVmb3JlIEkgd2lsbCBqdXN0IHVzZSBhY2N1cmFjeSBhcyB0aGUgbWVhc3VyZSBvZiB0aGUgbW9kZWwuIAoKTGV0IHVzIHRyeSB0aGUgbW9kZWwgd2l0aG91dCB0aGUgd2ludGVyIGZlYXR1cmUKYGBge3J9CnNldC5zZWVkKDcpCm1vZGVsID0gbmFpdmVCYXllcyhJbnNwZWN0aW9uX1Jlc3VsdHMgfiBDaXR5ICsgUmVhc29uICsgUmlza0xldmVsICsgVHlwZSArIFNlY3Rpb25WaW9sYXRpb25zLCBkYXRhPXRyYWluaW5nKQoKIyBQcmVkaWN0IHVzaW5nIHRoZSBtb2RlbCBhbmQgdGhlIHZhbGlkYXRpb24gZGF0YXNldApQcmVkaWN0IDwtIHByZWRpY3QobW9kZWwsbmV3ZGF0YSA9IHRlc3RpbmcgKQoKI0NvbmZ1c2lvbiBtYXRyaXggdG8gY2hlY2sgYWNjdXJhY3kKY20gPC0gdGFibGUoUHJlZGljdCwgdGVzdGluZyRJbnNwZWN0aW9uX1Jlc3VsdHMgKQpzdW0oZGlhZyhjbSkpIC8gc3VtKGNtKSAjIEFjY3VyYWN5CgpybShtb2RlbCwgUHJlZGljdCwgY20pCmBgYAoKQWxtb3N0IG5vIGRpZmZlcmVuY2UgaW4gYWNjdXJhY3kuIEFzc3VtaW5nIGEgc2ltcGxlciBtb2RlbCBpcyBhIGJldHRlciBtb2RlbCwgSSB3aWxsIG9taXQgd2ludGVyLiBJIGFtIHNrZXB0aWNhbCBvZiBTZWN0aW9uVmlvbGF0aW9ucyBhbmQgd2lsbCB0cnkgdGhlIG1vZGVsIHdpdGhvdXQgaXQuCgpgYGB7cn0Kc2V0LnNlZWQoNykKbW9kZWwgPSBuYWl2ZUJheWVzKEluc3BlY3Rpb25fUmVzdWx0cyB+IENpdHkgKyBSZWFzb24gKyBSaXNrTGV2ZWwgKyBUeXBlLCBkYXRhPXRyYWluaW5nKQoKIyBQcmVkaWN0IHVzaW5nIHRoZSBtb2RlbCBhbmQgdGhlIHZhbGlkYXRpb24gZGF0YXNldApQcmVkaWN0IDwtIHByZWRpY3QobW9kZWwsbmV3ZGF0YSA9IHRlc3RpbmcgKQoKI0NvbmZ1c2lvbiBtYXRyaXggdG8gY2hlY2sgYWNjdXJhY3kKY20gPC0gdGFibGUoUHJlZGljdCwgdGVzdGluZyRJbnNwZWN0aW9uX1Jlc3VsdHMgKQoKKHN1bShjbSkgLSBzdW0oZGlhZyhjbSkpKSAvIHN1bShjbSkgI0FjY3VyYWN5CnJtKG1vZGVsLCBQcmVkaWN0LCBjbSkKYGBgCgpJIHdhcyB3cm9uZyEhIFNlY3Rpb25WaW9sYXRpb25zIGlzIGltcG9ydGFudCEgSSB3aWxsIHB1dCBTZWN0aW9uVmlvbGF0aW9ucyBiYWNrIGluIGFzIGEgZmVhdHVyZSBidXQgdHJ5IHRvIG9taXQgQ2l0eS4KYGBge3J9CnNldC5zZWVkKDcpCm1vZGVsID0gbmFpdmVCYXllcyhJbnNwZWN0aW9uX1Jlc3VsdHMgfiBSZWFzb24gKyBSaXNrTGV2ZWwgKyBUeXBlICsgU2VjdGlvblZpb2xhdGlvbnMsIGRhdGE9dHJhaW5pbmcpCgojIFByZWRpY3QgdXNpbmcgdGhlIG1vZGVsIGFuZCB0aGUgdmFsaWRhdGlvbiBkYXRhc2V0ClByZWRpY3QgPC0gcHJlZGljdChtb2RlbCxuZXdkYXRhID0gdGVzdGluZyApCgojQ29uZnVzaW9uIG1hdHJpeCB0byBjaGVjayBhY2N1cmFjeQpjbSA8LSB0YWJsZShQcmVkaWN0LCB0ZXN0aW5nJEluc3BlY3Rpb25fUmVzdWx0cyApCnN1bShkaWFnKGNtKSkgLyBzdW0oY20pICMgQWNjdXJhY3kKCnJtKG1vZGVsLCBQcmVkaWN0LCBjbSkKYGBgCgpJdCBhcHBlYXJzIENpdHkgaGFzIG5vIGltcGFjdCBvbiB0aGUgbW9kZWwuIE5vdyBJIHdpbGwgdHJ5IHRoZSBtb2RlbCB3aXRob3V0IFR5cGUKYGBge3J9CnNldC5zZWVkKDcpCm1vZGVsID0gbmFpdmVCYXllcyhJbnNwZWN0aW9uX1Jlc3VsdHMgfiBSZWFzb24gKyBSaXNrTGV2ZWwgKyBTZWN0aW9uVmlvbGF0aW9ucywgZGF0YT10cmFpbmluZykKCiMgUHJlZGljdCB1c2luZyB0aGUgbW9kZWwgYW5kIHRoZSB2YWxpZGF0aW9uIGRhdGFzZXQKUHJlZGljdCA8LSBwcmVkaWN0KG1vZGVsLG5ld2RhdGEgPSB0ZXN0aW5nICkKCiNDb25mdXNpb24gbWF0cml4IHRvIGNoZWNrIGFjY3VyYWN5CmNtIDwtIHRhYmxlKFByZWRpY3QsIHRlc3RpbmckSW5zcGVjdGlvbl9SZXN1bHRzICkKc3VtKGRpYWcoY20pKSAvIHN1bShjbSkgIyBBY2N1cmFjeQoKcm0obW9kZWwsIFByZWRpY3QsIGNtKQpgYGAKCk9ubHkgbWFyZ2luYWwsIGJ1dCB0aGlzIG1vZGVsIGlzIHRoZSBiZXN0IHNvIGZhci4gTWF5YmUgSSBuZWVkIHRvIGRvIHNvbWV0aGluZyB3aXRoIHRoZSBUeXBlIHdpdGggTkEuIFRyeSB0aGUgbW9kZWwgd2l0aCBUeXBlIHdpdGhvdXQgTkEncwpgYGB7cn0Kc2V0LnNlZWQoNykKbW9kZWwgPSB0cmFpbmluZyAlPiUKICBmaWx0ZXIoIWlzLm5hKFR5cGUpKSAlPiUKICBuYWl2ZUJheWVzKEluc3BlY3Rpb25fUmVzdWx0cyB+IFJlYXNvbiArIFJpc2tMZXZlbCArIFNlY3Rpb25WaW9sYXRpb25zICsgVHlwZSwgZGF0YT0uKQogIAojIFByZWRpY3QgdXNpbmcgdGhlIG1vZGVsIGFuZCB0aGUgdmFsaWRhdGlvbiBkYXRhc2V0ClByZWRpY3QgPC0gcHJlZGljdChtb2RlbCxuZXdkYXRhID0gdGVzdGluZyApCgojQ29uZnVzaW9uIG1hdHJpeCB0byBjaGVjayBhY2N1cmFjeQpjbSA8LSB0YWJsZShQcmVkaWN0LCB0ZXN0aW5nJEluc3BlY3Rpb25fUmVzdWx0cyApCnN1bShkaWFnKGNtKSkgLyBzdW0oY20pICMgQWNjdXJhY3kKCnJtKG1vZGVsLCBQcmVkaWN0LCBjbSkKYGBgCgpBcyBJIHN1c3BlY3RlZCBlYXJsaWVyLCB3ZSBsb3NlIHNvbWV0aGluZyB3aGVuIFR5cGUgb2YgTkEgaXMgb21pdHRlZC4gSSBndWVzcyB0aGUgTkEncyBhcmUgcHJlZGljdGl2ZSwgYnV0IFR5cGUgc3RpbGwgbm90IGhlbHBmdWwuIEZyb20gd2hhdCBJIHRyaWVkLCB0aGUgYmVzdCBOYWl2ZSBCYXllcyBtb2RlbCBnZXRzIGFyb3VuZCA3NyUgYWNjdXJhY3kgd2l0aCB0aGUgbW9kZWwgPSBuYWl2ZUJheWVzKEluc3BlY3Rpb25fUmVzdWx0cyB+IFJlYXNvbiArIFJpc2tMZXZlbCArIFNlY3Rpb25WaW9sYXRpb25zLCBkYXRhPXRyYWluaW5nKS4KCkp1c3QgYW4gYXNpZGUsIHRoaXMgImNoYWxsZW5nZSIgd2FzIG9uIE1hY2hpbmVIYWNrIGJ1dCBoYWQgZW5kZWQgYmVmb3JlIEkgZm91bmQgaXQuIE9mIGNvdXJzZSBpdCBkb2VzIG5vdCByZWFsbHkgbWF0dGVyIHNpbmNlIG15IHJlc3VsdHMgYXJlIG5vdCBhcyBnb29kIGFzIHRoZSB0b3AgbW9kZWxzIHN1Ym1pdHRlZCB0byB0aGUgY2hhbGxlbmdlLgo=