Según wikipedia.org, “RFM es un método utilizado para analizar el valor del cliente”.

RFM representa las tres dimensiones:

Los segmentos resultantes se pueden ordenar desde los más valiosos (mayor antigüedad, frecuencia y valor) hasta los menos valiosos (menor antigüedad, frecuencia y valor). La identificación de los segmentos de RFM más valiosos puede aprovechar las relaciones fortuitas en los datos utilizados para este análisis.

library(tidyverse)
library(skimr)
library(data.table)
library(knitr)
library(rmarkdown)

Lectura de datos

df <- 
  readr::read_csv(file = 'C:/Users/HUAWEI/Downloads/data.csv/data.csv', 
                  locale = readr::locale(encoding = 'UTF-8'))

dplyr::glimpse(df)
## Rows: 541,909
## Columns: 8
## $ InvoiceNo   <chr> "536365", "536365", "536365", "536365", "536365", "536365"~
## $ StockCode   <chr> "85123A", "71053", "84406B", "84029G", "84029E", "22752", ~
## $ Description <chr> "WHITE HANGING HEART T-LIGHT HOLDER", "WHITE METAL LANTERN~
## $ Quantity    <dbl> 6, 6, 8, 6, 6, 2, 6, 6, 6, 32, 6, 6, 8, 6, 6, 3, 2, 3, 3, ~
## $ InvoiceDate <chr> "12/1/2010 8:26", "12/1/2010 8:26", "12/1/2010 8:26", "12/~
## $ UnitPrice   <dbl> 2.55, 3.39, 2.75, 3.39, 3.39, 7.65, 4.25, 1.85, 1.85, 1.69~
## $ CustomerID  <dbl> 17850, 17850, 17850, 17850, 17850, 17850, 17850, 17850, 17~
## $ Country     <chr> "United Kingdom", "United Kingdom", "United Kingdom", "Uni~

Tratamiento de los datos

Se remueven las cantiades negativas y consumidores con NA asignados.

# Remocion de negativos y NA's
df1 <- 
  df %>% 
  dplyr::filter(Quantity > 0 , 
                UnitPrice > 0) %>% 
  tidyr::drop_na()

head(df1, n = 10)

Recodificacion de variables

df2 <- 
  df1 %>% 
  dplyr::mutate(InvoiceDate = as.Date(x = InvoiceDate, format = '%m/%d/%Y')) %>% 
  dplyr::mutate_if(is.character, as.factor) %>% 
  dplyr::mutate(total_dolar = Quantity*UnitPrice)

skimr::skim(df2)
Data summary
Name df2
Number of rows 397884
Number of columns 9
_______________________
Column type frequency:
Date 1
factor 4
numeric 4
________________________
Group variables None

Variable type: Date

skim_variable n_missing complete_rate min max median n_unique
InvoiceDate 0 1 2010-12-01 2011-12-09 2011-07-31 305

Variable type: factor

skim_variable n_missing complete_rate ordered n_unique top_counts
InvoiceNo 0 1 FALSE 18532 576: 542, 579: 533, 580: 529, 578: 442
StockCode 0 1 FALSE 3665 851: 2035, 224: 1723, 850: 1618, 848: 1408
Description 0 1 FALSE 3866 WHI: 2028, REG: 1723, JUM: 1618, ASS: 1408
Country 0 1 FALSE 37 Uni: 354321, Ger: 9040, Fra: 8341, EIR: 7236

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
Quantity 0 1 12.99 179.33 1 2.00 6.00 12.00 80995.00 ▇▁▁▁▁
UnitPrice 0 1 3.12 22.10 0 1.25 1.95 3.75 8142.75 ▇▁▁▁▁
CustomerID 0 1 15294.42 1713.14 12346 13969.00 15159.00 16795.00 18287.00 ▇▇▇▇▇
total_dolar 0 1 22.40 309.07 0 4.68 11.80 19.80 168469.60 ▇▁▁▁▁

Calcular el conjunto de datos de RFM

Para implementar el análisis de RFM, necesitamos seguir procesando el conjunto de datos mediante los siguientes pasos:

  1. Busque la fecha más reciente para cada ID y calcule los días hasta la fecha actual o alguna otra para obtener los datos de antigüedad.
  2. Calcule la cantidad de traducciones de un cliente para obtener los datos de frecuencia.
  3. Sume la cantidad de dinero que gastó un cliente y divídala por Frecuencia, para obtener la cantidad por transacción en promedio, que son los datos monetarios.
df_RFM <- 
  df2 %>% 
  dplyr::group_by(CustomerID) %>% 
  dplyr::summarise(Recency = Sys.Date() - max(InvoiceDate), 
                   Recency = as.numeric(Recency), 
                   frequenci = dplyr::n_distinct(InvoiceNo), 
                   monitery = sum(total_dolar)/frequenci) %>% 
  dplyr::ungroup()

summary(df_RFM)

kableExtra::kable(head(df_RFM))
##    CustomerID       Recency       frequenci          monitery       
##  Min.   :12346   Min.   :3423   Min.   :  1.000   Min.   :    3.45  
##  1st Qu.:13813   1st Qu.:3440   1st Qu.:  1.000   1st Qu.:  178.62  
##  Median :15300   Median :3473   Median :  2.000   Median :  293.90  
##  Mean   :15300   Mean   :3515   Mean   :  4.272   Mean   :  419.17  
##  3rd Qu.:16779   3rd Qu.:3565   3rd Qu.:  5.000   3rd Qu.:  430.11  
##  Max.   :18287   Max.   :3796   Max.   :209.000   Max.   :84236.25
CustomerID Recency frequenci monitery
12346 3748 1 77183.6000
12347 3425 7 615.7143
12348 3498 4 449.3100
12349 3441 1 1757.5500
12350 3733 1 334.4000
12352 3459 8 313.2550

Antigüedad

Experiencia reciente: ¿Qué tan reciente fue la compra del cliente?

df_RFM %>% 
  dplyr::filter(monitery < 7000, 
                frequenci < 200) %>% 
  ggplot2::ggplot() + 
  ggplot2::aes(Recency) + 
  ggplot2::geom_histogram(col = 'red', fill = 'skyblue', bins = 50) + 
  ggplot2::theme_minimal() + 
  ggplot2::theme(text = ggplot2::element_text(family = 'serif'))

Frecuencia

Frecuencia: ¿con qué frecuencia compran?

df_RFM %>% 
  dplyr::filter(monitery < 7000, 
                frequenci < 200) %>% 
  ggplot2::ggplot() + 
  ggplot2::aes(frequenci) + 
  ggplot2::geom_histogram(col = 'red', fill = 'skyblue', bins = 50) + 
  ggplot2::theme_minimal() + 
  ggplot2::theme(text = ggplot2::element_text(family = 'serif'))

Monetario

Valor monetario: ¿cuánto gastan?

df_RFM %>% 
  dplyr::filter(monitery < 7000, 
                frequenci < 200) %>% 
  ggplot2::ggplot() + 
  ggplot2::aes(monitery) + 
  ggplot2::geom_histogram(col = 'red', fill = 'skyblue', bins = 50) + 
  ggplot2::theme_minimal() + 
  ggplot2::theme(text = ggplot2::element_text(family = 'serif'))

Debido a que los datos están realmente sesgados, usamos la escala logarítmica para normalizar.

df_RFM$monitery <- log(df_RFM$monitery)

df_RFM %>% 
  dplyr::filter(monitery < 7000, 
                frequenci < 200) %>% 
  ggplot2::ggplot() + 
  ggplot2::aes(monitery) + 
  ggplot2::geom_histogram(col = 'red', fill = 'skyblue', bins = 50) + 
  ggplot2::theme_minimal() + 
  ggplot2::theme(text = ggplot2::element_text(family = 'serif'))

RFM Analisis

rfm_result <- 
  rfm::rfm_table_customer(df_RFM, 
                          customer_id = CustomerID,
                          recency_days = Recency,
                          n_transactions = frequenci,
                          total_revenue = monitery)

Heat Map

rfm::rfm_heatmap(rfm_result)

Bar Chart

rfm::rfm_bar_chart(rfm_result)

Histogram

rfm::rfm_histograms(rfm_result)

Customers by Orders

rfm::rfm_order_dist(rfm_result)

Refinamiento del analisis

Segmentacion del cliente:

ggplot(data = BDF) + aes(x = cus_seg, fill = cus_seg)+ geom_bar() +
  geom_text(aes(label=scales::percent(..count../sum(..count..))),
            stat='count',position=position_dodge(1))+
  labs(title = "Segmentacion del Cliente", x = "Segmento", y = "Total de clientes") + coord_flip()+ theme_minimal()

LS0tDQp0aXRsZTogIlNlZ21lbnRhY2lvbiBkZSBjb25zdW1pZG9yZXMgdXNhbmRvIFJGTSINCmF1dGhvcjogJ0dydXBvIDUnDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiwgJVknKWAiDQpvdXRwdXQ6IA0KICAgIGh0bWxfZG9jdW1lbnQ6DQogICAgICAgIGRmX3ByaW50OiBwYWdlZA0KICAgICAgICBmaWdfaGVpZ2h0OiA0DQogICAgICAgIGZpZ193aWR0aDogNw0KICAgICAgICB0b2M6IHRydWUNCiAgICAgICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgICAgICBjb2RlX2ZvbGRpbmc6ICJzaG93Ig0KLS0tDQoNClNlZ8O6biB3aWtpcGVkaWEub3JnLCAiUkZNIGVzIHVuIG3DqXRvZG8gdXRpbGl6YWRvIHBhcmEgYW5hbGl6YXIgZWwgdmFsb3IgZGVsIGNsaWVudGUiLg0KDQpSRk0gcmVwcmVzZW50YSBsYXMgdHJlcyBkaW1lbnNpb25lczoNCg0KKiBBY3RpdmlkYWQgcmVjaWVudGU6IMK/UXXDqSB0YW4gcmVjaWVudGUgZnVlIGxhIGNvbXByYSBkZWwgY2xpZW50ZT8NCiogRnJlY3VlbmNpYTogwr9jb24gcXXDqSBmcmVjdWVuY2lhIGNvbXByYW4/DQoqIFZhbG9yIG1vbmV0YXJpbyAtIMK/Q3XDoW50byBnYXN0YW4/DQoNCkxvcyBzZWdtZW50b3MgcmVzdWx0YW50ZXMgc2UgcHVlZGVuIG9yZGVuYXIgZGVzZGUgbG9zIG3DoXMgdmFsaW9zb3MgKG1heW9yIGFudGlnw7xlZGFkLCBmcmVjdWVuY2lhIHkgdmFsb3IpIGhhc3RhIGxvcyBtZW5vcyB2YWxpb3NvcyAobWVub3IgYW50aWfDvGVkYWQsIGZyZWN1ZW5jaWEgeSB2YWxvcikuIExhIGlkZW50aWZpY2FjacOzbiBkZSBsb3Mgc2VnbWVudG9zIGRlIFJGTSBtw6FzIHZhbGlvc29zIHB1ZWRlIGFwcm92ZWNoYXIgbGFzIHJlbGFjaW9uZXMgZm9ydHVpdGFzIGVuIGxvcyBkYXRvcyB1dGlsaXphZG9zIHBhcmEgZXN0ZSBhbsOhbGlzaXMuDQoNCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0UsIG1lc3NhZ2U9RiwgZXJyb3I9Riwgd2FybmluZz1GfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0KYGBgDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzPSdoaWRlJ30NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShza2ltcikNCmxpYnJhcnkoZGF0YS50YWJsZSkNCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KHJtYXJrZG93bikNCg0KYGBgDQoNCiMjIExlY3R1cmEgZGUgZGF0b3MNCg0KYGBge3IsIHJlc3VsdHM9J21hcmt1cCcsIG1lc3NhZ2U9RkFMU0V9DQoNCmRmIDwtIA0KICByZWFkcjo6cmVhZF9jc3YoZmlsZSA9ICdDOi9Vc2Vycy9IVUFXRUkvRG93bmxvYWRzL2RhdGEuY3N2L2RhdGEuY3N2JywgDQogICAgICAgICAgICAgICAgICBsb2NhbGUgPSByZWFkcjo6bG9jYWxlKGVuY29kaW5nID0gJ1VURi04JykpDQoNCmRwbHlyOjpnbGltcHNlKGRmKQ0KDQpgYGANCg0KIyMgVHJhdGFtaWVudG8gZGUgbG9zIGRhdG9zDQpTZSByZW11ZXZlbiBsYXMgY2FudGlhZGVzIG5lZ2F0aXZhcyB5IGNvbnN1bWlkb3JlcyBjb24gTkEgYXNpZ25hZG9zLg0KDQpgYGB7cn0NCiMgUmVtb2Npb24gZGUgbmVnYXRpdm9zIHkgTkEncw0KZGYxIDwtIA0KICBkZiAlPiUgDQogIGRwbHlyOjpmaWx0ZXIoUXVhbnRpdHkgPiAwICwgDQogICAgICAgICAgICAgICAgVW5pdFByaWNlID4gMCkgJT4lIA0KICB0aWR5cjo6ZHJvcF9uYSgpDQoNCmhlYWQoZGYxLCBuID0gMTApDQoNCmBgYA0KDQojIyBSZWNvZGlmaWNhY2lvbiBkZSB2YXJpYWJsZXMNCg0KYGBge3J9DQpkZjIgPC0gDQogIGRmMSAlPiUgDQogIGRwbHlyOjptdXRhdGUoSW52b2ljZURhdGUgPSBhcy5EYXRlKHggPSBJbnZvaWNlRGF0ZSwgZm9ybWF0ID0gJyVtLyVkLyVZJykpICU+JSANCiAgZHBseXI6Om11dGF0ZV9pZihpcy5jaGFyYWN0ZXIsIGFzLmZhY3RvcikgJT4lIA0KICBkcGx5cjo6bXV0YXRlKHRvdGFsX2RvbGFyID0gUXVhbnRpdHkqVW5pdFByaWNlKQ0KDQpza2ltcjo6c2tpbShkZjIpDQoNCmBgYA0KDQojIyBDYWxjdWxhciBlbCBjb25qdW50byBkZSBkYXRvcyBkZSBSRk0gey50YWJzZXR9DQpQYXJhIGltcGxlbWVudGFyIGVsIGFuw6FsaXNpcyBkZSBSRk0sIG5lY2VzaXRhbW9zIHNlZ3VpciBwcm9jZXNhbmRvIGVsIGNvbmp1bnRvIGRlIGRhdG9zIG1lZGlhbnRlIGxvcyBzaWd1aWVudGVzIHBhc29zOg0KDQoxLiBCdXNxdWUgbGEgZmVjaGEgbcOhcyByZWNpZW50ZSBwYXJhIGNhZGEgSUQgeSBjYWxjdWxlIGxvcyBkw61hcyBoYXN0YSBsYSBmZWNoYSBhY3R1YWwgbyBhbGd1bmEgb3RyYSBwYXJhIG9idGVuZXIgbG9zIGRhdG9zIGRlIGFudGlnw7xlZGFkLg0KMi4gQ2FsY3VsZSBsYSBjYW50aWRhZCBkZSB0cmFkdWNjaW9uZXMgZGUgdW4gY2xpZW50ZSBwYXJhIG9idGVuZXIgbG9zIGRhdG9zIGRlIGZyZWN1ZW5jaWEuDQozLiBTdW1lIGxhIGNhbnRpZGFkIGRlIGRpbmVybyBxdWUgZ2FzdMOzIHVuIGNsaWVudGUgeSBkaXbDrWRhbGEgcG9yIEZyZWN1ZW5jaWEsIHBhcmEgb2J0ZW5lciBsYSBjYW50aWRhZCBwb3IgdHJhbnNhY2Npw7NuIGVuIHByb21lZGlvLCBxdWUgc29uIGxvcyBkYXRvcyBtb25ldGFyaW9zLg0KDQpgYGB7ciByZXN1bHRzPSdob2xkJ30NCg0KZGZfUkZNIDwtIA0KICBkZjIgJT4lIA0KICBkcGx5cjo6Z3JvdXBfYnkoQ3VzdG9tZXJJRCkgJT4lIA0KICBkcGx5cjo6c3VtbWFyaXNlKFJlY2VuY3kgPSBTeXMuRGF0ZSgpIC0gbWF4KEludm9pY2VEYXRlKSwgDQogICAgICAgICAgICAgICAgICAgUmVjZW5jeSA9IGFzLm51bWVyaWMoUmVjZW5jeSksIA0KICAgICAgICAgICAgICAgICAgIGZyZXF1ZW5jaSA9IGRwbHlyOjpuX2Rpc3RpbmN0KEludm9pY2VObyksIA0KICAgICAgICAgICAgICAgICAgIG1vbml0ZXJ5ID0gc3VtKHRvdGFsX2RvbGFyKS9mcmVxdWVuY2kpICU+JSANCiAgZHBseXI6OnVuZ3JvdXAoKQ0KDQpzdW1tYXJ5KGRmX1JGTSkNCg0Ka2FibGVFeHRyYTo6a2FibGUoaGVhZChkZl9SRk0pKQ0KDQpgYGANCg0KIyMjIEFudGlnw7xlZGFkDQpFeHBlcmllbmNpYSByZWNpZW50ZTogwr9RdcOpIHRhbiByZWNpZW50ZSBmdWUgbGEgY29tcHJhIGRlbCBjbGllbnRlPw0KDQpgYGB7cn0NCg0KZGZfUkZNICU+JSANCiAgZHBseXI6OmZpbHRlcihtb25pdGVyeSA8IDcwMDAsIA0KICAgICAgICAgICAgICAgIGZyZXF1ZW5jaSA8IDIwMCkgJT4lIA0KICBnZ3Bsb3QyOjpnZ3Bsb3QoKSArIA0KICBnZ3Bsb3QyOjphZXMoUmVjZW5jeSkgKyANCiAgZ2dwbG90Mjo6Z2VvbV9oaXN0b2dyYW0oY29sID0gJ3JlZCcsIGZpbGwgPSAnc2t5Ymx1ZScsIGJpbnMgPSA1MCkgKyANCiAgZ2dwbG90Mjo6dGhlbWVfbWluaW1hbCgpICsgDQogIGdncGxvdDI6OnRoZW1lKHRleHQgPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoZmFtaWx5ID0gJ3NlcmlmJykpDQoNCmBgYA0KDQojIyMgRnJlY3VlbmNpYQ0KRnJlY3VlbmNpYTogwr9jb24gcXXDqSBmcmVjdWVuY2lhIGNvbXByYW4/DQoNCmBgYHtyfSANCg0KZGZfUkZNICU+JSANCiAgZHBseXI6OmZpbHRlcihtb25pdGVyeSA8IDcwMDAsIA0KICAgICAgICAgICAgICAgIGZyZXF1ZW5jaSA8IDIwMCkgJT4lIA0KICBnZ3Bsb3QyOjpnZ3Bsb3QoKSArIA0KICBnZ3Bsb3QyOjphZXMoZnJlcXVlbmNpKSArIA0KICBnZ3Bsb3QyOjpnZW9tX2hpc3RvZ3JhbShjb2wgPSAncmVkJywgZmlsbCA9ICdza3libHVlJywgYmlucyA9IDUwKSArIA0KICBnZ3Bsb3QyOjp0aGVtZV9taW5pbWFsKCkgKyANCiAgZ2dwbG90Mjo6dGhlbWUodGV4dCA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChmYW1pbHkgPSAnc2VyaWYnKSkNCg0KYGBgDQoNCiMjIyBNb25ldGFyaW8NClZhbG9yIG1vbmV0YXJpbzogwr9jdcOhbnRvIGdhc3Rhbj8NCg0KYGBge3J9IA0KDQpkZl9SRk0gJT4lIA0KICBkcGx5cjo6ZmlsdGVyKG1vbml0ZXJ5IDwgNzAwMCwgDQogICAgICAgICAgICAgICAgZnJlcXVlbmNpIDwgMjAwKSAlPiUgDQogIGdncGxvdDI6OmdncGxvdCgpICsgDQogIGdncGxvdDI6OmFlcyhtb25pdGVyeSkgKyANCiAgZ2dwbG90Mjo6Z2VvbV9oaXN0b2dyYW0oY29sID0gJ3JlZCcsIGZpbGwgPSAnc2t5Ymx1ZScsIGJpbnMgPSA1MCkgKyANCiAgZ2dwbG90Mjo6dGhlbWVfbWluaW1hbCgpICsgDQogIGdncGxvdDI6OnRoZW1lKHRleHQgPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoZmFtaWx5ID0gJ3NlcmlmJykpDQoNCmBgYA0KDQpEZWJpZG8gYSBxdWUgbG9zIGRhdG9zIGVzdMOhbiByZWFsbWVudGUgc2VzZ2Fkb3MsIHVzYW1vcyBsYSBlc2NhbGEgbG9nYXLDrXRtaWNhIHBhcmEgbm9ybWFsaXphci4NCg0KYGBge3J9DQoNCmRmX1JGTSRtb25pdGVyeSA8LSBsb2coZGZfUkZNJG1vbml0ZXJ5KQ0KDQpkZl9SRk0gJT4lIA0KICBkcGx5cjo6ZmlsdGVyKG1vbml0ZXJ5IDwgNzAwMCwgDQogICAgICAgICAgICAgICAgZnJlcXVlbmNpIDwgMjAwKSAlPiUgDQogIGdncGxvdDI6OmdncGxvdCgpICsgDQogIGdncGxvdDI6OmFlcyhtb25pdGVyeSkgKyANCiAgZ2dwbG90Mjo6Z2VvbV9oaXN0b2dyYW0oY29sID0gJ3JlZCcsIGZpbGwgPSAnc2t5Ymx1ZScsIGJpbnMgPSA1MCkgKyANCiAgZ2dwbG90Mjo6dGhlbWVfbWluaW1hbCgpICsgDQogIGdncGxvdDI6OnRoZW1lKHRleHQgPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoZmFtaWx5ID0gJ3NlcmlmJykpDQpgYGANCg0KIyMgUkZNIEFuYWxpc2lzIHsudGFic2V0fQ0KDQpgYGB7cn0NCnJmbV9yZXN1bHQgPC0gDQogIHJmbTo6cmZtX3RhYmxlX2N1c3RvbWVyKGRmX1JGTSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIGN1c3RvbWVyX2lkID0gQ3VzdG9tZXJJRCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgcmVjZW5jeV9kYXlzID0gUmVjZW5jeSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbl90cmFuc2FjdGlvbnMgPSBmcmVxdWVuY2ksDQogICAgICAgICAgICAgICAgICAgICAgICAgIHRvdGFsX3JldmVudWUgPSBtb25pdGVyeSkNCg0KYGBgDQoNCiMjIyBIZWF0IE1hcA0KYGBge3J9DQpyZm06OnJmbV9oZWF0bWFwKHJmbV9yZXN1bHQpDQpgYGANCg0KIyMjIEJhciBDaGFydA0KYGBge3J9DQpyZm06OnJmbV9iYXJfY2hhcnQocmZtX3Jlc3VsdCkNCmBgYA0KDQojIyMgSGlzdG9ncmFtDQpgYGB7cn0NCnJmbTo6cmZtX2hpc3RvZ3JhbXMocmZtX3Jlc3VsdCkNCmBgYA0KDQojIyMgQ3VzdG9tZXJzIGJ5IE9yZGVycw0KYGBge3J9DQpyZm06OnJmbV9vcmRlcl9kaXN0KHJmbV9yZXN1bHQpDQoNCmBgYA0KDQojIyBSZWZpbmFtaWVudG8gZGVsIGFuYWxpc2lzDQoNCmBgYHtyIGluY2x1ZGU9RkFMU0V9DQpsaWJyYXJ5KHNxbGRmKQ0KDQpCREYgPC0gYXMuZGF0YS5mcmFtZShyZm1fcmVzdWx0JHJmbSkNCkJERiA8LSANCiAgcmVzaGFwZTo6cmVuYW1lKEJERiwgDQogICAgICAgICAgICAgICAgICBjKHJlY2VuY3lfc2NvcmUgPSAicmZtX3JlY2VuY3kiLCANCiAgICAgICAgICAgICAgICAgICAgZnJlcXVlbmN5X3Njb3JlID0gInJmbV9mcmVxIiwgDQogICAgICAgICAgICAgICAgICAgIG1vbmV0YXJ5X3Njb3JlID0gInJmbV9tb25ldGFyeSIpKQ0KDQpCREYgPC0gc3FsZGYoInNlbGVjdA0KY3VzdG9tZXJfaWQscmVjZW5jeV9kYXlzLHRyYW5zYWN0aW9uX2NvdW50LGFtb3VudCxyZm1fcmVjZW5jeSxyZm1fZnJlcSxyZm1fbW9uZXRhcnkscmZtX3Njb3JlLA0KY2FzZSANCndoZW4gKHJmbV9yZWNlbmN5IGJldHdlZW4gNCBhbmQgNSkgYW5kIChyZm1fZnJlcSBiZXR3ZWVuIDQgYW5kIDUgYW5kIHJmbV9tb25ldGFyeSBiZXR3ZWVuIDQgYW5kIDUpIHRoZW4gJ0NhbXBlb25lcycNCndoZW4gKHJmbV9yZWNlbmN5IGJldHdlZW4gMiBhbmQgNSkgYW5kIChyZm1fZnJlcSBiZXR3ZWVuIDIgYW5kIDUgYW5kIHJmbV9tb25ldGFyeSBiZXR3ZWVuIDIgYW5kIDUpIHRoZW4gJ0NsaWVudGVzIGxlYWxlcycNCndoZW4gKHJmbV9yZWNlbmN5IGJldHdlZW4gMyBhbmQgNSkgYW5kIChyZm1fZnJlcSBiZXR3ZWVuIDEgYW5kIDMgYW5kIHJmbV9tb25ldGFyeSBiZXR3ZWVuIDEgYW5kIDMpIHRoZW4gJ0xlYWwgcG90ZW5jaWFsJw0Kd2hlbiAocmZtX3JlY2VuY3kgYmV0d2VlbiA0IGFuZCA1KSBhbmQgKHJmbV9mcmVxIGJldHdlZW4gMCBhbmQgMSBhbmQgcmZtX21vbmV0YXJ5IGJldHdlZW4gMCBhbmQgMSkgdGhlbiAnQ2xpZW50ZXMgcmVjaWVudGVzJw0Kd2hlbiAocmZtX3JlY2VuY3kgYmV0d2VlbiAzIGFuZCA0KSBhbmQgKHJmbV9mcmVxIGJldHdlZW4gMCBhbmQgMSBhbmQgcmZtX21vbmV0YXJ5IGJldHdlZW4gMCBhbmQgMSkgdGhlbiAnUHJvbWV0ZWRvcicNCndoZW4gKHJmbV9yZWNlbmN5IGJldHdlZW4gMiBhbmQgMykgYW5kIChyZm1fZnJlcSBiZXR3ZWVuIDIgYW5kIDMgYW5kIHJmbV9tb25ldGFyeSBiZXR3ZWVuIDIgYW5kIDMpIHRoZW4gJ0NsaWVudGVzIHF1ZSBuZWNlc2l0YW4gYXRlbmNpb24nDQp3aGVuIChyZm1fcmVjZW5jeSBiZXR3ZWVuIDIgYW5kIDMpIGFuZCAocmZtX2ZyZXEgYmV0d2VlbiAwIGFuZCAyIGFuZCByZm1fbW9uZXRhcnkgYmV0d2VlbiAwIGFuZCAyKSB0aGVuICdBIHB1bnRvIGRlIGRvcm1pcicNCndoZW4gKHJmbV9yZWNlbmN5IGJldHdlZW4gMCBhbmQgMikgYW5kIChyZm1fZnJlcSBiZXR3ZWVuIDIgYW5kIDUgYW5kIHJmbV9tb25ldGFyeSBiZXR3ZWVuIDIgYW5kIDUpIHRoZW4gJ0VuIHJpZXNnbycNCndoZW4gKHJmbV9yZWNlbmN5IGJldHdlZW4gMCBhbmQgMSkgYW5kIChyZm1fZnJlcSBiZXR3ZWVuIDQgYW5kIDUgYW5kIHJmbV9tb25ldGFyeSBiZXR3ZWVuIDQgYW5kIDUpIHRoZW4gJ05vIHB1ZWRvIFBlcmRlcmxvcycNCndoZW4gKHJmbV9yZWNlbmN5IGJldHdlZW4gMSBhbmQgMikgYW5kIChyZm1fZnJlcSBiZXR3ZWVuIDEgYW5kIDIgYW5kIHJmbV9tb25ldGFyeSBiZXR3ZWVuIDEgYW5kIDIpIHRoZW4gJ0hpYmVybmFuZG8nDQp3aGVuIChyZm1fcmVjZW5jeSBiZXR3ZWVuIDAgYW5kIDIpIGFuZCAocmZtX2ZyZXEgYmV0d2VlbiAwIGFuZCAyIGFuZCByZm1fbW9uZXRhcnkgYmV0d2VlbiAwIGFuZCAyKSB0aGVuICdQZXJkaWRvJw0KZWxzZSAnb3RoZXInIGVuZCBhcyBjdXNfc2VnDQpmcm9tIEJERiIpDQpgYGANCg0KDQoNCiMjIyBTZWdtZW50YWNpb24gZGVsIGNsaWVudGU6IA0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gQkRGKSArIGFlcyh4ID0gY3VzX3NlZywgZmlsbCA9IGN1c19zZWcpKyBnZW9tX2JhcigpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1zY2FsZXM6OnBlcmNlbnQoLi5jb3VudC4uL3N1bSguLmNvdW50Li4pKSksDQogICAgICAgICAgICBzdGF0PSdjb3VudCcscG9zaXRpb249cG9zaXRpb25fZG9kZ2UoMSkpKw0KICBsYWJzKHRpdGxlID0gIlNlZ21lbnRhY2lvbiBkZWwgQ2xpZW50ZSIsIHggPSAiU2VnbWVudG8iLCB5ID0gIlRvdGFsIGRlIGNsaWVudGVzIikgKyBjb29yZF9mbGlwKCkrIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCg==