EJERCICIO 2 “Hershey´s”

Instrucciones: Modela la sig. Serie de tiempo y elige la mejor opcion. Pronostica los siguientes 6 meses

Se cuenta con las ventas históricas mensuales de leche saborizada Hershey México (en miles de dólares), de enero 2017 a diciembre 2019 (36 observaciones). El objetivo es probar 5 modelos de pronóstico, compararlos y elegir el que mejor se ajuste a los datos históricos, para proyectar las siguientes 6 meses (enero–junio 2020).

INSTALACIÓN DE LIBRERÍAS

#install.packages("forecast")
#install.packages("readxl")
library(forecast)
library(readxl)

CARGA Y ACOMODO DE LOS DATOS

El archivo Ventas_Históricas_Lechitas.xlsx viene organizado en 3 bloques de columnas (uno por año: 2017, 2018 y 2019), cada uno con un par de columnas “Mes” / “Ventas”. Se elige el archivo, se leen los 3 bloques y se concatenan en una sola serie continua de 36 meses.

# En vez de file.choose() (que requiere selección manual e interactiva),
# se indica la ruta directa del archivo ya subido:
ruta_archivo <- "Ventas_Históricas_Lechitas.xlsx"

# Se brincan las primeras 2 filas (título y subtítulo "Miles de dólares")
# y se toma la fila 3 como encabezado (Mes, Ventas, Mes, Ventas, Mes, Ventas)
datos_crudos <- read_excel(ruta_archivo, sheet = "Hoja1", skip = 2)
colnames(datos_crudos)  # readxl renombra los duplicados como ...1, ...2, etc.
## [1] "Mes...1"    "Ventas...2" "Mes...3"    "Ventas...4" "Mes...5"   
## [6] "Ventas...6"
# Bloque 2017 = columnas 1 y 2 ; Bloque 2018 = columnas 3 y 4 ; Bloque 2019 = columnas 5 y 6
ventas_2017 <- na.omit(datos_crudos[[2]])
ventas_2018 <- na.omit(datos_crudos[[4]])
ventas_2019 <- na.omit(datos_crudos[[6]])

ventas <- c(ventas_2017, ventas_2018, ventas_2019)
mes <- 1:length(ventas)

dataframe2 <- data.frame(mes, ventas)
head(dataframe2, 12)
##    mes   ventas
## 1    1 25520.51
## 2    2 23740.11
## 3    3 26253.58
## 4    4 25868.43
## 5    5 27072.87
## 6    6 27150.50
## 7    7 27067.10
## 8    8 28145.25
## 9    9 27546.29
## 10  10 28400.37
## 11  11 27441.98
## 12  12 27852.47
nrow(dataframe2)  # deben ser 36 meses (12 x 3 años)
## [1] 36
# Serie de tiempo MENSUAL (frequency = 12), iniciando en Enero 2017
tsDOS <- ts(ventas, start = c(2017,1), frequency = 12)
tsDOS
##           Jan      Feb      Mar      Apr      May      Jun      Jul      Aug
## 2017 25520.51 23740.11 26253.58 25868.43 27072.87 27150.50 27067.10 28145.25
## 2018 28463.69 26996.11 29768.20 29292.51 29950.68 30099.17 30851.26 32271.76
## 2019 32496.44 31287.28 33376.02 32949.77 34004.11 33757.89 32927.30 34324.12
##           Sep      Oct      Nov      Dec
## 2017 27546.29 28400.37 27441.98 27852.47
## 2018 31940.74 32995.93 32197.12 31984.82
## 2019 35151.28 36133.07 34799.91 34846.17
plot(tsDOS, main = "Ventas históricas de leche saborizada Hershey México",
     ylab = "Ventas (miles de dólares)", xlab = "Año", type = "o", col = "blue")

A diferencia del ejercicio anterior, aquí sí se observa una tendencia clara y sostenida: las ventas pasan de aproximadamente $23,700 a $36,100 miles de dólares entre 2017 y 2019, con ligeras caídas en enero/febrero de cada año (posible efecto estacional post-diciembre).

MODELO 1. NAIVE

naive2 <- naive(tsDOS, h = 6)
summary(naive2)
## 
## Forecast method: Naive method
## 
## Model Information:
## Call: naive(y = tsDOS, h = 6) 
## 
## Residual sd: 1112.6435 
## 
## Error measures:
##                    ME     RMSE      MAE       MPE     MAPE      MASE       ACF1
## Training set 266.4474 1112.644 902.8514 0.8162353 3.013136 0.2579763 -0.5648044
## 
## Forecasts:
##          Point Forecast    Lo 80    Hi 80    Lo 95    Hi 95
## Jan 2020       34846.17 33420.26 36272.08 32665.43 37026.91
## Feb 2020       34846.17 32829.63 36862.71 31762.14 37930.20
## Mar 2020       34846.17 32376.42 37315.92 31069.02 38623.32
## Apr 2020       34846.17 31994.35 37697.99 30484.69 39207.65
## May 2020       34846.17 31657.74 38034.60 29969.88 39722.46
## Jun 2020       34846.17 31353.42 38338.92 29504.47 40187.87
plot(naive2)

MODELO 2. Promedio Móvil (k=3)

# Promedio móvil de orden 3 sobre los datos históricos (referencia / suavizado)
ma3 <- ma(tsDOS, order = 3)

# Pronóstico recursivo con promedio móvil simple k=3
ma_forecast <- function(serie, k = 3, h = 6){
  serie <- as.numeric(serie)
  pronosticos <- numeric(h)
  for (i in 1:h){
    ultimos <- tail(serie, k)
    siguiente <- mean(ultimos)
    pronosticos[i] <- siguiente
    serie <- c(serie, siguiente)
  }
  return(pronosticos)
}

pronostico_ma3 <- ma_forecast(tsDOS, k = 3, h = 6)
pronostico_ma3
## [1] 35259.72 34968.60 35024.83 35084.38 35025.94 35045.05

MODELO 3. Promedio Móvil Ponderado (k=3), Pesos: 3/6, 2/6, 1/6

pesos <- c(3/6, 2/6, 1/6)  # del más reciente al más antiguo

wma_forecast <- function(serie, pesos = c(3/6, 2/6, 1/6), h = 6){
  serie <- as.numeric(serie)
  k <- length(pesos)
  pronosticos <- numeric(h)
  for (i in 1:h){
    ultimos <- tail(serie, k)
    ultimos_ordenado <- rev(ultimos)
    siguiente <- sum(ultimos_ordenado * pesos)
    pronosticos[i] <- siguiente
    serie <- c(serie, siguiente)
  }
  return(pronosticos)
}

pronostico_wma3 <- wma_forecast(tsDOS, pesos = pesos, h = 6)
pronostico_wma3
## [1] 35045.23 34937.99 34958.44 34966.09 34958.85 34961.20

Tabla de datos (histórico + promedio móvil de referencia)

tabla_referencia <- data.frame(
  mes = mes,
  ventas = ventas,
  MA3 = as.numeric(ma3)
)
tabla_referencia
##    mes   ventas      MA3
## 1    1 25520.51       NA
## 2    2 23740.11 25171.40
## 3    3 26253.58 25287.37
## 4    4 25868.43 26398.29
## 5    5 27072.87 26697.26
## 6    6 27150.50 27096.82
## 7    7 27067.10 27454.28
## 8    8 28145.25 27586.21
## 9    9 27546.29 28030.64
## 10  10 28400.37 27796.21
## 11  11 27441.98 27898.27
## 12  12 27852.47 27919.38
## 13  13 28463.69 27770.76
## 14  14 26996.11 28409.33
## 15  15 29768.20 28685.60
## 16  16 29292.51 29670.46
## 17  17 29950.68 29780.79
## 18  18 30099.17 30300.37
## 19  19 30851.26 31074.06
## 20  20 32271.76 31687.92
## 21  21 31940.74 32402.81
## 22  22 32995.93 32377.93
## 23  23 32197.12 32392.62
## 24  24 31984.82 32226.13
## 25  25 32496.44 31922.84
## 26  26 31287.28 32386.58
## 27  27 33376.02 32537.69
## 28  28 32949.77 33443.30
## 29  29 34004.11 33570.59
## 30  30 33757.89 33563.10
## 31  31 32927.30 33669.77
## 32  32 34324.12 34134.23
## 33  33 35151.28 35202.82
## 34  34 36133.07 35361.42
## 35  35 34799.91 35259.72
## 36  36 34846.17       NA

MODELO 4. Suavizado Exponencial Simple, alpha = 0.2

ses2 <- ses(tsDOS, alpha = 0.2, h = 6, initial = "simple")
summary(ses2)
## 
## Forecast method: Simple exponential smoothing
## 
## Model Information:
## Simple exponential smoothing 
## 
## Call:
## ses(y = tsDOS, h = 6, initial = "simple", alpha = 0.2)
## 
##   Smoothing parameters:
##     alpha = 0.2 
## 
##   Initial states:
##     l = 25520.5113 
## 
##   sigma:  1545.881
## Error measures:
##                    ME     RMSE      MAE     MPE    MAPE      MASE      ACF1
## Training set 1220.525 1545.881 1363.334 3.87354 4.44632 0.3895523 0.2043858
## 
## Forecasts:
##          Point Forecast    Lo 80    Hi 80    Lo 95    Hi 95
## Jan 2020       34308.29 32327.16 36289.42 31278.42 37338.16
## Feb 2020       34308.29 32287.93 36328.65 31218.42 37398.17
## Mar 2020       34308.29 32249.44 36367.14 31159.56 37457.03
## Apr 2020       34308.29 32211.66 36404.92 31101.78 37514.81
## May 2020       34308.29 32174.55 36442.03 31045.02 37571.56
## Jun 2020       34308.29 32138.08 36478.51 30989.23 37627.35
plot(ses2)

MODELO 5. ARIMA

arima2 <- auto.arima(tsDOS)
summary(arima2)
## Series: tsDOS 
## ARIMA(1,0,0)(1,1,0)[12] with drift 
## 
## Coefficients:
##          ar1     sar1     drift
##       0.6383  -0.5517  288.8979
## s.e.  0.1551   0.2047   14.5026
## 
## sigma^2 = 202701:  log likelihood = -181.5
## AIC=371   AICc=373.11   BIC=375.72
## 
## Training set error measures:
##                    ME    RMSE    MAE        MPE      MAPE       MASE      ACF1
## Training set 25.22158 343.864 227.17 0.08059932 0.7069542 0.06491044 0.2081026
pronostico_arima <- forecast(arima2, level = c(95), h = 6)
pronostico_arima
##          Point Forecast    Lo 95    Hi 95
## Jan 2020       35498.90 34616.48 36381.32
## Feb 2020       34202.17 33155.28 35249.05
## Mar 2020       36703.01 35596.10 37809.92
## Apr 2020       36271.90 35141.44 37402.36
## May 2020       37121.98 35982.07 38261.90
## Jun 2020       37102.65 35958.90 38246.40
plot(pronostico_arima)

COMPARACIÓN DE MODELOS Y SELECCIÓN DEL MEJOR

acc_naive <- accuracy(naive2)
acc_ses   <- accuracy(ses2)
acc_arima <- accuracy(arima2)

valor_num <- as.numeric(tsDOS)
n <- length(valor_num)

# Ajuste in-sample MA(3)
ma_fitted <- sapply(4:n, function(t) mean(valor_num[(t-3):(t-1)]))
ma_actual <- valor_num[4:n]
ma_rmse <- sqrt(mean((ma_actual - ma_fitted)^2))
ma_mae  <- mean(abs(ma_actual - ma_fitted))

# Ajuste in-sample WMA(3)
wma_fitted <- sapply(4:n, function(t){
  ventana <- rev(valor_num[(t-3):(t-1)])
  sum(ventana * pesos)
})
wma_actual <- valor_num[4:n]
wma_rmse <- sqrt(mean((wma_actual - wma_fitted)^2))
wma_mae  <- mean(abs(wma_actual - wma_fitted))

tabla_comparacion <- data.frame(
  Modelo = c("Naive", "Promedio Movil (k=3)", "Promedio Movil Ponderado (k=3)",
             "Suavizado Exponencial (alpha=0.2)", "ARIMA"),
  RMSE = c(acc_naive[1,"RMSE"], ma_rmse, wma_rmse, acc_ses[1,"RMSE"], acc_arima[1,"RMSE"]),
  MAE  = c(acc_naive[1,"MAE"],  ma_mae,  wma_mae,  acc_ses[1,"MAE"],  acc_arima[1,"MAE"])
)

tabla_comparacion[order(tabla_comparacion$RMSE), ]
##                              Modelo      RMSE       MAE
## 5                             ARIMA  343.8640  227.1700
## 3    Promedio Movil Ponderado (k=3)  958.9819  814.1926
## 2              Promedio Movil (k=3) 1031.2592  870.4659
## 1                             Naive 1112.6435  902.8514
## 4 Suavizado Exponencial (alpha=0.2) 1545.8812 1363.3336

Como la serie tiene una tendencia creciente marcada, se espera que los modelos que solo promedian valores pasados sin adaptarse a la tendencia (Naive, Promedio Móvil, Promedio Móvil Ponderado, Suavizado Exponencial simple) se queden sistemáticamente POR DEBAJO de los valores reales, mientras que ARIMA —al poder incluir un término de diferenciación (d=1) y/o deriva (“drift”)— captura la tendencia y logra el menor RMSE/MAE de los cinco modelos.

PRONÓSTICO FINAL - MODELO GANADOR (6 MESES)

mejor_modelo <- tabla_comparacion[order(tabla_comparacion$RMSE), ][1, "Modelo"]
cat("Modelo elegido según RMSE/MAE mas bajos:", mejor_modelo, "\n")
## Modelo elegido según RMSE/MAE mas bajos: ARIMA
pronostico_final <- forecast(arima2, h = 6)

fechas_pronostico <- seq(as.Date("2020-01-01"), by = "month", length.out = 6)

tabla_pronostico_final <- data.frame(
  Mes = format(fechas_pronostico, "%Y-%m"),
  Pronostico_Ventas = round(as.numeric(pronostico_final$mean), 1),
  Limite_Inferior_95 = round(as.numeric(pronostico_final$lower[,2]), 1),
  Limite_Superior_95 = round(as.numeric(pronostico_final$upper[,2]), 1)
)
tabla_pronostico_final
##       Mes Pronostico_Ventas Limite_Inferior_95 Limite_Superior_95
## 1 2020-01           35498.9            34616.5            36381.3
## 2 2020-02           34202.2            33155.3            35249.0
## 3 2020-03           36703.0            35596.1            37809.9
## 4 2020-04           36271.9            35141.4            37402.4
## 5 2020-05           37122.0            35982.1            38261.9
## 6 2020-06           37102.6            35958.9            38246.4
plot(pronostico_final, main = "Pronóstico de ventas - siguientes 6 meses (Ene-Jun 2020)",
     ylab = "Ventas (miles de dólares)", xlab = "Año")

CONCLUSIÓN

Al comparar los cinco modelos, Naive, Promedio Móvil, Promedio Móvil Ponderado y el Suavizado Exponencial (alpha=0.2) tienden a subestimar los valores futuros, ya que estos métodos solo “miran hacia atrás” y no incorporan explícitamente una tendencia; su pronóstico converge hacia el promedio de las últimas observaciones, quedándose por debajo del nivel que la serie viene alcanzando.

El modelo ARIMA, seleccionado automáticamente con auto.arima(), es el que mejor se ajusta a los datos (menor RMSE y MAE), porque logra capturar la tendencia creciente de la serie mediante diferenciación y/o un término de deriva. Por esta razón, ARIMA se elige como el modelo final para pronosticar las ventas de los siguientes 6 meses (enero a junio de 2020), proyectando que la tendencia de crecimiento observada en 2017-2019 continuará, con ventas estimadas por encima de los $35,000 mil dólares mensuales y una banda de confianza al 95% que refleja la incertidumbre natural de proyectar hacia el futuro. Desde el punto de vista del negocio, esto sugiere que Hershey México debería planear capacidad de producción, inventario y distribución al alza para los primeros meses de 2020, dado que la demanda de este producto no muestra señales de desaceleración en el periodo analizado.

LS0tDQp0aXRsZTogIlRFTUEyX1RpbWVzZXJpZXMrR0VGQSAtIFZlbnRhcyBIZXJzaGV5IE3DqXhpY28gKFByb27Ds3N0aWNvIDYgbWVzZXMpIg0KYXV0aG9yOiAiTHVjZXJvX1NhbGF6YXJfQTAwODM0ODkzIg0KZGF0ZTogIjIwMjYtMDgtMTkiDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KICAgIGNvZGVfZG93bmxvYWQ6IFRSVUUNCiAgICB0aGVtZTogdW5pdGVkDQotLS0NCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlOyI+RUpFUkNJQ0lPIDIgIkhlcnNoZXnCtHMiPC9zcGFuPg0KSW5zdHJ1Y2Npb25lczogTW9kZWxhIGxhIHNpZy4gU2VyaWUgZGUgdGllbXBvIHkgZWxpZ2UgbGEgbWVqb3Igb3BjaW9uLiBQcm9ub3N0aWNhIGxvcyBzaWd1aWVudGVzIDYgbWVzZXMNCg0KU2UgY3VlbnRhIGNvbiBsYXMgKip2ZW50YXMgaGlzdMOzcmljYXMgbWVuc3VhbGVzIGRlIGxlY2hlIHNhYm9yaXphZGEgSGVyc2hleSBNw6l4aWNvKiogKGVuIG1pbGVzIGRlIGTDs2xhcmVzKSwgZGUgZW5lcm8gMjAxNyBhIGRpY2llbWJyZSAyMDE5ICgzNiBvYnNlcnZhY2lvbmVzKS4gRWwgb2JqZXRpdm8gZXMgcHJvYmFyIDUgbW9kZWxvcyBkZSBwcm9uw7NzdGljbywgY29tcGFyYXJsb3MgeSBlbGVnaXIgZWwgcXVlIG1lam9yIHNlIGFqdXN0ZSBhIGxvcyBkYXRvcyBoaXN0w7NyaWNvcywgcGFyYSBwcm95ZWN0YXIgbGFzICoqc2lndWllbnRlcyA2IG1lc2VzKiogKGVuZXJv4oCTanVuaW8gMjAyMCkuDQoNCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiBncmVlbjsiPklOU1RBTEFDScOTTiBERSBMSUJSRVLDjUFTPC9zcGFuPg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiNpbnN0YWxsLnBhY2thZ2VzKCJmb3JlY2FzdCIpDQojaW5zdGFsbC5wYWNrYWdlcygicmVhZHhsIikNCmxpYnJhcnkoZm9yZWNhc3QpDQpsaWJyYXJ5KHJlYWR4bCkNCmBgYA0KDQojIDxzcGFuIHN0eWxlPSJjb2xvcjogZ3JlZW47Ij5DQVJHQSBZIEFDT01PRE8gREUgTE9TIERBVE9TPC9zcGFuPg0KDQpFbCBhcmNoaXZvIGBWZW50YXNfSGlzdMOzcmljYXNfTGVjaGl0YXMueGxzeGAgdmllbmUgb3JnYW5pemFkbyBlbiAqKjMgYmxvcXVlcyBkZSBjb2x1bW5hcyoqICh1bm8gcG9yIGHDsW86IDIwMTcsIDIwMTggeSAyMDE5KSwgY2FkYSB1bm8gY29uIHVuIHBhciBkZSBjb2x1bW5hcyAiTWVzIiAvICJWZW50YXMiLiBTZSBlbGlnZSBlbCBhcmNoaXZvLCBzZSBsZWVuIGxvcyAzIGJsb3F1ZXMgeSBzZSBjb25jYXRlbmFuIGVuIHVuYSBzb2xhIHNlcmllIGNvbnRpbnVhIGRlIDM2IG1lc2VzLg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBFbiB2ZXogZGUgZmlsZS5jaG9vc2UoKSAocXVlIHJlcXVpZXJlIHNlbGVjY2nDs24gbWFudWFsIGUgaW50ZXJhY3RpdmEpLA0KIyBzZSBpbmRpY2EgbGEgcnV0YSBkaXJlY3RhIGRlbCBhcmNoaXZvIHlhIHN1YmlkbzoNCnJ1dGFfYXJjaGl2byA8LSAiVmVudGFzX0hpc3TDs3JpY2FzX0xlY2hpdGFzLnhsc3giDQoNCiMgU2UgYnJpbmNhbiBsYXMgcHJpbWVyYXMgMiBmaWxhcyAodMOtdHVsbyB5IHN1YnTDrXR1bG8gIk1pbGVzIGRlIGTDs2xhcmVzIikNCiMgeSBzZSB0b21hIGxhIGZpbGEgMyBjb21vIGVuY2FiZXphZG8gKE1lcywgVmVudGFzLCBNZXMsIFZlbnRhcywgTWVzLCBWZW50YXMpDQpkYXRvc19jcnVkb3MgPC0gcmVhZF9leGNlbChydXRhX2FyY2hpdm8sIHNoZWV0ID0gIkhvamExIiwgc2tpcCA9IDIpDQpjb2xuYW1lcyhkYXRvc19jcnVkb3MpICAjIHJlYWR4bCByZW5vbWJyYSBsb3MgZHVwbGljYWRvcyBjb21vIC4uLjEsIC4uLjIsIGV0Yy4NCg0KIyBCbG9xdWUgMjAxNyA9IGNvbHVtbmFzIDEgeSAyIDsgQmxvcXVlIDIwMTggPSBjb2x1bW5hcyAzIHkgNCA7IEJsb3F1ZSAyMDE5ID0gY29sdW1uYXMgNSB5IDYNCnZlbnRhc18yMDE3IDwtIG5hLm9taXQoZGF0b3NfY3J1ZG9zW1syXV0pDQp2ZW50YXNfMjAxOCA8LSBuYS5vbWl0KGRhdG9zX2NydWRvc1tbNF1dKQ0KdmVudGFzXzIwMTkgPC0gbmEub21pdChkYXRvc19jcnVkb3NbWzZdXSkNCg0KdmVudGFzIDwtIGModmVudGFzXzIwMTcsIHZlbnRhc18yMDE4LCB2ZW50YXNfMjAxOSkNCm1lcyA8LSAxOmxlbmd0aCh2ZW50YXMpDQoNCmRhdGFmcmFtZTIgPC0gZGF0YS5mcmFtZShtZXMsIHZlbnRhcykNCmhlYWQoZGF0YWZyYW1lMiwgMTIpDQpucm93KGRhdGFmcmFtZTIpICAjIGRlYmVuIHNlciAzNiBtZXNlcyAoMTIgeCAzIGHDsW9zKQ0KYGBgDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIFNlcmllIGRlIHRpZW1wbyBNRU5TVUFMIChmcmVxdWVuY3kgPSAxMiksIGluaWNpYW5kbyBlbiBFbmVybyAyMDE3DQp0c0RPUyA8LSB0cyh2ZW50YXMsIHN0YXJ0ID0gYygyMDE3LDEpLCBmcmVxdWVuY3kgPSAxMikNCnRzRE9TDQoNCnBsb3QodHNET1MsIG1haW4gPSAiVmVudGFzIGhpc3TDs3JpY2FzIGRlIGxlY2hlIHNhYm9yaXphZGEgSGVyc2hleSBNw6l4aWNvIiwNCiAgICAgeWxhYiA9ICJWZW50YXMgKG1pbGVzIGRlIGTDs2xhcmVzKSIsIHhsYWIgPSAiQcOxbyIsIHR5cGUgPSAibyIsIGNvbCA9ICJibHVlIikNCmBgYA0KDQpBIGRpZmVyZW5jaWEgZGVsIGVqZXJjaWNpbyBhbnRlcmlvciwgYXF1w60gc8OtIHNlIG9ic2VydmEgdW5hICoqdGVuZGVuY2lhIGNsYXJhIHkgc29zdGVuaWRhKio6IGxhcyB2ZW50YXMgcGFzYW4gZGUgYXByb3hpbWFkYW1lbnRlICQyMyw3MDAgYSAkMzYsMTAwIG1pbGVzIGRlIGTDs2xhcmVzIGVudHJlIDIwMTcgeSAyMDE5LCBjb24gbGlnZXJhcyBjYcOtZGFzIGVuIGVuZXJvL2ZlYnJlcm8gZGUgY2FkYSBhw7FvIChwb3NpYmxlIGVmZWN0byBlc3RhY2lvbmFsIHBvc3QtZGljaWVtYnJlKS4NCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiB5ZWxsb3c7Ij5NT0RFTE8gMS4gTkFJVkU8L3NwYW4+DQoNCmBgYHtyfQ0KbmFpdmUyIDwtIG5haXZlKHRzRE9TLCBoID0gNikNCnN1bW1hcnkobmFpdmUyKQ0KcGxvdChuYWl2ZTIpDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiB5ZWxsb3c7Ij5NT0RFTE8gMi4gUHJvbWVkaW8gTcOzdmlsIChrPTMpPC9zcGFuPg0KDQpgYGB7cn0NCiMgUHJvbWVkaW8gbcOzdmlsIGRlIG9yZGVuIDMgc29icmUgbG9zIGRhdG9zIGhpc3TDs3JpY29zIChyZWZlcmVuY2lhIC8gc3Vhdml6YWRvKQ0KbWEzIDwtIG1hKHRzRE9TLCBvcmRlciA9IDMpDQoNCiMgUHJvbsOzc3RpY28gcmVjdXJzaXZvIGNvbiBwcm9tZWRpbyBtw7N2aWwgc2ltcGxlIGs9Mw0KbWFfZm9yZWNhc3QgPC0gZnVuY3Rpb24oc2VyaWUsIGsgPSAzLCBoID0gNil7DQogIHNlcmllIDwtIGFzLm51bWVyaWMoc2VyaWUpDQogIHByb25vc3RpY29zIDwtIG51bWVyaWMoaCkNCiAgZm9yIChpIGluIDE6aCl7DQogICAgdWx0aW1vcyA8LSB0YWlsKHNlcmllLCBrKQ0KICAgIHNpZ3VpZW50ZSA8LSBtZWFuKHVsdGltb3MpDQogICAgcHJvbm9zdGljb3NbaV0gPC0gc2lndWllbnRlDQogICAgc2VyaWUgPC0gYyhzZXJpZSwgc2lndWllbnRlKQ0KICB9DQogIHJldHVybihwcm9ub3N0aWNvcykNCn0NCg0KcHJvbm9zdGljb19tYTMgPC0gbWFfZm9yZWNhc3QodHNET1MsIGsgPSAzLCBoID0gNikNCnByb25vc3RpY29fbWEzDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiB5ZWxsb3c7Ij5NT0RFTE8gMy4gUHJvbWVkaW8gTcOzdmlsIFBvbmRlcmFkbyAoaz0zKSwgUGVzb3M6IDMvNiwgMi82LCAxLzY8L3NwYW4+DQoNCmBgYHtyfQ0KcGVzb3MgPC0gYygzLzYsIDIvNiwgMS82KSAgIyBkZWwgbcOhcyByZWNpZW50ZSBhbCBtw6FzIGFudGlndW8NCg0Kd21hX2ZvcmVjYXN0IDwtIGZ1bmN0aW9uKHNlcmllLCBwZXNvcyA9IGMoMy82LCAyLzYsIDEvNiksIGggPSA2KXsNCiAgc2VyaWUgPC0gYXMubnVtZXJpYyhzZXJpZSkNCiAgayA8LSBsZW5ndGgocGVzb3MpDQogIHByb25vc3RpY29zIDwtIG51bWVyaWMoaCkNCiAgZm9yIChpIGluIDE6aCl7DQogICAgdWx0aW1vcyA8LSB0YWlsKHNlcmllLCBrKQ0KICAgIHVsdGltb3Nfb3JkZW5hZG8gPC0gcmV2KHVsdGltb3MpDQogICAgc2lndWllbnRlIDwtIHN1bSh1bHRpbW9zX29yZGVuYWRvICogcGVzb3MpDQogICAgcHJvbm9zdGljb3NbaV0gPC0gc2lndWllbnRlDQogICAgc2VyaWUgPC0gYyhzZXJpZSwgc2lndWllbnRlKQ0KICB9DQogIHJldHVybihwcm9ub3N0aWNvcykNCn0NCg0KcHJvbm9zdGljb193bWEzIDwtIHdtYV9mb3JlY2FzdCh0c0RPUywgcGVzb3MgPSBwZXNvcywgaCA9IDYpDQpwcm9ub3N0aWNvX3dtYTMNCmBgYA0KDQojIyMgVGFibGEgZGUgZGF0b3MgKGhpc3TDs3JpY28gKyBwcm9tZWRpbyBtw7N2aWwgZGUgcmVmZXJlbmNpYSkNCg0KYGBge3J9DQp0YWJsYV9yZWZlcmVuY2lhIDwtIGRhdGEuZnJhbWUoDQogIG1lcyA9IG1lcywNCiAgdmVudGFzID0gdmVudGFzLA0KICBNQTMgPSBhcy5udW1lcmljKG1hMykNCikNCnRhYmxhX3JlZmVyZW5jaWENCmBgYA0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6IHllbGxvdzsiPk1PREVMTyA0LiBTdWF2aXphZG8gRXhwb25lbmNpYWwgU2ltcGxlLCBhbHBoYSA9IDAuMjwvc3Bhbj4NCg0KYGBge3J9DQpzZXMyIDwtIHNlcyh0c0RPUywgYWxwaGEgPSAwLjIsIGggPSA2LCBpbml0aWFsID0gInNpbXBsZSIpDQpzdW1tYXJ5KHNlczIpDQpwbG90KHNlczIpDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiB5ZWxsb3c7Ij5NT0RFTE8gNS4gQVJJTUE8L3NwYW4+DQoNCmBgYHtyfQ0KYXJpbWEyIDwtIGF1dG8uYXJpbWEodHNET1MpDQpzdW1tYXJ5KGFyaW1hMikNCg0KcHJvbm9zdGljb19hcmltYSA8LSBmb3JlY2FzdChhcmltYTIsIGxldmVsID0gYyg5NSksIGggPSA2KQ0KcHJvbm9zdGljb19hcmltYQ0KcGxvdChwcm9ub3N0aWNvX2FyaW1hKQ0KYGBgDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogYmx1ZTsiPkNPTVBBUkFDScOTTiBERSBNT0RFTE9TIFkgU0VMRUNDScOTTiBERUwgTUVKT1I8L3NwYW4+DQoNCmBgYHtyfQ0KYWNjX25haXZlIDwtIGFjY3VyYWN5KG5haXZlMikNCmFjY19zZXMgICA8LSBhY2N1cmFjeShzZXMyKQ0KYWNjX2FyaW1hIDwtIGFjY3VyYWN5KGFyaW1hMikNCg0KdmFsb3JfbnVtIDwtIGFzLm51bWVyaWModHNET1MpDQpuIDwtIGxlbmd0aCh2YWxvcl9udW0pDQoNCiMgQWp1c3RlIGluLXNhbXBsZSBNQSgzKQ0KbWFfZml0dGVkIDwtIHNhcHBseSg0Om4sIGZ1bmN0aW9uKHQpIG1lYW4odmFsb3JfbnVtWyh0LTMpOih0LTEpXSkpDQptYV9hY3R1YWwgPC0gdmFsb3JfbnVtWzQ6bl0NCm1hX3Jtc2UgPC0gc3FydChtZWFuKChtYV9hY3R1YWwgLSBtYV9maXR0ZWQpXjIpKQ0KbWFfbWFlICA8LSBtZWFuKGFicyhtYV9hY3R1YWwgLSBtYV9maXR0ZWQpKQ0KDQojIEFqdXN0ZSBpbi1zYW1wbGUgV01BKDMpDQp3bWFfZml0dGVkIDwtIHNhcHBseSg0Om4sIGZ1bmN0aW9uKHQpew0KICB2ZW50YW5hIDwtIHJldih2YWxvcl9udW1bKHQtMyk6KHQtMSldKQ0KICBzdW0odmVudGFuYSAqIHBlc29zKQ0KfSkNCndtYV9hY3R1YWwgPC0gdmFsb3JfbnVtWzQ6bl0NCndtYV9ybXNlIDwtIHNxcnQobWVhbigod21hX2FjdHVhbCAtIHdtYV9maXR0ZWQpXjIpKQ0Kd21hX21hZSAgPC0gbWVhbihhYnMod21hX2FjdHVhbCAtIHdtYV9maXR0ZWQpKQ0KDQp0YWJsYV9jb21wYXJhY2lvbiA8LSBkYXRhLmZyYW1lKA0KICBNb2RlbG8gPSBjKCJOYWl2ZSIsICJQcm9tZWRpbyBNb3ZpbCAoaz0zKSIsICJQcm9tZWRpbyBNb3ZpbCBQb25kZXJhZG8gKGs9MykiLA0KICAgICAgICAgICAgICJTdWF2aXphZG8gRXhwb25lbmNpYWwgKGFscGhhPTAuMikiLCAiQVJJTUEiKSwNCiAgUk1TRSA9IGMoYWNjX25haXZlWzEsIlJNU0UiXSwgbWFfcm1zZSwgd21hX3Jtc2UsIGFjY19zZXNbMSwiUk1TRSJdLCBhY2NfYXJpbWFbMSwiUk1TRSJdKSwNCiAgTUFFICA9IGMoYWNjX25haXZlWzEsIk1BRSJdLCAgbWFfbWFlLCAgd21hX21hZSwgIGFjY19zZXNbMSwiTUFFIl0sICBhY2NfYXJpbWFbMSwiTUFFIl0pDQopDQoNCnRhYmxhX2NvbXBhcmFjaW9uW29yZGVyKHRhYmxhX2NvbXBhcmFjaW9uJFJNU0UpLCBdDQpgYGANCg0KQ29tbyBsYSBzZXJpZSB0aWVuZSB1bmEgKip0ZW5kZW5jaWEgY3JlY2llbnRlIG1hcmNhZGEqKiwgc2UgZXNwZXJhIHF1ZSBsb3MgbW9kZWxvcyBxdWUgc29sbyBwcm9tZWRpYW4gdmFsb3JlcyBwYXNhZG9zIHNpbiBhZGFwdGFyc2UgYSBsYSB0ZW5kZW5jaWEgKE5haXZlLCBQcm9tZWRpbyBNw7N2aWwsIFByb21lZGlvIE3Ds3ZpbCBQb25kZXJhZG8sIFN1YXZpemFkbyBFeHBvbmVuY2lhbCBzaW1wbGUpIHNlIHF1ZWRlbiBzaXN0ZW3DoXRpY2FtZW50ZSBQT1IgREVCQUpPIGRlIGxvcyB2YWxvcmVzIHJlYWxlcywgbWllbnRyYXMgcXVlICoqQVJJTUEqKiDigJRhbCBwb2RlciBpbmNsdWlyIHVuIHTDqXJtaW5vIGRlIGRpZmVyZW5jaWFjacOzbiAoZD0xKSB5L28gZGVyaXZhICgiZHJpZnQiKeKAlCBjYXB0dXJhIGxhIHRlbmRlbmNpYSB5IGxvZ3JhIGVsIG1lbm9yIFJNU0UvTUFFIGRlIGxvcyBjaW5jbyBtb2RlbG9zLg0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij5QUk9Ow5NTVElDTyBGSU5BTCAtIE1PREVMTyBHQU5BRE9SICg2IE1FU0VTKTwvc3Bhbj4NCg0KYGBge3J9DQptZWpvcl9tb2RlbG8gPC0gdGFibGFfY29tcGFyYWNpb25bb3JkZXIodGFibGFfY29tcGFyYWNpb24kUk1TRSksIF1bMSwgIk1vZGVsbyJdDQpjYXQoIk1vZGVsbyBlbGVnaWRvIHNlZ8O6biBSTVNFL01BRSBtYXMgYmFqb3M6IiwgbWVqb3JfbW9kZWxvLCAiXG4iKQ0KDQpwcm9ub3N0aWNvX2ZpbmFsIDwtIGZvcmVjYXN0KGFyaW1hMiwgaCA9IDYpDQoNCmZlY2hhc19wcm9ub3N0aWNvIDwtIHNlcShhcy5EYXRlKCIyMDIwLTAxLTAxIiksIGJ5ID0gIm1vbnRoIiwgbGVuZ3RoLm91dCA9IDYpDQoNCnRhYmxhX3Byb25vc3RpY29fZmluYWwgPC0gZGF0YS5mcmFtZSgNCiAgTWVzID0gZm9ybWF0KGZlY2hhc19wcm9ub3N0aWNvLCAiJVktJW0iKSwNCiAgUHJvbm9zdGljb19WZW50YXMgPSByb3VuZChhcy5udW1lcmljKHByb25vc3RpY29fZmluYWwkbWVhbiksIDEpLA0KICBMaW1pdGVfSW5mZXJpb3JfOTUgPSByb3VuZChhcy5udW1lcmljKHByb25vc3RpY29fZmluYWwkbG93ZXJbLDJdKSwgMSksDQogIExpbWl0ZV9TdXBlcmlvcl85NSA9IHJvdW5kKGFzLm51bWVyaWMocHJvbm9zdGljb19maW5hbCR1cHBlclssMl0pLCAxKQ0KKQ0KdGFibGFfcHJvbm9zdGljb19maW5hbA0KDQpwbG90KHByb25vc3RpY29fZmluYWwsIG1haW4gPSAiUHJvbsOzc3RpY28gZGUgdmVudGFzIC0gc2lndWllbnRlcyA2IG1lc2VzIChFbmUtSnVuIDIwMjApIiwNCiAgICAgeWxhYiA9ICJWZW50YXMgKG1pbGVzIGRlIGTDs2xhcmVzKSIsIHhsYWIgPSAiQcOxbyIpDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiBncmVlbjsiPkNPTkNMVVNJw5NOPC9zcGFuPg0KDQpBbCBjb21wYXJhciBsb3MgY2luY28gbW9kZWxvcywgKipOYWl2ZSoqLCAqKlByb21lZGlvIE3Ds3ZpbCoqLCAqKlByb21lZGlvIE3Ds3ZpbCBQb25kZXJhZG8qKiB5IGVsICoqU3Vhdml6YWRvIEV4cG9uZW5jaWFsIChhbHBoYT0wLjIpKiogdGllbmRlbiBhIHN1YmVzdGltYXIgbG9zIHZhbG9yZXMgZnV0dXJvcywgeWEgcXVlIGVzdG9zIG3DqXRvZG9zIHNvbG8gIm1pcmFuIGhhY2lhIGF0csOhcyIgeSBubyBpbmNvcnBvcmFuIGV4cGzDrWNpdGFtZW50ZSB1bmEgdGVuZGVuY2lhOyBzdSBwcm9uw7NzdGljbyBjb252ZXJnZSBoYWNpYSBlbCBwcm9tZWRpbyBkZSBsYXMgw7psdGltYXMgb2JzZXJ2YWNpb25lcywgcXVlZMOhbmRvc2UgcG9yIGRlYmFqbyBkZWwgbml2ZWwgcXVlIGxhIHNlcmllIHZpZW5lIGFsY2FuemFuZG8uDQoNCkVsIG1vZGVsbyAqKkFSSU1BKiosIHNlbGVjY2lvbmFkbyBhdXRvbcOhdGljYW1lbnRlIGNvbiBgYXV0by5hcmltYSgpYCwgZXMgZWwgcXVlIG1lam9yIHNlIGFqdXN0YSBhIGxvcyBkYXRvcyAobWVub3IgUk1TRSB5IE1BRSksIHBvcnF1ZSBsb2dyYSBjYXB0dXJhciBsYSB0ZW5kZW5jaWEgY3JlY2llbnRlIGRlIGxhIHNlcmllIG1lZGlhbnRlIGRpZmVyZW5jaWFjacOzbiB5L28gdW4gdMOpcm1pbm8gZGUgZGVyaXZhLiBQb3IgZXN0YSByYXrDs24sICoqQVJJTUEgc2UgZWxpZ2UgY29tbyBlbCBtb2RlbG8gZmluYWwqKiBwYXJhIHByb25vc3RpY2FyIGxhcyB2ZW50YXMgZGUgbG9zIHNpZ3VpZW50ZXMgNiBtZXNlcyAoZW5lcm8gYSBqdW5pbyBkZSAyMDIwKSwgcHJveWVjdGFuZG8gcXVlIGxhIHRlbmRlbmNpYSBkZSBjcmVjaW1pZW50byBvYnNlcnZhZGEgZW4gMjAxNy0yMDE5IGNvbnRpbnVhcsOhLCBjb24gdmVudGFzIGVzdGltYWRhcyBwb3IgZW5jaW1hIGRlIGxvcyAkMzUsMDAwIG1pbCBkw7NsYXJlcyBtZW5zdWFsZXMgeSB1bmEgYmFuZGEgZGUgY29uZmlhbnphIGFsIDk1JSBxdWUgcmVmbGVqYSBsYSBpbmNlcnRpZHVtYnJlIG5hdHVyYWwgZGUgcHJveWVjdGFyIGhhY2lhIGVsIGZ1dHVyby4NCkRlc2RlIGVsIHB1bnRvIGRlIHZpc3RhIGRlbCBuZWdvY2lvLCBlc3RvIHN1Z2llcmUgcXVlIEhlcnNoZXkgTcOpeGljbyBkZWJlcsOtYSAqKnBsYW5lYXIgY2FwYWNpZGFkIGRlIHByb2R1Y2Npw7NuLCBpbnZlbnRhcmlvIHkgZGlzdHJpYnVjacOzbiBhbCBhbHphKiogcGFyYSBsb3MgcHJpbWVyb3MgbWVzZXMgZGUgMjAyMCwgZGFkbyBxdWUgbGEgZGVtYW5kYSBkZSBlc3RlIHByb2R1Y3RvIG5vIG11ZXN0cmEgc2XDsWFsZXMgZGUgZGVzYWNlbGVyYWNpw7NuIGVuIGVsIHBlcmlvZG8gYW5hbGl6YWRvLg0K