AD3003B Analítica Prescriptiva

Módulo 1 – Analítica Espacial de Datos

Febrero – Junio 2025

Actividad 0: Precio Elasticidad & Ingresos Óptimos

Instrucciones:
A partir de la base de datos “regional_price_dataset.csv” calcular los diferentes precios elasticidad e ingresos óptimos para las zonas norte, centro y sur.

EDA

Librerias

library(ggplot2)
library(dplyr)
library(readr)
library(lubridate)
library(tidyverse)

Datos

df <- read.csv("C:\\Users\\Diego Pérez\\Downloads\\regional_price_dataset.csv")

df$fecha <- as.Date(df$fecha)

# Columna ingreso
df <- df %>% mutate(ingreso = precio * cantidad)


# Ver registros
head(df)
##        fecha   zona precio cantidad ingreso
## 1 2018-01-01 Centro   13.7      195  2671.5
## 2 2018-01-01  Norte   13.7      149  2041.3
## 3 2018-01-01    Sur   13.7      168  2301.6
## 4 2018-01-02 Centro   13.7      221  3027.7
## 5 2018-01-02  Norte   13.7      179  2452.3
## 6 2018-01-02    Sur   13.7      183  2507.1

I. Análisis de Datos de Series de Tiempo

a. Visualización del precio y la cantidad a través del período de tiempo.

# Agregar columna de semana
df <- df %>%
  mutate(semana = floor_date(fecha, unit = "week"))

# Agrupar por semana y zona
df_semanal <- df %>%
  group_by(zona, semana) %>%
  summarise(cantidad = sum(cantidad), .groups = "drop")

# Gráfico de series
ggplot(df_semanal, aes(x = semana, y = cantidad, color = zona)) +
  geom_line(size = 1) +
  labs(
    title = "Serie de Tiempo de Cantidad por Región (Datos Semanales)",
    x = "Fecha (Inicio de la Semana)",
    y = "Cantidad"
  ) +
  theme_minimal()

# Convertir fecha y extraer día de la semana
df <- df %>%
  mutate(
    fecha = as.Date(fecha),
    dia_semana = wday(fecha, label = TRUE, abbr = FALSE, week_start = 1)
  )

# Gráfico tipo boxplot
ggplot(df, aes(x = dia_semana, y = cantidad, fill = zona)) +
  geom_boxplot(outlier.shape = 1) +
  labs(
    title = "Distribución de cantidad por día de la semana y zona",
    x = "Día de la semana",
    y = "Cantidad"
  ) +
  theme_minimal()

b. Visualización del precio y la cantidad a través del período de tiempo y zona.

# Asegurar que la fecha es tipo Date
df <- df %>%
  mutate(fecha = as.Date(fecha))

# Visualizar el precio por zona a lo largo del tiempo
ggplot(df, aes(x = fecha, y = precio, color = zona)) +
  geom_line() +
  labs(
    title = "Evolución del Precio por Zona",
    x = "Fecha", y = "Precio"
  ) +
  theme_minimal()

# Visualizar la cantidad por zona a lo largo del tiempo
ggplot(df, aes(x = fecha, y = cantidad, color = zona)) +
  geom_line() +
  labs(
    title = "Evolución de la Cantidad Vendida por Zona",
    x = "Fecha", y = "Cantidad Vendida"
  ) +
  theme_minimal()

c. Identificación de estacionalidad a partir de datos de series de tiempo diarios. Es decir, ¿qué días de la semana se incrementa / disminuye el nivel de ventas?

# Agrupar y calcular la cantidad promedio vendida por día de la semana (todas las zonas)
df_dia <- df %>%
  group_by(dia_semana) %>%
  summarise(cantidad_promedio = mean(cantidad))

# Visualizador: barras por día de la semana
ggplot(df_dia, aes(x = dia_semana, y = cantidad_promedio)) +
  geom_col(fill = "steelblue") +
  labs(
    title = "Promedio de ventas por día de la semana (todas las zonas)",
    x = "Día de la semana",
    y = "Cantidad promedio vendida"
  ) +
  theme_minimal()

d. Identificación de estacionalidad a nivel zona a partir de datos de series de tiempo diarios. Es decir, ¿qué días de la semana se incrementa / disminuye el nivel de ventas para cada una de las zonas?

# Calcular cantidad promedio por día y zona
df_dia_zona <- df %>%
  group_by(zona, dia_semana) %>%
  summarise(cantidad_promedio = mean(cantidad), .groups = "drop")

# Visualizador: barras por día y zona
ggplot(df_dia_zona, aes(x = dia_semana, y = cantidad_promedio, fill = zona)) +
  geom_col(position = "dodge") +
  labs(
    title = "Promedio de ventas por día de la semana por zona",
    x = "Día de la semana",
    y = "Cantidad promedio vendida"
  ) +
  theme_minimal()

e. Agrupar los datos de precio y cantidad por semana para cada una de las zonas.

# Agrupar por semana y zona
df_semanal <- df %>%
  group_by(zona, semana) %>%
  summarise(
    cantidad_total = sum(cantidad),
    precio_promedio = mean(precio),
    .groups = "drop"
  )

# Visualizador: evolución de cantidad semanal por zona
ggplot(df_semanal, aes(x = semana, y = cantidad_total, color = zona)) +
  geom_line(size = 1) +
  labs(
    title = "Evolución semanal de cantidad vendida por zona",
    x = "Semana",
    y = "Cantidad total vendida"
  ) +
  theme_minimal()

II. Modelo de Demanda

a. Visualizar la relación entre el precio y la cantidad de ventas

# Gráfico de dispersión con tendencia
ggplot(df, aes(x = precio, y = cantidad, color = zona)) +
  geom_point(alpha = 0.6) +
  geom_smooth(method = "lm", se = FALSE) +
  labs(
    title = "Relación entre precio y cantidad vendida",
    x = "Precio",
    y = "Cantidad vendida"
  ) +
  theme_minimal()

b. Estimar el modelo de demanda considerando cada una de las zonas.

# Modelo lineal por zona
ggplot(df, aes(x = precio, y = cantidad, color = zona)) +
  geom_point(alpha = 0.5) +
  geom_smooth(method = "lm", se = FALSE) +
  facet_wrap(~ zona) +
  labs(
    title = "Modelos de demanda por zona (regresión lineal)",
    x = "Precio",
    y = "Cantidad vendida"
  ) +
  theme_minimal()

modelos <- df %>%
  group_by(zona) %>%
  group_map(~ lm(cantidad ~ precio, data = .x), .keep = TRUE)

summary(modelos[[1]])
## 
## Call:
## lm(formula = cantidad ~ precio, data = .x)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -77.403 -22.115  -5.413  15.883 120.316 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 299.6711     5.7533   52.09   <2e-16 ***
## precio       -7.1412     0.4809  -14.85   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 33.32 on 495 degrees of freedom
## Multiple R-squared:  0.3082, Adjusted R-squared:  0.3068 
## F-statistic: 220.5 on 1 and 495 DF,  p-value: < 2.2e-16

III. Precio Elasticidad

a. Calcular el precio elasticidad para cada una de las zonas.

# Calcular elasticidad por zona
elasticidades <- df %>%
  group_by(zona) %>%
  summarise(
    modelo = list(lm(cantidad ~ precio, data = cur_data())),
    precio_prom = mean(precio),
    cantidad_prom = mean(cantidad)
  ) %>%
  mutate(
    pendiente = map_dbl(modelo, ~ coef(.x)[["precio"]]),
    elasticidad = pendiente * (precio_prom / cantidad_prom)
  ) %>%
  select(zona, pendiente, precio_prom, cantidad_prom, elasticidad)

print(elasticidades)
## # A tibble: 3 × 5
##   zona   pendiente precio_prom cantidad_prom elasticidad
##   <chr>      <dbl>       <dbl>         <dbl>       <dbl>
## 1 Centro     -7.14        11.6          217.      -0.380
## 2 Norte     -13.8         11.6          201.      -0.792
## 3 Sur        -6.08        11.6          167.      -0.420

b. Visualizar la elasticidad precio de la demanada para cada una de las zonas.

# Visualizar como gráfico de barras
ggplot(elasticidades, aes(x = zona, y = elasticidad, fill = zona)) +
  geom_col() +
  geom_text(aes(label = round(elasticidad, 2)), vjust = -0.5) +
  labs(
    title = "Elasticidad precio de la demanda por zona",
    x = "Zona",
    y = "Elasticidad estimada"
  ) +
  theme_minimal()

IV. Precios & Ingresos Óptimos

a. Calcular los precios e ingresos óptimos para cada una de las zonas.

get_precio_optimo <- function(modelo, zona) {
  coef <- coef(modelo)
  a <- coef[1]
  b <- coef[2]
  
  precio_optimo <- -a / (2 * b)
  
  cat("Zona:", zona, "\n")
  cat("Modelo: cantidad =", round(a, 2), "+", round(b, 2), "* precio\n")
  cat("Precio óptimo:", round(precio_optimo, 2), "\n\n")
  
  return(precio_optimo)
}

# Obtener zonas
zonas <- unique(df$zona)

# Calcular precio óptimo por zona
precios_optimos <- mapply(get_precio_optimo, modelos, zonas)
## Zona: Centro 
## Modelo: cantidad = 299.67 + -7.14 * precio
## Precio óptimo: 20.98 
## 
## Zona: Norte 
## Modelo: cantidad = 360.88 + -13.81 * precio
## Precio óptimo: 13.06 
## 
## Zona: Sur 
## Modelo: cantidad = 237.18 + -6.08 * precio
## Precio óptimo: 19.52
# Crear una secuencia de precios simulados
precios_simulados <- seq(1, 25, length.out = 200) 

# Calcular ingresos estimados por zona usando el modelo lineal
simular_ingresos <- function(modelo, zona) {
  coefs <- coef(modelo)
  a <- coefs[1]
  b <- coefs[2]
  cantidad_estimada <- a + b * precios_simulados
  ingreso_estimado <- precios_simulados * cantidad_estimada
  
  data.frame(
    zona = zona,
    precio = precios_simulados,
    ingreso = ingreso_estimado,  # <- CORREGIDO
    precio_optimo = -a / (2 * b)
  )
}

# Aplicar a cada zona
df_ingresos_simulados <- purrr::map2_dfr(modelos, zonas, simular_ingresos)

# Graficar
ggplot(df_ingresos_simulados, aes(x = precio, y = ingreso)) +
  geom_line(aes(color = zona), size = 1.2) +
  geom_vline(aes(xintercept = precio_optimo, color = zona), linetype = "dashed") +
  labs(
    title = "Ingreso estimado vs Precio por zona",
    subtitle = "Línea punteada indica el precio óptimo",
    x = "Precio",
    y = "Ingreso estimado"
  ) +
  theme_minimal() +
  expand_limits(x = c(0, 25))

V. Responder brevemente las siguientes preguntas de análisis

a. ¿Qué es optimización de precios?

La obtención del “precio ideal” que permita maximizar los ingresos. Buscando obtener un equilibrio entre la mayor cantidad de unidades vendidas y la obtención de ganancias.

b. ¿Cuál es la relación entre precio elasticidad y optimización de precios?

La relación nos ayuda a determinar que tanto cambia la cantidad vendida al modificar el precio del producto.

c. ¿Cuál es el propósito de la optimización de precios por zona?

La adaptación del precio a las caracteristicas de elasticidad de cada región.

LS0tDQp0aXRsZTogIkFjdGl2aWRhZCAwIg0KYXV0aG9yOiAiRGllZ28gQWxlamFuZHJvIFDDqXJleiBDaXNuZXJvcyAtIEEwMTI3NTU2MQ0KICAgICAgICAgIEx1aXMgRGF2aWQgU2FuY2jDqXogQ2FzdGlsbG8gLSBBMDEyNzU2NTUNCiAgICAgICAgICBBZHJpw6FuIFF1ZXphZGEgUm9kcsOtZ3VleiAtIEEwMTI1MjUzMQ0KICAgICAgICAgIEphaW1lIEVybmVzdG8gQWd1aWxhciBUcmVqbyAtIEEwMTI3NTg1OSINCmRhdGU6ICIyMDI1LTAzLTI1Ig0Kb3V0cHV0OiANCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFDQogIA0KLS0tDQoNCkFEMzAwM0IgQW5hbMOtdGljYSBQcmVzY3JpcHRpdmENCg0KTcOzZHVsbyAxIOKAkyBBbmFsw610aWNhIEVzcGFjaWFsIGRlIERhdG9zDQoNCkZlYnJlcm8g4oCTIEp1bmlvIDIwMjUNCg0KIyBBY3RpdmlkYWQgMDogUHJlY2lvIEVsYXN0aWNpZGFkICYgSW5ncmVzb3Mgw5NwdGltb3MNCg0KKipJbnN0cnVjY2lvbmVzOioqICANCkEgcGFydGlyIGRlIGxhIGJhc2UgZGUgZGF0b3MgInJlZ2lvbmFsX3ByaWNlX2RhdGFzZXQuY3N2IiBjYWxjdWxhciBsb3MgZGlmZXJlbnRlcyBwcmVjaW9zIGVsYXN0aWNpZGFkIGUgaW5ncmVzb3Mgw7NwdGltb3MgcGFyYSBsYXMgem9uYXMgbm9ydGUsIGNlbnRybyB5IHN1ci4gIA0KDQojIyBFREENCiMjIyBMaWJyZXJpYXMNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShyZWFkcikNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpgYGANCg0KIyMjIERhdG9zDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZGYgPC0gcmVhZC5jc3YoIkM6XFxVc2Vyc1xcRGllZ28gUMOpcmV6XFxEb3dubG9hZHNcXHJlZ2lvbmFsX3ByaWNlX2RhdGFzZXQuY3N2IikNCg0KZGYkZmVjaGEgPC0gYXMuRGF0ZShkZiRmZWNoYSkNCg0KIyBDb2x1bW5hIGluZ3Jlc28NCmRmIDwtIGRmICU+JSBtdXRhdGUoaW5ncmVzbyA9IHByZWNpbyAqIGNhbnRpZGFkKQ0KDQoNCiMgVmVyIHJlZ2lzdHJvcw0KaGVhZChkZikNCmBgYA0KDQojIEkuIEFuw6FsaXNpcyBkZSBEYXRvcyBkZSBTZXJpZXMgZGUgVGllbXBvICANCiFbXShDOlxcVXNlcnNcXERpZWdvIFDDqXJlelxcRG93bmxvYWRzXFxXaGF0c0FwcCBJbWFnZSAyMDI1LTAzLTI1IGF0IDguMDEuMjUgQU0uanBlZykNCg0KIVtdKEM6XFxVc2Vyc1xcRGllZ28gUMOpcmV6XFxEb3dubG9hZHNcXFdoYXRzQXBwIEltYWdlIDIwMjUtMDMtMjUgYXQgOC4wMS40MyBBTS5qcGVnKQ0KDQojIyBhLiBWaXN1YWxpemFjacOzbiBkZWwgcHJlY2lvIHkgbGEgY2FudGlkYWQgYSB0cmF2w6lzIGRlbCBwZXLDrW9kbyBkZSB0aWVtcG8uIA0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgQWdyZWdhciBjb2x1bW5hIGRlIHNlbWFuYQ0KZGYgPC0gZGYgJT4lDQogIG11dGF0ZShzZW1hbmEgPSBmbG9vcl9kYXRlKGZlY2hhLCB1bml0ID0gIndlZWsiKSkNCg0KIyBBZ3J1cGFyIHBvciBzZW1hbmEgeSB6b25hDQpkZl9zZW1hbmFsIDwtIGRmICU+JQ0KICBncm91cF9ieSh6b25hLCBzZW1hbmEpICU+JQ0KICBzdW1tYXJpc2UoY2FudGlkYWQgPSBzdW0oY2FudGlkYWQpLCAuZ3JvdXBzID0gImRyb3AiKQ0KDQojIEdyw6FmaWNvIGRlIHNlcmllcw0KZ2dwbG90KGRmX3NlbWFuYWwsIGFlcyh4ID0gc2VtYW5hLCB5ID0gY2FudGlkYWQsIGNvbG9yID0gem9uYSkpICsNCiAgZ2VvbV9saW5lKHNpemUgPSAxKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiU2VyaWUgZGUgVGllbXBvIGRlIENhbnRpZGFkIHBvciBSZWdpw7NuIChEYXRvcyBTZW1hbmFsZXMpIiwNCiAgICB4ID0gIkZlY2hhIChJbmljaW8gZGUgbGEgU2VtYW5hKSIsDQogICAgeSA9ICJDYW50aWRhZCINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIENvbnZlcnRpciBmZWNoYSB5IGV4dHJhZXIgZMOtYSBkZSBsYSBzZW1hbmENCmRmIDwtIGRmICU+JQ0KICBtdXRhdGUoDQogICAgZmVjaGEgPSBhcy5EYXRlKGZlY2hhKSwNCiAgICBkaWFfc2VtYW5hID0gd2RheShmZWNoYSwgbGFiZWwgPSBUUlVFLCBhYmJyID0gRkFMU0UsIHdlZWtfc3RhcnQgPSAxKQ0KICApDQoNCiMgR3LDoWZpY28gdGlwbyBib3hwbG90DQpnZ3Bsb3QoZGYsIGFlcyh4ID0gZGlhX3NlbWFuYSwgeSA9IGNhbnRpZGFkLCBmaWxsID0gem9uYSkpICsNCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2hhcGUgPSAxKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiRGlzdHJpYnVjacOzbiBkZSBjYW50aWRhZCBwb3IgZMOtYSBkZSBsYSBzZW1hbmEgeSB6b25hIiwNCiAgICB4ID0gIkTDrWEgZGUgbGEgc2VtYW5hIiwNCiAgICB5ID0gIkNhbnRpZGFkIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KIyMgYi4gVmlzdWFsaXphY2nDs24gZGVsIHByZWNpbyB5IGxhIGNhbnRpZGFkIGEgdHJhdsOpcyBkZWwgcGVyw61vZG8gZGUgdGllbXBvIHkgem9uYS4gDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBBc2VndXJhciBxdWUgbGEgZmVjaGEgZXMgdGlwbyBEYXRlDQpkZiA8LSBkZiAlPiUNCiAgbXV0YXRlKGZlY2hhID0gYXMuRGF0ZShmZWNoYSkpDQoNCiMgVmlzdWFsaXphciBlbCBwcmVjaW8gcG9yIHpvbmEgYSBsbyBsYXJnbyBkZWwgdGllbXBvDQpnZ3Bsb3QoZGYsIGFlcyh4ID0gZmVjaGEsIHkgPSBwcmVjaW8sIGNvbG9yID0gem9uYSkpICsNCiAgZ2VvbV9saW5lKCkgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkV2b2x1Y2nDs24gZGVsIFByZWNpbyBwb3IgWm9uYSIsDQogICAgeCA9ICJGZWNoYSIsIHkgPSAiUHJlY2lvIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgVmlzdWFsaXphciBsYSBjYW50aWRhZCBwb3Igem9uYSBhIGxvIGxhcmdvIGRlbCB0aWVtcG8NCmdncGxvdChkZiwgYWVzKHggPSBmZWNoYSwgeSA9IGNhbnRpZGFkLCBjb2xvciA9IHpvbmEpKSArDQogIGdlb21fbGluZSgpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJFdm9sdWNpw7NuIGRlIGxhIENhbnRpZGFkIFZlbmRpZGEgcG9yIFpvbmEiLA0KICAgIHggPSAiRmVjaGEiLCB5ID0gIkNhbnRpZGFkIFZlbmRpZGEiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQojIyBjLiBJZGVudGlmaWNhY2nDs24gZGUgZXN0YWNpb25hbGlkYWQgYSBwYXJ0aXIgZGUgZGF0b3MgZGUgc2VyaWVzIGRlIHRpZW1wbyBkaWFyaW9zLiBFcyBkZWNpciwgwr9xdcOpIGTDrWFzIGRlIGxhIHNlbWFuYSBzZSBpbmNyZW1lbnRhIC8gZGlzbWludXllIGVsIG5pdmVsIGRlIHZlbnRhcz8gDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBBZ3J1cGFyIHkgY2FsY3VsYXIgbGEgY2FudGlkYWQgcHJvbWVkaW8gdmVuZGlkYSBwb3IgZMOtYSBkZSBsYSBzZW1hbmEgKHRvZGFzIGxhcyB6b25hcykNCmRmX2RpYSA8LSBkZiAlPiUNCiAgZ3JvdXBfYnkoZGlhX3NlbWFuYSkgJT4lDQogIHN1bW1hcmlzZShjYW50aWRhZF9wcm9tZWRpbyA9IG1lYW4oY2FudGlkYWQpKQ0KDQojIFZpc3VhbGl6YWRvcjogYmFycmFzIHBvciBkw61hIGRlIGxhIHNlbWFuYQ0KZ2dwbG90KGRmX2RpYSwgYWVzKHggPSBkaWFfc2VtYW5hLCB5ID0gY2FudGlkYWRfcHJvbWVkaW8pKSArDQogIGdlb21fY29sKGZpbGwgPSAic3RlZWxibHVlIikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIlByb21lZGlvIGRlIHZlbnRhcyBwb3IgZMOtYSBkZSBsYSBzZW1hbmEgKHRvZGFzIGxhcyB6b25hcykiLA0KICAgIHggPSAiRMOtYSBkZSBsYSBzZW1hbmEiLA0KICAgIHkgPSAiQ2FudGlkYWQgcHJvbWVkaW8gdmVuZGlkYSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCiMjIGQuIElkZW50aWZpY2FjacOzbiBkZSBlc3RhY2lvbmFsaWRhZCBhIG5pdmVsIHpvbmEgYSBwYXJ0aXIgZGUgZGF0b3MgZGUgc2VyaWVzIGRlIHRpZW1wbyBkaWFyaW9zLiBFcyBkZWNpciwgwr9xdcOpIGTDrWFzIGRlIGxhIHNlbWFuYSBzZSBpbmNyZW1lbnRhIC8gZGlzbWludXllIGVsIG5pdmVsIGRlIHZlbnRhcyBwYXJhIGNhZGEgdW5hIGRlIGxhcyB6b25hcz8NCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIENhbGN1bGFyIGNhbnRpZGFkIHByb21lZGlvIHBvciBkw61hIHkgem9uYQ0KZGZfZGlhX3pvbmEgPC0gZGYgJT4lDQogIGdyb3VwX2J5KHpvbmEsIGRpYV9zZW1hbmEpICU+JQ0KICBzdW1tYXJpc2UoY2FudGlkYWRfcHJvbWVkaW8gPSBtZWFuKGNhbnRpZGFkKSwgLmdyb3VwcyA9ICJkcm9wIikNCg0KIyBWaXN1YWxpemFkb3I6IGJhcnJhcyBwb3IgZMOtYSB5IHpvbmENCmdncGxvdChkZl9kaWFfem9uYSwgYWVzKHggPSBkaWFfc2VtYW5hLCB5ID0gY2FudGlkYWRfcHJvbWVkaW8sIGZpbGwgPSB6b25hKSkgKw0KICBnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJQcm9tZWRpbyBkZSB2ZW50YXMgcG9yIGTDrWEgZGUgbGEgc2VtYW5hIHBvciB6b25hIiwNCiAgICB4ID0gIkTDrWEgZGUgbGEgc2VtYW5hIiwNCiAgICB5ID0gIkNhbnRpZGFkIHByb21lZGlvIHZlbmRpZGEiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQojIyBlLiBBZ3J1cGFyIGxvcyBkYXRvcyBkZSBwcmVjaW8geSBjYW50aWRhZCBwb3Igc2VtYW5hIHBhcmEgY2FkYSB1bmEgZGUgbGFzIHpvbmFzLiANCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIEFncnVwYXIgcG9yIHNlbWFuYSB5IHpvbmENCmRmX3NlbWFuYWwgPC0gZGYgJT4lDQogIGdyb3VwX2J5KHpvbmEsIHNlbWFuYSkgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBjYW50aWRhZF90b3RhbCA9IHN1bShjYW50aWRhZCksDQogICAgcHJlY2lvX3Byb21lZGlvID0gbWVhbihwcmVjaW8pLA0KICAgIC5ncm91cHMgPSAiZHJvcCINCiAgKQ0KDQojIFZpc3VhbGl6YWRvcjogZXZvbHVjacOzbiBkZSBjYW50aWRhZCBzZW1hbmFsIHBvciB6b25hDQpnZ3Bsb3QoZGZfc2VtYW5hbCwgYWVzKHggPSBzZW1hbmEsIHkgPSBjYW50aWRhZF90b3RhbCwgY29sb3IgPSB6b25hKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDEpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJFdm9sdWNpw7NuIHNlbWFuYWwgZGUgY2FudGlkYWQgdmVuZGlkYSBwb3Igem9uYSIsDQogICAgeCA9ICJTZW1hbmEiLA0KICAgIHkgPSAiQ2FudGlkYWQgdG90YWwgdmVuZGlkYSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCiMgSUkuIE1vZGVsbyBkZSBEZW1hbmRhDQohW10oQzpcXFVzZXJzXFxEaWVnbyBQw6lyZXpcXERvd25sb2Fkc1xcV2hhdHNBcHAgSW1hZ2UgMjAyNS0wMy0yNSBhdCA4LjA4LjU4IEFNLmpwZWcpDQoNCiFbXShDOlxcVXNlcnNcXERpZWdvIFDDqXJlelxcRG93bmxvYWRzXFxXaGF0c0FwcCBJbWFnZSAyMDI1LTAzLTI1IGF0IDguMDkuMTMgQU0uanBlZykNCg0KIyMgYS4gVmlzdWFsaXphciBsYSByZWxhY2nDs24gZW50cmUgZWwgcHJlY2lvIHkgbGEgY2FudGlkYWQgZGUgdmVudGFzICANCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIEdyw6FmaWNvIGRlIGRpc3BlcnNpw7NuIGNvbiB0ZW5kZW5jaWENCmdncGxvdChkZiwgYWVzKHggPSBwcmVjaW8sIHkgPSBjYW50aWRhZCwgY29sb3IgPSB6b25hKSkgKw0KICBnZW9tX3BvaW50KGFscGhhID0gMC42KSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJSZWxhY2nDs24gZW50cmUgcHJlY2lvIHkgY2FudGlkYWQgdmVuZGlkYSIsDQogICAgeCA9ICJQcmVjaW8iLA0KICAgIHkgPSAiQ2FudGlkYWQgdmVuZGlkYSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCiMjIGIuIEVzdGltYXIgZWwgbW9kZWxvIGRlIGRlbWFuZGEgY29uc2lkZXJhbmRvIGNhZGEgdW5hIGRlIGxhcyB6b25hcy4gDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBNb2RlbG8gbGluZWFsIHBvciB6b25hDQpnZ3Bsb3QoZGYsIGFlcyh4ID0gcHJlY2lvLCB5ID0gY2FudGlkYWQsIGNvbG9yID0gem9uYSkpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFKSArDQogIGZhY2V0X3dyYXAofiB6b25hKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTW9kZWxvcyBkZSBkZW1hbmRhIHBvciB6b25hIChyZWdyZXNpw7NuIGxpbmVhbCkiLA0KICAgIHggPSAiUHJlY2lvIiwNCiAgICB5ID0gIkNhbnRpZGFkIHZlbmRpZGEiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCm1vZGVsb3MgPC0gZGYgJT4lDQogIGdyb3VwX2J5KHpvbmEpICU+JQ0KICBncm91cF9tYXAofiBsbShjYW50aWRhZCB+IHByZWNpbywgZGF0YSA9IC54KSwgLmtlZXAgPSBUUlVFKQ0KDQpzdW1tYXJ5KG1vZGVsb3NbWzFdXSkNCmBgYA0KDQojIElJSS4gUHJlY2lvIEVsYXN0aWNpZGFkDQoNCiMjIGEuIENhbGN1bGFyIGVsIHByZWNpbyBlbGFzdGljaWRhZCBwYXJhIGNhZGEgdW5hIGRlIGxhcyB6b25hcy4gDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBDYWxjdWxhciBlbGFzdGljaWRhZCBwb3Igem9uYQ0KZWxhc3RpY2lkYWRlcyA8LSBkZiAlPiUNCiAgZ3JvdXBfYnkoem9uYSkgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBtb2RlbG8gPSBsaXN0KGxtKGNhbnRpZGFkIH4gcHJlY2lvLCBkYXRhID0gY3VyX2RhdGEoKSkpLA0KICAgIHByZWNpb19wcm9tID0gbWVhbihwcmVjaW8pLA0KICAgIGNhbnRpZGFkX3Byb20gPSBtZWFuKGNhbnRpZGFkKQ0KICApICU+JQ0KICBtdXRhdGUoDQogICAgcGVuZGllbnRlID0gbWFwX2RibChtb2RlbG8sIH4gY29lZigueClbWyJwcmVjaW8iXV0pLA0KICAgIGVsYXN0aWNpZGFkID0gcGVuZGllbnRlICogKHByZWNpb19wcm9tIC8gY2FudGlkYWRfcHJvbSkNCiAgKSAlPiUNCiAgc2VsZWN0KHpvbmEsIHBlbmRpZW50ZSwgcHJlY2lvX3Byb20sIGNhbnRpZGFkX3Byb20sIGVsYXN0aWNpZGFkKQ0KDQpwcmludChlbGFzdGljaWRhZGVzKQ0KYGBgDQoNCiMjIGIuIFZpc3VhbGl6YXIgbGEgZWxhc3RpY2lkYWQgcHJlY2lvIGRlIGxhIGRlbWFuYWRhIHBhcmEgY2FkYSB1bmEgZGUgbGFzIHpvbmFzLg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgVmlzdWFsaXphciBjb21vIGdyw6FmaWNvIGRlIGJhcnJhcw0KZ2dwbG90KGVsYXN0aWNpZGFkZXMsIGFlcyh4ID0gem9uYSwgeSA9IGVsYXN0aWNpZGFkLCBmaWxsID0gem9uYSkpICsNCiAgZ2VvbV9jb2woKSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChlbGFzdGljaWRhZCwgMikpLCB2anVzdCA9IC0wLjUpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJFbGFzdGljaWRhZCBwcmVjaW8gZGUgbGEgZGVtYW5kYSBwb3Igem9uYSIsDQogICAgeCA9ICJab25hIiwNCiAgICB5ID0gIkVsYXN0aWNpZGFkIGVzdGltYWRhIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KIVtdKEM6XFxVc2Vyc1xcRGllZ28gUMOpcmV6XFxEb3dubG9hZHNcXFdoYXRzQXBwIEltYWdlIDIwMjUtMDMtMjUgYXQgOC4wOS4yNiBBTS5qcGVnKQ0KDQojIElWLiAgUHJlY2lvcyAmIEluZ3Jlc29zIMOTcHRpbW9zIA0KDQojIyBhLiBDYWxjdWxhciBsb3MgcHJlY2lvcyBlIGluZ3Jlc29zIMOzcHRpbW9zIHBhcmEgY2FkYSB1bmEgZGUgbGFzIHpvbmFzLg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmdldF9wcmVjaW9fb3B0aW1vIDwtIGZ1bmN0aW9uKG1vZGVsbywgem9uYSkgew0KICBjb2VmIDwtIGNvZWYobW9kZWxvKQ0KICBhIDwtIGNvZWZbMV0NCiAgYiA8LSBjb2VmWzJdDQogIA0KICBwcmVjaW9fb3B0aW1vIDwtIC1hIC8gKDIgKiBiKQ0KICANCiAgY2F0KCJab25hOiIsIHpvbmEsICJcbiIpDQogIGNhdCgiTW9kZWxvOiBjYW50aWRhZCA9Iiwgcm91bmQoYSwgMiksICIrIiwgcm91bmQoYiwgMiksICIqIHByZWNpb1xuIikNCiAgY2F0KCJQcmVjaW8gw7NwdGltbzoiLCByb3VuZChwcmVjaW9fb3B0aW1vLCAyKSwgIlxuXG4iKQ0KICANCiAgcmV0dXJuKHByZWNpb19vcHRpbW8pDQp9DQoNCiMgT2J0ZW5lciB6b25hcw0Kem9uYXMgPC0gdW5pcXVlKGRmJHpvbmEpDQoNCiMgQ2FsY3VsYXIgcHJlY2lvIMOzcHRpbW8gcG9yIHpvbmENCnByZWNpb3Nfb3B0aW1vcyA8LSBtYXBwbHkoZ2V0X3ByZWNpb19vcHRpbW8sIG1vZGVsb3MsIHpvbmFzKQ0KYGBgDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBDcmVhciB1bmEgc2VjdWVuY2lhIGRlIHByZWNpb3Mgc2ltdWxhZG9zDQpwcmVjaW9zX3NpbXVsYWRvcyA8LSBzZXEoMSwgMjUsIGxlbmd0aC5vdXQgPSAyMDApIA0KDQojIENhbGN1bGFyIGluZ3Jlc29zIGVzdGltYWRvcyBwb3Igem9uYSB1c2FuZG8gZWwgbW9kZWxvIGxpbmVhbA0Kc2ltdWxhcl9pbmdyZXNvcyA8LSBmdW5jdGlvbihtb2RlbG8sIHpvbmEpIHsNCiAgY29lZnMgPC0gY29lZihtb2RlbG8pDQogIGEgPC0gY29lZnNbMV0NCiAgYiA8LSBjb2Vmc1syXQ0KICBjYW50aWRhZF9lc3RpbWFkYSA8LSBhICsgYiAqIHByZWNpb3Nfc2ltdWxhZG9zDQogIGluZ3Jlc29fZXN0aW1hZG8gPC0gcHJlY2lvc19zaW11bGFkb3MgKiBjYW50aWRhZF9lc3RpbWFkYQ0KICANCiAgZGF0YS5mcmFtZSgNCiAgICB6b25hID0gem9uYSwNCiAgICBwcmVjaW8gPSBwcmVjaW9zX3NpbXVsYWRvcywNCiAgICBpbmdyZXNvID0gaW5ncmVzb19lc3RpbWFkbywgICMgPC0gQ09SUkVHSURPDQogICAgcHJlY2lvX29wdGltbyA9IC1hIC8gKDIgKiBiKQ0KICApDQp9DQoNCiMgQXBsaWNhciBhIGNhZGEgem9uYQ0KZGZfaW5ncmVzb3Nfc2ltdWxhZG9zIDwtIHB1cnJyOjptYXAyX2Rmcihtb2RlbG9zLCB6b25hcywgc2ltdWxhcl9pbmdyZXNvcykNCg0KIyBHcmFmaWNhcg0KZ2dwbG90KGRmX2luZ3Jlc29zX3NpbXVsYWRvcywgYWVzKHggPSBwcmVjaW8sIHkgPSBpbmdyZXNvKSkgKw0KICBnZW9tX2xpbmUoYWVzKGNvbG9yID0gem9uYSksIHNpemUgPSAxLjIpICsNCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdCA9IHByZWNpb19vcHRpbW8sIGNvbG9yID0gem9uYSksIGxpbmV0eXBlID0gImRhc2hlZCIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJJbmdyZXNvIGVzdGltYWRvIHZzIFByZWNpbyBwb3Igem9uYSIsDQogICAgc3VidGl0bGUgPSAiTMOtbmVhIHB1bnRlYWRhIGluZGljYSBlbCBwcmVjaW8gw7NwdGltbyIsDQogICAgeCA9ICJQcmVjaW8iLA0KICAgIHkgPSAiSW5ncmVzbyBlc3RpbWFkbyINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIGV4cGFuZF9saW1pdHMoeCA9IGMoMCwgMjUpKQ0KYGBgDQoNCiFbXShDOlxcVXNlcnNcXERpZWdvIFDDqXJlelxcRG93bmxvYWRzXFxXaGF0c0FwcCBJbWFnZSAyMDI1LTAzLTI1IGF0IDguMDkuNDAgQU0uanBlZykgDQoNCiMgVi4gUmVzcG9uZGVyIGJyZXZlbWVudGUgbGFzIHNpZ3VpZW50ZXMgcHJlZ3VudGFzIGRlIGFuw6FsaXNpcyANCg0KIyMgYS4gwr9RdcOpIGVzIG9wdGltaXphY2nDs24gZGUgcHJlY2lvcz8gIA0KTGEgb2J0ZW5jacOzbiBkZWwgInByZWNpbyBpZGVhbCIgcXVlIHBlcm1pdGEgbWF4aW1pemFyIGxvcyBpbmdyZXNvcy4gQnVzY2FuZG8gb2J0ZW5lciB1biBlcXVpbGlicmlvIGVudHJlIGxhIG1heW9yIGNhbnRpZGFkIGRlIHVuaWRhZGVzIHZlbmRpZGFzIHkgbGEgb2J0ZW5jacOzbiBkZSBnYW5hbmNpYXMuDQoNCiMjIGIuIMK/Q3XDoWwgZXMgbGEgcmVsYWNpw7NuIGVudHJlIHByZWNpbyBlbGFzdGljaWRhZCB5IG9wdGltaXphY2nDs24gZGUgcHJlY2lvcz8gICANCkxhIHJlbGFjacOzbiBub3MgYXl1ZGEgYSBkZXRlcm1pbmFyIHF1ZSB0YW50byBjYW1iaWEgbGEgY2FudGlkYWQgdmVuZGlkYSBhbCBtb2RpZmljYXIgZWwgcHJlY2lvIGRlbCBwcm9kdWN0by4NCg0KIyMgYy4gwr9DdcOhbCBlcyBlbCBwcm9ww7NzaXRvIGRlIGxhIG9wdGltaXphY2nDs24gZGUgcHJlY2lvcyBwb3Igem9uYT8NCkxhIGFkYXB0YWNpw7NuIGRlbCBwcmVjaW8gYSBsYXMgY2FyYWN0ZXJpc3RpY2FzIGRlIGVsYXN0aWNpZGFkIGRlIGNhZGEgcmVnacOzbi4NCg0K