Problem to solve

The company is a shopping credit card who offer cards to the people with discount and fees to buy in the shopping, in the previous campaign the acceptance rate is near to the 20% and the late payments rate is over the 50%, they want that a algorithm returns the top 100 potentials clients.

For they it´s more important that the client don´t finish in late payment and after that the prospect accept the card, when have the results of campaign will have a return score where punish more the late payment (-7500), the accepted card with no late payment will be +5000, and if not accept no change in the result.

To solve who are the best clients i have different DB with historical information, and need to create a algorithm to solve.

1- Load the bases

I load 5 DB that we have with information, the description of each one is:

I merge the 5 DB into 1 big DB with all the information

library(reshape)
financial_report <- read.csv(file = "Base Info Financiera.csv", sep = ";", header = T, stringsAsFactors = F)
social_security <- read.csv(file = "Base info de ANSES.csv", sep = ";", header = T, stringsAsFactors = F)
cellphone <- read.csv(file = "Base Info de Telefonía Movil.csv", sep = ";", header = T, stringsAsFactors = F)
clients <- read.csv(file = "Base Info de Clientes.csv", sep = ";", header = T, stringsAsFactors = F)
previous_results <- read.csv(file = "Base Resultado Campaña.csv", sep = ";", header = T, stringsAsFactors = F)
#### JOINEO BASES
df_list <- list(financial_report, social_security, cellphone,clients,previous_results)
DataBase <- merge_recurse(df_list)
rm(financial_report, social_security, cellphone,clients,previous_results, df_list)

table (DataBase$HISTORIAL_ATRASO) #Historial_Atraso = Late payment historial

                         El ultimo mes                       En el ultimo año 
                                    60                                     47 
                      En el último año                       En el último mes 
                                   284                                     33 
                 En los últimos 5 años No registra mora en los últimos 5 años 
                                  2362                                   1214 
#El ultimo mes   = the last month                   
#En el ultimo año = the last year
#En el último año  = the last year                     
#En el último mes = the last month
#En los últimos 5 años = the last 5 years
#No registra mora en los últimos 5 años = no late payments in 5 years

2- Summary of the DB

I summary the DB to see what have each row, if we have outliers or NA values, the type of each row, and after the summary in the next steps i will take out some values or parameters

summary (DataBase)
 IDENTIFICADOR.CLIENTE TIENE_CAJAAHORRO_SISTFIN TIENE_TARJETA_SISTFIN TIENE_PRESTAMO_SISTFIN INGRESO_MENSUAL  
 Min.   :   1          Min.   :0.0000           Min.   :0.0000        Min.   :0.000          Min.   :   5000  
 1st Qu.:1001          1st Qu.:0.0000           1st Qu.:0.0000        1st Qu.:0.000          1st Qu.:  25000  
 Median :2000          Median :1.0000           Median :0.0000        Median :0.000          Median :  36000  
 Mean   :2000          Mean   :0.7037           Mean   :0.3663        Mean   :0.201          Mean   :  41717  
 3rd Qu.:3000          3rd Qu.:1.0000           3rd Qu.:1.0000        3rd Qu.:0.000          3rd Qu.:  46000  
 Max.   :4000          Max.   :1.0000           Max.   :1.0000        Max.   :1.000          Max.   :1000000  
                                                                                                              
 DEUDA_SISTEMAFINANCIERO INGRESO_FORMAL    RELACION_CUOTA_INGRESO HISTORIAL_ATRASO   TRABAJA.EN.SECTOR.PUBLICO
 Min.   :    0           Min.   :      0   Min.   :0.0000         Length:4000        Min.   :0.0000           
 1st Qu.:    0           1st Qu.:  11700   1st Qu.:0.0000         Class :character   1st Qu.:0.0000           
 Median :    0           Median :  25600   Median :0.0000         Mode  :character   Median :0.0000           
 Mean   : 4319           Mean   :  30463   Mean   :0.1546                            Mean   :0.0935           
 3rd Qu.: 5000           3rd Qu.:  36900   3rd Qu.:0.1600                            3rd Qu.:0.0000           
 Max.   :40000           Max.   :1000000   Max.   :7.2000                            Max.   :1.0000           
 NA's   :58                                                                                                   
    JUBILADO      PLANES.SOCIALES   TIENE_MOVISTAR   TIENE_PERSONAL   TIENE_CLARO          edad       
 Min.   :0.0000   Min.   :0.00000   Min.   :0.0000   Min.   :0.000   Min.   :0.0000   Min.   : 25.00  
 1st Qu.:0.0000   1st Qu.:0.00000   1st Qu.:0.0000   1st Qu.:0.000   1st Qu.:0.0000   1st Qu.: 37.00  
 Median :0.0000   Median :0.00000   Median :0.0000   Median :0.000   Median :0.0000   Median : 50.00  
 Mean   :0.1862   Mean   :0.09875   Mean   :0.4873   Mean   :0.409   Mean   :0.1037   Mean   : 49.37  
 3rd Qu.:0.0000   3rd Qu.:0.00000   3rd Qu.:1.0000   3rd Qu.:1.000   3rd Qu.:0.0000   3rd Qu.: 62.00  
 Max.   :1.0000   Max.   :1.00000   Max.   :1.0000   Max.   :1.000   Max.   :1.0000   Max.   :110.00  
                                                                                                      
 HIJOS_MENORES    ESTADO_CIVIL        PROVINCIA         NACIONALIDAD       ACEPTO.TARJETA 
 Min.   : 0.000   Length:4000        Length:4000        Length:4000        Min.   :0.000  
 1st Qu.: 0.000   Class :character   Class :character   Class :character   1st Qu.:0.000  
 Median : 1.000   Mode  :character   Mode  :character   Mode  :character   Median :0.000  
 Mean   : 1.593                                                            Mean   :0.214  
 3rd Qu.: 2.000                                                            3rd Qu.:0.000  
 Max.   :25.000                                                            Max.   :1.000  
                                                                                          
 TUVO_ATRASO_CON_LA_.TARJETA
 Min.   :0.0000             
 1st Qu.:0.0000             
 Median :0.0000             
 Mean   :0.1123             
 3rd Qu.:0.0000             
 Max.   :1.0000             
                            

3- Fill the NA values

The cells of DEUDA_SISTEMAFINANCIERO refer to know if the clients have debts with a public or private bank, in this case they have some NA, i will put in 0, thinking that if it´s no info, it´s no debts

DataBase$DEUDA_SISTEMAFINANCIERO <- ifelse(is.na(DataBase$DEUDA_SISTEMAFINANCIERO), 0, DataBase$DEUDA_SISTEMAFINANCIERO )

4- Manipulate character values

anyNA(DataBase$NACIONALIDAD)
[1] FALSE
unique(DataBase$NACIONALIDAD)
[1] "Argentina" "Armenia"   ""          "Chile"     "Albania"  
unique(DataBase$PROVINCIA)
 [1] "BUENOS AIRES"                    "CHUBUT"                          "CORDOBA"                        
 [4] "SANTA CRUZ"                      "CIUDAD AUTÓNOMA DE BUENOS AIRES" "MENDOZA"                        
 [7] "SAN JUAN"                        "SANTA FE"                        "RIO NEGRO"                      
[10] "LA PAMPA"                        "CORRIENTES"                      "ENTRE RIOS"                     
[13] "NEUQUEN"                         "TIERRA DEL FUEGO"                "CATAMARCA"                      
[16] "SALTA"                           "TUCUMAN"                         ""                               
[19] "CHACO"                           "MISIONES"                        "LA RIOJA"                       
[22] "JUJUY"                           "SANTIAGO DEL ESTERO"             "SAN LUIS"                       
[25] "FORMOSA"                        
unique(DataBase$ESTADO_CIVIL)
[1] "CASADO"     "DIVORCIADO" "VIUDO"      "SOLTERO"    ""          
table(DataBase$NACIONALIDAD)

            Albania Argentina   Armenia     Chile 
      197         1      3773        28         1 
DataBase$NACIONALIDAD <- NULL
DataBase$PROVINCIA <- NULL


table (DataBase$ESTADO_CIVIL)

               CASADO DIVORCIADO    SOLTERO      VIUDO 
        30       1795        409       1723         43 
prop.table(table(DataBase$ACEPTO.TARJETA))

    0     1 
0.786 0.214 

5- Create late payment class

DataBase$HISTORIAL_ATRASO <- ifelse(DataBase$HISTORIAL_ATRASO=="En los últimos 5 años" |DataBase$HISTORIAL_ATRASO=="No registra mora en los últimos 5 años", 0, 1 )

DataBase$ESTADO_CIVIL <- ifelse(DataBase$ESTADO_CIVIL == "", "SOLTERO",DataBase$ESTADO_CIVIL )

table (DataBase$ESTADO_CIVIL)

    CASADO DIVORCIADO    SOLTERO      VIUDO 
      1795        409       1753         43 

6- Delete outliers

boxplot(DataBase$INGRESO_MENSUAL)

boxplot(DataBase$INGRESO_FORMAL)

boxplot(DataBase$RELACION_CUOTA_INGRESO)

boxplot(DataBase$edad)

boxplot(DataBase$HIJOS_MENORES)


DataBase$INGRESO_MENSUAL <- ifelse(DataBase$INGRESO_MENSUAL > 80000, 80000,DataBase$INGRESO_MENSUAL)

DataBase$INGRESO_FORMAL <- ifelse(DataBase$INGRESO_FORMAL > 80000, 80000,DataBase$INGRESO_FORMAL)

DataBase$RELACION_CUOTA_INGRESO <- ifelse(DataBase$RELACION_CUOTA_INGRESO> 0.7, 0.7, DataBase$RELACION_CUOTA_INGRESO)

DataBase$edad <- ifelse(DataBase$edad > 80, 80, DataBase$edad)

DataBase$HIJOS_MENORES <- ifelse(DataBase$HIJOS_MENORES > 10, 10, DataBase$HIJOS_MENORES )

summary(DataBase)
 IDENTIFICADOR.CLIENTE TIENE_CAJAAHORRO_SISTFIN TIENE_TARJETA_SISTFIN TIENE_PRESTAMO_SISTFIN INGRESO_MENSUAL
 Min.   :   1          Min.   :0.0000           Min.   :0.0000        Min.   :0.000          Min.   : 5000  
 1st Qu.:1001          1st Qu.:0.0000           1st Qu.:0.0000        1st Qu.:0.000          1st Qu.:25000  
 Median :2000          Median :1.0000           Median :0.0000        Median :0.000          Median :36000  
 Mean   :2000          Mean   :0.7037           Mean   :0.3663        Mean   :0.201          Mean   :36427  
 3rd Qu.:3000          3rd Qu.:1.0000           3rd Qu.:1.0000        3rd Qu.:0.000          3rd Qu.:46000  
 Max.   :4000          Max.   :1.0000           Max.   :1.0000        Max.   :1.000          Max.   :80000  
 DEUDA_SISTEMAFINANCIERO INGRESO_FORMAL  RELACION_CUOTA_INGRESO HISTORIAL_ATRASO TRABAJA.EN.SECTOR.PUBLICO
 Min.   :    0           Min.   :    0   Min.   :0.0000         Min.   :0.000    Min.   :0.0000           
 1st Qu.:    0           1st Qu.:11700   1st Qu.:0.0000         1st Qu.:0.000    1st Qu.:0.0000           
 Median :    0           Median :25600   Median :0.0000         Median :0.000    Median :0.0000           
 Mean   : 4256           Mean   :25173   Mean   :0.1239         Mean   :0.106    Mean   :0.0935           
 3rd Qu.: 5000           3rd Qu.:36900   3rd Qu.:0.1600         3rd Qu.:0.000    3rd Qu.:0.0000           
 Max.   :40000           Max.   :80000   Max.   :0.7000         Max.   :1.000    Max.   :1.0000           
    JUBILADO      PLANES.SOCIALES   TIENE_MOVISTAR   TIENE_PERSONAL   TIENE_CLARO          edad     
 Min.   :0.0000   Min.   :0.00000   Min.   :0.0000   Min.   :0.000   Min.   :0.0000   Min.   :25.0  
 1st Qu.:0.0000   1st Qu.:0.00000   1st Qu.:0.0000   1st Qu.:0.000   1st Qu.:0.0000   1st Qu.:37.0  
 Median :0.0000   Median :0.00000   Median :0.0000   Median :0.000   Median :0.0000   Median :50.0  
 Mean   :0.1862   Mean   :0.09875   Mean   :0.4873   Mean   :0.409   Mean   :0.1037   Mean   :49.3  
 3rd Qu.:0.0000   3rd Qu.:0.00000   3rd Qu.:1.0000   3rd Qu.:1.000   3rd Qu.:0.0000   3rd Qu.:62.0  
 Max.   :1.0000   Max.   :1.00000   Max.   :1.0000   Max.   :1.000   Max.   :1.0000   Max.   :80.0  
 HIJOS_MENORES    ESTADO_CIVIL       ACEPTO.TARJETA  TUVO_ATRASO_CON_LA_.TARJETA
 Min.   : 0.000   Length:4000        Min.   :0.000   Min.   :0.0000             
 1st Qu.: 0.000   Class :character   1st Qu.:0.000   1st Qu.:0.0000             
 Median : 1.000   Mode  :character   Median :0.000   Median :0.0000             
 Mean   : 1.331                      Mean   :0.214   Mean   :0.1123             
 3rd Qu.: 2.000                      3rd Qu.:0.000   3rd Qu.:0.0000             
 Max.   :10.000                      Max.   :1.000   Max.   :1.0000             

7- Separate into training and test

I separated the DB into two parts, the 80% of the results in training to the machine learning model, and the 20% in testing to predict and see how its worked the algorithm.

The new database are Entrenamiento (training) and Validacion (testing)

set.seed(3234)
muestra <- floor(nrow(DataBase)*0.8)
trIndex <- sample(nrow(DataBase), muestra, replace=F)
vaIndex <- seq_len(nrow(DataBase))[!(seq_len(nrow(DataBase)) %in% trIndex)]

Entrenamiento <- DataBase[trIndex,]
Validacion <- DataBase[vaIndex,]

8- Create logistic regression

A logistic regression to determinate if the person will accept or no the card its the first step, in this case i want to determinate the acceptation or no of the card with this data:

DataBaseRegresionTarjeta <- DataBase

modelo <- glm(DataBaseRegresionTarjeta$ACEPTO.TARJETA ~ TIENE_CAJAAHORRO_SISTFIN + INGRESO_FORMAL + edad + JUBILADO + PLANES.SOCIALES, data=DataBaseRegresionTarjeta, family=binomial(link="logit"))

summary(modelo)

Call:
glm(formula = DataBaseRegresionTarjeta$ACEPTO.TARJETA ~ TIENE_CAJAAHORRO_SISTFIN + 
    INGRESO_FORMAL + edad + JUBILADO + PLANES.SOCIALES, family = binomial(link = "logit"), 
    data = DataBaseRegresionTarjeta)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.5749  -0.5527  -0.3941  -0.3418   2.4200  

Coefficients:
                           Estimate Std. Error z value Pr(>|z|)    
(Intercept)              -1.103e+00  2.266e-01  -4.870 1.12e-06 ***
TIENE_CAJAAHORRO_SISTFIN -9.011e-01  9.451e-02  -9.534  < 2e-16 ***
INGRESO_FORMAL           -7.924e-06  2.623e-06  -3.021  0.00252 ** 
edad                     -7.614e-03  4.483e-03  -1.698  0.08941 .  
JUBILADO                  2.295e+00  1.436e-01  15.983  < 2e-16 ***
PLANES.SOCIALES           2.543e+00  1.303e-01  19.519  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 4153.7  on 3999  degrees of freedom
Residual deviance: 3114.3  on 3994  degrees of freedom
AIC: 3126.3

Number of Fisher Scoring iterations: 5

9 - Predict over testing

I predict into the testing model the col of accept the card or no.

prediccion <- predict(modelo,Validacion,type='response')
str(prediccion)
 Named num [1:800] 0.6555 0.1532 0.0638 0.077 0.0532 ...
 - attr(*, "names")= chr [1:800] "1" "2" "13" "14" ...
summary(prediccion)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
0.04299 0.07345 0.09684 0.21805 0.32159 0.96340 
Validacion$Pred <- prediccion

10 - The ROCR curve

I calculated the ROCR curve, the idea it´s that this results was over 0.75

library(ROCR)
pred_lrr <- prediction(prediccion, Validacion$ACEPTO.TARJETA)
auc <- performance(pred_lrr,"auc")
auc@y.values[[1]]
[1] 0.8186458

11 - Create a DB for late payment

We create a new database where only have the people who accept the cards, because if we want to made the late payment with all people (accepted or no the card) we will have a fail result

DataBaseMora <- subset(DataBase, subset = DataBase$ACEPTO.TARJETA == 1)

12- Separate into training and test

I separated the DB of late payment into two parts, the 80% of the results in training to the machine learning model, and the 20% in testing to predict and see how its worked the algorithm.

The new database are Entrenamiento (training) and Validacion (testing)

set.seed(3234)
muestraMora <- floor(nrow(DataBaseMora)*0.8)
trIndexMora <- sample(nrow(DataBaseMora), muestraMora, replace=F)
vaIndexMora <- seq_len(nrow(DataBaseMora))[!(seq_len(nrow(DataBaseMora)) %in% trIndexMora)]

EntrenamientoMora <- DataBaseMora[trIndexMora,]
ValidacionMora <- DataBaseMora[vaIndexMora,]

13- Create tree

In this case for the late payment i will use two methods, the tree and the logistic regression, first i created the tree with :

library(rpart)
library(rpart.plot)
fit <- rpart(EntrenamientoMora$TUVO_ATRASO_CON_LA_.TARJETA ~ TIENE_CAJAAHORRO_SISTFIN + TIENE_PRESTAMO_SISTFIN + INGRESO_MENSUAL + DEUDA_SISTEMAFINANCIERO + JUBILADO + PLANES.SOCIALES, data=EntrenamientoMora)
rpart.plot(fit, extra=0, type=2)

14 - Predict over testing

I predict into the late payment testing model the col of have late payment or no with a tree.

prediccionMora <- predict(fit,ValidacionMora,method='class')

ValidacionMora$Pred <- prediccionMora

15- Create the logistic

A logistic regression to determinate if the person will be or no in the late payment its the next step, in this case i want to determinate with the same data of the tree

modeloMora <- glm(DataBaseMora$TUVO_ATRASO_CON_LA_.TARJETA ~ TIENE_CAJAAHORRO_SISTFIN + TIENE_PRESTAMO_SISTFIN + INGRESO_MENSUAL + DEUDA_SISTEMAFINANCIERO + JUBILADO + PLANES.SOCIALES, data=DataBaseMora, family=binomial(link="logit"))

summary(modeloMora)

Call:
glm(formula = DataBaseMora$TUVO_ATRASO_CON_LA_.TARJETA ~ TIENE_CAJAAHORRO_SISTFIN + 
    TIENE_PRESTAMO_SISTFIN + INGRESO_MENSUAL + DEUDA_SISTEMAFINANCIERO + 
    JUBILADO + PLANES.SOCIALES, family = binomial(link = "logit"), 
    data = DataBaseMora)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.5904  -0.6308   0.1900   0.6335   2.5658  

Coefficients:
                           Estimate Std. Error z value Pr(>|z|)    
(Intercept)               2.188e+00  2.810e-01   7.787 6.84e-15 ***
TIENE_CAJAAHORRO_SISTFIN -1.044e+00  2.314e-01  -4.512 6.43e-06 ***
TIENE_PRESTAMO_SISTFIN    1.317e+00  2.834e-01   4.645 3.40e-06 ***
INGRESO_MENSUAL          -3.592e-05  6.630e-06  -5.417 6.06e-08 ***
DEUDA_SISTEMAFINANCIERO   5.778e-05  1.653e-05   3.495 0.000474 ***
JUBILADO                 -2.638e+00  1.892e-01 -13.941  < 2e-16 ***
PLANES.SOCIALES           1.125e+00  2.037e-01   5.526 3.28e-08 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 1184.61  on 855  degrees of freedom
Residual deviance:  748.74  on 849  degrees of freedom
AIC: 762.74

Number of Fisher Scoring iterations: 5

16 - Predict over testing

I predict into the late payment testing model the col of have late payment or no with a logistic.

prediccionMora2 <- predict(modeloMora,ValidacionMora,type='response')

ValidacionMora$Pred2 <- prediccionMora2

17 - The ROCR curve

I calculated the ROCR curve, the idea it´s that this results was over 0.75

pred_lrr <- prediction(prediccionMora, ValidacionMora$TUVO_ATRASO_CON_LA_.TARJETA)
auc <- performance(pred_lrr,"auc")
auc@y.values[[1]]
[1] 0.8491076

18 - Load the new potential clients

I load the database with information of the new potentials clients that the company card want to call to do a marketing campaign

Base_Nuevos_clientes <- read.csv(file = "BASE TEST_sin_targets.csv", sep = ";", header = T, stringsAsFactors = F)

19 - Summary the new DB

I load the database with information of the new potentials clients that the company card want to call to do a marketing campaign

summary(Base_Nuevos_clientes)
 IDENTIFICADOR.CLIENTE TIENE_CAJAAHORRO_SISTFIN TIENE_TARJETA_SISTFIN TIENE_PRESTAMO_SISTFIN
 Min.   :4001          Min.   :0.0000           Min.   :0.0000        Min.   :0.0000        
 1st Qu.:4250          1st Qu.:0.0000           1st Qu.:0.0000        1st Qu.:0.0000        
 Median :4500          Median :1.0000           Median :0.0000        Median :0.0000        
 Mean   :4500          Mean   :0.7097           Mean   :0.3734        Mean   :0.1832        
 3rd Qu.:4750          3rd Qu.:1.0000           3rd Qu.:1.0000        3rd Qu.:0.0000        
 Max.   :4999          Max.   :1.0000           Max.   :1.0000        Max.   :1.0000        
 TRABAJA.EN.SECTOR.PUBLICO    JUBILADO      PLANES.SOCIALES       edad        HIJOS_MENORES  
 Min.   :0.0000            Min.   :0.0000   Min.   :0.0000   Min.   : 25.00   Min.   :0.000  
 1st Qu.:0.0000            1st Qu.:0.0000   1st Qu.:0.0000   1st Qu.: 37.00   1st Qu.:0.000  
 Median :0.0000            Median :0.0000   Median :0.0000   Median : 50.00   Median :1.000  
 Mean   :0.1031            Mean   :0.1792   Mean   :0.0981   Mean   : 49.21   Mean   :1.134  
 3rd Qu.:0.0000            3rd Qu.:0.0000   3rd Qu.:0.0000   3rd Qu.: 62.00   3rd Qu.:2.000  
 Max.   :1.0000            Max.   :1.0000   Max.   :1.0000   Max.   :110.00   Max.   :8.000  
 ESTADO_CIVIL        PROVINCIA         NACIONALIDAD       TIENE_MOVISTAR   TIENE_PERSONAL    TIENE_CLARO    
 Length:999         Length:999         Length:999         Min.   :0.0000   Min.   :0.0000   Min.   :0.0000  
 Class :character   Class :character   Class :character   1st Qu.:0.0000   1st Qu.:0.0000   1st Qu.:0.0000  
 Mode  :character   Mode  :character   Mode  :character   Median :0.0000   Median :0.0000   Median :0.0000  
                                                          Mean   :0.3363   Mean   :0.3283   Mean   :0.3353  
                                                          3rd Qu.:1.0000   3rd Qu.:1.0000   3rd Qu.:1.0000  
                                                          Max.   :1.0000   Max.   :1.0000   Max.   :1.0000  
 INGRESO_MENSUAL DEUDA_SISTEMAFINANCIERO INGRESO_FORMAL  RELACION_CUOTA_INGRESO HISTORIAL_ATRASO  
 Min.   : 5000   Min.   :    0           Min.   :    0   Min.   :0.0000         Length:999        
 1st Qu.:25000   1st Qu.:    0           1st Qu.:12600   1st Qu.:0.0000         Class :character  
 Median :35000   Median :    0           Median :25600   Median :0.0000         Mode  :character  
 Mean   :36288   Mean   : 4498           Mean   :25243   Mean   :0.1591                           
 3rd Qu.:46000   3rd Qu.: 6000           3rd Qu.:36000   3rd Qu.:0.1900                           
 Max.   :75000   Max.   :40000           Max.   :75000   Max.   :3.6000                           

20 - ETL process

I will make in only one step all the clean and transform to the DB similar to the original

Base_Nuevos_clientes$NACIONALIDAD <- NULL
Base_Nuevos_clientes$PROVINCIA <- NULL

Base_Nuevos_clientes$HISTORIAL_ATRASO <- ifelse(Base_Nuevos_clientes$HISTORIAL_ATRASO=="En los últimos 5 años" |Base_Nuevos_clientes$HISTORIAL_ATRASO=="No registra mora en los últimos 5 años", 0, 1 )

Base_Nuevos_clientes$ESTADO_CIVIL <- ifelse(Base_Nuevos_clientes$ESTADO_CIVIL == "", "SOLTERO",Base_Nuevos_clientes$ESTADO_CIVIL )

Base_Nuevos_clientes$INGRESO_MENSUAL <- ifelse(Base_Nuevos_clientes$INGRESO_MENSUAL > 80000, 80000,Base_Nuevos_clientes$INGRESO_MENSUAL)

Base_Nuevos_clientes$INGRESO_FORMAL <- ifelse(Base_Nuevos_clientes$INGRESO_FORMAL > 80000, 80000,Base_Nuevos_clientes$INGRESO_FORMAL)

Base_Nuevos_clientes$RELACION_CUOTA_INGRESO <- ifelse(Base_Nuevos_clientes$RELACION_CUOTA_INGRESO> 0.7, 0.7, Base_Nuevos_clientes$RELACION_CUOTA_INGRESO)

Base_Nuevos_clientes$edad <- ifelse(Base_Nuevos_clientes$edad > 80, 80, Base_Nuevos_clientes$edad)

Base_Nuevos_clientes$HIJOS_MENORES <- ifelse(Base_Nuevos_clientes$HIJOS_MENORES > 10, 10, Base_Nuevos_clientes$HIJOS_MENORES )

21 - Predict late payment

I will predict the late payment into the new clients with the tree and regression made before, with a assemble between row means.

prediccionMora <- predict(fit,Base_Nuevos_clientes,method='class')
prediccionMora2 <- predict(modeloMora,Base_Nuevos_clientes,type='response')

summary(prediccionMora)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
0.07692 0.21875 0.67857 0.57140 0.67857 1.00000 
Base_Nuevos_clientes$PredMora <- prediccionMora
Base_Nuevos_clientes$PredMora2 <- prediccionMora2

Base_Nuevos_clientes$ensamble <- rowMeans(Base_Nuevos_clientes[,c("PredMora", "PredMora2")])

22 - Predict for accept the card

I will predict the acceptation of the card into the new clients with the regression made before

prediccion <- predict(modelo,Base_Nuevos_clientes,type='response')
str(prediccion)
 Named num [1:999] 0.0715 0.0626 0.0814 0.077 0.0581 ...
 - attr(*, "names")= chr [1:999] "1" "2" "3" "4" ...
summary(prediccion)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
0.04465 0.07147 0.08976 0.20912 0.21266 0.96340 
Base_Nuevos_clientes$PredAceptacion <- prediccion

23 - Create the best clients

Now the next step it´s create the table with the 100 best potential clients, thinking that it´s more important that the new client don´t finish in late payment and then the probability of accept the card.

Only sned the IDENTIFICADOR.CLIENTE (Client ID)


summary(Base_Nuevos_clientes$PredAceptacion)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
0.04465 0.07147 0.08976 0.20912 0.21266 0.96340 
summary(Base_Nuevos_clientes$PredMora)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
0.07692 0.21875 0.67857 0.57140 0.67857 1.00000 
BaseMejoresClientes <- subset(Base_Nuevos_clientes, subset = Base_Nuevos_clientes$PredAceptacion > 0.20 & Base_Nuevos_clientes$ensamble < 0.175)

BaseMejoresClientes <- BaseMejoresClientes %>% select(IDENTIFICADOR.CLIENTE)

24 - Write a CSV table

The final step is to save the table in a CSV with the 100 best client to send a the card company

write.table (BaseMejoresClientes,file="Clientes_potenciales.csv",row.names=FALSE,col.names=TRUE,quote=TRUE,sep=",")

Results

After a time the company gave me back the results of the campaign over this 100 clients

25 - loads the results

The result have the 100 observation of the clients that was sended and says if accept the card of it was in late payment.

results <- read.csv(file = "resultado.csv", sep = ";", header = T, stringsAsFactors = F)

26 - The performance

Let´s graph the performance, remember that acceptance of card is 20% and late payment is 50%

library(ggplot2)
DF_graph = data.frame(resultado = "variable", acceptance = sum(results$ACEPTO.TARJETA)/100 , latepayment = sum(results$TUVO_ATRASO_CON_LA_.TARJETA)/100)

DF_graph = melt(data= DF_graph, id="resultado")

DF_graph %>%
ggplot(aes(x = variable , y= value, fill = variable)) +
    geom_col() +
    labs(x = "variable", y = "% of", title = "Final results of campaign") +
  scale_y_continuous(limits = c(0.00, 0.99)) +
  theme_replace()

Conclusion

The algorithm performance was very good, the percentage of acceptation was improve from 20% to 43% and the late payment that was the main problem was reduced from 50% to 3%.

This work demonstrates the importance of machine learning to improve business

LS0tDQp0aXRsZTogIkNsYXNzIGFuYWx5c2lzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KYGBge3IgZWNobz1GQUxTRX0NCnNldHdkKCJKOi9Xb3JrU2hvcDItIE1hY2hpbmUgTGVhcm5pbmcgQ2xhc2lmaWNhY2lvbi8wMi0gTWFjaGluZSBMZWFybmluZyBDbGFzaWZpY2FjaW9uL0Jhc2VzIikNCmBgYA0KDQo8Y2VudGVyPiA8c3BhbiBzdHlsZT0iY29sb3I6ICMwNzcxODc7Ij4gPGgyPiBQcm9ibGVtIHRvIHNvbHZlIDwvaDI+IDwvc3Bhbj4gPC9jZW50ZXI+DQoNClRoZSBjb21wYW55IGlzIGEgc2hvcHBpbmcgY3JlZGl0IGNhcmQgd2hvIG9mZmVyIGNhcmRzIHRvIHRoZSBwZW9wbGUgd2l0aCBkaXNjb3VudCBhbmQgZmVlcyB0byBidXkgaW4gdGhlIHNob3BwaW5nLCBpbiB0aGUgcHJldmlvdXMgY2FtcGFpZ24gdGhlIGFjY2VwdGFuY2UgcmF0ZSBpcyBuZWFyIHRvIHRoZSAyMCUgYW5kIHRoZSBsYXRlIHBheW1lbnRzIHJhdGUgaXMgb3ZlciB0aGUgNTAlLCB0aGV5IHdhbnQgdGhhdCBhIGFsZ29yaXRobSByZXR1cm5zIHRoZSB0b3AgMTAwIHBvdGVudGlhbHMgY2xpZW50cy4NCg0KRm9yIHRoZXkgaXTCtHMgbW9yZSBpbXBvcnRhbnQgdGhhdCB0aGUgY2xpZW50IGRvbsK0dCBmaW5pc2ggaW4gbGF0ZSBwYXltZW50IGFuZCBhZnRlciB0aGF0IHRoZSBwcm9zcGVjdCBhY2NlcHQgdGhlIGNhcmQsIHdoZW4gIGhhdmUgdGhlIHJlc3VsdHMgb2YgY2FtcGFpZ24gd2lsbCBoYXZlIGEgcmV0dXJuIHNjb3JlIHdoZXJlIHB1bmlzaCBtb3JlIHRoZSBsYXRlIHBheW1lbnQgKC03NTAwKSwgdGhlIGFjY2VwdGVkIGNhcmQgd2l0aCBubyBsYXRlIHBheW1lbnQgd2lsbCBiZSArNTAwMCwgYW5kIGlmIG5vdCBhY2NlcHQgbm8gY2hhbmdlIGluIHRoZSByZXN1bHQuDQoNClRvIHNvbHZlIHdobyBhcmUgdGhlIGJlc3QgY2xpZW50cyBpIGhhdmUgZGlmZmVyZW50IERCIHdpdGggaGlzdG9yaWNhbCBpbmZvcm1hdGlvbiwgYW5kIG5lZWQgdG8gY3JlYXRlIGEgYWxnb3JpdGhtIHRvIHNvbHZlLg0KDQo8Y2VudGVyPiA8c3BhbiBzdHlsZT0iY29sb3I6ICM5ZDAyMDg7Ij4gPGgzPiAxLSBMb2FkIHRoZSBiYXNlcyA8L2gzPiA8L3NwYW4+IDwvY2VudGVyPg0KDQpJIGxvYWQgNSBEQiB0aGF0IHdlIGhhdmUgd2l0aCBpbmZvcm1hdGlvbiwgdGhlIGRlc2NyaXB0aW9uIG9mIGVhY2ggb25lIGlzOg0KDQogIC0gKipmaW5hbmNpYWxfcmVwb3J0Kio6IEJhbmsgYW5kIGNyZWRpdCBjYXJkIGluZm8sIGxvYW5zLCBkZWJ0cywgbnVtYmVycyBvZiBjYXJkcywgZXRjLg0KICANCiAgLSAqKnNvY2lhbF9zZWN1cml0eSoqOiBOdW1iZXJzIG9mIGNoaWxkLCB3YWdlLCBwbGFjZSB3aGVyZSBsaXZlLCByZXRpcmVkLCBwZW5zaW9uZXIsIHdvcmsgaW4gcHVibGljIG9yIGluIHByaXZhdGUuDQogIA0KICAtICoqY2VsbHBob25lKio6IEluZm9ybWF0aW9uIGFib3V0IG51bWJlcnMgb2YgbGluZSwgY29tcGFueSBvZiB0aGUgcGhvbmUgbGluZSwgbGF0ZSBwYXltZW50LCBtb3ZlbWVudHMuDQogIA0KICAtICoqY2xpZW50cyoqOiBJbnRlcm5hbCBpbmZvcm1hdGlvbiBvZiBjbGllbnRzIHRoYXQgaGF2ZSBzaG9wcGluZyBjYXJkLCBsYXRlIHBheW1lbnRzLCBkYXlzIG9mIGxhdGUsIG1vdmVtZW50cyB3aXRoIHRoZSBjYXJkLg0KICANCiAgLSAqKnByZXZpb3VzIHJlc3VsdHMqKjogSW5mb3JtYXRpb24gZm9yIG90aGVyIGNhbXBhaWducyB0aGF0IHRoZSBjb21wYW55IG1hZGUsIHdobyBhY2NlcHRzIHRoZSBjYXJkIGl0cyB0aGUgbW9zdCBpbXBvcnRhbnQuDQogIA0KSSBtZXJnZSB0aGUgNSBEQiBpbnRvIDEgYmlnIERCIHdpdGggYWxsIHRoZSBpbmZvcm1hdGlvbg0KDQoNCmBgYHtyfQ0KbGlicmFyeShyZXNoYXBlKQ0KZmluYW5jaWFsX3JlcG9ydCA8LSByZWFkLmNzdihmaWxlID0gIkJhc2UgSW5mbyBGaW5hbmNpZXJhLmNzdiIsIHNlcCA9ICI7IiwgaGVhZGVyID0gVCwgc3RyaW5nc0FzRmFjdG9ycyA9IEYpDQpzb2NpYWxfc2VjdXJpdHkgPC0gcmVhZC5jc3YoZmlsZSA9ICJCYXNlIGluZm8gZGUgQU5TRVMuY3N2Iiwgc2VwID0gIjsiLCBoZWFkZXIgPSBULCBzdHJpbmdzQXNGYWN0b3JzID0gRikNCmNlbGxwaG9uZSA8LSByZWFkLmNzdihmaWxlID0gIkJhc2UgSW5mbyBkZSBUZWxlZm9uw61hIE1vdmlsLmNzdiIsIHNlcCA9ICI7IiwgaGVhZGVyID0gVCwgc3RyaW5nc0FzRmFjdG9ycyA9IEYpDQpjbGllbnRzIDwtIHJlYWQuY3N2KGZpbGUgPSAiQmFzZSBJbmZvIGRlIENsaWVudGVzLmNzdiIsIHNlcCA9ICI7IiwgaGVhZGVyID0gVCwgc3RyaW5nc0FzRmFjdG9ycyA9IEYpDQpwcmV2aW91c19yZXN1bHRzIDwtIHJlYWQuY3N2KGZpbGUgPSAiQmFzZSBSZXN1bHRhZG8gQ2FtcGHDsWEuY3N2Iiwgc2VwID0gIjsiLCBoZWFkZXIgPSBULCBzdHJpbmdzQXNGYWN0b3JzID0gRikNCiMjIyMgSk9JTkVPIEJBU0VTDQpkZl9saXN0IDwtIGxpc3QoZmluYW5jaWFsX3JlcG9ydCwgc29jaWFsX3NlY3VyaXR5LCBjZWxscGhvbmUsY2xpZW50cyxwcmV2aW91c19yZXN1bHRzKQ0KRGF0YUJhc2UgPC0gbWVyZ2VfcmVjdXJzZShkZl9saXN0KQ0Kcm0oZmluYW5jaWFsX3JlcG9ydCwgc29jaWFsX3NlY3VyaXR5LCBjZWxscGhvbmUsY2xpZW50cyxwcmV2aW91c19yZXN1bHRzLCBkZl9saXN0KQ0KDQp0YWJsZSAoRGF0YUJhc2UkSElTVE9SSUFMX0FUUkFTTykgI0hpc3RvcmlhbF9BdHJhc28gPSBMYXRlIHBheW1lbnQgaGlzdG9yaWFsDQoNCg0KI0VsIHVsdGltbyBtZXMgICA9IHRoZSBsYXN0IG1vbnRoICAgICAgICAgICAgICAgICAgIA0KI0VuIGVsIHVsdGltbyBhw7FvID0gdGhlIGxhc3QgeWVhcg0KI0VuIGVsIMO6bHRpbW8gYcOxbyAgPSB0aGUgbGFzdCB5ZWFyICAgICAgICAgICAgICAgICAgICAgDQojRW4gZWwgw7psdGltbyBtZXMgPSB0aGUgbGFzdCBtb250aA0KI0VuIGxvcyDDumx0aW1vcyA1IGHDsW9zID0gdGhlIGxhc3QgNSB5ZWFycw0KI05vIHJlZ2lzdHJhIG1vcmEgZW4gbG9zIMO6bHRpbW9zIDUgYcOxb3MgPSBubyBsYXRlIHBheW1lbnRzIGluIDUgeWVhcnMNCmBgYA0KDQoNCjxjZW50ZXI+IDxzcGFuIHN0eWxlPSJjb2xvcjogIzlkMDIwODsiPiA8aDM+IDItIFN1bW1hcnkgb2YgdGhlIERCIDwvaDM+IDwvc3Bhbj4gPC9jZW50ZXI+DQoNCkkgc3VtbWFyeSB0aGUgREIgdG8gc2VlIHdoYXQgaGF2ZSBlYWNoIHJvdywgaWYgd2UgaGF2ZSBvdXRsaWVycyBvciBOQSB2YWx1ZXMsIHRoZSB0eXBlIG9mIGVhY2ggcm93LCBhbmQgYWZ0ZXIgdGhlIHN1bW1hcnkgaW4gdGhlIG5leHQgc3RlcHMgaSB3aWxsIHRha2Ugb3V0IHNvbWUgdmFsdWVzIG9yIHBhcmFtZXRlcnMNCg0KYGBge3J9DQpzdW1tYXJ5IChEYXRhQmFzZSkNCmBgYA0KDQoNCg0KPGNlbnRlcj4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjOWQwMjA4OyI+IDxoMz4gMy0gRmlsbCB0aGUgTkEgdmFsdWVzIDwvaDM+IDwvc3Bhbj4gPC9jZW50ZXI+DQoNClRoZSBjZWxscyBvZiBERVVEQV9TSVNURU1BRklOQU5DSUVSTyByZWZlciB0byBrbm93IGlmIHRoZSBjbGllbnRzIGhhdmUgZGVidHMgd2l0aCBhIHB1YmxpYyBvciBwcml2YXRlIGJhbmssIGluIHRoaXMgY2FzZSB0aGV5IGhhdmUgc29tZSBOQSwgaSB3aWxsIHB1dCBpbiAwLCB0aGlua2luZyB0aGF0IGlmIGl0wrRzIG5vIGluZm8sIGl0wrRzIG5vIGRlYnRzDQoNCg0KYGBge3J9DQpEYXRhQmFzZSRERVVEQV9TSVNURU1BRklOQU5DSUVSTyA8LSBpZmVsc2UoaXMubmEoRGF0YUJhc2UkREVVREFfU0lTVEVNQUZJTkFOQ0lFUk8pLCAwLCBEYXRhQmFzZSRERVVEQV9TSVNURU1BRklOQU5DSUVSTyApDQpgYGANCg0KPGNlbnRlcj4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjOWQwMjA4OyI+IDxoMz4gNC0gTWFuaXB1bGF0ZSBjaGFyYWN0ZXIgdmFsdWVzIDwvaDM+IDwvc3Bhbj4gPC9jZW50ZXI+DQoNCiAgLSBGaXJzdCBpIHNlYXJjaCBpZiBpbiB0aGUgY2VsbHMgb2YgTmFjaW9uYWxpZGFkICgqbmF0aW9uYWxpdHkqKSBoYXZlIG51bGxzIG9yIG5vDQogIA0KICAtIEFmdGVyIHRoYXQgaSBzZWFyY2ggdGhlIHVuaXF1ZXMgdmFsdWUgb2YgTmFjaW9uYWxpZGFkICgqbmF0aW9uYWxpdHkqKSwgUHJvdmluY2lhICgqU3RhdGUqKSwgRXN0YWRvIGNpdmlsICgqY2l2aWwgc3RhdHVzKikgDQogIA0KICAtIEkgY3JlYXRlIHRoZSB0YWJsZSBvZiBOYXRpb25hbGl0eSB0byBrbm93IHRoZSBwcm9wb3J0aW9uIG9mIGVhY2ggY291bnRyeQ0KICANCiAgLSBJIGRlbGV0ZSBOYXRpb25hbGl0eSBhbmQgU3RhdGUgYmVjYXVzZSBkb27CtHQgYnJpbmcgbWUgaW1wb3J0YW50IGluZm9ybWF0aW9uDQogIA0KICAtIEkgbWFkZSBhIHRhYmxlIG9mIENpdmlsIHN0YXR1cyB0byBrbm93IGlmIGl0wrRzIGltcG9ydGFudCBvciBubw0KICANCiAgLSBJIG1hZGUgdGhlIHRhYmxlIG9mIHRoZSBhY2NlcHRhdGlvbiBvZiB0aGUgY2FyZCB0byBzZWUgdGhlIGJhbGFuY2Ugb2YgdGhlIGNsYXNzICgqMjElIGFjY2VwdCB0aGUgY2FyZCopDQogIA0KDQpgYGB7cn0NCmFueU5BKERhdGFCYXNlJE5BQ0lPTkFMSURBRCkNCg0KdW5pcXVlKERhdGFCYXNlJE5BQ0lPTkFMSURBRCkNCnVuaXF1ZShEYXRhQmFzZSRQUk9WSU5DSUEpDQp1bmlxdWUoRGF0YUJhc2UkRVNUQURPX0NJVklMKQ0KDQp0YWJsZShEYXRhQmFzZSROQUNJT05BTElEQUQpDQoNCkRhdGFCYXNlJE5BQ0lPTkFMSURBRCA8LSBOVUxMDQpEYXRhQmFzZSRQUk9WSU5DSUEgPC0gTlVMTA0KDQoNCnRhYmxlIChEYXRhQmFzZSRFU1RBRE9fQ0lWSUwpDQpwcm9wLnRhYmxlKHRhYmxlKERhdGFCYXNlJEFDRVBUTy5UQVJKRVRBKSkNCmBgYA0KDQoNCjxjZW50ZXI+IDxzcGFuIHN0eWxlPSJjb2xvcjogIzlkMDIwODsiPiA8aDM+IDUtIENyZWF0ZSBsYXRlIHBheW1lbnQgY2xhc3MgPC9oMz4gPC9zcGFuPiA8L2NlbnRlcj4NCg0KICAtIFRoZSBjbGFzcyBvZiB0aGUgbGF0ZSBwYXltZW50IChIaXN0b3JpYWxfYXRyYXNvKSB3aWxsIGJlIDEgaWYgdGhlIHBlcnNvbiBkb27CtHQgcmVnaXN0IGxhdGUgcGF5bWVudCBpbiB0aGUgbGFzdCBmaXZlIHllYXJzDQogIA0KICAtIEFsc28gaWYgdGhlIGNpdmlsIHN0YXR1cyBpcyBpbiBibGFuaywgaSBkZXRlcm1pbmVkIHRoYXQgaXMgc29sdGVybyAoKnNpbmdsZSopDQogIA0KICAtIFdlIHNlZSBpbiBhIHRhYmxlIHRoZSBwcm9wb3J0aW9uIG9mIGVhY2ggY2l2aWwgc3RhdHVzDQoNCg0KYGBge3J9DQpEYXRhQmFzZSRISVNUT1JJQUxfQVRSQVNPIDwtIGlmZWxzZShEYXRhQmFzZSRISVNUT1JJQUxfQVRSQVNPPT0iRW4gbG9zIMO6bHRpbW9zIDUgYcOxb3MiIHxEYXRhQmFzZSRISVNUT1JJQUxfQVRSQVNPPT0iTm8gcmVnaXN0cmEgbW9yYSBlbiBsb3Mgw7psdGltb3MgNSBhw7FvcyIsIDAsIDEgKQ0KDQpEYXRhQmFzZSRFU1RBRE9fQ0lWSUwgPC0gaWZlbHNlKERhdGFCYXNlJEVTVEFET19DSVZJTCA9PSAiIiwgIlNPTFRFUk8iLERhdGFCYXNlJEVTVEFET19DSVZJTCApDQoNCnRhYmxlIChEYXRhQmFzZSRFU1RBRE9fQ0lWSUwpDQpgYGANCg0KDQoNCjxjZW50ZXI+IDxzcGFuIHN0eWxlPSJjb2xvcjogIzlkMDIwODsiPiA8aDM+IDYtIERlbGV0ZSBvdXRsaWVycyA8L2gzPiA8L3NwYW4+IDwvY2VudGVyPg0KDQogIC0gSSBtYWRlIGJveHBsb3QgZ3JhcGggb2YgaW5ncmVzbyBtZW5zdWFsICgqbW9udGhseSBpbmNvbWUqKSwgaW5ncmVzbyBmb3JtYWwgKCpmb3JtYWwgaW5jb21lKiksIHJlbGFjaW9uIGN1b3RhIGluZ3Jlc28gKCpyYXRpbyBiZXR3ZWVuIGluY29tZSBhbmQgbW9udGhseSBwYXltZW50KiksIGVkYWQgKCphZ2UqKSBhbmQgaGlqb3MgbWVub3JlcyAoKm1pbm9yIGNoaWxkcmVuKikNCiAgDQogIC0gQWZ0ZXIgc2VlIHRoZSBvdXRsaWVycywgaSBwdXQgYSBtYXhpbXVuIGZvciBlYWNoIGNhdGVnb3J5DQogIA0KICAtIEkgc3VtbWFyeSBhZ2FpbiB0aGUgREIgdG8gc2VlIHRoZSBuZXcgdmFsdWVzDQogIA0KDQpgYGB7cn0NCmJveHBsb3QoRGF0YUJhc2UkSU5HUkVTT19NRU5TVUFMKQ0KYm94cGxvdChEYXRhQmFzZSRJTkdSRVNPX0ZPUk1BTCkNCmJveHBsb3QoRGF0YUJhc2UkUkVMQUNJT05fQ1VPVEFfSU5HUkVTTykNCmJveHBsb3QoRGF0YUJhc2UkZWRhZCkNCmJveHBsb3QoRGF0YUJhc2UkSElKT1NfTUVOT1JFUykNCg0KRGF0YUJhc2UkSU5HUkVTT19NRU5TVUFMIDwtIGlmZWxzZShEYXRhQmFzZSRJTkdSRVNPX01FTlNVQUwgPiA4MDAwMCwgODAwMDAsRGF0YUJhc2UkSU5HUkVTT19NRU5TVUFMKQ0KDQpEYXRhQmFzZSRJTkdSRVNPX0ZPUk1BTCA8LSBpZmVsc2UoRGF0YUJhc2UkSU5HUkVTT19GT1JNQUwgPiA4MDAwMCwgODAwMDAsRGF0YUJhc2UkSU5HUkVTT19GT1JNQUwpDQoNCkRhdGFCYXNlJFJFTEFDSU9OX0NVT1RBX0lOR1JFU08gPC0gaWZlbHNlKERhdGFCYXNlJFJFTEFDSU9OX0NVT1RBX0lOR1JFU08+IDAuNywgMC43LCBEYXRhQmFzZSRSRUxBQ0lPTl9DVU9UQV9JTkdSRVNPKQ0KDQpEYXRhQmFzZSRlZGFkIDwtIGlmZWxzZShEYXRhQmFzZSRlZGFkID4gODAsIDgwLCBEYXRhQmFzZSRlZGFkKQ0KDQpEYXRhQmFzZSRISUpPU19NRU5PUkVTIDwtIGlmZWxzZShEYXRhQmFzZSRISUpPU19NRU5PUkVTID4gMTAsIDEwLCBEYXRhQmFzZSRISUpPU19NRU5PUkVTICkNCg0Kc3VtbWFyeShEYXRhQmFzZSkNCmBgYA0KDQoNCjxjZW50ZXI+IDxzcGFuIHN0eWxlPSJjb2xvcjogIzlkMDIwODsiPiA8aDM+IDctIFNlcGFyYXRlIGludG8gdHJhaW5pbmcgYW5kIHRlc3QgPC9oMz4gPC9zcGFuPiA8L2NlbnRlcj4NCg0KSSBzZXBhcmF0ZWQgdGhlIERCIGludG8gdHdvIHBhcnRzLCB0aGUgODAlIG9mIHRoZSByZXN1bHRzIGluIHRyYWluaW5nIHRvIHRoZSBtYWNoaW5lIGxlYXJuaW5nIG1vZGVsLCBhbmQgdGhlIDIwJSBpbiB0ZXN0aW5nIHRvIHByZWRpY3QgYW5kIHNlZSBob3cgaXRzIHdvcmtlZCB0aGUgYWxnb3JpdGhtLiANCg0KVGhlIG5ldyBkYXRhYmFzZSBhcmUgRW50cmVuYW1pZW50byAoKnRyYWluaW5nKikgYW5kIFZhbGlkYWNpb24gICgqdGVzdGluZyopDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMzIzNCkNCm11ZXN0cmEgPC0gZmxvb3IobnJvdyhEYXRhQmFzZSkqMC44KQ0KdHJJbmRleCA8LSBzYW1wbGUobnJvdyhEYXRhQmFzZSksIG11ZXN0cmEsIHJlcGxhY2U9RikNCnZhSW5kZXggPC0gc2VxX2xlbihucm93KERhdGFCYXNlKSlbIShzZXFfbGVuKG5yb3coRGF0YUJhc2UpKSAlaW4lIHRySW5kZXgpXQ0KDQpFbnRyZW5hbWllbnRvIDwtIERhdGFCYXNlW3RySW5kZXgsXQ0KVmFsaWRhY2lvbiA8LSBEYXRhQmFzZVt2YUluZGV4LF0NCmBgYA0KDQoNCjxjZW50ZXI+IDxzcGFuIHN0eWxlPSJjb2xvcjogIzlkMDIwODsiPiA8aDM+IDgtIENyZWF0ZSBsb2dpc3RpYyByZWdyZXNzaW9uIDwvaDM+IDwvc3Bhbj4gPC9jZW50ZXI+DQoNCkEgbG9naXN0aWMgcmVncmVzc2lvbiB0byBkZXRlcm1pbmF0ZSBpZiB0aGUgcGVyc29uIHdpbGwgYWNjZXB0IG9yIG5vIHRoZSBjYXJkIGl0cyB0aGUgZmlyc3Qgc3RlcCwgaW4gdGhpcyBjYXNlIGkgd2FudCB0byBkZXRlcm1pbmF0ZSB0aGUgYWNjZXB0YXRpb24gb3Igbm8gb2YgdGhlIGNhcmQgd2l0aCB0aGlzIGRhdGE6DQoNCiAgLSBUSUVORV9DQUpBQUhPUlJPX1NJU1RGSU4gKCpIYXZlIGNoZWNrIGFjY291bnQgaW4gYmFuayopDQogIA0KICAtIElOR1JFU09fRk9STUFMICgqRm9ybWFsIGluY29tZSopIA0KICANCiAgLSBlZGFkICgqYWdlKikgDQogIA0KICAtIEpVQklMQURPICgqcmV0aXJlZCopIA0KICANCiAgLSBQTEFORVMuU09DSUFMRVMgKCpIYXZlIHNvY2lhbCBnb3Zlcm5tZW50IGluY29tZSopDQogIA0KICANCg0KYGBge3J9DQpEYXRhQmFzZVJlZ3Jlc2lvblRhcmpldGEgPC0gRGF0YUJhc2UNCg0KbW9kZWxvIDwtIGdsbShEYXRhQmFzZVJlZ3Jlc2lvblRhcmpldGEkQUNFUFRPLlRBUkpFVEEgfiBUSUVORV9DQUpBQUhPUlJPX1NJU1RGSU4gKyBJTkdSRVNPX0ZPUk1BTCArIGVkYWQgKyBKVUJJTEFETyArIFBMQU5FUy5TT0NJQUxFUywgZGF0YT1EYXRhQmFzZVJlZ3Jlc2lvblRhcmpldGEsIGZhbWlseT1iaW5vbWlhbChsaW5rPSJsb2dpdCIpKQ0KDQpzdW1tYXJ5KG1vZGVsbykNCmBgYA0KDQoNCjxjZW50ZXI+IDxzcGFuIHN0eWxlPSJjb2xvcjogIzlkMDIwODsiPiA8aDM+IDkgLSBQcmVkaWN0IG92ZXIgdGVzdGluZyA8L2gzPiA8L3NwYW4+IDwvY2VudGVyPg0KDQpJIHByZWRpY3QgaW50byB0aGUgdGVzdGluZyBtb2RlbCB0aGUgY29sIG9mIGFjY2VwdCB0aGUgY2FyZCBvciBuby4NCg0KYGBge3J9DQpwcmVkaWNjaW9uIDwtIHByZWRpY3QobW9kZWxvLFZhbGlkYWNpb24sdHlwZT0ncmVzcG9uc2UnKQ0Kc3RyKHByZWRpY2Npb24pDQoNCnN1bW1hcnkocHJlZGljY2lvbikNCg0KVmFsaWRhY2lvbiRQcmVkIDwtIHByZWRpY2Npb24NCmBgYA0KDQoNCg0KPGNlbnRlcj4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjOWQwMjA4OyI+IDxoMz4gMTAgLSBUaGUgUk9DUiBjdXJ2ZSA8L2gzPiA8L3NwYW4+IDwvY2VudGVyPg0KDQpJIGNhbGN1bGF0ZWQgdGhlIFJPQ1IgY3VydmUsIHRoZSBpZGVhIGl0wrRzIHRoYXQgdGhpcyByZXN1bHRzIHdhcyBvdmVyIDAuNzUNCg0KDQpgYGB7cn0NCmxpYnJhcnkoUk9DUikNCnByZWRfbHJyIDwtIHByZWRpY3Rpb24ocHJlZGljY2lvbiwgVmFsaWRhY2lvbiRBQ0VQVE8uVEFSSkVUQSkNCmF1YyA8LSBwZXJmb3JtYW5jZShwcmVkX2xyciwiYXVjIikNCmF1Y0B5LnZhbHVlc1tbMV1dDQpgYGANCg0KDQoNCjxjZW50ZXI+IDxzcGFuIHN0eWxlPSJjb2xvcjogIzlkMDIwODsiPiA8aDM+IDExIC0gQ3JlYXRlIGEgREIgZm9yIGxhdGUgcGF5bWVudCA8L2gzPiA8L3NwYW4+IDwvY2VudGVyPg0KDQpXZSBjcmVhdGUgYSBuZXcgZGF0YWJhc2Ugd2hlcmUgb25seSBoYXZlIHRoZSBwZW9wbGUgd2hvIGFjY2VwdCB0aGUgY2FyZHMsIGJlY2F1c2UgaWYgd2Ugd2FudCB0byBtYWRlIHRoZSBsYXRlIHBheW1lbnQgd2l0aCBhbGwgcGVvcGxlIChhY2NlcHRlZCBvciBubyB0aGUgY2FyZCkgd2Ugd2lsbCBoYXZlIGEgZmFpbCByZXN1bHQNCg0KDQoNCmBgYHtyfQ0KRGF0YUJhc2VNb3JhIDwtIHN1YnNldChEYXRhQmFzZSwgc3Vic2V0ID0gRGF0YUJhc2UkQUNFUFRPLlRBUkpFVEEgPT0gMSkNCmBgYA0KDQoNCjxjZW50ZXI+IDxzcGFuIHN0eWxlPSJjb2xvcjogIzlkMDIwODsiPiA8aDM+IDEyLSBTZXBhcmF0ZSBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IDwvaDM+IDwvc3Bhbj4gPC9jZW50ZXI+DQoNCkkgc2VwYXJhdGVkIHRoZSBEQiBvZiBsYXRlIHBheW1lbnQgaW50byB0d28gcGFydHMsIHRoZSA4MCUgb2YgdGhlIHJlc3VsdHMgaW4gdHJhaW5pbmcgdG8gdGhlIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWwsIGFuZCB0aGUgMjAlIGluIHRlc3RpbmcgdG8gcHJlZGljdCBhbmQgc2VlIGhvdyBpdHMgd29ya2VkIHRoZSBhbGdvcml0aG0uIA0KDQpUaGUgbmV3IGRhdGFiYXNlIGFyZSBFbnRyZW5hbWllbnRvICgqdHJhaW5pbmcqKSBhbmQgVmFsaWRhY2lvbiAgKCp0ZXN0aW5nKikNCg0KDQpgYGB7cn0NCnNldC5zZWVkKDMyMzQpDQptdWVzdHJhTW9yYSA8LSBmbG9vcihucm93KERhdGFCYXNlTW9yYSkqMC44KQ0KdHJJbmRleE1vcmEgPC0gc2FtcGxlKG5yb3coRGF0YUJhc2VNb3JhKSwgbXVlc3RyYU1vcmEsIHJlcGxhY2U9RikNCnZhSW5kZXhNb3JhIDwtIHNlcV9sZW4obnJvdyhEYXRhQmFzZU1vcmEpKVshKHNlcV9sZW4obnJvdyhEYXRhQmFzZU1vcmEpKSAlaW4lIHRySW5kZXhNb3JhKV0NCg0KRW50cmVuYW1pZW50b01vcmEgPC0gRGF0YUJhc2VNb3JhW3RySW5kZXhNb3JhLF0NClZhbGlkYWNpb25Nb3JhIDwtIERhdGFCYXNlTW9yYVt2YUluZGV4TW9yYSxdDQpgYGANCg0KDQo8Y2VudGVyPiA8c3BhbiBzdHlsZT0iY29sb3I6ICM5ZDAyMDg7Ij4gPGgzPiAxMy0gQ3JlYXRlIHRyZWUgPC9oMz4gPC9zcGFuPiA8L2NlbnRlcj4NCg0KSW4gdGhpcyBjYXNlIGZvciB0aGUgbGF0ZSBwYXltZW50IGkgd2lsbCB1c2UgdHdvIG1ldGhvZHMsIHRoZSB0cmVlIGFuZCB0aGUgbG9naXN0aWMgcmVncmVzc2lvbiwgZmlyc3QgaSBjcmVhdGVkIHRoZSB0cmVlIHdpdGggOg0KDQogIC0gVElFTkVfQ0FKQUFIT1JST19TSVNURklOICgqSGF2ZSBjaGVjayBhY2NvdW50IGluIGJhbmsqKQ0KICANCiAgLSBUSUVORV9QUkVTVEFNT19TSVNURklOICgqSGF2ZSBsb2FucyBpbiBiYW5rcyopDQogIA0KICAtIElOR1JFU09fTUVOU1VBTCAoKm1vbnRobHkgaW5jb21lKikNCiAgDQogIC0gREVVREFfU0lTVEVNQUZJTkFOQ0lFUk8gKCpkZWJ0cyBpbiB0aGUgZmluYW5jaWFsIHN5c3RlbSopDQogIA0KICAtIEpVQklMQURPICgqcmV0aXJlZCopIA0KICANCiAgLSBQTEFORVMuU09DSUFMRVMgKCpIYXZlIHNvY2lhbCBnb3Zlcm5tZW50IGluY29tZSopDQogIA0KICANCmBgYHtyfQ0KbGlicmFyeShycGFydCkNCmxpYnJhcnkocnBhcnQucGxvdCkNCmZpdCA8LSBycGFydChFbnRyZW5hbWllbnRvTW9yYSRUVVZPX0FUUkFTT19DT05fTEFfLlRBUkpFVEEgfiBUSUVORV9DQUpBQUhPUlJPX1NJU1RGSU4gKyBUSUVORV9QUkVTVEFNT19TSVNURklOICsgSU5HUkVTT19NRU5TVUFMICsgREVVREFfU0lTVEVNQUZJTkFOQ0lFUk8gKyBKVUJJTEFETyArIFBMQU5FUy5TT0NJQUxFUywgZGF0YT1FbnRyZW5hbWllbnRvTW9yYSkNCnJwYXJ0LnBsb3QoZml0LCBleHRyYT0wLCB0eXBlPTIpDQpgYGANCg0KDQo8Y2VudGVyPiA8c3BhbiBzdHlsZT0iY29sb3I6ICM5ZDAyMDg7Ij4gPGgzPiAxNCAtIFByZWRpY3Qgb3ZlciB0ZXN0aW5nIDwvaDM+IDwvc3Bhbj4gPC9jZW50ZXI+DQoNCkkgcHJlZGljdCBpbnRvIHRoZSBsYXRlIHBheW1lbnQgdGVzdGluZyBtb2RlbCB0aGUgY29sIG9mIGhhdmUgbGF0ZSBwYXltZW50IG9yIG5vIHdpdGggYSB0cmVlLg0KDQpgYGB7cn0NCnByZWRpY2Npb25Nb3JhIDwtIHByZWRpY3QoZml0LFZhbGlkYWNpb25Nb3JhLG1ldGhvZD0nY2xhc3MnKQ0KDQpWYWxpZGFjaW9uTW9yYSRQcmVkIDwtIHByZWRpY2Npb25Nb3JhDQpgYGANCiAgDQogIA0KICA8Y2VudGVyPiA8c3BhbiBzdHlsZT0iY29sb3I6ICM5ZDAyMDg7Ij4gPGgzPiAxNS0gQ3JlYXRlIHRoZSBsb2dpc3RpYyA8L2gzPiA8L3NwYW4+IDwvY2VudGVyPg0KICANCg0KQSBsb2dpc3RpYyByZWdyZXNzaW9uIHRvIGRldGVybWluYXRlIGlmIHRoZSBwZXJzb24gd2lsbCBiZSBvciBubyBpbiB0aGUgbGF0ZSBwYXltZW50IGl0cyB0aGUgbmV4dCBzdGVwLCBpbiB0aGlzIGNhc2UgaSB3YW50IHRvIGRldGVybWluYXRlIHdpdGggdGhlIHNhbWUgZGF0YSBvZiB0aGUgdHJlZQ0KDQoNCmBgYHtyfQ0KbW9kZWxvTW9yYSA8LSBnbG0oRGF0YUJhc2VNb3JhJFRVVk9fQVRSQVNPX0NPTl9MQV8uVEFSSkVUQSB+IFRJRU5FX0NBSkFBSE9SUk9fU0lTVEZJTiArIFRJRU5FX1BSRVNUQU1PX1NJU1RGSU4gKyBJTkdSRVNPX01FTlNVQUwgKyBERVVEQV9TSVNURU1BRklOQU5DSUVSTyArIEpVQklMQURPICsgUExBTkVTLlNPQ0lBTEVTLCBkYXRhPURhdGFCYXNlTW9yYSwgZmFtaWx5PWJpbm9taWFsKGxpbms9ImxvZ2l0IikpDQoNCnN1bW1hcnkobW9kZWxvTW9yYSkNCmBgYA0KICANCiA8Y2VudGVyPiA8c3BhbiBzdHlsZT0iY29sb3I6ICM5ZDAyMDg7Ij4gPGgzPiAxNiAtIFByZWRpY3Qgb3ZlciB0ZXN0aW5nIDwvaDM+IDwvc3Bhbj4gPC9jZW50ZXI+DQoNCkkgcHJlZGljdCBpbnRvIHRoZSBsYXRlIHBheW1lbnQgdGVzdGluZyBtb2RlbCB0aGUgY29sIG9mIGhhdmUgbGF0ZSBwYXltZW50IG9yIG5vIHdpdGggYSBsb2dpc3RpYy4gDQogIA0KICANCmBgYHtyfQ0KcHJlZGljY2lvbk1vcmEyIDwtIHByZWRpY3QobW9kZWxvTW9yYSxWYWxpZGFjaW9uTW9yYSx0eXBlPSdyZXNwb25zZScpDQoNClZhbGlkYWNpb25Nb3JhJFByZWQyIDwtIHByZWRpY2Npb25Nb3JhMg0KYGBgDQoNCg0KPGNlbnRlcj4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjOWQwMjA4OyI+IDxoMz4gMTcgLSBUaGUgUk9DUiBjdXJ2ZSA8L2gzPiA8L3NwYW4+IDwvY2VudGVyPg0KDQpJIGNhbGN1bGF0ZWQgdGhlIFJPQ1IgY3VydmUsIHRoZSBpZGVhIGl0wrRzIHRoYXQgdGhpcyByZXN1bHRzIHdhcyBvdmVyIDAuNzUNCg0KYGBge3J9DQpwcmVkX2xyciA8LSBwcmVkaWN0aW9uKHByZWRpY2Npb25Nb3JhLCBWYWxpZGFjaW9uTW9yYSRUVVZPX0FUUkFTT19DT05fTEFfLlRBUkpFVEEpDQphdWMgPC0gcGVyZm9ybWFuY2UocHJlZF9scnIsImF1YyIpDQphdWNAeS52YWx1ZXNbWzFdXQ0KYGBgDQoNCg0KPGNlbnRlcj4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjOWQwMjA4OyI+IDxoMz4gMTggLSBMb2FkIHRoZSBuZXcgcG90ZW50aWFsIGNsaWVudHMgPC9oMz4gPC9zcGFuPiA8L2NlbnRlcj4NCg0KSSBsb2FkIHRoZSBkYXRhYmFzZSB3aXRoIGluZm9ybWF0aW9uIG9mIHRoZSBuZXcgcG90ZW50aWFscyBjbGllbnRzIHRoYXQgdGhlIGNvbXBhbnkgY2FyZCB3YW50IHRvIGNhbGwgdG8gZG8gYSBtYXJrZXRpbmcgY2FtcGFpZ24NCg0KYGBge3J9DQpCYXNlX051ZXZvc19jbGllbnRlcyA8LSByZWFkLmNzdihmaWxlID0gIkJBU0UgVEVTVF9zaW5fdGFyZ2V0cy5jc3YiLCBzZXAgPSAiOyIsIGhlYWRlciA9IFQsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQ0KYGBgDQoNCg0KPGNlbnRlcj4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjOWQwMjA4OyI+IDxoMz4gMTkgLSBTdW1tYXJ5IHRoZSBuZXcgREIgPC9oMz4gPC9zcGFuPiA8L2NlbnRlcj4NCg0KSSBsb2FkIHRoZSBkYXRhYmFzZSB3aXRoIGluZm9ybWF0aW9uIG9mIHRoZSBuZXcgcG90ZW50aWFscyBjbGllbnRzIHRoYXQgdGhlIGNvbXBhbnkgY2FyZCB3YW50IHRvIGNhbGwgdG8gZG8gYSBtYXJrZXRpbmcgY2FtcGFpZ24NCg0KYGBge3J9DQpzdW1tYXJ5KEJhc2VfTnVldm9zX2NsaWVudGVzKQ0KYGBgDQoNCg0KPGNlbnRlcj4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjOWQwMjA4OyI+IDxoMz4gMjAgLSBFVEwgcHJvY2VzczwvaDM+IDwvc3Bhbj4gPC9jZW50ZXI+DQoNCg0KSSB3aWxsIG1ha2UgaW4gb25seSBvbmUgc3RlcCBhbGwgdGhlIGNsZWFuIGFuZCB0cmFuc2Zvcm0gdG8gdGhlIERCIHNpbWlsYXIgdG8gdGhlIG9yaWdpbmFsDQoNCg0KYGBge3J9DQpCYXNlX051ZXZvc19jbGllbnRlcyROQUNJT05BTElEQUQgPC0gTlVMTA0KQmFzZV9OdWV2b3NfY2xpZW50ZXMkUFJPVklOQ0lBIDwtIE5VTEwNCg0KQmFzZV9OdWV2b3NfY2xpZW50ZXMkSElTVE9SSUFMX0FUUkFTTyA8LSBpZmVsc2UoQmFzZV9OdWV2b3NfY2xpZW50ZXMkSElTVE9SSUFMX0FUUkFTTz09IkVuIGxvcyDDumx0aW1vcyA1IGHDsW9zIiB8QmFzZV9OdWV2b3NfY2xpZW50ZXMkSElTVE9SSUFMX0FUUkFTTz09Ik5vIHJlZ2lzdHJhIG1vcmEgZW4gbG9zIMO6bHRpbW9zIDUgYcOxb3MiLCAwLCAxICkNCg0KQmFzZV9OdWV2b3NfY2xpZW50ZXMkRVNUQURPX0NJVklMIDwtIGlmZWxzZShCYXNlX051ZXZvc19jbGllbnRlcyRFU1RBRE9fQ0lWSUwgPT0gIiIsICJTT0xURVJPIixCYXNlX051ZXZvc19jbGllbnRlcyRFU1RBRE9fQ0lWSUwgKQ0KDQpCYXNlX051ZXZvc19jbGllbnRlcyRJTkdSRVNPX01FTlNVQUwgPC0gaWZlbHNlKEJhc2VfTnVldm9zX2NsaWVudGVzJElOR1JFU09fTUVOU1VBTCA+IDgwMDAwLCA4MDAwMCxCYXNlX051ZXZvc19jbGllbnRlcyRJTkdSRVNPX01FTlNVQUwpDQoNCkJhc2VfTnVldm9zX2NsaWVudGVzJElOR1JFU09fRk9STUFMIDwtIGlmZWxzZShCYXNlX051ZXZvc19jbGllbnRlcyRJTkdSRVNPX0ZPUk1BTCA+IDgwMDAwLCA4MDAwMCxCYXNlX051ZXZvc19jbGllbnRlcyRJTkdSRVNPX0ZPUk1BTCkNCg0KQmFzZV9OdWV2b3NfY2xpZW50ZXMkUkVMQUNJT05fQ1VPVEFfSU5HUkVTTyA8LSBpZmVsc2UoQmFzZV9OdWV2b3NfY2xpZW50ZXMkUkVMQUNJT05fQ1VPVEFfSU5HUkVTTz4gMC43LCAwLjcsIEJhc2VfTnVldm9zX2NsaWVudGVzJFJFTEFDSU9OX0NVT1RBX0lOR1JFU08pDQoNCkJhc2VfTnVldm9zX2NsaWVudGVzJGVkYWQgPC0gaWZlbHNlKEJhc2VfTnVldm9zX2NsaWVudGVzJGVkYWQgPiA4MCwgODAsIEJhc2VfTnVldm9zX2NsaWVudGVzJGVkYWQpDQoNCkJhc2VfTnVldm9zX2NsaWVudGVzJEhJSk9TX01FTk9SRVMgPC0gaWZlbHNlKEJhc2VfTnVldm9zX2NsaWVudGVzJEhJSk9TX01FTk9SRVMgPiAxMCwgMTAsIEJhc2VfTnVldm9zX2NsaWVudGVzJEhJSk9TX01FTk9SRVMgKQ0KYGBgDQoNCg0KPGNlbnRlcj4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjOWQwMjA4OyI+IDxoMz4gMjEgLSBQcmVkaWN0IGxhdGUgcGF5bWVudDwvaDM+IDwvc3Bhbj4gPC9jZW50ZXI+DQoNCg0KSSB3aWxsIHByZWRpY3QgdGhlIGxhdGUgcGF5bWVudCBpbnRvIHRoZSBuZXcgY2xpZW50cyB3aXRoIHRoZSB0cmVlIGFuZCByZWdyZXNzaW9uIG1hZGUgYmVmb3JlLCB3aXRoIGEgYXNzZW1ibGUgYmV0d2VlbiByb3cgbWVhbnMuDQoNCg0KYGBge3J9DQpwcmVkaWNjaW9uTW9yYSA8LSBwcmVkaWN0KGZpdCxCYXNlX051ZXZvc19jbGllbnRlcyxtZXRob2Q9J2NsYXNzJykNCnByZWRpY2Npb25Nb3JhMiA8LSBwcmVkaWN0KG1vZGVsb01vcmEsQmFzZV9OdWV2b3NfY2xpZW50ZXMsdHlwZT0ncmVzcG9uc2UnKQ0KDQpzdW1tYXJ5KHByZWRpY2Npb25Nb3JhKQ0KDQpCYXNlX051ZXZvc19jbGllbnRlcyRQcmVkTW9yYSA8LSBwcmVkaWNjaW9uTW9yYQ0KQmFzZV9OdWV2b3NfY2xpZW50ZXMkUHJlZE1vcmEyIDwtIHByZWRpY2Npb25Nb3JhMg0KDQpCYXNlX051ZXZvc19jbGllbnRlcyRlbnNhbWJsZSA8LSByb3dNZWFucyhCYXNlX051ZXZvc19jbGllbnRlc1ssYygiUHJlZE1vcmEiLCAiUHJlZE1vcmEyIildKQ0KYGBgDQoNCg0KPGNlbnRlcj4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjOWQwMjA4OyI+IDxoMz4gMjIgLSBQcmVkaWN0IGZvciBhY2NlcHQgdGhlIGNhcmQ8L2gzPiA8L3NwYW4+IDwvY2VudGVyPg0KDQoNCkkgd2lsbCBwcmVkaWN0IHRoZSBhY2NlcHRhdGlvbiBvZiB0aGUgY2FyZCBpbnRvIHRoZSBuZXcgY2xpZW50cyB3aXRoIHRoZSByZWdyZXNzaW9uIG1hZGUgYmVmb3JlDQoNCg0KYGBge3J9DQpwcmVkaWNjaW9uIDwtIHByZWRpY3QobW9kZWxvLEJhc2VfTnVldm9zX2NsaWVudGVzLHR5cGU9J3Jlc3BvbnNlJykNCnN0cihwcmVkaWNjaW9uKQ0KDQpzdW1tYXJ5KHByZWRpY2Npb24pDQoNCkJhc2VfTnVldm9zX2NsaWVudGVzJFByZWRBY2VwdGFjaW9uIDwtIHByZWRpY2Npb24NCmBgYA0KDQo8Y2VudGVyPiA8c3BhbiBzdHlsZT0iY29sb3I6ICM5ZDAyMDg7Ij4gPGgzPiAyMyAtIENyZWF0ZSB0aGUgYmVzdCBjbGllbnRzIDwvaDM+IDwvc3Bhbj4gPC9jZW50ZXI+DQoNCg0KTm93IHRoZSBuZXh0IHN0ZXAgaXTCtHMgY3JlYXRlIHRoZSB0YWJsZSB3aXRoIHRoZSAxMDAgYmVzdCBwb3RlbnRpYWwgY2xpZW50cywgdGhpbmtpbmcgdGhhdCBpdMK0cyBtb3JlIGltcG9ydGFudCB0aGF0IHRoZSBuZXcgY2xpZW50IGRvbsK0dCBmaW5pc2ggaW4gbGF0ZSBwYXltZW50IGFuZCB0aGVuIHRoZSBwcm9iYWJpbGl0eSBvZiBhY2NlcHQgdGhlIGNhcmQuDQoNCk9ubHkgc25lZCB0aGUgSURFTlRJRklDQURPUi5DTElFTlRFICgqQ2xpZW50IElEKikNCg0KDQpgYGB7cn0NCg0Kc3VtbWFyeShCYXNlX051ZXZvc19jbGllbnRlcyRQcmVkQWNlcHRhY2lvbikNCnN1bW1hcnkoQmFzZV9OdWV2b3NfY2xpZW50ZXMkUHJlZE1vcmEpDQoNCkJhc2VNZWpvcmVzQ2xpZW50ZXMgPC0gc3Vic2V0KEJhc2VfTnVldm9zX2NsaWVudGVzLCBzdWJzZXQgPSBCYXNlX051ZXZvc19jbGllbnRlcyRQcmVkQWNlcHRhY2lvbiA+IDAuMjAgJiBCYXNlX051ZXZvc19jbGllbnRlcyRlbnNhbWJsZSA8IDAuMTc1KQ0KDQpCYXNlTWVqb3Jlc0NsaWVudGVzIDwtIEJhc2VNZWpvcmVzQ2xpZW50ZXMgJT4lIHNlbGVjdChJREVOVElGSUNBRE9SLkNMSUVOVEUpDQpgYGANCg0KDQo8Y2VudGVyPiA8c3BhbiBzdHlsZT0iY29sb3I6ICM5ZDAyMDg7Ij4gPGgzPiAyNCAtIFdyaXRlIGEgQ1NWIHRhYmxlIDwvaDM+IDwvc3Bhbj4gPC9jZW50ZXI+DQoNClRoZSBmaW5hbCBzdGVwIGlzIHRvIHNhdmUgdGhlIHRhYmxlIGluIGEgQ1NWIHdpdGggdGhlIDEwMCBiZXN0IGNsaWVudCB0byBzZW5kIGEgdGhlIGNhcmQgY29tcGFueQ0KDQpgYGB7cn0NCndyaXRlLnRhYmxlIChCYXNlTWVqb3Jlc0NsaWVudGVzLGZpbGU9IkNsaWVudGVzX3BvdGVuY2lhbGVzLmNzdiIscm93Lm5hbWVzPUZBTFNFLGNvbC5uYW1lcz1UUlVFLHF1b3RlPVRSVUUsc2VwPSIsIikNCmBgYA0KDQo8Y2VudGVyPiA8c3BhbiBzdHlsZT0iY29sb3I6ICMwNzcxODc7Ij4gPGgyPiBSZXN1bHRzIDwvaDI+IDwvc3Bhbj4gPC9jZW50ZXI+DQoNCkFmdGVyIGEgdGltZSB0aGUgY29tcGFueSBnYXZlIG1lIGJhY2sgdGhlIHJlc3VsdHMgb2YgdGhlIGNhbXBhaWduIG92ZXIgdGhpcyAxMDAgY2xpZW50cw0KDQoNCjxjZW50ZXI+IDxzcGFuIHN0eWxlPSJjb2xvcjogIzlkMDIwODsiPiA8aDM+IDI1IC0gbG9hZHMgdGhlIHJlc3VsdHMgPC9oMz4gPC9zcGFuPiA8L2NlbnRlcj4NCg0KVGhlIHJlc3VsdCBoYXZlIHRoZSAxMDAgb2JzZXJ2YXRpb24gb2YgdGhlIGNsaWVudHMgdGhhdCB3YXMgc2VuZGVkIGFuZCBzYXlzIGlmIGFjY2VwdCB0aGUgY2FyZCBvZiBpdCB3YXMgaW4gbGF0ZSBwYXltZW50Lg0KDQpgYGB7cn0NCnJlc3VsdHMgPC0gcmVhZC5jc3YoZmlsZSA9ICJyZXN1bHRhZG8uY3N2Iiwgc2VwID0gIjsiLCBoZWFkZXIgPSBULCBzdHJpbmdzQXNGYWN0b3JzID0gRikNCmBgYA0KDQo8Y2VudGVyPiA8c3BhbiBzdHlsZT0iY29sb3I6ICM5ZDAyMDg7Ij4gPGgzPiAyNiAtIFRoZSBwZXJmb3JtYW5jZSA8L2gzPiA8L3NwYW4+IDwvY2VudGVyPg0KDQpMZXTCtHMgZ3JhcGggdGhlIHBlcmZvcm1hbmNlLCByZW1lbWJlciB0aGF0IGFjY2VwdGFuY2Ugb2YgY2FyZCBpcyAyMCUgYW5kIGxhdGUgcGF5bWVudCBpcyA1MCUNCg0KYGBge3J9DQpsaWJyYXJ5KGdncGxvdDIpDQpERl9ncmFwaCA9IGRhdGEuZnJhbWUocmVzdWx0YWRvID0gInZhcmlhYmxlIiwgYWNjZXB0YW5jZSA9IHN1bShyZXN1bHRzJEFDRVBUTy5UQVJKRVRBKS8xMDAgLCBsYXRlcGF5bWVudCA9IHN1bShyZXN1bHRzJFRVVk9fQVRSQVNPX0NPTl9MQV8uVEFSSkVUQSkvMTAwKQ0KDQpERl9ncmFwaCA9IG1lbHQoZGF0YT0gREZfZ3JhcGgsIGlkPSJyZXN1bHRhZG8iKQ0KDQpERl9ncmFwaCAlPiUNCmdncGxvdChhZXMoeCA9IHZhcmlhYmxlICwgeT0gdmFsdWUsIGZpbGwgPSB2YXJpYWJsZSkpICsNCiAgICBnZW9tX2NvbCgpICsNCiAgICBsYWJzKHggPSAidmFyaWFibGUiLCB5ID0gIiUgb2YiLCB0aXRsZSA9ICJGaW5hbCByZXN1bHRzIG9mIGNhbXBhaWduIikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLjAwLCAwLjk5KSkgKw0KICB0aGVtZV9yZXBsYWNlKCkNCmBgYA0KDQoNCg0KPGNlbnRlcj4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjMDc3MTg3OyI+IDxoMj4gQ29uY2x1c2lvbiA8L2gyPiA8L3NwYW4+IDwvY2VudGVyPg0KDQpUaGUgYWxnb3JpdGhtIHBlcmZvcm1hbmNlIHdhcyB2ZXJ5IGdvb2QsIHRoZSBwZXJjZW50YWdlIG9mIGFjY2VwdGF0aW9uIHdhcyBpbXByb3ZlIGZyb20gMjAlIHRvIDQzJSAgYW5kIHRoZSBsYXRlIHBheW1lbnQgdGhhdCB3YXMgdGhlIG1haW4gcHJvYmxlbSB3YXMgcmVkdWNlZCBmcm9tIDUwJSB0byAzJS4NCg0KVGhpcyB3b3JrIGRlbW9uc3RyYXRlcyB0aGUgaW1wb3J0YW5jZSBvZiBtYWNoaW5lIGxlYXJuaW5nIHRvIGltcHJvdmUgYnVzaW5lc3M=