Libraries

library(tidyverse)
library(kableExtra)

Dataset

## ---- helpers --------------------------------------------------------
big <- function(x, mark = ".") ifelse(is.na(x), "–", formatC(x, format = "d", big.mark = mark))
chg <- function(x, d = 1) ifelse(is.na(x), "–", sprintf(paste0("%+.", d, "f%%"), x * 100))

sty <- function(x, cap, align)
  kbl(x, caption = cap, align = align, row.names = FALSE, booktabs = TRUE) |>
    kable_styling(full_width        = FALSE, position = "left",
                  bootstrap_options = c("striped", "hover", "condensed"),
                  latex_options     = c("striped", "hold_position")) |>
    column_spec(1, bold = TRUE)

## ---- data -----------------------------------------------------------
media_rates <- data.frame(
  channel = c("TV", "Print", "OOH", "Digital", "Social"),
  `2023`  = c(44350, 36420171, 59390260, 2341, 3986),
  `2024`  = c(45767, 37972992, 61251834, 2600, 3682),
  `2025`  = c(46519, 34194301, 59104413, 2990, 5003),
  `2026`  = c(50832,  3310887, 58615144, 3640, 4897),
  check.names = FALSE, stringsAsFactors = FALSE
)

yrs <- c("2023", "2024", "2025", "2026")

## ---- table ----------------------------------------------------------
data.frame(Channel = media_rates$channel,
           lapply(media_rates[yrs], big),
           check.names = FALSE, stringsAsFactors = FALSE) |>
  sty("Media rate card, 2023–2026 (Rp)", c("l", rep("r", 4))) |>
  column_spec(2:5, width = "7em", monospace = TRUE)
Media rate card, 2023–2026 (Rp)
Channel 2023 2024 2025 2026
TV 44.350 45.767 46.519 50.832
Print 36.420.171 37.972.992 34.194.301 3.310.887
OOH 59.390.260 61.251.834 59.104.413 58.615.144
Digital 2.341 2.600 2.990 3.640
Social 3.986 3.682 5.003 4.897

Data Visualization

## ---- long format ----------------------------------------------------
rates_long <- media_rates |>
  pivot_longer(-channel, names_to = "year", values_to = "rate") |>
  mutate(year    = as.integer(year),
         channel = factor(channel, levels = media_rates$channel))

## ---- shared plot layers ---------------------------------------------
obs <- list(geom_line(colour = "#2c6fbb", linewidth = 0.8),
            geom_point(colour = "#2c6fbb", size = 2))

facet_y <- function(expand = expansion(mult = c(0.05, 0.05)))
  list(facet_wrap(~ channel, nrow = 2, scales = "free_y"),
       scale_y_continuous(
         labels = scales::label_number(scale_cut = scales::cut_short_scale()),
         expand = expand),
       theme_minimal(base_size = 11),
       theme(panel.grid.minor = element_blank(),
             strip.text       = element_text(face = "bold")))

ggplot(rates_long, aes(year, rate)) +
  obs +
  scale_x_continuous(breaks = 2023:2026) +
  facet_y(expansion(mult = c(0.18, 0.18))) +
  labs(title = "Media rate card by channel, 2023–2026", x = NULL, y = "Rate (Rp)")

Regression Model

Regression with trend

h         <- 2027                                  # projection horizon
pred_grid <- tibble(year = seq(2023, h, by = 0.05))

fmt      <- scales::label_number(scale_cut = scales::cut_short_scale(), accuracy = 0.1)
num_full <- function(x, d = 15)
  vapply(x, function(z) format(z, digits = d, scientific = FALSE, trim = TRUE),
         character(1))

## ---- the model, defined once ----------------------------------------
fit_lm <- function(df) lm(rate ~ I(year - 2023), data = df)

fit_one <- function(df, newdata) {
  m  <- fit_lm(df)
  ci <- predict(m, newdata = newdata, interval = "confidence")
  pi <- predict(m, newdata = newdata, interval = "prediction")
  tibble(year   = newdata$year, fit = ci[, "fit"],
         ci_lwr = ci[, "lwr"], ci_upr = ci[, "upr"],
         pi_lwr = pi[, "lwr"], pi_upr = pi[, "upr"])
}

fits <- rates_long |>
  group_by(channel) |>
  group_modify(~ fit_one(.x, pred_grid)) |>
  ungroup()

models <- rates_long |>
  group_by(channel) |>
  group_modify(~ {
    m <- fit_lm(.x)
    tibble(b0 = coef(m)[[1]], b1 = coef(m)[[2]], r2 = summary(m)$r.squared)
  }) |>
  ungroup() |>
  mutate(sgn   = ifelse(b1 < 0, "\u2212", "+"),
         label = sprintf("\u0177 = %s %s %s\u00B7(year \u2212 2023)\nR\u00B2 = %.2f",
                         fmt(b0), sgn, fmt(abs(b1)), r2),
         eq    = sprintf("\u0177 = %s %s %s \u00B7 (year \u2212 2023)",
                         num_full(b0), sgn, num_full(abs(b1))))

## ---- CI/PI band layers ----------------------------------------------
bands <- function(d) list(
  geom_ribbon(data = d, aes(x = year, ymin = pi_lwr, ymax = pi_upr),
              inherit.aes = FALSE, fill = "#c0392b", alpha = 0.05),
  geom_ribbon(data = d, aes(x = year, ymin = ci_lwr, ymax = ci_upr),
              inherit.aes = FALSE, fill = "#c0392b", alpha = 0.12),
  geom_line(data = d, aes(x = year, y = fit), inherit.aes = FALSE,
            colour = "#c0392b", linewidth = 0.7, linetype = "22"))

ggplot(rates_long, aes(year, rate)) +
  bands(filter(fits, year <= 2026)) +
  obs +
  geom_text(data = models, aes(label = label), x = -Inf, y = Inf,
            hjust = -0.05, vjust = 1.25, size = 2.8, lineheight = 1.05,
            colour = "#c0392b", inherit.aes = FALSE) +
  scale_x_continuous(breaks = 2023:2026) +
  facet_y(expansion(mult = c(0.10, 0.40))) +
  labs(title    = "OLS fit with 95% confidence and prediction intervals",
       subtitle = "Dark band: mean response (CI) \u00B7 Light band: new observation (PI)",
       x = NULL, y = "Rate (Rp)")

models |>
  transmute(Channel = channel, Equation = eq) |>
  sty("Fitted model per channel (rate ~ year, n = 4)", c("l", "l")) |>
  column_spec(2, monospace = TRUE, extra_css = "white-space: nowrap;")
Fitted model per channel (rate ~ year, n = 4)
Channel Equation
TV ŷ = 43837.3 + 2019.8 · (year − 2023)
Print ŷ = 43440569.2 − 10310654.3 · (year − 2023)
OOH ŷ = 60261328.1 − 447276.899999999 · (year − 2023)
Digital ŷ = 2249.7 + 428.7 · (year − 2023)
Social ŷ = 3783.9 + 405.4 · (year − 2023)

Projection for year 2027

pred_h <- filter(fits, year == max(year))

ggplot(rates_long, aes(year, rate)) +
  geom_vline(xintercept = h - 0.5, linetype = "dotted", colour = "grey45") +
  bands(fits) +
  obs +
  geom_point(data = pred_h, aes(x = year, y = fit), inherit.aes = FALSE,
             shape = 21, size = 2.4, stroke = 0.9,
             colour = "#c0392b", fill = "white") +
  scale_x_continuous(breaks = 2023:h) +
  facet_y() +
  labs(title    = sprintf("Media rates with %d projection", h),
       subtitle = "Right of the dotted line is extrapolated \u00B7 hollow point = projection",
       x = NULL, y = "Rate (Rp)")

pred_h |>
  left_join(media_rates |> select(channel, last = `2026`), by = "channel") |>
  transmute(
    Channel  = channel,
    `2026`   = big(round(last)),
    `2027`   = big(round(fit)),
    Change   = chg(fit / last - 1),
    `95% CI` = sprintf("%s \u2013 %s", big(round(ci_lwr)), big(round(ci_upr))),
    `95% PI` = sprintf("%s \u2013 %s", big(round(pi_lwr)), big(round(pi_upr)))) |>
  sty("2027 projection from the linear trend (Rp)", c("l", rep("r", 5))) |>
  column_spec(2:4, monospace = TRUE)
2027 projection from the linear trend (Rp)
Channel 2026 2027 Change 95% CI 95% PI
TV 50.832 51.916 +2.1% 45.474 – 58.359 43.599 – 60.234
Print 3.310.887 2.197.952 -33.6% -60.898.397 – 65.294.301 -79.259.084 – 83.654.988
OOH 58.615.144 58.472.220 -0.2% 52.031.762 – 64.912.679 50.157.625 – 66.786.816
Digital 3.640 3.964 +8.9% 3.228 – 4.701 3.014 – 4.915
Social 4.897 5.405 +10.4% 2.836 – 7.975 2.088 – 8.723
LS0tCnRpdGxlOiAiTWVkaWEgSW5mbGF0aW9uIFByb2plY3Rpb24iCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6CiAgICAgIHRvY19jb2xsYXBzZWQ6IHRydWUKICAgIG1hdGhfbWV0aG9kOiBrYXRleAotLS0KCiMjIExpYnJhcmllcwoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGthYmxlRXh0cmEpCmBgYAoKCiMjIERhdGFzZXQKCmBgYHtyIHdhcm5pbmc9RkFMU0V9CiMjIC0tLS0gaGVscGVycyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpiaWcgPC0gZnVuY3Rpb24oeCwgbWFyayA9ICIuIikgaWZlbHNlKGlzLm5hKHgpLCAi4oCTIiwgZm9ybWF0Qyh4LCBmb3JtYXQgPSAiZCIsIGJpZy5tYXJrID0gbWFyaykpCmNoZyA8LSBmdW5jdGlvbih4LCBkID0gMSkgaWZlbHNlKGlzLm5hKHgpLCAi4oCTIiwgc3ByaW50ZihwYXN0ZTAoIiUrLiIsIGQsICJmJSUiKSwgeCAqIDEwMCkpCgpzdHkgPC0gZnVuY3Rpb24oeCwgY2FwLCBhbGlnbikKICBrYmwoeCwgY2FwdGlvbiA9IGNhcCwgYWxpZ24gPSBhbGlnbiwgcm93Lm5hbWVzID0gRkFMU0UsIGJvb2t0YWJzID0gVFJVRSkgfD4KICAgIGthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCAgICAgICAgPSBGQUxTRSwgcG9zaXRpb24gPSAibGVmdCIsCiAgICAgICAgICAgICAgICAgIGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSwKICAgICAgICAgICAgICAgICAgbGF0ZXhfb3B0aW9ucyAgICAgPSBjKCJzdHJpcGVkIiwgImhvbGRfcG9zaXRpb24iKSkgfD4KICAgIGNvbHVtbl9zcGVjKDEsIGJvbGQgPSBUUlVFKQoKIyMgLS0tLSBkYXRhIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCm1lZGlhX3JhdGVzIDwtIGRhdGEuZnJhbWUoCiAgY2hhbm5lbCA9IGMoIlRWIiwgIlByaW50IiwgIk9PSCIsICJEaWdpdGFsIiwgIlNvY2lhbCIpLAogIGAyMDIzYCAgPSBjKDQ0MzUwLCAzNjQyMDE3MSwgNTkzOTAyNjAsIDIzNDEsIDM5ODYpLAogIGAyMDI0YCAgPSBjKDQ1NzY3LCAzNzk3Mjk5MiwgNjEyNTE4MzQsIDI2MDAsIDM2ODIpLAogIGAyMDI1YCAgPSBjKDQ2NTE5LCAzNDE5NDMwMSwgNTkxMDQ0MTMsIDI5OTAsIDUwMDMpLAogIGAyMDI2YCAgPSBjKDUwODMyLCAgMzMxMDg4NywgNTg2MTUxNDQsIDM2NDAsIDQ4OTcpLAogIGNoZWNrLm5hbWVzID0gRkFMU0UsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQopCgp5cnMgPC0gYygiMjAyMyIsICIyMDI0IiwgIjIwMjUiLCAiMjAyNiIpCgojIyAtLS0tIHRhYmxlIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KZGF0YS5mcmFtZShDaGFubmVsID0gbWVkaWFfcmF0ZXMkY2hhbm5lbCwKICAgICAgICAgICBsYXBwbHkobWVkaWFfcmF0ZXNbeXJzXSwgYmlnKSwKICAgICAgICAgICBjaGVjay5uYW1lcyA9IEZBTFNFLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpIHw+CiAgc3R5KCJNZWRpYSByYXRlIGNhcmQsIDIwMjPigJMyMDI2IChScCkiLCBjKCJsIiwgcmVwKCJyIiwgNCkpKSB8PgogIGNvbHVtbl9zcGVjKDI6NSwgd2lkdGggPSAiN2VtIiwgbW9ub3NwYWNlID0gVFJVRSkKYGBgCgoKIyMgRGF0YSBWaXN1YWxpemF0aW9uCgpgYGB7ciBmaWcud2lkdGggPSA5LCBmaWcuaGVpZ2h0ID0gNSwgd2FybmluZyA9IEZBTFNFfQojIyAtLS0tIGxvbmcgZm9ybWF0IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KcmF0ZXNfbG9uZyA8LSBtZWRpYV9yYXRlcyB8PgogIHBpdm90X2xvbmdlcigtY2hhbm5lbCwgbmFtZXNfdG8gPSAieWVhciIsIHZhbHVlc190byA9ICJyYXRlIikgfD4KICBtdXRhdGUoeWVhciAgICA9IGFzLmludGVnZXIoeWVhciksCiAgICAgICAgIGNoYW5uZWwgPSBmYWN0b3IoY2hhbm5lbCwgbGV2ZWxzID0gbWVkaWFfcmF0ZXMkY2hhbm5lbCkpCgojIyAtLS0tIHNoYXJlZCBwbG90IGxheWVycyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0Kb2JzIDwtIGxpc3QoZ2VvbV9saW5lKGNvbG91ciA9ICIjMmM2ZmJiIiwgbGluZXdpZHRoID0gMC44KSwKICAgICAgICAgICAgZ2VvbV9wb2ludChjb2xvdXIgPSAiIzJjNmZiYiIsIHNpemUgPSAyKSkKCmZhY2V0X3kgPC0gZnVuY3Rpb24oZXhwYW5kID0gZXhwYW5zaW9uKG11bHQgPSBjKDAuMDUsIDAuMDUpKSkKICBsaXN0KGZhY2V0X3dyYXAofiBjaGFubmVsLCBucm93ID0gMiwgc2NhbGVzID0gImZyZWVfeSIpLAogICAgICAgc2NhbGVfeV9jb250aW51b3VzKAogICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OmxhYmVsX251bWJlcihzY2FsZV9jdXQgPSBzY2FsZXM6OmN1dF9zaG9ydF9zY2FsZSgpKSwKICAgICAgICAgZXhwYW5kID0gZXhwYW5kKSwKICAgICAgIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTEpLAogICAgICAgdGhlbWUocGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgIHN0cmlwLnRleHQgICAgICAgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIikpKQoKZ2dwbG90KHJhdGVzX2xvbmcsIGFlcyh5ZWFyLCByYXRlKSkgKwogIG9icyArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IDIwMjM6MjAyNikgKwogIGZhY2V0X3koZXhwYW5zaW9uKG11bHQgPSBjKDAuMTgsIDAuMTgpKSkgKwogIGxhYnModGl0bGUgPSAiTWVkaWEgcmF0ZSBjYXJkIGJ5IGNoYW5uZWwsIDIwMjPigJMyMDI2IiwgeCA9IE5VTEwsIHkgPSAiUmF0ZSAoUnApIikKYGBgCgojIyBSZWdyZXNzaW9uIE1vZGVsCgojIyMgUmVncmVzc2lvbiB3aXRoIHRyZW5kCgpgYGB7ciBmaWcud2lkdGggPSA5LCBmaWcuaGVpZ2h0ID0gNS41LCB3YXJuaW5nID0gRkFMU0V9CmggICAgICAgICA8LSAyMDI3ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgcHJvamVjdGlvbiBob3Jpem9uCnByZWRfZ3JpZCA8LSB0aWJibGUoeWVhciA9IHNlcSgyMDIzLCBoLCBieSA9IDAuMDUpKQoKZm10ICAgICAgPC0gc2NhbGVzOjpsYWJlbF9udW1iZXIoc2NhbGVfY3V0ID0gc2NhbGVzOjpjdXRfc2hvcnRfc2NhbGUoKSwgYWNjdXJhY3kgPSAwLjEpCm51bV9mdWxsIDwtIGZ1bmN0aW9uKHgsIGQgPSAxNSkKICB2YXBwbHkoeCwgZnVuY3Rpb24oeikgZm9ybWF0KHosIGRpZ2l0cyA9IGQsIHNjaWVudGlmaWMgPSBGQUxTRSwgdHJpbSA9IFRSVUUpLAogICAgICAgICBjaGFyYWN0ZXIoMSkpCgojIyAtLS0tIHRoZSBtb2RlbCwgZGVmaW5lZCBvbmNlIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KZml0X2xtIDwtIGZ1bmN0aW9uKGRmKSBsbShyYXRlIH4gSSh5ZWFyIC0gMjAyMyksIGRhdGEgPSBkZikKCmZpdF9vbmUgPC0gZnVuY3Rpb24oZGYsIG5ld2RhdGEpIHsKICBtICA8LSBmaXRfbG0oZGYpCiAgY2kgPC0gcHJlZGljdChtLCBuZXdkYXRhID0gbmV3ZGF0YSwgaW50ZXJ2YWwgPSAiY29uZmlkZW5jZSIpCiAgcGkgPC0gcHJlZGljdChtLCBuZXdkYXRhID0gbmV3ZGF0YSwgaW50ZXJ2YWwgPSAicHJlZGljdGlvbiIpCiAgdGliYmxlKHllYXIgICA9IG5ld2RhdGEkeWVhciwgZml0ID0gY2lbLCAiZml0Il0sCiAgICAgICAgIGNpX2x3ciA9IGNpWywgImx3ciJdLCBjaV91cHIgPSBjaVssICJ1cHIiXSwKICAgICAgICAgcGlfbHdyID0gcGlbLCAibHdyIl0sIHBpX3VwciA9IHBpWywgInVwciJdKQp9CgpmaXRzIDwtIHJhdGVzX2xvbmcgfD4KICBncm91cF9ieShjaGFubmVsKSB8PgogIGdyb3VwX21vZGlmeSh+IGZpdF9vbmUoLngsIHByZWRfZ3JpZCkpIHw+CiAgdW5ncm91cCgpCgptb2RlbHMgPC0gcmF0ZXNfbG9uZyB8PgogIGdyb3VwX2J5KGNoYW5uZWwpIHw+CiAgZ3JvdXBfbW9kaWZ5KH4gewogICAgbSA8LSBmaXRfbG0oLngpCiAgICB0aWJibGUoYjAgPSBjb2VmKG0pW1sxXV0sIGIxID0gY29lZihtKVtbMl1dLCByMiA9IHN1bW1hcnkobSkkci5zcXVhcmVkKQogIH0pIHw+CiAgdW5ncm91cCgpIHw+CiAgbXV0YXRlKHNnbiAgID0gaWZlbHNlKGIxIDwgMCwgIlx1MjIxMiIsICIrIiksCiAgICAgICAgIGxhYmVsID0gc3ByaW50ZigiXHUwMTc3ID0gJXMgJXMgJXNcdTAwQjcoeWVhciBcdTIyMTIgMjAyMylcblJcdTAwQjIgPSAlLjJmIiwKICAgICAgICAgICAgICAgICAgICAgICAgIGZtdChiMCksIHNnbiwgZm10KGFicyhiMSkpLCByMiksCiAgICAgICAgIGVxICAgID0gc3ByaW50ZigiXHUwMTc3ID0gJXMgJXMgJXMgXHUwMEI3ICh5ZWFyIFx1MjIxMiAyMDIzKSIsCiAgICAgICAgICAgICAgICAgICAgICAgICBudW1fZnVsbChiMCksIHNnbiwgbnVtX2Z1bGwoYWJzKGIxKSkpKQoKIyMgLS0tLSBDSS9QSSBiYW5kIGxheWVycyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCmJhbmRzIDwtIGZ1bmN0aW9uKGQpIGxpc3QoCiAgZ2VvbV9yaWJib24oZGF0YSA9IGQsIGFlcyh4ID0geWVhciwgeW1pbiA9IHBpX2x3ciwgeW1heCA9IHBpX3VwciksCiAgICAgICAgICAgICAgaW5oZXJpdC5hZXMgPSBGQUxTRSwgZmlsbCA9ICIjYzAzOTJiIiwgYWxwaGEgPSAwLjA1KSwKICBnZW9tX3JpYmJvbihkYXRhID0gZCwgYWVzKHggPSB5ZWFyLCB5bWluID0gY2lfbHdyLCB5bWF4ID0gY2lfdXByKSwKICAgICAgICAgICAgICBpbmhlcml0LmFlcyA9IEZBTFNFLCBmaWxsID0gIiNjMDM5MmIiLCBhbHBoYSA9IDAuMTIpLAogIGdlb21fbGluZShkYXRhID0gZCwgYWVzKHggPSB5ZWFyLCB5ID0gZml0KSwgaW5oZXJpdC5hZXMgPSBGQUxTRSwKICAgICAgICAgICAgY29sb3VyID0gIiNjMDM5MmIiLCBsaW5ld2lkdGggPSAwLjcsIGxpbmV0eXBlID0gIjIyIikpCgpnZ3Bsb3QocmF0ZXNfbG9uZywgYWVzKHllYXIsIHJhdGUpKSArCiAgYmFuZHMoZmlsdGVyKGZpdHMsIHllYXIgPD0gMjAyNikpICsKICBvYnMgKwogIGdlb21fdGV4dChkYXRhID0gbW9kZWxzLCBhZXMobGFiZWwgPSBsYWJlbCksIHggPSAtSW5mLCB5ID0gSW5mLAogICAgICAgICAgICBoanVzdCA9IC0wLjA1LCB2anVzdCA9IDEuMjUsIHNpemUgPSAyLjgsIGxpbmVoZWlnaHQgPSAxLjA1LAogICAgICAgICAgICBjb2xvdXIgPSAiI2MwMzkyYiIsIGluaGVyaXQuYWVzID0gRkFMU0UpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gMjAyMzoyMDI2KSArCiAgZmFjZXRfeShleHBhbnNpb24obXVsdCA9IGMoMC4xMCwgMC40MCkpKSArCiAgbGFicyh0aXRsZSAgICA9ICJPTFMgZml0IHdpdGggOTUlIGNvbmZpZGVuY2UgYW5kIHByZWRpY3Rpb24gaW50ZXJ2YWxzIiwKICAgICAgIHN1YnRpdGxlID0gIkRhcmsgYmFuZDogbWVhbiByZXNwb25zZSAoQ0kpIFx1MDBCNyBMaWdodCBiYW5kOiBuZXcgb2JzZXJ2YXRpb24gKFBJKSIsCiAgICAgICB4ID0gTlVMTCwgeSA9ICJSYXRlIChScCkiKQpgYGAKCmBgYHtyfQptb2RlbHMgfD4KICB0cmFuc211dGUoQ2hhbm5lbCA9IGNoYW5uZWwsIEVxdWF0aW9uID0gZXEpIHw+CiAgc3R5KCJGaXR0ZWQgbW9kZWwgcGVyIGNoYW5uZWwgKHJhdGUgfiB5ZWFyLCBuID0gNCkiLCBjKCJsIiwgImwiKSkgfD4KICBjb2x1bW5fc3BlYygyLCBtb25vc3BhY2UgPSBUUlVFLCBleHRyYV9jc3MgPSAid2hpdGUtc3BhY2U6IG5vd3JhcDsiKQpgYGAKCiMjIyBQcm9qZWN0aW9uIGZvciB5ZWFyIDIwMjcKCmBgYHtyIGZpZy53aWR0aCA9IDksIGZpZy5oZWlnaHQgPSA1LjUsIHdhcm5pbmcgPSBGQUxTRX0KcHJlZF9oIDwtIGZpbHRlcihmaXRzLCB5ZWFyID09IG1heCh5ZWFyKSkKCmdncGxvdChyYXRlc19sb25nLCBhZXMoeWVhciwgcmF0ZSkpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBoIC0gMC41LCBsaW5ldHlwZSA9ICJkb3R0ZWQiLCBjb2xvdXIgPSAiZ3JleTQ1IikgKwogIGJhbmRzKGZpdHMpICsKICBvYnMgKwogIGdlb21fcG9pbnQoZGF0YSA9IHByZWRfaCwgYWVzKHggPSB5ZWFyLCB5ID0gZml0KSwgaW5oZXJpdC5hZXMgPSBGQUxTRSwKICAgICAgICAgICAgIHNoYXBlID0gMjEsIHNpemUgPSAyLjQsIHN0cm9rZSA9IDAuOSwKICAgICAgICAgICAgIGNvbG91ciA9ICIjYzAzOTJiIiwgZmlsbCA9ICJ3aGl0ZSIpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gMjAyMzpoKSArCiAgZmFjZXRfeSgpICsKICBsYWJzKHRpdGxlICAgID0gc3ByaW50ZigiTWVkaWEgcmF0ZXMgd2l0aCAlZCBwcm9qZWN0aW9uIiwgaCksCiAgICAgICBzdWJ0aXRsZSA9ICJSaWdodCBvZiB0aGUgZG90dGVkIGxpbmUgaXMgZXh0cmFwb2xhdGVkIFx1MDBCNyBob2xsb3cgcG9pbnQgPSBwcm9qZWN0aW9uIiwKICAgICAgIHggPSBOVUxMLCB5ID0gIlJhdGUgKFJwKSIpCmBgYAoKYGBge3Igd2FybmluZz1GQUxTRX0KcHJlZF9oIHw+CiAgbGVmdF9qb2luKG1lZGlhX3JhdGVzIHw+IHNlbGVjdChjaGFubmVsLCBsYXN0ID0gYDIwMjZgKSwgYnkgPSAiY2hhbm5lbCIpIHw+CiAgdHJhbnNtdXRlKAogICAgQ2hhbm5lbCAgPSBjaGFubmVsLAogICAgYDIwMjZgICAgPSBiaWcocm91bmQobGFzdCkpLAogICAgYDIwMjdgICAgPSBiaWcocm91bmQoZml0KSksCiAgICBDaGFuZ2UgICA9IGNoZyhmaXQgLyBsYXN0IC0gMSksCiAgICBgOTUlIENJYCA9IHNwcmludGYoIiVzIFx1MjAxMyAlcyIsIGJpZyhyb3VuZChjaV9sd3IpKSwgYmlnKHJvdW5kKGNpX3VwcikpKSwKICAgIGA5NSUgUElgID0gc3ByaW50ZigiJXMgXHUyMDEzICVzIiwgYmlnKHJvdW5kKHBpX2x3cikpLCBiaWcocm91bmQocGlfdXByKSkpKSB8PgogIHN0eSgiMjAyNyBwcm9qZWN0aW9uIGZyb20gdGhlIGxpbmVhciB0cmVuZCAoUnApIiwgYygibCIsIHJlcCgiciIsIDUpKSkgfD4KICBjb2x1bW5fc3BlYygyOjQsIG1vbm9zcGFjZSA9IFRSVUUpCmBgYA==