library(dplyr)
data(sales, package="DMwR2")
sales
summary(sales)
       ID              Prod            Quant                Val         
 v431   : 10159   p1125  :  3923   Min.   :      100   Min.   :   1005  
 v54    :  6017   p3774  :  1824   1st Qu.:      107   1st Qu.:   1345  
 v426   :  3902   p1437  :  1720   Median :      168   Median :   2675  
 v1679  :  3016   p1917  :  1702   Mean   :     8442   Mean   :  14617  
 v1085  :  3001   p4089  :  1598   3rd Qu.:      738   3rd Qu.:   8680  
 v1183  :  2642   p2742  :  1519   Max.   :473883883   Max.   :4642955  
 (Other):372409   (Other):388860   NA's   :13842       NA's   :1182     
    Insp       
 ok   : 14462  
 unkn :385414  
 fraud:  1270  
               
               
               
               
nlevels(sales$ID)
[1] 6016
nlevels(sales$Prod)
[1] 4548
filter(sales,is.na(Quant),is.na(Val))

We can look at the proportions of the missing values

table(sales$Insp)/nrow(sales) * 100

       ok      unkn     fraud 
 3.605171 96.078236  0.316593 
library(ggplot2)
ggplot(group_by(sales,ID) %>% summarize(nTrans=n()),aes(x=ID,y=nTrans)) +
    geom_bar(stat="identity") + 
    theme(axis.text.x = element_blank(), axis.ticks.x=element_blank()) + 
    xlab("Salesmen") + ylab("Nr. of Transactions") +
    ggtitle("Nr. of Transactions per Salesman")

ggplot(group_by(sales,Prod) %>% summarize(nTrans=n()),aes(x=Prod,y=nTrans)) +
    geom_bar(stat="identity") + 
    theme(axis.text.x = element_blank(), axis.ticks.x=element_blank()) + 
    xlab("Product") + ylab("Nr. of Transactions") +
    ggtitle("Nr. of Transactions per Product")

ggplot(group_by(sales,ID) %>% summarize(nTrans=n()),aes(x=ID,y=nTrans)) +
    geom_bar(stat="identity") + 
    theme(axis.text.x = element_blank(), axis.ticks.x=element_blank()) + 
    xlab("Product") + ylab("Nr. of Transactions") +
    ggtitle("Nr. of Transactions per Salesman")

ggplot(group_by(sales,Prod) %>% summarize(nTrans=n()),aes(x=Prod,y=nTrans)) +
    geom_bar(stat="identity") + 
    theme(axis.text.x = element_blank(), axis.ticks.x=element_blank()) + 
    xlab("Salesmen") + ylab("Nr. of Transactions") +
    ggtitle("Nr. of Transactions per Product")

sales <- mutate(sales,Uprice=Val/Quant)
sales
summary(sales$Uprice)
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max.     NA's 
    0.00     8.46    11.89    20.30    19.11 26460.70    14136 
prods <- group_by(sales,Prod)
mpProds <- summarize(prods,medianPrice=median(Uprice,na.rm=TRUE))
bind_cols(mpProds %>% arrange(medianPrice) %>% slice(1:5),
          mpProds %>% arrange(desc(medianPrice)) %>% slice(1:5))
New names:
library(ggplot2)
library(forcats)
ggplot(filter(sales,Prod %in% c("p3689","p560")),aes(x=fct_drop(Prod),y=Uprice)) +
    geom_boxplot() + scale_y_log10() + 
    xlab("") + ylab("log10(UnitPrice)")

ids <- group_by(sales,ID)
tvIDs <- summarize(ids,totalVal=sum(Val,na.rm=TRUE))
bind_cols(tvIDs %>% arrange(totalVal) %>% slice(1:5),
          tvIDs %>% arrange(desc(totalVal)) %>% slice(1:5))
New names:
arrange(tvIDs,desc(totalVal)) %>% slice(1:100) %>% 
    summarize(t100=sum(totalVal)) / 
    (summarize(tvIDs,sum(totalVal))) * 100
arrange(tvIDs,totalVal) %>% slice(1:2000) %>% 
    summarize(b2000=sum(totalVal)) / 
    (summarize(tvIDs,sum(totalVal))) * 100
prods <- group_by(sales,Prod)
qtProds <- summarize(prods,totalQty=sum(Quant,na.rm=TRUE))
bind_cols(qtProds %>% arrange(desc(totalQty)) %>% slice(1:5),
          qtProds %>% arrange(totalQty) %>% slice(1:5))
New names:
arrange(qtProds,desc(totalQty)) %>% slice(1:100) %>% 
    summarize(t100=sum(as.numeric(totalQty))) / 
    (summarize(qtProds,sum(as.numeric(totalQty)))) * 100
arrange(qtProds,totalQty) %>% slice(1:4000) %>% 
    summarize(b4000=sum(as.numeric(totalQty))) / 
    (summarize(qtProds,sum(as.numeric(totalQty)))) * 100
nouts <- function(x) length(boxplot.stats(x)$out)
noutsProds <- summarise(prods,nOut=nouts(Uprice))
arrange(noutsProds,desc(nOut))
summarize(noutsProds,totalOuts=sum(nOut))
summarize(noutsProds,totalOuts=sum(nOut))/nrow(sales)*100
prop.naQandV <- function(q,v) 100*sum(is.na(q) & is.na(v))/length(q)
summarise(ids,nProbs=prop.naQandV(Quant,Val)) %>% arrange(desc(nProbs))
summarise(prods,nProbs=prop.naQandV(Quant,Val)) %>% arrange(desc(nProbs))
sales <- filter(sales,!(is.na(Quant) & is.na(Val)))
prop.nas <- function(x) 100*sum(is.na(x))/length(x)
summarise(prods,propNA.Q=prop.nas(Quant)) %>% arrange(desc(propNA.Q))
filter(sales, Prod %in% c("p2442","p2443")) %>% 
    group_by(Insp) %>% count()
sales <- droplevels(filter(sales,!(Prod %in% c("p2442", "p2443"))))
summarise(ids,propNA.Q=prop.nas(Quant)) %>% arrange(desc(propNA.Q))
summarise(prods,propNA.V=prop.nas(Val)) %>% arrange(desc(propNA.V))
summarise(ids,propNA.V=prop.nas(Val)) %>% arrange(desc(propNA.V))
tPrice <- filter(sales, Insp != "fraud") %>% 
          group_by(Prod) %>% 
          summarise(medianPrice = median(Uprice,na.rm=TRUE))
noQuantMedPrices <- filter(sales, is.na(Quant)) %>% 
    inner_join(tPrice) %>% 
    select(medianPrice)
Joining with `by = join_by(Prod)`
noValMedPrices <- filter(sales, is.na(Val)) %>% 
    inner_join(tPrice) %>% 
    select(medianPrice)
Joining with `by = join_by(Prod)`
noQuant <- which(is.na(sales$Quant))
noVal <- which(is.na(sales$Val))
sales[noQuant,'Quant'] <- ceiling(sales[noQuant,'Val'] /noQuantMedPrices)
sales[noVal,'Val'] <- sales[noVal,'Quant'] * noValMedPrices
sales$Uprice <- sales$Val/sales$Quant
save(sales, file = "salesClean.Rdata")
ms <- filter(sales,Insp != "fraud") %>% 
    group_by(Prod) %>% 
    summarize(median=median(Uprice,na.rm=TRUE),
              iqr=IQR(Uprice,na.rm=TRUE),
              nTrans=n(),
              fewTrans=ifelse(nTrans>20,FALSE,TRUE))
ms

Properties of the distribution of unit prices

ggplot(ms,aes(x=median,y=iqr,color=fewTrans)) + 
    geom_point() + 
    xlab("Median") + ylab("IQR")

ggplot(ms,aes(x=median,y=iqr,color=fewTrans)) + 
    geom_point() + 
    scale_y_log10() + scale_x_log10() + 
    xlab("log(Median)") + ylab("log(IQR)")

ms <- mutate(ms,smedian=scale(median),siqr=scale(iqr))
smalls <- which(ms$fewTrans)
nsmalls <- as.character(ms$Prod[smalls])
similar <- matrix(NA,length(smalls),7,
    dimnames=list(nsmalls,
      c("RowSimProd", "ks.stat", "ks.p", "medP", "iqrP", "medS","iqrS")))
xprods <- tapply(sales$Uprice, sales$Prod, list)
for(i in seq_along(smalls)) {
    d <- scale(ms[,c("smedian","siqr")],
               c(ms$smedian[smalls[i]],ms$siqr[smalls[i]]),
               FALSE)
    d <- sqrt(drop(d^2 %*% rep(1, ncol(d))))
    stat <- ks.test(xprods[[nsmalls[i]]], xprods[[order(d)[2]]])
    similar[i, ] <- c(order(d)[2], stat$statistic, stat$p.value,
                      ms$median[smalls[i]],ms$iqr[smalls[i]],
                      ms$median[order(d)[2]],ms$iqr[order(d)[2]])
}
Warning: p-value will be approximate in the presence of tiesWarning: p-value will be approximate in the presence of tiesWarning: p-value will be approximate in the presence of ties
head(similar)
    RowSimProd   ks.stat       ks.p     medP      iqrP     medS      iqrS
p8        2827 0.4339623 0.04073992 3.850211 0.7282168 3.868306 0.7938557
p18        213 0.2568922 0.21381724 5.187266 8.0359968 5.274884 7.8207052
p38       1044 0.3650794 0.08445708 5.490758 6.4162095 5.651818 6.2436224
p39       3418 0.2214286 0.69337166 7.986486 1.4229755 8.005181 1.5625650
p40       1335 0.3760000 0.02941385 9.674797 1.6104511 9.711538 1.6505602
p47       1387 0.3125000 0.35500258 2.504092 2.5625835 2.413498 2.6402087
bind_rows(filter(ms,Prod==rownames(similar)[1]),
          ms[similar[1,1],])
nrow(similar[similar[, "ks.p"] >= 0.9, ])
[1] 72
sum(similar[, "ks.p"] >= 0.9)
[1] 72
save(similar, file = "similarProducts.Rdata")

Evaluation Criteria

library(ROCR)
data(ROCR.simple)
pred <- prediction(ROCR.simple$predictions, ROCR.simple$labels)
perf <- performance(pred, "prec", "rec")
plot(perf)

PRcurve <- function(preds, trues, ...) {
    require(ROCR, quietly = TRUE)
    pd <- prediction(preds, trues)
    pf <- performance(pd, "prec", "rec")
    pf@y.values <- lapply(pf@y.values, function(x) rev(cummax(rev(x))))
    plot(pf, ...)
}
PRcurve(ROCR.simple$predictions, ROCR.simple$labels)

library(ROCR)
data(ROCR.simple)
pred <- prediction(ROCR.simple$predictions, ROCR.simple$labels)
perf <- performance(pred, "prec", "rec")
par( mfrow=c(1,2) )
plot(perf)
PRcurve(ROCR.simple$predictions, ROCR.simple$labels)
par( mfrow=c(1,1) )

pred <- prediction(ROCR.simple$predictions, ROCR.simple$labels)
perf <- performance(pred, "lift", "rpp")
plot(perf, main = "Lift Chart")

CRchart <- function(preds, trues, ...) {
    require(ROCR, quietly = T)
    pd <- prediction(preds, trues)
    pf <- performance(pd, "rec", "rpp")
    plot(pf, ...)
}
CRchart(ROCR.simple$predictions, ROCR.simple$labels, 
        main='Cumulative Recall Chart')

avgNDTP <- function(toInsp,train,stats) {
  if (missing(train) && missing(stats)) 
      stop('Provide either the training data or the product stats')
  if (missing(stats)) {
      stats <- as.matrix(filter(train,Insp != 'fraud') %>%
                         group_by(Prod) %>%
                         summarise(median=median(Uprice),iqr=IQR(Uprice)) %>%
                         select(median,iqr))
      rownames(stats) <- levels(train$Prod)
      stats[which(stats[,'iqr']==0),'iqr'] <- stats[which(stats[,'iqr']==0),'median']
  }
  
  return(mean(abs(toInsp$Uprice-stats[toInsp$Prod,'median']) /
                 stats[toInsp$Prod,'iqr']))
}
evalOutlierRanking <- function(testSet,rankOrder,Threshold,statsProds,...) 
{
   ordTS <- testSet[rankOrder,]
   N <- nrow(testSet)
   nF <- if (Threshold < 1) as.integer(Threshold*N) else Threshold
   cm <- table(c(rep('fraud',nF),rep('ok',N-nF)),ordTS$Insp)
   prec <- cm['fraud','fraud']/sum(cm['fraud',])
   rec <- cm['fraud','fraud']/sum(cm[,'fraud'])
   AVGndtp <- avgNDTP(ordTS[1:nF,],stats=statsProds)
   return(c(Precision=prec,Recall=rec,avgNDTP=AVGndtp))
}
BPrule.wf <- function(form,train,test,...) {
    require(dplyr, quietly=TRUE)
    ms <- as.matrix(filter(train,Insp != 'fraud') %>%
                    group_by(Prod) %>%
                    summarise(median=median(Uprice),iqr=IQR(Uprice)) %>%
                    select(median,iqr))
    rownames(ms) <- levels(train$Prod)
    ms[which(ms[,'iqr']==0),'iqr'] <- ms[which(ms[,'iqr']==0),'median']
    ORscore <- abs(test$Uprice-ms[test$Prod,'median']) /
               ms[test$Prod,'iqr']
    rankOrder <- order(ORscore,decreasing=TRUE)
    res <- list(testSet=test,rankOrder=rankOrder,
                probs=matrix(c(ORscore,ifelse(test$Insp=='fraud',1,0)),
                             ncol=2))
    res
}
library(dplyr)
globalStats <- as.matrix(filter(sales,Insp != 'fraud') %>%
                         group_by(Prod) %>%
                         summarise(median=median(Uprice),iqr=IQR(Uprice)) %>%
                         select(median,iqr))
rownames(globalStats) <- levels(sales$Prod)
globalStats[which(globalStats[,'iqr']==0),'iqr'] <- 
    globalStats[which(globalStats[,'iqr']==0),'median']
head(globalStats,3)
     median      iqr
p1 11.34615 8.563580
p2 10.87786 5.609731
p3 10.00000 4.809092
library(performanceEstimation)
bp.res <- performanceEstimation(
    PredTask(Insp ~ ., sales),
    Workflow("BPrule.wf"),
    EstimationTask(metrics=c("Precision","Recall","avgNDTP"),
                   method=Holdout(nReps=3, hldSz=0.3, strat=TRUE),
                   evaluator="evalOutlierRanking",
                   evaluator.pars=list(Threshold=0.1, statsProds=globalStats))
)


##### PERFORMANCE ESTIMATION USING  HOLD OUT  #####

** PREDICTIVE TASK :: sales.Insp

++ MODEL/WORKFLOW :: BPrule.wf 
Task for estimating  Precision,Recall,avgNDTP  using
Stratified  3 x 70 % / 30 % Holdout
     Run with seed =  1234 
Iteration :  1  2  3
summary(bp.res)

== Summary of a  Hold Out Performance Estimation Experiment ==

Task for estimating  Precision,Recall,avgNDTP  using
Stratified  3 x 70 % / 30 % Holdout
     Run with seed =  1234 

* Predictive Tasks ::  sales.Insp
* Workflows  ::  BPrule.wf 

-> Task:  sales.Insp
  *Workflow: BPrule.wf 
           Precision     Recall    avgNDTP
avg     0.0178244211 0.56315789 11.3639416
std     0.0007261201 0.02294157  0.7633666
med     0.0181575879 0.57368421 11.1037531
iqr     0.0006663335 0.02105263  0.7293525
min     0.0169915042 0.53684211 10.7646833
max     0.0183241712 0.57894737 12.2233884
invalid 0.0000000000 0.00000000  0.0000000
ps.bp <- sapply(getIterationsInfo(bp.res),function(i) i$probs[,1])
ts.bp <- sapply(getIterationsInfo(bp.res),function(i) i$probs[,2])
PRcurve(ps.bp,ts.bp,main="PR curve",avg="vertical")

CRchart(ps.bp,ts.bp,main='Cumulative Recall curve',avg='vertical')

par(mfrow=c(1,2)) 
ps.bp <- sapply(getIterationsInfo(bp.res), function(i) i$probs[,1])
ts.bp <- sapply(getIterationsInfo(bp.res), function(i) i$probs[,2])
PRcurve(ps.bp, ts.bp, main="PR curve", avg="vertical")
CRchart(ps.bp, ts.bp, main='Cumulative Recall curve', avg='vertical')

LOF.wf <- function(form, train, test, k, ...) {
    require(DMwR2, quietly=TRUE)
    ntr <- nrow(train)
    all <- as.data.frame(rbind(train,test))
    N <- nrow(all)
    ups <- split(all$Uprice,all$Prod)
    r <- list(length=ups)
    for(u in seq(along=ups)) 
        r[[u]] <- if (NROW(ups[[u]]) > 3) 
                      lofactor(ups[[u]],min(k,NROW(ups[[u]]) %/% 2)) 
                  else if (NROW(ups[[u]])) rep(0,NROW(ups[[u]])) 
                  else NULL
    all$lof <- vector(length=N)
    split(all$lof,all$Prod) <- r
    all$lof[which(!(is.infinite(all$lof) | is.nan(all$lof)))] <- 
        SoftMax(all$lof[which(!(is.infinite(all$lof) | is.nan(all$lof)))])
    
    res <- list(testSet=test,
                rankOrder=order(all[(ntr+1):N,'lof'],decreasing=TRUE),
                probs=as.matrix(cbind(all[(ntr+1):N,'lof'],
                                      ifelse(test$Insp=='fraud',1,0))))
    res
}
lof.res <- performanceEstimation(
    PredTask(Insp ~ . , sales),
    Workflow("LOF.wf", k=7),
    EstimationTask(metrics=c("Precision","Recall","avgNDTP"),
                   method=Holdout(nReps=3, hldSz=0.3, strat=TRUE),
                   evaluator="evalOutlierRanking",
                   evaluator.pars=list(Threshold=0.1, statsProds=globalStats))
    )


##### PERFORMANCE ESTIMATION USING  HOLD OUT  #####

** PREDICTIVE TASK :: sales.Insp

++ MODEL/WORKFLOW :: LOF.wf 
Task for estimating  Precision,Recall,avgNDTP  using
Stratified  3 x 70 % / 30 % Holdout
     Run with seed =  1234 
Iteration :  1
summary(lof.res)

== Summary of a  Hold Out Performance Estimation Experiment ==

Task for estimating  Precision,Recall,avgNDTP  using
Stratified  3 x 70 % / 30 % Holdout
     Run with seed =  1234 

* Predictive Tasks ::  sales.Insp
* Workflows  ::  LOF.wf 

-> Task:  sales.Insp
  *Workflow: LOF.wf 
           Precision      Recall   avgNDTP
avg     0.0225442834 0.712280702 8.9724516
std     0.0002925108 0.009241802 0.7111686
med     0.0225720473 0.713157895 8.7667068
iqr     0.0002915209 0.009210526 0.6884858
min     0.0222388806 0.702631579 8.3868382
max     0.0228219224 0.721052632 9.7638097
invalid 0.0000000000 0.000000000 0.0000000
ps.lof <- sapply(getIterationsInfo(lof.res), function(i) i$probs[,1]) 
ts.lof <- sapply(getIterationsInfo(lof.res), function(i) i$probs[,2])

PRcurve(ps.bp,ts.bp,main="PR curve",lty=1,
        xlim=c(0,1),ylim=c(0,1),avg="vertical")

#PRcurve(ps.lof,ts.lof,add=TRUE,lty=2,avg='vertical')
legend('topright',c('BPrule','LOF'),lty=c(1,2))


CRchart(ps.bp,ts.bp,main='Cumulative Recall curve',
        lty=1,xlim=c(0,1),ylim=c(0,1),avg='vertical')
#CRchart(ps.lof,ts.lof,add=TRUE,lty=2,avg='vertical')
legend('bottomright',c('BPrule','LOF'),lty=c(1,2))

par(mfrow=c(1,2)) 
ps.lof <- sapply(getIterationsInfo(lof.res), function(i) i$probs[,1])
ts.lof <- sapply(getIterationsInfo(lof.res), function(i) i$probs[,2])
PRcurve(ps.bp, ts.bp,main="PR curve",lty=1,
        xlim=c(0,1),ylim=c(0,1),avg="vertical")
#PRcurve(ps.lof, ts.lof,add=T,lty=2,avg='vertical')
legend('topright',c('BPrule','LOF'),lty=c(1,2))
CRchart(ps.bp,ts.bp,main='Cumulative Recall curve',
        lty=1,xlim=c(0,1),ylim=c(0,1),avg='vertical')
#CRchart(ps.lof,ts.lof,add=T,lty=2,avg='vertical')
legend('bottomright',c('BPrule','LOF'),lty=c(1,2))

ORh.wf <- function(form, train, test, ...) {
    require(DMwR2, quietly=TRUE)
    ntr <- nrow(train)
    all <- as.data.frame(rbind(train,test))
    N <- nrow(all)
    ups <- split(all$Uprice,all$Prod)
    r <- list(length=ups)
    for(u in seq(along=ups)) 
        r[[u]] <- if (NROW(ups[[u]]) > 3) 
                      outliers.ranking(ups[[u]])$prob.outliers
                  else if (NROW(ups[[u]])) rep(0,NROW(ups[[u]])) 
                  else NULL
    all$orh <- vector(length=N)
    split(all$orh,all$Prod) <- r
    all$orh[which(!(is.infinite(all$orh) | is.nan(all$orh)))] <- 
        SoftMax(all$orh[which(!(is.infinite(all$orh) | is.nan(all$orh)))])
    res <- list(testSet=test,
                rankOrder=order(all[(ntr+1):N,'orh'],decreasing=TRUE),
                probs=as.matrix(cbind(all[(ntr+1):N,'orh'],
                                      ifelse(test$Insp=='fraud',1,0))))
    res
    
}
orh.res <- performanceEstimation(
    PredTask(Insp ~ . , sales),
    Workflow("ORh.wf"),
    EstimationTask(metrics=c("Precision","Recall","avgNDTP"),
                   method=Holdout(nReps=3, hldSz=0.3, strat=TRUE),
                   evaluator="evalOutlierRanking",
                   evaluator.pars=list(Threshold=0.1, statsProds=globalStats))
    )


##### PERFORMANCE ESTIMATION USING  HOLD OUT  #####

** PREDICTIVE TASK :: sales.Insp

++ MODEL/WORKFLOW :: ORh.wf 
Task for estimating  Precision,Recall,avgNDTP  using
Stratified  3 x 70 % / 30 % Holdout
     Run with seed =  1234 
Iteration :  1  2  3
summary(orh.res)

== Summary of a  Hold Out Performance Estimation Experiment ==

Task for estimating  Precision,Recall,avgNDTP  using
Stratified  3 x 70 % / 30 % Holdout
     Run with seed =  1234 

* Predictive Tasks ::  sales.Insp
* Workflows  ::  ORh.wf 

-> Task:  sales.Insp
  *Workflow: ORh.wf 
           Precision     Recall    avgNDTP
avg     0.0222388806 0.70263158  9.1207378
std     0.0006006249 0.01897659  0.9773273
med     0.0220722972 0.69736842  8.6315065
iqr     0.0005830418 0.01842105  0.8807146
min     0.0217391304 0.68684211  8.4846389
max     0.0229052141 0.72368421 10.2460681
invalid 0.0000000000 0.00000000  0.0000000
ps.orh <- sapply(getIterationsInfo(orh.res), function(i) i$probs[,1])
ts.orh <- sapply(getIterationsInfo(orh.res), function(i) i$probs[,2])
PRcurve(ps.bp,ts.bp,main="PR curve",lty=1,
        xlim=c(0,1),ylim=c(0,1),avg="vertical")
#PRcurve(ps.lof,ts.lof,add=TRUE,lty=2,avg='vertical')
PRcurve(ps.orh,ts.orh,add=TRUE,lty=1,col='grey', avg='vertical')
legend('topright',c('BPrule','LOF','ORh'),lty=c(1,2,1),
       col=c('black','black','grey'))


CRchart(ps.bp,ts.bp,main='Cumulative Recall curve',
        lty=1,xlim=c(0,1),ylim=c(0,1),avg='vertical')
#CRchart(ps.lof,ts.lof,add=TRUE,lty=2,avg='vertical')
CRchart(ps.orh,ts.orh,add=TRUE,lty=1,col='grey',avg='vertical')
legend('bottomright',c('BPrule','LOF','ORh'),lty=c(1,2,1),
       col=c('black','black','grey'))

par(mfrow=c(1,2)) 
ps.orh <- sapply(getIterationsInfo(orh.res), function(i) i$probs[,1])
ts.orh <- sapply(getIterationsInfo(orh.res), function(i) i$probs[,2])
PRcurve(ps.bp,ts.bp,main="PR curve",lty=1,
        xlim=c(0,1),ylim=c(0,1),avg="vertical")
#PRcurve(ps.lof,ts.lof,add=T,lty=2,avg='vertical')
PRcurve(ps.orh,ts.orh,add=T,lty=1,col='grey', avg='vertical')
legend('topright',c('BPrule','LOF','ORh'),lty=c(1,2,1),
       col=c('black','black','grey'))

CRchart(ps.bp,ts.bp,main='Cumulative Recall curve',
        lty=1,xlim=c(0,1),ylim=c(0,1),avg='vertical')
#CRchart(ps.lof,ts.lof,add=T,lty=2,avg='vertical')
CRchart(ps.orh,ts.orh,add=T,lty=1,col='grey',avg='vertical')
legend('bottomright',c('BPrule','LOF','ORh'),lty=c(1,2,1),
       col=c('black','black','grey'))

Supervised Approaches

library(UBL)
data(iris)
data <- iris[, c(1, 2, 5)]
data$Species <- factor(ifelse(data$Species == "setosa", "rare","common"))
table(data$Species)

common   rare 
   100     50 
newData <- SmoteClassif(Species ~ ., data, C.perc = "balance")
table(newData$Species)

common   rare 
    75     75 
newData2 <- SmoteClassif(Species ~ ., data, C.perc = list(common = 1,rare = 6))
table(newData2$Species)

common   rare 
   100    300 
library(ggplot2)
ggplot(data,aes(x=Sepal.Length,y=Sepal.Width,color=Species)) +
    geom_point() + ggtitle("Original Data")

ggplot(newData2,aes(x=Sepal.Length,y=Sepal.Width,color=Species)) +
    geom_point() + ggtitle("SMOTE'd Data")

NB.wf <- function(form,train,test,...) {
    require(e1071,quietly=TRUE)
    sup <- which(train$Insp != 'unkn')
    data <- as.data.frame(train[sup,c('ID','Prod','Uprice','Insp')])
    data$Insp <- factor(data$Insp,levels=c('ok','fraud'))
    model <- naiveBayes(Insp ~ .,data, ...)
    preds <- predict(model,test[,c('ID','Prod','Uprice','Insp')], type='raw')
    rankOrder <- order(preds[,'fraud'], decreasing=TRUE)
    rankScore <- preds[,'fraud']
    res <- list(testSet=test,
                rankOrder=rankOrder,
                probs=as.matrix(cbind(rankScore,
                                ifelse(test$Insp=='fraud',1,0))))
    res
}
nb.res <- performanceEstimation(
    PredTask(Insp ~ . , sales),
    Workflow("NB.wf"),
    EstimationTask(metrics=c("Precision","Recall","avgNDTP"),
                   method=Holdout(nReps=3,hldSz=0.3,strat=TRUE),
                   evaluator="evalOutlierRanking",
                   evaluator.pars=list(Threshold=0.1,
                                       statsProds=globalStats))
    )


##### PERFORMANCE ESTIMATION USING  HOLD OUT  #####

** PREDICTIVE TASK :: sales.Insp

++ MODEL/WORKFLOW :: NB.wf 
Task for estimating  Precision,Recall,avgNDTP  using
Stratified  3 x 70 % / 30 % Holdout
     Run with seed =  1234 
Iteration :  1  2  3
summary(nb.res)

== Summary of a  Hold Out Performance Estimation Experiment ==

Task for estimating  Precision,Recall,avgNDTP  using
Stratified  3 x 70 % / 30 % Holdout
     Run with seed =  1234 

* Predictive Tasks ::  sales.Insp
* Workflows  ::  NB.wf 

-> Task:  sales.Insp
  *Workflow: NB.wf 
           Precision     Recall  avgNDTP
avg     0.0134932534 0.42631579 6.403751
std     0.0009009374 0.02846488 1.070227
med     0.0137431284 0.43421053 6.175571
iqr     0.0008745627 0.02763158 1.051825
min     0.0124937531 0.39473684 5.466015
max     0.0142428786 0.45000000 7.569665
invalid 0.0000000000 0.00000000 0.000000
ps.nb <- sapply(getIterationsInfo(nb.res), function(i) i$probs[,1])
ts.nb <- sapply(getIterationsInfo(nb.res), function(i) i$probs[,2])
PRcurve(ps.nb,ts.nb,main="PR curve",lty=1,
        xlim=c(0,1),ylim=c(0,1),avg="vertical")
PRcurve(ps.orh,ts.orh,add=TRUE,lty=2,avg='vertical')
legend('topright',c('NaiveBayes','ORh'),lty=1,col=c('black','grey'))


CRchart(ps.nb,ts.nb,main='Cumulative Recall curve',
        lty=1,xlim=c(0,1),ylim=c(0,1),avg='vertical')
CRchart(ps.orh,ts.orh,add=TRUE,lty=2,avg='vertical')
legend('bottomright',c('NaiveBayes','ORh'),lty=1,col=c('black','grey'))

par(mfrow=c(1,2)) 
ps.nb <- sapply(getIterationsInfo(nb.res), function(i) i$probs[,1])
ts.nb <- sapply(getIterationsInfo(nb.res), function(i) i$probs[,2])
PRcurve(ps.nb,ts.nb,main="PR curve",lty=1,
        xlim=c(0,1),ylim=c(0,1),avg="vertical")
PRcurve(ps.orh,ts.orh,add=T,lty=2,avg='vertical')
legend('topright',c('NaiveBayes','ORh'),lty=1,col=c('black','grey'))

CRchart(ps.nb,ts.nb,main='Cumulative Recall curve',
        lty=1,xlim=c(0,1),ylim=c(0,1),avg='vertical')
CRchart(ps.orh,ts.orh,add=T,lty=2,avg='vertical')
legend('bottomright',c('NaiveBayes','ORh'),lty=1,col=c('black','grey'))

NBsm.wf <- function(form,train,test,C.perc="balance",dist="HEOM",...) {
    require(e1071,quietly=TRUE)
    require(UBL,quietly=TRUE)

    sup <- which(train$Insp != 'unkn')
    data <- as.data.frame(train[sup,c('ID','Prod','Uprice','Insp')])
    data$Insp <- factor(data$Insp,levels=c('ok','fraud'))
    newData <- SmoteClassif(Insp ~ .,data,C.perc=C.perc,dist=dist,...)
    model <- naiveBayes(Insp ~ .,newData)
    preds <- predict(model,test[,c('ID','Prod','Uprice','Insp')],type='raw')
    rankOrder <- order(preds[,'fraud'],decreasing=T)
    rankScore <- preds[,'fraud']
    
    res <- list(testSet=test,
              rankOrder=rankOrder,
              probs=as.matrix(cbind(rankScore,
                                    ifelse(test$Insp=='fraud',1,0))))
    res
}
nbs.res <- performanceEstimation(
    PredTask(Insp ~ ., sales),
    Workflow("NBsm.wf"),
    EstimationTask(metrics=c("Precision","Recall","avgNDTP"),
                   method=Holdout(nReps=3,hldSz=0.3,strat=TRUE),
                   evaluator="evalOutlierRanking",
                   evaluator.pars=list(Threshold=0.1,
                                       statsProds=globalStats))
    )


##### PERFORMANCE ESTIMATION USING  HOLD OUT  #####

** PREDICTIVE TASK :: sales.Insp

++ MODEL/WORKFLOW :: NBsm.wf 
Task for estimating  Precision,Recall,avgNDTP  using
Stratified  3 x 70 % / 30 % Holdout
     Run with seed =  1234 
Iteration :  1  2  3
summary(nbs.res) 

== Summary of a  Hold Out Performance Estimation Experiment ==

Task for estimating  Precision,Recall,avgNDTP  using
Stratified  3 x 70 % / 30 % Holdout
     Run with seed =  1234 

* Predictive Tasks ::  sales.Insp
* Workflows  ::  NBsm.wf 

-> Task:  sales.Insp
  *Workflow: NBsm.wf 
           Precision     Recall   avgNDTP
avg     0.0145205175 0.45877193 6.7723410
std     0.0009866931 0.03117431 0.8098474
med     0.0139930035 0.44210526 6.3507932
iqr     0.0008745627 0.02763158 0.7228941
min     0.0139097118 0.43947368 6.2602207
max     0.0156588372 0.49473684 7.7060090
invalid 0.0000000000 0.00000000 0.0000000
ps.nbs <- sapply(getIterationsInfo(nbs.res), function(i) i$probs[,1])
ts.nbs <- sapply(getIterationsInfo(nbs.res), function(i) i$probs[,2])
PRcurve(ps.nb,ts.nb,main="PR curve",lty=1,
        xlim=c(0,1),ylim=c(0,1), avg="vertical")
PRcurve(ps.orh,ts.orh,add=TRUE,lty=2, avg='vertical')
PRcurve(ps.nbs,ts.nbs,add=TRUE,lty=1, col='grey',avg='vertical')
legend('topright',c('NaiveBayes','ORh','smoteNaiveBayes'),lty=c(1,2,1),
       col=c('black','black','grey'))

CRchart(ps.nb,ts.nb,main='Cumulative Recall curve',
        lty=1,xlim=c(0,1),ylim=c(0,1),avg='vertical')
CRchart(ps.orh,ts.orh,add=TRUE,lty=2,avg='vertical')
CRchart(ps.nbs,ts.nbs,add=TRUE,lty=1,col='grey',avg='vertical')
legend('bottomright',c('NaiveBayes','ORh','smoteNaiveBayes'),lty=c(1,2,1),
       col=c('black','black','grey'))

par(mfrow=c(1,2)) 
ps.nbs <- sapply(getIterationsInfo(nbs.res),  function(i) i$probs[,1])
ts.nbs <- sapply(getIterationsInfo(nbs.res),  function(i) i$probs[,2])
PRcurve(ps.nb,ts.nb,main="PR curve",lty=1,
        xlim=c(0,1),ylim=c(0,1), avg="vertical")
PRcurve(ps.orh,ts.orh,add=T,lty=2,avg='vertical')
PRcurve(ps.nbs,ts.nbs,add=T,lty=1,col='grey',avg='vertical')
legend('topright',c('NaiveBayes','ORh','smoteNaiveBayes'),
       lty=c(1,2,1),col=c('black','black','grey'))

CRchart(ps.nb,ts.nb,main='Cumulative Recall curve',
        lty=1,xlim=c(0,1),ylim=c(0,1),avg='vertical')
CRchart(ps.orh,ts.orh,add=T,lty=2,avg='vertical')
CRchart(ps.nbs,ts.nbs,add=T,lty=1,col='grey',avg='vertical')
legend('bottomright',c('NaiveBayes','ORh','smoteNaiveBayes'),
       lty=c(1,2,1),col=c('black','black','grey'))

Semi-Supervised Approaches

library(DMwR2)
library(e1071)
data(iris) 
set.seed(1234)
idx <- sample(150, 100)
tr <- iris[idx, ]
ts <- iris[-idx, ]
nb <- naiveBayes(Species ~ ., tr)
table(predict(nb, ts), ts$Species)
            
             setosa versicolor virginica
  setosa         18          0         0
  versicolor      0         16         1
  virginica       0          2        13
trST <- tr
nas <- sample(100, 90)
trST[nas, "Species"] <- NA
func <- function(m, d) {
    p <- predict(m, d, type = "raw")
    data.frame(cl = colnames(p)[ apply(p, 1, which.max) ], 
               p = apply(p, 1, max))
}
nbSTbase <- naiveBayes(Species ~ ., trST[-nas, ])
table(predict(nbSTbase, ts), ts$Species)
            
             setosa versicolor virginica
  setosa         18          0         0
  versicolor      0         16         2
  virginica       0          2        12
nbST <- SelfTrain(Species ~ ., trST, 
                  learner="naiveBayes", learner.pars=list(),
                  pred="func")
table(predict(nbST, ts), ts$Species)
            
             setosa versicolor virginica
  setosa         18          0         0
  versicolor      0         16         1
  virginica       0          2        13
pred.nb <- function(m,d) {
    p <- predict(m,d,type='raw')
    data.frame(cl=colnames(p)[apply(p,1,which.max)],
               p=apply(p,1,max)
               )
}
 
nb.st.wf <- function(form,train,test,...) {
    require(e1071,quietly=TRUE)
    require(DMwR2, quietly=TRUE)
    train <- as.data.frame(train[,c('ID','Prod','Uprice','Insp')])
    train[which(train$Insp == 'unkn'),'Insp'] <- NA
    train$Insp <- factor(train$Insp,levels=c('ok','fraud'))
    model <- SelfTrain(form,train,
                       learner='naiveBayes', learner.pars=list(),
                       pred='pred.nb')
    preds <- predict(model,test[,c('ID','Prod','Uprice','Insp')],
                     type='raw')

    rankOrder <- order(preds[,'fraud'],decreasing=TRUE)
    rankScore <- preds[,"fraud"]
    
    res <- list(testSet=test,
              rankOrder=rankOrder,
              probs=as.matrix(cbind(rankScore,
                                    ifelse(test$Insp=='fraud',1,0))))
    res
}
nb.st.res <- performanceEstimation(
    PredTask(Insp ~ .,sales),
    Workflow("nb.st.wf"),
    EstimationTask(metrics=c("Precision","Recall","avgNDTP"),
                   method=Holdout(nReps=3,hldSz=0.3,strat=TRUE),
                   evaluator="evalOutlierRanking",
                   evaluator.pars=list(Threshold=0.1,
                                       statsProds=globalStats))
    )


##### PERFORMANCE ESTIMATION USING  HOLD OUT  #####

** PREDICTIVE TASK :: sales.Insp

++ MODEL/WORKFLOW :: nb.st.wf 
Task for estimating  Precision,Recall,avgNDTP  using
Stratified  3 x 70 % / 30 % Holdout
     Run with seed =  1234 
Iteration :  1  2  3
summary(nb.st.res)

== Summary of a  Hold Out Performance Estimation Experiment ==

Task for estimating  Precision,Recall,avgNDTP  using
Stratified  3 x 70 % / 30 % Holdout
     Run with seed =  1234 

* Predictive Tasks ::  sales.Insp
* Workflows  ::  nb.st.wf 

-> Task:  sales.Insp
  *Workflow: nb.st.wf 
           Precision      Recall   avgNDTP
avg     0.0132989061 0.420175439 7.1697293
std     0.0002544603 0.008039606 0.6635963
med     0.0132433783 0.418421053 6.9139197
iqr     0.0002498751 0.007894737 0.6255247
min     0.0130767949 0.413157895 6.6721094
max     0.0135765451 0.428947368 7.9231588
invalid 0.0000000000 0.000000000 0.0000000
ps.nb.st <- sapply(getIterationsInfo(nb.st.res), function(i) i$probs[,1])
ts.nb.st <- sapply(getIterationsInfo(nb.st.res), function(i) i$probs[,2])
PRcurve(ps.nb,ts.nb,main="PR curve",lty=1,
        xlim=c(0,1),ylim=c(0,1), avg="vertical")
PRcurve(ps.orh,ts.orh,add=TRUE,lty=1, color='grey', avg='vertical')
PRcurve(ps.nb.st,ts.nb.st,add=TRUE,lty=2,avg='vertical')
legend('topright',c('NaiveBayes','ORh','NaiveBayes-ST'),
       lty=c(1,1,2),col=c('black','grey','black'))

CRchart(ps.nb,ts.nb,main='Cumulative Recall curve',
        lty=1,xlim=c(0,1),ylim=c(0,1),avg='vertical')
CRchart(ps.orh,ts.orh,add=TRUE,lty=1,color='grey',avg='vertical')
CRchart(ps.nb.st,ts.nb.st,add=TRUE,lty=2,avg='vertical')
legend('bottomright',c('NaiveBayes','ORh','NaiveBayes-ST'),
       lty=c(1,1,2),col=c('black','grey','grey'))

par(mfrow=c(1,2)) 
ps.nb.st <- sapply(getIterationsInfo(nb.st.res), function(i) i$probs[,1])
ts.nb.st <- sapply(getIterationsInfo(nb.st.res), function(i) i$probs[,2])
PRcurve(ps.nb,ts.nb,main="PR curve",lty=1,
        xlim=c(0,1),ylim=c(0,1), avg="vertical")
PRcurve(ps.orh,ts.orh,add=T,lty=1, color='grey', avg='vertical')
PRcurve(ps.nb.st,ts.nb.st,add=T,lty=2,avg='vertical')
legend('topright',c('NaiveBayes','ORh','NaiveBayes-ST'),
       lty=c(1,1,2),col=c('black','grey','black'))

CRchart(ps.nb,ts.nb,main='Cumulative Recall curve',
        lty=1,xlim=c(0,1),ylim=c(0,1),avg='vertical')
CRchart(ps.orh,ts.orh,add=T,lty=1,color='grey',avg='vertical')
CRchart(ps.nb.st,ts.nb.st,add=T,lty=2,avg='vertical')
legend('bottomright',c('NaiveBayes','ORh','NaiveBayes-ST'),
       lty=c(1,1,2),col=c('black','grey','grey'))

LS0tCnRpdGxlOiAiRGV0ZWN0aW5nIEZyYXVkdWxlbnQgVHJhbnNhY3Rpb25zIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgoKCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQpkYXRhKHNhbGVzLCBwYWNrYWdlPSJETXdSMiIpCmBgYAoKYGBge3J9CnNhbGVzCmBgYAoKCgoKYGBge3J9CnN1bW1hcnkoc2FsZXMpCmBgYAoKCgpgYGB7cn0KbmxldmVscyhzYWxlcyRJRCkKbmxldmVscyhzYWxlcyRQcm9kKQpgYGAKCmBgYHtyfQpmaWx0ZXIoc2FsZXMsaXMubmEoUXVhbnQpLGlzLm5hKFZhbCkpCmBgYAoKV2UgY2FuIGxvb2sgYXQgdGhlIHByb3BvcnRpb25zIG9mIHRoZSBtaXNzaW5nIHZhbHVlcwpgYGB7cn0KdGFibGUoc2FsZXMkSW5zcCkvbnJvdyhzYWxlcykgKiAxMDAKYGBgCgpgYGB7cn0KbGlicmFyeShnZ3Bsb3QyKQpnZ3Bsb3QoZ3JvdXBfYnkoc2FsZXMsSUQpICU+JSBzdW1tYXJpemUoblRyYW5zPW4oKSksYWVzKHg9SUQseT1uVHJhbnMpKSArCiAgICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsgCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aWNrcy54PWVsZW1lbnRfYmxhbmsoKSkgKyAKICAgIHhsYWIoIlNhbGVzbWVuIikgKyB5bGFiKCJOci4gb2YgVHJhbnNhY3Rpb25zIikgKwogICAgZ2d0aXRsZSgiTnIuIG9mIFRyYW5zYWN0aW9ucyBwZXIgU2FsZXNtYW4iKQpnZ3Bsb3QoZ3JvdXBfYnkoc2FsZXMsUHJvZCkgJT4lIHN1bW1hcml6ZShuVHJhbnM9bigpKSxhZXMoeD1Qcm9kLHk9blRyYW5zKSkgKwogICAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArIAogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGlja3MueD1lbGVtZW50X2JsYW5rKCkpICsgCiAgICB4bGFiKCJQcm9kdWN0IikgKyB5bGFiKCJOci4gb2YgVHJhbnNhY3Rpb25zIikgKwogICAgZ2d0aXRsZSgiTnIuIG9mIFRyYW5zYWN0aW9ucyBwZXIgUHJvZHVjdCIpCmBgYApgYGB7cn0KZ2dwbG90KGdyb3VwX2J5KHNhbGVzLElEKSAlPiUgc3VtbWFyaXplKG5UcmFucz1uKCkpLGFlcyh4PUlELHk9blRyYW5zKSkgKwogICAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArIAogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGlja3MueD1lbGVtZW50X2JsYW5rKCkpICsgCiAgICB4bGFiKCJQcm9kdWN0IikgKyB5bGFiKCJOci4gb2YgVHJhbnNhY3Rpb25zIikgKwogICAgZ2d0aXRsZSgiTnIuIG9mIFRyYW5zYWN0aW9ucyBwZXIgU2FsZXNtYW4iKQpgYGAKYGBge3J9CmdncGxvdChncm91cF9ieShzYWxlcyxQcm9kKSAlPiUgc3VtbWFyaXplKG5UcmFucz1uKCkpLGFlcyh4PVByb2QseT1uVHJhbnMpKSArCiAgICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsgCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aWNrcy54PWVsZW1lbnRfYmxhbmsoKSkgKyAKICAgIHhsYWIoIlNhbGVzbWVuIikgKyB5bGFiKCJOci4gb2YgVHJhbnNhY3Rpb25zIikgKwogICAgZ2d0aXRsZSgiTnIuIG9mIFRyYW5zYWN0aW9ucyBwZXIgUHJvZHVjdCIpCmBgYAoKYGBge3J9CnNhbGVzIDwtIG11dGF0ZShzYWxlcyxVcHJpY2U9VmFsL1F1YW50KQpgYGAKCmBgYHtyfQpzYWxlcwpgYGAKCmBgYHtyfQpzdW1tYXJ5KHNhbGVzJFVwcmljZSkKYGBgCgpgYGB7cn0KcHJvZHMgPC0gZ3JvdXBfYnkoc2FsZXMsUHJvZCkKbXBQcm9kcyA8LSBzdW1tYXJpemUocHJvZHMsbWVkaWFuUHJpY2U9bWVkaWFuKFVwcmljZSxuYS5ybT1UUlVFKSkKYmluZF9jb2xzKG1wUHJvZHMgJT4lIGFycmFuZ2UobWVkaWFuUHJpY2UpICU+JSBzbGljZSgxOjUpLAogICAgICAgICAgbXBQcm9kcyAlPiUgYXJyYW5nZShkZXNjKG1lZGlhblByaWNlKSkgJT4lIHNsaWNlKDE6NSkpCmBgYAoKCmBgYHtyfQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZm9yY2F0cykKZ2dwbG90KGZpbHRlcihzYWxlcyxQcm9kICVpbiUgYygicDM2ODkiLCJwNTYwIikpLGFlcyh4PWZjdF9kcm9wKFByb2QpLHk9VXByaWNlKSkgKwogICAgZ2VvbV9ib3hwbG90KCkgKyBzY2FsZV95X2xvZzEwKCkgKyAKICAgIHhsYWIoIiIpICsgeWxhYigibG9nMTAoVW5pdFByaWNlKSIpCmBgYAoKYGBge3J9CmlkcyA8LSBncm91cF9ieShzYWxlcyxJRCkKdHZJRHMgPC0gc3VtbWFyaXplKGlkcyx0b3RhbFZhbD1zdW0oVmFsLG5hLnJtPVRSVUUpKQpiaW5kX2NvbHModHZJRHMgJT4lIGFycmFuZ2UodG90YWxWYWwpICU+JSBzbGljZSgxOjUpLAogICAgICAgICAgdHZJRHMgJT4lIGFycmFuZ2UoZGVzYyh0b3RhbFZhbCkpICU+JSBzbGljZSgxOjUpKQpgYGAKCmBgYHtyfQphcnJhbmdlKHR2SURzLGRlc2ModG90YWxWYWwpKSAlPiUgc2xpY2UoMToxMDApICU+JSAKICAgIHN1bW1hcml6ZSh0MTAwPXN1bSh0b3RhbFZhbCkpIC8gCiAgICAoc3VtbWFyaXplKHR2SURzLHN1bSh0b3RhbFZhbCkpKSAqIDEwMAphcnJhbmdlKHR2SURzLHRvdGFsVmFsKSAlPiUgc2xpY2UoMToyMDAwKSAlPiUgCiAgICBzdW1tYXJpemUoYjIwMDA9c3VtKHRvdGFsVmFsKSkgLyAKICAgIChzdW1tYXJpemUodHZJRHMsc3VtKHRvdGFsVmFsKSkpICogMTAwCmBgYAoKCmBgYHtyfQpwcm9kcyA8LSBncm91cF9ieShzYWxlcyxQcm9kKQpxdFByb2RzIDwtIHN1bW1hcml6ZShwcm9kcyx0b3RhbFF0eT1zdW0oUXVhbnQsbmEucm09VFJVRSkpCmJpbmRfY29scyhxdFByb2RzICU+JSBhcnJhbmdlKGRlc2ModG90YWxRdHkpKSAlPiUgc2xpY2UoMTo1KSwKICAgICAgICAgIHF0UHJvZHMgJT4lIGFycmFuZ2UodG90YWxRdHkpICU+JSBzbGljZSgxOjUpKQpgYGAKCmBgYHtyfQphcnJhbmdlKHF0UHJvZHMsZGVzYyh0b3RhbFF0eSkpICU+JSBzbGljZSgxOjEwMCkgJT4lIAogICAgc3VtbWFyaXplKHQxMDA9c3VtKGFzLm51bWVyaWModG90YWxRdHkpKSkgLyAKICAgIChzdW1tYXJpemUocXRQcm9kcyxzdW0oYXMubnVtZXJpYyh0b3RhbFF0eSkpKSkgKiAxMDAKYXJyYW5nZShxdFByb2RzLHRvdGFsUXR5KSAlPiUgc2xpY2UoMTo0MDAwKSAlPiUgCiAgICBzdW1tYXJpemUoYjQwMDA9c3VtKGFzLm51bWVyaWModG90YWxRdHkpKSkgLyAKICAgIChzdW1tYXJpemUocXRQcm9kcyxzdW0oYXMubnVtZXJpYyh0b3RhbFF0eSkpKSkgKiAxMDAKYGBgCgpgYGB7cn0Kbm91dHMgPC0gZnVuY3Rpb24oeCkgbGVuZ3RoKGJveHBsb3Quc3RhdHMoeCkkb3V0KQpub3V0c1Byb2RzIDwtIHN1bW1hcmlzZShwcm9kcyxuT3V0PW5vdXRzKFVwcmljZSkpCmBgYAoKCmBgYHtyfQphcnJhbmdlKG5vdXRzUHJvZHMsZGVzYyhuT3V0KSkKYGBgCgoKYGBge3J9CnN1bW1hcml6ZShub3V0c1Byb2RzLHRvdGFsT3V0cz1zdW0obk91dCkpCnN1bW1hcml6ZShub3V0c1Byb2RzLHRvdGFsT3V0cz1zdW0obk91dCkpL25yb3coc2FsZXMpKjEwMApgYGAKCgpgYGB7cn0KcHJvcC5uYVFhbmRWIDwtIGZ1bmN0aW9uKHEsdikgMTAwKnN1bShpcy5uYShxKSAmIGlzLm5hKHYpKS9sZW5ndGgocSkKc3VtbWFyaXNlKGlkcyxuUHJvYnM9cHJvcC5uYVFhbmRWKFF1YW50LFZhbCkpICU+JSBhcnJhbmdlKGRlc2MoblByb2JzKSkKYGBgCgoKYGBge3J9CnN1bW1hcmlzZShwcm9kcyxuUHJvYnM9cHJvcC5uYVFhbmRWKFF1YW50LFZhbCkpICU+JSBhcnJhbmdlKGRlc2MoblByb2JzKSkKYGBgCmBgYHtyfQpzYWxlcyA8LSBmaWx0ZXIoc2FsZXMsIShpcy5uYShRdWFudCkgJiBpcy5uYShWYWwpKSkKYGBgCgpgYGB7cn0KcHJvcC5uYXMgPC0gZnVuY3Rpb24oeCkgMTAwKnN1bShpcy5uYSh4KSkvbGVuZ3RoKHgpCnN1bW1hcmlzZShwcm9kcyxwcm9wTkEuUT1wcm9wLm5hcyhRdWFudCkpICU+JSBhcnJhbmdlKGRlc2MocHJvcE5BLlEpKQpgYGAKCmBgYHtyfQpmaWx0ZXIoc2FsZXMsIFByb2QgJWluJSBjKCJwMjQ0MiIsInAyNDQzIikpICU+JSAKICAgIGdyb3VwX2J5KEluc3ApICU+JSBjb3VudCgpCmBgYAoKYGBge3J9CnNhbGVzIDwtIGRyb3BsZXZlbHMoZmlsdGVyKHNhbGVzLCEoUHJvZCAlaW4lIGMoInAyNDQyIiwgInAyNDQzIikpKSkKYGBgCgpgYGB7cn0Kc3VtbWFyaXNlKGlkcyxwcm9wTkEuUT1wcm9wLm5hcyhRdWFudCkpICU+JSBhcnJhbmdlKGRlc2MocHJvcE5BLlEpKQpgYGAKCmBgYHtyfQpzdW1tYXJpc2UocHJvZHMscHJvcE5BLlY9cHJvcC5uYXMoVmFsKSkgJT4lIGFycmFuZ2UoZGVzYyhwcm9wTkEuVikpCmBgYAoKYGBge3J9CnN1bW1hcmlzZShpZHMscHJvcE5BLlY9cHJvcC5uYXMoVmFsKSkgJT4lIGFycmFuZ2UoZGVzYyhwcm9wTkEuVikpCmBgYAoKYGBge3J9CnRQcmljZSA8LSBmaWx0ZXIoc2FsZXMsIEluc3AgIT0gImZyYXVkIikgJT4lIAogICAgICAgICAgZ3JvdXBfYnkoUHJvZCkgJT4lIAogICAgICAgICAgc3VtbWFyaXNlKG1lZGlhblByaWNlID0gbWVkaWFuKFVwcmljZSxuYS5ybT1UUlVFKSkKYGBgCgpgYGB7cn0Kbm9RdWFudE1lZFByaWNlcyA8LSBmaWx0ZXIoc2FsZXMsIGlzLm5hKFF1YW50KSkgJT4lIAogICAgaW5uZXJfam9pbih0UHJpY2UpICU+JSAKICAgIHNlbGVjdChtZWRpYW5QcmljZSkKbm9WYWxNZWRQcmljZXMgPC0gZmlsdGVyKHNhbGVzLCBpcy5uYShWYWwpKSAlPiUgCiAgICBpbm5lcl9qb2luKHRQcmljZSkgJT4lIAogICAgc2VsZWN0KG1lZGlhblByaWNlKQoKbm9RdWFudCA8LSB3aGljaChpcy5uYShzYWxlcyRRdWFudCkpCm5vVmFsIDwtIHdoaWNoKGlzLm5hKHNhbGVzJFZhbCkpCnNhbGVzW25vUXVhbnQsJ1F1YW50J10gPC0gY2VpbGluZyhzYWxlc1tub1F1YW50LCdWYWwnXSAvbm9RdWFudE1lZFByaWNlcykKc2FsZXNbbm9WYWwsJ1ZhbCddIDwtIHNhbGVzW25vVmFsLCdRdWFudCddICogbm9WYWxNZWRQcmljZXMKYGBgCgpgYGB7cn0Kc2FsZXMkVXByaWNlIDwtIHNhbGVzJFZhbC9zYWxlcyRRdWFudApgYGAKCmBgYHtyfQpzYXZlKHNhbGVzLCBmaWxlID0gInNhbGVzQ2xlYW4uUmRhdGEiKQpgYGAKCmBgYHtyfQptcyA8LSBmaWx0ZXIoc2FsZXMsSW5zcCAhPSAiZnJhdWQiKSAlPiUgCiAgICBncm91cF9ieShQcm9kKSAlPiUgCiAgICBzdW1tYXJpemUobWVkaWFuPW1lZGlhbihVcHJpY2UsbmEucm09VFJVRSksCiAgICAgICAgICAgICAgaXFyPUlRUihVcHJpY2UsbmEucm09VFJVRSksCiAgICAgICAgICAgICAgblRyYW5zPW4oKSwKICAgICAgICAgICAgICBmZXdUcmFucz1pZmVsc2UoblRyYW5zPjIwLEZBTFNFLFRSVUUpKQptcwpgYGAKClByb3BlcnRpZXMgb2YgdGhlIGRpc3RyaWJ1dGlvbiBvZiB1bml0IHByaWNlcwpgYGB7cn0KZ2dwbG90KG1zLGFlcyh4PW1lZGlhbix5PWlxcixjb2xvcj1mZXdUcmFucykpICsgCiAgICBnZW9tX3BvaW50KCkgKyAKICAgIHhsYWIoIk1lZGlhbiIpICsgeWxhYigiSVFSIikKZ2dwbG90KG1zLGFlcyh4PW1lZGlhbix5PWlxcixjb2xvcj1mZXdUcmFucykpICsgCiAgICBnZW9tX3BvaW50KCkgKyAKICAgIHNjYWxlX3lfbG9nMTAoKSArIHNjYWxlX3hfbG9nMTAoKSArIAogICAgeGxhYigibG9nKE1lZGlhbikiKSArIHlsYWIoImxvZyhJUVIpIikKYGBgCgpgYGB7cn0KbXMgPC0gbXV0YXRlKG1zLHNtZWRpYW49c2NhbGUobWVkaWFuKSxzaXFyPXNjYWxlKGlxcikpCnNtYWxscyA8LSB3aGljaChtcyRmZXdUcmFucykKbnNtYWxscyA8LSBhcy5jaGFyYWN0ZXIobXMkUHJvZFtzbWFsbHNdKQpzaW1pbGFyIDwtIG1hdHJpeChOQSxsZW5ndGgoc21hbGxzKSw3LAogICAgZGltbmFtZXM9bGlzdChuc21hbGxzLAogICAgICBjKCJSb3dTaW1Qcm9kIiwgImtzLnN0YXQiLCAia3MucCIsICJtZWRQIiwgImlxclAiLCAibWVkUyIsImlxclMiKSkpCnhwcm9kcyA8LSB0YXBwbHkoc2FsZXMkVXByaWNlLCBzYWxlcyRQcm9kLCBsaXN0KQpmb3IoaSBpbiBzZXFfYWxvbmcoc21hbGxzKSkgewogICAgZCA8LSBzY2FsZShtc1ssYygic21lZGlhbiIsInNpcXIiKV0sCiAgICAgICAgICAgICAgIGMobXMkc21lZGlhbltzbWFsbHNbaV1dLG1zJHNpcXJbc21hbGxzW2ldXSksCiAgICAgICAgICAgICAgIEZBTFNFKQogICAgZCA8LSBzcXJ0KGRyb3AoZF4yICUqJSByZXAoMSwgbmNvbChkKSkpKQogICAgc3RhdCA8LSBrcy50ZXN0KHhwcm9kc1tbbnNtYWxsc1tpXV1dLCB4cHJvZHNbW29yZGVyKGQpWzJdXV0pCiAgICBzaW1pbGFyW2ksIF0gPC0gYyhvcmRlcihkKVsyXSwgc3RhdCRzdGF0aXN0aWMsIHN0YXQkcC52YWx1ZSwKICAgICAgICAgICAgICAgICAgICAgIG1zJG1lZGlhbltzbWFsbHNbaV1dLG1zJGlxcltzbWFsbHNbaV1dLAogICAgICAgICAgICAgICAgICAgICAgbXMkbWVkaWFuW29yZGVyKGQpWzJdXSxtcyRpcXJbb3JkZXIoZClbMl1dKQp9CmBgYAoKYGBge3J9CmhlYWQoc2ltaWxhcikKYGBgCgpgYGB7cn0KYmluZF9yb3dzKGZpbHRlcihtcyxQcm9kPT1yb3duYW1lcyhzaW1pbGFyKVsxXSksCiAgICAgICAgICBtc1tzaW1pbGFyWzEsMV0sXSkKYGBgCmBgYHtyfQpucm93KHNpbWlsYXJbc2ltaWxhclssICJrcy5wIl0gPj0gMC45LCBdKQpgYGAKCmBgYHtyfQpzdW0oc2ltaWxhclssICJrcy5wIl0gPj0gMC45KQpgYGAKCmBgYHtyfQpzYXZlKHNpbWlsYXIsIGZpbGUgPSAic2ltaWxhclByb2R1Y3RzLlJkYXRhIikKYGBgCgoKIyBFdmFsdWF0aW9uIENyaXRlcmlhCgpgYGB7cn0KbGlicmFyeShST0NSKQpkYXRhKFJPQ1Iuc2ltcGxlKQpwcmVkIDwtIHByZWRpY3Rpb24oUk9DUi5zaW1wbGUkcHJlZGljdGlvbnMsIFJPQ1Iuc2ltcGxlJGxhYmVscykKcGVyZiA8LSBwZXJmb3JtYW5jZShwcmVkLCAicHJlYyIsICJyZWMiKQpwbG90KHBlcmYpCmBgYAoKYGBge3J9ClBSY3VydmUgPC0gZnVuY3Rpb24ocHJlZHMsIHRydWVzLCAuLi4pIHsKICAgIHJlcXVpcmUoUk9DUiwgcXVpZXRseSA9IFRSVUUpCiAgICBwZCA8LSBwcmVkaWN0aW9uKHByZWRzLCB0cnVlcykKICAgIHBmIDwtIHBlcmZvcm1hbmNlKHBkLCAicHJlYyIsICJyZWMiKQogICAgcGZAeS52YWx1ZXMgPC0gbGFwcGx5KHBmQHkudmFsdWVzLCBmdW5jdGlvbih4KSByZXYoY3VtbWF4KHJldih4KSkpKQogICAgcGxvdChwZiwgLi4uKQp9CmBgYAoKYGBge3J9ClBSY3VydmUoUk9DUi5zaW1wbGUkcHJlZGljdGlvbnMsIFJPQ1Iuc2ltcGxlJGxhYmVscykKYGBgCgpgYGB7cn0KbGlicmFyeShST0NSKQpkYXRhKFJPQ1Iuc2ltcGxlKQpwcmVkIDwtIHByZWRpY3Rpb24oUk9DUi5zaW1wbGUkcHJlZGljdGlvbnMsIFJPQ1Iuc2ltcGxlJGxhYmVscykKcGVyZiA8LSBwZXJmb3JtYW5jZShwcmVkLCAicHJlYyIsICJyZWMiKQpwYXIoIG1mcm93PWMoMSwyKSApCnBsb3QocGVyZikKUFJjdXJ2ZShST0NSLnNpbXBsZSRwcmVkaWN0aW9ucywgUk9DUi5zaW1wbGUkbGFiZWxzKQpwYXIoIG1mcm93PWMoMSwxKSApCmBgYAoKYGBge3J9CnByZWQgPC0gcHJlZGljdGlvbihST0NSLnNpbXBsZSRwcmVkaWN0aW9ucywgUk9DUi5zaW1wbGUkbGFiZWxzKQpwZXJmIDwtIHBlcmZvcm1hbmNlKHByZWQsICJsaWZ0IiwgInJwcCIpCnBsb3QocGVyZiwgbWFpbiA9ICJMaWZ0IENoYXJ0IikKYGBgCgpgYGB7cn0KQ1JjaGFydCA8LSBmdW5jdGlvbihwcmVkcywgdHJ1ZXMsIC4uLikgewogICAgcmVxdWlyZShST0NSLCBxdWlldGx5ID0gVCkKICAgIHBkIDwtIHByZWRpY3Rpb24ocHJlZHMsIHRydWVzKQogICAgcGYgPC0gcGVyZm9ybWFuY2UocGQsICJyZWMiLCAicnBwIikKICAgIHBsb3QocGYsIC4uLikKfQpgYGAKCmBgYHtyfQpDUmNoYXJ0KFJPQ1Iuc2ltcGxlJHByZWRpY3Rpb25zLCBST0NSLnNpbXBsZSRsYWJlbHMsIAogICAgICAgIG1haW49J0N1bXVsYXRpdmUgUmVjYWxsIENoYXJ0JykKYGBgCgpgYGB7cn0KYXZnTkRUUCA8LSBmdW5jdGlvbih0b0luc3AsdHJhaW4sc3RhdHMpIHsKICBpZiAobWlzc2luZyh0cmFpbikgJiYgbWlzc2luZyhzdGF0cykpIAogICAgICBzdG9wKCdQcm92aWRlIGVpdGhlciB0aGUgdHJhaW5pbmcgZGF0YSBvciB0aGUgcHJvZHVjdCBzdGF0cycpCiAgaWYgKG1pc3Npbmcoc3RhdHMpKSB7CiAgICAgIHN0YXRzIDwtIGFzLm1hdHJpeChmaWx0ZXIodHJhaW4sSW5zcCAhPSAnZnJhdWQnKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwX2J5KFByb2QpICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyaXNlKG1lZGlhbj1tZWRpYW4oVXByaWNlKSxpcXI9SVFSKFVwcmljZSkpICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgc2VsZWN0KG1lZGlhbixpcXIpKQogICAgICByb3duYW1lcyhzdGF0cykgPC0gbGV2ZWxzKHRyYWluJFByb2QpCiAgICAgIHN0YXRzW3doaWNoKHN0YXRzWywnaXFyJ109PTApLCdpcXInXSA8LSBzdGF0c1t3aGljaChzdGF0c1ssJ2lxciddPT0wKSwnbWVkaWFuJ10KICB9CiAgCiAgcmV0dXJuKG1lYW4oYWJzKHRvSW5zcCRVcHJpY2Utc3RhdHNbdG9JbnNwJFByb2QsJ21lZGlhbiddKSAvCiAgICAgICAgICAgICAgICAgc3RhdHNbdG9JbnNwJFByb2QsJ2lxciddKSkKfQoKYGBgCgoKCgpgYGB7cn0KZXZhbE91dGxpZXJSYW5raW5nIDwtIGZ1bmN0aW9uKHRlc3RTZXQscmFua09yZGVyLFRocmVzaG9sZCxzdGF0c1Byb2RzLC4uLikgCnsKICAgb3JkVFMgPC0gdGVzdFNldFtyYW5rT3JkZXIsXQogICBOIDwtIG5yb3codGVzdFNldCkKICAgbkYgPC0gaWYgKFRocmVzaG9sZCA8IDEpIGFzLmludGVnZXIoVGhyZXNob2xkKk4pIGVsc2UgVGhyZXNob2xkCiAgIGNtIDwtIHRhYmxlKGMocmVwKCdmcmF1ZCcsbkYpLHJlcCgnb2snLE4tbkYpKSxvcmRUUyRJbnNwKQogICBwcmVjIDwtIGNtWydmcmF1ZCcsJ2ZyYXVkJ10vc3VtKGNtWydmcmF1ZCcsXSkKICAgcmVjIDwtIGNtWydmcmF1ZCcsJ2ZyYXVkJ10vc3VtKGNtWywnZnJhdWQnXSkKICAgQVZHbmR0cCA8LSBhdmdORFRQKG9yZFRTWzE6bkYsXSxzdGF0cz1zdGF0c1Byb2RzKQogICByZXR1cm4oYyhQcmVjaXNpb249cHJlYyxSZWNhbGw9cmVjLGF2Z05EVFA9QVZHbmR0cCkpCn0KYGBgCgoKCmBgYHtyfQpCUHJ1bGUud2YgPC0gZnVuY3Rpb24oZm9ybSx0cmFpbix0ZXN0LC4uLikgewogICAgcmVxdWlyZShkcGx5ciwgcXVpZXRseT1UUlVFKQogICAgbXMgPC0gYXMubWF0cml4KGZpbHRlcih0cmFpbixJbnNwICE9ICdmcmF1ZCcpICU+JQogICAgICAgICAgICAgICAgICAgIGdyb3VwX2J5KFByb2QpICU+JQogICAgICAgICAgICAgICAgICAgIHN1bW1hcmlzZShtZWRpYW49bWVkaWFuKFVwcmljZSksaXFyPUlRUihVcHJpY2UpKSAlPiUKICAgICAgICAgICAgICAgICAgICBzZWxlY3QobWVkaWFuLGlxcikpCiAgICByb3duYW1lcyhtcykgPC0gbGV2ZWxzKHRyYWluJFByb2QpCiAgICBtc1t3aGljaChtc1ssJ2lxciddPT0wKSwnaXFyJ10gPC0gbXNbd2hpY2gobXNbLCdpcXInXT09MCksJ21lZGlhbiddCiAgICBPUnNjb3JlIDwtIGFicyh0ZXN0JFVwcmljZS1tc1t0ZXN0JFByb2QsJ21lZGlhbiddKSAvCiAgICAgICAgICAgICAgIG1zW3Rlc3QkUHJvZCwnaXFyJ10KICAgIHJhbmtPcmRlciA8LSBvcmRlcihPUnNjb3JlLGRlY3JlYXNpbmc9VFJVRSkKICAgIHJlcyA8LSBsaXN0KHRlc3RTZXQ9dGVzdCxyYW5rT3JkZXI9cmFua09yZGVyLAogICAgICAgICAgICAgICAgcHJvYnM9bWF0cml4KGMoT1JzY29yZSxpZmVsc2UodGVzdCRJbnNwPT0nZnJhdWQnLDEsMCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5jb2w9MikpCiAgICByZXMKfQpgYGAKCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQpnbG9iYWxTdGF0cyA8LSBhcy5tYXRyaXgoZmlsdGVyKHNhbGVzLEluc3AgIT0gJ2ZyYXVkJykgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICBncm91cF9ieShQcm9kKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgIHN1bW1hcmlzZShtZWRpYW49bWVkaWFuKFVwcmljZSksaXFyPUlRUihVcHJpY2UpKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdChtZWRpYW4saXFyKSkKcm93bmFtZXMoZ2xvYmFsU3RhdHMpIDwtIGxldmVscyhzYWxlcyRQcm9kKQpnbG9iYWxTdGF0c1t3aGljaChnbG9iYWxTdGF0c1ssJ2lxciddPT0wKSwnaXFyJ10gPC0gCiAgICBnbG9iYWxTdGF0c1t3aGljaChnbG9iYWxTdGF0c1ssJ2lxciddPT0wKSwnbWVkaWFuJ10KaGVhZChnbG9iYWxTdGF0cywzKQoKYGBgCgoKYGBge3J9CmxpYnJhcnkocGVyZm9ybWFuY2VFc3RpbWF0aW9uKQpicC5yZXMgPC0gcGVyZm9ybWFuY2VFc3RpbWF0aW9uKAogICAgUHJlZFRhc2soSW5zcCB+IC4sIHNhbGVzKSwKICAgIFdvcmtmbG93KCJCUHJ1bGUud2YiKSwKICAgIEVzdGltYXRpb25UYXNrKG1ldHJpY3M9YygiUHJlY2lzaW9uIiwiUmVjYWxsIiwiYXZnTkRUUCIpLAogICAgICAgICAgICAgICAgICAgbWV0aG9kPUhvbGRvdXQoblJlcHM9MywgaGxkU3o9MC4zLCBzdHJhdD1UUlVFKSwKICAgICAgICAgICAgICAgICAgIGV2YWx1YXRvcj0iZXZhbE91dGxpZXJSYW5raW5nIiwKICAgICAgICAgICAgICAgICAgIGV2YWx1YXRvci5wYXJzPWxpc3QoVGhyZXNob2xkPTAuMSwgc3RhdHNQcm9kcz1nbG9iYWxTdGF0cykpCikKYGBgCgpgYGB7cn0Kc3VtbWFyeShicC5yZXMpCgpgYGAKCmBgYHtyfQpwcy5icCA8LSBzYXBwbHkoZ2V0SXRlcmF0aW9uc0luZm8oYnAucmVzKSxmdW5jdGlvbihpKSBpJHByb2JzWywxXSkKdHMuYnAgPC0gc2FwcGx5KGdldEl0ZXJhdGlvbnNJbmZvKGJwLnJlcyksZnVuY3Rpb24oaSkgaSRwcm9ic1ssMl0pClBSY3VydmUocHMuYnAsdHMuYnAsbWFpbj0iUFIgY3VydmUiLGF2Zz0idmVydGljYWwiKQpDUmNoYXJ0KHBzLmJwLHRzLmJwLG1haW49J0N1bXVsYXRpdmUgUmVjYWxsIGN1cnZlJyxhdmc9J3ZlcnRpY2FsJykKYGBgCgpgYGB7cn0KcGFyKG1mcm93PWMoMSwyKSkgCnBzLmJwIDwtIHNhcHBseShnZXRJdGVyYXRpb25zSW5mbyhicC5yZXMpLCBmdW5jdGlvbihpKSBpJHByb2JzWywxXSkKdHMuYnAgPC0gc2FwcGx5KGdldEl0ZXJhdGlvbnNJbmZvKGJwLnJlcyksIGZ1bmN0aW9uKGkpIGkkcHJvYnNbLDJdKQpQUmN1cnZlKHBzLmJwLCB0cy5icCwgbWFpbj0iUFIgY3VydmUiLCBhdmc9InZlcnRpY2FsIikKQ1JjaGFydChwcy5icCwgdHMuYnAsIG1haW49J0N1bXVsYXRpdmUgUmVjYWxsIGN1cnZlJywgYXZnPSd2ZXJ0aWNhbCcpCmBgYAoKCmBgYHtyfQpMT0Yud2YgPC0gZnVuY3Rpb24oZm9ybSwgdHJhaW4sIHRlc3QsIGssIC4uLikgewogICAgcmVxdWlyZShETXdSMiwgcXVpZXRseT1UUlVFKQogICAgbnRyIDwtIG5yb3codHJhaW4pCiAgICBhbGwgPC0gYXMuZGF0YS5mcmFtZShyYmluZCh0cmFpbix0ZXN0KSkKICAgIE4gPC0gbnJvdyhhbGwpCiAgICB1cHMgPC0gc3BsaXQoYWxsJFVwcmljZSxhbGwkUHJvZCkKICAgIHIgPC0gbGlzdChsZW5ndGg9dXBzKQogICAgZm9yKHUgaW4gc2VxKGFsb25nPXVwcykpIAogICAgICAgIHJbW3VdXSA8LSBpZiAoTlJPVyh1cHNbW3VdXSkgPiAzKSAKICAgICAgICAgICAgICAgICAgICAgIGxvZmFjdG9yKHVwc1tbdV1dLG1pbihrLE5ST1codXBzW1t1XV0pICUvJSAyKSkgCiAgICAgICAgICAgICAgICAgIGVsc2UgaWYgKE5ST1codXBzW1t1XV0pKSByZXAoMCxOUk9XKHVwc1tbdV1dKSkgCiAgICAgICAgICAgICAgICAgIGVsc2UgTlVMTAogICAgYWxsJGxvZiA8LSB2ZWN0b3IobGVuZ3RoPU4pCiAgICBzcGxpdChhbGwkbG9mLGFsbCRQcm9kKSA8LSByCiAgICBhbGwkbG9mW3doaWNoKCEoaXMuaW5maW5pdGUoYWxsJGxvZikgfCBpcy5uYW4oYWxsJGxvZikpKV0gPC0gCiAgICAgICAgU29mdE1heChhbGwkbG9mW3doaWNoKCEoaXMuaW5maW5pdGUoYWxsJGxvZikgfCBpcy5uYW4oYWxsJGxvZikpKV0pCiAgICAKICAgIHJlcyA8LSBsaXN0KHRlc3RTZXQ9dGVzdCwKICAgICAgICAgICAgICAgIHJhbmtPcmRlcj1vcmRlcihhbGxbKG50cisxKTpOLCdsb2YnXSxkZWNyZWFzaW5nPVRSVUUpLAogICAgICAgICAgICAgICAgcHJvYnM9YXMubWF0cml4KGNiaW5kKGFsbFsobnRyKzEpOk4sJ2xvZiddLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZSh0ZXN0JEluc3A9PSdmcmF1ZCcsMSwwKSkpKQogICAgcmVzCn0KYGBgCgoKYGBge3J9CmxvZi5yZXMgPC0gcGVyZm9ybWFuY2VFc3RpbWF0aW9uKAogICAgUHJlZFRhc2soSW5zcCB+IC4gLCBzYWxlcyksCiAgICBXb3JrZmxvdygiTE9GLndmIiwgaz03KSwKICAgIEVzdGltYXRpb25UYXNrKG1ldHJpY3M9YygiUHJlY2lzaW9uIiwiUmVjYWxsIiwiYXZnTkRUUCIpLAogICAgICAgICAgICAgICAgICAgbWV0aG9kPUhvbGRvdXQoblJlcHM9MywgaGxkU3o9MC4zLCBzdHJhdD1UUlVFKSwKICAgICAgICAgICAgICAgICAgIGV2YWx1YXRvcj0iZXZhbE91dGxpZXJSYW5raW5nIiwKICAgICAgICAgICAgICAgICAgIGV2YWx1YXRvci5wYXJzPWxpc3QoVGhyZXNob2xkPTAuMSwgc3RhdHNQcm9kcz1nbG9iYWxTdGF0cykpCiAgICApCgpgYGAKCmBgYHtyfQoKCgpgYGAKCmBgYHtyfQpzdW1tYXJ5KGxvZi5yZXMpCmBgYApgYGB7cn0KCmBgYAoKYGBge3J9CnBzLmxvZiA8LSBzYXBwbHkoZ2V0SXRlcmF0aW9uc0luZm8obG9mLnJlcyksIGZ1bmN0aW9uKGkpIGkkcHJvYnNbLDFdKSAKdHMubG9mIDwtIHNhcHBseShnZXRJdGVyYXRpb25zSW5mbyhsb2YucmVzKSwgZnVuY3Rpb24oaSkgaSRwcm9ic1ssMl0pCgpQUmN1cnZlKHBzLmJwLHRzLmJwLG1haW49IlBSIGN1cnZlIixsdHk9MSwKICAgICAgICB4bGltPWMoMCwxKSx5bGltPWMoMCwxKSxhdmc9InZlcnRpY2FsIikKCiNQUmN1cnZlKHBzLmxvZix0cy5sb2YsYWRkPVRSVUUsbHR5PTIsYXZnPSd2ZXJ0aWNhbCcpCmxlZ2VuZCgndG9wcmlnaHQnLGMoJ0JQcnVsZScsJ0xPRicpLGx0eT1jKDEsMikpCgpDUmNoYXJ0KHBzLmJwLHRzLmJwLG1haW49J0N1bXVsYXRpdmUgUmVjYWxsIGN1cnZlJywKICAgICAgICBsdHk9MSx4bGltPWMoMCwxKSx5bGltPWMoMCwxKSxhdmc9J3ZlcnRpY2FsJykKI0NSY2hhcnQocHMubG9mLHRzLmxvZixhZGQ9VFJVRSxsdHk9Mixhdmc9J3ZlcnRpY2FsJykKbGVnZW5kKCdib3R0b21yaWdodCcsYygnQlBydWxlJywnTE9GJyksbHR5PWMoMSwyKSkKYGBgCgpgYGB7cn0KcGFyKG1mcm93PWMoMSwyKSkgCnBzLmxvZiA8LSBzYXBwbHkoZ2V0SXRlcmF0aW9uc0luZm8obG9mLnJlcyksIGZ1bmN0aW9uKGkpIGkkcHJvYnNbLDFdKQp0cy5sb2YgPC0gc2FwcGx5KGdldEl0ZXJhdGlvbnNJbmZvKGxvZi5yZXMpLCBmdW5jdGlvbihpKSBpJHByb2JzWywyXSkKUFJjdXJ2ZShwcy5icCwgdHMuYnAsbWFpbj0iUFIgY3VydmUiLGx0eT0xLAogICAgICAgIHhsaW09YygwLDEpLHlsaW09YygwLDEpLGF2Zz0idmVydGljYWwiKQojUFJjdXJ2ZShwcy5sb2YsIHRzLmxvZixhZGQ9VCxsdHk9Mixhdmc9J3ZlcnRpY2FsJykKbGVnZW5kKCd0b3ByaWdodCcsYygnQlBydWxlJywnTE9GJyksbHR5PWMoMSwyKSkKQ1JjaGFydChwcy5icCx0cy5icCxtYWluPSdDdW11bGF0aXZlIFJlY2FsbCBjdXJ2ZScsCiAgICAgICAgbHR5PTEseGxpbT1jKDAsMSkseWxpbT1jKDAsMSksYXZnPSd2ZXJ0aWNhbCcpCiNDUmNoYXJ0KHBzLmxvZix0cy5sb2YsYWRkPVQsbHR5PTIsYXZnPSd2ZXJ0aWNhbCcpCmxlZ2VuZCgnYm90dG9tcmlnaHQnLGMoJ0JQcnVsZScsJ0xPRicpLGx0eT1jKDEsMikpCmBgYAoKYGBge3J9Ck9SaC53ZiA8LSBmdW5jdGlvbihmb3JtLCB0cmFpbiwgdGVzdCwgLi4uKSB7CiAgICByZXF1aXJlKERNd1IyLCBxdWlldGx5PVRSVUUpCiAgICBudHIgPC0gbnJvdyh0cmFpbikKICAgIGFsbCA8LSBhcy5kYXRhLmZyYW1lKHJiaW5kKHRyYWluLHRlc3QpKQogICAgTiA8LSBucm93KGFsbCkKICAgIHVwcyA8LSBzcGxpdChhbGwkVXByaWNlLGFsbCRQcm9kKQogICAgciA8LSBsaXN0KGxlbmd0aD11cHMpCiAgICBmb3IodSBpbiBzZXEoYWxvbmc9dXBzKSkgCiAgICAgICAgcltbdV1dIDwtIGlmIChOUk9XKHVwc1tbdV1dKSA+IDMpIAogICAgICAgICAgICAgICAgICAgICAgb3V0bGllcnMucmFua2luZyh1cHNbW3VdXSkkcHJvYi5vdXRsaWVycwogICAgICAgICAgICAgICAgICBlbHNlIGlmIChOUk9XKHVwc1tbdV1dKSkgcmVwKDAsTlJPVyh1cHNbW3VdXSkpIAogICAgICAgICAgICAgICAgICBlbHNlIE5VTEwKICAgIGFsbCRvcmggPC0gdmVjdG9yKGxlbmd0aD1OKQogICAgc3BsaXQoYWxsJG9yaCxhbGwkUHJvZCkgPC0gcgogICAgYWxsJG9yaFt3aGljaCghKGlzLmluZmluaXRlKGFsbCRvcmgpIHwgaXMubmFuKGFsbCRvcmgpKSldIDwtIAogICAgICAgIFNvZnRNYXgoYWxsJG9yaFt3aGljaCghKGlzLmluZmluaXRlKGFsbCRvcmgpIHwgaXMubmFuKGFsbCRvcmgpKSldKQogICAgcmVzIDwtIGxpc3QodGVzdFNldD10ZXN0LAogICAgICAgICAgICAgICAgcmFua09yZGVyPW9yZGVyKGFsbFsobnRyKzEpOk4sJ29yaCddLGRlY3JlYXNpbmc9VFJVRSksCiAgICAgICAgICAgICAgICBwcm9icz1hcy5tYXRyaXgoY2JpbmQoYWxsWyhudHIrMSk6Tiwnb3JoJ10sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHRlc3QkSW5zcD09J2ZyYXVkJywxLDApKSkpCiAgICByZXMKICAgIAp9CmBgYAoKYGBge3J9Cm9yaC5yZXMgPC0gcGVyZm9ybWFuY2VFc3RpbWF0aW9uKAogICAgUHJlZFRhc2soSW5zcCB+IC4gLCBzYWxlcyksCiAgICBXb3JrZmxvdygiT1JoLndmIiksCiAgICBFc3RpbWF0aW9uVGFzayhtZXRyaWNzPWMoIlByZWNpc2lvbiIsIlJlY2FsbCIsImF2Z05EVFAiKSwKICAgICAgICAgICAgICAgICAgIG1ldGhvZD1Ib2xkb3V0KG5SZXBzPTMsIGhsZFN6PTAuMywgc3RyYXQ9VFJVRSksCiAgICAgICAgICAgICAgICAgICBldmFsdWF0b3I9ImV2YWxPdXRsaWVyUmFua2luZyIsCiAgICAgICAgICAgICAgICAgICBldmFsdWF0b3IucGFycz1saXN0KFRocmVzaG9sZD0wLjEsIHN0YXRzUHJvZHM9Z2xvYmFsU3RhdHMpKQogICAgKQpgYGAKCmBgYHtyfQpzdW1tYXJ5KG9yaC5yZXMpCgpgYGAKCmBgYHtyfQpwcy5vcmggPC0gc2FwcGx5KGdldEl0ZXJhdGlvbnNJbmZvKG9yaC5yZXMpLCBmdW5jdGlvbihpKSBpJHByb2JzWywxXSkKdHMub3JoIDwtIHNhcHBseShnZXRJdGVyYXRpb25zSW5mbyhvcmgucmVzKSwgZnVuY3Rpb24oaSkgaSRwcm9ic1ssMl0pClBSY3VydmUocHMuYnAsdHMuYnAsbWFpbj0iUFIgY3VydmUiLGx0eT0xLAogICAgICAgIHhsaW09YygwLDEpLHlsaW09YygwLDEpLGF2Zz0idmVydGljYWwiKQojUFJjdXJ2ZShwcy5sb2YsdHMubG9mLGFkZD1UUlVFLGx0eT0yLGF2Zz0ndmVydGljYWwnKQpQUmN1cnZlKHBzLm9yaCx0cy5vcmgsYWRkPVRSVUUsbHR5PTEsY29sPSdncmV5JywgYXZnPSd2ZXJ0aWNhbCcpCmxlZ2VuZCgndG9wcmlnaHQnLGMoJ0JQcnVsZScsJ0xPRicsJ09SaCcpLGx0eT1jKDEsMiwxKSwKICAgICAgIGNvbD1jKCdibGFjaycsJ2JsYWNrJywnZ3JleScpKQoKQ1JjaGFydChwcy5icCx0cy5icCxtYWluPSdDdW11bGF0aXZlIFJlY2FsbCBjdXJ2ZScsCiAgICAgICAgbHR5PTEseGxpbT1jKDAsMSkseWxpbT1jKDAsMSksYXZnPSd2ZXJ0aWNhbCcpCiNDUmNoYXJ0KHBzLmxvZix0cy5sb2YsYWRkPVRSVUUsbHR5PTIsYXZnPSd2ZXJ0aWNhbCcpCkNSY2hhcnQocHMub3JoLHRzLm9yaCxhZGQ9VFJVRSxsdHk9MSxjb2w9J2dyZXknLGF2Zz0ndmVydGljYWwnKQpsZWdlbmQoJ2JvdHRvbXJpZ2h0JyxjKCdCUHJ1bGUnLCdMT0YnLCdPUmgnKSxsdHk9YygxLDIsMSksCiAgICAgICBjb2w9YygnYmxhY2snLCdibGFjaycsJ2dyZXknKSkKYGBgCgpgYGB7cn0KcGFyKG1mcm93PWMoMSwyKSkgCnBzLm9yaCA8LSBzYXBwbHkoZ2V0SXRlcmF0aW9uc0luZm8ob3JoLnJlcyksIGZ1bmN0aW9uKGkpIGkkcHJvYnNbLDFdKQp0cy5vcmggPC0gc2FwcGx5KGdldEl0ZXJhdGlvbnNJbmZvKG9yaC5yZXMpLCBmdW5jdGlvbihpKSBpJHByb2JzWywyXSkKUFJjdXJ2ZShwcy5icCx0cy5icCxtYWluPSJQUiBjdXJ2ZSIsbHR5PTEsCiAgICAgICAgeGxpbT1jKDAsMSkseWxpbT1jKDAsMSksYXZnPSJ2ZXJ0aWNhbCIpCiNQUmN1cnZlKHBzLmxvZix0cy5sb2YsYWRkPVQsbHR5PTIsYXZnPSd2ZXJ0aWNhbCcpClBSY3VydmUocHMub3JoLHRzLm9yaCxhZGQ9VCxsdHk9MSxjb2w9J2dyZXknLCBhdmc9J3ZlcnRpY2FsJykKbGVnZW5kKCd0b3ByaWdodCcsYygnQlBydWxlJywnTE9GJywnT1JoJyksbHR5PWMoMSwyLDEpLAogICAgICAgY29sPWMoJ2JsYWNrJywnYmxhY2snLCdncmV5JykpCgpDUmNoYXJ0KHBzLmJwLHRzLmJwLG1haW49J0N1bXVsYXRpdmUgUmVjYWxsIGN1cnZlJywKICAgICAgICBsdHk9MSx4bGltPWMoMCwxKSx5bGltPWMoMCwxKSxhdmc9J3ZlcnRpY2FsJykKI0NSY2hhcnQocHMubG9mLHRzLmxvZixhZGQ9VCxsdHk9Mixhdmc9J3ZlcnRpY2FsJykKQ1JjaGFydChwcy5vcmgsdHMub3JoLGFkZD1ULGx0eT0xLGNvbD0nZ3JleScsYXZnPSd2ZXJ0aWNhbCcpCmxlZ2VuZCgnYm90dG9tcmlnaHQnLGMoJ0JQcnVsZScsJ0xPRicsJ09SaCcpLGx0eT1jKDEsMiwxKSwKICAgICAgIGNvbD1jKCdibGFjaycsJ2JsYWNrJywnZ3JleScpKQpgYGAKCgojIyMjIFN1cGVydmlzZWQgQXBwcm9hY2hlcwoKYGBge3J9CmxpYnJhcnkoVUJMKQpkYXRhKGlyaXMpCmRhdGEgPC0gaXJpc1ssIGMoMSwgMiwgNSldCmRhdGEkU3BlY2llcyA8LSBmYWN0b3IoaWZlbHNlKGRhdGEkU3BlY2llcyA9PSAic2V0b3NhIiwgInJhcmUiLCJjb21tb24iKSkKdGFibGUoZGF0YSRTcGVjaWVzKQpuZXdEYXRhIDwtIFNtb3RlQ2xhc3NpZihTcGVjaWVzIH4gLiwgZGF0YSwgQy5wZXJjID0gImJhbGFuY2UiKQp0YWJsZShuZXdEYXRhJFNwZWNpZXMpCm5ld0RhdGEyIDwtIFNtb3RlQ2xhc3NpZihTcGVjaWVzIH4gLiwgZGF0YSwgQy5wZXJjID0gbGlzdChjb21tb24gPSAxLHJhcmUgPSA2KSkKdGFibGUobmV3RGF0YTIkU3BlY2llcykKYGBgCgoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKZ2dwbG90KGRhdGEsYWVzKHg9U2VwYWwuTGVuZ3RoLHk9U2VwYWwuV2lkdGgsY29sb3I9U3BlY2llcykpICsKICAgIGdlb21fcG9pbnQoKSArIGdndGl0bGUoIk9yaWdpbmFsIERhdGEiKQpnZ3Bsb3QobmV3RGF0YTIsYWVzKHg9U2VwYWwuTGVuZ3RoLHk9U2VwYWwuV2lkdGgsY29sb3I9U3BlY2llcykpICsKICAgIGdlb21fcG9pbnQoKSArIGdndGl0bGUoIlNNT1RFJ2QgRGF0YSIpCmBgYApgYGB7cn0KTkIud2YgPC0gZnVuY3Rpb24oZm9ybSx0cmFpbix0ZXN0LC4uLikgewogICAgcmVxdWlyZShlMTA3MSxxdWlldGx5PVRSVUUpCiAgICBzdXAgPC0gd2hpY2godHJhaW4kSW5zcCAhPSAndW5rbicpCiAgICBkYXRhIDwtIGFzLmRhdGEuZnJhbWUodHJhaW5bc3VwLGMoJ0lEJywnUHJvZCcsJ1VwcmljZScsJ0luc3AnKV0pCiAgICBkYXRhJEluc3AgPC0gZmFjdG9yKGRhdGEkSW5zcCxsZXZlbHM9Yygnb2snLCdmcmF1ZCcpKQogICAgbW9kZWwgPC0gbmFpdmVCYXllcyhJbnNwIH4gLixkYXRhLCAuLi4pCiAgICBwcmVkcyA8LSBwcmVkaWN0KG1vZGVsLHRlc3RbLGMoJ0lEJywnUHJvZCcsJ1VwcmljZScsJ0luc3AnKV0sIHR5cGU9J3JhdycpCiAgICByYW5rT3JkZXIgPC0gb3JkZXIocHJlZHNbLCdmcmF1ZCddLCBkZWNyZWFzaW5nPVRSVUUpCiAgICByYW5rU2NvcmUgPC0gcHJlZHNbLCdmcmF1ZCddCiAgICByZXMgPC0gbGlzdCh0ZXN0U2V0PXRlc3QsCiAgICAgICAgICAgICAgICByYW5rT3JkZXI9cmFua09yZGVyLAogICAgICAgICAgICAgICAgcHJvYnM9YXMubWF0cml4KGNiaW5kKHJhbmtTY29yZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UodGVzdCRJbnNwPT0nZnJhdWQnLDEsMCkpKSkKICAgIHJlcwp9CmBgYAoKYGBge3J9Cm5iLnJlcyA8LSBwZXJmb3JtYW5jZUVzdGltYXRpb24oCiAgICBQcmVkVGFzayhJbnNwIH4gLiAsIHNhbGVzKSwKICAgIFdvcmtmbG93KCJOQi53ZiIpLAogICAgRXN0aW1hdGlvblRhc2sobWV0cmljcz1jKCJQcmVjaXNpb24iLCJSZWNhbGwiLCJhdmdORFRQIiksCiAgICAgICAgICAgICAgICAgICBtZXRob2Q9SG9sZG91dChuUmVwcz0zLGhsZFN6PTAuMyxzdHJhdD1UUlVFKSwKICAgICAgICAgICAgICAgICAgIGV2YWx1YXRvcj0iZXZhbE91dGxpZXJSYW5raW5nIiwKICAgICAgICAgICAgICAgICAgIGV2YWx1YXRvci5wYXJzPWxpc3QoVGhyZXNob2xkPTAuMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhdHNQcm9kcz1nbG9iYWxTdGF0cykpCiAgICApCmBgYAoKYGBge3J9CnN1bW1hcnkobmIucmVzKQpgYGAKCmBgYHtyfQpwcy5uYiA8LSBzYXBwbHkoZ2V0SXRlcmF0aW9uc0luZm8obmIucmVzKSwgZnVuY3Rpb24oaSkgaSRwcm9ic1ssMV0pCnRzLm5iIDwtIHNhcHBseShnZXRJdGVyYXRpb25zSW5mbyhuYi5yZXMpLCBmdW5jdGlvbihpKSBpJHByb2JzWywyXSkKUFJjdXJ2ZShwcy5uYix0cy5uYixtYWluPSJQUiBjdXJ2ZSIsbHR5PTEsCiAgICAgICAgeGxpbT1jKDAsMSkseWxpbT1jKDAsMSksYXZnPSJ2ZXJ0aWNhbCIpClBSY3VydmUocHMub3JoLHRzLm9yaCxhZGQ9VFJVRSxsdHk9Mixhdmc9J3ZlcnRpY2FsJykKbGVnZW5kKCd0b3ByaWdodCcsYygnTmFpdmVCYXllcycsJ09SaCcpLGx0eT0xLGNvbD1jKCdibGFjaycsJ2dyZXknKSkKCkNSY2hhcnQocHMubmIsdHMubmIsbWFpbj0nQ3VtdWxhdGl2ZSBSZWNhbGwgY3VydmUnLAogICAgICAgIGx0eT0xLHhsaW09YygwLDEpLHlsaW09YygwLDEpLGF2Zz0ndmVydGljYWwnKQpDUmNoYXJ0KHBzLm9yaCx0cy5vcmgsYWRkPVRSVUUsbHR5PTIsYXZnPSd2ZXJ0aWNhbCcpCmxlZ2VuZCgnYm90dG9tcmlnaHQnLGMoJ05haXZlQmF5ZXMnLCdPUmgnKSxsdHk9MSxjb2w9YygnYmxhY2snLCdncmV5JykpCmBgYAoKYGBge3J9CnBhcihtZnJvdz1jKDEsMikpIApwcy5uYiA8LSBzYXBwbHkoZ2V0SXRlcmF0aW9uc0luZm8obmIucmVzKSwgZnVuY3Rpb24oaSkgaSRwcm9ic1ssMV0pCnRzLm5iIDwtIHNhcHBseShnZXRJdGVyYXRpb25zSW5mbyhuYi5yZXMpLCBmdW5jdGlvbihpKSBpJHByb2JzWywyXSkKUFJjdXJ2ZShwcy5uYix0cy5uYixtYWluPSJQUiBjdXJ2ZSIsbHR5PTEsCiAgICAgICAgeGxpbT1jKDAsMSkseWxpbT1jKDAsMSksYXZnPSJ2ZXJ0aWNhbCIpClBSY3VydmUocHMub3JoLHRzLm9yaCxhZGQ9VCxsdHk9Mixhdmc9J3ZlcnRpY2FsJykKbGVnZW5kKCd0b3ByaWdodCcsYygnTmFpdmVCYXllcycsJ09SaCcpLGx0eT0xLGNvbD1jKCdibGFjaycsJ2dyZXknKSkKCkNSY2hhcnQocHMubmIsdHMubmIsbWFpbj0nQ3VtdWxhdGl2ZSBSZWNhbGwgY3VydmUnLAogICAgICAgIGx0eT0xLHhsaW09YygwLDEpLHlsaW09YygwLDEpLGF2Zz0ndmVydGljYWwnKQpDUmNoYXJ0KHBzLm9yaCx0cy5vcmgsYWRkPVQsbHR5PTIsYXZnPSd2ZXJ0aWNhbCcpCmxlZ2VuZCgnYm90dG9tcmlnaHQnLGMoJ05haXZlQmF5ZXMnLCdPUmgnKSxsdHk9MSxjb2w9YygnYmxhY2snLCdncmV5JykpCgpgYGAKCmBgYHtyfQpOQnNtLndmIDwtIGZ1bmN0aW9uKGZvcm0sdHJhaW4sdGVzdCxDLnBlcmM9ImJhbGFuY2UiLGRpc3Q9IkhFT00iLC4uLikgewogICAgcmVxdWlyZShlMTA3MSxxdWlldGx5PVRSVUUpCiAgICByZXF1aXJlKFVCTCxxdWlldGx5PVRSVUUpCgogICAgc3VwIDwtIHdoaWNoKHRyYWluJEluc3AgIT0gJ3Vua24nKQogICAgZGF0YSA8LSBhcy5kYXRhLmZyYW1lKHRyYWluW3N1cCxjKCdJRCcsJ1Byb2QnLCdVcHJpY2UnLCdJbnNwJyldKQogICAgZGF0YSRJbnNwIDwtIGZhY3RvcihkYXRhJEluc3AsbGV2ZWxzPWMoJ29rJywnZnJhdWQnKSkKICAgIG5ld0RhdGEgPC0gU21vdGVDbGFzc2lmKEluc3AgfiAuLGRhdGEsQy5wZXJjPUMucGVyYyxkaXN0PWRpc3QsLi4uKQogICAgbW9kZWwgPC0gbmFpdmVCYXllcyhJbnNwIH4gLixuZXdEYXRhKQogICAgcHJlZHMgPC0gcHJlZGljdChtb2RlbCx0ZXN0WyxjKCdJRCcsJ1Byb2QnLCdVcHJpY2UnLCdJbnNwJyldLHR5cGU9J3JhdycpCiAgICByYW5rT3JkZXIgPC0gb3JkZXIocHJlZHNbLCdmcmF1ZCddLGRlY3JlYXNpbmc9VCkKICAgIHJhbmtTY29yZSA8LSBwcmVkc1ssJ2ZyYXVkJ10KICAgIAogICAgcmVzIDwtIGxpc3QodGVzdFNldD10ZXN0LAogICAgICAgICAgICAgIHJhbmtPcmRlcj1yYW5rT3JkZXIsCiAgICAgICAgICAgICAgcHJvYnM9YXMubWF0cml4KGNiaW5kKHJhbmtTY29yZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHRlc3QkSW5zcD09J2ZyYXVkJywxLDApKSkpCiAgICByZXMKfQpgYGAKCmBgYHtyfQpuYnMucmVzIDwtIHBlcmZvcm1hbmNlRXN0aW1hdGlvbigKICAgIFByZWRUYXNrKEluc3AgfiAuLCBzYWxlcyksCiAgICBXb3JrZmxvdygiTkJzbS53ZiIpLAogICAgRXN0aW1hdGlvblRhc2sobWV0cmljcz1jKCJQcmVjaXNpb24iLCJSZWNhbGwiLCJhdmdORFRQIiksCiAgICAgICAgICAgICAgICAgICBtZXRob2Q9SG9sZG91dChuUmVwcz0zLGhsZFN6PTAuMyxzdHJhdD1UUlVFKSwKICAgICAgICAgICAgICAgICAgIGV2YWx1YXRvcj0iZXZhbE91dGxpZXJSYW5raW5nIiwKICAgICAgICAgICAgICAgICAgIGV2YWx1YXRvci5wYXJzPWxpc3QoVGhyZXNob2xkPTAuMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhdHNQcm9kcz1nbG9iYWxTdGF0cykpCiAgICApCmBgYAoKYGBge3J9CnN1bW1hcnkobmJzLnJlcykgCgpgYGAKCmBgYHtyfQpwcy5uYnMgPC0gc2FwcGx5KGdldEl0ZXJhdGlvbnNJbmZvKG5icy5yZXMpLCBmdW5jdGlvbihpKSBpJHByb2JzWywxXSkKdHMubmJzIDwtIHNhcHBseShnZXRJdGVyYXRpb25zSW5mbyhuYnMucmVzKSwgZnVuY3Rpb24oaSkgaSRwcm9ic1ssMl0pClBSY3VydmUocHMubmIsdHMubmIsbWFpbj0iUFIgY3VydmUiLGx0eT0xLAogICAgICAgIHhsaW09YygwLDEpLHlsaW09YygwLDEpLCBhdmc9InZlcnRpY2FsIikKUFJjdXJ2ZShwcy5vcmgsdHMub3JoLGFkZD1UUlVFLGx0eT0yLCBhdmc9J3ZlcnRpY2FsJykKUFJjdXJ2ZShwcy5uYnMsdHMubmJzLGFkZD1UUlVFLGx0eT0xLCBjb2w9J2dyZXknLGF2Zz0ndmVydGljYWwnKQpsZWdlbmQoJ3RvcHJpZ2h0JyxjKCdOYWl2ZUJheWVzJywnT1JoJywnc21vdGVOYWl2ZUJheWVzJyksbHR5PWMoMSwyLDEpLAogICAgICAgY29sPWMoJ2JsYWNrJywnYmxhY2snLCdncmV5JykpCmBgYAoKCmBgYHtyfQpDUmNoYXJ0KHBzLm5iLHRzLm5iLG1haW49J0N1bXVsYXRpdmUgUmVjYWxsIGN1cnZlJywKICAgICAgICBsdHk9MSx4bGltPWMoMCwxKSx5bGltPWMoMCwxKSxhdmc9J3ZlcnRpY2FsJykKQ1JjaGFydChwcy5vcmgsdHMub3JoLGFkZD1UUlVFLGx0eT0yLGF2Zz0ndmVydGljYWwnKQpDUmNoYXJ0KHBzLm5icyx0cy5uYnMsYWRkPVRSVUUsbHR5PTEsY29sPSdncmV5Jyxhdmc9J3ZlcnRpY2FsJykKbGVnZW5kKCdib3R0b21yaWdodCcsYygnTmFpdmVCYXllcycsJ09SaCcsJ3Ntb3RlTmFpdmVCYXllcycpLGx0eT1jKDEsMiwxKSwKICAgICAgIGNvbD1jKCdibGFjaycsJ2JsYWNrJywnZ3JleScpKQpgYGAKCgpgYGB7cn0KcGFyKG1mcm93PWMoMSwyKSkgCnBzLm5icyA8LSBzYXBwbHkoZ2V0SXRlcmF0aW9uc0luZm8obmJzLnJlcyksICBmdW5jdGlvbihpKSBpJHByb2JzWywxXSkKdHMubmJzIDwtIHNhcHBseShnZXRJdGVyYXRpb25zSW5mbyhuYnMucmVzKSwgIGZ1bmN0aW9uKGkpIGkkcHJvYnNbLDJdKQpQUmN1cnZlKHBzLm5iLHRzLm5iLG1haW49IlBSIGN1cnZlIixsdHk9MSwKICAgICAgICB4bGltPWMoMCwxKSx5bGltPWMoMCwxKSwgYXZnPSJ2ZXJ0aWNhbCIpClBSY3VydmUocHMub3JoLHRzLm9yaCxhZGQ9VCxsdHk9Mixhdmc9J3ZlcnRpY2FsJykKUFJjdXJ2ZShwcy5uYnMsdHMubmJzLGFkZD1ULGx0eT0xLGNvbD0nZ3JleScsYXZnPSd2ZXJ0aWNhbCcpCmxlZ2VuZCgndG9wcmlnaHQnLGMoJ05haXZlQmF5ZXMnLCdPUmgnLCdzbW90ZU5haXZlQmF5ZXMnKSwKICAgICAgIGx0eT1jKDEsMiwxKSxjb2w9YygnYmxhY2snLCdibGFjaycsJ2dyZXknKSkKYGBgCgoKYGBge3J9CkNSY2hhcnQocHMubmIsdHMubmIsbWFpbj0nQ3VtdWxhdGl2ZSBSZWNhbGwgY3VydmUnLAogICAgICAgIGx0eT0xLHhsaW09YygwLDEpLHlsaW09YygwLDEpLGF2Zz0ndmVydGljYWwnKQpDUmNoYXJ0KHBzLm9yaCx0cy5vcmgsYWRkPVQsbHR5PTIsYXZnPSd2ZXJ0aWNhbCcpCkNSY2hhcnQocHMubmJzLHRzLm5icyxhZGQ9VCxsdHk9MSxjb2w9J2dyZXknLGF2Zz0ndmVydGljYWwnKQpsZWdlbmQoJ2JvdHRvbXJpZ2h0JyxjKCdOYWl2ZUJheWVzJywnT1JoJywnc21vdGVOYWl2ZUJheWVzJyksCiAgICAgICBsdHk9YygxLDIsMSksY29sPWMoJ2JsYWNrJywnYmxhY2snLCdncmV5JykpCmBgYAoKIyMjIyBTZW1pLVN1cGVydmlzZWQgQXBwcm9hY2hlcwoKCmBgYHtyfQpsaWJyYXJ5KERNd1IyKQpsaWJyYXJ5KGUxMDcxKQpkYXRhKGlyaXMpIApzZXQuc2VlZCgxMjM0KQppZHggPC0gc2FtcGxlKDE1MCwgMTAwKQp0ciA8LSBpcmlzW2lkeCwgXQp0cyA8LSBpcmlzWy1pZHgsIF0KbmIgPC0gbmFpdmVCYXllcyhTcGVjaWVzIH4gLiwgdHIpCnRhYmxlKHByZWRpY3QobmIsIHRzKSwgdHMkU3BlY2llcykKdHJTVCA8LSB0cgpuYXMgPC0gc2FtcGxlKDEwMCwgOTApCnRyU1RbbmFzLCAiU3BlY2llcyJdIDwtIE5BCmZ1bmMgPC0gZnVuY3Rpb24obSwgZCkgewogICAgcCA8LSBwcmVkaWN0KG0sIGQsIHR5cGUgPSAicmF3IikKICAgIGRhdGEuZnJhbWUoY2wgPSBjb2xuYW1lcyhwKVsgYXBwbHkocCwgMSwgd2hpY2gubWF4KSBdLCAKICAgICAgICAgICAgICAgcCA9IGFwcGx5KHAsIDEsIG1heCkpCn0KbmJTVGJhc2UgPC0gbmFpdmVCYXllcyhTcGVjaWVzIH4gLiwgdHJTVFstbmFzLCBdKQp0YWJsZShwcmVkaWN0KG5iU1RiYXNlLCB0cyksIHRzJFNwZWNpZXMpCm5iU1QgPC0gU2VsZlRyYWluKFNwZWNpZXMgfiAuLCB0clNULCAKICAgICAgICAgICAgICAgICAgbGVhcm5lcj0ibmFpdmVCYXllcyIsIGxlYXJuZXIucGFycz1saXN0KCksCiAgICAgICAgICAgICAgICAgIHByZWQ9ImZ1bmMiKQp0YWJsZShwcmVkaWN0KG5iU1QsIHRzKSwgdHMkU3BlY2llcykKYGBgCgpgYGB7cn0KcHJlZC5uYiA8LSBmdW5jdGlvbihtLGQpIHsKICAgIHAgPC0gcHJlZGljdChtLGQsdHlwZT0ncmF3JykKICAgIGRhdGEuZnJhbWUoY2w9Y29sbmFtZXMocClbYXBwbHkocCwxLHdoaWNoLm1heCldLAogICAgICAgICAgICAgICBwPWFwcGx5KHAsMSxtYXgpCiAgICAgICAgICAgICAgICkKfQogCm5iLnN0LndmIDwtIGZ1bmN0aW9uKGZvcm0sdHJhaW4sdGVzdCwuLi4pIHsKICAgIHJlcXVpcmUoZTEwNzEscXVpZXRseT1UUlVFKQogICAgcmVxdWlyZShETXdSMiwgcXVpZXRseT1UUlVFKQogICAgdHJhaW4gPC0gYXMuZGF0YS5mcmFtZSh0cmFpblssYygnSUQnLCdQcm9kJywnVXByaWNlJywnSW5zcCcpXSkKICAgIHRyYWluW3doaWNoKHRyYWluJEluc3AgPT0gJ3Vua24nKSwnSW5zcCddIDwtIE5BCiAgICB0cmFpbiRJbnNwIDwtIGZhY3Rvcih0cmFpbiRJbnNwLGxldmVscz1jKCdvaycsJ2ZyYXVkJykpCiAgICBtb2RlbCA8LSBTZWxmVHJhaW4oZm9ybSx0cmFpbiwKICAgICAgICAgICAgICAgICAgICAgICBsZWFybmVyPSduYWl2ZUJheWVzJywgbGVhcm5lci5wYXJzPWxpc3QoKSwKICAgICAgICAgICAgICAgICAgICAgICBwcmVkPSdwcmVkLm5iJykKICAgIHByZWRzIDwtIHByZWRpY3QobW9kZWwsdGVzdFssYygnSUQnLCdQcm9kJywnVXByaWNlJywnSW5zcCcpXSwKICAgICAgICAgICAgICAgICAgICAgdHlwZT0ncmF3JykKCiAgICByYW5rT3JkZXIgPC0gb3JkZXIocHJlZHNbLCdmcmF1ZCddLGRlY3JlYXNpbmc9VFJVRSkKICAgIHJhbmtTY29yZSA8LSBwcmVkc1ssImZyYXVkIl0KICAgIAogICAgcmVzIDwtIGxpc3QodGVzdFNldD10ZXN0LAogICAgICAgICAgICAgIHJhbmtPcmRlcj1yYW5rT3JkZXIsCiAgICAgICAgICAgICAgcHJvYnM9YXMubWF0cml4KGNiaW5kKHJhbmtTY29yZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHRlc3QkSW5zcD09J2ZyYXVkJywxLDApKSkpCiAgICByZXMKfQpgYGAKCmBgYHtyfQpuYi5zdC5yZXMgPC0gcGVyZm9ybWFuY2VFc3RpbWF0aW9uKAogICAgUHJlZFRhc2soSW5zcCB+IC4sc2FsZXMpLAogICAgV29ya2Zsb3coIm5iLnN0LndmIiksCiAgICBFc3RpbWF0aW9uVGFzayhtZXRyaWNzPWMoIlByZWNpc2lvbiIsIlJlY2FsbCIsImF2Z05EVFAiKSwKICAgICAgICAgICAgICAgICAgIG1ldGhvZD1Ib2xkb3V0KG5SZXBzPTMsaGxkU3o9MC4zLHN0cmF0PVRSVUUpLAogICAgICAgICAgICAgICAgICAgZXZhbHVhdG9yPSJldmFsT3V0bGllclJhbmtpbmciLAogICAgICAgICAgICAgICAgICAgZXZhbHVhdG9yLnBhcnM9bGlzdChUaHJlc2hvbGQ9MC4xLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdGF0c1Byb2RzPWdsb2JhbFN0YXRzKSkKICAgICkKCmBgYAoKYGBge3J9CnN1bW1hcnkobmIuc3QucmVzKQoKYGBgCgoKYGBge3J9CnBzLm5iLnN0IDwtIHNhcHBseShnZXRJdGVyYXRpb25zSW5mbyhuYi5zdC5yZXMpLCBmdW5jdGlvbihpKSBpJHByb2JzWywxXSkKdHMubmIuc3QgPC0gc2FwcGx5KGdldEl0ZXJhdGlvbnNJbmZvKG5iLnN0LnJlcyksIGZ1bmN0aW9uKGkpIGkkcHJvYnNbLDJdKQpQUmN1cnZlKHBzLm5iLHRzLm5iLG1haW49IlBSIGN1cnZlIixsdHk9MSwKICAgICAgICB4bGltPWMoMCwxKSx5bGltPWMoMCwxKSwgYXZnPSJ2ZXJ0aWNhbCIpClBSY3VydmUocHMub3JoLHRzLm9yaCxhZGQ9VFJVRSxsdHk9MSwgY29sb3I9J2dyZXknLCBhdmc9J3ZlcnRpY2FsJykKUFJjdXJ2ZShwcy5uYi5zdCx0cy5uYi5zdCxhZGQ9VFJVRSxsdHk9Mixhdmc9J3ZlcnRpY2FsJykKbGVnZW5kKCd0b3ByaWdodCcsYygnTmFpdmVCYXllcycsJ09SaCcsJ05haXZlQmF5ZXMtU1QnKSwKICAgICAgIGx0eT1jKDEsMSwyKSxjb2w9YygnYmxhY2snLCdncmV5JywnYmxhY2snKSkKYGBgCgoKYGBge3J9CkNSY2hhcnQocHMubmIsdHMubmIsbWFpbj0nQ3VtdWxhdGl2ZSBSZWNhbGwgY3VydmUnLAogICAgICAgIGx0eT0xLHhsaW09YygwLDEpLHlsaW09YygwLDEpLGF2Zz0ndmVydGljYWwnKQpDUmNoYXJ0KHBzLm9yaCx0cy5vcmgsYWRkPVRSVUUsbHR5PTEsY29sb3I9J2dyZXknLGF2Zz0ndmVydGljYWwnKQpDUmNoYXJ0KHBzLm5iLnN0LHRzLm5iLnN0LGFkZD1UUlVFLGx0eT0yLGF2Zz0ndmVydGljYWwnKQpsZWdlbmQoJ2JvdHRvbXJpZ2h0JyxjKCdOYWl2ZUJheWVzJywnT1JoJywnTmFpdmVCYXllcy1TVCcpLAogICAgICAgbHR5PWMoMSwxLDIpLGNvbD1jKCdibGFjaycsJ2dyZXknLCdncmV5JykpCgpgYGAKCmBgYHtyfQpwYXIobWZyb3c9YygxLDIpKSAKcHMubmIuc3QgPC0gc2FwcGx5KGdldEl0ZXJhdGlvbnNJbmZvKG5iLnN0LnJlcyksIGZ1bmN0aW9uKGkpIGkkcHJvYnNbLDFdKQp0cy5uYi5zdCA8LSBzYXBwbHkoZ2V0SXRlcmF0aW9uc0luZm8obmIuc3QucmVzKSwgZnVuY3Rpb24oaSkgaSRwcm9ic1ssMl0pClBSY3VydmUocHMubmIsdHMubmIsbWFpbj0iUFIgY3VydmUiLGx0eT0xLAogICAgICAgIHhsaW09YygwLDEpLHlsaW09YygwLDEpLCBhdmc9InZlcnRpY2FsIikKUFJjdXJ2ZShwcy5vcmgsdHMub3JoLGFkZD1ULGx0eT0xLCBjb2xvcj0nZ3JleScsIGF2Zz0ndmVydGljYWwnKQpQUmN1cnZlKHBzLm5iLnN0LHRzLm5iLnN0LGFkZD1ULGx0eT0yLGF2Zz0ndmVydGljYWwnKQpsZWdlbmQoJ3RvcHJpZ2h0JyxjKCdOYWl2ZUJheWVzJywnT1JoJywnTmFpdmVCYXllcy1TVCcpLAogICAgICAgbHR5PWMoMSwxLDIpLGNvbD1jKCdibGFjaycsJ2dyZXknLCdibGFjaycpKQpgYGAKCgpgYGB7cn0KQ1JjaGFydChwcy5uYix0cy5uYixtYWluPSdDdW11bGF0aXZlIFJlY2FsbCBjdXJ2ZScsCiAgICAgICAgbHR5PTEseGxpbT1jKDAsMSkseWxpbT1jKDAsMSksYXZnPSd2ZXJ0aWNhbCcpCkNSY2hhcnQocHMub3JoLHRzLm9yaCxhZGQ9VCxsdHk9MSxjb2xvcj0nZ3JleScsYXZnPSd2ZXJ0aWNhbCcpCkNSY2hhcnQocHMubmIuc3QsdHMubmIuc3QsYWRkPVQsbHR5PTIsYXZnPSd2ZXJ0aWNhbCcpCmxlZ2VuZCgnYm90dG9tcmlnaHQnLGMoJ05haXZlQmF5ZXMnLCdPUmgnLCdOYWl2ZUJheWVzLVNUJyksCiAgICAgICBsdHk9YygxLDEsMiksY29sPWMoJ2JsYWNrJywnZ3JleScsJ2dyZXknKSkKCmBgYAoK