Background

Scenario

You are a junior data analyst working on the marketing analyst team at Cyclistic, a bike-share company in Chicago. The director of marketing believes the company’s future success depends on maximizing the number of annual memberships. Therefore, your team wants to understand how casual riders and annual members use Cyclistic bikes differently. From these insights, your team will design a new marketing strategy to convert casual riders into annual members. But first, Cyclistic executives must approve your recommendations, so they must be backed up with compelling data insights and professional data visualizations.

About the company

In 2016, Cyclistic launched a successful bike-share offering. Since then, the program has grown to a fleet of 5,824 bicycles that are geotracked and locked into a network of 692 stations across Chicago. The bikes can be unlocked from one station and returned to any other station in the system anytime. Until now, Cyclistic’s marketing strategy relied on building general awareness and appealing to broad consumer segments. One approach that helped make these things possible was the flexibility of its pricing plans: single-ride passes, full-day passes, and annual memberships. Customers who purchase single-ride or full-day passes are referred to as casual riders. Customers who purchase annual memberships are Cyclistic members.

Ask

Business Task In order to maximize the number of annual membership, I, data analyst, will find trend and patterns among casual riders and membership riders, and identify potential riders who can get benefit from annual membership. I do not need to raise awareness of annual membership among casual riders as they are already aware of the program.

Stakeholders

The director of marketing The marketing analysis team *Cyclistic’s Executive team

Stakeholder’s expectation Design marketing strategies aimed at converting casual riders into annual members. In order to do that, however, the marketing analyst team needs to better understand how annual members and casual riders differ, why casual riders would buy a membership, and how digital media could affect their marketing tactics. The marketing team is interested in analyzing the Cyclistic historical bike trip data to identify trends.

Prepare

DATA TYPE check

1.ROOOC Check 2.Sampling bias 3.Observer bias 4.Interpretation bias 5.Confirmation bias

About the data set:

Since Cyclistic is a fictional company, I will use Divvy’s, a bike-share program based in Chicago, data from May 2020 – April 2021 to complete this case study. To download the data, please use this link. This data was made public by Motivate International Inc, under this license. Due to data privacy issues, personal information has been removed or encrypted.

Install relevant packages

install.packages("tidyverse")
install.packages("dplyr")
install.packages("lubridate")
install.packages("geosphere")

Library

library(tidyverse)
── Attaching core tidyverse packages ─────────────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.1     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.2     
── Conflicts ───────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(ggplot2)
library(dplyr)
library(tidyr)
library(lubridate)

Trip Data

tripdata_202401 <- read.csv ("202401-divvy-tripdata.csv")
tripdata_202402 <- read.csv ("202402-divvy-tripdata.csv")
tripdata_202403 <- read.csv ("202403-divvy-tripdata.csv")
tripdata_202404 <- read.csv ("202404-divvy-tripdata.csv")
tripdata_202405 <- read.csv ("202405-divvy-tripdata.csv")
tripdata_202406 <- read.csv ("202406-divvy-tripdata.csv")

Data Check

colnames(tripdata_202401)
 [1] "ride_id"            "rideable_type"      "started_at"        
 [4] "ended_at"           "start_station_name" "start_station_id"  
 [7] "end_station_name"   "end_station_id"     "start_lat"         
[10] "start_lng"          "end_lat"            "end_lng"           
[13] "member_casual"     
colnames(tripdata_202402)
 [1] "ride_id"            "rideable_type"      "started_at"        
 [4] "ended_at"           "start_station_name" "start_station_id"  
 [7] "end_station_name"   "end_station_id"     "start_lat"         
[10] "start_lng"          "end_lat"            "end_lng"           
[13] "member_casual"     
colnames(tripdata_202403)
 [1] "ride_id"            "rideable_type"      "started_at"        
 [4] "ended_at"           "start_station_name" "start_station_id"  
 [7] "end_station_name"   "end_station_id"     "start_lat"         
[10] "start_lng"          "end_lat"            "end_lng"           
[13] "member_casual"     
colnames(tripdata_202404)
 [1] "ride_id"            "rideable_type"      "started_at"        
 [4] "ended_at"           "start_station_name" "start_station_id"  
 [7] "end_station_name"   "end_station_id"     "start_lat"         
[10] "start_lng"          "end_lat"            "end_lng"           
[13] "member_casual"     
colnames(tripdata_202405)
 [1] "ride_id"            "rideable_type"      "started_at"        
 [4] "ended_at"           "start_station_name" "start_station_id"  
 [7] "end_station_name"   "end_station_id"     "start_lat"         
[10] "start_lng"          "end_lat"            "end_lng"           
[13] "member_casual"     
colnames(tripdata_202406)
 [1] "ride_id"            "rideable_type"      "started_at"        
 [4] "ended_at"           "start_station_name" "start_station_id"  
 [7] "end_station_name"   "end_station_id"     "start_lat"         
[10] "start_lng"          "end_lat"            "end_lng"           
[13] "member_casual"     

combine data

All_trip <- bind_rows(tripdata_202401,tripdata_202402,tripdata_202403,tripdata_202404,tripdata_202405,tripdata_202406)
colnames(All_trip)
 [1] "ride_id"            "rideable_type"      "started_at"        
 [4] "ended_at"           "start_station_name" "start_station_id"  
 [7] "end_station_name"   "end_station_id"     "start_lat"         
[10] "start_lng"          "end_lat"            "end_lng"           
[13] "member_casual"     
str(All_trip)
'data.frame':   2404963 obs. of  13 variables:
 $ ride_id           : chr  "C1D650626C8C899A" "EECD38BDB25BFCB0" "F4A9CE78061F17F7" "0A0D9E15EE50B171" ...
 $ rideable_type     : chr  "electric_bike" "electric_bike" "electric_bike" "classic_bike" ...
 $ started_at        : chr  "2024-01-12 15:30:27" "2024-01-08 15:45:46" "2024-01-27 12:27:19" "2024-01-29 16:26:17" ...
 $ ended_at          : chr  "2024-01-12 15:37:59" "2024-01-08 15:52:59" "2024-01-27 12:35:19" "2024-01-29 16:56:06" ...
 $ start_station_name: chr  "Wells St & Elm St" "Wells St & Elm St" "Wells St & Elm St" "Wells St & Randolph St" ...
 $ start_station_id  : chr  "KA1504000135" "KA1504000135" "KA1504000135" "TA1305000030" ...
 $ end_station_name  : chr  "Kingsbury St & Kinzie St" "Kingsbury St & Kinzie St" "Kingsbury St & Kinzie St" "Larrabee St & Webster Ave" ...
 $ end_station_id    : chr  "KA1503000043" "KA1503000043" "KA1503000043" "13193" ...
 $ start_lat         : num  41.9 41.9 41.9 41.9 41.9 ...
 $ start_lng         : num  -87.6 -87.6 -87.6 -87.6 -87.7 ...
 $ end_lat           : num  41.9 41.9 41.9 41.9 41.9 ...
 $ end_lng           : num  -87.6 -87.6 -87.6 -87.6 -87.6 ...
 $ member_casual     : chr  "member" "member" "member" "member" ...

Process

New column

All_trip$start_date <- as.Date(All_trip$started_at) 
All_trip$start_datetime <- ymd_hms(All_trip$started_at, quiet = TRUE)
All_trip$month <- format(as.Date(All_trip$start_date), "%m")
All_trip$day <- format(as.Date(All_trip$start_date), "%d")
All_trip$year <- format(as.Date(All_trip$start_date), "%Y")
All_trip$day_of_week <- format(as.Date(All_trip$start_date), "%A")
All_trip$end_datetime <- ymd_hms(All_trip$ended_at, quiet = TRUE)
All_trip$start_Time <- format(All_trip$start_datetime, "%H:%M:%S")
All_trip$end_Time <- format(All_trip$end_datetime, "%H:%M:%S")
All_trip$duration <- (All_trip$end_datetime - All_trip$start_datetime)
All_trip$ride_distance <- distGeo(matrix(c(All_trip$start_lng, All_trip$start_lat), ncol = 2), matrix(c(All_trip$end_lng, All_trip$end_lat), ncol = 2))
colnames(All_trip)
 [1] "ride_id"            "rideable_type"      "started_at"        
 [4] "ended_at"           "start_station_name" "start_station_id"  
 [7] "end_station_name"   "end_station_id"     "start_lat"         
[10] "start_lng"          "end_lat"            "end_lng"           
[13] "member_casual"      "start_date"         "start_datetime"    
[16] "month"              "day"                "year"              
[19] "day_of_week"        "end_datetime"       "start_Time"        
[22] "end_Time"           "duration"           "ride_distance"     
str(All_trip)
'data.frame':   2404963 obs. of  24 variables:
 $ ride_id           : chr  "C1D650626C8C899A" "EECD38BDB25BFCB0" "F4A9CE78061F17F7" "0A0D9E15EE50B171" ...
 $ rideable_type     : chr  "electric_bike" "electric_bike" "electric_bike" "classic_bike" ...
 $ started_at        : chr  "2024-01-12 15:30:27" "2024-01-08 15:45:46" "2024-01-27 12:27:19" "2024-01-29 16:26:17" ...
 $ ended_at          : chr  "2024-01-12 15:37:59" "2024-01-08 15:52:59" "2024-01-27 12:35:19" "2024-01-29 16:56:06" ...
 $ start_station_name: chr  "Wells St & Elm St" "Wells St & Elm St" "Wells St & Elm St" "Wells St & Randolph St" ...
 $ start_station_id  : chr  "KA1504000135" "KA1504000135" "KA1504000135" "TA1305000030" ...
 $ end_station_name  : chr  "Kingsbury St & Kinzie St" "Kingsbury St & Kinzie St" "Kingsbury St & Kinzie St" "Larrabee St & Webster Ave" ...
 $ end_station_id    : chr  "KA1503000043" "KA1503000043" "KA1503000043" "13193" ...
 $ start_lat         : num  41.9 41.9 41.9 41.9 41.9 ...
 $ start_lng         : num  -87.6 -87.6 -87.6 -87.6 -87.7 ...
 $ end_lat           : num  41.9 41.9 41.9 41.9 41.9 ...
 $ end_lng           : num  -87.6 -87.6 -87.6 -87.6 -87.6 ...
 $ member_casual     : chr  "member" "member" "member" "member" ...
 $ start_date        : Date, format: "2024-01-12" "2024-01-08" ...
 $ start_datetime    : POSIXct, format: "2024-01-12 15:30:27" "2024-01-08 15:45:46" ...
 $ month             : chr  "01" "01" "01" "01" ...
 $ day               : chr  "12" "08" "27" "29" ...
 $ year              : chr  "2024" "2024" "2024" "2024" ...
 $ day_of_week       : chr  "Friday" "Monday" "Saturday" "Monday" ...
 $ end_datetime      : POSIXct, format: "2024-01-12 15:37:59" "2024-01-08 15:52:59" ...
 $ start_Time        : chr  "15:30:27" "15:45:46" "12:27:19" "16:26:17" ...
 $ end_Time          : chr  "15:37:59" "15:52:59" "12:35:19" "16:56:06" ...
 $ duration          : 'difftime' num  452 433 480 1789 ...
  ..- attr(*, "units")= chr "secs"
 $ ride_distance     : num  1596 1565 1566 4253 7291 ...

Calculation

All_trip$duration <- as.numeric(as.character(All_trip$duration))
is.numeric(All_trip$duration)
[1] TRUE

Remove bad data

all_trips <- All_trip[All_trip$duration != 0, ]

Check new data

dim(all_trips) 
[1] 2404467      24
View(all_trips)
summary(all_trips)
   ride_id          rideable_type       started_at          ended_at        
 Length:2404467     Length:2404467     Length:2404467     Length:2404467    
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
                                                                            
 start_station_name start_station_id   end_station_name   end_station_id    
 Length:2404467     Length:2404467     Length:2404467     Length:2404467    
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
                                                                            
   start_lat       start_lng         end_lat         end_lng       member_casual     
 Min.   :41.65   Min.   :-87.89   Min.   :41.60   Min.   :-88.12   Length:2404467    
 1st Qu.:41.88   1st Qu.:-87.66   1st Qu.:41.88   1st Qu.:-87.66   Class :character  
 Median :41.90   Median :-87.64   Median :41.90   Median :-87.64   Mode  :character  
 Mean   :41.90   Mean   :-87.65   Mean   :41.90   Mean   :-87.65                     
 3rd Qu.:41.93   3rd Qu.:-87.63   3rd Qu.:41.93   3rd Qu.:-87.63                     
 Max.   :42.07   Max.   :-87.53   Max.   :42.19   Max.   :-87.46                     
                                  NA's   :3389    NA's   :3389                       
   start_date         start_datetime                      month          
 Min.   :2024-01-01   Min.   :2024-01-01 00:00:39.00   Length:2404467    
 1st Qu.:2024-03-24   1st Qu.:2024-03-24 20:17:05.00   Class :character  
 Median :2024-05-07   Median :2024-05-07 15:08:08.00   Mode  :character  
 Mean   :2024-04-26   Mean   :2024-04-27 10:59:45.57                     
 3rd Qu.:2024-06-06   3rd Qu.:2024-06-06 13:09:25.78                     
 Max.   :2024-06-30   Max.   :2024-06-30 23:55:17.06                     
                                                                         
     day                year           day_of_week       
 Length:2404467     Length:2404467     Length:2404467    
 Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character  
                                                         
                                                         
                                                         
                                                         
  end_datetime                     start_Time          end_Time        
 Min.   :2024-01-01 00:04:20.00   Length:2404467     Length:2404467    
 1st Qu.:2024-03-24 20:31:39.00   Class :character   Class :character  
 Median :2024-05-07 15:22:15.00   Mode  :character   Mode  :character  
 Mean   :2024-04-27 11:17:32.56                                        
 3rd Qu.:2024-06-06 13:31:27.61                                        
 Max.   :2024-06-30 23:59:57.93                                        
                                                                       
    duration       ride_distance    
 Min.   :-164899   Min.   :    0.0  
 1st Qu.:    330   1st Qu.:  868.1  
 Median :    582   Median : 1559.1  
 Mean   :   1067   Mean   : 2132.1  
 3rd Qu.:   1042   3rd Qu.: 2775.5  
 Max.   :  93596   Max.   :33514.6  
                   NA's   :3389     

Remove duplicate ID

all_trips_v2 <- all_trips[!duplicated(all_trips$ride_id),]
dim(all_trips_v2) 
[1] 2404256      24
summary(all_trips_v2)
   ride_id          rideable_type       started_at          ended_at        
 Length:2404256     Length:2404256     Length:2404256     Length:2404256    
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
                                                                            
 start_station_name start_station_id   end_station_name   end_station_id    
 Length:2404256     Length:2404256     Length:2404256     Length:2404256    
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
                                                                            
   start_lat       start_lng         end_lat         end_lng       member_casual     
 Min.   :41.65   Min.   :-87.89   Min.   :41.60   Min.   :-88.12   Length:2404256    
 1st Qu.:41.88   1st Qu.:-87.66   1st Qu.:41.88   1st Qu.:-87.66   Class :character  
 Median :41.90   Median :-87.64   Median :41.90   Median :-87.64   Mode  :character  
 Mean   :41.90   Mean   :-87.65   Mean   :41.90   Mean   :-87.65                     
 3rd Qu.:41.93   3rd Qu.:-87.63   3rd Qu.:41.93   3rd Qu.:-87.63                     
 Max.   :42.07   Max.   :-87.53   Max.   :42.19   Max.   :-87.46                     
                                  NA's   :3349    NA's   :3349                       
   start_date         start_datetime                      month          
 Min.   :2024-01-01   Min.   :2024-01-01 00:00:39.00   Length:2404256    
 1st Qu.:2024-03-24   1st Qu.:2024-03-24 20:02:52.00   Class :character  
 Median :2024-05-07   Median :2024-05-07 14:46:39.00   Mode  :character  
 Mean   :2024-04-26   Mean   :2024-04-27 10:55:24.35                     
 3rd Qu.:2024-06-06   3rd Qu.:2024-06-06 13:12:13.01                     
 Max.   :2024-06-30   Max.   :2024-06-30 23:55:17.06                     
                                                                         
     day                year           day_of_week       
 Length:2404256     Length:2404256     Length:2404256    
 Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character  
                                                         
                                                         
                                                         
                                                         
  end_datetime                     start_Time          end_Time        
 Min.   :2024-01-01 00:04:20.00   Length:2404256     Length:2404256    
 1st Qu.:2024-03-24 20:20:09.00   Class :character   Class :character  
 Median :2024-05-07 15:04:06.00   Mode  :character   Mode  :character  
 Mean   :2024-04-27 11:13:09.35                                        
 3rd Qu.:2024-06-06 13:34:22.65                                        
 Max.   :2024-06-30 23:59:57.93                                        
                                                                       
    duration       ride_distance    
 Min.   :-164899   Min.   :    0.0  
 1st Qu.:    330   1st Qu.:  868.1  
 Median :    582   Median : 1559.0  
 Mean   :   1065   Mean   : 2132.0  
 3rd Qu.:   1042   3rd Qu.: 2775.3  
 Max.   :  93596   Max.   :33514.6  
                   NA's   :3349     

Analysis

all_trips_v2$day_of_week <- 
    ordered(all_trips_v2$day_of_week, levels = c('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'))

all_trips_v2 %>%
  group_by(member_casual,day_of_week) %>%
  summarise(number_of_rides=n(), .groups = "drop") %>%
  arrange(day_of_week)
summarized_data <- all_trips_v2 %>%
  group_by(member_casual, day_of_week) %>%
  summarise(number_of_rides = n(), .groups = "drop") %>%
  arrange(day_of_week)

ggplot(data = summarized_data, aes(x = day_of_week, y = number_of_rides, fill = member_casual)) + 
  geom_bar(position = "dodge", stat = "identity")

all_trips_v2 %>%
  group_by(member_casual,day_of_week) %>%
  summarise(sum(duration),.groups ="drop") %>%
  arrange(day_of_week)
summarized_data <- all_trips_v2 %>%
  group_by(member_casual,day_of_week) %>%
  summarise (total_duration=sum(duration),.groups ="drop") %>%
  arrange(day_of_week)
    ggplot(data = summarized_data, aes(x = day_of_week, y = total_duration, fill = member_casual)) + 
  geom_bar(position = "dodge", stat = "identity")

all_trips_v2 %>%
  group_by(month,member_casual) %>%
  summarise(number_of_rides=n(),.groups = "drop")%>%
  arrange(month)
all_trips_v2 %>%
  group_by(month,member_casual) %>%
  summarise(number_of_rides=n(),.groups = "drop")%>%
  arrange(month)%>%
  ggplot(aes(x=month,y=number_of_rides,fill=member_casual))+
  geom_bar(position = "dodge", stat = "identity")

all_trips_v2 %>%
  group_by(member_casual) %>%
  filter(ride_distance < 10000)%>%
  ggplot(aes(x = ride_distance, fill = member_casual)) +
  geom_histogram()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

It looks like casual users take longer bike rides compared to regular members. This suggests that casual users might use the bikes more for fun or leisure, while regular members might use them more for practical reasons, like commuting.

Share

Conclusion:

*Casual users tend to use the bikes for leisure and tourism, especially on weekends, which suggests they enjoy longer rides and recreational activities.

*Annual users primarily use the bikes for commuting or practical purposes during weekdays, indicating a more routine and functional use of the service.

*Casual users may prefer biking for social outings or exploring new places, as reflected in their longer ride distances and weekend activity.

*Annual users are more likely to incorporate biking into their daily routines, possibly for work or other regular activities, resulting in shorter and more frequent rides during the workweek.

*The difference in ride patterns highlights the diverse ways people utilize bike-sharing services, with casual users focusing on enjoyment and annual users on practicality.

LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpCYWNrZ3JvdW5kDQoNClNjZW5hcmlvDQoNCllvdSBhcmUgYSBqdW5pb3IgZGF0YSBhbmFseXN0IHdvcmtpbmcgb24gdGhlIG1hcmtldGluZyBhbmFseXN0IHRlYW0gYXQgQ3ljbGlzdGljLCBhIGJpa2Utc2hhcmUNCmNvbXBhbnkgaW4gQ2hpY2Fnby4gVGhlIGRpcmVjdG9yIG9mIG1hcmtldGluZyBiZWxpZXZlcyB0aGUgY29tcGFueeKAmXMgZnV0dXJlIHN1Y2Nlc3MNCmRlcGVuZHMgb24gbWF4aW1pemluZyB0aGUgbnVtYmVyIG9mIGFubnVhbCBtZW1iZXJzaGlwcy4gVGhlcmVmb3JlLCB5b3VyIHRlYW0gd2FudHMgdG8NCnVuZGVyc3RhbmQgaG93IGNhc3VhbCByaWRlcnMgYW5kIGFubnVhbCBtZW1iZXJzIHVzZSBDeWNsaXN0aWMgYmlrZXMgZGlmZmVyZW50bHkuIEZyb20gdGhlc2UNCmluc2lnaHRzLCB5b3VyIHRlYW0gd2lsbCBkZXNpZ24gYSBuZXcgbWFya2V0aW5nIHN0cmF0ZWd5IHRvIGNvbnZlcnQgY2FzdWFsIHJpZGVycyBpbnRvIGFubnVhbA0KbWVtYmVycy4gQnV0IGZpcnN0LCBDeWNsaXN0aWMgZXhlY3V0aXZlcyBtdXN0IGFwcHJvdmUgeW91ciByZWNvbW1lbmRhdGlvbnMsIHNvIHRoZXkgbXVzdCBiZQ0KYmFja2VkIHVwIHdpdGggY29tcGVsbGluZyBkYXRhIGluc2lnaHRzIGFuZCBwcm9mZXNzaW9uYWwgZGF0YSB2aXN1YWxpemF0aW9ucy4NCg0KQWJvdXQgdGhlIGNvbXBhbnkNCg0KSW4gMjAxNiwgQ3ljbGlzdGljIGxhdW5jaGVkIGEgc3VjY2Vzc2Z1bCBiaWtlLXNoYXJlIG9mZmVyaW5nLiBTaW5jZSB0aGVuLCB0aGUgcHJvZ3JhbSBoYXMgZ3Jvd24NCnRvIGEgZmxlZXQgb2YgNSw4MjQgYmljeWNsZXMgdGhhdCBhcmUgZ2VvdHJhY2tlZCBhbmQgbG9ja2VkIGludG8gYSBuZXR3b3JrIG9mIDY5MiBzdGF0aW9ucw0KYWNyb3NzIENoaWNhZ28uIFRoZSBiaWtlcyBjYW4gYmUgdW5sb2NrZWQgZnJvbSBvbmUgc3RhdGlvbiBhbmQgcmV0dXJuZWQgdG8gYW55IG90aGVyIHN0YXRpb24NCmluIHRoZSBzeXN0ZW0gYW55dGltZS4NClVudGlsIG5vdywgQ3ljbGlzdGlj4oCZcyBtYXJrZXRpbmcgc3RyYXRlZ3kgcmVsaWVkIG9uIGJ1aWxkaW5nIGdlbmVyYWwgYXdhcmVuZXNzIGFuZCBhcHBlYWxpbmcgdG8NCmJyb2FkIGNvbnN1bWVyIHNlZ21lbnRzLiBPbmUgYXBwcm9hY2ggdGhhdCBoZWxwZWQgbWFrZSB0aGVzZSB0aGluZ3MgcG9zc2libGUgd2FzIHRoZQ0KZmxleGliaWxpdHkgb2YgaXRzIHByaWNpbmcgcGxhbnM6IHNpbmdsZS1yaWRlIHBhc3NlcywgZnVsbC1kYXkgcGFzc2VzLCBhbmQgYW5udWFsIG1lbWJlcnNoaXBzLg0KQ3VzdG9tZXJzIHdobyBwdXJjaGFzZSBzaW5nbGUtcmlkZSBvciBmdWxsLWRheSBwYXNzZXMgYXJlIHJlZmVycmVkIHRvIGFzIGNhc3VhbCByaWRlcnMuDQpDdXN0b21lcnMgd2hvIHB1cmNoYXNlIGFubnVhbCBtZW1iZXJzaGlwcyBhcmUgQ3ljbGlzdGljIG1lbWJlcnMuDQoNCkFzaw0KDQpCdXNpbmVzcyBUYXNrIEluIG9yZGVyIHRvIG1heGltaXplIHRoZSBudW1iZXIgb2YgYW5udWFsIG1lbWJlcnNoaXAsIEksIGRhdGEgYW5hbHlzdCwgd2lsbCBmaW5kIHRyZW5kIGFuZCBwYXR0ZXJucyBhbW9uZyBjYXN1YWwgcmlkZXJzIGFuZCBtZW1iZXJzaGlwIHJpZGVycywgYW5kIGlkZW50aWZ5IHBvdGVudGlhbCByaWRlcnMgd2hvIGNhbiBnZXQgYmVuZWZpdCBmcm9tIGFubnVhbCBtZW1iZXJzaGlwLiBJIGRvIG5vdCBuZWVkIHRvIHJhaXNlIGF3YXJlbmVzcyBvZiBhbm51YWwgbWVtYmVyc2hpcCBhbW9uZyBjYXN1YWwgcmlkZXJzIGFzIHRoZXkgYXJlIGFscmVhZHkgYXdhcmUgb2YgdGhlIHByb2dyYW0uDQoNClN0YWtlaG9sZGVycw0KDQoqVGhlIGRpcmVjdG9yIG9mIG1hcmtldGluZw0KKlRoZSBtYXJrZXRpbmcgYW5hbHlzaXMgdGVhbQ0KKkN5Y2xpc3RpYydzIEV4ZWN1dGl2ZSB0ZWFtDQoNClN0YWtlaG9sZGVyJ3MgZXhwZWN0YXRpb24gRGVzaWduIG1hcmtldGluZyBzdHJhdGVnaWVzIGFpbWVkIGF0IGNvbnZlcnRpbmcgY2FzdWFsIHJpZGVycyBpbnRvIGFubnVhbCBtZW1iZXJzLiBJbiBvcmRlciB0byBkbyB0aGF0LCBob3dldmVyLCB0aGUgbWFya2V0aW5nIGFuYWx5c3QgdGVhbSBuZWVkcyB0byBiZXR0ZXIgdW5kZXJzdGFuZCBob3cgYW5udWFsIG1lbWJlcnMgYW5kIGNhc3VhbCByaWRlcnMgZGlmZmVyLCB3aHkgY2FzdWFsIHJpZGVycyB3b3VsZCBidXkgYSBtZW1iZXJzaGlwLCBhbmQgaG93IGRpZ2l0YWwgbWVkaWEgY291bGQgYWZmZWN0IHRoZWlyIG1hcmtldGluZyB0YWN0aWNzLiBUaGUgbWFya2V0aW5nIHRlYW0gaXMgaW50ZXJlc3RlZCBpbiBhbmFseXppbmcgdGhlIEN5Y2xpc3RpYyBoaXN0b3JpY2FsIGJpa2UgdHJpcCBkYXRhIHRvIGlkZW50aWZ5IHRyZW5kcy4NCg0KDQpQcmVwYXJlDQoNCg0KREFUQSBUWVBFIGNoZWNrDQoNCjEuUk9PT0MgQ2hlY2sNCjIuU2FtcGxpbmcgYmlhcw0KMy5PYnNlcnZlciBiaWFzDQo0LkludGVycHJldGF0aW9uIGJpYXMNCjUuQ29uZmlybWF0aW9uIGJpYXMNCg0KQWJvdXQgdGhlIGRhdGEgc2V0Og0KDQpTaW5jZSBDeWNsaXN0aWMgaXMgYSBmaWN0aW9uYWwgY29tcGFueSwgSSB3aWxsIHVzZSBEaXZ2eeKAmXMsIGEgYmlrZS1zaGFyZSBwcm9ncmFtIGJhc2VkIGluIENoaWNhZ28sIGRhdGEgZnJvbSBNYXkgMjAyMCDigJMgQXByaWwgMjAyMSB0byBjb21wbGV0ZSB0aGlzIGNhc2Ugc3R1ZHkuIFRvIGRvd25sb2FkIHRoZSBkYXRhLCBwbGVhc2UgdXNlIHRoaXMgbGluay4gVGhpcyBkYXRhIHdhcyBtYWRlIHB1YmxpYyBieSBNb3RpdmF0ZSBJbnRlcm5hdGlvbmFsIEluYywgdW5kZXIgdGhpcyBsaWNlbnNlLiBEdWUgdG8gZGF0YSBwcml2YWN5IGlzc3VlcywgcGVyc29uYWwgaW5mb3JtYXRpb24gaGFzIGJlZW4gcmVtb3ZlZCBvciBlbmNyeXB0ZWQuDQoNCg0KSW5zdGFsbCByZWxldmFudCBwYWNrYWdlcw0KYGBge3IgaW5zdGFsbCBwYWNrZWdlc30NCmluc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIpDQppbnN0YWxsLnBhY2thZ2VzKCJkcGx5ciIpDQppbnN0YWxsLnBhY2thZ2VzKCJsdWJyaWRhdGUiKQ0KaW5zdGFsbC5wYWNrYWdlcygiZ2Vvc3BoZXJlIikNCmBgYA0KDQpMaWJyYXJ5DQpgYGB7ciBsaWJyYXJ5fQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeShnZW9zcGhlcmUpDQpgYGANCg0KVHJpcCBEYXRhDQpgYGB7ciBpbnB1dCBkYXRhfQ0KdHJpcGRhdGFfMjAyNDAxIDwtIHJlYWQuY3N2ICgiMjAyNDAxLWRpdnZ5LXRyaXBkYXRhLmNzdiIpDQp0cmlwZGF0YV8yMDI0MDIgPC0gcmVhZC5jc3YgKCIyMDI0MDItZGl2dnktdHJpcGRhdGEuY3N2IikNCnRyaXBkYXRhXzIwMjQwMyA8LSByZWFkLmNzdiAoIjIwMjQwMy1kaXZ2eS10cmlwZGF0YS5jc3YiKQ0KdHJpcGRhdGFfMjAyNDA0IDwtIHJlYWQuY3N2ICgiMjAyNDA0LWRpdnZ5LXRyaXBkYXRhLmNzdiIpDQp0cmlwZGF0YV8yMDI0MDUgPC0gcmVhZC5jc3YgKCIyMDI0MDUtZGl2dnktdHJpcGRhdGEuY3N2IikNCnRyaXBkYXRhXzIwMjQwNiA8LSByZWFkLmNzdiAoIjIwMjQwNi1kaXZ2eS10cmlwZGF0YS5jc3YiKQ0KYGBgDQoNCkRhdGEgQ2hlY2sNCmBgYHtyIGRhdGEgY2hlY2t9DQpjb2xuYW1lcyh0cmlwZGF0YV8yMDI0MDEpDQpjb2xuYW1lcyh0cmlwZGF0YV8yMDI0MDIpDQpjb2xuYW1lcyh0cmlwZGF0YV8yMDI0MDMpDQpjb2xuYW1lcyh0cmlwZGF0YV8yMDI0MDQpDQpjb2xuYW1lcyh0cmlwZGF0YV8yMDI0MDUpDQpjb2xuYW1lcyh0cmlwZGF0YV8yMDI0MDYpDQpgYGANCg0KY29tYmluZSBkYXRhDQpgYGB7ciBjb21iaW5lIGRhdGF9DQpBbGxfdHJpcCA8LSBiaW5kX3Jvd3ModHJpcGRhdGFfMjAyNDAxLHRyaXBkYXRhXzIwMjQwMix0cmlwZGF0YV8yMDI0MDMsdHJpcGRhdGFfMjAyNDA0LHRyaXBkYXRhXzIwMjQwNSx0cmlwZGF0YV8yMDI0MDYpDQpgYGANCg0KYGBge3IgY2hlY2sgYWxsIHRyaXAgZGF0YX0NCmNvbG5hbWVzKEFsbF90cmlwKQ0Kc3RyKEFsbF90cmlwKQ0KYGBgDQpQcm9jZXNzDQoNCk5ldyBjb2x1bW4NCmBgYHtyIEFkZCBjb2x1bW5zfQ0KQWxsX3RyaXAkc3RhcnRfZGF0ZSA8LSBhcy5EYXRlKEFsbF90cmlwJHN0YXJ0ZWRfYXQpIA0KQWxsX3RyaXAkc3RhcnRfZGF0ZXRpbWUgPC0geW1kX2htcyhBbGxfdHJpcCRzdGFydGVkX2F0LCBxdWlldCA9IFRSVUUpDQpBbGxfdHJpcCRtb250aCA8LSBmb3JtYXQoYXMuRGF0ZShBbGxfdHJpcCRzdGFydF9kYXRlKSwgIiVtIikNCkFsbF90cmlwJGRheSA8LSBmb3JtYXQoYXMuRGF0ZShBbGxfdHJpcCRzdGFydF9kYXRlKSwgIiVkIikNCkFsbF90cmlwJHllYXIgPC0gZm9ybWF0KGFzLkRhdGUoQWxsX3RyaXAkc3RhcnRfZGF0ZSksICIlWSIpDQpBbGxfdHJpcCRkYXlfb2Zfd2VlayA8LSBmb3JtYXQoYXMuRGF0ZShBbGxfdHJpcCRzdGFydF9kYXRlKSwgIiVBIikNCkFsbF90cmlwJGVuZF9kYXRldGltZSA8LSB5bWRfaG1zKEFsbF90cmlwJGVuZGVkX2F0LCBxdWlldCA9IFRSVUUpDQpBbGxfdHJpcCRzdGFydF9UaW1lIDwtIGZvcm1hdChBbGxfdHJpcCRzdGFydF9kYXRldGltZSwgIiVIOiVNOiVTIikNCkFsbF90cmlwJGVuZF9UaW1lIDwtIGZvcm1hdChBbGxfdHJpcCRlbmRfZGF0ZXRpbWUsICIlSDolTTolUyIpDQpBbGxfdHJpcCRkdXJhdGlvbiA8LSAoQWxsX3RyaXAkZW5kX2RhdGV0aW1lIC0gQWxsX3RyaXAkc3RhcnRfZGF0ZXRpbWUpDQpBbGxfdHJpcCRyaWRlX2Rpc3RhbmNlIDwtIGRpc3RHZW8obWF0cml4KGMoQWxsX3RyaXAkc3RhcnRfbG5nLCBBbGxfdHJpcCRzdGFydF9sYXQpLCBuY29sID0gMiksIG1hdHJpeChjKEFsbF90cmlwJGVuZF9sbmcsIEFsbF90cmlwJGVuZF9sYXQpLCBuY29sID0gMikpDQoNCmBgYA0KDQoNCmBgYHtyIGNoZWNrIGFsbCB0cmlwIGRhdGEtLTJ9DQpjb2xuYW1lcyhBbGxfdHJpcCkNCnN0cihBbGxfdHJpcCkNCmBgYA0KQ2FsY3VsYXRpb24NCg0KYGBge3J9DQpBbGxfdHJpcCRkdXJhdGlvbiA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihBbGxfdHJpcCRkdXJhdGlvbikpDQppcy5udW1lcmljKEFsbF90cmlwJGR1cmF0aW9uKQ0KYGBgDQpSZW1vdmUgYmFkIGRhdGENCmBgYHtyfQ0KYWxsX3RyaXBzIDwtIEFsbF90cmlwW0FsbF90cmlwJGR1cmF0aW9uICE9IDAsIF0NCmBgYA0KDQpDaGVjayBuZXcgZGF0YQ0KYGBge3J9DQpkaW0oYWxsX3RyaXBzKSANClZpZXcoYWxsX3RyaXBzKQ0Kc3VtbWFyeShhbGxfdHJpcHMpDQpgYGANClJlbW92ZSBkdXBsaWNhdGUgSUQNCmBgYHtyfQ0KYWxsX3RyaXBzX3YyIDwtIGFsbF90cmlwc1shZHVwbGljYXRlZChhbGxfdHJpcHMkcmlkZV9pZCksXQ0KZGltKGFsbF90cmlwc192MikgDQpzdW1tYXJ5KGFsbF90cmlwc192MikNCmBgYA0KQW5hbHlzaXMNCmBgYHtyfQ0KYWxsX3RyaXBzX3YyJGRheV9vZl93ZWVrIDwtIA0KICAgIG9yZGVyZWQoYWxsX3RyaXBzX3YyJGRheV9vZl93ZWVrLCBsZXZlbHMgPSBjKCdNb25kYXknLCAnVHVlc2RheScsICdXZWRuZXNkYXknLCAnVGh1cnNkYXknLCAnRnJpZGF5JywgJ1NhdHVyZGF5JywgJ1N1bmRheScpKQ0KDQphbGxfdHJpcHNfdjIgJT4lDQogIGdyb3VwX2J5KG1lbWJlcl9jYXN1YWwsZGF5X29mX3dlZWspICU+JQ0KICBzdW1tYXJpc2UobnVtYmVyX29mX3JpZGVzPW4oKSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lDQogIGFycmFuZ2UoZGF5X29mX3dlZWspDQpgYGANCg0KYGBge3J9DQpzdW1tYXJpemVkX2RhdGEgPC0gYWxsX3RyaXBzX3YyICU+JQ0KICBncm91cF9ieShtZW1iZXJfY2FzdWFsLCBkYXlfb2Zfd2VlaykgJT4lDQogIHN1bW1hcmlzZShudW1iZXJfb2ZfcmlkZXMgPSBuKCksIC5ncm91cHMgPSAiZHJvcCIpICU+JQ0KICBhcnJhbmdlKGRheV9vZl93ZWVrKQ0KDQpnZ3Bsb3QoZGF0YSA9IHN1bW1hcml6ZWRfZGF0YSwgYWVzKHggPSBkYXlfb2Zfd2VlaywgeSA9IG51bWJlcl9vZl9yaWRlcywgZmlsbCA9IG1lbWJlcl9jYXN1YWwpKSArIA0KICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJkb2RnZSIsIHN0YXQgPSAiaWRlbnRpdHkiKQ0KYGBgDQoNCmBgYHtyfQ0KYWxsX3RyaXBzX3YyICU+JQ0KICBncm91cF9ieShtZW1iZXJfY2FzdWFsLGRheV9vZl93ZWVrKSAlPiUNCiAgc3VtbWFyaXNlKHN1bShkdXJhdGlvbiksLmdyb3VwcyA9ImRyb3AiKSAlPiUNCiAgYXJyYW5nZShkYXlfb2Zfd2VlaykNCmBgYA0KYGBge3J9DQpzdW1tYXJpemVkX2RhdGEgPC0gYWxsX3RyaXBzX3YyICU+JQ0KICBncm91cF9ieShtZW1iZXJfY2FzdWFsLGRheV9vZl93ZWVrKSAlPiUNCiAgc3VtbWFyaXNlICh0b3RhbF9kdXJhdGlvbj1zdW0oZHVyYXRpb24pLC5ncm91cHMgPSJkcm9wIikgJT4lDQogIGFycmFuZ2UoZGF5X29mX3dlZWspDQogICAgZ2dwbG90KGRhdGEgPSBzdW1tYXJpemVkX2RhdGEsIGFlcyh4ID0gZGF5X29mX3dlZWssIHkgPSB0b3RhbF9kdXJhdGlvbiwgZmlsbCA9IG1lbWJlcl9jYXN1YWwpKSArIA0KICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJkb2RnZSIsIHN0YXQgPSAiaWRlbnRpdHkiKQ0KYGBgDQpgYGB7cn0NCmFsbF90cmlwc192MiAlPiUNCiAgZ3JvdXBfYnkobW9udGgsbWVtYmVyX2Nhc3VhbCkgJT4lDQogIHN1bW1hcmlzZShudW1iZXJfb2ZfcmlkZXM9bigpLC5ncm91cHMgPSAiZHJvcCIpJT4lDQogIGFycmFuZ2UobW9udGgpDQpgYGANCg0KYGBge3J9DQphbGxfdHJpcHNfdjIgJT4lDQogIGdyb3VwX2J5KG1vbnRoLG1lbWJlcl9jYXN1YWwpICU+JQ0KICBzdW1tYXJpc2UobnVtYmVyX29mX3JpZGVzPW4oKSwuZ3JvdXBzID0gImRyb3AiKSU+JQ0KICBhcnJhbmdlKG1vbnRoKSU+JQ0KICBnZ3Bsb3QoYWVzKHg9bW9udGgseT1udW1iZXJfb2ZfcmlkZXMsZmlsbD1tZW1iZXJfY2FzdWFsKSkrDQogIGdlb21fYmFyKHBvc2l0aW9uID0gImRvZGdlIiwgc3RhdCA9ICJpZGVudGl0eSIpDQpgYGANCmBgYHtyfQ0KYWxsX3RyaXBzX3YyICU+JQ0KICBncm91cF9ieShtZW1iZXJfY2FzdWFsKSAlPiUNCiAgZmlsdGVyKHJpZGVfZGlzdGFuY2UgPCAxMDAwMCklPiUNCiAgZ2dwbG90KGFlcyh4ID0gcmlkZV9kaXN0YW5jZSwgZmlsbCA9IG1lbWJlcl9jYXN1YWwpKSArDQogIGdlb21faGlzdG9ncmFtKCkNCmBgYA0KDQpJdCBsb29rcyBsaWtlIGNhc3VhbCB1c2VycyB0YWtlIGxvbmdlciBiaWtlIHJpZGVzIGNvbXBhcmVkIHRvIHJlZ3VsYXIgbWVtYmVycy4gVGhpcyBzdWdnZXN0cyB0aGF0IGNhc3VhbCB1c2VycyBtaWdodCB1c2UgdGhlIGJpa2VzIG1vcmUgZm9yIGZ1biBvciBsZWlzdXJlLCB3aGlsZSByZWd1bGFyIG1lbWJlcnMgbWlnaHQgdXNlIHRoZW0gbW9yZSBmb3IgcHJhY3RpY2FsIHJlYXNvbnMsIGxpa2UgY29tbXV0aW5nLg0KDQpTaGFyZQ0KDQpDb25jbHVzaW9uOg0KDQoqQ2FzdWFsIHVzZXJzIHRlbmQgdG8gdXNlIHRoZSBiaWtlcyBmb3IgbGVpc3VyZSBhbmQgdG91cmlzbSwgZXNwZWNpYWxseSBvbiB3ZWVrZW5kcywgd2hpY2ggc3VnZ2VzdHMgdGhleSBlbmpveSBsb25nZXIgcmlkZXMgYW5kIHJlY3JlYXRpb25hbCBhY3Rpdml0aWVzLg0KDQoqQW5udWFsIHVzZXJzIHByaW1hcmlseSB1c2UgdGhlIGJpa2VzIGZvciBjb21tdXRpbmcgb3IgcHJhY3RpY2FsIHB1cnBvc2VzIGR1cmluZyB3ZWVrZGF5cywgaW5kaWNhdGluZyBhIG1vcmUgcm91dGluZSBhbmQgZnVuY3Rpb25hbCB1c2Ugb2YgdGhlIHNlcnZpY2UuDQoNCipDYXN1YWwgdXNlcnMgbWF5IHByZWZlciBiaWtpbmcgZm9yIHNvY2lhbCBvdXRpbmdzIG9yIGV4cGxvcmluZyBuZXcgcGxhY2VzLCBhcyByZWZsZWN0ZWQgaW4gdGhlaXIgbG9uZ2VyIHJpZGUgZGlzdGFuY2VzIGFuZCB3ZWVrZW5kIGFjdGl2aXR5Lg0KDQoqQW5udWFsIHVzZXJzIGFyZSBtb3JlIGxpa2VseSB0byBpbmNvcnBvcmF0ZSBiaWtpbmcgaW50byB0aGVpciBkYWlseSByb3V0aW5lcywgcG9zc2libHkgZm9yIHdvcmsgb3Igb3RoZXIgcmVndWxhciBhY3Rpdml0aWVzLCByZXN1bHRpbmcgaW4gc2hvcnRlciBhbmQgbW9yZSBmcmVxdWVudCByaWRlcyBkdXJpbmcgdGhlIHdvcmt3ZWVrLg0KDQoqVGhlIGRpZmZlcmVuY2UgaW4gcmlkZSBwYXR0ZXJucyBoaWdobGlnaHRzIHRoZSBkaXZlcnNlIHdheXMgcGVvcGxlIHV0aWxpemUgYmlrZS1zaGFyaW5nIHNlcnZpY2VzLCB3aXRoIGNhc3VhbCB1c2VycyBmb2N1c2luZyBvbiBlbmpveW1lbnQgYW5kIGFubnVhbCB1c2VycyBvbiBwcmFjdGljYWxpdHkuDQo=