Finding great data to explore methods in Health Data Science can be difficult in a climate where personal data is intensely private by definition. Recent advances are being made in differential privacy techniques and federated data analysis, while others make data available by generating synthetic data based on the features of underlying real health data.

SyntheaTM is a synthetic patient generator that models the medical history of synthetic patients. Our mission is to output high-quality synthetic, realistic but not real, patient data and associated health records covering every aspect of healthcare. The resulting data is free from cost, privacy, and security restrictions. It can be used without restriction for a variety of secondary uses in academia, research, industry, and government. (https://github.com/synthetichealth/synthea/wiki)

The data provided by Synthea is detailed, workable, and can be downloaded from https://synthea.mitre.org/downloads. Additional information about the data can be found at https://github.com/synthetichealth/synthea/wiki/CSV-File-Data-Dictionary.

In this series of posts, I will be using a subset of the Synthea data, focussing on the recent COVID pandemic. In this post, I pose the question -

How do pre-existing coditions affect the risk of COVID death?

Since all data in this set are suspected COVID patients, this might be re-phrased, how do pre-existing conditions affect the risk of COVID death, given one is infected and symptomatic enough to seek medical help. The active period covers 27-05-2019 to 27-05-2020.

# Import some packages
library(tidyverse)
library(data.table)
library(survival)
library(caret)
library(glmnet)

# And some handy functions
putZeros<- function(df) {
  df<- df %>% mutate_at(vars(-group_cols()),~replace(.,is.na(.),0))
  return(df)
}

not_including <- function(x,elements){
  y<- x[!x %in% elements]
  return(y)
}

# And read in the data that I cleaned and wrangled in a previous session

IP<- readRDS("Dev Data 0/IP_0.rds") # 1766943 row

Let’s begin

In this post I will be making use of the data in two main parts.

  1. Patient characterists (age, sex, etc. - one row per patient)
  2. Patient histories (observations, procedures etc. - many rows per patient)
# Create a patient summary set

PID<- IP %>% select(PID, age, sex, Status, futime, ethnicity_fac, allergy) %>% distinct()
#9040 patients

# Add some labels
PID$over_60<-      ifelse(PID$age >= 60, "Over 60", "Under 60")
PID$death_status<- ifelse(PID$Status== 0, "Alive", "Died")

# Mosaic plot - what we can see already about COVID death
ptable<-           table(PID$death_status, PID$over_60)
mosaicplot(ptable, main= "COVID Death Counts by Age", color = c("#ef5675", "#374c80"),
           cex.axis= 1)

We can see already that patients that are over the age of 60 at the start of the study are much more likely to die from COVID infection than patients that are under 60. But what other factors might be at play?

# Extract patient histories from the main set

history<- IP %>% filter(type %like% "history", 
                        type %like% "condition|observation|procedure")

# The patient histories are a collection of codes and descriptions for past conditions,
# observations, disorders and procedures.

# I used this mapping table to put these tags into groups
groupRef<- read_csv("group.csv")
groupRef %>% head(20)

After joining the grouping reference with the patient histories, we can see that there are a number of conditions present prior to the active COVID period.

# Join the reference list with patient histories
history<- left_join(history,groupRef, by="DESCRIPTION")

history %>% count(Group) %>% filter(!is.na(Group)) %>%
  mutate(Condition= fct_reorder(Group, n)) %>%
  ggplot(aes(x= n, y= Condition, fill= n)) +
  geom_col() +
  labs(title= "Condition mentions in patient histories", x= "Count of mentions") +
  theme(legend.position = "none") +
  scale_fill_gradient(low= "#7a5195", high="#ff764a")

This is great but we need to create a matrix that is good for modelling. In other words, we need one row per patient, and an indicator matrix with a binary flag as to whether the patient has a record of each condition or not.

# Create an indicator matrix
tmat<- history %>% count(PID,Group) %>% 
          filter(!is.na(Group)) %>% spread(Group, n) %>% putZeros()

conditions<- names(tmat) %>% not_including(c( "PID")) %>% print()
 [1] "Allergic_rhinitis"    "Alzheimers_disease"   "Anemia"              
 [4] "Appendicitis"         "Arthritis"            "Asthma_bronchitis"   
 [7] "Cancer"               "Cardiac"              "Chronic_pain"        
[10] "Chronic_respiratory"  "Dermatitis"           "Diabetes"            
[13] "Drug_abuse"           "ENT"                  "High_cholesterol"    
[16] "Hypertension"         "Kidney_urinary_etc"   "Mental"              
[19] "Metabolic"            "Neuro"                "Obesity"             
[22] "Orthopaedic_disorder" "Sinusitis"            "Smoking"             
# Then attach the indicator matrix to the patient characteristics set, and fill in the zeros
PID<-              left_join(PID, tmat, by="PID")
PID[,conditions]<- putZeros(PID[, conditions])
PID[,conditions]<- apply(PID[,conditions],2, function(x) ifelse(x > 1, 1, x))

Survival analysis

Fantastic! Now that we have our features ready, we can set up our survival outcome variable, which relates the survival time futime to death status Status. Also to simplify the ethnicity factor, I will recode this a binary predictor, since the patients in the “Black” category had a somewhat higher risk that the other groups in previous EDA.

My first step will be to run each pre-existing condition in turn, in a basic model including age, sex and ethnicity as covariates. I also make use of the purrr package to streamline modelling.

# Create survival object
PID$Surv<-      with(PID, Surv(futime, Status))
PID$ethnicity<- ifelse(PID$ethnicity_fac %like% "Black", 1, 0)

# Create formulas
forms<-         paste("Surv ~ age + sex + ethnicity +",conditions)
formulas<-      map(forms, as.formula)

# Run the models, and tidy them up
models<-        map(formulas, ~coxph(.x,data= PID))
tidies<-        map(models, broom::tidy)
out<-           bind_rows(tidies, .id= "ID")
out %>%         head(20)

Where do we see a signal? It might be easier to visualise the adjusted univariate effects in a plot…

out_plot<- out %>% filter(!term %in% c("sex","age","ethnicity")) %>%
    mutate(ExpE= exp(estimate), ExpL= exp(conf.low), ExpU= exp(conf.high),
           Significant= ifelse(p.value < 0.05, "Significant", "Not significant"),
           alpha= ifelse(p.value < 0.05, 1, 0.3),
           Predictor= fct_reorder(term, ExpE))
out_plot$alpha<- I(out_plot$alpha)

ggplot(out_plot, aes(x= ExpE, y= Predictor, fill= estimate, alpha= alpha)) +
  geom_col() +
  scale_x_continuous( expand = c(0, 0)) +
  theme_bw() +
  theme(legend.position= "none",
        axis.ticks.y= element_blank(),
        axis.text.y = element_text(size=10),
        panel.grid.major.y = element_blank()) +
  scale_fill_gradient(low= "#7a5195", high="#ff764a")+
  labs(x= "\nOdds ratio - Increase in risk if condition is present", y= "Pre-existing condition",
       title= "Univariate effects of Pre-existing Conditions on COVID Death",
       subtitle = "Effects in light shading indicate non-significant coefficients.")

The Smoking effect really stands out! This is much higher than what we have found in our real COVID research. This could be because the Smoking observation in the patient histories is recorded alongside current, daily smoking or tobacco use.

More focused survival modelling

Now we will pull through the conditions that have shown some signal, and begin survival modelling in earnest. This includes splitting the data into a training and test set.

# Identify variables with significant univariate association
sigs<- out %>% filter(p.value < 0.05) %>% pull(term) %>% unique() 

# Modelling data set
XY<-   PID %>% select(Status, Surv, all_of(sigs)) %>% na.omit()

# Create test-train-split
set.seed(123)
trainIndex<- createDataPartition(XY$Status, p=0.7, list=FALSE, times=1)

X<-           XY %>% select(-c(Surv, Status))  %>% as.matrix() %>% scale()
y<-           XY$Surv
X_train<-     X[trainIndex,]
X_test<-      X[-trainIndex,]
y_train<-     y[trainIndex]
y_test<-      XY$Status[-trainIndex]
PID_train<-   PID[trainIndex,]
PID_test<-    PID[-trainIndex, ]

# Use glmnet to apply regularised cox proportional hazards
fit <-        glmnet(X_train, y_train, family="cox")
plot(fit, label=TRUE, cex= 1.5)

cv.fit <-     cv.glmnet(X_train, y_train, family="cox")
plot(cv.fit)

Cross-validated on the cox ph suggests model with between 4 and 8 predictors. Lets see what coefficients enter the model in those first 4 - 8 steps. I like to examine how the coefficient matrix evolves.

# Extract the coefficient matrix and sort by absolute value to get the predictors into a sensible order

mmat<-          as.matrix(coef(fit)) %>% data.frame() 
mmat$row_sums<- rowSums(abs(mmat))
mmat<-          mmat %>% arrange(desc(row_sums))

print(rownames(mmat))
 [1] "age"                  "Smoking"              "Hypertension"        
 [4] "Chronic_respiratory"  "Anemia"               "Appendicitis"        
 [7] "Kidney_urinary_etc"   "Sinusitis"            "sex"                 
[10] "Orthopaedic_disorder" "Asthma_bronchitis"    "Diabetes"            
[13] "ENT"                 

So this gives us the approximate order in which the various conditions enter the multivariate model. I tend to use the columns of the coefficient matrix to explore different models in the optimal region until I find one that looks sensible, with all predictors having a significant p-value.

# Create in indicator matrix to show model sizes at each step
Zmat<-        mmat!=0
model_sizes<- Zmat %>% colSums() %>% print()
      s0       s1       s2       s3       s4       s5       s6       s7       s8       s9 
       0        1        1        1        1        1        1        2        2        2 
     s10      s11      s12      s13      s14      s15      s16      s17      s18      s19 
       2        2        2        2        2        2        2        2        2        3 
     s20      s21      s22      s23      s24      s25      s26      s27      s28      s29 
       4        4        4        4        4        5        5        5        6        6 
     s30      s31      s32      s33      s34      s35      s36      s37      s38      s39 
       7        8        8        8        8        8        8        8        8        8 
     s40      s41      s42      s43      s44      s45      s46      s47      s48      s49 
       8        9        9        9        9       10       11       11       11       11 
     s50      s51      s52 row_sums 
      11       11       11       11 
# I use this to iteractively explore the model selections at each step
trialSet<-   row.names(Zmat)[Zmat[,28]] %>% print()
[1] "age"                 "Smoking"             "Hypertension"        "Chronic_respiratory"
[5] "Anemia"             
formula<-    as.formula(paste("Surv ~",str_c(trialSet, collapse= " + "))) %>% print()
Surv ~ age + Smoking + Hypertension + Chronic_respiratory + Anemia
m2<- coxph(formula,  data= PID_train)
summary(m2)
Call:
coxph(formula = formula, data = PID_train)

  n= 6328, number of events= 247 

                         coef exp(coef)  se(coef)      z Pr(>|z|)    
age                  0.044822  1.045842  0.003161 14.179  < 2e-16 ***
Smoking              2.359141 10.581855  0.157834 14.947  < 2e-16 ***
Hypertension         0.567629  1.764080  0.130118  4.362 1.29e-05 ***
Chronic_respiratory  0.786792  2.196339  0.225826  3.484 0.000494 ***
Anemia               0.271028  1.311312  0.132843  2.040 0.041329 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

                    exp(coef) exp(-coef) lower .95 upper .95
age                     1.046     0.9562     1.039     1.052
Smoking                10.582     0.0945     7.766    14.418
Hypertension            1.764     0.5669     1.367     2.277
Chronic_respiratory     2.196     0.4553     1.411     3.419
Anemia                  1.311     0.7626     1.011     1.701

Concordance= 0.881  (se = 0.01 )
Likelihood ratio test= 622.1  on 5 df,   p=<2e-16
Wald test            = 809.6  on 5 df,   p=<2e-16
Score (logrank) test = 1949  on 5 df,   p=<2e-16

Assess predictions from the suggested model

The above analysis indicates a multivariate model with five features. How does this model do predicting against the test set?

m3<-        coxph(Surv ~ age + Smoking + Hypertension + Anemia + Chronic_respiratory,  
                  data= PID_train)
preds<-     predict(m3, newdata = PID_test, type= "risk")

pROC_obj <- pROC::roc(y_test, preds,
                smoothed = TRUE,
                ci=TRUE, ci.alpha=0.9, stratified=FALSE, auc.polygon.col="#e6cbdb",
                plot=TRUE, auc.polygon=TRUE, max.auc.polygon=TRUE, grid=TRUE,
                print.auc=TRUE, show.thres=TRUE)

Great! The model suggested by regularised cox ph has good predictive ability on the test set, with an AUC of 0.899.

Final model and discussion

Now we put all the data back together to estimate the effects with the final model.

# Put together final model to get best estimates of the coefficients

# Lets predict the effect of a 10 year increment in age, to get a clear odds ratio interval
PID$Age_10_yrs<- PID$age/10

final<- coxph(Surv ~ Age_10_yrs + Smoking + Hypertension + Anemia + Chronic_respiratory,  
              data= PID)
final_sum<- broom::tidy(final) %>% mutate( ExpE= exp(estimate),
                                           ExpL= exp(conf.low),
                                           ExpU= exp(conf.high),
                                           Order= 5:1)
final_sum<- final_sum %>% mutate(Comment= paste0(sprintf("%2.1f",ExpE),"  [",
                                                sprintf("%2.1f",ExpL),", ",
                                                sprintf("%2.1f",ExpU),"]"))

cols5<- c( "#374c80", "#7a5195", "#bc5090", "#ef5675", "#ff764a")

ggplot(final_sum,aes(x= ExpE,y=Order, color= factor(Order))) +
  geom_point(size=2) +
  geom_errorbarh(aes(xmin=ExpL,xmax= ExpU),height=0.2)+
  geom_vline(xintercept= 1, col="grey30",size=.1, lty="dashed") +
  geom_text(aes(x=ExpE,y=Order+0.25,label= term), fontface= "bold") +
  geom_text(aes(x=ExpE,y=Order-0.25,label= Comment)) +
  theme_bw() +
  expand_limits(x=c(0,2))+
  theme(title= element_text(size=12),
        axis.title.x = element_text(margin = margin(t = 5, r = 20, b = 0, l = 0)),
        axis.text.y= element_blank(),axis.ticks.y=element_blank(),
        axis.text.x= element_text(size=10,lineheight = 0.3),
        legend.position = "none",
        panel.grid= element_blank(),
        strip.background= element_rect(fill="grey20"),
        strip.text= element_text(color="white",face="bold",size=12),
        panel.spacing = unit(1, "lines"),
        panel.border= element_rect(colour= "grey30")) +
  labs(title= "Inreased Risk of COVID Death (Odds Ratio) by Predictor", 
       x= "Change in Odds associated with each predictor",
       y= element_blank()) +
  scale_color_manual(values= cols5)

Many thanks to Synthea for producing such a detailed synthetic data set for demonstrating various modelling methods.

Again, Smoking looks to be the real villain here, with current, daily smoking increasing the odds of death (once infected with COVID) by about ten-fold.

Conditions under the Chronic_Respiratory heading include pulmonary emphysema, chronic obstructive bronchitis, suspected lung cancer, pulmonary rehabilitation (regime/therapy) and cystic fibrosis. Patients with these conditions might be expected to have an increased risk of death once infected with COVID of between 1.4x and 2.9x compared with those who do not.

Every additional 10 years of age of the patient at admission is seen to increase the risk of death by between 50% to 70%, while those with hypertension also have about a 70% increased risk.

References

Jason Walonoski, Mark Kramer, Joseph Nichols, Andre Quina, Chris Moesel, Dylan Hall, Carlton Duffett, Kudakwashe Dube, Thomas Gallagher, Scott McLachlan, Synthea: An approach, method, and software mechanism for generating synthetic patients and the synthetic electronic health care record, Journal of the American Medical Informatics Association, Volume 25, Issue 3, March 2018, Pages 230–238, https://doi.org/10.1093/jamia/ocx079

LS0tCnRpdGxlOiAnU3ludGhlYTogQ09WSUQgRGVhdGggYW5kIFByZS1FeGlzdGluZyBDb25kaXRpb25zJwphdXRob3I6ICJDZWwgTWNDcmFja2VuIgpkYXRlOiAiMDQvMDcvMjAyMCIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKRmluZGluZyBncmVhdCBkYXRhIHRvIGV4cGxvcmUgbWV0aG9kcyBpbiBIZWFsdGggRGF0YSBTY2llbmNlIGNhbiBiZSBkaWZmaWN1bHQgaW4gYSBjbGltYXRlIHdoZXJlIHBlcnNvbmFsIGRhdGEgaXMgaW50ZW5zZWx5IHByaXZhdGUgYnkgZGVmaW5pdGlvbi4gClJlY2VudCBhZHZhbmNlcyBhcmUgYmVpbmcgbWFkZSBpbiBkaWZmZXJlbnRpYWwgcHJpdmFjeSB0ZWNobmlxdWVzIGFuZCBmZWRlcmF0ZWQgZGF0YSBhbmFseXNpcywgd2hpbGUgb3RoZXJzIG1ha2UgZGF0YSBhdmFpbGFibGUgYnkgZ2VuZXJhdGluZyBzeW50aGV0aWMgZGF0YSBiYXNlZCBvbiB0aGUgZmVhdHVyZXMgb2YgdW5kZXJseWluZyByZWFsIGhlYWx0aCBkYXRhLgoKPiBTeW50aGVhVE0gaXMgYSBzeW50aGV0aWMgcGF0aWVudCBnZW5lcmF0b3IgdGhhdCBtb2RlbHMgdGhlIG1lZGljYWwgaGlzdG9yeSBvZiBzeW50aGV0aWMgcGF0aWVudHMuIE91ciBtaXNzaW9uIGlzIHRvIG91dHB1dCBoaWdoLXF1YWxpdHkgc3ludGhldGljLCByZWFsaXN0aWMgYnV0IG5vdCByZWFsLCBwYXRpZW50IGRhdGEgYW5kIGFzc29jaWF0ZWQgaGVhbHRoIHJlY29yZHMgY292ZXJpbmcgZXZlcnkgYXNwZWN0IG9mIGhlYWx0aGNhcmUuIFRoZSByZXN1bHRpbmcgZGF0YSBpcyBmcmVlIGZyb20gY29zdCwgcHJpdmFjeSwgYW5kIHNlY3VyaXR5IHJlc3RyaWN0aW9ucy4gSXQgY2FuIGJlIHVzZWQgd2l0aG91dCByZXN0cmljdGlvbiBmb3IgYSB2YXJpZXR5IG9mIHNlY29uZGFyeSB1c2VzIGluIGFjYWRlbWlhLCByZXNlYXJjaCwgaW5kdXN0cnksIGFuZCBnb3Zlcm5tZW50LiAoaHR0cHM6Ly9naXRodWIuY29tL3N5bnRoZXRpY2hlYWx0aC9zeW50aGVhL3dpa2kpCgpUaGUgZGF0YSBwcm92aWRlZCBieSBTeW50aGVhIGlzIGRldGFpbGVkLCB3b3JrYWJsZSwgYW5kIGNhbiBiZSBkb3dubG9hZGVkIGZyb20gW2h0dHBzOi8vc3ludGhlYS5taXRyZS5vcmcvZG93bmxvYWRzXShodHRwczovL3N5bnRoZWEubWl0cmUub3JnL2Rvd25sb2FkcykuICBBZGRpdGlvbmFsIGluZm9ybWF0aW9uIGFib3V0IHRoZSBkYXRhIGNhbiBiZSBmb3VuZCBhdCBbaHR0cHM6Ly9naXRodWIuY29tL3N5bnRoZXRpY2hlYWx0aC9zeW50aGVhL3dpa2kvQ1NWLUZpbGUtRGF0YS1EaWN0aW9uYXJ5XShodHRwczovL2dpdGh1Yi5jb20vc3ludGhldGljaGVhbHRoL3N5bnRoZWEvd2lraS9DU1YtRmlsZS1EYXRhLURpY3Rpb25hcnkpLgoKSW4gdGhpcyBzZXJpZXMgb2YgcG9zdHMsIEkgd2lsbCBiZSB1c2luZyBhIHN1YnNldCBvZiB0aGUgU3ludGhlYSBkYXRhLCBmb2N1c3Npbmcgb24gdGhlIHJlY2VudCBDT1ZJRCBwYW5kZW1pYy4gSW4gdGhpcyBwb3N0LCBJIHBvc2UgdGhlIHF1ZXN0aW9uIC0KCj4gSG93IGRvIHByZS1leGlzdGluZyBjb2RpdGlvbnMgYWZmZWN0IHRoZSByaXNrIG9mIENPVklEIGRlYXRoPwoKU2luY2UgYWxsIGRhdGEgaW4gdGhpcyBzZXQgYXJlIHN1c3BlY3RlZCBDT1ZJRCBwYXRpZW50cywgdGhpcyBtaWdodCBiZSByZS1waHJhc2VkLCBob3cgZG8gcHJlLWV4aXN0aW5nIGNvbmRpdGlvbnMgYWZmZWN0IHRoZSByaXNrIG9mIENPVklEIGRlYXRoLCBnaXZlbiBvbmUgaXMgaW5mZWN0ZWQgYW5kIHN5bXB0b21hdGljIGVub3VnaCB0byBzZWVrIG1lZGljYWwgaGVscC4gVGhlIGFjdGl2ZSBwZXJpb2QgY292ZXJzIDI3LTA1LTIwMTkgdG8gMjctMDUtMjAyMC4KCmBgYHtyIGNhcnN9CiMgSW1wb3J0IHNvbWUgcGFja2FnZXMKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZGF0YS50YWJsZSkKbGlicmFyeShzdXJ2aXZhbCkKbGlicmFyeShjYXJldCkKbGlicmFyeShnbG1uZXQpCgojIEFuZCBzb21lIGhhbmR5IGZ1bmN0aW9ucwpwdXRaZXJvczwtIGZ1bmN0aW9uKGRmKSB7CiAgZGY8LSBkZiAlPiUgbXV0YXRlX2F0KHZhcnMoLWdyb3VwX2NvbHMoKSksfnJlcGxhY2UoLixpcy5uYSguKSwwKSkKICByZXR1cm4oZGYpCn0KCm5vdF9pbmNsdWRpbmcgPC0gZnVuY3Rpb24oeCxlbGVtZW50cyl7CiAgeTwtIHhbIXggJWluJSBlbGVtZW50c10KICByZXR1cm4oeSkKfQoKIyBBbmQgcmVhZCBpbiB0aGUgZGF0YSB0aGF0IEkgY2xlYW5lZCBhbmQgd3JhbmdsZWQgaW4gYSBwcmV2aW91cyBzZXNzaW9uCgpJUDwtIHJlYWRSRFMoIkRldiBEYXRhIDAvSVBfMC5yZHMiKSAjIDE3NjY5NDMgcm93cwpgYGAKCiMjIyBMZXQncyBiZWdpbgoKSW4gdGhpcyBwb3N0IEkgd2lsbCBiZSBtYWtpbmcgdXNlIG9mIHRoZSBkYXRhIGluIHR3byBtYWluIHBhcnRzLiAKCjEuIFBhdGllbnQgY2hhcmFjdGVyaXN0cyAoYWdlLCBzZXgsIGV0Yy4gLSBvbmUgcm93IHBlciBwYXRpZW50KQoyLiBQYXRpZW50IGhpc3RvcmllcyAob2JzZXJ2YXRpb25zLCBwcm9jZWR1cmVzIGV0Yy4gLSBtYW55IHJvd3MgcGVyIHBhdGllbnQpCgpgYGB7ciBjcmVhdGVfcGF0aWVudF9zZXQsIG1lc3NhZ2U9RkFMU0V9CiMgQ3JlYXRlIGEgcGF0aWVudCBzdW1tYXJ5IHNldAoKUElEPC0gSVAgJT4lIHNlbGVjdChQSUQsIGFnZSwgc2V4LCBTdGF0dXMsIGZ1dGltZSwgZXRobmljaXR5X2ZhYywgYWxsZXJneSkgJT4lIGRpc3RpbmN0KCkKIzkwNDAgcGF0aWVudHMKCiMgQWRkIHNvbWUgbGFiZWxzClBJRCRvdmVyXzYwPC0gICAgICBpZmVsc2UoUElEJGFnZSA+PSA2MCwgIk92ZXIgNjAiLCAiVW5kZXIgNjAiKQpQSUQkZGVhdGhfc3RhdHVzPC0gaWZlbHNlKFBJRCRTdGF0dXM9PSAwLCAiQWxpdmUiLCAiRGllZCIpCgojIE1vc2FpYyBwbG90IC0gd2hhdCB3ZSBjYW4gc2VlIGFscmVhZHkgYWJvdXQgQ09WSUQgZGVhdGgKcHRhYmxlPC0gICAgICAgICAgIHRhYmxlKFBJRCRkZWF0aF9zdGF0dXMsIFBJRCRvdmVyXzYwKQptb3NhaWNwbG90KHB0YWJsZSwgbWFpbj0gIkNPVklEIERlYXRoIENvdW50cyBieSBBZ2UiLCBjb2xvciA9IGMoIiNlZjU2NzUiLCAiIzM3NGM4MCIpLAogICAgICAgICAgIGNleC5heGlzPSAxKQpgYGAKV2UgY2FuIHNlZSBhbHJlYWR5IHRoYXQgcGF0aWVudHMgdGhhdCBhcmUgb3ZlciB0aGUgYWdlIG9mIDYwIGF0IHRoZSBzdGFydCBvZiB0aGUgc3R1ZHkgYXJlIG11Y2ggbW9yZSBsaWtlbHkgdG8gZGllIGZyb20gQ09WSUQgaW5mZWN0aW9uIHRoYW4gcGF0aWVudHMgdGhhdCBhcmUgdW5kZXIgNjAuICBCdXQgd2hhdCBvdGhlciBmYWN0b3JzIG1pZ2h0IGJlIGF0IHBsYXk/CgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPSBGQUxTRX0KIyBFeHRyYWN0IHBhdGllbnQgaGlzdG9yaWVzIGZyb20gdGhlIG1haW4gc2V0CgpoaXN0b3J5PC0gSVAgJT4lIGZpbHRlcih0eXBlICVsaWtlJSAiaGlzdG9yeSIsIAogICAgICAgICAgICAgICAgICAgICAgICB0eXBlICVsaWtlJSAiY29uZGl0aW9ufG9ic2VydmF0aW9ufHByb2NlZHVyZSIpCgojIFRoZSBwYXRpZW50IGhpc3RvcmllcyBhcmUgYSBjb2xsZWN0aW9uIG9mIGNvZGVzIGFuZCBkZXNjcmlwdGlvbnMgZm9yIHBhc3QgY29uZGl0aW9ucywKIyBvYnNlcnZhdGlvbnMsIGRpc29yZGVycyBhbmQgcHJvY2VkdXJlcy4KCiMgSSB1c2VkIHRoaXMgbWFwcGluZyB0YWJsZSB0byBwdXQgdGhlc2UgdGFncyBpbnRvIGdyb3Vwcwpncm91cFJlZjwtIHJlYWRfY3N2KCJncm91cC5jc3YiKQpncm91cFJlZiAlPiUgaGVhZCgyMCkKYGBgCkFmdGVyIGpvaW5pbmcgdGhlIGdyb3VwaW5nIHJlZmVyZW5jZSB3aXRoIHRoZSBwYXRpZW50IGhpc3Rvcmllcywgd2UgY2FuIHNlZSB0aGF0IHRoZXJlIGFyZSBhIG51bWJlciBvZiBjb25kaXRpb25zIHByZXNlbnQgcHJpb3IgdG8gdGhlIGFjdGl2ZSBDT1ZJRCBwZXJpb2QuCmBgYHtyICBtZXNzYWdlPSBGQUxTRX0KIyBKb2luIHRoZSByZWZlcmVuY2UgbGlzdCB3aXRoIHBhdGllbnQgaGlzdG9yaWVzCmhpc3Rvcnk8LSBsZWZ0X2pvaW4oaGlzdG9yeSxncm91cFJlZiwgYnk9IkRFU0NSSVBUSU9OIikKCmhpc3RvcnkgJT4lIGNvdW50KEdyb3VwKSAlPiUgZmlsdGVyKCFpcy5uYShHcm91cCkpICU+JQogIG11dGF0ZShDb25kaXRpb249IGZjdF9yZW9yZGVyKEdyb3VwLCBuKSkgJT4lCiAgZ2dwbG90KGFlcyh4PSBuLCB5PSBDb25kaXRpb24sIGZpbGw9IG4pKSArCiAgZ2VvbV9jb2woKSArCiAgbGFicyh0aXRsZT0gIkNvbmRpdGlvbiBtZW50aW9ucyBpbiBwYXRpZW50IGhpc3RvcmllcyIsIHg9ICJDb3VudCBvZiBtZW50aW9ucyIpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdz0gIiM3YTUxOTUiLCBoaWdoPSIjZmY3NjRhIikKYGBgClRoaXMgaXMgZ3JlYXQgYnV0IHdlIG5lZWQgdG8gY3JlYXRlIGEgbWF0cml4IHRoYXQgaXMgZ29vZCBmb3IgbW9kZWxsaW5nLiBJbiBvdGhlciB3b3Jkcywgd2UgbmVlZCBvbmUgcm93IHBlciBwYXRpZW50LCBhbmQgYW4gaW5kaWNhdG9yIG1hdHJpeCB3aXRoIGEgYmluYXJ5IGZsYWcgYXMgdG8gd2hldGhlciB0aGUgcGF0aWVudCBoYXMgYSByZWNvcmQgb2YgZWFjaCBjb25kaXRpb24gb3Igbm90LgoKYGBge3Igd2FybmluZz1GQUxTRX0KIyBDcmVhdGUgYW4gaW5kaWNhdG9yIG1hdHJpeAp0bWF0PC0gaGlzdG9yeSAlPiUgY291bnQoUElELEdyb3VwKSAlPiUgCiAgICAgICAgICBmaWx0ZXIoIWlzLm5hKEdyb3VwKSkgJT4lIHNwcmVhZChHcm91cCwgbikgJT4lIHB1dFplcm9zKCkKCmNvbmRpdGlvbnM8LSBuYW1lcyh0bWF0KSAlPiUgbm90X2luY2x1ZGluZyhjKCAiUElEIikpICU+JSBwcmludCgpCgojIFRoZW4gYXR0YWNoIHRoZSBpbmRpY2F0b3IgbWF0cml4IHRvIHRoZSBwYXRpZW50IGNoYXJhY3RlcmlzdGljcyBzZXQsIGFuZCBmaWxsIGluIHRoZSB6ZXJvcwpQSUQ8LSAgICAgICAgICAgICAgbGVmdF9qb2luKFBJRCwgdG1hdCwgYnk9IlBJRCIpClBJRFssY29uZGl0aW9uc108LSBwdXRaZXJvcyhQSURbLCBjb25kaXRpb25zXSkKUElEWyxjb25kaXRpb25zXTwtIGFwcGx5KFBJRFssY29uZGl0aW9uc10sMiwgZnVuY3Rpb24oeCkgaWZlbHNlKHggPiAxLCAxLCB4KSkKYGBgCgojIyMgU3Vydml2YWwgYW5hbHlzaXMKRmFudGFzdGljISBOb3cgdGhhdCB3ZSBoYXZlIG91ciBmZWF0dXJlcyByZWFkeSwgd2UgY2FuIHNldCB1cCBvdXIgc3Vydml2YWwgb3V0Y29tZSB2YXJpYWJsZSwgd2hpY2ggcmVsYXRlcyB0aGUgc3Vydml2YWwgdGltZSBgZnV0aW1lYCB0byBkZWF0aCBzdGF0dXMgYFN0YXR1c2AuIEFsc28gdG8gc2ltcGxpZnkgdGhlIGV0aG5pY2l0eSBmYWN0b3IsIEkgd2lsbCByZWNvZGUgdGhpcyBhIGJpbmFyeSBwcmVkaWN0b3IsIHNpbmNlIHRoZSBwYXRpZW50cyBpbiB0aGUgIkJsYWNrIiBjYXRlZ29yeSBoYWQgYSBzb21ld2hhdCBoaWdoZXIgcmlzayB0aGF0IHRoZSBvdGhlciBncm91cHMgaW4gcHJldmlvdXMgRURBLgoKTXkgZmlyc3Qgc3RlcCB3aWxsIGJlIHRvIHJ1biBlYWNoIHByZS1leGlzdGluZyBjb25kaXRpb24gaW4gdHVybiwgaW4gYSBiYXNpYyBtb2RlbCBpbmNsdWRpbmcgYWdlLCBzZXggYW5kIGV0aG5pY2l0eSBhcyBjb3ZhcmlhdGVzLiBJIGFsc28gbWFrZSB1c2Ugb2YgdGhlIGBwdXJycmAgcGFja2FnZSB0byBzdHJlYW1saW5lIG1vZGVsbGluZy4KCmBgYHtyfQojIENyZWF0ZSBzdXJ2aXZhbCBvYmplY3QKUElEJFN1cnY8LSAgICAgIHdpdGgoUElELCBTdXJ2KGZ1dGltZSwgU3RhdHVzKSkKUElEJGV0aG5pY2l0eTwtIGlmZWxzZShQSUQkZXRobmljaXR5X2ZhYyAlbGlrZSUgIkJsYWNrIiwgMSwgMCkKCiMgQ3JlYXRlIGZvcm11bGFzCmZvcm1zPC0gICAgICAgICBwYXN0ZSgiU3VydiB+IGFnZSArIHNleCArIGV0aG5pY2l0eSArIixjb25kaXRpb25zKQpmb3JtdWxhczwtICAgICAgbWFwKGZvcm1zLCBhcy5mb3JtdWxhKQoKIyBSdW4gdGhlIG1vZGVscywgYW5kIHRpZHkgdGhlbSB1cAptb2RlbHM8LSAgICAgICAgbWFwKGZvcm11bGFzLCB+Y294cGgoLngsZGF0YT0gUElEKSkKdGlkaWVzPC0gICAgICAgIG1hcChtb2RlbHMsIGJyb29tOjp0aWR5KQpvdXQ8LSAgICAgICAgICAgYmluZF9yb3dzKHRpZGllcywgLmlkPSAiSUQiKQpvdXQgJT4lICAgICAgICAgaGVhZCgyMCkKYGBgCldoZXJlIGRvIHdlIHNlZSBhIHNpZ25hbD8gSXQgbWlnaHQgYmUgZWFzaWVyIHRvIHZpc3VhbGlzZSB0aGUgYWRqdXN0ZWQgdW5pdmFyaWF0ZSBlZmZlY3RzIGluIGEgcGxvdC4uLgpgYGB7ciBtZXNzYWdlPSBGQUxTRX0Kb3V0X3Bsb3Q8LSBvdXQgJT4lIGZpbHRlcighdGVybSAlaW4lIGMoInNleCIsImFnZSIsImV0aG5pY2l0eSIpKSAlPiUKICAgIG11dGF0ZShFeHBFPSBleHAoZXN0aW1hdGUpLCBFeHBMPSBleHAoY29uZi5sb3cpLCBFeHBVPSBleHAoY29uZi5oaWdoKSwKICAgICAgICAgICBTaWduaWZpY2FudD0gaWZlbHNlKHAudmFsdWUgPCAwLjA1LCAiU2lnbmlmaWNhbnQiLCAiTm90IHNpZ25pZmljYW50IiksCiAgICAgICAgICAgYWxwaGE9IGlmZWxzZShwLnZhbHVlIDwgMC4wNSwgMSwgMC4zKSwKICAgICAgICAgICBQcmVkaWN0b3I9IGZjdF9yZW9yZGVyKHRlcm0sIEV4cEUpKQpvdXRfcGxvdCRhbHBoYTwtIEkob3V0X3Bsb3QkYWxwaGEpCgpnZ3Bsb3Qob3V0X3Bsb3QsIGFlcyh4PSBFeHBFLCB5PSBQcmVkaWN0b3IsIGZpbGw9IGVzdGltYXRlLCBhbHBoYT0gYWxwaGEpKSArCiAgZ2VvbV9jb2woKSArCiAgc2NhbGVfeF9jb250aW51b3VzKCBleHBhbmQgPSBjKDAsIDApKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSAibm9uZSIsCiAgICAgICAgYXhpcy50aWNrcy55PSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCksCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3c9ICIjN2E1MTk1IiwgaGlnaD0iI2ZmNzY0YSIpKwogIGxhYnMoeD0gIlxuT2RkcyByYXRpbyAtIEluY3JlYXNlIGluIHJpc2sgaWYgY29uZGl0aW9uIGlzIHByZXNlbnQiLCB5PSAiUHJlLWV4aXN0aW5nIGNvbmRpdGlvbiIsCiAgICAgICB0aXRsZT0gIlVuaXZhcmlhdGUgZWZmZWN0cyBvZiBQcmUtZXhpc3RpbmcgQ29uZGl0aW9ucyBvbiBDT1ZJRCBEZWF0aCIsCiAgICAgICBzdWJ0aXRsZSA9ICJFZmZlY3RzIGluIGxpZ2h0IHNoYWRpbmcgaW5kaWNhdGUgbm9uLXNpZ25pZmljYW50IGNvZWZmaWNpZW50cy4iKQpgYGAKCgpUaGUgYFNtb2tpbmdgIGVmZmVjdCByZWFsbHkgc3RhbmRzIG91dCEgVGhpcyBpcyBtdWNoIGhpZ2hlciB0aGFuIHdoYXQgd2UgaGF2ZSBmb3VuZCBpbiBvdXIgcmVhbCBDT1ZJRCByZXNlYXJjaC4gVGhpcyBjb3VsZCBiZSBiZWNhdXNlIHRoZSBgU21va2luZ2Agb2JzZXJ2YXRpb24gaW4gdGhlIHBhdGllbnQgaGlzdG9yaWVzIGlzIHJlY29yZGVkIGFsb25nc2lkZSBjdXJyZW50LCBkYWlseSBzbW9raW5nIG9yIHRvYmFjY28gdXNlLgoKIyMjIE1vcmUgZm9jdXNlZCBzdXJ2aXZhbCBtb2RlbGxpbmcKCk5vdyB3ZSB3aWxsIHB1bGwgdGhyb3VnaCB0aGUgY29uZGl0aW9ucyB0aGF0IGhhdmUgc2hvd24gc29tZSBzaWduYWwsIGFuZCBiZWdpbiBzdXJ2aXZhbCBtb2RlbGxpbmcgaW4gZWFybmVzdC4gIFRoaXMgaW5jbHVkZXMgc3BsaXR0aW5nIHRoZSBkYXRhIGludG8gYSB0cmFpbmluZyBhbmQgdGVzdCBzZXQuCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIElkZW50aWZ5IHZhcmlhYmxlcyB3aXRoIHNpZ25pZmljYW50IHVuaXZhcmlhdGUgYXNzb2NpYXRpb24Kc2lnczwtIG91dCAlPiUgZmlsdGVyKHAudmFsdWUgPCAwLjA1KSAlPiUgcHVsbCh0ZXJtKSAlPiUgdW5pcXVlKCkgCgojIE1vZGVsbGluZyBkYXRhIHNldApYWTwtICAgUElEICU+JSBzZWxlY3QoU3RhdHVzLCBTdXJ2LCBhbGxfb2Yoc2lncykpICU+JSBuYS5vbWl0KCkKCiMgQ3JlYXRlIHRlc3QtdHJhaW4tc3BsaXQKc2V0LnNlZWQoMTIzKQp0cmFpbkluZGV4PC0gY3JlYXRlRGF0YVBhcnRpdGlvbihYWSRTdGF0dXMsIHA9MC43LCBsaXN0PUZBTFNFLCB0aW1lcz0xKQoKWDwtICAgICAgICAgICBYWSAlPiUgc2VsZWN0KC1jKFN1cnYsIFN0YXR1cykpICAlPiUgYXMubWF0cml4KCkgJT4lIHNjYWxlKCkKeTwtICAgICAgICAgICBYWSRTdXJ2ClhfdHJhaW48LSAgICAgWFt0cmFpbkluZGV4LF0KWF90ZXN0PC0gICAgICBYWy10cmFpbkluZGV4LF0KeV90cmFpbjwtICAgICB5W3RyYWluSW5kZXhdCnlfdGVzdDwtICAgICAgWFkkU3RhdHVzWy10cmFpbkluZGV4XQpQSURfdHJhaW48LSAgIFBJRFt0cmFpbkluZGV4LF0KUElEX3Rlc3Q8LSAgICBQSURbLXRyYWluSW5kZXgsIF0KCiMgVXNlIGdsbW5ldCB0byBhcHBseSByZWd1bGFyaXNlZCBjb3ggcHJvcG9ydGlvbmFsIGhhemFyZHMKZml0IDwtICAgICAgICBnbG1uZXQoWF90cmFpbiwgeV90cmFpbiwgZmFtaWx5PSJjb3giKQpwbG90KGZpdCwgbGFiZWw9VFJVRSwgY2V4PSAxLjUpCmN2LmZpdCA8LSAgICAgY3YuZ2xtbmV0KFhfdHJhaW4sIHlfdHJhaW4sIGZhbWlseT0iY294IikKcGxvdChjdi5maXQpCmBgYApDcm9zcy12YWxpZGF0ZWQgb24gdGhlIGNveCBwaCBzdWdnZXN0cyBtb2RlbCB3aXRoIGJldHdlZW4gNCBhbmQgOCBwcmVkaWN0b3JzLiBMZXRzIHNlZSB3aGF0IGNvZWZmaWNpZW50cyBlbnRlciB0aGUgbW9kZWwgaW4gdGhvc2UgZmlyc3QgNCAtIDggc3RlcHMuIEkgbGlrZSB0byBleGFtaW5lIGhvdyB0aGUgY29lZmZpY2llbnQgbWF0cml4IGV2b2x2ZXMuCgpgYGB7cn0KIyBFeHRyYWN0IHRoZSBjb2VmZmljaWVudCBtYXRyaXggYW5kIHNvcnQgYnkgYWJzb2x1dGUgdmFsdWUgdG8gZ2V0IHRoZSBwcmVkaWN0b3JzIGludG8gYSBzZW5zaWJsZSBvcmRlcgoKbW1hdDwtICAgICAgICAgIGFzLm1hdHJpeChjb2VmKGZpdCkpICU+JSBkYXRhLmZyYW1lKCkgCm1tYXQkcm93X3N1bXM8LSByb3dTdW1zKGFicyhtbWF0KSkKbW1hdDwtICAgICAgICAgIG1tYXQgJT4lIGFycmFuZ2UoZGVzYyhyb3dfc3VtcykpCgpwcmludChyb3duYW1lcyhtbWF0KSkKYGBgClNvIHRoaXMgZ2l2ZXMgdXMgdGhlIGFwcHJveGltYXRlIG9yZGVyIGluIHdoaWNoIHRoZSB2YXJpb3VzIGNvbmRpdGlvbnMgZW50ZXIgdGhlIG11bHRpdmFyaWF0ZSBtb2RlbC4gSSB0ZW5kIHRvIHVzZSB0aGUgY29sdW1ucyBvZiB0aGUgY29lZmZpY2llbnQgbWF0cml4IHRvIGV4cGxvcmUgZGlmZmVyZW50IG1vZGVscyBpbiB0aGUgb3B0aW1hbCByZWdpb24gdW50aWwgSSBmaW5kIG9uZSB0aGF0IGxvb2tzIHNlbnNpYmxlLCB3aXRoIGFsbCBwcmVkaWN0b3JzIGhhdmluZyBhIHNpZ25pZmljYW50IHAtdmFsdWUuCgpgYGB7cn0KIyBDcmVhdGUgaW4gaW5kaWNhdG9yIG1hdHJpeCB0byBzaG93IG1vZGVsIHNpemVzIGF0IGVhY2ggc3RlcApabWF0PC0gICAgICAgIG1tYXQhPTAKbW9kZWxfc2l6ZXM8LSBabWF0ICU+JSBjb2xTdW1zKCkgJT4lIHByaW50KCkKCiMgSSB1c2UgdGhpcyB0byBpdGVyYWN0aXZlbHkgZXhwbG9yZSB0aGUgbW9kZWwgc2VsZWN0aW9ucyBhdCBlYWNoIHN0ZXAKdHJpYWxTZXQ8LSAgIHJvdy5uYW1lcyhabWF0KVtabWF0WywyOF1dIApmb3JtdWxhPC0gICAgYXMuZm9ybXVsYShwYXN0ZSgiU3VydiB+IixzdHJfYyh0cmlhbFNldCwgY29sbGFwc2U9ICIgKyAiKSkpIAoKbTI8LSBjb3hwaChmb3JtdWxhLCAgZGF0YT0gUElEX3RyYWluKQpzdW1tYXJ5KG0yKQpgYGAKCiMjIyBBc3Nlc3MgcHJlZGljdGlvbnMgZnJvbSB0aGUgc3VnZ2VzdGVkIG1vZGVsCgpUaGUgYWJvdmUgYW5hbHlzaXMgaW5kaWNhdGVzIGEgbXVsdGl2YXJpYXRlIG1vZGVsIHdpdGggZml2ZSBmZWF0dXJlcy4gSG93IGRvZXMgdGhpcyBtb2RlbCBkbyBwcmVkaWN0aW5nIGFnYWluc3QgdGhlIHRlc3Qgc2V0PwoKYGBge3IgbWVzc2FnZT0gRkFMU0V9Cm0zPC0gICAgICAgIGNveHBoKFN1cnYgfiBhZ2UgKyBTbW9raW5nICsgSHlwZXJ0ZW5zaW9uICsgQW5lbWlhICsgQ2hyb25pY19yZXNwaXJhdG9yeSwgIAogICAgICAgICAgICAgICAgICBkYXRhPSBQSURfdHJhaW4pCnByZWRzPC0gICAgIHByZWRpY3QobTMsIG5ld2RhdGEgPSBQSURfdGVzdCwgdHlwZT0gInJpc2siKQoKcFJPQ19vYmogPC0gcFJPQzo6cm9jKHlfdGVzdCwgcHJlZHMsCiAgICAgICAgICAgICAgICBzbW9vdGhlZCA9IFRSVUUsCiAgICAgICAgICAgICAgICBjaT1UUlVFLCBjaS5hbHBoYT0wLjksIHN0cmF0aWZpZWQ9RkFMU0UsIGF1Yy5wb2x5Z29uLmNvbD0iI2U2Y2JkYiIsCiAgICAgICAgICAgICAgICBwbG90PVRSVUUsIGF1Yy5wb2x5Z29uPVRSVUUsIG1heC5hdWMucG9seWdvbj1UUlVFLCBncmlkPVRSVUUsCiAgICAgICAgICAgICAgICBwcmludC5hdWM9VFJVRSwgc2hvdy50aHJlcz1UUlVFKQpgYGAKR3JlYXQhIFRoZSBtb2RlbCBzdWdnZXN0ZWQgYnkgcmVndWxhcmlzZWQgY294IHBoIGhhcyBnb29kIHByZWRpY3RpdmUgYWJpbGl0eSBvbiB0aGUgdGVzdCBzZXQsIHdpdGggYW4gQVVDIG9mIDAuODk5LiAgCgojIyMgRmluYWwgbW9kZWwgYW5kIGRpc2N1c3Npb24KCk5vdyB3ZSBwdXQgYWxsIHRoZSBkYXRhIGJhY2sgdG9nZXRoZXIgdG8gZXN0aW1hdGUgdGhlIGVmZmVjdHMgd2l0aCB0aGUgZmluYWwgbW9kZWwuCmBgYHtyIH0KIyBQdXQgdG9nZXRoZXIgZmluYWwgbW9kZWwgdG8gZ2V0IGJlc3QgZXN0aW1hdGVzIG9mIHRoZSBjb2VmZmljaWVudHMKCiMgTGV0cyBwcmVkaWN0IHRoZSBlZmZlY3Qgb2YgYSAxMCB5ZWFyIGluY3JlbWVudCBpbiBhZ2UsIHRvIGdldCBhIGNsZWFyIG9kZHMgcmF0aW8gaW50ZXJ2YWwKUElEJEFnZV8xMF95cnM8LSBQSUQkYWdlLzEwCgpmaW5hbDwtIGNveHBoKFN1cnYgfiBBZ2VfMTBfeXJzICsgU21va2luZyArIEh5cGVydGVuc2lvbiArIEFuZW1pYSArIENocm9uaWNfcmVzcGlyYXRvcnksICAKICAgICAgICAgICAgICBkYXRhPSBQSUQpCmZpbmFsX3N1bTwtIGJyb29tOjp0aWR5KGZpbmFsKSAlPiUgbXV0YXRlKCBFeHBFPSBleHAoZXN0aW1hdGUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRXhwTD0gZXhwKGNvbmYubG93KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEV4cFU9IGV4cChjb25mLmhpZ2gpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgT3JkZXI9IDU6MSkKZmluYWxfc3VtPC0gZmluYWxfc3VtICU+JSBtdXRhdGUoQ29tbWVudD0gcGFzdGUwKHNwcmludGYoIiUyLjFmIixFeHBFKSwiICBbIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3ByaW50ZigiJTIuMWYiLEV4cEwpLCIsICIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwcmludGYoIiUyLjFmIixFeHBVKSwiXSIpKQoKY29sczU8LSBjKCAiIzM3NGM4MCIsICIjN2E1MTk1IiwgIiNiYzUwOTAiLCAiI2VmNTY3NSIsICIjZmY3NjRhIikKCmdncGxvdChmaW5hbF9zdW0sYWVzKHg9IEV4cEUseT1PcmRlciwgY29sb3I9IGZhY3RvcihPcmRlcikpKSArCiAgZ2VvbV9wb2ludChzaXplPTIpICsKICBnZW9tX2Vycm9yYmFyaChhZXMoeG1pbj1FeHBMLHhtYXg9IEV4cFUpLGhlaWdodD0wLjIpKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD0gMSwgY29sPSJncmV5MzAiLHNpemU9LjEsIGx0eT0iZGFzaGVkIikgKwogIGdlb21fdGV4dChhZXMoeD1FeHBFLHk9T3JkZXIrMC4yNSxsYWJlbD0gdGVybSksIGZvbnRmYWNlPSAiYm9sZCIpICsKICBnZW9tX3RleHQoYWVzKHg9RXhwRSx5PU9yZGVyLTAuMjUsbGFiZWw9IENvbW1lbnQpKSArCiAgdGhlbWVfYncoKSArCiAgZXhwYW5kX2xpbWl0cyh4PWMoMCwyKSkrCiAgdGhlbWUodGl0bGU9IGVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQobWFyZ2luID0gbWFyZ2luKHQgPSA1LCByID0gMjAsIGIgPSAwLCBsID0gMCkpLAogICAgICAgIGF4aXMudGV4dC55PSBlbGVtZW50X2JsYW5rKCksYXhpcy50aWNrcy55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueD0gZWxlbWVudF90ZXh0KHNpemU9MTAsbGluZWhlaWdodCA9IDAuMyksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgICAgIHBhbmVsLmdyaWQ9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBzdHJpcC5iYWNrZ3JvdW5kPSBlbGVtZW50X3JlY3QoZmlsbD0iZ3JleTIwIiksCiAgICAgICAgc3RyaXAudGV4dD0gZWxlbWVudF90ZXh0KGNvbG9yPSJ3aGl0ZSIsZmFjZT0iYm9sZCIsc2l6ZT0xMiksCiAgICAgICAgcGFuZWwuc3BhY2luZyA9IHVuaXQoMSwgImxpbmVzIiksCiAgICAgICAgcGFuZWwuYm9yZGVyPSBlbGVtZW50X3JlY3QoY29sb3VyPSAiZ3JleTMwIikpICsKICBsYWJzKHRpdGxlPSAiSW5yZWFzZWQgUmlzayBvZiBDT1ZJRCBEZWF0aCAoT2RkcyBSYXRpbykgYnkgUHJlZGljdG9yIiwgCiAgICAgICB4PSAiQ2hhbmdlIGluIE9kZHMgYXNzb2NpYXRlZCB3aXRoIGVhY2ggcHJlZGljdG9yIiwKICAgICAgIHk9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9IGNvbHM1KQpgYGAKTWFueSB0aGFua3MgdG8gU3ludGhlYSBmb3IgcHJvZHVjaW5nIHN1Y2ggYSBkZXRhaWxlZCBzeW50aGV0aWMgZGF0YSBzZXQgZm9yIGRlbW9uc3RyYXRpbmcgdmFyaW91cyBtb2RlbGxpbmcgbWV0aG9kcy4KCkFnYWluLCBgU21va2luZ2AgbG9va3MgdG8gYmUgdGhlIHJlYWwgdmlsbGFpbiBoZXJlLCB3aXRoIGN1cnJlbnQsIGRhaWx5IHNtb2tpbmcgaW5jcmVhc2luZyB0aGUgb2RkcyBvZiBkZWF0aCAob25jZSBpbmZlY3RlZCB3aXRoIENPVklEKSBieSBhYm91dCB0ZW4tZm9sZC4gIAoKQ29uZGl0aW9ucyB1bmRlciB0aGUgYENocm9uaWNfUmVzcGlyYXRvcnlgIGhlYWRpbmcgaW5jbHVkZSBwdWxtb25hcnkgZW1waHlzZW1hLCAKY2hyb25pYyBvYnN0cnVjdGl2ZSBicm9uY2hpdGlzLCBzdXNwZWN0ZWQgbHVuZyBjYW5jZXIsIHB1bG1vbmFyeSByZWhhYmlsaXRhdGlvbiAocmVnaW1lL3RoZXJhcHkpIGFuZCBjeXN0aWMgZmlicm9zaXMuIFBhdGllbnRzIHdpdGggdGhlc2UgY29uZGl0aW9ucyBtaWdodCBiZSBleHBlY3RlZCB0byBoYXZlIGFuIGluY3JlYXNlZCByaXNrIG9mIGRlYXRoIG9uY2UgaW5mZWN0ZWQgd2l0aCBDT1ZJRCBvZiBiZXR3ZWVuIDEuNHggYW5kIDIuOXggY29tcGFyZWQgd2l0aCB0aG9zZSB3aG8gZG8gbm90LgoKRXZlcnkgYWRkaXRpb25hbCAxMCB5ZWFycyBvZiBhZ2Ugb2YgdGhlIHBhdGllbnQgYXQgYWRtaXNzaW9uIGlzIHNlZW4gdG8gaW5jcmVhc2UgdGhlIHJpc2sgb2YgZGVhdGggYnkgYmV0d2VlbiA1MCUgdG8gNzAlLCB3aGlsZSB0aG9zZSB3aXRoIGh5cGVydGVuc2lvbiBhbHNvIGhhdmUgYWJvdXQgYSA3MCUgaW5jcmVhc2VkIHJpc2suCgoKIyMjIFJlZmVyZW5jZXMKCkphc29uIFdhbG9ub3NraSwgTWFyayBLcmFtZXIsIEpvc2VwaCBOaWNob2xzLCBBbmRyZSBRdWluYSwgQ2hyaXMgTW9lc2VsLCBEeWxhbiBIYWxsLCBDYXJsdG9uIER1ZmZldHQsIEt1ZGFrd2FzaGUgRHViZSwgVGhvbWFzIEdhbGxhZ2hlciwgU2NvdHQgTWNMYWNobGFuLCBTeW50aGVhOiBBbiBhcHByb2FjaCwgbWV0aG9kLCBhbmQgc29mdHdhcmUgbWVjaGFuaXNtIGZvciBnZW5lcmF0aW5nIHN5bnRoZXRpYyBwYXRpZW50cyBhbmQgdGhlIHN5bnRoZXRpYyBlbGVjdHJvbmljIGhlYWx0aCBjYXJlIHJlY29yZCwgSm91cm5hbCBvZiB0aGUgQW1lcmljYW4gTWVkaWNhbCBJbmZvcm1hdGljcyBBc3NvY2lhdGlvbiwgVm9sdW1lIDI1LCBJc3N1ZSAzLCBNYXJjaCAyMDE4LCBQYWdlcyAyMzDigJMyMzgsIGh0dHBzOi8vZG9pLm9yZy8xMC4xMDkzL2phbWlhL29jeDA3OQo=