Group4:
B034020012 謝雨靜
B034020027 陳韻卉
B044012015 王譯苓
M064111025 黃威豪
M064111039 王 欣
行傳所 孟祥瑄


packages = c(
  "dplyr","ggplot2","googleVis","devtools","magrittr","slam","irlba","plotly",
  "arules","arulesViz","Matrix","recommenderlab")
existing = as.character(installed.packages()[,1])
for(pkg in packages[!(packages %in% existing)]) install.packages(pkg)
rm(list=ls(all=TRUE))
LOAD = TRUE
library(dplyr)
library(ggplot2)
library(googleVis)
library(Matrix)
library(slam)
library(irlba)
library(plotly)
library(arules)
library(arulesViz)
library(recommenderlab)


A. RFM分群

load("data/tf0.rdata")
A = A0; X = X0; Z = Z0; rm(A0,X0,Z0); gc()
k = 8; set.seed(777)
A$group = kmeans(scale(A[,2:6]), k)$cluster; table(A$group)
df = group_by(A, group) %>% summarise(
  avg_frequency = mean(f),
  avg_monetary = mean(m),
  total_revenue = sum(rev),
  group_size = n(),
  avg_recency = mean(r),
  profit = sum(raw)
  ) %>% ungroup %>% 
  mutate(
    pc_revenue = round(100*total_revenue/sum(total_revenue),1), # % revenue contrib.
    pc_profit = round(100*profit/sum(profit),1),                # % profit contrib. 
    dummy = 2001  # to fit googleViz's data format
  ) %>% data.frame
head(df)
plot( gvisMotionChart(
  df, "group", "dummy",
  options=list(width=800, height=600) 
  ))


【QUIZ】 如果我們把X,Y軸分別改成log(avg_monetary)和log(avg_recency),根據圖上的顯示 …



B. 顧客產品矩陣

n_distinct(Z$cust)  # 32256
n_distinct(Z$prod)  # 23789

操作矩陣運算之前,通常我會載入這兩個套件

library(Matrix)
library(slam)

製作顧客產品矩陣其實很快、也很容易

mx = xtabs(~ cust + prod, Z, sparse=T)

顧客產品矩陣通常是一個很稀疏的矩陣

dim(mx)
mean(mx > 0)

有一些產品沒什麼人買

table(colSums(mx) < 10)       # 12588 

刪去購買次數小於10的產品,然後刪去沒有購買產品的顧客

mx = mx[, colSums(mx) > 10]   # 
mx = mx[rowSums(mx) > 0, ]    #
dim(mx)                       # 32256>32066 23789>10675

檢查一下矩陣裡面的值分布

max(mx)                       # 49
table(mx@x) %>% prop.table %>% round(4) %>% head(10)


【QUIZ】 如果mx[i, j] = 3,這表示 ….



C. 依購買頻率分群

C1. 改變(稀疏)矩陣格式

稀疏矩陣有很多種格式,不同的工具會使用不同的格式

library(slam)
tmx = as(mx,"dgTMatrix")
tmx = simple_triplet_matrix(
  1+tmx@i, 1+tmx@j, tmx@x, dimnames=mx@Dimnames)
dim(tmx)
C2. 估計各產品的平均重要性 (Average TF-IDF Scores)

我們借用文字分析裡面估計單字在文章之中的重要性的方法(TF-IDF),計算各產品在所有顧客之間的平均重要性

tfidf = tapply(tmx$v/row_sums(tmx)[tmx$i], tmx$j, mean) *
  log2(nrow(tmx)/col_sums(tmx > 0))
summary(tfidf)
hist(tfidf)
C3. 篩選產品、重新排列

篩去平均重要性比較低的產品、然後將產品依被購買次數降冪排列

tmx = tmx[, tfidf > mean(tfidf)]    # remove products with low tfidf score
tmx = tmx[, order(-col_sums(tmx))]  # order product by frequency
dim(tmx)
C4. 依(購買頻率最高的)產品對顧客分群
nop= 400  # no. product
k = 200   # no. cluster
set.seed(111); kg = kmeans(tmx[,1:nop], k)$cluster
table(kg) %>% as.vector %>% sort
C5. 各群組平均屬性

將分群結果併入顧客資料框(A)

df = inner_join(A, data.frame(
  cust = as.integer(tmx$dimnames$cust), kg))

計算各群組的平均屬性

df = data.frame(
  aggregate(. ~ kg, df[,c(2:7,11)], mean),
  size = as.vector(table(kg))
  )

head(df) 
C6. 互動式泡泡圖
df$dummy = 2001                        # dummy column for googleViz
plot( gvisMotionChart(
  subset(df[,c(1,4,5,6,8,2,3,7,9)], 
         size >= 20 & size <= 5000     # range of group size
         ), 
  "kg", "dummy", options=list(width=800, height=600) ) )
C7. 各群組的代表性產品 (Signature Product)
Sig = function(gx, P=1000, H=10) {
  print(sprintf("Group %d: No. Customers = %d", gx, sum(kg==gx)))
  bx = tmx[,1:P]
  data.frame(n = col_sums(bx[kg==gx,])) %>%      # frequency
    mutate(
      share = round(100*n/col_sums(bx),2),       # %prod sold to this cluster
      conf = round(100*n/sum(kg==gx),2),         # %buy this product, given cluster
      base = round(100*col_sums(bx)/nrow(bx),2), # %buy this product, all cust 
      lift = round(conf/base,1),                 # conf/base  
      name = colnames(bx)                        # name of prod
    ) %>% arrange(desc(lift)) %>% head(H)
  }
Sig(1)
Sig(19)[,1]%>% sort(T)
Sig(19)[Sig(19)$n==57,]

【QUIZ】 從互動式泡泡圖看來 …

  • 平均客單價、平均購買次數、平均營收貢獻、平均獲利貢獻最大的分別是哪一個族群?
    • 平均客單價:G190
    • 平均購買次數:G186
    • 平均營收貢獻:G19
    • 平均獲利貢獻:G19
  • 它們的特徵產品分別是什麼?
    • 8712045008539
    • 4710063031106
    • 4710628079017
    • 4710628079017

【QUIZ】 在以上這個段落裡面 …

  • 我們分群的區隔變數是什麼?
    • 所有的產品為區隔變數
  • 我們在泡泡圖裡面觀察的變數是哪一些?
    • r(最後一次購買距今天數)
    • f(購買頻率)
    • m(客單價)
    • raw(獲利貢獻)
    • rev(營收貢獻)
    • group_size(族群大小)
    • s(第一次購買距今天數)
  • 它們是相同的嗎?
    • 不同。
  • 這個分析的程序,跟Airlines CRM那一個例子有甚麼不同?
    資料型態 常態化 篩選 區隔變數相同?
    Airlines Yes,讓變數之間的權重一樣 No,保留所有的顧客做分群,由分群結果看特徵 Yes,利用現有變數分群,再用「相同」變數解讀不同分群特徵
    顧客產品矩陣 屬於稀鬆矩陣 No,矩陣本身為數量的計數所以不須常態化

    Yes,有以下2種情形:

    • 顧客購買太少產品
    • 產品被購買的次數過少,所提供資訊太少,故刪除
    NO,用產品購買頻率分群,用分群結果與顧客交易資料合併,看群的特徵
  • 我們從這個例子學到什麼?
    • 利用xtabs可以容易地將顧客產品矩陣製作出來。
    • 用購買產品頻率與用顧客購買習慣所分群出來的結果會不一樣。
    • 可以透過此方法從產品的角度出發去做行銷決策。
    • 再由各群組的代表性產品、互動泡泡圖來獲得更多資訊。


D. 使用尺度縮減方法抽取顧客(產品)的特徵向量

D1. 巨大尺度縮減 (SVD, Sigular Value Decomposition)
library(irlba)
if(LOAD) {
  load("data/svd.rdata")
} else {
  smx = mx
  smx@x = pmin(smx@x, 2)            # cap at 2, similar to normalization  
  t0 = Sys.time()
  svd = irlba(smx, 
              nv=400,               # length of feature vector
              maxit=800, work=800)    
  print(Sys.time() - t0)            # 1.8795 mins
  save(svd, file = "data/svd.rdata")
}
D2. 依顧客向量對顧客分群
set.seed(111); kg = kmeans(svd$u, 200)$cluster
table(kg) %>% as.vector %>% sort
D3. 互動式泡泡圖 (Google Motion Chart)
# clustster summary
df = left_join(A, data.frame(         
  cust = as.integer(smx@Dimnames$cust), kg)) %>% 
  group_by(kg) %>% summarise(
    avg_frequency = mean(f),
    avg_monetary = mean(m),
    avg_revenue_contr = mean(rev),
    group_size = n(),
    avg_recency = mean(r),
    avg_gross_profit = mean(raw)) %>% 
  ungroup %>% 
  mutate(dummy = 2001, kg = sprintf("G%03d",kg)) %>% 
  data.frame

# Google Motion Chart
plot( gvisMotionChart(
  subset(df, group_size >= 20 & group_size <= 5000),     
  "kg", "dummy", options=list(width=800, height=600) ) )
D4. 互動式泡泡圖 (ggplot + plotly)
filter(df, group_size >= 20 & group_size <= 5000)$group_size %>% 
  sqrt %>% range    # for bubble size adjustment
library(ggplot2)
library(plotly)

p = df %>% filter(group_size >= 20 & group_size <= 5000) %>% 
  ggplot(aes(x=avg_frequency, y=avg_monetary)) +
  geom_point(aes(size=group_size, col=avg_revenue_contr),alpha=0.7) +
  geom_text(aes(label=kg), alpha=0) +
  scale_size(range=c(1.5,12)) +
  #scale_color_gradient(low="green",high="magenta") +
  scale_colour_gradientn(
    colours = rev(c("red","yellow","green","lightblue","darkblue"))) +
  theme_bw() + guides(size=F) + labs(
    title="顧客集群(依購買產品)",
    color="平均營收貢獻", size="集群人數") +
  xlab("平均購買次數") + 
  ylab("平均購買金額")
plotly_build(p)
D5. 群組的代表性產品 (Signature Product)
Sig(138)


E. 購物籃分析 Baskets Analysis

dim(mx)   # 32066 cust * 10675 prod
E1. 準備資料 (for Association Rule Analysis)
library(arules)
library(arulesViz)
bx = subset(Z, prod %in% as.numeric(colnames(mx)), 
            select=c("cust","prod"))  # select product items
bx = split(bx$prod, bx$cust)          # split by customer
bx = as(bx, "transactions")           # data for arules package
E2. Top20 熱賣產品
itemFrequencyPlot(bx, topN=20, type="absolute", cex=0.8)
E3. 關聯規則和Apriori演算法

關聯規則(A => B)

rules = apriori(bx, parameter=list(supp=0.005, conf=0.6))
summary(rules)
E4. 檢視關聯規則

關聯規則 (A => B):

options(digits=4)
inspect(rules)
# install.packages(
#   "https://cran.r-project.org/bin/windows/contrib/3.5/arulesViz_1.3-1.zip",
#   repos=NULL)

# install.packages("arulesViz_1.3-1.zip", repos=NULL)
# library(plotly)
# plotly_arules(rules,colors=c("red","green"),
#               marker=list(opacity=.6,size=10))
# plotly_arules(rules,method="matrix",
#               shading="lift",
#               colors=c("red", "green"))
# 
E5. 互動圖表顯示
plot(rules,colors=c("red","green"),engine="htmlwidget",
     marker=list(opacity=.6,size=8))
plot(rules,method="matrix",shading="lift",engine="htmlwidget",
     colors=c("red", "green"))
E6. 篩選產品、互動式關聯圖
r1 = subset(rules, subset = rhs %in% c("4719090790000"))
summary(r1)
plot(r1,method="graph",engine="htmlwidget",itemCol="cyan") 
r2 = subset(rules, subset = rhs %in% c("4710011401135"))
summary(r2)
plot(r2,method="graph",engine="htmlwidget",itemCol="cyan") 


F. 產品推薦 Product Recommendation

F1. 篩選顧客、產品

太少被購買的產品和購買太少產品的顧客都不適合使用Collaborative Filtering這種產品推薦方法,所以我們先對顧客和產品做一次篩選

library(recommenderlab)
rx = mx[, colSums(mx > 0) >= 50]
rx = rx[rowSums(rx > 0) >= 20 & rowSums(rx > 0) <= 300, ]
dim(rx)
F2. 選擇產品評分方式

可以選擇要用

做模型。

rx = as(rx, "realRatingMatrix")  # realRatingMatrix
bx = binarize(rx, minRating=1)   # binaryRatingMatrix
dim(bx)
F3. 設定模型(準確性)驗證方式
set.seed(4321)
scheme = evaluationScheme(     
  bx, method="split", train = .75,  given=5)
F4. 設定推薦方法(參數)
algorithms = list(            
  AR53 = list(name="AR", param=list(support=0.0005, confidence=0.3)),
  AR43 = list(name="AR", param=list(support=0.0004, confidence=0.3)),
  RANDOM = list(name="RANDOM", param=NULL),
  POPULAR = list(name="POPULAR", param=NULL),
  UBCF = list(name="UBCF", param=NULL),
  IBCF = list(name="IBCF", param=NULL) )
F5. 建模、預測、驗證(準確性)
if(LOAD) {
  load("results.rdata")
} else {
  t0 = Sys.time()
  results = evaluate(            
    scheme, algorithms, type="topNList",
    n=c(5, 10, 15, 20))
  print(Sys.time() - t0)
  save(results, file="results.rdata")
}
F6. 模型準確性比較
# load("data/results.rdata")
par(mar=c(4,4,3,2),cex=0.8)
cols = c("red", "magenta", "gray", "orange", "blue", "green")
plot(results, annotate=c(1,3), legend="topleft", pch=19, lwd=2, col=cols)
abline(v=seq(0,0.006,0.001), h=seq(0,0.08,0.01), col='lightgray', lty=2)
F7. 儲存產品推薦模型
save(results, file="data/results.rdata")






LS0tDQp0aXRsZTogIlByb2R1Y3TvvJrnlKLlk4HpirfllK7os4foqIoiDQphdXRob3I6ICJHcm91cDQsIDIwMTgvMDgvMDEiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCmVkaXRvcl9vcHRpb25zOiANCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQ0KLS0tDQoNCjx1bCBjbGFzcz0ibmF2Ij4NCiAgPGxpPjxhIGhyZWY9IiNBIj5SRk3liIbnvqQ8L2E+PC9saT4NCiAgPGxpPjxhIGhyZWY9IiNCIj7poaflrqLnlKLlk4Hnn6npmaM8L2E+PC9saT4NCiAgPGxpPjxhIGhyZWY9IiNDIj7kvp3os7zosrfpoLvnjofliIbnvqQ8L2E+PC9saT4NCiAgPGxpPjxhIGhyZWY9IiNEIj7lsLrluqbnuK7muJs8L2E+PC9saT4NCiAgPGxpPjxhIGhyZWY9IiNFIj7os7zniannsYPliIbmnpA8L2E+PC9saT4NCiAgPGxpPjxhIGhyZWY9IiNGIj7nlKLlk4HmjqjolqY8L2E+PC9saT4NCjwvdWw+DQoNCjxkaXYgY2xhc3M9ImNvbW1lbnQiPg0KICBHcm91cDQ6PGJyPg0KICBCMDM0MDIwMDEyIOisnembqOmdnDxicj4NCiAgQjAzNDAyMDAyNyDpmbPpn7vljYk8YnI+DQogIEIwNDQwMTIwMTUg546L6K2v6IuTPGJyPg0KICBNMDY0MTExMDI1IOm7g+Wogeixqjxicj4NCiAgTTA2NDExMTAzOSDnjosgIOasozxicj4NCiAg6KGM5YKz5omAICAgICDlrZ/npaXnkYQ8YnI+DQo8L2Rpdj4NCg0KDQo8YnI+DQoNCmBgYHtyfQ0KcGFja2FnZXMgPSBjKA0KICAiZHBseXIiLCJnZ3Bsb3QyIiwiZ29vZ2xlVmlzIiwiZGV2dG9vbHMiLCJtYWdyaXR0ciIsInNsYW0iLCJpcmxiYSIsInBsb3RseSIsDQogICJhcnVsZXMiLCJhcnVsZXNWaXoiLCJNYXRyaXgiLCJyZWNvbW1lbmRlcmxhYiIpDQpleGlzdGluZyA9IGFzLmNoYXJhY3RlcihpbnN0YWxsZWQucGFja2FnZXMoKVssMV0pDQpmb3IocGtnIGluIHBhY2thZ2VzWyEocGFja2FnZXMgJWluJSBleGlzdGluZyldKSBpbnN0YWxsLnBhY2thZ2VzKHBrZykNCmBgYA0KDQpgYGB7ciB3YXJuaW5nPUYsIG1lc3NhZ2U9RiwgY2FjaGU9RiwgZXJyb3I9Rn0NCnJtKGxpc3Q9bHMoYWxsPVRSVUUpKQ0KTE9BRCA9IFRSVUUNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGdvb2dsZVZpcykNCmxpYnJhcnkoTWF0cml4KQ0KbGlicmFyeShzbGFtKQ0KbGlicmFyeShpcmxiYSkNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeShhcnVsZXMpDQpsaWJyYXJ5KGFydWxlc1ZpeikNCmxpYnJhcnkocmVjb21tZW5kZXJsYWIpDQpgYGANCjxicj48aHI+DQoNCjxkaXYgaWQ9IkEiPg0KIyMjIEEuIFJGTeWIhue+pA0KPC9kaXY+DQoNCmBgYHtyfQ0KbG9hZCgiZGF0YS90ZjAucmRhdGEiKQ0KQSA9IEEwOyBYID0gWDA7IFogPSBaMDsgcm0oQTAsWDAsWjApOyBnYygpDQpgYGANCg0KYGBge3J9DQprID0gODsgc2V0LnNlZWQoNzc3KQ0KQSRncm91cCA9IGttZWFucyhzY2FsZShBWywyOjZdKSwgaykkY2x1c3RlcjsgdGFibGUoQSRncm91cCkNCmBgYA0KDQpgYGB7cn0NCmRmID0gZ3JvdXBfYnkoQSwgZ3JvdXApICU+JSBzdW1tYXJpc2UoDQogIGF2Z19mcmVxdWVuY3kgPSBtZWFuKGYpLA0KICBhdmdfbW9uZXRhcnkgPSBtZWFuKG0pLA0KICB0b3RhbF9yZXZlbnVlID0gc3VtKHJldiksDQogIGdyb3VwX3NpemUgPSBuKCksDQogIGF2Z19yZWNlbmN5ID0gbWVhbihyKSwNCiAgcHJvZml0ID0gc3VtKHJhdykNCiAgKSAlPiUgdW5ncm91cCAlPiUgDQogIG11dGF0ZSgNCiAgICBwY19yZXZlbnVlID0gcm91bmQoMTAwKnRvdGFsX3JldmVudWUvc3VtKHRvdGFsX3JldmVudWUpLDEpLCAjICUgcmV2ZW51ZSBjb250cmliLg0KICAgIHBjX3Byb2ZpdCA9IHJvdW5kKDEwMCpwcm9maXQvc3VtKHByb2ZpdCksMSksICAgICAgICAgICAgICAgICMgJSBwcm9maXQgY29udHJpYi4gDQogICAgZHVtbXkgPSAyMDAxICAjIHRvIGZpdCBnb29nbGVWaXoncyBkYXRhIGZvcm1hdA0KICApICU+JSBkYXRhLmZyYW1lDQpgYGANCg0KYGBge3J9DQpoZWFkKGRmKQ0KYGBgDQoNCmBgYHtyfQ0KcGxvdCggZ3Zpc01vdGlvbkNoYXJ0KA0KICBkZiwgImdyb3VwIiwgImR1bW15IiwNCiAgb3B0aW9ucz1saXN0KHdpZHRoPTgwMCwgaGVpZ2h0PTYwMCkgDQogICkpDQpgYGANCjxicj4NCg0KPGRpdiBjbGFzcz0iY29tbWVudCI+DQoqKuOAkFFVSVrjgJEqKiDlpoLmnpzmiJHlgJHmiopY77yMWei7uOWIhuWIpeaUueaIkGxvZyhhdmdfbW9uZXRhcnkp5ZKMbG9nKGF2Z19yZWNlbmN5Ke+8jOagueaTmuWcluS4iueahOmhr+ekuiAuLi4NCg0KKyDkvaDoqo3ngrrpgJnlrrblupfnm67liY3mnIDpnIDopoHlsI3pgqPkuIDlgIvml4/nvqTvvJ8NCiAgICArIEdyb3VwIDUNCisg5YGa5ZOq5LiA5YCL5YuV5L2c77yfDQogICAgKyDmiJHlgJHlsIdHcm91cCA15a6a576p54K65qC45b+D6aGn5a6i77yM5YGH6Kit6IO95Y+W5b6X6YCZ5Lqb6aGn5a6i6LO86LK357+S5oWj44CB5YGP5aW944CB5pCc5bCL5rS75YuV55qE55u46Zec6LOH5paZ77yM5by35YyW5bCN6YCZ5Lqb6aGn5a6i55qE5LqG6Kej77yM6ICM54K65LqG5aKe5Yqg5YW26LO86LK35Zau5YO56Lef5o+Q6auY6LO86LK35qyh5pW477yM5o6h5Y+W5Lul5LiL5YWp56iu6KGM6Yq35pa55qGI44CCDQogICAgKyDkuIDjgIHlnKjlupflhafpgLLooYzjgIznlKLlk4HlpKflrrnph4/vvIzmipjmlbjotorliJLnrpfjgI3kuYvmlrnlvI/vvIzliLrmv4DpoaflrqLpm5bos7zosrflg7nmoLzovIPpq5jkvYbmgKflg7nmr5TkuZ/ovIPpq5jnmoTnlKLlk4HvvIzku6Xmj5Dpq5jlrqLllq7lg7njgIINCiAgICArIOS6jOOAgeaOqOWHuuS4jeWQjOS4u+mhjOaqlOacn++8muWIhuaekOmAmeS6m+mhp+WuoueUouWTgeizvOiyt+e0gOmMhOaJvuWHuui8g+eGsemWgOeahOeUouWTge+8jOS4pumFjeWQiOeUouWTgeS9v+eUqOmAseacn++8jOS+huaOqOWHuuS4jeWQjOaqlOacn+eahOS4jeWQjOS/g+mKt+Wto++8geWQjOaZguS5n+imgeWkp+mHj+aUvuWHuuWEquaDoOa2iOaBr+iuk+mAmeS6m+aguOW/g+mhp+WuouaOpeinuOWIsO+8jOmBlOaIkOmhp+WuouS+huW6l+mgu+eOh+WinuWKoOOAgg0KDQorIOeCuuS7gOm6vO+8nw0KICAgICsg5Zyo5rOh5rOh5ZyW5LiK5oiR5YCR5Y+v5Lul55yL5Ye65ZyoYXZnX21vbmV0YXJ55ZKMYXZnX3JlY2VuY3kg5YWp6Lu45LiK77yM5q2k5peP576k55qG6LyD5bGs5pa85Lit6ZaT55qE5L2N572u77yM6ZuW54S2R3JvdXAgMuWcqOebuOi8g+aWvEdyb3VwIDXmnInntZXlsI3lhKrli6IoYXZnX21vbmV0YXJ56LyD6auYICYgYXZnX3JlY2VuY3novIPkvY4p77yM5Y+v5pivR3JvdXAgNeaXj+e+pOWkp+Wwj+eCuuaJgOacieaXj+e+pOS4reacgOWkp+eahO+8jOaVheacrOe1hOiqjeeCuuWFtumWi+eZvOa9m+WKm+altemrmOOAgg0KICAgIA0KICA8L2Rpdj4NCg0KDQoNCjxicj48aHI+DQoNCjxkaXYgaWQ9IkIiPg0KIyMjIEIuIOmhp+WuoueUouWTgeefqemZow0KPC9kaXY+DQoNCg0KYGBge3J9DQpuX2Rpc3RpbmN0KFokY3VzdCkgICMgMzIyNTYNCm5fZGlzdGluY3QoWiRwcm9kKSAgIyAyMzc4OQ0KYGBgDQoNCuaTjeS9nOefqemZo+mBi+eul+S5i+WJje+8jOmAmuW4uOaIkeacg+i8ieWFpemAmeWFqeWAi+Wll+S7tg0KYGBge3J9DQpsaWJyYXJ5KE1hdHJpeCkNCmxpYnJhcnkoc2xhbSkNCmBgYA0KDQroo73kvZzpoaflrqLnlKLlk4Hnn6npmaPlhbblr6blvojlv6vjgIHkuZ/lvojlrrnmmJMNCmBgYHtyfQ0KbXggPSB4dGFicyh+IGN1c3QgKyBwcm9kLCBaLCBzcGFyc2U9VCkNCmBgYA0KDQrpoaflrqLnlKLlk4Hnn6npmaPpgJrluLjmmK/kuIDlgIvlvojnqIDnlo/nmoTnn6npmaMNCmBgYHtyfQ0KZGltKG14KQ0KbWVhbihteCA+IDApDQpgYGANCg0K5pyJ5LiA5Lqb55Si5ZOB5rKS5LuA6bq85Lq66LK3DQpgYGB7cn0NCnRhYmxlKGNvbFN1bXMobXgpIDwgMTApICAgICAgICMgMTI1ODggDQpgYGANCg0K5Yiq5Y676LO86LK35qyh5pW45bCP5pa8MTDnmoTnlKLlk4HvvIznhLblvozliKrljrvmspLmnInos7zosrfnlKLlk4HnmoTpoaflrqINCmBgYHtyfQ0KbXggPSBteFssIGNvbFN1bXMobXgpID4gMTBdICAgIyANCm14ID0gbXhbcm93U3VtcyhteCkgPiAwLCBdICAgICMNCmRpbShteCkgICAgICAgICAgICAgICAgICAgICAgICMgMzIyNTY+MzIwNjYgMjM3ODk+MTA2NzUNCmBgYA0KDQrmqqLmn6XkuIDkuIvnn6npmaPoo6HpnaLnmoTlgLzliIbluIMNCmBgYHtyfQ0KbWF4KG14KSAgICAgICAgICAgICAgICAgICAgICAgIyA0OQ0KdGFibGUobXhAeCkgJT4lIHByb3AudGFibGUgJT4lIHJvdW5kKDQpICU+JSBoZWFkKDEwKQ0KYGBgDQoNCjxicj4NCg0KPGRpdiBjbGFzcz0iY29tbWVudCI+DQoqKuOAkFFVSVrjgJEqKiDlpoLmnpxgbXhbaSwgal0gPSAzYO+8jOmAmeihqOekuiAuLi4uDQoNCisgJEN1c3RvbWVyX2kk57i95YWx6LK3JFByb2RpY3RfaiTosrfkuobkuInmrKENCiAgICArIOmhp+WuoueUouWTgeefqemZo+WPquiDvei+qOitmOacieizvOiyt+eUouWTgeeahOihjOeCuu+8jOeEoeazleino+iugOaVuOmHj++8jOaVheefqemZo+WFp+eahOWAvOeCuuizvOiyt+asoeaVuOOAgg0KDQo8L2Rpdj4NCjxicj48aHI+DQo8ZGl2IGlkPSJDIj4NCiMjIyBDLiDkvp3os7zosrfpoLvnjofliIbnvqQgDQo8L2Rpdj4NCg0KIyMjIyMgQzEuIOaUueiuiijnqIDnlo8p55+p6Zmj5qC85byPDQrnqIDnlo/nn6npmaPmnInlvojlpJrnqK7moLzlvI/vvIzkuI3lkIznmoTlt6XlhbfmnIPkvb/nlKjkuI3lkIznmoTmoLzlvI8NCmBgYHtyfQ0KbGlicmFyeShzbGFtKQ0KdG14ID0gYXMobXgsImRnVE1hdHJpeCIpDQp0bXggPSBzaW1wbGVfdHJpcGxldF9tYXRyaXgoDQogIDErdG14QGksIDErdG14QGosIHRteEB4LCBkaW1uYW1lcz1teEBEaW1uYW1lcykNCmRpbSh0bXgpDQpgYGANCg0KIyMjIyMgQzIuIOS8sOioiOWQhOeUouWTgeeahOW5s+Wdh+mHjeimgeaApyAoQXZlcmFnZSBURi1JREYgU2NvcmVzKQ0K5oiR5YCR5YCf55So5paH5a2X5YiG5p6Q6KOh6Z2i5Lyw6KiI5Zau5a2X5Zyo5paH56ug5LmL5Lit55qE6YeN6KaB5oCn55qE5pa55rOVKFRGLUlERinvvIzoqIjnrpflkITnlKLlk4HlnKjmiYDmnInpoaflrqLkuYvplpPnmoTlubPlnYfph43opoHmgKcNCmBgYHtyfQ0KdGZpZGYgPSB0YXBwbHkodG14JHYvcm93X3N1bXModG14KVt0bXgkaV0sIHRteCRqLCBtZWFuKSAqDQogIGxvZzIobnJvdyh0bXgpL2NvbF9zdW1zKHRteCA+IDApKQ0Kc3VtbWFyeSh0ZmlkZikNCmBgYA0KDQorIFRGKHRlcm0tZnJlcXVlbmN5Ke+8muS4gOWAi+aWh+Wtl+WcqOS4gOevh+aWh+eroOWHuuePvueahOasoeaVuOOAgg0KKyBJREbvvJrlnKjlub7nr4fmlofnq6Dlh7rnj77jgIINCisg5Y675o6S6Zmk6auY6aC7546H6LO86LK35Y+v5piv6YeN6KaB5oCn5LiN6auY55qE55Si5ZOB44CCDQpgYGB7ciBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD03LjJ9DQpoaXN0KHRmaWRmKQ0KYGBgDQoNCiMjIyMjIEMzLiDnr6npgbjnlKLlk4HjgIHph43mlrDmjpLliJcNCuevqeWOu+W5s+Wdh+mHjeimgeaAp+avlOi8g+S9jueahOeUouWTgeOAgeeEtuW+jOWwh+eUouWTgeS+neiiq+izvOiyt+asoeaVuOmZjeWGquaOkuWIlw0KYGBge3J9DQp0bXggPSB0bXhbLCB0ZmlkZiA+IG1lYW4odGZpZGYpXSAgICAjIHJlbW92ZSBwcm9kdWN0cyB3aXRoIGxvdyB0ZmlkZiBzY29yZQ0KdG14ID0gdG14Wywgb3JkZXIoLWNvbF9zdW1zKHRteCkpXSAgIyBvcmRlciBwcm9kdWN0IGJ5IGZyZXF1ZW5jeQ0KZGltKHRteCkNCmBgYA0KDQojIyMjIyBDNC4g5L6dKOizvOiyt+mgu+eOh+acgOmrmOeahCnnlKLlk4HlsI3poaflrqLliIbnvqQNCmBgYHtyfQ0Kbm9wPSA0MDAgICMgbm8uIHByb2R1Y3QNCmsgPSAyMDAgICAjIG5vLiBjbHVzdGVyDQpzZXQuc2VlZCgxMTEpOyBrZyA9IGttZWFucyh0bXhbLDE6bm9wXSwgaykkY2x1c3Rlcg0KdGFibGUoa2cpICU+JSBhcy52ZWN0b3IgJT4lIHNvcnQNCmBgYA0KDQojIyMjIyBDNS4g5ZCE576k57WE5bmz5Z2H5bGs5oCnDQrlsIfliIbnvqTntZDmnpzkvbXlhaXpoaflrqLos4fmlpnmoYYoYEFgKQ0KYGBge3J9DQpkZiA9IGlubmVyX2pvaW4oQSwgZGF0YS5mcmFtZSgNCiAgY3VzdCA9IGFzLmludGVnZXIodG14JGRpbW5hbWVzJGN1c3QpLCBrZykpDQpgYGANCg0K6KiI566X5ZCE576k57WE55qE5bmz5Z2H5bGs5oCnDQpgYGB7cn0NCmRmID0gZGF0YS5mcmFtZSgNCiAgYWdncmVnYXRlKC4gfiBrZywgZGZbLGMoMjo3LDExKV0sIG1lYW4pLA0KICBzaXplID0gYXMudmVjdG9yKHRhYmxlKGtnKSkNCiAgKQ0KDQpoZWFkKGRmKSANCmBgYA0KDQojIyMjIyBDNi4g5LqS5YuV5byP5rOh5rOh5ZyWDQpgYGB7cn0NCmRmJGR1bW15ID0gMjAwMSAgICAgICAgICAgICAgICAgICAgICAgICMgZHVtbXkgY29sdW1uIGZvciBnb29nbGVWaXoNCnBsb3QoIGd2aXNNb3Rpb25DaGFydCgNCiAgc3Vic2V0KGRmWyxjKDEsNCw1LDYsOCwyLDMsNyw5KV0sIA0KICAgICAgICAgc2l6ZSA+PSAyMCAmIHNpemUgPD0gNTAwMCAgICAgIyByYW5nZSBvZiBncm91cCBzaXplDQogICAgICAgICApLCANCiAgImtnIiwgImR1bW15Iiwgb3B0aW9ucz1saXN0KHdpZHRoPTgwMCwgaGVpZ2h0PTYwMCkgKSApDQpgYGANCg0KIyMjIyMgQzcuIOWQhOe+pOe1hOeahOS7o+ihqOaAp+eUouWTgSAoU2lnbmF0dXJlIFByb2R1Y3QpDQpgYGB7cn0NClNpZyA9IGZ1bmN0aW9uKGd4LCBQPTEwMDAsIEg9MTApIHsNCiAgcHJpbnQoc3ByaW50ZigiR3JvdXAgJWQ6IE5vLiBDdXN0b21lcnMgPSAlZCIsIGd4LCBzdW0oa2c9PWd4KSkpDQogIGJ4ID0gdG14WywxOlBdDQogIGRhdGEuZnJhbWUobiA9IGNvbF9zdW1zKGJ4W2tnPT1neCxdKSkgJT4lICAgICAgIyBmcmVxdWVuY3kNCiAgICBtdXRhdGUoDQogICAgICBzaGFyZSA9IHJvdW5kKDEwMCpuL2NvbF9zdW1zKGJ4KSwyKSwgICAgICAgIyAlcHJvZCBzb2xkIHRvIHRoaXMgY2x1c3Rlcg0KICAgICAgY29uZiA9IHJvdW5kKDEwMCpuL3N1bShrZz09Z3gpLDIpLCAgICAgICAgICMgJWJ1eSB0aGlzIHByb2R1Y3QsIGdpdmVuIGNsdXN0ZXINCiAgICAgIGJhc2UgPSByb3VuZCgxMDAqY29sX3N1bXMoYngpL25yb3coYngpLDIpLCAjICVidXkgdGhpcyBwcm9kdWN0LCBhbGwgY3VzdCANCiAgICAgIGxpZnQgPSByb3VuZChjb25mL2Jhc2UsMSksICAgICAgICAgICAgICAgICAjIGNvbmYvYmFzZSAgDQogICAgICBuYW1lID0gY29sbmFtZXMoYngpICAgICAgICAgICAgICAgICAgICAgICAgIyBuYW1lIG9mIHByb2QNCiAgICApICU+JSBhcnJhbmdlKGRlc2MobGlmdCkpICU+JSBoZWFkKEgpDQogIH0NCmBgYA0KDQpgYGB7cn0NClNpZygxKQ0KU2lnKDE5KVssMV0lPiUgc29ydChUKQ0KU2lnKDE5KVtTaWcoMTkpJG49PTU3LF0NCmBgYA0KDQorIEfml4/nvqTkuIDlhbHosrfkuoZgbmDlgItQ55Si5ZOBDQorIFDnlKLlk4HmnIlgc2hhcmUlYOaYr+izo+e1pkfml4/nvqQNCisgR+aXj+e+pOavj+S6uuW5s+Wdh+izvOiyt2Bjb25mLzEwMGDlgItQ55Si5ZOBDQorIOaJgOaciemhp+WuouS5i+S4re+8jOavj+S6uuW5s+Wdh+izvOiyt2BiYXNlLzEwMGDlgItQ55Si5ZOBDQorIOi3n+aJgOaciemhp+WuouebuOavlO+8jEfml4/nvqTos7zosrdQ55Si5ZOB55qE5qmf546H5LiK5Y2H5LqGYGxpZnQtMWDlgI0gDQoNCjxkaXYgY2xhc3M9ImNvbW1lbnQiPg0KDQoqKuOAkFFVSVrjgJEqKiDlvp7kupLli5XlvI/ms6Hms6HlnJbnnIvkvoYgLi4uDQoNCisg5bmz5Z2H5a6i5Zau5YO544CB5bmz5Z2H6LO86LK35qyh5pW444CB5bmz5Z2H54ef5pS26LKi542744CB5bmz5Z2H542y5Yip6LKi54275pyA5aSn55qE5YiG5Yil5piv5ZOq5LiA5YCL5peP576k77yfDQogICAgKyDlubPlnYflrqLllq7lg7nvvJpHMTkwDQogICAgKyDlubPlnYfos7zosrfmrKHmlbjvvJpHMTg2DQogICAgKyDlubPlnYfnh5/mlLbosqLnjbvvvJpHMTkNCiAgICArIOW5s+Wdh+eNsuWIqeiyoueNu++8mkcxOQ0KKyDlroPlgJHnmoTnibnlvrXnlKLlk4HliIbliKXmmK/ku4DpurzvvJ8NCiAgICArIDg3MTIwNDUwMDg1MzkNCiAgICArIDQ3MTAwNjMwMzExMDYNCiAgICArIDQ3MTA2MjgwNzkwMTcNCiAgICArIDQ3MTA2MjgwNzkwMTcNCg0KKirjgJBRVUla44CRKiog5Zyo5Lul5LiK6YCZ5YCL5q616JC96KOh6Z2iIC4uLg0KDQorIOaIkeWAkeWIhue+pOeahOWNgOmalOiuiuaVuOaYr+S7gOm6vO+8nw0KICAgICsg5omA5pyJ55qE55Si5ZOB54K65Y2A6ZqU6K6K5pW4DQorIOaIkeWAkeWcqOazoeazoeWcluijoemdouingOWvn+eahOiuiuaVuOaYr+WTquS4gOS6m++8nw0KICAgICsgcijmnIDlvozkuIDmrKHos7zosrfot53ku4rlpKnmlbgpDQogICAgKyBmKOizvOiyt+mgu+eOhykNCiAgICArIG0o5a6i5Zau5YO5KQ0KICAgICsgcmF3KOeNsuWIqeiyoueNuykNCiAgICArIHJldijnh5/mlLbosqLnjbspDQogICAgKyBncm91cF9zaXplKOaXj+e+pOWkp+WwjykNCiAgICArIHMo56ys5LiA5qyh6LO86LK36Led5LuK5aSp5pW4KQ0KKyDlroPlgJHmmK/nm7jlkIznmoTll47vvJ8NCiAgICArIOS4jeWQjOOAgg0KKyDpgJnlgIvliIbmnpDnmoTnqIvluo/vvIzot59BaXJsaW5lcyBDUk3pgqPkuIDlgIvkvovlrZDmnInnlJrpurzkuI3lkIzvvJ8NCjx0YWJsZSBjbGFzcz0idGciPg0KICA8dHI+DQogICAgPHRoIGNsYXNzPSJ0Zy1iNDRyIj48L3RoPg0KICAgIDx0aCBjbGFzcz0idGctYjQ0ciI+6LOH5paZ5Z6L5oWLPC90aD4NCiAgICA8dGggY2xhc3M9InRnLWI0NHIiPuW4uOaFi+WMljwvdGg+DQogICAgPHRoIGNsYXNzPSJ0Zy1iNDRyIj7nr6npgbg8L3RoPg0KICAgIDx0aCBjbGFzcz0idGctYjQ0ciI+5Y2A6ZqU6K6K5pW455u45ZCMPzwvdGg+DQogIDwvdHI+DQogIDx0cj4NCiAgICA8dGQgY2xhc3M9InRnLWI0NHIiPkFpcmxpbmVzPC90ZD4NCiAgICA8dGQgY2xhc3M9InRnLXl3NGwiPjwvdGQ+DQogICAgPHRkIGNsYXNzPSJ0Zy15dzRsIj5ZZXMs6K6T6K6K5pW45LmL6ZaT55qE5qyK6YeN5LiA5qijPC90ZD4NCiAgICA8dGQgY2xhc3M9InRnLXl3NGwiPk5vLOS/neeVmeaJgOacieeahOmhp+WuouWBmuWIhue+pO+8jOeUseWIhue+pOe1kOaenOeci+eJueW+tTwvdGQ+DQogICAgPHRkIGNsYXNzPSJ0Zy15dzRsIj5ZZXMs5Yip55So54++5pyJ6K6K5pW45YiG576k77yM5YaN55So44CM55u45ZCM44CN6K6K5pW46Kej6K6A5LiN5ZCM5YiG576k54m55b61DQogICAgPC90ZD4NCiAgPC90cj4NCiAgPHRyPg0KICAgIDx0ZCBjbGFzcz0idGctYjQ0ciI+6aGn5a6i55Si5ZOB55+p6ZmjPC90ZD4NCiAgICA8dGQgY2xhc3M9InRnLXl3NGwiPuWxrOaWvOeogOmshuefqemZozwvdGQ+DQogICAgPHRkIGNsYXNzPSJ0Zy15dzRsIj5Obyznn6npmaPmnKzouqvngrrmlbjph4/nmoToqIjmlbjmiYDku6XkuI3poIjluLjmhYvljJY8L3RkPg0KICAgIDx0ZCBjbGFzcz0idGcteXc0bCI+DQogICAgWWVzLOacieS7peS4izLnqK7mg4XlvaLvvJoNCiAgICANCiAgICArIOmhp+WuouizvOiyt+WkquWwkeeUouWTgQ0KICAgICsg55Si5ZOB6KKr6LO86LK355qE5qyh5pW46YGO5bCR77yM5omA5o+Q5L6b6LOH6KiK5aSq5bCR77yM5pWF5Yiq6ZmkDQogICAgPC90ZD4NCiAgICA8dGQgY2xhc3M9InRnLXl3NGwiPk5PLOeUqOeUouWTgeizvOiyt+mgu+eOh+WIhue+pO+8jOeUqOWIhue+pOe1kOaenOiIh+mhp+WuouS6pOaYk+izh+aWmeWQiOS9te+8jOeci+e+pOeahOeJueW+tTwvdGQ+DQogIDwvdHI+DQo8L3RhYmxlPiAgICANCisg5oiR5YCR5b6e6YCZ5YCL5L6L5a2Q5a245Yiw5LuA6bq877yfDQogICAgKyDliKnnlKh4dGFic+WPr+S7peWuueaYk+WcsOWwh+mhp+WuoueUouWTgeefqemZo+ijveS9nOWHuuS+huOAgg0KICAgICsg55So6LO86LK355Si5ZOB6aC7546H6IiH55So6aGn5a6i6LO86LK357+S5oWj5omA5YiG576k5Ye65L6G55qE57WQ5p6c5pyD5LiN5LiA5qij44CCDQogICAgKyDlj6/ku6XpgI/pgY7mraTmlrnms5Xlvp7nlKLlk4HnmoTop5Lluqblh7rnmbzljrvlgZrooYzpirfmsbrnrZbjgIINCiAgICArIOWGjeeUseWQhOe+pOe1hOeahOS7o+ihqOaAp+eUouWTgeOAgeS6kuWLleazoeazoeWcluS+hueNsuW+l+abtOWkmuizh+ioiuOAgg0KICAgIA0KPC9kaXY+DQoNCjxicj48aHI+DQoNCg0KDQo8ZGl2IGlkPSJEIj4NCiMjIyBELiDkvb/nlKjlsLrluqbnuK7muJvmlrnms5Xmir3lj5bpoaflrqIo55Si5ZOBKeeahOeJueW+teWQkemHjw0KPC9kaXY+DQogDQoNCiMjIyMjIEQxLiDlt6jlpKflsLrluqbnuK7muJsgKFNWRCwgU2lndWxhciBWYWx1ZSBEZWNvbXBvc2l0aW9uKQ0KYGBge3J9DQpsaWJyYXJ5KGlybGJhKQ0KaWYoTE9BRCkgew0KICBsb2FkKCJkYXRhL3N2ZC5yZGF0YSIpDQp9IGVsc2Ugew0KICBzbXggPSBteA0KICBzbXhAeCA9IHBtaW4oc214QHgsIDIpICAgICAgICAgICAgIyBjYXAgYXQgMiwgc2ltaWxhciB0byBub3JtYWxpemF0aW9uICANCiAgdDAgPSBTeXMudGltZSgpDQogIHN2ZCA9IGlybGJhKHNteCwgDQogICAgICAgICAgICAgIG52PTQwMCwgICAgICAgICAgICAgICAjIGxlbmd0aCBvZiBmZWF0dXJlIHZlY3Rvcg0KICAgICAgICAgICAgICBtYXhpdD04MDAsIHdvcms9ODAwKSAgICANCiAgcHJpbnQoU3lzLnRpbWUoKSAtIHQwKSAgICAgICAgICAgICMgMS44Nzk1IG1pbnMNCiAgc2F2ZShzdmQsIGZpbGUgPSAiZGF0YS9zdmQucmRhdGEiKQ0KfQ0KYGBgDQoNCiMjIyMjIEQyLiDkvp3poaflrqLlkJHph4/lsI3poaflrqLliIbnvqQNCmBgYHtyfQ0Kc2V0LnNlZWQoMTExKTsga2cgPSBrbWVhbnMoc3ZkJHUsIDIwMCkkY2x1c3Rlcg0KdGFibGUoa2cpICU+JSBhcy52ZWN0b3IgJT4lIHNvcnQNCmBgYA0KDQojIyMjIyBEMy4g5LqS5YuV5byP5rOh5rOh5ZyWIChHb29nbGUgTW90aW9uIENoYXJ0KQ0KYGBge1J9DQojIGNsdXN0c3RlciBzdW1tYXJ5DQpkZiA9IGxlZnRfam9pbihBLCBkYXRhLmZyYW1lKCAgICAgICAgIA0KICBjdXN0ID0gYXMuaW50ZWdlcihzbXhARGltbmFtZXMkY3VzdCksIGtnKSkgJT4lIA0KICBncm91cF9ieShrZykgJT4lIHN1bW1hcmlzZSgNCiAgICBhdmdfZnJlcXVlbmN5ID0gbWVhbihmKSwNCiAgICBhdmdfbW9uZXRhcnkgPSBtZWFuKG0pLA0KICAgIGF2Z19yZXZlbnVlX2NvbnRyID0gbWVhbihyZXYpLA0KICAgIGdyb3VwX3NpemUgPSBuKCksDQogICAgYXZnX3JlY2VuY3kgPSBtZWFuKHIpLA0KICAgIGF2Z19ncm9zc19wcm9maXQgPSBtZWFuKHJhdykpICU+JSANCiAgdW5ncm91cCAlPiUgDQogIG11dGF0ZShkdW1teSA9IDIwMDEsIGtnID0gc3ByaW50ZigiRyUwM2QiLGtnKSkgJT4lIA0KICBkYXRhLmZyYW1lDQoNCiMgR29vZ2xlIE1vdGlvbiBDaGFydA0KcGxvdCggZ3Zpc01vdGlvbkNoYXJ0KA0KICBzdWJzZXQoZGYsIGdyb3VwX3NpemUgPj0gMjAgJiBncm91cF9zaXplIDw9IDUwMDApLCAgICAgDQogICJrZyIsICJkdW1teSIsIG9wdGlvbnM9bGlzdCh3aWR0aD04MDAsIGhlaWdodD02MDApICkgKQ0KYGBgDQoNCiMjIyMjIEQ0LiDkupLli5XlvI/ms6Hms6HlnJYgKGdncGxvdCArIHBsb3RseSkNCmBgYHtyfQ0KZmlsdGVyKGRmLCBncm91cF9zaXplID49IDIwICYgZ3JvdXBfc2l6ZSA8PSA1MDAwKSRncm91cF9zaXplICU+JSANCiAgc3FydCAlPiUgcmFuZ2UgICAgIyBmb3IgYnViYmxlIHNpemUgYWRqdXN0bWVudA0KYGBgDQoNCmBgYHtyIGZpZy5oZWlnaHQ9NywgZmlnLndpZHRoPTh9DQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHBsb3RseSkNCg0KcCA9IGRmICU+JSBmaWx0ZXIoZ3JvdXBfc2l6ZSA+PSAyMCAmIGdyb3VwX3NpemUgPD0gNTAwMCkgJT4lIA0KICBnZ3Bsb3QoYWVzKHg9YXZnX2ZyZXF1ZW5jeSwgeT1hdmdfbW9uZXRhcnkpKSArDQogIGdlb21fcG9pbnQoYWVzKHNpemU9Z3JvdXBfc2l6ZSwgY29sPWF2Z19yZXZlbnVlX2NvbnRyKSxhbHBoYT0wLjcpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1rZyksIGFscGhhPTApICsNCiAgc2NhbGVfc2l6ZShyYW5nZT1jKDEuNSwxMikpICsNCiAgI3NjYWxlX2NvbG9yX2dyYWRpZW50KGxvdz0iZ3JlZW4iLGhpZ2g9Im1hZ2VudGEiKSArDQogIHNjYWxlX2NvbG91cl9ncmFkaWVudG4oDQogICAgY29sb3VycyA9IHJldihjKCJyZWQiLCJ5ZWxsb3ciLCJncmVlbiIsImxpZ2h0Ymx1ZSIsImRhcmtibHVlIikpKSArDQogIHRoZW1lX2J3KCkgKyBndWlkZXMoc2l6ZT1GKSArIGxhYnMoDQogICAgdGl0bGU9Iumhp+Wuoumbhue+pCjkvp3os7zosrfnlKLlk4EpIiwNCiAgICBjb2xvcj0i5bmz5Z2H54ef5pS26LKi5427Iiwgc2l6ZT0i6ZuG576k5Lq65pW4IikgKw0KICB4bGFiKCLlubPlnYfos7zosrfmrKHmlbgiKSArIA0KICB5bGFiKCLlubPlnYfos7zosrfph5HpoY0iKQ0KcGxvdGx5X2J1aWxkKHApDQpgYGANCg0KIyMjIyMgRDUuIOe+pOe1hOeahOS7o+ihqOaAp+eUouWTgSAoU2lnbmF0dXJlIFByb2R1Y3QpDQpgYGB7cn0NClNpZygxMzgpDQpgYGANCjxicj48aHI+DQoNCg0KPGRpdiBpZD0iRSI+DQojIyMgRS4g6LO854mp57GD5YiG5p6QIEJhc2tldHMgQW5hbHlzaXMgDQo8L2Rpdj4NCg0KDQpgYGB7cn0NCmRpbShteCkgICAjIDMyMDY2IGN1c3QgKiAxMDY3NSBwcm9kDQpgYGANCg0KIyMjIyMgRTEuIOa6luWCmeizh+aWmSAoZm9yIEFzc29jaWF0aW9uIFJ1bGUgQW5hbHlzaXMpDQpgYGB7cn0NCmxpYnJhcnkoYXJ1bGVzKQ0KbGlicmFyeShhcnVsZXNWaXopDQpieCA9IHN1YnNldChaLCBwcm9kICVpbiUgYXMubnVtZXJpYyhjb2xuYW1lcyhteCkpLCANCiAgICAgICAgICAgIHNlbGVjdD1jKCJjdXN0IiwicHJvZCIpKSAgIyBzZWxlY3QgcHJvZHVjdCBpdGVtcw0KYnggPSBzcGxpdChieCRwcm9kLCBieCRjdXN0KSAgICAgICAgICAjIHNwbGl0IGJ5IGN1c3RvbWVyDQpieCA9IGFzKGJ4LCAidHJhbnNhY3Rpb25zIikgICAgICAgICAgICMgZGF0YSBmb3IgYXJ1bGVzIHBhY2thZ2UNCmBgYA0KDQojIyMjIyBFMi4gVG9wMjAg54ax6LOj55Si5ZOBDQpgYGB7ciBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD03LjJ9DQppdGVtRnJlcXVlbmN5UGxvdChieCwgdG9wTj0yMCwgdHlwZT0iYWJzb2x1dGUiLCBjZXg9MC44KQ0KYGBgDQoNCiMjIyMjIEUzLiDpl5zoga/opo/liYflkoxBcHJpb3Jp5ryU566X5rOVDQoNCumXnOiBr+imj+WJhyhBID0+IEIpDQoNCisgc3VwcG9ydDogQeiiq+izvOiyt+eahOapn+eOhyAoQeeahOWfuuekjuapn+eOhykNCisgY29uZmlkZW5jZTogQeiiq+izvOiyt+aZgu+8jELooqvos7zosrfnmoTmqZ/njocNCisgbGlmdDogQeiiq+izvOiyt+aZgu+8jELooqvos7zosrfnmoTmqZ/njoflop7liqDnmoTlgI3mlbggKOiIh0LnmoTln7rnpI7mqZ/njofnm7jmr5QpDQorIOS4gOiIrOS+huism3N1cHBvcnTjgIFjb25maWRlbmNl5ZKMbGlmdOi2iumrmOeahOmXnOiBr+imj+WJh+i2iumHjeimgQ0KKyBzdXBwb3J044CBY29uZmlkZW5jZeWSjGxpZnToqK3nmoTotorkvY4o6auYKe+8jOaJvuWIsOeahOmXnOiBr+imj+WJh+i2iuWkmijlsJEpDQorIOW7uuitsOS4gOmWi+Wni+aKiuaomea6luioreS9ju+8jOWFiOaJvuWIsOWkmuS4gOm7nuimj+WJh++8jOS5i+W+jOWGjeeUqHN1YnNldOevqemBuOWHuueJueWumueahOimj+WJh+S+hueciw0KDQoNCmBgYHtyfQ0KcnVsZXMgPSBhcHJpb3JpKGJ4LCBwYXJhbWV0ZXI9bGlzdChzdXBwPTAuMDA1LCBjb25mPTAuNikpDQpzdW1tYXJ5KHJ1bGVzKQ0KYGBgDQoNCiMjIyMjIEU0LiDmqqLoppbpl5zoga/opo/liYcNCg0K6Zec6IGv6KaP5YmHIChBID0+IEIp77yaDQoNCisgc3VwcG9ydDogQeiiq+izvOiyt+eahOapn+eOhyAoQeeahOWfuuekjuapn+eOhykNCisgY29uZmlkZW5jZTogQeiiq+izvOiyt+aZgu+8jELooqvos7zosrfnmoTmqZ/njocNCisgbGlmdDogQeiiq+izvOiyt+aZgu+8jELooqvos7zosrfnmoTmqZ/njoflop7liqDnmoTlgI3mlbggKOiIh0LnmoTln7rnpI7mqZ/njofnm7jmr5QpDQoNCmBgYHtyfQ0Kb3B0aW9ucyhkaWdpdHM9NCkNCmluc3BlY3QocnVsZXMpDQpgYGANCg0KYGBge3J9DQojIGluc3RhbGwucGFja2FnZXMoDQojICAgImh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL2Jpbi93aW5kb3dzL2NvbnRyaWIvMy41L2FydWxlc1Zpel8xLjMtMS56aXAiLA0KIyAgIHJlcG9zPU5VTEwpDQoNCiMgaW5zdGFsbC5wYWNrYWdlcygiYXJ1bGVzVml6XzEuMy0xLnppcCIsIHJlcG9zPU5VTEwpDQojIGxpYnJhcnkocGxvdGx5KQ0KIyBwbG90bHlfYXJ1bGVzKHJ1bGVzLGNvbG9ycz1jKCJyZWQiLCJncmVlbiIpLA0KIyAgICAgICAgICAgICAgIG1hcmtlcj1saXN0KG9wYWNpdHk9LjYsc2l6ZT0xMCkpDQojIHBsb3RseV9hcnVsZXMocnVsZXMsbWV0aG9kPSJtYXRyaXgiLA0KIyAgICAgICAgICAgICAgIHNoYWRpbmc9ImxpZnQiLA0KIyAgICAgICAgICAgICAgIGNvbG9ycz1jKCJyZWQiLCAiZ3JlZW4iKSkNCiMgDQpgYGANCg0KIyMjIyMgRTUuIOS6kuWLleWcluihqOmhr+ekug0KYGBge3J9DQpwbG90KHJ1bGVzLGNvbG9ycz1jKCJyZWQiLCJncmVlbiIpLGVuZ2luZT0iaHRtbHdpZGdldCIsDQogICAgIG1hcmtlcj1saXN0KG9wYWNpdHk9LjYsc2l6ZT04KSkNCmBgYA0KDQpgYGB7cn0NCnBsb3QocnVsZXMsbWV0aG9kPSJtYXRyaXgiLHNoYWRpbmc9ImxpZnQiLGVuZ2luZT0iaHRtbHdpZGdldCIsDQogICAgIGNvbG9ycz1jKCJyZWQiLCAiZ3JlZW4iKSkNCmBgYA0KDQojIyMjIyBFNi4g56+p6YG455Si5ZOB44CB5LqS5YuV5byP6Zec6IGv5ZyWDQpgYGB7cn0NCnIxID0gc3Vic2V0KHJ1bGVzLCBzdWJzZXQgPSByaHMgJWluJSBjKCI0NzE5MDkwNzkwMDAwIikpDQpzdW1tYXJ5KHIxKQ0KcGxvdChyMSxtZXRob2Q9ImdyYXBoIixlbmdpbmU9Imh0bWx3aWRnZXQiLGl0ZW1Db2w9ImN5YW4iKSANCmBgYA0KDQorIOazoeazoeWkp+Wwj++8mnN1cHBvcnQ6IEHooqvos7zosrfnmoTmqZ/njocgKEHnmoTln7rnpI7mqZ/njocpDQorIOazoeazoemhj+iJsu+8mmxpZnQ6IEHooqvos7zosrfmmYLvvIxC6KKr6LO86LK355qE5qmf546H5aKe5Yqg55qE5YCN5pW4ICjoiIdC55qE5Z+656SO5qmf546H55u45q+UKQ0KDQoNCmBgYHtyfQ0KcjIgPSBzdWJzZXQocnVsZXMsIHN1YnNldCA9IHJocyAlaW4lIGMoIjQ3MTAwMTE0MDExMzUiKSkNCnN1bW1hcnkocjIpDQpwbG90KHIyLG1ldGhvZD0iZ3JhcGgiLGVuZ2luZT0iaHRtbHdpZGdldCIsaXRlbUNvbD0iY3lhbiIpIA0KYGBgDQo8YnI+PGhyPg0KDQo8ZGl2IGlkPSJGIj4NCiMjIyBGLiDnlKLlk4HmjqjolqYgUHJvZHVjdCBSZWNvbW1lbmRhdGlvbg0KPC9kaXY+DQoNCg0KIyMjIyMgRjEuIOevqemBuOmhp+WuouOAgeeUouWTgQ0K5aSq5bCR6KKr6LO86LK355qE55Si5ZOB5ZKM6LO86LK35aSq5bCR55Si5ZOB55qE6aGn5a6i6YO95LiN6YGp5ZCI5L2/55SoQ29sbGFib3JhdGl2ZSBGaWx0ZXJpbmfpgJnnqK7nlKLlk4Hmjqjolqbmlrnms5XvvIzmiYDku6XmiJHlgJHlhYjlsI3poaflrqLlkoznlKLlk4HlgZrkuIDmrKHnr6npgbgNCmBgYHtyfQ0KbGlicmFyeShyZWNvbW1lbmRlcmxhYikNCnJ4ID0gbXhbLCBjb2xTdW1zKG14ID4gMCkgPj0gNTBdDQpyeCA9IHJ4W3Jvd1N1bXMocnggPiAwKSA+PSAyMCAmIHJvd1N1bXMocnggPiAwKSA8PSAzMDAsIF0NCmRpbShyeCkNCmBgYA0KDQojIyMjIyBGMi4g6YG45pOH55Si5ZOB6KmV5YiG5pa55byPDQrlj6/ku6Xpgbjmk4fopoHnlKgNCg0KKyDos7zosrfmrKHmlbggKHJlYWxSYXRpbmdNYXRyaXgpIOaIlg0KKyDmmK/lkKbos7zosrcgKGJpbmFyeVJhdGluZ01hdHJpeCkNCg0K5YGa5qih5Z6L44CCDQpgYGB7cn0NCnJ4ID0gYXMocngsICJyZWFsUmF0aW5nTWF0cml4IikgICMgcmVhbFJhdGluZ01hdHJpeA0KYnggPSBiaW5hcml6ZShyeCwgbWluUmF0aW5nPTEpICAgIyBiaW5hcnlSYXRpbmdNYXRyaXgNCmRpbShieCkNCmBgYA0KDQojIyMjIyBGMy4g6Kit5a6a5qih5Z6LKOa6lueiuuaApynpqZforYnmlrnlvI8NCmBgYHtyfQ0Kc2V0LnNlZWQoNDMyMSkNCnNjaGVtZSA9IGV2YWx1YXRpb25TY2hlbWUoICAgICANCiAgYngsIG1ldGhvZD0ic3BsaXQiLCB0cmFpbiA9IC43NSwgIGdpdmVuPTUpDQpgYGANCg0KIyMjIyMgRjQuIOioreWumuaOqOiWpuaWueazlSjlj4PmlbgpDQpgYGB7cn0NCmFsZ29yaXRobXMgPSBsaXN0KCAgICAgICAgICAgIA0KICBBUjUzID0gbGlzdChuYW1lPSJBUiIsIHBhcmFtPWxpc3Qoc3VwcG9ydD0wLjAwMDUsIGNvbmZpZGVuY2U9MC4zKSksDQogIEFSNDMgPSBsaXN0KG5hbWU9IkFSIiwgcGFyYW09bGlzdChzdXBwb3J0PTAuMDAwNCwgY29uZmlkZW5jZT0wLjMpKSwNCiAgUkFORE9NID0gbGlzdChuYW1lPSJSQU5ET00iLCBwYXJhbT1OVUxMKSwNCiAgUE9QVUxBUiA9IGxpc3QobmFtZT0iUE9QVUxBUiIsIHBhcmFtPU5VTEwpLA0KICBVQkNGID0gbGlzdChuYW1lPSJVQkNGIiwgcGFyYW09TlVMTCksDQogIElCQ0YgPSBsaXN0KG5hbWU9IklCQ0YiLCBwYXJhbT1OVUxMKSApDQpgYGANCg0KIyMjIyMgRjUuIOW7uuaooeOAgemgkOa4rOOAgempl+itiSjmupbnorrmgKcpDQpgYGB7cn0NCmlmKExPQUQpIHsNCiAgbG9hZCgicmVzdWx0cy5yZGF0YSIpDQp9IGVsc2Ugew0KICB0MCA9IFN5cy50aW1lKCkNCiAgcmVzdWx0cyA9IGV2YWx1YXRlKCAgICAgICAgICAgIA0KICAgIHNjaGVtZSwgYWxnb3JpdGhtcywgdHlwZT0idG9wTkxpc3QiLA0KICAgIG49Yyg1LCAxMCwgMTUsIDIwKSkNCiAgcHJpbnQoU3lzLnRpbWUoKSAtIHQwKQ0KICBzYXZlKHJlc3VsdHMsIGZpbGU9InJlc3VsdHMucmRhdGEiKQ0KfQ0KYGBgDQoNCiMjIyMjIEY2LiDmqKHlnovmupbnorrmgKfmr5TovIMNCmBgYHtyIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTV9DQojIGxvYWQoImRhdGEvcmVzdWx0cy5yZGF0YSIpDQpwYXIobWFyPWMoNCw0LDMsMiksY2V4PTAuOCkNCmNvbHMgPSBjKCJyZWQiLCAibWFnZW50YSIsICJncmF5IiwgIm9yYW5nZSIsICJibHVlIiwgImdyZWVuIikNCnBsb3QocmVzdWx0cywgYW5ub3RhdGU9YygxLDMpLCBsZWdlbmQ9InRvcGxlZnQiLCBwY2g9MTksIGx3ZD0yLCBjb2w9Y29scykNCmFibGluZSh2PXNlcSgwLDAuMDA2LDAuMDAxKSwgaD1zZXEoMCwwLjA4LDAuMDEpLCBjb2w9J2xpZ2h0Z3JheScsIGx0eT0yKQ0KYGBgDQoNCiMjIyMjIEY3LiDlhLLlrZjnlKLlk4HmjqjolqbmqKHlnosNCmBgYHtyfQ0Kc2F2ZShyZXN1bHRzLCBmaWxlPSJkYXRhL3Jlc3VsdHMucmRhdGEiKQ0KYGBgDQoNCjxicj48YnI+PGhyPjxicj48YnI+PGJyPg0KDQo8c3R5bGU+DQoNCi5jYXB0aW9uIHsNCiAgY29sb3I6ICM3Nzc7DQogIG1hcmdpbi10b3A6IDEwcHg7DQp9DQpwIGNvZGUgew0KICB3aGl0ZS1zcGFjZTogaW5oZXJpdDsNCn0NCnByZSB7DQogIHdvcmQtYnJlYWs6IG5vcm1hbDsNCiAgd29yZC13cmFwOiBub3JtYWw7DQogIGxpbmUtaGVpZ2h0OiAxOw0KfQ0KcHJlIGNvZGUgew0KICB3aGl0ZS1zcGFjZTogaW5oZXJpdDsNCn0NCnAsbGkgew0KICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsNCn0NCg0KLnJ7DQogIGxpbmUtaGVpZ2h0OiAxLjI7DQp9DQoNCi5xaXogew0KICBsaW5lLWhlaWdodDogMS43NTsNCiAgYmFja2dyb3VuZDogI2YwZjBmMDsNCiAgYm9yZGVyLWxlZnQ6IDEycHggc29saWQgI2NjZmZjYzsNCiAgcGFkZGluZzogNHB4Ow0KICBwYWRkaW5nLWxlZnQ6IDEwcHg7DQogIGNvbG9yOiAjMDA5OTAwOw0KfQ0KDQp0aXRsZXsNCiAgY29sb3I6ICNjYzAwMDA7DQogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOw0KfQ0KDQpib2R5ew0KICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsNCn0NCg0KaDEsaDIsaDMsaDQsaDV7DQogIGNvbG9yOiAjMDA2NmZmOw0KICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsNCn0NCg0KDQpoM3sNCiAgY29sb3I6ICMwMDg4MDA7DQogIGJhY2tncm91bmQ6ICNlNmZmZTY7DQogIGxpbmUtaGVpZ2h0OiAyOw0KICBmb250LXdlaWdodDogYm9sZDsNCn0NCg0KaDV7DQogIGNvbG9yOiAjMDA2MDAwOw0KICBiYWNrZ3JvdW5kOiAjZjhmOGY4Ow0KICBsaW5lLWhlaWdodDogMS41Ow0KICBmb250LXdlaWdodDogYm9sZDsNCn0NCg0KLnRnICB7Ym9yZGVyLWNvbGxhcHNlOmNvbGxhcHNlO2JvcmRlci1zcGFjaW5nOjA7fQ0KLnRnIHRke2ZvbnQtZmFtaWx5OkFyaWFsLCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNHB4O3BhZGRpbmc6MTBweCA1cHg7Ym9yZGVyLXN0eWxlOnNvbGlkO2JvcmRlci13aWR0aDoxcHg7b3ZlcmZsb3c6aGlkZGVuO3dvcmQtYnJlYWs6bm9ybWFsO2JvcmRlci1jb2xvcjpibGFjazt9DQoudGcgdGh7Zm9udC1mYW1pbHk6QXJpYWwsIHNhbnMtc2VyaWY7Zm9udC1zaXplOjE0cHg7Zm9udC13ZWlnaHQ6bm9ybWFsO3BhZGRpbmc6MTBweCA1cHg7Ym9yZGVyLXN0eWxlOnNvbGlkO2JvcmRlci13aWR0aDoxcHg7b3ZlcmZsb3c6aGlkZGVuO3dvcmQtYnJlYWs6bm9ybWFsO2JvcmRlci1jb2xvcjpibGFjazt9DQoudGcgLnRnLWI0NHJ7YmFja2dyb3VuZC1jb2xvcjojY2JjZWZiO3ZlcnRpY2FsLWFsaWduOnRvcH0NCi50ZyAudGcteXc0bHt2ZXJ0aWNhbC1hbGlnbjp0b3B9DQoNCi5jb21tZW50IHsNCiAgICBib3JkZXItcmFkaXVzOiAxMHB4Ow0KICAgIGJvcmRlcjogMnB4IHNvbGlkICNjYThlZmY7DQogICAgcGFkZGluZzogMjBweDsgDQp9DQoNCi5uYXYgew0KICAgIGxpc3Qtc3R5bGUtdHlwZTogbm9uZTsNCiAgICBtYXJnaW46IDA7DQogICAgcGFkZGluZzogMDsNCiAgICB3aWR0aDogMjAwcHg7DQogICAgYmFja2dyb3VuZC1jb2xvcjogI2YxZjFmMTsNCiAgICBwb3NpdGlvbjogZml4ZWQ7DQogICAgbGVmdDogNXB4Ow0KfQ0KDQpsaSBhIHsNCiAgICBkaXNwbGF5OiBibG9jazsNCiAgICBjb2xvcjogIzAwMDsNCiAgICBwYWRkaW5nOiA4cHggMTZweDsNCiAgICB0ZXh0LWRlY29yYXRpb246IG5vbmU7DQp9DQoNCmxpIGEuYWN0aXZlIHsNCiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjNENBRjUwOw0KICAgIGNvbG9yOiB3aGl0ZTsNCn0NCg0KbGkgYTpob3Zlcjpub3QoLmFjdGl2ZSkgew0KICAgIGJhY2tncm91bmQtY29sb3I6ICM1NTU7DQogICAgY29sb3I6IHdoaXRlOw0KfQ0KDQo8L3N0eWxlPg0KDQo=