Skip to content

Instantly share code, notes, and snippets.

@Heidi75
Created April 14, 2020 16:49
Show Gist options
  • Save Heidi75/fb72dde25022213fc5ae65d25fdf8d9f to your computer and use it in GitHub Desktop.
Save Heidi75/fb72dde25022213fc5ae65d25fdf8d9f to your computer and use it in GitHub Desktop.
Created on Skills Network Labs
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# SEGMENTING AND CLUSTERING NEIGHBORHOODS IN TORONTO"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In this assignment, you will be required to explore, segment, and cluster the neighborhoods in the city of Toronto. However, unlike New York, the neighborhood data is not readily available on the internet. What is interesting about the field of data science is that each project can be challenging in its unique way, so you need to learn to be agile and refine the skill to learn new libraries and tools quickly depending on the project.\n",
"\n",
"For the Toronto neighborhood data, a Wikipedia page exists that has all the information we need to explore and cluster the neighborhoods in Toronto. You will be required to scrape the Wikipedia page and wrangle the data, clean it, and then read it into a pandas dataframe so that it is in a structured format like the New York dataset.\n",
"\n",
"Once the data is in a structured format, you can replicate the analysis that we did to the New York City dataset to explore and cluster the neighborhoods in the city of Toronto.\n",
"\n",
"Your submission will be a link to your Jupyter Notebook on your Github repository.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Requirement already satisfied: lxml in /home/jupyterlab/conda/envs/python/lib/python3.6/site-packages (4.5.0)\n",
"Requirement already satisfied: bs4 in /home/jupyterlab/conda/envs/python/lib/python3.6/site-packages (0.0.1)\n",
"Requirement already satisfied: beautifulsoup4 in /home/jupyterlab/conda/envs/python/lib/python3.6/site-packages (from bs4) (4.9.0)\n",
"Requirement already satisfied: soupsieve>1.2 in /home/jupyterlab/conda/envs/python/lib/python3.6/site-packages (from beautifulsoup4->bs4) (2.0)\n",
"Requirement already satisfied: html5lib in /home/jupyterlab/conda/envs/python/lib/python3.6/site-packages (0.9999999)\n",
"Requirement already satisfied: six in /home/jupyterlab/conda/envs/python/lib/python3.6/site-packages (from html5lib) (1.14.0)\n",
"Collecting package metadata (current_repodata.json): done\n",
"Solving environment: done\n",
"\n",
"# All requested packages already installed.\n",
"\n",
"Collecting package metadata (current_repodata.json): done\n",
"Solving environment: done\n",
"\n",
"# All requested packages already installed.\n",
"\n",
"Folium installed\n",
"Libraries imported.\n"
]
}
],
"source": [
"import requests # library to handle requests\n",
"import pandas as pd # library for data analsysis\n",
"import numpy as np # library to handle data in a vectorized manner\n",
"import json #library to handle json files\n",
"import random # library for random number generation\n",
"\n",
"#scraping wikitable\n",
"!pip install lxml\n",
"import lxml\n",
"!pip install bs4\n",
"!pip install html5lib\n",
"from pandas.io.html import read_html\n",
"\n",
"!conda install -c conda-forge geopy --yes \n",
"from geopy.geocoders import Nominatim # module to convert an address into latitude and longitude values\n",
"\n",
"# matplotlib and associated plotting modules\n",
"import matplotlib.cm as cm\n",
"import matplotlib.colors as colors\n",
"import matplotlib.pyplot as plt\n",
"\n",
"\n",
"# import k-means for clustering\n",
"from sklearn.cluster import KMeans\n",
"\n",
"#libraries for displaying images\n",
"from IPython.display import Image \n",
"from IPython.core.display import HTML \n",
" \n",
"#tranforming json file into a pandas dataframe library\n",
"from pandas.io.json import json_normalize\n",
"\n",
"!conda install -c conda-forge folium=0.5.0 --yes\n",
"import folium # plotting library\n",
"\n",
"print('Folium installed')\n",
"print('Libraries imported.')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. Download and Explore Dataset "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### scrape the Wikipedia page and wrangle the data"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Extracted 1 wikitables\n"
]
}
],
"source": [
"URL = 'https://en.wikipedia.org/wiki/List_of_postal_codes_of_Canada:_M'\n",
"wikitables = read_html(URL, attrs={\"class\":\"wikitable\"})\n",
"\n",
"print (\"Extracted {num} wikitables\".format(num=len(wikitables))) "
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Postal code</th>\n",
" <th>Borough</th>\n",
" <th>Neighborhood</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>M1A</td>\n",
" <td>Not assigned</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>M2A</td>\n",
" <td>Not assigned</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>M3A</td>\n",
" <td>North York</td>\n",
" <td>Parkwoods</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>M4A</td>\n",
" <td>North York</td>\n",
" <td>Victoria Village</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>M5A</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Regent Park / Harbourfront</td>\n",
" </tr>\n",
" <tr>\n",
" <th>...</th>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>175</th>\n",
" <td>M5Z</td>\n",
" <td>Not assigned</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>176</th>\n",
" <td>M6Z</td>\n",
" <td>Not assigned</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>177</th>\n",
" <td>M7Z</td>\n",
" <td>Not assigned</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>178</th>\n",
" <td>M8Z</td>\n",
" <td>Etobicoke</td>\n",
" <td>Mimico NW / The Queensway West / South of Bloo...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>179</th>\n",
" <td>M9Z</td>\n",
" <td>Not assigned</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>180 rows × 3 columns</p>\n",
"</div>"
],
"text/plain": [
" Postal code Borough \\\n",
"0 M1A Not assigned \n",
"1 M2A Not assigned \n",
"2 M3A North York \n",
"3 M4A North York \n",
"4 M5A Downtown Toronto \n",
".. ... ... \n",
"175 M5Z Not assigned \n",
"176 M6Z Not assigned \n",
"177 M7Z Not assigned \n",
"178 M8Z Etobicoke \n",
"179 M9Z Not assigned \n",
"\n",
" Neighborhood \n",
"0 NaN \n",
"1 NaN \n",
"2 Parkwoods \n",
"3 Victoria Village \n",
"4 Regent Park / Harbourfront \n",
".. ... \n",
"175 NaN \n",
"176 NaN \n",
"177 NaN \n",
"178 Mimico NW / The Queensway West / South of Bloo... \n",
"179 NaN \n",
"\n",
"[180 rows x 3 columns]"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"wikitables[0]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"convert wikitable to a panda dataframe"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"df = pd.DataFrame(wikitables[0])\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"removed not assigned neighborhoods\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Postal code</th>\n",
" <th>Borough</th>\n",
" <th>Neighborhood</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>M3A</td>\n",
" <td>North York</td>\n",
" <td>Parkwoods</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>M4A</td>\n",
" <td>North York</td>\n",
" <td>Victoria Village</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>M5A</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Regent Park / Harbourfront</td>\n",
" </tr>\n",
" <tr>\n",
" <th>5</th>\n",
" <td>M6A</td>\n",
" <td>North York</td>\n",
" <td>Lawrence Manor / Lawrence Heights</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6</th>\n",
" <td>M7A</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Queen's Park / Ontario Provincial Government</td>\n",
" </tr>\n",
" <tr>\n",
" <th>...</th>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>160</th>\n",
" <td>M8X</td>\n",
" <td>Etobicoke</td>\n",
" <td>The Kingsway / Montgomery Road / Old Mill North</td>\n",
" </tr>\n",
" <tr>\n",
" <th>165</th>\n",
" <td>M4Y</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Church and Wellesley</td>\n",
" </tr>\n",
" <tr>\n",
" <th>168</th>\n",
" <td>M7Y</td>\n",
" <td>East Toronto</td>\n",
" <td>Business reply mail Processing CentrE</td>\n",
" </tr>\n",
" <tr>\n",
" <th>169</th>\n",
" <td>M8Y</td>\n",
" <td>Etobicoke</td>\n",
" <td>Old Mill South / King's Mill Park / Sunnylea /...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>178</th>\n",
" <td>M8Z</td>\n",
" <td>Etobicoke</td>\n",
" <td>Mimico NW / The Queensway West / South of Bloo...</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>103 rows × 3 columns</p>\n",
"</div>"
],
"text/plain": [
" Postal code Borough \\\n",
"2 M3A North York \n",
"3 M4A North York \n",
"4 M5A Downtown Toronto \n",
"5 M6A North York \n",
"6 M7A Downtown Toronto \n",
".. ... ... \n",
"160 M8X Etobicoke \n",
"165 M4Y Downtown Toronto \n",
"168 M7Y East Toronto \n",
"169 M8Y Etobicoke \n",
"178 M8Z Etobicoke \n",
"\n",
" Neighborhood \n",
"2 Parkwoods \n",
"3 Victoria Village \n",
"4 Regent Park / Harbourfront \n",
"5 Lawrence Manor / Lawrence Heights \n",
"6 Queen's Park / Ontario Provincial Government \n",
".. ... \n",
"160 The Kingsway / Montgomery Road / Old Mill North \n",
"165 Church and Wellesley \n",
"168 Business reply mail Processing CentrE \n",
"169 Old Mill South / King's Mill Park / Sunnylea /... \n",
"178 Mimico NW / The Queensway West / South of Bloo... \n",
"\n",
"[103 rows x 3 columns]"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# get the names of the index which they are not assigned\n",
"indexnames = df[df['Borough'] == 'Not assigned'].index\n",
"#delete the rows\n",
"df.drop(indexnames, inplace=True)\n",
"df"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(103, 3)"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df.shape"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Use Geoply to get the latitude and longitude values of Canada\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Postal Code</th>\n",
" <th>Latitude</th>\n",
" <th>Longitude</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>M1B</td>\n",
" <td>43.806686</td>\n",
" <td>-79.194353</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>M1C</td>\n",
" <td>43.784535</td>\n",
" <td>-79.160497</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>M1E</td>\n",
" <td>43.763573</td>\n",
" <td>-79.188711</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>M1G</td>\n",
" <td>43.770992</td>\n",
" <td>-79.216917</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>M1H</td>\n",
" <td>43.773136</td>\n",
" <td>-79.239476</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Postal Code Latitude Longitude\n",
"0 M1B 43.806686 -79.194353\n",
"1 M1C 43.784535 -79.160497\n",
"2 M1E 43.763573 -79.188711\n",
"3 M1G 43.770992 -79.216917\n",
"4 M1H 43.773136 -79.239476"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"lat_lon = pd.read_csv('https://cocl.us/Geospatial_data')\n",
"lat_lon.head()"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"#change column names to match the df to merge\n",
"lat_lon.columns=['Postal code','Latitude','Longitude']"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Postal code</th>\n",
" <th>Borough</th>\n",
" <th>Neighborhood</th>\n",
" <th>Latitude</th>\n",
" <th>Longitude</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>M3A</td>\n",
" <td>North York</td>\n",
" <td>Parkwoods</td>\n",
" <td>43.753259</td>\n",
" <td>-79.329656</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>M4A</td>\n",
" <td>North York</td>\n",
" <td>Victoria Village</td>\n",
" <td>43.725882</td>\n",
" <td>-79.315572</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>M5A</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Regent Park / Harbourfront</td>\n",
" <td>43.654260</td>\n",
" <td>-79.360636</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>M6A</td>\n",
" <td>North York</td>\n",
" <td>Lawrence Manor / Lawrence Heights</td>\n",
" <td>43.718518</td>\n",
" <td>-79.464763</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>M7A</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Queen's Park / Ontario Provincial Government</td>\n",
" <td>43.662301</td>\n",
" <td>-79.389494</td>\n",
" </tr>\n",
" <tr>\n",
" <th>...</th>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>98</th>\n",
" <td>M8X</td>\n",
" <td>Etobicoke</td>\n",
" <td>The Kingsway / Montgomery Road / Old Mill North</td>\n",
" <td>43.653654</td>\n",
" <td>-79.506944</td>\n",
" </tr>\n",
" <tr>\n",
" <th>99</th>\n",
" <td>M4Y</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Church and Wellesley</td>\n",
" <td>43.665860</td>\n",
" <td>-79.383160</td>\n",
" </tr>\n",
" <tr>\n",
" <th>100</th>\n",
" <td>M7Y</td>\n",
" <td>East Toronto</td>\n",
" <td>Business reply mail Processing CentrE</td>\n",
" <td>43.662744</td>\n",
" <td>-79.321558</td>\n",
" </tr>\n",
" <tr>\n",
" <th>101</th>\n",
" <td>M8Y</td>\n",
" <td>Etobicoke</td>\n",
" <td>Old Mill South / King's Mill Park / Sunnylea /...</td>\n",
" <td>43.636258</td>\n",
" <td>-79.498509</td>\n",
" </tr>\n",
" <tr>\n",
" <th>102</th>\n",
" <td>M8Z</td>\n",
" <td>Etobicoke</td>\n",
" <td>Mimico NW / The Queensway West / South of Bloo...</td>\n",
" <td>43.628841</td>\n",
" <td>-79.520999</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>103 rows × 5 columns</p>\n",
"</div>"
],
"text/plain": [
" Postal code Borough \\\n",
"0 M3A North York \n",
"1 M4A North York \n",
"2 M5A Downtown Toronto \n",
"3 M6A North York \n",
"4 M7A Downtown Toronto \n",
".. ... ... \n",
"98 M8X Etobicoke \n",
"99 M4Y Downtown Toronto \n",
"100 M7Y East Toronto \n",
"101 M8Y Etobicoke \n",
"102 M8Z Etobicoke \n",
"\n",
" Neighborhood Latitude Longitude \n",
"0 Parkwoods 43.753259 -79.329656 \n",
"1 Victoria Village 43.725882 -79.315572 \n",
"2 Regent Park / Harbourfront 43.654260 -79.360636 \n",
"3 Lawrence Manor / Lawrence Heights 43.718518 -79.464763 \n",
"4 Queen's Park / Ontario Provincial Government 43.662301 -79.389494 \n",
".. ... ... ... \n",
"98 The Kingsway / Montgomery Road / Old Mill North 43.653654 -79.506944 \n",
"99 Church and Wellesley 43.665860 -79.383160 \n",
"100 Business reply mail Processing CentrE 43.662744 -79.321558 \n",
"101 Old Mill South / King's Mill Park / Sunnylea /... 43.636258 -79.498509 \n",
"102 Mimico NW / The Queensway West / South of Bloo... 43.628841 -79.520999 \n",
"\n",
"[103 rows x 5 columns]"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"#merge dataframes\n",
"Canada_df= pd.merge( df,lat_lon, on='Postal code')\n",
"Canada_df"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(103, 5)"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Canada_df.shape"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"How many boroughs and neighborhoods in Canada?"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The dataframe has 10 boroughs and 103 neighborhoods.\n"
]
}
],
"source": [
"print('The dataframe has {} boroughs and {} neighborhoods.'.format(\n",
" len(Canada_df['Borough'].unique()),\n",
" Canada_df.shape[0]\n",
" )\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Use Geoply library to get the longitude and latitude of Canada"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"in order to define and instance in we define a user agent of Canada_explorer shown below"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
},
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The geograpical coordinate of Canada are 61.0666922, -107.9917071.\n"
]
}
],
"source": [
"address = 'Canada'\n",
"\n",
"geolocator = Nominatim(user_agent=\"Canada_explorer\")\n",
"location = geolocator.geocode(address)\n",
"latitude = location.latitude\n",
"longitude = location.longitude\n",
"print('The geograpical coordinate of Canada are {}, {}.'.format(latitude, longitude))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### New Dataframe with Only Toronto Data"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Postal code</th>\n",
" <th>Borough</th>\n",
" <th>Neighborhood</th>\n",
" <th>Latitude</th>\n",
" <th>Longitude</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>M5A</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Regent Park / Harbourfront</td>\n",
" <td>43.654260</td>\n",
" <td>-79.360636</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>M7A</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Queen's Park / Ontario Provincial Government</td>\n",
" <td>43.662301</td>\n",
" <td>-79.389494</td>\n",
" </tr>\n",
" <tr>\n",
" <th>9</th>\n",
" <td>M5B</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Garden District, Ryerson</td>\n",
" <td>43.657162</td>\n",
" <td>-79.378937</td>\n",
" </tr>\n",
" <tr>\n",
" <th>15</th>\n",
" <td>M5C</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>St. James Town</td>\n",
" <td>43.651494</td>\n",
" <td>-79.375418</td>\n",
" </tr>\n",
" <tr>\n",
" <th>19</th>\n",
" <td>M4E</td>\n",
" <td>East Toronto</td>\n",
" <td>The Beaches</td>\n",
" <td>43.676357</td>\n",
" <td>-79.293031</td>\n",
" </tr>\n",
" <tr>\n",
" <th>20</th>\n",
" <td>M5E</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Berczy Park</td>\n",
" <td>43.644771</td>\n",
" <td>-79.373306</td>\n",
" </tr>\n",
" <tr>\n",
" <th>24</th>\n",
" <td>M5G</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Central Bay Street</td>\n",
" <td>43.657952</td>\n",
" <td>-79.387383</td>\n",
" </tr>\n",
" <tr>\n",
" <th>25</th>\n",
" <td>M6G</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Christie</td>\n",
" <td>43.669542</td>\n",
" <td>-79.422564</td>\n",
" </tr>\n",
" <tr>\n",
" <th>30</th>\n",
" <td>M5H</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Richmond / Adelaide / King</td>\n",
" <td>43.650571</td>\n",
" <td>-79.384568</td>\n",
" </tr>\n",
" <tr>\n",
" <th>31</th>\n",
" <td>M6H</td>\n",
" <td>West Toronto</td>\n",
" <td>Dufferin / Dovercourt Village</td>\n",
" <td>43.669005</td>\n",
" <td>-79.442259</td>\n",
" </tr>\n",
" <tr>\n",
" <th>36</th>\n",
" <td>M5J</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Harbourfront East / Union Station / Toronto Is...</td>\n",
" <td>43.640816</td>\n",
" <td>-79.381752</td>\n",
" </tr>\n",
" <tr>\n",
" <th>37</th>\n",
" <td>M6J</td>\n",
" <td>West Toronto</td>\n",
" <td>Little Portugal / Trinity</td>\n",
" <td>43.647927</td>\n",
" <td>-79.419750</td>\n",
" </tr>\n",
" <tr>\n",
" <th>41</th>\n",
" <td>M4K</td>\n",
" <td>East Toronto</td>\n",
" <td>The Danforth West / Riverdale</td>\n",
" <td>43.679557</td>\n",
" <td>-79.352188</td>\n",
" </tr>\n",
" <tr>\n",
" <th>42</th>\n",
" <td>M5K</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Toronto Dominion Centre / Design Exchange</td>\n",
" <td>43.647177</td>\n",
" <td>-79.381576</td>\n",
" </tr>\n",
" <tr>\n",
" <th>43</th>\n",
" <td>M6K</td>\n",
" <td>West Toronto</td>\n",
" <td>Brockton / Parkdale Village / Exhibition Place</td>\n",
" <td>43.636847</td>\n",
" <td>-79.428191</td>\n",
" </tr>\n",
" <tr>\n",
" <th>47</th>\n",
" <td>M4L</td>\n",
" <td>East Toronto</td>\n",
" <td>India Bazaar / The Beaches West</td>\n",
" <td>43.668999</td>\n",
" <td>-79.315572</td>\n",
" </tr>\n",
" <tr>\n",
" <th>48</th>\n",
" <td>M5L</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Commerce Court / Victoria Hotel</td>\n",
" <td>43.648198</td>\n",
" <td>-79.379817</td>\n",
" </tr>\n",
" <tr>\n",
" <th>54</th>\n",
" <td>M4M</td>\n",
" <td>East Toronto</td>\n",
" <td>Studio District</td>\n",
" <td>43.659526</td>\n",
" <td>-79.340923</td>\n",
" </tr>\n",
" <tr>\n",
" <th>61</th>\n",
" <td>M4N</td>\n",
" <td>Central Toronto</td>\n",
" <td>Lawrence Park</td>\n",
" <td>43.728020</td>\n",
" <td>-79.388790</td>\n",
" </tr>\n",
" <tr>\n",
" <th>62</th>\n",
" <td>M5N</td>\n",
" <td>Central Toronto</td>\n",
" <td>Roselawn</td>\n",
" <td>43.711695</td>\n",
" <td>-79.416936</td>\n",
" </tr>\n",
" <tr>\n",
" <th>67</th>\n",
" <td>M4P</td>\n",
" <td>Central Toronto</td>\n",
" <td>Davisville North</td>\n",
" <td>43.712751</td>\n",
" <td>-79.390197</td>\n",
" </tr>\n",
" <tr>\n",
" <th>68</th>\n",
" <td>M5P</td>\n",
" <td>Central Toronto</td>\n",
" <td>Forest Hill North &amp; West</td>\n",
" <td>43.696948</td>\n",
" <td>-79.411307</td>\n",
" </tr>\n",
" <tr>\n",
" <th>69</th>\n",
" <td>M6P</td>\n",
" <td>West Toronto</td>\n",
" <td>High Park / The Junction South</td>\n",
" <td>43.661608</td>\n",
" <td>-79.464763</td>\n",
" </tr>\n",
" <tr>\n",
" <th>73</th>\n",
" <td>M4R</td>\n",
" <td>Central Toronto</td>\n",
" <td>North Toronto West</td>\n",
" <td>43.715383</td>\n",
" <td>-79.405678</td>\n",
" </tr>\n",
" <tr>\n",
" <th>74</th>\n",
" <td>M5R</td>\n",
" <td>Central Toronto</td>\n",
" <td>The Annex / North Midtown / Yorkville</td>\n",
" <td>43.672710</td>\n",
" <td>-79.405678</td>\n",
" </tr>\n",
" <tr>\n",
" <th>75</th>\n",
" <td>M6R</td>\n",
" <td>West Toronto</td>\n",
" <td>Parkdale / Roncesvalles</td>\n",
" <td>43.648960</td>\n",
" <td>-79.456325</td>\n",
" </tr>\n",
" <tr>\n",
" <th>79</th>\n",
" <td>M4S</td>\n",
" <td>Central Toronto</td>\n",
" <td>Davisville</td>\n",
" <td>43.704324</td>\n",
" <td>-79.388790</td>\n",
" </tr>\n",
" <tr>\n",
" <th>80</th>\n",
" <td>M5S</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>University of Toronto / Harbord</td>\n",
" <td>43.662696</td>\n",
" <td>-79.400049</td>\n",
" </tr>\n",
" <tr>\n",
" <th>81</th>\n",
" <td>M6S</td>\n",
" <td>West Toronto</td>\n",
" <td>Runnymede / Swansea</td>\n",
" <td>43.651571</td>\n",
" <td>-79.484450</td>\n",
" </tr>\n",
" <tr>\n",
" <th>83</th>\n",
" <td>M4T</td>\n",
" <td>Central Toronto</td>\n",
" <td>Moore Park / Summerhill East</td>\n",
" <td>43.689574</td>\n",
" <td>-79.383160</td>\n",
" </tr>\n",
" <tr>\n",
" <th>84</th>\n",
" <td>M5T</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Kensington Market / Chinatown / Grange Park</td>\n",
" <td>43.653206</td>\n",
" <td>-79.400049</td>\n",
" </tr>\n",
" <tr>\n",
" <th>86</th>\n",
" <td>M4V</td>\n",
" <td>Central Toronto</td>\n",
" <td>Summerhill West / Rathnelly / South Hill / For...</td>\n",
" <td>43.686412</td>\n",
" <td>-79.400049</td>\n",
" </tr>\n",
" <tr>\n",
" <th>87</th>\n",
" <td>M5V</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>CN Tower / King and Spadina / Railway Lands / ...</td>\n",
" <td>43.628947</td>\n",
" <td>-79.394420</td>\n",
" </tr>\n",
" <tr>\n",
" <th>91</th>\n",
" <td>M4W</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Rosedale</td>\n",
" <td>43.679563</td>\n",
" <td>-79.377529</td>\n",
" </tr>\n",
" <tr>\n",
" <th>92</th>\n",
" <td>M5W</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Stn A PO Boxes</td>\n",
" <td>43.646435</td>\n",
" <td>-79.374846</td>\n",
" </tr>\n",
" <tr>\n",
" <th>96</th>\n",
" <td>M4X</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>St. James Town / Cabbagetown</td>\n",
" <td>43.667967</td>\n",
" <td>-79.367675</td>\n",
" </tr>\n",
" <tr>\n",
" <th>97</th>\n",
" <td>M5X</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>First Canadian Place / Underground city</td>\n",
" <td>43.648429</td>\n",
" <td>-79.382280</td>\n",
" </tr>\n",
" <tr>\n",
" <th>99</th>\n",
" <td>M4Y</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Church and Wellesley</td>\n",
" <td>43.665860</td>\n",
" <td>-79.383160</td>\n",
" </tr>\n",
" <tr>\n",
" <th>100</th>\n",
" <td>M7Y</td>\n",
" <td>East Toronto</td>\n",
" <td>Business reply mail Processing CentrE</td>\n",
" <td>43.662744</td>\n",
" <td>-79.321558</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Postal code Borough \\\n",
"2 M5A Downtown Toronto \n",
"4 M7A Downtown Toronto \n",
"9 M5B Downtown Toronto \n",
"15 M5C Downtown Toronto \n",
"19 M4E East Toronto \n",
"20 M5E Downtown Toronto \n",
"24 M5G Downtown Toronto \n",
"25 M6G Downtown Toronto \n",
"30 M5H Downtown Toronto \n",
"31 M6H West Toronto \n",
"36 M5J Downtown Toronto \n",
"37 M6J West Toronto \n",
"41 M4K East Toronto \n",
"42 M5K Downtown Toronto \n",
"43 M6K West Toronto \n",
"47 M4L East Toronto \n",
"48 M5L Downtown Toronto \n",
"54 M4M East Toronto \n",
"61 M4N Central Toronto \n",
"62 M5N Central Toronto \n",
"67 M4P Central Toronto \n",
"68 M5P Central Toronto \n",
"69 M6P West Toronto \n",
"73 M4R Central Toronto \n",
"74 M5R Central Toronto \n",
"75 M6R West Toronto \n",
"79 M4S Central Toronto \n",
"80 M5S Downtown Toronto \n",
"81 M6S West Toronto \n",
"83 M4T Central Toronto \n",
"84 M5T Downtown Toronto \n",
"86 M4V Central Toronto \n",
"87 M5V Downtown Toronto \n",
"91 M4W Downtown Toronto \n",
"92 M5W Downtown Toronto \n",
"96 M4X Downtown Toronto \n",
"97 M5X Downtown Toronto \n",
"99 M4Y Downtown Toronto \n",
"100 M7Y East Toronto \n",
"\n",
" Neighborhood Latitude Longitude \n",
"2 Regent Park / Harbourfront 43.654260 -79.360636 \n",
"4 Queen's Park / Ontario Provincial Government 43.662301 -79.389494 \n",
"9 Garden District, Ryerson 43.657162 -79.378937 \n",
"15 St. James Town 43.651494 -79.375418 \n",
"19 The Beaches 43.676357 -79.293031 \n",
"20 Berczy Park 43.644771 -79.373306 \n",
"24 Central Bay Street 43.657952 -79.387383 \n",
"25 Christie 43.669542 -79.422564 \n",
"30 Richmond / Adelaide / King 43.650571 -79.384568 \n",
"31 Dufferin / Dovercourt Village 43.669005 -79.442259 \n",
"36 Harbourfront East / Union Station / Toronto Is... 43.640816 -79.381752 \n",
"37 Little Portugal / Trinity 43.647927 -79.419750 \n",
"41 The Danforth West / Riverdale 43.679557 -79.352188 \n",
"42 Toronto Dominion Centre / Design Exchange 43.647177 -79.381576 \n",
"43 Brockton / Parkdale Village / Exhibition Place 43.636847 -79.428191 \n",
"47 India Bazaar / The Beaches West 43.668999 -79.315572 \n",
"48 Commerce Court / Victoria Hotel 43.648198 -79.379817 \n",
"54 Studio District 43.659526 -79.340923 \n",
"61 Lawrence Park 43.728020 -79.388790 \n",
"62 Roselawn 43.711695 -79.416936 \n",
"67 Davisville North 43.712751 -79.390197 \n",
"68 Forest Hill North & West 43.696948 -79.411307 \n",
"69 High Park / The Junction South 43.661608 -79.464763 \n",
"73 North Toronto West 43.715383 -79.405678 \n",
"74 The Annex / North Midtown / Yorkville 43.672710 -79.405678 \n",
"75 Parkdale / Roncesvalles 43.648960 -79.456325 \n",
"79 Davisville 43.704324 -79.388790 \n",
"80 University of Toronto / Harbord 43.662696 -79.400049 \n",
"81 Runnymede / Swansea 43.651571 -79.484450 \n",
"83 Moore Park / Summerhill East 43.689574 -79.383160 \n",
"84 Kensington Market / Chinatown / Grange Park 43.653206 -79.400049 \n",
"86 Summerhill West / Rathnelly / South Hill / For... 43.686412 -79.400049 \n",
"87 CN Tower / King and Spadina / Railway Lands / ... 43.628947 -79.394420 \n",
"91 Rosedale 43.679563 -79.377529 \n",
"92 Stn A PO Boxes 43.646435 -79.374846 \n",
"96 St. James Town / Cabbagetown 43.667967 -79.367675 \n",
"97 First Canadian Place / Underground city 43.648429 -79.382280 \n",
"99 Church and Wellesley 43.665860 -79.383160 \n",
"100 Business reply mail Processing CentrE 43.662744 -79.321558 "
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Toronto_df = Canada_df[Canada_df['Borough'].str.contains('Toronto', regex =False)]\n",
"Toronto_df"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(39, 5)"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Toronto_df.shape"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's find the latitude and longitude of Toronto"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The geograpical coordinate of Toronto are 43.6534817, -79.3839347.\n"
]
}
],
"source": [
"address = 'Toronto'\n",
"\n",
"geolocator = Nominatim(user_agent=\"ny_explorer\")\n",
"location = geolocator.geocode(address)\n",
"latitude = location.latitude\n",
"longitude = location.longitude\n",
"print('The geograpical coordinate of Toronto are {}, {}.'.format(latitude, longitude))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Explore and cluster the neighborhoods in Toronto"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
}
},
"source": [
"Explore a Map of Toronto \n"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {
"button": false,
"collapsed": false,
"jupyter": {
"outputs_hidden": false
},
"new_sheet": false,
"run_control": {
"read_only": false
}
},
"outputs": [
{
"data": {
"text/html": [
"<div style=\"width:100%;\"><div style=\"position:relative;width:100%;height:0;padding-bottom:60%;\"><iframe src=\"about:blank\" style=\"position:absolute;width:100%;height:100%;left:0;top:0;border:none !important;\" data-html=PCFET0NUWVBFIGh0bWw+CjxoZWFkPiAgICAKICAgIDxtZXRhIGh0dHAtZXF1aXY9ImNvbnRlbnQtdHlwZSIgY29udGVudD0idGV4dC9odG1sOyBjaGFyc2V0PVVURi04IiAvPgogICAgPHNjcmlwdD5MX1BSRUZFUl9DQU5WQVMgPSBmYWxzZTsgTF9OT19UT1VDSCA9IGZhbHNlOyBMX0RJU0FCTEVfM0QgPSBmYWxzZTs8L3NjcmlwdD4KICAgIDxzY3JpcHQgc3JjPSJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL2xlYWZsZXRAMS4yLjAvZGlzdC9sZWFmbGV0LmpzIj48L3NjcmlwdD4KICAgIDxzY3JpcHQgc3JjPSJodHRwczovL2FqYXguZ29vZ2xlYXBpcy5jb20vYWpheC9saWJzL2pxdWVyeS8xLjExLjEvanF1ZXJ5Lm1pbi5qcyI+PC9zY3JpcHQ+CiAgICA8c2NyaXB0IHNyYz0iaHR0cHM6Ly9tYXhjZG4uYm9vdHN0cmFwY2RuLmNvbS9ib290c3RyYXAvMy4yLjAvanMvYm9vdHN0cmFwLm1pbi5qcyI+PC9zY3JpcHQ+CiAgICA8c2NyaXB0IHNyYz0iaHR0cHM6Ly9jZG5qcy5jbG91ZGZsYXJlLmNvbS9hamF4L2xpYnMvTGVhZmxldC5hd2Vzb21lLW1hcmtlcnMvMi4wLjIvbGVhZmxldC5hd2Vzb21lLW1hcmtlcnMuanMiPjwvc2NyaXB0PgogICAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL2xlYWZsZXRAMS4yLjAvZGlzdC9sZWFmbGV0LmNzcyIvPgogICAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJodHRwczovL21heGNkbi5ib290c3RyYXBjZG4uY29tL2Jvb3RzdHJhcC8zLjIuMC9jc3MvYm9vdHN0cmFwLm1pbi5jc3MiLz4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iaHR0cHM6Ly9tYXhjZG4uYm9vdHN0cmFwY2RuLmNvbS9ib290c3RyYXAvMy4yLjAvY3NzL2Jvb3RzdHJhcC10aGVtZS5taW4uY3NzIi8+CiAgICA8bGluayByZWw9InN0eWxlc2hlZXQiIGhyZWY9Imh0dHBzOi8vbWF4Y2RuLmJvb3RzdHJhcGNkbi5jb20vZm9udC1hd2Vzb21lLzQuNi4zL2Nzcy9mb250LWF3ZXNvbWUubWluLmNzcyIvPgogICAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJodHRwczovL2NkbmpzLmNsb3VkZmxhcmUuY29tL2FqYXgvbGlicy9MZWFmbGV0LmF3ZXNvbWUtbWFya2Vycy8yLjAuMi9sZWFmbGV0LmF3ZXNvbWUtbWFya2Vycy5jc3MiLz4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iaHR0cHM6Ly9yYXdnaXQuY29tL3B5dGhvbi12aXN1YWxpemF0aW9uL2ZvbGl1bS9tYXN0ZXIvZm9saXVtL3RlbXBsYXRlcy9sZWFmbGV0LmF3ZXNvbWUucm90YXRlLmNzcyIvPgogICAgPHN0eWxlPmh0bWwsIGJvZHkge3dpZHRoOiAxMDAlO2hlaWdodDogMTAwJTttYXJnaW46IDA7cGFkZGluZzogMDt9PC9zdHlsZT4KICAgIDxzdHlsZT4jbWFwIHtwb3NpdGlvbjphYnNvbHV0ZTt0b3A6MDtib3R0b206MDtyaWdodDowO2xlZnQ6MDt9PC9zdHlsZT4KICAgIAogICAgICAgICAgICA8c3R5bGU+ICNtYXBfYzY3MDI2OTEyNmI0NGNiMDg0OTc4NTFkNzA2ZmMxMzIgewogICAgICAgICAgICAgICAgcG9zaXRpb24gOiByZWxhdGl2ZTsKICAgICAgICAgICAgICAgIHdpZHRoIDogMTAwLjAlOwogICAgICAgICAgICAgICAgaGVpZ2h0OiAxMDAuMCU7CiAgICAgICAgICAgICAgICBsZWZ0OiAwLjAlOwogICAgICAgICAgICAgICAgdG9wOiAwLjAlOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICA8L3N0eWxlPgogICAgICAgIAo8L2hlYWQ+Cjxib2R5PiAgICAKICAgIAogICAgICAgICAgICA8ZGl2IGNsYXNzPSJmb2xpdW0tbWFwIiBpZD0ibWFwX2M2NzAyNjkxMjZiNDRjYjA4NDk3ODUxZDcwNmZjMTMyIiA+PC9kaXY+CiAgICAgICAgCjwvYm9keT4KPHNjcmlwdD4gICAgCiAgICAKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGJvdW5kcyA9IG51bGw7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgdmFyIG1hcF9jNjcwMjY5MTI2YjQ0Y2IwODQ5Nzg1MWQ3MDZmYzEzMiA9IEwubWFwKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ21hcF9jNjcwMjY5MTI2YjQ0Y2IwODQ5Nzg1MWQ3MDZmYzEzMicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7Y2VudGVyOiBbNDMuNjUzNDgxNywtNzkuMzgzOTM0N10sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB6b29tOiAxMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heEJvdW5kczogYm91bmRzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGF5ZXJzOiBbXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdvcmxkQ29weUp1bXA6IGZhbHNlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY3JzOiBMLkNSUy5FUFNHMzg1NwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9KTsKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHRpbGVfbGF5ZXJfOGMyNzYzMTNlNjQ5NDNhNjk4Y2M1MjI1MTIwOGFmZjQgPSBMLnRpbGVMYXllcigKICAgICAgICAgICAgICAgICdodHRwczovL3tzfS50aWxlLm9wZW5zdHJlZXRtYXAub3JnL3t6fS97eH0ve3l9LnBuZycsCiAgICAgICAgICAgICAgICB7CiAgImF0dHJpYnV0aW9uIjogbnVsbCwKICAiZGV0ZWN0UmV0aW5hIjogZmFsc2UsCiAgIm1heFpvb20iOiAxOCwKICAibWluWm9vbSI6IDEsCiAgIm5vV3JhcCI6IGZhbHNlLAogICJzdWJkb21haW5zIjogImFiYyIKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfYzY3MDI2OTEyNmI0NGNiMDg0OTc4NTFkNzA2ZmMxMzIpOwogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzNmYTE2ODk3MDMwZDQ3NTc5YjQzOWM0NmQ1YWFlZjdmID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjU0MjU5OSwtNzkuMzYwNjM1OV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICJibHVlIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzMxODZjYyIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogMiwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF9jNjcwMjY5MTI2YjQ0Y2IwODQ5Nzg1MWQ3MDZmYzEzMik7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9jMDk2Nzc1NTcyMjI0OGJiOGQzYjQ3Y2QyMjM2N2YzZSA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9kZDQ2MGYwMTdjZjE0YzVmOWYxNGQ4NDQ0M2I0Yzc4YiA9ICQoJzxkaXYgaWQ9Imh0bWxfZGQ0NjBmMDE3Y2YxNGM1ZjlmMTRkODQ0NDNiNGM3OGIiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPlJlZ2VudCBQYXJrIC8gSGFyYm91cmZyb250LCBEb3dudG93biBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9jMDk2Nzc1NTcyMjI0OGJiOGQzYjQ3Y2QyMjM2N2YzZS5zZXRDb250ZW50KGh0bWxfZGQ0NjBmMDE3Y2YxNGM1ZjlmMTRkODQ0NDNiNGM3OGIpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfM2ZhMTY4OTcwMzBkNDc1NzliNDM5YzQ2ZDVhYWVmN2YuYmluZFBvcHVwKHBvcHVwX2MwOTY3NzU1NzIyMjQ4YmI4ZDNiNDdjZDIyMzY3ZjNlKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzg5NjBhMzNiMGJkNjQ1ZmRhZWFiNjM4MjY2NTE4YTU0ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjYyMzAxNSwtNzkuMzg5NDkzOF0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICJibHVlIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzMxODZjYyIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogMiwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF9jNjcwMjY5MTI2YjQ0Y2IwODQ5Nzg1MWQ3MDZmYzEzMik7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF8xMjUwZmRkNzFhNDY0N2YwODc1NGRiNzBiNmZjZTViOCA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF8zNGFjNTZhYzA3Y2Q0MjQyOTQyOTQ0ZWVhYjI3Njg4YSA9ICQoJzxkaXYgaWQ9Imh0bWxfMzRhYzU2YWMwN2NkNDI0Mjk0Mjk0NGVlYWIyNzY4OGEiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPlF1ZWVuJiMzOTtzIFBhcmsgLyBPbnRhcmlvIFByb3ZpbmNpYWwgR292ZXJubWVudCwgRG93bnRvd24gVG9yb250bzwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfMTI1MGZkZDcxYTQ2NDdmMDg3NTRkYjcwYjZmY2U1Yjguc2V0Q29udGVudChodG1sXzM0YWM1NmFjMDdjZDQyNDI5NDI5NDRlZWFiMjc2ODhhKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzg5NjBhMzNiMGJkNjQ1ZmRhZWFiNjM4MjY2NTE4YTU0LmJpbmRQb3B1cChwb3B1cF8xMjUwZmRkNzFhNDY0N2YwODc1NGRiNzBiNmZjZTViOCk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl8xY2JiMGVjOGY1NTQ0YTU1YmI1YmVjNjRhZDJlOWQ2MCA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY1NzE2MTgsLTc5LjM3ODkzNzA5OTk5OTk5XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogImJsdWUiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjMzE4NmNjIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiAyLAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2M2NzAyNjkxMjZiNDRjYjA4NDk3ODUxZDcwNmZjMTMyKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzU2NWNhY2IyYjA4MTRjZWE5NTk1NWQ0Njk1YzI3ZTUxID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzJjNzBhZjNiMzQ0NTRlNWY4OGU5OGMyMmQzZGY2OWYzID0gJCgnPGRpdiBpZD0iaHRtbF8yYzcwYWYzYjM0NDU0ZTVmODhlOThjMjJkM2RmNjlmMyIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+R2FyZGVuIERpc3RyaWN0LCBSeWVyc29uLCBEb3dudG93biBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF81NjVjYWNiMmIwODE0Y2VhOTU5NTVkNDY5NWMyN2U1MS5zZXRDb250ZW50KGh0bWxfMmM3MGFmM2IzNDQ1NGU1Zjg4ZTk4YzIyZDNkZjY5ZjMpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfMWNiYjBlYzhmNTU0NGE1NWJiNWJlYzY0YWQyZTlkNjAuYmluZFBvcHVwKHBvcHVwXzU2NWNhY2IyYjA4MTRjZWE5NTk1NWQ0Njk1YzI3ZTUxKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2Y1NjYzNjJiODE3MTQ4YzE4NDAwZmNmNzMzZmM5MWZiID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjUxNDkzOSwtNzkuMzc1NDE3OV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICJibHVlIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzMxODZjYyIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogMiwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF9jNjcwMjY5MTI2YjQ0Y2IwODQ5Nzg1MWQ3MDZmYzEzMik7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF8zZmRkYTRkYmY3NWE0N2JjYjQxYmUxZjM5MThhNTUzZCA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9iMGUxNzY3YmVmYTA0YTZjYmI0MGE0ZjE0MjBkNzZlYSA9ICQoJzxkaXYgaWQ9Imh0bWxfYjBlMTc2N2JlZmEwNGE2Y2JiNDBhNGYxNDIwZDc2ZWEiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPlN0LiBKYW1lcyBUb3duLCBEb3dudG93biBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF8zZmRkYTRkYmY3NWE0N2JjYjQxYmUxZjM5MThhNTUzZC5zZXRDb250ZW50KGh0bWxfYjBlMTc2N2JlZmEwNGE2Y2JiNDBhNGYxNDIwZDc2ZWEpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfZjU2NjM2MmI4MTcxNDhjMTg0MDBmY2Y3MzNmYzkxZmIuYmluZFBvcHVwKHBvcHVwXzNmZGRhNGRiZjc1YTQ3YmNiNDFiZTFmMzkxOGE1NTNkKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzQ4YTBiMTNiMDcwODQzNWRhOGQxYmZlNDliNzQ3NTBhID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjc2MzU3Mzk5OTk5OTksLTc5LjI5MzAzMTJdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDIsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfYzY3MDI2OTEyNmI0NGNiMDg0OTc4NTFkNzA2ZmMxMzIpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfZThiMjFmOTlhMDk1NDE0MGE5YjdiZGRkMTdkY2QwNDIgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfMjU3NWIzZTYzNTQ1NDk3NGFkZGJjZjUxZmMyMzc0YjcgPSAkKCc8ZGl2IGlkPSJodG1sXzI1NzViM2U2MzU0NTQ5NzRhZGRiY2Y1MWZjMjM3NGI3IiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5UaGUgQmVhY2hlcywgRWFzdCBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9lOGIyMWY5OWEwOTU0MTQwYTliN2JkZGQxN2RjZDA0Mi5zZXRDb250ZW50KGh0bWxfMjU3NWIzZTYzNTQ1NDk3NGFkZGJjZjUxZmMyMzc0YjcpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfNDhhMGIxM2IwNzA4NDM1ZGE4ZDFiZmU0OWI3NDc1MGEuYmluZFBvcHVwKHBvcHVwX2U4YjIxZjk5YTA5NTQxNDBhOWI3YmRkZDE3ZGNkMDQyKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzQxNjI4ZWMyZDI3YjRhNDk5MTg4YjlkNmU5Y2QxYWNlID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjQ0NzcwNzk5OTk5OTk2LC03OS4zNzMzMDY0XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogImJsdWUiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjMzE4NmNjIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiAyLAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2M2NzAyNjkxMjZiNDRjYjA4NDk3ODUxZDcwNmZjMTMyKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwX2M5Mjc1MzY4YzdjODRiMzViNDc0OGQwYWQ1MTNlMGY3ID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sX2FjYTk5ZTgwZDVjMTQyZTFhN2Y0NjJkMjRkZTc0OGQ0ID0gJCgnPGRpdiBpZD0iaHRtbF9hY2E5OWU4MGQ1YzE0MmUxYTdmNDYyZDI0ZGU3NDhkNCIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+QmVyY3p5IFBhcmssIERvd250b3duIFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2M5Mjc1MzY4YzdjODRiMzViNDc0OGQwYWQ1MTNlMGY3LnNldENvbnRlbnQoaHRtbF9hY2E5OWU4MGQ1YzE0MmUxYTdmNDYyZDI0ZGU3NDhkNCk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl80MTYyOGVjMmQyN2I0YTQ5OTE4OGI5ZDZlOWNkMWFjZS5iaW5kUG9wdXAocG9wdXBfYzkyNzUzNjhjN2M4NGIzNWI0NzQ4ZDBhZDUxM2UwZjcpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfZGM1ZGI0MTQ4YjcxNDY0MGE2NTY0Njc3ZmU5YWNmMGIgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NTc5NTI0LC03OS4zODczODI2XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogImJsdWUiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjMzE4NmNjIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiAyLAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2M2NzAyNjkxMjZiNDRjYjA4NDk3ODUxZDcwNmZjMTMyKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwX2UzNmU3NmMzOWUxYjQxMTJiNDgwYjljNDk0NTMxYmNjID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzZjNmE5ZGMxMGFhNDQ3NGI5MDFjMDZmM2Y2MDc0ZjUzID0gJCgnPGRpdiBpZD0iaHRtbF82YzZhOWRjMTBhYTQ0NzRiOTAxYzA2ZjNmNjA3NGY1MyIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+Q2VudHJhbCBCYXkgU3RyZWV0LCBEb3dudG93biBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9lMzZlNzZjMzllMWI0MTEyYjQ4MGI5YzQ5NDUzMWJjYy5zZXRDb250ZW50KGh0bWxfNmM2YTlkYzEwYWE0NDc0YjkwMWMwNmYzZjYwNzRmNTMpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfZGM1ZGI0MTQ4YjcxNDY0MGE2NTY0Njc3ZmU5YWNmMGIuYmluZFBvcHVwKHBvcHVwX2UzNmU3NmMzOWUxYjQxMTJiNDgwYjljNDk0NTMxYmNjKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzFmZjMxZWRkZWI0YzQ2YzA5MzE2ZTFkODY3ZDBhZDY2ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjY5NTQyLC03OS40MjI1NjM3XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogImJsdWUiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjMzE4NmNjIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiAyLAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2M2NzAyNjkxMjZiNDRjYjA4NDk3ODUxZDcwNmZjMTMyKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzdjNDgzZWY2NzczMzRhODRhNzc2NDcyY2IyYjhkNDQ1ID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzBhMWZmMGRhN2QzZjRlZmM4MDFhOGI4YWVhZjc0YWZkID0gJCgnPGRpdiBpZD0iaHRtbF8wYTFmZjBkYTdkM2Y0ZWZjODAxYThiOGFlYWY3NGFmZCIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+Q2hyaXN0aWUsIERvd250b3duIFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzdjNDgzZWY2NzczMzRhODRhNzc2NDcyY2IyYjhkNDQ1LnNldENvbnRlbnQoaHRtbF8wYTFmZjBkYTdkM2Y0ZWZjODAxYThiOGFlYWY3NGFmZCk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl8xZmYzMWVkZGViNGM0NmMwOTMxNmUxZDg2N2QwYWQ2Ni5iaW5kUG9wdXAocG9wdXBfN2M0ODNlZjY3NzMzNGE4NGE3NzY0NzJjYjJiOGQ0NDUpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfNzY4YjI2OGQyNWQzNDJlNmEyNzRkODYxNzdiYjg2NjAgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NTA1NzEyMDAwMDAwMSwtNzkuMzg0NTY3NV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICJibHVlIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzMxODZjYyIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogMiwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF9jNjcwMjY5MTI2YjQ0Y2IwODQ5Nzg1MWQ3MDZmYzEzMik7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF8wZTdhZWZkZWRhYzU0NGNjYWM2ZmRmZDU2Yjc5NGEwNyA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF81MWI3NTliNjA0NzE0MmUxYmM2ZDk5ZDUyMWQwZDcxOSA9ICQoJzxkaXYgaWQ9Imh0bWxfNTFiNzU5YjYwNDcxNDJlMWJjNmQ5OWQ1MjFkMGQ3MTkiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPlJpY2htb25kIC8gQWRlbGFpZGUgLyBLaW5nLCBEb3dudG93biBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF8wZTdhZWZkZWRhYzU0NGNjYWM2ZmRmZDU2Yjc5NGEwNy5zZXRDb250ZW50KGh0bWxfNTFiNzU5YjYwNDcxNDJlMWJjNmQ5OWQ1MjFkMGQ3MTkpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfNzY4YjI2OGQyNWQzNDJlNmEyNzRkODYxNzdiYjg2NjAuYmluZFBvcHVwKHBvcHVwXzBlN2FlZmRlZGFjNTQ0Y2NhYzZmZGZkNTZiNzk0YTA3KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2MzMGQ0NGU0Yzk4ZjQzNzZiZjVkNGYyN2U2OThjNGE3ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjY5MDA1MTAwMDAwMDEsLTc5LjQ0MjI1OTNdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDIsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfYzY3MDI2OTEyNmI0NGNiMDg0OTc4NTFkNzA2ZmMxMzIpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfNjJhZDk4YjU3ZWY4NDYwYWE0YzIzYzczOTc3MzFkOTYgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfNzAwZGY5ODhkODBiNDY2OTlkYWQ4NWZjZjQyZGIyODcgPSAkKCc8ZGl2IGlkPSJodG1sXzcwMGRmOTg4ZDgwYjQ2Njk5ZGFkODVmY2Y0MmRiMjg3IiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5EdWZmZXJpbiAvIERvdmVyY291cnQgVmlsbGFnZSwgV2VzdCBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF82MmFkOThiNTdlZjg0NjBhYTRjMjNjNzM5NzczMWQ5Ni5zZXRDb250ZW50KGh0bWxfNzAwZGY5ODhkODBiNDY2OTlkYWQ4NWZjZjQyZGIyODcpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfYzMwZDQ0ZTRjOThmNDM3NmJmNWQ0ZjI3ZTY5OGM0YTcuYmluZFBvcHVwKHBvcHVwXzYyYWQ5OGI1N2VmODQ2MGFhNGMyM2M3Mzk3NzMxZDk2KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzYyZmVjYjVmMDEyOTRhMjU4ZTZjYWJlYTMxNDQwMTFmID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjQwODE1NywtNzkuMzgxNzUyMjk5OTk5OTldLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDIsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfYzY3MDI2OTEyNmI0NGNiMDg0OTc4NTFkNzA2ZmMxMzIpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfOTU3NmQ0ZDcwYzk1NDM1Mjg2M2QyNGZkYjE3MzFhN2QgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfODY2ZDEzY2Q2NzEwNDZlNGFiNDlhYTg2ZWQyYTQ2ZWIgPSAkKCc8ZGl2IGlkPSJodG1sXzg2NmQxM2NkNjcxMDQ2ZTRhYjQ5YWE4NmVkMmE0NmViIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5IYXJib3VyZnJvbnQgRWFzdCAvIFVuaW9uIFN0YXRpb24gLyBUb3JvbnRvIElzbGFuZHMsIERvd250b3duIFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzk1NzZkNGQ3MGM5NTQzNTI4NjNkMjRmZGIxNzMxYTdkLnNldENvbnRlbnQoaHRtbF84NjZkMTNjZDY3MTA0NmU0YWI0OWFhODZlZDJhNDZlYik7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl82MmZlY2I1ZjAxMjk0YTI1OGU2Y2FiZWEzMTQ0MDExZi5iaW5kUG9wdXAocG9wdXBfOTU3NmQ0ZDcwYzk1NDM1Mjg2M2QyNGZkYjE3MzFhN2QpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfNzEyMDI0MzBmZTM3NGUzNmEyY2I1MDU5M2VjZTM4NzggPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NDc5MjY3MDAwMDAwMDYsLTc5LjQxOTc0OTddLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDIsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfYzY3MDI2OTEyNmI0NGNiMDg0OTc4NTFkNzA2ZmMxMzIpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfNjBjY2E5ZTRkMjQ5NDA0ZGE4ZDZkZjA2NWI1YjkzZDMgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfN2NhNzQ0OWQ0NWU2NDE3NTgyZGU3MjFiMmQ3NmJhZTAgPSAkKCc8ZGl2IGlkPSJodG1sXzdjYTc0NDlkNDVlNjQxNzU4MmRlNzIxYjJkNzZiYWUwIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5MaXR0bGUgUG9ydHVnYWwgLyBUcmluaXR5LCBXZXN0IFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzYwY2NhOWU0ZDI0OTQwNGRhOGQ2ZGYwNjViNWI5M2QzLnNldENvbnRlbnQoaHRtbF83Y2E3NDQ5ZDQ1ZTY0MTc1ODJkZTcyMWIyZDc2YmFlMCk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl83MTIwMjQzMGZlMzc0ZTM2YTJjYjUwNTkzZWNlMzg3OC5iaW5kUG9wdXAocG9wdXBfNjBjY2E5ZTRkMjQ5NDA0ZGE4ZDZkZjA2NWI1YjkzZDMpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfNGRjMmE4NjA4MWVhNDQwN2FlMzBhNmRkMzU3NTA4NDcgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42Nzk1NTcxLC03OS4zNTIxODhdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDIsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfYzY3MDI2OTEyNmI0NGNiMDg0OTc4NTFkNzA2ZmMxMzIpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfMDU1MTliMzMwODFlNDRiOWExOTU1ODVlYjg3NzczNDggPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfNGFjZWUwMTQwMWNjNDQ1NjkxNGE5Yzc2MDU0N2NkMDIgPSAkKCc8ZGl2IGlkPSJodG1sXzRhY2VlMDE0MDFjYzQ0NTY5MTRhOWM3NjA1NDdjZDAyIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5UaGUgRGFuZm9ydGggV2VzdCAvIFJpdmVyZGFsZSwgRWFzdCBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF8wNTUxOWIzMzA4MWU0NGI5YTE5NTU4NWViODc3NzM0OC5zZXRDb250ZW50KGh0bWxfNGFjZWUwMTQwMWNjNDQ1NjkxNGE5Yzc2MDU0N2NkMDIpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfNGRjMmE4NjA4MWVhNDQwN2FlMzBhNmRkMzU3NTA4NDcuYmluZFBvcHVwKHBvcHVwXzA1NTE5YjMzMDgxZTQ0YjlhMTk1NTg1ZWI4Nzc3MzQ4KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2ZhMWVhZDI3YmQxYjQyYTRiN2EyZjFmOTI4YTcyMThmID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjQ3MTc2OCwtNzkuMzgxNTc2NDAwMDAwMDFdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDIsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfYzY3MDI2OTEyNmI0NGNiMDg0OTc4NTFkNzA2ZmMxMzIpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfYmE2NGU0OTk3OTI5NDM5YjlmODk0N2NiMGFiM2Y0YzcgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfZTdkMGFlOWM1YmRiNGM5NmJiN2I3N2I5MjJlODBhNGYgPSAkKCc8ZGl2IGlkPSJodG1sX2U3ZDBhZTljNWJkYjRjOTZiYjdiNzdiOTIyZTgwYTRmIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5Ub3JvbnRvIERvbWluaW9uIENlbnRyZSAvIERlc2lnbiBFeGNoYW5nZSwgRG93bnRvd24gVG9yb250bzwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfYmE2NGU0OTk3OTI5NDM5YjlmODk0N2NiMGFiM2Y0Yzcuc2V0Q29udGVudChodG1sX2U3ZDBhZTljNWJkYjRjOTZiYjdiNzdiOTIyZTgwYTRmKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyX2ZhMWVhZDI3YmQxYjQyYTRiN2EyZjFmOTI4YTcyMThmLmJpbmRQb3B1cChwb3B1cF9iYTY0ZTQ5OTc5Mjk0MzliOWY4OTQ3Y2IwYWIzZjRjNyk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl84ZjBmNDY4ZmJiYTQ0Y2YwYjM4N2I0MzFjOTQ5NGQ2ZCA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjYzNjg0NzIsLTc5LjQyODE5MTQwMDAwMDAyXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogImJsdWUiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjMzE4NmNjIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiAyLAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2M2NzAyNjkxMjZiNDRjYjA4NDk3ODUxZDcwNmZjMTMyKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwX2Y1ZTliNzJiMmZjMDQyYmJhZjNlYWYwNDg5ZThlNWQ5ID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sX2VmMTBjNmM4YjYxZTQzYWJiN2NmNzU2ZWQ5MzRhNzI4ID0gJCgnPGRpdiBpZD0iaHRtbF9lZjEwYzZjOGI2MWU0M2FiYjdjZjc1NmVkOTM0YTcyOCIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+QnJvY2t0b24gLyBQYXJrZGFsZSBWaWxsYWdlIC8gRXhoaWJpdGlvbiBQbGFjZSwgV2VzdCBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9mNWU5YjcyYjJmYzA0MmJiYWYzZWFmMDQ4OWU4ZTVkOS5zZXRDb250ZW50KGh0bWxfZWYxMGM2YzhiNjFlNDNhYmI3Y2Y3NTZlZDkzNGE3MjgpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfOGYwZjQ2OGZiYmE0NGNmMGIzODdiNDMxYzk0OTRkNmQuYmluZFBvcHVwKHBvcHVwX2Y1ZTliNzJiMmZjMDQyYmJhZjNlYWYwNDg5ZThlNWQ5KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzY5ZDZkODcwMjNkMjRlNDZhZjZjZWRmOTZjM2JhOTQ4ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjY4OTk4NSwtNzkuMzE1NTcxNTk5OTk5OThdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDIsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfYzY3MDI2OTEyNmI0NGNiMDg0OTc4NTFkNzA2ZmMxMzIpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfMzkyNjFiOWFhMmU4NGQ0OGE0MjNlOGEzY2NjMGZkZWEgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfNjk2MDg0ZDNiN2VkNGIwNWIzMWYyODQ2NzMxNTE3ZGMgPSAkKCc8ZGl2IGlkPSJodG1sXzY5NjA4NGQzYjdlZDRiMDViMzFmMjg0NjczMTUxN2RjIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5JbmRpYSBCYXphYXIgLyBUaGUgQmVhY2hlcyBXZXN0LCBFYXN0IFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzM5MjYxYjlhYTJlODRkNDhhNDIzZThhM2NjYzBmZGVhLnNldENvbnRlbnQoaHRtbF82OTYwODRkM2I3ZWQ0YjA1YjMxZjI4NDY3MzE1MTdkYyk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl82OWQ2ZDg3MDIzZDI0ZTQ2YWY2Y2VkZjk2YzNiYTk0OC5iaW5kUG9wdXAocG9wdXBfMzkyNjFiOWFhMmU4NGQ0OGE0MjNlOGEzY2NjMGZkZWEpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfM2VjYmZmZmE3YWIxNDljZjg1NmI0YjAyNWJmYmVhNTIgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NDgxOTg1LC03OS4zNzk4MTY5MDAwMDAwMV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICJibHVlIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzMxODZjYyIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogMiwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF9jNjcwMjY5MTI2YjQ0Y2IwODQ5Nzg1MWQ3MDZmYzEzMik7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF8yNTgyMmIyYzllZDk0ZTcwOTQ0MTAwNjQ1ODVkZGNkMyA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF8xMmYxNDQyODNlNzQ0ZWM3YWUyYzhkZWU2N2RlZGFjYyA9ICQoJzxkaXYgaWQ9Imh0bWxfMTJmMTQ0MjgzZTc0NGVjN2FlMmM4ZGVlNjdkZWRhY2MiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkNvbW1lcmNlIENvdXJ0IC8gVmljdG9yaWEgSG90ZWwsIERvd250b3duIFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzI1ODIyYjJjOWVkOTRlNzA5NDQxMDA2NDU4NWRkY2QzLnNldENvbnRlbnQoaHRtbF8xMmYxNDQyODNlNzQ0ZWM3YWUyYzhkZWU2N2RlZGFjYyk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl8zZWNiZmZmYTdhYjE0OWNmODU2YjRiMDI1YmZiZWE1Mi5iaW5kUG9wdXAocG9wdXBfMjU4MjJiMmM5ZWQ5NGU3MDk0NDEwMDY0NTg1ZGRjZDMpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfZjRhODBiOWFhZTczNDA0OWEwNzUyZGE2ZjNkOGY5OTEgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NTk1MjU1LC03OS4zNDA5MjNdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDIsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfYzY3MDI2OTEyNmI0NGNiMDg0OTc4NTFkNzA2ZmMxMzIpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfMmI3YWI4ZWE1NTIyNGQ3YmFiODkzNTg0ZTlmYWRiNDkgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfZDBiMzVmMWY5MWVjNGM1MDg0OTkzNDMzODFkYTNhMGYgPSAkKCc8ZGl2IGlkPSJodG1sX2QwYjM1ZjFmOTFlYzRjNTA4NDk5MzQzMzgxZGEzYTBmIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5TdHVkaW8gRGlzdHJpY3QsIEVhc3QgVG9yb250bzwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfMmI3YWI4ZWE1NTIyNGQ3YmFiODkzNTg0ZTlmYWRiNDkuc2V0Q29udGVudChodG1sX2QwYjM1ZjFmOTFlYzRjNTA4NDk5MzQzMzgxZGEzYTBmKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyX2Y0YTgwYjlhYWU3MzQwNDlhMDc1MmRhNmYzZDhmOTkxLmJpbmRQb3B1cChwb3B1cF8yYjdhYjhlYTU1MjI0ZDdiYWI4OTM1ODRlOWZhZGI0OSk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl9hNWM5MzMyYmU1Zjc0MWZjOGY2YmFkNDc1MTQzNjUyMCA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjcyODAyMDUsLTc5LjM4ODc5MDFdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDIsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfYzY3MDI2OTEyNmI0NGNiMDg0OTc4NTFkNzA2ZmMxMzIpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfNDdmYzVkYzA0MWQ0NDJlMmE0N2MwZmViYzk4ZGQ4ZmMgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfMGM0NDY3YTAzZTVhNDBmYjkyNmYyM2ZkMmM0NTY3ZTcgPSAkKCc8ZGl2IGlkPSJodG1sXzBjNDQ2N2EwM2U1YTQwZmI5MjZmMjNmZDJjNDU2N2U3IiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5MYXdyZW5jZSBQYXJrLCBDZW50cmFsIFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzQ3ZmM1ZGMwNDFkNDQyZTJhNDdjMGZlYmM5OGRkOGZjLnNldENvbnRlbnQoaHRtbF8wYzQ0NjdhMDNlNWE0MGZiOTI2ZjIzZmQyYzQ1NjdlNyk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9hNWM5MzMyYmU1Zjc0MWZjOGY2YmFkNDc1MTQzNjUyMC5iaW5kUG9wdXAocG9wdXBfNDdmYzVkYzA0MWQ0NDJlMmE0N2MwZmViYzk4ZGQ4ZmMpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfMWQxMGJhYjBlZTg3NDM0MmI4OWJjYjk4ZjhlNWU2NTggPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My43MTE2OTQ4LC03OS40MTY5MzU1OTk5OTk5OV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICJibHVlIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzMxODZjYyIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogMiwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF9jNjcwMjY5MTI2YjQ0Y2IwODQ5Nzg1MWQ3MDZmYzEzMik7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9lOGQ4MThhMDA1MjY0OTZmYjAxZTgyNmZjMzg5YzU5ZCA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9hNWQzMzg5YjBjNTM0MzdmYTFjZTdmYThmZWQ4YjBlNSA9ICQoJzxkaXYgaWQ9Imh0bWxfYTVkMzM4OWIwYzUzNDM3ZmExY2U3ZmE4ZmVkOGIwZTUiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPlJvc2VsYXduLCBDZW50cmFsIFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2U4ZDgxOGEwMDUyNjQ5NmZiMDFlODI2ZmMzODljNTlkLnNldENvbnRlbnQoaHRtbF9hNWQzMzg5YjBjNTM0MzdmYTFjZTdmYThmZWQ4YjBlNSk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl8xZDEwYmFiMGVlODc0MzQyYjg5YmNiOThmOGU1ZTY1OC5iaW5kUG9wdXAocG9wdXBfZThkODE4YTAwNTI2NDk2ZmIwMWU4MjZmYzM4OWM1OWQpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfOWQ4Y2NkOTFhNzRjNDdhMDgwNDM2MTAwOTFhYTQxY2MgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My43MTI3NTExLC03OS4zOTAxOTc1XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogImJsdWUiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjMzE4NmNjIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiAyLAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2M2NzAyNjkxMjZiNDRjYjA4NDk3ODUxZDcwNmZjMTMyKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzJlMjQ0OGVmODEwYzQ1YjdiZTljMjE4YmVmYTVhZTY2ID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzU1NTY0MTZiZTk5YjQ5YTI4YjhjOWVmMzlkMTNhNTg1ID0gJCgnPGRpdiBpZD0iaHRtbF81NTU2NDE2YmU5OWI0OWEyOGI4YzllZjM5ZDEzYTU4NSIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+RGF2aXN2aWxsZSBOb3J0aCwgQ2VudHJhbCBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF8yZTI0NDhlZjgxMGM0NWI3YmU5YzIxOGJlZmE1YWU2Ni5zZXRDb250ZW50KGh0bWxfNTU1NjQxNmJlOTliNDlhMjhiOGM5ZWYzOWQxM2E1ODUpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfOWQ4Y2NkOTFhNzRjNDdhMDgwNDM2MTAwOTFhYTQxY2MuYmluZFBvcHVwKHBvcHVwXzJlMjQ0OGVmODEwYzQ1YjdiZTljMjE4YmVmYTVhZTY2KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzU3ZDdlZDIxM2JhZTQwMTFhYzVhNDZlODIyOWExYmY1ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjk2OTQ3NiwtNzkuNDExMzA3MjAwMDAwMDFdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDIsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfYzY3MDI2OTEyNmI0NGNiMDg0OTc4NTFkNzA2ZmMxMzIpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfMDY0ZjQ3ZTk2ZjI1NDYxZThlNzM5ZDlhMjg0ZTdjZGIgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfODJhNTFjOWM4MTQ3NDE0ODk5NDU2OWVhY2JlZDMyODggPSAkKCc8ZGl2IGlkPSJodG1sXzgyYTUxYzljODE0NzQxNDg5OTQ1NjllYWNiZWQzMjg4IiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5Gb3Jlc3QgSGlsbCBOb3J0aCAmYW1wOyBXZXN0LCBDZW50cmFsIFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzA2NGY0N2U5NmYyNTQ2MWU4ZTczOWQ5YTI4NGU3Y2RiLnNldENvbnRlbnQoaHRtbF84MmE1MWM5YzgxNDc0MTQ4OTk0NTY5ZWFjYmVkMzI4OCk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl81N2Q3ZWQyMTNiYWU0MDExYWM1YTQ2ZTgyMjlhMWJmNS5iaW5kUG9wdXAocG9wdXBfMDY0ZjQ3ZTk2ZjI1NDYxZThlNzM5ZDlhMjg0ZTdjZGIpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfMDIzOWJlNjJhMGQyNDM3YWEyZTQyNTBlOTA0N2M4OTAgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NjE2MDgzLC03OS40NjQ3NjMyOTk5OTk5OV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICJibHVlIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzMxODZjYyIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogMiwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF9jNjcwMjY5MTI2YjQ0Y2IwODQ5Nzg1MWQ3MDZmYzEzMik7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF8wYTUzMjAwYmMxN2E0Yjk0YjYzZWMwNjM4YWYyNGVlOSA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF82ZTQ3NWVmZWY3ZTk0MWIzODYyODA2YzMzYzUxMTZlOSA9ICQoJzxkaXYgaWQ9Imh0bWxfNmU0NzVlZmVmN2U5NDFiMzg2MjgwNmMzM2M1MTE2ZTkiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkhpZ2ggUGFyayAvIFRoZSBKdW5jdGlvbiBTb3V0aCwgV2VzdCBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF8wYTUzMjAwYmMxN2E0Yjk0YjYzZWMwNjM4YWYyNGVlOS5zZXRDb250ZW50KGh0bWxfNmU0NzVlZmVmN2U5NDFiMzg2MjgwNmMzM2M1MTE2ZTkpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfMDIzOWJlNjJhMGQyNDM3YWEyZTQyNTBlOTA0N2M4OTAuYmluZFBvcHVwKHBvcHVwXzBhNTMyMDBiYzE3YTRiOTRiNjNlYzA2MzhhZjI0ZWU5KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2RkYmI3OGMxM2M2YTRlZTg4NWM1OGIyNGUyNzQ4YmY3ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNzE1MzgzNCwtNzkuNDA1Njc4NDAwMDAwMDFdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDIsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfYzY3MDI2OTEyNmI0NGNiMDg0OTc4NTFkNzA2ZmMxMzIpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfZDIwZDRjNWVlNDAwNDBkOWE2MDQzYzE5M2ZjOGNkMDIgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfYTZjMjZmOTI3NzE3NDNjM2EzZTBkYTQyZGNhODk4ODMgPSAkKCc8ZGl2IGlkPSJodG1sX2E2YzI2ZjkyNzcxNzQzYzNhM2UwZGE0MmRjYTg5ODgzIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5Ob3J0aCBUb3JvbnRvIFdlc3QsIENlbnRyYWwgVG9yb250bzwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfZDIwZDRjNWVlNDAwNDBkOWE2MDQzYzE5M2ZjOGNkMDIuc2V0Q29udGVudChodG1sX2E2YzI2ZjkyNzcxNzQzYzNhM2UwZGE0MmRjYTg5ODgzKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyX2RkYmI3OGMxM2M2YTRlZTg4NWM1OGIyNGUyNzQ4YmY3LmJpbmRQb3B1cChwb3B1cF9kMjBkNGM1ZWU0MDA0MGQ5YTYwNDNjMTkzZmM4Y2QwMik7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl84MGY3Y2NhNzk1ODY0NDBlYTNhM2I4YjhmZmQ0ZGMxOCA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY3MjcwOTcsLTc5LjQwNTY3ODQwMDAwMDAxXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogImJsdWUiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjMzE4NmNjIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiAyLAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2M2NzAyNjkxMjZiNDRjYjA4NDk3ODUxZDcwNmZjMTMyKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzVlNDU0OGJkYjI1NjQzNTJhZDAyYWY0YWM1ZTEyOWIyID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sX2E4YjdmOGQyMjJmMzQ1MmI4MTEyNDIwZGI5ZjEyZTkyID0gJCgnPGRpdiBpZD0iaHRtbF9hOGI3ZjhkMjIyZjM0NTJiODExMjQyMGRiOWYxMmU5MiIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+VGhlIEFubmV4IC8gTm9ydGggTWlkdG93biAvIFlvcmt2aWxsZSwgQ2VudHJhbCBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF81ZTQ1NDhiZGIyNTY0MzUyYWQwMmFmNGFjNWUxMjliMi5zZXRDb250ZW50KGh0bWxfYThiN2Y4ZDIyMmYzNDUyYjgxMTI0MjBkYjlmMTJlOTIpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfODBmN2NjYTc5NTg2NDQwZWEzYTNiOGI4ZmZkNGRjMTguYmluZFBvcHVwKHBvcHVwXzVlNDU0OGJkYjI1NjQzNTJhZDAyYWY0YWM1ZTEyOWIyKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzhhZmEzNWE2MWU4NjRlMjBiMDVmN2ZiMGUwYjRlNmExID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjQ4OTU5NywtNzkuNDU2MzI1XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogImJsdWUiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjMzE4NmNjIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiAyLAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2M2NzAyNjkxMjZiNDRjYjA4NDk3ODUxZDcwNmZjMTMyKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwX2QwZDlkOGYwNjk5OTQ3OTViOTg1ODY2NGI5Mzc0Zjc1ID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sX2E0ZWZjMzFhYTNiNDQxY2VhNTkzMjZhMzJlYTY5MTg0ID0gJCgnPGRpdiBpZD0iaHRtbF9hNGVmYzMxYWEzYjQ0MWNlYTU5MzI2YTMyZWE2OTE4NCIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+UGFya2RhbGUgLyBSb25jZXN2YWxsZXMsIFdlc3QgVG9yb250bzwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfZDBkOWQ4ZjA2OTk5NDc5NWI5ODU4NjY0YjkzNzRmNzUuc2V0Q29udGVudChodG1sX2E0ZWZjMzFhYTNiNDQxY2VhNTkzMjZhMzJlYTY5MTg0KTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzhhZmEzNWE2MWU4NjRlMjBiMDVmN2ZiMGUwYjRlNmExLmJpbmRQb3B1cChwb3B1cF9kMGQ5ZDhmMDY5OTk0Nzk1Yjk4NTg2NjRiOTM3NGY3NSk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl81Nzk2NmJlNTllOGI0MWIxOTNlNDkzMGI0ZjMxMWY0OCA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjcwNDMyNDQsLTc5LjM4ODc5MDFdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDIsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfYzY3MDI2OTEyNmI0NGNiMDg0OTc4NTFkNzA2ZmMxMzIpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfZTRhMzE0NWRmNDhiNDg0ZGE3NDcyODQ2ZWNjMzcwNzMgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfM2Y5MTYxMTRiZjAwNDZmMzkwMWVlMmU1ZDBlZDU5MDEgPSAkKCc8ZGl2IGlkPSJodG1sXzNmOTE2MTE0YmYwMDQ2ZjM5MDFlZTJlNWQwZWQ1OTAxIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5EYXZpc3ZpbGxlLCBDZW50cmFsIFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2U0YTMxNDVkZjQ4YjQ4NGRhNzQ3Mjg0NmVjYzM3MDczLnNldENvbnRlbnQoaHRtbF8zZjkxNjExNGJmMDA0NmYzOTAxZWUyZTVkMGVkNTkwMSk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl81Nzk2NmJlNTllOGI0MWIxOTNlNDkzMGI0ZjMxMWY0OC5iaW5kUG9wdXAocG9wdXBfZTRhMzE0NWRmNDhiNDg0ZGE3NDcyODQ2ZWNjMzcwNzMpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfNGFiODYzZWEyNzU2NDU1NDk1NGEzYWY1Y2YwNDIxZmUgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NjI2OTU2LC03OS40MDAwNDkzXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogImJsdWUiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjMzE4NmNjIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiAyLAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2M2NzAyNjkxMjZiNDRjYjA4NDk3ODUxZDcwNmZjMTMyKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzUwZTlmOWE0M2I0NjRmMDZhNGM5ZDMxMzRmMzBhYjAwID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sX2NhMjBiODcxZTY2NTRhN2Y4MTNkOWRhOWMzZmI0MjRlID0gJCgnPGRpdiBpZD0iaHRtbF9jYTIwYjg3MWU2NjU0YTdmODEzZDlkYTljM2ZiNDI0ZSIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+VW5pdmVyc2l0eSBvZiBUb3JvbnRvIC8gSGFyYm9yZCwgRG93bnRvd24gVG9yb250bzwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfNTBlOWY5YTQzYjQ2NGYwNmE0YzlkMzEzNGYzMGFiMDAuc2V0Q29udGVudChodG1sX2NhMjBiODcxZTY2NTRhN2Y4MTNkOWRhOWMzZmI0MjRlKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzRhYjg2M2VhMjc1NjQ1NTQ5NTRhM2FmNWNmMDQyMWZlLmJpbmRQb3B1cChwb3B1cF81MGU5ZjlhNDNiNDY0ZjA2YTRjOWQzMTM0ZjMwYWIwMCk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl83NTUxYTNkOWIxMjU0M2RmOTdiODY2ODI5YzQ3ODk0MyA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY1MTU3MDYsLTc5LjQ4NDQ0OTldLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDIsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfYzY3MDI2OTEyNmI0NGNiMDg0OTc4NTFkNzA2ZmMxMzIpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfMGE1MzBiZDkwODUwNDczYWI0OGY1OGVlYWU1NWU2NTUgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfNDkzYmJhMTEyNjI2NDdiZGI2ODJjMTkyYmMzMDlkYTAgPSAkKCc8ZGl2IGlkPSJodG1sXzQ5M2JiYTExMjYyNjQ3YmRiNjgyYzE5MmJjMzA5ZGEwIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5SdW5ueW1lZGUgLyBTd2Fuc2VhLCBXZXN0IFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzBhNTMwYmQ5MDg1MDQ3M2FiNDhmNThlZWFlNTVlNjU1LnNldENvbnRlbnQoaHRtbF80OTNiYmExMTI2MjY0N2JkYjY4MmMxOTJiYzMwOWRhMCk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl83NTUxYTNkOWIxMjU0M2RmOTdiODY2ODI5YzQ3ODk0My5iaW5kUG9wdXAocG9wdXBfMGE1MzBiZDkwODUwNDczYWI0OGY1OGVlYWU1NWU2NTUpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfNGQ4OWNmYmMzNmU0NGNkOTlkNGEwNmIxNDRiZjJjNWIgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42ODk1NzQzLC03OS4zODMxNTk5MDAwMDAwMV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICJibHVlIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzMxODZjYyIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogMiwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF9jNjcwMjY5MTI2YjQ0Y2IwODQ5Nzg1MWQ3MDZmYzEzMik7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF80MmYxMjU1ODMyNTE0YTA5OWNlMWVlNTZkZjM3YTkzOCA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF84YTBkOGZmZGNhMGU0MDQ0YTllNDZmYmQ1Y2EwMDAxYSA9ICQoJzxkaXYgaWQ9Imh0bWxfOGEwZDhmZmRjYTBlNDA0NGE5ZTQ2ZmJkNWNhMDAwMWEiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPk1vb3JlIFBhcmsgLyBTdW1tZXJoaWxsIEVhc3QsIENlbnRyYWwgVG9yb250bzwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfNDJmMTI1NTgzMjUxNGEwOTljZTFlZTU2ZGYzN2E5Mzguc2V0Q29udGVudChodG1sXzhhMGQ4ZmZkY2EwZTQwNDRhOWU0NmZiZDVjYTAwMDFhKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzRkODljZmJjMzZlNDRjZDk5ZDRhMDZiMTQ0YmYyYzViLmJpbmRQb3B1cChwb3B1cF80MmYxMjU1ODMyNTE0YTA5OWNlMWVlNTZkZjM3YTkzOCk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl8wMDZkZWNlNGYwMzU0ZTdiYTI0MTBhY2QxZjc3OTBjZSA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY1MzIwNTcsLTc5LjQwMDA0OTNdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDIsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfYzY3MDI2OTEyNmI0NGNiMDg0OTc4NTFkNzA2ZmMxMzIpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfNTI4OTY4ZWNiNzdmNDZjYzgwZDc3ZTY2NDI2NzAzMzkgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfNTk0Mjk1ODc2ODEzNDQ5Yjk5OGUzNWZkMTE2MmQ2MDQgPSAkKCc8ZGl2IGlkPSJodG1sXzU5NDI5NTg3NjgxMzQ0OWI5OThlMzVmZDExNjJkNjA0IiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5LZW5zaW5ndG9uIE1hcmtldCAvIENoaW5hdG93biAvIEdyYW5nZSBQYXJrLCBEb3dudG93biBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF81Mjg5NjhlY2I3N2Y0NmNjODBkNzdlNjY0MjY3MDMzOS5zZXRDb250ZW50KGh0bWxfNTk0Mjk1ODc2ODEzNDQ5Yjk5OGUzNWZkMTE2MmQ2MDQpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfMDA2ZGVjZTRmMDM1NGU3YmEyNDEwYWNkMWY3NzkwY2UuYmluZFBvcHVwKHBvcHVwXzUyODk2OGVjYjc3ZjQ2Y2M4MGQ3N2U2NjQyNjcwMzM5KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2Y0ZDQ5YmRkMGVhMDRmYTE5ZGExYjU1ZDFjZDAzZjNlID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjg2NDEyMjk5OTk5OTksLTc5LjQwMDA0OTNdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDIsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfYzY3MDI2OTEyNmI0NGNiMDg0OTc4NTFkNzA2ZmMxMzIpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfYTlhNzZmNmJmZmYxNDFlN2E1MmRlYTZmYmU2NTJjZGIgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfMmM0MjRlMmY0MzlhNGE1Yzg3OTExYzc4ZDEwMjJhMDYgPSAkKCc8ZGl2IGlkPSJodG1sXzJjNDI0ZTJmNDM5YTRhNWM4NzkxMWM3OGQxMDIyYTA2IiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5TdW1tZXJoaWxsIFdlc3QgLyBSYXRobmVsbHkgLyBTb3V0aCBIaWxsIC8gRm9yZXN0IEhpbGwgU0UgLyBEZWVyIFBhcmssIENlbnRyYWwgVG9yb250bzwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfYTlhNzZmNmJmZmYxNDFlN2E1MmRlYTZmYmU2NTJjZGIuc2V0Q29udGVudChodG1sXzJjNDI0ZTJmNDM5YTRhNWM4NzkxMWM3OGQxMDIyYTA2KTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyX2Y0ZDQ5YmRkMGVhMDRmYTE5ZGExYjU1ZDFjZDAzZjNlLmJpbmRQb3B1cChwb3B1cF9hOWE3NmY2YmZmZjE0MWU3YTUyZGVhNmZiZTY1MmNkYik7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl81M2FhMTYzNGFjNjk0ZWUzYTdiNzAxNDRkYWUxZTQ5OSA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjYyODk0NjcsLTc5LjM5NDQxOTldLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDIsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfYzY3MDI2OTEyNmI0NGNiMDg0OTc4NTFkNzA2ZmMxMzIpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfZDYzYjMwY2ZiYmM0NDE4ZWI3ZTFkZmUxY2I1ZjUzZjQgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfODAyNTE1YjMwZjRiNDQzZjllYTY3ODg5OGIxNjc0OTggPSAkKCc8ZGl2IGlkPSJodG1sXzgwMjUxNWIzMGY0YjQ0M2Y5ZWE2Nzg4OThiMTY3NDk4IiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5DTiBUb3dlciAvIEtpbmcgYW5kIFNwYWRpbmEgLyBSYWlsd2F5IExhbmRzIC8gSGFyYm91cmZyb250IFdlc3QgLyBCYXRodXJzdCAgUXVheSAvIFNvdXRoIE5pYWdhcmEgLyBJc2xhbmQgYWlycG9ydCwgRG93bnRvd24gVG9yb250bzwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfZDYzYjMwY2ZiYmM0NDE4ZWI3ZTFkZmUxY2I1ZjUzZjQuc2V0Q29udGVudChodG1sXzgwMjUxNWIzMGY0YjQ0M2Y5ZWE2Nzg4OThiMTY3NDk4KTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzUzYWExNjM0YWM2OTRlZTNhN2I3MDE0NGRhZTFlNDk5LmJpbmRQb3B1cChwb3B1cF9kNjNiMzBjZmJiYzQ0MThlYjdlMWRmZTFjYjVmNTNmNCk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl9jMDQzZDg2ZTM3MTQ0YzhhYjI3YjIzMjAyNjFhMTNiMSA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY3OTU2MjYsLTc5LjM3NzUyOTQwMDAwMDAxXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogImJsdWUiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjMzE4NmNjIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiAyLAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2M2NzAyNjkxMjZiNDRjYjA4NDk3ODUxZDcwNmZjMTMyKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzExZWYwZDhlMTBjNDQ5ZTk4ZmQzMTVkYzJlYWQ5MDYwID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzczZWUwNzdkMjZjYjQ2YjdiOWU2YWE1OWQ1ZjMyYmIzID0gJCgnPGRpdiBpZD0iaHRtbF83M2VlMDc3ZDI2Y2I0NmI3YjllNmFhNTlkNWYzMmJiMyIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+Um9zZWRhbGUsIERvd250b3duIFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzExZWYwZDhlMTBjNDQ5ZTk4ZmQzMTVkYzJlYWQ5MDYwLnNldENvbnRlbnQoaHRtbF83M2VlMDc3ZDI2Y2I0NmI3YjllNmFhNTlkNWYzMmJiMyk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9jMDQzZDg2ZTM3MTQ0YzhhYjI3YjIzMjAyNjFhMTNiMS5iaW5kUG9wdXAocG9wdXBfMTFlZjBkOGUxMGM0NDllOThmZDMxNWRjMmVhZDkwNjApOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfODY2ZjU3MWI3Yzg1NDE2NDhmZTgwMDY4ODk1OTY2MWIgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NDY0MzUyLC03OS4zNzQ4NDU5OTk5OTk5OV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICJibHVlIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzMxODZjYyIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogMiwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF9jNjcwMjY5MTI2YjQ0Y2IwODQ5Nzg1MWQ3MDZmYzEzMik7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9mYTljYmEzNDhlYzA0MjFmYmExNWZkOTdiMDA3ZGU5ZSA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF82ZWU4NDFiOTU4MzQ0MmZiODU1ZGRiMGEyNDc2NmI0ZCA9ICQoJzxkaXYgaWQ9Imh0bWxfNmVlODQxYjk1ODM0NDJmYjg1NWRkYjBhMjQ3NjZiNGQiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPlN0biBBIFBPIEJveGVzLCBEb3dudG93biBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9mYTljYmEzNDhlYzA0MjFmYmExNWZkOTdiMDA3ZGU5ZS5zZXRDb250ZW50KGh0bWxfNmVlODQxYjk1ODM0NDJmYjg1NWRkYjBhMjQ3NjZiNGQpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfODY2ZjU3MWI3Yzg1NDE2NDhmZTgwMDY4ODk1OTY2MWIuYmluZFBvcHVwKHBvcHVwX2ZhOWNiYTM0OGVjMDQyMWZiYTE1ZmQ5N2IwMDdkZTllKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2Q1Njk1N2MzNzVmODRkMWNiODYwMWUxMTVkYjg2M2Y2ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjY3OTY3LC03OS4zNjc2NzUzXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogImJsdWUiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjMzE4NmNjIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiAyLAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2M2NzAyNjkxMjZiNDRjYjA4NDk3ODUxZDcwNmZjMTMyKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzQ2ZjFiZjY3ZjIyMDRlNzNhOGE0NTBhNGI5ZTdhZDQyID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzdjYWEwMTVmZDAyNzQ2MTM4ZTM4YjdkN2QyZmI0NTZmID0gJCgnPGRpdiBpZD0iaHRtbF83Y2FhMDE1ZmQwMjc0NjEzOGUzOGI3ZDdkMmZiNDU2ZiIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+U3QuIEphbWVzIFRvd24gLyBDYWJiYWdldG93biwgRG93bnRvd24gVG9yb250bzwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfNDZmMWJmNjdmMjIwNGU3M2E4YTQ1MGE0YjllN2FkNDIuc2V0Q29udGVudChodG1sXzdjYWEwMTVmZDAyNzQ2MTM4ZTM4YjdkN2QyZmI0NTZmKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyX2Q1Njk1N2MzNzVmODRkMWNiODYwMWUxMTVkYjg2M2Y2LmJpbmRQb3B1cChwb3B1cF80NmYxYmY2N2YyMjA0ZTczYThhNDUwYTRiOWU3YWQ0Mik7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl80ZTY0OTMxZDk5MGM0NjkwYjAyNjZjM2NhODYwYjM3MiA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY0ODQyOTIsLTc5LjM4MjI4MDJdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDIsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfYzY3MDI2OTEyNmI0NGNiMDg0OTc4NTFkNzA2ZmMxMzIpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfOWIyOGQ1OGY4YzU0NDQ2MmE3ZTJiZDY1MjJiZjg2MGEgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfMjBmOGE3MDQ3YzYyNGVkODhkOTA5MDg1YjdkOTQwNjkgPSAkKCc8ZGl2IGlkPSJodG1sXzIwZjhhNzA0N2M2MjRlZDg4ZDkwOTA4NWI3ZDk0MDY5IiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5GaXJzdCBDYW5hZGlhbiBQbGFjZSAvIFVuZGVyZ3JvdW5kIGNpdHksIERvd250b3duIFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzliMjhkNThmOGM1NDQ0NjJhN2UyYmQ2NTIyYmY4NjBhLnNldENvbnRlbnQoaHRtbF8yMGY4YTcwNDdjNjI0ZWQ4OGQ5MDkwODViN2Q5NDA2OSk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl80ZTY0OTMxZDk5MGM0NjkwYjAyNjZjM2NhODYwYjM3Mi5iaW5kUG9wdXAocG9wdXBfOWIyOGQ1OGY4YzU0NDQ2MmE3ZTJiZDY1MjJiZjg2MGEpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfNzViYzc0NDExZjU4NGFmZjlhYTIyYTVlNjZlMjFhNmMgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NjU4NTk5LC03OS4zODMxNTk5MDAwMDAwMV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICJibHVlIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzMxODZjYyIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogMiwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF9jNjcwMjY5MTI2YjQ0Y2IwODQ5Nzg1MWQ3MDZmYzEzMik7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF84NGJhMTg4YzM5OTA0ZmUxOTU4ODFlZTNjMTVhNzRlZiA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF82MGRmNTY3MmVmMjc0ZGI4YmM1ZmYyYjQ1NGQ0MGQyNiA9ICQoJzxkaXYgaWQ9Imh0bWxfNjBkZjU2NzJlZjI3NGRiOGJjNWZmMmI0NTRkNDBkMjYiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkNodXJjaCBhbmQgV2VsbGVzbGV5LCBEb3dudG93biBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF84NGJhMTg4YzM5OTA0ZmUxOTU4ODFlZTNjMTVhNzRlZi5zZXRDb250ZW50KGh0bWxfNjBkZjU2NzJlZjI3NGRiOGJjNWZmMmI0NTRkNDBkMjYpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfNzViYzc0NDExZjU4NGFmZjlhYTIyYTVlNjZlMjFhNmMuYmluZFBvcHVwKHBvcHVwXzg0YmExODhjMzk5MDRmZTE5NTg4MWVlM2MxNWE3NGVmKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzAwZWUzYjlhMzQyYTQ5MTNiOTg4N2Y4YzI0Yzc3Y2I5ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjYyNzQzOSwtNzkuMzIxNTU4XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogImJsdWUiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjMzE4NmNjIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiAyLAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2M2NzAyNjkxMjZiNDRjYjA4NDk3ODUxZDcwNmZjMTMyKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwX2UwZDc3MWVjNTljMDRkOTE4MjhhNzQzODM2NmFlYjk2ID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzUyMjU1N2MyNjE4NzRlZTg4MTlhNDMzOTJhZDdhOTliID0gJCgnPGRpdiBpZD0iaHRtbF81MjI1NTdjMjYxODc0ZWU4ODE5YTQzMzkyYWQ3YTk5YiIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+QnVzaW5lc3MgcmVwbHkgbWFpbCBQcm9jZXNzaW5nIENlbnRyRSwgRWFzdCBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9lMGQ3NzFlYzU5YzA0ZDkxODI4YTc0MzgzNjZhZWI5Ni5zZXRDb250ZW50KGh0bWxfNTIyNTU3YzI2MTg3NGVlODgxOWE0MzM5MmFkN2E5OWIpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfMDBlZTNiOWEzNDJhNDkxM2I5ODg3ZjhjMjRjNzdjYjkuYmluZFBvcHVwKHBvcHVwX2UwZDc3MWVjNTljMDRkOTE4MjhhNzQzODM2NmFlYjk2KTsKCiAgICAgICAgICAgIAogICAgICAgIAo8L3NjcmlwdD4= onload=\"this.contentDocument.open();this.contentDocument.write(atob(this.getAttribute('data-html')));this.contentDocument.close();\" allowfullscreen webkitallowfullscreen mozallowfullscreen></iframe></div></div>"
],
"text/plain": [
"<folium.folium.Map at 0x7fa747f26a58>"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"map_toronto = folium.Map(location=[43.6534817,-79.3839347],zoom_start=12)\n",
"\n",
"for lat,lng,borough,neighborhood in zip(Toronto_df['Latitude'],Toronto_df['Longitude'],Toronto_df['Borough'],Toronto_df['Neighborhood']):\n",
" label = '{}, {}'.format(neighborhood, borough)\n",
" label = folium.Popup(label, parse_html=True)\n",
" folium.CircleMarker(\n",
" [lat,lng],\n",
" radius=2,\n",
" popup=label,\n",
" color='blue',\n",
" fill=True,\n",
" fill_color='#3186cc',\n",
" fill_opacity=0.7,\n",
" parse_html=False).add_to(map_toronto)\n",
"map_toronto"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Generate Maps to visualize Neighborhoods and how they go together"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, we are going to start utilizing the Foursquare API to explore the neighborhoods and segment them."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Define Foursquare Credentials and Version"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"button": false,
"collapsed": false,
"jupyter": {
"outputs_hidden": false
},
"new_sheet": false,
"run_control": {
"read_only": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Your credentails:\n",
"CLIENT_ID: LAUAGY5VQH2DJ4VUXN4OXSNGEGCKT0TLXSDASO4FL1XB4SES\n",
"CLIENT_SECRET:D3CTWFSGB2D5XW0DQ02ZB2VGVOI2IZMI0ISACKDSVCL0MEV2\n"
]
}
],
"source": [
"CLIENT_ID = 'LAUAGY5VQH2DJ4VUXN4OXSNGEGCKT0TLXSDASO4FL1XB4SES' # your Foursquare ID\n",
"CLIENT_SECRET = 'D3CTWFSGB2D5XW0DQ02ZB2VGVOI2IZMI0ISACKDSVCL0MEV2' # your Foursquare Secret\n",
"VERSION = '20180605'\n",
"\n",
"print('Your credentails:')\n",
"print('CLIENT_ID: ' + CLIENT_ID)\n",
"print('CLIENT_SECRET:' + CLIENT_SECRET)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Let's explore the neighborhood of Toronto\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"get venues"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [],
"source": [
"LIMIT = 100 # limit of number of venues returned by Foursquare API\n",
"radius = 500 # define radius"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'https://api.foursquare.com/v2/venues/explore?&client_id=LAUAGY5VQH2DJ4VUXN4OXSNGEGCKT0TLXSDASO4FL1XB4SES&client_secret=D3CTWFSGB2D5XW0DQ02ZB2VGVOI2IZMI0ISACKDSVCL0MEV2&v=20180605&ll=43.6534817,-79.3839347&radius=500&limit=100'"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# create URL\n",
"url = 'https://api.foursquare.com/v2/venues/explore?&client_id={}&client_secret={}&v={}&ll={},{}&radius={}&limit={}'.format(\n",
" CLIENT_ID, \n",
" CLIENT_SECRET, \n",
" VERSION, \n",
" latitude, \n",
" longitude, \n",
" radius, \n",
" LIMIT)\n",
"url # display URL\n",
" "
]
},
{
"cell_type": "markdown",
"metadata": {
"jupyter": {
"outputs_hidden": false
}
},
"source": [
"send Get request to examn the results"
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"results = requests.get(url).json()\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"from the foursquare model in previous example we know that information is in item's key. Lets borrow the get_category_type function from foresquare."
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [],
"source": [
"# function that extracts the category of the venue\n",
"def get_category_type(row):\n",
" try:\n",
" categories_list = row['categories']\n",
" except:\n",
" categories_list = row['venue.categories']\n",
" \n",
" if len(categories_list) == 0:\n",
" return None\n",
" else:\n",
" return categories_list[0]['name']"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Clean Data in JSon File put it in panda dataframe "
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/home/jupyterlab/conda/envs/python/lib/python3.6/site-packages/ipykernel_launcher.py:3: FutureWarning: pandas.io.json.json_normalize is deprecated, use pandas.json_normalize instead\n",
" This is separate from the ipykernel package so we can avoid doing imports until\n"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>name</th>\n",
" <th>categories</th>\n",
" <th>lat</th>\n",
" <th>lng</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>Downtown Toronto</td>\n",
" <td>Neighborhood</td>\n",
" <td>43.653232</td>\n",
" <td>-79.385296</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>Nathan Phillips Square</td>\n",
" <td>Plaza</td>\n",
" <td>43.652270</td>\n",
" <td>-79.383516</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>Eggspectation Bell Trinity Square</td>\n",
" <td>Breakfast Spot</td>\n",
" <td>43.653144</td>\n",
" <td>-79.381980</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>Japango</td>\n",
" <td>Sushi Restaurant</td>\n",
" <td>43.655268</td>\n",
" <td>-79.385165</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>Indigo</td>\n",
" <td>Bookstore</td>\n",
" <td>43.653515</td>\n",
" <td>-79.380696</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" name categories lat lng\n",
"0 Downtown Toronto Neighborhood 43.653232 -79.385296\n",
"1 Nathan Phillips Square Plaza 43.652270 -79.383516\n",
"2 Eggspectation Bell Trinity Square Breakfast Spot 43.653144 -79.381980\n",
"3 Japango Sushi Restaurant 43.655268 -79.385165\n",
"4 Indigo Bookstore 43.653515 -79.380696"
]
},
"execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"venues = results['response']['groups'][0]['items']\n",
" \n",
"nearby_venues = json_normalize(venues) # flatten JSON\n",
"\n",
"# filter columns\n",
"filtered_columns = ['venue.name', 'venue.categories', 'venue.location.lat', 'venue.location.lng']\n",
"nearby_venues =nearby_venues.loc[:, filtered_columns]\n",
"\n",
"# filter the category for each row\n",
"nearby_venues['venue.categories'] = nearby_venues.apply(get_category_type, axis=1)\n",
"\n",
"# clean columns\n",
"nearby_venues.columns = [col.split(\".\")[-1] for col in nearby_venues.columns]\n",
"\n",
"nearby_venues.head()"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"78 venues were returned by Foursquare.\n"
]
}
],
"source": [
"print('{} venues were returned by Foursquare.'.format(nearby_venues.shape[0]))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Lets check the size of the resulting dataframe"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"make new data frame "
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {},
"outputs": [],
"source": [
"def getNearbyVenues(names, latitudes, longitudes, radius=500):\n",
" \n",
" venues_list=[]\n",
" for name, lat, lng in zip(names, latitudes, longitudes):\n",
" print(name)\n",
" \n",
" # create the API request URL\n",
" url = 'https://api.foursquare.com/v2/venues/explore?&client_id={}&client_secret={}&v={}&ll={},{}&radius={}&limit={}'.format(\n",
" CLIENT_ID, \n",
" CLIENT_SECRET, \n",
" VERSION, \n",
" lat, \n",
" lng, \n",
" radius, \n",
" LIMIT)\n",
" \n",
" # make the GET request\n",
" results = requests.get(url).json()[\"response\"]['groups'][0]['items']\n",
" \n",
" # return only relevant information for each nearby venue\n",
" venues_list.append([(\n",
" name, \n",
" lat, \n",
" lng, \n",
" v['venue']['name'], \n",
" v['venue']['location']['lat'], \n",
" v['venue']['location']['lng'], \n",
" v['venue']['categories'][0]['name']) for v in results])\n",
"\n",
" nearby_venues = pd.DataFrame([item for venue_list in venues_list for item in venue_list])\n",
" nearby_venues.columns = ['Neighborhood', \n",
" 'Neighborhood Latitude', \n",
" 'Neighborhood Longitude', \n",
" 'Venue', \n",
" 'Venue Latitude', \n",
" 'Venue Longitude', \n",
" 'Venue Category']\n",
" \n",
" return(nearby_venues)"
]
},
{
"cell_type": "code",
"execution_count": 39,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
},
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Regent Park / Harbourfront\n",
"Queen's Park / Ontario Provincial Government\n",
"Garden District, Ryerson\n",
"St. James Town\n",
"The Beaches\n",
"Berczy Park\n",
"Central Bay Street\n",
"Christie\n",
"Richmond / Adelaide / King\n",
"Dufferin / Dovercourt Village\n",
"Harbourfront East / Union Station / Toronto Islands\n",
"Little Portugal / Trinity\n",
"The Danforth West / Riverdale\n",
"Toronto Dominion Centre / Design Exchange\n",
"Brockton / Parkdale Village / Exhibition Place\n",
"India Bazaar / The Beaches West\n",
"Commerce Court / Victoria Hotel\n",
"Studio District\n",
"Lawrence Park\n",
"Roselawn\n",
"Davisville North\n",
"Forest Hill North & West\n",
"High Park / The Junction South\n",
"North Toronto West\n",
"The Annex / North Midtown / Yorkville\n",
"Parkdale / Roncesvalles\n",
"Davisville\n",
"University of Toronto / Harbord\n",
"Runnymede / Swansea\n",
"Moore Park / Summerhill East\n",
"Kensington Market / Chinatown / Grange Park\n",
"Summerhill West / Rathnelly / South Hill / Forest Hill SE / Deer Park\n",
"CN Tower / King and Spadina / Railway Lands / Harbourfront West / Bathurst Quay / South Niagara / Island airport\n",
"Rosedale\n",
"Stn A PO Boxes\n",
"St. James Town / Cabbagetown\n",
"First Canadian Place / Underground city\n",
"Church and Wellesley\n",
"Business reply mail Processing CentrE\n"
]
}
],
"source": [
"Toronto_venues = getNearbyVenues(names=Toronto_df['Neighborhood'],\n",
" latitudes=Toronto_df['Latitude'],\n",
" longitudes=Toronto_df['Longitude']\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Neighborhood</th>\n",
" <th>Neighborhood Latitude</th>\n",
" <th>Neighborhood Longitude</th>\n",
" <th>Venue</th>\n",
" <th>Venue Latitude</th>\n",
" <th>Venue Longitude</th>\n",
" <th>Venue Category</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>Regent Park / Harbourfront</td>\n",
" <td>43.65426</td>\n",
" <td>-79.360636</td>\n",
" <td>Roselle Desserts</td>\n",
" <td>43.653447</td>\n",
" <td>-79.362017</td>\n",
" <td>Bakery</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>Regent Park / Harbourfront</td>\n",
" <td>43.65426</td>\n",
" <td>-79.360636</td>\n",
" <td>Tandem Coffee</td>\n",
" <td>43.653559</td>\n",
" <td>-79.361809</td>\n",
" <td>Coffee Shop</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>Regent Park / Harbourfront</td>\n",
" <td>43.65426</td>\n",
" <td>-79.360636</td>\n",
" <td>Cooper Koo Family YMCA</td>\n",
" <td>43.653249</td>\n",
" <td>-79.358008</td>\n",
" <td>Distribution Center</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>Regent Park / Harbourfront</td>\n",
" <td>43.65426</td>\n",
" <td>-79.360636</td>\n",
" <td>Body Blitz Spa East</td>\n",
" <td>43.654735</td>\n",
" <td>-79.359874</td>\n",
" <td>Spa</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>Regent Park / Harbourfront</td>\n",
" <td>43.65426</td>\n",
" <td>-79.360636</td>\n",
" <td>Morning Glory Cafe</td>\n",
" <td>43.653947</td>\n",
" <td>-79.361149</td>\n",
" <td>Breakfast Spot</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Neighborhood Neighborhood Latitude Neighborhood Longitude \\\n",
"0 Regent Park / Harbourfront 43.65426 -79.360636 \n",
"1 Regent Park / Harbourfront 43.65426 -79.360636 \n",
"2 Regent Park / Harbourfront 43.65426 -79.360636 \n",
"3 Regent Park / Harbourfront 43.65426 -79.360636 \n",
"4 Regent Park / Harbourfront 43.65426 -79.360636 \n",
"\n",
" Venue Venue Latitude Venue Longitude \\\n",
"0 Roselle Desserts 43.653447 -79.362017 \n",
"1 Tandem Coffee 43.653559 -79.361809 \n",
"2 Cooper Koo Family YMCA 43.653249 -79.358008 \n",
"3 Body Blitz Spa East 43.654735 -79.359874 \n",
"4 Morning Glory Cafe 43.653947 -79.361149 \n",
"\n",
" Venue Category \n",
"0 Bakery \n",
"1 Coffee Shop \n",
"2 Distribution Center \n",
"3 Spa \n",
"4 Breakfast Spot "
]
},
"execution_count": 41,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Toronto_venues.head()"
]
},
{
"cell_type": "code",
"execution_count": 44,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Neighborhood Latitude</th>\n",
" <th>Neighborhood Longitude</th>\n",
" <th>Venue</th>\n",
" <th>Venue Latitude</th>\n",
" <th>Venue Longitude</th>\n",
" <th>Venue Category</th>\n",
" </tr>\n",
" <tr>\n",
" <th>Neighborhood</th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>Berczy Park</th>\n",
" <td>55</td>\n",
" <td>55</td>\n",
" <td>55</td>\n",
" <td>55</td>\n",
" <td>55</td>\n",
" <td>55</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Brockton / Parkdale Village / Exhibition Place</th>\n",
" <td>24</td>\n",
" <td>24</td>\n",
" <td>24</td>\n",
" <td>24</td>\n",
" <td>24</td>\n",
" <td>24</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Business reply mail Processing CentrE</th>\n",
" <td>19</td>\n",
" <td>19</td>\n",
" <td>19</td>\n",
" <td>19</td>\n",
" <td>19</td>\n",
" <td>19</td>\n",
" </tr>\n",
" <tr>\n",
" <th>CN Tower / King and Spadina / Railway Lands / Harbourfront West / Bathurst Quay / South Niagara / Island airport</th>\n",
" <td>17</td>\n",
" <td>17</td>\n",
" <td>17</td>\n",
" <td>17</td>\n",
" <td>17</td>\n",
" <td>17</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Central Bay Street</th>\n",
" <td>65</td>\n",
" <td>65</td>\n",
" <td>65</td>\n",
" <td>65</td>\n",
" <td>65</td>\n",
" <td>65</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Christie</th>\n",
" <td>17</td>\n",
" <td>17</td>\n",
" <td>17</td>\n",
" <td>17</td>\n",
" <td>17</td>\n",
" <td>17</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Church and Wellesley</th>\n",
" <td>74</td>\n",
" <td>74</td>\n",
" <td>74</td>\n",
" <td>74</td>\n",
" <td>74</td>\n",
" <td>74</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Commerce Court / Victoria Hotel</th>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Davisville</th>\n",
" <td>34</td>\n",
" <td>34</td>\n",
" <td>34</td>\n",
" <td>34</td>\n",
" <td>34</td>\n",
" <td>34</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Davisville North</th>\n",
" <td>11</td>\n",
" <td>11</td>\n",
" <td>11</td>\n",
" <td>11</td>\n",
" <td>11</td>\n",
" <td>11</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Dufferin / Dovercourt Village</th>\n",
" <td>15</td>\n",
" <td>15</td>\n",
" <td>15</td>\n",
" <td>15</td>\n",
" <td>15</td>\n",
" <td>15</td>\n",
" </tr>\n",
" <tr>\n",
" <th>First Canadian Place / Underground city</th>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Forest Hill North &amp; West</th>\n",
" <td>4</td>\n",
" <td>4</td>\n",
" <td>4</td>\n",
" <td>4</td>\n",
" <td>4</td>\n",
" <td>4</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Garden District, Ryerson</th>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Harbourfront East / Union Station / Toronto Islands</th>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" </tr>\n",
" <tr>\n",
" <th>High Park / The Junction South</th>\n",
" <td>25</td>\n",
" <td>25</td>\n",
" <td>25</td>\n",
" <td>25</td>\n",
" <td>25</td>\n",
" <td>25</td>\n",
" </tr>\n",
" <tr>\n",
" <th>India Bazaar / The Beaches West</th>\n",
" <td>20</td>\n",
" <td>20</td>\n",
" <td>20</td>\n",
" <td>20</td>\n",
" <td>20</td>\n",
" <td>20</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Kensington Market / Chinatown / Grange Park</th>\n",
" <td>62</td>\n",
" <td>62</td>\n",
" <td>62</td>\n",
" <td>62</td>\n",
" <td>62</td>\n",
" <td>62</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Lawrence Park</th>\n",
" <td>3</td>\n",
" <td>3</td>\n",
" <td>3</td>\n",
" <td>3</td>\n",
" <td>3</td>\n",
" <td>3</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Little Portugal / Trinity</th>\n",
" <td>43</td>\n",
" <td>43</td>\n",
" <td>43</td>\n",
" <td>43</td>\n",
" <td>43</td>\n",
" <td>43</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Moore Park / Summerhill East</th>\n",
" <td>3</td>\n",
" <td>3</td>\n",
" <td>3</td>\n",
" <td>3</td>\n",
" <td>3</td>\n",
" <td>3</td>\n",
" </tr>\n",
" <tr>\n",
" <th>North Toronto West</th>\n",
" <td>21</td>\n",
" <td>21</td>\n",
" <td>21</td>\n",
" <td>21</td>\n",
" <td>21</td>\n",
" <td>21</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Parkdale / Roncesvalles</th>\n",
" <td>14</td>\n",
" <td>14</td>\n",
" <td>14</td>\n",
" <td>14</td>\n",
" <td>14</td>\n",
" <td>14</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Queen's Park / Ontario Provincial Government</th>\n",
" <td>31</td>\n",
" <td>31</td>\n",
" <td>31</td>\n",
" <td>31</td>\n",
" <td>31</td>\n",
" <td>31</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Regent Park / Harbourfront</th>\n",
" <td>47</td>\n",
" <td>47</td>\n",
" <td>47</td>\n",
" <td>47</td>\n",
" <td>47</td>\n",
" <td>47</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Richmond / Adelaide / King</th>\n",
" <td>97</td>\n",
" <td>97</td>\n",
" <td>97</td>\n",
" <td>97</td>\n",
" <td>97</td>\n",
" <td>97</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Rosedale</th>\n",
" <td>4</td>\n",
" <td>4</td>\n",
" <td>4</td>\n",
" <td>4</td>\n",
" <td>4</td>\n",
" <td>4</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Roselawn</th>\n",
" <td>2</td>\n",
" <td>2</td>\n",
" <td>2</td>\n",
" <td>2</td>\n",
" <td>2</td>\n",
" <td>2</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Runnymede / Swansea</th>\n",
" <td>41</td>\n",
" <td>41</td>\n",
" <td>41</td>\n",
" <td>41</td>\n",
" <td>41</td>\n",
" <td>41</td>\n",
" </tr>\n",
" <tr>\n",
" <th>St. James Town</th>\n",
" <td>86</td>\n",
" <td>86</td>\n",
" <td>86</td>\n",
" <td>86</td>\n",
" <td>86</td>\n",
" <td>86</td>\n",
" </tr>\n",
" <tr>\n",
" <th>St. James Town / Cabbagetown</th>\n",
" <td>44</td>\n",
" <td>44</td>\n",
" <td>44</td>\n",
" <td>44</td>\n",
" <td>44</td>\n",
" <td>44</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Stn A PO Boxes</th>\n",
" <td>95</td>\n",
" <td>95</td>\n",
" <td>95</td>\n",
" <td>95</td>\n",
" <td>95</td>\n",
" <td>95</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Studio District</th>\n",
" <td>41</td>\n",
" <td>41</td>\n",
" <td>41</td>\n",
" <td>41</td>\n",
" <td>41</td>\n",
" <td>41</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Summerhill West / Rathnelly / South Hill / Forest Hill SE / Deer Park</th>\n",
" <td>16</td>\n",
" <td>16</td>\n",
" <td>16</td>\n",
" <td>16</td>\n",
" <td>16</td>\n",
" <td>16</td>\n",
" </tr>\n",
" <tr>\n",
" <th>The Annex / North Midtown / Yorkville</th>\n",
" <td>22</td>\n",
" <td>22</td>\n",
" <td>22</td>\n",
" <td>22</td>\n",
" <td>22</td>\n",
" <td>22</td>\n",
" </tr>\n",
" <tr>\n",
" <th>The Beaches</th>\n",
" <td>5</td>\n",
" <td>5</td>\n",
" <td>5</td>\n",
" <td>5</td>\n",
" <td>5</td>\n",
" <td>5</td>\n",
" </tr>\n",
" <tr>\n",
" <th>The Danforth West / Riverdale</th>\n",
" <td>43</td>\n",
" <td>43</td>\n",
" <td>43</td>\n",
" <td>43</td>\n",
" <td>43</td>\n",
" <td>43</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Toronto Dominion Centre / Design Exchange</th>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" <td>100</td>\n",
" </tr>\n",
" <tr>\n",
" <th>University of Toronto / Harbord</th>\n",
" <td>35</td>\n",
" <td>35</td>\n",
" <td>35</td>\n",
" <td>35</td>\n",
" <td>35</td>\n",
" <td>35</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Neighborhood Latitude \\\n",
"Neighborhood \n",
"Berczy Park 55 \n",
"Brockton / Parkdale Village / Exhibition Place 24 \n",
"Business reply mail Processing CentrE 19 \n",
"CN Tower / King and Spadina / Railway Lands / H... 17 \n",
"Central Bay Street 65 \n",
"Christie 17 \n",
"Church and Wellesley 74 \n",
"Commerce Court / Victoria Hotel 100 \n",
"Davisville 34 \n",
"Davisville North 11 \n",
"Dufferin / Dovercourt Village 15 \n",
"First Canadian Place / Underground city 100 \n",
"Forest Hill North & West 4 \n",
"Garden District, Ryerson 100 \n",
"Harbourfront East / Union Station / Toronto Isl... 100 \n",
"High Park / The Junction South 25 \n",
"India Bazaar / The Beaches West 20 \n",
"Kensington Market / Chinatown / Grange Park 62 \n",
"Lawrence Park 3 \n",
"Little Portugal / Trinity 43 \n",
"Moore Park / Summerhill East 3 \n",
"North Toronto West 21 \n",
"Parkdale / Roncesvalles 14 \n",
"Queen's Park / Ontario Provincial Government 31 \n",
"Regent Park / Harbourfront 47 \n",
"Richmond / Adelaide / King 97 \n",
"Rosedale 4 \n",
"Roselawn 2 \n",
"Runnymede / Swansea 41 \n",
"St. James Town 86 \n",
"St. James Town / Cabbagetown 44 \n",
"Stn A PO Boxes 95 \n",
"Studio District 41 \n",
"Summerhill West / Rathnelly / South Hill / Fore... 16 \n",
"The Annex / North Midtown / Yorkville 22 \n",
"The Beaches 5 \n",
"The Danforth West / Riverdale 43 \n",
"Toronto Dominion Centre / Design Exchange 100 \n",
"University of Toronto / Harbord 35 \n",
"\n",
" Neighborhood Longitude \\\n",
"Neighborhood \n",
"Berczy Park 55 \n",
"Brockton / Parkdale Village / Exhibition Place 24 \n",
"Business reply mail Processing CentrE 19 \n",
"CN Tower / King and Spadina / Railway Lands / H... 17 \n",
"Central Bay Street 65 \n",
"Christie 17 \n",
"Church and Wellesley 74 \n",
"Commerce Court / Victoria Hotel 100 \n",
"Davisville 34 \n",
"Davisville North 11 \n",
"Dufferin / Dovercourt Village 15 \n",
"First Canadian Place / Underground city 100 \n",
"Forest Hill North & West 4 \n",
"Garden District, Ryerson 100 \n",
"Harbourfront East / Union Station / Toronto Isl... 100 \n",
"High Park / The Junction South 25 \n",
"India Bazaar / The Beaches West 20 \n",
"Kensington Market / Chinatown / Grange Park 62 \n",
"Lawrence Park 3 \n",
"Little Portugal / Trinity 43 \n",
"Moore Park / Summerhill East 3 \n",
"North Toronto West 21 \n",
"Parkdale / Roncesvalles 14 \n",
"Queen's Park / Ontario Provincial Government 31 \n",
"Regent Park / Harbourfront 47 \n",
"Richmond / Adelaide / King 97 \n",
"Rosedale 4 \n",
"Roselawn 2 \n",
"Runnymede / Swansea 41 \n",
"St. James Town 86 \n",
"St. James Town / Cabbagetown 44 \n",
"Stn A PO Boxes 95 \n",
"Studio District 41 \n",
"Summerhill West / Rathnelly / South Hill / Fore... 16 \n",
"The Annex / North Midtown / Yorkville 22 \n",
"The Beaches 5 \n",
"The Danforth West / Riverdale 43 \n",
"Toronto Dominion Centre / Design Exchange 100 \n",
"University of Toronto / Harbord 35 \n",
"\n",
" Venue Venue Latitude \\\n",
"Neighborhood \n",
"Berczy Park 55 55 \n",
"Brockton / Parkdale Village / Exhibition Place 24 24 \n",
"Business reply mail Processing CentrE 19 19 \n",
"CN Tower / King and Spadina / Railway Lands / H... 17 17 \n",
"Central Bay Street 65 65 \n",
"Christie 17 17 \n",
"Church and Wellesley 74 74 \n",
"Commerce Court / Victoria Hotel 100 100 \n",
"Davisville 34 34 \n",
"Davisville North 11 11 \n",
"Dufferin / Dovercourt Village 15 15 \n",
"First Canadian Place / Underground city 100 100 \n",
"Forest Hill North & West 4 4 \n",
"Garden District, Ryerson 100 100 \n",
"Harbourfront East / Union Station / Toronto Isl... 100 100 \n",
"High Park / The Junction South 25 25 \n",
"India Bazaar / The Beaches West 20 20 \n",
"Kensington Market / Chinatown / Grange Park 62 62 \n",
"Lawrence Park 3 3 \n",
"Little Portugal / Trinity 43 43 \n",
"Moore Park / Summerhill East 3 3 \n",
"North Toronto West 21 21 \n",
"Parkdale / Roncesvalles 14 14 \n",
"Queen's Park / Ontario Provincial Government 31 31 \n",
"Regent Park / Harbourfront 47 47 \n",
"Richmond / Adelaide / King 97 97 \n",
"Rosedale 4 4 \n",
"Roselawn 2 2 \n",
"Runnymede / Swansea 41 41 \n",
"St. James Town 86 86 \n",
"St. James Town / Cabbagetown 44 44 \n",
"Stn A PO Boxes 95 95 \n",
"Studio District 41 41 \n",
"Summerhill West / Rathnelly / South Hill / Fore... 16 16 \n",
"The Annex / North Midtown / Yorkville 22 22 \n",
"The Beaches 5 5 \n",
"The Danforth West / Riverdale 43 43 \n",
"Toronto Dominion Centre / Design Exchange 100 100 \n",
"University of Toronto / Harbord 35 35 \n",
"\n",
" Venue Longitude \\\n",
"Neighborhood \n",
"Berczy Park 55 \n",
"Brockton / Parkdale Village / Exhibition Place 24 \n",
"Business reply mail Processing CentrE 19 \n",
"CN Tower / King and Spadina / Railway Lands / H... 17 \n",
"Central Bay Street 65 \n",
"Christie 17 \n",
"Church and Wellesley 74 \n",
"Commerce Court / Victoria Hotel 100 \n",
"Davisville 34 \n",
"Davisville North 11 \n",
"Dufferin / Dovercourt Village 15 \n",
"First Canadian Place / Underground city 100 \n",
"Forest Hill North & West 4 \n",
"Garden District, Ryerson 100 \n",
"Harbourfront East / Union Station / Toronto Isl... 100 \n",
"High Park / The Junction South 25 \n",
"India Bazaar / The Beaches West 20 \n",
"Kensington Market / Chinatown / Grange Park 62 \n",
"Lawrence Park 3 \n",
"Little Portugal / Trinity 43 \n",
"Moore Park / Summerhill East 3 \n",
"North Toronto West 21 \n",
"Parkdale / Roncesvalles 14 \n",
"Queen's Park / Ontario Provincial Government 31 \n",
"Regent Park / Harbourfront 47 \n",
"Richmond / Adelaide / King 97 \n",
"Rosedale 4 \n",
"Roselawn 2 \n",
"Runnymede / Swansea 41 \n",
"St. James Town 86 \n",
"St. James Town / Cabbagetown 44 \n",
"Stn A PO Boxes 95 \n",
"Studio District 41 \n",
"Summerhill West / Rathnelly / South Hill / Fore... 16 \n",
"The Annex / North Midtown / Yorkville 22 \n",
"The Beaches 5 \n",
"The Danforth West / Riverdale 43 \n",
"Toronto Dominion Centre / Design Exchange 100 \n",
"University of Toronto / Harbord 35 \n",
"\n",
" Venue Category \n",
"Neighborhood \n",
"Berczy Park 55 \n",
"Brockton / Parkdale Village / Exhibition Place 24 \n",
"Business reply mail Processing CentrE 19 \n",
"CN Tower / King and Spadina / Railway Lands / H... 17 \n",
"Central Bay Street 65 \n",
"Christie 17 \n",
"Church and Wellesley 74 \n",
"Commerce Court / Victoria Hotel 100 \n",
"Davisville 34 \n",
"Davisville North 11 \n",
"Dufferin / Dovercourt Village 15 \n",
"First Canadian Place / Underground city 100 \n",
"Forest Hill North & West 4 \n",
"Garden District, Ryerson 100 \n",
"Harbourfront East / Union Station / Toronto Isl... 100 \n",
"High Park / The Junction South 25 \n",
"India Bazaar / The Beaches West 20 \n",
"Kensington Market / Chinatown / Grange Park 62 \n",
"Lawrence Park 3 \n",
"Little Portugal / Trinity 43 \n",
"Moore Park / Summerhill East 3 \n",
"North Toronto West 21 \n",
"Parkdale / Roncesvalles 14 \n",
"Queen's Park / Ontario Provincial Government 31 \n",
"Regent Park / Harbourfront 47 \n",
"Richmond / Adelaide / King 97 \n",
"Rosedale 4 \n",
"Roselawn 2 \n",
"Runnymede / Swansea 41 \n",
"St. James Town 86 \n",
"St. James Town / Cabbagetown 44 \n",
"Stn A PO Boxes 95 \n",
"Studio District 41 \n",
"Summerhill West / Rathnelly / South Hill / Fore... 16 \n",
"The Annex / North Midtown / Yorkville 22 \n",
"The Beaches 5 \n",
"The Danforth West / Riverdale 43 \n",
"Toronto Dominion Centre / Design Exchange 100 \n",
"University of Toronto / Harbord 35 "
]
},
"execution_count": 44,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Toronto_venues.groupby('Neighborhood').count()"
]
},
{
"cell_type": "code",
"execution_count": 45,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"There are 226 uniques categories.\n"
]
}
],
"source": [
"print('There are {} uniques categories.'.format(len(Toronto_venues['Venue Category'].unique())))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Analyze Each Neighborhood"
]
},
{
"cell_type": "code",
"execution_count": 48,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Yoga Studio</th>\n",
" <th>Airport</th>\n",
" <th>Airport Food Court</th>\n",
" <th>Airport Gate</th>\n",
" <th>Airport Lounge</th>\n",
" <th>Airport Service</th>\n",
" <th>Airport Terminal</th>\n",
" <th>American Restaurant</th>\n",
" <th>Antique Shop</th>\n",
" <th>Aquarium</th>\n",
" <th>...</th>\n",
" <th>Theater</th>\n",
" <th>Theme Restaurant</th>\n",
" <th>Toy / Game Store</th>\n",
" <th>Trail</th>\n",
" <th>Train Station</th>\n",
" <th>Vegetarian / Vegan Restaurant</th>\n",
" <th>Video Game Store</th>\n",
" <th>Vietnamese Restaurant</th>\n",
" <th>Wine Bar</th>\n",
" <th>Women's Store</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>...</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>...</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>...</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>...</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>...</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>5 rows × 226 columns</p>\n",
"</div>"
],
"text/plain": [
" Yoga Studio Airport Airport Food Court Airport Gate Airport Lounge \\\n",
"0 0 0 0 0 0 \n",
"1 0 0 0 0 0 \n",
"2 0 0 0 0 0 \n",
"3 0 0 0 0 0 \n",
"4 0 0 0 0 0 \n",
"\n",
" Airport Service Airport Terminal American Restaurant Antique Shop \\\n",
"0 0 0 0 0 \n",
"1 0 0 0 0 \n",
"2 0 0 0 0 \n",
"3 0 0 0 0 \n",
"4 0 0 0 0 \n",
"\n",
" Aquarium ... Theater Theme Restaurant Toy / Game Store Trail \\\n",
"0 0 ... 0 0 0 0 \n",
"1 0 ... 0 0 0 0 \n",
"2 0 ... 0 0 0 0 \n",
"3 0 ... 0 0 0 0 \n",
"4 0 ... 0 0 0 0 \n",
"\n",
" Train Station Vegetarian / Vegan Restaurant Video Game Store \\\n",
"0 0 0 0 \n",
"1 0 0 0 \n",
"2 0 0 0 \n",
"3 0 0 0 \n",
"4 0 0 0 \n",
"\n",
" Vietnamese Restaurant Wine Bar Women's Store \n",
"0 0 0 0 \n",
"1 0 0 0 \n",
"2 0 0 0 \n",
"3 0 0 0 \n",
"4 0 0 0 \n",
"\n",
"[5 rows x 226 columns]"
]
},
"execution_count": 48,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# one hot encoding\n",
"Toronto_onehot = pd.get_dummies(Toronto_venues[['Venue Category']], prefix=\"\", prefix_sep=\"\")\n",
"\n",
"# add neighborhood column back to dataframe\n",
"Toronto_onehot['Neighborhood'] = Toronto_venues['Neighborhood'] \n",
"\n",
"# move neighborhood column to the first column\n",
"fixed_columns = [Toronto_onehot.columns[-1]] + list(Toronto_onehot.columns[:-1])\n",
"Toronto_onehot = Toronto_onehot[fixed_columns]\n",
"\n",
"Toronto_onehot.head()"
]
},
{
"cell_type": "code",
"execution_count": 50,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(1635, 226)"
]
},
"execution_count": 50,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# examn new data frame\n",
"Toronto_onehot.shape"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Next, let's group rows by neighborhood and by taking the mean of the frequency of occurrence of each category"
]
},
{
"cell_type": "code",
"execution_count": 51,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Neighborhood</th>\n",
" <th>Yoga Studio</th>\n",
" <th>Airport</th>\n",
" <th>Airport Food Court</th>\n",
" <th>Airport Gate</th>\n",
" <th>Airport Lounge</th>\n",
" <th>Airport Service</th>\n",
" <th>Airport Terminal</th>\n",
" <th>American Restaurant</th>\n",
" <th>Antique Shop</th>\n",
" <th>...</th>\n",
" <th>Theater</th>\n",
" <th>Theme Restaurant</th>\n",
" <th>Toy / Game Store</th>\n",
" <th>Trail</th>\n",
" <th>Train Station</th>\n",
" <th>Vegetarian / Vegan Restaurant</th>\n",
" <th>Video Game Store</th>\n",
" <th>Vietnamese Restaurant</th>\n",
" <th>Wine Bar</th>\n",
" <th>Women's Store</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>Berczy Park</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>...</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.018182</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.000000</td>\n",
" <td>0.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>Brockton / Parkdale Village / Exhibition Place</td>\n",
" <td>0.041667</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>...</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.000000</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.000000</td>\n",
" <td>0.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>Business reply mail Processing CentrE</td>\n",
" <td>0.052632</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>...</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.000000</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.000000</td>\n",
" <td>0.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>CN Tower / King and Spadina / Railway Lands / ...</td>\n",
" <td>0.000000</td>\n",
" <td>0.058824</td>\n",
" <td>0.058824</td>\n",
" <td>0.058824</td>\n",
" <td>0.117647</td>\n",
" <td>0.176471</td>\n",
" <td>0.117647</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>...</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.000000</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.000000</td>\n",
" <td>0.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>Central Bay Street</td>\n",
" <td>0.015385</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>...</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.015385</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.015385</td>\n",
" <td>0.0</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>5 rows × 226 columns</p>\n",
"</div>"
],
"text/plain": [
" Neighborhood Yoga Studio Airport \\\n",
"0 Berczy Park 0.000000 0.000000 \n",
"1 Brockton / Parkdale Village / Exhibition Place 0.041667 0.000000 \n",
"2 Business reply mail Processing CentrE 0.052632 0.000000 \n",
"3 CN Tower / King and Spadina / Railway Lands / ... 0.000000 0.058824 \n",
"4 Central Bay Street 0.015385 0.000000 \n",
"\n",
" Airport Food Court Airport Gate Airport Lounge Airport Service \\\n",
"0 0.000000 0.000000 0.000000 0.000000 \n",
"1 0.000000 0.000000 0.000000 0.000000 \n",
"2 0.000000 0.000000 0.000000 0.000000 \n",
"3 0.058824 0.058824 0.117647 0.176471 \n",
"4 0.000000 0.000000 0.000000 0.000000 \n",
"\n",
" Airport Terminal American Restaurant Antique Shop ... Theater \\\n",
"0 0.000000 0.0 0.0 ... 0.0 \n",
"1 0.000000 0.0 0.0 ... 0.0 \n",
"2 0.000000 0.0 0.0 ... 0.0 \n",
"3 0.117647 0.0 0.0 ... 0.0 \n",
"4 0.000000 0.0 0.0 ... 0.0 \n",
"\n",
" Theme Restaurant Toy / Game Store Trail Train Station \\\n",
"0 0.0 0.0 0.0 0.0 \n",
"1 0.0 0.0 0.0 0.0 \n",
"2 0.0 0.0 0.0 0.0 \n",
"3 0.0 0.0 0.0 0.0 \n",
"4 0.0 0.0 0.0 0.0 \n",
"\n",
" Vegetarian / Vegan Restaurant Video Game Store Vietnamese Restaurant \\\n",
"0 0.018182 0.0 0.0 \n",
"1 0.000000 0.0 0.0 \n",
"2 0.000000 0.0 0.0 \n",
"3 0.000000 0.0 0.0 \n",
"4 0.015385 0.0 0.0 \n",
"\n",
" Wine Bar Women's Store \n",
"0 0.000000 0.0 \n",
"1 0.000000 0.0 \n",
"2 0.000000 0.0 \n",
"3 0.000000 0.0 \n",
"4 0.015385 0.0 \n",
"\n",
"[5 rows x 226 columns]"
]
},
"execution_count": 51,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Toronto_grouped = Toronto_onehot.groupby('Neighborhood').mean().reset_index()\n",
"Toronto_grouped.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Let's confirm the new size"
]
},
{
"cell_type": "code",
"execution_count": 52,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(39, 226)"
]
},
"execution_count": 52,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Toronto_grouped.shape"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Let's print each neighborhood along with the top 5 most common venues"
]
},
{
"cell_type": "code",
"execution_count": 53,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"----Berczy Park----\n",
" venue freq\n",
"0 Coffee Shop 0.05\n",
"1 Farmers Market 0.04\n",
"2 Italian Restaurant 0.04\n",
"3 Seafood Restaurant 0.04\n",
"4 Cheese Shop 0.04\n",
"\n",
"\n",
"----Brockton / Parkdale Village / Exhibition Place----\n",
" venue freq\n",
"0 Café 0.12\n",
"1 Coffee Shop 0.08\n",
"2 Nightclub 0.08\n",
"3 Breakfast Spot 0.08\n",
"4 Yoga Studio 0.04\n",
"\n",
"\n",
"----Business reply mail Processing CentrE----\n",
" venue freq\n",
"0 Light Rail Station 0.11\n",
"1 Yoga Studio 0.05\n",
"2 Spa 0.05\n",
"3 Garden Center 0.05\n",
"4 Garden 0.05\n",
"\n",
"\n",
"----CN Tower / King and Spadina / Railway Lands / Harbourfront West / Bathurst Quay / South Niagara / Island airport----\n",
" venue freq\n",
"0 Airport Service 0.18\n",
"1 Airport Lounge 0.12\n",
"2 Airport Terminal 0.12\n",
"3 Harbor / Marina 0.06\n",
"4 Boutique 0.06\n",
"\n",
"\n",
"----Central Bay Street----\n",
" venue freq\n",
"0 Coffee Shop 0.18\n",
"1 Italian Restaurant 0.06\n",
"2 Café 0.05\n",
"3 Sandwich Place 0.05\n",
"4 Bubble Tea Shop 0.03\n",
"\n",
"\n",
"----Christie----\n",
" venue freq\n",
"0 Grocery Store 0.24\n",
"1 Café 0.18\n",
"2 Park 0.12\n",
"3 Diner 0.06\n",
"4 Italian Restaurant 0.06\n",
"\n",
"\n",
"----Church and Wellesley----\n",
" venue freq\n",
"0 Coffee Shop 0.07\n",
"1 Japanese Restaurant 0.05\n",
"2 Gay Bar 0.05\n",
"3 Sushi Restaurant 0.04\n",
"4 Restaurant 0.04\n",
"\n",
"\n",
"----Commerce Court / Victoria Hotel----\n",
" venue freq\n",
"0 Coffee Shop 0.10\n",
"1 Café 0.07\n",
"2 Restaurant 0.07\n",
"3 Hotel 0.06\n",
"4 American Restaurant 0.04\n",
"\n",
"\n",
"----Davisville----\n",
" venue freq\n",
"0 Sandwich Place 0.09\n",
"1 Dessert Shop 0.09\n",
"2 Coffee Shop 0.06\n",
"3 Gym 0.06\n",
"4 Café 0.06\n",
"\n",
"\n",
"----Davisville North----\n",
" venue freq\n",
"0 Gym 0.09\n",
"1 Hotel 0.09\n",
"2 Convenience Store 0.09\n",
"3 Pizza Place 0.09\n",
"4 Dance Studio 0.09\n",
"\n",
"\n",
"----Dufferin / Dovercourt Village----\n",
" venue freq\n",
"0 Pharmacy 0.13\n",
"1 Bakery 0.13\n",
"2 Pizza Place 0.07\n",
"3 Park 0.07\n",
"4 Gym / Fitness Center 0.07\n",
"\n",
"\n",
"----First Canadian Place / Underground city----\n",
" venue freq\n",
"0 Coffee Shop 0.10\n",
"1 Café 0.07\n",
"2 Restaurant 0.06\n",
"3 Hotel 0.04\n",
"4 Asian Restaurant 0.03\n",
"\n",
"\n",
"----Forest Hill North & West----\n",
" venue freq\n",
"0 Park 0.25\n",
"1 Jewelry Store 0.25\n",
"2 Trail 0.25\n",
"3 Sushi Restaurant 0.25\n",
"4 Yoga Studio 0.00\n",
"\n",
"\n",
"----Garden District, Ryerson----\n",
" venue freq\n",
"0 Coffee Shop 0.09\n",
"1 Clothing Store 0.08\n",
"2 Café 0.04\n",
"3 Cosmetics Shop 0.03\n",
"4 Bubble Tea Shop 0.03\n",
"\n",
"\n",
"----Harbourfront East / Union Station / Toronto Islands----\n",
" venue freq\n",
"0 Coffee Shop 0.12\n",
"1 Aquarium 0.05\n",
"2 Hotel 0.04\n",
"3 Café 0.04\n",
"4 Restaurant 0.04\n",
"\n",
"\n",
"----High Park / The Junction South----\n",
" venue freq\n",
"0 Bar 0.08\n",
"1 Mexican Restaurant 0.08\n",
"2 Café 0.08\n",
"3 Thai Restaurant 0.08\n",
"4 Bakery 0.04\n",
"\n",
"\n",
"----India Bazaar / The Beaches West----\n",
" venue freq\n",
"0 Fast Food Restaurant 0.10\n",
"1 Gym 0.05\n",
"2 Italian Restaurant 0.05\n",
"3 Pet Store 0.05\n",
"4 Pizza Place 0.05\n",
"\n",
"\n",
"----Kensington Market / Chinatown / Grange Park----\n",
" venue freq\n",
"0 Café 0.08\n",
"1 Coffee Shop 0.06\n",
"2 Mexican Restaurant 0.05\n",
"3 Vietnamese Restaurant 0.05\n",
"4 Vegetarian / Vegan Restaurant 0.05\n",
"\n",
"\n",
"----Lawrence Park----\n",
" venue freq\n",
"0 Park 0.33\n",
"1 Bus Line 0.33\n",
"2 Swim School 0.33\n",
"3 Yoga Studio 0.00\n",
"4 Monument / Landmark 0.00\n",
"\n",
"\n",
"----Little Portugal / Trinity----\n",
" venue freq\n",
"0 Bar 0.12\n",
"1 Restaurant 0.07\n",
"2 Café 0.05\n",
"3 Vegetarian / Vegan Restaurant 0.05\n",
"4 Coffee Shop 0.05\n",
"\n",
"\n",
"----Moore Park / Summerhill East----\n",
" venue freq\n",
"0 Park 0.33\n",
"1 Playground 0.33\n",
"2 Summer Camp 0.33\n",
"3 Yoga Studio 0.00\n",
"4 Monument / Landmark 0.00\n",
"\n",
"\n",
"----North Toronto West----\n",
" venue freq\n",
"0 Clothing Store 0.14\n",
"1 Coffee Shop 0.10\n",
"2 Yoga Studio 0.05\n",
"3 Seafood Restaurant 0.05\n",
"4 Salon / Barbershop 0.05\n",
"\n",
"\n",
"----Parkdale / Roncesvalles----\n",
" venue freq\n",
"0 Gift Shop 0.14\n",
"1 Bookstore 0.07\n",
"2 Dessert Shop 0.07\n",
"3 Eastern European Restaurant 0.07\n",
"4 Movie Theater 0.07\n",
"\n",
"\n",
"----Queen's Park / Ontario Provincial Government----\n",
" venue freq\n",
"0 Coffee Shop 0.26\n",
"1 Diner 0.06\n",
"2 Yoga Studio 0.03\n",
"3 Burrito Place 0.03\n",
"4 Juice Bar 0.03\n",
"\n",
"\n",
"----Regent Park / Harbourfront----\n",
" venue freq\n",
"0 Coffee Shop 0.17\n",
"1 Park 0.06\n",
"2 Bakery 0.06\n",
"3 Pub 0.06\n",
"4 Theater 0.04\n",
"\n",
"\n",
"----Richmond / Adelaide / King----\n",
" venue freq\n",
"0 Coffee Shop 0.08\n",
"1 Café 0.05\n",
"2 Gym 0.04\n",
"3 Restaurant 0.04\n",
"4 Deli / Bodega 0.03\n",
"\n",
"\n",
"----Rosedale----\n",
" venue freq\n",
"0 Park 0.50\n",
"1 Playground 0.25\n",
"2 Trail 0.25\n",
"3 Yoga Studio 0.00\n",
"4 Moroccan Restaurant 0.00\n",
"\n",
"\n",
"----Roselawn----\n",
" venue freq\n",
"0 Garden 0.5\n",
"1 Music Venue 0.5\n",
"2 Yoga Studio 0.0\n",
"3 Moroccan Restaurant 0.0\n",
"4 Liquor Store 0.0\n",
"\n",
"\n",
"----Runnymede / Swansea----\n",
" venue freq\n",
"0 Café 0.07\n",
"1 Pizza Place 0.07\n",
"2 Coffee Shop 0.07\n",
"3 Pub 0.05\n",
"4 Italian Restaurant 0.05\n",
"\n",
"\n",
"----St. James Town----\n",
" venue freq\n",
"0 Café 0.06\n",
"1 Coffee Shop 0.06\n",
"2 Cocktail Bar 0.05\n",
"3 American Restaurant 0.03\n",
"4 Restaurant 0.03\n",
"\n",
"\n",
"----St. James Town / Cabbagetown----\n",
" venue freq\n",
"0 Coffee Shop 0.07\n",
"1 Pub 0.05\n",
"2 Bakery 0.05\n",
"3 Pizza Place 0.05\n",
"4 Restaurant 0.05\n",
"\n",
"\n",
"----Stn A PO Boxes----\n",
" venue freq\n",
"0 Coffee Shop 0.09\n",
"1 Italian Restaurant 0.04\n",
"2 Café 0.04\n",
"3 Restaurant 0.04\n",
"4 Hotel 0.03\n",
"\n",
"\n",
"----Studio District----\n",
" venue freq\n",
"0 Café 0.10\n",
"1 Coffee Shop 0.07\n",
"2 Brewery 0.05\n",
"3 Bakery 0.05\n",
"4 Gastropub 0.05\n",
"\n",
"\n",
"----Summerhill West / Rathnelly / South Hill / Forest Hill SE / Deer Park----\n",
" venue freq\n",
"0 Pub 0.12\n",
"1 Coffee Shop 0.12\n",
"2 Sports Bar 0.06\n",
"3 Fried Chicken Joint 0.06\n",
"4 Restaurant 0.06\n",
"\n",
"\n",
"----The Annex / North Midtown / Yorkville----\n",
" venue freq\n",
"0 Sandwich Place 0.14\n",
"1 Café 0.14\n",
"2 Coffee Shop 0.09\n",
"3 Cosmetics Shop 0.05\n",
"4 Indian Restaurant 0.05\n",
"\n",
"\n",
"----The Beaches----\n",
" venue freq\n",
"0 Asian Restaurant 0.2\n",
"1 Health Food Store 0.2\n",
"2 Trail 0.2\n",
"3 Pub 0.2\n",
"4 Yoga Studio 0.0\n",
"\n",
"\n",
"----The Danforth West / Riverdale----\n",
" venue freq\n",
"0 Greek Restaurant 0.19\n",
"1 Italian Restaurant 0.07\n",
"2 Coffee Shop 0.07\n",
"3 Bookstore 0.05\n",
"4 Restaurant 0.05\n",
"\n",
"\n",
"----Toronto Dominion Centre / Design Exchange----\n",
" venue freq\n",
"0 Coffee Shop 0.12\n",
"1 Hotel 0.08\n",
"2 Café 0.07\n",
"3 Restaurant 0.05\n",
"4 Italian Restaurant 0.03\n",
"\n",
"\n",
"----University of Toronto / Harbord----\n",
" venue freq\n",
"0 Café 0.14\n",
"1 Restaurant 0.06\n",
"2 Italian Restaurant 0.06\n",
"3 Japanese Restaurant 0.06\n",
"4 Bar 0.06\n",
"\n",
"\n"
]
}
],
"source": [
"num_top_venues = 5\n",
"\n",
"for hood in Toronto_grouped['Neighborhood']:\n",
" print(\"----\"+hood+\"----\")\n",
" temp = Toronto_grouped[Toronto_grouped['Neighborhood'] == hood].T.reset_index()\n",
" temp.columns = ['venue','freq']\n",
" temp = temp.iloc[1:]\n",
" temp['freq'] = temp['freq'].astype(float)\n",
" temp = temp.round({'freq': 2})\n",
" print(temp.sort_values('freq', ascending=False).reset_index(drop=True).head(num_top_venues))\n",
" print('\\n')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Let's put that into a *pandas* dataframe"
]
},
{
"cell_type": "code",
"execution_count": 54,
"metadata": {},
"outputs": [],
"source": [
"#function to sort in decending order\n",
"def return_most_common_venues(row, num_top_venues):\n",
" row_categories = row.iloc[1:]\n",
" row_categories_sorted = row_categories.sort_values(ascending=False)\n",
" \n",
" return row_categories_sorted.index.values[0:num_top_venues]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now let's create the new dataframe and display the top 10 venues for each neighborhood."
]
},
{
"cell_type": "code",
"execution_count": 55,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Neighborhood</th>\n",
" <th>1st Most Common Venue</th>\n",
" <th>2nd Most Common Venue</th>\n",
" <th>3rd Most Common Venue</th>\n",
" <th>4th Most Common Venue</th>\n",
" <th>5th Most Common Venue</th>\n",
" <th>6th Most Common Venue</th>\n",
" <th>7th Most Common Venue</th>\n",
" <th>8th Most Common Venue</th>\n",
" <th>9th Most Common Venue</th>\n",
" <th>10th Most Common Venue</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>Berczy Park</td>\n",
" <td>Coffee Shop</td>\n",
" <td>Cocktail Bar</td>\n",
" <td>Italian Restaurant</td>\n",
" <td>Restaurant</td>\n",
" <td>Beer Bar</td>\n",
" <td>Café</td>\n",
" <td>Cheese Shop</td>\n",
" <td>Bakery</td>\n",
" <td>Seafood Restaurant</td>\n",
" <td>Farmers Market</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>Brockton / Parkdale Village / Exhibition Place</td>\n",
" <td>Café</td>\n",
" <td>Coffee Shop</td>\n",
" <td>Breakfast Spot</td>\n",
" <td>Nightclub</td>\n",
" <td>Pet Store</td>\n",
" <td>Stadium</td>\n",
" <td>Burrito Place</td>\n",
" <td>Restaurant</td>\n",
" <td>Climbing Gym</td>\n",
" <td>Performing Arts Venue</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>Business reply mail Processing CentrE</td>\n",
" <td>Light Rail Station</td>\n",
" <td>Yoga Studio</td>\n",
" <td>Spa</td>\n",
" <td>Garden Center</td>\n",
" <td>Garden</td>\n",
" <td>Gym / Fitness Center</td>\n",
" <td>Fast Food Restaurant</td>\n",
" <td>Farmers Market</td>\n",
" <td>Comic Shop</td>\n",
" <td>Pizza Place</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>CN Tower / King and Spadina / Railway Lands / ...</td>\n",
" <td>Airport Service</td>\n",
" <td>Airport Lounge</td>\n",
" <td>Airport Terminal</td>\n",
" <td>Airport</td>\n",
" <td>Airport Food Court</td>\n",
" <td>Airport Gate</td>\n",
" <td>Bar</td>\n",
" <td>Boutique</td>\n",
" <td>Rental Car Location</td>\n",
" <td>Boat or Ferry</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>Central Bay Street</td>\n",
" <td>Coffee Shop</td>\n",
" <td>Italian Restaurant</td>\n",
" <td>Sandwich Place</td>\n",
" <td>Café</td>\n",
" <td>Salad Place</td>\n",
" <td>Ice Cream Shop</td>\n",
" <td>Japanese Restaurant</td>\n",
" <td>Middle Eastern Restaurant</td>\n",
" <td>Sushi Restaurant</td>\n",
" <td>Spa</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Neighborhood 1st Most Common Venue \\\n",
"0 Berczy Park Coffee Shop \n",
"1 Brockton / Parkdale Village / Exhibition Place Café \n",
"2 Business reply mail Processing CentrE Light Rail Station \n",
"3 CN Tower / King and Spadina / Railway Lands / ... Airport Service \n",
"4 Central Bay Street Coffee Shop \n",
"\n",
" 2nd Most Common Venue 3rd Most Common Venue 4th Most Common Venue \\\n",
"0 Cocktail Bar Italian Restaurant Restaurant \n",
"1 Coffee Shop Breakfast Spot Nightclub \n",
"2 Yoga Studio Spa Garden Center \n",
"3 Airport Lounge Airport Terminal Airport \n",
"4 Italian Restaurant Sandwich Place Café \n",
"\n",
" 5th Most Common Venue 6th Most Common Venue 7th Most Common Venue \\\n",
"0 Beer Bar Café Cheese Shop \n",
"1 Pet Store Stadium Burrito Place \n",
"2 Garden Gym / Fitness Center Fast Food Restaurant \n",
"3 Airport Food Court Airport Gate Bar \n",
"4 Salad Place Ice Cream Shop Japanese Restaurant \n",
"\n",
" 8th Most Common Venue 9th Most Common Venue 10th Most Common Venue \n",
"0 Bakery Seafood Restaurant Farmers Market \n",
"1 Restaurant Climbing Gym Performing Arts Venue \n",
"2 Farmers Market Comic Shop Pizza Place \n",
"3 Boutique Rental Car Location Boat or Ferry \n",
"4 Middle Eastern Restaurant Sushi Restaurant Spa "
]
},
"execution_count": 55,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"num_top_venues = 10\n",
"\n",
"indicators = ['st', 'nd', 'rd']\n",
"\n",
"# create columns according to number of top venues\n",
"columns = ['Neighborhood']\n",
"for ind in np.arange(num_top_venues):\n",
" try:\n",
" columns.append('{}{} Most Common Venue'.format(ind+1, indicators[ind]))\n",
" except:\n",
" columns.append('{}th Most Common Venue'.format(ind+1))\n",
"\n",
"# create a new dataframe\n",
"neighborhoods_venues_sorted = pd.DataFrame(columns=columns)\n",
"neighborhoods_venues_sorted['Neighborhood'] = Toronto_grouped['Neighborhood']\n",
"\n",
"for ind in np.arange(Toronto_grouped.shape[0]):\n",
" neighborhoods_venues_sorted.iloc[ind, 1:] = return_most_common_venues(Toronto_grouped.iloc[ind, :], num_top_venues)\n",
"\n",
"neighborhoods_venues_sorted.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Cluster Neighborhoods"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Run *k*-means to cluster the neighborhood into 5 clusters."
]
},
{
"cell_type": "code",
"execution_count": 56,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0], dtype=int32)"
]
},
"execution_count": 56,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# set number of clusters\n",
"kclusters = 5\n",
"\n",
"Toronto_grouped_clustering = Toronto_grouped.drop('Neighborhood', 1)\n",
"\n",
"# run k-means clustering\n",
"kmeans = KMeans(n_clusters=kclusters, random_state=0).fit(Toronto_grouped_clustering)\n",
"\n",
"# check cluster labels generated for each row in the dataframe\n",
"kmeans.labels_[0:10] "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's create a new dataframe that includes the cluster as well as the top 10 venues for each neighborhood."
]
},
{
"cell_type": "code",
"execution_count": 57,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Postal code</th>\n",
" <th>Borough</th>\n",
" <th>Neighborhood</th>\n",
" <th>Latitude</th>\n",
" <th>Longitude</th>\n",
" <th>Cluster Labels</th>\n",
" <th>1st Most Common Venue</th>\n",
" <th>2nd Most Common Venue</th>\n",
" <th>3rd Most Common Venue</th>\n",
" <th>4th Most Common Venue</th>\n",
" <th>5th Most Common Venue</th>\n",
" <th>6th Most Common Venue</th>\n",
" <th>7th Most Common Venue</th>\n",
" <th>8th Most Common Venue</th>\n",
" <th>9th Most Common Venue</th>\n",
" <th>10th Most Common Venue</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>M5A</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Regent Park / Harbourfront</td>\n",
" <td>43.654260</td>\n",
" <td>-79.360636</td>\n",
" <td>0</td>\n",
" <td>Coffee Shop</td>\n",
" <td>Bakery</td>\n",
" <td>Park</td>\n",
" <td>Pub</td>\n",
" <td>Theater</td>\n",
" <td>Mexican Restaurant</td>\n",
" <td>Breakfast Spot</td>\n",
" <td>Restaurant</td>\n",
" <td>Café</td>\n",
" <td>Shoe Store</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>M7A</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Queen's Park / Ontario Provincial Government</td>\n",
" <td>43.662301</td>\n",
" <td>-79.389494</td>\n",
" <td>0</td>\n",
" <td>Coffee Shop</td>\n",
" <td>Diner</td>\n",
" <td>Yoga Studio</td>\n",
" <td>Creperie</td>\n",
" <td>Beer Bar</td>\n",
" <td>Sandwich Place</td>\n",
" <td>Burger Joint</td>\n",
" <td>Burrito Place</td>\n",
" <td>Café</td>\n",
" <td>College Auditorium</td>\n",
" </tr>\n",
" <tr>\n",
" <th>9</th>\n",
" <td>M5B</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Garden District, Ryerson</td>\n",
" <td>43.657162</td>\n",
" <td>-79.378937</td>\n",
" <td>0</td>\n",
" <td>Coffee Shop</td>\n",
" <td>Clothing Store</td>\n",
" <td>Café</td>\n",
" <td>Japanese Restaurant</td>\n",
" <td>Italian Restaurant</td>\n",
" <td>Middle Eastern Restaurant</td>\n",
" <td>Cosmetics Shop</td>\n",
" <td>Bubble Tea Shop</td>\n",
" <td>Electronics Store</td>\n",
" <td>Lingerie Store</td>\n",
" </tr>\n",
" <tr>\n",
" <th>15</th>\n",
" <td>M5C</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>St. James Town</td>\n",
" <td>43.651494</td>\n",
" <td>-79.375418</td>\n",
" <td>0</td>\n",
" <td>Café</td>\n",
" <td>Coffee Shop</td>\n",
" <td>Cocktail Bar</td>\n",
" <td>Beer Bar</td>\n",
" <td>Restaurant</td>\n",
" <td>American Restaurant</td>\n",
" <td>Hotel</td>\n",
" <td>Diner</td>\n",
" <td>Art Gallery</td>\n",
" <td>Cosmetics Shop</td>\n",
" </tr>\n",
" <tr>\n",
" <th>19</th>\n",
" <td>M4E</td>\n",
" <td>East Toronto</td>\n",
" <td>The Beaches</td>\n",
" <td>43.676357</td>\n",
" <td>-79.293031</td>\n",
" <td>0</td>\n",
" <td>Asian Restaurant</td>\n",
" <td>Trail</td>\n",
" <td>Pub</td>\n",
" <td>Health Food Store</td>\n",
" <td>Women's Store</td>\n",
" <td>Distribution Center</td>\n",
" <td>Dessert Shop</td>\n",
" <td>Diner</td>\n",
" <td>Discount Store</td>\n",
" <td>Dog Run</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Postal code Borough \\\n",
"2 M5A Downtown Toronto \n",
"4 M7A Downtown Toronto \n",
"9 M5B Downtown Toronto \n",
"15 M5C Downtown Toronto \n",
"19 M4E East Toronto \n",
"\n",
" Neighborhood Latitude Longitude \\\n",
"2 Regent Park / Harbourfront 43.654260 -79.360636 \n",
"4 Queen's Park / Ontario Provincial Government 43.662301 -79.389494 \n",
"9 Garden District, Ryerson 43.657162 -79.378937 \n",
"15 St. James Town 43.651494 -79.375418 \n",
"19 The Beaches 43.676357 -79.293031 \n",
"\n",
" Cluster Labels 1st Most Common Venue 2nd Most Common Venue \\\n",
"2 0 Coffee Shop Bakery \n",
"4 0 Coffee Shop Diner \n",
"9 0 Coffee Shop Clothing Store \n",
"15 0 Café Coffee Shop \n",
"19 0 Asian Restaurant Trail \n",
"\n",
" 3rd Most Common Venue 4th Most Common Venue 5th Most Common Venue \\\n",
"2 Park Pub Theater \n",
"4 Yoga Studio Creperie Beer Bar \n",
"9 Café Japanese Restaurant Italian Restaurant \n",
"15 Cocktail Bar Beer Bar Restaurant \n",
"19 Pub Health Food Store Women's Store \n",
"\n",
" 6th Most Common Venue 7th Most Common Venue 8th Most Common Venue \\\n",
"2 Mexican Restaurant Breakfast Spot Restaurant \n",
"4 Sandwich Place Burger Joint Burrito Place \n",
"9 Middle Eastern Restaurant Cosmetics Shop Bubble Tea Shop \n",
"15 American Restaurant Hotel Diner \n",
"19 Distribution Center Dessert Shop Diner \n",
"\n",
" 9th Most Common Venue 10th Most Common Venue \n",
"2 Café Shoe Store \n",
"4 Café College Auditorium \n",
"9 Electronics Store Lingerie Store \n",
"15 Art Gallery Cosmetics Shop \n",
"19 Discount Store Dog Run "
]
},
"execution_count": 57,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# add clustering labels\n",
"neighborhoods_venues_sorted.insert(0, 'Cluster Labels', kmeans.labels_)\n",
"\n",
"Toronto_merged = Toronto_df\n",
"\n",
"# merge toronto_grouped with toronto_data to add latitude/longitude for each neighborhood\n",
"Toronto_merged = Toronto_merged.join(neighborhoods_venues_sorted.set_index('Neighborhood'), on='Neighborhood')\n",
"\n",
"Toronto_merged.head() # check the last columns!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally, let's visualize the resulting clusters"
]
},
{
"cell_type": "code",
"execution_count": 60,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"data": {
"text/html": [
"<div style=\"width:100%;\"><div style=\"position:relative;width:100%;height:0;padding-bottom:60%;\"><iframe src=\"about:blank\" style=\"position:absolute;width:100%;height:100%;left:0;top:0;border:none !important;\" data-html=PCFET0NUWVBFIGh0bWw+CjxoZWFkPiAgICAKICAgIDxtZXRhIGh0dHAtZXF1aXY9ImNvbnRlbnQtdHlwZSIgY29udGVudD0idGV4dC9odG1sOyBjaGFyc2V0PVVURi04IiAvPgogICAgPHNjcmlwdD5MX1BSRUZFUl9DQU5WQVMgPSBmYWxzZTsgTF9OT19UT1VDSCA9IGZhbHNlOyBMX0RJU0FCTEVfM0QgPSBmYWxzZTs8L3NjcmlwdD4KICAgIDxzY3JpcHQgc3JjPSJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL2xlYWZsZXRAMS4yLjAvZGlzdC9sZWFmbGV0LmpzIj48L3NjcmlwdD4KICAgIDxzY3JpcHQgc3JjPSJodHRwczovL2FqYXguZ29vZ2xlYXBpcy5jb20vYWpheC9saWJzL2pxdWVyeS8xLjExLjEvanF1ZXJ5Lm1pbi5qcyI+PC9zY3JpcHQ+CiAgICA8c2NyaXB0IHNyYz0iaHR0cHM6Ly9tYXhjZG4uYm9vdHN0cmFwY2RuLmNvbS9ib290c3RyYXAvMy4yLjAvanMvYm9vdHN0cmFwLm1pbi5qcyI+PC9zY3JpcHQ+CiAgICA8c2NyaXB0IHNyYz0iaHR0cHM6Ly9jZG5qcy5jbG91ZGZsYXJlLmNvbS9hamF4L2xpYnMvTGVhZmxldC5hd2Vzb21lLW1hcmtlcnMvMi4wLjIvbGVhZmxldC5hd2Vzb21lLW1hcmtlcnMuanMiPjwvc2NyaXB0PgogICAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL2xlYWZsZXRAMS4yLjAvZGlzdC9sZWFmbGV0LmNzcyIvPgogICAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJodHRwczovL21heGNkbi5ib290c3RyYXBjZG4uY29tL2Jvb3RzdHJhcC8zLjIuMC9jc3MvYm9vdHN0cmFwLm1pbi5jc3MiLz4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iaHR0cHM6Ly9tYXhjZG4uYm9vdHN0cmFwY2RuLmNvbS9ib290c3RyYXAvMy4yLjAvY3NzL2Jvb3RzdHJhcC10aGVtZS5taW4uY3NzIi8+CiAgICA8bGluayByZWw9InN0eWxlc2hlZXQiIGhyZWY9Imh0dHBzOi8vbWF4Y2RuLmJvb3RzdHJhcGNkbi5jb20vZm9udC1hd2Vzb21lLzQuNi4zL2Nzcy9mb250LWF3ZXNvbWUubWluLmNzcyIvPgogICAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJodHRwczovL2NkbmpzLmNsb3VkZmxhcmUuY29tL2FqYXgvbGlicy9MZWFmbGV0LmF3ZXNvbWUtbWFya2Vycy8yLjAuMi9sZWFmbGV0LmF3ZXNvbWUtbWFya2Vycy5jc3MiLz4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iaHR0cHM6Ly9yYXdnaXQuY29tL3B5dGhvbi12aXN1YWxpemF0aW9uL2ZvbGl1bS9tYXN0ZXIvZm9saXVtL3RlbXBsYXRlcy9sZWFmbGV0LmF3ZXNvbWUucm90YXRlLmNzcyIvPgogICAgPHN0eWxlPmh0bWwsIGJvZHkge3dpZHRoOiAxMDAlO2hlaWdodDogMTAwJTttYXJnaW46IDA7cGFkZGluZzogMDt9PC9zdHlsZT4KICAgIDxzdHlsZT4jbWFwIHtwb3NpdGlvbjphYnNvbHV0ZTt0b3A6MDtib3R0b206MDtyaWdodDowO2xlZnQ6MDt9PC9zdHlsZT4KICAgIAogICAgICAgICAgICA8c3R5bGU+ICNtYXBfMGYxMmI4NTYzY2U0NDk4NjkzNjIyMjBkZWQ3ZmMxYzAgewogICAgICAgICAgICAgICAgcG9zaXRpb24gOiByZWxhdGl2ZTsKICAgICAgICAgICAgICAgIHdpZHRoIDogMTAwLjAlOwogICAgICAgICAgICAgICAgaGVpZ2h0OiAxMDAuMCU7CiAgICAgICAgICAgICAgICBsZWZ0OiAwLjAlOwogICAgICAgICAgICAgICAgdG9wOiAwLjAlOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICA8L3N0eWxlPgogICAgICAgIAo8L2hlYWQ+Cjxib2R5PiAgICAKICAgIAogICAgICAgICAgICA8ZGl2IGNsYXNzPSJmb2xpdW0tbWFwIiBpZD0ibWFwXzBmMTJiODU2M2NlNDQ5ODY5MzYyMjIwZGVkN2ZjMWMwIiA+PC9kaXY+CiAgICAgICAgCjwvYm9keT4KPHNjcmlwdD4gICAgCiAgICAKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGJvdW5kcyA9IG51bGw7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgdmFyIG1hcF8wZjEyYjg1NjNjZTQ0OTg2OTM2MjIyMGRlZDdmYzFjMCA9IEwubWFwKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ21hcF8wZjEyYjg1NjNjZTQ0OTg2OTM2MjIyMGRlZDdmYzFjMCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7Y2VudGVyOiBbNDMuNjUzNDgxNywtNzkuMzgzOTM0N10sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB6b29tOiAxMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heEJvdW5kczogYm91bmRzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGF5ZXJzOiBbXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdvcmxkQ29weUp1bXA6IGZhbHNlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY3JzOiBMLkNSUy5FUFNHMzg1NwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9KTsKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHRpbGVfbGF5ZXJfYjU0MGRkM2E2NzgzNDJkMTlhNjFmMmRkN2RkYjEyMzkgPSBMLnRpbGVMYXllcigKICAgICAgICAgICAgICAgICdodHRwczovL3tzfS50aWxlLm9wZW5zdHJlZXRtYXAub3JnL3t6fS97eH0ve3l9LnBuZycsCiAgICAgICAgICAgICAgICB7CiAgImF0dHJpYnV0aW9uIjogbnVsbCwKICAiZGV0ZWN0UmV0aW5hIjogZmFsc2UsCiAgIm1heFpvb20iOiAxOCwKICAibWluWm9vbSI6IDEsCiAgIm5vV3JhcCI6IGZhbHNlLAogICJzdWJkb21haW5zIjogImFiYyIKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfMGYxMmI4NTYzY2U0NDk4NjkzNjIyMjBkZWQ3ZmMxYzApOwogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzExMTc5OWRlODI5ODQzNWY4Zjg2YjU0YTBjM2Y4ZjU3ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjU0MjU5OSwtNzkuMzYwNjM1OV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjZmYwMDAwIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiI2ZmMDAwMCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF8wZjEyYjg1NjNjZTQ0OTg2OTM2MjIyMGRlZDdmYzFjMCk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9hYmQyNGI0OWI5NTQ0NTMyYTg3MzA2MGJmYjhhMGE5ZSA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF85MWUyMDhkYWU4MWM0MGQzOWQwYWJkYTk0YmNjODQ1MiA9ICQoJzxkaXYgaWQ9Imh0bWxfOTFlMjA4ZGFlODFjNDBkMzlkMGFiZGE5NGJjYzg0NTIiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPlJlZ2VudCBQYXJrIC8gSGFyYm91cmZyb250IENsdXN0ZXIgMDwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfYWJkMjRiNDliOTU0NDUzMmE4NzMwNjBiZmI4YTBhOWUuc2V0Q29udGVudChodG1sXzkxZTIwOGRhZTgxYzQwZDM5ZDBhYmRhOTRiY2M4NDUyKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzExMTc5OWRlODI5ODQzNWY4Zjg2YjU0YTBjM2Y4ZjU3LmJpbmRQb3B1cChwb3B1cF9hYmQyNGI0OWI5NTQ0NTMyYTg3MzA2MGJmYjhhMGE5ZSk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl8xY2QxYTJiMzNiM2I0NzAxYWFhZmEyMWFhZDQ0MWJmYSA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY2MjMwMTUsLTc5LjM4OTQ5MzhdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmMDAwMCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZjAwMDAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfMGYxMmI4NTYzY2U0NDk4NjkzNjIyMjBkZWQ3ZmMxYzApOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfMjhiNGFkZTY3MzQ2NDk2NWJiMDgwYWZmMjM3NjI4ZjQgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfZDljY2NhMjhhOWRhNDk1ZGFjNmEwYmIzYTQ3MTcxMmIgPSAkKCc8ZGl2IGlkPSJodG1sX2Q5Y2NjYTI4YTlkYTQ5NWRhYzZhMGJiM2E0NzE3MTJiIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5RdWVlbiYjMzk7cyBQYXJrIC8gT250YXJpbyBQcm92aW5jaWFsIEdvdmVybm1lbnQgQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF8yOGI0YWRlNjczNDY0OTY1YmIwODBhZmYyMzc2MjhmNC5zZXRDb250ZW50KGh0bWxfZDljY2NhMjhhOWRhNDk1ZGFjNmEwYmIzYTQ3MTcxMmIpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfMWNkMWEyYjMzYjNiNDcwMWFhYWZhMjFhYWQ0NDFiZmEuYmluZFBvcHVwKHBvcHVwXzI4YjRhZGU2NzM0NjQ5NjViYjA4MGFmZjIzNzYyOGY0KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2Y4OTk0MzY0NDc5ZjQ0YzdiZmJlN2U4Njc3MjllNzRlID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjU3MTYxOCwtNzkuMzc4OTM3MDk5OTk5OTldLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmMDAwMCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZjAwMDAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfMGYxMmI4NTYzY2U0NDk4NjkzNjIyMjBkZWQ3ZmMxYzApOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfYmM4YTQ3ZmQ0NmYwNDk1OGE3M2FmMGYxYjY0ZTAzM2YgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfNmIyZjM3YjcyZWY1NGFkY2EyZDhjNDUwMTkyZTg2ODkgPSAkKCc8ZGl2IGlkPSJodG1sXzZiMmYzN2I3MmVmNTRhZGNhMmQ4YzQ1MDE5MmU4Njg5IiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5HYXJkZW4gRGlzdHJpY3QsIFJ5ZXJzb24gQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9iYzhhNDdmZDQ2ZjA0OTU4YTczYWYwZjFiNjRlMDMzZi5zZXRDb250ZW50KGh0bWxfNmIyZjM3YjcyZWY1NGFkY2EyZDhjNDUwMTkyZTg2ODkpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfZjg5OTQzNjQ0NzlmNDRjN2JmYmU3ZTg2NzcyOWU3NGUuYmluZFBvcHVwKHBvcHVwX2JjOGE0N2ZkNDZmMDQ5NThhNzNhZjBmMWI2NGUwMzNmKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzBhMTQ4Y2U3Y2YzODQwMGJiZTYxNGFjMDhlYThjYzRhID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjUxNDkzOSwtNzkuMzc1NDE3OV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjZmYwMDAwIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiI2ZmMDAwMCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF8wZjEyYjg1NjNjZTQ0OTg2OTM2MjIyMGRlZDdmYzFjMCk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF80NWY0ZTM0MWJiMjM0OGViYmJiMjgyNDczZTUzYjI1ZCA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF83Yzc0NWYwNzNhYzU0YWY1OTc0NWNjNDI5OGY0ODdjOCA9ICQoJzxkaXYgaWQ9Imh0bWxfN2M3NDVmMDczYWM1NGFmNTk3NDVjYzQyOThmNDg3YzgiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPlN0LiBKYW1lcyBUb3duIENsdXN0ZXIgMDwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfNDVmNGUzNDFiYjIzNDhlYmJiYjI4MjQ3M2U1M2IyNWQuc2V0Q29udGVudChodG1sXzdjNzQ1ZjA3M2FjNTRhZjU5NzQ1Y2M0Mjk4ZjQ4N2M4KTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzBhMTQ4Y2U3Y2YzODQwMGJiZTYxNGFjMDhlYThjYzRhLmJpbmRQb3B1cChwb3B1cF80NWY0ZTM0MWJiMjM0OGViYmJiMjgyNDczZTUzYjI1ZCk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl9jNmExYzU4MGY1ZDY0MjliODg4MThhNmJlYWY4ZjJiYyA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY3NjM1NzM5OTk5OTk5LC03OS4yOTMwMzEyXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZjAwMDAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmYwMDAwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzBmMTJiODU2M2NlNDQ5ODY5MzYyMjIwZGVkN2ZjMWMwKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzA5N2IwMzU5ZTYzOTQ5OGU5ZWM0MDNjOTQ1NmU1NzcwID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sX2Q0YWY5ZDlhZWI0ODQyMWU4MzNhMWUxN2VkYTE4ZGUyID0gJCgnPGRpdiBpZD0iaHRtbF9kNGFmOWQ5YWViNDg0MjFlODMzYTFlMTdlZGExOGRlMiIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+VGhlIEJlYWNoZXMgQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF8wOTdiMDM1OWU2Mzk0OThlOWVjNDAzYzk0NTZlNTc3MC5zZXRDb250ZW50KGh0bWxfZDRhZjlkOWFlYjQ4NDIxZTgzM2ExZTE3ZWRhMThkZTIpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfYzZhMWM1ODBmNWQ2NDI5Yjg4ODE4YTZiZWFmOGYyYmMuYmluZFBvcHVwKHBvcHVwXzA5N2IwMzU5ZTYzOTQ5OGU5ZWM0MDNjOTQ1NmU1NzcwKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2U0YzU4NGNmMDQzYjRiMWZiNWNkM2I3YmRkMGNhYmZmID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjQ0NzcwNzk5OTk5OTk2LC03OS4zNzMzMDY0XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZjAwMDAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmYwMDAwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzBmMTJiODU2M2NlNDQ5ODY5MzYyMjIwZGVkN2ZjMWMwKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzY5ZmJjNzA4OTFmZDQ0ZmY4ZmExZDE1ZTc2YTM5YjA3ID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzI0ZWIzZWFhZjI2MDQwYWNiYzJiMGJmODZmN2ZlZDE1ID0gJCgnPGRpdiBpZD0iaHRtbF8yNGViM2VhYWYyNjA0MGFjYmMyYjBiZjg2ZjdmZWQxNSIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+QmVyY3p5IFBhcmsgQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF82OWZiYzcwODkxZmQ0NGZmOGZhMWQxNWU3NmEzOWIwNy5zZXRDb250ZW50KGh0bWxfMjRlYjNlYWFmMjYwNDBhY2JjMmIwYmY4NmY3ZmVkMTUpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfZTRjNTg0Y2YwNDNiNGIxZmI1Y2QzYjdiZGQwY2FiZmYuYmluZFBvcHVwKHBvcHVwXzY5ZmJjNzA4OTFmZDQ0ZmY4ZmExZDE1ZTc2YTM5YjA3KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2QzMTVhZjhiMTA3OTQzMDk5MzRhMzQ0OTNhMGMwNWJlID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjU3OTUyNCwtNzkuMzg3MzgyNl0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjZmYwMDAwIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiI2ZmMDAwMCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF8wZjEyYjg1NjNjZTQ0OTg2OTM2MjIyMGRlZDdmYzFjMCk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF8zOTcwZGRiNTZhM2U0NzNmYWFhODk5MzA0ZDIxOWY4NCA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9mYjMzYjgyNWViNWM0Y2YxOThiNTgxYTZlYjc4ZmI4YyA9ICQoJzxkaXYgaWQ9Imh0bWxfZmIzM2I4MjVlYjVjNGNmMTk4YjU4MWE2ZWI3OGZiOGMiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkNlbnRyYWwgQmF5IFN0cmVldCBDbHVzdGVyIDA8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzM5NzBkZGI1NmEzZTQ3M2ZhYWE4OTkzMDRkMjE5Zjg0LnNldENvbnRlbnQoaHRtbF9mYjMzYjgyNWViNWM0Y2YxOThiNTgxYTZlYjc4ZmI4Yyk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9kMzE1YWY4YjEwNzk0MzA5OTM0YTM0NDkzYTBjMDViZS5iaW5kUG9wdXAocG9wdXBfMzk3MGRkYjU2YTNlNDczZmFhYTg5OTMwNGQyMTlmODQpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfODNiMzFiNmM4MGI3NDQyZTkxMmJkODhmNzE2ZDEzMzEgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42Njk1NDIsLTc5LjQyMjU2MzddLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmMDAwMCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZjAwMDAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfMGYxMmI4NTYzY2U0NDk4NjkzNjIyMjBkZWQ3ZmMxYzApOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfZGQ5MmFmODc1MWU4NGZkZjhkZDJlZjNkYTNmYWEyOTcgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfOTAxZGI5MGRkN2M4NGViNmI2MjgyNTlmOTMxOTBjZjggPSAkKCc8ZGl2IGlkPSJodG1sXzkwMWRiOTBkZDdjODRlYjZiNjI4MjU5ZjkzMTkwY2Y4IiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5DaHJpc3RpZSBDbHVzdGVyIDA8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2RkOTJhZjg3NTFlODRmZGY4ZGQyZWYzZGEzZmFhMjk3LnNldENvbnRlbnQoaHRtbF85MDFkYjkwZGQ3Yzg0ZWI2YjYyODI1OWY5MzE5MGNmOCk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl84M2IzMWI2YzgwYjc0NDJlOTEyYmQ4OGY3MTZkMTMzMS5iaW5kUG9wdXAocG9wdXBfZGQ5MmFmODc1MWU4NGZkZjhkZDJlZjNkYTNmYWEyOTcpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfNjQyNGI4ZmE1MDlkNDg5MGJiZjhkY2M3ZTBhZjZkMTcgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NTA1NzEyMDAwMDAwMSwtNzkuMzg0NTY3NV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjZmYwMDAwIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiI2ZmMDAwMCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF8wZjEyYjg1NjNjZTQ0OTg2OTM2MjIyMGRlZDdmYzFjMCk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF8yYmQ4ODAyZmU0MWE0ZTMzYWEzYWQ0ODNlMGU1OTNhYiA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9jM2ZlOTA4OTg1Nzg0NjE4YjZkYzllNzlkY2IxNTZkNiA9ICQoJzxkaXYgaWQ9Imh0bWxfYzNmZTkwODk4NTc4NDYxOGI2ZGM5ZTc5ZGNiMTU2ZDYiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPlJpY2htb25kIC8gQWRlbGFpZGUgLyBLaW5nIENsdXN0ZXIgMDwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfMmJkODgwMmZlNDFhNGUzM2FhM2FkNDgzZTBlNTkzYWIuc2V0Q29udGVudChodG1sX2MzZmU5MDg5ODU3ODQ2MThiNmRjOWU3OWRjYjE1NmQ2KTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzY0MjRiOGZhNTA5ZDQ4OTBiYmY4ZGNjN2UwYWY2ZDE3LmJpbmRQb3B1cChwb3B1cF8yYmQ4ODAyZmU0MWE0ZTMzYWEzYWQ0ODNlMGU1OTNhYik7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl8wZjdjYTgwOTE4OGE0NjA3OWU5ZmU4NzFkN2IzNWI3ZSA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY2OTAwNTEwMDAwMDAxLC03OS40NDIyNTkzXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZjAwMDAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmYwMDAwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzBmMTJiODU2M2NlNDQ5ODY5MzYyMjIwZGVkN2ZjMWMwKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzFjNzBiZDk2YzM3YzQ4NDVhY2RlZjgwNWUwZjBiNDI0ID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sX2Q3OWI4OGQ0NWZmYjRkOGY5YWU5N2RjYjNjMThkNzI0ID0gJCgnPGRpdiBpZD0iaHRtbF9kNzliODhkNDVmZmI0ZDhmOWFlOTdkY2IzYzE4ZDcyNCIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+RHVmZmVyaW4gLyBEb3ZlcmNvdXJ0IFZpbGxhZ2UgQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF8xYzcwYmQ5NmMzN2M0ODQ1YWNkZWY4MDVlMGYwYjQyNC5zZXRDb250ZW50KGh0bWxfZDc5Yjg4ZDQ1ZmZiNGQ4ZjlhZTk3ZGNiM2MxOGQ3MjQpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfMGY3Y2E4MDkxODhhNDYwNzllOWZlODcxZDdiMzViN2UuYmluZFBvcHVwKHBvcHVwXzFjNzBiZDk2YzM3YzQ4NDVhY2RlZjgwNWUwZjBiNDI0KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzRlODJhODg3ZmZkNDRlNmFhNWM4ZmE1ZjFjNDVmM2ViID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjQwODE1NywtNzkuMzgxNzUyMjk5OTk5OTldLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmMDAwMCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZjAwMDAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfMGYxMmI4NTYzY2U0NDk4NjkzNjIyMjBkZWQ3ZmMxYzApOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfMWQzZTllZmYxMmU1NGIzNGIwYjgzMTdiOThkYjZlOWIgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfNjY2YzljZmY5YzgwNGEyMzllOTViM2M0YzgxNWE2MjcgPSAkKCc8ZGl2IGlkPSJodG1sXzY2NmM5Y2ZmOWM4MDRhMjM5ZTk1YjNjNGM4MTVhNjI3IiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5IYXJib3VyZnJvbnQgRWFzdCAvIFVuaW9uIFN0YXRpb24gLyBUb3JvbnRvIElzbGFuZHMgQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF8xZDNlOWVmZjEyZTU0YjM0YjBiODMxN2I5OGRiNmU5Yi5zZXRDb250ZW50KGh0bWxfNjY2YzljZmY5YzgwNGEyMzllOTViM2M0YzgxNWE2MjcpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfNGU4MmE4ODdmZmQ0NGU2YWE1YzhmYTVmMWM0NWYzZWIuYmluZFBvcHVwKHBvcHVwXzFkM2U5ZWZmMTJlNTRiMzRiMGI4MzE3Yjk4ZGI2ZTliKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2I2Nzk2ZmRiMTdkNTRhNDBiZTljY2Y3M2RmN2Y5NjAyID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjQ3OTI2NzAwMDAwMDA2LC03OS40MTk3NDk3XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZjAwMDAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmYwMDAwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzBmMTJiODU2M2NlNDQ5ODY5MzYyMjIwZGVkN2ZjMWMwKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzI4OTNjZTA4ZGMzYTQ0YjQ4MmI3YmFkZjM3NjY3MTBhID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sX2VhMmMwZmFhZTgyNDRmYTBhMzk4N2Y2YmQ3NjZhMTRiID0gJCgnPGRpdiBpZD0iaHRtbF9lYTJjMGZhYWU4MjQ0ZmEwYTM5ODdmNmJkNzY2YTE0YiIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+TGl0dGxlIFBvcnR1Z2FsIC8gVHJpbml0eSBDbHVzdGVyIDA8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzI4OTNjZTA4ZGMzYTQ0YjQ4MmI3YmFkZjM3NjY3MTBhLnNldENvbnRlbnQoaHRtbF9lYTJjMGZhYWU4MjQ0ZmEwYTM5ODdmNmJkNzY2YTE0Yik7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9iNjc5NmZkYjE3ZDU0YTQwYmU5Y2NmNzNkZjdmOTYwMi5iaW5kUG9wdXAocG9wdXBfMjg5M2NlMDhkYzNhNDRiNDgyYjdiYWRmMzc2NjcxMGEpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfNzk4YjM4NWI1OGFjNDE1ZTk3OWU0YWEzZWNlYzRmZTEgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42Nzk1NTcxLC03OS4zNTIxODhdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmMDAwMCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZjAwMDAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfMGYxMmI4NTYzY2U0NDk4NjkzNjIyMjBkZWQ3ZmMxYzApOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfNzQ4N2MyNjYxMmI0NGVhYzlkODMyMDBkMTZjOWFmMTQgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfMjNiY2IyN2MxZmNkNDM1MDkwZTA0NWVlMTAzMDJlODEgPSAkKCc8ZGl2IGlkPSJodG1sXzIzYmNiMjdjMWZjZDQzNTA5MGUwNDVlZTEwMzAyZTgxIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5UaGUgRGFuZm9ydGggV2VzdCAvIFJpdmVyZGFsZSBDbHVzdGVyIDA8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzc0ODdjMjY2MTJiNDRlYWM5ZDgzMjAwZDE2YzlhZjE0LnNldENvbnRlbnQoaHRtbF8yM2JjYjI3YzFmY2Q0MzUwOTBlMDQ1ZWUxMDMwMmU4MSk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl83OThiMzg1YjU4YWM0MTVlOTc5ZTRhYTNlY2VjNGZlMS5iaW5kUG9wdXAocG9wdXBfNzQ4N2MyNjYxMmI0NGVhYzlkODMyMDBkMTZjOWFmMTQpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfY2ZiZGY2ZmFhODliNDRiNjgwNjU3ZGU2OGQ4ZjI5M2IgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NDcxNzY4LC03OS4zODE1NzY0MDAwMDAwMV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjZmYwMDAwIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiI2ZmMDAwMCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF8wZjEyYjg1NjNjZTQ0OTg2OTM2MjIyMGRlZDdmYzFjMCk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9lNzAyMTRlMDllMGE0ZDQ4YWI2ZmE5MmY3Yzg0NDUzOCA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9lZjY5NTQyMjdiZjI0ODgxYTE0NDc4Mzc2MTMyY2Q3OCA9ICQoJzxkaXYgaWQ9Imh0bWxfZWY2OTU0MjI3YmYyNDg4MWExNDQ3ODM3NjEzMmNkNzgiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPlRvcm9udG8gRG9taW5pb24gQ2VudHJlIC8gRGVzaWduIEV4Y2hhbmdlIENsdXN0ZXIgMDwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfZTcwMjE0ZTA5ZTBhNGQ0OGFiNmZhOTJmN2M4NDQ1Mzguc2V0Q29udGVudChodG1sX2VmNjk1NDIyN2JmMjQ4ODFhMTQ0NzgzNzYxMzJjZDc4KTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyX2NmYmRmNmZhYTg5YjQ0YjY4MDY1N2RlNjhkOGYyOTNiLmJpbmRQb3B1cChwb3B1cF9lNzAyMTRlMDllMGE0ZDQ4YWI2ZmE5MmY3Yzg0NDUzOCk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl8zYTdkNTdhNGRlODI0YTgxYWE4ZWEyN2I4N2YxOTc5YiA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjYzNjg0NzIsLTc5LjQyODE5MTQwMDAwMDAyXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZjAwMDAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmYwMDAwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzBmMTJiODU2M2NlNDQ5ODY5MzYyMjIwZGVkN2ZjMWMwKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzU2NGE3NDhlOWY5YjQ3ZTFhOGY5MWE3NDczYWE0NTY4ID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzk1NjcxMTE4OWI4NDQxYzI4OTM1Mzk1MTdkNmRlYzlkID0gJCgnPGRpdiBpZD0iaHRtbF85NTY3MTExODliODQ0MWMyODkzNTM5NTE3ZDZkZWM5ZCIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+QnJvY2t0b24gLyBQYXJrZGFsZSBWaWxsYWdlIC8gRXhoaWJpdGlvbiBQbGFjZSBDbHVzdGVyIDA8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzU2NGE3NDhlOWY5YjQ3ZTFhOGY5MWE3NDczYWE0NTY4LnNldENvbnRlbnQoaHRtbF85NTY3MTExODliODQ0MWMyODkzNTM5NTE3ZDZkZWM5ZCk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl8zYTdkNTdhNGRlODI0YTgxYWE4ZWEyN2I4N2YxOTc5Yi5iaW5kUG9wdXAocG9wdXBfNTY0YTc0OGU5ZjliNDdlMWE4ZjkxYTc0NzNhYTQ1NjgpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfNmFhNjJhNTU1MjU5NDk4NGJlNTYwYjMxNGZmZDM5OTcgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42Njg5OTg1LC03OS4zMTU1NzE1OTk5OTk5OF0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjZmYwMDAwIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiI2ZmMDAwMCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF8wZjEyYjg1NjNjZTQ0OTg2OTM2MjIyMGRlZDdmYzFjMCk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9jMDE5ZWI4Y2JkNWY0MDUyOTU2YmVmM2NkMzVhMThjYyA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF85OTFmNGIxZTFkZDA0MjE2YjQyMmM2YzA5NGNjMTM0YyA9ICQoJzxkaXYgaWQ9Imh0bWxfOTkxZjRiMWUxZGQwNDIxNmI0MjJjNmMwOTRjYzEzNGMiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkluZGlhIEJhemFhciAvIFRoZSBCZWFjaGVzIFdlc3QgQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9jMDE5ZWI4Y2JkNWY0MDUyOTU2YmVmM2NkMzVhMThjYy5zZXRDb250ZW50KGh0bWxfOTkxZjRiMWUxZGQwNDIxNmI0MjJjNmMwOTRjYzEzNGMpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfNmFhNjJhNTU1MjU5NDk4NGJlNTYwYjMxNGZmZDM5OTcuYmluZFBvcHVwKHBvcHVwX2MwMTllYjhjYmQ1ZjQwNTI5NTZiZWYzY2QzNWExOGNjKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzViMGE4OTIxNmVkMDQxYjViMTIyN2VhMGQ4Mzg5MGVlID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjQ4MTk4NSwtNzkuMzc5ODE2OTAwMDAwMDFdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmMDAwMCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZjAwMDAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfMGYxMmI4NTYzY2U0NDk4NjkzNjIyMjBkZWQ3ZmMxYzApOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfMTUxM2I5NGNjYWIyNDdkNThlNjRjOTNlOTMxZjRlODkgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfNDhlYWE5MTFlYjNkNGRmYmI3ZDYyOWY5NDBkZTQxN2IgPSAkKCc8ZGl2IGlkPSJodG1sXzQ4ZWFhOTExZWIzZDRkZmJiN2Q2MjlmOTQwZGU0MTdiIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5Db21tZXJjZSBDb3VydCAvIFZpY3RvcmlhIEhvdGVsIENsdXN0ZXIgMDwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfMTUxM2I5NGNjYWIyNDdkNThlNjRjOTNlOTMxZjRlODkuc2V0Q29udGVudChodG1sXzQ4ZWFhOTExZWIzZDRkZmJiN2Q2MjlmOTQwZGU0MTdiKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzViMGE4OTIxNmVkMDQxYjViMTIyN2VhMGQ4Mzg5MGVlLmJpbmRQb3B1cChwb3B1cF8xNTEzYjk0Y2NhYjI0N2Q1OGU2NGM5M2U5MzFmNGU4OSk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl80ZDk2NmNiYzFmOTE0NTQ4YmI2YjQwMWNjNDE2MTczYiA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY1OTUyNTUsLTc5LjM0MDkyM10sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjZmYwMDAwIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiI2ZmMDAwMCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF8wZjEyYjg1NjNjZTQ0OTg2OTM2MjIyMGRlZDdmYzFjMCk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9lNWQxMDQ2OTFlOTY0MDk2YjEwZGRlNWY2YjJhYjM5NSA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF80NzU1NGRmNjJhNmY0OTkxYTNmZjJhNzcxNzVhNGZiNCA9ICQoJzxkaXYgaWQ9Imh0bWxfNDc1NTRkZjYyYTZmNDk5MWEzZmYyYTc3MTc1YTRmYjQiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPlN0dWRpbyBEaXN0cmljdCBDbHVzdGVyIDA8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2U1ZDEwNDY5MWU5NjQwOTZiMTBkZGU1ZjZiMmFiMzk1LnNldENvbnRlbnQoaHRtbF80NzU1NGRmNjJhNmY0OTkxYTNmZjJhNzcxNzVhNGZiNCk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl80ZDk2NmNiYzFmOTE0NTQ4YmI2YjQwMWNjNDE2MTczYi5iaW5kUG9wdXAocG9wdXBfZTVkMTA0NjkxZTk2NDA5NmIxMGRkZTVmNmIyYWIzOTUpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfZDkwMjdhYjQxYWIxNDE2YThkNjE4YjI5OTM3NWEwNGMgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My43MjgwMjA1LC03OS4zODg3OTAxXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZmIzNjAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmZiMzYwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzBmMTJiODU2M2NlNDQ5ODY5MzYyMjIwZGVkN2ZjMWMwKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzI3ZWUwMGVkNDI0YzQ1MDViYTQwNDE4MDE4OWEyNzM4ID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sX2I1ZjlhNTU1NDg0MjQ3M2ViMTU0ZmJkZTViMTcxNTQ4ID0gJCgnPGRpdiBpZD0iaHRtbF9iNWY5YTU1NTQ4NDI0NzNlYjE1NGZiZGU1YjE3MTU0OCIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+TGF3cmVuY2UgUGFyayBDbHVzdGVyIDQ8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzI3ZWUwMGVkNDI0YzQ1MDViYTQwNDE4MDE4OWEyNzM4LnNldENvbnRlbnQoaHRtbF9iNWY5YTU1NTQ4NDI0NzNlYjE1NGZiZGU1YjE3MTU0OCk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9kOTAyN2FiNDFhYjE0MTZhOGQ2MThiMjk5Mzc1YTA0Yy5iaW5kUG9wdXAocG9wdXBfMjdlZTAwZWQ0MjRjNDUwNWJhNDA0MTgwMTg5YTI3MzgpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfM2U2YjViYzQzNTcyNGZkYWE0ODY4MzcxMDJkNDA5ZDUgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My43MTE2OTQ4LC03OS40MTY5MzU1OTk5OTk5OV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjMDBiNWViIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzAwYjVlYiIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF8wZjEyYjg1NjNjZTQ0OTg2OTM2MjIyMGRlZDdmYzFjMCk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF80MmJkMjMwZjdiN2Q0OGY4OTg1MjBhYWIxMTFlZGRhZCA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9iZDE1MWZlNDJjNDI0OWZjODhhNzBkNDM4NzQ3NmNkMSA9ICQoJzxkaXYgaWQ9Imh0bWxfYmQxNTFmZTQyYzQyNDlmYzg4YTcwZDQzODc0NzZjZDEiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPlJvc2VsYXduIENsdXN0ZXIgMjwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfNDJiZDIzMGY3YjdkNDhmODk4NTIwYWFiMTExZWRkYWQuc2V0Q29udGVudChodG1sX2JkMTUxZmU0MmM0MjQ5ZmM4OGE3MGQ0Mzg3NDc2Y2QxKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzNlNmI1YmM0MzU3MjRmZGFhNDg2ODM3MTAyZDQwOWQ1LmJpbmRQb3B1cChwb3B1cF80MmJkMjMwZjdiN2Q0OGY4OTg1MjBhYWIxMTFlZGRhZCk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl8zOTA0ODNmMjg5MWQ0ZDQzYTU5NWQ1ZWU2NjM4M2Y4MSA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjcxMjc1MTEsLTc5LjM5MDE5NzVdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmMDAwMCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZjAwMDAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfMGYxMmI4NTYzY2U0NDk4NjkzNjIyMjBkZWQ3ZmMxYzApOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfOWRiYzhhYTAzOTFhNDNmOGE4Y2YxNGJkNTNhNjg1OTAgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfYzczZDIzNzA2NDIxNGVkZWEzYzNiMWUwMWQ2NWE2OGIgPSAkKCc8ZGl2IGlkPSJodG1sX2M3M2QyMzcwNjQyMTRlZGVhM2MzYjFlMDFkNjVhNjhiIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5EYXZpc3ZpbGxlIE5vcnRoIENsdXN0ZXIgMDwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfOWRiYzhhYTAzOTFhNDNmOGE4Y2YxNGJkNTNhNjg1OTAuc2V0Q29udGVudChodG1sX2M3M2QyMzcwNjQyMTRlZGVhM2MzYjFlMDFkNjVhNjhiKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzM5MDQ4M2YyODkxZDRkNDNhNTk1ZDVlZTY2MzgzZjgxLmJpbmRQb3B1cChwb3B1cF85ZGJjOGFhMDM5MWE0M2Y4YThjZjE0YmQ1M2E2ODU5MCk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl81ZDI0OGIxZTcyN2E0Zjg0OTZhMGExOWZiNzhiMmVkZiA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY5Njk0NzYsLTc5LjQxMTMwNzIwMDAwMDAxXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiM4MGZmYjQiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjODBmZmI0IiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzBmMTJiODU2M2NlNDQ5ODY5MzYyMjIwZGVkN2ZjMWMwKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzM5N2RiNDQwN2Y4YTQ3Y2U4NmFmOWJiMGU2Y2NjMzg0ID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzIzMWY5ZWFhN2U3ZTQxZWM4YTY3MTUwNmRjNjk1M2I5ID0gJCgnPGRpdiBpZD0iaHRtbF8yMzFmOWVhYTdlN2U0MWVjOGE2NzE1MDZkYzY5NTNiOSIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+Rm9yZXN0IEhpbGwgTm9ydGggJmFtcDsgV2VzdCBDbHVzdGVyIDM8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzM5N2RiNDQwN2Y4YTQ3Y2U4NmFmOWJiMGU2Y2NjMzg0LnNldENvbnRlbnQoaHRtbF8yMzFmOWVhYTdlN2U0MWVjOGE2NzE1MDZkYzY5NTNiOSk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl81ZDI0OGIxZTcyN2E0Zjg0OTZhMGExOWZiNzhiMmVkZi5iaW5kUG9wdXAocG9wdXBfMzk3ZGI0NDA3ZjhhNDdjZTg2YWY5YmIwZTZjY2MzODQpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfZGEyZjkzNzE2Mjg0NDg4NTlhN2UzZTZjZGMzN2RlMDggPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NjE2MDgzLC03OS40NjQ3NjMyOTk5OTk5OV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjZmYwMDAwIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiI2ZmMDAwMCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF8wZjEyYjg1NjNjZTQ0OTg2OTM2MjIyMGRlZDdmYzFjMCk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9iYzAwNWY0OTkzYWY0M2ZhODQyZTA1NGM3ZTMzZDcyNCA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF85ODJmYWU4NThkNWU0ODY4OWMwNzE3ZDk0MmM5ZWUyYSA9ICQoJzxkaXYgaWQ9Imh0bWxfOTgyZmFlODU4ZDVlNDg2ODljMDcxN2Q5NDJjOWVlMmEiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkhpZ2ggUGFyayAvIFRoZSBKdW5jdGlvbiBTb3V0aCBDbHVzdGVyIDA8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2JjMDA1ZjQ5OTNhZjQzZmE4NDJlMDU0YzdlMzNkNzI0LnNldENvbnRlbnQoaHRtbF85ODJmYWU4NThkNWU0ODY4OWMwNzE3ZDk0MmM5ZWUyYSk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9kYTJmOTM3MTYyODQ0ODg1OWE3ZTNlNmNkYzM3ZGUwOC5iaW5kUG9wdXAocG9wdXBfYmMwMDVmNDk5M2FmNDNmYTg0MmUwNTRjN2UzM2Q3MjQpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfNWQzYzc2ZTRlYzBiNGYxZDgxN2U3YjUyNDRmYjVlYWQgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My43MTUzODM0LC03OS40MDU2Nzg0MDAwMDAwMV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjZmYwMDAwIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiI2ZmMDAwMCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF8wZjEyYjg1NjNjZTQ0OTg2OTM2MjIyMGRlZDdmYzFjMCk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9iYWZiNzVjZTY0M2M0NjQ0OWYzNTUyZTg1ZGM2YTFjNSA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF8zNDUyYTMzZGI5YTQ0MzdkOTQ5NjNhOWM5OGM3ZGZiZCA9ICQoJzxkaXYgaWQ9Imh0bWxfMzQ1MmEzM2RiOWE0NDM3ZDk0OTYzYTljOThjN2RmYmQiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPk5vcnRoIFRvcm9udG8gV2VzdCBDbHVzdGVyIDA8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2JhZmI3NWNlNjQzYzQ2NDQ5ZjM1NTJlODVkYzZhMWM1LnNldENvbnRlbnQoaHRtbF8zNDUyYTMzZGI5YTQ0MzdkOTQ5NjNhOWM5OGM3ZGZiZCk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl81ZDNjNzZlNGVjMGI0ZjFkODE3ZTdiNTI0NGZiNWVhZC5iaW5kUG9wdXAocG9wdXBfYmFmYjc1Y2U2NDNjNDY0NDlmMzU1MmU4NWRjNmExYzUpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfMDI3YmJiN2NiYjgzNGE4NzlhNTNmM2I2YTZhMmUwMTAgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NzI3MDk3LC03OS40MDU2Nzg0MDAwMDAwMV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjZmYwMDAwIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiI2ZmMDAwMCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF8wZjEyYjg1NjNjZTQ0OTg2OTM2MjIyMGRlZDdmYzFjMCk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9jZjIwMzdkZWQxMWE0MDdiODU2MTc4NmNkYTU4OGNmOCA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9lMGY0Y2JiZTVjODE0MjFlYjM0OGViNDlhN2ViMDUwNCA9ICQoJzxkaXYgaWQ9Imh0bWxfZTBmNGNiYmU1YzgxNDIxZWIzNDhlYjQ5YTdlYjA1MDQiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPlRoZSBBbm5leCAvIE5vcnRoIE1pZHRvd24gLyBZb3JrdmlsbGUgQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9jZjIwMzdkZWQxMWE0MDdiODU2MTc4NmNkYTU4OGNmOC5zZXRDb250ZW50KGh0bWxfZTBmNGNiYmU1YzgxNDIxZWIzNDhlYjQ5YTdlYjA1MDQpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfMDI3YmJiN2NiYjgzNGE4NzlhNTNmM2I2YTZhMmUwMTAuYmluZFBvcHVwKHBvcHVwX2NmMjAzN2RlZDExYTQwN2I4NTYxNzg2Y2RhNTg4Y2Y4KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzBjMjcwMTJmMTJiNDQyZWRiNzEwNTY5ZjJiYmY2OTk2ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjQ4OTU5NywtNzkuNDU2MzI1XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZjAwMDAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmYwMDAwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzBmMTJiODU2M2NlNDQ5ODY5MzYyMjIwZGVkN2ZjMWMwKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzI5YTVkMTcxOWViMDRmM2ZhMDEzOGE1ZTljN2RhNDUwID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sX2E5MDI5NjVkNjNhMDQxYmNhYThiYzQzNDA2MDBmNzI0ID0gJCgnPGRpdiBpZD0iaHRtbF9hOTAyOTY1ZDYzYTA0MWJjYWE4YmM0MzQwNjAwZjcyNCIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+UGFya2RhbGUgLyBSb25jZXN2YWxsZXMgQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF8yOWE1ZDE3MTllYjA0ZjNmYTAxMzhhNWU5YzdkYTQ1MC5zZXRDb250ZW50KGh0bWxfYTkwMjk2NWQ2M2EwNDFiY2FhOGJjNDM0MDYwMGY3MjQpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfMGMyNzAxMmYxMmI0NDJlZGI3MTA1NjlmMmJiZjY5OTYuYmluZFBvcHVwKHBvcHVwXzI5YTVkMTcxOWViMDRmM2ZhMDEzOGE1ZTljN2RhNDUwKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2NjODk5ODI3ZjQzNTRkN2ZiYTkxNjU3NmRjN2U3MzNhID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNzA0MzI0NCwtNzkuMzg4NzkwMV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjZmYwMDAwIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiI2ZmMDAwMCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF8wZjEyYjg1NjNjZTQ0OTg2OTM2MjIyMGRlZDdmYzFjMCk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9mMjk3MzMxZDc2MmU0NmVkOTNhODdlNDU0ZWM4YWZjMyA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF8zNjQzZDdlZWQ0NzE0MmIwYWFlZWQ5YjZiNTcyNWIwNSA9ICQoJzxkaXYgaWQ9Imh0bWxfMzY0M2Q3ZWVkNDcxNDJiMGFhZWVkOWI2YjU3MjViMDUiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkRhdmlzdmlsbGUgQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9mMjk3MzMxZDc2MmU0NmVkOTNhODdlNDU0ZWM4YWZjMy5zZXRDb250ZW50KGh0bWxfMzY0M2Q3ZWVkNDcxNDJiMGFhZWVkOWI2YjU3MjViMDUpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfY2M4OTk4MjdmNDM1NGQ3ZmJhOTE2NTc2ZGM3ZTczM2EuYmluZFBvcHVwKHBvcHVwX2YyOTczMzFkNzYyZTQ2ZWQ5M2E4N2U0NTRlYzhhZmMzKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzUwYTc4YjMzZmE0NTQzZDRhNWY1ZDI2NTM4ZThjNzU2ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjYyNjk1NiwtNzkuNDAwMDQ5M10sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjZmYwMDAwIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiI2ZmMDAwMCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF8wZjEyYjg1NjNjZTQ0OTg2OTM2MjIyMGRlZDdmYzFjMCk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF8yNWY3ZDM1YzE4YmQ0YmI3YWI4YmY3ZDczYzJkMDZjMCA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9mODA3OGI1OGIxNTc0ZGM5YjcyODc4N2VhMGM1NjA2MyA9ICQoJzxkaXYgaWQ9Imh0bWxfZjgwNzhiNThiMTU3NGRjOWI3Mjg3ODdlYTBjNTYwNjMiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPlVuaXZlcnNpdHkgb2YgVG9yb250byAvIEhhcmJvcmQgQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF8yNWY3ZDM1YzE4YmQ0YmI3YWI4YmY3ZDczYzJkMDZjMC5zZXRDb250ZW50KGh0bWxfZjgwNzhiNThiMTU3NGRjOWI3Mjg3ODdlYTBjNTYwNjMpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfNTBhNzhiMzNmYTQ1NDNkNGE1ZjVkMjY1MzhlOGM3NTYuYmluZFBvcHVwKHBvcHVwXzI1ZjdkMzVjMThiZDRiYjdhYjhiZjdkNzNjMmQwNmMwKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2EwOWYxNGUxMjE2NTQ1ZDc5ZWU5YmIwZjBlNjdiOTMxID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjUxNTcwNiwtNzkuNDg0NDQ5OV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjZmYwMDAwIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiI2ZmMDAwMCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF8wZjEyYjg1NjNjZTQ0OTg2OTM2MjIyMGRlZDdmYzFjMCk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF80YmQxNjQwZDZkMTc0ZTkwOTdkNDgzNWM5NDgwOWRmNSA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9kNmIyMDQ5YmU5MzM0NTFiYjQ5ODg5YmRjYmI5NDc4ZiA9ICQoJzxkaXYgaWQ9Imh0bWxfZDZiMjA0OWJlOTMzNDUxYmI0OTg4OWJkY2JiOTQ3OGYiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPlJ1bm55bWVkZSAvIFN3YW5zZWEgQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF80YmQxNjQwZDZkMTc0ZTkwOTdkNDgzNWM5NDgwOWRmNS5zZXRDb250ZW50KGh0bWxfZDZiMjA0OWJlOTMzNDUxYmI0OTg4OWJkY2JiOTQ3OGYpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfYTA5ZjE0ZTEyMTY1NDVkNzllZTliYjBmMGU2N2I5MzEuYmluZFBvcHVwKHBvcHVwXzRiZDE2NDBkNmQxNzRlOTA5N2Q0ODM1Yzk0ODA5ZGY1KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzFjYTE2ZTNlMmU1YjRhODQ5ZTExZDNhZmU5MThhM2E1ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjg5NTc0MywtNzkuMzgzMTU5OTAwMDAwMDFdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiIzgwMDBmZiIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiM4MDAwZmYiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfMGYxMmI4NTYzY2U0NDk4NjkzNjIyMjBkZWQ3ZmMxYzApOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfYjYzYTQ5NjM4ZTUwNDljMjhkOTk5Yzk4ZDFjZDhlODcgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfYjgyMGM1ZWM0ODkxNDhiYWJkNzhmYTFmZDFjMTNhNmQgPSAkKCc8ZGl2IGlkPSJodG1sX2I4MjBjNWVjNDg5MTQ4YmFiZDc4ZmExZmQxYzEzYTZkIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5Nb29yZSBQYXJrIC8gU3VtbWVyaGlsbCBFYXN0IENsdXN0ZXIgMTwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfYjYzYTQ5NjM4ZTUwNDljMjhkOTk5Yzk4ZDFjZDhlODcuc2V0Q29udGVudChodG1sX2I4MjBjNWVjNDg5MTQ4YmFiZDc4ZmExZmQxYzEzYTZkKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzFjYTE2ZTNlMmU1YjRhODQ5ZTExZDNhZmU5MThhM2E1LmJpbmRQb3B1cChwb3B1cF9iNjNhNDk2MzhlNTA0OWMyOGQ5OTljOThkMWNkOGU4Nyk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl8yYmVkNDA5ZDk1MTA0MTRlYjEwMjlkOTk4NDlkNWFiNCA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY1MzIwNTcsLTc5LjQwMDA0OTNdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmMDAwMCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZjAwMDAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfMGYxMmI4NTYzY2U0NDk4NjkzNjIyMjBkZWQ3ZmMxYzApOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfOWQ1OWI1NzkzNDM3NDk1ODg2OGExZTJhMWMyNTZhMmMgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfZjQ5OGQ0Mjk2YTI2NGU0NmFkYjIwMjUwOTg2ZmE2NjQgPSAkKCc8ZGl2IGlkPSJodG1sX2Y0OThkNDI5NmEyNjRlNDZhZGIyMDI1MDk4NmZhNjY0IiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5LZW5zaW5ndG9uIE1hcmtldCAvIENoaW5hdG93biAvIEdyYW5nZSBQYXJrIENsdXN0ZXIgMDwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfOWQ1OWI1NzkzNDM3NDk1ODg2OGExZTJhMWMyNTZhMmMuc2V0Q29udGVudChodG1sX2Y0OThkNDI5NmEyNjRlNDZhZGIyMDI1MDk4NmZhNjY0KTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzJiZWQ0MDlkOTUxMDQxNGViMTAyOWQ5OTg0OWQ1YWI0LmJpbmRQb3B1cChwb3B1cF85ZDU5YjU3OTM0Mzc0OTU4ODY4YTFlMmExYzI1NmEyYyk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl8xNDFlN2QyOGYzYzg0ZTk4YjEwMDZhOTMyM2RhZDRkOCA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY4NjQxMjI5OTk5OTk5LC03OS40MDAwNDkzXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZjAwMDAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmYwMDAwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzBmMTJiODU2M2NlNDQ5ODY5MzYyMjIwZGVkN2ZjMWMwKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzUwZDcxOTkyY2M1MjQzYTg4NWVhZTMxMTkzYTAzZDRjID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sX2VlNzBhMmUwNWE1ODQ4YTA5Y2FkNWE1ZTNjYTM2NmViID0gJCgnPGRpdiBpZD0iaHRtbF9lZTcwYTJlMDVhNTg0OGEwOWNhZDVhNWUzY2EzNjZlYiIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+U3VtbWVyaGlsbCBXZXN0IC8gUmF0aG5lbGx5IC8gU291dGggSGlsbCAvIEZvcmVzdCBIaWxsIFNFIC8gRGVlciBQYXJrIENsdXN0ZXIgMDwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfNTBkNzE5OTJjYzUyNDNhODg1ZWFlMzExOTNhMDNkNGMuc2V0Q29udGVudChodG1sX2VlNzBhMmUwNWE1ODQ4YTA5Y2FkNWE1ZTNjYTM2NmViKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzE0MWU3ZDI4ZjNjODRlOThiMTAwNmE5MzIzZGFkNGQ4LmJpbmRQb3B1cChwb3B1cF81MGQ3MTk5MmNjNTI0M2E4ODVlYWUzMTE5M2EwM2Q0Yyk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl9jY2EyZThiZTRkNjI0YjI3YmNkZGIzNTE2N2E3ZmEyMCA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjYyODk0NjcsLTc5LjM5NDQxOTldLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmMDAwMCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZjAwMDAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfMGYxMmI4NTYzY2U0NDk4NjkzNjIyMjBkZWQ3ZmMxYzApOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfMWVjNmYzOTEyODcwNGRjMDhmY2Y4YzQ1YjkwNTRjMWUgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfMmUxNDczZWMyYTI0NDRmNTkzODc0ZGJmOWFkYzk0NGMgPSAkKCc8ZGl2IGlkPSJodG1sXzJlMTQ3M2VjMmEyNDQ0ZjU5Mzg3NGRiZjlhZGM5NDRjIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5DTiBUb3dlciAvIEtpbmcgYW5kIFNwYWRpbmEgLyBSYWlsd2F5IExhbmRzIC8gSGFyYm91cmZyb250IFdlc3QgLyBCYXRodXJzdCAgUXVheSAvIFNvdXRoIE5pYWdhcmEgLyBJc2xhbmQgYWlycG9ydCBDbHVzdGVyIDA8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzFlYzZmMzkxMjg3MDRkYzA4ZmNmOGM0NWI5MDU0YzFlLnNldENvbnRlbnQoaHRtbF8yZTE0NzNlYzJhMjQ0NGY1OTM4NzRkYmY5YWRjOTQ0Yyk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9jY2EyZThiZTRkNjI0YjI3YmNkZGIzNTE2N2E3ZmEyMC5iaW5kUG9wdXAocG9wdXBfMWVjNmYzOTEyODcwNGRjMDhmY2Y4YzQ1YjkwNTRjMWUpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfNWJjNzJkNTZlM2JiNGUyYWJlMWMyNjBlYjQ1NWZjZTIgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42Nzk1NjI2LC03OS4zNzc1Mjk0MDAwMDAwMV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjODAwMGZmIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzgwMDBmZiIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF8wZjEyYjg1NjNjZTQ0OTg2OTM2MjIyMGRlZDdmYzFjMCk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF8wMGRlNTE4ZjRhNWY0NzI4YmM2M2QwNDRhZTI2NWVlNCA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF85YjYxMmQwY2UyNGI0Y2QzODFmNWEyNDNmYjM0NTEzMSA9ICQoJzxkaXYgaWQ9Imh0bWxfOWI2MTJkMGNlMjRiNGNkMzgxZjVhMjQzZmIzNDUxMzEiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPlJvc2VkYWxlIENsdXN0ZXIgMTwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfMDBkZTUxOGY0YTVmNDcyOGJjNjNkMDQ0YWUyNjVlZTQuc2V0Q29udGVudChodG1sXzliNjEyZDBjZTI0YjRjZDM4MWY1YTI0M2ZiMzQ1MTMxKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzViYzcyZDU2ZTNiYjRlMmFiZTFjMjYwZWI0NTVmY2UyLmJpbmRQb3B1cChwb3B1cF8wMGRlNTE4ZjRhNWY0NzI4YmM2M2QwNDRhZTI2NWVlNCk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl83NzVlNzBjZTM2Mjk0ZjM0YWQzOTcyN2JmMDU1YjFjOCA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY0NjQzNTIsLTc5LjM3NDg0NTk5OTk5OTk5XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZjAwMDAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmYwMDAwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzBmMTJiODU2M2NlNDQ5ODY5MzYyMjIwZGVkN2ZjMWMwKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzY5ZDE1MjM0MGVhNDQwM2ZhZGM2MmUwOTA0YzcyN2MxID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzZiM2ZiMmVhMGFjODQ1Y2I4NDA0Nzg2NWExNzljNjRkID0gJCgnPGRpdiBpZD0iaHRtbF82YjNmYjJlYTBhYzg0NWNiODQwNDc4NjVhMTc5YzY0ZCIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+U3RuIEEgUE8gQm94ZXMgQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF82OWQxNTIzNDBlYTQ0MDNmYWRjNjJlMDkwNGM3MjdjMS5zZXRDb250ZW50KGh0bWxfNmIzZmIyZWEwYWM4NDVjYjg0MDQ3ODY1YTE3OWM2NGQpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfNzc1ZTcwY2UzNjI5NGYzNGFkMzk3MjdiZjA1NWIxYzguYmluZFBvcHVwKHBvcHVwXzY5ZDE1MjM0MGVhNDQwM2ZhZGM2MmUwOTA0YzcyN2MxKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzBhNTVkYzI4ZmVjMDQ2MWM5MTUyMTRkZjZlNTAyMTNhID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjY3OTY3LC03OS4zNjc2NzUzXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZjAwMDAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmYwMDAwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzBmMTJiODU2M2NlNDQ5ODY5MzYyMjIwZGVkN2ZjMWMwKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzMwOTk5MjI1NjUzNzQzNDliODE3Y2U5OTQzZWQyZTIwID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzllOGVjZjYzNGMzZTRhZjk4MjE5OGI2ZmZiYmI3NDY2ID0gJCgnPGRpdiBpZD0iaHRtbF85ZThlY2Y2MzRjM2U0YWY5ODIxOThiNmZmYmJiNzQ2NiIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+U3QuIEphbWVzIFRvd24gLyBDYWJiYWdldG93biBDbHVzdGVyIDA8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzMwOTk5MjI1NjUzNzQzNDliODE3Y2U5OTQzZWQyZTIwLnNldENvbnRlbnQoaHRtbF85ZThlY2Y2MzRjM2U0YWY5ODIxOThiNmZmYmJiNzQ2Nik7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl8wYTU1ZGMyOGZlYzA0NjFjOTE1MjE0ZGY2ZTUwMjEzYS5iaW5kUG9wdXAocG9wdXBfMzA5OTkyMjU2NTM3NDM0OWI4MTdjZTk5NDNlZDJlMjApOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfZTA0OWEwMGRhZDA0NDUyZTljZGZmOTE1Mzc2YjIzZjEgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NDg0MjkyLC03OS4zODIyODAyXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZjAwMDAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmYwMDAwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzBmMTJiODU2M2NlNDQ5ODY5MzYyMjIwZGVkN2ZjMWMwKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzJkZGM2Zjg3ZDUzZTRlZmM5Yjk2MmM1ODJiYzRlZTA4ID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzhjMzgyZDNkOWUwMDQxNGZhNjA2YTA1NDE5MjQ1ODk0ID0gJCgnPGRpdiBpZD0iaHRtbF84YzM4MmQzZDllMDA0MTRmYTYwNmEwNTQxOTI0NTg5NCIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+Rmlyc3QgQ2FuYWRpYW4gUGxhY2UgLyBVbmRlcmdyb3VuZCBjaXR5IENsdXN0ZXIgMDwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfMmRkYzZmODdkNTNlNGVmYzliOTYyYzU4MmJjNGVlMDguc2V0Q29udGVudChodG1sXzhjMzgyZDNkOWUwMDQxNGZhNjA2YTA1NDE5MjQ1ODk0KTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyX2UwNDlhMDBkYWQwNDQ1MmU5Y2RmZjkxNTM3NmIyM2YxLmJpbmRQb3B1cChwb3B1cF8yZGRjNmY4N2Q1M2U0ZWZjOWI5NjJjNTgyYmM0ZWUwOCk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl80OTVjODc2OWNmMWQ0NTNkODI1NzhkM2ZmYTllYThjMCA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY2NTg1OTksLTc5LjM4MzE1OTkwMDAwMDAxXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZjAwMDAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmYwMDAwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzBmMTJiODU2M2NlNDQ5ODY5MzYyMjIwZGVkN2ZjMWMwKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzQ4YmJiMDBmNTJiZjQ5M2JiNzg3ZDU4MTJiYWNiMjdlID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sX2M1Yjc0NWY2NThkMzRjY2Y4MmY0OTlhODVhMjQwNWU0ID0gJCgnPGRpdiBpZD0iaHRtbF9jNWI3NDVmNjU4ZDM0Y2NmODJmNDk5YTg1YTI0MDVlNCIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+Q2h1cmNoIGFuZCBXZWxsZXNsZXkgQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF80OGJiYjAwZjUyYmY0OTNiYjc4N2Q1ODEyYmFjYjI3ZS5zZXRDb250ZW50KGh0bWxfYzViNzQ1ZjY1OGQzNGNjZjgyZjQ5OWE4NWEyNDA1ZTQpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfNDk1Yzg3NjljZjFkNDUzZDgyNTc4ZDNmZmE5ZWE4YzAuYmluZFBvcHVwKHBvcHVwXzQ4YmJiMDBmNTJiZjQ5M2JiNzg3ZDU4MTJiYWNiMjdlKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2I1MDFkYmU5MzZlNDQyMjRhNmFlODJlZTRmOTcyYmExID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjYyNzQzOSwtNzkuMzIxNTU4XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZjAwMDAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmYwMDAwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzBmMTJiODU2M2NlNDQ5ODY5MzYyMjIwZGVkN2ZjMWMwKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzZkNGQyMThmYmYyNDQ1N2M5M2Q0ZmVlY2FlYmQxOTFlID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzQ2ZDJiNDk4NGU2OTRmZWFiYTBkMDI4OTdiYjQxODc1ID0gJCgnPGRpdiBpZD0iaHRtbF80NmQyYjQ5ODRlNjk0ZmVhYmEwZDAyODk3YmI0MTg3NSIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+QnVzaW5lc3MgcmVwbHkgbWFpbCBQcm9jZXNzaW5nIENlbnRyRSBDbHVzdGVyIDA8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzZkNGQyMThmYmYyNDQ1N2M5M2Q0ZmVlY2FlYmQxOTFlLnNldENvbnRlbnQoaHRtbF80NmQyYjQ5ODRlNjk0ZmVhYmEwZDAyODk3YmI0MTg3NSk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9iNTAxZGJlOTM2ZTQ0MjI0YTZhZTgyZWU0Zjk3MmJhMS5iaW5kUG9wdXAocG9wdXBfNmQ0ZDIxOGZiZjI0NDU3YzkzZDRmZWVjYWViZDE5MWUpOwoKICAgICAgICAgICAgCiAgICAgICAgCjwvc2NyaXB0Pg== onload=\"this.contentDocument.open();this.contentDocument.write(atob(this.getAttribute('data-html')));this.contentDocument.close();\" allowfullscreen webkitallowfullscreen mozallowfullscreen></iframe></div></div>"
],
"text/plain": [
"<folium.folium.Map at 0x7fa746ee97b8>"
]
},
"execution_count": 60,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# create map\n",
"map_clusters = folium.Map(location=[latitude, longitude], zoom_start=11)\n",
"\n",
"# set color scheme for the clusters\n",
"x = np.arange(kclusters)\n",
"ys = [i + x + (i*x)**2 for i in range(kclusters)]\n",
"colors_array = cm.rainbow(np.linspace(0, 1, len(ys)))\n",
"rainbow = [colors.rgb2hex(i) for i in colors_array]\n",
"\n",
"# add markers to the map\n",
"markers_colors = []\n",
"for lat, lon, poi, cluster in zip(Toronto_merged['Latitude'], Toronto_merged['Longitude'], Toronto_merged['Neighborhood'], Toronto_merged['Cluster Labels']):\n",
" label = folium.Popup(str(poi) + ' Cluster ' + str(cluster), parse_html=True)\n",
" folium.CircleMarker(\n",
" [lat, lon],\n",
" radius=5,\n",
" popup=label,\n",
" color=rainbow[cluster-1],\n",
" fill=True,\n",
" fill_color=rainbow[cluster-1],\n",
" fill_opacity=0.7).add_to(map_clusters)\n",
" \n",
"map_clusters"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python",
"language": "python",
"name": "conda-env-python-py"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.10"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment