Trabajo Final Machine Learning

Introducción:

Este análisis forma parte de la evaluación final del módulo Machine Learning. La idea es llevar adelante un análisis de los siniestros en la Ciudad de Buenos Aires utilizando la técnica de Regresión Logística para su clasificación. Este trabajo se encuentra disponible en mi blog personal

Objetivo:

Visualizar espacialmente los siniestros considerados de gravedad ocurridos en la Ciudad Autónoma de Buenos Aires utilizando como fuente los datos abiertos publicados por el municipio.

Analizar y modelar bajo la lógica de clasificación mediante regresión logística la naturaleza de los accidentes de transito y su mortalidad durante el período 2019-2023.

Metodología:

Para llevar adelante los objetivos planteados y, considerando que lo que se busca es encontrar un modelo de clasificación se utilizará el conjunto de técnicas específicas de regresión logística. Esta técnica se utiliza para clasificar una variable respuesta que, en este caso será dicotómica. Para ello, resultará de gran utilidad los resultados y el análisis de las estadísticas del modelo problematizando el grado de mortalidad en los siniestros acontecidos en CABA.

Fuentes de datos:

A los fines de los ideas planteadas, trabajaremos con los datos de siniestros ocurridos durante el período 2019-2023. Según las notas metodológicas, las categorías de la variable “gravedad” varian en:

GRAVE: Toda persona que cuya lesión exige la hospitalización de al menos 24 hs o una atención especializada, como fracturas, conmoción, shock grave y laceraciones importantes. MORTAL: Víctima que fallece dentro de los 30 días de producido el siniestro vial por causas directa o indirectamente atribuibles al hecho. SD: Sin datos sobre la gravedad de las lesiones provocadas. A efectos analíticos, los casos sin datos se corresponden con una alta probabilidad a casos leves.

Procederemos a considerar sólamente los casos declarados como “graves” o “mortales” a efectos de entrenar un modelo que permita clasificarlos.

Instalamos librerias

rm(list = ls()) 
options(scipen = 999)

###################################################################################
# Instalamos librerías de trabajo
if (!require("pacman")) install.packages("pacman")
pacman::p_load("lubridate",
               "tidyverse",
               "leaflet",
               "sf",
               "tidymodels",
               "spatialsample",
               "themis",
               "ggmap",
               "showtext",
               "ggtext")

###################################################################################                                                                                                               

Limpieza y transformación

A los fines de abordar las distintas formas de analizar los datos generaremos dos datasets, uno espacial para mapear los sucesos y otro dataset sin generar geometrías espaciales para construir el modelo. Se advierte que, debido a los filtros aplicados, el dataset espacial cuenta con 1507 casos, mientras que el dataset sin considerar geometrías espaciales contiene 1844 sinisetros.

Levantamos los datos y realizamos una limpieza y recodificación de las variables de interés haciendo hincapié en la variable objetivo que es “gravedad” la cual en una primera vista, tiene datos en mayúsculas y minúsculas en un claro error de ingreso de datos. Tambien procedemos a recodificar la variable hora (hh) según los momentos del día.

siniestros_geo <- read.csv("data/siniestros_viales_hechos.csv", sep = ";") %>% 
  filter(!latitud %in% c("SD", "","sd"),
         !gravedad %in% c("SD","sd")) %>% 
  mutate(longitud = as.numeric(longitud),
         latitud = as.numeric(latitud),
         hh = as.numeric(hh),
         "HoraRec" = case_when(hh >= 0 & hh < 6 ~ "Madrugada", 
                               hh >= 6 & hh < 12 ~ "Mañana",
                               hh >= 12 & hh < 18 ~ "Tarde",
                               TRUE ~ "Noche" ),
         gravedad = case_when(gravedad %in%  c("GRAVE","grave") ~ "Grave",
                                    gravedad == "MORTAL" ~ "Mortal"))%>%
  filter(!is.na(latitud)) %>% 
  st_as_sf(coords =c("longitud", "latitud"), crs = 4326) %>% 
  select(!c(direccion_normalizada,fecha,calle, altura, cruce, otra_direccion, hora, comuna, tipo_de_calle, geocodificacion_caba,tipo_de_dato, participantes))


siniestros <- read.csv("data/siniestros_viales_hechos.csv", sep = ";") %>%
  filter(!gravedad %in% c("SD","sd")) %>% 
  mutate(longitud = as.numeric(longitud),
         latitud = as.numeric(latitud),
         hh = as.numeric(hh),
         "HoraRec" = case_when(hh >= 0 & hh < 6 ~ "Madrugada", 
                               hh >= 6 & hh < 12 ~ "Mañana",
                               hh >= 12 & hh < 18 ~ "Tarde",
                               TRUE ~ "Noche" ),
         gravedad = case_when(gravedad %in%  c("GRAVE","grave") ~ "Grave",
                                    gravedad == "MORTAL" ~ "Mortal"))%>%
  select(!c(direccion_normalizada,fecha,calle, altura, cruce, otra_direccion, hora, comuna, tipo_de_calle, geocodificacion_caba,tipo_de_dato, participantes))


comunas <- st_read("data/comunas.geoson", 
                  crs = 4326)
## Reading layer `comunas' from data source 
##   `C:\Users\Guille\Documents\Proyectos\FLACSO\Machine Learning\tp final\data\comunas.geoson' 
##   using driver `GeoJSON'
## Simple feature collection with 15 features and 6 fields
## Geometry type: MULTIPOLYGON
## Dimension:     XY
## Bounding box:  xmin: -58.53152 ymin: -34.70529 xmax: -58.33516 ymax: -34.52649
## Geodetic CRS:  WGS 84
tabla_1 <- siniestros %>% 
  group_by(aaaa, gravedad) %>% 
  summarise(tot = n()) %>% 
  mutate(porcentaje = round(tot/sum(tot),4)) %>% 
  mutate(Total_Acum = cumsum(tot)) %>% 
  mutate(Porcentaje_Acum = cumsum(porcentaje))

ggplot(tabla_1, aes(x = aaaa, y = tot, fill = gravedad))+
  geom_col(position = "dodge")+
  geom_text(aes(label = percent(porcentaje)), vjust = -0.5, check_overlap = TRUE, position = position_dodge(width = .9), size = 3)+
  scale_fill_manual(values = c("#f55951","#000000"))+
  theme_minimal()+
  labs(
    title = "<span style = 'color:#272643;'> Evolución anual de siniestros: <span style = 'color:#f55951;'> Graves <span style = 'color:#543c52;'> - <span style = 'color:#000000;'> Mortales",
    subtitle = "",
    caption = "<b>Fuente</b>: ReNaBaP",
    x = "Año",
    y = "Barrios Populares"
  )+
  theme(
    legend.position = "none",
    plot.title = element_markdown(face = "bold", size = 15),
    plot.caption = element_markdown(size = 12),
  )

ggplot()+
  geom_sf(data=comunas)+
  geom_sf(data=siniestros_geo, aes(color=gravedad), alpha=0.75, show.legend = TRUE)+
  scale_color_manual(values = c("#f55951","#000000"))+
  theme_minimal()+
  theme_void()+
  labs(
    title = "<span style = 'color:#272643;'> Siniestros viales - CABA 2019-2023",
    caption = "<b>Fuente</b>: Datos Abiertos CABA",
    y = "",
    x = ""
  )+
  theme(
    plot.title = element_markdown(size = 20, hjust = 0.5),
    plot.caption= element_markdown(size = 12),
    legend.position = "top",
    legend.title = element_blank(),
    legend.box.margin = margin(11, 16, 6, 6)
  )+
  facet_wrap(~aaaa, nrow = 1)+
  guides(fill=guide_legend(nrow=1,byrow=TRUE))

Al aplicar filtros vinculados a eliminar registros sin georeferenciación, el año 2019 sólo contiene casos mortales. Los años siguientes contienen una mayor cantidad de casos de gravedad manteniendo ciertos patrones para los siniestros fatales.

A continuación, se muestra un mapa interactivo para visualizar la totalidad de casos registrados en el dataset con geometrías espaciales.

factpal <- colorFactor(palette = c("#f55951","#000000"), 
               levels = siniestros_geo$gravedad)

oceanIcons <- iconList(
  ship = makeIcon(
    "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Medical_hospital_emergency_beds.svg/240px-Medical_hospital_emergency_beds.svg.png",
    "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Medical_hospital_emergency_beds.svg/240px-Medical_hospital_emergency_beds.svg.png",
    18,
    18
  ),
  pirate = makeIcon(
    "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c3/Maki2-danger-24.svg/240px-Maki2-danger-24.svg.png",
    "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c3/Maki2-danger-24.svg/24px-Maki2-danger-24.svg.png",
    24,
    24
  )
)


 mapa_interactivo <- leaflet() %>%
     addTiles() %>%
     addMarkers(data = siniestros_geo, icon = ~oceanIcons, clusterOptions = markerClusterOptions(),popup = ~ glue::glue("<b>Estado</b>:{gravedad}<br/>
                                                                    <b>Año:</b>{aaaa}<br/>
                                                                    <b>Cantidad de Víctimas:</b>{n_victimas}<br/>
                                                                    <b>Hora:</b>{hh}<br/>"))

mapa_interactivo

Modelado

En primer lugar procedemos a la confección de set de entrenamientos y testeos considerando 0.8 como proporción y tomando el dataseet que no contiene coordenadas geográficas.

#seteo la semilla
set.seed(123)
#divido los datos
sini_split <- initial_split(siniestros, prop = 0.80, strata = gravedad) #estratificamos segun las clases 
sini_train <- training(sini_split)
sini_test <- testing(sini_split)

Folds con validación cruzada

set.seed(234)
sini_folds <- vfold_cv(sini_train, strata = gravedad)
sini_folds
sini_train
sini_test 
sini_train %>%
  count(gravedad)
sini_test %>%
  count(gravedad)

Se verifica que las proporciones correspondientes a la distribución de las categorías de la variable “gravedad” se conservan.

Receta

El siguiente paso es determinar la receta del modelo tomano “gravedad” como la variable a calsificar.

logistic_recipe <-
  recipe(formula = gravedad ~ ., data = sini_train) %>%
  update_role(id_hecho, new_role = "id") %>%
  step_unknown(all_nominal_predictors()) %>%
  step_other(all_nominal_predictors(), threshold = 0.03) %>%
  step_downsample(gravedad)

Modelización

Especificamos el modelo, en este caso, una regresión logística.

logistic_spec <- logistic_reg()

Workflow

Agregamos al Workflow la receta y la especificación del modelo.

log_workflow <- workflow() %>%
  add_recipe(logistic_recipe) %>%
  add_model(logistic_spec)

Entrenamiento del algoritmo

Con la función fit_resamples() procedemos a hacer el ajuste correspondiente.

doParallel::registerDoParallel() 
set.seed(7443)
logistic_rs <-
  fit_resamples(log_workflow,
    resamples = sini_folds,
    control = control_resamples(save_pred = TRUE)
  )

Performance

logistic_rs %>%
  collect_metrics()

La performance muestra resultados aceptables en el set de entrenamiento. La precisión casi alcanza el 80%, mientras que el área bajo la curva ROC llega al valor de 0.88. El brier_class arroja valores bajos, lo cual es bueno para nuestro modelo

collect_predictions(logistic_rs) %>%
  group_by(id) %>%
  roc_curve(gravedad, .pred_Grave) %>%
  autoplot()

Predicción

En esta etapa con la función last_fit vamos a seleccionar el mejor modelo para predecir en el set de datos de TEST.

final_fitted <- last_fit(log_workflow, sini_split)
final_fitted 

Métricas

final_fitted %>%
  collect_metrics()

Las métricas del modelo final arrojan cifras aceptables para la precisión (78 %) y valores buenos para el área bajo la curva ROC. Tambien muestra valores bajos para brier_class

Predicciones del modelo

final_fitted %>%
  collect_predictions()

Matriz de confusión

Para finalizar y completar la consigna, vamos a crear una matriz de confusión con las predicciones para contrastarla con los casos reales.

final_fitted %>%
  collect_predictions() %>%
  conf_mat(gravedad, .pred_class) 
##           Truth
## Prediction Grave Mortal
##     Grave    155     16
##     Mortal    47     66

Como podemos ver la diagonal principal contiene la mayoría de los casos, dejando en valores bajos los falsos positivos y los falsos negativos.

Conclusión

A partir del empleo de técnicas de modelado, pudimos arribar una problemática severa de la movilidad urbana como lo son los siniestros. Con datos abiertos y técnicas de visualización y regresión logística construimos un modelo que, a pesar de la escasa cantidad de datos, obtuvo cifras de precisión considerables.

LS0tDQp0aXRsZTogIlRyYWJham8gRmluYWwgTWFjaGluZSBMZWFybmluZyINCmF1dGhvcjogIkp1YW4gR3VpbGxlcm1vIEZlcmNoZXJvIg0KZGF0ZTogImByIFN5cy5EYXRlKClgIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogIHBkZl9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSkNCm9wdGlvbnMoc2NpcGVuID0gOTk5KQ0KYGBgDQoNCiMgVHJhYmFqbyBGaW5hbCBNYWNoaW5lIExlYXJuaW5nDQoNCiMjIEludHJvZHVjY2nDs246DQoNCkVzdGUgYW7DoWxpc2lzIGZvcm1hIHBhcnRlIGRlIGxhIGV2YWx1YWNpw7NuIGZpbmFsIGRlbCBtw7NkdWxvIE1hY2hpbmUgTGVhcm5pbmcuIExhIGlkZWEgZXMgbGxldmFyIGFkZWxhbnRlIHVuIGFuw6FsaXNpcyBkZSBsb3Mgc2luaWVzdHJvcyBlbiBsYSBDaXVkYWQgZGUgQnVlbm9zIEFpcmVzIHV0aWxpemFuZG8gbGEgdMOpY25pY2EgZGUgUmVncmVzacOzbiBMb2fDrXN0aWNhIHBhcmEgc3UgY2xhc2lmaWNhY2nDs24uIEVzdGUgdHJhYmFqbyBzZSBlbmN1ZW50cmEgZGlzcG9uaWJsZSBlbiBtaSBbYmxvZyBwZXJzb25hbF0oaHR0cHM6Ly9ycHVicy5jb20vR3VpbGxlRmVyY2hlcm8vdHBfbWwpDQoNCiMjIyBPYmpldGl2bzoNCg0KVmlzdWFsaXphciBlc3BhY2lhbG1lbnRlIGxvcyBzaW5pZXN0cm9zIGNvbnNpZGVyYWRvcyBkZSBncmF2ZWRhZCBvY3Vycmlkb3MgZW4gbGEgQ2l1ZGFkIEF1dMOzbm9tYSBkZSBCdWVub3MgQWlyZXMgdXRpbGl6YW5kbyBjb21vIGZ1ZW50ZSBsb3MgZGF0b3MgYWJpZXJ0b3MgcHVibGljYWRvcyBwb3IgZWwgbXVuaWNpcGlvLg0KDQpBbmFsaXphciB5IG1vZGVsYXIgYmFqbyBsYSBsw7NnaWNhIGRlIGNsYXNpZmljYWNpw7NuIG1lZGlhbnRlIHJlZ3Jlc2nDs24gbG9nw61zdGljYSBsYSBuYXR1cmFsZXphIGRlIGxvcyBhY2NpZGVudGVzIGRlIHRyYW5zaXRvIHkgc3UgbW9ydGFsaWRhZCBkdXJhbnRlIGVsIHBlcsOtb2RvIDIwMTktMjAyMy4NCg0KDQojIyMgTWV0b2RvbG9nw61hOg0KDQpQYXJhIGxsZXZhciBhZGVsYW50ZSBsb3Mgb2JqZXRpdm9zIHBsYW50ZWFkb3MgeSwgY29uc2lkZXJhbmRvIHF1ZSBsbyBxdWUgc2UgYnVzY2EgZXMgZW5jb250cmFyIHVuIG1vZGVsbyBkZSBjbGFzaWZpY2FjacOzbiBzZSB1dGlsaXphcsOhIGVsIGNvbmp1bnRvIGRlIHTDqWNuaWNhcyBlc3BlY8OtZmljYXMgZGUgcmVncmVzacOzbiBsb2fDrXN0aWNhLiBFc3RhIHTDqWNuaWNhIHNlIHV0aWxpemEgcGFyYSBjbGFzaWZpY2FyIHVuYSB2YXJpYWJsZSByZXNwdWVzdGEgcXVlLCBlbiBlc3RlIGNhc28gc2Vyw6EgZGljb3TDs21pY2EuIFBhcmEgZWxsbywgcmVzdWx0YXLDoSBkZSBncmFuIHV0aWxpZGFkIGxvcyByZXN1bHRhZG9zIHkgZWwgYW7DoWxpc2lzIGRlIGxhcyBlc3RhZMOtc3RpY2FzIGRlbCBtb2RlbG8gcHJvYmxlbWF0aXphbmRvIGVsIGdyYWRvIGRlIG1vcnRhbGlkYWQgZW4gbG9zIHNpbmllc3Ryb3MgYWNvbnRlY2lkb3MgZW4gQ0FCQS4NCg0KIyMjIEZ1ZW50ZXMgZGUgZGF0b3M6DQoNCkEgbG9zIGZpbmVzIGRlIGxvcyBpZGVhcyBwbGFudGVhZGFzLCB0cmFiYWphcmVtb3MgY29uIGxvcyBbZGF0b3MgZGUgc2luaWVzdHJvc10oaHR0cHM6Ly9kYXRhLmJ1ZW5vc2FpcmVzLmdvYi5hci9kYXRhc2V0L3ZpY3RpbWFzLXNpbmllc3Ryb3MtdmlhbGVzKSBvY3Vycmlkb3MgZHVyYW50ZSBlbCBwZXLDrW9kbyAyMDE5LTIwMjMuIFNlZ8O6biBsYXMgbm90YXMgbWV0b2RvbMOzZ2ljYXMsIGxhcyBjYXRlZ29yw61hcyBkZSBsYSB2YXJpYWJsZSAiZ3JhdmVkYWQiIHZhcmlhbiBlbjoNCg0KR1JBVkU6IFRvZGEgcGVyc29uYSBxdWUgY3V5YSBsZXNpw7NuIGV4aWdlIGxhIGhvc3BpdGFsaXphY2nDs24gZGUgYWwgbWVub3MgMjQgaHMgbyB1bmEgYXRlbmNpw7NuIGVzcGVjaWFsaXphZGEsIGNvbW8gZnJhY3R1cmFzLCBjb25tb2Npw7NuLCBzaG9jayBncmF2ZSB5IGxhY2VyYWNpb25lcyBpbXBvcnRhbnRlcy4NCk1PUlRBTDogVsOtY3RpbWEgcXVlIGZhbGxlY2UgZGVudHJvIGRlIGxvcyAzMCBkw61hcyBkZSBwcm9kdWNpZG8gZWwgc2luaWVzdHJvIHZpYWwgcG9yIGNhdXNhcyBkaXJlY3RhIG8gaW5kaXJlY3RhbWVudGUgYXRyaWJ1aWJsZXMgYWwgaGVjaG8uDQpTRDogU2luIGRhdG9zIHNvYnJlIGxhIGdyYXZlZGFkIGRlIGxhcyBsZXNpb25lcyBwcm92b2NhZGFzLiBBIGVmZWN0b3MgYW5hbMOtdGljb3MsIGxvcyBjYXNvcyBzaW4gZGF0b3Mgc2UgY29ycmVzcG9uZGVuIGNvbg0KdW5hIGFsdGEgcHJvYmFiaWxpZGFkIGEgY2Fzb3MgbGV2ZXMuDQoNClByb2NlZGVyZW1vcyBhIGNvbnNpZGVyYXIgc8OzbGFtZW50ZSBsb3MgY2Fzb3MgZGVjbGFyYWRvcyBjb21vICJncmF2ZXMiIG8gIm1vcnRhbGVzIiBhIGVmZWN0b3MgZGUgZW50cmVuYXIgdW4gbW9kZWxvIHF1ZSBwZXJtaXRhIGNsYXNpZmljYXJsb3MuIA0KDQoNCiMjIEluc3RhbGFtb3MgbGlicmVyaWFzDQoNCmBgYHtyIGxpYnJlcmlhcywgd2FybmluZz1GQUxTRX0NCg0Kcm0obGlzdCA9IGxzKCkpIA0Kb3B0aW9ucyhzY2lwZW4gPSA5OTkpDQoNCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQojIEluc3RhbGFtb3MgbGlicmVyw61hcyBkZSB0cmFiYWpvDQppZiAoIXJlcXVpcmUoInBhY21hbiIpKSBpbnN0YWxsLnBhY2thZ2VzKCJwYWNtYW4iKQ0KcGFjbWFuOjpwX2xvYWQoImx1YnJpZGF0ZSIsDQogICAgICAgICAgICAgICAidGlkeXZlcnNlIiwNCiAgICAgICAgICAgICAgICJsZWFmbGV0IiwNCiAgICAgICAgICAgICAgICJzZiIsDQogICAgICAgICAgICAgICAidGlkeW1vZGVscyIsDQogICAgICAgICAgICAgICAic3BhdGlhbHNhbXBsZSIsDQogICAgICAgICAgICAgICAidGhlbWlzIiwNCiAgICAgICAgICAgICAgICJnZ21hcCIsDQogICAgICAgICAgICAgICAic2hvd3RleHQiLA0KICAgICAgICAgICAgICAgImdndGV4dCIpDQoNCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQpgYGANCg0KDQojIyBMaW1waWV6YSB5IHRyYW5zZm9ybWFjacOzbg0KDQpBIGxvcyBmaW5lcyBkZSBhYm9yZGFyIGxhcyBkaXN0aW50YXMgZm9ybWFzIGRlIGFuYWxpemFyIGxvcyBkYXRvcyBnZW5lcmFyZW1vcyBkb3MgZGF0YXNldHMsIHVubyBlc3BhY2lhbCBwYXJhIG1hcGVhciBsb3Mgc3VjZXNvcyB5IG90cm8gZGF0YXNldCBzaW4gZ2VuZXJhciBnZW9tZXRyw61hcyBlc3BhY2lhbGVzIHBhcmEgY29uc3RydWlyIGVsIG1vZGVsby4gU2UgYWR2aWVydGUgcXVlLCBkZWJpZG8gYSBsb3MgZmlsdHJvcyBhcGxpY2Fkb3MsIGVsIGRhdGFzZXQgZXNwYWNpYWwgY3VlbnRhIGNvbiAxNTA3IGNhc29zLCBtaWVudHJhcyBxdWUgZWwgZGF0YXNldCBzaW4gY29uc2lkZXJhciBnZW9tZXRyw61hcyBlc3BhY2lhbGVzIGNvbnRpZW5lIDE4NDQgc2luaXNldHJvcy4gDQoNCkxldmFudGFtb3MgbG9zIGRhdG9zIHkgcmVhbGl6YW1vcyB1bmEgbGltcGllemEgeSByZWNvZGlmaWNhY2nDs24gZGUgbGFzIHZhcmlhYmxlcyBkZSBpbnRlcsOpcyBoYWNpZW5kbyBoaW5jYXBpw6kgZW4gbGEgdmFyaWFibGUgb2JqZXRpdm8gcXVlIGVzICJncmF2ZWRhZCIgbGEgY3VhbCBlbiB1bmEgcHJpbWVyYSB2aXN0YSwgdGllbmUgZGF0b3MgZW4gbWF5w7pzY3VsYXMgeSBtaW7DunNjdWxhcyBlbiB1biBjbGFybyBlcnJvciBkZSBpbmdyZXNvIGRlIGRhdG9zLiBUYW1iaWVuIHByb2NlZGVtb3MgYSByZWNvZGlmaWNhciBsYSB2YXJpYWJsZSBob3JhIChoaCkgc2Vnw7puIGxvcyBtb21lbnRvcyBkZWwgZMOtYS4gDQoNCmBgYHtyIGRhdGEsIHdhcm5pbmc9RkFMU0V9DQoNCnNpbmllc3Ryb3NfZ2VvIDwtIHJlYWQuY3N2KCJkYXRhL3Npbmllc3Ryb3NfdmlhbGVzX2hlY2hvcy5jc3YiLCBzZXAgPSAiOyIpICU+JSANCiAgZmlsdGVyKCFsYXRpdHVkICVpbiUgYygiU0QiLCAiIiwic2QiKSwNCiAgICAgICAgICFncmF2ZWRhZCAlaW4lIGMoIlNEIiwic2QiKSkgJT4lIA0KICBtdXRhdGUobG9uZ2l0dWQgPSBhcy5udW1lcmljKGxvbmdpdHVkKSwNCiAgICAgICAgIGxhdGl0dWQgPSBhcy5udW1lcmljKGxhdGl0dWQpLA0KICAgICAgICAgaGggPSBhcy5udW1lcmljKGhoKSwNCiAgICAgICAgICJIb3JhUmVjIiA9IGNhc2Vfd2hlbihoaCA+PSAwICYgaGggPCA2IH4gIk1hZHJ1Z2FkYSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhoID49IDYgJiBoaCA8IDEyIH4gIk1hw7FhbmEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhoID49IDEyICYgaGggPCAxOCB+ICJUYXJkZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+ICJOb2NoZSIgKSwNCiAgICAgICAgIGdyYXZlZGFkID0gY2FzZV93aGVuKGdyYXZlZGFkICVpbiUgIGMoIkdSQVZFIiwiZ3JhdmUiKSB+ICJHcmF2ZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncmF2ZWRhZCA9PSAiTU9SVEFMIiB+ICJNb3J0YWwiKSklPiUNCiAgZmlsdGVyKCFpcy5uYShsYXRpdHVkKSkgJT4lIA0KICBzdF9hc19zZihjb29yZHMgPWMoImxvbmdpdHVkIiwgImxhdGl0dWQiKSwgY3JzID0gNDMyNikgJT4lIA0KICBzZWxlY3QoIWMoZGlyZWNjaW9uX25vcm1hbGl6YWRhLGZlY2hhLGNhbGxlLCBhbHR1cmEsIGNydWNlLCBvdHJhX2RpcmVjY2lvbiwgaG9yYSwgY29tdW5hLCB0aXBvX2RlX2NhbGxlLCBnZW9jb2RpZmljYWNpb25fY2FiYSx0aXBvX2RlX2RhdG8sIHBhcnRpY2lwYW50ZXMpKQ0KDQoNCnNpbmllc3Ryb3MgPC0gcmVhZC5jc3YoImRhdGEvc2luaWVzdHJvc192aWFsZXNfaGVjaG9zLmNzdiIsIHNlcCA9ICI7IikgJT4lDQogIGZpbHRlcighZ3JhdmVkYWQgJWluJSBjKCJTRCIsInNkIikpICU+JSANCiAgbXV0YXRlKGxvbmdpdHVkID0gYXMubnVtZXJpYyhsb25naXR1ZCksDQogICAgICAgICBsYXRpdHVkID0gYXMubnVtZXJpYyhsYXRpdHVkKSwNCiAgICAgICAgIGhoID0gYXMubnVtZXJpYyhoaCksDQogICAgICAgICAiSG9yYVJlYyIgPSBjYXNlX3doZW4oaGggPj0gMCAmIGhoIDwgNiB+ICJNYWRydWdhZGEiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoaCA+PSA2ICYgaGggPCAxMiB+ICJNYcOxYW5hIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoaCA+PSAxMiAmIGhoIDwgMTggfiAiVGFyZGUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiAiTm9jaGUiICksDQogICAgICAgICBncmF2ZWRhZCA9IGNhc2Vfd2hlbihncmF2ZWRhZCAlaW4lICBjKCJHUkFWRSIsImdyYXZlIikgfiAiR3JhdmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JhdmVkYWQgPT0gIk1PUlRBTCIgfiAiTW9ydGFsIikpJT4lDQogIHNlbGVjdCghYyhkaXJlY2Npb25fbm9ybWFsaXphZGEsZmVjaGEsY2FsbGUsIGFsdHVyYSwgY3J1Y2UsIG90cmFfZGlyZWNjaW9uLCBob3JhLCBjb211bmEsIHRpcG9fZGVfY2FsbGUsIGdlb2NvZGlmaWNhY2lvbl9jYWJhLHRpcG9fZGVfZGF0bywgcGFydGljaXBhbnRlcykpDQoNCg0KY29tdW5hcyA8LSBzdF9yZWFkKCJkYXRhL2NvbXVuYXMuZ2Vvc29uIiwgDQogICAgICAgICAgICAgICAgICBjcnMgPSA0MzI2KQ0KDQoNCmBgYA0KDQoNCmBgYHtyIGdncGxvdDEsIHdhcm5pbmc9RkFMU0V9DQoNCnRhYmxhXzEgPC0gc2luaWVzdHJvcyAlPiUgDQogIGdyb3VwX2J5KGFhYWEsIGdyYXZlZGFkKSAlPiUgDQogIHN1bW1hcmlzZSh0b3QgPSBuKCkpICU+JSANCiAgbXV0YXRlKHBvcmNlbnRhamUgPSByb3VuZCh0b3Qvc3VtKHRvdCksNCkpICU+JSANCiAgbXV0YXRlKFRvdGFsX0FjdW0gPSBjdW1zdW0odG90KSkgJT4lIA0KICBtdXRhdGUoUG9yY2VudGFqZV9BY3VtID0gY3Vtc3VtKHBvcmNlbnRhamUpKQ0KDQpnZ3Bsb3QodGFibGFfMSwgYWVzKHggPSBhYWFhLCB5ID0gdG90LCBmaWxsID0gZ3JhdmVkYWQpKSsNCiAgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UiKSsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHBlcmNlbnQocG9yY2VudGFqZSkpLCB2anVzdCA9IC0wLjUsIGNoZWNrX292ZXJsYXAgPSBUUlVFLCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoID0gLjkpLCBzaXplID0gMykrDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIiNmNTU5NTEiLCIjMDAwMDAwIikpKw0KICB0aGVtZV9taW5pbWFsKCkrDQogIGxhYnMoDQogICAgdGl0bGUgPSAiPHNwYW4gc3R5bGUgPSAnY29sb3I6IzI3MjY0MzsnPiBFdm9sdWNpw7NuIGFudWFsIGRlIHNpbmllc3Ryb3M6IDxzcGFuIHN0eWxlID0gJ2NvbG9yOiNmNTU5NTE7Jz4gR3JhdmVzIDxzcGFuIHN0eWxlID0gJ2NvbG9yOiM1NDNjNTI7Jz4gLSA8c3BhbiBzdHlsZSA9ICdjb2xvcjojMDAwMDAwOyc+IE1vcnRhbGVzIiwNCiAgICBzdWJ0aXRsZSA9ICIiLA0KICAgIGNhcHRpb24gPSAiPGI+RnVlbnRlPC9iPjogUmVOYUJhUCIsDQogICAgeCA9ICJBw7FvIiwNCiAgICB5ID0gIkJhcnJpb3MgUG9wdWxhcmVzIg0KICApKw0KICB0aGVtZSgNCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfbWFya2Rvd24oZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDE1KSwNCiAgICBwbG90LmNhcHRpb24gPSBlbGVtZW50X21hcmtkb3duKHNpemUgPSAxMiksDQogICkNCg0KDQpgYGANCg0KDQoNCg0KDQpgYGB7ciBtYXBhIGdncGxvdCwgd2FybmluZz1GQUxTRX0NCg0KZ2dwbG90KCkrDQogIGdlb21fc2YoZGF0YT1jb211bmFzKSsNCiAgZ2VvbV9zZihkYXRhPXNpbmllc3Ryb3NfZ2VvLCBhZXMoY29sb3I9Z3JhdmVkYWQpLCBhbHBoYT0wLjc1LCBzaG93LmxlZ2VuZCA9IFRSVUUpKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiI2Y1NTk1MSIsIiMwMDAwMDAiKSkrDQogIHRoZW1lX21pbmltYWwoKSsNCiAgdGhlbWVfdm9pZCgpKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIjxzcGFuIHN0eWxlID0gJ2NvbG9yOiMyNzI2NDM7Jz4gU2luaWVzdHJvcyB2aWFsZXMgLSBDQUJBIDIwMTktMjAyMyIsDQogICAgY2FwdGlvbiA9ICI8Yj5GdWVudGU8L2I+OiBEYXRvcyBBYmllcnRvcyBDQUJBIiwNCiAgICB5ID0gIiIsDQogICAgeCA9ICIiDQogICkrDQogIHRoZW1lKA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X21hcmtkb3duKHNpemUgPSAyMCwgaGp1c3QgPSAwLjUpLA0KICAgIHBsb3QuY2FwdGlvbj0gZWxlbWVudF9tYXJrZG93bihzaXplID0gMTIpLA0KICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiLA0KICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBsZWdlbmQuYm94Lm1hcmdpbiA9IG1hcmdpbigxMSwgMTYsIDYsIDYpDQogICkrDQogIGZhY2V0X3dyYXAofmFhYWEsIG5yb3cgPSAxKSsNCiAgZ3VpZGVzKGZpbGw9Z3VpZGVfbGVnZW5kKG5yb3c9MSxieXJvdz1UUlVFKSkNCg0KDQpgYGANCkFsIGFwbGljYXIgZmlsdHJvcyB2aW5jdWxhZG9zIGEgZWxpbWluYXIgcmVnaXN0cm9zIHNpbiBnZW9yZWZlcmVuY2lhY2nDs24sIGVsIGHDsW8gMjAxOSBzw7NsbyBjb250aWVuZSBjYXNvcyBtb3J0YWxlcy4gTG9zIGHDsW9zIHNpZ3VpZW50ZXMgY29udGllbmVuIHVuYSBtYXlvciBjYW50aWRhZCBkZSBjYXNvcyBkZSBncmF2ZWRhZCBtYW50ZW5pZW5kbyBjaWVydG9zIHBhdHJvbmVzIHBhcmEgbG9zIHNpbmllc3Ryb3MgZmF0YWxlcy4gDQoNCkEgY29udGludWFjacOzbiwgc2UgbXVlc3RyYSB1biBtYXBhIGludGVyYWN0aXZvIHBhcmEgdmlzdWFsaXphciBsYSB0b3RhbGlkYWQgZGUgY2Fzb3MgcmVnaXN0cmFkb3MgIGVuIGVsIGRhdGFzZXQgY29uIGdlb21ldHLDrWFzIGVzcGFjaWFsZXMuIA0KDQpgYGB7ciBtYXBhIGRlc2NyaXB0aXZvLCB3YXJuaW5nPUZBTFNFfQ0KDQpmYWN0cGFsIDwtIGNvbG9yRmFjdG9yKHBhbGV0dGUgPSBjKCIjZjU1OTUxIiwiIzAwMDAwMCIpLCANCiAgICAgICAgICAgICAgIGxldmVscyA9IHNpbmllc3Ryb3NfZ2VvJGdyYXZlZGFkKQ0KDQpvY2Vhbkljb25zIDwtIGljb25MaXN0KA0KICBzaGlwID0gbWFrZUljb24oDQogICAgImh0dHBzOi8vdXBsb2FkLndpa2ltZWRpYS5vcmcvd2lraXBlZGlhL2NvbW1vbnMvdGh1bWIvOS85YS9NZWRpY2FsX2hvc3BpdGFsX2VtZXJnZW5jeV9iZWRzLnN2Zy8yNDBweC1NZWRpY2FsX2hvc3BpdGFsX2VtZXJnZW5jeV9iZWRzLnN2Zy5wbmciLA0KICAgICJodHRwczovL3VwbG9hZC53aWtpbWVkaWEub3JnL3dpa2lwZWRpYS9jb21tb25zL3RodW1iLzkvOWEvTWVkaWNhbF9ob3NwaXRhbF9lbWVyZ2VuY3lfYmVkcy5zdmcvMjQwcHgtTWVkaWNhbF9ob3NwaXRhbF9lbWVyZ2VuY3lfYmVkcy5zdmcucG5nIiwNCiAgICAxOCwNCiAgICAxOA0KICApLA0KICBwaXJhdGUgPSBtYWtlSWNvbigNCiAgICAiaHR0cHM6Ly91cGxvYWQud2lraW1lZGlhLm9yZy93aWtpcGVkaWEvY29tbW9ucy90aHVtYi9jL2MzL01ha2kyLWRhbmdlci0yNC5zdmcvMjQwcHgtTWFraTItZGFuZ2VyLTI0LnN2Zy5wbmciLA0KICAgICJodHRwczovL3VwbG9hZC53aWtpbWVkaWEub3JnL3dpa2lwZWRpYS9jb21tb25zL3RodW1iL2MvYzMvTWFraTItZGFuZ2VyLTI0LnN2Zy8yNHB4LU1ha2kyLWRhbmdlci0yNC5zdmcucG5nIiwNCiAgICAyNCwNCiAgICAyNA0KICApDQopDQoNCg0KIG1hcGFfaW50ZXJhY3Rpdm8gPC0gbGVhZmxldCgpICU+JQ0KICAgICBhZGRUaWxlcygpICU+JQ0KICAgICBhZGRNYXJrZXJzKGRhdGEgPSBzaW5pZXN0cm9zX2dlbywgaWNvbiA9IH5vY2Vhbkljb25zLCBjbHVzdGVyT3B0aW9ucyA9IG1hcmtlckNsdXN0ZXJPcHRpb25zKCkscG9wdXAgPSB+IGdsdWU6OmdsdWUoIjxiPkVzdGFkbzwvYj46e2dyYXZlZGFkfTxici8+DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxiPkHDsW86PC9iPnthYWFhfTxici8+DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxiPkNhbnRpZGFkIGRlIFbDrWN0aW1hczo8L2I+e25fdmljdGltYXN9PGJyLz4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGI+SG9yYTo8L2I+e2hofTxici8+IikpDQoNCm1hcGFfaW50ZXJhY3Rpdm8NCg0KDQpgYGANCg0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQoNCiMjIE1vZGVsYWRvDQoNCkVuIHByaW1lciBsdWdhciBwcm9jZWRlbW9zIGEgbGEgY29uZmVjY2nDs24gZGUgc2V0IGRlIGVudHJlbmFtaWVudG9zIHkgdGVzdGVvcyBjb25zaWRlcmFuZG8gMC44IGNvbW8gcHJvcG9yY2nDs24geSB0b21hbmRvIGVsIGRhdGFzZWV0IHF1ZSBubyBjb250aWVuZSBjb29yZGVuYWRhcyBnZW9ncsOhZmljYXMuIA0KDQpgYGB7ciBzcGwsIHdhcm5pbmc9RkFMU0V9DQojc2V0ZW8gbGEgc2VtaWxsYQ0Kc2V0LnNlZWQoMTIzKQ0KI2RpdmlkbyBsb3MgZGF0b3MNCnNpbmlfc3BsaXQgPC0gaW5pdGlhbF9zcGxpdChzaW5pZXN0cm9zLCBwcm9wID0gMC44MCwgc3RyYXRhID0gZ3JhdmVkYWQpICNlc3RyYXRpZmljYW1vcyBzZWd1biBsYXMgY2xhc2VzIA0Kc2luaV90cmFpbiA8LSB0cmFpbmluZyhzaW5pX3NwbGl0KQ0Kc2luaV90ZXN0IDwtIHRlc3Rpbmcoc2luaV9zcGxpdCkNCmBgYA0KDQojIyBGb2xkcyBjb24gdmFsaWRhY2nDs24gY3J1emFkYQ0KDQpgYGB7ciBzcGxfZiwgd2FybmluZz1GQUxTRX0NCnNldC5zZWVkKDIzNCkNCnNpbmlfZm9sZHMgPC0gdmZvbGRfY3Yoc2luaV90cmFpbiwgc3RyYXRhID0gZ3JhdmVkYWQpDQpzaW5pX2ZvbGRzDQpgYGANCg0KDQpgYGB7cn0NCnNpbmlfdHJhaW4NCmBgYA0KDQpgYGB7cn0NCnNpbmlfdGVzdCANCmBgYA0KDQoNCmBgYHtyfQ0Kc2luaV90cmFpbiAlPiUNCiAgY291bnQoZ3JhdmVkYWQpDQpgYGANCg0KDQoNCmBgYHtyfQ0Kc2luaV90ZXN0ICU+JQ0KICBjb3VudChncmF2ZWRhZCkNCmBgYA0KDQpTZSB2ZXJpZmljYSBxdWUgbGFzIHByb3BvcmNpb25lcyBjb3JyZXNwb25kaWVudGVzIGEgbGEgZGlzdHJpYnVjacOzbiBkZSBsYXMgY2F0ZWdvcsOtYXMgZGUgbGEgdmFyaWFibGUgImdyYXZlZGFkIiBzZSBjb25zZXJ2YW4uDQoNCiMjIFJlY2V0YQ0KDQpFbCBzaWd1aWVudGUgcGFzbyBlcyBkZXRlcm1pbmFyIGxhIHJlY2V0YSBkZWwgbW9kZWxvIHRvbWFubyAiZ3JhdmVkYWQiIGNvbW8gbGEgdmFyaWFibGUgYSBjYWxzaWZpY2FyLiANCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxvZ2lzdGljX3JlY2lwZSA8LQ0KICByZWNpcGUoZm9ybXVsYSA9IGdyYXZlZGFkIH4gLiwgZGF0YSA9IHNpbmlfdHJhaW4pICU+JQ0KICB1cGRhdGVfcm9sZShpZF9oZWNobywgbmV3X3JvbGUgPSAiaWQiKSAlPiUNCiAgc3RlcF91bmtub3duKGFsbF9ub21pbmFsX3ByZWRpY3RvcnMoKSkgJT4lDQogIHN0ZXBfb3RoZXIoYWxsX25vbWluYWxfcHJlZGljdG9ycygpLCB0aHJlc2hvbGQgPSAwLjAzKSAlPiUNCiAgc3RlcF9kb3duc2FtcGxlKGdyYXZlZGFkKQ0KYGBgDQoNCg0KDQojIyBNb2RlbGl6YWNpw7NuDQoNCkVzcGVjaWZpY2Ftb3MgZWwgbW9kZWxvLCBlbiBlc3RlIGNhc28sIHVuYSByZWdyZXNpw7NuIGxvZ8Otc3RpY2EuIA0KDQpgYGB7cn0NCmxvZ2lzdGljX3NwZWMgPC0gbG9naXN0aWNfcmVnKCkNCmBgYA0KDQoNCg0KIyMgV29ya2Zsb3cNCg0KQWdyZWdhbW9zIGFsIFdvcmtmbG93IGxhIHJlY2V0YSB5IGxhIGVzcGVjaWZpY2FjacOzbiBkZWwgbW9kZWxvLiANCg0KYGBge3J9DQpsb2dfd29ya2Zsb3cgPC0gd29ya2Zsb3coKSAlPiUNCiAgYWRkX3JlY2lwZShsb2dpc3RpY19yZWNpcGUpICU+JQ0KICBhZGRfbW9kZWwobG9naXN0aWNfc3BlYykNCg0KYGBgDQoNCg0KDQoNCiMjIEVudHJlbmFtaWVudG8gZGVsIGFsZ29yaXRtbw0KDQpDb24gbGEgZnVuY2nDs24gZml0X3Jlc2FtcGxlcygpIHByb2NlZGVtb3MgYSBoYWNlciBlbCBhanVzdGUgY29ycmVzcG9uZGllbnRlLg0KDQoNCmBgYHtyfQ0KZG9QYXJhbGxlbDo6cmVnaXN0ZXJEb1BhcmFsbGVsKCkgDQpzZXQuc2VlZCg3NDQzKQ0KbG9naXN0aWNfcnMgPC0NCiAgZml0X3Jlc2FtcGxlcyhsb2dfd29ya2Zsb3csDQogICAgcmVzYW1wbGVzID0gc2luaV9mb2xkcywNCiAgICBjb250cm9sID0gY29udHJvbF9yZXNhbXBsZXMoc2F2ZV9wcmVkID0gVFJVRSkNCiAgKQ0KDQpgYGANCg0KDQojIyBQZXJmb3JtYW5jZQ0KDQpgYGB7cn0NCmxvZ2lzdGljX3JzICU+JQ0KICBjb2xsZWN0X21ldHJpY3MoKQ0KYGBgDQoNCkxhIHBlcmZvcm1hbmNlIG11ZXN0cmEgcmVzdWx0YWRvcyBhY2VwdGFibGVzIGVuIGVsIHNldCBkZSBlbnRyZW5hbWllbnRvLiAgTGEgcHJlY2lzacOzbiBjYXNpIGFsY2FuemEgZWwgODAlLCBtaWVudHJhcyBxdWUgZWwgw6FyZWEgYmFqbyBsYSBjdXJ2YSBST0MgbGxlZ2EgYWwgdmFsb3IgZGUgMC44OC4gRWwgYnJpZXJfY2xhc3MgYXJyb2phIHZhbG9yZXMgYmFqb3MsIGxvIGN1YWwgZXMgYnVlbm8gcGFyYSBudWVzdHJvIG1vZGVsbw0KDQoNCmBgYHtyfQ0KY29sbGVjdF9wcmVkaWN0aW9ucyhsb2dpc3RpY19ycykgJT4lDQogIGdyb3VwX2J5KGlkKSAlPiUNCiAgcm9jX2N1cnZlKGdyYXZlZGFkLCAucHJlZF9HcmF2ZSkgJT4lDQogIGF1dG9wbG90KCkNCmBgYA0KDQojIyBQcmVkaWNjacOzbg0KDQpFbiBlc3RhIGV0YXBhIGNvbiBsYSBmdW5jacOzbiBsYXN0X2ZpdCB2YW1vcyBhIHNlbGVjY2lvbmFyIGVsIG1lam9yIG1vZGVsbyBwYXJhIHByZWRlY2lyIGVuIGVsIHNldCBkZSBkYXRvcyBkZSBURVNULiANCg0KDQpgYGB7cn0NCmZpbmFsX2ZpdHRlZCA8LSBsYXN0X2ZpdChsb2dfd29ya2Zsb3csIHNpbmlfc3BsaXQpDQpmaW5hbF9maXR0ZWQgDQpgYGANCg0KDQojIyBNw6l0cmljYXMNCg0KDQoNCmBgYHtyfQ0KZmluYWxfZml0dGVkICU+JQ0KICBjb2xsZWN0X21ldHJpY3MoKQ0KYGBgDQoNCg0KTGFzIG3DqXRyaWNhcyBkZWwgbW9kZWxvIGZpbmFsIGFycm9qYW4gY2lmcmFzIGFjZXB0YWJsZXMgcGFyYSBsYSBwcmVjaXNpw7NuICg3OCAlKSB5IHZhbG9yZXMgYnVlbm9zIHBhcmEgZWwgw6FyZWEgYmFqbyBsYSBjdXJ2YSBST0MuIFRhbWJpZW4gbXVlc3RyYSB2YWxvcmVzIGJham9zIHBhcmEgYnJpZXJfY2xhc3MNCg0KIyMgUHJlZGljY2lvbmVzIGRlbCBtb2RlbG8NCg0KYGBge3J9DQpmaW5hbF9maXR0ZWQgJT4lDQogIGNvbGxlY3RfcHJlZGljdGlvbnMoKQ0KYGBgDQoNCiMjIE1hdHJpeiBkZSBjb25mdXNpw7NuDQoNClBhcmEgZmluYWxpemFyIHkgY29tcGxldGFyIGxhIGNvbnNpZ25hLCB2YW1vcyBhIGNyZWFyIHVuYSBtYXRyaXogZGUgY29uZnVzacOzbiBjb24gbGFzIHByZWRpY2Npb25lcyBwYXJhIGNvbnRyYXN0YXJsYSBjb24gbG9zIGNhc29zIHJlYWxlcy4gDQoNCg0KYGBge3J9DQpmaW5hbF9maXR0ZWQgJT4lDQogIGNvbGxlY3RfcHJlZGljdGlvbnMoKSAlPiUNCiAgY29uZl9tYXQoZ3JhdmVkYWQsIC5wcmVkX2NsYXNzKSANCmBgYA0KDQpDb21vIHBvZGVtb3MgdmVyIGxhIGRpYWdvbmFsIHByaW5jaXBhbCBjb250aWVuZSBsYSBtYXlvcsOtYSBkZSBsb3MgY2Fzb3MsIGRlamFuZG8gZW4gdmFsb3JlcyBiYWpvcyBsb3MgZmFsc29zIHBvc2l0aXZvcyB5IGxvcyBmYWxzb3MgbmVnYXRpdm9zLg0KDQoNCiMjIENvbmNsdXNpw7NuDQoNCkEgcGFydGlyIGRlbCBlbXBsZW8gZGUgdMOpY25pY2FzIGRlIG1vZGVsYWRvLCBwdWRpbW9zIGFycmliYXIgdW5hIHByb2JsZW3DoXRpY2Egc2V2ZXJhIGRlIGxhIG1vdmlsaWRhZCB1cmJhbmEgY29tbyBsbyBzb24gbG9zIHNpbmllc3Ryb3MuIENvbiBkYXRvcyBhYmllcnRvcyB5IHTDqWNuaWNhcyBkZSB2aXN1YWxpemFjacOzbiB5IHJlZ3Jlc2nDs24gbG9nw61zdGljYSBjb25zdHJ1aW1vcyB1biBtb2RlbG8gcXVlLCBhIHBlc2FyIGRlIGxhIGVzY2FzYSBjYW50aWRhZCBkZSBkYXRvcywgb2J0dXZvIGNpZnJhcyBkZSBwcmVjaXNpw7NuIGNvbnNpZGVyYWJsZXMuIA0KDQoNCg0KDQoNCg0K