Deskripsi

Sebagai seseorang bekerja dibidang HR, Anda tidak hanya harus bertanggung jawab atas rekutmen karyawan, tetapi ketika merkeka memustuskan untuk Resign. Masalahnya, tidak seperti rekrutmen karyawan yang bisa disiapkan dari jauh-jauh hari, karyawan yang resign tidak pernah membuat anda kaget. hal tersebut selalu datang tanpa warning meskipun karyawan yang bersangkutan memiliki waktu minimal dua minggu hingga ia akhirnya benar-benar Resign.

Untuk itu anda perlu mempersiapkan diri dari sekarang agar tidak binggung ketika ada seorang karyawan yang mengajukan Resign. jadi pada kesempatan ini kali ini, saya akan melakukan analysis untuk memprediksi karyawan yang akan memutuskan Resign.

Sumber Data

Sumber data yang digunakan dalam laporan ini adalah data pegawai terminasi di perusahaan KIFEST. Dapat diakses melalui : http://bit.ly/KIFEST_HR.

Input Data

Pengambilan sumber data dari google spreedsheet

Data Ekspolarasi

Pada data tersebut, memiliki 654 baris dan 21 variabel

glimpse(kifest_df)
Rows: 654
Columns: 21
$ id                <dbl> 10001537, 10001541, 10001807, 10001812, 10001272, 10001316, 10001190, 10001297, 10001273, 10001275, 10001~
$ gender            <chr> "Male", "Female", "Male", "Male", "Male", "Male", "Male", "Female", "Male", "Male", "Male", "Male", "Male~
$ employee_type     <chr> "Contract", "Contract", "Contract", "Contract", "Contract", "Contract", "Contract", "Contract", "Contract~
$ level_of_position <chr> "PELAKSANA", "PELAKSANA", "PELAKSANA", "PELAKSANA", "PELAKSANA", "PELAKSANA", "PELAKSANA", "PELAKSANA", "~
$ band              <chr> "BOD-5", "BOD-5", "BOD-5", "BOD-5", "BOD-5", "BOD-5", "BOD-5", "BOD-5", "BOD-5", "BOD-5", "BOD-5", "BOD-5~
$ org_unit          <chr> "UNIT PLANT SEMARANG", "UNIT PLANT SEMARANG", "UNIT PENGADAAN", "UNIT PLANT JAKARTA", "UNIT PLANT JAKARTA~
$ org_division      <chr> "SBU MANUFAKTUR", "SBU MANUFAKTUR", "DIVISI SUPPLY CHAIN", "SBU MANUFAKTUR", "SBU MANUFAKTUR", "SBU MANUF~
$ funct_bus         <chr> "MANUFACTURE", "MANUFACTURE", "SUPPORT", "MANUFACTURE", "MANUFACTURE", "MANUFACTURE", "MANUFACTURE", "MAN~
$ position          <chr> "PELAKSANA PRODUKSI KOSMETIK", "PELAKSANA PRODUKSI KOSMETIK", "ADMINISTRASI PEMBELIAN BAHAN KEMASAN", "PE~
$ location          <chr> "Plant Semarang", "Plant Semarang", "Kantor Pusat", "Plant Jakarta", "Plant Jakarta", "Plant Jakarta", "P~
$ age               <dbl> 26, 21, 21, 21, 27, 40, 23, 27, 20, 21, 23, 20, 21, 22, 22, 29, 24, 55, 21, 21, 23, 24, 20, 22, 26, 23, 4~
$ long_work         <dbl> 2, 2, 1, 1, 2, 4, 3, 4, 2, 2, 2, 2, 2, 2, 3, 5, 2, 27, 3, 1, 2, 0, 1, 3, 3, 3, 2, 3, 36, 2, 3, 3, 3, 3, 3~
$ education_type    <chr> "SMA", "SMA", "SMA", "SMA", "SMP", "SMP", "SMP", "S1", "SMA", "SMA", "SMA", "SMP", "SMA", "SMP", "SMA", "~
$ entitas           <chr> "PT KIMIA FARMA Tbk", "PT KIMIA FARMA Tbk", "PT KIMIA FARMA Tbk", "PT KIMIA FARMA Tbk", "PT KIMIA FARMA T~
$ salary            <dbl> 2125000, 2125000, 3457000, 3649000, 3457000, 3607000, 3613000, 4730000, 4289000, 4289000, 4177000, 345700~
$ attrition         <chr> "TERMINATION", "TERMINATION", "TERMINATION", "TERMINATION", "TERMINATION", "TERMINATION", "TERMINATION", ~
$ resign_type       <chr> "Contract_Ends", "Contract_Ends", "Contract_Ends", "Contract_Ends", "Contract_Ends", "Contract_Ends", "Co~
$ marital_status    <chr> "Single", "Single", "Single", "Single", "Single", "Single", "Single", "Single", "Single", "Single", "Sing~
$ dependent         <dbl> 0, 0, 0, 0, 0, 3, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 2, 2, 2, ~
$ grade             <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ~
$ termination_date  <chr> "1/2/2018", "1/2/2018", "1/2/2018", "1/2/2018", "1/3/2018", "1/3/2018", "1/3/2018", "1/3/2018", "1/3/2018~

Dan ringkasan masing-masing variabel sebagai berikut :

summary(kifest_df)
       id              gender          employee_type      level_of_position      band             org_unit        
 Min.   :10000012   Length:654         Length:654         Length:654         Length:654         Length:654        
 1st Qu.:10000931   Class :character   Class :character   Class :character   Class :character   Class :character  
 Median :10001723   Mode  :character   Mode  :character   Mode  :character   Mode  :character   Mode  :character  
 Mean   :10093226                                                                                                 
 3rd Qu.:10001996                                                                                                 
 Max.   :30000063                                                                                                 
 org_division        funct_bus           position           location              age          long_work     education_type    
 Length:654         Length:654         Length:654         Length:654         Min.   :20.00   Min.   : 0.00   Length:654        
 Class :character   Class :character   Class :character   Class :character   1st Qu.:23.00   1st Qu.: 2.00   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Median :26.00   Median : 2.00   Mode  :character  
                                                                             Mean   :34.95   Mean   :11.47                     
                                                                             3rd Qu.:55.00   3rd Qu.:27.00                     
                                                                             Max.   :62.00   Max.   :40.00                     
   entitas              salary          attrition         resign_type        marital_status       dependent          grade       
 Length:654         Min.   : 2125000   Length:654         Length:654         Length:654         Min.   :0.0000   Min.   : 0.000  
 Class :character   1st Qu.: 3803000   Class :character   Class :character   Class :character   1st Qu.:0.0000   1st Qu.: 0.000  
 Mode  :character   Median : 4383000   Mode  :character   Mode  :character   Mode  :character   Median :0.0000   Median : 0.000  
                    Mean   : 5557305                                                            Mean   :0.6269   Mean   : 2.096  
                    3rd Qu.: 5632000                                                            3rd Qu.:1.0000   3rd Qu.: 4.000  
                    Max.   :50000000                                                            Max.   :4.0000   Max.   :16.000  
 termination_date  
 Length:654        
 Class :character  
 Mode  :character  
                   
                   
                   

Analysis Univariat

Analysis Univariat adalah suatu teknik analysis data terhadap satu variabel secara mandiri, tiap variabel dianalysis tanpa dikaitkan dengan variabel lainya.

Pada kasus ini saya menggunakan Analysis Univariat menggunakan visualisaisiggplot2 dengan menjabarkan 4 varaibel ke dalam diagram batang

Berdasarkan diagaram tersebut, bahwa :

  1. Jenis Teriminasi Terdapat 6 jenis teriminasi, urutan terbanyak Contrac_End, Normal_End, Resign, Died, Fraud dan terkahir Demotion

  2. Tanggungan Keluarga Pegawai yang tidak mempunyai tanggunan keluarga paling banyak teriminasi yaitu 0 maskudnya tidak memiliki tanggunan, disusul tanggungan 1,2,3,dan 4.

  3. Jenis Kelamin Pegawai laki-laki lebih banyak teriminasi di badingkan karyawan perempuan.

  4. Level Jabatan Pegawai dengan level jabatan pelaksana paling banyak teriminasi di badingkan dengan level jabatan yang lain.

Analysis Bivariat

Analysis Bivariat adalah dilakukan mengatahui ada tidaknya hubungan kedua variabel

Analysis Bivariat menggunakan ggplot dengan membandingkan varaibel target resign_type dengan variabel salary ke dalam bentuk boxplot

Dari boxplot diatas, dapat disimpulkan bahwa pegawai teriminasi dengan alasan habis kontrak contrac_end paling banyak, dengan rentang gajih antata 3k-40K, disusl dengan Normal_Retirement, Resign, Died, dan Demotion.

Dari analysis ini univariat dan bivariat diatas, saya keructkan target teriminasi menjadi 2 kategori yaitu :

  1. Resign berisi variabel Resign

  2. Termination berisi variabel contrac_end, Normal_Retirement, Resign, Died, dan Demotion. Pengerecutan ini dilakukan karena variabel Resign adalah jenis teriminasi yang diusulkan pegawai atau atas permintaan pegawai senderi.

Analysis bivariat dengan 2 variabel target sebagai berikut :

Analysis Multivariat

Analysis Multivariat adalah membandingkan dua variabel atau melihat pengaruh varibel lainya

Analysis multivariat saya membandingkan variabel target dengan 4 variabel lain (Gaji “salary”, Umur “age”, Lama Kerja “long_work” dan Jumlah tanggungan dependen.

Berdasarkan data diatas, hubungan antara lama kerja dan umur sangar berkaitan erat, disusul hubungan antara umur dan lama kerja di bandung dengan jumlah tanggungan keluarga, dengan detail hubungan variabel tersebut sebagai berikut:

cor(kifest_df[,c("salary", "age", "long_work", 
                      "dependent")])
             salary       age long_work dependent
salary    1.0000000 0.4078812 0.2017021 0.2450270
age       0.4078812 1.0000000 0.9196373 0.6540971
long_work 0.2017021 0.9196373 1.0000000 0.5874774
dependent 0.2450270 0.6540971 0.5874774 1.0000000

Data Clening

Handle missing value with Mean

Detect Missing Value on Data

Dari data tersebut, beberapa data dengan jenis variabel “chr” harus di ubah menjadi jenis variabel “factor” dengan cara sebagai berikut:

kifest_df$resign_type = as.factor(kifest_df$resign_type)
kifest_df$gender = as.factor(kifest_df$gender)
kifest_df$marital_status = as.factor(kifest_df$marital_status)
kifest_df$employee_type = as.factor(kifest_df$employee_type)
kifest_df$level_of_position = as.factor(kifest_df$level_of_position)
kifest_df$attrition = as.factor(kifest_df$attrition)

Handle Outliers

Get Outliers Values

out_salary <- boxplot.stats(kifest_df$salary)$out

Berdasarkan dari boxplot diatas, terdapat pencilan data gaji pegawai di angka 50K, yang akan kami hilangkan agar hasil prediksi lebih baik (tidak terlalu bias), dengan cara sebagai berikut:

Get Outliers Index

out_idx <- which(kifest_df$salary%in% c(out_salary))

Data with out outliers

Setelah dihilangkan pencilan data, diperoleh data seperti boxplot diatas, dari 654 data observasi menjadi 594 data observasi.

*Sebelum dihilangkan pencilan data**

dim (kifest_df)
[1] 654  21

Setelah dihilang menggunakan pencilan data

dim (kifest_df_clean)
[1] 594  21

Training & Test Division

Melihat apakah data berisi nilai NA

sum(is.na(TrainingSet))
[1] 0

Ternyata datanya tidak ada nilai NA

MODELLING

Logistic Regression (LR)

Model_2 <- train(attrition ~ age+salary+marital_status+long_work+gender+employee_type+level_of_position, 
               data = TrainingSet,
               method = "svmPoly",
               na.action = na.omit,
               preProcess=c("scale","center"),
               trControl= trainControl(method="none"),
               tuneGrid = data.frame(degree=1,scale=1,C=1)
)
Warning in preProcess.default(thresh = 0.95, k = 5, freqCut = 19, uniqueCut = 10,  :
  These variables have zero variances: level_of_positionGENERAL MANAGER, level_of_positionMANAGER
Warning in .local(x, ...) : Variable(s) `' constant. Cannot scale data.

Evaluation Testing LR

model.matrix_2
Confusion Matrix and Statistics

             Reference
Prediction    RESIGN TERMINATION
  RESIGN           3           5
  TERMINATION     13          95
                                          
               Accuracy : 0.8448          
                 95% CI : (0.7659, 0.9054)
    No Information Rate : 0.8621          
    P-Value [Acc > NIR] : 0.75520         
                                          
                  Kappa : 0.1741          
                                          
 Mcnemar's Test P-Value : 0.09896         
                                          
            Sensitivity : 0.18750         
            Specificity : 0.95000         
         Pos Pred Value : 0.37500         
         Neg Pred Value : 0.87963         
             Prevalence : 0.13793         
         Detection Rate : 0.02586         
   Detection Prevalence : 0.06897         
      Balanced Accuracy : 0.56875         
                                          
       'Positive' Class : RESIGN          
                                          
table(Model.pred_2)
Model.pred_2
     RESIGN TERMINATION 
          8         108 
table(TestingSet$attrition)

     RESIGN TERMINATION 
         16         100 
logit.perf_2
             Predicted
Actual        RESIGN TERMINATION
  RESIGN           3          13
  TERMINATION      5          95

Classification Tree (Tree)

Evaluation Testing Tree

ctree.perf_2
             Predict
Actual        RESIGN TERMINATION
  RESIGN           9           7
  TERMINATION     10          90

Random Forest (RF)

fit.forest_2

Call:
 randomForest(formula = attrition ~ age + salary + marital_status +      long_work + gender, data = TrainingSet, na.action = na.roughfix) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 2

        OOB estimate of  error rate: 8.79%
Confusion matrix:
            RESIGN TERMINATION class.error
RESIGN          38          26  0.40625000
TERMINATION     16         398  0.03864734

Evaluation Testing RF

forest.perf_2
             Actual
Predict       RESIGN TERMINATION
  RESIGN           5           8
  TERMINATION     11          92

Support Vector Machine (SVM)

Model_svm_2
Support Vector Machines with Polynomial Kernel 

478 samples
  5 predictor
  2 classes: 'RESIGN', 'TERMINATION' 

Pre-processing: scaled (6), centered (6) 
Resampling: None 

Evaluation Testing SVM

Model.testing.confusion_2
Confusion Matrix and Statistics

             Reference
Prediction    RESIGN TERMINATION
  RESIGN           0           0
  TERMINATION     16         100
                                          
               Accuracy : 0.8621          
                 95% CI : (0.7857, 0.9191)
    No Information Rate : 0.8621          
    P-Value [Acc > NIR] : 0.5661446       
                                          
                  Kappa : 0               
                                          
 Mcnemar's Test P-Value : 0.0001768       
                                          
            Sensitivity : 0.0000          
            Specificity : 1.0000          
         Pos Pred Value :    NaN          
         Neg Pred Value : 0.8621          
             Prevalence : 0.1379          
         Detection Rate : 0.0000          
   Detection Prevalence : 0.0000          
      Balanced Accuracy : 0.5000          
                                          
       'Positive' Class : RESIGN          
                                          
svm.perf_2
             Actual
Predict       RESIGN TERMINATION
  RESIGN           0           0
  TERMINATION     16         100

Evaluasi

Performance Comparison

performance <- function(table, n=2){
  tn <- table[1,1]
  fp <- table[1,2]
  fn <- table[2,1]
  tp <- table[2,2]
  
  
  sensitivity = tp/(tp+fn)         # recall
  specificity = tn/(tn+fp) 
  ppp = tp/(tp+fp)                 # precision
  npp = tn/(tn+fn)
  hitrate = (tp+tn)/(tp+tn+fp+fn)  # accuracy
  fiscore   <- 2 * ppp * sensitivity /(ppp+sensitivity)
  
  
  
  result <- paste("Sensitivity = ", round(sensitivity, n) ,
                  "\nSpecificity = ", round(specificity, n),
                  "\nPositive Predictive Value = ", round(ppp, n),
                  "\nNegative Predictive Value = ", round(npp, n),
                  "\nAccuracy = ", round(hitrate, n),
                  "\nF1 Score = ",round(fiscore,n))
  
  cat(result)
  
}

Show Result Logistic Regression

performance(logit.perf_2)
Sensitivity =  0.95 
Specificity =  0.19 
Positive Predictive Value =  0.88 
Negative Predictive Value =  0.38 
Accuracy =  0.84 
F1 Score =  0.91

Show Result Classification Tree

performance(ctree.perf_2)
Sensitivity =  0.9 
Specificity =  0.56 
Positive Predictive Value =  0.93 
Negative Predictive Value =  0.47 
Accuracy =  0.85 
F1 Score =  0.91

Show Result Random Forest

performance(forest.perf_2)  
Sensitivity =  0.89 
Specificity =  0.38 
Positive Predictive Value =  0.92 
Negative Predictive Value =  0.31 
Accuracy =  0.84 
F1 Score =  0.91

Show Result Support Vector Machine

performance(svm.perf_2) 
Sensitivity =  0.86 
Specificity =  NaN 
Positive Predictive Value =  1 
Negative Predictive Value =  0 
Accuracy =  0.86 
F1 Score =  0.93

Rekomedasi

1 Variabel-variabel yang penting dalam memprediksi karyawan yang terminasi adalah: Usia, Lama Bekerja, Salary Jenis Kelamin dan Dependent.

2 Performa algoritma yang paling baik adalah Ctree dengan sensitifitas 0,75, spesifikasi 0,93, akurasi 0,91 dengan memprediksi pegawai yang terminasi 10.34% dari populasi.

3 Penilaian dari segi bisnis, algoritma classification regression lebih baik, karena dapat memprediksi 16% dari populasi, walau sensitifitasnya lebih rendah yaitu 0,25 tetapi lebih baik pada nilai spesikasi 1 dan akurasi 0,9. Dapat di simpulkan, jika dari 116 pegawai yang di prediksi terminasi, dapat dipastikan 16 pegawai yang terminasi dengan alasan mengundurkan diri “resign” (spesifik) sedangkan jika menggunakan algoritma Ctree dimana memprediksi 16 orang terminasi tetapi masih terdapat 4 pegawai yang terminasi dengan alasan normal terminasi dan 12 dengan alasan mengundurkan diri “resign”. Dengan demikian: - HR lebih focus mengelola pegawai yang sudah diprediksi resign. - Perusahaan dapat menghemat biaya rekrutment, pelatiahan dan pesangon.

4 Disarankan untuk menambah variable KPI dan Unit Kerja untuk menilai apakah memberikan kontribusi yang significant atau tidak.

LS0tDQp0aXRsZTogIkRhdGEgQW5hbHlzaXMgTWVtcHJlZGlrc2kgUGVuZ3VuZHVyYW4gRGlyaSBLYXJ5YXdhbiINCmF1dGhvcjogIlxVMDAwMUY1RTMgSmFtYWxsdWRpbiINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHllcw0KICBodG1sX2RvY3VtZW50Og0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdG9jOiB5ZXMNCiAgICBmaWdfd2lkdGg6IDcNCiAgICBmaWdfaGVpZ2h0OiA0LjUNCiAgICB0aGVtZTogcmVhZGFibGUNCiAgICBoaWdobGlnaHQ6IHRhbmdvDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQotLS0NCg0KIyBEZXNrcmlwc2kgDQoNCg0KIVtdKERlc2tyaXBzaS5naWYpDQoNClNlYmFnYWkgc2VzZW9yYW5nIGJla2VyamEgZGliaWRhbmcgSFIsIEFuZGEgdGlkYWsgaGFueWEgaGFydXMgYmVydGFuZ2d1bmcgamF3YWIgYXRhcyByZWt1dG1lbiBrYXJ5YXdhbiwgdGV0YXBpIGtldGlrYSBtZXJrZWthIG1lbXVzdHVza2FuIHVudHVrICoqUmVzaWduKiouIE1hc2FsYWhueWEsIHRpZGFrIHNlcGVydGkgcmVrcnV0bWVuIGthcnlhd2FuIHlhbmcgYmlzYSBkaXNpYXBrYW4gZGFyaSBqYXVoLWphdWggaGFyaSwga2FyeWF3YW4geWFuZyByZXNpZ24gdGlkYWsgcGVybmFoIG1lbWJ1YXQgYW5kYSBrYWdldC4gaGFsIHRlcnNlYnV0IHNlbGFsdSBkYXRhbmcgdGFucGEgKip3YXJuaW5nKiogbWVza2lwdW4ga2FyeWF3YW4geWFuZyBiZXJzYW5na3V0YW4gbWVtaWxpa2kgd2FrdHUgbWluaW1hbCBkdWEgbWluZ2d1IGhpbmdnYSBpYSBha2hpcm55YSBiZW5hci1iZW5hciAqKlJlc2lnbioqLg0KDQpVbnR1ayBpdHUgYW5kYSBwZXJsdSBtZW1wZXJzaWFwa2FuIGRpcmkgZGFyaSBzZWthcmFuZyBhZ2FyIHRpZGFrIGJpbmdndW5nIGtldGlrYSBhZGEgc2VvcmFuZyBrYXJ5YXdhbiB5YW5nIG1lbmdhanVrYW4gKipSZXNpZ24qKi4gamFkaSBwYWRhIGtlc2VtcGF0YW4gaW5pIGthbGkgaW5pLCBzYXlhIGFrYW4gbWVsYWt1a2FuIGFuYWx5c2lzIHVudHVrIG1lbXByZWRpa3NpIGthcnlhd2FuIHlhbmcgYWthbiBtZW11dHVza2FuICoqUmVzaWduKiouDQoNCioqU3VtYmVyIERhdGEqKg0KDQpTdW1iZXIgZGF0YSB5YW5nIGRpZ3VuYWthbiBkYWxhbSBsYXBvcmFuIGluaSBhZGFsYWggZGF0YSBwZWdhd2FpIHRlcm1pbmFzaSBkaSBwZXJ1c2FoYWFuIEtJRkVTVC4gRGFwYXQgZGlha3NlcyBtZWxhbHVpIDogDQpodHRwOi8vYml0Lmx5L0tJRkVTVF9IUi4NCg0KDQojIElucHV0IERhdGENCg0KDQohW10oSW5wdXQgZGF0YS5naWYpDQoNCg0KUGVuZ2FtYmlsYW4gc3VtYmVyIGRhdGEgZGFyaSAqKmdvb2dsZSBzcHJlZWRzaGVldCoqIA0KDQpgYGB7cn0NCnJtKGxpc3QgPSBscygpKQ0KbGlicmFyeShnc2hlZXQpDQpsaWJyYXJ5KHJlYWN0YWJsZSkNCg0Ka2lmZXN0X2RmIDwtIGdzaGVldDJ0YmwoJ2RvY3MuZ29vZ2xlLmNvbS9zcHJlYWRzaGVldHMvZC8xUkszb2JodFFSenVLTWxqcHpUSXl6Q0FQYUpuZkFQNnpqRUx4OGJJTGtxZy9lZGl0P3VzcD1zaGFyaW5nJykNCg0KcmVhY3RhYmxlKA0KICBraWZlc3RfZGYsDQogICAgZGVmYXVsdFBhZ2VTaXplID0gNSwNCiAgYm9yZGVyZWQgPSBUUlVFLCBzdHJpcGVkID0gVFJVRSwgaGlnaGxpZ2h0ID0gVFJVRQ0KICApDQpgYGANCg0KDQoNCiMgRGF0YSBFa3Nwb2xhcmFzaSANCg0KDQohW10oRGF0YSBla3NwbG9yYXNpLmdpZikNCg0KUGFkYSBkYXRhIHRlcnNlYnV0LCBtZW1pbGlraSA2NTQgYmFyaXMgZGFuIDIxIHZhcmlhYmVsDQoNCg0KYGBge3J9DQpnbGltcHNlKGtpZmVzdF9kZikNCmBgYA0KDQpEYW4gcmluZ2thc2FuIG1hc2luZy1tYXNpbmcgdmFyaWFiZWwgc2ViYWdhaSBiZXJpa3V0IDogDQoNCmBgYHtyfQ0Kc3VtbWFyeShraWZlc3RfZGYpDQpgYGANCg0KDQojIEFuYWx5c2lzIFVuaXZhcmlhdCANCg0KDQohW10oVW5pdmFyaWF0LmdpZikNCg0KDQoNCkFuYWx5c2lzIFVuaXZhcmlhdCBhZGFsYWggc3VhdHUgdGVrbmlrICoqYW5hbHlzaXMqKiBkYXRhIHRlcmhhZGFwIHNhdHUgdmFyaWFiZWwgc2VjYXJhIG1hbmRpcmksIHRpYXAgdmFyaWFiZWwgZGlhbmFseXNpcyB0YW5wYSBkaWthaXRrYW4gZGVuZ2FuIHZhcmlhYmVsIGxhaW55YS4gDQoNClBhZGEga2FzdXMgaW5pIHNheWEgbWVuZ2d1bmFrYW4gQW5hbHlzaXMgVW5pdmFyaWF0IG1lbmdndW5ha2FuICB2aXN1YWxpc2Fpc2kqKmdncGxvdDIqKiBkZW5nYW4gbWVuamFiYXJrYW4gNCB2YXJhaWJlbCBrZSBkYWxhbSBkaWFncmFtIGJhdGFuZw0KDQpgYGB7cn0NCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGdyaWRFeHRyYSkNCg0KY2JQYWxldHRlIDwtIGMoIiM5OTk5OTkiLCAiI0U2OUYwMCIsICIjNTZCNEU5IiwgIiMwMDlFNzMiLCAiI0YwRTQ0MiIsICIjMDA3MkIyIiwgIiNENTVFMDAiLCAiI0NDNzlBNyIpDQoNCnAxIDwtIGtpZmVzdF9kZiAlPiUNCiAgZ3JvdXBfYnkocmVzaWduX3R5cGUpICU+JQ0KICBzdW1tYXJpc2UoY291bnRzID0gbigpKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gYXMuZmFjdG9yKHJlc2lnbl90eXBlKSwgeSA9IGNvdW50cykpICsgZ2VvbV9iYXIoc3RhdCA9ICdpZGVudGl0eScsDQogIGZpbGwgPSAiY29yYWwxIikgKyBnZ3RpdGxlKCJKZW5pcyBUZXJtaW5hc2kiKSArZ2VvbV90ZXh0KGFlcyhsYWJlbD1jb3VudHMpLCBzaXplID0gMi41LCANCiAgcG9zaXRpb249cG9zaXRpb25fZG9kZ2Uod2lkdGg9MC4yKSwgdmp1c3Q9LTAuMjUpICsgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0xMCksDQogIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPTcsYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwNCiAgYXhpcy50aXRsZS54PWVsZW1lbnRfYmxhbmsoKSkgKyBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCA5MDApKQ0KDQpwMiA8LSBraWZlc3RfZGYgJT4lDQogIGdyb3VwX2J5KGRlcGVuZGVudCkgJT4lDQogIHN1bW1hcmlzZShjb3VudHMgPSBuKCkpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBhcy5mYWN0b3IoZGVwZW5kZW50KSwgeSA9IGNvdW50cykpICsgZ2VvbV9iYXIoc3RhdCA9ICdpZGVudGl0eScsDQogIGZpbGwgPSAiY29yYWwxIikgKyBnZ3RpdGxlKCJUYW5nZ3VuZ2FuIEtlbHVhcmdhIikgK2dlb21fdGV4dChhZXMobGFiZWw9Y291bnRzKSwgc2l6ZSA9IDIuNSwgDQogIHBvc2l0aW9uPXBvc2l0aW9uX2RvZGdlKHdpZHRoPTAuMiksIHZqdXN0PS0wLjI1KSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9MTApLA0KICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID03LGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksDQogIGF4aXMudGl0bGUueD1lbGVtZW50X2JsYW5rKCkpICsgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgOTAwKSkNCg0KcDMgPC0ga2lmZXN0X2RmICU+JQ0KICBncm91cF9ieShnZW5kZXIpICU+JQ0KICBzdW1tYXJpc2UoY291bnRzID0gbigpKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gYXMuZmFjdG9yKGdlbmRlciksIHkgPSBjb3VudHMpKSArIGdlb21fYmFyKHN0YXQgPSAnaWRlbnRpdHknLA0KICBmaWxsID0gImNvcmFsMSIpICsgZ2d0aXRsZSgiSmVuaXMgS2VsYW1pbiIpICtnZW9tX3RleHQoYWVzKGxhYmVsPWNvdW50cyksIHNpemUgPSAyLjUsIA0KICBwb3NpdGlvbj1wb3NpdGlvbl9kb2RnZSh3aWR0aD0wLjIpLCB2anVzdD0tMC4yNSkgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPTEwKSwNCiAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9NyxhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLA0KICBheGlzLnRpdGxlLng9ZWxlbWVudF9ibGFuaygpKSArIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDkwMCkpDQoNCnA0IDwtIGtpZmVzdF9kZiAlPiUNCiAgZ3JvdXBfYnkobGV2ZWxfb2ZfcG9zaXRpb24pICU+JQ0KICBzdW1tYXJpc2UoY291bnRzID0gbigpKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gYXMuZmFjdG9yKGxldmVsX29mX3Bvc2l0aW9uKSwgeSA9IGNvdW50cykpICsgZ2VvbV9iYXIoc3RhdCA9ICdpZGVudGl0eScsDQogIGZpbGwgPSAiY29yYWwxIikgKyBnZ3RpdGxlKCJMZXZlbCBKYWJhdGFuIikgK2dlb21fdGV4dChhZXMobGFiZWw9Y291bnRzKSwgc2l6ZSA9IDIuNSwgDQogIHBvc2l0aW9uPXBvc2l0aW9uX2RvZGdlKHdpZHRoPTAuMiksIHZqdXN0PS0wLjI1KSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9MTApLA0KICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID03LGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksDQogIGF4aXMudGl0bGUueD1lbGVtZW50X2JsYW5rKCkpICsgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgOTAwKSkNCg0KZ3JpZC5hcnJhbmdlKHAxLCBwMiwgcDMsIHA0LCBucm93ID0gMiwgbmNvbCA9IDIpDQoNCmBgYA0KDQoNCkJlcmRhc2Fya2FuIGRpYWdhcmFtIHRlcnNlYnV0LCBiYWh3YSA6IA0KDQoxLiBKZW5pcyBUZXJpbWluYXNpIA0KVGVyZGFwYXQgNiBqZW5pcyB0ZXJpbWluYXNpLCB1cnV0YW4gdGVyYmFueWFrICoqQ29udHJhY19FbmQsIE5vcm1hbF9FbmQsIFJlc2lnbiwgRGllZCwgRnJhdWQgZGFuIHRlcmthaGlyIERlbW90aW9uKioNCg0KMi4gVGFuZ2d1bmdhbiBLZWx1YXJnYSANClBlZ2F3YWkgeWFuZyB0aWRhayBtZW1wdW55YWkgdGFuZ2d1bmFuIGtlbHVhcmdhIHBhbGluZyBiYW55YWsgdGVyaW1pbmFzaSB5YWl0dSAwIG1hc2t1ZG55YSB0aWRhayBtZW1pbGlraSB0YW5nZ3VuYW4sIGRpc3VzdWwgdGFuZ2d1bmdhbiAxLDIsMyxkYW4gNC4NCg0KMy4gSmVuaXMgS2VsYW1pbiANClBlZ2F3YWkgbGFraS1sYWtpIGxlYmloIGJhbnlhayB0ZXJpbWluYXNpIGRpIGJhZGluZ2thbiBrYXJ5YXdhbiBwZXJlbXB1YW4uDQoNCjQuIExldmVsIEphYmF0YW4gDQpQZWdhd2FpIGRlbmdhbiBsZXZlbCBqYWJhdGFuIHBlbGFrc2FuYSBwYWxpbmcgYmFueWFrIHRlcmltaW5hc2kgZGkgYmFkaW5na2FuIGRlbmdhbiBsZXZlbCBqYWJhdGFuIHlhbmcgbGFpbi4NCg0KDQoNCiMgQW5hbHlzaXMgQml2YXJpYXQNCg0KDQohW10oQW5hbHlzaXMgYml2YXJpYXQuZ2lmKQ0KDQoNCkFuYWx5c2lzIEJpdmFyaWF0IGFkYWxhaCBkaWxha3VrYW4gbWVuZ2F0YWh1aSBhZGEgdGlkYWtueWEgaHVidW5nYW4ga2VkdWEgdmFyaWFiZWwgDQoNCg0KQW5hbHlzaXMgQml2YXJpYXQgbWVuZ2d1bmFrYW4gKipnZ3Bsb3QqKiBkZW5nYW4gbWVtYmFuZGluZ2thbiB2YXJhaWJlbCB0YXJnZXQgKipyZXNpZ25fdHlwZSoqIGRlbmdhbiB2YXJpYWJlbCAqKnNhbGFyeSoqIGtlIGRhbGFtIGJlbnR1ayBib3hwbG90DQoNCmBgYHtyfQ0Ka2lmZXN0X2RmJGxldmVsX29mX3Bvc2l0aW9uIDwtIGZhY3RvcihraWZlc3RfZGYkbGV2ZWxfb2ZfcG9zaXRpb24pDQoNCg0KZ2dwbG90KGRhdGE9a2lmZXN0X2RmLCBhZXMoeD1yZXNpZ25fdHlwZSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICB5PXNhbGFyeSkpICsgDQogIGdlb21fYm94cGxvdCgpICsgDQogIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBjKDEwMDAwMDAsMjAwMDAwMCwzMDAwMDAwLDQwMDAwMDAsNTAwMDAwMCwxMDAwMDAwMCwxNTAwMDAwMCwyMDAwMDAwMCwzMDAwMDAwMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgNDAwMDAwMDAsNTAwMDAwMDANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLDYwMDAwLDcwMDAwKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIjFLIiwiMksiLCIzSyIsIjRLIiwiNUsiLCIxMEsiLCIxNUsiLCIyMEsiLCIzMEsiLCI0MEsiLCI1MEsiLCI2MEsiLCI3MEsiKSwNCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMTAwMDAwMCw2MDAwMDAwMCkpKw0KICANCiAgZ2VvbV9qaXR0ZXIoYWxwaGEgPSAwLjMsDQogICAgICAgICAgICAgIGNvbG9yID0gImJsdWUiLCANCiAgICAgICAgICAgICAgd2lkdGggPSAwLjIpDQpgYGANCg0KDQpEYXJpIGJveHBsb3QgZGlhdGFzLCBkYXBhdCBkaXNpbXB1bGthbiBiYWh3YSBwZWdhd2FpIHRlcmltaW5hc2kgZGVuZ2FuIGFsYXNhbiBoYWJpcyBrb250cmFrICoqY29udHJhY19lbmQqKiBwYWxpbmcgYmFueWFrLCBkZW5nYW4gcmVudGFuZyBnYWppaCBhbnRhdGEgM2stNDBLLCBkaXN1c2wgZGVuZ2FuICoqTm9ybWFsX1JldGlyZW1lbnQqKiwgKipSZXNpZ24qKiwgKipEaWVkKiosIGRhbiAqKkRlbW90aW9uKiouDQoNCkRhcmkgYW5hbHlzaXMgaW5pIHVuaXZhcmlhdCBkYW4gYml2YXJpYXQgZGlhdGFzLCBzYXlhIGtlcnVjdGthbiB0YXJnZXQgdGVyaW1pbmFzaSBtZW5qYWRpIDIga2F0ZWdvcmkgeWFpdHUgOiANCg0KMS4gKipSZXNpZ24qKiBiZXJpc2kgdmFyaWFiZWwgKipSZXNpZ24qKg0KDQoyLiAqKlRlcm1pbmF0aW9uKiogYmVyaXNpIHZhcmlhYmVsICoqY29udHJhY19lbmQqKiwgKipOb3JtYWxfUmV0aXJlbWVudCoqLCAqKlJlc2lnbioqLCAqKkRpZWQqKiwgZGFuICoqRGVtb3Rpb24qKi4gDQpQZW5nZXJlY3V0YW4gaW5pIGRpbGFrdWthbiBrYXJlbmEgdmFyaWFiZWwgKipSZXNpZ24qKiBhZGFsYWggamVuaXMgdGVyaW1pbmFzaSB5YW5nIGRpdXN1bGthbiBwZWdhd2FpIGF0YXUgYXRhcyBwZXJtaW50YWFuIHBlZ2F3YWkgc2VuZGVyaS4gDQoNCkFuYWx5c2lzIGJpdmFyaWF0IGRlbmdhbiAyIHZhcmlhYmVsIHRhcmdldCBzZWJhZ2FpIGJlcmlrdXQgOiANCg0KDQpgYGB7cn0NCnA1IDwtIGtpZmVzdF9kZiAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gYWdlLCBmaWxsID0gYXR0cml0aW9uKSkgKyBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjUpICsgZ2d0aXRsZSgiQWdlIikgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPTEwKSxheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID03LGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksYXhpcy50aXRsZS54PWVsZW1lbnRfYmxhbmsoKSkNCg0KcDYgPC0ga2lmZXN0X2RmICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBsb25nX3dvcmssIGZpbGwgPSBhdHRyaXRpb24pKSArIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuNSkgKyBnZ3RpdGxlKCJMb25nIFdvcmsiKSAgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPTEwKSxheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID03LGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksYXhpcy50aXRsZS54PWVsZW1lbnRfYmxhbmsoKSkNCg0KcDcgPC0ga2lmZXN0X2RmICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBkZXBlbmRlbnQsIGZpbGwgPSBhdHRyaXRpb24pKSArIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuNSkgKyBnZ3RpdGxlKCJEZXBlbmRlbnQiKSAgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPTEwKSxheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID03LGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksYXhpcy50aXRsZS54PWVsZW1lbnRfYmxhbmsoKSkNCg0KcDggPC0ga2lmZXN0X2RmICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBncmFkZSwgZmlsbCA9IGF0dHJpdGlvbikpICsgZ2VvbV9kZW5zaXR5KGFscGhhID0gMC41KSArIGdndGl0bGUoIkdyYWRlIikgICsgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0xMCksYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9NyxhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLGF4aXMudGl0bGUueD1lbGVtZW50X2JsYW5rKCkpDQoNCmdyaWQuYXJyYW5nZShwNSwgcDYsIHA3LCBwOCwgbnJvdyA9IDIsIG5jb2wgPSAyKQ0KYGBgDQoNCg0KIyBBbmFseXNpcyBNdWx0aXZhcmlhdCANCg0KDQohW10oQW5hbHlzaXMgbXVsdGl2YXJpYXQuZ2lmKQ0KDQoNCkFuYWx5c2lzIE11bHRpdmFyaWF0IGFkYWxhaCBtZW1iYW5kaW5na2FuIGR1YSB2YXJpYWJlbCBhdGF1IG1lbGloYXQgcGVuZ2FydWggdmFyaWJlbCBsYWlueWENCg0KDQpBbmFseXNpcyBtdWx0aXZhcmlhdCBzYXlhIG1lbWJhbmRpbmdrYW4gdmFyaWFiZWwgdGFyZ2V0IGRlbmdhbiA0IHZhcmlhYmVsIGxhaW4gKEdhamkg4oCcc2FsYXJ54oCdLCBVbXVyIOKAnGFnZeKAnSwgTGFtYSBLZXJqYSDigJxsb25nX3dvcmvigJ0gZGFuIEp1bWxhaCB0YW5nZ3VuZ2FuICoqZGVwZW5kZW4qKi4NCg0KYGBge3J9DQpsaWJyYXJ5KGNvcnJncmFtKQ0KY29ycmdyYW0oa2lmZXN0X2RmWyxjKCJzYWxhcnkiLCAiYWdlIiwgImxvbmdfd29yayIsIA0KICAgICAgICAgICAgICAgICAgICAgICJkZXBlbmRlbnQiKV0sDQogICAgICAgICBtYWluPSJDb3JyZWxhdGlvbiBCZXR3ZWVuIENvbnRpbm91cyBWYXJpYWJsZSIpDQpgYGANCg0KDQpCZXJkYXNhcmthbiBkYXRhIGRpYXRhcywgaHVidW5nYW4gYW50YXJhIGxhbWEga2VyamEgZGFuIHVtdXIgc2FuZ2FyIGJlcmthaXRhbiBlcmF0LCBkaXN1c3VsIGh1YnVuZ2FuIGFudGFyYSB1bXVyIGRhbiBsYW1hIGtlcmphIGRpIGJhbmR1bmcgZGVuZ2FuIGp1bWxhaCB0YW5nZ3VuZ2FuIGtlbHVhcmdhLCBkZW5nYW4gZGV0YWlsIGh1YnVuZ2FuIHZhcmlhYmVsIHRlcnNlYnV0IHNlYmFnYWkgYmVyaWt1dDoNCg0KDQpgYGB7cn0NCmNvcihraWZlc3RfZGZbLGMoInNhbGFyeSIsICJhZ2UiLCAibG9uZ193b3JrIiwgDQogICAgICAgICAgICAgICAgICAgICAgImRlcGVuZGVudCIpXSkNCmBgYA0KDQoNCiMgRGF0YSBDbGVuaW5nDQoNCg0KIVtdKERhdGEgY2xlYW5pbmcuZ2lmKQ0KDQoNCg0KIyMgSGFuZGxlIG1pc3NpbmcgdmFsdWUgd2l0aCBNZWFuDQoNCkRldGVjdCBNaXNzaW5nIFZhbHVlIG9uIERhdGENCg0KYGBge3J9DQpraWZlc3RfZGYgJT4lIHN1bW1hcmlzZV9hbGwoIH4gc3VtKGlzLm5hKC4pKQ0KKQ0KYGBgDQoNCg0KRGFyaSBkYXRhIHRlcnNlYnV0LCBiZWJlcmFwYSBkYXRhIGRlbmdhbiBqZW5pcyB2YXJpYWJlbCDigJxjaHLigJ0gaGFydXMgZGkgdWJhaCBtZW5qYWRpIGplbmlzIHZhcmlhYmVsIOKAnGZhY3RvcuKAnSBkZW5nYW4gY2FyYSBzZWJhZ2FpIGJlcmlrdXQ6DQoNCmBgYHtyfQ0Ka2lmZXN0X2RmJHJlc2lnbl90eXBlID0gYXMuZmFjdG9yKGtpZmVzdF9kZiRyZXNpZ25fdHlwZSkNCmtpZmVzdF9kZiRnZW5kZXIgPSBhcy5mYWN0b3Ioa2lmZXN0X2RmJGdlbmRlcikNCmtpZmVzdF9kZiRtYXJpdGFsX3N0YXR1cyA9IGFzLmZhY3RvcihraWZlc3RfZGYkbWFyaXRhbF9zdGF0dXMpDQpraWZlc3RfZGYkZW1wbG95ZWVfdHlwZSA9IGFzLmZhY3RvcihraWZlc3RfZGYkZW1wbG95ZWVfdHlwZSkNCmtpZmVzdF9kZiRsZXZlbF9vZl9wb3NpdGlvbiA9IGFzLmZhY3RvcihraWZlc3RfZGYkbGV2ZWxfb2ZfcG9zaXRpb24pDQpraWZlc3RfZGYkYXR0cml0aW9uID0gYXMuZmFjdG9yKGtpZmVzdF9kZiRhdHRyaXRpb24pDQpgYGANCg0KDQoNCg0KIyMgSGFuZGxlIE91dGxpZXJzDQoNCkdldCBPdXRsaWVycyBWYWx1ZXMNCg0KYGBge3J9DQpib3hwbG90KChraWZlc3RfZGYkc2FsYXJ5KSwNCm1haW49IlNhbGFyeSIpDQpgYGANCg0KDQpgYGB7cn0NCm91dF9zYWxhcnkgPC0gYm94cGxvdC5zdGF0cyhraWZlc3RfZGYkc2FsYXJ5KSRvdXQNCmBgYA0KDQpCZXJkYXNhcmthbiBkYXJpIGJveHBsb3QgZGlhdGFzLCB0ZXJkYXBhdCBwZW5jaWxhbiBkYXRhIGdhamkgcGVnYXdhaSBkaSBhbmdrYSA1MEssIHlhbmcgYWthbiBrYW1pIGhpbGFuZ2thbiBhZ2FyIGhhc2lsIHByZWRpa3NpIGxlYmloIGJhaWsgKHRpZGFrIHRlcmxhbHUgYmlhcyksIGRlbmdhbiBjYXJhIHNlYmFnYWkgYmVyaWt1dDoNCg0KR2V0IE91dGxpZXJzIEluZGV4DQoNCmBgYHtyfQ0Kb3V0X2lkeCA8LSB3aGljaChraWZlc3RfZGYkc2FsYXJ5JWluJSBjKG91dF9zYWxhcnkpKQ0KYGBgDQoNCg0KRGF0YSB3aXRoIG91dCBvdXRsaWVycw0KDQoNCmBgYHtyfQ0Ka2lmZXN0X2RmX2NsZWFuIDwta2lmZXN0X2RmWy1vdXRfaWR4LF0NCmJveHBsb3Qoa2lmZXN0X2RmX2NsZWFuJHNhbGFyeSkNCmBgYA0KDQpTZXRlbGFoIGRpaGlsYW5na2FuIHBlbmNpbGFuIGRhdGEsIGRpcGVyb2xlaCBkYXRhIHNlcGVydGkgYm94cGxvdCBkaWF0YXMsIGRhcmkgNjU0IGRhdGEgb2JzZXJ2YXNpIG1lbmphZGkgNTk0IGRhdGEgb2JzZXJ2YXNpLg0KDQoqU2ViZWx1bSBkaWhpbGFuZ2thbiBwZW5jaWxhbiBkYXRhKioNCg0KYGBge3J9DQpkaW0gKGtpZmVzdF9kZikNCmBgYA0KDQoqKlNldGVsYWggZGloaWxhbmcgbWVuZ2d1bmFrYW4gcGVuY2lsYW4gZGF0YSoqDQoNCg0KYGBge3J9DQpkaW0gKGtpZmVzdF9kZl9jbGVhbikNCmBgYA0KDQoNCg0KIyBUcmFpbmluZyAmIFRlc3QgRGl2aXNpb24NCg0KDQohW10oVHJhbmluZy5naWYpDQoNCg0KDQpgYGB7cn0NCmxpYnJhcnkoY2FyZXQpDQpraWZlc3RfZGZfY2xlYW4kcmVzaWduX3R5cGUgPSBhcy5mYWN0b3Ioa2lmZXN0X2RmX2NsZWFuJHJlc2lnbl90eXBlKQ0KVHJhaW5pbmdJbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGtpZmVzdF9kZl9jbGVhbiRyZXNpZ25fdHlwZSwgcD0wLjgsIGxpc3QgPSBGQUxTRSkNClRyYWluaW5nU2V0IDwtIGtpZmVzdF9kZl9jbGVhbltUcmFpbmluZ0luZGV4LF0gIyBUcmFpbmluZyBTZXQNClRlc3RpbmdTZXQgPC0ga2lmZXN0X2RmX2NsZWFuWy1UcmFpbmluZ0luZGV4LF0gIyBUZXN0IFNldA0KVHJhaW5pbmdTZXQgJT4lIHN1bW1hcmlzZV9hbGwoDQogIH4gc3VtKGlzLm5hKC4pKQ0KKQ0KYGBgDQoNCg0KTWVsaWhhdCBhcGFrYWggZGF0YSBiZXJpc2kgbmlsYWkgTkENCg0KYGBge3J9DQpzdW0oaXMubmEoVHJhaW5pbmdTZXQpKQ0KYGBgDQoNClRlcm55YXRhIGRhdGFueWEgdGlkYWsgYWRhIG5pbGFpIE5BDQoNCg0KDQojIE1PREVMTElORw0KDQoNCiFbXShEZXNrcmlwc2kuZ2lmKQ0KDQojIyBMb2dpc3RpYyBSZWdyZXNzaW9uIChMUikNCg0KDQpgYGB7cn0NCk1vZGVsXzIgPC0gdHJhaW4oYXR0cml0aW9uIH4gYWdlK3NhbGFyeSttYXJpdGFsX3N0YXR1cytsb25nX3dvcmsrZ2VuZGVyK2VtcGxveWVlX3R5cGUrbGV2ZWxfb2ZfcG9zaXRpb24sIA0KICAgICAgICAgICAgICAgZGF0YSA9IFRyYWluaW5nU2V0LA0KICAgICAgICAgICAgICAgbWV0aG9kID0gInN2bVBvbHkiLA0KICAgICAgICAgICAgICAgbmEuYWN0aW9uID0gbmEub21pdCwNCiAgICAgICAgICAgICAgIHByZVByb2Nlc3M9Yygic2NhbGUiLCJjZW50ZXIiKSwNCiAgICAgICAgICAgICAgIHRyQ29udHJvbD0gdHJhaW5Db250cm9sKG1ldGhvZD0ibm9uZSIpLA0KICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBkYXRhLmZyYW1lKGRlZ3JlZT0xLHNjYWxlPTEsQz0xKQ0KKQ0KYGBgDQoNCkV2YWx1YXRpb24gVGVzdGluZyBMUg0KDQpgYGB7cn0NCk1vZGVsLnByZWRfMiA8LXByZWRpY3QoTW9kZWxfMiwgVGVzdGluZ1NldCkNCm1vZGVsLm1hdHJpeF8yIDwtY29uZnVzaW9uTWF0cml4KE1vZGVsLnByZWRfMiwgVGVzdGluZ1NldCRhdHRyaXRpb24pDQptb2RlbC5tYXRyaXhfMg0KYGBgDQoNCg0KYGBge3J9DQp0YWJsZShNb2RlbC5wcmVkXzIpDQpgYGANCg0KDQpgYGB7cn0NCnRhYmxlKFRlc3RpbmdTZXQkYXR0cml0aW9uKQ0KYGBgDQoNCg0KYGBge3J9DQpsb2dpdC5wZXJmXzIgPC0gdGFibGUoVGVzdGluZ1NldCRhdHRyaXRpb24sIE1vZGVsLnByZWRfMiwNCiAgICAgICAgICAgICAgICAgICAgZG5uID0gYygiQWN0dWFsIiwgIlByZWRpY3RlZCIpKQ0KbG9naXQucGVyZl8yDQpgYGANCg0KDQojIyBDbGFzc2lmaWNhdGlvbiBUcmVlIChUcmVlKQ0KDQoNCmBgYHtyfQ0KbGlicmFyeShwYXJ0eSkNCg0KZml0LmN0cmVlXzIgPC0gY3RyZWUoZm9ybXVsYSA9IGF0dHJpdGlvbiB+IGFnZStzYWxhcnkrbWFyaXRhbF9zdGF0dXMrbG9uZ193b3JrK2dlbmRlcisNCiAgICAgICAgICAgICAgICAgICBlbXBsb3llZV90eXBlK2xldmVsX29mX3Bvc2l0aW9uLA0KICAgICAgICAgICAgICAgICAgIGRhdGEgPSBUcmFpbmluZ1NldCkNCnBsb3QoZml0LmN0cmVlXzIpDQpgYGANCg0KDQpFdmFsdWF0aW9uIFRlc3RpbmcgVHJlZQ0KDQoNCmBgYHtyfQ0KY3RyZWUucHJlZF8yIDwtIHByZWRpY3QoZml0LmN0cmVlXzIsIFRlc3RpbmdTZXQpDQpjdHJlZS5wZXJmXzIgPC0gdGFibGUoVGVzdGluZ1NldCRhdHRyaXRpb24sIGN0cmVlLnByZWRfMiwNCiAgICAgICAgICAgICAgICAgICAgICBkbm4gPSBjKCJBY3R1YWwiLCJQcmVkaWN0IikpDQpjdHJlZS5wZXJmXzINCmBgYA0KDQoNCiMjIFJhbmRvbSBGb3Jlc3QgKFJGKQ0KDQoNCmBgYHtyfQ0KbGlicmFyeShyYW5kb21Gb3Jlc3QpDQoNCnNldC5zZWVkKDIwMjEpDQoNCmZpdC5mb3Jlc3RfMiA8LSByYW5kb21Gb3Jlc3QoZm9ybXVsYSA9IGF0dHJpdGlvbiB+IGFnZStzYWxhcnkrbWFyaXRhbF9zdGF0dXMrbG9uZ193b3JrK2dlbmRlciwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBUcmFpbmluZ1NldCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hLmFjdGlvbiA9IG5hLnJvdWdoZml4KQ0KZml0LmZvcmVzdF8yDQpgYGANCg0KDQpFdmFsdWF0aW9uIFRlc3RpbmcgUkYNCg0KDQpgYGB7cn0NCmZvcmVzdC5wcmVkXzIgPC0gcHJlZGljdChmaXQuZm9yZXN0XzIsIFRlc3RpbmdTZXQpDQpmb3Jlc3QucGVyZl8yIDwtIHRhYmxlKGZvcmVzdC5wcmVkXzIsIFRlc3RpbmdTZXQkYXR0cml0aW9uLCANCiAgICAgICAgICAgICAgICAgICAgIGRubiA9IGMoIlByZWRpY3QiLCJBY3R1YWwiKSkNCmZvcmVzdC5wZXJmXzINCmBgYA0KDQoNCiMjIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmUgKFNWTSkNCg0KDQoNCmBgYHtyfQ0KbGlicmFyeShlMTA3MSkNCmxpYnJhcnkoY2FyZXQpDQoNCk1vZGVsX3N2bV8yIDwtIHRyYWluKGF0dHJpdGlvbiB+IGFnZStzYWxhcnkrbWFyaXRhbF9zdGF0dXMrbG9uZ193b3JrK2dlbmRlciwgZGF0YSA9IFRyYWluaW5nU2V0LA0KICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInN2bVBvbHkiLA0KICAgICAgICAgICAgICAgICAgICAgbmEuYWN0aW9uID0gbmEub21pdCwNCiAgICAgICAgICAgICAgICAgICAgIHByZVByb2Nlc3M9Yygic2NhbGUiLCJjZW50ZXIiKSwNCiAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbD0gdHJhaW5Db250cm9sKG1ldGhvZD0ibm9uZSIpLA0KICAgICAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBkYXRhLmZyYW1lKGRlZ3JlZT0xLHNjYWxlPTEsQz0xKQ0KKQ0KTW9kZWxfc3ZtXzINCmBgYA0KDQpFdmFsdWF0aW9uIFRlc3RpbmcgU1ZNDQoNCg0KYGBge3J9DQpNb2RlbC5UZXN0aW5nLlNWXzIgPC0gcHJlZGljdChNb2RlbF9zdm1fMiwgVGVzdGluZ1NldCkNCk1vZGVsLnRlc3RpbmcuY29uZnVzaW9uXzIgPC1jb25mdXNpb25NYXRyaXgoTW9kZWwuVGVzdGluZy5TVl8yLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUZXN0aW5nU2V0JGF0dHJpdGlvbikNCk1vZGVsLnRlc3RpbmcuY29uZnVzaW9uXzINCmBgYA0KDQoNCmBgYHtyfQ0Kc3ZtLnBlcmZfMiA8LSB0YWJsZShNb2RlbC5UZXN0aW5nLlNWXzIsIFRlc3RpbmdTZXQkYXR0cml0aW9uLCANCiAgICAgICAgICAgICAgICAgICAgICAgZG5uID0gYygiUHJlZGljdCIsIkFjdHVhbCIpKQ0Kc3ZtLnBlcmZfMg0KYGBgDQoNCg0KIyBFdmFsdWFzaQ0KDQoNCiFbXShFdmFsdWFzaS5naWYpDQoNCg0KDQpQZXJmb3JtYW5jZSBDb21wYXJpc29uDQoNCmBgYHtyfQ0KcGVyZm9ybWFuY2UgPC0gZnVuY3Rpb24odGFibGUsIG49Mil7DQogIHRuIDwtIHRhYmxlWzEsMV0NCiAgZnAgPC0gdGFibGVbMSwyXQ0KICBmbiA8LSB0YWJsZVsyLDFdDQogIHRwIDwtIHRhYmxlWzIsMl0NCiAgDQogIA0KICBzZW5zaXRpdml0eSA9IHRwLyh0cCtmbikgICAgICAgICAjIHJlY2FsbA0KICBzcGVjaWZpY2l0eSA9IHRuLyh0bitmcCkgDQogIHBwcCA9IHRwLyh0cCtmcCkgICAgICAgICAgICAgICAgICMgcHJlY2lzaW9uDQogIG5wcCA9IHRuLyh0bitmbikNCiAgaGl0cmF0ZSA9ICh0cCt0bikvKHRwK3RuK2ZwK2ZuKSAgIyBhY2N1cmFjeQ0KICBmaXNjb3JlICAgPC0gMiAqIHBwcCAqIHNlbnNpdGl2aXR5IC8ocHBwK3NlbnNpdGl2aXR5KQ0KICANCiAgDQogIA0KICByZXN1bHQgPC0gcGFzdGUoIlNlbnNpdGl2aXR5ID0gIiwgcm91bmQoc2Vuc2l0aXZpdHksIG4pICwNCiAgICAgICAgICAgICAgICAgICJcblNwZWNpZmljaXR5ID0gIiwgcm91bmQoc3BlY2lmaWNpdHksIG4pLA0KICAgICAgICAgICAgICAgICAgIlxuUG9zaXRpdmUgUHJlZGljdGl2ZSBWYWx1ZSA9ICIsIHJvdW5kKHBwcCwgbiksDQogICAgICAgICAgICAgICAgICAiXG5OZWdhdGl2ZSBQcmVkaWN0aXZlIFZhbHVlID0gIiwgcm91bmQobnBwLCBuKSwNCiAgICAgICAgICAgICAgICAgICJcbkFjY3VyYWN5ID0gIiwgcm91bmQoaGl0cmF0ZSwgbiksDQogICAgICAgICAgICAgICAgICAiXG5GMSBTY29yZSA9ICIscm91bmQoZmlzY29yZSxuKSkNCiAgDQogIGNhdChyZXN1bHQpDQogIA0KfQ0KYGBgDQoNCg0KU2hvdyBSZXN1bHQgTG9naXN0aWMgUmVncmVzc2lvbg0KDQoNCg0KYGBge3J9DQoNCnBlcmZvcm1hbmNlKGxvZ2l0LnBlcmZfMikNCmBgYA0KDQoNClNob3cgUmVzdWx0IENsYXNzaWZpY2F0aW9uIFRyZWUNCg0KDQpgYGB7cn0NCnBlcmZvcm1hbmNlKGN0cmVlLnBlcmZfMikNCmBgYA0KDQoNClNob3cgUmVzdWx0IFJhbmRvbSBGb3Jlc3QNCg0KDQpgYGB7cn0NCnBlcmZvcm1hbmNlKGZvcmVzdC5wZXJmXzIpICANCmBgYA0KDQoNClNob3cgUmVzdWx0IFN1cHBvcnQgVmVjdG9yIE1hY2hpbmUNCg0KDQoNCmBgYHtyfQ0KcGVyZm9ybWFuY2Uoc3ZtLnBlcmZfMikgDQpgYGANCg0KDQojIFJla29tZWRhc2kgDQoNCg0KIVtdKFJla29tZW5kYXNpLmdpZikNCg0KKioxKiogVmFyaWFiZWwtdmFyaWFiZWwgeWFuZyBwZW50aW5nIGRhbGFtIG1lbXByZWRpa3NpIGthcnlhd2FuIHlhbmcgdGVybWluYXNpIGFkYWxhaDogVXNpYSwgTGFtYSBCZWtlcmphLCBTYWxhcnkgSmVuaXMgS2VsYW1pbiBkYW4gRGVwZW5kZW50Lg0KDQoNCioqMioqIFBlcmZvcm1hIGFsZ29yaXRtYSB5YW5nIHBhbGluZyBiYWlrIGFkYWxhaCBDdHJlZSBkZW5nYW4gc2Vuc2l0aWZpdGFzIDAsNzUsIHNwZXNpZmlrYXNpIDAsOTMsIGFrdXJhc2kgMCw5MSBkZW5nYW4gbWVtcHJlZGlrc2kgcGVnYXdhaSB5YW5nIHRlcm1pbmFzaSAxMC4zNCUgZGFyaSBwb3B1bGFzaS4NCg0KDQoqKjMqKiBQZW5pbGFpYW4gZGFyaSBzZWdpIGJpc25pcywgYWxnb3JpdG1hIGNsYXNzaWZpY2F0aW9uIHJlZ3Jlc3Npb24gbGViaWggYmFpaywga2FyZW5hIGRhcGF0IG1lbXByZWRpa3NpIDE2JSBkYXJpIHBvcHVsYXNpLCB3YWxhdSBzZW5zaXRpZml0YXNueWEgbGViaWggcmVuZGFoIHlhaXR1IDAsMjUgdGV0YXBpIGxlYmloIGJhaWsgcGFkYSBuaWxhaSBzcGVzaWthc2kgMSBkYW4gYWt1cmFzaSAwLDkuIERhcGF0IGRpIHNpbXB1bGthbiwgamlrYSBkYXJpIDExNiBwZWdhd2FpIHlhbmcgZGkgcHJlZGlrc2kgdGVybWluYXNpLCBkYXBhdCBkaXBhc3Rpa2FuIDE2IHBlZ2F3YWkgeWFuZyB0ZXJtaW5hc2kgZGVuZ2FuIGFsYXNhbiBtZW5ndW5kdXJrYW4gZGlyaSDigJxyZXNpZ27igJ0gKHNwZXNpZmlrKSBzZWRhbmdrYW4gamlrYSBtZW5nZ3VuYWthbiBhbGdvcml0bWEgQ3RyZWUgZGltYW5hIG1lbXByZWRpa3NpIDE2IG9yYW5nIHRlcm1pbmFzaSB0ZXRhcGkgbWFzaWggdGVyZGFwYXQgNCBwZWdhd2FpIHlhbmcgdGVybWluYXNpIGRlbmdhbiBhbGFzYW4gbm9ybWFsIHRlcm1pbmFzaSBkYW4gMTIgZGVuZ2FuIGFsYXNhbiBtZW5ndW5kdXJrYW4gZGlyaSDigJxyZXNpZ27igJ0uIERlbmdhbiBkZW1pa2lhbjoNCi0gSFIgbGViaWggZm9jdXMgbWVuZ2Vsb2xhIHBlZ2F3YWkgeWFuZyBzdWRhaCBkaXByZWRpa3NpIHJlc2lnbi4NCi0gUGVydXNhaGFhbiBkYXBhdCBtZW5naGVtYXQgYmlheWEgcmVrcnV0bWVudCwgcGVsYXRpYWhhbiBkYW4gcGVzYW5nb24uDQoNCg0KKio0KiogRGlzYXJhbmthbiB1bnR1ayBtZW5hbWJhaCB2YXJpYWJsZSBLUEkgZGFuIFVuaXQgS2VyamEgdW50dWsgbWVuaWxhaSBhcGFrYWggbWVtYmVyaWthbiBrb250cmlidXNpIHlhbmcgc2lnbmlmaWNhbnQgYXRhdSB0aWRhay4NCg==