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.
- Patient characterists (age, sex, etc. - one row per patient)
- 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=