Project Goal:

I am mandated to determine how annual members and casual riders use Cyclistic bikes differently with the aim of converting casual riders into annual member.

Key stakeholders:
Lily Moreno: The director of marketing,
Cyclistic executive team and
Cyclistic marketing analytics team.

Ask Phase

By the end of this project, i should be able to determine how annual members and casual riders use Cyclistic bikes differently, Why casual riders would want to buy Cyclistic annual memberships and how Cyclistic can use digital media to influence casual riders to become members.

Tools Used for this project.

Excel, SQL and R-Language

Prepare Phase

The datasets being used for the project are 12 months of trip data owned by Motivate International Inc who have granted me a non-exclusive, royalty-free, limited, perpetual license to access, reproduce, analyze, copy, modify, distribute in my product or service and use the Data for any lawful purpose (“License”).

There are no issues of bias or credibility with these datasets. They are Reliable, Original, Comprehensive, Current and Cited.

Upon initial inspection of each of the 12 csv datasets using ISBLANK function in excel, i noticed that some of the observations are missing. This i will address subsequently using RStudio.

Loading Datasets

Loading the needed 12 csv datasets, i will first load the tidyverse package which contains most of the dataframe manipulation packages needed for loading and cleaning the datasets for this project.

library("tidyverse")

DATA COMBINATION

I have just the needed 12 csv files for this project in my current working directory with each file representing trip data for a month. read.csv function from readr package will import all the files and the rbind function will bind all the rows of these separate files into one dataframe.

files <- dir(pattern = "*.csv")
files
trip_data <- files %>%
  map(read_csv) %>%   
  reduce(rbind)        
trip_data
NA

lets take a glimpse of my full data by inspecting the data names and types

glimpse(trip_data)
Rows: 4,731,081
Columns: 13
$ ride_id            <chr> "6C992BD37A98A63F", "1E0145613A209000", "E498E15508~
$ rideable_type      <chr> "classic_bike", "docked_bike", "docked_bike", "clas~
$ started_at         <dttm> 2021-04-12 18:25:36, 2021-04-27 17:27:11, 2021-04-~
$ ended_at           <dttm> 2021-04-12 18:56:55, 2021-04-27 18:31:29, 2021-04-~
$ start_station_name <chr> "State St & Pearson St", "Dorchester Ave & 49th St"~
$ start_station_id   <chr> "TA1307000061", "KA1503000069", "20121", "TA1305000~
$ end_station_name   <chr> "Southport Ave & Waveland Ave", "Dorchester Ave & 4~
$ end_station_id     <chr> "13235", "KA1503000069", "20121", "13235", "20121",~
$ start_lat          <dbl> 41.89745, 41.80577, 41.74149, 41.90312, 41.74149, 4~
$ start_lng          <dbl> -87.62872, -87.59246, -87.65841, -87.67394, -87.658~
$ end_lat            <dbl> 41.94815, 41.80577, 41.74149, 41.94815, 41.74149, 4~
$ end_lng            <dbl> -87.66394, -87.59246, -87.65841, -87.66394, -87.658~
$ member_casual      <chr> "member", "casual", "casual", "member", "casual", "~

Process Phase

For thise phase, i am using RStudio for my data cleaning. ### DATA CLEANING

from the glimpse, i can see that i now have 4,731,081 rows and it also reveals that some variables need renaming for consistency and clarity sake.
variables like “started_at” will be renamed “start_time”, “ended_at” will be renamed “end_time” and “member_casual” will be renamed “user_status”

renamed_col <- trip_data %>% 
  rename(start_time=started_at, end_time = ended_at,user_status= member_casual
         , bike_type = rideable_type)
glimpse(renamed_col)
Rows: 4,731,081
Columns: 13
$ ride_id            <chr> "6C992BD37A98A63F", "1E0145613A209000", "E498E15508~
$ bike_type          <chr> "classic_bike", "docked_bike", "docked_bike", "clas~
$ start_time         <dttm> 2021-04-12 18:25:36, 2021-04-27 17:27:11, 2021-04-~
$ end_time           <dttm> 2021-04-12 18:56:55, 2021-04-27 18:31:29, 2021-04-~
$ start_station_name <chr> "State St & Pearson St", "Dorchester Ave & 49th St"~
$ start_station_id   <chr> "TA1307000061", "KA1503000069", "20121", "TA1305000~
$ end_station_name   <chr> "Southport Ave & Waveland Ave", "Dorchester Ave & 4~
$ end_station_id     <chr> "13235", "KA1503000069", "20121", "13235", "20121",~
$ start_lat          <dbl> 41.89745, 41.80577, 41.74149, 41.90312, 41.74149, 4~
$ start_lng          <dbl> -87.62872, -87.59246, -87.65841, -87.67394, -87.658~
$ end_lat            <dbl> 41.94815, 41.80577, 41.74149, 41.94815, 41.74149, 4~
$ end_lng            <dbl> -87.66394, -87.59246, -87.65841, -87.66394, -87.658~
$ user_status        <chr> "member", "casual", "casual", "member", "casual", "~
head(renamed_col)

I will add trip_duration column by using the mutate function in combination with some functions from the lubridate package.

library("lubridate")
with_trip_duration <-mutate(renamed_col, trip_duration= as.duration
                            (interval(ymd_hms(renamed_col$start_time),
                                      ymd_hms(renamed_col$end_time))))
with_trip_duration

I will add another column with ride weekdays in order to determine what day of the week majority of the users prefer riding.

with_week_day <- with_trip_duration %>% 
  mutate(weekday=weekdays(as.Date(with_trip_duration$start_time)))

with_week_day

checking for consistency in some of the variable names with unique()

unique(with_week_day$user_status)
[1] "member" "casual"
unique(with_week_day$bike_type)
[1] "classic_bike"  "docked_bike"   "electric_bike"

checking and removing null and na data for ease of data analysis

is.null(with_week_day)
[1] FALSE
cleaned_trip <-na.omit(with_week_day) 
cleaned_trip

the na.omit function removed about 600,000 values which i am assuming are negligible

Analyze and Share Phases.

l will first determine the count of the two user_types

user_count <-table(cleaned_trip$user_status)
user_count

 casual  member 
1828502 2338897 
plot(user_count)

pie(user_count)

From the above analysis, it shows that we have more registered users than casual users. 1828502 for casual users and 2338897 for registered users.

Now, lets determine the average duration of each of the categories of riders

avg_trip_duration <- cleaned_trip %>% 
  group_by(user_status) %>% 
  summarize(avg = mean(trip_duration)/60)

avg_trip_duration 

From the above analysis, it is evident that the casual riders tend to ride longer at an average of of 36.6 minutes/ride than the member/registered riders who ride for an average of 11.2 minutes/ride.

Next, i will determine when majority of the riders prefer to ride by extracting the aggregate count of days for each of the user_status

day_of_ride<- cleaned_trip %>% 
  group_by(weekday, user_status) %>% 
  select(user_status, weekday) %>% 
  summarize(number = table(weekday))

day_of_ride

in order to extract the casual user with their corresponding weekday and number, i exported the outcome above to SQL. I obtained the desired result by performing the command below;

SELECT * FROM day_of_ride WHERE user_status= "member"
SELECT * FROM day_of_ride WHERE user_status= "casual"

Then i imported the query result into Rstudio for futher analysis.

casual_ride <- read.csv("C:\\Users\\EDEH EMEKA NWEKE\\Desktop\\casual_ride_data.csv")
member_ride <-read.csv("C:\\Users\\EDEH EMEKA NWEKE\\Desktop\\member_ride_data.csv")
casual_ride
member_ride

Then i sorted the above outcome in a descending order for clearer picture.

sort_casual_ride <- casual_ride %>% 
  arrange(number)

sort_casual_ride
library('ggplot2')
ggplot(sort_casual_ride, aes(x=weekday, y=number, color =weekday, fill= weekday)) + 
  labs(title = 'Number of Casual Riders vs Days of the week', 
       caption = 'Data analyzed by Edeh Emeka')+ 
  geom_bar(stat = "identity")+
  theme(legend.position="none")

NA
NA
NA
NA
sort_member_ride <- member_ride %>% 
  arrange(number)

sort_member_ride
ggplot(sort_member_ride, aes(x=weekday, y=number, color =weekday, fill = weekday)) + 
  labs(title = 'Number of member Riders vs Days of the week', 
       caption = 'Data analyzed by Edeh Emeka')+ 
  geom_bar(stat = "identity")+
  theme(legend.position="none")

From the above graph, it is evident that majority of the casual riders, close to 60%, prefer riding over the weekend, Fridays, Saturdays and Sundays as against the member/registered riders that are almost evenly distributed across the weeks. about 60% of casual trips take place over the weekend.

In trying to extract the months of these rides, i ran the following codes, exported the outcome to SQL in order to aggregate the user counts with their respective months.

as_month<- cleaned_trip %>% 
  mutate(month=format(cleaned_trip$end_time, "%m"))
View(as_month)

Since the month(number) is in character format, i have to convert it to a numeric format by running the code below.

month_as_num <- as_month %>% 
  mutate(month_num = as.numeric(as_month$month))

str(month_as_num)
tibble[,17] [4,167,399 x 17] (S3: tbl_df/tbl/data.frame)
 $ ride_id           : chr [1:4167399] "6C992BD37A98A63F" "1E0145613A209000" "E498E15508A80BAD" "1887262AD101C604" ...
 $ bike_type         : chr [1:4167399] "classic_bike" "docked_bike" "docked_bike" "classic_bike" ...
 $ start_time        : POSIXct[1:4167399], format: "2021-04-12 18:25:36" "2021-04-27 17:27:11" ...
 $ end_time          : POSIXct[1:4167399], format: "2021-04-12 18:56:55" "2021-04-27 18:31:29" ...
 $ start_station_name: chr [1:4167399] "State St & Pearson St" "Dorchester Ave & 49th St" "Loomis Blvd & 84th St" "Honore St & Division St" ...
 $ start_station_id  : chr [1:4167399] "TA1307000061" "KA1503000069" "20121" "TA1305000034" ...
 $ end_station_name  : chr [1:4167399] "Southport Ave & Waveland Ave" "Dorchester Ave & 49th St" "Loomis Blvd & 84th St" "Southport Ave & Waveland Ave" ...
 $ end_station_id    : chr [1:4167399] "13235" "KA1503000069" "20121" "13235" ...
 $ start_lat         : num [1:4167399] 41.9 41.8 41.7 41.9 41.7 ...
 $ start_lng         : num [1:4167399] -87.6 -87.6 -87.7 -87.7 -87.7 ...
 $ end_lat           : num [1:4167399] 41.9 41.8 41.7 41.9 41.7 ...
 $ end_lng           : num [1:4167399] -87.7 -87.6 -87.7 -87.7 -87.7 ...
 $ user_status       : chr [1:4167399] "member" "casual" "casual" "member" ...
 $ trip_duration     :Formal class 'Duration' [package "lubridate"] with 1 slot
  .. ..@ .Data: num [1:4167399] 1879 3858 341859 1506 5477 ...
 $ weekday           : chr [1:4167399] "Monday" "Tuesday" "Saturday" "Saturday" ...
 $ month             : chr [1:4167399] "04" "04" "04" "04" ...
 $ month_num         : num [1:4167399] 4 4 4 4 4 4 4 4 4 4 ...
 - attr(*, "na.action")= 'omit' Named int [1:563682] 160 492 493 494 1083 1084 1085 1086 1091 1998 ...
  ..- attr(*, "names")= chr [1:563682] "160" "492" "493" "494" ...

I will convert the numeric month to month names by running the code below.

month_letter <- month_as_num %>% 
  mutate(month_l = month.name[month_as_num$month_num])
month_letter
month_of_ride<- month_letter %>% 
  group_by(month_l, user_status) %>% 
  summarize(number = table(month_l))
`summarise()` has grouped output by 'month_l'. You can override using the `.groups` argument.
month_of_ride

I will export the above outcome to a csv file for further analysis in SQL in order to aggregate the user counts with their corresponding months.

write.csv(month_of_ride, "month_of_ride.csv")

I will extract the data for the two different user types, casual and member, using the sql command below.

SELECT * FROM month_of_ride WHERE user_status = "member"

SELECT * FROM month_of_ride WHERE user_status = "casual"

Next, i will import and sort the results of the above commands in a descending order.

member_month <-read.csv("C:\\Users\\EDEH EMEKA NWEKE\\Desktop\\member_month_of_ride.csv")


sort_member_month <- member_month %>% 
  arrange(number)

sort_member_month
sort_casual_month <- casual_month %>% 
  arrange(number)

sort_casual_month

I will further present the above results graphically for clearer picture.

ggplot(sort_member_month, aes(x=month_l, y=number, color =month_l, fill = month_l)) + 
  labs(title = 'Number of member Rides vs Months of the year', 
       caption = 'Data analyzed by Edeh Emeka')+ 
  geom_bar(stat = "identity")+
  theme(legend.position="none")

ggplot(sort_casual_month, aes(x=month_l, y=number, color =month_l, fill = month_l)) + 
  labs(title = 'Number of casual Rides vs Months of the year', 
       caption = 'Data analyzed by Edeh Emeka')+ 
  geom_bar(stat = "identity")+
  theme(legend.position="none")

From the above graphs, it is evident that casual riders are mostly active in the summer months of June, July and August.

ACT

Here are my three observations and recommendations in order to convert casual riders to annual riders.

Observations
1. Casual users are mostly active on weekends (Fridays, Saturdays and Sundays)as against annual/member users that are almost evenly active all through the week.
2. on the average, Casual users tend to ride for about 25 minutes longer than the member/annual users.
3. Casual users are mostly active during the summer months (June, July, August) while annual/member users are active in all the months bar winter months.

Recommendations
1. Offer membership exclusive incentives/packages for weekend rides.
2. Offer discounted special summer packages to members.
3. Offer special discounts for rides that last longer than 15 minutes.

Edeh Emeka N.
2021-08-26

LS0tDQp0aXRsZTogIkdvb2dsZSBEYXRhIEFuYWx5dGljcyBDYXBzdG9uZSBQcm9qZWN0Ig0KYXV0aG9yOiAiRWRlaCBFbWVrYSBOIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQpoaWdobGlnaHQ6IHRhbmdvDQpEYXRlOiAiMjAyMS0wOC0yNiINCi0tLQ0KDQojIyMgUHJvamVjdCBHb2FsOiANCkkgYW0gbWFuZGF0ZWQgdG8gZGV0ZXJtaW5lIGhvdyBhbm51YWwgbWVtYmVycyBhbmQgY2FzdWFsIHJpZGVycyB1c2UgQ3ljbGlzdGljIGJpa2VzIGRpZmZlcmVudGx5IHdpdGggdGhlIGFpbSBvZiBjb252ZXJ0aW5nIGNhc3VhbCByaWRlcnMgaW50byBhbm51YWwgbWVtYmVyLiAgDQoNCioqS2V5IHN0YWtlaG9sZGVycyoqOiAgICAgIA0KTGlseSBNb3Jlbm86IFRoZSBkaXJlY3RvciBvZiBtYXJrZXRpbmcsICAgIA0KQ3ljbGlzdGljIGV4ZWN1dGl2ZSB0ZWFtIGFuZCAgDQpDeWNsaXN0aWMgbWFya2V0aW5nIGFuYWx5dGljcyB0ZWFtLiAgICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgDQojIyMgQXNrIFBoYXNlDQpCeSB0aGUgZW5kIG9mIHRoaXMgcHJvamVjdCwgaSBzaG91bGQgYmUgYWJsZSB0byBkZXRlcm1pbmUgaG93IGFubnVhbCBtZW1iZXJzIGFuZCBjYXN1YWwgcmlkZXJzIHVzZSBDeWNsaXN0aWMgYmlrZXMgZGlmZmVyZW50bHksIFdoeSBjYXN1YWwgcmlkZXJzIHdvdWxkIHdhbnQgdG8gYnV5IEN5Y2xpc3RpYyBhbm51YWwgbWVtYmVyc2hpcHMgYW5kIGhvdyBDeWNsaXN0aWMgY2FuIHVzZSBkaWdpdGFsIG1lZGlhIHRvIGluZmx1ZW5jZSBjYXN1YWwgcmlkZXJzIHRvIGJlY29tZSBtZW1iZXJzLg0KDQojIyMjIFRvb2xzIFVzZWQgZm9yIHRoaXMgcHJvamVjdC4NCg0KRXhjZWwsIFNRTCBhbmQgUi1MYW5ndWFnZQ0KDQojIyMgUHJlcGFyZSBQaGFzZQ0KVGhlIGRhdGFzZXRzIGJlaW5nIHVzZWQgZm9yIHRoZSBwcm9qZWN0IGFyZSAxMiBtb250aHMgb2YgdHJpcCBkYXRhIG93bmVkIGJ5IE1vdGl2YXRlIEludGVybmF0aW9uYWwgSW5jIHdobyBoYXZlIGdyYW50ZWQgbWUgYSBub24tZXhjbHVzaXZlLCByb3lhbHR5LWZyZWUsIGxpbWl0ZWQsIHBlcnBldHVhbCBsaWNlbnNlIHRvIGFjY2VzcywgcmVwcm9kdWNlLCBhbmFseXplLCBjb3B5LCBtb2RpZnksIGRpc3RyaWJ1dGUgaW4gbXkgcHJvZHVjdCBvciBzZXJ2aWNlIGFuZCB1c2UgdGhlIERhdGEgZm9yIGFueSBsYXdmdWwgcHVycG9zZSAo4oCcTGljZW5zZeKAnSkuICANCg0KVGhlcmUgYXJlIG5vIGlzc3VlcyBvZiBiaWFzIG9yIGNyZWRpYmlsaXR5IHdpdGggdGhlc2UgZGF0YXNldHMuIFRoZXkgYXJlIFJlbGlhYmxlLCBPcmlnaW5hbCwgQ29tcHJlaGVuc2l2ZSwgQ3VycmVudCBhbmQgQ2l0ZWQuICANCg0KVXBvbiBpbml0aWFsIGluc3BlY3Rpb24gb2YgZWFjaCBvZiB0aGUgMTIgY3N2IGRhdGFzZXRzIHVzaW5nIElTQkxBTksgZnVuY3Rpb24gaW4gZXhjZWwsIGkgbm90aWNlZCB0aGF0IHNvbWUgb2YgdGhlIG9ic2VydmF0aW9ucyBhcmUgbWlzc2luZy4gVGhpcyBpIHdpbGwgYWRkcmVzcyBzdWJzZXF1ZW50bHkgdXNpbmcgUlN0dWRpby4NCiAgICAgICAgICAgICAgICAgICAgIA0KIyMjIExvYWRpbmcgRGF0YXNldHMNCg0KTG9hZGluZyB0aGUgbmVlZGVkIDEyIGNzdiBkYXRhc2V0cywgaSB3aWxsIGZpcnN0IGxvYWQgdGhlIHRpZHl2ZXJzZSBwYWNrYWdlIHdoaWNoIGNvbnRhaW5zIG1vc3Qgb2YgdGhlIGRhdGFmcmFtZSBtYW5pcHVsYXRpb24gcGFja2FnZXMgbmVlZGVkIA0KZm9yIGxvYWRpbmcgYW5kIGNsZWFuaW5nIHRoZSBkYXRhc2V0cyBmb3IgdGhpcyBwcm9qZWN0Lg0KYGBge3J9DQpsaWJyYXJ5KCJ0aWR5dmVyc2UiKQ0KYGBgDQoNCiMjIyBEQVRBIENPTUJJTkFUSU9ODQpJIGhhdmUganVzdCB0aGUgbmVlZGVkIDEyIGNzdiBmaWxlcyBmb3IgdGhpcyBwcm9qZWN0IGluIG15IGN1cnJlbnQgd29ya2luZyBkaXJlY3Rvcnkgd2l0aCBlYWNoIGZpbGUgcmVwcmVzZW50aW5nIHRyaXAgZGF0YSBmb3IgYSBtb250aC4gcmVhZC5jc3YgZnVuY3Rpb24gZnJvbSByZWFkciBwYWNrYWdlIHdpbGwgaW1wb3J0IGFsbCB0aGUgZmlsZXMgYW5kIHRoZSByYmluZCBmdW5jdGlvbiB3aWxsIGJpbmQgYWxsIHRoZSByb3dzIG9mIHRoZXNlIHNlcGFyYXRlIGZpbGVzIGludG8gb25lIGRhdGFmcmFtZS4gDQoNCmBgYHtyIHJlc3VsdHM9ImhpZGUifQ0KZmlsZXMgPC0gZGlyKHBhdHRlcm4gPSAiKi5jc3YiKQ0KZmlsZXMNCg0KdHJpcF9kYXRhIDwtIGZpbGVzICU+JQ0KICBtYXAocmVhZF9jc3YpICU+JSAgIA0KICByZWR1Y2UocmJpbmQpICAgICAgICANCnRyaXBfZGF0YQ0KDQpgYGANCmxldHMgdGFrZSBhIGdsaW1wc2Ugb2YgbXkgZnVsbCBkYXRhIGJ5IGluc3BlY3RpbmcgdGhlIGRhdGEgbmFtZXMgYW5kIHR5cGVzDQpgYGB7cn0NCmdsaW1wc2UodHJpcF9kYXRhKQ0KYGBgDQojIyMgUHJvY2VzcyBQaGFzZQ0KDQpGb3IgdGhpc2UgcGhhc2UsIGkgYW0gdXNpbmcgUlN0dWRpbyBmb3IgbXkgZGF0YSBjbGVhbmluZy4NCiMjIyBEQVRBIENMRUFOSU5HDQoNCmZyb20gdGhlIGdsaW1wc2UsIGkgY2FuIHNlZSB0aGF0IGkgbm93IGhhdmUgNCw3MzEsMDgxIHJvd3MgYW5kIGl0IA0KYWxzbyByZXZlYWxzIHRoYXQgc29tZSB2YXJpYWJsZXMgbmVlZCByZW5hbWluZyBmb3IgY29uc2lzdGVuY3kNCmFuZCBjbGFyaXR5IHNha2UuICANCnZhcmlhYmxlcyBsaWtlICJzdGFydGVkX2F0IiB3aWxsIGJlIHJlbmFtZWQgInN0YXJ0X3RpbWUiLCAiZW5kZWRfYXQiIHdpbGwNCmJlIHJlbmFtZWQgImVuZF90aW1lIiBhbmQgIm1lbWJlcl9jYXN1YWwiIHdpbGwgYmUgcmVuYW1lZCAidXNlcl9zdGF0dXMiDQoNCmBgYHtyfQ0KcmVuYW1lZF9jb2wgPC0gdHJpcF9kYXRhICU+JSANCiAgcmVuYW1lKHN0YXJ0X3RpbWU9c3RhcnRlZF9hdCwgZW5kX3RpbWUgPSBlbmRlZF9hdCx1c2VyX3N0YXR1cz0gbWVtYmVyX2Nhc3VhbA0KICAgICAgICAgLCBiaWtlX3R5cGUgPSByaWRlYWJsZV90eXBlKQ0KZ2xpbXBzZShyZW5hbWVkX2NvbCkNCmhlYWQocmVuYW1lZF9jb2wpDQpgYGANCkkgd2lsbCBhZGQgdHJpcF9kdXJhdGlvbiBjb2x1bW4gYnkgdXNpbmcgdGhlIG11dGF0ZSBmdW5jdGlvbg0KaW4gY29tYmluYXRpb24gd2l0aCBzb21lIGZ1bmN0aW9ucyBmcm9tIHRoZSBsdWJyaWRhdGUgcGFja2FnZS4NCmBgYHtyfQ0KbGlicmFyeSgibHVicmlkYXRlIikNCndpdGhfdHJpcF9kdXJhdGlvbiA8LW11dGF0ZShyZW5hbWVkX2NvbCwgdHJpcF9kdXJhdGlvbj0gYXMuZHVyYXRpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAoaW50ZXJ2YWwoeW1kX2htcyhyZW5hbWVkX2NvbCRzdGFydF90aW1lKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeW1kX2htcyhyZW5hbWVkX2NvbCRlbmRfdGltZSkpKSkNCg0KDQpgYGANCg0KYGBge3J9DQp3aXRoX3RyaXBfZHVyYXRpb24NCmBgYA0KSSB3aWxsIGFkZCBhbm90aGVyIGNvbHVtbiB3aXRoIHJpZGUgd2Vla2RheXMgaW4gb3JkZXIgdG8gZGV0ZXJtaW5lIHdoYXQgZGF5IG9mDQp0aGUgd2VlayBtYWpvcml0eSBvZiB0aGUgdXNlcnMgcHJlZmVyIHJpZGluZy4NCmBgYHtyfQ0Kd2l0aF93ZWVrX2RheSA8LSB3aXRoX3RyaXBfZHVyYXRpb24gJT4lIA0KICBtdXRhdGUod2Vla2RheT13ZWVrZGF5cyhhcy5EYXRlKHdpdGhfdHJpcF9kdXJhdGlvbiRzdGFydF90aW1lKSkpDQoNCndpdGhfd2Vla19kYXkNCmBgYA0KDQpjaGVja2luZyBmb3IgY29uc2lzdGVuY3kgaW4gc29tZSBvZiB0aGUgdmFyaWFibGUgbmFtZXMgd2l0aCB1bmlxdWUoKQ0KDQpgYGB7cn0NCnVuaXF1ZSh3aXRoX3dlZWtfZGF5JHVzZXJfc3RhdHVzKQ0KdW5pcXVlKHdpdGhfd2Vla19kYXkkYmlrZV90eXBlKQ0KYGBgDQpjaGVja2luZyBhbmQgcmVtb3ZpbmcgbnVsbCBhbmQgbmEgZGF0YSBmb3IgZWFzZSBvZiBkYXRhIGFuYWx5c2lzDQpgYGB7cn0NCmlzLm51bGwod2l0aF93ZWVrX2RheSkNCmNsZWFuZWRfdHJpcCA8LW5hLm9taXQod2l0aF93ZWVrX2RheSkgDQpjbGVhbmVkX3RyaXANCmBgYA0KdGhlIG5hLm9taXQgZnVuY3Rpb24gcmVtb3ZlZCBhYm91dCA2MDAsMDAwIHZhbHVlcyB3aGljaCBpIGFtIGFzc3VtaW5nIGFyZSBuZWdsaWdpYmxlDQoNCiMjIyBBbmFseXplIGFuZCBTaGFyZSBQaGFzZXMuDQoNCmwgd2lsbCBmaXJzdCBkZXRlcm1pbmUgdGhlIGNvdW50IG9mIHRoZSB0d28gdXNlcl90eXBlcw0KYGBge3J9DQp1c2VyX2NvdW50IDwtdGFibGUoY2xlYW5lZF90cmlwJHVzZXJfc3RhdHVzKQ0KdXNlcl9jb3VudA0KcGxvdCh1c2VyX2NvdW50KQ0KcGllKHVzZXJfY291bnQpDQpgYGANCg0KRnJvbSB0aGUgYWJvdmUgYW5hbHlzaXMsIGl0IHNob3dzIHRoYXQgd2UgaGF2ZSBtb3JlIHJlZ2lzdGVyZWQgdXNlcnMgdGhhbiBjYXN1YWwNCnVzZXJzLiAxODI4NTAyIGZvciBjYXN1YWwgdXNlcnMgYW5kIDIzMzg4OTcgZm9yIHJlZ2lzdGVyZWQgdXNlcnMuICANCg0KTm93LCBsZXRzIGRldGVybWluZSB0aGUgYXZlcmFnZSBkdXJhdGlvbiBvZiBlYWNoIG9mIHRoZSBjYXRlZ29yaWVzIG9mIHJpZGVycw0KDQpgYGB7cn0NCmF2Z190cmlwX2R1cmF0aW9uIDwtIGNsZWFuZWRfdHJpcCAlPiUgDQogIGdyb3VwX2J5KHVzZXJfc3RhdHVzKSAlPiUgDQogIHN1bW1hcml6ZShhdmcgPSBtZWFuKHRyaXBfZHVyYXRpb24pLzYwKQ0KDQphdmdfdHJpcF9kdXJhdGlvbiANCmBgYA0KDQpGcm9tIHRoZSBhYm92ZSBhbmFseXNpcywgaXQgaXMgZXZpZGVudCB0aGF0IHRoZSBjYXN1YWwgcmlkZXJzIHRlbmQgdG8gcmlkZQ0KbG9uZ2VyIGF0IGFuIGF2ZXJhZ2Ugb2Ygb2YgMzYuNiBtaW51dGVzL3JpZGUgdGhhbiB0aGUgbWVtYmVyL3JlZ2lzdGVyZWQgcmlkZXJzIHdobyByaWRlIGZvciBhbiBhdmVyYWdlIG9mIDExLjIgbWludXRlcy9yaWRlLiAgDQoNCg0KTmV4dCwgaSB3aWxsIGRldGVybWluZSB3aGVuIG1ham9yaXR5IG9mIHRoZSByaWRlcnMgcHJlZmVyIHRvIHJpZGUgYnkgZXh0cmFjdGluZw0KdGhlIGFnZ3JlZ2F0ZSBjb3VudCBvZiBkYXlzIGZvciBlYWNoIG9mIHRoZSB1c2VyX3N0YXR1cw0KYGBge3J9DQpkYXlfb2ZfcmlkZTwtIGNsZWFuZWRfdHJpcCAlPiUgDQogIGdyb3VwX2J5KHdlZWtkYXksIHVzZXJfc3RhdHVzKSAlPiUgDQogIHNlbGVjdCh1c2VyX3N0YXR1cywgd2Vla2RheSkgJT4lIA0KICBzdW1tYXJpemUobnVtYmVyID0gdGFibGUod2Vla2RheSkpDQoNCmRheV9vZl9yaWRlDQpgYGANCmluIG9yZGVyIHRvIGV4dHJhY3QgdGhlIGNhc3VhbCB1c2VyIHdpdGggdGhlaXIgY29ycmVzcG9uZGluZyB3ZWVrZGF5IGFuZCBudW1iZXIsIGkgZXhwb3J0ZWQgdGhlIG91dGNvbWUgYWJvdmUgdG8gU1FMLiAgSSBvYnRhaW5lZCB0aGUgZGVzaXJlZCByZXN1bHQgYnkgcGVyZm9ybWluZyB0aGUgY29tbWFuZCBiZWxvdzsNCg0KYGBge3NxbCBjb25uZWN0aW9uPWNvbn0NClNFTEVDVCAqIEZST00gZGF5X29mX3JpZGUgV0hFUkUgdXNlcl9zdGF0dXM9ICJtZW1iZXIiDQpgYGANCg0KYGBge3NxbCBjb25uZWN0aW9uPWNvbn0NClNFTEVDVCAqIEZST00gZGF5X29mX3JpZGUgV0hFUkUgdXNlcl9zdGF0dXM9ICJjYXN1YWwiDQpgYGANCg0KVGhlbiBpIGltcG9ydGVkIHRoZSBxdWVyeSByZXN1bHQgaW50byBSc3R1ZGlvIGZvciBmdXRoZXIgYW5hbHlzaXMuDQoNCmBgYHtyfQ0KY2FzdWFsX3JpZGUgPC0gcmVhZC5jc3YoIkM6XFxVc2Vyc1xcRURFSCBFTUVLQSBOV0VLRVxcRGVza3RvcFxcY2FzdWFsX3JpZGVfZGF0YS5jc3YiKQ0KYGBgDQpgYGB7cn0NCm1lbWJlcl9yaWRlIDwtcmVhZC5jc3YoIkM6XFxVc2Vyc1xcRURFSCBFTUVLQSBOV0VLRVxcRGVza3RvcFxcbWVtYmVyX3JpZGVfZGF0YS5jc3YiKQ0KYGBgDQoNCmBgYHtyfQ0KY2FzdWFsX3JpZGUNCmBgYA0KDQpgYGB7cn0NCm1lbWJlcl9yaWRlDQpgYGANClRoZW4gaSBzb3J0ZWQgdGhlIGFib3ZlIG91dGNvbWUgaW4gYSBkZXNjZW5kaW5nIG9yZGVyIGZvciBjbGVhcmVyIHBpY3R1cmUuDQoNCg0KYGBge3J9DQpzb3J0X2Nhc3VhbF9yaWRlIDwtIGNhc3VhbF9yaWRlICU+JSANCiAgYXJyYW5nZShudW1iZXIpDQoNCnNvcnRfY2FzdWFsX3JpZGUNCmBgYA0KDQpgYGB7cn0NCmxpYnJhcnkoJ2dncGxvdDInKQ0KYGBgDQoNCmBgYHtyfQ0KZ2dwbG90KHNvcnRfY2FzdWFsX3JpZGUsIGFlcyh4PXdlZWtkYXksIHk9bnVtYmVyLCBjb2xvciA9d2Vla2RheSwgZmlsbD0gd2Vla2RheSkpICsgDQogIGxhYnModGl0bGUgPSAnTnVtYmVyIG9mIENhc3VhbCBSaWRlcnMgdnMgRGF5cyBvZiB0aGUgd2VlaycsIA0KICAgICAgIGNhcHRpb24gPSAnRGF0YSBhbmFseXplZCBieSBFZGVoIEVtZWthJykrIA0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikrDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpDQoNCiANCg0KDQpgYGANCmBgYHtyfQ0Kc29ydF9tZW1iZXJfcmlkZSA8LSBtZW1iZXJfcmlkZSAlPiUgDQogIGFycmFuZ2UobnVtYmVyKQ0KDQpzb3J0X21lbWJlcl9yaWRlDQpgYGANCmBgYHtyfQ0KZ2dwbG90KHNvcnRfbWVtYmVyX3JpZGUsIGFlcyh4PXdlZWtkYXksIHk9bnVtYmVyLCBjb2xvciA9d2Vla2RheSwgZmlsbCA9IHdlZWtkYXkpKSArIA0KICBsYWJzKHRpdGxlID0gJ051bWJlciBvZiBtZW1iZXIgUmlkZXJzIHZzIERheXMgb2YgdGhlIHdlZWsnLCANCiAgICAgICBjYXB0aW9uID0gJ0RhdGEgYW5hbHl6ZWQgYnkgRWRlaCBFbWVrYScpKyANCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKQ0KYGBgDQpGcm9tIHRoZSBhYm92ZSBncmFwaCwgaXQgaXMgZXZpZGVudCB0aGF0IG1ham9yaXR5IG9mIHRoZSBjYXN1YWwgcmlkZXJzLCBjbG9zZSB0byA2MCUsIHByZWZlciByaWRpbmcgb3ZlciB0aGUgd2Vla2VuZCwgRnJpZGF5cywgU2F0dXJkYXlzIGFuZCBTdW5kYXlzIGFzIGFnYWluc3QgdGhlIG1lbWJlci9yZWdpc3RlcmVkIHJpZGVycyB0aGF0IGFyZSBhbG1vc3QgZXZlbmx5IGRpc3RyaWJ1dGVkIGFjcm9zcyB0aGUgd2Vla3MuDQphYm91dCA2MCUgb2YgY2FzdWFsIHRyaXBzIHRha2UgcGxhY2Ugb3ZlciB0aGUgd2Vla2VuZC4gIA0KDQoNCg0KSW4gdHJ5aW5nIHRvIGV4dHJhY3QgdGhlIG1vbnRocyBvZiB0aGVzZSByaWRlcywgaSByYW4gdGhlIGZvbGxvd2luZyBjb2RlcywNCmV4cG9ydGVkIHRoZSBvdXRjb21lIHRvIFNRTCBpbiBvcmRlciB0byBhZ2dyZWdhdGUgdGhlIHVzZXIgY291bnRzIHdpdGggdGhlaXINCnJlc3BlY3RpdmUgbW9udGhzLg0KYGBge3J9DQphc19tb250aDwtIGNsZWFuZWRfdHJpcCAlPiUgDQogIG11dGF0ZShtb250aD1mb3JtYXQoY2xlYW5lZF90cmlwJGVuZF90aW1lLCAiJW0iKSkNCmFzX21vbnRoDQpgYGANCg0KU2luY2UgdGhlIG1vbnRoKG51bWJlcikgaXMgaW4gY2hhcmFjdGVyIGZvcm1hdCwgaSBoYXZlIHRvIGNvbnZlcnQgaXQgdG8gYSBudW1lcmljIGZvcm1hdCBieSBydW5uaW5nIHRoZSBjb2RlIGJlbG93Lg0KDQpgYGB7cn0NCm1vbnRoX2FzX251bSA8LSBhc19tb250aCAlPiUgDQogIG11dGF0ZShtb250aF9udW0gPSBhcy5udW1lcmljKGFzX21vbnRoJG1vbnRoKSkNCg0Kc3RyKG1vbnRoX2FzX251bSkNCmBgYA0KSSB3aWxsIGNvbnZlcnQgdGhlIG51bWVyaWMgbW9udGggdG8gbW9udGggbmFtZXMgYnkgcnVubmluZyB0aGUgY29kZSBiZWxvdy4NCmBgYHtyfQ0KbW9udGhfbGV0dGVyIDwtIG1vbnRoX2FzX251bSAlPiUgDQogIG11dGF0ZShtb250aF9sID0gbW9udGgubmFtZVttb250aF9hc19udW0kbW9udGhfbnVtXSkNCm1vbnRoX2xldHRlcg0KYGBgDQoNCmBgYHtyfQ0KbW9udGhfb2ZfcmlkZTwtIG1vbnRoX2xldHRlciAlPiUgDQogIGdyb3VwX2J5KG1vbnRoX2wsIHVzZXJfc3RhdHVzKSAlPiUgDQogIHN1bW1hcml6ZShudW1iZXIgPSB0YWJsZShtb250aF9sKSkNCg0KbW9udGhfb2ZfcmlkZQ0KYGBgDQpJIHdpbGwgZXhwb3J0IHRoZSBhYm92ZSBvdXRjb21lIHRvIGEgY3N2IGZpbGUgZm9yIGZ1cnRoZXIgYW5hbHlzaXMgaW4gU1FMIGluIG9yZGVyIHRvIGFnZ3JlZ2F0ZSB0aGUgdXNlciBjb3VudHMgd2l0aCB0aGVpciBjb3JyZXNwb25kaW5nIG1vbnRocy4NCmBgYHtyfQ0Kd3JpdGUuY3N2KG1vbnRoX29mX3JpZGUsICJtb250aF9vZl9yaWRlLmNzdiIpDQpgYGANCg0KSSB3aWxsIGV4dHJhY3QgdGhlIGRhdGEgZm9yIHRoZSB0d28gZGlmZmVyZW50IHVzZXIgdHlwZXMsIGNhc3VhbCBhbmQgbWVtYmVyLCB1c2luZyB0aGUgc3FsIGNvbW1hbmQgYmVsb3cuDQpgYGB7c3FsIGNvbm5lY3Rpb249Y29ufQ0KU0VMRUNUICogRlJPTSBtb250aF9vZl9yaWRlIFdIRVJFIHVzZXJfc3RhdHVzID0gIm1lbWJlciINCg0KU0VMRUNUICogRlJPTSBtb250aF9vZl9yaWRlIFdIRVJFIHVzZXJfc3RhdHVzID0gImNhc3VhbCINCmBgYA0KDQpOZXh0LCBpIHdpbGwgaW1wb3J0IGFuZCBzb3J0IHRoZSByZXN1bHRzIG9mIHRoZSBhYm92ZSBjb21tYW5kcyBpbiBhIGRlc2NlbmRpbmcgb3JkZXIuDQoNCmBgYHtyfQ0KbWVtYmVyX21vbnRoIDwtcmVhZC5jc3YoIkM6XFxVc2Vyc1xcRURFSCBFTUVLQSBOV0VLRVxcRGVza3RvcFxcbWVtYmVyX21vbnRoX29mX3JpZGUuY3N2IikNCmBgYA0KYGBge3J9DQpjYXN1YWxfbW9udGggPC1yZWFkLmNzdigiQzpcXFVzZXJzXFxFREVIIEVNRUtBIE5XRUtFXFxEZXNrdG9wXFxjYXN1YWxfbW9udGhfb2ZfcmlkZS5jc3YiKQ0KYGBgDQpgYGB7cn0NCg0KDQpzb3J0X21lbWJlcl9tb250aCA8LSBtZW1iZXJfbW9udGggJT4lIA0KICBhcnJhbmdlKG51bWJlcikNCg0Kc29ydF9tZW1iZXJfbW9udGgNCmBgYA0KDQpgYGB7cn0NCnNvcnRfY2FzdWFsX21vbnRoIDwtIGNhc3VhbF9tb250aCAlPiUgDQogIGFycmFuZ2UobnVtYmVyKQ0KDQpzb3J0X2Nhc3VhbF9tb250aA0KYGBgDQoNCkkgd2lsbCBmdXJ0aGVyIHByZXNlbnQgdGhlIGFib3ZlIHJlc3VsdHMgZ3JhcGhpY2FsbHkgZm9yIGNsZWFyZXIgcGljdHVyZS4NCg0KYGBge3J9DQpnZ3Bsb3Qoc29ydF9tZW1iZXJfbW9udGgsIGFlcyh4PW1vbnRoX2wsIHk9bnVtYmVyLCBjb2xvciA9bW9udGhfbCwgZmlsbCA9IG1vbnRoX2wpKSArIA0KICBsYWJzKHRpdGxlID0gJ051bWJlciBvZiBtZW1iZXIgUmlkZXMgdnMgTW9udGhzIG9mIHRoZSB5ZWFyJywgDQogICAgICAgY2FwdGlvbiA9ICdEYXRhIGFuYWx5emVkIGJ5IEVkZWggRW1la2EnKSsgDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChzb3J0X2Nhc3VhbF9tb250aCwgYWVzKHg9bW9udGhfbCwgeT1udW1iZXIsIGNvbG9yID1tb250aF9sLCBmaWxsID0gbW9udGhfbCkpICsgDQogIGxhYnModGl0bGUgPSAnTnVtYmVyIG9mIGNhc3VhbCBSaWRlcyB2cyBNb250aHMgb2YgdGhlIHllYXInLCANCiAgICAgICBjYXB0aW9uID0gJ0RhdGEgYW5hbHl6ZWQgYnkgRWRlaCBFbWVrYScpKyANCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKQ0KYGBgDQoNCkZyb20gdGhlIGFib3ZlIGdyYXBocywgaXQgaXMgZXZpZGVudCB0aGF0IGNhc3VhbCByaWRlcnMgYXJlIG1vc3RseSBhY3RpdmUgaW4gdGhlIHN1bW1lciBtb250aHMgb2YgSnVuZSwgSnVseSBhbmQgQXVndXN0Lg0KDQojIyMgQUNUDQoNCkhlcmUgYXJlIG15IHRocmVlIG9ic2VydmF0aW9ucyBhbmQgcmVjb21tZW5kYXRpb25zIGluIG9yZGVyIHRvIGNvbnZlcnQgY2FzdWFsIHJpZGVycyB0byBhbm51YWwgcmlkZXJzLiAgDQoNCioqT2JzZXJ2YXRpb25zKiogIA0KMS4gQ2FzdWFsIHVzZXJzIGFyZSBtb3N0bHkgYWN0aXZlIG9uIHdlZWtlbmRzIChGcmlkYXlzLCBTYXR1cmRheXMgYW5kIFN1bmRheXMpYXMgYWdhaW5zdCBhbm51YWwvbWVtYmVyIHVzZXJzIHRoYXQgYXJlIGFsbW9zdCBldmVubHkgYWN0aXZlIGFsbCB0aHJvdWdoIHRoZSB3ZWVrLiAgDQoyLiBvbiB0aGUgYXZlcmFnZSwgQ2FzdWFsIHVzZXJzIHRlbmQgdG8gcmlkZSBmb3IgYWJvdXQgMjUgbWludXRlcyBsb25nZXIgdGhhbiB0aGUgbWVtYmVyL2FubnVhbCB1c2Vycy4gIA0KMy4gQ2FzdWFsIHVzZXJzIGFyZSBtb3N0bHkgYWN0aXZlIGR1cmluZyB0aGUgc3VtbWVyIG1vbnRocyAoSnVuZSwgSnVseSwgQXVndXN0KSB3aGlsZSBhbm51YWwvbWVtYmVyIHVzZXJzIGFyZSBhY3RpdmUgaW4gYWxsIHRoZSBtb250aHMgYmFyIHdpbnRlciBtb250aHMuICANCg0KKipSZWNvbW1lbmRhdGlvbnMqKiAgDQoxLiBPZmZlciBtZW1iZXJzaGlwIGV4Y2x1c2l2ZSBpbmNlbnRpdmVzL3BhY2thZ2VzIGZvciB3ZWVrZW5kIHJpZGVzLiAgDQoyLiBPZmZlciBkaXNjb3VudGVkIHNwZWNpYWwgc3VtbWVyIHBhY2thZ2VzIHRvIG1lbWJlcnMuICANCjMuIE9mZmVyIHNwZWNpYWwgZGlzY291bnRzIGZvciByaWRlcyB0aGF0IGxhc3QgbG9uZ2VyIHRoYW4gMTUgbWludXRlcy4gIA0KDQoNCg0KRWRlaCBFbWVrYSBOLiAgIA0KMjAyMS0wOC0yNg0KICANCiAgICANCg0KDQoNCg0KDQoNCg==