Introducción:

(TL;DR: cómo obtener datos de 251070 proyectos de kickstarter.)

kickstarter es un sitio web de micromecenazgo (crowfunding) en el que, básicamente, personas o equipos de todo el mundo muestran sus proyectos con el fin de conseguir fondos para llevarlos a cabo.

(Si quieren saber un poco más, wikipedia tiene un artículo bastante completo.)

Estos proyectos se agrupan por categorías, se plantean un tiempo para recaudar un monto específico, consiguen backers o respaldadores… pero sobre todo, al final, alcanzan el propósito o no lo hacen: finalmente son exitosos o fracasan en su cometido.

Vamos en esta primera parte a plasmar en la notebook a ver cómo obtener los datos principales de 251070 proyectos que durante 2017 estuvieron en la plataforma.

Descargando los datos:

Lo primero es darle las gracias y gran part del mérito a la gente de webrobots, quienes scrapearon la página de kicstarter para poner a disposición datos de 2014 hasta 2018. Ya que es de su página de donde obtenemos los datos sin tener que hacer nosotros mismos el scrapping.

Nota: remover el parámetro “eval=FALSE” si quiere que se ejecute la descarga. Yo deshabilité la descarga para la notebook debido a que ya hice la descarga y toma un buen tiempo; además, para no hacer un uso innecesario de recursos.

# Primero definimos las urls. En este caso, yo elegí descargar solamente las 2017. Con las 2018 tuve problemas
url <- c("https://s3.amazonaws.com/weruns/forfun/Kickstarter/Kickstarter_2017-01-15T22_21_04_985Z.zip", 
         "https://s3.amazonaws.com/weruns/forfun/Kickstarter/Kickstarter_2017-02-15T22_22_48_377Z.zip", 
         "https://s3.amazonaws.com/weruns/forfun/Kickstarter/Kickstarter_2017-03-15T22_20_55_874Z.zip", 
         "https://s3.amazonaws.com/weruns/forfun/Kickstarter/Kickstarter_2017-04-15T22_21_18_122Z.zip", 
         "https://s3.amazonaws.com/weruns/forfun/Kickstarter/Kickstarter_2017-05-15T22_21_11_300Z.zip", 
         "https://s3.amazonaws.com/weruns/forfun/Kickstarter/Kickstarter_2017-06-15T22_20_03_059Z.zip", 
         "https://s3.amazonaws.com/weruns/forfun/Kickstarter/Kickstarter_2017-07-15T22_20_48_951Z.zip", 
         "https://s3.amazonaws.com/weruns/forfun/Kickstarter/Kickstarter_2017-08-15T22_20_51_958Z.zip", 
         "https://s3.amazonaws.com/weruns/forfun/Kickstarter/Kickstarter_2017-09-15T22_20_48_432Z.zip", 
         "https://s3.amazonaws.com/weruns/forfun/Kickstarter/Kickstarter_2017-10-15T10_20_38_271Z.zip", 
         "https://s3.amazonaws.com/weruns/forfun/Kickstarter/Kickstarter_2017-11-15T10_21_04_919Z.zip", 
         "https://s3.amazonaws.com/weruns/forfun/Kickstarter/Kickstarter_2017-12-15T10_20_51_610Z.zip")

# Creamos una función que extrae los n últimos caracteres
substrRight <- function(x, n){
  substr(x, nchar(x)-n+1, nchar(x))
}

# Aplicamos la función recién creada para extraer los último 28 caracteres de la url (año, mes y consecutivo)
# con los caracteres extraídos nombramos los archivos y los guardamos dentro de la carpeta data dentro del proyecto

library(here)
for (i in 1:length(url)) {
  path_name <- paste(here("./data/"), substrRight(url[i], 28))
  download.file(url[i], path_name)
}

Descomprimiendo los datos:

Una vez que hemos descargado os datos, vamos a descomprimirlos (la lectura desde el zip me produjo errores que preferí evitar)

library(here)

# hacemos una lista de todos los archivos .zip descargados
all_files <- list.files(here("data"))
# Establecemos dónde los vamos a guardar
outdir <- here("data/unzipped")

# función de apoyo para extraer parte del nombre y usarlo para renombrar
substrLeft <- function(x, n){
  substr(x, 2, n+1)
}

# Renombrado masivo
setwd(here("/data"))

for(i in 1:length(all_files)) {
  file.rename(from = all_files[i], to = substrLeft(all_files[i], 7))
}
setwd(here())

# Extraemos y guardamos
for(i in 1:length(all_files)) {
  f <- paste(here("data") , all_files[i], sep = "/")
  thisOutDir <- paste(outdir, all_files[i], sep = "/")
  unzip(f, exdir = thisOutDir)
}

# Lista de todos los descomprimidos
all_abs_paths <- list.files(here("data/unzipped"), pattern = ".csv", recursive = TRUE, full.names = TRUE)

# Guardamos la lista de todos los descomprimidos
saveRDS(all_abs_paths, here("data/all_files_unzipped.rds"))

Ordenando los datos:

Ahora que tenemos todos los datos descomprimidos, los vamos a leer para juntarlos en un único dataframe/set de datos: tenemos una carpeta para cada mes del año 2017 (12 carpetas), cada una con 34.66 archivos en promedio. Un total de 416 archivos que juntos suman 8.2 GiB.

library(here)
setwd(here())

# Importamos la lista de archivos de todos los archivos
all_files <- readRDS(here("data/all_files_unzipped.rds"))

library(stringr)

# Arreglamos los nombres de los archivos en tanto que colnames, para que purrr retenga el periodo del que proviene el dato
names(all_files) <- all_files %>% 
  str_remove(".csv") %>% 
  str_sub(79, 80)

# Con purrr aplicamos read_csv de la librería readr a todos los archivos listados: los leemos todos
setwd(here("data/unzipped"))
df <- purrr::safely(purrr::map_df(all_files, readr::read_csv, .id = "periodo"))

setwd(here())

# Tenemos variables de tiempo como número. Les damos el formato adecuado
df[, 15:18] <- lapply(df[, 15:18], as.Date.POSIXct) # convertir el número a fecha

# Si tomamos los casos que no son NAs en estas últimas columnas, y las dividimos sobre el total:
# las columnas * las filas divididas: 
(((ncol(df)-29) * nrow(df)) - (sum(is.na(df[, 30:ncol(df)])))) / ((ncol(df)-29) * nrow(df)) 

# Nos damos cuenta que la proporción de casos no ameritan el esfuerzo de la limpieza ni deja lugar a la
# imputación: entonces las dejamos de lado.
df <- df[, 1:29]

# De las variables a disposición, realmente me interesa, sobre todo, el texto y el estado final.
# Pero necesitamos el periodo, y los pledges, usd_rate, goal y otros datos pueden resultar de utilidad para modelos
# que tomen como entrada Procesamiento del Lenguaje Natural (NLP), que es el centro del asunto.
df_text <- df[, c(1:2, 4:9, 11:22, 24:25)]

# Debido a que se presentan problemas con la RAM disponible a la hora de escribir los archivos en R, los partimos
# en unidades más pequeñas.
df_text1 <- df_text[1:528584, ]
df_text2 <- df_text[528585:1057170, ]
df_text3 <- df_text[1057171:1585756, ]
df_text4 <- df_text[1585757:2114333, ]

# Y las guardamos
saveRDS(df_text1, "./data/text_sets/data_text1.rds")
saveRDS(df_text2, "./data/text_sets/data_text2.rds")
saveRDS(df_text3, "./data/text_sets/data_text3.rds")
saveRDS(df_text4, "./data/text_sets/data_text4.rds")

Limpiando datos:

Ahora que tenemos todos los datos, nos damos cuenta fácil que cada proyecto aparece muchas veces: un proyecto puede aparecer en enero, febrero, marzo… y su estado va cambiando y el recaudo, los backers. Pero lo que nos interesa saber es su estado final: si logró o no recaudar el dinero.

Es por eso que necesitamos retener solamente el último estado del proyecto, su última aparición, que es el periodo máximo de cada proyecto.

library(here)

# Leemos todos los set de datos
df1 <- readRDS(here("/data/text_sets/data_text1.rds"))
df2 <- readRDS(here("/data/text_sets/data_text2.rds"))
df3 <- readRDS(here("/data/text_sets/data_text3.rds"))
df4 <- readRDS(here("/data/text_sets/data_text4.rds"))

# los juntamos
df <- rbind(df1, df2, df3, df4)

# borramos los sets parciales (sólo por liberar espacio)
rm(list = c("df1", "df2", "df3", "df4"))

# verificamos el tamaño del nuevo set
format(object.size(df), units = "Mb")

library(dplyr)
library(stringr)

# El periodo lo pasamos de mes y año a solamente mes (todos son 2017)
df <- df %>% 
    mutate(periodo = as.integer(str_sub(periodo, 6, 7)))

# Pasamos el id (identificador del proyecto) a character.
df$id <- as.character(df$id)

# Retenemos de cada proyecto la fila en la que tiene su último mes, y eliminamos los casos en los que haya duplicados
# en cuanto a periodo e id.
df_unique <- df %>% 
    group_by(id) %>% 
    filter(periodo == max(periodo)) %>% 
    distinct(id, periodo, .keep_all = TRUE)

# Revisamos cuánto ocupa ahora el nuevo set
format(object.size(df_unique), units = "Mb")

# Le damos una mirada al set
glimpse(df_unique)

# Y lo guardamos
saveRDS(df_unique, here("/data/text_sets/df.rds"))

Nota final:

Esto es todo por ahora. Ya tenemos así listos los datos de 251070 proyectos de kickstarter. Lo que sigue es transformar las clases de estado, eliminar casos en los que no se ha definido el proyecto y extraer el blurb (descripción del proyecto) y pasarlo a word embeddings para usarlo como datos de entrenamiento contra la variable dependiente, ya sea goal, state o category.

Los modelos en las siguientes notebooks.

Gracias por seguirme hasta aquí.

LS0tCnRpdGxlOiAiS2lja3N0YXJ0ZXIgMjAxNyBnZXR0aW5nLCBjbGVhbmluZyBhbmQgdGlkeWluZyBkYXRhIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIyBJbnRyb2R1Y2Npw7NuOiAKIyMjIChUTDtEUjogY8OzbW8gb2J0ZW5lciBkYXRvcyBkZSAyNTEwNzAgcHJveWVjdG9zIGRlIGtpY2tzdGFydGVyLikKCltraWNrc3RhcnRlcl0oaHR0cHM6Ly93d3cua2lja3N0YXJ0ZXIuY29tLz9sYW5nPWVzKSBlcyB1biBzaXRpbyB3ZWIgZGUgbWljcm9tZWNlbmF6Z28gKGNyb3dmdW5kaW5nKSBlbiBlbCBxdWUsIGLDoXNpY2FtZW50ZSwgcGVyc29uYXMgbyBlcXVpcG9zIGRlIHRvZG8gZWwgbXVuZG8gbXVlc3RyYW4gc3VzIHByb3llY3RvcyBjb24gZWwgZmluIGRlIGNvbnNlZ3VpciBmb25kb3MgcGFyYSBsbGV2YXJsb3MgYSBjYWJvLiAKCihTaSBxdWllcmVuIHNhYmVyIHVuIHBvY28gbcOhcywgW3dpa2lwZWRpYV0oaHR0cHM6Ly9lcy53aWtpcGVkaWEub3JnL3dpa2kvS2lja3N0YXJ0ZXIpIHRpZW5lIHVuIGFydMOtY3VsbyBiYXN0YW50ZSBjb21wbGV0by4pCgpFc3RvcyBwcm95ZWN0b3Mgc2UgYWdydXBhbiBwb3IgY2F0ZWdvcsOtYXMsIHNlIHBsYW50ZWFuIHVuIHRpZW1wbyBwYXJhIHJlY2F1ZGFyIHVuIG1vbnRvIGVzcGVjw61maWNvLCBjb25zaWd1ZW4gYmFja2VycyBvIHJlc3BhbGRhZG9yZXMuLi4gcGVybyBzb2JyZSB0b2RvLCBhbCBmaW5hbCwgYWxjYW56YW4gZWwgcHJvcMOzc2l0byBvIG5vIGxvIGhhY2VuOiBmaW5hbG1lbnRlIHNvbiBleGl0b3NvcyBvIGZyYWNhc2FuIGVuIHN1IGNvbWV0aWRvLgoKVmFtb3MgZW4gZXN0YSBwcmltZXJhIHBhcnRlIGEgcGxhc21hciBlbiBsYSBub3RlYm9vayBhIHZlciBjw7NtbyBvYnRlbmVyIGxvcyBkYXRvcyBwcmluY2lwYWxlcyBkZSAyNTEwNzAgcHJveWVjdG9zIHF1ZSBkdXJhbnRlIDIwMTcgZXN0dXZpZXJvbiBlbiBsYSBwbGF0YWZvcm1hLgoKIyMgRGVzY2FyZ2FuZG8gbG9zIGRhdG9zOgoKTG8gcHJpbWVybyBlcyBkYXJsZSBsYXMgZ3JhY2lhcyB5IGdyYW4gcGFydCBkZWwgbcOpcml0byBhIFtsYSBnZW50ZSBkZSB3ZWJyb2JvdHNdKGh0dHBzOi8vd2Vicm9ib3RzLmlvL2Fib3V0LXVzLyksIHF1aWVuZXMgc2NyYXBlYXJvbiBsYSBww6FnaW5hIGRlIGtpY3N0YXJ0ZXIgcGFyYSBwb25lciBhIGRpc3Bvc2ljacOzbiBkYXRvcyBkZSAyMDE0IGhhc3RhIDIwMTguIFlhIHF1ZSBlcyBkZSBzdSBww6FnaW5hIGRlIGRvbmRlIG9idGVuZW1vcyBsb3MgZGF0b3Mgc2luIHRlbmVyIHF1ZSBoYWNlciBub3NvdHJvcyBtaXNtb3MgZWwgc2NyYXBwaW5nLiAKCiMjIyBOb3RhOiByZW1vdmVyIGVsIHBhcsOhbWV0cm8gImV2YWw9RkFMU0UiIHNpIHF1aWVyZSBxdWUgc2UgZWplY3V0ZSBsYSBkZXNjYXJnYS4gWW8gZGVzaGFiaWxpdMOpIGxhIGRlc2NhcmdhIHBhcmEgbGEgbm90ZWJvb2sgZGViaWRvIGEgcXVlIHlhIGhpY2UgbGEgZGVzY2FyZ2EgeSB0b21hIHVuIGJ1ZW4gdGllbXBvOyBhZGVtw6FzLCBwYXJhIG5vIGhhY2VyIHVuIHVzbyBpbm5lY2VzYXJpbyBkZSByZWN1cnNvcy4KCmBgYHtyLCBkb3dubG9hZF9kYXRhLCBjYWNoZT1UUlVFLCBldmFsPUZBTFNFfQojIFByaW1lcm8gZGVmaW5pbW9zIGxhcyB1cmxzLiBFbiBlc3RlIGNhc28sIHlvIGVsZWfDrSBkZXNjYXJnYXIgc29sYW1lbnRlIGxhcyAyMDE3LiBDb24gbGFzIDIwMTggdHV2ZSBwcm9ibGVtYXMKdXJsIDwtIGMoImh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS93ZXJ1bnMvZm9yZnVuL0tpY2tzdGFydGVyL0tpY2tzdGFydGVyXzIwMTctMDEtMTVUMjJfMjFfMDRfOTg1Wi56aXAiLCAKICAgICAgICAgImh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS93ZXJ1bnMvZm9yZnVuL0tpY2tzdGFydGVyL0tpY2tzdGFydGVyXzIwMTctMDItMTVUMjJfMjJfNDhfMzc3Wi56aXAiLCAKICAgICAgICAgImh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS93ZXJ1bnMvZm9yZnVuL0tpY2tzdGFydGVyL0tpY2tzdGFydGVyXzIwMTctMDMtMTVUMjJfMjBfNTVfODc0Wi56aXAiLCAKICAgICAgICAgImh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS93ZXJ1bnMvZm9yZnVuL0tpY2tzdGFydGVyL0tpY2tzdGFydGVyXzIwMTctMDQtMTVUMjJfMjFfMThfMTIyWi56aXAiLCAKICAgICAgICAgImh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS93ZXJ1bnMvZm9yZnVuL0tpY2tzdGFydGVyL0tpY2tzdGFydGVyXzIwMTctMDUtMTVUMjJfMjFfMTFfMzAwWi56aXAiLCAKICAgICAgICAgImh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS93ZXJ1bnMvZm9yZnVuL0tpY2tzdGFydGVyL0tpY2tzdGFydGVyXzIwMTctMDYtMTVUMjJfMjBfMDNfMDU5Wi56aXAiLCAKICAgICAgICAgImh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS93ZXJ1bnMvZm9yZnVuL0tpY2tzdGFydGVyL0tpY2tzdGFydGVyXzIwMTctMDctMTVUMjJfMjBfNDhfOTUxWi56aXAiLCAKICAgICAgICAgImh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS93ZXJ1bnMvZm9yZnVuL0tpY2tzdGFydGVyL0tpY2tzdGFydGVyXzIwMTctMDgtMTVUMjJfMjBfNTFfOTU4Wi56aXAiLCAKICAgICAgICAgImh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS93ZXJ1bnMvZm9yZnVuL0tpY2tzdGFydGVyL0tpY2tzdGFydGVyXzIwMTctMDktMTVUMjJfMjBfNDhfNDMyWi56aXAiLCAKICAgICAgICAgImh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS93ZXJ1bnMvZm9yZnVuL0tpY2tzdGFydGVyL0tpY2tzdGFydGVyXzIwMTctMTAtMTVUMTBfMjBfMzhfMjcxWi56aXAiLCAKICAgICAgICAgImh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS93ZXJ1bnMvZm9yZnVuL0tpY2tzdGFydGVyL0tpY2tzdGFydGVyXzIwMTctMTEtMTVUMTBfMjFfMDRfOTE5Wi56aXAiLCAKICAgICAgICAgImh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS93ZXJ1bnMvZm9yZnVuL0tpY2tzdGFydGVyL0tpY2tzdGFydGVyXzIwMTctMTItMTVUMTBfMjBfNTFfNjEwWi56aXAiKQoKIyBDcmVhbW9zIHVuYSBmdW5jacOzbiBxdWUgZXh0cmFlIGxvcyBuIMO6bHRpbW9zIGNhcmFjdGVyZXMKc3Vic3RyUmlnaHQgPC0gZnVuY3Rpb24oeCwgbil7CiAgc3Vic3RyKHgsIG5jaGFyKHgpLW4rMSwgbmNoYXIoeCkpCn0KCiMgQXBsaWNhbW9zIGxhIGZ1bmNpw7NuIHJlY2nDqW4gY3JlYWRhIHBhcmEgZXh0cmFlciBsb3Mgw7psdGltbyAyOCBjYXJhY3RlcmVzIGRlIGxhIHVybCAoYcOxbywgbWVzIHkgY29uc2VjdXRpdm8pCiMgY29uIGxvcyBjYXJhY3RlcmVzIGV4dHJhw61kb3Mgbm9tYnJhbW9zIGxvcyBhcmNoaXZvcyB5IGxvcyBndWFyZGFtb3MgZGVudHJvIGRlIGxhIGNhcnBldGEgZGF0YSBkZW50cm8gZGVsIHByb3llY3RvCgpsaWJyYXJ5KGhlcmUpCmZvciAoaSBpbiAxOmxlbmd0aCh1cmwpKSB7CiAgcGF0aF9uYW1lIDwtIHBhc3RlKGhlcmUoIi4vZGF0YS8iKSwgc3Vic3RyUmlnaHQodXJsW2ldLCAyOCkpCiAgZG93bmxvYWQuZmlsZSh1cmxbaV0sIHBhdGhfbmFtZSkKfQpgYGAKCiMjIERlc2NvbXByaW1pZW5kbyBsb3MgZGF0b3M6CgpVbmEgdmV6IHF1ZSBoZW1vcyBkZXNjYXJnYWRvIG9zIGRhdG9zLCB2YW1vcyBhIGRlc2NvbXByaW1pcmxvcyAobGEgbGVjdHVyYSBkZXNkZSBlbCB6aXAgbWUgcHJvZHVqbyBlcnJvcmVzIHF1ZSBwcmVmZXLDrSBldml0YXIpCgpgYGB7ciwgdW56aXBwaW5nLCBjYWNoZT1UUlVFLCBldmFsPUZBTFNFfQpsaWJyYXJ5KGhlcmUpCgojIGhhY2Vtb3MgdW5hIGxpc3RhIGRlIHRvZG9zIGxvcyBhcmNoaXZvcyAuemlwIGRlc2NhcmdhZG9zCmFsbF9maWxlcyA8LSBsaXN0LmZpbGVzKGhlcmUoImRhdGEiKSkKIyBFc3RhYmxlY2Vtb3MgZMOzbmRlIGxvcyB2YW1vcyBhIGd1YXJkYXIKb3V0ZGlyIDwtIGhlcmUoImRhdGEvdW56aXBwZWQiKQoKIyBmdW5jacOzbiBkZSBhcG95byBwYXJhIGV4dHJhZXIgcGFydGUgZGVsIG5vbWJyZSB5IHVzYXJsbyBwYXJhIHJlbm9tYnJhcgpzdWJzdHJMZWZ0IDwtIGZ1bmN0aW9uKHgsIG4pewogIHN1YnN0cih4LCAyLCBuKzEpCn0KCiMgUmVub21icmFkbyBtYXNpdm8Kc2V0d2QoaGVyZSgiL2RhdGEiKSkKCmZvcihpIGluIDE6bGVuZ3RoKGFsbF9maWxlcykpIHsKICBmaWxlLnJlbmFtZShmcm9tID0gYWxsX2ZpbGVzW2ldLCB0byA9IHN1YnN0ckxlZnQoYWxsX2ZpbGVzW2ldLCA3KSkKfQpzZXR3ZChoZXJlKCkpCgojIEV4dHJhZW1vcyB5IGd1YXJkYW1vcwpmb3IoaSBpbiAxOmxlbmd0aChhbGxfZmlsZXMpKSB7CiAgZiA8LSBwYXN0ZShoZXJlKCJkYXRhIikgLCBhbGxfZmlsZXNbaV0sIHNlcCA9ICIvIikKICB0aGlzT3V0RGlyIDwtIHBhc3RlKG91dGRpciwgYWxsX2ZpbGVzW2ldLCBzZXAgPSAiLyIpCiAgdW56aXAoZiwgZXhkaXIgPSB0aGlzT3V0RGlyKQp9CgojIExpc3RhIGRlIHRvZG9zIGxvcyBkZXNjb21wcmltaWRvcwphbGxfYWJzX3BhdGhzIDwtIGxpc3QuZmlsZXMoaGVyZSgiZGF0YS91bnppcHBlZCIpLCBwYXR0ZXJuID0gIi5jc3YiLCByZWN1cnNpdmUgPSBUUlVFLCBmdWxsLm5hbWVzID0gVFJVRSkKCiMgR3VhcmRhbW9zIGxhIGxpc3RhIGRlIHRvZG9zIGxvcyBkZXNjb21wcmltaWRvcwpzYXZlUkRTKGFsbF9hYnNfcGF0aHMsIGhlcmUoImRhdGEvYWxsX2ZpbGVzX3VuemlwcGVkLnJkcyIpKQpgYGAKCiMjIE9yZGVuYW5kbyBsb3MgZGF0b3M6CgpBaG9yYSBxdWUgdGVuZW1vcyB0b2RvcyBsb3MgZGF0b3MgZGVzY29tcHJpbWlkb3MsIGxvcyB2YW1vcyBhIGxlZXIgcGFyYSBqdW50YXJsb3MgZW4gdW4gw7puaWNvIGRhdGFmcmFtZS9zZXQgZGUgZGF0b3M6IHRlbmVtb3MgdW5hIGNhcnBldGEgcGFyYSBjYWRhIG1lcyBkZWwgYcOxbyAyMDE3ICgxMiBjYXJwZXRhcyksIGNhZGEgdW5hIGNvbiAzNC42NiBhcmNoaXZvcyBlbiBwcm9tZWRpby4gVW4gdG90YWwgZGUgNDE2IGFyY2hpdm9zIHF1ZSBqdW50b3Mgc3VtYW4gOC4yIEdpQi4KCmBgYHtyLCBvcmRlbmFuZG8sIGNhY2hlPVRSVUUsIGV2YWw9RkFMU0V9CmxpYnJhcnkoaGVyZSkKc2V0d2QoaGVyZSgpKQoKIyBJbXBvcnRhbW9zIGxhIGxpc3RhIGRlIGFyY2hpdm9zIGRlIHRvZG9zIGxvcyBhcmNoaXZvcwphbGxfZmlsZXMgPC0gcmVhZFJEUyhoZXJlKCJkYXRhL2FsbF9maWxlc191bnppcHBlZC5yZHMiKSkKCmxpYnJhcnkoc3RyaW5ncikKCiMgQXJyZWdsYW1vcyBsb3Mgbm9tYnJlcyBkZSBsb3MgYXJjaGl2b3MgZW4gdGFudG8gcXVlIGNvbG5hbWVzLCBwYXJhIHF1ZSBwdXJyciByZXRlbmdhIGVsIHBlcmlvZG8gZGVsIHF1ZSBwcm92aWVuZSBlbCBkYXRvCm5hbWVzKGFsbF9maWxlcykgPC0gYWxsX2ZpbGVzICU+JSAKICBzdHJfcmVtb3ZlKCIuY3N2IikgJT4lIAogIHN0cl9zdWIoNzksIDgwKQoKIyBDb24gcHVycnIgYXBsaWNhbW9zIHJlYWRfY3N2IGRlIGxhIGxpYnJlcsOtYSByZWFkciBhIHRvZG9zIGxvcyBhcmNoaXZvcyBsaXN0YWRvczogbG9zIGxlZW1vcyB0b2RvcwpzZXR3ZChoZXJlKCJkYXRhL3VuemlwcGVkIikpCmRmIDwtIHB1cnJyOjpzYWZlbHkocHVycnI6Om1hcF9kZihhbGxfZmlsZXMsIHJlYWRyOjpyZWFkX2NzdiwgLmlkID0gInBlcmlvZG8iKSkKCnNldHdkKGhlcmUoKSkKCiMgVGVuZW1vcyB2YXJpYWJsZXMgZGUgdGllbXBvIGNvbW8gbsO6bWVyby4gTGVzIGRhbW9zIGVsIGZvcm1hdG8gYWRlY3VhZG8KZGZbLCAxNToxOF0gPC0gbGFwcGx5KGRmWywgMTU6MThdLCBhcy5EYXRlLlBPU0lYY3QpICMgY29udmVydGlyIGVsIG7Dum1lcm8gYSBmZWNoYQoKIyBTaSB0b21hbW9zIGxvcyBjYXNvcyBxdWUgbm8gc29uIE5BcyBlbiBlc3RhcyDDumx0aW1hcyBjb2x1bW5hcywgeSBsYXMgZGl2aWRpbW9zIHNvYnJlIGVsIHRvdGFsOgojIGxhcyBjb2x1bW5hcyAqIGxhcyBmaWxhcyBkaXZpZGlkYXM6IAooKChuY29sKGRmKS0yOSkgKiBucm93KGRmKSkgLSAoc3VtKGlzLm5hKGRmWywgMzA6bmNvbChkZildKSkpKSAvICgobmNvbChkZiktMjkpICogbnJvdyhkZikpIAoKIyBOb3MgZGFtb3MgY3VlbnRhIHF1ZSBsYSBwcm9wb3JjacOzbiBkZSBjYXNvcyBubyBhbWVyaXRhbiBlbCBlc2Z1ZXJ6byBkZSBsYSBsaW1waWV6YSBuaSBkZWphIGx1Z2FyIGEgbGEKIyBpbXB1dGFjacOzbjogZW50b25jZXMgbGFzIGRlamFtb3MgZGUgbGFkby4KZGYgPC0gZGZbLCAxOjI5XQoKIyBEZSBsYXMgdmFyaWFibGVzIGEgZGlzcG9zaWNpw7NuLCByZWFsbWVudGUgbWUgaW50ZXJlc2EsIHNvYnJlIHRvZG8sIGVsIHRleHRvIHkgZWwgZXN0YWRvIGZpbmFsLgojIFBlcm8gbmVjZXNpdGFtb3MgZWwgcGVyaW9kbywgeSBsb3MgcGxlZGdlcywgdXNkX3JhdGUsIGdvYWwgeSBvdHJvcyBkYXRvcyBwdWVkZW4gcmVzdWx0YXIgZGUgdXRpbGlkYWQgcGFyYSBtb2RlbG9zCiMgcXVlIHRvbWVuIGNvbW8gZW50cmFkYSBQcm9jZXNhbWllbnRvIGRlbCBMZW5ndWFqZSBOYXR1cmFsIChOTFApLCBxdWUgZXMgZWwgY2VudHJvIGRlbCBhc3VudG8uCmRmX3RleHQgPC0gZGZbLCBjKDE6MiwgNDo5LCAxMToyMiwgMjQ6MjUpXQoKIyBEZWJpZG8gYSBxdWUgc2UgcHJlc2VudGFuIHByb2JsZW1hcyBjb24gbGEgUkFNIGRpc3BvbmlibGUgYSBsYSBob3JhIGRlIGVzY3JpYmlyIGxvcyBhcmNoaXZvcyBlbiBSLCBsb3MgcGFydGltb3MKIyBlbiB1bmlkYWRlcyBtw6FzIHBlcXVlw7Fhcy4KZGZfdGV4dDEgPC0gZGZfdGV4dFsxOjUyODU4NCwgXQpkZl90ZXh0MiA8LSBkZl90ZXh0WzUyODU4NToxMDU3MTcwLCBdCmRmX3RleHQzIDwtIGRmX3RleHRbMTA1NzE3MToxNTg1NzU2LCBdCmRmX3RleHQ0IDwtIGRmX3RleHRbMTU4NTc1NzoyMTE0MzMzLCBdCgojIFkgbGFzIGd1YXJkYW1vcwpzYXZlUkRTKGRmX3RleHQxLCAiLi9kYXRhL3RleHRfc2V0cy9kYXRhX3RleHQxLnJkcyIpCnNhdmVSRFMoZGZfdGV4dDIsICIuL2RhdGEvdGV4dF9zZXRzL2RhdGFfdGV4dDIucmRzIikKc2F2ZVJEUyhkZl90ZXh0MywgIi4vZGF0YS90ZXh0X3NldHMvZGF0YV90ZXh0My5yZHMiKQpzYXZlUkRTKGRmX3RleHQ0LCAiLi9kYXRhL3RleHRfc2V0cy9kYXRhX3RleHQ0LnJkcyIpCmBgYAoKIyMgTGltcGlhbmRvIGRhdG9zOgoKQWhvcmEgcXVlIHRlbmVtb3MgdG9kb3MgbG9zIGRhdG9zLCBub3MgZGFtb3MgY3VlbnRhIGbDoWNpbCBxdWUgY2FkYSBwcm95ZWN0byBhcGFyZWNlIG11Y2hhcyB2ZWNlczogdW4gcHJveWVjdG8gcHVlZGUgYXBhcmVjZXIgZW4gZW5lcm8sIGZlYnJlcm8sIG1hcnpvLi4uIHkgc3UgZXN0YWRvIHZhIGNhbWJpYW5kbyB5IGVsIHJlY2F1ZG8sIGxvcyBiYWNrZXJzLiBQZXJvIGxvIHF1ZSBub3MgaW50ZXJlc2Egc2FiZXIgZXMgc3UgZXN0YWRvIGZpbmFsOiBzaSBsb2dyw7MgbyBubyByZWNhdWRhciBlbCBkaW5lcm8uCgpFcyBwb3IgZXNvIHF1ZSBuZWNlc2l0YW1vcyByZXRlbmVyIHNvbGFtZW50ZSBlbCDDumx0aW1vIGVzdGFkbyBkZWwgcHJveWVjdG8sIHN1IMO6bHRpbWEgYXBhcmljacOzbiwgcXVlIGVzIGVsIHBlcmlvZG8gbcOheGltbyBkZSBjYWRhIHByb3llY3RvLgoKYGBge3IsIHVuaXF1ZSwgY2FjaGU9VFJVRSwgZXZhbD1GQUxTRX0KbGlicmFyeShoZXJlKQoKIyBMZWVtb3MgdG9kb3MgbG9zIHNldCBkZSBkYXRvcwpkZjEgPC0gcmVhZFJEUyhoZXJlKCIvZGF0YS90ZXh0X3NldHMvZGF0YV90ZXh0MS5yZHMiKSkKZGYyIDwtIHJlYWRSRFMoaGVyZSgiL2RhdGEvdGV4dF9zZXRzL2RhdGFfdGV4dDIucmRzIikpCmRmMyA8LSByZWFkUkRTKGhlcmUoIi9kYXRhL3RleHRfc2V0cy9kYXRhX3RleHQzLnJkcyIpKQpkZjQgPC0gcmVhZFJEUyhoZXJlKCIvZGF0YS90ZXh0X3NldHMvZGF0YV90ZXh0NC5yZHMiKSkKCiMgbG9zIGp1bnRhbW9zCmRmIDwtIHJiaW5kKGRmMSwgZGYyLCBkZjMsIGRmNCkKCiMgYm9ycmFtb3MgbG9zIHNldHMgcGFyY2lhbGVzIChzw7NsbyBwb3IgbGliZXJhciBlc3BhY2lvKQpybShsaXN0ID0gYygiZGYxIiwgImRmMiIsICJkZjMiLCAiZGY0IikpCgojIHZlcmlmaWNhbW9zIGVsIHRhbWHDsW8gZGVsIG51ZXZvIHNldApmb3JtYXQob2JqZWN0LnNpemUoZGYpLCB1bml0cyA9ICJNYiIpCgpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHN0cmluZ3IpCgojIEVsIHBlcmlvZG8gbG8gcGFzYW1vcyBkZSBtZXMgeSBhw7FvIGEgc29sYW1lbnRlIG1lcyAodG9kb3Mgc29uIDIwMTcpCmRmIDwtIGRmICU+JSAKICAgIG11dGF0ZShwZXJpb2RvID0gYXMuaW50ZWdlcihzdHJfc3ViKHBlcmlvZG8sIDYsIDcpKSkKCiMgUGFzYW1vcyBlbCBpZCAoaWRlbnRpZmljYWRvciBkZWwgcHJveWVjdG8pIGEgY2hhcmFjdGVyLgpkZiRpZCA8LSBhcy5jaGFyYWN0ZXIoZGYkaWQpCgojIFJldGVuZW1vcyBkZSBjYWRhIHByb3llY3RvIGxhIGZpbGEgZW4gbGEgcXVlIHRpZW5lIHN1IMO6bHRpbW8gbWVzLCB5IGVsaW1pbmFtb3MgbG9zIGNhc29zIGVuIGxvcyBxdWUgaGF5YSBkdXBsaWNhZG9zCiMgZW4gY3VhbnRvIGEgcGVyaW9kbyBlIGlkLgpkZl91bmlxdWUgPC0gZGYgJT4lIAogICAgZ3JvdXBfYnkoaWQpICU+JSAKICAgIGZpbHRlcihwZXJpb2RvID09IG1heChwZXJpb2RvKSkgJT4lIAogICAgZGlzdGluY3QoaWQsIHBlcmlvZG8sIC5rZWVwX2FsbCA9IFRSVUUpCgojIFJldmlzYW1vcyBjdcOhbnRvIG9jdXBhIGFob3JhIGVsIG51ZXZvIHNldApmb3JtYXQob2JqZWN0LnNpemUoZGZfdW5pcXVlKSwgdW5pdHMgPSAiTWIiKQoKIyBMZSBkYW1vcyB1bmEgbWlyYWRhIGFsIHNldApnbGltcHNlKGRmX3VuaXF1ZSkKCiMgWSBsbyBndWFyZGFtb3MKc2F2ZVJEUyhkZl91bmlxdWUsIGhlcmUoIi9kYXRhL3RleHRfc2V0cy9kZi5yZHMiKSkKYGBgCgojIyBOb3RhIGZpbmFsOgoKRXN0byBlcyB0b2RvIHBvciBhaG9yYS4gWWEgdGVuZW1vcyBhc8OtIGxpc3RvcyBsb3MgZGF0b3MgZGUgMjUxMDcwIHByb3llY3RvcyBkZSBraWNrc3RhcnRlci4gCkxvIHF1ZSBzaWd1ZSBlcyB0cmFuc2Zvcm1hciBsYXMgY2xhc2VzIGRlIGVzdGFkbywgZWxpbWluYXIgY2Fzb3MgZW4gbG9zIHF1ZSBubyBzZSBoYSBkZWZpbmlkbyBlbCBwcm95ZWN0byB5IGV4dHJhZXIgZWwgYmx1cmIgKGRlc2NyaXBjacOzbiBkZWwgcHJveWVjdG8pIHkgcGFzYXJsbyBhIHdvcmQgZW1iZWRkaW5ncyBwYXJhIHVzYXJsbyBjb21vIGRhdG9zIGRlIGVudHJlbmFtaWVudG8gY29udHJhIGxhIHZhcmlhYmxlIGRlcGVuZGllbnRlLCB5YSBzZWEgZ29hbCwgc3RhdGUgbyBjYXRlZ29yeS4KCkxvcyBtb2RlbG9zIGVuIGxhcyBzaWd1aWVudGVzIG5vdGVib29rcy4KCkdyYWNpYXMgcG9yIHNlZ3Vpcm1lIGhhc3RhIGFxdcOtLg==