Modelos de análisis predictivo

1. Llamar a las librerias e Importar datos

#Llamar a las librerias
library(tidyverse)
library(readxl)
library(dplyr)
library(rpart)
library(rpart.plot)
library(ggplot2)
library(factoextra)
library(cluster)
library(data.table)

#Importar datos
claims_data <- read_excel("ClaimsData2018.xlsx")
transactions_summary <- read.csv("TransactionsSummary2018.csv")

1. Entender la base de datos

merged_df <- merge(claims_data, transactions_summary, by = "ClaimID", all = TRUE)
#summary(merged_df)
#count(merged_df, ClaimStatus, sort=TRUE)
#count(merged_df, IncidentDescription, sort=TRUE)
#count(merged_df, Gender, sort=TRUE)
#count(merged_df, ClaimantType, sort=TRUE)
#count(merged_df, InjuryNature, sort=TRUE)
#count(merged_df, BodyPartRegion, sort=TRUE)
#count(merged_df, BodyPart, sort=TRUE)
#count(merged_df, IsDenied, sort=TRUE)

2. Limpiar la base de datos

## Corregir el tipo de datos:

# as.Date
merged_df$IncidentDate <- as.Date(merged_df$IncidentDate,"%m/%d/%Y")
merged_df$ReturnToWorkDate <- as.Date(merged_df$ReturnToWorkDate,"%m/%d/%Y")
merged_df$ClaimantOpenedDate <- as.Date(merged_df$ClaimantOpenedDate,"%m/%d/%Y")
merged_df$ClaimantClosedDate <- as.Date(merged_df$ClaimantClosedDate,"%m/%d/%Y")
merged_df$EmployerNotificationDate <- as.Date(merged_df$EmployerNotificationDate,"%m/%d/%Y")
merged_df$ReceivedDate <- as.Date(merged_df$ReceivedDate,"%m/%d/%Y")

#as.numeric
merged_df$AverageWeeklyWage <- as.numeric(merged_df$AverageWeeklyWage)
merged_df$IsDenied <- as.character(merged_df$IsDenied)
merged_df$ClaimantAge_at_DOI <- as.numeric(merged_df$ClaimantAge_at_DOI)
merged_df$TotalPaid <- as.numeric(merged_df$TotalReserves)
merged_df$TotalRecovery <- as.numeric(merged_df$TotalRecovery)
merged_df$TotalReserves <- as.numeric(merged_df$TotalReserves)
merged_df$TotalPaid <- as.numeric(merged_df$TotalPaid)
merged_df$IndemnityPaid <- as.numeric(merged_df$IndemnityPaid)
merged_df$OtherPaid <- as.numeric(merged_df$OtherPaid)
merged_df$ClaimantAge_at_DOI <- as.numeric(merged_df$ClaimantAge_at_DOI)



summary(merged_df)
##     ClaimID           TotalPaid       TotalReserves     TotalRecovery      
##  Min.   :  633915   Min.   :      0   Min.   :      0   Min.   :     0.00  
##  1st Qu.:  810246   1st Qu.:      0   1st Qu.:      0   1st Qu.:     0.00  
##  Median :  856915   Median :      0   Median :      0   Median :     0.00  
##  Mean   :12344572   Mean   :   2233   Mean   :   2233   Mean   :    68.88  
##  3rd Qu.:22716420   3rd Qu.:      0   3rd Qu.:      0   3rd Qu.:     0.00  
##  Max.   :62246496   Max.   :2069575   Max.   :2069575   Max.   :130541.03  
##                     NA's   :52673     NA's   :52673     NA's   :52673      
##  IndemnityPaid      OtherPaid       ClaimStatus         IncidentDate       
##  Min.   :  -475   Min.   :  -7820   Length:186677      Min.   :1947-02-24  
##  1st Qu.:     0   1st Qu.:     58   Class :character   1st Qu.:1998-12-21  
##  Median :     0   Median :    230   Mode  :character   Median :2004-01-05  
##  Mean   :  3061   Mean   :   3685                      Mean   :2003-12-08  
##  3rd Qu.:     0   3rd Qu.:    855                      3rd Qu.:2009-02-02  
##  Max.   :640732   Max.   :4129915                      Max.   :2014-06-27  
##  NA's   :52673    NA's   :52673                        NA's   :52673       
##  IncidentDescription ReturnToWorkDate     AverageWeeklyWage  
##  Length:186677       Min.   :1976-10-29   Min.   :      0.0  
##  Class :character    1st Qu.:2002-04-25   1st Qu.:    300.0  
##  Mode  :character    Median :2007-07-09   Median :    492.0  
##                      Mean   :2006-06-01   Mean   :    587.3  
##                      3rd Qu.:2011-06-01   3rd Qu.:    660.4  
##                      Max.   :2015-05-07   Max.   :2024000.0  
##                      NA's   :111310       NA's   :137597     
##  ClaimantOpenedDate   ClaimantClosedDate   EmployerNotificationDate
##  Min.   :1947-02-24   Min.   :1999-06-01   Min.   :1972-09-10      
##  1st Qu.:1999-02-09   1st Qu.:2005-03-31   1st Qu.:2000-03-13      
##  Median :2004-02-17   Median :2006-04-04   Median :2004-12-28      
##  Mean   :2004-01-23   Mean   :2007-05-24   Mean   :2005-08-29      
##  3rd Qu.:2009-04-09   3rd Qu.:2009-11-11   3rd Qu.:2009-11-03      
##  Max.   :2014-06-30   Max.   :2014-06-30   Max.   :9999-07-21      
##  NA's   :52673        NA's   :57351        NA's   :74961           
##   ReceivedDate          IsDenied         ClaimantAge_at_DOI    Gender         
##  Min.   :1947-02-24   Length:186677      Min.   :-8000.00   Length:186677     
##  1st Qu.:1999-02-09   Class :character   1st Qu.:   33.00   Class :character  
##  Median :2004-02-13   Mode  :character   Median :   42.00   Mode  :character  
##  Mean   :2004-07-19                      Mean   :   39.85                     
##  3rd Qu.:2009-02-27                      3rd Qu.:   51.00                     
##  Max.   :9999-07-21                      Max.   :   94.00                     
##  NA's   :52673                           NA's   :97751                        
##  ClaimantType       InjuryNature       BodyPartRegion       BodyPart        
##  Length:186677      Length:186677      Length:186677      Length:186677     
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##                                                                             
##  BillReviewALE        Hospital         PhysicianOutpatient       Rx          
##  Min.   : -456.0   Min.   : -12570.4   Min.   :  -4655.7   Min.   :  -469.5  
##  1st Qu.:   16.0   1st Qu.:    193.9   1st Qu.:    107.6   1st Qu.:    23.3  
##  Median :   32.0   Median :    559.1   Median :    221.6   Median :    58.3  
##  Mean   :  191.2   Mean   :   4394.7   Mean   :   1752.3   Mean   :  1140.4  
##  3rd Qu.:   80.0   3rd Qu.:   2253.4   3rd Qu.:    710.5   3rd Qu.:   174.5  
##  Max.   :56475.3   Max.   :2759604.0   Max.   :1481468.5   Max.   :631635.5  
##  NA's   :139865    NA's   :145262      NA's   :84986       NA's   :145752

Limpiar la base de datos

# ¿Cuántos NA tengo en al base de datos?
sum(is.na(merged_df$EmployerNotificationDate))
## [1] 74961
sum(is.na(merged_df$ReceivedDate))
## [1] 52673
sum(is.na(merged_df$ClaimantClosedDate))
## [1] 57351
sum(is.na(merged_df$TotalRecovery))
## [1] 52673
sum(is.na(merged_df$TotalReserves))
## [1] 52673
# ¿Cuántos NA tengo por variable?
sapply(merged_df, function(x) sum(is.na(x)))
##                  ClaimID                TotalPaid            TotalReserves 
##                        0                    52673                    52673 
##            TotalRecovery            IndemnityPaid                OtherPaid 
##                    52673                    52673                    52673 
##              ClaimStatus             IncidentDate      IncidentDescription 
##                    52673                    52673                    52673 
##         ReturnToWorkDate        AverageWeeklyWage       ClaimantOpenedDate 
##                   111310                   137597                    52673 
##       ClaimantClosedDate EmployerNotificationDate             ReceivedDate 
##                    57351                    74961                    52673 
##                 IsDenied       ClaimantAge_at_DOI                   Gender 
##                    52673                    97751                    52673 
##             ClaimantType             InjuryNature           BodyPartRegion 
##                    52673                    52673                    52673 
##                 BodyPart            BillReviewALE                 Hospital 
##                    52673                   139865                   145262 
##      PhysicianOutpatient                       Rx 
##                    84986                   145752

Agregar columnas

merged_df$TotalIncurredCost <- merged_df$TotalReserves + merged_df$Indemnity + merged_df$OtherPaid - merged_df$TotalRecovery



merged_df$TimeProcesses <- as.numeric(difftime(merged_df$ClaimantClosedDate, merged_df$ClaimantOpenedDate, unit="days"))

Regresión Lineal

rldata <- merged_df %>% select(TotalIncurredCost,BodyPartRegion,BodyPart,ClaimantType,Gender,ClaimantAge_at_DOI,TimeProcesses,InjuryNature,IsDenied)

# ¿Cuántos NA tengo por variable?
sapply(rldata, function(x) sum(is.na(x)))
##  TotalIncurredCost     BodyPartRegion           BodyPart       ClaimantType 
##              52673              52673              52673              52673 
##             Gender ClaimantAge_at_DOI      TimeProcesses       InjuryNature 
##              52673              97751              57351              52673 
##           IsDenied 
##              52673
rldata <- rldata %>% na.omit()


regresion <- lm(TotalIncurredCost ~ BodyPartRegion + BodyPart + ClaimantType + Gender + ClaimantAge_at_DOI + TimeProcesses + InjuryNature + IsDenied , data = rldata)

#summary(regresion)

Construcción de Modelo Predictivo

data <- data.frame(BodyPartRegion = "Upper Extremities", BodyPart = "Hand", ClaimantType = "Medical Only", Gender = "Male", ClaimantAge_at_DOI = 42, TimeProcesses = 989.3, InjuryNature = "Strain", IsDenied = "0" )

predict(regresion, data)
##        1 
## 1090.108

Arbol de Decisiones

# Bases de datos nuevas

cuerpo <- merged_df %>% select(Gender, BodyPartRegion)
denied <- merged_df %>% select(Gender, IsDenied)

#Árboles de predicción
arbol <- rpart(formula = Gender ~ ., data=cuerpo)
rpart.plot(arbol)

prp(arbol, extra = 3)

tree <- rpart(formula = Gender ~ ., data=denied)
rpart.plot(tree)

prp(tree, extra = 3)

Clusters

cluster <- merged_df %>% select(TotalIncurredCost, TimeProcesses) %>% na.omit() %>% filter(TotalIncurredCost>0) 

cluster <- as.data.frame(scale(cluster))
summary(cluster)
##  TotalIncurredCost TimeProcesses    
##  Min.   :-0.2087   Min.   :-1.0576  
##  1st Qu.:-0.2027   1st Qu.:-0.9005  
##  Median :-0.1951   Median :-0.2542  
##  Mean   : 0.0000   Mean   : 0.0000  
##  3rd Qu.:-0.1597   3rd Qu.: 0.6083  
##  Max.   :71.4549   Max.   :12.2470
# Los datos fuera de lo normal están fuera de los siguientes límites: 
# Límite inferior = q1 -1.5*IQR
# Límite superior = Q3 + 1.5*IQR
# Q1: Cuartil 1, Q3  

IQR_C <- IQR(cluster$TotalIncurredCost)
LI_C <- -0.2027-1.5*IQR_C
LS_C <- -0.1597+1.5*IQR_C
cluster <- cluster[cluster$TotalIncurredCost <=-0.095,]

IQR_T <- IQR(cluster$TimeProcesses)
LI_T<- -0.9280-1.5*IQR_T
LS_T <- 0.5945+1.5*IQR_T
cluster <- cluster[cluster$TimeProcesses<=2.8783,]


grupos <- 4
segmentos <- kmeans(cluster, grupos)
asignación <- cbind(cluster, cluster=segmentos$cluster)

Gráfica y Optimización

fviz_cluster(segmentos, data=cluster)

Shiny App

library(shiny)

# Define la UI
ui <- fluidPage(
  
  # Encabezado de la aplicación
  headerPanel("Análisis de Datos de Reclamaciones"),
  
  # Contenido principal
  mainPanel(
    tabsetPanel(
      tabPanel("Resumen", verbatimTextOutput("summary_output")),
      tabPanel("Gráficos", plotOutput("plots")),
      tabPanel("Regresión Lineal",verbatimTextOutput("regression_output"),
  sidebarLayout(
    sidebarPanel(
      selectInput("BodyPartRegion", "Región de la Parte del Cuerpo", choices=unique(merged_df$BodyPartRegion)),
      selectInput("BodyPart", "Parte del Cuerpo", choices=unique(merged_df$BodyPart)),
      selectInput("ClaimantType", "Tipo de Demandante", choices=unique(merged_df$ClaimantType)),
      selectInput("Gender", "Género", choices=unique(merged_df$Gender)),
      sliderInput("ClaimantAge_at_DOI", "Edad del Demandante en DOI", min=0, max=100, value=0),
      sliderInput("TimeProcesses", "Tiempo de Procesos", min=0, max=1500, value=0),
      selectInput("InjuryNature", "Naturaleza de la Lesión", choices=unique(merged_df$InjuryNature)),
      selectInput("IsDenied", "Es Denegado", choices=unique(merged_df$IsDenied)),
      actionButton("predictButton", "Predecir"),
      br()
    ),
    mainPanel(
      h4("Total Incurred Cost Predicho:"),
      verbatimTextOutput("predictedCost")
    )
  )
),
      tabPanel("Predicción", verbatimTextOutput("prediction_output")),
      tabPanel("Árbol de Decisiones", plotOutput("tree_plot"))
    )
  )
)

# Define el servidor
server <- function(input, output) {
  
  # Resumen de datos
  output$summary_output <- renderPrint({
    data_summary <- summary(merged_df)  # Utiliza la base de datos cargada en RMarkdown
    return(data_summary)
  })
  
  # Gráficos (agrega tus gráficos aquí)
  output$plots <- renderPlot({
    # Puedes agregar tus gráficos personalizados aquí
    # Ejemplo: ggplot(merged_df, aes(x = ClaimStatus)) + geom_bar()
  })
  
  # Regresión Lineal
  output$regression_output <- renderPrint({
  observeEvent(input$predictButton, {
    new_data <- data.frame(
      BodyPartRegion = input$BodyPartRegion,
      BodyPart = input$BodyPart,
      ClaimantType = input$ClaimantType,
      Gender = input$Gender,
      ClaimantAge_at_DOI = input$ClaimantAge_at_DOI,
      TimeProcesses = input$TimeProcesses,
      InjuryNature = input$InjuryNature,
      IsDenied = input$IsDenied
    )
    predicted_cost <- predict(regresion, new_data)
    output$predictedCost <- renderText({
      paste("Total Incurred Cost estimado: $", round(predicted_cost, 2))
    })
  })
})
  
  # Predicción
  output$prediction_output <- renderPrint({
    # Realiza la predicción aquí
    # Ejemplo: data_to_predict <- data.frame(BodyPartRegion = "Upper Extremities", BodyPart = "Hand", ClaimantType = "Medical Only", Gender = "Male", ClaimantAge_at_DOI = 42, TimeProcesses = 989.3, InjuryNature = "Strain", IsDenied = "0")
    # prediction <- predict(regresion, newdata = data_to_predict)
    # prediction
  })
  
  # Árbol de Decisiones
  output$tree_plot <- renderPlot({
    # Crea y muestra el árbol de decisiones aquí
    rpart.plot(arbol)
  })
  
}

# Crea la aplicación Shiny
shinyApp(ui, server)
Shiny applications not supported in static R Markdown documents
LS0tDQp0aXRsZTogIkFjdGl2aWRhZCA0LjYiDQphdXRob3I6ICJFcXVpcG8gNiINCmRhdGU6ICI5LzI2LzIwMjMiDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFDQogICAgdGhlbWU6ICJzaW1wbGV4Ig0KICAgIGhpZ2hsaWdodDogInB5Z21lbnRzIg0KLS0tDQoNCiFbXShkYXRhYW5hbHl0aWNzLnBuZykgIA0KDQojIDxzcGFuIHN0eWxlPSJjb2xvcjogb3JhbmdlOyI+TW9kZWxvcyBkZSBhbsOhbGlzaXMgcHJlZGljdGl2bzwvc3Bhbj4NCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiBvcmFuZ2U7Ij4xLiBMbGFtYXIgYSBsYXMgbGlicmVyaWFzIGUgSW1wb3J0YXIgZGF0b3M8L3NwYW4+DQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQojTGxhbWFyIGEgbGFzIGxpYnJlcmlhcw0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHJlYWR4bCkNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHJwYXJ0KQ0KbGlicmFyeShycGFydC5wbG90KQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShmYWN0b2V4dHJhKQ0KbGlicmFyeShjbHVzdGVyKQ0KbGlicmFyeShkYXRhLnRhYmxlKQ0KDQojSW1wb3J0YXIgZGF0b3MNCmNsYWltc19kYXRhIDwtIHJlYWRfZXhjZWwoIkNsYWltc0RhdGEyMDE4Lnhsc3giKQ0KdHJhbnNhY3Rpb25zX3N1bW1hcnkgPC0gcmVhZC5jc3YoIlRyYW5zYWN0aW9uc1N1bW1hcnkyMDE4LmNzdiIpDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiBvcmFuZ2U7Ij4xLiBFbnRlbmRlciBsYSBiYXNlIGRlIGRhdG9zPC9zcGFuPg0KDQpgYGB7cn0NCm1lcmdlZF9kZiA8LSBtZXJnZShjbGFpbXNfZGF0YSwgdHJhbnNhY3Rpb25zX3N1bW1hcnksIGJ5ID0gIkNsYWltSUQiLCBhbGwgPSBUUlVFKQ0KI3N1bW1hcnkobWVyZ2VkX2RmKQ0KI2NvdW50KG1lcmdlZF9kZiwgQ2xhaW1TdGF0dXMsIHNvcnQ9VFJVRSkNCiNjb3VudChtZXJnZWRfZGYsIEluY2lkZW50RGVzY3JpcHRpb24sIHNvcnQ9VFJVRSkNCiNjb3VudChtZXJnZWRfZGYsIEdlbmRlciwgc29ydD1UUlVFKQ0KI2NvdW50KG1lcmdlZF9kZiwgQ2xhaW1hbnRUeXBlLCBzb3J0PVRSVUUpDQojY291bnQobWVyZ2VkX2RmLCBJbmp1cnlOYXR1cmUsIHNvcnQ9VFJVRSkNCiNjb3VudChtZXJnZWRfZGYsIEJvZHlQYXJ0UmVnaW9uLCBzb3J0PVRSVUUpDQojY291bnQobWVyZ2VkX2RmLCBCb2R5UGFydCwgc29ydD1UUlVFKQ0KI2NvdW50KG1lcmdlZF9kZiwgSXNEZW5pZWQsIHNvcnQ9VFJVRSkNCg0KDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiBvcmFuZ2U7Ij4yLiBMaW1waWFyIGxhIGJhc2UgZGUgZGF0b3M8L3NwYW4+DQoNCmBgYHtyIHdhcm5pbmc9RkFMU0V9DQojIyBDb3JyZWdpciBlbCB0aXBvIGRlIGRhdG9zOg0KDQojIGFzLkRhdGUNCm1lcmdlZF9kZiRJbmNpZGVudERhdGUgPC0gYXMuRGF0ZShtZXJnZWRfZGYkSW5jaWRlbnREYXRlLCIlbS8lZC8lWSIpDQptZXJnZWRfZGYkUmV0dXJuVG9Xb3JrRGF0ZSA8LSBhcy5EYXRlKG1lcmdlZF9kZiRSZXR1cm5Ub1dvcmtEYXRlLCIlbS8lZC8lWSIpDQptZXJnZWRfZGYkQ2xhaW1hbnRPcGVuZWREYXRlIDwtIGFzLkRhdGUobWVyZ2VkX2RmJENsYWltYW50T3BlbmVkRGF0ZSwiJW0vJWQvJVkiKQ0KbWVyZ2VkX2RmJENsYWltYW50Q2xvc2VkRGF0ZSA8LSBhcy5EYXRlKG1lcmdlZF9kZiRDbGFpbWFudENsb3NlZERhdGUsIiVtLyVkLyVZIikNCm1lcmdlZF9kZiRFbXBsb3llck5vdGlmaWNhdGlvbkRhdGUgPC0gYXMuRGF0ZShtZXJnZWRfZGYkRW1wbG95ZXJOb3RpZmljYXRpb25EYXRlLCIlbS8lZC8lWSIpDQptZXJnZWRfZGYkUmVjZWl2ZWREYXRlIDwtIGFzLkRhdGUobWVyZ2VkX2RmJFJlY2VpdmVkRGF0ZSwiJW0vJWQvJVkiKQ0KDQojYXMubnVtZXJpYw0KbWVyZ2VkX2RmJEF2ZXJhZ2VXZWVrbHlXYWdlIDwtIGFzLm51bWVyaWMobWVyZ2VkX2RmJEF2ZXJhZ2VXZWVrbHlXYWdlKQ0KbWVyZ2VkX2RmJElzRGVuaWVkIDwtIGFzLmNoYXJhY3RlcihtZXJnZWRfZGYkSXNEZW5pZWQpDQptZXJnZWRfZGYkQ2xhaW1hbnRBZ2VfYXRfRE9JIDwtIGFzLm51bWVyaWMobWVyZ2VkX2RmJENsYWltYW50QWdlX2F0X0RPSSkNCm1lcmdlZF9kZiRUb3RhbFBhaWQgPC0gYXMubnVtZXJpYyhtZXJnZWRfZGYkVG90YWxSZXNlcnZlcykNCm1lcmdlZF9kZiRUb3RhbFJlY292ZXJ5IDwtIGFzLm51bWVyaWMobWVyZ2VkX2RmJFRvdGFsUmVjb3ZlcnkpDQptZXJnZWRfZGYkVG90YWxSZXNlcnZlcyA8LSBhcy5udW1lcmljKG1lcmdlZF9kZiRUb3RhbFJlc2VydmVzKQ0KbWVyZ2VkX2RmJFRvdGFsUGFpZCA8LSBhcy5udW1lcmljKG1lcmdlZF9kZiRUb3RhbFBhaWQpDQptZXJnZWRfZGYkSW5kZW1uaXR5UGFpZCA8LSBhcy5udW1lcmljKG1lcmdlZF9kZiRJbmRlbW5pdHlQYWlkKQ0KbWVyZ2VkX2RmJE90aGVyUGFpZCA8LSBhcy5udW1lcmljKG1lcmdlZF9kZiRPdGhlclBhaWQpDQptZXJnZWRfZGYkQ2xhaW1hbnRBZ2VfYXRfRE9JIDwtIGFzLm51bWVyaWMobWVyZ2VkX2RmJENsYWltYW50QWdlX2F0X0RPSSkNCg0KDQoNCnN1bW1hcnkobWVyZ2VkX2RmKQ0KDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlOyI+TGltcGlhciBsYSBiYXNlIGRlIGRhdG9zPC9zcGFuPg0KDQpgYGB7cn0NCg0KIyDCv0N1w6FudG9zIE5BIHRlbmdvIGVuIGFsIGJhc2UgZGUgZGF0b3M/DQpzdW0oaXMubmEobWVyZ2VkX2RmJEVtcGxveWVyTm90aWZpY2F0aW9uRGF0ZSkpDQpzdW0oaXMubmEobWVyZ2VkX2RmJFJlY2VpdmVkRGF0ZSkpDQpzdW0oaXMubmEobWVyZ2VkX2RmJENsYWltYW50Q2xvc2VkRGF0ZSkpDQpzdW0oaXMubmEobWVyZ2VkX2RmJFRvdGFsUmVjb3ZlcnkpKQ0Kc3VtKGlzLm5hKG1lcmdlZF9kZiRUb3RhbFJlc2VydmVzKSkNCg0KIyDCv0N1w6FudG9zIE5BIHRlbmdvIHBvciB2YXJpYWJsZT8NCnNhcHBseShtZXJnZWRfZGYsIGZ1bmN0aW9uKHgpIHN1bShpcy5uYSh4KSkpDQoNCg0KYGBgDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogYmx1ZTsiPkFncmVnYXIgY29sdW1uYXM8L3NwYW4+DQoNCmBgYHtyfQ0KDQptZXJnZWRfZGYkVG90YWxJbmN1cnJlZENvc3QgPC0gbWVyZ2VkX2RmJFRvdGFsUmVzZXJ2ZXMgKyBtZXJnZWRfZGYkSW5kZW1uaXR5ICsgbWVyZ2VkX2RmJE90aGVyUGFpZCAtIG1lcmdlZF9kZiRUb3RhbFJlY292ZXJ5DQoNCg0KDQptZXJnZWRfZGYkVGltZVByb2Nlc3NlcyA8LSBhcy5udW1lcmljKGRpZmZ0aW1lKG1lcmdlZF9kZiRDbGFpbWFudENsb3NlZERhdGUsIG1lcmdlZF9kZiRDbGFpbWFudE9wZW5lZERhdGUsIHVuaXQ9ImRheXMiKSkNCg0KYGBgDQoNCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlOyI+UmVncmVzacOzbiBMaW5lYWw8L3NwYW4+DQoNCmBgYHtyfQ0KDQpybGRhdGEgPC0gbWVyZ2VkX2RmICU+JSBzZWxlY3QoVG90YWxJbmN1cnJlZENvc3QsQm9keVBhcnRSZWdpb24sQm9keVBhcnQsQ2xhaW1hbnRUeXBlLEdlbmRlcixDbGFpbWFudEFnZV9hdF9ET0ksVGltZVByb2Nlc3NlcyxJbmp1cnlOYXR1cmUsSXNEZW5pZWQpDQoNCiMgwr9DdcOhbnRvcyBOQSB0ZW5nbyBwb3IgdmFyaWFibGU/DQpzYXBwbHkocmxkYXRhLCBmdW5jdGlvbih4KSBzdW0oaXMubmEoeCkpKQ0KcmxkYXRhIDwtIHJsZGF0YSAlPiUgbmEub21pdCgpDQoNCg0KcmVncmVzaW9uIDwtIGxtKFRvdGFsSW5jdXJyZWRDb3N0IH4gQm9keVBhcnRSZWdpb24gKyBCb2R5UGFydCArIENsYWltYW50VHlwZSArIEdlbmRlciArIENsYWltYW50QWdlX2F0X0RPSSArIFRpbWVQcm9jZXNzZXMgKyBJbmp1cnlOYXR1cmUgKyBJc0RlbmllZCAsIGRhdGEgPSBybGRhdGEpDQoNCiNzdW1tYXJ5KHJlZ3Jlc2lvbikNCmBgYA0KDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogYmx1ZTsiPkNvbnN0cnVjY2nDs24gZGUgTW9kZWxvIFByZWRpY3Rpdm88L3NwYW4+DQoNCmBgYHtyfQ0KDQpkYXRhIDwtIGRhdGEuZnJhbWUoQm9keVBhcnRSZWdpb24gPSAiVXBwZXIgRXh0cmVtaXRpZXMiLCBCb2R5UGFydCA9ICJIYW5kIiwgQ2xhaW1hbnRUeXBlID0gIk1lZGljYWwgT25seSIsIEdlbmRlciA9ICJNYWxlIiwgQ2xhaW1hbnRBZ2VfYXRfRE9JID0gNDIsIFRpbWVQcm9jZXNzZXMgPSA5ODkuMywgSW5qdXJ5TmF0dXJlID0gIlN0cmFpbiIsIElzRGVuaWVkID0gIjAiICkNCg0KcHJlZGljdChyZWdyZXNpb24sIGRhdGEpDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlOyI+QXJib2wgZGUgRGVjaXNpb25lcyA8L3NwYW4+DQpgYGB7cn0NCg0KIyBCYXNlcyBkZSBkYXRvcyBudWV2YXMNCg0KY3VlcnBvIDwtIG1lcmdlZF9kZiAlPiUgc2VsZWN0KEdlbmRlciwgQm9keVBhcnRSZWdpb24pDQpkZW5pZWQgPC0gbWVyZ2VkX2RmICU+JSBzZWxlY3QoR2VuZGVyLCBJc0RlbmllZCkNCg0KI8OBcmJvbGVzIGRlIHByZWRpY2Npw7NuDQphcmJvbCA8LSBycGFydChmb3JtdWxhID0gR2VuZGVyIH4gLiwgZGF0YT1jdWVycG8pDQpycGFydC5wbG90KGFyYm9sKQ0KDQpwcnAoYXJib2wsIGV4dHJhID0gMykNCg0KdHJlZSA8LSBycGFydChmb3JtdWxhID0gR2VuZGVyIH4gLiwgZGF0YT1kZW5pZWQpDQpycGFydC5wbG90KHRyZWUpDQpwcnAodHJlZSwgZXh0cmEgPSAzKQ0KDQoNCmBgYA0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij5DbHVzdGVyczwvc3Bhbj4NCmBgYHtyfQ0KDQpjbHVzdGVyIDwtIG1lcmdlZF9kZiAlPiUgc2VsZWN0KFRvdGFsSW5jdXJyZWRDb3N0LCBUaW1lUHJvY2Vzc2VzKSAlPiUgbmEub21pdCgpICU+JSBmaWx0ZXIoVG90YWxJbmN1cnJlZENvc3Q+MCkgDQoNCmNsdXN0ZXIgPC0gYXMuZGF0YS5mcmFtZShzY2FsZShjbHVzdGVyKSkNCnN1bW1hcnkoY2x1c3RlcikNCg0KIyBMb3MgZGF0b3MgZnVlcmEgZGUgbG8gbm9ybWFsIGVzdMOhbiBmdWVyYSBkZSBsb3Mgc2lndWllbnRlcyBsw61taXRlczogDQojIEzDrW1pdGUgaW5mZXJpb3IgPSBxMSAtMS41KklRUg0KIyBMw61taXRlIHN1cGVyaW9yID0gUTMgKyAxLjUqSVFSDQojIFExOiBDdWFydGlsIDEsIFEzICANCg0KSVFSX0MgPC0gSVFSKGNsdXN0ZXIkVG90YWxJbmN1cnJlZENvc3QpDQpMSV9DIDwtIC0wLjIwMjctMS41KklRUl9DDQpMU19DIDwtIC0wLjE1OTcrMS41KklRUl9DDQpjbHVzdGVyIDwtIGNsdXN0ZXJbY2x1c3RlciRUb3RhbEluY3VycmVkQ29zdCA8PS0wLjA5NSxdDQoNCklRUl9UIDwtIElRUihjbHVzdGVyJFRpbWVQcm9jZXNzZXMpDQpMSV9UPC0gLTAuOTI4MC0xLjUqSVFSX1QNCkxTX1QgPC0gMC41OTQ1KzEuNSpJUVJfVA0KY2x1c3RlciA8LSBjbHVzdGVyW2NsdXN0ZXIkVGltZVByb2Nlc3Nlczw9Mi44NzgzLF0NCg0KDQpncnVwb3MgPC0gNA0Kc2VnbWVudG9zIDwtIGttZWFucyhjbHVzdGVyLCBncnVwb3MpDQphc2lnbmFjacOzbiA8LSBjYmluZChjbHVzdGVyLCBjbHVzdGVyPXNlZ21lbnRvcyRjbHVzdGVyKQ0KDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlOyI+R3LDoWZpY2EgeSBPcHRpbWl6YWNpw7NuPC9zcGFuPg0KDQpgYGB7cn0NCmZ2aXpfY2x1c3RlcihzZWdtZW50b3MsIGRhdGE9Y2x1c3RlcikNCg0KYGBgDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogYmx1ZTsiPlNoaW55IEFwcDwvc3Bhbj4NCmBgYHtyfQ0KbGlicmFyeShzaGlueSkNCg0KIyBEZWZpbmUgbGEgVUkNCnVpIDwtIGZsdWlkUGFnZSgNCiAgDQogICMgRW5jYWJlemFkbyBkZSBsYSBhcGxpY2FjacOzbg0KICBoZWFkZXJQYW5lbCgiQW7DoWxpc2lzIGRlIERhdG9zIGRlIFJlY2xhbWFjaW9uZXMiKSwNCiAgDQogICMgQ29udGVuaWRvIHByaW5jaXBhbA0KICBtYWluUGFuZWwoDQogICAgdGFic2V0UGFuZWwoDQogICAgICB0YWJQYW5lbCgiUmVzdW1lbiIsIHZlcmJhdGltVGV4dE91dHB1dCgic3VtbWFyeV9vdXRwdXQiKSksDQogICAgICB0YWJQYW5lbCgiR3LDoWZpY29zIiwgcGxvdE91dHB1dCgicGxvdHMiKSksDQogICAgICB0YWJQYW5lbCgiUmVncmVzacOzbiBMaW5lYWwiLHZlcmJhdGltVGV4dE91dHB1dCgicmVncmVzc2lvbl9vdXRwdXQiKSwNCiAgc2lkZWJhckxheW91dCgNCiAgICBzaWRlYmFyUGFuZWwoDQogICAgICBzZWxlY3RJbnB1dCgiQm9keVBhcnRSZWdpb24iLCAiUmVnacOzbiBkZSBsYSBQYXJ0ZSBkZWwgQ3VlcnBvIiwgY2hvaWNlcz11bmlxdWUobWVyZ2VkX2RmJEJvZHlQYXJ0UmVnaW9uKSksDQogICAgICBzZWxlY3RJbnB1dCgiQm9keVBhcnQiLCAiUGFydGUgZGVsIEN1ZXJwbyIsIGNob2ljZXM9dW5pcXVlKG1lcmdlZF9kZiRCb2R5UGFydCkpLA0KICAgICAgc2VsZWN0SW5wdXQoIkNsYWltYW50VHlwZSIsICJUaXBvIGRlIERlbWFuZGFudGUiLCBjaG9pY2VzPXVuaXF1ZShtZXJnZWRfZGYkQ2xhaW1hbnRUeXBlKSksDQogICAgICBzZWxlY3RJbnB1dCgiR2VuZGVyIiwgIkfDqW5lcm8iLCBjaG9pY2VzPXVuaXF1ZShtZXJnZWRfZGYkR2VuZGVyKSksDQogICAgICBzbGlkZXJJbnB1dCgiQ2xhaW1hbnRBZ2VfYXRfRE9JIiwgIkVkYWQgZGVsIERlbWFuZGFudGUgZW4gRE9JIiwgbWluPTAsIG1heD0xMDAsIHZhbHVlPTApLA0KICAgICAgc2xpZGVySW5wdXQoIlRpbWVQcm9jZXNzZXMiLCAiVGllbXBvIGRlIFByb2Nlc29zIiwgbWluPTAsIG1heD0xNTAwLCB2YWx1ZT0wKSwNCiAgICAgIHNlbGVjdElucHV0KCJJbmp1cnlOYXR1cmUiLCAiTmF0dXJhbGV6YSBkZSBsYSBMZXNpw7NuIiwgY2hvaWNlcz11bmlxdWUobWVyZ2VkX2RmJEluanVyeU5hdHVyZSkpLA0KICAgICAgc2VsZWN0SW5wdXQoIklzRGVuaWVkIiwgIkVzIERlbmVnYWRvIiwgY2hvaWNlcz11bmlxdWUobWVyZ2VkX2RmJElzRGVuaWVkKSksDQogICAgICBhY3Rpb25CdXR0b24oInByZWRpY3RCdXR0b24iLCAiUHJlZGVjaXIiKSwNCiAgICAgIGJyKCkNCiAgICApLA0KICAgIG1haW5QYW5lbCgNCiAgICAgIGg0KCJUb3RhbCBJbmN1cnJlZCBDb3N0IFByZWRpY2hvOiIpLA0KICAgICAgdmVyYmF0aW1UZXh0T3V0cHV0KCJwcmVkaWN0ZWRDb3N0IikNCiAgICApDQogICkNCiksDQogICAgICB0YWJQYW5lbCgiUHJlZGljY2nDs24iLCB2ZXJiYXRpbVRleHRPdXRwdXQoInByZWRpY3Rpb25fb3V0cHV0IikpLA0KICAgICAgdGFiUGFuZWwoIsOBcmJvbCBkZSBEZWNpc2lvbmVzIiwgcGxvdE91dHB1dCgidHJlZV9wbG90IikpDQogICAgKQ0KICApDQopDQoNCiMgRGVmaW5lIGVsIHNlcnZpZG9yDQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCkgew0KICANCiAgIyBSZXN1bWVuIGRlIGRhdG9zDQogIG91dHB1dCRzdW1tYXJ5X291dHB1dCA8LSByZW5kZXJQcmludCh7DQogICAgZGF0YV9zdW1tYXJ5IDwtIHN1bW1hcnkobWVyZ2VkX2RmKSAgIyBVdGlsaXphIGxhIGJhc2UgZGUgZGF0b3MgY2FyZ2FkYSBlbiBSTWFya2Rvd24NCiAgICByZXR1cm4oZGF0YV9zdW1tYXJ5KQ0KICB9KQ0KICANCiAgIyBHcsOhZmljb3MgKGFncmVnYSB0dXMgZ3LDoWZpY29zIGFxdcOtKQ0KICBvdXRwdXQkcGxvdHMgPC0gcmVuZGVyUGxvdCh7DQogICAgIyBQdWVkZXMgYWdyZWdhciB0dXMgZ3LDoWZpY29zIHBlcnNvbmFsaXphZG9zIGFxdcOtDQogICAgIyBFamVtcGxvOiBnZ3Bsb3QobWVyZ2VkX2RmLCBhZXMoeCA9IENsYWltU3RhdHVzKSkgKyBnZW9tX2JhcigpDQogIH0pDQogIA0KICAjIFJlZ3Jlc2nDs24gTGluZWFsDQogIG91dHB1dCRyZWdyZXNzaW9uX291dHB1dCA8LSByZW5kZXJQcmludCh7DQogIG9ic2VydmVFdmVudChpbnB1dCRwcmVkaWN0QnV0dG9uLCB7DQogICAgbmV3X2RhdGEgPC0gZGF0YS5mcmFtZSgNCiAgICAgIEJvZHlQYXJ0UmVnaW9uID0gaW5wdXQkQm9keVBhcnRSZWdpb24sDQogICAgICBCb2R5UGFydCA9IGlucHV0JEJvZHlQYXJ0LA0KICAgICAgQ2xhaW1hbnRUeXBlID0gaW5wdXQkQ2xhaW1hbnRUeXBlLA0KICAgICAgR2VuZGVyID0gaW5wdXQkR2VuZGVyLA0KICAgICAgQ2xhaW1hbnRBZ2VfYXRfRE9JID0gaW5wdXQkQ2xhaW1hbnRBZ2VfYXRfRE9JLA0KICAgICAgVGltZVByb2Nlc3NlcyA9IGlucHV0JFRpbWVQcm9jZXNzZXMsDQogICAgICBJbmp1cnlOYXR1cmUgPSBpbnB1dCRJbmp1cnlOYXR1cmUsDQogICAgICBJc0RlbmllZCA9IGlucHV0JElzRGVuaWVkDQogICAgKQ0KICAgIHByZWRpY3RlZF9jb3N0IDwtIHByZWRpY3QocmVncmVzaW9uLCBuZXdfZGF0YSkNCiAgICBvdXRwdXQkcHJlZGljdGVkQ29zdCA8LSByZW5kZXJUZXh0KHsNCiAgICAgIHBhc3RlKCJUb3RhbCBJbmN1cnJlZCBDb3N0IGVzdGltYWRvOiAkIiwgcm91bmQocHJlZGljdGVkX2Nvc3QsIDIpKQ0KICAgIH0pDQogIH0pDQp9KQ0KICANCiAgIyBQcmVkaWNjacOzbg0KICBvdXRwdXQkcHJlZGljdGlvbl9vdXRwdXQgPC0gcmVuZGVyUHJpbnQoew0KICAgICMgUmVhbGl6YSBsYSBwcmVkaWNjacOzbiBhcXXDrQ0KICAgICMgRWplbXBsbzogZGF0YV90b19wcmVkaWN0IDwtIGRhdGEuZnJhbWUoQm9keVBhcnRSZWdpb24gPSAiVXBwZXIgRXh0cmVtaXRpZXMiLCBCb2R5UGFydCA9ICJIYW5kIiwgQ2xhaW1hbnRUeXBlID0gIk1lZGljYWwgT25seSIsIEdlbmRlciA9ICJNYWxlIiwgQ2xhaW1hbnRBZ2VfYXRfRE9JID0gNDIsIFRpbWVQcm9jZXNzZXMgPSA5ODkuMywgSW5qdXJ5TmF0dXJlID0gIlN0cmFpbiIsIElzRGVuaWVkID0gIjAiKQ0KICAgICMgcHJlZGljdGlvbiA8LSBwcmVkaWN0KHJlZ3Jlc2lvbiwgbmV3ZGF0YSA9IGRhdGFfdG9fcHJlZGljdCkNCiAgICAjIHByZWRpY3Rpb24NCiAgfSkNCiAgDQogICMgw4FyYm9sIGRlIERlY2lzaW9uZXMNCiAgb3V0cHV0JHRyZWVfcGxvdCA8LSByZW5kZXJQbG90KHsNCiAgICAjIENyZWEgeSBtdWVzdHJhIGVsIMOhcmJvbCBkZSBkZWNpc2lvbmVzIGFxdcOtDQogICAgcnBhcnQucGxvdChhcmJvbCkNCiAgfSkNCiAgDQp9DQoNCiMgQ3JlYSBsYSBhcGxpY2FjacOzbiBTaGlueQ0Kc2hpbnlBcHAodWksIHNlcnZlcikNCmBgYA0KDQoNCg0KDQoNCg0KDQo=