Load the relevant libraries.

# rm(list = ls())
library("tidyverse")          # data manipulation
library("magrittr")           # data manipulation (pipeing data)
library("stringr")            # string manipulation
library("tidytext")           # text manipulation
library("ggplot2")            # viz
library("caret")              # measuring model performance

Session Info.

sessionInfo()
R version 3.3.3 (2017-03-06)
Platform: x86_64-apple-darwin13.4.0 (64-bit)
Running under: macOS  10.13.1

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] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] bindrcpp_0.2    knitr_1.17      caret_6.0-77    lattice_0.20-35 tidytext_0.1.5 
 [6] magrittr_1.5    forcats_0.2.0   stringr_1.2.0   dplyr_0.7.4     purrr_0.2.4    
[11] readr_1.1.1     tidyr_0.7.2     tibble_1.3.4    ggplot2_2.2.1   tidyverse_1.2.1

loaded via a namespace (and not attached):
 [1] httr_1.3.1         ddalpha_1.3.1      sfsmisc_1.1-1      jsonlite_1.5      
 [5] splines_3.3.3      foreach_1.4.3      prodlim_1.6.1      modelr_0.1.1      
 [9] assertthat_0.2.0   highr_0.6          stats4_3.3.3       DRR_0.0.2         
[13] cellranger_1.1.0   yaml_2.1.15        robustbase_0.92-8  ipred_0.9-6       
[17] backports_1.1.1    glue_1.2.0         digest_0.6.12      rvest_0.3.2       
[21] colorspace_1.3-2   recipes_0.1.1      htmltools_0.3.6    Matrix_1.2-12     
[25] plyr_1.8.4         psych_1.7.8        timeDate_3042.101  pkgconfig_2.0.1   
[29] CVST_0.2-1         broom_0.4.3        haven_1.1.0        scales_0.5.0      
[33] gower_0.1.2        lava_1.5.1         withr_2.1.0        nnet_7.3-12       
[37] lazyeval_0.2.1     cli_1.0.0          mnormt_1.5-5       survival_2.41-3   
[41] crayon_1.3.4       readxl_1.0.0       evaluate_0.10.1    tokenizers_0.1.4  
[45] janeaustenr_0.1.5  nlme_3.1-131       SnowballC_0.5.1    MASS_7.3-47       
[49] xml2_1.1.1         dimRed_0.1.0       foreign_0.8-69     class_7.3-14      
[53] rsconnect_0.8.5    tools_3.3.3        hms_0.4.0          kernlab_0.9-25    
[57] munsell_0.4.3      e1071_1.6-8        RcppRoll_0.2.2     rlang_0.1.4       
[61] grid_3.3.3         iterators_1.0.8    rstudioapi_0.7     base64enc_0.1-3   
[65] rmarkdown_1.8      gtable_0.2.0       ModelMetrics_1.1.0 codetools_0.2-15  
[69] curl_3.0           reshape2_1.4.2     R6_2.2.2           lubridate_1.7.1   
[73] rprojroot_1.2      bindr_0.1          stringi_1.1.6      parallel_3.3.3    
[77] Rcpp_0.12.14       rpart_4.1-11       DEoptimR_1.0-8     tidyselect_0.2.3  

Setup the root directory.

Setting wd as the working directory.

wd <- getwd()
wd
[1] "/Users/mdturse/Desktop/Analytics/dc_doh_hackathon"

From the prior analyses as part of analyses, we’ll first load the ServiceNotesCleaned2 dataset. This is a dataset that performs some basically text cleaning (e.g., removing stop words, removing numerics, removing punctuation, etc.).

Note that this dataset has both the original text field (servicenotes) as well as the cleaned text field (servicenotes_cleaned).

Cleaned_Data <- readRDS(paste0(wd,
                               "/Data_Processed/",
                               "ServiceNotesCleaned2.Rds"
                               )
                        )
str(Cleaned_Data)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   26302 obs. of  18 variables:
 $ servicerequestid          : chr  "09-00001211" "09-00001323" "09-00001410" "09-00001865" ...
 $ 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: "1999-04-27 12:59:00" "1999-04-30 19:59:00" ...
 $ 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." ...
 $ 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 ...
 $ 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   " ...
 $ servicenotes_cleaned      : 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 rats " ...
Cleaned_Data
# View(head(Cleaned_Data, 1000))

Prior analyses created two versions of regex functions to determine if the result of the rat inspection was rats_found, no_rats_found, and unknown. Here, we’ll specify those functions before comparing how they agree/disagree.

From the prior analyses, for regex_1_ratsfound, I added the regex look-behind for “no” “rats found”. Also, for regex_1_noratsfound, I added the regex for “no activity”.

regex_1_ratsfound <- "(a){0,1}ba(i){0,1}ted|blocks epa( ){0,1}|ditrac|( ){0,1}epa( ){0,1}|(?<!no )rat(s){0,1} burrows found|reveal rat burrows|rat burrows (n|r)ear property|soft bait"
regex_1_noratsfound <- "no rat(s){0,1}|no rodent|no action|no (active ){0,1}burrow(s){0,1}|no activity|no(t){0,1} eviden(ce){0,1}(ts){0,1}|no sign(s){0,1} rat(s){0,1}|no sign(s){0,1}|no(t){0,1} find"
regex_2_ratsfound <- "((baited|found) *([\\d]*|one|two|three|four|five|six|seven|eight|nine|ten) *(rat|burrow))|( baited )"
regex_2_noratsfound <- "found no (rat|activity|rodent|evidence)"
Now we'll create the variables we need to do the comparisons.
Regex_Model_Compare <- Cleaned_Data %>% 
  select(servicerequestid,
         serviceorder_date,
         serviceorder_yr_posix,
         serviceorder_yrmth_posix,
         servicenotes,
         servicenotes_cleaned
         ) %>%
  mutate(sn_1_ratsfound = str_detect(str_to_lower(servicenotes),
                                     regex_1_ratsfound
                                     ),
         sn_1_noratsfound = str_detect(str_to_lower(servicenotes),
                                       regex_1_noratsfound
                                       ),
         sn_2_ratsfound = str_detect(str_to_lower(servicenotes),
                                     regex_2_ratsfound
                                     ),
         sn_2_noratsfound = str_detect(str_to_lower(servicenotes),
                                       regex_2_noratsfound
                                       ),
         snc_1_ratsfound = str_detect(servicenotes_cleaned,
                                      regex_1_ratsfound
                                      ),
         snc_1_noratsfound = str_detect(servicenotes_cleaned,
                                        regex_1_noratsfound
                                        ),
         snc_2_ratsfound = str_detect(servicenotes_cleaned,
                                      regex_2_ratsfound
                                      ),
         snc_2_noratsfound = str_detect(servicenotes_cleaned,
                                        regex_2_noratsfound
                                        ),
         outcome_sn_1 = case_when(sn_1_ratsfound == TRUE &
                                    sn_1_noratsfound == FALSE ~ "rats_found",
                                  sn_1_ratsfound == FALSE &
                                    sn_1_noratsfound == TRUE ~ "no_rats_found",
                                  TRUE ~ "unknown"
                                  ),
         outcome_sn_2 = case_when(sn_2_ratsfound == TRUE &
                                    sn_2_noratsfound == FALSE ~ "rats_found",
                                  sn_2_ratsfound == FALSE &
                                    sn_2_noratsfound == TRUE ~ "no_rats_found",
                                  TRUE ~ "unknown"
                                  ),
         outcome_snc_1 = case_when(snc_1_ratsfound == TRUE &
                                     snc_1_noratsfound == FALSE ~ "rats_found",
                                   snc_1_ratsfound == FALSE &
                                     snc_1_noratsfound == TRUE ~ "no_rats_found",
                                   TRUE ~ "unknown"
                                   ),
         outcome_snc_2 = case_when(snc_2_ratsfound == TRUE &
                                     snc_2_noratsfound == FALSE ~ "rats_found",
                                   snc_2_ratsfound == FALSE &
                                     snc_2_noratsfound == TRUE ~ "no_rats_found",
                                   TRUE ~ "unknown"
                                   ),
         regex_compare_snsnc_1 = case_when(outcome_sn_1 == "rats_found" &
                                             outcome_snc_1 == "rats_found" ~ "match_rats",
                                           outcome_sn_1 == "no_rats_found" &
                                             outcome_snc_1 == "no_rats_found" ~ "match_norats",
                                           outcome_sn_1 == "unknown" &
                                             outcome_snc_1 == "unknown" ~ "match_unknown",
                                           TRUE ~ "mismatch"
                                           ),
         regex_compare_snsnc_2 = case_when(outcome_sn_2 == "rats_found" &
                                             outcome_snc_2 == "rats_found" ~ "match_rats",
                                           outcome_sn_2 == "no_rats_found" &
                                             outcome_snc_2 == "no_rats_found" ~ "match_norats",
                                           outcome_sn_2 == "unknown" &
                                             outcome_snc_2 == "unknown" ~ "match_unknown",
                                           TRUE ~ "mismatch"
                                           ),
         regex_compare_sn_12 = case_when(outcome_sn_1 == "rats_found" &
                                           outcome_sn_2 == "rats_found" ~ "match_rats",
                                         outcome_sn_1 == "no_rats_found" &
                                           outcome_sn_2 == "no_rats_found" ~ "match_norats",
                                         outcome_sn_1 == "unknown" &
                                           outcome_sn_2 == "unknown" ~ "match_unknown",
                                         TRUE ~ "mismatch"
                                         ),
         regex_compare_snc_12 = case_when(outcome_snc_1 == "rats_found" &
                                            outcome_snc_2 == "rats_found" ~ "match_rats",
                                          outcome_snc_1 == "no_rats_found" &
                                            outcome_snc_2 == "no_rats_found" ~ "match_norats",
                                          outcome_snc_1 == "unknown" &
                                            outcome_snc_2 == "unknown" ~ "match_unknown",
                                          TRUE ~ "mismatch"
                                          )
         )
str(Regex_Model_Compare)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   26302 obs. of  22 variables:
 $ servicerequestid        : chr  "09-00001211" "09-00001323" "09-00001410" "09-00001865" ...
 $ serviceorder_date       : Date, format: "1999-04-27" "1999-04-30" ...
 $ serviceorder_yr_posix   : POSIXct, format: "1999-01-01" "1999-01-01" ...
 $ serviceorder_yrmth_posix: POSIXct, format: "1999-04-01" "1999-04-01" ...
 $ 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_cleaned    : 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 rats " ...
 $ sn_1_ratsfound          : logi  FALSE FALSE FALSE TRUE TRUE FALSE ...
 $ sn_1_noratsfound        : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ sn_2_ratsfound          : logi  FALSE FALSE FALSE TRUE TRUE FALSE ...
 $ sn_2_noratsfound        : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ snc_1_ratsfound         : logi  FALSE FALSE FALSE TRUE TRUE FALSE ...
 $ snc_1_noratsfound       : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ snc_2_ratsfound         : logi  FALSE FALSE FALSE TRUE TRUE FALSE ...
 $ snc_2_noratsfound       : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ outcome_sn_1            : chr  "unknown" "unknown" "unknown" "rats_found" ...
 $ outcome_sn_2            : chr  "unknown" "unknown" "unknown" "rats_found" ...
 $ outcome_snc_1           : chr  "unknown" "unknown" "unknown" "rats_found" ...
 $ outcome_snc_2           : chr  "unknown" "unknown" "unknown" "rats_found" ...
 $ regex_compare_snsnc_1   : chr  "match_unknown" "match_unknown" "match_unknown" "match_rats" ...
 $ regex_compare_snsnc_2   : chr  "match_unknown" "match_unknown" "match_unknown" "match_rats" ...
 $ regex_compare_sn_12     : chr  "match_unknown" "match_unknown" "match_unknown" "match_rats" ...
 $ regex_compare_snc_12    : chr  "match_unknown" "match_unknown" "match_unknown" "match_rats" ...
head(Regex_Model_Compare, 100)
# View(head(Regex_Model_Compare, 1000))

As a first check, let’s see if the two regex functions provide the same result whether used on the unmodified servicenotes field or used on the servicenotes_cleaned field. To do this, we can use the newly created regex_compare_snsnc_1 and regex_compare_snsnc_2 fields.

First, let’s create a basic ggplot theme.

ggplot_theme_basic <-
  theme(panel.background = element_blank(),
        panel.grid.minor = element_blank(),
        panel.grid.major = element_blank(),
        axis.ticks = element_blank(),
        axis.line = element_line(size = 1, colour = "black")
        )

Now for the plotting/inspection.

It looks like the bulk of inspections (although not all) are classified the same way regardless of whether the regex is run on the raw text (servicenotes) or the cleaned text (servicenotes_cleaned). Also, when comparing the number of mismatches between using raw text and cleaned text, Model 2 has less mismatches than Model 1.

Snsnc_Counts_1 <- Regex_Model_Compare %>% 
  count(regex_compare_snsnc_1) %>% 
  mutate(n_pct = n / sum(n)
         )
Snsnc_Counts_2 <- Regex_Model_Compare %>% 
  count(regex_compare_snsnc_2) %>% 
  mutate(n_pct = n / sum(n)
         )
Counts_Combined <- Snsnc_Counts_1 %>% 
  rename(match_status = regex_compare_snsnc_1) %>% 
  mutate(model_type = "regex_1") %>% 
  bind_rows(Snsnc_Counts_2 %>% 
              rename(match_status = regex_compare_snsnc_2) %>% 
              mutate(model_type = "regex_2")
            )
rm(list = ls(pattern = "Snsnc_Counts_"))
ggplot(data = Counts_Combined,
       aes(x = match_status,
           y = n_pct,
           fill = match_status
           )
       ) +
  geom_col() +
  geom_text(aes(label = round(n_pct, 3)
                ),
            nudge_y = .07,
            size = 3
            ) +
  ggplot_theme_basic +
  theme(legend.position = "none") +
  labs(title = "Comparing Text Type Across Regex Models",
       subtitle = "text type is servicenotes or servicenotes_cleaned",
       x = "Text Type Match Status",
       y = "Counts (pct)"
       ) +
  scale_y_continuous(limits = c(0, 1),
                     breaks = seq(0, 1, .2)
                     ) +
  facet_wrap(~ model_type) +
  coord_flip()
ggsave(paste0(wd,
              "/Viz/",
              "Compare_TextType_Across_Models.png"
              ),
       scale = 4,
       width = 6,
       height = 6,
       units = "cm"
       )

rm(Counts_Combined)

As the amount of mismatches between running the regex models on raw text and on cleaned text is minimal, and as the more valuable metric is comparing how accurate Model 1 and Model 2 are, let’s move on to that now.

As a first step, we can say that the models coincide ~80% of the time (regardless of whether servicenotes is used or servicenotes_cleaned is used).

Sn_Counts <- Regex_Model_Compare %>% 
  count(regex_compare_sn_12) %>% 
  mutate(n_pct = n / sum(n)
         )
Snc_Counts <- Regex_Model_Compare %>% 
  count(regex_compare_snc_12) %>% 
  mutate(n_pct = n / sum(n)
         )
Counts_Combined <- Sn_Counts %>% 
  rename(match_status = regex_compare_sn_12) %>% 
  mutate(text_type = "servicenotes_raw") %>% 
  bind_rows(Snc_Counts %>% 
              rename(match_status = regex_compare_snc_12) %>% 
              mutate(text_type = "servicenotes_cleaned")
            )
rm(list = ls(pattern = "_Counts"))
ggplot(data = Counts_Combined,
       aes(x = match_status,
           y = n_pct,
           fill = match_status
           )
       ) +
  geom_col() +
  geom_text(aes(label = round(n_pct, 3)
                ),
            nudge_y = .07,
            size = 3
            ) +
  ggplot_theme_basic +
  theme(legend.position = "none") +
  labs(title = "Comparing Regex Models Across Text Types",
       subtitle = "regex type is regex_1 or regex_2",
       x = "Regex Type Match Status",
       y = "Counts (pct)"
       ) +
  scale_y_continuous(limits = c(0, 1),
                     breaks = seq(0, 1, .2)
                     ) +
  facet_wrap(~ text_type) +
  coord_flip()
ggsave(paste0(wd,
              "/Viz/",
              "Compare_Models_Across_TextType.png"
              ),
       scale = 4,
       width = 6,
       height = 6,
       units = "cm"
       )

rm(Counts_Combined)

So now let’s dig into some mismatches.

It looks like the bulk of the mismatches (~98%) are when the Model 2 is unknown and Model 1 is either rats_found or no_rats_found.

Regex_Model_Compare %>% 
  filter(regex_compare_snc_12 == "mismatch") %>% 
  mutate(outcome_1__2 = paste0(outcome_snc_1,
                                 "__",
                                 outcome_snc_2)
         ) %>% 
  count(outcome_1__2) %>% 
  mutate(n_pct = n/sum(n)
         ) %>% 
  arrange(desc(n_pct)
          )
# View(
  Regex_Model_Compare %>% 
    filter(regex_compare_snc_12 == "mismatch" &
             outcome_snc_2 == "unknown"
           ) %>% 
    select(servicerequestid,
           serviceorder_date,
           servicenotes,
           servicenotes_cleaned,
           outcome_snc_1,
           outcome_snc_2) %>% 
    sample_n(15) %>% 
    arrange(outcome_snc_1,
            outcome_snc_2
            )#)

There are some mismatches (~1.5%) when Model 2 is rats_found or no_rats_found and Model 1 is unknown. This appears to be the case when the text triggers BOTH the regex for rats_found and no_rats_found.

This tends to happen either when the text describes the inspector baiting one residence, but not finding evidence of rats at a neighboring residence inspected at the same time (e.g., “On 8/18/15 @ 9:57am C. Redman found no rat burrows found on the property, Rat burrows found in front of 2363 Champlain Street., N.W.”).

This also happens when the text describes no rats being found during the inspection, but a pest controller having done abatement (e.g., “On 7/1/15 @ 10:58AM R. Herrington found no rat holes in the alley, pest controller has abated area”).

# View(
  Regex_Model_Compare %>% 
    filter(regex_compare_snc_12 == "mismatch" &
             outcome_snc_2 == "rats_found"
           ) %>% 
    sample_n(7) %>% 
    arrange(outcome_snc_1) %>% 
    bind_rows(Regex_Model_Compare %>% 
                filter(regex_compare_snc_12 == "mismatch" &
                         outcome_snc_2 == "no_rats_found"
                       ) %>% 
                sample_n(7) %>% 
                arrange(outcome_snc_1)
              ) %>% 
    select(servicerequestid,
           serviceorder_date,
           servicenotes,
           servicenotes_cleaned,
           outcome_snc_1,
           outcome_snc_2
           )#)

Manually verified data were created as part of GitHub Issue #27, with the resulting dataset found here. So here, we simply pull in those data.

Manually_Verified_Data <- read_csv("https://raw.githubusercontent.com/austinbrian/the-rat-hack/ca51f6275e34b7a7c6ccf3d1912160f7f86d9d72/scripts/feature_engineering/supervised_data.csv")
Parsed with column specification:
cols(
  original_index = col_integer(),
  rats_y_n = col_character(),
  SERVICENOTES = col_character()
)
str(Manually_Verified_Data)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   400 obs. of  3 variables:
 $ original_index: int  3398190 3676265 5012016 5113346 4898668 4294619 100314 3393476 2995352 3084891 ...
 $ rats_y_n      : chr  "n" "y" "y" "y" ...
 $ SERVICENOTES  : chr  "On 6/7/2012 @ 1 pm there was no rat burrows found in the area." "rat holes located along walkway to deck" "On 11/21/16@11:59 am R Herrington baited 1 rat burrow in the rear yd. Treatment will continue until rodent activity ceases. Dit"| __truncated__ "On 1/23/17@1:30 pm G Cornes baited 1 rat burrow in the front yd Treatment will continue until rodent activity ceases. Ditrac/po"| __truncated__ ...
 - attr(*, "spec")=List of 2
  ..$ cols   :List of 3
  .. ..$ original_index: list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ rats_y_n      : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ SERVICENOTES  : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  ..$ default: list()
  .. ..- attr(*, "class")= chr  "collector_guess" "collector"
  ..- attr(*, "class")= chr "col_spec"
Manually_Verified_Data
# View(Manually_Verified_Data)

A quick visual inspection of the Manually_Verified_Data dataset shows that some of the text in the SERVICENOTES field appears to be mis-categorized when that text was from the residnet, and not from the inspector. For example, “rats are everywhere” is classified as yes/rats_found. However, this is most likely a note from the resident requesting the service, and not the inspector.

To quickly remove this discrepancy, below I build a quick regex function to look for dates, as this seems to be a common indicator that the note was from the inspector and not from the resident. I then only use the Manually_Verified_Data that has notes from the inspector.

Note, that this crude regex function will also remove some instances of when the SERVICENOTES text appears to actually be from the inspector, but the inspector just did not add a date to his text. However, this was the quickest way to get to a viable dataset for confirmation. This process reduced the “ground truth” data from 400 rows to 273 rows.

regex_inspector_note <- "\\d{1,2}(/|-)\\d{1,2}(/|-)\\d{2,4}"
Mark_Inspector_Notes <- Manually_Verified_Data %>% 
  mutate(inspector_note = str_detect(SERVICENOTES,
                                     regex_inspector_note
                                     )
         )
# View(
  Mark_Inspector_Notes#)
Mark_Inspector_Notes %>% 
  count(inspector_note)
# View(
  Mark_Inspector_Notes %>% 
       filter(inspector_note == FALSE |
                is.na(inspector_note)
              ) %>% 
       arrange(inspector_note)#)
# View(
  Mark_Inspector_Notes %>% 
       filter(inspector_note == TRUE &
                rats_y_n == "y"
              ) %>% 
       sample_n(5) %>% 
       bind_rows(Mark_Inspector_Notes %>% 
                   filter(inspector_note == TRUE &
                            rats_y_n == "n"
                          ) %>% 
                   sample_n(5)
                 ) %>% 
       bind_rows(Mark_Inspector_Notes %>% 
                   filter(inspector_note == TRUE &
                            rats_y_n == "u"
                          ) %>% 
                   sample_n(5)
                 )#)
  
Verified_Inspector_Notes <- Mark_Inspector_Notes %>% 
  filter(inspector_note == TRUE)
nrow(Mark_Inspector_Notes)
[1] 400
nrow(Verified_Inspector_Notes)
[1] 273
# View(
Verified_Inspector_Notes#)

After left joining Verified_Inspector_Notes and Regex_Model_Compare, we can see that there are 384 rows as opposed to the 273 rows in Verified_Inspector_Notes.

Manual_Plus_ModelCompare <- Verified_Inspector_Notes %>% 
  left_join(Regex_Model_Compare,
            by = c("SERVICENOTES" = "servicenotes")
            ) %>% 
  mutate(outcome_manual = case_when(rats_y_n == "y" ~ "rats_found",
                                    rats_y_n == "n" ~ "no_rats_found",
                                    rats_y_n == "u" ~ "unknown",
                                    TRUE ~ "XXXX"
                                    )
         ) 
nrow(Verified_Inspector_Notes)
[1] 273
nrow(Manual_Plus_ModelCompare)
[1] 384
Manual_Plus_ModelCompare

After some inspection, we can se that both Verified_Inspector_Notes and Regex_Model_Compare have the same exact text entered multiple times.

Verified_Inspector_Notes %>%
  count(original_index) %>%
  arrange(desc(n)
          )
Verified_Inspector_Notes %>%
  count(SERVICENOTES) %>%
  arrange(desc(n)
          )
Regex_Model_Compare %>% 
  count(servicerequestid) %>% 
  arrange(desc(n)
          )
Regex_Model_Compare %>%
  count(servicenotes) %>%
  arrange(desc(n)
          )
Regex_Model_Compare %>%
  count(servicenotes_cleaned) %>%
  arrange(desc(n)
          )

Although this is an issue for the data overall, for the simple task of comparing the two regex models, we can get around this issue by simply selecting the distinct SERVICENOTES values.

Manual_Plus_ModelCompare_Distinct <-
  Manual_Plus_ModelCompare %>% 
  select(SERVICENOTES,
         servicenotes_cleaned,
         outcome_manual,
         outcome_sn_1,
         outcome_sn_2,
         outcome_snc_1,
         outcome_snc_2
         ) %>% 
  distinct()
Manual_Plus_ModelCompare_Distinct
# View(Manual_Plus_ModelCompare_Distinct)

So now we can get the accuracy statistics using caret::confusionMatrix.

CM_Sn1 <- confusionMatrix(Manual_Plus_ModelCompare_Distinct$outcome_sn_1,
                          Manual_Plus_ModelCompare_Distinct$outcome_manual,
                          positive = "rats_found",
                          mode = "everything"
                          )
CM_Sn2 <- confusionMatrix(Manual_Plus_ModelCompare_Distinct$outcome_sn_2,
                          Manual_Plus_ModelCompare_Distinct$outcome_manual,
                          positive = "rats_found",
                          mode = "everything"
                          )
CM_Snc1 <- confusionMatrix(Manual_Plus_ModelCompare_Distinct$outcome_snc_1,
                           Manual_Plus_ModelCompare_Distinct$outcome_manual,
                           positive = "rats_found",
                           mode = "everything"
                           )
CM_Snc2 <- confusionMatrix(Manual_Plus_ModelCompare_Distinct$outcome_snc_2,
                           Manual_Plus_ModelCompare_Distinct$outcome_manual,
                           positive = "rats_found",
                           mode = "everything"
                           )
CM_AllModels <- list(CM_Sn1, CM_Sn2, CM_Snc1, CM_Snc2)
rm(list = ls(pattern = "CM_S"))
names(CM_AllModels) <- c("CM_Sn1", "CM_Sn2", "CM_Snc1", "CM_Snc2")
CM_AllModels
$CM_Sn1
Confusion Matrix and Statistics

               Reference
Prediction      no_rats_found rats_found unknown
  no_rats_found           117          1       0
  rats_found                1        111       0
  unknown                   9          3       6

Overall Statistics
                                          
               Accuracy : 0.9435          
                 95% CI : (0.9071, 0.9688)
    No Information Rate : 0.5121          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.8965          
 Mcnemar's Test P-Value : 0.007383        

Statistics by Class:

                     Class: no_rats_found Class: rats_found Class: unknown
Sensitivity                        0.9213            0.9652        1.00000
Specificity                        0.9917            0.9925        0.95041
Pos Pred Value                     0.9915            0.9911        0.33333
Neg Pred Value                     0.9231            0.9706        1.00000
Precision                          0.9915            0.9911        0.33333
Recall                             0.9213            0.9652        1.00000
F1                                 0.9551            0.9780        0.50000
Prevalence                         0.5121            0.4637        0.02419
Detection Rate                     0.4718            0.4476        0.02419
Detection Prevalence               0.4758            0.4516        0.07258
Balanced Accuracy                  0.9565            0.9788        0.97521

$CM_Sn2
Confusion Matrix and Statistics

               Reference
Prediction      no_rats_found rats_found unknown
  no_rats_found            93          0       0
  rats_found                1         82       0
  unknown                  33         33       6

Overall Statistics
                                        
               Accuracy : 0.7298        
                 95% CI : (0.67, 0.7841)
    No Information Rate : 0.5121        
    P-Value [Acc > NIR] : 2.054e-12     
                                        
                  Kappa : 0.5816        
 Mcnemar's Test P-Value : 1.873e-14     

Statistics by Class:

                     Class: no_rats_found Class: rats_found Class: unknown
Sensitivity                        0.7323            0.7130        1.00000
Specificity                        1.0000            0.9925        0.72727
Pos Pred Value                     1.0000            0.9880        0.08333
Neg Pred Value                     0.7806            0.8000        1.00000
Precision                          1.0000            0.9880        0.08333
Recall                             0.7323            0.7130        1.00000
F1                                 0.8455            0.8283        0.15385
Prevalence                         0.5121            0.4637        0.02419
Detection Rate                     0.3750            0.3306        0.02419
Detection Prevalence               0.3750            0.3347        0.29032
Balanced Accuracy                  0.8661            0.8528        0.86364

$CM_Snc1
Confusion Matrix and Statistics

               Reference
Prediction      no_rats_found rats_found unknown
  no_rats_found           120          1       0
  rats_found                1        111       0
  unknown                   6          3       6

Overall Statistics
                                         
               Accuracy : 0.9556         
                 95% CI : (0.922, 0.9777)
    No Information Rate : 0.5121         
    P-Value [Acc > NIR] : < 2e-16        
                                         
                  Kappa : 0.9177         
 Mcnemar's Test P-Value : 0.02929        

Statistics by Class:

                     Class: no_rats_found Class: rats_found Class: unknown
Sensitivity                        0.9449            0.9652        1.00000
Specificity                        0.9917            0.9925        0.96281
Pos Pred Value                     0.9917            0.9911        0.40000
Neg Pred Value                     0.9449            0.9706        1.00000
Precision                          0.9917            0.9911        0.40000
Recall                             0.9449            0.9652        1.00000
F1                                 0.9677            0.9780        0.57143
Prevalence                         0.5121            0.4637        0.02419
Detection Rate                     0.4839            0.4476        0.02419
Detection Prevalence               0.4879            0.4516        0.06048
Balanced Accuracy                  0.9683            0.9788        0.98140

$CM_Snc2
Confusion Matrix and Statistics

               Reference
Prediction      no_rats_found rats_found unknown
  no_rats_found            94          0       0
  rats_found                1         82       0
  unknown                  32         33       6

Overall Statistics
                                          
               Accuracy : 0.7339          
                 95% CI : (0.6743, 0.7878)
    No Information Rate : 0.5121          
    P-Value [Acc > NIR] : 7.841e-13       
                                          
                  Kappa : 0.5866          
 Mcnemar's Test P-Value : 3.065e-14       

Statistics by Class:

                     Class: no_rats_found Class: rats_found Class: unknown
Sensitivity                        0.7402            0.7130        1.00000
Specificity                        1.0000            0.9925        0.73140
Pos Pred Value                     1.0000            0.9880        0.08451
Neg Pred Value                     0.7857            0.8000        1.00000
Precision                          1.0000            0.9880        0.08451
Recall                             0.7402            0.7130        1.00000
F1                                 0.8507            0.8283        0.15584
Prevalence                         0.5121            0.4637        0.02419
Detection Rate                     0.3790            0.3306        0.02419
Detection Prevalence               0.3790            0.3347        0.28629
Balanced Accuracy                  0.8701            0.8528        0.86570

All of the Confusion Matrix Statistics are provided above to review. To look at two common metrics, both the F1 value and the Kappa value both show the highest scores for using Regex Model 1 on the servicenotes_cleaned text.

CM_AllModels %>% 
  map_df(~ .x["byClass"][[1]][19:21]) %>% 
  rename(servicenotes_regex1 = CM_Sn1,
         servicenotes_regex2 = CM_Sn2,
         servicenotescleaned_regex1 = CM_Snc1,
         servicenotescleaned_regex2 = CM_Snc2
         ) %>% 
  t() %>% 
  set_colnames(c("f1_no_rats_found",
                 "f1_rats_found",
                 "f1_unknown"
                 )
               ) %>% 
  as.data.frame() %>% 
  rownames_to_column() %>% 
  separate(col = rowname,
           into = c("text_type", "regex_model"),
           sep = "_"
           )
CM_AllModels %>% 
  map_df(~ .["overall"][[1]][[2]]) %>% 
  rename(servicenotes_regex1 = CM_Sn1,
         servicenotes_regex2 = CM_Sn2,
         servicenotescleaned_regex1 = CM_Snc1,
         servicenotescleaned_regex2 = CM_Snc2
         ) %>% 
  t() %>% 
  set_colnames("kappa") %>% 
  as.data.frame() %>% 
  rownames_to_column() %>% 
  separate(col = rowname,
           into = c("text_type", "regex_model"),
           sep = "_"
           )
LS0tCnRpdGxlOiAiQ29tcGFyaW5nIFR3byBWZXJzaW9ucyBvZiBSZWdFeCBNb2RlbHMgdG8gUHVsbCBPdXQgUmF0IEZlYXR1cmVzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCiAgCiAgICAKICBMb2FkIHRoZSByZWxldmFudCBsaWJyYXJpZXMuCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKIyBybShsaXN0ID0gbHMoKSkKCgpsaWJyYXJ5KCJ0aWR5dmVyc2UiKSAgICAgICAgICAjIGRhdGEgbWFuaXB1bGF0aW9uCmxpYnJhcnkoIm1hZ3JpdHRyIikgICAgICAgICAgICMgZGF0YSBtYW5pcHVsYXRpb24gKHBpcGVpbmcgZGF0YSkKbGlicmFyeSgic3RyaW5nciIpICAgICAgICAgICAgIyBzdHJpbmcgbWFuaXB1bGF0aW9uCmxpYnJhcnkoInRpZHl0ZXh0IikgICAgICAgICAgICMgdGV4dCBtYW5pcHVsYXRpb24KbGlicmFyeSgiZ2dwbG90MiIpICAgICAgICAgICAgIyB2aXoKbGlicmFyeSgiY2FyZXQiKSAgICAgICAgICAgICAgIyBtZWFzdXJpbmcgbW9kZWwgcGVyZm9ybWFuY2UKCmBgYAogIAogICAgCiAgU2Vzc2lvbiBJbmZvLgpgYGB7cn0KCnNlc3Npb25JbmZvKCkKCmBgYAogIAogICAgCiAgU2V0dXAgdGhlIHJvb3QgZGlyZWN0b3J5LgpgYGB7ciAic2V0dXAiLCBpbmNsdWRlID0gRkFMU0V9CgpyZXF1aXJlKCJrbml0ciIpCgpvcHRzX2tuaXQkc2V0KHJvb3QuZGlyID0gIi9Vc2Vycy9tZHR1cnNlL0Rlc2t0b3AvQW5hbHl0aWNzL2RjX2RvaF9oYWNrYXRob24iKQoKYGBgCiAgCiAgICAKICBTZXR0aW5nIGB3ZGAgYXMgdGhlIHdvcmtpbmcgZGlyZWN0b3J5LgpgYGB7cn0KCndkIDwtIGdldHdkKCkKCndkCgpgYGAKICAKICAgIAogIEZyb20gdGhlIHByaW9yIGFuYWx5c2VzIGFzIHBhcnQgb2YgYW5hbHlzZXMsIHdlJ2xsIGZpcnN0IGxvYWQgdGhlIGBTZXJ2aWNlTm90ZXNDbGVhbmVkMmAgZGF0YXNldC4gVGhpcyBpcyBhIGRhdGFzZXQgdGhhdCBwZXJmb3JtcyBzb21lIGJhc2ljYWxseSB0ZXh0IGNsZWFuaW5nIChlLmcuLCByZW1vdmluZyBzdG9wIHdvcmRzLCByZW1vdmluZyBudW1lcmljcywgcmVtb3ZpbmcgcHVuY3R1YXRpb24sIGV0Yy4pLiAgCiAgCiAgTm90ZSB0aGF0IHRoaXMgZGF0YXNldCBoYXMgYm90aCB0aGUgb3JpZ2luYWwgdGV4dCBmaWVsZCAoYHNlcnZpY2Vub3Rlc2ApIGFzIHdlbGwgYXMgdGhlIGNsZWFuZWQgdGV4dCBmaWVsZCAoYHNlcnZpY2Vub3Rlc19jbGVhbmVkYCkuCmBgYHtyfQoKQ2xlYW5lZF9EYXRhIDwtIHJlYWRSRFMocGFzdGUwKHdkLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIi9EYXRhX1Byb2Nlc3NlZC8iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlNlcnZpY2VOb3Rlc0NsZWFuZWQyLlJkcyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICAgICAgICAgKQoKc3RyKENsZWFuZWRfRGF0YSkKQ2xlYW5lZF9EYXRhCiMgVmlldyhoZWFkKENsZWFuZWRfRGF0YSwgMTAwMCkpCgpgYGAKICAKICAgIAogIFByaW9yIGFuYWx5c2VzIGNyZWF0ZWQgdHdvIHZlcnNpb25zIG9mIHJlZ2V4IGZ1bmN0aW9ucyB0byBkZXRlcm1pbmUgaWYgdGhlIHJlc3VsdCBvZiB0aGUgcmF0IGluc3BlY3Rpb24gd2FzIGByYXRzX2ZvdW5kYCwgYG5vX3JhdHNfZm91bmRgLCBhbmQgYHVua25vd25gLiBIZXJlLCB3ZSdsbCBzcGVjaWZ5IHRob3NlIGZ1bmN0aW9ucyBiZWZvcmUgY29tcGFyaW5nIGhvdyB0aGV5IGFncmVlL2Rpc2FncmVlLiAgCiAgIAogIEZyb20gdGhlIHByaW9yIGFuYWx5c2VzLCBmb3IgYHJlZ2V4XzFfcmF0c2ZvdW5kYCwgSSBhZGRlZCB0aGUgcmVnZXggbG9vay1iZWhpbmQgZm9yICJubyIgInJhdHMgZm91bmQiLiBBbHNvLCBmb3IgYHJlZ2V4XzFfbm9yYXRzZm91bmRgLCBJIGFkZGVkIHRoZSByZWdleCBmb3IgIm5vIGFjdGl2aXR5Ii4KYGBge3J9CgpyZWdleF8xX3JhdHNmb3VuZCA8LSAiKGEpezAsMX1iYShpKXswLDF9dGVkfGJsb2NrcyBlcGEoICl7MCwxfXxkaXRyYWN8KCApezAsMX1lcGEoICl7MCwxfXwoPzwhbm8gKXJhdChzKXswLDF9IGJ1cnJvd3MgZm91bmR8cmV2ZWFsIHJhdCBidXJyb3dzfHJhdCBidXJyb3dzIChufHIpZWFyIHByb3BlcnR5fHNvZnQgYmFpdCIKCgpyZWdleF8xX25vcmF0c2ZvdW5kIDwtICJubyByYXQocyl7MCwxfXxubyByb2RlbnR8bm8gYWN0aW9ufG5vIChhY3RpdmUgKXswLDF9YnVycm93KHMpezAsMX18bm8gYWN0aXZpdHl8bm8odCl7MCwxfSBldmlkZW4oY2UpezAsMX0odHMpezAsMX18bm8gc2lnbihzKXswLDF9IHJhdChzKXswLDF9fG5vIHNpZ24ocyl7MCwxfXxubyh0KXswLDF9IGZpbmQiCgpyZWdleF8yX3JhdHNmb3VuZCA8LSAiKChiYWl0ZWR8Zm91bmQpICooW1xcZF0qfG9uZXx0d298dGhyZWV8Zm91cnxmaXZlfHNpeHxzZXZlbnxlaWdodHxuaW5lfHRlbikgKihyYXR8YnVycm93KSl8KCBiYWl0ZWQgKSIKCnJlZ2V4XzJfbm9yYXRzZm91bmQgPC0gImZvdW5kIG5vIChyYXR8YWN0aXZpdHl8cm9kZW50fGV2aWRlbmNlKSIKCmBgYAogIAogICAgCiAgICBOb3cgd2UnbGwgY3JlYXRlIHRoZSB2YXJpYWJsZXMgd2UgbmVlZCB0byBkbyB0aGUgY29tcGFyaXNvbnMuCmBgYHtyfQoKUmVnZXhfTW9kZWxfQ29tcGFyZSA8LSBDbGVhbmVkX0RhdGEgJT4lIAogIHNlbGVjdChzZXJ2aWNlcmVxdWVzdGlkLAogICAgICAgICBzZXJ2aWNlb3JkZXJfZGF0ZSwKICAgICAgICAgc2VydmljZW9yZGVyX3lyX3Bvc2l4LAogICAgICAgICBzZXJ2aWNlb3JkZXJfeXJtdGhfcG9zaXgsCiAgICAgICAgIHNlcnZpY2Vub3RlcywKICAgICAgICAgc2VydmljZW5vdGVzX2NsZWFuZWQKICAgICAgICAgKSAlPiUKICBtdXRhdGUoc25fMV9yYXRzZm91bmQgPSBzdHJfZGV0ZWN0KHN0cl90b19sb3dlcihzZXJ2aWNlbm90ZXMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVnZXhfMV9yYXRzZm91bmQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgIHNuXzFfbm9yYXRzZm91bmQgPSBzdHJfZGV0ZWN0KHN0cl90b19sb3dlcihzZXJ2aWNlbm90ZXMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWdleF8xX25vcmF0c2ZvdW5kCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgIHNuXzJfcmF0c2ZvdW5kID0gc3RyX2RldGVjdChzdHJfdG9fbG93ZXIoc2VydmljZW5vdGVzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZ2V4XzJfcmF0c2ZvdW5kCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICBzbl8yX25vcmF0c2ZvdW5kID0gc3RyX2RldGVjdChzdHJfdG9fbG93ZXIoc2VydmljZW5vdGVzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVnZXhfMl9ub3JhdHNmb3VuZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICBzbmNfMV9yYXRzZm91bmQgPSBzdHJfZGV0ZWN0KHNlcnZpY2Vub3Rlc19jbGVhbmVkLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZ2V4XzFfcmF0c2ZvdW5kCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgc25jXzFfbm9yYXRzZm91bmQgPSBzdHJfZGV0ZWN0KHNlcnZpY2Vub3Rlc19jbGVhbmVkLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVnZXhfMV9ub3JhdHNmb3VuZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgc25jXzJfcmF0c2ZvdW5kID0gc3RyX2RldGVjdChzZXJ2aWNlbm90ZXNfY2xlYW5lZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWdleF8yX3JhdHNmb3VuZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgIHNuY18yX25vcmF0c2ZvdW5kID0gc3RyX2RldGVjdChzZXJ2aWNlbm90ZXNfY2xlYW5lZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZ2V4XzJfbm9yYXRzZm91bmQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgIG91dGNvbWVfc25fMSA9IGNhc2Vfd2hlbihzbl8xX3JhdHNmb3VuZCA9PSBUUlVFICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc25fMV9ub3JhdHNmb3VuZCA9PSBGQUxTRSB+ICJyYXRzX2ZvdW5kIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNuXzFfcmF0c2ZvdW5kID09IEZBTFNFICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc25fMV9ub3JhdHNmb3VuZCA9PSBUUlVFIH4gIm5vX3JhdHNfZm91bmQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+ICJ1bmtub3duIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgb3V0Y29tZV9zbl8yID0gY2FzZV93aGVuKHNuXzJfcmF0c2ZvdW5kID09IFRSVUUgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzbl8yX25vcmF0c2ZvdW5kID09IEZBTFNFIH4gInJhdHNfZm91bmQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc25fMl9yYXRzZm91bmQgPT0gRkFMU0UgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzbl8yX25vcmF0c2ZvdW5kID09IFRSVUUgfiAibm9fcmF0c19mb3VuZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gInVua25vd24iCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICBvdXRjb21lX3NuY18xID0gY2FzZV93aGVuKHNuY18xX3JhdHNmb3VuZCA9PSBUUlVFICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNuY18xX25vcmF0c2ZvdW5kID09IEZBTFNFIH4gInJhdHNfZm91bmQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNuY18xX3JhdHNmb3VuZCA9PSBGQUxTRSAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzbmNfMV9ub3JhdHNmb3VuZCA9PSBUUlVFIH4gIm5vX3JhdHNfZm91bmQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiAidW5rbm93biIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICBvdXRjb21lX3NuY18yID0gY2FzZV93aGVuKHNuY18yX3JhdHNmb3VuZCA9PSBUUlVFICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNuY18yX25vcmF0c2ZvdW5kID09IEZBTFNFIH4gInJhdHNfZm91bmQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNuY18yX3JhdHNmb3VuZCA9PSBGQUxTRSAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzbmNfMl9ub3JhdHNmb3VuZCA9PSBUUlVFIH4gIm5vX3JhdHNfZm91bmQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiAidW5rbm93biIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICByZWdleF9jb21wYXJlX3Nuc25jXzEgPSBjYXNlX3doZW4ob3V0Y29tZV9zbl8xID09ICJyYXRzX2ZvdW5kIiAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dGNvbWVfc25jXzEgPT0gInJhdHNfZm91bmQiIH4gIm1hdGNoX3JhdHMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3V0Y29tZV9zbl8xID09ICJub19yYXRzX2ZvdW5kIiAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dGNvbWVfc25jXzEgPT0gIm5vX3JhdHNfZm91bmQiIH4gIm1hdGNoX25vcmF0cyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdXRjb21lX3NuXzEgPT0gInVua25vd24iICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3V0Y29tZV9zbmNfMSA9PSAidW5rbm93biIgfiAibWF0Y2hfdW5rbm93biIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gIm1pc21hdGNoIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgcmVnZXhfY29tcGFyZV9zbnNuY18yID0gY2FzZV93aGVuKG91dGNvbWVfc25fMiA9PSAicmF0c19mb3VuZCIgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdXRjb21lX3NuY18yID09ICJyYXRzX2ZvdW5kIiB+ICJtYXRjaF9yYXRzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dGNvbWVfc25fMiA9PSAibm9fcmF0c19mb3VuZCIgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdXRjb21lX3NuY18yID09ICJub19yYXRzX2ZvdW5kIiB+ICJtYXRjaF9ub3JhdHMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3V0Y29tZV9zbl8yID09ICJ1bmtub3duIiAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dGNvbWVfc25jXzIgPT0gInVua25vd24iIH4gIm1hdGNoX3Vua25vd24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+ICJtaXNtYXRjaCIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgIHJlZ2V4X2NvbXBhcmVfc25fMTIgPSBjYXNlX3doZW4ob3V0Y29tZV9zbl8xID09ICJyYXRzX2ZvdW5kIiAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdXRjb21lX3NuXzIgPT0gInJhdHNfZm91bmQiIH4gIm1hdGNoX3JhdHMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dGNvbWVfc25fMSA9PSAibm9fcmF0c19mb3VuZCIgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3V0Y29tZV9zbl8yID09ICJub19yYXRzX2ZvdW5kIiB+ICJtYXRjaF9ub3JhdHMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dGNvbWVfc25fMSA9PSAidW5rbm93biIgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3V0Y29tZV9zbl8yID09ICJ1bmtub3duIiB+ICJtYXRjaF91bmtub3duIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gIm1pc21hdGNoIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgIHJlZ2V4X2NvbXBhcmVfc25jXzEyID0gY2FzZV93aGVuKG91dGNvbWVfc25jXzEgPT0gInJhdHNfZm91bmQiICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdXRjb21lX3NuY18yID09ICJyYXRzX2ZvdW5kIiB+ICJtYXRjaF9yYXRzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3V0Y29tZV9zbmNfMSA9PSAibm9fcmF0c19mb3VuZCIgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dGNvbWVfc25jXzIgPT0gIm5vX3JhdHNfZm91bmQiIH4gIm1hdGNoX25vcmF0cyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dGNvbWVfc25jXzEgPT0gInVua25vd24iICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdXRjb21lX3NuY18yID09ICJ1bmtub3duIiB+ICJtYXRjaF91bmtub3duIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+ICJtaXNtYXRjaCIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICApCgpzdHIoUmVnZXhfTW9kZWxfQ29tcGFyZSkKaGVhZChSZWdleF9Nb2RlbF9Db21wYXJlLCAxMDApCiMgVmlldyhoZWFkKFJlZ2V4X01vZGVsX0NvbXBhcmUsIDEwMDApKQoKYGBgCiAgCiAgICAKICBBcyBhIGZpcnN0IGNoZWNrLCBsZXQncyBzZWUgaWYgdGhlIHR3byByZWdleCBmdW5jdGlvbnMgcHJvdmlkZSB0aGUgc2FtZSByZXN1bHQgd2hldGhlciB1c2VkIG9uIHRoZSB1bm1vZGlmaWVkIGBzZXJ2aWNlbm90ZXNgIGZpZWxkIG9yIHVzZWQgb24gdGhlIGBzZXJ2aWNlbm90ZXNfY2xlYW5lZGAgZmllbGQuIFRvIGRvIHRoaXMsIHdlIGNhbiB1c2UgdGhlIG5ld2x5IGNyZWF0ZWQgYHJlZ2V4X2NvbXBhcmVfc25zbmNfMWAgYW5kIGByZWdleF9jb21wYXJlX3Nuc25jXzJgIGZpZWxkcy4gIAogICAgCiAgRmlyc3QsIGxldCdzIGNyZWF0ZSBhIGJhc2ljIGdncGxvdCB0aGVtZS4KYGBge3J9CgpnZ3Bsb3RfdGhlbWVfYmFzaWMgPC0KICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfbGluZShzaXplID0gMSwgY29sb3VyID0gImJsYWNrIikKICAgICAgICApCgoKYGBgCiAgCiAgICAKICBOb3cgZm9yIHRoZSBwbG90dGluZy9pbnNwZWN0aW9uLiAgCiAgICAKICBJdCBsb29rcyBsaWtlIHRoZSBidWxrIG9mIGluc3BlY3Rpb25zIChhbHRob3VnaCBub3QgYWxsKSBhcmUgY2xhc3NpZmllZCB0aGUgc2FtZSB3YXkgcmVnYXJkbGVzcyBvZiB3aGV0aGVyIHRoZSByZWdleCBpcyBydW4gb24gdGhlIHJhdyB0ZXh0IChgc2VydmljZW5vdGVzYCkgb3IgdGhlIGNsZWFuZWQgdGV4dCAoYHNlcnZpY2Vub3Rlc19jbGVhbmVkYCkuICBBbHNvLCB3aGVuIGNvbXBhcmluZyB0aGUgbnVtYmVyIG9mIG1pc21hdGNoZXMgYmV0d2VlbiB1c2luZyByYXcgdGV4dCBhbmQgY2xlYW5lZCB0ZXh0LCBNb2RlbCAyIGhhcyBsZXNzIG1pc21hdGNoZXMgdGhhbiBNb2RlbCAxLgpgYGB7cn0KClNuc25jX0NvdW50c18xIDwtIFJlZ2V4X01vZGVsX0NvbXBhcmUgJT4lIAogIGNvdW50KHJlZ2V4X2NvbXBhcmVfc25zbmNfMSkgJT4lIAogIG11dGF0ZShuX3BjdCA9IG4gLyBzdW0obikKICAgICAgICAgKQoKU25zbmNfQ291bnRzXzIgPC0gUmVnZXhfTW9kZWxfQ29tcGFyZSAlPiUgCiAgY291bnQocmVnZXhfY29tcGFyZV9zbnNuY18yKSAlPiUgCiAgbXV0YXRlKG5fcGN0ID0gbiAvIHN1bShuKQogICAgICAgICApCgoKQ291bnRzX0NvbWJpbmVkIDwtIFNuc25jX0NvdW50c18xICU+JSAKICByZW5hbWUobWF0Y2hfc3RhdHVzID0gcmVnZXhfY29tcGFyZV9zbnNuY18xKSAlPiUgCiAgbXV0YXRlKG1vZGVsX3R5cGUgPSAicmVnZXhfMSIpICU+JSAKICBiaW5kX3Jvd3MoU25zbmNfQ291bnRzXzIgJT4lIAogICAgICAgICAgICAgIHJlbmFtZShtYXRjaF9zdGF0dXMgPSByZWdleF9jb21wYXJlX3Nuc25jXzIpICU+JSAKICAgICAgICAgICAgICBtdXRhdGUobW9kZWxfdHlwZSA9ICJyZWdleF8yIikKICAgICAgICAgICAgKQoKcm0obGlzdCA9IGxzKHBhdHRlcm4gPSAiU25zbmNfQ291bnRzXyIpKQoKCgpnZ3Bsb3QoZGF0YSA9IENvdW50c19Db21iaW5lZCwKICAgICAgIGFlcyh4ID0gbWF0Y2hfc3RhdHVzLAogICAgICAgICAgIHkgPSBuX3BjdCwKICAgICAgICAgICBmaWxsID0gbWF0Y2hfc3RhdHVzCiAgICAgICAgICAgKQogICAgICAgKSArCiAgZ2VvbV9jb2woKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKG5fcGN0LCAzKQogICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgbnVkZ2VfeSA9IC4wNywKICAgICAgICAgICAgc2l6ZSA9IDMKICAgICAgICAgICAgKSArCiAgZ2dwbG90X3RoZW1lX2Jhc2ljICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICBsYWJzKHRpdGxlID0gIkNvbXBhcmluZyBUZXh0IFR5cGUgQWNyb3NzIFJlZ2V4IE1vZGVscyIsCiAgICAgICBzdWJ0aXRsZSA9ICJ0ZXh0IHR5cGUgaXMgc2VydmljZW5vdGVzIG9yIHNlcnZpY2Vub3Rlc19jbGVhbmVkIiwKICAgICAgIHggPSAiVGV4dCBUeXBlIE1hdGNoIFN0YXR1cyIsCiAgICAgICB5ID0gIkNvdW50cyAocGN0KSIKICAgICAgICkgKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDEpLAogICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzZXEoMCwgMSwgLjIpCiAgICAgICAgICAgICAgICAgICAgICkgKwogIGZhY2V0X3dyYXAofiBtb2RlbF90eXBlKSArCiAgY29vcmRfZmxpcCgpCgpnZ3NhdmUocGFzdGUwKHdkLAogICAgICAgICAgICAgICIvVml6LyIsCiAgICAgICAgICAgICAgIkNvbXBhcmVfVGV4dFR5cGVfQWNyb3NzX01vZGVscy5wbmciCiAgICAgICAgICAgICAgKSwKICAgICAgIHNjYWxlID0gNCwKICAgICAgIHdpZHRoID0gNiwKICAgICAgIGhlaWdodCA9IDYsCiAgICAgICB1bml0cyA9ICJjbSIKICAgICAgICkKCnJtKENvdW50c19Db21iaW5lZCkKCmBgYAogIAogICAgCiAgQXMgdGhlIGFtb3VudCBvZiBtaXNtYXRjaGVzIGJldHdlZW4gcnVubmluZyB0aGUgcmVnZXggbW9kZWxzIG9uIHJhdyB0ZXh0IGFuZCBvbiBjbGVhbmVkIHRleHQgaXMgbWluaW1hbCwgYW5kIGFzIHRoZSBtb3JlIHZhbHVhYmxlIG1ldHJpYyBpcyBjb21wYXJpbmcgaG93IGFjY3VyYXRlIE1vZGVsIDEgYW5kIE1vZGVsIDIgYXJlLCBsZXQncyBtb3ZlIG9uIHRvIHRoYXQgbm93LiAgCiAgICAKICBBcyBhIGZpcnN0IHN0ZXAsIHdlIGNhbiBzYXkgdGhhdCB0aGUgbW9kZWxzIGNvaW5jaWRlIH44MCUgb2YgdGhlIHRpbWUgKHJlZ2FyZGxlc3Mgb2Ygd2hldGhlciBgc2VydmljZW5vdGVzYCBpcyB1c2VkIG9yIGBzZXJ2aWNlbm90ZXNfY2xlYW5lZGAgaXMgdXNlZCkuCmBgYHtyfQoKU25fQ291bnRzIDwtIFJlZ2V4X01vZGVsX0NvbXBhcmUgJT4lIAogIGNvdW50KHJlZ2V4X2NvbXBhcmVfc25fMTIpICU+JSAKICBtdXRhdGUobl9wY3QgPSBuIC8gc3VtKG4pCiAgICAgICAgICkKClNuY19Db3VudHMgPC0gUmVnZXhfTW9kZWxfQ29tcGFyZSAlPiUgCiAgY291bnQocmVnZXhfY29tcGFyZV9zbmNfMTIpICU+JSAKICBtdXRhdGUobl9wY3QgPSBuIC8gc3VtKG4pCiAgICAgICAgICkKCkNvdW50c19Db21iaW5lZCA8LSBTbl9Db3VudHMgJT4lIAogIHJlbmFtZShtYXRjaF9zdGF0dXMgPSByZWdleF9jb21wYXJlX3NuXzEyKSAlPiUgCiAgbXV0YXRlKHRleHRfdHlwZSA9ICJzZXJ2aWNlbm90ZXNfcmF3IikgJT4lIAogIGJpbmRfcm93cyhTbmNfQ291bnRzICU+JSAKICAgICAgICAgICAgICByZW5hbWUobWF0Y2hfc3RhdHVzID0gcmVnZXhfY29tcGFyZV9zbmNfMTIpICU+JSAKICAgICAgICAgICAgICBtdXRhdGUodGV4dF90eXBlID0gInNlcnZpY2Vub3Rlc19jbGVhbmVkIikKICAgICAgICAgICAgKQoKcm0obGlzdCA9IGxzKHBhdHRlcm4gPSAiX0NvdW50cyIpKQoKCgpnZ3Bsb3QoZGF0YSA9IENvdW50c19Db21iaW5lZCwKICAgICAgIGFlcyh4ID0gbWF0Y2hfc3RhdHVzLAogICAgICAgICAgIHkgPSBuX3BjdCwKICAgICAgICAgICBmaWxsID0gbWF0Y2hfc3RhdHVzCiAgICAgICAgICAgKQogICAgICAgKSArCiAgZ2VvbV9jb2woKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKG5fcGN0LCAzKQogICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgbnVkZ2VfeSA9IC4wNywKICAgICAgICAgICAgc2l6ZSA9IDMKICAgICAgICAgICAgKSArCiAgZ2dwbG90X3RoZW1lX2Jhc2ljICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICBsYWJzKHRpdGxlID0gIkNvbXBhcmluZyBSZWdleCBNb2RlbHMgQWNyb3NzIFRleHQgVHlwZXMiLAogICAgICAgc3VidGl0bGUgPSAicmVnZXggdHlwZSBpcyByZWdleF8xIG9yIHJlZ2V4XzIiLAogICAgICAgeCA9ICJSZWdleCBUeXBlIE1hdGNoIFN0YXR1cyIsCiAgICAgICB5ID0gIkNvdW50cyAocGN0KSIKICAgICAgICkgKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDEpLAogICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzZXEoMCwgMSwgLjIpCiAgICAgICAgICAgICAgICAgICAgICkgKwogIGZhY2V0X3dyYXAofiB0ZXh0X3R5cGUpICsKICBjb29yZF9mbGlwKCkKCmdnc2F2ZShwYXN0ZTAod2QsCiAgICAgICAgICAgICAgIi9WaXovIiwKICAgICAgICAgICAgICAiQ29tcGFyZV9Nb2RlbHNfQWNyb3NzX1RleHRUeXBlLnBuZyIKICAgICAgICAgICAgICApLAogICAgICAgc2NhbGUgPSA0LAogICAgICAgd2lkdGggPSA2LAogICAgICAgaGVpZ2h0ID0gNiwKICAgICAgIHVuaXRzID0gImNtIgogICAgICAgKQoKcm0oQ291bnRzX0NvbWJpbmVkKQoKYGBgCiAgCiAgICAKICBTbyBub3cgbGV0J3MgZGlnIGludG8gc29tZSBtaXNtYXRjaGVzLiAgCiAgICAKICBJdCBsb29rcyBsaWtlIHRoZSBidWxrIG9mIHRoZSBtaXNtYXRjaGVzICh+OTglKSBhcmUgd2hlbiB0aGUgTW9kZWwgMiBpcyBgdW5rbm93bmAgYW5kIE1vZGVsIDEgaXMgZWl0aGVyIGByYXRzX2ZvdW5kYCBvciBgbm9fcmF0c19mb3VuZGAuICAKYGBge3J9CgpSZWdleF9Nb2RlbF9Db21wYXJlICU+JSAKICBmaWx0ZXIocmVnZXhfY29tcGFyZV9zbmNfMTIgPT0gIm1pc21hdGNoIikgJT4lIAogIG11dGF0ZShvdXRjb21lXzFfXzIgPSBwYXN0ZTAob3V0Y29tZV9zbmNfMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIl9fIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3V0Y29tZV9zbmNfMikKICAgICAgICAgKSAlPiUgCiAgY291bnQob3V0Y29tZV8xX18yKSAlPiUgCiAgbXV0YXRlKG5fcGN0ID0gbi9zdW0obikKICAgICAgICAgKSAlPiUgCiAgYXJyYW5nZShkZXNjKG5fcGN0KQogICAgICAgICAgKQoKCiMgVmlldygKICBSZWdleF9Nb2RlbF9Db21wYXJlICU+JSAKICAgIGZpbHRlcihyZWdleF9jb21wYXJlX3NuY18xMiA9PSAibWlzbWF0Y2giICYKICAgICAgICAgICAgIG91dGNvbWVfc25jXzIgPT0gInVua25vd24iCiAgICAgICAgICAgKSAlPiUgCiAgICBzZWxlY3Qoc2VydmljZXJlcXVlc3RpZCwKICAgICAgICAgICBzZXJ2aWNlb3JkZXJfZGF0ZSwKICAgICAgICAgICBzZXJ2aWNlbm90ZXMsCiAgICAgICAgICAgc2VydmljZW5vdGVzX2NsZWFuZWQsCiAgICAgICAgICAgb3V0Y29tZV9zbmNfMSwKICAgICAgICAgICBvdXRjb21lX3NuY18yKSAlPiUgCiAgICBzYW1wbGVfbigxNSkgJT4lIAogICAgYXJyYW5nZShvdXRjb21lX3NuY18xLAogICAgICAgICAgICBvdXRjb21lX3NuY18yCiAgICAgICAgICAgICkjKQoKYGBgCiAgCiAgICAKICBUaGVyZSBhcmUgc29tZSBtaXNtYXRjaGVzICh+MS41JSkgd2hlbiBNb2RlbCAyIGlzIGByYXRzX2ZvdW5kYCBvciBgbm9fcmF0c19mb3VuZGAgYW5kIE1vZGVsIDEgaXMgYHVua25vd25gLiBUaGlzIGFwcGVhcnMgdG8gYmUgdGhlIGNhc2Ugd2hlbiB0aGUgdGV4dCB0cmlnZ2VycyBCT1RIIHRoZSByZWdleCBmb3IgYHJhdHNfZm91bmRgIGFuZCBgbm9fcmF0c19mb3VuZGAuICAKICAgIAogIFRoaXMgdGVuZHMgdG8gaGFwcGVuIGVpdGhlciB3aGVuIHRoZSB0ZXh0IGRlc2NyaWJlcyB0aGUgaW5zcGVjdG9yIGJhaXRpbmcgb25lIHJlc2lkZW5jZSwgYnV0IG5vdCBmaW5kaW5nIGV2aWRlbmNlIG9mIHJhdHMgYXQgYSBuZWlnaGJvcmluZyByZXNpZGVuY2UgaW5zcGVjdGVkIGF0IHRoZSBzYW1lIHRpbWUgKGUuZy4sICJPbiA4LzE4LzE1IEAgOTo1N2FtIEMuIFJlZG1hbiBmb3VuZCBubyByYXQgYnVycm93cyBmb3VuZCBvbiB0aGUgcHJvcGVydHksIFJhdCBidXJyb3dzIGZvdW5kIGluIGZyb250IG9mIDIzNjMgQ2hhbXBsYWluIFN0cmVldC4sIE4uVy4iKS4gIAogICAgCiAgVGhpcyBhbHNvIGhhcHBlbnMgd2hlbiB0aGUgdGV4dCBkZXNjcmliZXMgbm8gcmF0cyBiZWluZyBmb3VuZCBkdXJpbmcgdGhlIGluc3BlY3Rpb24sIGJ1dCBhIHBlc3QgY29udHJvbGxlciBoYXZpbmcgZG9uZSBhYmF0ZW1lbnQgKGUuZy4sICJPbiA3LzEvMTUgQCAxMDo1OEFNIFIuIEhlcnJpbmd0b24gZm91bmQgbm8gcmF0IGhvbGVzIGluIHRoZSBhbGxleSwgcGVzdCBjb250cm9sbGVyIGhhcyBhYmF0ZWQgYXJlYSIpLgpgYGB7cn0KCiMgVmlldygKICBSZWdleF9Nb2RlbF9Db21wYXJlICU+JSAKICAgIGZpbHRlcihyZWdleF9jb21wYXJlX3NuY18xMiA9PSAibWlzbWF0Y2giICYKICAgICAgICAgICAgIG91dGNvbWVfc25jXzIgPT0gInJhdHNfZm91bmQiCiAgICAgICAgICAgKSAlPiUgCiAgICBzYW1wbGVfbig3KSAlPiUgCiAgICBhcnJhbmdlKG91dGNvbWVfc25jXzEpICU+JSAKICAgIGJpbmRfcm93cyhSZWdleF9Nb2RlbF9Db21wYXJlICU+JSAKICAgICAgICAgICAgICAgIGZpbHRlcihyZWdleF9jb21wYXJlX3NuY18xMiA9PSAibWlzbWF0Y2giICYKICAgICAgICAgICAgICAgICAgICAgICAgIG91dGNvbWVfc25jXzIgPT0gIm5vX3JhdHNfZm91bmQiCiAgICAgICAgICAgICAgICAgICAgICAgKSAlPiUgCiAgICAgICAgICAgICAgICBzYW1wbGVfbig3KSAlPiUgCiAgICAgICAgICAgICAgICBhcnJhbmdlKG91dGNvbWVfc25jXzEpCiAgICAgICAgICAgICAgKSAlPiUgCiAgICBzZWxlY3Qoc2VydmljZXJlcXVlc3RpZCwKICAgICAgICAgICBzZXJ2aWNlb3JkZXJfZGF0ZSwKICAgICAgICAgICBzZXJ2aWNlbm90ZXMsCiAgICAgICAgICAgc2VydmljZW5vdGVzX2NsZWFuZWQsCiAgICAgICAgICAgb3V0Y29tZV9zbmNfMSwKICAgICAgICAgICBvdXRjb21lX3NuY18yCiAgICAgICAgICAgKSMpCgpgYGAKICAKICAgIAogIE1hbnVhbGx5IHZlcmlmaWVkIGRhdGEgd2VyZSBjcmVhdGVkIGFzIHBhcnQgb2YgW0dpdEh1YiBJc3N1ZSAjMjddKGh0dHBzOi8vZ2l0aHViLmNvbS9lY2xlZTI1L3RoZS1yYXQtaGFjay9wdWxsLzI5KSwgd2l0aCB0aGUgcmVzdWx0aW5nIGRhdGFzZXQgZm91bmQgW2hlcmVdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9hdXN0aW5icmlhbi90aGUtcmF0LWhhY2svY2E1MWY2Mjc1ZTM0YjdhN2M2Y2NmM2QxOTEyMTYwZjdmODZkOWQ3Mi9zY3JpcHRzL2ZlYXR1cmVfZW5naW5lZXJpbmcvc3VwZXJ2aXNlZF9kYXRhLmNzdikuIFNvIGhlcmUsIHdlIHNpbXBseSBwdWxsIGluIHRob3NlIGRhdGEuCmBgYHtyfQoKTWFudWFsbHlfVmVyaWZpZWRfRGF0YSA8LSByZWFkX2NzdigiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2F1c3RpbmJyaWFuL3RoZS1yYXQtaGFjay9jYTUxZjYyNzVlMzRiN2E3YzZjY2YzZDE5MTIxNjBmN2Y4NmQ5ZDcyL3NjcmlwdHMvZmVhdHVyZV9lbmdpbmVlcmluZy9zdXBlcnZpc2VkX2RhdGEuY3N2IikKCnN0cihNYW51YWxseV9WZXJpZmllZF9EYXRhKQpNYW51YWxseV9WZXJpZmllZF9EYXRhCiMgVmlldyhNYW51YWxseV9WZXJpZmllZF9EYXRhKQoKYGBgCiAgCiAgICAKICBBIHF1aWNrIHZpc3VhbCBpbnNwZWN0aW9uIG9mIHRoZSBgTWFudWFsbHlfVmVyaWZpZWRfRGF0YWAgZGF0YXNldCBzaG93cyB0aGF0IHNvbWUgb2YgdGhlIHRleHQgaW4gdGhlIGBTRVJWSUNFTk9URVNgIGZpZWxkIGFwcGVhcnMgdG8gYmUgbWlzLWNhdGVnb3JpemVkIHdoZW4gdGhhdCB0ZXh0IHdhcyBmcm9tIHRoZSByZXNpZG5ldCwgYW5kIG5vdCBmcm9tIHRoZSBpbnNwZWN0b3IuIEZvciBleGFtcGxlLCAicmF0cyBhcmUgZXZlcnl3aGVyZSIgaXMgY2xhc3NpZmllZCBhcyB5ZXMvcmF0c19mb3VuZC4gSG93ZXZlciwgdGhpcyBpcyBtb3N0IGxpa2VseSBhIG5vdGUgZnJvbSB0aGUgcmVzaWRlbnQgcmVxdWVzdGluZyB0aGUgc2VydmljZSwgYW5kIG5vdCB0aGUgaW5zcGVjdG9yLiAgCiAgICAKICBUbyBxdWlja2x5IHJlbW92ZSB0aGlzIGRpc2NyZXBhbmN5LCBiZWxvdyBJIGJ1aWxkIGEgcXVpY2sgcmVnZXggZnVuY3Rpb24gdG8gbG9vayBmb3IgZGF0ZXMsIGFzIHRoaXMgc2VlbXMgdG8gYmUgYSBjb21tb24gaW5kaWNhdG9yIHRoYXQgdGhlIG5vdGUgd2FzIGZyb20gdGhlIGluc3BlY3RvciBhbmQgbm90IGZyb20gdGhlIHJlc2lkZW50LiBJIHRoZW4gb25seSB1c2UgdGhlIGBNYW51YWxseV9WZXJpZmllZF9EYXRhYCB0aGF0IGhhcyBub3RlcyBmcm9tIHRoZSBpbnNwZWN0b3IuICAKICAgIAogIE5vdGUsIHRoYXQgdGhpcyBjcnVkZSByZWdleCBmdW5jdGlvbiB3aWxsIGFsc28gcmVtb3ZlIHNvbWUgaW5zdGFuY2VzIG9mIHdoZW4gdGhlIGBTRVJWSUNFTk9URVNgIHRleHQgYXBwZWFycyB0byBhY3R1YWxseSBiZSBmcm9tIHRoZSBpbnNwZWN0b3IsIGJ1dCB0aGUgaW5zcGVjdG9yIGp1c3QgZGlkIG5vdCBhZGQgYSBkYXRlIHRvIGhpcyB0ZXh0LiBIb3dldmVyLCB0aGlzIHdhcyB0aGUgcXVpY2tlc3Qgd2F5IHRvIGdldCB0byBhIHZpYWJsZSBkYXRhc2V0IGZvciBjb25maXJtYXRpb24uIFRoaXMgcHJvY2VzcyByZWR1Y2VkIHRoZSAiZ3JvdW5kIHRydXRoIiBkYXRhIGZyb20gNDAwIHJvd3MgdG8gMjczIHJvd3MuCmBgYHtyfQoKcmVnZXhfaW5zcGVjdG9yX25vdGUgPC0gIlxcZHsxLDJ9KC98LSlcXGR7MSwyfSgvfC0pXFxkezIsNH0iCgpNYXJrX0luc3BlY3Rvcl9Ob3RlcyA8LSBNYW51YWxseV9WZXJpZmllZF9EYXRhICU+JSAKICBtdXRhdGUoaW5zcGVjdG9yX25vdGUgPSBzdHJfZGV0ZWN0KFNFUlZJQ0VOT1RFUywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZ2V4X2luc3BlY3Rvcl9ub3RlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICkKCiMgVmlldygKICBNYXJrX0luc3BlY3Rvcl9Ob3RlcyMpCgpNYXJrX0luc3BlY3Rvcl9Ob3RlcyAlPiUgCiAgY291bnQoaW5zcGVjdG9yX25vdGUpCgojIFZpZXcoCiAgTWFya19JbnNwZWN0b3JfTm90ZXMgJT4lIAogICAgICAgZmlsdGVyKGluc3BlY3Rvcl9ub3RlID09IEZBTFNFIHwKICAgICAgICAgICAgICAgIGlzLm5hKGluc3BlY3Rvcl9ub3RlKQogICAgICAgICAgICAgICkgJT4lIAogICAgICAgYXJyYW5nZShpbnNwZWN0b3Jfbm90ZSkjKQoKIyBWaWV3KAogIE1hcmtfSW5zcGVjdG9yX05vdGVzICU+JSAKICAgICAgIGZpbHRlcihpbnNwZWN0b3Jfbm90ZSA9PSBUUlVFICYKICAgICAgICAgICAgICAgIHJhdHNfeV9uID09ICJ5IgogICAgICAgICAgICAgICkgJT4lIAogICAgICAgc2FtcGxlX24oNSkgJT4lIAogICAgICAgYmluZF9yb3dzKE1hcmtfSW5zcGVjdG9yX05vdGVzICU+JSAKICAgICAgICAgICAgICAgICAgIGZpbHRlcihpbnNwZWN0b3Jfbm90ZSA9PSBUUlVFICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJhdHNfeV9uID09ICJuIgogICAgICAgICAgICAgICAgICAgICAgICAgICkgJT4lIAogICAgICAgICAgICAgICAgICAgc2FtcGxlX24oNSkKICAgICAgICAgICAgICAgICApICU+JSAKICAgICAgIGJpbmRfcm93cyhNYXJrX0luc3BlY3Rvcl9Ob3RlcyAlPiUgCiAgICAgICAgICAgICAgICAgICBmaWx0ZXIoaW5zcGVjdG9yX25vdGUgPT0gVFJVRSAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICByYXRzX3lfbiA9PSAidSIKICAgICAgICAgICAgICAgICAgICAgICAgICApICU+JSAKICAgICAgICAgICAgICAgICAgIHNhbXBsZV9uKDUpCiAgICAgICAgICAgICAgICAgKSMpCiAgCgpWZXJpZmllZF9JbnNwZWN0b3JfTm90ZXMgPC0gTWFya19JbnNwZWN0b3JfTm90ZXMgJT4lIAogIGZpbHRlcihpbnNwZWN0b3Jfbm90ZSA9PSBUUlVFKQoKCm5yb3coTWFya19JbnNwZWN0b3JfTm90ZXMpCm5yb3coVmVyaWZpZWRfSW5zcGVjdG9yX05vdGVzKQoKIyBWaWV3KApWZXJpZmllZF9JbnNwZWN0b3JfTm90ZXMjKQoKYGBgCiAgCiAgICAKICBBZnRlciBsZWZ0IGpvaW5pbmcgYFZlcmlmaWVkX0luc3BlY3Rvcl9Ob3Rlc2AgYW5kIGBSZWdleF9Nb2RlbF9Db21wYXJlYCwgd2UgY2FuIHNlZSB0aGF0IHRoZXJlIGFyZSAzODQgcm93cyBhcyBvcHBvc2VkIHRvIHRoZSAyNzMgcm93cyBpbiBgVmVyaWZpZWRfSW5zcGVjdG9yX05vdGVzYC4KYGBge3J9CgpNYW51YWxfUGx1c19Nb2RlbENvbXBhcmUgPC0gVmVyaWZpZWRfSW5zcGVjdG9yX05vdGVzICU+JSAKICBsZWZ0X2pvaW4oUmVnZXhfTW9kZWxfQ29tcGFyZSwKICAgICAgICAgICAgYnkgPSBjKCJTRVJWSUNFTk9URVMiID0gInNlcnZpY2Vub3RlcyIpCiAgICAgICAgICAgICkgJT4lIAogIG11dGF0ZShvdXRjb21lX21hbnVhbCA9IGNhc2Vfd2hlbihyYXRzX3lfbiA9PSAieSIgfiAicmF0c19mb3VuZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJhdHNfeV9uID09ICJuIiB+ICJub19yYXRzX2ZvdW5kIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmF0c195X24gPT0gInUiIH4gInVua25vd24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gIlhYWFgiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgKSAKCgpucm93KFZlcmlmaWVkX0luc3BlY3Rvcl9Ob3RlcykKbnJvdyhNYW51YWxfUGx1c19Nb2RlbENvbXBhcmUpCgpNYW51YWxfUGx1c19Nb2RlbENvbXBhcmUKCmBgYAogIAogICAgCiAgQWZ0ZXIgc29tZSBpbnNwZWN0aW9uLCB3ZSBjYW4gc2UgdGhhdCBib3RoIGBWZXJpZmllZF9JbnNwZWN0b3JfTm90ZXNgIGFuZCBgUmVnZXhfTW9kZWxfQ29tcGFyZWAgaGF2ZSB0aGUgc2FtZSBleGFjdCB0ZXh0IGVudGVyZWQgbXVsdGlwbGUgdGltZXMuCmBgYHtyfQoKVmVyaWZpZWRfSW5zcGVjdG9yX05vdGVzICU+JQogIGNvdW50KG9yaWdpbmFsX2luZGV4KSAlPiUKICBhcnJhbmdlKGRlc2MobikKICAgICAgICAgICkKClZlcmlmaWVkX0luc3BlY3Rvcl9Ob3RlcyAlPiUKICBjb3VudChTRVJWSUNFTk9URVMpICU+JQogIGFycmFuZ2UoZGVzYyhuKQogICAgICAgICAgKQoKCgpSZWdleF9Nb2RlbF9Db21wYXJlICU+JSAKICBjb3VudChzZXJ2aWNlcmVxdWVzdGlkKSAlPiUgCiAgYXJyYW5nZShkZXNjKG4pCiAgICAgICAgICApCgpSZWdleF9Nb2RlbF9Db21wYXJlICU+JQogIGNvdW50KHNlcnZpY2Vub3RlcykgJT4lCiAgYXJyYW5nZShkZXNjKG4pCiAgICAgICAgICApCgpSZWdleF9Nb2RlbF9Db21wYXJlICU+JQogIGNvdW50KHNlcnZpY2Vub3Rlc19jbGVhbmVkKSAlPiUKICBhcnJhbmdlKGRlc2MobikKICAgICAgICAgICkKCmBgYAogIAogICAgCiAgQWx0aG91Z2ggdGhpcyBpcyBhbiBpc3N1ZSBmb3IgdGhlIGRhdGEgb3ZlcmFsbCwgZm9yIHRoZSBzaW1wbGUgdGFzayBvZiBjb21wYXJpbmcgdGhlIHR3byByZWdleCBtb2RlbHMsIHdlIGNhbiBnZXQgYXJvdW5kIHRoaXMgaXNzdWUgYnkgc2ltcGx5IHNlbGVjdGluZyB0aGUgZGlzdGluY3QgYFNFUlZJQ0VOT1RFU2AgdmFsdWVzLgpgYGB7cn0KCk1hbnVhbF9QbHVzX01vZGVsQ29tcGFyZV9EaXN0aW5jdCA8LQogIE1hbnVhbF9QbHVzX01vZGVsQ29tcGFyZSAlPiUgCiAgc2VsZWN0KFNFUlZJQ0VOT1RFUywKICAgICAgICAgc2VydmljZW5vdGVzX2NsZWFuZWQsCiAgICAgICAgIG91dGNvbWVfbWFudWFsLAogICAgICAgICBvdXRjb21lX3NuXzEsCiAgICAgICAgIG91dGNvbWVfc25fMiwKICAgICAgICAgb3V0Y29tZV9zbmNfMSwKICAgICAgICAgb3V0Y29tZV9zbmNfMgogICAgICAgICApICU+JSAKICBkaXN0aW5jdCgpCgpNYW51YWxfUGx1c19Nb2RlbENvbXBhcmVfRGlzdGluY3QKIyBWaWV3KE1hbnVhbF9QbHVzX01vZGVsQ29tcGFyZV9EaXN0aW5jdCkKCmBgYAogIAogICAgCiAgU28gbm93IHdlIGNhbiBnZXQgdGhlIGFjY3VyYWN5IHN0YXRpc3RpY3MgdXNpbmcgYGNhcmV0Ojpjb25mdXNpb25NYXRyaXhgLgpgYGB7cn0KCkNNX1NuMSA8LSBjb25mdXNpb25NYXRyaXgoTWFudWFsX1BsdXNfTW9kZWxDb21wYXJlX0Rpc3RpbmN0JG91dGNvbWVfc25fMSwKICAgICAgICAgICAgICAgICAgICAgICAgICBNYW51YWxfUGx1c19Nb2RlbENvbXBhcmVfRGlzdGluY3Qkb3V0Y29tZV9tYW51YWwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgcG9zaXRpdmUgPSAicmF0c19mb3VuZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZSA9ICJldmVyeXRoaW5nIgogICAgICAgICAgICAgICAgICAgICAgICAgICkKCgpDTV9TbjIgPC0gY29uZnVzaW9uTWF0cml4KE1hbnVhbF9QbHVzX01vZGVsQ29tcGFyZV9EaXN0aW5jdCRvdXRjb21lX3NuXzIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgTWFudWFsX1BsdXNfTW9kZWxDb21wYXJlX0Rpc3RpbmN0JG91dGNvbWVfbWFudWFsLAogICAgICAgICAgICAgICAgICAgICAgICAgIHBvc2l0aXZlID0gInJhdHNfZm91bmQiLAogICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGUgPSAiZXZlcnl0aGluZyIKICAgICAgICAgICAgICAgICAgICAgICAgICApCgoKQ01fU25jMSA8LSBjb25mdXNpb25NYXRyaXgoTWFudWFsX1BsdXNfTW9kZWxDb21wYXJlX0Rpc3RpbmN0JG91dGNvbWVfc25jXzEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIE1hbnVhbF9QbHVzX01vZGVsQ29tcGFyZV9EaXN0aW5jdCRvdXRjb21lX21hbnVhbCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgcG9zaXRpdmUgPSAicmF0c19mb3VuZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGUgPSAiZXZlcnl0aGluZyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgKQoKCkNNX1NuYzIgPC0gY29uZnVzaW9uTWF0cml4KE1hbnVhbF9QbHVzX01vZGVsQ29tcGFyZV9EaXN0aW5jdCRvdXRjb21lX3NuY18yLAogICAgICAgICAgICAgICAgICAgICAgICAgICBNYW51YWxfUGx1c19Nb2RlbENvbXBhcmVfRGlzdGluY3Qkb3V0Y29tZV9tYW51YWwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBvc2l0aXZlID0gInJhdHNfZm91bmQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlID0gImV2ZXJ5dGhpbmciCiAgICAgICAgICAgICAgICAgICAgICAgICAgICkKCgpDTV9BbGxNb2RlbHMgPC0gbGlzdChDTV9TbjEsIENNX1NuMiwgQ01fU25jMSwgQ01fU25jMikKcm0obGlzdCA9IGxzKHBhdHRlcm4gPSAiQ01fUyIpKQoKbmFtZXMoQ01fQWxsTW9kZWxzKSA8LSBjKCJDTV9TbjEiLCAiQ01fU24yIiwgIkNNX1NuYzEiLCAiQ01fU25jMiIpCgpDTV9BbGxNb2RlbHMKCmBgYAogIAogICAgCiAgQWxsIG9mIHRoZSBDb25mdXNpb24gTWF0cml4IFN0YXRpc3RpY3MgYXJlIHByb3ZpZGVkIGFib3ZlIHRvIHJldmlldy4gVG8gbG9vayBhdCB0d28gY29tbW9uIG1ldHJpY3MsIGJvdGggdGhlIEYxIHZhbHVlIGFuZCB0aGUgS2FwcGEgdmFsdWUgYm90aCBzaG93IHRoZSBoaWdoZXN0IHNjb3JlcyBmb3IgdXNpbmcgUmVnZXggTW9kZWwgMSBvbiB0aGUgYHNlcnZpY2Vub3Rlc19jbGVhbmVkYCB0ZXh0LgpgYGB7cn0KCkNNX0FsbE1vZGVscyAlPiUgCiAgbWFwX2RmKH4gLnhbImJ5Q2xhc3MiXVtbMV1dWzE5OjIxXSkgJT4lIAogIHJlbmFtZShzZXJ2aWNlbm90ZXNfcmVnZXgxID0gQ01fU24xLAogICAgICAgICBzZXJ2aWNlbm90ZXNfcmVnZXgyID0gQ01fU24yLAogICAgICAgICBzZXJ2aWNlbm90ZXNjbGVhbmVkX3JlZ2V4MSA9IENNX1NuYzEsCiAgICAgICAgIHNlcnZpY2Vub3Rlc2NsZWFuZWRfcmVnZXgyID0gQ01fU25jMgogICAgICAgICApICU+JSAKICB0KCkgJT4lIAogIHNldF9jb2xuYW1lcyhjKCJmMV9ub19yYXRzX2ZvdW5kIiwKICAgICAgICAgICAgICAgICAiZjFfcmF0c19mb3VuZCIsCiAgICAgICAgICAgICAgICAgImYxX3Vua25vd24iCiAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICApICU+JSAKICBhcy5kYXRhLmZyYW1lKCkgJT4lIAogIHJvd25hbWVzX3RvX2NvbHVtbigpICU+JSAKICBzZXBhcmF0ZShjb2wgPSByb3duYW1lLAogICAgICAgICAgIGludG8gPSBjKCJ0ZXh0X3R5cGUiLCAicmVnZXhfbW9kZWwiKSwKICAgICAgICAgICBzZXAgPSAiXyIKICAgICAgICAgICApCgoKQ01fQWxsTW9kZWxzICU+JSAKICBtYXBfZGYofiAuWyJvdmVyYWxsIl1bWzFdXVtbMl1dKSAlPiUgCiAgcmVuYW1lKHNlcnZpY2Vub3Rlc19yZWdleDEgPSBDTV9TbjEsCiAgICAgICAgIHNlcnZpY2Vub3Rlc19yZWdleDIgPSBDTV9TbjIsCiAgICAgICAgIHNlcnZpY2Vub3Rlc2NsZWFuZWRfcmVnZXgxID0gQ01fU25jMSwKICAgICAgICAgc2VydmljZW5vdGVzY2xlYW5lZF9yZWdleDIgPSBDTV9TbmMyCiAgICAgICAgICkgJT4lIAogIHQoKSAlPiUgCiAgc2V0X2NvbG5hbWVzKCJrYXBwYSIpICU+JSAKICBhcy5kYXRhLmZyYW1lKCkgJT4lIAogIHJvd25hbWVzX3RvX2NvbHVtbigpICU+JSAKICBzZXBhcmF0ZShjb2wgPSByb3duYW1lLAogICAgICAgICAgIGludG8gPSBjKCJ0ZXh0X3R5cGUiLCAicmVnZXhfbW9kZWwiKSwKICAgICAgICAgICBzZXAgPSAiXyIKICAgICAgICAgICApCgpgYGAKCgo=