rm(list=ls(all=T))
options(digits=3, scipen=40)
library(caTools)
library(magrittr)



1. 價量關係

\[ \sigma = \sqrt{\frac{s^2 \pi^2}{3}} \quad ;\quad s = \sqrt{\frac{3\sigma^2}{\pi^2}}\]

par(cex=0.7, mfcol=c(2,2), mar=c(4,4,4,2))
mu=100; sigma=10; s = sqrt(3 * sigma^2 / pi^2)
curve(dlogis(x,mu,s),60,140,col='blue',lwd=2,xlab="",ylab="",
      main="Logistic Dist. over WTP")
curve(1-plogis(x,mu,s),60,140,col='cyan',lwd=2,xlab="",ylab="",
      main="Logistic Price Response Function") 
curve(dnorm(x,mu,sigma),60,140,col='blue',lwd=2,xlab="",ylab="",
      main="Normal Dist. over WTP")
curve(1-pnorm(x,mu,sigma),60,140,col='cyan',lwd=2,xlab="",ylab="",
      main="Probit Price Response Function")


2. 顧客價值與顧客選擇

\(value = utility - price = V\)

\(V \sim \mathit{logistic} \rightarrow Pr[buy] \sim \mathit{Logistic}\)

\(Pr[buy] = \frac{1}{1+exp(-V)}\)

par(cex=0.8, mar=c(4,4,4,2))
curve(plogis(x,0,4),-30,30,col='cyan',lwd=3,xlab="Value",ylab="Prob[buy]",
      main="Logistic Function")
abline(v=seq(-30,30,5), h=seq(0,1,0.1), col='lightgray',lty=3)
points(0,0.5,pch=20,cex=2.5,col='orange')


2.1 Where does the Value come from?

Value as a function of product attribures - \(V(x_1, x_2, ..., price)\) where \(x\)’s are product option items - - -

3. 顧客選擇模型

1.將資料中要加入的變數先做線性迴歸模型,得出linear function: \(f(x)=y=b_0+b_1 x_1+⋯+b_k x_k\) 這裡的概念是透過一些屬性\((x_1,x_2,x_3…,x_k)\)來得出顧客對這次訂單的效用(y)

2.由剛剛線性迴歸得出的效用(y)代入logistic function可轉換出「購買機率(p)」 p= 1/(1+Exp(-f(X)))

接下來以一份顧客資料來實際操作模型。

bd <- read.csv("RawShort1.csv")
head(bd)

3.1 Modeling

mod1 <- glm(Order ~ Time+Quantity+PricePerLb, data=bd, family=binomial)
summary(mod1)

Call:
glm(formula = Order ~ Time + Quantity + PricePerLb, family = binomial, 
    data = bd)

Deviance Residuals: 
   Min      1Q  Median      3Q     Max  
-1.332  -1.083  -0.805   1.221   3.175  

Coefficients:
            Estimate Std. Error z value            Pr(>|z|)    
(Intercept)  0.64640    0.05509    11.7 <0.0000000000000002 ***
Time        -0.03106    0.00259   -12.0 <0.0000000000000002 ***
Quantity    -0.48567    0.02522   -19.3 <0.0000000000000002 ***
PricePerLb  -0.19365    0.01514   -12.8 <0.0000000000000002 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 21532  on 15886  degrees of freedom
Residual deviance: 20702  on 15883  degrees of freedom
AIC: 20710

Number of Fisher Scoring iterations: 5

3.2 Regression Coefficient 迴歸係數

coef(mod1)
(Intercept)        Time    Quantity  PricePerLb 
     0.6464     -0.0311     -0.4857     -0.1936 


\[V(x) = 0.646 - 0.031 Time - 0.4857 Qty - 0.1937 Price\] \[\text{Logit (log odd):} \quad V(x) = b_0 + b_1 x_1 + ... + b_k x_k = logit = log(odd) = log(\frac{p}{1-p})\]

\[\text{Prob. of Buying:} \quad p = \mathfrak{L}(logit) = \frac{1}{1+exp(-V)}\]

  • 觀念複習:迴歸結果的係數會如何影Prob. Logit與Odd呢?

3.3 Prediction

bd$logOdd = predict(mod1, bd)
bd$ProbBuy = predict(mod1, bd, type='response')
head(bd)
  • 若在指令中未鍵入”type=’response’”,R會計算出顧客價值(V)。
  • 反之,若在指令中鍵入”type=’response’”則會轉化成購買機率。
1/(1+exp(-bd$logOdd[1:6]))
[1] 0.149 0.367 0.472 0.362 0.505 0.387
  • Q: 這行的目的與上面的response有何差異?

3.4 Marginal Effect of Predictors

從迴歸係數估計預測變數對購買機率的邊際效果

\[ \text{odd} = e^{b_0+b_1x_1} \\ e^{b_0+b_1(x_1+1)} = e^{b_0+b_1x_1+b_1} = e^{b_0+b_1x_1} \times e^{b_1}\]

迴歸係數可以透過指數函數轉換為勝率比

  • 結論:logit的邊際效果不用代入指數函數,以變數前的係數即可衡量;odd的邊際效果則要代入指數函數。
coef(mod1) %>% exp
(Intercept)        Time    Quantity  PricePerLb 
      1.909       0.969       0.615       0.824 

注意:勝率和機率之間沒有固定的比率關係

\[ o = \frac{p}{1-p} \quad ; \quad p = \frac{o}{1+o} \]

par(mfcol= c(1,2), cex=0.8)
curve(x/(1-x), 0.01, 0.99, xlab="prob", ylab="odd")
curve(x/(1+x), 0.01, 100, xlab="odd", ylab="prob")

k = 2
p = seq(0.01, 0.99, 0.01)
o = p/(1-p)
pko = (k*o)/(1+k*o)
plot(p, pko - p, type='l', xlab="prob", ylab="delta prob", lwd=2, col="cyan")

par(cex=0.8)
plot(0.5,0,pch=20,col='gray',xlim=c(0,1),ylim=c(-0.8,0.8),
     xlab="base prob.",ylab="prob. increment",
     main = "Odd Ratio = 50, 10, 5, 2, 1, 1/2, 1/5, 1/10, 1/50")
abline(h=seq(-0.8,0.8,0.1), v=seq(0,1,0.05), col='lightgray', lty=3 )
p = seq(0.01, 0.99, 0.01)
for(k in c(1/50, 1/10, 1/5, 1/2, 1, 2, 5, 10, 50)) {
  o = p/(1-p)
  pko = (k*o)/(1+k*o)
  lines(p, pko - p, lwd=2)  
}

  • 給定每個不同機率p,在odd比值的改變下,所影響p的效果也會不一樣。
par(cex=0.8)
plot(0.5,0.5,pch=20,col='gray',xlim=c(0,1),ylim=c(0,1),
     xlab="base prob.",ylab="resultant prob.",
     main = "Odd Ratio = 50, 10, 5, 2, 1, 1/2, 1/5, 1/10, 1/50")
abline(h=seq(0,1,0.1), v=seq(0,1,0.1), col='lightgray', lty=3 )
p = seq(0.01, 0.99, 0.01)
for(k in c(1/50, 1/10, 1/5, 1/2, 1, 2, 5, 10, 50)) {
  o = p/(1-p)
  pko = (k*o)/(1+k*o)
  lines(p, pko, lwd=2)  
}


4. 客製化報價

expProfit =  function(price, mod, data) {
  data$PricePerLb = price
  (price - data$CostPerLb) * predict(mod, data, type="response")
}
expProfit(4.5, mod1, bd[1,])
   1 
0.29 
mean(bd$PricePerLb)
[1] 3.15
optim(3.15, expProfit, method="BFGS", control=list(fnscale=-1), 
      mod=mod1, data=bd[1,] )
$par
[1] 7.09

$value
[1] 0.345

$counts
function gradient 
      11        9 

$convergence
[1] 0

$message
NULL
bd[1,]
price = seq(4, 10, 0.2)
prob = predict(mod1, data.frame(Time=9, Quantity=3.5, PricePerLb=price), type="response")
expReturn = prob * (price - 1.58)
plot(price, expReturn , type='l')

5. 行為經濟模型(是否刪除?)

5.1 Modeling

bd$gain = (bd$LagPrice - bd$PricePerLb)*(bd$LagPrice > bd$PricePerLb)
bd$loss = (bd$PricePerLb - bd$LagPrice)*(bd$PricePerLb > bd$LagPrice)
mod2 = glm(Order ~ Time+Quantity+gain+loss+Quantity:gain+Quantity:loss, 
            data=bd, family=binomial)
summary(mod2)

Call:
glm(formula = Order ~ Time + Quantity + gain + loss + Quantity:gain + 
    Quantity:loss, family = binomial, data = bd)

Deviance Residuals: 
   Min      1Q  Median      3Q     Max  
-1.544  -1.081  -0.751   1.206   3.176  

Coefficients:
              Estimate Std. Error z value             Pr(>|z|)    
(Intercept)    0.13223    0.02789    4.74           0.00000213 ***
Time          -0.03248    0.00261  -12.43 < 0.0000000000000002 ***
Quantity      -0.50230    0.03079  -16.31 < 0.0000000000000002 ***
gain           0.10646    0.02124    5.01           0.00000054 ***
loss          -0.32350    0.02513  -12.87 < 0.0000000000000002 ***
Quantity:gain  0.05545    0.01818    3.05               0.0023 ** 
Quantity:loss -0.13806    0.07437   -1.86               0.0634 .  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 21532  on 15886  degrees of freedom
Residual deviance: 20475  on 15880  degrees of freedom
AIC: 20489

Number of Fisher Scoring iterations: 5

5.2 Model Comparison

par(cex=0.8)
data.frame(
  model1 = predict(mod1, bd, type="response"),
  model2 = predict(mod2, bd, type="response")
  ) %>% colAUC(bd$Order, plotROC=TRUE)
        model1 model2
0 vs. 1  0.614  0.633

5.3 Optimization/Customization

expProfit2 =  function(price, mod, data) {
  data$gain = (price - data$PricePerLb)*(data$LagPrice > price)
  data$loss = (price - data$LagPrice)*(price > data$LagPrice)
  (price - data$CostPerLb) * predict(mod, data, type="response")
}
expProfit2(4.5, mod2, bd[1,])
     1 
0.0655 
optim(4, expProfit2, method="BFGS", control=list(fnscale=-1), 
      mod=mod2, data=bd[1,] )
$par
[1] 2.93

$value
[1] 0.101

$counts
function gradient 
      10        7 

$convergence
[1] 0

$message
NULL





LS0tDQp0aXRsZTogIumhp+WuouWDueWAvOOAgemhp+WuoumBuOaTh+OAgeWuouijveWMluWgseWDuSINCmF1dGhvcjogInRvbnljaHVvQG1haWwubnN5c3UuZWR1LnR3Ig0KZGF0ZTogImByIFN5cy50aW1lKClgIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCisg5bCN5pa86aGn5a6i6aGY5LuY5YO55qC85LmLcGRm77yM54K65L2V5Zyobm9ybWFsIGRpc3Qu6IiHbG9naXN0aWMgZGlzdC7kuK3pgbjmk4flvozogIXlkaI/DQorIOWJjeaDheaPkOimgTog5LiK5qyh5oiR5YCR6Yed5bCN5biC5aC05LmL5YO55qC85Y+N5oeJ5Ye95pW477yM5o6o5Lyw5Ye65pyA6auY5Yip5r2k5LmL5a6a5YO577yM5L2G6YCZ5piv5bCN5biC5aC05omA5pyJ5raI6LK76ICF57Wx5LiA5a6a5YO544CCDQorIOWmguaenOWPr+S7peagueaTmuavj+WAi+a2iOiyu+iAheaJgOaEn+imuueahCLlg7nlgLwi77yM5bCN5LuW5YCR5Yi25a6a5LiN5ZCM5YO55qC877yM55u45bCN5pa857Wx5LiA5YO55qC85pyJ5pu06auY5Yip55uK44CCDQoNCjxicj4NCmBgYHtyIHNldC1vcHRpb25zLCBlY2hvPUZBTFNFLCBjYWNoZT1GQUxTRX0NCmxpYnJhcnkoa25pdHIpDQpvcHRpb25zKHdpZHRoPTEwMCkNCm9wdHNfY2h1bmskc2V0KGNvbW1lbnQgPSBOQSkNCmBgYA0KDQpgYGB7ciAgd2FybmluZz1GLCBtZXNzYWdlPUYsIGNhY2hlPUYsIGVycm9yPUZ9DQpybShsaXN0PWxzKGFsbD1UKSkNCm9wdGlvbnMoZGlnaXRzPTMsIHNjaXBlbj00MCkNCmxpYnJhcnkoY2FUb29scykNCmxpYnJhcnkobWFncml0dHIpDQpgYGANCjxicj4NCg0KLSAtIC0NCg0KIyMjIDEuIOWDuemHj+mXnOS/gg0KDQokJCBcc2lnbWEgPSBcc3FydHtcZnJhY3tzXjIgXHBpXjJ9ezN9fSBccXVhZCA7XHF1YWQgDQpzID0gXHNxcnR7XGZyYWN7M1xzaWdtYV4yfXtccGleMn19JCQNCg0KYGBge3IgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9Nn0NCnBhcihjZXg9MC43LCBtZmNvbD1jKDIsMiksIG1hcj1jKDQsNCw0LDIpKQ0KbXU9MTAwOyBzaWdtYT0xMDsgcyA9IHNxcnQoMyAqIHNpZ21hXjIgLyBwaV4yKQ0KY3VydmUoZGxvZ2lzKHgsbXUscyksNjAsMTQwLGNvbD0nYmx1ZScsbHdkPTIseGxhYj0iIix5bGFiPSIiLA0KICAgICAgbWFpbj0iTG9naXN0aWMgRGlzdC4gb3ZlciBXVFAiKQ0KY3VydmUoMS1wbG9naXMoeCxtdSxzKSw2MCwxNDAsY29sPSdjeWFuJyxsd2Q9Mix4bGFiPSIiLHlsYWI9IiIsDQogICAgICBtYWluPSJMb2dpc3RpYyBQcmljZSBSZXNwb25zZSBGdW5jdGlvbiIpIA0KY3VydmUoZG5vcm0oeCxtdSxzaWdtYSksNjAsMTQwLGNvbD0nYmx1ZScsbHdkPTIseGxhYj0iIix5bGFiPSIiLA0KICAgICAgbWFpbj0iTm9ybWFsIERpc3QuIG92ZXIgV1RQIikNCmN1cnZlKDEtcG5vcm0oeCxtdSxzaWdtYSksNjAsMTQwLGNvbD0nY3lhbicsbHdkPTIseGxhYj0iIix5bGFiPSIiLA0KICAgICAgbWFpbj0iUHJvYml0IFByaWNlIFJlc3BvbnNlIEZ1bmN0aW9uIikNCmBgYA0KDQorIOS4iumdoldUUOiIh+WDueagvOWPjeaHieWHveaVuOS5i+WcluS4re+8jFjou7joiIdZ6Lu45YiG5Yil5Luj6KGo5LuA6bq85ZGiPw0KKyBwZGboiIdjZGbkuYvlt67liKU/IOWmguS9leWcqFLkuK3nlavlh7rpgJnkupvliIbluIM/DQoNCi0gLSAtDQoNCiMjIyAyLiDpoaflrqLlg7nlgLzoiIfpoaflrqLpgbjmk4cNCg0KKyDku4DpurzmmK/poaflrqLlg7nlgLwoVmFsdWUpPyDmr4/lgIvpoaflrqLlg7nlgLznmoTliIbluIPmnIPplbfku4DpurzmqKPlrZA/DQorIOaIkeWAkeaciei+puazleefpemBk+avj+WAi+mhp+WuouWDueWAvOWXjj8NCisg5L2/55So6YKP6Lyv5byP6L+05q245Y+v5L6d5pOa6aGn5a6i6LO86LK35LmL5qmf546HKFByb2JhYmlsaXR5Ke+8jOmgkOa4rOWIsOacgOW+jOmhp+Wuouacieaykuacieiytw0KDQokdmFsdWUgPSB1dGlsaXR5IC0gcHJpY2UgPSBWJA0KDQokViBcc2ltIFxtYXRoaXR7bG9naXN0aWN9IFxyaWdodGFycm93IFByW2J1eV0gXHNpbSBcbWF0aGl0e0xvZ2lzdGljfSQNCg0KJFByW2J1eV0gPSBcZnJhY3sxfXsxK2V4cCgtVil9JA0KDQpgYGB7cn0NCnBhcihjZXg9MC44LCBtYXI9Yyg0LDQsNCwyKSkNCmN1cnZlKHBsb2dpcyh4LDAsNCksLTMwLDMwLGNvbD0nY3lhbicsbHdkPTMseGxhYj0iVmFsdWUiLHlsYWI9IlByb2JbYnV5XSIsDQogICAgICBtYWluPSJMb2dpc3RpYyBGdW5jdGlvbiIpDQphYmxpbmUodj1zZXEoLTMwLDMwLDUpLCBoPXNlcSgwLDEsMC4xKSwgY29sPSdsaWdodGdyYXknLGx0eT0zKQ0KcG9pbnRzKDAsMC41LHBjaD0yMCxjZXg9Mi41LGNvbD0nb3JhbmdlJykNCmBgYA0KPGJyPg0KDQorIOWvpuWLmeS4iu+8jOWboOeCuueEoeazleWPluW+l+mhp+WuouWDueWAvChWYWx1ZSnnmoTliIbluIPvvIzkvYbmiJHlgJHlj6/ku6Xnn6XpgZPnlbbkuIDlgIvnlKLlk4HnmoTlg7nmoLzlkozmlYjnlKjkuIDmqKPnmoTmmYLlgJnvvIzpoaflrqLpgbjmk4fos7zosrfnmoTmqZ/njofmraPmmK8wLjXjgIIo5ZyW5Lit55qE5qmY6bueKQ0KKyDogIzlsIfpoaflrqLlg7nlgLzoiIfos7zosrfmqZ/njofnmoTpl5zkv4LmmK/pgI/pgY5Mb2dpc3RpYyBmdW5jdGlvbui9ieaPm+W+l+efpe+8jOiAjOatpOWcluWJh+aYr0xvZ2lzdGljIGZ1bmN0aW9u55qE5Ye95pW45ZyW5b2i44CCDQoNCg0KIyMjIyAyLjEgV2hlcmUgZG9lcyB0aGUgVmFsdWUgY29tZSBmcm9tPw0KVmFsdWUgYXMgYSBmdW5jdGlvbiBvZiBwcm9kdWN0IGF0dHJpYnVyZXMgLSAkVih4XzEsIHhfMiwgLi4uLCBwcmljZSkkDQp3aGVyZSAkeCQncyBhcmUgcHJvZHVjdCBvcHRpb24gaXRlbXMNCi0gLSAtDQoNCiMjIyAzLiDpoaflrqLpgbjmk4fmqKHlnosNCg0KMS7lsIfos4fmlpnkuK3opoHliqDlhaXnmoTorormlbjlhYjlgZrnt5rmgKfov7TmrbjmqKHlnovvvIzlvpflh7psaW5lYXIgZnVuY3Rpb246DQogJGYoeCk9eT1iXzArYl8xIHhfMSvii68rYl9rIHhfayQNCumAmeijoeeahOamguW/teaYr+mAj+mBjuS4gOS6m+WxrOaApyQoeF8xLHhfMix4XzPigKYseF9rKSTkvoblvpflh7rpoaflrqLlsI3pgJnmrKHoqILllq7nmoTmlYjnlKgoeSkNCg0KMi7nlLHliZvliZvnt5rmgKfov7Tmrbjlvpflh7rnmoTmlYjnlKgoeSnku6PlhaVsb2dpc3RpYyBmdW5jdGlvbuWPr+i9ieaPm+WHuuOAjOizvOiyt+apn+eOhyhwKeOAjQ0KcD0gMS8oMStFeHAoLWYoWCkpKQ0KDQorIOijnOWFheiqquaYju+8mg0KICAgICsJcOeCuuizvOiyt+apn+eOh++8m29kZOeCuuWLneeulyhvZGQ9cC8oMS1wKSnvvJtMb2dpdOaYr+WLneeul+WPlmxvZ+eahOWAvOOAgijmiYDku6Xpgo/ovK/lvI/ov7Tmrbjot5/pgo/ovK/lrozlhajnhKHpl5zvvIEpDQogICAgKyDpgJnkuInogIUocCxvZGQsbG9naXQp5LqL5a+m5LiK55qG5piv5qmf546H55qE5qaC5b+177yM5L2G5omA6KaB6KGo6YGU55qE5pys6LOq5LiN5ZCM44CCDQogICAgKyDmlYjnlKh2YWx1ZSh5KeWFtuWvpuWJm+WlveaYr2xuKG9kZCnnmoTntZDmnpzjgILoi6XmiJHlgJHog73op4Dlr5/liLB2YWx1Ze+8jOWJh+iDveefpemBk+WFtuWIhuW4g++8jOS9huimgeWmguS9leingOWvn+WIsOWRoj8NCg0K5o6l5LiL5L6G5Lul5LiA5Lu96aGn5a6i6LOH5paZ5L6G5a+m6Zqb5pON5L2c5qih5Z6L44CCDQoNCg0KYGBge3J9DQpiZCA8LSByZWFkLmNzdigiUmF3U2hvcnQxLmNzdiIpDQpoZWFkKGJkKQ0KYGBgDQoNCg0KDQojIyMjIDMuMSBNb2RlbGluZw0KYGBge3J9DQptb2QxIDwtIGdsbShPcmRlciB+IFRpbWUrUXVhbnRpdHkrUHJpY2VQZXJMYiwgZGF0YT1iZCwgZmFtaWx5PWJpbm9taWFsKQ0Kc3VtbWFyeShtb2QxKQ0KYGBgDQoNCiMjIyMgMy4yIFJlZ3Jlc3Npb24gQ29lZmZpY2llbnQg6L+05q245L+C5pW4DQoNCmBgYHtyfQ0KY29lZihtb2QxKQ0KYGBgDQo8YnI+DQoNCiQkVih4KSA9IDAuNjQ2IC0gMC4wMzEgVGltZSAtIDAuNDg1NyBRdHkgLSAwLjE5MzcgUHJpY2UkJA0KJCRcdGV4dHtMb2dpdCAobG9nIG9kZCk6fSBccXVhZCANClYoeCkgPSBiXzAgKyBiXzEgeF8xICsgLi4uICsgYl9rIHhfayA9IGxvZ2l0ID0gbG9nKG9kZCkgPSBsb2coXGZyYWN7cH17MS1wfSkkJA0KDQokJFx0ZXh0e1Byb2IuIG9mIEJ1eWluZzp9IFxxdWFkIHAgPSBcbWF0aGZyYWt7TH0obG9naXQpID0gXGZyYWN7MX17MStleHAoLVYpfSQkDQoNCisg6KeA5b+16KSH57+SOui/tOatuOe1kOaenOeahOS/guaVuOacg+WmguS9leW9sVByb2IuIExvZ2l06IiHT2Rk5ZGiPw0KDQojIyMjIDMuMyBQcmVkaWN0aW9uDQpgYGB7cn0NCmJkJGxvZ09kZCA9IHByZWRpY3QobW9kMSwgYmQpDQpiZCRQcm9iQnV5ID0gcHJlZGljdChtb2QxLCBiZCwgdHlwZT0ncmVzcG9uc2UnKQ0KaGVhZChiZCkNCmBgYA0KKyDoi6XlnKjmjIfku6TkuK3mnKrpjbXlhaXigJ10eXBlPeKAmXJlc3BvbnNl4oCZ4oCd77yMUuacg+ioiOeul+WHuumhp+WuouWDueWAvChWKeOAgg0KKyDlj43kuYvvvIzoi6XlnKjmjIfku6TkuK3pjbXlhaXigJ10eXBlPeKAmXJlc3BvbnNl4oCZ4oCd5YmH5pyD6L2J5YyW5oiQ6LO86LK35qmf546H44CCDQoNCmBgYHtyfQ0KMS8oMStleHAoLWJkJGxvZ09kZFsxOjZdKSkNCmBgYA0KKyBROiDpgJnooYznmoTnm67nmoToiIfkuIrpnaLnmoRyZXNwb25zZeacieS9leW3rueVsO+8nw0KDQojIyMjIDMuNCBNYXJnaW5hbCBFZmZlY3Qgb2YgUHJlZGljdG9ycyANCg0K5b6e6L+05q245L+C5pW45Lyw6KiI6aCQ5ris6K6K5pW45bCN6LO86LK35qmf546H55qE6YKK6Zqb5pWI5p6cDQoNCiQkIFx0ZXh0e29kZH0gPSBlXntiXzArYl8xeF8xfSBcXA0KZV57Yl8wK2JfMSh4XzErMSl9ID0gZV57Yl8wK2JfMXhfMStiXzF9ID0gZV57Yl8wK2JfMXhfMX0gXHRpbWVzIGVee2JfMX0kJA0KDQrov7Tmrbjkv4Lmlbjlj6/ku6XpgI/pgY7mjIfmlbjlh73mlbjovYnmj5vngrrli53njofmr5QNCg0KKyDntZDoq5bvvJpsb2dpdOeahOmCiumam+aViOaenOS4jeeUqOS7o+WFpeaMh+aVuOWHveaVuO+8jOS7peiuiuaVuOWJjeeahOS/guaVuOWNs+WPr+ihoemHj++8m29kZOeahOmCiumam+aViOaenOWJh+imgeS7o+WFpeaMh+aVuOWHveaVuOOAgg0KDQpgYGB7cn0NCmNvZWYobW9kMSkgJT4lIGV4cA0KYGBgDQoNCuazqOaEj++8muWLneeOh+WSjOapn+eOh+S5i+mWk+aykuacieWbuuWumueahOavlOeOh+mXnOS/gg0KDQokJCBvID0gXGZyYWN7cH17MS1wfSBccXVhZCA7IFxxdWFkIHAgPSBcZnJhY3tvfXsxK299ICQkIA0KDQpgYGB7ciBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD03fQ0KcGFyKG1mY29sPSBjKDEsMiksIGNleD0wLjgpDQpjdXJ2ZSh4LygxLXgpLCAwLjAxLCAwLjk5LCB4bGFiPSJwcm9iIiwgeWxhYj0ib2RkIikNCmN1cnZlKHgvKDEreCksIDAuMDEsIDEwMCwgeGxhYj0ib2RkIiwgeWxhYj0icHJvYiIpDQpgYGANCg0KYGBge3J9DQprID0gMg0KcCA9IHNlcSgwLjAxLCAwLjk5LCAwLjAxKQ0KbyA9IHAvKDEtcCkNCnBrbyA9IChrKm8pLygxK2sqbykNCnBsb3QocCwgcGtvIC0gcCwgdHlwZT0nbCcsIHhsYWI9InByb2IiLCB5bGFiPSJkZWx0YSBwcm9iIiwgbHdkPTIsIGNvbD0iY3lhbiIpDQpgYGANCg0KYGBge3IgZmlnLmhlaWdodD00LjUsIGZpZy53aWR0aD02fQ0KcGFyKGNleD0wLjgpDQpwbG90KDAuNSwwLHBjaD0yMCxjb2w9J2dyYXknLHhsaW09YygwLDEpLHlsaW09YygtMC44LDAuOCksDQogICAgIHhsYWI9ImJhc2UgcHJvYi4iLHlsYWI9InByb2IuIGluY3JlbWVudCIsDQogICAgIG1haW4gPSAiT2RkIFJhdGlvID0gNTAsIDEwLCA1LCAyLCAxLCAxLzIsIDEvNSwgMS8xMCwgMS81MCIpDQphYmxpbmUoaD1zZXEoLTAuOCwwLjgsMC4xKSwgdj1zZXEoMCwxLDAuMDUpLCBjb2w9J2xpZ2h0Z3JheScsIGx0eT0zICkNCnAgPSBzZXEoMC4wMSwgMC45OSwgMC4wMSkNCmZvcihrIGluIGMoMS81MCwgMS8xMCwgMS81LCAxLzIsIDEsIDIsIDUsIDEwLCA1MCkpIHsNCiAgbyA9IHAvKDEtcCkNCiAgcGtvID0gKGsqbykvKDEraypvKQ0KICBsaW5lcyhwLCBwa28gLSBwLCBsd2Q9MikgIA0KfQ0KYGBgDQorIOe1puWumuavj+WAi+S4jeWQjOapn+eOh3DvvIzlnKhvZGTmr5TlgLznmoTmlLnororkuIvvvIzmiYDlvbHpn79w55qE5pWI5p6c5Lmf5pyD5LiN5LiA5qij44CCDQoNCmBgYHtyIGZpZy5oZWlnaHQ9NC41LCBmaWcud2lkdGg9Nn0NCnBhcihjZXg9MC44KQ0KcGxvdCgwLjUsMC41LHBjaD0yMCxjb2w9J2dyYXknLHhsaW09YygwLDEpLHlsaW09YygwLDEpLA0KICAgICB4bGFiPSJiYXNlIHByb2IuIix5bGFiPSJyZXN1bHRhbnQgcHJvYi4iLA0KICAgICBtYWluID0gIk9kZCBSYXRpbyA9IDUwLCAxMCwgNSwgMiwgMSwgMS8yLCAxLzUsIDEvMTAsIDEvNTAiKQ0KYWJsaW5lKGg9c2VxKDAsMSwwLjEpLCB2PXNlcSgwLDEsMC4xKSwgY29sPSdsaWdodGdyYXknLCBsdHk9MyApDQpwID0gc2VxKDAuMDEsIDAuOTksIDAuMDEpDQpmb3IoayBpbiBjKDEvNTAsIDEvMTAsIDEvNSwgMS8yLCAxLCAyLCA1LCAxMCwgNTApKSB7DQogIG8gPSBwLygxLXApDQogIHBrbyA9IChrKm8pLygxK2sqbykNCiAgbGluZXMocCwgcGtvLCBsd2Q9MikgIA0KfQ0KYGBgDQoNCi0gLSAtDQoNCiMjIyA0LiDlrqLoo73ljJbloLHlg7kNCg0KYGBge3J9DQpleHBQcm9maXQgPSAgZnVuY3Rpb24ocHJpY2UsIG1vZCwgZGF0YSkgew0KICBkYXRhJFByaWNlUGVyTGIgPSBwcmljZQ0KICAocHJpY2UgLSBkYXRhJENvc3RQZXJMYikgKiBwcmVkaWN0KG1vZCwgZGF0YSwgdHlwZT0icmVzcG9uc2UiKQ0KfQ0KZXhwUHJvZml0KDQuNSwgbW9kMSwgYmRbMSxdKQ0KYGBgDQorIOWFiOeUqOesrOS4gOethumhp+WuouS+huWBmmRlbW/igKYNCg0KYGBge3J9DQptZWFuKGJkJFByaWNlUGVyTGIpDQpvcHRpbSgzLjE1LCBleHBQcm9maXQsIG1ldGhvZD0iQkZHUyIsIGNvbnRyb2w9bGlzdChmbnNjYWxlPS0xKSwgDQogICAgICBtb2Q9bW9kMSwgZGF0YT1iZFsxLF0gKQ0KYGBgDQorIG9wdGlt55qE5Y+D5pW46KaB5YWI57Wm5LiA5YCL5Yid5aeL5YC877yM6YCZ6YKK5Lul6LOH5paZcHJpY2XlubPlnYflgLzngrrlj4PogIPjgIINCisg5Zugb3B0aW3lh73mlbjpoJDoqK3ngrrmib7lh7rmnIDlsI/lgLzvvIzlm6DmraTlnKhmbnNjYWxlPS0x5L2/5LuW5om+5Ye65pyA5aSn5YC844CCDQorIOW+nue1kOaenOW+l+efpeWwjeesrOS4gOWAi+mhp+WuoueahOacgOmBqeWDueagvOeCujcuMDnlhYPvvIznjbLliKnngrowLjM0Ne+8jOS9humZpOS6huaVuOaTmue1puaIkeWAkeeahOizh+ioiuS7peWklu+8jOaIkeWAkemChOaYr+W/hemgiOWPg+iAg+WFtuS7luaVuOaTmuaIluWAi+S6uueahOS4u+ingOefpeitmOS+huWBmuWumuWDueOAgg0KYGBge3J9DQpiZFsxLF0NCmBgYA0KDQoNCmBgYHtyfQ0KcHJpY2UgPSBzZXEoNCwgMTAsIDAuMikNCnByb2IgPSBwcmVkaWN0KG1vZDEsIGRhdGEuZnJhbWUoVGltZT05LCBRdWFudGl0eT0zLjUsIFByaWNlUGVyTGI9cHJpY2UpLCB0eXBlPSJyZXNwb25zZSIpDQpleHBSZXR1cm4gPSBwcm9iICogKHByaWNlIC0gMS41OCkNCnBsb3QocHJpY2UsIGV4cFJldHVybiAsIHR5cGU9J2wnKQ0KYGBgDQoNCisg6L+05q245qih5Z6L5bmr5Yqp5oiR5YCR5LqG6KejWOiIh1nkuYvpl5zoga8NCisgb3B0aW1pemF0aW9u5bmr5Yqp5oiR5YCR55Sx6L+05q2457WQ5p6c5om+5Ye65pyA6YGpWQ0KLSAtIC0NCg0KIyMjIDUuIOihjOeCuue2k+a/n+aooeWeiyjmmK/lkKbliKrpmaQ/KQ0KDQojIyMjIDUuMSBNb2RlbGluZw0KYGBge3J9DQpiZCRnYWluID0gKGJkJExhZ1ByaWNlIC0gYmQkUHJpY2VQZXJMYikqKGJkJExhZ1ByaWNlID4gYmQkUHJpY2VQZXJMYikNCmJkJGxvc3MgPSAoYmQkUHJpY2VQZXJMYiAtIGJkJExhZ1ByaWNlKSooYmQkUHJpY2VQZXJMYiA+IGJkJExhZ1ByaWNlKQ0KbW9kMiA9IGdsbShPcmRlciB+IFRpbWUrUXVhbnRpdHkrZ2Fpbitsb3NzK1F1YW50aXR5OmdhaW4rUXVhbnRpdHk6bG9zcywgDQogICAgICAgICAgICBkYXRhPWJkLCBmYW1pbHk9Ymlub21pYWwpDQpzdW1tYXJ5KG1vZDIpDQpgYGANCg0KIyMjIyA1LjIgTW9kZWwgQ29tcGFyaXNvbg0KYGBge3IgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9NH0NCnBhcihjZXg9MC44KQ0KZGF0YS5mcmFtZSgNCiAgbW9kZWwxID0gcHJlZGljdChtb2QxLCBiZCwgdHlwZT0icmVzcG9uc2UiKSwNCiAgbW9kZWwyID0gcHJlZGljdChtb2QyLCBiZCwgdHlwZT0icmVzcG9uc2UiKQ0KICApICU+JSBjb2xBVUMoYmQkT3JkZXIsIHBsb3RST0M9VFJVRSkNCmBgYA0KDQojIyMjIDUuMyBPcHRpbWl6YXRpb24vQ3VzdG9taXphdGlvbg0KYGBge3J9DQpleHBQcm9maXQyID0gIGZ1bmN0aW9uKHByaWNlLCBtb2QsIGRhdGEpIHsNCiAgZGF0YSRnYWluID0gKHByaWNlIC0gZGF0YSRQcmljZVBlckxiKSooZGF0YSRMYWdQcmljZSA+IHByaWNlKQ0KICBkYXRhJGxvc3MgPSAocHJpY2UgLSBkYXRhJExhZ1ByaWNlKSoocHJpY2UgPiBkYXRhJExhZ1ByaWNlKQ0KICAocHJpY2UgLSBkYXRhJENvc3RQZXJMYikgKiBwcmVkaWN0KG1vZCwgZGF0YSwgdHlwZT0icmVzcG9uc2UiKQ0KfQ0KZXhwUHJvZml0Mig0LjUsIG1vZDIsIGJkWzEsXSkNCmBgYA0KDQoNCmBgYHtyfQ0Kb3B0aW0oNCwgZXhwUHJvZml0MiwgbWV0aG9kPSJCRkdTIiwgY29udHJvbD1saXN0KGZuc2NhbGU9LTEpLCANCiAgICAgIG1vZD1tb2QyLCBkYXRhPWJkWzEsXSApDQpgYGANCg0KLSAtIC0NCg0KDQoNCjxicj48YnI+PGJyPjxicj4NCg0KPHN0eWxlPg0KLmNhcHRpb24gew0KICBjb2xvcjogIzc3NzsNCiAgbWFyZ2luLXRvcDogMTBweDsNCn0NCnAgY29kZSB7DQogIHdoaXRlLXNwYWNlOiBpbmhlcml0Ow0KfQ0KcHJlIHsNCiAgd29yZC1icmVhazogbm9ybWFsOw0KICB3b3JkLXdyYXA6IG5vcm1hbDsNCiAgbGluZS1oZWlnaHQ6IDE7DQp9DQpwcmUgY29kZSB7DQogIHdoaXRlLXNwYWNlOiBpbmhlcml0Ow0KfQ0KDQpwLGxpIHsNCiAgZm9udC1mYW1pbHk6ICJUcmVidWNoZXQgTVMiLCAi5b6u6Luf5q2j6buR6auUIiwgIk1pY3Jvc29mdCBKaGVuZ0hlaSI7DQp9DQoNCi5yew0KICBsaW5lLWhlaWdodDogMS4yOw0KfQ0KDQoucWl6IHsNCiAgbGluZS1oZWlnaHQ6IDEuNzU7DQogIGJhY2tncm91bmQ6ICNmMGYwZjA7DQogIGJvcmRlci1sZWZ0OiAxMnB4IHNvbGlkICNjY2ZmY2M7DQogIHBhZGRpbmc6IDRweDsNCiAgcGFkZGluZy1sZWZ0OiAxMHB4Ow0KICBjb2xvcjogIzAwOTkwMDsNCn0NCg0KdGl0bGV7DQogIGNvbG9yOiAjY2MwMDAwOw0KICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsNCn0NCg0KYm9keXsNCiAgZm9udC1mYW1pbHk6ICJUcmVidWNoZXQgTVMiLCAi5b6u6Luf5q2j6buR6auUIiwgIk1pY3Jvc29mdCBKaGVuZ0hlaSI7DQp9DQoNCmgxLGgyLGgzLGg0LGg1ew0KICBjb2xvcjogIzAwNjZmZjsNCiAgZm9udC1mYW1pbHk6ICJUcmVidWNoZXQgTVMiLCAi5b6u6Luf5q2j6buR6auUIiwgIk1pY3Jvc29mdCBKaGVuZ0hlaSI7DQp9DQoNCg0KaDN7DQogIGNvbG9yOiAjMDA4ODAwOw0KICBiYWNrZ3JvdW5kOiAjZTZmZmU2Ow0KICBsaW5lLWhlaWdodDogMjsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQp9DQoNCmg1ew0KICBjb2xvcjogIzAwNjAwMDsNCiAgYmFja2dyb3VuZDogI2Y4ZjhmODsNCiAgbGluZS1oZWlnaHQ6IDEuNTsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQp9DQo8L3N0eWxlPg0K