library(lubridate)
library(stringr)
library(ggplot2)
library(dplyr)
library(sqldf)
library(aod)

Read in data

#gsub('\\\\','/',readClipboard())
setwd(readClipboard())
df <-read.csv(gzfile('data_challenge_price.csv.gz','rt')
              , header=T
              , stringsAsFactors = F)
str(df)
'data.frame':   329097 obs. of  9 variables:
 $ business_id   : chr  "cdd69a9e27026cd2b0667d18b765c918" "cdd69a9e27026cd2b0667d18b765c918" "cdd69a9e27026cd2b0667d18b765c918" "cdd69a9e27026cd2b0667d18b765c918" ...
 $ cob_id        : int  5010 5010 5010 5010 5010 5010 5003 5003 5003 5003 ...
 $ state         : chr  "MO" "MO" "MO" "MO" ...
 $ bundle_name   : chr  "proPlus" "basicTria" "basic" "proPlusTria" ...
 $ status_name   : chr  "Quote" "Quote" "Quote" "Quote" ...
 $ yearly_premium: num  434 350 350 435 411 413 350 350 350 350 ...
 $ start_date    : chr  "2018-03-02" "2018-03-02" "2018-03-02" "2018-03-02" ...
 $ creation_time : chr  "2018-01-01 17:35:14.949" "2018-01-01 17:35:14.994" "2018-01-01 17:35:15.025" "2018-01-01 17:35:15.068" ...
 $ orig_premium  : chr  "" "" "" "" ...

Quick glance

c
function (...)  .Primitive("c")
nrow(unique(df[1]))
[1] 30650
summary(df$yearly_premium)
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
     350      396      750    25873      944 56822079 

Buz ID: 30650 Cob_ID: 5 State: 46 Bundle: 6

Not sure why orig_premium is double-quoted, change type

df$orig_premium1 <- as.numeric(str_sub(df$orig_premium, 2, -2))
##Checking
y <- df[is.na(df$orig_premium1)==F, c("yearly_premium", "orig_premium", "orig_premium1")]
y <- unique(y[order(y$orig_premium1),])
head(y); tail(y)

If orig_premium represents the premium before rounding up to the minimum, some prices seem to be ridiculously too low for a sound policy. We wouldn’t use it to construct price elasticity curves.

Highly skewed yearly premium

ggplot(data = df[df$yearly_premium > 10000, ], aes(x = yearly_premium)) + 
  geom_histogram(bins= 500, color = 'black') +
  ggtitle("Yearly Premium > $10000")

Premium range by state, cob

premium_range <-
  df %>% group_by(state, cob_id) %>%
         summarise(minPrem = min(yearly_premium)
                   ,maxPrem = max(yearly_premium))
##Fluctuation of state-cob specific premium range
premium_range %>% group_by(cob_id) %>%
                  summarise(min_minPrem = min(minPrem)
                            ,max_minPrem = max(minPrem)
                            ,min_maxPrem = min(maxPrem)
                            ,max_maxPrem = max(maxPrem))

Minimum premium set to $350, fluctuates ~$100 - $200 across states, depending on cob.

Select premium

#g <- function(x) c(n = length(x), quantile(x, c(0, 0.25, 0.5, 0.75, 0.9, 0.99, 1)))
#tapply(df$yearly_premium, df$status_name, g)
df %>% group_by(status_name) %>%
       summarise(n = n()
                  ,min = min(yearly_premium)
                  ,q25 = quantile(yearly_premium, 0.25)
                 ,q50 = quantile(yearly_premium, 0.5)
                 ,q75 = quantile(yearly_premium, 0.75)
                 ,q90 = quantile(yearly_premium, 0.9)
                 ,q99 = quantile(yearly_premium, 0.99)
                 ,max = max(yearly_premium)
    )
### Pick latest quote per status
#   If any quote is purchased (status of Active or Canceled), use the associated premium
# Else, if any quote is selected (meaning the user has selected that package and clicked through to the payment screen), use the associated premium from the latest quote
# Else, use the associated premium from the latest quote for the Pro bundle
### Assume one staus per business id regardless of state
last_quote <- sqldf("select *, case when status_name in ('Active', 'Canceled') then 1
                                   when status_name = 'Selected' then 2
                                   when status_name = 'Quote' and bundle_name = 'pro' then 3
                                   else 4    
                                   end as s
                         From df
                         group by business_id, s
                         having creation_time = max(creation_time)
                         order by business_id, s")
### Check if business with only Quote status always have pro bundle
Quote <- sqldf("select * From last_quote where business_id not in
                (select business_id from last_quote where s in (1,2))")
DFO <- sqldf("select business_id from Quote where status_name = 'Quote' and bundle_name != 'pro'
              except
              select business_id from Quote where status_name = 'Quote' and bundle_name = 'pro'")
### -> 0 count, Yes!
status <- sqldf("select *
                ,case when s =1 then 1 else 0 end as Converted
                From last_quote
                  group by business_id
                  having s = min(s)") # --> get back 30650 unique business id!
table(status[status$s==3, ]$bundle_name) #--> if status = Quote, bundle = pro!

 pro 
9763 
table(status$status_name)

  Active Canceled    Quote Selected 
    7280     1355     9763    12252 

Coversion rate

### Overall
round(prop.table(table(status$Converted))*100, 2)

    0     1 
71.83 28.17 
#tapply(status$Converted, status$cob_id, function(x) prop.table(table(x)))

28% converted overall

By business class

### Tabulate
status %>% group_by(cob_id) %>% summarise('Conversion%' = round(mean(Converted)*100, 2)) %>%
                                arrange(desc(.$'Conversion%'))
### Statistical test
status$cob_id <- factor(status$cob_id)
logit_cob <- glm(Converted ~ cob_id, data = status, family = "binomial")
#summary(logit_cob)
wald.test(b = coef(logit_cob), Sigma = vcov(logit_cob), Terms = 2:5)
Wald test:
----------

Chi-squared test:
X2 = 107.7, df = 4, P(> X2) = 0.0

Conversion% significantly different between business classes

By State

### Tabulate
Conv_State <- status %>% 
              group_by(state) %>% 
              summarise(conversion_rate = round(mean(Converted)*100, 2)) %>%
              arrange(desc(.$conversion_rate))
Conv_State 
### Visualize
ggplot(data = Conv_State, aes(x = reorder(state, -conversion_rate), y = conversion_rate))+
  geom_point() +
  geom_line(group=1) + 
  theme(axis.text.x = element_text(angle = 40)) +
  labs(x = 'State') + 
  ggtitle("Conversion Rate per State")

### Statistical test
logit_state <- glm(Converted ~ state, data = status, family = "binomial")
#summary(logit_state)
wald.test(b = coef(logit_state), Sigma = vcov(logit_state), Terms = 2:46)
Wald test:
----------

Chi-squared test:
X2 = 325.4, df = 45, P(> X2) = 0.0

Conversion% significantly different between states, ranging from 13% to 48%

By pricing bin

### Distribution of premium per converted status
g <- function(x) c(n = length(x), quantile(x, c(0, 0.25, 0.5, 0.75, 0.9, 0.99, 1)))
tapply(status$yearly_premium, status$Converted, g)
$`0`
         n         0%        25%        50%        75%        90%        99%       100% 
   22015.0      350.0      447.0      750.0     1166.0     2238.2    10048.2 53632126.0 

$`1`
       n       0%      25%      50%      75%      90%      99%     100% 
 8635.00   350.00   361.00   566.00   750.00  1143.00  3174.98 13145.00 
### Highly skewed distribution, zoom in to just premium < $1500 and see how conversion varies
# ggplot(status[status$yearly_premium <=1500, ],
#        aes(x=bin10, color =as.factor(Converted))) +
#   geom_histogram(fill="white", binwidth = 10, stat = 'count') +
#   ggtitle('Conversion vs Premium')
### Cut into $10 increments, 116 buckets
cutoff <- seq(350, 1500, 10)
status$bin10 <- cut(status$yearly_premium, breaks = cutoff, right = F, labels = F)
status[is.na(status$bin10), ]$bin10 <- 116
x <- status %>% group_by(bin10) %>%
                summarise(min = min(yearly_premium)
                          ,max = max(yearly_premium))
#tail(x)
Conv_bin10 <- status %>%
              group_by(bin10) %>% 
              summarise(conversion_rate = round(mean(Converted)*100, 2))
Conv_bin10 <- sqldf("select x.*, b.conversion_rate 
                    from x left join Conv_bin10 b
                    on x.bin10 = b.bin10")
Conv_bin10
ggplot(data=Conv_bin10,aes(x=min, y=conversion_rate)) +
  geom_point() +
  geom_line(group=1) +
  scale_x_continuous(breaks=seq(350,1500,50)) +
  labs(x = 'Premium in $10 increment, lower bound'
       ,y = 'Conversion Rate %') +
  ggtitle('Conversion vs Premium')

This price elasticity curve makes sense as we would expect more people to buy an insurance policy at a lower price.

Based on the line graph, re-bin premium into smaller number of buckets

mean(status[status$yearly_premium >=350 & status$yearly_premium <360, ]$Converted) #34.0%
[1] 0.3401469
mean(status[status$yearly_premium >=360 & status$yearly_premium <440, ]$Converted) #40.7%
[1] 0.4070143
mean(status[status$yearly_premium >=440 & status$yearly_premium <600, ]$Converted) #35.4%
[1] 0.3538642
mean(status[status$yearly_premium >=600 & status$yearly_premium <720, ]$Converted) #32.7%
[1] 0.3265661
mean(status[status$yearly_premium >=720 & status$yearly_premium <1000, ]$Converted) #28.4
[1] 0.2839477
mean(status[status$yearly_premium >=1000 & status$yearly_premium <1360, ]$Converted) #20.4%
[1] 0.2041667
mean(status[status$yearly_premium >=1360, ]$Converted) #11.9%
[1] 0.1189343
# ggplot(status[status$yearly_premium <=1500, ],
#        aes(x=yearly_premium, color =as.factor(Converted))) +
#   geom_histogram(fill="white", binwidth = 10) +
#   ggtitle('Conversion vs Premium')
  
status$bin <- ifelse(status$yearly_premium >=350 & status$yearly_premium <360, '1-350-359',
                     ifelse(status$yearly_premium >=360 & status$yearly_premium <440, '2-360-439',
                      ifelse(status$yearly_premium >=440 & status$yearly_premium <600, '3-440-599',
                        ifelse(status$yearly_premium >=600 & status$yearly_premium <720, '4-600-719',
                          ifelse(status$yearly_premium >=720 & status$yearly_premium <1000, '5-720-999',
                          ifelse(status$yearly_premium >=1000 & status$yearly_premium <1360, '6-1000-1359',
                            ifelse(status$yearly_premium >=1360, '7-1360+', 'NA')))))))
status$bin <-factor(status$bin)
###Check binning
tapply(status$yearly_premium, status$bin, range)
$`1-350-359`
[1] 350 359

$`2-360-439`
[1] 360 439

$`3-440-599`
[1] 440 599

$`4-600-719`
[1] 600 719

$`5-720-999`
[1] 720 999

$`6-1000-1359`
[1] 1000 1359

$`7-1360+`
[1]     1360 53632126
### Tabulate
Conv_bin <- status %>% 
              group_by(bin) %>% 
              summarise(conversion_rate= round(mean(Converted)*100, 2)
                        ,n = n()) %>%
              arrange(desc(.$conversion_rate))
Conv_bin 
### Visualize
ggplot(data = Conv_bin, aes(x = bin,
            #x = reorder(bin2, conversion_rate),
           y = conversion_rate))+
  geom_point() +
  geom_line(group=1) + 
  labs(x = 'Pricing Bin $'
       ,y = 'Conversion Rate %') + 
  ggtitle("Conversion Rate per Pricing")

### Statistical test
logit_bin <- glm(Converted ~ bin, data = status, family = "binomial")
#summary(logit_bin)
wald.test(b = coef(logit_bin), Sigma = vcov(logit_bin), Terms = 2:7)
Wald test:
----------

Chi-squared test:
X2 = 1071.2, df = 6, P(> X2) = 0.0

Conversion% generally drops with increasing premium, and is significantly different between price buckets.

Also, highest conversion rate happens at $360-439. Does it suggset raising the minimum premium from $350 or is it just a minimum price per state requirement for certain business classes?

Does binning effect change with business class?

Conv_bin_cob <- status %>% 
              group_by(cob_id, bin) %>% 
              summarise(conversion_rate= round(mean(Converted)*100, 2)
                        ,n = n()) #%>%
              #arrange(desc(.$conversion_rate))
Conv_bin_cob
### Visualize
ggplot(data = Conv_bin_cob, aes(x = bin,
                                #x = reorder(bin2, conversion_rate),
                                y = conversion_rate)) +
  geom_point() +
  geom_line(aes(group=cob_id, color=cob_id)) + 
  labs(x = 'Pricing Bin $') + 
  ggtitle("Conversion Rate per Pricing per COB")

Conversion trending generally looks similar across all business classes except for 5003 that shows the highest conversion rate of 26.3% at $350-$359 range. For class 100001, premium starts at $720-$999 range. It’s also the same range where the highest conversion hits.

Does binning effect change with state?

Conv_bin_state <- status %>% 
              group_by(state, bin) %>% 
              summarise(conversion_rate= round(mean(Converted)*100, 2)
                        ,n = n()) #%>%
              #arrange(desc(.$conversion_rate))
Conv_bin_state 
### Visualize
### 7 states at a time
st <- sort(unique(Conv_bin_state$state))
for (k in 0:6){
  i=1+k*7
  if (k==6){
    j=46
  }
  else{
      j=7+k*7
  }
p <- ggplot(data = Conv_bin_state[Conv_bin_state$state %in% st[i:j], ]
            , aes(x = bin,
                  #x = reorder(bin, conversion_rate),
                  y = conversion_rate)) +
    geom_point() +
    geom_line(aes(group=state, color=state)) + 
    labs(x = 'Pricing Bin $'
         ,y = 'Conversion Rate %') + 
    ggtitle(paste("Conversion Rate per Pricing per States ", st[i], " - ", st[j]))
print(p)
}

Conversion trending does vary by state. Not all the states start and peak at the same price range. For example, CA, ME, ND, OK, VT, WY start from $440 - $599 range. OK peaks at $600 - 719 range. Some states have very small sample size (e.g. only 15 customers in DC) and absolute conversion of 100% is based on only 1 case.

Setting minimum premium should take factors like business class and state into consideration, because different business classes may have different risk levels to protect and different states may have different concentrations of business and regulations. Also, when looking at favorable pricing in terms of conversion, one needs to also look at the size of converted pool – are we talking about 9 cases out of 10 or 900 cases out of 1000 being converted? Basing minimum premium on sparse data can lead to unreliable results.

LS0tDQp0aXRsZTogIkRhdGEgQ2hhbGxlbmdlIC0gUHJpY2UgRWxhc3RpY2l0eSINCnN1YnRpdGxlOiB8DQogICAgfCBDb252ZXJzaW9uIEFuYWx5c2lzDQogICAgfCBPYmplY3RpdmU6ICAgQnVpbGQgcXVvdGUtdG8tcHVyY2hhc2UgY29udmVyc2lvbiBjdXJ2ZSBhcyBhIGZ1bmN0aW9uIG9mIHByaWNlDQojIGRhdGU6ICJgciBmb3JtYXQoU3lzLkRhdGUoKSwgJyVZLiVtLiVkJylgIg0KZGF0ZTogImByIGZvcm1hdChhcy5EYXRlKCcyMDE4LjEwLjE5JywgZm9ybWF0PSclWS4lbS4lZCcpLCAnJVkuJW0uJWQnKWAiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UpDQpgYGANCg0KYGBge3IgTGlicmFyeX0NCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeShzdHJpbmdyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoc3FsZGYpDQpsaWJyYXJ5KGFvZCkNCmBgYA0KDQojIyNSZWFkIGluIGRhdGENCmBgYHtyIFJlYWQgZGF0YX0NCiNnc3ViKCdcXFxcJywnLycscmVhZENsaXBib2FyZCgpKQ0Kc2V0d2QocmVhZENsaXBib2FyZCgpKQ0KZGYgPC1yZWFkLmNzdihnemZpbGUoJ2RhdGFfY2hhbGxlbmdlX3ByaWNlLmNzdi5neicsJ3J0JykNCiAgICAgICAgICAgICAgLCBoZWFkZXI9VA0KICAgICAgICAgICAgICAsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQ0Kc3RyKGRmKQ0KYGBgDQoNCiMjI1F1aWNrIGdsYW5jZQ0KYGBge3IgR2xhbmNlfQ0KYw0KbnJvdyh1bmlxdWUoZGZbMV0pKQ0Kc3VtbWFyeShkZiR5ZWFybHlfcHJlbWl1bSkNCmBgYA0KQnV6IElEOiAzMDY1MA0KQ29iX0lEOiA1DQpTdGF0ZTogNDYNCkJ1bmRsZTogNg0KDQojIyMgTm90IHN1cmUgd2h5IG9yaWdfcHJlbWl1bSBpcyBkb3VibGUtcXVvdGVkLCBjaGFuZ2UgdHlwZQ0KYGBge3J9DQpkZiRvcmlnX3ByZW1pdW0xIDwtIGFzLm51bWVyaWMoc3RyX3N1YihkZiRvcmlnX3ByZW1pdW0sIDIsIC0yKSkNCg0KIyNDaGVja2luZw0KeSA8LSBkZltpcy5uYShkZiRvcmlnX3ByZW1pdW0xKT09RiwgYygieWVhcmx5X3ByZW1pdW0iLCAib3JpZ19wcmVtaXVtIiwgIm9yaWdfcHJlbWl1bTEiKV0NCg0KeSA8LSB1bmlxdWUoeVtvcmRlcih5JG9yaWdfcHJlbWl1bTEpLF0pDQpoZWFkKHkpOyB0YWlsKHkpDQpgYGANCklmIG9yaWdfcHJlbWl1bSByZXByZXNlbnRzIHRoZSBwcmVtaXVtIGJlZm9yZSByb3VuZGluZyB1cCB0byB0aGUgbWluaW11bSwgc29tZSBwcmljZXMgc2VlbSB0byBiZSByaWRpY3Vsb3VzbHkgdG9vIGxvdyBmb3IgYSBzb3VuZCBwb2xpY3kuIFdlIHdvdWxkbid0IHVzZSBpdCB0byBjb25zdHJ1Y3QgcHJpY2UgZWxhc3RpY2l0eSBjdXJ2ZXMuDQoNCg0KIyMjSGlnaGx5IHNrZXdlZCB5ZWFybHkgcHJlbWl1bQ0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGRmW2RmJHllYXJseV9wcmVtaXVtID4gMTAwMDAsIF0sIGFlcyh4ID0geWVhcmx5X3ByZW1pdW0pKSArIA0KICBnZW9tX2hpc3RvZ3JhbShiaW5zPSA1MDAsIGNvbG9yID0gJ2JsYWNrJykgKw0KICBnZ3RpdGxlKCJZZWFybHkgUHJlbWl1bSA+ICQxMDAwMCIpDQpgYGANCg0KIyMjUHJlbWl1bSByYW5nZSBieSBzdGF0ZSwgY29iDQpgYGB7cn0NCnByZW1pdW1fcmFuZ2UgPC0NCiAgZGYgJT4lIGdyb3VwX2J5KHN0YXRlLCBjb2JfaWQpICU+JQ0KICAgICAgICAgc3VtbWFyaXNlKG1pblByZW0gPSBtaW4oeWVhcmx5X3ByZW1pdW0pDQogICAgICAgICAgICAgICAgICAgLG1heFByZW0gPSBtYXgoeWVhcmx5X3ByZW1pdW0pKQ0KDQojI0ZsdWN0dWF0aW9uIG9mIHN0YXRlLWNvYiBzcGVjaWZpYyBwcmVtaXVtIHJhbmdlDQpwcmVtaXVtX3JhbmdlICU+JSBncm91cF9ieShjb2JfaWQpICU+JQ0KICAgICAgICAgICAgICAgICAgc3VtbWFyaXNlKG1pbl9taW5QcmVtID0gbWluKG1pblByZW0pDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgLG1heF9taW5QcmVtID0gbWF4KG1pblByZW0pDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgLG1pbl9tYXhQcmVtID0gbWluKG1heFByZW0pDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgLG1heF9tYXhQcmVtID0gbWF4KG1heFByZW0pKQ0KDQpgYGANCk1pbmltdW0gcHJlbWl1bSBzZXQgdG8gJDM1MCwgZmx1Y3R1YXRlcyB+JDEwMCAtICQyMDAgYWNyb3NzIHN0YXRlcywgZGVwZW5kaW5nIG9uIGNvYi4NCg0KIyMjIFNlbGVjdCBwcmVtaXVtDQpgYGB7cn0NCiNnIDwtIGZ1bmN0aW9uKHgpIGMobiA9IGxlbmd0aCh4KSwgcXVhbnRpbGUoeCwgYygwLCAwLjI1LCAwLjUsIDAuNzUsIDAuOSwgMC45OSwgMSkpKQ0KI3RhcHBseShkZiR5ZWFybHlfcHJlbWl1bSwgZGYkc3RhdHVzX25hbWUsIGcpDQpkZiAlPiUgZ3JvdXBfYnkoc3RhdHVzX25hbWUpICU+JQ0KICAgICAgIHN1bW1hcmlzZShuID0gbigpDQogICAgICAgICAgICAgICAgICAsbWluID0gbWluKHllYXJseV9wcmVtaXVtKQ0KICAgICAgICAgICAgICAgICAgLHEyNSA9IHF1YW50aWxlKHllYXJseV9wcmVtaXVtLCAwLjI1KQ0KICAgICAgICAgICAgICAgICAscTUwID0gcXVhbnRpbGUoeWVhcmx5X3ByZW1pdW0sIDAuNSkNCiAgICAgICAgICAgICAgICAgLHE3NSA9IHF1YW50aWxlKHllYXJseV9wcmVtaXVtLCAwLjc1KQ0KICAgICAgICAgICAgICAgICAscTkwID0gcXVhbnRpbGUoeWVhcmx5X3ByZW1pdW0sIDAuOSkNCiAgICAgICAgICAgICAgICAgLHE5OSA9IHF1YW50aWxlKHllYXJseV9wcmVtaXVtLCAwLjk5KQ0KICAgICAgICAgICAgICAgICAsbWF4ID0gbWF4KHllYXJseV9wcmVtaXVtKQ0KICAgICkNCg0KDQojIyMgUGljayBsYXRlc3QgcXVvdGUgcGVyIHN0YXR1cw0KDQojICAgSWYgYW55IHF1b3RlIGlzIHB1cmNoYXNlZCAoc3RhdHVzIG9mIEFjdGl2ZSBvciBDYW5jZWxlZCksIHVzZSB0aGUgYXNzb2NpYXRlZCBwcmVtaXVtDQojIEVsc2UsIGlmIGFueSBxdW90ZSBpcyBzZWxlY3RlZCAobWVhbmluZyB0aGUgdXNlciBoYXMgc2VsZWN0ZWQgdGhhdCBwYWNrYWdlIGFuZCBjbGlja2VkIHRocm91Z2ggdG8gdGhlIHBheW1lbnQgc2NyZWVuKSwgdXNlIHRoZSBhc3NvY2lhdGVkIHByZW1pdW0gZnJvbSB0aGUgbGF0ZXN0IHF1b3RlDQojIEVsc2UsIHVzZSB0aGUgYXNzb2NpYXRlZCBwcmVtaXVtIGZyb20gdGhlIGxhdGVzdCBxdW90ZSBmb3IgdGhlIFBybyBidW5kbGUNCg0KIyMjIEFzc3VtZSBvbmUgc3RhdXMgcGVyIGJ1c2luZXNzIGlkIHJlZ2FyZGxlc3Mgb2Ygc3RhdGUNCmxhc3RfcXVvdGUgPC0gc3FsZGYoInNlbGVjdCAqLCBjYXNlIHdoZW4gc3RhdHVzX25hbWUgaW4gKCdBY3RpdmUnLCAnQ2FuY2VsZWQnKSB0aGVuIDENCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2hlbiBzdGF0dXNfbmFtZSA9ICdTZWxlY3RlZCcgdGhlbiAyDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdoZW4gc3RhdHVzX25hbWUgPSAnUXVvdGUnIGFuZCBidW5kbGVfbmFtZSA9ICdwcm8nIHRoZW4gMw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbHNlIDQgICAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVuZCBhcyBzDQogICAgICAgICAgICAgICAgICAgICAgICAgRnJvbSBkZg0KICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwIGJ5IGJ1c2luZXNzX2lkLCBzDQogICAgICAgICAgICAgICAgICAgICAgICAgaGF2aW5nIGNyZWF0aW9uX3RpbWUgPSBtYXgoY3JlYXRpb25fdGltZSkNCiAgICAgICAgICAgICAgICAgICAgICAgICBvcmRlciBieSBidXNpbmVzc19pZCwgcyIpDQoNCiMjIyBDaGVjayBpZiBidXNpbmVzcyB3aXRoIG9ubHkgUXVvdGUgc3RhdHVzIGFsd2F5cyBoYXZlIHBybyBidW5kbGUNClF1b3RlIDwtIHNxbGRmKCJzZWxlY3QgKiBGcm9tIGxhc3RfcXVvdGUgd2hlcmUgYnVzaW5lc3NfaWQgbm90IGluDQogICAgICAgICAgICAgICAgKHNlbGVjdCBidXNpbmVzc19pZCBmcm9tIGxhc3RfcXVvdGUgd2hlcmUgcyBpbiAoMSwyKSkiKQ0KREZPIDwtIHNxbGRmKCJzZWxlY3QgYnVzaW5lc3NfaWQgZnJvbSBRdW90ZSB3aGVyZSBzdGF0dXNfbmFtZSA9ICdRdW90ZScgYW5kIGJ1bmRsZV9uYW1lICE9ICdwcm8nDQogICAgICAgICAgICAgIGV4Y2VwdA0KICAgICAgICAgICAgICBzZWxlY3QgYnVzaW5lc3NfaWQgZnJvbSBRdW90ZSB3aGVyZSBzdGF0dXNfbmFtZSA9ICdRdW90ZScgYW5kIGJ1bmRsZV9uYW1lID0gJ3BybyciKQ0KIyMjIC0+IDAgY291bnQsIFllcyENCg0KDQpzdGF0dXMgPC0gc3FsZGYoInNlbGVjdCAqDQogICAgICAgICAgICAgICAgLGNhc2Ugd2hlbiBzID0xIHRoZW4gMSBlbHNlIDAgZW5kIGFzIENvbnZlcnRlZA0KICAgICAgICAgICAgICAgIEZyb20gbGFzdF9xdW90ZQ0KICAgICAgICAgICAgICAgICAgZ3JvdXAgYnkgYnVzaW5lc3NfaWQNCiAgICAgICAgICAgICAgICAgIGhhdmluZyBzID0gbWluKHMpIikgIyAtLT4gZ2V0IGJhY2sgMzA2NTAgdW5pcXVlIGJ1c2luZXNzIGlkIQ0KdGFibGUoc3RhdHVzW3N0YXR1cyRzPT0zLCBdJGJ1bmRsZV9uYW1lKSAjLS0+IGlmIHN0YXR1cyA9IFF1b3RlLCBidW5kbGUgPSBwcm8hDQoNCnRhYmxlKHN0YXR1cyRzdGF0dXNfbmFtZSkNCmBgYA0KDQoNCiMjI0NvdmVyc2lvbiByYXRlDQpgYGB7cn0NCiMjIyBPdmVyYWxsDQpyb3VuZChwcm9wLnRhYmxlKHRhYmxlKHN0YXR1cyRDb252ZXJ0ZWQpKSoxMDAsIDIpDQojdGFwcGx5KHN0YXR1cyRDb252ZXJ0ZWQsIHN0YXR1cyRjb2JfaWQsIGZ1bmN0aW9uKHgpIHByb3AudGFibGUodGFibGUoeCkpKQ0KYGBgDQoyOCUgY29udmVydGVkIG92ZXJhbGwNCg0KIyMjIEJ5IGJ1c2luZXNzIGNsYXNzDQpgYGB7cn0NCg0KIyMjIFRhYnVsYXRlDQpzdGF0dXMgJT4lIGdyb3VwX2J5KGNvYl9pZCkgJT4lIHN1bW1hcmlzZSgnQ29udmVyc2lvbiUnID0gcm91bmQobWVhbihDb252ZXJ0ZWQpKjEwMCwgMikpICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcnJhbmdlKGRlc2MoLiQnQ29udmVyc2lvbiUnKSkNCg0KIyMjIFN0YXRpc3RpY2FsIHRlc3QNCnN0YXR1cyRjb2JfaWQgPC0gZmFjdG9yKHN0YXR1cyRjb2JfaWQpDQpsb2dpdF9jb2IgPC0gZ2xtKENvbnZlcnRlZCB+IGNvYl9pZCwgZGF0YSA9IHN0YXR1cywgZmFtaWx5ID0gImJpbm9taWFsIikNCiNzdW1tYXJ5KGxvZ2l0X2NvYikNCg0Kd2FsZC50ZXN0KGIgPSBjb2VmKGxvZ2l0X2NvYiksIFNpZ21hID0gdmNvdihsb2dpdF9jb2IpLCBUZXJtcyA9IDI6NSkNCmBgYA0KQ29udmVyc2lvbiUgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQgYmV0d2VlbiBidXNpbmVzcyBjbGFzc2VzDQoNCiMjIyBCeSBTdGF0ZQ0KYGBge3IsIGZpZy53aWR0aD0xMH0NCiMjIyBUYWJ1bGF0ZQ0KQ29udl9TdGF0ZSA8LSBzdGF0dXMgJT4lIA0KICAgICAgICAgICAgICBncm91cF9ieShzdGF0ZSkgJT4lIA0KICAgICAgICAgICAgICBzdW1tYXJpc2UoY29udmVyc2lvbl9yYXRlID0gcm91bmQobWVhbihDb252ZXJ0ZWQpKjEwMCwgMikpICU+JQ0KICAgICAgICAgICAgICBhcnJhbmdlKGRlc2MoLiRjb252ZXJzaW9uX3JhdGUpKQ0KDQpDb252X1N0YXRlIA0KDQojIyMgVmlzdWFsaXplDQpnZ3Bsb3QoZGF0YSA9IENvbnZfU3RhdGUsIGFlcyh4ID0gcmVvcmRlcihzdGF0ZSwgLWNvbnZlcnNpb25fcmF0ZSksIHkgPSBjb252ZXJzaW9uX3JhdGUpKSsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9saW5lKGdyb3VwPTEpICsgDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDApKSArDQogIGxhYnMoeCA9ICdTdGF0ZScpICsgDQogIGdndGl0bGUoIkNvbnZlcnNpb24gUmF0ZSBwZXIgU3RhdGUiKQ0KDQojIyMgU3RhdGlzdGljYWwgdGVzdA0KbG9naXRfc3RhdGUgPC0gZ2xtKENvbnZlcnRlZCB+IHN0YXRlLCBkYXRhID0gc3RhdHVzLCBmYW1pbHkgPSAiYmlub21pYWwiKQ0KI3N1bW1hcnkobG9naXRfc3RhdGUpDQoNCndhbGQudGVzdChiID0gY29lZihsb2dpdF9zdGF0ZSksIFNpZ21hID0gdmNvdihsb2dpdF9zdGF0ZSksIFRlcm1zID0gMjo0NikNCg0KYGBgIA0KQ29udmVyc2lvbiUgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQgYmV0d2VlbiBzdGF0ZXMsIHJhbmdpbmcgZnJvbSAxMyUgdG8gNDglDQogIA0KDQoNCg0KIyMjIEJ5IHByaWNpbmcgYmluDQpgYGB7ciwgZmlnLndpZHRoPTEwfQ0KIyMjIERpc3RyaWJ1dGlvbiBvZiBwcmVtaXVtIHBlciBjb252ZXJ0ZWQgc3RhdHVzDQpnIDwtIGZ1bmN0aW9uKHgpIGMobiA9IGxlbmd0aCh4KSwgcXVhbnRpbGUoeCwgYygwLCAwLjI1LCAwLjUsIDAuNzUsIDAuOSwgMC45OSwgMSkpKQ0KdGFwcGx5KHN0YXR1cyR5ZWFybHlfcHJlbWl1bSwgc3RhdHVzJENvbnZlcnRlZCwgZykNCg0KIyMjIEhpZ2hseSBza2V3ZWQgZGlzdHJpYnV0aW9uLCB6b29tIGluIHRvIGp1c3QgcHJlbWl1bSA8ICQxNTAwIGFuZCBzZWUgaG93IGNvbnZlcnNpb24gdmFyaWVzDQoNCiMgZ2dwbG90KHN0YXR1c1tzdGF0dXMkeWVhcmx5X3ByZW1pdW0gPD0xNTAwLCBdLA0KIyAgICAgICAgYWVzKHg9YmluMTAsIGNvbG9yID1hcy5mYWN0b3IoQ29udmVydGVkKSkpICsNCiMgICBnZW9tX2hpc3RvZ3JhbShmaWxsPSJ3aGl0ZSIsIGJpbndpZHRoID0gMTAsIHN0YXQgPSAnY291bnQnKSArDQojICAgZ2d0aXRsZSgnQ29udmVyc2lvbiB2cyBQcmVtaXVtJykNCg0KIyMjIEN1dCBpbnRvICQxMCBpbmNyZW1lbnRzLCAxMTYgYnVja2V0cw0KY3V0b2ZmIDwtIHNlcSgzNTAsIDE1MDAsIDEwKQ0Kc3RhdHVzJGJpbjEwIDwtIGN1dChzdGF0dXMkeWVhcmx5X3ByZW1pdW0sIGJyZWFrcyA9IGN1dG9mZiwgcmlnaHQgPSBGLCBsYWJlbHMgPSBGKQ0Kc3RhdHVzW2lzLm5hKHN0YXR1cyRiaW4xMCksIF0kYmluMTAgPC0gMTE2DQoNCnggPC0gc3RhdHVzICU+JSBncm91cF9ieShiaW4xMCkgJT4lDQogICAgICAgICAgICAgICAgc3VtbWFyaXNlKG1pbiA9IG1pbih5ZWFybHlfcHJlbWl1bSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgLG1heCA9IG1heCh5ZWFybHlfcHJlbWl1bSkpDQoNCiN0YWlsKHgpDQoNCkNvbnZfYmluMTAgPC0gc3RhdHVzICU+JQ0KICAgICAgICAgICAgICBncm91cF9ieShiaW4xMCkgJT4lIA0KICAgICAgICAgICAgICBzdW1tYXJpc2UoY29udmVyc2lvbl9yYXRlID0gcm91bmQobWVhbihDb252ZXJ0ZWQpKjEwMCwgMikpDQoNCkNvbnZfYmluMTAgPC0gc3FsZGYoInNlbGVjdCB4LiosIGIuY29udmVyc2lvbl9yYXRlIA0KICAgICAgICAgICAgICAgICAgICBmcm9tIHggbGVmdCBqb2luIENvbnZfYmluMTAgYg0KICAgICAgICAgICAgICAgICAgICBvbiB4LmJpbjEwID0gYi5iaW4xMCIpDQpDb252X2JpbjEwDQoNCmdncGxvdChkYXRhPUNvbnZfYmluMTAsYWVzKHg9bWluLCB5PWNvbnZlcnNpb25fcmF0ZSkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9saW5lKGdyb3VwPTEpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1zZXEoMzUwLDE1MDAsNTApKSArDQogIGxhYnMoeCA9ICdQcmVtaXVtIGluICQxMCBpbmNyZW1lbnQsIGxvd2VyIGJvdW5kJw0KICAgICAgICx5ID0gJ0NvbnZlcnNpb24gUmF0ZSAlJykgKw0KICBnZ3RpdGxlKCdDb252ZXJzaW9uIHZzIFByZW1pdW0nKQ0KYGBgDQpUaGlzIHByaWNlIGVsYXN0aWNpdHkgY3VydmUgbWFrZXMgc2Vuc2UgYXMgd2Ugd291bGQgZXhwZWN0IG1vcmUgcGVvcGxlIHRvIGJ1eSBhbiBpbnN1cmFuY2UgcG9saWN5IGF0IGEgbG93ZXIgcHJpY2UuDQoNCiMjIyBCYXNlZCBvbiB0aGUgbGluZSBncmFwaCwgcmUtYmluIHByZW1pdW0gaW50byBzbWFsbGVyIG51bWJlciBvZiBidWNrZXRzICANCmBgYHtyLCBmaWcud2lkdGg9MTB9DQptZWFuKHN0YXR1c1tzdGF0dXMkeWVhcmx5X3ByZW1pdW0gPj0zNTAgJiBzdGF0dXMkeWVhcmx5X3ByZW1pdW0gPDM2MCwgXSRDb252ZXJ0ZWQpICMzNC4wJQ0KbWVhbihzdGF0dXNbc3RhdHVzJHllYXJseV9wcmVtaXVtID49MzYwICYgc3RhdHVzJHllYXJseV9wcmVtaXVtIDw0NDAsIF0kQ29udmVydGVkKSAjNDAuNyUNCm1lYW4oc3RhdHVzW3N0YXR1cyR5ZWFybHlfcHJlbWl1bSA+PTQ0MCAmIHN0YXR1cyR5ZWFybHlfcHJlbWl1bSA8NjAwLCBdJENvbnZlcnRlZCkgIzM1LjQlDQptZWFuKHN0YXR1c1tzdGF0dXMkeWVhcmx5X3ByZW1pdW0gPj02MDAgJiBzdGF0dXMkeWVhcmx5X3ByZW1pdW0gPDcyMCwgXSRDb252ZXJ0ZWQpICMzMi43JQ0KbWVhbihzdGF0dXNbc3RhdHVzJHllYXJseV9wcmVtaXVtID49NzIwICYgc3RhdHVzJHllYXJseV9wcmVtaXVtIDwxMDAwLCBdJENvbnZlcnRlZCkgIzI4LjQNCm1lYW4oc3RhdHVzW3N0YXR1cyR5ZWFybHlfcHJlbWl1bSA+PTEwMDAgJiBzdGF0dXMkeWVhcmx5X3ByZW1pdW0gPDEzNjAsIF0kQ29udmVydGVkKSAjMjAuNCUNCm1lYW4oc3RhdHVzW3N0YXR1cyR5ZWFybHlfcHJlbWl1bSA+PTEzNjAsIF0kQ29udmVydGVkKSAjMTEuOSUNCg0KIyBnZ3Bsb3Qoc3RhdHVzW3N0YXR1cyR5ZWFybHlfcHJlbWl1bSA8PTE1MDAsIF0sDQojICAgICAgICBhZXMoeD15ZWFybHlfcHJlbWl1bSwgY29sb3IgPWFzLmZhY3RvcihDb252ZXJ0ZWQpKSkgKw0KIyAgIGdlb21faGlzdG9ncmFtKGZpbGw9IndoaXRlIiwgYmlud2lkdGggPSAxMCkgKw0KIyAgIGdndGl0bGUoJ0NvbnZlcnNpb24gdnMgUHJlbWl1bScpDQogIA0Kc3RhdHVzJGJpbiA8LSBpZmVsc2Uoc3RhdHVzJHllYXJseV9wcmVtaXVtID49MzUwICYgc3RhdHVzJHllYXJseV9wcmVtaXVtIDwzNjAsICcxLTM1MC0zNTknLA0KICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHN0YXR1cyR5ZWFybHlfcHJlbWl1bSA+PTM2MCAmIHN0YXR1cyR5ZWFybHlfcHJlbWl1bSA8NDQwLCAnMi0zNjAtNDM5JywNCiAgICAgICAgICAgICAgICAgICAgICBpZmVsc2Uoc3RhdHVzJHllYXJseV9wcmVtaXVtID49NDQwICYgc3RhdHVzJHllYXJseV9wcmVtaXVtIDw2MDAsICczLTQ0MC01OTknLA0KICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHN0YXR1cyR5ZWFybHlfcHJlbWl1bSA+PTYwMCAmIHN0YXR1cyR5ZWFybHlfcHJlbWl1bSA8NzIwLCAnNC02MDAtNzE5JywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHN0YXR1cyR5ZWFybHlfcHJlbWl1bSA+PTcyMCAmIHN0YXR1cyR5ZWFybHlfcHJlbWl1bSA8MTAwMCwgJzUtNzIwLTk5OScsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShzdGF0dXMkeWVhcmx5X3ByZW1pdW0gPj0xMDAwICYgc3RhdHVzJHllYXJseV9wcmVtaXVtIDwxMzYwLCAnNi0xMDAwLTEzNTknLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShzdGF0dXMkeWVhcmx5X3ByZW1pdW0gPj0xMzYwLCAnNy0xMzYwKycsICdOQScpKSkpKSkpDQpzdGF0dXMkYmluIDwtZmFjdG9yKHN0YXR1cyRiaW4pDQoNCiMjI0NoZWNrIGJpbm5pbmcNCnRhcHBseShzdGF0dXMkeWVhcmx5X3ByZW1pdW0sIHN0YXR1cyRiaW4sIHJhbmdlKQ0KDQojIyMgVGFidWxhdGUNCkNvbnZfYmluIDwtIHN0YXR1cyAlPiUgDQogICAgICAgICAgICAgIGdyb3VwX2J5KGJpbikgJT4lIA0KICAgICAgICAgICAgICBzdW1tYXJpc2UoY29udmVyc2lvbl9yYXRlPSByb3VuZChtZWFuKENvbnZlcnRlZCkqMTAwLCAyKQ0KICAgICAgICAgICAgICAgICAgICAgICAgLG4gPSBuKCkpICU+JQ0KICAgICAgICAgICAgICBhcnJhbmdlKGRlc2MoLiRjb252ZXJzaW9uX3JhdGUpKQ0KDQpDb252X2JpbiANCg0KIyMjIFZpc3VhbGl6ZQ0KZ2dwbG90KGRhdGEgPSBDb252X2JpbiwgYWVzKHggPSBiaW4sDQogICAgICAgICAgICAjeCA9IHJlb3JkZXIoYmluMiwgY29udmVyc2lvbl9yYXRlKSwNCiAgICAgICAgICAgeSA9IGNvbnZlcnNpb25fcmF0ZSkpKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX2xpbmUoZ3JvdXA9MSkgKyANCiAgbGFicyh4ID0gJ1ByaWNpbmcgQmluICQnDQogICAgICAgLHkgPSAnQ29udmVyc2lvbiBSYXRlICUnKSArIA0KICBnZ3RpdGxlKCJDb252ZXJzaW9uIFJhdGUgcGVyIFByaWNpbmciKQ0KDQojIyMgU3RhdGlzdGljYWwgdGVzdA0KbG9naXRfYmluIDwtIGdsbShDb252ZXJ0ZWQgfiBiaW4sIGRhdGEgPSBzdGF0dXMsIGZhbWlseSA9ICJiaW5vbWlhbCIpDQojc3VtbWFyeShsb2dpdF9iaW4pDQp3YWxkLnRlc3QoYiA9IGNvZWYobG9naXRfYmluKSwgU2lnbWEgPSB2Y292KGxvZ2l0X2JpbiksIFRlcm1zID0gMjo3KQ0KYGBgDQpDb252ZXJzaW9uJSBnZW5lcmFsbHkgZHJvcHMgd2l0aCBpbmNyZWFzaW5nIHByZW1pdW0sIGFuZCBpcyBzaWduaWZpY2FudGx5IGRpZmZlcmVudCBiZXR3ZWVuIHByaWNlIGJ1Y2tldHMuDQoNCkFsc28sIGhpZ2hlc3QgY29udmVyc2lvbiByYXRlIGhhcHBlbnMgYXQgJDM2MC00MzkuIERvZXMgaXQgc3VnZ3NldCByYWlzaW5nIHRoZSBtaW5pbXVtIHByZW1pdW0gZnJvbSAkMzUwIG9yIGlzIGl0IGp1c3QgYSBtaW5pbXVtIHByaWNlIHBlciBzdGF0ZSByZXF1aXJlbWVudCBmb3IgY2VydGFpbiBidXNpbmVzcyBjbGFzc2VzPw0KDQojIyMgRG9lcyBiaW5uaW5nIGVmZmVjdCBjaGFuZ2Ugd2l0aCBidXNpbmVzcyBjbGFzcz8NCmBgYHtyLCBmaWcud2lkdGg9MTB9DQpDb252X2Jpbl9jb2IgPC0gc3RhdHVzICU+JSANCiAgICAgICAgICAgICAgZ3JvdXBfYnkoY29iX2lkLCBiaW4pICU+JSANCiAgICAgICAgICAgICAgc3VtbWFyaXNlKGNvbnZlcnNpb25fcmF0ZT0gcm91bmQobWVhbihDb252ZXJ0ZWQpKjEwMCwgMikNCiAgICAgICAgICAgICAgICAgICAgICAgICxuID0gbigpKSAjJT4lDQogICAgICAgICAgICAgICNhcnJhbmdlKGRlc2MoLiRjb252ZXJzaW9uX3JhdGUpKQ0KDQpDb252X2Jpbl9jb2INCg0KIyMjIFZpc3VhbGl6ZQ0KZ2dwbG90KGRhdGEgPSBDb252X2Jpbl9jb2IsIGFlcyh4ID0gYmluLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjeCA9IHJlb3JkZXIoYmluMiwgY29udmVyc2lvbl9yYXRlKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IGNvbnZlcnNpb25fcmF0ZSkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9saW5lKGFlcyhncm91cD1jb2JfaWQsIGNvbG9yPWNvYl9pZCkpICsgDQogIGxhYnMoeCA9ICdQcmljaW5nIEJpbiAkJykgKyANCiAgZ2d0aXRsZSgiQ29udmVyc2lvbiBSYXRlIHBlciBQcmljaW5nIHBlciBDT0IiKQ0KDQpgYGANCkNvbnZlcnNpb24gdHJlbmRpbmcgZ2VuZXJhbGx5IGxvb2tzIHNpbWlsYXIgYWNyb3NzIGFsbCBidXNpbmVzcyBjbGFzc2VzIGV4Y2VwdCBmb3IgNTAwMyB0aGF0IHNob3dzIHRoZSBoaWdoZXN0IGNvbnZlcnNpb24gcmF0ZSBvZiAyNi4zJSBhdCAkMzUwLSQzNTkgcmFuZ2UuIEZvciBjbGFzcyAxMDAwMDEsIHByZW1pdW0gc3RhcnRzIGF0ICQ3MjAtJDk5OSByYW5nZS4gSXQncyBhbHNvIHRoZSBzYW1lIHJhbmdlIHdoZXJlIHRoZSBoaWdoZXN0IGNvbnZlcnNpb24gaGl0cy4NCg0KDQojIyMgRG9lcyBiaW5uaW5nIGVmZmVjdCBjaGFuZ2Ugd2l0aCBzdGF0ZT8NCmBgYHtyLCBmaWcud2lkdGg9MTB9DQpDb252X2Jpbl9zdGF0ZSA8LSBzdGF0dXMgJT4lIA0KICAgICAgICAgICAgICBncm91cF9ieShzdGF0ZSwgYmluKSAlPiUgDQogICAgICAgICAgICAgIHN1bW1hcmlzZShjb252ZXJzaW9uX3JhdGU9IHJvdW5kKG1lYW4oQ29udmVydGVkKSoxMDAsIDIpDQogICAgICAgICAgICAgICAgICAgICAgICAsbiA9IG4oKSkgIyU+JQ0KICAgICAgICAgICAgICAjYXJyYW5nZShkZXNjKC4kY29udmVyc2lvbl9yYXRlKSkNCg0KQ29udl9iaW5fc3RhdGUgDQoNCiMjIyBWaXN1YWxpemUNCiMjIyA3IHN0YXRlcyBhdCBhIHRpbWUNCnN0IDwtIHNvcnQodW5pcXVlKENvbnZfYmluX3N0YXRlJHN0YXRlKSkNCmZvciAoayBpbiAwOjYpew0KICBpPTErayo3DQogIGlmIChrPT02KXsNCiAgICBqPTQ2DQogIH0NCiAgZWxzZXsNCiAgICAgIGo9NytrKjcNCiAgfQ0KcCA8LSBnZ3Bsb3QoZGF0YSA9IENvbnZfYmluX3N0YXRlW0NvbnZfYmluX3N0YXRlJHN0YXRlICVpbiUgc3RbaTpqXSwgXQ0KICAgICAgICAgICAgLCBhZXMoeCA9IGJpbiwNCiAgICAgICAgICAgICAgICAgICN4ID0gcmVvcmRlcihiaW4sIGNvbnZlcnNpb25fcmF0ZSksDQogICAgICAgICAgICAgICAgICB5ID0gY29udmVyc2lvbl9yYXRlKSkgKw0KICAgIGdlb21fcG9pbnQoKSArDQogICAgZ2VvbV9saW5lKGFlcyhncm91cD1zdGF0ZSwgY29sb3I9c3RhdGUpKSArIA0KICAgIGxhYnMoeCA9ICdQcmljaW5nIEJpbiAkJw0KICAgICAgICAgLHkgPSAnQ29udmVyc2lvbiBSYXRlICUnKSArIA0KICAgIGdndGl0bGUocGFzdGUoIkNvbnZlcnNpb24gUmF0ZSBwZXIgUHJpY2luZyBwZXIgU3RhdGVzICIsIHN0W2ldLCAiIC0gIiwgc3Rbal0pKQ0KDQpwcmludChwKQ0KfQ0KYGBgDQoNCkNvbnZlcnNpb24gdHJlbmRpbmcgZG9lcyB2YXJ5IGJ5IHN0YXRlLiBOb3QgYWxsIHRoZSBzdGF0ZXMgc3RhcnQgYW5kIHBlYWsgYXQgdGhlIHNhbWUgcHJpY2UgcmFuZ2UuIEZvciBleGFtcGxlLCBDQSwgTUUsIE5ELCBPSywgVlQsIFdZIHN0YXJ0IGZyb20gJDQ0MCAtICQ1OTkgcmFuZ2UuIE9LIHBlYWtzIGF0ICQ2MDAgLSA3MTkgcmFuZ2UuDQpTb21lIHN0YXRlcyBoYXZlIHZlcnkgc21hbGwgc2FtcGxlIHNpemUgKGUuZy4gb25seSAxNSBjdXN0b21lcnMgaW4gREMpIGFuZCBhYnNvbHV0ZSBjb252ZXJzaW9uIG9mIDEwMCUgaXMgYmFzZWQgb24gb25seSAxIGNhc2UuDQoNClNldHRpbmcgbWluaW11bSBwcmVtaXVtIHNob3VsZCB0YWtlIGZhY3RvcnMgbGlrZSBidXNpbmVzcyBjbGFzcyBhbmQgc3RhdGUgaW50byBjb25zaWRlcmF0aW9uLCBiZWNhdXNlIGRpZmZlcmVudCBidXNpbmVzcyBjbGFzc2VzIG1heSBoYXZlIGRpZmZlcmVudCByaXNrIGxldmVscyB0byBwcm90ZWN0IGFuZCBkaWZmZXJlbnQgc3RhdGVzIG1heSBoYXZlIGRpZmZlcmVudCBjb25jZW50cmF0aW9ucyBvZiBidXNpbmVzcyBhbmQgcmVndWxhdGlvbnMuIEFsc28sIHdoZW4gbG9va2luZyBhdCBmYXZvcmFibGUgcHJpY2luZyBpbiB0ZXJtcyBvZiBjb252ZXJzaW9uLCBvbmUgbmVlZHMgdG8gYWxzbyBsb29rIGF0IHRoZSBzaXplIG9mIGNvbnZlcnRlZCBwb29sIC0tIGFyZSB3ZSB0YWxraW5nIGFib3V0IDkgY2FzZXMgb3V0IG9mIDEwIG9yIDkwMCBjYXNlcyBvdXQgb2YgMTAwMCBiZWluZyBjb252ZXJ0ZWQ/IEJhc2luZyBtaW5pbXVtIHByZW1pdW0gb24gc3BhcnNlIGRhdGEgY2FuIGxlYWQgdG8gdW5yZWxpYWJsZSByZXN1bHRzLg0KDQoNCg0K