1 Librerias

# Librerías necesarias
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.1     ✔ tibble    3.2.1
## ✔ lubridate 1.9.4     ✔ tidyr     1.3.1
## ✔ purrr     1.0.4     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(readxl)
library(purrr)
library(knitr)
#install.packages("kableExtra")
library(kableExtra)
## 
## Attaching package: 'kableExtra'
## 
## The following object is masked from 'package:dplyr':
## 
##     group_rows
library(ggplot2)
#install.packages("igraph")
library(igraph)
## 
## Attaching package: 'igraph'
## 
## The following objects are masked from 'package:lubridate':
## 
##     %--%, union
## 
## The following objects are masked from 'package:dplyr':
## 
##     as_data_frame, groups, union
## 
## The following objects are masked from 'package:purrr':
## 
##     compose, simplify
## 
## The following object is masked from 'package:tidyr':
## 
##     crossing
## 
## The following object is masked from 'package:tibble':
## 
##     as_data_frame
## 
## The following objects are masked from 'package:stats':
## 
##     decompose, spectrum
## 
## The following object is masked from 'package:base':
## 
##     union
#install.packages("forecast")
#install.packages("lubridate")
library(forecast)
## Registered S3 method overwritten by 'quantmod':
##   method            from
##   as.zoo.data.frame zoo
library(lubridate)
library(corrplot)
## corrplot 0.95 loaded
library(RColorBrewer)
#install.packages("ggcorrplot")
library(ggcorrplot)
library(caret)
## Loading required package: lattice
## 
## Attaching package: 'caret'
## 
## The following object is masked from 'package:purrr':
## 
##     lift
library(car)
## Loading required package: carData
## 
## Attaching package: 'car'
## 
## The following object is masked from 'package:dplyr':
## 
##     recode
## 
## The following object is masked from 'package:purrr':
## 
##     some
library(randomForest)
## randomForest 4.7-1.2
## Type rfNews() to see new features/changes/bug fixes.
## 
## Attaching package: 'randomForest'
## 
## The following object is masked from 'package:dplyr':
## 
##     combine
## 
## The following object is masked from 'package:ggplot2':
## 
##     margin
#install.packages("xgboost")
library(xgboost)
## 
## Attaching package: 'xgboost'
## 
## The following object is masked from 'package:dplyr':
## 
##     slice
#install.packages("patchwork")
library(patchwork)

2 Carga de datos

# Cargar archivo Excel desde ruta local
ruta <- "/Users/oscarcastanedagarcia/Downloads/IA con impacto empresarial/filtered_data.xlsx"
datos <- read_excel(ruta)

# Vista genera
head(datos)
## # A tibble: 6 × 25
##   Trx_Num Trx_Fecha           Origen    Sucursal Num_Cliente ID_Inventario  Cant
##     <dbl> <dttm>              <chr>     <chr>          <dbl>         <dbl> <dbl>
## 1   46453 2023-02-09 00:00:00 FACTURAS… ACA             1049        155001     2
## 2   46455 2023-02-09 00:00:00 FACTURAS… ACA             7455        155001    40
## 3   46476 2023-02-16 00:00:00 FACTURAS… ACA             4981        155001    30
## 4   46480 2023-02-16 00:00:00 FACTURAS… ACA             7455        155001    60
## 5   46501 2023-02-20 00:00:00 FACTURAS… ACA             2925        155001     5
## 6   46528 2023-02-25 00:00:00 FACTURAS… ACA             1049        155001     2
## # ℹ 18 more variables: Linea <chr>, Especifico <chr>, Canal_Venta <chr>,
## #   Tipo_Precio <dbl>, Tipo_Modificador <chr>, Venta <dbl>, Costo_Venta <dbl>,
## #   Costo_Devolucion <dbl>, Ajus_Sistema <dbl>, Precio_Lista_Unitario <dbl>,
## #   Precio_Final_Unitario <dbl>, Semana <dbl>, Mes <dbl>, Ciudad <chr>,
## #   Zona <chr>, Devoluciones <dbl>, Diferencia_Precio <dbl>,
## #   Descuento_Porcentaje <dbl>
str(datos)
## tibble [32,244 × 25] (S3: tbl_df/tbl/data.frame)
##  $ Trx_Num              : num [1:32244] 46453 46455 46476 46480 46501 ...
##  $ Trx_Fecha            : POSIXct[1:32244], format: "2023-02-09" "2023-02-09" ...
##  $ Origen               : chr [1:32244] "FACTURAS IMPORTADAS" "FACTURAS IMPORTADAS" "FACTURAS IMPORTADAS" "FACTURAS IMPORTADAS" ...
##  $ Sucursal             : chr [1:32244] "ACA" "ACA" "ACA" "ACA" ...
##  $ Num_Cliente          : num [1:32244] 1049 7455 4981 7455 2925 ...
##  $ ID_Inventario        : num [1:32244] 155001 155001 155001 155001 155001 ...
##  $ Cant                 : num [1:32244] 2 40 30 60 5 2 1 4 1 1 ...
##  $ Linea                : chr [1:32244] "PQU" "PQU" "PQU" "PQU" ...
##  $ Especifico           : chr [1:32244] "TCG" "TCG" "TCG" "TCG" ...
##  $ Canal_Venta          : chr [1:32244] "Sucursal" "Sucursal" "Sucursal" "Sucursal" ...
##  $ Tipo_Precio          : num [1:32244] 1 1 1 1 1 1 1 1 1 1 ...
##  $ Tipo_Modificador     : chr [1:32244] "CARTA DESCUENTO" "CARTA DESCUENTO" "CARTA DESCUENTO" "CARTA DESCUENTO" ...
##  $ Venta                : num [1:32244] 1187 21280 15960 31920 2968 ...
##  $ Costo_Venta          : num [1:32244] 1194 23874 17906 35811 2570 ...
##  $ Costo_Devolucion     : num [1:32244] 0 0 0 0 0 0 0 0 0 0 ...
##  $ Ajus_Sistema         : num [1:32244] -2515 -2515 -2515 -2515 -2515 ...
##  $ Precio_Lista_Unitario: num [1:32244] 4192 4192 4192 4192 4192 ...
##  $ Precio_Final_Unitario: num [1:32244] 594 532 532 532 594 ...
##  $ Semana               : num [1:32244] 6 6 7 7 8 8 5 5 5 5 ...
##  $ Mes                  : num [1:32244] 2 2 2 2 2 2 2 2 2 2 ...
##  $ Ciudad               : chr [1:32244] "Acapulco" "Acapulco" "Acapulco" "Acapulco" ...
##  $ Zona                 : chr [1:32244] "Sur" "Sur" "Sur" "Sur" ...
##  $ Devoluciones         : num [1:32244] 0 0 0 0 0 0 0 0 0 0 ...
##  $ Diferencia_Precio    : num [1:32244] 3598 3660 3660 3660 3598 ...
##  $ Descuento_Porcentaje : num [1:32244] 85.8 87.3 87.3 87.3 85.8 ...
# Obtener los 5 productos más vendidos (por valor)
top_ids <- datos %>%
  group_by(ID_Inventario) %>%
  summarise(Ventas_Totales = sum(Venta, na.rm = TRUE)) %>%
  arrange(desc(Ventas_Totales)) %>%
  slice_head(n = 5) %>%
  pull(ID_Inventario)

print("Top 5 productos más vendidos (ID_Inventario):")
## [1] "Top 5 productos más vendidos (ID_Inventario):"
print(top_ids)
## [1]  155001 3929788 3904152  155002 3678055
# Filtrar datos válidos
datos_filtrados <- datos %>%
  filter(ID_Inventario %in% top_ids) %>%
  filter(!is.na(Precio_Final_Unitario))

# Contar observaciones por producto
conteo <- datos_filtrados %>%
  count(ID_Inventario, sort = TRUE)

print("Número de registros por producto en datos_filtrados:")
## [1] "Número de registros por producto en datos_filtrados:"
print(conteo)
## # A tibble: 5 × 2
##   ID_Inventario     n
##           <dbl> <int>
## 1       3929788 13717
## 2        155001  8559
## 3        155002  5752
## 4       3904152  2556
## 5       3678055  1660
# Verifica si hay suficientes datos
if (nrow(datos_filtrados) == 0) {
  stop("No hay datos suficientes luego de filtrar por top_ids y precios válidos.")
}
# Combinaciones de pares
productos <- unique(datos_filtrados$ID_Inventario)
pares_productos <- combn(productos, 2, simplify = FALSE)

# Inicializar resultados
resultados_ks <- map_df(pares_productos, function(par) {
  prod1 <- par[1]
  prod2 <- par[2]
  
  precios1 <- datos_filtrados %>%
    filter(ID_Inventario == prod1) %>%
    pull(Precio_Final_Unitario)
  
  precios2 <- datos_filtrados %>%
    filter(ID_Inventario == prod2) %>%
    pull(Precio_Final_Unitario)
  
  print(paste("Comparando productos", prod1, "vs", prod2))
  print(paste("Cantidad de precios:", length(precios1), "y", length(precios2)))
  
  if (length(precios1) >= 5 & length(precios2) >= 5) {
    prueba <- suppressWarnings(ks.test(precios1, precios2))
    data.frame(
      Producto_1 = prod1,
      Producto_2 = prod2,
      D = round(prueba$statistic, 4),
      p_value = round(prueba$p.value, 4),
      Conclusion = ifelse(prueba$p.value > 0.05, "Distribuciones similares", "Distribuciones diferentes")
    )
  } else {
    data.frame(
      Producto_1 = prod1,
      Producto_2 = prod2,
      D = NA,
      p_value = NA,
      Conclusion = "Datos insuficientes"
    )
  }
})
## [1] "Comparando productos 155001 vs 3929788"
## [1] "Cantidad de precios: 8559 y 13717"
## [1] "Comparando productos 155001 vs 155002"
## [1] "Cantidad de precios: 8559 y 5752"
## [1] "Comparando productos 155001 vs 3904152"
## [1] "Cantidad de precios: 8559 y 2556"
## [1] "Comparando productos 155001 vs 3678055"
## [1] "Cantidad de precios: 8559 y 1660"
## [1] "Comparando productos 3929788 vs 155002"
## [1] "Cantidad de precios: 13717 y 5752"
## [1] "Comparando productos 3929788 vs 3904152"
## [1] "Cantidad de precios: 13717 y 2556"
## [1] "Comparando productos 3929788 vs 3678055"
## [1] "Cantidad de precios: 13717 y 1660"
## [1] "Comparando productos 155002 vs 3904152"
## [1] "Cantidad de precios: 5752 y 2556"
## [1] "Comparando productos 155002 vs 3678055"
## [1] "Cantidad de precios: 5752 y 1660"
## [1] "Comparando productos 3904152 vs 3678055"
## [1] "Cantidad de precios: 2556 y 1660"
print("Resultados de la prueba KS:")
## [1] "Resultados de la prueba KS:"
print(resultados_ks)
##        Producto_1 Producto_2     D p_value                Conclusion
## D...1      155001    3929788 1.000       0 Distribuciones diferentes
## D...2      155001     155002 0.043       0 Distribuciones diferentes
## D...3      155001    3904152 1.000       0 Distribuciones diferentes
## D...4      155001    3678055 1.000       0 Distribuciones diferentes
## D...5     3929788     155002 1.000       0 Distribuciones diferentes
## D...6     3929788    3904152 1.000       0 Distribuciones diferentes
## D...7     3929788    3678055 1.000       0 Distribuciones diferentes
## D...8      155002    3904152 1.000       0 Distribuciones diferentes
## D...9      155002    3678055 1.000       0 Distribuciones diferentes
## D...10    3904152    3678055 1.000       0 Distribuciones diferentes
try(dev.off(), silent = TRUE)
## null device 
##           1
# Filtrar los productos
df_155001 <- datos_filtrados %>%
  filter(ID_Inventario == 155001) %>%
  select(Precio_Final_Unitario) %>%
  mutate(Producto = "155001")

df_155002 <- datos_filtrados %>%
  filter(ID_Inventario == 155002) %>%
  select(Precio_Final_Unitario) %>%
  mutate(Producto = "155002")

# Unir en un solo dataframe
df_ecdf <- bind_rows(df_155001, df_155002)

# Graficar ECDF
ggplot(df_ecdf, aes(x = Precio_Final_Unitario, color = Producto)) +
  stat_ecdf(geom = "step", size = 1) +
  labs(title = "ECDF de Precio Final Unitario: Productos 155001 vs 155002",
       x = "Precio Final Unitario",
       y = "Función de Distribución Acumulada (ECDF)",
       color = "Producto") +
  theme_minimal(base_size = 14)
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

3 ARMA

4 PREDICCIONES DE VENTAS

4.1 PRODUCTO 155001

# Producto 155001
id_prod <- 155001

# Crear la serie de tiempo mensual
ventas_mensuales <- datos_filtrados %>%
  filter(ID_Inventario == id_prod) %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month"))) %>%
  group_by(Fecha) %>%
  summarise(Venta = sum(Venta, na.rm = TRUE)) %>%
  arrange(Fecha)

serie_ts <- ts(ventas_mensuales$Venta, frequency = 12,
               start = c(year(min(ventas_mensuales$Fecha)), 
                         month(min(ventas_mensuales$Fecha))))

# Modelo ARMA
modelo_arma <- auto.arima(serie_ts, seasonal = FALSE, stepwise = FALSE, approximation = FALSE)
forecast_modelo <- forecast(modelo_arma, h = 3)

# Gráfico del pronóstico
autoplot(forecast_modelo) +
  labs(title = paste("Pronóstico mensual de ventas - ARMA (Producto", id_prod, ")"),
       x = "Mes", y = "Ventas ($)") +
  theme_minimal()

# Calcular métricas
fitted_values <- fitted(modelo_arma)
mape <- mean(abs((serie_ts - fitted_values) / pmax(serie_ts, 0.01))) * 100
rmse <- sqrt(mean((serie_ts - fitted_values)^2))


# Crear tabla de métricas
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = id_prod,
  Modelo = "ARMA",
  MAPE = mape,
  RMSE = rmse
))

# Mostrar tabla para este producto
tail(metricas_comparativas, 1) %>%
  knitr::kable(caption = paste("Métricas del modelo ARMA para Producto", id_prod)) %>%
  kableExtra::kable_styling(full_width = FALSE)
Métricas del modelo ARMA para Producto 155001
Producto Modelo MAPE RMSE
155001 ARMA 17.68164 224023.2

4.2 PRODUCTO 3929788

# Producto 3929788

id_prod <- 3929788

# Crear la serie de tiempo mensual
ventas_mensuales <- datos_filtrados %>%
  filter(ID_Inventario == id_prod) %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month"))) %>%
  group_by(Fecha) %>%
  summarise(Venta = sum(Venta, na.rm = TRUE)) %>%
  arrange(Fecha)

serie_ts <- ts(ventas_mensuales$Venta, frequency = 12,
               start = c(year(min(ventas_mensuales$Fecha)), 
                         month(min(ventas_mensuales$Fecha))))

# Modelo ARMA
modelo_arma <- auto.arima(serie_ts, seasonal = FALSE, stepwise = FALSE, approximation = FALSE)
forecast_modelo <- forecast(modelo_arma, h = 3)

# Gráfico del pronóstico
autoplot(forecast_modelo) +
  labs(title = paste("Pronóstico mensual de ventas - ARMA (Producto", id_prod, ")"),
       x = "Mes", y = "Ventas ($)") +
  theme_minimal()

# Calcular métricas
fitted_values <- fitted(modelo_arma)
mape <- mean(abs((serie_ts - fitted_values) / pmax(serie_ts, 0.01))) * 100
mse <- mean((serie_ts - fitted_values)^2)

# Crear tabla de métricas
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = id_prod,
  Modelo = "ARMA",
  MAPE = mape,
  RMSE  = rmse
))

# Mostrar tabla para este producto
tail(metricas_comparativas, 1) %>%
  knitr::kable(caption = paste("Métricas del modelo ARMA para Producto", id_prod)) %>%
  kableExtra::kable_styling(full_width = FALSE)
Métricas del modelo ARMA para Producto 3929788
Producto Modelo MAPE RMSE
2 3929788 ARMA 12.83705 224023.2

4.3 PRODUCTO 3904152

# Producto 3904152
id_prod <- 3904152

# Crear la serie de tiempo mensual
ventas_mensuales <- datos_filtrados %>%
  filter(ID_Inventario == id_prod) %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month"))) %>%
  group_by(Fecha) %>%
  summarise(Venta = sum(Venta, na.rm = TRUE)) %>%
  arrange(Fecha)

serie_ts <- ts(ventas_mensuales$Venta, frequency = 12,
               start = c(year(min(ventas_mensuales$Fecha)), 
                         month(min(ventas_mensuales$Fecha))))

# Modelo ARMA
modelo_arma <- auto.arima(serie_ts, seasonal = FALSE, stepwise = FALSE, approximation = FALSE)
forecast_modelo <- forecast(modelo_arma, h = 3)

# Gráfico del pronóstico
autoplot(forecast_modelo) +
  labs(title = paste("Pronóstico mensual de ventas - ARMA (Producto", id_prod, ")"),
       x = "Mes", y = "Ventas ($)") +
  theme_minimal()

# Calcular métricas
fitted_values <- fitted(modelo_arma)
mape <- mean(abs((serie_ts - fitted_values) / pmax(serie_ts, 0.01))) * 100
rmse <- sqrt(mean((serie_ts - fitted_values)^2))


# Crear tabla de métricas
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = id_prod,
  Modelo = "ARMA",
  MAPE = mape,
  RMSE = rmse
))

# Mostrar tabla para este producto
tail(metricas_comparativas, 1) %>%
  knitr::kable(caption = paste("Métricas del modelo ARMA para Producto", id_prod)) %>%
  kableExtra::kable_styling(full_width = FALSE)
Métricas del modelo ARMA para Producto 3904152
Producto Modelo MAPE RMSE
3 3904152 ARMA 15.35679 156981.2

4.4 PRODUCTO 155002

# Producto 155002
id_prod <- 155002

# Crear la serie de tiempo mensual
ventas_mensuales <- datos_filtrados %>%
  filter(ID_Inventario == id_prod) %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month"))) %>%
  group_by(Fecha) %>%
  summarise(Venta = sum(Venta, na.rm = TRUE)) %>%
  arrange(Fecha)

serie_ts <- ts(ventas_mensuales$Venta, frequency = 12,
               start = c(year(min(ventas_mensuales$Fecha)), 
                         month(min(ventas_mensuales$Fecha))))

# Modelo ARMA
modelo_arma <- auto.arima(serie_ts, seasonal = FALSE, stepwise = FALSE, approximation = FALSE)
forecast_modelo <- forecast(modelo_arma, h = 3)

# Gráfico del pronóstico
autoplot(forecast_modelo) +
  labs(title = paste("Pronóstico mensual de ventas - ARMA (Producto", id_prod, ")"),
       x = "Mes", y = "Ventas ($)") +
  theme_minimal()

# Calcular métricas
fitted_values <- fitted(modelo_arma)
mape <- mean(abs((serie_ts - fitted_values) / pmax(serie_ts, 0.01))) * 100
rmse <- sqrt(mean((serie_ts - fitted_values)^2))


# Crear tabla de métricas
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = id_prod,
  Modelo = "ARMA",
  MAPE = mape,
  RMSE = rmse
))

# Mostrar tabla para este producto
tail(metricas_comparativas, 1) %>%
  knitr::kable(caption = paste("Métricas del modelo ARMA para Producto", id_prod)) %>%
  kableExtra::kable_styling(full_width = FALSE)
Métricas del modelo ARMA para Producto 155002
Producto Modelo MAPE RMSE
4 155002 ARMA 25.83303 192121.8

4.5 PRODUCTO 3678055

# Producto 3678055
id_prod <- 3678055

# Crear la serie de tiempo mensual
ventas_mensuales <- datos_filtrados %>%
  filter(ID_Inventario == id_prod) %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month"))) %>%
  group_by(Fecha) %>%
  summarise(Venta = sum(Venta, na.rm = TRUE)) %>%
  arrange(Fecha)

serie_ts <- ts(ventas_mensuales$Venta, frequency = 12,
               start = c(year(min(ventas_mensuales$Fecha)), 
                         month(min(ventas_mensuales$Fecha))))

# Modelo ARMA
modelo_arma <- auto.arima(serie_ts, seasonal = FALSE, stepwise = FALSE, approximation = FALSE)
forecast_modelo <- forecast(modelo_arma, h = 3)

# Gráfico del pronóstico
autoplot(forecast_modelo) +
  labs(title = paste("Pronóstico mensual de ventas - ARMA (Producto", id_prod, ")"),
       x = "Mes", y = "Ventas ($)") +
  theme_minimal()

# Calcular métricas
fitted_values <- fitted(modelo_arma)
mape <- mean(abs((serie_ts - fitted_values) / pmax(serie_ts, 0.01))) * 100
rmse <- sqrt(mean((serie_ts - fitted_values)^2))

# Crear tabla de métricas
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = id_prod,
  Modelo = "ARMA",
  MAPE = mape,
  RMSE = rmse
))

# Mostrar tabla para este producto
tail(metricas_comparativas, 1) %>%
  knitr::kable(caption = paste("Métricas del modelo ARMA para Producto", id_prod)) %>%
  kableExtra::kable_styling(full_width = FALSE)
Métricas del modelo ARMA para Producto 3678055
Producto Modelo MAPE RMSE
5 3678055 ARMA 22.73775 177040.5

5 REGRESION LINEAL

5.1 MAPA DE CALOR

# Variables numéricas relevantes
vars_numericas <- c("Cant", "Venta", "Costo_Venta",
                    "Precio_Final_Unitario", "Descuento_Porcentaje")

# Preparación de los datos
datos_cor <- datos_filtrados %>%
  select(all_of(vars_numericas)) %>%
  na.omit()

# Generar la matriz de correlación
matriz_cor <- cor(datos_cor)

# Ajuste del gráfico sin mar
ggcorrplot(matriz_cor,
           method = "square",
           type = "upper",
           lab = TRUE, 
           lab_size = 2,                   # Mejor tamaño de los coeficientes
           tl.cex = 10,                    # Tamaño de etiquetas más grande
           tl.srt = 45,                    # Rotación de 45° de etiquetas
           colors = c("#6D9EC1", "white", "#E46726"),
           title = "Mapa de Correlación - Variables Numéricas",
           ggtheme = theme_minimal(base_size = 14) +
             theme(
               axis.text.x = element_text(angle = 45, hjust = 1),
               axis.text.y = element_text(angle = 0, hjust = 1))
)

5.2 PRODUCTO 155001

library(dplyr)
library(lubridate)

# Paso 1: Filtrar, seleccionar y crear fecha mensual
datos_155001 <- datos_filtrados %>%
  filter(ID_Inventario == 155001) %>%
  select(Venta, Cant, Costo_Venta,
         Precio_Final_Unitario, Descuento_Porcentaje, Trx_Fecha) %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month"))) %>%
  group_by(Fecha) %>%
  summarise(
    Venta = sum(Venta, na.rm = TRUE),
    Cant = sum(Cant, na.rm = TRUE),
    Costo_Venta = sum(Costo_Venta, na.rm = TRUE),
    Precio_Final_Unitario = mean(Precio_Final_Unitario, na.rm = TRUE),
    Descuento_Porcentaje = mean(Descuento_Porcentaje, na.rm = TRUE)
  ) %>%
  arrange(Fecha) %>%
  # Paso 2: Crear variable de tiempo y aplicar lag sobrescribiendo variables
  mutate(
    Tiempo = as.numeric(Fecha - min(Fecha)) / 30,
    Venta = Venta,
    Cant = lag(Cant, 1),
    Costo_Venta = lag(Costo_Venta, 1),
    Precio_Final_Unitario = lag(Precio_Final_Unitario, 1),
    Descuento_Porcentaje = lag(Descuento_Porcentaje, 1)
  ) %>%
  drop_na()

# Paso 3: Quitar el mes más reciente
datos_155001 <- datos_155001 %>%
  filter(Fecha < max(Fecha))
# Ajustar el modelo de regresión lineal
modelo_regresion_155001 <- lm(Venta ~ Cant + Costo_Venta +
                              Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
                             data = datos_155001)

# Ver resumen del modelo
summary(modelo_regresion_155001)
## 
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Precio_Final_Unitario + 
##     Descuento_Porcentaje + Tiempo, data = datos_155001)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -304387 -194808  -58441  121201  756969 
## 
## Coefficients:
##                         Estimate Std. Error t value Pr(>|t|)
## (Intercept)           -1.445e+05  1.698e+07  -0.009    0.993
## Cant                  -4.767e+01  5.197e+02  -0.092    0.928
## Costo_Venta            6.118e-01  1.545e+00   0.396    0.697
## Precio_Final_Unitario -3.825e+02  3.806e+03  -0.100    0.921
## Descuento_Porcentaje   1.118e+04  1.802e+05   0.062    0.951
## Tiempo                -4.862e+03  3.596e+04  -0.135    0.894
## 
## Residual standard error: 284500 on 16 degrees of freedom
## Multiple R-squared:  0.1694, Adjusted R-squared:  -0.09011 
## F-statistic: 0.6528 on 5 and 16 DF,  p-value: 0.6638
# Ajuste del modelo de regresión lineal
modelo_regresion_155001 <- lm(Venta ~ Cant + Costo_Venta +
                              Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
                             data = datos_155001)

# Predicciones usando el modelo ajustado
predicciones_155001 <- predict(modelo_regresion_155001, newdata = datos_155001)

# Calcular MAPE (Mean Absolute Percentage Error)
mape_155001 <- mean(abs((datos_155001$Venta - predicciones_155001) / datos_155001$Venta)) * 100


# Calcular RMSE (Root Mean Squared Error)
rmse_155001 <- sqrt(mean((datos_155001$Venta - predicciones_155001)^2))



# Mostrar las métricas
cat("MAPE del modelo de regresión lineal para 155001: ", mape_155001, "\n")
## MAPE del modelo de regresión lineal para 155001:  18.54839
cat("RMSE del modelo de regresión lineal para 155001: ", rmse_155001, "\n")
## RMSE del modelo de regresión lineal para 155001:  242664.1
# Diagnóstico de residuos del modelo
par(mfrow = c(2, 2))
plot(modelo_regresion_155001)

# Guardar métricas de Regresión Lineal para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "155001",  # Cambia este ID para cada producto
  Modelo = "Regresión Lineal",
  MAPE = mape_155001,
  RMSE = rmse_155001

))

5.3 PRODUCTO 3929788

library(dplyr)
library(lubridate)

# Paso 1: Filtrar, seleccionar y crear fecha mensual
datos_3929788 <- datos_filtrados %>%
  filter(ID_Inventario == 3929788) %>%
  select(Venta, Cant, Costo_Venta,
         Precio_Final_Unitario, Descuento_Porcentaje, Trx_Fecha) %>%
  na.omit() %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month"))) %>%
  group_by(Fecha) %>%
  summarise(
    Venta = sum(Venta, na.rm = TRUE),
    Cant = sum(Cant, na.rm = TRUE),
    Costo_Venta = sum(Costo_Venta, na.rm = TRUE),
    Precio_Final_Unitario = mean(Precio_Final_Unitario, na.rm = TRUE),
    Descuento_Porcentaje = mean(Descuento_Porcentaje, na.rm = TRUE)
  ) %>%
  arrange(Fecha) %>%
  # Paso 2: Aplicar lag sobrescribiendo los nombres originales y crear variable Tiempo
  mutate(
    Tiempo = as.numeric(Fecha - min(Fecha)) / 30,
    Cant = lag(Cant, 1),
    Costo_Venta = lag(Costo_Venta, 1),
    Precio_Final_Unitario = lag(Precio_Final_Unitario, 1),
    Descuento_Porcentaje = lag(Descuento_Porcentaje, 1)
  ) %>%
  drop_na()

# Paso 3: Quitar el mes más reciente
datos_3929788 <- datos_3929788 %>%
  filter(Fecha < max(Fecha))

# Verificar las primeras filas
head(datos_3929788)
## # A tibble: 6 × 7
##   Fecha       Venta  Cant Costo_Venta Precio_Final_Unitario Descuento_Porcentaje
##   <date>      <dbl> <dbl>       <dbl>                 <dbl>                <dbl>
## 1 2023-02-01 6.74e5 24313     658512.                  36.5                 59.9
## 2 2023-03-01 1.06e6 18359     525675.                  38.2                 60.7
## 3 2023-04-01 8.02e5 28117     829172.                  40.1                 59.9
## 4 2023-05-01 8.49e5 21365     618700.                  39.2                 61.1
## 5 2023-06-01 8.84e5 22508     663456.                  39.6                 60.7
## 6 2023-07-01 8.51e5 23474     687216.                  39.2                 61.1
## # ℹ 1 more variable: Tiempo <dbl>
# Ajustar el modelo de regresión lineal
modelo_regresion_3929788 <- lm(Venta ~ Cant + Costo_Venta +
                              Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
                              data = datos_3929788)

# Ver resumen del modelo
summary(modelo_regresion_3929788)
## 
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Precio_Final_Unitario + 
##     Descuento_Porcentaje + Tiempo, data = datos_3929788)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -251762  -80277  -34916   90712  239480 
## 
## Coefficients:
##                         Estimate Std. Error t value Pr(>|t|)
## (Intercept)           -9.423e+06  7.607e+06  -1.239    0.233
## Cant                   5.698e+00  1.425e+02   0.040    0.969
## Costo_Venta           -3.808e-01  4.671e+00  -0.082    0.936
## Precio_Final_Unitario  7.467e+04  7.604e+04   0.982    0.341
## Descuento_Porcentaje   1.242e+05  9.096e+04   1.366    0.191
## Tiempo                -6.400e+03  1.210e+04  -0.529    0.604
## 
## Residual standard error: 149400 on 16 degrees of freedom
## Multiple R-squared:  0.4338, Adjusted R-squared:  0.2569 
## F-statistic: 2.452 on 5 and 16 DF,  p-value: 0.07851
# Predicciones usando el modelo ajustado
predicciones_3929788 <- predict(modelo_regresion_3929788, newdata = datos_3929788)

# Calcular MAPE (Mean Absolute Percentage Error)
# Añadimos protección contra división por cero
mape_3929788 <- mean(abs((datos_3929788$Venta - predicciones_3929788) / pmax(datos_3929788$Venta, 0.01))) * 100

# Calcular MSE (Mean Squared Error)
rmse_3929788 <- sqrt(mean((datos_3929788$Venta - predicciones_3929788)^2))


# Mostrar las métricas
cat("MAPE del modelo de regresión lineal para 3929788: ", mape_3929788, "\n")
## MAPE del modelo de regresión lineal para 3929788:  11.53878
cat("RMSE del modelo de regresión lineal para 3929788: ", rmse_3929788, "\n")
## RMSE del modelo de regresión lineal para 3929788:  127410.9
# Diagnóstico de residuos del modelo
par(mfrow = c(2, 2))
plot(modelo_regresion_3929788)

# Guardar métricas de Regresión Lineal para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3929788",  # Cambia este ID para cada producto
  Modelo = "Regresión Lineal",
  MAPE = mape_3929788,
  RMSE = rmse_3929788
))

5.4 PRODUCTO 3904152

library(dplyr)
library(lubridate)

# Paso 1: Filtrar, seleccionar y preparar fecha mensual
datos_3904152 <- datos_filtrados %>%
  filter(ID_Inventario == 3904152) %>%
  select(Venta, Cant, Costo_Venta,
         Precio_Final_Unitario, Descuento_Porcentaje, Trx_Fecha) %>%
  na.omit() %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month"))) %>%
  group_by(Fecha) %>%
  summarise(
    Venta = sum(Venta, na.rm = TRUE),
    Cant = sum(Cant, na.rm = TRUE),
    Costo_Venta = sum(Costo_Venta, na.rm = TRUE),
    Precio_Final_Unitario = mean(Precio_Final_Unitario, na.rm = TRUE),
    Descuento_Porcentaje = mean(Descuento_Porcentaje, na.rm = TRUE)
  ) %>%
  arrange(Fecha) %>%
  # Paso 2: Crear variable de tiempo y aplicar lag sobrescribiendo los nombres
  mutate(
    Tiempo = as.numeric(Fecha - min(Fecha)) / 30,
    Cant = lag(Cant, 1),
    Costo_Venta = lag(Costo_Venta, 1),
    Precio_Final_Unitario = lag(Precio_Final_Unitario, 1),
    Descuento_Porcentaje = lag(Descuento_Porcentaje, 1)
  ) %>%
  drop_na()

# Paso 3: Eliminar el mes más reciente
datos_3904152 <- datos_3904152 %>%
  filter(Fecha < max(Fecha))

# Verificar resultado
head(datos_3904152)
## # A tibble: 6 × 7
##   Fecha       Venta  Cant Costo_Venta Precio_Final_Unitario Descuento_Porcentaje
##   <date>      <dbl> <dbl>       <dbl>                 <dbl>                <dbl>
## 1 2023-02-01 6.35e5   249     612759.                 3294.                 63.6
## 2 2023-03-01 8.54e5   195     479793.                 3304.                 63.5
## 3 2023-04-01 7.02e5   266     655051.                 3287.                 63.7
## 4 2023-05-01 8.42e5   216     532155.                 3284.                 63.7
## 5 2023-06-01 7.57e5   261     639753.                 3262.                 64.0
## 6 2023-07-01 4.93e5   240     581623.                 3203.                 64.6
## # ℹ 1 more variable: Tiempo <dbl>
# Ajustar el modelo de regresión lineal
modelo_regresion_3904152 <- lm(Venta ~ Cant + Costo_Venta +
                              Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
                             data = datos_3904152)

# Ver resumen del modelo
summary(modelo_regresion_3904152)
## 
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Precio_Final_Unitario + 
##     Descuento_Porcentaje + Tiempo, data = datos_3904152)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -231962  -87438   -6496   86715  286694 
## 
## Coefficients:
##                         Estimate Std. Error t value Pr(>|t|)
## (Intercept)           -5.258e+06  8.132e+06  -0.647    0.527
## Cant                   2.854e+04  1.780e+04   1.603    0.128
## Costo_Venta           -1.242e+01  7.598e+00  -1.635    0.122
## Precio_Final_Unitario  1.374e+03  1.190e+03   1.155    0.265
## Descuento_Porcentaje   3.102e+04  7.775e+04   0.399    0.695
## Tiempo                -8.244e+03  1.411e+04  -0.584    0.567
## 
## Residual standard error: 154500 on 16 degrees of freedom
## Multiple R-squared:  0.2534, Adjusted R-squared:  0.02012 
## F-statistic: 1.086 on 5 and 16 DF,  p-value: 0.4051
# Predicciones usando el modelo ajustado
predicciones_3904152 <- predict(modelo_regresion_3904152, newdata = datos_3904152)

# Calcular MAPE (Mean Absolute Percentage Error)
# Añadimos protección contra división por cero
mape_3904152 <- mean(abs((datos_3904152$Venta - predicciones_3904152) / pmax(datos_3904152$Venta, 0.01))) * 100

# Calcular MSE (Mean Squared Error)
rmse_3904152 <- sqrt(mean((datos_3904152$Venta - predicciones_3904152)^2))


# Mostrar las métricas
cat("MAPE del modelo de regresión lineal para 3904152: ", mape_3904152, "\n")
## MAPE del modelo de regresión lineal para 3904152:  13.72326
cat("RMSE del modelo de regresión lineal para 3904152: ", rmse_3904152, "\n")
## RMSE del modelo de regresión lineal para 3904152:  131733.7
# Diagnóstico de residuos del modelo
par(mfrow = c(2, 2))
plot(modelo_regresion_3904152)

# Guardar métricas de Regresión Lineal para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3904152",  # Cambia este ID para cada producto
  Modelo = "Regresión Lineal",
  MAPE = mape_3904152,
  RMSE = rmse_3904152
))

5.5 PRODUCTO 155002

library(dplyr)
library(lubridate)

# Paso 1: Filtrar, seleccionar y crear fecha mensual
datos_155002 <- datos_filtrados %>%
  filter(ID_Inventario == 155002) %>%
  select(Venta, Cant, Costo_Venta,
         Precio_Final_Unitario, Descuento_Porcentaje, Trx_Fecha) %>%
  na.omit() %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month"))) %>%
  group_by(Fecha) %>%
  summarise(
    Venta = sum(Venta, na.rm = TRUE),
    Cant = sum(Cant, na.rm = TRUE),
    Costo_Venta = sum(Costo_Venta, na.rm = TRUE),
    Precio_Final_Unitario = mean(Precio_Final_Unitario, na.rm = TRUE),
    Descuento_Porcentaje = mean(Descuento_Porcentaje, na.rm = TRUE)
  ) %>%
  arrange(Fecha) %>%
  # Paso 2: Crear variable de tiempo y aplicar lag sobrescribiendo los nombres originales
  mutate(
    Tiempo = as.numeric(Fecha - min(Fecha)) / 30,
    Cant = lag(Cant, 1),
    Costo_Venta = lag(Costo_Venta, 1),
    Precio_Final_Unitario = lag(Precio_Final_Unitario, 1),
    Descuento_Porcentaje = lag(Descuento_Porcentaje, 1)
  ) %>%
  drop_na()

# Paso 3: Eliminar el mes más reciente
datos_155002 <- datos_155002 %>%
  filter(Fecha < max(Fecha))

# Verificar las primeras filas
head(datos_155002)
## # A tibble: 6 × 7
##   Fecha       Venta  Cant Costo_Venta Precio_Final_Unitario Descuento_Porcentaje
##   <date>      <dbl> <dbl>       <dbl>                 <dbl>                <dbl>
## 1 2023-02-01 3.60e5   729     509256.                  693.                 83.5
## 2 2023-03-01 9.01e5   626     390041.                  618.                 85.3
## 3 2023-04-01 6.14e5  1824    1038385.                  556.                 86.7
## 4 2023-05-01 8.88e5  1259     622059.                  526.                 87.5
## 5 2023-06-01 9.11e5  1860     751527.                  511.                 87.8
## 6 2023-07-01 4.87e5  1893     724182.                  493.                 88.2
## # ℹ 1 more variable: Tiempo <dbl>
# Ajustar el modelo de regresión lineal
modelo_regresion_155002 <- lm(Venta ~ Cant + Costo_Venta +
                              Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
                             data = datos_155002)

# Ver resumen del modelo
summary(modelo_regresion_155002)
## 
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Precio_Final_Unitario + 
##     Descuento_Porcentaje + Tiempo, data = datos_155002)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -390673 -138485  -67526  165419  398310 
## 
## Coefficients:
##                         Estimate Std. Error t value Pr(>|t|)
## (Intercept)           -1.192e+05  1.479e+07  -0.008    0.994
## Cant                   1.634e+02  3.363e+02   0.486    0.634
## Costo_Venta           -9.306e-02  8.710e-01  -0.107    0.916
## Precio_Final_Unitario  2.575e+02  2.743e+03   0.094    0.926
## Descuento_Porcentaje   5.622e+03  1.563e+05   0.036    0.972
## Tiempo                -4.757e+02  2.854e+04  -0.017    0.987
## 
## Residual standard error: 249400 on 16 degrees of freedom
## Multiple R-squared:  0.1138, Adjusted R-squared:  -0.1631 
## F-statistic: 0.4109 on 5 and 16 DF,  p-value: 0.8342
# Predicciones usando el modelo ajustado
predicciones_155002 <- predict(modelo_regresion_155002, newdata = datos_155002)

# Calcular MAPE (Mean Absolute Percentage Error)
# Añadimos protección contra división por cero
mape_155002 <- mean(abs((datos_155002$Venta - predicciones_155002) / pmax(datos_155002$Venta, 0.01))) * 100

# Calcular RMSE (Root Mean Squared Error)
rmse_155002 <- sqrt(mean((datos_155002$Venta - predicciones_155002)^2))


# Mostrar las métricas
cat("MAPE del modelo de regresión lineal para 155002: ", mape_155002, "\n")
## MAPE del modelo de regresión lineal para 155002:  29.16643
cat("RMSE del modelo de regresión lineal para 155002: ", rmse_155002, "\n")
## RMSE del modelo de regresión lineal para 155002:  212703.4
# Diagnóstico de residuos del modelo
par(mfrow = c(2, 2))
plot(modelo_regresion_155002)

# Guardar métricas de Regresión Lineal para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "155002",  # Cambia este ID para cada producto
  Modelo = "Regresión Lineal",
  MAPE = mape_155002,
  RMSE = rmse_155002
  ))

5.6 PRODUCTO 3678055

library(dplyr)
library(lubridate)

# Paso 1: Filtrar, seleccionar y crear fecha mensual
datos_3678055 <- datos_filtrados %>%
  filter(ID_Inventario == 3678055) %>%
  select(Venta, Cant, Costo_Venta,
         Precio_Final_Unitario, Descuento_Porcentaje, Trx_Fecha) %>%
  na.omit() %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month"))) %>%
  group_by(Fecha) %>%
  summarise(
    Venta = sum(Venta, na.rm = TRUE),
    Cant = sum(Cant, na.rm = TRUE),
    Costo_Venta = sum(Costo_Venta, na.rm = TRUE),
    Precio_Final_Unitario = mean(Precio_Final_Unitario, na.rm = TRUE),
    Descuento_Porcentaje = mean(Descuento_Porcentaje, na.rm = TRUE)
  ) %>%
  arrange(Fecha) %>%
  # Paso 2: Crear variable Tiempo y aplicar lag sobrescribiendo nombres originales
  mutate(
    Tiempo = as.numeric(Fecha - min(Fecha)) / 30,
    Cant = lag(Cant, 1),
    Costo_Venta = lag(Costo_Venta, 1),
    Precio_Final_Unitario = lag(Precio_Final_Unitario, 1),
    Descuento_Porcentaje = lag(Descuento_Porcentaje, 1)
  ) %>%
  drop_na()

# Paso 3: Eliminar el mes más reciente
datos_3678055 <- datos_3678055 %>%
  filter(Fecha < max(Fecha))

# Verificar las primeras filas
head(datos_3678055)
## # A tibble: 6 × 7
##   Fecha       Venta  Cant Costo_Venta Precio_Final_Unitario Descuento_Porcentaje
##   <date>      <dbl> <dbl>       <dbl>                 <dbl>                <dbl>
## 1 2023-02-01 5.05e5   133     546959.                 5520.                 63.8
## 2 2023-03-01 9.50e5    92     382885.                 5541.                 63.7
## 3 2023-04-01 7.46e5   173     719873.                 5596.                 63.3
## 4 2023-05-01 5.77e5   139     566072.                 5420.                 64.5
## 5 2023-06-01 9.57e5   107     434320.                 5441.                 64.3
## 6 2023-07-01 4.77e5   182     709683.                 5283.                 65.4
## # ℹ 1 more variable: Tiempo <dbl>
# Ajustar el modelo de regresión lineal
modelo_regresion_3678055 <- lm(Venta ~ Cant + Costo_Venta +
                              Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
                             data = datos_3678055)

# Ver resumen del modelo
summary(modelo_regresion_3678055)
## 
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Precio_Final_Unitario + 
##     Descuento_Porcentaje + Tiempo, data = datos_3678055)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -295140  -96035   -9627  105471  413766 
## 
## Coefficients:
##                         Estimate Std. Error t value Pr(>|t|)
## (Intercept)           -7.506e+06  7.102e+06  -1.057    0.306
## Cant                   2.649e+04  2.901e+04   0.913    0.375
## Costo_Venta           -6.952e+00  7.484e+00  -0.929    0.367
## Precio_Final_Unitario  8.735e+02  6.085e+02   1.435    0.170
## Descuento_Porcentaje   5.800e+04  7.223e+04   0.803    0.434
## Tiempo                -9.404e+03  1.173e+04  -0.802    0.435
## 
## Residual standard error: 195000 on 16 degrees of freedom
## Multiple R-squared:  0.1526, Adjusted R-squared:  -0.1122 
## F-statistic: 0.5764 on 5 and 16 DF,  p-value: 0.7175
#Predicciones usando el modelo ajustado
predicciones_3678055 <- predict(modelo_regresion_3678055, newdata = datos_3678055)
# Calcular MAPE (Mean Absolute Percentage Error)
# Añadimos protección contra división por cero
mape_3678055 <- mean(abs((datos_3678055$Venta - predicciones_3678055) / pmax(datos_3678055$Venta, 0.01))) * 100

# Calcular RMSE (Root Mean Squared Error)

rmse_3678055 <- mean((datos_3678055$Venta - predicciones_3678055)^2)

# Mostrar las métricas
cat("MAPE del modelo de regresión lineal para 3678055: ", mape_3678055, "\n")
## MAPE del modelo de regresión lineal para 3678055:  21.14984
cat("RMSE del modelo de regresión lineal para 3678055: ", rmse_3678055, "\n")
## RMSE del modelo de regresión lineal para 3678055:  27658115614
# Diagnóstico de residuos del modelo
par(mfrow = c(2, 2))
plot(modelo_regresion_3678055)

# Guardar métricas de Regresión Lineal para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3678055",  # Cambia este ID para cada producto
  Modelo = "Regresión Lineal",
  MAPE = mape_3678055,
  RMSE = rmse_3678055
))

5.7 ANALISIS DE VARIABLES IMPORTANTES

# Función simplificada para analizar coeficientes
analizar_coeficientes <- function(modelo, nombre_producto) {
  resumen <- summary(modelo)
  coef_df <- as.data.frame(resumen$coefficients)
  colnames(coef_df) <- c("Estimate", "Std.Error", "t.value", "p.value")
  coef_df$Variable <- rownames(coef_df)
  coef_df$Producto <- nombre_producto
  coef_df$Significativo <- ifelse(coef_df$p.value < 0.05, "Sí", "No")
  
  return(coef_df %>%
           select(Producto, Variable, Estimate, p.value, Significativo) %>%
           arrange(desc(abs(Estimate))))
}

# Aplicar a cada modelo
coef_155001 <- analizar_coeficientes(modelo_regresion_155001, "155001")
coef_155002 <- analizar_coeficientes(modelo_regresion_155002, "155002")
coef_3678055 <- analizar_coeficientes(modelo_regresion_3678055, "3678055")
coef_3904152 <- analizar_coeficientes(modelo_regresion_3904152, "3904152")
coef_3929788 <- analizar_coeficientes(modelo_regresion_3929788, "3929788")

# Combinar todos los coeficientes
todos_coeficientes <- bind_rows(coef_155001, coef_155002, coef_3678055, coef_3904152, coef_3929788)

# Tabla con variables importantes incluyendo significancia
variables_importantes <- todos_coeficientes %>%
  filter(Variable != "(Intercept)") %>%
  group_by(Producto) %>%
  arrange(Producto, desc(abs(Estimate))) %>%
  mutate(Impacto = ifelse(Estimate > 0, "Positivo", "Negativo"))

# Tabla completa con todas las variables importantes
kable(variables_importantes %>% 
        select(Producto, Variable, Estimate, p.value, Significativo, Impacto),
      caption = "Variables importantes por producto",
      col.names = c("Producto", "Variable", "Coeficiente", "p-value", "Significativo", "Impacto"),
      digits = c(0, 0, 4, 4, 0, 0)) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"))
Variables importantes por producto
Producto Variable Coeficiente p-value Significativo Impacto
155001 Descuento_Porcentaje 11182.8868 0.9513 No Positivo
155001 Tiempo -4862.2295 0.8941 No Negativo
155001 Precio_Final_Unitario -382.4783 0.9212 No Negativo
155001 Cant -47.6710 0.9281 No Negativo
155001 Costo_Venta 0.6118 0.6973 No Positivo
155002 Descuento_Porcentaje 5621.8170 0.9717 No Positivo
155002 Tiempo -475.6563 0.9869 No Negativo
155002 Precio_Final_Unitario 257.4528 0.9264 No Positivo
155002 Cant 163.4161 0.6336 No Positivo
155002 Costo_Venta -0.0931 0.9162 No Negativo
3678055 Descuento_Porcentaje 58002.0622 0.4337 No Positivo
3678055 Cant 26492.5523 0.3748 No Positivo
3678055 Tiempo -9404.0730 0.4345 No Negativo
3678055 Precio_Final_Unitario 873.4896 0.1704 No Positivo
3678055 Costo_Venta -6.9518 0.3667 No Negativo
3904152 Descuento_Porcentaje 31017.9944 0.6952 No Positivo
3904152 Cant 28542.4385 0.1284 No Positivo
3904152 Tiempo -8243.7116 0.5672 No Negativo
3904152 Precio_Final_Unitario 1374.0560 0.2651 No Positivo
3904152 Costo_Venta -12.4189 0.1217 No Negativo
3929788 Descuento_Porcentaje 124221.7524 0.1909 No Positivo
3929788 Precio_Final_Unitario 74670.3371 0.3407 No Positivo
3929788 Tiempo -6399.6812 0.6041 No Negativo
3929788 Cant 5.6981 0.9686 No Positivo
3929788 Costo_Venta -0.3808 0.9360 No Negativo
# Tabla resumen con top 3 por producto
top_por_producto <- variables_importantes %>%
  group_by(Producto) %>%
  slice_head(n = 3) %>%
  select(Producto, Variable, Estimate, p.value, Significativo, Impacto)

kable(top_por_producto,
      caption = "Top 3 variables más importantes por producto",
      col.names = c("Producto", "Variable", "Coeficiente", "p-value", "Significativo", "Impacto"),
      digits = c(0, 0, 4, 4, 0, 0)) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"))
Top 3 variables más importantes por producto
Producto Variable Coeficiente p-value Significativo Impacto
155001 Descuento_Porcentaje 11182.8868 0.9513 No Positivo
155001 Tiempo -4862.2295 0.8941 No Negativo
155001 Precio_Final_Unitario -382.4783 0.9212 No Negativo
155002 Descuento_Porcentaje 5621.8170 0.9717 No Positivo
155002 Tiempo -475.6563 0.9869 No Negativo
155002 Precio_Final_Unitario 257.4528 0.9264 No Positivo
3678055 Descuento_Porcentaje 58002.0622 0.4337 No Positivo
3678055 Cant 26492.5523 0.3748 No Positivo
3678055 Tiempo -9404.0730 0.4345 No Negativo
3904152 Descuento_Porcentaje 31017.9944 0.6952 No Positivo
3904152 Cant 28542.4385 0.1284 No Positivo
3904152 Tiempo -8243.7116 0.5672 No Negativo
3929788 Descuento_Porcentaje 124221.7524 0.1909 No Positivo
3929788 Precio_Final_Unitario 74670.3371 0.3407 No Positivo
3929788 Tiempo -6399.6812 0.6041 No Negativo

6 RANDOM FOREST

6.1 PRODUCTO 155001

# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_155001 %>%
  select(-Fecha)

# Ajustar el modelo Random Forest
set.seed(123)  # Para reproducibilidad
modelo_rf_155001 <- randomForest(
  Venta ~ ., 
  data = datos_modelo,
  ntree = 500,          # Número de árboles
  mtry = floor(sqrt(ncol(datos_modelo) - 1)),  # Número de variables a considerar en cada split
  importance = TRUE     # Calcular importancia de variables
)

# Ver resumen del modelo
print(modelo_rf_155001)
## 
## Call:
##  randomForest(formula = Venta ~ ., data = datos_modelo, ntree = 500,      mtry = floor(sqrt(ncol(datos_modelo) - 1)), importance = TRUE) 
##                Type of random forest: regression
##                      Number of trees: 500
## No. of variables tried at each split: 2
## 
##           Mean of squared residuals: 69764878841
##                     % Var explained: 1.6
# Obtener predicciones
predicciones_rf <- predict(modelo_rf_155001, newdata = datos_modelo)

# Calcular métricas
# MAPE
mape_rf <- mean(abs((datos_modelo$Venta - predicciones_rf) / pmax(datos_modelo$Venta, 0.01))) * 100

# RMSE
rmse_rf <- mean((datos_modelo$Venta - predicciones_rf)^2)

# Mostrar las métricas
cat("Modelo Random Forest para producto 155001\n")
## Modelo Random Forest para producto 155001
cat("MAPE del modelo Random Forest:", mape_rf, "\n")
## MAPE del modelo Random Forest: 9.639597
cat("RMSE del modelo Random Forest:", rmse_rf, "\n\n")
## RMSE del modelo Random Forest: 15583286209
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_155001)
print(importancia_vars)
##                        %IncMSE IncNodePurity
## Cant                  2.399890  259118982893
## Costo_Venta           9.291212  427603221979
## Precio_Final_Unitario 6.188319  245656710335
## Descuento_Porcentaje  6.173530  180504145086
## Tiempo                5.677214  177368461598
# Graficar importancia de variables
varImpPlot(modelo_rf_155001, main = "Importancia de Variables - Producto 155001")

# Crear gráfico de valores observados vs predicciones
datos_grafico <- data.frame(
  Observado = datos_modelo$Venta,
  Predicho = predicciones_rf
)

ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
  labs(
    title = "Valores Observados vs Predicciones - Producto 155001",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

# NUEVOS ANÁLISIS AÑADIDOS

# Análisis del error
errores <- datos_grafico$Observado - datos_grafico$Predicho
hist(errores, 
     main = "Distribución de Errores - Producto 155001",
     xlab = "Error (Observado - Predicho)",
     col = "skyblue",
     breaks = 30)

# Estadísticas descriptivas de los errores
cat("Estadísticas descriptivas de los errores:\n")
## Estadísticas descriptivas de los errores:
cat("Media de errores:", mean(errores), "\n")
## Media de errores: -1507.918
cat("Desviación estándar de errores:", sd(errores), "\n")
## Desviación estándar de errores: 127761.4
cat("Mínimo:", min(errores), "\n")
## Mínimo: -183733.5
cat("Máximo:", max(errores), "\n")
## Máximo: 356389.7
cat("Mediana:", median(errores), "\n")
## Mediana: -19101.66
# Gráfico del error vs predicción
ggplot(data.frame(Predicho = predicciones_rf, Error = errores), aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(
    title = "Error vs Predicción - Producto 155001",
    x = "Ventas Predichas",
    y = "Error (Observado - Predicho)"
  ) +
  theme_minimal()

# Guardar métricas de Random Forest para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "155001",  # Cambia este ID para cada producto
  Modelo = "Random Forest",
  MAPE = mape_rf,
  RMSE = rmse_rf
))

6.2 PRODUCTO 3929788

# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_3929788 %>%
  select(-Fecha)

# Ajustar el modelo Random Forest
set.seed(123)  # Para reproducibilidad
modelo_rf_3929788 <- randomForest(
  Venta ~ ., 
  data = datos_modelo,
  ntree = 500,          # Número de árboles
  mtry = floor(sqrt(ncol(datos_modelo) - 1)),  # Número de variables a considerar en cada split
  importance = TRUE     # Calcular importancia de variables
)

# Ver resumen del modelo
print(modelo_rf_3929788)
## 
## Call:
##  randomForest(formula = Venta ~ ., data = datos_modelo, ntree = 500,      mtry = floor(sqrt(ncol(datos_modelo) - 1)), importance = TRUE) 
##                Type of random forest: regression
##                      Number of trees: 500
## No. of variables tried at each split: 2
## 
##           Mean of squared residuals: 22098763192
##                     % Var explained: 22.93
# Obtener predicciones
predicciones_rf <- predict(modelo_rf_3929788, newdata = datos_modelo)

# Calcular métricas
# MAPE
mape_rf <- mean(abs((datos_modelo$Venta - predicciones_rf) / pmax(datos_modelo$Venta, 0.01))) * 100

# RMSE

rmse_rf <- sqrt(mean((datos_modelo$Venta - predicciones_rf)^2))


# Mostrar las métricas
cat("Modelo Random Forest para producto 3929788\n")
## Modelo Random Forest para producto 3929788
cat("MAPE del modelo Random Forest:", mape_rf, "\n")
## MAPE del modelo Random Forest: 6.089162
cat("RMSE del modelo Random Forest:", rmse_rf, "\n\n")
## RMSE del modelo Random Forest: 66884.79
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_3929788)
print(importancia_vars)
##                        %IncMSE IncNodePurity
## Cant                  2.403953   69410237980
## Costo_Venta           3.652982   72239383844
## Precio_Final_Unitario 8.124968  130132085912
## Descuento_Porcentaje  5.606035  145090539662
## Tiempo                9.025752  128653803596
# Graficar importancia de variables
varImpPlot(modelo_rf_3929788, main = "Importancia de Variables - Producto 3929788")

# Crear gráfico de valores observados vs predicciones
datos_grafico <- data.frame(
  Observado = datos_modelo$Venta,
  Predicho = predicciones_rf
)

ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
  labs(
    title = "Valores Observados vs Predicciones - Producto 3929788",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

# Análisis del error
errores <- datos_grafico$Observado - datos_grafico$Predicho
hist(errores, 
     main = "Distribución de Errores - Producto 3929788",
     xlab = "Error (Observado - Predicho)",
     col = "skyblue",
     breaks = 30)

# Gráfico del error vs predicción
ggplot(data.frame(Predicho = predicciones_rf, Error = errores), aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(
    title = "Error vs Predicción - Producto 3929788",
    x = "Ventas Predichas",
    y = "Error (Observado - Predicho)"
  ) +
  theme_minimal()

# Guardar métricas de Random Forest para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),

    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3929788",  # Cambia este ID para cada producto
  Modelo = "Random Forest",
  MAPE = mape_rf,
  RMSE = rmse_rf

))

6.3 PRODUCTO 3904152

# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_3904152 %>%
  select(-Fecha)

# Ajustar el modelo Random Forest
set.seed(123)  # Para reproducibilidad
modelo_rf_3904152 <- randomForest(
  Venta ~ ., 
  data = datos_modelo,
  ntree = 500,          # Número de árboles
  mtry = floor(sqrt(ncol(datos_modelo) - 1)),  # Número de variables a considerar en cada split
  importance = TRUE     # Calcular importancia de variables
)

# Ver resumen del modelo
print(modelo_rf_3904152)
## 
## Call:
##  randomForest(formula = Venta ~ ., data = datos_modelo, ntree = 500,      mtry = floor(sqrt(ncol(datos_modelo) - 1)), importance = TRUE) 
##                Type of random forest: regression
##                      Number of trees: 500
## No. of variables tried at each split: 2
## 
##           Mean of squared residuals: 34906915882
##                     % Var explained: -50.17
# Obtener predicciones
predicciones_rf <- predict(modelo_rf_3904152, newdata = datos_modelo)

# Calcular métricas
# MAPE
mape_rf <- mean(abs((datos_modelo$Venta - predicciones_rf) / pmax(datos_modelo$Venta, 0.01))) * 100

# RMSE
rmse_rf <- sqrt(mean((datos_modelo$Venta - predicciones_rf)^2))


# Mostrar las métricas
cat("Modelo Random Forest para producto 3904152\n")
## Modelo Random Forest para producto 3904152
cat("MAPE del modelo Random Forest:", mape_rf, "\n")
## MAPE del modelo Random Forest: 8.207213
cat("RMSE del modelo Random Forest:", rmse_rf, "\n\n")
## RMSE del modelo Random Forest: 83391.79
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_3904152)
print(importancia_vars)
##                           %IncMSE IncNodePurity
## Cant                   3.02822435   71920676858
## Costo_Venta            2.75832784   99010592277
## Precio_Final_Unitario -1.62456668   86607008846
## Descuento_Porcentaje   0.06391131   94317773511
## Tiempo                 4.09906683   78406968325
# Graficar importancia de variables
varImpPlot(modelo_rf_3904152, main = "Importancia de Variables - Producto 3904152")

# Crear gráfico de valores observados vs predicciones
datos_grafico <- data.frame(
  Observado = datos_modelo$Venta,
  Predicho = predicciones_rf
)

ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
  labs(
    title = "Valores Observados vs Predicciones - Producto 3904152",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

# Análisis del error
errores <- datos_grafico$Observado - datos_grafico$Predicho
hist(errores, 
     main = "Distribución de Errores - Producto 3904152",
     xlab = "Error (Observado - Predicho)",
     col = "skyblue",
     breaks = 30)

# Gráfico del error vs predicción
ggplot(data.frame(Predicho = predicciones_rf, Error = errores), aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(
    title = "Error vs Predicción - Producto 3904152",
    x = "Ventas Predichas",
    y = "Error (Observado - Predicho)"
  ) +
  theme_minimal()

# Guardar métricas de Random Forest para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),

    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3904152",  # Cambia este ID para cada producto
  Modelo = "Random Forest",
  MAPE = mape_rf,
  RMSE = rmse_rf

))

6.4 PRODUCTO 155002

# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_155002 %>%
  select(-Fecha)

# Ajustar el modelo Random Forest
set.seed(123)  # Para reproducibilidad
modelo_rf_155002 <- randomForest(
  Venta ~ ., 
  data = datos_modelo,
  ntree = 500,          # Número de árboles
  mtry = floor(sqrt(ncol(datos_modelo) - 1)),  # Número de variables a considerar en cada split
  importance = TRUE     # Calcular importancia de variables
)

# Ver resumen del modelo
print(modelo_rf_155002)
## 
## Call:
##  randomForest(formula = Venta ~ ., data = datos_modelo, ntree = 500,      mtry = floor(sqrt(ncol(datos_modelo) - 1)), importance = TRUE) 
##                Type of random forest: regression
##                      Number of trees: 500
## No. of variables tried at each split: 2
## 
##           Mean of squared residuals: 51845743413
##                     % Var explained: -1.55
# Obtener predicciones
predicciones_rf <- predict(modelo_rf_155002, newdata = datos_modelo)

# Calcular métricas
# MAPE
mape_rf <- mean(abs((datos_modelo$Venta - predicciones_rf) / pmax(datos_modelo$Venta, 0.01))) * 100

# MSE
rmse_rf <- sqrt(mean((datos_modelo$Venta - predicciones_rf)^2))


# Mostrar las métricas
cat("Modelo Random Forest para producto 155002\n")
## Modelo Random Forest para producto 155002
cat("MAPE del modelo Random Forest:", mape_rf, "\n")
## MAPE del modelo Random Forest: 13.3742
cat("RMSE del modelo Random Forest:", rmse_rf, "\n\n")
## RMSE del modelo Random Forest: 103903.8
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_155002)
print(importancia_vars)
##                          %IncMSE IncNodePurity
## Cant                  -1.4239437  165041943794
## Costo_Venta            0.5547446  153975147910
## Precio_Final_Unitario  9.2890150  288682464917
## Descuento_Porcentaje   6.7191779  200207859941
## Tiempo                 2.9210790  149239859671
# Graficar importancia de variables
varImpPlot(modelo_rf_155002, main = "Importancia de Variables - Producto 155002")

# Crear gráfico de valores observados vs predicciones
datos_grafico <- data.frame(
  Observado = datos_modelo$Venta,
  Predicho = predicciones_rf
)

ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
  labs(
    title = "Valores Observados vs Predicciones - Producto 155002",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

# Análisis del error
errores <- datos_grafico$Observado - datos_grafico$Predicho
hist(errores, 
     main = "Distribución de Errores - Producto 155002",
     xlab = "Error (Observado - Predicho)",
     col = "skyblue",
     breaks = 30)

# Gráfico del error vs predicción
ggplot(data.frame(Predicho = predicciones_rf, Error = errores), aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(
    title = "Error vs Predicción - Producto 155002",
    x = "Ventas Predichas",
    y = "Error (Observado - Predicho)"
  ) +
  theme_minimal()

# Guardar métricas de Random Forest para producto 3678055
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3678055",  # Cambia este ID para cada producto
  Modelo = "Random Forest",
  MAPE = mape_rf,
  RMSE = rmse_rf

))

6.5 PRODUCTO 3678055

# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_3678055 %>%
  select(-Fecha)

# Ajustar el modelo Random Forest
set.seed(123)  # Para reproducibilidad
modelo_rf_3678055 <- randomForest(
  Venta ~ ., 
  data = datos_modelo,
  ntree = 500,          # Número de árboles
  mtry = floor(sqrt(ncol(datos_modelo) - 1)),  # Número de variables a considerar en cada split
  importance = TRUE     # Calcular importancia de variables
)

# Ver resumen del modelo
print(modelo_rf_3678055)
## 
## Call:
##  randomForest(formula = Venta ~ ., data = datos_modelo, ntree = 500,      mtry = floor(sqrt(ncol(datos_modelo) - 1)), importance = TRUE) 
##                Type of random forest: regression
##                      Number of trees: 500
## No. of variables tried at each split: 2
## 
##           Mean of squared residuals: 44900501694
##                     % Var explained: -37.56
# Obtener predicciones
predicciones_rf <- predict(modelo_rf_3678055, newdata = datos_modelo)

# Calcular métricas
# MAPE
mape_rf <- mean(abs((datos_modelo$Venta - predicciones_rf) / pmax(datos_modelo$Venta, 0.01))) * 100

# RMSE
rmse_rf <- sqrt(mean((datos_modelo$Venta - predicciones_rf)^2))


# Mostrar las métricas
cat("Modelo Random Forest para producto 3678055\n")
## Modelo Random Forest para producto 3678055
cat("MAPE del modelo Random Forest:", mape_rf, "\n")
## MAPE del modelo Random Forest: 13.13685
cat("RMSE del modelo Random Forest:", rmse_rf, "\n\n")
## RMSE del modelo Random Forest: 106293.2
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_3678055)
print(importancia_vars)
##                          %IncMSE IncNodePurity
## Cant                   1.3560856   91501105235
## Costo_Venta            0.6367836   71045453124
## Precio_Final_Unitario  4.0961557  142881477730
## Descuento_Porcentaje  -0.9252088  120460017194
## Tiempo                 2.0724678  124928635912
# Graficar importancia de variables
varImpPlot(modelo_rf_3678055, main = "Importancia de Variables - Producto 3678055")

# Crear gráfico de valores observados vs predicciones
datos_grafico <- data.frame(
  Observado = datos_modelo$Venta,
  Predicho = predicciones_rf
)

ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
  labs(
    title = "Valores Observados vs Predicciones - Producto 3678055",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

# Análisis del error
errores <- datos_grafico$Observado - datos_grafico$Predicho
hist(errores, 
     main = "Distribución de Errores - Producto 3678055",
     xlab = "Error (Observado - Predicho)",
     col = "skyblue",
     breaks = 30)

# Gráfico del error vs predicción
ggplot(data.frame(Predicho = predicciones_rf, Error = errores), aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(
    title = "Error vs Predicción - Producto 3678055",
    x = "Ventas Predichas",
    y = "Error (Observado - Predicho)"
  ) +
  theme_minimal()

# Guardar métricas de Random Forest para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3678055",  # Cambia este ID para cada producto
  Modelo = "Random Forest",
  MAPE = mape_rf,
  RMSE = rmse_rf
))

7 XGBOOST

7.1 PRODUCTO 155001

# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_155001 %>%
  select(-Fecha)

# Dividir los datos en conjuntos de entrenamiento (80%) y prueba (20%)
set.seed(123)  # Para reproducibilidad
indices_train <- createDataPartition(datos_modelo$Venta, p = 0.8, list = FALSE)
datos_train <- datos_modelo[indices_train, ]
datos_test <- datos_modelo[-indices_train, ]

# Separar variables predictoras y variable objetivo
X_train <- as.matrix(datos_train[, colnames(datos_train) != "Venta"])
y_train <- datos_train$Venta

X_test <- as.matrix(datos_test[, colnames(datos_test) != "Venta"])
y_test <- datos_test$Venta

# Crear matrices DMatrix para XGBoost
dtrain <- xgb.DMatrix(data = X_train, label = y_train)
dtest <- xgb.DMatrix(data = X_test, label = y_test)

# Definir una rejilla completa de hiperparámetros para búsqueda
param_grid <- expand.grid(
  eta = c(0.01, 0.05, 0.1, 0.3),         # Learning rate
  max_depth = c(3, 5, 7, 9),             # Profundidad máxima
  subsample = c(0.6, 0.8, 1.0),          # Submuestra de observaciones
  colsample_bytree = c(0.6, 0.8, 1.0),   # Submuestra de variables
  min_child_weight = c(1, 3, 5),         # Peso mínimo en nodos hijos
  gamma = c(0, 0.1, 0.3)                 # Regularización gamma
)

# Mostrar cuántas combinaciones tenemos
cat("Número total de combinaciones de hiperparámetros:", nrow(param_grid), "\n")
## Número total de combinaciones de hiperparámetros: 1296
# Para este ejemplo, vamos a limitar el número de combinaciones
# Seleccionando un subconjunto aleatorio de combinaciones (20 combinaciones)
set.seed(123)
if (nrow(param_grid) > 20) {
  muestra_indices <- sample(1:nrow(param_grid), 20)
  param_grid_reducida <- param_grid[muestra_indices, ]
} else {
  param_grid_reducida <- param_grid
}

cat("Número de combinaciones a evaluar:", nrow(param_grid_reducida), "\n")
## Número de combinaciones a evaluar: 20
# Función para evaluar un conjunto de hiperparámetros con validación cruzada
evaluate_params <- function(params_row) {
  params <- list(
    objective = "reg:squarederror",
    eval_metric = "rmse",
    eta = params_row$eta,
    max_depth = params_row$max_depth,
    subsample = params_row$subsample,
    colsample_bytree = params_row$colsample_bytree,
    min_child_weight = params_row$min_child_weight,
    gamma = params_row$gamma
  )
  
  # Realizar validación cruzada
  cv_results <- xgb.cv(
    params = params,
    data = dtrain,
    nrounds = 100,
    nfold = 5,  # 5-fold validación cruzada
    early_stopping_rounds = 10,
    verbose = 0
  )
  
  # Extraer el mejor RMSE y el número óptimo de rondas
  best_rmse <- min(cv_results$evaluation_log$test_rmse_mean)
  best_nrounds <- which.min(cv_results$evaluation_log$test_rmse_mean)
  
  return(list(rmse = best_rmse, nrounds = best_nrounds, params = params))
}

# Inicializar tabla para almacenar resultados
resultados_grid <- data.frame(
  eta = numeric(nrow(param_grid_reducida)),
  max_depth = numeric(nrow(param_grid_reducida)),
  subsample = numeric(nrow(param_grid_reducida)),
  colsample_bytree = numeric(nrow(param_grid_reducida)),
  min_child_weight = numeric(nrow(param_grid_reducida)),
  gamma = numeric(nrow(param_grid_reducida)),
  nrounds = numeric(nrow(param_grid_reducida)),
  rmse = numeric(nrow(param_grid_reducida))
)





# Realizar la búsqueda en cuadrícula (esto puede tardar varios minutos)
cat("Iniciando búsqueda en cuadrícula...\n")
## Iniciando búsqueda en cuadrícula...
for (i in 1:nrow(param_grid_reducida)) {
  cat(sprintf("Evaluando combinación %d de %d\n", i, nrow(param_grid_reducida)))
  
  # Obtener fila de parámetros actual
  params_row <- param_grid_reducida[i, ]
  
  # Evaluar combinación actual
  result <- evaluate_params(params_row)
  
  # Guardar resultados
  resultados_grid$eta[i] <- params_row$eta
  resultados_grid$max_depth[i] <- params_row$max_depth
  resultados_grid$subsample[i] <- params_row$subsample
  resultados_grid$colsample_bytree[i] <- params_row$colsample_bytree
  resultados_grid$min_child_weight[i] <- params_row$min_child_weight
  resultados_grid$gamma[i] <- params_row$gamma
  resultados_grid$nrounds[i] <- result$nrounds
  resultados_grid$rmse[i] <- result$rmse
}
## Evaluando combinación 1 de 20
## Evaluando combinación 2 de 20
## Evaluando combinación 3 de 20
## Evaluando combinación 4 de 20
## Evaluando combinación 5 de 20
## Evaluando combinación 6 de 20
## Evaluando combinación 7 de 20
## Evaluando combinación 8 de 20
## Evaluando combinación 9 de 20
## Evaluando combinación 10 de 20
## Evaluando combinación 11 de 20
## Evaluando combinación 12 de 20
## Evaluando combinación 13 de 20
## Evaluando combinación 14 de 20
## Evaluando combinación 15 de 20
## Evaluando combinación 16 de 20
## Evaluando combinación 17 de 20
## Evaluando combinación 18 de 20
## Evaluando combinación 19 de 20
## Evaluando combinación 20 de 20
# Ordenar resultados por RMSE (de menor a mayor)
resultados_grid <- resultados_grid[order(resultados_grid$rmse), ]

# Mostrar los 5 mejores conjuntos de hiperparámetros
cat("\nLos 5 mejores conjuntos de hiperparámetros:\n")
## 
## Los 5 mejores conjuntos de hiperparámetros:
print(head(resultados_grid, 5))
##     eta max_depth subsample colsample_bytree min_child_weight gamma nrounds
## 7  0.05         5       1.0              1.0                3   0.3      84
## 20 0.05         7       0.8              0.6                1   0.0      94
## 3  0.10         3       1.0              0.6                3   0.0      39
## 4  0.05         9       1.0              0.8                1   0.1      54
## 6  0.05         7       0.8              0.8                1   0.3      71
##        rmse
## 7  205303.8
## 20 214545.0
## 3  219909.3
## 4  227816.4
## 6  230852.3
# Obtener los mejores hiperparámetros
mejores_params <- list(
  objective = "reg:squarederror",
  eval_metric = "rmse",
  eta = resultados_grid$eta[1],
  max_depth = resultados_grid$max_depth[1],
  subsample = resultados_grid$subsample[1],
  colsample_bytree = resultados_grid$colsample_bytree[1],
  min_child_weight = resultados_grid$min_child_weight[1],
  gamma = resultados_grid$gamma[1]
)

mejor_nrounds <- resultados_grid$nrounds[1]

cat("\nMejores hiperparámetros encontrados:\n")
## 
## Mejores hiperparámetros encontrados:
print(mejores_params)
## $objective
## [1] "reg:squarederror"
## 
## $eval_metric
## [1] "rmse"
## 
## $eta
## [1] 0.05
## 
## $max_depth
## [1] 5
## 
## $subsample
## [1] 1
## 
## $colsample_bytree
## [1] 1
## 
## $min_child_weight
## [1] 3
## 
## $gamma
## [1] 0.3
cat("Número óptimo de rondas:", mejor_nrounds, "\n")
## Número óptimo de rondas: 84
cat("RMSE en validación cruzada:", resultados_grid$rmse[1], "\n\n")
## RMSE en validación cruzada: 205303.8
# Entrenar el modelo final con los mejores hiperparámetros
modelo_xgb_155001 <- xgb.train(
  params = mejores_params,
  data = dtrain,
  nrounds = mejor_nrounds,
  watchlist = list(train = dtrain, test = dtest),
  verbose = 0
)

# Hacer predicciones en el conjunto de prueba
predicciones_test <- predict(modelo_xgb_155001, dtest)

# Calcular métricas en el conjunto de prueba
# MAPE
mape_test <- mean(abs((y_test - predicciones_test) / pmax(y_test, 0.01))) * 100

# RMSE
rmse_test <- sqrt(mean((y_test - predicciones_test)^2))


# Mostrar las métricas en el conjunto de prueba
cat("Métricas en el conjunto de prueba:\n")
## Métricas en el conjunto de prueba:
cat("MAPE del modelo XGBoost:", mape_test, "\n")
## MAPE del modelo XGBoost: 23.62232
cat("RMSE del modelo XGBoost:", rmse_test, "\n\n")
## RMSE del modelo XGBoost: 250591.4
# Ahora hacer predicciones en el conjunto completo para comparabilidad con otros modelos
X_completo <- as.matrix(datos_modelo[, colnames(datos_modelo) != "Venta"])
predicciones_completas <- predict(modelo_xgb_155001, X_completo)







# Calcular métricas en el conjunto completo
# MAPE
mape_completo <- mean(abs((datos_modelo$Venta - predicciones_completas) / pmax(datos_modelo$Venta, 0.01))) * 100

# MSE
rmse_completo <- sqrt(mean((datos_modelo$Venta - predicciones_completas)^2))



# Mostrar las métricas en el conjunto completo
cat("Métricas en el conjunto completo:\n")
## Métricas en el conjunto completo:
cat("MAPE del modelo XGBoost:", mape_completo, "\n")
## MAPE del modelo XGBoost: 7.252695
cat("RMSE del modelo XGBoost:", rmse_completo, "\n\n")
## RMSE del modelo XGBoost: 139180.7
# Importancia de variables
importancia <- xgb.importance(
  feature_names = colnames(datos_modelo)[colnames(datos_modelo) != "Venta"],
  model = modelo_xgb_155001
)
print(importancia)
##                  Feature       Gain      Cover Frequency
##                   <char>      <num>      <num>     <num>
## 1:           Costo_Venta 0.52162296 0.50690468 0.4351464
## 2:                  Cant 0.17001198 0.17615359 0.1924686
## 3:  Descuento_Porcentaje 0.14815252 0.15863927 0.1882845
## 4: Precio_Final_Unitario 0.12519788 0.11451667 0.1380753
## 5:                Tiempo 0.03501466 0.04378579 0.0460251
# Graficar importancia de variables
xgb.plot.importance(importance_matrix = importancia, 
                   main = "Importancia de Variables - Producto 155001 (XGBoost)")

# Crear gráfico de valores observados vs predicciones
datos_grafico <- data.frame(
  Observado = datos_modelo$Venta,
  Predicho = predicciones_completas
)

ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
  labs(
    title = "Valores Observados vs Predicciones - Producto 155001 (XGBoost)",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

# Análisis del error
errores <- datos_grafico$Observado - datos_grafico$Predicho
hist(errores, 
     main = "Distribución de Errores - Producto 155001 (XGBoost)",
     xlab = "Error (Observado - Predicho)",
     col = "skyblue",
     breaks = 30)

# Gráfico del error vs predicción
ggplot(data.frame(Predicho = predicciones_completas, Error = errores), aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(
    title = "Error vs Predicción - Producto 155001 (XGBoost)",
    x = "Ventas Predichas",
    y = "Error (Observado - Predicho)"
  ) +
  theme_minimal()

# Guardar métricas de XGBoost para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "155001",  # Cambia este ID para cada producto
  Modelo = "XGBoost",
  MAPE = mape_completo,
  RMSE = rmse_completo

))

7.2 PRODUCTO 3929788

# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_3929788 %>%
  select(-Fecha)

# Paso 2: Dividir los datos en conjuntos de entrenamiento (80%) y prueba (20%)
set.seed(123)  # Para reproducibilidad
train_index <- createDataPartition(datos_modelo$Venta, p = 0.8, list = FALSE)
train_data <- datos_modelo[train_index, ]
test_data <- datos_modelo[-train_index, ]

# Preparar matrices para XGBoost
train_x <- as.matrix(train_data[, colnames(train_data) != "Venta"])
train_y <- train_data$Venta

test_x <- as.matrix(test_data[, colnames(test_data) != "Venta"])
test_y <- test_data$Venta

# Crear DMatrix para XGBoost
dtrain <- xgb.DMatrix(data = train_x, label = train_y)
dtest <- xgb.DMatrix(data = test_x, label = test_y)

# Paso 3: Definir la rejilla de hiperparámetros para Grid Search
param_grid <- expand.grid(
  eta = c(0.01, 0.05, 0.1, 0.3),          # Learning rate
  max_depth = c(3, 6, 9),                 # Profundidad máxima
  min_child_weight = c(1, 3, 5),          # Peso mínimo de nodo hijo
  subsample = c(0.7, 0.9),                # Proporción de observaciones
  colsample_bytree = c(0.7, 0.9),         # Proporción de variables
  gamma = c(0, 0.1, 0.3)                  # Regularización gamma
)

# Mostrar dimensiones de la rejilla
cat("Grid Search para XGBoost - Producto 3929788\n")
## Grid Search para XGBoost - Producto 3929788
cat("Número total de combinaciones de hiperparámetros:", nrow(param_grid), "\n\n")
## Número total de combinaciones de hiperparámetros: 432
# Para este ejemplo, limitar a 12 combinaciones para ahorrar tiempo
# En un escenario real, podrías evaluar todas o usar una estrategia más eficiente
set.seed(456)
if (nrow(param_grid) > 12) {
  selected_indices <- sample(1:nrow(param_grid), 12)
  param_grid <- param_grid[selected_indices, ]
  cat("Seleccionando 12 combinaciones aleatorias para evaluación.\n\n")
}
## Seleccionando 12 combinaciones aleatorias para evaluación.
# Paso 4: Implementar Grid Search
resultados <- data.frame()

cat("Iniciando Grid Search...\n")
## Iniciando Grid Search...
for (i in 1:nrow(param_grid)) {
  # Extraer parámetros de la combinación actual
  params <- list(
    objective = "reg:squarederror",      # Objetivo de regresión
    eval_metric = "rmse",               # Métrica de evaluación
    eta = param_grid$eta[i],
    max_depth = param_grid$max_depth[i],
    min_child_weight = param_grid$min_child_weight[i],
    subsample = param_grid$subsample[i],
    colsample_bytree = param_grid$colsample_bytree[i],
    gamma = param_grid$gamma[i]
  )
  
  cat("Evaluando combinación", i, "de", nrow(param_grid), ":\n")
  cat("  eta =", params$eta, 
      ", max_depth =", params$max_depth, 
      ", min_child_weight =", params$min_child_weight, 
      ", subsample =", params$subsample, 
      ", colsample_bytree =", params$colsample_bytree,
      ", gamma =", params$gamma, "\n")
  
  # Validación cruzada para encontrar el número óptimo de iteraciones
  cv_model <- xgb.cv(
    params = params,
    data = dtrain,
    nrounds = 200,                    # Máximo número de iteraciones
    nfold = 5,                        # 5-fold validación cruzada
    early_stopping_rounds = 20,       # Detener si no hay mejora en 20 rondas
    verbose = 0                       # Suprimir mensajes
  )
  
  # Extraer mejor iteración y su RMSE
  best_iteration <- cv_model$best_iteration
  best_rmse <- min(cv_model$evaluation_log$test_rmse_mean)
  
  cat("  Mejor iteración:", best_iteration, "\n")
  cat("  RMSE en validación cruzada:", best_rmse, "\n\n")
  
  # Guardar resultados
  resultado_actual <- data.frame(
    eta = params$eta,
    max_depth = params$max_depth,
    min_child_weight = params$min_child_weight,
    subsample = params$subsample,
    colsample_bytree = params$colsample_bytree,
    gamma = params$gamma,
    nrounds = best_iteration,
    rmse_cv = best_rmse
  )
  
  resultados <- rbind(resultados, resultado_actual)
}
## Evaluando combinación 1 de 12 :
##   eta = 0.01 , max_depth = 9 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.1 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 225622 
## 
## Evaluando combinación 2 de 12 :
##   eta = 0.1 , max_depth = 9 , min_child_weight = 3 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.3 
##   Mejor iteración: 43 
##   RMSE en validación cruzada: 170468.8 
## 
## Evaluando combinación 3 de 12 :
##   eta = 0.05 , max_depth = 3 , min_child_weight = 1 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0 
##   Mejor iteración: 89 
##   RMSE en validación cruzada: 183136.2 
## 
## Evaluando combinación 4 de 12 :
##   eta = 0.3 , max_depth = 9 , min_child_weight = 5 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.1 
##   Mejor iteración: 12 
##   RMSE en validación cruzada: 155705.3 
## 
## Evaluando combinación 5 de 12 :
##   eta = 0.1 , max_depth = 6 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.1 
##   Mejor iteración: 56 
##   RMSE en validación cruzada: 152445.7 
## 
## Evaluando combinación 6 de 12 :
##   eta = 0.01 , max_depth = 6 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.1 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 218792.1 
## 
## Evaluando combinación 7 de 12 :
##   eta = 0.05 , max_depth = 3 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.1 
##   Mejor iteración: 56 
##   RMSE en validación cruzada: 156288 
## 
## Evaluando combinación 8 de 12 :
##   eta = 0.1 , max_depth = 3 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.7 , gamma = 0.1 
##   Mejor iteración: 40 
##   RMSE en validación cruzada: 163582.8 
## 
## Evaluando combinación 9 de 12 :
##   eta = 0.05 , max_depth = 6 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.3 
##   Mejor iteración: 70 
##   RMSE en validación cruzada: 177167.3 
## 
## Evaluando combinación 10 de 12 :
##   eta = 0.1 , max_depth = 9 , min_child_weight = 1 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.3 
##   Mejor iteración: 46 
##   RMSE en validación cruzada: 148056.3 
## 
## Evaluando combinación 11 de 12 :
##   eta = 0.05 , max_depth = 6 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0 
##   Mejor iteración: 74 
##   RMSE en validación cruzada: 171304.3 
## 
## Evaluando combinación 12 de 12 :
##   eta = 0.1 , max_depth = 3 , min_child_weight = 3 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.3 
##   Mejor iteración: 30 
##   RMSE en validación cruzada: 153391.5
# Ordenar resultados por RMSE (de menor a mayor)
resultados <- resultados[order(resultados$rmse_cv), ]

# Paso 5: Mostrar resultados del Grid Search
cat("Resultados del Grid Search ordenados por RMSE:\n")
## Resultados del Grid Search ordenados por RMSE:
print(resultados)
##     eta max_depth min_child_weight subsample colsample_bytree gamma nrounds
## 10 0.10         9                1       0.9              0.7   0.3      46
## 5  0.10         6                5       0.9              0.9   0.1      56
## 12 0.10         3                3       0.9              0.7   0.3      30
## 4  0.30         9                5       0.7              0.9   0.1      12
## 7  0.05         3                5       0.9              0.7   0.1      56
## 8  0.10         3                3       0.7              0.7   0.1      40
## 2  0.10         9                3       0.9              0.9   0.3      43
## 11 0.05         6                3       0.7              0.9   0.0      74
## 9  0.05         6                3       0.7              0.9   0.3      70
## 3  0.05         3                1       0.9              0.7   0.0      89
## 6  0.01         6                5       0.9              0.9   0.1     200
## 1  0.01         9                3       0.7              0.9   0.1     200
##     rmse_cv
## 10 148056.3
## 5  152445.7
## 12 153391.5
## 4  155705.3
## 7  156288.0
## 8  163582.8
## 2  170468.8
## 11 171304.3
## 9  177167.3
## 3  183136.2
## 6  218792.1
## 1  225622.0
# Visualizar resultados del Grid Search
ggplot(resultados, aes(x = reorder(paste("Comb", 1:nrow(resultados)), rmse_cv), y = rmse_cv)) +
  geom_bar(stat = "identity", fill = "steelblue") +
  labs(
    title = "Resultados del Grid Search - Producto 3929788",
    x = "Combinación de Hiperparámetros",
    y = "RMSE en Validación Cruzada"
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

# Paso 6: Seleccionar los mejores hiperparámetros
mejores_params <- list(
  objective = "reg:squarederror",
  eval_metric = "rmse",
  eta = resultados$eta[1],
  max_depth = resultados$max_depth[1],
  min_child_weight = resultados$min_child_weight[1],
  subsample = resultados$subsample[1],
  colsample_bytree = resultados$colsample_bytree[1],
  gamma = resultados$gamma[1]
)

mejor_nrounds <- resultados$nrounds[1]

cat("\nMejores hiperparámetros encontrados:\n")
## 
## Mejores hiperparámetros encontrados:
print(mejores_params)
## $objective
## [1] "reg:squarederror"
## 
## $eval_metric
## [1] "rmse"
## 
## $eta
## [1] 0.1
## 
## $max_depth
## [1] 9
## 
## $min_child_weight
## [1] 1
## 
## $subsample
## [1] 0.9
## 
## $colsample_bytree
## [1] 0.7
## 
## $gamma
## [1] 0.3
cat("Número óptimo de rondas:", mejor_nrounds, "\n\n")
## Número óptimo de rondas: 46
# Paso 7: Entrenar el modelo final con los mejores hiperparámetros
cat("Entrenando modelo final con los mejores hiperparámetros...\n")
## Entrenando modelo final con los mejores hiperparámetros...
modelo_final <- xgb.train(
  params = mejores_params,
  data = dtrain,
  nrounds = mejor_nrounds,
  watchlist = list(train = dtrain, test = dtest),
  verbose = 0
)
# Generar predicciones sobre TODO el conjunto de datos
x_completo <- as.matrix(datos_modelo[, colnames(datos_modelo) != "Venta"])
dcompleto <- xgb.DMatrix(data = x_completo)
predicciones_completo <- predict(modelo_final, newdata = dcompleto)


ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
  labs(
    title = "Valores Observados vs Predicciones - Producto 3929788 (XGBoost)",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

# Gráfico 2: Análisis de residuos
errores <- datos_modelo$Venta - predicciones_completo

# Histograma de errores
hist(errores, 
     main = "Distribución de Errores - Producto 3929788 (XGBoost)",
     xlab = "Error (Observado - Predicho)",
     col = "skyblue",
     breaks = 30)

# Gráfico 3: Errores vs Predicciones
ggplot(data.frame(Predicho = predicciones_completo, Error = errores), aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(
    title = "Error vs Predicción - Producto 3929788 (XGBoost)",
    x = "Ventas Predichas",
    y = "Error (Observado - Predicho)"
  ) +
  theme_minimal()

# Guardar métricas de XGBoost para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3929788",  # Cambia este ID para cada producto
  Modelo = "XGBoost",
  MAPE = mape_completo,
  RMSE = rmse_completo
))

7.3 PRODUCTO 3904152

# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_3904152 %>%
  select(-Fecha)

# Paso 2: Dividir los datos en conjuntos de entrenamiento (80%) y prueba (20%)
set.seed(123)  # Para reproducibilidad
train_index <- createDataPartition(datos_modelo$Venta, p = 0.8, list = FALSE)
train_data <- datos_modelo[train_index, ]
test_data <- datos_modelo[-train_index, ]

# Preparar matrices para XGBoost
train_x <- as.matrix(train_data[, colnames(train_data) != "Venta"])
train_y <- train_data$Venta

test_x <- as.matrix(test_data[, colnames(test_data) != "Venta"])
test_y <- test_data$Venta

# Crear DMatrix para XGBoost
dtrain <- xgb.DMatrix(data = train_x, label = train_y)
dtest <- xgb.DMatrix(data = test_x, label = test_y)

# Paso 3: Definir la rejilla de hiperparámetros para Grid Search
param_grid <- expand.grid(
  eta = c(0.01, 0.05, 0.1, 0.3),          # Learning rate
  max_depth = c(3, 6, 9),                 # Profundidad máxima
  min_child_weight = c(1, 3, 5),          # Peso mínimo de nodo hijo
  subsample = c(0.7, 0.9),                # Proporción de observaciones
  colsample_bytree = c(0.7, 0.9),         # Proporción de variables
  gamma = c(0, 0.1, 0.3)                  # Regularización gamma
)

# Mostrar dimensiones de la rejilla
cat("Grid Search para XGBoost - Producto 3904152\n")
## Grid Search para XGBoost - Producto 3904152
cat("Número total de combinaciones de hiperparámetros:", nrow(param_grid), "\n\n")
## Número total de combinaciones de hiperparámetros: 432
# Para este ejemplo, limitar a 12 combinaciones para ahorrar tiempo
# En un escenario real, podrías evaluar todas o usar una estrategia más eficiente
set.seed(456)
if (nrow(param_grid) > 12) {
  selected_indices <- sample(1:nrow(param_grid), 12)
  param_grid <- param_grid[selected_indices, ]
  cat("Seleccionando 12 combinaciones aleatorias para evaluación.\n\n")
}
## Seleccionando 12 combinaciones aleatorias para evaluación.
# Paso 4: Implementar Grid Search
resultados <- data.frame()

cat("Iniciando Grid Search...\n")
## Iniciando Grid Search...
for (i in 1:nrow(param_grid)) {
  # Extraer parámetros de la combinación actual
  params <- list(
    objective = "reg:squarederror",      # Objetivo de regresión
    eval_metric = "rmse",               # Métrica de evaluación
    eta = param_grid$eta[i],
    max_depth = param_grid$max_depth[i],
    min_child_weight = param_grid$min_child_weight[i],
    subsample = param_grid$subsample[i],
    colsample_bytree = param_grid$colsample_bytree[i],
    gamma = param_grid$gamma[i]
  )
  
  cat("Evaluando combinación", i, "de", nrow(param_grid), ":\n")
  cat("  eta =", params$eta, 
      ", max_depth =", params$max_depth, 
      ", min_child_weight =", params$min_child_weight, 
      ", subsample =", params$subsample, 
      ", colsample_bytree =", params$colsample_bytree,
      ", gamma =", params$gamma, "\n")
  
  # Validación cruzada para encontrar el número óptimo de iteraciones
  cv_model <- xgb.cv(
    params = params,
    data = dtrain,
    nrounds = 200,                    # Máximo número de iteraciones
    nfold = 5,                        # 5-fold validación cruzada
    early_stopping_rounds = 20,       # Detener si no hay mejora en 20 rondas
    verbose = 0                       # Suprimir mensajes
  )
  
  # Extraer mejor iteración y su RMSE
  best_iteration <- cv_model$best_iteration
  best_rmse <- min(cv_model$evaluation_log$test_rmse_mean)
  
  cat("  Mejor iteración:", best_iteration, "\n")
  cat("  RMSE en validación cruzada:", best_rmse, "\n\n")
  
  # Guardar resultados
  resultado_actual <- data.frame(
    eta = params$eta,
    max_depth = params$max_depth,
    min_child_weight = params$min_child_weight,
    subsample = params$subsample,
    colsample_bytree = params$colsample_bytree,
    gamma = params$gamma,
    nrounds = best_iteration,
    rmse_cv = best_rmse
  )
  
  resultados <- rbind(resultados, resultado_actual)
}
## Evaluando combinación 1 de 12 :
##   eta = 0.01 , max_depth = 9 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.1 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 207996.5 
## 
## Evaluando combinación 2 de 12 :
##   eta = 0.1 , max_depth = 9 , min_child_weight = 3 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.3 
##   Mejor iteración: 27 
##   RMSE en validación cruzada: 163761.3 
## 
## Evaluando combinación 3 de 12 :
##   eta = 0.05 , max_depth = 3 , min_child_weight = 1 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0 
##   Mejor iteración: 70 
##   RMSE en validación cruzada: 209373.8 
## 
## Evaluando combinación 4 de 12 :
##   eta = 0.3 , max_depth = 9 , min_child_weight = 5 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.1 
##   Mejor iteración: 15 
##   RMSE en validación cruzada: 154502.3 
## 
## Evaluando combinación 5 de 12 :
##   eta = 0.1 , max_depth = 6 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.1 
##   Mejor iteración: 44 
##   RMSE en validación cruzada: 161616.2 
## 
## Evaluando combinación 6 de 12 :
##   eta = 0.01 , max_depth = 6 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.1 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 205856.1 
## 
## Evaluando combinación 7 de 12 :
##   eta = 0.05 , max_depth = 3 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.1 
##   Mejor iteración: 92 
##   RMSE en validación cruzada: 161438.6 
## 
## Evaluando combinación 8 de 12 :
##   eta = 0.1 , max_depth = 3 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.7 , gamma = 0.1 
##   Mejor iteración: 46 
##   RMSE en validación cruzada: 157412 
## 
## Evaluando combinación 9 de 12 :
##   eta = 0.05 , max_depth = 6 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.3 
##   Mejor iteración: 75 
##   RMSE en validación cruzada: 146072.5 
## 
## Evaluando combinación 10 de 12 :
##   eta = 0.1 , max_depth = 9 , min_child_weight = 1 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.3 
##   Mejor iteración: 44 
##   RMSE en validación cruzada: 166445.7 
## 
## Evaluando combinación 11 de 12 :
##   eta = 0.05 , max_depth = 6 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0 
##   Mejor iteración: 61 
##   RMSE en validación cruzada: 157089.5 
## 
## Evaluando combinación 12 de 12 :
##   eta = 0.1 , max_depth = 3 , min_child_weight = 3 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.3 
##   Mejor iteración: 30 
##   RMSE en validación cruzada: 207391.2
# Ordenar resultados por RMSE (de menor a mayor)
resultados <- resultados[order(resultados$rmse_cv), ]

# Paso 5: Mostrar resultados del Grid Search
cat("Resultados del Grid Search ordenados por RMSE:\n")
## Resultados del Grid Search ordenados por RMSE:
print(resultados)
##     eta max_depth min_child_weight subsample colsample_bytree gamma nrounds
## 9  0.05         6                3       0.7              0.9   0.3      75
## 4  0.30         9                5       0.7              0.9   0.1      15
## 11 0.05         6                3       0.7              0.9   0.0      61
## 8  0.10         3                3       0.7              0.7   0.1      46
## 7  0.05         3                5       0.9              0.7   0.1      92
## 5  0.10         6                5       0.9              0.9   0.1      44
## 2  0.10         9                3       0.9              0.9   0.3      27
## 10 0.10         9                1       0.9              0.7   0.3      44
## 6  0.01         6                5       0.9              0.9   0.1     200
## 12 0.10         3                3       0.9              0.7   0.3      30
## 1  0.01         9                3       0.7              0.9   0.1     200
## 3  0.05         3                1       0.9              0.7   0.0      70
##     rmse_cv
## 9  146072.5
## 4  154502.3
## 11 157089.5
## 8  157412.0
## 7  161438.6
## 5  161616.2
## 2  163761.3
## 10 166445.7
## 6  205856.1
## 12 207391.2
## 1  207996.5
## 3  209373.8
# Visualizar resultados del Grid Search
ggplot(resultados, aes(x = reorder(paste("Comb", 1:nrow(resultados)), rmse_cv), y = rmse_cv)) +
  geom_bar(stat = "identity", fill = "steelblue") +
  labs(
    title = "Resultados del Grid Search - Producto 3904152",
    x = "Combinación de Hiperparámetros",
    y = "RMSE en Validación Cruzada"
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

# Paso 6: Seleccionar los mejores hiperparámetros
mejores_params <- list(
  objective = "reg:squarederror",
  eval_metric = "rmse",
  eta = resultados$eta[1],
  max_depth = resultados$max_depth[1],
  min_child_weight = resultados$min_child_weight[1],
  subsample = resultados$subsample[1],
  colsample_bytree = resultados$colsample_bytree[1],
  gamma = resultados$gamma[1]
)

mejor_nrounds <- resultados$nrounds[1]

cat("\nMejores hiperparámetros encontrados:\n")
## 
## Mejores hiperparámetros encontrados:
print(mejores_params)
## $objective
## [1] "reg:squarederror"
## 
## $eval_metric
## [1] "rmse"
## 
## $eta
## [1] 0.05
## 
## $max_depth
## [1] 6
## 
## $min_child_weight
## [1] 3
## 
## $subsample
## [1] 0.7
## 
## $colsample_bytree
## [1] 0.9
## 
## $gamma
## [1] 0.3
cat("Número óptimo de rondas:", mejor_nrounds, "\n\n")
## Número óptimo de rondas: 75
# Paso 7: Entrenar el modelo final con los mejores hiperparámetros
cat("Entrenando modelo final con los mejores hiperparámetros...\n")
## Entrenando modelo final con los mejores hiperparámetros...
modelo_final <- xgb.train(
  params = mejores_params,
  data = dtrain,
  nrounds = mejor_nrounds,
  watchlist = list(train = dtrain, test = dtest),
  verbose = 0
)

modelo_xgb_3904152 <- modelo_final


# Paso 8: Evaluar el modelo
# Predicciones en conjunto de prueba
predicciones_test <- predict(modelo_final, dtest)

# Calcular métricas
# MAPE
mape_test <- mean(abs((test_y - predicciones_test) / pmax(test_y, 0.01))) * 100

# RMSE
rmse_test <- sqrt(mean((test_y - predicciones_test)^2))


# Mostrar métricas en conjunto de prueba
cat("\nMétricas en conjunto de prueba:\n")
## 
## Métricas en conjunto de prueba:
cat("MAPE del modelo XGBoost:", mape_test, "\n")
## MAPE del modelo XGBoost: 12.5281
cat("RMSE del modelo XGBoost:", rmse_test, "\n\n")
## RMSE del modelo XGBoost: 141272
# Paso 9: Predicciones en el conjunto completo
# Hacer predicciones en todo el conjunto de datos para comparación con otros modelos
x_completo <- as.matrix(datos_modelo[, colnames(datos_modelo) != "Venta"])
predicciones_completo <- predict(modelo_final, x_completo)

# Calcular métricas en conjunto completo
mape_completo <- mean(abs((datos_modelo$Venta - predicciones_completo) / 
                        pmax(datos_modelo$Venta, 0.01))) * 100

mse_completo <- mean((datos_modelo$Venta - predicciones_completo)^2)

# Mostrar métricas en conjunto completo
cat("Métricas en conjunto completo:\n")
## Métricas en conjunto completo:
cat("MAPE del modelo XGBoost:", mape_completo, "\n")
## MAPE del modelo XGBoost: 9.488338
cat("RMSE del modelo XGBoost:", rmse_completo, "\n\n")
## RMSE del modelo XGBoost: 139180.7
# Paso 10: Análisis de importancia de variables
# Calcular importancia de variables
importancia <- xgb.importance(
  feature_names = colnames(datos_modelo)[colnames(datos_modelo) != "Venta"],
  model = modelo_final
)

# Mostrar importancia de variables
cat("Importancia de variables:\n")
## Importancia de variables:
print(importancia)
##                  Feature       Gain      Cover Frequency
##                   <char>      <num>      <num>     <num>
## 1:           Costo_Venta 0.38684609 0.30354132 0.2413793
## 2:                  Cant 0.25885937 0.30607083 0.3448276
## 3: Precio_Final_Unitario 0.21772106 0.22765599 0.2413793
## 4:  Descuento_Porcentaje 0.08054190 0.08178752 0.0862069
## 5:                Tiempo 0.05603158 0.08094435 0.0862069
# Graficar importancia de variables
xgb.plot.importance(importance_matrix = importancia, 
                   main = "Importancia de Variables - Producto 3904152 (XGBoost)")

# Paso 11: Visualizaciones para evaluación
# Gráfico 1: Valores observados vs predichos
datos_grafico <- data.frame(
  Observado = datos_modelo$Venta,
  Predicho = predicciones_completo
)

ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
  labs(
    title = "Valores Observados vs Predicciones - Producto 3904152 (XGBoost)",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

# Gráfico 2: Análisis de residuos
errores <- datos_modelo$Venta - predicciones_completo

# Histograma de errores
hist(errores, 
     main = "Distribución de Errores - Producto 3904152 (XGBoost)",
     xlab = "Error (Observado - Predicho)",
     col = "skyblue",
     breaks = 30)

# Gráfico 3: Errores vs Predicciones
ggplot(data.frame(Predicho = predicciones_completo, Error = errores), aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(
    title = "Error vs Predicción - Producto 3904152 (XGBoost)",
    x = "Ventas Predichas",
    y = "Error (Observado - Predicho)"
  ) +
  theme_minimal()

# Guardar métricas de XGBoost para producto 
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3904152",  # Cambia este ID para cada producto
  Modelo = "XGBoost",
  MAPE = mape_completo,
  RMSE = rmse_completo
))

7.4 PRODUCTO 155002

# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_155002 %>%
  select(-Fecha)

# Paso 2: Dividir los datos en conjuntos de entrenamiento (80%) y prueba (20%)
set.seed(123)  # Para reproducibilidad
train_index <- createDataPartition(datos_modelo$Venta, p = 0.8, list = FALSE)
train_data <- datos_modelo[train_index, ]
test_data <- datos_modelo[-train_index, ]

# Preparar matrices para XGBoost
train_x <- as.matrix(train_data[, colnames(train_data) != "Venta"])
train_y <- train_data$Venta

test_x <- as.matrix(test_data[, colnames(test_data) != "Venta"])
test_y <- test_data$Venta

# Crear DMatrix para XGBoost
dtrain <- xgb.DMatrix(data = train_x, label = train_y)
dtest <- xgb.DMatrix(data = test_x, label = test_y)

# Paso 3: Definir la rejilla de hiperparámetros para Grid Search
param_grid <- expand.grid(
  eta = c(0.01, 0.05, 0.1, 0.3),          # Learning rate
  max_depth = c(3, 6, 9),                 # Profundidad máxima
  min_child_weight = c(1, 3, 5),          # Peso mínimo de nodo hijo
  subsample = c(0.7, 0.9),                # Proporción de observaciones
  colsample_bytree = c(0.7, 0.9),         # Proporción de variables
  gamma = c(0, 0.1, 0.3)                  # Regularización gamma
)

# Mostrar dimensiones de la rejilla
cat("Grid Search para XGBoost - Producto 155002\n")
## Grid Search para XGBoost - Producto 155002
cat("Número total de combinaciones de hiperparámetros:", nrow(param_grid), "\n\n")
## Número total de combinaciones de hiperparámetros: 432
# Para este ejemplo, limitar a 12 combinaciones para ahorrar tiempo
# En un escenario real, podrías evaluar todas o usar una estrategia más eficiente
set.seed(456)
if (nrow(param_grid) > 12) {
  selected_indices <- sample(1:nrow(param_grid), 12)
  param_grid <- param_grid[selected_indices, ]
  cat("Seleccionando 12 combinaciones aleatorias para evaluación.\n\n")
}
## Seleccionando 12 combinaciones aleatorias para evaluación.
# Paso 4: Implementar Grid Search
resultados <- data.frame()

cat("Iniciando Grid Search...\n")
## Iniciando Grid Search...
for (i in 1:nrow(param_grid)) {
  # Extraer parámetros de la combinación actual
  params <- list(
    objective = "reg:squarederror",      # Objetivo de regresión
    eval_metric = "rmse",               # Métrica de evaluación
    eta = param_grid$eta[i],
    max_depth = param_grid$max_depth[i],
    min_child_weight = param_grid$min_child_weight[i],
    subsample = param_grid$subsample[i],
    colsample_bytree = param_grid$colsample_bytree[i],
    gamma = param_grid$gamma[i]
  )
  
  cat("Evaluando combinación", i, "de", nrow(param_grid), ":\n")
  cat("  eta =", params$eta, 
      ", max_depth =", params$max_depth, 
      ", min_child_weight =", params$min_child_weight, 
      ", subsample =", params$subsample, 
      ", colsample_bytree =", params$colsample_bytree,
      ", gamma =", params$gamma, "\n")
  
  # Validación cruzada para encontrar el número óptimo de iteraciones
  cv_model <- xgb.cv(
    params = params,
    data = dtrain,
    nrounds = 200,                    # Máximo número de iteraciones
    nfold = 5,                        # 5-fold validación cruzada
    early_stopping_rounds = 20,       # Detener si no hay mejora en 20 rondas
    verbose = 0                       # Suprimir mensajes
  )
  
  # Extraer mejor iteración y su RMSE
  best_iteration <- cv_model$best_iteration
  best_rmse <- min(cv_model$evaluation_log$test_rmse_mean)
  
  cat("  Mejor iteración:", best_iteration, "\n")
  cat("  RMSE en validación cruzada:", best_rmse, "\n\n")
  
  # Guardar resultados
  resultado_actual <- data.frame(
    eta = params$eta,
    max_depth = params$max_depth,
    min_child_weight = params$min_child_weight,
    subsample = params$subsample,
    colsample_bytree = params$colsample_bytree,
    gamma = params$gamma,
    nrounds = best_iteration,
    rmse_cv = best_rmse
  )
  
  resultados <- rbind(resultados, resultado_actual)
}
## Evaluando combinación 1 de 12 :
##   eta = 0.01 , max_depth = 9 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.1 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 272170.7 
## 
## Evaluando combinación 2 de 12 :
##   eta = 0.1 , max_depth = 9 , min_child_weight = 3 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.3 
##   Mejor iteración: 38 
##   RMSE en validación cruzada: 208584 
## 
## Evaluando combinación 3 de 12 :
##   eta = 0.05 , max_depth = 3 , min_child_weight = 1 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0 
##   Mejor iteración: 172 
##   RMSE en validación cruzada: 212560.1 
## 
## Evaluando combinación 4 de 12 :
##   eta = 0.3 , max_depth = 9 , min_child_weight = 5 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.1 
##   Mejor iteración: 15 
##   RMSE en validación cruzada: 206354.4 
## 
## Evaluando combinación 5 de 12 :
##   eta = 0.1 , max_depth = 6 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.1 
##   Mejor iteración: 55 
##   RMSE en validación cruzada: 241567.8 
## 
## Evaluando combinación 6 de 12 :
##   eta = 0.01 , max_depth = 6 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.1 
##   Mejor iteración: 200 
##   RMSE en validación cruzada: 260376.4 
## 
## Evaluando combinación 7 de 12 :
##   eta = 0.05 , max_depth = 3 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.1 
##   Mejor iteración: 60 
##   RMSE en validación cruzada: 227808.4 
## 
## Evaluando combinación 8 de 12 :
##   eta = 0.1 , max_depth = 3 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.7 , gamma = 0.1 
##   Mejor iteración: 61 
##   RMSE en validación cruzada: 212881.5 
## 
## Evaluando combinación 9 de 12 :
##   eta = 0.05 , max_depth = 6 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.3 
##   Mejor iteración: 125 
##   RMSE en validación cruzada: 224317.2 
## 
## Evaluando combinación 10 de 12 :
##   eta = 0.1 , max_depth = 9 , min_child_weight = 1 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.3 
##   Mejor iteración: 35 
##   RMSE en validación cruzada: 204772.5 
## 
## Evaluando combinación 11 de 12 :
##   eta = 0.05 , max_depth = 6 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0 
##   Mejor iteración: 122 
##   RMSE en validación cruzada: 239478.1 
## 
## Evaluando combinación 12 de 12 :
##   eta = 0.1 , max_depth = 3 , min_child_weight = 3 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.3 
##   Mejor iteración: 59 
##   RMSE en validación cruzada: 162366.8
# Ordenar resultados por RMSE (de menor a mayor)
resultados <- resultados[order(resultados$rmse_cv), ]

# Paso 5: Mostrar resultados del Grid Search
cat("Resultados del Grid Search ordenados por RMSE:\n")
## Resultados del Grid Search ordenados por RMSE:
print(resultados)
##     eta max_depth min_child_weight subsample colsample_bytree gamma nrounds
## 12 0.10         3                3       0.9              0.7   0.3      59
## 10 0.10         9                1       0.9              0.7   0.3      35
## 4  0.30         9                5       0.7              0.9   0.1      15
## 2  0.10         9                3       0.9              0.9   0.3      38
## 3  0.05         3                1       0.9              0.7   0.0     172
## 8  0.10         3                3       0.7              0.7   0.1      61
## 9  0.05         6                3       0.7              0.9   0.3     125
## 7  0.05         3                5       0.9              0.7   0.1      60
## 11 0.05         6                3       0.7              0.9   0.0     122
## 5  0.10         6                5       0.9              0.9   0.1      55
## 6  0.01         6                5       0.9              0.9   0.1     200
## 1  0.01         9                3       0.7              0.9   0.1     200
##     rmse_cv
## 12 162366.8
## 10 204772.5
## 4  206354.4
## 2  208584.0
## 3  212560.1
## 8  212881.5
## 9  224317.2
## 7  227808.4
## 11 239478.1
## 5  241567.8
## 6  260376.4
## 1  272170.7
# Visualizar resultados del Grid Search
ggplot(resultados, aes(x = reorder(paste("Comb", 1:nrow(resultados)), rmse_cv), y = rmse_cv)) +
  geom_bar(stat = "identity", fill = "steelblue") +
  labs(
    title = "Resultados del Grid Search - Producto 155002",
    x = "Combinación de Hiperparámetros",
    y = "RMSE en Validación Cruzada"
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

# Paso 6: Seleccionar los mejores hiperparámetros
mejores_params <- list(
  objective = "reg:squarederror",
  eval_metric = "rmse",
  eta = resultados$eta[1],
  max_depth = resultados$max_depth[1],
  min_child_weight = resultados$min_child_weight[1],
  subsample = resultados$subsample[1],
  colsample_bytree = resultados$colsample_bytree[1],
  gamma = resultados$gamma[1]
)

mejor_nrounds <- resultados$nrounds[1]

cat("\nMejores hiperparámetros encontrados:\n")
## 
## Mejores hiperparámetros encontrados:
print(mejores_params)
## $objective
## [1] "reg:squarederror"
## 
## $eval_metric
## [1] "rmse"
## 
## $eta
## [1] 0.1
## 
## $max_depth
## [1] 3
## 
## $min_child_weight
## [1] 3
## 
## $subsample
## [1] 0.9
## 
## $colsample_bytree
## [1] 0.7
## 
## $gamma
## [1] 0.3
cat("Número óptimo de rondas:", mejor_nrounds, "\n\n")
## Número óptimo de rondas: 59
# Paso 7: Entrenar el modelo final con los mejores hiperparámetros
cat("Entrenando modelo final con los mejores hiperparámetros...\n")
## Entrenando modelo final con los mejores hiperparámetros...
modelo_final <- xgb.train(
  params = mejores_params,
  data = dtrain,
  nrounds = mejor_nrounds,
  watchlist = list(train = dtrain, test = dtest),
  verbose = 0
)

modelo_xgb_155002 <- modelo_final

# Paso 8: Evaluar el modelo
# Predicciones en conjunto de prueba
predicciones_test <- predict(modelo_final, dtest)

# Calcular métricas
# MAPE
mape_test <- mean(abs((test_y - predicciones_test) / pmax(test_y, 0.01))) * 100

# RMSE
rmse_test <- sqrt(mean((test_y - predicciones_test)^2))



# Mostrar métricas en conjunto de prueba
cat("\nMétricas en conjunto de prueba:\n")
## 
## Métricas en conjunto de prueba:
cat("MAPE del modelo XGBoost:", mape_test, "\n")
## MAPE del modelo XGBoost: 47.82396
cat("RMSE del modelo XGBoost:", rmse_test, "\n\n")
## RMSE del modelo XGBoost: 296599.4
# Paso 9: Predicciones en el conjunto completo
# Hacer predicciones en todo el conjunto de datos para comparación con otros modelos
x_completo <- as.matrix(datos_modelo[, colnames(datos_modelo) != "Venta"])
predicciones_completo <- predict(modelo_final, x_completo)

# Calcular métricas en conjunto completo
mape_completo <- mean(abs((datos_modelo$Venta - predicciones_completo) / 
                        pmax(datos_modelo$Venta, 0.01))) * 100

rmse_completo <- sqrt(mean((datos_modelo$Venta - predicciones_completo)^2))


# Mostrar métricas en conjunto completo
cat("Métricas en conjunto completo:\n")
## Métricas en conjunto completo:
cat("MAPE del modelo XGBoost:", mape_completo, "\n")
## MAPE del modelo XGBoost: 13.16291
cat("RMSE del modelo XGBoost:", rmse_completo, "\n\n")
## RMSE del modelo XGBoost: 137986.6
# Paso 10: Análisis de importancia de variables
# Calcular importancia de variables
importancia <- xgb.importance(
  feature_names = colnames(datos_modelo)[colnames(datos_modelo) != "Venta"],
  model = modelo_final
)

# Mostrar importancia de variables
cat("Importancia de variables:\n")
## Importancia de variables:
print(importancia)
##                  Feature       Gain      Cover Frequency
##                   <char>      <num>      <num>     <num>
## 1: Precio_Final_Unitario 0.29669048 0.22390397 0.2258065
## 2:  Descuento_Porcentaje 0.28146165 0.28862213 0.2967742
## 3:           Costo_Venta 0.22043409 0.26878914 0.2580645
## 4:                  Cant 0.14054751 0.17014614 0.1677419
## 5:                Tiempo 0.06086627 0.04853862 0.0516129
# Graficar importancia de variables
xgb.plot.importance(importance_matrix = importancia, 
                   main = "Importancia de Variables - Producto 155002 (XGBoost)")

# Paso 11: Visualizaciones para evaluación
# Gráfico 1: Valores observados vs predichos
datos_grafico <- data.frame(
  Observado = datos_modelo$Venta,
  Predicho = predicciones_completo
)

ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
  labs(
    title = "Valores Observados vs Predicciones - Producto 155002 (XGBoost)",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

# Gráfico 2: Análisis de residuos
errores <- datos_modelo$Venta - predicciones_completo

# Histograma de errores
hist(errores, 
     main = "Distribución de Errores - Producto 155002 (XGBoost)",
     xlab = "Error (Observado - Predicho)",
     col = "skyblue",
     breaks = 30)

# Gráfico 3: Errores vs Predicciones
ggplot(data.frame(Predicho = predicciones_completo, Error = errores), aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(
    title = "Error vs Predicción - Producto 155002 (XGBoost)",
    x = "Ventas Predichas",
    y = "Error (Observado - Predicho)"
  ) +
  theme_minimal()

# Guardar métricas de XGBoost para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "155002",  # Cambia este ID para cada producto
  Modelo = "XGBoost",
  MAPE = mape_completo,
  RMSE = rmse_completo
))

7.5 PRODUCTO 3678055

# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_3678055 %>%
  select(-Fecha)

# Paso 2: Dividir los datos en conjuntos de entrenamiento (80%) y prueba (20%)
set.seed(123)  # Para reproducibilidad
train_index <- createDataPartition(datos_modelo$Venta, p = 0.8, list = FALSE)
train_data <- datos_modelo[train_index, ]
test_data <- datos_modelo[-train_index, ]

# Preparar matrices para XGBoost
train_x <- as.matrix(train_data[, colnames(train_data) != "Venta"])
train_y <- train_data$Venta

test_x <- as.matrix(test_data[, colnames(test_data) != "Venta"])
test_y <- test_data$Venta

# Crear DMatrix para XGBoost
dtrain <- xgb.DMatrix(data = train_x, label = train_y)
dtest <- xgb.DMatrix(data = test_x, label = test_y)

# Paso 3: Definir la rejilla de hiperparámetros
param_grid <- expand.grid(
  eta = c(0.01, 0.05, 0.1, 0.3),
  max_depth = c(3, 6, 9),
  min_child_weight = c(1, 3, 5),
  subsample = c(0.7, 0.9),
  colsample_bytree = c(0.7, 0.9),
  gamma = c(0, 0.1, 0.3)
)

cat("Grid Search para XGBoost - Producto 3678055\n")
## Grid Search para XGBoost - Producto 3678055
cat("Número total de combinaciones de hiperparámetros:", nrow(param_grid), "\n\n")
## Número total de combinaciones de hiperparámetros: 432
# Selección aleatoria de 12 combinaciones (si se desea limitar)
set.seed(456)
if (nrow(param_grid) > 12) {
  param_grid <- param_grid[sample(1:nrow(param_grid), 12), ]
  cat("Seleccionando 12 combinaciones aleatorias para evaluación.\n\n")
}
## Seleccionando 12 combinaciones aleatorias para evaluación.
# Paso 4: Implementar Grid Search
resultados <- data.frame()
cat("Iniciando Grid Search...\n")
## Iniciando Grid Search...
for (i in 1:nrow(param_grid)) {
  params <- list(
    objective = "reg:squarederror",
    eval_metric = "rmse",
    eta = param_grid$eta[i],
    max_depth = param_grid$max_depth[i],
    min_child_weight = param_grid$min_child_weight[i],
    subsample = param_grid$subsample[i],
    colsample_bytree = param_grid$colsample_bytree[i],
    gamma = param_grid$gamma[i]
  )
  
  cat("Evaluando combinación", i, "de", nrow(param_grid), "...\n")
  
  cv_model <- xgb.cv(
    params = params,
    data = dtrain,
    nrounds = 200,
    nfold = 5,
    early_stopping_rounds = 20,
    verbose = 0
  )
  
  best_iteration <- cv_model$best_iteration
  best_rmse <- min(cv_model$evaluation_log$test_rmse_mean)
  
  resultado_actual <- data.frame(
    eta = params$eta,
    max_depth = params$max_depth,
    min_child_weight = params$min_child_weight,
    subsample = params$subsample,
    colsample_bytree = params$colsample_bytree,
    gamma = params$gamma,
    nrounds = best_iteration,
    rmse_cv = best_rmse
  )
  
  resultados <- rbind(resultados, resultado_actual)
}
## Evaluando combinación 1 de 12 ...
## Evaluando combinación 2 de 12 ...
## Evaluando combinación 3 de 12 ...
## Evaluando combinación 4 de 12 ...
## Evaluando combinación 5 de 12 ...
## Evaluando combinación 6 de 12 ...
## Evaluando combinación 7 de 12 ...
## Evaluando combinación 8 de 12 ...
## Evaluando combinación 9 de 12 ...
## Evaluando combinación 10 de 12 ...
## Evaluando combinación 11 de 12 ...
## Evaluando combinación 12 de 12 ...
resultados <- resultados[order(resultados$rmse_cv), ]

# Paso 5: Mostrar resultados
print(resultados)
##     eta max_depth min_child_weight subsample colsample_bytree gamma nrounds
## 9  0.05         6                3       0.7              0.9   0.3      85
## 4  0.30         9                5       0.7              0.9   0.1      15
## 8  0.10         3                3       0.7              0.7   0.1      25
## 11 0.05         6                3       0.7              0.9   0.0      52
## 5  0.10         6                5       0.9              0.9   0.1      31
## 2  0.10         9                3       0.9              0.9   0.3      43
## 7  0.05         3                5       0.9              0.7   0.1      60
## 1  0.01         9                3       0.7              0.9   0.1     200
## 12 0.10         3                3       0.9              0.7   0.3      32
## 6  0.01         6                5       0.9              0.9   0.1     200
## 3  0.05         3                1       0.9              0.7   0.0      53
## 10 0.10         9                1       0.9              0.7   0.3      31
##     rmse_cv
## 9  171050.4
## 4  182858.5
## 8  187928.5
## 11 189630.4
## 5  189900.6
## 2  193173.9
## 7  199734.3
## 1  206000.4
## 12 210177.3
## 6  220166.8
## 3  231847.6
## 10 243523.0
# Visualización
ggplot(resultados, aes(x = reorder(paste("Comb", 1:nrow(resultados)), rmse_cv), y = rmse_cv)) +
  geom_bar(stat = "identity", fill = "steelblue") +
  labs(
    title = "Resultados del Grid Search - Producto 3678055",
    x = "Combinación de Hiperparámetros",
    y = "RMSE"
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

# Paso 6: Seleccionar los mejores hiperparámetros
mejores_params <- list(
  objective = "reg:squarederror",
  eval_metric = "rmse",
  eta = resultados$eta[1],
  max_depth = resultados$max_depth[1],
  min_child_weight = resultados$min_child_weight[1],
  subsample = resultados$subsample[1],
  colsample_bytree = resultados$colsample_bytree[1],
  gamma = resultados$gamma[1]
)

mejor_nrounds <- resultados$nrounds[1]

# Paso 7: Entrenar modelo final
modelo_final <- xgb.train(
  params = mejores_params,
  data = dtrain,
  nrounds = mejor_nrounds,
  watchlist = list(train = dtrain, test = dtest),
  verbose = 0
)

modelo_xgb_3678055 <- modelo_final


# Paso 8: Evaluar modelo
predicciones_test <- predict(modelo_final, dtest)
mape_test <- mean(abs((test_y - predicciones_test) / pmax(test_y, 0.01))) * 100
rmse_test <- sqrt(mean((test_y - predicciones_test)^2))



# Paso 9: Predicción en todo el conjunto
x_completo <- as.matrix(datos_modelo[, colnames(datos_modelo) != "Venta"])
predicciones_completo <- predict(modelo_final, x_completo)

mape_completo <- mean(abs((datos_modelo$Venta - predicciones_completo) / pmax(datos_modelo$Venta, 0.01))) * 100
rmse_completo <- sqrt(mean((datos_modelo$Venta - predicciones_completo)^2))




# Paso 10: Importancia de variables
importancia <- xgb.importance(
  feature_names = colnames(datos_modelo)[colnames(datos_modelo) != "Venta"],
  model = modelo_final
)
xgb.plot.importance(importancia, 
                    main = "Importancia de Variables - Producto 3678055 (XGBoost)")

# Paso 11: Gráficos de evaluación
datos_grafico <- data.frame(Observado = datos_modelo$Venta, Predicho = predicciones_completo)

ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(slope = 1, intercept = 0, linetype = "dashed", color = "red") +
  labs(title = "Observado vs Predicho - Producto 3678055", x = "Venta Observada", y = "Venta Predicha") +
  theme_minimal()

errores <- datos_modelo$Venta - predicciones_completo

hist(errores, 
     main = "Distribución de Errores - Producto 3678055 (XGBoost)",
     xlab = "Error (Observado - Predicho)",
     col = "skyblue", breaks = 30)

ggplot(data.frame(Predicho = predicciones_completo, Error = errores), aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(title = "Errores vs Predicción - Producto 3678055", x = "Venta Predicha", y = "Error") +
  theme_minimal()

# Guardar métricas de XGBoost para producto 155001
if(!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3678055",  # Cambia este ID para cada producto
  Modelo = "XGBoost",
  MAPE = mape_completo,
  RMSE = rmse_completo
))

8 Visualización de Métricas

# Definir los colores para cada modelo
colores_modelos <- c(
  "ARMA/SARIMA" = "#1f77b4",    # Azul
  "Regresión Lineal" = "#ff7f0e", # Naranja
  "Random Forest" = "#2ca02c",   # Verde
  "XGBoost" = "#d62728"         # Rojo
)

8.1 PRODUCTO 155001

# Primero, veamos qué datos tenemos realmente
print("Datos actuales para el producto 155001:")
## [1] "Datos actuales para el producto 155001:"
print(metricas_comparativas %>% filter(Producto == "155001"))
##   Producto           Modelo      MAPE         RMSE
## 1   155001             ARMA 17.681643 2.240232e+05
## 2   155001 Regresión Lineal 18.548390 2.426641e+05
## 3   155001    Random Forest  9.639597 1.558329e+10
## 4   155001          XGBoost  7.252695 1.391807e+05
# Crear un dataframe manualmente con los 4 modelos para el producto 155001
# (con valores de ejemplo si es necesario)
datos_155001_completo <- data.frame(
  Producto = rep("155001", 4),
  Modelo = c("ARMA/SARIMA", "Regresión Lineal", "Random Forest", "XGBoost"),
  stringsAsFactors = FALSE
)

# Unir con los datos existentes
datos_155001_completo <- left_join(
  datos_155001_completo,
  metricas_comparativas %>% filter(Producto == "155001"),
  by = c("Producto", "Modelo")
)

# Ahora asigna valores para las métricas de los modelos faltantes
# Si tienes los valores, reemplaza los 0 con los valores correctos
# O toma nota de cuáles son NA para reemplazarlos con los valores reales

# Valores para Regresión Lineal (reemplaza estos con los valores reales)
if (is.na(datos_155001_completo$MAPE[2])) {
  datos_155001_completo$MAPE[2] <- mape_155001  # O el valor correcto
}
if (is.na(datos_155001_completo$RMSE[2])) {
  datos_155001_completo$RMSE[2] <- rmse_155001  # Ya no MSE
}




# Valores para Random Forest (reemplaza estos con los valores reales)
# Si ya ejecutaste la sección de Random Forest para el producto 155001,
# usa las variables r2_rf, rmse_rf, etc.
if (is.na(datos_155001_completo$MAPE[3]) && exists("mape_rf")) {
  datos_155001_completo$MAPE[3] <- mape_rf
}

if (is.na(datos_155001_completo$RMSE[3]) && exists("rmse_rf")) {
  datos_155001_completo$RMSE[3] <- rmse_rf
}






# Valores para XGBoost (reemplaza estos con los valores reales)
# Si ya ejecutaste la sección de XGBoost para el producto 155001,
# usa las variables r2_completo, rmse_completo, etc.
if (is.na(datos_155001_completo$MAPE[4]) && exists("mape_completo")) {
  datos_155001_completo$MAPE[4] <- mape_completo
}

if (is.na(datos_155001_completo$RMSE[4]) && exists("rmse_completo")) {
  datos_155001_completo$RMSE[4] <- rmse_completo
}


# Ver los datos completos
print("Datos completos para el producto 155001:")
## [1] "Datos completos para el producto 155001:"
print(datos_155001_completo)
##   Producto           Modelo      MAPE         RMSE
## 1   155001      ARMA/SARIMA        NA           NA
## 2   155001 Regresión Lineal 18.548390 2.426641e+05
## 3   155001    Random Forest  9.639597 1.558329e+10
## 4   155001          XGBoost  7.252695 1.391807e+05
# Gráfico para MAPE
ggplot(datos_155001_completo, aes(x = Modelo, y = MAPE, fill = Modelo)) +
  geom_bar(stat = "identity", width = 0.6) +
  geom_text(aes(label = round(MAPE, 1)), vjust = -0.5, size = 3.5) +
  scale_fill_manual(values = colores_modelos) +
  labs(
    title = "Comparación de modelos para Producto 155001",
    subtitle = "Métrica: MAPE (valores más bajos indican mejor precisión)",
    x = "",
    y = "MAPE (%)"
  ) +
  theme_minimal() +
  theme(
    legend.position = "none",
    plot.title = element_text(size = 12, face = "bold"),
    plot.subtitle = element_text(size = 10),
    axis.text.x = element_text(angle = 45, hjust = 1)
  ) 
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

# Gráfico para RMSE
ggplot(datos_155001_completo, aes(x = Modelo, y = RMSE, fill = Modelo)) +
  geom_bar(stat = "identity", width = 0.6) +
  geom_text(aes(label = round(RMSE, 1)), vjust = -0.5, size = 3.5) +
  scale_fill_manual(values = colores_modelos) +
  labs(
    title = "Comparación de modelos para Producto 155001",
    subtitle = "Métrica: RMSE (valores más bajos indican mejor precisión)",
    x = "",
    y = "RMSE"
  ) +
  theme_minimal() +
  theme(
    legend.position = "none",
    plot.title = element_text(size = 12, face = "bold"),
    plot.subtitle = element_text(size = 10),
    axis.text.x = element_text(angle = 45, hjust = 1)
  ) +
  ylim(0, max(datos_155001_completo$RMSE, na.rm = TRUE) * 1.1)  # Ajustar el límite Y
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

8.2 PRODUCTO 3929788

# Primero, veamos qué datos tenemos realmente
print("Datos actuales para el producto 3929788:")
## [1] "Datos actuales para el producto 3929788:"
print(metricas_comparativas %>% filter(Producto == "3929788"))
##   Producto           Modelo      MAPE      RMSE
## 1  3929788             ARMA 12.837051 224023.24
## 2  3929788 Regresión Lineal 11.538782 127410.94
## 3  3929788    Random Forest  6.089162  66884.79
## 4  3929788          XGBoost  7.252695 139180.74
# Crear un dataframe manualmente con los 4 modelos para el producto 3929788
datos_3929788_completo <- data.frame(
  Producto = rep("3929788", 4),
  Modelo = c("ARMA/SARIMA", "Regresión Lineal", "Random Forest", "XGBoost"),
  stringsAsFactors = FALSE
)

# Unir con los datos existentes
datos_3929788_completo <- left_join(
  datos_3929788_completo,
  metricas_comparativas %>% filter(Producto == "3929788"),
  by = c("Producto", "Modelo")
)

# Ahora asigna valores para las métricas de los modelos faltantes
# Valores para Regresión Lineal
if (is.na(datos_3929788_completo$MAPE[2])) {
  datos_3929788_completo$MAPE[2] <- mape_3929788
}

if (is.na(datos_3929788_completo$RMSE[2])) {
  datos_3929788_completo$RMSE[2] <- rmse_3929788
}

# Valores para Random Forest
# Si ya ejecutaste la sección de Random Forest para el producto 3929788
if (is.na(datos_3929788_completo$MAPE[3]) && exists("mape_rf")) {
  datos_3929788_completo$MAPE[3] <- mape_rf
}

if (is.na(datos_3929788_completo$RMSE[3]) && exists("rmse_rf")) {
  datos_3929788_completo$RMSE[3] <- rmse_rf
}

# Valores para XGBoost
if (is.na(datos_3929788_completo$MAPE[4]) && exists("mape_completo")) {
  datos_3929788_completo$MAPE[4] <- mape_completo
}


if (is.na(datos_3929788_completo$RMSE[4]) && exists("rmse_completo")) {
  datos_3929788_completo$RMSE[4] <- rmse_completo
}

# Ver los datos completos
print("Datos completos para el producto 3929788:")
## [1] "Datos completos para el producto 3929788:"
print(datos_3929788_completo)
##   Producto           Modelo      MAPE      RMSE
## 1  3929788      ARMA/SARIMA        NA        NA
## 2  3929788 Regresión Lineal 11.538782 127410.94
## 3  3929788    Random Forest  6.089162  66884.79
## 4  3929788          XGBoost  7.252695 139180.74
# Definir colores para los modelos
colores_modelos <- c("ARMA/SARIMA" = "#1f77b4", 
                     "Regresión Lineal" = "#ff7f0e", 
                     "Random Forest" = "#2ca02c", 
                     "XGBoost" = "#d62728")

# Gráfico para RMSE
ggplot(datos_3929788_completo, aes(x = Modelo, y = RMSE, fill = Modelo)) +
  geom_bar(stat = "identity", width = 0.6) +
  geom_text(aes(label = round(RMSE, 1)), vjust = -0.5, size = 3.5) +
  scale_fill_manual(values = colores_modelos) +
  labs(
    title = "Comparación de modelos para Producto 3929788",
    subtitle = "Métrica: RMSE (valores más bajos indican mejor precisión)",
    x = "",
    y = "RMSE"
  ) +
  theme_minimal() +
  theme(
    legend.position = "none",
    plot.title = element_text(size = 12, face = "bold"),
    plot.subtitle = element_text(size = 10),
    axis.text.x = element_text(angle = 45, hjust = 1)
  ) +
  ylim(0, max(datos_3929788_completo$RMSE, na.rm = TRUE) * 1.1)  # Ajustar el límite Y
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

# Gráfico para MAPE
ggplot(datos_3929788_completo, aes(x = Modelo, y = MAPE, fill = Modelo)) +
  geom_bar(stat = "identity", width = 0.6) +
  geom_text(aes(label = round(MAPE, 1)), vjust = -0.5, size = 3.5) +
  scale_fill_manual(values = colores_modelos) +
  labs(
    title = "Comparación de modelos para Producto 3929788",
    subtitle = "Métrica: MAPE (valores más bajos indican mejor precisión)",
    x = "",
    y = "MAPE (%)"
  ) +
  theme_minimal() +
  theme(
    legend.position = "none",
    plot.title = element_text(size = 12, face = "bold"),
    plot.subtitle = element_text(size = 10),
    axis.text.x = element_text(angle = 45, hjust = 1)
  ) +
  ylim(0, max(datos_3929788_completo$MAPE, na.rm = TRUE) * 1.1)  # Ajustar el límite Y
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

8.3 PRODUCTO 3904152

# Primero, veamos qué datos tenemos realmente
print("Datos actuales para el producto 3904152:")
## [1] "Datos actuales para el producto 3904152:"
print(metricas_comparativas %>% filter(Producto == "3904152"))
##   Producto           Modelo      MAPE      RMSE
## 1  3904152             ARMA 15.356792 156981.20
## 2  3904152 Regresión Lineal 13.723256 131733.72
## 3  3904152    Random Forest  8.207213  83391.79
## 4  3904152          XGBoost  9.488338 139180.74
# Crear un dataframe manualmente con los 4 modelos para el producto 3904152
datos_3904152_completo <- data.frame(
  Producto = rep("3904152", 4),
  Modelo = c("ARMA/SARIMA", "Regresión Lineal", "Random Forest", "XGBoost"),
  stringsAsFactors = FALSE
)

# Unir con los datos existentes
datos_3904152_completo <- left_join(
  datos_3904152_completo,
  metricas_comparativas %>% filter(Producto == "3904152"),
  by = c("Producto", "Modelo")
)

# Ahora asigna valores para las métricas de los modelos faltantes
# Valores para Regresión Lineal
if (is.na(datos_3904152_completo$MAPE[2])) {
  datos_3904152_completo$MAPE[2] <- mape_3904152
}
if (is.na(datos_3904152_completo$RMSE[2])) {
  datos_3904152_completo$RMSE[2] <- rmse_3904152
}

# Valores para Random Forest
if (is.na(datos_3904152_completo$MAPE[3]) && exists("mape_rf")) {
  datos_3904152_completo$MAPE[3] <- mape_rf
}

if (is.na(datos_3904152_completo$RMSE[3]) && exists("rmse_rf")) {
  datos_3904152_completo$RMSE[3] <- rmse_rf
}


# Valores para XGBoost
if (is.na(datos_3904152_completo$MAPE[4]) && exists("mape_completo")) {
  datos_3904152_completo$MAPE[4] <- mape_completo
}


if (is.na(datos_3904152_completo$RMSE[4]) && exists("rmse_completo")) {
  datos_3904152_completo$RMSE[4] <- rmse_completo
}


# Ver los datos completos
print("Datos completos para el producto 3904152:")
## [1] "Datos completos para el producto 3904152:"
print(datos_3904152_completo)
##   Producto           Modelo      MAPE      RMSE
## 1  3904152      ARMA/SARIMA        NA        NA
## 2  3904152 Regresión Lineal 13.723256 131733.72
## 3  3904152    Random Forest  8.207213  83391.79
## 4  3904152          XGBoost  9.488338 139180.74
# Definir colores para los modelos
colores_modelos <- c("ARMA/SARIMA" = "#1f77b4", 
                     "Regresión Lineal" = "#ff7f0e", 
                     "Random Forest" = "#2ca02c", 
                     "XGBoost" = "#d62728")

# Gráfico para MSE
ggplot(datos_3904152_completo, aes(x = Modelo, y = RMSE, fill = Modelo)) +
  geom_bar(stat = "identity", width = 0.6) +
  geom_text(aes(label = round(RMSE, 1)), vjust = -0.5, size = 3.5) +
  scale_fill_manual(values = colores_modelos) +
  labs(
    title = "Comparación de modelos para Producto 3904152",
    subtitle = "Métrica: RMSE (valores más bajos indican mejor precisión)",
    x = "",
    y = "RMSE"
  ) +
  theme_minimal() +
  theme(
    legend.position = "none",
    plot.title = element_text(size = 12, face = "bold"),
    plot.subtitle = element_text(size = 10),
    axis.text.x = element_text(angle = 45, hjust = 1)
  ) +
  ylim(0, max(datos_3904152_completo$RMSE, na.rm = TRUE) * 1.1)  # Ajustar el límite Y
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

# Gráfico para MAPE
ggplot(datos_3904152_completo, aes(x = Modelo, y = MAPE, fill = Modelo)) +
  geom_bar(stat = "identity", width = 0.6) +
  geom_text(aes(label = round(MAPE, 1)), vjust = -0.5, size = 3.5) +
  scale_fill_manual(values = colores_modelos) +
  labs(
    title = "Comparación de modelos para Producto 3904152",
    subtitle = "Métrica: MAPE (valores más bajos indican mejor precisión)",
    x = "",
    y = "MAPE (%)"
  ) +
  theme_minimal() +
  theme(
    legend.position = "none",
    plot.title = element_text(size = 12, face = "bold"),
    plot.subtitle = element_text(size = 10),
    axis.text.x = element_text(angle = 45, hjust = 1)
  ) +
  ylim(0, max(datos_3904152_completo$MAPE, na.rm = TRUE) * 1.1)  # Ajustar el límite Y
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

8.4 PRODUCTO 155002

# Primero, veamos qué datos tenemos realmente
print("Datos actuales para el producto 155002:")
## [1] "Datos actuales para el producto 155002:"
print(metricas_comparativas %>% filter(Producto == "155002"))
##   Producto           Modelo     MAPE     RMSE
## 1   155002             ARMA 25.83303 192121.8
## 2   155002 Regresión Lineal 29.16643 212703.4
## 3   155002          XGBoost 13.16291 137986.6
# Crear un dataframe manualmente con los 4 modelos para el producto 155002
datos_155002_completo <- data.frame(
  Producto = rep("155002", 4),
  Modelo = c("ARMA/SARIMA", "Regresión Lineal", "Random Forest", "XGBoost"),
  stringsAsFactors = FALSE
)

# Unir con los datos existentes
datos_155002_completo <- left_join(
  datos_155002_completo,
  metricas_comparativas %>% filter(Producto == "155002"),
  by = c("Producto", "Modelo")
)

# Ahora asigna valores para las métricas de los modelos faltantes
# Valores para Regresión Lineal
if (is.na(datos_155002_completo$MAPE[2])) {
  datos_155002_completo$MAPE[2] <- mape_155002
}

if (is.na(datos_155002_completo$RMSE[2])) {
  datos_155002_completo$RMSE[2] <- rmse_155002
}



# Valores para Random Forest
# Si ya ejecutaste la sección de Random Forest para el producto 155002
if (is.na(datos_155002_completo$MAPE[3]) && exists("mape_rf")) {
  datos_155002_completo$MAPE[3] <- mape_rf
}
if (is.na(datos_155002_completo$RMSE[3]) && exists("rmse_rf")) {
  datos_155002_completo$RMSE[3] <- rmse_rf
}

# Valores para XGBoost
if (is.na(datos_155002_completo$MAPE[4]) && exists("mape_completo")) {
  datos_155002_completo$MAPE[4] <- mape_completo
}
if (is.na(datos_155002_completo$RMSE[4]) && exists("rmse_completo")) {
  datos_155002_completo$RMSE[4] <- rmse_completo
}

# Ver los datos completos
print("Datos completos para el producto 155002:")
## [1] "Datos completos para el producto 155002:"
print(datos_155002_completo)
##   Producto           Modelo     MAPE     RMSE
## 1   155002      ARMA/SARIMA       NA       NA
## 2   155002 Regresión Lineal 29.16643 212703.4
## 3   155002    Random Forest 13.13685 106293.2
## 4   155002          XGBoost 13.16291 137986.6
# Definir colores para los modelos
colores_modelos <- c("ARMA/SARIMA" = "#1f77b4", 
                     "Regresión Lineal" = "#ff7f0e", 
                     "Random Forest" = "#2ca02c", 
                     "XGBoost" = "#d62728")

# Gráfico para MSE
ggplot(datos_155002_completo, aes(x = Modelo, y = RMSE, fill = Modelo)) +
  geom_bar(stat = "identity", width = 0.6) +
  geom_text(aes(label = round(RMSE, 1)), vjust = -0.5, size = 3.5) +
  scale_fill_manual(values = colores_modelos) +
  labs(
    title = "Comparación de modelos para Producto 155002",
    subtitle = "Métrica: RMSE (valores más bajos indican mejor precisión)",
    x = "",
    y = "RMSE"
  ) +
  theme_minimal() +
  theme(
    legend.position = "none",
    plot.title = element_text(size = 12, face = "bold"),
    plot.subtitle = element_text(size = 10),
    axis.text.x = element_text(angle = 45, hjust = 1)
  ) +
  ylim(0, max(datos_155002_completo$RMSE, na.rm = TRUE) * 1.1)  # Ajustar el límite Y
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

# Gráfico para MAPE
ggplot(datos_155002_completo, aes(x = Modelo, y = MAPE, fill = Modelo)) +
  geom_bar(stat = "identity", width = 0.6) +
  geom_text(aes(label = round(MAPE, 1)), vjust = -0.5, size = 3.5) +
  scale_fill_manual(values = colores_modelos) +
  labs(
    title = "Comparación de modelos para Producto 155002",
    subtitle = "Métrica: MAPE (valores más bajos indican mejor precisión)",
    x = "",
    y = "MAPE (%)"
  ) +
  theme_minimal() +
  theme(
    legend.position = "none",
    plot.title = element_text(size = 12, face = "bold"),
    plot.subtitle = element_text(size = 10),
    axis.text.x = element_text(angle = 45, hjust = 1)
  ) +
  ylim(0, max(datos_155002_completo$MAPE, na.rm = TRUE) * 1.1)  # Ajustar el límite Y
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

8.5 PRODUCTO 3678055

# Primero, veamos qué datos tenemos realmente
print("Datos actuales para el producto 3678055:")
## [1] "Datos actuales para el producto 3678055:"
print(metricas_comparativas %>% filter(Producto == "3678055"))
##   Producto           Modelo     MAPE         RMSE
## 1  3678055             ARMA 22.73774 1.770405e+05
## 2  3678055 Regresión Lineal 21.14984 2.765812e+10
## 3  3678055    Random Forest 13.37420 1.039038e+05
## 4  3678055    Random Forest 13.13685 1.062932e+05
## 5  3678055          XGBoost 14.62159 1.313350e+05
# Crear un dataframe manualmente con los 4 modelos para el producto 3678055
datos_3678055_completo <- data.frame(
  Producto = rep("3678055", 4),
  Modelo = c("ARMA/SARIMA", "Regresión Lineal", "Random Forest", "XGBoost"),
  stringsAsFactors = FALSE
)

# Unir con los datos existentes
datos_3678055_completo <- left_join(
  datos_3678055_completo,
  metricas_comparativas %>% filter(Producto == "3678055"),
  by = c("Producto", "Modelo")
)

# Ahora asigna valores para las métricas de los modelos faltantes
# Valores para Regresión Lineal
if (is.na(datos_3678055_completo$MAPE[2])) {
  datos_3678055_completo$MAPE[2] <- mape_3678055
}
if (is.na(datos_3678055_completo$RMSE[2])) {
  datos_3678055_completo$RMSE[2] <- rmse_3678055
}

# Valores para Random Forest
# Si ya ejecutaste la sección de Random Forest para el producto 3678055
if (is.na(datos_3678055_completo$MAPE[3]) && exists("mape_rf")) {
  datos_3678055_completo$MAPE[3] <- mape_rf
}
if (is.na(datos_3678055_completo$RMSE[3]) && exists("rmse_rf")) {
  datos_3678055_completo$RMSE[3] <- rmse_rf
}

# Valores para XGBoost
if (is.na(datos_3678055_completo$MAPE[4]) && exists("mape_completo")) {
  datos_3678055_completo$MAPE[4] <- mape_completo
}
if (is.na(datos_3678055_completo$RMSE[4]) && exists("rmse_completo")) {
  datos_3678055_completo$RMSE[4] <- rmse_completo
}

# Ver los datos completos
print("Datos completos para el producto 3678055:")
## [1] "Datos completos para el producto 3678055:"
print(datos_3678055_completo)
##   Producto           Modelo     MAPE         RMSE
## 1  3678055      ARMA/SARIMA       NA           NA
## 2  3678055 Regresión Lineal 21.14984 2.765812e+10
## 3  3678055    Random Forest 13.37420 1.039038e+05
## 4  3678055    Random Forest 13.13685 1.062932e+05
## 5  3678055          XGBoost 14.62159 1.313350e+05
# Definir colores para los modelos
colores_modelos <- c("ARMA/SARIMA" = "#1f77b4", 
                     "Regresión Lineal" = "#ff7f0e", 
                     "Random Forest" = "#2ca02c", 
                     "XGBoost" = "#d62728")
# Gráfico para MSE
ggplot(datos_3678055_completo, aes(x = Modelo, y = RMSE, fill = Modelo)) +
  geom_bar(stat = "identity", width = 0.6) +
  geom_text(aes(label = round(RMSE, 1)), vjust = -0.5, size = 3.5) +
  scale_fill_manual(values = colores_modelos) +
  labs(
    title = "Comparación de modelos para Producto 3678055",
    subtitle = "Métrica: RMSE (valores más bajos indican mejor precisión)",
    x = "",
    y = "RMSE"
  ) +
  theme_minimal() +
  theme(
    legend.position = "none",
    plot.title = element_text(size = 12, face = "bold"),
    plot.subtitle = element_text(size = 10),
    axis.text.x = element_text(angle = 45, hjust = 1)
  ) +
  ylim(0, max(datos_3678055_completo$RMSE, na.rm = TRUE) * 1.1)  # Ajustar el límite Y
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

# Gráfico para MAPE
ggplot(datos_3678055_completo, aes(x = Modelo, y = MAPE, fill = Modelo)) +
  geom_bar(stat = "identity", width = 0.6) +
  geom_text(aes(label = round(MAPE, 1)), vjust = -0.5, size = 3.5) +
  scale_fill_manual(values = colores_modelos) +
  labs(
    title = "Comparación de modelos para Producto 3678055",
    subtitle = "Métrica: MAPE (valores más bajos indican mejor precisión)",
    x = "",
    y = "MAPE (%)"
  ) +
  theme_minimal() +
  theme(
    legend.position = "none",
    plot.title = element_text(size = 12, face = "bold"),
    plot.subtitle = element_text(size = 10),
    axis.text.x = element_text(angle = 45, hjust = 1)
  ) +
  ylim(0, max(datos_3678055_completo$MAPE, na.rm = TRUE) * 1.1)  # Ajustar el límite Y
## Warning: Removed 2 rows containing missing values or values outside the scale range
## (`geom_bar()`).
## Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

9 ESTIMACIÓN DE PRECIOS

9.0.1 Preparación de datos

# Función para preparar datos de un producto
prepare_price_data <- function(df, product_id) {
  product_data <- df %>%
    filter(ID_Inventario == product_id) %>%
    arrange(Trx_Fecha) %>%
    select(
      Trx_Fecha, Precio_Final_Unitario, Cant, Venta, 
      Costo_Venta, Descuento_Porcentaje, Semana, Mes
    ) %>%
    mutate(
      Dia_Semana = wday(Trx_Fecha),
      Mes_Num = month(Trx_Fecha),
      Anio = year(Trx_Fecha),
      Dias_Desde_Inicio = as.numeric(difftime(Trx_Fecha, min(Trx_Fecha), units = "days")),
      Margen_Unitario = (Venta / Cant) - (Costo_Venta / Cant),
      Precio_Unitario_Calc = Venta / Cant,
      ID_Inventario = product_id
    )
  
  return(product_data)
}

# Asegúrate de que 'datos' sea tu data.frame cargado correctamente
# Por ejemplo, si vienes de un archivo .csv:
# datos <- read.csv("archivo.csv")

# Aplicar la función a todos los productos
ids <- unique(datos$ID_Inventario)

productos_preparados <- map_df(ids, function(id) {
  prepare_price_data(datos, id)
})

# Mostrar una parte del resultado
head(productos_preparados)
## # A tibble: 6 × 15
##   Trx_Fecha           Precio_Final_Unitario  Cant Venta Costo_Venta
##   <dttm>                              <dbl> <dbl> <dbl>       <dbl>
## 1 2023-01-02 00:00:00                   980     1   980        727.
## 2 2023-01-03 00:00:00                   728     1   728        905.
## 3 2023-01-03 00:00:00                   840     6  5040       3598.
## 4 2023-01-04 00:00:00                  1120     1  1120        577.
## 5 2023-01-04 00:00:00                   728     8  5824       6619.
## 6 2023-01-04 00:00:00                   980    10  9800       7273.
## # ℹ 10 more variables: Descuento_Porcentaje <dbl>, Semana <dbl>, Mes <dbl>,
## #   Dia_Semana <dbl>, Mes_Num <dbl>, Anio <dbl>, Dias_Desde_Inicio <dbl>,
## #   Margen_Unitario <dbl>, Precio_Unitario_Calc <dbl>, ID_Inventario <dbl>
# Vector con productos (debe ir primero)
productos_ids <- top_ids

# Función para entrenar modelo ARMA por producto
train_arma_model <- function(data, product_id) {
  library(forecast)  # Asegúrate de cargar forecast si no está cargado aún
  product_data <- data %>% filter(ID_Inventario == product_id)
  serie_ts <- ts(product_data$Venta, frequency = 12)
  modelo_arma <- auto.arima(serie_ts, seasonal = FALSE, stepwise = FALSE, approximation = FALSE)
  return(modelo_arma)
}

# Crear lista de modelos ARMA por producto
modelos_arma_lista <- setNames(
  lapply(productos_ids, function(id) train_arma_model(datos, id)),
  as.character(productos_ids)
)

# Función para modelo regresión lineal
train_reg_model <- function(data, product_id) {
  product_data <- data %>% filter(ID_Inventario == product_id)
  modelo_reg <- lm(Venta ~ Precio_Final_Unitario, data = product_data)
  return(modelo_reg)
}

# Función para modelo Random Forest
train_rf_model <- function(data, product_id) {
  product_data <- data %>% filter(ID_Inventario == product_id)
  predictors <- c("Precio_Final_Unitario", "Cant", "Descuento_Porcentaje")
  rf_data <- product_data %>% select(all_of(predictors), Venta)
  modelo_rf <- randomForest(Venta ~ ., data = rf_data, ntree = 100)
  return(modelo_rf)
}

# Función para modelo XGBoost
train_xgb_model <- function(data, product_id) {
  product_data <- data %>% filter(ID_Inventario == product_id)
  predictors <- c("Precio_Final_Unitario", "Cant", "Descuento_Porcentaje")
  train_matrix <- xgb.DMatrix(data = as.matrix(product_data[, predictors]), label = product_data$Venta)
  params <- list(objective = "reg:squarederror")
  modelo_xgb <- xgb.train(params = params, data = train_matrix, nrounds = 50, verbose = 0)
  return(modelo_xgb)
}

# Crear listas de modelos
modelos_reg_lista <- setNames(lapply(productos_ids, function(id) train_reg_model(datos, id)), as.character(productos_ids))
modelos_rf_lista <- setNames(lapply(productos_ids, function(id) train_rf_model(datos, id)), as.character(productos_ids))
modelos_xgb_lista <- setNames(lapply(productos_ids, function(id) train_xgb_model(datos, id)), as.character(productos_ids))

9.0.2 Entrenar modelos de predicción de precios

# Función para entrenar modelos de predicción de precios
train_price_models <- function(data, product_id, test_size = 0.2) {
  price_data <- prepare_price_data(data, product_id) %>%
    drop_na() %>%
    select(
      Precio_Final_Unitario,
      Cant, Costo_Venta, Descuento_Porcentaje,
      Dia_Semana, Mes_Num, Anio, Dias_Desde_Inicio,
      Margen_Unitario
    )

  # Evitar fallos si hay muy pocos datos
  if (nrow(price_data) < 10) {
    warning(paste("Producto", product_id, "tiene menos de 10 registros. Se omite."))
    return(NULL)
  }

  set.seed(123)
  train_index <- createDataPartition(price_data$Precio_Final_Unitario, p = 1 - test_size, list = FALSE)
  train_data <- price_data[train_index, ]
  test_data <- price_data[-train_index, ]

  # 1. Regresión Lineal
  lm_model <- lm(Precio_Final_Unitario ~ ., data = train_data)

  # 2. Random Forest
  rf_model <- randomForest(
    Precio_Final_Unitario ~ .,
    data = train_data,
    ntree = 500,
    importance = TRUE
  )

  # 3. XGBoost
  features <- setdiff(names(train_data), "Precio_Final_Unitario")
  x_train <- as.matrix(train_data[, features])
  y_train <- train_data$Precio_Final_Unitario
  x_test <- as.matrix(test_data[, features])
  y_test <- test_data$Precio_Final_Unitario
  dtrain <- xgb.DMatrix(data = x_train, label = y_train)
  dtest <- xgb.DMatrix(data = x_test, label = y_test)

  xgb_params <- list(
    objective = "reg:squarederror",
    eval_metric = "rmse",
    eta = 0.1,
    max_depth = 6,
    min_child_weight = 3,
    subsample = 0.8,
    colsample_bytree = 0.8
  )

  xgb_model <- xgb.train(
    params = xgb_params,
    data = dtrain,
    nrounds = 100,
    watchlist = list(train = dtrain, test = dtest),
    early_stopping_rounds = 10,
    verbose = 0
  )

  # Evaluación
  lm_pred <- predict(lm_model, newdata = test_data)
  rf_pred <- predict(rf_model, newdata = test_data)
  xgb_pred <- predict(xgb_model, x_test)

  lm_rmse <- sqrt(mean((lm_pred - test_data$Precio_Final_Unitario)^2))
  rf_rmse <- sqrt(mean((rf_pred - test_data$Precio_Final_Unitario)^2))
  xgb_rmse <- sqrt(mean((xgb_pred - test_data$Precio_Final_Unitario)^2))

  lm_r2 <- 1 - sum((test_data$Precio_Final_Unitario - lm_pred)^2) /
    sum((test_data$Precio_Final_Unitario - mean(test_data$Precio_Final_Unitario))^2)
  rf_r2 <- 1 - sum((test_data$Precio_Final_Unitario - rf_pred)^2) /
    sum((test_data$Precio_Final_Unitario - mean(test_data$Precio_Final_Unitario))^2)
  xgb_r2 <- 1 - sum((test_data$Precio_Final_Unitario - xgb_pred)^2) /
    sum((test_data$Precio_Final_Unitario - mean(test_data$Precio_Final_Unitario))^2)

  metrics <- data.frame(
    Model = c("Linear Regression", "Random Forest", "XGBoost"),
    RMSE = c(lm_rmse, rf_rmse, xgb_rmse),
    R2 = c(lm_r2, rf_r2, xgb_r2)
  )

  return(list(metrics = metrics))
}

# IDs de los 5 productos a modelar
productos_ids <- c(155001, 3929788, 3904152, 155002, 3678055)

# Aplicar modelo a cada producto
resultados_modelos <- map(productos_ids, function(id) {
  resultado <- train_price_models(datos, product_id = id)
  if (!is.null(resultado)) {
    resultado$metrics %>% mutate(ID_Inventario = id)
  } else {
    NULL
  }
}) %>% compact() %>% bind_rows()

# Mostrar resultados
resultados_modelos
##                Model        RMSE        R2 ID_Inventario
## 1  Linear Regression  30.2314808 0.9301839        155001
## 2      Random Forest  20.4235912 0.9681360        155001
## 3            XGBoost  10.4223865 0.9917020        155001
## 4  Linear Regression   0.9961090 0.9745183       3929788
## 5      Random Forest   0.7372416 0.9860417       3929788
## 6            XGBoost   0.3352396 0.9971138       3929788
## 7  Linear Regression  63.2610620 0.8980785       3904152
## 8      Random Forest  12.2140718 0.9962006       3904152
## 9            XGBoost  11.1592529 0.9968285       3904152
## 10 Linear Regression  25.7977520 0.9226069        155002
## 11     Random Forest  10.3007416 0.9876611        155002
## 12           XGBoost   5.1695872 0.9968922        155002
## 13 Linear Regression 110.6053375 0.8924438       3678055
## 14     Random Forest  30.7411253 0.9916915       3678055
## 15           XGBoost  18.7278222 0.9969164       3678055
# Lista con los IDs de productos (puedes usar top_ids que ya definiste)
productos_ids <- top_ids

# Entrenar modelos para cada producto y guardar en lista
modelos_precio_lista <- setNames(
  lapply(productos_ids, function(id) train_price_models(datos, id)),
  as.character(productos_ids)
)

9.0.3 Estimar precios óptimos

estimate_optimal_prices <- function(data, product_id, price_models, demand_models = NULL, future_dates = NULL) {
  price_steps <- 20

  # Selección del mejor modelo de precio
  best_price_model_idx <- which.max(price_models$metrics$R2)
  best_price_model_name <- price_models$metrics$Model[best_price_model_idx]

  # Datos del producto
  product_data <- data %>% filter(ID_Inventario == product_id)

  # Rango de precios restringido a percentiles 5% - 95% y limitado a 1.5x la mediana
  min_price <- quantile(product_data$Precio_Final_Unitario, 0.05, na.rm = TRUE)
  max_price <- quantile(product_data$Precio_Final_Unitario, 0.95, na.rm = TRUE)
  price_range <- seq(min_price, max_price, length.out = price_steps)
  price_range <- pmin(price_range, 1.5 * median(product_data$Precio_Final_Unitario, na.rm = TRUE))

  # Inicializar escenarios futuros
  future_scenarios <- data.frame()

  for (future_date in future_dates) {
    future_date <- as.Date(future_date)
    mes_actual <- lubridate::month(future_date)

    mes_data <- product_data %>% filter(lubridate::month(Trx_Fecha) == mes_actual)
    if (nrow(mes_data) < 5) mes_data <- product_data

    costo_mes <- median(mes_data$Costo_Venta, na.rm = TRUE)
    cant_mes <- median(mes_data$Cant, na.rm = TRUE)
    desc_mes <- median(mes_data$Descuento_Porcentaje, na.rm = TRUE)

    if (is.na(costo_mes)) costo_mes <- median(product_data$Costo_Venta, na.rm = TRUE)
    if (is.na(cant_mes) || cant_mes == 0) cant_mes <- median(product_data$Cant, na.rm = TRUE)
    if (is.na(desc_mes)) desc_mes <- median(product_data$Descuento_Porcentaje, na.rm = TRUE)

    date_df <- data.frame(
      Trx_Fecha = rep(future_date, price_steps),
      Precio_Final_Unitario = price_range,
      Cant = cant_mes,
      Costo_Venta = costo_mes,
      Descuento_Porcentaje = desc_mes,
      Dia_Semana = lubridate::wday(future_date),
      Mes_Num = mes_actual,
      Anio = lubridate::year(future_date),
      Dias_Desde_Inicio = as.numeric(difftime(future_date, min(product_data$Trx_Fecha), units = "days")),
      Margen_Unitario = NA
    )

    future_scenarios <- rbind(future_scenarios, date_df)
  }

  # Calcular margen unitario simulado
  future_scenarios$Margen_Unitario <- future_scenarios$Precio_Final_Unitario -
    (future_scenarios$Costo_Venta / future_scenarios$Cant)

  # Estimar elasticidad histórica (con mínimo 15 puntos válidos)
  product_data <- product_data %>% arrange(Trx_Fecha)
  elasticity_df <- product_data %>%
    filter(!is.na(Cant) & !is.na(Precio_Final_Unitario)) %>%
    mutate(
      P_lag = lag(Precio_Final_Unitario),
      Q_lag = lag(Cant),
      dP = Precio_Final_Unitario - P_lag,
      dQ = Cant - Q_lag,
      elasticity_point = (dQ / Q_lag) / (dP / P_lag)
    ) %>%
    filter(!is.na(elasticity_point), is.finite(elasticity_point))

  elasticity <- median(elasticity_df$elasticity_point, na.rm = TRUE)
  if (nrow(elasticity_df) < 15 || is.na(elasticity) || !is.finite(elasticity)) {
    elasticity <- 1
  }

  # Estimar ventas y márgenes
  results <- future_scenarios %>%
    mutate(Venta_Esperada = 0, Margen_Total = 0)

  # Precio base sin descuentos (más robusto)
  baseline_price <- median(product_data %>% filter(Descuento_Porcentaje == 0) %>%
                             pull(Precio_Final_Unitario), na.rm = TRUE)
  if (is.na(baseline_price)) {
    baseline_price <- median(product_data$Precio_Final_Unitario, na.rm = TRUE)
  }

  for (i in 1:nrow(results)) {
    price_ratio <- baseline_price / results$Precio_Final_Unitario[i]
    adjusted_quantity <- results$Cant[i] * (price_ratio ^ elasticity)
    results$Venta_Esperada[i] <- results$Precio_Final_Unitario[i] * adjusted_quantity
    results$Margen_Total[i] <- adjusted_quantity * results$Margen_Unitario[i]
  }

  # Seleccionar precios óptimos (por fecha)
  optimal_prices <- results %>%
    group_by(Trx_Fecha) %>%
    slice_max(Venta_Esperada, n = 1) %>%
    select(Trx_Fecha, Precio_Optimal = Precio_Final_Unitario, Venta_Esperada, Margen_Total)

  # Validación de precios extremos
  precio_median <- median(product_data$Precio_Final_Unitario, na.rm = TRUE)
  if (any(optimal_prices$Precio_Optimal > 2 * precio_median)) {
    warning(paste(" Precio óptimo muy alto detectado para producto", product_id))
  }

  return(list(
    resultados = results,
    precios_optimos = optimal_prices,
    elasticidad = elasticity
  ))
}

9.0.4 Visualizar resultados

# Fechas futuras para simulación
dates_future <- seq(as.Date("2025-01-01"), by = "month", length.out = 6)

# Lista para guardar resultados por producto
precios_optimos_lista <- list()

# Iterar por productos
for (pid in productos_ids) {
  cat("PRODUCTO:", pid, "\n")
  
  modelo_precio <- modelos_precio_lista[[as.character(pid)]]
  
  if (!is.null(modelo_precio)) {
    resultado <- estimate_optimal_prices(
      data = datos,
      product_id = pid,
      price_models = modelo_precio,
      future_dates = dates_future
    )
    
    precios_optimos_lista[[as.character(pid)]] <- resultado
    
    cat("Elasticidad estimada:", round(resultado$elasticidad, 2), "\n\n")
    
    # Mostrar tabla manualmente fila por fila
    print(resultado$precios_optimos)
    
  } else {
    cat("No hay modelo de precios para el producto", pid, "\n")
  }
}
## PRODUCTO: 155001 
## Elasticidad estimada: -2.91 
## 
## # A tibble: 6 × 4
## # Groups:   Trx_Fecha [6]
##   Trx_Fecha  Precio_Optimal Venta_Esperada Margen_Total
##   <date>              <dbl>          <dbl>        <dbl>
## 1 2025-01-01            630          3397.         606.
## 2 2025-02-01            630          3397.         637.
## 3 2025-03-01            630          3397.         733.
## 4 2025-04-01            630          3397.        1004.
## 5 2025-05-01            630          3397.        1094.
## 6 2025-06-01            630          3397.         911.
## PRODUCTO: 3929788 
## Elasticidad estimada: -3.1 
## 
## # A tibble: 6 × 4
## # Groups:   Trx_Fecha [6]
##   Trx_Fecha  Precio_Optimal Venta_Esperada Margen_Total
##   <date>              <dbl>          <dbl>        <dbl>
## 1 2025-01-01           51.6           162.         74.9
## 2 2025-02-01           51.6           130.         53.7
## 3 2025-03-01           51.6           130.         42.0
## 4 2025-04-01           51.6           130.         53.6
## 5 2025-05-01           51.6           130.         42.2
## 6 2025-06-01           51.6           130.         42.6
## PRODUCTO: 3904152 
## Elasticidad estimada: 0 
## 
## # A tibble: 6 × 4
## # Groups:   Trx_Fecha [6]
##   Trx_Fecha  Precio_Optimal Venta_Esperada Margen_Total
##   <date>              <dbl>          <dbl>        <dbl>
## 1 2025-01-01           3556           3556        1089.
## 2 2025-02-01           3556           3556        1088.
## 3 2025-03-01           3556           3556        1092.
## 4 2025-04-01           3556           3556        1092.
## 5 2025-05-01           3556           3556        1092.
## 6 2025-06-01           3556           3556        1113.
## PRODUCTO: 155002 
## Elasticidad estimada: -4.19 
## 
## # A tibble: 6 × 4
## # Groups:   Trx_Fecha [6]
##   Trx_Fecha  Precio_Optimal Venta_Esperada Margen_Total
##   <date>              <dbl>          <dbl>        <dbl>
## 1 2025-01-01           594.          6619.        1630.
## 2 2025-02-01           594.          4413.        -391.
## 3 2025-03-01           594.          6619.        1740.
## 4 2025-04-01           594.          6619.        2980.
## 5 2025-05-01           594.          6619.        2825.
## 6 2025-06-01           594.          6619.        2436.
## PRODUCTO: 3678055 
## Elasticidad estimada: 0 
## 
## # A tibble: 6 × 4
## # Groups:   Trx_Fecha [6]
##   Trx_Fecha  Precio_Optimal Venta_Esperada Margen_Total
##   <date>              <dbl>          <dbl>        <dbl>
## 1 2025-01-01           5908           5908        1794.
## 2 2025-02-01           5908           5908        1722.
## 3 2025-03-01           5908           5908        1695.
## 4 2025-04-01           5908           5908        1841.
## 5 2025-05-01           5908           5908        1807.
## 6 2025-06-01           5908           5908        1983.

9.0.5 Integración de precios óptimos y modelos

integrate_with_existing_models <- function(data, product_id, price_opt_results, xgb_model) {
  optimal_prices <- price_opt_results[[as.character(product_id)]]$precios_optimos
  
  if (is.null(optimal_prices) || nrow(optimal_prices) == 0) {
    warning(paste("No se encontraron precios óptimos para el producto", product_id))
    return(data.frame())
  }
  
  hist_data <- data %>%
    filter(ID_Inventario == product_id) %>%
    arrange(Trx_Fecha)
  
  future_features <- data.frame()
  
  for (i in 1:nrow(optimal_prices)) {
    future_date <- optimal_prices$Trx_Fecha[i]
    future_price <- optimal_prices$Precio_Optimal[i]
    
    mes_data <- hist_data %>%
      filter(lubridate::month(Trx_Fecha) == lubridate::month(future_date))
    
    if (nrow(mes_data) < 5) mes_data <- hist_data
    
    avg_features <- mes_data %>%
      summarise(
        Cant = median(Cant, na.rm = TRUE),
        Costo_Venta = median(Costo_Venta, na.rm = TRUE),
        Costo_Devolucion = median(Costo_Devolucion, na.rm = TRUE),
        Precio_Lista_Unitario = median(Precio_Lista_Unitario, na.rm = TRUE),
        Descuento_Porcentaje = median(Descuento_Porcentaje, na.rm = TRUE),
        Tiempo = as.numeric(difftime(future_date, min(hist_data$Trx_Fecha), units = "days")) / 30
      )
    
    # Variables de tendencia:
    avg_features$Precio_Final_Unitario <- future_price
    avg_features$Trx_Fecha <- future_date
    avg_features$Mes_Num <- lubridate::month(future_date)
    avg_features$Anio <- lubridate::year(future_date)
    avg_features$Mes_Desde_Inicio <- as.numeric(difftime(future_date, min(hist_data$Trx_Fecha), units = "days")) %/% 30
    
    future_features <- rbind(future_features, avg_features)
  }
  
  future_data <- data.frame(
    Fecha = future_features$Trx_Fecha,
    Precio_Final_Unitario = future_features$Precio_Final_Unitario
  )
  
  # === PREDICCIÓN CON XGBoost ===
  tryCatch({
    features <- xgb_model$feature_names
    if (is.null(features)) {
      features <- setdiff(names(future_features), "Venta")
    }
    xgb_matrix <- as.matrix(future_features[, features, drop = FALSE])
    future_data$Venta_XGBoost <- predict(xgb_model, xgb_matrix)
  }, error = function(e) {
    warning(paste("Error al predecir con XGBoost para producto", product_id, ":", e$message))
    future_data$Venta_XGBoost <- NA
  })
  
  # === MÉTRICAS ===
  avg_cost_per_unit <- median(hist_data$Costo_Venta / hist_data$Cant, na.rm = TRUE)
  
  future_data$Unidades_XGBoost <- future_data$Venta_XGBoost / future_data$Precio_Final_Unitario
  future_data$Costo_XGBoost <- future_data$Unidades_XGBoost * avg_cost_per_unit
  future_data$Margen_XGBoost <- future_data$Venta_XGBoost - future_data$Costo_XGBoost
  
  return(future_data)
}

9.0.6 Pipeline correcto

corregir_formato_fechas <- function(datos) {
  if ("Trx_Fecha" %in% colnames(datos)) {
    datos$Trx_Fecha_Original <- datos$Trx_Fecha

    if (is.character(datos$Trx_Fecha) &&
        any(grepl("^\\d{7}-\\d{2}-\\d{2}$", datos$Trx_Fecha))) {

      cat("Corrigiendo formato de fechas extraño...\n")

      datos$Trx_Fecha <- sapply(datos$Trx_Fecha, function(fecha) {
        if (is.na(fecha) || !is.character(fecha)) return(NA)

        partes <- strsplit(fecha, "-")[[1]]
        if (length(partes) == 3) {
          fecha_corregida <- paste("2023", partes[2], partes[3], sep = "-")
          return(fecha_corregida)
        } else {
          return(NA)
        }
      })

      datos$Trx_Fecha <- as.Date(datos$Trx_Fecha)
      cat("Fechas corregidas exitosamente.\n")
    } else if (!inherits(datos$Trx_Fecha, "Date")) {
      cat("Intentando convertir fechas a formato Date...\n")
      datos$Trx_Fecha <- as.Date(datos$Trx_Fecha)
    }
  }
  return(datos)
}

# Aplicar la corrección a tu dataframe antes de usarlo
datos_filtrados <- corregir_formato_fechas(datos_filtrados)
## Intentando convertir fechas a formato Date...
dates_future <- seq.Date(as.Date("2023-01-01"), by = "month", length.out = 6)
precios_optimos_lista <- list()

for (id in productos_ids) {
  cat("Estimando precios óptimos para producto:", id, "\n")

  modelo_precio <- modelos_precio_lista[[as.character(id)]]

  if (!is.null(modelo_precio)) {
    precios_optimos_lista[[as.character(id)]] <- estimate_optimal_prices(
      data = datos_filtrados,
      product_id = id,
      price_models = modelo_precio,
      future_dates = dates_future
    )
  }
}
## Estimando precios óptimos para producto: 155001 
## Estimando precios óptimos para producto: 3929788 
## Estimando precios óptimos para producto: 3904152 
## Estimando precios óptimos para producto: 155002 
## Estimando precios óptimos para producto: 3678055
for (id in names(precios_optimos_lista)) {
  df_optimo <- precios_optimos_lista[[id]]$precios_optimos

  if (!inherits(df_optimo$Trx_Fecha, "Date")) {
    df_optimo$Trx_Fecha <- as.Date(df_optimo$Trx_Fecha)
  }

  cat(paste0("\n### Producto: ", id, "\n"))

  print(
    ggplot(df_optimo, aes(x = Trx_Fecha, y = Precio_Optimal)) +
      geom_line(color = "#1f77b4", linewidth = 1.2) +
      geom_point(color = "#1f77b4", size = 2) +
      labs(
        title = paste("Precio Óptimo por Mes - Producto", id),
        x = "Fecha",
        y = "Precio Óptimo"
      ) +
      scale_x_date(date_labels = "%b %Y", date_breaks = "1 month") +
      theme_minimal(base_size = 12) +
      theme(
        plot.title = element_text(face = "bold"),
        axis.text.x = element_text(angle = 45, hjust = 1)
      )
  )
}
## 
## ### Producto: 155001

## 
## ### Producto: 3929788

## 
## ### Producto: 3904152

## 
## ### Producto: 155002

## 
## ### Producto: 3678055

run_price_optimization <- function(data, product_ids, future_dates = NULL, modelos_precio_lista = NULL) {
  if (is.null(future_dates)) {
    future_dates <- seq.Date(Sys.Date(), by = "month", length.out = 6)
  }

  precios_optimos_lista <- list()

  for (id in product_ids) {
    cat("Estimando precios óptimos para producto:", id, "\n")

    price_model <- NULL
    if (!is.null(modelos_precio_lista)) {
      price_model <- modelos_precio_lista[[as.character(id)]]
    }

    precios_optimos_lista[[as.character(id)]] <- estimate_optimal_prices(
      data = data,
      product_id = id,
      price_models = price_model,
      future_dates = future_dates
    )
  }

  return(precios_optimos_lista)
}
# Función principal que integra todo el pipeline con solo XGBoost
run_complete_analysis <- function(data, top_ids, modelos_xgb, modelos_precio_lista = NULL) {
  # 1. Estimar precios óptimos
  all_results <- run_price_optimization(data, top_ids, modelos_precio_lista = modelos_precio_lista)

  # 2. Integrar con modelo XGBoost
  integrated_results <- list()

  for (i in seq_along(top_ids)) {
    pid <- top_ids[i]
    pid_str <- as.character(pid)

    xgb_model <- if (length(modelos_xgb) >= i) modelos_xgb[[i]] else NULL

    future_predictions <- integrate_with_existing_models(
      data = data,
      product_id = pid,
      price_opt_results = all_results,
      xgb_model = xgb_model
    )

    integrated_results[[pid_str]] <- future_predictions

    if (nrow(future_predictions) > 0) {
      p_sales <- ggplot(future_predictions, aes(x = Fecha, y = Venta_XGBoost)) +
        geom_line(color = "#1f77b4", linewidth = 1.2) +
        geom_point(size = 2) +
        labs(
          title = paste("Predicciones de ventas con precios óptimos - Producto", pid),
          x = "Fecha",
          y = "Ventas estimadas ($)"
        ) +
        theme_minimal()

      p_margins <- ggplot(future_predictions, aes(x = Fecha, y = Margen_XGBoost)) +
        geom_col(fill = "steelblue", width = 15) +
        geom_text(aes(label = round(Margen_XGBoost, 0)), vjust = -0.5, size = 3.5) +
        labs(
          title = paste("Margen esperado con precios óptimos - Producto", pid),
          x = "Fecha",
          y = "Margen estimado ($)"
        ) +
        theme_minimal()

      all_results[[pid_str]]$integrated_plots <- list(
        sales = p_sales,
        margins = p_margins
      )
    }
  }

  # 3. Visualización de precios óptimos
  all_optimal_prices <- data.frame()

  for (pid in top_ids) {
    pid_str <- as.character(pid)
    if (pid_str %in% names(all_results)) {
      opt_prices <- all_results[[pid_str]]$precios_optimos %>%
        mutate(ID_Inventario = pid)

      all_optimal_prices <- rbind(all_optimal_prices, opt_prices)
    }
  }

  p_comparison <- ggplot(all_optimal_prices,
                         aes(x = Trx_Fecha, y = Precio_Optimal, color = factor(ID_Inventario))) +
    geom_line(size = 1.2) +
    geom_point(size = 3) +
    labs(
      title = "Comparación de Precios Óptimos por Producto",
      x = "Fecha",
      y = "Precio Óptimo",
      color = "ID Producto"
    ) +
    theme_minimal()

  # 4. Métricas finales
  metricas_optimas <- data.frame()

  for (pid in top_ids) {
    pid_str <- as.character(pid)
    if (pid_str %in% names(integrated_results)) {
      pred_data <- integrated_results[[pid_str]]

      if ("Margen_XGBoost" %in% names(pred_data)) {
        metrics_row <- data.frame(
          ID_Inventario = pid,
          Precio_Promedio = mean(pred_data$Precio_Final_Unitario, na.rm = TRUE),
          Venta_Total = sum(pred_data$Venta_XGBoost, na.rm = TRUE),
          Margen_Total = sum(pred_data$Margen_XGBoost, na.rm = TRUE),
          Margen_Porcentual = 100 * sum(pred_data$Margen_XGBoost, na.rm = TRUE) /
            sum(pred_data$Venta_XGBoost, na.rm = TRUE)
        )
        metricas_optimas <- rbind(metricas_optimas, metrics_row)
      }
    }
  }

  return(list(
    resultados = all_results,
    integracion = integrated_results,
    precios_optimos = all_optimal_prices,
    metricas_optimas = metricas_optimas,
    grafico_comparativo = p_comparison
  ))
}
resultado_completo <- run_complete_analysis(
  data = datos,
  top_ids = productos_ids,
  modelos_xgb = modelos_xgb_lista,
  modelos_precio_lista = modelos_precio_lista
)
## Estimando precios óptimos para producto: 155001 
## Estimando precios óptimos para producto: 3929788 
## Estimando precios óptimos para producto: 3904152 
## Estimando precios óptimos para producto: 155002 
## Estimando precios óptimos para producto: 3678055

9.0.7 Gráfico comparativo de precios óptimos por producto:

# Mostrar métricas si estás en modo interactivo
if (interactive()) View(resultado_completo$metricas_optimas)

cat("Gráfico comparativo de precios óptimos por producto:\n")
## Gráfico comparativo de precios óptimos por producto:
print(resultado_completo$grafico_comparativo)

10 Predicción de ventas con precios optimos por producto

cat("Gráficos individuales por producto:\n")
## Gráficos individuales por producto:
for (pid in names(resultado_completo$resultados)) {
  plots <- resultado_completo$resultados[[pid]]$integrated_plots
  if (!is.null(plots)) {
    cat(paste0("## Producto: ", pid, "\n\n"))
    print(plots$sales)   # solo usa Venta_XGBoost internamente
    print(plots$margins) # solo usa Margen_XGBoost internamente
    
    cat("\n---\n\n")
  }
}
## ## Producto: 155001

## 
## ---
## 
## ## Producto: 3929788

## 
## ---
## 
## ## Producto: 3904152

## 
## ---
## 
## ## Producto: 155002

## 
## ---
## 
## ## Producto: 3678055

## 
## ---

10.0.1 TABLA FINAL

tabla_final <- data.frame()

for (pid in productos_ids) {
  pred <- resultado_completo$integracion[[as.character(pid)]]
  real <- datos_filtrados %>% filter(ID_Inventario == pid)

  if (!is.null(pred) && nrow(pred) > 0) {
    precio_promedio <- mean(real$Precio_Final_Unitario, na.rm = TRUE)
    margen_historico_promedio <- mean(real$Venta - real$Costo_Venta, na.rm = TRUE)

    # Extraer mes de las fechas futuras simuladas
    pred$Mes_Simulado <- lubridate::month(pred$Fecha)

    # Calcular venta promedio histórica por mes
    venta_historica_por_mes <- real %>%
      mutate(Mes = lubridate::month(Trx_Fecha)) %>%
      group_by(Mes) %>%
      summarise(Venta_Historica_Promedio = mean(Venta, na.rm = TRUE), .groups = "drop")

    # Agregar columna de venta esperada histórica al df de predicción
    pred <- pred %>%
      left_join(venta_historica_por_mes, by = c("Mes_Simulado" = "Mes"))

    tabla_producto <- pred %>%
      select(Fecha, Precio_Final_Unitario, Venta_XGBoost, Margen_XGBoost, Venta_Historica_Promedio) %>%
      rename(
        Precio_Optimo = Precio_Final_Unitario,
        Venta_Esperada_XGBoost = Venta_XGBoost,
        Margen_XGBoost = Margen_XGBoost,
        Venta_Esperada_Historica = Venta_Historica_Promedio
      ) %>%
      mutate(
        ID_Inventario = pid,
        Precio_Promedio_Historico = round(precio_promedio, 2),
        Margen_Historico_Promedio = round(margen_historico_promedio, 2)
      ) %>%
      select(ID_Inventario, Fecha, Precio_Promedio_Historico, Precio_Optimo,
             Venta_Esperada_Historica, Venta_Esperada_XGBoost,
             Margen_Historico_Promedio, Margen_XGBoost)

    tabla_final <- bind_rows(tabla_final, tabla_producto)
  }
}

print(tabla_final)
##    ID_Inventario      Fecha Precio_Promedio_Historico Precio_Optimo
## 1         155001 2025-06-05                    467.28      630.0000
## 2         155001 2025-07-05                    467.28      630.0000
## 3         155001 2025-08-05                    467.28      630.0000
## 4         155001 2025-09-05                    467.28      630.0000
## 5         155001 2025-10-05                    467.28      630.0000
## 6         155001 2025-11-05                    467.28      630.0000
## 7        3929788 2025-06-05                     40.83       51.5872
## 8        3929788 2025-07-05                     40.83       51.5872
## 9        3929788 2025-08-05                     40.83       51.5872
## 10       3929788 2025-09-05                     40.83       51.5872
## 11       3929788 2025-10-05                     40.83       51.5872
## 12       3929788 2025-11-05                     40.83       51.5872
## 13       3904152 2025-06-05                   3157.60     3556.0000
## 14       3904152 2025-07-05                   3157.60     3556.0000
## 15       3904152 2025-08-05                   3157.60     3556.0000
## 16       3904152 2025-09-05                   3157.60     3556.0000
## 17       3904152 2025-10-05                   3157.60     3556.0000
## 18       3904152 2025-11-05                   3157.60     3556.0000
## 19        155002 2025-06-05                    458.03      593.6000
## 20        155002 2025-07-05                    458.03      593.6000
## 21        155002 2025-08-05                    458.03      593.6000
## 22        155002 2025-09-05                    458.03      593.6000
## 23        155002 2025-10-05                    458.03      593.6000
## 24        155002 2025-11-05                    458.03      593.6000
## 25       3678055 2025-06-05                   5316.65     5908.0000
## 26       3678055 2025-07-05                   5316.65     5908.0000
## 27       3678055 2025-08-05                   5316.65     5908.0000
## 28       3678055 2025-09-05                   5316.65     5908.0000
## 29       3678055 2025-10-05                   5316.65     5908.0000
## 30       3678055 2025-11-05                   5316.65     5908.0000
##    Venta_Esperada_Historica Venta_Esperada_XGBoost Margen_Historico_Promedio
## 1                  2773.289               1253.110                    354.20
## 2                  3824.150               1255.131                    354.20
## 3                  3309.438               1793.815                    354.20
## 4                  2906.493               1255.131                    354.20
## 5                  2853.639               1255.131                    354.20
## 6                  2933.349               1255.131                    354.20
## 7                  1658.104               1044.346                    358.30
## 8                  1796.298               1044.346                    358.30
## 9                  1715.712               1044.775                    358.30
## 10                 1568.125               1044.346                    358.30
## 11                 1647.976               1274.590                    358.30
## 12                 1481.254               1044.346                    358.30
## 13                 7427.639               3551.571                   1770.29
## 14                 6116.994               3554.225                   1770.29
## 15                 7902.450               3558.141                   1770.29
## 16                 7601.677               7108.436                   1770.29
## 17                 7653.351               3538.695                   1770.29
## 18                10330.334               3558.141                   1770.29
## 19                 3240.199               1703.100                    281.58
## 20                 3341.957               1703.100                    281.58
## 21                 2821.299               1703.100                    281.58
## 22                 2747.125               1099.524                    281.58
## 23                 2730.078               1099.524                    281.58
## 24                 2855.958               1099.524                    281.58
## 25                10800.705               5894.670                   2438.95
## 26                10692.688               5894.670                   2438.95
## 27                10281.788               5894.670                   2438.95
## 28                10671.964               5924.096                   2438.95
## 29                 9284.032               5915.545                   2438.95
## 30                10459.696               5897.285                   2438.95
##    Margen_XGBoost
## 1        551.2630
## 2        552.1520
## 3        789.1277
## 4        552.1520
## 5        552.1520
## 6        552.1520
## 7        431.6053
## 8        431.6053
## 9        431.7824
## 10       431.6053
## 11       526.7599
## 12       431.6053
## 13      1212.4252
## 14      1213.3312
## 15      1214.6681
## 16      2426.6575
## 17      1208.0296
## 18      1214.6681
## 19       669.6821
## 20       669.6821
## 21       669.6821
## 22       432.3477
## 23       432.3477
## 24       432.3477
## 25      1991.6554
## 26      1991.6554
## 27      1991.6554
## 28      2001.5978
## 29      1998.7085
## 30      1992.5389
LS0tCnRpdGxlOiAiPHNwYW4gc3R5bGU9J2NvbG9yOiBicm93bjsnPkVWSURFTkNJQSBOT1ZFTTwvc3Bhbj4iCmF1dGhvcjogIkVxdWlwbyA0IgpkYXRlOiAiMjAyNC0wMy0wNCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHRydWUgCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIHRoZW1lOiBqb3VybmFsCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKZWRpdG9yX3M6IAogIGNodW5rX291dHB1dF90eXBlOiBjb25zb2xlCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogY29uc29sZQotLS0KCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibGFjazsiPiAgTGlicmVyaWFzIDwvc3Bhbj4gCgoKYGBge3J9CiMgTGlicmVyw61hcyBuZWNlc2FyaWFzCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHJlYWR4bCkKbGlicmFyeShwdXJycikKbGlicmFyeShrbml0cikKI2luc3RhbGwucGFja2FnZXMoImthYmxlRXh0cmEiKQpsaWJyYXJ5KGthYmxlRXh0cmEpCmxpYnJhcnkoZ2dwbG90MikKI2luc3RhbGwucGFja2FnZXMoImlncmFwaCIpCmxpYnJhcnkoaWdyYXBoKQojaW5zdGFsbC5wYWNrYWdlcygiZm9yZWNhc3QiKQojaW5zdGFsbC5wYWNrYWdlcygibHVicmlkYXRlIikKbGlicmFyeShmb3JlY2FzdCkKbGlicmFyeShsdWJyaWRhdGUpCmxpYnJhcnkoY29ycnBsb3QpCmxpYnJhcnkoUkNvbG9yQnJld2VyKQojaW5zdGFsbC5wYWNrYWdlcygiZ2djb3JycGxvdCIpCmxpYnJhcnkoZ2djb3JycGxvdCkKbGlicmFyeShjYXJldCkKbGlicmFyeShjYXIpCmxpYnJhcnkocmFuZG9tRm9yZXN0KQojaW5zdGFsbC5wYWNrYWdlcygieGdib29zdCIpCmxpYnJhcnkoeGdib29zdCkKI2luc3RhbGwucGFja2FnZXMoInBhdGNod29yayIpCmxpYnJhcnkocGF0Y2h3b3JrKQpgYGAKCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibGFjazsiPiAgQ2FyZ2EgZGUgZGF0b3MgPC9zcGFuPiAKCmBgYHtyfQojIENhcmdhciBhcmNoaXZvIEV4Y2VsIGRlc2RlIHJ1dGEgbG9jYWwKcnV0YSA8LSAiL1VzZXJzL29zY2FyY2FzdGFuZWRhZ2FyY2lhL0Rvd25sb2Fkcy9JQSBjb24gaW1wYWN0byBlbXByZXNhcmlhbC9maWx0ZXJlZF9kYXRhLnhsc3giCmRhdG9zIDwtIHJlYWRfZXhjZWwocnV0YSkKCiMgVmlzdGEgZ2VuZXJhCmhlYWQoZGF0b3MpCnN0cihkYXRvcykKYGBgCgpgYGB7cn0KIyBPYnRlbmVyIGxvcyA1IHByb2R1Y3RvcyBtw6FzIHZlbmRpZG9zIChwb3IgdmFsb3IpCnRvcF9pZHMgPC0gZGF0b3MgJT4lCiAgZ3JvdXBfYnkoSURfSW52ZW50YXJpbykgJT4lCiAgc3VtbWFyaXNlKFZlbnRhc19Ub3RhbGVzID0gc3VtKFZlbnRhLCBuYS5ybSA9IFRSVUUpKSAlPiUKICBhcnJhbmdlKGRlc2MoVmVudGFzX1RvdGFsZXMpKSAlPiUKICBzbGljZV9oZWFkKG4gPSA1KSAlPiUKICBwdWxsKElEX0ludmVudGFyaW8pCgpwcmludCgiVG9wIDUgcHJvZHVjdG9zIG3DoXMgdmVuZGlkb3MgKElEX0ludmVudGFyaW8pOiIpCnByaW50KHRvcF9pZHMpCgpgYGAKCmBgYHtyfQojIEZpbHRyYXIgZGF0b3MgdsOhbGlkb3MKZGF0b3NfZmlsdHJhZG9zIDwtIGRhdG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvICVpbiUgdG9wX2lkcykgJT4lCiAgZmlsdGVyKCFpcy5uYShQcmVjaW9fRmluYWxfVW5pdGFyaW8pKQoKIyBDb250YXIgb2JzZXJ2YWNpb25lcyBwb3IgcHJvZHVjdG8KY29udGVvIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUKICBjb3VudChJRF9JbnZlbnRhcmlvLCBzb3J0ID0gVFJVRSkKCnByaW50KCJOw7ptZXJvIGRlIHJlZ2lzdHJvcyBwb3IgcHJvZHVjdG8gZW4gZGF0b3NfZmlsdHJhZG9zOiIpCnByaW50KGNvbnRlbykKCiMgVmVyaWZpY2Egc2kgaGF5IHN1ZmljaWVudGVzIGRhdG9zCmlmIChucm93KGRhdG9zX2ZpbHRyYWRvcykgPT0gMCkgewogIHN0b3AoIk5vIGhheSBkYXRvcyBzdWZpY2llbnRlcyBsdWVnbyBkZSBmaWx0cmFyIHBvciB0b3BfaWRzIHkgcHJlY2lvcyB2w6FsaWRvcy4iKQp9CgpgYGAKCmBgYHtyfQojIENvbWJpbmFjaW9uZXMgZGUgcGFyZXMKcHJvZHVjdG9zIDwtIHVuaXF1ZShkYXRvc19maWx0cmFkb3MkSURfSW52ZW50YXJpbykKcGFyZXNfcHJvZHVjdG9zIDwtIGNvbWJuKHByb2R1Y3RvcywgMiwgc2ltcGxpZnkgPSBGQUxTRSkKCiMgSW5pY2lhbGl6YXIgcmVzdWx0YWRvcwpyZXN1bHRhZG9zX2tzIDwtIG1hcF9kZihwYXJlc19wcm9kdWN0b3MsIGZ1bmN0aW9uKHBhcikgewogIHByb2QxIDwtIHBhclsxXQogIHByb2QyIDwtIHBhclsyXQogIAogIHByZWNpb3MxIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUKICAgIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IHByb2QxKSAlPiUKICAgIHB1bGwoUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKQogIAogIHByZWNpb3MyIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUKICAgIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IHByb2QyKSAlPiUKICAgIHB1bGwoUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKQogIAogIHByaW50KHBhc3RlKCJDb21wYXJhbmRvIHByb2R1Y3RvcyIsIHByb2QxLCAidnMiLCBwcm9kMikpCiAgcHJpbnQocGFzdGUoIkNhbnRpZGFkIGRlIHByZWNpb3M6IiwgbGVuZ3RoKHByZWNpb3MxKSwgInkiLCBsZW5ndGgocHJlY2lvczIpKSkKICAKICBpZiAobGVuZ3RoKHByZWNpb3MxKSA+PSA1ICYgbGVuZ3RoKHByZWNpb3MyKSA+PSA1KSB7CiAgICBwcnVlYmEgPC0gc3VwcHJlc3NXYXJuaW5ncyhrcy50ZXN0KHByZWNpb3MxLCBwcmVjaW9zMikpCiAgICBkYXRhLmZyYW1lKAogICAgICBQcm9kdWN0b18xID0gcHJvZDEsCiAgICAgIFByb2R1Y3RvXzIgPSBwcm9kMiwKICAgICAgRCA9IHJvdW5kKHBydWViYSRzdGF0aXN0aWMsIDQpLAogICAgICBwX3ZhbHVlID0gcm91bmQocHJ1ZWJhJHAudmFsdWUsIDQpLAogICAgICBDb25jbHVzaW9uID0gaWZlbHNlKHBydWViYSRwLnZhbHVlID4gMC4wNSwgIkRpc3RyaWJ1Y2lvbmVzIHNpbWlsYXJlcyIsICJEaXN0cmlidWNpb25lcyBkaWZlcmVudGVzIikKICAgICkKICB9IGVsc2UgewogICAgZGF0YS5mcmFtZSgKICAgICAgUHJvZHVjdG9fMSA9IHByb2QxLAogICAgICBQcm9kdWN0b18yID0gcHJvZDIsCiAgICAgIEQgPSBOQSwKICAgICAgcF92YWx1ZSA9IE5BLAogICAgICBDb25jbHVzaW9uID0gIkRhdG9zIGluc3VmaWNpZW50ZXMiCiAgICApCiAgfQp9KQoKcHJpbnQoIlJlc3VsdGFkb3MgZGUgbGEgcHJ1ZWJhIEtTOiIpCnByaW50KHJlc3VsdGFkb3Nfa3MpCgpgYGAKCmBgYHtyfQp0cnkoZGV2Lm9mZigpLCBzaWxlbnQgPSBUUlVFKQpgYGAKCgoKCmBgYHtyfQoKIyBGaWx0cmFyIGxvcyBwcm9kdWN0b3MKZGZfMTU1MDAxIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUKICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAxNTUwMDEpICU+JQogIHNlbGVjdChQcmVjaW9fRmluYWxfVW5pdGFyaW8pICU+JQogIG11dGF0ZShQcm9kdWN0byA9ICIxNTUwMDEiKQoKZGZfMTU1MDAyIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUKICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAxNTUwMDIpICU+JQogIHNlbGVjdChQcmVjaW9fRmluYWxfVW5pdGFyaW8pICU+JQogIG11dGF0ZShQcm9kdWN0byA9ICIxNTUwMDIiKQoKIyBVbmlyIGVuIHVuIHNvbG8gZGF0YWZyYW1lCmRmX2VjZGYgPC0gYmluZF9yb3dzKGRmXzE1NTAwMSwgZGZfMTU1MDAyKQoKIyBHcmFmaWNhciBFQ0RGCmdncGxvdChkZl9lY2RmLCBhZXMoeCA9IFByZWNpb19GaW5hbF9Vbml0YXJpbywgY29sb3IgPSBQcm9kdWN0bykpICsKICBzdGF0X2VjZGYoZ2VvbSA9ICJzdGVwIiwgc2l6ZSA9IDEpICsKICBsYWJzKHRpdGxlID0gIkVDREYgZGUgUHJlY2lvIEZpbmFsIFVuaXRhcmlvOiBQcm9kdWN0b3MgMTU1MDAxIHZzIDE1NTAwMiIsCiAgICAgICB4ID0gIlByZWNpbyBGaW5hbCBVbml0YXJpbyIsCiAgICAgICB5ID0gIkZ1bmNpw7NuIGRlIERpc3RyaWJ1Y2nDs24gQWN1bXVsYWRhIChFQ0RGKSIsCiAgICAgICBjb2xvciA9ICJQcm9kdWN0byIpICsKICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDE0KQoKYGBgCgoKPCEtLSBBUk1BIC0tPgojIEFSTUEgCgojIFBSRURJQ0NJT05FUyBERSBWRU5UQVMKCgo8IS0tIFBST0RVQ1RPIDE1NTAwMSAtLT4KIyMgUFJPRFVDVE8gMTU1MDAxCgpgYGB7ciBhcm1hLTE1NTAwMX0KIyBQcm9kdWN0byAxNTUwMDEKaWRfcHJvZCA8LSAxNTUwMDEKCiMgQ3JlYXIgbGEgc2VyaWUgZGUgdGllbXBvIG1lbnN1YWwKdmVudGFzX21lbnN1YWxlcyA8LSBkYXRvc19maWx0cmFkb3MgJT4lCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gaWRfcHJvZCkgJT4lCiAgbXV0YXRlKEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpKSAlPiUKICBncm91cF9ieShGZWNoYSkgJT4lCiAgc3VtbWFyaXNlKFZlbnRhID0gc3VtKFZlbnRhLCBuYS5ybSA9IFRSVUUpKSAlPiUKICBhcnJhbmdlKEZlY2hhKQoKc2VyaWVfdHMgPC0gdHModmVudGFzX21lbnN1YWxlcyRWZW50YSwgZnJlcXVlbmN5ID0gMTIsCiAgICAgICAgICAgICAgIHN0YXJ0ID0gYyh5ZWFyKG1pbih2ZW50YXNfbWVuc3VhbGVzJEZlY2hhKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgbW9udGgobWluKHZlbnRhc19tZW5zdWFsZXMkRmVjaGEpKSkpCgojIE1vZGVsbyBBUk1BCm1vZGVsb19hcm1hIDwtIGF1dG8uYXJpbWEoc2VyaWVfdHMsIHNlYXNvbmFsID0gRkFMU0UsIHN0ZXB3aXNlID0gRkFMU0UsIGFwcHJveGltYXRpb24gPSBGQUxTRSkKZm9yZWNhc3RfbW9kZWxvIDwtIGZvcmVjYXN0KG1vZGVsb19hcm1hLCBoID0gMykKCiMgR3LDoWZpY28gZGVsIHByb27Ds3N0aWNvCmF1dG9wbG90KGZvcmVjYXN0X21vZGVsbykgKwogIGxhYnModGl0bGUgPSBwYXN0ZSgiUHJvbsOzc3RpY28gbWVuc3VhbCBkZSB2ZW50YXMgLSBBUk1BIChQcm9kdWN0byIsIGlkX3Byb2QsICIpIiksCiAgICAgICB4ID0gIk1lcyIsIHkgPSAiVmVudGFzICgkKSIpICsKICB0aGVtZV9taW5pbWFsKCkKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzCmZpdHRlZF92YWx1ZXMgPC0gZml0dGVkKG1vZGVsb19hcm1hKQptYXBlIDwtIG1lYW4oYWJzKChzZXJpZV90cyAtIGZpdHRlZF92YWx1ZXMpIC8gcG1heChzZXJpZV90cywgMC4wMSkpKSAqIDEwMApybXNlIDwtIHNxcnQobWVhbigoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKV4yKSkKCgojIENyZWFyIHRhYmxhIGRlIG3DqXRyaWNhcwppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgewogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLAogICAgTUFQRSA9IG51bWVyaWMoKSwKICAgIFJNU0UgPSBudW1lcmljKCksCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCn0KCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSBpZF9wcm9kLAogIE1vZGVsbyA9ICJBUk1BIiwKICBNQVBFID0gbWFwZSwKICBSTVNFID0gcm1zZQopKQoKIyBNb3N0cmFyIHRhYmxhIHBhcmEgZXN0ZSBwcm9kdWN0bwp0YWlsKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgMSkgJT4lCiAga25pdHI6OmthYmxlKGNhcHRpb24gPSBwYXN0ZSgiTcOpdHJpY2FzIGRlbCBtb2RlbG8gQVJNQSBwYXJhIFByb2R1Y3RvIiwgaWRfcHJvZCkpICU+JQogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEZBTFNFKQpgYGAKCgo8IS0tIFBST0RVQ1RPIDM5Mjk3ODggLS0+CiMjIFBST0RVQ1RPIDM5Mjk3ODgKCmBgYHtyIGFybWEtMzkyOTc4OH0KIyBQcm9kdWN0byAzOTI5Nzg4CgppZF9wcm9kIDwtIDM5Mjk3ODgKCiMgQ3JlYXIgbGEgc2VyaWUgZGUgdGllbXBvIG1lbnN1YWwKdmVudGFzX21lbnN1YWxlcyA8LSBkYXRvc19maWx0cmFkb3MgJT4lCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gaWRfcHJvZCkgJT4lCiAgbXV0YXRlKEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpKSAlPiUKICBncm91cF9ieShGZWNoYSkgJT4lCiAgc3VtbWFyaXNlKFZlbnRhID0gc3VtKFZlbnRhLCBuYS5ybSA9IFRSVUUpKSAlPiUKICBhcnJhbmdlKEZlY2hhKQoKc2VyaWVfdHMgPC0gdHModmVudGFzX21lbnN1YWxlcyRWZW50YSwgZnJlcXVlbmN5ID0gMTIsCiAgICAgICAgICAgICAgIHN0YXJ0ID0gYyh5ZWFyKG1pbih2ZW50YXNfbWVuc3VhbGVzJEZlY2hhKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgbW9udGgobWluKHZlbnRhc19tZW5zdWFsZXMkRmVjaGEpKSkpCgojIE1vZGVsbyBBUk1BCm1vZGVsb19hcm1hIDwtIGF1dG8uYXJpbWEoc2VyaWVfdHMsIHNlYXNvbmFsID0gRkFMU0UsIHN0ZXB3aXNlID0gRkFMU0UsIGFwcHJveGltYXRpb24gPSBGQUxTRSkKZm9yZWNhc3RfbW9kZWxvIDwtIGZvcmVjYXN0KG1vZGVsb19hcm1hLCBoID0gMykKCiMgR3LDoWZpY28gZGVsIHByb27Ds3N0aWNvCmF1dG9wbG90KGZvcmVjYXN0X21vZGVsbykgKwogIGxhYnModGl0bGUgPSBwYXN0ZSgiUHJvbsOzc3RpY28gbWVuc3VhbCBkZSB2ZW50YXMgLSBBUk1BIChQcm9kdWN0byIsIGlkX3Byb2QsICIpIiksCiAgICAgICB4ID0gIk1lcyIsIHkgPSAiVmVudGFzICgkKSIpICsKICB0aGVtZV9taW5pbWFsKCkKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzCmZpdHRlZF92YWx1ZXMgPC0gZml0dGVkKG1vZGVsb19hcm1hKQptYXBlIDwtIG1lYW4oYWJzKChzZXJpZV90cyAtIGZpdHRlZF92YWx1ZXMpIC8gcG1heChzZXJpZV90cywgMC4wMSkpKSAqIDEwMAptc2UgPC0gbWVhbigoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKV4yKQoKIyBDcmVhciB0YWJsYSBkZSBtw6l0cmljYXMKaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1BUEUgPSBudW1lcmljKCksCiAgICBSTVNFID0gbnVtZXJpYygpLAogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKQp9CgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gaWRfcHJvZCwKICBNb2RlbG8gPSAiQVJNQSIsCiAgTUFQRSA9IG1hcGUsCiAgUk1TRSAgPSBybXNlCikpCgojIE1vc3RyYXIgdGFibGEgcGFyYSBlc3RlIHByb2R1Y3RvCnRhaWwobWV0cmljYXNfY29tcGFyYXRpdmFzLCAxKSAlPiUKICBrbml0cjo6a2FibGUoY2FwdGlvbiA9IHBhc3RlKCJNw6l0cmljYXMgZGVsIG1vZGVsbyBBUk1BIHBhcmEgUHJvZHVjdG8iLCBpZF9wcm9kKSkgJT4lCiAga2FibGVFeHRyYTo6a2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UpCmBgYAoKCjwhLS0gUFJPRFVDVE8gMzkwNDE1MiAtLT4KIyMgUFJPRFVDVE8gMzkwNDE1MgoKYGBge3IgYXJtYS0zOTA0MTUyfQojIFByb2R1Y3RvIDM5MDQxNTIKaWRfcHJvZCA8LSAzOTA0MTUyCgojIENyZWFyIGxhIHNlcmllIGRlIHRpZW1wbyBtZW5zdWFsCnZlbnRhc19tZW5zdWFsZXMgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IGlkX3Byb2QpICU+JQogIG11dGF0ZShGZWNoYSA9IGFzLkRhdGUoZmxvb3JfZGF0ZShUcnhfRmVjaGEsICJtb250aCIpKSkgJT4lCiAgZ3JvdXBfYnkoRmVjaGEpICU+JQogIHN1bW1hcmlzZShWZW50YSA9IHN1bShWZW50YSwgbmEucm0gPSBUUlVFKSkgJT4lCiAgYXJyYW5nZShGZWNoYSkKCnNlcmllX3RzIDwtIHRzKHZlbnRhc19tZW5zdWFsZXMkVmVudGEsIGZyZXF1ZW5jeSA9IDEyLAogICAgICAgICAgICAgICBzdGFydCA9IGMoeWVhcihtaW4odmVudGFzX21lbnN1YWxlcyRGZWNoYSkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIG1vbnRoKG1pbih2ZW50YXNfbWVuc3VhbGVzJEZlY2hhKSkpKQoKIyBNb2RlbG8gQVJNQQptb2RlbG9fYXJtYSA8LSBhdXRvLmFyaW1hKHNlcmllX3RzLCBzZWFzb25hbCA9IEZBTFNFLCBzdGVwd2lzZSA9IEZBTFNFLCBhcHByb3hpbWF0aW9uID0gRkFMU0UpCmZvcmVjYXN0X21vZGVsbyA8LSBmb3JlY2FzdChtb2RlbG9fYXJtYSwgaCA9IDMpCgojIEdyw6FmaWNvIGRlbCBwcm9uw7NzdGljbwphdXRvcGxvdChmb3JlY2FzdF9tb2RlbG8pICsKICBsYWJzKHRpdGxlID0gcGFzdGUoIlByb27Ds3N0aWNvIG1lbnN1YWwgZGUgdmVudGFzIC0gQVJNQSAoUHJvZHVjdG8iLCBpZF9wcm9kLCAiKSIpLAogICAgICAgeCA9ICJNZXMiLCB5ID0gIlZlbnRhcyAoJCkiKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIENhbGN1bGFyIG3DqXRyaWNhcwpmaXR0ZWRfdmFsdWVzIDwtIGZpdHRlZChtb2RlbG9fYXJtYSkKbWFwZSA8LSBtZWFuKGFicygoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKSAvIHBtYXgoc2VyaWVfdHMsIDAuMDEpKSkgKiAxMDAKcm1zZSA8LSBzcXJ0KG1lYW4oKHNlcmllX3RzIC0gZml0dGVkX3ZhbHVlcyleMikpCgoKIyBDcmVhciB0YWJsYSBkZSBtw6l0cmljYXMKaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1BUEUgPSBudW1lcmljKCksCiAgICBSTVNFID0gbnVtZXJpYygpLAogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKQp9CgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gaWRfcHJvZCwKICBNb2RlbG8gPSAiQVJNQSIsCiAgTUFQRSA9IG1hcGUsCiAgUk1TRSA9IHJtc2UKKSkKCiMgTW9zdHJhciB0YWJsYSBwYXJhIGVzdGUgcHJvZHVjdG8KdGFpbChtZXRyaWNhc19jb21wYXJhdGl2YXMsIDEpICU+JQogIGtuaXRyOjprYWJsZShjYXB0aW9uID0gcGFzdGUoIk3DqXRyaWNhcyBkZWwgbW9kZWxvIEFSTUEgcGFyYSBQcm9kdWN0byIsIGlkX3Byb2QpKSAlPiUKICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGQUxTRSkKYGBgCgoKPCEtLSBQUk9EVUNUTyAxNTUwMDIgLS0+CiMjIFBST0RVQ1RPIDE1NTAwMgoKYGBge3IgYXJtYS0xNTUwMDJ9CiMgUHJvZHVjdG8gMTU1MDAyCmlkX3Byb2QgPC0gMTU1MDAyCgojIENyZWFyIGxhIHNlcmllIGRlIHRpZW1wbyBtZW5zdWFsCnZlbnRhc19tZW5zdWFsZXMgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IGlkX3Byb2QpICU+JQogIG11dGF0ZShGZWNoYSA9IGFzLkRhdGUoZmxvb3JfZGF0ZShUcnhfRmVjaGEsICJtb250aCIpKSkgJT4lCiAgZ3JvdXBfYnkoRmVjaGEpICU+JQogIHN1bW1hcmlzZShWZW50YSA9IHN1bShWZW50YSwgbmEucm0gPSBUUlVFKSkgJT4lCiAgYXJyYW5nZShGZWNoYSkKCnNlcmllX3RzIDwtIHRzKHZlbnRhc19tZW5zdWFsZXMkVmVudGEsIGZyZXF1ZW5jeSA9IDEyLAogICAgICAgICAgICAgICBzdGFydCA9IGMoeWVhcihtaW4odmVudGFzX21lbnN1YWxlcyRGZWNoYSkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIG1vbnRoKG1pbih2ZW50YXNfbWVuc3VhbGVzJEZlY2hhKSkpKQoKIyBNb2RlbG8gQVJNQQptb2RlbG9fYXJtYSA8LSBhdXRvLmFyaW1hKHNlcmllX3RzLCBzZWFzb25hbCA9IEZBTFNFLCBzdGVwd2lzZSA9IEZBTFNFLCBhcHByb3hpbWF0aW9uID0gRkFMU0UpCmZvcmVjYXN0X21vZGVsbyA8LSBmb3JlY2FzdChtb2RlbG9fYXJtYSwgaCA9IDMpCgojIEdyw6FmaWNvIGRlbCBwcm9uw7NzdGljbwphdXRvcGxvdChmb3JlY2FzdF9tb2RlbG8pICsKICBsYWJzKHRpdGxlID0gcGFzdGUoIlByb27Ds3N0aWNvIG1lbnN1YWwgZGUgdmVudGFzIC0gQVJNQSAoUHJvZHVjdG8iLCBpZF9wcm9kLCAiKSIpLAogICAgICAgeCA9ICJNZXMiLCB5ID0gIlZlbnRhcyAoJCkiKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIENhbGN1bGFyIG3DqXRyaWNhcwpmaXR0ZWRfdmFsdWVzIDwtIGZpdHRlZChtb2RlbG9fYXJtYSkKbWFwZSA8LSBtZWFuKGFicygoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKSAvIHBtYXgoc2VyaWVfdHMsIDAuMDEpKSkgKiAxMDAKcm1zZSA8LSBzcXJ0KG1lYW4oKHNlcmllX3RzIC0gZml0dGVkX3ZhbHVlcyleMikpCgoKIyBDcmVhciB0YWJsYSBkZSBtw6l0cmljYXMKaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1BUEUgPSBudW1lcmljKCksCiAgICBSTVNFID0gbnVtZXJpYygpLAogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKQp9CgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gaWRfcHJvZCwKICBNb2RlbG8gPSAiQVJNQSIsCiAgTUFQRSA9IG1hcGUsCiAgUk1TRSA9IHJtc2UKKSkKCiMgTW9zdHJhciB0YWJsYSBwYXJhIGVzdGUgcHJvZHVjdG8KdGFpbChtZXRyaWNhc19jb21wYXJhdGl2YXMsIDEpICU+JQogIGtuaXRyOjprYWJsZShjYXB0aW9uID0gcGFzdGUoIk3DqXRyaWNhcyBkZWwgbW9kZWxvIEFSTUEgcGFyYSBQcm9kdWN0byIsIGlkX3Byb2QpKSAlPiUKICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGQUxTRSkKYGBgCgoKPCEtLVByb2R1Y3RvIDM2NzgwNTUgLS0+CiMjIFBST0RVQ1RPIDM2NzgwNTUKYGBge3IgYXJtYS0zNjc4MDU1fQojIFByb2R1Y3RvIDM2NzgwNTUKaWRfcHJvZCA8LSAzNjc4MDU1CgojIENyZWFyIGxhIHNlcmllIGRlIHRpZW1wbyBtZW5zdWFsCnZlbnRhc19tZW5zdWFsZXMgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IGlkX3Byb2QpICU+JQogIG11dGF0ZShGZWNoYSA9IGFzLkRhdGUoZmxvb3JfZGF0ZShUcnhfRmVjaGEsICJtb250aCIpKSkgJT4lCiAgZ3JvdXBfYnkoRmVjaGEpICU+JQogIHN1bW1hcmlzZShWZW50YSA9IHN1bShWZW50YSwgbmEucm0gPSBUUlVFKSkgJT4lCiAgYXJyYW5nZShGZWNoYSkKCnNlcmllX3RzIDwtIHRzKHZlbnRhc19tZW5zdWFsZXMkVmVudGEsIGZyZXF1ZW5jeSA9IDEyLAogICAgICAgICAgICAgICBzdGFydCA9IGMoeWVhcihtaW4odmVudGFzX21lbnN1YWxlcyRGZWNoYSkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIG1vbnRoKG1pbih2ZW50YXNfbWVuc3VhbGVzJEZlY2hhKSkpKQoKIyBNb2RlbG8gQVJNQQptb2RlbG9fYXJtYSA8LSBhdXRvLmFyaW1hKHNlcmllX3RzLCBzZWFzb25hbCA9IEZBTFNFLCBzdGVwd2lzZSA9IEZBTFNFLCBhcHByb3hpbWF0aW9uID0gRkFMU0UpCmZvcmVjYXN0X21vZGVsbyA8LSBmb3JlY2FzdChtb2RlbG9fYXJtYSwgaCA9IDMpCgojIEdyw6FmaWNvIGRlbCBwcm9uw7NzdGljbwphdXRvcGxvdChmb3JlY2FzdF9tb2RlbG8pICsKICBsYWJzKHRpdGxlID0gcGFzdGUoIlByb27Ds3N0aWNvIG1lbnN1YWwgZGUgdmVudGFzIC0gQVJNQSAoUHJvZHVjdG8iLCBpZF9wcm9kLCAiKSIpLAogICAgICAgeCA9ICJNZXMiLCB5ID0gIlZlbnRhcyAoJCkiKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIENhbGN1bGFyIG3DqXRyaWNhcwpmaXR0ZWRfdmFsdWVzIDwtIGZpdHRlZChtb2RlbG9fYXJtYSkKbWFwZSA8LSBtZWFuKGFicygoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKSAvIHBtYXgoc2VyaWVfdHMsIDAuMDEpKSkgKiAxMDAKcm1zZSA8LSBzcXJ0KG1lYW4oKHNlcmllX3RzIC0gZml0dGVkX3ZhbHVlcyleMikpCgojIENyZWFyIHRhYmxhIGRlIG3DqXRyaWNhcwppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgewogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLAogICAgTUFQRSA9IG51bWVyaWMoKSwKICAgIFJNU0UgPSBudW1lcmljKCksCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCn0KCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSBpZF9wcm9kLAogIE1vZGVsbyA9ICJBUk1BIiwKICBNQVBFID0gbWFwZSwKICBSTVNFID0gcm1zZQopKQoKIyBNb3N0cmFyIHRhYmxhIHBhcmEgZXN0ZSBwcm9kdWN0bwp0YWlsKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgMSkgJT4lCiAga25pdHI6OmthYmxlKGNhcHRpb24gPSBwYXN0ZSgiTcOpdHJpY2FzIGRlbCBtb2RlbG8gQVJNQSBwYXJhIFByb2R1Y3RvIiwgaWRfcHJvZCkpICU+JQogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEZBTFNFKQpgYGAKCgojIFJFR1JFU0lPTiBMSU5FQUwKIyMgTUFQQSBERSBDQUxPUgoKYGBge3IgbWFwYV9jYWxvcl9jb3JyZWxhY2lvbiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBWYXJpYWJsZXMgbnVtw6lyaWNhcyByZWxldmFudGVzCnZhcnNfbnVtZXJpY2FzIDwtIGMoIkNhbnQiLCAiVmVudGEiLCAiQ29zdG9fVmVudGEiLAogICAgICAgICAgICAgICAgICAgICJQcmVjaW9fRmluYWxfVW5pdGFyaW8iLCAiRGVzY3VlbnRvX1BvcmNlbnRhamUiKQoKIyBQcmVwYXJhY2nDs24gZGUgbG9zIGRhdG9zCmRhdG9zX2NvciA8LSBkYXRvc19maWx0cmFkb3MgJT4lCiAgc2VsZWN0KGFsbF9vZih2YXJzX251bWVyaWNhcykpICU+JQogIG5hLm9taXQoKQoKIyBHZW5lcmFyIGxhIG1hdHJpeiBkZSBjb3JyZWxhY2nDs24KbWF0cml6X2NvciA8LSBjb3IoZGF0b3NfY29yKQoKIyBBanVzdGUgZGVsIGdyw6FmaWNvIHNpbiBtYXIKZ2djb3JycGxvdChtYXRyaXpfY29yLAogICAgICAgICAgIG1ldGhvZCA9ICJzcXVhcmUiLAogICAgICAgICAgIHR5cGUgPSAidXBwZXIiLAogICAgICAgICAgIGxhYiA9IFRSVUUsIAogICAgICAgICAgIGxhYl9zaXplID0gMiwgICAgICAgICAgICAgICAgICAgIyBNZWpvciB0YW1hw7FvIGRlIGxvcyBjb2VmaWNpZW50ZXMKICAgICAgICAgICB0bC5jZXggPSAxMCwgICAgICAgICAgICAgICAgICAgICMgVGFtYcOxbyBkZSBldGlxdWV0YXMgbcOhcyBncmFuZGUKICAgICAgICAgICB0bC5zcnQgPSA0NSwgICAgICAgICAgICAgICAgICAgICMgUm90YWNpw7NuIGRlIDQ1wrAgZGUgZXRpcXVldGFzCiAgICAgICAgICAgY29sb3JzID0gYygiIzZEOUVDMSIsICJ3aGl0ZSIsICIjRTQ2NzI2IiksCiAgICAgICAgICAgdGl0bGUgPSAiTWFwYSBkZSBDb3JyZWxhY2nDs24gLSBWYXJpYWJsZXMgTnVtw6lyaWNhcyIsCiAgICAgICAgICAgZ2d0aGVtZSA9IHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTQpICsKICAgICAgICAgICAgIHRoZW1lKAogICAgICAgICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLAogICAgICAgICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDAsIGhqdXN0ID0gMSkpCikKYGBgCgojIyBQUk9EVUNUTyAxNTUwMDEKYGBge3J9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkobHVicmlkYXRlKQoKIyBQYXNvIDE6IEZpbHRyYXIsIHNlbGVjY2lvbmFyIHkgY3JlYXIgZmVjaGEgbWVuc3VhbApkYXRvc18xNTUwMDEgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDE1NTAwMSkgJT4lCiAgc2VsZWN0KFZlbnRhLCBDYW50LCBDb3N0b19WZW50YSwKICAgICAgICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCBEZXNjdWVudG9fUG9yY2VudGFqZSwgVHJ4X0ZlY2hhKSAlPiUKICBtdXRhdGUoRmVjaGEgPSBhcy5EYXRlKGZsb29yX2RhdGUoVHJ4X0ZlY2hhLCAibW9udGgiKSkpICU+JQogIGdyb3VwX2J5KEZlY2hhKSAlPiUKICBzdW1tYXJpc2UoCiAgICBWZW50YSA9IHN1bShWZW50YSwgbmEucm0gPSBUUlVFKSwKICAgIENhbnQgPSBzdW0oQ2FudCwgbmEucm0gPSBUUlVFKSwKICAgIENvc3RvX1ZlbnRhID0gc3VtKENvc3RvX1ZlbnRhLCBuYS5ybSA9IFRSVUUpLAogICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvID0gbWVhbihQcmVjaW9fRmluYWxfVW5pdGFyaW8sIG5hLnJtID0gVFJVRSksCiAgICBEZXNjdWVudG9fUG9yY2VudGFqZSA9IG1lYW4oRGVzY3VlbnRvX1BvcmNlbnRhamUsIG5hLnJtID0gVFJVRSkKICApICU+JQogIGFycmFuZ2UoRmVjaGEpICU+JQogICMgUGFzbyAyOiBDcmVhciB2YXJpYWJsZSBkZSB0aWVtcG8geSBhcGxpY2FyIGxhZyBzb2JyZXNjcmliaWVuZG8gdmFyaWFibGVzCiAgbXV0YXRlKAogICAgVGllbXBvID0gYXMubnVtZXJpYyhGZWNoYSAtIG1pbihGZWNoYSkpIC8gMzAsCiAgICBWZW50YSA9IFZlbnRhLAogICAgQ2FudCA9IGxhZyhDYW50LCAxKSwKICAgIENvc3RvX1ZlbnRhID0gbGFnKENvc3RvX1ZlbnRhLCAxKSwKICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbyA9IGxhZyhQcmVjaW9fRmluYWxfVW5pdGFyaW8sIDEpLAogICAgRGVzY3VlbnRvX1BvcmNlbnRhamUgPSBsYWcoRGVzY3VlbnRvX1BvcmNlbnRhamUsIDEpCiAgKSAlPiUKICBkcm9wX25hKCkKCiMgUGFzbyAzOiBRdWl0YXIgZWwgbWVzIG3DoXMgcmVjaWVudGUKZGF0b3NfMTU1MDAxIDwtIGRhdG9zXzE1NTAwMSAlPiUKICBmaWx0ZXIoRmVjaGEgPCBtYXgoRmVjaGEpKQoKYGBgCgpgYGB7cn0KIyBBanVzdGFyIGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbAptb2RlbG9fcmVncmVzaW9uXzE1NTAwMSA8LSBsbShWZW50YSB+IENhbnQgKyBDb3N0b19WZW50YSArCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbyArIERlc2N1ZW50b19Qb3JjZW50YWplICsgVGllbXBvLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkYXRvc18xNTUwMDEpCgojIFZlciByZXN1bWVuIGRlbCBtb2RlbG8Kc3VtbWFyeShtb2RlbG9fcmVncmVzaW9uXzE1NTAwMSkKYGBgCgpgYGB7cn0KIyBBanVzdGUgZGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbAptb2RlbG9fcmVncmVzaW9uXzE1NTAwMSA8LSBsbShWZW50YSB+IENhbnQgKyBDb3N0b19WZW50YSArCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbyArIERlc2N1ZW50b19Qb3JjZW50YWplICsgVGllbXBvLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkYXRvc18xNTUwMDEpCgojIFByZWRpY2Npb25lcyB1c2FuZG8gZWwgbW9kZWxvIGFqdXN0YWRvCnByZWRpY2Npb25lc18xNTUwMDEgPC0gcHJlZGljdChtb2RlbG9fcmVncmVzaW9uXzE1NTAwMSwgbmV3ZGF0YSA9IGRhdG9zXzE1NTAwMSkKCiMgQ2FsY3VsYXIgTUFQRSAoTWVhbiBBYnNvbHV0ZSBQZXJjZW50YWdlIEVycm9yKQptYXBlXzE1NTAwMSA8LSBtZWFuKGFicygoZGF0b3NfMTU1MDAxJFZlbnRhIC0gcHJlZGljY2lvbmVzXzE1NTAwMSkgLyBkYXRvc18xNTUwMDEkVmVudGEpKSAqIDEwMAoKCiMgQ2FsY3VsYXIgUk1TRSAoUm9vdCBNZWFuIFNxdWFyZWQgRXJyb3IpCnJtc2VfMTU1MDAxIDwtIHNxcnQobWVhbigoZGF0b3NfMTU1MDAxJFZlbnRhIC0gcHJlZGljY2lvbmVzXzE1NTAwMSleMikpCgoKCiMgTW9zdHJhciBsYXMgbcOpdHJpY2FzCmNhdCgiTUFQRSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgMTU1MDAxOiAiLCBtYXBlXzE1NTAwMSwgIlxuIikKY2F0KCJSTVNFIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAxNTUwMDE6ICIsIHJtc2VfMTU1MDAxLCAiXG4iKQoKIyBEaWFnbsOzc3RpY28gZGUgcmVzaWR1b3MgZGVsIG1vZGVsbwpwYXIobWZyb3cgPSBjKDIsIDIpKQpwbG90KG1vZGVsb19yZWdyZXNpb25fMTU1MDAxKQpgYGAKYGBge3J9CiMgR3VhcmRhciBtw6l0cmljYXMgZGUgUmVncmVzacOzbiBMaW5lYWwgcGFyYSBwcm9kdWN0byAxNTUwMDEKaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwKICAgIFJNU0UgPSBudW1lcmljKCksCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCn0KCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMTU1MDAxIiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvCiAgTW9kZWxvID0gIlJlZ3Jlc2nDs24gTGluZWFsIiwKICBNQVBFID0gbWFwZV8xNTUwMDEsCiAgUk1TRSA9IHJtc2VfMTU1MDAxCgopKQpgYGAKCgojIyBQUk9EVUNUTyAzOTI5Nzg4CgpgYGB7cn0KbGlicmFyeShkcGx5cikKbGlicmFyeShsdWJyaWRhdGUpCgojIFBhc28gMTogRmlsdHJhciwgc2VsZWNjaW9uYXIgeSBjcmVhciBmZWNoYSBtZW5zdWFsCmRhdG9zXzM5Mjk3ODggPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDM5Mjk3ODgpICU+JQogIHNlbGVjdChWZW50YSwgQ2FudCwgQ29zdG9fVmVudGEsCiAgICAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbywgRGVzY3VlbnRvX1BvcmNlbnRhamUsIFRyeF9GZWNoYSkgJT4lCiAgbmEub21pdCgpICU+JQogIG11dGF0ZShGZWNoYSA9IGFzLkRhdGUoZmxvb3JfZGF0ZShUcnhfRmVjaGEsICJtb250aCIpKSkgJT4lCiAgZ3JvdXBfYnkoRmVjaGEpICU+JQogIHN1bW1hcmlzZSgKICAgIFZlbnRhID0gc3VtKFZlbnRhLCBuYS5ybSA9IFRSVUUpLAogICAgQ2FudCA9IHN1bShDYW50LCBuYS5ybSA9IFRSVUUpLAogICAgQ29zdG9fVmVudGEgPSBzdW0oQ29zdG9fVmVudGEsIG5hLnJtID0gVFJVRSksCiAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gPSBtZWFuKFByZWNpb19GaW5hbF9Vbml0YXJpbywgbmEucm0gPSBUUlVFKSwKICAgIERlc2N1ZW50b19Qb3JjZW50YWplID0gbWVhbihEZXNjdWVudG9fUG9yY2VudGFqZSwgbmEucm0gPSBUUlVFKQogICkgJT4lCiAgYXJyYW5nZShGZWNoYSkgJT4lCiAgIyBQYXNvIDI6IEFwbGljYXIgbGFnIHNvYnJlc2NyaWJpZW5kbyBsb3Mgbm9tYnJlcyBvcmlnaW5hbGVzIHkgY3JlYXIgdmFyaWFibGUgVGllbXBvCiAgbXV0YXRlKAogICAgVGllbXBvID0gYXMubnVtZXJpYyhGZWNoYSAtIG1pbihGZWNoYSkpIC8gMzAsCiAgICBDYW50ID0gbGFnKENhbnQsIDEpLAogICAgQ29zdG9fVmVudGEgPSBsYWcoQ29zdG9fVmVudGEsIDEpLAogICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvID0gbGFnKFByZWNpb19GaW5hbF9Vbml0YXJpbywgMSksCiAgICBEZXNjdWVudG9fUG9yY2VudGFqZSA9IGxhZyhEZXNjdWVudG9fUG9yY2VudGFqZSwgMSkKICApICU+JQogIGRyb3BfbmEoKQoKIyBQYXNvIDM6IFF1aXRhciBlbCBtZXMgbcOhcyByZWNpZW50ZQpkYXRvc18zOTI5Nzg4IDwtIGRhdG9zXzM5Mjk3ODggJT4lCiAgZmlsdGVyKEZlY2hhIDwgbWF4KEZlY2hhKSkKCiMgVmVyaWZpY2FyIGxhcyBwcmltZXJhcyBmaWxhcwpoZWFkKGRhdG9zXzM5Mjk3ODgpCgpgYGAKCmBgYHtyfQojIEFqdXN0YXIgZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsCm1vZGVsb19yZWdyZXNpb25fMzkyOTc4OCA8LSBsbShWZW50YSB+IENhbnQgKyBDb3N0b19WZW50YSArCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbyArIERlc2N1ZW50b19Qb3JjZW50YWplICsgVGllbXBvLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGF0b3NfMzkyOTc4OCkKCiMgVmVyIHJlc3VtZW4gZGVsIG1vZGVsbwpzdW1tYXJ5KG1vZGVsb19yZWdyZXNpb25fMzkyOTc4OCkKYGBgCgpgYGB7cn0KIyBQcmVkaWNjaW9uZXMgdXNhbmRvIGVsIG1vZGVsbyBhanVzdGFkbwpwcmVkaWNjaW9uZXNfMzkyOTc4OCA8LSBwcmVkaWN0KG1vZGVsb19yZWdyZXNpb25fMzkyOTc4OCwgbmV3ZGF0YSA9IGRhdG9zXzM5Mjk3ODgpCgojIENhbGN1bGFyIE1BUEUgKE1lYW4gQWJzb2x1dGUgUGVyY2VudGFnZSBFcnJvcikKIyBBw7FhZGltb3MgcHJvdGVjY2nDs24gY29udHJhIGRpdmlzacOzbiBwb3IgY2VybwptYXBlXzM5Mjk3ODggPC0gbWVhbihhYnMoKGRhdG9zXzM5Mjk3ODgkVmVudGEgLSBwcmVkaWNjaW9uZXNfMzkyOTc4OCkgLyBwbWF4KGRhdG9zXzM5Mjk3ODgkVmVudGEsIDAuMDEpKSkgKiAxMDAKCiMgQ2FsY3VsYXIgTVNFIChNZWFuIFNxdWFyZWQgRXJyb3IpCnJtc2VfMzkyOTc4OCA8LSBzcXJ0KG1lYW4oKGRhdG9zXzM5Mjk3ODgkVmVudGEgLSBwcmVkaWNjaW9uZXNfMzkyOTc4OCleMikpCgoKIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMKY2F0KCJNQVBFIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAzOTI5Nzg4OiAiLCBtYXBlXzM5Mjk3ODgsICJcbiIpCmNhdCgiUk1TRSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgMzkyOTc4ODogIiwgcm1zZV8zOTI5Nzg4LCAiXG4iKQoKIyBEaWFnbsOzc3RpY28gZGUgcmVzaWR1b3MgZGVsIG1vZGVsbwpwYXIobWZyb3cgPSBjKDIsIDIpKQpwbG90KG1vZGVsb19yZWdyZXNpb25fMzkyOTc4OCkKYGBgCmBgYHtyfQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFJlZ3Jlc2nDs24gTGluZWFsIHBhcmEgcHJvZHVjdG8gMTU1MDAxCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7CiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLAogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksCiAgICBNQVBFID0gbnVtZXJpYygpLAogICAgUk1TRSA9IG51bWVyaWMoKSwKICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQogICkKfQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIzOTI5Nzg4IiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvCiAgTW9kZWxvID0gIlJlZ3Jlc2nDs24gTGluZWFsIiwKICBNQVBFID0gbWFwZV8zOTI5Nzg4LAogIFJNU0UgPSBybXNlXzM5Mjk3ODgKKSkKYGBgCgoKIyMgUFJPRFVDVE8gMzkwNDE1MgoKYGBge3J9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkobHVicmlkYXRlKQoKIyBQYXNvIDE6IEZpbHRyYXIsIHNlbGVjY2lvbmFyIHkgcHJlcGFyYXIgZmVjaGEgbWVuc3VhbApkYXRvc18zOTA0MTUyIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUKICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAzOTA0MTUyKSAlPiUKICBzZWxlY3QoVmVudGEsIENhbnQsIENvc3RvX1ZlbnRhLAogICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8sIERlc2N1ZW50b19Qb3JjZW50YWplLCBUcnhfRmVjaGEpICU+JQogIG5hLm9taXQoKSAlPiUKICBtdXRhdGUoRmVjaGEgPSBhcy5EYXRlKGZsb29yX2RhdGUoVHJ4X0ZlY2hhLCAibW9udGgiKSkpICU+JQogIGdyb3VwX2J5KEZlY2hhKSAlPiUKICBzdW1tYXJpc2UoCiAgICBWZW50YSA9IHN1bShWZW50YSwgbmEucm0gPSBUUlVFKSwKICAgIENhbnQgPSBzdW0oQ2FudCwgbmEucm0gPSBUUlVFKSwKICAgIENvc3RvX1ZlbnRhID0gc3VtKENvc3RvX1ZlbnRhLCBuYS5ybSA9IFRSVUUpLAogICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvID0gbWVhbihQcmVjaW9fRmluYWxfVW5pdGFyaW8sIG5hLnJtID0gVFJVRSksCiAgICBEZXNjdWVudG9fUG9yY2VudGFqZSA9IG1lYW4oRGVzY3VlbnRvX1BvcmNlbnRhamUsIG5hLnJtID0gVFJVRSkKICApICU+JQogIGFycmFuZ2UoRmVjaGEpICU+JQogICMgUGFzbyAyOiBDcmVhciB2YXJpYWJsZSBkZSB0aWVtcG8geSBhcGxpY2FyIGxhZyBzb2JyZXNjcmliaWVuZG8gbG9zIG5vbWJyZXMKICBtdXRhdGUoCiAgICBUaWVtcG8gPSBhcy5udW1lcmljKEZlY2hhIC0gbWluKEZlY2hhKSkgLyAzMCwKICAgIENhbnQgPSBsYWcoQ2FudCwgMSksCiAgICBDb3N0b19WZW50YSA9IGxhZyhDb3N0b19WZW50YSwgMSksCiAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gPSBsYWcoUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCAxKSwKICAgIERlc2N1ZW50b19Qb3JjZW50YWplID0gbGFnKERlc2N1ZW50b19Qb3JjZW50YWplLCAxKQogICkgJT4lCiAgZHJvcF9uYSgpCgojIFBhc28gMzogRWxpbWluYXIgZWwgbWVzIG3DoXMgcmVjaWVudGUKZGF0b3NfMzkwNDE1MiA8LSBkYXRvc18zOTA0MTUyICU+JQogIGZpbHRlcihGZWNoYSA8IG1heChGZWNoYSkpCgojIFZlcmlmaWNhciByZXN1bHRhZG8KaGVhZChkYXRvc18zOTA0MTUyKQoKYGBgCgpgYGB7cn0KIyBBanVzdGFyIGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbAptb2RlbG9fcmVncmVzaW9uXzM5MDQxNTIgPC0gbG0oVmVudGEgfiBDYW50ICsgQ29zdG9fVmVudGEgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gKyBEZXNjdWVudG9fUG9yY2VudGFqZSArIFRpZW1wbywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGF0b3NfMzkwNDE1MikKCiMgVmVyIHJlc3VtZW4gZGVsIG1vZGVsbwpzdW1tYXJ5KG1vZGVsb19yZWdyZXNpb25fMzkwNDE1MikKYGBgCgpgYGB7cn0KIyBQcmVkaWNjaW9uZXMgdXNhbmRvIGVsIG1vZGVsbyBhanVzdGFkbwpwcmVkaWNjaW9uZXNfMzkwNDE1MiA8LSBwcmVkaWN0KG1vZGVsb19yZWdyZXNpb25fMzkwNDE1MiwgbmV3ZGF0YSA9IGRhdG9zXzM5MDQxNTIpCgojIENhbGN1bGFyIE1BUEUgKE1lYW4gQWJzb2x1dGUgUGVyY2VudGFnZSBFcnJvcikKIyBBw7FhZGltb3MgcHJvdGVjY2nDs24gY29udHJhIGRpdmlzacOzbiBwb3IgY2VybwptYXBlXzM5MDQxNTIgPC0gbWVhbihhYnMoKGRhdG9zXzM5MDQxNTIkVmVudGEgLSBwcmVkaWNjaW9uZXNfMzkwNDE1MikgLyBwbWF4KGRhdG9zXzM5MDQxNTIkVmVudGEsIDAuMDEpKSkgKiAxMDAKCiMgQ2FsY3VsYXIgTVNFIChNZWFuIFNxdWFyZWQgRXJyb3IpCnJtc2VfMzkwNDE1MiA8LSBzcXJ0KG1lYW4oKGRhdG9zXzM5MDQxNTIkVmVudGEgLSBwcmVkaWNjaW9uZXNfMzkwNDE1MileMikpCgoKIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMKY2F0KCJNQVBFIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAzOTA0MTUyOiAiLCBtYXBlXzM5MDQxNTIsICJcbiIpCmNhdCgiUk1TRSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgMzkwNDE1MjogIiwgcm1zZV8zOTA0MTUyLCAiXG4iKQoKIyBEaWFnbsOzc3RpY28gZGUgcmVzaWR1b3MgZGVsIG1vZGVsbwpwYXIobWZyb3cgPSBjKDIsIDIpKQpwbG90KG1vZGVsb19yZWdyZXNpb25fMzkwNDE1MikKYGBgCmBgYHtyfQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFJlZ3Jlc2nDs24gTGluZWFsIHBhcmEgcHJvZHVjdG8gMTU1MDAxCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7CiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLAogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksCiAgICBNQVBFID0gbnVtZXJpYygpLAogICAgUk1TRSA9IG51bWVyaWMoKSwKICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQogICkKfQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIzOTA0MTUyIiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvCiAgTW9kZWxvID0gIlJlZ3Jlc2nDs24gTGluZWFsIiwKICBNQVBFID0gbWFwZV8zOTA0MTUyLAogIFJNU0UgPSBybXNlXzM5MDQxNTIKKSkKYGBgCgoKIyMgUFJPRFVDVE8gMTU1MDAyCgpgYGB7cn0KbGlicmFyeShkcGx5cikKbGlicmFyeShsdWJyaWRhdGUpCgojIFBhc28gMTogRmlsdHJhciwgc2VsZWNjaW9uYXIgeSBjcmVhciBmZWNoYSBtZW5zdWFsCmRhdG9zXzE1NTAwMiA8LSBkYXRvc19maWx0cmFkb3MgJT4lCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gMTU1MDAyKSAlPiUKICBzZWxlY3QoVmVudGEsIENhbnQsIENvc3RvX1ZlbnRhLAogICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8sIERlc2N1ZW50b19Qb3JjZW50YWplLCBUcnhfRmVjaGEpICU+JQogIG5hLm9taXQoKSAlPiUKICBtdXRhdGUoRmVjaGEgPSBhcy5EYXRlKGZsb29yX2RhdGUoVHJ4X0ZlY2hhLCAibW9udGgiKSkpICU+JQogIGdyb3VwX2J5KEZlY2hhKSAlPiUKICBzdW1tYXJpc2UoCiAgICBWZW50YSA9IHN1bShWZW50YSwgbmEucm0gPSBUUlVFKSwKICAgIENhbnQgPSBzdW0oQ2FudCwgbmEucm0gPSBUUlVFKSwKICAgIENvc3RvX1ZlbnRhID0gc3VtKENvc3RvX1ZlbnRhLCBuYS5ybSA9IFRSVUUpLAogICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvID0gbWVhbihQcmVjaW9fRmluYWxfVW5pdGFyaW8sIG5hLnJtID0gVFJVRSksCiAgICBEZXNjdWVudG9fUG9yY2VudGFqZSA9IG1lYW4oRGVzY3VlbnRvX1BvcmNlbnRhamUsIG5hLnJtID0gVFJVRSkKICApICU+JQogIGFycmFuZ2UoRmVjaGEpICU+JQogICMgUGFzbyAyOiBDcmVhciB2YXJpYWJsZSBkZSB0aWVtcG8geSBhcGxpY2FyIGxhZyBzb2JyZXNjcmliaWVuZG8gbG9zIG5vbWJyZXMgb3JpZ2luYWxlcwogIG11dGF0ZSgKICAgIFRpZW1wbyA9IGFzLm51bWVyaWMoRmVjaGEgLSBtaW4oRmVjaGEpKSAvIDMwLAogICAgQ2FudCA9IGxhZyhDYW50LCAxKSwKICAgIENvc3RvX1ZlbnRhID0gbGFnKENvc3RvX1ZlbnRhLCAxKSwKICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbyA9IGxhZyhQcmVjaW9fRmluYWxfVW5pdGFyaW8sIDEpLAogICAgRGVzY3VlbnRvX1BvcmNlbnRhamUgPSBsYWcoRGVzY3VlbnRvX1BvcmNlbnRhamUsIDEpCiAgKSAlPiUKICBkcm9wX25hKCkKCiMgUGFzbyAzOiBFbGltaW5hciBlbCBtZXMgbcOhcyByZWNpZW50ZQpkYXRvc18xNTUwMDIgPC0gZGF0b3NfMTU1MDAyICU+JQogIGZpbHRlcihGZWNoYSA8IG1heChGZWNoYSkpCgojIFZlcmlmaWNhciBsYXMgcHJpbWVyYXMgZmlsYXMKaGVhZChkYXRvc18xNTUwMDIpCgpgYGAKCgpgYGB7cn0KIyBBanVzdGFyIGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbAptb2RlbG9fcmVncmVzaW9uXzE1NTAwMiA8LSBsbShWZW50YSB+IENhbnQgKyBDb3N0b19WZW50YSArCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbyArIERlc2N1ZW50b19Qb3JjZW50YWplICsgVGllbXBvLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkYXRvc18xNTUwMDIpCgojIFZlciByZXN1bWVuIGRlbCBtb2RlbG8Kc3VtbWFyeShtb2RlbG9fcmVncmVzaW9uXzE1NTAwMikKYGBgCgpgYGB7cn0KIyBQcmVkaWNjaW9uZXMgdXNhbmRvIGVsIG1vZGVsbyBhanVzdGFkbwpwcmVkaWNjaW9uZXNfMTU1MDAyIDwtIHByZWRpY3QobW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDIsIG5ld2RhdGEgPSBkYXRvc18xNTUwMDIpCgojIENhbGN1bGFyIE1BUEUgKE1lYW4gQWJzb2x1dGUgUGVyY2VudGFnZSBFcnJvcikKIyBBw7FhZGltb3MgcHJvdGVjY2nDs24gY29udHJhIGRpdmlzacOzbiBwb3IgY2VybwptYXBlXzE1NTAwMiA8LSBtZWFuKGFicygoZGF0b3NfMTU1MDAyJFZlbnRhIC0gcHJlZGljY2lvbmVzXzE1NTAwMikgLyBwbWF4KGRhdG9zXzE1NTAwMiRWZW50YSwgMC4wMSkpKSAqIDEwMAoKIyBDYWxjdWxhciBSTVNFIChSb290IE1lYW4gU3F1YXJlZCBFcnJvcikKcm1zZV8xNTUwMDIgPC0gc3FydChtZWFuKChkYXRvc18xNTUwMDIkVmVudGEgLSBwcmVkaWNjaW9uZXNfMTU1MDAyKV4yKSkKCgojIE1vc3RyYXIgbGFzIG3DqXRyaWNhcwpjYXQoIk1BUEUgZGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbCBwYXJhIDE1NTAwMjogIiwgbWFwZV8xNTUwMDIsICJcbiIpCmNhdCgiUk1TRSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgMTU1MDAyOiAiLCBybXNlXzE1NTAwMiwgIlxuIikKCiMgRGlhZ27Ds3N0aWNvIGRlIHJlc2lkdW9zIGRlbCBtb2RlbG8KcGFyKG1mcm93ID0gYygyLCAyKSkKcGxvdChtb2RlbG9fcmVncmVzaW9uXzE1NTAwMikKYGBgCgpgYGB7cn0KIyBHdWFyZGFyIG3DqXRyaWNhcyBkZSBSZWdyZXNpw7NuIExpbmVhbCBwYXJhIHByb2R1Y3RvIDE1NTAwMQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgewogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLAogICAgTUFQRSA9IG51bWVyaWMoKSwKICAgIFJNU0UgPSBudW1lcmljKCksCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCn0KCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMTU1MDAyIiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvCiAgTW9kZWxvID0gIlJlZ3Jlc2nDs24gTGluZWFsIiwKICBNQVBFID0gbWFwZV8xNTUwMDIsCiAgUk1TRSA9IHJtc2VfMTU1MDAyCiAgKSkKYGBgCgoKIyMgUFJPRFVDVE8gMzY3ODA1NQpgYGB7cn0KbGlicmFyeShkcGx5cikKbGlicmFyeShsdWJyaWRhdGUpCgojIFBhc28gMTogRmlsdHJhciwgc2VsZWNjaW9uYXIgeSBjcmVhciBmZWNoYSBtZW5zdWFsCmRhdG9zXzM2NzgwNTUgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDM2NzgwNTUpICU+JQogIHNlbGVjdChWZW50YSwgQ2FudCwgQ29zdG9fVmVudGEsCiAgICAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbywgRGVzY3VlbnRvX1BvcmNlbnRhamUsIFRyeF9GZWNoYSkgJT4lCiAgbmEub21pdCgpICU+JQogIG11dGF0ZShGZWNoYSA9IGFzLkRhdGUoZmxvb3JfZGF0ZShUcnhfRmVjaGEsICJtb250aCIpKSkgJT4lCiAgZ3JvdXBfYnkoRmVjaGEpICU+JQogIHN1bW1hcmlzZSgKICAgIFZlbnRhID0gc3VtKFZlbnRhLCBuYS5ybSA9IFRSVUUpLAogICAgQ2FudCA9IHN1bShDYW50LCBuYS5ybSA9IFRSVUUpLAogICAgQ29zdG9fVmVudGEgPSBzdW0oQ29zdG9fVmVudGEsIG5hLnJtID0gVFJVRSksCiAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gPSBtZWFuKFByZWNpb19GaW5hbF9Vbml0YXJpbywgbmEucm0gPSBUUlVFKSwKICAgIERlc2N1ZW50b19Qb3JjZW50YWplID0gbWVhbihEZXNjdWVudG9fUG9yY2VudGFqZSwgbmEucm0gPSBUUlVFKQogICkgJT4lCiAgYXJyYW5nZShGZWNoYSkgJT4lCiAgIyBQYXNvIDI6IENyZWFyIHZhcmlhYmxlIFRpZW1wbyB5IGFwbGljYXIgbGFnIHNvYnJlc2NyaWJpZW5kbyBub21icmVzIG9yaWdpbmFsZXMKICBtdXRhdGUoCiAgICBUaWVtcG8gPSBhcy5udW1lcmljKEZlY2hhIC0gbWluKEZlY2hhKSkgLyAzMCwKICAgIENhbnQgPSBsYWcoQ2FudCwgMSksCiAgICBDb3N0b19WZW50YSA9IGxhZyhDb3N0b19WZW50YSwgMSksCiAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gPSBsYWcoUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCAxKSwKICAgIERlc2N1ZW50b19Qb3JjZW50YWplID0gbGFnKERlc2N1ZW50b19Qb3JjZW50YWplLCAxKQogICkgJT4lCiAgZHJvcF9uYSgpCgojIFBhc28gMzogRWxpbWluYXIgZWwgbWVzIG3DoXMgcmVjaWVudGUKZGF0b3NfMzY3ODA1NSA8LSBkYXRvc18zNjc4MDU1ICU+JQogIGZpbHRlcihGZWNoYSA8IG1heChGZWNoYSkpCgojIFZlcmlmaWNhciBsYXMgcHJpbWVyYXMgZmlsYXMKaGVhZChkYXRvc18zNjc4MDU1KQoKYGBgCgpgYGB7cn0KIyBBanVzdGFyIGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbAptb2RlbG9fcmVncmVzaW9uXzM2NzgwNTUgPC0gbG0oVmVudGEgfiBDYW50ICsgQ29zdG9fVmVudGEgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gKyBEZXNjdWVudG9fUG9yY2VudGFqZSArIFRpZW1wbywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGF0b3NfMzY3ODA1NSkKCiMgVmVyIHJlc3VtZW4gZGVsIG1vZGVsbwpzdW1tYXJ5KG1vZGVsb19yZWdyZXNpb25fMzY3ODA1NSkKYGBgCgpgYGB7cn0KI1ByZWRpY2Npb25lcyB1c2FuZG8gZWwgbW9kZWxvIGFqdXN0YWRvCnByZWRpY2Npb25lc18zNjc4MDU1IDwtIHByZWRpY3QobW9kZWxvX3JlZ3Jlc2lvbl8zNjc4MDU1LCBuZXdkYXRhID0gZGF0b3NfMzY3ODA1NSkKIyBDYWxjdWxhciBNQVBFIChNZWFuIEFic29sdXRlIFBlcmNlbnRhZ2UgRXJyb3IpCiMgQcOxYWRpbW9zIHByb3RlY2Npw7NuIGNvbnRyYSBkaXZpc2nDs24gcG9yIGNlcm8KbWFwZV8zNjc4MDU1IDwtIG1lYW4oYWJzKChkYXRvc18zNjc4MDU1JFZlbnRhIC0gcHJlZGljY2lvbmVzXzM2NzgwNTUpIC8gcG1heChkYXRvc18zNjc4MDU1JFZlbnRhLCAwLjAxKSkpICogMTAwCgojIENhbGN1bGFyIFJNU0UgKFJvb3QgTWVhbiBTcXVhcmVkIEVycm9yKQoKcm1zZV8zNjc4MDU1IDwtIG1lYW4oKGRhdG9zXzM2NzgwNTUkVmVudGEgLSBwcmVkaWNjaW9uZXNfMzY3ODA1NSleMikKCiMgTW9zdHJhciBsYXMgbcOpdHJpY2FzCmNhdCgiTUFQRSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgMzY3ODA1NTogIiwgbWFwZV8zNjc4MDU1LCAiXG4iKQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbCBwYXJhIDM2NzgwNTU6ICIsIHJtc2VfMzY3ODA1NSwgIlxuIikKCiMgRGlhZ27Ds3N0aWNvIGRlIHJlc2lkdW9zIGRlbCBtb2RlbG8KcGFyKG1mcm93ID0gYygyLCAyKSkKcGxvdChtb2RlbG9fcmVncmVzaW9uXzM2NzgwNTUpCmBgYAoKYGBge3J9CiMgR3VhcmRhciBtw6l0cmljYXMgZGUgUmVncmVzacOzbiBMaW5lYWwgcGFyYSBwcm9kdWN0byAxNTUwMDEKaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1BUEUgPSBudW1lcmljKCksCiAgICBSTVNFID0gbnVtZXJpYygpLAogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKQp9CgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjM2NzgwNTUiLCAgIyBDYW1iaWEgZXN0ZSBJRCBwYXJhIGNhZGEgcHJvZHVjdG8KICBNb2RlbG8gPSAiUmVncmVzacOzbiBMaW5lYWwiLAogIE1BUEUgPSBtYXBlXzM2NzgwNTUsCiAgUk1TRSA9IHJtc2VfMzY3ODA1NQopKQpgYGAKCiMjIEFOQUxJU0lTIERFIFZBUklBQkxFUyBJTVBPUlRBTlRFUwpgYGB7cn0KIyBGdW5jacOzbiBzaW1wbGlmaWNhZGEgcGFyYSBhbmFsaXphciBjb2VmaWNpZW50ZXMKYW5hbGl6YXJfY29lZmljaWVudGVzIDwtIGZ1bmN0aW9uKG1vZGVsbywgbm9tYnJlX3Byb2R1Y3RvKSB7CiAgcmVzdW1lbiA8LSBzdW1tYXJ5KG1vZGVsbykKICBjb2VmX2RmIDwtIGFzLmRhdGEuZnJhbWUocmVzdW1lbiRjb2VmZmljaWVudHMpCiAgY29sbmFtZXMoY29lZl9kZikgPC0gYygiRXN0aW1hdGUiLCAiU3RkLkVycm9yIiwgInQudmFsdWUiLCAicC52YWx1ZSIpCiAgY29lZl9kZiRWYXJpYWJsZSA8LSByb3duYW1lcyhjb2VmX2RmKQogIGNvZWZfZGYkUHJvZHVjdG8gPC0gbm9tYnJlX3Byb2R1Y3RvCiAgY29lZl9kZiRTaWduaWZpY2F0aXZvIDwtIGlmZWxzZShjb2VmX2RmJHAudmFsdWUgPCAwLjA1LCAiU8OtIiwgIk5vIikKICAKICByZXR1cm4oY29lZl9kZiAlPiUKICAgICAgICAgICBzZWxlY3QoUHJvZHVjdG8sIFZhcmlhYmxlLCBFc3RpbWF0ZSwgcC52YWx1ZSwgU2lnbmlmaWNhdGl2bykgJT4lCiAgICAgICAgICAgYXJyYW5nZShkZXNjKGFicyhFc3RpbWF0ZSkpKSkKfQoKIyBBcGxpY2FyIGEgY2FkYSBtb2RlbG8KY29lZl8xNTUwMDEgPC0gYW5hbGl6YXJfY29lZmljaWVudGVzKG1vZGVsb19yZWdyZXNpb25fMTU1MDAxLCAiMTU1MDAxIikKY29lZl8xNTUwMDIgPC0gYW5hbGl6YXJfY29lZmljaWVudGVzKG1vZGVsb19yZWdyZXNpb25fMTU1MDAyLCAiMTU1MDAyIikKY29lZl8zNjc4MDU1IDwtIGFuYWxpemFyX2NvZWZpY2llbnRlcyhtb2RlbG9fcmVncmVzaW9uXzM2NzgwNTUsICIzNjc4MDU1IikKY29lZl8zOTA0MTUyIDwtIGFuYWxpemFyX2NvZWZpY2llbnRlcyhtb2RlbG9fcmVncmVzaW9uXzM5MDQxNTIsICIzOTA0MTUyIikKY29lZl8zOTI5Nzg4IDwtIGFuYWxpemFyX2NvZWZpY2llbnRlcyhtb2RlbG9fcmVncmVzaW9uXzM5Mjk3ODgsICIzOTI5Nzg4IikKCiMgQ29tYmluYXIgdG9kb3MgbG9zIGNvZWZpY2llbnRlcwp0b2Rvc19jb2VmaWNpZW50ZXMgPC0gYmluZF9yb3dzKGNvZWZfMTU1MDAxLCBjb2VmXzE1NTAwMiwgY29lZl8zNjc4MDU1LCBjb2VmXzM5MDQxNTIsIGNvZWZfMzkyOTc4OCkKCiMgVGFibGEgY29uIHZhcmlhYmxlcyBpbXBvcnRhbnRlcyBpbmNsdXllbmRvIHNpZ25pZmljYW5jaWEKdmFyaWFibGVzX2ltcG9ydGFudGVzIDwtIHRvZG9zX2NvZWZpY2llbnRlcyAlPiUKICBmaWx0ZXIoVmFyaWFibGUgIT0gIihJbnRlcmNlcHQpIikgJT4lCiAgZ3JvdXBfYnkoUHJvZHVjdG8pICU+JQogIGFycmFuZ2UoUHJvZHVjdG8sIGRlc2MoYWJzKEVzdGltYXRlKSkpICU+JQogIG11dGF0ZShJbXBhY3RvID0gaWZlbHNlKEVzdGltYXRlID4gMCwgIlBvc2l0aXZvIiwgIk5lZ2F0aXZvIikpCgojIFRhYmxhIGNvbXBsZXRhIGNvbiB0b2RhcyBsYXMgdmFyaWFibGVzIGltcG9ydGFudGVzCmthYmxlKHZhcmlhYmxlc19pbXBvcnRhbnRlcyAlPiUgCiAgICAgICAgc2VsZWN0KFByb2R1Y3RvLCBWYXJpYWJsZSwgRXN0aW1hdGUsIHAudmFsdWUsIFNpZ25pZmljYXRpdm8sIEltcGFjdG8pLAogICAgICBjYXB0aW9uID0gIlZhcmlhYmxlcyBpbXBvcnRhbnRlcyBwb3IgcHJvZHVjdG8iLAogICAgICBjb2wubmFtZXMgPSBjKCJQcm9kdWN0byIsICJWYXJpYWJsZSIsICJDb2VmaWNpZW50ZSIsICJwLXZhbHVlIiwgIlNpZ25pZmljYXRpdm8iLCAiSW1wYWN0byIpLAogICAgICBkaWdpdHMgPSBjKDAsIDAsIDQsIDQsIDAsIDApKSAlPiUKICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSkKCiMgVGFibGEgcmVzdW1lbiBjb24gdG9wIDMgcG9yIHByb2R1Y3RvCnRvcF9wb3JfcHJvZHVjdG8gPC0gdmFyaWFibGVzX2ltcG9ydGFudGVzICU+JQogIGdyb3VwX2J5KFByb2R1Y3RvKSAlPiUKICBzbGljZV9oZWFkKG4gPSAzKSAlPiUKICBzZWxlY3QoUHJvZHVjdG8sIFZhcmlhYmxlLCBFc3RpbWF0ZSwgcC52YWx1ZSwgU2lnbmlmaWNhdGl2bywgSW1wYWN0bykKCmthYmxlKHRvcF9wb3JfcHJvZHVjdG8sCiAgICAgIGNhcHRpb24gPSAiVG9wIDMgdmFyaWFibGVzIG3DoXMgaW1wb3J0YW50ZXMgcG9yIHByb2R1Y3RvIiwKICAgICAgY29sLm5hbWVzID0gYygiUHJvZHVjdG8iLCAiVmFyaWFibGUiLCAiQ29lZmljaWVudGUiLCAicC12YWx1ZSIsICJTaWduaWZpY2F0aXZvIiwgIkltcGFjdG8iKSwKICAgICAgZGlnaXRzID0gYygwLCAwLCA0LCA0LCAwLCAwKSkgJT4lCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIikpCmBgYAoKCiMgUkFORE9NIEZPUkVTVAoKCiMjIFBST0RVQ1RPIDE1NTAwMQpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykKZGF0b3NfbW9kZWxvIDwtIGRhdG9zXzE1NTAwMSAlPiUKICBzZWxlY3QoLUZlY2hhKQoKIyBBanVzdGFyIGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0CnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkCm1vZGVsb19yZl8xNTUwMDEgPC0gcmFuZG9tRm9yZXN0KAogIFZlbnRhIH4gLiwgCiAgZGF0YSA9IGRhdG9zX21vZGVsbywKICBudHJlZSA9IDUwMCwgICAgICAgICAgIyBOw7ptZXJvIGRlIMOhcmJvbGVzCiAgbXRyeSA9IGZsb29yKHNxcnQobmNvbChkYXRvc19tb2RlbG8pIC0gMSkpLCAgIyBOw7ptZXJvIGRlIHZhcmlhYmxlcyBhIGNvbnNpZGVyYXIgZW4gY2FkYSBzcGxpdAogIGltcG9ydGFuY2UgPSBUUlVFICAgICAjIENhbGN1bGFyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwopCgojIFZlciByZXN1bWVuIGRlbCBtb2RlbG8KcHJpbnQobW9kZWxvX3JmXzE1NTAwMSkKCiMgT2J0ZW5lciBwcmVkaWNjaW9uZXMKcHJlZGljY2lvbmVzX3JmIDwtIHByZWRpY3QobW9kZWxvX3JmXzE1NTAwMSwgbmV3ZGF0YSA9IGRhdG9zX21vZGVsbykKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzCiMgTUFQRQptYXBlX3JmIDwtIG1lYW4oYWJzKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpIC8gcG1heChkYXRvc19tb2RlbG8kVmVudGEsIDAuMDEpKSkgKiAxMDAKCiMgUk1TRQpybXNlX3JmIDwtIG1lYW4oKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19yZileMikKCiMgTW9zdHJhciBsYXMgbcOpdHJpY2FzCmNhdCgiTW9kZWxvIFJhbmRvbSBGb3Jlc3QgcGFyYSBwcm9kdWN0byAxNTUwMDFcbiIpCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3Q6IiwgbWFwZV9yZiwgIlxuIikKY2F0KCJSTVNFIGRlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdDoiLCBybXNlX3JmLCAiXG5cbiIpCgojIE1vc3RyYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCmltcG9ydGFuY2lhX3ZhcnMgPC0gaW1wb3J0YW5jZShtb2RlbG9fcmZfMTU1MDAxKQpwcmludChpbXBvcnRhbmNpYV92YXJzKQoKIyBHcmFmaWNhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKdmFySW1wUGxvdChtb2RlbG9fcmZfMTU1MDAxLCBtYWluID0gIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyAtIFByb2R1Y3RvIDE1NTAwMSIpCgojIENyZWFyIGdyw6FmaWNvIGRlIHZhbG9yZXMgb2JzZXJ2YWRvcyB2cyBwcmVkaWNjaW9uZXMKZGF0b3NfZ3JhZmljbyA8LSBkYXRhLmZyYW1lKAogIE9ic2VydmFkbyA9IGRhdG9zX21vZGVsbyRWZW50YSwKICBQcmVkaWNobyA9IHByZWRpY2Npb25lc19yZgopCgpnZ3Bsb3QoZGF0b3NfZ3JhZmljbywgYWVzKHggPSBPYnNlcnZhZG8sIHkgPSBQcmVkaWNobykpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicygKICAgIHRpdGxlID0gIlZhbG9yZXMgT2JzZXJ2YWRvcyB2cyBQcmVkaWNjaW9uZXMgLSBQcm9kdWN0byAxNTUwMDEiLAogICAgeCA9ICJWZW50YXMgT2JzZXJ2YWRhcyIsCiAgICB5ID0gIlZlbnRhcyBQcmVkaWNoYXMiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIE5VRVZPUyBBTsOBTElTSVMgQcORQURJRE9TCgojIEFuw6FsaXNpcyBkZWwgZXJyb3IKZXJyb3JlcyA8LSBkYXRvc19ncmFmaWNvJE9ic2VydmFkbyAtIGRhdG9zX2dyYWZpY28kUHJlZGljaG8KaGlzdChlcnJvcmVzLCAKICAgICBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDE1NTAwMSIsCiAgICAgeGxhYiA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIiwKICAgICBjb2wgPSAic2t5Ymx1ZSIsCiAgICAgYnJlYWtzID0gMzApCgojIEVzdGFkw61zdGljYXMgZGVzY3JpcHRpdmFzIGRlIGxvcyBlcnJvcmVzCmNhdCgiRXN0YWTDrXN0aWNhcyBkZXNjcmlwdGl2YXMgZGUgbG9zIGVycm9yZXM6XG4iKQpjYXQoIk1lZGlhIGRlIGVycm9yZXM6IiwgbWVhbihlcnJvcmVzKSwgIlxuIikKY2F0KCJEZXN2aWFjacOzbiBlc3TDoW5kYXIgZGUgZXJyb3JlczoiLCBzZChlcnJvcmVzKSwgIlxuIikKY2F0KCJNw61uaW1vOiIsIG1pbihlcnJvcmVzKSwgIlxuIikKY2F0KCJNw6F4aW1vOiIsIG1heChlcnJvcmVzKSwgIlxuIikKY2F0KCJNZWRpYW5hOiIsIG1lZGlhbihlcnJvcmVzKSwgIlxuIikKCiMgR3LDoWZpY28gZGVsIGVycm9yIHZzIHByZWRpY2Npw7NuCmdncGxvdChkYXRhLmZyYW1lKFByZWRpY2hvID0gcHJlZGljY2lvbmVzX3JmLCBFcnJvciA9IGVycm9yZXMpLCBhZXMoeCA9IFByZWRpY2hvLCB5ID0gRXJyb3IpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiRXJyb3IgdnMgUHJlZGljY2nDs24gLSBQcm9kdWN0byAxNTUwMDEiLAogICAgeCA9ICJWZW50YXMgUHJlZGljaGFzIiwKICAgIHkgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIKICApICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgpgYGB7cn0KIyBHdWFyZGFyIG3DqXRyaWNhcyBkZSBSYW5kb20gRm9yZXN0IHBhcmEgcHJvZHVjdG8gMTU1MDAxCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7CiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLAogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksCiAgICBNQVBFID0gbnVtZXJpYygpLAogICAgUk1TRSA9IG51bWVyaWMoKSwKICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQogICkKfQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIxNTUwMDEiLCAgIyBDYW1iaWEgZXN0ZSBJRCBwYXJhIGNhZGEgcHJvZHVjdG8KICBNb2RlbG8gPSAiUmFuZG9tIEZvcmVzdCIsCiAgTUFQRSA9IG1hcGVfcmYsCiAgUk1TRSA9IHJtc2VfcmYKKSkKYGBgCgojIyBQUk9EVUNUTyAzOTI5Nzg4CmBgYHtyLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD02fQojIFByZXBhcmFyIGRhdG9zIHBhcmEgZWwgbW9kZWxvIChlbGltaW5hciBjb2x1bW5hcyBubyBuZWNlc2FyaWFzKQpkYXRvc19tb2RlbG8gPC0gZGF0b3NfMzkyOTc4OCAlPiUKICBzZWxlY3QoLUZlY2hhKQoKIyBBanVzdGFyIGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0CnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkCm1vZGVsb19yZl8zOTI5Nzg4IDwtIHJhbmRvbUZvcmVzdCgKICBWZW50YSB+IC4sIAogIGRhdGEgPSBkYXRvc19tb2RlbG8sCiAgbnRyZWUgPSA1MDAsICAgICAgICAgICMgTsO6bWVybyBkZSDDoXJib2xlcwogIG10cnkgPSBmbG9vcihzcXJ0KG5jb2woZGF0b3NfbW9kZWxvKSAtIDEpKSwgICMgTsO6bWVybyBkZSB2YXJpYWJsZXMgYSBjb25zaWRlcmFyIGVuIGNhZGEgc3BsaXQKICBpbXBvcnRhbmNlID0gVFJVRSAgICAgIyBDYWxjdWxhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKKQoKIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvCnByaW50KG1vZGVsb19yZl8zOTI5Nzg4KQoKIyBPYnRlbmVyIHByZWRpY2Npb25lcwpwcmVkaWNjaW9uZXNfcmYgPC0gcHJlZGljdChtb2RlbG9fcmZfMzkyOTc4OCwgbmV3ZGF0YSA9IGRhdG9zX21vZGVsbykKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzCiMgTUFQRQptYXBlX3JmIDwtIG1lYW4oYWJzKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpIC8gcG1heChkYXRvc19tb2RlbG8kVmVudGEsIDAuMDEpKSkgKiAxMDAKCiMgUk1TRQoKcm1zZV9yZiA8LSBzcXJ0KG1lYW4oKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19yZileMikpCgoKIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMKY2F0KCJNb2RlbG8gUmFuZG9tIEZvcmVzdCBwYXJhIHByb2R1Y3RvIDM5Mjk3ODhcbiIpCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3Q6IiwgbWFwZV9yZiwgIlxuIikKY2F0KCJSTVNFIGRlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdDoiLCBybXNlX3JmLCAiXG5cbiIpCgoKIyBNb3N0cmFyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwppbXBvcnRhbmNpYV92YXJzIDwtIGltcG9ydGFuY2UobW9kZWxvX3JmXzM5Mjk3ODgpCnByaW50KGltcG9ydGFuY2lhX3ZhcnMpCgojIEdyYWZpY2FyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwp2YXJJbXBQbG90KG1vZGVsb19yZl8zOTI5Nzg4LCBtYWluID0gIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyAtIFByb2R1Y3RvIDM5Mjk3ODgiKQoKIyBDcmVhciBncsOhZmljbyBkZSB2YWxvcmVzIG9ic2VydmFkb3MgdnMgcHJlZGljY2lvbmVzCmRhdG9zX2dyYWZpY28gPC0gZGF0YS5mcmFtZSgKICBPYnNlcnZhZG8gPSBkYXRvc19tb2RlbG8kVmVudGEsCiAgUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmYKKQoKZ2dwbG90KGRhdG9zX2dyYWZpY28sIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJWYWxvcmVzIE9ic2VydmFkb3MgdnMgUHJlZGljY2lvbmVzIC0gUHJvZHVjdG8gMzkyOTc4OCIsCiAgICB4ID0gIlZlbnRhcyBPYnNlcnZhZGFzIiwKICAgIHkgPSAiVmVudGFzIFByZWRpY2hhcyIKICApICsKICB0aGVtZV9taW5pbWFsKCkKCiMgQW7DoWxpc2lzIGRlbCBlcnJvcgplcnJvcmVzIDwtIGRhdG9zX2dyYWZpY28kT2JzZXJ2YWRvIC0gZGF0b3NfZ3JhZmljbyRQcmVkaWNobwpoaXN0KGVycm9yZXMsIAogICAgIG1haW4gPSAiRGlzdHJpYnVjacOzbiBkZSBFcnJvcmVzIC0gUHJvZHVjdG8gMzkyOTc4OCIsCiAgICAgeGxhYiA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIiwKICAgICBjb2wgPSAic2t5Ymx1ZSIsCiAgICAgYnJlYWtzID0gMzApCgojIEdyw6FmaWNvIGRlbCBlcnJvciB2cyBwcmVkaWNjacOzbgpnZ3Bsb3QoZGF0YS5mcmFtZShQcmVkaWNobyA9IHByZWRpY2Npb25lc19yZiwgRXJyb3IgPSBlcnJvcmVzKSwgYWVzKHggPSBQcmVkaWNobywgeSA9IEVycm9yKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicygKICAgIHRpdGxlID0gIkVycm9yIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMzkyOTc4OCIsCiAgICB4ID0gIlZlbnRhcyBQcmVkaWNoYXMiLAogICAgeSA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIgogICkgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCmBgYHtyfQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFJhbmRvbSBGb3Jlc3QgcGFyYSBwcm9kdWN0byAxNTUwMDEKaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1BUEUgPSBudW1lcmljKCksCiAgICBSTVNFID0gbnVtZXJpYygpLAoKICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQogICkKfQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIzOTI5Nzg4IiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvCiAgTW9kZWxvID0gIlJhbmRvbSBGb3Jlc3QiLAogIE1BUEUgPSBtYXBlX3JmLAogIFJNU0UgPSBybXNlX3JmCgopKQpgYGAKCgojIyBQUk9EVUNUTyAzOTA0MTUyCmBgYHtyLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD02fQojIFByZXBhcmFyIGRhdG9zIHBhcmEgZWwgbW9kZWxvIChlbGltaW5hciBjb2x1bW5hcyBubyBuZWNlc2FyaWFzKQpkYXRvc19tb2RlbG8gPC0gZGF0b3NfMzkwNDE1MiAlPiUKICBzZWxlY3QoLUZlY2hhKQoKIyBBanVzdGFyIGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0CnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkCm1vZGVsb19yZl8zOTA0MTUyIDwtIHJhbmRvbUZvcmVzdCgKICBWZW50YSB+IC4sIAogIGRhdGEgPSBkYXRvc19tb2RlbG8sCiAgbnRyZWUgPSA1MDAsICAgICAgICAgICMgTsO6bWVybyBkZSDDoXJib2xlcwogIG10cnkgPSBmbG9vcihzcXJ0KG5jb2woZGF0b3NfbW9kZWxvKSAtIDEpKSwgICMgTsO6bWVybyBkZSB2YXJpYWJsZXMgYSBjb25zaWRlcmFyIGVuIGNhZGEgc3BsaXQKICBpbXBvcnRhbmNlID0gVFJVRSAgICAgIyBDYWxjdWxhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKKQoKIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvCnByaW50KG1vZGVsb19yZl8zOTA0MTUyKQoKIyBPYnRlbmVyIHByZWRpY2Npb25lcwpwcmVkaWNjaW9uZXNfcmYgPC0gcHJlZGljdChtb2RlbG9fcmZfMzkwNDE1MiwgbmV3ZGF0YSA9IGRhdG9zX21vZGVsbykKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzCiMgTUFQRQptYXBlX3JmIDwtIG1lYW4oYWJzKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpIC8gcG1heChkYXRvc19tb2RlbG8kVmVudGEsIDAuMDEpKSkgKiAxMDAKCiMgUk1TRQpybXNlX3JmIDwtIHNxcnQobWVhbigoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX3JmKV4yKSkKCgojIE1vc3RyYXIgbGFzIG3DqXRyaWNhcwpjYXQoIk1vZGVsbyBSYW5kb20gRm9yZXN0IHBhcmEgcHJvZHVjdG8gMzkwNDE1MlxuIikKY2F0KCJNQVBFIGRlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdDoiLCBtYXBlX3JmLCAiXG4iKQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0OiIsIHJtc2VfcmYsICJcblxuIikKCiMgTW9zdHJhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKaW1wb3J0YW5jaWFfdmFycyA8LSBpbXBvcnRhbmNlKG1vZGVsb19yZl8zOTA0MTUyKQpwcmludChpbXBvcnRhbmNpYV92YXJzKQoKIyBHcmFmaWNhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKdmFySW1wUGxvdChtb2RlbG9fcmZfMzkwNDE1MiwgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAzOTA0MTUyIikKCiMgQ3JlYXIgZ3LDoWZpY28gZGUgdmFsb3JlcyBvYnNlcnZhZG9zIHZzIHByZWRpY2Npb25lcwpkYXRvc19ncmFmaWNvIDwtIGRhdGEuZnJhbWUoCiAgT2JzZXJ2YWRvID0gZGF0b3NfbW9kZWxvJFZlbnRhLAogIFByZWRpY2hvID0gcHJlZGljY2lvbmVzX3JmCikKCmdncGxvdChkYXRvc19ncmFmaWNvLCBhZXMoeCA9IE9ic2VydmFkbywgeSA9IFByZWRpY2hvKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDM5MDQxNTIiLAogICAgeCA9ICJWZW50YXMgT2JzZXJ2YWRhcyIsCiAgICB5ID0gIlZlbnRhcyBQcmVkaWNoYXMiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIEFuw6FsaXNpcyBkZWwgZXJyb3IKZXJyb3JlcyA8LSBkYXRvc19ncmFmaWNvJE9ic2VydmFkbyAtIGRhdG9zX2dyYWZpY28kUHJlZGljaG8KaGlzdChlcnJvcmVzLCAKICAgICBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDM5MDQxNTIiLAogICAgIHhsYWIgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIsCiAgICAgY29sID0gInNreWJsdWUiLAogICAgIGJyZWFrcyA9IDMwKQoKIyBHcsOhZmljbyBkZWwgZXJyb3IgdnMgcHJlZGljY2nDs24KZ2dwbG90KGRhdGEuZnJhbWUoUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmYsIEVycm9yID0gZXJyb3JlcyksIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJFcnJvciB2cyBQcmVkaWNjacOzbiAtIFByb2R1Y3RvIDM5MDQxNTIiLAogICAgeCA9ICJWZW50YXMgUHJlZGljaGFzIiwKICAgIHkgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIKICApICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgpgYGB7cn0KIyBHdWFyZGFyIG3DqXRyaWNhcyBkZSBSYW5kb20gRm9yZXN0IHBhcmEgcHJvZHVjdG8gMTU1MDAxCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7CiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLAogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksCiAgICBNQVBFID0gbnVtZXJpYygpLAogICAgUk1TRSA9IG51bWVyaWMoKSwKCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCn0KCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMzkwNDE1MiIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bwogIE1vZGVsbyA9ICJSYW5kb20gRm9yZXN0IiwKICBNQVBFID0gbWFwZV9yZiwKICBSTVNFID0gcm1zZV9yZgoKKSkKYGBgCgoKIyMgUFJPRFVDVE8gMTU1MDAyCgpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykKZGF0b3NfbW9kZWxvIDwtIGRhdG9zXzE1NTAwMiAlPiUKICBzZWxlY3QoLUZlY2hhKQoKIyBBanVzdGFyIGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0CnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkCm1vZGVsb19yZl8xNTUwMDIgPC0gcmFuZG9tRm9yZXN0KAogIFZlbnRhIH4gLiwgCiAgZGF0YSA9IGRhdG9zX21vZGVsbywKICBudHJlZSA9IDUwMCwgICAgICAgICAgIyBOw7ptZXJvIGRlIMOhcmJvbGVzCiAgbXRyeSA9IGZsb29yKHNxcnQobmNvbChkYXRvc19tb2RlbG8pIC0gMSkpLCAgIyBOw7ptZXJvIGRlIHZhcmlhYmxlcyBhIGNvbnNpZGVyYXIgZW4gY2FkYSBzcGxpdAogIGltcG9ydGFuY2UgPSBUUlVFICAgICAjIENhbGN1bGFyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwopCgojIFZlciByZXN1bWVuIGRlbCBtb2RlbG8KcHJpbnQobW9kZWxvX3JmXzE1NTAwMikKCiMgT2J0ZW5lciBwcmVkaWNjaW9uZXMKcHJlZGljY2lvbmVzX3JmIDwtIHByZWRpY3QobW9kZWxvX3JmXzE1NTAwMiwgbmV3ZGF0YSA9IGRhdG9zX21vZGVsbykKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzCiMgTUFQRQptYXBlX3JmIDwtIG1lYW4oYWJzKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpIC8gcG1heChkYXRvc19tb2RlbG8kVmVudGEsIDAuMDEpKSkgKiAxMDAKCiMgTVNFCnJtc2VfcmYgPC0gc3FydChtZWFuKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpXjIpKQoKCiMgTW9zdHJhciBsYXMgbcOpdHJpY2FzCmNhdCgiTW9kZWxvIFJhbmRvbSBGb3Jlc3QgcGFyYSBwcm9kdWN0byAxNTUwMDJcbiIpCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3Q6IiwgbWFwZV9yZiwgIlxuIikKY2F0KCJSTVNFIGRlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdDoiLCBybXNlX3JmLCAiXG5cbiIpCgojIE1vc3RyYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCmltcG9ydGFuY2lhX3ZhcnMgPC0gaW1wb3J0YW5jZShtb2RlbG9fcmZfMTU1MDAyKQpwcmludChpbXBvcnRhbmNpYV92YXJzKQoKIyBHcmFmaWNhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKdmFySW1wUGxvdChtb2RlbG9fcmZfMTU1MDAyLCBtYWluID0gIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyAtIFByb2R1Y3RvIDE1NTAwMiIpCgojIENyZWFyIGdyw6FmaWNvIGRlIHZhbG9yZXMgb2JzZXJ2YWRvcyB2cyBwcmVkaWNjaW9uZXMKZGF0b3NfZ3JhZmljbyA8LSBkYXRhLmZyYW1lKAogIE9ic2VydmFkbyA9IGRhdG9zX21vZGVsbyRWZW50YSwKICBQcmVkaWNobyA9IHByZWRpY2Npb25lc19yZgopCgpnZ3Bsb3QoZGF0b3NfZ3JhZmljbywgYWVzKHggPSBPYnNlcnZhZG8sIHkgPSBQcmVkaWNobykpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicygKICAgIHRpdGxlID0gIlZhbG9yZXMgT2JzZXJ2YWRvcyB2cyBQcmVkaWNjaW9uZXMgLSBQcm9kdWN0byAxNTUwMDIiLAogICAgeCA9ICJWZW50YXMgT2JzZXJ2YWRhcyIsCiAgICB5ID0gIlZlbnRhcyBQcmVkaWNoYXMiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIEFuw6FsaXNpcyBkZWwgZXJyb3IKZXJyb3JlcyA8LSBkYXRvc19ncmFmaWNvJE9ic2VydmFkbyAtIGRhdG9zX2dyYWZpY28kUHJlZGljaG8KaGlzdChlcnJvcmVzLCAKICAgICBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDE1NTAwMiIsCiAgICAgeGxhYiA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIiwKICAgICBjb2wgPSAic2t5Ymx1ZSIsCiAgICAgYnJlYWtzID0gMzApCgojIEdyw6FmaWNvIGRlbCBlcnJvciB2cyBwcmVkaWNjacOzbgpnZ3Bsb3QoZGF0YS5mcmFtZShQcmVkaWNobyA9IHByZWRpY2Npb25lc19yZiwgRXJyb3IgPSBlcnJvcmVzKSwgYWVzKHggPSBQcmVkaWNobywgeSA9IEVycm9yKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicygKICAgIHRpdGxlID0gIkVycm9yIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMTU1MDAyIiwKICAgIHggPSAiVmVudGFzIFByZWRpY2hhcyIsCiAgICB5ID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKYGBge3J9CiMgR3VhcmRhciBtw6l0cmljYXMgZGUgUmFuZG9tIEZvcmVzdCBwYXJhIHByb2R1Y3RvIDM2NzgwNTUKaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1BUEUgPSBudW1lcmljKCksCiAgICBSTVNFID0gbnVtZXJpYygpLAogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKQp9CgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjM2NzgwNTUiLCAgIyBDYW1iaWEgZXN0ZSBJRCBwYXJhIGNhZGEgcHJvZHVjdG8KICBNb2RlbG8gPSAiUmFuZG9tIEZvcmVzdCIsCiAgTUFQRSA9IG1hcGVfcmYsCiAgUk1TRSA9IHJtc2VfcmYKCikpCmBgYAoKCiMjIFBST0RVQ1RPIDM2NzgwNTUKCmBgYHtyLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD02fQojIFByZXBhcmFyIGRhdG9zIHBhcmEgZWwgbW9kZWxvIChlbGltaW5hciBjb2x1bW5hcyBubyBuZWNlc2FyaWFzKQpkYXRvc19tb2RlbG8gPC0gZGF0b3NfMzY3ODA1NSAlPiUKICBzZWxlY3QoLUZlY2hhKQoKIyBBanVzdGFyIGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0CnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkCm1vZGVsb19yZl8zNjc4MDU1IDwtIHJhbmRvbUZvcmVzdCgKICBWZW50YSB+IC4sIAogIGRhdGEgPSBkYXRvc19tb2RlbG8sCiAgbnRyZWUgPSA1MDAsICAgICAgICAgICMgTsO6bWVybyBkZSDDoXJib2xlcwogIG10cnkgPSBmbG9vcihzcXJ0KG5jb2woZGF0b3NfbW9kZWxvKSAtIDEpKSwgICMgTsO6bWVybyBkZSB2YXJpYWJsZXMgYSBjb25zaWRlcmFyIGVuIGNhZGEgc3BsaXQKICBpbXBvcnRhbmNlID0gVFJVRSAgICAgIyBDYWxjdWxhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKKQoKIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvCnByaW50KG1vZGVsb19yZl8zNjc4MDU1KQoKIyBPYnRlbmVyIHByZWRpY2Npb25lcwpwcmVkaWNjaW9uZXNfcmYgPC0gcHJlZGljdChtb2RlbG9fcmZfMzY3ODA1NSwgbmV3ZGF0YSA9IGRhdG9zX21vZGVsbykKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzCiMgTUFQRQptYXBlX3JmIDwtIG1lYW4oYWJzKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpIC8gcG1heChkYXRvc19tb2RlbG8kVmVudGEsIDAuMDEpKSkgKiAxMDAKCiMgUk1TRQpybXNlX3JmIDwtIHNxcnQobWVhbigoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX3JmKV4yKSkKCgojIE1vc3RyYXIgbGFzIG3DqXRyaWNhcwpjYXQoIk1vZGVsbyBSYW5kb20gRm9yZXN0IHBhcmEgcHJvZHVjdG8gMzY3ODA1NVxuIikKY2F0KCJNQVBFIGRlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdDoiLCBtYXBlX3JmLCAiXG4iKQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0OiIsIHJtc2VfcmYsICJcblxuIikKCiMgTW9zdHJhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKaW1wb3J0YW5jaWFfdmFycyA8LSBpbXBvcnRhbmNlKG1vZGVsb19yZl8zNjc4MDU1KQpwcmludChpbXBvcnRhbmNpYV92YXJzKQoKIyBHcmFmaWNhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKdmFySW1wUGxvdChtb2RlbG9fcmZfMzY3ODA1NSwgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAzNjc4MDU1IikKCiMgQ3JlYXIgZ3LDoWZpY28gZGUgdmFsb3JlcyBvYnNlcnZhZG9zIHZzIHByZWRpY2Npb25lcwpkYXRvc19ncmFmaWNvIDwtIGRhdGEuZnJhbWUoCiAgT2JzZXJ2YWRvID0gZGF0b3NfbW9kZWxvJFZlbnRhLAogIFByZWRpY2hvID0gcHJlZGljY2lvbmVzX3JmCikKCmdncGxvdChkYXRvc19ncmFmaWNvLCBhZXMoeCA9IE9ic2VydmFkbywgeSA9IFByZWRpY2hvKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDM2NzgwNTUiLAogICAgeCA9ICJWZW50YXMgT2JzZXJ2YWRhcyIsCiAgICB5ID0gIlZlbnRhcyBQcmVkaWNoYXMiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIEFuw6FsaXNpcyBkZWwgZXJyb3IKZXJyb3JlcyA8LSBkYXRvc19ncmFmaWNvJE9ic2VydmFkbyAtIGRhdG9zX2dyYWZpY28kUHJlZGljaG8KaGlzdChlcnJvcmVzLCAKICAgICBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDM2NzgwNTUiLAogICAgIHhsYWIgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIsCiAgICAgY29sID0gInNreWJsdWUiLAogICAgIGJyZWFrcyA9IDMwKQoKIyBHcsOhZmljbyBkZWwgZXJyb3IgdnMgcHJlZGljY2nDs24KZ2dwbG90KGRhdGEuZnJhbWUoUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmYsIEVycm9yID0gZXJyb3JlcyksIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJFcnJvciB2cyBQcmVkaWNjacOzbiAtIFByb2R1Y3RvIDM2NzgwNTUiLAogICAgeCA9ICJWZW50YXMgUHJlZGljaGFzIiwKICAgIHkgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIKICApICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCmBgYHtyfQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFJhbmRvbSBGb3Jlc3QgcGFyYSBwcm9kdWN0byAxNTUwMDEKaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1BUEUgPSBudW1lcmljKCksCiAgICBSTVNFID0gbnVtZXJpYygpLAogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKQp9CgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjM2NzgwNTUiLCAgIyBDYW1iaWEgZXN0ZSBJRCBwYXJhIGNhZGEgcHJvZHVjdG8KICBNb2RlbG8gPSAiUmFuZG9tIEZvcmVzdCIsCiAgTUFQRSA9IG1hcGVfcmYsCiAgUk1TRSA9IHJtc2VfcmYKKSkKYGBgCgoKIyBYR0JPT1NUCgojIyBQUk9EVUNUTyAxNTUwMDEKYGBge3J9CiMgUHJlcGFyYXIgZGF0b3MgcGFyYSBlbCBtb2RlbG8gKGVsaW1pbmFyIGNvbHVtbmFzIG5vIG5lY2VzYXJpYXMpCmRhdG9zX21vZGVsbyA8LSBkYXRvc18xNTUwMDEgJT4lCiAgc2VsZWN0KC1GZWNoYSkKCiMgRGl2aWRpciBsb3MgZGF0b3MgZW4gY29uanVudG9zIGRlIGVudHJlbmFtaWVudG8gKDgwJSkgeSBwcnVlYmEgKDIwJSkKc2V0LnNlZWQoMTIzKSAgIyBQYXJhIHJlcHJvZHVjaWJpbGlkYWQKaW5kaWNlc190cmFpbiA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGRhdG9zX21vZGVsbyRWZW50YSwgcCA9IDAuOCwgbGlzdCA9IEZBTFNFKQpkYXRvc190cmFpbiA8LSBkYXRvc19tb2RlbG9baW5kaWNlc190cmFpbiwgXQpkYXRvc190ZXN0IDwtIGRhdG9zX21vZGVsb1staW5kaWNlc190cmFpbiwgXQoKIyBTZXBhcmFyIHZhcmlhYmxlcyBwcmVkaWN0b3JhcyB5IHZhcmlhYmxlIG9iamV0aXZvClhfdHJhaW4gPC0gYXMubWF0cml4KGRhdG9zX3RyYWluWywgY29sbmFtZXMoZGF0b3NfdHJhaW4pICE9ICJWZW50YSJdKQp5X3RyYWluIDwtIGRhdG9zX3RyYWluJFZlbnRhCgpYX3Rlc3QgPC0gYXMubWF0cml4KGRhdG9zX3Rlc3RbLCBjb2xuYW1lcyhkYXRvc190ZXN0KSAhPSAiVmVudGEiXSkKeV90ZXN0IDwtIGRhdG9zX3Rlc3QkVmVudGEKCiMgQ3JlYXIgbWF0cmljZXMgRE1hdHJpeCBwYXJhIFhHQm9vc3QKZHRyYWluIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBYX3RyYWluLCBsYWJlbCA9IHlfdHJhaW4pCmR0ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSBYX3Rlc3QsIGxhYmVsID0geV90ZXN0KQoKIyBEZWZpbmlyIHVuYSByZWppbGxhIGNvbXBsZXRhIGRlIGhpcGVycGFyw6FtZXRyb3MgcGFyYSBiw7pzcXVlZGEKcGFyYW1fZ3JpZCA8LSBleHBhbmQuZ3JpZCgKICBldGEgPSBjKDAuMDEsIDAuMDUsIDAuMSwgMC4zKSwgICAgICAgICAjIExlYXJuaW5nIHJhdGUKICBtYXhfZGVwdGggPSBjKDMsIDUsIDcsIDkpLCAgICAgICAgICAgICAjIFByb2Z1bmRpZGFkIG3DoXhpbWEKICBzdWJzYW1wbGUgPSBjKDAuNiwgMC44LCAxLjApLCAgICAgICAgICAjIFN1Ym11ZXN0cmEgZGUgb2JzZXJ2YWNpb25lcwogIGNvbHNhbXBsZV9ieXRyZWUgPSBjKDAuNiwgMC44LCAxLjApLCAgICMgU3VibXVlc3RyYSBkZSB2YXJpYWJsZXMKICBtaW5fY2hpbGRfd2VpZ2h0ID0gYygxLCAzLCA1KSwgICAgICAgICAjIFBlc28gbcOtbmltbyBlbiBub2RvcyBoaWpvcwogIGdhbW1hID0gYygwLCAwLjEsIDAuMykgICAgICAgICAgICAgICAgICMgUmVndWxhcml6YWNpw7NuIGdhbW1hCikKCiMgTW9zdHJhciBjdcOhbnRhcyBjb21iaW5hY2lvbmVzIHRlbmVtb3MKY2F0KCJOw7ptZXJvIHRvdGFsIGRlIGNvbWJpbmFjaW9uZXMgZGUgaGlwZXJwYXLDoW1ldHJvczoiLCBucm93KHBhcmFtX2dyaWQpLCAiXG4iKQoKIyBQYXJhIGVzdGUgZWplbXBsbywgdmFtb3MgYSBsaW1pdGFyIGVsIG7Dum1lcm8gZGUgY29tYmluYWNpb25lcwojIFNlbGVjY2lvbmFuZG8gdW4gc3ViY29uanVudG8gYWxlYXRvcmlvIGRlIGNvbWJpbmFjaW9uZXMgKDIwIGNvbWJpbmFjaW9uZXMpCnNldC5zZWVkKDEyMykKaWYgKG5yb3cocGFyYW1fZ3JpZCkgPiAyMCkgewogIG11ZXN0cmFfaW5kaWNlcyA8LSBzYW1wbGUoMTpucm93KHBhcmFtX2dyaWQpLCAyMCkKICBwYXJhbV9ncmlkX3JlZHVjaWRhIDwtIHBhcmFtX2dyaWRbbXVlc3RyYV9pbmRpY2VzLCBdCn0gZWxzZSB7CiAgcGFyYW1fZ3JpZF9yZWR1Y2lkYSA8LSBwYXJhbV9ncmlkCn0KCmNhdCgiTsO6bWVybyBkZSBjb21iaW5hY2lvbmVzIGEgZXZhbHVhcjoiLCBucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpLCAiXG4iKQoKIyBGdW5jacOzbiBwYXJhIGV2YWx1YXIgdW4gY29uanVudG8gZGUgaGlwZXJwYXLDoW1ldHJvcyBjb24gdmFsaWRhY2nDs24gY3J1emFkYQpldmFsdWF0ZV9wYXJhbXMgPC0gZnVuY3Rpb24ocGFyYW1zX3JvdykgewogIHBhcmFtcyA8LSBsaXN0KAogICAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLAogICAgZXZhbF9tZXRyaWMgPSAicm1zZSIsCiAgICBldGEgPSBwYXJhbXNfcm93JGV0YSwKICAgIG1heF9kZXB0aCA9IHBhcmFtc19yb3ckbWF4X2RlcHRoLAogICAgc3Vic2FtcGxlID0gcGFyYW1zX3JvdyRzdWJzYW1wbGUsCiAgICBjb2xzYW1wbGVfYnl0cmVlID0gcGFyYW1zX3JvdyRjb2xzYW1wbGVfYnl0cmVlLAogICAgbWluX2NoaWxkX3dlaWdodCA9IHBhcmFtc19yb3ckbWluX2NoaWxkX3dlaWdodCwKICAgIGdhbW1hID0gcGFyYW1zX3JvdyRnYW1tYQogICkKICAKICAjIFJlYWxpemFyIHZhbGlkYWNpw7NuIGNydXphZGEKICBjdl9yZXN1bHRzIDwtIHhnYi5jdigKICAgIHBhcmFtcyA9IHBhcmFtcywKICAgIGRhdGEgPSBkdHJhaW4sCiAgICBucm91bmRzID0gMTAwLAogICAgbmZvbGQgPSA1LCAgIyA1LWZvbGQgdmFsaWRhY2nDs24gY3J1emFkYQogICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gMTAsCiAgICB2ZXJib3NlID0gMAogICkKICAKICAjIEV4dHJhZXIgZWwgbWVqb3IgUk1TRSB5IGVsIG7Dum1lcm8gw7NwdGltbyBkZSByb25kYXMKICBiZXN0X3Jtc2UgPC0gbWluKGN2X3Jlc3VsdHMkZXZhbHVhdGlvbl9sb2ckdGVzdF9ybXNlX21lYW4pCiAgYmVzdF9ucm91bmRzIDwtIHdoaWNoLm1pbihjdl9yZXN1bHRzJGV2YWx1YXRpb25fbG9nJHRlc3Rfcm1zZV9tZWFuKQogIAogIHJldHVybihsaXN0KHJtc2UgPSBiZXN0X3Jtc2UsIG5yb3VuZHMgPSBiZXN0X25yb3VuZHMsIHBhcmFtcyA9IHBhcmFtcykpCn0KCiMgSW5pY2lhbGl6YXIgdGFibGEgcGFyYSBhbG1hY2VuYXIgcmVzdWx0YWRvcwpyZXN1bHRhZG9zX2dyaWQgPC0gZGF0YS5mcmFtZSgKICBldGEgPSBudW1lcmljKG5yb3cocGFyYW1fZ3JpZF9yZWR1Y2lkYSkpLAogIG1heF9kZXB0aCA9IG51bWVyaWMobnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSksCiAgc3Vic2FtcGxlID0gbnVtZXJpYyhucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpKSwKICBjb2xzYW1wbGVfYnl0cmVlID0gbnVtZXJpYyhucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpKSwKICBtaW5fY2hpbGRfd2VpZ2h0ID0gbnVtZXJpYyhucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpKSwKICBnYW1tYSA9IG51bWVyaWMobnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSksCiAgbnJvdW5kcyA9IG51bWVyaWMobnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSksCiAgcm1zZSA9IG51bWVyaWMobnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSkKKQoKCgoKCiMgUmVhbGl6YXIgbGEgYsO6c3F1ZWRhIGVuIGN1YWRyw61jdWxhIChlc3RvIHB1ZWRlIHRhcmRhciB2YXJpb3MgbWludXRvcykKY2F0KCJJbmljaWFuZG8gYsO6c3F1ZWRhIGVuIGN1YWRyw61jdWxhLi4uXG4iKQoKZm9yIChpIGluIDE6bnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSkgewogIGNhdChzcHJpbnRmKCJFdmFsdWFuZG8gY29tYmluYWNpw7NuICVkIGRlICVkXG4iLCBpLCBucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpKSkKICAKICAjIE9idGVuZXIgZmlsYSBkZSBwYXLDoW1ldHJvcyBhY3R1YWwKICBwYXJhbXNfcm93IDwtIHBhcmFtX2dyaWRfcmVkdWNpZGFbaSwgXQogIAogICMgRXZhbHVhciBjb21iaW5hY2nDs24gYWN0dWFsCiAgcmVzdWx0IDwtIGV2YWx1YXRlX3BhcmFtcyhwYXJhbXNfcm93KQogIAogICMgR3VhcmRhciByZXN1bHRhZG9zCiAgcmVzdWx0YWRvc19ncmlkJGV0YVtpXSA8LSBwYXJhbXNfcm93JGV0YQogIHJlc3VsdGFkb3NfZ3JpZCRtYXhfZGVwdGhbaV0gPC0gcGFyYW1zX3JvdyRtYXhfZGVwdGgKICByZXN1bHRhZG9zX2dyaWQkc3Vic2FtcGxlW2ldIDwtIHBhcmFtc19yb3ckc3Vic2FtcGxlCiAgcmVzdWx0YWRvc19ncmlkJGNvbHNhbXBsZV9ieXRyZWVbaV0gPC0gcGFyYW1zX3JvdyRjb2xzYW1wbGVfYnl0cmVlCiAgcmVzdWx0YWRvc19ncmlkJG1pbl9jaGlsZF93ZWlnaHRbaV0gPC0gcGFyYW1zX3JvdyRtaW5fY2hpbGRfd2VpZ2h0CiAgcmVzdWx0YWRvc19ncmlkJGdhbW1hW2ldIDwtIHBhcmFtc19yb3ckZ2FtbWEKICByZXN1bHRhZG9zX2dyaWQkbnJvdW5kc1tpXSA8LSByZXN1bHQkbnJvdW5kcwogIHJlc3VsdGFkb3NfZ3JpZCRybXNlW2ldIDwtIHJlc3VsdCRybXNlCn0KCiMgT3JkZW5hciByZXN1bHRhZG9zIHBvciBSTVNFIChkZSBtZW5vciBhIG1heW9yKQpyZXN1bHRhZG9zX2dyaWQgPC0gcmVzdWx0YWRvc19ncmlkW29yZGVyKHJlc3VsdGFkb3NfZ3JpZCRybXNlKSwgXQoKIyBNb3N0cmFyIGxvcyA1IG1lam9yZXMgY29uanVudG9zIGRlIGhpcGVycGFyw6FtZXRyb3MKY2F0KCJcbkxvcyA1IG1lam9yZXMgY29uanVudG9zIGRlIGhpcGVycGFyw6FtZXRyb3M6XG4iKQpwcmludChoZWFkKHJlc3VsdGFkb3NfZ3JpZCwgNSkpCgojIE9idGVuZXIgbG9zIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcwptZWpvcmVzX3BhcmFtcyA8LSBsaXN0KAogIG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIiwKICBldmFsX21ldHJpYyA9ICJybXNlIiwKICBldGEgPSByZXN1bHRhZG9zX2dyaWQkZXRhWzFdLAogIG1heF9kZXB0aCA9IHJlc3VsdGFkb3NfZ3JpZCRtYXhfZGVwdGhbMV0sCiAgc3Vic2FtcGxlID0gcmVzdWx0YWRvc19ncmlkJHN1YnNhbXBsZVsxXSwKICBjb2xzYW1wbGVfYnl0cmVlID0gcmVzdWx0YWRvc19ncmlkJGNvbHNhbXBsZV9ieXRyZWVbMV0sCiAgbWluX2NoaWxkX3dlaWdodCA9IHJlc3VsdGFkb3NfZ3JpZCRtaW5fY2hpbGRfd2VpZ2h0WzFdLAogIGdhbW1hID0gcmVzdWx0YWRvc19ncmlkJGdhbW1hWzFdCikKCm1lam9yX25yb3VuZHMgPC0gcmVzdWx0YWRvc19ncmlkJG5yb3VuZHNbMV0KCmNhdCgiXG5NZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MgZW5jb250cmFkb3M6XG4iKQpwcmludChtZWpvcmVzX3BhcmFtcykKY2F0KCJOw7ptZXJvIMOzcHRpbW8gZGUgcm9uZGFzOiIsIG1lam9yX25yb3VuZHMsICJcbiIpCmNhdCgiUk1TRSBlbiB2YWxpZGFjacOzbiBjcnV6YWRhOiIsIHJlc3VsdGFkb3NfZ3JpZCRybXNlWzFdLCAiXG5cbiIpCgojIEVudHJlbmFyIGVsIG1vZGVsbyBmaW5hbCBjb24gbG9zIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcwptb2RlbG9feGdiXzE1NTAwMSA8LSB4Z2IudHJhaW4oCiAgcGFyYW1zID0gbWVqb3Jlc19wYXJhbXMsCiAgZGF0YSA9IGR0cmFpbiwKICBucm91bmRzID0gbWVqb3JfbnJvdW5kcywKICB3YXRjaGxpc3QgPSBsaXN0KHRyYWluID0gZHRyYWluLCB0ZXN0ID0gZHRlc3QpLAogIHZlcmJvc2UgPSAwCikKCiMgSGFjZXIgcHJlZGljY2lvbmVzIGVuIGVsIGNvbmp1bnRvIGRlIHBydWViYQpwcmVkaWNjaW9uZXNfdGVzdCA8LSBwcmVkaWN0KG1vZGVsb194Z2JfMTU1MDAxLCBkdGVzdCkKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzIGVuIGVsIGNvbmp1bnRvIGRlIHBydWViYQojIE1BUEUKbWFwZV90ZXN0IDwtIG1lYW4oYWJzKCh5X3Rlc3QgLSBwcmVkaWNjaW9uZXNfdGVzdCkgLyBwbWF4KHlfdGVzdCwgMC4wMSkpKSAqIDEwMAoKIyBSTVNFCnJtc2VfdGVzdCA8LSBzcXJ0KG1lYW4oKHlfdGVzdCAtIHByZWRpY2Npb25lc190ZXN0KV4yKSkKCgojIE1vc3RyYXIgbGFzIG3DqXRyaWNhcyBlbiBlbCBjb25qdW50byBkZSBwcnVlYmEKY2F0KCJNw6l0cmljYXMgZW4gZWwgY29uanVudG8gZGUgcHJ1ZWJhOlxuIikKY2F0KCJNQVBFIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBtYXBlX3Rlc3QsICJcbiIpCmNhdCgiUk1TRSBkZWwgbW9kZWxvIFhHQm9vc3Q6Iiwgcm1zZV90ZXN0LCAiXG5cbiIpCgoKIyBBaG9yYSBoYWNlciBwcmVkaWNjaW9uZXMgZW4gZWwgY29uanVudG8gY29tcGxldG8gcGFyYSBjb21wYXJhYmlsaWRhZCBjb24gb3Ryb3MgbW9kZWxvcwpYX2NvbXBsZXRvIDwtIGFzLm1hdHJpeChkYXRvc19tb2RlbG9bLCBjb2xuYW1lcyhkYXRvc19tb2RlbG8pICE9ICJWZW50YSJdKQpwcmVkaWNjaW9uZXNfY29tcGxldGFzIDwtIHByZWRpY3QobW9kZWxvX3hnYl8xNTUwMDEsIFhfY29tcGxldG8pCgoKCgoKCgojIENhbGN1bGFyIG3DqXRyaWNhcyBlbiBlbCBjb25qdW50byBjb21wbGV0bwojIE1BUEUKbWFwZV9jb21wbGV0byA8LSBtZWFuKGFicygoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX2NvbXBsZXRhcykgLyBwbWF4KGRhdG9zX21vZGVsbyRWZW50YSwgMC4wMSkpKSAqIDEwMAoKIyBNU0UKcm1zZV9jb21wbGV0byA8LSBzcXJ0KG1lYW4oKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0YXMpXjIpKQoKCgojIE1vc3RyYXIgbGFzIG3DqXRyaWNhcyBlbiBlbCBjb25qdW50byBjb21wbGV0bwpjYXQoIk3DqXRyaWNhcyBlbiBlbCBjb25qdW50byBjb21wbGV0bzpcbiIpCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgbWFwZV9jb21wbGV0bywgIlxuIikKY2F0KCJSTVNFIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBybXNlX2NvbXBsZXRvLCAiXG5cbiIpCgoKIyBJbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKaW1wb3J0YW5jaWEgPC0geGdiLmltcG9ydGFuY2UoCiAgZmVhdHVyZV9uYW1lcyA9IGNvbG5hbWVzKGRhdG9zX21vZGVsbylbY29sbmFtZXMoZGF0b3NfbW9kZWxvKSAhPSAiVmVudGEiXSwKICBtb2RlbCA9IG1vZGVsb194Z2JfMTU1MDAxCikKcHJpbnQoaW1wb3J0YW5jaWEpCgojIEdyYWZpY2FyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwp4Z2IucGxvdC5pbXBvcnRhbmNlKGltcG9ydGFuY2VfbWF0cml4ID0gaW1wb3J0YW5jaWEsIAogICAgICAgICAgICAgICAgICAgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAxNTUwMDEgKFhHQm9vc3QpIikKCiMgQ3JlYXIgZ3LDoWZpY28gZGUgdmFsb3JlcyBvYnNlcnZhZG9zIHZzIHByZWRpY2Npb25lcwpkYXRvc19ncmFmaWNvIDwtIGRhdGEuZnJhbWUoCiAgT2JzZXJ2YWRvID0gZGF0b3NfbW9kZWxvJFZlbnRhLAogIFByZWRpY2hvID0gcHJlZGljY2lvbmVzX2NvbXBsZXRhcwopCgpnZ3Bsb3QoZGF0b3NfZ3JhZmljbywgYWVzKHggPSBPYnNlcnZhZG8sIHkgPSBQcmVkaWNobykpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicygKICAgIHRpdGxlID0gIlZhbG9yZXMgT2JzZXJ2YWRvcyB2cyBQcmVkaWNjaW9uZXMgLSBQcm9kdWN0byAxNTUwMDEgKFhHQm9vc3QpIiwKICAgIHggPSAiVmVudGFzIE9ic2VydmFkYXMiLAogICAgeSA9ICJWZW50YXMgUHJlZGljaGFzIgogICkgKwogIHRoZW1lX21pbmltYWwoKQoKCgoKCgoKCgoKCiMgQW7DoWxpc2lzIGRlbCBlcnJvcgplcnJvcmVzIDwtIGRhdG9zX2dyYWZpY28kT2JzZXJ2YWRvIC0gZGF0b3NfZ3JhZmljbyRQcmVkaWNobwpoaXN0KGVycm9yZXMsIAogICAgIG1haW4gPSAiRGlzdHJpYnVjacOzbiBkZSBFcnJvcmVzIC0gUHJvZHVjdG8gMTU1MDAxIChYR0Jvb3N0KSIsCiAgICAgeGxhYiA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIiwKICAgICBjb2wgPSAic2t5Ymx1ZSIsCiAgICAgYnJlYWtzID0gMzApCgojIEdyw6FmaWNvIGRlbCBlcnJvciB2cyBwcmVkaWNjacOzbgpnZ3Bsb3QoZGF0YS5mcmFtZShQcmVkaWNobyA9IHByZWRpY2Npb25lc19jb21wbGV0YXMsIEVycm9yID0gZXJyb3JlcyksIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJFcnJvciB2cyBQcmVkaWNjacOzbiAtIFByb2R1Y3RvIDE1NTAwMSAoWEdCb29zdCkiLAogICAgeCA9ICJWZW50YXMgUHJlZGljaGFzIiwKICAgIHkgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIKICApICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCmBgYHtyfQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFhHQm9vc3QgcGFyYSBwcm9kdWN0byAxNTUwMDEKaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1BUEUgPSBudW1lcmljKCksCiAgICBSTVNFID0gbnVtZXJpYygpLAogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKQp9CgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjE1NTAwMSIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bwogIE1vZGVsbyA9ICJYR0Jvb3N0IiwKICBNQVBFID0gbWFwZV9jb21wbGV0bywKICBSTVNFID0gcm1zZV9jb21wbGV0bwoKKSkKYGBgCgpgYGB7cn0KCmBgYAoKCgojIyBQUk9EVUNUTyAzOTI5Nzg4CgpgYGB7cn0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykKZGF0b3NfbW9kZWxvIDwtIGRhdG9zXzM5Mjk3ODggJT4lCiAgc2VsZWN0KC1GZWNoYSkKCiMgUGFzbyAyOiBEaXZpZGlyIGxvcyBkYXRvcyBlbiBjb25qdW50b3MgZGUgZW50cmVuYW1pZW50byAoODAlKSB5IHBydWViYSAoMjAlKQpzZXQuc2VlZCgxMjMpICAjIFBhcmEgcmVwcm9kdWNpYmlsaWRhZAp0cmFpbl9pbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGRhdG9zX21vZGVsbyRWZW50YSwgcCA9IDAuOCwgbGlzdCA9IEZBTFNFKQp0cmFpbl9kYXRhIDwtIGRhdG9zX21vZGVsb1t0cmFpbl9pbmRleCwgXQp0ZXN0X2RhdGEgPC0gZGF0b3NfbW9kZWxvWy10cmFpbl9pbmRleCwgXQoKIyBQcmVwYXJhciBtYXRyaWNlcyBwYXJhIFhHQm9vc3QKdHJhaW5feCA8LSBhcy5tYXRyaXgodHJhaW5fZGF0YVssIGNvbG5hbWVzKHRyYWluX2RhdGEpICE9ICJWZW50YSJdKQp0cmFpbl95IDwtIHRyYWluX2RhdGEkVmVudGEKCnRlc3RfeCA8LSBhcy5tYXRyaXgodGVzdF9kYXRhWywgY29sbmFtZXModGVzdF9kYXRhKSAhPSAiVmVudGEiXSkKdGVzdF95IDwtIHRlc3RfZGF0YSRWZW50YQoKIyBDcmVhciBETWF0cml4IHBhcmEgWEdCb29zdApkdHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IHRyYWluX3gsIGxhYmVsID0gdHJhaW5feSkKZHRlc3QgPC0geGdiLkRNYXRyaXgoZGF0YSA9IHRlc3RfeCwgbGFiZWwgPSB0ZXN0X3kpCgojIFBhc28gMzogRGVmaW5pciBsYSByZWppbGxhIGRlIGhpcGVycGFyw6FtZXRyb3MgcGFyYSBHcmlkIFNlYXJjaApwYXJhbV9ncmlkIDwtIGV4cGFuZC5ncmlkKAogIGV0YSA9IGMoMC4wMSwgMC4wNSwgMC4xLCAwLjMpLCAgICAgICAgICAjIExlYXJuaW5nIHJhdGUKICBtYXhfZGVwdGggPSBjKDMsIDYsIDkpLCAgICAgICAgICAgICAgICAgIyBQcm9mdW5kaWRhZCBtw6F4aW1hCiAgbWluX2NoaWxkX3dlaWdodCA9IGMoMSwgMywgNSksICAgICAgICAgICMgUGVzbyBtw61uaW1vIGRlIG5vZG8gaGlqbwogIHN1YnNhbXBsZSA9IGMoMC43LCAwLjkpLCAgICAgICAgICAgICAgICAjIFByb3BvcmNpw7NuIGRlIG9ic2VydmFjaW9uZXMKICBjb2xzYW1wbGVfYnl0cmVlID0gYygwLjcsIDAuOSksICAgICAgICAgIyBQcm9wb3JjacOzbiBkZSB2YXJpYWJsZXMKICBnYW1tYSA9IGMoMCwgMC4xLCAwLjMpICAgICAgICAgICAgICAgICAgIyBSZWd1bGFyaXphY2nDs24gZ2FtbWEKKQoKIyBNb3N0cmFyIGRpbWVuc2lvbmVzIGRlIGxhIHJlamlsbGEKY2F0KCJHcmlkIFNlYXJjaCBwYXJhIFhHQm9vc3QgLSBQcm9kdWN0byAzOTI5Nzg4XG4iKQpjYXQoIk7Dum1lcm8gdG90YWwgZGUgY29tYmluYWNpb25lcyBkZSBoaXBlcnBhcsOhbWV0cm9zOiIsIG5yb3cocGFyYW1fZ3JpZCksICJcblxuIikKCiMgUGFyYSBlc3RlIGVqZW1wbG8sIGxpbWl0YXIgYSAxMiBjb21iaW5hY2lvbmVzIHBhcmEgYWhvcnJhciB0aWVtcG8KIyBFbiB1biBlc2NlbmFyaW8gcmVhbCwgcG9kcsOtYXMgZXZhbHVhciB0b2RhcyBvIHVzYXIgdW5hIGVzdHJhdGVnaWEgbcOhcyBlZmljaWVudGUKc2V0LnNlZWQoNDU2KQppZiAobnJvdyhwYXJhbV9ncmlkKSA+IDEyKSB7CiAgc2VsZWN0ZWRfaW5kaWNlcyA8LSBzYW1wbGUoMTpucm93KHBhcmFtX2dyaWQpLCAxMikKICBwYXJhbV9ncmlkIDwtIHBhcmFtX2dyaWRbc2VsZWN0ZWRfaW5kaWNlcywgXQogIGNhdCgiU2VsZWNjaW9uYW5kbyAxMiBjb21iaW5hY2lvbmVzIGFsZWF0b3JpYXMgcGFyYSBldmFsdWFjacOzbi5cblxuIikKfQoKIyBQYXNvIDQ6IEltcGxlbWVudGFyIEdyaWQgU2VhcmNoCnJlc3VsdGFkb3MgPC0gZGF0YS5mcmFtZSgpCgpjYXQoIkluaWNpYW5kbyBHcmlkIFNlYXJjaC4uLlxuIikKCmZvciAoaSBpbiAxOm5yb3cocGFyYW1fZ3JpZCkpIHsKICAjIEV4dHJhZXIgcGFyw6FtZXRyb3MgZGUgbGEgY29tYmluYWNpw7NuIGFjdHVhbAogIHBhcmFtcyA8LSBsaXN0KAogICAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLCAgICAgICMgT2JqZXRpdm8gZGUgcmVncmVzacOzbgogICAgZXZhbF9tZXRyaWMgPSAicm1zZSIsICAgICAgICAgICAgICAgIyBNw6l0cmljYSBkZSBldmFsdWFjacOzbgogICAgZXRhID0gcGFyYW1fZ3JpZCRldGFbaV0sCiAgICBtYXhfZGVwdGggPSBwYXJhbV9ncmlkJG1heF9kZXB0aFtpXSwKICAgIG1pbl9jaGlsZF93ZWlnaHQgPSBwYXJhbV9ncmlkJG1pbl9jaGlsZF93ZWlnaHRbaV0sCiAgICBzdWJzYW1wbGUgPSBwYXJhbV9ncmlkJHN1YnNhbXBsZVtpXSwKICAgIGNvbHNhbXBsZV9ieXRyZWUgPSBwYXJhbV9ncmlkJGNvbHNhbXBsZV9ieXRyZWVbaV0sCiAgICBnYW1tYSA9IHBhcmFtX2dyaWQkZ2FtbWFbaV0KICApCiAgCiAgY2F0KCJFdmFsdWFuZG8gY29tYmluYWNpw7NuIiwgaSwgImRlIiwgbnJvdyhwYXJhbV9ncmlkKSwgIjpcbiIpCiAgY2F0KCIgIGV0YSA9IiwgcGFyYW1zJGV0YSwgCiAgICAgICIsIG1heF9kZXB0aCA9IiwgcGFyYW1zJG1heF9kZXB0aCwgCiAgICAgICIsIG1pbl9jaGlsZF93ZWlnaHQgPSIsIHBhcmFtcyRtaW5fY2hpbGRfd2VpZ2h0LCAKICAgICAgIiwgc3Vic2FtcGxlID0iLCBwYXJhbXMkc3Vic2FtcGxlLCAKICAgICAgIiwgY29sc2FtcGxlX2J5dHJlZSA9IiwgcGFyYW1zJGNvbHNhbXBsZV9ieXRyZWUsCiAgICAgICIsIGdhbW1hID0iLCBwYXJhbXMkZ2FtbWEsICJcbiIpCiAgCiAgIyBWYWxpZGFjacOzbiBjcnV6YWRhIHBhcmEgZW5jb250cmFyIGVsIG7Dum1lcm8gw7NwdGltbyBkZSBpdGVyYWNpb25lcwogIGN2X21vZGVsIDwtIHhnYi5jdigKICAgIHBhcmFtcyA9IHBhcmFtcywKICAgIGRhdGEgPSBkdHJhaW4sCiAgICBucm91bmRzID0gMjAwLCAgICAgICAgICAgICAgICAgICAgIyBNw6F4aW1vIG7Dum1lcm8gZGUgaXRlcmFjaW9uZXMKICAgIG5mb2xkID0gNSwgICAgICAgICAgICAgICAgICAgICAgICAjIDUtZm9sZCB2YWxpZGFjacOzbiBjcnV6YWRhCiAgICBlYXJseV9zdG9wcGluZ19yb3VuZHMgPSAyMCwgICAgICAgIyBEZXRlbmVyIHNpIG5vIGhheSBtZWpvcmEgZW4gMjAgcm9uZGFzCiAgICB2ZXJib3NlID0gMCAgICAgICAgICAgICAgICAgICAgICAgIyBTdXByaW1pciBtZW5zYWplcwogICkKICAKICAjIEV4dHJhZXIgbWVqb3IgaXRlcmFjacOzbiB5IHN1IFJNU0UKICBiZXN0X2l0ZXJhdGlvbiA8LSBjdl9tb2RlbCRiZXN0X2l0ZXJhdGlvbgogIGJlc3Rfcm1zZSA8LSBtaW4oY3ZfbW9kZWwkZXZhbHVhdGlvbl9sb2ckdGVzdF9ybXNlX21lYW4pCiAgCiAgY2F0KCIgIE1lam9yIGl0ZXJhY2nDs246IiwgYmVzdF9pdGVyYXRpb24sICJcbiIpCiAgY2F0KCIgIFJNU0UgZW4gdmFsaWRhY2nDs24gY3J1emFkYToiLCBiZXN0X3Jtc2UsICJcblxuIikKICAKICAjIEd1YXJkYXIgcmVzdWx0YWRvcwogIHJlc3VsdGFkb19hY3R1YWwgPC0gZGF0YS5mcmFtZSgKICAgIGV0YSA9IHBhcmFtcyRldGEsCiAgICBtYXhfZGVwdGggPSBwYXJhbXMkbWF4X2RlcHRoLAogICAgbWluX2NoaWxkX3dlaWdodCA9IHBhcmFtcyRtaW5fY2hpbGRfd2VpZ2h0LAogICAgc3Vic2FtcGxlID0gcGFyYW1zJHN1YnNhbXBsZSwKICAgIGNvbHNhbXBsZV9ieXRyZWUgPSBwYXJhbXMkY29sc2FtcGxlX2J5dHJlZSwKICAgIGdhbW1hID0gcGFyYW1zJGdhbW1hLAogICAgbnJvdW5kcyA9IGJlc3RfaXRlcmF0aW9uLAogICAgcm1zZV9jdiA9IGJlc3Rfcm1zZQogICkKICAKICByZXN1bHRhZG9zIDwtIHJiaW5kKHJlc3VsdGFkb3MsIHJlc3VsdGFkb19hY3R1YWwpCn0KCgoKCgoKCgoKCgoKIyBPcmRlbmFyIHJlc3VsdGFkb3MgcG9yIFJNU0UgKGRlIG1lbm9yIGEgbWF5b3IpCnJlc3VsdGFkb3MgPC0gcmVzdWx0YWRvc1tvcmRlcihyZXN1bHRhZG9zJHJtc2VfY3YpLCBdCgojIFBhc28gNTogTW9zdHJhciByZXN1bHRhZG9zIGRlbCBHcmlkIFNlYXJjaApjYXQoIlJlc3VsdGFkb3MgZGVsIEdyaWQgU2VhcmNoIG9yZGVuYWRvcyBwb3IgUk1TRTpcbiIpCnByaW50KHJlc3VsdGFkb3MpCgojIFZpc3VhbGl6YXIgcmVzdWx0YWRvcyBkZWwgR3JpZCBTZWFyY2gKZ2dwbG90KHJlc3VsdGFkb3MsIGFlcyh4ID0gcmVvcmRlcihwYXN0ZSgiQ29tYiIsIDE6bnJvdyhyZXN1bHRhZG9zKSksIHJtc2VfY3YpLCB5ID0gcm1zZV9jdikpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgZmlsbCA9ICJzdGVlbGJsdWUiKSArCiAgbGFicygKICAgIHRpdGxlID0gIlJlc3VsdGFkb3MgZGVsIEdyaWQgU2VhcmNoIC0gUHJvZHVjdG8gMzkyOTc4OCIsCiAgICB4ID0gIkNvbWJpbmFjacOzbiBkZSBIaXBlcnBhcsOhbWV0cm9zIiwKICAgIHkgPSAiUk1TRSBlbiBWYWxpZGFjacOzbiBDcnV6YWRhIgogICkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkKCiMgUGFzbyA2OiBTZWxlY2Npb25hciBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zCm1lam9yZXNfcGFyYW1zIDwtIGxpc3QoCiAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLAogIGV2YWxfbWV0cmljID0gInJtc2UiLAogIGV0YSA9IHJlc3VsdGFkb3MkZXRhWzFdLAogIG1heF9kZXB0aCA9IHJlc3VsdGFkb3MkbWF4X2RlcHRoWzFdLAogIG1pbl9jaGlsZF93ZWlnaHQgPSByZXN1bHRhZG9zJG1pbl9jaGlsZF93ZWlnaHRbMV0sCiAgc3Vic2FtcGxlID0gcmVzdWx0YWRvcyRzdWJzYW1wbGVbMV0sCiAgY29sc2FtcGxlX2J5dHJlZSA9IHJlc3VsdGFkb3MkY29sc2FtcGxlX2J5dHJlZVsxXSwKICBnYW1tYSA9IHJlc3VsdGFkb3MkZ2FtbWFbMV0KKQoKbWVqb3JfbnJvdW5kcyA8LSByZXN1bHRhZG9zJG5yb3VuZHNbMV0KCmNhdCgiXG5NZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MgZW5jb250cmFkb3M6XG4iKQpwcmludChtZWpvcmVzX3BhcmFtcykKY2F0KCJOw7ptZXJvIMOzcHRpbW8gZGUgcm9uZGFzOiIsIG1lam9yX25yb3VuZHMsICJcblxuIikKCiMgUGFzbyA3OiBFbnRyZW5hciBlbCBtb2RlbG8gZmluYWwgY29uIGxvcyBtZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MKY2F0KCJFbnRyZW5hbmRvIG1vZGVsbyBmaW5hbCBjb24gbG9zIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcy4uLlxuIikKCm1vZGVsb19maW5hbCA8LSB4Z2IudHJhaW4oCiAgcGFyYW1zID0gbWVqb3Jlc19wYXJhbXMsCiAgZGF0YSA9IGR0cmFpbiwKICBucm91bmRzID0gbWVqb3JfbnJvdW5kcywKICB3YXRjaGxpc3QgPSBsaXN0KHRyYWluID0gZHRyYWluLCB0ZXN0ID0gZHRlc3QpLAogIHZlcmJvc2UgPSAwCikKIyBHZW5lcmFyIHByZWRpY2Npb25lcyBzb2JyZSBUT0RPIGVsIGNvbmp1bnRvIGRlIGRhdG9zCnhfY29tcGxldG8gPC0gYXMubWF0cml4KGRhdG9zX21vZGVsb1ssIGNvbG5hbWVzKGRhdG9zX21vZGVsbykgIT0gIlZlbnRhIl0pCmRjb21wbGV0byA8LSB4Z2IuRE1hdHJpeChkYXRhID0geF9jb21wbGV0bykKcHJlZGljY2lvbmVzX2NvbXBsZXRvIDwtIHByZWRpY3QobW9kZWxvX2ZpbmFsLCBuZXdkYXRhID0gZGNvbXBsZXRvKQoKCmdncGxvdChkYXRvc19ncmFmaWNvLCBhZXMoeCA9IE9ic2VydmFkbywgeSA9IFByZWRpY2hvKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDM5Mjk3ODggKFhHQm9vc3QpIiwKICAgIHggPSAiVmVudGFzIE9ic2VydmFkYXMiLAogICAgeSA9ICJWZW50YXMgUHJlZGljaGFzIgogICkgKwogIHRoZW1lX21pbmltYWwoKQoKCgojIEdyw6FmaWNvIDI6IEFuw6FsaXNpcyBkZSByZXNpZHVvcwplcnJvcmVzIDwtIGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0bwoKIyBIaXN0b2dyYW1hIGRlIGVycm9yZXMKaGlzdChlcnJvcmVzLCAKICAgICBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDM5Mjk3ODggKFhHQm9vc3QpIiwKICAgICB4bGFiID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiLAogICAgIGNvbCA9ICJza3libHVlIiwKICAgICBicmVha3MgPSAzMCkKCiMgR3LDoWZpY28gMzogRXJyb3JlcyB2cyBQcmVkaWNjaW9uZXMKZ2dwbG90KGRhdGEuZnJhbWUoUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfY29tcGxldG8sIEVycm9yID0gZXJyb3JlcyksIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJFcnJvciB2cyBQcmVkaWNjacOzbiAtIFByb2R1Y3RvIDM5Mjk3ODggKFhHQm9vc3QpIiwKICAgIHggPSAiVmVudGFzIFByZWRpY2hhcyIsCiAgICB5ID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYApgYGB7cn0KIyBHdWFyZGFyIG3DqXRyaWNhcyBkZSBYR0Jvb3N0IHBhcmEgcHJvZHVjdG8gMTU1MDAxCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7CiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLAogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksCiAgICBNQVBFID0gbnVtZXJpYygpLAogICAgUk1TRSA9IG51bWVyaWMoKSwKICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQogICkKfQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIzOTI5Nzg4IiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvCiAgTW9kZWxvID0gIlhHQm9vc3QiLAogIE1BUEUgPSBtYXBlX2NvbXBsZXRvLAogIFJNU0UgPSBybXNlX2NvbXBsZXRvCikpCmBgYAoKCgoKCgoKCgoKCgoKIyMgUFJPRFVDVE8gMzkwNDE1MgpgYGB7cn0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykKZGF0b3NfbW9kZWxvIDwtIGRhdG9zXzM5MDQxNTIgJT4lCiAgc2VsZWN0KC1GZWNoYSkKCiMgUGFzbyAyOiBEaXZpZGlyIGxvcyBkYXRvcyBlbiBjb25qdW50b3MgZGUgZW50cmVuYW1pZW50byAoODAlKSB5IHBydWViYSAoMjAlKQpzZXQuc2VlZCgxMjMpICAjIFBhcmEgcmVwcm9kdWNpYmlsaWRhZAp0cmFpbl9pbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGRhdG9zX21vZGVsbyRWZW50YSwgcCA9IDAuOCwgbGlzdCA9IEZBTFNFKQp0cmFpbl9kYXRhIDwtIGRhdG9zX21vZGVsb1t0cmFpbl9pbmRleCwgXQp0ZXN0X2RhdGEgPC0gZGF0b3NfbW9kZWxvWy10cmFpbl9pbmRleCwgXQoKIyBQcmVwYXJhciBtYXRyaWNlcyBwYXJhIFhHQm9vc3QKdHJhaW5feCA8LSBhcy5tYXRyaXgodHJhaW5fZGF0YVssIGNvbG5hbWVzKHRyYWluX2RhdGEpICE9ICJWZW50YSJdKQp0cmFpbl95IDwtIHRyYWluX2RhdGEkVmVudGEKCnRlc3RfeCA8LSBhcy5tYXRyaXgodGVzdF9kYXRhWywgY29sbmFtZXModGVzdF9kYXRhKSAhPSAiVmVudGEiXSkKdGVzdF95IDwtIHRlc3RfZGF0YSRWZW50YQoKIyBDcmVhciBETWF0cml4IHBhcmEgWEdCb29zdApkdHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IHRyYWluX3gsIGxhYmVsID0gdHJhaW5feSkKZHRlc3QgPC0geGdiLkRNYXRyaXgoZGF0YSA9IHRlc3RfeCwgbGFiZWwgPSB0ZXN0X3kpCgojIFBhc28gMzogRGVmaW5pciBsYSByZWppbGxhIGRlIGhpcGVycGFyw6FtZXRyb3MgcGFyYSBHcmlkIFNlYXJjaApwYXJhbV9ncmlkIDwtIGV4cGFuZC5ncmlkKAogIGV0YSA9IGMoMC4wMSwgMC4wNSwgMC4xLCAwLjMpLCAgICAgICAgICAjIExlYXJuaW5nIHJhdGUKICBtYXhfZGVwdGggPSBjKDMsIDYsIDkpLCAgICAgICAgICAgICAgICAgIyBQcm9mdW5kaWRhZCBtw6F4aW1hCiAgbWluX2NoaWxkX3dlaWdodCA9IGMoMSwgMywgNSksICAgICAgICAgICMgUGVzbyBtw61uaW1vIGRlIG5vZG8gaGlqbwogIHN1YnNhbXBsZSA9IGMoMC43LCAwLjkpLCAgICAgICAgICAgICAgICAjIFByb3BvcmNpw7NuIGRlIG9ic2VydmFjaW9uZXMKICBjb2xzYW1wbGVfYnl0cmVlID0gYygwLjcsIDAuOSksICAgICAgICAgIyBQcm9wb3JjacOzbiBkZSB2YXJpYWJsZXMKICBnYW1tYSA9IGMoMCwgMC4xLCAwLjMpICAgICAgICAgICAgICAgICAgIyBSZWd1bGFyaXphY2nDs24gZ2FtbWEKKQoKIyBNb3N0cmFyIGRpbWVuc2lvbmVzIGRlIGxhIHJlamlsbGEKY2F0KCJHcmlkIFNlYXJjaCBwYXJhIFhHQm9vc3QgLSBQcm9kdWN0byAzOTA0MTUyXG4iKQpjYXQoIk7Dum1lcm8gdG90YWwgZGUgY29tYmluYWNpb25lcyBkZSBoaXBlcnBhcsOhbWV0cm9zOiIsIG5yb3cocGFyYW1fZ3JpZCksICJcblxuIikKCiMgUGFyYSBlc3RlIGVqZW1wbG8sIGxpbWl0YXIgYSAxMiBjb21iaW5hY2lvbmVzIHBhcmEgYWhvcnJhciB0aWVtcG8KIyBFbiB1biBlc2NlbmFyaW8gcmVhbCwgcG9kcsOtYXMgZXZhbHVhciB0b2RhcyBvIHVzYXIgdW5hIGVzdHJhdGVnaWEgbcOhcyBlZmljaWVudGUKc2V0LnNlZWQoNDU2KQppZiAobnJvdyhwYXJhbV9ncmlkKSA+IDEyKSB7CiAgc2VsZWN0ZWRfaW5kaWNlcyA8LSBzYW1wbGUoMTpucm93KHBhcmFtX2dyaWQpLCAxMikKICBwYXJhbV9ncmlkIDwtIHBhcmFtX2dyaWRbc2VsZWN0ZWRfaW5kaWNlcywgXQogIGNhdCgiU2VsZWNjaW9uYW5kbyAxMiBjb21iaW5hY2lvbmVzIGFsZWF0b3JpYXMgcGFyYSBldmFsdWFjacOzbi5cblxuIikKfQoKIyBQYXNvIDQ6IEltcGxlbWVudGFyIEdyaWQgU2VhcmNoCnJlc3VsdGFkb3MgPC0gZGF0YS5mcmFtZSgpCgpjYXQoIkluaWNpYW5kbyBHcmlkIFNlYXJjaC4uLlxuIikKCmZvciAoaSBpbiAxOm5yb3cocGFyYW1fZ3JpZCkpIHsKICAjIEV4dHJhZXIgcGFyw6FtZXRyb3MgZGUgbGEgY29tYmluYWNpw7NuIGFjdHVhbAogIHBhcmFtcyA8LSBsaXN0KAogICAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLCAgICAgICMgT2JqZXRpdm8gZGUgcmVncmVzacOzbgogICAgZXZhbF9tZXRyaWMgPSAicm1zZSIsICAgICAgICAgICAgICAgIyBNw6l0cmljYSBkZSBldmFsdWFjacOzbgogICAgZXRhID0gcGFyYW1fZ3JpZCRldGFbaV0sCiAgICBtYXhfZGVwdGggPSBwYXJhbV9ncmlkJG1heF9kZXB0aFtpXSwKICAgIG1pbl9jaGlsZF93ZWlnaHQgPSBwYXJhbV9ncmlkJG1pbl9jaGlsZF93ZWlnaHRbaV0sCiAgICBzdWJzYW1wbGUgPSBwYXJhbV9ncmlkJHN1YnNhbXBsZVtpXSwKICAgIGNvbHNhbXBsZV9ieXRyZWUgPSBwYXJhbV9ncmlkJGNvbHNhbXBsZV9ieXRyZWVbaV0sCiAgICBnYW1tYSA9IHBhcmFtX2dyaWQkZ2FtbWFbaV0KICApCiAgCiAgY2F0KCJFdmFsdWFuZG8gY29tYmluYWNpw7NuIiwgaSwgImRlIiwgbnJvdyhwYXJhbV9ncmlkKSwgIjpcbiIpCiAgY2F0KCIgIGV0YSA9IiwgcGFyYW1zJGV0YSwgCiAgICAgICIsIG1heF9kZXB0aCA9IiwgcGFyYW1zJG1heF9kZXB0aCwgCiAgICAgICIsIG1pbl9jaGlsZF93ZWlnaHQgPSIsIHBhcmFtcyRtaW5fY2hpbGRfd2VpZ2h0LCAKICAgICAgIiwgc3Vic2FtcGxlID0iLCBwYXJhbXMkc3Vic2FtcGxlLCAKICAgICAgIiwgY29sc2FtcGxlX2J5dHJlZSA9IiwgcGFyYW1zJGNvbHNhbXBsZV9ieXRyZWUsCiAgICAgICIsIGdhbW1hID0iLCBwYXJhbXMkZ2FtbWEsICJcbiIpCiAgCiAgIyBWYWxpZGFjacOzbiBjcnV6YWRhIHBhcmEgZW5jb250cmFyIGVsIG7Dum1lcm8gw7NwdGltbyBkZSBpdGVyYWNpb25lcwogIGN2X21vZGVsIDwtIHhnYi5jdigKICAgIHBhcmFtcyA9IHBhcmFtcywKICAgIGRhdGEgPSBkdHJhaW4sCiAgICBucm91bmRzID0gMjAwLCAgICAgICAgICAgICAgICAgICAgIyBNw6F4aW1vIG7Dum1lcm8gZGUgaXRlcmFjaW9uZXMKICAgIG5mb2xkID0gNSwgICAgICAgICAgICAgICAgICAgICAgICAjIDUtZm9sZCB2YWxpZGFjacOzbiBjcnV6YWRhCiAgICBlYXJseV9zdG9wcGluZ19yb3VuZHMgPSAyMCwgICAgICAgIyBEZXRlbmVyIHNpIG5vIGhheSBtZWpvcmEgZW4gMjAgcm9uZGFzCiAgICB2ZXJib3NlID0gMCAgICAgICAgICAgICAgICAgICAgICAgIyBTdXByaW1pciBtZW5zYWplcwogICkKICAKICAjIEV4dHJhZXIgbWVqb3IgaXRlcmFjacOzbiB5IHN1IFJNU0UKICBiZXN0X2l0ZXJhdGlvbiA8LSBjdl9tb2RlbCRiZXN0X2l0ZXJhdGlvbgogIGJlc3Rfcm1zZSA8LSBtaW4oY3ZfbW9kZWwkZXZhbHVhdGlvbl9sb2ckdGVzdF9ybXNlX21lYW4pCiAgCiAgY2F0KCIgIE1lam9yIGl0ZXJhY2nDs246IiwgYmVzdF9pdGVyYXRpb24sICJcbiIpCiAgY2F0KCIgIFJNU0UgZW4gdmFsaWRhY2nDs24gY3J1emFkYToiLCBiZXN0X3Jtc2UsICJcblxuIikKICAKICAjIEd1YXJkYXIgcmVzdWx0YWRvcwogIHJlc3VsdGFkb19hY3R1YWwgPC0gZGF0YS5mcmFtZSgKICAgIGV0YSA9IHBhcmFtcyRldGEsCiAgICBtYXhfZGVwdGggPSBwYXJhbXMkbWF4X2RlcHRoLAogICAgbWluX2NoaWxkX3dlaWdodCA9IHBhcmFtcyRtaW5fY2hpbGRfd2VpZ2h0LAogICAgc3Vic2FtcGxlID0gcGFyYW1zJHN1YnNhbXBsZSwKICAgIGNvbHNhbXBsZV9ieXRyZWUgPSBwYXJhbXMkY29sc2FtcGxlX2J5dHJlZSwKICAgIGdhbW1hID0gcGFyYW1zJGdhbW1hLAogICAgbnJvdW5kcyA9IGJlc3RfaXRlcmF0aW9uLAogICAgcm1zZV9jdiA9IGJlc3Rfcm1zZQogICkKICAKICByZXN1bHRhZG9zIDwtIHJiaW5kKHJlc3VsdGFkb3MsIHJlc3VsdGFkb19hY3R1YWwpCn0KCiMgT3JkZW5hciByZXN1bHRhZG9zIHBvciBSTVNFIChkZSBtZW5vciBhIG1heW9yKQpyZXN1bHRhZG9zIDwtIHJlc3VsdGFkb3Nbb3JkZXIocmVzdWx0YWRvcyRybXNlX2N2KSwgXQoKIyBQYXNvIDU6IE1vc3RyYXIgcmVzdWx0YWRvcyBkZWwgR3JpZCBTZWFyY2gKY2F0KCJSZXN1bHRhZG9zIGRlbCBHcmlkIFNlYXJjaCBvcmRlbmFkb3MgcG9yIFJNU0U6XG4iKQpwcmludChyZXN1bHRhZG9zKQoKIyBWaXN1YWxpemFyIHJlc3VsdGFkb3MgZGVsIEdyaWQgU2VhcmNoCmdncGxvdChyZXN1bHRhZG9zLCBhZXMoeCA9IHJlb3JkZXIocGFzdGUoIkNvbWIiLCAxOm5yb3cocmVzdWx0YWRvcykpLCBybXNlX2N2KSwgeSA9IHJtc2VfY3YpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAic3RlZWxibHVlIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJSZXN1bHRhZG9zIGRlbCBHcmlkIFNlYXJjaCAtIFByb2R1Y3RvIDM5MDQxNTIiLAogICAgeCA9ICJDb21iaW5hY2nDs24gZGUgSGlwZXJwYXLDoW1ldHJvcyIsCiAgICB5ID0gIlJNU0UgZW4gVmFsaWRhY2nDs24gQ3J1emFkYSIKICApICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCgojIFBhc28gNjogU2VsZWNjaW9uYXIgbG9zIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcwptZWpvcmVzX3BhcmFtcyA8LSBsaXN0KAogIG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIiwKICBldmFsX21ldHJpYyA9ICJybXNlIiwKICBldGEgPSByZXN1bHRhZG9zJGV0YVsxXSwKICBtYXhfZGVwdGggPSByZXN1bHRhZG9zJG1heF9kZXB0aFsxXSwKICBtaW5fY2hpbGRfd2VpZ2h0ID0gcmVzdWx0YWRvcyRtaW5fY2hpbGRfd2VpZ2h0WzFdLAogIHN1YnNhbXBsZSA9IHJlc3VsdGFkb3Mkc3Vic2FtcGxlWzFdLAogIGNvbHNhbXBsZV9ieXRyZWUgPSByZXN1bHRhZG9zJGNvbHNhbXBsZV9ieXRyZWVbMV0sCiAgZ2FtbWEgPSByZXN1bHRhZG9zJGdhbW1hWzFdCikKCm1lam9yX25yb3VuZHMgPC0gcmVzdWx0YWRvcyRucm91bmRzWzFdCgpjYXQoIlxuTWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zIGVuY29udHJhZG9zOlxuIikKcHJpbnQobWVqb3Jlc19wYXJhbXMpCmNhdCgiTsO6bWVybyDDs3B0aW1vIGRlIHJvbmRhczoiLCBtZWpvcl9ucm91bmRzLCAiXG5cbiIpCgojIFBhc28gNzogRW50cmVuYXIgZWwgbW9kZWxvIGZpbmFsIGNvbiBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zCmNhdCgiRW50cmVuYW5kbyBtb2RlbG8gZmluYWwgY29uIGxvcyBtZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MuLi5cbiIpCgptb2RlbG9fZmluYWwgPC0geGdiLnRyYWluKAogIHBhcmFtcyA9IG1lam9yZXNfcGFyYW1zLAogIGRhdGEgPSBkdHJhaW4sCiAgbnJvdW5kcyA9IG1lam9yX25yb3VuZHMsCiAgd2F0Y2hsaXN0ID0gbGlzdCh0cmFpbiA9IGR0cmFpbiwgdGVzdCA9IGR0ZXN0KSwKICB2ZXJib3NlID0gMAopCgptb2RlbG9feGdiXzM5MDQxNTIgPC0gbW9kZWxvX2ZpbmFsCgoKIyBQYXNvIDg6IEV2YWx1YXIgZWwgbW9kZWxvCiMgUHJlZGljY2lvbmVzIGVuIGNvbmp1bnRvIGRlIHBydWViYQpwcmVkaWNjaW9uZXNfdGVzdCA8LSBwcmVkaWN0KG1vZGVsb19maW5hbCwgZHRlc3QpCgojIENhbGN1bGFyIG3DqXRyaWNhcwojIE1BUEUKbWFwZV90ZXN0IDwtIG1lYW4oYWJzKCh0ZXN0X3kgLSBwcmVkaWNjaW9uZXNfdGVzdCkgLyBwbWF4KHRlc3RfeSwgMC4wMSkpKSAqIDEwMAoKIyBSTVNFCnJtc2VfdGVzdCA8LSBzcXJ0KG1lYW4oKHRlc3RfeSAtIHByZWRpY2Npb25lc190ZXN0KV4yKSkKCgojIE1vc3RyYXIgbcOpdHJpY2FzIGVuIGNvbmp1bnRvIGRlIHBydWViYQpjYXQoIlxuTcOpdHJpY2FzIGVuIGNvbmp1bnRvIGRlIHBydWViYTpcbiIpCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgbWFwZV90ZXN0LCAiXG4iKQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIHJtc2VfdGVzdCwgIlxuXG4iKQoKIyBQYXNvIDk6IFByZWRpY2Npb25lcyBlbiBlbCBjb25qdW50byBjb21wbGV0bwojIEhhY2VyIHByZWRpY2Npb25lcyBlbiB0b2RvIGVsIGNvbmp1bnRvIGRlIGRhdG9zIHBhcmEgY29tcGFyYWNpw7NuIGNvbiBvdHJvcyBtb2RlbG9zCnhfY29tcGxldG8gPC0gYXMubWF0cml4KGRhdG9zX21vZGVsb1ssIGNvbG5hbWVzKGRhdG9zX21vZGVsbykgIT0gIlZlbnRhIl0pCnByZWRpY2Npb25lc19jb21wbGV0byA8LSBwcmVkaWN0KG1vZGVsb19maW5hbCwgeF9jb21wbGV0bykKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzIGVuIGNvbmp1bnRvIGNvbXBsZXRvCm1hcGVfY29tcGxldG8gPC0gbWVhbihhYnMoKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0bykgLyAKICAgICAgICAgICAgICAgICAgICAgICAgcG1heChkYXRvc19tb2RlbG8kVmVudGEsIDAuMDEpKSkgKiAxMDAKCm1zZV9jb21wbGV0byA8LSBtZWFuKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfY29tcGxldG8pXjIpCgojIE1vc3RyYXIgbcOpdHJpY2FzIGVuIGNvbmp1bnRvIGNvbXBsZXRvCmNhdCgiTcOpdHJpY2FzIGVuIGNvbmp1bnRvIGNvbXBsZXRvOlxuIikKY2F0KCJNQVBFIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBtYXBlX2NvbXBsZXRvLCAiXG4iKQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIHJtc2VfY29tcGxldG8sICJcblxuIikKCiMgUGFzbyAxMDogQW7DoWxpc2lzIGRlIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwojIENhbGN1bGFyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwppbXBvcnRhbmNpYSA8LSB4Z2IuaW1wb3J0YW5jZSgKICBmZWF0dXJlX25hbWVzID0gY29sbmFtZXMoZGF0b3NfbW9kZWxvKVtjb2xuYW1lcyhkYXRvc19tb2RlbG8pICE9ICJWZW50YSJdLAogIG1vZGVsID0gbW9kZWxvX2ZpbmFsCikKCiMgTW9zdHJhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKY2F0KCJJbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXM6XG4iKQpwcmludChpbXBvcnRhbmNpYSkKCiMgR3JhZmljYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCnhnYi5wbG90LmltcG9ydGFuY2UoaW1wb3J0YW5jZV9tYXRyaXggPSBpbXBvcnRhbmNpYSwgCiAgICAgICAgICAgICAgICAgICBtYWluID0gIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyAtIFByb2R1Y3RvIDM5MDQxNTIgKFhHQm9vc3QpIikKCiMgUGFzbyAxMTogVmlzdWFsaXphY2lvbmVzIHBhcmEgZXZhbHVhY2nDs24KIyBHcsOhZmljbyAxOiBWYWxvcmVzIG9ic2VydmFkb3MgdnMgcHJlZGljaG9zCmRhdG9zX2dyYWZpY28gPC0gZGF0YS5mcmFtZSgKICBPYnNlcnZhZG8gPSBkYXRvc19tb2RlbG8kVmVudGEsCiAgUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfY29tcGxldG8KKQoKZ2dwbG90KGRhdG9zX2dyYWZpY28sIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJWYWxvcmVzIE9ic2VydmFkb3MgdnMgUHJlZGljY2lvbmVzIC0gUHJvZHVjdG8gMzkwNDE1MiAoWEdCb29zdCkiLAogICAgeCA9ICJWZW50YXMgT2JzZXJ2YWRhcyIsCiAgICB5ID0gIlZlbnRhcyBQcmVkaWNoYXMiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIEdyw6FmaWNvIDI6IEFuw6FsaXNpcyBkZSByZXNpZHVvcwplcnJvcmVzIDwtIGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0bwoKIyBIaXN0b2dyYW1hIGRlIGVycm9yZXMKaGlzdChlcnJvcmVzLCAKICAgICBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDM5MDQxNTIgKFhHQm9vc3QpIiwKICAgICB4bGFiID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiLAogICAgIGNvbCA9ICJza3libHVlIiwKICAgICBicmVha3MgPSAzMCkKCiMgR3LDoWZpY28gMzogRXJyb3JlcyB2cyBQcmVkaWNjaW9uZXMKZ2dwbG90KGRhdGEuZnJhbWUoUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfY29tcGxldG8sIEVycm9yID0gZXJyb3JlcyksIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJFcnJvciB2cyBQcmVkaWNjacOzbiAtIFByb2R1Y3RvIDM5MDQxNTIgKFhHQm9vc3QpIiwKICAgIHggPSAiVmVudGFzIFByZWRpY2hhcyIsCiAgICB5ID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKYGBge3J9CiMgR3VhcmRhciBtw6l0cmljYXMgZGUgWEdCb29zdCBwYXJhIHByb2R1Y3RvIAppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgewogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLAogICAgTUFQRSA9IG51bWVyaWMoKSwKICAgIFJNU0UgPSBudW1lcmljKCksCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCn0KCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMzkwNDE1MiIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bwogIE1vZGVsbyA9ICJYR0Jvb3N0IiwKICBNQVBFID0gbWFwZV9jb21wbGV0bywKICBSTVNFID0gcm1zZV9jb21wbGV0bwopKQpgYGAKCiMjIFBST0RVQ1RPIDE1NTAwMgpgYGB7cn0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykKZGF0b3NfbW9kZWxvIDwtIGRhdG9zXzE1NTAwMiAlPiUKICBzZWxlY3QoLUZlY2hhKQoKIyBQYXNvIDI6IERpdmlkaXIgbG9zIGRhdG9zIGVuIGNvbmp1bnRvcyBkZSBlbnRyZW5hbWllbnRvICg4MCUpIHkgcHJ1ZWJhICgyMCUpCnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkCnRyYWluX2luZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGF0b3NfbW9kZWxvJFZlbnRhLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpCnRyYWluX2RhdGEgPC0gZGF0b3NfbW9kZWxvW3RyYWluX2luZGV4LCBdCnRlc3RfZGF0YSA8LSBkYXRvc19tb2RlbG9bLXRyYWluX2luZGV4LCBdCgojIFByZXBhcmFyIG1hdHJpY2VzIHBhcmEgWEdCb29zdAp0cmFpbl94IDwtIGFzLm1hdHJpeCh0cmFpbl9kYXRhWywgY29sbmFtZXModHJhaW5fZGF0YSkgIT0gIlZlbnRhIl0pCnRyYWluX3kgPC0gdHJhaW5fZGF0YSRWZW50YQoKdGVzdF94IDwtIGFzLm1hdHJpeCh0ZXN0X2RhdGFbLCBjb2xuYW1lcyh0ZXN0X2RhdGEpICE9ICJWZW50YSJdKQp0ZXN0X3kgPC0gdGVzdF9kYXRhJFZlbnRhCgojIENyZWFyIERNYXRyaXggcGFyYSBYR0Jvb3N0CmR0cmFpbiA8LSB4Z2IuRE1hdHJpeChkYXRhID0gdHJhaW5feCwgbGFiZWwgPSB0cmFpbl95KQpkdGVzdCA8LSB4Z2IuRE1hdHJpeChkYXRhID0gdGVzdF94LCBsYWJlbCA9IHRlc3RfeSkKCiMgUGFzbyAzOiBEZWZpbmlyIGxhIHJlamlsbGEgZGUgaGlwZXJwYXLDoW1ldHJvcyBwYXJhIEdyaWQgU2VhcmNoCnBhcmFtX2dyaWQgPC0gZXhwYW5kLmdyaWQoCiAgZXRhID0gYygwLjAxLCAwLjA1LCAwLjEsIDAuMyksICAgICAgICAgICMgTGVhcm5pbmcgcmF0ZQogIG1heF9kZXB0aCA9IGMoMywgNiwgOSksICAgICAgICAgICAgICAgICAjIFByb2Z1bmRpZGFkIG3DoXhpbWEKICBtaW5fY2hpbGRfd2VpZ2h0ID0gYygxLCAzLCA1KSwgICAgICAgICAgIyBQZXNvIG3DrW5pbW8gZGUgbm9kbyBoaWpvCiAgc3Vic2FtcGxlID0gYygwLjcsIDAuOSksICAgICAgICAgICAgICAgICMgUHJvcG9yY2nDs24gZGUgb2JzZXJ2YWNpb25lcwogIGNvbHNhbXBsZV9ieXRyZWUgPSBjKDAuNywgMC45KSwgICAgICAgICAjIFByb3BvcmNpw7NuIGRlIHZhcmlhYmxlcwogIGdhbW1hID0gYygwLCAwLjEsIDAuMykgICAgICAgICAgICAgICAgICAjIFJlZ3VsYXJpemFjacOzbiBnYW1tYQopCgojIE1vc3RyYXIgZGltZW5zaW9uZXMgZGUgbGEgcmVqaWxsYQpjYXQoIkdyaWQgU2VhcmNoIHBhcmEgWEdCb29zdCAtIFByb2R1Y3RvIDE1NTAwMlxuIikKY2F0KCJOw7ptZXJvIHRvdGFsIGRlIGNvbWJpbmFjaW9uZXMgZGUgaGlwZXJwYXLDoW1ldHJvczoiLCBucm93KHBhcmFtX2dyaWQpLCAiXG5cbiIpCgojIFBhcmEgZXN0ZSBlamVtcGxvLCBsaW1pdGFyIGEgMTIgY29tYmluYWNpb25lcyBwYXJhIGFob3JyYXIgdGllbXBvCiMgRW4gdW4gZXNjZW5hcmlvIHJlYWwsIHBvZHLDrWFzIGV2YWx1YXIgdG9kYXMgbyB1c2FyIHVuYSBlc3RyYXRlZ2lhIG3DoXMgZWZpY2llbnRlCnNldC5zZWVkKDQ1NikKaWYgKG5yb3cocGFyYW1fZ3JpZCkgPiAxMikgewogIHNlbGVjdGVkX2luZGljZXMgPC0gc2FtcGxlKDE6bnJvdyhwYXJhbV9ncmlkKSwgMTIpCiAgcGFyYW1fZ3JpZCA8LSBwYXJhbV9ncmlkW3NlbGVjdGVkX2luZGljZXMsIF0KICBjYXQoIlNlbGVjY2lvbmFuZG8gMTIgY29tYmluYWNpb25lcyBhbGVhdG9yaWFzIHBhcmEgZXZhbHVhY2nDs24uXG5cbiIpCn0KCiMgUGFzbyA0OiBJbXBsZW1lbnRhciBHcmlkIFNlYXJjaApyZXN1bHRhZG9zIDwtIGRhdGEuZnJhbWUoKQoKY2F0KCJJbmljaWFuZG8gR3JpZCBTZWFyY2guLi5cbiIpCgpmb3IgKGkgaW4gMTpucm93KHBhcmFtX2dyaWQpKSB7CiAgIyBFeHRyYWVyIHBhcsOhbWV0cm9zIGRlIGxhIGNvbWJpbmFjacOzbiBhY3R1YWwKICBwYXJhbXMgPC0gbGlzdCgKICAgIG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIiwgICAgICAjIE9iamV0aXZvIGRlIHJlZ3Jlc2nDs24KICAgIGV2YWxfbWV0cmljID0gInJtc2UiLCAgICAgICAgICAgICAgICMgTcOpdHJpY2EgZGUgZXZhbHVhY2nDs24KICAgIGV0YSA9IHBhcmFtX2dyaWQkZXRhW2ldLAogICAgbWF4X2RlcHRoID0gcGFyYW1fZ3JpZCRtYXhfZGVwdGhbaV0sCiAgICBtaW5fY2hpbGRfd2VpZ2h0ID0gcGFyYW1fZ3JpZCRtaW5fY2hpbGRfd2VpZ2h0W2ldLAogICAgc3Vic2FtcGxlID0gcGFyYW1fZ3JpZCRzdWJzYW1wbGVbaV0sCiAgICBjb2xzYW1wbGVfYnl0cmVlID0gcGFyYW1fZ3JpZCRjb2xzYW1wbGVfYnl0cmVlW2ldLAogICAgZ2FtbWEgPSBwYXJhbV9ncmlkJGdhbW1hW2ldCiAgKQogIAogIGNhdCgiRXZhbHVhbmRvIGNvbWJpbmFjacOzbiIsIGksICJkZSIsIG5yb3cocGFyYW1fZ3JpZCksICI6XG4iKQogIGNhdCgiICBldGEgPSIsIHBhcmFtcyRldGEsIAogICAgICAiLCBtYXhfZGVwdGggPSIsIHBhcmFtcyRtYXhfZGVwdGgsIAogICAgICAiLCBtaW5fY2hpbGRfd2VpZ2h0ID0iLCBwYXJhbXMkbWluX2NoaWxkX3dlaWdodCwgCiAgICAgICIsIHN1YnNhbXBsZSA9IiwgcGFyYW1zJHN1YnNhbXBsZSwgCiAgICAgICIsIGNvbHNhbXBsZV9ieXRyZWUgPSIsIHBhcmFtcyRjb2xzYW1wbGVfYnl0cmVlLAogICAgICAiLCBnYW1tYSA9IiwgcGFyYW1zJGdhbW1hLCAiXG4iKQogIAogICMgVmFsaWRhY2nDs24gY3J1emFkYSBwYXJhIGVuY29udHJhciBlbCBuw7ptZXJvIMOzcHRpbW8gZGUgaXRlcmFjaW9uZXMKICBjdl9tb2RlbCA8LSB4Z2IuY3YoCiAgICBwYXJhbXMgPSBwYXJhbXMsCiAgICBkYXRhID0gZHRyYWluLAogICAgbnJvdW5kcyA9IDIwMCwgICAgICAgICAgICAgICAgICAgICMgTcOheGltbyBuw7ptZXJvIGRlIGl0ZXJhY2lvbmVzCiAgICBuZm9sZCA9IDUsICAgICAgICAgICAgICAgICAgICAgICAgIyA1LWZvbGQgdmFsaWRhY2nDs24gY3J1emFkYQogICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gMjAsICAgICAgICMgRGV0ZW5lciBzaSBubyBoYXkgbWVqb3JhIGVuIDIwIHJvbmRhcwogICAgdmVyYm9zZSA9IDAgICAgICAgICAgICAgICAgICAgICAgICMgU3VwcmltaXIgbWVuc2FqZXMKICApCiAgCiAgIyBFeHRyYWVyIG1lam9yIGl0ZXJhY2nDs24geSBzdSBSTVNFCiAgYmVzdF9pdGVyYXRpb24gPC0gY3ZfbW9kZWwkYmVzdF9pdGVyYXRpb24KICBiZXN0X3Jtc2UgPC0gbWluKGN2X21vZGVsJGV2YWx1YXRpb25fbG9nJHRlc3Rfcm1zZV9tZWFuKQogIAogIGNhdCgiICBNZWpvciBpdGVyYWNpw7NuOiIsIGJlc3RfaXRlcmF0aW9uLCAiXG4iKQogIGNhdCgiICBSTVNFIGVuIHZhbGlkYWNpw7NuIGNydXphZGE6IiwgYmVzdF9ybXNlLCAiXG5cbiIpCiAgCiAgIyBHdWFyZGFyIHJlc3VsdGFkb3MKICByZXN1bHRhZG9fYWN0dWFsIDwtIGRhdGEuZnJhbWUoCiAgICBldGEgPSBwYXJhbXMkZXRhLAogICAgbWF4X2RlcHRoID0gcGFyYW1zJG1heF9kZXB0aCwKICAgIG1pbl9jaGlsZF93ZWlnaHQgPSBwYXJhbXMkbWluX2NoaWxkX3dlaWdodCwKICAgIHN1YnNhbXBsZSA9IHBhcmFtcyRzdWJzYW1wbGUsCiAgICBjb2xzYW1wbGVfYnl0cmVlID0gcGFyYW1zJGNvbHNhbXBsZV9ieXRyZWUsCiAgICBnYW1tYSA9IHBhcmFtcyRnYW1tYSwKICAgIG5yb3VuZHMgPSBiZXN0X2l0ZXJhdGlvbiwKICAgIHJtc2VfY3YgPSBiZXN0X3Jtc2UKICApCiAgCiAgcmVzdWx0YWRvcyA8LSByYmluZChyZXN1bHRhZG9zLCByZXN1bHRhZG9fYWN0dWFsKQp9CgojIE9yZGVuYXIgcmVzdWx0YWRvcyBwb3IgUk1TRSAoZGUgbWVub3IgYSBtYXlvcikKcmVzdWx0YWRvcyA8LSByZXN1bHRhZG9zW29yZGVyKHJlc3VsdGFkb3Mkcm1zZV9jdiksIF0KCiMgUGFzbyA1OiBNb3N0cmFyIHJlc3VsdGFkb3MgZGVsIEdyaWQgU2VhcmNoCmNhdCgiUmVzdWx0YWRvcyBkZWwgR3JpZCBTZWFyY2ggb3JkZW5hZG9zIHBvciBSTVNFOlxuIikKcHJpbnQocmVzdWx0YWRvcykKCiMgVmlzdWFsaXphciByZXN1bHRhZG9zIGRlbCBHcmlkIFNlYXJjaApnZ3Bsb3QocmVzdWx0YWRvcywgYWVzKHggPSByZW9yZGVyKHBhc3RlKCJDb21iIiwgMTpucm93KHJlc3VsdGFkb3MpKSwgcm1zZV9jdiksIHkgPSBybXNlX2N2KSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gInN0ZWVsYmx1ZSIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiUmVzdWx0YWRvcyBkZWwgR3JpZCBTZWFyY2ggLSBQcm9kdWN0byAxNTUwMDIiLAogICAgeCA9ICJDb21iaW5hY2nDs24gZGUgSGlwZXJwYXLDoW1ldHJvcyIsCiAgICB5ID0gIlJNU0UgZW4gVmFsaWRhY2nDs24gQ3J1emFkYSIKICApICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCgojIFBhc28gNjogU2VsZWNjaW9uYXIgbG9zIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcwptZWpvcmVzX3BhcmFtcyA8LSBsaXN0KAogIG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIiwKICBldmFsX21ldHJpYyA9ICJybXNlIiwKICBldGEgPSByZXN1bHRhZG9zJGV0YVsxXSwKICBtYXhfZGVwdGggPSByZXN1bHRhZG9zJG1heF9kZXB0aFsxXSwKICBtaW5fY2hpbGRfd2VpZ2h0ID0gcmVzdWx0YWRvcyRtaW5fY2hpbGRfd2VpZ2h0WzFdLAogIHN1YnNhbXBsZSA9IHJlc3VsdGFkb3Mkc3Vic2FtcGxlWzFdLAogIGNvbHNhbXBsZV9ieXRyZWUgPSByZXN1bHRhZG9zJGNvbHNhbXBsZV9ieXRyZWVbMV0sCiAgZ2FtbWEgPSByZXN1bHRhZG9zJGdhbW1hWzFdCikKCm1lam9yX25yb3VuZHMgPC0gcmVzdWx0YWRvcyRucm91bmRzWzFdCgpjYXQoIlxuTWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zIGVuY29udHJhZG9zOlxuIikKcHJpbnQobWVqb3Jlc19wYXJhbXMpCmNhdCgiTsO6bWVybyDDs3B0aW1vIGRlIHJvbmRhczoiLCBtZWpvcl9ucm91bmRzLCAiXG5cbiIpCgojIFBhc28gNzogRW50cmVuYXIgZWwgbW9kZWxvIGZpbmFsIGNvbiBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zCmNhdCgiRW50cmVuYW5kbyBtb2RlbG8gZmluYWwgY29uIGxvcyBtZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MuLi5cbiIpCgptb2RlbG9fZmluYWwgPC0geGdiLnRyYWluKAogIHBhcmFtcyA9IG1lam9yZXNfcGFyYW1zLAogIGRhdGEgPSBkdHJhaW4sCiAgbnJvdW5kcyA9IG1lam9yX25yb3VuZHMsCiAgd2F0Y2hsaXN0ID0gbGlzdCh0cmFpbiA9IGR0cmFpbiwgdGVzdCA9IGR0ZXN0KSwKICB2ZXJib3NlID0gMAopCgptb2RlbG9feGdiXzE1NTAwMiA8LSBtb2RlbG9fZmluYWwKCiMgUGFzbyA4OiBFdmFsdWFyIGVsIG1vZGVsbwojIFByZWRpY2Npb25lcyBlbiBjb25qdW50byBkZSBwcnVlYmEKcHJlZGljY2lvbmVzX3Rlc3QgPC0gcHJlZGljdChtb2RlbG9fZmluYWwsIGR0ZXN0KQoKIyBDYWxjdWxhciBtw6l0cmljYXMKIyBNQVBFCm1hcGVfdGVzdCA8LSBtZWFuKGFicygodGVzdF95IC0gcHJlZGljY2lvbmVzX3Rlc3QpIC8gcG1heCh0ZXN0X3ksIDAuMDEpKSkgKiAxMDAKCiMgUk1TRQpybXNlX3Rlc3QgPC0gc3FydChtZWFuKCh0ZXN0X3kgLSBwcmVkaWNjaW9uZXNfdGVzdCleMikpCgoKCiMgTW9zdHJhciBtw6l0cmljYXMgZW4gY29uanVudG8gZGUgcHJ1ZWJhCmNhdCgiXG5Nw6l0cmljYXMgZW4gY29uanVudG8gZGUgcHJ1ZWJhOlxuIikKY2F0KCJNQVBFIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBtYXBlX3Rlc3QsICJcbiIpCmNhdCgiUk1TRSBkZWwgbW9kZWxvIFhHQm9vc3Q6Iiwgcm1zZV90ZXN0LCAiXG5cbiIpCgojIFBhc28gOTogUHJlZGljY2lvbmVzIGVuIGVsIGNvbmp1bnRvIGNvbXBsZXRvCiMgSGFjZXIgcHJlZGljY2lvbmVzIGVuIHRvZG8gZWwgY29uanVudG8gZGUgZGF0b3MgcGFyYSBjb21wYXJhY2nDs24gY29uIG90cm9zIG1vZGVsb3MKeF9jb21wbGV0byA8LSBhcy5tYXRyaXgoZGF0b3NfbW9kZWxvWywgY29sbmFtZXMoZGF0b3NfbW9kZWxvKSAhPSAiVmVudGEiXSkKcHJlZGljY2lvbmVzX2NvbXBsZXRvIDwtIHByZWRpY3QobW9kZWxvX2ZpbmFsLCB4X2NvbXBsZXRvKQoKIyBDYWxjdWxhciBtw6l0cmljYXMgZW4gY29uanVudG8gY29tcGxldG8KbWFwZV9jb21wbGV0byA8LSBtZWFuKGFicygoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX2NvbXBsZXRvKSAvIAogICAgICAgICAgICAgICAgICAgICAgICBwbWF4KGRhdG9zX21vZGVsbyRWZW50YSwgMC4wMSkpKSAqIDEwMAoKcm1zZV9jb21wbGV0byA8LSBzcXJ0KG1lYW4oKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0byleMikpCgoKIyBNb3N0cmFyIG3DqXRyaWNhcyBlbiBjb25qdW50byBjb21wbGV0bwpjYXQoIk3DqXRyaWNhcyBlbiBjb25qdW50byBjb21wbGV0bzpcbiIpCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgbWFwZV9jb21wbGV0bywgIlxuIikKY2F0KCJSTVNFIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBybXNlX2NvbXBsZXRvLCAiXG5cbiIpCgoKIyBQYXNvIDEwOiBBbsOhbGlzaXMgZGUgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCiMgQ2FsY3VsYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCmltcG9ydGFuY2lhIDwtIHhnYi5pbXBvcnRhbmNlKAogIGZlYXR1cmVfbmFtZXMgPSBjb2xuYW1lcyhkYXRvc19tb2RlbG8pW2NvbG5hbWVzKGRhdG9zX21vZGVsbykgIT0gIlZlbnRhIl0sCiAgbW9kZWwgPSBtb2RlbG9fZmluYWwKKQoKIyBNb3N0cmFyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwpjYXQoIkltcG9ydGFuY2lhIGRlIHZhcmlhYmxlczpcbiIpCnByaW50KGltcG9ydGFuY2lhKQoKIyBHcmFmaWNhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKeGdiLnBsb3QuaW1wb3J0YW5jZShpbXBvcnRhbmNlX21hdHJpeCA9IGltcG9ydGFuY2lhLCAKICAgICAgICAgICAgICAgICAgIG1haW4gPSAiSW1wb3J0YW5jaWEgZGUgVmFyaWFibGVzIC0gUHJvZHVjdG8gMTU1MDAyIChYR0Jvb3N0KSIpCgojIFBhc28gMTE6IFZpc3VhbGl6YWNpb25lcyBwYXJhIGV2YWx1YWNpw7NuCiMgR3LDoWZpY28gMTogVmFsb3JlcyBvYnNlcnZhZG9zIHZzIHByZWRpY2hvcwpkYXRvc19ncmFmaWNvIDwtIGRhdGEuZnJhbWUoCiAgT2JzZXJ2YWRvID0gZGF0b3NfbW9kZWxvJFZlbnRhLAogIFByZWRpY2hvID0gcHJlZGljY2lvbmVzX2NvbXBsZXRvCikKCmdncGxvdChkYXRvc19ncmFmaWNvLCBhZXMoeCA9IE9ic2VydmFkbywgeSA9IFByZWRpY2hvKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDE1NTAwMiAoWEdCb29zdCkiLAogICAgeCA9ICJWZW50YXMgT2JzZXJ2YWRhcyIsCiAgICB5ID0gIlZlbnRhcyBQcmVkaWNoYXMiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIEdyw6FmaWNvIDI6IEFuw6FsaXNpcyBkZSByZXNpZHVvcwplcnJvcmVzIDwtIGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0bwoKIyBIaXN0b2dyYW1hIGRlIGVycm9yZXMKaGlzdChlcnJvcmVzLCAKICAgICBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDE1NTAwMiAoWEdCb29zdCkiLAogICAgIHhsYWIgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIsCiAgICAgY29sID0gInNreWJsdWUiLAogICAgIGJyZWFrcyA9IDMwKQoKIyBHcsOhZmljbyAzOiBFcnJvcmVzIHZzIFByZWRpY2Npb25lcwpnZ3Bsb3QoZGF0YS5mcmFtZShQcmVkaWNobyA9IHByZWRpY2Npb25lc19jb21wbGV0bywgRXJyb3IgPSBlcnJvcmVzKSwgYWVzKHggPSBQcmVkaWNobywgeSA9IEVycm9yKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicygKICAgIHRpdGxlID0gIkVycm9yIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMTU1MDAyIChYR0Jvb3N0KSIsCiAgICB4ID0gIlZlbnRhcyBQcmVkaWNoYXMiLAogICAgeSA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIgogICkgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKYGBge3J9CiMgR3VhcmRhciBtw6l0cmljYXMgZGUgWEdCb29zdCBwYXJhIHByb2R1Y3RvIDE1NTAwMQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgewogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLAogICAgTUFQRSA9IG51bWVyaWMoKSwKICAgIFJNU0UgPSBudW1lcmljKCksCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCn0KCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMTU1MDAyIiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvCiAgTW9kZWxvID0gIlhHQm9vc3QiLAogIE1BUEUgPSBtYXBlX2NvbXBsZXRvLAogIFJNU0UgPSBybXNlX2NvbXBsZXRvCikpCmBgYAoKIyMgUFJPRFVDVE8gMzY3ODA1NQoKYGBge3J9CiMgUHJlcGFyYXIgZGF0b3MgcGFyYSBlbCBtb2RlbG8gKGVsaW1pbmFyIGNvbHVtbmFzIG5vIG5lY2VzYXJpYXMpCmRhdG9zX21vZGVsbyA8LSBkYXRvc18zNjc4MDU1ICU+JQogIHNlbGVjdCgtRmVjaGEpCgojIFBhc28gMjogRGl2aWRpciBsb3MgZGF0b3MgZW4gY29uanVudG9zIGRlIGVudHJlbmFtaWVudG8gKDgwJSkgeSBwcnVlYmEgKDIwJSkKc2V0LnNlZWQoMTIzKSAgIyBQYXJhIHJlcHJvZHVjaWJpbGlkYWQKdHJhaW5faW5kZXggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihkYXRvc19tb2RlbG8kVmVudGEsIHAgPSAwLjgsIGxpc3QgPSBGQUxTRSkKdHJhaW5fZGF0YSA8LSBkYXRvc19tb2RlbG9bdHJhaW5faW5kZXgsIF0KdGVzdF9kYXRhIDwtIGRhdG9zX21vZGVsb1stdHJhaW5faW5kZXgsIF0KCiMgUHJlcGFyYXIgbWF0cmljZXMgcGFyYSBYR0Jvb3N0CnRyYWluX3ggPC0gYXMubWF0cml4KHRyYWluX2RhdGFbLCBjb2xuYW1lcyh0cmFpbl9kYXRhKSAhPSAiVmVudGEiXSkKdHJhaW5feSA8LSB0cmFpbl9kYXRhJFZlbnRhCgp0ZXN0X3ggPC0gYXMubWF0cml4KHRlc3RfZGF0YVssIGNvbG5hbWVzKHRlc3RfZGF0YSkgIT0gIlZlbnRhIl0pCnRlc3RfeSA8LSB0ZXN0X2RhdGEkVmVudGEKCiMgQ3JlYXIgRE1hdHJpeCBwYXJhIFhHQm9vc3QKZHRyYWluIDwtIHhnYi5ETWF0cml4KGRhdGEgPSB0cmFpbl94LCBsYWJlbCA9IHRyYWluX3kpCmR0ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSB0ZXN0X3gsIGxhYmVsID0gdGVzdF95KQoKIyBQYXNvIDM6IERlZmluaXIgbGEgcmVqaWxsYSBkZSBoaXBlcnBhcsOhbWV0cm9zCnBhcmFtX2dyaWQgPC0gZXhwYW5kLmdyaWQoCiAgZXRhID0gYygwLjAxLCAwLjA1LCAwLjEsIDAuMyksCiAgbWF4X2RlcHRoID0gYygzLCA2LCA5KSwKICBtaW5fY2hpbGRfd2VpZ2h0ID0gYygxLCAzLCA1KSwKICBzdWJzYW1wbGUgPSBjKDAuNywgMC45KSwKICBjb2xzYW1wbGVfYnl0cmVlID0gYygwLjcsIDAuOSksCiAgZ2FtbWEgPSBjKDAsIDAuMSwgMC4zKQopCgpjYXQoIkdyaWQgU2VhcmNoIHBhcmEgWEdCb29zdCAtIFByb2R1Y3RvIDM2NzgwNTVcbiIpCmNhdCgiTsO6bWVybyB0b3RhbCBkZSBjb21iaW5hY2lvbmVzIGRlIGhpcGVycGFyw6FtZXRyb3M6IiwgbnJvdyhwYXJhbV9ncmlkKSwgIlxuXG4iKQoKIyBTZWxlY2Npw7NuIGFsZWF0b3JpYSBkZSAxMiBjb21iaW5hY2lvbmVzIChzaSBzZSBkZXNlYSBsaW1pdGFyKQpzZXQuc2VlZCg0NTYpCmlmIChucm93KHBhcmFtX2dyaWQpID4gMTIpIHsKICBwYXJhbV9ncmlkIDwtIHBhcmFtX2dyaWRbc2FtcGxlKDE6bnJvdyhwYXJhbV9ncmlkKSwgMTIpLCBdCiAgY2F0KCJTZWxlY2Npb25hbmRvIDEyIGNvbWJpbmFjaW9uZXMgYWxlYXRvcmlhcyBwYXJhIGV2YWx1YWNpw7NuLlxuXG4iKQp9CgojIFBhc28gNDogSW1wbGVtZW50YXIgR3JpZCBTZWFyY2gKcmVzdWx0YWRvcyA8LSBkYXRhLmZyYW1lKCkKY2F0KCJJbmljaWFuZG8gR3JpZCBTZWFyY2guLi5cbiIpCgpmb3IgKGkgaW4gMTpucm93KHBhcmFtX2dyaWQpKSB7CiAgcGFyYW1zIDwtIGxpc3QoCiAgICBvYmplY3RpdmUgPSAicmVnOnNxdWFyZWRlcnJvciIsCiAgICBldmFsX21ldHJpYyA9ICJybXNlIiwKICAgIGV0YSA9IHBhcmFtX2dyaWQkZXRhW2ldLAogICAgbWF4X2RlcHRoID0gcGFyYW1fZ3JpZCRtYXhfZGVwdGhbaV0sCiAgICBtaW5fY2hpbGRfd2VpZ2h0ID0gcGFyYW1fZ3JpZCRtaW5fY2hpbGRfd2VpZ2h0W2ldLAogICAgc3Vic2FtcGxlID0gcGFyYW1fZ3JpZCRzdWJzYW1wbGVbaV0sCiAgICBjb2xzYW1wbGVfYnl0cmVlID0gcGFyYW1fZ3JpZCRjb2xzYW1wbGVfYnl0cmVlW2ldLAogICAgZ2FtbWEgPSBwYXJhbV9ncmlkJGdhbW1hW2ldCiAgKQogIAogIGNhdCgiRXZhbHVhbmRvIGNvbWJpbmFjacOzbiIsIGksICJkZSIsIG5yb3cocGFyYW1fZ3JpZCksICIuLi5cbiIpCiAgCiAgY3ZfbW9kZWwgPC0geGdiLmN2KAogICAgcGFyYW1zID0gcGFyYW1zLAogICAgZGF0YSA9IGR0cmFpbiwKICAgIG5yb3VuZHMgPSAyMDAsCiAgICBuZm9sZCA9IDUsCiAgICBlYXJseV9zdG9wcGluZ19yb3VuZHMgPSAyMCwKICAgIHZlcmJvc2UgPSAwCiAgKQogIAogIGJlc3RfaXRlcmF0aW9uIDwtIGN2X21vZGVsJGJlc3RfaXRlcmF0aW9uCiAgYmVzdF9ybXNlIDwtIG1pbihjdl9tb2RlbCRldmFsdWF0aW9uX2xvZyR0ZXN0X3Jtc2VfbWVhbikKICAKICByZXN1bHRhZG9fYWN0dWFsIDwtIGRhdGEuZnJhbWUoCiAgICBldGEgPSBwYXJhbXMkZXRhLAogICAgbWF4X2RlcHRoID0gcGFyYW1zJG1heF9kZXB0aCwKICAgIG1pbl9jaGlsZF93ZWlnaHQgPSBwYXJhbXMkbWluX2NoaWxkX3dlaWdodCwKICAgIHN1YnNhbXBsZSA9IHBhcmFtcyRzdWJzYW1wbGUsCiAgICBjb2xzYW1wbGVfYnl0cmVlID0gcGFyYW1zJGNvbHNhbXBsZV9ieXRyZWUsCiAgICBnYW1tYSA9IHBhcmFtcyRnYW1tYSwKICAgIG5yb3VuZHMgPSBiZXN0X2l0ZXJhdGlvbiwKICAgIHJtc2VfY3YgPSBiZXN0X3Jtc2UKICApCiAgCiAgcmVzdWx0YWRvcyA8LSByYmluZChyZXN1bHRhZG9zLCByZXN1bHRhZG9fYWN0dWFsKQp9CgpyZXN1bHRhZG9zIDwtIHJlc3VsdGFkb3Nbb3JkZXIocmVzdWx0YWRvcyRybXNlX2N2KSwgXQoKIyBQYXNvIDU6IE1vc3RyYXIgcmVzdWx0YWRvcwpwcmludChyZXN1bHRhZG9zKQoKIyBWaXN1YWxpemFjacOzbgpnZ3Bsb3QocmVzdWx0YWRvcywgYWVzKHggPSByZW9yZGVyKHBhc3RlKCJDb21iIiwgMTpucm93KHJlc3VsdGFkb3MpKSwgcm1zZV9jdiksIHkgPSBybXNlX2N2KSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gInN0ZWVsYmx1ZSIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiUmVzdWx0YWRvcyBkZWwgR3JpZCBTZWFyY2ggLSBQcm9kdWN0byAzNjc4MDU1IiwKICAgIHggPSAiQ29tYmluYWNpw7NuIGRlIEhpcGVycGFyw6FtZXRyb3MiLAogICAgeSA9ICJSTVNFIgogICkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkKCiMgUGFzbyA2OiBTZWxlY2Npb25hciBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zCm1lam9yZXNfcGFyYW1zIDwtIGxpc3QoCiAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLAogIGV2YWxfbWV0cmljID0gInJtc2UiLAogIGV0YSA9IHJlc3VsdGFkb3MkZXRhWzFdLAogIG1heF9kZXB0aCA9IHJlc3VsdGFkb3MkbWF4X2RlcHRoWzFdLAogIG1pbl9jaGlsZF93ZWlnaHQgPSByZXN1bHRhZG9zJG1pbl9jaGlsZF93ZWlnaHRbMV0sCiAgc3Vic2FtcGxlID0gcmVzdWx0YWRvcyRzdWJzYW1wbGVbMV0sCiAgY29sc2FtcGxlX2J5dHJlZSA9IHJlc3VsdGFkb3MkY29sc2FtcGxlX2J5dHJlZVsxXSwKICBnYW1tYSA9IHJlc3VsdGFkb3MkZ2FtbWFbMV0KKQoKbWVqb3JfbnJvdW5kcyA8LSByZXN1bHRhZG9zJG5yb3VuZHNbMV0KCiMgUGFzbyA3OiBFbnRyZW5hciBtb2RlbG8gZmluYWwKbW9kZWxvX2ZpbmFsIDwtIHhnYi50cmFpbigKICBwYXJhbXMgPSBtZWpvcmVzX3BhcmFtcywKICBkYXRhID0gZHRyYWluLAogIG5yb3VuZHMgPSBtZWpvcl9ucm91bmRzLAogIHdhdGNobGlzdCA9IGxpc3QodHJhaW4gPSBkdHJhaW4sIHRlc3QgPSBkdGVzdCksCiAgdmVyYm9zZSA9IDAKKQoKbW9kZWxvX3hnYl8zNjc4MDU1IDwtIG1vZGVsb19maW5hbAoKCiMgUGFzbyA4OiBFdmFsdWFyIG1vZGVsbwpwcmVkaWNjaW9uZXNfdGVzdCA8LSBwcmVkaWN0KG1vZGVsb19maW5hbCwgZHRlc3QpCm1hcGVfdGVzdCA8LSBtZWFuKGFicygodGVzdF95IC0gcHJlZGljY2lvbmVzX3Rlc3QpIC8gcG1heCh0ZXN0X3ksIDAuMDEpKSkgKiAxMDAKcm1zZV90ZXN0IDwtIHNxcnQobWVhbigodGVzdF95IC0gcHJlZGljY2lvbmVzX3Rlc3QpXjIpKQoKCgojIFBhc28gOTogUHJlZGljY2nDs24gZW4gdG9kbyBlbCBjb25qdW50bwp4X2NvbXBsZXRvIDwtIGFzLm1hdHJpeChkYXRvc19tb2RlbG9bLCBjb2xuYW1lcyhkYXRvc19tb2RlbG8pICE9ICJWZW50YSJdKQpwcmVkaWNjaW9uZXNfY29tcGxldG8gPC0gcHJlZGljdChtb2RlbG9fZmluYWwsIHhfY29tcGxldG8pCgptYXBlX2NvbXBsZXRvIDwtIG1lYW4oYWJzKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfY29tcGxldG8pIC8gcG1heChkYXRvc19tb2RlbG8kVmVudGEsIDAuMDEpKSkgKiAxMDAKcm1zZV9jb21wbGV0byA8LSBzcXJ0KG1lYW4oKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0byleMikpCgoKCgojIFBhc28gMTA6IEltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwppbXBvcnRhbmNpYSA8LSB4Z2IuaW1wb3J0YW5jZSgKICBmZWF0dXJlX25hbWVzID0gY29sbmFtZXMoZGF0b3NfbW9kZWxvKVtjb2xuYW1lcyhkYXRvc19tb2RlbG8pICE9ICJWZW50YSJdLAogIG1vZGVsID0gbW9kZWxvX2ZpbmFsCikKeGdiLnBsb3QuaW1wb3J0YW5jZShpbXBvcnRhbmNpYSwgCiAgICAgICAgICAgICAgICAgICAgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAzNjc4MDU1IChYR0Jvb3N0KSIpCgojIFBhc28gMTE6IEdyw6FmaWNvcyBkZSBldmFsdWFjacOzbgpkYXRvc19ncmFmaWNvIDwtIGRhdGEuZnJhbWUoT2JzZXJ2YWRvID0gZGF0b3NfbW9kZWxvJFZlbnRhLCBQcmVkaWNobyA9IHByZWRpY2Npb25lc19jb21wbGV0bykKCmdncGxvdChkYXRvc19ncmFmaWNvLCBhZXMoeCA9IE9ic2VydmFkbywgeSA9IFByZWRpY2hvKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2FibGluZShzbG9wZSA9IDEsIGludGVyY2VwdCA9IDAsIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gInJlZCIpICsKICBsYWJzKHRpdGxlID0gIk9ic2VydmFkbyB2cyBQcmVkaWNobyAtIFByb2R1Y3RvIDM2NzgwNTUiLCB4ID0gIlZlbnRhIE9ic2VydmFkYSIsIHkgPSAiVmVudGEgUHJlZGljaGEiKSArCiAgdGhlbWVfbWluaW1hbCgpCgplcnJvcmVzIDwtIGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0bwoKaGlzdChlcnJvcmVzLCAKICAgICBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDM2NzgwNTUgKFhHQm9vc3QpIiwKICAgICB4bGFiID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiLAogICAgIGNvbCA9ICJza3libHVlIiwgYnJlYWtzID0gMzApCgpnZ3Bsb3QoZGF0YS5mcmFtZShQcmVkaWNobyA9IHByZWRpY2Npb25lc19jb21wbGV0bywgRXJyb3IgPSBlcnJvcmVzKSwgYWVzKHggPSBQcmVkaWNobywgeSA9IEVycm9yKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicyh0aXRsZSA9ICJFcnJvcmVzIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMzY3ODA1NSIsIHggPSAiVmVudGEgUHJlZGljaGEiLCB5ID0gIkVycm9yIikgKwogIHRoZW1lX21pbmltYWwoKQoKYGBgCgpgYGB7cn0KIyBHdWFyZGFyIG3DqXRyaWNhcyBkZSBYR0Jvb3N0IHBhcmEgcHJvZHVjdG8gMTU1MDAxCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7CiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLAogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksCiAgICBNQVBFID0gbnVtZXJpYygpLAogICAgUk1TRSA9IG51bWVyaWMoKSwKICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQogICkKfQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIzNjc4MDU1IiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvCiAgTW9kZWxvID0gIlhHQm9vc3QiLAogIE1BUEUgPSBtYXBlX2NvbXBsZXRvLAogIFJNU0UgPSBybXNlX2NvbXBsZXRvCikpCmBgYAoKIyBWaXN1YWxpemFjacOzbiBkZSBNw6l0cmljYXMKCmBgYHtyfQojIERlZmluaXIgbG9zIGNvbG9yZXMgcGFyYSBjYWRhIG1vZGVsbwpjb2xvcmVzX21vZGVsb3MgPC0gYygKICAiQVJNQS9TQVJJTUEiID0gIiMxZjc3YjQiLCAgICAjIEF6dWwKICAiUmVncmVzacOzbiBMaW5lYWwiID0gIiNmZjdmMGUiLCAjIE5hcmFuamEKICAiUmFuZG9tIEZvcmVzdCIgPSAiIzJjYTAyYyIsICAgIyBWZXJkZQogICJYR0Jvb3N0IiA9ICIjZDYyNzI4IiAgICAgICAgICMgUm9qbwopCmBgYAoKIyMgUFJPRFVDVE8gMTU1MDAxCmBgYHtyfQojIFByaW1lcm8sIHZlYW1vcyBxdcOpIGRhdG9zIHRlbmVtb3MgcmVhbG1lbnRlCnByaW50KCJEYXRvcyBhY3R1YWxlcyBwYXJhIGVsIHByb2R1Y3RvIDE1NTAwMToiKQpwcmludChtZXRyaWNhc19jb21wYXJhdGl2YXMgJT4lIGZpbHRlcihQcm9kdWN0byA9PSAiMTU1MDAxIikpCgojIENyZWFyIHVuIGRhdGFmcmFtZSBtYW51YWxtZW50ZSBjb24gbG9zIDQgbW9kZWxvcyBwYXJhIGVsIHByb2R1Y3RvIDE1NTAwMQojIChjb24gdmFsb3JlcyBkZSBlamVtcGxvIHNpIGVzIG5lY2VzYXJpbykKZGF0b3NfMTU1MDAxX2NvbXBsZXRvIDwtIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSByZXAoIjE1NTAwMSIsIDQpLAogIE1vZGVsbyA9IGMoIkFSTUEvU0FSSU1BIiwgIlJlZ3Jlc2nDs24gTGluZWFsIiwgIlJhbmRvbSBGb3Jlc3QiLCAiWEdCb29zdCIpLAogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQopCgojIFVuaXIgY29uIGxvcyBkYXRvcyBleGlzdGVudGVzCmRhdG9zXzE1NTAwMV9jb21wbGV0byA8LSBsZWZ0X2pvaW4oCiAgZGF0b3NfMTU1MDAxX2NvbXBsZXRvLAogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyAlPiUgZmlsdGVyKFByb2R1Y3RvID09ICIxNTUwMDEiKSwKICBieSA9IGMoIlByb2R1Y3RvIiwgIk1vZGVsbyIpCikKCiMgQWhvcmEgYXNpZ25hIHZhbG9yZXMgcGFyYSBsYXMgbcOpdHJpY2FzIGRlIGxvcyBtb2RlbG9zIGZhbHRhbnRlcwojIFNpIHRpZW5lcyBsb3MgdmFsb3JlcywgcmVlbXBsYXphIGxvcyAwIGNvbiBsb3MgdmFsb3JlcyBjb3JyZWN0b3MKIyBPIHRvbWEgbm90YSBkZSBjdcOhbGVzIHNvbiBOQSBwYXJhIHJlZW1wbGF6YXJsb3MgY29uIGxvcyB2YWxvcmVzIHJlYWxlcwoKIyBWYWxvcmVzIHBhcmEgUmVncmVzacOzbiBMaW5lYWwgKHJlZW1wbGF6YSBlc3RvcyBjb24gbG9zIHZhbG9yZXMgcmVhbGVzKQppZiAoaXMubmEoZGF0b3NfMTU1MDAxX2NvbXBsZXRvJE1BUEVbMl0pKSB7CiAgZGF0b3NfMTU1MDAxX2NvbXBsZXRvJE1BUEVbMl0gPC0gbWFwZV8xNTUwMDEgICMgTyBlbCB2YWxvciBjb3JyZWN0bwp9CmlmIChpcy5uYShkYXRvc18xNTUwMDFfY29tcGxldG8kUk1TRVsyXSkpIHsKICBkYXRvc18xNTUwMDFfY29tcGxldG8kUk1TRVsyXSA8LSBybXNlXzE1NTAwMSAgIyBZYSBubyBNU0UKfQoKCgoKIyBWYWxvcmVzIHBhcmEgUmFuZG9tIEZvcmVzdCAocmVlbXBsYXphIGVzdG9zIGNvbiBsb3MgdmFsb3JlcyByZWFsZXMpCiMgU2kgeWEgZWplY3V0YXN0ZSBsYSBzZWNjacOzbiBkZSBSYW5kb20gRm9yZXN0IHBhcmEgZWwgcHJvZHVjdG8gMTU1MDAxLAojIHVzYSBsYXMgdmFyaWFibGVzIHIyX3JmLCBybXNlX3JmLCBldGMuCmlmIChpcy5uYShkYXRvc18xNTUwMDFfY29tcGxldG8kTUFQRVszXSkgJiYgZXhpc3RzKCJtYXBlX3JmIikpIHsKICBkYXRvc18xNTUwMDFfY29tcGxldG8kTUFQRVszXSA8LSBtYXBlX3JmCn0KCmlmIChpcy5uYShkYXRvc18xNTUwMDFfY29tcGxldG8kUk1TRVszXSkgJiYgZXhpc3RzKCJybXNlX3JmIikpIHsKICBkYXRvc18xNTUwMDFfY29tcGxldG8kUk1TRVszXSA8LSBybXNlX3JmCn0KCgoKCgoKIyBWYWxvcmVzIHBhcmEgWEdCb29zdCAocmVlbXBsYXphIGVzdG9zIGNvbiBsb3MgdmFsb3JlcyByZWFsZXMpCiMgU2kgeWEgZWplY3V0YXN0ZSBsYSBzZWNjacOzbiBkZSBYR0Jvb3N0IHBhcmEgZWwgcHJvZHVjdG8gMTU1MDAxLAojIHVzYSBsYXMgdmFyaWFibGVzIHIyX2NvbXBsZXRvLCBybXNlX2NvbXBsZXRvLCBldGMuCmlmIChpcy5uYShkYXRvc18xNTUwMDFfY29tcGxldG8kTUFQRVs0XSkgJiYgZXhpc3RzKCJtYXBlX2NvbXBsZXRvIikpIHsKICBkYXRvc18xNTUwMDFfY29tcGxldG8kTUFQRVs0XSA8LSBtYXBlX2NvbXBsZXRvCn0KCmlmIChpcy5uYShkYXRvc18xNTUwMDFfY29tcGxldG8kUk1TRVs0XSkgJiYgZXhpc3RzKCJybXNlX2NvbXBsZXRvIikpIHsKICBkYXRvc18xNTUwMDFfY29tcGxldG8kUk1TRVs0XSA8LSBybXNlX2NvbXBsZXRvCn0KCgojIFZlciBsb3MgZGF0b3MgY29tcGxldG9zCnByaW50KCJEYXRvcyBjb21wbGV0b3MgcGFyYSBlbCBwcm9kdWN0byAxNTUwMDE6IikKcHJpbnQoZGF0b3NfMTU1MDAxX2NvbXBsZXRvKQoKIyBHcsOhZmljbyBwYXJhIE1BUEUKZ2dwbG90KGRhdG9zXzE1NTAwMV9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBNQVBFLCBmaWxsID0gTW9kZWxvKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IDAuNikgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChNQVBFLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgbW9kZWxvcyBwYXJhIFByb2R1Y3RvIDE1NTAwMSIsCiAgICBzdWJ0aXRsZSA9ICJNw6l0cmljYTogTUFQRSAodmFsb3JlcyBtw6FzIGJham9zIGluZGljYW4gbWVqb3IgcHJlY2lzacOzbikiLAogICAgeCA9ICIiLAogICAgeSA9ICJNQVBFICglKSIKICApICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKAogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKQogICkgCgojIEdyw6FmaWNvIHBhcmEgUk1TRQpnZ3Bsb3QoZGF0b3NfMTU1MDAxX2NvbXBsZXRvLCBhZXMoeCA9IE1vZGVsbywgeSA9IFJNU0UsIGZpbGwgPSBNb2RlbG8pKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKFJNU0UsIDEpKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sb3Jlc19tb2RlbG9zKSArCiAgbGFicygKICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHBhcmEgUHJvZHVjdG8gMTU1MDAxIiwKICAgIHN1YnRpdGxlID0gIk3DqXRyaWNhOiBSTVNFICh2YWxvcmVzIG3DoXMgYmFqb3MgaW5kaWNhbiBtZWpvciBwcmVjaXNpw7NuKSIsCiAgICB4ID0gIiIsCiAgICB5ID0gIlJNU0UiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkKICApICsKICB5bGltKDAsIG1heChkYXRvc18xNTUwMDFfY29tcGxldG8kUk1TRSwgbmEucm0gPSBUUlVFKSAqIDEuMSkgICMgQWp1c3RhciBlbCBsw61taXRlIFkKYGBgCgoKIyMgUFJPRFVDVE8gMzkyOTc4OApgYGB7cn0KIyBQcmltZXJvLCB2ZWFtb3MgcXXDqSBkYXRvcyB0ZW5lbW9zIHJlYWxtZW50ZQpwcmludCgiRGF0b3MgYWN0dWFsZXMgcGFyYSBlbCBwcm9kdWN0byAzOTI5Nzg4OiIpCnByaW50KG1ldHJpY2FzX2NvbXBhcmF0aXZhcyAlPiUgZmlsdGVyKFByb2R1Y3RvID09ICIzOTI5Nzg4IikpCgojIENyZWFyIHVuIGRhdGFmcmFtZSBtYW51YWxtZW50ZSBjb24gbG9zIDQgbW9kZWxvcyBwYXJhIGVsIHByb2R1Y3RvIDM5Mjk3ODgKZGF0b3NfMzkyOTc4OF9jb21wbGV0byA8LSBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gcmVwKCIzOTI5Nzg4IiwgNCksCiAgTW9kZWxvID0gYygiQVJNQS9TQVJJTUEiLCAiUmVncmVzacOzbiBMaW5lYWwiLCAiUmFuZG9tIEZvcmVzdCIsICJYR0Jvb3N0IiksCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCikKCiMgVW5pciBjb24gbG9zIGRhdG9zIGV4aXN0ZW50ZXMKZGF0b3NfMzkyOTc4OF9jb21wbGV0byA8LSBsZWZ0X2pvaW4oCiAgZGF0b3NfMzkyOTc4OF9jb21wbGV0bywKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgJT4lIGZpbHRlcihQcm9kdWN0byA9PSAiMzkyOTc4OCIpLAogIGJ5ID0gYygiUHJvZHVjdG8iLCAiTW9kZWxvIikKKQoKIyBBaG9yYSBhc2lnbmEgdmFsb3JlcyBwYXJhIGxhcyBtw6l0cmljYXMgZGUgbG9zIG1vZGVsb3MgZmFsdGFudGVzCiMgVmFsb3JlcyBwYXJhIFJlZ3Jlc2nDs24gTGluZWFsCmlmIChpcy5uYShkYXRvc18zOTI5Nzg4X2NvbXBsZXRvJE1BUEVbMl0pKSB7CiAgZGF0b3NfMzkyOTc4OF9jb21wbGV0byRNQVBFWzJdIDwtIG1hcGVfMzkyOTc4OAp9CgppZiAoaXMubmEoZGF0b3NfMzkyOTc4OF9jb21wbGV0byRSTVNFWzJdKSkgewogIGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kUk1TRVsyXSA8LSBybXNlXzM5Mjk3ODgKfQoKIyBWYWxvcmVzIHBhcmEgUmFuZG9tIEZvcmVzdAojIFNpIHlhIGVqZWN1dGFzdGUgbGEgc2VjY2nDs24gZGUgUmFuZG9tIEZvcmVzdCBwYXJhIGVsIHByb2R1Y3RvIDM5Mjk3ODgKaWYgKGlzLm5hKGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kTUFQRVszXSkgJiYgZXhpc3RzKCJtYXBlX3JmIikpIHsKICBkYXRvc18zOTI5Nzg4X2NvbXBsZXRvJE1BUEVbM10gPC0gbWFwZV9yZgp9CgppZiAoaXMubmEoZGF0b3NfMzkyOTc4OF9jb21wbGV0byRSTVNFWzNdKSAmJiBleGlzdHMoInJtc2VfcmYiKSkgewogIGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kUk1TRVszXSA8LSBybXNlX3JmCn0KCiMgVmFsb3JlcyBwYXJhIFhHQm9vc3QKaWYgKGlzLm5hKGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kTUFQRVs0XSkgJiYgZXhpc3RzKCJtYXBlX2NvbXBsZXRvIikpIHsKICBkYXRvc18zOTI5Nzg4X2NvbXBsZXRvJE1BUEVbNF0gPC0gbWFwZV9jb21wbGV0bwp9CgoKaWYgKGlzLm5hKGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kUk1TRVs0XSkgJiYgZXhpc3RzKCJybXNlX2NvbXBsZXRvIikpIHsKICBkYXRvc18zOTI5Nzg4X2NvbXBsZXRvJFJNU0VbNF0gPC0gcm1zZV9jb21wbGV0bwp9CgojIFZlciBsb3MgZGF0b3MgY29tcGxldG9zCnByaW50KCJEYXRvcyBjb21wbGV0b3MgcGFyYSBlbCBwcm9kdWN0byAzOTI5Nzg4OiIpCnByaW50KGRhdG9zXzM5Mjk3ODhfY29tcGxldG8pCgojIERlZmluaXIgY29sb3JlcyBwYXJhIGxvcyBtb2RlbG9zCmNvbG9yZXNfbW9kZWxvcyA8LSBjKCJBUk1BL1NBUklNQSIgPSAiIzFmNzdiNCIsIAogICAgICAgICAgICAgICAgICAgICAiUmVncmVzacOzbiBMaW5lYWwiID0gIiNmZjdmMGUiLCAKICAgICAgICAgICAgICAgICAgICAgIlJhbmRvbSBGb3Jlc3QiID0gIiMyY2EwMmMiLCAKICAgICAgICAgICAgICAgICAgICAgIlhHQm9vc3QiID0gIiNkNjI3MjgiKQoKIyBHcsOhZmljbyBwYXJhIFJNU0UKZ2dwbG90KGRhdG9zXzM5Mjk3ODhfY29tcGxldG8sIGFlcyh4ID0gTW9kZWxvLCB5ID0gUk1TRSwgZmlsbCA9IE1vZGVsbykpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQoUk1TRSwgMSkpLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzLjUpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsKICBsYWJzKAogICAgdGl0bGUgPSAiQ29tcGFyYWNpw7NuIGRlIG1vZGVsb3MgcGFyYSBQcm9kdWN0byAzOTI5Nzg4IiwKICAgIHN1YnRpdGxlID0gIk3DqXRyaWNhOiBSTVNFICh2YWxvcmVzIG3DoXMgYmFqb3MgaW5kaWNhbiBtZWpvciBwcmVjaXNpw7NuKSIsCiAgICB4ID0gIiIsCiAgICB5ID0gIlJNU0UiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkKICApICsKICB5bGltKDAsIG1heChkYXRvc18zOTI5Nzg4X2NvbXBsZXRvJFJNU0UsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZCgojIEdyw6FmaWNvIHBhcmEgTUFQRQpnZ3Bsb3QoZGF0b3NfMzkyOTc4OF9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBNQVBFLCBmaWxsID0gTW9kZWxvKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IDAuNikgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChNQVBFLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgbW9kZWxvcyBwYXJhIFByb2R1Y3RvIDM5Mjk3ODgiLAogICAgc3VidGl0bGUgPSAiTcOpdHJpY2E6IE1BUEUgKHZhbG9yZXMgbcOhcyBiYWpvcyBpbmRpY2FuIG1lam9yIHByZWNpc2nDs24pIiwKICAgIHggPSAiIiwKICAgIHkgPSAiTUFQRSAoJSkiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkKICApICsKICB5bGltKDAsIG1heChkYXRvc18zOTI5Nzg4X2NvbXBsZXRvJE1BUEUsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZCgpgYGAKCiMjIFBST0RVQ1RPIDM5MDQxNTIKYGBge3J9CiMgUHJpbWVybywgdmVhbW9zIHF1w6kgZGF0b3MgdGVuZW1vcyByZWFsbWVudGUKcHJpbnQoIkRhdG9zIGFjdHVhbGVzIHBhcmEgZWwgcHJvZHVjdG8gMzkwNDE1MjoiKQpwcmludChtZXRyaWNhc19jb21wYXJhdGl2YXMgJT4lIGZpbHRlcihQcm9kdWN0byA9PSAiMzkwNDE1MiIpKQoKIyBDcmVhciB1biBkYXRhZnJhbWUgbWFudWFsbWVudGUgY29uIGxvcyA0IG1vZGVsb3MgcGFyYSBlbCBwcm9kdWN0byAzOTA0MTUyCmRhdG9zXzM5MDQxNTJfY29tcGxldG8gPC0gZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9IHJlcCgiMzkwNDE1MiIsIDQpLAogIE1vZGVsbyA9IGMoIkFSTUEvU0FSSU1BIiwgIlJlZ3Jlc2nDs24gTGluZWFsIiwgIlJhbmRvbSBGb3Jlc3QiLCAiWEdCb29zdCIpLAogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQopCgojIFVuaXIgY29uIGxvcyBkYXRvcyBleGlzdGVudGVzCmRhdG9zXzM5MDQxNTJfY29tcGxldG8gPC0gbGVmdF9qb2luKAogIGRhdG9zXzM5MDQxNTJfY29tcGxldG8sCiAgbWV0cmljYXNfY29tcGFyYXRpdmFzICU+JSBmaWx0ZXIoUHJvZHVjdG8gPT0gIjM5MDQxNTIiKSwKICBieSA9IGMoIlByb2R1Y3RvIiwgIk1vZGVsbyIpCikKCiMgQWhvcmEgYXNpZ25hIHZhbG9yZXMgcGFyYSBsYXMgbcOpdHJpY2FzIGRlIGxvcyBtb2RlbG9zIGZhbHRhbnRlcwojIFZhbG9yZXMgcGFyYSBSZWdyZXNpw7NuIExpbmVhbAppZiAoaXMubmEoZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRNQVBFWzJdKSkgewogIGRhdG9zXzM5MDQxNTJfY29tcGxldG8kTUFQRVsyXSA8LSBtYXBlXzM5MDQxNTIKfQppZiAoaXMubmEoZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRSTVNFWzJdKSkgewogIGRhdG9zXzM5MDQxNTJfY29tcGxldG8kUk1TRVsyXSA8LSBybXNlXzM5MDQxNTIKfQoKIyBWYWxvcmVzIHBhcmEgUmFuZG9tIEZvcmVzdAppZiAoaXMubmEoZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRNQVBFWzNdKSAmJiBleGlzdHMoIm1hcGVfcmYiKSkgewogIGRhdG9zXzM5MDQxNTJfY29tcGxldG8kTUFQRVszXSA8LSBtYXBlX3JmCn0KCmlmIChpcy5uYShkYXRvc18zOTA0MTUyX2NvbXBsZXRvJFJNU0VbM10pICYmIGV4aXN0cygicm1zZV9yZiIpKSB7CiAgZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRSTVNFWzNdIDwtIHJtc2VfcmYKfQoKCiMgVmFsb3JlcyBwYXJhIFhHQm9vc3QKaWYgKGlzLm5hKGRhdG9zXzM5MDQxNTJfY29tcGxldG8kTUFQRVs0XSkgJiYgZXhpc3RzKCJtYXBlX2NvbXBsZXRvIikpIHsKICBkYXRvc18zOTA0MTUyX2NvbXBsZXRvJE1BUEVbNF0gPC0gbWFwZV9jb21wbGV0bwp9CgoKaWYgKGlzLm5hKGRhdG9zXzM5MDQxNTJfY29tcGxldG8kUk1TRVs0XSkgJiYgZXhpc3RzKCJybXNlX2NvbXBsZXRvIikpIHsKICBkYXRvc18zOTA0MTUyX2NvbXBsZXRvJFJNU0VbNF0gPC0gcm1zZV9jb21wbGV0bwp9CgoKIyBWZXIgbG9zIGRhdG9zIGNvbXBsZXRvcwpwcmludCgiRGF0b3MgY29tcGxldG9zIHBhcmEgZWwgcHJvZHVjdG8gMzkwNDE1MjoiKQpwcmludChkYXRvc18zOTA0MTUyX2NvbXBsZXRvKQoKIyBEZWZpbmlyIGNvbG9yZXMgcGFyYSBsb3MgbW9kZWxvcwpjb2xvcmVzX21vZGVsb3MgPC0gYygiQVJNQS9TQVJJTUEiID0gIiMxZjc3YjQiLCAKICAgICAgICAgICAgICAgICAgICAgIlJlZ3Jlc2nDs24gTGluZWFsIiA9ICIjZmY3ZjBlIiwgCiAgICAgICAgICAgICAgICAgICAgICJSYW5kb20gRm9yZXN0IiA9ICIjMmNhMDJjIiwgCiAgICAgICAgICAgICAgICAgICAgICJYR0Jvb3N0IiA9ICIjZDYyNzI4IikKCiMgR3LDoWZpY28gcGFyYSBNU0UKZ2dwbG90KGRhdG9zXzM5MDQxNTJfY29tcGxldG8sIGFlcyh4ID0gTW9kZWxvLCB5ID0gUk1TRSwgZmlsbCA9IE1vZGVsbykpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQoUk1TRSwgMSkpLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzLjUpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsKICBsYWJzKAogICAgdGl0bGUgPSAiQ29tcGFyYWNpw7NuIGRlIG1vZGVsb3MgcGFyYSBQcm9kdWN0byAzOTA0MTUyIiwKICAgIHN1YnRpdGxlID0gIk3DqXRyaWNhOiBSTVNFICh2YWxvcmVzIG3DoXMgYmFqb3MgaW5kaWNhbiBtZWpvciBwcmVjaXNpw7NuKSIsCiAgICB4ID0gIiIsCiAgICB5ID0gIlJNU0UiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkKICApICsKICB5bGltKDAsIG1heChkYXRvc18zOTA0MTUyX2NvbXBsZXRvJFJNU0UsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZCgojIEdyw6FmaWNvIHBhcmEgTUFQRQpnZ3Bsb3QoZGF0b3NfMzkwNDE1Ml9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBNQVBFLCBmaWxsID0gTW9kZWxvKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IDAuNikgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChNQVBFLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgbW9kZWxvcyBwYXJhIFByb2R1Y3RvIDM5MDQxNTIiLAogICAgc3VidGl0bGUgPSAiTcOpdHJpY2E6IE1BUEUgKHZhbG9yZXMgbcOhcyBiYWpvcyBpbmRpY2FuIG1lam9yIHByZWNpc2nDs24pIiwKICAgIHggPSAiIiwKICAgIHkgPSAiTUFQRSAoJSkiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkKICApICsKICB5bGltKDAsIG1heChkYXRvc18zOTA0MTUyX2NvbXBsZXRvJE1BUEUsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZCmBgYAoKIyMgUFJPRFVDVE8gMTU1MDAyCmBgYHtyfQojIFByaW1lcm8sIHZlYW1vcyBxdcOpIGRhdG9zIHRlbmVtb3MgcmVhbG1lbnRlCnByaW50KCJEYXRvcyBhY3R1YWxlcyBwYXJhIGVsIHByb2R1Y3RvIDE1NTAwMjoiKQpwcmludChtZXRyaWNhc19jb21wYXJhdGl2YXMgJT4lIGZpbHRlcihQcm9kdWN0byA9PSAiMTU1MDAyIikpCgojIENyZWFyIHVuIGRhdGFmcmFtZSBtYW51YWxtZW50ZSBjb24gbG9zIDQgbW9kZWxvcyBwYXJhIGVsIHByb2R1Y3RvIDE1NTAwMgpkYXRvc18xNTUwMDJfY29tcGxldG8gPC0gZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9IHJlcCgiMTU1MDAyIiwgNCksCiAgTW9kZWxvID0gYygiQVJNQS9TQVJJTUEiLCAiUmVncmVzacOzbiBMaW5lYWwiLCAiUmFuZG9tIEZvcmVzdCIsICJYR0Jvb3N0IiksCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCikKCiMgVW5pciBjb24gbG9zIGRhdG9zIGV4aXN0ZW50ZXMKZGF0b3NfMTU1MDAyX2NvbXBsZXRvIDwtIGxlZnRfam9pbigKICBkYXRvc18xNTUwMDJfY29tcGxldG8sCiAgbWV0cmljYXNfY29tcGFyYXRpdmFzICU+JSBmaWx0ZXIoUHJvZHVjdG8gPT0gIjE1NTAwMiIpLAogIGJ5ID0gYygiUHJvZHVjdG8iLCAiTW9kZWxvIikKKQoKIyBBaG9yYSBhc2lnbmEgdmFsb3JlcyBwYXJhIGxhcyBtw6l0cmljYXMgZGUgbG9zIG1vZGVsb3MgZmFsdGFudGVzCiMgVmFsb3JlcyBwYXJhIFJlZ3Jlc2nDs24gTGluZWFsCmlmIChpcy5uYShkYXRvc18xNTUwMDJfY29tcGxldG8kTUFQRVsyXSkpIHsKICBkYXRvc18xNTUwMDJfY29tcGxldG8kTUFQRVsyXSA8LSBtYXBlXzE1NTAwMgp9CgppZiAoaXMubmEoZGF0b3NfMTU1MDAyX2NvbXBsZXRvJFJNU0VbMl0pKSB7CiAgZGF0b3NfMTU1MDAyX2NvbXBsZXRvJFJNU0VbMl0gPC0gcm1zZV8xNTUwMDIKfQoKCgojIFZhbG9yZXMgcGFyYSBSYW5kb20gRm9yZXN0CiMgU2kgeWEgZWplY3V0YXN0ZSBsYSBzZWNjacOzbiBkZSBSYW5kb20gRm9yZXN0IHBhcmEgZWwgcHJvZHVjdG8gMTU1MDAyCmlmIChpcy5uYShkYXRvc18xNTUwMDJfY29tcGxldG8kTUFQRVszXSkgJiYgZXhpc3RzKCJtYXBlX3JmIikpIHsKICBkYXRvc18xNTUwMDJfY29tcGxldG8kTUFQRVszXSA8LSBtYXBlX3JmCn0KaWYgKGlzLm5hKGRhdG9zXzE1NTAwMl9jb21wbGV0byRSTVNFWzNdKSAmJiBleGlzdHMoInJtc2VfcmYiKSkgewogIGRhdG9zXzE1NTAwMl9jb21wbGV0byRSTVNFWzNdIDwtIHJtc2VfcmYKfQoKIyBWYWxvcmVzIHBhcmEgWEdCb29zdAppZiAoaXMubmEoZGF0b3NfMTU1MDAyX2NvbXBsZXRvJE1BUEVbNF0pICYmIGV4aXN0cygibWFwZV9jb21wbGV0byIpKSB7CiAgZGF0b3NfMTU1MDAyX2NvbXBsZXRvJE1BUEVbNF0gPC0gbWFwZV9jb21wbGV0bwp9CmlmIChpcy5uYShkYXRvc18xNTUwMDJfY29tcGxldG8kUk1TRVs0XSkgJiYgZXhpc3RzKCJybXNlX2NvbXBsZXRvIikpIHsKICBkYXRvc18xNTUwMDJfY29tcGxldG8kUk1TRVs0XSA8LSBybXNlX2NvbXBsZXRvCn0KCiMgVmVyIGxvcyBkYXRvcyBjb21wbGV0b3MKcHJpbnQoIkRhdG9zIGNvbXBsZXRvcyBwYXJhIGVsIHByb2R1Y3RvIDE1NTAwMjoiKQpwcmludChkYXRvc18xNTUwMDJfY29tcGxldG8pCgojIERlZmluaXIgY29sb3JlcyBwYXJhIGxvcyBtb2RlbG9zCmNvbG9yZXNfbW9kZWxvcyA8LSBjKCJBUk1BL1NBUklNQSIgPSAiIzFmNzdiNCIsIAogICAgICAgICAgICAgICAgICAgICAiUmVncmVzacOzbiBMaW5lYWwiID0gIiNmZjdmMGUiLCAKICAgICAgICAgICAgICAgICAgICAgIlJhbmRvbSBGb3Jlc3QiID0gIiMyY2EwMmMiLCAKICAgICAgICAgICAgICAgICAgICAgIlhHQm9vc3QiID0gIiNkNjI3MjgiKQoKIyBHcsOhZmljbyBwYXJhIE1TRQpnZ3Bsb3QoZGF0b3NfMTU1MDAyX2NvbXBsZXRvLCBhZXMoeCA9IE1vZGVsbywgeSA9IFJNU0UsIGZpbGwgPSBNb2RlbG8pKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKFJNU0UsIDEpKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sb3Jlc19tb2RlbG9zKSArCiAgbGFicygKICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHBhcmEgUHJvZHVjdG8gMTU1MDAyIiwKICAgIHN1YnRpdGxlID0gIk3DqXRyaWNhOiBSTVNFICh2YWxvcmVzIG3DoXMgYmFqb3MgaW5kaWNhbiBtZWpvciBwcmVjaXNpw7NuKSIsCiAgICB4ID0gIiIsCiAgICB5ID0gIlJNU0UiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkKICApICsKICB5bGltKDAsIG1heChkYXRvc18xNTUwMDJfY29tcGxldG8kUk1TRSwgbmEucm0gPSBUUlVFKSAqIDEuMSkgICMgQWp1c3RhciBlbCBsw61taXRlIFkKCiMgR3LDoWZpY28gcGFyYSBNQVBFCmdncGxvdChkYXRvc18xNTUwMDJfY29tcGxldG8sIGFlcyh4ID0gTW9kZWxvLCB5ID0gTUFQRSwgZmlsbCA9IE1vZGVsbykpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQoTUFQRSwgMSkpLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzLjUpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsKICBsYWJzKAogICAgdGl0bGUgPSAiQ29tcGFyYWNpw7NuIGRlIG1vZGVsb3MgcGFyYSBQcm9kdWN0byAxNTUwMDIiLAogICAgc3VidGl0bGUgPSAiTcOpdHJpY2E6IE1BUEUgKHZhbG9yZXMgbcOhcyBiYWpvcyBpbmRpY2FuIG1lam9yIHByZWNpc2nDs24pIiwKICAgIHggPSAiIiwKICAgIHkgPSAiTUFQRSAoJSkiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkKICApICsKICB5bGltKDAsIG1heChkYXRvc18xNTUwMDJfY29tcGxldG8kTUFQRSwgbmEucm0gPSBUUlVFKSAqIDEuMSkgICMgQWp1c3RhciBlbCBsw61taXRlIFkKYGBgCgojIyBQUk9EVUNUTyAzNjc4MDU1CmBgYHtyfQojIFByaW1lcm8sIHZlYW1vcyBxdcOpIGRhdG9zIHRlbmVtb3MgcmVhbG1lbnRlCnByaW50KCJEYXRvcyBhY3R1YWxlcyBwYXJhIGVsIHByb2R1Y3RvIDM2NzgwNTU6IikKcHJpbnQobWV0cmljYXNfY29tcGFyYXRpdmFzICU+JSBmaWx0ZXIoUHJvZHVjdG8gPT0gIjM2NzgwNTUiKSkKCiMgQ3JlYXIgdW4gZGF0YWZyYW1lIG1hbnVhbG1lbnRlIGNvbiBsb3MgNCBtb2RlbG9zIHBhcmEgZWwgcHJvZHVjdG8gMzY3ODA1NQpkYXRvc18zNjc4MDU1X2NvbXBsZXRvIDwtIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSByZXAoIjM2NzgwNTUiLCA0KSwKICBNb2RlbG8gPSBjKCJBUk1BL1NBUklNQSIsICJSZWdyZXNpw7NuIExpbmVhbCIsICJSYW5kb20gRm9yZXN0IiwgIlhHQm9vc3QiKSwKICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKKQoKIyBVbmlyIGNvbiBsb3MgZGF0b3MgZXhpc3RlbnRlcwpkYXRvc18zNjc4MDU1X2NvbXBsZXRvIDwtIGxlZnRfam9pbigKICBkYXRvc18zNjc4MDU1X2NvbXBsZXRvLAogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyAlPiUgZmlsdGVyKFByb2R1Y3RvID09ICIzNjc4MDU1IiksCiAgYnkgPSBjKCJQcm9kdWN0byIsICJNb2RlbG8iKQopCgojIEFob3JhIGFzaWduYSB2YWxvcmVzIHBhcmEgbGFzIG3DqXRyaWNhcyBkZSBsb3MgbW9kZWxvcyBmYWx0YW50ZXMKIyBWYWxvcmVzIHBhcmEgUmVncmVzacOzbiBMaW5lYWwKaWYgKGlzLm5hKGRhdG9zXzM2NzgwNTVfY29tcGxldG8kTUFQRVsyXSkpIHsKICBkYXRvc18zNjc4MDU1X2NvbXBsZXRvJE1BUEVbMl0gPC0gbWFwZV8zNjc4MDU1Cn0KaWYgKGlzLm5hKGRhdG9zXzM2NzgwNTVfY29tcGxldG8kUk1TRVsyXSkpIHsKICBkYXRvc18zNjc4MDU1X2NvbXBsZXRvJFJNU0VbMl0gPC0gcm1zZV8zNjc4MDU1Cn0KCiMgVmFsb3JlcyBwYXJhIFJhbmRvbSBGb3Jlc3QKIyBTaSB5YSBlamVjdXRhc3RlIGxhIHNlY2Npw7NuIGRlIFJhbmRvbSBGb3Jlc3QgcGFyYSBlbCBwcm9kdWN0byAzNjc4MDU1CmlmIChpcy5uYShkYXRvc18zNjc4MDU1X2NvbXBsZXRvJE1BUEVbM10pICYmIGV4aXN0cygibWFwZV9yZiIpKSB7CiAgZGF0b3NfMzY3ODA1NV9jb21wbGV0byRNQVBFWzNdIDwtIG1hcGVfcmYKfQppZiAoaXMubmEoZGF0b3NfMzY3ODA1NV9jb21wbGV0byRSTVNFWzNdKSAmJiBleGlzdHMoInJtc2VfcmYiKSkgewogIGRhdG9zXzM2NzgwNTVfY29tcGxldG8kUk1TRVszXSA8LSBybXNlX3JmCn0KCiMgVmFsb3JlcyBwYXJhIFhHQm9vc3QKaWYgKGlzLm5hKGRhdG9zXzM2NzgwNTVfY29tcGxldG8kTUFQRVs0XSkgJiYgZXhpc3RzKCJtYXBlX2NvbXBsZXRvIikpIHsKICBkYXRvc18zNjc4MDU1X2NvbXBsZXRvJE1BUEVbNF0gPC0gbWFwZV9jb21wbGV0bwp9CmlmIChpcy5uYShkYXRvc18zNjc4MDU1X2NvbXBsZXRvJFJNU0VbNF0pICYmIGV4aXN0cygicm1zZV9jb21wbGV0byIpKSB7CiAgZGF0b3NfMzY3ODA1NV9jb21wbGV0byRSTVNFWzRdIDwtIHJtc2VfY29tcGxldG8KfQoKIyBWZXIgbG9zIGRhdG9zIGNvbXBsZXRvcwpwcmludCgiRGF0b3MgY29tcGxldG9zIHBhcmEgZWwgcHJvZHVjdG8gMzY3ODA1NToiKQpwcmludChkYXRvc18zNjc4MDU1X2NvbXBsZXRvKQoKIyBEZWZpbmlyIGNvbG9yZXMgcGFyYSBsb3MgbW9kZWxvcwpjb2xvcmVzX21vZGVsb3MgPC0gYygiQVJNQS9TQVJJTUEiID0gIiMxZjc3YjQiLCAKICAgICAgICAgICAgICAgICAgICAgIlJlZ3Jlc2nDs24gTGluZWFsIiA9ICIjZmY3ZjBlIiwgCiAgICAgICAgICAgICAgICAgICAgICJSYW5kb20gRm9yZXN0IiA9ICIjMmNhMDJjIiwgCiAgICAgICAgICAgICAgICAgICAgICJYR0Jvb3N0IiA9ICIjZDYyNzI4IikKIyBHcsOhZmljbyBwYXJhIE1TRQpnZ3Bsb3QoZGF0b3NfMzY3ODA1NV9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBSTVNFLCBmaWxsID0gTW9kZWxvKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IDAuNikgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChSTVNFLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgbW9kZWxvcyBwYXJhIFByb2R1Y3RvIDM2NzgwNTUiLAogICAgc3VidGl0bGUgPSAiTcOpdHJpY2E6IFJNU0UgKHZhbG9yZXMgbcOhcyBiYWpvcyBpbmRpY2FuIG1lam9yIHByZWNpc2nDs24pIiwKICAgIHggPSAiIiwKICAgIHkgPSAiUk1TRSIKICApICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKAogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKQogICkgKwogIHlsaW0oMCwgbWF4KGRhdG9zXzM2NzgwNTVfY29tcGxldG8kUk1TRSwgbmEucm0gPSBUUlVFKSAqIDEuMSkgICMgQWp1c3RhciBlbCBsw61taXRlIFkKCiMgR3LDoWZpY28gcGFyYSBNQVBFCmdncGxvdChkYXRvc18zNjc4MDU1X2NvbXBsZXRvLCBhZXMoeCA9IE1vZGVsbywgeSA9IE1BUEUsIGZpbGwgPSBNb2RlbG8pKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKE1BUEUsIDEpKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sb3Jlc19tb2RlbG9zKSArCiAgbGFicygKICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHBhcmEgUHJvZHVjdG8gMzY3ODA1NSIsCiAgICBzdWJ0aXRsZSA9ICJNw6l0cmljYTogTUFQRSAodmFsb3JlcyBtw6FzIGJham9zIGluZGljYW4gbWVqb3IgcHJlY2lzacOzbikiLAogICAgeCA9ICIiLAogICAgeSA9ICJNQVBFICglKSIKICApICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKAogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKQogICkgKwogIHlsaW0oMCwgbWF4KGRhdG9zXzM2NzgwNTVfY29tcGxldG8kTUFQRSwgbmEucm0gPSBUUlVFKSAqIDEuMSkgICMgQWp1c3RhciBlbCBsw61taXRlIFkKYGBgCgojIEVTVElNQUNJw5NOIERFIFBSRUNJT1MKCiMjIyBQcmVwYXJhY2nDs24gZGUgZGF0b3MKYGBge3J9CiMgRnVuY2nDs24gcGFyYSBwcmVwYXJhciBkYXRvcyBkZSB1biBwcm9kdWN0bwpwcmVwYXJlX3ByaWNlX2RhdGEgPC0gZnVuY3Rpb24oZGYsIHByb2R1Y3RfaWQpIHsKICBwcm9kdWN0X2RhdGEgPC0gZGYgJT4lCiAgICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSBwcm9kdWN0X2lkKSAlPiUKICAgIGFycmFuZ2UoVHJ4X0ZlY2hhKSAlPiUKICAgIHNlbGVjdCgKICAgICAgVHJ4X0ZlY2hhLCBQcmVjaW9fRmluYWxfVW5pdGFyaW8sIENhbnQsIFZlbnRhLCAKICAgICAgQ29zdG9fVmVudGEsIERlc2N1ZW50b19Qb3JjZW50YWplLCBTZW1hbmEsIE1lcwogICAgKSAlPiUKICAgIG11dGF0ZSgKICAgICAgRGlhX1NlbWFuYSA9IHdkYXkoVHJ4X0ZlY2hhKSwKICAgICAgTWVzX051bSA9IG1vbnRoKFRyeF9GZWNoYSksCiAgICAgIEFuaW8gPSB5ZWFyKFRyeF9GZWNoYSksCiAgICAgIERpYXNfRGVzZGVfSW5pY2lvID0gYXMubnVtZXJpYyhkaWZmdGltZShUcnhfRmVjaGEsIG1pbihUcnhfRmVjaGEpLCB1bml0cyA9ICJkYXlzIikpLAogICAgICBNYXJnZW5fVW5pdGFyaW8gPSAoVmVudGEgLyBDYW50KSAtIChDb3N0b19WZW50YSAvIENhbnQpLAogICAgICBQcmVjaW9fVW5pdGFyaW9fQ2FsYyA9IFZlbnRhIC8gQ2FudCwKICAgICAgSURfSW52ZW50YXJpbyA9IHByb2R1Y3RfaWQKICAgICkKICAKICByZXR1cm4ocHJvZHVjdF9kYXRhKQp9CgojIEFzZWfDunJhdGUgZGUgcXVlICdkYXRvcycgc2VhIHR1IGRhdGEuZnJhbWUgY2FyZ2FkbyBjb3JyZWN0YW1lbnRlCiMgUG9yIGVqZW1wbG8sIHNpIHZpZW5lcyBkZSB1biBhcmNoaXZvIC5jc3Y6CiMgZGF0b3MgPC0gcmVhZC5jc3YoImFyY2hpdm8uY3N2IikKCiMgQXBsaWNhciBsYSBmdW5jacOzbiBhIHRvZG9zIGxvcyBwcm9kdWN0b3MKaWRzIDwtIHVuaXF1ZShkYXRvcyRJRF9JbnZlbnRhcmlvKQoKcHJvZHVjdG9zX3ByZXBhcmFkb3MgPC0gbWFwX2RmKGlkcywgZnVuY3Rpb24oaWQpIHsKICBwcmVwYXJlX3ByaWNlX2RhdGEoZGF0b3MsIGlkKQp9KQoKIyBNb3N0cmFyIHVuYSBwYXJ0ZSBkZWwgcmVzdWx0YWRvCmhlYWQocHJvZHVjdG9zX3ByZXBhcmFkb3MpCmBgYAoKCgoKYGBge3J9CiMgVmVjdG9yIGNvbiBwcm9kdWN0b3MgKGRlYmUgaXIgcHJpbWVybykKcHJvZHVjdG9zX2lkcyA8LSB0b3BfaWRzCgojIEZ1bmNpw7NuIHBhcmEgZW50cmVuYXIgbW9kZWxvIEFSTUEgcG9yIHByb2R1Y3RvCnRyYWluX2FybWFfbW9kZWwgPC0gZnVuY3Rpb24oZGF0YSwgcHJvZHVjdF9pZCkgewogIGxpYnJhcnkoZm9yZWNhc3QpICAjIEFzZWfDunJhdGUgZGUgY2FyZ2FyIGZvcmVjYXN0IHNpIG5vIGVzdMOhIGNhcmdhZG8gYcO6bgogIHByb2R1Y3RfZGF0YSA8LSBkYXRhICU+JSBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSBwcm9kdWN0X2lkKQogIHNlcmllX3RzIDwtIHRzKHByb2R1Y3RfZGF0YSRWZW50YSwgZnJlcXVlbmN5ID0gMTIpCiAgbW9kZWxvX2FybWEgPC0gYXV0by5hcmltYShzZXJpZV90cywgc2Vhc29uYWwgPSBGQUxTRSwgc3RlcHdpc2UgPSBGQUxTRSwgYXBwcm94aW1hdGlvbiA9IEZBTFNFKQogIHJldHVybihtb2RlbG9fYXJtYSkKfQoKIyBDcmVhciBsaXN0YSBkZSBtb2RlbG9zIEFSTUEgcG9yIHByb2R1Y3RvCm1vZGVsb3NfYXJtYV9saXN0YSA8LSBzZXROYW1lcygKICBsYXBwbHkocHJvZHVjdG9zX2lkcywgZnVuY3Rpb24oaWQpIHRyYWluX2FybWFfbW9kZWwoZGF0b3MsIGlkKSksCiAgYXMuY2hhcmFjdGVyKHByb2R1Y3Rvc19pZHMpCikKCiMgRnVuY2nDs24gcGFyYSBtb2RlbG8gcmVncmVzacOzbiBsaW5lYWwKdHJhaW5fcmVnX21vZGVsIDwtIGZ1bmN0aW9uKGRhdGEsIHByb2R1Y3RfaWQpIHsKICBwcm9kdWN0X2RhdGEgPC0gZGF0YSAlPiUgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gcHJvZHVjdF9pZCkKICBtb2RlbG9fcmVnIDwtIGxtKFZlbnRhIH4gUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCBkYXRhID0gcHJvZHVjdF9kYXRhKQogIHJldHVybihtb2RlbG9fcmVnKQp9CgojIEZ1bmNpw7NuIHBhcmEgbW9kZWxvIFJhbmRvbSBGb3Jlc3QKdHJhaW5fcmZfbW9kZWwgPC0gZnVuY3Rpb24oZGF0YSwgcHJvZHVjdF9pZCkgewogIHByb2R1Y3RfZGF0YSA8LSBkYXRhICU+JSBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSBwcm9kdWN0X2lkKQogIHByZWRpY3RvcnMgPC0gYygiUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIiwgIkNhbnQiLCAiRGVzY3VlbnRvX1BvcmNlbnRhamUiKQogIHJmX2RhdGEgPC0gcHJvZHVjdF9kYXRhICU+JSBzZWxlY3QoYWxsX29mKHByZWRpY3RvcnMpLCBWZW50YSkKICBtb2RlbG9fcmYgPC0gcmFuZG9tRm9yZXN0KFZlbnRhIH4gLiwgZGF0YSA9IHJmX2RhdGEsIG50cmVlID0gMTAwKQogIHJldHVybihtb2RlbG9fcmYpCn0KCiMgRnVuY2nDs24gcGFyYSBtb2RlbG8gWEdCb29zdAp0cmFpbl94Z2JfbW9kZWwgPC0gZnVuY3Rpb24oZGF0YSwgcHJvZHVjdF9pZCkgewogIHByb2R1Y3RfZGF0YSA8LSBkYXRhICU+JSBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSBwcm9kdWN0X2lkKQogIHByZWRpY3RvcnMgPC0gYygiUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIiwgIkNhbnQiLCAiRGVzY3VlbnRvX1BvcmNlbnRhamUiKQogIHRyYWluX21hdHJpeCA8LSB4Z2IuRE1hdHJpeChkYXRhID0gYXMubWF0cml4KHByb2R1Y3RfZGF0YVssIHByZWRpY3RvcnNdKSwgbGFiZWwgPSBwcm9kdWN0X2RhdGEkVmVudGEpCiAgcGFyYW1zIDwtIGxpc3Qob2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiKQogIG1vZGVsb194Z2IgPC0geGdiLnRyYWluKHBhcmFtcyA9IHBhcmFtcywgZGF0YSA9IHRyYWluX21hdHJpeCwgbnJvdW5kcyA9IDUwLCB2ZXJib3NlID0gMCkKICByZXR1cm4obW9kZWxvX3hnYikKfQoKIyBDcmVhciBsaXN0YXMgZGUgbW9kZWxvcwptb2RlbG9zX3JlZ19saXN0YSA8LSBzZXROYW1lcyhsYXBwbHkocHJvZHVjdG9zX2lkcywgZnVuY3Rpb24oaWQpIHRyYWluX3JlZ19tb2RlbChkYXRvcywgaWQpKSwgYXMuY2hhcmFjdGVyKHByb2R1Y3Rvc19pZHMpKQptb2RlbG9zX3JmX2xpc3RhIDwtIHNldE5hbWVzKGxhcHBseShwcm9kdWN0b3NfaWRzLCBmdW5jdGlvbihpZCkgdHJhaW5fcmZfbW9kZWwoZGF0b3MsIGlkKSksIGFzLmNoYXJhY3Rlcihwcm9kdWN0b3NfaWRzKSkKbW9kZWxvc194Z2JfbGlzdGEgPC0gc2V0TmFtZXMobGFwcGx5KHByb2R1Y3Rvc19pZHMsIGZ1bmN0aW9uKGlkKSB0cmFpbl94Z2JfbW9kZWwoZGF0b3MsIGlkKSksIGFzLmNoYXJhY3Rlcihwcm9kdWN0b3NfaWRzKSkKYGBgCgojIyMgRW50cmVuYXIgbW9kZWxvcyBkZSBwcmVkaWNjacOzbiBkZSBwcmVjaW9zCmBgYHtyfQojIEZ1bmNpw7NuIHBhcmEgZW50cmVuYXIgbW9kZWxvcyBkZSBwcmVkaWNjacOzbiBkZSBwcmVjaW9zCnRyYWluX3ByaWNlX21vZGVscyA8LSBmdW5jdGlvbihkYXRhLCBwcm9kdWN0X2lkLCB0ZXN0X3NpemUgPSAwLjIpIHsKICBwcmljZV9kYXRhIDwtIHByZXBhcmVfcHJpY2VfZGF0YShkYXRhLCBwcm9kdWN0X2lkKSAlPiUKICAgIGRyb3BfbmEoKSAlPiUKICAgIHNlbGVjdCgKICAgICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLAogICAgICBDYW50LCBDb3N0b19WZW50YSwgRGVzY3VlbnRvX1BvcmNlbnRhamUsCiAgICAgIERpYV9TZW1hbmEsIE1lc19OdW0sIEFuaW8sIERpYXNfRGVzZGVfSW5pY2lvLAogICAgICBNYXJnZW5fVW5pdGFyaW8KICAgICkKCiAgIyBFdml0YXIgZmFsbG9zIHNpIGhheSBtdXkgcG9jb3MgZGF0b3MKICBpZiAobnJvdyhwcmljZV9kYXRhKSA8IDEwKSB7CiAgICB3YXJuaW5nKHBhc3RlKCJQcm9kdWN0byIsIHByb2R1Y3RfaWQsICJ0aWVuZSBtZW5vcyBkZSAxMCByZWdpc3Ryb3MuIFNlIG9taXRlLiIpKQogICAgcmV0dXJuKE5VTEwpCiAgfQoKICBzZXQuc2VlZCgxMjMpCiAgdHJhaW5faW5kZXggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihwcmljZV9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbywgcCA9IDEgLSB0ZXN0X3NpemUsIGxpc3QgPSBGQUxTRSkKICB0cmFpbl9kYXRhIDwtIHByaWNlX2RhdGFbdHJhaW5faW5kZXgsIF0KICB0ZXN0X2RhdGEgPC0gcHJpY2VfZGF0YVstdHJhaW5faW5kZXgsIF0KCiAgIyAxLiBSZWdyZXNpw7NuIExpbmVhbAogIGxtX21vZGVsIDwtIGxtKFByZWNpb19GaW5hbF9Vbml0YXJpbyB+IC4sIGRhdGEgPSB0cmFpbl9kYXRhKQoKICAjIDIuIFJhbmRvbSBGb3Jlc3QKICByZl9tb2RlbCA8LSByYW5kb21Gb3Jlc3QoCiAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gfiAuLAogICAgZGF0YSA9IHRyYWluX2RhdGEsCiAgICBudHJlZSA9IDUwMCwKICAgIGltcG9ydGFuY2UgPSBUUlVFCiAgKQoKICAjIDMuIFhHQm9vc3QKICBmZWF0dXJlcyA8LSBzZXRkaWZmKG5hbWVzKHRyYWluX2RhdGEpLCAiUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIikKICB4X3RyYWluIDwtIGFzLm1hdHJpeCh0cmFpbl9kYXRhWywgZmVhdHVyZXNdKQogIHlfdHJhaW4gPC0gdHJhaW5fZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8KICB4X3Rlc3QgPC0gYXMubWF0cml4KHRlc3RfZGF0YVssIGZlYXR1cmVzXSkKICB5X3Rlc3QgPC0gdGVzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbwogIGR0cmFpbiA8LSB4Z2IuRE1hdHJpeChkYXRhID0geF90cmFpbiwgbGFiZWwgPSB5X3RyYWluKQogIGR0ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSB4X3Rlc3QsIGxhYmVsID0geV90ZXN0KQoKICB4Z2JfcGFyYW1zIDwtIGxpc3QoCiAgICBvYmplY3RpdmUgPSAicmVnOnNxdWFyZWRlcnJvciIsCiAgICBldmFsX21ldHJpYyA9ICJybXNlIiwKICAgIGV0YSA9IDAuMSwKICAgIG1heF9kZXB0aCA9IDYsCiAgICBtaW5fY2hpbGRfd2VpZ2h0ID0gMywKICAgIHN1YnNhbXBsZSA9IDAuOCwKICAgIGNvbHNhbXBsZV9ieXRyZWUgPSAwLjgKICApCgogIHhnYl9tb2RlbCA8LSB4Z2IudHJhaW4oCiAgICBwYXJhbXMgPSB4Z2JfcGFyYW1zLAogICAgZGF0YSA9IGR0cmFpbiwKICAgIG5yb3VuZHMgPSAxMDAsCiAgICB3YXRjaGxpc3QgPSBsaXN0KHRyYWluID0gZHRyYWluLCB0ZXN0ID0gZHRlc3QpLAogICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gMTAsCiAgICB2ZXJib3NlID0gMAogICkKCiAgIyBFdmFsdWFjacOzbgogIGxtX3ByZWQgPC0gcHJlZGljdChsbV9tb2RlbCwgbmV3ZGF0YSA9IHRlc3RfZGF0YSkKICByZl9wcmVkIDwtIHByZWRpY3QocmZfbW9kZWwsIG5ld2RhdGEgPSB0ZXN0X2RhdGEpCiAgeGdiX3ByZWQgPC0gcHJlZGljdCh4Z2JfbW9kZWwsIHhfdGVzdCkKCiAgbG1fcm1zZSA8LSBzcXJ0KG1lYW4oKGxtX3ByZWQgLSB0ZXN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKV4yKSkKICByZl9ybXNlIDwtIHNxcnQobWVhbigocmZfcHJlZCAtIHRlc3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8pXjIpKQogIHhnYl9ybXNlIDwtIHNxcnQobWVhbigoeGdiX3ByZWQgLSB0ZXN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKV4yKSkKCiAgbG1fcjIgPC0gMSAtIHN1bSgodGVzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbyAtIGxtX3ByZWQpXjIpIC8KICAgIHN1bSgodGVzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbyAtIG1lYW4odGVzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbykpXjIpCiAgcmZfcjIgPC0gMSAtIHN1bSgodGVzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbyAtIHJmX3ByZWQpXjIpIC8KICAgIHN1bSgodGVzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbyAtIG1lYW4odGVzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbykpXjIpCiAgeGdiX3IyIDwtIDEgLSBzdW0oKHRlc3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8gLSB4Z2JfcHJlZCleMikgLwogICAgc3VtKCh0ZXN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIC0gbWVhbih0ZXN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKSleMikKCiAgbWV0cmljcyA8LSBkYXRhLmZyYW1lKAogICAgTW9kZWwgPSBjKCJMaW5lYXIgUmVncmVzc2lvbiIsICJSYW5kb20gRm9yZXN0IiwgIlhHQm9vc3QiKSwKICAgIFJNU0UgPSBjKGxtX3Jtc2UsIHJmX3Jtc2UsIHhnYl9ybXNlKSwKICAgIFIyID0gYyhsbV9yMiwgcmZfcjIsIHhnYl9yMikKICApCgogIHJldHVybihsaXN0KG1ldHJpY3MgPSBtZXRyaWNzKSkKfQoKIyBJRHMgZGUgbG9zIDUgcHJvZHVjdG9zIGEgbW9kZWxhcgpwcm9kdWN0b3NfaWRzIDwtIGMoMTU1MDAxLCAzOTI5Nzg4LCAzOTA0MTUyLCAxNTUwMDIsIDM2NzgwNTUpCgojIEFwbGljYXIgbW9kZWxvIGEgY2FkYSBwcm9kdWN0bwpyZXN1bHRhZG9zX21vZGVsb3MgPC0gbWFwKHByb2R1Y3Rvc19pZHMsIGZ1bmN0aW9uKGlkKSB7CiAgcmVzdWx0YWRvIDwtIHRyYWluX3ByaWNlX21vZGVscyhkYXRvcywgcHJvZHVjdF9pZCA9IGlkKQogIGlmICghaXMubnVsbChyZXN1bHRhZG8pKSB7CiAgICByZXN1bHRhZG8kbWV0cmljcyAlPiUgbXV0YXRlKElEX0ludmVudGFyaW8gPSBpZCkKICB9IGVsc2UgewogICAgTlVMTAogIH0KfSkgJT4lIGNvbXBhY3QoKSAlPiUgYmluZF9yb3dzKCkKCiMgTW9zdHJhciByZXN1bHRhZG9zCnJlc3VsdGFkb3NfbW9kZWxvcwpgYGAKCmBgYHtyfQojIExpc3RhIGNvbiBsb3MgSURzIGRlIHByb2R1Y3RvcyAocHVlZGVzIHVzYXIgdG9wX2lkcyBxdWUgeWEgZGVmaW5pc3RlKQpwcm9kdWN0b3NfaWRzIDwtIHRvcF9pZHMKCiMgRW50cmVuYXIgbW9kZWxvcyBwYXJhIGNhZGEgcHJvZHVjdG8geSBndWFyZGFyIGVuIGxpc3RhCm1vZGVsb3NfcHJlY2lvX2xpc3RhIDwtIHNldE5hbWVzKAogIGxhcHBseShwcm9kdWN0b3NfaWRzLCBmdW5jdGlvbihpZCkgdHJhaW5fcHJpY2VfbW9kZWxzKGRhdG9zLCBpZCkpLAogIGFzLmNoYXJhY3Rlcihwcm9kdWN0b3NfaWRzKQopCmBgYAoKIyMjIEVzdGltYXIgcHJlY2lvcyDDs3B0aW1vcwpgYGB7cn0KZXN0aW1hdGVfb3B0aW1hbF9wcmljZXMgPC0gZnVuY3Rpb24oZGF0YSwgcHJvZHVjdF9pZCwgcHJpY2VfbW9kZWxzLCBkZW1hbmRfbW9kZWxzID0gTlVMTCwgZnV0dXJlX2RhdGVzID0gTlVMTCkgewogIHByaWNlX3N0ZXBzIDwtIDIwCgogICMgU2VsZWNjacOzbiBkZWwgbWVqb3IgbW9kZWxvIGRlIHByZWNpbwogIGJlc3RfcHJpY2VfbW9kZWxfaWR4IDwtIHdoaWNoLm1heChwcmljZV9tb2RlbHMkbWV0cmljcyRSMikKICBiZXN0X3ByaWNlX21vZGVsX25hbWUgPC0gcHJpY2VfbW9kZWxzJG1ldHJpY3MkTW9kZWxbYmVzdF9wcmljZV9tb2RlbF9pZHhdCgogICMgRGF0b3MgZGVsIHByb2R1Y3RvCiAgcHJvZHVjdF9kYXRhIDwtIGRhdGEgJT4lIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IHByb2R1Y3RfaWQpCgogICMgUmFuZ28gZGUgcHJlY2lvcyByZXN0cmluZ2lkbyBhIHBlcmNlbnRpbGVzIDUlIC0gOTUlIHkgbGltaXRhZG8gYSAxLjV4IGxhIG1lZGlhbmEKICBtaW5fcHJpY2UgPC0gcXVhbnRpbGUocHJvZHVjdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbywgMC4wNSwgbmEucm0gPSBUUlVFKQogIG1heF9wcmljZSA8LSBxdWFudGlsZShwcm9kdWN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCAwLjk1LCBuYS5ybSA9IFRSVUUpCiAgcHJpY2VfcmFuZ2UgPC0gc2VxKG1pbl9wcmljZSwgbWF4X3ByaWNlLCBsZW5ndGgub3V0ID0gcHJpY2Vfc3RlcHMpCiAgcHJpY2VfcmFuZ2UgPC0gcG1pbihwcmljZV9yYW5nZSwgMS41ICogbWVkaWFuKHByb2R1Y3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8sIG5hLnJtID0gVFJVRSkpCgogICMgSW5pY2lhbGl6YXIgZXNjZW5hcmlvcyBmdXR1cm9zCiAgZnV0dXJlX3NjZW5hcmlvcyA8LSBkYXRhLmZyYW1lKCkKCiAgZm9yIChmdXR1cmVfZGF0ZSBpbiBmdXR1cmVfZGF0ZXMpIHsKICAgIGZ1dHVyZV9kYXRlIDwtIGFzLkRhdGUoZnV0dXJlX2RhdGUpCiAgICBtZXNfYWN0dWFsIDwtIGx1YnJpZGF0ZTo6bW9udGgoZnV0dXJlX2RhdGUpCgogICAgbWVzX2RhdGEgPC0gcHJvZHVjdF9kYXRhICU+JSBmaWx0ZXIobHVicmlkYXRlOjptb250aChUcnhfRmVjaGEpID09IG1lc19hY3R1YWwpCiAgICBpZiAobnJvdyhtZXNfZGF0YSkgPCA1KSBtZXNfZGF0YSA8LSBwcm9kdWN0X2RhdGEKCiAgICBjb3N0b19tZXMgPC0gbWVkaWFuKG1lc19kYXRhJENvc3RvX1ZlbnRhLCBuYS5ybSA9IFRSVUUpCiAgICBjYW50X21lcyA8LSBtZWRpYW4obWVzX2RhdGEkQ2FudCwgbmEucm0gPSBUUlVFKQogICAgZGVzY19tZXMgPC0gbWVkaWFuKG1lc19kYXRhJERlc2N1ZW50b19Qb3JjZW50YWplLCBuYS5ybSA9IFRSVUUpCgogICAgaWYgKGlzLm5hKGNvc3RvX21lcykpIGNvc3RvX21lcyA8LSBtZWRpYW4ocHJvZHVjdF9kYXRhJENvc3RvX1ZlbnRhLCBuYS5ybSA9IFRSVUUpCiAgICBpZiAoaXMubmEoY2FudF9tZXMpIHx8IGNhbnRfbWVzID09IDApIGNhbnRfbWVzIDwtIG1lZGlhbihwcm9kdWN0X2RhdGEkQ2FudCwgbmEucm0gPSBUUlVFKQogICAgaWYgKGlzLm5hKGRlc2NfbWVzKSkgZGVzY19tZXMgPC0gbWVkaWFuKHByb2R1Y3RfZGF0YSREZXNjdWVudG9fUG9yY2VudGFqZSwgbmEucm0gPSBUUlVFKQoKICAgIGRhdGVfZGYgPC0gZGF0YS5mcmFtZSgKICAgICAgVHJ4X0ZlY2hhID0gcmVwKGZ1dHVyZV9kYXRlLCBwcmljZV9zdGVwcyksCiAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbyA9IHByaWNlX3JhbmdlLAogICAgICBDYW50ID0gY2FudF9tZXMsCiAgICAgIENvc3RvX1ZlbnRhID0gY29zdG9fbWVzLAogICAgICBEZXNjdWVudG9fUG9yY2VudGFqZSA9IGRlc2NfbWVzLAogICAgICBEaWFfU2VtYW5hID0gbHVicmlkYXRlOjp3ZGF5KGZ1dHVyZV9kYXRlKSwKICAgICAgTWVzX051bSA9IG1lc19hY3R1YWwsCiAgICAgIEFuaW8gPSBsdWJyaWRhdGU6OnllYXIoZnV0dXJlX2RhdGUpLAogICAgICBEaWFzX0Rlc2RlX0luaWNpbyA9IGFzLm51bWVyaWMoZGlmZnRpbWUoZnV0dXJlX2RhdGUsIG1pbihwcm9kdWN0X2RhdGEkVHJ4X0ZlY2hhKSwgdW5pdHMgPSAiZGF5cyIpKSwKICAgICAgTWFyZ2VuX1VuaXRhcmlvID0gTkEKICAgICkKCiAgICBmdXR1cmVfc2NlbmFyaW9zIDwtIHJiaW5kKGZ1dHVyZV9zY2VuYXJpb3MsIGRhdGVfZGYpCiAgfQoKICAjIENhbGN1bGFyIG1hcmdlbiB1bml0YXJpbyBzaW11bGFkbwogIGZ1dHVyZV9zY2VuYXJpb3MkTWFyZ2VuX1VuaXRhcmlvIDwtIGZ1dHVyZV9zY2VuYXJpb3MkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIC0KICAgIChmdXR1cmVfc2NlbmFyaW9zJENvc3RvX1ZlbnRhIC8gZnV0dXJlX3NjZW5hcmlvcyRDYW50KQoKICAjIEVzdGltYXIgZWxhc3RpY2lkYWQgaGlzdMOzcmljYSAoY29uIG3DrW5pbW8gMTUgcHVudG9zIHbDoWxpZG9zKQogIHByb2R1Y3RfZGF0YSA8LSBwcm9kdWN0X2RhdGEgJT4lIGFycmFuZ2UoVHJ4X0ZlY2hhKQogIGVsYXN0aWNpdHlfZGYgPC0gcHJvZHVjdF9kYXRhICU+JQogICAgZmlsdGVyKCFpcy5uYShDYW50KSAmICFpcy5uYShQcmVjaW9fRmluYWxfVW5pdGFyaW8pKSAlPiUKICAgIG11dGF0ZSgKICAgICAgUF9sYWcgPSBsYWcoUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKSwKICAgICAgUV9sYWcgPSBsYWcoQ2FudCksCiAgICAgIGRQID0gUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIC0gUF9sYWcsCiAgICAgIGRRID0gQ2FudCAtIFFfbGFnLAogICAgICBlbGFzdGljaXR5X3BvaW50ID0gKGRRIC8gUV9sYWcpIC8gKGRQIC8gUF9sYWcpCiAgICApICU+JQogICAgZmlsdGVyKCFpcy5uYShlbGFzdGljaXR5X3BvaW50KSwgaXMuZmluaXRlKGVsYXN0aWNpdHlfcG9pbnQpKQoKICBlbGFzdGljaXR5IDwtIG1lZGlhbihlbGFzdGljaXR5X2RmJGVsYXN0aWNpdHlfcG9pbnQsIG5hLnJtID0gVFJVRSkKICBpZiAobnJvdyhlbGFzdGljaXR5X2RmKSA8IDE1IHx8IGlzLm5hKGVsYXN0aWNpdHkpIHx8ICFpcy5maW5pdGUoZWxhc3RpY2l0eSkpIHsKICAgIGVsYXN0aWNpdHkgPC0gMQogIH0KCiAgIyBFc3RpbWFyIHZlbnRhcyB5IG3DoXJnZW5lcwogIHJlc3VsdHMgPC0gZnV0dXJlX3NjZW5hcmlvcyAlPiUKICAgIG11dGF0ZShWZW50YV9Fc3BlcmFkYSA9IDAsIE1hcmdlbl9Ub3RhbCA9IDApCgogICMgUHJlY2lvIGJhc2Ugc2luIGRlc2N1ZW50b3MgKG3DoXMgcm9idXN0bykKICBiYXNlbGluZV9wcmljZSA8LSBtZWRpYW4ocHJvZHVjdF9kYXRhICU+JSBmaWx0ZXIoRGVzY3VlbnRvX1BvcmNlbnRhamUgPT0gMCkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHVsbChQcmVjaW9fRmluYWxfVW5pdGFyaW8pLCBuYS5ybSA9IFRSVUUpCiAgaWYgKGlzLm5hKGJhc2VsaW5lX3ByaWNlKSkgewogICAgYmFzZWxpbmVfcHJpY2UgPC0gbWVkaWFuKHByb2R1Y3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8sIG5hLnJtID0gVFJVRSkKICB9CgogIGZvciAoaSBpbiAxOm5yb3cocmVzdWx0cykpIHsKICAgIHByaWNlX3JhdGlvIDwtIGJhc2VsaW5lX3ByaWNlIC8gcmVzdWx0cyRQcmVjaW9fRmluYWxfVW5pdGFyaW9baV0KICAgIGFkanVzdGVkX3F1YW50aXR5IDwtIHJlc3VsdHMkQ2FudFtpXSAqIChwcmljZV9yYXRpbyBeIGVsYXN0aWNpdHkpCiAgICByZXN1bHRzJFZlbnRhX0VzcGVyYWRhW2ldIDwtIHJlc3VsdHMkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvW2ldICogYWRqdXN0ZWRfcXVhbnRpdHkKICAgIHJlc3VsdHMkTWFyZ2VuX1RvdGFsW2ldIDwtIGFkanVzdGVkX3F1YW50aXR5ICogcmVzdWx0cyRNYXJnZW5fVW5pdGFyaW9baV0KICB9CgogICMgU2VsZWNjaW9uYXIgcHJlY2lvcyDDs3B0aW1vcyAocG9yIGZlY2hhKQogIG9wdGltYWxfcHJpY2VzIDwtIHJlc3VsdHMgJT4lCiAgICBncm91cF9ieShUcnhfRmVjaGEpICU+JQogICAgc2xpY2VfbWF4KFZlbnRhX0VzcGVyYWRhLCBuID0gMSkgJT4lCiAgICBzZWxlY3QoVHJ4X0ZlY2hhLCBQcmVjaW9fT3B0aW1hbCA9IFByZWNpb19GaW5hbF9Vbml0YXJpbywgVmVudGFfRXNwZXJhZGEsIE1hcmdlbl9Ub3RhbCkKCiAgIyBWYWxpZGFjacOzbiBkZSBwcmVjaW9zIGV4dHJlbW9zCiAgcHJlY2lvX21lZGlhbiA8LSBtZWRpYW4ocHJvZHVjdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbywgbmEucm0gPSBUUlVFKQogIGlmIChhbnkob3B0aW1hbF9wcmljZXMkUHJlY2lvX09wdGltYWwgPiAyICogcHJlY2lvX21lZGlhbikpIHsKICAgIHdhcm5pbmcocGFzdGUoIiBQcmVjaW8gw7NwdGltbyBtdXkgYWx0byBkZXRlY3RhZG8gcGFyYSBwcm9kdWN0byIsIHByb2R1Y3RfaWQpKQogIH0KCiAgcmV0dXJuKGxpc3QoCiAgICByZXN1bHRhZG9zID0gcmVzdWx0cywKICAgIHByZWNpb3Nfb3B0aW1vcyA9IG9wdGltYWxfcHJpY2VzLAogICAgZWxhc3RpY2lkYWQgPSBlbGFzdGljaXR5CiAgKSkKfQoKYGBgCgojIyMgVmlzdWFsaXphciByZXN1bHRhZG9zCmBgYHtyfQojIEZlY2hhcyBmdXR1cmFzIHBhcmEgc2ltdWxhY2nDs24KZGF0ZXNfZnV0dXJlIDwtIHNlcShhcy5EYXRlKCIyMDI1LTAxLTAxIiksIGJ5ID0gIm1vbnRoIiwgbGVuZ3RoLm91dCA9IDYpCgojIExpc3RhIHBhcmEgZ3VhcmRhciByZXN1bHRhZG9zIHBvciBwcm9kdWN0bwpwcmVjaW9zX29wdGltb3NfbGlzdGEgPC0gbGlzdCgpCgojIEl0ZXJhciBwb3IgcHJvZHVjdG9zCmZvciAocGlkIGluIHByb2R1Y3Rvc19pZHMpIHsKICBjYXQoIlBST0RVQ1RPOiIsIHBpZCwgIlxuIikKICAKICBtb2RlbG9fcHJlY2lvIDwtIG1vZGVsb3NfcHJlY2lvX2xpc3RhW1thcy5jaGFyYWN0ZXIocGlkKV1dCiAgCiAgaWYgKCFpcy5udWxsKG1vZGVsb19wcmVjaW8pKSB7CiAgICByZXN1bHRhZG8gPC0gZXN0aW1hdGVfb3B0aW1hbF9wcmljZXMoCiAgICAgIGRhdGEgPSBkYXRvcywKICAgICAgcHJvZHVjdF9pZCA9IHBpZCwKICAgICAgcHJpY2VfbW9kZWxzID0gbW9kZWxvX3ByZWNpbywKICAgICAgZnV0dXJlX2RhdGVzID0gZGF0ZXNfZnV0dXJlCiAgICApCiAgICAKICAgIHByZWNpb3Nfb3B0aW1vc19saXN0YVtbYXMuY2hhcmFjdGVyKHBpZCldXSA8LSByZXN1bHRhZG8KICAgIAogICAgY2F0KCJFbGFzdGljaWRhZCBlc3RpbWFkYToiLCByb3VuZChyZXN1bHRhZG8kZWxhc3RpY2lkYWQsIDIpLCAiXG5cbiIpCiAgICAKICAgICMgTW9zdHJhciB0YWJsYSBtYW51YWxtZW50ZSBmaWxhIHBvciBmaWxhCiAgICBwcmludChyZXN1bHRhZG8kcHJlY2lvc19vcHRpbW9zKQogICAgCiAgfSBlbHNlIHsKICAgIGNhdCgiTm8gaGF5IG1vZGVsbyBkZSBwcmVjaW9zIHBhcmEgZWwgcHJvZHVjdG8iLCBwaWQsICJcbiIpCiAgfQp9CmBgYAoKCgojIyMgSW50ZWdyYWNpw7NuIGRlIHByZWNpb3Mgw7NwdGltb3MgeSBtb2RlbG9zCmBgYHtyfQppbnRlZ3JhdGVfd2l0aF9leGlzdGluZ19tb2RlbHMgPC0gZnVuY3Rpb24oZGF0YSwgcHJvZHVjdF9pZCwgcHJpY2Vfb3B0X3Jlc3VsdHMsIHhnYl9tb2RlbCkgewogIG9wdGltYWxfcHJpY2VzIDwtIHByaWNlX29wdF9yZXN1bHRzW1thcy5jaGFyYWN0ZXIocHJvZHVjdF9pZCldXSRwcmVjaW9zX29wdGltb3MKICAKICBpZiAoaXMubnVsbChvcHRpbWFsX3ByaWNlcykgfHwgbnJvdyhvcHRpbWFsX3ByaWNlcykgPT0gMCkgewogICAgd2FybmluZyhwYXN0ZSgiTm8gc2UgZW5jb250cmFyb24gcHJlY2lvcyDDs3B0aW1vcyBwYXJhIGVsIHByb2R1Y3RvIiwgcHJvZHVjdF9pZCkpCiAgICByZXR1cm4oZGF0YS5mcmFtZSgpKQogIH0KICAKICBoaXN0X2RhdGEgPC0gZGF0YSAlPiUKICAgIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IHByb2R1Y3RfaWQpICU+JQogICAgYXJyYW5nZShUcnhfRmVjaGEpCiAgCiAgZnV0dXJlX2ZlYXR1cmVzIDwtIGRhdGEuZnJhbWUoKQogIAogIGZvciAoaSBpbiAxOm5yb3cob3B0aW1hbF9wcmljZXMpKSB7CiAgICBmdXR1cmVfZGF0ZSA8LSBvcHRpbWFsX3ByaWNlcyRUcnhfRmVjaGFbaV0KICAgIGZ1dHVyZV9wcmljZSA8LSBvcHRpbWFsX3ByaWNlcyRQcmVjaW9fT3B0aW1hbFtpXQogICAgCiAgICBtZXNfZGF0YSA8LSBoaXN0X2RhdGEgJT4lCiAgICAgIGZpbHRlcihsdWJyaWRhdGU6Om1vbnRoKFRyeF9GZWNoYSkgPT0gbHVicmlkYXRlOjptb250aChmdXR1cmVfZGF0ZSkpCiAgICAKICAgIGlmIChucm93KG1lc19kYXRhKSA8IDUpIG1lc19kYXRhIDwtIGhpc3RfZGF0YQogICAgCiAgICBhdmdfZmVhdHVyZXMgPC0gbWVzX2RhdGEgJT4lCiAgICAgIHN1bW1hcmlzZSgKICAgICAgICBDYW50ID0gbWVkaWFuKENhbnQsIG5hLnJtID0gVFJVRSksCiAgICAgICAgQ29zdG9fVmVudGEgPSBtZWRpYW4oQ29zdG9fVmVudGEsIG5hLnJtID0gVFJVRSksCiAgICAgICAgQ29zdG9fRGV2b2x1Y2lvbiA9IG1lZGlhbihDb3N0b19EZXZvbHVjaW9uLCBuYS5ybSA9IFRSVUUpLAogICAgICAgIFByZWNpb19MaXN0YV9Vbml0YXJpbyA9IG1lZGlhbihQcmVjaW9fTGlzdGFfVW5pdGFyaW8sIG5hLnJtID0gVFJVRSksCiAgICAgICAgRGVzY3VlbnRvX1BvcmNlbnRhamUgPSBtZWRpYW4oRGVzY3VlbnRvX1BvcmNlbnRhamUsIG5hLnJtID0gVFJVRSksCiAgICAgICAgVGllbXBvID0gYXMubnVtZXJpYyhkaWZmdGltZShmdXR1cmVfZGF0ZSwgbWluKGhpc3RfZGF0YSRUcnhfRmVjaGEpLCB1bml0cyA9ICJkYXlzIikpIC8gMzAKICAgICAgKQogICAgCiAgICAjIFZhcmlhYmxlcyBkZSB0ZW5kZW5jaWE6CiAgICBhdmdfZmVhdHVyZXMkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIDwtIGZ1dHVyZV9wcmljZQogICAgYXZnX2ZlYXR1cmVzJFRyeF9GZWNoYSA8LSBmdXR1cmVfZGF0ZQogICAgYXZnX2ZlYXR1cmVzJE1lc19OdW0gPC0gbHVicmlkYXRlOjptb250aChmdXR1cmVfZGF0ZSkKICAgIGF2Z19mZWF0dXJlcyRBbmlvIDwtIGx1YnJpZGF0ZTo6eWVhcihmdXR1cmVfZGF0ZSkKICAgIGF2Z19mZWF0dXJlcyRNZXNfRGVzZGVfSW5pY2lvIDwtIGFzLm51bWVyaWMoZGlmZnRpbWUoZnV0dXJlX2RhdGUsIG1pbihoaXN0X2RhdGEkVHJ4X0ZlY2hhKSwgdW5pdHMgPSAiZGF5cyIpKSAlLyUgMzAKICAgIAogICAgZnV0dXJlX2ZlYXR1cmVzIDwtIHJiaW5kKGZ1dHVyZV9mZWF0dXJlcywgYXZnX2ZlYXR1cmVzKQogIH0KICAKICBmdXR1cmVfZGF0YSA8LSBkYXRhLmZyYW1lKAogICAgRmVjaGEgPSBmdXR1cmVfZmVhdHVyZXMkVHJ4X0ZlY2hhLAogICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvID0gZnV0dXJlX2ZlYXR1cmVzJFByZWNpb19GaW5hbF9Vbml0YXJpbwogICkKICAKICAjID09PSBQUkVESUNDScOTTiBDT04gWEdCb29zdCA9PT0KICB0cnlDYXRjaCh7CiAgICBmZWF0dXJlcyA8LSB4Z2JfbW9kZWwkZmVhdHVyZV9uYW1lcwogICAgaWYgKGlzLm51bGwoZmVhdHVyZXMpKSB7CiAgICAgIGZlYXR1cmVzIDwtIHNldGRpZmYobmFtZXMoZnV0dXJlX2ZlYXR1cmVzKSwgIlZlbnRhIikKICAgIH0KICAgIHhnYl9tYXRyaXggPC0gYXMubWF0cml4KGZ1dHVyZV9mZWF0dXJlc1ssIGZlYXR1cmVzLCBkcm9wID0gRkFMU0VdKQogICAgZnV0dXJlX2RhdGEkVmVudGFfWEdCb29zdCA8LSBwcmVkaWN0KHhnYl9tb2RlbCwgeGdiX21hdHJpeCkKICB9LCBlcnJvciA9IGZ1bmN0aW9uKGUpIHsKICAgIHdhcm5pbmcocGFzdGUoIkVycm9yIGFsIHByZWRlY2lyIGNvbiBYR0Jvb3N0IHBhcmEgcHJvZHVjdG8iLCBwcm9kdWN0X2lkLCAiOiIsIGUkbWVzc2FnZSkpCiAgICBmdXR1cmVfZGF0YSRWZW50YV9YR0Jvb3N0IDwtIE5BCiAgfSkKICAKICAjID09PSBNw4lUUklDQVMgPT09CiAgYXZnX2Nvc3RfcGVyX3VuaXQgPC0gbWVkaWFuKGhpc3RfZGF0YSRDb3N0b19WZW50YSAvIGhpc3RfZGF0YSRDYW50LCBuYS5ybSA9IFRSVUUpCiAgCiAgZnV0dXJlX2RhdGEkVW5pZGFkZXNfWEdCb29zdCA8LSBmdXR1cmVfZGF0YSRWZW50YV9YR0Jvb3N0IC8gZnV0dXJlX2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvCiAgZnV0dXJlX2RhdGEkQ29zdG9fWEdCb29zdCA8LSBmdXR1cmVfZGF0YSRVbmlkYWRlc19YR0Jvb3N0ICogYXZnX2Nvc3RfcGVyX3VuaXQKICBmdXR1cmVfZGF0YSRNYXJnZW5fWEdCb29zdCA8LSBmdXR1cmVfZGF0YSRWZW50YV9YR0Jvb3N0IC0gZnV0dXJlX2RhdGEkQ29zdG9fWEdCb29zdAogIAogIHJldHVybihmdXR1cmVfZGF0YSkKfQoKYGBgCgojIyMgUGlwZWxpbmUgY29ycmVjdG8KYGBge3J9CmNvcnJlZ2lyX2Zvcm1hdG9fZmVjaGFzIDwtIGZ1bmN0aW9uKGRhdG9zKSB7CiAgaWYgKCJUcnhfRmVjaGEiICVpbiUgY29sbmFtZXMoZGF0b3MpKSB7CiAgICBkYXRvcyRUcnhfRmVjaGFfT3JpZ2luYWwgPC0gZGF0b3MkVHJ4X0ZlY2hhCgogICAgaWYgKGlzLmNoYXJhY3RlcihkYXRvcyRUcnhfRmVjaGEpICYmCiAgICAgICAgYW55KGdyZXBsKCJeXFxkezd9LVxcZHsyfS1cXGR7Mn0kIiwgZGF0b3MkVHJ4X0ZlY2hhKSkpIHsKCiAgICAgIGNhdCgiQ29ycmlnaWVuZG8gZm9ybWF0byBkZSBmZWNoYXMgZXh0cmHDsW8uLi5cbiIpCgogICAgICBkYXRvcyRUcnhfRmVjaGEgPC0gc2FwcGx5KGRhdG9zJFRyeF9GZWNoYSwgZnVuY3Rpb24oZmVjaGEpIHsKICAgICAgICBpZiAoaXMubmEoZmVjaGEpIHx8ICFpcy5jaGFyYWN0ZXIoZmVjaGEpKSByZXR1cm4oTkEpCgogICAgICAgIHBhcnRlcyA8LSBzdHJzcGxpdChmZWNoYSwgIi0iKVtbMV1dCiAgICAgICAgaWYgKGxlbmd0aChwYXJ0ZXMpID09IDMpIHsKICAgICAgICAgIGZlY2hhX2NvcnJlZ2lkYSA8LSBwYXN0ZSgiMjAyMyIsIHBhcnRlc1syXSwgcGFydGVzWzNdLCBzZXAgPSAiLSIpCiAgICAgICAgICByZXR1cm4oZmVjaGFfY29ycmVnaWRhKQogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICByZXR1cm4oTkEpCiAgICAgICAgfQogICAgICB9KQoKICAgICAgZGF0b3MkVHJ4X0ZlY2hhIDwtIGFzLkRhdGUoZGF0b3MkVHJ4X0ZlY2hhKQogICAgICBjYXQoIkZlY2hhcyBjb3JyZWdpZGFzIGV4aXRvc2FtZW50ZS5cbiIpCiAgICB9IGVsc2UgaWYgKCFpbmhlcml0cyhkYXRvcyRUcnhfRmVjaGEsICJEYXRlIikpIHsKICAgICAgY2F0KCJJbnRlbnRhbmRvIGNvbnZlcnRpciBmZWNoYXMgYSBmb3JtYXRvIERhdGUuLi5cbiIpCiAgICAgIGRhdG9zJFRyeF9GZWNoYSA8LSBhcy5EYXRlKGRhdG9zJFRyeF9GZWNoYSkKICAgIH0KICB9CiAgcmV0dXJuKGRhdG9zKQp9CgojIEFwbGljYXIgbGEgY29ycmVjY2nDs24gYSB0dSBkYXRhZnJhbWUgYW50ZXMgZGUgdXNhcmxvCmRhdG9zX2ZpbHRyYWRvcyA8LSBjb3JyZWdpcl9mb3JtYXRvX2ZlY2hhcyhkYXRvc19maWx0cmFkb3MpCmBgYAoKYGBge3J9CmRhdGVzX2Z1dHVyZSA8LSBzZXEuRGF0ZShhcy5EYXRlKCIyMDIzLTAxLTAxIiksIGJ5ID0gIm1vbnRoIiwgbGVuZ3RoLm91dCA9IDYpCnByZWNpb3Nfb3B0aW1vc19saXN0YSA8LSBsaXN0KCkKCmZvciAoaWQgaW4gcHJvZHVjdG9zX2lkcykgewogIGNhdCgiRXN0aW1hbmRvIHByZWNpb3Mgw7NwdGltb3MgcGFyYSBwcm9kdWN0bzoiLCBpZCwgIlxuIikKCiAgbW9kZWxvX3ByZWNpbyA8LSBtb2RlbG9zX3ByZWNpb19saXN0YVtbYXMuY2hhcmFjdGVyKGlkKV1dCgogIGlmICghaXMubnVsbChtb2RlbG9fcHJlY2lvKSkgewogICAgcHJlY2lvc19vcHRpbW9zX2xpc3RhW1thcy5jaGFyYWN0ZXIoaWQpXV0gPC0gZXN0aW1hdGVfb3B0aW1hbF9wcmljZXMoCiAgICAgIGRhdGEgPSBkYXRvc19maWx0cmFkb3MsCiAgICAgIHByb2R1Y3RfaWQgPSBpZCwKICAgICAgcHJpY2VfbW9kZWxzID0gbW9kZWxvX3ByZWNpbywKICAgICAgZnV0dXJlX2RhdGVzID0gZGF0ZXNfZnV0dXJlCiAgICApCiAgfQp9CmBgYAoKYGBge3J9CmZvciAoaWQgaW4gbmFtZXMocHJlY2lvc19vcHRpbW9zX2xpc3RhKSkgewogIGRmX29wdGltbyA8LSBwcmVjaW9zX29wdGltb3NfbGlzdGFbW2lkXV0kcHJlY2lvc19vcHRpbW9zCgogIGlmICghaW5oZXJpdHMoZGZfb3B0aW1vJFRyeF9GZWNoYSwgIkRhdGUiKSkgewogICAgZGZfb3B0aW1vJFRyeF9GZWNoYSA8LSBhcy5EYXRlKGRmX29wdGltbyRUcnhfRmVjaGEpCiAgfQoKICBjYXQocGFzdGUwKCJcbiMjIyBQcm9kdWN0bzogIiwgaWQsICJcbiIpKQoKICBwcmludCgKICAgIGdncGxvdChkZl9vcHRpbW8sIGFlcyh4ID0gVHJ4X0ZlY2hhLCB5ID0gUHJlY2lvX09wdGltYWwpKSArCiAgICAgIGdlb21fbGluZShjb2xvciA9ICIjMWY3N2I0IiwgbGluZXdpZHRoID0gMS4yKSArCiAgICAgIGdlb21fcG9pbnQoY29sb3IgPSAiIzFmNzdiNCIsIHNpemUgPSAyKSArCiAgICAgIGxhYnMoCiAgICAgICAgdGl0bGUgPSBwYXN0ZSgiUHJlY2lvIMOTcHRpbW8gcG9yIE1lcyAtIFByb2R1Y3RvIiwgaWQpLAogICAgICAgIHggPSAiRmVjaGEiLAogICAgICAgIHkgPSAiUHJlY2lvIMOTcHRpbW8iCiAgICAgICkgKwogICAgICBzY2FsZV94X2RhdGUoZGF0ZV9sYWJlbHMgPSAiJWIgJVkiLCBkYXRlX2JyZWFrcyA9ICIxIG1vbnRoIikgKwogICAgICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDEyKSArCiAgICAgIHRoZW1lKAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKQogICAgICApCiAgKQp9CgpgYGAKCmBgYHtyfQpydW5fcHJpY2Vfb3B0aW1pemF0aW9uIDwtIGZ1bmN0aW9uKGRhdGEsIHByb2R1Y3RfaWRzLCBmdXR1cmVfZGF0ZXMgPSBOVUxMLCBtb2RlbG9zX3ByZWNpb19saXN0YSA9IE5VTEwpIHsKICBpZiAoaXMubnVsbChmdXR1cmVfZGF0ZXMpKSB7CiAgICBmdXR1cmVfZGF0ZXMgPC0gc2VxLkRhdGUoU3lzLkRhdGUoKSwgYnkgPSAibW9udGgiLCBsZW5ndGgub3V0ID0gNikKICB9CgogIHByZWNpb3Nfb3B0aW1vc19saXN0YSA8LSBsaXN0KCkKCiAgZm9yIChpZCBpbiBwcm9kdWN0X2lkcykgewogICAgY2F0KCJFc3RpbWFuZG8gcHJlY2lvcyDDs3B0aW1vcyBwYXJhIHByb2R1Y3RvOiIsIGlkLCAiXG4iKQoKICAgIHByaWNlX21vZGVsIDwtIE5VTEwKICAgIGlmICghaXMubnVsbChtb2RlbG9zX3ByZWNpb19saXN0YSkpIHsKICAgICAgcHJpY2VfbW9kZWwgPC0gbW9kZWxvc19wcmVjaW9fbGlzdGFbW2FzLmNoYXJhY3RlcihpZCldXQogICAgfQoKICAgIHByZWNpb3Nfb3B0aW1vc19saXN0YVtbYXMuY2hhcmFjdGVyKGlkKV1dIDwtIGVzdGltYXRlX29wdGltYWxfcHJpY2VzKAogICAgICBkYXRhID0gZGF0YSwKICAgICAgcHJvZHVjdF9pZCA9IGlkLAogICAgICBwcmljZV9tb2RlbHMgPSBwcmljZV9tb2RlbCwKICAgICAgZnV0dXJlX2RhdGVzID0gZnV0dXJlX2RhdGVzCiAgICApCiAgfQoKICByZXR1cm4ocHJlY2lvc19vcHRpbW9zX2xpc3RhKQp9CgpgYGAKCmBgYHtyfQojIEZ1bmNpw7NuIHByaW5jaXBhbCBxdWUgaW50ZWdyYSB0b2RvIGVsIHBpcGVsaW5lIGNvbiBzb2xvIFhHQm9vc3QKcnVuX2NvbXBsZXRlX2FuYWx5c2lzIDwtIGZ1bmN0aW9uKGRhdGEsIHRvcF9pZHMsIG1vZGVsb3NfeGdiLCBtb2RlbG9zX3ByZWNpb19saXN0YSA9IE5VTEwpIHsKICAjIDEuIEVzdGltYXIgcHJlY2lvcyDDs3B0aW1vcwogIGFsbF9yZXN1bHRzIDwtIHJ1bl9wcmljZV9vcHRpbWl6YXRpb24oZGF0YSwgdG9wX2lkcywgbW9kZWxvc19wcmVjaW9fbGlzdGEgPSBtb2RlbG9zX3ByZWNpb19saXN0YSkKCiAgIyAyLiBJbnRlZ3JhciBjb24gbW9kZWxvIFhHQm9vc3QKICBpbnRlZ3JhdGVkX3Jlc3VsdHMgPC0gbGlzdCgpCgogIGZvciAoaSBpbiBzZXFfYWxvbmcodG9wX2lkcykpIHsKICAgIHBpZCA8LSB0b3BfaWRzW2ldCiAgICBwaWRfc3RyIDwtIGFzLmNoYXJhY3RlcihwaWQpCgogICAgeGdiX21vZGVsIDwtIGlmIChsZW5ndGgobW9kZWxvc194Z2IpID49IGkpIG1vZGVsb3NfeGdiW1tpXV0gZWxzZSBOVUxMCgogICAgZnV0dXJlX3ByZWRpY3Rpb25zIDwtIGludGVncmF0ZV93aXRoX2V4aXN0aW5nX21vZGVscygKICAgICAgZGF0YSA9IGRhdGEsCiAgICAgIHByb2R1Y3RfaWQgPSBwaWQsCiAgICAgIHByaWNlX29wdF9yZXN1bHRzID0gYWxsX3Jlc3VsdHMsCiAgICAgIHhnYl9tb2RlbCA9IHhnYl9tb2RlbAogICAgKQoKICAgIGludGVncmF0ZWRfcmVzdWx0c1tbcGlkX3N0cl1dIDwtIGZ1dHVyZV9wcmVkaWN0aW9ucwoKICAgIGlmIChucm93KGZ1dHVyZV9wcmVkaWN0aW9ucykgPiAwKSB7CiAgICAgIHBfc2FsZXMgPC0gZ2dwbG90KGZ1dHVyZV9wcmVkaWN0aW9ucywgYWVzKHggPSBGZWNoYSwgeSA9IFZlbnRhX1hHQm9vc3QpKSArCiAgICAgICAgZ2VvbV9saW5lKGNvbG9yID0gIiMxZjc3YjQiLCBsaW5ld2lkdGggPSAxLjIpICsKICAgICAgICBnZW9tX3BvaW50KHNpemUgPSAyKSArCiAgICAgICAgbGFicygKICAgICAgICAgIHRpdGxlID0gcGFzdGUoIlByZWRpY2Npb25lcyBkZSB2ZW50YXMgY29uIHByZWNpb3Mgw7NwdGltb3MgLSBQcm9kdWN0byIsIHBpZCksCiAgICAgICAgICB4ID0gIkZlY2hhIiwKICAgICAgICAgIHkgPSAiVmVudGFzIGVzdGltYWRhcyAoJCkiCiAgICAgICAgKSArCiAgICAgICAgdGhlbWVfbWluaW1hbCgpCgogICAgICBwX21hcmdpbnMgPC0gZ2dwbG90KGZ1dHVyZV9wcmVkaWN0aW9ucywgYWVzKHggPSBGZWNoYSwgeSA9IE1hcmdlbl9YR0Jvb3N0KSkgKwogICAgICAgIGdlb21fY29sKGZpbGwgPSAic3RlZWxibHVlIiwgd2lkdGggPSAxNSkgKwogICAgICAgIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChNYXJnZW5fWEdCb29zdCwgMCkpLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzLjUpICsKICAgICAgICBsYWJzKAogICAgICAgICAgdGl0bGUgPSBwYXN0ZSgiTWFyZ2VuIGVzcGVyYWRvIGNvbiBwcmVjaW9zIMOzcHRpbW9zIC0gUHJvZHVjdG8iLCBwaWQpLAogICAgICAgICAgeCA9ICJGZWNoYSIsCiAgICAgICAgICB5ID0gIk1hcmdlbiBlc3RpbWFkbyAoJCkiCiAgICAgICAgKSArCiAgICAgICAgdGhlbWVfbWluaW1hbCgpCgogICAgICBhbGxfcmVzdWx0c1tbcGlkX3N0cl1dJGludGVncmF0ZWRfcGxvdHMgPC0gbGlzdCgKICAgICAgICBzYWxlcyA9IHBfc2FsZXMsCiAgICAgICAgbWFyZ2lucyA9IHBfbWFyZ2lucwogICAgICApCiAgICB9CiAgfQoKICAjIDMuIFZpc3VhbGl6YWNpw7NuIGRlIHByZWNpb3Mgw7NwdGltb3MKICBhbGxfb3B0aW1hbF9wcmljZXMgPC0gZGF0YS5mcmFtZSgpCgogIGZvciAocGlkIGluIHRvcF9pZHMpIHsKICAgIHBpZF9zdHIgPC0gYXMuY2hhcmFjdGVyKHBpZCkKICAgIGlmIChwaWRfc3RyICVpbiUgbmFtZXMoYWxsX3Jlc3VsdHMpKSB7CiAgICAgIG9wdF9wcmljZXMgPC0gYWxsX3Jlc3VsdHNbW3BpZF9zdHJdXSRwcmVjaW9zX29wdGltb3MgJT4lCiAgICAgICAgbXV0YXRlKElEX0ludmVudGFyaW8gPSBwaWQpCgogICAgICBhbGxfb3B0aW1hbF9wcmljZXMgPC0gcmJpbmQoYWxsX29wdGltYWxfcHJpY2VzLCBvcHRfcHJpY2VzKQogICAgfQogIH0KCiAgcF9jb21wYXJpc29uIDwtIGdncGxvdChhbGxfb3B0aW1hbF9wcmljZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICBhZXMoeCA9IFRyeF9GZWNoYSwgeSA9IFByZWNpb19PcHRpbWFsLCBjb2xvciA9IGZhY3RvcihJRF9JbnZlbnRhcmlvKSkpICsKICAgIGdlb21fbGluZShzaXplID0gMS4yKSArCiAgICBnZW9tX3BvaW50KHNpemUgPSAzKSArCiAgICBsYWJzKAogICAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgUHJlY2lvcyDDk3B0aW1vcyBwb3IgUHJvZHVjdG8iLAogICAgICB4ID0gIkZlY2hhIiwKICAgICAgeSA9ICJQcmVjaW8gw5NwdGltbyIsCiAgICAgIGNvbG9yID0gIklEIFByb2R1Y3RvIgogICAgKSArCiAgICB0aGVtZV9taW5pbWFsKCkKCiAgIyA0LiBNw6l0cmljYXMgZmluYWxlcwogIG1ldHJpY2FzX29wdGltYXMgPC0gZGF0YS5mcmFtZSgpCgogIGZvciAocGlkIGluIHRvcF9pZHMpIHsKICAgIHBpZF9zdHIgPC0gYXMuY2hhcmFjdGVyKHBpZCkKICAgIGlmIChwaWRfc3RyICVpbiUgbmFtZXMoaW50ZWdyYXRlZF9yZXN1bHRzKSkgewogICAgICBwcmVkX2RhdGEgPC0gaW50ZWdyYXRlZF9yZXN1bHRzW1twaWRfc3RyXV0KCiAgICAgIGlmICgiTWFyZ2VuX1hHQm9vc3QiICVpbiUgbmFtZXMocHJlZF9kYXRhKSkgewogICAgICAgIG1ldHJpY3Nfcm93IDwtIGRhdGEuZnJhbWUoCiAgICAgICAgICBJRF9JbnZlbnRhcmlvID0gcGlkLAogICAgICAgICAgUHJlY2lvX1Byb21lZGlvID0gbWVhbihwcmVkX2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCBuYS5ybSA9IFRSVUUpLAogICAgICAgICAgVmVudGFfVG90YWwgPSBzdW0ocHJlZF9kYXRhJFZlbnRhX1hHQm9vc3QsIG5hLnJtID0gVFJVRSksCiAgICAgICAgICBNYXJnZW5fVG90YWwgPSBzdW0ocHJlZF9kYXRhJE1hcmdlbl9YR0Jvb3N0LCBuYS5ybSA9IFRSVUUpLAogICAgICAgICAgTWFyZ2VuX1BvcmNlbnR1YWwgPSAxMDAgKiBzdW0ocHJlZF9kYXRhJE1hcmdlbl9YR0Jvb3N0LCBuYS5ybSA9IFRSVUUpIC8KICAgICAgICAgICAgc3VtKHByZWRfZGF0YSRWZW50YV9YR0Jvb3N0LCBuYS5ybSA9IFRSVUUpCiAgICAgICAgKQogICAgICAgIG1ldHJpY2FzX29wdGltYXMgPC0gcmJpbmQobWV0cmljYXNfb3B0aW1hcywgbWV0cmljc19yb3cpCiAgICAgIH0KICAgIH0KICB9CgogIHJldHVybihsaXN0KAogICAgcmVzdWx0YWRvcyA9IGFsbF9yZXN1bHRzLAogICAgaW50ZWdyYWNpb24gPSBpbnRlZ3JhdGVkX3Jlc3VsdHMsCiAgICBwcmVjaW9zX29wdGltb3MgPSBhbGxfb3B0aW1hbF9wcmljZXMsCiAgICBtZXRyaWNhc19vcHRpbWFzID0gbWV0cmljYXNfb3B0aW1hcywKICAgIGdyYWZpY29fY29tcGFyYXRpdm8gPSBwX2NvbXBhcmlzb24KICApKQp9CgpgYGAKCmBgYHtyfQpyZXN1bHRhZG9fY29tcGxldG8gPC0gcnVuX2NvbXBsZXRlX2FuYWx5c2lzKAogIGRhdGEgPSBkYXRvcywKICB0b3BfaWRzID0gcHJvZHVjdG9zX2lkcywKICBtb2RlbG9zX3hnYiA9IG1vZGVsb3NfeGdiX2xpc3RhLAogIG1vZGVsb3NfcHJlY2lvX2xpc3RhID0gbW9kZWxvc19wcmVjaW9fbGlzdGEKKQpgYGAKCgojIyMgR3LDoWZpY28gY29tcGFyYXRpdm8gZGUgcHJlY2lvcyDDs3B0aW1vcyBwb3IgcHJvZHVjdG86CmBgYHtyfQojIE1vc3RyYXIgbcOpdHJpY2FzIHNpIGVzdMOhcyBlbiBtb2RvIGludGVyYWN0aXZvCmlmIChpbnRlcmFjdGl2ZSgpKSBWaWV3KHJlc3VsdGFkb19jb21wbGV0byRtZXRyaWNhc19vcHRpbWFzKQoKY2F0KCJHcsOhZmljbyBjb21wYXJhdGl2byBkZSBwcmVjaW9zIMOzcHRpbW9zIHBvciBwcm9kdWN0bzpcbiIpCmBgYAoKYGBge3J9CnByaW50KHJlc3VsdGFkb19jb21wbGV0byRncmFmaWNvX2NvbXBhcmF0aXZvKQpgYGAKCiMgUHJlZGljY2nDs24gZGUgdmVudGFzIGNvbiBwcmVjaW9zIG9wdGltb3MgcG9yIHByb2R1Y3RvCgoKYGBge3J9CmNhdCgiR3LDoWZpY29zIGluZGl2aWR1YWxlcyBwb3IgcHJvZHVjdG86XG4iKQpgYGAKCgpgYGB7cn0KZm9yIChwaWQgaW4gbmFtZXMocmVzdWx0YWRvX2NvbXBsZXRvJHJlc3VsdGFkb3MpKSB7CiAgcGxvdHMgPC0gcmVzdWx0YWRvX2NvbXBsZXRvJHJlc3VsdGFkb3NbW3BpZF1dJGludGVncmF0ZWRfcGxvdHMKICBpZiAoIWlzLm51bGwocGxvdHMpKSB7CiAgICBjYXQocGFzdGUwKCIjIyBQcm9kdWN0bzogIiwgcGlkLCAiXG5cbiIpKQogICAgcHJpbnQocGxvdHMkc2FsZXMpICAgIyBzb2xvIHVzYSBWZW50YV9YR0Jvb3N0IGludGVybmFtZW50ZQogICAgcHJpbnQocGxvdHMkbWFyZ2lucykgIyBzb2xvIHVzYSBNYXJnZW5fWEdCb29zdCBpbnRlcm5hbWVudGUKICAgIAogICAgY2F0KCJcbi0tLVxuXG4iKQogIH0KfQpgYGAKCgojIyMgVEFCTEEgRklOQUwgCmBgYHtyfQp0YWJsYV9maW5hbCA8LSBkYXRhLmZyYW1lKCkKCmZvciAocGlkIGluIHByb2R1Y3Rvc19pZHMpIHsKICBwcmVkIDwtIHJlc3VsdGFkb19jb21wbGV0byRpbnRlZ3JhY2lvbltbYXMuY2hhcmFjdGVyKHBpZCldXQogIHJlYWwgPC0gZGF0b3NfZmlsdHJhZG9zICU+JSBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSBwaWQpCgogIGlmICghaXMubnVsbChwcmVkKSAmJiBucm93KHByZWQpID4gMCkgewogICAgcHJlY2lvX3Byb21lZGlvIDwtIG1lYW4ocmVhbCRQcmVjaW9fRmluYWxfVW5pdGFyaW8sIG5hLnJtID0gVFJVRSkKICAgIG1hcmdlbl9oaXN0b3JpY29fcHJvbWVkaW8gPC0gbWVhbihyZWFsJFZlbnRhIC0gcmVhbCRDb3N0b19WZW50YSwgbmEucm0gPSBUUlVFKQoKICAgICMgRXh0cmFlciBtZXMgZGUgbGFzIGZlY2hhcyBmdXR1cmFzIHNpbXVsYWRhcwogICAgcHJlZCRNZXNfU2ltdWxhZG8gPC0gbHVicmlkYXRlOjptb250aChwcmVkJEZlY2hhKQoKICAgICMgQ2FsY3VsYXIgdmVudGEgcHJvbWVkaW8gaGlzdMOzcmljYSBwb3IgbWVzCiAgICB2ZW50YV9oaXN0b3JpY2FfcG9yX21lcyA8LSByZWFsICU+JQogICAgICBtdXRhdGUoTWVzID0gbHVicmlkYXRlOjptb250aChUcnhfRmVjaGEpKSAlPiUKICAgICAgZ3JvdXBfYnkoTWVzKSAlPiUKICAgICAgc3VtbWFyaXNlKFZlbnRhX0hpc3RvcmljYV9Qcm9tZWRpbyA9IG1lYW4oVmVudGEsIG5hLnJtID0gVFJVRSksIC5ncm91cHMgPSAiZHJvcCIpCgogICAgIyBBZ3JlZ2FyIGNvbHVtbmEgZGUgdmVudGEgZXNwZXJhZGEgaGlzdMOzcmljYSBhbCBkZiBkZSBwcmVkaWNjacOzbgogICAgcHJlZCA8LSBwcmVkICU+JQogICAgICBsZWZ0X2pvaW4odmVudGFfaGlzdG9yaWNhX3Bvcl9tZXMsIGJ5ID0gYygiTWVzX1NpbXVsYWRvIiA9ICJNZXMiKSkKCiAgICB0YWJsYV9wcm9kdWN0byA8LSBwcmVkICU+JQogICAgICBzZWxlY3QoRmVjaGEsIFByZWNpb19GaW5hbF9Vbml0YXJpbywgVmVudGFfWEdCb29zdCwgTWFyZ2VuX1hHQm9vc3QsIFZlbnRhX0hpc3RvcmljYV9Qcm9tZWRpbykgJT4lCiAgICAgIHJlbmFtZSgKICAgICAgICBQcmVjaW9fT3B0aW1vID0gUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLAogICAgICAgIFZlbnRhX0VzcGVyYWRhX1hHQm9vc3QgPSBWZW50YV9YR0Jvb3N0LAogICAgICAgIE1hcmdlbl9YR0Jvb3N0ID0gTWFyZ2VuX1hHQm9vc3QsCiAgICAgICAgVmVudGFfRXNwZXJhZGFfSGlzdG9yaWNhID0gVmVudGFfSGlzdG9yaWNhX1Byb21lZGlvCiAgICAgICkgJT4lCiAgICAgIG11dGF0ZSgKICAgICAgICBJRF9JbnZlbnRhcmlvID0gcGlkLAogICAgICAgIFByZWNpb19Qcm9tZWRpb19IaXN0b3JpY28gPSByb3VuZChwcmVjaW9fcHJvbWVkaW8sIDIpLAogICAgICAgIE1hcmdlbl9IaXN0b3JpY29fUHJvbWVkaW8gPSByb3VuZChtYXJnZW5faGlzdG9yaWNvX3Byb21lZGlvLCAyKQogICAgICApICU+JQogICAgICBzZWxlY3QoSURfSW52ZW50YXJpbywgRmVjaGEsIFByZWNpb19Qcm9tZWRpb19IaXN0b3JpY28sIFByZWNpb19PcHRpbW8sCiAgICAgICAgICAgICBWZW50YV9Fc3BlcmFkYV9IaXN0b3JpY2EsIFZlbnRhX0VzcGVyYWRhX1hHQm9vc3QsCiAgICAgICAgICAgICBNYXJnZW5fSGlzdG9yaWNvX1Byb21lZGlvLCBNYXJnZW5fWEdCb29zdCkKCiAgICB0YWJsYV9maW5hbCA8LSBiaW5kX3Jvd3ModGFibGFfZmluYWwsIHRhYmxhX3Byb2R1Y3RvKQogIH0KfQoKcHJpbnQodGFibGFfZmluYWwpCmBgYAoKCgoKCgoKCgo=