Problema

Una empresa inmobiliaria líder en una gran ciudad está buscando comprender en profundidad el mercado de viviendas urbanas para tomar decisiones estratégicas más informadas. La empresa posee una base de datos extensa que contiene información detallada sobre diversas propiedades residenciales disponibles en el mercado. Se requiere realizar un análisis holístico de estos datos para identificar patrones, relaciones y segmentaciones relevantes que permitan mejorar la toma de decisiones en cuanto a la compra, venta y valoración de propiedades.

Retos:

El reto principal consisten en realizar un análisis integral y multidimensional de la base de datos para obtener una comprensión del mercado inmobiliario urbano. Se requiere aplicar diversas técnicas de análisis de datos, incluyendo:

  1. Análisis de Componentes Principales: Reducir la dimensionalidad del conjunto de datos y visualizar la estructura de las variables en componentes principales para identificar características clave que influyen en la variación de precios y preferencias del mercado.

  2. Análisis de Conglomerados: Agrupar las propiedades residenciales en segmentos homogéneos con características similares para entender las dinámicas y demandas específicas en diferentes partes de la ciudad y en diferentes estratos socioeconómicos.

  3. Análisis de Correspondencia : Examinar la relación entre las variables categóricas (tipo de vivienda, zona y barrio) y las variables numéricas (precio, área construida, número de parqueaderos, baños, habitaciones) para identificar patrones de comportamiento del mercado inmobiliario.

  4. Visualización de resultados: Presentar gráficos, mapas y otros recursos visuales para comunicar los hallazgos de manera clara y efectiva a la dirección de la empresa.

El informe final debe incluir análisis detallados de los resultados obtenidos, las conclusiones clave y las recomendaciones específicas para guiar las decisiones estratégicas de la empresa inmobiliaria. Se espera que este análisis de datos proporcione ventajas competitivas en el mercado, optimizando la inversión y maximizando los beneficios en un entorno altamente competitivo y en constante cambio.

Desarrollo

head(vivienda)

EDA

El conjunto de datos de ofertas de viviendas en OLX presenta una estructura compuesta por 3 variables categóricas (zona, piso, tipo, barrio) y 7 variables numéricas. En este análisis, optaremos por descartar la variable “id”. La exclusión de esta variable se justifica en virtud de la naturaleza del análisis propuesto, que se enfocará en la identificación de patrones mediante un enfoque no supervisado. En este contexto, la variable “id” carece de significado semántico y contribución al análisis.

set.seed(42) # universe answer
housing = paqueteDAT::vivienda
housing = housing[rowSums(is.na(housing)) < ncol(housing) - 1, ] # remove rows with all NA features
housing = housing %>% mutate(piso = ifelse(is.na(piso), NA, as.numeric(housing$piso)))

NAs

percentage_na = nrow(housing[apply(is.na(housing), 1, any), ]) / nrow(housing) * 100

pie_na_data = data.frame(
  Status = c("NA", "Complete"),
  Percentage = c(percentage_na, 100 - percentage_na)
)

plot = ggplot(pie_na_data, aes(x = "", y = Percentage, fill = Status)) +
  geom_bar(stat = "identity", width = 1) +
  coord_polar(theta = "y") +
  labs(x = NULL, y = NULL, fill = "Status") +
  theme_void() +
  geom_text(aes(label = sprintf("%.1f%%", Percentage)), position = position_stack(vjust = 0.5))

print(plot)

El diagrama de pastel ilustra cómo cerca del 50% de los datos presentan al menos una característica con valores faltantes (NA), lo que introduce una capa adicional de complejidad en la implementación de los algoritmos. Es importante señalar que gran parte de los valores faltantes se concentran en la variable “Piso”. Esta variable reviste una gran relevancia, ya que desde una perspectiva de negocio, se reconoce su impacto significativo en el precio de las propiedades. Un ejemplo concreto es la variación en el valor de un apartamento idéntico, pero ubicado en pisos diferentes, que puede traducirse en diferencias de millones de pesos.

Dado este contexto, se tomará la decisión de abordar la imputación de estos valores faltantes en la variable “Piso”. Esta elección se fundamenta en la importancia de la variable en términos de influencia en los resultados de negocio. La corrección de estos valores faltantes se convierte en una prioridad, ya que su integridad mejorará la calidad y confiabilidad de los análisis posteriores y de los algoritmos que se implementarán. Este proceso de imputación se llevará a cabo con la finalidad de evitar distorsiones en los resultados y asegurar una representación más precisa de la relación entre las características y los precios de las propiedades.

Resumen

Variable Stats / Values Freqs (% of Valid) Graph Missing
id [numeric]
Mean (sd) : 4160 (2401.6)
min ≤ med ≤ max:
1 ≤ 4160 ≤ 8319
IQR (CV) : 4159 (0.6)
8319 distinct values 0 (0.0%)
zona [factor]
1. Zona Centro
2. Zona Norte
3. Zona Oeste
4. Zona Oriente
5. Zona Sur
124(1.5%)
1920(23.1%)
1198(14.4%)
351(4.2%)
4726(56.8%)
0 (0.0%)
piso [numeric]
Mean (sd) : 3.8 (2.6)
min ≤ med ≤ max:
1 ≤ 3 ≤ 12
IQR (CV) : 3 (0.7)
12 distinct values 2635 (31.7%)
estrato [numeric]
Mean (sd) : 4.6 (1)
min ≤ med ≤ max:
3 ≤ 5 ≤ 6
IQR (CV) : 1 (0.2)
3:1453(17.5%)
4:2129(25.6%)
5:2750(33.1%)
6:1987(23.9%)
0 (0.0%)
preciom [numeric]
Mean (sd) : 433.9 (328.7)
min ≤ med ≤ max:
58 ≤ 330 ≤ 1999
IQR (CV) : 320 (0.8)
539 distinct values 0 (0.0%)
areaconst [numeric]
Mean (sd) : 174.9 (143)
min ≤ med ≤ max:
30 ≤ 123 ≤ 1745
IQR (CV) : 149 (0.8)
652 distinct values 0 (0.0%)
parqueaderos [numeric]
Mean (sd) : 1.8 (1.1)
min ≤ med ≤ max:
1 ≤ 2 ≤ 10
IQR (CV) : 1 (0.6)
1:3155(47.0%)
2:2475(36.8%)
3:520(7.7%)
4:384(5.7%)
5:68(1.0%)
6:68(1.0%)
7:18(0.3%)
8:17(0.3%)
9:4(0.1%)
10:8(0.1%)
1602 (19.3%)
banios [numeric]
Mean (sd) : 3.1 (1.4)
min ≤ med ≤ max:
0 ≤ 3 ≤ 10
IQR (CV) : 2 (0.5)
11 distinct values 0 (0.0%)
habitaciones [numeric]
Mean (sd) : 3.6 (1.5)
min ≤ med ≤ max:
0 ≤ 3 ≤ 10
IQR (CV) : 1 (0.4)
11 distinct values 0 (0.0%)
tipo [factor]
1. Apartamento
2. Casa
5100(61.3%)
3219(38.7%)
0 (0.0%)
barrio [factor]
1. 20 de julio
2. 3 de julio
3. acopi
4. agua blanca
5. aguablanca
6. aguacatal
7. alameda
8. alameda del rio
9. alameda del río
10. alamos
[ 426 others ]
3(0.0%)
1(0.0%)
158(1.9%)
1(0.0%)
2(0.0%)
109(1.3%)
16(0.2%)
1(0.0%)
2(0.0%)
14(0.2%)
8012(96.3%)
0 (0.0%)
longitud [numeric]
Mean (sd) : -76.5 (0)
min ≤ med ≤ max:
-76.6 ≤ -76.5 ≤ -76.5
IQR (CV) : 0 (0)
2928 distinct values 0 (0.0%)
latitud [numeric]
Mean (sd) : 3.4 (0)
min ≤ med ≤ max:
3.3 ≤ 3.4 ≤ 3.5
IQR (CV) : 0.1 (0)
3679 distinct values 0 (0.0%)

Generated by summarytools 1.0.1 (R version 4.2.2)
2023-08-13

Es evidente que las variables numéricas no exhiben una distribución exhaustivamente normal en la mayoría de las instancias. Esta observación sugiere que no es factible inferir conclusiones semánticas con respecto a la totalidad de la población, ya que la representación gráfica indica que no se ajusta al teorema del límite central. Dicha falta de normalidad en la distribución de los datos es un aspecto crucial que debe ser considerado al interpretar tanto el análisis como los resultados subsiguientes. Esta circunstancia puede influir en las estrategias de modelado y en las técnicas estadísticas que se empleen, ya que las suposiciones asociadas a la distribución normal pueden no ser válidas en este contexto.

Completación Missign Values

En el conjunto de datos, hemos identificado la presencia de registros con valores igual a 0 en las variables correspondientes a “habitaciones” y “baños”. Una revisión de los estratos nos ha permitido observar que estos valores solo abarcan el rango de 3 a 6. Dado que los tipos de propiedades registradas son viviendas y apartamentos, resulta poco razonable que una propiedad carezca de al menos una habitación o un baño.

Por lo tanto, abordaremos estas discrepancias considerándolas como valores faltantes (missing values). Esto nos permitirá incluirlas en el proceso de imputación de datos faltantes, con el propósito de lograr una corrección más completa y precisa.

Optaremos por la imputación de los valores faltantes utilizando el algoritmo KNN, en vista de que los datos exhiben un comportamiento no lineal que no se ajusta al enfoque de la regresión lineal. Es esencial destacar que, a pesar de la viabilidad de la regresión, su aplicación sin cumplir los supuestos podría introducir distorsiones en las interpretaciones resultantes. Si bien alternativas más avanzadas, como el Deep Learning, y opciones más sencillas, como el cálculo del promedio global, están disponibles, hemos optado por el empleo de KNN.

La selección de KNN se respalda en la perspectiva de obtener resultados más efectivos en comparación con la mera estimación promedio. Esta elección encuentra sustento en diversos elementos, entre los cuales destaca la Ley 675 del 2001, que aboga por la homogeneidad de las propiedades en régimen de propiedad horizontal y en los entornos residenciales. Es crucial mantener presente que esta determinación implica un supuesto que abordaremos en el curso del proceso.

Linealidad

En el gráfico que relaciona el área con el precio, es evidente que para un valor de área pueden existir múltiples precios. Esta observación demuestra que no existe una relación funcional clara entre estas dos dimensiones. Para una evaluación más completa del enfoque de regresión lineal, podemos realizar una rápida revisión utilizando el test RESET, considerando un análisis más profundo con múltiples dimensiones.

linear_model <- lm(preciom ~ areaconst + estrato + banios + habitaciones + latitud + longitud, data = housing)
print(resettest(linear_model, power = 2), method = 'render')
## 
##  RESET test
## 
## data:  linear_model
## RESET = 54.196, df1 = 1, df2 = 8311, p-value = 1.988e-13

Como habíamos previsto, el enfoque de regresión no demuestra ser efectivo. Dado que el valor p se acerca a cero, rechazamos la hipótesis nula que sostiene que los datos están adecuadamente modelados mediante regresión. Esta observación fortalece aún más la sugerencia de considerar la adopción del algoritmo KNN.

KNN

housing = housing %>% 
  mutate(banios = ifelse(banios == 0, NA, banios)) %>% 
  mutate(habitaciones = ifelse(habitaciones == 0, NA, habitaciones))

housing = kNN(housing, variable = c("piso", "banios", "parqueaderos", "habitaciones"))
any(is.na(housing))
## [1] FALSE

Patrones

map = leaflet() %>%
  addTiles() %>%
  addHeatmap(data = housing, lng = ~longitud, lat = ~latitud, blur = 5, max = 5, radius = 5)

map

Resulta curioso observar que existe una amplia cobertura en la zona de Cali; sin embargo, esta cobertura solo abarca los estratos 3 al 6. Resulta evidente la presencia de áreas pertenecientes a los estratos 1 y 2 en distintas zonas, lo que sugiere la existencia de posibles inconsistencias en los datos recolectados.

Correlation

Dado que planeamos emplear algoritmos sensibles a la escala de unidades, los cuales operan a través de funciones de distancia y comparaciones susceptibles a ser influenciadas por rangos extensos, resulta necesario llevar a cabo una normalización de los datos. Este procedimiento será aplicado exclusivamente a las variables numéricas.

housing_num = housing[, c("piso","estrato","preciom","areaconst","parqueaderos","habitaciones","longitud","latitud")]
housing_scaled = scale(housing_num)
print(cor(housing_scaled[, c("piso","estrato","areaconst","parqueaderos","habitaciones","longitud","latitud")], housing_scaled[, c("preciom")]), method = "render")
##                     [,1]
## piso         -0.02428561
## estrato       0.60980664
## areaconst     0.68735196
## parqueaderos  0.69892075
## habitaciones  0.27510149
## longitud     -0.34358822
## latitud      -0.11566757

Al analizar las correlaciones entre las variables, se evidencia una fuerte asociación en todos los casos, exceptuando la variable que hace referencia al tipo de piso, la cual muestra una correlación menos significativa con respecto al precio. Estos hallazgos nos permiten llegar a las siguientes conclusiones:

  • La alta correlación observada entre la mayoría de las variables y el precio indica que estas desempeñan un papel crucial en la determinación de los valores de las propiedades. Específicamente, factores como el tamaño del inmueble, la ubicación y la cantidad de habitaciones presentan una influencia directa en el precio.

  • La baja correlación entre el tipo de piso y el precio, que puede haber sido influenciada por el hecho de que esta variable contenía casi un 50% de valores faltantes, sugiere que este atributo podría tener un impacto menos decisivo en la valoración de las propiedades. La presencia de un gran número de valores faltantes puede haber distorsionado la relación real entre el tipo de piso y el precio, lo que contribuyó a este resultado.

K-Means

Dado que no disponemos de un conocimiento intrínseco de las agrupaciones presentes, es imperativo explorar diversos enfoques de agrupamiento. Por lo tanto, procederemos a generar resultados de agrupamientos en un rango de 1 a 10 grupos. Posteriormente, emplearemos el método del codo para identificar una cantidad de grupos sensata que permita iniciar un análisis semántico de los patrones presentes en los datos.

Este proceso nos permitirá obtener una comprensión más profunda de la estructura subyacente de los datos y facilitará la identificación de relaciones y tendencias significativas entre las observaciones. La elección del número óptimo de grupos a través del método del codo contribuirá a una segmentación más precisa y significativa de los datos, lo que a su vez facilitará la interpretación y aplicación de los resultados obtenidos.

Es evidente que a partir de K=2, la variabilidad entre los grupos disminuye de manera significativa. A medida que el valor de K continúa aumentando, la reducción en la variabilidad se vuelve marginal. Por lo tanto, tenemos la opción de seleccionar un valor de K en el rango de 2 a 6 para una representación adecuada de los grupos. En este contexto, optaremos por el valor intermedio de 4, que se encuentra equidistante dentro de este intervalo. Esta elección nos permite alcanzar un compromiso óptimo entre la simplicidad de la segmentación y la captura de la estructura latente en los datos.

## [1] "#FF0000" "#80FF00" "#00FFFF" "#8000FF"

Al observar los clústeres y ubicarlos en el mapa, se identifica que no existe una relación semántica consistente entre los grupos generados y su ubicación geográfica. Aunque se pueden percibir ciertos indicios de agrupación por zona, al repetir el proceso sin considerar las variables de latitud y longitud, los grupos en términos de ubicación se vuelven más ambiguos. Esto nos lleva a identificar la ausencia de un patrón directo con respecto a la zona geográfica. Por consiguiente, estos resultados sugieren que otros factores podrían estar influyendo en la formación de los clústeres.

Ingeniería de Variables (PCA)

housing_pca <- prcomp(housing_scaled, scale = TRUE)
housing_pca_df <- data.frame(housing_pca$x, cluster = housing_cluster$cluster)
ggplot(housing_pca_df, aes(x = PC1, y = PC2, color = factor(cluster))) + geom_point() + labs(x = "PC1", y = "PC2")

tsne = Rtsne(housing_scaled, perplexity = 30, check_duplicates = FALSE)
plot(tsne$Y, col = "black", bg= housing_cluster$cluster, pch = 21, cex = 1)

A través del gráfico de Análisis de Componentes Principales (PCA), es posible distinguir claramente las cuatro agrupaciones, lo cual coincide con el objetivo de K-Means de minimizar la varianza entre los grupos generados. Sin embargo, es importante resaltar que estos grupos carecen de significado semántico y son más bien constructos matemáticos o abstractos. Esto se debe a que no se puede establecer una asociación contextual que permita etiquetar o categorizar adecuadamente dichos grupos según el dominio de estudio.

Podemos observar que en el gráfico de Análisis de Componentes Principales (PCA) no se presenta una relación lineal clara entre los datos, lo cual concuerda con las observaciones iniciales acerca de la falta de linealidad en el conjunto de datos. Por esta razón, hemos decidido examinar la estructura de los datos utilizando Rtsne, una técnica que permite, al igual que PCA, mapear los datos en un espacio de menor dimensión mientras intenta preservar las relaciones de vecindad entre ellos. Sin embargo, resulta evidente que incluso esta herramienta no logra discriminar los grupos de manera precisa. Este hecho comienza a sugerir la posible insuficiencia de características (features) para lograr una discriminación más efectiva entre los grupos, lo que a su vez afecta la identificación de patrones dentro de estos mismos grupos.

Este hallazgo subraya la necesidad de considerar una exploración más exhaustiva de las características utilizadas en el análisis y la posibilidad de incorporar elementos adicionales que puedan capturar de manera más precisa las diferencias y relaciones entre los grupos. Este enfoque más completo podría ayudar a revelar patrones y relaciones que actualmente no están siendo detectados debido a la limitación de las características utilizadas hasta ahora.

Esta percepción se refuerza al examinar los resultados del resumen del cluster 1 en relación con el rango de valores presentes en las variables que conforman este grupo. Una vez más, esta observación subraya la falta de un patrón semántico discernible. En este sentido, es fundamental reconocer que la interpretación y aplicación de estos grupos requieren un enfoque más profundo y contextual para comprender si existen factores subyacentes que puedan aportar sentido a estos agrupamientos desde una perspectiva más allá de lo meramente modelado.

Cluster 1
summary(housing[housing$cluster == 1,])
##        id                 zona          piso           estrato     
##  Min.   :   4   Zona Centro :  2   Min.   : 1.000   Min.   :3.000  
##  1st Qu.:4761   Zona Norte  :134   1st Qu.: 2.000   1st Qu.:6.000  
##  Median :5876   Zona Oeste  :373   Median : 2.000   Median :6.000  
##  Mean   :5727   Zona Oriente:  2   Mean   : 3.325   Mean   :5.751  
##  3rd Qu.:7024   Zona Sur    :698   3rd Qu.: 4.000   3rd Qu.:6.000  
##  Max.   :8319                      Max.   :12.000   Max.   :6.000  
##                                                                    
##     preciom       areaconst       parqueaderos        banios      
##  Min.   : 190   Min.   :  50.0   Min.   : 1.000   Min.   : 1.000  
##  1st Qu.: 800   1st Qu.: 250.0   1st Qu.: 2.000   1st Qu.: 4.000  
##  Median : 980   Median : 320.0   Median : 3.000   Median : 5.000  
##  Mean   :1055   Mean   : 376.5   Mean   : 3.493   Mean   : 4.868  
##  3rd Qu.:1280   3rd Qu.: 450.0   3rd Qu.: 4.000   3rd Qu.: 6.000  
##  Max.   :1999   Max.   :1745.0   Max.   :10.000   Max.   :10.000  
##                                                                   
##   habitaciones             tipo                     barrio       longitud     
##  Min.   : 1.000   Apartamento:437   ciudad jardín      :239   Min.   :-76.59  
##  1st Qu.: 3.000   Casa       :772   pance              :214   1st Qu.:-76.55  
##  Median : 4.000                     santa teresita     :122   Median :-76.54  
##  Mean   : 4.185                     normandía          : 60   Mean   :-76.54  
##  3rd Qu.: 5.000                     aguacatal          : 46   3rd Qu.:-76.53  
##  Max.   :10.000                     parcelaciones pance: 45   Max.   :-76.46  
##                                     (Other)            :483                   
##     latitud       piso_imp       banios_imp      parqueaderos_imp
##  Min.   :3.333   Mode :logical   Mode :logical   Mode :logical   
##  1st Qu.:3.359   FALSE:777       FALSE:1200      FALSE:1121      
##  Median :3.404   TRUE :432       TRUE :9         TRUE :88        
##  Mean   :3.404                                                   
##  3rd Qu.:3.451                                                   
##  Max.   :3.493                                                   
##                                                                  
##  habitaciones_imp    cluster 
##  Mode :logical    Min.   :1  
##  FALSE:1201       1st Qu.:1  
##  TRUE :8          Median :1  
##                   Mean   :1  
##                   3rd Qu.:1  
##                   Max.   :1  
## 
Cluster 2
summary(housing[housing$cluster == 2,])
##        id                 zona          piso           estrato    
##  Min.   :   2   Zona Centro : 64   Min.   : 1.000   Min.   :3.00  
##  1st Qu.:1852   Zona Norte  :309   1st Qu.: 2.000   1st Qu.:3.00  
##  Median :3884   Zona Oeste  : 72   Median : 2.000   Median :4.00  
##  Mean   :3915   Zona Oriente:152   Mean   : 2.151   Mean   :4.04  
##  3rd Qu.:5854   Zona Sur    :555   3rd Qu.: 3.000   3rd Qu.:5.00  
##  Max.   :8318                      Max.   :11.000   Max.   :6.00  
##                                                                   
##     preciom         areaconst       parqueaderos       banios      
##  Min.   : 127.0   Min.   :  70.0   Min.   :1.000   Min.   : 1.000  
##  1st Qu.: 330.0   1st Qu.: 214.0   1st Qu.:1.000   1st Qu.: 3.000  
##  Median : 425.0   Median : 280.0   Median :2.000   Median : 4.000  
##  Mean   : 453.3   Mean   : 297.1   Mean   :1.708   Mean   : 4.217  
##  3rd Qu.: 550.0   3rd Qu.: 360.0   3rd Qu.:2.000   3rd Qu.: 5.000  
##  Max.   :1800.0   Max.   :1440.0   Max.   :6.000   Max.   :10.000  
##                                                                    
##   habitaciones             tipo               barrio       longitud     
##  Min.   : 3.000   Apartamento:  36   la flora    : 55   Min.   :-76.59  
##  1st Qu.: 5.000   Casa       :1116   el limonar  : 41   1st Qu.:-76.54  
##  Median : 6.000                      acopi       : 35   Median :-76.53  
##  Mean   : 6.094                      el caney    : 32   Mean   :-76.53  
##  3rd Qu.: 7.000                      ciudad 2000 : 26   3rd Qu.:-76.52  
##  Max.   :10.000                      san fernando: 26   Max.   :-76.46  
##                                      (Other)     :937                   
##     latitud       piso_imp       banios_imp      parqueaderos_imp
##  Min.   :3.338   Mode :logical   Mode :logical   Mode :logical   
##  1st Qu.:3.404   FALSE:683       FALSE:1132      FALSE:840       
##  Median :3.431   TRUE :469       TRUE :20        TRUE :312       
##  Mean   :3.430                                                   
##  3rd Qu.:3.451                                                   
##  Max.   :3.494                                                   
##                                                                  
##  habitaciones_imp    cluster 
##  Mode :logical    Min.   :2  
##  FALSE:1123       1st Qu.:2  
##  TRUE :29         Median :2  
##                   Mean   :2  
##                   3rd Qu.:2  
##                   Max.   :2  
## 
Cluster 3
summary(housing[housing$cluster == 3,])
##        id                 zona           piso           estrato     
##  Min.   : 640   Zona Centro :   8   Min.   : 1.000   Min.   :3.000  
##  1st Qu.:2979   Zona Norte  : 307   1st Qu.: 2.000   1st Qu.:4.000  
##  Median :4907   Zona Oeste  : 720   Median : 3.000   Median :5.000  
##  Mean   :4855   Zona Oriente:   7   Mean   : 4.066   Mean   :4.838  
##  3rd Qu.:6783   Zona Sur    :3083   3rd Qu.: 5.000   3rd Qu.:5.000  
##  Max.   :8316                       Max.   :12.000   Max.   :6.000  
##                                                                     
##     preciom         areaconst      parqueaderos       banios     
##  Min.   :  78.0   Min.   : 40.0   Min.   :1.000   Min.   :1.000  
##  1st Qu.: 220.0   1st Qu.: 75.0   1st Qu.:1.000   1st Qu.:2.000  
##  Median : 300.0   Median :101.0   Median :1.000   Median :3.000  
##  Mean   : 339.7   Mean   :115.6   Mean   :1.466   Mean   :2.747  
##  3rd Qu.: 430.0   3rd Qu.:141.0   3rd Qu.:2.000   3rd Qu.:3.000  
##  Max.   :1100.0   Max.   :605.0   Max.   :4.000   Max.   :7.000  
##                                                                  
##   habitaciones           tipo                 barrio        longitud     
##  Min.   :1.00   Apartamento:3336   valle del lili: 873   Min.   :-76.59  
##  1st Qu.:3.00   Casa       : 789   ciudad jardín : 263   1st Qu.:-76.54  
##  Median :3.00                      pance         : 181   Median :-76.53  
##  Mean   :3.05                      el caney      : 157   Mean   :-76.53  
##  3rd Qu.:3.00                      la hacienda   : 153   3rd Qu.:-76.52  
##  Max.   :6.00                      el ingenio    : 151   Max.   :-76.50  
##                                    (Other)       :2347                   
##     latitud       piso_imp       banios_imp      parqueaderos_imp
##  Min.   :3.333   Mode :logical   Mode :logical   Mode :logical   
##  1st Qu.:3.371   FALSE:3004      FALSE:4113      FALSE:3588      
##  Median :3.391   TRUE :1121      TRUE :12        TRUE :537       
##  Mean   :3.399                                                   
##  3rd Qu.:3.425                                                   
##  Max.   :3.493                                                   
##                                                                  
##  habitaciones_imp    cluster 
##  Mode :logical    Min.   :3  
##  FALSE:4109       1st Qu.:3  
##  TRUE :16         Median :3  
##                   Mean   :3  
##                   3rd Qu.:3  
##                   Max.   :3  
## 
Cluster 4
summary(housing[housing$cluster == 4,])
##        id                 zona           piso           estrato     
##  Min.   :   1   Zona Centro :  50   Min.   : 1.000   Min.   :3.000  
##  1st Qu.: 540   Zona Norte  :1170   1st Qu.: 2.000   1st Qu.:3.000  
##  Median :1137   Zona Oeste  :  33   Median : 3.000   Median :4.000  
##  Mean   :1716   Zona Oriente: 190   Mean   : 3.829   Mean   :3.809  
##  3rd Qu.:2767   Zona Sur    : 390   3rd Qu.: 5.000   3rd Qu.:5.000  
##  Max.   :7108                       Max.   :12.000   Max.   :6.000  
##                                                                     
##     preciom        areaconst       parqueaderos       banios      
##  Min.   : 58.0   Min.   : 30.00   Min.   :1.000   Min.   : 1.000  
##  1st Qu.:135.0   1st Qu.: 62.00   1st Qu.:1.000   1st Qu.: 2.000  
##  Median :198.0   Median : 83.00   Median :1.000   Median : 2.000  
##  Mean   :223.7   Mean   : 98.79   Mean   :1.191   Mean   : 2.169  
##  3rd Qu.:290.0   3rd Qu.:120.00   3rd Qu.:1.000   3rd Qu.: 3.000  
##  Max.   :880.0   Max.   :550.00   Max.   :5.000   Max.   :10.000  
##                                                                   
##   habitaciones            tipo                        barrio    
##  Min.   :1.000   Apartamento:1291   la flora             : 271  
##  1st Qu.:3.000   Casa       : 542   valle del lili       : 113  
##  Median :3.000                      prados del norte     : 101  
##  Mean   :3.066                      brisas de los        :  76  
##  3rd Qu.:3.000                      urbanización la flora:  62  
##  Max.   :6.000                      torres de comfandi   :  54  
##                                     (Other)              :1156  
##     longitud         latitud       piso_imp       banios_imp     
##  Min.   :-76.55   Min.   :3.380   Mode :logical   Mode :logical  
##  1st Qu.:-76.52   1st Qu.:3.445   FALSE:1220      FALSE:1829     
##  Median :-76.51   Median :3.469   TRUE :613       TRUE :4        
##  Mean   :-76.51   Mean   :3.462                                  
##  3rd Qu.:-76.50   3rd Qu.:3.484                                  
##  Max.   :-76.46   Max.   :3.498                                  
##                                                                  
##  parqueaderos_imp habitaciones_imp    cluster 
##  Mode :logical    Mode :logical    Min.   :4  
##  FALSE:1168       FALSE:1820       1st Qu.:4  
##  TRUE :665        TRUE :13         Median :4  
##                                    Mean   :4  
##                                    3rd Qu.:4  
##                                    Max.   :4  
## 

Correspondencia

Ahora procederemos a analizar las posibles relaciones y contribuciones de las variables mediante un Análisis de Correspondencia utilizando FAMD (Análisis de Factores Múltiples de Correspondencia), el cual abarca tanto variables numéricas como categóricas. Esta técnica nos permitirá explorar de manera integral cómo las diferentes variables interactúan y contribuyen en el conjunto de datos, revelando patrones de asociación que podrían no ser evidentes en análisis anteriores.

housing_scaled = as.data.frame(housing_scaled)
housing_scaled$tipo = housing$tipo
housing_scaled$zona = housing$zona
housing_scaled$barrio = housing$barrio

res.famd <- FAMD(housing[,c("zona","piso","estrato","preciom","areaconst","parqueaderos","banios","habitaciones","tipo","barrio","longitud","latitud")], graph = FALSE)
Scree Plot
fviz_screeplot(res.famd)

Se puede evidenciar que en 2 dimensiones las variables tanto categoricas como númericas representa el 1.075% de los datos, lo cual equivale a un valor muy inferior.

Variables
fviz_famd_var(res.famd, repel = TRUE)

Las variables barrio, area, parqueaderos, baño y precio son las mejores para discriminar siendo muy aparte barrio que explica muy bien la varianza en las dos dimensiones que más explican la varianza de los datos.

Numerical
fviz_famd_var(res.famd, "quanti.var", col.var = "contrib", 
             gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
             repel = TRUE)

Las variables que tienen un vinculo proporcional son habitaciones areaconst baños y parqueaderos, y la correlacion inversa se puede observar en piso vs habitaciones y estrato vs ubicación (longitud y latitud).

Dim 1
fviz_contrib(res.famd, "var", axes = 1)

Dentro de la dimension 1 se evidencia que las variables que tienen mas influencia en los datos son las que representan entre 10 a 15 % de contribución (precio, baños, barrio, parqueaderos, area, estrato).

Dim 2
fviz_contrib(res.famd, "var", axes = 2)

Dentro de la dimension 2 se evidencia que las variables que tienen mas influencia en los datos son las que representan entre 10 a 25 % de contribución (barrio, zona, tipo, habitaciones).

Conclusión

Según el análisis del ejercicio propuesto se esperaba encontrar patrones que ayudaran a identificar la agrupación de los datos mediante la tecnica de Cluster, PCA, y Correspondencia, pero durante el desarrollo del caso se identificó que aunque se evidencien 4 clusters a nivel matemático según la varianza de los datos en el diagrama de PCA, no se detallan patrones a nivel semántico o de dominio que permitan dar conclusiones para el mercado inmobiliario, lo que indica que al conjunto de datos podrían adicionar multiples variables para determinar a nivel de negocio los patrones de agrupamiento como por ejemplo (nivel de ruído, seguridad, clima, accesabilidad al servicio) y demás variables que puedan considerarse dentro del mercado inmobiliario y que puedan mostrar un porcentaje mas alto de representatividad, lo anterior puede deberse a la gran cantidad de missing values presentes en las variables representativas en el dataset.

No Obstante fue posible observar según la correspondencia una fuerte influencia en la variable barrio por ser la que mejor representa a nivel de varianza de los datos, adicional que dentro de las variables numéricas se evidencia una buena correlacion entre habitaciones, area construida, baños y parqueaderos.

Para el desarrollo del ejercicio se utilizó la imputación por el método KNN para datos nulos, concluyendo que fue una buena implementación ya que para intentar aumentar significativamente el % de representatividad se analizó la correspondencia con el dataset completo excluyendo los datos faltantes dando como resultado un porcentaje similar de representatividad al anterior, por lo tanto, el método KNN fue una estrategia clave para complementar los datos faltantes.

LS0tDQp0aXRsZTogIkFjdGl2aWRhZCAxIEV2YWx1YWNpw7NuIGRlbCBtZXJjYWRvIGlubW9iaWxpYXJpbyB1cmJhbm8iDQphdXRob3I6ICJTdGVmYW5pYSBBc3R1ZGlsbG8iDQpkYXRlOiAiMjAyMy0wOC0xMyINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAnaHRtbF9kb2N1bWVudDogZGZfcHJpbnQ6IHBhZ2VkJzogZGVmYXVsdA0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KHdhcm5pbmcgPSBGQUxTRSwgZWNobyA9IFRSVUUpDQpgYGANCg0KYGBge3IsIGluY2x1ZGU9RkFMU0V9DQpyZXF1aXJlZF9saWJzIDwtIGMoDQogICJkZXZ0b29scyIsICJjbHVzdGVyIiwgImRwbHlyIiwgImdncGxvdDIiLCAiRmFjdG9NaW5lUiIsICJmYWN0b2V4dHJhIiwNCiAgImdnZm9ydGlmeSIsICJncmlkRXh0cmEiLCAibGVhZmxldCIsICJsZWFmbGV0LmV4dHJhcyIsICJsbXRlc3QiLA0KICAiUnRzbmUiLCAic3VtbWFyeXRvb2xzIiwgInRpZHl2ZXJzZSIsICJWSU0iLCAid2lkZ2V0ZnJhbWUiDQopDQpmb3IgKHBrZyBpbiByZXF1aXJlZF9saWJzKSB7DQogICNpZiAoIXJlcXVpcmUocGtnLCBxdWlldGx5ID0gRkFMU0UpKSB7DQogICMgIGluc3RhbGwucGFja2FnZXMocGtnLCByZXBvcyA9ICJodHRwOi8vY3Jhbi51cy5yLXByb2plY3Qub3JnIikNCiAgI30NCiAgbGlicmFyeShwa2csIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkNCn0NCg0KaWYgKCFyZXF1aXJlKHBhcXVldGVEQVQsIHF1aWV0bHkgPSBUUlVFKSkgew0KICBkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoImRnb254YWxleDgwL3BhcXVldGVNT0QiLCBmb3JjZSA9VFJVRSkNCn0NCmxpYnJhcnkocGFxdWV0ZURBVCkNCmBgYA0KDQojIyBQcm9ibGVtYQ0KDQpVbmEgZW1wcmVzYSBpbm1vYmlsaWFyaWEgbMOtZGVyIGVuIHVuYSBncmFuIGNpdWRhZCBlc3TDoSBidXNjYW5kbyBjb21wcmVuZGVyIGVuIHByb2Z1bmRpZGFkIGVsIG1lcmNhZG8gZGUgdml2aWVuZGFzIHVyYmFuYXMgcGFyYSB0b21hciBkZWNpc2lvbmVzIGVzdHJhdMOpZ2ljYXMgbcOhcyBpbmZvcm1hZGFzLiBMYSBlbXByZXNhIHBvc2VlIHVuYSBiYXNlIGRlIGRhdG9zIGV4dGVuc2EgcXVlIGNvbnRpZW5lIGluZm9ybWFjacOzbiBkZXRhbGxhZGEgc29icmUgZGl2ZXJzYXMgcHJvcGllZGFkZXMgcmVzaWRlbmNpYWxlcyBkaXNwb25pYmxlcyBlbiBlbCBtZXJjYWRvLiBTZSByZXF1aWVyZSByZWFsaXphciB1biBhbsOhbGlzaXMgaG9sw61zdGljbyBkZSBlc3RvcyBkYXRvcyBwYXJhIGlkZW50aWZpY2FyIHBhdHJvbmVzLCByZWxhY2lvbmVzIHkgc2VnbWVudGFjaW9uZXMgcmVsZXZhbnRlcyBxdWUgcGVybWl0YW4gbWVqb3JhciBsYSB0b21hIGRlIGRlY2lzaW9uZXMgZW4gY3VhbnRvIGEgbGEgY29tcHJhLCB2ZW50YSB5IHZhbG9yYWNpw7NuIGRlIHByb3BpZWRhZGVzLg0KDQpSZXRvczoNCg0KRWwgcmV0byBwcmluY2lwYWwgY29uc2lzdGVuIGVuIHJlYWxpemFyIHVuIGFuw6FsaXNpcyBpbnRlZ3JhbCB5IG11bHRpZGltZW5zaW9uYWwgZGUgbGEgYmFzZSBkZSBkYXRvcyBwYXJhIG9idGVuZXIgdW5hIGNvbXByZW5zacOzbiBkZWwgbWVyY2FkbyBpbm1vYmlsaWFyaW8gdXJiYW5vLiBTZSByZXF1aWVyZSBhcGxpY2FyIGRpdmVyc2FzIHTDqWNuaWNhcyBkZSBhbsOhbGlzaXMgZGUgZGF0b3MsIGluY2x1eWVuZG86DQoNCjEuIEFuw6FsaXNpcyBkZSBDb21wb25lbnRlcyBQcmluY2lwYWxlczogUmVkdWNpciBsYSBkaW1lbnNpb25hbGlkYWQgZGVsIGNvbmp1bnRvIGRlIGRhdG9zIHkgdmlzdWFsaXphciBsYSBlc3RydWN0dXJhIGRlIGxhcyB2YXJpYWJsZXMgZW4gY29tcG9uZW50ZXMgcHJpbmNpcGFsZXMgcGFyYSBpZGVudGlmaWNhciBjYXJhY3RlcsOtc3RpY2FzIGNsYXZlIHF1ZSBpbmZsdXllbiBlbiBsYSB2YXJpYWNpw7NuIGRlIHByZWNpb3MgeSBwcmVmZXJlbmNpYXMgZGVsIG1lcmNhZG8uDQoNCjIuIEFuw6FsaXNpcyBkZSBDb25nbG9tZXJhZG9zOiBBZ3J1cGFyIGxhcyBwcm9waWVkYWRlcyByZXNpZGVuY2lhbGVzIGVuIHNlZ21lbnRvcyBob21vZ8OpbmVvcyBjb24gY2FyYWN0ZXLDrXN0aWNhcyBzaW1pbGFyZXMgcGFyYSBlbnRlbmRlciBsYXMgZGluw6FtaWNhcyB5IGRlbWFuZGFzIGVzcGVjw61maWNhcyBlbiBkaWZlcmVudGVzIHBhcnRlcyBkZSBsYSBjaXVkYWQgeSBlbiBkaWZlcmVudGVzIGVzdHJhdG9zIHNvY2lvZWNvbsOzbWljb3MuDQoNCjMuIEFuw6FsaXNpcyBkZSBDb3JyZXNwb25kZW5jaWEgOiBFeGFtaW5hciBsYSByZWxhY2nDs24gZW50cmUgbGFzIHZhcmlhYmxlcyBjYXRlZ8OzcmljYXMgKHRpcG8gZGUgdml2aWVuZGEsIHpvbmEgeSBiYXJyaW8pIHkgbGFzIHZhcmlhYmxlcyBudW3DqXJpY2FzIChwcmVjaW8sIMOhcmVhIGNvbnN0cnVpZGEsIG7Dum1lcm8gZGUgcGFycXVlYWRlcm9zLCBiYcOxb3MsIGhhYml0YWNpb25lcykgcGFyYSBpZGVudGlmaWNhciBwYXRyb25lcyBkZSBjb21wb3J0YW1pZW50byBkZWwgbWVyY2FkbyBpbm1vYmlsaWFyaW8uDQoNCjQuIFZpc3VhbGl6YWNpw7NuIGRlIHJlc3VsdGFkb3M6IFByZXNlbnRhciBncsOhZmljb3MsIG1hcGFzIHkgb3Ryb3MgcmVjdXJzb3MgdmlzdWFsZXMgcGFyYSBjb211bmljYXIgbG9zIGhhbGxhemdvcyBkZSBtYW5lcmEgY2xhcmEgeSBlZmVjdGl2YSBhIGxhIGRpcmVjY2nDs24gZGUgbGEgZW1wcmVzYS4NCg0KRWwgaW5mb3JtZSBmaW5hbCBkZWJlIGluY2x1aXIgYW7DoWxpc2lzIGRldGFsbGFkb3MgZGUgbG9zIHJlc3VsdGFkb3Mgb2J0ZW5pZG9zLCBsYXMgY29uY2x1c2lvbmVzIGNsYXZlIHkgbGFzIHJlY29tZW5kYWNpb25lcyBlc3BlY8OtZmljYXMgcGFyYSBndWlhciBsYXMgZGVjaXNpb25lcyBlc3RyYXTDqWdpY2FzIGRlIGxhIGVtcHJlc2EgaW5tb2JpbGlhcmlhLiBTZSBlc3BlcmEgcXVlIGVzdGUgYW7DoWxpc2lzIGRlIGRhdG9zIHByb3BvcmNpb25lIHZlbnRhamFzIGNvbXBldGl0aXZhcyBlbiBlbCBtZXJjYWRvLCBvcHRpbWl6YW5kbyBsYSBpbnZlcnNpw7NuIHkgbWF4aW1pemFuZG8gbG9zIGJlbmVmaWNpb3MgZW4gdW4gZW50b3JubyBhbHRhbWVudGUgY29tcGV0aXRpdm8geSBlbiBjb25zdGFudGUgY2FtYmlvLg0KDQoNCiMjIERlc2Fycm9sbG8NCg0KYGBge3J9DQpoZWFkKHZpdmllbmRhKQ0KYGBgDQoNCiMjIEVEQSB7LnRhYnNldH0NCg0KRWwgY29uanVudG8gZGUgZGF0b3MgZGUgb2ZlcnRhcyBkZSB2aXZpZW5kYXMgZW4gT0xYIHByZXNlbnRhIHVuYSBlc3RydWN0dXJhIGNvbXB1ZXN0YSBwb3IgMyB2YXJpYWJsZXMgY2F0ZWfDs3JpY2FzICh6b25hLCBwaXNvLCB0aXBvLCBiYXJyaW8pIHkgNyB2YXJpYWJsZXMgbnVtw6lyaWNhcy4gRW4gZXN0ZSBhbsOhbGlzaXMsIG9wdGFyZW1vcyBwb3IgZGVzY2FydGFyIGxhIHZhcmlhYmxlICJpZCIuIExhIGV4Y2x1c2nDs24gZGUgZXN0YSB2YXJpYWJsZSBzZSBqdXN0aWZpY2EgZW4gdmlydHVkIGRlIGxhIG5hdHVyYWxlemEgZGVsIGFuw6FsaXNpcyBwcm9wdWVzdG8sIHF1ZSBzZSBlbmZvY2Fyw6EgZW4gbGEgaWRlbnRpZmljYWNpw7NuIGRlIHBhdHJvbmVzIG1lZGlhbnRlIHVuIGVuZm9xdWUgbm8gc3VwZXJ2aXNhZG8uIEVuIGVzdGUgY29udGV4dG8sIGxhIHZhcmlhYmxlICJpZCIgY2FyZWNlIGRlIHNpZ25pZmljYWRvIHNlbcOhbnRpY28geSBjb250cmlidWNpw7NuIGFsIGFuw6FsaXNpcy4NCg0KDQpgYGB7cn0NCiN8IGNvbGxhcHNlID0gRkFMU0UNCnNldC5zZWVkKDQyKSAjIHVuaXZlcnNlIGFuc3dlcg0KaG91c2luZyA9IHBhcXVldGVEQVQ6OnZpdmllbmRhDQpob3VzaW5nID0gaG91c2luZ1tyb3dTdW1zKGlzLm5hKGhvdXNpbmcpKSA8IG5jb2woaG91c2luZykgLSAxLCBdICMgcmVtb3ZlIHJvd3Mgd2l0aCBhbGwgTkEgZmVhdHVyZXMNCmhvdXNpbmcgPSBob3VzaW5nICU+JSBtdXRhdGUocGlzbyA9IGlmZWxzZShpcy5uYShwaXNvKSwgTkEsIGFzLm51bWVyaWMoaG91c2luZyRwaXNvKSkpDQpgYGANCg0KIyMjIE5Bcw0KDQpgYGB7ciwgcmVzdWx0cz0nYXNpcycsIGZpZy5hbGlnbj0nY2VudGVyJ30NCiN8IGNvbGxhcHNlID0gVFJVRQ0KcGVyY2VudGFnZV9uYSA9IG5yb3coaG91c2luZ1thcHBseShpcy5uYShob3VzaW5nKSwgMSwgYW55KSwgXSkgLyBucm93KGhvdXNpbmcpICogMTAwDQoNCnBpZV9uYV9kYXRhID0gZGF0YS5mcmFtZSgNCiAgU3RhdHVzID0gYygiTkEiLCAiQ29tcGxldGUiKSwNCiAgUGVyY2VudGFnZSA9IGMocGVyY2VudGFnZV9uYSwgMTAwIC0gcGVyY2VudGFnZV9uYSkNCikNCg0KcGxvdCA9IGdncGxvdChwaWVfbmFfZGF0YSwgYWVzKHggPSAiIiwgeSA9IFBlcmNlbnRhZ2UsIGZpbGwgPSBTdGF0dXMpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IDEpICsNCiAgY29vcmRfcG9sYXIodGhldGEgPSAieSIpICsNCiAgbGFicyh4ID0gTlVMTCwgeSA9IE5VTEwsIGZpbGwgPSAiU3RhdHVzIikgKw0KICB0aGVtZV92b2lkKCkgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gc3ByaW50ZigiJS4xZiUlIiwgUGVyY2VudGFnZSkpLCBwb3NpdGlvbiA9IHBvc2l0aW9uX3N0YWNrKHZqdXN0ID0gMC41KSkNCg0KcHJpbnQocGxvdCkNCmBgYA0KDQpFbCBkaWFncmFtYSBkZSBwYXN0ZWwgaWx1c3RyYSBjw7NtbyBjZXJjYSBkZWwgNTAlIGRlIGxvcyBkYXRvcyBwcmVzZW50YW4gYWwgbWVub3MgdW5hIGNhcmFjdGVyw61zdGljYSBjb24gdmFsb3JlcyBmYWx0YW50ZXMgKE5BKSwgbG8gcXVlIGludHJvZHVjZSB1bmEgY2FwYSBhZGljaW9uYWwgZGUgY29tcGxlamlkYWQgZW4gbGEgaW1wbGVtZW50YWNpw7NuIGRlIGxvcyBhbGdvcml0bW9zLiBFcyBpbXBvcnRhbnRlIHNlw7FhbGFyIHF1ZSBncmFuIHBhcnRlIGRlIGxvcyB2YWxvcmVzIGZhbHRhbnRlcyBzZSBjb25jZW50cmFuIGVuIGxhIHZhcmlhYmxlICJQaXNvIi4gRXN0YSB2YXJpYWJsZSByZXZpc3RlIHVuYSBncmFuIHJlbGV2YW5jaWEsIHlhIHF1ZSBkZXNkZSB1bmEgcGVyc3BlY3RpdmEgZGUgbmVnb2Npbywgc2UgcmVjb25vY2Ugc3UgaW1wYWN0byBzaWduaWZpY2F0aXZvIGVuIGVsIHByZWNpbyBkZSBsYXMgcHJvcGllZGFkZXMuIFVuIGVqZW1wbG8gY29uY3JldG8gZXMgbGEgdmFyaWFjacOzbiBlbiBlbCB2YWxvciBkZSB1biBhcGFydGFtZW50byBpZMOpbnRpY28sIHBlcm8gdWJpY2FkbyBlbiBwaXNvcyBkaWZlcmVudGVzLCBxdWUgcHVlZGUgdHJhZHVjaXJzZSBlbiBkaWZlcmVuY2lhcyBkZSBtaWxsb25lcyBkZSBwZXNvcy4NCg0KRGFkbyBlc3RlIGNvbnRleHRvLCBzZSB0b21hcsOhIGxhIGRlY2lzacOzbiBkZSBhYm9yZGFyIGxhIGltcHV0YWNpw7NuIGRlIGVzdG9zIHZhbG9yZXMgZmFsdGFudGVzIGVuIGxhIHZhcmlhYmxlICJQaXNvIi4gRXN0YSBlbGVjY2nDs24gc2UgZnVuZGFtZW50YSBlbiBsYSBpbXBvcnRhbmNpYSBkZSBsYSB2YXJpYWJsZSBlbiB0w6lybWlub3MgZGUgaW5mbHVlbmNpYSBlbiBsb3MgcmVzdWx0YWRvcyBkZSBuZWdvY2lvLiBMYSBjb3JyZWNjacOzbiBkZSBlc3RvcyB2YWxvcmVzIGZhbHRhbnRlcyBzZSBjb252aWVydGUgZW4gdW5hIHByaW9yaWRhZCwgeWEgcXVlIHN1IGludGVncmlkYWQgbWVqb3JhcsOhIGxhIGNhbGlkYWQgeSBjb25maWFiaWxpZGFkIGRlIGxvcyBhbsOhbGlzaXMgcG9zdGVyaW9yZXMgeSBkZSBsb3MgYWxnb3JpdG1vcyBxdWUgc2UgaW1wbGVtZW50YXLDoW4uIEVzdGUgcHJvY2VzbyBkZSBpbXB1dGFjacOzbiBzZSBsbGV2YXLDoSBhIGNhYm8gY29uIGxhIGZpbmFsaWRhZCBkZSBldml0YXIgZGlzdG9yc2lvbmVzIGVuIGxvcyByZXN1bHRhZG9zIHkgYXNlZ3VyYXIgdW5hIHJlcHJlc2VudGFjacOzbiBtw6FzIHByZWNpc2EgZGUgbGEgcmVsYWNpw7NuIGVudHJlIGxhcyBjYXJhY3RlcsOtc3RpY2FzIHkgbG9zIHByZWNpb3MgZGUgbGFzIHByb3BpZWRhZGVzLg0KDQojIyMgUmVzdW1lbg0KYGBge3IsIHJlc3VsdHM9J2FzaXMnLCBlY2hvID0gRkFMU0V9DQojfCBjb2xsYXBzZSA9IFRSVUUNCmhvdXNpbmcgPSBob3VzaW5nICU+JQ0KICAjc2VsZWN0KC1jKCJpZCIsICJsb25naXR1ZCIsICJsYXRpdHVkIikpICU+JQ0KICBtdXRhdGUoYWNyb3NzKHdoZXJlKGlzLmNoYXJhY3RlciksIGZhY3RvcikpDQoNCnByaW50KGRmU3VtbWFyeShob3VzaW5nLCBoZWFkaW5ncyA9IEZBTFNFLCBwbGFpbi5hc2NpaSA9IEZBTFNFLCBzdHlsZSA9ICJncmlkIiwgdmFybnVtYmVycyA9IEZBTFNFLCB2YWxpZC5jb2wgPSBGQUxTRSksIG1ldGhvZCA9ICdyZW5kZXInKQ0KYGBgDQoNCkVzIGV2aWRlbnRlIHF1ZSBsYXMgdmFyaWFibGVzIG51bcOpcmljYXMgbm8gZXhoaWJlbiB1bmEgZGlzdHJpYnVjacOzbiBleGhhdXN0aXZhbWVudGUgbm9ybWFsIGVuIGxhIG1heW9yw61hIGRlIGxhcyBpbnN0YW5jaWFzLiBFc3RhIG9ic2VydmFjacOzbiBzdWdpZXJlIHF1ZSBubyBlcyBmYWN0aWJsZSBpbmZlcmlyIGNvbmNsdXNpb25lcyBzZW3DoW50aWNhcyBjb24gcmVzcGVjdG8gYSBsYSB0b3RhbGlkYWQgZGUgbGEgcG9ibGFjacOzbiwgeWEgcXVlIGxhIHJlcHJlc2VudGFjacOzbiBncsOhZmljYSBpbmRpY2EgcXVlIG5vIHNlIGFqdXN0YSBhbCB0ZW9yZW1hIGRlbCBsw61taXRlIGNlbnRyYWwuIERpY2hhIGZhbHRhIGRlIG5vcm1hbGlkYWQgZW4gbGEgZGlzdHJpYnVjacOzbiBkZSBsb3MgZGF0b3MgZXMgdW4gYXNwZWN0byBjcnVjaWFsIHF1ZSBkZWJlIHNlciBjb25zaWRlcmFkbyBhbCBpbnRlcnByZXRhciB0YW50byBlbCBhbsOhbGlzaXMgY29tbyBsb3MgcmVzdWx0YWRvcyBzdWJzaWd1aWVudGVzLiBFc3RhIGNpcmN1bnN0YW5jaWEgcHVlZGUgaW5mbHVpciBlbiBsYXMgZXN0cmF0ZWdpYXMgZGUgbW9kZWxhZG8geSBlbiBsYXMgdMOpY25pY2FzIGVzdGFkw61zdGljYXMgcXVlIHNlIGVtcGxlZW4sIHlhIHF1ZSBsYXMgc3Vwb3NpY2lvbmVzIGFzb2NpYWRhcyBhIGxhIGRpc3RyaWJ1Y2nDs24gbm9ybWFsIHB1ZWRlbiBubyBzZXIgdsOhbGlkYXMgZW4gZXN0ZSBjb250ZXh0by4NCg0KIyMgey19DQoNCiMjIyBDb21wbGV0YWNpw7NuIE1pc3NpZ24gVmFsdWVzIHsudGFic2V0fQ0KDQoNCkVuIGVsIGNvbmp1bnRvIGRlIGRhdG9zLCBoZW1vcyBpZGVudGlmaWNhZG8gbGEgcHJlc2VuY2lhIGRlIHJlZ2lzdHJvcyBjb24gdmFsb3JlcyBpZ3VhbCBhIDAgZW4gbGFzIHZhcmlhYmxlcyBjb3JyZXNwb25kaWVudGVzIGEgImhhYml0YWNpb25lcyIgeSAiYmHDsW9zIi4gVW5hIHJldmlzacOzbiBkZSBsb3MgZXN0cmF0b3Mgbm9zIGhhIHBlcm1pdGlkbyBvYnNlcnZhciBxdWUgZXN0b3MgdmFsb3JlcyBzb2xvIGFiYXJjYW4gZWwgcmFuZ28gZGUgMyBhIDYuIERhZG8gcXVlIGxvcyB0aXBvcyBkZSBwcm9waWVkYWRlcyByZWdpc3RyYWRhcyBzb24gdml2aWVuZGFzIHkgYXBhcnRhbWVudG9zLCByZXN1bHRhIHBvY28gcmF6b25hYmxlIHF1ZSB1bmEgcHJvcGllZGFkIGNhcmV6Y2EgZGUgYWwgbWVub3MgdW5hIGhhYml0YWNpw7NuIG8gdW4gYmHDsW8uDQoNClBvciBsbyB0YW50bywgYWJvcmRhcmVtb3MgZXN0YXMgZGlzY3JlcGFuY2lhcyBjb25zaWRlcsOhbmRvbGFzIGNvbW8gdmFsb3JlcyBmYWx0YW50ZXMgKG1pc3NpbmcgdmFsdWVzKS4gRXN0byBub3MgcGVybWl0aXLDoSBpbmNsdWlybGFzIGVuIGVsIHByb2Nlc28gZGUgaW1wdXRhY2nDs24gZGUgZGF0b3MgZmFsdGFudGVzLCBjb24gZWwgcHJvcMOzc2l0byBkZSBsb2dyYXIgdW5hIGNvcnJlY2Npw7NuIG3DoXMgY29tcGxldGEgeSBwcmVjaXNhLg0KDQpPcHRhcmVtb3MgcG9yIGxhIGltcHV0YWNpw7NuIGRlIGxvcyB2YWxvcmVzIGZhbHRhbnRlcyB1dGlsaXphbmRvIGVsIGFsZ29yaXRtbyBLTk4sIGVuIHZpc3RhIGRlIHF1ZSBsb3MgZGF0b3MgZXhoaWJlbiB1biBjb21wb3J0YW1pZW50byBubyBsaW5lYWwgcXVlIG5vIHNlIGFqdXN0YSBhbCBlbmZvcXVlIGRlIGxhIHJlZ3Jlc2nDs24gbGluZWFsLiBFcyBlc2VuY2lhbCBkZXN0YWNhciBxdWUsIGEgcGVzYXIgZGUgbGEgdmlhYmlsaWRhZCBkZSBsYSByZWdyZXNpw7NuLCBzdSBhcGxpY2FjacOzbiBzaW4gY3VtcGxpciBsb3Mgc3VwdWVzdG9zIHBvZHLDrWEgaW50cm9kdWNpciBkaXN0b3JzaW9uZXMgZW4gbGFzIGludGVycHJldGFjaW9uZXMgcmVzdWx0YW50ZXMuIFNpIGJpZW4gYWx0ZXJuYXRpdmFzIG3DoXMgYXZhbnphZGFzLCBjb21vIGVsIERlZXAgTGVhcm5pbmcsIHkgb3BjaW9uZXMgbcOhcyBzZW5jaWxsYXMsIGNvbW8gZWwgY8OhbGN1bG8gZGVsIHByb21lZGlvIGdsb2JhbCwgZXN0w6FuIGRpc3BvbmlibGVzLCBoZW1vcyBvcHRhZG8gcG9yIGVsIGVtcGxlbyBkZSBLTk4uDQoNCkxhIHNlbGVjY2nDs24gZGUgS05OIHNlIHJlc3BhbGRhIGVuIGxhIHBlcnNwZWN0aXZhIGRlIG9idGVuZXIgcmVzdWx0YWRvcyBtw6FzIGVmZWN0aXZvcyBlbiBjb21wYXJhY2nDs24gY29uIGxhIG1lcmEgZXN0aW1hY2nDs24gcHJvbWVkaW8uIEVzdGEgZWxlY2Npw7NuIGVuY3VlbnRyYSBzdXN0ZW50byBlbiBkaXZlcnNvcyBlbGVtZW50b3MsIGVudHJlIGxvcyBjdWFsZXMgZGVzdGFjYSBsYSBMZXkgNjc1IGRlbCAyMDAxLCBxdWUgYWJvZ2EgcG9yIGxhIGhvbW9nZW5laWRhZCBkZSBsYXMgcHJvcGllZGFkZXMgZW4gcsOpZ2ltZW4gZGUgcHJvcGllZGFkIGhvcml6b250YWwgeSBlbiBsb3MgZW50b3Jub3MgcmVzaWRlbmNpYWxlcy4gRXMgY3J1Y2lhbCBtYW50ZW5lciBwcmVzZW50ZSBxdWUgZXN0YSBkZXRlcm1pbmFjacOzbiBpbXBsaWNhIHVuIHN1cHVlc3RvIHF1ZSBhYm9yZGFyZW1vcyBlbiBlbCBjdXJzbyBkZWwgcHJvY2Vzby4NCg0KIyMjIyBMaW5lYWxpZGFkDQoNCmBgYHtyLCByZXN1bHRzPSdhc2lzJywgZWNobyA9IEZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcid9DQojfCBjb2xsYXBzZSA9IFRSVUUNCmdncGxvdChob3VzaW5nLCBhZXMoeCA9IGFyZWFjb25zdCwgeSA9IHByZWNpb20pKSArIGdlb21fcG9pbnQoKQ0KYGBgDQoNCkVuIGVsIGdyw6FmaWNvIHF1ZSByZWxhY2lvbmEgZWwgw6FyZWEgY29uIGVsIHByZWNpbywgZXMgZXZpZGVudGUgcXVlIHBhcmEgdW4gdmFsb3IgZGUgw6FyZWEgcHVlZGVuIGV4aXN0aXIgbcO6bHRpcGxlcyBwcmVjaW9zLiBFc3RhIG9ic2VydmFjacOzbiBkZW11ZXN0cmEgcXVlIG5vIGV4aXN0ZSB1bmEgcmVsYWNpw7NuIGZ1bmNpb25hbCBjbGFyYSBlbnRyZSBlc3RhcyBkb3MgZGltZW5zaW9uZXMuIFBhcmEgdW5hIGV2YWx1YWNpw7NuIG3DoXMgY29tcGxldGEgZGVsIGVuZm9xdWUgZGUgcmVncmVzacOzbiBsaW5lYWwsIHBvZGVtb3MgcmVhbGl6YXIgdW5hIHLDoXBpZGEgcmV2aXNpw7NuIHV0aWxpemFuZG8gZWwgdGVzdCBSRVNFVCwgY29uc2lkZXJhbmRvIHVuIGFuw6FsaXNpcyBtw6FzIHByb2Z1bmRvIGNvbiBtw7psdGlwbGVzIGRpbWVuc2lvbmVzLg0KDQpgYGB7cn0NCiN8IGNvbGxhcHNlID0gVFJVRQ0KbGluZWFyX21vZGVsIDwtIGxtKHByZWNpb20gfiBhcmVhY29uc3QgKyBlc3RyYXRvICsgYmFuaW9zICsgaGFiaXRhY2lvbmVzICsgbGF0aXR1ZCArIGxvbmdpdHVkLCBkYXRhID0gaG91c2luZykNCnByaW50KHJlc2V0dGVzdChsaW5lYXJfbW9kZWwsIHBvd2VyID0gMiksIG1ldGhvZCA9ICdyZW5kZXInKQ0KYGBgDQoNCkNvbW8gaGFiw61hbW9zIHByZXZpc3RvLCBlbCBlbmZvcXVlIGRlIHJlZ3Jlc2nDs24gbm8gZGVtdWVzdHJhIHNlciBlZmVjdGl2by4gRGFkbyBxdWUgZWwgdmFsb3IgcCBzZSBhY2VyY2EgYSBjZXJvLCByZWNoYXphbW9zIGxhIGhpcMOzdGVzaXMgbnVsYSBxdWUgc29zdGllbmUgcXVlIGxvcyBkYXRvcyBlc3TDoW4gYWRlY3VhZGFtZW50ZSBtb2RlbGFkb3MgbWVkaWFudGUgcmVncmVzacOzbi4gRXN0YSBvYnNlcnZhY2nDs24gZm9ydGFsZWNlIGHDum4gbcOhcyBsYSBzdWdlcmVuY2lhIGRlIGNvbnNpZGVyYXIgbGEgYWRvcGNpw7NuIGRlbCBhbGdvcml0bW8gS05OLg0KDQojIyMjIEtOTg0KDQpgYGB7cn0NCiN8IGNvbGxhcHNlID0gRkFMU0UNCmhvdXNpbmcgPSBob3VzaW5nICU+JSANCiAgbXV0YXRlKGJhbmlvcyA9IGlmZWxzZShiYW5pb3MgPT0gMCwgTkEsIGJhbmlvcykpICU+JSANCiAgbXV0YXRlKGhhYml0YWNpb25lcyA9IGlmZWxzZShoYWJpdGFjaW9uZXMgPT0gMCwgTkEsIGhhYml0YWNpb25lcykpDQoNCmhvdXNpbmcgPSBrTk4oaG91c2luZywgdmFyaWFibGUgPSBjKCJwaXNvIiwgImJhbmlvcyIsICJwYXJxdWVhZGVyb3MiLCAiaGFiaXRhY2lvbmVzIikpDQphbnkoaXMubmEoaG91c2luZykpDQpgYGANCg0KIyMjIHstfQ0KDQojIyMgUGF0cm9uZXMNCg0KYGBge3IsIGZpZy5hbGlnbj0iY2VudGVyIn0NCm1hcCA9IGxlYWZsZXQoKSAlPiUNCiAgYWRkVGlsZXMoKSAlPiUNCiAgYWRkSGVhdG1hcChkYXRhID0gaG91c2luZywgbG5nID0gfmxvbmdpdHVkLCBsYXQgPSB+bGF0aXR1ZCwgYmx1ciA9IDUsIG1heCA9IDUsIHJhZGl1cyA9IDUpDQoNCm1hcA0KYGBgDQoNClJlc3VsdGEgY3VyaW9zbyBvYnNlcnZhciBxdWUgZXhpc3RlIHVuYSBhbXBsaWEgY29iZXJ0dXJhIGVuIGxhIHpvbmEgZGUgQ2FsaTsgc2luIGVtYmFyZ28sIGVzdGEgY29iZXJ0dXJhIHNvbG8gYWJhcmNhIGxvcyBlc3RyYXRvcyAzIGFsIDYuIFJlc3VsdGEgZXZpZGVudGUgbGEgcHJlc2VuY2lhIGRlIMOhcmVhcyBwZXJ0ZW5lY2llbnRlcyBhIGxvcyBlc3RyYXRvcyAxIHkgMiBlbiBkaXN0aW50YXMgem9uYXMsIGxvIHF1ZSBzdWdpZXJlIGxhIGV4aXN0ZW5jaWEgZGUgcG9zaWJsZXMgaW5jb25zaXN0ZW5jaWFzIGVuIGxvcyBkYXRvcyByZWNvbGVjdGFkb3MuDQoNCiMjIyMgQ29ycmVsYXRpb24NCg0KRGFkbyBxdWUgcGxhbmVhbW9zIGVtcGxlYXIgYWxnb3JpdG1vcyBzZW5zaWJsZXMgYSBsYSBlc2NhbGEgZGUgdW5pZGFkZXMsIGxvcyBjdWFsZXMgb3BlcmFuIGEgdHJhdsOpcyBkZSBmdW5jaW9uZXMgZGUgZGlzdGFuY2lhIHkgY29tcGFyYWNpb25lcyBzdXNjZXB0aWJsZXMgYSBzZXIgaW5mbHVlbmNpYWRhcyBwb3IgcmFuZ29zIGV4dGVuc29zLCByZXN1bHRhIG5lY2VzYXJpbyBsbGV2YXIgYSBjYWJvIHVuYSBub3JtYWxpemFjacOzbiBkZSBsb3MgZGF0b3MuIEVzdGUgcHJvY2VkaW1pZW50byBzZXLDoSBhcGxpY2FkbyBleGNsdXNpdmFtZW50ZSBhIGxhcyB2YXJpYWJsZXMgbnVtw6lyaWNhcy4NCg0KYGBge3J9DQpob3VzaW5nX251bSA9IGhvdXNpbmdbLCBjKCJwaXNvIiwiZXN0cmF0byIsInByZWNpb20iLCJhcmVhY29uc3QiLCJwYXJxdWVhZGVyb3MiLCJoYWJpdGFjaW9uZXMiLCJsb25naXR1ZCIsImxhdGl0dWQiKV0NCmhvdXNpbmdfc2NhbGVkID0gc2NhbGUoaG91c2luZ19udW0pDQpgYGANCg0KYGBge3J9DQpwcmludChjb3IoaG91c2luZ19zY2FsZWRbLCBjKCJwaXNvIiwiZXN0cmF0byIsImFyZWFjb25zdCIsInBhcnF1ZWFkZXJvcyIsImhhYml0YWNpb25lcyIsImxvbmdpdHVkIiwibGF0aXR1ZCIpXSwgaG91c2luZ19zY2FsZWRbLCBjKCJwcmVjaW9tIildKSwgbWV0aG9kID0gInJlbmRlciIpDQpgYGANCg0KQWwgYW5hbGl6YXIgbGFzIGNvcnJlbGFjaW9uZXMgZW50cmUgbGFzIHZhcmlhYmxlcywgc2UgZXZpZGVuY2lhIHVuYSBmdWVydGUgYXNvY2lhY2nDs24gZW4gdG9kb3MgbG9zIGNhc29zLCBleGNlcHR1YW5kbyBsYSB2YXJpYWJsZSBxdWUgaGFjZSByZWZlcmVuY2lhIGFsIHRpcG8gZGUgcGlzbywgbGEgY3VhbCBtdWVzdHJhIHVuYSBjb3JyZWxhY2nDs24gbWVub3Mgc2lnbmlmaWNhdGl2YSBjb24gcmVzcGVjdG8gYWwgcHJlY2lvLiBFc3RvcyBoYWxsYXpnb3Mgbm9zIHBlcm1pdGVuIGxsZWdhciBhIGxhcyBzaWd1aWVudGVzIGNvbmNsdXNpb25lczoNCg0KKiBMYSBhbHRhIGNvcnJlbGFjacOzbiBvYnNlcnZhZGEgZW50cmUgbGEgbWF5b3LDrWEgZGUgbGFzIHZhcmlhYmxlcyB5IGVsIHByZWNpbyBpbmRpY2EgcXVlIGVzdGFzIGRlc2VtcGXDsWFuIHVuIHBhcGVsIGNydWNpYWwgZW4gbGEgZGV0ZXJtaW5hY2nDs24gZGUgbG9zIHZhbG9yZXMgZGUgbGFzIHByb3BpZWRhZGVzLiBFc3BlY8OtZmljYW1lbnRlLCBmYWN0b3JlcyBjb21vIGVsIHRhbWHDsW8gZGVsIGlubXVlYmxlLCBsYSB1YmljYWNpw7NuIHkgbGEgY2FudGlkYWQgZGUgaGFiaXRhY2lvbmVzIHByZXNlbnRhbiB1bmEgaW5mbHVlbmNpYSBkaXJlY3RhIGVuIGVsIHByZWNpby4NCg0KKiBMYSBiYWphIGNvcnJlbGFjacOzbiBlbnRyZSBlbCB0aXBvIGRlIHBpc28geSBlbCBwcmVjaW8sIHF1ZSBwdWVkZSBoYWJlciBzaWRvIGluZmx1ZW5jaWFkYSBwb3IgZWwgaGVjaG8gZGUgcXVlIGVzdGEgdmFyaWFibGUgY29udGVuw61hIGNhc2kgdW4gNTAlIGRlIHZhbG9yZXMgZmFsdGFudGVzLCBzdWdpZXJlIHF1ZSBlc3RlIGF0cmlidXRvIHBvZHLDrWEgdGVuZXIgdW4gaW1wYWN0byBtZW5vcyBkZWNpc2l2byBlbiBsYSB2YWxvcmFjacOzbiBkZSBsYXMgcHJvcGllZGFkZXMuIExhIHByZXNlbmNpYSBkZSB1biBncmFuIG7Dum1lcm8gZGUgdmFsb3JlcyBmYWx0YW50ZXMgcHVlZGUgaGFiZXIgZGlzdG9yc2lvbmFkbyBsYSByZWxhY2nDs24gcmVhbCBlbnRyZSBlbCB0aXBvIGRlIHBpc28geSBlbCBwcmVjaW8sIGxvIHF1ZSBjb250cmlidXnDsyBhIGVzdGUgcmVzdWx0YWRvLg0KDQojIyMjIEstTWVhbnMNCg0KRGFkbyBxdWUgbm8gZGlzcG9uZW1vcyBkZSB1biBjb25vY2ltaWVudG8gaW50csOtbnNlY28gZGUgbGFzIGFncnVwYWNpb25lcyBwcmVzZW50ZXMsIGVzIGltcGVyYXRpdm8gZXhwbG9yYXIgZGl2ZXJzb3MgZW5mb3F1ZXMgZGUgYWdydXBhbWllbnRvLiBQb3IgbG8gdGFudG8sIHByb2NlZGVyZW1vcyBhIGdlbmVyYXIgcmVzdWx0YWRvcyBkZSBhZ3J1cGFtaWVudG9zIGVuIHVuIHJhbmdvIGRlIDEgYSAxMCBncnVwb3MuIFBvc3Rlcmlvcm1lbnRlLCBlbXBsZWFyZW1vcyBlbCBtw6l0b2RvIGRlbCBjb2RvIHBhcmEgaWRlbnRpZmljYXIgdW5hIGNhbnRpZGFkIGRlIGdydXBvcyBzZW5zYXRhIHF1ZSBwZXJtaXRhIGluaWNpYXIgdW4gYW7DoWxpc2lzIHNlbcOhbnRpY28gZGUgbG9zIHBhdHJvbmVzIHByZXNlbnRlcyBlbiBsb3MgZGF0b3MuDQoNCkVzdGUgcHJvY2VzbyBub3MgcGVybWl0aXLDoSBvYnRlbmVyIHVuYSBjb21wcmVuc2nDs24gbcOhcyBwcm9mdW5kYSBkZSBsYSBlc3RydWN0dXJhIHN1YnlhY2VudGUgZGUgbG9zIGRhdG9zIHkgZmFjaWxpdGFyw6EgbGEgaWRlbnRpZmljYWNpw7NuIGRlIHJlbGFjaW9uZXMgeSB0ZW5kZW5jaWFzIHNpZ25pZmljYXRpdmFzIGVudHJlIGxhcyBvYnNlcnZhY2lvbmVzLiBMYSBlbGVjY2nDs24gZGVsIG7Dum1lcm8gw7NwdGltbyBkZSBncnVwb3MgYSB0cmF2w6lzIGRlbCBtw6l0b2RvIGRlbCBjb2RvIGNvbnRyaWJ1aXLDoSBhIHVuYSBzZWdtZW50YWNpw7NuIG3DoXMgcHJlY2lzYSB5IHNpZ25pZmljYXRpdmEgZGUgbG9zIGRhdG9zLCBsbyBxdWUgYSBzdSB2ZXogZmFjaWxpdGFyw6EgbGEgaW50ZXJwcmV0YWNpw7NuIHkgYXBsaWNhY2nDs24gZGUgbG9zIHJlc3VsdGFkb3Mgb2J0ZW5pZG9zLg0KDQpgYGB7ciwgZWNobyA9IEZBTFNFfQ0KI3wgY29sbGFwc2UgPSBUUlVFDQp3c3MgPSBmdW5jdGlvbihrKSB7DQogIGttZWFucyhob3VzaW5nX3NjYWxlZCwgaywgbnN0YXJ0PTI1LCBhbGdvcml0aG09Ik1hY1F1ZWVuIikkdG90LndpdGhpbnNzDQp9DQoNCmsudmFsdWVzID0gMToxMA0KDQp3c3NfdmFsdWVzIDwtIG1hcF9kYmwoay52YWx1ZXMsIHdzcykNCg0KcGxvdChrLnZhbHVlcywgd3NzX3ZhbHVlcywNCiAgICAgdHlwZT0iYiIsIHBjaD0xOSwgZnJhbWU9RkFMU0UsIA0KICAgICB4bGFiPSIjIENsdXN0ZXIiLA0KICAgICB5bGFiPSJUb3RhbCBXU1MiKQ0KYGBgDQoNCkVzIGV2aWRlbnRlIHF1ZSBhIHBhcnRpciBkZSBLPTIsIGxhIHZhcmlhYmlsaWRhZCBlbnRyZSBsb3MgZ3J1cG9zIGRpc21pbnV5ZSBkZSBtYW5lcmEgc2lnbmlmaWNhdGl2YS4gQSBtZWRpZGEgcXVlIGVsIHZhbG9yIGRlIEsgY29udGluw7phIGF1bWVudGFuZG8sIGxhIHJlZHVjY2nDs24gZW4gbGEgdmFyaWFiaWxpZGFkIHNlIHZ1ZWx2ZSBtYXJnaW5hbC4gUG9yIGxvIHRhbnRvLCB0ZW5lbW9zIGxhIG9wY2nDs24gZGUgc2VsZWNjaW9uYXIgdW4gdmFsb3IgZGUgSyBlbiBlbCByYW5nbyBkZSAyIGEgNiBwYXJhIHVuYSByZXByZXNlbnRhY2nDs24gYWRlY3VhZGEgZGUgbG9zIGdydXBvcy4gRW4gZXN0ZSBjb250ZXh0bywgb3B0YXJlbW9zIHBvciBlbCB2YWxvciBpbnRlcm1lZGlvIGRlIDQsIHF1ZSBzZSBlbmN1ZW50cmEgZXF1aWRpc3RhbnRlIGRlbnRybyBkZSBlc3RlIGludGVydmFsby4gRXN0YSBlbGVjY2nDs24gbm9zIHBlcm1pdGUgYWxjYW56YXIgdW4gY29tcHJvbWlzbyDDs3B0aW1vIGVudHJlIGxhIHNpbXBsaWNpZGFkIGRlIGxhIHNlZ21lbnRhY2nDs24geSBsYSBjYXB0dXJhIGRlIGxhIGVzdHJ1Y3R1cmEgbGF0ZW50ZSBlbiBsb3MgZGF0b3MuDQoNCmBgYHtyLCBlY2hvID0gRkFMU0V9DQpob3VzaW5nX2NsdXN0ZXIgPC0ga21lYW5zKGhvdXNpbmdfc2NhbGVkLCA0LCBuc3RhcnQgPSAyNSwgYWxnb3JpdGhtPSJNYWNRdWVlbiIpDQpob3VzaW5nID0gaG91c2luZyAlPiUgbXV0YXRlKGNsdXN0ZXIgPSBob3VzaW5nX2NsdXN0ZXIkY2x1c3RlcikNCmhvdXNpbmcgJT4lIGdyb3VwX2J5KGNsdXN0ZXIpICU+JSBzdW1tYXJpc2VfYWxsKCJtZWFuIikNCmBgYA0KDQpgYGB7ciwgZWNobyA9IEZBTFNFfQ0KY29sb3JzIDwtIHJhaW5ib3coNCkNCmNvbG9ycw0KYGBgDQoNCmBgYHtyLCBlY2hvID0gRkFMU0V9DQptYXAgPSBsZWFmbGV0KCkgJT4lDQogIGFkZFRpbGVzKCkgJT4lDQogIGFkZENpcmNsZU1hcmtlcnMoDQogICAgZGF0YSA9IGhvdXNpbmcsDQogICAgbG5nID0gfmxvbmdpdHVkLA0KICAgIGxhdCA9IH5sYXRpdHVkLA0KICAgIGNvbG9yID0gY29sb3JzW2hvdXNpbmckY2x1c3Rlcl0sDQogICAgb3BhY2l0eSA9IDAuOCwNCiAgICBmaWxsT3BhY2l0eSA9IDAuOCwNCiAgICByYWRpdXMgPSA2DQogICkNCg0KbWFwDQpgYGANCg0KQWwgb2JzZXJ2YXIgbG9zIGNsw7pzdGVyZXMgeSB1YmljYXJsb3MgZW4gZWwgbWFwYSwgc2UgaWRlbnRpZmljYSBxdWUgbm8gZXhpc3RlIHVuYSByZWxhY2nDs24gc2Vtw6FudGljYSBjb25zaXN0ZW50ZSBlbnRyZSBsb3MgZ3J1cG9zIGdlbmVyYWRvcyB5IHN1IHViaWNhY2nDs24gZ2VvZ3LDoWZpY2EuIEF1bnF1ZSBzZSBwdWVkZW4gcGVyY2liaXIgY2llcnRvcyBpbmRpY2lvcyBkZSBhZ3J1cGFjacOzbiBwb3Igem9uYSwgYWwgcmVwZXRpciBlbCBwcm9jZXNvIHNpbiBjb25zaWRlcmFyIGxhcyB2YXJpYWJsZXMgZGUgbGF0aXR1ZCB5IGxvbmdpdHVkLCBsb3MgZ3J1cG9zIGVuIHTDqXJtaW5vcyBkZSB1YmljYWNpw7NuIHNlIHZ1ZWx2ZW4gbcOhcyBhbWJpZ3Vvcy4gRXN0byBub3MgbGxldmEgYSBpZGVudGlmaWNhciBsYSBhdXNlbmNpYSBkZSB1biBwYXRyw7NuIGRpcmVjdG8gY29uIHJlc3BlY3RvIGEgbGEgem9uYSBnZW9ncsOhZmljYS4gUG9yIGNvbnNpZ3VpZW50ZSwgZXN0b3MgcmVzdWx0YWRvcyBzdWdpZXJlbiBxdWUgb3Ryb3MgZmFjdG9yZXMgcG9kcsOtYW4gZXN0YXIgaW5mbHV5ZW5kbyBlbiBsYSBmb3JtYWNpw7NuIGRlIGxvcyBjbMO6c3RlcmVzLg0KDQojIyMjIEluZ2VuaWVyw61hIGRlIFZhcmlhYmxlcyAoUENBKSB7LnRhYnNldH0NCg0KYGBge3J9DQpob3VzaW5nX3BjYSA8LSBwcmNvbXAoaG91c2luZ19zY2FsZWQsIHNjYWxlID0gVFJVRSkNCmhvdXNpbmdfcGNhX2RmIDwtIGRhdGEuZnJhbWUoaG91c2luZ19wY2EkeCwgY2x1c3RlciA9IGhvdXNpbmdfY2x1c3RlciRjbHVzdGVyKQ0KZ2dwbG90KGhvdXNpbmdfcGNhX2RmLCBhZXMoeCA9IFBDMSwgeSA9IFBDMiwgY29sb3IgPSBmYWN0b3IoY2x1c3RlcikpKSArIGdlb21fcG9pbnQoKSArIGxhYnMoeCA9ICJQQzEiLCB5ID0gIlBDMiIpDQpgYGANCg0KYGBge3J9DQp0c25lID0gUnRzbmUoaG91c2luZ19zY2FsZWQsIHBlcnBsZXhpdHkgPSAzMCwgY2hlY2tfZHVwbGljYXRlcyA9IEZBTFNFKQ0KcGxvdCh0c25lJFksIGNvbCA9ICJibGFjayIsIGJnPSBob3VzaW5nX2NsdXN0ZXIkY2x1c3RlciwgcGNoID0gMjEsIGNleCA9IDEpDQpgYGANCg0KQSB0cmF2w6lzIGRlbCBncsOhZmljbyBkZSBBbsOhbGlzaXMgZGUgQ29tcG9uZW50ZXMgUHJpbmNpcGFsZXMgKFBDQSksIGVzIHBvc2libGUgZGlzdGluZ3VpciBjbGFyYW1lbnRlIGxhcyBjdWF0cm8gYWdydXBhY2lvbmVzLCBsbyBjdWFsIGNvaW5jaWRlIGNvbiBlbCBvYmpldGl2byBkZSBLLU1lYW5zIGRlIG1pbmltaXphciBsYSB2YXJpYW56YSBlbnRyZSBsb3MgZ3J1cG9zIGdlbmVyYWRvcy4gU2luIGVtYmFyZ28sIGVzIGltcG9ydGFudGUgcmVzYWx0YXIgcXVlIGVzdG9zIGdydXBvcyBjYXJlY2VuIGRlIHNpZ25pZmljYWRvIHNlbcOhbnRpY28geSBzb24gbcOhcyBiaWVuIGNvbnN0cnVjdG9zIG1hdGVtw6F0aWNvcyBvIGFic3RyYWN0b3MuIEVzdG8gc2UgZGViZSBhIHF1ZSBubyBzZSBwdWVkZSBlc3RhYmxlY2VyIHVuYSBhc29jaWFjacOzbiBjb250ZXh0dWFsIHF1ZSBwZXJtaXRhIGV0aXF1ZXRhciBvIGNhdGVnb3JpemFyIGFkZWN1YWRhbWVudGUgZGljaG9zIGdydXBvcyBzZWfDum4gZWwgZG9taW5pbyBkZSBlc3R1ZGlvLg0KDQpQb2RlbW9zIG9ic2VydmFyIHF1ZSBlbiBlbCBncsOhZmljbyBkZSBBbsOhbGlzaXMgZGUgQ29tcG9uZW50ZXMgUHJpbmNpcGFsZXMgKFBDQSkgbm8gc2UgcHJlc2VudGEgdW5hIHJlbGFjacOzbiBsaW5lYWwgY2xhcmEgZW50cmUgbG9zIGRhdG9zLCBsbyBjdWFsIGNvbmN1ZXJkYSBjb24gbGFzIG9ic2VydmFjaW9uZXMgaW5pY2lhbGVzIGFjZXJjYSBkZSBsYSBmYWx0YSBkZSBsaW5lYWxpZGFkIGVuIGVsIGNvbmp1bnRvIGRlIGRhdG9zLiBQb3IgZXN0YSByYXrDs24sIGhlbW9zIGRlY2lkaWRvIGV4YW1pbmFyIGxhIGVzdHJ1Y3R1cmEgZGUgbG9zIGRhdG9zIHV0aWxpemFuZG8gUnRzbmUsIHVuYSB0w6ljbmljYSBxdWUgcGVybWl0ZSwgYWwgaWd1YWwgcXVlIFBDQSwgIG1hcGVhciBsb3MgZGF0b3MgZW4gdW4gZXNwYWNpbyBkZSBtZW5vciBkaW1lbnNpw7NuIG1pZW50cmFzIGludGVudGEgcHJlc2VydmFyIGxhcyByZWxhY2lvbmVzIGRlIHZlY2luZGFkIGVudHJlIGVsbG9zLiBTaW4gZW1iYXJnbywgcmVzdWx0YSBldmlkZW50ZSBxdWUgaW5jbHVzbyBlc3RhIGhlcnJhbWllbnRhIG5vIGxvZ3JhIGRpc2NyaW1pbmFyIGxvcyBncnVwb3MgZGUgbWFuZXJhIHByZWNpc2EuIEVzdGUgaGVjaG8gY29taWVuemEgYSBzdWdlcmlyIGxhIHBvc2libGUgaW5zdWZpY2llbmNpYSBkZSBjYXJhY3RlcsOtc3RpY2FzIChmZWF0dXJlcykgcGFyYSBsb2dyYXIgdW5hIGRpc2NyaW1pbmFjacOzbiBtw6FzIGVmZWN0aXZhIGVudHJlIGxvcyBncnVwb3MsIGxvIHF1ZSBhIHN1IHZleiBhZmVjdGEgbGEgaWRlbnRpZmljYWNpw7NuIGRlIHBhdHJvbmVzIGRlbnRybyBkZSBlc3RvcyBtaXNtb3MgZ3J1cG9zLg0KDQpFc3RlIGhhbGxhemdvIHN1YnJheWEgbGEgbmVjZXNpZGFkIGRlIGNvbnNpZGVyYXIgdW5hIGV4cGxvcmFjacOzbiBtw6FzIGV4aGF1c3RpdmEgZGUgbGFzIGNhcmFjdGVyw61zdGljYXMgdXRpbGl6YWRhcyBlbiBlbCBhbsOhbGlzaXMgeSBsYSBwb3NpYmlsaWRhZCBkZSBpbmNvcnBvcmFyIGVsZW1lbnRvcyBhZGljaW9uYWxlcyBxdWUgcHVlZGFuIGNhcHR1cmFyIGRlIG1hbmVyYSBtw6FzIHByZWNpc2EgbGFzIGRpZmVyZW5jaWFzIHkgcmVsYWNpb25lcyBlbnRyZSBsb3MgZ3J1cG9zLiBFc3RlIGVuZm9xdWUgbcOhcyBjb21wbGV0byBwb2Ryw61hIGF5dWRhciBhIHJldmVsYXIgcGF0cm9uZXMgeSByZWxhY2lvbmVzIHF1ZSBhY3R1YWxtZW50ZSBubyBlc3TDoW4gc2llbmRvIGRldGVjdGFkb3MgZGViaWRvIGEgbGEgbGltaXRhY2nDs24gZGUgbGFzIGNhcmFjdGVyw61zdGljYXMgdXRpbGl6YWRhcyBoYXN0YSBhaG9yYS4NCg0KRXN0YSBwZXJjZXBjacOzbiBzZSByZWZ1ZXJ6YSBhbCBleGFtaW5hciBsb3MgcmVzdWx0YWRvcyBkZWwgcmVzdW1lbiBkZWwgY2x1c3RlciAxIGVuIHJlbGFjacOzbiBjb24gZWwgcmFuZ28gZGUgdmFsb3JlcyBwcmVzZW50ZXMgZW4gbGFzIHZhcmlhYmxlcyBxdWUgY29uZm9ybWFuIGVzdGUgZ3J1cG8uIFVuYSB2ZXogbcOhcywgZXN0YSBvYnNlcnZhY2nDs24gc3VicmF5YSBsYSBmYWx0YSBkZSB1biBwYXRyw7NuIHNlbcOhbnRpY28gZGlzY2VybmlibGUuIEVuIGVzdGUgc2VudGlkbywgZXMgZnVuZGFtZW50YWwgcmVjb25vY2VyIHF1ZSBsYSBpbnRlcnByZXRhY2nDs24geSBhcGxpY2FjacOzbiBkZSBlc3RvcyBncnVwb3MgcmVxdWllcmVuIHVuIGVuZm9xdWUgbcOhcyBwcm9mdW5kbyB5IGNvbnRleHR1YWwgcGFyYSBjb21wcmVuZGVyIHNpIGV4aXN0ZW4gZmFjdG9yZXMgc3VieWFjZW50ZXMgcXVlIHB1ZWRhbiBhcG9ydGFyIHNlbnRpZG8gYSBlc3RvcyBhZ3J1cGFtaWVudG9zIGRlc2RlIHVuYSBwZXJzcGVjdGl2YSBtw6FzIGFsbMOhIGRlIGxvIG1lcmFtZW50ZSBtb2RlbGFkby4NCg0KIyMjIyMgQ2x1c3RlciAxDQoNCmBgYHtyfQ0Kc3VtbWFyeShob3VzaW5nW2hvdXNpbmckY2x1c3RlciA9PSAxLF0pDQpgYGANCg0KIyMjIyMgQ2x1c3RlciAyDQoNCmBgYHtyfQ0Kc3VtbWFyeShob3VzaW5nW2hvdXNpbmckY2x1c3RlciA9PSAyLF0pDQpgYGANCg0KIyMjIyMgQ2x1c3RlciAzDQoNCmBgYHtyfQ0Kc3VtbWFyeShob3VzaW5nW2hvdXNpbmckY2x1c3RlciA9PSAzLF0pDQpgYGANCg0KIyMjIyMgQ2x1c3RlciA0DQoNCmBgYHtyfQ0Kc3VtbWFyeShob3VzaW5nW2hvdXNpbmckY2x1c3RlciA9PSA0LF0pDQpgYGANCg0KIyMjIyB7LX0NCg0KIyMjIyBDb3JyZXNwb25kZW5jaWEgey50YWJzZXR9DQoNCkFob3JhIHByb2NlZGVyZW1vcyBhIGFuYWxpemFyIGxhcyBwb3NpYmxlcyByZWxhY2lvbmVzIHkgY29udHJpYnVjaW9uZXMgZGUgbGFzIHZhcmlhYmxlcyBtZWRpYW50ZSB1biBBbsOhbGlzaXMgZGUgQ29ycmVzcG9uZGVuY2lhIHV0aWxpemFuZG8gRkFNRCAoQW7DoWxpc2lzIGRlIEZhY3RvcmVzIE3Dumx0aXBsZXMgZGUgQ29ycmVzcG9uZGVuY2lhKSwgZWwgY3VhbCBhYmFyY2EgdGFudG8gdmFyaWFibGVzIG51bcOpcmljYXMgY29tbyBjYXRlZ8OzcmljYXMuIEVzdGEgdMOpY25pY2Egbm9zIHBlcm1pdGlyw6EgZXhwbG9yYXIgZGUgbWFuZXJhIGludGVncmFsIGPDs21vIGxhcyBkaWZlcmVudGVzIHZhcmlhYmxlcyBpbnRlcmFjdMO6YW4geSBjb250cmlidXllbiBlbiBlbCBjb25qdW50byBkZSBkYXRvcywgcmV2ZWxhbmRvIHBhdHJvbmVzIGRlIGFzb2NpYWNpw7NuIHF1ZSBwb2Ryw61hbiBubyBzZXIgZXZpZGVudGVzIGVuIGFuw6FsaXNpcyBhbnRlcmlvcmVzLg0KDQpgYGB7cn0NCmhvdXNpbmdfc2NhbGVkID0gYXMuZGF0YS5mcmFtZShob3VzaW5nX3NjYWxlZCkNCmhvdXNpbmdfc2NhbGVkJHRpcG8gPSBob3VzaW5nJHRpcG8NCmhvdXNpbmdfc2NhbGVkJHpvbmEgPSBob3VzaW5nJHpvbmENCmhvdXNpbmdfc2NhbGVkJGJhcnJpbyA9IGhvdXNpbmckYmFycmlvDQoNCnJlcy5mYW1kIDwtIEZBTUQoaG91c2luZ1ssYygiem9uYSIsInBpc28iLCJlc3RyYXRvIiwicHJlY2lvbSIsImFyZWFjb25zdCIsInBhcnF1ZWFkZXJvcyIsImJhbmlvcyIsImhhYml0YWNpb25lcyIsInRpcG8iLCJiYXJyaW8iLCJsb25naXR1ZCIsImxhdGl0dWQiKV0sIGdyYXBoID0gRkFMU0UpDQpgYGANCg0KIyMjIyMgU2NyZWUgUGxvdA0KYGBge3J9DQpmdml6X3NjcmVlcGxvdChyZXMuZmFtZCkNCmBgYA0KDQpTZSBwdWVkZSBldmlkZW5jaWFyIHF1ZSBlbiAyIGRpbWVuc2lvbmVzIGxhcyB2YXJpYWJsZXMgdGFudG8gY2F0ZWdvcmljYXMgY29tbyBuw7ptZXJpY2FzIHJlcHJlc2VudGEgZWwgMS4wNzUlIGRlIGxvcyBkYXRvcywgbG8gY3VhbCBlcXVpdmFsZSBhIHVuIHZhbG9yIG11eSBpbmZlcmlvci4NCg0KIyMjIyMgVmFyaWFibGVzDQpgYGB7cn0NCmZ2aXpfZmFtZF92YXIocmVzLmZhbWQsIHJlcGVsID0gVFJVRSkNCmBgYA0KDQpMYXMgdmFyaWFibGVzIGJhcnJpbywgYXJlYSwgcGFycXVlYWRlcm9zLCBiYcOxbyB5IHByZWNpbyBzb24gbGFzIG1lam9yZXMgcGFyYSBkaXNjcmltaW5hciBzaWVuZG8gbXV5IGFwYXJ0ZSBiYXJyaW8gcXVlIGV4cGxpY2EgbXV5IGJpZW4gbGEgdmFyaWFuemEgZW4gbGFzIGRvcyBkaW1lbnNpb25lcyBxdWUgbcOhcyBleHBsaWNhbiBsYSB2YXJpYW56YSBkZSBsb3MgZGF0b3MuDQoNCiMjIyMjIE51bWVyaWNhbA0KYGBge3J9DQpmdml6X2ZhbWRfdmFyKHJlcy5mYW1kLCAicXVhbnRpLnZhciIsIGNvbC52YXIgPSAiY29udHJpYiIsIA0KICAgICAgICAgICAgIGdyYWRpZW50LmNvbHMgPSBjKCIjMDBBRkJCIiwgIiNFN0I4MDAiLCAiI0ZDNEUwNyIpLA0KICAgICAgICAgICAgIHJlcGVsID0gVFJVRSkNCmBgYA0KDQpMYXMgdmFyaWFibGVzIHF1ZSB0aWVuZW4gdW4gdmluY3VsbyBwcm9wb3JjaW9uYWwgc29uIGhhYml0YWNpb25lcyBhcmVhY29uc3QgYmHDsW9zIHkgcGFycXVlYWRlcm9zLCB5IGxhIGNvcnJlbGFjaW9uIGludmVyc2Egc2UgcHVlZGUgb2JzZXJ2YXIgZW4gcGlzbyB2cyBoYWJpdGFjaW9uZXMgeSBlc3RyYXRvIHZzIHViaWNhY2nDs24gKGxvbmdpdHVkIHkgbGF0aXR1ZCkuDQoNCiMjIyMjIERpbSAxDQpgYGB7cn0NCmZ2aXpfY29udHJpYihyZXMuZmFtZCwgInZhciIsIGF4ZXMgPSAxKQ0KYGBgDQoNCkRlbnRybyBkZSBsYSBkaW1lbnNpb24gMSBzZSBldmlkZW5jaWEgcXVlIGxhcyB2YXJpYWJsZXMgcXVlIHRpZW5lbiBtYXMgaW5mbHVlbmNpYSBlbiBsb3MgZGF0b3Mgc29uIGxhcyBxdWUgcmVwcmVzZW50YW4gZW50cmUgMTAgYSAxNSAlIGRlIGNvbnRyaWJ1Y2nDs24gKHByZWNpbywgYmHDsW9zLCBiYXJyaW8sIHBhcnF1ZWFkZXJvcywgYXJlYSwgZXN0cmF0bykuDQoNCiMjIyMjIERpbSAyDQpgYGB7cn0NCmZ2aXpfY29udHJpYihyZXMuZmFtZCwgInZhciIsIGF4ZXMgPSAyKQ0KYGBgDQoNCkRlbnRybyBkZSBsYSBkaW1lbnNpb24gMiBzZSBldmlkZW5jaWEgcXVlIGxhcyB2YXJpYWJsZXMgcXVlIHRpZW5lbiBtYXMgaW5mbHVlbmNpYSBlbiBsb3MgZGF0b3Mgc29uIGxhcyBxdWUgcmVwcmVzZW50YW4gZW50cmUgMTAgYSAyNSAlIGRlIGNvbnRyaWJ1Y2nDs24gKGJhcnJpbywgem9uYSwgdGlwbywgaGFiaXRhY2lvbmVzKS4NCg0KIyMjIyB7LX0NCg0KIyMjIENvbmNsdXNpw7NuDQoNClNlZ8O6biBlbCBhbsOhbGlzaXMgZGVsIGVqZXJjaWNpbyBwcm9wdWVzdG8gc2UgZXNwZXJhYmEgZW5jb250cmFyIHBhdHJvbmVzIHF1ZSBheXVkYXJhbiBhIGlkZW50aWZpY2FyIGxhIGFncnVwYWNpw7NuIGRlIGxvcyBkYXRvcyBtZWRpYW50ZSBsYSB0ZWNuaWNhIGRlIENsdXN0ZXIsIFBDQSwgeSBDb3JyZXNwb25kZW5jaWEsIHBlcm8gZHVyYW50ZSBlbCBkZXNhcnJvbGxvIGRlbCBjYXNvIHNlIGlkZW50aWZpY8OzIHF1ZSBhdW5xdWUgc2UgZXZpZGVuY2llbiA0IGNsdXN0ZXJzIGEgbml2ZWwgbWF0ZW3DoXRpY28gc2Vnw7puIGxhIHZhcmlhbnphIGRlIGxvcyBkYXRvcyBlbiBlbCBkaWFncmFtYSBkZSBQQ0EsIG5vIHNlIGRldGFsbGFuIHBhdHJvbmVzIGEgbml2ZWwgc2Vtw6FudGljbyBvIGRlIGRvbWluaW8gcXVlIHBlcm1pdGFuIGRhciBjb25jbHVzaW9uZXMgcGFyYSBlbCBtZXJjYWRvIGlubW9iaWxpYXJpbywgbG8gcXVlIGluZGljYSBxdWUgYWwgY29uanVudG8gZGUgZGF0b3MgcG9kcsOtYW4gYWRpY2lvbmFyIG11bHRpcGxlcyB2YXJpYWJsZXMgcGFyYSBkZXRlcm1pbmFyIGEgbml2ZWwgZGUgbmVnb2NpbyBsb3MgcGF0cm9uZXMgZGUgYWdydXBhbWllbnRvIGNvbW8gcG9yIGVqZW1wbG8gKG5pdmVsIGRlIHJ1w61kbywgc2VndXJpZGFkLCBjbGltYSwgYWNjZXNhYmlsaWRhZCBhbCBzZXJ2aWNpbykgeSBkZW3DoXMgdmFyaWFibGVzIHF1ZSBwdWVkYW4gY29uc2lkZXJhcnNlIGRlbnRybyBkZWwgbWVyY2FkbyBpbm1vYmlsaWFyaW8geSBxdWUgcHVlZGFuIG1vc3RyYXIgdW4gcG9yY2VudGFqZSBtYXMgYWx0byBkZSByZXByZXNlbnRhdGl2aWRhZCwgbG8gYW50ZXJpb3IgcHVlZGUgZGViZXJzZSBhIGxhIGdyYW4gY2FudGlkYWQgZGUgbWlzc2luZyB2YWx1ZXMgcHJlc2VudGVzIGVuIGxhcyB2YXJpYWJsZXMgcmVwcmVzZW50YXRpdmFzIGVuIGVsIGRhdGFzZXQuDQoNCk5vIE9ic3RhbnRlIGZ1ZSBwb3NpYmxlIG9ic2VydmFyIHNlZ8O6biBsYSBjb3JyZXNwb25kZW5jaWEgdW5hIGZ1ZXJ0ZSBpbmZsdWVuY2lhIGVuIGxhIHZhcmlhYmxlIGJhcnJpbyBwb3Igc2VyIGxhIHF1ZSBtZWpvciByZXByZXNlbnRhIGEgbml2ZWwgZGUgdmFyaWFuemEgZGUgbG9zIGRhdG9zLCBhZGljaW9uYWwgcXVlIGRlbnRybyBkZSBsYXMgdmFyaWFibGVzIG51bcOpcmljYXMgc2UgZXZpZGVuY2lhIHVuYSBidWVuYSBjb3JyZWxhY2lvbiBlbnRyZSBoYWJpdGFjaW9uZXMsIGFyZWEgY29uc3RydWlkYSwgYmHDsW9zIHkgcGFycXVlYWRlcm9zLg0KDQpQYXJhIGVsIGRlc2Fycm9sbG8gZGVsIGVqZXJjaWNpbyBzZSB1dGlsaXrDsyBsYSBpbXB1dGFjacOzbiBwb3IgZWwgbcOpdG9kbyBLTk4gcGFyYSBkYXRvcyBudWxvcywgY29uY2x1eWVuZG8gcXVlIGZ1ZSB1bmEgYnVlbmEgaW1wbGVtZW50YWNpw7NuIHlhIHF1ZSBwYXJhIGludGVudGFyIGF1bWVudGFyIHNpZ25pZmljYXRpdmFtZW50ZSBlbCAlIGRlIHJlcHJlc2VudGF0aXZpZGFkIHNlIGFuYWxpesOzIGxhIGNvcnJlc3BvbmRlbmNpYSBjb24gZWwgZGF0YXNldCBjb21wbGV0byBleGNsdXllbmRvIGxvcyBkYXRvcyBmYWx0YW50ZXMgZGFuZG8gY29tbyByZXN1bHRhZG8gdW4gcG9yY2VudGFqZSBzaW1pbGFyIGRlIHJlcHJlc2VudGF0aXZpZGFkIGFsIGFudGVyaW9yLCBwb3IgbG8gdGFudG8sIGVsIG3DqXRvZG8gS05OIGZ1ZSB1bmEgZXN0cmF0ZWdpYSBjbGF2ZSBwYXJhIGNvbXBsZW1lbnRhciBsb3MgZGF0b3MgZmFsdGFudGVzLg0KDQoNCg==