Load the relevant libraries.
# rm(list = ls())
library("tidyverse")          # data manipulation
library("magrittr")           # data manipulation (pipeing data)
library("stringr")            # string manipulation
library("lubridate")          # date manipulation
library("tidytext")           # text manipulation
library("topicmodels")        # topic modeling
library("ggplot2")            # viz
library("doParallel")         # parallel processing
library("ldatuning")          # estimating the proper number of topics
# library("scales")
# library("cowplot")            # grid plotting
Session Info.
sessionInfo()
R version 3.3.3 (2017-03-06)
Platform: x86_64-apple-darwin13.4.0 (64-bit)
Running under: macOS Sierra 10.12.6

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] parallel  stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] ldatuning_0.2.0   doParallel_1.0.11 iterators_1.0.8   foreach_1.4.3    
 [5] topicmodels_0.2-7 tidytext_0.1.4    lubridate_1.7.1   stringr_1.2.0    
 [9] magrittr_1.5      dplyr_0.7.4       purrr_0.2.4       readr_1.1.1      
[13] tidyr_0.7.2       tibble_1.3.4      ggplot2_2.2.1     tidyverse_1.1.1  

loaded via a namespace (and not attached):
 [1] modeltools_0.2-21 slam_0.1-40       NLP_0.1-11        reshape2_1.4.2   
 [5] haven_1.1.0       lattice_0.20-35   colorspace_1.3-2  SnowballC_0.5.1  
 [9] stats4_3.3.3      yaml_2.1.14       rlang_0.1.4       foreign_0.8-69   
[13] glue_1.2.0        modelr_0.1.1      readxl_1.0.0      bindrcpp_0.2     
[17] bindr_0.1         plyr_1.8.4        munsell_0.4.3     gtable_0.2.0     
[21] cellranger_1.1.0  rvest_0.3.2       codetools_0.2-15  psych_1.7.8      
[25] knitr_1.17        forcats_0.2.0     tm_0.7-1          broom_0.4.2      
[29] tokenizers_0.1.4  Rcpp_0.12.13      scales_0.5.0      jsonlite_1.5     
[33] mnormt_1.5-5      hms_0.3           stringi_1.1.5     grid_3.3.3       
[37] tools_3.3.3       lazyeval_0.2.1    janeaustenr_0.1.5 pkgconfig_2.0.1  
[41] Matrix_1.2-11     xml2_1.1.1        assertthat_0.2.0  httr_1.3.1       
[45] R6_2.2.2          nlme_3.1-131     
Setup the root directory.
Setting `wd` as the working directory.
wd <- getwd()
wd
[1] "/Users/mdturse/Desktop/Analytics/dc_doh_hackathon"
Get the raw data. Because of trouble maintaining a connection to Dropbox via R, I first downloaded the raw data from [https://www.dropbox.com/sh/4j7q53lltasez3h/AABQrwrQj3aenqG42wbW7-qra/years_combined/dc_311-2017-01-16.csv?dl=0](https://www.dropbox.com/sh/4j7q53lltasez3h/AABQrwrQj3aenqG42wbW7-qra/years_combined/dc_311-2017-01-16.csv?dl=0) and saved the file locally.
Raw311Data <- read_csv(paste0(wd,
                              "/Data_Raw/dc_311-2017-01-16.csv"
                              # "/Data_Raw/dc_311-2017-10-07.csv"
                              ),
                       progress = FALSE
                       )
Parsed with column specification:
cols(
  .default = col_character(),
  SERVICEORDERDATE = col_datetime(format = ""),
  SERVICECALLCOUNT = col_integer(),
  INSPECTIONDATE = col_datetime(format = ""),
  RESOLUTIONDATE = col_datetime(format = ""),
  SERVICEDUEDATE = col_datetime(format = ""),
  ADDDATE = col_datetime(format = ""),
  LASTMODIFIEDDATE = col_datetime(format = ""),
  LATITUDE = col_double(),
  LONGITUDE = col_double(),
  ZIPCODE = col_integer(),
  MARADDRESSREPOSITORYID = col_integer(),
  DCSTATADDRESSKEY = col_integer(),
  DCSTATLOCATIONKEY = col_integer(),
  WARD = col_integer(),
  PSA = col_integer()
)
See spec(...) for full column specifications.
# saving is done to avoid having to download all the data again
saveRDS(Raw311Data,
        paste0(wd,
               "/Data_Processed/",
               "Raw311Data.Rds"
               )
        )
str(Raw311Data)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   5093887 obs. of  36 variables:
 $ SERVICEREQUESTID          : chr  "10-00020033" "10-00020034" "10-00020035" "10-00020036" ...
 $ SERVICEPRIORITY           : chr  "UNKNOWN" "UNKNOWN" "UNKNOWN" "UNKNOWN" ...
 $ SERVICECODE               : chr  "S0276" "S0276" "S0276" "S0276" ...
 $ SERVICECODEDESCRIPTION    : chr  "Parking Meter Repair" "Parking Meter Repair" "Parking Meter Repair" "Parking Meter Repair" ...
 $ SERVICETYPECODE           : chr  "TRAOP001" "TRAOP001" "TRAOP001" "TRAOP001" ...
 $ SERVICETYPECODEDESCRIPTION: chr  "TOA" "TOA" "TOA" "TOA" ...
 $ SERVICEORDERDATE          : POSIXct, format: "2010-01-21 14:58:07" "2010-01-21 14:58:08" ...
 $ SERVICEORDERSTATUS        : chr  "OVERDUE CLOSED" "CLOSED" "CLOSED" "CLOSED" ...
 $ SERVICECALLCOUNT          : int  1 1 1 1 1 1 1 1 1 1 ...
 $ AGENCYABBREVIATION        : chr  "DDOT" "DDOT" "DDOT" "DDOT" ...
 $ INSPECTIONFLAG            : chr  "N" "N" "N" "N" ...
 $ INSPECTIONDATE            : POSIXct, format: NA NA ...
 $ RESOLUTION                : chr  "UNKNOWN" "UNKNOWN" "UNKNOWN" "UNKNOWN" ...
 $ RESOLUTIONDATE            : POSIXct, format: "2010-01-26 19:31:32" "2010-01-21 15:43:45" ...
 $ SERVICEDUEDATE            : POSIXct, format: "2010-01-26 14:58:07" "2010-01-26 14:58:08" ...
 $ SERVICENOTES              : chr  NA NA NA NA ...
 $ PARENTSERVICEREQUESTID    : chr  NA NA NA NA ...
 $ ADDDATE                   : POSIXct, format: "2010-01-21 14:58:07" "2010-01-21 14:58:08" ...
 $ LASTMODIFIEDDATE          : POSIXct, format: "2011-09-01 11:18:17" "2012-08-06 21:19:19" ...
 $ SITEADDRESS               : chr  "17TH STREET NW & RHODE ISLAND AVENUE NW" "20TH STREET NW & K STREET NW" "800 I STREET NW" "901 15TH STREET NW" ...
 $ LATITUDE                  : num  38.9 38.9 38.9 38.9 38.9 ...
 $ LONGITUDE                 : num  -77 -77 -77 -77 -77 ...
 $ ZIPCODE                   : int  NA NA 20001 20005 NA NA NA 20002 20018 NA ...
 $ MARADDRESSREPOSITORYID    : int  0 0 285559 218779 0 -100 -100 76069 50068 -100 ...
 $ DCSTATADDRESSKEY          : int  207502 207430 124416 62196 455845 -100 -100 49582 20159 -100 ...
 $ DCSTATLOCATIONKEY         : int  207502 207430 124416 62196 391873 348291 348291 49582 20159 348291 ...
 $ WARD                      : int  2 2 2 2 2 NA NA 6 5 NA ...
 $ ANC                       : chr  "2B" "2A" "2C" "2F" ...
 $ SMD                       : chr  "2B05" "2A06" "2C03" "2F03" ...
 $ DISTRICT                  : chr  "SECOND" "SECOND" "FIRST" "SECOND" ...
 $ PSA                       : int  208 207 102 207 208 NA NA 108 503 NA ...
 $ NEIGHBORHOODCLUSTER       : chr  "6" "6" "8" "8" ...
 $ HOTSPOT2006NAME           : chr  "NONE" "NONE" "NONE" "NONE" ...
 $ HOTSPOT2005NAME           : chr  "NONE" "NONE" "NONE" "NONE" ...
 $ HOTSPOT2004NAME           : chr  "NONE" "NONE" "NONE" "NONE" ...
 $ SERVICESOURCECODE         : chr  "PHONE" "PHONE" "PHONE" "PHONE" ...
 - attr(*, "problems")=Classes ‘tbl_df’, ‘tbl’ and 'data.frame':    3874 obs. of  5 variables:
  ..$ row     : int  3938387 3938538 3939027 3939187 3939240 3939316 3939514 3939869 3940110 3940469 ...
  ..$ col     : chr  "WARD" "WARD" "WARD" "WARD" ...
  ..$ expected: chr  "an integer" "an integer" "an integer" "an integer" ...
  ..$ actual  : chr  "Null" "Null" "Null" "Null" ...
  ..$ file    : chr  "'/Users/mdturse/Desktop/Analytics/dc_doh_hackathon/Data_Raw/dc_311-2017-01-16.csv'" "'/Users/mdturse/Desktop/Analytics/dc_doh_hackathon/Data_Raw/dc_311-2017-01-16.csv'" "'/Users/mdturse/Desktop/Analytics/dc_doh_hackathon/Data_Raw/dc_311-2017-01-16.csv'" "'/Users/mdturse/Desktop/Analytics/dc_doh_hackathon/Data_Raw/dc_311-2017-01-16.csv'" ...
 - attr(*, "spec")=List of 2
  ..$ cols   :List of 36
  .. ..$ SERVICEREQUESTID          : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICEPRIORITY           : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICECODE               : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICECODEDESCRIPTION    : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICETYPECODE           : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICETYPECODEDESCRIPTION: list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICEORDERDATE          :List of 1
  .. .. ..$ format: chr ""
  .. .. ..- attr(*, "class")= chr  "collector_datetime" "collector"
  .. ..$ SERVICEORDERSTATUS        : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICECALLCOUNT          : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ AGENCYABBREVIATION        : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ INSPECTIONFLAG            : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ INSPECTIONDATE            :List of 1
  .. .. ..$ format: chr ""
  .. .. ..- attr(*, "class")= chr  "collector_datetime" "collector"
  .. ..$ RESOLUTION                : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ RESOLUTIONDATE            :List of 1
  .. .. ..$ format: chr ""
  .. .. ..- attr(*, "class")= chr  "collector_datetime" "collector"
  .. ..$ SERVICEDUEDATE            :List of 1
  .. .. ..$ format: chr ""
  .. .. ..- attr(*, "class")= chr  "collector_datetime" "collector"
  .. ..$ SERVICENOTES              : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ PARENTSERVICEREQUESTID    : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ ADDDATE                   :List of 1
  .. .. ..$ format: chr ""
  .. .. ..- attr(*, "class")= chr  "collector_datetime" "collector"
  .. ..$ LASTMODIFIEDDATE          :List of 1
  .. .. ..$ format: chr ""
  .. .. ..- attr(*, "class")= chr  "collector_datetime" "collector"
  .. ..$ SITEADDRESS               : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ LATITUDE                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_double" "collector"
  .. ..$ LONGITUDE                 : list()
  .. .. ..- attr(*, "class")= chr  "collector_double" "collector"
  .. ..$ ZIPCODE                   : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ MARADDRESSREPOSITORYID    : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ DCSTATADDRESSKEY          : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ DCSTATLOCATIONKEY         : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ WARD                      : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ ANC                       : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SMD                       : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ DISTRICT                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ PSA                       : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ NEIGHBORHOODCLUSTER       : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ HOTSPOT2006NAME           : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ HOTSPOT2005NAME           : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ HOTSPOT2004NAME           : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICESOURCECODE         : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  ..$ default: list()
  .. ..- attr(*, "class")= chr  "collector_guess" "collector"
  ..- attr(*, "class")= chr "col_spec"
tail(Raw311Data, 500)
Un-comment the chunk below to load the saved raw data (to avoid having to download the raw data again).
# Raw311Data <- readRDS(paste0(wd,
#                              "/Data_Processed/",
#                              "Raw311Data.Rds"
#                              )
#                       )
# 
# str(Raw311Data)
# tail(Raw311Data, 500)
# View(tail(Raw311Data, 1000))
Selecting those variables that may be useful to test breakdowns of topic modeling.  For example, running a topic model separately for the different levels of `servicecode`.
SelectedVars <- select(Raw311Data,
                       SERVICEREQUESTID,
                       SERVICEPRIORITY,
                       SERVICECODE,
                       SERVICECODEDESCRIPTION,
                       SERVICETYPECODE,
                       SERVICETYPECODEDESCRIPTION,
                       SERVICEORDERDATE,
                       SERVICENOTES
                       )
names(SelectedVars) <- tolower(names(SelectedVars))
rm(Raw311Data)
str(SelectedVars)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   5093887 obs. of  8 variables:
 $ servicerequestid          : chr  "10-00020033" "10-00020034" "10-00020035" "10-00020036" ...
 $ servicepriority           : chr  "UNKNOWN" "UNKNOWN" "UNKNOWN" "UNKNOWN" ...
 $ servicecode               : chr  "S0276" "S0276" "S0276" "S0276" ...
 $ servicecodedescription    : chr  "Parking Meter Repair" "Parking Meter Repair" "Parking Meter Repair" "Parking Meter Repair" ...
 $ servicetypecode           : chr  "TRAOP001" "TRAOP001" "TRAOP001" "TRAOP001" ...
 $ servicetypecodedescription: chr  "TOA" "TOA" "TOA" "TOA" ...
 $ serviceorderdate          : POSIXct, format: "2010-01-21 14:58:07" "2010-01-21 14:58:08" ...
 $ servicenotes              : chr  NA NA NA NA ...
 - attr(*, "problems")=Classes ‘tbl_df’, ‘tbl’ and 'data.frame':    3874 obs. of  5 variables:
  ..$ row     : int  3938387 3938538 3939027 3939187 3939240 3939316 3939514 3939869 3940110 3940469 ...
  ..$ col     : chr  "WARD" "WARD" "WARD" "WARD" ...
  ..$ expected: chr  "an integer" "an integer" "an integer" "an integer" ...
  ..$ actual  : chr  "Null" "Null" "Null" "Null" ...
  ..$ file    : chr  "'/Users/mdturse/Desktop/Analytics/dc_doh_hackathon/Data_Raw/dc_311-2017-01-16.csv'" "'/Users/mdturse/Desktop/Analytics/dc_doh_hackathon/Data_Raw/dc_311-2017-01-16.csv'" "'/Users/mdturse/Desktop/Analytics/dc_doh_hackathon/Data_Raw/dc_311-2017-01-16.csv'" "'/Users/mdturse/Desktop/Analytics/dc_doh_hackathon/Data_Raw/dc_311-2017-01-16.csv'" ...
 - attr(*, "spec")=List of 2
  ..$ cols   :List of 36
  .. ..$ SERVICEREQUESTID          : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICEPRIORITY           : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICECODE               : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICECODEDESCRIPTION    : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICETYPECODE           : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICETYPECODEDESCRIPTION: list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICEORDERDATE          :List of 1
  .. .. ..$ format: chr ""
  .. .. ..- attr(*, "class")= chr  "collector_datetime" "collector"
  .. ..$ SERVICEORDERSTATUS        : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICECALLCOUNT          : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ AGENCYABBREVIATION        : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ INSPECTIONFLAG            : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ INSPECTIONDATE            :List of 1
  .. .. ..$ format: chr ""
  .. .. ..- attr(*, "class")= chr  "collector_datetime" "collector"
  .. ..$ RESOLUTION                : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ RESOLUTIONDATE            :List of 1
  .. .. ..$ format: chr ""
  .. .. ..- attr(*, "class")= chr  "collector_datetime" "collector"
  .. ..$ SERVICEDUEDATE            :List of 1
  .. .. ..$ format: chr ""
  .. .. ..- attr(*, "class")= chr  "collector_datetime" "collector"
  .. ..$ SERVICENOTES              : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ PARENTSERVICEREQUESTID    : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ ADDDATE                   :List of 1
  .. .. ..$ format: chr ""
  .. .. ..- attr(*, "class")= chr  "collector_datetime" "collector"
  .. ..$ LASTMODIFIEDDATE          :List of 1
  .. .. ..$ format: chr ""
  .. .. ..- attr(*, "class")= chr  "collector_datetime" "collector"
  .. ..$ SITEADDRESS               : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ LATITUDE                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_double" "collector"
  .. ..$ LONGITUDE                 : list()
  .. .. ..- attr(*, "class")= chr  "collector_double" "collector"
  .. ..$ ZIPCODE                   : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ MARADDRESSREPOSITORYID    : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ DCSTATADDRESSKEY          : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ DCSTATLOCATIONKEY         : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ WARD                      : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ ANC                       : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SMD                       : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ DISTRICT                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ PSA                       : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ NEIGHBORHOODCLUSTER       : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ HOTSPOT2006NAME           : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ HOTSPOT2005NAME           : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ HOTSPOT2004NAME           : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICESOURCECODE         : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  ..$ default: list()
  .. ..- attr(*, "class")= chr  "collector_guess" "collector"
  ..- attr(*, "class")= chr "col_spec"
Quick visual inspection of filtering the data to only service calls with notes (i.e., removing NA values), and only those that are rat-related (`servicecode == "S0311`).  
  
Removing NA values takes us from 5,093,887 rows to 3,018,411 rows.  

Looking at only rat-related service calls takes us from 3,018,411 rows to 15,581 rows.
NoNAServiceNotes <- filter(SelectedVars,
                           !is.na(servicenotes)
                           )
# message("SelectedVars")
nrow(SelectedVars)
[1] 5093887
# message("NoNAServiceNotes")
nrow(NoNAServiceNotes)
[1] 3018411
rm(SelectedVars)
str(NoNAServiceNotes)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   3018411 obs. of  8 variables:
 $ servicerequestid          : chr  "10-00020037" "10-00020041" "10-00020044" "10-00020050" ...
 $ servicepriority           : chr  "UNKNOWN" "UNKNOWN" "UNKNOWN" "UNKNOWN" ...
 $ servicecode               : chr  "DMV19" "S0003" "S0276" "S0276" ...
 $ servicecodedescription    : chr  "DMV - Vehicle Registration Issues" "Abandoned Vehicle - On Private Property" "Parking Meter Repair" "Parking Meter Repair" ...
 $ servicetypecode           : chr  "DRIVEHSE" "PRSVAVOP" "TRAOP001" "TRAOP001" ...
 $ servicetypecodedescription: chr  "Driver Vehicle Services" "Abandoned Vehicle Operations - Private" "TOA" "TOA" ...
 $ serviceorderdate          : POSIXct, format: "2010-01-21 14:58:22" "2010-01-21 14:58:56" ...
 $ servicenotes              : chr  "customer was called informed a trace will be placed on the request" "VEHICLE IN QUESTION WAS GONE ON ARRIVAL  PER INVESTIGATOR AUSTIN......TJ" "Comments update:  10-00020044-DEAD" "Comments update:  10-00020050-METER DISPLAYED OUT OF ORDER." ...
 - attr(*, "problems")=Classes ‘tbl_df’, ‘tbl’ and 'data.frame':    3874 obs. of  5 variables:
  ..$ row     : int  3938387 3938538 3939027 3939187 3939240 3939316 3939514 3939869 3940110 3940469 ...
  ..$ col     : chr  "WARD" "WARD" "WARD" "WARD" ...
  ..$ expected: chr  "an integer" "an integer" "an integer" "an integer" ...
  ..$ actual  : chr  "Null" "Null" "Null" "Null" ...
  ..$ file    : chr  "'/Users/mdturse/Desktop/Analytics/dc_doh_hackathon/Data_Raw/dc_311-2017-01-16.csv'" "'/Users/mdturse/Desktop/Analytics/dc_doh_hackathon/Data_Raw/dc_311-2017-01-16.csv'" "'/Users/mdturse/Desktop/Analytics/dc_doh_hackathon/Data_Raw/dc_311-2017-01-16.csv'" "'/Users/mdturse/Desktop/Analytics/dc_doh_hackathon/Data_Raw/dc_311-2017-01-16.csv'" ...
 - attr(*, "spec")=List of 2
  ..$ cols   :List of 36
  .. ..$ SERVICEREQUESTID          : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICEPRIORITY           : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICECODE               : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICECODEDESCRIPTION    : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICETYPECODE           : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICETYPECODEDESCRIPTION: list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICEORDERDATE          :List of 1
  .. .. ..$ format: chr ""
  .. .. ..- attr(*, "class")= chr  "collector_datetime" "collector"
  .. ..$ SERVICEORDERSTATUS        : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICECALLCOUNT          : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ AGENCYABBREVIATION        : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ INSPECTIONFLAG            : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ INSPECTIONDATE            :List of 1
  .. .. ..$ format: chr ""
  .. .. ..- attr(*, "class")= chr  "collector_datetime" "collector"
  .. ..$ RESOLUTION                : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ RESOLUTIONDATE            :List of 1
  .. .. ..$ format: chr ""
  .. .. ..- attr(*, "class")= chr  "collector_datetime" "collector"
  .. ..$ SERVICEDUEDATE            :List of 1
  .. .. ..$ format: chr ""
  .. .. ..- attr(*, "class")= chr  "collector_datetime" "collector"
  .. ..$ SERVICENOTES              : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ PARENTSERVICEREQUESTID    : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ ADDDATE                   :List of 1
  .. .. ..$ format: chr ""
  .. .. ..- attr(*, "class")= chr  "collector_datetime" "collector"
  .. ..$ LASTMODIFIEDDATE          :List of 1
  .. .. ..$ format: chr ""
  .. .. ..- attr(*, "class")= chr  "collector_datetime" "collector"
  .. ..$ SITEADDRESS               : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ LATITUDE                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_double" "collector"
  .. ..$ LONGITUDE                 : list()
  .. .. ..- attr(*, "class")= chr  "collector_double" "collector"
  .. ..$ ZIPCODE                   : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ MARADDRESSREPOSITORYID    : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ DCSTATADDRESSKEY          : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ DCSTATLOCATIONKEY         : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ WARD                      : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ ANC                       : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SMD                       : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ DISTRICT                  : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ PSA                       : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ NEIGHBORHOODCLUSTER       : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ HOTSPOT2006NAME           : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ HOTSPOT2005NAME           : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ HOTSPOT2004NAME           : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICESOURCECODE         : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  ..$ default: list()
  .. ..- attr(*, "class")= chr  "collector_guess" "collector"
  ..- attr(*, "class")= chr "col_spec"
View(head(NoNAServiceNotes, 1000))
RatCalls <- filter(NoNAServiceNotes,
                   servicecode == "S0311"
                   )
# message("NoNAServiceNotes")
nrow(NoNAServiceNotes)
[1] 3018411
# message("RatCalls")
nrow(RatCalls)
[1] 15581
rm(NoNAServiceNotes)
View(RatCalls)
Add in time-related variables.
RatCalls_Time <- RatCalls %>%
  mutate(serviceorder_date = as_date(serviceorderdate),
         serviceorder_yr = year(serviceorderdate),
         serviceorder_yr_posix = floor_date(serviceorderdate, "year"),
         serviceorder_mth = month(serviceorderdate, label = TRUE),
         serviceorder_yrmth = as.character(serviceorder_date) %>% 
           substr(1, 7),
         serviceorder_yrmth_posix = floor_date(serviceorderdate, "month"),
         serviceorder_day = day(serviceorderdate),
         serviceorder_wkday = wday(serviceorderdate, label = TRUE)
         )
rm(RatCalls)
str(RatCalls_Time)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   15581 obs. of  16 variables:
 $ servicerequestid          : chr  "10-00020177" "10-00020346" "10-00020514" "10-00020754" ...
 $ servicepriority           : chr  "UNKNOWN" "UNKNOWN" "UNKNOWN" "UNKNOWN" ...
 $ servicecode               : chr  "S0311" "S0311" "S0311" "S0311" ...
 $ servicecodedescription    : chr  "Rat Abatement" "Rat Abatement" "Rat Abatement" "Rat Abatement" ...
 $ servicetypecode           : chr  "DEPAHEAL" "DEPAHEAL" "DEPAHEAL" "DEPAHEAL" ...
 $ servicetypecodedescription: chr  "DOH" "DOH" "DOH" "DOH" ...
 $ serviceorderdate          : POSIXct, format: "2010-01-21 15:46:23" "2010-01-21 16:32:51" ...
 $ servicenotes              : chr  "On 1/25/10 L Rogers baited 3 rat burrows in the rear yd." "On 1/26/10 G Cornes found no rat burrows in the area." "On 1/28/10 L Rogers baited 4 rat burrows in the rear yd." "On 1/26/10 V Purvis baited 9 rat burrows in the park." ...
 $ serviceorder_date         : Date, format: "2010-01-21" "2010-01-21" ...
 $ serviceorder_yr           : num  2010 2010 2010 2010 2010 2010 2010 2010 2010 2010 ...
 $ serviceorder_yr_posix     : POSIXct, format: "2010-01-01" "2010-01-01" ...
 $ serviceorder_mth          : Ord.factor w/ 12 levels "Jan"<"Feb"<"Mar"<..: 1 1 1 1 1 1 1 1 1 1 ...
 $ serviceorder_yrmth        : chr  "2010-01" "2010-01" "2010-01" "2010-01" ...
 $ serviceorder_yrmth_posix  : POSIXct, format: "2010-01-01" "2010-01-01" ...
 $ serviceorder_day          : int  21 21 21 21 21 22 22 22 22 22 ...
 $ serviceorder_wkday        : Ord.factor w/ 7 levels "Sun"<"Mon"<"Tue"<..: 5 5 5 5 5 6 6 6 6 6 ...
tail(RatCalls_Time, 500)
View(tail(RatCalls_Time, 1000))
Next we need to clean up the text of the `servicenotes` variable - this will be done in multiple steps.  
  
As the first step, we'll remove common "stopwords" (e.g., is, the, and, etc.) as they won't be very useful in finding topics in the `servicenotes` text. Although it's a stopwords, we specifically do not remove the word "no" as it is often used to distinguish between "rats found" and "no rats found".
NoStopWords_Unnest <- 
  RatCalls_Time %>% 
  select(servicerequestid,
         servicenotes
         ) %>% 
  unnest_tokens(word,
                servicenotes
                ) %>% 
  anti_join(filter(stop_words,
                   word != "no" # we don't remove the word "no" as it is often used to distinguish between "rats found" and "no rats found"
                   ),
            by = "word"
            )
Servicenotes_NoStopWrds <- NoStopWords_Unnest %>% 
  nest(word) %>% 
  mutate(servicenotes_nostop = map(data,
                                   unlist
                                   ),
         servicenotes_nostop = map_chr(servicenotes_nostop,
                                       paste,
                                       collapse = " "
                                       )
         ) %>% 
  select(-data)
Remove_StopWrds <- RatCalls_Time %>% 
  left_join(Servicenotes_NoStopWrds,
            by = "servicerequestid"
            )
dim(RatCalls_Time)
[1] 15581    16
dim(Remove_StopWrds)
[1] 15581    17
rm(NoStopWords_Unnest, Servicenotes_NoStopWrds)
head(Remove_StopWrds, 100)
View(head(Remove_StopWrds, 100))
# str(Remove_StopWrds)
# View(Remove_StopWrds %>% 
#        filter(str_detect(servicenotes_nostop,
#                          "washington dc"
#                          )
#               ) %>% 
#        select(servicenotes,
#               servicenotes_nostop
#               )
#      )
Then, we'll remove any numeric characters from 'servicenotes' to avoid distinctions not needed at this level (e.g., "baited 3 rat borrows" vs. "baited 6 rat burrows"). We'll also remove punctuation.
ServiceNotesCleaned <- Remove_StopWrds %>% 
  mutate(servicenotes_nonums_nopunc = str_replace_all(servicenotes_nostop,
                                                      "[[:digit:]]",
                                                      ""
                                                      ) %>% 
           str_replace_all("[[:punct:]]",
                           ""
                           )
         ) %>% 
  select(-servicenotes_nostop)
dim(RatCalls_Time)
[1] 15581    16
dim(Remove_StopWrds)
[1] 15581    17
dim(ServiceNotesCleaned)
[1] 15581    17
# View(select(ServiceNotesCleaned,
#             servicerequestid,
#             servicenotes,
#             servicenotes_nonums_nopunc
#             ) %>% 
#        filter(servicerequestid %in% nomatch$servicerequestid)
#      )
rm(RatCalls_Time, Remove_StopWrds)
head(ServiceNotesCleaned, 100)
View(head(ServiceNotesCleaned, 100))
Now, we can inspect the frequencies of rat-related service requests.
summary(ServiceNotesCleaned)
 servicerequestid   servicepriority    servicecode        servicecodedescription
 Length:15581       Length:15581       Length:15581       Length:15581          
 Class :character   Class :character   Class :character   Class :character      
 Mode  :character   Mode  :character   Mode  :character   Mode  :character      
                                                                                
                                                                                
                                                                                
                                                                                
 servicetypecode    servicetypecodedescription serviceorderdate             
 Length:15581       Length:15581               Min.   :1999-04-27 12:59:00  
 Class :character   Class :character           1st Qu.:2009-07-22 12:00:00  
 Mode  :character   Mode  :character           Median :2011-04-07 15:24:33  
                                               Mean   :2010-01-30 02:23:50  
                                               3rd Qu.:2012-10-05 12:31:54  
                                               Max.   :2016-12-14 10:45:49  
                                                                            
 servicenotes       serviceorder_date    serviceorder_yr serviceorder_yr_posix        
 Length:15581       Min.   :1999-04-27   Min.   :1999    Min.   :1999-01-01 00:00:00  
 Class :character   1st Qu.:2009-07-22   1st Qu.:2009    1st Qu.:2009-01-01 00:00:00  
 Mode  :character   Median :2011-04-07   Median :2011    Median :2011-01-01 00:00:00  
                    Mean   :2010-01-29   Mean   :2010    Mean   :2009-07-25 06:25:01  
                    3rd Qu.:2012-10-05   3rd Qu.:2012    3rd Qu.:2012-01-01 00:00:00  
                    Max.   :2016-12-14   Max.   :2016    Max.   :2016-01-01 00:00:00  
                                                                                      
 serviceorder_mth serviceorder_yrmth serviceorder_yrmth_posix      serviceorder_day
 Aug    :1923     Length:15581       Min.   :1999-04-01 00:00:00   Min.   : 1.00   
 Jun    :1697     Class :character   1st Qu.:2009-07-01 00:00:00   1st Qu.: 8.00   
 Sep    :1666     Mode  :character   Median :2011-04-01 00:00:00   Median :16.00   
 May    :1549                        Mean   :2010-01-14 17:37:56   Mean   :15.71   
 Oct    :1523                        3rd Qu.:2012-10-01 00:00:00   3rd Qu.:23.00   
 Jul    :1507                        Max.   :2016-12-01 00:00:00   Max.   :31.00   
 (Other):5716                                                                      
 serviceorder_wkday servicenotes_nonums_nopunc
 Sun: 489           Length:15581              
 Mon:3140           Class :character          
 Tue:3132           Mode  :character          
 Wed:3025                                     
 Thu:2654                                     
 Fri:2450                                     
 Sat: 691                                     
# summary(RatCalls_Time$serviceorderdate)
# library("psych")
# describe(RatCalls_Time$serviceorderdate)
ggplot_theme_basic <-
  theme(panel.background = element_blank(),
        panel.grid.minor = element_blank(),
        panel.grid.major = element_blank(),
        # axis.text.x = element_blank(),
        axis.ticks = element_blank(),
        axis.line = element_line(size = 1, colour = "black")
        )
# ggplot(data = RatCalls_Time,
#        aes(x = serviceorder_date)
#        ) +
#   geom_histogram() +
#   ggplot_theme_basic
yr_counts <- ServiceNotesCleaned %>% 
  group_by(serviceorder_yr_posix) %>% 
  summarise(Cnt = n()
            ) %>% 
  arrange(serviceorder_yr_posix)
ggplot(data = yr_counts,
       aes(x = serviceorder_yr_posix,
           y = Cnt
           )
       ) +
  geom_col(fill = "light blue") + #, alpha = 0.1) +
  geom_text(aes(label = Cnt),
            nudge_y = 50,
            size = 3
            ) +
  labs(title = "Counts of non-NA ServiceNotes",
       # subtitle = "by year",
       x = "Year",
       y = "Count"
       ) +
  ggplot_theme_basic +
  theme(axis.text.x = element_text(angle = 90)
        ) +
  scale_x_datetime(date_breaks = "1 year")

yrmth_counts <- ServiceNotesCleaned %>% 
  group_by(serviceorder_yrmth_posix) %>% 
  summarise(Cnt = n()
            ) %>% 
  arrange(serviceorder_yrmth_posix)
ggplot(data = yrmth_counts,
       aes(x = serviceorder_yrmth_posix,
           y = Cnt
           )
       ) +
  geom_col(fill = "light blue") + #, alpha = 0.1) +
  # geom_text(aes(label = Cnt),
  #           nudge_y = 50,
  #           size = 3
  #           ) +
  labs(title = "Counts of non-NA ServiceNotes",
       x = "Year-Month",
       y = "Count"
       ) +
  ggplot_theme_basic +
  theme(axis.text.x = element_text(angle = 90)
        ) +
  coord_cartesian(xlim = c(as.POSIXct("1998-12-01"),
                           as.POSIXct("2017-12-01")
                           ),
                  expand = TRUE
                  ) +
  scale_x_datetime(date_breaks = "6 months")

Based on the frequencies of when we actually have 'servicenotes' data, let's try limiting the dataset to service calls from 2010 or later. This reduces the dataset further, from 15,581 rows to 11,037 rows. 
rm(yr_counts, yrmth_counts)
ServiceNotesCleanedAfter2010 <- ServiceNotesCleaned %>%
  filter(serviceorderdate >= as_date("2010-01-01")
         )
nrow(ServiceNotesCleaned)
[1] 15581
nrow(ServiceNotesCleanedAfter2010)
[1] 11037
summary(ServiceNotesCleanedAfter2010)
 servicerequestid   servicepriority    servicecode        servicecodedescription
 Length:11037       Length:11037       Length:11037       Length:11037          
 Class :character   Class :character   Class :character   Class :character      
 Mode  :character   Mode  :character   Mode  :character   Mode  :character      
                                                                                
                                                                                
                                                                                
                                                                                
 servicetypecode    servicetypecodedescription serviceorderdate             
 Length:11037       Length:11037               Min.   :2010-01-02 12:51:20  
 Class :character   Class :character           1st Qu.:2010-12-08 16:46:26  
 Mode  :character   Mode  :character           Median :2012-02-06 19:55:15  
                                               Mean   :2012-05-08 12:20:41  
                                               3rd Qu.:2013-05-07 18:58:17  
                                               Max.   :2016-12-14 10:45:49  
                                                                            
 servicenotes       serviceorder_date    serviceorder_yr serviceorder_yr_posix        
 Length:11037       Min.   :2010-01-02   Min.   :2010    Min.   :2010-01-01 00:00:00  
 Class :character   1st Qu.:2010-12-08   1st Qu.:2010    1st Qu.:2010-01-01 00:00:00  
 Mode  :character   Median :2012-02-06   Median :2012    Median :2012-01-01 00:00:00  
                    Mean   :2012-05-07   Mean   :2012    Mean   :2011-11-01 14:15:37  
                    3rd Qu.:2013-05-07   3rd Qu.:2013    3rd Qu.:2013-01-01 00:00:00  
                    Max.   :2016-12-14   Max.   :2016    Max.   :2016-01-01 00:00:00  
                                                                                      
 serviceorder_mth serviceorder_yrmth serviceorder_yrmth_posix      serviceorder_day
 Aug    :1429     Length:11037       Min.   :2010-01-01 00:00:00   Min.   : 1.00   
 Jun    :1268     Class :character   1st Qu.:2010-12-01 00:00:00   1st Qu.: 8.00   
 Sep    :1260     Mode  :character   Median :2012-02-01 00:00:00   Median :16.00   
 Jul    :1075                        Mean   :2012-04-23 02:01:28   Mean   :15.78   
 Oct    :1056                        3rd Qu.:2013-05-01 00:00:00   3rd Qu.:23.00   
 May    :1030                        Max.   :2016-12-01 00:00:00   Max.   :31.00   
 (Other):3919                                                                      
 serviceorder_wkday servicenotes_nonums_nopunc
 Sun: 427           Length:11037              
 Mon:2211           Class :character          
 Tue:2166           Mode  :character          
 Wed:2085                                     
 Thu:1846                                     
 Fri:1736                                     
 Sat: 566                                     
Then, transform the `servicenotes` field into one row per n-gram. Because we don't know what level of 'n' to use, we'll cycle through the possibilities from n = 1 to n = 5.
ngram_list <- 1:5
Rat_Ngram_list <- list()
Rat_Ngram_list <- lapply(ngram_list,
                         function(i) {
                           # x <- paste0("0", i, "_gram")
                           ServiceNotesCleaned %>% 
                             unnest_tokens(n_gram,
                                           servicenotes_nonums_nopunc,
                                           token = "ngrams",
                                           n = i
                                           )
                           }
                         )
# rm(ngram_list)
Rat_Ngram_list
[[1]]

[[2]]

[[3]]

[[4]]

[[5]]
# str(Rat_Ngram_list[[1]])
Counting the n-grams in each `servicerequestid`.
word_counts_list <- list()
word_counts_list <- lapply(ngram_list,
                           function(i) {
                             Rat_Ngram_list[[i]] %>% 
                               count(servicerequestid,
                                     n_gram,
                                     sort = TRUE
                                     )
                             }
                           )
word_counts_list
[[1]]

[[2]]

[[3]]

[[4]]

[[5]]
NA
Transforming the dataframe into a document term matrix - i.e., documents (`servicerequestid`s) are the rows and n-grams are the columns.
dtm_list <- list()
dtm_list <- lapply(ngram_list,
                   function(i) {
                     word_counts_list[[i]] %>% 
                       cast_dtm(document = servicerequestid,
                                term = n_gram,
                                value = n,
                                # weighting = tm::weightTfIdf,
                                # using term frequency inverse document frequency (TfIdf) weighting is another, possibly more accurate measure, but topicmodels::LDA (used below) only accepts document term matrices with term-frequency weighting
                                weighting = tm::weightTf
                                )
                     }
                   )
dtm_list
[[1]]
<<DocumentTermMatrix (documents: 15580, terms: 3177)>>
Non-/sparse entries: 116407/49381253
Sparsity           : 100%
Maximal term length: 20
Weighting          : term frequency (tf)

[[2]]
<<DocumentTermMatrix (documents: 15268, terms: 12341)>>
Non-/sparse entries: 102353/188320035
Sparsity           : 100%
Maximal term length: 32
Weighting          : term frequency (tf)

[[3]]
<<DocumentTermMatrix (documents: 14543, terms: 18256)>>
Non-/sparse entries: 87424/265409584
Sparsity           : 100%
Maximal term length: 40
Weighting          : term frequency (tf)

[[4]]
<<DocumentTermMatrix (documents: 13157, terms: 20491)>>
Non-/sparse entries: 73020/269527067
Sparsity           : 100%
Maximal term length: 47
Weighting          : term frequency (tf)

[[5]]
<<DocumentTermMatrix (documents: 11846, terms: 21176)>>
Non-/sparse entries: 59946/250790950
Sparsity           : 100%
Maximal term length: 58
Weighting          : term frequency (tf)
To determine the "proper" number of topics, here I try using the `ldatuning::FindTopicsNumber` function. The code chunk is based on the vignette here:  [https://cran.r-project.org/web/packages/ldatuning/vignettes/topics.html](https://cran.r-project.org/web/packages/ldatuning/vignettes/topics.html).  

This analyses was done separately for each n-gram level (n = 1:5), and the overall results were inconclusive - the "proper" number of topics fluctuated between the highest level tried (12 topics) and one of the lowest levels tried (2, 3, or 4 topics).
detectCores(logical = TRUE) - 1
[1] 3
tunes_list <- dtm_list %>% 
  map(~ FindTopicsNumber(.x,
                         topics = c(2:12),
                         metrics = c("Griffiths2004",
                                     "CaoJuan2009",
                                     "Arun2010",
                                     "Deveaud2014"
                                     ),
                         method = "Gibbs",
                         control = list(seed = 123456789),
                         mc.cores = 3L,
                         verbose = TRUE
                         )
      )
fit models... done.
calculate metrics:
  Griffiths2004... done.
  CaoJuan2009... done.
  Arun2010... done.
  Deveaud2014... done.
fit models... done.
calculate metrics:
  Griffiths2004... done.
  CaoJuan2009... done.
  Arun2010... done.
  Deveaud2014... done.
fit models... done.
calculate metrics:
  Griffiths2004... done.
  CaoJuan2009... done.
  Arun2010... done.
  Deveaud2014... done.
fit models... done.
calculate metrics:
  Griffiths2004... done.
  CaoJuan2009... done.
  Arun2010... done.
  Deveaud2014... done.
fit models... done.
calculate metrics:
  Griffiths2004... done.
  CaoJuan2009... done.
  Arun2010... done.
  Deveaud2014... done.
# str(tunes_list[[5]])
topic_plots <-
  tunes_list %>% 
  map(~ FindTopicsNumber_plot(.x)
      )

topic_plots
[[1]]
NULL

[[2]]
NULL

[[3]]
NULL

[[4]]
NULL

[[5]]
NULL
As an alternative, here I try to determine the "proper" number of topics using `topicmodels::perplexity`. Perplexity measures how well a probability model predicts a sample, and I use it here via 10-folder cross validation. For computational purposes, I'm only trying this for the 5-gram model.  

This is based on the method used here:
[http://ellisp.github.io/blog/2017/01/05/topic-model-cv](http://ellisp.github.io/blog/2017/01/05/topic-model-cv)

As with the `ldatuning::FindTopicsNumber` function used previously, `topicmodels::perplexity` is also inconclusive as there is no clear "elbow" in the perplexity plot.
full_data <- dtm_list[[5]]
n <- nrow(full_data)
seed <- 123456789
topic_guess <- 12
folds <- 10
burnin <- 1000
iter <-1000
keep <-50
#----------------10-fold cross-validation, different numbers of topics----------------
cluster <- makeCluster(detectCores(logical = TRUE) - 1
                       ) # leave one CPU spare...
registerDoParallel(cluster)
clusterEvalQ(cluster, {
   library(topicmodels)
})
[[1]]
[1] "topicmodels" "stats"       "graphics"    "grDevices"   "utils"       "datasets"   
[7] "methods"     "base"       

[[2]]
[1] "topicmodels" "stats"       "graphics"    "grDevices"   "utils"       "datasets"   
[7] "methods"     "base"       

[[3]]
[1] "topicmodels" "stats"       "graphics"    "grDevices"   "utils"       "datasets"   
[7] "methods"     "base"       
folds <- folds
splitfolds <- sample(1:folds, n, replace = TRUE)
candidate_k <- c(2:topic_guess) # candidates for how many topics
clusterExport(cluster,
              c("full_data", "burnin", "iter", "keep", "splitfolds", "folds", "candidate_k")
              )
# we parallelize by the different number of topics.  A processor is allocated a value
# of k, and does the cross-validation serially.  This is because it is assumed there
# are more candidate values of k than there are cross-validation folds, hence it
# will be more efficient to parallelise
system.time({
results <- foreach(j = 1:length(candidate_k),
                   .combine = rbind
                   ) %dopar%{
   k <- candidate_k[j]
   
   results_1k <- matrix(0,
                        nrow = folds,
                        ncol = 2
                        )
   
   colnames(results_1k) <- c("k", "perplexity")
   
   for(i in 1:folds){
      train_set <- full_data[splitfolds != i , ]
      valid_set <- full_data[splitfolds == i, ]
      
      fitted <- LDA(train_set,
                    k = k,
                    method = "Gibbs",
                    control = list(seed = seed,
                                   verbose = 1,
                                   burnin = burnin,
                                   iter = iter,
                                   keep = keep
                                   )
                    )
      
      results_1k[i, ] <- c(k, perplexity(fitted, newdata = valid_set)
                           )
   }
   
   return(results_1k)
}
})
    user   system  elapsed 
   1.684    2.252 1080.057 
stopCluster(cluster)
results_df <- as.data.frame(results)
# ggplot(data = results_df,
#        aes(x = k,
#            y = perplexity)
#        ) +
#   geom_point() +
#   geom_smooth(se = FALSE) +
#   coord_cartesian(xlim = c(0, 12)
#                   ) +
#   scale_x_continuous(breaks = seq(0, 12, 2)
#                      ) +
#   ggplot_theme_basic +
#   ggtitle(label = "10-fold cross-validation of topic modelling",
#           subtitle = "(i.e., 10 different models fit for each potential number of topics)"
#           ) +
#   labs(x = "Potential Number of Topics",
#        y = "Perplexity When Fitting the Trained Model to the Hold-Out Set"
#        )
ggplot(data = results_df,
       aes(x = k,
           y = perplexity)
       ) +
  geom_point() +
  geom_smooth(se = TRUE) +
  coord_cartesian(xlim = c(0, 12),
                  ylim = c(0, 6000)
                  ) +
  scale_x_continuous(breaks = seq(0, 12, 2)
                     ) +
  # ggplot_theme_basic +
  ggtitle(label = "10-fold cross-validation of topic modelling",
          subtitle = "(i.e., 10 different models fit for each potential number of topics)"
          ) +
  labs(x = "Potential Number of Topics",
       y = "Perplexity When Fitting the Trained Model to the Hold-Out Set"
       )

Remove the no-longer-needed files.
rm(cluster, folds, full_data, results, topic_plots, tunes_list, burnin, candidate_k, folds, iter, k, keep, n, seed, splitfolds, topic_guess)
object 'folds' not foundobject 'k' not found
Here I use Latent Dirichlet allocation for topic modeling. As determining the "proper" number of topics was inconclusive, I'm cycling through every combination of ngrams (1:5) and topics (2:12).

topic_guess <- 2:12

lda_list <- list()


cluster <- makeCluster(detectCores(logical = TRUE) - 1
                       ) # leave one CPU spare...
registerDoParallel(cluster)

for(i in ngram_list) {
  for(j in topic_guess) {
    x <- LDA(dtm_list[[i]],
             k = j,
             control = list(seed = 123456789,
                            verbose = 1
                            )
             )
    
    ifelse((i == min(ngram_list) &
              j == min(topic_guess)
            ),
           countx <- 1,
           countx <- countx + 1
           )
    
    lda_list[[countx]] <- list(ngram = i,
                               topic = j,
                               lda_model = x
                               )
    }
  }

stopCluster(cluster)

rm(ngram_list, topic_guess, i, j, x, countx, cluster)

lda_list
Creating a dataframe with `beta` - the per-topic-per-ngram probability (i.e., the probability that each ngram is in each topic).
PerTopicPerNgram <- list()
for(i in 1:length(lda_list)
    ) {
  x <- tidy(lda_list[[i]]$lda_model,
            matrix = "beta"
            ) %>% 
    arrange(term,
            desc(beta)
            )
  
  PerTopicPerNgram[[i]] <- list(ngram = lda_list[[i]]$ngram,
                                topic = lda_list[[i]]$topic,
                                PerTopicPerNgram = x
                                )
  }
rm(i, x)
str(PerTopicPerNgram[[55]]$PerTopicPerNgram)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   254112 obs. of  3 variables:
 $ topic: int  5 6 8 2 9 3 1 4 11 7 ...
 $ term : chr  "a covnes parker bated rat" "a covnes parker bated rat" "a covnes parker bated rat" "a covnes parker bated rat" ...
 $ beta : num  2.33e-04 3.29e-13 9.58e-153 8.26e-153 5.04e-153 ...
# rm(serv_req_id_lda)
head(PerTopicPerNgram[[55]]$PerTopicPerNgram, 500)
Creating a dataframe with just the top ten terms (ranked by beta) in each topic.
top_terms <- list()
for(i in 1:length(PerTopicPerNgram)
    ) {
  x <- PerTopicPerNgram[[i]]$PerTopicPerNgram %>% 
    group_by(topic) %>% 
    top_n(10,
          beta
          ) %>% 
    ungroup() %>% 
    arrange(topic,
            -beta
            )
  
  top_terms[[i]] <- list(ngram = PerTopicPerNgram[[i]]$ngram,
                         topic = PerTopicPerNgram[[i]]$topic,
                         top_terms = x
                         )
  }
rm(i, x)
top_terms[[55]]$top_terms
View(top_terms[[55]]$top_terms)
Now we can plot the top 10 n-grams in each topic to visually inspect if the topic classifications "make sense" based on the n-gram text.  

Here, we're just creating and saving the plots themselves.
TopNgrams_ByTopic_BarGraphs <-
  top_terms %>%
  # to_graph %>%
  map(function(x) 
    x$top_terms %>%
      mutate(term = reorder(term,
                            beta
                            ),
             topic = paste0("Topic ",
                            str_pad(as.character(topic),
                                    width = 2,
                                    side = "left",
                                    pad = "0"
                                    )
                            )
             ) %>% 
      ggplot(aes(x = term,
                 y = beta,
                 fill = factor(topic)
                 )
             ) +
      geom_col(show.legend = FALSE) +
      facet_wrap(~ topic,
                 scales = "free",
                 ncol = 2
                 ) +
      ggplot_theme_basic +
      theme(plot.title = element_text(size = 11),
            axis.title = element_text(size = 10),
            axis.text = element_text(size = 9)
            ) +
      labs(title = "Most Common Terms Per Topic",
           subtitle = paste0("(",
                             str_pad(x$ngram,
                                     width = 2,
                                     side = "left",
                                     pad = "0"
                                     ),
                             "gram",
                             str_pad(x$topic,
                                     width = 2,
                                     side = "left",
                                     pad = "0"
                             ),
                             "topic)"
                             ),
           x = paste0(str_pad(x$ngram,
                              width = 2,
                              side = "left",
                              pad = "0"
                              ),
                      "gram"
                      ),
           y = paste0("probability of the ",
                      str_pad(x$ngram,
                              width = 2,
                              side = "left",
                              pad = "0"
                              ),
                      "gram in the topic"
                      )
           ) +
      coord_flip()
    )
# TopNgrams_ByTopic_BarGraphs
TopNgrams_ByTopic_BarGraphs[[24]] # ngram = 3 & topics = 3

TopNgrams_ByTopic_BarGraphs[[25]] # ngram = 3 & topics = 4

TopNgrams_ByTopic_BarGraphs[[35]] # ngram = 4 & topics = 3

TopNgrams_ByTopic_BarGraphs[[36]] # ngram = 4 & topics = 4

TopNgrams_ByTopic_BarGraphs[[46]] # ngram = 5 & topics = 3

TopNgrams_ByTopic_BarGraphs[[47]] # ngram = 5 & topics = 4
TopNgrams_ByTopic_BarGraphs %>% 
  map(function(x)
    ggsave(paste0(wd,
                  "/Viz/",
                  # "New_",
                  substr(x$labels$subtitle,
                         2,
                         (nchar(x$labels$subtitle) - 1)
                  ),
                  "_Top10Terms_facet.png"
                  ),
           x,
           scale = 4,
           width = 6,
           height = 6,
           units = "cm"
           )
    )
[[1]]
NULL

[[2]]
NULL

[[3]]
NULL

[[4]]
NULL

[[5]]
NULL

[[6]]
NULL

[[7]]
NULL

[[8]]
NULL

[[9]]
NULL

[[10]]
NULL

[[11]]
NULL

[[12]]
NULL

[[13]]
NULL

[[14]]
NULL

[[15]]
NULL

[[16]]
NULL

[[17]]
NULL

[[18]]
NULL

[[19]]
NULL

[[20]]
NULL

[[21]]
NULL

[[22]]
NULL

[[23]]
NULL

[[24]]
NULL

[[25]]
NULL

[[26]]
NULL

[[27]]
NULL

[[28]]
NULL

[[29]]
NULL

[[30]]
NULL

[[31]]
NULL

[[32]]
NULL

[[33]]
NULL

[[34]]
NULL

[[35]]
NULL

[[36]]
NULL

[[37]]
NULL

[[38]]
NULL

[[39]]
NULL

[[40]]
NULL

[[41]]
NULL

[[42]]
NULL

[[43]]
NULL

[[44]]
NULL

[[45]]
NULL

[[46]]
NULL

[[47]]
NULL

[[48]]
NULL

[[49]]
NULL

[[50]]
NULL

[[51]]
NULL

[[52]]
NULL

[[53]]
NULL

[[54]]
NULL

[[55]]
NULL

Creating a dataframe with `gamma` - the per-document-per-topic probability (i.e., the probability that each document (serv_req_id) is in each topic).  

I chose to do this for six different combinations of ngrams and topics (ngram = 3 & topic = 3, 3 & 4, 4 & 3, 4 & 4, 5 & 3, 5 & 4). This was chosen in part becasue after looking at the graphs produced above, these models seemed (by visual inspection) to perform better. It was also done in part becasue a portion of the analyses below requries defining the topics as `unknown`, `no_rats_found`, or `rats_found` by visual inspection of the graphs produced above. Six also seemed to be a good medium between too few and too many visual inspections to do.
rm(TopNgrams_ByTopic_BarGraphs)
top_terms[[55]] #lda model with ngram = 5 & topics = 12
$ngram
[1] 5

$topic
[1] 12

$top_terms
top_terms[[2]] #lda model with ngram = 1 & topics = 3
$ngram
[1] 1

$topic
[1] 3

$top_terms
top_terms[[3]] #lda model with ngram = 1 & topics = 4
$ngram
[1] 1

$topic
[1] 4

$top_terms
top_terms[[24]] #lda model with ngram = 3 & topics = 3
$ngram
[1] 3

$topic
[1] 3

$top_terms
top_terms[[25]] #lda model with ngram = 3 & topics = 4
$ngram
[1] 3

$topic
[1] 4

$top_terms
top_terms[[35]] #lda model with ngram = 4 & topics = 3
$ngram
[1] 4

$topic
[1] 3

$top_terms
top_terms[[36]] #lda model with ngram = 4 & topics = 4
$ngram
[1] 4

$topic
[1] 4

$top_terms
top_terms[[46]] #lda model with ngram = 5 & topics = 3
$ngram
[1] 5

$topic
[1] 3

$top_terms
top_terms[[47]] #lda model with ngram = 5 & topics = 4
$ngram
[1] 5

$topic
[1] 4

$top_terms
ProbDocInTopic_ngram03_topic03 <-
  list(ngram = lda_list[[24]]$ngram,
       topic = lda_list[[24]]$topic,
       data = tidy(lda_list[[24]]$lda_model,
                   matrix = "gamma"
                   ) %>% 
         arrange(document,
                 desc(gamma)
                 ) %>% 
         mutate(topic_name = case_when(topic %in% c(1) ~ "unknown",
                                       topic %in% c(2) ~ "no_rats_found",
                                       topic %in% c(3) ~ "rats_found"
                                       ),
                model_ngram = lda_list[[24]]$ngram,
                model_topic = lda_list[[24]]$topic
                )
       )
ProbDocInTopic_ngram03_topic04 <-
  list(ngram = lda_list[[25]]$ngram,
       topic = lda_list[[25]]$topic,
       data = tidy(lda_list[[25]]$lda_model,
                   matrix = "gamma"
                   ) %>% 
         arrange(document,
                 desc(gamma)
                 ) %>% 
         mutate(topic_name = case_when(topic %in% c(1) ~ "unknown",
                                       topic %in% c(2) ~ "no_rats_found",
                                       topic %in% c(3, 4) ~ "rats_found"
                                       ),
                model_ngram = lda_list[[25]]$ngram,
                model_topic = lda_list[[25]]$topic
                )
       )
ProbDocInTopic_ngram04_topic03 <-
  list(ngram = lda_list[[35]]$ngram,
       topic = lda_list[[35]]$topic,
       data = tidy(lda_list[[35]]$lda_model,
                   matrix = "gamma"
                   ) %>% 
         arrange(document,
                 desc(gamma)
                 ) %>% 
         mutate(topic_name = case_when(#topic %in% c() ~ "unknown",
                                       topic %in% c(1) ~ "no_rats_found",
                                       topic %in% c(2, 3) ~ "rats_found"
                                       ),
                model_ngram = lda_list[[35]]$ngram,
                model_topic = lda_list[[35]]$topic
                )
       )
ProbDocInTopic_ngram04_topic04 <-
  list(ngram = lda_list[[36]]$ngram,
       topic = lda_list[[36]]$topic,
       data = tidy(lda_list[[36]]$lda_model,
                   matrix = "gamma"
                   ) %>% 
         arrange(document,
                 desc(gamma)
                 ) %>% 
         mutate(topic_name = case_when(topic %in% c(4) ~ "unknown",
                                       topic %in% c(1) ~ "no_rats_found",
                                       topic %in% c(2, 3) ~ "rats_found"
                                       ),
                model_ngram = lda_list[[36]]$ngram,
                model_topic = lda_list[[36]]$topic
                )
       )
ProbDocInTopic_ngram05_topic03 <-
  list(ngram = lda_list[[46]]$ngram,
       topic = lda_list[[46]]$topic,
       data = tidy(lda_list[[46]]$lda_model,
                   matrix = "gamma"
                   ) %>% 
         arrange(document,
                 desc(gamma)
                 ) %>% 
         mutate(topic_name = case_when(topic %in% c(2) ~ "unknown",
                                       topic %in% c(3) ~ "no_rats_found",
                                       topic %in% c(1) ~ "rats_found"
                                       ),
                model_ngram = lda_list[[46]]$ngram,
                model_topic = lda_list[[46]]$topic
                )
       )
ProbDocInTopic_ngram05_topic04 <-
  list(ngram = lda_list[[47]]$ngram,
       topic = lda_list[[47]]$topic,
       data = tidy(lda_list[[47]]$lda_model,
                   matrix = "gamma"
                   ) %>% 
         arrange(document,
                 desc(gamma)
                 ) %>% 
         mutate(topic_name = case_when(topic %in% c(4) ~ "unknown",
                                       topic %in% c(3) ~ "no_rats_found",
                                       topic %in% c(1, 2) ~ "rats_found"
                                       ),
                model_ngram = lda_list[[47]]$ngram,
                model_topic = lda_list[[47]]$topic
                )
       )
Here, we put the six individual ProbDocInTopic models together, and add in some of the original information (e.g., the original `servicenotes`) for context.
ProbDocInTopic_AllModels <-
  bind_rows(ProbDocInTopic_ngram03_topic03[[3]],
            ProbDocInTopic_ngram03_topic04[[3]],
            ProbDocInTopic_ngram04_topic03[[3]],
            ProbDocInTopic_ngram04_topic04[[3]],
            ProbDocInTopic_ngram05_topic03[[3]],
            ProbDocInTopic_ngram05_topic04[[3]]
            ) %>% 
  arrange(document,
          model_ngram,
          model_topic,
          gamma
          ) %>% 
  rename("servicerequestid" = "document") %>% 
  left_join(select(ServiceNotesCleaned,
                   servicerequestid,
                   servicenotes,
                   serviceorderdate
                   ),
            by = c("servicerequestid" = "servicerequestid")
            )
rm(list = ls(pattern = "ProbDocInTopic_ngram"))
str(ProbDocInTopic_AllModels)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   276822 obs. of  8 variables:
 $ servicerequestid: chr  "09-00001211" "09-00001211" "09-00001211" "09-00001211" ...
 $ topic           : int  2 3 1 2 3 4 1 2 3 1 ...
 $ gamma           : num  0.0142 0.0142 0.9717 0.018 0.018 ...
 $ topic_name      : chr  "no_rats_found" "rats_found" "unknown" "no_rats_found" ...
 $ model_ngram     : int  3 3 3 3 3 3 3 4 4 4 ...
 $ model_topic     : int  3 3 3 4 4 4 4 3 3 3 ...
 $ servicenotes    : chr  "CUSTOMER WAS CALLED BY VECTOR CONTROL. CONTROL NO.: 1382" "CUSTOMER WAS CALLED BY VECTOR CONTROL. CONTROL NO.: 1382" "CUSTOMER WAS CALLED BY VECTOR CONTROL. CONTROL NO.: 1382" "CUSTOMER WAS CALLED BY VECTOR CONTROL. CONTROL NO.: 1382" ...
 $ serviceorderdate: POSIXct, format: "1999-04-27 12:59:00" "1999-04-27 12:59:00" ...
head(ProbDocInTopic_AllModels, 100)
View(head(ProbDocInTopic_AllModels, 1000))
Remove the datafiles that are no longer needed.
rm(list = ls(pattern = "_list"))
rm(PerTopicPerNgram, top_terms)
Next, for each model (e.g., 3-gram 4-topic), we sum the probabilities given for each numeric topic, into the "rats topics" (e.g., `rats_found`) which were defined above via visual inspection of the graphs on the Top 10 ngrams in each numeric topic.  

I also pull out the 5-gram 4-topic model because it appeared (visually) to be the most accurate individual model.
ProbDocInTopic_ProbsSummed_ByModel <-
  ProbDocInTopic_AllModels %>% 
  group_by(servicerequestid,
           model_ngram,
           model_topic,
           topic_name
           ) %>% 
  summarise(prob_ = sum(gamma)
            ) %>% 
  ungroup() %>% 
  left_join(select(ServiceNotesCleaned,
                   servicerequestid,
                   servicenotes,
                   serviceorderdate
                   ),
            by = c("servicerequestid" = "servicerequestid")
            )
str(ProbDocInTopic_ProbsSummed_ByModel)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   224119 obs. of  7 variables:
 $ servicerequestid: chr  "09-00001211" "09-00001211" "09-00001211" "09-00001211" ...
 $ model_ngram     : int  3 3 3 3 3 3 4 4 4 4 ...
 $ model_topic     : int  3 3 3 4 4 4 3 3 4 4 ...
 $ topic_name      : chr  "no_rats_found" "rats_found" "unknown" "no_rats_found" ...
 $ prob_           : num  0.0142 0.0142 0.9717 0.018 0.0359 ...
 $ servicenotes    : chr  "CUSTOMER WAS CALLED BY VECTOR CONTROL. CONTROL NO.: 1382" "CUSTOMER WAS CALLED BY VECTOR CONTROL. CONTROL NO.: 1382" "CUSTOMER WAS CALLED BY VECTOR CONTROL. CONTROL NO.: 1382" "CUSTOMER WAS CALLED BY VECTOR CONTROL. CONTROL NO.: 1382" ...
 $ serviceorderdate: POSIXct, format: "1999-04-27 12:59:00" "1999-04-27 12:59:00" ...
head(ProbDocInTopic_ProbsSummed_ByModel, 100)
View(head(ProbDocInTopic_ProbsSummed_ByModel, 1000))
ProbDocInTopic_ProbsSummed_05gram04topic <-
  ProbDocInTopic_ProbsSummed_ByModel %>% 
  filter(model_ngram == 5 &
           model_topic == 4)
str(ProbDocInTopic_ProbsSummed_05gram04topic)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   35538 obs. of  7 variables:
 $ servicerequestid: chr  "09-00001211" "09-00001211" "09-00001211" "09-00001410" ...
 $ model_ngram     : int  5 5 5 5 5 5 5 5 5 5 ...
 $ model_topic     : int  4 4 4 4 4 4 4 4 4 4 ...
 $ topic_name      : chr  "no_rats_found" "rats_found" "unknown" "no_rats_found" ...
 $ prob_           : num  0.0208 0.0415 0.9377 0.0063 0.0126 ...
 $ servicenotes    : chr  "CUSTOMER WAS CALLED BY VECTOR CONTROL. CONTROL NO.: 1382" "CUSTOMER WAS CALLED BY VECTOR CONTROL. CONTROL NO.: 1382" "CUSTOMER WAS CALLED BY VECTOR CONTROL. CONTROL NO.: 1382" "the rat are coming from an apartment building adjacent to the        alley.  there is alot of trash pilled up behind the apartm"| __truncated__ ...
 $ serviceorderdate: POSIXct, format: "1999-04-27 12:59:00" "1999-04-27 12:59:00" ...
head(ProbDocInTopic_ProbsSummed_05gram04topic, 100)
View(head(ProbDocInTopic_ProbsSummed_05gram04topic, 1000))
Next, for each ngram-topic combination, I create histograms of the probabilities assigned to each topic.  This is done to help visualy determine if the topic assignments are clearly separating documents (serv_request_id values).  

A log10 transformation of the probability is done to help more clearly see any differences.
# str(ProbDocInTopic_ProbsSummed_ByModel)
ProbDocInTopic_ProbsSummed_ByModel_Details <-
  ProbDocInTopic_ProbsSummed_ByModel %>% 
  mutate(model = paste0("0",
                        model_ngram,
                        "gram_",
                        "0",
                        model_topic,
                        "topic"
                        ),
         serviceorder_yr = year(serviceorderdate),
         model_and_yr = paste0(model,
                               "_",
                               as.character(serviceorder_yr)
                               )
         )
head(ProbDocInTopic_ProbsSummed_ByModel_Details, 100)
View(head(ProbDocInTopic_ProbsSummed_ByModel_Details, 1000))
TopicDistro_MainModels_Histogram_ByModel <-
  ProbDocInTopic_ProbsSummed_ByModel_Details %>% 
  split(.$model) %>%
  map(~ ggplot(data = .x,
               aes(x = prob_,
                   fill = topic_name
                   )
               ) +
        geom_histogram(binwidth = 0.05) +
        scale_x_continuous(limits = c(0, 1)
                           ) +
        scale_y_log10() + # this transformation is used to help more clearly see any differences in the probability values
        ggtitle(label = paste0(.x$model,
                               "_Histogram"
                               )
                ) +
        theme(legend.position = "none") +
        labs(x = "Prob of ServiceRequestId in the Topic",
             y = "log10 of counts"
             ) +
        facet_wrap(~topic_name)
      )
TopicDistro_MainModels_Histogram_ByModelYr <-
  ProbDocInTopic_ProbsSummed_ByModel_Details %>% 
  split(.$model_and_yr) %>% 
  map(~ ggplot(data = .x,
               aes(x = prob_,
                   fill = topic_name
                   )
               ) +
        geom_histogram(binwidth = 0.05) +
        scale_x_continuous(limits = c(0, 1)
                           ) +
        scale_y_log10() + # this transformation is used to help more clearly see any differences in the probability values
        ggtitle(label = paste0(.x$model_and_yr,
                               "_Histogram"
                               )
                ) +
        theme(legend.position = "none") +
        labs(x = "Prob of ServiceRequestId in the Topic",
             y = "log10 of counts"
             ) +
        facet_wrap(~topic_name)
      )
Saving the histograms.
Removing unneeded files.
rm(list = ls(pattern = "TopicDistro_"))
Comparing how the histograms look with a regular y-scale vs a log10 y-scale.
ProbDocInTopic_ProbsSummed_ByModel_Details %>% 
  split(.$model) %>%
  map(~ ggplot(data = .x,
               aes(x = prob_,
                   fill = topic_name
                   )
               ) +
        geom_histogram(binwidth = 0.05) +
        scale_x_continuous(limits = c(0, 1)
                           ) +
        scale_y_log10() +
        ggtitle(label = paste0(.x$model,
                               "_Histogram"
                               )
                ) +
        theme(legend.position = "none") +
        labs(x = "Prob of ServiceRequestId in the Topic",
             y = "log10 of counts"
             ) +
        facet_wrap(~topic_name,
                   scales = "fixed"
                   )
      )
$`03gram_03topic`

$`03gram_04topic`

$`04gram_03topic`

$`04gram_04topic`

$`05gram_03topic`

$`05gram_04topic`

ProbDocInTopic_ProbsSummed_ByModel_Details %>% 
  split(.$model) %>%
  map(~ ggplot(data = .x,
               aes(x = prob_,
                   fill = topic_name
                   )
               ) +
        geom_histogram(binwidth = 0.05) +
        scale_x_continuous(limits = c(0, 1)
                           ) +
        # scale_y_log10() +
        ggtitle(label = paste0(.x$model,
                               "_Histogram"
                               )
                ) +
        theme(legend.position = "none") +
        labs(x = "Prob of ServiceRequestId in the Topic",
             y = "counts"
             ) +
        coord_cartesian(ylim = c(0, 5000)
                        ) +
        facet_wrap(~topic_name,
                   scales = "fixed"
                   )
      )
$`03gram_03topic`

$`03gram_04topic`

$`04gram_03topic`

$`04gram_04topic`

$`05gram_03topic`

$`05gram_04topic`

Then, for each `servicerequestid` and `topic_name` we calculate the mean topic probability across all the models. NOTE: This step could be modified more as my instinct is that more weight should probably be given to the models with larger ngrams and topics (e.g., the 5-gram & 4-topic model). However, using larger values of n-grams will not be able to analyze those records that do not have at least `n` words. Meaning that smaller values of n-grams can analyze more documents, but possibly less accurately.
ProbDocInTopic_MeanProb_BySrvcRqstId <-
  ProbDocInTopic_ProbsSummed_ByModel %>% 
  group_by(servicerequestid,
           topic_name
           ) %>% 
  summarise(MeanProb = mean(prob_, na.rm = TRUE)
            ) %>% 
  left_join(select(ServiceNotesCleaned,
                   servicerequestid,
                   servicenotes,
                   serviceorderdate
                   ),
            by = c("servicerequestid" = "servicerequestid")
            ) %>% 
  arrange(servicerequestid,
          desc(MeanProb)
          )
str(ProbDocInTopic_MeanProb_BySrvcRqstId)
Classes ‘grouped_df’, ‘tbl_df’, ‘tbl’ and 'data.frame': 43629 obs. of  5 variables:
 $ servicerequestid: chr  "09-00001211" "09-00001211" "09-00001211" "09-00001323" ...
 $ topic_name      : chr  "unknown" "no_rats_found" "rats_found" "unknown" ...
 $ MeanProb        : num  0.581 0.333 0.183 0.479 0.466 ...
 $ servicenotes    : chr  "CUSTOMER WAS CALLED BY VECTOR CONTROL. CONTROL NO.: 1382" "CUSTOMER WAS CALLED BY VECTOR CONTROL. CONTROL NO.: 1382" "CUSTOMER WAS CALLED BY VECTOR CONTROL. CONTROL NO.: 1382" "rats in the alley behind house" ...
 $ serviceorderdate: POSIXct, format: "1999-04-27 12:59:00" "1999-04-27 12:59:00" ...
 - attr(*, "vars")= chr "servicerequestid"
 - attr(*, "indices")=List of 14543
  ..$ : int  0 1 2
  ..$ : int  3 4 5
  ..$ : int  6 7 8
  ..$ : int  9 10 11
  ..$ : int  12 13 14
  ..$ : int  15 16 17
  ..$ : int  18 19 20
  ..$ : int  21 22 23
  ..$ : int  24 25 26
  ..$ : int  27 28 29
  ..$ : int  30 31 32
  ..$ : int  33 34 35
  ..$ : int  36 37 38
  ..$ : int  39 40 41
  ..$ : int  42 43 44
  ..$ : int  45 46 47
  ..$ : int  48 49 50
  ..$ : int  51 52 53
  ..$ : int  54 55 56
  ..$ : int  57 58 59
  ..$ : int  60 61 62
  ..$ : int  63 64 65
  ..$ : int  66 67 68
  ..$ : int  69 70 71
  ..$ : int  72 73 74
  ..$ : int  75 76 77
  ..$ : int  78 79 80
  ..$ : int  81 82 83
  ..$ : int  84 85 86
  ..$ : int  87 88 89
  ..$ : int  90 91 92
  ..$ : int  93 94 95
  ..$ : int  96 97 98
  ..$ : int  99 100 101
  ..$ : int  102 103 104
  ..$ : int  105 106 107
  ..$ : int  108 109 110
  ..$ : int  111 112 113
  ..$ : int  114 115 116
  ..$ : int  117 118 119
  ..$ : int  120 121 122
  ..$ : int  123 124 125
  ..$ : int  126 127 128
  ..$ : int  129 130 131
  ..$ : int  132 133 134
  ..$ : int  135 136 137
  ..$ : int  138 139 140
  ..$ : int  141 142 143
  ..$ : int  144 145 146
  ..$ : int  147 148 149
  ..$ : int  150 151 152
  ..$ : int  153 154 155
  ..$ : int  156 157 158
  ..$ : int  159 160 161
  ..$ : int  162 163 164
  ..$ : int  165 166 167
  ..$ : int  168 169 170
  ..$ : int  171 172 173
  ..$ : int  174 175 176
  ..$ : int  177 178 179
  ..$ : int  180 181 182
  ..$ : int  183 184 185
  ..$ : int  186 187 188
  ..$ : int  189 190 191
  ..$ : int  192 193 194
  ..$ : int  195 196 197
  ..$ : int  198 199 200
  ..$ : int  201 202 203
  ..$ : int  204 205 206
  ..$ : int  207 208 209
  ..$ : int  210 211 212
  ..$ : int  213 214 215
  ..$ : int  216 217 218
  ..$ : int  219 220 221
  ..$ : int  222 223 224
  ..$ : int  225 226 227
  ..$ : int  228 229 230
  ..$ : int  231 232 233
  ..$ : int  234 235 236
  ..$ : int  237 238 239
  ..$ : int  240 241 242
  ..$ : int  243 244 245
  ..$ : int  246 247 248
  ..$ : int  249 250 251
  ..$ : int  252 253 254
  ..$ : int  255 256 257
  ..$ : int  258 259 260
  ..$ : int  261 262 263
  ..$ : int  264 265 266
  ..$ : int  267 268 269
  ..$ : int  270 271 272
  ..$ : int  273 274 275
  ..$ : int  276 277 278
  ..$ : int  279 280 281
  ..$ : int  282 283 284
  ..$ : int  285 286 287
  ..$ : int  288 289 290
  ..$ : int  291 292 293
  ..$ : int  294 295 296
  .. [list output truncated]
 - attr(*, "group_sizes")= int  3 3 3 3 3 3 3 3 3 3 ...
 - attr(*, "biggest_group_size")= int 3
 - attr(*, "labels")='data.frame':  14543 obs. of  1 variable:
  ..$ servicerequestid: chr  "09-00001211" "09-00001323" "09-00001410" "09-00001865" ...
  ..- attr(*, "vars")= chr "servicerequestid"
tail(ProbDocInTopic_MeanProb_BySrvcRqstId, 100)
View(head(ProbDocInTopic_MeanProb_BySrvcRqstId, 1000))
View(tail(ProbDocInTopic_MeanProb_BySrvcRqstId, 1000))
Here, we create a dataset suitable for graphing and analyzing, by simply selecting the highest topic probability (the `MeanProb` value) assigned to each topic. We also do this for the single 5-gram 4-topic model.
# ProbDocInTopic_AllModels
# ProbDocInTopic_ProbsSummed_ByModel
# ProbDocInTopic_MeanProb_BySrvcRqstId
# 
# 
# ProbDocInTopic_AllModels %>% select(servicerequestid) %>% distinct() %>% nrow
# ProbDocInTopic_ProbsSummed_ByModel %>% select(servicerequestid) %>% distinct() %>% nrow
# ProbDocInTopic_MeanProb_BySrvcRqstId %>% select(servicerequestid) %>% distinct() %>% nrow
TopProb_BySrvcRqstId <-
  ProbDocInTopic_MeanProb_BySrvcRqstId %>% 
  mutate(serviceorder_yr = year(serviceorderdate),
         # serviceorder_yr2 = as.factor(serviceorder_yr),
         yr_group = paste0(as.character(serviceorder_yr),
                           "_",
                           as.character(topic_name)
                           ),
         model = "AllModels_MeanProb"
         ) %>% 
  rename("prob" = "MeanProb") %>% 
  group_by(servicerequestid) %>% 
  top_n(1,
        prob
        ) %>% 
  ungroup() %>% 
  arrange(prob)
str(TopProb_BySrvcRqstId)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   14543 obs. of  8 variables:
 $ servicerequestid: chr  "09-00119541" "09-00029707" "09-00053342" "09-00040528" ...
 $ topic_name      : chr  "no_rats_found" "unknown" "rats_found" "rats_found" ...
 $ prob            : num  0.36 0.365 0.365 0.366 0.366 ...
 $ servicenotes    : chr  "3-8-01  baited 2 burrows   front yard    3-30-01   baited 2 rat burrows  RH/GB    4-6-01    baited 1 burrow in front gb rh" "3350 C ST SE, NO RAT BURROWS WERE FOUND ON THE PROPERTY. AT 3346 & 3348 C ST SE BAITED REAR GARAGE. RICKS/DD" "THE VECTOR CONTROL BRANCH BAITED 2 BURROWS, RIGHT FRONT STEPS. FOLLOW UP BAITING WILL TAKE PLACE SOON; RICKS/PRICE;  DD" "THE VECTOR CONTROL BRANCH BAITED THE REAR YARD; 10 RAT BURROWS WERE FOUND. FOLLOW-UP BAITING WILL TAKE PLACE SHORTLY.HERRINGTON"| __truncated__ ...
 $ serviceorderdate: POSIXct, format: "2001-02-22 12:25:00" "1999-10-28 16:11:00" ...
 $ serviceorder_yr : num  2001 1999 2000 2000 2000 ...
 $ yr_group        : chr  "2001_no_rats_found" "1999_unknown" "2000_rats_found" "2000_rats_found" ...
 $ model           : chr  "AllModels_MeanProb" "AllModels_MeanProb" "AllModels_MeanProb" "AllModels_MeanProb" ...
TopProb_BySrvcRqstId
View(TopProb_BySrvcRqstId)
TopProb_BySrvcRqstId_05gram04topic <- 
  ProbDocInTopic_ProbsSummed_05gram04topic %>% 
  mutate(serviceorder_yr = year(serviceorderdate),
         # serviceorder_yr2 = as.factor(serviceorder_yr),
         yr_group = paste0(as.character(serviceorder_yr),
                           "_",
                           as.character(topic_name)
                           ),
         model = "5gram4topic"
         ) %>% 
  rename("prob" = "prob_") %>% 
  group_by(servicerequestid) %>% 
  top_n(1,
        prob
        ) %>% 
  ungroup() %>% 
  arrange(prob)
str(TopProb_BySrvcRqstId_05gram04topic)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   11846 obs. of  10 variables:
 $ servicerequestid: chr  "10-00147588" "11-00106490" "10-00165933" "11-00138104" ...
 $ model_ngram     : int  5 5 5 5 5 5 5 5 5 5 ...
 $ model_topic     : int  4 4 4 4 4 4 4 4 4 4 ...
 $ topic_name      : chr  "rats_found" "unknown" "unknown" "unknown" ...
 $ prob            : num  0.343 0.383 0.391 0.414 0.442 ...
 $ servicenotes    : chr  "On 5/24/10 G Cornes baited 10 rat burrows in front of bldg around sidewalk." "On 4/27/11@9:11am L rogers baited 4 rat burrows in the rear yd by shed. Eaton Bait Block/Peanut Butter, EPA# 56-42, .005% 4oz, "| __truncated__ "On 5/18/10 10:25AM G Curtis baited 2 rat burrows in the alley and tree box" "On 5/18/11@10:45am L Rogers baited 1 rat burrow at tree box. Eaton Bait Block/Peanut Butter, EPA# 56-42, .005% 1oz, Gloves & Sh"| __truncated__ ...
 $ serviceorderdate: POSIXct, format: "2010-04-29 14:45:58" "2011-04-15 03:57:15" ...
 $ serviceorder_yr : num  2010 2011 2010 2011 2010 ...
 $ yr_group        : chr  "2010_rats_found" "2011_unknown" "2010_unknown" "2011_unknown" ...
 $ model           : chr  "5gram4topic" "5gram4topic" "5gram4topic" "5gram4topic" ...
TopProb_BySrvcRqstId_05gram04topic
View(TopProb_BySrvcRqstId_05gram04topic)
  
Now, we can simply create the freqpoly plots and density plots for each year-topic combination to investigate how the topic assignment did over time, and then save them.
# str(TopProb_BySrvcRqstId)
# str(TopProb_BySrvcRqstId_05gram04topic)
TopicDistro_AllModelsMean_Freqpoly <-
  TopProb_BySrvcRqstId %>%
  split(.$serviceorder_yr) %>%
  map(~ ggplot(data = .x,
               aes(x = prob,
                   colour = topic_name
                   )
               ) +
        geom_freqpoly(binwidth = 0.05,
                      alpha = 0.6
                      ) +
        scale_x_continuous(limits = c(0, 1)
                           ) +
        ggtitle(label = paste0("TopicDistro_",
                               .x$model,
                               "_Freqpoly_",
                               as.character(.x$serviceorder_yr)
                               )
                ) +
        labs(x = "Prob of ServiceRequestId in the Topic")
      )
TopicDistro_AllModelsMean_Density <-
  TopProb_BySrvcRqstId %>%
  split(.$serviceorder_yr) %>%
  map(~ ggplot(data = .x,
               aes(x = prob,
                   colour = topic_name
                   )
               ) +
        geom_density() +
        scale_x_continuous(limits = c(0, 1)
                           ) +
        ggtitle(label = paste0("TopicDistro_",
                               .x$model,
                               "_Density_",
                               as.character(.x$serviceorder_yr)
                               )
                ) +
        labs(x = "Prob of ServiceRequestId in the Topic")
      )
TopicDistro_05gram04topic_Freqpoly <-
  TopProb_BySrvcRqstId_05gram04topic %>%
  split(.$serviceorder_yr) %>%
  map(~ ggplot(data = .x,
               aes(x = prob,
                   colour = topic_name
                   )
               ) +
        geom_freqpoly(binwidth = 0.05,
                      alpha = 0.6
                      ) +
        scale_x_continuous(limits = c(0, 1)
                           ) +
        ggtitle(label = paste0("TopicDistro_",
                               .x$model,
                               "_Freqpoly_",
                               as.character(.x$serviceorder_yr)
                               )
                ) +
        labs(x = "Prob of ServiceRequestId in the Topic")
      )
TopicDistro_05gram04topic_Density <-
  TopProb_BySrvcRqstId_05gram04topic %>%
  split(.$serviceorder_yr) %>%
  map(~ ggplot(data = .x,
               aes(x = prob,
                   colour = topic_name
                   )
               ) +
        geom_density() +
        scale_x_continuous(limits = c(0, 1)
                           ) +
        ggtitle(label = paste0("TopicDistro_",
                               .x$model,
                               "_Density_",
                               as.character(.x$serviceorder_yr)
                               )
                ) +
        labs(x = "Prob of ServiceRequestId in the Topic")
      )
# str(TopicDistro_AllModelsMean_Freqpoly[[18]])
# str(TopicDistro_AllModelsMean_Density[[18]])
# str(TopicDistro_05gram04topic_Freqpoly[[18]])
# str(TopicDistro_05gram04topic_Density[[18]])
TopicDistro_AllModelsMean_Freqpoly[[18]]

TopicDistro_AllModelsMean_Density[[18]]

TopicDistro_05gram04topic_Freqpoly[[18]]

TopicDistro_05gram04topic_Density[[18]]

Saving the visuals created above.
Removing unneeded files.
rm(list = ls(pattern = "TopicDistro_"))
As another method to determine the "correct" topic assignment, here I simply count the number of times a topic assignment was given to each document (servicerequestid), and assign the "correct" topic to the topic with the most assignments. In the case of ties (e.g., all three topics, each assigned twice), I use the `MeanProb` calculated above in the `ProbDocInTopic_MeanProb_BySrvcRqstId` dataframe previously.
str(ProbDocInTopic_ProbsSummed_ByModel)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   224119 obs. of  7 variables:
 $ servicerequestid: chr  "09-00001211" "09-00001211" "09-00001211" "09-00001211" ...
 $ model_ngram     : int  3 3 3 3 3 3 4 4 4 4 ...
 $ model_topic     : int  3 3 3 4 4 4 3 3 4 4 ...
 $ topic_name      : chr  "no_rats_found" "rats_found" "unknown" "no_rats_found" ...
 $ prob_           : num  0.0142 0.0142 0.9717 0.018 0.0359 ...
 $ servicenotes    : chr  "CUSTOMER WAS CALLED BY VECTOR CONTROL. CONTROL NO.: 1382" "CUSTOMER WAS CALLED BY VECTOR CONTROL. CONTROL NO.: 1382" "CUSTOMER WAS CALLED BY VECTOR CONTROL. CONTROL NO.: 1382" "CUSTOMER WAS CALLED BY VECTOR CONTROL. CONTROL NO.: 1382" ...
 $ serviceorderdate: POSIXct, format: "1999-04-27 12:59:00" "1999-04-27 12:59:00" ...
View(ProbDocInTopic_ProbsSummed_ByModel)
# ProbDocInTopic_ProbsSummed_ByModel %>% select(model_ngram, model_topic) %>% distinct()
str(ProbDocInTopic_MeanProb_BySrvcRqstId)
Classes ‘grouped_df’, ‘tbl_df’, ‘tbl’ and 'data.frame': 43629 obs. of  5 variables:
 $ servicerequestid: chr  "09-00001211" "09-00001211" "09-00001211" "09-00001323" ...
 $ topic_name      : chr  "unknown" "no_rats_found" "rats_found" "unknown" ...
 $ MeanProb        : num  0.581 0.333 0.183 0.479 0.466 ...
 $ servicenotes    : chr  "CUSTOMER WAS CALLED BY VECTOR CONTROL. CONTROL NO.: 1382" "CUSTOMER WAS CALLED BY VECTOR CONTROL. CONTROL NO.: 1382" "CUSTOMER WAS CALLED BY VECTOR CONTROL. CONTROL NO.: 1382" "rats in the alley behind house" ...
 $ serviceorderdate: POSIXct, format: "1999-04-27 12:59:00" "1999-04-27 12:59:00" ...
 - attr(*, "vars")= chr "servicerequestid"
 - attr(*, "indices")=List of 14543
  ..$ : int  0 1 2
  ..$ : int  3 4 5
  ..$ : int  6 7 8
  ..$ : int  9 10 11
  ..$ : int  12 13 14
  ..$ : int  15 16 17
  ..$ : int  18 19 20
  ..$ : int  21 22 23
  ..$ : int  24 25 26
  ..$ : int  27 28 29
  ..$ : int  30 31 32
  ..$ : int  33 34 35
  ..$ : int  36 37 38
  ..$ : int  39 40 41
  ..$ : int  42 43 44
  ..$ : int  45 46 47
  ..$ : int  48 49 50
  ..$ : int  51 52 53
  ..$ : int  54 55 56
  ..$ : int  57 58 59
  ..$ : int  60 61 62
  ..$ : int  63 64 65
  ..$ : int  66 67 68
  ..$ : int  69 70 71
  ..$ : int  72 73 74
  ..$ : int  75 76 77
  ..$ : int  78 79 80
  ..$ : int  81 82 83
  ..$ : int  84 85 86
  ..$ : int  87 88 89
  ..$ : int  90 91 92
  ..$ : int  93 94 95
  ..$ : int  96 97 98
  ..$ : int  99 100 101
  ..$ : int  102 103 104
  ..$ : int  105 106 107
  ..$ : int  108 109 110
  ..$ : int  111 112 113
  ..$ : int  114 115 116
  ..$ : int  117 118 119
  ..$ : int  120 121 122
  ..$ : int  123 124 125
  ..$ : int  126 127 128
  ..$ : int  129 130 131
  ..$ : int  132 133 134
  ..$ : int  135 136 137
  ..$ : int  138 139 140
  ..$ : int  141 142 143
  ..$ : int  144 145 146
  ..$ : int  147 148 149
  ..$ : int  150 151 152
  ..$ : int  153 154 155
  ..$ : int  156 157 158
  ..$ : int  159 160 161
  ..$ : int  162 163 164
  ..$ : int  165 166 167
  ..$ : int  168 169 170
  ..$ : int  171 172 173
  ..$ : int  174 175 176
  ..$ : int  177 178 179
  ..$ : int  180 181 182
  ..$ : int  183 184 185
  ..$ : int  186 187 188
  ..$ : int  189 190 191
  ..$ : int  192 193 194
  ..$ : int  195 196 197
  ..$ : int  198 199 200
  ..$ : int  201 202 203
  ..$ : int  204 205 206
  ..$ : int  207 208 209
  ..$ : int  210 211 212
  ..$ : int  213 214 215
  ..$ : int  216 217 218
  ..$ : int  219 220 221
  ..$ : int  222 223 224
  ..$ : int  225 226 227
  ..$ : int  228 229 230
  ..$ : int  231 232 233
  ..$ : int  234 235 236
  ..$ : int  237 238 239
  ..$ : int  240 241 242
  ..$ : int  243 244 245
  ..$ : int  246 247 248
  ..$ : int  249 250 251
  ..$ : int  252 253 254
  ..$ : int  255 256 257
  ..$ : int  258 259 260
  ..$ : int  261 262 263
  ..$ : int  264 265 266
  ..$ : int  267 268 269
  ..$ : int  270 271 272
  ..$ : int  273 274 275
  ..$ : int  276 277 278
  ..$ : int  279 280 281
  ..$ : int  282 283 284
  ..$ : int  285 286 287
  ..$ : int  288 289 290
  ..$ : int  291 292 293
  ..$ : int  294 295 296
  .. [list output truncated]
 - attr(*, "group_sizes")= int  3 3 3 3 3 3 3 3 3 3 ...
 - attr(*, "biggest_group_size")= int 3
 - attr(*, "labels")='data.frame':  14543 obs. of  1 variable:
  ..$ servicerequestid: chr  "09-00001211" "09-00001323" "09-00001410" "09-00001865" ...
  ..- attr(*, "vars")= chr "servicerequestid"
View(ProbDocInTopic_MeanProb_BySrvcRqstId)
TopicAssigned_ByCounts_ByMeanProb <-
  ProbDocInTopic_ProbsSummed_ByModel %>% 
  group_by(servicerequestid,
           model_ngram,
           model_topic
           ) %>% 
  top_n(1,
        prob_
        ) %>% 
  ungroup() %>% 
  count(servicerequestid,
        topic_name
        ) %>% 
  left_join(ProbDocInTopic_MeanProb_BySrvcRqstId,
            by = c("servicerequestid" = "servicerequestid",
                   "topic_name" = "topic_name"
                   )
            ) %>% 
  select(servicerequestid,
         topic_name,
         n,
         MeanProb
         ) %>% 
  group_by(servicerequestid) %>%
  arrange(servicerequestid,
          desc(n),
          desc(MeanProb)
          ) %>% 
  ungroup() %>% 
  group_by(servicerequestid) %>%
  mutate(RowNum = row_number()
         ) %>% 
  ungroup() %>% 
  filter(RowNum == 1) %>% 
  rename(times_topic_assigned = n) %>% 
  left_join(ServiceNotesCleaned,
            by = "servicerequestid"
            ) %>% 
  select(servicerequestid,
         topic_name,
         times_topic_assigned,
         MeanProb,
         servicenotes,
         servicenotes_nonums_nopunc,
         serviceorderdate,
         serviceorder_date,
         serviceorder_yr,
         serviceorder_yr_posix,
         serviceorder_mth,
         serviceorder_yrmth,
         serviceorder_yrmth_posix,
         serviceorder_day,
         serviceorder_wkday
         )
str(ServiceNotesCleaned)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   15581 obs. of  17 variables:
 $ servicerequestid          : chr  "10-00020177" "10-00020346" "10-00020514" "10-00020754" ...
 $ servicepriority           : chr  "UNKNOWN" "UNKNOWN" "UNKNOWN" "UNKNOWN" ...
 $ servicecode               : chr  "S0311" "S0311" "S0311" "S0311" ...
 $ servicecodedescription    : chr  "Rat Abatement" "Rat Abatement" "Rat Abatement" "Rat Abatement" ...
 $ servicetypecode           : chr  "DEPAHEAL" "DEPAHEAL" "DEPAHEAL" "DEPAHEAL" ...
 $ servicetypecodedescription: chr  "DOH" "DOH" "DOH" "DOH" ...
 $ serviceorderdate          : POSIXct, format: "2010-01-21 15:46:23" "2010-01-21 16:32:51" ...
 $ servicenotes              : chr  "On 1/25/10 L Rogers baited 3 rat burrows in the rear yd." "On 1/26/10 G Cornes found no rat burrows in the area." "On 1/28/10 L Rogers baited 4 rat burrows in the rear yd." "On 1/26/10 V Purvis baited 9 rat burrows in the park." ...
 $ serviceorder_date         : Date, format: "2010-01-21" "2010-01-21" ...
 $ serviceorder_yr           : num  2010 2010 2010 2010 2010 2010 2010 2010 2010 2010 ...
 $ serviceorder_yr_posix     : POSIXct, format: "2010-01-01" "2010-01-01" ...
 $ serviceorder_mth          : Ord.factor w/ 12 levels "Jan"<"Feb"<"Mar"<..: 1 1 1 1 1 1 1 1 1 1 ...
 $ serviceorder_yrmth        : chr  "2010-01" "2010-01" "2010-01" "2010-01" ...
 $ serviceorder_yrmth_posix  : POSIXct, format: "2010-01-01" "2010-01-01" ...
 $ serviceorder_day          : int  21 21 21 21 21 22 22 22 22 22 ...
 $ serviceorder_wkday        : Ord.factor w/ 7 levels "Sun"<"Mon"<"Tue"<..: 5 5 5 5 5 6 6 6 6 6 ...
 $ servicenotes_nonums_nopunc: chr  "   rogers baited  rat burrows rear yd" "   cornes found no rat burrows" "   rogers baited  rat burrows rear yd" "   purvis baited  rat burrows park" ...
str(TopicAssigned_ByCounts_ByMeanProb)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   14543 obs. of  15 variables:
 $ servicerequestid          : chr  "09-00001211" "09-00001323" "09-00001410" "09-00001865" ...
 $ topic_name                : chr  "unknown" "unknown" "rats_found" "no_rats_found" ...
 $ times_topic_assigned      : int  3 1 4 4 3 3 3 4 2 2 ...
 $ MeanProb                  : num  0.581 0.479 0.662 0.651 0.593 ...
 $ servicenotes              : chr  "CUSTOMER WAS CALLED BY VECTOR CONTROL. CONTROL NO.: 1382" "rats in the alley behind house" "the rat are coming from an apartment building adjacent to the        alley.  there is alot of trash pilled up behind the apartm"| __truncated__ "The vector control branch baited at 2874 Perry St. NE for rats on 5-25-99." ...
 $ servicenotes_nonums_nopunc: chr  "customer called vector control control no " "rats alley house" "rat coming apartment building adjacent alley alot trash pilled apartment building" "vector control branch baited  perry st ne rats   " ...
 $ serviceorderdate          : POSIXct, format: "1999-04-27 12:59:00" "1999-04-30 19:59:00" ...
 $ serviceorder_date         : Date, format: "1999-04-27" "1999-04-30" ...
 $ serviceorder_yr           : num  1999 1999 1999 1999 1999 ...
 $ serviceorder_yr_posix     : POSIXct, format: "1999-01-01" "1999-01-01" ...
 $ serviceorder_mth          : Ord.factor w/ 12 levels "Jan"<"Feb"<"Mar"<..: 4 4 5 5 5 5 5 5 6 6 ...
 $ serviceorder_yrmth        : chr  "1999-04" "1999-04" "1999-05" "1999-05" ...
 $ serviceorder_yrmth_posix  : POSIXct, format: "1999-04-01" "1999-04-01" ...
 $ serviceorder_day          : int  27 30 6 14 19 21 26 28 3 8 ...
 $ serviceorder_wkday        : Ord.factor w/ 7 levels "Sun"<"Mon"<"Tue"<..: 3 6 5 6 4 6 4 6 5 3 ...
head(TopicAssigned_ByCounts_ByMeanProb, 1000)
View(TopicAssigned_ByCounts_ByMeanProb)
So now, we can compare two different models, each being slight variations of six LDA models.  

1) `TopProb_BySrvcRqstId` assigns the topic by taking the mean topic probability score across all six models.
2) `TopicAssigned_ByCounts_ByMeanProb` assigns the topic by taking the most frequently assigned topic across all six models.
str(TopicAssigned_ByCounts_ByMeanProb)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   14543 obs. of  15 variables:
 $ servicerequestid          : chr  "09-00001211" "09-00001323" "09-00001410" "09-00001865" ...
 $ topic_name                : chr  "unknown" "unknown" "rats_found" "no_rats_found" ...
 $ times_topic_assigned      : int  3 1 4 4 3 3 3 4 2 2 ...
 $ MeanProb                  : num  0.581 0.479 0.662 0.651 0.593 ...
 $ servicenotes              : chr  "CUSTOMER WAS CALLED BY VECTOR CONTROL. CONTROL NO.: 1382" "rats in the alley behind house" "the rat are coming from an apartment building adjacent to the        alley.  there is alot of trash pilled up behind the apartm"| __truncated__ "The vector control branch baited at 2874 Perry St. NE for rats on 5-25-99." ...
 $ servicenotes_nonums_nopunc: chr  "customer called vector control control no " "rats alley house" "rat coming apartment building adjacent alley alot trash pilled apartment building" "vector control branch baited  perry st ne rats   " ...
 $ serviceorderdate          : POSIXct, format: "1999-04-27 12:59:00" "1999-04-30 19:59:00" ...
 $ serviceorder_date         : Date, format: "1999-04-27" "1999-04-30" ...
 $ serviceorder_yr           : num  1999 1999 1999 1999 1999 ...
 $ serviceorder_yr_posix     : POSIXct, format: "1999-01-01" "1999-01-01" ...
 $ serviceorder_mth          : Ord.factor w/ 12 levels "Jan"<"Feb"<"Mar"<..: 4 4 5 5 5 5 5 5 6 6 ...
 $ serviceorder_yrmth        : chr  "1999-04" "1999-04" "1999-05" "1999-05" ...
 $ serviceorder_yrmth_posix  : POSIXct, format: "1999-04-01" "1999-04-01" ...
 $ serviceorder_day          : int  27 30 6 14 19 21 26 28 3 8 ...
 $ serviceorder_wkday        : Ord.factor w/ 7 levels "Sun"<"Mon"<"Tue"<..: 3 6 5 6 4 6 4 6 5 3 ...
str(TopProb_BySrvcRqstId)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   14543 obs. of  8 variables:
 $ servicerequestid: chr  "09-00119541" "09-00029707" "09-00053342" "09-00040528" ...
 $ topic_name      : chr  "no_rats_found" "unknown" "rats_found" "rats_found" ...
 $ prob            : num  0.36 0.365 0.365 0.366 0.366 ...
 $ servicenotes    : chr  "3-8-01  baited 2 burrows   front yard    3-30-01   baited 2 rat burrows  RH/GB    4-6-01    baited 1 burrow in front gb rh" "3350 C ST SE, NO RAT BURROWS WERE FOUND ON THE PROPERTY. AT 3346 & 3348 C ST SE BAITED REAR GARAGE. RICKS/DD" "THE VECTOR CONTROL BRANCH BAITED 2 BURROWS, RIGHT FRONT STEPS. FOLLOW UP BAITING WILL TAKE PLACE SOON; RICKS/PRICE;  DD" "THE VECTOR CONTROL BRANCH BAITED THE REAR YARD; 10 RAT BURROWS WERE FOUND. FOLLOW-UP BAITING WILL TAKE PLACE SHORTLY.HERRINGTON"| __truncated__ ...
 $ serviceorderdate: POSIXct, format: "2001-02-22 12:25:00" "1999-10-28 16:11:00" ...
 $ serviceorder_yr : num  2001 1999 2000 2000 2000 ...
 $ yr_group        : chr  "2001_no_rats_found" "1999_unknown" "2000_rats_found" "2000_rats_found" ...
 $ model           : chr  "AllModels_MeanProb" "AllModels_MeanProb" "AllModels_MeanProb" "AllModels_MeanProb" ...
str(TopProb_BySrvcRqstId_05gram04topic)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   11846 obs. of  10 variables:
 $ servicerequestid: chr  "10-00147588" "11-00106490" "10-00165933" "11-00138104" ...
 $ model_ngram     : int  5 5 5 5 5 5 5 5 5 5 ...
 $ model_topic     : int  4 4 4 4 4 4 4 4 4 4 ...
 $ topic_name      : chr  "rats_found" "unknown" "unknown" "unknown" ...
 $ prob            : num  0.343 0.383 0.391 0.414 0.442 ...
 $ servicenotes    : chr  "On 5/24/10 G Cornes baited 10 rat burrows in front of bldg around sidewalk." "On 4/27/11@9:11am L rogers baited 4 rat burrows in the rear yd by shed. Eaton Bait Block/Peanut Butter, EPA# 56-42, .005% 4oz, "| __truncated__ "On 5/18/10 10:25AM G Curtis baited 2 rat burrows in the alley and tree box" "On 5/18/11@10:45am L Rogers baited 1 rat burrow at tree box. Eaton Bait Block/Peanut Butter, EPA# 56-42, .005% 1oz, Gloves & Sh"| __truncated__ ...
 $ serviceorderdate: POSIXct, format: "2010-04-29 14:45:58" "2011-04-15 03:57:15" ...
 $ serviceorder_yr : num  2010 2011 2010 2011 2010 ...
 $ yr_group        : chr  "2010_rats_found" "2011_unknown" "2010_unknown" "2011_unknown" ...
 $ model           : chr  "5gram4topic" "5gram4topic" "5gram4topic" "5gram4topic" ...
dim(TopicAssigned_ByCounts_ByMeanProb)
[1] 14543    15
dim(TopProb_BySrvcRqstId)
[1] 14543     8
dim(TopProb_BySrvcRqstId_05gram04topic)
[1] 11846    10
a <- TopProb_BySrvcRqstId %>% 
  select(servicerequestid,
         topic_name,
         prob
         ) %>% 
  rename(topic_name_meanprob = topic_name,
         prob_meanprob = prob
         )
b <- TopicAssigned_ByCounts_ByMeanProb %>% 
  rename(topic_name_topcounts = topic_name,
         times_topic_assigned_topcounts = times_topic_assigned,
         prob_topcounts = MeanProb
         )
ModelsCompare <- a %>% 
  left_join(b,
            by = "servicerequestid"
            )
rm(a, b)
str(ModelsCompare)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   14543 obs. of  17 variables:
 $ servicerequestid              : chr  "09-00119541" "09-00029707" "09-00053342" "09-00040528" ...
 $ topic_name_meanprob           : chr  "no_rats_found" "unknown" "rats_found" "rats_found" ...
 $ prob_meanprob                 : num  0.36 0.365 0.365 0.366 0.366 ...
 $ topic_name_topcounts          : chr  "no_rats_found" "unknown" "rats_found" "rats_found" ...
 $ times_topic_assigned_topcounts: int  3 2 2 2 2 2 2 2 2 2 ...
 $ prob_topcounts                : num  0.36 0.365 0.365 0.366 0.366 ...
 $ servicenotes                  : chr  "3-8-01  baited 2 burrows   front yard    3-30-01   baited 2 rat burrows  RH/GB    4-6-01    baited 1 burrow in front gb rh" "3350 C ST SE, NO RAT BURROWS WERE FOUND ON THE PROPERTY. AT 3346 & 3348 C ST SE BAITED REAR GARAGE. RICKS/DD" "THE VECTOR CONTROL BRANCH BAITED 2 BURROWS, RIGHT FRONT STEPS. FOLLOW UP BAITING WILL TAKE PLACE SOON; RICKS/PRICE;  DD" "THE VECTOR CONTROL BRANCH BAITED THE REAR YARD; 10 RAT BURROWS WERE FOUND. FOLLOW-UP BAITING WILL TAKE PLACE SHORTLY.HERRINGTON"| __truncated__ ...
 $ servicenotes_nonums_nopunc    : chr  "   baited  burrows front yard    baited  rat burrows rh gb    baited  burrow front gb rh" " st se no rat burrows found property   st se baited rear garage ricks dd" "vector control branch baited  burrows front steps follow baiting ricks price dd" "vector control branch baited rear yard  rat burrows found follow baiting shortlyherrington brown dd" ...
 $ serviceorderdate              : POSIXct, format: "2001-02-22 12:25:00" "1999-10-28 16:11:00" ...
 $ serviceorder_date             : Date, format: "2001-02-22" "1999-10-28" ...
 $ serviceorder_yr               : num  2001 1999 2000 2000 2000 ...
 $ serviceorder_yr_posix         : POSIXct, format: "2001-01-01" "1999-01-01" ...
 $ serviceorder_mth              : Ord.factor w/ 12 levels "Jan"<"Feb"<"Mar"<..: 2 10 3 1 1 4 4 5 5 5 ...
 $ serviceorder_yrmth            : chr  "2001-02" "1999-10" "2000-03" "2000-01" ...
 $ serviceorder_yrmth_posix      : POSIXct, format: "2001-02-01" "1999-10-01" ...
 $ serviceorder_day              : int  22 28 22 13 21 17 1 13 16 24 ...
 $ serviceorder_wkday            : Ord.factor w/ 7 levels "Sun"<"Mon"<"Tue"<..: 5 5 4 5 6 2 4 5 1 2 ...
ModelsCompare
View(ModelsCompare)
When comparing the two models, they coincide nearly 99% of the time...but the disagreements still raise questions.
filter(ModelsCompare,
       topic_name_meanprob != topic_name_topcounts
       ) %>% 
  nrow() / nrow(ModelsCompare) * 100
[1] 1.155195
filter(ModelsCompare,
       topic_name_meanprob == topic_name_topcounts
       ) %>% 
  nrow() / nrow(ModelsCompare) * 100
[1] 98.84481
# View(filter(ModelsCompare,
#             topic_name_meanprob != topic_name_topcounts
#             )
#      )
View(filter(ModelsCompare,
            topic_name_meanprob != topic_name_topcounts
            ) %>% 
       select(servicerequestid,
              topic_name_meanprob,
              topic_name_topcounts,
              servicenotes,
              servicenotes_nonums_nopunc
              )
     )
As the models assign topics almost identically, let's use the `topcounts` model to do some quick inspections about how often each of the topics were assigned, when, changes over time, etc.
Counts_AllYears <- ModelsCompare %>% 
  mutate(topic_name = factor(topic_name_topcounts,
                             levels = c("unknown",
                                        "no_rats_found",
                                        "rats_found"
                                        )
                             )
         ) %>% 
group_by(topic_name) %>% 
  count() %>% 
  rename(counts = n)
Counts_AcrossYears <- ModelsCompare %>% 
  mutate(topic_name = factor(topic_name_topcounts,
                             levels = c("unknown",
                                        "no_rats_found",
                                        "rats_found"
                                        )
                             )
         ) %>% 
group_by(topic_name,
         serviceorder_yr
         ) %>% 
  count() %>% 
  rename(counts = n)
ggplot(data = Counts_AllYears,
       aes(x = topic_name,
           y = counts,
           fill = topic_name
           )
       ) +
  geom_col() +
  geom_text(aes(label = counts),
            nudge_y = -200,
            size = 3
            ) +
  # theme_minimal() +
  theme(legend.position = "none") +
  coord_flip()
 ggsave(paste0(wd,
               "/Viz/",
               # "New_",
               "Topics_CountModel_Counts_AllYears.png"
                  ),
        scale = 4,
        width = 6,
        height = 6,
        units = "cm"
        )

ggplot(data = Counts_AcrossYears,
       aes(x = topic_name,
           y = counts,
           fill = topic_name
           )
       ) +
  geom_col() +
  geom_text(aes(label = counts),
            nudge_y = 100,
            size = 2.5
            ) +
  scale_y_continuous(limits = c(0, 1800),
                     breaks = seq(0, 1800, 300)
                     ) +
  facet_wrap(~serviceorder_yr) +
  theme(legend.position = "none") +
  coord_flip()
 ggsave(paste0(wd,
               "/Viz/",
               # "New_",
               "Topics_CountModel_Counts_AcrossYears.png"
                  ),
        scale = 4,
        width = 8,
        height = 6,
        units = "cm"
        )

LS0tCnRpdGxlOiAiRXhhbXBsZSBvZiBVc2luZyBUb3BpYyBNb2RlbGluZyAodmlhIExhdGVudCBEaXJpY2hsZXQgYWxsb2NhdGlvbikgdG8gUHVsbCBPdXQgRmVhdHVyZXMgZnJvbSAzMTEgU2VydmljZSBOb3RlcyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQogIAogICAgCiAgICBMb2FkIHRoZSByZWxldmFudCBsaWJyYXJpZXMuCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKIyBybShsaXN0ID0gbHMoKSkKCgpsaWJyYXJ5KCJ0aWR5dmVyc2UiKSAgICAgICAgICAjIGRhdGEgbWFuaXB1bGF0aW9uCmxpYnJhcnkoIm1hZ3JpdHRyIikgICAgICAgICAgICMgZGF0YSBtYW5pcHVsYXRpb24gKHBpcGVpbmcgZGF0YSkKbGlicmFyeSgic3RyaW5nciIpICAgICAgICAgICAgIyBzdHJpbmcgbWFuaXB1bGF0aW9uCmxpYnJhcnkoImx1YnJpZGF0ZSIpICAgICAgICAgICMgZGF0ZSBtYW5pcHVsYXRpb24KbGlicmFyeSgidGlkeXRleHQiKSAgICAgICAgICAgIyB0ZXh0IG1hbmlwdWxhdGlvbgpsaWJyYXJ5KCJ0b3BpY21vZGVscyIpICAgICAgICAjIHRvcGljIG1vZGVsaW5nCmxpYnJhcnkoImdncGxvdDIiKSAgICAgICAgICAgICMgdml6CmxpYnJhcnkoImRvUGFyYWxsZWwiKSAgICAgICAgICMgcGFyYWxsZWwgcHJvY2Vzc2luZwpsaWJyYXJ5KCJsZGF0dW5pbmciKSAgICAgICAgICAjIGVzdGltYXRpbmcgdGhlIHByb3BlciBudW1iZXIgb2YgdG9waWNzCiMgbGlicmFyeSgic2NhbGVzIikKIyBsaWJyYXJ5KCJjb3dwbG90IikgICAgICAgICAgICAjIGdyaWQgcGxvdHRpbmcKCmBgYAogIAogICAgCiAgICBTZXNzaW9uIEluZm8uCmBgYHtyfQoKc2Vzc2lvbkluZm8oKQoKYGBgCiAgCiAgICAKICAgIFNldHVwIHRoZSByb290IGRpcmVjdG9yeS4KYGBge3IgInNldHVwIiwgaW5jbHVkZSA9IEZBTFNFfQoKcmVxdWlyZSgia25pdHIiKQoKb3B0c19rbml0JHNldChyb290LmRpciA9ICIvVXNlcnMvbWR0dXJzZS9EZXNrdG9wL0FuYWx5dGljcy9kY19kb2hfaGFja2F0aG9uIikKCmBgYAogIAogICAgCiAgICBTZXR0aW5nIGB3ZGAgYXMgdGhlIHdvcmtpbmcgZGlyZWN0b3J5LgpgYGB7cn0KCndkIDwtIGdldHdkKCkKCndkCgpgYGAKICAKICAgIAogICAgR2V0IHRoZSByYXcgZGF0YS4gQmVjYXVzZSBvZiB0cm91YmxlIG1haW50YWluaW5nIGEgY29ubmVjdGlvbiB0byBEcm9wYm94IHZpYSBSLCBJIGZpcnN0IGRvd25sb2FkZWQgdGhlIHJhdyBkYXRhIGZyb20gW2h0dHBzOi8vd3d3LmRyb3Bib3guY29tL3NoLzRqN3E1M2xsdGFzZXozaC9BQUJRcndyUWozYWVucUc0MndiVzctcXJhL3llYXJzX2NvbWJpbmVkL2RjXzMxMS0yMDE3LTAxLTE2LmNzdj9kbD0wXShodHRwczovL3d3dy5kcm9wYm94LmNvbS9zaC80ajdxNTNsbHRhc2V6M2gvQUFCUXJ3clFqM2FlbnFHNDJ3Ylc3LXFyYS95ZWFyc19jb21iaW5lZC9kY18zMTEtMjAxNy0wMS0xNi5jc3Y/ZGw9MCkgYW5kIHNhdmVkIHRoZSBmaWxlIGxvY2FsbHkuCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQoKUmF3MzExRGF0YSA8LSByZWFkX2NzdihwYXN0ZTAod2QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIvRGF0YV9SYXcvZGNfMzExLTIwMTctMDEtMTYuY3N2IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjICIvRGF0YV9SYXcvZGNfMzExLTIwMTctMTAtMDcuY3N2IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgICAgIHByb2dyZXNzID0gRkFMU0UKICAgICAgICAgICAgICAgICAgICAgICApCgojIHNhdmluZyBpcyBkb25lIHRvIGF2b2lkIGhhdmluZyB0byBkb3dubG9hZCBhbGwgdGhlIGRhdGEgYWdhaW4Kc2F2ZVJEUyhSYXczMTFEYXRhLAogICAgICAgIHBhc3RlMCh3ZCwKICAgICAgICAgICAgICAgIi9EYXRhX1Byb2Nlc3NlZC8iLAogICAgICAgICAgICAgICAiUmF3MzExRGF0YS5SZHMiCiAgICAgICAgICAgICAgICkKICAgICAgICApCgpzdHIoUmF3MzExRGF0YSkKdGFpbChSYXczMTFEYXRhLCA1MDApCgpgYGAKICAKICAgIAogICAgVW4tY29tbWVudCB0aGUgY2h1bmsgYmVsb3cgdG8gbG9hZCB0aGUgc2F2ZWQgcmF3IGRhdGEgKHRvIGF2b2lkIGhhdmluZyB0byBkb3dubG9hZCB0aGUgcmF3IGRhdGEgYWdhaW4pLgpgYGB7cn0KCiMgUmF3MzExRGF0YSA8LSByZWFkUkRTKHBhc3RlMCh3ZCwKIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIvRGF0YV9Qcm9jZXNzZWQvIiwKIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJSYXczMTFEYXRhLlJkcyIKIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKIyAgICAgICAgICAgICAgICAgICAgICAgKQojIAojIHN0cihSYXczMTFEYXRhKQojIHRhaWwoUmF3MzExRGF0YSwgNTAwKQojIFZpZXcodGFpbChSYXczMTFEYXRhLCAxMDAwKSkKCmBgYAogIAogICAgCiAgICBTZWxlY3RpbmcgdGhvc2UgdmFyaWFibGVzIHRoYXQgbWF5IGJlIHVzZWZ1bCB0byB0ZXN0IGJyZWFrZG93bnMgb2YgdG9waWMgbW9kZWxpbmcuICBGb3IgZXhhbXBsZSwgcnVubmluZyBhIHRvcGljIG1vZGVsIHNlcGFyYXRlbHkgZm9yIHRoZSBkaWZmZXJlbnQgbGV2ZWxzIG9mIGBzZXJ2aWNlY29kZWAuCmBgYHtyfQoKU2VsZWN0ZWRWYXJzIDwtIHNlbGVjdChSYXczMTFEYXRhLAogICAgICAgICAgICAgICAgICAgICAgIFNFUlZJQ0VSRVFVRVNUSUQsCiAgICAgICAgICAgICAgICAgICAgICAgU0VSVklDRVBSSU9SSVRZLAogICAgICAgICAgICAgICAgICAgICAgIFNFUlZJQ0VDT0RFLAogICAgICAgICAgICAgICAgICAgICAgIFNFUlZJQ0VDT0RFREVTQ1JJUFRJT04sCiAgICAgICAgICAgICAgICAgICAgICAgU0VSVklDRVRZUEVDT0RFLAogICAgICAgICAgICAgICAgICAgICAgIFNFUlZJQ0VUWVBFQ09ERURFU0NSSVBUSU9OLAogICAgICAgICAgICAgICAgICAgICAgIFNFUlZJQ0VPUkRFUkRBVEUsCiAgICAgICAgICAgICAgICAgICAgICAgU0VSVklDRU5PVEVTCiAgICAgICAgICAgICAgICAgICAgICAgKQoKbmFtZXMoU2VsZWN0ZWRWYXJzKSA8LSB0b2xvd2VyKG5hbWVzKFNlbGVjdGVkVmFycykpCgpybShSYXczMTFEYXRhKQpzdHIoU2VsZWN0ZWRWYXJzKQoKYGBgCiAgCiAgICAKICAgIFF1aWNrIHZpc3VhbCBpbnNwZWN0aW9uIG9mIGZpbHRlcmluZyB0aGUgZGF0YSB0byBvbmx5IHNlcnZpY2UgY2FsbHMgd2l0aCBub3RlcyAoaS5lLiwgcmVtb3ZpbmcgTkEgdmFsdWVzKSwgYW5kIG9ubHkgdGhvc2UgdGhhdCBhcmUgcmF0LXJlbGF0ZWQgKGBzZXJ2aWNlY29kZSA9PSAiUzAzMTFgKS4gIAogICAgICAKICAgIFJlbW92aW5nIE5BIHZhbHVlcyB0YWtlcyB1cyBmcm9tIDUsMDkzLDg4NyByb3dzIHRvIDMsMDE4LDQxMSByb3dzLiAgCiAgICAKICAgIExvb2tpbmcgYXQgb25seSByYXQtcmVsYXRlZCBzZXJ2aWNlIGNhbGxzIHRha2VzIHVzIGZyb20gMywwMTgsNDExIHJvd3MgdG8gMTUsNTgxIHJvd3MuCmBgYHtyfQoKTm9OQVNlcnZpY2VOb3RlcyA8LSBmaWx0ZXIoU2VsZWN0ZWRWYXJzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAhaXMubmEoc2VydmljZW5vdGVzKQogICAgICAgICAgICAgICAgICAgICAgICAgICApCgojIG1lc3NhZ2UoIlNlbGVjdGVkVmFycyIpCm5yb3coU2VsZWN0ZWRWYXJzKQojIG1lc3NhZ2UoIk5vTkFTZXJ2aWNlTm90ZXMiKQpucm93KE5vTkFTZXJ2aWNlTm90ZXMpCgpybShTZWxlY3RlZFZhcnMpCnN0cihOb05BU2VydmljZU5vdGVzKQoKVmlldyhoZWFkKE5vTkFTZXJ2aWNlTm90ZXMsIDEwMDApKQoKCgpSYXRDYWxscyA8LSBmaWx0ZXIoTm9OQVNlcnZpY2VOb3RlcywKICAgICAgICAgICAgICAgICAgIHNlcnZpY2Vjb2RlID09ICJTMDMxMSIKICAgICAgICAgICAgICAgICAgICkKCiMgbWVzc2FnZSgiTm9OQVNlcnZpY2VOb3RlcyIpCm5yb3coTm9OQVNlcnZpY2VOb3RlcykKIyBtZXNzYWdlKCJSYXRDYWxscyIpCm5yb3coUmF0Q2FsbHMpCgpybShOb05BU2VydmljZU5vdGVzKQpWaWV3KFJhdENhbGxzKQoKYGBgCiAgCiAgICAKICAgIEFkZCBpbiB0aW1lLXJlbGF0ZWQgdmFyaWFibGVzLgpgYGB7cn0KClJhdENhbGxzX1RpbWUgPC0gUmF0Q2FsbHMgJT4lCiAgbXV0YXRlKHNlcnZpY2VvcmRlcl9kYXRlID0gYXNfZGF0ZShzZXJ2aWNlb3JkZXJkYXRlKSwKICAgICAgICAgc2VydmljZW9yZGVyX3lyID0geWVhcihzZXJ2aWNlb3JkZXJkYXRlKSwKICAgICAgICAgc2VydmljZW9yZGVyX3lyX3Bvc2l4ID0gZmxvb3JfZGF0ZShzZXJ2aWNlb3JkZXJkYXRlLCAieWVhciIpLAogICAgICAgICBzZXJ2aWNlb3JkZXJfbXRoID0gbW9udGgoc2VydmljZW9yZGVyZGF0ZSwgbGFiZWwgPSBUUlVFKSwKICAgICAgICAgc2VydmljZW9yZGVyX3lybXRoID0gYXMuY2hhcmFjdGVyKHNlcnZpY2VvcmRlcl9kYXRlKSAlPiUgCiAgICAgICAgICAgc3Vic3RyKDEsIDcpLAogICAgICAgICBzZXJ2aWNlb3JkZXJfeXJtdGhfcG9zaXggPSBmbG9vcl9kYXRlKHNlcnZpY2VvcmRlcmRhdGUsICJtb250aCIpLAogICAgICAgICBzZXJ2aWNlb3JkZXJfZGF5ID0gZGF5KHNlcnZpY2VvcmRlcmRhdGUpLAogICAgICAgICBzZXJ2aWNlb3JkZXJfd2tkYXkgPSB3ZGF5KHNlcnZpY2VvcmRlcmRhdGUsIGxhYmVsID0gVFJVRSkKICAgICAgICAgKQoKcm0oUmF0Q2FsbHMpCnN0cihSYXRDYWxsc19UaW1lKQp0YWlsKFJhdENhbGxzX1RpbWUsIDUwMCkKVmlldyh0YWlsKFJhdENhbGxzX1RpbWUsIDEwMDApKQoKYGBgCiAgCiAgICAKICAgIE5leHQgd2UgbmVlZCB0byBjbGVhbiB1cCB0aGUgdGV4dCBvZiB0aGUgYHNlcnZpY2Vub3Rlc2AgdmFyaWFibGUgLSB0aGlzIHdpbGwgYmUgZG9uZSBpbiBtdWx0aXBsZSBzdGVwcy4gIAogICAgICAKICAgIEFzIHRoZSBmaXJzdCBzdGVwLCB3ZSdsbCByZW1vdmUgY29tbW9uICJzdG9wd29yZHMiIChlLmcuLCBpcywgdGhlLCBhbmQsIGV0Yy4pIGFzIHRoZXkgd29uJ3QgYmUgdmVyeSB1c2VmdWwgaW4gZmluZGluZyB0b3BpY3MgaW4gdGhlIGBzZXJ2aWNlbm90ZXNgIHRleHQuIEFsdGhvdWdoIGl0J3MgYSBzdG9wd29yZHMsIHdlIHNwZWNpZmljYWxseSBkbyBub3QgcmVtb3ZlIHRoZSB3b3JkICJubyIgYXMgaXQgaXMgb2Z0ZW4gdXNlZCB0byBkaXN0aW5ndWlzaCBiZXR3ZWVuICJyYXRzIGZvdW5kIiBhbmQgIm5vIHJhdHMgZm91bmQiLgpgYGB7cn0KCk5vU3RvcFdvcmRzX1VubmVzdCA8LSAKICBSYXRDYWxsc19UaW1lICU+JSAKICBzZWxlY3Qoc2VydmljZXJlcXVlc3RpZCwKICAgICAgICAgc2VydmljZW5vdGVzCiAgICAgICAgICkgJT4lIAogIHVubmVzdF90b2tlbnMod29yZCwKICAgICAgICAgICAgICAgIHNlcnZpY2Vub3RlcwogICAgICAgICAgICAgICAgKSAlPiUgCiAgYW50aV9qb2luKGZpbHRlcihzdG9wX3dvcmRzLAogICAgICAgICAgICAgICAgICAgd29yZCAhPSAibm8iICMgd2UgZG9uJ3QgcmVtb3ZlIHRoZSB3b3JkICJubyIgYXMgaXQgaXMgb2Z0ZW4gdXNlZCB0byBkaXN0aW5ndWlzaCBiZXR3ZWVuICJyYXRzIGZvdW5kIiBhbmQgIm5vIHJhdHMgZm91bmQiCiAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICBieSA9ICJ3b3JkIgogICAgICAgICAgICApCgpTZXJ2aWNlbm90ZXNfTm9TdG9wV3JkcyA8LSBOb1N0b3BXb3Jkc19Vbm5lc3QgJT4lIAogIG5lc3Qod29yZCkgJT4lIAogIG11dGF0ZShzZXJ2aWNlbm90ZXNfbm9zdG9wID0gbWFwKGRhdGEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdW5saXN0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgc2VydmljZW5vdGVzX25vc3RvcCA9IG1hcF9jaHIoc2VydmljZW5vdGVzX25vc3RvcCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbGxhcHNlID0gIiAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgKSAlPiUgCiAgc2VsZWN0KC1kYXRhKQoKClJlbW92ZV9TdG9wV3JkcyA8LSBSYXRDYWxsc19UaW1lICU+JSAKICBsZWZ0X2pvaW4oU2VydmljZW5vdGVzX05vU3RvcFdyZHMsCiAgICAgICAgICAgIGJ5ID0gInNlcnZpY2VyZXF1ZXN0aWQiCiAgICAgICAgICAgICkKCmRpbShSYXRDYWxsc19UaW1lKQpkaW0oUmVtb3ZlX1N0b3BXcmRzKQoKcm0oTm9TdG9wV29yZHNfVW5uZXN0LCBTZXJ2aWNlbm90ZXNfTm9TdG9wV3JkcykKCmhlYWQoUmVtb3ZlX1N0b3BXcmRzLCAxMDApClZpZXcoaGVhZChSZW1vdmVfU3RvcFdyZHMsIDEwMCkpCgpgYGAKICAKICAgIAogICAgCmBgYHtyfQoKIyBzdHIoUmVtb3ZlX1N0b3BXcmRzKQoKIyBWaWV3KFJlbW92ZV9TdG9wV3JkcyAlPiUgCiMgICAgICAgIGZpbHRlcihzdHJfZGV0ZWN0KHNlcnZpY2Vub3Rlc19ub3N0b3AsCiMgICAgICAgICAgICAgICAgICAgICAgICAgICJ3YXNoaW5ndG9uIGRjIgojICAgICAgICAgICAgICAgICAgICAgICAgICApCiMgICAgICAgICAgICAgICApICU+JSAKIyAgICAgICAgc2VsZWN0KHNlcnZpY2Vub3RlcywKIyAgICAgICAgICAgICAgIHNlcnZpY2Vub3Rlc19ub3N0b3AKIyAgICAgICAgICAgICAgICkKIyAgICAgICkKCmBgYAogIAogICAgCiAgICBUaGVuLCB3ZSdsbCByZW1vdmUgYW55IG51bWVyaWMgY2hhcmFjdGVycyBmcm9tICdzZXJ2aWNlbm90ZXMnIHRvIGF2b2lkIGRpc3RpbmN0aW9ucyBub3QgbmVlZGVkIGF0IHRoaXMgbGV2ZWwgKGUuZy4sICJiYWl0ZWQgMyByYXQgYm9ycm93cyIgdnMuICJiYWl0ZWQgNiByYXQgYnVycm93cyIpLiBXZSdsbCBhbHNvIHJlbW92ZSBwdW5jdHVhdGlvbi4KYGBge3J9CgpTZXJ2aWNlTm90ZXNDbGVhbmVkIDwtIFJlbW92ZV9TdG9wV3JkcyAlPiUgCiAgbXV0YXRlKHNlcnZpY2Vub3Rlc19ub251bXNfbm9wdW5jID0gc3RyX3JlcGxhY2VfYWxsKHNlcnZpY2Vub3Rlc19ub3N0b3AsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJbWzpkaWdpdDpdXSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkgJT4lIAogICAgICAgICAgIHN0cl9yZXBsYWNlX2FsbCgiW1s6cHVuY3Q6XV0iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAiIgogICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICkgJT4lIAogIHNlbGVjdCgtc2VydmljZW5vdGVzX25vc3RvcCkKCmRpbShSYXRDYWxsc19UaW1lKQpkaW0oUmVtb3ZlX1N0b3BXcmRzKQpkaW0oU2VydmljZU5vdGVzQ2xlYW5lZCkKCiMgVmlldyhzZWxlY3QoU2VydmljZU5vdGVzQ2xlYW5lZCwKIyAgICAgICAgICAgICBzZXJ2aWNlcmVxdWVzdGlkLAojICAgICAgICAgICAgIHNlcnZpY2Vub3RlcywKIyAgICAgICAgICAgICBzZXJ2aWNlbm90ZXNfbm9udW1zX25vcHVuYwojICAgICAgICAgICAgICkgJT4lIAojICAgICAgICBmaWx0ZXIoc2VydmljZXJlcXVlc3RpZCAlaW4lIG5vbWF0Y2gkc2VydmljZXJlcXVlc3RpZCkKIyAgICAgICkKCnJtKFJhdENhbGxzX1RpbWUsIFJlbW92ZV9TdG9wV3JkcykKCmhlYWQoU2VydmljZU5vdGVzQ2xlYW5lZCwgMTAwKQpWaWV3KGhlYWQoU2VydmljZU5vdGVzQ2xlYW5lZCwgMTAwKSkKCmBgYAogIAogICAgCiAgICBOb3csIHdlIGNhbiBpbnNwZWN0IHRoZSBmcmVxdWVuY2llcyBvZiByYXQtcmVsYXRlZCBzZXJ2aWNlIHJlcXVlc3RzLgpgYGB7cn0KCnN1bW1hcnkoU2VydmljZU5vdGVzQ2xlYW5lZCkKIyBzdW1tYXJ5KFJhdENhbGxzX1RpbWUkc2VydmljZW9yZGVyZGF0ZSkKIyBsaWJyYXJ5KCJwc3ljaCIpCiMgZGVzY3JpYmUoUmF0Q2FsbHNfVGltZSRzZXJ2aWNlb3JkZXJkYXRlKQoKCmdncGxvdF90aGVtZV9iYXNpYyA8LQogIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICMgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IDEsIGNvbG91ciA9ICJibGFjayIpCiAgICAgICAgKQoKIyBnZ3Bsb3QoZGF0YSA9IFJhdENhbGxzX1RpbWUsCiMgICAgICAgIGFlcyh4ID0gc2VydmljZW9yZGVyX2RhdGUpCiMgICAgICAgICkgKwojICAgZ2VvbV9oaXN0b2dyYW0oKSArCiMgICBnZ3Bsb3RfdGhlbWVfYmFzaWMKCnlyX2NvdW50cyA8LSBTZXJ2aWNlTm90ZXNDbGVhbmVkICU+JSAKICBncm91cF9ieShzZXJ2aWNlb3JkZXJfeXJfcG9zaXgpICU+JSAKICBzdW1tYXJpc2UoQ250ID0gbigpCiAgICAgICAgICAgICkgJT4lIAogIGFycmFuZ2Uoc2VydmljZW9yZGVyX3lyX3Bvc2l4KQoKZ2dwbG90KGRhdGEgPSB5cl9jb3VudHMsCiAgICAgICBhZXMoeCA9IHNlcnZpY2VvcmRlcl95cl9wb3NpeCwKICAgICAgICAgICB5ID0gQ250CiAgICAgICAgICAgKQogICAgICAgKSArCiAgZ2VvbV9jb2woZmlsbCA9ICJsaWdodCBibHVlIikgKyAjLCBhbHBoYSA9IDAuMSkgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSBDbnQpLAogICAgICAgICAgICBudWRnZV95ID0gNTAsCiAgICAgICAgICAgIHNpemUgPSAzCiAgICAgICAgICAgICkgKwogIGxhYnModGl0bGUgPSAiQ291bnRzIG9mIG5vbi1OQSBTZXJ2aWNlTm90ZXMiLAogICAgICAgIyBzdWJ0aXRsZSA9ICJieSB5ZWFyIiwKICAgICAgIHggPSAiWWVhciIsCiAgICAgICB5ID0gIkNvdW50IgogICAgICAgKSArCiAgZ2dwbG90X3RoZW1lX2Jhc2ljICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKQogICAgICAgICkgKwogIHNjYWxlX3hfZGF0ZXRpbWUoZGF0ZV9icmVha3MgPSAiMSB5ZWFyIikKCgoKeXJtdGhfY291bnRzIDwtIFNlcnZpY2VOb3Rlc0NsZWFuZWQgJT4lIAogIGdyb3VwX2J5KHNlcnZpY2VvcmRlcl95cm10aF9wb3NpeCkgJT4lIAogIHN1bW1hcmlzZShDbnQgPSBuKCkKICAgICAgICAgICAgKSAlPiUgCiAgYXJyYW5nZShzZXJ2aWNlb3JkZXJfeXJtdGhfcG9zaXgpCgpnZ3Bsb3QoZGF0YSA9IHlybXRoX2NvdW50cywKICAgICAgIGFlcyh4ID0gc2VydmljZW9yZGVyX3lybXRoX3Bvc2l4LAogICAgICAgICAgIHkgPSBDbnQKICAgICAgICAgICApCiAgICAgICApICsKICBnZW9tX2NvbChmaWxsID0gImxpZ2h0IGJsdWUiKSArICMsIGFscGhhID0gMC4xKSArCiAgIyBnZW9tX3RleHQoYWVzKGxhYmVsID0gQ250KSwKICAjICAgICAgICAgICBudWRnZV95ID0gNTAsCiAgIyAgICAgICAgICAgc2l6ZSA9IDMKICAjICAgICAgICAgICApICsKICBsYWJzKHRpdGxlID0gIkNvdW50cyBvZiBub24tTkEgU2VydmljZU5vdGVzIiwKICAgICAgIHggPSAiWWVhci1Nb250aCIsCiAgICAgICB5ID0gIkNvdW50IgogICAgICAgKSArCiAgZ2dwbG90X3RoZW1lX2Jhc2ljICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKQogICAgICAgICkgKwogIGNvb3JkX2NhcnRlc2lhbih4bGltID0gYyhhcy5QT1NJWGN0KCIxOTk4LTEyLTAxIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzLlBPU0lYY3QoIjIwMTctMTItMDEiKQogICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICBleHBhbmQgPSBUUlVFCiAgICAgICAgICAgICAgICAgICkgKwogIHNjYWxlX3hfZGF0ZXRpbWUoZGF0ZV9icmVha3MgPSAiNiBtb250aHMiKQoKYGBgCiAgCiAgICAKICAgIEJhc2VkIG9uIHRoZSBmcmVxdWVuY2llcyBvZiB3aGVuIHdlIGFjdHVhbGx5IGhhdmUgJ3NlcnZpY2Vub3RlcycgZGF0YSwgbGV0J3MgdHJ5IGxpbWl0aW5nIHRoZSBkYXRhc2V0IHRvIHNlcnZpY2UgY2FsbHMgZnJvbSAyMDEwIG9yIGxhdGVyLiBUaGlzIHJlZHVjZXMgdGhlIGRhdGFzZXQgZnVydGhlciwgZnJvbSAxNSw1ODEgcm93cyB0byAxMSwwMzcgcm93cy4gCmBgYHtyfQoKcm0oeXJfY291bnRzLCB5cm10aF9jb3VudHMpCgoKU2VydmljZU5vdGVzQ2xlYW5lZEFmdGVyMjAxMCA8LSBTZXJ2aWNlTm90ZXNDbGVhbmVkICU+JQogIGZpbHRlcihzZXJ2aWNlb3JkZXJkYXRlID49IGFzX2RhdGUoIjIwMTAtMDEtMDEiKQogICAgICAgICApCgpucm93KFNlcnZpY2VOb3Rlc0NsZWFuZWQpCm5yb3coU2VydmljZU5vdGVzQ2xlYW5lZEFmdGVyMjAxMCkKCnN1bW1hcnkoU2VydmljZU5vdGVzQ2xlYW5lZEFmdGVyMjAxMCkKCmBgYAogIAogICAgCiAgICBUaGVuLCB0cmFuc2Zvcm0gdGhlIGBzZXJ2aWNlbm90ZXNgIGZpZWxkIGludG8gb25lIHJvdyBwZXIgbi1ncmFtLiBCZWNhdXNlIHdlIGRvbid0IGtub3cgd2hhdCBsZXZlbCBvZiAnbicgdG8gdXNlLCB3ZSdsbCBjeWNsZSB0aHJvdWdoIHRoZSBwb3NzaWJpbGl0aWVzIGZyb20gbiA9IDEgdG8gbiA9IDUuCmBgYHtyfQoKbmdyYW1fbGlzdCA8LSAxOjUKClJhdF9OZ3JhbV9saXN0IDwtIGxpc3QoKQoKUmF0X05ncmFtX2xpc3QgPC0gbGFwcGx5KG5ncmFtX2xpc3QsCiAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbihpKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICMgeCA8LSBwYXN0ZTAoIjAiLCBpLCAiX2dyYW0iKQogICAgICAgICAgICAgICAgICAgICAgICAgICBTZXJ2aWNlTm90ZXNDbGVhbmVkICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bm5lc3RfdG9rZW5zKG5fZ3JhbSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlcnZpY2Vub3Rlc19ub251bXNfbm9wdW5jLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG9rZW4gPSAibmdyYW1zIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG4gPSBpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgICkKCiMgcm0obmdyYW1fbGlzdCkKUmF0X05ncmFtX2xpc3QKIyBzdHIoUmF0X05ncmFtX2xpc3RbWzFdXSkKCmBgYAogIAogICAgCiAgICBDb3VudGluZyB0aGUgbi1ncmFtcyBpbiBlYWNoIGBzZXJ2aWNlcmVxdWVzdGlkYC4KYGBge3J9Cgp3b3JkX2NvdW50c19saXN0IDwtIGxpc3QoKQoKd29yZF9jb3VudHNfbGlzdCA8LSBsYXBwbHkobmdyYW1fbGlzdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oaSkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJhdF9OZ3JhbV9saXN0W1tpXV0gJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY291bnQoc2VydmljZXJlcXVlc3RpZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fZ3JhbSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNvcnQgPSBUUlVFCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgICAgICAgICApCgp3b3JkX2NvdW50c19saXN0CgpgYGAKICAKICAgIAogICAgVHJhbnNmb3JtaW5nIHRoZSBkYXRhZnJhbWUgaW50byBhIGRvY3VtZW50IHRlcm0gbWF0cml4IC0gaS5lLiwgZG9jdW1lbnRzIChgc2VydmljZXJlcXVlc3RpZGBzKSBhcmUgdGhlIHJvd3MgYW5kIG4tZ3JhbXMgYXJlIHRoZSBjb2x1bW5zLgpgYGB7cn0KCmR0bV9saXN0IDwtIGxpc3QoKQoKZHRtX2xpc3QgPC0gbGFwcGx5KG5ncmFtX2xpc3QsCiAgICAgICAgICAgICAgICAgICBmdW5jdGlvbihpKSB7CiAgICAgICAgICAgICAgICAgICAgIHdvcmRfY291bnRzX2xpc3RbW2ldXSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgY2FzdF9kdG0oZG9jdW1lbnQgPSBzZXJ2aWNlcmVxdWVzdGlkLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlcm0gPSBuX2dyYW0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSBuLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgd2VpZ2h0aW5nID0gdG06OndlaWdodFRmSWRmLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgdXNpbmcgdGVybSBmcmVxdWVuY3kgaW52ZXJzZSBkb2N1bWVudCBmcmVxdWVuY3kgKFRmSWRmKSB3ZWlnaHRpbmcgaXMgYW5vdGhlciwgcG9zc2libHkgbW9yZSBhY2N1cmF0ZSBtZWFzdXJlLCBidXQgdG9waWNtb2RlbHM6OkxEQSAodXNlZCBiZWxvdykgb25seSBhY2NlcHRzIGRvY3VtZW50IHRlcm0gbWF0cmljZXMgd2l0aCB0ZXJtLWZyZXF1ZW5jeSB3ZWlnaHRpbmcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3ZWlnaHRpbmcgPSB0bTo6d2VpZ2h0VGYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICkKCmR0bV9saXN0CgpgYGAKICAKICAgIAogICAgVG8gZGV0ZXJtaW5lIHRoZSAicHJvcGVyIiBudW1iZXIgb2YgdG9waWNzLCBoZXJlIEkgdHJ5IHVzaW5nIHRoZSBgbGRhdHVuaW5nOjpGaW5kVG9waWNzTnVtYmVyYCBmdW5jdGlvbi4gVGhlIGNvZGUgY2h1bmsgaXMgYmFzZWQgb24gdGhlIHZpZ25ldHRlIGhlcmU6ICBbaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2xkYXR1bmluZy92aWduZXR0ZXMvdG9waWNzLmh0bWxdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9sZGF0dW5pbmcvdmlnbmV0dGVzL3RvcGljcy5odG1sKS4gIAogICAgCiAgICBUaGlzIGFuYWx5c2VzIHdhcyBkb25lIHNlcGFyYXRlbHkgZm9yIGVhY2ggbi1ncmFtIGxldmVsIChuID0gMTo1KSwgYW5kIHRoZSBvdmVyYWxsIHJlc3VsdHMgd2VyZSBpbmNvbmNsdXNpdmUgLSB0aGUgInByb3BlciIgbnVtYmVyIG9mIHRvcGljcyBmbHVjdHVhdGVkIGJldHdlZW4gdGhlIGhpZ2hlc3QgbGV2ZWwgdHJpZWQgKDEyIHRvcGljcykgYW5kIG9uZSBvZiB0aGUgbG93ZXN0IGxldmVscyB0cmllZCAoMiwgMywgb3IgNCB0b3BpY3MpLgpgYGB7cn0KCmRldGVjdENvcmVzKGxvZ2ljYWwgPSBUUlVFKSAtIDEKCgp0dW5lc19saXN0IDwtIGR0bV9saXN0ICU+JSAKICBtYXAofiBGaW5kVG9waWNzTnVtYmVyKC54LAogICAgICAgICAgICAgICAgICAgICAgICAgdG9waWNzID0gYygyOjEyKSwKICAgICAgICAgICAgICAgICAgICAgICAgIG1ldHJpY3MgPSBjKCJHcmlmZml0aHMyMDA0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJDYW9KdWFuMjAwOSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQXJ1bjIwMTAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkRldmVhdWQyMDE0IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJHaWJicyIsCiAgICAgICAgICAgICAgICAgICAgICAgICBjb250cm9sID0gbGlzdChzZWVkID0gMTIzNDU2Nzg5KSwKICAgICAgICAgICAgICAgICAgICAgICAgIG1jLmNvcmVzID0gM0wsCiAgICAgICAgICAgICAgICAgICAgICAgICB2ZXJib3NlID0gVFJVRQogICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICApCgoKIyBzdHIodHVuZXNfbGlzdFtbNV1dKQoKdG9waWNfcGxvdHMgPC0KICB0dW5lc19saXN0ICU+JSAKICBtYXAofiBGaW5kVG9waWNzTnVtYmVyX3Bsb3QoLngpCiAgICAgICkKCnRvcGljX3Bsb3RzCgpgYGAKICAKICAgIAogICAgQXMgYW4gYWx0ZXJuYXRpdmUsIGhlcmUgSSB0cnkgdG8gZGV0ZXJtaW5lIHRoZSAicHJvcGVyIiBudW1iZXIgb2YgdG9waWNzIHVzaW5nIGB0b3BpY21vZGVsczo6cGVycGxleGl0eWAuIFBlcnBsZXhpdHkgbWVhc3VyZXMgaG93IHdlbGwgYSBwcm9iYWJpbGl0eSBtb2RlbCBwcmVkaWN0cyBhIHNhbXBsZSwgYW5kIEkgdXNlIGl0IGhlcmUgdmlhIDEwLWZvbGRlciBjcm9zcyB2YWxpZGF0aW9uLiBGb3IgY29tcHV0YXRpb25hbCBwdXJwb3NlcywgSSdtIG9ubHkgdHJ5aW5nIHRoaXMgZm9yIHRoZSA1LWdyYW0gbW9kZWwuICAKICAgIAogICAgVGhpcyBpcyBiYXNlZCBvbiB0aGUgbWV0aG9kIHVzZWQgaGVyZToKICAgIFtodHRwOi8vZWxsaXNwLmdpdGh1Yi5pby9ibG9nLzIwMTcvMDEvMDUvdG9waWMtbW9kZWwtY3ZdKGh0dHA6Ly9lbGxpc3AuZ2l0aHViLmlvL2Jsb2cvMjAxNy8wMS8wNS90b3BpYy1tb2RlbC1jdikKICAgIAogICAgQXMgd2l0aCB0aGUgYGxkYXR1bmluZzo6RmluZFRvcGljc051bWJlcmAgZnVuY3Rpb24gdXNlZCBwcmV2aW91c2x5LCBgdG9waWNtb2RlbHM6OnBlcnBsZXhpdHlgIGlzIGFsc28gaW5jb25jbHVzaXZlIGFzIHRoZXJlIGlzIG5vIGNsZWFyICJlbGJvdyIgaW4gdGhlIHBlcnBsZXhpdHkgcGxvdC4KYGBge3J9CgpmdWxsX2RhdGEgPC0gZHRtX2xpc3RbWzVdXQoKbiA8LSBucm93KGZ1bGxfZGF0YSkKc2VlZCA8LSAxMjM0NTY3ODkKdG9waWNfZ3Vlc3MgPC0gMTIKZm9sZHMgPC0gMTAKYnVybmluIDwtIDEwMDAKaXRlciA8LTEwMDAKa2VlcCA8LTUwCgojLS0tLS0tLS0tLS0tLS0tLTEwLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiwgZGlmZmVyZW50IG51bWJlcnMgb2YgdG9waWNzLS0tLS0tLS0tLS0tLS0tLQpjbHVzdGVyIDwtIG1ha2VDbHVzdGVyKGRldGVjdENvcmVzKGxvZ2ljYWwgPSBUUlVFKSAtIDEKICAgICAgICAgICAgICAgICAgICAgICApICMgbGVhdmUgb25lIENQVSBzcGFyZS4uLgpyZWdpc3RlckRvUGFyYWxsZWwoY2x1c3RlcikKCmNsdXN0ZXJFdmFsUShjbHVzdGVyLCB7CiAgIGxpYnJhcnkodG9waWNtb2RlbHMpCn0pCgpmb2xkcyA8LSBmb2xkcwpzcGxpdGZvbGRzIDwtIHNhbXBsZSgxOmZvbGRzLCBuLCByZXBsYWNlID0gVFJVRSkKY2FuZGlkYXRlX2sgPC0gYygyOnRvcGljX2d1ZXNzKSAjIGNhbmRpZGF0ZXMgZm9yIGhvdyBtYW55IHRvcGljcwpjbHVzdGVyRXhwb3J0KGNsdXN0ZXIsCiAgICAgICAgICAgICAgYygiZnVsbF9kYXRhIiwgImJ1cm5pbiIsICJpdGVyIiwgImtlZXAiLCAic3BsaXRmb2xkcyIsICJmb2xkcyIsICJjYW5kaWRhdGVfayIpCiAgICAgICAgICAgICAgKQoKIyB3ZSBwYXJhbGxlbGl6ZSBieSB0aGUgZGlmZmVyZW50IG51bWJlciBvZiB0b3BpY3MuICBBIHByb2Nlc3NvciBpcyBhbGxvY2F0ZWQgYSB2YWx1ZQojIG9mIGssIGFuZCBkb2VzIHRoZSBjcm9zcy12YWxpZGF0aW9uIHNlcmlhbGx5LiAgVGhpcyBpcyBiZWNhdXNlIGl0IGlzIGFzc3VtZWQgdGhlcmUKIyBhcmUgbW9yZSBjYW5kaWRhdGUgdmFsdWVzIG9mIGsgdGhhbiB0aGVyZSBhcmUgY3Jvc3MtdmFsaWRhdGlvbiBmb2xkcywgaGVuY2UgaXQKIyB3aWxsIGJlIG1vcmUgZWZmaWNpZW50IHRvIHBhcmFsbGVsaXNlCnN5c3RlbS50aW1lKHsKcmVzdWx0cyA8LSBmb3JlYWNoKGogPSAxOmxlbmd0aChjYW5kaWRhdGVfayksCiAgICAgICAgICAgICAgICAgICAuY29tYmluZSA9IHJiaW5kCiAgICAgICAgICAgICAgICAgICApICVkb3BhciV7CiAgIGsgPC0gY2FuZGlkYXRlX2tbal0KICAgCiAgIHJlc3VsdHNfMWsgPC0gbWF0cml4KDAsCiAgICAgICAgICAgICAgICAgICAgICAgIG5yb3cgPSBmb2xkcywKICAgICAgICAgICAgICAgICAgICAgICAgbmNvbCA9IDIKICAgICAgICAgICAgICAgICAgICAgICAgKQogICAKICAgY29sbmFtZXMocmVzdWx0c18xaykgPC0gYygiayIsICJwZXJwbGV4aXR5IikKICAgCiAgIGZvcihpIGluIDE6Zm9sZHMpewogICAgICB0cmFpbl9zZXQgPC0gZnVsbF9kYXRhW3NwbGl0Zm9sZHMgIT0gaSAsIF0KICAgICAgdmFsaWRfc2V0IDwtIGZ1bGxfZGF0YVtzcGxpdGZvbGRzID09IGksIF0KICAgICAgCiAgICAgIGZpdHRlZCA8LSBMREEodHJhaW5fc2V0LAogICAgICAgICAgICAgICAgICAgIGsgPSBrLAogICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJHaWJicyIsCiAgICAgICAgICAgICAgICAgICAgY29udHJvbCA9IGxpc3Qoc2VlZCA9IHNlZWQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZSA9IDEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnVybmluID0gYnVybmluLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGl0ZXIgPSBpdGVyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtlZXAgPSBrZWVwCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICAgICkKICAgICAgCiAgICAgIHJlc3VsdHNfMWtbaSwgXSA8LSBjKGssIHBlcnBsZXhpdHkoZml0dGVkLCBuZXdkYXRhID0gdmFsaWRfc2V0KQogICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgIH0KICAgCiAgIHJldHVybihyZXN1bHRzXzFrKQp9Cn0pCgpzdG9wQ2x1c3RlcihjbHVzdGVyKQoKcmVzdWx0c19kZiA8LSBhcy5kYXRhLmZyYW1lKHJlc3VsdHMpCgoKIyBnZ3Bsb3QoZGF0YSA9IHJlc3VsdHNfZGYsCiMgICAgICAgIGFlcyh4ID0gaywKIyAgICAgICAgICAgIHkgPSBwZXJwbGV4aXR5KQojICAgICAgICApICsKIyAgIGdlb21fcG9pbnQoKSArCiMgICBnZW9tX3Ntb290aChzZSA9IEZBTFNFKSArCiMgICBjb29yZF9jYXJ0ZXNpYW4oeGxpbSA9IGMoMCwgMTIpCiMgICAgICAgICAgICAgICAgICAgKSArCiMgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDEyLCAyKQojICAgICAgICAgICAgICAgICAgICAgICkgKwojICAgZ2dwbG90X3RoZW1lX2Jhc2ljICsKIyAgIGdndGl0bGUobGFiZWwgPSAiMTAtZm9sZCBjcm9zcy12YWxpZGF0aW9uIG9mIHRvcGljIG1vZGVsbGluZyIsCiMgICAgICAgICAgIHN1YnRpdGxlID0gIihpLmUuLCAxMCBkaWZmZXJlbnQgbW9kZWxzIGZpdCBmb3IgZWFjaCBwb3RlbnRpYWwgbnVtYmVyIG9mIHRvcGljcykiCiMgICAgICAgICAgICkgKwojICAgbGFicyh4ID0gIlBvdGVudGlhbCBOdW1iZXIgb2YgVG9waWNzIiwKIyAgICAgICAgeSA9ICJQZXJwbGV4aXR5IFdoZW4gRml0dGluZyB0aGUgVHJhaW5lZCBNb2RlbCB0byB0aGUgSG9sZC1PdXQgU2V0IgojICAgICAgICApCgoKZ2dwbG90KGRhdGEgPSByZXN1bHRzX2RmLAogICAgICAgYWVzKHggPSBrLAogICAgICAgICAgIHkgPSBwZXJwbGV4aXR5KQogICAgICAgKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX3Ntb290aChzZSA9IFRSVUUpICsKICBjb29yZF9jYXJ0ZXNpYW4oeGxpbSA9IGMoMCwgMTIpLAogICAgICAgICAgICAgICAgICB5bGltID0gYygwLCA2MDAwKQogICAgICAgICAgICAgICAgICApICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDEyLCAyKQogICAgICAgICAgICAgICAgICAgICApICsKICAjIGdncGxvdF90aGVtZV9iYXNpYyArCiAgZ2d0aXRsZShsYWJlbCA9ICIxMC1mb2xkIGNyb3NzLXZhbGlkYXRpb24gb2YgdG9waWMgbW9kZWxsaW5nIiwKICAgICAgICAgIHN1YnRpdGxlID0gIihpLmUuLCAxMCBkaWZmZXJlbnQgbW9kZWxzIGZpdCBmb3IgZWFjaCBwb3RlbnRpYWwgbnVtYmVyIG9mIHRvcGljcykiCiAgICAgICAgICApICsKICBsYWJzKHggPSAiUG90ZW50aWFsIE51bWJlciBvZiBUb3BpY3MiLAogICAgICAgeSA9ICJQZXJwbGV4aXR5IFdoZW4gRml0dGluZyB0aGUgVHJhaW5lZCBNb2RlbCB0byB0aGUgSG9sZC1PdXQgU2V0IgogICAgICAgKQoKYGBgCiAgCiAgICAKICAgIFJlbW92ZSB0aGUgbm8tbG9uZ2VyLW5lZWRlZCBmaWxlcy4KYGBge3J9CgpybShjbHVzdGVyLCBmdWxsX2RhdGEsIHJlc3VsdHMsIHRvcGljX3Bsb3RzLCB0dW5lc19saXN0LCBidXJuaW4sIGNhbmRpZGF0ZV9rLCBmb2xkcywgaXRlciwga2VlcCwgbiwgc2VlZCwgc3BsaXRmb2xkcywgdG9waWNfZ3Vlc3MpCgpgYGAKICAKICAgIAogICAgSGVyZSBJIHVzZSBMYXRlbnQgRGlyaWNobGV0IGFsbG9jYXRpb24gZm9yIHRvcGljIG1vZGVsaW5nLiBBcyBkZXRlcm1pbmluZyB0aGUgInByb3BlciIgbnVtYmVyIG9mIHRvcGljcyB3YXMgaW5jb25jbHVzaXZlLCBJJ20gY3ljbGluZyB0aHJvdWdoIGV2ZXJ5IGNvbWJpbmF0aW9uIG9mIG5ncmFtcyAoMTo1KSBhbmQgdG9waWNzICgyOjEyKS4KYGBge3J9Cgp0b3BpY19ndWVzcyA8LSAyOjEyCgpsZGFfbGlzdCA8LSBsaXN0KCkKCgpjbHVzdGVyIDwtIG1ha2VDbHVzdGVyKGRldGVjdENvcmVzKGxvZ2ljYWwgPSBUUlVFKSAtIDEKICAgICAgICAgICAgICAgICAgICAgICApICMgbGVhdmUgb25lIENQVSBzcGFyZS4uLgpyZWdpc3RlckRvUGFyYWxsZWwoY2x1c3RlcikKCmZvcihpIGluIG5ncmFtX2xpc3QpIHsKICBmb3IoaiBpbiB0b3BpY19ndWVzcykgewogICAgeCA8LSBMREEoZHRtX2xpc3RbW2ldXSwKICAgICAgICAgICAgIGsgPSBqLAogICAgICAgICAgICAgY29udHJvbCA9IGxpc3Qoc2VlZCA9IDEyMzQ1Njc4OSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2UgPSAxCiAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICApCiAgICAKICAgIGlmZWxzZSgoaSA9PSBtaW4obmdyYW1fbGlzdCkgJgogICAgICAgICAgICAgIGogPT0gbWluKHRvcGljX2d1ZXNzKQogICAgICAgICAgICApLAogICAgICAgICAgIGNvdW50eCA8LSAxLAogICAgICAgICAgIGNvdW50eCA8LSBjb3VudHggKyAxCiAgICAgICAgICAgKQogICAgCiAgICBsZGFfbGlzdFtbY291bnR4XV0gPC0gbGlzdChuZ3JhbSA9IGksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b3BpYyA9IGosCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZGFfbW9kZWwgPSB4CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICB9CiAgfQoKc3RvcENsdXN0ZXIoY2x1c3RlcikKCnJtKG5ncmFtX2xpc3QsIHRvcGljX2d1ZXNzLCBpLCBqLCB4LCBjb3VudHgsIGNsdXN0ZXIpCgpsZGFfbGlzdAoKYGBgCiAgCiAgICAKICAgIENyZWF0aW5nIGEgZGF0YWZyYW1lIHdpdGggYGJldGFgIC0gdGhlIHBlci10b3BpYy1wZXItbmdyYW0gcHJvYmFiaWxpdHkgKGkuZS4sIHRoZSBwcm9iYWJpbGl0eSB0aGF0IGVhY2ggbmdyYW0gaXMgaW4gZWFjaCB0b3BpYykuCmBgYHtyfQoKUGVyVG9waWNQZXJOZ3JhbSA8LSBsaXN0KCkKCmZvcihpIGluIDE6bGVuZ3RoKGxkYV9saXN0KQogICAgKSB7CiAgeCA8LSB0aWR5KGxkYV9saXN0W1tpXV0kbGRhX21vZGVsLAogICAgICAgICAgICBtYXRyaXggPSAiYmV0YSIKICAgICAgICAgICAgKSAlPiUgCiAgICBhcnJhbmdlKHRlcm0sCiAgICAgICAgICAgIGRlc2MoYmV0YSkKICAgICAgICAgICAgKQogIAogIFBlclRvcGljUGVyTmdyYW1bW2ldXSA8LSBsaXN0KG5ncmFtID0gbGRhX2xpc3RbW2ldXSRuZ3JhbSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b3BpYyA9IGxkYV9saXN0W1tpXV0kdG9waWMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUGVyVG9waWNQZXJOZ3JhbSA9IHgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgfQoKcm0oaSwgeCkKCgpzdHIoUGVyVG9waWNQZXJOZ3JhbVtbNTVdXSRQZXJUb3BpY1Blck5ncmFtKQoKIyBybShzZXJ2X3JlcV9pZF9sZGEpCmhlYWQoUGVyVG9waWNQZXJOZ3JhbVtbNTVdXSRQZXJUb3BpY1Blck5ncmFtLCA1MDApCgpgYGAKICAKICAgIAogICAgQ3JlYXRpbmcgYSBkYXRhZnJhbWUgd2l0aCBqdXN0IHRoZSB0b3AgdGVuIHRlcm1zIChyYW5rZWQgYnkgYmV0YSkgaW4gZWFjaCB0b3BpYy4KYGBge3J9Cgp0b3BfdGVybXMgPC0gbGlzdCgpCgpmb3IoaSBpbiAxOmxlbmd0aChQZXJUb3BpY1Blck5ncmFtKQogICAgKSB7CiAgeCA8LSBQZXJUb3BpY1Blck5ncmFtW1tpXV0kUGVyVG9waWNQZXJOZ3JhbSAlPiUgCiAgICBncm91cF9ieSh0b3BpYykgJT4lIAogICAgdG9wX24oMTAsCiAgICAgICAgICBiZXRhCiAgICAgICAgICApICU+JSAKICAgIHVuZ3JvdXAoKSAlPiUgCiAgICBhcnJhbmdlKHRvcGljLAogICAgICAgICAgICAtYmV0YQogICAgICAgICAgICApCiAgCiAgdG9wX3Rlcm1zW1tpXV0gPC0gbGlzdChuZ3JhbSA9IFBlclRvcGljUGVyTmdyYW1bW2ldXSRuZ3JhbSwKICAgICAgICAgICAgICAgICAgICAgICAgIHRvcGljID0gUGVyVG9waWNQZXJOZ3JhbVtbaV1dJHRvcGljLAogICAgICAgICAgICAgICAgICAgICAgICAgdG9wX3Rlcm1zID0geAogICAgICAgICAgICAgICAgICAgICAgICAgKQogIH0KCnJtKGksIHgpCgoKdG9wX3Rlcm1zW1s1NV1dJHRvcF90ZXJtcwpWaWV3KHRvcF90ZXJtc1tbNTVdXSR0b3BfdGVybXMpCgpgYGAKICAKICAgIAogICAgTm93IHdlIGNhbiBwbG90IHRoZSB0b3AgMTAgbi1ncmFtcyBpbiBlYWNoIHRvcGljIHRvIHZpc3VhbGx5IGluc3BlY3QgaWYgdGhlIHRvcGljIGNsYXNzaWZpY2F0aW9ucyAibWFrZSBzZW5zZSIgYmFzZWQgb24gdGhlIG4tZ3JhbSB0ZXh0LiAgCiAgICAKICAgIEhlcmUsIHdlJ3JlIGp1c3QgY3JlYXRpbmcgYW5kIHNhdmluZyB0aGUgcGxvdHMgdGhlbXNlbHZlcy4KYGBge3J9CgpUb3BOZ3JhbXNfQnlUb3BpY19CYXJHcmFwaHMgPC0KICB0b3BfdGVybXMgJT4lCiAgIyB0b19ncmFwaCAlPiUKICBtYXAoZnVuY3Rpb24oeCkgCiAgICB4JHRvcF90ZXJtcyAlPiUKICAgICAgbXV0YXRlKHRlcm0gPSByZW9yZGVyKHRlcm0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZXRhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgdG9waWMgPSBwYXN0ZTAoIlRvcGljICIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJfcGFkKGFzLmNoYXJhY3Rlcih0b3BpYyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdpZHRoID0gMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2lkZSA9ICJsZWZ0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFkID0gIjAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICkgJT4lIAogICAgICBnZ3Bsb3QoYWVzKHggPSB0ZXJtLAogICAgICAgICAgICAgICAgIHkgPSBiZXRhLAogICAgICAgICAgICAgICAgIGZpbGwgPSBmYWN0b3IodG9waWMpCiAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgKSArCiAgICAgIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgZmFjZXRfd3JhcCh+IHRvcGljLAogICAgICAgICAgICAgICAgIHNjYWxlcyA9ICJmcmVlIiwKICAgICAgICAgICAgICAgICBuY29sID0gMgogICAgICAgICAgICAgICAgICkgKwogICAgICBnZ3Bsb3RfdGhlbWVfYmFzaWMgKwogICAgICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMSksCiAgICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSA5KQogICAgICAgICAgICApICsKICAgICAgbGFicyh0aXRsZSA9ICJNb3N0IENvbW1vbiBUZXJtcyBQZXIgVG9waWMiLAogICAgICAgICAgIHN1YnRpdGxlID0gcGFzdGUwKCIoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJfcGFkKHgkbmdyYW0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWR0aCA9IDIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaWRlID0gImxlZnQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFkID0gIjAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJncmFtIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJfcGFkKHgkdG9waWMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWR0aCA9IDIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaWRlID0gImxlZnQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFkID0gIjAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidG9waWMpIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgeCA9IHBhc3RlMChzdHJfcGFkKHgkbmdyYW0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdpZHRoID0gMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2lkZSA9ICJsZWZ0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFkID0gIjAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgICAiZ3JhbSIKICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgeSA9IHBhc3RlMCgicHJvYmFiaWxpdHkgb2YgdGhlICIsCiAgICAgICAgICAgICAgICAgICAgICBzdHJfcGFkKHgkbmdyYW0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdpZHRoID0gMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2lkZSA9ICJsZWZ0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFkID0gIjAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgICAiZ3JhbSBpbiB0aGUgdG9waWMiCiAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgKSArCiAgICAgIGNvb3JkX2ZsaXAoKQogICAgKQoKIyBUb3BOZ3JhbXNfQnlUb3BpY19CYXJHcmFwaHMKVG9wTmdyYW1zX0J5VG9waWNfQmFyR3JhcGhzW1syNF1dICMgbmdyYW0gPSAzICYgdG9waWNzID0gMwpUb3BOZ3JhbXNfQnlUb3BpY19CYXJHcmFwaHNbWzI1XV0gIyBuZ3JhbSA9IDMgJiB0b3BpY3MgPSA0ClRvcE5ncmFtc19CeVRvcGljX0JhckdyYXBoc1tbMzVdXSAjIG5ncmFtID0gNCAmIHRvcGljcyA9IDMKVG9wTmdyYW1zX0J5VG9waWNfQmFyR3JhcGhzW1szNl1dICMgbmdyYW0gPSA0ICYgdG9waWNzID0gNApUb3BOZ3JhbXNfQnlUb3BpY19CYXJHcmFwaHNbWzQ2XV0gIyBuZ3JhbSA9IDUgJiB0b3BpY3MgPSAzClRvcE5ncmFtc19CeVRvcGljX0JhckdyYXBoc1tbNDddXSAjIG5ncmFtID0gNSAmIHRvcGljcyA9IDQKCgpUb3BOZ3JhbXNfQnlUb3BpY19CYXJHcmFwaHMgJT4lIAogIG1hcChmdW5jdGlvbih4KQogICAgZ2dzYXZlKHBhc3RlMCh3ZCwKICAgICAgICAgICAgICAgICAgIi9WaXovIiwKICAgICAgICAgICAgICAgICAgIyAiTmV3XyIsCiAgICAgICAgICAgICAgICAgIHN1YnN0cih4JGxhYmVscyRzdWJ0aXRsZSwKICAgICAgICAgICAgICAgICAgICAgICAgIDIsCiAgICAgICAgICAgICAgICAgICAgICAgICAobmNoYXIoeCRsYWJlbHMkc3VidGl0bGUpIC0gMSkKICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgIl9Ub3AxMFRlcm1zX2ZhY2V0LnBuZyIKICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICB4LAogICAgICAgICAgIHNjYWxlID0gNCwKICAgICAgICAgICB3aWR0aCA9IDYsCiAgICAgICAgICAgaGVpZ2h0ID0gNiwKICAgICAgICAgICB1bml0cyA9ICJjbSIKICAgICAgICAgICApCiAgICApCgpgYGAKICAKICAgIAogICAgQ3JlYXRpbmcgYSBkYXRhZnJhbWUgd2l0aCBgZ2FtbWFgIC0gdGhlIHBlci1kb2N1bWVudC1wZXItdG9waWMgcHJvYmFiaWxpdHkgKGkuZS4sIHRoZSBwcm9iYWJpbGl0eSB0aGF0IGVhY2ggZG9jdW1lbnQgKHNlcnZfcmVxX2lkKSBpcyBpbiBlYWNoIHRvcGljKS4gIAogICAgCiAgICBJIGNob3NlIHRvIGRvIHRoaXMgZm9yIHNpeCBkaWZmZXJlbnQgY29tYmluYXRpb25zIG9mIG5ncmFtcyBhbmQgdG9waWNzIChuZ3JhbSA9IDMgJiB0b3BpYyA9IDMsIDMgJiA0LCA0ICYgMywgNCAmIDQsIDUgJiAzLCA1ICYgNCkuIFRoaXMgd2FzIGNob3NlbiBpbiBwYXJ0IGJlY2FzdWUgYWZ0ZXIgbG9va2luZyBhdCB0aGUgZ3JhcGhzIHByb2R1Y2VkIGFib3ZlLCB0aGVzZSBtb2RlbHMgc2VlbWVkIChieSB2aXN1YWwgaW5zcGVjdGlvbikgdG8gcGVyZm9ybSBiZXR0ZXIuIEl0IHdhcyBhbHNvIGRvbmUgaW4gcGFydCBiZWNhc3VlIGEgcG9ydGlvbiBvZiB0aGUgYW5hbHlzZXMgYmVsb3cgcmVxdXJpZXMgZGVmaW5pbmcgdGhlIHRvcGljcyBhcyBgdW5rbm93bmAsIGBub19yYXRzX2ZvdW5kYCwgb3IgYHJhdHNfZm91bmRgIGJ5IHZpc3VhbCBpbnNwZWN0aW9uIG9mIHRoZSBncmFwaHMgcHJvZHVjZWQgYWJvdmUuIFNpeCBhbHNvIHNlZW1lZCB0byBiZSBhIGdvb2QgbWVkaXVtIGJldHdlZW4gdG9vIGZldyBhbmQgdG9vIG1hbnkgdmlzdWFsIGluc3BlY3Rpb25zIHRvIGRvLgpgYGB7cn0KCnJtKFRvcE5ncmFtc19CeVRvcGljX0JhckdyYXBocykKCgp0b3BfdGVybXNbWzU1XV0gI2xkYSBtb2RlbCB3aXRoIG5ncmFtID0gNSAmIHRvcGljcyA9IDEyCnRvcF90ZXJtc1tbMl1dICNsZGEgbW9kZWwgd2l0aCBuZ3JhbSA9IDEgJiB0b3BpY3MgPSAzCnRvcF90ZXJtc1tbM11dICNsZGEgbW9kZWwgd2l0aCBuZ3JhbSA9IDEgJiB0b3BpY3MgPSA0Cgp0b3BfdGVybXNbWzI0XV0gI2xkYSBtb2RlbCB3aXRoIG5ncmFtID0gMyAmIHRvcGljcyA9IDMKdG9wX3Rlcm1zW1syNV1dICNsZGEgbW9kZWwgd2l0aCBuZ3JhbSA9IDMgJiB0b3BpY3MgPSA0CnRvcF90ZXJtc1tbMzVdXSAjbGRhIG1vZGVsIHdpdGggbmdyYW0gPSA0ICYgdG9waWNzID0gMwp0b3BfdGVybXNbWzM2XV0gI2xkYSBtb2RlbCB3aXRoIG5ncmFtID0gNCAmIHRvcGljcyA9IDQKdG9wX3Rlcm1zW1s0Nl1dICNsZGEgbW9kZWwgd2l0aCBuZ3JhbSA9IDUgJiB0b3BpY3MgPSAzCnRvcF90ZXJtc1tbNDddXSAjbGRhIG1vZGVsIHdpdGggbmdyYW0gPSA1ICYgdG9waWNzID0gNAoKClByb2JEb2NJblRvcGljX25ncmFtMDNfdG9waWMwMyA8LQogIGxpc3QobmdyYW0gPSBsZGFfbGlzdFtbMjRdXSRuZ3JhbSwKICAgICAgIHRvcGljID0gbGRhX2xpc3RbWzI0XV0kdG9waWMsCiAgICAgICBkYXRhID0gdGlkeShsZGFfbGlzdFtbMjRdXSRsZGFfbW9kZWwsCiAgICAgICAgICAgICAgICAgICBtYXRyaXggPSAiZ2FtbWEiCiAgICAgICAgICAgICAgICAgICApICU+JSAKICAgICAgICAgYXJyYW5nZShkb2N1bWVudCwKICAgICAgICAgICAgICAgICBkZXNjKGdhbW1hKQogICAgICAgICAgICAgICAgICkgJT4lIAogICAgICAgICBtdXRhdGUodG9waWNfbmFtZSA9IGNhc2Vfd2hlbih0b3BpYyAlaW4lIGMoMSkgfiAidW5rbm93biIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvcGljICVpbiUgYygyKSB+ICJub19yYXRzX2ZvdW5kIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG9waWMgJWluJSBjKDMpIH4gInJhdHNfZm91bmQiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICBtb2RlbF9uZ3JhbSA9IGxkYV9saXN0W1syNF1dJG5ncmFtLAogICAgICAgICAgICAgICAgbW9kZWxfdG9waWMgPSBsZGFfbGlzdFtbMjRdXSR0b3BpYwogICAgICAgICAgICAgICAgKQogICAgICAgKQoKClByb2JEb2NJblRvcGljX25ncmFtMDNfdG9waWMwNCA8LQogIGxpc3QobmdyYW0gPSBsZGFfbGlzdFtbMjVdXSRuZ3JhbSwKICAgICAgIHRvcGljID0gbGRhX2xpc3RbWzI1XV0kdG9waWMsCiAgICAgICBkYXRhID0gdGlkeShsZGFfbGlzdFtbMjVdXSRsZGFfbW9kZWwsCiAgICAgICAgICAgICAgICAgICBtYXRyaXggPSAiZ2FtbWEiCiAgICAgICAgICAgICAgICAgICApICU+JSAKICAgICAgICAgYXJyYW5nZShkb2N1bWVudCwKICAgICAgICAgICAgICAgICBkZXNjKGdhbW1hKQogICAgICAgICAgICAgICAgICkgJT4lIAogICAgICAgICBtdXRhdGUodG9waWNfbmFtZSA9IGNhc2Vfd2hlbih0b3BpYyAlaW4lIGMoMSkgfiAidW5rbm93biIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvcGljICVpbiUgYygyKSB+ICJub19yYXRzX2ZvdW5kIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG9waWMgJWluJSBjKDMsIDQpIH4gInJhdHNfZm91bmQiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICBtb2RlbF9uZ3JhbSA9IGxkYV9saXN0W1syNV1dJG5ncmFtLAogICAgICAgICAgICAgICAgbW9kZWxfdG9waWMgPSBsZGFfbGlzdFtbMjVdXSR0b3BpYwogICAgICAgICAgICAgICAgKQogICAgICAgKQoKClByb2JEb2NJblRvcGljX25ncmFtMDRfdG9waWMwMyA8LQogIGxpc3QobmdyYW0gPSBsZGFfbGlzdFtbMzVdXSRuZ3JhbSwKICAgICAgIHRvcGljID0gbGRhX2xpc3RbWzM1XV0kdG9waWMsCiAgICAgICBkYXRhID0gdGlkeShsZGFfbGlzdFtbMzVdXSRsZGFfbW9kZWwsCiAgICAgICAgICAgICAgICAgICBtYXRyaXggPSAiZ2FtbWEiCiAgICAgICAgICAgICAgICAgICApICU+JSAKICAgICAgICAgYXJyYW5nZShkb2N1bWVudCwKICAgICAgICAgICAgICAgICBkZXNjKGdhbW1hKQogICAgICAgICAgICAgICAgICkgJT4lIAogICAgICAgICBtdXRhdGUodG9waWNfbmFtZSA9IGNhc2Vfd2hlbigjdG9waWMgJWluJSBjKCkgfiAidW5rbm93biIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvcGljICVpbiUgYygxKSB+ICJub19yYXRzX2ZvdW5kIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG9waWMgJWluJSBjKDIsIDMpIH4gInJhdHNfZm91bmQiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICBtb2RlbF9uZ3JhbSA9IGxkYV9saXN0W1szNV1dJG5ncmFtLAogICAgICAgICAgICAgICAgbW9kZWxfdG9waWMgPSBsZGFfbGlzdFtbMzVdXSR0b3BpYwogICAgICAgICAgICAgICAgKQogICAgICAgKQoKClByb2JEb2NJblRvcGljX25ncmFtMDRfdG9waWMwNCA8LQogIGxpc3QobmdyYW0gPSBsZGFfbGlzdFtbMzZdXSRuZ3JhbSwKICAgICAgIHRvcGljID0gbGRhX2xpc3RbWzM2XV0kdG9waWMsCiAgICAgICBkYXRhID0gdGlkeShsZGFfbGlzdFtbMzZdXSRsZGFfbW9kZWwsCiAgICAgICAgICAgICAgICAgICBtYXRyaXggPSAiZ2FtbWEiCiAgICAgICAgICAgICAgICAgICApICU+JSAKICAgICAgICAgYXJyYW5nZShkb2N1bWVudCwKICAgICAgICAgICAgICAgICBkZXNjKGdhbW1hKQogICAgICAgICAgICAgICAgICkgJT4lIAogICAgICAgICBtdXRhdGUodG9waWNfbmFtZSA9IGNhc2Vfd2hlbih0b3BpYyAlaW4lIGMoNCkgfiAidW5rbm93biIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvcGljICVpbiUgYygxKSB+ICJub19yYXRzX2ZvdW5kIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG9waWMgJWluJSBjKDIsIDMpIH4gInJhdHNfZm91bmQiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICBtb2RlbF9uZ3JhbSA9IGxkYV9saXN0W1szNl1dJG5ncmFtLAogICAgICAgICAgICAgICAgbW9kZWxfdG9waWMgPSBsZGFfbGlzdFtbMzZdXSR0b3BpYwogICAgICAgICAgICAgICAgKQogICAgICAgKQoKClByb2JEb2NJblRvcGljX25ncmFtMDVfdG9waWMwMyA8LQogIGxpc3QobmdyYW0gPSBsZGFfbGlzdFtbNDZdXSRuZ3JhbSwKICAgICAgIHRvcGljID0gbGRhX2xpc3RbWzQ2XV0kdG9waWMsCiAgICAgICBkYXRhID0gdGlkeShsZGFfbGlzdFtbNDZdXSRsZGFfbW9kZWwsCiAgICAgICAgICAgICAgICAgICBtYXRyaXggPSAiZ2FtbWEiCiAgICAgICAgICAgICAgICAgICApICU+JSAKICAgICAgICAgYXJyYW5nZShkb2N1bWVudCwKICAgICAgICAgICAgICAgICBkZXNjKGdhbW1hKQogICAgICAgICAgICAgICAgICkgJT4lIAogICAgICAgICBtdXRhdGUodG9waWNfbmFtZSA9IGNhc2Vfd2hlbih0b3BpYyAlaW4lIGMoMikgfiAidW5rbm93biIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvcGljICVpbiUgYygzKSB+ICJub19yYXRzX2ZvdW5kIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG9waWMgJWluJSBjKDEpIH4gInJhdHNfZm91bmQiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICBtb2RlbF9uZ3JhbSA9IGxkYV9saXN0W1s0Nl1dJG5ncmFtLAogICAgICAgICAgICAgICAgbW9kZWxfdG9waWMgPSBsZGFfbGlzdFtbNDZdXSR0b3BpYwogICAgICAgICAgICAgICAgKQogICAgICAgKQoKClByb2JEb2NJblRvcGljX25ncmFtMDVfdG9waWMwNCA8LQogIGxpc3QobmdyYW0gPSBsZGFfbGlzdFtbNDddXSRuZ3JhbSwKICAgICAgIHRvcGljID0gbGRhX2xpc3RbWzQ3XV0kdG9waWMsCiAgICAgICBkYXRhID0gdGlkeShsZGFfbGlzdFtbNDddXSRsZGFfbW9kZWwsCiAgICAgICAgICAgICAgICAgICBtYXRyaXggPSAiZ2FtbWEiCiAgICAgICAgICAgICAgICAgICApICU+JSAKICAgICAgICAgYXJyYW5nZShkb2N1bWVudCwKICAgICAgICAgICAgICAgICBkZXNjKGdhbW1hKQogICAgICAgICAgICAgICAgICkgJT4lIAogICAgICAgICBtdXRhdGUodG9waWNfbmFtZSA9IGNhc2Vfd2hlbih0b3BpYyAlaW4lIGMoNCkgfiAidW5rbm93biIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvcGljICVpbiUgYygzKSB+ICJub19yYXRzX2ZvdW5kIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG9waWMgJWluJSBjKDEsIDIpIH4gInJhdHNfZm91bmQiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICBtb2RlbF9uZ3JhbSA9IGxkYV9saXN0W1s0N11dJG5ncmFtLAogICAgICAgICAgICAgICAgbW9kZWxfdG9waWMgPSBsZGFfbGlzdFtbNDddXSR0b3BpYwogICAgICAgICAgICAgICAgKQogICAgICAgKQoKYGBgCiAgCiAgICAKICAgIEhlcmUsIHdlIHB1dCB0aGUgc2l4IGluZGl2aWR1YWwgUHJvYkRvY0luVG9waWMgbW9kZWxzIHRvZ2V0aGVyLCBhbmQgYWRkIGluIHNvbWUgb2YgdGhlIG9yaWdpbmFsIGluZm9ybWF0aW9uIChlLmcuLCB0aGUgb3JpZ2luYWwgYHNlcnZpY2Vub3Rlc2ApIGZvciBjb250ZXh0LgpgYGB7cn0KClByb2JEb2NJblRvcGljX0FsbE1vZGVscyA8LQogIGJpbmRfcm93cyhQcm9iRG9jSW5Ub3BpY19uZ3JhbTAzX3RvcGljMDNbWzNdXSwKICAgICAgICAgICAgUHJvYkRvY0luVG9waWNfbmdyYW0wM190b3BpYzA0W1szXV0sCiAgICAgICAgICAgIFByb2JEb2NJblRvcGljX25ncmFtMDRfdG9waWMwM1tbM11dLAogICAgICAgICAgICBQcm9iRG9jSW5Ub3BpY19uZ3JhbTA0X3RvcGljMDRbWzNdXSwKICAgICAgICAgICAgUHJvYkRvY0luVG9waWNfbmdyYW0wNV90b3BpYzAzW1szXV0sCiAgICAgICAgICAgIFByb2JEb2NJblRvcGljX25ncmFtMDVfdG9waWMwNFtbM11dCiAgICAgICAgICAgICkgJT4lIAogIGFycmFuZ2UoZG9jdW1lbnQsCiAgICAgICAgICBtb2RlbF9uZ3JhbSwKICAgICAgICAgIG1vZGVsX3RvcGljLAogICAgICAgICAgZ2FtbWEKICAgICAgICAgICkgJT4lIAogIHJlbmFtZSgic2VydmljZXJlcXVlc3RpZCIgPSAiZG9jdW1lbnQiKSAlPiUgCiAgbGVmdF9qb2luKHNlbGVjdChTZXJ2aWNlTm90ZXNDbGVhbmVkLAogICAgICAgICAgICAgICAgICAgc2VydmljZXJlcXVlc3RpZCwKICAgICAgICAgICAgICAgICAgIHNlcnZpY2Vub3RlcywKICAgICAgICAgICAgICAgICAgIHNlcnZpY2VvcmRlcmRhdGUKICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgIGJ5ID0gYygic2VydmljZXJlcXVlc3RpZCIgPSAic2VydmljZXJlcXVlc3RpZCIpCiAgICAgICAgICAgICkKCnJtKGxpc3QgPSBscyhwYXR0ZXJuID0gIlByb2JEb2NJblRvcGljX25ncmFtIikpCgpzdHIoUHJvYkRvY0luVG9waWNfQWxsTW9kZWxzKQpoZWFkKFByb2JEb2NJblRvcGljX0FsbE1vZGVscywgMTAwKQpWaWV3KGhlYWQoUHJvYkRvY0luVG9waWNfQWxsTW9kZWxzLCAxMDAwKSkKCmBgYAogIAogICAgCiAgICBSZW1vdmUgdGhlIGRhdGFmaWxlcyB0aGF0IGFyZSBubyBsb25nZXIgbmVlZGVkLgpgYGB7cn0KCnJtKGxpc3QgPSBscyhwYXR0ZXJuID0gIl9saXN0IikpCnJtKFBlclRvcGljUGVyTmdyYW0sIHRvcF90ZXJtcykKCmBgYAogIAogICAgCiAgICBOZXh0LCBmb3IgZWFjaCBtb2RlbCAoZS5nLiwgMy1ncmFtIDQtdG9waWMpLCB3ZSBzdW0gdGhlIHByb2JhYmlsaXRpZXMgZ2l2ZW4gZm9yIGVhY2ggbnVtZXJpYyB0b3BpYywgaW50byB0aGUgInJhdHMgdG9waWNzIiAoZS5nLiwgYHJhdHNfZm91bmRgKSB3aGljaCB3ZXJlIGRlZmluZWQgYWJvdmUgdmlhIHZpc3VhbCBpbnNwZWN0aW9uIG9mIHRoZSBncmFwaHMgb24gdGhlIFRvcCAxMCBuZ3JhbXMgaW4gZWFjaCBudW1lcmljIHRvcGljLiAgCiAgICAKICAgIEkgYWxzbyBwdWxsIG91dCB0aGUgNS1ncmFtIDQtdG9waWMgbW9kZWwgYmVjYXVzZSBpdCBhcHBlYXJlZCAodmlzdWFsbHkpIHRvIGJlIHRoZSBtb3N0IGFjY3VyYXRlIGluZGl2aWR1YWwgbW9kZWwuCmBgYHtyfQoKUHJvYkRvY0luVG9waWNfUHJvYnNTdW1tZWRfQnlNb2RlbCA8LQogIFByb2JEb2NJblRvcGljX0FsbE1vZGVscyAlPiUgCiAgZ3JvdXBfYnkoc2VydmljZXJlcXVlc3RpZCwKICAgICAgICAgICBtb2RlbF9uZ3JhbSwKICAgICAgICAgICBtb2RlbF90b3BpYywKICAgICAgICAgICB0b3BpY19uYW1lCiAgICAgICAgICAgKSAlPiUgCiAgc3VtbWFyaXNlKHByb2JfID0gc3VtKGdhbW1hKQogICAgICAgICAgICApICU+JSAKICB1bmdyb3VwKCkgJT4lIAogIGxlZnRfam9pbihzZWxlY3QoU2VydmljZU5vdGVzQ2xlYW5lZCwKICAgICAgICAgICAgICAgICAgIHNlcnZpY2VyZXF1ZXN0aWQsCiAgICAgICAgICAgICAgICAgICBzZXJ2aWNlbm90ZXMsCiAgICAgICAgICAgICAgICAgICBzZXJ2aWNlb3JkZXJkYXRlCiAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICBieSA9IGMoInNlcnZpY2VyZXF1ZXN0aWQiID0gInNlcnZpY2VyZXF1ZXN0aWQiKQogICAgICAgICAgICApCgpzdHIoUHJvYkRvY0luVG9waWNfUHJvYnNTdW1tZWRfQnlNb2RlbCkKaGVhZChQcm9iRG9jSW5Ub3BpY19Qcm9ic1N1bW1lZF9CeU1vZGVsLCAxMDApClZpZXcoaGVhZChQcm9iRG9jSW5Ub3BpY19Qcm9ic1N1bW1lZF9CeU1vZGVsLCAxMDAwKSkKCgoKUHJvYkRvY0luVG9waWNfUHJvYnNTdW1tZWRfMDVncmFtMDR0b3BpYyA8LQogIFByb2JEb2NJblRvcGljX1Byb2JzU3VtbWVkX0J5TW9kZWwgJT4lIAogIGZpbHRlcihtb2RlbF9uZ3JhbSA9PSA1ICYKICAgICAgICAgICBtb2RlbF90b3BpYyA9PSA0KQoKc3RyKFByb2JEb2NJblRvcGljX1Byb2JzU3VtbWVkXzA1Z3JhbTA0dG9waWMpCmhlYWQoUHJvYkRvY0luVG9waWNfUHJvYnNTdW1tZWRfMDVncmFtMDR0b3BpYywgMTAwKQpWaWV3KGhlYWQoUHJvYkRvY0luVG9waWNfUHJvYnNTdW1tZWRfMDVncmFtMDR0b3BpYywgMTAwMCkpCgpgYGAKICAKICAgIAogICAgTmV4dCwgZm9yIGVhY2ggbmdyYW0tdG9waWMgY29tYmluYXRpb24sIEkgY3JlYXRlIGhpc3RvZ3JhbXMgb2YgdGhlIHByb2JhYmlsaXRpZXMgYXNzaWduZWQgdG8gZWFjaCB0b3BpYy4gIFRoaXMgaXMgZG9uZSB0byBoZWxwIHZpc3VhbHkgZGV0ZXJtaW5lIGlmIHRoZSB0b3BpYyBhc3NpZ25tZW50cyBhcmUgY2xlYXJseSBzZXBhcmF0aW5nIGRvY3VtZW50cyAoc2Vydl9yZXF1ZXN0X2lkIHZhbHVlcykuICAKICAgIAogICAgQSBsb2cxMCB0cmFuc2Zvcm1hdGlvbiBvZiB0aGUgcHJvYmFiaWxpdHkgaXMgZG9uZSB0byBoZWxwIG1vcmUgY2xlYXJseSBzZWUgYW55IGRpZmZlcmVuY2VzLgpgYGB7cn0KCiMgc3RyKFByb2JEb2NJblRvcGljX1Byb2JzU3VtbWVkX0J5TW9kZWwpCgpQcm9iRG9jSW5Ub3BpY19Qcm9ic1N1bW1lZF9CeU1vZGVsX0RldGFpbHMgPC0KICBQcm9iRG9jSW5Ub3BpY19Qcm9ic1N1bW1lZF9CeU1vZGVsICU+JSAKICBtdXRhdGUobW9kZWwgPSBwYXN0ZTAoIjAiLAogICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9uZ3JhbSwKICAgICAgICAgICAgICAgICAgICAgICAgImdyYW1fIiwKICAgICAgICAgICAgICAgICAgICAgICAgIjAiLAogICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF90b3BpYywKICAgICAgICAgICAgICAgICAgICAgICAgInRvcGljIgogICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICBzZXJ2aWNlb3JkZXJfeXIgPSB5ZWFyKHNlcnZpY2VvcmRlcmRhdGUpLAogICAgICAgICBtb2RlbF9hbmRfeXIgPSBwYXN0ZTAobW9kZWwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiXyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5jaGFyYWN0ZXIoc2VydmljZW9yZGVyX3lyKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICApCgpoZWFkKFByb2JEb2NJblRvcGljX1Byb2JzU3VtbWVkX0J5TW9kZWxfRGV0YWlscywgMTAwKQpWaWV3KGhlYWQoUHJvYkRvY0luVG9waWNfUHJvYnNTdW1tZWRfQnlNb2RlbF9EZXRhaWxzLCAxMDAwKSkKCgoKVG9waWNEaXN0cm9fTWFpbk1vZGVsc19IaXN0b2dyYW1fQnlNb2RlbCA8LQogIFByb2JEb2NJblRvcGljX1Byb2JzU3VtbWVkX0J5TW9kZWxfRGV0YWlscyAlPiUgCiAgc3BsaXQoLiRtb2RlbCkgJT4lCiAgbWFwKH4gZ2dwbG90KGRhdGEgPSAueCwKICAgICAgICAgICAgICAgYWVzKHggPSBwcm9iXywKICAgICAgICAgICAgICAgICAgIGZpbGwgPSB0b3BpY19uYW1lCiAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICkgKwogICAgICAgIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC4wNSkgKwogICAgICAgIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDEpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICkgKwogICAgICAgIHNjYWxlX3lfbG9nMTAoKSArICMgdGhpcyB0cmFuc2Zvcm1hdGlvbiBpcyB1c2VkIHRvIGhlbHAgbW9yZSBjbGVhcmx5IHNlZSBhbnkgZGlmZmVyZW5jZXMgaW4gdGhlIHByb2JhYmlsaXR5IHZhbHVlcwogICAgICAgIGdndGl0bGUobGFiZWwgPSBwYXN0ZTAoLngkbW9kZWwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiX0hpc3RvZ3JhbSIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICkgKwogICAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogICAgICAgIGxhYnMoeCA9ICJQcm9iIG9mIFNlcnZpY2VSZXF1ZXN0SWQgaW4gdGhlIFRvcGljIiwKICAgICAgICAgICAgIHkgPSAibG9nMTAgb2YgY291bnRzIgogICAgICAgICAgICAgKSArCiAgICAgICAgZmFjZXRfd3JhcCh+dG9waWNfbmFtZSkKICAgICAgKQoKCgpUb3BpY0Rpc3Ryb19NYWluTW9kZWxzX0hpc3RvZ3JhbV9CeU1vZGVsWXIgPC0KICBQcm9iRG9jSW5Ub3BpY19Qcm9ic1N1bW1lZF9CeU1vZGVsX0RldGFpbHMgJT4lIAogIHNwbGl0KC4kbW9kZWxfYW5kX3lyKSAlPiUgCiAgbWFwKH4gZ2dwbG90KGRhdGEgPSAueCwKICAgICAgICAgICAgICAgYWVzKHggPSBwcm9iXywKICAgICAgICAgICAgICAgICAgIGZpbGwgPSB0b3BpY19uYW1lCiAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICkgKwogICAgICAgIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC4wNSkgKwogICAgICAgIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDEpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICkgKwogICAgICAgIHNjYWxlX3lfbG9nMTAoKSArICMgdGhpcyB0cmFuc2Zvcm1hdGlvbiBpcyB1c2VkIHRvIGhlbHAgbW9yZSBjbGVhcmx5IHNlZSBhbnkgZGlmZmVyZW5jZXMgaW4gdGhlIHByb2JhYmlsaXR5IHZhbHVlcwogICAgICAgIGdndGl0bGUobGFiZWwgPSBwYXN0ZTAoLngkbW9kZWxfYW5kX3lyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIl9IaXN0b2dyYW0iCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICApICsKICAgICAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICAgICAgICBsYWJzKHggPSAiUHJvYiBvZiBTZXJ2aWNlUmVxdWVzdElkIGluIHRoZSBUb3BpYyIsCiAgICAgICAgICAgICB5ID0gImxvZzEwIG9mIGNvdW50cyIKICAgICAgICAgICAgICkgKwogICAgICAgIGZhY2V0X3dyYXAofnRvcGljX25hbWUpCiAgICAgICkKCmBgYAogIAogICAgCiAgICBTYXZpbmcgdGhlIGhpc3RvZ3JhbXMuCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9RkFMU0UsIHBhZ2VkLnByaW50PUZBTFNFfQoKVG9waWNEaXN0cm9fTWFpbk1vZGVsc19IaXN0b2dyYW1fQnlNb2RlbCAlPiUKICBtYXAofiBnZ3NhdmUocGFzdGUwKHdkLAogICAgICAgICAgICAgICAgICAgICAgIi9WaXovIiwKICAgICAgICAgICAgICAgICAgICAgICMgIk5ld18iLAogICAgICAgICAgICAgICAgICAgICAgIlRvcGljRGlzdHJvX01haW5Nb2RlbHNfQnlNb2RlbF8iLAogICAgICAgICAgICAgICAgICAgICAgLngkbGFiZWxzJHRpdGxlW1sxXV0sCiAgICAgICAgICAgICAgICAgICAgICAiLnBuZyIKICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgIC54LAogICAgICAgICAgICAgICBzY2FsZSA9IDQsCiAgICAgICAgICAgICAgIHdpZHRoID0gNiwKICAgICAgICAgICAgICAgaGVpZ2h0ID0gNCwKICAgICAgICAgICAgICAgdW5pdHMgPSAiY20iCiAgICAgICAgICAgICAgICkKICAgICAgKQoKCgpUb3BpY0Rpc3Ryb19NYWluTW9kZWxzX0hpc3RvZ3JhbV9CeU1vZGVsWXIgJT4lIAogIG1hcCh+IGdnc2F2ZShwYXN0ZTAod2QsCiAgICAgICAgICAgICAgICAgICAgICAiL1Zpei8iLAogICAgICAgICAgICAgICAgICAgICAgIyAiTmV3XyIsCiAgICAgICAgICAgICAgICAgICAgICAiVG9waWNEaXN0cm9fTWFpbk1vZGVsc19CeU1vZGVsWXJfIiwKICAgICAgICAgICAgICAgICAgICAgIC54JGxhYmVscyR0aXRsZVtbMV1dLAogICAgICAgICAgICAgICAgICAgICAgIi5wbmciCiAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAueCwKICAgICAgICAgICAgICAgc2NhbGUgPSA0LAogICAgICAgICAgICAgICB3aWR0aCA9IDYsCiAgICAgICAgICAgICAgIGhlaWdodCA9IDQsCiAgICAgICAgICAgICAgIHVuaXRzID0gImNtIgogICAgICAgICAgICAgICApCiAgICAgICkKCmBgYAogIAogICAgCiAgICBSZW1vdmluZyB1bm5lZWRlZCBmaWxlcy4KYGBge3J9CgpybShsaXN0ID0gbHMocGF0dGVybiA9ICJUb3BpY0Rpc3Ryb18iKSkKCmBgYAogIAogICAgCiAgICBDb21wYXJpbmcgaG93IHRoZSBoaXN0b2dyYW1zIGxvb2sgd2l0aCBhIHJlZ3VsYXIgeS1zY2FsZSB2cyBhIGxvZzEwIHktc2NhbGUuCmBgYHtyfQoKUHJvYkRvY0luVG9waWNfUHJvYnNTdW1tZWRfQnlNb2RlbF9EZXRhaWxzICU+JSAKICBzcGxpdCguJG1vZGVsKSAlPiUKICBtYXAofiBnZ3Bsb3QoZGF0YSA9IC54LAogICAgICAgICAgICAgICBhZXMoeCA9IHByb2JfLAogICAgICAgICAgICAgICAgICAgZmlsbCA9IHRvcGljX25hbWUKICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgKSArCiAgICAgICAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjA1KSArCiAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgMSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgKSArCiAgICAgICAgc2NhbGVfeV9sb2cxMCgpICsKICAgICAgICBnZ3RpdGxlKGxhYmVsID0gcGFzdGUwKC54JG1vZGVsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIl9IaXN0b2dyYW0iCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICApICsKICAgICAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICAgICAgICBsYWJzKHggPSAiUHJvYiBvZiBTZXJ2aWNlUmVxdWVzdElkIGluIHRoZSBUb3BpYyIsCiAgICAgICAgICAgICB5ID0gImxvZzEwIG9mIGNvdW50cyIKICAgICAgICAgICAgICkgKwogICAgICAgIGZhY2V0X3dyYXAofnRvcGljX25hbWUsCiAgICAgICAgICAgICAgICAgICBzY2FsZXMgPSAiZml4ZWQiCiAgICAgICAgICAgICAgICAgICApCiAgICAgICkKCgpQcm9iRG9jSW5Ub3BpY19Qcm9ic1N1bW1lZF9CeU1vZGVsX0RldGFpbHMgJT4lIAogIHNwbGl0KC4kbW9kZWwpICU+JQogIG1hcCh+IGdncGxvdChkYXRhID0gLngsCiAgICAgICAgICAgICAgIGFlcyh4ID0gcHJvYl8sCiAgICAgICAgICAgICAgICAgICBmaWxsID0gdG9waWNfbmFtZQogICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICApICsKICAgICAgICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDAuMDUpICsKICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCAxKQogICAgICAgICAgICAgICAgICAgICAgICAgICApICsKICAgICAgICAjIHNjYWxlX3lfbG9nMTAoKSArCiAgICAgICAgZ2d0aXRsZShsYWJlbCA9IHBhc3RlMCgueCRtb2RlbCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJfSGlzdG9ncmFtIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgKSArCiAgICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgICAgICAgbGFicyh4ID0gIlByb2Igb2YgU2VydmljZVJlcXVlc3RJZCBpbiB0aGUgVG9waWMiLAogICAgICAgICAgICAgeSA9ICJjb3VudHMiCiAgICAgICAgICAgICApICsKICAgICAgICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMCwgNTAwMCkKICAgICAgICAgICAgICAgICAgICAgICAgKSArCiAgICAgICAgZmFjZXRfd3JhcCh+dG9waWNfbmFtZSwKICAgICAgICAgICAgICAgICAgIHNjYWxlcyA9ICJmaXhlZCIKICAgICAgICAgICAgICAgICAgICkKICAgICAgKQoKYGBgCiAgCiAgICAKICAgIFRoZW4sIGZvciBlYWNoIGBzZXJ2aWNlcmVxdWVzdGlkYCBhbmQgYHRvcGljX25hbWVgIHdlIGNhbGN1bGF0ZSB0aGUgbWVhbiB0b3BpYyBwcm9iYWJpbGl0eSBhY3Jvc3MgYWxsIHRoZSBtb2RlbHMuIE5PVEU6IFRoaXMgc3RlcCBjb3VsZCBiZSBtb2RpZmllZCBtb3JlIGFzIG15IGluc3RpbmN0IGlzIHRoYXQgbW9yZSB3ZWlnaHQgc2hvdWxkIHByb2JhYmx5IGJlIGdpdmVuIHRvIHRoZSBtb2RlbHMgd2l0aCBsYXJnZXIgbmdyYW1zIGFuZCB0b3BpY3MgKGUuZy4sIHRoZSA1LWdyYW0gJiA0LXRvcGljIG1vZGVsKS4gSG93ZXZlciwgdXNpbmcgbGFyZ2VyIHZhbHVlcyBvZiBuLWdyYW1zIHdpbGwgbm90IGJlIGFibGUgdG8gYW5hbHl6ZSB0aG9zZSByZWNvcmRzIHRoYXQgZG8gbm90IGhhdmUgYXQgbGVhc3QgYG5gIHdvcmRzLiBNZWFuaW5nIHRoYXQgc21hbGxlciB2YWx1ZXMgb2Ygbi1ncmFtcyBjYW4gYW5hbHl6ZSBtb3JlIGRvY3VtZW50cywgYnV0IHBvc3NpYmx5IGxlc3MgYWNjdXJhdGVseS4KYGBge3J9CgpQcm9iRG9jSW5Ub3BpY19NZWFuUHJvYl9CeVNydmNScXN0SWQgPC0KICBQcm9iRG9jSW5Ub3BpY19Qcm9ic1N1bW1lZF9CeU1vZGVsICU+JSAKICBncm91cF9ieShzZXJ2aWNlcmVxdWVzdGlkLAogICAgICAgICAgIHRvcGljX25hbWUKICAgICAgICAgICApICU+JSAKICBzdW1tYXJpc2UoTWVhblByb2IgPSBtZWFuKHByb2JfLCBuYS5ybSA9IFRSVUUpCiAgICAgICAgICAgICkgJT4lIAogIGxlZnRfam9pbihzZWxlY3QoU2VydmljZU5vdGVzQ2xlYW5lZCwKICAgICAgICAgICAgICAgICAgIHNlcnZpY2VyZXF1ZXN0aWQsCiAgICAgICAgICAgICAgICAgICBzZXJ2aWNlbm90ZXMsCiAgICAgICAgICAgICAgICAgICBzZXJ2aWNlb3JkZXJkYXRlCiAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICBieSA9IGMoInNlcnZpY2VyZXF1ZXN0aWQiID0gInNlcnZpY2VyZXF1ZXN0aWQiKQogICAgICAgICAgICApICU+JSAKICBhcnJhbmdlKHNlcnZpY2VyZXF1ZXN0aWQsCiAgICAgICAgICBkZXNjKE1lYW5Qcm9iKQogICAgICAgICAgKQoKc3RyKFByb2JEb2NJblRvcGljX01lYW5Qcm9iX0J5U3J2Y1Jxc3RJZCkKdGFpbChQcm9iRG9jSW5Ub3BpY19NZWFuUHJvYl9CeVNydmNScXN0SWQsIDEwMCkKVmlldyhoZWFkKFByb2JEb2NJblRvcGljX01lYW5Qcm9iX0J5U3J2Y1Jxc3RJZCwgMTAwMCkpClZpZXcodGFpbChQcm9iRG9jSW5Ub3BpY19NZWFuUHJvYl9CeVNydmNScXN0SWQsIDEwMDApKQoKYGBgCiAgCiAgICAKICAgIEhlcmUsIHdlIGNyZWF0ZSBhIGRhdGFzZXQgc3VpdGFibGUgZm9yIGdyYXBoaW5nIGFuZCBhbmFseXppbmcsIGJ5IHNpbXBseSBzZWxlY3RpbmcgdGhlIGhpZ2hlc3QgdG9waWMgcHJvYmFiaWxpdHkgKHRoZSBgTWVhblByb2JgIHZhbHVlKSBhc3NpZ25lZCB0byBlYWNoIHRvcGljLiBXZSBhbHNvIGRvIHRoaXMgZm9yIHRoZSBzaW5nbGUgNS1ncmFtIDQtdG9waWMgbW9kZWwuCmBgYHtyfQoKIyBQcm9iRG9jSW5Ub3BpY19BbGxNb2RlbHMKIyBQcm9iRG9jSW5Ub3BpY19Qcm9ic1N1bW1lZF9CeU1vZGVsCiMgUHJvYkRvY0luVG9waWNfTWVhblByb2JfQnlTcnZjUnFzdElkCiMgCiMgCiMgUHJvYkRvY0luVG9waWNfQWxsTW9kZWxzICU+JSBzZWxlY3Qoc2VydmljZXJlcXVlc3RpZCkgJT4lIGRpc3RpbmN0KCkgJT4lIG5yb3cKIyBQcm9iRG9jSW5Ub3BpY19Qcm9ic1N1bW1lZF9CeU1vZGVsICU+JSBzZWxlY3Qoc2VydmljZXJlcXVlc3RpZCkgJT4lIGRpc3RpbmN0KCkgJT4lIG5yb3cKIyBQcm9iRG9jSW5Ub3BpY19NZWFuUHJvYl9CeVNydmNScXN0SWQgJT4lIHNlbGVjdChzZXJ2aWNlcmVxdWVzdGlkKSAlPiUgZGlzdGluY3QoKSAlPiUgbnJvdwoKClRvcFByb2JfQnlTcnZjUnFzdElkIDwtCiAgUHJvYkRvY0luVG9waWNfTWVhblByb2JfQnlTcnZjUnFzdElkICU+JSAKICBtdXRhdGUoc2VydmljZW9yZGVyX3lyID0geWVhcihzZXJ2aWNlb3JkZXJkYXRlKSwKICAgICAgICAgIyBzZXJ2aWNlb3JkZXJfeXIyID0gYXMuZmFjdG9yKHNlcnZpY2VvcmRlcl95ciksCiAgICAgICAgIHlyX2dyb3VwID0gcGFzdGUwKGFzLmNoYXJhY3RlcihzZXJ2aWNlb3JkZXJfeXIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAiXyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzLmNoYXJhY3Rlcih0b3BpY19uYW1lKQogICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICBtb2RlbCA9ICJBbGxNb2RlbHNfTWVhblByb2IiCiAgICAgICAgICkgJT4lIAogIHJlbmFtZSgicHJvYiIgPSAiTWVhblByb2IiKSAlPiUgCiAgZ3JvdXBfYnkoc2VydmljZXJlcXVlc3RpZCkgJT4lIAogIHRvcF9uKDEsCiAgICAgICAgcHJvYgogICAgICAgICkgJT4lIAogIHVuZ3JvdXAoKSAlPiUgCiAgYXJyYW5nZShwcm9iKQoKc3RyKFRvcFByb2JfQnlTcnZjUnFzdElkKQpUb3BQcm9iX0J5U3J2Y1Jxc3RJZApWaWV3KFRvcFByb2JfQnlTcnZjUnFzdElkKQoKCgpUb3BQcm9iX0J5U3J2Y1Jxc3RJZF8wNWdyYW0wNHRvcGljIDwtIAogIFByb2JEb2NJblRvcGljX1Byb2JzU3VtbWVkXzA1Z3JhbTA0dG9waWMgJT4lIAogIG11dGF0ZShzZXJ2aWNlb3JkZXJfeXIgPSB5ZWFyKHNlcnZpY2VvcmRlcmRhdGUpLAogICAgICAgICAjIHNlcnZpY2VvcmRlcl95cjIgPSBhcy5mYWN0b3Ioc2VydmljZW9yZGVyX3lyKSwKICAgICAgICAgeXJfZ3JvdXAgPSBwYXN0ZTAoYXMuY2hhcmFjdGVyKHNlcnZpY2VvcmRlcl95ciksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICJfIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHRvcGljX25hbWUpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgIG1vZGVsID0gIjVncmFtNHRvcGljIgogICAgICAgICApICU+JSAKICByZW5hbWUoInByb2IiID0gInByb2JfIikgJT4lIAogIGdyb3VwX2J5KHNlcnZpY2VyZXF1ZXN0aWQpICU+JSAKICB0b3BfbigxLAogICAgICAgIHByb2IKICAgICAgICApICU+JSAKICB1bmdyb3VwKCkgJT4lIAogIGFycmFuZ2UocHJvYikKCnN0cihUb3BQcm9iX0J5U3J2Y1Jxc3RJZF8wNWdyYW0wNHRvcGljKQpUb3BQcm9iX0J5U3J2Y1Jxc3RJZF8wNWdyYW0wNHRvcGljClZpZXcoVG9wUHJvYl9CeVNydmNScXN0SWRfMDVncmFtMDR0b3BpYykKICAKYGBgCiAgCiAgICAKICAgIE5vdywgd2UgY2FuIHNpbXBseSBjcmVhdGUgdGhlIGZyZXFwb2x5IHBsb3RzIGFuZCBkZW5zaXR5IHBsb3RzIGZvciBlYWNoIHllYXItdG9waWMgY29tYmluYXRpb24gdG8gaW52ZXN0aWdhdGUgaG93IHRoZSB0b3BpYyBhc3NpZ25tZW50IGRpZCBvdmVyIHRpbWUsIGFuZCB0aGVuIHNhdmUgdGhlbS4KYGBge3J9CgojIHN0cihUb3BQcm9iX0J5U3J2Y1Jxc3RJZCkKIyBzdHIoVG9wUHJvYl9CeVNydmNScXN0SWRfMDVncmFtMDR0b3BpYykKCgpUb3BpY0Rpc3Ryb19BbGxNb2RlbHNNZWFuX0ZyZXFwb2x5IDwtCiAgVG9wUHJvYl9CeVNydmNScXN0SWQgJT4lCiAgc3BsaXQoLiRzZXJ2aWNlb3JkZXJfeXIpICU+JQogIG1hcCh+IGdncGxvdChkYXRhID0gLngsCiAgICAgICAgICAgICAgIGFlcyh4ID0gcHJvYiwKICAgICAgICAgICAgICAgICAgIGNvbG91ciA9IHRvcGljX25hbWUKICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgKSArCiAgICAgICAgZ2VvbV9mcmVxcG9seShiaW53aWR0aCA9IDAuMDUsCiAgICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IDAuNgogICAgICAgICAgICAgICAgICAgICAgKSArCiAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgMSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgKSArCiAgICAgICAgZ2d0aXRsZShsYWJlbCA9IHBhc3RlMCgiVG9waWNEaXN0cm9fIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC54JG1vZGVsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIl9GcmVxcG9seV8iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXMuY2hhcmFjdGVyKC54JHNlcnZpY2VvcmRlcl95cikKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICkgKwogICAgICAgIGxhYnMoeCA9ICJQcm9iIG9mIFNlcnZpY2VSZXF1ZXN0SWQgaW4gdGhlIFRvcGljIikKICAgICAgKQoKVG9waWNEaXN0cm9fQWxsTW9kZWxzTWVhbl9EZW5zaXR5IDwtCiAgVG9wUHJvYl9CeVNydmNScXN0SWQgJT4lCiAgc3BsaXQoLiRzZXJ2aWNlb3JkZXJfeXIpICU+JQogIG1hcCh+IGdncGxvdChkYXRhID0gLngsCiAgICAgICAgICAgICAgIGFlcyh4ID0gcHJvYiwKICAgICAgICAgICAgICAgICAgIGNvbG91ciA9IHRvcGljX25hbWUKICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgKSArCiAgICAgICAgZ2VvbV9kZW5zaXR5KCkgKwogICAgICAgIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDEpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICkgKwogICAgICAgIGdndGl0bGUobGFiZWwgPSBwYXN0ZTAoIlRvcGljRGlzdHJvXyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAueCRtb2RlbCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJfRGVuc2l0eV8iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXMuY2hhcmFjdGVyKC54JHNlcnZpY2VvcmRlcl95cikKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICkgKwogICAgICAgIGxhYnMoeCA9ICJQcm9iIG9mIFNlcnZpY2VSZXF1ZXN0SWQgaW4gdGhlIFRvcGljIikKICAgICAgKQoKCgpUb3BpY0Rpc3Ryb18wNWdyYW0wNHRvcGljX0ZyZXFwb2x5IDwtCiAgVG9wUHJvYl9CeVNydmNScXN0SWRfMDVncmFtMDR0b3BpYyAlPiUKICBzcGxpdCguJHNlcnZpY2VvcmRlcl95cikgJT4lCiAgbWFwKH4gZ2dwbG90KGRhdGEgPSAueCwKICAgICAgICAgICAgICAgYWVzKHggPSBwcm9iLAogICAgICAgICAgICAgICAgICAgY29sb3VyID0gdG9waWNfbmFtZQogICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICApICsKICAgICAgICBnZW9tX2ZyZXFwb2x5KGJpbndpZHRoID0gMC4wNSwKICAgICAgICAgICAgICAgICAgICAgIGFscGhhID0gMC42CiAgICAgICAgICAgICAgICAgICAgICApICsKICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCAxKQogICAgICAgICAgICAgICAgICAgICAgICAgICApICsKICAgICAgICBnZ3RpdGxlKGxhYmVsID0gcGFzdGUwKCJUb3BpY0Rpc3Ryb18iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLngkbW9kZWwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiX0ZyZXFwb2x5XyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5jaGFyYWN0ZXIoLngkc2VydmljZW9yZGVyX3lyKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgKSArCiAgICAgICAgbGFicyh4ID0gIlByb2Igb2YgU2VydmljZVJlcXVlc3RJZCBpbiB0aGUgVG9waWMiKQogICAgICApCgpUb3BpY0Rpc3Ryb18wNWdyYW0wNHRvcGljX0RlbnNpdHkgPC0KICBUb3BQcm9iX0J5U3J2Y1Jxc3RJZF8wNWdyYW0wNHRvcGljICU+JQogIHNwbGl0KC4kc2VydmljZW9yZGVyX3lyKSAlPiUKICBtYXAofiBnZ3Bsb3QoZGF0YSA9IC54LAogICAgICAgICAgICAgICBhZXMoeCA9IHByb2IsCiAgICAgICAgICAgICAgICAgICBjb2xvdXIgPSB0b3BpY19uYW1lCiAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICkgKwogICAgICAgIGdlb21fZGVuc2l0eSgpICsKICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCAxKQogICAgICAgICAgICAgICAgICAgICAgICAgICApICsKICAgICAgICBnZ3RpdGxlKGxhYmVsID0gcGFzdGUwKCJUb3BpY0Rpc3Ryb18iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLngkbW9kZWwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiX0RlbnNpdHlfIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzLmNoYXJhY3RlcigueCRzZXJ2aWNlb3JkZXJfeXIpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICApICsKICAgICAgICBsYWJzKHggPSAiUHJvYiBvZiBTZXJ2aWNlUmVxdWVzdElkIGluIHRoZSBUb3BpYyIpCiAgICAgICkKCgoKIyBzdHIoVG9waWNEaXN0cm9fQWxsTW9kZWxzTWVhbl9GcmVxcG9seVtbMThdXSkKIyBzdHIoVG9waWNEaXN0cm9fQWxsTW9kZWxzTWVhbl9EZW5zaXR5W1sxOF1dKQojIHN0cihUb3BpY0Rpc3Ryb18wNWdyYW0wNHRvcGljX0ZyZXFwb2x5W1sxOF1dKQojIHN0cihUb3BpY0Rpc3Ryb18wNWdyYW0wNHRvcGljX0RlbnNpdHlbWzE4XV0pCgoKVG9waWNEaXN0cm9fQWxsTW9kZWxzTWVhbl9GcmVxcG9seVtbMThdXQpUb3BpY0Rpc3Ryb19BbGxNb2RlbHNNZWFuX0RlbnNpdHlbWzE4XV0KVG9waWNEaXN0cm9fMDVncmFtMDR0b3BpY19GcmVxcG9seVtbMThdXQpUb3BpY0Rpc3Ryb18wNWdyYW0wNHRvcGljX0RlbnNpdHlbWzE4XV0KCmBgYAogIAogICAgCiAgICBTYXZpbmcgdGhlIHZpc3VhbHMgY3JlYXRlZCBhYm92ZS4KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRSwgcGFnZWQucHJpbnQ9RkFMU0V9CgpUb3BpY0Rpc3Ryb19BbGxNb2RlbHNNZWFuX0ZyZXFwb2x5ICU+JQogIG1hcChmdW5jdGlvbih4KQogICAgZ2dzYXZlKHBhc3RlMCh3ZCwKICAgICAgICAgICAgICAgICAgIi9WaXovIiwKICAgICAgICAgICAgICAgICAgIyAiTmV3XyIsCiAgICAgICAgICAgICAgICAgIHgkbGFiZWxzJHRpdGxlW1sxXV0sCiAgICAgICAgICAgICAgICAgICIucG5nIgogICAgICAgICAgICAgICAgICApLAogICAgICAgICAgIHgsCiAgICAgICAgICAgc2NhbGUgPSA0LAogICAgICAgICAgIHdpZHRoID0gNiwKICAgICAgICAgICBoZWlnaHQgPSA0LAogICAgICAgICAgIHVuaXRzID0gImNtIgogICAgICAgICAgICkKICAgICkKCgoKVG9waWNEaXN0cm9fQWxsTW9kZWxzTWVhbl9EZW5zaXR5ICU+JQogIG1hcChmdW5jdGlvbih4KQogICAgZ2dzYXZlKHBhc3RlMCh3ZCwKICAgICAgICAgICAgICAgICAgIi9WaXovIiwKICAgICAgICAgICAgICAgICAgIyAiTmV3XyIsCiAgICAgICAgICAgICAgICAgIHgkbGFiZWxzJHRpdGxlW1sxXV0sCiAgICAgICAgICAgICAgICAgICIucG5nIgogICAgICAgICAgICAgICAgICApLAogICAgICAgICAgIHgsCiAgICAgICAgICAgc2NhbGUgPSA0LAogICAgICAgICAgIHdpZHRoID0gNiwKICAgICAgICAgICBoZWlnaHQgPSA0LAogICAgICAgICAgIHVuaXRzID0gImNtIgogICAgICAgICAgICkKICAgICkKCgoKVG9waWNEaXN0cm9fMDVncmFtMDR0b3BpY19GcmVxcG9seSAlPiUKICBtYXAoZnVuY3Rpb24oeCkKICAgIGdnc2F2ZShwYXN0ZTAod2QsCiAgICAgICAgICAgICAgICAgICIvVml6LyIsCiAgICAgICAgICAgICAgICAgICMgIk5ld18iLAogICAgICAgICAgICAgICAgICB4JGxhYmVscyR0aXRsZVtbMV1dLAogICAgICAgICAgICAgICAgICAiLnBuZyIKICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICB4LAogICAgICAgICAgIHNjYWxlID0gNCwKICAgICAgICAgICB3aWR0aCA9IDYsCiAgICAgICAgICAgaGVpZ2h0ID0gNCwKICAgICAgICAgICB1bml0cyA9ICJjbSIKICAgICAgICAgICApCiAgICApCgoKClRvcGljRGlzdHJvXzA1Z3JhbTA0dG9waWNfRGVuc2l0eSAlPiUKICBtYXAoZnVuY3Rpb24oeCkKICAgIGdnc2F2ZShwYXN0ZTAod2QsCiAgICAgICAgICAgICAgICAgICIvVml6LyIsCiAgICAgICAgICAgICAgICAgICMgIk5ld18iLAogICAgICAgICAgICAgICAgICB4JGxhYmVscyR0aXRsZVtbMV1dLAogICAgICAgICAgICAgICAgICAiLnBuZyIKICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICB4LAogICAgICAgICAgIHNjYWxlID0gNCwKICAgICAgICAgICB3aWR0aCA9IDYsCiAgICAgICAgICAgaGVpZ2h0ID0gNCwKICAgICAgICAgICB1bml0cyA9ICJjbSIKICAgICAgICAgICApCiAgICApCgpgYGAKICAKICAgIAogICAgUmVtb3ZpbmcgdW5uZWVkZWQgZmlsZXMuCmBgYHtyfQoKcm0obGlzdCA9IGxzKHBhdHRlcm4gPSAiVG9waWNEaXN0cm9fIikpCgpgYGAKICAKICAgIAogICAgQXMgYW5vdGhlciBtZXRob2QgdG8gZGV0ZXJtaW5lIHRoZSAiY29ycmVjdCIgdG9waWMgYXNzaWdubWVudCwgaGVyZSBJIHNpbXBseSBjb3VudCB0aGUgbnVtYmVyIG9mIHRpbWVzIGEgdG9waWMgYXNzaWdubWVudCB3YXMgZ2l2ZW4gdG8gZWFjaCBkb2N1bWVudCAoc2VydmljZXJlcXVlc3RpZCksIGFuZCBhc3NpZ24gdGhlICJjb3JyZWN0IiB0b3BpYyB0byB0aGUgdG9waWMgd2l0aCB0aGUgbW9zdCBhc3NpZ25tZW50cy4gSW4gdGhlIGNhc2Ugb2YgdGllcyAoZS5nLiwgYWxsIHRocmVlIHRvcGljcywgZWFjaCBhc3NpZ25lZCB0d2ljZSksIEkgdXNlIHRoZSBgTWVhblByb2JgIGNhbGN1bGF0ZWQgYWJvdmUgaW4gdGhlIGBQcm9iRG9jSW5Ub3BpY19NZWFuUHJvYl9CeVNydmNScXN0SWRgIGRhdGFmcmFtZSBwcmV2aW91c2x5LgpgYGB7cn0KCnN0cihQcm9iRG9jSW5Ub3BpY19Qcm9ic1N1bW1lZF9CeU1vZGVsKQpWaWV3KFByb2JEb2NJblRvcGljX1Byb2JzU3VtbWVkX0J5TW9kZWwpCiMgUHJvYkRvY0luVG9waWNfUHJvYnNTdW1tZWRfQnlNb2RlbCAlPiUgc2VsZWN0KG1vZGVsX25ncmFtLCBtb2RlbF90b3BpYykgJT4lIGRpc3RpbmN0KCkKCnN0cihQcm9iRG9jSW5Ub3BpY19NZWFuUHJvYl9CeVNydmNScXN0SWQpClZpZXcoUHJvYkRvY0luVG9waWNfTWVhblByb2JfQnlTcnZjUnFzdElkKQoKClRvcGljQXNzaWduZWRfQnlDb3VudHNfQnlNZWFuUHJvYiA8LQogIFByb2JEb2NJblRvcGljX1Byb2JzU3VtbWVkX0J5TW9kZWwgJT4lIAogIGdyb3VwX2J5KHNlcnZpY2VyZXF1ZXN0aWQsCiAgICAgICAgICAgbW9kZWxfbmdyYW0sCiAgICAgICAgICAgbW9kZWxfdG9waWMKICAgICAgICAgICApICU+JSAKICB0b3BfbigxLAogICAgICAgIHByb2JfCiAgICAgICAgKSAlPiUgCiAgdW5ncm91cCgpICU+JSAKICBjb3VudChzZXJ2aWNlcmVxdWVzdGlkLAogICAgICAgIHRvcGljX25hbWUKICAgICAgICApICU+JSAKICBsZWZ0X2pvaW4oUHJvYkRvY0luVG9waWNfTWVhblByb2JfQnlTcnZjUnFzdElkLAogICAgICAgICAgICBieSA9IGMoInNlcnZpY2VyZXF1ZXN0aWQiID0gInNlcnZpY2VyZXF1ZXN0aWQiLAogICAgICAgICAgICAgICAgICAgInRvcGljX25hbWUiID0gInRvcGljX25hbWUiCiAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICkgJT4lIAogIHNlbGVjdChzZXJ2aWNlcmVxdWVzdGlkLAogICAgICAgICB0b3BpY19uYW1lLAogICAgICAgICBuLAogICAgICAgICBNZWFuUHJvYgogICAgICAgICApICU+JSAKICBncm91cF9ieShzZXJ2aWNlcmVxdWVzdGlkKSAlPiUKICBhcnJhbmdlKHNlcnZpY2VyZXF1ZXN0aWQsCiAgICAgICAgICBkZXNjKG4pLAogICAgICAgICAgZGVzYyhNZWFuUHJvYikKICAgICAgICAgICkgJT4lIAogIHVuZ3JvdXAoKSAlPiUgCiAgZ3JvdXBfYnkoc2VydmljZXJlcXVlc3RpZCkgJT4lCiAgbXV0YXRlKFJvd051bSA9IHJvd19udW1iZXIoKQogICAgICAgICApICU+JSAKICB1bmdyb3VwKCkgJT4lIAogIGZpbHRlcihSb3dOdW0gPT0gMSkgJT4lIAogIHJlbmFtZSh0aW1lc190b3BpY19hc3NpZ25lZCA9IG4pICU+JSAKICBsZWZ0X2pvaW4oU2VydmljZU5vdGVzQ2xlYW5lZCwKICAgICAgICAgICAgYnkgPSAic2VydmljZXJlcXVlc3RpZCIKICAgICAgICAgICAgKSAlPiUgCiAgc2VsZWN0KHNlcnZpY2VyZXF1ZXN0aWQsCiAgICAgICAgIHRvcGljX25hbWUsCiAgICAgICAgIHRpbWVzX3RvcGljX2Fzc2lnbmVkLAogICAgICAgICBNZWFuUHJvYiwKICAgICAgICAgc2VydmljZW5vdGVzLAogICAgICAgICBzZXJ2aWNlbm90ZXNfbm9udW1zX25vcHVuYywKICAgICAgICAgc2VydmljZW9yZGVyZGF0ZSwKICAgICAgICAgc2VydmljZW9yZGVyX2RhdGUsCiAgICAgICAgIHNlcnZpY2VvcmRlcl95ciwKICAgICAgICAgc2VydmljZW9yZGVyX3lyX3Bvc2l4LAogICAgICAgICBzZXJ2aWNlb3JkZXJfbXRoLAogICAgICAgICBzZXJ2aWNlb3JkZXJfeXJtdGgsCiAgICAgICAgIHNlcnZpY2VvcmRlcl95cm10aF9wb3NpeCwKICAgICAgICAgc2VydmljZW9yZGVyX2RheSwKICAgICAgICAgc2VydmljZW9yZGVyX3drZGF5CiAgICAgICAgICkKCnN0cihTZXJ2aWNlTm90ZXNDbGVhbmVkKQpzdHIoVG9waWNBc3NpZ25lZF9CeUNvdW50c19CeU1lYW5Qcm9iKQpoZWFkKFRvcGljQXNzaWduZWRfQnlDb3VudHNfQnlNZWFuUHJvYiwgMTAwMCkKVmlldyhUb3BpY0Fzc2lnbmVkX0J5Q291bnRzX0J5TWVhblByb2IpCgpgYGAKICAKICAgIAogICAgU28gbm93LCB3ZSBjYW4gY29tcGFyZSB0d28gZGlmZmVyZW50IG1vZGVscywgZWFjaCBiZWluZyBzbGlnaHQgdmFyaWF0aW9ucyBvZiBzaXggTERBIG1vZGVscy4gIAogICAgCiAgICAxKSBgVG9wUHJvYl9CeVNydmNScXN0SWRgIGFzc2lnbnMgdGhlIHRvcGljIGJ5IHRha2luZyB0aGUgbWVhbiB0b3BpYyBwcm9iYWJpbGl0eSBzY29yZSBhY3Jvc3MgYWxsIHNpeCBtb2RlbHMuCiAgICAyKSBgVG9waWNBc3NpZ25lZF9CeUNvdW50c19CeU1lYW5Qcm9iYCBhc3NpZ25zIHRoZSB0b3BpYyBieSB0YWtpbmcgdGhlIG1vc3QgZnJlcXVlbnRseSBhc3NpZ25lZCB0b3BpYyBhY3Jvc3MgYWxsIHNpeCBtb2RlbHMuCmBgYHtyfQoKc3RyKFRvcGljQXNzaWduZWRfQnlDb3VudHNfQnlNZWFuUHJvYikKc3RyKFRvcFByb2JfQnlTcnZjUnFzdElkKQpzdHIoVG9wUHJvYl9CeVNydmNScXN0SWRfMDVncmFtMDR0b3BpYykKCgpkaW0oVG9waWNBc3NpZ25lZF9CeUNvdW50c19CeU1lYW5Qcm9iKQpkaW0oVG9wUHJvYl9CeVNydmNScXN0SWQpCmRpbShUb3BQcm9iX0J5U3J2Y1Jxc3RJZF8wNWdyYW0wNHRvcGljKQoKCmEgPC0gVG9wUHJvYl9CeVNydmNScXN0SWQgJT4lIAogIHNlbGVjdChzZXJ2aWNlcmVxdWVzdGlkLAogICAgICAgICB0b3BpY19uYW1lLAogICAgICAgICBwcm9iCiAgICAgICAgICkgJT4lIAogIHJlbmFtZSh0b3BpY19uYW1lX21lYW5wcm9iID0gdG9waWNfbmFtZSwKICAgICAgICAgcHJvYl9tZWFucHJvYiA9IHByb2IKICAgICAgICAgKQoKCmIgPC0gVG9waWNBc3NpZ25lZF9CeUNvdW50c19CeU1lYW5Qcm9iICU+JSAKICByZW5hbWUodG9waWNfbmFtZV90b3Bjb3VudHMgPSB0b3BpY19uYW1lLAogICAgICAgICB0aW1lc190b3BpY19hc3NpZ25lZF90b3Bjb3VudHMgPSB0aW1lc190b3BpY19hc3NpZ25lZCwKICAgICAgICAgcHJvYl90b3Bjb3VudHMgPSBNZWFuUHJvYgogICAgICAgICApCgoKTW9kZWxzQ29tcGFyZSA8LSBhICU+JSAKICBsZWZ0X2pvaW4oYiwKICAgICAgICAgICAgYnkgPSAic2VydmljZXJlcXVlc3RpZCIKICAgICAgICAgICAgKQoKcm0oYSwgYikKc3RyKE1vZGVsc0NvbXBhcmUpCk1vZGVsc0NvbXBhcmUKVmlldyhNb2RlbHNDb21wYXJlKQoKYGBgCiAgCiAgICAKICAgIFdoZW4gY29tcGFyaW5nIHRoZSB0d28gbW9kZWxzLCB0aGV5IGNvaW5jaWRlIG5lYXJseSA5OSUgb2YgdGhlIHRpbWUuLi5idXQgdGhlIGRpc2FncmVlbWVudHMgc3RpbGwgcmFpc2UgcXVlc3Rpb25zLgpgYGB7cn0KCmZpbHRlcihNb2RlbHNDb21wYXJlLAogICAgICAgdG9waWNfbmFtZV9tZWFucHJvYiAhPSB0b3BpY19uYW1lX3RvcGNvdW50cwogICAgICAgKSAlPiUgCiAgbnJvdygpIC8gbnJvdyhNb2RlbHNDb21wYXJlKSAqIDEwMAoKZmlsdGVyKE1vZGVsc0NvbXBhcmUsCiAgICAgICB0b3BpY19uYW1lX21lYW5wcm9iID09IHRvcGljX25hbWVfdG9wY291bnRzCiAgICAgICApICU+JSAKICBucm93KCkgLyBucm93KE1vZGVsc0NvbXBhcmUpICogMTAwCgoKIyBWaWV3KGZpbHRlcihNb2RlbHNDb21wYXJlLAojICAgICAgICAgICAgIHRvcGljX25hbWVfbWVhbnByb2IgIT0gdG9waWNfbmFtZV90b3Bjb3VudHMKIyAgICAgICAgICAgICApCiMgICAgICApCgpWaWV3KGZpbHRlcihNb2RlbHNDb21wYXJlLAogICAgICAgICAgICB0b3BpY19uYW1lX21lYW5wcm9iICE9IHRvcGljX25hbWVfdG9wY291bnRzCiAgICAgICAgICAgICkgJT4lIAogICAgICAgc2VsZWN0KHNlcnZpY2VyZXF1ZXN0aWQsCiAgICAgICAgICAgICAgdG9waWNfbmFtZV9tZWFucHJvYiwKICAgICAgICAgICAgICB0b3BpY19uYW1lX3RvcGNvdW50cywKICAgICAgICAgICAgICBzZXJ2aWNlbm90ZXMsCiAgICAgICAgICAgICAgc2VydmljZW5vdGVzX25vbnVtc19ub3B1bmMKICAgICAgICAgICAgICApCiAgICAgKQoKYGBgCiAgCiAgICAKICAgIEFzIHRoZSBtb2RlbHMgYXNzaWduIHRvcGljcyBhbG1vc3QgaWRlbnRpY2FsbHksIGxldCdzIHVzZSB0aGUgYHRvcGNvdW50c2AgbW9kZWwgdG8gZG8gc29tZSBxdWljayBpbnNwZWN0aW9ucyBhYm91dCBob3cgb2Z0ZW4gZWFjaCBvZiB0aGUgdG9waWNzIHdlcmUgYXNzaWduZWQsIHdoZW4sIGNoYW5nZXMgb3ZlciB0aW1lLCBldGMuCmBgYHtyfQoKCkNvdW50c19BbGxZZWFycyA8LSBNb2RlbHNDb21wYXJlICU+JSAKICBtdXRhdGUodG9waWNfbmFtZSA9IGZhY3Rvcih0b3BpY19uYW1lX3RvcGNvdW50cywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKCJ1bmtub3duIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJub19yYXRzX2ZvdW5kIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJyYXRzX2ZvdW5kIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgKSAlPiUgCmdyb3VwX2J5KHRvcGljX25hbWUpICU+JSAKICBjb3VudCgpICU+JSAKICByZW5hbWUoY291bnRzID0gbikKCgoKQ291bnRzX0Fjcm9zc1llYXJzIDwtIE1vZGVsc0NvbXBhcmUgJT4lIAogIG11dGF0ZSh0b3BpY19uYW1lID0gZmFjdG9yKHRvcGljX25hbWVfdG9wY291bnRzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoInVua25vd24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm5vX3JhdHNfZm91bmQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInJhdHNfZm91bmQiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICApICU+JSAKZ3JvdXBfYnkodG9waWNfbmFtZSwKICAgICAgICAgc2VydmljZW9yZGVyX3lyCiAgICAgICAgICkgJT4lIAogIGNvdW50KCkgJT4lIAogIHJlbmFtZShjb3VudHMgPSBuKQoKCgpnZ3Bsb3QoZGF0YSA9IENvdW50c19BbGxZZWFycywKICAgICAgIGFlcyh4ID0gdG9waWNfbmFtZSwKICAgICAgICAgICB5ID0gY291bnRzLAogICAgICAgICAgIGZpbGwgPSB0b3BpY19uYW1lCiAgICAgICAgICAgKQogICAgICAgKSArCiAgZ2VvbV9jb2woKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IGNvdW50cyksCiAgICAgICAgICAgIG51ZGdlX3kgPSAtMjAwLAogICAgICAgICAgICBzaXplID0gMwogICAgICAgICAgICApICsKICAjIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgY29vcmRfZmxpcCgpCgogZ2dzYXZlKHBhc3RlMCh3ZCwKICAgICAgICAgICAgICAgIi9WaXovIiwKICAgICAgICAgICAgICAgIyAiTmV3XyIsCiAgICAgICAgICAgICAgICJUb3BpY3NfQ291bnRNb2RlbF9Db3VudHNfQWxsWWVhcnMucG5nIgogICAgICAgICAgICAgICAgICApLAogICAgICAgIHNjYWxlID0gNCwKICAgICAgICB3aWR0aCA9IDYsCiAgICAgICAgaGVpZ2h0ID0gNiwKICAgICAgICB1bml0cyA9ICJjbSIKICAgICAgICApCgoKZ2dwbG90KGRhdGEgPSBDb3VudHNfQWNyb3NzWWVhcnMsCiAgICAgICBhZXMoeCA9IHRvcGljX25hbWUsCiAgICAgICAgICAgeSA9IGNvdW50cywKICAgICAgICAgICBmaWxsID0gdG9waWNfbmFtZQogICAgICAgICAgICkKICAgICAgICkgKwogIGdlb21fY29sKCkgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSBjb3VudHMpLAogICAgICAgICAgICBudWRnZV95ID0gMTAwLAogICAgICAgICAgICBzaXplID0gMi41CiAgICAgICAgICAgICkgKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDE4MDApLAogICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzZXEoMCwgMTgwMCwgMzAwKQogICAgICAgICAgICAgICAgICAgICApICsKICBmYWNldF93cmFwKH5zZXJ2aWNlb3JkZXJfeXIpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICBjb29yZF9mbGlwKCkKCiBnZ3NhdmUocGFzdGUwKHdkLAogICAgICAgICAgICAgICAiL1Zpei8iLAogICAgICAgICAgICAgICAjICJOZXdfIiwKICAgICAgICAgICAgICAgIlRvcGljc19Db3VudE1vZGVsX0NvdW50c19BY3Jvc3NZZWFycy5wbmciCiAgICAgICAgICAgICAgICAgICksCiAgICAgICAgc2NhbGUgPSA0LAogICAgICAgIHdpZHRoID0gOCwKICAgICAgICBoZWlnaHQgPSA2LAogICAgICAgIHVuaXRzID0gImNtIgogICAgICAgICkKCmBgYAoKCgo=