Presentación

En la presente publicación se expondrá un proyecto de Discovery. Este tipo de proyectos van más enfocados al análisis descriptivo y exploratorio, es decir, a analizar la data para encontrar tendencias, patrones y anomalías (si es que las hay) que ayuden a generar insights al negocio y así poder contribuir a que se tomen mejores decisiones. Ahora, lo anterior, no significa que no se pueda complementar con un análisis más avanzado como, por ejemplo, un modelo de machine learning, incluso, desde mi punto de vista, previo a la creación de un modelo analítico avanzado es altamente recomendable, primero, hacer un análisis de discovery, ya que esto permitirá tener un acercamiento y entendimiento al giro del negocio. En resumen, este proyecto será un trabajo de data mining.

Contexto

El análisis se hará a la demanda de reservaciones de dos hoteles ubicados en Lisboa, Portugal. Un hotel resort y el otro un hotel en ciudad. Dicha información abarca el periodo comprendido entre el 1 de julio de 2015 al 31 de agosto de 2017. Por razones de seguridad y de confidencialidad, se han omitido los datos de identificación tanto de los hoteles como de los clientes.

Supongamos, para fines didácticos, que se nos ha encargado hacer un análisis con la información antes mencionada para encontrar insights que ayuden a mejorar la toma de decisiones así como a la identificación de diferencias o similitudes (si las hay) entre ambos hoteles con el propósito de elaborar estrategias de marketing diferenciadas. Todo lo anteior, para lograr incrementar la rentabilidad del negocio.

Por tanto, los objetivos del proyecto serán:

  1. Identificar y entender el patrón de consumo de los clientes.
  2. Determinar las posibles diferencias entre las demandas en ambos hoteles.
  3. Localizar insights de interés que puedan ayudar a hacer crecer el negocio.

Set up

Iniciamos configurando las opciones generales que vamos a requerir para el desarrollo de este proyecto..

knitr::opts_chunk$set(echo = TRUE,
                      warning = FALSE,
                      message = FALSE,
                      warning = FALSE,
                      fig.align = "center",
                      out.width = "90%",
                      out.height = "70%")

paquetes <- c('tidyverse',    # manipulación datos
              'lubridate',    # manejar fechas
              'knitr',        # manejo de tablas
              'kableExtra',   # manejo de tablas
              'plotly',       # gráficos interactivos
              'DT',           # visualizción de tablas
              'gridExtra',    # layout de graficos ggplot
              'nortest',      # pruebas estadísticas
              "summarytools", # estadísticas univariantes
              "sfo")          # grafica sankey

instalados <- paquetes %in% installed.packages()

if(sum(instalados == FALSE) > 0) {
  install.packages(paquetes[!instalados])
}

lapply(paquetes, require, character.only = TRUE)

 

Datos

La información para este análisis es pública y los datos así como su descripción se encuentran en la página de ScienceDirect. También, se encuentran en mi repositorio de github.

Ambos hoteles comparten la misma estructura de datos contenida en 32 variables, el hotel resort tiene 40,060 observaciones y el hotel de ciudad 79,330 observaciones. Cada registro representa una reservación.

df <- read.csv("hoteles.csv")

table(df$hotel) %>% 
  kbl(align = "c", digits = 2 ,format.args = list(big.mark = ",")) %>%
  kable_paper("hover", full_width = F)
Var1 Freq
City Hotel 79,330
Resort Hotel 40,060

La descripción de las variables es la siguiente:

t1 <- read.csv("variables.csv")

DT::datatable(t1,
                    extensions = 'FixedColumns',
                    rownames = FALSE,
                    filter = 'top',
                    options = list(
                      pageLength = 5,
                      autoWidth = TRUE,
                      columnDefs = 
                        list(list(width = '300px')),
                      scrollX = TRUE,
                      escape = T)
              )

 

Análisis Exploratorio

Tipos de variables

Hechemos un primer vistazo a la estructura de las variables para identificar si tienen los tipos correctos o de lo contrario cambiarlos.

glimpse(df)
## Rows: 119,390
## Columns: 32
## $ hotel                          <chr> "Resort Hotel", "Resort Hotel", "Resort~
## $ is_canceled                    <int> 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, ~
## $ lead_time                      <int> 342, 737, 7, 13, 14, 14, 0, 9, 85, 75, ~
## $ arrival_date_year              <int> 2015, 2015, 2015, 2015, 2015, 2015, 201~
## $ arrival_date_month             <chr> "July", "July", "July", "July", "July",~
## $ arrival_date_week_number       <int> 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,~
## $ arrival_date_day_of_month      <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ~
## $ stays_in_weekend_nights        <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ~
## $ stays_in_week_nights           <int> 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 4, ~
## $ adults                         <int> 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, ~
## $ children                       <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ~
## $ babies                         <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ~
## $ meal                           <chr> "BB", "BB", "BB", "BB", "BB", "BB", "BB~
## $ country                        <chr> "PRT", "PRT", "GBR", "GBR", "GBR", "GBR~
## $ market_segment                 <chr> "Direct", "Direct", "Direct", "Corporat~
## $ distribution_channel           <chr> "Direct", "Direct", "Direct", "Corporat~
## $ is_repeated_guest              <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ~
## $ previous_cancellations         <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ~
## $ previous_bookings_not_canceled <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ~
## $ reserved_room_type             <chr> "C", "C", "A", "A", "A", "A", "C", "C",~
## $ assigned_room_type             <chr> "C", "C", "C", "A", "A", "A", "C", "C",~
## $ booking_changes                <int> 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ~
## $ deposit_type                   <chr> "No Deposit", "No Deposit", "No Deposit~
## $ agent                          <chr> "NULL", "NULL", "NULL", "304", "240", "~
## $ company                        <chr> "NULL", "NULL", "NULL", "NULL", "NULL",~
## $ days_in_waiting_list           <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ~
## $ customer_type                  <chr> "Transient", "Transient", "Transient", ~
## $ adr                            <dbl> 0.00, 0.00, 75.00, 75.00, 98.00, 98.00,~
## $ required_car_parking_spaces    <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ~
## $ total_of_special_requests      <int> 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 3, ~
## $ reservation_status             <chr> "Check-Out", "Check-Out", "Check-Out", ~
## $ reservation_status_date        <chr> "2015-07-01", "2015-07-01", "2015-07-02~

 

Con este primer resumen vemos que hay variables a las que tenemos que cambiarles el tipo como, por ejemplo, la variable hotel, que está como character y es recomendable pasarla a tipo factor, sobre todo para que se nos facilite la creación de gráficos, además, hay algoritmos que necesitan que las variables categóricas estén como factores y aunque en este trabajo no se utilizará ningún algoritmo es bueno tenerlo presente (al menos si se está trabajando con R, pues en Python no se tiene ese detalle ya que no existe el tipo factor).

También, vemos a primera vista que hay valores NULL en las variables agent y company.

 

Valores NA y NULL

Ahora, antes de hacer alguna modificación a los tipos es altamente recomendable conocer la cardinalidad de las variables categóricas con el objetivo de identificar desde el inicio del proyecto los posibles datos raros o extraños como espacios o caracteres especiales. Para esto nos apoyaremos de tablas de frecuencias:

df2 <- df %>% select_if(is.character)

  for(i in 1:length(df2)){
  table(df2[i])
  print(table(df2[i]))
  }
## 
##   City Hotel Resort Hotel 
##        79330        40060 
## 
##     April    August  December  February   January      July      June     March 
##     11089     13877      6780      8068      5929     12661     10939      9794 
##       May  November   October September 
##     11791      6794     11160     10508 
## 
##        BB        FB        HB        SC Undefined 
##     92310       798     14463     10650      1169 
## 
##   ABW   AGO   AIA   ALB   AND   ARE   ARG   ARM   ASM   ATA   ATF   AUS   AUT 
##     2   362     1    12     7    51   214     8     1     2     1   426  1263 
##   AZE   BDI   BEL   BEN   BFA   BGD   BGR   BHR   BHS   BIH   BLR   BOL   BRA 
##    17     1  2342     3     1    12    75     5     1    13    26    10  2224 
##   BRB   BWA   CAF   CHE   CHL   CHN   CIV   CMR    CN   COL   COM   CPV   CRI 
##     4     1     5  1730    65   999     6    10  1279    71     2    24    19 
##   CUB   CYM   CYP   CZE   DEU   DJI   DMA   DNK   DOM   DZA   ECU   EGY   ESP 
##     8     1    51   171  7287     1     1   435    14   103    27    32  8568 
##   EST   ETH   FIN   FJI   FRA   FRO   GAB   GBR   GEO   GGY   GHA   GIB   GLP 
##    83     3   447     1 10415     5     4 12129    22     3     4    18     2 
##   GNB   GRC   GTM   GUY   HKG   HND   HRV   HUN   IDN   IMN   IND   IRL   IRN 
##     9   128     4     1    29     1   100   230    35     2   152  3375    83 
##   IRQ   ISL   ISR   ITA   JAM   JEY   JOR   JPN   KAZ   KEN   KHM   KIR   KNA 
##    14    57   669  3766     6     8    21   197    19     6     2     1     2 
##   KOR   KWT   LAO   LBN   LBY   LCA   LIE   LKA   LTU   LUX   LVA   MAC   MAR 
##   133    16     2    31     8     1     3     7    81   287    55    16   259 
##   MCO   MDG   MDV   MEX   MKD   MLI   MLT   MMR   MNE   MOZ   MRT   MUS   MWI 
##     4     1    12    85    10     1    18     1     5    67     1     7     2 
##   MYS   MYT   NAM   NCL   NGA   NIC   NLD   NOR   NPL  NULL   NZL   OMN   PAK 
##    28     2     1     1    34     1  2104   607     1   488    74    18    14 
##   PAN   PER   PHL   PLW   POL   PRI   PRT   PRY   PYF   QAT   ROU   RUS   RWA 
##     9    29    40     1   919    12 48590     4     1    15   500   632     2 
##   SAU   SDN   SEN   SGP   SLE   SLV   SMR   SRB   STP   SUR   SVK   SVN   SWE 
##    48     1    11    39     1     2     1   101     2     5    65    57  1024 
##   SYC   SYR   TGO   THA   TJK   TMP   TUN   TUR   TWN   TZA   UGA   UKR   UMI 
##     2     3     2    59     9     3    39   248    51     5     2    68     1 
##   URY   USA   UZB   VEN   VGB   VNM   ZAF   ZMB   ZWE 
##    32  2097     4    26     1     8    80     2     4 
## 
##      Aviation Complementary     Corporate        Direct        Groups 
##           237           743          5295         12606         19811 
## Offline TA/TO     Online TA     Undefined 
##         24219         56477             2 
## 
## Corporate    Direct       GDS     TA/TO Undefined 
##      6677     14645       193     97870         5 
## 
##     A     B     C     D     E     F     G     H     L     P 
## 85994  1118   932 19201  6535  2897  2094   601     6    12 
## 
##     A     B     C     D     E     F     G     H     I     K     L     P 
## 74053  2163  2375 25322  7806  3751  2553   712   363   279     1    12 
## 
## No Deposit Non Refund Refundable 
##     104641      14587        162 
## 
##     1    10   103   104   105   106   107    11   110   111   112   114   115 
##  7191   260    21    53    14     2     2   395    12    16    15     1   225 
##   117   118   119    12   121   122   126   127   128   129    13   132   133 
##     1    69   304   578    37     2    14    45    23    14    82   143    56 
##   134   135   138   139    14   141   142   143   144   146   147   148   149 
##   482     2   287     8  3640     6   137   172     1   124   156     4    28 
##    15   150   151   152   153   154   155   156   157   158   159    16   162 
##   402     5    56   183    25   193    94   190    61     1    89   246    37 
##   163   165   167   168    17   170   171   173   174   175   177   179   180 
##     7     1     3   184   241    93   607    29    22   195   347     2     4 
##   181   182   183   184   185   187    19   191   192   193   195   196   197 
##    59     8    45    52    78    24  1061   198    41    15   193   301     1 
##     2    20   201   205   208    21   210   211   213   214   215   216   219 
##   162   540    42    27   173   875     7     2     1     5    15     1    13 
##    22   220   223   227   229    23   232   234   235   236    24   240   241 
##   382   104    18     2   786    25     2   128    29   247    22 13922  1721 
##   242   243   244   245   247   248   249    25   250   251   252   253   254 
##   780   514     4    37     1   131    51     3  2870   220    29    87    29 
##   256   257   258    26   261   262   265   267   269    27   270   273   275 
##    24    24     3   401    38    22     1     1     2   450     6   349     8 
##   276   278    28   280   281   282   283   285   286   287   288   289    29 
##     8     1  1666     1    82     2     2     1    45     8    14     1   683 
##   290   291   294   295   296   298   299     3    30   300   301   302   303 
##    19     1     1     4    42   472     1  1336   484     1     1     3     2 
##   304   305   306   307   308    31   310   313   314   315    32   321   323 
##     1    45    35    14    54   162    25    36   927   284    15     3    25 
##   324   325   326   327   328    33   330   331   332   333   334   335   336 
##     9     6   165    20     9    31   125     2    55     1    28     4    23 
##   337   339    34   341   344   346   348    35   350   352   354   355   358 
##     1    77   294     4     8     1    22   109    28     1    14     4     1 
##   359    36   360   363   364   367   368    37   370   371   375   378    38 
##    21   100    15     6    19     1    45  1230     3     4    40    36   274 
##   384   385   387   388    39   390   391   393   394   397     4    40   403 
##     2    60    32     1   127    57     2    13    33     1    47  1039     4 
##   404   405   406   408    41   410   411   414   416   418    42   420   423 
##     2     5     1     1    75   133    16     2     1     8   211     3    19 
##   425   426   427   429   430   431   432   433   434   436   438    44   440 
##    16     3     3     5     4     1     1     1    33    49     2   292    56 
##   441   444   446   449    45   450   451   453   454   455   459   461   464 
##     7     1     1     2    32     1     1     1     2    19    16     2    98 
##   467   468   469    47   472   474   475   476   479   480   481   483   484 
##    39    49     2    50     1    17     8     2    32     1     8     1    11 
##   492   493   495   497     5    50   502   508   509   510    52   526   527 
##    28    35    57     1   330    20    24     6    10     2   137    10    35 
##    53   531   535    54    55    56    57    58    59     6    60    61    63 
##    18    68     3     1    16   375    28   335     1  3290    19     2    29 
##    64    66    67    68    69     7    70    71    72    73    74    75    77 
##    23    44   127   211    90  3539     1    73     6     1    20    73    33 
##    78    79     8    81    82    83    85    86    87    88    89     9    90 
##    37    47  1514     6    77   696   554   338    77    19    99 31961     1 
##    91    92    93    94    95    96    98    99  NULL 
##    58     7     1   114   135   537   124    68 16340 
## 
##     10    100    101    102    103    104    105    106    107    108    109 
##      1      1      1      1     16      1      8      2      9     11      1 
##     11    110    112    113    115    116    118     12    120    122    126 
##      1     52     13     36      4      6      7     14     14     18      1 
##    127    130    132    135    137    139     14    140    142    143    144 
##     15     12      1     66      4      3      9      1      1     17     27 
##    146    148    149    150    153    154    158    159     16    160    163 
##      3     37      5     19    215    133      2      6      5      1     17 
##    165    167    168    169    174    178    179     18    180    183    184 
##      3      7      2     65    149     27     24      1      5     16      1 
##    185    186    192    193    195    197     20    200    202    203    204 
##      4     12      4     16     38     47     50      3     38     13     34 
##    207    209    210    212    213    215    216    217    218    219     22 
##      9     19      2      1      1      8     21      2     43    141      6 
##    220    221    222    223    224    225    227    229    230    232    233 
##      4     27      2    784      3      7     24      1      3      2    114 
##    234    237    238    240    242    243    245    246    250    251    253 
##      1      1     33      3     62      2      3      3      2     18      1 
##    254    255    257    258    259    260    263    264    268    269    270 
##     10      6      1      1      2      3     14      2     14     33     43 
##    271    272    273    274    275    277    278    279     28    280    281 
##      2      3      1     14      3      5      2      8      5     48    138 
##    282    284    286    287    288    289     29    290    291    292    293 
##      4      1     21      5      1      2      2     17     12     18      5 
##    297    301    302    304    305    307    308    309     31    311    312 
##      7      1      5      2      1     36     33      1     17      2      3 
##    313    314    316    317    318    319     32    320    321    323    324 
##      1      1      2      9      1      3      1      1      2     10      9 
##    325    329    330    331    332    333    334    337    338     34    341 
##      2     12      4     61      2     11      3     25     12      8      5 
##    342    343    346    347    348    349     35    350    351    352    353 
##     48     29     14      1     59      2      1      3      2      1      4 
##    355    356    357    358    360    361    362    364    365    366    367 
##     13     10      5      7     12      2      2      6     29     24     14 
##    368    369     37    370    371    372    373    376    377    378    379 
##      1      5     10      2     11      3      1      1      5      3      9 
##     38    380    382    383    384    385    386    388     39    390    391 
##     51     12      5      6      9     30      1      7      8     13      2 
##    392    393    394    395    396    397    398    399     40    400    401 
##      4      1      6      4     18     15      1     11    927      2      1 
##    402    403    405    407    408    409    410    411    412    413    415 
##      1      2    119     22     15     12      5      2      1      1      1 
##    416    417    418    419     42    420    421    422    423    424    425 
##      1      1     25      1      5      1      9      1      2     24      1 
##    426    428    429     43    433    435    436    437    439    442    443 
##      4     13      2     29      2     12      2      7      6      1      5 
##    444    445    446    447    448     45    450    451    452    454    455 
##      5      4      1      2      4    250     10      6      4      1      1 
##    456    457    458    459     46    460    461    465    466     47    470 
##      2      3      2      5     26      3      1     12      3     72      5 
##    477    478    479     48    481    482    483    484    485    486    487 
##     23      2      1      5      1      2      2      2     14      2      1 
##    489     49    490    491    492    494    496    497    498    499    501 
##      1      5      5      2      2      4      1      1     58      1      1 
##    504    506    507     51    511    512    513    514    515    516    518 
##     11      1     23     99      6      3      2      2      6      1      2 
##     52    520    521    523    525    528     53    530    531    534    539 
##      2      1      7     19     15      2      8      5      1      2      2 
##     54    541    543     59      6     61     62     64     65     67     68 
##      1      1      2      7      1      2     47      1      1    267     46 
##     71     72     73     76     77     78      8     80     81     82     83 
##      2     30      3      1      1     22      1      1     23     14      9 
##     84     85     86     88      9     91     92     93     94     96     99 
##      3      2     32     22     37     48     13      3     87      1     12 
##   NULL 
## 112593 
## 
##        Contract           Group       Transient Transient-Party 
##            4076             577           89613           25124 
## 
##  Canceled Check-Out   No-Show 
##     43017     75166      1207 
## 
## 2014-10-17 2014-11-18 2015-01-01 2015-01-02 2015-01-18 2015-01-20 2015-01-21 
##        180          1        763         16          1          2         91 
## 2015-01-22 2015-01-28 2015-01-29 2015-01-30 2015-02-02 2015-02-05 2015-02-06 
##          6          1          1         67          2          4          1 
## 2015-02-09 2015-02-10 2015-02-11 2015-02-12 2015-02-17 2015-02-19 2015-02-20 
##          1          2          3          1          2          1         20 
## 2015-02-23 2015-02-24 2015-02-25 2015-02-26 2015-02-27 2015-03-03 2015-03-04 
##          2          1          2          1          1         38          2 
## 2015-03-05 2015-03-06 2015-03-09 2015-03-10 2015-03-11 2015-03-12 2015-03-13 
##          1          2          4          1          1          1          1 
## 2015-03-17 2015-03-18 2015-03-23 2015-03-24 2015-03-25 2015-03-28 2015-03-29 
##          4          1          2          3         17          1          1 
## 2015-03-30 2015-03-31 2015-04-02 2015-04-03 2015-04-04 2015-04-05 2015-04-06 
##          1          4         18          2          8          6          4 
## 2015-04-07 2015-04-08 2015-04-10 2015-04-11 2015-04-13 2015-04-14 2015-04-15 
##          1          3          5          3          2          3          9 
## 2015-04-16 2015-04-17 2015-04-18 2015-04-20 2015-04-21 2015-04-22 2015-04-23 
##          7          2          2          6          1          7          3 
## 2015-04-24 2015-04-25 2015-04-27 2015-04-28 2015-04-29 2015-04-30 2015-05-01 
##          4          1          1         25         27          1          9 
## 2015-05-04 2015-05-05 2015-05-06 2015-05-07 2015-05-08 2015-05-09 2015-05-11 
##          3          4          6          6          3          4          5 
## 2015-05-12 2015-05-13 2015-05-14 2015-05-15 2015-05-16 2015-05-18 2015-05-19 
##         11         13         50          1          3         12         33 
## 2015-05-20 2015-05-21 2015-05-22 2015-05-23 2015-05-25 2015-05-26 2015-05-27 
##          8          3         10          8          6          2         10 
## 2015-05-28 2015-05-29 2015-05-30 2015-06-01 2015-06-02 2015-06-03 2015-06-04 
##         28         34          3         13         10          9          6 
## 2015-06-05 2015-06-06 2015-06-08 2015-06-09 2015-06-10 2015-06-11 2015-06-12 
##          7          3         10         35          4         13         10 
## 2015-06-13 2015-06-14 2015-06-15 2015-06-16 2015-06-17 2015-06-18 2015-06-19 
##          4          1         53         44        100          7         14 
## 2015-06-20 2015-06-22 2015-06-23 2015-06-24 2015-06-25 2015-06-26 2015-06-27 
##          8         16         22          6         15         67         29 
## 2015-06-29 2015-06-30 2015-07-01 2015-07-02 2015-07-03 2015-07-04 2015-07-05 
##         96         64         15        469        153         16         33 
## 2015-07-06 2015-07-07 2015-07-08 2015-07-09 2015-07-10 2015-07-11 2015-07-12 
##        805         55         98         81         98         42         46 
## 2015-07-13 2015-07-14 2015-07-15 2015-07-16 2015-07-17 2015-07-18 2015-07-19 
##         51         69         44         80        111         55         97 
## 2015-07-20 2015-07-21 2015-07-22 2015-07-23 2015-07-24 2015-07-25 2015-07-26 
##         87         55        132        209        113         88         77 
## 2015-07-27 2015-07-28 2015-07-29 2015-07-30 2015-07-31 2015-08-01 2015-08-02 
##         51         57         99        107        122        107         51 
## 2015-08-03 2015-08-04 2015-08-05 2015-08-06 2015-08-07 2015-08-08 2015-08-09 
##         55        107         46         97         91         96        102 
## 2015-08-10 2015-08-11 2015-08-12 2015-08-13 2015-08-14 2015-08-15 2015-08-16 
##        130        155        150        104        166         74        152 
## 2015-08-17 2015-08-18 2015-08-19 2015-08-20 2015-08-21 2015-08-22 2015-08-23 
##        139        101        121         84        202         94         75 
## 2015-08-24 2015-08-25 2015-08-26 2015-08-27 2015-08-28 2015-08-29 2015-08-30 
##         84         94         90         68        115         96         85 
## 2015-08-31 2015-09-01 2015-09-02 2015-09-03 2015-09-04 2015-09-05 2015-09-06 
##        116        103         80        109        124        121        112 
## 2015-09-07 2015-09-08 2015-09-09 2015-09-10 2015-09-11 2015-09-12 2015-09-13 
##        137        142        290         96        173        103        128 
## 2015-09-14 2015-09-15 2015-09-16 2015-09-17 2015-09-18 2015-09-19 2015-09-20 
##        109        120        154        183        153        123        137 
## 2015-09-21 2015-09-22 2015-09-23 2015-09-24 2015-09-25 2015-09-26 2015-09-27 
##        135        124        136        166        149        110         48 
## 2015-09-28 2015-09-29 2015-09-30 2015-10-01 2015-10-02 2015-10-03 2015-10-04 
##        147        111        194        131        107         83        161 
## 2015-10-05 2015-10-06 2015-10-07 2015-10-08 2015-10-09 2015-10-10 2015-10-11 
##        119        111        160        118        186        150        133 
## 2015-10-12 2015-10-13 2015-10-14 2015-10-15 2015-10-16 2015-10-17 2015-10-18 
##        242        177        129        116        187         73        160 
## 2015-10-19 2015-10-20 2015-10-21 2015-10-22 2015-10-23 2015-10-24 2015-10-25 
##        262        109       1461        203        189        106         82 
## 2015-10-26 2015-10-27 2015-10-28 2015-10-29 2015-10-30 2015-10-31 2015-11-01 
##        115        128        199         87         96        162        106 
## 2015-11-02 2015-11-03 2015-11-04 2015-11-05 2015-11-06 2015-11-07 2015-11-08 
##         95         71         90         61        113         73         53 
## 2015-11-09 2015-11-10 2015-11-11 2015-11-12 2015-11-13 2015-11-14 2015-11-15 
##        129         45        158         57         50         80        148 
## 2015-11-16 2015-11-17 2015-11-18 2015-11-19 2015-11-20 2015-11-21 2015-11-22 
##        150        256         65         98         73        116        203 
## 2015-11-23 2015-11-24 2015-11-25 2015-11-26 2015-11-27 2015-11-28 2015-11-29 
##        111        113         52        135        109         50         97 
## 2015-11-30 2015-12-01 2015-12-02 2015-12-03 2015-12-04 2015-12-05 2015-12-06 
##        120         53        142         99         92         31         40 
## 2015-12-07 2015-12-08 2015-12-09 2015-12-10 2015-12-11 2015-12-12 2015-12-13 
##         96        254        124        124         90         76         77 
## 2015-12-14 2015-12-15 2015-12-16 2015-12-17 2015-12-18 2015-12-19 2015-12-20 
##        100         68         45         90        423         63         55 
## 2015-12-21 2015-12-22 2015-12-23 2015-12-24 2015-12-25 2015-12-26 2015-12-27 
##         36        114        108         24         52         64         75 
## 2015-12-28 2015-12-29 2015-12-30 2015-12-31 2016-01-01 2016-01-02 2016-01-03 
##         77        172        130         68         99        145        194 
## 2016-01-04 2016-01-05 2016-01-06 2016-01-07 2016-01-08 2016-01-09 2016-01-10 
##         80        205        231        108        177         59         71 
## 2016-01-11 2016-01-12 2016-01-13 2016-01-14 2016-01-15 2016-01-16 2016-01-17 
##        126         66         89        104         94        176        118 
## 2016-01-18 2016-01-19 2016-01-20 2016-01-21 2016-01-22 2016-01-23 2016-01-24 
##        625        233        117        170        216         66        119 
## 2016-01-25 2016-01-26 2016-01-27 2016-01-28 2016-01-29 2016-01-30 2016-01-31 
##         92        145        101        177        122         64         93 
## 2016-02-01 2016-02-02 2016-02-03 2016-02-04 2016-02-05 2016-02-06 2016-02-07 
##        258         86        138        123        134         64        133 
## 2016-02-08 2016-02-09 2016-02-10 2016-02-11 2016-02-12 2016-02-13 2016-02-14 
##        111        412        228        109        134        113        257 
## 2016-02-15 2016-02-16 2016-02-17 2016-02-18 2016-02-19 2016-02-20 2016-02-21 
##        150        105        129        119        118        182        138 
## 2016-02-22 2016-02-23 2016-02-24 2016-02-25 2016-02-26 2016-02-27 2016-02-28 
##        132        160        120        212        135        189        189 
## 2016-02-29 2016-03-01 2016-03-02 2016-03-03 2016-03-04 2016-03-05 2016-03-06 
##        218        141        169        170        201        100        181 
## 2016-03-07 2016-03-08 2016-03-09 2016-03-10 2016-03-11 2016-03-12 2016-03-13 
##        129        125        187        120        146        156        177 
## 2016-03-14 2016-03-15 2016-03-16 2016-03-17 2016-03-18 2016-03-19 2016-03-20 
##        261        329        184        143        254        114        167 
## 2016-03-21 2016-03-22 2016-03-23 2016-03-24 2016-03-25 2016-03-26 2016-03-27 
##        180        171        176        180        135        111        206 
## 2016-03-28 2016-03-29 2016-03-30 2016-03-31 2016-04-01 2016-04-02 2016-04-03 
##        214        188        131        173        125        114        173 
## 2016-04-04 2016-04-05 2016-04-06 2016-04-07 2016-04-08 2016-04-09 2016-04-10 
##        382        103        189        172        163        158        152 
## 2016-04-11 2016-04-12 2016-04-13 2016-04-14 2016-04-15 2016-04-16 2016-04-17 
##        190        148        170        164        201        131        299 
## 2016-04-18 2016-04-19 2016-04-20 2016-04-21 2016-04-22 2016-04-23 2016-04-24 
##        167        130        181        148        159        127        152 
## 2016-04-25 2016-04-26 2016-04-27 2016-04-28 2016-04-29 2016-04-30 2016-05-01 
##        185        151        283        177        173        147        125 
## 2016-05-02 2016-05-03 2016-05-04 2016-05-05 2016-05-06 2016-05-07 2016-05-08 
##        198        180        203        225        193        105        194 
## 2016-05-09 2016-05-10 2016-05-11 2016-05-12 2016-05-13 2016-05-14 2016-05-15 
##        152        174        130        171        138        129        168 
## 2016-05-16 2016-05-17 2016-05-18 2016-05-19 2016-05-20 2016-05-21 2016-05-22 
##        200        173        159        187        168        124        155 
## 2016-05-23 2016-05-24 2016-05-25 2016-05-26 2016-05-27 2016-05-28 2016-05-29 
##        174        144        158        192        108         67        245 
## 2016-05-30 2016-05-31 2016-06-01 2016-06-02 2016-06-03 2016-06-04 2016-06-05 
##        171        113         96        257        152        115        160 
## 2016-06-06 2016-06-07 2016-06-08 2016-06-09 2016-06-10 2016-06-11 2016-06-12 
##        164        127        155        166        125         89        181 
## 2016-06-13 2016-06-14 2016-06-15 2016-06-16 2016-06-17 2016-06-18 2016-06-19 
##        177        137        156        120        202         89        141 
## 2016-06-20 2016-06-21 2016-06-22 2016-06-23 2016-06-24 2016-06-25 2016-06-26 
##        279        150        148        142        180         64        271 
## 2016-06-27 2016-06-28 2016-06-29 2016-06-30 2016-07-01 2016-07-02 2016-07-03 
##        119        145        113        132        125        126        125 
## 2016-07-04 2016-07-05 2016-07-06 2016-07-07 2016-07-08 2016-07-09 2016-07-10 
##        125        127        125        152        140        130        112 
## 2016-07-11 2016-07-12 2016-07-13 2016-07-14 2016-07-15 2016-07-16 2016-07-17 
##        140        137        230        129        122        145        139 
## 2016-07-18 2016-07-19 2016-07-20 2016-07-21 2016-07-22 2016-07-23 2016-07-24 
##        197        154        145        195        128        163        147 
## 2016-07-25 2016-07-26 2016-07-27 2016-07-28 2016-07-29 2016-07-30 2016-07-31 
##        152        120        124        160        140        174        125 
## 2016-08-01 2016-08-02 2016-08-03 2016-08-04 2016-08-05 2016-08-06 2016-08-07 
##        194        137        164        133        126        131        117 
## 2016-08-08 2016-08-09 2016-08-10 2016-08-11 2016-08-12 2016-08-13 2016-08-14 
##        186        121        120        184        143        134        114 
## 2016-08-15 2016-08-16 2016-08-17 2016-08-18 2016-08-19 2016-08-20 2016-08-21 
##        168        139        151        191        135        176        139 
## 2016-08-22 2016-08-23 2016-08-24 2016-08-25 2016-08-26 2016-08-27 2016-08-28 
##        154        136        167        144        145        100        134 
## 2016-08-29 2016-08-30 2016-08-31 2016-09-01 2016-09-02 2016-09-03 2016-09-04 
##        175        172        155        138        196        145        127 
## 2016-09-05 2016-09-06 2016-09-07 2016-09-08 2016-09-09 2016-09-10 2016-09-11 
##        179        271        113        133        137        105        157 
## 2016-09-12 2016-09-13 2016-09-14 2016-09-15 2016-09-16 2016-09-17 2016-09-18 
##        172        168        139        251        152        127        178 
## 2016-09-19 2016-09-20 2016-09-21 2016-09-22 2016-09-23 2016-09-24 2016-09-25 
##        175        303        182        163        167         79        219 
## 2016-09-26 2016-09-27 2016-09-28 2016-09-29 2016-09-30 2016-10-01 2016-10-02 
##        199        137        140        201        140         99        156 
## 2016-10-03 2016-10-04 2016-10-05 2016-10-06 2016-10-07 2016-10-08 2016-10-09 
##        154        134        151        224        230        103        185 
## 2016-10-10 2016-10-11 2016-10-12 2016-10-13 2016-10-14 2016-10-15 2016-10-16 
##        242        185        166        186        163         88        223 
## 2016-10-17 2016-10-18 2016-10-19 2016-10-20 2016-10-21 2016-10-22 2016-10-23 
##        179        151        129        176        265        101        162 
## 2016-10-24 2016-10-25 2016-10-26 2016-10-27 2016-10-28 2016-10-29 2016-10-30 
##        157        173        176        192        211        163        158 
## 2016-10-31 2016-11-01 2016-11-02 2016-11-03 2016-11-04 2016-11-05 2016-11-06 
##        139        168        158        118        151        139        184 
## 2016-11-07 2016-11-08 2016-11-09 2016-11-10 2016-11-11 2016-11-12 2016-11-13 
##        151         78        111        152        142        127        145 
## 2016-11-14 2016-11-15 2016-11-16 2016-11-17 2016-11-18 2016-11-19 2016-11-20 
##        144        145        122        143        145        123        175 
## 2016-11-21 2016-11-22 2016-11-23 2016-11-24 2016-11-25 2016-11-26 2016-11-27 
##        340        104        176        172        790        116        169 
## 2016-11-28 2016-11-29 2016-11-30 2016-12-01 2016-12-02 2016-12-03 2016-12-04 
##         93        103        137         68        128         96        180 
## 2016-12-05 2016-12-06 2016-12-07 2016-12-08 2016-12-09 2016-12-10 2016-12-11 
##         94        133        450        153        217         81        217 
## 2016-12-12 2016-12-13 2016-12-14 2016-12-15 2016-12-16 2016-12-17 2016-12-18 
##        243        249        102        134        115         91        111 
## 2016-12-19 2016-12-20 2016-12-21 2016-12-22 2016-12-23 2016-12-24 2016-12-25 
##         93        109        171        121        123         58         41 
## 2016-12-26 2016-12-27 2016-12-28 2016-12-29 2016-12-30 2016-12-31 2017-01-01 
##         92        173        148        147        126         74        149 
## 2017-01-02 2017-01-03 2017-01-04 2017-01-05 2017-01-06 2017-01-07 2017-01-08 
##        214        164        114        159        211        114        135 
## 2017-01-09 2017-01-10 2017-01-11 2017-01-12 2017-01-13 2017-01-14 2017-01-15 
##        144        177        146        225        111        120        170 
## 2017-01-16 2017-01-17 2017-01-18 2017-01-19 2017-01-20 2017-01-21 2017-01-22 
##         89        121        206        321        249        108        122 
## 2017-01-23 2017-01-24 2017-01-25 2017-01-26 2017-01-27 2017-01-28 2017-01-29 
##        134        343        145        155        211        112        184 
## 2017-01-30 2017-01-31 2017-02-01 2017-02-02 2017-02-03 2017-02-04 2017-02-05 
##        145        253        135        315        194        114        169 
## 2017-02-06 2017-02-07 2017-02-08 2017-02-09 2017-02-10 2017-02-11 2017-02-12 
##        213        119        139        191        178        112        194 
## 2017-02-13 2017-02-14 2017-02-15 2017-02-16 2017-02-17 2017-02-18 2017-02-19 
##        150        121        229        163        208        101        203 
## 2017-02-20 2017-02-21 2017-02-22 2017-02-23 2017-02-24 2017-02-25 2017-02-26 
##        162        169        136        182        231        192        188 
## 2017-02-27 2017-02-28 2017-03-01 2017-03-02 2017-03-03 2017-03-04 2017-03-05 
##        124        226        165        172        140        105        185 
## 2017-03-06 2017-03-07 2017-03-08 2017-03-09 2017-03-10 2017-03-11 2017-03-12 
##        221        154        160        143        170        124        189 
## 2017-03-13 2017-03-14 2017-03-15 2017-03-16 2017-03-17 2017-03-18 2017-03-19 
##        183        110        148        160        139        132        164 
## 2017-03-20 2017-03-21 2017-03-22 2017-03-23 2017-03-24 2017-03-25 2017-03-26 
##        194        144        143        149        155        109        218 
## 2017-03-27 2017-03-28 2017-03-29 2017-03-30 2017-03-31 2017-04-01 2017-04-02 
##        129        127        176        139        179        135        147 
## 2017-04-03 2017-04-04 2017-04-05 2017-04-06 2017-04-07 2017-04-08 2017-04-09 
##        178        147        204        157        158        114        212 
## 2017-04-10 2017-04-11 2017-04-12 2017-04-13 2017-04-14 2017-04-15 2017-04-16 
##        164        116        130        143        127        143        120 
## 2017-04-17 2017-04-18 2017-04-19 2017-04-20 2017-04-21 2017-04-22 2017-04-23 
##        177        147        133        134        263        132        183 
## 2017-04-24 2017-04-25 2017-04-26 2017-04-27 2017-04-28 2017-04-29 2017-04-30 
##        143        139        121        155        178        193        141 
## 2017-05-01 2017-05-02 2017-05-03 2017-05-04 2017-05-05 2017-05-06 2017-05-07 
##        163        198        136        158        297        163        146 
## 2017-05-08 2017-05-09 2017-05-10 2017-05-11 2017-05-12 2017-05-13 2017-05-14 
##        177        160        153        165        170        138        166 
## 2017-05-15 2017-05-16 2017-05-17 2017-05-18 2017-05-19 2017-05-20 2017-05-21 
##        171        138        170        154        130        141        137 
## 2017-05-22 2017-05-23 2017-05-24 2017-05-25 2017-05-26 2017-05-27 2017-05-28 
##        186        156        184        216        127        104        214 
## 2017-05-29 2017-05-30 2017-05-31 2017-06-01 2017-06-02 2017-06-03 2017-06-04 
##        156        125        107        161        144        146        131 
## 2017-06-05 2017-06-06 2017-06-07 2017-06-08 2017-06-09 2017-06-10 2017-06-11 
##        144        152        128        155        154        104        174 
## 2017-06-12 2017-06-13 2017-06-14 2017-06-15 2017-06-16 2017-06-17 2017-06-18 
##        141        103         97        136        115        118        140 
## 2017-06-19 2017-06-20 2017-06-21 2017-06-22 2017-06-23 2017-06-24 2017-06-25 
##        111        115        103        145        141        109        140 
## 2017-06-26 2017-06-27 2017-06-28 2017-06-29 2017-06-30 2017-07-01 2017-07-02 
##        115        177        167        116        178        119        126 
## 2017-07-03 2017-07-04 2017-07-05 2017-07-06 2017-07-07 2017-07-08 2017-07-09 
##        133        206         84        172        130        102        157 
## 2017-07-10 2017-07-11 2017-07-12 2017-07-13 2017-07-14 2017-07-15 2017-07-16 
##        138        153        145        176        119        190        125 
## 2017-07-17 2017-07-18 2017-07-19 2017-07-20 2017-07-21 2017-07-22 2017-07-23 
##        117        129         97        104        115        132        116 
## 2017-07-24 2017-07-25 2017-07-26 2017-07-27 2017-07-28 2017-07-29 2017-07-30 
##        156         96        109        111        107        143        119 
## 2017-07-31 2017-08-01 2017-08-02 2017-08-03 2017-08-04 2017-08-05 2017-08-06 
##        112        111        106        132        133        120         99 
## 2017-08-07 2017-08-08 2017-08-09 2017-08-10 2017-08-11 2017-08-12 2017-08-13 
##        133         89        116        130         76        123        122 
## 2017-08-14 2017-08-15 2017-08-16 2017-08-17 2017-08-18 2017-08-19 2017-08-20 
##         94        117        108        125        103        116        119 
## 2017-08-21 2017-08-22 2017-08-23 2017-08-24 2017-08-25 2017-08-26 2017-08-27 
##        101         76         91        106        143        103        159 
## 2017-08-28 2017-08-29 2017-08-30 2017-08-31 2017-09-01 2017-09-02 2017-09-03 
##        135         86         71         74        136         66         81 
## 2017-09-04 2017-09-05 2017-09-06 2017-09-07 2017-09-08 2017-09-09 2017-09-10 
##         37         21         16         19          4          6          4 
## 2017-09-12 2017-09-14 
##          1          2

Es mucha información en esta salida, sin embargo, podemos identificar:

  • una variable con 488 valores NULL
  • una segunda variable con 16,340 valores NULL
  • una tercer variable tiene 112,593 valores NULL

Veamos otro resumen diferente que nos permitirá identificar las variables con valores NA:

map_dbl(df, .f = function(x){sum(is.na(x))})
##                          hotel                    is_canceled 
##                              0                              0 
##                      lead_time              arrival_date_year 
##                              0                              0 
##             arrival_date_month       arrival_date_week_number 
##                              0                              0 
##      arrival_date_day_of_month        stays_in_weekend_nights 
##                              0                              0 
##           stays_in_week_nights                         adults 
##                              0                              0 
##                       children                         babies 
##                              4                              0 
##                           meal                        country 
##                              0                              0 
##                 market_segment           distribution_channel 
##                              0                              0 
##              is_repeated_guest         previous_cancellations 
##                              0                              0 
## previous_bookings_not_canceled             reserved_room_type 
##                              0                              0 
##             assigned_room_type                booking_changes 
##                              0                              0 
##                   deposit_type                          agent 
##                              0                              0 
##                        company           days_in_waiting_list 
##                              0                              0 
##                  customer_type                            adr 
##                              0                              0 
##    required_car_parking_spaces      total_of_special_requests 
##                              0                              0 
##             reservation_status        reservation_status_date 
##                              0                              0

En esta otra vista se puede ver que hay 4 valores perdidos solo en la variable children.

Identifiquemos cuáles son las variables en donde hay valores NULL:

nulls <- function(variable) {
  sum(if_else(variable == 'NULL', 1, 0))
}

df %>% 
  mutate_all(as.character) %>% 
  lapply(., nulls)
## $hotel
## [1] 0
## 
## $is_canceled
## [1] 0
## 
## $lead_time
## [1] 0
## 
## $arrival_date_year
## [1] 0
## 
## $arrival_date_month
## [1] 0
## 
## $arrival_date_week_number
## [1] 0
## 
## $arrival_date_day_of_month
## [1] 0
## 
## $stays_in_weekend_nights
## [1] 0
## 
## $stays_in_week_nights
## [1] 0
## 
## $adults
## [1] 0
## 
## $children
## [1] NA
## 
## $babies
## [1] 0
## 
## $meal
## [1] 0
## 
## $country
## [1] 488
## 
## $market_segment
## [1] 0
## 
## $distribution_channel
## [1] 0
## 
## $is_repeated_guest
## [1] 0
## 
## $previous_cancellations
## [1] 0
## 
## $previous_bookings_not_canceled
## [1] 0
## 
## $reserved_room_type
## [1] 0
## 
## $assigned_room_type
## [1] 0
## 
## $booking_changes
## [1] 0
## 
## $deposit_type
## [1] 0
## 
## $agent
## [1] 16340
## 
## $company
## [1] 112593
## 
## $days_in_waiting_list
## [1] 0
## 
## $customer_type
## [1] 0
## 
## $adr
## [1] 0
## 
## $required_car_parking_spaces
## [1] 0
## 
## $total_of_special_requests
## [1] 0
## 
## $reservation_status
## [1] 0
## 
## $reservation_status_date
## [1] 0

Hemos identificado que en las variables country, agent y company hay valores NULL.

Después de haber realizado un primer análisis exploratorio lo que debemos hacer es:

  • Cambiar algunos tipos de datos de character a factor.
  • Trabajar con los datos NA de la variable children.
  • Decidir qué hacer con las observaciones NULL de las tres variables que identificamos.

 

Outliers y Cardinalidad

En esta parte del análisis exploratorio empezaremos por revisar las variables categóricas o de tipo character. Aquí el objetivo es conocer cómo está conformada la cardinalidad en cada variable, ya que de haber pocas observaciones de una categoría será preferible modificarla y crear grupos.

Empecemos con el primer grupo de variables:

df2 %>% 
  select(1:7) %>% 
  mutate(id = 1:nrow(.)) %>% 
  pivot_longer(-id, names_to = 'variable', values_to = 'valor') %>% 
  ggplot(aes(valor)) +
  geom_bar() +
  scale_y_continuous(labels = scales::comma) + 
  facet_wrap(~variable, scales = 'free', ncol = 2)

 

Al observar las gráficas anteriores podemos determinar las siguientes acciones a seguir:

  • Para la variable distribution_channel: juntar las dos o tres categorías más bajas en una sola.
  • En la variable market_segment: revisar la frecuencia de los segmentos para agrupar en una sola categoría las que tienen muy baja cantidad de observaciones.
  • Variable resereved_room_type: seguir la misma estratégia, juntar en una sola categoría las observaciones con menor frecuencia.
  • Variable country: determinar cómo agrupar las observaciones que presentan mucha dispersión.
  • Para la variable meal: igualmente, hacer un grupo con las categorías menos frecuentes.

Segundo grupo de variables tipo character:

df2 %>% 
  select(8:14) %>% 
  mutate(id = 1:nrow(.)) %>% 
  pivot_longer(-id, names_to = 'variable', values_to = 'valor') %>% 
  ggplot(aes(valor)) +
  geom_bar() +
  scale_y_continuous(labels = scales::comma) + 
  facet_wrap(~variable, scales = 'free', ncol = 2)

 

En esta segunda parte de variables categóricas, podemos concluir las siguientes acciones a seguir:

  • En la variable agent: revisar la variable ya que parece que no hay mucha información.
  • Variable company: eliminarla, ya que hay demasiados valores NULL (112,593 de 119,390).
  • Para la variable deposit_type: juntar la categoría de menor frecuencia con la categoría Non Refund.
  • Assigned_room_type: dejar las principales categorías y juntar el resto en otra.
  • Customer_type: juntar todas las categorías en una sola excepto la de mayor frecuencia.
  • Reservation_staus: dejar solo dos categorías pasando No-Show a Canceled.

 

Variables discretas

Ahora, veamos las variables de tipo discreto, o sea, las de tipo entero:

df3 <- df %>% select_if(is.integer)

df3 %>% 
  select(1:9) %>% 
  mutate(id = 1:nrow(.)) %>% 
  pivot_longer(-id, names_to = 'variable', values_to = 'valor') %>% 
  ggplot(aes(valor)) +
  geom_bar() +
  scale_y_continuous(labels = scales::comma) + 
  facet_wrap(vars(variable), scales = 'free',ncol = 2)

df3 %>% 
  select(10:17) %>% 
  mutate(id = 1:nrow(.)) %>% 
  pivot_longer(-id, names_to = 'variable', values_to = 'valor') %>% 
  ggplot(aes(valor)) +
  geom_bar() +
  scale_y_continuous(labels = scales::comma) + 
  facet_wrap(vars(variable), scales = 'free',ncol = 2)

 

Al analizar visualmente esas variables podemos afirmar que también tenemos que hacer algunas transformaciones:

  • Las siguientes variables pasarlas a tipo binarias, ya que por su alto desbalanceo conviene tenerlas en ese tipo:
    • babies
    • children
    • previous_cancelations
    • total_special_request
    • booking_changes
    • days_in_waiting_list
    • previous_bookings_not_canceled
    • required_car_parking_spaces
  • Revisar la variable adults: crear solo 3 categorías (1, 2 y más de 2).
  • En la variable stays_in_week_nights: juntar las observaciones que sean más de 4.
  • En stays_in_weekend_nights: por conveniencia, hacer dos categorías, 1 y más de 1.

 

Variables continuas

Finalmente, revisamos la única variable continua que tenemos, adr:

df %>% 
  ggplot(aes(x = factor(1), y = adr)) +
  geom_boxplot(outlier.colour = "red") +
  scale_y_continuous(labels = scales::comma) + 
  labs(
    title = "adr"
  )

En el boxplot se aprecia claramente que hay un valor extremo, sin embargo, no sabemos su valor. En estos casos es recomendable hacer una tabla resumen con las principales medidas estadísticas:

descr(df$adr, 
      style = "rmarkdown", 
      justify = "c",
      headings = T) %>% 
  kbl(align = "c", digits = 2 ,format.args = list(big.mark = ",")) %>%
  kable_paper("hover", full_width = F)
adr
Mean 101.83
Std.Dev 50.54
Min -6.38
Q1 69.29
Median 94.58
Q3 126.00
Max 5,400.00
MAD 41.25
IQR 56.71
CV 0.50
Skewness 10.53
SE.Skewness 0.01
Kurtosis 1,013.13
N.Valid 119,390.00
Pct.Valid 100.00
arrange(df,desc(adr)) %>% 
  select(adr) %>%
  head(10) %>%
  kbl(align = "c", digits = 2 ,format.args = list(big.mark = ",")) %>%
  kable_paper("hover", full_width = F)
adr
5,400.00
510.00
508.00
451.50
450.00
437.00
426.25
402.00
397.38
392.00

Con apoyo de las tablas resumen sabemos que es de 5,400. Es una sola observación la que está afectado a toda la variable. Aquí lo recomendable será:

  • Eliminar la observacion extrema de 5,400.

 

Transformación de datos

A continuación, realizaremos las transformaciones que previamente identificamos en la etapa de exploración.

Cambiar el tipo de variables

# Se prepara el cambio

a_factor <- c('hotel','arrival_date_year','arrival_date_month','arrival_date_week_number',
              'arrival_date_day_of_month','meal','country','market_segment',
              'distribution_channel', 'reserved_room_type','assigned_room_type',
              'deposit_type','agent','company','customer_type','reservation_status')

a_logico <- c('is_canceled','is_repeated_guest')

a_discreto <- c('stays_in_weekend_nights','stays_in_week_nights','adults','children',
                'babies','previous_cancellations','previous_bookings_not_canceled',
                'booking_changes','days_in_waiting_list','required_car_parking_spaces',
                'total_of_special_requests')

# Hacemos el cambio

df <- df %>% 
  mutate_at(all_of(a_factor), as.factor) %>% 
  mutate_at(all_of(a_logico), as.logical) %>% 
  mutate_at(all_of(a_discreto), as.integer)

glimpse(df)
## Rows: 119,390
## Columns: 32
## $ hotel                          <fct> Resort Hotel, Resort Hotel, Resort Hote~
## $ is_canceled                    <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALS~
## $ lead_time                      <int> 342, 737, 7, 13, 14, 14, 0, 9, 85, 75, ~
## $ arrival_date_year              <fct> 2015, 2015, 2015, 2015, 2015, 2015, 201~
## $ arrival_date_month             <fct> July, July, July, July, July, July, Jul~
## $ arrival_date_week_number       <fct> 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,~
## $ arrival_date_day_of_month      <fct> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ~
## $ stays_in_weekend_nights        <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ~
## $ stays_in_week_nights           <int> 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 4, ~
## $ adults                         <int> 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, ~
## $ children                       <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ~
## $ babies                         <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ~
## $ meal                           <fct> BB, BB, BB, BB, BB, BB, BB, FB, BB, HB,~
## $ country                        <fct> PRT, PRT, GBR, GBR, GBR, GBR, PRT, PRT,~
## $ market_segment                 <fct> Direct, Direct, Direct, Corporate, Onli~
## $ distribution_channel           <fct> Direct, Direct, Direct, Corporate, TA/T~
## $ is_repeated_guest              <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALS~
## $ previous_cancellations         <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ~
## $ previous_bookings_not_canceled <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ~
## $ reserved_room_type             <fct> C, C, A, A, A, A, C, C, A, D, E, D, D, ~
## $ assigned_room_type             <fct> C, C, C, A, A, A, C, C, A, D, E, D, E, ~
## $ booking_changes                <int> 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ~
## $ deposit_type                   <fct> No Deposit, No Deposit, No Deposit, No ~
## $ agent                          <fct> NULL, NULL, NULL, 304, 240, 240, NULL, ~
## $ company                        <fct> NULL, NULL, NULL, NULL, NULL, NULL, NUL~
## $ days_in_waiting_list           <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ~
## $ customer_type                  <fct> Transient, Transient, Transient, Transi~
## $ adr                            <dbl> 0.00, 0.00, 75.00, 75.00, 98.00, 98.00,~
## $ required_car_parking_spaces    <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ~
## $ total_of_special_requests      <int> 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 3, ~
## $ reservation_status             <fct> Check-Out, Check-Out, Check-Out, Check-~
## $ reservation_status_date        <chr> "2015-07-01", "2015-07-01", "2015-07-02~

Ya tenemos todas las variables con el tipo correcto. La variable adr de origen viene bien y la variable reservation_status_date por el momento conviene tenerla como character (en ese tipo no ocupa mucho espacio en memoria).

 

Tratamiento de valores NA y NULL

Ya habíamos identificado que en la variable children hay 4 valores NA, por lo que, se procederá a imputarlos por el valor más frecuente (la moda). También, en la variable country se detectaron 488 valores NULL, por lo que, se seguirá la misma estratégia de imputación debido a que son relativamente pocos datos en relacion con el tamaño de la base de datos. En este paso, aprovecharemos para eliminar las variables agent y company, ya que tienen demasiados valores NULL y no conviene realizar algún tipo de imputación:

# se identifica el valor más frecuente

count(df, children, sort = T) %>% # children
  kbl(align = "c", digits = 2 ,format.args = list(big.mark = ",")) %>%
  kable_paper("hover", full_width = F)
children n
0 110,796
1 4,861
2 3,652
3 76
NA 4
10 1
count(df, country, sort = T) %>% head(10) %>% # country
  kbl(align = "c", digits = 2 ,format.args = list(big.mark = ",")) %>%
  kable_paper("hover", full_width = F)
country n
PRT 48,590
GBR 12,129
FRA 10,415
ESP 8,568
DEU 7,287
ITA 3,766
IRL 3,375
BEL 2,342
BRA 2,224
NLD 2,104

Se eliminan las variables agent y company. Se imputan los valores NA y NULL.

df <- df %>% 
  select(-agent, -company) %>% # se eliminan esas variables
  mutate(children = ifelse(is.na(children), 0, children), # se sustituyen NA por 0
         country = fct_recode(country, 'PRT' = 'NULL') # se sustituyenn NULL por PRT
  )

 

Outlier y discretización

Como solo se identificó una observacion exrema (5,400), se procederá a eliminarla del dataset:

df <- df %>% 
  filter(adr != 5400)

Las acciones para recodificar algunas variables serán:

  • En country dejar los 15 primeros mercados y juntar el resto en la categoría OTROS
  • En market_segment unir Undefined, Aviation, Complementary en el grupo Corporate
  • En assigned_room_type y reserved_room_type dejar A y D juntar el resto
  • En customer_type agrupar para dejar solo las categorías Transient y OTROS
  • En distribution_channel juntar Corporate, GDS y Undefined en Corporate
  • En reservation_status agrupar las observaciones de No-Show con Canceled
  • En meal recodificar entre BB y OTROS
  • En desposit_type juntar Non Refund con Refundable y llamarlo Deposit
df <- df %>%
  #Recodificar
  mutate(
         country = fct_lump(country, n = 15, other_level = 'OTROS'),
         market_segment = fct_collapse(market_segment,
                                       'Corporate' = c('Corporate','Undefined','Aviation','Complementary')),
         distribution_channel = fct_collapse(distribution_channel,
                                       'Corporate' = c('Corporate','Undefined','GDS')),
         reservation_status = fct_collapse(reservation_status,
                                       'Canceled' = c('Canceled','No-Show')),
         deposit_type = fct_collapse(deposit_type,
                                       'Deposit' = c('Non Refund','Refundable')),
         assigned_room_type = fct_collapse(assigned_room_type,
                                           'A' = 'A',
                                           'D' = 'D',
                                           other_level = 'OTROS'),
         reserved_room_type = fct_collapse(reserved_room_type,
                                           'A' = 'A',
                                           'D' = 'D',
                                           other_level = 'OTROS'),
         customer_type = fct_collapse(customer_type,
                                           'Transient' = 'Transient',
                                           other_level = 'OTROS'),
         meal = fct_collapse(meal,
                                  'BB' = 'BB',
                                  other_level = 'OTROS'))

Ahora, lo siguiente es discretizar las tres variables que por su distribución tan dispersa es mejor modificarlas:

  • Convertir adults a 1, 2 y mas de 2
  • En stays_in_week_nights juntar en 1, 2, 3, 4 y mas de 4
  • En stays_in_weekend_nights juntar en 1 y más de 1
df <- df %>%
  mutate(
         adults = as.factor(case_when(adults <= 1 ~ '01_Uno',
                            adults == 2 ~ '02_Dos',
                            adults > 2 ~ '03_Mas_de_Dos',
                            TRUE ~ '99_ERROR')),
         stays_in_week_nights_disc = as.factor(case_when(stays_in_week_nights <= 1 ~ '01_Uno',
                            stays_in_week_nights == 2 ~ '02_Dos',
                            stays_in_week_nights == 3 ~ '03_Tres',
                            stays_in_week_nights == 4 ~ '04_Cuatro',
                            stays_in_week_nights > 4 ~ '05_Mas_de_Cuatro',
                            TRUE ~ '99_ERROR')),
         stays_in_weekend_nights_disc = as.factor(case_when(stays_in_weekend_nights <= 1 ~ '01_Uno',
                            stays_in_weekend_nights > 1 ~ '02_Mas_de_Uno',
                            TRUE ~ '99_ERROR')),
        
         )

Por último, crear variables binarias para las siguientes variables:

  • babies
  • booking_changes
  • children
  • previous_cancelations
  • required_car_parking_spaces
  • total_special_request
  • days_in_waiting_list
  • previous_bookings_not_canceled
df <- df %>%
  mutate(
         babies = as.logical(ifelse(babies == 0, 0, 1)),
         booking_changes = as.logical(ifelse(booking_changes == 0, 0, 1)),
         children = as.logical(ifelse(children == 0, 0, 1)),
         previous_cancellations = as.logical(ifelse(previous_cancellations == 0, 0, 1)),
         required_car_parking_spaces = as.logical(ifelse(required_car_parking_spaces == 0, 0, 1)),
         total_of_special_requests = as.logical(ifelse(total_of_special_requests == 0, 0, 1)),
         days_in_waiting_list = as.logical(ifelse(days_in_waiting_list == 0, 0, 1)),
         previous_bookings_not_canceled = as.logical(ifelse(previous_bookings_not_canceled == 0, 0, 1))
         )

Una vez que hemos realizado todas las transformaciones que identificamos en la parte del análisis exploratorio, Volvemos a hacer los gráficos para comprobar los resultados deseados.

Variables de tipo factor:

# primer bloque de variables

df %>% 
  select_if(is.factor) %>%
  select(1:9) %>% 
  mutate(id = 1:nrow(.)) %>% 
  pivot_longer(-id, names_to = 'variable', values_to = 'valor') %>% 
  ggplot(aes(valor)) +
  geom_bar() +
  scale_y_continuous(labels = scales::comma) + 
  facet_wrap(vars(variable), scales = 'free', ncol = 3)

# segundo bloque de variables

df %>% 
  select_if(is.factor) %>%
  select(10:17) %>% 
  mutate(id = 1:nrow(.)) %>% 
  pivot_longer(-id, names_to = 'variable', values_to = 'valor') %>% 
  ggplot(aes(valor)) +
  geom_bar() +
  scale_y_continuous(labels = scales::comma) + 
  facet_wrap(vars(variable), scales = 'free', ncol = 3)

Variables binarias o lógicas:

df %>% 
  select_if(is.logical) %>% 
  mutate(id = 1:nrow(.)) %>% 
  pivot_longer(-id, names_to = 'variable', values_to = 'valor') %>% 
  ggplot(aes(valor)) +
  geom_bar() +
  scale_y_continuous(labels = scales::comma) + 
  facet_wrap(vars(variable), scales = 'free',ncol = 2)

Variable continua:

df %>% 
  ggplot(aes(x = factor(1), y = adr)) +
  geom_boxplot(outlier.color = "red") +
  labs(
    title = "adr"
  )

descr(df$adr, 
      style = "rmarkdown", 
      justify = "center",
      headings = T) %>% 
  kbl(align = "c", digits = 2 ,format.args = list(big.mark = ",")) %>%
  kable_paper("hover", full_width = F)
adr
Mean 101.79
Std.Dev 48.15
Min -6.38
Q1 69.29
Median 94.56
Q3 126.00
Max 510.00
MAD 41.28
IQR 56.71
CV 0.47
Skewness 1.02
SE.Skewness 0.01
Kurtosis 2.13
N.Valid 119,389.00
Pct.Valid 100.00

En las gráficas actualizadas, ya podemos apreciar mejor las distribuciones de las variables, pues no hay asimetrías fuertes ni desbalances severos en las variables de tipo factor. Las variables binarias o lógicas se ven bien y la única variable continua aunque presenta valores atípicos ya no hay presencia de valores extremos enmascarados.

 

Creación de variables sintéticas

Una de las partes más interesantes en cualquier proyecto de discovery es el de crear variables nuevas a partir de las variables originales, las cuales son comúnmente llamadas variables sintéticas. Estas variables deben de crearse con un sentido de negocio. A continuación, crearemos algunas variables que nos servirán para la etapa de discovery.

En la base de datos no viene una fecha de llegada como tal, o sea, compuesta por el día, mes y año, sin embargo, si tenemos cada componente por separado, por lo que, vamos a crearla.

Ademas, tenemos información sobre el número de personas adultas, niños y bebés, por tanto, podemos crear una variable que nos indique el tipo de familia que se ha hospedado.

Podemos incluir otra variable para saber si la persona es extranjera o nacional, entendiendo que nacional correspondería a los clientes de nacionalidad portuguesa.

Una tercera variable que nos sería de utilidad es el cambio de habitación, que la obtendremos de la diferencia entre el tipo de cuarto reservado y el tipo de cuarto asignado.

Tambien, puede ser de utilidad conocer las noches totales que las personas se quedaron en algún hotel, conformadas por la suma de las noches entre semana más las noches en fin de semana.

Finalmente, una variable que sorprende que no venga en la base es la facturación total, por lo que, la crearemos.

En resumen, lo que haremos será crear las siguientes variables:

  • fecha_llegada: a partir de arrival_date_year, arrival_date_month y arrival_date_day_of_month
  • familia: a partir de adults, children y babies
  • extranjero: 0 si es de Portugal y 1 si no lo es
  • cambio_habit: si el valor de reserved_room_type es diferente al de assigned_room_type
  • noches_totales: suma de stays_in_weekend_nights más stays_in_week_nights
  • facturacion_reserva: lo que se ha facturado en esa reservación como resultado de multiplicar la variable creada noches_totales por adr
df <- df %>% 
  mutate(fecha_llegada = as_date(paste(arrival_date_year,
                                       arrival_date_month,
                                       arrival_date_day_of_month,
                               sep = '/')),
         familia = case_when(
           adults == '01_Uno' & children + babies == 0 ~ '01_Soltero',
           adults == '01_Uno' & children + babies > 0 ~ '02_Monoparental',
           adults == '02_Dos' & children + babies == 0 ~ '03_Pareja_Sin_Hijos',
           adults == '02_Dos' & children + babies > 0 ~ '04_Pareja_Con_Hijos',
           adults == '03_Mas_de_Dos' & children + babies == 0 ~ '05_Grupo_Amigos',
           adults == '03_Mas_de_Dos' & children + babies > 0 ~ '06_Grupo_Amigos_Con_Hijos',
           TRUE ~ '99_ERROR'),
         extranjero = ifelse(country == 'PRT', 0, 1),
         cambio_habit = ifelse(reserved_room_type == assigned_room_type, 0, 1),
         noches_totales = stays_in_weekend_nights + stays_in_week_nights,
         facturacion_reserva = noches_totales * adr
         )

 

Discovery y Generación de Insights

En esta etapa es donde vamos a hacer propiamente el análisis del negocio, pues ya tenemos la data limpia y transformada. Es importante tener presente el tipo de giro o sector, ya que de eso dependerá los insights a buscar.

Estacionalidad

Para el sector hotelero y, en general, el turismo, el identificar las llamadas temporadas altas y bajas es muy importante, ya que eso les permitirá prepararse, por ejemplo, para la llegada de masiva de personas (temporada alta) y/o hacer remodelaciones o ampliaciones a su infraestructura (temporada baja).

Técnicamente, para poder determinar si una serie presenta estacionalidad, entendida ésta como tendencias o patrones que se repiten constantemente dentro de un periodo de tiempo, es necesario al memos cuatro años de historia, sin embargo, como no contamos con tanta historia solo nos enfocaremos en el único periodo completo que tenemos, año 2016.

Veamos la tendencia del año 2016 en general, para ambos hoteles:

df %>% 
  filter(arrival_date_year == '2016') %>% 
  ggplot(aes(fecha_llegada)) + 
  geom_density(size = 2, color = 'grey') + 
  theme(axis.text.x = element_text(angle = 90, size = 10)) +
    labs(title = "Llegadas 2016")

Al ver la gráfica podemos apreciar que al inicio y al final del año la demanda es baja, pero al inicio del tercer trimestre la demanda es la más alta. Complementemos el análisis visual con una tabla resumen:

df %>% 
  mutate(reservaciones = rep(1, nrow(df))) %>% # se crea la variable reservaciones
  filter(arrival_date_year == 2016) %>%        # se filtra por el año 2016
  group_by(arrival_date_month) %>%             # agrupamos por el mes de llegada
  summarise(reservaciones = n()) %>%           # contamos el número de reservaciones
  arrange(desc(-reservaciones)) %>%            # ordenamos de menor a mayor
  kbl(align = "c", digits = 2 ,format.args = list(big.mark = ",")) %>%
  kable_paper("hover", full_width = F)      # acomodamos la salida en una tabla 
arrival_date_month reservaciones
January 2,248
December 3,860
February 3,891
November 4,454
July 4,572
March 4,823
August 5,063
June 5,292
September 5,394
April 5,428
May 5,478
October 6,203

Ya con esta otra vista podemos concluir lo siguiente:

  • En los meses de mayo y octubre hay mayor demanda de reservaciones.
  • Enero, febrero y diciembre son los meses de menor demanda.
  • Sorprende Julio y Agosto que son relativamente bajos.

Ahora, veamos la tendencia por tipo de hotel:

df %>% 
  filter(arrival_date_year == '2016') %>% 
  ggplot(aes(fecha_llegada, color = hotel)) + 
  scale_color_manual(values = c("steelblue", "grey50"))+
  geom_density(size = 2) + 
  theme(axis.text.x = element_text(angle = 90, size = 10)) +
  labs(title = "Llegadas 2016 por tipo de Hotel")

De la gráfica anterior podemos ver que:

  • Mantienen curvas de demanda parecidas.
  • El hotel resort tiene ligeramente más demanda hasta la primavera.
  • Mientras que el de ciudad destaca en Junio, Septiembre y Octubre

Hagamos una tabla para comparar los datos:

df %>% 
  mutate(reservaciones = rep(1, nrow(df))) %>% # se crea la variable reservaciones
  filter(arrival_date_year == 2016) %>%        # se filtra por el año 2016
  group_by(arrival_date_month, hotel) %>%      # se agrupa por el mes de arrivo y hotel
  summarise(reservaciones = n()) %>%           # se hace conteo de reservaciones
  arrange(desc(-reservaciones)) %>%            # se ordena de menor a mayor
  pivot_wider(names_from = hotel, values_from = reservaciones) %>% # se pasa a formato ancho
  kbl(align = "c", digits = 2 ,format.args = list(big.mark = ",")) %>% 
  kable_paper("hover", full_width = F) # salida en forma de tabla
arrival_date_month Resort Hotel City Hotel
January 884 1,364
November 1,332 3,122
June 1,369 3,923
December 1,382 2,478
July 1,441 3,131
February 1,520 2,371
September 1,523 3,871
August 1,685 3,378
March 1,778 3,045
May 1,802 3,676
April 1,867 3,561
October 1,984 4,219

 

Análisis de facturación

Para poder analizar la facturación tenemos que trabajar sólo con las reservas que finalmente tuvieron check-out.

df %>% 
  filter(reservation_status == 'Check-Out') %>% 
  group_by(arrival_date_year, hotel) %>% 
  summarise(facturacion = sum(facturacion_reserva)) %>% 
  ggplot(aes(x = arrival_date_year, y = facturacion, fill = hotel)) +
  scale_fill_manual(values = c("steelblue", "grey50")) +
  scale_y_continuous(labels = scales::comma) + 
  geom_col(position = 'dodge') +
  labs(title = "Facturación por Tipo de Hotel", y = "Facturación", x = "Año de Llegada")

En la gráfica podemos apreciar claramente que la facturación se ha ido incrementando cada año, ahora, hay que tener presente que solo el año 2016 está completo, sin embargo, vemos que el 2017 se ve bien, pues solo llega al mes de agosto y casi alcanza los niveles de 2016.

Este incremento pudo deberse a varios motivos como, por ejemplo:

  • un incremento en el número de las reservaciones, hubo más clientes.
  • aumentó el precio medio.
  • crecimiento promedio de noches por reserva.

Con el apoyo de una tabla veamos si esos motivos explican el incremento en la facturación:

df %>% 
  filter(reservation_status == 'Check-Out' & arrival_date_year %in% c('2015','2016')) %>% 
  group_by(hotel,arrival_date_year) %>% 
  summarise(
    num_reservas = n(),
    noches_por_reserva = mean(noches_totales),
    precio_noche = mean(adr)) %>% 
  kbl(align = "c", digits = 2 ,format.args = list(big.mark = ",")) %>% 
  kable_paper("hover", full_width = T)
hotel arrival_date_year num_reservas noches_por_reserva precio_noche
City Hotel 2015 7,678 2.76 87.87
City Hotel 2016 22,733 2.90 104.08
Resort Hotel 2015 6,176 4.39 89.76
Resort Hotel 2016 13,637 3.96 83.91

 

Ya con la tabla podemos concluir que, para el caso del hotel de ciudad, el incremento de su facturación se debió, principalmente, al aumento en el número de reservas (casi 3 veceses más) y, en segundo lugar, al aumento del precio promedio por noche. Para el caso del hotel resort, el incremento en su facturación es debido únicamente al aumento en el número de reservas, porque tanto el número de noches por reserva como el precio promedio por noche presentaron disminuciones.

Veamos cómo está conformada la facturación por segmento de mercado:

df %>% 
  group_by(hotel, market_segment) %>% 
  summarise(facturacion_segmento = sum(facturacion_reserva)) %>% 
  group_by(hotel) %>% 
  mutate(facturacion_total = sum(facturacion_segmento),
         porc_facturacion_total = facturacion_segmento / facturacion_total * 100) %>%
  dplyr::select(-facturacion_total) %>% 
  kbl(align = "c", digits = 2 ,format.args = list(big.mark = ",")) %>% 
  kable_paper("hover", full_width = T)
hotel market_segment facturacion_segmento porc_facturacion_total
City Hotel Corporate 574,501.0 2.27
City Hotel Direct 2,122,531.5 8.40
City Hotel Groups 3,121,991.0 12.35
City Hotel Offline TA/TO 4,469,802.0 17.69
City Hotel Online TA 14,985,244.4 59.29
Resort Hotel Corporate 292,371.2 1.68
Resort Hotel Direct 2,970,496.9 17.03
Resort Hotel Groups 1,547,645.7 8.87
Resort Hotel Offline TA/TO 3,676,710.8 21.08
Resort Hotel Online TA 8,956,803.1 51.35

 

De la tabla anterior, podemos concluir que la mayor parte de la facturación en ambos hoteles viene por el lado del segmento Online y, por el contrario, el segmento que menos contribuye es el Corporate.

t0 <- df %>% 
  group_by(hotel, market_segment) %>% 
  summarise(facturacion_segmento = sum(facturacion_reserva)) %>% 
  group_by(hotel) %>% 
  mutate(facturacion_total = sum(facturacion_segmento),
         porc_facturacion_total = facturacion_segmento / facturacion_total * 100) %>%
  dplyr::select(-facturacion_total, -porc_facturacion_total)

t0$hotel <- as.character(t0$hotel)
t0$market_segment <- as.character(t0$market_segment)

sankey_ly(x = t0, 
          cat_cols = c("hotel", "market_segment"), 
          num_col = "facturacion_segmento", 
          title = "Distribución de Ingresos por Segmento Según Tipo de Hotel") 

Ahora, analicemos la facturación por familia, para ver la contribución de cada segmento:

df %>% 
  group_by(hotel, familia) %>% 
  summarise(facturacion_familia = sum(facturacion_reserva)) %>% 
  group_by(hotel) %>% 
  mutate(facturacion_total = sum(facturacion_familia),
         porc_facturacion_total = facturacion_familia / facturacion_total * 100) %>% 
  dplyr::select(-facturacion_total) %>% 
  kbl(align = "c", digits = 2 ,format.args = list(big.mark = ",")) %>% 
  kable_paper("hover", full_width = T)
hotel familia facturacion_familia porc_facturacion_total
City Hotel 01_Soltero 3,797,560.88 15.03
City Hotel 02_Monoparental 217,619.03 0.86
City Hotel 03_Pareja_Sin_Hijos 16,374,295.01 64.79
City Hotel 04_Pareja_Con_Hijos 2,347,447.84 9.29
City Hotel 05_Grupo_Amigos 2,435,302.74 9.64
City Hotel 06_Grupo_Amigos_Con_Hijos 101,844.46 0.40
Resort Hotel 01_Soltero 1,221,868.26 7.00
Resort Hotel 02_Monoparental 97,736.74 0.56
Resort Hotel 03_Pareja_Sin_Hijos 12,250,816.46 70.23
Resort Hotel 04_Pareja_Con_Hijos 2,676,953.73 15.35
Resort Hotel 05_Grupo_Amigos 853,298.76 4.89
Resort Hotel 06_Grupo_Amigos_Con_Hijos 343,353.62 1.97

 

Claramente se aprecia en el cuadro anterior, que el segmento de parejas sin hijos es el que más contribuye a la facturación total en ambos hoteles.

t2 <- df %>% 
  group_by(hotel, familia) %>% 
  summarise(facturacion_familia = sum(facturacion_reserva)) %>% 
  group_by(hotel) %>% 
  mutate(facturacion_total = sum(facturacion_familia),
         porc_facturacion_total = facturacion_familia / facturacion_total * 100) %>% 
  dplyr::select(-facturacion_total, -porc_facturacion_total)

t2$hotel <- as.character(t2$hotel)

sankey_ly(x = t2, 
          cat_cols = c("hotel", "familia"), 
          num_col = "facturacion_familia", 
          title = "Distribución de Ingresos por Familia Según Tipo de Hotel") 

Análisis del mercado y del cliente

En cuanto a los clientes, como ambos hoteles se encuentran en Portugal suponemos que la mayoría son de ese mismo país, sin embargo, hagamos una tabla para ver la distribución entre clientes nacionales y extranjeros:

count(df, hotel, extranjero) %>% 
  group_by(hotel) %>% 
  mutate(total_hotel = sum(n),
         porc_hotel = n / total_hotel * 100) %>% 
  kbl(align = "c", digits = 2 ,format.args = list(big.mark = ",")) %>% 
  kable_paper("hover", full_width = F)
hotel extranjero n total_hotel porc_hotel
City Hotel 0 30,983 79,329 39.06
City Hotel 1 48,346 79,329 60.94
Resort Hotel 0 18,094 40,060 45.17
Resort Hotel 1 21,966 40,060 54.83

 

Al ver el cuadro anterior, sorprende identificar que la mayoría de los clientes son extranjeros en ambos hoteles, ademas, en el hotel de ciudad hay casi el doble de clientes que en el hotel resort y en éste último poco menos de la mitad son clientes nacionales.

Ahora, veamos el top 5 de clientes por nacionalidad para ver de qué países hay mayor presencia para ambos hoteles:

count(df, hotel, country) %>% 
  group_by(hotel) %>% 
  mutate(total_hotel = sum(n),
         porc_hotel = n / total_hotel * 100) %>% 
  arrange(hotel, desc(porc_hotel)) %>% 
  top_n(5) %>% 
  dplyr::select(-total_hotel) %>% 
  kbl(align = "c", digits = 2 ,format.args = list(big.mark = ",")) %>% 
  kable_paper("hover", full_width = F)
hotel country n porc_hotel
City Hotel PRT 30,983 39.06
City Hotel FRA 8,804 11.10
City Hotel OTROS 8,483 10.69
City Hotel DEU 6,084 7.67
City Hotel GBR 5,315 6.70
Resort Hotel PRT 18,094 45.17
Resort Hotel GBR 6,814 17.01
Resort Hotel ESP 3,957 9.88
Resort Hotel OTROS 2,226 5.56
Resort Hotel IRL 2,166 5.41

 

Como era de esperarse, por país, la mayoría de los clientes de esta cadena de hoteles son de Portugal. Sin embargo, en el hotel de ciudad son los franceses y alemanes los que le siguen, a diferencia del hotel resort donde son los británicos y españoles los que tienen mayor frecuencia después de los portugueses.

 

Análisis de precios

A continuación, echemos un vistazo al promedio de los precios por noche:

df %>% 
  filter(arrival_date_year %in% c('2015','2016')) %>% 
  group_by(hotel, arrival_date_week_number) %>% 
  summarise(precio_medio = mean(adr)) %>%
  ggplot(aes(x = arrival_date_week_number, y = precio_medio, group = 1)) +
  geom_line(color = 'grey50') +
  geom_smooth(color = 'red') +
  theme(axis.text.x = element_text(size = 8)) +
  labs(title = "Tendencia de Precios: 2015-2016")

La gráfica nos muestra que, al inicio de cada año y al final, el precio promedio de las reservas tiene su menor nivel, lo cual coincide con las épocas de baja demanda. Son entre las semanas 29 a 35 donde el precio promedio alcanza sus niveles máximos que, igualmente, se corresponde con la temporada de alta demanda.

Por lo anterior, veamos si existe alguna correlación entre el precio promedio y la demanda de reservas.

 

Correlación Reservas vs Precio Medio

Vamos hacer un pequeño análisis estadístico de correlación, por lo que, debemos tener presente algunas consideraciones:

  1. Conocer si las variables presentan distribución normal, ya que de eso dependerá el coeficiente de correlación a usar.
  2. De acuerdo con el resultado del punto anterior, elegir el coeficiente de correlación que corresponda.
  3. Hacer una prueba de significancia estadística para descartar una posible correlación debido al azar.

Normalidad

Análisis visual:

c <- df %>% 
  filter(arrival_date_year %in% c('2015', '2016')) %>% 
  group_by(hotel, arrival_date_week_number) %>% 
  summarise(num_reservas = n(),
            precio_medio = mean(adr)) %>% 
  select(num_reservas, precio_medio)


h1 <- ggplot(data = c, aes(x = num_reservas)) + geom_histogram(aes(y = ..density.., fill = ..count..)) + scale_fill_gradient(low = "#DCDCDC", high = "#7C7C7C") + stat_function(fun = dnorm, colour = "firebrick", args = list(mean = mean(c$num_reservas), sd = sd(c$num_reservas))) + ggtitle("Histograma Reservas") + theme_bw()

qq1 <- ggplot(c, aes(sample = num_reservas)) +
  stat_qq() +
  stat_qq_line() +
  labs(title = "Distribución de Reservas")

h2 <- ggplot(data = c, aes(x = precio_medio)) + geom_histogram(aes(y = ..density.., fill = ..count..)) + scale_fill_gradient(low = "#DCDCDC", high = "#7C7C7C") + stat_function(fun = dnorm, colour = "firebrick", args = list(mean = mean(c$precio_medio), sd = sd(c$precio_medio))) + ggtitle("Histograma Precio Medio") + theme_bw()

qq2 <- ggplot(c, aes(sample = precio_medio)) +
  stat_qq() +
  stat_qq_line() +
  labs(title = "Distribución de Precio Medio")


grid.arrange(h1, h2, qq1, qq2, nrow = 2, ncol = 2, top = "Análisis de Normalidad")

Al ver las gráficas podemos sospechar que ambas variables no presentan una distribución normal. Para poder confirmar lo anterior tenemos que hacer pruebas estadísticas. Ahora, como ambas series tienen más de 50 observaciones aplicaremos la prueba de normalidad de Lilliefors:

Pruebas de normalidad:

lillie.test(x = c$num_reservas)
## 
##  Lilliefors (Kolmogorov-Smirnov) normality test
## 
## data:  c$num_reservas
## D = 0.12858, p-value = 0.000183
lillie.test(x = c$precio_medio)
## 
##  Lilliefors (Kolmogorov-Smirnov) normality test
## 
## data:  c$precio_medio
## D = 0.10501, p-value = 0.005855

Los resultados muestran que las dos series no presentan una distribución normal, ya que ambos p-value son menores al 0.05 de significancia. Por lo anterior, ya podemos decidir cuál coeficiente usar para conocer el grado de asociación entre ellas dadas las características de las series.

 

Correlación

Coeficiente de Correlación

Ya sabiendo que las series no presentan distribución normal, se usará el coeficiente de correlación de Spearman:

cor(c$num_reservas, log(c$precio_medio), method = "spearman")
## [1] 0.5401534

La correlación resulta ser positiva de 0.54, lo que nos indica un grado de asociación moderada, sin embargo, hay que hacer el test de significancia, para confirmar que realmente existe correlación entre el número de reservas y el precio medio, ya que de lo contrario dicha asociación puede deberse al azar.

cor.test(x = c$num_reservas,
         y = log(c$precio_medio),
         alternative = "two.sided",
         conf.level = 0.95,
         method = "spearman")
## 
##  Spearman's rank correlation rho
## 
## data:  c$num_reservas and log(c$precio_medio)
## S = 91273, p-value = 2.289e-09
## alternative hypothesis: true rho is not equal to 0
## sample estimates:
##       rho 
## 0.5401534

El p-value es cercano a cero (menor a 0.05), por lo que, podemos afirmar que el coeficiente de correlación antes obtenido es significativo, o sea, la asociación entre las reservas y el precio medio no se da por casualidad.

Ya por último, calculemos el coeficiente de determinación para medir el tamaño del efecto:

cor(c$num_reservas, log(c$precio_medio), method = "spearman")^2
## [1] 0.2917657

El tamaño del efecto es mediano, cercano al 0.3. Veamos la relación visualmente:

g <- c %>% 
  ggplot(aes(num_reservas, precio_medio)) +
  geom_point() +
  geom_smooth() +
  labs(title = "Relación Reservas vs Precio Promedio")

ggplotly(g)

Se aprecia que no hay una clara tendencia positiva o negativa, pues la nube de puntos casi se mantiene constante. Ahí hay una oportunidad de negocio.

veamos ahora por tipo de hotel y por semana durante el periodo de análisis:

df4 <- df %>% 
  filter(reservation_status == 'Check-Out' & adr > 0)
  
df4 <- df4 %>%  
  group_by(arrival_date_year, hotel, arrival_date_week_number) %>% 
   summarise(num_reservas = n(),
             precio_medio = mean(adr)) %>% 
   select(arrival_date_year, num_reservas, precio_medio, arrival_date_week_number)

df4$arrival_date_week_number <- as.numeric(df4$arrival_date_week_number)

dfr <- df4 %>% 
  dplyr::filter(hotel == "Resort Hotel")

dfc <- df4 %>% 
  dplyr::filter(hotel == "City Hotel")


gr <- ggplot(dfr, aes(num_reservas, precio_medio)) +
  geom_jitter() +
  geom_point(aes(colour = arrival_date_week_number), size = 3) +
  labs(title = "Tendencia Semanal Hotel Resort: Precio Medio y Número de Reservaciones")

gc <- ggplot(dfc, aes(num_reservas, precio_medio)) +
  geom_jitter() +
  geom_point(aes(colour = arrival_date_week_number), size = 3) +
  labs(title = "Tendencia Semanal Hotel Ciudad: Precio Medio y Número de Reservaciones")

grid.arrange(gr, gc, nrow = 2, top = "Tendencias Semanales")

En las gráficas anteriores, el color de los puntos indica el tiempo (número de semana), entre más claro es el color más cercano al fin de año nos encontramos. Es en el hotel resort donde, en general, no se corresponde la temporada alta con precios promedio elevados, por lo que, sería conveniente analizar la posibilidad de aumentar los precios en temporada alta, o sea, buscar el precio óptimo en ese periodo del año.

Veamos la distribución de los precios por tipo de hotel:

ggplot(df, aes(x = hotel, y = adr, color = hotel)) +
  geom_boxplot() +
  scale_color_manual(values = c("steelblue", "grey50")) +
  stat_summary(fun = mean, 
               geom = "point",
               shape = 20, 
               size = 4, 
               color = "red",
               fill = "red") +
  labs(title = "Precio Medio por Hotel")

Los precios medios en ambos tipos de hotel son muy parecidos, siendo en el hotel resort donde el 50% de los precios son más bajos.

pm <- df %>% 
  group_by(hotel, lead_time) %>% 
  summarise(precio_medio = mean(adr))
  
gm <- pm %>% 
  ggplot(aes(x = lead_time, y = precio_medio, color = hotel)) +
  geom_point(alpha = 0.3, size = 1) +
  scale_color_manual(values = c("blue", "black")) +
  geom_smooth(se = F, size = 1) +
  geom_vline(xintercept = 180, colour = "red") +
  labs(title = "Tiempo de Espera")

ggplotly(gm)

Esta gráfica nos indica que reservando con más de 6 meses de anticipación se consiguen precios más bajos, sobre todo en el hotel resort. Hay dos observaciones con precios de cero, lo que probablemente se deba a error de origen o fueron promociones que se obsequiaron.

 

Curva de Pareto

A continuación, se graficará la curva de Pareto de la facturación. Recordando que la curva de pareto consiste en mostrar la regla de que, aproximadamente, el 20% de las observaciones analizadas generan el 80% de sus resultados. Esta regla no es fija y puede variar la distribución, sin embargo, es un buen indicador que permite conocer a los mejores clientes, los más rentables.

Entonces, hagamos una curva de pareto de la facturación por país para conocer la contribución de cada uno al negocio con las siguientes consideraciones: solo crearla para las reservaciones que no fueron canceladas y, también, sin considerar la categoría de OTROS que creamos anteriormente (por su nivel marginal de contribución de cada país que integran esa categoría).

df %>% 
  filter(is_canceled == FALSE & country != "OTROS") %>% 
  group_by(country) %>% 
  summarise(total = sum(facturacion_reserva)) %>% 
  arrange(desc(total)) %>% 
  mutate(porc.acum.ventas = cumsum(total) / sum(total),
         ranking.ventas = row_number(desc(total))) %>% 
  dplyr::select(ranking.ventas, porc.acum.ventas) %>% 
  ggplot(aes(x = ranking.ventas, y = porc.acum.ventas)) +
  geom_line() +
  geom_vline(xintercept = 6, colour = "red") +
  geom_hline(yintercept = 0.79, colour = "red") +
  scale_x_continuous(breaks = c(1:15), labels = c("PRT", 
                                                  "GBR",
                                                  "FRA",
                                                  "ESP",
                                                  "DEU",
                                                  "IRL",
                                                  "ITA",
                                                  "BEL",
                                                  "NLD",
                                                  "CHE",
                                                  "USA",
                                                  "BRA",
                                                  "CN",
                                                  "AUT",
                                                  "SWE")) +
  ggplot2::annotate("text", 
           label = "80%-6",
           x = 5.5, y = 0.83,
           size = 3, 
           color = "navyblue") +
  labs(title = "Curva Pareto Facturación")

La gráfica nos muestra la contribución, a nivel de país, que tienen los clientes. Ahora, sin considerar a Portugal (por ser el país en donde se encuentran los hoteles), son solo 5 países los que contribuyen a generar casi el 80% de la facturación total: Gran Bretaña, Francia, España, Alemania e Irlanda. Sabiendo esto podemos recomentar focalizar promociones y/o campañas para los turistas de esas nacionalidades.

 

Conclusiones

Del análisis anterior, podemos señalar las siguentes conclusiones:

  • Como toda empresa que pertenece al giro del turismo sus actividades presentan temporadas de alta y baja demanda, es decir, se ven afectadas por la estacionalidad propia de su sector. En el caso de los hoteles, se detectó estacionalidad (considerando solo el año 2016 por la falta de más información histórica).
  • El hotel que más facturación tiene es el de ciudad, por lo que, si el objetivo es incrementar la facturación total de esta cadena de hoteles, se deben de enfocar los recursos en mejorar la facturación del hotel resort a través de campañas para lograr que el promedio de noches por reserva aumente así como analizar la posibilidad de incrementar los precios promedio sobre todo en temporada de alta demanda.
  • La mayor parte de la facturación proviene del segmento Online en ambos tipos de hotel, en cambio, el segmento Corporate es el que menor contribuye.
  • El mercado externo (sin considerar a Portugal), son diferentes, ya que mientras en el hotel de ciudad los franceses y alemanes son los clientes que más hacen reservas, en el hotel resort son los británicos y españoles los que más se hospedan.
  • Se presenta estacionalidad en el precio de la reserva, pues es en el periodo comprendido entre las semanas 29 a 35 que los precios son más altos y menor en el resto del año.

 

Insights

  • La temporada de alta demanda se presenta en los meses de mayo y octubre y, por el contrario, la temporada de menor demanda es en el periodo de diciembre a febrero.
  • El hotel resort tiene un poco más de demanda hasta el primer trimestre del año mientras que en el hotel de ciudad la demanda es mayor en los meses de junio septiembre y octubre.
  • Se podrían incrementar los precios en el hotel resort en temporada alta (inicio de año), ya que éstos se mantienen, en general, constantes. Se tendría que buscar un precio óptimo para ese periodo de tiempo.
  • La mayor parte de la facturación proviene del segmento parejas sin hijos: en el hotel resort equivale al 70% y en el hotel de ciudad al 65%.
  • Existe margen de maniobra para optimizar la política de precios, ya que las curvas de demanda tienen forma bimodal (dos picos durante el periodo de análisis) y, en cambio, la curva de precios solo presenta un pico. Lo anteior, recordando que la lógica de negocio indicaría que en época de alta demanda se pueden aumentar los precios buscando maximizar ganancias.
  • Es en el hotel resort donde el periodo de alta demanda no se corresponde con precios promedio altos, por lo tanto, es en este hotel donde se tendría que arrancar un piloto de optimización de precios.
  • A nivel de país y sin contar el mercado local (Portugal), son cinco países los que contribuyen a generar casi el 80% de la facturación total: Gran Bretaña, Francia, España, Alemania e Irlanda.
LS0tDQp0aXRsZTogIkdlbmVyYW5kbyBJbnNpZ2h0czogQW7DoWxpc2lzIGRlIGxhIERlbWFuZGEgZGUgUmVzZXJ2YWNpb25lcyBlbiBIb3RlbGVzIg0Kc3VidGl0bGU6ICJQcm95ZWN0byBkZSBEaXNjb3ZlcnkiDQphdXRob3I6ICJBbGVqYW5kcm8gUm9qYXMgTW9yZW5vIg0KZGF0ZTogIjgvMi8yMDIxIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0aGVtZTogc3BhY2VsYWINCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCmVkaXRvcl9vcHRpb25zOg0KICBjaHVua19vdXRwdXRfdHlwZTogY29uc29sZQ0KLS0tDQoNCiZuYnNwOw0KDQojIDxzcGFuIHN0eWxlPSJjb2xvcjpyZ2IoMCwgMCwgMjA1KSI+UHJlc2VudGFjacOzbjwvc3Bhbj4NCg0KPGRpdiBjbGFzcz10ZXh0LWp1c3RpZnk+DQpFbiBsYSBwcmVzZW50ZSBwdWJsaWNhY2nDs24gc2UgZXhwb25kcsOhIHVuIHByb3llY3RvIGRlICoqRGlzY292ZXJ5KiouIEVzdGUgdGlwbyBkZSBwcm95ZWN0b3MgdmFuIG3DoXMgZW5mb2NhZG9zIGFsIGFuw6FsaXNpcyBkZXNjcmlwdGl2byB5IGV4cGxvcmF0b3JpbywgZXMgZGVjaXIsIGEgYW5hbGl6YXIgbGEgZGF0YSBwYXJhIGVuY29udHJhciB0ZW5kZW5jaWFzLCBwYXRyb25lcyB5IGFub21hbMOtYXMgKHNpIGVzIHF1ZSBsYXMgaGF5KSBxdWUgYXl1ZGVuIGEgZ2VuZXJhciBpbnNpZ2h0cyBhbCBuZWdvY2lvIHkgYXPDrSBwb2RlciBjb250cmlidWlyIGEgcXVlIHNlIHRvbWVuIG1lam9yZXMgZGVjaXNpb25lcy4gQWhvcmEsIGxvIGFudGVyaW9yLCBubyBzaWduaWZpY2EgcXVlIG5vIHNlIHB1ZWRhIGNvbXBsZW1lbnRhciBjb24gdW4gYW7DoWxpc2lzIG3DoXMgYXZhbnphZG8gY29tbywgcG9yIGVqZW1wbG8sIHVuIG1vZGVsbyBkZSBtYWNoaW5lIGxlYXJuaW5nLCBpbmNsdXNvLCBkZXNkZSBtaSBwdW50byBkZSB2aXN0YSwgcHJldmlvIGEgbGEgY3JlYWNpw7NuIGRlIHVuIG1vZGVsbyBhbmFsw610aWNvIGF2YW56YWRvIGVzIGFsdGFtZW50ZSByZWNvbWVuZGFibGUsIHByaW1lcm8sIGhhY2VyIHVuIGFuw6FsaXNpcyBkZSBkaXNjb3ZlcnksIHlhIHF1ZSBlc3RvIHBlcm1pdGlyw6EgdGVuZXIgdW4gYWNlcmNhbWllbnRvIHkgZW50ZW5kaW1pZW50byBhbCBnaXJvIGRlbCBuZWdvY2lvLiBFbiByZXN1bWVuLCBlc3RlIHByb3llY3RvIHNlcsOhIHVuIHRyYWJham8gZGUgKipkYXRhIG1pbmluZyoqLg0KPC9kaXY+DQoNCiMgPHNwYW4gc3R5bGU9ImNvbG9yOnJnYigwLCAwLCAyMDUpIj5Db250ZXh0bzwvc3Bhbj4NCg0KPGRpdiBjbGFzcz10ZXh0LWp1c3RpZnk+DQpFbCBhbsOhbGlzaXMgc2UgaGFyw6EgYSBsYSBkZW1hbmRhIGRlIHJlc2VydmFjaW9uZXMgZGUgZG9zIGhvdGVsZXMgdWJpY2Fkb3MgZW4gTGlzYm9hLCBQb3J0dWdhbC4gVW4gKipob3RlbCByZXNvcnQqKiB5IGVsIG90cm8gdW4gKipob3RlbCBlbiBjaXVkYWQqKi4gRGljaGEgaW5mb3JtYWNpw7NuIGFiYXJjYSBlbCBwZXJpb2RvIGNvbXByZW5kaWRvIGVudHJlIGVsIDEgZGUganVsaW8gZGUgMjAxNSBhbCAzMSBkZSBhZ29zdG8gZGUgMjAxNy4gUG9yIHJhem9uZXMgZGUgc2VndXJpZGFkIHkgZGUgY29uZmlkZW5jaWFsaWRhZCwgc2UgaGFuIG9taXRpZG8gbG9zIGRhdG9zIGRlIGlkZW50aWZpY2FjacOzbiB0YW50byBkZSBsb3MgaG90ZWxlcyBjb21vIGRlIGxvcyBjbGllbnRlcy4NCg0KU3Vwb25nYW1vcywgcGFyYSBmaW5lcyBkaWTDoWN0aWNvcywgcXVlIHNlIG5vcyBoYSBlbmNhcmdhZG8gaGFjZXIgdW4gYW7DoWxpc2lzIGNvbiBsYSBpbmZvcm1hY2nDs24gYW50ZXMgbWVuY2lvbmFkYSBwYXJhIGVuY29udHJhciBpbnNpZ2h0cyBxdWUgYXl1ZGVuIGEgbWVqb3JhciBsYSB0b21hIGRlIGRlY2lzaW9uZXMgYXPDrSBjb21vIGEgbGEgaWRlbnRpZmljYWNpw7NuIGRlIGRpZmVyZW5jaWFzIG8gc2ltaWxpdHVkZXMgKHNpIGxhcyBoYXkpIGVudHJlIGFtYm9zIGhvdGVsZXMgY29uIGVsIHByb3DDs3NpdG8gZGUgZWxhYm9yYXIgZXN0cmF0ZWdpYXMgZGUgbWFya2V0aW5nIGRpZmVyZW5jaWFkYXMuIFRvZG8gbG8gYW50ZWlvciwgcGFyYSBsb2dyYXIgaW5jcmVtZW50YXIgbGEgcmVudGFiaWxpZGFkIGRlbCBuZWdvY2lvLg0KPC9kaXY+DQoNClBvciB0YW50bywgbG9zIG9iamV0aXZvcyBkZWwgcHJveWVjdG8gc2Vyw6FuOg0KDQoxLiBJZGVudGlmaWNhciB5IGVudGVuZGVyIGVsIHBhdHLDs24gZGUgY29uc3VtbyBkZSBsb3MgY2xpZW50ZXMuDQoyLiBEZXRlcm1pbmFyIGxhcyBwb3NpYmxlcyBkaWZlcmVuY2lhcyBlbnRyZSBsYXMgZGVtYW5kYXMgZW4gYW1ib3MgaG90ZWxlcy4NCjMuIExvY2FsaXphciBpbnNpZ2h0cyBkZSBpbnRlcsOpcyBxdWUgcHVlZGFuIGF5dWRhciBhIGhhY2VyIGNyZWNlciBlbCBuZWdvY2lvLg0KDQojIDxzcGFuIHN0eWxlPSJjb2xvcjpyZ2IoMCwgMCwgMjA1KSI+U2V0IHVwPC9zcGFuPg0KDQpJbmljaWFtb3MgY29uZmlndXJhbmRvIGxhcyBvcGNpb25lcyBnZW5lcmFsZXMgcXVlIHZhbW9zIGEgcmVxdWVyaXIgcGFyYSBlbCBkZXNhcnJvbGxvIGRlIGVzdGUgcHJveWVjdG8uLg0KDQpgYGB7ciBzZXR1cCwgbWVzc2FnZT1GQUxTRSwgY29tbWVudD0iIiwgd2FybmluZz1GQUxTRSwgcmVzdWx0cz0naGlkZSd9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgZmlnLmFsaWduID0gImNlbnRlciIsDQogICAgICAgICAgICAgICAgICAgICAgb3V0LndpZHRoID0gIjkwJSIsDQogICAgICAgICAgICAgICAgICAgICAgb3V0LmhlaWdodCA9ICI3MCUiKQ0KDQpwYXF1ZXRlcyA8LSBjKCd0aWR5dmVyc2UnLCAgICAjIG1hbmlwdWxhY2nDs24gZGF0b3MNCiAgICAgICAgICAgICAgJ2x1YnJpZGF0ZScsICAgICMgbWFuZWphciBmZWNoYXMNCiAgICAgICAgICAgICAgJ2tuaXRyJywgICAgICAgICMgbWFuZWpvIGRlIHRhYmxhcw0KICAgICAgICAgICAgICAna2FibGVFeHRyYScsICAgIyBtYW5lam8gZGUgdGFibGFzDQogICAgICAgICAgICAgICdwbG90bHknLCAgICAgICAjIGdyw6FmaWNvcyBpbnRlcmFjdGl2b3MNCiAgICAgICAgICAgICAgJ0RUJywgICAgICAgICAgICMgdmlzdWFsaXpjacOzbiBkZSB0YWJsYXMNCiAgICAgICAgICAgICAgJ2dyaWRFeHRyYScsICAgICMgbGF5b3V0IGRlIGdyYWZpY29zIGdncGxvdA0KICAgICAgICAgICAgICAnbm9ydGVzdCcsICAgICAgIyBwcnVlYmFzIGVzdGFkw61zdGljYXMNCiAgICAgICAgICAgICAgInN1bW1hcnl0b29scyIsICMgZXN0YWTDrXN0aWNhcyB1bml2YXJpYW50ZXMNCiAgICAgICAgICAgICAgInNmbyIpICAgICAgICAgICMgZ3JhZmljYSBzYW5rZXkNCg0KaW5zdGFsYWRvcyA8LSBwYXF1ZXRlcyAlaW4lIGluc3RhbGxlZC5wYWNrYWdlcygpDQoNCmlmKHN1bShpbnN0YWxhZG9zID09IEZBTFNFKSA+IDApIHsNCiAgaW5zdGFsbC5wYWNrYWdlcyhwYXF1ZXRlc1shaW5zdGFsYWRvc10pDQp9DQoNCmxhcHBseShwYXF1ZXRlcywgcmVxdWlyZSwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKQ0KDQpgYGANCg0KJm5ic3A7DQoNCiMgPHNwYW4gc3R5bGU9ImNvbG9yOnJnYigwLCAwLCAyMDUpIj5EYXRvczwvc3Bhbj4NCg0KTGEgaW5mb3JtYWNpw7NuIHBhcmEgZXN0ZSBhbsOhbGlzaXMgZXMgcMO6YmxpY2EgeSBsb3MgZGF0b3MgYXPDrSBjb21vIHN1IGRlc2NyaXBjacOzbiBzZSBlbmN1ZW50cmFuIGVuIGxhIHDDoWdpbmEgZGUgW1NjaWVuY2VEaXJlY3RdKGh0dHBzOi8vd3d3LnNjaWVuY2VkaXJlY3QuY29tL3NjaWVuY2UvYXJ0aWNsZS9waWkvUzIzNTIzNDA5MTgzMTUxOTEpLiBUYW1iacOpbiwgc2UgZW5jdWVudHJhbiBlbiBtaSByZXBvc2l0b3JpbyBkZSBbZ2l0aHViXShodHRwczovL2dpdGh1Yi5jb20vYXJvamFzbW9yL0hvdGVsZXMpLg0KDQpBbWJvcyBob3RlbGVzIGNvbXBhcnRlbiBsYSBtaXNtYSBlc3RydWN0dXJhIGRlIGRhdG9zIGNvbnRlbmlkYSBlbiAzMiB2YXJpYWJsZXMsIGVsIGhvdGVsIHJlc29ydCB0aWVuZSA0MCwwNjAgb2JzZXJ2YWNpb25lcyB5IGVsIGhvdGVsIGRlIGNpdWRhZCA3OSwzMzAgb2JzZXJ2YWNpb25lcy4gQ2FkYSByZWdpc3RybyByZXByZXNlbnRhIHVuYSByZXNlcnZhY2nDs24uDQoNCmBgYHtyIHZhcmlhYmxlc30NCmRmIDwtIHJlYWQuY3N2KCJob3RlbGVzLmNzdiIpDQoNCnRhYmxlKGRmJGhvdGVsKSAlPiUgDQogIGtibChhbGlnbiA9ICJjIiwgZGlnaXRzID0gMiAsZm9ybWF0LmFyZ3MgPSBsaXN0KGJpZy5tYXJrID0gIiwiKSkgJT4lDQogIGthYmxlX3BhcGVyKCJob3ZlciIsIGZ1bGxfd2lkdGggPSBGKQ0KYGBgDQoNCkxhIGRlc2NyaXBjacOzbiBkZSBsYXMgdmFyaWFibGVzIGVzIGxhIHNpZ3VpZW50ZToNCg0KYGBge3J9DQp0MSA8LSByZWFkLmNzdigidmFyaWFibGVzLmNzdiIpDQoNCkRUOjpkYXRhdGFibGUodDEsDQogICAgICAgICAgICAgICAgICAgIGV4dGVuc2lvbnMgPSAnRml4ZWRDb2x1bW5zJywNCiAgICAgICAgICAgICAgICAgICAgcm93bmFtZXMgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgZmlsdGVyID0gJ3RvcCcsDQogICAgICAgICAgICAgICAgICAgIG9wdGlvbnMgPSBsaXN0KA0KICAgICAgICAgICAgICAgICAgICAgIHBhZ2VMZW5ndGggPSA1LA0KICAgICAgICAgICAgICAgICAgICAgIGF1dG9XaWR0aCA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgY29sdW1uRGVmcyA9IA0KICAgICAgICAgICAgICAgICAgICAgICAgbGlzdChsaXN0KHdpZHRoID0gJzMwMHB4JykpLA0KICAgICAgICAgICAgICAgICAgICAgIHNjcm9sbFggPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgIGVzY2FwZSA9IFQpDQogICAgICAgICAgICAgICkNCmBgYA0KDQombmJzcDsNCg0KIyA8c3BhbiBzdHlsZT0iY29sb3I6cmdiKDAsIDAsIDIwNSkiPkFuw6FsaXNpcyBFeHBsb3JhdG9yaW88L3NwYW4+DQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjpyZ2IoMCwgMCwgMjA1KSI+VGlwb3MgZGUgdmFyaWFibGVzPC9zcGFuPg0KDQpIZWNoZW1vcyB1biBwcmltZXIgdmlzdGF6byBhIGxhIGVzdHJ1Y3R1cmEgZGUgbGFzIHZhcmlhYmxlcyBwYXJhIGlkZW50aWZpY2FyIHNpIHRpZW5lbiBsb3MgdGlwb3MgY29ycmVjdG9zIG8gZGUgbG8gY29udHJhcmlvIGNhbWJpYXJsb3MuDQoNCmBgYHtyIEVEQTF9DQpnbGltcHNlKGRmKQ0KDQpgYGANCg0KJm5ic3A7DQoNCjxkaXYgY2xhc3M9dGV4dC1qdXN0aWZ5Pg0KQ29uIGVzdGUgcHJpbWVyIHJlc3VtZW4gIHZlbW9zIHF1ZSBoYXkgdmFyaWFibGVzIGEgbGFzIHF1ZSB0ZW5lbW9zIHF1ZSBjYW1iaWFybGVzIGVsIHRpcG8gY29tbywgcG9yIGVqZW1wbG8sIGxhIHZhcmlhYmxlIGhvdGVsLCBxdWUgZXN0w6EgY29tbyBjaGFyYWN0ZXIgeSBlcyByZWNvbWVuZGFibGUgcGFzYXJsYSBhIHRpcG8gZmFjdG9yLCBzb2JyZSB0b2RvIHBhcmEgcXVlIHNlIG5vcyBmYWNpbGl0ZSBsYSBjcmVhY2nDs24gZGUgZ3LDoWZpY29zLCBhZGVtw6FzLCBoYXkgYWxnb3JpdG1vcyBxdWUgbmVjZXNpdGFuIHF1ZSBsYXMgdmFyaWFibGVzIGNhdGVnw7NyaWNhcyBlc3TDqW4gY29tbyBmYWN0b3JlcyB5IGF1bnF1ZSBlbiBlc3RlIHRyYWJham8gbm8gc2UgdXRpbGl6YXLDoSBuaW5nw7puIGFsZ29yaXRtbyBlcyBidWVubyB0ZW5lcmxvIHByZXNlbnRlIChhbCBtZW5vcyBzaSBzZSBlc3TDoSB0cmFiYWphbmRvIGNvbiBSLCBwdWVzIGVuIFB5dGhvbiBubyBzZSB0aWVuZSBlc2UgZGV0YWxsZSB5YSBxdWUgbm8gZXhpc3RlIGVsIHRpcG8gZmFjdG9yKS4NCjwvZGl2Pg0KDQpUYW1iacOpbiwgdmVtb3MgYSBwcmltZXJhIHZpc3RhIHF1ZSBoYXkgdmFsb3JlcyBOVUxMIGVuIGxhcyB2YXJpYWJsZXMgYWdlbnQgeSBjb21wYW55Lg0KDQombmJzcDsNCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnJnYigwLCAwLCAyMDUpIj5WYWxvcmVzIE5BIHkgTlVMTDwvc3Bhbj4NCg0KPGRpdiBjbGFzcz10ZXh0LWp1c3RpZnk+DQpBaG9yYSwgYW50ZXMgZGUgaGFjZXIgYWxndW5hIG1vZGlmaWNhY2nDs24gYSBsb3MgdGlwb3MgZXMgYWx0YW1lbnRlIHJlY29tZW5kYWJsZSBjb25vY2VyIGxhIGNhcmRpbmFsaWRhZCBkZSBsYXMgdmFyaWFibGVzIGNhdGVnw7NyaWNhcyBjb24gZWwgb2JqZXRpdm8gZGUgaWRlbnRpZmljYXIgZGVzZGUgZWwgaW5pY2lvIGRlbCBwcm95ZWN0byBsb3MgcG9zaWJsZXMgZGF0b3MgcmFyb3MgbyBleHRyYcOxb3MgY29tbyBlc3BhY2lvcyBvIGNhcmFjdGVyZXMgZXNwZWNpYWxlcy4gUGFyYSBlc3RvIG5vcyBhcG95YXJlbW9zIGRlIHRhYmxhcyBkZSBmcmVjdWVuY2lhczoNCjwvZGl2Pg0KDQpgYGB7ciBFREEyfQ0KZGYyIDwtIGRmICU+JSBzZWxlY3RfaWYoaXMuY2hhcmFjdGVyKQ0KDQogIGZvcihpIGluIDE6bGVuZ3RoKGRmMikpew0KICB0YWJsZShkZjJbaV0pDQogIHByaW50KHRhYmxlKGRmMltpXSkpDQogIH0NCg0KYGBgDQoNCkVzIG11Y2hhIGluZm9ybWFjacOzbiBlbiBlc3RhIHNhbGlkYSwgc2luIGVtYmFyZ28sIHBvZGVtb3MgaWRlbnRpZmljYXI6DQoNCiogdW5hIHZhcmlhYmxlIGNvbiA0ODggdmFsb3JlcyBOVUxMDQoqIHVuYSBzZWd1bmRhIHZhcmlhYmxlIGNvbiAxNiwzNDAgdmFsb3JlcyBOVUxMDQoqIHVuYSB0ZXJjZXIgdmFyaWFibGUgdGllbmUgMTEyLDU5MyB2YWxvcmVzIE5VTEwNCg0KVmVhbW9zIG90cm8gcmVzdW1lbiBkaWZlcmVudGUgcXVlIG5vcyBwZXJtaXRpcsOhIGlkZW50aWZpY2FyIGxhcyB2YXJpYWJsZXMgY29uIHZhbG9yZXMgTkE6DQoNCmBgYHtyIEVEQTN9DQptYXBfZGJsKGRmLCAuZiA9IGZ1bmN0aW9uKHgpe3N1bShpcy5uYSh4KSl9KQ0KYGBgDQoNCkVuIGVzdGEgb3RyYSB2aXN0YSBzZSBwdWVkZSB2ZXIgcXVlIGhheSA0IHZhbG9yZXMgcGVyZGlkb3Mgc29sbyBlbiBsYSB2YXJpYWJsZSBjaGlsZHJlbi4NCg0KSWRlbnRpZmlxdWVtb3MgY3XDoWxlcyBzb24gbGFzIHZhcmlhYmxlcyBlbiBkb25kZSBoYXkgdmFsb3JlcyBOVUxMOg0KDQpgYGB7ciBFREE0fQ0KbnVsbHMgPC0gZnVuY3Rpb24odmFyaWFibGUpIHsNCiAgc3VtKGlmX2Vsc2UodmFyaWFibGUgPT0gJ05VTEwnLCAxLCAwKSkNCn0NCg0KZGYgJT4lIA0KICBtdXRhdGVfYWxsKGFzLmNoYXJhY3RlcikgJT4lIA0KICBsYXBwbHkoLiwgbnVsbHMpDQpgYGANCg0KSGVtb3MgaWRlbnRpZmljYWRvIHF1ZSBlbiBsYXMgdmFyaWFibGVzIGNvdW50cnksIGFnZW50IHkgY29tcGFueSBoYXkgdmFsb3JlcyBOVUxMLg0KDQpEZXNwdcOpcyBkZSBoYWJlciByZWFsaXphZG8gdW4gcHJpbWVyIGFuw6FsaXNpcyBleHBsb3JhdG9yaW8gbG8gcXVlIGRlYmVtb3MgaGFjZXIgZXM6DQoNCiogQ2FtYmlhciBhbGd1bm9zIHRpcG9zIGRlIGRhdG9zIGRlIGNoYXJhY3RlciBhIGZhY3Rvci4NCiogVHJhYmFqYXIgY29uIGxvcyBkYXRvcyBOQSBkZSBsYSB2YXJpYWJsZSBjaGlsZHJlbi4NCiogRGVjaWRpciBxdcOpIGhhY2VyIGNvbiBsYXMgb2JzZXJ2YWNpb25lcyBOVUxMIGRlIGxhcyB0cmVzIHZhcmlhYmxlcyBxdWUgaWRlbnRpZmljYW1vcy4NCg0KJm5ic3A7DQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjpyZ2IoMCwgMCwgMjA1KSI+T3V0bGllcnMgeSBDYXJkaW5hbGlkYWQ8L3NwYW4+DQoNCjxkaXYgY2xhc3M9dGV4dC1qdXN0aWZ5Pg0KRW4gZXN0YSBwYXJ0ZSBkZWwgYW7DoWxpc2lzIGV4cGxvcmF0b3JpbyBlbXBlemFyZW1vcyBwb3IgcmV2aXNhciBsYXMgdmFyaWFibGVzIGNhdGVnw7NyaWNhcyBvIGRlIHRpcG8gY2hhcmFjdGVyLiBBcXXDrSBlbCBvYmpldGl2byBlcyBjb25vY2VyIGPDs21vIGVzdMOhIGNvbmZvcm1hZGEgbGEgY2FyZGluYWxpZGFkIGVuIGNhZGEgdmFyaWFibGUsIHlhIHF1ZSBkZSBoYWJlciBwb2NhcyBvYnNlcnZhY2lvbmVzIGRlIHVuYSBjYXRlZ29yw61hIHNlcsOhIHByZWZlcmlibGUgbW9kaWZpY2FybGEgeSBjcmVhciBncnVwb3MuDQo8L2Rpdj4NCg0KRW1wZWNlbW9zIGNvbiBlbCBwcmltZXIgZ3J1cG8gZGUgdmFyaWFibGVzOg0KDQpgYGB7ciBncmFmaWNhMX0NCmRmMiAlPiUgDQogIHNlbGVjdCgxOjcpICU+JSANCiAgbXV0YXRlKGlkID0gMTpucm93KC4pKSAlPiUgDQogIHBpdm90X2xvbmdlcigtaWQsIG5hbWVzX3RvID0gJ3ZhcmlhYmxlJywgdmFsdWVzX3RvID0gJ3ZhbG9yJykgJT4lIA0KICBnZ3Bsb3QoYWVzKHZhbG9yKSkgKw0KICBnZW9tX2JhcigpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6Y29tbWEpICsgDQogIGZhY2V0X3dyYXAofnZhcmlhYmxlLCBzY2FsZXMgPSAnZnJlZScsIG5jb2wgPSAyKQ0KDQpgYGANCg0KJm5ic3A7DQoNCkFsIG9ic2VydmFyIGxhcyBncsOhZmljYXMgYW50ZXJpb3JlcyBwb2RlbW9zIGRldGVybWluYXIgbGFzIHNpZ3VpZW50ZXMgYWNjaW9uZXMgYSBzZWd1aXI6DQoNCiogUGFyYSBsYSB2YXJpYWJsZSAqKmRpc3RyaWJ1dGlvbl9jaGFubmVsKio6IGp1bnRhciBsYXMgZG9zIG8gdHJlcyBjYXRlZ29yw61hcyBtw6FzIGJhamFzIGVuIHVuYQ0KICBzb2xhLg0KKiBFbiBsYSB2YXJpYWJsZSAqKm1hcmtldF9zZWdtZW50Kio6IHJldmlzYXIgbGEgZnJlY3VlbmNpYSBkZSBsb3Mgc2VnbWVudG9zIHBhcmEgYWdydXBhciBlbg0KICB1bmEgc29sYSBjYXRlZ29yw61hIGxhcyBxdWUgdGllbmVuIG11eSBiYWphIGNhbnRpZGFkIGRlIG9ic2VydmFjaW9uZXMuDQoqIFZhcmlhYmxlICoqcmVzZXJldmVkX3Jvb21fdHlwZSoqOiBzZWd1aXIgbGEgbWlzbWEgZXN0cmF0w6lnaWEsIGp1bnRhciBlbiB1bmEgc29sYSBjYXRlZ29yw61hDQogIGxhcyBvYnNlcnZhY2lvbmVzIGNvbiBtZW5vciBmcmVjdWVuY2lhLg0KKiBWYXJpYWJsZSAqKmNvdW50cnkqKjogZGV0ZXJtaW5hciBjw7NtbyBhZ3J1cGFyIGxhcyBvYnNlcnZhY2lvbmVzIHF1ZSBwcmVzZW50YW4gbXVjaGEgZGlzcGVyc2nDs24uDQoqIFBhcmEgbGEgdmFyaWFibGUgKiptZWFsKio6IGlndWFsbWVudGUsIGhhY2VyIHVuIGdydXBvIGNvbiBsYXMgY2F0ZWdvcsOtYXMgbWVub3MgZnJlY3VlbnRlcy4NCg0KU2VndW5kbyBncnVwbyBkZSB2YXJpYWJsZXMgdGlwbyBjaGFyYWN0ZXI6DQoNCmBgYHtyIGdyYWZpY2EyfQ0KZGYyICU+JSANCiAgc2VsZWN0KDg6MTQpICU+JSANCiAgbXV0YXRlKGlkID0gMTpucm93KC4pKSAlPiUgDQogIHBpdm90X2xvbmdlcigtaWQsIG5hbWVzX3RvID0gJ3ZhcmlhYmxlJywgdmFsdWVzX3RvID0gJ3ZhbG9yJykgJT4lIA0KICBnZ3Bsb3QoYWVzKHZhbG9yKSkgKw0KICBnZW9tX2JhcigpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6Y29tbWEpICsgDQogIGZhY2V0X3dyYXAofnZhcmlhYmxlLCBzY2FsZXMgPSAnZnJlZScsIG5jb2wgPSAyKQ0KDQpgYGANCg0KJm5ic3A7DQoNCkVuIGVzdGEgc2VndW5kYSBwYXJ0ZSBkZSB2YXJpYWJsZXMgY2F0ZWfDs3JpY2FzLCBwb2RlbW9zIGNvbmNsdWlyIGxhcyBzaWd1aWVudGVzIGFjY2lvbmVzIGEgc2VndWlyOg0KDQoqIEVuIGxhIHZhcmlhYmxlICoqYWdlbnQqKjogcmV2aXNhciBsYSB2YXJpYWJsZSB5YSBxdWUgcGFyZWNlIHF1ZSBubyBoYXkgbXVjaGEgaW5mb3JtYWNpw7NuLg0KKiBWYXJpYWJsZSAqKmNvbXBhbnkqKjogZWxpbWluYXJsYSwgeWEgcXVlIGhheSBkZW1hc2lhZG9zIHZhbG9yZXMgTlVMTCAoMTEyLDU5MyBkZSAxMTksMzkwKS4NCiogUGFyYSBsYSB2YXJpYWJsZSAqKmRlcG9zaXRfdHlwZSoqOiBqdW50YXIgbGEgY2F0ZWdvcsOtYSBkZSBtZW5vciBmcmVjdWVuY2lhIGNvbiBsYSBjYXRlZ29yw61hDQogIE5vbiBSZWZ1bmQuDQoqICoqQXNzaWduZWRfcm9vbV90eXBlKio6IGRlamFyIGxhcyBwcmluY2lwYWxlcyBjYXRlZ29yw61hcyB5IGp1bnRhciBlbCByZXN0byBlbiBvdHJhLg0KKiAqKkN1c3RvbWVyX3R5cGUqKjoganVudGFyIHRvZGFzIGxhcyBjYXRlZ29yw61hcyBlbiB1bmEgc29sYSBleGNlcHRvIGxhIGRlIG1heW9yIGZyZWN1ZW5jaWEuDQoqICoqUmVzZXJ2YXRpb25fc3RhdXMqKjogZGVqYXIgc29sbyBkb3MgY2F0ZWdvcsOtYXMgcGFzYW5kbyBOby1TaG93IGEgQ2FuY2VsZWQuDQoNCiZuYnNwOw0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6cmdiKDAsIDAsIDIwNSkiPlZhcmlhYmxlcyBkaXNjcmV0YXM8L3NwYW4+DQoNCkFob3JhLCB2ZWFtb3MgbGFzIHZhcmlhYmxlcyBkZSB0aXBvIGRpc2NyZXRvLCBvIHNlYSwgbGFzIGRlIHRpcG8gZW50ZXJvOg0KDQpgYGB7ciBncmFmaWNhMywgbWVzc2FnZT1GQUxTRX0NCmRmMyA8LSBkZiAlPiUgc2VsZWN0X2lmKGlzLmludGVnZXIpDQoNCmRmMyAlPiUgDQogIHNlbGVjdCgxOjkpICU+JSANCiAgbXV0YXRlKGlkID0gMTpucm93KC4pKSAlPiUgDQogIHBpdm90X2xvbmdlcigtaWQsIG5hbWVzX3RvID0gJ3ZhcmlhYmxlJywgdmFsdWVzX3RvID0gJ3ZhbG9yJykgJT4lIA0KICBnZ3Bsb3QoYWVzKHZhbG9yKSkgKw0KICBnZW9tX2JhcigpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6Y29tbWEpICsgDQogIGZhY2V0X3dyYXAodmFycyh2YXJpYWJsZSksIHNjYWxlcyA9ICdmcmVlJyxuY29sID0gMikNCg0KZGYzICU+JSANCiAgc2VsZWN0KDEwOjE3KSAlPiUgDQogIG11dGF0ZShpZCA9IDE6bnJvdyguKSkgJT4lIA0KICBwaXZvdF9sb25nZXIoLWlkLCBuYW1lc190byA9ICd2YXJpYWJsZScsIHZhbHVlc190byA9ICd2YWxvcicpICU+JSANCiAgZ2dwbG90KGFlcyh2YWxvcikpICsNCiAgZ2VvbV9iYXIoKSArDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmNvbW1hKSArIA0KICBmYWNldF93cmFwKHZhcnModmFyaWFibGUpLCBzY2FsZXMgPSAnZnJlZScsbmNvbCA9IDIpDQpgYGANCg0KJm5ic3A7DQoNCkFsIGFuYWxpemFyIHZpc3VhbG1lbnRlIGVzYXMgdmFyaWFibGVzIHBvZGVtb3MgYWZpcm1hciBxdWUgdGFtYmnDqW4gdGVuZW1vcyBxdWUgaGFjZXIgYWxndW5hcyB0cmFuc2Zvcm1hY2lvbmVzOg0KDQoqIExhcyBzaWd1aWVudGVzIHZhcmlhYmxlcyBwYXNhcmxhcyBhIHRpcG8gYmluYXJpYXMsIHlhIHF1ZSBwb3Igc3UgYWx0byBkZXNiYWxhbmNlbyBjb252aWVuZQ0KICB0ZW5lcmxhcyBlbiBlc2UgdGlwbzogDQogIC0gKipiYWJpZXMqKg0KICAtICoqY2hpbGRyZW4qKg0KICAtICoqcHJldmlvdXNfY2FuY2VsYXRpb25zKioNCiAgLSAqKnRvdGFsX3NwZWNpYWxfcmVxdWVzdCoqDQogIC0gKipib29raW5nX2NoYW5nZXMqKg0KICAtICoqZGF5c19pbl93YWl0aW5nX2xpc3QqKg0KICAtICoqcHJldmlvdXNfYm9va2luZ3Nfbm90X2NhbmNlbGVkKioNCiAgLSAqKnJlcXVpcmVkX2Nhcl9wYXJraW5nX3NwYWNlcyoqDQoqIFJldmlzYXIgbGEgdmFyaWFibGUgKiphZHVsdHMqKjogY3JlYXIgc29sbyAzIGNhdGVnb3LDrWFzICgxLCAyIHkgbcOhcyBkZSAyKS4NCiogRW4gbGEgdmFyaWFibGUgKipzdGF5c19pbl93ZWVrX25pZ2h0cyoqOiBqdW50YXIgbGFzIG9ic2VydmFjaW9uZXMgcXVlIHNlYW4gbcOhcyBkZSA0Lg0KKiBFbiAqKnN0YXlzX2luX3dlZWtlbmRfbmlnaHRzKio6IHBvciBjb252ZW5pZW5jaWEsIGhhY2VyIGRvcyBjYXRlZ29yw61hcywgMSB5IG3DoXMgZGUgMS4NCg0KJm5ic3A7DQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjpyZ2IoMCwgMCwgMjA1KSI+VmFyaWFibGVzIGNvbnRpbnVhczwvc3Bhbj4NCg0KRmluYWxtZW50ZSwgcmV2aXNhbW9zIGxhIMO6bmljYSB2YXJpYWJsZSBjb250aW51YSBxdWUgdGVuZW1vcywgKiphZHIqKjoNCg0KYGBge3IgZ3JhZmljYTR9DQpkZiAlPiUgDQogIGdncGxvdChhZXMoeCA9IGZhY3RvcigxKSwgeSA9IGFkcikpICsNCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuY29sb3VyID0gInJlZCIpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6Y29tbWEpICsgDQogIGxhYnMoDQogICAgdGl0bGUgPSAiYWRyIg0KICApDQoNCmBgYA0KDQpFbiBlbCBib3hwbG90IHNlIGFwcmVjaWEgY2xhcmFtZW50ZSBxdWUgaGF5IHVuIHZhbG9yIGV4dHJlbW8sIHNpbiBlbWJhcmdvLCBubyBzYWJlbW9zIHN1IHZhbG9yLiBFbiBlc3RvcyBjYXNvcyBlcyByZWNvbWVuZGFibGUgaGFjZXIgdW5hIHRhYmxhIHJlc3VtZW4gY29uIGxhcyBwcmluY2lwYWxlcyBtZWRpZGFzIGVzdGFkw61zdGljYXM6DQoNCmBgYHtyfQ0KZGVzY3IoZGYkYWRyLCANCiAgICAgIHN0eWxlID0gInJtYXJrZG93biIsIA0KICAgICAganVzdGlmeSA9ICJjIiwNCiAgICAgIGhlYWRpbmdzID0gVCkgJT4lIA0KICBrYmwoYWxpZ24gPSAiYyIsIGRpZ2l0cyA9IDIgLGZvcm1hdC5hcmdzID0gbGlzdChiaWcubWFyayA9ICIsIikpICU+JQ0KICBrYWJsZV9wYXBlcigiaG92ZXIiLCBmdWxsX3dpZHRoID0gRikNCg0KYXJyYW5nZShkZixkZXNjKGFkcikpICU+JSANCiAgc2VsZWN0KGFkcikgJT4lDQogIGhlYWQoMTApICU+JQ0KICBrYmwoYWxpZ24gPSAiYyIsIGRpZ2l0cyA9IDIgLGZvcm1hdC5hcmdzID0gbGlzdChiaWcubWFyayA9ICIsIikpICU+JQ0KICBrYWJsZV9wYXBlcigiaG92ZXIiLCBmdWxsX3dpZHRoID0gRikNCmBgYA0KDQpDb24gYXBveW8gZGUgbGFzIHRhYmxhcyByZXN1bWVuIHNhYmVtb3MgcXVlIGVzIGRlIDUsNDAwLiBFcyB1bmEgc29sYSBvYnNlcnZhY2nDs24gbGEgcXVlIGVzdMOhIGFmZWN0YWRvIGEgdG9kYSBsYSB2YXJpYWJsZS4gQXF1w60gbG8gcmVjb21lbmRhYmxlIHNlcsOhOg0KDQoqIEVsaW1pbmFyIGxhIG9ic2VydmFjaW9uIGV4dHJlbWEgZGUgNSw0MDAuDQoNCiZuYnNwOw0KDQojIDxzcGFuIHN0eWxlPSJjb2xvcjpyZ2IoMCwgMCwgMjA1KSI+VHJhbnNmb3JtYWNpw7NuIGRlIGRhdG9zPC9zcGFuPg0KDQpBIGNvbnRpbnVhY2nDs24sIHJlYWxpemFyZW1vcyBsYXMgdHJhbnNmb3JtYWNpb25lcyBxdWUgcHJldmlhbWVudGUgaWRlbnRpZmljYW1vcyBlbiBsYSBldGFwYSBkZSBleHBsb3JhY2nDs24uDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjpyZ2IoMCwgMCwgMjA1KSI+Q2FtYmlhciBlbCB0aXBvIGRlIHZhcmlhYmxlczwvc3Bhbj4NCg0KYGBge3J9DQojIFNlIHByZXBhcmEgZWwgY2FtYmlvDQoNCmFfZmFjdG9yIDwtIGMoJ2hvdGVsJywnYXJyaXZhbF9kYXRlX3llYXInLCdhcnJpdmFsX2RhdGVfbW9udGgnLCdhcnJpdmFsX2RhdGVfd2Vla19udW1iZXInLA0KICAgICAgICAgICAgICAnYXJyaXZhbF9kYXRlX2RheV9vZl9tb250aCcsJ21lYWwnLCdjb3VudHJ5JywnbWFya2V0X3NlZ21lbnQnLA0KICAgICAgICAgICAgICAnZGlzdHJpYnV0aW9uX2NoYW5uZWwnLCAncmVzZXJ2ZWRfcm9vbV90eXBlJywnYXNzaWduZWRfcm9vbV90eXBlJywNCiAgICAgICAgICAgICAgJ2RlcG9zaXRfdHlwZScsJ2FnZW50JywnY29tcGFueScsJ2N1c3RvbWVyX3R5cGUnLCdyZXNlcnZhdGlvbl9zdGF0dXMnKQ0KDQphX2xvZ2ljbyA8LSBjKCdpc19jYW5jZWxlZCcsJ2lzX3JlcGVhdGVkX2d1ZXN0JykNCg0KYV9kaXNjcmV0byA8LSBjKCdzdGF5c19pbl93ZWVrZW5kX25pZ2h0cycsJ3N0YXlzX2luX3dlZWtfbmlnaHRzJywnYWR1bHRzJywnY2hpbGRyZW4nLA0KICAgICAgICAgICAgICAgICdiYWJpZXMnLCdwcmV2aW91c19jYW5jZWxsYXRpb25zJywncHJldmlvdXNfYm9va2luZ3Nfbm90X2NhbmNlbGVkJywNCiAgICAgICAgICAgICAgICAnYm9va2luZ19jaGFuZ2VzJywnZGF5c19pbl93YWl0aW5nX2xpc3QnLCdyZXF1aXJlZF9jYXJfcGFya2luZ19zcGFjZXMnLA0KICAgICAgICAgICAgICAgICd0b3RhbF9vZl9zcGVjaWFsX3JlcXVlc3RzJykNCg0KIyBIYWNlbW9zIGVsIGNhbWJpbw0KDQpkZiA8LSBkZiAlPiUgDQogIG11dGF0ZV9hdChhbGxfb2YoYV9mYWN0b3IpLCBhcy5mYWN0b3IpICU+JSANCiAgbXV0YXRlX2F0KGFsbF9vZihhX2xvZ2ljbyksIGFzLmxvZ2ljYWwpICU+JSANCiAgbXV0YXRlX2F0KGFsbF9vZihhX2Rpc2NyZXRvKSwgYXMuaW50ZWdlcikNCg0KZ2xpbXBzZShkZikNCmBgYA0KDQpZYSB0ZW5lbW9zIHRvZGFzIGxhcyB2YXJpYWJsZXMgY29uIGVsIHRpcG8gY29ycmVjdG8uIExhIHZhcmlhYmxlIGFkciBkZSBvcmlnZW4gdmllbmUgYmllbiB5IGxhIHZhcmlhYmxlIHJlc2VydmF0aW9uX3N0YXR1c19kYXRlIHBvciBlbCBtb21lbnRvIGNvbnZpZW5lIHRlbmVybGEgY29tbyBjaGFyYWN0ZXIgKGVuIGVzZSB0aXBvIG5vIG9jdXBhIG11Y2hvIGVzcGFjaW8gZW4gbWVtb3JpYSkuDQoNCiZuYnNwOw0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6cmdiKDAsIDAsIDIwNSkiPlRyYXRhbWllbnRvIGRlIHZhbG9yZXMgTkEgeSBOVUxMPC9zcGFuPg0KDQo8ZGl2IGNsYXNzPXRleHQtanVzdGlmeT4NCllhIGhhYsOtYW1vcyBpZGVudGlmaWNhZG8gcXVlIGVuIGxhIHZhcmlhYmxlIGNoaWxkcmVuIGhheSA0IHZhbG9yZXMgTkEsIHBvciBsbyBxdWUsIHNlIHByb2NlZGVyw6EgYSBpbXB1dGFybG9zIHBvciBlbCB2YWxvciBtw6FzIGZyZWN1ZW50ZSAobGEgbW9kYSkuIFRhbWJpw6luLCBlbiBsYSB2YXJpYWJsZSBjb3VudHJ5IHNlIGRldGVjdGFyb24gNDg4IHZhbG9yZXMgTlVMTCwgcG9yIGxvIHF1ZSwgc2Ugc2VndWlyw6EgbGEgbWlzbWEgZXN0cmF0w6lnaWEgZGUgaW1wdXRhY2nDs24gZGViaWRvIGEgcXVlIHNvbiByZWxhdGl2YW1lbnRlIHBvY29zIGRhdG9zIGVuIHJlbGFjaW9uIGNvbiBlbCB0YW1hw7FvIGRlIGxhIGJhc2UgZGUgZGF0b3MuIEVuIGVzdGUgcGFzbywgYXByb3ZlY2hhcmVtb3MgcGFyYSBlbGltaW5hciBsYXMgdmFyaWFibGVzIGFnZW50IHkgY29tcGFueSwgeWEgcXVlIHRpZW5lbiBkZW1hc2lhZG9zIHZhbG9yZXMgTlVMTCB5IG5vIGNvbnZpZW5lIHJlYWxpemFyIGFsZ8O6biB0aXBvIGRlIGltcHV0YWNpw7NuOiANCjwvZGl2Pg0KDQpgYGB7cn0NCiMgc2UgaWRlbnRpZmljYSBlbCB2YWxvciBtw6FzIGZyZWN1ZW50ZQ0KDQpjb3VudChkZiwgY2hpbGRyZW4sIHNvcnQgPSBUKSAlPiUgIyBjaGlsZHJlbg0KICBrYmwoYWxpZ24gPSAiYyIsIGRpZ2l0cyA9IDIgLGZvcm1hdC5hcmdzID0gbGlzdChiaWcubWFyayA9ICIsIikpICU+JQ0KICBrYWJsZV9wYXBlcigiaG92ZXIiLCBmdWxsX3dpZHRoID0gRikNCg0KY291bnQoZGYsIGNvdW50cnksIHNvcnQgPSBUKSAlPiUgaGVhZCgxMCkgJT4lICMgY291bnRyeQ0KICBrYmwoYWxpZ24gPSAiYyIsIGRpZ2l0cyA9IDIgLGZvcm1hdC5hcmdzID0gbGlzdChiaWcubWFyayA9ICIsIikpICU+JQ0KICBrYWJsZV9wYXBlcigiaG92ZXIiLCBmdWxsX3dpZHRoID0gRikNCmBgYA0KDQpTZSBlbGltaW5hbiBsYXMgdmFyaWFibGVzIGFnZW50IHkgY29tcGFueS4gU2UgaW1wdXRhbiBsb3MgdmFsb3JlcyBOQSB5IE5VTEwuDQoNCmBgYHtyfQ0KZGYgPC0gZGYgJT4lIA0KICBzZWxlY3QoLWFnZW50LCAtY29tcGFueSkgJT4lICMgc2UgZWxpbWluYW4gZXNhcyB2YXJpYWJsZXMNCiAgbXV0YXRlKGNoaWxkcmVuID0gaWZlbHNlKGlzLm5hKGNoaWxkcmVuKSwgMCwgY2hpbGRyZW4pLCAjIHNlIHN1c3RpdHV5ZW4gTkEgcG9yIDANCiAgICAgICAgIGNvdW50cnkgPSBmY3RfcmVjb2RlKGNvdW50cnksICdQUlQnID0gJ05VTEwnKSAjIHNlIHN1c3RpdHV5ZW5uIE5VTEwgcG9yIFBSVA0KICApDQpgYGANCg0KJm5ic3A7DQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjpyZ2IoMCwgMCwgMjA1KSI+T3V0bGllciB5IGRpc2NyZXRpemFjacOzbjwvc3Bhbj4NCg0KQ29tbyBzb2xvIHNlIGlkZW50aWZpY8OzIHVuYSBvYnNlcnZhY2lvbiBleHJlbWEgKDUsNDAwKSwgc2UgcHJvY2VkZXLDoSBhIGVsaW1pbmFybGEgZGVsIGRhdGFzZXQ6DQoNCmBgYHtyfQ0KZGYgPC0gZGYgJT4lIA0KICBmaWx0ZXIoYWRyICE9IDU0MDApDQpgYGANCg0KTGFzIGFjY2lvbmVzIHBhcmEgcmVjb2RpZmljYXIgYWxndW5hcyB2YXJpYWJsZXMgc2Vyw6FuOg0KDQotIEVuIGNvdW50cnkgZGVqYXIgbG9zIDE1IHByaW1lcm9zIG1lcmNhZG9zIHkganVudGFyIGVsIHJlc3RvIGVuIGxhIGNhdGVnb3LDrWEgT1RST1MNCi0gRW4gbWFya2V0X3NlZ21lbnQgdW5pciBVbmRlZmluZWQsIEF2aWF0aW9uLCBDb21wbGVtZW50YXJ5IGVuIGVsIGdydXBvIENvcnBvcmF0ZQ0KLSBFbiBhc3NpZ25lZF9yb29tX3R5cGUgeSByZXNlcnZlZF9yb29tX3R5cGUgZGVqYXIgQSB5IEQganVudGFyIGVsIHJlc3RvDQotIEVuIGN1c3RvbWVyX3R5cGUgYWdydXBhciBwYXJhIGRlamFyIHNvbG8gbGFzIGNhdGVnb3LDrWFzIFRyYW5zaWVudCB5IE9UUk9TDQotIEVuIGRpc3RyaWJ1dGlvbl9jaGFubmVsIGp1bnRhciBDb3Jwb3JhdGUsIEdEUyB5IFVuZGVmaW5lZCBlbiBDb3Jwb3JhdGUNCi0gRW4gcmVzZXJ2YXRpb25fc3RhdHVzIGFncnVwYXIgbGFzIG9ic2VydmFjaW9uZXMgZGUgTm8tU2hvdyBjb24gQ2FuY2VsZWQNCi0gRW4gbWVhbCByZWNvZGlmaWNhciBlbnRyZSBCQiB5IE9UUk9TDQotIEVuIGRlc3Bvc2l0X3R5cGUganVudGFyIE5vbiBSZWZ1bmQgY29uIFJlZnVuZGFibGUgeSBsbGFtYXJsbyBEZXBvc2l0DQoNCmBgYHtyfQ0KZGYgPC0gZGYgJT4lDQogICNSZWNvZGlmaWNhcg0KICBtdXRhdGUoDQogICAgICAgICBjb3VudHJ5ID0gZmN0X2x1bXAoY291bnRyeSwgbiA9IDE1LCBvdGhlcl9sZXZlbCA9ICdPVFJPUycpLA0KICAgICAgICAgbWFya2V0X3NlZ21lbnQgPSBmY3RfY29sbGFwc2UobWFya2V0X3NlZ21lbnQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnQ29ycG9yYXRlJyA9IGMoJ0NvcnBvcmF0ZScsJ1VuZGVmaW5lZCcsJ0F2aWF0aW9uJywnQ29tcGxlbWVudGFyeScpKSwNCiAgICAgICAgIGRpc3RyaWJ1dGlvbl9jaGFubmVsID0gZmN0X2NvbGxhcHNlKGRpc3RyaWJ1dGlvbl9jaGFubmVsLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0NvcnBvcmF0ZScgPSBjKCdDb3Jwb3JhdGUnLCdVbmRlZmluZWQnLCdHRFMnKSksDQogICAgICAgICByZXNlcnZhdGlvbl9zdGF0dXMgPSBmY3RfY29sbGFwc2UocmVzZXJ2YXRpb25fc3RhdHVzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0NhbmNlbGVkJyA9IGMoJ0NhbmNlbGVkJywnTm8tU2hvdycpKSwNCiAgICAgICAgIGRlcG9zaXRfdHlwZSA9IGZjdF9jb2xsYXBzZShkZXBvc2l0X3R5cGUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnRGVwb3NpdCcgPSBjKCdOb24gUmVmdW5kJywnUmVmdW5kYWJsZScpKSwNCiAgICAgICAgIGFzc2lnbmVkX3Jvb21fdHlwZSA9IGZjdF9jb2xsYXBzZShhc3NpZ25lZF9yb29tX3R5cGUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0EnID0gJ0EnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdEJyA9ICdEJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdGhlcl9sZXZlbCA9ICdPVFJPUycpLA0KICAgICAgICAgcmVzZXJ2ZWRfcm9vbV90eXBlID0gZmN0X2NvbGxhcHNlKHJlc2VydmVkX3Jvb21fdHlwZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnQScgPSAnQScsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0QnID0gJ0QnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG90aGVyX2xldmVsID0gJ09UUk9TJyksDQogICAgICAgICBjdXN0b21lcl90eXBlID0gZmN0X2NvbGxhcHNlKGN1c3RvbWVyX3R5cGUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1RyYW5zaWVudCcgPSAnVHJhbnNpZW50JywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdGhlcl9sZXZlbCA9ICdPVFJPUycpLA0KICAgICAgICAgbWVhbCA9IGZjdF9jb2xsYXBzZShtZWFsLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdCQicgPSAnQkInLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG90aGVyX2xldmVsID0gJ09UUk9TJykpDQpgYGANCg0KQWhvcmEsIGxvIHNpZ3VpZW50ZSBlcyBkaXNjcmV0aXphciBsYXMgdHJlcyB2YXJpYWJsZXMgcXVlIHBvciBzdSBkaXN0cmlidWNpw7NuIHRhbiBkaXNwZXJzYSBlcyBtZWpvciBtb2RpZmljYXJsYXM6DQoNCi0gQ29udmVydGlyIGFkdWx0cyBhIDEsIDIgeSBtYXMgZGUgMg0KLSBFbiBzdGF5c19pbl93ZWVrX25pZ2h0cyBqdW50YXIgZW4gMSwgMiwgMywgNCB5IG1hcyBkZSA0DQotIEVuIHN0YXlzX2luX3dlZWtlbmRfbmlnaHRzIGp1bnRhciBlbiAxIHkgbcOhcyBkZSAxDQoNCmBgYHtyfQ0KZGYgPC0gZGYgJT4lDQogIG11dGF0ZSgNCiAgICAgICAgIGFkdWx0cyA9IGFzLmZhY3RvcihjYXNlX3doZW4oYWR1bHRzIDw9IDEgfiAnMDFfVW5vJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZHVsdHMgPT0gMiB+ICcwMl9Eb3MnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFkdWx0cyA+IDIgfiAnMDNfTWFzX2RlX0RvcycsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+ICc5OV9FUlJPUicpKSwNCiAgICAgICAgIHN0YXlzX2luX3dlZWtfbmlnaHRzX2Rpc2MgPSBhcy5mYWN0b3IoY2FzZV93aGVuKHN0YXlzX2luX3dlZWtfbmlnaHRzIDw9IDEgfiAnMDFfVW5vJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdGF5c19pbl93ZWVrX25pZ2h0cyA9PSAyIH4gJzAyX0RvcycsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RheXNfaW5fd2Vla19uaWdodHMgPT0gMyB+ICcwM19UcmVzJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdGF5c19pbl93ZWVrX25pZ2h0cyA9PSA0IH4gJzA0X0N1YXRybycsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RheXNfaW5fd2Vla19uaWdodHMgPiA0IH4gJzA1X01hc19kZV9DdWF0cm8nLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiAnOTlfRVJST1InKSksDQogICAgICAgICBzdGF5c19pbl93ZWVrZW5kX25pZ2h0c19kaXNjID0gYXMuZmFjdG9yKGNhc2Vfd2hlbihzdGF5c19pbl93ZWVrZW5kX25pZ2h0cyA8PSAxIH4gJzAxX1VubycsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RheXNfaW5fd2Vla2VuZF9uaWdodHMgPiAxIH4gJzAyX01hc19kZV9Vbm8nLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiAnOTlfRVJST1InKSksDQogICAgICAgIA0KICAgICAgICAgKQ0KYGBgDQoNClBvciDDumx0aW1vLCBjcmVhciB2YXJpYWJsZXMgYmluYXJpYXMgcGFyYSBsYXMgc2lndWllbnRlcyB2YXJpYWJsZXM6DQoNCi0gYmFiaWVzDQotIGJvb2tpbmdfY2hhbmdlcw0KLSBjaGlsZHJlbg0KLSBwcmV2aW91c19jYW5jZWxhdGlvbnMNCi0gcmVxdWlyZWRfY2FyX3Bhcmtpbmdfc3BhY2VzDQotIHRvdGFsX3NwZWNpYWxfcmVxdWVzdA0KLSBkYXlzX2luX3dhaXRpbmdfbGlzdA0KLSBwcmV2aW91c19ib29raW5nc19ub3RfY2FuY2VsZWQNCg0KYGBge3J9DQpkZiA8LSBkZiAlPiUNCiAgbXV0YXRlKA0KICAgICAgICAgYmFiaWVzID0gYXMubG9naWNhbChpZmVsc2UoYmFiaWVzID09IDAsIDAsIDEpKSwNCiAgICAgICAgIGJvb2tpbmdfY2hhbmdlcyA9IGFzLmxvZ2ljYWwoaWZlbHNlKGJvb2tpbmdfY2hhbmdlcyA9PSAwLCAwLCAxKSksDQogICAgICAgICBjaGlsZHJlbiA9IGFzLmxvZ2ljYWwoaWZlbHNlKGNoaWxkcmVuID09IDAsIDAsIDEpKSwNCiAgICAgICAgIHByZXZpb3VzX2NhbmNlbGxhdGlvbnMgPSBhcy5sb2dpY2FsKGlmZWxzZShwcmV2aW91c19jYW5jZWxsYXRpb25zID09IDAsIDAsIDEpKSwNCiAgICAgICAgIHJlcXVpcmVkX2Nhcl9wYXJraW5nX3NwYWNlcyA9IGFzLmxvZ2ljYWwoaWZlbHNlKHJlcXVpcmVkX2Nhcl9wYXJraW5nX3NwYWNlcyA9PSAwLCAwLCAxKSksDQogICAgICAgICB0b3RhbF9vZl9zcGVjaWFsX3JlcXVlc3RzID0gYXMubG9naWNhbChpZmVsc2UodG90YWxfb2Zfc3BlY2lhbF9yZXF1ZXN0cyA9PSAwLCAwLCAxKSksDQogICAgICAgICBkYXlzX2luX3dhaXRpbmdfbGlzdCA9IGFzLmxvZ2ljYWwoaWZlbHNlKGRheXNfaW5fd2FpdGluZ19saXN0ID09IDAsIDAsIDEpKSwNCiAgICAgICAgIHByZXZpb3VzX2Jvb2tpbmdzX25vdF9jYW5jZWxlZCA9IGFzLmxvZ2ljYWwoaWZlbHNlKHByZXZpb3VzX2Jvb2tpbmdzX25vdF9jYW5jZWxlZCA9PSAwLCAwLCAxKSkNCiAgICAgICAgICkNCmBgYA0KDQpVbmEgdmV6IHF1ZSBoZW1vcyByZWFsaXphZG8gdG9kYXMgbGFzIHRyYW5zZm9ybWFjaW9uZXMgcXVlIGlkZW50aWZpY2Ftb3MgZW4gbGEgcGFydGUgZGVsIGFuw6FsaXNpcyBleHBsb3JhdG9yaW8sIFZvbHZlbW9zIGEgaGFjZXIgbG9zIGdyw6FmaWNvcyBwYXJhIGNvbXByb2JhciBsb3MgcmVzdWx0YWRvcyBkZXNlYWRvcy4NCg0KVmFyaWFibGVzIGRlIHRpcG8gZmFjdG9yOg0KDQpgYGB7cn0NCiMgcHJpbWVyIGJsb3F1ZSBkZSB2YXJpYWJsZXMNCg0KZGYgJT4lIA0KICBzZWxlY3RfaWYoaXMuZmFjdG9yKSAlPiUNCiAgc2VsZWN0KDE6OSkgJT4lIA0KICBtdXRhdGUoaWQgPSAxOm5yb3coLikpICU+JSANCiAgcGl2b3RfbG9uZ2VyKC1pZCwgbmFtZXNfdG8gPSAndmFyaWFibGUnLCB2YWx1ZXNfdG8gPSAndmFsb3InKSAlPiUgDQogIGdncGxvdChhZXModmFsb3IpKSArDQogIGdlb21fYmFyKCkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpjb21tYSkgKyANCiAgZmFjZXRfd3JhcCh2YXJzKHZhcmlhYmxlKSwgc2NhbGVzID0gJ2ZyZWUnLCBuY29sID0gMykNCg0KIyBzZWd1bmRvIGJsb3F1ZSBkZSB2YXJpYWJsZXMNCg0KZGYgJT4lIA0KICBzZWxlY3RfaWYoaXMuZmFjdG9yKSAlPiUNCiAgc2VsZWN0KDEwOjE3KSAlPiUgDQogIG11dGF0ZShpZCA9IDE6bnJvdyguKSkgJT4lIA0KICBwaXZvdF9sb25nZXIoLWlkLCBuYW1lc190byA9ICd2YXJpYWJsZScsIHZhbHVlc190byA9ICd2YWxvcicpICU+JSANCiAgZ2dwbG90KGFlcyh2YWxvcikpICsNCiAgZ2VvbV9iYXIoKSArDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmNvbW1hKSArIA0KICBmYWNldF93cmFwKHZhcnModmFyaWFibGUpLCBzY2FsZXMgPSAnZnJlZScsIG5jb2wgPSAzKQ0KYGBgDQoNClZhcmlhYmxlcyBiaW5hcmlhcyBvIGzDs2dpY2FzOg0KDQpgYGB7cn0NCmRmICU+JSANCiAgc2VsZWN0X2lmKGlzLmxvZ2ljYWwpICU+JSANCiAgbXV0YXRlKGlkID0gMTpucm93KC4pKSAlPiUgDQogIHBpdm90X2xvbmdlcigtaWQsIG5hbWVzX3RvID0gJ3ZhcmlhYmxlJywgdmFsdWVzX3RvID0gJ3ZhbG9yJykgJT4lIA0KICBnZ3Bsb3QoYWVzKHZhbG9yKSkgKw0KICBnZW9tX2JhcigpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6Y29tbWEpICsgDQogIGZhY2V0X3dyYXAodmFycyh2YXJpYWJsZSksIHNjYWxlcyA9ICdmcmVlJyxuY29sID0gMikNCmBgYA0KDQpWYXJpYWJsZSBjb250aW51YToNCg0KYGBge3J9DQpkZiAlPiUgDQogIGdncGxvdChhZXMoeCA9IGZhY3RvcigxKSwgeSA9IGFkcikpICsNCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuY29sb3IgPSAicmVkIikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gImFkciINCiAgKQ0KDQoNCmRlc2NyKGRmJGFkciwgDQogICAgICBzdHlsZSA9ICJybWFya2Rvd24iLCANCiAgICAgIGp1c3RpZnkgPSAiY2VudGVyIiwNCiAgICAgIGhlYWRpbmdzID0gVCkgJT4lIA0KICBrYmwoYWxpZ24gPSAiYyIsIGRpZ2l0cyA9IDIgLGZvcm1hdC5hcmdzID0gbGlzdChiaWcubWFyayA9ICIsIikpICU+JQ0KICBrYWJsZV9wYXBlcigiaG92ZXIiLCBmdWxsX3dpZHRoID0gRikNCg0KYGBgDQoNCjxkaXYgY2xhc3M9dGV4dC1qdXN0aWZ5Pg0KRW4gbGFzIGdyw6FmaWNhcyBhY3R1YWxpemFkYXMsIHlhIHBvZGVtb3MgYXByZWNpYXIgbWVqb3IgbGFzIGRpc3RyaWJ1Y2lvbmVzIGRlIGxhcyB2YXJpYWJsZXMsIHB1ZXMgbm8gaGF5IGFzaW1ldHLDrWFzIGZ1ZXJ0ZXMgbmkgZGVzYmFsYW5jZXMgc2V2ZXJvcyBlbiBsYXMgdmFyaWFibGVzIGRlIHRpcG8gZmFjdG9yLiBMYXMgdmFyaWFibGVzIGJpbmFyaWFzIG8gbMOzZ2ljYXMgc2UgdmVuIGJpZW4geSBsYSDDum5pY2EgdmFyaWFibGUgY29udGludWEgYXVucXVlIHByZXNlbnRhIHZhbG9yZXMgYXTDrXBpY29zIHlhIG5vIGhheSBwcmVzZW5jaWEgZGUgdmFsb3JlcyBleHRyZW1vcyBlbm1hc2NhcmFkb3MuIA0KPC9kaXY+DQoNCiZuYnNwOw0KDQojIDxzcGFuIHN0eWxlPSJjb2xvcjpyZ2IoMCwgMCwgMjA1KSI+Q3JlYWNpw7NuIGRlIHZhcmlhYmxlcyBzaW50w6l0aWNhczwvc3Bhbj4NCg0KPGRpdiBjbGFzcz10ZXh0LWp1c3RpZnk+DQpVbmEgZGUgbGFzIHBhcnRlcyBtw6FzIGludGVyZXNhbnRlcyBlbiBjdWFscXVpZXIgcHJveWVjdG8gZGUgZGlzY292ZXJ5IGVzIGVsIGRlIGNyZWFyIHZhcmlhYmxlcyBudWV2YXMgYSBwYXJ0aXIgZGUgbGFzIHZhcmlhYmxlcyBvcmlnaW5hbGVzLCBsYXMgY3VhbGVzIHNvbiBjb23Dum5tZW50ZSBsbGFtYWRhcyB2YXJpYWJsZXMgc2ludMOpdGljYXMuIEVzdGFzIHZhcmlhYmxlcyBkZWJlbiBkZSBjcmVhcnNlIGNvbiB1biBzZW50aWRvIGRlIG5lZ29jaW8uIEEgY29udGludWFjacOzbiwgY3JlYXJlbW9zIGFsZ3VuYXMgdmFyaWFibGVzIHF1ZSBub3Mgc2Vydmlyw6FuIHBhcmEgbGEgZXRhcGEgZGUgZGlzY292ZXJ5Lg0KDQpFbiBsYSBiYXNlIGRlIGRhdG9zIG5vIHZpZW5lIHVuYSBmZWNoYSBkZSBsbGVnYWRhIGNvbW8gdGFsLCBvIHNlYSwgY29tcHVlc3RhIHBvciBlbCBkw61hLCBtZXMgeSBhw7FvLCBzaW4gZW1iYXJnbywgc2kgdGVuZW1vcyBjYWRhIGNvbXBvbmVudGUgcG9yIHNlcGFyYWRvLCBwb3IgbG8gcXVlLCB2YW1vcyBhIGNyZWFybGEuDQoNCkFkZW1hcywgdGVuZW1vcyBpbmZvcm1hY2nDs24gc29icmUgZWwgbsO6bWVybyBkZSBwZXJzb25hcyBhZHVsdGFzLCBuacOxb3MgeSBiZWLDqXMsIHBvciB0YW50bywgcG9kZW1vcyBjcmVhciB1bmEgdmFyaWFibGUgcXVlIG5vcyBpbmRpcXVlIGVsIHRpcG8gZGUgZmFtaWxpYSBxdWUgc2UgaGEgaG9zcGVkYWRvLiANCg0KUG9kZW1vcyBpbmNsdWlyIG90cmEgdmFyaWFibGUgcGFyYSBzYWJlciBzaSBsYSBwZXJzb25hIGVzIGV4dHJhbmplcmEgbyBuYWNpb25hbCwgZW50ZW5kaWVuZG8gcXVlIG5hY2lvbmFsIGNvcnJlc3BvbmRlcsOtYSBhIGxvcyBjbGllbnRlcyBkZSBuYWNpb25hbGlkYWQgcG9ydHVndWVzYS4NCg0KVW5hIHRlcmNlcmEgdmFyaWFibGUgcXVlIG5vcyBzZXLDrWEgZGUgdXRpbGlkYWQgZXMgZWwgY2FtYmlvIGRlIGhhYml0YWNpw7NuLCBxdWUgbGEgb2J0ZW5kcmVtb3MgZGUgbGEgZGlmZXJlbmNpYSBlbnRyZSBlbCB0aXBvIGRlIGN1YXJ0byByZXNlcnZhZG8geSBlbCB0aXBvIGRlIGN1YXJ0byBhc2lnbmFkby4gDQoNClRhbWJpZW4sIHB1ZWRlIHNlciBkZSB1dGlsaWRhZCBjb25vY2VyIGxhcyBub2NoZXMgdG90YWxlcyBxdWUgbGFzIHBlcnNvbmFzIHNlIHF1ZWRhcm9uIGVuIGFsZ8O6biBob3RlbCwgY29uZm9ybWFkYXMgcG9yIGxhIHN1bWEgZGUgbGFzIG5vY2hlcyBlbnRyZSBzZW1hbmEgbcOhcyBsYXMgbm9jaGVzIGVuIGZpbiBkZSBzZW1hbmEuDQoNCkZpbmFsbWVudGUsIHVuYSB2YXJpYWJsZSBxdWUgc29ycHJlbmRlIHF1ZSBubyB2ZW5nYSBlbiBsYSBiYXNlIGVzIGxhIGZhY3R1cmFjacOzbiB0b3RhbCwgcG9yIGxvIHF1ZSwgbGEgY3JlYXJlbW9zLg0KDQpFbiByZXN1bWVuLCBsbyBxdWUgaGFyZW1vcyBzZXLDoSBjcmVhciBsYXMgc2lndWllbnRlcyB2YXJpYWJsZXM6DQoNCi0gKipmZWNoYV9sbGVnYWRhKio6IGEgcGFydGlyIGRlIGFycml2YWxfZGF0ZV95ZWFyLCBhcnJpdmFsX2RhdGVfbW9udGggeSBhcnJpdmFsX2RhdGVfZGF5X29mX21vbnRoDQotICoqZmFtaWxpYSoqOiBhIHBhcnRpciBkZSBhZHVsdHMsIGNoaWxkcmVuIHkgYmFiaWVzDQotICoqZXh0cmFuamVybyoqOiAwIHNpIGVzIGRlIFBvcnR1Z2FsIHkgMSBzaSBubyBsbyBlcw0KLSAqKmNhbWJpb19oYWJpdCoqOiBzaSBlbCB2YWxvciBkZSByZXNlcnZlZF9yb29tX3R5cGUgZXMgZGlmZXJlbnRlIGFsIGRlIGFzc2lnbmVkX3Jvb21fdHlwZQ0KLSAqKm5vY2hlc190b3RhbGVzKio6IHN1bWEgZGUgc3RheXNfaW5fd2Vla2VuZF9uaWdodHMgbcOhcyBzdGF5c19pbl93ZWVrX25pZ2h0cw0KLSAqKmZhY3R1cmFjaW9uX3Jlc2VydmEqKjogbG8gcXVlIHNlIGhhIGZhY3R1cmFkbyBlbiBlc2EgcmVzZXJ2YWNpw7NuIGNvbW8gcmVzdWx0YWRvIGRlIG11bHRpcGxpY2FyIGxhIHZhcmlhYmxlIGNyZWFkYSBub2NoZXNfdG90YWxlcyBwb3IgYWRyDQo8L2Rpdj4NCg0KYGBge3J9DQpkZiA8LSBkZiAlPiUgDQogIG11dGF0ZShmZWNoYV9sbGVnYWRhID0gYXNfZGF0ZShwYXN0ZShhcnJpdmFsX2RhdGVfeWVhciwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFycml2YWxfZGF0ZV9tb250aCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFycml2YWxfZGF0ZV9kYXlfb2ZfbW9udGgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VwID0gJy8nKSksDQogICAgICAgICBmYW1pbGlhID0gY2FzZV93aGVuKA0KICAgICAgICAgICBhZHVsdHMgPT0gJzAxX1VubycgJiBjaGlsZHJlbiArIGJhYmllcyA9PSAwIH4gJzAxX1NvbHRlcm8nLA0KICAgICAgICAgICBhZHVsdHMgPT0gJzAxX1VubycgJiBjaGlsZHJlbiArIGJhYmllcyA+IDAgfiAnMDJfTW9ub3BhcmVudGFsJywNCiAgICAgICAgICAgYWR1bHRzID09ICcwMl9Eb3MnICYgY2hpbGRyZW4gKyBiYWJpZXMgPT0gMCB+ICcwM19QYXJlamFfU2luX0hpam9zJywNCiAgICAgICAgICAgYWR1bHRzID09ICcwMl9Eb3MnICYgY2hpbGRyZW4gKyBiYWJpZXMgPiAwIH4gJzA0X1BhcmVqYV9Db25fSGlqb3MnLA0KICAgICAgICAgICBhZHVsdHMgPT0gJzAzX01hc19kZV9Eb3MnICYgY2hpbGRyZW4gKyBiYWJpZXMgPT0gMCB+ICcwNV9HcnVwb19BbWlnb3MnLA0KICAgICAgICAgICBhZHVsdHMgPT0gJzAzX01hc19kZV9Eb3MnICYgY2hpbGRyZW4gKyBiYWJpZXMgPiAwIH4gJzA2X0dydXBvX0FtaWdvc19Db25fSGlqb3MnLA0KICAgICAgICAgICBUUlVFIH4gJzk5X0VSUk9SJyksDQogICAgICAgICBleHRyYW5qZXJvID0gaWZlbHNlKGNvdW50cnkgPT0gJ1BSVCcsIDAsIDEpLA0KICAgICAgICAgY2FtYmlvX2hhYml0ID0gaWZlbHNlKHJlc2VydmVkX3Jvb21fdHlwZSA9PSBhc3NpZ25lZF9yb29tX3R5cGUsIDAsIDEpLA0KICAgICAgICAgbm9jaGVzX3RvdGFsZXMgPSBzdGF5c19pbl93ZWVrZW5kX25pZ2h0cyArIHN0YXlzX2luX3dlZWtfbmlnaHRzLA0KICAgICAgICAgZmFjdHVyYWNpb25fcmVzZXJ2YSA9IG5vY2hlc190b3RhbGVzICogYWRyDQogICAgICAgICApDQpgYGANCg0KJm5ic3A7DQoNCiMgPHNwYW4gc3R5bGU9ImNvbG9yOnJnYigwLCAwLCAyMDUpIj5EaXNjb3ZlcnkgeSBHZW5lcmFjacOzbiBkZSBJbnNpZ2h0czwvc3Bhbj4NCg0KRW4gZXN0YSBldGFwYSBlcyBkb25kZSB2YW1vcyBhIGhhY2VyIHByb3BpYW1lbnRlIGVsIGFuw6FsaXNpcyBkZWwgbmVnb2NpbywgcHVlcyB5YSB0ZW5lbW9zIGxhIGRhdGEgbGltcGlhIHkgdHJhbnNmb3JtYWRhLiBFcyBpbXBvcnRhbnRlIHRlbmVyIHByZXNlbnRlIGVsIHRpcG8gZGUgZ2lybyBvIHNlY3RvciwgeWEgcXVlIGRlIGVzbyBkZXBlbmRlcsOhIGxvcyBpbnNpZ2h0cyBhIGJ1c2Nhci4NCg0KIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjpyZ2IoMCwgMCwgMjA1KSI+RXN0YWNpb25hbGlkYWQ8L3NwYW4+DQoNCjxkaXYgY2xhc3M9dGV4dC1qdXN0aWZ5Pg0KUGFyYSBlbCBzZWN0b3IgaG90ZWxlcm8geSwgZW4gZ2VuZXJhbCwgZWwgdHVyaXNtbywgZWwgaWRlbnRpZmljYXIgbGFzIGxsYW1hZGFzIHRlbXBvcmFkYXMgYWx0YXMgeSBiYWphcyBlcyBtdXkgaW1wb3J0YW50ZSwgeWEgcXVlIGVzbyBsZXMgcGVybWl0aXLDoSBwcmVwYXJhcnNlLCBwb3IgZWplbXBsbywgcGFyYSBsYSBsbGVnYWRhIGRlIG1hc2l2YSBkZSBwZXJzb25hcyAodGVtcG9yYWRhIGFsdGEpIHkvbyBoYWNlciByZW1vZGVsYWNpb25lcyBvIGFtcGxpYWNpb25lcyBhIHN1IGluZnJhZXN0cnVjdHVyYSAodGVtcG9yYWRhIGJhamEpLg0KDQpUw6ljbmljYW1lbnRlLCBwYXJhIHBvZGVyIGRldGVybWluYXIgc2kgdW5hIHNlcmllIHByZXNlbnRhIGVzdGFjaW9uYWxpZGFkLCBlbnRlbmRpZGEgw6lzdGEgY29tbyB0ZW5kZW5jaWFzIG8gcGF0cm9uZXMgcXVlIHNlIHJlcGl0ZW4gY29uc3RhbnRlbWVudGUgZGVudHJvIGRlIHVuIHBlcmlvZG8gZGUgdGllbXBvLCBlcyBuZWNlc2FyaW8gYWwgbWVtb3MgY3VhdHJvIGHDsW9zIGRlIGhpc3RvcmlhLCBzaW4gZW1iYXJnbywgY29tbyBubyBjb250YW1vcyBjb24gdGFudGEgaGlzdG9yaWEgc29sbyBub3MgZW5mb2NhcmVtb3MgZW4gZWwgw7puaWNvIHBlcmlvZG8gY29tcGxldG8gcXVlIHRlbmVtb3MsIGHDsW8gMjAxNi4NCjwvZGl2Pg0KDQpWZWFtb3MgbGEgdGVuZGVuY2lhIGRlbCBhw7FvIDIwMTYgZW4gZ2VuZXJhbCwgcGFyYSBhbWJvcyBob3RlbGVzOg0KDQpgYGB7cn0NCmRmICU+JSANCiAgZmlsdGVyKGFycml2YWxfZGF0ZV95ZWFyID09ICcyMDE2JykgJT4lIA0KICBnZ3Bsb3QoYWVzKGZlY2hhX2xsZWdhZGEpKSArIA0KICBnZW9tX2RlbnNpdHkoc2l6ZSA9IDIsIGNvbG9yID0gJ2dyZXknKSArIA0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBzaXplID0gMTApKSArDQogICAgbGFicyh0aXRsZSA9ICJMbGVnYWRhcyAyMDE2IikNCmBgYA0KDQpBbCB2ZXIgbGEgZ3LDoWZpY2EgcG9kZW1vcyBhcHJlY2lhciBxdWUgYWwgaW5pY2lvIHkgYWwgZmluYWwgZGVsIGHDsW8gbGEgZGVtYW5kYSBlcyBiYWphLCBwZXJvICBhbCBpbmljaW8gZGVsIHRlcmNlciB0cmltZXN0cmUgbGEgZGVtYW5kYSBlcyBsYSBtw6FzIGFsdGEuIENvbXBsZW1lbnRlbW9zIGVsIGFuw6FsaXNpcyB2aXN1YWwgY29uIHVuYSB0YWJsYSByZXN1bWVuOg0KDQpgYGB7cn0NCmRmICU+JSANCiAgbXV0YXRlKHJlc2VydmFjaW9uZXMgPSByZXAoMSwgbnJvdyhkZikpKSAlPiUgIyBzZSBjcmVhIGxhIHZhcmlhYmxlIHJlc2VydmFjaW9uZXMNCiAgZmlsdGVyKGFycml2YWxfZGF0ZV95ZWFyID09IDIwMTYpICU+JSAgICAgICAgIyBzZSBmaWx0cmEgcG9yIGVsIGHDsW8gMjAxNg0KICBncm91cF9ieShhcnJpdmFsX2RhdGVfbW9udGgpICU+JSAgICAgICAgICAgICAjIGFncnVwYW1vcyBwb3IgZWwgbWVzIGRlIGxsZWdhZGENCiAgc3VtbWFyaXNlKHJlc2VydmFjaW9uZXMgPSBuKCkpICU+JSAgICAgICAgICAgIyBjb250YW1vcyBlbCBuw7ptZXJvIGRlIHJlc2VydmFjaW9uZXMNCiAgYXJyYW5nZShkZXNjKC1yZXNlcnZhY2lvbmVzKSkgJT4lICAgICAgICAgICAgIyBvcmRlbmFtb3MgZGUgbWVub3IgYSBtYXlvcg0KICBrYmwoYWxpZ24gPSAiYyIsIGRpZ2l0cyA9IDIgLGZvcm1hdC5hcmdzID0gbGlzdChiaWcubWFyayA9ICIsIikpICU+JQ0KICBrYWJsZV9wYXBlcigiaG92ZXIiLCBmdWxsX3dpZHRoID0gRikgICAgICAjIGFjb21vZGFtb3MgbGEgc2FsaWRhIGVuIHVuYSB0YWJsYSANCmBgYA0KDQpZYSBjb24gZXN0YSBvdHJhIHZpc3RhIHBvZGVtb3MgY29uY2x1aXIgbG8gc2lndWllbnRlOg0KDQotIEVuIGxvcyBtZXNlcyBkZSBtYXlvIHkgb2N0dWJyZSBoYXkgbWF5b3IgZGVtYW5kYSBkZSByZXNlcnZhY2lvbmVzLg0KLSBFbmVybywgZmVicmVybyB5IGRpY2llbWJyZSBzb24gbG9zIG1lc2VzIGRlIG1lbm9yIGRlbWFuZGEuDQotIFNvcnByZW5kZSBKdWxpbyB5IEFnb3N0byBxdWUgc29uIHJlbGF0aXZhbWVudGUgYmFqb3MuDQoNCkFob3JhLCB2ZWFtb3MgbGEgdGVuZGVuY2lhIHBvciB0aXBvIGRlIGhvdGVsOg0KDQpgYGB7cn0NCmRmICU+JSANCiAgZmlsdGVyKGFycml2YWxfZGF0ZV95ZWFyID09ICcyMDE2JykgJT4lIA0KICBnZ3Bsb3QoYWVzKGZlY2hhX2xsZWdhZGEsIGNvbG9yID0gaG90ZWwpKSArIA0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygic3RlZWxibHVlIiwgImdyZXk1MCIpKSsNCiAgZ2VvbV9kZW5zaXR5KHNpemUgPSAyKSArIA0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBzaXplID0gMTApKSArDQogIGxhYnModGl0bGUgPSAiTGxlZ2FkYXMgMjAxNiBwb3IgdGlwbyBkZSBIb3RlbCIpDQpgYGANCg0KRGUgbGEgZ3LDoWZpY2EgYW50ZXJpb3IgcG9kZW1vcyB2ZXIgcXVlOg0KDQotIE1hbnRpZW5lbiBjdXJ2YXMgZGUgZGVtYW5kYSBwYXJlY2lkYXMuDQotIEVsIGhvdGVsIHJlc29ydCB0aWVuZSBsaWdlcmFtZW50ZSBtw6FzIGRlbWFuZGEgaGFzdGEgbGEgcHJpbWF2ZXJhLg0KLSBNaWVudHJhcyBxdWUgZWwgZGUgY2l1ZGFkIGRlc3RhY2EgZW4gSnVuaW8sIFNlcHRpZW1icmUgeSBPY3R1YnJlDQoNCkhhZ2Ftb3MgdW5hIHRhYmxhIHBhcmEgY29tcGFyYXIgbG9zIGRhdG9zOg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFfQ0KZGYgJT4lIA0KICBtdXRhdGUocmVzZXJ2YWNpb25lcyA9IHJlcCgxLCBucm93KGRmKSkpICU+JSAjIHNlIGNyZWEgbGEgdmFyaWFibGUgcmVzZXJ2YWNpb25lcw0KICBmaWx0ZXIoYXJyaXZhbF9kYXRlX3llYXIgPT0gMjAxNikgJT4lICAgICAgICAjIHNlIGZpbHRyYSBwb3IgZWwgYcOxbyAyMDE2DQogIGdyb3VwX2J5KGFycml2YWxfZGF0ZV9tb250aCwgaG90ZWwpICU+JSAgICAgICMgc2UgYWdydXBhIHBvciBlbCBtZXMgZGUgYXJyaXZvIHkgaG90ZWwNCiAgc3VtbWFyaXNlKHJlc2VydmFjaW9uZXMgPSBuKCkpICU+JSAgICAgICAgICAgIyBzZSBoYWNlIGNvbnRlbyBkZSByZXNlcnZhY2lvbmVzDQogIGFycmFuZ2UoZGVzYygtcmVzZXJ2YWNpb25lcykpICU+JSAgICAgICAgICAgICMgc2Ugb3JkZW5hIGRlIG1lbm9yIGEgbWF5b3INCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IGhvdGVsLCB2YWx1ZXNfZnJvbSA9IHJlc2VydmFjaW9uZXMpICU+JSAjIHNlIHBhc2EgYSBmb3JtYXRvIGFuY2hvDQogIGtibChhbGlnbiA9ICJjIiwgZGlnaXRzID0gMiAsZm9ybWF0LmFyZ3MgPSBsaXN0KGJpZy5tYXJrID0gIiwiKSkgJT4lIA0KICBrYWJsZV9wYXBlcigiaG92ZXIiLCBmdWxsX3dpZHRoID0gRikgIyBzYWxpZGEgZW4gZm9ybWEgZGUgdGFibGENCmBgYA0KDQombmJzcDsNCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnJnYigwLCAwLCAyMDUpIj5BbsOhbGlzaXMgZGUgZmFjdHVyYWNpw7NuPC9zcGFuPg0KDQpQYXJhIHBvZGVyIGFuYWxpemFyIGxhIGZhY3R1cmFjacOzbiB0ZW5lbW9zIHF1ZSB0cmFiYWphciBzw7NsbyBjb24gbGFzIHJlc2VydmFzIHF1ZSBmaW5hbG1lbnRlIHR1dmllcm9uIGNoZWNrLW91dC4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCmRmICU+JSANCiAgZmlsdGVyKHJlc2VydmF0aW9uX3N0YXR1cyA9PSAnQ2hlY2stT3V0JykgJT4lIA0KICBncm91cF9ieShhcnJpdmFsX2RhdGVfeWVhciwgaG90ZWwpICU+JSANCiAgc3VtbWFyaXNlKGZhY3R1cmFjaW9uID0gc3VtKGZhY3R1cmFjaW9uX3Jlc2VydmEpKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IGFycml2YWxfZGF0ZV95ZWFyLCB5ID0gZmFjdHVyYWNpb24sIGZpbGwgPSBob3RlbCkpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygic3RlZWxibHVlIiwgImdyZXk1MCIpKSArDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmNvbW1hKSArIA0KICBnZW9tX2NvbChwb3NpdGlvbiA9ICdkb2RnZScpICsNCiAgbGFicyh0aXRsZSA9ICJGYWN0dXJhY2nDs24gcG9yIFRpcG8gZGUgSG90ZWwiLCB5ID0gIkZhY3R1cmFjacOzbiIsIHggPSAiQcOxbyBkZSBMbGVnYWRhIikNCmBgYA0KDQpFbiBsYSBncsOhZmljYSBwb2RlbW9zIGFwcmVjaWFyIGNsYXJhbWVudGUgcXVlIGxhIGZhY3R1cmFjacOzbiBzZSBoYSBpZG8gaW5jcmVtZW50YW5kbyBjYWRhIGHDsW8sIGFob3JhLCBoYXkgcXVlIHRlbmVyIHByZXNlbnRlIHF1ZSBzb2xvIGVsIGHDsW8gMjAxNiBlc3TDoSBjb21wbGV0bywgc2luIGVtYmFyZ28sIHZlbW9zIHF1ZSBlbCAyMDE3IHNlIHZlIGJpZW4sIHB1ZXMgc29sbyBsbGVnYSBhbCBtZXMgZGUgYWdvc3RvIHkgY2FzaSBhbGNhbnphIGxvcyBuaXZlbGVzIGRlIDIwMTYuDQoNCkVzdGUgaW5jcmVtZW50byBwdWRvIGRlYmVyc2UgYSB2YXJpb3MgbW90aXZvcyBjb21vLCBwb3IgZWplbXBsbzoNCg0KKiB1biBpbmNyZW1lbnRvIGVuIGVsIG7Dum1lcm8gZGUgbGFzIHJlc2VydmFjaW9uZXMsIGh1Ym8gbcOhcyBjbGllbnRlcy4NCiogYXVtZW50w7MgZWwgcHJlY2lvIG1lZGlvLg0KKiBjcmVjaW1pZW50byBwcm9tZWRpbyBkZSBub2NoZXMgcG9yIHJlc2VydmEuDQoNCkNvbiBlbCBhcG95byBkZSB1bmEgdGFibGEgdmVhbW9zIHNpIGVzb3MgbW90aXZvcyBleHBsaWNhbiBlbCBpbmNyZW1lbnRvIGVuIGxhIGZhY3R1cmFjacOzbjoNCg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCmRmICU+JSANCiAgZmlsdGVyKHJlc2VydmF0aW9uX3N0YXR1cyA9PSAnQ2hlY2stT3V0JyAmIGFycml2YWxfZGF0ZV95ZWFyICVpbiUgYygnMjAxNScsJzIwMTYnKSkgJT4lIA0KICBncm91cF9ieShob3RlbCxhcnJpdmFsX2RhdGVfeWVhcikgJT4lIA0KICBzdW1tYXJpc2UoDQogICAgbnVtX3Jlc2VydmFzID0gbigpLA0KICAgIG5vY2hlc19wb3JfcmVzZXJ2YSA9IG1lYW4obm9jaGVzX3RvdGFsZXMpLA0KICAgIHByZWNpb19ub2NoZSA9IG1lYW4oYWRyKSkgJT4lIA0KICBrYmwoYWxpZ24gPSAiYyIsIGRpZ2l0cyA9IDIgLGZvcm1hdC5hcmdzID0gbGlzdChiaWcubWFyayA9ICIsIikpICU+JSANCiAga2FibGVfcGFwZXIoImhvdmVyIiwgZnVsbF93aWR0aCA9IFQpDQpgYGANCiZuYnNwOw0KDQo8ZGl2IGNsYXNzPXRleHQtanVzdGlmeT4NCllhIGNvbiBsYSB0YWJsYSBwb2RlbW9zIGNvbmNsdWlyIHF1ZSwgcGFyYSBlbCBjYXNvIGRlbCBob3RlbCBkZSBjaXVkYWQsIGVsIGluY3JlbWVudG8gZGUgc3UgZmFjdHVyYWNpw7NuIHNlIGRlYmnDsywgcHJpbmNpcGFsbWVudGUsIGFsIGF1bWVudG8gZW4gZWwgbsO6bWVybyBkZSByZXNlcnZhcyAoY2FzaSAzIHZlY2VzZXMgbcOhcykgeSwgZW4gc2VndW5kbyBsdWdhciwgYWwgYXVtZW50byBkZWwgcHJlY2lvIHByb21lZGlvIHBvciBub2NoZS4gUGFyYSBlbCBjYXNvIGRlbCBob3RlbCByZXNvcnQsIGVsIGluY3JlbWVudG8gZW4gc3UgZmFjdHVyYWNpw7NuIGVzIGRlYmlkbyDDum5pY2FtZW50ZSBhbCBhdW1lbnRvIGVuIGVsIG7Dum1lcm8gZGUgcmVzZXJ2YXMsIHBvcnF1ZSB0YW50byBlbCBuw7ptZXJvIGRlIG5vY2hlcyBwb3IgcmVzZXJ2YSBjb21vIGVsIHByZWNpbyBwcm9tZWRpbyBwb3Igbm9jaGUgcHJlc2VudGFyb24gZGlzbWludWNpb25lcy4NCg0KVmVhbW9zIGPDs21vIGVzdMOhIGNvbmZvcm1hZGEgbGEgZmFjdHVyYWNpw7NuIHBvciBzZWdtZW50byBkZSBtZXJjYWRvOg0KPC9kaXY+DQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQpkZiAlPiUgDQogIGdyb3VwX2J5KGhvdGVsLCBtYXJrZXRfc2VnbWVudCkgJT4lIA0KICBzdW1tYXJpc2UoZmFjdHVyYWNpb25fc2VnbWVudG8gPSBzdW0oZmFjdHVyYWNpb25fcmVzZXJ2YSkpICU+JSANCiAgZ3JvdXBfYnkoaG90ZWwpICU+JSANCiAgbXV0YXRlKGZhY3R1cmFjaW9uX3RvdGFsID0gc3VtKGZhY3R1cmFjaW9uX3NlZ21lbnRvKSwNCiAgICAgICAgIHBvcmNfZmFjdHVyYWNpb25fdG90YWwgPSBmYWN0dXJhY2lvbl9zZWdtZW50byAvIGZhY3R1cmFjaW9uX3RvdGFsICogMTAwKSAlPiUNCiAgZHBseXI6OnNlbGVjdCgtZmFjdHVyYWNpb25fdG90YWwpICU+JSANCiAga2JsKGFsaWduID0gImMiLCBkaWdpdHMgPSAyICxmb3JtYXQuYXJncyA9IGxpc3QoYmlnLm1hcmsgPSAiLCIpKSAlPiUgDQogIGthYmxlX3BhcGVyKCJob3ZlciIsIGZ1bGxfd2lkdGggPSBUKQ0KDQpgYGANCg0KJm5ic3A7DQoNCkRlIGxhIHRhYmxhIGFudGVyaW9yLCBwb2RlbW9zIGNvbmNsdWlyIHF1ZSBsYSBtYXlvciBwYXJ0ZSBkZSBsYSBmYWN0dXJhY2nDs24gZW4gYW1ib3MgaG90ZWxlcyB2aWVuZSBwb3IgZWwgbGFkbyBkZWwgc2VnbWVudG8gT25saW5lIHksIHBvciBlbCBjb250cmFyaW8sIGVsIHNlZ21lbnRvIHF1ZSBtZW5vcyBjb250cmlidXllIGVzIGVsIENvcnBvcmF0ZS4NCg0KYGBge3J9DQp0MCA8LSBkZiAlPiUgDQogIGdyb3VwX2J5KGhvdGVsLCBtYXJrZXRfc2VnbWVudCkgJT4lIA0KICBzdW1tYXJpc2UoZmFjdHVyYWNpb25fc2VnbWVudG8gPSBzdW0oZmFjdHVyYWNpb25fcmVzZXJ2YSkpICU+JSANCiAgZ3JvdXBfYnkoaG90ZWwpICU+JSANCiAgbXV0YXRlKGZhY3R1cmFjaW9uX3RvdGFsID0gc3VtKGZhY3R1cmFjaW9uX3NlZ21lbnRvKSwNCiAgICAgICAgIHBvcmNfZmFjdHVyYWNpb25fdG90YWwgPSBmYWN0dXJhY2lvbl9zZWdtZW50byAvIGZhY3R1cmFjaW9uX3RvdGFsICogMTAwKSAlPiUNCiAgZHBseXI6OnNlbGVjdCgtZmFjdHVyYWNpb25fdG90YWwsIC1wb3JjX2ZhY3R1cmFjaW9uX3RvdGFsKQ0KDQp0MCRob3RlbCA8LSBhcy5jaGFyYWN0ZXIodDAkaG90ZWwpDQp0MCRtYXJrZXRfc2VnbWVudCA8LSBhcy5jaGFyYWN0ZXIodDAkbWFya2V0X3NlZ21lbnQpDQoNCnNhbmtleV9seSh4ID0gdDAsIA0KICAgICAgICAgIGNhdF9jb2xzID0gYygiaG90ZWwiLCAibWFya2V0X3NlZ21lbnQiKSwgDQogICAgICAgICAgbnVtX2NvbCA9ICJmYWN0dXJhY2lvbl9zZWdtZW50byIsIA0KICAgICAgICAgIHRpdGxlID0gIkRpc3RyaWJ1Y2nDs24gZGUgSW5ncmVzb3MgcG9yIFNlZ21lbnRvIFNlZ8O6biBUaXBvIGRlIEhvdGVsIikgDQoNCmBgYA0KDQpBaG9yYSwgYW5hbGljZW1vcyBsYSBmYWN0dXJhY2nDs24gcG9yIGZhbWlsaWEsIHBhcmEgdmVyIGxhIGNvbnRyaWJ1Y2nDs24gZGUgY2FkYSBzZWdtZW50bzoNCg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCmRmICU+JSANCiAgZ3JvdXBfYnkoaG90ZWwsIGZhbWlsaWEpICU+JSANCiAgc3VtbWFyaXNlKGZhY3R1cmFjaW9uX2ZhbWlsaWEgPSBzdW0oZmFjdHVyYWNpb25fcmVzZXJ2YSkpICU+JSANCiAgZ3JvdXBfYnkoaG90ZWwpICU+JSANCiAgbXV0YXRlKGZhY3R1cmFjaW9uX3RvdGFsID0gc3VtKGZhY3R1cmFjaW9uX2ZhbWlsaWEpLA0KICAgICAgICAgcG9yY19mYWN0dXJhY2lvbl90b3RhbCA9IGZhY3R1cmFjaW9uX2ZhbWlsaWEgLyBmYWN0dXJhY2lvbl90b3RhbCAqIDEwMCkgJT4lIA0KICBkcGx5cjo6c2VsZWN0KC1mYWN0dXJhY2lvbl90b3RhbCkgJT4lIA0KICBrYmwoYWxpZ24gPSAiYyIsIGRpZ2l0cyA9IDIgLGZvcm1hdC5hcmdzID0gbGlzdChiaWcubWFyayA9ICIsIikpICU+JSANCiAga2FibGVfcGFwZXIoImhvdmVyIiwgZnVsbF93aWR0aCA9IFQpDQpgYGANCg0KJm5ic3A7DQoNCkNsYXJhbWVudGUgc2UgYXByZWNpYSBlbiBlbCBjdWFkcm8gYW50ZXJpb3IsIHF1ZSBlbCBzZWdtZW50byBkZSBwYXJlamFzIHNpbiBoaWpvcyBlcyBlbCBxdWUgbcOhcyBjb250cmlidXllIGEgbGEgZmFjdHVyYWNpw7NuIHRvdGFsIGVuIGFtYm9zIGhvdGVsZXMuDQoNCmBgYHtyfQ0KdDIgPC0gZGYgJT4lIA0KICBncm91cF9ieShob3RlbCwgZmFtaWxpYSkgJT4lIA0KICBzdW1tYXJpc2UoZmFjdHVyYWNpb25fZmFtaWxpYSA9IHN1bShmYWN0dXJhY2lvbl9yZXNlcnZhKSkgJT4lIA0KICBncm91cF9ieShob3RlbCkgJT4lIA0KICBtdXRhdGUoZmFjdHVyYWNpb25fdG90YWwgPSBzdW0oZmFjdHVyYWNpb25fZmFtaWxpYSksDQogICAgICAgICBwb3JjX2ZhY3R1cmFjaW9uX3RvdGFsID0gZmFjdHVyYWNpb25fZmFtaWxpYSAvIGZhY3R1cmFjaW9uX3RvdGFsICogMTAwKSAlPiUgDQogIGRwbHlyOjpzZWxlY3QoLWZhY3R1cmFjaW9uX3RvdGFsLCAtcG9yY19mYWN0dXJhY2lvbl90b3RhbCkNCg0KdDIkaG90ZWwgPC0gYXMuY2hhcmFjdGVyKHQyJGhvdGVsKQ0KDQpzYW5rZXlfbHkoeCA9IHQyLCANCiAgICAgICAgICBjYXRfY29scyA9IGMoImhvdGVsIiwgImZhbWlsaWEiKSwgDQogICAgICAgICAgbnVtX2NvbCA9ICJmYWN0dXJhY2lvbl9mYW1pbGlhIiwgDQogICAgICAgICAgdGl0bGUgPSAiRGlzdHJpYnVjacOzbiBkZSBJbmdyZXNvcyBwb3IgRmFtaWxpYSBTZWfDum4gVGlwbyBkZSBIb3RlbCIpIA0KDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnJnYigwLCAwLCAyMDUpIj5BbsOhbGlzaXMgZGVsIG1lcmNhZG8geSBkZWwgY2xpZW50ZTwvc3Bhbj4NCg0KRW4gY3VhbnRvIGEgbG9zIGNsaWVudGVzLCBjb21vIGFtYm9zIGhvdGVsZXMgc2UgZW5jdWVudHJhbiBlbiBQb3J0dWdhbCBzdXBvbmVtb3MgcXVlIGxhIG1heW9yw61hIHNvbiBkZSBlc2UgbWlzbW8gcGHDrXMsIHNpbiBlbWJhcmdvLCBoYWdhbW9zIHVuYSB0YWJsYSBwYXJhIHZlciBsYSBkaXN0cmlidWNpw7NuIGVudHJlIGNsaWVudGVzIG5hY2lvbmFsZXMgeSBleHRyYW5qZXJvczoNCg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCmNvdW50KGRmLCBob3RlbCwgZXh0cmFuamVybykgJT4lIA0KICBncm91cF9ieShob3RlbCkgJT4lIA0KICBtdXRhdGUodG90YWxfaG90ZWwgPSBzdW0obiksDQogICAgICAgICBwb3JjX2hvdGVsID0gbiAvIHRvdGFsX2hvdGVsICogMTAwKSAlPiUgDQogIGtibChhbGlnbiA9ICJjIiwgZGlnaXRzID0gMiAsZm9ybWF0LmFyZ3MgPSBsaXN0KGJpZy5tYXJrID0gIiwiKSkgJT4lIA0KICBrYWJsZV9wYXBlcigiaG92ZXIiLCBmdWxsX3dpZHRoID0gRikNCmBgYA0KDQombmJzcDsNCg0KQWwgdmVyIGVsIGN1YWRybyBhbnRlcmlvciwgc29ycHJlbmRlIGlkZW50aWZpY2FyIHF1ZSBsYSBtYXlvcsOtYSBkZSBsb3MgY2xpZW50ZXMgc29uIGV4dHJhbmplcm9zIGVuIGFtYm9zIGhvdGVsZXMsIGFkZW1hcywgZW4gZWwgaG90ZWwgZGUgY2l1ZGFkIGhheSBjYXNpIGVsIGRvYmxlIGRlIGNsaWVudGVzIHF1ZSBlbiBlbCBob3RlbCByZXNvcnQgeSBlbiDDqXN0ZSDDumx0aW1vIHBvY28gbWVub3MgZGUgbGEgbWl0YWQgc29uIGNsaWVudGVzIG5hY2lvbmFsZXMuDQoNCkFob3JhLCB2ZWFtb3MgZWwgdG9wIDUgZGUgY2xpZW50ZXMgcG9yIG5hY2lvbmFsaWRhZCBwYXJhIHZlciBkZSBxdcOpIHBhw61zZXMgaGF5IG1heW9yIHByZXNlbmNpYSBwYXJhIGFtYm9zIGhvdGVsZXM6DQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQpjb3VudChkZiwgaG90ZWwsIGNvdW50cnkpICU+JSANCiAgZ3JvdXBfYnkoaG90ZWwpICU+JSANCiAgbXV0YXRlKHRvdGFsX2hvdGVsID0gc3VtKG4pLA0KICAgICAgICAgcG9yY19ob3RlbCA9IG4gLyB0b3RhbF9ob3RlbCAqIDEwMCkgJT4lIA0KICBhcnJhbmdlKGhvdGVsLCBkZXNjKHBvcmNfaG90ZWwpKSAlPiUgDQogIHRvcF9uKDUpICU+JSANCiAgZHBseXI6OnNlbGVjdCgtdG90YWxfaG90ZWwpICU+JSANCiAga2JsKGFsaWduID0gImMiLCBkaWdpdHMgPSAyICxmb3JtYXQuYXJncyA9IGxpc3QoYmlnLm1hcmsgPSAiLCIpKSAlPiUgDQogIGthYmxlX3BhcGVyKCJob3ZlciIsIGZ1bGxfd2lkdGggPSBGKQ0KYGBgDQoNCiZuYnNwOw0KDQpDb21vIGVyYSBkZSBlc3BlcmFyc2UsIHBvciBwYcOtcywgbGEgbWF5b3LDrWEgZGUgbG9zIGNsaWVudGVzIGRlIGVzdGEgY2FkZW5hIGRlIGhvdGVsZXMgc29uIGRlIFBvcnR1Z2FsLiBTaW4gZW1iYXJnbywgZW4gZWwgaG90ZWwgZGUgY2l1ZGFkIHNvbiBsb3MgZnJhbmNlc2VzIHkgYWxlbWFuZXMgbG9zIHF1ZSBsZSBzaWd1ZW4sIGEgZGlmZXJlbmNpYSBkZWwgaG90ZWwgcmVzb3J0IGRvbmRlIHNvbiBsb3MgYnJpdMOhbmljb3MgeSBlc3Bhw7FvbGVzIGxvcyBxdWUgdGllbmVuIG1heW9yIGZyZWN1ZW5jaWEgZGVzcHXDqXMgZGUgbG9zIHBvcnR1Z3Vlc2VzLg0KDQombmJzcDsNCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnJnYigwLCAwLCAyMDUpIj5BbsOhbGlzaXMgZGUgcHJlY2lvczwvc3Bhbj4NCg0KQSBjb250aW51YWNpw7NuLCBlY2hlbW9zIHVuIHZpc3Rhem8gYWwgcHJvbWVkaW8gZGUgbG9zIHByZWNpb3MgcG9yIG5vY2hlOg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFfQ0KZGYgJT4lIA0KICBmaWx0ZXIoYXJyaXZhbF9kYXRlX3llYXIgJWluJSBjKCcyMDE1JywnMjAxNicpKSAlPiUgDQogIGdyb3VwX2J5KGhvdGVsLCBhcnJpdmFsX2RhdGVfd2Vla19udW1iZXIpICU+JSANCiAgc3VtbWFyaXNlKHByZWNpb19tZWRpbyA9IG1lYW4oYWRyKSkgJT4lDQogIGdncGxvdChhZXMoeCA9IGFycml2YWxfZGF0ZV93ZWVrX251bWJlciwgeSA9IHByZWNpb19tZWRpbywgZ3JvdXAgPSAxKSkgKw0KICBnZW9tX2xpbmUoY29sb3IgPSAnZ3JleTUwJykgKw0KICBnZW9tX3Ntb290aChjb2xvciA9ICdyZWQnKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSA4KSkgKw0KICBsYWJzKHRpdGxlID0gIlRlbmRlbmNpYSBkZSBQcmVjaW9zOiAyMDE1LTIwMTYiKQ0KYGBgDQoNCkxhIGdyw6FmaWNhIG5vcyBtdWVzdHJhIHF1ZSwgYWwgaW5pY2lvIGRlIGNhZGEgYcOxbyB5IGFsIGZpbmFsLCBlbCBwcmVjaW8gcHJvbWVkaW8gZGUgbGFzIHJlc2VydmFzIHRpZW5lIHN1IG1lbm9yIG5pdmVsLCBsbyBjdWFsIGNvaW5jaWRlIGNvbiBsYXMgw6lwb2NhcyBkZSBiYWphIGRlbWFuZGEuIFNvbiBlbnRyZSBsYXMgc2VtYW5hcyAyOSBhIDM1IGRvbmRlIGVsIHByZWNpbyBwcm9tZWRpbyBhbGNhbnphIHN1cyBuaXZlbGVzIG3DoXhpbW9zIHF1ZSwgaWd1YWxtZW50ZSwgc2UgY29ycmVzcG9uZGUgY29uIGxhIHRlbXBvcmFkYSBkZSBhbHRhIGRlbWFuZGEuDQoNClBvciBsbyBhbnRlcmlvciwgdmVhbW9zIHNpIGV4aXN0ZSBhbGd1bmEgY29ycmVsYWNpw7NuIGVudHJlIGVsIHByZWNpbyBwcm9tZWRpbyB5IGxhIGRlbWFuZGEgZGUgcmVzZXJ2YXMuDQoNCiZuYnNwOw0KDQojIDxzcGFuIHN0eWxlPSJjb2xvcjpyZ2IoMCwgMCwgMjA1KSI+Q29ycmVsYWNpw7NuIFJlc2VydmFzIHZzIFByZWNpbyBNZWRpbzwvc3Bhbj4NCg0KVmFtb3MgaGFjZXIgdW4gcGVxdWXDsW8gYW7DoWxpc2lzIGVzdGFkw61zdGljbyBkZSBjb3JyZWxhY2nDs24sIHBvciBsbyBxdWUsIGRlYmVtb3MgdGVuZXIgcHJlc2VudGUgYWxndW5hcyBjb25zaWRlcmFjaW9uZXM6DQoNCjEuIENvbm9jZXIgc2kgbGFzIHZhcmlhYmxlcyBwcmVzZW50YW4gZGlzdHJpYnVjacOzbiBub3JtYWwsIHlhIHF1ZSBkZSBlc28gZGVwZW5kZXLDoSBlbCBjb2VmaWNpZW50ZSBkZSBjb3JyZWxhY2nDs24gYSB1c2FyLg0KMi4gRGUgYWN1ZXJkbyBjb24gZWwgcmVzdWx0YWRvIGRlbCBwdW50byBhbnRlcmlvciwgZWxlZ2lyIGVsIGNvZWZpY2llbnRlIGRlIGNvcnJlbGFjacOzbiBxdWUgY29ycmVzcG9uZGEuDQozLiBIYWNlciB1bmEgcHJ1ZWJhIGRlIHNpZ25pZmljYW5jaWEgZXN0YWTDrXN0aWNhIHBhcmEgZGVzY2FydGFyIHVuYSBwb3NpYmxlIGNvcnJlbGFjacOzbiBkZWJpZG8gYWwgYXphci4NCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnJnYigwLCAwLCAyMDUpIj5Ob3JtYWxpZGFkPC9zcGFuPg0KDQpBbsOhbGlzaXMgdmlzdWFsOg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFfQ0KYyA8LSBkZiAlPiUgDQogIGZpbHRlcihhcnJpdmFsX2RhdGVfeWVhciAlaW4lIGMoJzIwMTUnLCAnMjAxNicpKSAlPiUgDQogIGdyb3VwX2J5KGhvdGVsLCBhcnJpdmFsX2RhdGVfd2Vla19udW1iZXIpICU+JSANCiAgc3VtbWFyaXNlKG51bV9yZXNlcnZhcyA9IG4oKSwNCiAgICAgICAgICAgIHByZWNpb19tZWRpbyA9IG1lYW4oYWRyKSkgJT4lIA0KICBzZWxlY3QobnVtX3Jlc2VydmFzLCBwcmVjaW9fbWVkaW8pDQoNCg0KaDEgPC0gZ2dwbG90KGRhdGEgPSBjLCBhZXMoeCA9IG51bV9yZXNlcnZhcykpICsgZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSAuLmRlbnNpdHkuLiwgZmlsbCA9IC4uY291bnQuLikpICsgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAiI0RDRENEQyIsIGhpZ2ggPSAiIzdDN0M3QyIpICsgc3RhdF9mdW5jdGlvbihmdW4gPSBkbm9ybSwgY29sb3VyID0gImZpcmVicmljayIsIGFyZ3MgPSBsaXN0KG1lYW4gPSBtZWFuKGMkbnVtX3Jlc2VydmFzKSwgc2QgPSBzZChjJG51bV9yZXNlcnZhcykpKSArIGdndGl0bGUoIkhpc3RvZ3JhbWEgUmVzZXJ2YXMiKSArIHRoZW1lX2J3KCkNCg0KcXExIDwtIGdncGxvdChjLCBhZXMoc2FtcGxlID0gbnVtX3Jlc2VydmFzKSkgKw0KICBzdGF0X3FxKCkgKw0KICBzdGF0X3FxX2xpbmUoKSArDQogIGxhYnModGl0bGUgPSAiRGlzdHJpYnVjacOzbiBkZSBSZXNlcnZhcyIpDQoNCmgyIDwtIGdncGxvdChkYXRhID0gYywgYWVzKHggPSBwcmVjaW9fbWVkaW8pKSArIGdlb21faGlzdG9ncmFtKGFlcyh5ID0gLi5kZW5zaXR5Li4sIGZpbGwgPSAuLmNvdW50Li4pKSArIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93ID0gIiNEQ0RDREMiLCBoaWdoID0gIiM3QzdDN0MiKSArIHN0YXRfZnVuY3Rpb24oZnVuID0gZG5vcm0sIGNvbG91ciA9ICJmaXJlYnJpY2siLCBhcmdzID0gbGlzdChtZWFuID0gbWVhbihjJHByZWNpb19tZWRpbyksIHNkID0gc2QoYyRwcmVjaW9fbWVkaW8pKSkgKyBnZ3RpdGxlKCJIaXN0b2dyYW1hIFByZWNpbyBNZWRpbyIpICsgdGhlbWVfYncoKQ0KDQpxcTIgPC0gZ2dwbG90KGMsIGFlcyhzYW1wbGUgPSBwcmVjaW9fbWVkaW8pKSArDQogIHN0YXRfcXEoKSArDQogIHN0YXRfcXFfbGluZSgpICsNCiAgbGFicyh0aXRsZSA9ICJEaXN0cmlidWNpw7NuIGRlIFByZWNpbyBNZWRpbyIpDQoNCg0KZ3JpZC5hcnJhbmdlKGgxLCBoMiwgcXExLCBxcTIsIG5yb3cgPSAyLCBuY29sID0gMiwgdG9wID0gIkFuw6FsaXNpcyBkZSBOb3JtYWxpZGFkIikNCmBgYA0KDQpBbCB2ZXIgbGFzIGdyw6FmaWNhcyBwb2RlbW9zIHNvc3BlY2hhciBxdWUgYW1iYXMgdmFyaWFibGVzIG5vIHByZXNlbnRhbiB1bmEgZGlzdHJpYnVjacOzbiBub3JtYWwuIFBhcmEgcG9kZXIgY29uZmlybWFyIGxvIGFudGVyaW9yIHRlbmVtb3MgcXVlIGhhY2VyIHBydWViYXMgZXN0YWTDrXN0aWNhcy4gQWhvcmEsIGNvbW8gYW1iYXMgc2VyaWVzIHRpZW5lbiBtw6FzIGRlIDUwIG9ic2VydmFjaW9uZXMgYXBsaWNhcmVtb3MgbGEgcHJ1ZWJhIGRlIG5vcm1hbGlkYWQgZGUgTGlsbGllZm9yczoNCg0KUHJ1ZWJhcyBkZSBub3JtYWxpZGFkOg0KDQpgYGB7cn0NCmxpbGxpZS50ZXN0KHggPSBjJG51bV9yZXNlcnZhcykNCg0KbGlsbGllLnRlc3QoeCA9IGMkcHJlY2lvX21lZGlvKQ0KYGBgDQoNCkxvcyByZXN1bHRhZG9zIG11ZXN0cmFuIHF1ZSBsYXMgZG9zIHNlcmllcyAqKm5vIHByZXNlbnRhbiB1bmEgZGlzdHJpYnVjacOzbiBub3JtYWwqKiwgeWEgcXVlIGFtYm9zIHAtdmFsdWUgc29uIG1lbm9yZXMgYWwgMC4wNSBkZSBzaWduaWZpY2FuY2lhLiBQb3IgbG8gYW50ZXJpb3IsIHlhIHBvZGVtb3MgZGVjaWRpciBjdcOhbCBjb2VmaWNpZW50ZSB1c2FyIHBhcmEgY29ub2NlciBlbCBncmFkbyBkZSBhc29jaWFjacOzbiBlbnRyZSBlbGxhcyBkYWRhcyBsYXMgY2FyYWN0ZXLDrXN0aWNhcyBkZSBsYXMgc2VyaWVzLg0KDQombmJzcDsNCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnJnYigwLCAwLCAyMDUpIj5Db3JyZWxhY2nDs248L3NwYW4+DQoNCkNvZWZpY2llbnRlIGRlIENvcnJlbGFjacOzbg0KDQpZYSBzYWJpZW5kbyBxdWUgbGFzIHNlcmllcyBubyBwcmVzZW50YW4gZGlzdHJpYnVjacOzbiBub3JtYWwsIHNlIHVzYXLDoSBlbCBjb2VmaWNpZW50ZSBkZSBjb3JyZWxhY2nDs24gZGUgU3BlYXJtYW46DQoNCmBgYHtyfQ0KY29yKGMkbnVtX3Jlc2VydmFzLCBsb2coYyRwcmVjaW9fbWVkaW8pLCBtZXRob2QgPSAic3BlYXJtYW4iKQ0KYGBgDQoNCkxhIGNvcnJlbGFjacOzbiByZXN1bHRhIHNlciBwb3NpdGl2YSBkZSAwLjU0LCBsbyBxdWUgbm9zIGluZGljYSB1biBncmFkbyBkZSBhc29jaWFjacOzbiBtb2RlcmFkYSwgc2luIGVtYmFyZ28sIGhheSBxdWUgaGFjZXIgZWwgdGVzdCBkZSBzaWduaWZpY2FuY2lhLCBwYXJhIGNvbmZpcm1hciBxdWUgcmVhbG1lbnRlIGV4aXN0ZSBjb3JyZWxhY2nDs24gZW50cmUgZWwgbsO6bWVybyBkZSByZXNlcnZhcyB5IGVsIHByZWNpbyBtZWRpbywgeWEgcXVlIGRlIGxvIGNvbnRyYXJpbyBkaWNoYSBhc29jaWFjacOzbiBwdWVkZSBkZWJlcnNlIGFsIGF6YXIuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQpjb3IudGVzdCh4ID0gYyRudW1fcmVzZXJ2YXMsDQogICAgICAgICB5ID0gbG9nKGMkcHJlY2lvX21lZGlvKSwNCiAgICAgICAgIGFsdGVybmF0aXZlID0gInR3by5zaWRlZCIsDQogICAgICAgICBjb25mLmxldmVsID0gMC45NSwNCiAgICAgICAgIG1ldGhvZCA9ICJzcGVhcm1hbiIpDQpgYGANCg0KRWwgcC12YWx1ZSBlcyBjZXJjYW5vIGEgY2VybyAobWVub3IgYSAwLjA1KSwgcG9yIGxvIHF1ZSwgcG9kZW1vcyBhZmlybWFyIHF1ZSBlbCBjb2VmaWNpZW50ZSBkZSBjb3JyZWxhY2nDs24gYW50ZXMgb2J0ZW5pZG8gZXMgc2lnbmlmaWNhdGl2bywgbyBzZWEsIGxhIGFzb2NpYWNpw7NuIGVudHJlIGxhcyByZXNlcnZhcyB5IGVsIHByZWNpbyBtZWRpbyBubyBzZSBkYSBwb3IgY2FzdWFsaWRhZC4NCg0KWWEgcG9yIMO6bHRpbW8sIGNhbGN1bGVtb3MgZWwgY29lZmljaWVudGUgZGUgZGV0ZXJtaW5hY2nDs24gcGFyYSBtZWRpciBlbCB0YW1hw7FvIGRlbCBlZmVjdG86DQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQpjb3IoYyRudW1fcmVzZXJ2YXMsIGxvZyhjJHByZWNpb19tZWRpbyksIG1ldGhvZCA9ICJzcGVhcm1hbiIpXjINCmBgYA0KDQpFbCB0YW1hw7FvIGRlbCBlZmVjdG8gZXMgbWVkaWFubywgY2VyY2FubyBhbCAwLjMuIFZlYW1vcyBsYSByZWxhY2nDs24gdmlzdWFsbWVudGU6DQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQpnIDwtIGMgJT4lIA0KICBnZ3Bsb3QoYWVzKG51bV9yZXNlcnZhcywgcHJlY2lvX21lZGlvKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aCgpICsNCiAgbGFicyh0aXRsZSA9ICJSZWxhY2nDs24gUmVzZXJ2YXMgdnMgUHJlY2lvIFByb21lZGlvIikNCg0KZ2dwbG90bHkoZykNCmBgYA0KDQpTZSBhcHJlY2lhIHF1ZSBubyBoYXkgdW5hIGNsYXJhIHRlbmRlbmNpYSBwb3NpdGl2YSBvIG5lZ2F0aXZhLCBwdWVzIGxhIG51YmUgZGUgcHVudG9zIGNhc2kgc2UgbWFudGllbmUgY29uc3RhbnRlLiBBaMOtIGhheSB1bmEgb3BvcnR1bmlkYWQgZGUgbmVnb2Npby4NCg0KdmVhbW9zIGFob3JhIHBvciB0aXBvIGRlIGhvdGVsIHkgcG9yIHNlbWFuYSBkdXJhbnRlIGVsIHBlcmlvZG8gZGUgYW7DoWxpc2lzOg0KDQpgYGB7ciB0aW1lLCBtZXNzYWdlPUZBTFNFfQ0KZGY0IDwtIGRmICU+JSANCiAgZmlsdGVyKHJlc2VydmF0aW9uX3N0YXR1cyA9PSAnQ2hlY2stT3V0JyAmIGFkciA+IDApDQogIA0KZGY0IDwtIGRmNCAlPiUgIA0KICBncm91cF9ieShhcnJpdmFsX2RhdGVfeWVhciwgaG90ZWwsIGFycml2YWxfZGF0ZV93ZWVrX251bWJlcikgJT4lIA0KICAgc3VtbWFyaXNlKG51bV9yZXNlcnZhcyA9IG4oKSwNCiAgICAgICAgICAgICBwcmVjaW9fbWVkaW8gPSBtZWFuKGFkcikpICU+JSANCiAgIHNlbGVjdChhcnJpdmFsX2RhdGVfeWVhciwgbnVtX3Jlc2VydmFzLCBwcmVjaW9fbWVkaW8sIGFycml2YWxfZGF0ZV93ZWVrX251bWJlcikNCg0KZGY0JGFycml2YWxfZGF0ZV93ZWVrX251bWJlciA8LSBhcy5udW1lcmljKGRmNCRhcnJpdmFsX2RhdGVfd2Vla19udW1iZXIpDQoNCmRmciA8LSBkZjQgJT4lIA0KICBkcGx5cjo6ZmlsdGVyKGhvdGVsID09ICJSZXNvcnQgSG90ZWwiKQ0KDQpkZmMgPC0gZGY0ICU+JSANCiAgZHBseXI6OmZpbHRlcihob3RlbCA9PSAiQ2l0eSBIb3RlbCIpDQoNCg0KZ3IgPC0gZ2dwbG90KGRmciwgYWVzKG51bV9yZXNlcnZhcywgcHJlY2lvX21lZGlvKSkgKw0KICBnZW9tX2ppdHRlcigpICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3VyID0gYXJyaXZhbF9kYXRlX3dlZWtfbnVtYmVyKSwgc2l6ZSA9IDMpICsNCiAgbGFicyh0aXRsZSA9ICJUZW5kZW5jaWEgU2VtYW5hbCBIb3RlbCBSZXNvcnQ6IFByZWNpbyBNZWRpbyB5IE7Dum1lcm8gZGUgUmVzZXJ2YWNpb25lcyIpDQoNCmdjIDwtIGdncGxvdChkZmMsIGFlcyhudW1fcmVzZXJ2YXMsIHByZWNpb19tZWRpbykpICsNCiAgZ2VvbV9qaXR0ZXIoKSArDQogIGdlb21fcG9pbnQoYWVzKGNvbG91ciA9IGFycml2YWxfZGF0ZV93ZWVrX251bWJlciksIHNpemUgPSAzKSArDQogIGxhYnModGl0bGUgPSAiVGVuZGVuY2lhIFNlbWFuYWwgSG90ZWwgQ2l1ZGFkOiBQcmVjaW8gTWVkaW8geSBOw7ptZXJvIGRlIFJlc2VydmFjaW9uZXMiKQ0KDQpncmlkLmFycmFuZ2UoZ3IsIGdjLCBucm93ID0gMiwgdG9wID0gIlRlbmRlbmNpYXMgU2VtYW5hbGVzIikNCg0KYGBgDQoNCjxkaXYgY2xhc3M9dGV4dC1qdXN0aWZ5Pg0KRW4gbGFzIGdyw6FmaWNhcyBhbnRlcmlvcmVzLCBlbCBjb2xvciBkZSBsb3MgcHVudG9zIGluZGljYSBlbCB0aWVtcG8gKG7Dum1lcm8gZGUgc2VtYW5hKSwgZW50cmUgbcOhcyBjbGFybyBlcyBlbCBjb2xvciBtw6FzIGNlcmNhbm8gYWwgZmluIGRlIGHDsW8gbm9zIGVuY29udHJhbW9zLiBFcyBlbiBlbCBob3RlbCByZXNvcnQgZG9uZGUsIGVuIGdlbmVyYWwsIG5vIHNlIGNvcnJlc3BvbmRlIGxhIHRlbXBvcmFkYSBhbHRhIGNvbiBwcmVjaW9zIHByb21lZGlvIGVsZXZhZG9zLCBwb3IgbG8gcXVlLCBzZXLDrWEgY29udmVuaWVudGUgYW5hbGl6YXIgbGEgcG9zaWJpbGlkYWQgZGUgYXVtZW50YXIgbG9zIHByZWNpb3MgZW4gdGVtcG9yYWRhIGFsdGEsIG8gc2VhLCBidXNjYXIgZWwgcHJlY2lvIMOzcHRpbW8gZW4gZXNlIHBlcmlvZG8gZGVsIGHDsW8uDQo8L2Rpdj4NCg0KVmVhbW9zIGxhIGRpc3RyaWJ1Y2nDs24gZGUgbG9zIHByZWNpb3MgcG9yIHRpcG8gZGUgaG90ZWw6DQoNCmBgYHtyfQ0KZ2dwbG90KGRmLCBhZXMoeCA9IGhvdGVsLCB5ID0gYWRyLCBjb2xvciA9IGhvdGVsKSkgKw0KICBnZW9tX2JveHBsb3QoKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJzdGVlbGJsdWUiLCAiZ3JleTUwIikpICsNCiAgc3RhdF9zdW1tYXJ5KGZ1biA9IG1lYW4sIA0KICAgICAgICAgICAgICAgZ2VvbSA9ICJwb2ludCIsDQogICAgICAgICAgICAgICBzaGFwZSA9IDIwLCANCiAgICAgICAgICAgICAgIHNpemUgPSA0LCANCiAgICAgICAgICAgICAgIGNvbG9yID0gInJlZCIsDQogICAgICAgICAgICAgICBmaWxsID0gInJlZCIpICsNCiAgbGFicyh0aXRsZSA9ICJQcmVjaW8gTWVkaW8gcG9yIEhvdGVsIikNCmBgYA0KDQpMb3MgcHJlY2lvcyBtZWRpb3MgZW4gYW1ib3MgdGlwb3MgZGUgaG90ZWwgc29uIG11eSBwYXJlY2lkb3MsIHNpZW5kbyBlbiBlbCBob3RlbCByZXNvcnQgZG9uZGUgZWwgNTAlIGRlIGxvcyBwcmVjaW9zIHNvbiBtw6FzIGJham9zLg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFfQ0KcG0gPC0gZGYgJT4lIA0KICBncm91cF9ieShob3RlbCwgbGVhZF90aW1lKSAlPiUgDQogIHN1bW1hcmlzZShwcmVjaW9fbWVkaW8gPSBtZWFuKGFkcikpDQogIA0KZ20gPC0gcG0gJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBsZWFkX3RpbWUsIHkgPSBwcmVjaW9fbWVkaW8sIGNvbG9yID0gaG90ZWwpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjMsIHNpemUgPSAxKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJibHVlIiwgImJsYWNrIikpICsNCiAgZ2VvbV9zbW9vdGgoc2UgPSBGLCBzaXplID0gMSkgKw0KICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAxODAsIGNvbG91ciA9ICJyZWQiKSArDQogIGxhYnModGl0bGUgPSAiVGllbXBvIGRlIEVzcGVyYSIpDQoNCmdncGxvdGx5KGdtKQ0KDQpgYGANCg0KRXN0YSBncsOhZmljYSBub3MgaW5kaWNhIHF1ZSByZXNlcnZhbmRvIGNvbiBtw6FzIGRlIDYgbWVzZXMgZGUgYW50aWNpcGFjacOzbiBzZSBjb25zaWd1ZW4gcHJlY2lvcyBtw6FzIGJham9zLCBzb2JyZSB0b2RvIGVuIGVsIGhvdGVsIHJlc29ydC4gSGF5IGRvcyBvYnNlcnZhY2lvbmVzIGNvbiBwcmVjaW9zIGRlIGNlcm8sIGxvIHF1ZSBwcm9iYWJsZW1lbnRlIHNlIGRlYmEgYSBlcnJvciBkZSBvcmlnZW4gbyBmdWVyb24gcHJvbW9jaW9uZXMgcXVlIHNlIG9ic2VxdWlhcm9uLg0KDQombmJzcDsNCg0KIyA8c3BhbiBzdHlsZT0iY29sb3I6cmdiKDAsIDAsIDIwNSkiPkN1cnZhIGRlIFBhcmV0bzwvc3Bhbj4NCg0KPGRpdiBjbGFzcz10ZXh0LWp1c3RpZnk+DQpBIGNvbnRpbnVhY2nDs24sIHNlIGdyYWZpY2Fyw6EgbGEgY3VydmEgZGUgUGFyZXRvIGRlIGxhIGZhY3R1cmFjacOzbi4gUmVjb3JkYW5kbyBxdWUgbGEgY3VydmEgZGUgcGFyZXRvIGNvbnNpc3RlIGVuIG1vc3RyYXIgbGEgcmVnbGEgZGUgcXVlLCBhcHJveGltYWRhbWVudGUsIGVsIDIwJSBkZSBsYXMgb2JzZXJ2YWNpb25lcyBhbmFsaXphZGFzIGdlbmVyYW4gZWwgODAlIGRlIHN1cyByZXN1bHRhZG9zLiBFc3RhIHJlZ2xhIG5vIGVzIGZpamEgeSBwdWVkZSB2YXJpYXIgbGEgZGlzdHJpYnVjacOzbiwgc2luIGVtYmFyZ28sIGVzIHVuIGJ1ZW4gaW5kaWNhZG9yIHF1ZSBwZXJtaXRlIGNvbm9jZXIgYSBsb3MgbWVqb3JlcyBjbGllbnRlcywgbG9zIG3DoXMgcmVudGFibGVzLg0KDQpFbnRvbmNlcywgaGFnYW1vcyB1bmEgY3VydmEgZGUgcGFyZXRvIGRlIGxhIGZhY3R1cmFjacOzbiBwb3IgcGHDrXMgcGFyYSBjb25vY2VyIGxhIGNvbnRyaWJ1Y2nDs24gZGUgY2FkYSB1bm8gYWwgbmVnb2NpbyBjb24gbGFzIHNpZ3VpZW50ZXMgY29uc2lkZXJhY2lvbmVzOiBzb2xvIGNyZWFybGEgcGFyYSBsYXMgcmVzZXJ2YWNpb25lcyBxdWUgbm8gZnVlcm9uIGNhbmNlbGFkYXMgeSwgdGFtYmnDqW4sIHNpbiBjb25zaWRlcmFyIGxhIGNhdGVnb3LDrWEgZGUgT1RST1MgcXVlIGNyZWFtb3MgYW50ZXJpb3JtZW50ZSAocG9yIHN1IG5pdmVsIG1hcmdpbmFsIGRlIGNvbnRyaWJ1Y2nDs24gZGUgY2FkYSBwYcOtcyBxdWUgaW50ZWdyYW4gZXNhIGNhdGVnb3LDrWEpLg0KPC9kaXY+DQoNCmBgYHtyfQ0KZGYgJT4lIA0KICBmaWx0ZXIoaXNfY2FuY2VsZWQgPT0gRkFMU0UgJiBjb3VudHJ5ICE9ICJPVFJPUyIpICU+JSANCiAgZ3JvdXBfYnkoY291bnRyeSkgJT4lIA0KICBzdW1tYXJpc2UodG90YWwgPSBzdW0oZmFjdHVyYWNpb25fcmVzZXJ2YSkpICU+JSANCiAgYXJyYW5nZShkZXNjKHRvdGFsKSkgJT4lIA0KICBtdXRhdGUocG9yYy5hY3VtLnZlbnRhcyA9IGN1bXN1bSh0b3RhbCkgLyBzdW0odG90YWwpLA0KICAgICAgICAgcmFua2luZy52ZW50YXMgPSByb3dfbnVtYmVyKGRlc2ModG90YWwpKSkgJT4lIA0KICBkcGx5cjo6c2VsZWN0KHJhbmtpbmcudmVudGFzLCBwb3JjLmFjdW0udmVudGFzKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IHJhbmtpbmcudmVudGFzLCB5ID0gcG9yYy5hY3VtLnZlbnRhcykpICsNCiAgZ2VvbV9saW5lKCkgKw0KICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSA2LCBjb2xvdXIgPSAicmVkIikgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLjc5LCBjb2xvdXIgPSAicmVkIikgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gYygxOjE1KSwgbGFiZWxzID0gYygiUFJUIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJHQlIiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRlJBIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkVTUCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJERVUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiSVJMIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIklUQSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJCRUwiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTkxEIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNIRSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJVU0EiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQlJBIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNOIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkFVVCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJTV0UiKSkgKw0KICBnZ3Bsb3QyOjphbm5vdGF0ZSgidGV4dCIsIA0KICAgICAgICAgICBsYWJlbCA9ICI4MCUtNiIsDQogICAgICAgICAgIHggPSA1LjUsIHkgPSAwLjgzLA0KICAgICAgICAgICBzaXplID0gMywgDQogICAgICAgICAgIGNvbG9yID0gIm5hdnlibHVlIikgKw0KICBsYWJzKHRpdGxlID0gIkN1cnZhIFBhcmV0byBGYWN0dXJhY2nDs24iKQ0KDQpgYGANCg0KPGRpdiBjbGFzcz10ZXh0LWp1c3RpZnk+DQpMYSBncsOhZmljYSBub3MgbXVlc3RyYSBsYSBjb250cmlidWNpw7NuLCBhIG5pdmVsIGRlIHBhw61zLCBxdWUgdGllbmVuIGxvcyBjbGllbnRlcy4gQWhvcmEsIHNpbiBjb25zaWRlcmFyIGEgUG9ydHVnYWwgKHBvciBzZXIgZWwgcGHDrXMgZW4gZG9uZGUgc2UgZW5jdWVudHJhbiBsb3MgaG90ZWxlcyksIHNvbiBzb2xvIDUgcGHDrXNlcyBsb3MgcXVlIGNvbnRyaWJ1eWVuIGEgZ2VuZXJhciBjYXNpIGVsIDgwJSBkZSBsYSBmYWN0dXJhY2nDs24gdG90YWw6IEdyYW4gQnJldGHDsWEsIEZyYW5jaWEsIEVzcGHDsWEsIEFsZW1hbmlhIGUgSXJsYW5kYS4gU2FiaWVuZG8gZXN0byBwb2RlbW9zIHJlY29tZW50YXIgZm9jYWxpemFyIHByb21vY2lvbmVzIHkvbyBjYW1wYcOxYXMgcGFyYSBsb3MgdHVyaXN0YXMgZGUgZXNhcyBuYWNpb25hbGlkYWRlcy4NCjwvZGl2Pg0KDQombmJzcDsNCg0KIyA8c3BhbiBzdHlsZT0iY29sb3I6cmdiKDAsIDAsIDIwNSkiPkNvbmNsdXNpb25lczwvc3Bhbj4NCg0KPGRpdiBjbGFzcz10ZXh0LWp1c3RpZnk+DQpEZWwgYW7DoWxpc2lzIGFudGVyaW9yLCBwb2RlbW9zIHNlw7FhbGFyIGxhcyBzaWd1ZW50ZXMgY29uY2x1c2lvbmVzOg0KDQoqIENvbW8gdG9kYSBlbXByZXNhIHF1ZSBwZXJ0ZW5lY2UgYWwgZ2lybyBkZWwgdHVyaXNtbyBzdXMgYWN0aXZpZGFkZXMgcHJlc2VudGFuIHRlbXBvcmFkYXMgZGUgYWx0YSB5IGJhamEgZGVtYW5kYSwgZXMgZGVjaXIsIHNlIHZlbiBhZmVjdGFkYXMgcG9yIGxhIGVzdGFjaW9uYWxpZGFkIHByb3BpYSBkZSBzdSBzZWN0b3IuIEVuIGVsIGNhc28gZGUgbG9zIGhvdGVsZXMsIHNlIGRldGVjdMOzIGVzdGFjaW9uYWxpZGFkIChjb25zaWRlcmFuZG8gc29sbyBlbCBhw7FvIDIwMTYgcG9yIGxhIGZhbHRhIGRlIG3DoXMgaW5mb3JtYWNpw7NuIGhpc3TDs3JpY2EpLg0KKiBFbCBob3RlbCBxdWUgbcOhcyBmYWN0dXJhY2nDs24gdGllbmUgZXMgZWwgZGUgY2l1ZGFkLCBwb3IgbG8gcXVlLCBzaSBlbCBvYmpldGl2byBlcyBpbmNyZW1lbnRhciBsYSBmYWN0dXJhY2nDs24gdG90YWwgZGUgZXN0YSBjYWRlbmEgZGUgaG90ZWxlcywgc2UgZGViZW4gZGUgZW5mb2NhciBsb3MgcmVjdXJzb3MgZW4gbWVqb3JhciBsYSBmYWN0dXJhY2nDs24gZGVsIGhvdGVsIHJlc29ydCBhIHRyYXbDqXMgZGUgY2FtcGHDsWFzIHBhcmEgbG9ncmFyIHF1ZSBlbCBwcm9tZWRpbyBkZSBub2NoZXMgcG9yIHJlc2VydmEgYXVtZW50ZSBhc8OtIGNvbW8gYW5hbGl6YXIgbGEgcG9zaWJpbGlkYWQgZGUgaW5jcmVtZW50YXIgbG9zIHByZWNpb3MgcHJvbWVkaW8gc29icmUgdG9kbyBlbiB0ZW1wb3JhZGEgZGUgYWx0YSBkZW1hbmRhLg0KKiBMYSBtYXlvciBwYXJ0ZSBkZSBsYSBmYWN0dXJhY2nDs24gcHJvdmllbmUgZGVsIHNlZ21lbnRvIE9ubGluZSBlbiBhbWJvcyB0aXBvcyBkZSBob3RlbCwgZW4gY2FtYmlvLCBlbCBzZWdtZW50byBDb3Jwb3JhdGUgZXMgZWwgcXVlIG1lbm9yIGNvbnRyaWJ1eWUuDQoqIEVsIG1lcmNhZG8gZXh0ZXJubyAoc2luIGNvbnNpZGVyYXIgYSBQb3J0dWdhbCksIHNvbiBkaWZlcmVudGVzLCB5YSBxdWUgbWllbnRyYXMgZW4gZWwgaG90ZWwgZGUgY2l1ZGFkIGxvcyBmcmFuY2VzZXMgeSBhbGVtYW5lcyBzb24gbG9zIGNsaWVudGVzIHF1ZSBtw6FzIGhhY2VuIHJlc2VydmFzLCBlbiBlbCBob3RlbCByZXNvcnQgc29uIGxvcyBicml0w6FuaWNvcyB5IGVzcGHDsW9sZXMgbG9zIHF1ZSBtw6FzIHNlIGhvc3BlZGFuLg0KKiBTZSBwcmVzZW50YSBlc3RhY2lvbmFsaWRhZCBlbiBlbCBwcmVjaW8gZGUgbGEgcmVzZXJ2YSwgcHVlcyBlcyBlbiBlbCBwZXJpb2RvIGNvbXByZW5kaWRvIGVudHJlIGxhcyBzZW1hbmFzIDI5IGEgMzUgcXVlIGxvcyBwcmVjaW9zIHNvbiBtw6FzIGFsdG9zIHkgbWVub3IgZW4gZWwgcmVzdG8gZGVsIGHDsW8uDQo8L2Rpdj4NCg0KJm5ic3A7DQoNCiMgPHNwYW4gc3R5bGU9ImNvbG9yOnJnYigwLCAwLCAyMDUpIj5JbnNpZ2h0czwvc3Bhbj4NCg0KPGRpdiBjbGFzcz10ZXh0LWp1c3RpZnk+DQoqIExhIHRlbXBvcmFkYSBkZSBhbHRhIGRlbWFuZGEgc2UgcHJlc2VudGEgZW4gbG9zIG1lc2VzIGRlIG1heW8geSBvY3R1YnJlIHksIHBvciBlbCBjb250cmFyaW8sIGxhIHRlbXBvcmFkYSBkZSBtZW5vciBkZW1hbmRhIGVzIGVuIGVsIHBlcmlvZG8gZGUgZGljaWVtYnJlIGEgZmVicmVyby4NCiogRWwgaG90ZWwgcmVzb3J0IHRpZW5lIHVuIHBvY28gbcOhcyBkZSBkZW1hbmRhIGhhc3RhIGVsIHByaW1lciB0cmltZXN0cmUgZGVsIGHDsW8gbWllbnRyYXMgcXVlIGVuIGVsIGhvdGVsIGRlIGNpdWRhZCBsYSBkZW1hbmRhIGVzIG1heW9yIGVuIGxvcyBtZXNlcyBkZSBqdW5pbyBzZXB0aWVtYnJlIHkgb2N0dWJyZS4NCiogU2UgcG9kcsOtYW4gaW5jcmVtZW50YXIgbG9zIHByZWNpb3MgZW4gZWwgaG90ZWwgcmVzb3J0IGVuIHRlbXBvcmFkYSBhbHRhIChpbmljaW8gZGUgYcOxbyksIHlhIHF1ZSDDqXN0b3Mgc2UgbWFudGllbmVuLCBlbiBnZW5lcmFsLCBjb25zdGFudGVzLiBTZSB0ZW5kcsOtYSBxdWUgYnVzY2FyIHVuIHByZWNpbyDDs3B0aW1vIHBhcmEgZXNlIHBlcmlvZG8gZGUgdGllbXBvLg0KKiBMYSBtYXlvciBwYXJ0ZSBkZSBsYSBmYWN0dXJhY2nDs24gcHJvdmllbmUgZGVsIHNlZ21lbnRvIHBhcmVqYXMgc2luIGhpam9zOiBlbiBlbCBob3RlbCByZXNvcnQgZXF1aXZhbGUgYWwgNzAlIHkgZW4gZWwgaG90ZWwgZGUgY2l1ZGFkIGFsIDY1JS4NCiogRXhpc3RlIG1hcmdlbiBkZSBtYW5pb2JyYSBwYXJhIG9wdGltaXphciBsYSBwb2zDrXRpY2EgZGUgcHJlY2lvcywgeWEgcXVlIGxhcyBjdXJ2YXMgZGUgZGVtYW5kYSB0aWVuZW4gZm9ybWEgYmltb2RhbCAoZG9zIHBpY29zIGR1cmFudGUgZWwgcGVyaW9kbyBkZSBhbsOhbGlzaXMpIHksIGVuIGNhbWJpbywgbGEgY3VydmEgZGUgcHJlY2lvcyBzb2xvIHByZXNlbnRhIHVuIHBpY28uIExvIGFudGVpb3IsIHJlY29yZGFuZG8gcXVlIGxhIGzDs2dpY2EgZGUgbmVnb2NpbyBpbmRpY2Fyw61hIHF1ZSBlbiDDqXBvY2EgZGUgYWx0YSBkZW1hbmRhIHNlIHB1ZWRlbiBhdW1lbnRhciBsb3MgcHJlY2lvcyBidXNjYW5kbyBtYXhpbWl6YXIgZ2FuYW5jaWFzLg0KKiBFcyBlbiBlbCBob3RlbCByZXNvcnQgZG9uZGUgZWwgcGVyaW9kbyBkZSBhbHRhIGRlbWFuZGEgbm8gc2UgY29ycmVzcG9uZGUgY29uIHByZWNpb3MgcHJvbWVkaW8gYWx0b3MsIHBvciBsbyB0YW50bywgZXMgZW4gZXN0ZSBob3RlbCBkb25kZSBzZSB0ZW5kcsOtYSBxdWUgYXJyYW5jYXIgdW4gcGlsb3RvIGRlIG9wdGltaXphY2nDs24gZGUgcHJlY2lvcy4NCiogQSBuaXZlbCBkZSBwYcOtcyB5IHNpbiBjb250YXIgZWwgbWVyY2FkbyBsb2NhbCAoUG9ydHVnYWwpLCBzb24gY2luY28gcGHDrXNlcyBsb3MgcXVlIGNvbnRyaWJ1eWVuIGEgZ2VuZXJhciBjYXNpIGVsIDgwJSBkZSBsYSBmYWN0dXJhY2nDs24gdG90YWw6IEdyYW4gQnJldGHDsWEsIEZyYW5jaWEsIEVzcGHDsWEsIEFsZW1hbmlhIGUgSXJsYW5kYS4gDQo8L2Rpdj4NCg0K