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

library(readxl)
library(dplyr)
library(lubridate)

# Leer datos
datos <- read_excel("/Users/oscarcastanedagarcia/Downloads/IA con impacto empresarial/filtered_data.xlsx") %>%
  mutate(Trx_Fecha = as.Date(Trx_Fecha))

# Partición temporal
train <- datos %>% filter(Trx_Fecha >= as.Date("2023-01-01") & Trx_Fecha < as.Date("2024-10-01"))
test  <- datos %>% filter(Trx_Fecha >= as.Date("2024-10-01") & Trx_Fecha <= as.Date("2024-12-31"))

# Validación rápida
summary(train$Trx_Fecha)
##         Min.      1st Qu.       Median         Mean      3rd Qu.         Max. 
## "2023-01-02" "2023-06-21" "2023-11-30" "2023-12-01" "2024-05-16" "2024-09-30"
summary(test$Trx_Fecha)
##         Min.      1st Qu.       Median         Mean      3rd Qu.         Max. 
## "2024-10-02" "2024-10-23" "2024-11-13" "2024-11-13" "2024-12-04" "2024-12-31"
# 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 <- train %>%
  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 11997
## 2        155001  7669
## 3        155002  5146
## 4       3904152  2275
## 5       3678055  1442
# 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: 7669 y 11997"
## [1] "Comparando productos 155001 vs 155002"
## [1] "Cantidad de precios: 7669 y 5146"
## [1] "Comparando productos 155001 vs 3904152"
## [1] "Cantidad de precios: 7669 y 2275"
## [1] "Comparando productos 155001 vs 3678055"
## [1] "Cantidad de precios: 7669 y 1442"
## [1] "Comparando productos 3929788 vs 155002"
## [1] "Cantidad de precios: 11997 y 5146"
## [1] "Comparando productos 3929788 vs 3904152"
## [1] "Cantidad de precios: 11997 y 2275"
## [1] "Comparando productos 3929788 vs 3678055"
## [1] "Cantidad de precios: 11997 y 1442"
## [1] "Comparando productos 155002 vs 3904152"
## [1] "Cantidad de precios: 5146 y 2275"
## [1] "Comparando productos 155002 vs 3678055"
## [1] "Cantidad de precios: 5146 y 1442"
## [1] "Comparando productos 3904152 vs 3678055"
## [1] "Cantidad de precios: 2275 y 1442"
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.0000       0 Distribuciones diferentes
## D...2      155001     155002 0.0488       0 Distribuciones diferentes
## D...3      155001    3904152 1.0000       0 Distribuciones diferentes
## D...4      155001    3678055 1.0000       0 Distribuciones diferentes
## D...5     3929788     155002 1.0000       0 Distribuciones diferentes
## D...6     3929788    3904152 1.0000       0 Distribuciones diferentes
## D...7     3929788    3678055 1.0000       0 Distribuciones diferentes
## D...8      155002    3904152 1.0000       0 Distribuciones diferentes
## D...9      155002    3678055 1.0000       0 Distribuciones diferentes
## D...10    3904152    3678055 1.0000       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 19.23659 253085.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.3378 253085.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 14.91903 155842.3

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 33.55621 235419.9

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.32809 175375.1

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

# Filtrar solo los datos para el producto 155001
datos_155001 <- datos_filtrados %>%
  filter(ID_Inventario == 155001) %>%
  select(Venta, Cant, Costo_Venta,
         Precio_Final_Unitario, Descuento_Porcentaje, Trx_Fecha) %>%
  na.omit()  # Eliminar filas con valores NA

# Crear una variable de tiempo continua basada en la fecha
datos_155001 <- datos_155001 %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month")),   # Asegúrate de que la fecha esté en formato Date
         Tiempo = as.numeric(Fecha - min(Fecha)) / (30 * 24 * 60 * 60))  # Tiempo en meses (ajustado por días)

# Verificar las primeras filas para asegurarnos de que la variable de tiempo esté bien creada
head(datos_155001)
## # A tibble: 6 × 8
##    Venta  Cant Costo_Venta Precio_Final_Unitario Descuento_Porcentaje Trx_Fecha 
##    <dbl> <dbl>       <dbl>                 <dbl>                <dbl> <date>    
## 1  1187.     2       1194.                  594.                 85.8 2023-02-09
## 2 21280     40      23874.                  532                  87.3 2023-02-09
## 3 15960     30      17906.                  532                  87.3 2023-02-16
## 4 31920     60      35811.                  532                  87.3 2023-02-16
## 5  2968      5       2570.                  594.                 85.8 2023-02-20
## 6  1187.     2        958.                  594.                 85.8 2023-02-25
## # ℹ 2 more variables: Fecha <date>, Tiempo <dbl>
# Filtrar solo los datos para el producto 155001
test_155001 <- test %>%
  filter(ID_Inventario == 155001) %>%
  select(Venta, Cant, Costo_Venta,
         Precio_Final_Unitario, Descuento_Porcentaje, Trx_Fecha) %>%
  na.omit() %>%
  mutate(
    Fecha = as.Date(floor_date(Trx_Fecha, "month")),
    Tiempo = as.numeric(Fecha - min(Fecha)) / (30 * 24 * 60 * 60)
  )

# Verificar
head(test_155001)
## # A tibble: 6 × 8
##   Venta  Cant Costo_Venta Precio_Final_Unitario Descuento_Porcentaje Trx_Fecha 
##   <dbl> <dbl>       <dbl>                 <dbl>                <dbl> <date>    
## 1  4536     9       3801.                   504                 91   2024-10-08
## 2 17640    35      14782.                   504                 91   2024-10-08
## 3  9240    20       8447.                   462                 91.8 2024-10-08
## 4  4032     8       3379.                   504                 91   2024-10-08
## 5  7350    15       6336.                   490                 91.2 2024-10-10
## 6  1008     2        845.                   504                 91   2024-10-10
## # ℹ 2 more variables: Fecha <date>, Tiempo <dbl>
# 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 
## -11512.1   -107.6     38.0     86.6  26600.8 
## 
## Coefficients:
##                         Estimate Std. Error t value Pr(>|t|)    
## (Intercept)           -3.647e+03  1.362e+03  -2.677  0.00745 ** 
## Cant                   1.813e+02  3.469e+00  52.255  < 2e-16 ***
## Costo_Venta            6.035e-01  9.413e-03  64.120  < 2e-16 ***
## Precio_Final_Unitario  2.880e+00  3.082e-01   9.346  < 2e-16 ***
## Descuento_Porcentaje   2.786e+01  1.390e+01   2.004  0.04510 *  
## Tiempo                -4.150e+05  1.973e+05  -2.104  0.03543 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 750.7 on 7663 degrees of freedom
## Multiple R-squared:  0.9868, Adjusted R-squared:  0.9868 
## F-statistic: 1.143e+05 on 5 and 7663 DF,  p-value: < 2.2e-16
# Ajustar el modelo de regresión lineal
modelo_regresion_155001_test <- lm(Venta ~ Cant + Costo_Venta +
                              Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
                             data = test_155001)

# Ver resumen del modelo
summary(modelo_regresion_155001_test)
## 
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Precio_Final_Unitario + 
##     Descuento_Porcentaje + Tiempo, data = test_155001)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1160.96   -88.67   -40.17    32.41  1402.28 
## 
## Coefficients:
##                         Estimate Std. Error t value Pr(>|t|)    
## (Intercept)           -1.544e+04  1.011e+04  -1.527 0.127159    
## Cant                   6.952e+02  3.124e+01  22.256  < 2e-16 ***
## Costo_Venta           -5.240e-01  7.440e-02  -7.043 3.79e-12 ***
## Precio_Final_Unitario  6.658e+00  1.755e+00   3.795 0.000158 ***
## Descuento_Porcentaje   1.338e+02  1.017e+02   1.316 0.188645    
## Tiempo                -1.810e+06  1.276e+06  -1.419 0.156281    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 193.4 on 884 degrees of freedom
## Multiple R-squared:  0.9995, Adjusted R-squared:  0.9995 
## F-statistic: 3.328e+05 on 5 and 884 DF,  p-value: < 2.2e-16
# 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 (train data): ", mape_155001, "\n")
## MAPE del modelo de regresión lineal para 155001 (train data):  13.66606
cat("RMSE del modelo de regresión lineal para 155001 (train data): ", rmse_155001, "\n")
## RMSE del modelo de regresión lineal para 155001 (train data):  750.419
# Diagnóstico de residuos del modelo
par(mfrow = c(2, 2))
plot(modelo_regresion_155001)

# Ajuste del modelo de regresión lineal
modelo_regresion_155001_test <- lm(Venta ~ Cant + Costo_Venta +
                              Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
                             data = test_155001)

# Predicciones usando el modelo ajustado
predicciones_155001_test <- predict(modelo_regresion_155001_test, newdata = test_155001)

# Calcular MAPE (Mean Absolute Percentage Error)
mape_155001_test <- mean(abs((test_155001$Venta - predicciones_155001_test) / test_155001$Venta)) * 100


# Calcular RMSE (Root Mean Squared Error)
rmse_155001_test <- sqrt(mean((test_155001$Venta - predicciones_155001_test)^2))



# Mostrar las métricas
cat("MAPE del modelo de regresión lineal para 155001 (test data): ", mape_155001_test, "\n")
## MAPE del modelo de regresión lineal para 155001 (test data):  8.668306
cat("RMSE del modelo de regresión lineal para 155001 (test data): ", rmse_155001_test, "\n")
## RMSE del modelo de regresión lineal para 155001 (test data):  192.789
# Diagnóstico de residuos del modelo
par(mfrow = c(2, 2))
plot(modelo_regresion_155001_test)

# Inicializar el data.frame correctamente con todas las columnas esperadas
if (!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

# Asegúrate de que las métricas existen y son numéricas
print(mape_155001_test)
## [1] 8.668306
print(rmse_155001_test)
## [1] 192.789
# Crear la nueva fila
nueva_fila <- data.frame(
  Producto = "155001",
  Modelo = "Regresión Lineal",
  MAPE = mape_155001_test,
  RMSE = rmse_155001_test,
  stringsAsFactors = FALSE
)

# Confirmar que las columnas coinciden
print(names(metricas_comparativas))
## [1] "Producto" "Modelo"   "MAPE"     "RMSE"
print(names(nueva_fila))
## [1] "Producto" "Modelo"   "MAPE"     "RMSE"
# Agregar la nueva fila
metricas_comparativas <- rbind(metricas_comparativas, nueva_fila)

# Verificar resultado
print(metricas_comparativas)
##   Producto           Modelo      MAPE       RMSE
## 1   155001             ARMA 19.236587 253085.203
## 2  3929788             ARMA 12.337802 253085.203
## 3  3904152             ARMA 14.919034 155842.280
## 4   155002             ARMA 33.556214 235419.911
## 5  3678055             ARMA 22.328087 175375.134
## 6   155001 Regresión Lineal  8.668306    192.789

5.3 PRODUCTO 3929788

# Filtrar solo los datos para el producto 3929788
datos_3929788 <- datos_filtrados %>%
  filter(ID_Inventario == 3929788) %>%
  select(Venta, Cant, Costo_Venta,
         Precio_Final_Unitario, Descuento_Porcentaje, Trx_Fecha) %>%
  na.omit()  # Eliminar filas con valores NA

# Crear una variable de tiempo continua basada en la fecha
datos_3929788 <- datos_3929788 %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month")),   # Asegúrate de que la fecha esté en formato Date
         Tiempo = as.numeric(Fecha - min(Fecha)) / (30 * 24 * 60 * 60))  # Tiempo en meses (ajustado por días)

# Verificar las primeras filas para asegurarnos de que la variable de tiempo esté bien creada
head(datos_3929788)
## # A tibble: 6 × 8
##    Venta  Cant Costo_Venta Precio_Final_Unitario Descuento_Porcentaje Trx_Fecha 
##    <dbl> <dbl>       <dbl>                 <dbl>                <dbl> <date>    
## 1   364     10        254.                  36.4                 60   2023-02-03
## 2   242.     6        167.                  40.3                 60   2023-02-23
## 3   697.    15        506.                  46.5                 48.9 2023-02-01
## 4 13020    300      10110.                  43.4                 52.3 2023-02-03
## 5  2170     50       1685.                  43.4                 52.3 2023-02-07
## 6   434     10        337.                  43.4                 52.3 2023-02-08
## # ℹ 2 more variables: Fecha <date>, Tiempo <dbl>
# Filtrar solo los datos para el producto 155001
test_3929788 <- test %>%
  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")),
    Tiempo = as.numeric(Fecha - min(Fecha)) / (30 * 24 * 60 * 60)
  )

# Verificar
head(test_3929788)
## # A tibble: 6 × 8
##   Venta  Cant Costo_Venta Precio_Final_Unitario Descuento_Porcentaje Trx_Fecha 
##   <dbl> <dbl>       <dbl>                 <dbl>                <dbl> <date>    
## 1  697.    16        484.                  43.6                 60   2024-10-04
## 2 3116.    70       2119.                  44.5                 59.1 2024-10-12
## 3 1307.    30        908.                  43.6                 60   2024-10-14
## 4  436.    10        303.                  43.6                 60   2024-10-21
## 5 1736     40       1211.                  43.4                 60.2 2024-10-29
## 6 1862     35       1522.                  53.2                 51.2 2024-10-02
## # ℹ 2 more variables: Fecha <date>, 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 
## -4293.6   -78.7   -45.5    25.8  3411.6 
## 
## Coefficients:
##                         Estimate Std. Error t value Pr(>|t|)    
## (Intercept)            5.875e+01  1.792e+02   0.328  0.74311    
## Cant                   3.342e+00  2.676e-01  12.489  < 2e-16 ***
## Costo_Venta            1.070e+00  8.920e-03 119.985  < 2e-16 ***
## Precio_Final_Unitario  5.819e+00  1.783e+00   3.263  0.00111 ** 
## Descuento_Porcentaje  -2.972e+00  1.859e+00  -1.599  0.10987    
## Tiempo                 1.520e+04  5.306e+04   0.287  0.77445    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 217.7 on 11991 degrees of freedom
## Multiple R-squared:  0.9952, Adjusted R-squared:  0.9952 
## F-statistic: 4.974e+05 on 5 and 11991 DF,  p-value: < 2.2e-16
# Ajustar el modelo de regresión lineal
modelo_regresion_3929788_test <- lm(Venta ~ Cant + Costo_Venta +
                              Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
                             data = test_3929788)

# Ver resumen del modelo
summary(modelo_regresion_3929788_test)
## 
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Precio_Final_Unitario + 
##     Descuento_Porcentaje + Tiempo, data = test_3929788)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -2961.66   -58.44   -42.50    19.43  1815.57 
## 
## Coefficients:
##                         Estimate Std. Error t value Pr(>|t|)    
## (Intercept)            7.604e+04  2.351e+04   3.234  0.00124 ** 
## Cant                   5.662e+00  5.792e-01   9.776  < 2e-16 ***
## Costo_Venta            1.072e+00  1.840e-02  58.281  < 2e-16 ***
## Precio_Final_Unitario -6.925e+02  2.159e+02  -3.208  0.00136 ** 
## Descuento_Porcentaje  -7.631e+02  2.351e+02  -3.246  0.00119 ** 
## Tiempo                 3.006e+05  4.245e+05   0.708  0.47894    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 167.7 on 1714 degrees of freedom
## Multiple R-squared:  0.9958, Adjusted R-squared:  0.9958 
## F-statistic: 8.078e+04 on 5 and 1714 DF,  p-value: < 2.2e-16
# 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 (train data): ", mape_3929788, "\n")
## MAPE del modelo de regresión lineal para 3929788 (train data):  22.76398
cat("RMSE del modelo de regresión lineal para 3929788 (train data): ", rmse_3929788, "\n")
## RMSE del modelo de regresión lineal para 3929788 (train data):  217.6829
# 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
))
# Ajuste del modelo de regresión lineal
modelo_regresion_3929788_test <- lm(Venta ~ Cant + Costo_Venta +
                              Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
                             data = test_3929788)

# Predicciones usando el modelo ajustado
predicciones_3929788_test <- predict(modelo_regresion_3929788_test, newdata = test_3929788)

# Calcular MAPE (Mean Absolute Percentage Error)
mape_3929788_test <- mean(abs((test_3929788$Venta - predicciones_3929788_test) / test_3929788$Venta)) * 100


# Calcular RMSE (Root Mean Squared Error)
rmse_3929788_test <- sqrt(mean((test_3929788$Venta - predicciones_3929788_test)^2))



# Mostrar las métricas
cat("MAPE del modelo de regresión lineal para 3929788 (test data): ", mape_3929788_test, "\n")
## MAPE del modelo de regresión lineal para 3929788 (test data):  16.03039
cat("RMSE del modelo de regresión lineal para 3929788 (test data): ", rmse_3929788_test, "\n")
## RMSE del modelo de regresión lineal para 3929788 (test data):  167.3956
# Diagnóstico de residuos del modelo
par(mfrow = c(2, 2))
plot(modelo_regresion_3929788_test)

5.4 PRODUCTO 3904152

# Filtrar solo los datos para el producto 3904152
datos_3904152 <- datos_filtrados %>%
  filter(ID_Inventario == 3904152) %>%
  select(Venta, Cant, Costo_Venta,
         Precio_Final_Unitario, Descuento_Porcentaje, Trx_Fecha) %>%
  na.omit()  # Eliminar filas con valores NA
# Crear una variable de tiempo continua basada en la fecha
datos_3904152 <- datos_3904152 %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month")),   # Asegúrate de que la fecha esté en formato Date
         Tiempo = as.numeric(Fecha - min(Fecha)) / (30 * 24 * 60 * 60))  # Tiempo en meses (ajustado por días)

# Verificar las primeras filas para asegurarnos de que la variable de tiempo esté bien creada
head(datos_3904152)
## # A tibble: 6 × 8
##   Venta  Cant Costo_Venta Precio_Final_Unitario Descuento_Porcentaje Trx_Fecha 
##   <dbl> <dbl>       <dbl>                 <dbl>                <dbl> <date>    
## 1  3402     1       2462.                  3402                 62.4 2023-02-02
## 2  9240     3       7382.                  3080                 66.0 2023-02-13
## 3  3402     1       2461.                  3402                 62.4 2023-02-14
## 4  3402     1       2462.                  3402                 62.4 2023-02-22
## 5  3402     1       2462.                  3402                 62.4 2023-02-27
## 6 30800    10      24563.                  3080                 66.0 2023-02-10
## # ℹ 2 more variables: Fecha <date>, Tiempo <dbl>
# Filtrar solo los datos para el producto 155001
test_3904152 <- test %>%
  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")),
    Tiempo = as.numeric(Fecha - min(Fecha)) / (30 * 24 * 60 * 60)
  )

# Verificar
head(test_3904152)
## # A tibble: 6 × 8
##   Venta  Cant Costo_Venta Precio_Final_Unitario Descuento_Porcentaje Trx_Fecha 
##   <dbl> <dbl>       <dbl>                 <dbl>                <dbl> <date>    
## 1  3556     1       2389.                  3556                 64.7 2024-10-03
## 2  3556     1       2389.                  3556                 64.7 2024-10-03
## 3  3556     1       2476.                  3556                 64.7 2024-10-10
## 4  6720     2       4947.                  3360                 66.6 2024-10-09
## 5  3556     1       2473.                  3556                 64.7 2024-10-11
## 6  3556     1       2473.                  3556                 64.7 2024-10-14
## # ℹ 2 more variables: Fecha <date>, 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 
## -6959.8  -130.9    -6.0   120.4  3001.7 
## 
## Coefficients:
##                         Estimate Std. Error t value Pr(>|t|)    
## (Intercept)            4.936e+03  1.379e+03   3.579 0.000352 ***
## Cant                  -7.447e+01  8.108e+01  -0.918 0.358496    
## Costo_Venta            1.316e+00  3.454e-02  38.102  < 2e-16 ***
## Precio_Final_Unitario  6.564e-01  1.438e-01   4.565 5.26e-06 ***
## Descuento_Porcentaje  -1.090e+02  1.460e+01  -7.464 1.19e-13 ***
## Tiempo                 2.523e+06  1.528e+05  16.511  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 371.5 on 2269 degrees of freedom
## Multiple R-squared:  0.9988, Adjusted R-squared:  0.9988 
## F-statistic: 3.895e+05 on 5 and 2269 DF,  p-value: < 2.2e-16
# Ajustar el modelo de regresión lineal
modelo_regresion_3904152_test <- lm(Venta ~ Cant + Costo_Venta +
                              Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
                             data = test_3904152)

# Ver resumen del modelo
summary(modelo_regresion_3904152_test)
## 
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Precio_Final_Unitario + 
##     Descuento_Porcentaje + Tiempo, data = test_3904152)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1103.33  -116.88     6.33    99.88  1029.64 
## 
## Coefficients: (1 not defined because of singularities)
##                         Estimate Std. Error t value Pr(>|t|)    
## (Intercept)           -5.552e+03  3.387e+02 -16.395   <2e-16 ***
## Cant                   3.681e+03  1.761e+02  20.901   <2e-16 ***
## Costo_Venta           -1.521e-01  6.865e-02  -2.216   0.0275 *  
## Precio_Final_Unitario  1.654e+00  9.659e-02  17.124   <2e-16 ***
## Descuento_Porcentaje          NA         NA      NA       NA    
## Tiempo                 3.435e+06  1.509e+06   2.277   0.0236 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 211.1 on 276 degrees of freedom
## Multiple R-squared:  0.9994, Adjusted R-squared:  0.9994 
## F-statistic: 1.096e+05 on 4 and 276 DF,  p-value: < 2.2e-16
# 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 (train data): ", mape_3904152, "\n")
## MAPE del modelo de regresión lineal para 3904152 (train data):  3.298787
cat("RMSE del modelo de regresión lineal para 3904152 (train data): ", rmse_3904152, "\n")
## RMSE del modelo de regresión lineal para 3904152 (train data):  371.0529
# 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
))
# Ajuste del modelo de regresión lineal
modelo_regresion_3904152_test <- lm(Venta ~ Cant + Costo_Venta +
                              Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
                             data = test_3904152)

# Predicciones usando el modelo ajustado
predicciones_3904152_test <- predict(modelo_regresion_3904152_test, newdata = test_3904152)

# Calcular MAPE (Mean Absolute Percentage Error)
mape_3904152_test <- mean(abs((test_3904152$Venta - predicciones_3904152_test) / test_3904152$Venta)) * 100


# Calcular RMSE (Root Mean Squared Error)
rmse_3904152_test <- sqrt(mean((test_3904152$Venta - predicciones_3904152_test)^2))



# Mostrar las métricas
cat("MAPE del modelo de regresión lineal para 3904152 (test data): ", mape_3904152_test, "\n")
## MAPE del modelo de regresión lineal para 3904152 (test data):  2.097418
cat("RMSE del modelo de regresión lineal para 3904152 (test data): ", rmse_3904152_test, "\n")
## RMSE del modelo de regresión lineal para 3904152 (test data):  209.1719
# Diagnóstico de residuos del modelo
par(mfrow = c(2, 2))
plot(modelo_regresion_3904152_test)

5.5 PRODUCTO 155002

# Filtrar solo los datos para el producto 155002
datos_155002 <- datos_filtrados %>%
  filter(ID_Inventario == 155002) %>%
  select(Venta, Cant, Costo_Venta,
         Precio_Final_Unitario, Descuento_Porcentaje, Trx_Fecha) %>%
  na.omit()  # Eliminar filas con valores NA

# Crear una variable de tiempo continua basada en la fecha
datos_155002 <- datos_155002 %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month")),   # Asegúrate de que la fecha esté en formato Date
         Tiempo = as.numeric(Fecha - min(Fecha)) / (30 * 24 * 60 * 60))  # Tiempo en meses (ajustado por días)

# Verificar las primeras filas para asegurarnos de que la variable de tiempo esté bien creada
head(datos_155002)
## # A tibble: 6 × 8
##   Venta  Cant Costo_Venta Precio_Final_Unitario Descuento_Porcentaje Trx_Fecha 
##   <dbl> <dbl>       <dbl>                 <dbl>                <dbl> <date>    
## 1 5320     10       8247.                  532                  87.3 2023-02-09
## 2 5320     10       8247.                  532                  87.3 2023-02-20
## 3 2660      5       4124.                  532                  87.3 2023-02-28
## 4  630      1        519.                  630                  85.0 2023-02-02
## 5 1120      2       1037.                  560                  86.6 2023-02-03
## 6 1537.     3       1556.                  512.                 87.8 2023-02-07
## # ℹ 2 more variables: Fecha <date>, Tiempo <dbl>
# Filtrar solo los datos para el producto 155001
test_155002 <- test %>%
  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")),
    Tiempo = as.numeric(Fecha - min(Fecha)) / (30 * 24 * 60 * 60)
  )

# Verificar
head(test_155002)
## # A tibble: 6 × 8
##   Venta  Cant Costo_Venta Precio_Final_Unitario Descuento_Porcentaje Trx_Fecha 
##   <dbl> <dbl>       <dbl>                 <dbl>                <dbl> <date>    
## 1  1008     2        830.                   504                 91   2024-10-04
## 2  2310     5       2076.                   462                 91.8 2024-10-08
## 3  2450     5       2076.                   490                 91.2 2024-10-16
## 4  2310     5       2086.                   462                 91.8 2024-10-21
## 5  4900    10       4167.                   490                 91.2 2024-10-31
## 6 13860    30      12656.                   462                 91.8 2024-10-02
## # ℹ 2 more variables: Fecha <date>, 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 
##  -8478   -135     51    120  46880 
## 
## Coefficients:
##                         Estimate Std. Error t value Pr(>|t|)    
## (Intercept)            1.353e+03  2.248e+03   0.602   0.5473    
## Cant                   3.044e+02  3.951e+00  77.061  < 2e-16 ***
## Costo_Venta            2.602e-01  9.988e-03  26.049  < 2e-16 ***
## Precio_Final_Unitario  3.432e+00  4.934e-01   6.956 3.93e-12 ***
## Descuento_Porcentaje  -3.166e+01  2.300e+01  -1.376   0.1688    
## Tiempo                 5.214e+05  3.018e+05   1.727   0.0842 .  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 890.3 on 5140 degrees of freedom
## Multiple R-squared:  0.9675, Adjusted R-squared:  0.9675 
## F-statistic: 3.064e+04 on 5 and 5140 DF,  p-value: < 2.2e-16
# Ajustar el modelo de regresión lineal
modelo_regresion_155002_test <- lm(Venta ~ Cant + Costo_Venta +
                              Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
                             data = test_155002)

# Ver resumen del modelo
summary(modelo_regresion_155002_test)
## 
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Precio_Final_Unitario + 
##     Descuento_Porcentaje + Tiempo, data = test_155002)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -568.05  -67.17  -20.80   49.31  749.84 
## 
## Coefficients:
##                         Estimate Std. Error t value Pr(>|t|)    
## (Intercept)            1.855e+04  8.751e+03   2.120 0.034394 *  
## Cant                   5.730e+02  2.938e+01  19.504  < 2e-16 ***
## Costo_Venta           -2.398e-01  6.955e-02  -3.448 0.000605 ***
## Precio_Final_Unitario  1.195e-01  1.507e+00   0.079 0.936853    
## Descuento_Porcentaje  -2.038e+02  8.809e+01  -2.313 0.021061 *  
## Tiempo                 3.313e+06  1.092e+06   3.034 0.002515 ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 142.5 on 600 degrees of freedom
## Multiple R-squared:  0.9997, Adjusted R-squared:  0.9997 
## F-statistic: 3.841e+05 on 5 and 600 DF,  p-value: < 2.2e-16
# 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 (train data): ", mape_155002, "\n")
## MAPE del modelo de regresión lineal para 155002 (train data):  18.14239
cat("RMSE del modelo de regresión lineal para 155002 (train data): ", rmse_155002, "\n")
## RMSE del modelo de regresión lineal para 155002 (train data):  889.8286
# Diagnóstico de residuos del modelo
par(mfrow = c(2, 2))
plot(modelo_regresion_155002)

# Ajuste del modelo de regresión lineal
modelo_regresion_155002_test <- lm(Venta ~ Cant + Costo_Venta +
                              Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
                             data = test_155002)

# Predicciones usando el modelo ajustado
predicciones_155002_test <- predict(modelo_regresion_155002_test, newdata = test_155002)

# Calcular MAPE (Mean Absolute Percentage Error)
mape_155002_test <- mean(abs((test_155002$Venta - predicciones_155002_test) / test_155002$Venta)) * 100


# Calcular RMSE (Root Mean Squared Error)
rmse_155002_test <- sqrt(mean((test_155002$Venta - predicciones_155002_test)^2))



# Mostrar las métricas
cat("MAPE del modelo de regresión lineal para 155002 (test data): ", mape_155002_test, "\n")
## MAPE del modelo de regresión lineal para 155002 (test data):  6.503138
cat("RMSE del modelo de regresión lineal para 155002 (test data): ", rmse_155002_test, "\n")
## RMSE del modelo de regresión lineal para 155002 (test data):  141.8191
# Diagnóstico de residuos del modelo
par(mfrow = c(2, 2))
plot(modelo_regresion_155002_test)

# 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

# Filtrar solo los datos para el producto 3678055
datos_3678055 <- datos_filtrados %>%
  filter(ID_Inventario == 3678055) %>%
  select(Venta, Cant, Costo_Venta,
         Precio_Final_Unitario, Descuento_Porcentaje, Trx_Fecha) %>%
  na.omit()  # Eliminar filas con valores NA

# Crear una variable de tiempo continua basada en la fecha
datos_3678055 <- datos_3678055 %>%
  mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month")),   # Asegúrate de que la fecha esté en formato Date
         Tiempo = as.numeric(Fecha - min(Fecha)) / (30 * 24 * 60 * 60))  # Tiempo en meses (ajustado por días)

# Verificar las primeras filas para asegurarnos de que la variable de tiempo esté bien creada
head(datos_3678055)
## # A tibble: 6 × 8
##    Venta  Cant Costo_Venta Precio_Final_Unitario Descuento_Porcentaje Trx_Fecha 
##    <dbl> <dbl>       <dbl>                 <dbl>                <dbl> <date>    
## 1 36358      7      28807.                 5194                  65.9 2023-02-13
## 2  5670      1       4213.                 5670                  62.8 2023-02-22
## 3 10773      2       8232.                 5386.                 64.7 2023-02-08
## 4  5670      1       4116.                 5670                  62.8 2023-02-09
## 5  5386.     1       4156.                 5386.                 64.7 2023-02-16
## 6  5386.     1       4213.                 5386.                 64.7 2023-02-22
## # ℹ 2 more variables: Fecha <date>, Tiempo <dbl>
# Filtrar solo los datos para el producto 155001
test_3678055 <- test %>%
  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")),
    Tiempo = as.numeric(Fecha - min(Fecha)) / (30 * 24 * 60 * 60)
  )

# Verificar
head(test_3678055)
## # A tibble: 6 × 8
##   Venta  Cant Costo_Venta Precio_Final_Unitario Descuento_Porcentaje Trx_Fecha 
##   <dbl> <dbl>       <dbl>                 <dbl>                <dbl> <date>    
## 1  5936     1       3943.                  5936                 65.0 2024-10-03
## 2 11872     2       8374.                  5936                 65.0 2024-10-09
## 3 11872     2       8374.                  5936                 65.0 2024-10-15
## 4  5936     1       3854.                  5936                 65.0 2024-10-11
## 5  5936     1       3854.                  5936                 65.0 2024-10-23
## 6 20608     4      15415.                  5152                 69.6 2024-10-31
## # ℹ 2 more variables: Fecha <date>, 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 
## -3691.7  -164.8    -9.3   173.9  3792.6 
## 
## Coefficients:
##                         Estimate Std. Error t value Pr(>|t|)    
## (Intercept)            3.089e+03  1.934e+03   1.597     0.11    
## Cant                   1.007e+03  1.979e+02   5.090 4.06e-07 ***
## Costo_Venta            1.046e+00  5.040e-02  20.764  < 2e-16 ***
## Precio_Final_Unitario  8.544e-01  1.242e-01   6.881 8.85e-12 ***
## Descuento_Porcentaje  -1.169e+02  2.017e+01  -5.797 8.31e-09 ***
## Tiempo                 2.283e+06  2.130e+05  10.718  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 449 on 1436 degrees of freedom
## Multiple R-squared:  0.9977, Adjusted R-squared:  0.9977 
## F-statistic: 1.234e+05 on 5 and 1436 DF,  p-value: < 2.2e-16
# Ajustar el modelo de regresión lineal
modelo_regresion_3678055_test <- lm(Venta ~ Cant + Costo_Venta +
                              Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
                             data = test_3678055)

# Ver resumen del modelo
summary(modelo_regresion_3678055_test)
## 
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Precio_Final_Unitario + 
##     Descuento_Porcentaje + Tiempo, data = test_3678055)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1028.08  -177.32    23.09   138.07  2130.24 
## 
## Coefficients: (1 not defined because of singularities)
##                         Estimate Std. Error t value Pr(>|t|)    
## (Intercept)           -9.649e+03  5.265e+02 -18.327  < 2e-16 ***
## Cant                   4.509e+03  2.864e+02  15.744  < 2e-16 ***
## Costo_Venta            2.701e-01  6.394e-02   4.225 3.54e-05 ***
## Precio_Final_Unitario  1.699e+00  9.070e-02  18.729  < 2e-16 ***
## Descuento_Porcentaje          NA         NA      NA       NA    
## Tiempo                -6.787e+06  2.856e+06  -2.376   0.0184 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 323.2 on 213 degrees of freedom
## Multiple R-squared:  0.999,  Adjusted R-squared:  0.999 
## F-statistic: 5.217e+04 on 4 and 213 DF,  p-value: < 2.2e-16
#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 (train data): ", mape_3678055, "\n")
## MAPE del modelo de regresión lineal para 3678055 (train data):  2.899461
cat("RMSE del modelo de regresión lineal para 3678055 (train data): ", rmse_3678055, "\n")
## RMSE del modelo de regresión lineal para 3678055 (train data):  200797.3
# Diagnóstico de residuos del modelo
par(mfrow = c(2, 2))
plot(modelo_regresion_3678055)

# Ajuste del modelo de regresión lineal
modelo_regresion_3678055_test <- lm(Venta ~ Cant + Costo_Venta +
                              Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
                             data = test_3678055)

# Predicciones usando el modelo ajustado
predicciones_3678055_test <- predict(modelo_regresion_3678055_test, newdata = test_3678055)

# Calcular MAPE (Mean Absolute Percentage Error)
mape_3678055_test <- mean(abs((test_3678055$Venta - predicciones_3678055_test) / test_3678055$Venta)) * 100


# Calcular RMSE (Root Mean Squared Error)
rmse_3678055_test <- sqrt(mean((test_3678055$Venta - predicciones_3678055_test)^2))



# Mostrar las métricas
cat("MAPE del modelo de regresión lineal para 3678055 (test data): ", mape_3678055_test, "\n")
## MAPE del modelo de regresión lineal para 3678055 (test data):  2.159551
cat("RMSE del modelo de regresión lineal para 3678055 (test data): ", rmse_3678055_test, "\n")
## RMSE del modelo de regresión lineal para 3678055 (test data):  319.427
# Diagnóstico de residuos del modelo
par(mfrow = c(2, 2))
plot(modelo_regresion_3678055_test)

# 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 Tiempo -415043.4262 0.0354 Negativo
155001 Cant 181.2904 0.0000 Positivo
155001 Descuento_Porcentaje 27.8634 0.0451 Positivo
155001 Precio_Final_Unitario 2.8801 0.0000 Positivo
155001 Costo_Venta 0.6035 0.0000 Positivo
155002 Tiempo 521359.6070 0.0842 No Positivo
155002 Cant 304.4423 0.0000 Positivo
155002 Descuento_Porcentaje -31.6568 0.1688 No Negativo
155002 Precio_Final_Unitario 3.4319 0.0000 Positivo
155002 Costo_Venta 0.2602 0.0000 Positivo
3678055 Tiempo 2283221.4176 0.0000 Positivo
3678055 Cant 1007.0933 0.0000 Positivo
3678055 Descuento_Porcentaje -116.9320 0.0000 Negativo
3678055 Costo_Venta 1.0465 0.0000 Positivo
3678055 Precio_Final_Unitario 0.8544 0.0000 Positivo
3904152 Tiempo 2522894.4915 0.0000 Positivo
3904152 Descuento_Porcentaje -108.9532 0.0000 Negativo
3904152 Cant -74.4699 0.3585 No Negativo
3904152 Costo_Venta 1.3162 0.0000 Positivo
3904152 Precio_Final_Unitario 0.6564 0.0000 Positivo
3929788 Tiempo 15204.6827 0.7744 No Positivo
3929788 Precio_Final_Unitario 5.8190 0.0011 Positivo
3929788 Cant 3.3425 0.0000 Positivo
3929788 Descuento_Porcentaje -2.9717 0.1099 No Negativo
3929788 Costo_Venta 1.0702 0.0000 Positivo
# 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 Tiempo -415043.4262 0.0354 Negativo
155001 Cant 181.2904 0.0000 Positivo
155001 Descuento_Porcentaje 27.8634 0.0451 Positivo
155002 Tiempo 521359.6070 0.0842 No Positivo
155002 Cant 304.4423 0.0000 Positivo
155002 Descuento_Porcentaje -31.6568 0.1688 No Negativo
3678055 Tiempo 2283221.4176 0.0000 Positivo
3678055 Cant 1007.0933 0.0000 Positivo
3678055 Descuento_Porcentaje -116.9320 0.0000 Negativo
3904152 Tiempo 2522894.4915 0.0000 Positivo
3904152 Descuento_Porcentaje -108.9532 0.0000 Negativo
3904152 Cant -74.4699 0.3585 No Negativo
3929788 Tiempo 15204.6827 0.7744 No Positivo
3929788 Precio_Final_Unitario 5.8190 0.0011 Positivo
3929788 Cant 3.3425 0.0000 Positivo

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: 2132474
##                     % Var explained: 94.99
# 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 (train data) 155001\n")
## Modelo Random Forest para producto (train data) 155001
cat("MAPE del modelo Random Forest (train data) :", mape_rf, "\n")
## MAPE del modelo Random Forest (train data) : 0.701904
cat("RMSE del modelo Random Forest (train data):", rmse_rf, "\n\n")
## RMSE del modelo Random Forest (train data): 527761.9
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_155001)
print(importancia_vars)
##                         %IncMSE IncNodePurity
## Cant                  32.049700  142609614365
## Costo_Venta           32.806424  155679567124
## Precio_Final_Unitario  3.320283    5492277697
## Descuento_Porcentaje   3.444756    8703102472
## Trx_Fecha              4.332390    6840504795
## Tiempo                 2.631392    2908631295
# Graficar importancia de variables
varImpPlot(modelo_rf_155001, main = "Importancia de Variables - Producto 155001 (train data)")

# 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 (train data)",
    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 (train data)",
     xlab = "Error (Observado - Predicho)",
     col = "skyblue",
     breaks = 30)

# Estadísticas descriptivas de los errores
cat("Estadísticas descriptivas de los errores (train data):\n")
## Estadísticas descriptivas de los errores (train data):
cat("Media de errores:", mean(errores), "\n")
## Media de errores: 12.21121
cat("Desviación estándar de errores:", sd(errores), "\n")
## Desviación estándar de errores: 726.4169
cat("Mínimo:", min(errores), "\n")
## Mínimo: -3411.861
cat("Máximo:", max(errores), "\n")
## Máximo: 51561.52
cat("Mediana:", median(errores), "\n")
## Mediana: -0.2979223
# 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 (train data)",
    x = "Ventas Predichas",
    y = "Error (Observado - Predicho)"
  ) +
  theme_minimal()

# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo_test_155001 <- test_155001

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

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

# Calcular métricas
# MAPE
mape_rf_test_155001 <- mean(abs((datos_modelo_test_155001$Venta - predicciones_rf_test_155001) / pmax(datos_modelo_test_155001$Venta, 0.01))) * 100

# RMSE
rmse_rf_test_155001 <- mean((datos_modelo_test_155001$Venta - predicciones_rf_test_155001)^2)

# Mostrar las métricas
cat("Modelo Random Forest para producto (test data) 155001\n")
## Modelo Random Forest para producto (test data) 155001
cat("MAPE del modelo Random Forest (test data):", mape_rf_test_155001, "\n")
## MAPE del modelo Random Forest (test data): 6.752892
cat("RMSE del modelo Random Forest (test data):", rmse_rf_test_155001, "\n\n")
## RMSE del modelo Random Forest (test data): 1504360
# Mostrar importancia de variables
importancia_vars_test_155001 <- importance(modelo_rf_155001_test)
print(importancia_vars_test_155001)
##                         %IncMSE IncNodePurity
## Cant                  24.884496   25265243000
## Costo_Venta           23.487860   23491898884
## Precio_Final_Unitario  7.505503    3655275964
## Descuento_Porcentaje  10.537405    5173846466
## Trx_Fecha              5.822356    1702459467
## Fecha                  4.647490     654964906
## Tiempo                 5.252892     717457809
# Graficar importancia de variables
varImpPlot(modelo_rf_155001_test, main = "Importancia de Variables - Producto 155001 (test data)")

# Crear gráfico de valores observados vs predicciones
datos_grafico_test_155001 <- data.frame(
  Observado = datos_modelo_test_155001$Venta,
  Predicho = predicciones_rf_test_155001
)

ggplot(datos_grafico_test_155001, 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 (test data)",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

# NUEVOS ANÁLISIS AÑADIDOS

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

# Estadísticas descriptivas de los errores
cat("Estadísticas descriptivas de los errores (test data):\n")
## Estadísticas descriptivas de los errores (test data):
cat("Media de errores:", mean(errores), "\n")
## Media de errores: 12.21121
cat("Desviación estándar de errores:", sd(errores), "\n")
## Desviación estándar de errores: 726.4169
cat("Mínimo:", min(errores), "\n")
## Mínimo: -3411.861
cat("Máximo:", max(errores), "\n")
## Máximo: 51561.52
cat("Mediana:", median(errores), "\n")
## Mediana: -0.2979223
# Gráfico del error vs predicción
ggplot(data.frame(Predicho = predicciones_rf_test_155001, Error = errores_test_155001), 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 (test data)",
    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: 39829.87
##                     % Var explained: 99.6
# 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: 0.9817282
cat("RMSE del modelo Random Forest:", rmse_rf, "\n\n")
## RMSE del modelo Random Forest: 94.54023
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_3929788)
print(importancia_vars)
##                         %IncMSE IncNodePurity
## Cant                  34.565424   56405206014
## Costo_Venta           33.824097   54221433004
## Precio_Final_Unitario  3.120182    1650599061
## Descuento_Porcentaje   8.901095    4380202612
## Trx_Fecha              2.128393     785310884
## Tiempo                 4.468018     345594151
# 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()

# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo_test_3929788 <- test_3929788

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

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

# Calcular métricas
# MAPE
mape_rf_test_3929788 <- mean(abs((datos_modelo_test_3929788$Venta - predicciones_rf_test_3929788) / pmax(datos_modelo_test_3929788$Venta, 0.01))) * 100

# RMSE
rmse_rf_test_3929788 <- mean((datos_modelo_test_3929788$Venta - predicciones_rf_test_3929788)^2)

# Mostrar las métricas
cat("Modelo Random Forest para producto (test data) 3929788\n")
## Modelo Random Forest para producto (test data) 3929788
cat("MAPE del modelo Random Forest (test data):", mape_rf_test_3929788, "\n")
## MAPE del modelo Random Forest (test data): 8.976429
cat("RMSE del modelo Random Forest (test data):", rmse_rf_test_3929788, "\n\n")
## RMSE del modelo Random Forest (test data): 158673
# Mostrar importancia de variables
importancia_vars_test_3929788 <- importance(modelo_rf_3929788_test)
print(importancia_vars_test_3929788)
##                         %IncMSE IncNodePurity
## Cant                  30.794654    4614065401
## Costo_Venta           34.166523    5188019152
## Precio_Final_Unitario  5.479426     393910118
## Descuento_Porcentaje   5.175467     544306760
## Trx_Fecha              1.968545     155625591
## Fecha                  2.613152      43299897
## Tiempo                 1.924918      45550324
# Graficar importancia de variables
varImpPlot(modelo_rf_3929788_test, main = "Importancia de Variables - Producto 3929788 (test data)")

# Crear gráfico de valores observados vs predicciones
datos_grafico_test_3929788 <- data.frame(
  Observado = datos_modelo_test_3929788$Venta,
  Predicho = predicciones_rf_test_3929788
)

ggplot(datos_grafico_test_3929788, 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 (test data)",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

# NUEVOS ANÁLISIS AÑADIDOS

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

# Estadísticas descriptivas de los errores
cat("Estadísticas descriptivas de los errores (test data):\n")
## Estadísticas descriptivas de los errores (test data):
cat("Media de errores:", mean(errores), "\n")
## Media de errores: 1.599261
cat("Desviación estándar de errores:", sd(errores), "\n")
## Desviación estándar de errores: 94.53064
cat("Mínimo:", min(errores), "\n")
## Mínimo: -2370.319
cat("Máximo:", max(errores), "\n")
## Máximo: 4297.056
cat("Mediana:", median(errores), "\n")
## Mediana: -0.1006844
# Gráfico del error vs predicción
ggplot(data.frame(Predicho = predicciones_rf_test_3929788, Error = errores_test_3929788), 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 (test data)",
    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: 1578218
##                     % Var explained: 98.67
# 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: 0.5437703
cat("RMSE del modelo Random Forest:", rmse_rf, "\n\n")
## RMSE del modelo Random Forest: 629.1225
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_3904152)
print(importancia_vars)
##                         %IncMSE IncNodePurity
## Cant                  29.924235  124309165376
## Costo_Venta           30.983250  128110656598
## Precio_Final_Unitario  3.806059    4793475902
## Descuento_Porcentaje   4.101713    6809288283
## Trx_Fecha              5.740053    2814351997
## Tiempo                 6.736106    1284898509
# 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()

# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo_test_3904152 <- test_3904152

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

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

# Calcular métricas
# MAPE
mape_rf_test_3904152 <- mean(abs((datos_modelo_test_3904152$Venta - predicciones_rf_test_3904152) / pmax(datos_modelo_test_3904152$Venta, 0.01))) * 100

# RMSE
rmse_rf_test_3904152 <- mean((datos_modelo_test_3904152$Venta - predicciones_rf_test_3904152)^2)

# Mostrar las métricas
cat("Modelo Random Forest para producto (test data) 3904152\n")
## Modelo Random Forest para producto (test data) 3904152
cat("MAPE del modelo Random Forest (test data):", mape_rf_test_3904152, "\n")
## MAPE del modelo Random Forest (test data): 2.735045
cat("RMSE del modelo Random Forest (test data):", rmse_rf_test_3904152, "\n\n")
## RMSE del modelo Random Forest (test data): 1458227
# Mostrar importancia de variables
importancia_vars_test_3904152 <- importance(modelo_rf_3904152_test)
print(importancia_vars_test_3904152)
##                         %IncMSE IncNodePurity
## Cant                  26.293581    7714057450
## Costo_Venta           27.344374    7726767513
## Precio_Final_Unitario  5.102386    1204434919
## Descuento_Porcentaje   4.775149    1488830133
## Trx_Fecha              4.482390     544654133
## Fecha                  3.454133     105678549
## Tiempo                 3.154488      81959045
# Graficar importancia de variables
varImpPlot(modelo_rf_3904152_test, main = "Importancia de Variables - Producto 3904152 (test data)")

# Crear gráfico de valores observados vs predicciones
datos_grafico_test_3904152 <- data.frame(
  Observado = datos_modelo_test_3904152$Venta,
  Predicho = predicciones_rf_test_3904152
)

ggplot(datos_grafico_test_3904152, 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 (test data)",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

# NUEVOS ANÁLISIS AÑADIDOS

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

# Estadísticas descriptivas de los errores
cat("Estadísticas descriptivas de los errores (test data):\n")
## Estadísticas descriptivas de los errores (test data):
cat("Media de errores:", mean(errores), "\n")
## Media de errores: 16.49376
cat("Desviación estándar de errores:", sd(errores), "\n")
## Desviación estándar de errores: 629.0445
cat("Mínimo:", min(errores), "\n")
## Mínimo: -14109.35
cat("Máximo:", max(errores), "\n")
## Máximo: 13054.65
cat("Mediana:", median(errores), "\n")
## Mediana: -2.867313
# Gráfico del error vs predicción
ggplot(data.frame(Predicho = predicciones_rf_test_3904152, Error = errores_test_3904152), 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 (test data)",
    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

))
# 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: 716053.3
##                     % Var explained: 97.06
# Obtener predicciones
predicciones_rf_155002 <- predict(modelo_rf_155002, newdata = datos_modelo)

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

# MSE
rmse_rf <- sqrt(mean((datos_modelo$Venta - predicciones_rf_155002)^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: 0.9725775
cat("RMSE del modelo Random Forest:", rmse_rf, "\n\n")
## RMSE del modelo Random Forest: 361.6748
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_155002)
print(importancia_vars)
##                         %IncMSE IncNodePurity
## Cant                  35.801353   60291286951
## Costo_Venta           31.171652   55323271976
## Precio_Final_Unitario  6.171213    2709627912
## Descuento_Porcentaje   6.037915    4341557629
## Trx_Fecha              5.018121    1517973856
## Tiempo                 2.846029     620446866
# Graficar importancia de variables
varImpPlot(modelo_rf_155002, main = "Importancia de Variables - Producto 155002")

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

ggplot(datos_grafico_155002, 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_155002 <- datos_grafico_155002$Observado - datos_grafico_155002$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_155002, Error = errores_155002), 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()

# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo_test_155002 <- test_155002

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

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

# Calcular métricas
# MAPE
mape_rf_test_155002 <- mean(abs((datos_modelo_test_155002$Venta - predicciones_rf_test_155002) / pmax(datos_modelo_test_155002$Venta, 0.01))) * 100

# RMSE
rmse_rf_test_155002 <- mean((datos_modelo_test_155002$Venta - predicciones_rf_test_155002)^2)

# Mostrar las métricas
cat("Modelo Random Forest para producto (test data) 155002\n")
## Modelo Random Forest para producto (test data) 155002
cat("MAPE del modelo Random Forest (test data):", mape_rf_test_155002, "\n")
## MAPE del modelo Random Forest (test data): 6.83582
cat("RMSE del modelo Random Forest (test data):", rmse_rf_test_155002, "\n\n")
## RMSE del modelo Random Forest (test data): 1150814
# Mostrar importancia de variables
importancia_vars_test_155002 <- importance(modelo_rf_155002_test)
print(importancia_vars_test_155002)
##                         %IncMSE IncNodePurity
## Cant                  19.711191   13818251523
## Costo_Venta           20.955367   15476170247
## Precio_Final_Unitario 10.526501    2850184809
## Descuento_Porcentaje  13.230897    4090345702
## Trx_Fecha              6.048552    1151039268
## Fecha                  3.537532     191735335
## Tiempo                 3.249559     249388442
# Graficar importancia de variables
varImpPlot(modelo_rf_155002_test, main = "Importancia de Variables - Producto 155002 (test data)")

# Crear gráfico de valores observados vs predicciones
datos_grafico_test_155002 <- data.frame(
  Observado = datos_modelo_test_155002$Venta,
  Predicho = predicciones_rf_test_155002
)

ggplot(datos_grafico_test_155002, 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 (test data)",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

# NUEVOS ANÁLISIS AÑADIDOS

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

# Estadísticas descriptivas de los errores
cat("Estadísticas descriptivas de los errores (test data):\n")
## Estadísticas descriptivas de los errores (test data):
cat("Media de errores:", mean(errores), "\n")
## Media de errores: 16.49376
cat("Desviación estándar de errores:", sd(errores), "\n")
## Desviación estándar de errores: 629.0445
cat("Mínimo:", min(errores), "\n")
## Mínimo: -14109.35
cat("Máximo:", max(errores), "\n")
## Máximo: 13054.65
cat("Mediana:", median(errores), "\n")
## Mediana: -2.867313
# Gráfico del error vs predicción
ggplot(data.frame(Predicho = predicciones_rf_test_155002, Error = errores_test_155002), 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 (test data)",
    x = "Ventas Predichas",
    y = "Error (Observado - Predicho)"
  ) +
  theme_minimal()

# Guardar métricas de Random Forest para producto 155002
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 = "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: 1475556
##                     % Var explained: 98.29
# Obtener predicciones
predicciones_rf_3678055 <- predict(modelo_rf_3678055, newdata = datos_modelo)

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

# RMSE
rmse_rf <- sqrt(mean((datos_modelo$Venta - predicciones_rf_3678055)^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: 0.4065828
cat("RMSE del modelo Random Forest:", rmse_rf, "\n\n")
## RMSE del modelo Random Forest: 577.2374
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_3678055)
print(importancia_vars)
##                         %IncMSE IncNodePurity
## Cant                  32.842295   58196243223
## Costo_Venta           32.920184   59308904779
## Precio_Final_Unitario  3.539369    1414924238
## Descuento_Porcentaje   3.885702    2121109333
## Trx_Fecha              4.718997    2136228519
## Tiempo                 4.865764    1056056197
# Graficar importancia de variables
varImpPlot(modelo_rf_3678055, main = "Importancia de Variables - Producto 3678055")

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

ggplot(datos_grafico_3678055, 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_3678055 <- datos_grafico_3678055$Observado - datos_grafico_3678055$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_3678055, Error = errores_3678055), 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
))
# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo_test_3678055 <- test_3678055

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

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

# Calcular métricas
# MAPE
mape_rf_test_3678055 <- mean(abs((datos_modelo_test_3678055$Venta - predicciones_rf_test_3678055) / pmax(datos_modelo_test_3678055$Venta, 0.01))) * 100

# RMSE
rmse_rf_test_3678055 <- mean((datos_modelo_test_3678055$Venta - predicciones_rf_test_3678055)^2)

# Mostrar las métricas
cat("Modelo Random Forest para producto (test data) 3678055\n")
## Modelo Random Forest para producto (test data) 3678055
cat("MAPE del modelo Random Forest (test data):", mape_rf_test_3678055, "\n")
## MAPE del modelo Random Forest (test data): 2.418527
cat("RMSE del modelo Random Forest (test data):", rmse_rf_test_3678055, "\n\n")
## RMSE del modelo Random Forest (test data): 834319.4
# Mostrar importancia de variables
importancia_vars_test_3678055 <- importance(modelo_rf_3678055_test)
print(importancia_vars_test_3678055)
##                         %IncMSE IncNodePurity
## Cant                  25.475995    9197163081
## Costo_Venta           26.561093    9644126047
## Precio_Final_Unitario  5.935788     627250059
## Descuento_Porcentaje   5.339095     582778911
## Trx_Fecha              6.973089     703148145
## Fecha                  2.953230     111547021
## Tiempo                 3.757128     110131277
# Graficar importancia de variables
varImpPlot(modelo_rf_3678055_test, main = "Importancia de Variables - Producto 3678055 (test data)")

# Crear gráfico de valores observados vs predicciones
datos_grafico_test_3678055 <- data.frame(
  Observado = datos_modelo_test_3678055$Venta,
  Predicho = predicciones_rf_test_3678055
)

ggplot(datos_grafico_test_3678055, 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 (test data)",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

# NUEVOS ANÁLISIS AÑADIDOS

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

# Estadísticas descriptivas de los errores
cat("Estadísticas descriptivas de los errores (test data):\n")
## Estadísticas descriptivas de los errores (test data):
cat("Media de errores:", mean(errores), "\n")
## Media de errores: 16.49376
cat("Desviación estándar de errores:", sd(errores), "\n")
## Desviación estándar de errores: 629.0445
cat("Mínimo:", min(errores), "\n")
## Mínimo: -14109.35
cat("Máximo:", max(errores), "\n")
## Máximo: 13054.65
cat("Mediana:", median(errores), "\n")
## Mediana: -2.867313
# Gráfico del error vs predicción
ggplot(data.frame(Predicho = predicciones_rf_test_3678055, Error = errores_test_3678055), 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 (test data)",
    x = "Ventas Predichas",
    y = "Error (Observado - Predicho)"
  ) +
  theme_minimal()

7 XGBOOST

7.1 PRODUCTO 155001

# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_155001 %>%
  select(-Trx_Fecha, -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
## 2  0.10         9       0.8              0.6                1   0.1     100
## 6  0.05         7       0.8              0.8                1   0.3     100
## 20 0.05         7       0.8              0.6                1   0.0     100
## 4  0.05         9       1.0              0.8                1   0.1     100
## 10 0.05         9       0.8              0.6                3   0.3     100
##        rmse
## 2  1032.425
## 6  1144.109
## 20 1200.149
## 4  1204.508
## 10 1238.920
# 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.1
## 
## $max_depth
## [1] 9
## 
## $subsample
## [1] 0.8
## 
## $colsample_bytree
## [1] 0.6
## 
## $min_child_weight
## [1] 1
## 
## $gamma
## [1] 0.1
cat("Número óptimo de rondas:", mejor_nrounds, "\n")
## Número óptimo de rondas: 100
cat("RMSE en validación cruzada:", resultados_grid$rmse[1], "\n\n")
## RMSE en validación cruzada: 1032.425
# 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_155001 <- predict(modelo_xgb_155001, dtest)

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

# RMSE
rmse_test_155001 <- sqrt(mean((y_test - predicciones_test_155001)^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_155001, "\n")
## MAPE del modelo XGBoost: 3.305494
cat("RMSE del modelo XGBoost:", rmse_test_155001, "\n\n")
## RMSE del modelo XGBoost: 320.9938
# Ahora hacer predicciones en el conjunto completo para comparabilidad con otros modelos
X_completo <- as.matrix(datos_modelo[, colnames(datos_modelo) != "Venta"])
predicciones_completas_155001 <- predict(modelo_xgb_155001, X_completo)
# Calcular métricas en el conjunto completo
# MAPE
mape_completo_155001 <- mean(abs((datos_modelo$Venta - predicciones_completas_155001) / pmax(datos_modelo$Venta, 0.01))) * 100

# MSE
rmse_completo_155001 <- sqrt(mean((datos_modelo$Venta - predicciones_completas_155001)^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_155001, "\n")
## MAPE del modelo XGBoost: 2.272554
cat("RMSE del modelo XGBoost:", rmse_completo_155001, "\n\n")
## RMSE del modelo XGBoost: 159.1664
# 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:                  Cant 0.643088769 0.25864707 0.1777807
## 2:           Costo_Venta 0.341281261 0.32864829 0.2788744
## 3: Precio_Final_Unitario 0.006372213 0.15682701 0.2129901
## 4:  Descuento_Porcentaje 0.005465607 0.15812397 0.1789811
## 5:                Tiempo 0.003792151 0.09775366 0.1513737
# 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_155001
)

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_155001, 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_155001,
  RMSE = rmse_completo_155001

))

7.2 PRODUCTO 3929788

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

# Dividir los datos en conjuntos de entrenamiento (80%) y prueba (20%)
set.seed(123)
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)

# 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 3929788\n")
## Grid Search para XGBoost - Producto 3929788
cat("Número total de combinaciones:", nrow(param_grid), "\n\n")
## Número total de combinaciones: 432
# Limitar a 12 combinaciones aleatorias
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.
# 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
# Ordenar por menor RMSE
resultados <- resultados[order(resultados$rmse_cv), ]

# Mostrar top resultados
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
## 2  0.10         9                3       0.9              0.9   0.3     194
## 5  0.10         6                5       0.9              0.9   0.1     199
## 10 0.10         9                1       0.9              0.7   0.3     200
## 9  0.05         6                3       0.7              0.9   0.3     200
## 4  0.30         9                5       0.7              0.9   0.1     200
## 11 0.05         6                3       0.7              0.9   0.0     200
## 12 0.10         3                3       0.9              0.7   0.3     200
## 8  0.10         3                3       0.7              0.7   0.1     200
## 7  0.05         3                5       0.9              0.7   0.1     200
## 3  0.05         3                1       0.9              0.7   0.0     200
## 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
## 2  122.6098
## 5  124.9375
## 10 127.9715
## 9  128.0007
## 4  132.5968
## 11 135.6303
## 12 142.0943
## 8  147.5475
## 7  164.1518
## 3  168.5773
## 6  555.0874
## 1  576.2904
# Graficar resultados
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))

# Seleccionar 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] 3
## 
## $subsample
## [1] 0.9
## 
## $colsample_bytree
## [1] 0.9
## 
## $gamma
## [1] 0.3
cat("Número óptimo de rondas:", mejor_nrounds, "\n\n")
## Número óptimo de rondas: 194
# Entrenar modelo final
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
)

# Evaluar en datos completos
X_completo <- as.matrix(datos_modelo[, colnames(datos_modelo) != "Venta"])
dcompleto <- xgb.DMatrix(data = X_completo)
predicciones_completas <- predict(modelo_final, newdata = dcompleto)

# Métricas finales
mape_completo <- mean(abs((datos_modelo$Venta - predicciones_completas) / pmax(datos_modelo$Venta, 0.01))) * 100
rmse_completo <- sqrt(mean((datos_modelo$Venta - predicciones_completas)^2))

cat("Evaluación final en todo el conjunto:\n")
## Evaluación final en todo el conjunto:
cat("MAPE:", mape_completo, "\n")
## MAPE: 0.8136359
cat("RMSE:", rmse_completo, "\n\n")
## RMSE: 46.33887
# Graficar resultados
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 3929788 (XGBoost)",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

# Análisis de errores
errores <- datos_modelo$Venta - predicciones_completas

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

# 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 3929788 (XGBoost)",
    x = "Ventas Predichas",
    y = "Error (Observado - Predicho)"
  ) +
  theme_minimal()

# Guardar 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 = "3929788",
  Modelo = "XGBoost",
  MAPE = mape_completo,
  RMSE = rmse_completo
))

7.3 PRODUCTO 3904152

# Paso 1: Preparar datos
datos_modelo_3904152 <- datos_3904152 %>%
  select(-Trx_Fecha, -Fecha)

# Paso 2: Dividir en entrenamiento y prueba (80/20)
set.seed(123)
train_index_3904152 <- createDataPartition(datos_modelo_3904152$Venta, p = 0.8, list = FALSE)
train_data_3904152 <- datos_modelo_3904152[train_index_3904152, ]
test_data_3904152 <- datos_modelo_3904152[-train_index_3904152, ]

# Paso 3: Preparar matrices
train_x_3904152 <- as.matrix(train_data_3904152[, colnames(train_data_3904152) != "Venta"])
train_y_3904152 <- train_data_3904152$Venta
test_x_3904152 <- as.matrix(test_data_3904152[, colnames(test_data_3904152) != "Venta"])
test_y_3904152 <- test_data_3904152$Venta

dtrain_3904152 <- xgb.DMatrix(data = train_x_3904152, label = train_y_3904152)
dtest_3904152 <- xgb.DMatrix(data = test_x_3904152, label = test_y_3904152)

# Paso 4: Hiperparámetros
param_grid_3904152 <- 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 3904152\n")
## Grid Search para XGBoost - Producto 3904152
cat("Número total de combinaciones:", nrow(param_grid_3904152), "\n\n")
## Número total de combinaciones: 432
# Paso 5: Limitar combinaciones
set.seed(456)
if (nrow(param_grid_3904152) > 12) {
  selected_indices_3904152 <- sample(1:nrow(param_grid_3904152), 12)
  param_grid_3904152 <- param_grid_3904152[selected_indices_3904152, ]
}

# Paso 6: Grid Search
resultados_3904152 <- data.frame()

cat("Iniciando Grid Search...\n")
## Iniciando Grid Search...
for (i in 1:nrow(param_grid_3904152)) {
  params <- list(
    objective = "reg:squarederror",
    eval_metric = "rmse",
    eta = param_grid_3904152$eta[i],
    max_depth = param_grid_3904152$max_depth[i],
    min_child_weight = param_grid_3904152$min_child_weight[i],
    subsample = param_grid_3904152$subsample[i],
    colsample_bytree = param_grid_3904152$colsample_bytree[i],
    gamma = param_grid_3904152$gamma[i]
  )
  
  cv_model <- xgb.cv(
    params = params,
    data = dtrain_3904152,
    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)
  
  resultados_3904152 <- rbind(resultados_3904152, 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_3904152 <- resultados_3904152[order(resultados_3904152$rmse_cv), ]

cat("Resultados del Grid Search ordenados por RMSE:\n")
## Resultados del Grid Search ordenados por RMSE:
print(resultados_3904152)
##     eta max_depth min_child_weight subsample colsample_bytree gamma nrounds
## 3  0.05         3                1       0.9              0.7   0.0     200
## 10 0.10         9                1       0.9              0.7   0.3     198
## 8  0.10         3                3       0.7              0.7   0.1      89
## 9  0.05         6                3       0.7              0.9   0.3      87
## 12 0.10         3                3       0.9              0.7   0.3     200
## 11 0.05         6                3       0.7              0.9   0.0     105
## 2  0.10         9                3       0.9              0.9   0.3      39
## 5  0.10         6                5       0.9              0.9   0.1     116
## 4  0.30         9                5       0.7              0.9   0.1      42
## 7  0.05         3                5       0.9              0.7   0.1     200
## 1  0.01         9                3       0.7              0.9   0.1     200
## 6  0.01         6                5       0.9              0.9   0.1     200
##      rmse_cv
## 3   586.1932
## 10  764.3933
## 8  1028.1565
## 9  1028.2188
## 12 1049.4183
## 11 1131.8182
## 2  1190.5265
## 5  1190.6046
## 4  2095.0464
## 7  2440.8901
## 1  2578.7707
## 6  3218.2390
# Visualizar resultados
ggplot(resultados_3904152, aes(x = reorder(paste("Comb", 1:nrow(resultados_3904152)), 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 7: Entrenamiento final
mejores_params_3904152 <- list(
  objective = "reg:squarederror",
  eval_metric = "rmse",
  eta = resultados_3904152$eta[1],
  max_depth = resultados_3904152$max_depth[1],
  min_child_weight = resultados_3904152$min_child_weight[1],
  subsample = resultados_3904152$subsample[1],
  colsample_bytree = resultados_3904152$colsample_bytree[1],
  gamma = resultados_3904152$gamma[1]
)

mejor_nrounds_3904152 <- resultados_3904152$nrounds[1]

modelo_xgb_3904152 <- xgb.train(
  params = mejores_params_3904152,
  data = dtrain_3904152,
  nrounds = mejor_nrounds_3904152,
  watchlist = list(train = dtrain_3904152, test = dtest_3904152),
  verbose = 0
)

# Paso 8: Evaluación en test
predicciones_test_3904152 <- predict(modelo_xgb_3904152, dtest_3904152)

mape_test_3904152 <- mean(abs((test_y_3904152 - predicciones_test_3904152) / pmax(test_y_3904152, 0.01))) * 100
rmse_test_3904152 <- sqrt(mean((test_y_3904152 - predicciones_test_3904152)^2))

cat("\nMétricas en conjunto de prueba:\n")
## 
## Métricas en conjunto de prueba:
cat("MAPE:", mape_test_3904152, "\n")
## MAPE: 1.53285
cat("RMSE:", rmse_test_3904152, "\n\n")
## RMSE: 778.3901
# Paso 9: Evaluación en conjunto completo
x_completo_3904152 <- as.matrix(datos_modelo_3904152[, colnames(datos_modelo_3904152) != "Venta"])
predicciones_completo_3904152 <- predict(modelo_xgb_3904152, x_completo_3904152)

mape_completo_3904152 <- mean(abs((datos_modelo_3904152$Venta - predicciones_completo_3904152) /
                                  pmax(datos_modelo_3904152$Venta, 0.01))) * 100
rmse_completo_3904152 <- sqrt(mean((datos_modelo_3904152$Venta - predicciones_completo_3904152)^2))

cat("Métricas en conjunto completo:\n")
## Métricas en conjunto completo:
cat("MAPE:", mape_completo_3904152, "\n")
## MAPE: 1.363134
cat("RMSE:", rmse_completo_3904152, "\n\n")
## RMSE: 388.0705
# Paso 10: Importancia de variables
importancia_3904152 <- xgb.importance(
  feature_names = colnames(datos_modelo_3904152)[colnames(datos_modelo_3904152) != "Venta"],
  model = modelo_xgb_3904152
)

cat("Importancia de variables:\n")
## Importancia de variables:
print(importancia_3904152)
##                  Feature         Gain      Cover  Frequency
##                   <char>        <num>      <num>      <num>
## 1:           Costo_Venta 0.5462442713 0.35773001 0.34397528
## 2:                  Cant 0.4468336656 0.38482232 0.35942327
## 3: Precio_Final_Unitario 0.0045837219 0.12539919 0.15653965
## 4:  Descuento_Porcentaje 0.0019549300 0.07185305 0.07415036
## 5:                Tiempo 0.0003834112 0.06019543 0.06591143
xgb.plot.importance(importance_matrix = importancia_3904152, 
                    main = "Importancia de Variables - Producto 3904152 (XGBoost)")

# Paso 11: Visualizaciones
datos_grafico_3904152 <- data.frame(
  Observado = datos_modelo_3904152$Venta,
  Predicho = predicciones_completo_3904152
)

# Observado vs Predicho
ggplot(datos_grafico_3904152, 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()

# Distribución de errores
errores_3904152 <- datos_modelo_3904152$Venta - predicciones_completo_3904152

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

# Error vs Predicción
ggplot(data.frame(Predicho = predicciones_completo_3904152, Error = errores_3904152), 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()

# Paso 12: Guardar 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 = "3904152",
  Modelo = "XGBoost",
  MAPE = mape_completo_3904152,
  RMSE = rmse_completo_3904152
))

7.4 PRODUCTO 155002

library(dplyr)
library(xgboost)
library(caret)
library(ggplot2)

# Paso 1: Preparar datos
datos_modelo_155002 <- datos_155002 %>%
  select(-Trx_Fecha, -Fecha)

# Paso 2: División en entrenamiento y prueba
set.seed(123)
train_index_155002 <- createDataPartition(datos_modelo_155002$Venta, p = 0.8, list = FALSE)
train_data_155002 <- datos_modelo_155002[train_index_155002, ]
test_data_155002 <- datos_modelo_155002[-train_index_155002, ]

# Paso 3: Matrices para XGBoost
train_x_155002 <- as.matrix(train_data_155002[, colnames(train_data_155002) != "Venta"])
train_y_155002 <- train_data_155002$Venta
test_x_155002 <- as.matrix(test_data_155002[, colnames(test_data_155002) != "Venta"])
test_y_155002 <- test_data_155002$Venta

dtrain_155002 <- xgb.DMatrix(data = train_x_155002, label = train_y_155002)
dtest_155002 <- xgb.DMatrix(data = test_x_155002, label = test_y_155002)

# Paso 4: Definir grid
param_grid_155002 <- 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 155002\n")
## Grid Search para XGBoost - Producto 155002
cat("Número total de combinaciones de hiperparámetros:", nrow(param_grid_155002), "\n\n")
## Número total de combinaciones de hiperparámetros: 432
# Limitar combinaciones
set.seed(456)
if (nrow(param_grid_155002) > 12) {
  selected_indices <- sample(1:nrow(param_grid_155002), 12)
  param_grid_155002 <- param_grid_155002[selected_indices, ]
  cat("Seleccionando 12 combinaciones aleatorias para evaluación.\n\n")
}
## Seleccionando 12 combinaciones aleatorias para evaluación.
# Paso 5: Grid Search
resultados_155002 <- data.frame()

cat("Iniciando Grid Search...\n")
## Iniciando Grid Search...
for (i in 1:nrow(param_grid_155002)) {
  params <- list(
    objective = "reg:squarederror",
    eval_metric = "rmse",
    eta = param_grid_155002$eta[i],
    max_depth = param_grid_155002$max_depth[i],
    min_child_weight = param_grid_155002$min_child_weight[i],
    subsample = param_grid_155002$subsample[i],
    colsample_bytree = param_grid_155002$colsample_bytree[i],
    gamma = param_grid_155002$gamma[i]
  )
  
  cat("Evaluando combinación", i, "de", nrow(param_grid_155002), ":\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")
  
  cv_model <- xgb.cv(
    params = params,
    data = dtrain_155002,
    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)
  
  cat("  Mejor iteración:", best_iteration, "\n")
  cat("  RMSE en validación cruzada:", best_rmse, "\n\n")
  
  resultados_155002 <- rbind(resultados_155002, 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
  ))
}
## 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: 1260.823 
## 
## 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: 65 
##   RMSE en validación cruzada: 717.1348 
## 
## 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: 200 
##   RMSE en validación cruzada: 704.4149 
## 
## 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: 55 
##   RMSE en validación cruzada: 705.3839 
## 
## 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: 135 
##   RMSE en validación cruzada: 710.5157 
## 
## 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: 1235.378 
## 
## 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: 200 
##   RMSE en validación cruzada: 747.3757 
## 
## 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: 121 
##   RMSE en validación cruzada: 690.5727 
## 
## 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: 160 
##   RMSE en validación cruzada: 699.2501 
## 
## 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: 200 
##   RMSE en validación cruzada: 689.4535 
## 
## 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: 127 
##   RMSE en validación cruzada: 652.4376 
## 
## 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: 200 
##   RMSE en validación cruzada: 675.3904
# Paso 6: Ordenar y mostrar resultados
resultados_155002 <- resultados_155002[order(resultados_155002$rmse_cv), ]
cat("Resultados del Grid Search ordenados por RMSE:\n")
## Resultados del Grid Search ordenados por RMSE:
print(resultados_155002)
##     eta max_depth min_child_weight subsample colsample_bytree gamma nrounds
## 11 0.05         6                3       0.7              0.9   0.0     127
## 12 0.10         3                3       0.9              0.7   0.3     200
## 10 0.10         9                1       0.9              0.7   0.3     200
## 8  0.10         3                3       0.7              0.7   0.1     121
## 9  0.05         6                3       0.7              0.9   0.3     160
## 3  0.05         3                1       0.9              0.7   0.0     200
## 4  0.30         9                5       0.7              0.9   0.1      55
## 5  0.10         6                5       0.9              0.9   0.1     135
## 2  0.10         9                3       0.9              0.9   0.3      65
## 7  0.05         3                5       0.9              0.7   0.1     200
## 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
## 11  652.4376
## 12  675.3904
## 10  689.4535
## 8   690.5727
## 9   699.2501
## 3   704.4149
## 4   705.3839
## 5   710.5157
## 2   717.1348
## 7   747.3757
## 6  1235.3782
## 1  1260.8228
# Visualización
ggplot(resultados_155002, aes(x = reorder(paste("Comb", 1:nrow(resultados_155002)), 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 7: Entrenamiento final
mejores_params_155002 <- list(
  objective = "reg:squarederror",
  eval_metric = "rmse",
  eta = resultados_155002$eta[1],
  max_depth = resultados_155002$max_depth[1],
  min_child_weight = resultados_155002$min_child_weight[1],
  subsample = resultados_155002$subsample[1],
  colsample_bytree = resultados_155002$colsample_bytree[1],
  gamma = resultados_155002$gamma[1]
)

mejor_nrounds_155002 <- resultados_155002$nrounds[1]

cat("\nMejores hiperparámetros encontrados:\n")
## 
## Mejores hiperparámetros encontrados:
print(mejores_params_155002)
## $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
cat("Número óptimo de rondas:", mejor_nrounds_155002, "\n\n")
## Número óptimo de rondas: 127
modelo_xgb_155002 <- xgb.train(
  params = mejores_params_155002,
  data = dtrain_155002,
  nrounds = mejor_nrounds_155002,
  watchlist = list(train = dtrain_155002, test = dtest_155002),
  verbose = 0
)

# Paso 8: Evaluación en conjunto de prueba
predicciones_test_155002 <- predict(modelo_xgb_155002, dtest_155002)
mape_test_155002 <- mean(abs((test_y_155002 - predicciones_test_155002) / pmax(test_y_155002, 0.01))) * 100
rmse_test_155002 <- sqrt(mean((test_y_155002 - predicciones_test_155002)^2))

cat("\nMétricas en conjunto de prueba:\n")
## 
## Métricas en conjunto de prueba:
cat("MAPE del modelo XGBoost:", mape_test_155002, "\n")
## MAPE del modelo XGBoost: 2.940774
cat("RMSE del modelo XGBoost:", rmse_test_155002, "\n\n")
## RMSE del modelo XGBoost: 417.2237
# Paso 9: Predicciones en el conjunto completo
x_completo_155002 <- as.matrix(datos_modelo_155002[, colnames(datos_modelo_155002) != "Venta"])
predicciones_completo_155002 <- predict(modelo_xgb_155002, x_completo_155002)
mape_completo_155002 <- mean(abs((datos_modelo_155002$Venta - predicciones_completo_155002) / 
                                 pmax(datos_modelo_155002$Venta, 0.01))) * 100
rmse_completo_155002 <- sqrt(mean((datos_modelo_155002$Venta - predicciones_completo_155002)^2))

cat("Métricas en conjunto completo:\n")
## Métricas en conjunto completo:
cat("MAPE del modelo XGBoost:", mape_completo_155002, "\n")
## MAPE del modelo XGBoost: 2.671758
cat("RMSE del modelo XGBoost:", rmse_completo_155002, "\n\n")
## RMSE del modelo XGBoost: 406.7509
# Paso 10: Importancia de variables
importancia_155002 <- xgb.importance(
  feature_names = colnames(datos_modelo_155002)[colnames(datos_modelo_155002) != "Venta"],
  model = modelo_xgb_155002
)
cat("Importancia de variables:\n")
## Importancia de variables:
print(importancia_155002)
##                  Feature        Gain      Cover  Frequency
##                   <char>       <num>      <num>      <num>
## 1:                  Cant 0.609804436 0.45159665 0.28820656
## 2:           Costo_Venta 0.364060010 0.24522640 0.22889044
## 3: Precio_Final_Unitario 0.019895535 0.23101579 0.32728542
## 4:  Descuento_Porcentaje 0.004647796 0.05118654 0.09595255
## 5:                Tiempo 0.001592222 0.02097463 0.05966504
xgb.plot.importance(importance_matrix = importancia_155002, 
                    main = "Importancia de Variables - Producto 155002 (XGBoost)")

# Paso 11: Visualizaciones
datos_grafico_155002 <- data.frame(
  Observado = datos_modelo_155002$Venta,
  Predicho = predicciones_completo_155002
)

ggplot(datos_grafico_155002, 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()

errores_155002 <- datos_modelo_155002$Venta - predicciones_completo_155002

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

ggplot(data.frame(Predicho = predicciones_completo_155002, Error = errores_155002), 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()

# Paso 12: Guardar 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 = "155002",
  Modelo = "XGBoost",
  MAPE = mape_completo_155002,
  RMSE = rmse_completo_155002
))

7.5 PRODUCTO 3678055

# Paso 1: Preparar datos
datos_modelo_3678055 <- datos_3678055 %>%
  select(-Trx_Fecha, -Fecha)

# Paso 2: División en entrenamiento y prueba
set.seed(123)
train_index_3678055 <- createDataPartition(datos_modelo_3678055$Venta, p = 0.8, list = FALSE)
train_data_3678055 <- datos_modelo_3678055[train_index_3678055, ]
test_data_3678055 <- datos_modelo_3678055[-train_index_3678055, ]

# Paso 3: Matrices para XGBoost
train_x_3678055 <- as.matrix(train_data_3678055[, colnames(train_data_3678055) != "Venta"])
train_y_3678055 <- train_data_3678055$Venta
test_x_3678055 <- as.matrix(test_data_3678055[, colnames(test_data_3678055) != "Venta"])
test_y_3678055 <- test_data_3678055$Venta

dtrain_3678055 <- xgb.DMatrix(data = train_x_3678055, label = train_y_3678055)
dtest_3678055 <- xgb.DMatrix(data = test_x_3678055, label = test_y_3678055)

# Paso 4: Definir grid
param_grid_3678055 <- 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_3678055), "\n\n")
## Número total de combinaciones de hiperparámetros: 432
# Limitar combinaciones
set.seed(456)
if (nrow(param_grid_3678055) > 12) {
  selected_indices <- sample(1:nrow(param_grid_3678055), 12)
  param_grid_3678055 <- param_grid_3678055[selected_indices, ]
  cat("Seleccionando 12 combinaciones aleatorias para evaluación.\n\n")
}
## Seleccionando 12 combinaciones aleatorias para evaluación.
# Paso 5: Grid Search
resultados_3678055 <- data.frame()

cat("Iniciando Grid Search...\n")
## Iniciando Grid Search...
for (i in 1:nrow(param_grid_3678055)) {
  params <- list(
    objective = "reg:squarederror",
    eval_metric = "rmse",
    eta = param_grid_3678055$eta[i],
    max_depth = param_grid_3678055$max_depth[i],
    min_child_weight = param_grid_3678055$min_child_weight[i],
    subsample = param_grid_3678055$subsample[i],
    colsample_bytree = param_grid_3678055$colsample_bytree[i],
    gamma = param_grid_3678055$gamma[i]
  )
  
  cat("Evaluando combinación", i, "de", nrow(param_grid_3678055), ":\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")
  
  cv_model <- xgb.cv(
    params = params,
    data = dtrain_3678055,
    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)
  
  cat("  Mejor iteración:", best_iteration, "\n")
  cat("  RMSE en validación cruzada:", best_rmse, "\n\n")
  
  resultados_3678055 <- rbind(resultados_3678055, 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
  ))
}
## 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: 2390.195 
## 
## 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: 91 
##   RMSE en validación cruzada: 1055.863 
## 
## 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: 200 
##   RMSE en validación cruzada: 832.0375 
## 
## 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: 24 
##   RMSE en validación cruzada: 1206.289 
## 
## 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: 98 
##   RMSE en validación cruzada: 1010.817 
## 
## 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: 2454.596 
## 
## 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: 166 
##   RMSE en validación cruzada: 1201.99 
## 
## 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: 140 
##   RMSE en validación cruzada: 1041.128 
## 
## 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: 171 
##   RMSE en validación cruzada: 943.3198 
## 
## 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: 200 
##   RMSE en validación cruzada: 1031.93 
## 
## 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: 157 
##   RMSE en validación cruzada: 837.9873 
## 
## 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: 55 
##   RMSE en validación cruzada: 929.559
# Paso 6: Selección y entrenamiento final
resultados_3678055 <- resultados_3678055[order(resultados_3678055$rmse_cv), ]

mejores_params_3678055 <- list(
  objective = "reg:squarederror",
  eval_metric = "rmse",
  eta = resultados_3678055$eta[1],
  max_depth = resultados_3678055$max_depth[1],
  min_child_weight = resultados_3678055$min_child_weight[1],
  subsample = resultados_3678055$subsample[1],
  colsample_bytree = resultados_3678055$colsample_bytree[1],
  gamma = resultados_3678055$gamma[1]
)

mejor_nrounds_3678055 <- resultados_3678055$nrounds[1]

cat("\nMejores hiperparámetros encontrados:\n")
## 
## Mejores hiperparámetros encontrados:
print(mejores_params_3678055)
## $objective
## [1] "reg:squarederror"
## 
## $eval_metric
## [1] "rmse"
## 
## $eta
## [1] 0.05
## 
## $max_depth
## [1] 3
## 
## $min_child_weight
## [1] 1
## 
## $subsample
## [1] 0.9
## 
## $colsample_bytree
## [1] 0.7
## 
## $gamma
## [1] 0
cat("Número óptimo de rondas:", mejor_nrounds_3678055, "\n\n")
## Número óptimo de rondas: 200
modelo_xgb_3678055 <- xgb.train(
  params = mejores_params_3678055,
  data = dtrain_3678055,
  nrounds = mejor_nrounds_3678055,
  watchlist = list(train = dtrain_3678055, test = dtest_3678055),
  verbose = 0
)

# Paso 7: Evaluación en test
predicciones_test_3678055 <- predict(modelo_xgb_3678055, dtest_3678055)
mape_test_3678055 <- mean(abs((test_y_3678055 - predicciones_test_3678055) / pmax(test_y_3678055, 0.01))) * 100
rmse_test_3678055 <- sqrt(mean((test_y_3678055 - predicciones_test_3678055)^2))

cat("\nMétricas en conjunto de prueba:\n")
## 
## Métricas en conjunto de prueba:
cat("MAPE del modelo XGBoost:", mape_test_3678055, "\n")
## MAPE del modelo XGBoost: 1.326412
cat("RMSE del modelo XGBoost:", rmse_test_3678055, "\n\n")
## RMSE del modelo XGBoost: 430.9164
# Paso 8: Evaluación en conjunto completo
x_completo_3678055 <- as.matrix(datos_modelo_3678055[, colnames(datos_modelo_3678055) != "Venta"])
predicciones_completo_3678055 <- predict(modelo_xgb_3678055, x_completo_3678055)

mape_completo_3678055 <- mean(abs((datos_modelo_3678055$Venta - predicciones_completo_3678055) / 
                                  pmax(datos_modelo_3678055$Venta, 0.01))) * 100
rmse_completo_3678055 <- sqrt(mean((datos_modelo_3678055$Venta - predicciones_completo_3678055)^2))

cat("Métricas en conjunto completo:\n")
## Métricas en conjunto completo:
cat("MAPE del modelo XGBoost:", mape_completo_3678055, "\n")
## MAPE del modelo XGBoost: 1.167788
cat("RMSE del modelo XGBoost:", rmse_completo_3678055, "\n\n")
## RMSE del modelo XGBoost: 282.4049
# Paso 9: Importancia de variables
importancia_3678055 <- xgb.importance(
  feature_names = colnames(datos_modelo_3678055)[colnames(datos_modelo_3678055) != "Venta"],
  model = modelo_xgb_3678055
)

cat("Importancia de variables:\n")
## Importancia de variables:
print(importancia_3678055)
##                  Feature         Gain      Cover  Frequency
##                   <char>        <num>      <num>      <num>
## 1:                  Cant 0.5396883195 0.29827593 0.29791099
## 2:           Costo_Venta 0.4512038328 0.38143789 0.36330609
## 3:  Descuento_Porcentaje 0.0056748631 0.07157271 0.08174387
## 4: Precio_Final_Unitario 0.0028615777 0.18066485 0.17711172
## 5:                Tiempo 0.0005714069 0.06804862 0.07992734
xgb.plot.importance(importance_matrix = importancia_3678055, 
                    main = "Importancia de Variables - Producto 3678055 (XGBoost)")

# Paso 10: Visualizaciones
datos_grafico_3678055 <- data.frame(
  Observado = datos_modelo_3678055$Venta,
  Predicho = predicciones_completo_3678055
)

ggplot(datos_grafico_3678055, 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 (XGBoost)",
    x = "Ventas Observadas",
    y = "Ventas Predichas"
  ) +
  theme_minimal()

errores_3678055 <- datos_modelo_3678055$Venta - predicciones_completo_3678055

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

ggplot(data.frame(Predicho = predicciones_completo_3678055, Error = errores_3678055), 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 (XGBoost)",
    x = "Ventas Predichas",
    y = "Error (Observado - Predicho)"
  ) +
  theme_minimal()

# Paso 11: Guardar 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 = "3678055",
  Modelo = "XGBoost",
  MAPE = mape_completo_3678055,
  RMSE = rmse_completo_3678055
))
# 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 19.236587 253085.2030
## 2   155001 Regresión Lineal  8.668306    192.7890
## 3   155001    Random Forest  0.701904 527761.8868
## 4   155001          XGBoost  2.272554    159.1664
# 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 8.668306    192.7890
## 3   155001    Random Forest 0.701904 527761.8868
## 4   155001          XGBoost 2.272554    159.1664
# 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.3378022 253085.20298
## 2  3929788 Regresión Lineal 22.7639768    217.68292
## 3  3929788    Random Forest  0.9817282     94.54023
## 4  3929788    Random Forest  0.5437703    629.12246
## 5  3929788          XGBoost  0.8136359     46.33887
# 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 22.7639768 217.68292
## 3  3929788    Random Forest  0.9817282  94.54023
## 4  3929788    Random Forest  0.5437703 629.12246
## 5  3929788          XGBoost  0.8136359  46.33887
# 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 2 rows 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 14.9190340 155842.2797
## 2  3904152 Regresión Lineal  3.2987873    371.0529
## 3  3904152    Random Forest  0.5437703    629.1225
## 4  3904152          XGBoost  1.3631340    388.0705
# 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 3.2987873 371.0529
## 3  3904152    Random Forest 0.5437703 629.1225
## 4  3904152          XGBoost 1.3631340 388.0705
# 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 33.5562137 235419.9113
## 2   155002 Regresión Lineal 18.1423912    889.8286
## 3   155002    Random Forest  0.9725775    361.6748
## 4   155002          XGBoost  2.6717585    406.7509
# 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 18.1423912 889.8286
## 3   155002    Random Forest  0.9725775 361.6748
## 4   155002          XGBoost  2.6717585 406.7509
# 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.3280866 175375.13435
## 2  3678055 Regresión Lineal  2.8994611 200797.25481
## 3  3678055    Random Forest  0.4065828    577.23739
## 4  3678055          XGBoost  1.1677885    282.40488
## 5  3678055          XGBoost  0.8136359     46.33887
# 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 2.8994611 200797.25481
## 3  3678055    Random Forest 0.4065828    577.23739
## 4  3678055          XGBoost 1.1677885    282.40488
## 5  3678055          XGBoost 0.8136359     46.33887
# 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 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()`).

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 Descuento_Porcentaje
##   <date>                     <dbl> <dbl> <dbl>       <dbl>                <dbl>
## 1 2023-01-02                   980     1   980        727.                 76.6
## 2 2023-01-03                   728     1   728        905.                 82.6
## 3 2023-01-03                   840     6  5040       3598.                 80.0
## 4 2023-01-04                  1120     1  1120        577.                 73.3
## 5 2023-01-04                   728     8  5824       6619.                 82.6
## 6 2023-01-04                   980    10  9800       7273.                 76.6
## # ℹ 9 more variables: 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)
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-06                    462.67      630.0000
## 2         155001 2025-07-06                    462.67      630.0000
## 3         155001 2025-08-06                    462.67      630.0000
## 4         155001 2025-09-06                    462.67      630.0000
## 5         155001 2025-10-06                    462.67      630.0000
## 6         155001 2025-11-06                    462.67      630.0000
## 7        3929788 2025-06-06                     40.46       51.5872
## 8        3929788 2025-07-06                     40.46       51.5872
## 9        3929788 2025-08-06                     40.46       51.5872
## 10       3929788 2025-09-06                     40.46       51.5872
## 11       3929788 2025-10-06                     40.46       51.5872
## 12       3929788 2025-11-06                     40.46       51.5872
## 13       3904152 2025-06-06                   3124.61     3556.0000
## 14       3904152 2025-07-06                   3124.61     3556.0000
## 15       3904152 2025-08-06                   3124.61     3556.0000
## 16       3904152 2025-09-06                   3124.61     3556.0000
## 17       3904152 2025-10-06                   3124.61     3556.0000
## 18       3904152 2025-11-06                   3124.61     3556.0000
## 19        155002 2025-06-06                    452.59      593.6000
## 20        155002 2025-07-06                    452.59      593.6000
## 21        155002 2025-08-06                    452.59      593.6000
## 22        155002 2025-09-06                    452.59      593.6000
## 23        155002 2025-10-06                    452.59      593.6000
## 24        155002 2025-11-06                    452.59      593.6000
## 25       3678055 2025-06-06                   5248.73     5908.0000
## 26       3678055 2025-07-06                   5248.73     5908.0000
## 27       3678055 2025-08-06                   5248.73     5908.0000
## 28       3678055 2025-09-06                   5248.73     5908.0000
## 29       3678055 2025-10-06                   5248.73     5908.0000
## 30       3678055 2025-11-06                   5248.73     5908.0000
##    Venta_Esperada_Historica Venta_Esperada_XGBoost Margen_Historico_Promedio
## 1                  2773.289               1253.110                    337.87
## 2                  3824.150               1255.131                    337.87
## 3                  3309.438               1793.815                    337.87
## 4                  2906.493               1255.131                    337.87
## 5                  2740.059               1255.131                    337.87
## 6                  2445.293               1255.131                    337.87
## 7                  1658.104               1044.346                    352.34
## 8                  1796.298               1044.346                    352.34
## 9                  1715.712               1044.775                    352.34
## 10                 1568.125               1044.346                    352.34
## 11                 1728.707               1274.590                    352.34
## 12                 1398.353               1044.346                    352.34
## 13                 7427.639               3551.571                   1761.58
## 14                 6116.994               3554.225                   1761.58
## 15                 7902.450               3558.141                   1761.58
## 16                 7601.677               7108.436                   1761.58
## 17                 7545.842               3538.695                   1761.58
## 18                11175.169               3558.141                   1761.58
## 19                 3240.199               1703.100                    260.63
## 20                 3341.957               1703.100                    260.63
## 21                 2821.299               1703.100                    260.63
## 22                 2747.125               1099.524                    260.63
## 23                 2609.795               1099.524                    260.63
## 24                 2170.307               1099.524                    260.63
## 25                10800.705               5894.670                   2405.74
## 26                10692.688               5894.670                   2405.74
## 27                10281.788               5894.670                   2405.74
## 28                10671.964               5924.096                   2405.74
## 29                 9014.365               5915.545                   2405.74
## 30                 7998.486               5897.285                   2405.74
##    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
LS0tCnRpdGxlOiAiPHNwYW4gc3R5bGU9J2NvbG9yOiBicm93bjsnPkVWSURFTkNJQSBOT1ZFTTwvc3Bhbj4iCmF1dGhvcjogIkVxdWlwbyA0IgpkYXRlOiAiMjAyNC0wMy0wNCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHRydWUgCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIHRoZW1lOiBqb3VybmFsCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKZWRpdG9yX3M6IAogIGNodW5rX291dHB1dF90eXBlOiBjb25zb2xlCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogY29uc29sZQotLS0KCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibGFjazsiPiAgTGlicmVyaWFzIDwvc3Bhbj4gCgoKYGBge3J9CiMgTGlicmVyw61hcyBuZWNlc2FyaWFzCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHJlYWR4bCkKbGlicmFyeShwdXJycikKbGlicmFyeShrbml0cikKI2luc3RhbGwucGFja2FnZXMoImthYmxlRXh0cmEiKQpsaWJyYXJ5KGthYmxlRXh0cmEpCmxpYnJhcnkoZ2dwbG90MikKI2luc3RhbGwucGFja2FnZXMoImlncmFwaCIpCmxpYnJhcnkoaWdyYXBoKQojaW5zdGFsbC5wYWNrYWdlcygiZm9yZWNhc3QiKQojaW5zdGFsbC5wYWNrYWdlcygibHVicmlkYXRlIikKbGlicmFyeShmb3JlY2FzdCkKbGlicmFyeShsdWJyaWRhdGUpCmxpYnJhcnkoY29ycnBsb3QpCmxpYnJhcnkoUkNvbG9yQnJld2VyKQojaW5zdGFsbC5wYWNrYWdlcygiZ2djb3JycGxvdCIpCmxpYnJhcnkoZ2djb3JycGxvdCkKbGlicmFyeShjYXJldCkKbGlicmFyeShjYXIpCmxpYnJhcnkocmFuZG9tRm9yZXN0KQojaW5zdGFsbC5wYWNrYWdlcygieGdib29zdCIpCmxpYnJhcnkoeGdib29zdCkKI2luc3RhbGwucGFja2FnZXMoInBhdGNod29yayIpCmxpYnJhcnkocGF0Y2h3b3JrKQpgYGAKCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibGFjazsiPiAgQ2FyZ2EgZGUgZGF0b3MgPC9zcGFuPiAKCmBgYHtyfQpsaWJyYXJ5KHJlYWR4bCkKbGlicmFyeShkcGx5cikKbGlicmFyeShsdWJyaWRhdGUpCgojIExlZXIgZGF0b3MKZGF0b3MgPC0gcmVhZF9leGNlbCgiL1VzZXJzL29zY2FyY2FzdGFuZWRhZ2FyY2lhL0Rvd25sb2Fkcy9JQSBjb24gaW1wYWN0byBlbXByZXNhcmlhbC9maWx0ZXJlZF9kYXRhLnhsc3giKSAlPiUKICBtdXRhdGUoVHJ4X0ZlY2hhID0gYXMuRGF0ZShUcnhfRmVjaGEpKQoKIyBQYXJ0aWNpw7NuIHRlbXBvcmFsCnRyYWluIDwtIGRhdG9zICU+JSBmaWx0ZXIoVHJ4X0ZlY2hhID49IGFzLkRhdGUoIjIwMjMtMDEtMDEiKSAmIFRyeF9GZWNoYSA8IGFzLkRhdGUoIjIwMjQtMTAtMDEiKSkKdGVzdCAgPC0gZGF0b3MgJT4lIGZpbHRlcihUcnhfRmVjaGEgPj0gYXMuRGF0ZSgiMjAyNC0xMC0wMSIpICYgVHJ4X0ZlY2hhIDw9IGFzLkRhdGUoIjIwMjQtMTItMzEiKSkKCiMgVmFsaWRhY2nDs24gcsOhcGlkYQpzdW1tYXJ5KHRyYWluJFRyeF9GZWNoYSkKc3VtbWFyeSh0ZXN0JFRyeF9GZWNoYSkKYGBgCgpgYGB7cn0KIyBPYnRlbmVyIGxvcyA1IHByb2R1Y3RvcyBtw6FzIHZlbmRpZG9zIChwb3IgdmFsb3IpCnRvcF9pZHMgPC0gZGF0b3MgJT4lCiAgZ3JvdXBfYnkoSURfSW52ZW50YXJpbykgJT4lCiAgc3VtbWFyaXNlKFZlbnRhc19Ub3RhbGVzID0gc3VtKFZlbnRhLCBuYS5ybSA9IFRSVUUpKSAlPiUKICBhcnJhbmdlKGRlc2MoVmVudGFzX1RvdGFsZXMpKSAlPiUKICBzbGljZV9oZWFkKG4gPSA1KSAlPiUKICBwdWxsKElEX0ludmVudGFyaW8pCgpwcmludCgiVG9wIDUgcHJvZHVjdG9zIG3DoXMgdmVuZGlkb3MgKElEX0ludmVudGFyaW8pOiIpCnByaW50KHRvcF9pZHMpCgpgYGAKCmBgYHtyfQojIEZpbHRyYXIgZGF0b3MgdsOhbGlkb3MKZGF0b3NfZmlsdHJhZG9zIDwtIHRyYWluICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvICVpbiUgdG9wX2lkcykgJT4lCiAgZmlsdGVyKCFpcy5uYShQcmVjaW9fRmluYWxfVW5pdGFyaW8pKQoKIyBDb250YXIgb2JzZXJ2YWNpb25lcyBwb3IgcHJvZHVjdG8KY29udGVvIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUKICBjb3VudChJRF9JbnZlbnRhcmlvLCBzb3J0ID0gVFJVRSkKCnByaW50KCJOw7ptZXJvIGRlIHJlZ2lzdHJvcyBwb3IgcHJvZHVjdG8gZW4gZGF0b3NfZmlsdHJhZG9zOiIpCnByaW50KGNvbnRlbykKCiMgVmVyaWZpY2Egc2kgaGF5IHN1ZmljaWVudGVzIGRhdG9zCmlmIChucm93KGRhdG9zX2ZpbHRyYWRvcykgPT0gMCkgewogIHN0b3AoIk5vIGhheSBkYXRvcyBzdWZpY2llbnRlcyBsdWVnbyBkZSBmaWx0cmFyIHBvciB0b3BfaWRzIHkgcHJlY2lvcyB2w6FsaWRvcy4iKQp9CgpgYGAKCmBgYHtyfQojIENvbWJpbmFjaW9uZXMgZGUgcGFyZXMKcHJvZHVjdG9zIDwtIHVuaXF1ZShkYXRvc19maWx0cmFkb3MkSURfSW52ZW50YXJpbykKcGFyZXNfcHJvZHVjdG9zIDwtIGNvbWJuKHByb2R1Y3RvcywgMiwgc2ltcGxpZnkgPSBGQUxTRSkKCiMgSW5pY2lhbGl6YXIgcmVzdWx0YWRvcwpyZXN1bHRhZG9zX2tzIDwtIG1hcF9kZihwYXJlc19wcm9kdWN0b3MsIGZ1bmN0aW9uKHBhcikgewogIHByb2QxIDwtIHBhclsxXQogIHByb2QyIDwtIHBhclsyXQogIAogIHByZWNpb3MxIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUKICAgIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IHByb2QxKSAlPiUKICAgIHB1bGwoUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKQogIAogIHByZWNpb3MyIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUKICAgIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IHByb2QyKSAlPiUKICAgIHB1bGwoUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKQogIAogIHByaW50KHBhc3RlKCJDb21wYXJhbmRvIHByb2R1Y3RvcyIsIHByb2QxLCAidnMiLCBwcm9kMikpCiAgcHJpbnQocGFzdGUoIkNhbnRpZGFkIGRlIHByZWNpb3M6IiwgbGVuZ3RoKHByZWNpb3MxKSwgInkiLCBsZW5ndGgocHJlY2lvczIpKSkKICAKICBpZiAobGVuZ3RoKHByZWNpb3MxKSA+PSA1ICYgbGVuZ3RoKHByZWNpb3MyKSA+PSA1KSB7CiAgICBwcnVlYmEgPC0gc3VwcHJlc3NXYXJuaW5ncyhrcy50ZXN0KHByZWNpb3MxLCBwcmVjaW9zMikpCiAgICBkYXRhLmZyYW1lKAogICAgICBQcm9kdWN0b18xID0gcHJvZDEsCiAgICAgIFByb2R1Y3RvXzIgPSBwcm9kMiwKICAgICAgRCA9IHJvdW5kKHBydWViYSRzdGF0aXN0aWMsIDQpLAogICAgICBwX3ZhbHVlID0gcm91bmQocHJ1ZWJhJHAudmFsdWUsIDQpLAogICAgICBDb25jbHVzaW9uID0gaWZlbHNlKHBydWViYSRwLnZhbHVlID4gMC4wNSwgIkRpc3RyaWJ1Y2lvbmVzIHNpbWlsYXJlcyIsICJEaXN0cmlidWNpb25lcyBkaWZlcmVudGVzIikKICAgICkKICB9IGVsc2UgewogICAgZGF0YS5mcmFtZSgKICAgICAgUHJvZHVjdG9fMSA9IHByb2QxLAogICAgICBQcm9kdWN0b18yID0gcHJvZDIsCiAgICAgIEQgPSBOQSwKICAgICAgcF92YWx1ZSA9IE5BLAogICAgICBDb25jbHVzaW9uID0gIkRhdG9zIGluc3VmaWNpZW50ZXMiCiAgICApCiAgfQp9KQoKcHJpbnQoIlJlc3VsdGFkb3MgZGUgbGEgcHJ1ZWJhIEtTOiIpCnByaW50KHJlc3VsdGFkb3Nfa3MpCgpgYGAKCmBgYHtyfQp0cnkoZGV2Lm9mZigpLCBzaWxlbnQgPSBUUlVFKQpgYGAKCgoKCmBgYHtyfQoKIyBGaWx0cmFyIGxvcyBwcm9kdWN0b3MKZGZfMTU1MDAxIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUKICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAxNTUwMDEpICU+JQogIHNlbGVjdChQcmVjaW9fRmluYWxfVW5pdGFyaW8pICU+JQogIG11dGF0ZShQcm9kdWN0byA9ICIxNTUwMDEiKQoKZGZfMTU1MDAyIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUKICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAxNTUwMDIpICU+JQogIHNlbGVjdChQcmVjaW9fRmluYWxfVW5pdGFyaW8pICU+JQogIG11dGF0ZShQcm9kdWN0byA9ICIxNTUwMDIiKQoKIyBVbmlyIGVuIHVuIHNvbG8gZGF0YWZyYW1lCmRmX2VjZGYgPC0gYmluZF9yb3dzKGRmXzE1NTAwMSwgZGZfMTU1MDAyKQoKIyBHcmFmaWNhciBFQ0RGCmdncGxvdChkZl9lY2RmLCBhZXMoeCA9IFByZWNpb19GaW5hbF9Vbml0YXJpbywgY29sb3IgPSBQcm9kdWN0bykpICsKICBzdGF0X2VjZGYoZ2VvbSA9ICJzdGVwIiwgc2l6ZSA9IDEpICsKICBsYWJzKHRpdGxlID0gIkVDREYgZGUgUHJlY2lvIEZpbmFsIFVuaXRhcmlvOiBQcm9kdWN0b3MgMTU1MDAxIHZzIDE1NTAwMiIsCiAgICAgICB4ID0gIlByZWNpbyBGaW5hbCBVbml0YXJpbyIsCiAgICAgICB5ID0gIkZ1bmNpw7NuIGRlIERpc3RyaWJ1Y2nDs24gQWN1bXVsYWRhIChFQ0RGKSIsCiAgICAgICBjb2xvciA9ICJQcm9kdWN0byIpICsKICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDE0KQoKYGBgCgoKPCEtLSBBUk1BIC0tPgojIEFSTUEgCgojIFBSRURJQ0NJT05FUyBERSBWRU5UQVMKCgo8IS0tIFBST0RVQ1RPIDE1NTAwMSAtLT4KIyMgUFJPRFVDVE8gMTU1MDAxCgpgYGB7ciBhcm1hLTE1NTAwMX0KIyBQcm9kdWN0byAxNTUwMDEKaWRfcHJvZCA8LSAxNTUwMDEKCiMgQ3JlYXIgbGEgc2VyaWUgZGUgdGllbXBvIG1lbnN1YWwKdmVudGFzX21lbnN1YWxlcyA8LSBkYXRvc19maWx0cmFkb3MgJT4lCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gaWRfcHJvZCkgJT4lCiAgbXV0YXRlKEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpKSAlPiUKICBncm91cF9ieShGZWNoYSkgJT4lCiAgc3VtbWFyaXNlKFZlbnRhID0gc3VtKFZlbnRhLCBuYS5ybSA9IFRSVUUpKSAlPiUKICBhcnJhbmdlKEZlY2hhKQoKc2VyaWVfdHMgPC0gdHModmVudGFzX21lbnN1YWxlcyRWZW50YSwgZnJlcXVlbmN5ID0gMTIsCiAgICAgICAgICAgICAgIHN0YXJ0ID0gYyh5ZWFyKG1pbih2ZW50YXNfbWVuc3VhbGVzJEZlY2hhKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgbW9udGgobWluKHZlbnRhc19tZW5zdWFsZXMkRmVjaGEpKSkpCgojIE1vZGVsbyBBUk1BCm1vZGVsb19hcm1hIDwtIGF1dG8uYXJpbWEoc2VyaWVfdHMsIHNlYXNvbmFsID0gRkFMU0UsIHN0ZXB3aXNlID0gRkFMU0UsIGFwcHJveGltYXRpb24gPSBGQUxTRSkKZm9yZWNhc3RfbW9kZWxvIDwtIGZvcmVjYXN0KG1vZGVsb19hcm1hLCBoID0gMykKCiMgR3LDoWZpY28gZGVsIHByb27Ds3N0aWNvCmF1dG9wbG90KGZvcmVjYXN0X21vZGVsbykgKwogIGxhYnModGl0bGUgPSBwYXN0ZSgiUHJvbsOzc3RpY28gbWVuc3VhbCBkZSB2ZW50YXMgLSBBUk1BIChQcm9kdWN0byIsIGlkX3Byb2QsICIpIiksCiAgICAgICB4ID0gIk1lcyIsIHkgPSAiVmVudGFzICgkKSIpICsKICB0aGVtZV9taW5pbWFsKCkKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzCmZpdHRlZF92YWx1ZXMgPC0gZml0dGVkKG1vZGVsb19hcm1hKQptYXBlIDwtIG1lYW4oYWJzKChzZXJpZV90cyAtIGZpdHRlZF92YWx1ZXMpIC8gcG1heChzZXJpZV90cywgMC4wMSkpKSAqIDEwMApybXNlIDwtIHNxcnQobWVhbigoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKV4yKSkKCgojIENyZWFyIHRhYmxhIGRlIG3DqXRyaWNhcwppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgewogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLAogICAgTUFQRSA9IG51bWVyaWMoKSwKICAgIFJNU0UgPSBudW1lcmljKCksCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCn0KCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSBpZF9wcm9kLAogIE1vZGVsbyA9ICJBUk1BIiwKICBNQVBFID0gbWFwZSwKICBSTVNFID0gcm1zZQopKQoKIyBNb3N0cmFyIHRhYmxhIHBhcmEgZXN0ZSBwcm9kdWN0bwp0YWlsKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgMSkgJT4lCiAga25pdHI6OmthYmxlKGNhcHRpb24gPSBwYXN0ZSgiTcOpdHJpY2FzIGRlbCBtb2RlbG8gQVJNQSBwYXJhIFByb2R1Y3RvIiwgaWRfcHJvZCkpICU+JQogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEZBTFNFKQpgYGAKCgo8IS0tIFBST0RVQ1RPIDM5Mjk3ODggLS0+CiMjIFBST0RVQ1RPIDM5Mjk3ODgKCmBgYHtyIGFybWEtMzkyOTc4OH0KIyBQcm9kdWN0byAzOTI5Nzg4CgppZF9wcm9kIDwtIDM5Mjk3ODgKCiMgQ3JlYXIgbGEgc2VyaWUgZGUgdGllbXBvIG1lbnN1YWwKdmVudGFzX21lbnN1YWxlcyA8LSBkYXRvc19maWx0cmFkb3MgJT4lCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gaWRfcHJvZCkgJT4lCiAgbXV0YXRlKEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpKSAlPiUKICBncm91cF9ieShGZWNoYSkgJT4lCiAgc3VtbWFyaXNlKFZlbnRhID0gc3VtKFZlbnRhLCBuYS5ybSA9IFRSVUUpKSAlPiUKICBhcnJhbmdlKEZlY2hhKQoKc2VyaWVfdHMgPC0gdHModmVudGFzX21lbnN1YWxlcyRWZW50YSwgZnJlcXVlbmN5ID0gMTIsCiAgICAgICAgICAgICAgIHN0YXJ0ID0gYyh5ZWFyKG1pbih2ZW50YXNfbWVuc3VhbGVzJEZlY2hhKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgbW9udGgobWluKHZlbnRhc19tZW5zdWFsZXMkRmVjaGEpKSkpCgojIE1vZGVsbyBBUk1BCm1vZGVsb19hcm1hIDwtIGF1dG8uYXJpbWEoc2VyaWVfdHMsIHNlYXNvbmFsID0gRkFMU0UsIHN0ZXB3aXNlID0gRkFMU0UsIGFwcHJveGltYXRpb24gPSBGQUxTRSkKZm9yZWNhc3RfbW9kZWxvIDwtIGZvcmVjYXN0KG1vZGVsb19hcm1hLCBoID0gMykKCiMgR3LDoWZpY28gZGVsIHByb27Ds3N0aWNvCmF1dG9wbG90KGZvcmVjYXN0X21vZGVsbykgKwogIGxhYnModGl0bGUgPSBwYXN0ZSgiUHJvbsOzc3RpY28gbWVuc3VhbCBkZSB2ZW50YXMgLSBBUk1BIChQcm9kdWN0byIsIGlkX3Byb2QsICIpIiksCiAgICAgICB4ID0gIk1lcyIsIHkgPSAiVmVudGFzICgkKSIpICsKICB0aGVtZV9taW5pbWFsKCkKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzCmZpdHRlZF92YWx1ZXMgPC0gZml0dGVkKG1vZGVsb19hcm1hKQptYXBlIDwtIG1lYW4oYWJzKChzZXJpZV90cyAtIGZpdHRlZF92YWx1ZXMpIC8gcG1heChzZXJpZV90cywgMC4wMSkpKSAqIDEwMAptc2UgPC0gbWVhbigoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKV4yKQoKIyBDcmVhciB0YWJsYSBkZSBtw6l0cmljYXMKaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1BUEUgPSBudW1lcmljKCksCiAgICBSTVNFID0gbnVtZXJpYygpLAogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKQp9CgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gaWRfcHJvZCwKICBNb2RlbG8gPSAiQVJNQSIsCiAgTUFQRSA9IG1hcGUsCiAgUk1TRSAgPSBybXNlCikpCgojIE1vc3RyYXIgdGFibGEgcGFyYSBlc3RlIHByb2R1Y3RvCnRhaWwobWV0cmljYXNfY29tcGFyYXRpdmFzLCAxKSAlPiUKICBrbml0cjo6a2FibGUoY2FwdGlvbiA9IHBhc3RlKCJNw6l0cmljYXMgZGVsIG1vZGVsbyBBUk1BIHBhcmEgUHJvZHVjdG8iLCBpZF9wcm9kKSkgJT4lCiAga2FibGVFeHRyYTo6a2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UpCmBgYAoKCjwhLS0gUFJPRFVDVE8gMzkwNDE1MiAtLT4KIyMgUFJPRFVDVE8gMzkwNDE1MgoKYGBge3IgYXJtYS0zOTA0MTUyfQojIFByb2R1Y3RvIDM5MDQxNTIKaWRfcHJvZCA8LSAzOTA0MTUyCgojIENyZWFyIGxhIHNlcmllIGRlIHRpZW1wbyBtZW5zdWFsCnZlbnRhc19tZW5zdWFsZXMgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IGlkX3Byb2QpICU+JQogIG11dGF0ZShGZWNoYSA9IGFzLkRhdGUoZmxvb3JfZGF0ZShUcnhfRmVjaGEsICJtb250aCIpKSkgJT4lCiAgZ3JvdXBfYnkoRmVjaGEpICU+JQogIHN1bW1hcmlzZShWZW50YSA9IHN1bShWZW50YSwgbmEucm0gPSBUUlVFKSkgJT4lCiAgYXJyYW5nZShGZWNoYSkKCnNlcmllX3RzIDwtIHRzKHZlbnRhc19tZW5zdWFsZXMkVmVudGEsIGZyZXF1ZW5jeSA9IDEyLAogICAgICAgICAgICAgICBzdGFydCA9IGMoeWVhcihtaW4odmVudGFzX21lbnN1YWxlcyRGZWNoYSkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIG1vbnRoKG1pbih2ZW50YXNfbWVuc3VhbGVzJEZlY2hhKSkpKQoKIyBNb2RlbG8gQVJNQQptb2RlbG9fYXJtYSA8LSBhdXRvLmFyaW1hKHNlcmllX3RzLCBzZWFzb25hbCA9IEZBTFNFLCBzdGVwd2lzZSA9IEZBTFNFLCBhcHByb3hpbWF0aW9uID0gRkFMU0UpCmZvcmVjYXN0X21vZGVsbyA8LSBmb3JlY2FzdChtb2RlbG9fYXJtYSwgaCA9IDMpCgojIEdyw6FmaWNvIGRlbCBwcm9uw7NzdGljbwphdXRvcGxvdChmb3JlY2FzdF9tb2RlbG8pICsKICBsYWJzKHRpdGxlID0gcGFzdGUoIlByb27Ds3N0aWNvIG1lbnN1YWwgZGUgdmVudGFzIC0gQVJNQSAoUHJvZHVjdG8iLCBpZF9wcm9kLCAiKSIpLAogICAgICAgeCA9ICJNZXMiLCB5ID0gIlZlbnRhcyAoJCkiKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIENhbGN1bGFyIG3DqXRyaWNhcwpmaXR0ZWRfdmFsdWVzIDwtIGZpdHRlZChtb2RlbG9fYXJtYSkKbWFwZSA8LSBtZWFuKGFicygoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKSAvIHBtYXgoc2VyaWVfdHMsIDAuMDEpKSkgKiAxMDAKcm1zZSA8LSBzcXJ0KG1lYW4oKHNlcmllX3RzIC0gZml0dGVkX3ZhbHVlcyleMikpCgoKIyBDcmVhciB0YWJsYSBkZSBtw6l0cmljYXMKaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1BUEUgPSBudW1lcmljKCksCiAgICBSTVNFID0gbnVtZXJpYygpLAogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKQp9CgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gaWRfcHJvZCwKICBNb2RlbG8gPSAiQVJNQSIsCiAgTUFQRSA9IG1hcGUsCiAgUk1TRSA9IHJtc2UKKSkKCiMgTW9zdHJhciB0YWJsYSBwYXJhIGVzdGUgcHJvZHVjdG8KdGFpbChtZXRyaWNhc19jb21wYXJhdGl2YXMsIDEpICU+JQogIGtuaXRyOjprYWJsZShjYXB0aW9uID0gcGFzdGUoIk3DqXRyaWNhcyBkZWwgbW9kZWxvIEFSTUEgcGFyYSBQcm9kdWN0byIsIGlkX3Byb2QpKSAlPiUKICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGQUxTRSkKYGBgCgoKPCEtLSBQUk9EVUNUTyAxNTUwMDIgLS0+CiMjIFBST0RVQ1RPIDE1NTAwMgoKYGBge3IgYXJtYS0xNTUwMDJ9CiMgUHJvZHVjdG8gMTU1MDAyCmlkX3Byb2QgPC0gMTU1MDAyCgojIENyZWFyIGxhIHNlcmllIGRlIHRpZW1wbyBtZW5zdWFsCnZlbnRhc19tZW5zdWFsZXMgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IGlkX3Byb2QpICU+JQogIG11dGF0ZShGZWNoYSA9IGFzLkRhdGUoZmxvb3JfZGF0ZShUcnhfRmVjaGEsICJtb250aCIpKSkgJT4lCiAgZ3JvdXBfYnkoRmVjaGEpICU+JQogIHN1bW1hcmlzZShWZW50YSA9IHN1bShWZW50YSwgbmEucm0gPSBUUlVFKSkgJT4lCiAgYXJyYW5nZShGZWNoYSkKCnNlcmllX3RzIDwtIHRzKHZlbnRhc19tZW5zdWFsZXMkVmVudGEsIGZyZXF1ZW5jeSA9IDEyLAogICAgICAgICAgICAgICBzdGFydCA9IGMoeWVhcihtaW4odmVudGFzX21lbnN1YWxlcyRGZWNoYSkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIG1vbnRoKG1pbih2ZW50YXNfbWVuc3VhbGVzJEZlY2hhKSkpKQoKIyBNb2RlbG8gQVJNQQptb2RlbG9fYXJtYSA8LSBhdXRvLmFyaW1hKHNlcmllX3RzLCBzZWFzb25hbCA9IEZBTFNFLCBzdGVwd2lzZSA9IEZBTFNFLCBhcHByb3hpbWF0aW9uID0gRkFMU0UpCmZvcmVjYXN0X21vZGVsbyA8LSBmb3JlY2FzdChtb2RlbG9fYXJtYSwgaCA9IDMpCgojIEdyw6FmaWNvIGRlbCBwcm9uw7NzdGljbwphdXRvcGxvdChmb3JlY2FzdF9tb2RlbG8pICsKICBsYWJzKHRpdGxlID0gcGFzdGUoIlByb27Ds3N0aWNvIG1lbnN1YWwgZGUgdmVudGFzIC0gQVJNQSAoUHJvZHVjdG8iLCBpZF9wcm9kLCAiKSIpLAogICAgICAgeCA9ICJNZXMiLCB5ID0gIlZlbnRhcyAoJCkiKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIENhbGN1bGFyIG3DqXRyaWNhcwpmaXR0ZWRfdmFsdWVzIDwtIGZpdHRlZChtb2RlbG9fYXJtYSkKbWFwZSA8LSBtZWFuKGFicygoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKSAvIHBtYXgoc2VyaWVfdHMsIDAuMDEpKSkgKiAxMDAKcm1zZSA8LSBzcXJ0KG1lYW4oKHNlcmllX3RzIC0gZml0dGVkX3ZhbHVlcyleMikpCgoKIyBDcmVhciB0YWJsYSBkZSBtw6l0cmljYXMKaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1BUEUgPSBudW1lcmljKCksCiAgICBSTVNFID0gbnVtZXJpYygpLAogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKQp9CgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gaWRfcHJvZCwKICBNb2RlbG8gPSAiQVJNQSIsCiAgTUFQRSA9IG1hcGUsCiAgUk1TRSA9IHJtc2UKKSkKCiMgTW9zdHJhciB0YWJsYSBwYXJhIGVzdGUgcHJvZHVjdG8KdGFpbChtZXRyaWNhc19jb21wYXJhdGl2YXMsIDEpICU+JQogIGtuaXRyOjprYWJsZShjYXB0aW9uID0gcGFzdGUoIk3DqXRyaWNhcyBkZWwgbW9kZWxvIEFSTUEgcGFyYSBQcm9kdWN0byIsIGlkX3Byb2QpKSAlPiUKICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGQUxTRSkKYGBgCgoKPCEtLVByb2R1Y3RvIDM2NzgwNTUgLS0+CiMjIFBST0RVQ1RPIDM2NzgwNTUKYGBge3IgYXJtYS0zNjc4MDU1fQojIFByb2R1Y3RvIDM2NzgwNTUKaWRfcHJvZCA8LSAzNjc4MDU1CgojIENyZWFyIGxhIHNlcmllIGRlIHRpZW1wbyBtZW5zdWFsCnZlbnRhc19tZW5zdWFsZXMgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IGlkX3Byb2QpICU+JQogIG11dGF0ZShGZWNoYSA9IGFzLkRhdGUoZmxvb3JfZGF0ZShUcnhfRmVjaGEsICJtb250aCIpKSkgJT4lCiAgZ3JvdXBfYnkoRmVjaGEpICU+JQogIHN1bW1hcmlzZShWZW50YSA9IHN1bShWZW50YSwgbmEucm0gPSBUUlVFKSkgJT4lCiAgYXJyYW5nZShGZWNoYSkKCnNlcmllX3RzIDwtIHRzKHZlbnRhc19tZW5zdWFsZXMkVmVudGEsIGZyZXF1ZW5jeSA9IDEyLAogICAgICAgICAgICAgICBzdGFydCA9IGMoeWVhcihtaW4odmVudGFzX21lbnN1YWxlcyRGZWNoYSkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIG1vbnRoKG1pbih2ZW50YXNfbWVuc3VhbGVzJEZlY2hhKSkpKQoKIyBNb2RlbG8gQVJNQQptb2RlbG9fYXJtYSA8LSBhdXRvLmFyaW1hKHNlcmllX3RzLCBzZWFzb25hbCA9IEZBTFNFLCBzdGVwd2lzZSA9IEZBTFNFLCBhcHByb3hpbWF0aW9uID0gRkFMU0UpCmZvcmVjYXN0X21vZGVsbyA8LSBmb3JlY2FzdChtb2RlbG9fYXJtYSwgaCA9IDMpCgojIEdyw6FmaWNvIGRlbCBwcm9uw7NzdGljbwphdXRvcGxvdChmb3JlY2FzdF9tb2RlbG8pICsKICBsYWJzKHRpdGxlID0gcGFzdGUoIlByb27Ds3N0aWNvIG1lbnN1YWwgZGUgdmVudGFzIC0gQVJNQSAoUHJvZHVjdG8iLCBpZF9wcm9kLCAiKSIpLAogICAgICAgeCA9ICJNZXMiLCB5ID0gIlZlbnRhcyAoJCkiKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIENhbGN1bGFyIG3DqXRyaWNhcwpmaXR0ZWRfdmFsdWVzIDwtIGZpdHRlZChtb2RlbG9fYXJtYSkKbWFwZSA8LSBtZWFuKGFicygoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKSAvIHBtYXgoc2VyaWVfdHMsIDAuMDEpKSkgKiAxMDAKcm1zZSA8LSBzcXJ0KG1lYW4oKHNlcmllX3RzIC0gZml0dGVkX3ZhbHVlcyleMikpCgojIENyZWFyIHRhYmxhIGRlIG3DqXRyaWNhcwppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgewogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLAogICAgTUFQRSA9IG51bWVyaWMoKSwKICAgIFJNU0UgPSBudW1lcmljKCksCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCn0KCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSBpZF9wcm9kLAogIE1vZGVsbyA9ICJBUk1BIiwKICBNQVBFID0gbWFwZSwKICBSTVNFID0gcm1zZQopKQoKIyBNb3N0cmFyIHRhYmxhIHBhcmEgZXN0ZSBwcm9kdWN0bwp0YWlsKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgMSkgJT4lCiAga25pdHI6OmthYmxlKGNhcHRpb24gPSBwYXN0ZSgiTcOpdHJpY2FzIGRlbCBtb2RlbG8gQVJNQSBwYXJhIFByb2R1Y3RvIiwgaWRfcHJvZCkpICU+JQogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEZBTFNFKQpgYGAKCgojIFJFR1JFU0lPTiBMSU5FQUwKIyMgTUFQQSBERSBDQUxPUgoKYGBge3IgbWFwYV9jYWxvcl9jb3JyZWxhY2lvbiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBWYXJpYWJsZXMgbnVtw6lyaWNhcyByZWxldmFudGVzCnZhcnNfbnVtZXJpY2FzIDwtIGMoIkNhbnQiLCAiVmVudGEiLCAiQ29zdG9fVmVudGEiLAogICAgICAgICAgICAgICAgICAgICJQcmVjaW9fRmluYWxfVW5pdGFyaW8iLCAiRGVzY3VlbnRvX1BvcmNlbnRhamUiKQoKIyBQcmVwYXJhY2nDs24gZGUgbG9zIGRhdG9zCmRhdG9zX2NvciA8LSBkYXRvc19maWx0cmFkb3MgJT4lCiAgc2VsZWN0KGFsbF9vZih2YXJzX251bWVyaWNhcykpICU+JQogIG5hLm9taXQoKQoKIyBHZW5lcmFyIGxhIG1hdHJpeiBkZSBjb3JyZWxhY2nDs24KbWF0cml6X2NvciA8LSBjb3IoZGF0b3NfY29yKQoKIyBBanVzdGUgZGVsIGdyw6FmaWNvIHNpbiBtYXIKZ2djb3JycGxvdChtYXRyaXpfY29yLAogICAgICAgICAgIG1ldGhvZCA9ICJzcXVhcmUiLAogICAgICAgICAgIHR5cGUgPSAidXBwZXIiLAogICAgICAgICAgIGxhYiA9IFRSVUUsIAogICAgICAgICAgIGxhYl9zaXplID0gMiwgICAgICAgICAgICAgICAgICAgIyBNZWpvciB0YW1hw7FvIGRlIGxvcyBjb2VmaWNpZW50ZXMKICAgICAgICAgICB0bC5jZXggPSAxMCwgICAgICAgICAgICAgICAgICAgICMgVGFtYcOxbyBkZSBldGlxdWV0YXMgbcOhcyBncmFuZGUKICAgICAgICAgICB0bC5zcnQgPSA0NSwgICAgICAgICAgICAgICAgICAgICMgUm90YWNpw7NuIGRlIDQ1wrAgZGUgZXRpcXVldGFzCiAgICAgICAgICAgY29sb3JzID0gYygiIzZEOUVDMSIsICJ3aGl0ZSIsICIjRTQ2NzI2IiksCiAgICAgICAgICAgdGl0bGUgPSAiTWFwYSBkZSBDb3JyZWxhY2nDs24gLSBWYXJpYWJsZXMgTnVtw6lyaWNhcyIsCiAgICAgICAgICAgZ2d0aGVtZSA9IHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTQpICsKICAgICAgICAgICAgIHRoZW1lKAogICAgICAgICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLAogICAgICAgICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDAsIGhqdXN0ID0gMSkpCikKYGBgCgojIyBQUk9EVUNUTyAxNTUwMDEKYGBge3J9CiMgRmlsdHJhciBzb2xvIGxvcyBkYXRvcyBwYXJhIGVsIHByb2R1Y3RvIDE1NTAwMQpkYXRvc18xNTUwMDEgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDE1NTAwMSkgJT4lCiAgc2VsZWN0KFZlbnRhLCBDYW50LCBDb3N0b19WZW50YSwKICAgICAgICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCBEZXNjdWVudG9fUG9yY2VudGFqZSwgVHJ4X0ZlY2hhKSAlPiUKICBuYS5vbWl0KCkgICMgRWxpbWluYXIgZmlsYXMgY29uIHZhbG9yZXMgTkEKCiMgQ3JlYXIgdW5hIHZhcmlhYmxlIGRlIHRpZW1wbyBjb250aW51YSBiYXNhZGEgZW4gbGEgZmVjaGEKZGF0b3NfMTU1MDAxIDwtIGRhdG9zXzE1NTAwMSAlPiUKICBtdXRhdGUoRmVjaGEgPSBhcy5EYXRlKGZsb29yX2RhdGUoVHJ4X0ZlY2hhLCAibW9udGgiKSksICAgIyBBc2Vnw7pyYXRlIGRlIHF1ZSBsYSBmZWNoYSBlc3TDqSBlbiBmb3JtYXRvIERhdGUKICAgICAgICAgVGllbXBvID0gYXMubnVtZXJpYyhGZWNoYSAtIG1pbihGZWNoYSkpIC8gKDMwICogMjQgKiA2MCAqIDYwKSkgICMgVGllbXBvIGVuIG1lc2VzIChhanVzdGFkbyBwb3IgZMOtYXMpCgojIFZlcmlmaWNhciBsYXMgcHJpbWVyYXMgZmlsYXMgcGFyYSBhc2VndXJhcm5vcyBkZSBxdWUgbGEgdmFyaWFibGUgZGUgdGllbXBvIGVzdMOpIGJpZW4gY3JlYWRhCmhlYWQoZGF0b3NfMTU1MDAxKQpgYGAKCmBgYHtyfQojIEZpbHRyYXIgc29sbyBsb3MgZGF0b3MgcGFyYSBlbCBwcm9kdWN0byAxNTUwMDEKdGVzdF8xNTUwMDEgPC0gdGVzdCAlPiUKICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAxNTUwMDEpICU+JQogIHNlbGVjdChWZW50YSwgQ2FudCwgQ29zdG9fVmVudGEsCiAgICAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbywgRGVzY3VlbnRvX1BvcmNlbnRhamUsIFRyeF9GZWNoYSkgJT4lCiAgbmEub21pdCgpICU+JQogIG11dGF0ZSgKICAgIEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpLAogICAgVGllbXBvID0gYXMubnVtZXJpYyhGZWNoYSAtIG1pbihGZWNoYSkpIC8gKDMwICogMjQgKiA2MCAqIDYwKQogICkKCiMgVmVyaWZpY2FyCmhlYWQodGVzdF8xNTUwMDEpCmBgYAoKYGBge3J9CiMgQWp1c3RhciBlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwKbW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDEgPC0gbG0oVmVudGEgfiBDYW50ICsgQ29zdG9fVmVudGEgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gKyBEZXNjdWVudG9fUG9yY2VudGFqZSArIFRpZW1wbywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGF0b3NfMTU1MDAxKQoKIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvCnN1bW1hcnkobW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDEpCmBgYAoKYGBge3J9CiMgQWp1c3RhciBlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwKbW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDFfdGVzdCA8LSBsbShWZW50YSB+IENhbnQgKyBDb3N0b19WZW50YSArCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbyArIERlc2N1ZW50b19Qb3JjZW50YWplICsgVGllbXBvLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0ZXN0XzE1NTAwMSkKCiMgVmVyIHJlc3VtZW4gZGVsIG1vZGVsbwpzdW1tYXJ5KG1vZGVsb19yZWdyZXNpb25fMTU1MDAxX3Rlc3QpCmBgYAoKYGBge3J9CiMgQWp1c3RlIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwKbW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDEgPC0gbG0oVmVudGEgfiBDYW50ICsgQ29zdG9fVmVudGEgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gKyBEZXNjdWVudG9fUG9yY2VudGFqZSArIFRpZW1wbywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGF0b3NfMTU1MDAxKQoKIyBQcmVkaWNjaW9uZXMgdXNhbmRvIGVsIG1vZGVsbyBhanVzdGFkbwpwcmVkaWNjaW9uZXNfMTU1MDAxIDwtIHByZWRpY3QobW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDEsIG5ld2RhdGEgPSBkYXRvc18xNTUwMDEpCgojIENhbGN1bGFyIE1BUEUgKE1lYW4gQWJzb2x1dGUgUGVyY2VudGFnZSBFcnJvcikKbWFwZV8xNTUwMDEgPC0gbWVhbihhYnMoKGRhdG9zXzE1NTAwMSRWZW50YSAtIHByZWRpY2Npb25lc18xNTUwMDEpIC8gZGF0b3NfMTU1MDAxJFZlbnRhKSkgKiAxMDAKCgojIENhbGN1bGFyIFJNU0UgKFJvb3QgTWVhbiBTcXVhcmVkIEVycm9yKQpybXNlXzE1NTAwMSA8LSBzcXJ0KG1lYW4oKGRhdG9zXzE1NTAwMSRWZW50YSAtIHByZWRpY2Npb25lc18xNTUwMDEpXjIpKQoKCgojIE1vc3RyYXIgbGFzIG3DqXRyaWNhcwpjYXQoIk1BUEUgZGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbCBwYXJhIDE1NTAwMSAodHJhaW4gZGF0YSk6ICIsIG1hcGVfMTU1MDAxLCAiXG4iKQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbCBwYXJhIDE1NTAwMSAodHJhaW4gZGF0YSk6ICIsIHJtc2VfMTU1MDAxLCAiXG4iKQoKIyBEaWFnbsOzc3RpY28gZGUgcmVzaWR1b3MgZGVsIG1vZGVsbwpwYXIobWZyb3cgPSBjKDIsIDIpKQpwbG90KG1vZGVsb19yZWdyZXNpb25fMTU1MDAxKQpgYGAKCmBgYHtyfQoKYGBgCgoKCmBgYHtyfQojIEFqdXN0ZSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsCm1vZGVsb19yZWdyZXNpb25fMTU1MDAxX3Rlc3QgPC0gbG0oVmVudGEgfiBDYW50ICsgQ29zdG9fVmVudGEgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gKyBEZXNjdWVudG9fUG9yY2VudGFqZSArIFRpZW1wbywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gdGVzdF8xNTUwMDEpCgojIFByZWRpY2Npb25lcyB1c2FuZG8gZWwgbW9kZWxvIGFqdXN0YWRvCnByZWRpY2Npb25lc18xNTUwMDFfdGVzdCA8LSBwcmVkaWN0KG1vZGVsb19yZWdyZXNpb25fMTU1MDAxX3Rlc3QsIG5ld2RhdGEgPSB0ZXN0XzE1NTAwMSkKCiMgQ2FsY3VsYXIgTUFQRSAoTWVhbiBBYnNvbHV0ZSBQZXJjZW50YWdlIEVycm9yKQptYXBlXzE1NTAwMV90ZXN0IDwtIG1lYW4oYWJzKCh0ZXN0XzE1NTAwMSRWZW50YSAtIHByZWRpY2Npb25lc18xNTUwMDFfdGVzdCkgLyB0ZXN0XzE1NTAwMSRWZW50YSkpICogMTAwCgoKIyBDYWxjdWxhciBSTVNFIChSb290IE1lYW4gU3F1YXJlZCBFcnJvcikKcm1zZV8xNTUwMDFfdGVzdCA8LSBzcXJ0KG1lYW4oKHRlc3RfMTU1MDAxJFZlbnRhIC0gcHJlZGljY2lvbmVzXzE1NTAwMV90ZXN0KV4yKSkKCgoKIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMKY2F0KCJNQVBFIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAxNTUwMDEgKHRlc3QgZGF0YSk6ICIsIG1hcGVfMTU1MDAxX3Rlc3QsICJcbiIpCmNhdCgiUk1TRSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgMTU1MDAxICh0ZXN0IGRhdGEpOiAiLCBybXNlXzE1NTAwMV90ZXN0LCAiXG4iKQoKIyBEaWFnbsOzc3RpY28gZGUgcmVzaWR1b3MgZGVsIG1vZGVsbwpwYXIobWZyb3cgPSBjKDIsIDIpKQpwbG90KG1vZGVsb19yZWdyZXNpb25fMTU1MDAxX3Rlc3QpCmBgYAoKYGBge3J9CiMgSW5pY2lhbGl6YXIgZWwgZGF0YS5mcmFtZSBjb3JyZWN0YW1lbnRlIGNvbiB0b2RhcyBsYXMgY29sdW1uYXMgZXNwZXJhZGFzCmlmICghZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgewogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLAogICAgTUFQRSA9IG51bWVyaWMoKSwKICAgIFJNU0UgPSBudW1lcmljKCksCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCn0KCiMgQXNlZ8O6cmF0ZSBkZSBxdWUgbGFzIG3DqXRyaWNhcyBleGlzdGVuIHkgc29uIG51bcOpcmljYXMKcHJpbnQobWFwZV8xNTUwMDFfdGVzdCkKcHJpbnQocm1zZV8xNTUwMDFfdGVzdCkKCiMgQ3JlYXIgbGEgbnVldmEgZmlsYQpudWV2YV9maWxhIDwtIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMTU1MDAxIiwKICBNb2RlbG8gPSAiUmVncmVzacOzbiBMaW5lYWwiLAogIE1BUEUgPSBtYXBlXzE1NTAwMV90ZXN0LAogIFJNU0UgPSBybXNlXzE1NTAwMV90ZXN0LAogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQopCgojIENvbmZpcm1hciBxdWUgbGFzIGNvbHVtbmFzIGNvaW5jaWRlbgpwcmludChuYW1lcyhtZXRyaWNhc19jb21wYXJhdGl2YXMpKQpwcmludChuYW1lcyhudWV2YV9maWxhKSkKCiMgQWdyZWdhciBsYSBudWV2YSBmaWxhCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIG51ZXZhX2ZpbGEpCgojIFZlcmlmaWNhciByZXN1bHRhZG8KcHJpbnQobWV0cmljYXNfY29tcGFyYXRpdmFzKQoKYGBgCgpgYGB7cn0KCmBgYAoKCiMjIFBST0RVQ1RPIDM5Mjk3ODgKCmBgYHtyfQojIEZpbHRyYXIgc29sbyBsb3MgZGF0b3MgcGFyYSBlbCBwcm9kdWN0byAzOTI5Nzg4CmRhdG9zXzM5Mjk3ODggPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDM5Mjk3ODgpICU+JQogIHNlbGVjdChWZW50YSwgQ2FudCwgQ29zdG9fVmVudGEsCiAgICAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbywgRGVzY3VlbnRvX1BvcmNlbnRhamUsIFRyeF9GZWNoYSkgJT4lCiAgbmEub21pdCgpICAjIEVsaW1pbmFyIGZpbGFzIGNvbiB2YWxvcmVzIE5BCgojIENyZWFyIHVuYSB2YXJpYWJsZSBkZSB0aWVtcG8gY29udGludWEgYmFzYWRhIGVuIGxhIGZlY2hhCmRhdG9zXzM5Mjk3ODggPC0gZGF0b3NfMzkyOTc4OCAlPiUKICBtdXRhdGUoRmVjaGEgPSBhcy5EYXRlKGZsb29yX2RhdGUoVHJ4X0ZlY2hhLCAibW9udGgiKSksICAgIyBBc2Vnw7pyYXRlIGRlIHF1ZSBsYSBmZWNoYSBlc3TDqSBlbiBmb3JtYXRvIERhdGUKICAgICAgICAgVGllbXBvID0gYXMubnVtZXJpYyhGZWNoYSAtIG1pbihGZWNoYSkpIC8gKDMwICogMjQgKiA2MCAqIDYwKSkgICMgVGllbXBvIGVuIG1lc2VzIChhanVzdGFkbyBwb3IgZMOtYXMpCgojIFZlcmlmaWNhciBsYXMgcHJpbWVyYXMgZmlsYXMgcGFyYSBhc2VndXJhcm5vcyBkZSBxdWUgbGEgdmFyaWFibGUgZGUgdGllbXBvIGVzdMOpIGJpZW4gY3JlYWRhCmhlYWQoZGF0b3NfMzkyOTc4OCkKCmBgYAoKYGBge3J9CiMgRmlsdHJhciBzb2xvIGxvcyBkYXRvcyBwYXJhIGVsIHByb2R1Y3RvIDE1NTAwMQp0ZXN0XzM5Mjk3ODggPC0gdGVzdCAlPiUKICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAzOTI5Nzg4KSAlPiUKICBzZWxlY3QoVmVudGEsIENhbnQsIENvc3RvX1ZlbnRhLAogICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8sIERlc2N1ZW50b19Qb3JjZW50YWplLCBUcnhfRmVjaGEpICU+JQogIG5hLm9taXQoKSAlPiUKICBtdXRhdGUoCiAgICBGZWNoYSA9IGFzLkRhdGUoZmxvb3JfZGF0ZShUcnhfRmVjaGEsICJtb250aCIpKSwKICAgIFRpZW1wbyA9IGFzLm51bWVyaWMoRmVjaGEgLSBtaW4oRmVjaGEpKSAvICgzMCAqIDI0ICogNjAgKiA2MCkKICApCgojIFZlcmlmaWNhcgpoZWFkKHRlc3RfMzkyOTc4OCkKYGBgCgoKYGBge3J9CiMgQWp1c3RhciBlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwKbW9kZWxvX3JlZ3Jlc2lvbl8zOTI5Nzg4IDwtIGxtKFZlbnRhIH4gQ2FudCArIENvc3RvX1ZlbnRhICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvICsgRGVzY3VlbnRvX1BvcmNlbnRhamUgKyBUaWVtcG8sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkYXRvc18zOTI5Nzg4KQoKIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvCnN1bW1hcnkobW9kZWxvX3JlZ3Jlc2lvbl8zOTI5Nzg4KQpgYGAKCmBgYHtyfQojIEFqdXN0YXIgZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsCm1vZGVsb19yZWdyZXNpb25fMzkyOTc4OF90ZXN0IDwtIGxtKFZlbnRhIH4gQ2FudCArIENvc3RvX1ZlbnRhICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvICsgRGVzY3VlbnRvX1BvcmNlbnRhamUgKyBUaWVtcG8sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRlc3RfMzkyOTc4OCkKCiMgVmVyIHJlc3VtZW4gZGVsIG1vZGVsbwpzdW1tYXJ5KG1vZGVsb19yZWdyZXNpb25fMzkyOTc4OF90ZXN0KQpgYGAKCmBgYHtyfQojIFByZWRpY2Npb25lcyB1c2FuZG8gZWwgbW9kZWxvIGFqdXN0YWRvCnByZWRpY2Npb25lc18zOTI5Nzg4IDwtIHByZWRpY3QobW9kZWxvX3JlZ3Jlc2lvbl8zOTI5Nzg4LCBuZXdkYXRhID0gZGF0b3NfMzkyOTc4OCkKCiMgQ2FsY3VsYXIgTUFQRSAoTWVhbiBBYnNvbHV0ZSBQZXJjZW50YWdlIEVycm9yKQojIEHDsWFkaW1vcyBwcm90ZWNjacOzbiBjb250cmEgZGl2aXNpw7NuIHBvciBjZXJvCm1hcGVfMzkyOTc4OCA8LSBtZWFuKGFicygoZGF0b3NfMzkyOTc4OCRWZW50YSAtIHByZWRpY2Npb25lc18zOTI5Nzg4KSAvIHBtYXgoZGF0b3NfMzkyOTc4OCRWZW50YSwgMC4wMSkpKSAqIDEwMAoKIyBDYWxjdWxhciBNU0UgKE1lYW4gU3F1YXJlZCBFcnJvcikKcm1zZV8zOTI5Nzg4IDwtIHNxcnQobWVhbigoZGF0b3NfMzkyOTc4OCRWZW50YSAtIHByZWRpY2Npb25lc18zOTI5Nzg4KV4yKSkKCgojIE1vc3RyYXIgbGFzIG3DqXRyaWNhcwpjYXQoIk1BUEUgZGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbCBwYXJhIDM5Mjk3ODggKHRyYWluIGRhdGEpOiAiLCBtYXBlXzM5Mjk3ODgsICJcbiIpCmNhdCgiUk1TRSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgMzkyOTc4OCAodHJhaW4gZGF0YSk6ICIsIHJtc2VfMzkyOTc4OCwgIlxuIikKCiMgRGlhZ27Ds3N0aWNvIGRlIHJlc2lkdW9zIGRlbCBtb2RlbG8KcGFyKG1mcm93ID0gYygyLCAyKSkKcGxvdChtb2RlbG9fcmVncmVzaW9uXzM5Mjk3ODgpCmBgYApgYGB7cn0KIyBHdWFyZGFyIG3DqXRyaWNhcyBkZSBSZWdyZXNpw7NuIExpbmVhbCBwYXJhIHByb2R1Y3RvIDE1NTAwMQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgewogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLAogICAgTUFQRSA9IG51bWVyaWMoKSwKICAgIFJNU0UgPSBudW1lcmljKCksCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCn0KCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMzkyOTc4OCIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bwogIE1vZGVsbyA9ICJSZWdyZXNpw7NuIExpbmVhbCIsCiAgTUFQRSA9IG1hcGVfMzkyOTc4OCwKICBSTVNFID0gcm1zZV8zOTI5Nzg4CikpCmBgYAoKYGBge3J9CiMgQWp1c3RlIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwKbW9kZWxvX3JlZ3Jlc2lvbl8zOTI5Nzg4X3Rlc3QgPC0gbG0oVmVudGEgfiBDYW50ICsgQ29zdG9fVmVudGEgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gKyBEZXNjdWVudG9fUG9yY2VudGFqZSArIFRpZW1wbywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gdGVzdF8zOTI5Nzg4KQoKIyBQcmVkaWNjaW9uZXMgdXNhbmRvIGVsIG1vZGVsbyBhanVzdGFkbwpwcmVkaWNjaW9uZXNfMzkyOTc4OF90ZXN0IDwtIHByZWRpY3QobW9kZWxvX3JlZ3Jlc2lvbl8zOTI5Nzg4X3Rlc3QsIG5ld2RhdGEgPSB0ZXN0XzM5Mjk3ODgpCgojIENhbGN1bGFyIE1BUEUgKE1lYW4gQWJzb2x1dGUgUGVyY2VudGFnZSBFcnJvcikKbWFwZV8zOTI5Nzg4X3Rlc3QgPC0gbWVhbihhYnMoKHRlc3RfMzkyOTc4OCRWZW50YSAtIHByZWRpY2Npb25lc18zOTI5Nzg4X3Rlc3QpIC8gdGVzdF8zOTI5Nzg4JFZlbnRhKSkgKiAxMDAKCgojIENhbGN1bGFyIFJNU0UgKFJvb3QgTWVhbiBTcXVhcmVkIEVycm9yKQpybXNlXzM5Mjk3ODhfdGVzdCA8LSBzcXJ0KG1lYW4oKHRlc3RfMzkyOTc4OCRWZW50YSAtIHByZWRpY2Npb25lc18zOTI5Nzg4X3Rlc3QpXjIpKQoKCgojIE1vc3RyYXIgbGFzIG3DqXRyaWNhcwpjYXQoIk1BUEUgZGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbCBwYXJhIDM5Mjk3ODggKHRlc3QgZGF0YSk6ICIsIG1hcGVfMzkyOTc4OF90ZXN0LCAiXG4iKQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbCBwYXJhIDM5Mjk3ODggKHRlc3QgZGF0YSk6ICIsIHJtc2VfMzkyOTc4OF90ZXN0LCAiXG4iKQoKIyBEaWFnbsOzc3RpY28gZGUgcmVzaWR1b3MgZGVsIG1vZGVsbwpwYXIobWZyb3cgPSBjKDIsIDIpKQpwbG90KG1vZGVsb19yZWdyZXNpb25fMzkyOTc4OF90ZXN0KQpgYGAKCgoKIyMgUFJPRFVDVE8gMzkwNDE1MgoKYGBge3J9CiMgRmlsdHJhciBzb2xvIGxvcyBkYXRvcyBwYXJhIGVsIHByb2R1Y3RvIDM5MDQxNTIKZGF0b3NfMzkwNDE1MiA8LSBkYXRvc19maWx0cmFkb3MgJT4lCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gMzkwNDE1MikgJT4lCiAgc2VsZWN0KFZlbnRhLCBDYW50LCBDb3N0b19WZW50YSwKICAgICAgICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCBEZXNjdWVudG9fUG9yY2VudGFqZSwgVHJ4X0ZlY2hhKSAlPiUKICBuYS5vbWl0KCkgICMgRWxpbWluYXIgZmlsYXMgY29uIHZhbG9yZXMgTkEKIyBDcmVhciB1bmEgdmFyaWFibGUgZGUgdGllbXBvIGNvbnRpbnVhIGJhc2FkYSBlbiBsYSBmZWNoYQpkYXRvc18zOTA0MTUyIDwtIGRhdG9zXzM5MDQxNTIgJT4lCiAgbXV0YXRlKEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpLCAgICMgQXNlZ8O6cmF0ZSBkZSBxdWUgbGEgZmVjaGEgZXN0w6kgZW4gZm9ybWF0byBEYXRlCiAgICAgICAgIFRpZW1wbyA9IGFzLm51bWVyaWMoRmVjaGEgLSBtaW4oRmVjaGEpKSAvICgzMCAqIDI0ICogNjAgKiA2MCkpICAjIFRpZW1wbyBlbiBtZXNlcyAoYWp1c3RhZG8gcG9yIGTDrWFzKQoKIyBWZXJpZmljYXIgbGFzIHByaW1lcmFzIGZpbGFzIHBhcmEgYXNlZ3VyYXJub3MgZGUgcXVlIGxhIHZhcmlhYmxlIGRlIHRpZW1wbyBlc3TDqSBiaWVuIGNyZWFkYQpoZWFkKGRhdG9zXzM5MDQxNTIpCgpgYGAKCmBgYHtyfQojIEZpbHRyYXIgc29sbyBsb3MgZGF0b3MgcGFyYSBlbCBwcm9kdWN0byAxNTUwMDEKdGVzdF8zOTA0MTUyIDwtIHRlc3QgJT4lCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gMzkwNDE1MikgJT4lCiAgc2VsZWN0KFZlbnRhLCBDYW50LCBDb3N0b19WZW50YSwKICAgICAgICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCBEZXNjdWVudG9fUG9yY2VudGFqZSwgVHJ4X0ZlY2hhKSAlPiUKICBuYS5vbWl0KCkgJT4lCiAgbXV0YXRlKAogICAgRmVjaGEgPSBhcy5EYXRlKGZsb29yX2RhdGUoVHJ4X0ZlY2hhLCAibW9udGgiKSksCiAgICBUaWVtcG8gPSBhcy5udW1lcmljKEZlY2hhIC0gbWluKEZlY2hhKSkgLyAoMzAgKiAyNCAqIDYwICogNjApCiAgKQoKIyBWZXJpZmljYXIKaGVhZCh0ZXN0XzM5MDQxNTIpCmBgYAoKCmBgYHtyfQojIEFqdXN0YXIgZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsCm1vZGVsb19yZWdyZXNpb25fMzkwNDE1MiA8LSBsbShWZW50YSB+IENhbnQgKyBDb3N0b19WZW50YSArCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbyArIERlc2N1ZW50b19Qb3JjZW50YWplICsgVGllbXBvLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkYXRvc18zOTA0MTUyKQoKIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvCnN1bW1hcnkobW9kZWxvX3JlZ3Jlc2lvbl8zOTA0MTUyKQpgYGAKCmBgYHtyfQojIEFqdXN0YXIgZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsCm1vZGVsb19yZWdyZXNpb25fMzkwNDE1Ml90ZXN0IDwtIGxtKFZlbnRhIH4gQ2FudCArIENvc3RvX1ZlbnRhICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvICsgRGVzY3VlbnRvX1BvcmNlbnRhamUgKyBUaWVtcG8sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRlc3RfMzkwNDE1MikKCiMgVmVyIHJlc3VtZW4gZGVsIG1vZGVsbwpzdW1tYXJ5KG1vZGVsb19yZWdyZXNpb25fMzkwNDE1Ml90ZXN0KQpgYGAKCgpgYGB7cn0KIyBQcmVkaWNjaW9uZXMgdXNhbmRvIGVsIG1vZGVsbyBhanVzdGFkbwpwcmVkaWNjaW9uZXNfMzkwNDE1MiA8LSBwcmVkaWN0KG1vZGVsb19yZWdyZXNpb25fMzkwNDE1MiwgbmV3ZGF0YSA9IGRhdG9zXzM5MDQxNTIpCgojIENhbGN1bGFyIE1BUEUgKE1lYW4gQWJzb2x1dGUgUGVyY2VudGFnZSBFcnJvcikKIyBBw7FhZGltb3MgcHJvdGVjY2nDs24gY29udHJhIGRpdmlzacOzbiBwb3IgY2VybwptYXBlXzM5MDQxNTIgPC0gbWVhbihhYnMoKGRhdG9zXzM5MDQxNTIkVmVudGEgLSBwcmVkaWNjaW9uZXNfMzkwNDE1MikgLyBwbWF4KGRhdG9zXzM5MDQxNTIkVmVudGEsIDAuMDEpKSkgKiAxMDAKCiMgQ2FsY3VsYXIgTVNFIChNZWFuIFNxdWFyZWQgRXJyb3IpCnJtc2VfMzkwNDE1MiA8LSBzcXJ0KG1lYW4oKGRhdG9zXzM5MDQxNTIkVmVudGEgLSBwcmVkaWNjaW9uZXNfMzkwNDE1MileMikpCgoKIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMKY2F0KCJNQVBFIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAzOTA0MTUyICh0cmFpbiBkYXRhKTogIiwgbWFwZV8zOTA0MTUyLCAiXG4iKQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbCBwYXJhIDM5MDQxNTIgKHRyYWluIGRhdGEpOiAiLCBybXNlXzM5MDQxNTIsICJcbiIpCgojIERpYWduw7NzdGljbyBkZSByZXNpZHVvcyBkZWwgbW9kZWxvCnBhcihtZnJvdyA9IGMoMiwgMikpCnBsb3QobW9kZWxvX3JlZ3Jlc2lvbl8zOTA0MTUyKQpgYGAKYGBge3J9CiMgR3VhcmRhciBtw6l0cmljYXMgZGUgUmVncmVzacOzbiBMaW5lYWwgcGFyYSBwcm9kdWN0byAxNTUwMDEKaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1BUEUgPSBudW1lcmljKCksCiAgICBSTVNFID0gbnVtZXJpYygpLAogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKQp9CgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjM5MDQxNTIiLCAgIyBDYW1iaWEgZXN0ZSBJRCBwYXJhIGNhZGEgcHJvZHVjdG8KICBNb2RlbG8gPSAiUmVncmVzacOzbiBMaW5lYWwiLAogIE1BUEUgPSBtYXBlXzM5MDQxNTIsCiAgUk1TRSA9IHJtc2VfMzkwNDE1MgopKQpgYGAKCmBgYHtyfQojIEFqdXN0ZSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsCm1vZGVsb19yZWdyZXNpb25fMzkwNDE1Ml90ZXN0IDwtIGxtKFZlbnRhIH4gQ2FudCArIENvc3RvX1ZlbnRhICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvICsgRGVzY3VlbnRvX1BvcmNlbnRhamUgKyBUaWVtcG8sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRlc3RfMzkwNDE1MikKCiMgUHJlZGljY2lvbmVzIHVzYW5kbyBlbCBtb2RlbG8gYWp1c3RhZG8KcHJlZGljY2lvbmVzXzM5MDQxNTJfdGVzdCA8LSBwcmVkaWN0KG1vZGVsb19yZWdyZXNpb25fMzkwNDE1Ml90ZXN0LCBuZXdkYXRhID0gdGVzdF8zOTA0MTUyKQoKIyBDYWxjdWxhciBNQVBFIChNZWFuIEFic29sdXRlIFBlcmNlbnRhZ2UgRXJyb3IpCm1hcGVfMzkwNDE1Ml90ZXN0IDwtIG1lYW4oYWJzKCh0ZXN0XzM5MDQxNTIkVmVudGEgLSBwcmVkaWNjaW9uZXNfMzkwNDE1Ml90ZXN0KSAvIHRlc3RfMzkwNDE1MiRWZW50YSkpICogMTAwCgoKIyBDYWxjdWxhciBSTVNFIChSb290IE1lYW4gU3F1YXJlZCBFcnJvcikKcm1zZV8zOTA0MTUyX3Rlc3QgPC0gc3FydChtZWFuKCh0ZXN0XzM5MDQxNTIkVmVudGEgLSBwcmVkaWNjaW9uZXNfMzkwNDE1Ml90ZXN0KV4yKSkKCgoKIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMKY2F0KCJNQVBFIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAzOTA0MTUyICh0ZXN0IGRhdGEpOiAiLCBtYXBlXzM5MDQxNTJfdGVzdCwgIlxuIikKY2F0KCJSTVNFIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAzOTA0MTUyICh0ZXN0IGRhdGEpOiAiLCBybXNlXzM5MDQxNTJfdGVzdCwgIlxuIikKCiMgRGlhZ27Ds3N0aWNvIGRlIHJlc2lkdW9zIGRlbCBtb2RlbG8KcGFyKG1mcm93ID0gYygyLCAyKSkKcGxvdChtb2RlbG9fcmVncmVzaW9uXzM5MDQxNTJfdGVzdCkKYGBgCgoKIyMgUFJPRFVDVE8gMTU1MDAyCgpgYGB7cn0KIyBGaWx0cmFyIHNvbG8gbG9zIGRhdG9zIHBhcmEgZWwgcHJvZHVjdG8gMTU1MDAyCmRhdG9zXzE1NTAwMiA8LSBkYXRvc19maWx0cmFkb3MgJT4lCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gMTU1MDAyKSAlPiUKICBzZWxlY3QoVmVudGEsIENhbnQsIENvc3RvX1ZlbnRhLAogICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8sIERlc2N1ZW50b19Qb3JjZW50YWplLCBUcnhfRmVjaGEpICU+JQogIG5hLm9taXQoKSAgIyBFbGltaW5hciBmaWxhcyBjb24gdmFsb3JlcyBOQQoKIyBDcmVhciB1bmEgdmFyaWFibGUgZGUgdGllbXBvIGNvbnRpbnVhIGJhc2FkYSBlbiBsYSBmZWNoYQpkYXRvc18xNTUwMDIgPC0gZGF0b3NfMTU1MDAyICU+JQogIG11dGF0ZShGZWNoYSA9IGFzLkRhdGUoZmxvb3JfZGF0ZShUcnhfRmVjaGEsICJtb250aCIpKSwgICAjIEFzZWfDunJhdGUgZGUgcXVlIGxhIGZlY2hhIGVzdMOpIGVuIGZvcm1hdG8gRGF0ZQogICAgICAgICBUaWVtcG8gPSBhcy5udW1lcmljKEZlY2hhIC0gbWluKEZlY2hhKSkgLyAoMzAgKiAyNCAqIDYwICogNjApKSAgIyBUaWVtcG8gZW4gbWVzZXMgKGFqdXN0YWRvIHBvciBkw61hcykKCiMgVmVyaWZpY2FyIGxhcyBwcmltZXJhcyBmaWxhcyBwYXJhIGFzZWd1cmFybm9zIGRlIHF1ZSBsYSB2YXJpYWJsZSBkZSB0aWVtcG8gZXN0w6kgYmllbiBjcmVhZGEKaGVhZChkYXRvc18xNTUwMDIpCgpgYGAKCmBgYHtyfQojIEZpbHRyYXIgc29sbyBsb3MgZGF0b3MgcGFyYSBlbCBwcm9kdWN0byAxNTUwMDEKdGVzdF8xNTUwMDIgPC0gdGVzdCAlPiUKICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAxNTUwMDIpICU+JQogIHNlbGVjdChWZW50YSwgQ2FudCwgQ29zdG9fVmVudGEsCiAgICAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbywgRGVzY3VlbnRvX1BvcmNlbnRhamUsIFRyeF9GZWNoYSkgJT4lCiAgbmEub21pdCgpICU+JQogIG11dGF0ZSgKICAgIEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpLAogICAgVGllbXBvID0gYXMubnVtZXJpYyhGZWNoYSAtIG1pbihGZWNoYSkpIC8gKDMwICogMjQgKiA2MCAqIDYwKQogICkKCiMgVmVyaWZpY2FyCmhlYWQodGVzdF8xNTUwMDIpCmBgYAoKCmBgYHtyfQojIEFqdXN0YXIgZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsCm1vZGVsb19yZWdyZXNpb25fMTU1MDAyIDwtIGxtKFZlbnRhIH4gQ2FudCArIENvc3RvX1ZlbnRhICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvICsgRGVzY3VlbnRvX1BvcmNlbnRhamUgKyBUaWVtcG8sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGRhdG9zXzE1NTAwMikKCiMgVmVyIHJlc3VtZW4gZGVsIG1vZGVsbwpzdW1tYXJ5KG1vZGVsb19yZWdyZXNpb25fMTU1MDAyKQpgYGAKCmBgYHtyfQojIEFqdXN0YXIgZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsCm1vZGVsb19yZWdyZXNpb25fMTU1MDAyX3Rlc3QgPC0gbG0oVmVudGEgfiBDYW50ICsgQ29zdG9fVmVudGEgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gKyBEZXNjdWVudG9fUG9yY2VudGFqZSArIFRpZW1wbywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gdGVzdF8xNTUwMDIpCgojIFZlciByZXN1bWVuIGRlbCBtb2RlbG8Kc3VtbWFyeShtb2RlbG9fcmVncmVzaW9uXzE1NTAwMl90ZXN0KQpgYGAKCgpgYGB7cn0KIyBQcmVkaWNjaW9uZXMgdXNhbmRvIGVsIG1vZGVsbyBhanVzdGFkbwpwcmVkaWNjaW9uZXNfMTU1MDAyIDwtIHByZWRpY3QobW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDIsIG5ld2RhdGEgPSBkYXRvc18xNTUwMDIpCgojIENhbGN1bGFyIE1BUEUgKE1lYW4gQWJzb2x1dGUgUGVyY2VudGFnZSBFcnJvcikKIyBBw7FhZGltb3MgcHJvdGVjY2nDs24gY29udHJhIGRpdmlzacOzbiBwb3IgY2VybwptYXBlXzE1NTAwMiA8LSBtZWFuKGFicygoZGF0b3NfMTU1MDAyJFZlbnRhIC0gcHJlZGljY2lvbmVzXzE1NTAwMikgLyBwbWF4KGRhdG9zXzE1NTAwMiRWZW50YSwgMC4wMSkpKSAqIDEwMAoKIyBDYWxjdWxhciBSTVNFIChSb290IE1lYW4gU3F1YXJlZCBFcnJvcikKcm1zZV8xNTUwMDIgPC0gc3FydChtZWFuKChkYXRvc18xNTUwMDIkVmVudGEgLSBwcmVkaWNjaW9uZXNfMTU1MDAyKV4yKSkKCgojIE1vc3RyYXIgbGFzIG3DqXRyaWNhcwpjYXQoIk1BUEUgZGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbCBwYXJhIDE1NTAwMiAodHJhaW4gZGF0YSk6ICIsIG1hcGVfMTU1MDAyLCAiXG4iKQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbCBwYXJhIDE1NTAwMiAodHJhaW4gZGF0YSk6ICIsIHJtc2VfMTU1MDAyLCAiXG4iKQoKIyBEaWFnbsOzc3RpY28gZGUgcmVzaWR1b3MgZGVsIG1vZGVsbwpwYXIobWZyb3cgPSBjKDIsIDIpKQpwbG90KG1vZGVsb19yZWdyZXNpb25fMTU1MDAyKQpgYGAKCmBgYHtyfQojIEFqdXN0ZSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsCm1vZGVsb19yZWdyZXNpb25fMTU1MDAyX3Rlc3QgPC0gbG0oVmVudGEgfiBDYW50ICsgQ29zdG9fVmVudGEgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gKyBEZXNjdWVudG9fUG9yY2VudGFqZSArIFRpZW1wbywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gdGVzdF8xNTUwMDIpCgojIFByZWRpY2Npb25lcyB1c2FuZG8gZWwgbW9kZWxvIGFqdXN0YWRvCnByZWRpY2Npb25lc18xNTUwMDJfdGVzdCA8LSBwcmVkaWN0KG1vZGVsb19yZWdyZXNpb25fMTU1MDAyX3Rlc3QsIG5ld2RhdGEgPSB0ZXN0XzE1NTAwMikKCiMgQ2FsY3VsYXIgTUFQRSAoTWVhbiBBYnNvbHV0ZSBQZXJjZW50YWdlIEVycm9yKQptYXBlXzE1NTAwMl90ZXN0IDwtIG1lYW4oYWJzKCh0ZXN0XzE1NTAwMiRWZW50YSAtIHByZWRpY2Npb25lc18xNTUwMDJfdGVzdCkgLyB0ZXN0XzE1NTAwMiRWZW50YSkpICogMTAwCgoKIyBDYWxjdWxhciBSTVNFIChSb290IE1lYW4gU3F1YXJlZCBFcnJvcikKcm1zZV8xNTUwMDJfdGVzdCA8LSBzcXJ0KG1lYW4oKHRlc3RfMTU1MDAyJFZlbnRhIC0gcHJlZGljY2lvbmVzXzE1NTAwMl90ZXN0KV4yKSkKCgoKIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMKY2F0KCJNQVBFIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAxNTUwMDIgKHRlc3QgZGF0YSk6ICIsIG1hcGVfMTU1MDAyX3Rlc3QsICJcbiIpCmNhdCgiUk1TRSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgMTU1MDAyICh0ZXN0IGRhdGEpOiAiLCBybXNlXzE1NTAwMl90ZXN0LCAiXG4iKQoKIyBEaWFnbsOzc3RpY28gZGUgcmVzaWR1b3MgZGVsIG1vZGVsbwpwYXIobWZyb3cgPSBjKDIsIDIpKQpwbG90KG1vZGVsb19yZWdyZXNpb25fMTU1MDAyX3Rlc3QpCmBgYAoKCmBgYHtyfQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFJlZ3Jlc2nDs24gTGluZWFsIHBhcmEgcHJvZHVjdG8gMTU1MDAxCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7CiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLAogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksCiAgICBNQVBFID0gbnVtZXJpYygpLAogICAgUk1TRSA9IG51bWVyaWMoKSwKICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQogICkKfQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIxNTUwMDIiLCAgIyBDYW1iaWEgZXN0ZSBJRCBwYXJhIGNhZGEgcHJvZHVjdG8KICBNb2RlbG8gPSAiUmVncmVzacOzbiBMaW5lYWwiLAogIE1BUEUgPSBtYXBlXzE1NTAwMiwKICBSTVNFID0gcm1zZV8xNTUwMDIKICApKQpgYGAKCgojIyBQUk9EVUNUTyAzNjc4MDU1CmBgYHtyfQojIEZpbHRyYXIgc29sbyBsb3MgZGF0b3MgcGFyYSBlbCBwcm9kdWN0byAzNjc4MDU1CmRhdG9zXzM2NzgwNTUgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDM2NzgwNTUpICU+JQogIHNlbGVjdChWZW50YSwgQ2FudCwgQ29zdG9fVmVudGEsCiAgICAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbywgRGVzY3VlbnRvX1BvcmNlbnRhamUsIFRyeF9GZWNoYSkgJT4lCiAgbmEub21pdCgpICAjIEVsaW1pbmFyIGZpbGFzIGNvbiB2YWxvcmVzIE5BCgojIENyZWFyIHVuYSB2YXJpYWJsZSBkZSB0aWVtcG8gY29udGludWEgYmFzYWRhIGVuIGxhIGZlY2hhCmRhdG9zXzM2NzgwNTUgPC0gZGF0b3NfMzY3ODA1NSAlPiUKICBtdXRhdGUoRmVjaGEgPSBhcy5EYXRlKGZsb29yX2RhdGUoVHJ4X0ZlY2hhLCAibW9udGgiKSksICAgIyBBc2Vnw7pyYXRlIGRlIHF1ZSBsYSBmZWNoYSBlc3TDqSBlbiBmb3JtYXRvIERhdGUKICAgICAgICAgVGllbXBvID0gYXMubnVtZXJpYyhGZWNoYSAtIG1pbihGZWNoYSkpIC8gKDMwICogMjQgKiA2MCAqIDYwKSkgICMgVGllbXBvIGVuIG1lc2VzIChhanVzdGFkbyBwb3IgZMOtYXMpCgojIFZlcmlmaWNhciBsYXMgcHJpbWVyYXMgZmlsYXMgcGFyYSBhc2VndXJhcm5vcyBkZSBxdWUgbGEgdmFyaWFibGUgZGUgdGllbXBvIGVzdMOpIGJpZW4gY3JlYWRhCmhlYWQoZGF0b3NfMzY3ODA1NSkKCmBgYAoKYGBge3J9CiMgRmlsdHJhciBzb2xvIGxvcyBkYXRvcyBwYXJhIGVsIHByb2R1Y3RvIDE1NTAwMQp0ZXN0XzM2NzgwNTUgPC0gdGVzdCAlPiUKICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAzNjc4MDU1KSAlPiUKICBzZWxlY3QoVmVudGEsIENhbnQsIENvc3RvX1ZlbnRhLAogICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8sIERlc2N1ZW50b19Qb3JjZW50YWplLCBUcnhfRmVjaGEpICU+JQogIG5hLm9taXQoKSAlPiUKICBtdXRhdGUoCiAgICBGZWNoYSA9IGFzLkRhdGUoZmxvb3JfZGF0ZShUcnhfRmVjaGEsICJtb250aCIpKSwKICAgIFRpZW1wbyA9IGFzLm51bWVyaWMoRmVjaGEgLSBtaW4oRmVjaGEpKSAvICgzMCAqIDI0ICogNjAgKiA2MCkKICApCgojIFZlcmlmaWNhcgpoZWFkKHRlc3RfMzY3ODA1NSkKYGBgCgoKYGBge3J9CiMgQWp1c3RhciBlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwKbW9kZWxvX3JlZ3Jlc2lvbl8zNjc4MDU1IDwtIGxtKFZlbnRhIH4gQ2FudCArIENvc3RvX1ZlbnRhICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvICsgRGVzY3VlbnRvX1BvcmNlbnRhamUgKyBUaWVtcG8sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGRhdG9zXzM2NzgwNTUpCgojIFZlciByZXN1bWVuIGRlbCBtb2RlbG8Kc3VtbWFyeShtb2RlbG9fcmVncmVzaW9uXzM2NzgwNTUpCmBgYAoKYGBge3J9CiMgQWp1c3RhciBlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwKbW9kZWxvX3JlZ3Jlc2lvbl8zNjc4MDU1X3Rlc3QgPC0gbG0oVmVudGEgfiBDYW50ICsgQ29zdG9fVmVudGEgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gKyBEZXNjdWVudG9fUG9yY2VudGFqZSArIFRpZW1wbywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gdGVzdF8zNjc4MDU1KQoKIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvCnN1bW1hcnkobW9kZWxvX3JlZ3Jlc2lvbl8zNjc4MDU1X3Rlc3QpCmBgYAoKCmBgYHtyfQojUHJlZGljY2lvbmVzIHVzYW5kbyBlbCBtb2RlbG8gYWp1c3RhZG8KcHJlZGljY2lvbmVzXzM2NzgwNTUgPC0gcHJlZGljdChtb2RlbG9fcmVncmVzaW9uXzM2NzgwNTUsIG5ld2RhdGEgPSBkYXRvc18zNjc4MDU1KQojIENhbGN1bGFyIE1BUEUgKE1lYW4gQWJzb2x1dGUgUGVyY2VudGFnZSBFcnJvcikKIyBBw7FhZGltb3MgcHJvdGVjY2nDs24gY29udHJhIGRpdmlzacOzbiBwb3IgY2VybwptYXBlXzM2NzgwNTUgPC0gbWVhbihhYnMoKGRhdG9zXzM2NzgwNTUkVmVudGEgLSBwcmVkaWNjaW9uZXNfMzY3ODA1NSkgLyBwbWF4KGRhdG9zXzM2NzgwNTUkVmVudGEsIDAuMDEpKSkgKiAxMDAKCiMgQ2FsY3VsYXIgUk1TRSAoUm9vdCBNZWFuIFNxdWFyZWQgRXJyb3IpCgpybXNlXzM2NzgwNTUgPC0gbWVhbigoZGF0b3NfMzY3ODA1NSRWZW50YSAtIHByZWRpY2Npb25lc18zNjc4MDU1KV4yKQoKIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMKY2F0KCJNQVBFIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAzNjc4MDU1ICh0cmFpbiBkYXRhKTogIiwgbWFwZV8zNjc4MDU1LCAiXG4iKQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbCBwYXJhIDM2NzgwNTUgKHRyYWluIGRhdGEpOiAiLCBybXNlXzM2NzgwNTUsICJcbiIpCgojIERpYWduw7NzdGljbyBkZSByZXNpZHVvcyBkZWwgbW9kZWxvCnBhcihtZnJvdyA9IGMoMiwgMikpCnBsb3QobW9kZWxvX3JlZ3Jlc2lvbl8zNjc4MDU1KQpgYGAKCmBgYHtyfQojIEFqdXN0ZSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsCm1vZGVsb19yZWdyZXNpb25fMzY3ODA1NV90ZXN0IDwtIGxtKFZlbnRhIH4gQ2FudCArIENvc3RvX1ZlbnRhICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvICsgRGVzY3VlbnRvX1BvcmNlbnRhamUgKyBUaWVtcG8sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRlc3RfMzY3ODA1NSkKCiMgUHJlZGljY2lvbmVzIHVzYW5kbyBlbCBtb2RlbG8gYWp1c3RhZG8KcHJlZGljY2lvbmVzXzM2NzgwNTVfdGVzdCA8LSBwcmVkaWN0KG1vZGVsb19yZWdyZXNpb25fMzY3ODA1NV90ZXN0LCBuZXdkYXRhID0gdGVzdF8zNjc4MDU1KQoKIyBDYWxjdWxhciBNQVBFIChNZWFuIEFic29sdXRlIFBlcmNlbnRhZ2UgRXJyb3IpCm1hcGVfMzY3ODA1NV90ZXN0IDwtIG1lYW4oYWJzKCh0ZXN0XzM2NzgwNTUkVmVudGEgLSBwcmVkaWNjaW9uZXNfMzY3ODA1NV90ZXN0KSAvIHRlc3RfMzY3ODA1NSRWZW50YSkpICogMTAwCgoKIyBDYWxjdWxhciBSTVNFIChSb290IE1lYW4gU3F1YXJlZCBFcnJvcikKcm1zZV8zNjc4MDU1X3Rlc3QgPC0gc3FydChtZWFuKCh0ZXN0XzM2NzgwNTUkVmVudGEgLSBwcmVkaWNjaW9uZXNfMzY3ODA1NV90ZXN0KV4yKSkKCgoKIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMKY2F0KCJNQVBFIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAzNjc4MDU1ICh0ZXN0IGRhdGEpOiAiLCBtYXBlXzM2NzgwNTVfdGVzdCwgIlxuIikKY2F0KCJSTVNFIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAzNjc4MDU1ICh0ZXN0IGRhdGEpOiAiLCBybXNlXzM2NzgwNTVfdGVzdCwgIlxuIikKCiMgRGlhZ27Ds3N0aWNvIGRlIHJlc2lkdW9zIGRlbCBtb2RlbG8KcGFyKG1mcm93ID0gYygyLCAyKSkKcGxvdChtb2RlbG9fcmVncmVzaW9uXzM2NzgwNTVfdGVzdCkKYGBgCgoKYGBge3J9CiMgR3VhcmRhciBtw6l0cmljYXMgZGUgUmVncmVzacOzbiBMaW5lYWwgcGFyYSBwcm9kdWN0byAxNTUwMDEKaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1BUEUgPSBudW1lcmljKCksCiAgICBSTVNFID0gbnVtZXJpYygpLAogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKQp9CgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjM2NzgwNTUiLCAgIyBDYW1iaWEgZXN0ZSBJRCBwYXJhIGNhZGEgcHJvZHVjdG8KICBNb2RlbG8gPSAiUmVncmVzacOzbiBMaW5lYWwiLAogIE1BUEUgPSBtYXBlXzM2NzgwNTUsCiAgUk1TRSA9IHJtc2VfMzY3ODA1NQopKQpgYGAKCiMjIEFOQUxJU0lTIERFIFZBUklBQkxFUyBJTVBPUlRBTlRFUwpgYGB7cn0KIyBGdW5jacOzbiBzaW1wbGlmaWNhZGEgcGFyYSBhbmFsaXphciBjb2VmaWNpZW50ZXMKYW5hbGl6YXJfY29lZmljaWVudGVzIDwtIGZ1bmN0aW9uKG1vZGVsbywgbm9tYnJlX3Byb2R1Y3RvKSB7CiAgcmVzdW1lbiA8LSBzdW1tYXJ5KG1vZGVsbykKICBjb2VmX2RmIDwtIGFzLmRhdGEuZnJhbWUocmVzdW1lbiRjb2VmZmljaWVudHMpCiAgY29sbmFtZXMoY29lZl9kZikgPC0gYygiRXN0aW1hdGUiLCAiU3RkLkVycm9yIiwgInQudmFsdWUiLCAicC52YWx1ZSIpCiAgY29lZl9kZiRWYXJpYWJsZSA8LSByb3duYW1lcyhjb2VmX2RmKQogIGNvZWZfZGYkUHJvZHVjdG8gPC0gbm9tYnJlX3Byb2R1Y3RvCiAgY29lZl9kZiRTaWduaWZpY2F0aXZvIDwtIGlmZWxzZShjb2VmX2RmJHAudmFsdWUgPCAwLjA1LCAiU8OtIiwgIk5vIikKICAKICByZXR1cm4oY29lZl9kZiAlPiUKICAgICAgICAgICBzZWxlY3QoUHJvZHVjdG8sIFZhcmlhYmxlLCBFc3RpbWF0ZSwgcC52YWx1ZSwgU2lnbmlmaWNhdGl2bykgJT4lCiAgICAgICAgICAgYXJyYW5nZShkZXNjKGFicyhFc3RpbWF0ZSkpKSkKfQoKIyBBcGxpY2FyIGEgY2FkYSBtb2RlbG8KY29lZl8xNTUwMDEgPC0gYW5hbGl6YXJfY29lZmljaWVudGVzKG1vZGVsb19yZWdyZXNpb25fMTU1MDAxLCAiMTU1MDAxIikKY29lZl8xNTUwMDIgPC0gYW5hbGl6YXJfY29lZmljaWVudGVzKG1vZGVsb19yZWdyZXNpb25fMTU1MDAyLCAiMTU1MDAyIikKY29lZl8zNjc4MDU1IDwtIGFuYWxpemFyX2NvZWZpY2llbnRlcyhtb2RlbG9fcmVncmVzaW9uXzM2NzgwNTUsICIzNjc4MDU1IikKY29lZl8zOTA0MTUyIDwtIGFuYWxpemFyX2NvZWZpY2llbnRlcyhtb2RlbG9fcmVncmVzaW9uXzM5MDQxNTIsICIzOTA0MTUyIikKY29lZl8zOTI5Nzg4IDwtIGFuYWxpemFyX2NvZWZpY2llbnRlcyhtb2RlbG9fcmVncmVzaW9uXzM5Mjk3ODgsICIzOTI5Nzg4IikKCiMgQ29tYmluYXIgdG9kb3MgbG9zIGNvZWZpY2llbnRlcwp0b2Rvc19jb2VmaWNpZW50ZXMgPC0gYmluZF9yb3dzKGNvZWZfMTU1MDAxLCBjb2VmXzE1NTAwMiwgY29lZl8zNjc4MDU1LCBjb2VmXzM5MDQxNTIsIGNvZWZfMzkyOTc4OCkKCiMgVGFibGEgY29uIHZhcmlhYmxlcyBpbXBvcnRhbnRlcyBpbmNsdXllbmRvIHNpZ25pZmljYW5jaWEKdmFyaWFibGVzX2ltcG9ydGFudGVzIDwtIHRvZG9zX2NvZWZpY2llbnRlcyAlPiUKICBmaWx0ZXIoVmFyaWFibGUgIT0gIihJbnRlcmNlcHQpIikgJT4lCiAgZ3JvdXBfYnkoUHJvZHVjdG8pICU+JQogIGFycmFuZ2UoUHJvZHVjdG8sIGRlc2MoYWJzKEVzdGltYXRlKSkpICU+JQogIG11dGF0ZShJbXBhY3RvID0gaWZlbHNlKEVzdGltYXRlID4gMCwgIlBvc2l0aXZvIiwgIk5lZ2F0aXZvIikpCgojIFRhYmxhIGNvbXBsZXRhIGNvbiB0b2RhcyBsYXMgdmFyaWFibGVzIGltcG9ydGFudGVzCmthYmxlKHZhcmlhYmxlc19pbXBvcnRhbnRlcyAlPiUgCiAgICAgICAgc2VsZWN0KFByb2R1Y3RvLCBWYXJpYWJsZSwgRXN0aW1hdGUsIHAudmFsdWUsIFNpZ25pZmljYXRpdm8sIEltcGFjdG8pLAogICAgICBjYXB0aW9uID0gIlZhcmlhYmxlcyBpbXBvcnRhbnRlcyBwb3IgcHJvZHVjdG8iLAogICAgICBjb2wubmFtZXMgPSBjKCJQcm9kdWN0byIsICJWYXJpYWJsZSIsICJDb2VmaWNpZW50ZSIsICJwLXZhbHVlIiwgIlNpZ25pZmljYXRpdm8iLCAiSW1wYWN0byIpLAogICAgICBkaWdpdHMgPSBjKDAsIDAsIDQsIDQsIDAsIDApKSAlPiUKICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSkKCiMgVGFibGEgcmVzdW1lbiBjb24gdG9wIDMgcG9yIHByb2R1Y3RvCnRvcF9wb3JfcHJvZHVjdG8gPC0gdmFyaWFibGVzX2ltcG9ydGFudGVzICU+JQogIGdyb3VwX2J5KFByb2R1Y3RvKSAlPiUKICBzbGljZV9oZWFkKG4gPSAzKSAlPiUKICBzZWxlY3QoUHJvZHVjdG8sIFZhcmlhYmxlLCBFc3RpbWF0ZSwgcC52YWx1ZSwgU2lnbmlmaWNhdGl2bywgSW1wYWN0bykKCmthYmxlKHRvcF9wb3JfcHJvZHVjdG8sCiAgICAgIGNhcHRpb24gPSAiVG9wIDMgdmFyaWFibGVzIG3DoXMgaW1wb3J0YW50ZXMgcG9yIHByb2R1Y3RvIiwKICAgICAgY29sLm5hbWVzID0gYygiUHJvZHVjdG8iLCAiVmFyaWFibGUiLCAiQ29lZmljaWVudGUiLCAicC12YWx1ZSIsICJTaWduaWZpY2F0aXZvIiwgIkltcGFjdG8iKSwKICAgICAgZGlnaXRzID0gYygwLCAwLCA0LCA0LCAwLCAwKSkgJT4lCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIikpCmBgYAoKCiMgUkFORE9NIEZPUkVTVAoKCiMjIFBST0RVQ1RPIDE1NTAwMQpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykKZGF0b3NfbW9kZWxvIDwtIGRhdG9zXzE1NTAwMSAlPiUKICBzZWxlY3QoLUZlY2hhKQoKIyBBanVzdGFyIGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0CnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkCm1vZGVsb19yZl8xNTUwMDEgPC0gcmFuZG9tRm9yZXN0KAogIFZlbnRhIH4gLiwgCiAgZGF0YSA9IGRhdG9zX21vZGVsbywKICBudHJlZSA9IDUwMCwgICAgICAgICAgIyBOw7ptZXJvIGRlIMOhcmJvbGVzCiAgbXRyeSA9IGZsb29yKHNxcnQobmNvbChkYXRvc19tb2RlbG8pIC0gMSkpLCAgIyBOw7ptZXJvIGRlIHZhcmlhYmxlcyBhIGNvbnNpZGVyYXIgZW4gY2FkYSBzcGxpdAogIGltcG9ydGFuY2UgPSBUUlVFICAgICAjIENhbGN1bGFyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwopCgojIFZlciByZXN1bWVuIGRlbCBtb2RlbG8KcHJpbnQobW9kZWxvX3JmXzE1NTAwMSkKCiMgT2J0ZW5lciBwcmVkaWNjaW9uZXMKcHJlZGljY2lvbmVzX3JmIDwtIHByZWRpY3QobW9kZWxvX3JmXzE1NTAwMSwgbmV3ZGF0YSA9IGRhdG9zX21vZGVsbykKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzCiMgTUFQRQptYXBlX3JmIDwtIG1lYW4oYWJzKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpIC8gcG1heChkYXRvc19tb2RlbG8kVmVudGEsIDAuMDEpKSkgKiAxMDAKCiMgUk1TRQpybXNlX3JmIDwtIG1lYW4oKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19yZileMikKCiMgTW9zdHJhciBsYXMgbcOpdHJpY2FzCmNhdCgiTW9kZWxvIFJhbmRvbSBGb3Jlc3QgcGFyYSBwcm9kdWN0byAodHJhaW4gZGF0YSkgMTU1MDAxXG4iKQpjYXQoIk1BUEUgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0ICh0cmFpbiBkYXRhKSA6IiwgbWFwZV9yZiwgIlxuIikKY2F0KCJSTVNFIGRlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdCAodHJhaW4gZGF0YSk6Iiwgcm1zZV9yZiwgIlxuXG4iKQoKIyBNb3N0cmFyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwppbXBvcnRhbmNpYV92YXJzIDwtIGltcG9ydGFuY2UobW9kZWxvX3JmXzE1NTAwMSkKcHJpbnQoaW1wb3J0YW5jaWFfdmFycykKCiMgR3JhZmljYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCnZhckltcFBsb3QobW9kZWxvX3JmXzE1NTAwMSwgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAxNTUwMDEgKHRyYWluIGRhdGEpIikKCiMgQ3JlYXIgZ3LDoWZpY28gZGUgdmFsb3JlcyBvYnNlcnZhZG9zIHZzIHByZWRpY2Npb25lcwpkYXRvc19ncmFmaWNvIDwtIGRhdGEuZnJhbWUoCiAgT2JzZXJ2YWRvID0gZGF0b3NfbW9kZWxvJFZlbnRhLAogIFByZWRpY2hvID0gcHJlZGljY2lvbmVzX3JmCikKCmdncGxvdChkYXRvc19ncmFmaWNvLCBhZXMoeCA9IE9ic2VydmFkbywgeSA9IFByZWRpY2hvKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDE1NTAwMSAodHJhaW4gZGF0YSkiLAogICAgeCA9ICJWZW50YXMgT2JzZXJ2YWRhcyIsCiAgICB5ID0gIlZlbnRhcyBQcmVkaWNoYXMiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIE5VRVZPUyBBTsOBTElTSVMgQcORQURJRE9TCgojIEFuw6FsaXNpcyBkZWwgZXJyb3IKZXJyb3JlcyA8LSBkYXRvc19ncmFmaWNvJE9ic2VydmFkbyAtIGRhdG9zX2dyYWZpY28kUHJlZGljaG8KaGlzdChlcnJvcmVzLCAKICAgICBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDE1NTAwMSAodHJhaW4gZGF0YSkiLAogICAgIHhsYWIgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIsCiAgICAgY29sID0gInNreWJsdWUiLAogICAgIGJyZWFrcyA9IDMwKQoKIyBFc3RhZMOtc3RpY2FzIGRlc2NyaXB0aXZhcyBkZSBsb3MgZXJyb3JlcwpjYXQoIkVzdGFkw61zdGljYXMgZGVzY3JpcHRpdmFzIGRlIGxvcyBlcnJvcmVzICh0cmFpbiBkYXRhKTpcbiIpCmNhdCgiTWVkaWEgZGUgZXJyb3JlczoiLCBtZWFuKGVycm9yZXMpLCAiXG4iKQpjYXQoIkRlc3ZpYWNpw7NuIGVzdMOhbmRhciBkZSBlcnJvcmVzOiIsIHNkKGVycm9yZXMpLCAiXG4iKQpjYXQoIk3DrW5pbW86IiwgbWluKGVycm9yZXMpLCAiXG4iKQpjYXQoIk3DoXhpbW86IiwgbWF4KGVycm9yZXMpLCAiXG4iKQpjYXQoIk1lZGlhbmE6IiwgbWVkaWFuKGVycm9yZXMpLCAiXG4iKQoKIyBHcsOhZmljbyBkZWwgZXJyb3IgdnMgcHJlZGljY2nDs24KZ2dwbG90KGRhdGEuZnJhbWUoUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmYsIEVycm9yID0gZXJyb3JlcyksIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJFcnJvciB2cyBQcmVkaWNjacOzbiAtIFByb2R1Y3RvIDE1NTAwMSAodHJhaW4gZGF0YSkiLAogICAgeCA9ICJWZW50YXMgUHJlZGljaGFzIiwKICAgIHkgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIKICApICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgpgYGB7cn0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykKZGF0b3NfbW9kZWxvX3Rlc3RfMTU1MDAxIDwtIHRlc3RfMTU1MDAxCgojIEFqdXN0YXIgZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3QKc2V0LnNlZWQoMTIzKSAgIyBQYXJhIHJlcHJvZHVjaWJpbGlkYWQKbW9kZWxvX3JmXzE1NTAwMV90ZXN0IDwtIHJhbmRvbUZvcmVzdCgKICBWZW50YSB+IC4sIAogIGRhdGEgPSBkYXRvc19tb2RlbG9fdGVzdF8xNTUwMDEsCiAgbnRyZWUgPSA1MDAsICAgICAgICAgICMgTsO6bWVybyBkZSDDoXJib2xlcwogIG10cnkgPSBmbG9vcihzcXJ0KG5jb2woZGF0b3NfbW9kZWxvX3Rlc3RfMTU1MDAxKSAtIDEpKSwgICMgTsO6bWVybyBkZSB2YXJpYWJsZXMgYSBjb25zaWRlcmFyIGVuIGNhZGEgc3BsaXQKICBpbXBvcnRhbmNlID0gVFJVRSAgICAgIyBDYWxjdWxhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKKQoKIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvCnByaW50KG1vZGVsb19yZl8xNTUwMDFfdGVzdCkKCiMgT2J0ZW5lciBwcmVkaWNjaW9uZXMKcHJlZGljY2lvbmVzX3JmX3Rlc3RfMTU1MDAxIDwtIHByZWRpY3QobW9kZWxvX3JmXzE1NTAwMV90ZXN0LCBuZXdkYXRhID0gZGF0b3NfbW9kZWxvX3Rlc3RfMTU1MDAxKQoKIyBDYWxjdWxhciBtw6l0cmljYXMKIyBNQVBFCm1hcGVfcmZfdGVzdF8xNTUwMDEgPC0gbWVhbihhYnMoKGRhdG9zX21vZGVsb190ZXN0XzE1NTAwMSRWZW50YSAtIHByZWRpY2Npb25lc19yZl90ZXN0XzE1NTAwMSkgLyBwbWF4KGRhdG9zX21vZGVsb190ZXN0XzE1NTAwMSRWZW50YSwgMC4wMSkpKSAqIDEwMAoKIyBSTVNFCnJtc2VfcmZfdGVzdF8xNTUwMDEgPC0gbWVhbigoZGF0b3NfbW9kZWxvX3Rlc3RfMTU1MDAxJFZlbnRhIC0gcHJlZGljY2lvbmVzX3JmX3Rlc3RfMTU1MDAxKV4yKQoKIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMKY2F0KCJNb2RlbG8gUmFuZG9tIEZvcmVzdCBwYXJhIHByb2R1Y3RvICh0ZXN0IGRhdGEpIDE1NTAwMVxuIikKY2F0KCJNQVBFIGRlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdCAodGVzdCBkYXRhKToiLCBtYXBlX3JmX3Rlc3RfMTU1MDAxLCAiXG4iKQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0ICh0ZXN0IGRhdGEpOiIsIHJtc2VfcmZfdGVzdF8xNTUwMDEsICJcblxuIikKCiMgTW9zdHJhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKaW1wb3J0YW5jaWFfdmFyc190ZXN0XzE1NTAwMSA8LSBpbXBvcnRhbmNlKG1vZGVsb19yZl8xNTUwMDFfdGVzdCkKcHJpbnQoaW1wb3J0YW5jaWFfdmFyc190ZXN0XzE1NTAwMSkKCiMgR3JhZmljYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCnZhckltcFBsb3QobW9kZWxvX3JmXzE1NTAwMV90ZXN0LCBtYWluID0gIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyAtIFByb2R1Y3RvIDE1NTAwMSAodGVzdCBkYXRhKSIpCgojIENyZWFyIGdyw6FmaWNvIGRlIHZhbG9yZXMgb2JzZXJ2YWRvcyB2cyBwcmVkaWNjaW9uZXMKZGF0b3NfZ3JhZmljb190ZXN0XzE1NTAwMSA8LSBkYXRhLmZyYW1lKAogIE9ic2VydmFkbyA9IGRhdG9zX21vZGVsb190ZXN0XzE1NTAwMSRWZW50YSwKICBQcmVkaWNobyA9IHByZWRpY2Npb25lc19yZl90ZXN0XzE1NTAwMQopCgpnZ3Bsb3QoZGF0b3NfZ3JhZmljb190ZXN0XzE1NTAwMSwgYWVzKHggPSBPYnNlcnZhZG8sIHkgPSBQcmVkaWNobykpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicygKICAgIHRpdGxlID0gIlZhbG9yZXMgT2JzZXJ2YWRvcyB2cyBQcmVkaWNjaW9uZXMgLSBQcm9kdWN0byAxNTUwMDEgKHRlc3QgZGF0YSkiLAogICAgeCA9ICJWZW50YXMgT2JzZXJ2YWRhcyIsCiAgICB5ID0gIlZlbnRhcyBQcmVkaWNoYXMiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIE5VRVZPUyBBTsOBTElTSVMgQcORQURJRE9TCgojIEFuw6FsaXNpcyBkZWwgZXJyb3IKZXJyb3Jlc190ZXN0XzE1NTAwMSA8LSBkYXRvc19ncmFmaWNvX3Rlc3RfMTU1MDAxJE9ic2VydmFkbyAtIGRhdG9zX2dyYWZpY29fdGVzdF8xNTUwMDEkUHJlZGljaG8KaGlzdChlcnJvcmVzLCAKICAgICBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDE1NTAwMSAodGVzdCBkYXRhKSIsCiAgICAgeGxhYiA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIiwKICAgICBjb2wgPSAic2t5Ymx1ZSIsCiAgICAgYnJlYWtzID0gMzApCgojIEVzdGFkw61zdGljYXMgZGVzY3JpcHRpdmFzIGRlIGxvcyBlcnJvcmVzCmNhdCgiRXN0YWTDrXN0aWNhcyBkZXNjcmlwdGl2YXMgZGUgbG9zIGVycm9yZXMgKHRlc3QgZGF0YSk6XG4iKQpjYXQoIk1lZGlhIGRlIGVycm9yZXM6IiwgbWVhbihlcnJvcmVzKSwgIlxuIikKY2F0KCJEZXN2aWFjacOzbiBlc3TDoW5kYXIgZGUgZXJyb3JlczoiLCBzZChlcnJvcmVzKSwgIlxuIikKY2F0KCJNw61uaW1vOiIsIG1pbihlcnJvcmVzKSwgIlxuIikKY2F0KCJNw6F4aW1vOiIsIG1heChlcnJvcmVzKSwgIlxuIikKY2F0KCJNZWRpYW5hOiIsIG1lZGlhbihlcnJvcmVzKSwgIlxuIikKCiMgR3LDoWZpY28gZGVsIGVycm9yIHZzIHByZWRpY2Npw7NuCmdncGxvdChkYXRhLmZyYW1lKFByZWRpY2hvID0gcHJlZGljY2lvbmVzX3JmX3Rlc3RfMTU1MDAxLCBFcnJvciA9IGVycm9yZXNfdGVzdF8xNTUwMDEpLCBhZXMoeCA9IFByZWRpY2hvLCB5ID0gRXJyb3IpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiRXJyb3IgdnMgUHJlZGljY2nDs24gLSBQcm9kdWN0byAxNTUwMDEgKHRlc3QgZGF0YSkiLAogICAgeCA9ICJWZW50YXMgUHJlZGljaGFzIiwKICAgIHkgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIKICApICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgoKYGBge3J9CiMgR3VhcmRhciBtw6l0cmljYXMgZGUgUmFuZG9tIEZvcmVzdCBwYXJhIHByb2R1Y3RvIDE1NTAwMQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgewogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLAogICAgTUFQRSA9IG51bWVyaWMoKSwKICAgIFJNU0UgPSBudW1lcmljKCksCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCn0KCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMTU1MDAxIiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvCiAgTW9kZWxvID0gIlJhbmRvbSBGb3Jlc3QiLAogIE1BUEUgPSBtYXBlX3JmLAogIFJNU0UgPSBybXNlX3JmCikpCmBgYAoKIyMgUFJPRFVDVE8gMzkyOTc4OApgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykKZGF0b3NfbW9kZWxvIDwtIGRhdG9zXzM5Mjk3ODggJT4lCiAgc2VsZWN0KC1GZWNoYSkKCiMgQWp1c3RhciBlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdApzZXQuc2VlZCgxMjMpICAjIFBhcmEgcmVwcm9kdWNpYmlsaWRhZAptb2RlbG9fcmZfMzkyOTc4OCA8LSByYW5kb21Gb3Jlc3QoCiAgVmVudGEgfiAuLCAKICBkYXRhID0gZGF0b3NfbW9kZWxvLAogIG50cmVlID0gNTAwLCAgICAgICAgICAjIE7Dum1lcm8gZGUgw6FyYm9sZXMKICBtdHJ5ID0gZmxvb3Ioc3FydChuY29sKGRhdG9zX21vZGVsbykgLSAxKSksICAjIE7Dum1lcm8gZGUgdmFyaWFibGVzIGEgY29uc2lkZXJhciBlbiBjYWRhIHNwbGl0CiAgaW1wb3J0YW5jZSA9IFRSVUUgICAgICMgQ2FsY3VsYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCikKCiMgVmVyIHJlc3VtZW4gZGVsIG1vZGVsbwpwcmludChtb2RlbG9fcmZfMzkyOTc4OCkKCiMgT2J0ZW5lciBwcmVkaWNjaW9uZXMKcHJlZGljY2lvbmVzX3JmIDwtIHByZWRpY3QobW9kZWxvX3JmXzM5Mjk3ODgsIG5ld2RhdGEgPSBkYXRvc19tb2RlbG8pCgojIENhbGN1bGFyIG3DqXRyaWNhcwojIE1BUEUKbWFwZV9yZiA8LSBtZWFuKGFicygoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX3JmKSAvIHBtYXgoZGF0b3NfbW9kZWxvJFZlbnRhLCAwLjAxKSkpICogMTAwCgojIFJNU0UKCnJtc2VfcmYgPC0gc3FydChtZWFuKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpXjIpKQoKCiMgTW9zdHJhciBsYXMgbcOpdHJpY2FzCmNhdCgiTW9kZWxvIFJhbmRvbSBGb3Jlc3QgcGFyYSBwcm9kdWN0byAzOTI5Nzg4XG4iKQpjYXQoIk1BUEUgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0OiIsIG1hcGVfcmYsICJcbiIpCmNhdCgiUk1TRSBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3Q6Iiwgcm1zZV9yZiwgIlxuXG4iKQoKCiMgTW9zdHJhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKaW1wb3J0YW5jaWFfdmFycyA8LSBpbXBvcnRhbmNlKG1vZGVsb19yZl8zOTI5Nzg4KQpwcmludChpbXBvcnRhbmNpYV92YXJzKQoKIyBHcmFmaWNhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKdmFySW1wUGxvdChtb2RlbG9fcmZfMzkyOTc4OCwgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAzOTI5Nzg4IikKCiMgQ3JlYXIgZ3LDoWZpY28gZGUgdmFsb3JlcyBvYnNlcnZhZG9zIHZzIHByZWRpY2Npb25lcwpkYXRvc19ncmFmaWNvIDwtIGRhdGEuZnJhbWUoCiAgT2JzZXJ2YWRvID0gZGF0b3NfbW9kZWxvJFZlbnRhLAogIFByZWRpY2hvID0gcHJlZGljY2lvbmVzX3JmCikKCmdncGxvdChkYXRvc19ncmFmaWNvLCBhZXMoeCA9IE9ic2VydmFkbywgeSA9IFByZWRpY2hvKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDM5Mjk3ODgiLAogICAgeCA9ICJWZW50YXMgT2JzZXJ2YWRhcyIsCiAgICB5ID0gIlZlbnRhcyBQcmVkaWNoYXMiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIEFuw6FsaXNpcyBkZWwgZXJyb3IKZXJyb3JlcyA8LSBkYXRvc19ncmFmaWNvJE9ic2VydmFkbyAtIGRhdG9zX2dyYWZpY28kUHJlZGljaG8KaGlzdChlcnJvcmVzLCAKICAgICBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDM5Mjk3ODgiLAogICAgIHhsYWIgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIsCiAgICAgY29sID0gInNreWJsdWUiLAogICAgIGJyZWFrcyA9IDMwKQoKIyBHcsOhZmljbyBkZWwgZXJyb3IgdnMgcHJlZGljY2nDs24KZ2dwbG90KGRhdGEuZnJhbWUoUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmYsIEVycm9yID0gZXJyb3JlcyksIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJFcnJvciB2cyBQcmVkaWNjacOzbiAtIFByb2R1Y3RvIDM5Mjk3ODgiLAogICAgeCA9ICJWZW50YXMgUHJlZGljaGFzIiwKICAgIHkgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIKICApICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgpgYGB7cn0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykKZGF0b3NfbW9kZWxvX3Rlc3RfMzkyOTc4OCA8LSB0ZXN0XzM5Mjk3ODgKCiMgQWp1c3RhciBlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdApzZXQuc2VlZCgxMjMpICAjIFBhcmEgcmVwcm9kdWNpYmlsaWRhZAptb2RlbG9fcmZfMzkyOTc4OF90ZXN0IDwtIHJhbmRvbUZvcmVzdCgKICBWZW50YSB+IC4sIAogIGRhdGEgPSBkYXRvc19tb2RlbG9fdGVzdF8zOTI5Nzg4LAogIG50cmVlID0gNTAwLCAgICAgICAgICAjIE7Dum1lcm8gZGUgw6FyYm9sZXMKICBtdHJ5ID0gZmxvb3Ioc3FydChuY29sKGRhdG9zX21vZGVsb190ZXN0XzM5Mjk3ODgpIC0gMSkpLCAgIyBOw7ptZXJvIGRlIHZhcmlhYmxlcyBhIGNvbnNpZGVyYXIgZW4gY2FkYSBzcGxpdAogIGltcG9ydGFuY2UgPSBUUlVFICAgICAjIENhbGN1bGFyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwopCgojIFZlciByZXN1bWVuIGRlbCBtb2RlbG8KcHJpbnQobW9kZWxvX3JmXzM5Mjk3ODhfdGVzdCkKCiMgT2J0ZW5lciBwcmVkaWNjaW9uZXMKcHJlZGljY2lvbmVzX3JmX3Rlc3RfMzkyOTc4OCA8LSBwcmVkaWN0KG1vZGVsb19yZl8zOTI5Nzg4X3Rlc3QsIG5ld2RhdGEgPSBkYXRvc19tb2RlbG9fdGVzdF8zOTI5Nzg4KQoKIyBDYWxjdWxhciBtw6l0cmljYXMKIyBNQVBFCm1hcGVfcmZfdGVzdF8zOTI5Nzg4IDwtIG1lYW4oYWJzKChkYXRvc19tb2RlbG9fdGVzdF8zOTI5Nzg4JFZlbnRhIC0gcHJlZGljY2lvbmVzX3JmX3Rlc3RfMzkyOTc4OCkgLyBwbWF4KGRhdG9zX21vZGVsb190ZXN0XzM5Mjk3ODgkVmVudGEsIDAuMDEpKSkgKiAxMDAKCiMgUk1TRQpybXNlX3JmX3Rlc3RfMzkyOTc4OCA8LSBtZWFuKChkYXRvc19tb2RlbG9fdGVzdF8zOTI5Nzg4JFZlbnRhIC0gcHJlZGljY2lvbmVzX3JmX3Rlc3RfMzkyOTc4OCleMikKCiMgTW9zdHJhciBsYXMgbcOpdHJpY2FzCmNhdCgiTW9kZWxvIFJhbmRvbSBGb3Jlc3QgcGFyYSBwcm9kdWN0byAodGVzdCBkYXRhKSAzOTI5Nzg4XG4iKQpjYXQoIk1BUEUgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0ICh0ZXN0IGRhdGEpOiIsIG1hcGVfcmZfdGVzdF8zOTI5Nzg4LCAiXG4iKQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0ICh0ZXN0IGRhdGEpOiIsIHJtc2VfcmZfdGVzdF8zOTI5Nzg4LCAiXG5cbiIpCgojIE1vc3RyYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCmltcG9ydGFuY2lhX3ZhcnNfdGVzdF8zOTI5Nzg4IDwtIGltcG9ydGFuY2UobW9kZWxvX3JmXzM5Mjk3ODhfdGVzdCkKcHJpbnQoaW1wb3J0YW5jaWFfdmFyc190ZXN0XzM5Mjk3ODgpCgojIEdyYWZpY2FyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwp2YXJJbXBQbG90KG1vZGVsb19yZl8zOTI5Nzg4X3Rlc3QsIG1haW4gPSAiSW1wb3J0YW5jaWEgZGUgVmFyaWFibGVzIC0gUHJvZHVjdG8gMzkyOTc4OCAodGVzdCBkYXRhKSIpCgojIENyZWFyIGdyw6FmaWNvIGRlIHZhbG9yZXMgb2JzZXJ2YWRvcyB2cyBwcmVkaWNjaW9uZXMKZGF0b3NfZ3JhZmljb190ZXN0XzM5Mjk3ODggPC0gZGF0YS5mcmFtZSgKICBPYnNlcnZhZG8gPSBkYXRvc19tb2RlbG9fdGVzdF8zOTI5Nzg4JFZlbnRhLAogIFByZWRpY2hvID0gcHJlZGljY2lvbmVzX3JmX3Rlc3RfMzkyOTc4OAopCgpnZ3Bsb3QoZGF0b3NfZ3JhZmljb190ZXN0XzM5Mjk3ODgsIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJWYWxvcmVzIE9ic2VydmFkb3MgdnMgUHJlZGljY2lvbmVzIC0gUHJvZHVjdG8gMzkyOTc4OCAodGVzdCBkYXRhKSIsCiAgICB4ID0gIlZlbnRhcyBPYnNlcnZhZGFzIiwKICAgIHkgPSAiVmVudGFzIFByZWRpY2hhcyIKICApICsKICB0aGVtZV9taW5pbWFsKCkKCiMgTlVFVk9TIEFOw4FMSVNJUyBBw5FBRElET1MKCiMgQW7DoWxpc2lzIGRlbCBlcnJvcgplcnJvcmVzX3Rlc3RfMzkyOTc4OCA8LSBkYXRvc19ncmFmaWNvX3Rlc3RfMzkyOTc4OCRPYnNlcnZhZG8gLSBkYXRvc19ncmFmaWNvX3Rlc3RfMzkyOTc4OCRQcmVkaWNobwpoaXN0KGVycm9yZXMsIAogICAgIG1haW4gPSAiRGlzdHJpYnVjacOzbiBkZSBFcnJvcmVzIC0gUHJvZHVjdG8gMzkyOTc4OCAodGVzdCBkYXRhKSIsCiAgICAgeGxhYiA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIiwKICAgICBjb2wgPSAic2t5Ymx1ZSIsCiAgICAgYnJlYWtzID0gMzApCgojIEVzdGFkw61zdGljYXMgZGVzY3JpcHRpdmFzIGRlIGxvcyBlcnJvcmVzCmNhdCgiRXN0YWTDrXN0aWNhcyBkZXNjcmlwdGl2YXMgZGUgbG9zIGVycm9yZXMgKHRlc3QgZGF0YSk6XG4iKQpjYXQoIk1lZGlhIGRlIGVycm9yZXM6IiwgbWVhbihlcnJvcmVzKSwgIlxuIikKY2F0KCJEZXN2aWFjacOzbiBlc3TDoW5kYXIgZGUgZXJyb3JlczoiLCBzZChlcnJvcmVzKSwgIlxuIikKY2F0KCJNw61uaW1vOiIsIG1pbihlcnJvcmVzKSwgIlxuIikKY2F0KCJNw6F4aW1vOiIsIG1heChlcnJvcmVzKSwgIlxuIikKY2F0KCJNZWRpYW5hOiIsIG1lZGlhbihlcnJvcmVzKSwgIlxuIikKCiMgR3LDoWZpY28gZGVsIGVycm9yIHZzIHByZWRpY2Npw7NuCmdncGxvdChkYXRhLmZyYW1lKFByZWRpY2hvID0gcHJlZGljY2lvbmVzX3JmX3Rlc3RfMzkyOTc4OCwgRXJyb3IgPSBlcnJvcmVzX3Rlc3RfMzkyOTc4OCksIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJFcnJvciB2cyBQcmVkaWNjacOzbiAtIFByb2R1Y3RvIDM5Mjk3ODggKHRlc3QgZGF0YSkiLAogICAgeCA9ICJWZW50YXMgUHJlZGljaGFzIiwKICAgIHkgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIKICApICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgoKYGBge3J9CiMgR3VhcmRhciBtw6l0cmljYXMgZGUgUmFuZG9tIEZvcmVzdCBwYXJhIHByb2R1Y3RvIDE1NTAwMQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgewogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLAogICAgTUFQRSA9IG51bWVyaWMoKSwKICAgIFJNU0UgPSBudW1lcmljKCksCgogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKQp9CgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjM5Mjk3ODgiLCAgIyBDYW1iaWEgZXN0ZSBJRCBwYXJhIGNhZGEgcHJvZHVjdG8KICBNb2RlbG8gPSAiUmFuZG9tIEZvcmVzdCIsCiAgTUFQRSA9IG1hcGVfcmYsCiAgUk1TRSA9IHJtc2VfcmYKCikpCmBgYAoKCiMjIFBST0RVQ1RPIDM5MDQxNTIKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTZ9CiMgUHJlcGFyYXIgZGF0b3MgcGFyYSBlbCBtb2RlbG8gKGVsaW1pbmFyIGNvbHVtbmFzIG5vIG5lY2VzYXJpYXMpCmRhdG9zX21vZGVsbyA8LSBkYXRvc18zOTA0MTUyICU+JQogIHNlbGVjdCgtRmVjaGEpCgojIEFqdXN0YXIgZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3QKc2V0LnNlZWQoMTIzKSAgIyBQYXJhIHJlcHJvZHVjaWJpbGlkYWQKbW9kZWxvX3JmXzM5MDQxNTIgPC0gcmFuZG9tRm9yZXN0KAogIFZlbnRhIH4gLiwgCiAgZGF0YSA9IGRhdG9zX21vZGVsbywKICBudHJlZSA9IDUwMCwgICAgICAgICAgIyBOw7ptZXJvIGRlIMOhcmJvbGVzCiAgbXRyeSA9IGZsb29yKHNxcnQobmNvbChkYXRvc19tb2RlbG8pIC0gMSkpLCAgIyBOw7ptZXJvIGRlIHZhcmlhYmxlcyBhIGNvbnNpZGVyYXIgZW4gY2FkYSBzcGxpdAogIGltcG9ydGFuY2UgPSBUUlVFICAgICAjIENhbGN1bGFyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwopCgojIFZlciByZXN1bWVuIGRlbCBtb2RlbG8KcHJpbnQobW9kZWxvX3JmXzM5MDQxNTIpCgojIE9idGVuZXIgcHJlZGljY2lvbmVzCnByZWRpY2Npb25lc19yZiA8LSBwcmVkaWN0KG1vZGVsb19yZl8zOTA0MTUyLCBuZXdkYXRhID0gZGF0b3NfbW9kZWxvKQoKIyBDYWxjdWxhciBtw6l0cmljYXMKIyBNQVBFCm1hcGVfcmYgPC0gbWVhbihhYnMoKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19yZikgLyBwbWF4KGRhdG9zX21vZGVsbyRWZW50YSwgMC4wMSkpKSAqIDEwMAoKIyBSTVNFCnJtc2VfcmYgPC0gc3FydChtZWFuKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpXjIpKQoKCiMgTW9zdHJhciBsYXMgbcOpdHJpY2FzCmNhdCgiTW9kZWxvIFJhbmRvbSBGb3Jlc3QgcGFyYSBwcm9kdWN0byAzOTA0MTUyXG4iKQpjYXQoIk1BUEUgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0OiIsIG1hcGVfcmYsICJcbiIpCmNhdCgiUk1TRSBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3Q6Iiwgcm1zZV9yZiwgIlxuXG4iKQoKIyBNb3N0cmFyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwppbXBvcnRhbmNpYV92YXJzIDwtIGltcG9ydGFuY2UobW9kZWxvX3JmXzM5MDQxNTIpCnByaW50KGltcG9ydGFuY2lhX3ZhcnMpCgojIEdyYWZpY2FyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwp2YXJJbXBQbG90KG1vZGVsb19yZl8zOTA0MTUyLCBtYWluID0gIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyAtIFByb2R1Y3RvIDM5MDQxNTIiKQoKIyBDcmVhciBncsOhZmljbyBkZSB2YWxvcmVzIG9ic2VydmFkb3MgdnMgcHJlZGljY2lvbmVzCmRhdG9zX2dyYWZpY28gPC0gZGF0YS5mcmFtZSgKICBPYnNlcnZhZG8gPSBkYXRvc19tb2RlbG8kVmVudGEsCiAgUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmYKKQoKZ2dwbG90KGRhdG9zX2dyYWZpY28sIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJWYWxvcmVzIE9ic2VydmFkb3MgdnMgUHJlZGljY2lvbmVzIC0gUHJvZHVjdG8gMzkwNDE1MiIsCiAgICB4ID0gIlZlbnRhcyBPYnNlcnZhZGFzIiwKICAgIHkgPSAiVmVudGFzIFByZWRpY2hhcyIKICApICsKICB0aGVtZV9taW5pbWFsKCkKCiMgQW7DoWxpc2lzIGRlbCBlcnJvcgplcnJvcmVzIDwtIGRhdG9zX2dyYWZpY28kT2JzZXJ2YWRvIC0gZGF0b3NfZ3JhZmljbyRQcmVkaWNobwpoaXN0KGVycm9yZXMsIAogICAgIG1haW4gPSAiRGlzdHJpYnVjacOzbiBkZSBFcnJvcmVzIC0gUHJvZHVjdG8gMzkwNDE1MiIsCiAgICAgeGxhYiA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIiwKICAgICBjb2wgPSAic2t5Ymx1ZSIsCiAgICAgYnJlYWtzID0gMzApCgojIEdyw6FmaWNvIGRlbCBlcnJvciB2cyBwcmVkaWNjacOzbgpnZ3Bsb3QoZGF0YS5mcmFtZShQcmVkaWNobyA9IHByZWRpY2Npb25lc19yZiwgRXJyb3IgPSBlcnJvcmVzKSwgYWVzKHggPSBQcmVkaWNobywgeSA9IEVycm9yKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicygKICAgIHRpdGxlID0gIkVycm9yIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMzkwNDE1MiIsCiAgICB4ID0gIlZlbnRhcyBQcmVkaWNoYXMiLAogICAgeSA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIgogICkgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCmBgYHtyfQojIFByZXBhcmFyIGRhdG9zIHBhcmEgZWwgbW9kZWxvIChlbGltaW5hciBjb2x1bW5hcyBubyBuZWNlc2FyaWFzKQpkYXRvc19tb2RlbG9fdGVzdF8zOTA0MTUyIDwtIHRlc3RfMzkwNDE1MgoKIyBBanVzdGFyIGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0CnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkCm1vZGVsb19yZl8zOTA0MTUyX3Rlc3QgPC0gcmFuZG9tRm9yZXN0KAogIFZlbnRhIH4gLiwgCiAgZGF0YSA9IGRhdG9zX21vZGVsb190ZXN0XzM5MDQxNTIsCiAgbnRyZWUgPSA1MDAsICAgICAgICAgICMgTsO6bWVybyBkZSDDoXJib2xlcwogIG10cnkgPSBmbG9vcihzcXJ0KG5jb2woZGF0b3NfbW9kZWxvX3Rlc3RfMzkwNDE1MikgLSAxKSksICAjIE7Dum1lcm8gZGUgdmFyaWFibGVzIGEgY29uc2lkZXJhciBlbiBjYWRhIHNwbGl0CiAgaW1wb3J0YW5jZSA9IFRSVUUgICAgICMgQ2FsY3VsYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCikKCiMgVmVyIHJlc3VtZW4gZGVsIG1vZGVsbwpwcmludChtb2RlbG9fcmZfMzkwNDE1Ml90ZXN0KQoKIyBPYnRlbmVyIHByZWRpY2Npb25lcwpwcmVkaWNjaW9uZXNfcmZfdGVzdF8zOTA0MTUyIDwtIHByZWRpY3QobW9kZWxvX3JmXzM5MDQxNTJfdGVzdCwgbmV3ZGF0YSA9IGRhdG9zX21vZGVsb190ZXN0XzM5MDQxNTIpCgojIENhbGN1bGFyIG3DqXRyaWNhcwojIE1BUEUKbWFwZV9yZl90ZXN0XzM5MDQxNTIgPC0gbWVhbihhYnMoKGRhdG9zX21vZGVsb190ZXN0XzM5MDQxNTIkVmVudGEgLSBwcmVkaWNjaW9uZXNfcmZfdGVzdF8zOTA0MTUyKSAvIHBtYXgoZGF0b3NfbW9kZWxvX3Rlc3RfMzkwNDE1MiRWZW50YSwgMC4wMSkpKSAqIDEwMAoKIyBSTVNFCnJtc2VfcmZfdGVzdF8zOTA0MTUyIDwtIG1lYW4oKGRhdG9zX21vZGVsb190ZXN0XzM5MDQxNTIkVmVudGEgLSBwcmVkaWNjaW9uZXNfcmZfdGVzdF8zOTA0MTUyKV4yKQoKIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMKY2F0KCJNb2RlbG8gUmFuZG9tIEZvcmVzdCBwYXJhIHByb2R1Y3RvICh0ZXN0IGRhdGEpIDM5MDQxNTJcbiIpCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3QgKHRlc3QgZGF0YSk6IiwgbWFwZV9yZl90ZXN0XzM5MDQxNTIsICJcbiIpCmNhdCgiUk1TRSBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3QgKHRlc3QgZGF0YSk6Iiwgcm1zZV9yZl90ZXN0XzM5MDQxNTIsICJcblxuIikKCiMgTW9zdHJhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKaW1wb3J0YW5jaWFfdmFyc190ZXN0XzM5MDQxNTIgPC0gaW1wb3J0YW5jZShtb2RlbG9fcmZfMzkwNDE1Ml90ZXN0KQpwcmludChpbXBvcnRhbmNpYV92YXJzX3Rlc3RfMzkwNDE1MikKCiMgR3JhZmljYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCnZhckltcFBsb3QobW9kZWxvX3JmXzM5MDQxNTJfdGVzdCwgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAzOTA0MTUyICh0ZXN0IGRhdGEpIikKCiMgQ3JlYXIgZ3LDoWZpY28gZGUgdmFsb3JlcyBvYnNlcnZhZG9zIHZzIHByZWRpY2Npb25lcwpkYXRvc19ncmFmaWNvX3Rlc3RfMzkwNDE1MiA8LSBkYXRhLmZyYW1lKAogIE9ic2VydmFkbyA9IGRhdG9zX21vZGVsb190ZXN0XzM5MDQxNTIkVmVudGEsCiAgUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmZfdGVzdF8zOTA0MTUyCikKCmdncGxvdChkYXRvc19ncmFmaWNvX3Rlc3RfMzkwNDE1MiwgYWVzKHggPSBPYnNlcnZhZG8sIHkgPSBQcmVkaWNobykpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicygKICAgIHRpdGxlID0gIlZhbG9yZXMgT2JzZXJ2YWRvcyB2cyBQcmVkaWNjaW9uZXMgLSBQcm9kdWN0byAzOTA0MTUyICh0ZXN0IGRhdGEpIiwKICAgIHggPSAiVmVudGFzIE9ic2VydmFkYXMiLAogICAgeSA9ICJWZW50YXMgUHJlZGljaGFzIgogICkgKwogIHRoZW1lX21pbmltYWwoKQoKIyBOVUVWT1MgQU7DgUxJU0lTIEHDkUFESURPUwoKIyBBbsOhbGlzaXMgZGVsIGVycm9yCmVycm9yZXNfdGVzdF8zOTA0MTUyIDwtIGRhdG9zX2dyYWZpY29fdGVzdF8zOTA0MTUyJE9ic2VydmFkbyAtIGRhdG9zX2dyYWZpY29fdGVzdF8zOTA0MTUyJFByZWRpY2hvCmhpc3QoZXJyb3JlcywgCiAgICAgbWFpbiA9ICJEaXN0cmlidWNpw7NuIGRlIEVycm9yZXMgLSBQcm9kdWN0byAzOTA0MTUyICh0ZXN0IGRhdGEpIiwKICAgICB4bGFiID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiLAogICAgIGNvbCA9ICJza3libHVlIiwKICAgICBicmVha3MgPSAzMCkKCiMgRXN0YWTDrXN0aWNhcyBkZXNjcmlwdGl2YXMgZGUgbG9zIGVycm9yZXMKY2F0KCJFc3RhZMOtc3RpY2FzIGRlc2NyaXB0aXZhcyBkZSBsb3MgZXJyb3JlcyAodGVzdCBkYXRhKTpcbiIpCmNhdCgiTWVkaWEgZGUgZXJyb3JlczoiLCBtZWFuKGVycm9yZXMpLCAiXG4iKQpjYXQoIkRlc3ZpYWNpw7NuIGVzdMOhbmRhciBkZSBlcnJvcmVzOiIsIHNkKGVycm9yZXMpLCAiXG4iKQpjYXQoIk3DrW5pbW86IiwgbWluKGVycm9yZXMpLCAiXG4iKQpjYXQoIk3DoXhpbW86IiwgbWF4KGVycm9yZXMpLCAiXG4iKQpjYXQoIk1lZGlhbmE6IiwgbWVkaWFuKGVycm9yZXMpLCAiXG4iKQoKIyBHcsOhZmljbyBkZWwgZXJyb3IgdnMgcHJlZGljY2nDs24KZ2dwbG90KGRhdGEuZnJhbWUoUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmZfdGVzdF8zOTA0MTUyLCBFcnJvciA9IGVycm9yZXNfdGVzdF8zOTA0MTUyKSwgYWVzKHggPSBQcmVkaWNobywgeSA9IEVycm9yKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicygKICAgIHRpdGxlID0gIkVycm9yIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMzkwNDE1MiAodGVzdCBkYXRhKSIsCiAgICB4ID0gIlZlbnRhcyBQcmVkaWNoYXMiLAogICAgeSA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIgogICkgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCgpgYGB7cn0KIyBHdWFyZGFyIG3DqXRyaWNhcyBkZSBSYW5kb20gRm9yZXN0IHBhcmEgcHJvZHVjdG8gMTU1MDAxCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7CiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLAogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksCiAgICBNQVBFID0gbnVtZXJpYygpLAogICAgUk1TRSA9IG51bWVyaWMoKSwKCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCn0KCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMzkyOTc4OCIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bwogIE1vZGVsbyA9ICJSYW5kb20gRm9yZXN0IiwKICBNQVBFID0gbWFwZV9yZiwKICBSTVNFID0gcm1zZV9yZgoKKSkKYGBgCgoKYGBge3J9CiMgR3VhcmRhciBtw6l0cmljYXMgZGUgUmFuZG9tIEZvcmVzdCBwYXJhIHByb2R1Y3RvIDE1NTAwMQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgewogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLAogICAgTUFQRSA9IG51bWVyaWMoKSwKICAgIFJNU0UgPSBudW1lcmljKCksCgogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKQp9CgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjM5MDQxNTIiLCAgIyBDYW1iaWEgZXN0ZSBJRCBwYXJhIGNhZGEgcHJvZHVjdG8KICBNb2RlbG8gPSAiUmFuZG9tIEZvcmVzdCIsCiAgTUFQRSA9IG1hcGVfcmYsCiAgUk1TRSA9IHJtc2VfcmYKCikpCmBgYAoKCiMjIFBST0RVQ1RPIDE1NTAwMgoKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTZ9CiMgUHJlcGFyYXIgZGF0b3MgcGFyYSBlbCBtb2RlbG8gKGVsaW1pbmFyIGNvbHVtbmFzIG5vIG5lY2VzYXJpYXMpCmRhdG9zX21vZGVsbyA8LSBkYXRvc18xNTUwMDIgJT4lCiAgc2VsZWN0KC1GZWNoYSkKCiMgQWp1c3RhciBlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdApzZXQuc2VlZCgxMjMpICAjIFBhcmEgcmVwcm9kdWNpYmlsaWRhZAptb2RlbG9fcmZfMTU1MDAyIDwtIHJhbmRvbUZvcmVzdCgKICBWZW50YSB+IC4sIAogIGRhdGEgPSBkYXRvc19tb2RlbG8sCiAgbnRyZWUgPSA1MDAsICAgICAgICAgICMgTsO6bWVybyBkZSDDoXJib2xlcwogIG10cnkgPSBmbG9vcihzcXJ0KG5jb2woZGF0b3NfbW9kZWxvKSAtIDEpKSwgICMgTsO6bWVybyBkZSB2YXJpYWJsZXMgYSBjb25zaWRlcmFyIGVuIGNhZGEgc3BsaXQKICBpbXBvcnRhbmNlID0gVFJVRSAgICAgIyBDYWxjdWxhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKKQoKIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvCnByaW50KG1vZGVsb19yZl8xNTUwMDIpCgojIE9idGVuZXIgcHJlZGljY2lvbmVzCnByZWRpY2Npb25lc19yZl8xNTUwMDIgPC0gcHJlZGljdChtb2RlbG9fcmZfMTU1MDAyLCBuZXdkYXRhID0gZGF0b3NfbW9kZWxvKQoKIyBDYWxjdWxhciBtw6l0cmljYXMKIyBNQVBFCm1hcGVfcmYgPC0gbWVhbihhYnMoKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19yZl8xNTUwMDIpIC8gcG1heChkYXRvc19tb2RlbG8kVmVudGEsIDAuMDEpKSkgKiAxMDAKCiMgTVNFCnJtc2VfcmYgPC0gc3FydChtZWFuKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmZfMTU1MDAyKV4yKSkKCgojIE1vc3RyYXIgbGFzIG3DqXRyaWNhcwpjYXQoIk1vZGVsbyBSYW5kb20gRm9yZXN0IHBhcmEgcHJvZHVjdG8gMTU1MDAyXG4iKQpjYXQoIk1BUEUgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0OiIsIG1hcGVfcmYsICJcbiIpCmNhdCgiUk1TRSBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3Q6Iiwgcm1zZV9yZiwgIlxuXG4iKQoKIyBNb3N0cmFyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwppbXBvcnRhbmNpYV92YXJzIDwtIGltcG9ydGFuY2UobW9kZWxvX3JmXzE1NTAwMikKcHJpbnQoaW1wb3J0YW5jaWFfdmFycykKCiMgR3JhZmljYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCnZhckltcFBsb3QobW9kZWxvX3JmXzE1NTAwMiwgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAxNTUwMDIiKQoKIyBDcmVhciBncsOhZmljbyBkZSB2YWxvcmVzIG9ic2VydmFkb3MgdnMgcHJlZGljY2lvbmVzCmRhdG9zX2dyYWZpY29fMTU1MDAyIDwtIGRhdGEuZnJhbWUoCiAgT2JzZXJ2YWRvID0gZGF0b3NfbW9kZWxvJFZlbnRhLAogIFByZWRpY2hvID0gcHJlZGljY2lvbmVzX3JmXzE1NTAwMgopCgpnZ3Bsb3QoZGF0b3NfZ3JhZmljb18xNTUwMDIsIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJWYWxvcmVzIE9ic2VydmFkb3MgdnMgUHJlZGljY2lvbmVzIC0gUHJvZHVjdG8gMTU1MDAyIiwKICAgIHggPSAiVmVudGFzIE9ic2VydmFkYXMiLAogICAgeSA9ICJWZW50YXMgUHJlZGljaGFzIgogICkgKwogIHRoZW1lX21pbmltYWwoKQoKIyBBbsOhbGlzaXMgZGVsIGVycm9yCmVycm9yZXNfMTU1MDAyIDwtIGRhdG9zX2dyYWZpY29fMTU1MDAyJE9ic2VydmFkbyAtIGRhdG9zX2dyYWZpY29fMTU1MDAyJFByZWRpY2hvCmhpc3QoZXJyb3JlcywgCiAgICAgbWFpbiA9ICJEaXN0cmlidWNpw7NuIGRlIEVycm9yZXMgLSBQcm9kdWN0byAxNTUwMDIiLAogICAgIHhsYWIgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIsCiAgICAgY29sID0gInNreWJsdWUiLAogICAgIGJyZWFrcyA9IDMwKQoKIyBHcsOhZmljbyBkZWwgZXJyb3IgdnMgcHJlZGljY2nDs24KZ2dwbG90KGRhdGEuZnJhbWUoUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmZfMTU1MDAyLCBFcnJvciA9IGVycm9yZXNfMTU1MDAyKSwgYWVzKHggPSBQcmVkaWNobywgeSA9IEVycm9yKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicygKICAgIHRpdGxlID0gIkVycm9yIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMTU1MDAyIiwKICAgIHggPSAiVmVudGFzIFByZWRpY2hhcyIsCiAgICB5ID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKYGBge3J9CiMgUHJlcGFyYXIgZGF0b3MgcGFyYSBlbCBtb2RlbG8gKGVsaW1pbmFyIGNvbHVtbmFzIG5vIG5lY2VzYXJpYXMpCmRhdG9zX21vZGVsb190ZXN0XzE1NTAwMiA8LSB0ZXN0XzE1NTAwMgoKIyBBanVzdGFyIGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0CnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkCm1vZGVsb19yZl8xNTUwMDJfdGVzdCA8LSByYW5kb21Gb3Jlc3QoCiAgVmVudGEgfiAuLCAKICBkYXRhID0gZGF0b3NfbW9kZWxvX3Rlc3RfMTU1MDAyLAogIG50cmVlID0gNTAwLCAgICAgICAgICAjIE7Dum1lcm8gZGUgw6FyYm9sZXMKICBtdHJ5ID0gZmxvb3Ioc3FydChuY29sKGRhdG9zX21vZGVsb190ZXN0XzE1NTAwMikgLSAxKSksICAjIE7Dum1lcm8gZGUgdmFyaWFibGVzIGEgY29uc2lkZXJhciBlbiBjYWRhIHNwbGl0CiAgaW1wb3J0YW5jZSA9IFRSVUUgICAgICMgQ2FsY3VsYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCikKCiMgVmVyIHJlc3VtZW4gZGVsIG1vZGVsbwpwcmludChtb2RlbG9fcmZfMTU1MDAyX3Rlc3QpCgojIE9idGVuZXIgcHJlZGljY2lvbmVzCnByZWRpY2Npb25lc19yZl90ZXN0XzE1NTAwMiA8LSBwcmVkaWN0KG1vZGVsb19yZl8xNTUwMDJfdGVzdCwgbmV3ZGF0YSA9IGRhdG9zX21vZGVsb190ZXN0XzE1NTAwMikKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzCiMgTUFQRQptYXBlX3JmX3Rlc3RfMTU1MDAyIDwtIG1lYW4oYWJzKChkYXRvc19tb2RlbG9fdGVzdF8xNTUwMDIkVmVudGEgLSBwcmVkaWNjaW9uZXNfcmZfdGVzdF8xNTUwMDIpIC8gcG1heChkYXRvc19tb2RlbG9fdGVzdF8xNTUwMDIkVmVudGEsIDAuMDEpKSkgKiAxMDAKCiMgUk1TRQpybXNlX3JmX3Rlc3RfMTU1MDAyIDwtIG1lYW4oKGRhdG9zX21vZGVsb190ZXN0XzE1NTAwMiRWZW50YSAtIHByZWRpY2Npb25lc19yZl90ZXN0XzE1NTAwMileMikKCiMgTW9zdHJhciBsYXMgbcOpdHJpY2FzCmNhdCgiTW9kZWxvIFJhbmRvbSBGb3Jlc3QgcGFyYSBwcm9kdWN0byAodGVzdCBkYXRhKSAxNTUwMDJcbiIpCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3QgKHRlc3QgZGF0YSk6IiwgbWFwZV9yZl90ZXN0XzE1NTAwMiwgIlxuIikKY2F0KCJSTVNFIGRlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdCAodGVzdCBkYXRhKToiLCBybXNlX3JmX3Rlc3RfMTU1MDAyLCAiXG5cbiIpCgojIE1vc3RyYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCmltcG9ydGFuY2lhX3ZhcnNfdGVzdF8xNTUwMDIgPC0gaW1wb3J0YW5jZShtb2RlbG9fcmZfMTU1MDAyX3Rlc3QpCnByaW50KGltcG9ydGFuY2lhX3ZhcnNfdGVzdF8xNTUwMDIpCgojIEdyYWZpY2FyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwp2YXJJbXBQbG90KG1vZGVsb19yZl8xNTUwMDJfdGVzdCwgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAxNTUwMDIgKHRlc3QgZGF0YSkiKQoKIyBDcmVhciBncsOhZmljbyBkZSB2YWxvcmVzIG9ic2VydmFkb3MgdnMgcHJlZGljY2lvbmVzCmRhdG9zX2dyYWZpY29fdGVzdF8xNTUwMDIgPC0gZGF0YS5mcmFtZSgKICBPYnNlcnZhZG8gPSBkYXRvc19tb2RlbG9fdGVzdF8xNTUwMDIkVmVudGEsCiAgUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmZfdGVzdF8xNTUwMDIKKQoKZ2dwbG90KGRhdG9zX2dyYWZpY29fdGVzdF8xNTUwMDIsIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJWYWxvcmVzIE9ic2VydmFkb3MgdnMgUHJlZGljY2lvbmVzIC0gUHJvZHVjdG8gMTU1MDAyICh0ZXN0IGRhdGEpIiwKICAgIHggPSAiVmVudGFzIE9ic2VydmFkYXMiLAogICAgeSA9ICJWZW50YXMgUHJlZGljaGFzIgogICkgKwogIHRoZW1lX21pbmltYWwoKQoKIyBOVUVWT1MgQU7DgUxJU0lTIEHDkUFESURPUwoKIyBBbsOhbGlzaXMgZGVsIGVycm9yCmVycm9yZXNfdGVzdF8xNTUwMDIgPC0gZGF0b3NfZ3JhZmljb190ZXN0XzE1NTAwMiRPYnNlcnZhZG8gLSBkYXRvc19ncmFmaWNvX3Rlc3RfMTU1MDAyJFByZWRpY2hvCmhpc3QoZXJyb3JlcywgCiAgICAgbWFpbiA9ICJEaXN0cmlidWNpw7NuIGRlIEVycm9yZXMgLSBQcm9kdWN0byAxNTUwMDIgKHRlc3QgZGF0YSkiLAogICAgIHhsYWIgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIsCiAgICAgY29sID0gInNreWJsdWUiLAogICAgIGJyZWFrcyA9IDMwKQoKIyBFc3RhZMOtc3RpY2FzIGRlc2NyaXB0aXZhcyBkZSBsb3MgZXJyb3JlcwpjYXQoIkVzdGFkw61zdGljYXMgZGVzY3JpcHRpdmFzIGRlIGxvcyBlcnJvcmVzICh0ZXN0IGRhdGEpOlxuIikKY2F0KCJNZWRpYSBkZSBlcnJvcmVzOiIsIG1lYW4oZXJyb3JlcyksICJcbiIpCmNhdCgiRGVzdmlhY2nDs24gZXN0w6FuZGFyIGRlIGVycm9yZXM6Iiwgc2QoZXJyb3JlcyksICJcbiIpCmNhdCgiTcOtbmltbzoiLCBtaW4oZXJyb3JlcyksICJcbiIpCmNhdCgiTcOheGltbzoiLCBtYXgoZXJyb3JlcyksICJcbiIpCmNhdCgiTWVkaWFuYToiLCBtZWRpYW4oZXJyb3JlcyksICJcbiIpCgojIEdyw6FmaWNvIGRlbCBlcnJvciB2cyBwcmVkaWNjacOzbgpnZ3Bsb3QoZGF0YS5mcmFtZShQcmVkaWNobyA9IHByZWRpY2Npb25lc19yZl90ZXN0XzE1NTAwMiwgRXJyb3IgPSBlcnJvcmVzX3Rlc3RfMTU1MDAyKSwgYWVzKHggPSBQcmVkaWNobywgeSA9IEVycm9yKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicygKICAgIHRpdGxlID0gIkVycm9yIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMzkwNDE1MiAodGVzdCBkYXRhKSIsCiAgICB4ID0gIlZlbnRhcyBQcmVkaWNoYXMiLAogICAgeSA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIgogICkgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCgpgYGB7cn0KIyBHdWFyZGFyIG3DqXRyaWNhcyBkZSBSYW5kb20gRm9yZXN0IHBhcmEgcHJvZHVjdG8gMTU1MDAyCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7CiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLAogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksCiAgICBNQVBFID0gbnVtZXJpYygpLAogICAgUk1TRSA9IG51bWVyaWMoKSwKICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQogICkKfQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIxNTUwMDIiLCAgIyBDYW1iaWEgZXN0ZSBJRCBwYXJhIGNhZGEgcHJvZHVjdG8KICBNb2RlbG8gPSAiUmFuZG9tIEZvcmVzdCIsCiAgTUFQRSA9IG1hcGVfcmYsCiAgUk1TRSA9IHJtc2VfcmYKCikpCmBgYAoKCiMjIFBST0RVQ1RPIDM2NzgwNTUKCmBgYHtyLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD02fQojIFByZXBhcmFyIGRhdG9zIHBhcmEgZWwgbW9kZWxvIChlbGltaW5hciBjb2x1bW5hcyBubyBuZWNlc2FyaWFzKQpkYXRvc19tb2RlbG8gPC0gZGF0b3NfMzY3ODA1NSAlPiUKICBzZWxlY3QoLUZlY2hhKQoKIyBBanVzdGFyIGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0CnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkCm1vZGVsb19yZl8zNjc4MDU1IDwtIHJhbmRvbUZvcmVzdCgKICBWZW50YSB+IC4sIAogIGRhdGEgPSBkYXRvc19tb2RlbG8sCiAgbnRyZWUgPSA1MDAsICAgICAgICAgICMgTsO6bWVybyBkZSDDoXJib2xlcwogIG10cnkgPSBmbG9vcihzcXJ0KG5jb2woZGF0b3NfbW9kZWxvKSAtIDEpKSwgICMgTsO6bWVybyBkZSB2YXJpYWJsZXMgYSBjb25zaWRlcmFyIGVuIGNhZGEgc3BsaXQKICBpbXBvcnRhbmNlID0gVFJVRSAgICAgIyBDYWxjdWxhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKKQoKIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvCnByaW50KG1vZGVsb19yZl8zNjc4MDU1KQoKIyBPYnRlbmVyIHByZWRpY2Npb25lcwpwcmVkaWNjaW9uZXNfcmZfMzY3ODA1NSA8LSBwcmVkaWN0KG1vZGVsb19yZl8zNjc4MDU1LCBuZXdkYXRhID0gZGF0b3NfbW9kZWxvKQoKIyBDYWxjdWxhciBtw6l0cmljYXMKIyBNQVBFCm1hcGVfcmYgPC0gbWVhbihhYnMoKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19yZl8zNjc4MDU1KSAvIHBtYXgoZGF0b3NfbW9kZWxvJFZlbnRhLCAwLjAxKSkpICogMTAwCgojIFJNU0UKcm1zZV9yZiA8LSBzcXJ0KG1lYW4oKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19yZl8zNjc4MDU1KV4yKSkKCgojIE1vc3RyYXIgbGFzIG3DqXRyaWNhcwpjYXQoIk1vZGVsbyBSYW5kb20gRm9yZXN0IHBhcmEgcHJvZHVjdG8gMzY3ODA1NVxuIikKY2F0KCJNQVBFIGRlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdDoiLCBtYXBlX3JmLCAiXG4iKQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0OiIsIHJtc2VfcmYsICJcblxuIikKCiMgTW9zdHJhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKaW1wb3J0YW5jaWFfdmFycyA8LSBpbXBvcnRhbmNlKG1vZGVsb19yZl8zNjc4MDU1KQpwcmludChpbXBvcnRhbmNpYV92YXJzKQoKIyBHcmFmaWNhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKdmFySW1wUGxvdChtb2RlbG9fcmZfMzY3ODA1NSwgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAzNjc4MDU1IikKCiMgQ3JlYXIgZ3LDoWZpY28gZGUgdmFsb3JlcyBvYnNlcnZhZG9zIHZzIHByZWRpY2Npb25lcwpkYXRvc19ncmFmaWNvXzM2NzgwNTUgPC0gZGF0YS5mcmFtZSgKICBPYnNlcnZhZG8gPSBkYXRvc19tb2RlbG8kVmVudGEsCiAgUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmZfMzY3ODA1NQopCgpnZ3Bsb3QoZGF0b3NfZ3JhZmljb18zNjc4MDU1LCBhZXMoeCA9IE9ic2VydmFkbywgeSA9IFByZWRpY2hvKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDM2NzgwNTUiLAogICAgeCA9ICJWZW50YXMgT2JzZXJ2YWRhcyIsCiAgICB5ID0gIlZlbnRhcyBQcmVkaWNoYXMiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIEFuw6FsaXNpcyBkZWwgZXJyb3IKZXJyb3Jlc18zNjc4MDU1IDwtIGRhdG9zX2dyYWZpY29fMzY3ODA1NSRPYnNlcnZhZG8gLSBkYXRvc19ncmFmaWNvXzM2NzgwNTUkUHJlZGljaG8KaGlzdChlcnJvcmVzLCAKICAgICBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDM2NzgwNTUiLAogICAgIHhsYWIgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIsCiAgICAgY29sID0gInNreWJsdWUiLAogICAgIGJyZWFrcyA9IDMwKQoKIyBHcsOhZmljbyBkZWwgZXJyb3IgdnMgcHJlZGljY2nDs24KZ2dwbG90KGRhdGEuZnJhbWUoUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmZfMzY3ODA1NSwgRXJyb3IgPSBlcnJvcmVzXzM2NzgwNTUpLCBhZXMoeCA9IFByZWRpY2hvLCB5ID0gRXJyb3IpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiRXJyb3IgdnMgUHJlZGljY2nDs24gLSBQcm9kdWN0byAzNjc4MDU1IiwKICAgIHggPSAiVmVudGFzIFByZWRpY2hhcyIsCiAgICB5ID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYApgYGB7cn0KIyBHdWFyZGFyIG3DqXRyaWNhcyBkZSBSYW5kb20gRm9yZXN0IHBhcmEgcHJvZHVjdG8gMTU1MDAxCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7CiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLAogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksCiAgICBNQVBFID0gbnVtZXJpYygpLAogICAgUk1TRSA9IG51bWVyaWMoKSwKICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQogICkKfQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIzNjc4MDU1IiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvCiAgTW9kZWxvID0gIlJhbmRvbSBGb3Jlc3QiLAogIE1BUEUgPSBtYXBlX3JmLAogIFJNU0UgPSBybXNlX3JmCikpCmBgYAoKYGBge3J9CiMgUHJlcGFyYXIgZGF0b3MgcGFyYSBlbCBtb2RlbG8gKGVsaW1pbmFyIGNvbHVtbmFzIG5vIG5lY2VzYXJpYXMpCmRhdG9zX21vZGVsb190ZXN0XzM2NzgwNTUgPC0gdGVzdF8zNjc4MDU1CgojIEFqdXN0YXIgZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3QKc2V0LnNlZWQoMTIzKSAgIyBQYXJhIHJlcHJvZHVjaWJpbGlkYWQKbW9kZWxvX3JmXzM2NzgwNTVfdGVzdCA8LSByYW5kb21Gb3Jlc3QoCiAgVmVudGEgfiAuLCAKICBkYXRhID0gZGF0b3NfbW9kZWxvX3Rlc3RfMzY3ODA1NSwKICBudHJlZSA9IDUwMCwgICAgICAgICAgIyBOw7ptZXJvIGRlIMOhcmJvbGVzCiAgbXRyeSA9IGZsb29yKHNxcnQobmNvbChkYXRvc19tb2RlbG9fdGVzdF8zNjc4MDU1KSAtIDEpKSwgICMgTsO6bWVybyBkZSB2YXJpYWJsZXMgYSBjb25zaWRlcmFyIGVuIGNhZGEgc3BsaXQKICBpbXBvcnRhbmNlID0gVFJVRSAgICAgIyBDYWxjdWxhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKKQoKIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvCnByaW50KG1vZGVsb19yZl8zNjc4MDU1X3Rlc3QpCgojIE9idGVuZXIgcHJlZGljY2lvbmVzCnByZWRpY2Npb25lc19yZl90ZXN0XzM2NzgwNTUgPC0gcHJlZGljdChtb2RlbG9fcmZfMzY3ODA1NV90ZXN0LCBuZXdkYXRhID0gZGF0b3NfbW9kZWxvX3Rlc3RfMzY3ODA1NSkKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzCiMgTUFQRQptYXBlX3JmX3Rlc3RfMzY3ODA1NSA8LSBtZWFuKGFicygoZGF0b3NfbW9kZWxvX3Rlc3RfMzY3ODA1NSRWZW50YSAtIHByZWRpY2Npb25lc19yZl90ZXN0XzM2NzgwNTUpIC8gcG1heChkYXRvc19tb2RlbG9fdGVzdF8zNjc4MDU1JFZlbnRhLCAwLjAxKSkpICogMTAwCgojIFJNU0UKcm1zZV9yZl90ZXN0XzM2NzgwNTUgPC0gbWVhbigoZGF0b3NfbW9kZWxvX3Rlc3RfMzY3ODA1NSRWZW50YSAtIHByZWRpY2Npb25lc19yZl90ZXN0XzM2NzgwNTUpXjIpCgojIE1vc3RyYXIgbGFzIG3DqXRyaWNhcwpjYXQoIk1vZGVsbyBSYW5kb20gRm9yZXN0IHBhcmEgcHJvZHVjdG8gKHRlc3QgZGF0YSkgMzY3ODA1NVxuIikKY2F0KCJNQVBFIGRlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdCAodGVzdCBkYXRhKToiLCBtYXBlX3JmX3Rlc3RfMzY3ODA1NSwgIlxuIikKY2F0KCJSTVNFIGRlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdCAodGVzdCBkYXRhKToiLCBybXNlX3JmX3Rlc3RfMzY3ODA1NSwgIlxuXG4iKQoKIyBNb3N0cmFyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwppbXBvcnRhbmNpYV92YXJzX3Rlc3RfMzY3ODA1NSA8LSBpbXBvcnRhbmNlKG1vZGVsb19yZl8zNjc4MDU1X3Rlc3QpCnByaW50KGltcG9ydGFuY2lhX3ZhcnNfdGVzdF8zNjc4MDU1KQoKIyBHcmFmaWNhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKdmFySW1wUGxvdChtb2RlbG9fcmZfMzY3ODA1NV90ZXN0LCBtYWluID0gIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyAtIFByb2R1Y3RvIDM2NzgwNTUgKHRlc3QgZGF0YSkiKQoKIyBDcmVhciBncsOhZmljbyBkZSB2YWxvcmVzIG9ic2VydmFkb3MgdnMgcHJlZGljY2lvbmVzCmRhdG9zX2dyYWZpY29fdGVzdF8zNjc4MDU1IDwtIGRhdGEuZnJhbWUoCiAgT2JzZXJ2YWRvID0gZGF0b3NfbW9kZWxvX3Rlc3RfMzY3ODA1NSRWZW50YSwKICBQcmVkaWNobyA9IHByZWRpY2Npb25lc19yZl90ZXN0XzM2NzgwNTUKKQoKZ2dwbG90KGRhdG9zX2dyYWZpY29fdGVzdF8zNjc4MDU1LCBhZXMoeCA9IE9ic2VydmFkbywgeSA9IFByZWRpY2hvKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDE1NTAwMiAodGVzdCBkYXRhKSIsCiAgICB4ID0gIlZlbnRhcyBPYnNlcnZhZGFzIiwKICAgIHkgPSAiVmVudGFzIFByZWRpY2hhcyIKICApICsKICB0aGVtZV9taW5pbWFsKCkKCiMgTlVFVk9TIEFOw4FMSVNJUyBBw5FBRElET1MKCiMgQW7DoWxpc2lzIGRlbCBlcnJvcgplcnJvcmVzX3Rlc3RfMzY3ODA1NSA8LSBkYXRvc19ncmFmaWNvX3Rlc3RfMzY3ODA1NSRPYnNlcnZhZG8gLSBkYXRvc19ncmFmaWNvX3Rlc3RfMzY3ODA1NSRQcmVkaWNobwpoaXN0KGVycm9yZXMsIAogICAgIG1haW4gPSAiRGlzdHJpYnVjacOzbiBkZSBFcnJvcmVzIC0gUHJvZHVjdG8gMzY3ODA1NSAodGVzdCBkYXRhKSIsCiAgICAgeGxhYiA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIiwKICAgICBjb2wgPSAic2t5Ymx1ZSIsCiAgICAgYnJlYWtzID0gMzApCgojIEVzdGFkw61zdGljYXMgZGVzY3JpcHRpdmFzIGRlIGxvcyBlcnJvcmVzCmNhdCgiRXN0YWTDrXN0aWNhcyBkZXNjcmlwdGl2YXMgZGUgbG9zIGVycm9yZXMgKHRlc3QgZGF0YSk6XG4iKQpjYXQoIk1lZGlhIGRlIGVycm9yZXM6IiwgbWVhbihlcnJvcmVzKSwgIlxuIikKY2F0KCJEZXN2aWFjacOzbiBlc3TDoW5kYXIgZGUgZXJyb3JlczoiLCBzZChlcnJvcmVzKSwgIlxuIikKY2F0KCJNw61uaW1vOiIsIG1pbihlcnJvcmVzKSwgIlxuIikKY2F0KCJNw6F4aW1vOiIsIG1heChlcnJvcmVzKSwgIlxuIikKY2F0KCJNZWRpYW5hOiIsIG1lZGlhbihlcnJvcmVzKSwgIlxuIikKCiMgR3LDoWZpY28gZGVsIGVycm9yIHZzIHByZWRpY2Npw7NuCmdncGxvdChkYXRhLmZyYW1lKFByZWRpY2hvID0gcHJlZGljY2lvbmVzX3JmX3Rlc3RfMzY3ODA1NSwgRXJyb3IgPSBlcnJvcmVzX3Rlc3RfMzY3ODA1NSksIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJFcnJvciB2cyBQcmVkaWNjacOzbiAtIFByb2R1Y3RvIDM2NzgwNTUgKHRlc3QgZGF0YSkiLAogICAgeCA9ICJWZW50YXMgUHJlZGljaGFzIiwKICAgIHkgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIKICApICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgoKIyBYR0JPT1NUCgojIyBQUk9EVUNUTyAxNTUwMDEKYGBge3J9CiMgUHJlcGFyYXIgZGF0b3MgcGFyYSBlbCBtb2RlbG8gKGVsaW1pbmFyIGNvbHVtbmFzIG5vIG5lY2VzYXJpYXMpCmRhdG9zX21vZGVsbyA8LSBkYXRvc18xNTUwMDEgJT4lCiAgc2VsZWN0KC1UcnhfRmVjaGEsIC1GZWNoYSkKCiMgRGl2aWRpciBsb3MgZGF0b3MgZW4gY29uanVudG9zIGRlIGVudHJlbmFtaWVudG8gKDgwJSkgeSBwcnVlYmEgKDIwJSkKc2V0LnNlZWQoMTIzKSAgIyBQYXJhIHJlcHJvZHVjaWJpbGlkYWQKaW5kaWNlc190cmFpbiA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGRhdG9zX21vZGVsbyRWZW50YSwgcCA9IDAuOCwgbGlzdCA9IEZBTFNFKQpkYXRvc190cmFpbiA8LSBkYXRvc19tb2RlbG9baW5kaWNlc190cmFpbiwgXQpkYXRvc190ZXN0IDwtIGRhdG9zX21vZGVsb1staW5kaWNlc190cmFpbiwgXQoKIyBTZXBhcmFyIHZhcmlhYmxlcyBwcmVkaWN0b3JhcyB5IHZhcmlhYmxlIG9iamV0aXZvClhfdHJhaW4gPC0gYXMubWF0cml4KGRhdG9zX3RyYWluWywgY29sbmFtZXMoZGF0b3NfdHJhaW4pICE9ICJWZW50YSJdKQp5X3RyYWluIDwtIGRhdG9zX3RyYWluJFZlbnRhCgpYX3Rlc3QgPC0gYXMubWF0cml4KGRhdG9zX3Rlc3RbLCBjb2xuYW1lcyhkYXRvc190ZXN0KSAhPSAiVmVudGEiXSkKeV90ZXN0IDwtIGRhdG9zX3Rlc3QkVmVudGEKCiMgQ3JlYXIgbWF0cmljZXMgRE1hdHJpeCBwYXJhIFhHQm9vc3QKZHRyYWluIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBYX3RyYWluLCBsYWJlbCA9IHlfdHJhaW4pCmR0ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSBYX3Rlc3QsIGxhYmVsID0geV90ZXN0KQoKIyBEZWZpbmlyIHVuYSByZWppbGxhIGNvbXBsZXRhIGRlIGhpcGVycGFyw6FtZXRyb3MgcGFyYSBiw7pzcXVlZGEKcGFyYW1fZ3JpZCA8LSBleHBhbmQuZ3JpZCgKICBldGEgPSBjKDAuMDEsIDAuMDUsIDAuMSwgMC4zKSwgICAgICAgICAjIExlYXJuaW5nIHJhdGUKICBtYXhfZGVwdGggPSBjKDMsIDUsIDcsIDkpLCAgICAgICAgICAgICAjIFByb2Z1bmRpZGFkIG3DoXhpbWEKICBzdWJzYW1wbGUgPSBjKDAuNiwgMC44LCAxLjApLCAgICAgICAgICAjIFN1Ym11ZXN0cmEgZGUgb2JzZXJ2YWNpb25lcwogIGNvbHNhbXBsZV9ieXRyZWUgPSBjKDAuNiwgMC44LCAxLjApLCAgICMgU3VibXVlc3RyYSBkZSB2YXJpYWJsZXMKICBtaW5fY2hpbGRfd2VpZ2h0ID0gYygxLCAzLCA1KSwgICAgICAgICAjIFBlc28gbcOtbmltbyBlbiBub2RvcyBoaWpvcwogIGdhbW1hID0gYygwLCAwLjEsIDAuMykgICAgICAgICAgICAgICAgICMgUmVndWxhcml6YWNpw7NuIGdhbW1hCikKCiMgTW9zdHJhciBjdcOhbnRhcyBjb21iaW5hY2lvbmVzIHRlbmVtb3MKY2F0KCJOw7ptZXJvIHRvdGFsIGRlIGNvbWJpbmFjaW9uZXMgZGUgaGlwZXJwYXLDoW1ldHJvczoiLCBucm93KHBhcmFtX2dyaWQpLCAiXG4iKQoKIyBQYXJhIGVzdGUgZWplbXBsbywgdmFtb3MgYSBsaW1pdGFyIGVsIG7Dum1lcm8gZGUgY29tYmluYWNpb25lcwojIFNlbGVjY2lvbmFuZG8gdW4gc3ViY29uanVudG8gYWxlYXRvcmlvIGRlIGNvbWJpbmFjaW9uZXMgKDIwIGNvbWJpbmFjaW9uZXMpCnNldC5zZWVkKDEyMykKaWYgKG5yb3cocGFyYW1fZ3JpZCkgPiAyMCkgewogIG11ZXN0cmFfaW5kaWNlcyA8LSBzYW1wbGUoMTpucm93KHBhcmFtX2dyaWQpLCAyMCkKICBwYXJhbV9ncmlkX3JlZHVjaWRhIDwtIHBhcmFtX2dyaWRbbXVlc3RyYV9pbmRpY2VzLCBdCn0gZWxzZSB7CiAgcGFyYW1fZ3JpZF9yZWR1Y2lkYSA8LSBwYXJhbV9ncmlkCn0KCmNhdCgiTsO6bWVybyBkZSBjb21iaW5hY2lvbmVzIGEgZXZhbHVhcjoiLCBucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpLCAiXG4iKQoKIyBGdW5jacOzbiBwYXJhIGV2YWx1YXIgdW4gY29uanVudG8gZGUgaGlwZXJwYXLDoW1ldHJvcyBjb24gdmFsaWRhY2nDs24gY3J1emFkYQpldmFsdWF0ZV9wYXJhbXMgPC0gZnVuY3Rpb24ocGFyYW1zX3JvdykgewogIHBhcmFtcyA8LSBsaXN0KAogICAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLAogICAgZXZhbF9tZXRyaWMgPSAicm1zZSIsCiAgICBldGEgPSBwYXJhbXNfcm93JGV0YSwKICAgIG1heF9kZXB0aCA9IHBhcmFtc19yb3ckbWF4X2RlcHRoLAogICAgc3Vic2FtcGxlID0gcGFyYW1zX3JvdyRzdWJzYW1wbGUsCiAgICBjb2xzYW1wbGVfYnl0cmVlID0gcGFyYW1zX3JvdyRjb2xzYW1wbGVfYnl0cmVlLAogICAgbWluX2NoaWxkX3dlaWdodCA9IHBhcmFtc19yb3ckbWluX2NoaWxkX3dlaWdodCwKICAgIGdhbW1hID0gcGFyYW1zX3JvdyRnYW1tYQogICkKICAKICAjIFJlYWxpemFyIHZhbGlkYWNpw7NuIGNydXphZGEKICBjdl9yZXN1bHRzIDwtIHhnYi5jdigKICAgIHBhcmFtcyA9IHBhcmFtcywKICAgIGRhdGEgPSBkdHJhaW4sCiAgICBucm91bmRzID0gMTAwLAogICAgbmZvbGQgPSA1LCAgIyA1LWZvbGQgdmFsaWRhY2nDs24gY3J1emFkYQogICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gMTAsCiAgICB2ZXJib3NlID0gMAogICkKICAKICAjIEV4dHJhZXIgZWwgbWVqb3IgUk1TRSB5IGVsIG7Dum1lcm8gw7NwdGltbyBkZSByb25kYXMKICBiZXN0X3Jtc2UgPC0gbWluKGN2X3Jlc3VsdHMkZXZhbHVhdGlvbl9sb2ckdGVzdF9ybXNlX21lYW4pCiAgYmVzdF9ucm91bmRzIDwtIHdoaWNoLm1pbihjdl9yZXN1bHRzJGV2YWx1YXRpb25fbG9nJHRlc3Rfcm1zZV9tZWFuKQogIAogIHJldHVybihsaXN0KHJtc2UgPSBiZXN0X3Jtc2UsIG5yb3VuZHMgPSBiZXN0X25yb3VuZHMsIHBhcmFtcyA9IHBhcmFtcykpCn0KCiMgSW5pY2lhbGl6YXIgdGFibGEgcGFyYSBhbG1hY2VuYXIgcmVzdWx0YWRvcwpyZXN1bHRhZG9zX2dyaWQgPC0gZGF0YS5mcmFtZSgKICBldGEgPSBudW1lcmljKG5yb3cocGFyYW1fZ3JpZF9yZWR1Y2lkYSkpLAogIG1heF9kZXB0aCA9IG51bWVyaWMobnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSksCiAgc3Vic2FtcGxlID0gbnVtZXJpYyhucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpKSwKICBjb2xzYW1wbGVfYnl0cmVlID0gbnVtZXJpYyhucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpKSwKICBtaW5fY2hpbGRfd2VpZ2h0ID0gbnVtZXJpYyhucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpKSwKICBnYW1tYSA9IG51bWVyaWMobnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSksCiAgbnJvdW5kcyA9IG51bWVyaWMobnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSksCiAgcm1zZSA9IG51bWVyaWMobnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSkKKQoKIyBSZWFsaXphciBsYSBiw7pzcXVlZGEgZW4gY3VhZHLDrWN1bGEgKGVzdG8gcHVlZGUgdGFyZGFyIHZhcmlvcyBtaW51dG9zKQpjYXQoIkluaWNpYW5kbyBiw7pzcXVlZGEgZW4gY3VhZHLDrWN1bGEuLi5cbiIpCgpmb3IgKGkgaW4gMTpucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpKSB7CiAgY2F0KHNwcmludGYoIkV2YWx1YW5kbyBjb21iaW5hY2nDs24gJWQgZGUgJWRcbiIsIGksIG5yb3cocGFyYW1fZ3JpZF9yZWR1Y2lkYSkpKQogIAogICMgT2J0ZW5lciBmaWxhIGRlIHBhcsOhbWV0cm9zIGFjdHVhbAogIHBhcmFtc19yb3cgPC0gcGFyYW1fZ3JpZF9yZWR1Y2lkYVtpLCBdCiAgCiAgIyBFdmFsdWFyIGNvbWJpbmFjacOzbiBhY3R1YWwKICByZXN1bHQgPC0gZXZhbHVhdGVfcGFyYW1zKHBhcmFtc19yb3cpCiAgCiAgIyBHdWFyZGFyIHJlc3VsdGFkb3MKICByZXN1bHRhZG9zX2dyaWQkZXRhW2ldIDwtIHBhcmFtc19yb3ckZXRhCiAgcmVzdWx0YWRvc19ncmlkJG1heF9kZXB0aFtpXSA8LSBwYXJhbXNfcm93JG1heF9kZXB0aAogIHJlc3VsdGFkb3NfZ3JpZCRzdWJzYW1wbGVbaV0gPC0gcGFyYW1zX3JvdyRzdWJzYW1wbGUKICByZXN1bHRhZG9zX2dyaWQkY29sc2FtcGxlX2J5dHJlZVtpXSA8LSBwYXJhbXNfcm93JGNvbHNhbXBsZV9ieXRyZWUKICByZXN1bHRhZG9zX2dyaWQkbWluX2NoaWxkX3dlaWdodFtpXSA8LSBwYXJhbXNfcm93JG1pbl9jaGlsZF93ZWlnaHQKICByZXN1bHRhZG9zX2dyaWQkZ2FtbWFbaV0gPC0gcGFyYW1zX3JvdyRnYW1tYQogIHJlc3VsdGFkb3NfZ3JpZCRucm91bmRzW2ldIDwtIHJlc3VsdCRucm91bmRzCiAgcmVzdWx0YWRvc19ncmlkJHJtc2VbaV0gPC0gcmVzdWx0JHJtc2UKfQoKIyBPcmRlbmFyIHJlc3VsdGFkb3MgcG9yIFJNU0UgKGRlIG1lbm9yIGEgbWF5b3IpCnJlc3VsdGFkb3NfZ3JpZCA8LSByZXN1bHRhZG9zX2dyaWRbb3JkZXIocmVzdWx0YWRvc19ncmlkJHJtc2UpLCBdCgojIE1vc3RyYXIgbG9zIDUgbWVqb3JlcyBjb25qdW50b3MgZGUgaGlwZXJwYXLDoW1ldHJvcwpjYXQoIlxuTG9zIDUgbWVqb3JlcyBjb25qdW50b3MgZGUgaGlwZXJwYXLDoW1ldHJvczpcbiIpCnByaW50KGhlYWQocmVzdWx0YWRvc19ncmlkLCA1KSkKCiMgT2J0ZW5lciBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zCm1lam9yZXNfcGFyYW1zIDwtIGxpc3QoCiAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLAogIGV2YWxfbWV0cmljID0gInJtc2UiLAogIGV0YSA9IHJlc3VsdGFkb3NfZ3JpZCRldGFbMV0sCiAgbWF4X2RlcHRoID0gcmVzdWx0YWRvc19ncmlkJG1heF9kZXB0aFsxXSwKICBzdWJzYW1wbGUgPSByZXN1bHRhZG9zX2dyaWQkc3Vic2FtcGxlWzFdLAogIGNvbHNhbXBsZV9ieXRyZWUgPSByZXN1bHRhZG9zX2dyaWQkY29sc2FtcGxlX2J5dHJlZVsxXSwKICBtaW5fY2hpbGRfd2VpZ2h0ID0gcmVzdWx0YWRvc19ncmlkJG1pbl9jaGlsZF93ZWlnaHRbMV0sCiAgZ2FtbWEgPSByZXN1bHRhZG9zX2dyaWQkZ2FtbWFbMV0KKQoKbWVqb3JfbnJvdW5kcyA8LSByZXN1bHRhZG9zX2dyaWQkbnJvdW5kc1sxXQoKY2F0KCJcbk1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcyBlbmNvbnRyYWRvczpcbiIpCnByaW50KG1lam9yZXNfcGFyYW1zKQpjYXQoIk7Dum1lcm8gw7NwdGltbyBkZSByb25kYXM6IiwgbWVqb3JfbnJvdW5kcywgIlxuIikKY2F0KCJSTVNFIGVuIHZhbGlkYWNpw7NuIGNydXphZGE6IiwgcmVzdWx0YWRvc19ncmlkJHJtc2VbMV0sICJcblxuIikKCiMgRW50cmVuYXIgZWwgbW9kZWxvIGZpbmFsIGNvbiBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zCm1vZGVsb194Z2JfMTU1MDAxIDwtIHhnYi50cmFpbigKICBwYXJhbXMgPSBtZWpvcmVzX3BhcmFtcywKICBkYXRhID0gZHRyYWluLAogIG5yb3VuZHMgPSBtZWpvcl9ucm91bmRzLAogIHdhdGNobGlzdCA9IGxpc3QodHJhaW4gPSBkdHJhaW4sIHRlc3QgPSBkdGVzdCksCiAgdmVyYm9zZSA9IDAKKQoKIyBIYWNlciBwcmVkaWNjaW9uZXMgZW4gZWwgY29uanVudG8gZGUgcHJ1ZWJhCnByZWRpY2Npb25lc190ZXN0XzE1NTAwMSA8LSBwcmVkaWN0KG1vZGVsb194Z2JfMTU1MDAxLCBkdGVzdCkKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzIGVuIGVsIGNvbmp1bnRvIGRlIHBydWViYQojIE1BUEUKbWFwZV90ZXN0XzE1NTAwMSA8LSBtZWFuKGFicygoeV90ZXN0IC0gcHJlZGljY2lvbmVzX3Rlc3RfMTU1MDAxKSAvIHBtYXgoeV90ZXN0LCAwLjAxKSkpICogMTAwCgojIFJNU0UKcm1zZV90ZXN0XzE1NTAwMSA8LSBzcXJ0KG1lYW4oKHlfdGVzdCAtIHByZWRpY2Npb25lc190ZXN0XzE1NTAwMSleMikpCgoKIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMgZW4gZWwgY29uanVudG8gZGUgcHJ1ZWJhCmNhdCgiTcOpdHJpY2FzIGVuIGVsIGNvbmp1bnRvIGRlIHBydWViYTpcbiIpCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgbWFwZV90ZXN0XzE1NTAwMSwgIlxuIikKY2F0KCJSTVNFIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBybXNlX3Rlc3RfMTU1MDAxLCAiXG5cbiIpCgoKIyBBaG9yYSBoYWNlciBwcmVkaWNjaW9uZXMgZW4gZWwgY29uanVudG8gY29tcGxldG8gcGFyYSBjb21wYXJhYmlsaWRhZCBjb24gb3Ryb3MgbW9kZWxvcwpYX2NvbXBsZXRvIDwtIGFzLm1hdHJpeChkYXRvc19tb2RlbG9bLCBjb2xuYW1lcyhkYXRvc19tb2RlbG8pICE9ICJWZW50YSJdKQpwcmVkaWNjaW9uZXNfY29tcGxldGFzXzE1NTAwMSA8LSBwcmVkaWN0KG1vZGVsb194Z2JfMTU1MDAxLCBYX2NvbXBsZXRvKQojIENhbGN1bGFyIG3DqXRyaWNhcyBlbiBlbCBjb25qdW50byBjb21wbGV0bwojIE1BUEUKbWFwZV9jb21wbGV0b18xNTUwMDEgPC0gbWVhbihhYnMoKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0YXNfMTU1MDAxKSAvIHBtYXgoZGF0b3NfbW9kZWxvJFZlbnRhLCAwLjAxKSkpICogMTAwCgojIE1TRQpybXNlX2NvbXBsZXRvXzE1NTAwMSA8LSBzcXJ0KG1lYW4oKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0YXNfMTU1MDAxKV4yKSkKCgoKIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMgZW4gZWwgY29uanVudG8gY29tcGxldG8KY2F0KCJNw6l0cmljYXMgZW4gZWwgY29uanVudG8gY29tcGxldG86XG4iKQpjYXQoIk1BUEUgZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIG1hcGVfY29tcGxldG9fMTU1MDAxLCAiXG4iKQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIHJtc2VfY29tcGxldG9fMTU1MDAxLCAiXG5cbiIpCgoKIyBJbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKaW1wb3J0YW5jaWEgPC0geGdiLmltcG9ydGFuY2UoCiAgZmVhdHVyZV9uYW1lcyA9IGNvbG5hbWVzKGRhdG9zX21vZGVsbylbY29sbmFtZXMoZGF0b3NfbW9kZWxvKSAhPSAiVmVudGEiXSwKICBtb2RlbCA9IG1vZGVsb194Z2JfMTU1MDAxCikKcHJpbnQoaW1wb3J0YW5jaWEpCgojIEdyYWZpY2FyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwp4Z2IucGxvdC5pbXBvcnRhbmNlKGltcG9ydGFuY2VfbWF0cml4ID0gaW1wb3J0YW5jaWEsIAogICAgICAgICAgICAgICAgICAgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAxNTUwMDEgKFhHQm9vc3QpIikKCiMgQ3JlYXIgZ3LDoWZpY28gZGUgdmFsb3JlcyBvYnNlcnZhZG9zIHZzIHByZWRpY2Npb25lcwpkYXRvc19ncmFmaWNvIDwtIGRhdGEuZnJhbWUoCiAgT2JzZXJ2YWRvID0gZGF0b3NfbW9kZWxvJFZlbnRhLAogIFByZWRpY2hvID0gcHJlZGljY2lvbmVzX2NvbXBsZXRhc18xNTUwMDEKKQoKZ2dwbG90KGRhdG9zX2dyYWZpY28sIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJWYWxvcmVzIE9ic2VydmFkb3MgdnMgUHJlZGljY2lvbmVzIC0gUHJvZHVjdG8gMTU1MDAxIChYR0Jvb3N0KSIsCiAgICB4ID0gIlZlbnRhcyBPYnNlcnZhZGFzIiwKICAgIHkgPSAiVmVudGFzIFByZWRpY2hhcyIKICApICsKICB0aGVtZV9taW5pbWFsKCkKIyBBbsOhbGlzaXMgZGVsIGVycm9yCmVycm9yZXMgPC0gZGF0b3NfZ3JhZmljbyRPYnNlcnZhZG8gLSBkYXRvc19ncmFmaWNvJFByZWRpY2hvCmhpc3QoZXJyb3JlcywgCiAgICAgbWFpbiA9ICJEaXN0cmlidWNpw7NuIGRlIEVycm9yZXMgLSBQcm9kdWN0byAxNTUwMDEgKFhHQm9vc3QpIiwKICAgICB4bGFiID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiLAogICAgIGNvbCA9ICJza3libHVlIiwKICAgICBicmVha3MgPSAzMCkKCiMgR3LDoWZpY28gZGVsIGVycm9yIHZzIHByZWRpY2Npw7NuCmdncGxvdChkYXRhLmZyYW1lKFByZWRpY2hvID0gcHJlZGljY2lvbmVzX2NvbXBsZXRhc18xNTUwMDEsIEVycm9yID0gZXJyb3JlcyksIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJFcnJvciB2cyBQcmVkaWNjacOzbiAtIFByb2R1Y3RvIDE1NTAwMSAoWEdCb29zdCkiLAogICAgeCA9ICJWZW50YXMgUHJlZGljaGFzIiwKICAgIHkgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIKICApICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCmBgYHtyfQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFhHQm9vc3QgcGFyYSBwcm9kdWN0byAxNTUwMDEKaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1BUEUgPSBudW1lcmljKCksCiAgICBSTVNFID0gbnVtZXJpYygpLAogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKQp9CgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjE1NTAwMSIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bwogIE1vZGVsbyA9ICJYR0Jvb3N0IiwKICBNQVBFID0gbWFwZV9jb21wbGV0b18xNTUwMDEsCiAgUk1TRSA9IHJtc2VfY29tcGxldG9fMTU1MDAxCgopKQpgYGAKCiMjIFBST0RVQ1RPIDM5Mjk3ODgKCmBgYHtyfQojIFByZXBhcmFyIGRhdG9zIHBhcmEgZWwgbW9kZWxvIChlbGltaW5hciBjb2x1bW5hcyBubyBuZWNlc2FyaWFzKQpkYXRvc19tb2RlbG8gPC0gZGF0b3NfMzkyOTc4OCAlPiUKICBzZWxlY3QoLVRyeF9GZWNoYSwgLUZlY2hhKQoKIyBEaXZpZGlyIGxvcyBkYXRvcyBlbiBjb25qdW50b3MgZGUgZW50cmVuYW1pZW50byAoODAlKSB5IHBydWViYSAoMjAlKQpzZXQuc2VlZCgxMjMpCnRyYWluX2luZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGF0b3NfbW9kZWxvJFZlbnRhLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpCnRyYWluX2RhdGEgPC0gZGF0b3NfbW9kZWxvW3RyYWluX2luZGV4LCBdCnRlc3RfZGF0YSA8LSBkYXRvc19tb2RlbG9bLXRyYWluX2luZGV4LCBdCgojIFByZXBhcmFyIG1hdHJpY2VzIHBhcmEgWEdCb29zdAp0cmFpbl94IDwtIGFzLm1hdHJpeCh0cmFpbl9kYXRhWywgY29sbmFtZXModHJhaW5fZGF0YSkgIT0gIlZlbnRhIl0pCnRyYWluX3kgPC0gdHJhaW5fZGF0YSRWZW50YQoKdGVzdF94IDwtIGFzLm1hdHJpeCh0ZXN0X2RhdGFbLCBjb2xuYW1lcyh0ZXN0X2RhdGEpICE9ICJWZW50YSJdKQp0ZXN0X3kgPC0gdGVzdF9kYXRhJFZlbnRhCgojIENyZWFyIERNYXRyaXggcGFyYSBYR0Jvb3N0CmR0cmFpbiA8LSB4Z2IuRE1hdHJpeChkYXRhID0gdHJhaW5feCwgbGFiZWwgPSB0cmFpbl95KQpkdGVzdCA8LSB4Z2IuRE1hdHJpeChkYXRhID0gdGVzdF94LCBsYWJlbCA9IHRlc3RfeSkKCiMgRGVmaW5pciBsYSByZWppbGxhIGRlIGhpcGVycGFyw6FtZXRyb3MKcGFyYW1fZ3JpZCA8LSBleHBhbmQuZ3JpZCgKICBldGEgPSBjKDAuMDEsIDAuMDUsIDAuMSwgMC4zKSwKICBtYXhfZGVwdGggPSBjKDMsIDYsIDkpLAogIG1pbl9jaGlsZF93ZWlnaHQgPSBjKDEsIDMsIDUpLAogIHN1YnNhbXBsZSA9IGMoMC43LCAwLjkpLAogIGNvbHNhbXBsZV9ieXRyZWUgPSBjKDAuNywgMC45KSwKICBnYW1tYSA9IGMoMCwgMC4xLCAwLjMpCikKCmNhdCgiR3JpZCBTZWFyY2ggcGFyYSBYR0Jvb3N0IC0gUHJvZHVjdG8gMzkyOTc4OFxuIikKY2F0KCJOw7ptZXJvIHRvdGFsIGRlIGNvbWJpbmFjaW9uZXM6IiwgbnJvdyhwYXJhbV9ncmlkKSwgIlxuXG4iKQoKIyBMaW1pdGFyIGEgMTIgY29tYmluYWNpb25lcyBhbGVhdG9yaWFzCnNldC5zZWVkKDQ1NikKaWYgKG5yb3cocGFyYW1fZ3JpZCkgPiAxMikgewogIHNlbGVjdGVkX2luZGljZXMgPC0gc2FtcGxlKDE6bnJvdyhwYXJhbV9ncmlkKSwgMTIpCiAgcGFyYW1fZ3JpZCA8LSBwYXJhbV9ncmlkW3NlbGVjdGVkX2luZGljZXMsIF0KICBjYXQoIlNlbGVjY2lvbmFuZG8gMTIgY29tYmluYWNpb25lcyBhbGVhdG9yaWFzIHBhcmEgZXZhbHVhY2nDs24uXG5cbiIpCn0KCiMgSW1wbGVtZW50YXIgR3JpZCBTZWFyY2gKcmVzdWx0YWRvcyA8LSBkYXRhLmZyYW1lKCkKCmNhdCgiSW5pY2lhbmRvIEdyaWQgU2VhcmNoLi4uXG4iKQoKZm9yIChpIGluIDE6bnJvdyhwYXJhbV9ncmlkKSkgewogIHBhcmFtcyA8LSBsaXN0KAogICAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLAogICAgZXZhbF9tZXRyaWMgPSAicm1zZSIsCiAgICBldGEgPSBwYXJhbV9ncmlkJGV0YVtpXSwKICAgIG1heF9kZXB0aCA9IHBhcmFtX2dyaWQkbWF4X2RlcHRoW2ldLAogICAgbWluX2NoaWxkX3dlaWdodCA9IHBhcmFtX2dyaWQkbWluX2NoaWxkX3dlaWdodFtpXSwKICAgIHN1YnNhbXBsZSA9IHBhcmFtX2dyaWQkc3Vic2FtcGxlW2ldLAogICAgY29sc2FtcGxlX2J5dHJlZSA9IHBhcmFtX2dyaWQkY29sc2FtcGxlX2J5dHJlZVtpXSwKICAgIGdhbW1hID0gcGFyYW1fZ3JpZCRnYW1tYVtpXQogICkKICAKICBjYXQoIkV2YWx1YW5kbyBjb21iaW5hY2nDs24iLCBpLCAiZGUiLCBucm93KHBhcmFtX2dyaWQpLCAiXG4iKQogIAogIGN2X21vZGVsIDwtIHhnYi5jdigKICAgIHBhcmFtcyA9IHBhcmFtcywKICAgIGRhdGEgPSBkdHJhaW4sCiAgICBucm91bmRzID0gMjAwLAogICAgbmZvbGQgPSA1LAogICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gMjAsCiAgICB2ZXJib3NlID0gMAogICkKICAKICBiZXN0X2l0ZXJhdGlvbiA8LSBjdl9tb2RlbCRiZXN0X2l0ZXJhdGlvbgogIGJlc3Rfcm1zZSA8LSBtaW4oY3ZfbW9kZWwkZXZhbHVhdGlvbl9sb2ckdGVzdF9ybXNlX21lYW4pCiAgCiAgcmVzdWx0YWRvX2FjdHVhbCA8LSBkYXRhLmZyYW1lKAogICAgZXRhID0gcGFyYW1zJGV0YSwKICAgIG1heF9kZXB0aCA9IHBhcmFtcyRtYXhfZGVwdGgsCiAgICBtaW5fY2hpbGRfd2VpZ2h0ID0gcGFyYW1zJG1pbl9jaGlsZF93ZWlnaHQsCiAgICBzdWJzYW1wbGUgPSBwYXJhbXMkc3Vic2FtcGxlLAogICAgY29sc2FtcGxlX2J5dHJlZSA9IHBhcmFtcyRjb2xzYW1wbGVfYnl0cmVlLAogICAgZ2FtbWEgPSBwYXJhbXMkZ2FtbWEsCiAgICBucm91bmRzID0gYmVzdF9pdGVyYXRpb24sCiAgICBybXNlX2N2ID0gYmVzdF9ybXNlCiAgKQogIAogIHJlc3VsdGFkb3MgPC0gcmJpbmQocmVzdWx0YWRvcywgcmVzdWx0YWRvX2FjdHVhbCkKfQoKIyBPcmRlbmFyIHBvciBtZW5vciBSTVNFCnJlc3VsdGFkb3MgPC0gcmVzdWx0YWRvc1tvcmRlcihyZXN1bHRhZG9zJHJtc2VfY3YpLCBdCgojIE1vc3RyYXIgdG9wIHJlc3VsdGFkb3MKY2F0KCJSZXN1bHRhZG9zIGRlbCBHcmlkIFNlYXJjaCBvcmRlbmFkb3MgcG9yIFJNU0U6XG4iKQpwcmludChyZXN1bHRhZG9zKQoKIyBHcmFmaWNhciByZXN1bHRhZG9zCmdncGxvdChyZXN1bHRhZG9zLCBhZXMoeCA9IHJlb3JkZXIocGFzdGUoIkNvbWIiLCAxOm5yb3cocmVzdWx0YWRvcykpLCBybXNlX2N2KSwgeSA9IHJtc2VfY3YpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAic3RlZWxibHVlIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJSZXN1bHRhZG9zIGRlbCBHcmlkIFNlYXJjaCAtIFByb2R1Y3RvIDM5Mjk3ODgiLAogICAgeCA9ICJDb21iaW5hY2nDs24gZGUgSGlwZXJwYXLDoW1ldHJvcyIsCiAgICB5ID0gIlJNU0UgZW4gVmFsaWRhY2nDs24gQ3J1emFkYSIKICApICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCgojIFNlbGVjY2lvbmFyIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcwptZWpvcmVzX3BhcmFtcyA8LSBsaXN0KAogIG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIiwKICBldmFsX21ldHJpYyA9ICJybXNlIiwKICBldGEgPSByZXN1bHRhZG9zJGV0YVsxXSwKICBtYXhfZGVwdGggPSByZXN1bHRhZG9zJG1heF9kZXB0aFsxXSwKICBtaW5fY2hpbGRfd2VpZ2h0ID0gcmVzdWx0YWRvcyRtaW5fY2hpbGRfd2VpZ2h0WzFdLAogIHN1YnNhbXBsZSA9IHJlc3VsdGFkb3Mkc3Vic2FtcGxlWzFdLAogIGNvbHNhbXBsZV9ieXRyZWUgPSByZXN1bHRhZG9zJGNvbHNhbXBsZV9ieXRyZWVbMV0sCiAgZ2FtbWEgPSByZXN1bHRhZG9zJGdhbW1hWzFdCikKCm1lam9yX25yb3VuZHMgPC0gcmVzdWx0YWRvcyRucm91bmRzWzFdCgpjYXQoIlxuTWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zIGVuY29udHJhZG9zOlxuIikKcHJpbnQobWVqb3Jlc19wYXJhbXMpCmNhdCgiTsO6bWVybyDDs3B0aW1vIGRlIHJvbmRhczoiLCBtZWpvcl9ucm91bmRzLCAiXG5cbiIpCgojIEVudHJlbmFyIG1vZGVsbyBmaW5hbApjYXQoIkVudHJlbmFuZG8gbW9kZWxvIGZpbmFsIGNvbiBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zLi4uXG4iKQptb2RlbG9fZmluYWwgPC0geGdiLnRyYWluKAogIHBhcmFtcyA9IG1lam9yZXNfcGFyYW1zLAogIGRhdGEgPSBkdHJhaW4sCiAgbnJvdW5kcyA9IG1lam9yX25yb3VuZHMsCiAgd2F0Y2hsaXN0ID0gbGlzdCh0cmFpbiA9IGR0cmFpbiwgdGVzdCA9IGR0ZXN0KSwKICB2ZXJib3NlID0gMAopCgojIEV2YWx1YXIgZW4gZGF0b3MgY29tcGxldG9zClhfY29tcGxldG8gPC0gYXMubWF0cml4KGRhdG9zX21vZGVsb1ssIGNvbG5hbWVzKGRhdG9zX21vZGVsbykgIT0gIlZlbnRhIl0pCmRjb21wbGV0byA8LSB4Z2IuRE1hdHJpeChkYXRhID0gWF9jb21wbGV0bykKcHJlZGljY2lvbmVzX2NvbXBsZXRhcyA8LSBwcmVkaWN0KG1vZGVsb19maW5hbCwgbmV3ZGF0YSA9IGRjb21wbGV0bykKCiMgTcOpdHJpY2FzIGZpbmFsZXMKbWFwZV9jb21wbGV0byA8LSBtZWFuKGFicygoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX2NvbXBsZXRhcykgLyBwbWF4KGRhdG9zX21vZGVsbyRWZW50YSwgMC4wMSkpKSAqIDEwMApybXNlX2NvbXBsZXRvIDwtIHNxcnQobWVhbigoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX2NvbXBsZXRhcyleMikpCgpjYXQoIkV2YWx1YWNpw7NuIGZpbmFsIGVuIHRvZG8gZWwgY29uanVudG86XG4iKQpjYXQoIk1BUEU6IiwgbWFwZV9jb21wbGV0bywgIlxuIikKY2F0KCJSTVNFOiIsIHJtc2VfY29tcGxldG8sICJcblxuIikKCiMgR3JhZmljYXIgcmVzdWx0YWRvcwpkYXRvc19ncmFmaWNvIDwtIGRhdGEuZnJhbWUoCiAgT2JzZXJ2YWRvID0gZGF0b3NfbW9kZWxvJFZlbnRhLAogIFByZWRpY2hvID0gcHJlZGljY2lvbmVzX2NvbXBsZXRhcwopCgpnZ3Bsb3QoZGF0b3NfZ3JhZmljbywgYWVzKHggPSBPYnNlcnZhZG8sIHkgPSBQcmVkaWNobykpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicygKICAgIHRpdGxlID0gIlZhbG9yZXMgT2JzZXJ2YWRvcyB2cyBQcmVkaWNjaW9uZXMgLSBQcm9kdWN0byAzOTI5Nzg4IChYR0Jvb3N0KSIsCiAgICB4ID0gIlZlbnRhcyBPYnNlcnZhZGFzIiwKICAgIHkgPSAiVmVudGFzIFByZWRpY2hhcyIKICApICsKICB0aGVtZV9taW5pbWFsKCkKCiMgQW7DoWxpc2lzIGRlIGVycm9yZXMKZXJyb3JlcyA8LSBkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfY29tcGxldGFzCgojIEhpc3RvZ3JhbWEgZGUgZXJyb3JlcwpoaXN0KGVycm9yZXMsIAogICAgIG1haW4gPSAiRGlzdHJpYnVjacOzbiBkZSBFcnJvcmVzIC0gUHJvZHVjdG8gMzkyOTc4OCAoWEdCb29zdCkiLAogICAgIHhsYWIgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIsCiAgICAgY29sID0gInNreWJsdWUiLAogICAgIGJyZWFrcyA9IDMwKQoKIyBFcnJvciB2cyBwcmVkaWNjacOzbgpnZ3Bsb3QoZGF0YS5mcmFtZShQcmVkaWNobyA9IHByZWRpY2Npb25lc19jb21wbGV0YXMsIEVycm9yID0gZXJyb3JlcyksIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJFcnJvciB2cyBQcmVkaWNjacOzbiAtIFByb2R1Y3RvIDM5Mjk3ODggKFhHQm9vc3QpIiwKICAgIHggPSAiVmVudGFzIFByZWRpY2hhcyIsCiAgICB5ID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIEd1YXJkYXIgbcOpdHJpY2FzCmlmICghZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgewogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLAogICAgTUFQRSA9IG51bWVyaWMoKSwKICAgIFJNU0UgPSBudW1lcmljKCksCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCn0KCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMzkyOTc4OCIsCiAgTW9kZWxvID0gIlhHQm9vc3QiLAogIE1BUEUgPSBtYXBlX2NvbXBsZXRvLAogIFJNU0UgPSBybXNlX2NvbXBsZXRvCikpCgpgYGAKCiMjIFBST0RVQ1RPIDM5MDQxNTIKYGBge3J9CiMgUGFzbyAxOiBQcmVwYXJhciBkYXRvcwpkYXRvc19tb2RlbG9fMzkwNDE1MiA8LSBkYXRvc18zOTA0MTUyICU+JQogIHNlbGVjdCgtVHJ4X0ZlY2hhLCAtRmVjaGEpCgojIFBhc28gMjogRGl2aWRpciBlbiBlbnRyZW5hbWllbnRvIHkgcHJ1ZWJhICg4MC8yMCkKc2V0LnNlZWQoMTIzKQp0cmFpbl9pbmRleF8zOTA0MTUyIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGF0b3NfbW9kZWxvXzM5MDQxNTIkVmVudGEsIHAgPSAwLjgsIGxpc3QgPSBGQUxTRSkKdHJhaW5fZGF0YV8zOTA0MTUyIDwtIGRhdG9zX21vZGVsb18zOTA0MTUyW3RyYWluX2luZGV4XzM5MDQxNTIsIF0KdGVzdF9kYXRhXzM5MDQxNTIgPC0gZGF0b3NfbW9kZWxvXzM5MDQxNTJbLXRyYWluX2luZGV4XzM5MDQxNTIsIF0KCiMgUGFzbyAzOiBQcmVwYXJhciBtYXRyaWNlcwp0cmFpbl94XzM5MDQxNTIgPC0gYXMubWF0cml4KHRyYWluX2RhdGFfMzkwNDE1MlssIGNvbG5hbWVzKHRyYWluX2RhdGFfMzkwNDE1MikgIT0gIlZlbnRhIl0pCnRyYWluX3lfMzkwNDE1MiA8LSB0cmFpbl9kYXRhXzM5MDQxNTIkVmVudGEKdGVzdF94XzM5MDQxNTIgPC0gYXMubWF0cml4KHRlc3RfZGF0YV8zOTA0MTUyWywgY29sbmFtZXModGVzdF9kYXRhXzM5MDQxNTIpICE9ICJWZW50YSJdKQp0ZXN0X3lfMzkwNDE1MiA8LSB0ZXN0X2RhdGFfMzkwNDE1MiRWZW50YQoKZHRyYWluXzM5MDQxNTIgPC0geGdiLkRNYXRyaXgoZGF0YSA9IHRyYWluX3hfMzkwNDE1MiwgbGFiZWwgPSB0cmFpbl95XzM5MDQxNTIpCmR0ZXN0XzM5MDQxNTIgPC0geGdiLkRNYXRyaXgoZGF0YSA9IHRlc3RfeF8zOTA0MTUyLCBsYWJlbCA9IHRlc3RfeV8zOTA0MTUyKQoKIyBQYXNvIDQ6IEhpcGVycGFyw6FtZXRyb3MKcGFyYW1fZ3JpZF8zOTA0MTUyIDwtIGV4cGFuZC5ncmlkKAogIGV0YSA9IGMoMC4wMSwgMC4wNSwgMC4xLCAwLjMpLAogIG1heF9kZXB0aCA9IGMoMywgNiwgOSksCiAgbWluX2NoaWxkX3dlaWdodCA9IGMoMSwgMywgNSksCiAgc3Vic2FtcGxlID0gYygwLjcsIDAuOSksCiAgY29sc2FtcGxlX2J5dHJlZSA9IGMoMC43LCAwLjkpLAogIGdhbW1hID0gYygwLCAwLjEsIDAuMykKKQoKY2F0KCJHcmlkIFNlYXJjaCBwYXJhIFhHQm9vc3QgLSBQcm9kdWN0byAzOTA0MTUyXG4iKQpjYXQoIk7Dum1lcm8gdG90YWwgZGUgY29tYmluYWNpb25lczoiLCBucm93KHBhcmFtX2dyaWRfMzkwNDE1MiksICJcblxuIikKCiMgUGFzbyA1OiBMaW1pdGFyIGNvbWJpbmFjaW9uZXMKc2V0LnNlZWQoNDU2KQppZiAobnJvdyhwYXJhbV9ncmlkXzM5MDQxNTIpID4gMTIpIHsKICBzZWxlY3RlZF9pbmRpY2VzXzM5MDQxNTIgPC0gc2FtcGxlKDE6bnJvdyhwYXJhbV9ncmlkXzM5MDQxNTIpLCAxMikKICBwYXJhbV9ncmlkXzM5MDQxNTIgPC0gcGFyYW1fZ3JpZF8zOTA0MTUyW3NlbGVjdGVkX2luZGljZXNfMzkwNDE1MiwgXQp9CgojIFBhc28gNjogR3JpZCBTZWFyY2gKcmVzdWx0YWRvc18zOTA0MTUyIDwtIGRhdGEuZnJhbWUoKQoKY2F0KCJJbmljaWFuZG8gR3JpZCBTZWFyY2guLi5cbiIpCgpmb3IgKGkgaW4gMTpucm93KHBhcmFtX2dyaWRfMzkwNDE1MikpIHsKICBwYXJhbXMgPC0gbGlzdCgKICAgIG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIiwKICAgIGV2YWxfbWV0cmljID0gInJtc2UiLAogICAgZXRhID0gcGFyYW1fZ3JpZF8zOTA0MTUyJGV0YVtpXSwKICAgIG1heF9kZXB0aCA9IHBhcmFtX2dyaWRfMzkwNDE1MiRtYXhfZGVwdGhbaV0sCiAgICBtaW5fY2hpbGRfd2VpZ2h0ID0gcGFyYW1fZ3JpZF8zOTA0MTUyJG1pbl9jaGlsZF93ZWlnaHRbaV0sCiAgICBzdWJzYW1wbGUgPSBwYXJhbV9ncmlkXzM5MDQxNTIkc3Vic2FtcGxlW2ldLAogICAgY29sc2FtcGxlX2J5dHJlZSA9IHBhcmFtX2dyaWRfMzkwNDE1MiRjb2xzYW1wbGVfYnl0cmVlW2ldLAogICAgZ2FtbWEgPSBwYXJhbV9ncmlkXzM5MDQxNTIkZ2FtbWFbaV0KICApCiAgCiAgY3ZfbW9kZWwgPC0geGdiLmN2KAogICAgcGFyYW1zID0gcGFyYW1zLAogICAgZGF0YSA9IGR0cmFpbl8zOTA0MTUyLAogICAgbnJvdW5kcyA9IDIwMCwKICAgIG5mb2xkID0gNSwKICAgIGVhcmx5X3N0b3BwaW5nX3JvdW5kcyA9IDIwLAogICAgdmVyYm9zZSA9IDAKICApCiAgCiAgYmVzdF9pdGVyYXRpb24gPC0gY3ZfbW9kZWwkYmVzdF9pdGVyYXRpb24KICBiZXN0X3Jtc2UgPC0gbWluKGN2X21vZGVsJGV2YWx1YXRpb25fbG9nJHRlc3Rfcm1zZV9tZWFuKQogIAogIHJlc3VsdGFkb3NfMzkwNDE1MiA8LSByYmluZChyZXN1bHRhZG9zXzM5MDQxNTIsIGRhdGEuZnJhbWUoCiAgICBldGEgPSBwYXJhbXMkZXRhLAogICAgbWF4X2RlcHRoID0gcGFyYW1zJG1heF9kZXB0aCwKICAgIG1pbl9jaGlsZF93ZWlnaHQgPSBwYXJhbXMkbWluX2NoaWxkX3dlaWdodCwKICAgIHN1YnNhbXBsZSA9IHBhcmFtcyRzdWJzYW1wbGUsCiAgICBjb2xzYW1wbGVfYnl0cmVlID0gcGFyYW1zJGNvbHNhbXBsZV9ieXRyZWUsCiAgICBnYW1tYSA9IHBhcmFtcyRnYW1tYSwKICAgIG5yb3VuZHMgPSBiZXN0X2l0ZXJhdGlvbiwKICAgIHJtc2VfY3YgPSBiZXN0X3Jtc2UKICApKQp9CgpyZXN1bHRhZG9zXzM5MDQxNTIgPC0gcmVzdWx0YWRvc18zOTA0MTUyW29yZGVyKHJlc3VsdGFkb3NfMzkwNDE1MiRybXNlX2N2KSwgXQoKY2F0KCJSZXN1bHRhZG9zIGRlbCBHcmlkIFNlYXJjaCBvcmRlbmFkb3MgcG9yIFJNU0U6XG4iKQpwcmludChyZXN1bHRhZG9zXzM5MDQxNTIpCgojIFZpc3VhbGl6YXIgcmVzdWx0YWRvcwpnZ3Bsb3QocmVzdWx0YWRvc18zOTA0MTUyLCBhZXMoeCA9IHJlb3JkZXIocGFzdGUoIkNvbWIiLCAxOm5yb3cocmVzdWx0YWRvc18zOTA0MTUyKSksIHJtc2VfY3YpLCB5ID0gcm1zZV9jdikpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgZmlsbCA9ICJzdGVlbGJsdWUiKSArCiAgbGFicygKICAgIHRpdGxlID0gIlJlc3VsdGFkb3MgZGVsIEdyaWQgU2VhcmNoIC0gUHJvZHVjdG8gMzkwNDE1MiIsCiAgICB4ID0gIkNvbWJpbmFjacOzbiBkZSBIaXBlcnBhcsOhbWV0cm9zIiwKICAgIHkgPSAiUk1TRSBlbiBWYWxpZGFjacOzbiBDcnV6YWRhIgogICkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkKCiMgUGFzbyA3OiBFbnRyZW5hbWllbnRvIGZpbmFsCm1lam9yZXNfcGFyYW1zXzM5MDQxNTIgPC0gbGlzdCgKICBvYmplY3RpdmUgPSAicmVnOnNxdWFyZWRlcnJvciIsCiAgZXZhbF9tZXRyaWMgPSAicm1zZSIsCiAgZXRhID0gcmVzdWx0YWRvc18zOTA0MTUyJGV0YVsxXSwKICBtYXhfZGVwdGggPSByZXN1bHRhZG9zXzM5MDQxNTIkbWF4X2RlcHRoWzFdLAogIG1pbl9jaGlsZF93ZWlnaHQgPSByZXN1bHRhZG9zXzM5MDQxNTIkbWluX2NoaWxkX3dlaWdodFsxXSwKICBzdWJzYW1wbGUgPSByZXN1bHRhZG9zXzM5MDQxNTIkc3Vic2FtcGxlWzFdLAogIGNvbHNhbXBsZV9ieXRyZWUgPSByZXN1bHRhZG9zXzM5MDQxNTIkY29sc2FtcGxlX2J5dHJlZVsxXSwKICBnYW1tYSA9IHJlc3VsdGFkb3NfMzkwNDE1MiRnYW1tYVsxXQopCgptZWpvcl9ucm91bmRzXzM5MDQxNTIgPC0gcmVzdWx0YWRvc18zOTA0MTUyJG5yb3VuZHNbMV0KCm1vZGVsb194Z2JfMzkwNDE1MiA8LSB4Z2IudHJhaW4oCiAgcGFyYW1zID0gbWVqb3Jlc19wYXJhbXNfMzkwNDE1MiwKICBkYXRhID0gZHRyYWluXzM5MDQxNTIsCiAgbnJvdW5kcyA9IG1lam9yX25yb3VuZHNfMzkwNDE1MiwKICB3YXRjaGxpc3QgPSBsaXN0KHRyYWluID0gZHRyYWluXzM5MDQxNTIsIHRlc3QgPSBkdGVzdF8zOTA0MTUyKSwKICB2ZXJib3NlID0gMAopCgojIFBhc28gODogRXZhbHVhY2nDs24gZW4gdGVzdApwcmVkaWNjaW9uZXNfdGVzdF8zOTA0MTUyIDwtIHByZWRpY3QobW9kZWxvX3hnYl8zOTA0MTUyLCBkdGVzdF8zOTA0MTUyKQoKbWFwZV90ZXN0XzM5MDQxNTIgPC0gbWVhbihhYnMoKHRlc3RfeV8zOTA0MTUyIC0gcHJlZGljY2lvbmVzX3Rlc3RfMzkwNDE1MikgLyBwbWF4KHRlc3RfeV8zOTA0MTUyLCAwLjAxKSkpICogMTAwCnJtc2VfdGVzdF8zOTA0MTUyIDwtIHNxcnQobWVhbigodGVzdF95XzM5MDQxNTIgLSBwcmVkaWNjaW9uZXNfdGVzdF8zOTA0MTUyKV4yKSkKCmNhdCgiXG5Nw6l0cmljYXMgZW4gY29uanVudG8gZGUgcHJ1ZWJhOlxuIikKY2F0KCJNQVBFOiIsIG1hcGVfdGVzdF8zOTA0MTUyLCAiXG4iKQpjYXQoIlJNU0U6Iiwgcm1zZV90ZXN0XzM5MDQxNTIsICJcblxuIikKCiMgUGFzbyA5OiBFdmFsdWFjacOzbiBlbiBjb25qdW50byBjb21wbGV0bwp4X2NvbXBsZXRvXzM5MDQxNTIgPC0gYXMubWF0cml4KGRhdG9zX21vZGVsb18zOTA0MTUyWywgY29sbmFtZXMoZGF0b3NfbW9kZWxvXzM5MDQxNTIpICE9ICJWZW50YSJdKQpwcmVkaWNjaW9uZXNfY29tcGxldG9fMzkwNDE1MiA8LSBwcmVkaWN0KG1vZGVsb194Z2JfMzkwNDE1MiwgeF9jb21wbGV0b18zOTA0MTUyKQoKbWFwZV9jb21wbGV0b18zOTA0MTUyIDwtIG1lYW4oYWJzKChkYXRvc19tb2RlbG9fMzkwNDE1MiRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0b18zOTA0MTUyKSAvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwbWF4KGRhdG9zX21vZGVsb18zOTA0MTUyJFZlbnRhLCAwLjAxKSkpICogMTAwCnJtc2VfY29tcGxldG9fMzkwNDE1MiA8LSBzcXJ0KG1lYW4oKGRhdG9zX21vZGVsb18zOTA0MTUyJFZlbnRhIC0gcHJlZGljY2lvbmVzX2NvbXBsZXRvXzM5MDQxNTIpXjIpKQoKY2F0KCJNw6l0cmljYXMgZW4gY29uanVudG8gY29tcGxldG86XG4iKQpjYXQoIk1BUEU6IiwgbWFwZV9jb21wbGV0b18zOTA0MTUyLCAiXG4iKQpjYXQoIlJNU0U6Iiwgcm1zZV9jb21wbGV0b18zOTA0MTUyLCAiXG5cbiIpCgojIFBhc28gMTA6IEltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwppbXBvcnRhbmNpYV8zOTA0MTUyIDwtIHhnYi5pbXBvcnRhbmNlKAogIGZlYXR1cmVfbmFtZXMgPSBjb2xuYW1lcyhkYXRvc19tb2RlbG9fMzkwNDE1MilbY29sbmFtZXMoZGF0b3NfbW9kZWxvXzM5MDQxNTIpICE9ICJWZW50YSJdLAogIG1vZGVsID0gbW9kZWxvX3hnYl8zOTA0MTUyCikKCmNhdCgiSW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzOlxuIikKcHJpbnQoaW1wb3J0YW5jaWFfMzkwNDE1MikKCnhnYi5wbG90LmltcG9ydGFuY2UoaW1wb3J0YW5jZV9tYXRyaXggPSBpbXBvcnRhbmNpYV8zOTA0MTUyLCAKICAgICAgICAgICAgICAgICAgICBtYWluID0gIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyAtIFByb2R1Y3RvIDM5MDQxNTIgKFhHQm9vc3QpIikKCiMgUGFzbyAxMTogVmlzdWFsaXphY2lvbmVzCmRhdG9zX2dyYWZpY29fMzkwNDE1MiA8LSBkYXRhLmZyYW1lKAogIE9ic2VydmFkbyA9IGRhdG9zX21vZGVsb18zOTA0MTUyJFZlbnRhLAogIFByZWRpY2hvID0gcHJlZGljY2lvbmVzX2NvbXBsZXRvXzM5MDQxNTIKKQoKIyBPYnNlcnZhZG8gdnMgUHJlZGljaG8KZ2dwbG90KGRhdG9zX2dyYWZpY29fMzkwNDE1MiwgYWVzKHggPSBPYnNlcnZhZG8sIHkgPSBQcmVkaWNobykpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicygKICAgIHRpdGxlID0gIlZhbG9yZXMgT2JzZXJ2YWRvcyB2cyBQcmVkaWNjaW9uZXMgLSBQcm9kdWN0byAzOTA0MTUyIChYR0Jvb3N0KSIsCiAgICB4ID0gIlZlbnRhcyBPYnNlcnZhZGFzIiwKICAgIHkgPSAiVmVudGFzIFByZWRpY2hhcyIKICApICsKICB0aGVtZV9taW5pbWFsKCkKCiMgRGlzdHJpYnVjacOzbiBkZSBlcnJvcmVzCmVycm9yZXNfMzkwNDE1MiA8LSBkYXRvc19tb2RlbG9fMzkwNDE1MiRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0b18zOTA0MTUyCgpoaXN0KGVycm9yZXNfMzkwNDE1MiwgCiAgICAgbWFpbiA9ICJEaXN0cmlidWNpw7NuIGRlIEVycm9yZXMgLSBQcm9kdWN0byAzOTA0MTUyIChYR0Jvb3N0KSIsCiAgICAgeGxhYiA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIiwKICAgICBjb2wgPSAic2t5Ymx1ZSIsCiAgICAgYnJlYWtzID0gMzApCgojIEVycm9yIHZzIFByZWRpY2Npw7NuCmdncGxvdChkYXRhLmZyYW1lKFByZWRpY2hvID0gcHJlZGljY2lvbmVzX2NvbXBsZXRvXzM5MDQxNTIsIEVycm9yID0gZXJyb3Jlc18zOTA0MTUyKSwgYWVzKHggPSBQcmVkaWNobywgeSA9IEVycm9yKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicygKICAgIHRpdGxlID0gIkVycm9yIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMzkwNDE1MiAoWEdCb29zdCkiLAogICAgeCA9ICJWZW50YXMgUHJlZGljaGFzIiwKICAgIHkgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIKICApICsKICB0aGVtZV9taW5pbWFsKCkKCiMgUGFzbyAxMjogR3VhcmRhciBtw6l0cmljYXMKaWYgKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7CiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLAogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksCiAgICBNQVBFID0gbnVtZXJpYygpLAogICAgUk1TRSA9IG51bWVyaWMoKSwKICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQogICkKfQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIzOTA0MTUyIiwKICBNb2RlbG8gPSAiWEdCb29zdCIsCiAgTUFQRSA9IG1hcGVfY29tcGxldG9fMzkwNDE1MiwKICBSTVNFID0gcm1zZV9jb21wbGV0b18zOTA0MTUyCikpCmBgYAoKIyMgUFJPRFVDVE8gMTU1MDAyCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHhnYm9vc3QpCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkoZ2dwbG90MikKCiMgUGFzbyAxOiBQcmVwYXJhciBkYXRvcwpkYXRvc19tb2RlbG9fMTU1MDAyIDwtIGRhdG9zXzE1NTAwMiAlPiUKICBzZWxlY3QoLVRyeF9GZWNoYSwgLUZlY2hhKQoKIyBQYXNvIDI6IERpdmlzacOzbiBlbiBlbnRyZW5hbWllbnRvIHkgcHJ1ZWJhCnNldC5zZWVkKDEyMykKdHJhaW5faW5kZXhfMTU1MDAyIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGF0b3NfbW9kZWxvXzE1NTAwMiRWZW50YSwgcCA9IDAuOCwgbGlzdCA9IEZBTFNFKQp0cmFpbl9kYXRhXzE1NTAwMiA8LSBkYXRvc19tb2RlbG9fMTU1MDAyW3RyYWluX2luZGV4XzE1NTAwMiwgXQp0ZXN0X2RhdGFfMTU1MDAyIDwtIGRhdG9zX21vZGVsb18xNTUwMDJbLXRyYWluX2luZGV4XzE1NTAwMiwgXQoKIyBQYXNvIDM6IE1hdHJpY2VzIHBhcmEgWEdCb29zdAp0cmFpbl94XzE1NTAwMiA8LSBhcy5tYXRyaXgodHJhaW5fZGF0YV8xNTUwMDJbLCBjb2xuYW1lcyh0cmFpbl9kYXRhXzE1NTAwMikgIT0gIlZlbnRhIl0pCnRyYWluX3lfMTU1MDAyIDwtIHRyYWluX2RhdGFfMTU1MDAyJFZlbnRhCnRlc3RfeF8xNTUwMDIgPC0gYXMubWF0cml4KHRlc3RfZGF0YV8xNTUwMDJbLCBjb2xuYW1lcyh0ZXN0X2RhdGFfMTU1MDAyKSAhPSAiVmVudGEiXSkKdGVzdF95XzE1NTAwMiA8LSB0ZXN0X2RhdGFfMTU1MDAyJFZlbnRhCgpkdHJhaW5fMTU1MDAyIDwtIHhnYi5ETWF0cml4KGRhdGEgPSB0cmFpbl94XzE1NTAwMiwgbGFiZWwgPSB0cmFpbl95XzE1NTAwMikKZHRlc3RfMTU1MDAyIDwtIHhnYi5ETWF0cml4KGRhdGEgPSB0ZXN0X3hfMTU1MDAyLCBsYWJlbCA9IHRlc3RfeV8xNTUwMDIpCgojIFBhc28gNDogRGVmaW5pciBncmlkCnBhcmFtX2dyaWRfMTU1MDAyIDwtIGV4cGFuZC5ncmlkKAogIGV0YSA9IGMoMC4wMSwgMC4wNSwgMC4xLCAwLjMpLAogIG1heF9kZXB0aCA9IGMoMywgNiwgOSksCiAgbWluX2NoaWxkX3dlaWdodCA9IGMoMSwgMywgNSksCiAgc3Vic2FtcGxlID0gYygwLjcsIDAuOSksCiAgY29sc2FtcGxlX2J5dHJlZSA9IGMoMC43LCAwLjkpLAogIGdhbW1hID0gYygwLCAwLjEsIDAuMykKKQoKY2F0KCJHcmlkIFNlYXJjaCBwYXJhIFhHQm9vc3QgLSBQcm9kdWN0byAxNTUwMDJcbiIpCmNhdCgiTsO6bWVybyB0b3RhbCBkZSBjb21iaW5hY2lvbmVzIGRlIGhpcGVycGFyw6FtZXRyb3M6IiwgbnJvdyhwYXJhbV9ncmlkXzE1NTAwMiksICJcblxuIikKCiMgTGltaXRhciBjb21iaW5hY2lvbmVzCnNldC5zZWVkKDQ1NikKaWYgKG5yb3cocGFyYW1fZ3JpZF8xNTUwMDIpID4gMTIpIHsKICBzZWxlY3RlZF9pbmRpY2VzIDwtIHNhbXBsZSgxOm5yb3cocGFyYW1fZ3JpZF8xNTUwMDIpLCAxMikKICBwYXJhbV9ncmlkXzE1NTAwMiA8LSBwYXJhbV9ncmlkXzE1NTAwMltzZWxlY3RlZF9pbmRpY2VzLCBdCiAgY2F0KCJTZWxlY2Npb25hbmRvIDEyIGNvbWJpbmFjaW9uZXMgYWxlYXRvcmlhcyBwYXJhIGV2YWx1YWNpw7NuLlxuXG4iKQp9CgojIFBhc28gNTogR3JpZCBTZWFyY2gKcmVzdWx0YWRvc18xNTUwMDIgPC0gZGF0YS5mcmFtZSgpCgpjYXQoIkluaWNpYW5kbyBHcmlkIFNlYXJjaC4uLlxuIikKCmZvciAoaSBpbiAxOm5yb3cocGFyYW1fZ3JpZF8xNTUwMDIpKSB7CiAgcGFyYW1zIDwtIGxpc3QoCiAgICBvYmplY3RpdmUgPSAicmVnOnNxdWFyZWRlcnJvciIsCiAgICBldmFsX21ldHJpYyA9ICJybXNlIiwKICAgIGV0YSA9IHBhcmFtX2dyaWRfMTU1MDAyJGV0YVtpXSwKICAgIG1heF9kZXB0aCA9IHBhcmFtX2dyaWRfMTU1MDAyJG1heF9kZXB0aFtpXSwKICAgIG1pbl9jaGlsZF93ZWlnaHQgPSBwYXJhbV9ncmlkXzE1NTAwMiRtaW5fY2hpbGRfd2VpZ2h0W2ldLAogICAgc3Vic2FtcGxlID0gcGFyYW1fZ3JpZF8xNTUwMDIkc3Vic2FtcGxlW2ldLAogICAgY29sc2FtcGxlX2J5dHJlZSA9IHBhcmFtX2dyaWRfMTU1MDAyJGNvbHNhbXBsZV9ieXRyZWVbaV0sCiAgICBnYW1tYSA9IHBhcmFtX2dyaWRfMTU1MDAyJGdhbW1hW2ldCiAgKQogIAogIGNhdCgiRXZhbHVhbmRvIGNvbWJpbmFjacOzbiIsIGksICJkZSIsIG5yb3cocGFyYW1fZ3JpZF8xNTUwMDIpLCAiOlxuIikKICBjYXQoIiAgZXRhID0iLCBwYXJhbXMkZXRhLCAKICAgICAgIiwgbWF4X2RlcHRoID0iLCBwYXJhbXMkbWF4X2RlcHRoLCAKICAgICAgIiwgbWluX2NoaWxkX3dlaWdodCA9IiwgcGFyYW1zJG1pbl9jaGlsZF93ZWlnaHQsIAogICAgICAiLCBzdWJzYW1wbGUgPSIsIHBhcmFtcyRzdWJzYW1wbGUsIAogICAgICAiLCBjb2xzYW1wbGVfYnl0cmVlID0iLCBwYXJhbXMkY29sc2FtcGxlX2J5dHJlZSwKICAgICAgIiwgZ2FtbWEgPSIsIHBhcmFtcyRnYW1tYSwgIlxuIikKICAKICBjdl9tb2RlbCA8LSB4Z2IuY3YoCiAgICBwYXJhbXMgPSBwYXJhbXMsCiAgICBkYXRhID0gZHRyYWluXzE1NTAwMiwKICAgIG5yb3VuZHMgPSAyMDAsCiAgICBuZm9sZCA9IDUsCiAgICBlYXJseV9zdG9wcGluZ19yb3VuZHMgPSAyMCwKICAgIHZlcmJvc2UgPSAwCiAgKQogIAogIGJlc3RfaXRlcmF0aW9uIDwtIGN2X21vZGVsJGJlc3RfaXRlcmF0aW9uCiAgYmVzdF9ybXNlIDwtIG1pbihjdl9tb2RlbCRldmFsdWF0aW9uX2xvZyR0ZXN0X3Jtc2VfbWVhbikKICAKICBjYXQoIiAgTWVqb3IgaXRlcmFjacOzbjoiLCBiZXN0X2l0ZXJhdGlvbiwgIlxuIikKICBjYXQoIiAgUk1TRSBlbiB2YWxpZGFjacOzbiBjcnV6YWRhOiIsIGJlc3Rfcm1zZSwgIlxuXG4iKQogIAogIHJlc3VsdGFkb3NfMTU1MDAyIDwtIHJiaW5kKHJlc3VsdGFkb3NfMTU1MDAyLCBkYXRhLmZyYW1lKAogICAgZXRhID0gcGFyYW1zJGV0YSwKICAgIG1heF9kZXB0aCA9IHBhcmFtcyRtYXhfZGVwdGgsCiAgICBtaW5fY2hpbGRfd2VpZ2h0ID0gcGFyYW1zJG1pbl9jaGlsZF93ZWlnaHQsCiAgICBzdWJzYW1wbGUgPSBwYXJhbXMkc3Vic2FtcGxlLAogICAgY29sc2FtcGxlX2J5dHJlZSA9IHBhcmFtcyRjb2xzYW1wbGVfYnl0cmVlLAogICAgZ2FtbWEgPSBwYXJhbXMkZ2FtbWEsCiAgICBucm91bmRzID0gYmVzdF9pdGVyYXRpb24sCiAgICBybXNlX2N2ID0gYmVzdF9ybXNlCiAgKSkKfQoKIyBQYXNvIDY6IE9yZGVuYXIgeSBtb3N0cmFyIHJlc3VsdGFkb3MKcmVzdWx0YWRvc18xNTUwMDIgPC0gcmVzdWx0YWRvc18xNTUwMDJbb3JkZXIocmVzdWx0YWRvc18xNTUwMDIkcm1zZV9jdiksIF0KY2F0KCJSZXN1bHRhZG9zIGRlbCBHcmlkIFNlYXJjaCBvcmRlbmFkb3MgcG9yIFJNU0U6XG4iKQpwcmludChyZXN1bHRhZG9zXzE1NTAwMikKCiMgVmlzdWFsaXphY2nDs24KZ2dwbG90KHJlc3VsdGFkb3NfMTU1MDAyLCBhZXMoeCA9IHJlb3JkZXIocGFzdGUoIkNvbWIiLCAxOm5yb3cocmVzdWx0YWRvc18xNTUwMDIpKSwgcm1zZV9jdiksIHkgPSBybXNlX2N2KSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gInN0ZWVsYmx1ZSIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiUmVzdWx0YWRvcyBkZWwgR3JpZCBTZWFyY2ggLSBQcm9kdWN0byAxNTUwMDIiLAogICAgeCA9ICJDb21iaW5hY2nDs24gZGUgSGlwZXJwYXLDoW1ldHJvcyIsCiAgICB5ID0gIlJNU0UgZW4gVmFsaWRhY2nDs24gQ3J1emFkYSIKICApICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCgojIFBhc28gNzogRW50cmVuYW1pZW50byBmaW5hbAptZWpvcmVzX3BhcmFtc18xNTUwMDIgPC0gbGlzdCgKICBvYmplY3RpdmUgPSAicmVnOnNxdWFyZWRlcnJvciIsCiAgZXZhbF9tZXRyaWMgPSAicm1zZSIsCiAgZXRhID0gcmVzdWx0YWRvc18xNTUwMDIkZXRhWzFdLAogIG1heF9kZXB0aCA9IHJlc3VsdGFkb3NfMTU1MDAyJG1heF9kZXB0aFsxXSwKICBtaW5fY2hpbGRfd2VpZ2h0ID0gcmVzdWx0YWRvc18xNTUwMDIkbWluX2NoaWxkX3dlaWdodFsxXSwKICBzdWJzYW1wbGUgPSByZXN1bHRhZG9zXzE1NTAwMiRzdWJzYW1wbGVbMV0sCiAgY29sc2FtcGxlX2J5dHJlZSA9IHJlc3VsdGFkb3NfMTU1MDAyJGNvbHNhbXBsZV9ieXRyZWVbMV0sCiAgZ2FtbWEgPSByZXN1bHRhZG9zXzE1NTAwMiRnYW1tYVsxXQopCgptZWpvcl9ucm91bmRzXzE1NTAwMiA8LSByZXN1bHRhZG9zXzE1NTAwMiRucm91bmRzWzFdCgpjYXQoIlxuTWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zIGVuY29udHJhZG9zOlxuIikKcHJpbnQobWVqb3Jlc19wYXJhbXNfMTU1MDAyKQpjYXQoIk7Dum1lcm8gw7NwdGltbyBkZSByb25kYXM6IiwgbWVqb3JfbnJvdW5kc18xNTUwMDIsICJcblxuIikKCm1vZGVsb194Z2JfMTU1MDAyIDwtIHhnYi50cmFpbigKICBwYXJhbXMgPSBtZWpvcmVzX3BhcmFtc18xNTUwMDIsCiAgZGF0YSA9IGR0cmFpbl8xNTUwMDIsCiAgbnJvdW5kcyA9IG1lam9yX25yb3VuZHNfMTU1MDAyLAogIHdhdGNobGlzdCA9IGxpc3QodHJhaW4gPSBkdHJhaW5fMTU1MDAyLCB0ZXN0ID0gZHRlc3RfMTU1MDAyKSwKICB2ZXJib3NlID0gMAopCgojIFBhc28gODogRXZhbHVhY2nDs24gZW4gY29uanVudG8gZGUgcHJ1ZWJhCnByZWRpY2Npb25lc190ZXN0XzE1NTAwMiA8LSBwcmVkaWN0KG1vZGVsb194Z2JfMTU1MDAyLCBkdGVzdF8xNTUwMDIpCm1hcGVfdGVzdF8xNTUwMDIgPC0gbWVhbihhYnMoKHRlc3RfeV8xNTUwMDIgLSBwcmVkaWNjaW9uZXNfdGVzdF8xNTUwMDIpIC8gcG1heCh0ZXN0X3lfMTU1MDAyLCAwLjAxKSkpICogMTAwCnJtc2VfdGVzdF8xNTUwMDIgPC0gc3FydChtZWFuKCh0ZXN0X3lfMTU1MDAyIC0gcHJlZGljY2lvbmVzX3Rlc3RfMTU1MDAyKV4yKSkKCmNhdCgiXG5Nw6l0cmljYXMgZW4gY29uanVudG8gZGUgcHJ1ZWJhOlxuIikKY2F0KCJNQVBFIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBtYXBlX3Rlc3RfMTU1MDAyLCAiXG4iKQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIHJtc2VfdGVzdF8xNTUwMDIsICJcblxuIikKCiMgUGFzbyA5OiBQcmVkaWNjaW9uZXMgZW4gZWwgY29uanVudG8gY29tcGxldG8KeF9jb21wbGV0b18xNTUwMDIgPC0gYXMubWF0cml4KGRhdG9zX21vZGVsb18xNTUwMDJbLCBjb2xuYW1lcyhkYXRvc19tb2RlbG9fMTU1MDAyKSAhPSAiVmVudGEiXSkKcHJlZGljY2lvbmVzX2NvbXBsZXRvXzE1NTAwMiA8LSBwcmVkaWN0KG1vZGVsb194Z2JfMTU1MDAyLCB4X2NvbXBsZXRvXzE1NTAwMikKbWFwZV9jb21wbGV0b18xNTUwMDIgPC0gbWVhbihhYnMoKGRhdG9zX21vZGVsb18xNTUwMDIkVmVudGEgLSBwcmVkaWNjaW9uZXNfY29tcGxldG9fMTU1MDAyKSAvIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwbWF4KGRhdG9zX21vZGVsb18xNTUwMDIkVmVudGEsIDAuMDEpKSkgKiAxMDAKcm1zZV9jb21wbGV0b18xNTUwMDIgPC0gc3FydChtZWFuKChkYXRvc19tb2RlbG9fMTU1MDAyJFZlbnRhIC0gcHJlZGljY2lvbmVzX2NvbXBsZXRvXzE1NTAwMileMikpCgpjYXQoIk3DqXRyaWNhcyBlbiBjb25qdW50byBjb21wbGV0bzpcbiIpCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgbWFwZV9jb21wbGV0b18xNTUwMDIsICJcbiIpCmNhdCgiUk1TRSBkZWwgbW9kZWxvIFhHQm9vc3Q6Iiwgcm1zZV9jb21wbGV0b18xNTUwMDIsICJcblxuIikKCiMgUGFzbyAxMDogSW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCmltcG9ydGFuY2lhXzE1NTAwMiA8LSB4Z2IuaW1wb3J0YW5jZSgKICBmZWF0dXJlX25hbWVzID0gY29sbmFtZXMoZGF0b3NfbW9kZWxvXzE1NTAwMilbY29sbmFtZXMoZGF0b3NfbW9kZWxvXzE1NTAwMikgIT0gIlZlbnRhIl0sCiAgbW9kZWwgPSBtb2RlbG9feGdiXzE1NTAwMgopCmNhdCgiSW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzOlxuIikKcHJpbnQoaW1wb3J0YW5jaWFfMTU1MDAyKQoKeGdiLnBsb3QuaW1wb3J0YW5jZShpbXBvcnRhbmNlX21hdHJpeCA9IGltcG9ydGFuY2lhXzE1NTAwMiwgCiAgICAgICAgICAgICAgICAgICAgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAxNTUwMDIgKFhHQm9vc3QpIikKCiMgUGFzbyAxMTogVmlzdWFsaXphY2lvbmVzCmRhdG9zX2dyYWZpY29fMTU1MDAyIDwtIGRhdGEuZnJhbWUoCiAgT2JzZXJ2YWRvID0gZGF0b3NfbW9kZWxvXzE1NTAwMiRWZW50YSwKICBQcmVkaWNobyA9IHByZWRpY2Npb25lc19jb21wbGV0b18xNTUwMDIKKQoKZ2dwbG90KGRhdG9zX2dyYWZpY29fMTU1MDAyLCBhZXMoeCA9IE9ic2VydmFkbywgeSA9IFByZWRpY2hvKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDE1NTAwMiAoWEdCb29zdCkiLAogICAgeCA9ICJWZW50YXMgT2JzZXJ2YWRhcyIsCiAgICB5ID0gIlZlbnRhcyBQcmVkaWNoYXMiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCgplcnJvcmVzXzE1NTAwMiA8LSBkYXRvc19tb2RlbG9fMTU1MDAyJFZlbnRhIC0gcHJlZGljY2lvbmVzX2NvbXBsZXRvXzE1NTAwMgoKaGlzdChlcnJvcmVzXzE1NTAwMiwgCiAgICAgbWFpbiA9ICJEaXN0cmlidWNpw7NuIGRlIEVycm9yZXMgLSBQcm9kdWN0byAxNTUwMDIgKFhHQm9vc3QpIiwKICAgICB4bGFiID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiLAogICAgIGNvbCA9ICJza3libHVlIiwKICAgICBicmVha3MgPSAzMCkKCmdncGxvdChkYXRhLmZyYW1lKFByZWRpY2hvID0gcHJlZGljY2lvbmVzX2NvbXBsZXRvXzE1NTAwMiwgRXJyb3IgPSBlcnJvcmVzXzE1NTAwMiksIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJFcnJvciB2cyBQcmVkaWNjacOzbiAtIFByb2R1Y3RvIDE1NTAwMiAoWEdCb29zdCkiLAogICAgeCA9ICJWZW50YXMgUHJlZGljaGFzIiwKICAgIHkgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIKICApICsKICB0aGVtZV9taW5pbWFsKCkKCiMgUGFzbyAxMjogR3VhcmRhciBtw6l0cmljYXMKaWYgKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7CiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLAogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksCiAgICBNQVBFID0gbnVtZXJpYygpLAogICAgUk1TRSA9IG51bWVyaWMoKSwKICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQogICkKfQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIxNTUwMDIiLAogIE1vZGVsbyA9ICJYR0Jvb3N0IiwKICBNQVBFID0gbWFwZV9jb21wbGV0b18xNTUwMDIsCiAgUk1TRSA9IHJtc2VfY29tcGxldG9fMTU1MDAyCikpCmBgYAoKIyMgUFJPRFVDVE8gMzY3ODA1NQoKYGBge3J9CiMgUGFzbyAxOiBQcmVwYXJhciBkYXRvcwpkYXRvc19tb2RlbG9fMzY3ODA1NSA8LSBkYXRvc18zNjc4MDU1ICU+JQogIHNlbGVjdCgtVHJ4X0ZlY2hhLCAtRmVjaGEpCgojIFBhc28gMjogRGl2aXNpw7NuIGVuIGVudHJlbmFtaWVudG8geSBwcnVlYmEKc2V0LnNlZWQoMTIzKQp0cmFpbl9pbmRleF8zNjc4MDU1IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGF0b3NfbW9kZWxvXzM2NzgwNTUkVmVudGEsIHAgPSAwLjgsIGxpc3QgPSBGQUxTRSkKdHJhaW5fZGF0YV8zNjc4MDU1IDwtIGRhdG9zX21vZGVsb18zNjc4MDU1W3RyYWluX2luZGV4XzM2NzgwNTUsIF0KdGVzdF9kYXRhXzM2NzgwNTUgPC0gZGF0b3NfbW9kZWxvXzM2NzgwNTVbLXRyYWluX2luZGV4XzM2NzgwNTUsIF0KCiMgUGFzbyAzOiBNYXRyaWNlcyBwYXJhIFhHQm9vc3QKdHJhaW5feF8zNjc4MDU1IDwtIGFzLm1hdHJpeCh0cmFpbl9kYXRhXzM2NzgwNTVbLCBjb2xuYW1lcyh0cmFpbl9kYXRhXzM2NzgwNTUpICE9ICJWZW50YSJdKQp0cmFpbl95XzM2NzgwNTUgPC0gdHJhaW5fZGF0YV8zNjc4MDU1JFZlbnRhCnRlc3RfeF8zNjc4MDU1IDwtIGFzLm1hdHJpeCh0ZXN0X2RhdGFfMzY3ODA1NVssIGNvbG5hbWVzKHRlc3RfZGF0YV8zNjc4MDU1KSAhPSAiVmVudGEiXSkKdGVzdF95XzM2NzgwNTUgPC0gdGVzdF9kYXRhXzM2NzgwNTUkVmVudGEKCmR0cmFpbl8zNjc4MDU1IDwtIHhnYi5ETWF0cml4KGRhdGEgPSB0cmFpbl94XzM2NzgwNTUsIGxhYmVsID0gdHJhaW5feV8zNjc4MDU1KQpkdGVzdF8zNjc4MDU1IDwtIHhnYi5ETWF0cml4KGRhdGEgPSB0ZXN0X3hfMzY3ODA1NSwgbGFiZWwgPSB0ZXN0X3lfMzY3ODA1NSkKCiMgUGFzbyA0OiBEZWZpbmlyIGdyaWQKcGFyYW1fZ3JpZF8zNjc4MDU1IDwtIGV4cGFuZC5ncmlkKAogIGV0YSA9IGMoMC4wMSwgMC4wNSwgMC4xLCAwLjMpLAogIG1heF9kZXB0aCA9IGMoMywgNiwgOSksCiAgbWluX2NoaWxkX3dlaWdodCA9IGMoMSwgMywgNSksCiAgc3Vic2FtcGxlID0gYygwLjcsIDAuOSksCiAgY29sc2FtcGxlX2J5dHJlZSA9IGMoMC43LCAwLjkpLAogIGdhbW1hID0gYygwLCAwLjEsIDAuMykKKQoKY2F0KCJHcmlkIFNlYXJjaCBwYXJhIFhHQm9vc3QgLSBQcm9kdWN0byAzNjc4MDU1XG4iKQpjYXQoIk7Dum1lcm8gdG90YWwgZGUgY29tYmluYWNpb25lcyBkZSBoaXBlcnBhcsOhbWV0cm9zOiIsIG5yb3cocGFyYW1fZ3JpZF8zNjc4MDU1KSwgIlxuXG4iKQoKIyBMaW1pdGFyIGNvbWJpbmFjaW9uZXMKc2V0LnNlZWQoNDU2KQppZiAobnJvdyhwYXJhbV9ncmlkXzM2NzgwNTUpID4gMTIpIHsKICBzZWxlY3RlZF9pbmRpY2VzIDwtIHNhbXBsZSgxOm5yb3cocGFyYW1fZ3JpZF8zNjc4MDU1KSwgMTIpCiAgcGFyYW1fZ3JpZF8zNjc4MDU1IDwtIHBhcmFtX2dyaWRfMzY3ODA1NVtzZWxlY3RlZF9pbmRpY2VzLCBdCiAgY2F0KCJTZWxlY2Npb25hbmRvIDEyIGNvbWJpbmFjaW9uZXMgYWxlYXRvcmlhcyBwYXJhIGV2YWx1YWNpw7NuLlxuXG4iKQp9CgojIFBhc28gNTogR3JpZCBTZWFyY2gKcmVzdWx0YWRvc18zNjc4MDU1IDwtIGRhdGEuZnJhbWUoKQoKY2F0KCJJbmljaWFuZG8gR3JpZCBTZWFyY2guLi5cbiIpCgpmb3IgKGkgaW4gMTpucm93KHBhcmFtX2dyaWRfMzY3ODA1NSkpIHsKICBwYXJhbXMgPC0gbGlzdCgKICAgIG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIiwKICAgIGV2YWxfbWV0cmljID0gInJtc2UiLAogICAgZXRhID0gcGFyYW1fZ3JpZF8zNjc4MDU1JGV0YVtpXSwKICAgIG1heF9kZXB0aCA9IHBhcmFtX2dyaWRfMzY3ODA1NSRtYXhfZGVwdGhbaV0sCiAgICBtaW5fY2hpbGRfd2VpZ2h0ID0gcGFyYW1fZ3JpZF8zNjc4MDU1JG1pbl9jaGlsZF93ZWlnaHRbaV0sCiAgICBzdWJzYW1wbGUgPSBwYXJhbV9ncmlkXzM2NzgwNTUkc3Vic2FtcGxlW2ldLAogICAgY29sc2FtcGxlX2J5dHJlZSA9IHBhcmFtX2dyaWRfMzY3ODA1NSRjb2xzYW1wbGVfYnl0cmVlW2ldLAogICAgZ2FtbWEgPSBwYXJhbV9ncmlkXzM2NzgwNTUkZ2FtbWFbaV0KICApCiAgCiAgY2F0KCJFdmFsdWFuZG8gY29tYmluYWNpw7NuIiwgaSwgImRlIiwgbnJvdyhwYXJhbV9ncmlkXzM2NzgwNTUpLCAiOlxuIikKICBjYXQoIiAgZXRhID0iLCBwYXJhbXMkZXRhLCAKICAgICAgIiwgbWF4X2RlcHRoID0iLCBwYXJhbXMkbWF4X2RlcHRoLCAKICAgICAgIiwgbWluX2NoaWxkX3dlaWdodCA9IiwgcGFyYW1zJG1pbl9jaGlsZF93ZWlnaHQsIAogICAgICAiLCBzdWJzYW1wbGUgPSIsIHBhcmFtcyRzdWJzYW1wbGUsIAogICAgICAiLCBjb2xzYW1wbGVfYnl0cmVlID0iLCBwYXJhbXMkY29sc2FtcGxlX2J5dHJlZSwKICAgICAgIiwgZ2FtbWEgPSIsIHBhcmFtcyRnYW1tYSwgIlxuIikKICAKICBjdl9tb2RlbCA8LSB4Z2IuY3YoCiAgICBwYXJhbXMgPSBwYXJhbXMsCiAgICBkYXRhID0gZHRyYWluXzM2NzgwNTUsCiAgICBucm91bmRzID0gMjAwLAogICAgbmZvbGQgPSA1LAogICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gMjAsCiAgICB2ZXJib3NlID0gMAogICkKICAKICBiZXN0X2l0ZXJhdGlvbiA8LSBjdl9tb2RlbCRiZXN0X2l0ZXJhdGlvbgogIGJlc3Rfcm1zZSA8LSBtaW4oY3ZfbW9kZWwkZXZhbHVhdGlvbl9sb2ckdGVzdF9ybXNlX21lYW4pCiAgCiAgY2F0KCIgIE1lam9yIGl0ZXJhY2nDs246IiwgYmVzdF9pdGVyYXRpb24sICJcbiIpCiAgY2F0KCIgIFJNU0UgZW4gdmFsaWRhY2nDs24gY3J1emFkYToiLCBiZXN0X3Jtc2UsICJcblxuIikKICAKICByZXN1bHRhZG9zXzM2NzgwNTUgPC0gcmJpbmQocmVzdWx0YWRvc18zNjc4MDU1LCBkYXRhLmZyYW1lKAogICAgZXRhID0gcGFyYW1zJGV0YSwKICAgIG1heF9kZXB0aCA9IHBhcmFtcyRtYXhfZGVwdGgsCiAgICBtaW5fY2hpbGRfd2VpZ2h0ID0gcGFyYW1zJG1pbl9jaGlsZF93ZWlnaHQsCiAgICBzdWJzYW1wbGUgPSBwYXJhbXMkc3Vic2FtcGxlLAogICAgY29sc2FtcGxlX2J5dHJlZSA9IHBhcmFtcyRjb2xzYW1wbGVfYnl0cmVlLAogICAgZ2FtbWEgPSBwYXJhbXMkZ2FtbWEsCiAgICBucm91bmRzID0gYmVzdF9pdGVyYXRpb24sCiAgICBybXNlX2N2ID0gYmVzdF9ybXNlCiAgKSkKfQoKIyBQYXNvIDY6IFNlbGVjY2nDs24geSBlbnRyZW5hbWllbnRvIGZpbmFsCnJlc3VsdGFkb3NfMzY3ODA1NSA8LSByZXN1bHRhZG9zXzM2NzgwNTVbb3JkZXIocmVzdWx0YWRvc18zNjc4MDU1JHJtc2VfY3YpLCBdCgptZWpvcmVzX3BhcmFtc18zNjc4MDU1IDwtIGxpc3QoCiAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLAogIGV2YWxfbWV0cmljID0gInJtc2UiLAogIGV0YSA9IHJlc3VsdGFkb3NfMzY3ODA1NSRldGFbMV0sCiAgbWF4X2RlcHRoID0gcmVzdWx0YWRvc18zNjc4MDU1JG1heF9kZXB0aFsxXSwKICBtaW5fY2hpbGRfd2VpZ2h0ID0gcmVzdWx0YWRvc18zNjc4MDU1JG1pbl9jaGlsZF93ZWlnaHRbMV0sCiAgc3Vic2FtcGxlID0gcmVzdWx0YWRvc18zNjc4MDU1JHN1YnNhbXBsZVsxXSwKICBjb2xzYW1wbGVfYnl0cmVlID0gcmVzdWx0YWRvc18zNjc4MDU1JGNvbHNhbXBsZV9ieXRyZWVbMV0sCiAgZ2FtbWEgPSByZXN1bHRhZG9zXzM2NzgwNTUkZ2FtbWFbMV0KKQoKbWVqb3JfbnJvdW5kc18zNjc4MDU1IDwtIHJlc3VsdGFkb3NfMzY3ODA1NSRucm91bmRzWzFdCgpjYXQoIlxuTWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zIGVuY29udHJhZG9zOlxuIikKcHJpbnQobWVqb3Jlc19wYXJhbXNfMzY3ODA1NSkKY2F0KCJOw7ptZXJvIMOzcHRpbW8gZGUgcm9uZGFzOiIsIG1lam9yX25yb3VuZHNfMzY3ODA1NSwgIlxuXG4iKQoKbW9kZWxvX3hnYl8zNjc4MDU1IDwtIHhnYi50cmFpbigKICBwYXJhbXMgPSBtZWpvcmVzX3BhcmFtc18zNjc4MDU1LAogIGRhdGEgPSBkdHJhaW5fMzY3ODA1NSwKICBucm91bmRzID0gbWVqb3JfbnJvdW5kc18zNjc4MDU1LAogIHdhdGNobGlzdCA9IGxpc3QodHJhaW4gPSBkdHJhaW5fMzY3ODA1NSwgdGVzdCA9IGR0ZXN0XzM2NzgwNTUpLAogIHZlcmJvc2UgPSAwCikKCiMgUGFzbyA3OiBFdmFsdWFjacOzbiBlbiB0ZXN0CnByZWRpY2Npb25lc190ZXN0XzM2NzgwNTUgPC0gcHJlZGljdChtb2RlbG9feGdiXzM2NzgwNTUsIGR0ZXN0XzM2NzgwNTUpCm1hcGVfdGVzdF8zNjc4MDU1IDwtIG1lYW4oYWJzKCh0ZXN0X3lfMzY3ODA1NSAtIHByZWRpY2Npb25lc190ZXN0XzM2NzgwNTUpIC8gcG1heCh0ZXN0X3lfMzY3ODA1NSwgMC4wMSkpKSAqIDEwMApybXNlX3Rlc3RfMzY3ODA1NSA8LSBzcXJ0KG1lYW4oKHRlc3RfeV8zNjc4MDU1IC0gcHJlZGljY2lvbmVzX3Rlc3RfMzY3ODA1NSleMikpCgpjYXQoIlxuTcOpdHJpY2FzIGVuIGNvbmp1bnRvIGRlIHBydWViYTpcbiIpCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgbWFwZV90ZXN0XzM2NzgwNTUsICJcbiIpCmNhdCgiUk1TRSBkZWwgbW9kZWxvIFhHQm9vc3Q6Iiwgcm1zZV90ZXN0XzM2NzgwNTUsICJcblxuIikKCiMgUGFzbyA4OiBFdmFsdWFjacOzbiBlbiBjb25qdW50byBjb21wbGV0bwp4X2NvbXBsZXRvXzM2NzgwNTUgPC0gYXMubWF0cml4KGRhdG9zX21vZGVsb18zNjc4MDU1WywgY29sbmFtZXMoZGF0b3NfbW9kZWxvXzM2NzgwNTUpICE9ICJWZW50YSJdKQpwcmVkaWNjaW9uZXNfY29tcGxldG9fMzY3ODA1NSA8LSBwcmVkaWN0KG1vZGVsb194Z2JfMzY3ODA1NSwgeF9jb21wbGV0b18zNjc4MDU1KQoKbWFwZV9jb21wbGV0b18zNjc4MDU1IDwtIG1lYW4oYWJzKChkYXRvc19tb2RlbG9fMzY3ODA1NSRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0b18zNjc4MDU1KSAvIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcG1heChkYXRvc19tb2RlbG9fMzY3ODA1NSRWZW50YSwgMC4wMSkpKSAqIDEwMApybXNlX2NvbXBsZXRvXzM2NzgwNTUgPC0gc3FydChtZWFuKChkYXRvc19tb2RlbG9fMzY3ODA1NSRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0b18zNjc4MDU1KV4yKSkKCmNhdCgiTcOpdHJpY2FzIGVuIGNvbmp1bnRvIGNvbXBsZXRvOlxuIikKY2F0KCJNQVBFIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBtYXBlX2NvbXBsZXRvXzM2NzgwNTUsICJcbiIpCmNhdCgiUk1TRSBkZWwgbW9kZWxvIFhHQm9vc3Q6Iiwgcm1zZV9jb21wbGV0b18zNjc4MDU1LCAiXG5cbiIpCgojIFBhc28gOTogSW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCmltcG9ydGFuY2lhXzM2NzgwNTUgPC0geGdiLmltcG9ydGFuY2UoCiAgZmVhdHVyZV9uYW1lcyA9IGNvbG5hbWVzKGRhdG9zX21vZGVsb18zNjc4MDU1KVtjb2xuYW1lcyhkYXRvc19tb2RlbG9fMzY3ODA1NSkgIT0gIlZlbnRhIl0sCiAgbW9kZWwgPSBtb2RlbG9feGdiXzM2NzgwNTUKKQoKY2F0KCJJbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXM6XG4iKQpwcmludChpbXBvcnRhbmNpYV8zNjc4MDU1KQoKeGdiLnBsb3QuaW1wb3J0YW5jZShpbXBvcnRhbmNlX21hdHJpeCA9IGltcG9ydGFuY2lhXzM2NzgwNTUsIAogICAgICAgICAgICAgICAgICAgIG1haW4gPSAiSW1wb3J0YW5jaWEgZGUgVmFyaWFibGVzIC0gUHJvZHVjdG8gMzY3ODA1NSAoWEdCb29zdCkiKQoKIyBQYXNvIDEwOiBWaXN1YWxpemFjaW9uZXMKZGF0b3NfZ3JhZmljb18zNjc4MDU1IDwtIGRhdGEuZnJhbWUoCiAgT2JzZXJ2YWRvID0gZGF0b3NfbW9kZWxvXzM2NzgwNTUkVmVudGEsCiAgUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfY29tcGxldG9fMzY3ODA1NQopCgpnZ3Bsb3QoZGF0b3NfZ3JhZmljb18zNjc4MDU1LCBhZXMoeCA9IE9ic2VydmFkbywgeSA9IFByZWRpY2hvKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDM2NzgwNTUgKFhHQm9vc3QpIiwKICAgIHggPSAiVmVudGFzIE9ic2VydmFkYXMiLAogICAgeSA9ICJWZW50YXMgUHJlZGljaGFzIgogICkgKwogIHRoZW1lX21pbmltYWwoKQoKZXJyb3Jlc18zNjc4MDU1IDwtIGRhdG9zX21vZGVsb18zNjc4MDU1JFZlbnRhIC0gcHJlZGljY2lvbmVzX2NvbXBsZXRvXzM2NzgwNTUKCmhpc3QoZXJyb3Jlc18zNjc4MDU1LCAKICAgICBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDM2NzgwNTUgKFhHQm9vc3QpIiwKICAgICB4bGFiID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiLAogICAgIGNvbCA9ICJza3libHVlIiwKICAgICBicmVha3MgPSAzMCkKCmdncGxvdChkYXRhLmZyYW1lKFByZWRpY2hvID0gcHJlZGljY2lvbmVzX2NvbXBsZXRvXzM2NzgwNTUsIEVycm9yID0gZXJyb3Jlc18zNjc4MDU1KSwgYWVzKHggPSBQcmVkaWNobywgeSA9IEVycm9yKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicygKICAgIHRpdGxlID0gIkVycm9yIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMzY3ODA1NSAoWEdCb29zdCkiLAogICAgeCA9ICJWZW50YXMgUHJlZGljaGFzIiwKICAgIHkgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIKICApICsKICB0aGVtZV9taW5pbWFsKCkKCiMgUGFzbyAxMTogR3VhcmRhciBtw6l0cmljYXMKaWYgKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7CiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLAogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksCiAgICBNQVBFID0gbnVtZXJpYygpLAogICAgUk1TRSA9IG51bWVyaWMoKSwKICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQogICkKfQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIzNjc4MDU1IiwKICBNb2RlbG8gPSAiWEdCb29zdCIsCiAgTUFQRSA9IG1hcGVfY29tcGxldG9fMzY3ODA1NSwKICBSTVNFID0gcm1zZV9jb21wbGV0b18zNjc4MDU1CikpCmBgYAoKYGBge3J9CiMgR3VhcmRhciBtw6l0cmljYXMgZGUgWEdCb29zdCBwYXJhIHByb2R1Y3RvIDE1NTAwMQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgewogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLAogICAgTUFQRSA9IG51bWVyaWMoKSwKICAgIFJNU0UgPSBudW1lcmljKCksCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCn0KCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMzY3ODA1NSIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bwogIE1vZGVsbyA9ICJYR0Jvb3N0IiwKICBNQVBFID0gbWFwZV9jb21wbGV0bywKICBSTVNFID0gcm1zZV9jb21wbGV0bwopKQpgYGAKCiMgVmlzdWFsaXphY2nDs24gZGUgTcOpdHJpY2FzCgpgYGB7cn0KIyBEZWZpbmlyIGxvcyBjb2xvcmVzIHBhcmEgY2FkYSBtb2RlbG8KY29sb3Jlc19tb2RlbG9zIDwtIGMoCiAgIkFSTUEvU0FSSU1BIiA9ICIjMWY3N2I0IiwgICAgIyBBenVsCiAgIlJlZ3Jlc2nDs24gTGluZWFsIiA9ICIjZmY3ZjBlIiwgIyBOYXJhbmphCiAgIlJhbmRvbSBGb3Jlc3QiID0gIiMyY2EwMmMiLCAgICMgVmVyZGUKICAiWEdCb29zdCIgPSAiI2Q2MjcyOCIgICAgICAgICAjIFJvam8KKQpgYGAKCiMjIFBST0RVQ1RPIDE1NTAwMQpgYGB7cn0KIyBQcmltZXJvLCB2ZWFtb3MgcXXDqSBkYXRvcyB0ZW5lbW9zIHJlYWxtZW50ZQpwcmludCgiRGF0b3MgYWN0dWFsZXMgcGFyYSBlbCBwcm9kdWN0byAxNTUwMDE6IikKcHJpbnQobWV0cmljYXNfY29tcGFyYXRpdmFzICU+JSBmaWx0ZXIoUHJvZHVjdG8gPT0gIjE1NTAwMSIpKQoKIyBDcmVhciB1biBkYXRhZnJhbWUgbWFudWFsbWVudGUgY29uIGxvcyA0IG1vZGVsb3MgcGFyYSBlbCBwcm9kdWN0byAxNTUwMDEKIyAoY29uIHZhbG9yZXMgZGUgZWplbXBsbyBzaSBlcyBuZWNlc2FyaW8pCmRhdG9zXzE1NTAwMV9jb21wbGV0byA8LSBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gcmVwKCIxNTUwMDEiLCA0KSwKICBNb2RlbG8gPSBjKCJBUk1BL1NBUklNQSIsICJSZWdyZXNpw7NuIExpbmVhbCIsICJSYW5kb20gRm9yZXN0IiwgIlhHQm9vc3QiKSwKICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKKQoKIyBVbmlyIGNvbiBsb3MgZGF0b3MgZXhpc3RlbnRlcwpkYXRvc18xNTUwMDFfY29tcGxldG8gPC0gbGVmdF9qb2luKAogIGRhdG9zXzE1NTAwMV9jb21wbGV0bywKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgJT4lIGZpbHRlcihQcm9kdWN0byA9PSAiMTU1MDAxIiksCiAgYnkgPSBjKCJQcm9kdWN0byIsICJNb2RlbG8iKQopCgojIEFob3JhIGFzaWduYSB2YWxvcmVzIHBhcmEgbGFzIG3DqXRyaWNhcyBkZSBsb3MgbW9kZWxvcyBmYWx0YW50ZXMKIyBTaSB0aWVuZXMgbG9zIHZhbG9yZXMsIHJlZW1wbGF6YSBsb3MgMCBjb24gbG9zIHZhbG9yZXMgY29ycmVjdG9zCiMgTyB0b21hIG5vdGEgZGUgY3XDoWxlcyBzb24gTkEgcGFyYSByZWVtcGxhemFybG9zIGNvbiBsb3MgdmFsb3JlcyByZWFsZXMKCiMgVmFsb3JlcyBwYXJhIFJlZ3Jlc2nDs24gTGluZWFsIChyZWVtcGxhemEgZXN0b3MgY29uIGxvcyB2YWxvcmVzIHJlYWxlcykKaWYgKGlzLm5hKGRhdG9zXzE1NTAwMV9jb21wbGV0byRNQVBFWzJdKSkgewogIGRhdG9zXzE1NTAwMV9jb21wbGV0byRNQVBFWzJdIDwtIG1hcGVfMTU1MDAxICAjIE8gZWwgdmFsb3IgY29ycmVjdG8KfQppZiAoaXMubmEoZGF0b3NfMTU1MDAxX2NvbXBsZXRvJFJNU0VbMl0pKSB7CiAgZGF0b3NfMTU1MDAxX2NvbXBsZXRvJFJNU0VbMl0gPC0gcm1zZV8xNTUwMDEgICMgWWEgbm8gTVNFCn0KCgoKCiMgVmFsb3JlcyBwYXJhIFJhbmRvbSBGb3Jlc3QgKHJlZW1wbGF6YSBlc3RvcyBjb24gbG9zIHZhbG9yZXMgcmVhbGVzKQojIFNpIHlhIGVqZWN1dGFzdGUgbGEgc2VjY2nDs24gZGUgUmFuZG9tIEZvcmVzdCBwYXJhIGVsIHByb2R1Y3RvIDE1NTAwMSwKIyB1c2EgbGFzIHZhcmlhYmxlcyByMl9yZiwgcm1zZV9yZiwgZXRjLgppZiAoaXMubmEoZGF0b3NfMTU1MDAxX2NvbXBsZXRvJE1BUEVbM10pICYmIGV4aXN0cygibWFwZV9yZiIpKSB7CiAgZGF0b3NfMTU1MDAxX2NvbXBsZXRvJE1BUEVbM10gPC0gbWFwZV9yZgp9CgppZiAoaXMubmEoZGF0b3NfMTU1MDAxX2NvbXBsZXRvJFJNU0VbM10pICYmIGV4aXN0cygicm1zZV9yZiIpKSB7CiAgZGF0b3NfMTU1MDAxX2NvbXBsZXRvJFJNU0VbM10gPC0gcm1zZV9yZgp9CgoKCgoKCiMgVmFsb3JlcyBwYXJhIFhHQm9vc3QgKHJlZW1wbGF6YSBlc3RvcyBjb24gbG9zIHZhbG9yZXMgcmVhbGVzKQojIFNpIHlhIGVqZWN1dGFzdGUgbGEgc2VjY2nDs24gZGUgWEdCb29zdCBwYXJhIGVsIHByb2R1Y3RvIDE1NTAwMSwKIyB1c2EgbGFzIHZhcmlhYmxlcyByMl9jb21wbGV0bywgcm1zZV9jb21wbGV0bywgZXRjLgppZiAoaXMubmEoZGF0b3NfMTU1MDAxX2NvbXBsZXRvJE1BUEVbNF0pICYmIGV4aXN0cygibWFwZV9jb21wbGV0byIpKSB7CiAgZGF0b3NfMTU1MDAxX2NvbXBsZXRvJE1BUEVbNF0gPC0gbWFwZV9jb21wbGV0bwp9CgppZiAoaXMubmEoZGF0b3NfMTU1MDAxX2NvbXBsZXRvJFJNU0VbNF0pICYmIGV4aXN0cygicm1zZV9jb21wbGV0byIpKSB7CiAgZGF0b3NfMTU1MDAxX2NvbXBsZXRvJFJNU0VbNF0gPC0gcm1zZV9jb21wbGV0bwp9CgoKIyBWZXIgbG9zIGRhdG9zIGNvbXBsZXRvcwpwcmludCgiRGF0b3MgY29tcGxldG9zIHBhcmEgZWwgcHJvZHVjdG8gMTU1MDAxOiIpCnByaW50KGRhdG9zXzE1NTAwMV9jb21wbGV0bykKCiMgR3LDoWZpY28gcGFyYSBNQVBFCmdncGxvdChkYXRvc18xNTUwMDFfY29tcGxldG8sIGFlcyh4ID0gTW9kZWxvLCB5ID0gTUFQRSwgZmlsbCA9IE1vZGVsbykpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQoTUFQRSwgMSkpLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzLjUpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsKICBsYWJzKAogICAgdGl0bGUgPSAiQ29tcGFyYWNpw7NuIGRlIG1vZGVsb3MgcGFyYSBQcm9kdWN0byAxNTUwMDEiLAogICAgc3VidGl0bGUgPSAiTcOpdHJpY2E6IE1BUEUgKHZhbG9yZXMgbcOhcyBiYWpvcyBpbmRpY2FuIG1lam9yIHByZWNpc2nDs24pIiwKICAgIHggPSAiIiwKICAgIHkgPSAiTUFQRSAoJSkiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkKICApIAoKIyBHcsOhZmljbyBwYXJhIFJNU0UKZ2dwbG90KGRhdG9zXzE1NTAwMV9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBSTVNFLCBmaWxsID0gTW9kZWxvKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IDAuNikgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChSTVNFLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgbW9kZWxvcyBwYXJhIFByb2R1Y3RvIDE1NTAwMSIsCiAgICBzdWJ0aXRsZSA9ICJNw6l0cmljYTogUk1TRSAodmFsb3JlcyBtw6FzIGJham9zIGluZGljYW4gbWVqb3IgcHJlY2lzacOzbikiLAogICAgeCA9ICIiLAogICAgeSA9ICJSTVNFIgogICkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpCiAgKSArCiAgeWxpbSgwLCBtYXgoZGF0b3NfMTU1MDAxX2NvbXBsZXRvJFJNU0UsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZCmBgYAoKCiMjIFBST0RVQ1RPIDM5Mjk3ODgKYGBge3J9CiMgUHJpbWVybywgdmVhbW9zIHF1w6kgZGF0b3MgdGVuZW1vcyByZWFsbWVudGUKcHJpbnQoIkRhdG9zIGFjdHVhbGVzIHBhcmEgZWwgcHJvZHVjdG8gMzkyOTc4ODoiKQpwcmludChtZXRyaWNhc19jb21wYXJhdGl2YXMgJT4lIGZpbHRlcihQcm9kdWN0byA9PSAiMzkyOTc4OCIpKQoKIyBDcmVhciB1biBkYXRhZnJhbWUgbWFudWFsbWVudGUgY29uIGxvcyA0IG1vZGVsb3MgcGFyYSBlbCBwcm9kdWN0byAzOTI5Nzg4CmRhdG9zXzM5Mjk3ODhfY29tcGxldG8gPC0gZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9IHJlcCgiMzkyOTc4OCIsIDQpLAogIE1vZGVsbyA9IGMoIkFSTUEvU0FSSU1BIiwgIlJlZ3Jlc2nDs24gTGluZWFsIiwgIlJhbmRvbSBGb3Jlc3QiLCAiWEdCb29zdCIpLAogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQopCgojIFVuaXIgY29uIGxvcyBkYXRvcyBleGlzdGVudGVzCmRhdG9zXzM5Mjk3ODhfY29tcGxldG8gPC0gbGVmdF9qb2luKAogIGRhdG9zXzM5Mjk3ODhfY29tcGxldG8sCiAgbWV0cmljYXNfY29tcGFyYXRpdmFzICU+JSBmaWx0ZXIoUHJvZHVjdG8gPT0gIjM5Mjk3ODgiKSwKICBieSA9IGMoIlByb2R1Y3RvIiwgIk1vZGVsbyIpCikKCiMgQWhvcmEgYXNpZ25hIHZhbG9yZXMgcGFyYSBsYXMgbcOpdHJpY2FzIGRlIGxvcyBtb2RlbG9zIGZhbHRhbnRlcwojIFZhbG9yZXMgcGFyYSBSZWdyZXNpw7NuIExpbmVhbAppZiAoaXMubmEoZGF0b3NfMzkyOTc4OF9jb21wbGV0byRNQVBFWzJdKSkgewogIGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kTUFQRVsyXSA8LSBtYXBlXzM5Mjk3ODgKfQoKaWYgKGlzLm5hKGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kUk1TRVsyXSkpIHsKICBkYXRvc18zOTI5Nzg4X2NvbXBsZXRvJFJNU0VbMl0gPC0gcm1zZV8zOTI5Nzg4Cn0KCiMgVmFsb3JlcyBwYXJhIFJhbmRvbSBGb3Jlc3QKIyBTaSB5YSBlamVjdXRhc3RlIGxhIHNlY2Npw7NuIGRlIFJhbmRvbSBGb3Jlc3QgcGFyYSBlbCBwcm9kdWN0byAzOTI5Nzg4CmlmIChpcy5uYShkYXRvc18zOTI5Nzg4X2NvbXBsZXRvJE1BUEVbM10pICYmIGV4aXN0cygibWFwZV9yZiIpKSB7CiAgZGF0b3NfMzkyOTc4OF9jb21wbGV0byRNQVBFWzNdIDwtIG1hcGVfcmYKfQoKaWYgKGlzLm5hKGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kUk1TRVszXSkgJiYgZXhpc3RzKCJybXNlX3JmIikpIHsKICBkYXRvc18zOTI5Nzg4X2NvbXBsZXRvJFJNU0VbM10gPC0gcm1zZV9yZgp9CgojIFZhbG9yZXMgcGFyYSBYR0Jvb3N0CmlmIChpcy5uYShkYXRvc18zOTI5Nzg4X2NvbXBsZXRvJE1BUEVbNF0pICYmIGV4aXN0cygibWFwZV9jb21wbGV0byIpKSB7CiAgZGF0b3NfMzkyOTc4OF9jb21wbGV0byRNQVBFWzRdIDwtIG1hcGVfY29tcGxldG8KfQoKCmlmIChpcy5uYShkYXRvc18zOTI5Nzg4X2NvbXBsZXRvJFJNU0VbNF0pICYmIGV4aXN0cygicm1zZV9jb21wbGV0byIpKSB7CiAgZGF0b3NfMzkyOTc4OF9jb21wbGV0byRSTVNFWzRdIDwtIHJtc2VfY29tcGxldG8KfQoKIyBWZXIgbG9zIGRhdG9zIGNvbXBsZXRvcwpwcmludCgiRGF0b3MgY29tcGxldG9zIHBhcmEgZWwgcHJvZHVjdG8gMzkyOTc4ODoiKQpwcmludChkYXRvc18zOTI5Nzg4X2NvbXBsZXRvKQoKIyBEZWZpbmlyIGNvbG9yZXMgcGFyYSBsb3MgbW9kZWxvcwpjb2xvcmVzX21vZGVsb3MgPC0gYygiQVJNQS9TQVJJTUEiID0gIiMxZjc3YjQiLCAKICAgICAgICAgICAgICAgICAgICAgIlJlZ3Jlc2nDs24gTGluZWFsIiA9ICIjZmY3ZjBlIiwgCiAgICAgICAgICAgICAgICAgICAgICJSYW5kb20gRm9yZXN0IiA9ICIjMmNhMDJjIiwgCiAgICAgICAgICAgICAgICAgICAgICJYR0Jvb3N0IiA9ICIjZDYyNzI4IikKCiMgR3LDoWZpY28gcGFyYSBSTVNFCmdncGxvdChkYXRvc18zOTI5Nzg4X2NvbXBsZXRvLCBhZXMoeCA9IE1vZGVsbywgeSA9IFJNU0UsIGZpbGwgPSBNb2RlbG8pKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKFJNU0UsIDEpKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sb3Jlc19tb2RlbG9zKSArCiAgbGFicygKICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHBhcmEgUHJvZHVjdG8gMzkyOTc4OCIsCiAgICBzdWJ0aXRsZSA9ICJNw6l0cmljYTogUk1TRSAodmFsb3JlcyBtw6FzIGJham9zIGluZGljYW4gbWVqb3IgcHJlY2lzacOzbikiLAogICAgeCA9ICIiLAogICAgeSA9ICJSTVNFIgogICkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpCiAgKSArCiAgeWxpbSgwLCBtYXgoZGF0b3NfMzkyOTc4OF9jb21wbGV0byRSTVNFLCBuYS5ybSA9IFRSVUUpICogMS4xKSAgIyBBanVzdGFyIGVsIGzDrW1pdGUgWQoKIyBHcsOhZmljbyBwYXJhIE1BUEUKZ2dwbG90KGRhdG9zXzM5Mjk3ODhfY29tcGxldG8sIGFlcyh4ID0gTW9kZWxvLCB5ID0gTUFQRSwgZmlsbCA9IE1vZGVsbykpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQoTUFQRSwgMSkpLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzLjUpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsKICBsYWJzKAogICAgdGl0bGUgPSAiQ29tcGFyYWNpw7NuIGRlIG1vZGVsb3MgcGFyYSBQcm9kdWN0byAzOTI5Nzg4IiwKICAgIHN1YnRpdGxlID0gIk3DqXRyaWNhOiBNQVBFICh2YWxvcmVzIG3DoXMgYmFqb3MgaW5kaWNhbiBtZWpvciBwcmVjaXNpw7NuKSIsCiAgICB4ID0gIiIsCiAgICB5ID0gIk1BUEUgKCUpIgogICkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpCiAgKSArCiAgeWxpbSgwLCBtYXgoZGF0b3NfMzkyOTc4OF9jb21wbGV0byRNQVBFLCBuYS5ybSA9IFRSVUUpICogMS4xKSAgIyBBanVzdGFyIGVsIGzDrW1pdGUgWQoKYGBgCgojIyBQUk9EVUNUTyAzOTA0MTUyCmBgYHtyfQojIFByaW1lcm8sIHZlYW1vcyBxdcOpIGRhdG9zIHRlbmVtb3MgcmVhbG1lbnRlCnByaW50KCJEYXRvcyBhY3R1YWxlcyBwYXJhIGVsIHByb2R1Y3RvIDM5MDQxNTI6IikKcHJpbnQobWV0cmljYXNfY29tcGFyYXRpdmFzICU+JSBmaWx0ZXIoUHJvZHVjdG8gPT0gIjM5MDQxNTIiKSkKCiMgQ3JlYXIgdW4gZGF0YWZyYW1lIG1hbnVhbG1lbnRlIGNvbiBsb3MgNCBtb2RlbG9zIHBhcmEgZWwgcHJvZHVjdG8gMzkwNDE1MgpkYXRvc18zOTA0MTUyX2NvbXBsZXRvIDwtIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSByZXAoIjM5MDQxNTIiLCA0KSwKICBNb2RlbG8gPSBjKCJBUk1BL1NBUklNQSIsICJSZWdyZXNpw7NuIExpbmVhbCIsICJSYW5kb20gRm9yZXN0IiwgIlhHQm9vc3QiKSwKICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKKQoKIyBVbmlyIGNvbiBsb3MgZGF0b3MgZXhpc3RlbnRlcwpkYXRvc18zOTA0MTUyX2NvbXBsZXRvIDwtIGxlZnRfam9pbigKICBkYXRvc18zOTA0MTUyX2NvbXBsZXRvLAogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyAlPiUgZmlsdGVyKFByb2R1Y3RvID09ICIzOTA0MTUyIiksCiAgYnkgPSBjKCJQcm9kdWN0byIsICJNb2RlbG8iKQopCgojIEFob3JhIGFzaWduYSB2YWxvcmVzIHBhcmEgbGFzIG3DqXRyaWNhcyBkZSBsb3MgbW9kZWxvcyBmYWx0YW50ZXMKIyBWYWxvcmVzIHBhcmEgUmVncmVzacOzbiBMaW5lYWwKaWYgKGlzLm5hKGRhdG9zXzM5MDQxNTJfY29tcGxldG8kTUFQRVsyXSkpIHsKICBkYXRvc18zOTA0MTUyX2NvbXBsZXRvJE1BUEVbMl0gPC0gbWFwZV8zOTA0MTUyCn0KaWYgKGlzLm5hKGRhdG9zXzM5MDQxNTJfY29tcGxldG8kUk1TRVsyXSkpIHsKICBkYXRvc18zOTA0MTUyX2NvbXBsZXRvJFJNU0VbMl0gPC0gcm1zZV8zOTA0MTUyCn0KCiMgVmFsb3JlcyBwYXJhIFJhbmRvbSBGb3Jlc3QKaWYgKGlzLm5hKGRhdG9zXzM5MDQxNTJfY29tcGxldG8kTUFQRVszXSkgJiYgZXhpc3RzKCJtYXBlX3JmIikpIHsKICBkYXRvc18zOTA0MTUyX2NvbXBsZXRvJE1BUEVbM10gPC0gbWFwZV9yZgp9CgppZiAoaXMubmEoZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRSTVNFWzNdKSAmJiBleGlzdHMoInJtc2VfcmYiKSkgewogIGRhdG9zXzM5MDQxNTJfY29tcGxldG8kUk1TRVszXSA8LSBybXNlX3JmCn0KCgojIFZhbG9yZXMgcGFyYSBYR0Jvb3N0CmlmIChpcy5uYShkYXRvc18zOTA0MTUyX2NvbXBsZXRvJE1BUEVbNF0pICYmIGV4aXN0cygibWFwZV9jb21wbGV0byIpKSB7CiAgZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRNQVBFWzRdIDwtIG1hcGVfY29tcGxldG8KfQoKCmlmIChpcy5uYShkYXRvc18zOTA0MTUyX2NvbXBsZXRvJFJNU0VbNF0pICYmIGV4aXN0cygicm1zZV9jb21wbGV0byIpKSB7CiAgZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRSTVNFWzRdIDwtIHJtc2VfY29tcGxldG8KfQoKCiMgVmVyIGxvcyBkYXRvcyBjb21wbGV0b3MKcHJpbnQoIkRhdG9zIGNvbXBsZXRvcyBwYXJhIGVsIHByb2R1Y3RvIDM5MDQxNTI6IikKcHJpbnQoZGF0b3NfMzkwNDE1Ml9jb21wbGV0bykKCiMgRGVmaW5pciBjb2xvcmVzIHBhcmEgbG9zIG1vZGVsb3MKY29sb3Jlc19tb2RlbG9zIDwtIGMoIkFSTUEvU0FSSU1BIiA9ICIjMWY3N2I0IiwgCiAgICAgICAgICAgICAgICAgICAgICJSZWdyZXNpw7NuIExpbmVhbCIgPSAiI2ZmN2YwZSIsIAogICAgICAgICAgICAgICAgICAgICAiUmFuZG9tIEZvcmVzdCIgPSAiIzJjYTAyYyIsIAogICAgICAgICAgICAgICAgICAgICAiWEdCb29zdCIgPSAiI2Q2MjcyOCIpCgojIEdyw6FmaWNvIHBhcmEgTVNFCmdncGxvdChkYXRvc18zOTA0MTUyX2NvbXBsZXRvLCBhZXMoeCA9IE1vZGVsbywgeSA9IFJNU0UsIGZpbGwgPSBNb2RlbG8pKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKFJNU0UsIDEpKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sb3Jlc19tb2RlbG9zKSArCiAgbGFicygKICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHBhcmEgUHJvZHVjdG8gMzkwNDE1MiIsCiAgICBzdWJ0aXRsZSA9ICJNw6l0cmljYTogUk1TRSAodmFsb3JlcyBtw6FzIGJham9zIGluZGljYW4gbWVqb3IgcHJlY2lzacOzbikiLAogICAgeCA9ICIiLAogICAgeSA9ICJSTVNFIgogICkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpCiAgKSArCiAgeWxpbSgwLCBtYXgoZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRSTVNFLCBuYS5ybSA9IFRSVUUpICogMS4xKSAgIyBBanVzdGFyIGVsIGzDrW1pdGUgWQoKIyBHcsOhZmljbyBwYXJhIE1BUEUKZ2dwbG90KGRhdG9zXzM5MDQxNTJfY29tcGxldG8sIGFlcyh4ID0gTW9kZWxvLCB5ID0gTUFQRSwgZmlsbCA9IE1vZGVsbykpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQoTUFQRSwgMSkpLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzLjUpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsKICBsYWJzKAogICAgdGl0bGUgPSAiQ29tcGFyYWNpw7NuIGRlIG1vZGVsb3MgcGFyYSBQcm9kdWN0byAzOTA0MTUyIiwKICAgIHN1YnRpdGxlID0gIk3DqXRyaWNhOiBNQVBFICh2YWxvcmVzIG3DoXMgYmFqb3MgaW5kaWNhbiBtZWpvciBwcmVjaXNpw7NuKSIsCiAgICB4ID0gIiIsCiAgICB5ID0gIk1BUEUgKCUpIgogICkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpCiAgKSArCiAgeWxpbSgwLCBtYXgoZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRNQVBFLCBuYS5ybSA9IFRSVUUpICogMS4xKSAgIyBBanVzdGFyIGVsIGzDrW1pdGUgWQpgYGAKCiMjIFBST0RVQ1RPIDE1NTAwMgpgYGB7cn0KIyBQcmltZXJvLCB2ZWFtb3MgcXXDqSBkYXRvcyB0ZW5lbW9zIHJlYWxtZW50ZQpwcmludCgiRGF0b3MgYWN0dWFsZXMgcGFyYSBlbCBwcm9kdWN0byAxNTUwMDI6IikKcHJpbnQobWV0cmljYXNfY29tcGFyYXRpdmFzICU+JSBmaWx0ZXIoUHJvZHVjdG8gPT0gIjE1NTAwMiIpKQoKIyBDcmVhciB1biBkYXRhZnJhbWUgbWFudWFsbWVudGUgY29uIGxvcyA0IG1vZGVsb3MgcGFyYSBlbCBwcm9kdWN0byAxNTUwMDIKZGF0b3NfMTU1MDAyX2NvbXBsZXRvIDwtIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSByZXAoIjE1NTAwMiIsIDQpLAogIE1vZGVsbyA9IGMoIkFSTUEvU0FSSU1BIiwgIlJlZ3Jlc2nDs24gTGluZWFsIiwgIlJhbmRvbSBGb3Jlc3QiLCAiWEdCb29zdCIpLAogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQopCgojIFVuaXIgY29uIGxvcyBkYXRvcyBleGlzdGVudGVzCmRhdG9zXzE1NTAwMl9jb21wbGV0byA8LSBsZWZ0X2pvaW4oCiAgZGF0b3NfMTU1MDAyX2NvbXBsZXRvLAogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyAlPiUgZmlsdGVyKFByb2R1Y3RvID09ICIxNTUwMDIiKSwKICBieSA9IGMoIlByb2R1Y3RvIiwgIk1vZGVsbyIpCikKCiMgQWhvcmEgYXNpZ25hIHZhbG9yZXMgcGFyYSBsYXMgbcOpdHJpY2FzIGRlIGxvcyBtb2RlbG9zIGZhbHRhbnRlcwojIFZhbG9yZXMgcGFyYSBSZWdyZXNpw7NuIExpbmVhbAppZiAoaXMubmEoZGF0b3NfMTU1MDAyX2NvbXBsZXRvJE1BUEVbMl0pKSB7CiAgZGF0b3NfMTU1MDAyX2NvbXBsZXRvJE1BUEVbMl0gPC0gbWFwZV8xNTUwMDIKfQoKaWYgKGlzLm5hKGRhdG9zXzE1NTAwMl9jb21wbGV0byRSTVNFWzJdKSkgewogIGRhdG9zXzE1NTAwMl9jb21wbGV0byRSTVNFWzJdIDwtIHJtc2VfMTU1MDAyCn0KCgoKIyBWYWxvcmVzIHBhcmEgUmFuZG9tIEZvcmVzdAojIFNpIHlhIGVqZWN1dGFzdGUgbGEgc2VjY2nDs24gZGUgUmFuZG9tIEZvcmVzdCBwYXJhIGVsIHByb2R1Y3RvIDE1NTAwMgppZiAoaXMubmEoZGF0b3NfMTU1MDAyX2NvbXBsZXRvJE1BUEVbM10pICYmIGV4aXN0cygibWFwZV9yZiIpKSB7CiAgZGF0b3NfMTU1MDAyX2NvbXBsZXRvJE1BUEVbM10gPC0gbWFwZV9yZgp9CmlmIChpcy5uYShkYXRvc18xNTUwMDJfY29tcGxldG8kUk1TRVszXSkgJiYgZXhpc3RzKCJybXNlX3JmIikpIHsKICBkYXRvc18xNTUwMDJfY29tcGxldG8kUk1TRVszXSA8LSBybXNlX3JmCn0KCiMgVmFsb3JlcyBwYXJhIFhHQm9vc3QKaWYgKGlzLm5hKGRhdG9zXzE1NTAwMl9jb21wbGV0byRNQVBFWzRdKSAmJiBleGlzdHMoIm1hcGVfY29tcGxldG8iKSkgewogIGRhdG9zXzE1NTAwMl9jb21wbGV0byRNQVBFWzRdIDwtIG1hcGVfY29tcGxldG8KfQppZiAoaXMubmEoZGF0b3NfMTU1MDAyX2NvbXBsZXRvJFJNU0VbNF0pICYmIGV4aXN0cygicm1zZV9jb21wbGV0byIpKSB7CiAgZGF0b3NfMTU1MDAyX2NvbXBsZXRvJFJNU0VbNF0gPC0gcm1zZV9jb21wbGV0bwp9CgojIFZlciBsb3MgZGF0b3MgY29tcGxldG9zCnByaW50KCJEYXRvcyBjb21wbGV0b3MgcGFyYSBlbCBwcm9kdWN0byAxNTUwMDI6IikKcHJpbnQoZGF0b3NfMTU1MDAyX2NvbXBsZXRvKQoKIyBEZWZpbmlyIGNvbG9yZXMgcGFyYSBsb3MgbW9kZWxvcwpjb2xvcmVzX21vZGVsb3MgPC0gYygiQVJNQS9TQVJJTUEiID0gIiMxZjc3YjQiLCAKICAgICAgICAgICAgICAgICAgICAgIlJlZ3Jlc2nDs24gTGluZWFsIiA9ICIjZmY3ZjBlIiwgCiAgICAgICAgICAgICAgICAgICAgICJSYW5kb20gRm9yZXN0IiA9ICIjMmNhMDJjIiwgCiAgICAgICAgICAgICAgICAgICAgICJYR0Jvb3N0IiA9ICIjZDYyNzI4IikKCiMgR3LDoWZpY28gcGFyYSBNU0UKZ2dwbG90KGRhdG9zXzE1NTAwMl9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBSTVNFLCBmaWxsID0gTW9kZWxvKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IDAuNikgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChSTVNFLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgbW9kZWxvcyBwYXJhIFByb2R1Y3RvIDE1NTAwMiIsCiAgICBzdWJ0aXRsZSA9ICJNw6l0cmljYTogUk1TRSAodmFsb3JlcyBtw6FzIGJham9zIGluZGljYW4gbWVqb3IgcHJlY2lzacOzbikiLAogICAgeCA9ICIiLAogICAgeSA9ICJSTVNFIgogICkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpCiAgKSArCiAgeWxpbSgwLCBtYXgoZGF0b3NfMTU1MDAyX2NvbXBsZXRvJFJNU0UsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZCgojIEdyw6FmaWNvIHBhcmEgTUFQRQpnZ3Bsb3QoZGF0b3NfMTU1MDAyX2NvbXBsZXRvLCBhZXMoeCA9IE1vZGVsbywgeSA9IE1BUEUsIGZpbGwgPSBNb2RlbG8pKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKE1BUEUsIDEpKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sb3Jlc19tb2RlbG9zKSArCiAgbGFicygKICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHBhcmEgUHJvZHVjdG8gMTU1MDAyIiwKICAgIHN1YnRpdGxlID0gIk3DqXRyaWNhOiBNQVBFICh2YWxvcmVzIG3DoXMgYmFqb3MgaW5kaWNhbiBtZWpvciBwcmVjaXNpw7NuKSIsCiAgICB4ID0gIiIsCiAgICB5ID0gIk1BUEUgKCUpIgogICkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpCiAgKSArCiAgeWxpbSgwLCBtYXgoZGF0b3NfMTU1MDAyX2NvbXBsZXRvJE1BUEUsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZCmBgYAoKIyMgUFJPRFVDVE8gMzY3ODA1NQpgYGB7cn0KIyBQcmltZXJvLCB2ZWFtb3MgcXXDqSBkYXRvcyB0ZW5lbW9zIHJlYWxtZW50ZQpwcmludCgiRGF0b3MgYWN0dWFsZXMgcGFyYSBlbCBwcm9kdWN0byAzNjc4MDU1OiIpCnByaW50KG1ldHJpY2FzX2NvbXBhcmF0aXZhcyAlPiUgZmlsdGVyKFByb2R1Y3RvID09ICIzNjc4MDU1IikpCgojIENyZWFyIHVuIGRhdGFmcmFtZSBtYW51YWxtZW50ZSBjb24gbG9zIDQgbW9kZWxvcyBwYXJhIGVsIHByb2R1Y3RvIDM2NzgwNTUKZGF0b3NfMzY3ODA1NV9jb21wbGV0byA8LSBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gcmVwKCIzNjc4MDU1IiwgNCksCiAgTW9kZWxvID0gYygiQVJNQS9TQVJJTUEiLCAiUmVncmVzacOzbiBMaW5lYWwiLCAiUmFuZG9tIEZvcmVzdCIsICJYR0Jvb3N0IiksCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCikKCiMgVW5pciBjb24gbG9zIGRhdG9zIGV4aXN0ZW50ZXMKZGF0b3NfMzY3ODA1NV9jb21wbGV0byA8LSBsZWZ0X2pvaW4oCiAgZGF0b3NfMzY3ODA1NV9jb21wbGV0bywKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgJT4lIGZpbHRlcihQcm9kdWN0byA9PSAiMzY3ODA1NSIpLAogIGJ5ID0gYygiUHJvZHVjdG8iLCAiTW9kZWxvIikKKQoKIyBBaG9yYSBhc2lnbmEgdmFsb3JlcyBwYXJhIGxhcyBtw6l0cmljYXMgZGUgbG9zIG1vZGVsb3MgZmFsdGFudGVzCiMgVmFsb3JlcyBwYXJhIFJlZ3Jlc2nDs24gTGluZWFsCmlmIChpcy5uYShkYXRvc18zNjc4MDU1X2NvbXBsZXRvJE1BUEVbMl0pKSB7CiAgZGF0b3NfMzY3ODA1NV9jb21wbGV0byRNQVBFWzJdIDwtIG1hcGVfMzY3ODA1NQp9CmlmIChpcy5uYShkYXRvc18zNjc4MDU1X2NvbXBsZXRvJFJNU0VbMl0pKSB7CiAgZGF0b3NfMzY3ODA1NV9jb21wbGV0byRSTVNFWzJdIDwtIHJtc2VfMzY3ODA1NQp9CgojIFZhbG9yZXMgcGFyYSBSYW5kb20gRm9yZXN0CiMgU2kgeWEgZWplY3V0YXN0ZSBsYSBzZWNjacOzbiBkZSBSYW5kb20gRm9yZXN0IHBhcmEgZWwgcHJvZHVjdG8gMzY3ODA1NQppZiAoaXMubmEoZGF0b3NfMzY3ODA1NV9jb21wbGV0byRNQVBFWzNdKSAmJiBleGlzdHMoIm1hcGVfcmYiKSkgewogIGRhdG9zXzM2NzgwNTVfY29tcGxldG8kTUFQRVszXSA8LSBtYXBlX3JmCn0KaWYgKGlzLm5hKGRhdG9zXzM2NzgwNTVfY29tcGxldG8kUk1TRVszXSkgJiYgZXhpc3RzKCJybXNlX3JmIikpIHsKICBkYXRvc18zNjc4MDU1X2NvbXBsZXRvJFJNU0VbM10gPC0gcm1zZV9yZgp9CgojIFZhbG9yZXMgcGFyYSBYR0Jvb3N0CmlmIChpcy5uYShkYXRvc18zNjc4MDU1X2NvbXBsZXRvJE1BUEVbNF0pICYmIGV4aXN0cygibWFwZV9jb21wbGV0byIpKSB7CiAgZGF0b3NfMzY3ODA1NV9jb21wbGV0byRNQVBFWzRdIDwtIG1hcGVfY29tcGxldG8KfQppZiAoaXMubmEoZGF0b3NfMzY3ODA1NV9jb21wbGV0byRSTVNFWzRdKSAmJiBleGlzdHMoInJtc2VfY29tcGxldG8iKSkgewogIGRhdG9zXzM2NzgwNTVfY29tcGxldG8kUk1TRVs0XSA8LSBybXNlX2NvbXBsZXRvCn0KCiMgVmVyIGxvcyBkYXRvcyBjb21wbGV0b3MKcHJpbnQoIkRhdG9zIGNvbXBsZXRvcyBwYXJhIGVsIHByb2R1Y3RvIDM2NzgwNTU6IikKcHJpbnQoZGF0b3NfMzY3ODA1NV9jb21wbGV0bykKCiMgRGVmaW5pciBjb2xvcmVzIHBhcmEgbG9zIG1vZGVsb3MKY29sb3Jlc19tb2RlbG9zIDwtIGMoIkFSTUEvU0FSSU1BIiA9ICIjMWY3N2I0IiwgCiAgICAgICAgICAgICAgICAgICAgICJSZWdyZXNpw7NuIExpbmVhbCIgPSAiI2ZmN2YwZSIsIAogICAgICAgICAgICAgICAgICAgICAiUmFuZG9tIEZvcmVzdCIgPSAiIzJjYTAyYyIsIAogICAgICAgICAgICAgICAgICAgICAiWEdCb29zdCIgPSAiI2Q2MjcyOCIpCiMgR3LDoWZpY28gcGFyYSBNU0UKZ2dwbG90KGRhdG9zXzM2NzgwNTVfY29tcGxldG8sIGFlcyh4ID0gTW9kZWxvLCB5ID0gUk1TRSwgZmlsbCA9IE1vZGVsbykpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQoUk1TRSwgMSkpLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzLjUpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsKICBsYWJzKAogICAgdGl0bGUgPSAiQ29tcGFyYWNpw7NuIGRlIG1vZGVsb3MgcGFyYSBQcm9kdWN0byAzNjc4MDU1IiwKICAgIHN1YnRpdGxlID0gIk3DqXRyaWNhOiBSTVNFICh2YWxvcmVzIG3DoXMgYmFqb3MgaW5kaWNhbiBtZWpvciBwcmVjaXNpw7NuKSIsCiAgICB4ID0gIiIsCiAgICB5ID0gIlJNU0UiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkKICApICsKICB5bGltKDAsIG1heChkYXRvc18zNjc4MDU1X2NvbXBsZXRvJFJNU0UsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZCgojIEdyw6FmaWNvIHBhcmEgTUFQRQpnZ3Bsb3QoZGF0b3NfMzY3ODA1NV9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBNQVBFLCBmaWxsID0gTW9kZWxvKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IDAuNikgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChNQVBFLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgbW9kZWxvcyBwYXJhIFByb2R1Y3RvIDM2NzgwNTUiLAogICAgc3VidGl0bGUgPSAiTcOpdHJpY2E6IE1BUEUgKHZhbG9yZXMgbcOhcyBiYWpvcyBpbmRpY2FuIG1lam9yIHByZWNpc2nDs24pIiwKICAgIHggPSAiIiwKICAgIHkgPSAiTUFQRSAoJSkiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkKICApICsKICB5bGltKDAsIG1heChkYXRvc18zNjc4MDU1X2NvbXBsZXRvJE1BUEUsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZCmBgYAoKIyBFU1RJTUFDScOTTiBERSBQUkVDSU9TCgojIyMgUHJlcGFyYWNpw7NuIGRlIGRhdG9zCmBgYHtyfQojIEZ1bmNpw7NuIHBhcmEgcHJlcGFyYXIgZGF0b3MgZGUgdW4gcHJvZHVjdG8KcHJlcGFyZV9wcmljZV9kYXRhIDwtIGZ1bmN0aW9uKGRmLCBwcm9kdWN0X2lkKSB7CiAgcHJvZHVjdF9kYXRhIDwtIGRmICU+JQogICAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gcHJvZHVjdF9pZCkgJT4lCiAgICBhcnJhbmdlKFRyeF9GZWNoYSkgJT4lCiAgICBzZWxlY3QoCiAgICAgIFRyeF9GZWNoYSwgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCBDYW50LCBWZW50YSwgCiAgICAgIENvc3RvX1ZlbnRhLCBEZXNjdWVudG9fUG9yY2VudGFqZSwgU2VtYW5hLCBNZXMKICAgICkgJT4lCiAgICBtdXRhdGUoCiAgICAgIERpYV9TZW1hbmEgPSB3ZGF5KFRyeF9GZWNoYSksCiAgICAgIE1lc19OdW0gPSBtb250aChUcnhfRmVjaGEpLAogICAgICBBbmlvID0geWVhcihUcnhfRmVjaGEpLAogICAgICBEaWFzX0Rlc2RlX0luaWNpbyA9IGFzLm51bWVyaWMoZGlmZnRpbWUoVHJ4X0ZlY2hhLCBtaW4oVHJ4X0ZlY2hhKSwgdW5pdHMgPSAiZGF5cyIpKSwKICAgICAgTWFyZ2VuX1VuaXRhcmlvID0gKFZlbnRhIC8gQ2FudCkgLSAoQ29zdG9fVmVudGEgLyBDYW50KSwKICAgICAgUHJlY2lvX1VuaXRhcmlvX0NhbGMgPSBWZW50YSAvIENhbnQsCiAgICAgIElEX0ludmVudGFyaW8gPSBwcm9kdWN0X2lkCiAgICApCiAgCiAgcmV0dXJuKHByb2R1Y3RfZGF0YSkKfQoKIyBBc2Vnw7pyYXRlIGRlIHF1ZSAnZGF0b3MnIHNlYSB0dSBkYXRhLmZyYW1lIGNhcmdhZG8gY29ycmVjdGFtZW50ZQojIFBvciBlamVtcGxvLCBzaSB2aWVuZXMgZGUgdW4gYXJjaGl2byAuY3N2OgojIGRhdG9zIDwtIHJlYWQuY3N2KCJhcmNoaXZvLmNzdiIpCgojIEFwbGljYXIgbGEgZnVuY2nDs24gYSB0b2RvcyBsb3MgcHJvZHVjdG9zCmlkcyA8LSB1bmlxdWUoZGF0b3MkSURfSW52ZW50YXJpbykKCnByb2R1Y3Rvc19wcmVwYXJhZG9zIDwtIG1hcF9kZihpZHMsIGZ1bmN0aW9uKGlkKSB7CiAgcHJlcGFyZV9wcmljZV9kYXRhKGRhdG9zLCBpZCkKfSkKCiMgTW9zdHJhciB1bmEgcGFydGUgZGVsIHJlc3VsdGFkbwpoZWFkKHByb2R1Y3Rvc19wcmVwYXJhZG9zKQpgYGAKCgoKCmBgYHtyfQojIFZlY3RvciBjb24gcHJvZHVjdG9zIChkZWJlIGlyIHByaW1lcm8pCnByb2R1Y3Rvc19pZHMgPC0gdG9wX2lkcwoKIyBGdW5jacOzbiBwYXJhIGVudHJlbmFyIG1vZGVsbyBBUk1BIHBvciBwcm9kdWN0bwp0cmFpbl9hcm1hX21vZGVsIDwtIGZ1bmN0aW9uKGRhdGEsIHByb2R1Y3RfaWQpIHsKICBsaWJyYXJ5KGZvcmVjYXN0KSAgIyBBc2Vnw7pyYXRlIGRlIGNhcmdhciBmb3JlY2FzdCBzaSBubyBlc3TDoSBjYXJnYWRvIGHDum4KICBwcm9kdWN0X2RhdGEgPC0gZGF0YSAlPiUgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gcHJvZHVjdF9pZCkKICBzZXJpZV90cyA8LSB0cyhwcm9kdWN0X2RhdGEkVmVudGEsIGZyZXF1ZW5jeSA9IDEyKQogIG1vZGVsb19hcm1hIDwtIGF1dG8uYXJpbWEoc2VyaWVfdHMsIHNlYXNvbmFsID0gRkFMU0UsIHN0ZXB3aXNlID0gRkFMU0UsIGFwcHJveGltYXRpb24gPSBGQUxTRSkKICByZXR1cm4obW9kZWxvX2FybWEpCn0KCiMgQ3JlYXIgbGlzdGEgZGUgbW9kZWxvcyBBUk1BIHBvciBwcm9kdWN0bwptb2RlbG9zX2FybWFfbGlzdGEgPC0gc2V0TmFtZXMoCiAgbGFwcGx5KHByb2R1Y3Rvc19pZHMsIGZ1bmN0aW9uKGlkKSB0cmFpbl9hcm1hX21vZGVsKGRhdG9zLCBpZCkpLAogIGFzLmNoYXJhY3Rlcihwcm9kdWN0b3NfaWRzKQopCgojIEZ1bmNpw7NuIHBhcmEgbW9kZWxvIHJlZ3Jlc2nDs24gbGluZWFsCnRyYWluX3JlZ19tb2RlbCA8LSBmdW5jdGlvbihkYXRhLCBwcm9kdWN0X2lkKSB7CiAgcHJvZHVjdF9kYXRhIDwtIGRhdGEgJT4lIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IHByb2R1Y3RfaWQpCiAgbW9kZWxvX3JlZyA8LSBsbShWZW50YSB+IFByZWNpb19GaW5hbF9Vbml0YXJpbywgZGF0YSA9IHByb2R1Y3RfZGF0YSkKICByZXR1cm4obW9kZWxvX3JlZykKfQoKIyBGdW5jacOzbiBwYXJhIG1vZGVsbyBSYW5kb20gRm9yZXN0CnRyYWluX3JmX21vZGVsIDwtIGZ1bmN0aW9uKGRhdGEsIHByb2R1Y3RfaWQpIHsKICBwcm9kdWN0X2RhdGEgPC0gZGF0YSAlPiUgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gcHJvZHVjdF9pZCkKICBwcmVkaWN0b3JzIDwtIGMoIlByZWNpb19GaW5hbF9Vbml0YXJpbyIsICJDYW50IiwgIkRlc2N1ZW50b19Qb3JjZW50YWplIikKICByZl9kYXRhIDwtIHByb2R1Y3RfZGF0YSAlPiUgc2VsZWN0KGFsbF9vZihwcmVkaWN0b3JzKSwgVmVudGEpCiAgbW9kZWxvX3JmIDwtIHJhbmRvbUZvcmVzdChWZW50YSB+IC4sIGRhdGEgPSByZl9kYXRhLCBudHJlZSA9IDEwMCkKICByZXR1cm4obW9kZWxvX3JmKQp9CgojIEZ1bmNpw7NuIHBhcmEgbW9kZWxvIFhHQm9vc3QKdHJhaW5feGdiX21vZGVsIDwtIGZ1bmN0aW9uKGRhdGEsIHByb2R1Y3RfaWQpIHsKICBwcm9kdWN0X2RhdGEgPC0gZGF0YSAlPiUgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gcHJvZHVjdF9pZCkKICBwcmVkaWN0b3JzIDwtIGMoIlByZWNpb19GaW5hbF9Vbml0YXJpbyIsICJDYW50IiwgIkRlc2N1ZW50b19Qb3JjZW50YWplIikKICB0cmFpbl9tYXRyaXggPC0geGdiLkRNYXRyaXgoZGF0YSA9IGFzLm1hdHJpeChwcm9kdWN0X2RhdGFbLCBwcmVkaWN0b3JzXSksIGxhYmVsID0gcHJvZHVjdF9kYXRhJFZlbnRhKQogIHBhcmFtcyA8LSBsaXN0KG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIikKICBtb2RlbG9feGdiIDwtIHhnYi50cmFpbihwYXJhbXMgPSBwYXJhbXMsIGRhdGEgPSB0cmFpbl9tYXRyaXgsIG5yb3VuZHMgPSA1MCwgdmVyYm9zZSA9IDApCiAgcmV0dXJuKG1vZGVsb194Z2IpCn0KCiMgQ3JlYXIgbGlzdGFzIGRlIG1vZGVsb3MKbW9kZWxvc19yZWdfbGlzdGEgPC0gc2V0TmFtZXMobGFwcGx5KHByb2R1Y3Rvc19pZHMsIGZ1bmN0aW9uKGlkKSB0cmFpbl9yZWdfbW9kZWwoZGF0b3MsIGlkKSksIGFzLmNoYXJhY3Rlcihwcm9kdWN0b3NfaWRzKSkKbW9kZWxvc19yZl9saXN0YSA8LSBzZXROYW1lcyhsYXBwbHkocHJvZHVjdG9zX2lkcywgZnVuY3Rpb24oaWQpIHRyYWluX3JmX21vZGVsKGRhdG9zLCBpZCkpLCBhcy5jaGFyYWN0ZXIocHJvZHVjdG9zX2lkcykpCm1vZGVsb3NfeGdiX2xpc3RhIDwtIHNldE5hbWVzKGxhcHBseShwcm9kdWN0b3NfaWRzLCBmdW5jdGlvbihpZCkgdHJhaW5feGdiX21vZGVsKGRhdG9zLCBpZCkpLCBhcy5jaGFyYWN0ZXIocHJvZHVjdG9zX2lkcykpCmBgYAoKIyMjIEVudHJlbmFyIG1vZGVsb3MgZGUgcHJlZGljY2nDs24gZGUgcHJlY2lvcwpgYGB7cn0KIyBGdW5jacOzbiBwYXJhIGVudHJlbmFyIG1vZGVsb3MgZGUgcHJlZGljY2nDs24gZGUgcHJlY2lvcwp0cmFpbl9wcmljZV9tb2RlbHMgPC0gZnVuY3Rpb24oZGF0YSwgcHJvZHVjdF9pZCwgdGVzdF9zaXplID0gMC4yKSB7CiAgcHJpY2VfZGF0YSA8LSBwcmVwYXJlX3ByaWNlX2RhdGEoZGF0YSwgcHJvZHVjdF9pZCkgJT4lCiAgICBkcm9wX25hKCkgJT4lCiAgICBzZWxlY3QoCiAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbywKICAgICAgQ2FudCwgQ29zdG9fVmVudGEsIERlc2N1ZW50b19Qb3JjZW50YWplLAogICAgICBEaWFfU2VtYW5hLCBNZXNfTnVtLCBBbmlvLCBEaWFzX0Rlc2RlX0luaWNpbywKICAgICAgTWFyZ2VuX1VuaXRhcmlvCiAgICApCgogICMgRXZpdGFyIGZhbGxvcyBzaSBoYXkgbXV5IHBvY29zIGRhdG9zCiAgaWYgKG5yb3cocHJpY2VfZGF0YSkgPCAxMCkgewogICAgd2FybmluZyhwYXN0ZSgiUHJvZHVjdG8iLCBwcm9kdWN0X2lkLCAidGllbmUgbWVub3MgZGUgMTAgcmVnaXN0cm9zLiBTZSBvbWl0ZS4iKSkKICAgIHJldHVybihOVUxMKQogIH0KCiAgc2V0LnNlZWQoMTIzKQogIHRyYWluX2luZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24ocHJpY2VfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8sIHAgPSAxIC0gdGVzdF9zaXplLCBsaXN0ID0gRkFMU0UpCiAgdHJhaW5fZGF0YSA8LSBwcmljZV9kYXRhW3RyYWluX2luZGV4LCBdCiAgdGVzdF9kYXRhIDwtIHByaWNlX2RhdGFbLXRyYWluX2luZGV4LCBdCgogICMgMS4gUmVncmVzacOzbiBMaW5lYWwKICBsbV9tb2RlbCA8LSBsbShQcmVjaW9fRmluYWxfVW5pdGFyaW8gfiAuLCBkYXRhID0gdHJhaW5fZGF0YSkKCiAgIyAyLiBSYW5kb20gRm9yZXN0CiAgcmZfbW9kZWwgPC0gcmFuZG9tRm9yZXN0KAogICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIH4gLiwKICAgIGRhdGEgPSB0cmFpbl9kYXRhLAogICAgbnRyZWUgPSA1MDAsCiAgICBpbXBvcnRhbmNlID0gVFJVRQogICkKCiAgIyAzLiBYR0Jvb3N0CiAgZmVhdHVyZXMgPC0gc2V0ZGlmZihuYW1lcyh0cmFpbl9kYXRhKSwgIlByZWNpb19GaW5hbF9Vbml0YXJpbyIpCiAgeF90cmFpbiA8LSBhcy5tYXRyaXgodHJhaW5fZGF0YVssIGZlYXR1cmVzXSkKICB5X3RyYWluIDwtIHRyYWluX2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvCiAgeF90ZXN0IDwtIGFzLm1hdHJpeCh0ZXN0X2RhdGFbLCBmZWF0dXJlc10pCiAgeV90ZXN0IDwtIHRlc3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8KICBkdHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IHhfdHJhaW4sIGxhYmVsID0geV90cmFpbikKICBkdGVzdCA8LSB4Z2IuRE1hdHJpeChkYXRhID0geF90ZXN0LCBsYWJlbCA9IHlfdGVzdCkKCiAgeGdiX3BhcmFtcyA8LSBsaXN0KAogICAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLAogICAgZXZhbF9tZXRyaWMgPSAicm1zZSIsCiAgICBldGEgPSAwLjEsCiAgICBtYXhfZGVwdGggPSA2LAogICAgbWluX2NoaWxkX3dlaWdodCA9IDMsCiAgICBzdWJzYW1wbGUgPSAwLjgsCiAgICBjb2xzYW1wbGVfYnl0cmVlID0gMC44CiAgKQoKICB4Z2JfbW9kZWwgPC0geGdiLnRyYWluKAogICAgcGFyYW1zID0geGdiX3BhcmFtcywKICAgIGRhdGEgPSBkdHJhaW4sCiAgICBucm91bmRzID0gMTAwLAogICAgd2F0Y2hsaXN0ID0gbGlzdCh0cmFpbiA9IGR0cmFpbiwgdGVzdCA9IGR0ZXN0KSwKICAgIGVhcmx5X3N0b3BwaW5nX3JvdW5kcyA9IDEwLAogICAgdmVyYm9zZSA9IDAKICApCgogICMgRXZhbHVhY2nDs24KICBsbV9wcmVkIDwtIHByZWRpY3QobG1fbW9kZWwsIG5ld2RhdGEgPSB0ZXN0X2RhdGEpCiAgcmZfcHJlZCA8LSBwcmVkaWN0KHJmX21vZGVsLCBuZXdkYXRhID0gdGVzdF9kYXRhKQogIHhnYl9wcmVkIDwtIHByZWRpY3QoeGdiX21vZGVsLCB4X3Rlc3QpCgogIGxtX3Jtc2UgPC0gc3FydChtZWFuKChsbV9wcmVkIC0gdGVzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbyleMikpCiAgcmZfcm1zZSA8LSBzcXJ0KG1lYW4oKHJmX3ByZWQgLSB0ZXN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKV4yKSkKICB4Z2Jfcm1zZSA8LSBzcXJ0KG1lYW4oKHhnYl9wcmVkIC0gdGVzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbyleMikpCgogIGxtX3IyIDwtIDEgLSBzdW0oKHRlc3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8gLSBsbV9wcmVkKV4yKSAvCiAgICBzdW0oKHRlc3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8gLSBtZWFuKHRlc3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8pKV4yKQogIHJmX3IyIDwtIDEgLSBzdW0oKHRlc3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8gLSByZl9wcmVkKV4yKSAvCiAgICBzdW0oKHRlc3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8gLSBtZWFuKHRlc3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8pKV4yKQogIHhnYl9yMiA8LSAxIC0gc3VtKCh0ZXN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIC0geGdiX3ByZWQpXjIpIC8KICAgIHN1bSgodGVzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbyAtIG1lYW4odGVzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbykpXjIpCgogIG1ldHJpY3MgPC0gZGF0YS5mcmFtZSgKICAgIE1vZGVsID0gYygiTGluZWFyIFJlZ3Jlc3Npb24iLCAiUmFuZG9tIEZvcmVzdCIsICJYR0Jvb3N0IiksCiAgICBSTVNFID0gYyhsbV9ybXNlLCByZl9ybXNlLCB4Z2Jfcm1zZSksCiAgICBSMiA9IGMobG1fcjIsIHJmX3IyLCB4Z2JfcjIpCiAgKQoKICByZXR1cm4obGlzdChtZXRyaWNzID0gbWV0cmljcykpCn0KCiMgSURzIGRlIGxvcyA1IHByb2R1Y3RvcyBhIG1vZGVsYXIKcHJvZHVjdG9zX2lkcyA8LSBjKDE1NTAwMSwgMzkyOTc4OCwgMzkwNDE1MiwgMTU1MDAyLCAzNjc4MDU1KQoKIyBBcGxpY2FyIG1vZGVsbyBhIGNhZGEgcHJvZHVjdG8KcmVzdWx0YWRvc19tb2RlbG9zIDwtIG1hcChwcm9kdWN0b3NfaWRzLCBmdW5jdGlvbihpZCkgewogIHJlc3VsdGFkbyA8LSB0cmFpbl9wcmljZV9tb2RlbHMoZGF0b3MsIHByb2R1Y3RfaWQgPSBpZCkKICBpZiAoIWlzLm51bGwocmVzdWx0YWRvKSkgewogICAgcmVzdWx0YWRvJG1ldHJpY3MgJT4lIG11dGF0ZShJRF9JbnZlbnRhcmlvID0gaWQpCiAgfSBlbHNlIHsKICAgIE5VTEwKICB9Cn0pICU+JSBjb21wYWN0KCkgJT4lIGJpbmRfcm93cygpCgojIE1vc3RyYXIgcmVzdWx0YWRvcwpyZXN1bHRhZG9zX21vZGVsb3MKYGBgCgpgYGB7cn0KIyBMaXN0YSBjb24gbG9zIElEcyBkZSBwcm9kdWN0b3MgKHB1ZWRlcyB1c2FyIHRvcF9pZHMgcXVlIHlhIGRlZmluaXN0ZSkKcHJvZHVjdG9zX2lkcyA8LSB0b3BfaWRzCgojIEVudHJlbmFyIG1vZGVsb3MgcGFyYSBjYWRhIHByb2R1Y3RvIHkgZ3VhcmRhciBlbiBsaXN0YQptb2RlbG9zX3ByZWNpb19saXN0YSA8LSBzZXROYW1lcygKICBsYXBwbHkocHJvZHVjdG9zX2lkcywgZnVuY3Rpb24oaWQpIHRyYWluX3ByaWNlX21vZGVscyhkYXRvcywgaWQpKSwKICBhcy5jaGFyYWN0ZXIocHJvZHVjdG9zX2lkcykKKQpgYGAKCiMjIyBFc3RpbWFyIHByZWNpb3Mgw7NwdGltb3MKYGBge3J9CmVzdGltYXRlX29wdGltYWxfcHJpY2VzIDwtIGZ1bmN0aW9uKGRhdGEsIHByb2R1Y3RfaWQsIHByaWNlX21vZGVscywgZGVtYW5kX21vZGVscyA9IE5VTEwsIGZ1dHVyZV9kYXRlcyA9IE5VTEwpIHsKICBwcmljZV9zdGVwcyA8LSAyMAoKICAjIFNlbGVjY2nDs24gZGVsIG1lam9yIG1vZGVsbyBkZSBwcmVjaW8KICBiZXN0X3ByaWNlX21vZGVsX2lkeCA8LSB3aGljaC5tYXgocHJpY2VfbW9kZWxzJG1ldHJpY3MkUjIpCiAgYmVzdF9wcmljZV9tb2RlbF9uYW1lIDwtIHByaWNlX21vZGVscyRtZXRyaWNzJE1vZGVsW2Jlc3RfcHJpY2VfbW9kZWxfaWR4XQoKICAjIERhdG9zIGRlbCBwcm9kdWN0bwogIHByb2R1Y3RfZGF0YSA8LSBkYXRhICU+JSBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSBwcm9kdWN0X2lkKQoKICAjIFJhbmdvIGRlIHByZWNpb3MgcmVzdHJpbmdpZG8gYSBwZXJjZW50aWxlcyA1JSAtIDk1JSB5IGxpbWl0YWRvIGEgMS41eCBsYSBtZWRpYW5hCiAgbWluX3ByaWNlIDwtIHF1YW50aWxlKHByb2R1Y3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8sIDAuMDUsIG5hLnJtID0gVFJVRSkKICBtYXhfcHJpY2UgPC0gcXVhbnRpbGUocHJvZHVjdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbywgMC45NSwgbmEucm0gPSBUUlVFKQogIHByaWNlX3JhbmdlIDwtIHNlcShtaW5fcHJpY2UsIG1heF9wcmljZSwgbGVuZ3RoLm91dCA9IHByaWNlX3N0ZXBzKQogIHByaWNlX3JhbmdlIDwtIHBtaW4ocHJpY2VfcmFuZ2UsIDEuNSAqIG1lZGlhbihwcm9kdWN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCBuYS5ybSA9IFRSVUUpKQoKICAjIEluaWNpYWxpemFyIGVzY2VuYXJpb3MgZnV0dXJvcwogIGZ1dHVyZV9zY2VuYXJpb3MgPC0gZGF0YS5mcmFtZSgpCgogIGZvciAoZnV0dXJlX2RhdGUgaW4gZnV0dXJlX2RhdGVzKSB7CiAgICBmdXR1cmVfZGF0ZSA8LSBhcy5EYXRlKGZ1dHVyZV9kYXRlKQogICAgbWVzX2FjdHVhbCA8LSBsdWJyaWRhdGU6Om1vbnRoKGZ1dHVyZV9kYXRlKQoKICAgIG1lc19kYXRhIDwtIHByb2R1Y3RfZGF0YSAlPiUgZmlsdGVyKGx1YnJpZGF0ZTo6bW9udGgoVHJ4X0ZlY2hhKSA9PSBtZXNfYWN0dWFsKQogICAgaWYgKG5yb3cobWVzX2RhdGEpIDwgNSkgbWVzX2RhdGEgPC0gcHJvZHVjdF9kYXRhCgogICAgY29zdG9fbWVzIDwtIG1lZGlhbihtZXNfZGF0YSRDb3N0b19WZW50YSwgbmEucm0gPSBUUlVFKQogICAgY2FudF9tZXMgPC0gbWVkaWFuKG1lc19kYXRhJENhbnQsIG5hLnJtID0gVFJVRSkKICAgIGRlc2NfbWVzIDwtIG1lZGlhbihtZXNfZGF0YSREZXNjdWVudG9fUG9yY2VudGFqZSwgbmEucm0gPSBUUlVFKQoKICAgIGlmIChpcy5uYShjb3N0b19tZXMpKSBjb3N0b19tZXMgPC0gbWVkaWFuKHByb2R1Y3RfZGF0YSRDb3N0b19WZW50YSwgbmEucm0gPSBUUlVFKQogICAgaWYgKGlzLm5hKGNhbnRfbWVzKSB8fCBjYW50X21lcyA9PSAwKSBjYW50X21lcyA8LSBtZWRpYW4ocHJvZHVjdF9kYXRhJENhbnQsIG5hLnJtID0gVFJVRSkKICAgIGlmIChpcy5uYShkZXNjX21lcykpIGRlc2NfbWVzIDwtIG1lZGlhbihwcm9kdWN0X2RhdGEkRGVzY3VlbnRvX1BvcmNlbnRhamUsIG5hLnJtID0gVFJVRSkKCiAgICBkYXRlX2RmIDwtIGRhdGEuZnJhbWUoCiAgICAgIFRyeF9GZWNoYSA9IHJlcChmdXR1cmVfZGF0ZSwgcHJpY2Vfc3RlcHMpLAogICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gPSBwcmljZV9yYW5nZSwKICAgICAgQ2FudCA9IGNhbnRfbWVzLAogICAgICBDb3N0b19WZW50YSA9IGNvc3RvX21lcywKICAgICAgRGVzY3VlbnRvX1BvcmNlbnRhamUgPSBkZXNjX21lcywKICAgICAgRGlhX1NlbWFuYSA9IGx1YnJpZGF0ZTo6d2RheShmdXR1cmVfZGF0ZSksCiAgICAgIE1lc19OdW0gPSBtZXNfYWN0dWFsLAogICAgICBBbmlvID0gbHVicmlkYXRlOjp5ZWFyKGZ1dHVyZV9kYXRlKSwKICAgICAgRGlhc19EZXNkZV9JbmljaW8gPSBhcy5udW1lcmljKGRpZmZ0aW1lKGZ1dHVyZV9kYXRlLCBtaW4ocHJvZHVjdF9kYXRhJFRyeF9GZWNoYSksIHVuaXRzID0gImRheXMiKSksCiAgICAgIE1hcmdlbl9Vbml0YXJpbyA9IE5BCiAgICApCgogICAgZnV0dXJlX3NjZW5hcmlvcyA8LSByYmluZChmdXR1cmVfc2NlbmFyaW9zLCBkYXRlX2RmKQogIH0KCiAgIyBDYWxjdWxhciBtYXJnZW4gdW5pdGFyaW8gc2ltdWxhZG8KICBmdXR1cmVfc2NlbmFyaW9zJE1hcmdlbl9Vbml0YXJpbyA8LSBmdXR1cmVfc2NlbmFyaW9zJFByZWNpb19GaW5hbF9Vbml0YXJpbyAtCiAgICAoZnV0dXJlX3NjZW5hcmlvcyRDb3N0b19WZW50YSAvIGZ1dHVyZV9zY2VuYXJpb3MkQ2FudCkKCiAgIyBFc3RpbWFyIGVsYXN0aWNpZGFkIGhpc3TDs3JpY2EgKGNvbiBtw61uaW1vIDE1IHB1bnRvcyB2w6FsaWRvcykKICBwcm9kdWN0X2RhdGEgPC0gcHJvZHVjdF9kYXRhICU+JSBhcnJhbmdlKFRyeF9GZWNoYSkKICBlbGFzdGljaXR5X2RmIDwtIHByb2R1Y3RfZGF0YSAlPiUKICAgIGZpbHRlcighaXMubmEoQ2FudCkgJiAhaXMubmEoUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKSkgJT4lCiAgICBtdXRhdGUoCiAgICAgIFBfbGFnID0gbGFnKFByZWNpb19GaW5hbF9Vbml0YXJpbyksCiAgICAgIFFfbGFnID0gbGFnKENhbnQpLAogICAgICBkUCA9IFByZWNpb19GaW5hbF9Vbml0YXJpbyAtIFBfbGFnLAogICAgICBkUSA9IENhbnQgLSBRX2xhZywKICAgICAgZWxhc3RpY2l0eV9wb2ludCA9IChkUSAvIFFfbGFnKSAvIChkUCAvIFBfbGFnKQogICAgKSAlPiUKICAgIGZpbHRlcighaXMubmEoZWxhc3RpY2l0eV9wb2ludCksIGlzLmZpbml0ZShlbGFzdGljaXR5X3BvaW50KSkKCiAgZWxhc3RpY2l0eSA8LSBtZWRpYW4oZWxhc3RpY2l0eV9kZiRlbGFzdGljaXR5X3BvaW50LCBuYS5ybSA9IFRSVUUpCiAgaWYgKG5yb3coZWxhc3RpY2l0eV9kZikgPCAxNSB8fCBpcy5uYShlbGFzdGljaXR5KSB8fCAhaXMuZmluaXRlKGVsYXN0aWNpdHkpKSB7CiAgICBlbGFzdGljaXR5IDwtIDEKICB9CgogICMgRXN0aW1hciB2ZW50YXMgeSBtw6FyZ2VuZXMKICByZXN1bHRzIDwtIGZ1dHVyZV9zY2VuYXJpb3MgJT4lCiAgICBtdXRhdGUoVmVudGFfRXNwZXJhZGEgPSAwLCBNYXJnZW5fVG90YWwgPSAwKQoKICAjIFByZWNpbyBiYXNlIHNpbiBkZXNjdWVudG9zIChtw6FzIHJvYnVzdG8pCiAgYmFzZWxpbmVfcHJpY2UgPC0gbWVkaWFuKHByb2R1Y3RfZGF0YSAlPiUgZmlsdGVyKERlc2N1ZW50b19Qb3JjZW50YWplID09IDApICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHB1bGwoUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKSwgbmEucm0gPSBUUlVFKQogIGlmIChpcy5uYShiYXNlbGluZV9wcmljZSkpIHsKICAgIGJhc2VsaW5lX3ByaWNlIDwtIG1lZGlhbihwcm9kdWN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCBuYS5ybSA9IFRSVUUpCiAgfQoKICBmb3IgKGkgaW4gMTpucm93KHJlc3VsdHMpKSB7CiAgICBwcmljZV9yYXRpbyA8LSBiYXNlbGluZV9wcmljZSAvIHJlc3VsdHMkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvW2ldCiAgICBhZGp1c3RlZF9xdWFudGl0eSA8LSByZXN1bHRzJENhbnRbaV0gKiAocHJpY2VfcmF0aW8gXiBlbGFzdGljaXR5KQogICAgcmVzdWx0cyRWZW50YV9Fc3BlcmFkYVtpXSA8LSByZXN1bHRzJFByZWNpb19GaW5hbF9Vbml0YXJpb1tpXSAqIGFkanVzdGVkX3F1YW50aXR5CiAgICByZXN1bHRzJE1hcmdlbl9Ub3RhbFtpXSA8LSBhZGp1c3RlZF9xdWFudGl0eSAqIHJlc3VsdHMkTWFyZ2VuX1VuaXRhcmlvW2ldCiAgfQoKICAjIFNlbGVjY2lvbmFyIHByZWNpb3Mgw7NwdGltb3MgKHBvciBmZWNoYSkKICBvcHRpbWFsX3ByaWNlcyA8LSByZXN1bHRzICU+JQogICAgZ3JvdXBfYnkoVHJ4X0ZlY2hhKSAlPiUKICAgIHNsaWNlX21heChWZW50YV9Fc3BlcmFkYSwgbiA9IDEpICU+JQogICAgc2VsZWN0KFRyeF9GZWNoYSwgUHJlY2lvX09wdGltYWwgPSBQcmVjaW9fRmluYWxfVW5pdGFyaW8sIFZlbnRhX0VzcGVyYWRhLCBNYXJnZW5fVG90YWwpCgogICMgVmFsaWRhY2nDs24gZGUgcHJlY2lvcyBleHRyZW1vcwogIHByZWNpb19tZWRpYW4gPC0gbWVkaWFuKHByb2R1Y3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8sIG5hLnJtID0gVFJVRSkKICBpZiAoYW55KG9wdGltYWxfcHJpY2VzJFByZWNpb19PcHRpbWFsID4gMiAqIHByZWNpb19tZWRpYW4pKSB7CiAgICB3YXJuaW5nKHBhc3RlKCIgUHJlY2lvIMOzcHRpbW8gbXV5IGFsdG8gZGV0ZWN0YWRvIHBhcmEgcHJvZHVjdG8iLCBwcm9kdWN0X2lkKSkKICB9CgogIHJldHVybihsaXN0KAogICAgcmVzdWx0YWRvcyA9IHJlc3VsdHMsCiAgICBwcmVjaW9zX29wdGltb3MgPSBvcHRpbWFsX3ByaWNlcywKICAgIGVsYXN0aWNpZGFkID0gZWxhc3RpY2l0eQogICkpCn0KCmBgYAoKIyMjIFZpc3VhbGl6YXIgcmVzdWx0YWRvcwpgYGB7cn0KIyBGZWNoYXMgZnV0dXJhcyBwYXJhIHNpbXVsYWNpw7NuCmRhdGVzX2Z1dHVyZSA8LSBzZXEoYXMuRGF0ZSgiMjAyNS0wMS0wMSIpLCBieSA9ICJtb250aCIsIGxlbmd0aC5vdXQgPSA2KQoKIyBMaXN0YSBwYXJhIGd1YXJkYXIgcmVzdWx0YWRvcyBwb3IgcHJvZHVjdG8KcHJlY2lvc19vcHRpbW9zX2xpc3RhIDwtIGxpc3QoKQoKIyBJdGVyYXIgcG9yIHByb2R1Y3Rvcwpmb3IgKHBpZCBpbiBwcm9kdWN0b3NfaWRzKSB7CiAgY2F0KCJQUk9EVUNUTzoiLCBwaWQsICJcbiIpCiAgCiAgbW9kZWxvX3ByZWNpbyA8LSBtb2RlbG9zX3ByZWNpb19saXN0YVtbYXMuY2hhcmFjdGVyKHBpZCldXQogIAogIGlmICghaXMubnVsbChtb2RlbG9fcHJlY2lvKSkgewogICAgcmVzdWx0YWRvIDwtIGVzdGltYXRlX29wdGltYWxfcHJpY2VzKAogICAgICBkYXRhID0gZGF0b3MsCiAgICAgIHByb2R1Y3RfaWQgPSBwaWQsCiAgICAgIHByaWNlX21vZGVscyA9IG1vZGVsb19wcmVjaW8sCiAgICAgIGZ1dHVyZV9kYXRlcyA9IGRhdGVzX2Z1dHVyZQogICAgKQogICAgCiAgICBwcmVjaW9zX29wdGltb3NfbGlzdGFbW2FzLmNoYXJhY3RlcihwaWQpXV0gPC0gcmVzdWx0YWRvCiAgICAKICAgIGNhdCgiRWxhc3RpY2lkYWQgZXN0aW1hZGE6Iiwgcm91bmQocmVzdWx0YWRvJGVsYXN0aWNpZGFkLCAyKSwgIlxuXG4iKQogICAgCiAgICAjIE1vc3RyYXIgdGFibGEgbWFudWFsbWVudGUgZmlsYSBwb3IgZmlsYQogICAgcHJpbnQocmVzdWx0YWRvJHByZWNpb3Nfb3B0aW1vcykKICAgIAogIH0gZWxzZSB7CiAgICBjYXQoIk5vIGhheSBtb2RlbG8gZGUgcHJlY2lvcyBwYXJhIGVsIHByb2R1Y3RvIiwgcGlkLCAiXG4iKQogIH0KfQpgYGAKCgoKIyMjIEludGVncmFjacOzbiBkZSBwcmVjaW9zIMOzcHRpbW9zIHkgbW9kZWxvcwpgYGB7cn0KaW50ZWdyYXRlX3dpdGhfZXhpc3RpbmdfbW9kZWxzIDwtIGZ1bmN0aW9uKGRhdGEsIHByb2R1Y3RfaWQsIHByaWNlX29wdF9yZXN1bHRzLCB4Z2JfbW9kZWwpIHsKICBvcHRpbWFsX3ByaWNlcyA8LSBwcmljZV9vcHRfcmVzdWx0c1tbYXMuY2hhcmFjdGVyKHByb2R1Y3RfaWQpXV0kcHJlY2lvc19vcHRpbW9zCiAgCiAgaWYgKGlzLm51bGwob3B0aW1hbF9wcmljZXMpIHx8IG5yb3cob3B0aW1hbF9wcmljZXMpID09IDApIHsKICAgIHdhcm5pbmcocGFzdGUoIk5vIHNlIGVuY29udHJhcm9uIHByZWNpb3Mgw7NwdGltb3MgcGFyYSBlbCBwcm9kdWN0byIsIHByb2R1Y3RfaWQpKQogICAgcmV0dXJuKGRhdGEuZnJhbWUoKSkKICB9CiAgCiAgaGlzdF9kYXRhIDwtIGRhdGEgJT4lCiAgICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSBwcm9kdWN0X2lkKSAlPiUKICAgIGFycmFuZ2UoVHJ4X0ZlY2hhKQogIAogIGZ1dHVyZV9mZWF0dXJlcyA8LSBkYXRhLmZyYW1lKCkKICAKICBmb3IgKGkgaW4gMTpucm93KG9wdGltYWxfcHJpY2VzKSkgewogICAgZnV0dXJlX2RhdGUgPC0gb3B0aW1hbF9wcmljZXMkVHJ4X0ZlY2hhW2ldCiAgICBmdXR1cmVfcHJpY2UgPC0gb3B0aW1hbF9wcmljZXMkUHJlY2lvX09wdGltYWxbaV0KICAgIAogICAgbWVzX2RhdGEgPC0gaGlzdF9kYXRhICU+JQogICAgICBmaWx0ZXIobHVicmlkYXRlOjptb250aChUcnhfRmVjaGEpID09IGx1YnJpZGF0ZTo6bW9udGgoZnV0dXJlX2RhdGUpKQogICAgCiAgICBpZiAobnJvdyhtZXNfZGF0YSkgPCA1KSBtZXNfZGF0YSA8LSBoaXN0X2RhdGEKICAgIAogICAgYXZnX2ZlYXR1cmVzIDwtIG1lc19kYXRhICU+JQogICAgICBzdW1tYXJpc2UoCiAgICAgICAgQ2FudCA9IG1lZGlhbihDYW50LCBuYS5ybSA9IFRSVUUpLAogICAgICAgIENvc3RvX1ZlbnRhID0gbWVkaWFuKENvc3RvX1ZlbnRhLCBuYS5ybSA9IFRSVUUpLAogICAgICAgIENvc3RvX0Rldm9sdWNpb24gPSBtZWRpYW4oQ29zdG9fRGV2b2x1Y2lvbiwgbmEucm0gPSBUUlVFKSwKICAgICAgICBQcmVjaW9fTGlzdGFfVW5pdGFyaW8gPSBtZWRpYW4oUHJlY2lvX0xpc3RhX1VuaXRhcmlvLCBuYS5ybSA9IFRSVUUpLAogICAgICAgIERlc2N1ZW50b19Qb3JjZW50YWplID0gbWVkaWFuKERlc2N1ZW50b19Qb3JjZW50YWplLCBuYS5ybSA9IFRSVUUpLAogICAgICAgIFRpZW1wbyA9IGFzLm51bWVyaWMoZGlmZnRpbWUoZnV0dXJlX2RhdGUsIG1pbihoaXN0X2RhdGEkVHJ4X0ZlY2hhKSwgdW5pdHMgPSAiZGF5cyIpKSAvIDMwCiAgICAgICkKICAgIAogICAgIyBWYXJpYWJsZXMgZGUgdGVuZGVuY2lhOgogICAgYXZnX2ZlYXR1cmVzJFByZWNpb19GaW5hbF9Vbml0YXJpbyA8LSBmdXR1cmVfcHJpY2UKICAgIGF2Z19mZWF0dXJlcyRUcnhfRmVjaGEgPC0gZnV0dXJlX2RhdGUKICAgIGF2Z19mZWF0dXJlcyRNZXNfTnVtIDwtIGx1YnJpZGF0ZTo6bW9udGgoZnV0dXJlX2RhdGUpCiAgICBhdmdfZmVhdHVyZXMkQW5pbyA8LSBsdWJyaWRhdGU6OnllYXIoZnV0dXJlX2RhdGUpCiAgICBhdmdfZmVhdHVyZXMkTWVzX0Rlc2RlX0luaWNpbyA8LSBhcy5udW1lcmljKGRpZmZ0aW1lKGZ1dHVyZV9kYXRlLCBtaW4oaGlzdF9kYXRhJFRyeF9GZWNoYSksIHVuaXRzID0gImRheXMiKSkgJS8lIDMwCiAgICAKICAgIGZ1dHVyZV9mZWF0dXJlcyA8LSByYmluZChmdXR1cmVfZmVhdHVyZXMsIGF2Z19mZWF0dXJlcykKICB9CiAgCiAgZnV0dXJlX2RhdGEgPC0gZGF0YS5mcmFtZSgKICAgIEZlY2hhID0gZnV0dXJlX2ZlYXR1cmVzJFRyeF9GZWNoYSwKICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbyA9IGZ1dHVyZV9mZWF0dXJlcyRQcmVjaW9fRmluYWxfVW5pdGFyaW8KICApCiAgCiAgIyA9PT0gUFJFRElDQ0nDk04gQ09OIFhHQm9vc3QgPT09CiAgdHJ5Q2F0Y2goewogICAgZmVhdHVyZXMgPC0geGdiX21vZGVsJGZlYXR1cmVfbmFtZXMKICAgIGlmIChpcy5udWxsKGZlYXR1cmVzKSkgewogICAgICBmZWF0dXJlcyA8LSBzZXRkaWZmKG5hbWVzKGZ1dHVyZV9mZWF0dXJlcyksICJWZW50YSIpCiAgICB9CiAgICB4Z2JfbWF0cml4IDwtIGFzLm1hdHJpeChmdXR1cmVfZmVhdHVyZXNbLCBmZWF0dXJlcywgZHJvcCA9IEZBTFNFXSkKICAgIGZ1dHVyZV9kYXRhJFZlbnRhX1hHQm9vc3QgPC0gcHJlZGljdCh4Z2JfbW9kZWwsIHhnYl9tYXRyaXgpCiAgfSwgZXJyb3IgPSBmdW5jdGlvbihlKSB7CiAgICB3YXJuaW5nKHBhc3RlKCJFcnJvciBhbCBwcmVkZWNpciBjb24gWEdCb29zdCBwYXJhIHByb2R1Y3RvIiwgcHJvZHVjdF9pZCwgIjoiLCBlJG1lc3NhZ2UpKQogICAgZnV0dXJlX2RhdGEkVmVudGFfWEdCb29zdCA8LSBOQQogIH0pCiAgCiAgIyA9PT0gTcOJVFJJQ0FTID09PQogIGF2Z19jb3N0X3Blcl91bml0IDwtIG1lZGlhbihoaXN0X2RhdGEkQ29zdG9fVmVudGEgLyBoaXN0X2RhdGEkQ2FudCwgbmEucm0gPSBUUlVFKQogIAogIGZ1dHVyZV9kYXRhJFVuaWRhZGVzX1hHQm9vc3QgPC0gZnV0dXJlX2RhdGEkVmVudGFfWEdCb29zdCAvIGZ1dHVyZV9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbwogIGZ1dHVyZV9kYXRhJENvc3RvX1hHQm9vc3QgPC0gZnV0dXJlX2RhdGEkVW5pZGFkZXNfWEdCb29zdCAqIGF2Z19jb3N0X3Blcl91bml0CiAgZnV0dXJlX2RhdGEkTWFyZ2VuX1hHQm9vc3QgPC0gZnV0dXJlX2RhdGEkVmVudGFfWEdCb29zdCAtIGZ1dHVyZV9kYXRhJENvc3RvX1hHQm9vc3QKICAKICByZXR1cm4oZnV0dXJlX2RhdGEpCn0KCmBgYAoKIyMjIFBpcGVsaW5lIGNvcnJlY3RvCmBgYHtyfQpjb3JyZWdpcl9mb3JtYXRvX2ZlY2hhcyA8LSBmdW5jdGlvbihkYXRvcykgewogIGlmICgiVHJ4X0ZlY2hhIiAlaW4lIGNvbG5hbWVzKGRhdG9zKSkgewogICAgZGF0b3MkVHJ4X0ZlY2hhX09yaWdpbmFsIDwtIGRhdG9zJFRyeF9GZWNoYQoKICAgIGlmIChpcy5jaGFyYWN0ZXIoZGF0b3MkVHJ4X0ZlY2hhKSAmJgogICAgICAgIGFueShncmVwbCgiXlxcZHs3fS1cXGR7Mn0tXFxkezJ9JCIsIGRhdG9zJFRyeF9GZWNoYSkpKSB7CgogICAgICBjYXQoIkNvcnJpZ2llbmRvIGZvcm1hdG8gZGUgZmVjaGFzIGV4dHJhw7FvLi4uXG4iKQoKICAgICAgZGF0b3MkVHJ4X0ZlY2hhIDwtIHNhcHBseShkYXRvcyRUcnhfRmVjaGEsIGZ1bmN0aW9uKGZlY2hhKSB7CiAgICAgICAgaWYgKGlzLm5hKGZlY2hhKSB8fCAhaXMuY2hhcmFjdGVyKGZlY2hhKSkgcmV0dXJuKE5BKQoKICAgICAgICBwYXJ0ZXMgPC0gc3Ryc3BsaXQoZmVjaGEsICItIilbWzFdXQogICAgICAgIGlmIChsZW5ndGgocGFydGVzKSA9PSAzKSB7CiAgICAgICAgICBmZWNoYV9jb3JyZWdpZGEgPC0gcGFzdGUoIjIwMjMiLCBwYXJ0ZXNbMl0sIHBhcnRlc1szXSwgc2VwID0gIi0iKQogICAgICAgICAgcmV0dXJuKGZlY2hhX2NvcnJlZ2lkYSkKICAgICAgICB9IGVsc2UgewogICAgICAgICAgcmV0dXJuKE5BKQogICAgICAgIH0KICAgICAgfSkKCiAgICAgIGRhdG9zJFRyeF9GZWNoYSA8LSBhcy5EYXRlKGRhdG9zJFRyeF9GZWNoYSkKICAgICAgY2F0KCJGZWNoYXMgY29ycmVnaWRhcyBleGl0b3NhbWVudGUuXG4iKQogICAgfSBlbHNlIGlmICghaW5oZXJpdHMoZGF0b3MkVHJ4X0ZlY2hhLCAiRGF0ZSIpKSB7CiAgICAgIGNhdCgiSW50ZW50YW5kbyBjb252ZXJ0aXIgZmVjaGFzIGEgZm9ybWF0byBEYXRlLi4uXG4iKQogICAgICBkYXRvcyRUcnhfRmVjaGEgPC0gYXMuRGF0ZShkYXRvcyRUcnhfRmVjaGEpCiAgICB9CiAgfQogIHJldHVybihkYXRvcykKfQoKIyBBcGxpY2FyIGxhIGNvcnJlY2Npw7NuIGEgdHUgZGF0YWZyYW1lIGFudGVzIGRlIHVzYXJsbwpkYXRvc19maWx0cmFkb3MgPC0gY29ycmVnaXJfZm9ybWF0b19mZWNoYXMoZGF0b3NfZmlsdHJhZG9zKQpgYGAKCmBgYHtyfQpkYXRlc19mdXR1cmUgPC0gc2VxLkRhdGUoYXMuRGF0ZSgiMjAyMy0wMS0wMSIpLCBieSA9ICJtb250aCIsIGxlbmd0aC5vdXQgPSA2KQpwcmVjaW9zX29wdGltb3NfbGlzdGEgPC0gbGlzdCgpCgpmb3IgKGlkIGluIHByb2R1Y3Rvc19pZHMpIHsKICBjYXQoIkVzdGltYW5kbyBwcmVjaW9zIMOzcHRpbW9zIHBhcmEgcHJvZHVjdG86IiwgaWQsICJcbiIpCgogIG1vZGVsb19wcmVjaW8gPC0gbW9kZWxvc19wcmVjaW9fbGlzdGFbW2FzLmNoYXJhY3RlcihpZCldXQoKICBpZiAoIWlzLm51bGwobW9kZWxvX3ByZWNpbykpIHsKICAgIHByZWNpb3Nfb3B0aW1vc19saXN0YVtbYXMuY2hhcmFjdGVyKGlkKV1dIDwtIGVzdGltYXRlX29wdGltYWxfcHJpY2VzKAogICAgICBkYXRhID0gZGF0b3NfZmlsdHJhZG9zLAogICAgICBwcm9kdWN0X2lkID0gaWQsCiAgICAgIHByaWNlX21vZGVscyA9IG1vZGVsb19wcmVjaW8sCiAgICAgIGZ1dHVyZV9kYXRlcyA9IGRhdGVzX2Z1dHVyZQogICAgKQogIH0KfQpgYGAKCmBgYHtyfQpmb3IgKGlkIGluIG5hbWVzKHByZWNpb3Nfb3B0aW1vc19saXN0YSkpIHsKICBkZl9vcHRpbW8gPC0gcHJlY2lvc19vcHRpbW9zX2xpc3RhW1tpZF1dJHByZWNpb3Nfb3B0aW1vcwoKICBpZiAoIWluaGVyaXRzKGRmX29wdGltbyRUcnhfRmVjaGEsICJEYXRlIikpIHsKICAgIGRmX29wdGltbyRUcnhfRmVjaGEgPC0gYXMuRGF0ZShkZl9vcHRpbW8kVHJ4X0ZlY2hhKQogIH0KCiAgY2F0KHBhc3RlMCgiXG4jIyMgUHJvZHVjdG86ICIsIGlkLCAiXG4iKSkKCiAgcHJpbnQoCiAgICBnZ3Bsb3QoZGZfb3B0aW1vLCBhZXMoeCA9IFRyeF9GZWNoYSwgeSA9IFByZWNpb19PcHRpbWFsKSkgKwogICAgICBnZW9tX2xpbmUoY29sb3IgPSAiIzFmNzdiNCIsIGxpbmV3aWR0aCA9IDEuMikgKwogICAgICBnZW9tX3BvaW50KGNvbG9yID0gIiMxZjc3YjQiLCBzaXplID0gMikgKwogICAgICBsYWJzKAogICAgICAgIHRpdGxlID0gcGFzdGUoIlByZWNpbyDDk3B0aW1vIHBvciBNZXMgLSBQcm9kdWN0byIsIGlkKSwKICAgICAgICB4ID0gIkZlY2hhIiwKICAgICAgICB5ID0gIlByZWNpbyDDk3B0aW1vIgogICAgICApICsKICAgICAgc2NhbGVfeF9kYXRlKGRhdGVfbGFiZWxzID0gIiViICVZIiwgZGF0ZV9icmVha3MgPSAiMSBtb250aCIpICsKICAgICAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxMikgKwogICAgICB0aGVtZSgKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkKICAgICAgKQogICkKfQoKYGBgCgpgYGB7cn0KcnVuX3ByaWNlX29wdGltaXphdGlvbiA8LSBmdW5jdGlvbihkYXRhLCBwcm9kdWN0X2lkcywgZnV0dXJlX2RhdGVzID0gTlVMTCwgbW9kZWxvc19wcmVjaW9fbGlzdGEgPSBOVUxMKSB7CiAgaWYgKGlzLm51bGwoZnV0dXJlX2RhdGVzKSkgewogICAgZnV0dXJlX2RhdGVzIDwtIHNlcS5EYXRlKFN5cy5EYXRlKCksIGJ5ID0gIm1vbnRoIiwgbGVuZ3RoLm91dCA9IDYpCiAgfQoKICBwcmVjaW9zX29wdGltb3NfbGlzdGEgPC0gbGlzdCgpCgogIGZvciAoaWQgaW4gcHJvZHVjdF9pZHMpIHsKICAgIGNhdCgiRXN0aW1hbmRvIHByZWNpb3Mgw7NwdGltb3MgcGFyYSBwcm9kdWN0bzoiLCBpZCwgIlxuIikKCiAgICBwcmljZV9tb2RlbCA8LSBOVUxMCiAgICBpZiAoIWlzLm51bGwobW9kZWxvc19wcmVjaW9fbGlzdGEpKSB7CiAgICAgIHByaWNlX21vZGVsIDwtIG1vZGVsb3NfcHJlY2lvX2xpc3RhW1thcy5jaGFyYWN0ZXIoaWQpXV0KICAgIH0KCiAgICBwcmVjaW9zX29wdGltb3NfbGlzdGFbW2FzLmNoYXJhY3RlcihpZCldXSA8LSBlc3RpbWF0ZV9vcHRpbWFsX3ByaWNlcygKICAgICAgZGF0YSA9IGRhdGEsCiAgICAgIHByb2R1Y3RfaWQgPSBpZCwKICAgICAgcHJpY2VfbW9kZWxzID0gcHJpY2VfbW9kZWwsCiAgICAgIGZ1dHVyZV9kYXRlcyA9IGZ1dHVyZV9kYXRlcwogICAgKQogIH0KCiAgcmV0dXJuKHByZWNpb3Nfb3B0aW1vc19saXN0YSkKfQoKYGBgCgpgYGB7cn0KIyBGdW5jacOzbiBwcmluY2lwYWwgcXVlIGludGVncmEgdG9kbyBlbCBwaXBlbGluZSBjb24gc29sbyBYR0Jvb3N0CnJ1bl9jb21wbGV0ZV9hbmFseXNpcyA8LSBmdW5jdGlvbihkYXRhLCB0b3BfaWRzLCBtb2RlbG9zX3hnYiwgbW9kZWxvc19wcmVjaW9fbGlzdGEgPSBOVUxMKSB7CiAgIyAxLiBFc3RpbWFyIHByZWNpb3Mgw7NwdGltb3MKICBhbGxfcmVzdWx0cyA8LSBydW5fcHJpY2Vfb3B0aW1pemF0aW9uKGRhdGEsIHRvcF9pZHMsIG1vZGVsb3NfcHJlY2lvX2xpc3RhID0gbW9kZWxvc19wcmVjaW9fbGlzdGEpCgogICMgMi4gSW50ZWdyYXIgY29uIG1vZGVsbyBYR0Jvb3N0CiAgaW50ZWdyYXRlZF9yZXN1bHRzIDwtIGxpc3QoKQoKICBmb3IgKGkgaW4gc2VxX2Fsb25nKHRvcF9pZHMpKSB7CiAgICBwaWQgPC0gdG9wX2lkc1tpXQogICAgcGlkX3N0ciA8LSBhcy5jaGFyYWN0ZXIocGlkKQoKICAgIHhnYl9tb2RlbCA8LSBpZiAobGVuZ3RoKG1vZGVsb3NfeGdiKSA+PSBpKSBtb2RlbG9zX3hnYltbaV1dIGVsc2UgTlVMTAoKICAgIGZ1dHVyZV9wcmVkaWN0aW9ucyA8LSBpbnRlZ3JhdGVfd2l0aF9leGlzdGluZ19tb2RlbHMoCiAgICAgIGRhdGEgPSBkYXRhLAogICAgICBwcm9kdWN0X2lkID0gcGlkLAogICAgICBwcmljZV9vcHRfcmVzdWx0cyA9IGFsbF9yZXN1bHRzLAogICAgICB4Z2JfbW9kZWwgPSB4Z2JfbW9kZWwKICAgICkKCiAgICBpbnRlZ3JhdGVkX3Jlc3VsdHNbW3BpZF9zdHJdXSA8LSBmdXR1cmVfcHJlZGljdGlvbnMKCiAgICBpZiAobnJvdyhmdXR1cmVfcHJlZGljdGlvbnMpID4gMCkgewogICAgICBwX3NhbGVzIDwtIGdncGxvdChmdXR1cmVfcHJlZGljdGlvbnMsIGFlcyh4ID0gRmVjaGEsIHkgPSBWZW50YV9YR0Jvb3N0KSkgKwogICAgICAgIGdlb21fbGluZShjb2xvciA9ICIjMWY3N2I0IiwgbGluZXdpZHRoID0gMS4yKSArCiAgICAgICAgZ2VvbV9wb2ludChzaXplID0gMikgKwogICAgICAgIGxhYnMoCiAgICAgICAgICB0aXRsZSA9IHBhc3RlKCJQcmVkaWNjaW9uZXMgZGUgdmVudGFzIGNvbiBwcmVjaW9zIMOzcHRpbW9zIC0gUHJvZHVjdG8iLCBwaWQpLAogICAgICAgICAgeCA9ICJGZWNoYSIsCiAgICAgICAgICB5ID0gIlZlbnRhcyBlc3RpbWFkYXMgKCQpIgogICAgICAgICkgKwogICAgICAgIHRoZW1lX21pbmltYWwoKQoKICAgICAgcF9tYXJnaW5zIDwtIGdncGxvdChmdXR1cmVfcHJlZGljdGlvbnMsIGFlcyh4ID0gRmVjaGEsIHkgPSBNYXJnZW5fWEdCb29zdCkpICsKICAgICAgICBnZW9tX2NvbChmaWxsID0gInN0ZWVsYmx1ZSIsIHdpZHRoID0gMTUpICsKICAgICAgICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQoTWFyZ2VuX1hHQm9vc3QsIDApKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArCiAgICAgICAgbGFicygKICAgICAgICAgIHRpdGxlID0gcGFzdGUoIk1hcmdlbiBlc3BlcmFkbyBjb24gcHJlY2lvcyDDs3B0aW1vcyAtIFByb2R1Y3RvIiwgcGlkKSwKICAgICAgICAgIHggPSAiRmVjaGEiLAogICAgICAgICAgeSA9ICJNYXJnZW4gZXN0aW1hZG8gKCQpIgogICAgICAgICkgKwogICAgICAgIHRoZW1lX21pbmltYWwoKQoKICAgICAgYWxsX3Jlc3VsdHNbW3BpZF9zdHJdXSRpbnRlZ3JhdGVkX3Bsb3RzIDwtIGxpc3QoCiAgICAgICAgc2FsZXMgPSBwX3NhbGVzLAogICAgICAgIG1hcmdpbnMgPSBwX21hcmdpbnMKICAgICAgKQogICAgfQogIH0KCiAgIyAzLiBWaXN1YWxpemFjacOzbiBkZSBwcmVjaW9zIMOzcHRpbW9zCiAgYWxsX29wdGltYWxfcHJpY2VzIDwtIGRhdGEuZnJhbWUoKQoKICBmb3IgKHBpZCBpbiB0b3BfaWRzKSB7CiAgICBwaWRfc3RyIDwtIGFzLmNoYXJhY3RlcihwaWQpCiAgICBpZiAocGlkX3N0ciAlaW4lIG5hbWVzKGFsbF9yZXN1bHRzKSkgewogICAgICBvcHRfcHJpY2VzIDwtIGFsbF9yZXN1bHRzW1twaWRfc3RyXV0kcHJlY2lvc19vcHRpbW9zICU+JQogICAgICAgIG11dGF0ZShJRF9JbnZlbnRhcmlvID0gcGlkKQoKICAgICAgYWxsX29wdGltYWxfcHJpY2VzIDwtIHJiaW5kKGFsbF9vcHRpbWFsX3ByaWNlcywgb3B0X3ByaWNlcykKICAgIH0KICB9CgogIHBfY29tcGFyaXNvbiA8LSBnZ3Bsb3QoYWxsX29wdGltYWxfcHJpY2VzLAogICAgICAgICAgICAgICAgICAgICAgICAgYWVzKHggPSBUcnhfRmVjaGEsIHkgPSBQcmVjaW9fT3B0aW1hbCwgY29sb3IgPSBmYWN0b3IoSURfSW52ZW50YXJpbykpKSArCiAgICBnZW9tX2xpbmUoc2l6ZSA9IDEuMikgKwogICAgZ2VvbV9wb2ludChzaXplID0gMykgKwogICAgbGFicygKICAgICAgdGl0bGUgPSAiQ29tcGFyYWNpw7NuIGRlIFByZWNpb3Mgw5NwdGltb3MgcG9yIFByb2R1Y3RvIiwKICAgICAgeCA9ICJGZWNoYSIsCiAgICAgIHkgPSAiUHJlY2lvIMOTcHRpbW8iLAogICAgICBjb2xvciA9ICJJRCBQcm9kdWN0byIKICAgICkgKwogICAgdGhlbWVfbWluaW1hbCgpCgogICMgNC4gTcOpdHJpY2FzIGZpbmFsZXMKICBtZXRyaWNhc19vcHRpbWFzIDwtIGRhdGEuZnJhbWUoKQoKICBmb3IgKHBpZCBpbiB0b3BfaWRzKSB7CiAgICBwaWRfc3RyIDwtIGFzLmNoYXJhY3RlcihwaWQpCiAgICBpZiAocGlkX3N0ciAlaW4lIG5hbWVzKGludGVncmF0ZWRfcmVzdWx0cykpIHsKICAgICAgcHJlZF9kYXRhIDwtIGludGVncmF0ZWRfcmVzdWx0c1tbcGlkX3N0cl1dCgogICAgICBpZiAoIk1hcmdlbl9YR0Jvb3N0IiAlaW4lIG5hbWVzKHByZWRfZGF0YSkpIHsKICAgICAgICBtZXRyaWNzX3JvdyA8LSBkYXRhLmZyYW1lKAogICAgICAgICAgSURfSW52ZW50YXJpbyA9IHBpZCwKICAgICAgICAgIFByZWNpb19Qcm9tZWRpbyA9IG1lYW4ocHJlZF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbywgbmEucm0gPSBUUlVFKSwKICAgICAgICAgIFZlbnRhX1RvdGFsID0gc3VtKHByZWRfZGF0YSRWZW50YV9YR0Jvb3N0LCBuYS5ybSA9IFRSVUUpLAogICAgICAgICAgTWFyZ2VuX1RvdGFsID0gc3VtKHByZWRfZGF0YSRNYXJnZW5fWEdCb29zdCwgbmEucm0gPSBUUlVFKSwKICAgICAgICAgIE1hcmdlbl9Qb3JjZW50dWFsID0gMTAwICogc3VtKHByZWRfZGF0YSRNYXJnZW5fWEdCb29zdCwgbmEucm0gPSBUUlVFKSAvCiAgICAgICAgICAgIHN1bShwcmVkX2RhdGEkVmVudGFfWEdCb29zdCwgbmEucm0gPSBUUlVFKQogICAgICAgICkKICAgICAgICBtZXRyaWNhc19vcHRpbWFzIDwtIHJiaW5kKG1ldHJpY2FzX29wdGltYXMsIG1ldHJpY3Nfcm93KQogICAgICB9CiAgICB9CiAgfQoKICByZXR1cm4obGlzdCgKICAgIHJlc3VsdGFkb3MgPSBhbGxfcmVzdWx0cywKICAgIGludGVncmFjaW9uID0gaW50ZWdyYXRlZF9yZXN1bHRzLAogICAgcHJlY2lvc19vcHRpbW9zID0gYWxsX29wdGltYWxfcHJpY2VzLAogICAgbWV0cmljYXNfb3B0aW1hcyA9IG1ldHJpY2FzX29wdGltYXMsCiAgICBncmFmaWNvX2NvbXBhcmF0aXZvID0gcF9jb21wYXJpc29uCiAgKSkKfQoKYGBgCgpgYGB7cn0KcmVzdWx0YWRvX2NvbXBsZXRvIDwtIHJ1bl9jb21wbGV0ZV9hbmFseXNpcygKICBkYXRhID0gZGF0b3MsCiAgdG9wX2lkcyA9IHByb2R1Y3Rvc19pZHMsCiAgbW9kZWxvc194Z2IgPSBtb2RlbG9zX3hnYl9saXN0YSwKICBtb2RlbG9zX3ByZWNpb19saXN0YSA9IG1vZGVsb3NfcHJlY2lvX2xpc3RhCikKYGBgCgoKIyMjIEdyw6FmaWNvIGNvbXBhcmF0aXZvIGRlIHByZWNpb3Mgw7NwdGltb3MgcG9yIHByb2R1Y3RvOgpgYGB7cn0KIyBNb3N0cmFyIG3DqXRyaWNhcyBzaSBlc3TDoXMgZW4gbW9kbyBpbnRlcmFjdGl2bwppZiAoaW50ZXJhY3RpdmUoKSkgVmlldyhyZXN1bHRhZG9fY29tcGxldG8kbWV0cmljYXNfb3B0aW1hcykKCmNhdCgiR3LDoWZpY28gY29tcGFyYXRpdm8gZGUgcHJlY2lvcyDDs3B0aW1vcyBwb3IgcHJvZHVjdG86XG4iKQpgYGAKCmBgYHtyfQpwcmludChyZXN1bHRhZG9fY29tcGxldG8kZ3JhZmljb19jb21wYXJhdGl2bykKYGBgCgojIFByZWRpY2Npw7NuIGRlIHZlbnRhcyBjb24gcHJlY2lvcyBvcHRpbW9zIHBvciBwcm9kdWN0bwoKCmBgYHtyfQpjYXQoIkdyw6FmaWNvcyBpbmRpdmlkdWFsZXMgcG9yIHByb2R1Y3RvOlxuIikKYGBgCgoKYGBge3J9CmZvciAocGlkIGluIG5hbWVzKHJlc3VsdGFkb19jb21wbGV0byRyZXN1bHRhZG9zKSkgewogIHBsb3RzIDwtIHJlc3VsdGFkb19jb21wbGV0byRyZXN1bHRhZG9zW1twaWRdXSRpbnRlZ3JhdGVkX3Bsb3RzCiAgaWYgKCFpcy5udWxsKHBsb3RzKSkgewogICAgY2F0KHBhc3RlMCgiIyMgUHJvZHVjdG86ICIsIHBpZCwgIlxuXG4iKSkKICAgIHByaW50KHBsb3RzJHNhbGVzKSAgICMgc29sbyB1c2EgVmVudGFfWEdCb29zdCBpbnRlcm5hbWVudGUKICAgIHByaW50KHBsb3RzJG1hcmdpbnMpICMgc29sbyB1c2EgTWFyZ2VuX1hHQm9vc3QgaW50ZXJuYW1lbnRlCiAgICAKICAgIGNhdCgiXG4tLS1cblxuIikKICB9Cn0KYGBgCgoKIyMjIFRBQkxBIEZJTkFMIApgYGB7cn0KdGFibGFfZmluYWwgPC0gZGF0YS5mcmFtZSgpCgpmb3IgKHBpZCBpbiBwcm9kdWN0b3NfaWRzKSB7CiAgcHJlZCA8LSByZXN1bHRhZG9fY29tcGxldG8kaW50ZWdyYWNpb25bW2FzLmNoYXJhY3RlcihwaWQpXV0KICByZWFsIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gcGlkKQoKICBpZiAoIWlzLm51bGwocHJlZCkgJiYgbnJvdyhwcmVkKSA+IDApIHsKICAgIHByZWNpb19wcm9tZWRpbyA8LSBtZWFuKHJlYWwkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCBuYS5ybSA9IFRSVUUpCiAgICBtYXJnZW5faGlzdG9yaWNvX3Byb21lZGlvIDwtIG1lYW4ocmVhbCRWZW50YSAtIHJlYWwkQ29zdG9fVmVudGEsIG5hLnJtID0gVFJVRSkKCiAgICAjIEV4dHJhZXIgbWVzIGRlIGxhcyBmZWNoYXMgZnV0dXJhcyBzaW11bGFkYXMKICAgIHByZWQkTWVzX1NpbXVsYWRvIDwtIGx1YnJpZGF0ZTo6bW9udGgocHJlZCRGZWNoYSkKCiAgICAjIENhbGN1bGFyIHZlbnRhIHByb21lZGlvIGhpc3TDs3JpY2EgcG9yIG1lcwogICAgdmVudGFfaGlzdG9yaWNhX3Bvcl9tZXMgPC0gcmVhbCAlPiUKICAgICAgbXV0YXRlKE1lcyA9IGx1YnJpZGF0ZTo6bW9udGgoVHJ4X0ZlY2hhKSkgJT4lCiAgICAgIGdyb3VwX2J5KE1lcykgJT4lCiAgICAgIHN1bW1hcmlzZShWZW50YV9IaXN0b3JpY2FfUHJvbWVkaW8gPSBtZWFuKFZlbnRhLCBuYS5ybSA9IFRSVUUpLCAuZ3JvdXBzID0gImRyb3AiKQoKICAgICMgQWdyZWdhciBjb2x1bW5hIGRlIHZlbnRhIGVzcGVyYWRhIGhpc3TDs3JpY2EgYWwgZGYgZGUgcHJlZGljY2nDs24KICAgIHByZWQgPC0gcHJlZCAlPiUKICAgICAgbGVmdF9qb2luKHZlbnRhX2hpc3RvcmljYV9wb3JfbWVzLCBieSA9IGMoIk1lc19TaW11bGFkbyIgPSAiTWVzIikpCgogICAgdGFibGFfcHJvZHVjdG8gPC0gcHJlZCAlPiUKICAgICAgc2VsZWN0KEZlY2hhLCBQcmVjaW9fRmluYWxfVW5pdGFyaW8sIFZlbnRhX1hHQm9vc3QsIE1hcmdlbl9YR0Jvb3N0LCBWZW50YV9IaXN0b3JpY2FfUHJvbWVkaW8pICU+JQogICAgICByZW5hbWUoCiAgICAgICAgUHJlY2lvX09wdGltbyA9IFByZWNpb19GaW5hbF9Vbml0YXJpbywKICAgICAgICBWZW50YV9Fc3BlcmFkYV9YR0Jvb3N0ID0gVmVudGFfWEdCb29zdCwKICAgICAgICBNYXJnZW5fWEdCb29zdCA9IE1hcmdlbl9YR0Jvb3N0LAogICAgICAgIFZlbnRhX0VzcGVyYWRhX0hpc3RvcmljYSA9IFZlbnRhX0hpc3RvcmljYV9Qcm9tZWRpbwogICAgICApICU+JQogICAgICBtdXRhdGUoCiAgICAgICAgSURfSW52ZW50YXJpbyA9IHBpZCwKICAgICAgICBQcmVjaW9fUHJvbWVkaW9fSGlzdG9yaWNvID0gcm91bmQocHJlY2lvX3Byb21lZGlvLCAyKSwKICAgICAgICBNYXJnZW5fSGlzdG9yaWNvX1Byb21lZGlvID0gcm91bmQobWFyZ2VuX2hpc3Rvcmljb19wcm9tZWRpbywgMikKICAgICAgKSAlPiUKICAgICAgc2VsZWN0KElEX0ludmVudGFyaW8sIEZlY2hhLCBQcmVjaW9fUHJvbWVkaW9fSGlzdG9yaWNvLCBQcmVjaW9fT3B0aW1vLAogICAgICAgICAgICAgVmVudGFfRXNwZXJhZGFfSGlzdG9yaWNhLCBWZW50YV9Fc3BlcmFkYV9YR0Jvb3N0LAogICAgICAgICAgICAgTWFyZ2VuX0hpc3Rvcmljb19Qcm9tZWRpbywgTWFyZ2VuX1hHQm9vc3QpCgogICAgdGFibGFfZmluYWwgPC0gYmluZF9yb3dzKHRhYmxhX2ZpbmFsLCB0YWJsYV9wcm9kdWN0bykKICB9Cn0KCnByaW50KHRhYmxhX2ZpbmFsKQpgYGAKCgoKCgoKCgoK