From 1ec75f6a06abf2837ebd6619158525989ef91422 Mon Sep 17 00:00:00 2001 From: "Kyungchan Im (Chris)" Date: Sat, 14 Oct 2023 18:00:39 -0700 Subject: [PATCH 01/14] Push --- {round 1 => python/data}/sfcc_2023_agents.json | 0 {round 1 => python/data}/sfcc_2023_claim_handlers.json | 0 {round 1 => python/data}/sfcc_2023_claims.json | 0 {round 1 => python/data}/sfcc_2023_disasters.json | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {round 1 => python/data}/sfcc_2023_agents.json (100%) rename {round 1 => python/data}/sfcc_2023_claim_handlers.json (100%) rename {round 1 => python/data}/sfcc_2023_claims.json (100%) rename {round 1 => python/data}/sfcc_2023_disasters.json (100%) diff --git a/round 1/sfcc_2023_agents.json b/python/data/sfcc_2023_agents.json similarity index 100% rename from round 1/sfcc_2023_agents.json rename to python/data/sfcc_2023_agents.json diff --git a/round 1/sfcc_2023_claim_handlers.json b/python/data/sfcc_2023_claim_handlers.json similarity index 100% rename from round 1/sfcc_2023_claim_handlers.json rename to python/data/sfcc_2023_claim_handlers.json diff --git a/round 1/sfcc_2023_claims.json b/python/data/sfcc_2023_claims.json similarity index 100% rename from round 1/sfcc_2023_claims.json rename to python/data/sfcc_2023_claims.json diff --git a/round 1/sfcc_2023_disasters.json b/python/data/sfcc_2023_disasters.json similarity index 100% rename from round 1/sfcc_2023_disasters.json rename to python/data/sfcc_2023_disasters.json From eca920063d080af0e10a574b16de78f483db3685 Mon Sep 17 00:00:00 2001 From: "Kyungchan Im (Chris)" Date: Sat, 14 Oct 2023 18:04:13 -0700 Subject: [PATCH 02/14] Update application.py --- python/application.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/python/application.py b/python/application.py index 50cb38a..fc13254 100644 --- a/python/application.py +++ b/python/application.py @@ -1 +1,7 @@ -print("I'm working") \ No newline at end of file +from simple_data_tool import SimpleDataTool +# Don't modify test_simple_data_tool.py +from test.test_simple_data_tool import TestSetOne, TestSetTwo, TestSetThree, TestSetFour + + +dataset = SimpleDataTool() +print(dataset.get_agent_data()) \ No newline at end of file From 855cbe8ef3a37493f479f49e6eb2d0b338bd550a Mon Sep 17 00:00:00 2001 From: "Kyungchan Im (Chris)" Date: Sat, 14 Oct 2023 18:16:51 -0700 Subject: [PATCH 03/14] SetTestOne --- python/application.py | 7 ++++++- python/simple_data_tool.py | 9 +++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/python/application.py b/python/application.py index fc13254..0723dbf 100644 --- a/python/application.py +++ b/python/application.py @@ -2,6 +2,11 @@ # Don't modify test_simple_data_tool.py from test.test_simple_data_tool import TestSetOne, TestSetTwo, TestSetThree, TestSetFour +import pytest dataset = SimpleDataTool() -print(dataset.get_agent_data()) \ No newline at end of file +claims = dataset.get_claim_data() + +print(dataset.get_num_closed_claims()) +print(dataset.get_num_claims_for_claim_handler_id(47)) +print(dataset.get_num_disasters_for_state("Texas")) \ No newline at end of file diff --git a/python/simple_data_tool.py b/python/simple_data_tool.py index f57ad2f..ce2eb87 100644 --- a/python/simple_data_tool.py +++ b/python/simple_data_tool.py @@ -54,6 +54,9 @@ def get_claim_data(self): # region Test Set One def get_num_closed_claims(self): + claims = self.get_claim_data() + closed_claims_count = sum(1 for claim in claims if claim['status'] == 'Closed') + return closed_claims_count """Calculates the number of claims where that status is "Closed" Returns: @@ -62,6 +65,9 @@ def get_num_closed_claims(self): pass def get_num_claims_for_claim_handler_id(self, claim_handler_id): + claims = self.get_claim_data() + claim_handler_claims = sum(1 for claim in claims if claim['claim_handler_assigned_id'] == claim_handler_id) + return claim_handler_claims """Calculates the number of claims assigned to a specific claim handler Args: @@ -73,6 +79,9 @@ def get_num_claims_for_claim_handler_id(self, claim_handler_id): pass def get_num_disasters_for_state(self, state): + disasters = self.get_disaster_data() + state_disasters = sum(1 for disaster in disasters if disaster['state'] == state) + return state_disasters """Calculates the number of disasters for a specific state Args: From 4201056f678b27549448971181fdf256e36a2039 Mon Sep 17 00:00:00 2001 From: "Kyungchan Im (Chris)" Date: Sat, 14 Oct 2023 18:57:02 -0700 Subject: [PATCH 04/14] part two update --- python/application.py | 7 +- python/simple_data_tool.py | 158 +++++++++++++++++++++++++++++++++++-- 2 files changed, 151 insertions(+), 14 deletions(-) diff --git a/python/application.py b/python/application.py index 0723dbf..2dbaf76 100644 --- a/python/application.py +++ b/python/application.py @@ -2,11 +2,6 @@ # Don't modify test_simple_data_tool.py from test.test_simple_data_tool import TestSetOne, TestSetTwo, TestSetThree, TestSetFour -import pytest - dataset = SimpleDataTool() -claims = dataset.get_claim_data() -print(dataset.get_num_closed_claims()) -print(dataset.get_num_claims_for_claim_handler_id(47)) -print(dataset.get_num_disasters_for_state("Texas")) \ No newline at end of file +print(dataset.get_top_three_months_with_highest_num_of_claims_desc()) \ No newline at end of file diff --git a/python/simple_data_tool.py b/python/simple_data_tool.py index ce2eb87..141e609 100644 --- a/python/simple_data_tool.py +++ b/python/simple_data_tool.py @@ -2,6 +2,7 @@ import math from statistics import mean +import datetime # For Test Set Four @@ -107,8 +108,21 @@ def get_total_claim_cost_for_disaster(self, disaster_id): float | None: estimate cost of disaster, rounded to the nearest hundredths place returns None if no claims are found """ + # Assign dataset to variable + claims = self.get_claim_data() + + # Total_cost to calculate all sums of estimate_cost + total_cost = 0 + + # Looping through claims to find those related to the disaster_id + for claim in claims: + if claim['disaster_id'] == disaster_id: + # add to total cost if disaster_id matches + total_cost += claim['estimate_cost'] + + # If there is no cost, return None + return round(total_cost, -2) if total_cost > 0 else None - pass def get_average_claim_cost_for_claim_handler(self, claim_handler_id): """Gets the average estimated cost of all claims assigned to a claim handler @@ -120,8 +134,22 @@ def get_average_claim_cost_for_claim_handler(self, claim_handler_id): float | None : average cost of claims, rounded to the nearest hundredths place or None if no claims are found """ + # Initialize variables for total_cost and count + total_cost = 0 + count = 0 - pass + # Assign dataset to variable + claims = self.get_claim_data() + + # Iterate through claims, add estimate_cost if claim_handler_assigned_id matches + for claim in claims: + if claim['claim_handler_assigned_id'] == claim_handler_id: + # If the dataset matches, add to total_cost and increment count + total_cost += claim['estimate_cost'] + count += 1 + + # If count is 0, return None, else return average cost rounded to nearest hundredths place + return None if count == 0 else round(total_cost / count, -2) def get_state_with_most_disasters(self): """Returns the name of the state with the most disasters based on disaster data @@ -136,7 +164,31 @@ def get_state_with_most_disasters(self): Returns: string: single name of state """ - pass + # Create a dictionary to hold counts of disasters per state + disaster_counts = {} + + # Assign dataset to variable + disasters = self.get_disaster_data() + + # Iterate through disasters, add to dictionary for each state + for disaster in disasters: + state = disaster['state'] + disaster_counts[state] = disaster_counts.get(state, 0) + 1 + + # Initialize variables to store the state with most disasters + most_disasters_state = None + max_disasters_count = 0 + + # Sorted list of states alphabetically by using keys() method + states = sorted(disaster_counts.keys()) + + # Iterate through states and find the state with the most disasters + for state in states: + if disaster_counts[state] > max_disasters_count: + most_disasters_state = state + max_disasters_count = disaster_counts[state] + + return most_disasters_state def get_state_with_least_disasters(self): """Returns the name of the state with the least disasters based on disaster data @@ -151,7 +203,31 @@ def get_state_with_least_disasters(self): Returns: string: single name of state """ - pass + # Create a dictionary to hold counts of disasters per state + disaster_counts = {} + + # Assign dataset to variable + disasters = self.get_disaster_data() + + # Iterate through disasters, add to dictionary for each state + for disaster in disasters: + state = disaster['state'] + disaster_counts[state] = disaster_counts.get(state, 0) + 1 + + # Initialize variables to store the state with the least disasters + least_disasters_state = None + min_disasters_count = float('inf') # Set initial value to positive infinity + + # Sorted list of states alphabetically + states = sorted(disaster_counts.keys()) + + # Iterate through states and find the state with the least disasters + for state in states: + if disaster_counts[state] < min_disasters_count: + least_disasters_state = state + min_disasters_count = disaster_counts[state] + + return least_disasters_state def get_most_spoken_agent_language_by_state(self, state): """Returns the name of the most spoken language by agents (besides English) for a specific state @@ -163,7 +239,29 @@ def get_most_spoken_agent_language_by_state(self, state): string: name of language or empty string if state doesn't exist """ - pass + # Create a dictionary to hold counts of languages per state + language_counts = {} + + # Assign dataset to variable + agents = self.get_agent_data() + + # Traverse through agents data + for agent in agents: + # Check if the agent is from the specified state + if agent['state'] == state: + # Get the languages spoken by the agent excluding English + languages = [lang for lang in agent['languages'] if lang != 'English'] + # Increment count for each language in the dictionary + for lang in languages: + language_counts[lang] = language_counts.get(lang, 0) + 1 + + # Check if no languages were found return empty string + if not language_counts: + return '' + + # Find and return the most spoken language / get function returns 0 if key not found + most_spoken_language = max(language_counts, key=language_counts.get) + return most_spoken_language def get_num_of_open_claims_for_agent_and_severity(self, agent_id, min_severity_rating): """Returns the number of open claims for a specific agent and for a minimum severity level and higher @@ -179,8 +277,35 @@ def get_num_of_open_claims_for_agent_and_severity(self, agent_id, min_severity_r -1 if severity rating out of bounds None if agent does not exist, or agent has no claims (open or not) """ - - pass + # Assign dataset to variable + agents = self.get_agent_data() + claims = self.get_claim_data() + + # Check if the severity rating is out of bounds + if min_severity_rating < 1 or min_severity_rating > 10: + return -1 + + # Check if the agent exists / any() returns True if any element of the iterable is true + agent_exists = any(agent['id'] == agent_id for agent in agents) + if not agent_exists: + return None + + + # Initialize a counter for open claims + open_claims_count = 0 + # Check every claim + for claim in claims: + # Check if the claim is assigned to the agent, is not closed, and has sufficient severity + if ( + claim['agent_assigned_id'] == agent_id and + claim['status'] != "Closed" and + claim['severity_rating'] >= min_severity_rating + ): + # If all conditions are met, increment the counter + open_claims_count += 1 + + # Return None if there are no qualifying claims + return open_claims_count if open_claims_count > 0 else None # endregion @@ -240,7 +365,24 @@ def get_top_three_months_with_highest_num_of_claims_desc(self): Returns: list: three strings of month and year, descending order of highest claims """ + # Dictionary to store the total cost of claims per month + monthly_costs = {} - pass + # Assign dataset to variable + claims = self.get_claim_data() + + # Loop through each claim + for claim in claims: + # Extract the month and year (assuming a date field is available and in 'YYYY-MM-DD' format) + claim_date = datetime.datetime.strptime(claim['date'], '%Y-%m-%d') + month_year = claim_date.strftime('%B %Y') + + # Add the cost of this claim to the monthly total + monthly_costs[month_year] = monthly_costs.get(month_year, 0) + claim['estimate_cost'] + + # Get the top three months by total claim cost + top_three_months = sorted(monthly_costs.keys(), key=lambda month: monthly_costs[month], reverse=True)[:3] + + return top_three_months # endregion From 1420d3f4652575eb330ed5be497d1a7e89044b98 Mon Sep 17 00:00:00 2001 From: "Kyungchan Im (Chris)" Date: Sat, 14 Oct 2023 19:01:50 -0700 Subject: [PATCH 05/14] Latest changes --- python/simple_data_tool.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/python/simple_data_tool.py b/python/simple_data_tool.py index 141e609..8a0a8ec 100644 --- a/python/simple_data_tool.py +++ b/python/simple_data_tool.py @@ -2,7 +2,7 @@ import math from statistics import mean -import datetime # For Test Set Four +from datetime import datetime# For Test Set Four @@ -121,7 +121,7 @@ def get_total_claim_cost_for_disaster(self, disaster_id): total_cost += claim['estimate_cost'] # If there is no cost, return None - return round(total_cost, -2) if total_cost > 0 else None + return round(total_cost, 2) if total_cost > 0 else None def get_average_claim_cost_for_claim_handler(self, claim_handler_id): @@ -149,7 +149,7 @@ def get_average_claim_cost_for_claim_handler(self, claim_handler_id): count += 1 # If count is 0, return None, else return average cost rounded to nearest hundredths place - return None if count == 0 else round(total_cost / count, -2) + return None if count == 0 else round(total_cost / count, 2) def get_state_with_most_disasters(self): """Returns the name of the state with the most disasters based on disaster data @@ -250,7 +250,7 @@ def get_most_spoken_agent_language_by_state(self, state): # Check if the agent is from the specified state if agent['state'] == state: # Get the languages spoken by the agent excluding English - languages = [lang for lang in agent['languages'] if lang != 'English'] + languages = [lang for lang in agent['secondary_language'] if lang != 'English'] # Increment count for each language in the dictionary for lang in languages: language_counts[lang] = language_counts.get(lang, 0) + 1 @@ -312,6 +312,10 @@ def get_num_of_open_claims_for_agent_and_severity(self, agent_id, min_severity_r # region TestSetThree def get_num_disasters_declared_after_end_date(self): + disasters = self.get_disaster_data() + + disasters_declared = sum(1 for disaster in disasters if datetime.strptime(disaster['declared_date'], '%Y-%m-%d').date() > datetime.strptime(disaster['end_date'], '%Y-%m-%d').date()) + return disasters_declared """Gets the number of disasters where it was declared after it ended Returns: @@ -321,6 +325,17 @@ def get_num_disasters_declared_after_end_date(self): pass def build_map_of_agents_to_total_claim_cost(self): + claims = self.get_claim_data() + agent_costs = {agent_id: 0 for agent_id in range(1, 101)} + + for claim in claims: + agent_id = claim["agent_assigned_id"] + cost = claim["estimate_cost"] + + agent_costs[agent_id] = round(agent_costs[agent_id] + cost, 2) + + return(agent_costs) + """Builds a map of agent and their total claim cost Hints: From 474d2ee2efb5a6afca12e7009183c86b871bf544 Mon Sep 17 00:00:00 2001 From: "Kyungchan Im (Chris)" Date: Sat, 14 Oct 2023 19:07:22 -0700 Subject: [PATCH 06/14] update --- python/simple_data_tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/simple_data_tool.py b/python/simple_data_tool.py index 8a0a8ec..076eefe 100644 --- a/python/simple_data_tool.py +++ b/python/simple_data_tool.py @@ -250,7 +250,7 @@ def get_most_spoken_agent_language_by_state(self, state): # Check if the agent is from the specified state if agent['state'] == state: # Get the languages spoken by the agent excluding English - languages = [lang for lang in agent['secondary_language'] if lang != 'English'] + languages = [lang for lang in [agent['primary_language'], agent['secondary_language']] if lang != 'English'] # Increment count for each language in the dictionary for lang in languages: language_counts[lang] = language_counts.get(lang, 0) + 1 From fa9b4990c4907f67710dbd8159ecfe3f8dd6dcc5 Mon Sep 17 00:00:00 2001 From: "Kyungchan Im (Chris)" Date: Sat, 14 Oct 2023 19:08:24 -0700 Subject: [PATCH 07/14] Update simple_data_tool.py --- python/simple_data_tool.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/python/simple_data_tool.py b/python/simple_data_tool.py index 076eefe..3ff6bf5 100644 --- a/python/simple_data_tool.py +++ b/python/simple_data_tool.py @@ -350,6 +350,22 @@ def build_map_of_agents_to_total_claim_cost(self): pass def calculate_disaster_claim_density(self, disaster_id): + disaster = next((d for d in self.get_disaster_data() if d['id'] == disaster_id), None) + + if not disaster: + return None + + # Calculate disaster impact area + radius = disaster['radius_miles'] + disaster_area = math.pi * (radius ** 2) + + # Count claims associated with this disaster + num_claims = sum(1 for claim in self.get_claim_data() if claim['disaster_id'] == disaster_id) + + # Compute claim density + density = num_claims / disaster_area + + return round(density, 5) """Calculates density of a diaster based on the number of claims and impact radius Hints: @@ -361,7 +377,7 @@ def calculate_disaster_claim_density(self, disaster_id): Returns: float: density of claims to disaster area, rounded to the nearest thousandths place - None if disaster does not exist + None if disaster does not exist """ pass From fa5210ec326f25d5292e5f1c968a3af38cbb3db1 Mon Sep 17 00:00:00 2001 From: "Kyungchan Im (Chris)" Date: Sat, 14 Oct 2023 19:48:55 -0700 Subject: [PATCH 08/14] Final tests passed --- python/application.py | 2 +- python/simple_data_tool.py | 48 ++++++++++++++++++++++++++++++++------ 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/python/application.py b/python/application.py index 2dbaf76..6075c40 100644 --- a/python/application.py +++ b/python/application.py @@ -4,4 +4,4 @@ dataset = SimpleDataTool() -print(dataset.get_top_three_months_with_highest_num_of_claims_desc()) \ No newline at end of file +print(dataset.get_regional_disaster_map()) \ No newline at end of file diff --git a/python/simple_data_tool.py b/python/simple_data_tool.py index 3ff6bf5..85087b9 100644 --- a/python/simple_data_tool.py +++ b/python/simple_data_tool.py @@ -386,7 +386,7 @@ def calculate_disaster_claim_density(self, disaster_id): # region TestSetFour def get_top_three_months_with_highest_num_of_claims_desc(self): - """Gets the top three months with the highest total claim cost + """Gets the top three months with the highest total claims Hint: Month should be full name like 01 is January and 12 is December @@ -396,24 +396,58 @@ def get_top_three_months_with_highest_num_of_claims_desc(self): Returns: list: three strings of month and year, descending order of highest claims """ + # Dictionary to store the total cost of claims per month monthly_costs = {} # Assign dataset to variable claims = self.get_claim_data() - + + # Convert disasters list to a dictionary for O(1) lookups + disasters_dict = {disaster['id']: disaster for disaster in self.get_disaster_data()} + # Loop through each claim for claim in claims: - # Extract the month and year (assuming a date field is available and in 'YYYY-MM-DD' format) - claim_date = datetime.datetime.strptime(claim['date'], '%Y-%m-%d') - month_year = claim_date.strftime('%B %Y') + disaster_id = claim['disaster_id'] + disaster = disasters_dict.get(disaster_id) + + if disaster: + claim_date = datetime.strptime(disaster['declared_date'], '%Y-%m-%d') + month_year = claim_date.strftime('%B %Y') - # Add the cost of this claim to the monthly total - monthly_costs[month_year] = monthly_costs.get(month_year, 0) + claim['estimate_cost'] + # Add the cost of this claim to the monthly total + monthly_costs[month_year] = monthly_costs.get(month_year, 0) + 1 # Get the top three months by total claim cost top_three_months = sorted(monthly_costs.keys(), key=lambda month: monthly_costs[month], reverse=True)[:3] return top_three_months + + def get_regional_disaster_map(self): + disasters = self.get_disaster_data() + regional_disaster_map = { + 'west': {}, + 'midwest': {}, + 'south': {}, + 'northeast': {} + } + + # Helper function to get the region of a given state + def get_region(state): + for region, states in self.REGION_MAP.items(): + if state in states.split(','): + return region + return None + + for disaster in disasters: + region = get_region(disaster['state']) + if region: + disaster_type = disaster['type'] + if disaster_type in regional_disaster_map[region]: + regional_disaster_map[region][disaster_type] += 1 + else: + regional_disaster_map[region][disaster_type] = 1 + + return regional_disaster_map # endregion From 1563cb9e0939bb4a4c86d52b4ea8e3302f620eb1 Mon Sep 17 00:00:00 2001 From: "Kyungchan Im (Chris)" Date: Sat, 14 Oct 2023 19:54:57 -0700 Subject: [PATCH 09/14] Clean Up --- python/simple_data_tool.py | 96 +++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 43 deletions(-) diff --git a/python/simple_data_tool.py b/python/simple_data_tool.py index 85087b9..b712141 100644 --- a/python/simple_data_tool.py +++ b/python/simple_data_tool.py @@ -1,11 +1,12 @@ +# ------------------------------------ Package Installation ------------------------------------ # import json import math -from statistics import mean -from datetime import datetime# For Test Set Four - +# from statistics import mean # Deprecated since we are not using it +from datetime import datetime # For formatting datetime strings +# ------------------------------------ Class Declaration ------------------------------------ # class SimpleDataTool: AGENTS_FILEPATH = 'data/sfcc_2023_agents.json' @@ -52,23 +53,21 @@ def get_claim_data(self): # Unit Test Methods +# ------------------------------------ Unit Test One ------------------------------------ # # region Test Set One def get_num_closed_claims(self): - claims = self.get_claim_data() - closed_claims_count = sum(1 for claim in claims if claim['status'] == 'Closed') - return closed_claims_count """Calculates the number of claims where that status is "Closed" Returns: int: number of closed claims """ - pass + claims = self.get_claim_data() + closed_claims_count = sum(1 for claim in claims if claim['status'] == 'Closed') + return closed_claims_count + def get_num_claims_for_claim_handler_id(self, claim_handler_id): - claims = self.get_claim_data() - claim_handler_claims = sum(1 for claim in claims if claim['claim_handler_assigned_id'] == claim_handler_id) - return claim_handler_claims """Calculates the number of claims assigned to a specific claim handler Args: @@ -77,12 +76,13 @@ def get_num_claims_for_claim_handler_id(self, claim_handler_id): Returns: int: number of claims assigned to claim handler """ - pass + claims = self.get_claim_data() + claim_handler_claims = sum(1 for claim in claims if claim['claim_handler_assigned_id'] == claim_handler_id) + return claim_handler_claims + + def get_num_disasters_for_state(self, state): - disasters = self.get_disaster_data() - state_disasters = sum(1 for disaster in disasters if disaster['state'] == state) - return state_disasters """Calculates the number of disasters for a specific state Args: @@ -92,10 +92,15 @@ def get_num_disasters_for_state(self, state): Returns: int: number of disasters for state """ - pass + disasters = self.get_disaster_data() + state_disasters = sum(1 for disaster in disasters if disaster['state'] == state) + return state_disasters + + # endregion +# ------------------------------------ Unit Test Two ------------------------------------ # # region Test Set Two def get_total_claim_cost_for_disaster(self, disaster_id): @@ -309,22 +314,33 @@ def get_num_of_open_claims_for_agent_and_severity(self, agent_id, min_severity_r # endregion +# ------------------------------------ Unit Test Three ------------------------------------ # # region TestSetThree def get_num_disasters_declared_after_end_date(self): - disasters = self.get_disaster_data() - - disasters_declared = sum(1 for disaster in disasters if datetime.strptime(disaster['declared_date'], '%Y-%m-%d').date() > datetime.strptime(disaster['end_date'], '%Y-%m-%d').date()) - return disasters_declared """Gets the number of disasters where it was declared after it ended Returns: int: number of disasters where the declared date is after the end date """ + disasters = self.get_disaster_data() + + disasters_declared = sum(1 for disaster in disasters if datetime.strptime(disaster['declared_date'], '%Y-%m-%d').date() > datetime.strptime(disaster['end_date'], '%Y-%m-%d').date()) + return disasters_declared + - pass def build_map_of_agents_to_total_claim_cost(self): + """Builds a map of agent and their total claim cost + + Hints: + An agent with no claims should return 0 + Invalid agent id should have a value of None + You should round your total_claim_cost to the nearest hundredths + + Returns: + dict: key is agent id, value is total cost of claims associated to the agent + """ claims = self.get_claim_data() agent_costs = {agent_id: 0 for agent_id in range(1, 101)} @@ -336,20 +352,21 @@ def build_map_of_agents_to_total_claim_cost(self): return(agent_costs) - """Builds a map of agent and their total claim cost + + def calculate_disaster_claim_density(self, disaster_id): + """Calculates density of a diaster based on the number of claims and impact radius Hints: - An agent with no claims should return 0 - Invalid agent id should have a value of None - You should round your total_claim_cost to the nearest hundredths + Assume uniform spacing between claims + Assume disaster impact area is a circle + + Args: + disaster_id (int): id of diaster Returns: - dict: key is agent id, value is total cost of claims associated to the agent + float: density of claims to disaster area, rounded to the nearest thousandths place + None if disaster does not exist """ - - pass - - def calculate_disaster_claim_density(self, disaster_id): disaster = next((d for d in self.get_disaster_data() if d['id'] == disaster_id), None) if not disaster: @@ -366,23 +383,11 @@ def calculate_disaster_claim_density(self, disaster_id): density = num_claims / disaster_area return round(density, 5) - """Calculates density of a diaster based on the number of claims and impact radius - - Hints: - Assume uniform spacing between claims - Assume disaster impact area is a circle - Args: - disaster_id (int): id of diaster - - Returns: - float: density of claims to disaster area, rounded to the nearest thousandths place - None if disaster does not exist - """ - pass # endregion +# ------------------------------------ Unit Test Four ------------------------------------ # # region TestSetFour def get_top_three_months_with_highest_num_of_claims_desc(self): @@ -423,6 +428,11 @@ def get_top_three_months_with_highest_num_of_claims_desc(self): return top_three_months + # endregion + +# ------------------------------------ Nice to Have! ------------------------------------ # + + # This funcftions maps out each regional disaster by type and counts the number of each type def get_regional_disaster_map(self): disasters = self.get_disaster_data() regional_disaster_map = { @@ -450,4 +460,4 @@ def get_region(state): return regional_disaster_map - # endregion + From 634467a1e382928441491d111396b93b4e637f33 Mon Sep 17 00:00:00 2001 From: "Kyungchan Im (Chris)" Date: Sat, 14 Oct 2023 20:02:49 -0700 Subject: [PATCH 10/14] update --- python/application.py | 3 --- python/data_analysis.ipynb | 27 +++++++++++++++++++++++++++ python/requirements.txt | 8 +++++++- 3 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 python/data_analysis.ipynb diff --git a/python/application.py b/python/application.py index 6075c40..bad82a2 100644 --- a/python/application.py +++ b/python/application.py @@ -1,7 +1,4 @@ from simple_data_tool import SimpleDataTool -# Don't modify test_simple_data_tool.py -from test.test_simple_data_tool import TestSetOne, TestSetTwo, TestSetThree, TestSetFour dataset = SimpleDataTool() -print(dataset.get_regional_disaster_map()) \ No newline at end of file diff --git a/python/data_analysis.ipynb b/python/data_analysis.ipynb new file mode 100644 index 0000000..66010f3 --- /dev/null +++ b/python/data_analysis.ipynb @@ -0,0 +1,27 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from simple_data_tool import SimpleDataTool\n", + "import pandas as pd\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# Load data\n", + "data = SimpleDataTool()\n", + "data.load_data()\n" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/python/requirements.txt b/python/requirements.txt index b705adb..6cff539 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -1 +1,7 @@ -pytest==7.2.2 \ No newline at end of file +pytest==7.2.2 + +numpy==1.26.0 + +pandas==1.3.5 + +matplotlib==3.6.0 \ No newline at end of file From 4735d94bc7463fbf1acd53bfc0d240e026ed9182 Mon Sep 17 00:00:00 2001 From: "Kyungchan Im (Chris)" Date: Sat, 14 Oct 2023 20:13:35 -0700 Subject: [PATCH 11/14] update --- python/data_analysis.ipynb | 102 +++++++++++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 4 deletions(-) diff --git a/python/data_analysis.ipynb b/python/data_analysis.ipynb index 66010f3..3827f9d 100644 --- a/python/data_analysis.ipynb +++ b/python/data_analysis.ipynb @@ -1,25 +1,119 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Data Anaysis Section\n", + "---\n", + "This section is for \"Nice to Have\" purpose for 2023-StateFarm-CodingCompetition.
\n", + "The purpose of this documentation is to find insight from given dataset and scrutinize it." + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ + "# Import libraries\n", "from simple_data_tool import SimpleDataTool\n", "import pandas as pd\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "# Load data\n", - "data = SimpleDataTool()\n", - "data.load_data()\n" + "data = SimpleDataTool()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Data Load\n", + "Import JSON dataset into DataFrame in order to work with Python Packages" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "# Load Agent JSON data\n", + "agents = pd.read_json(\"data/sfcc_2023_agents.json\")\n", + "agents.set_index('id', inplace=True) # Set the 'id' column as the index\n", + "df_agents = pd.json_normalize(agents.to_dict(orient='records')) # Normalize the 'profile' columns\n", + "\n", + "# Load Claim JSON data\n", + "claims = pd.read_json(\"data/sfcc_2023_claims.json\")\n", + "claims.set_index('id', inplace=True) # Set the 'id' column as the index\n", + "df_claims = pd.json_normalize(claims.to_dict(orient='records')) # Normalize the 'profile' columns\n", + "\n", + "# Load Claim Handler JSON data\n", + "claim_handlers = pd.read_json(\"data/sfcc_2023_claim_handlers.json\")\n", + "claim_handlers.set_index('id', inplace=True) # Set the 'id' column as the index\n", + "df_claim_handlers = pd.json_normalize(claim_handlers.to_dict(orient='records')) # Normalize the 'profile' columns\n", + "\n", + "# Load Disaster JSON data\n", + "disasters = pd.read_json(\"data/sfcc_2023_disasters.json\")\n", + "disasters.set_index('id', inplace=True) # Set the 'id' column as the index\n", + "df_disasters = pd.json_normalize(disasters.to_dict(orient='records')) # Normalize the 'profile' columns" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.9" } }, "nbformat": 4, From c5373216e8c607bb39ef5638e72174688a118f65 Mon Sep 17 00:00:00 2001 From: "Kyungchan Im (Chris)" Date: Sat, 14 Oct 2023 20:39:50 -0700 Subject: [PATCH 12/14] ipynb update --- python/data_analysis.ipynb | 628 +++++++++++++++++++++++++++++++++++-- python/requirements.txt | 4 +- python/simple_data_tool.py | 24 +- 3 files changed, 631 insertions(+), 25 deletions(-) diff --git a/python/data_analysis.ipynb b/python/data_analysis.ipynb index 3827f9d..83272d3 100644 --- a/python/data_analysis.ipynb +++ b/python/data_analysis.ipynb @@ -10,20 +10,29 @@ "The purpose of this documentation is to find insight from given dataset and scrutinize it." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Preparation" + ] + }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 33, "metadata": {}, "outputs": [], "source": [ - "# Import libraries\n", + "## Import libraries\n", "from simple_data_tool import SimpleDataTool\n", + "\n", + "# For Data Analysis\n", "import pandas as pd\n", "import numpy as np\n", - "import matplotlib.pyplot as plt\n", "\n", - "# Load data\n", - "data = SimpleDataTool()" + "# For Visualization\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns" ] }, { @@ -43,51 +52,630 @@ "# Load Agent JSON data\n", "agents = pd.read_json(\"data/sfcc_2023_agents.json\")\n", "agents.set_index('id', inplace=True) # Set the 'id' column as the index\n", - "df_agents = pd.json_normalize(agents.to_dict(orient='records')) # Normalize the 'profile' columns\n", + "df_agents = pd.json_normalize(agents.to_dict(orient='records')) # Normalize the columns\n", "\n", "# Load Claim JSON data\n", "claims = pd.read_json(\"data/sfcc_2023_claims.json\")\n", "claims.set_index('id', inplace=True) # Set the 'id' column as the index\n", - "df_claims = pd.json_normalize(claims.to_dict(orient='records')) # Normalize the 'profile' columns\n", + "df_claims = pd.json_normalize(claims.to_dict(orient='records')) # Normalize the columns\n", "\n", "# Load Claim Handler JSON data\n", "claim_handlers = pd.read_json(\"data/sfcc_2023_claim_handlers.json\")\n", "claim_handlers.set_index('id', inplace=True) # Set the 'id' column as the index\n", - "df_claim_handlers = pd.json_normalize(claim_handlers.to_dict(orient='records')) # Normalize the 'profile' columns\n", + "df_claim_handlers = pd.json_normalize(claim_handlers.to_dict(orient='records')) # Normalize the columns\n", "\n", "# Load Disaster JSON data\n", "disasters = pd.read_json(\"data/sfcc_2023_disasters.json\")\n", "disasters.set_index('id', inplace=True) # Set the 'id' column as the index\n", - "df_disasters = pd.json_normalize(disasters.to_dict(orient='records')) # Normalize the 'profile' columns" + "df_disasters = pd.json_normalize(disasters.to_dict(orient='records')) # Normalize the columns" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Check the info for each individual dataset" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "metadata": {}, - "outputs": [], - "source": [] + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
first_namelast_namestateregionprimary_languagesecondary_languageyears_active
id
1CathaAbrahmerMinnesotaMidwestEnglishGerman10
2YettaEasonOregonWestEnglishKorean12
3JanetaD'AvauxConnecticutNortheastEnglishNone47
4KalieTomkinsVirginiaSouthEnglishNone33
5TeddyDennittsIllinoisMidwestEnglishChinese48
\n", + "
" + ], + "text/plain": [ + " first_name last_name state region primary_language \\\n", + "id \n", + "1 Catha Abrahmer Minnesota Midwest English \n", + "2 Yetta Eason Oregon West English \n", + "3 Janeta D'Avaux Connecticut Northeast English \n", + "4 Kalie Tomkins Virginia South English \n", + "5 Teddy Dennitts Illinois Midwest English \n", + "\n", + " secondary_language years_active \n", + "id \n", + "1 German 10 \n", + "2 Korean 12 \n", + "3 None 47 \n", + "4 None 33 \n", + "5 Chinese 48 " + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "agents.head()" + ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "metadata": {}, - "outputs": [], - "source": [] + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
disaster_idstatustotal_lossloss_of_lifetypeseverity_ratingestimate_costagent_assigned_idclaim_handler_assigned_id
id
115ReceivedFalseFalseAuto43580.029750
224In ReviewFalseFalseAuto61741.323692
331ReceivedTrueFalseAuto415224.3094133
414ReceivedFalseTrueAuto86542.469239
530ClosedFalseFalseAuto1979.8186103
\n", + "
" + ], + "text/plain": [ + " disaster_id status total_loss loss_of_life type severity_rating \\\n", + "id \n", + "1 15 Received False False Auto 4 \n", + "2 24 In Review False False Auto 6 \n", + "3 31 Received True False Auto 4 \n", + "4 14 Received False True Auto 8 \n", + "5 30 Closed False False Auto 1 \n", + "\n", + " estimate_cost agent_assigned_id claim_handler_assigned_id \n", + "id \n", + "1 3580.02 97 50 \n", + "2 1741.32 36 92 \n", + "3 15224.30 94 133 \n", + "4 6542.46 92 39 \n", + "5 979.81 86 103 " + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "claims.head()" + ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 22, "metadata": {}, - "outputs": [], - "source": [] + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
first_namelast_name
id
1BarnabeClynman
2ValdemarSize
3EditheBleakley
4SonjaDiamant
5ElseySreenan
\n", + "
" + ], + "text/plain": [ + " first_name last_name\n", + "id \n", + "1 Barnabe Clynman\n", + "2 Valdemar Size\n", + "3 Edithe Bleakley\n", + "4 Sonja Diamant\n", + "5 Elsey Sreenan" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "claim_handlers.head()" + ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
typestatenamedescriptionstart_dateend_datedeclared_datelatlongradius_miles
id
1FloodAlaskaAlaska FloodFusce consequat. Nulla nisl. Nunc nisl.\\n\\nDui...2023-06-132023-06-202023-06-1758.3271-134.4742168
2WildfireTexasTexas WildfireDuis consequat dui nec nisi volutpat eleifend....2023-04-112023-04-192023-04-1233.5693-101.8904235
3TornadoVirginiaVirginia TornadoNullam sit amet turpis elementum ligula vehicu...2023-04-292023-05-072023-05-0638.9776-77.3860273
4EarthquakeConnecticutConnecticut EarthquakeFusce consequat. Nulla nisl. Nunc nisl.2023-04-242023-05-092023-04-2841.3657-72.9275120
5Winter StormMissouriMissouri Winter StormSuspendisse potenti. In eleifend quam a odio. ...2023-03-112023-03-212023-03-1439.0663-94.5674155
\n", + "
" + ], + "text/plain": [ + " type state name \\\n", + "id \n", + "1 Flood Alaska Alaska Flood \n", + "2 Wildfire Texas Texas Wildfire \n", + "3 Tornado Virginia Virginia Tornado \n", + "4 Earthquake Connecticut Connecticut Earthquake \n", + "5 Winter Storm Missouri Missouri Winter Storm \n", + "\n", + " description start_date end_date \\\n", + "id \n", + "1 Fusce consequat. Nulla nisl. Nunc nisl.\\n\\nDui... 2023-06-13 2023-06-20 \n", + "2 Duis consequat dui nec nisi volutpat eleifend.... 2023-04-11 2023-04-19 \n", + "3 Nullam sit amet turpis elementum ligula vehicu... 2023-04-29 2023-05-07 \n", + "4 Fusce consequat. Nulla nisl. Nunc nisl. 2023-04-24 2023-05-09 \n", + "5 Suspendisse potenti. In eleifend quam a odio. ... 2023-03-11 2023-03-21 \n", + "\n", + " declared_date lat long radius_miles \n", + "id \n", + "1 2023-06-17 58.3271 -134.4742 168 \n", + "2 2023-04-12 33.5693 -101.8904 235 \n", + "3 2023-05-06 38.9776 -77.3860 273 \n", + "4 2023-04-28 41.3657 -72.9275 120 \n", + "5 2023-03-14 39.0663 -94.5674 155 " + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "disasters.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Find some insight from the dataset" + ] + }, + { + "cell_type": "code", + "execution_count": 25, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "data = SimpleDataTool()\n", + "regional_disaster_map = data.get_regional_disaster_map()" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Count\n", + "Region \n", + "south 36\n", + "midwest 25\n", + "northeast 20\n", + "west 19\n" + ] + } + ], + "source": [ + "# Initialize lists to store the data\n", + "regions = []\n", + "disaster_types = []\n", + "counts = []\n", + "\n", + "# Populate the lists with data\n", + "for region, disasters in regional_disaster_map.items():\n", + " for disaster_type, count in disasters.items():\n", + " regions.append(region)\n", + " disaster_types.append(disaster_type)\n", + " counts.append(count)\n", + "\n", + "# Create a DataFrame\n", + "disaster_map = pd.DataFrame({\n", + " 'Region': regions,\n", + " 'Disaster Type': disaster_types,\n", + " 'Count': counts\n", + "})\n", + "\n", + "# Print the sum of the disaster regionally.\n", + "print(disaster_map.groupby(['Region']).sum().sort_values(by='Count', ascending=False))" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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P/9UtWrV5OXlpX379pmnff3116pfv76efPLJfMv556n5L730kpycnMzvH3nkEUnSL7/8Uuh1uap48eI3fHrA1W+eV65ced072zs6Oprry8vL07lz55STk6MmTZpYrLOXl5cuXbqk77//vsB1fv/997pw4YKeffZZ/fHHH+aXo6OjHnzwwWtehvD6668XaBn9+vWzeP/GG29I+v/Pv6enpzp16qQvvvjCfJlJbm6uli1bps6dO9/UPQiOHDmiMmXKqEyZMqpZs6beffddPfHEExan7H/zzTfKy8tT9+7dLdbV29tb1atXN6/rnj179Oeff6pPnz4W93V4/vnnVaJEiRvWkZycrLi4OIWFhalkyZLm9nr16umxxx4zr/PfvfbaaxbvH3nkEf3555/m/58AAGDvuDQAwF3Nw8NDkm768XEnT56Ug4ODqlWrZtHu7e0tLy+vQh+0S1KlSpUs3l89gDl//nyhxjt58qTKly+fL+S4enr5rdTq5uam1q1b37DPlStXNHnyZC1evFi//vqr+YBRklJTU80/nzhxQk899dRNLdfa2+jv0tPTVbZs2etOf/rpp7Vo0SL17t1bI0aMUKtWrdSlSxd17dpVDg7/n3svWbJE06dP15EjR5SdnW1u9/f3N//ct29fffnll2rbtq0qVKigxx9/XN27d1ebNm3+tc5jx45J+uveBtdy9TN9VZEiRf711Ph/ql69usX7qlWrysHBweL+Di+++KKWLVumbdu26dFHH9XGjRv1+++/q0ePHje1DD8/Py1cuFB5eXk6ceKEJk6cqLNnz1rcyO/YsWMyDCNfPVddvfP/1c/yP/fLIkWK/OsTF67OGxAQkG9aYGCg1q9fn+8Gizf6HP5z+wMAYI8IAgDc1Tw8PFS+fHkdOHCgQPP985vqgsjNzb1m+/Xukv73A+h7yRtvvKHFixcrPDxczZo1k6enp0wmk5555plCPyv+dm2j//3vf0pNTc13IPl3rq6u2rp1q6Kjo7VmzRqtW7dOy5YtU8uWLbVhwwY5Ojrqs88+U1hYmDp37qyhQ4eqbNmycnR01OTJk3XixAnzWGXLllVcXJzWr1+vtWvXau3atVq8eLFefPFFLVmy5Ia1Xt12n376qby9vfNN/+eTDpydnS2CisK41uc9NDRU5cqV02effaZHH31Un332mby9vf81ILrqn2FSixYt1KhRI40aNUqzZ8+W9Ne6mkwmrV279pq/++LFixdyjW7N/bavAgBgbQQBAO56HTp00IcffqgdO3aoWbNmN+xbuXJl5eXl6dixYxY31Pv999914cIFVa5c2dxWokQJXbhwwWL+rKwsJScnF7rWggQQlStX1saNG3Xx4kWLswKOHDlinn47LV++XD179tT06dPNbRkZGfm2SdWqVQscxFjbp59+Kkn/erd7BwcHtWrVSq1atdKMGTM0adIkvfXWW4qOjlbr1q21fPlyValSRd98843F72rs2LH5xnJyclLHjh3VsWNH5eXlqW/fvvrggw80evRoVatW7bq/66pVq0r6K0y42YPugjp27JjFGQzHjx9XXl6exbfrjo6Oeu655xQVFaUpU6ZoxYoV6tOnT6Ef+1evXj298MIL+uCDDzRkyBBVqlRJVatWlWEY8vf3V40aNa4779XP8vHjxxUSEmJuz8nJUVJSkurVq/ev8yYkJOSbduTIEZUuXfqueNwiAAD3Eu4RAOCuN2zYMLm5ual37976/fff800/ceKE+bFu7dq1k/TXHfz/bsaMGZKk9u3bm9uqVq2qrVu3WvT78MMPr3tGwM1wc3PLdyB9Pe3atVNubq7ef/99i/b33ntPJpNJbdu2LXQdN8PR0THfN6Rz5szJt/5PPfWU4uPjr/nEhTvxDevmzZs1fvx4+fv73/DReufOncvXdvUpAFcfHXn1IPjvde/atUs7duywmO/PP/+0eO/g4GA+WL061tWDz3/+vkNDQ+Xh4aFJkyZZXHpw1d8fOVlYc+fOtXg/Z84cScr3menRo4fOnz+vV199Venp6XrhhRduabnDhg1Tdna2eX/q0qWLHB0dFRkZme+zYBiGeTs2adJEpUqV0sKFC5WTk2Pus3Tp0n+9bMTHx0cNGjTQkiVLLLb1gQMHtGHDBvM+DwAAbh5nBAC461WtWlWff/65nn76aQUGBurFF19UnTp1lJWVpR9++EFfffWVwsLCJEn169dXz5499eGHH+rChQsKCgrSjz/+qCVLlqhz584W30b27t1br732mp566ik99thjio+P1/r16y0eh1dQjRs31vz58zVhwgRVq1ZNZcuWve614h07dlRISIjeeustJSUlqX79+tqwYYNWrlyp8PBw8zfLhZGamqrPPvvsmtOuHgx26NBBn376qTw9PVWrVi3t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evfurV9++UU9evTQ/v371aRJE82ZM+e6tVy9v8CaNWsUFxdnfh06dMjiPgGS5ObmVuh1btiwoerXr69PPvlEe/fu1cGDB833KLgZV+tcuHChRZ0HDhzQzp07C13X7cQZAQAAAAAAbd68Wfv379egQYOu28fDw0NPP/20nn76aXXt2lVt2rTRuXPntHfvXuXl5Wn69OlycPjr++Yvv/wy3/y+vr567bXX9Nprr2nkyJFauHCh3njjDTk5OUmScnNzzX1r1aolZ2dnnTp1yuIygNuhd+/emjlzpn799Ve1bt0631METp06pTNnzqh8+fKSpJ07d8rBwUEBAQEqV66cypcvr19++UXPP//8ba3TWggCAAAACmDUqFFWG2vSpElWGwsACiIzM1O//fabcnNz9fvvv2vdunWaPHmyOnTooBdffPGa88yYMUM+Pj5q2LChHBwc9NVXX8nb21teXl6qVq2asrOzNWfOHHXs2FGxsbFasGCBxfzh4eFq27atatSoofPnzys6OlqBgYGSpMqVK8tkMmn16tVq166dXF1d5e7uriFDhmjQoEHKy8vTww8/rNTUVMXGxsrDw8Oq194/99xzGjJkiBYuXKhPPvkk33QXFxf17NlT06ZNU1pamgYMGKDu3bvL29tbkhQZGakBAwbI09NTbdq0UWZmpvbs2aPz589r8ODBVqvTWrg0AAAAAADszLp16+Tj4yM/Pz+1adNG0dHRmj17tlauXClHR8drzuPu7q6pU6eqSZMmatq0qZKSkvTdd9/JwcFB9evX14wZMzRlyhTVqVNHS5cutXi0oPTXt/39+vVTYGCg2rRpoxo1aphvJFihQgVFRkZqxIgRKleunPr37y9JGj9+vEaPHq3Jkyeb51uzZs11L18oLE9PTz311FMqXry4OnfunG96tWrV1KVLF7Vr106PP/646tWrZ/F4wN69e2vRokVavHix6tatq6CgIEVFRVm9TmsxGYZh2LqIwkpLS5Onp6dSU1Pl4eFh63IAAIAd4IwA4O50rx4bZGRkKDExUf7+/nJxcbF1OXatVatWql27tmbPnm3RHhERoRUrViguLs42hRXAzX6euDQAAAAAAGC3zp8/r5iYGMXExFh8y38/IwgAAAAAANithg0b6vz585oyZYoCAgJsXc4dQRAAAAAAALBbSUlJN5weERGhiIiIO1LLncLNAgEAAAAAsCMEAQAAAAAA2BGCAAAAAAAA7AhBAAAAAAAAdoQgAAAAAAAAO0IQAAAAAACAHeHxgQAAAABwl3luTMwdXd7n44KtOl5wcLAaNGigmTNnWnXcf/Lz81N4eLjCw8Nv63LuN5wRAAAAAAAosLCwMJlMpnyv48eP27o0/AvOCAAAAAAAFEqbNm20ePFii7YyZcrYqBrcLM4IAAAAAAAUirOzs7y9vS1ejo6O+fqdP39eL774okqUKKFixYqpbdu2OnbsmEWfr7/+WrVr15azs7P8/Pw0ffp0i+kpKSnq2LGjXF1d5e/vr6VLl97WdbufEQQAAAAAAG6rsLAw7dmzR6tWrdKOHTtkGIbatWun7OxsSdLevXvVvXt3PfPMM9q/f78iIiI0evRoRUVFWYxx+vRpRUdHa/ny5Zo3b55SUlJstEb3Ni4NAAAAAAAUyurVq1W8eHHz+7Zt2+qrr76y6HPs2DGtWrVKsbGxat68uSRp6dKl8vX11YoVK9StWzfNmDFDrVq10ujRoyVJNWrU0KFDh/Tuu+8qLCxMR48e1dq1a/Xjjz+qadOmkqSPPvpIgYGBd2hN7y8EAQAAAACAQgkJCdH8+fPN793c3PL1OXz4sIoUKaIHH3zQ3FaqVCkFBATo8OHD5j6dOnWymK9FixaaOXOmcnNzzWM0btzYPL1mzZry8vKy8hrZB4IAAAAAAEChuLm5qVq1arYuAwXEPQIAAAAAALdNYGCgcnJytGvXLnPbn3/+qYSEBNWqVcvcJzY21mK+2NhY1ahRQ46OjqpZs6ZycnK0d+9e8/SEhARduHDhjqzD/YYgAAAAAABw21SvXl2dOnVSnz59tH37dsXHx+uFF15QhQoVzJcDvPnmm9q0aZPGjx+vo0ePasmSJXr//fc1ZMgQSVJAQIDatGmjV199Vbt27dLevXvVu3dvubq62nLV7llcGgAAAAAAd5nPxwXbugSrWrx4sQYOHKgOHTooKytLjz76qL777jsVLVpUktSoUSN9+eWXGjNmjMaPHy8fHx+NGzdOYWFhFmP07t1bQUFBKleunCZMmGC+uSAKxmQYhmHrIgorLS1Nnp6eSk1NlYeHh63LAQAAdmDUqFFWG2vSpElWGwuwd/fqsUFGRoYSExPl7+8vFxcXW5eDe9zNfp64NAAAAAAAADtCEAAAAAAAgB0hCAAAAAAAwI4QBAAAAAAAYEcIAgAAAAAAsCMEAQAAAAAA2BGCAAAAAAAA7AhBAAAAAAAAdoQgAAAAAAAAO1LE1gUAAAAAACwdnRZ2R5dXY0jUHV3enRITE6OQkBCdP39eXl5eti7nrsEZAQAAAACAm2YymW74ioiIsHWJ+BecEQAAAAAAuGnJycnmn5ctW6YxY8YoISHB3Fa8ePECjZedna2iRYtarT78O84IAAAAAADcNG9vb/PL09NTJpPJ/L5s2bKaMWOGKlasKGdnZzVo0EDr1q0zz5uUlCSTyaRly5YpKChILi4uWrp0qcLCwtS5c2dNmzZNPj4+KlWqlPr166fs7GzzvJ9++qmaNGkid3d3eXt767nnnlNKSopFbd99951q1KghV1dXhYSEKCkpKV/9X3/9tWrXri1nZ2f5+flp+vTpt21b3a0IAgAAAAAAVjFr1ixNnz5d06ZN088//6zQ0FA98cQTOnbsmEW/ESNGaODAgTp8+LBCQ0MlSdHR0Tpx4oSio6O1ZMkSRUVFKSoqyjxPdna2xo8fr/j4eK1YsUJJSUkKCwszTz99+rS6dOmijh07Ki4uTr1799aIESMslrt37151795dzzzzjPbv36+IiAiNHj3aYjn2gEsDAAAAAABWMW3aNA0fPlzPPPOMJGnKlCmKjo7WzJkzNXfuXHO/8PBwdenSxWLeEiVK6P3335ejo6Nq1qyp9u3ba9OmTerTp48k6eWXXzb3rVKlimbPnq2mTZsqPT1dxYsX1/z581W1alXzN/wBAQHav3+/pkyZYp5vxowZatWqlUaPHi1JqlGjhg4dOqR3333XIlS433FGAAAAAADglqWlpenMmTNq0aKFRXuLFi10+PBhi7YmTZrkm7927dpydHQ0v/fx8bE49X/v3r3q2LGjKlWqJHd3dwUFBUmSTp06JUk6fPiwHnzwQYsxmzVrZvH+8OHD16zv2LFjys3NvdlVvecRBAAAAAAA7ig3N7d8bf+8YaDJZFJeXp4k6dKlSwoNDZWHh4eWLl2q3bt369tvv5UkZWVl3f6C7zMEAQAAAACAW+bh4aHy5csrNjbWoj02Nla1atW6pbGPHDmiP//8U++8844eeeQR1axZM9+NAgMDA/Xjjz9atO3cuTNfn2vVV6NGDYuzEe53BAEAAAAAAKsYOnSopkyZomXLlikhIUEjRoxQXFycBg4ceEvjVqpUSU5OTpozZ45++eUXrVq1SuPHj7fo89prr+nYsWMaOnSoEhIS9Pnnn+e7CeCbb76pTZs2afz48Tp69KiWLFmi999/X0OGDLml+u413CwQAAAAAO4yNYZE2bqEQhkwYIBSU1P15ptvKiUlRbVq1dKqVatUvXr1Wxq3TJkyioqK0qhRozR79mw1atRI06ZN0xNPPGHuU6lSJX399dcaNGiQ5syZowceeECTJk2yuMlgo0aN9OWXX2rMmDEaP368fHx8NG7cOLu6UaAkmQzDMGxdRGGlpaXJ09NTqamp8vDwsHU5AADADowaNcpqY02aNMlqYwH27l49NsjIyFBiYqL8/f3l4uJi63Jwj7vZz5NNLw3Izc3V6NGj5e/vL1dXV1WtWlXjx4/XPZxNAAAAAABwV7PppQFTpkzR/PnztWTJEtWuXVt79uzRSy+9JE9PTw0YMMCWpQEAAAAAcF+yaRDwww8/qFOnTmrfvr0kyc/PT1988UW+Oz0CAAAAAADrsOmlAc2bN9emTZt09OhRSVJ8fLy2b9+utm3bXrN/Zmam0tLSLF4AAAAAAODm2fSMgBEjRigtLU01a9aUo6OjcnNzNXHiRD3//PPX7D958mRFRkbe4SoBALg/7bzFRzn900OzZll1PAAAcHvY9IyAL7/8UkuXLtXnn3+uffv2acmSJZo2bZqWLFlyzf4jR45Uamqq+XX69Ok7XDEAAAAAAPc2m54RMHToUI0YMULPPPOMJKlu3bo6efKkJk+erJ49e+br7+zsLGdn5ztdJgAAAAAA9w2bnhFw+fJlOThYluDo6Ki8vDwbVQQAAAAAwP3NpmcEdOzYURMnTlSlSpVUu3Zt/fTTT5oxY4ZefvllW5YFAAAAAMB9y6ZBwJw5czR69Gj17dtXKSkpKl++vF599VWNGTPGlmUBAAAAgE0Nixl8R5c3NXiGVceLiYlRSEiIzp8/Ly8vL6uOfTNMJpO+/fZbde7c+Y4v+15g00sD3N3dNXPmTJ08eVJXrlzRiRMnNGHCBDk5OdmyLAAAAADAdZhMphu+IiIibF3iHREcHKzw8HBbl1EoNj0jAAAAAABwb0lOTjb/vGzZMo0ZM0YJCQnmtuLFi2vPnj22KM1qsrKy7tgX1HdyWVfZ9IwAAAAAAMC9xdvb2/zy9PSUyWSyaCtevLi57969e9WkSRMVK1ZMzZs3twgMwsLC8p26Hx4eruDgYPP74OBgDRgwQMOGDVPJkiXl7e2d74yDY8eO6dFHH5WLi4tq1aql77//Pl/Np0+fVvfu3eXl5aWSJUuqU6dOSkpKylfLxIkTVb58eQUEBEiS5s2bp+rVq8vFxUXlypVT165dzf23bNmiWbNmmc+EuDreli1b9MADD8jZ2Vk+Pj4aMWKEcnJyLNapf//+Cg8PV+nSpRUaGqqYmBiZTCatX79eDRs2lKurq1q2bKmUlBStXbtWgYGB8vDw0HPPPafLly8X5Nd1TQQBAAAAAIDb4q233tL06dO1Z88eFSlSpFA3hl+yZInc3Ny0a9cuTZ06VePGjTMf7Ofl5alLly5ycnLSrl27tGDBAg0fPtxi/uzsbIWGhsrd3V3btm1TbGysihcvrjZt2igrK8vcb9OmTUpISND333+v1atXa8+ePRowYIDGjRunhIQErVu3To8++qgkadasWWrWrJn69Omj5ORkJScny9fXV7/++qvatWunpk2bKj4+XvPnz9dHH32kCRMm5FsnJycnxcbGasGCBeb2iIgIvf/++/rhhx/M4cXMmTP1+eefa82aNdqwYYPmzJlT4G34T1waAAAAAAC4LSZOnKigoCBJ0ogRI9S+fXtlZGTIxcXlpseoV6+exo4dK0mqXr263n//fW3atEmPPfaYNm7cqCNHjmj9+vUqX768JGnSpElq27atef5ly5YpLy9PixYtkslkkiQtXrxYXl5eiomJ0eOPPy5JcnNz06JFi8yn6X/zzTdyc3NThw4d5O7ursqVK6thw4aSJE9PTzk5OalYsWLy9vY2L2vevHny9fXV+++/L5PJpJo1a+rMmTMaPny4xowZIwcHB/N6TJ061Tzf1cstJkyYoBYtWkiSevXqpZEjR+rEiROqUqWKJKlr166Kjo7OF3YUFGcEAAAAAABui3r16pl/9vHxkSSlpKQUeoyr41wd4/Dhw/L19TWHAJLUrFkzi/7x8fE6fvy43N3dVbx4cRUvXlwlS5ZURkaGTpw4Ye5Xt25di2v1H3vsMVWuXFlVqlRRjx49tHTp0n89Lf/w4cNq1qyZOXCQpBYtWig9PV3/+9//zG2NGzf+13UtV66cihUrZg4BrrYVdPtdC2cEAAAAAABui6JFi5p/vnpwnJeXJ0lycHCQYRgW/bOzs284xtVxro5xM9LT09W4cWMtXbo037QyZcqYf3Zzc7OY5u7urn379ikmJkYbNmzQmDFjFBERod27d9/yIxH/uayr/rm9bnXdr4czAgAAAAAAd1yZMmUsnkAgSXFxcQUaIzAwUKdPn7YYZ+fOnRZ9GjVqpGPHjqls2bKqVq2axcvT0/OG4xcpUkStW7fW1KlT9fPPPyspKUmbN2+WJDk5OSk3NzdfPTt27LAIOGJjY+Xu7q6KFSsWaN1uJ4IAAAAAAMAd17JlS+3Zs0effPKJjh07prFjx+rAgQMFGqN169aqUaOGevbsqfj4eG3btk1vvfWWRZ/nn39epUuXVqdOnbRt2zYlJiYqJiZGAwYMsDhd/59Wr16t2bNnKy4uTidPntQnn3yivLw88xMF/Pz8tGvXLiUlJemPP/5QXl6e+vbtq9OnT+uNN97QkSNHtHLlSo0dO1aDBw823x/gbsClAQAAAABwl5kaPMPWJdx2oaGhGj16tIYNG6aMjAy9/PLLevHFF7V///6bHsPBwUHffvutevXqpQceeEB+fn6aPXu22rRpY+5TrFgxbd26VcOHD1eXLl108eJFVahQQa1atZKHh8d1x/by8tI333yjiIgIZWRkqHr16vriiy9Uu3ZtSdKQIUPUs2dP1apVS1euXFFiYqL8/Pz03XffaejQoapfv75KliypXr166e233y78hroNTMY/L8q4h6SlpcnT01Opqak3/AUCAID8dg4caNXxHpo1y6rj3a1GjRpltbEmTZpktbEAe3evHhtkZGQoMTFR/v7+BbqTPnAtN/t5unvOTQAAAAAAALcdQQAAAAAAAHaEIAAAAAAAADtCEAAAAAAAgB0hCAAAAAAAwI4QBAAAAAAAYEcIAgAAAAAAsCMEAQAAAAAA2BGCAAAAAAAA7EgRWxcAAAAAALC0c+DAO7q8h2bNuu3LiImJUUhIiM6fPy8vLy9FRUUpPDxcFy5cuO48ERERWrFiheLi4iza5s+fr5SUFH377bdasWKFLly4oBUrVtz2dbhfcEYAAAAAAKBAFixYIHd3d+Xk5Jjb0tPTVbRoUQUHB1v0jYmJkclkko+Pj5KTk+Xp6Vno5R4+fFiRkZH64IMPlJycrLZt22rWrFmKiooq9Jj2iDMCAAAAAAAFEhISovT0dO3Zs0cPPfSQJGnbtm3y9vbWrl27lJGRIRcXF0lSdHS0KlWqpICAgFte7okTJyRJnTp1kslkkiQ5OzvfcJ6srCw5OTnd8rLvJ5wRAAAAAAAokICAAPn4+CgmJsbcFhMTo06dOsnf3187d+60aA8JCTGfGXCjSwHeeecdlStXTu7u7urVq5cyMjLM0yIiItSxY0dJkoODgzkICAsLU+fOnc39goOD1b9/f4WHh6t06dIKDQ2VJB04cEBt27ZV8eLFVa5cOfXo0UN//PGHFbbGvYcgAAAAAABQYCEhIYqOjja/j46OVnBwsIKCgsztV65c0a5duxQSEvKv43355ZeKiIjQpEmTtGfPHvn4+GjevHnm6UOGDNHixYslScnJyUpOTr7uWEuWLJGTk5NiY2O1YMECXbhwQS1btlTDhg21Z88erVu3Tr///ru6d+9e2NW/p3FpAAAAAACgwEJCQhQeHq6cnBxduXJFP/30k4KCgpSdna0FCxZIknbs2KHMzEyFhITol19+ueF4M2fOVK9evdSrVy9J0oQJE7Rx40bzWQHFixeXl5eXJMnb2/uGY1WvXl1Tp041v58wYYIaNmyoSZMmmds+/vhj+fr66ujRo6pRo0aB1/9exhkBAAAAAIACCw4O1qVLl7R7925t27ZNNWrUUJkyZRQUFGS+T0BMTIyqVKmiSpUq/et4hw8f1oMPPmjR1qxZs0LV1rhxY4v38fHxio6OVvHixc2vmjVrSvr/+w7YE84IAAAAAAAUWLVq1VSxYkVFR0fr/PnzCgoKkiSVL19evr6++uGHHxQdHa2WLVve8drc3Nws3qenp6tjx46aMmVKvr4+Pj53qqy7BmcEAAAAAAAK5epNAGNiYiweG/joo49q7dq1+vHHH2/q/gCSFBgYqF27dlm0/f2mg7eiUaNGOnjwoPz8/FStWjWL1z9DA3tAEAAAAAAAKJSQkBBt375dcXFx5jMCJCkoKEgffPCBsrKybjoIGDhwoD7++GMtXrxYR48e1dixY3Xw4EGr1NmvXz+dO3dOzz77rHbv3q0TJ05o/fr1eumll5Sbm2uVZdxLuDQAAAAAAO4yD82aZesSbkpISIiuXLmimjVrqly5cub2oKAgXbx40fyYwZvx9NNP68SJExo2bJgyMjL01FNP6fXXX9f69etvuc7y5csrNjZWw4cP1+OPP67MzExVrlxZbdq0kYOD/X0/bjIMw7B1EYWVlpYmT09PpaamysPDw9blAABwT9k5cKBVx7tX/tF6q0aNGmW1sf5+92oAt+ZePTbIyMhQYmKi/P395eLiYutycI+72c+T/UUfAAAAAADYMYIAAAAAAADsCEEAAAAAAAB2hCAAAAAAAAA7QhAAAAAAAIAdIQgAAAAAAMCOEAQAAAAAAGBHCAIAAAAAALAjBAEAAAAAANiRIrYuAAAAAABgae6w5Xd0ef2mdr3ty4iJiVFISIjOnz8vLy+v2748XB9BAABYgTX/WN+JP8QAAACFtWDBAg0dOlTnz59XkSJ/HVKmp6erRIkSatGihWJiYsx9rx78Hz9+XM2bN1dycrI8PT1vellhYWG6cOGCVqxYYeW1+Etubq7effddRUVF6eTJk3J1dVX16tXVp08f9e7dW5IUHBysBg0aaObMmbelBlsgCAAAAAAA3LSQkBClp6drz549euihhyRJ27Ztk7e3t3bt2qWMjAy5uLhIkqKjo1WpUiVVrVpVkuTt7W2TmrOysuTk5JSvPTIyUh988IHef/99NWnSRGlpadqzZ4/Onz9/x2qwBe4RAAAAAAC4aQEBAfLx8cn3zX+nTp3k7++vnTt3WrSHhISYfzaZTLpw4YIkKSoqSl5eXlq/fr0CAwNVvHhxtWnTRsnJyZKkiIgILVmyRCtXrpTJZJLJZDIv8/Tp0+revbu8vLxUsmRJderUSUlJSeblhoWFqXPnzpo4caLKly+vgICAa67LqlWr1LdvX3Xr1k3+/v6qX7++evXqpSFDhpjH2bJli2bNmmWu4epytmzZogceeEDOzs7y8fHRiBEjlJOTYx47ODhY/fv3V3h4uEqXLq3Q0FDzNli/fr0aNmwoV1dXtWzZUikpKVq7dq0CAwPl4eGh5557TpcvX76VX9MNEQQAAAAAAAokJCRE0dHR5vfR0dEKDg5WUFCQuf3KlSvatWuXOQi4lsuXL2vatGn69NNPtXXrVp06dcp8ED5kyBB1797dHA4kJyerefPmys7OVmhoqNzd3bVt2zbFxsaaQ4SsrCzz2Js2bVJCQoK+//57rV69+prL9/b21ubNm3X27NlrTp81a5aaNWumPn36mGvw9fXVr7/+qnbt2qlp06aKj4/X/Pnz9dFHH2nChAkW8y9ZskROTk6KjY3VggULzO0RERF6//339cMPP5hDjZkzZ+rzzz/XmjVrtGHDBs2ZM+dffguFx6UBAAAAAIACCQkJUXh4uHJycnTlyhX99NNPCgoKUnZ2tvmAd8eOHcrMzLxhEHC1/9VLB/r3769x48ZJkooXLy5XV1dlZmZaXFLw2WefKS8vT4sWLZLJZJIkLV68WF5eXoqJidHjjz8uSXJzc9OiRYtueDr+jBkz1LVrV3l7e6t27dpq3ry5OnXqpLZt20qSPD095eTkpGLFilnUMG/ePPn6+ur999+XyWRSzZo1debMGQ0fPlxjxoyRg8Nf37lXr15dU6dONc939WyHCRMmqEWLFpKkXr16aeTIkTpx4oSqVKkiSeratauio6M1fPjwf/1dFAZnBAAAAAAACiQ4OFiXLl3S7t27tW3bNtWoUUNlypRRUFCQ+T4BMTExqlKliipVqnTdcYoVK2YOASTJx8dHKSkpN1x2fHy8jh8/Lnd3dxUvXlzFixdXyZIllZGRoRMnTpj71a1b91+vya9Vq5YOHDignTt36uWXX1ZKSoo6duxovlHg9Rw+fFjNmjUzBxGS1KJFC6Wnp+t///ufua1x48bXnL9evXrmn8uVK6dixYqZQ4Crbf+2HW4FZwQAAAAAAAqkWrVqqlixoqKjo3X+/HkFBQVJksqXLy9fX1/98MMPio6OVsuWLW84TtGiRS3em0wmGYZxw3nS09PVuHFjLV26NN+0MmXKmH92c3O7qXVxcHBQ06ZN1bRpU4WHh+uzzz5Tjx499NZbb8nf3/+mxrie69Xw9/U2mUzX3A55eXm3tOwbIQgAAAAAABRYSEiIYmJidP78eQ0dOtTc/uijj2rt2rX68ccf9frrr9/SMpycnJSbm2vR1qhRIy1btkxly5aVh4fHLY1/LbVq1ZIkXbp06bo1BAYG6uuvv5ZhGOazAmJjY+Xu7q6KFStavSZr49IAAAAAAECBhYSEaPv27YqLizOfESBJQUFB+uCDD5SVlXXD+wPcDD8/P/38889KSEjQH3/8oezsbD3//PMqXbq0OnXqpG3btikxMVExMTEaMGCAxWn5N6Nr16567733tGvXLp08eVIxMTHq16+fatSooZo1a5pr2LVrl5KSkvTHH38oLy9Pffv21enTp/XGG2/oyJEjWrlypcaOHavBgweb7w9wN+OMAAAAAAC4y/Sb2tXWJfyrkJAQXblyRTVr1lS5cuXM7UFBQbp48aL5MYO3ok+fPoqJiVGTJk2Unp5ufjrB1q1bNXz4cHXp0kUXL15UhQoV1KpVqwKfIRAaGqovvvhCkydPVmpqqry9vdWyZUtFRESoSJG/DpeHDBminj17qlatWrpy5YoSExPl5+en7777TkOHDlX9+vVVsmRJ9erVS2+//fYtre+dYjL+7QKMu1haWpo8PT2Vmpp6W04JAYCbNXfYcquNdS/84cf9YefAgVYd76FZs6w63t1q1KhRVhtr0qRJVhsLsHf36rFBRkaGEhMT5e/vLxcXF1uXg3vczX6e7v5zFgAAAAAAgNUQBAAAAAAAYEcIAgAAAAAAsCMEAQAAAAAA2BGCAAAAAAAA7AhBAAAAAAAAdoQgAAAAAAAAO0IQAAAAAACAHSEIAAAAAADcE6KiouTl5WXrMu55RWxdAAAAAADA0vheoXd0eaM/Wl/gecLCwnThwgWtWLHCoj0mJkYhISE6f/681Q/an376abVr186qY9ojggAAAAAAwF0jKytLTk5O+dqzs7Pl6uoqV1dXG1R1f+HSAAAAAADAbREREaEGDRpYtM2cOVN+fn7m92FhYercubMmTpyo8uXLKyAgQElJSTKZTFq2bJmCgoLk4uKipUuXXvPSgP/+979q2rSpXFxcVLp0aT355JPmaZ9++qmaNGkid3d3eXt767nnnlNKSop5ekxMjEwmkzZt2qQmTZqoWLFiat68uRISEiyWsXLlSjVq1EguLi6qUqWKIiMjlZOTY7XtdKcRBAAAAAAAbGrTpk1KSEjQ999/r9WrV5vbR4wYoYEDB+rw4cMKDc1/ucSaNWv05JNPql27dvrpp5+0adMmPfDAA+bp2dnZGj9+vOLj47VixQolJSUpLCws3zhvvfWWpk+frj179qhIkSJ6+eWXzdO2bdumF198UQMHDtShQ4f0wQcfKCoqShMnTrTuRriDuDQAAAAAAFAoq1evVvHixS3acnNzCzyOm5ubFi1aZL4kICkpSZIUHh6uLl26XHe+iRMn6plnnlFkZKS5rX79+uaf/35AX6VKFc2ePVtNmzZVenq6Rd0TJ05UUFCQpL/Ch/bt2ysjI0MuLi6KjIzUiBEj1LNnT/M448eP17BhwzR27NgCr+vdgCAAAAAAAFAoISEhmj9/vkXbrl279MILLxRonLp1617zvgBNmjS54XxxcXHq06fPdafv3btXERERio+P1/nz55WXlydJOnXqlGrVqmXuV69ePfPPPj4+kqSUlBRVqlRJ8fHxio2NtTgDIDc3VxkZGbp8+bKKFSt2cyt5FyEIAAAAAAAUipubm6pVq2bR9r///c/8s4ODgwzDsJienZ19zXGuN/6N3OjGgZcuXVJoaKhCQ0O1dOlSlSlTRqdOnVJoaKiysrIs+hYtWtT8s8lkkiRzaJCenq7IyMhrnpng4uJyw/ruVgQBAAAAAIDbokyZMvrtt99kGIb5ADsuLs5q49erV0+bNm3SSy+9lG/akSNH9Oeff+qdd96Rr6+vJGnPnj0FXkajRo2UkJCQL/C4lxEEAAAAAABui+DgYJ09e1ZTp05V165dtW7dOq1du1YeHh5WGX/s2LFq1aqVqlatqmeeeUY5OTn67rvvNHz4cFWqVElOTk6aM2eOXnvtNR04cEDjx48v8DLGjBmjDh06qFKlSuratascHBwUHx+vAwcOaMKECVZZjzuNpwYAAAAAAG6LwMBAzZs3T3PnzlX9+vX1448/asiQIVYbPzg4WF999ZVWrVqlBg0aqGXLlvrxxx8l/XU2QlRUlL766ivVqlVL77zzjqZNm1bgZYSGhmr16tXasGGDmjZtqoceekjvvfeeKleubLX1uNNMxj8v2LiHpKWlydPTU6mpqVZLlACgMOYOW261sfpN7Wq1sYAb2TlwoFXHe2jWLKuOd7caNWqU1caaNGmS1cYC7N29emyQkZGhxMRE+fv737PXm+PucbOfJ84IAAAAAADAjhAEAAAAAABgRwgCAAAAAACwIwQBAAAAAADYEYIAAAAAAADsCEEAAAAAAAB2hCAAAAAAAAA7QhAAAAAAAIAdIQgAAAAAAMCOEAQAAAAAAO5qSUlJMplMiouLs3Upd1UthVXE1gUAAAAAACzFv7vxji6v/tDWBZ4nLCxMS5YsydceGhqqdevWFbqWsLAwXbhwQStWrCj0GLgxggAAAAAAQKG0adNGixcvtmhzdnYu1Fi5ubkymUzWKAv/gksDAAAAAACF4uzsLG9vb4tXiRIlJEkzZsxQ3bp15ebmJl9fX/Xt21fp6enmeaOiouTl5aVVq1apVq1acnZ21ssvv6wlS5Zo5cqVMplMMplMiomJMc/zyy+/KCQkRMWKFVP9+vW1Y8cOi3qioqJUqVIlFStWTE8++aSmT58uLy8v8/SwsDB17tzZYp7w8HAFBweb369bt04PP/ywvLy8VKpUKXXo0EEnTpy47jbIzc3Vyy+/rJo1a+rUqVOSpJUrV6pRo0ZycXFRlSpVFBkZqZycnAJu3duHIAAAAAAAYHUODg6aPXu2Dh48qCVLlmjz5s0aNmyYRZ/Lly9rypQpWrRokQ4ePKjZs2ere/fuatOmjZKTk5WcnKzmzZub+7/11lsaMmSI4uLiVKNGDT377LPmA+xdu3apV69e6t+/v+Li4hQSEqIJEyYUuO5Lly5p8ODB2rNnjzZt2iQHBwc9+eSTysvLy9c3MzNT3bp1U1xcnLZt26ZKlSpp27ZtevHFFzVw4EAdOnRIH3zwgaKiojRx4sQC13K72DwI+PXXX/XCCy+oVKlScnV1Vd26dbVnzx5blwUAAAAA+BerV69W8eLFLV6TJk2S9Nc37SEhIfLz81PLli01YcIEffnllxbzZ2dna968eWrevLkCAgLk4eEhV1dXizMNnJyczP2HDBmi9u3bq0aNGoqMjNTJkyd1/PhxSdKsWbPUpk0bDRs2TDVq1NCAAQMUGhpa4HV66qmn1KVLF1WrVk0NGjTQxx9/rP379+vQoUMW/dLT09W+fXudPXtW0dHRKlOmjCQpMjJSI0aMUM+ePVWlShU99thjGj9+vD744IMC13K72PQeAefPn1eLFi0UEhKitWvXqkyZMjp27Jj5VBIAAAAAwN0rJCRE8+fPt2grWbKkJGnjxo2aPHmyjhw5orS0NOXk5CgjI0OXL19WsWLFJElOTk6qV6/eTS/v7319fHwkSSkpKapZs6YOHz6sJ5980qJ/s2bNCnzjwmPHjmnMmDHatWuX/vjjD/OZAKdOnVKdOnXM/Z599llVrFhRmzdvlqurq7k9Pj5esbGxFmcA5Obm5lt3W7JpEDBlyhT5+vpa3FzC39/fhhUBAAAAAG6Wm5ubqlWrlq89KSlJHTp00Ouvv66JEyeqZMmS2r59u3r16qWsrCzzwbCrq2uBbhBYtGhR889X57vWKfvX4+DgIMMwLNqys7Mt3nfs2FGVK1fWwoULVb58eeXl5alOnTrKysqy6NeuXTt99tln2rFjh1q2bGluT09PV2RkpLp06ZJv+S4uLjdd6+1k0yBg1apVCg0NVbdu3bRlyxZVqFBBffv2VZ8+fa7ZPzMzU5mZmeb3aWlpd6pUAAAAAMBN2rt3r/Ly8jR9+nQ5OPx1Rfo/Lwu4HicnJ+Xm5hZ4mYGBgdq1a5dF286dOy3elylTRgcOHLBoi4uLMwcMf/75pxISErRw4UI98sgjkqTt27dfc3mvv/666tSpoyeeeEJr1qxRUFCQJKlRo0ZKSEi4ZkByt7BpEPDLL79o/vz5Gjx4sEaNGqXdu3drwIABcnJyUs+ePfP1nzx5siIjI21QKQAAAADgnzIzM/Xbb79ZtBUpUkTVqlVTdna25syZo44dOyo2NlYLFiy4qTH9/Py0fv16JSQkqFSpUvL09Lyp+QYMGKAWLVpo2rRp6tSpk9avX5/vsoCWLVvq3Xff1SeffKJmzZrps88+04EDB9SwYUNJUokSJVSqVCl9+OGH8vHx0alTpzRixIjrLvONN95Qbm6uOnTooLVr1+rhhx/WmDFj1KFDB1WqVEldu3aVg4OD4uPjdeDAgULdvPB2sOnNAvPy8tSoUSNNmjRJDRs21CuvvKI+ffpc9wMycuRIpaamml+nT5++wxUDAAAAAK5at26dfHx8LF4PP/yw6tevrxkzZmjKlCmqU6eOli5dqsmTJ9/UmH369FFAQICaNGmiMmXKKDY29qbme+ihh7Rw4ULNmjVL9evX14YNG/T2229b9AkNDdXo0aM1bNgwNW3aVBcvXtSLL75onu7g4KD//Oc/2rt3r+rUqaNBgwbp3XffveFyw8PDFRkZqXbt2umHH35QaGioVq9erQ0bNqhp06Z66KGH9N5776ly5co3tR53gsn45wUSd1DlypX12GOPadGiRea2+fPna8KECfr111//df60tDR5enoqNTVVHh4et7NUALihucOWW22sflO7Wm0s4EZ2Dhxo1fEemjXLquPdrUaNGmW1sa7eWRvArbtXjw0yMjKUmJgof3//u+b68ftJVFSUwsPDdeHCBVuXckfc7OfJpmcEtGjRQgkJCRZtR48evauSEgAAAAAA7ic2DQIGDRqknTt3atKkSTp+/Lg+//xzffjhh+rXr58tywIAAAAA4L5l0yCgadOm+vbbb/XFF1+oTp06Gj9+vGbOnKnnn3/elmUBAAAAAO4DYWFhdnNZQEHY9KkBktShQwd16NDB1mUAAAAAAGAXbHpGAAAAAAAAuLMIAgAAAAAAsCMEAQAAAAAA2BGCAAAAAAAA7AhBAAAAAAAAdoQgAAAAAABwTwgLC1Pnzp0L1Tc4OFjh4eE3nMfPz08zZ84sdH33Cps/PhAAAAAAYGnUqFF3dHmTJk0q8DxhYWG6cOGCVqxYYf2CboNvvvlGRYsWtXUZdwWCAAAAAADAfa9kyZK2LuGuwaUBAAAAAACrmjFjhurWrSs3Nzf5+vqqb9++Sk9PN0+PioqSl5eX1q9fr8DAQBUvXlxt2rRRcnKyuU9ubq4GDx4sLy8vlSpVSsOGDZNhGBbLWb58uerWrStXV1eVKlVKrVu31qVLl65Z0z8vDUhJSVHHjh3l6uoqf39/LV26NN88Fy5cUO/evVWmTBl5eHioZcuWio+Pv8WtY3sEAQAAAAAAq3JwcNDs2bN18OBBLVmyRJs3b9awYcMs+ly+fFnTpk3Tp59+qq1bt+rUqVMaMmSIefr06dMVFRWljz/+WNu3b9e5c+f07bffmqcnJyfr2Wef1csvv6zDhw8rJiZGXbp0yRcWXE9YWJhOnz6t6OhoLV++XPPmzVNKSopFn27duiklJUVr167V3r171ahRI7Vq1Urnzp27ha1je1waAAAAAACwqr9/8+7n56cJEybotdde07x588zt2dnZWrBggapWrSpJ6t+/v8aNG2eePnPmTI0cOVJdunSRJC1YsEDr1683T09OTlZOTo66dOmiypUrS5Lq1q17U/UdPXpUa9eu1Y8//qimTZtKkj766CMFBgaa+2zfvl0//vijUlJS5OzsLEmaNm2aVqxYoeXLl+uVV14pyCa5qxAEAAAAAACsauPGjZo8ebKOHDmitLQ05eTkKCMjQ5cvX1axYsUkScWKFTOHAJLk4+Nj/kY+NTVVycnJevDBB83TixQpoiZNmpi/8a9fv75atWqlunXrKjQ0VI8//ri6du2qEiVK/Gt9hw8fVpEiRdS4cWNzW82aNeXl5WV+Hx8fr/T0dJUqVcpi3itXrujEiRMF3yh3ES4NAAAAAABYTVJSkjp06KB69erp66+/1t69ezV37lxJUlZWlrnfP+/gbzKZbvq0fklydHTU999/r7Vr16pWrVqaM2eOAgIClJiYaJX1SE9Pl4+Pj+Li4ixeCQkJGjp0qFWWYSsEAQAAAAAAq9m7d6/y8vI0ffp0PfTQQ6pRo4bOnDlToDE8PT3l4+OjXbt2mdtycnK0d+9ei34mk0ktWrRQZGSkfvrpJzk5OVncR+B6atasmW+8hIQEXbhwwfy+UaNG+u2331SkSBFVq1bN4lW6dOkCrc/dhksDAAAAAACFkpqaqri4OIu20qVLKzs7W3PmzFHHjh0VGxurBQsWFHjsgQMH6p133lH16tVVs2ZNzZgxw+JAfdeuXdq0aZMef/xxlS1bVrt27dLZs2ctrvO/noCAALVp00avvvqq5s+fryJFiig8PFyurq7mPq1bt1azZs3UuXNnTZ061RxorFmzRk8++aSaNGlS4HW6W3BGAAAAAACgUGJiYtSwYUOL16effqoZM2ZoypQpqlOnjpYuXarJkycXeOw333xTPXr0UM+ePdWsWTO5u7vrySefNE/38PDQ1q1b1a5dO9WoUUNvv/22pk+frrZt297U+IsXL1b58uUVFBSkLl266JVXXlHZsmXN000mk7777js9+uijeumll1SjRg0988wzOnnypMqVK1fg9bmbmIyCXIRxl0lLS5Onp6dSU1Pl4eFh63IA2LG5w5Zbbax+U7tabSzgRnYOHGjV8R6aNcuq492tRo0aZbWxJk2aZLWxAHt3rx4bZGRkKDExUf7+/nJxcbF1ObjH3ezniTMCAAAAAACwIwQBAAAAAADYEYIAAAAAAADsCEEAAAAAAAB2hCAAAAAAAAA7QhAAAAAAADaWl5dn6xJwH7jZz1GR21wHAAAAAOA6nJyc5ODgoDNnzqhMmTJycnKSyWSydVm4xxiGoaysLJ09e1YODg5ycnK6YX+CAAAAAACwEQcHB/n7+ys5OVlnzpyxdTm4xxUrVkyVKlWSg8ONT/4nCAAAAAAAG3JyclKlSpWUk5Oj3NxcW5eDe5Sjo6OKFClyU2eUEAQAAAAAgI2ZTCYVLVpURYsWtXUpsAPcLBAAAAAAADtCEAAAAAAAgB0hCAAAAAAAwI4QBAAAAAAAYEcIAgAAAAAAsCMEAQAAAAAA2BGCAAAAAAAA7AhBAAAAAAAAdoQgAAAAAAAAO0IQAAAAAACAHSEIAAAAAADAjhQqCKhSpYr+/PPPfO0XLlxQlSpVbrkoAAAAAABwexQqCEhKSlJubm6+9szMTP3666+3XBQAAAAAALg9ihSk86pVq8w/r1+/Xp6enub3ubm52rRpk/z8/KxWHAAAAAAAsK4CBQGdO3eWJJlMJvXs2dNiWtGiReXn56fp06dbrTgAAAAAAGBdBQoC8vLyJEn+/v7avXu3SpcufVuKQn7DYgZbdbypwTOsOh7uH8+NibHqeJ+PC7bqeNayc+BA6w7o/Ih1xwMAAABukwIFAVclJiZauw4AAAAAAHAHFCoIkKRNmzZp06ZNSklJMZ8pcNXHH398y4UBAAAAAADrK1QQEBkZqXHjxqlJkyby8fGRyWSydl0AAAAAAOA2KFQQsGDBAkVFRalHjx7WrgcAAAAAANxGDoWZKSsrS82bN7d2LQAAAAAA4DYrVBDQu3dvff7559auBQAAAAAA3GaFujQgIyNDH374oTZu3Kh69eqpaNGiFtNnzODRdAAAAAAA3I0KFQT8/PPPatCggSTpwIEDFtO4cSAAAAAAAHevQgUB0dHR1q4DAAAAAADcAYW6RwAAAAAAALg3FeqMgJCQkBteArB58+ZCFwQAAAAAAG6fQgUBV+8PcFV2drbi4uJ04MAB9ezZ0xp1AQAAAACA26BQQcB77713zfaIiAilp6ffUkEAAAAAAOD2seo9Al544QV9/PHH1hwSAAAAAABYkVWDgB07dsjFxcWaQwIAAAAAACsq1KUBXbp0sXhvGIaSk5O1Z88ejR492iqFAQAAAAAA6ytUEODp6Wnx3sHBQQEBARo3bpwef/xxqxQGAAAAAACsr1BBwOLFi61dBwAAAAAAuAMKFQRctXfvXh0+fFiSVLt2bTVs2NAqRQEAAAAAgNujUEFASkqKnnnmGcXExMjLy0uSdOHCBYWEhOg///mPypQpY80aAQAAAACAlRTqqQFvvPGGLl68qIMHD+rcuXM6d+6cDhw4oLS0NA0YMMDaNQIAAAAAACsp1BkB69at08aNGxUYGGhuq1WrlubOncvNAgEAAAAAuIsV6oyAvLw8FS1aNF970aJFlZeXd8tFAQAAAACA26NQQUDLli01cOBAnTlzxtz266+/atCgQWrVqpXVigMAAAAAANZVqCDg/fffV1pamvz8/FS1alVVrVpV/v7+SktL05w5c6xdIwAAAAAAsJJC3SPA19dX+/bt08aNG3XkyBFJUmBgoFq3bm3V4gAAAAAAgHUV6IyAzZs3q1atWkpLS5PJZNJjjz2mN954Q2+88YaaNm2q2rVra9u2bberVgAAAAAAcIsKFATMnDlTffr0kYeHR75pnp6eevXVVzVjxgyrFQcAAAAAAKyrQEFAfHy82rRpc93pjz/+uPbu3XvLRQEAAAAAgNujQEHA77//fs3HBl5VpEgRnT179paLAgAAAAAAt0eBgoAKFSrowIED153+888/y8fH55aLAgAAAAAAt0eBgoB27dpp9OjRysjIyDftypUrGjt2rDp06GC14gAAAAAAgHUV6PGBb7/9tr755hvVqFFD/fv3V0BAgCTpyJEjmjt3rnJzc/XWW2/dlkIBAAAAAMCtK1AQUK5cOf3www96/fXXNXLkSBmGIUkymUwKDQ3V3LlzVa5cudtSKAAAAAAAuHUFCgIkqXLlyvruu+90/vx5HT9+XIZhqHr16ipRosTtqA8AAAAAAFhRgYOAq0qUKKGmTZtasxYAAAAAAHCbFehmgQAAAAAA4N5GEAAAAAAAgB0hCAAAAAAAwI4QBAAAAAAAYEcIAgAAAAAAsCMEAQAAAAAA2BGCAAAAAAAA7AhBAAAAAAAAduSuCQLeeecdmUwmhYeH27oUAAAAAADuW3dFELB792598MEHqlevnq1LAQAAAADgvmbzICA9PV3PP/+8Fi5cqBIlSti6HAAAAAAA7ms2DwL69eun9u3bq3Xr1v/aNzMzU2lpaRYvAAAAAABw84rYcuH/+c9/tG/fPu3evfum+k+ePFmRkZG3uSoAuL/Ev7vRamPVH/rvoS3s19xhy602Vr+pXa02Fu4/w2IGW22sqcEzrDaWvRg1apRVx5s0aZJVxwPw72x2RsDp06c1cOBALV26VC4uLjc1z8iRI5Wammp+nT59+jZXCQAAAADA/cVmZwTs3btXKSkpatSokbktNzdXW7du1fvvv6/MzEw5OjpazOPs7CxnZ+c7XSoAAAAAAPcNmwUBrVq10v79+y3aXnrpJdWsWVPDhw/PFwIAAAAAAIBbZ7MgwN3dXXXq1LFoc3NzU6lSpfK1AwAAAAAA67D5UwMAAAAAAMCdY9OnBvxTTEyMrUsAAAAAAOC+xhkBAAAAAADYEYIAAAAAAADsCEEAAAAAAAB2hCAAAAAAAAA7QhAAAAAAAIAdIQgAAAAAAMCOEAQAAAAAAGBHCAIAAAAAALAjBAEAAAAAANgRggAAAAAAAOwIQQAAAAAAAHaEIAAAAAAAADtCEAAAAAAAgB0hCAAAAAAAwI4QBAAAAAAAYEcIAgAAAAAAsCMEAQAAAAAA2BGCAAAAAAAA7AhBAAAAAAAAdoQgAAAAAAAAO0IQAAAAAACAHSEIAAAAAADAjhAEAAAAAABgRwgCAAAAAACwIwQBAAAAAADYEYIAAAAAAADsCEEAAAAAAAB2hCAAAAAAAAA7QhAAAAAAAIAdIQgAAAAAAMCOEAQAAAAAAGBHCAIAAAAAALAjBAEAAAAAANgRggAAAAAAAOwIQQAAAAAAAHaEIAAAAAAAADtCEAAAAAAAgB0hCAAAAAAAwI4QBAAAAAAAYEcIAgAAAAAAsCMEAQAAAAAA2BGCAAAAAAAA7EgRWxdwt3luTIzVxorwiLLaWGpS0npjSdo5cKDVxnpo1iyrjQUAd4O79m+BpEVW/HvQxWojWd/4XqFWG+uJmkOtNhZwI3OHLbfqeP2mdrXaWPHvbrTaWADufZwRAAAAAACAHSEIAAAAAADAjhAEAAAAAABgRwgCAAAAAACwIwQBAAAAAADYEYIAAAAAAADsCEEAAAAAAAB2hCAAAAAAAAA7QhAAAAAAAIAdIQgAAAAAAMCOEAQAAAAAAGBHCAIAAAAAALAjBAEAAAAAANgRggAAAAAAAOwIQQAAAAAAAHaEIAAAAAAAADtCEAAAAAAAgB0hCAAAAAAAwI4QBAAAAAAAYEcIAgAAAAAAsCMEAQAAAAAA2BGCAAAAAAAA7AhBAAAAAAAAdoQgAAAAAAAAO0IQAAAAAACAHSEIAAAAAADAjhAEAAAAAABgRwgCAAAAAACwIwQBAAAAAADYEYIAAAAAAADsCEEAAAAAAAB2hCAAAAAAAAA7QhAAAAAAAIAdIQgAAAAAAMCOEAQAAAAAAGBHCAIAAAAAALAjBAEAAAAAANgRggAAAAAAAOwIQQAAAAAAAHaEIAAAAAAAADtCEAAAAAAAgB0hCAAAAAAAwI4QBAAAAAAAYEcIAgAAAAAAsCM2DQImT56spk2byt3dXWXLllXnzp2VkJBgy5IAAAAAALiv2TQI2LJli/r166edO3fq+++/V3Z2th5//HFdunTJlmUBAAAAAHDfKmLLha9bt87ifVRUlMqWLau9e/fq0UcftVFVAAAAAADcv2waBPxTamqqJKlkyZLXnJ6ZmanMzEzz+7S0tDtSFwAAAAAA94u7JgjIy8tTeHi4WrRooTp16lyzz+TJkxUZGXmHK8O/mTtsudXG6je1q9XGkqT4dzdabaz6Q1tbbSzgRsb3CrXqeE/UHGrV8QDYh+fGxFhtrAiPKKuNJUlqcu0vje431vx7wN8CAH931zw1oF+/fjpw4ID+85//XLfPyJEjlZqaan6dPn36DlYIAAAAAMC97644I6B///5avXq1tm7dqooVK163n7Ozs5ydne9gZQAAAAAA3F9sGgQYhqE33nhD3377rWJiYuTv72/LcgAAAAAAuO/ZNAjo16+fPv/8c61cuVLu7u767bffJEmenp5ydXW1ZWkAAAAAANyXbHqPgPnz5ys1NVXBwcHy8fExv5YtW2bLsgAAAAAAuG/Z/NIAAAAAAABw59w1Tw0AAAAAAAC3H0EAAAAAAAB2hCAAAAAAAAA7QhAAAAAAAIAdIQgAAAAAAMCOEAQAAAAAAGBHCAIAAAAAALAjBAEAAAAAANgRggAAAAAAAOwIQQAAAAAAAHaEIAAAAAAAADtCEAAAAAAAgB0hCAAAAAAAwI4QBAAAAAAAYEcIAgAAAAAAsCMEAQAAAAAA2BGCAAAAAAAA7AhBAAAAAAAAdoQgAAAAAAAAO0IQAAAAAACAHSEIAAAAAADAjhAEAAAAAABgRwgCAAAAAACwIwQBAAAAAADYEYIAAAAAAADsCEEAAAAAAAB2hCAAAAAAAAA7QhAAAAAAAIAdIQgAAAAAAMCOEAQAAAAAAGBHCAIAAAAAALAjBAEAAAAAANgRggAAAAAAAOwIQQAAAAAAAHaEIAAAAAAAADtCEAAAAAAAgB0hCAAAAAAAwI4QBAAAAAAAYEcIAgAAAAAAsCMEAQAAAAAA2BGCAAAAAAAA7AhBAAAAAAAAdqSIrQsA/m58r1CrjvdEzaFWHQ+2NSxmsNXG6mK1kezLqFGjrDbWpEmTrDYWAAAAbh5nBAAAAAAAYEcIAgAAAAAAsCMEAQAAAAAA2BGCAAAAAAAA7AhBAAAAAAAAdoQgAAAAAAAAO0IQAAAAAACAHSEIAAAAAADAjhAEAAAAAABgRwgCAAAAAACwIwQBAAAAAADYEYIAAAAAAADsCEEAAAAAAAB2hCAAAAAAAAA7QhAAAAAAAIAdIQgAAAAAAMCOEAQAAAAAAGBHCAIAAAAAALAjBAEAAAAAANgRggAAAAAAAOwIQQAAAAAAAHaEIAAAAAAAADtCEAAAAAAAgB0hCAAAAAAAwI4QBAAAAAAAYEcIAgAAAAAAsCMEAQAAAAAA2BGCAAAAAAAA7AhBAAAAAAAAdoQgAAAAAAAAO0IQAAAAAACAHSEIAAAAAADAjhAEAAAAAABgRwgCAAAAAACwIwQBAAAAAADYEYIAAAAAAADsCEEAAAAAAAB2hCAAAAAAAAA7QhAAAAAAAIAdIQgAAAAAAMCOEAQAAAAAAGBHCAIAAAAAALAjBAEAAAAAANgRggAAAAAAAOzIXREEzJ07V35+fnJxcdGDDz6oH3/80dYlAQAAAABwX7J5ELBs2TINHjxYY8eO1b59+1S/fn2FhoYqJSXF1qUBAAAAAHDfsXkQMGPGDPXp00cvvfSSatWqpQULFqhYsWL6+OOPbV0aAAAAAAD3nSK2XHhWVpb27t2rkSNHmtscHBzUunVr7dixI1//zMxMZWZmmt+npqZKktLS0qxWU3bmJauNlZ6RZbWxMi9l/nunAriUmWu1sa7ostXGysjKsdpYkpSeYb3fpzU/Z3cza+4DknW3mzX3A2vuA5L97Ad//3/wrbqb96m79W+BxH5QGNbcByT2g8K4m/cDa/4OrmRabx+Q7t79wJr7gGS938HVcQzDsMp4wP3MZNhwTzlz5owqVKigH374Qc2aNTO3Dxs2TFu2bNGuXbss+kdERCgyMvJOlwkAAADgHnH69GlVrFjR1mUAdzWbnhFQUCNHjtTgwYPN7/Py8nTu3DmVKlVKJpPJhpXZr7S0NPn6+ur06dPy8PCwdTmATbAfAOwHAPuA7RmGoYsXL6p8+fK2LgW469k0CChdurQcHR31+++/W7T//vvv8vb2ztff2dlZzs7OFm1eXl63s0TcJA8PD/7owe6xHwDsBwD7gG15enraugTgnmDTmwU6OTmpcePG2rRpk7ktLy9PmzZtsrhUAAAAAAAAWIfNLw0YPHiwevbsqSZNmuiBBx7QzJkzdenSJb300ku2Lg0AAAAAgPuOzYOAp59+WmfPntWYMWP022+/qUGDBlq3bp3KlStn69JwE5ydnTV27Nh8l2wA9oT9AGA/ANgHANxLbPrUAAAAAAAAcGfZ9B4BAAAAAADgziIIAAAAAADAjhAEAAAAAABgRwgCAEBSTEyMTCaTLly4cN0+UVFR8vLyumM1AfczPz8/zZw509ZlAIV2M383AOBuRRCAOyo4OFjh4eG2LgPIp3nz5kpOTpanp6etS7mmiIgINWjQwNZlAAVGgIb7wd367xfCCACFRRAAAJKcnJzk7e0tk8lk61IAAHeJrKwsW5cAALcFQQC0evVqeXl5KTc3V5IUFxcnk8mkESNGmPv07t1bL7zwgiRp+/bteuSRR+Tq6ipfX18NGDBAly5dMvedN2+eqlevLhcXF5UrV05du3aVJIWFhWnLli2aNWuWTCaTTCaTkpKS7tyKwq4EBwfrjTfeUHh4uEqUKKFy5cpp4cKFunTpkl566SW5u7urWrVqWrt2raRrf6sSFRWlSpUqqVixYnryySf1559/mqelpqbK0dFRe/bskSTl5eWpZMmSeuihh8x9PvvsM/n6+prfnz59Wt27d5eXl5dKliypTp06WewDMTExeuCBB+Tm5iYvLy+1aNFCJ0+eVFRUlCIjIxUfH2/ed6Kiom7PhoNdWr58uerWrStXV1eVKlVKrVu31qVLl5SXl6dx48apYsWKcnZ2VoMGDbRu3TrzfNfab67+DUlKSlJMTIxeeuklpaammj+7ERER5r6XL1/Wyy+/LHd3d1WqVEkffvjhHVxr3I+Cg4M1YMAADRs2TCVLlpS3t7fFZ+7UqVPq1KmTihcvLg8PD3Xv3l2///67efrVs68WLVokf39/ubi4/Ou/X/bu3asmTZqoWLFiat68uRISEixqWrlypRo1aiQXFxdVqVJFkZGRysnJMU+fMWOG6tatKzc3N/n6+qpv375KT083Tz958qQ6duyoEiVKyM3NTbVr19Z3332npKQkhYSESJJKlCghk8mksLAw625QAPcvA3bvwoULhoODg7F7927DMAxj5syZRunSpY0HH3zQ3KdatWrGwoULjePHjxtubm7Ge++9Zxw9etSIjY01GjZsaISFhRmGYRi7d+82HB0djc8//9xISkoy9u3bZ8yaNcu8nGbNmhl9+vQxkpOTjeTkZCMnJ+fOrzDsQlBQkOHu7m6MHz/eOHr0qDF+/HjD0dHRaNu2rfHhhx8aR48eNV5//XWjVKlSxqVLl4zo6GhDknH+/HnDMAxj586dhoODgzFlyhQjISHBmDVrluHl5WV4enqal9GoUSPj3XffNQzDMOLi4oySJUsaTk5OxsWLFw3DMIzevXsbzz//vGEYhpGVlWUEBgYaL7/8svHzzz8bhw4dMp577jkjICDAyMzMNLKzsw1PT09jyJAhxvHjx41Dhw4ZUVFRxsmTJ43Lly8bb775plG7dm3zvnP58uU7uj1x/zpz5oxRpEgRY8aMGUZiYqLx888/G3PnzjUuXrxozJgxw/Dw8DC++OIL48iRI8awYcOMokWLGkePHjUMw8i33xiGYfz000+GJCMxMdHIzMw0Zs6caXh4eJg/u1f3j8qVKxslS5Y05s6daxw7dsyYPHmy4eDgYBw5csQWmwH3iaCgIMPDw8OIiIgwjh49aixZssQwmUzGhg0bjNzcXKNBgwbGww8/bOzZs8fYuXOn0bhxYyMoKMg8/9ixYw03NzejTZs2xr59+4z4+Pjr/vvl6uf/wQcfNGJiYoyDBw8ajzzyiNG8eXPzeFu3bjU8PDyMqKgo48SJE8aGDRsMPz8/IyIiwtznvffeMzZv3mwkJiYamzZtMgICAozXX3/dPL19+/bGY489Zvz888/GiRMnjP/+97/Gli1bjJycHOPrr782JBkJCQlGcnKyceHChTuynQHc+wgCYBiG5QFN586djYkTJ5oPaP73v/8ZkoyjR48avXr1Ml555RWLebdt22Y4ODgYV65cMb7++mvDw8PDSEtLu+ZygoKCjIEDB97u1QGMoKAg4+GHHza/z8nJMdzc3IwePXqY25KTkw1Jxo4dO/Id0Dz77LNGu3btLMZ8+umnLYKAwYMHG+3btzcM468A7emnnzbq169vrF271jCMvwK0Dz/80DAMw/j000+NgIAAIy8vzzx/Zmam4erqaqxfv974888/DUlGTEzMNddn7NixRv369Qu9PYDr2bt3ryHJSEpKyjetfPnyxsSJEy3amjZtavTt29cwjH8PAgzDMBYvXmyx31xVuXJl44UXXjC/z8vLM8qWLWvMnz//1lcKduuf/+83jL8+s8OHDzc2bNhgODo6GqdOnTJPO3jwoCHJ+PHHHw3D+Ov/tUWLFjVSUlLyjfvPf79c/fxv3LjR3LZmzRpDknHlyhXDMAyjVatWxqRJkyzm+/TTTw0fH5/rrsNXX31llCpVyvy+bt26FsHBtWr4+z4IADeDSwMgSQoKClJMTIwMw9C2bdvUpUsXBQYGavv27dqyZYvKly+v6tWrKz4+XlFRUSpevLj5FRoaqry8PCUmJuqxxx5T5cqVVaVKFfXo0UNLly7V5cuXbb16sFP16tUz/+zo6KhSpUqpbt265rZy5cpJklJSUvLNe/jwYT344IMWbc2aNbN4HxQUpO3btys3N1dbtmxRcHCwgoODFRMTozNnzuj48eMKDg6WJMXHx+v48eNyd3c37zslS5ZURkaGTpw4oZIlSyosLEyhoaHq2LGjZs2apeTkZGttCuC66tevr1atWqlu3brq1q2bFi5cqPPnzystLU1nzpxRixYtLPq3aNFChw8ftsqy/76PmkwmeXt7X3N/BAri758rSfLx8VFKSooOHz4sX19fi0u2atWqJS8vL4vPdOXKlVWmTJlCLc/Hx0fS//9diY+P17hx4yz+3dSnTx8lJyeb/320ceNGtWrVShUqVJC7u7t69OihP//80zx9wIABmjBhglq0aKGxY8fq559/LuAWAYD8CAIg6a9r6rZv3674+HgVLVpUNWvWNB/QbNmyRUFBQZKk9PR0vfrqq4qLizO/4uPjdezYMVWtWlXu7u7at2+fvvjiC/n4+GjMmDGqX78+d7OFTRQtWtTivclksmi7emPAvLy8Qo3/6KOP6uLFi9q3b5+2bt1qEQT8PUCT/tp3GjdubLHvxMXF6ejRo3ruueckSYsXL9aOHTvUvHlzLVu2TDVq1NDOnTsLVRtwsxwdHfX9999r7dq1qlWrlubMmaOAgAAlJib+67wODn/9M8IwDHNbdnb2TS/7WvtoYfdH4Kpb/Vy5ubkVenn//LuSnp6uyMhIi//v79+/X8eOHZOLi4uSkpLUoUMH1atXT19//bX27t2ruXPnSvr/GxX27t1bv/zyi3r06KH9+/erSZMmmjNnToFqBIB/IgiAJOmRRx7RxYsX9d5775kP+q8e0MTExJi/1WzUqJEOHTqkatWq5Xs5OTlJkooUKaLWrVtr6tSp+vnnn5WUlKTNmzdL+uvO7FdvSgjczQIDA7Vr1y6Ltn8elHt5ealevXp6//33zQHao48+qp9++kmrV68270vSX/vOsWPHVLZs2Xz7zt8fWdiwYUONHDlSP/zwg+rUqaPPP/9cEvsObi+TyaQWLVooMjJSP/30k5ycnLRp0yaVL19esbGxFn1jY2NVq1YtSTJ/a/r3s1fi4uIs+vPZxd0iMDBQp0+f1unTp81thw4d0oULF8yf6esp7Oe4UaNGSkhIuOa/mxwcHLR3717l5eVp+vTpeuihh1SjRg2dOXMm3zi+vr567bXX9M033+jNN9/UwoULzXVJYh8DUGAEAZD0191m69Wrp6VLl5oP+h999FHt27dPR48eNR/QDB8+XD/88IP69++vuLg4HTt2TCtXrlT//v0l/fUEgtmzZysuLk4nT57UJ598ory8PAUEBEiS/Pz8tGvXLiUlJemPP/7gmx/ctQYMGKB169Zp2rRpOnbsmN5//32Lu6VfFRwcrKVLl5r3kZIlSyowMFDLli2zCAKef/55lS5dWp06ddK2bduUmJiomJgYDRgwQP/73/+UmJiokSNHaseOHTp58qQ2bNigY8eOKTAwUNJf+05iYqLi4uL0xx9/KDMz885sCNz3du3apUmTJmnPnj06deqUvvnmG509e1aBgYEaOnSopkyZomXLlikhIUEjRoxQXFycBg4cKEmqVq2afH19FRERoWPHjmnNmjWaPn26xfh+fn5KT0/Xpk2b9Mcff3C5GGymdevWqlu3rp5//nnt27dPP/74o1588UUFBQWpSZMmN5y3sP9+GTNmjD755BNFRkbq4MGDOnz4sP7zn//o7bfflvTXPpSdna05c+bol19+0aeffqoFCxZYjBEeHq7169crMTFR+/btU3R0tPlvQ+XKlWUymbR69WqdPXvW4mkDAHAjBAEwCwoKUm5urjkIKFmypGrVqiVvb2/zgXy9evW0ZcsWHT16VI888ogaNmyoMWPGqHz58pL++ob0m2++UcuWLRUYGKgFCxboiy++UO3atSVJQ4YMkaOjo2rVqqUyZcro1KlTNllX4N889NBDWrhwoWbNmqX69etrw4YN5n+4/d0/9xvpr3Dgn23FihXT1q1bValSJfM9OHr16qWMjAx5eHioWLFiOnLkiJ566inVqFFDr7zyivr166dXX31VkvTUU0+pTZs2CgkJUZkyZfTFF1/c7k0AO+Hh4aGtW7eqXbt2qlGjht5++21Nnz5dbdu21YABAzR48GC9+eabqlu3rtatW6dVq1aZL3kpWrSovvjiCx05ckT16tXTlClTNGHCBIvxmzdvrtdee01PP/20ypQpo6lTp9piNQGZTCatXLlSJUqU0KOPPqrWrVurSpUqWrZs2b/OW9h/v4SGhmr16tXasGGDmjZtqoceekjvvfeeKleuLOmve3TMmDFDU6ZMUZ06dbR06VJNnjzZYozc3Fz169dPgYGBatOmjWrUqKF58+ZJkipUqKDIyEiNGDFC5cqVM38xAwD/xmT8/cI+AAAAAABwX+OMAAAAAAAA7AhBAAAAAAAAdoQgAAAAAAAAO0IQAAAAAACAHSEIAAAAAADAjhAEAAAAAABgRwgCAAAAAACwIwQBAAAAAADYEYIAAMB9JSoqSl5eXrYuAwAA4K5FEAAAuGPCwsJkMplkMplUtGhR+fv7a9iwYcrIyLDaMp5++mkdPXrUauMBAADcb4rYugAAgH1p06aNFi9erOzsbO3du1c9e/aUyWTSlClTrDK+q6urXF1drTIWAADA/YgzAgAAd5Szs7O8vb3l6+urzp07q3Xr1vr+++8lSXl5eZo8ebL8/f3l6uqq+vXra/ny5Rbzr1q1StWrV5eLi4tCQkK0ZMkSmUwmXbhwQdK1Lw2YP3++qlatKicnJwUEBOjTTz+1mG4ymbRo0SI9+eSTKlasmKpXr65Vq1bdtm0AAABgSwQBAACbOXDggH744Qc5OTlJkiZPnqxPPvlECxYs0MGDBzVo0CC98MIL2rJliyQpMTFRXbt2VefOnRUfH69XX31Vb7311g2X8e2332rgwIF68803deDAAb366qt66aWXFB0dbdEvMjJS3bt3188//6x27drp+eef17lz527PigMAANiQyTAMw9ZFAADsQ1hYmD777DO5uLgoJydHmZmZcnBw0JdffqkOHTqoZMmS2rhxo5o1a2aep3fv3rp8+bI+//xzjRgxQmvWrNH+/fvN099++21NnDhR58+fl5eXl6KiohQeHm4+Q6BFixaqXbu2PvzwQ/M83bt316VLl7RmzRpJf50R8Pbbb2v8+PGSpEuXLql48eJau3at2rRpcwe2DAAAwJ3DPQIAAHdUSEiI5s+fr0uXLum9995TkSJF9NRTT+ngwYO6fPmyHnvsMYv+WVlZatiwoSQpISFBTZs2tZj+wAMP3HB5hw8f1iuvvGLR1qJFC82aNcuirV69euaf3dzc5OHhoZSUlAKvHwAAwN2OIAAAcEe5ubmpWrVqkqSPP/5Y9evX10cffaQ6depIktasWaMKFSpYzOPs7Hzb6ypatKjFe5PJpLy8vNu+XAAAgDuNIAAAYDMODg4aNWqUBg8erKNHj8rZ2VmnTp1SUFDQNfsHBATou+++s2jbvXv3DZcRGBio2NhY9ezZ09wWGxurWrVq3foKAAAA3IMIAgAANtWtWzcNHTpUH3zwgYYMGaJBgwYpLy9PDz/8sFJTUxUbGysPDw/17NlTr776qmbMmKHhw4erV69eiouLU1RUlKS/vsG/lqFDh6p79+5q2LChWrdurf/+97/65ptvtHHjxju4lgAAAHcPggAAgE0VKVJE/fv319SpU5WYmKgyZcpo8uTJ+uWXX+Tl5aVGjRpp1KhRkiR/f38tX75cb775pmbNmqVmzZrprbfe0uuvv37dywc6d+6sWbNmadq0aRo4cKD8/f21ePFiBQcH38G1BAAAuHvw1AAAwD1t4sSJWrBggU6fPm3rUgAAAO4JnBEAALinzJs3T02bNlWpUqUUGxurd999V/3797d1WQAAAPcMggAAwD3l2LFjmjBhgs6dO6dKlSrpzTff1MiRI21dFgAAwD2DSwMAAAAAALAjDrYuAAAAAAAA3DkEAQAAAAAA2BGCAAAAAAAA7AhBAAAAAAAAdoQgAAAAAAAAO0IQAAAAAACAHSEIAAAAAADAjhAEAAAAAABgR/4PnAWgJEyzUGAAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure(figsize=(10,6))\n", + "sns.barplot(x='Region', y='Count', hue='Disaster Type', data=disaster_map, errorbar=None, palette='muted')\n", + "plt.title('Count of Each Disaster by Region')\n", + "plt.ylabel('Count')\n", + "plt.xlabel('Region')\n", + "plt.legend(title='Disaster Type', bbox_to_anchor=(1.05, 1), loc='upper left')\n", + "plt.show()" + ] }, { "cell_type": "code", diff --git a/python/requirements.txt b/python/requirements.txt index 6cff539..6bdc9f6 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -4,4 +4,6 @@ numpy==1.26.0 pandas==1.3.5 -matplotlib==3.6.0 \ No newline at end of file +matplotlib==3.6.0 + +seaborn==0.13.0 \ No newline at end of file diff --git a/python/simple_data_tool.py b/python/simple_data_tool.py index b712141..09cab86 100644 --- a/python/simple_data_tool.py +++ b/python/simple_data_tool.py @@ -324,8 +324,19 @@ def get_num_disasters_declared_after_end_date(self): int: number of disasters where the declared date is after the end date """ disasters = self.get_disaster_data() + # Initialize a counter + disasters_declared = 0 - disasters_declared = sum(1 for disaster in disasters if datetime.strptime(disaster['declared_date'], '%Y-%m-%d').date() > datetime.strptime(disaster['end_date'], '%Y-%m-%d').date()) + # Loop through each disaster in the disasters list + for disaster in disasters: + # Convert string dates to datetime.date objects + declared_date = datetime.strptime(disaster['declared_date'], '%Y-%m-%d').date() + end_date = datetime.strptime(disaster['end_date'], '%Y-%m-%d').date() + + # Check if declared_date is greater than end_date and increment the counter if true + if declared_date > end_date: + disasters_declared += 1 + return disasters_declared @@ -341,16 +352,21 @@ def build_map_of_agents_to_total_claim_cost(self): Returns: dict: key is agent id, value is total cost of claims associated to the agent """ + # Assign dataset to variable claims = self.get_claim_data() + + # Initialize a dictionary to store the total cost of claims per agent agent_costs = {agent_id: 0 for agent_id in range(1, 101)} + # Loop through each claim for claim in claims: - agent_id = claim["agent_assigned_id"] - cost = claim["estimate_cost"] + agent_id = claim["agent_assigned_id"] # Get the agent id + cost = claim["estimate_cost"] # Get the cost of the claim + # Add the cost of this claim to the total cost for this agent agent_costs[agent_id] = round(agent_costs[agent_id] + cost, 2) - return(agent_costs) + return agent_costs def calculate_disaster_claim_density(self, disaster_id): From 298d3688b6a29d9207655d79c4450c1204f13f75 Mon Sep 17 00:00:00 2001 From: "Kyungchan Im (Chris)" Date: Sat, 14 Oct 2023 21:20:31 -0700 Subject: [PATCH 13/14] REST API and Feedback changes --- FEEDBACK.md | 13 ++-- python/application.py | 148 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 153 insertions(+), 8 deletions(-) diff --git a/FEEDBACK.md b/FEEDBACK.md index 010fecd..e2e610e 100644 --- a/FEEDBACK.md +++ b/FEEDBACK.md @@ -1,13 +1,14 @@ # Feedback -1. Your team: -2. Name of each individual participating: -3. How many unit tests were you able to pass? +1. Your team: GCU_CS +2. Name of each individual participating: Kyungchan Im and Gabriel Teixeira Lima Aracena +3. How many unit tests were you able to pass? 13 4. Document and describe any enhancements included to help the judges properly grade your submission. - - Example One - - Example Two - - Example Three + - We created a new function in the simple_data_tool.py file to map the regional disaster type. We saw the definitions of the region on top of the page that were not being utilized to we thought it would be a good idea to use it for something. + - We then created a jupyter notabook that enables the visualization of that data and implements some data analysis to our dataset. We initially thought about creating a neural network and implementing a deep learning program but the dataset is too small for that. + - We implemented a REST API in the application.py file that can be used to interact and access the data. We used it locally but one could use Postman to interact with it (that's what's more common in the industry) 5. Any feedback for the coding competition? Things you would like to see in future events? +We had a lot of fun completing it! I just think the project itself would be more descriptive, it took us the first hour just to understand what we were supposed to accomplish. This form can also be emailed to [codingcompetition@statefarm.com](mailto:codingcompetition@statefarm.com). Just make sure that you include a link to your GitHub pull requests. diff --git a/python/application.py b/python/application.py index bad82a2..544ad3b 100644 --- a/python/application.py +++ b/python/application.py @@ -1,4 +1,148 @@ -from simple_data_tool import SimpleDataTool +# This is the implementation of our simple API to interact with the JSON data files +# We enabled GET for every JSON. 1 to access all the data e.g: '/claims' (this can be used when displaying a list) +# and another one to see one sepcific data e.g: 'claim/claim_id' (this can be used when searching for a data and looking for details) +# We also created PUT and POST for disasters and claims, we thought it would make sense to enable the creation and update of those +# One could use '/disasters' as a POST to create a new disaster and '/disaster/disaster_id' as a PUT to update an existing one -dataset = SimpleDataTool() +from flask import Flask, jsonify, request +import json +app = Flask(__name__) + +# File paths +AGENTS_FILEPATH = 'data/sfcc_2023_agents.json' +CLAIM_HANDLERS_FILEPATH = 'data/sfcc_2023_claim_handlers.json' +CLAIMS_FILEPATH = 'data/sfcc_2023_claims.json' +DISASTERS_FILEPATH = 'data/sfcc_2023_disasters.json' + +# Utility function to load data from a given filepath +def load_data(filepath): + with open(filepath, 'r') as file: + return json.load(file) + +# Utility function to save data to a given filepath +def save_data(filepath, data): + with open(filepath, 'w') as file: + json.dump(data, file) + +@app.route('/claims', methods=['GET']) +def get_claims(): + return jsonify(load_data(CLAIMS_FILEPATH)) + +@app.route('/claim/', methods=['GET']) +def get_claim_by_id(claim_id): + claims = load_data(CLAIMS_FILEPATH) + claim = next((claim for claim in claims if claim["id"] == claim_id), None) + + if claim: + return jsonify(claim) + else: + return jsonify({"error": f"No claim found with ID {claim_id}"}), 404 + +@app.route('/claims', methods=['POST']) +def add_claim(): + claims = load_data(CLAIMS_FILEPATH) + new_claim = request.json + claims.append(new_claim) + save_data(CLAIMS_FILEPATH, claims) + return jsonify({"message": "New claim added successfully!"}) + +@app.route('/claims/', methods=['PUT']) +def update_claim(claim_id): + claims = load_data(CLAIMS_FILEPATH) + claim_to_update = next((claim for claim in claims if claim["id"] == claim_id), None) + + if claim_to_update: + updated_data = request.json + for key, value in updated_data.items(): + claim_to_update[key] = value + save_data(CLAIMS_FILEPATH, claims) + return jsonify({"message": f"Claim with ID {claim_id} updated successfully!"}) + else: + return jsonify({"error": f"No claim found with ID {claim_id}"}), 404 + +@app.route('/claim_handlers', methods=['GET']) +def get_claim_handlers(): + return jsonify(load_data(CLAIM_HANDLERS_FILEPATH)) + +@app.route('/claim_handler/', methods=['GET']) +def get_claim_handler_by_id(handler_id): + claim_handlers = load_data(CLAIM_HANDLERS_FILEPATH) + handler = next((handler for handler in claim_handlers if handler["id"] == handler_id), None) + + if handler: + return jsonify(handler) + else: + return jsonify({"error": f"No claim handler found with ID {handler_id}"}), 404 + +@app.route('/agents', methods=['GET']) +def get_agents(): + return jsonify(load_data(AGENTS_FILEPATH)) + +@app.route('/agent/', methods=['GET']) +def get_agent_by_id(agent_id): + agents = load_data(AGENTS_FILEPATH) + agent = next((agent for agent in agents if agent["id"] == agent_id), None) + + if agent: + return jsonify(agent) + else: + return jsonify({"error": f"No agent found with ID {agent_id}"}), 404 + +@app.route('/disasters', methods=['GET']) +def get_disasters(): + return jsonify(load_data(DISASTERS_FILEPATH)) + +@app.route('/disaster/', methods=['GET']) +def get_disaster_by_id(disaster_id): + # Load existing disasters + disasters = load_data(DISASTERS_FILEPATH) + + # Find the disaster with the specified ID + disaster = next((disaster for disaster in disasters if disaster["id"] == disaster_id), None) + + if disaster: + return jsonify(disaster) + else: + return jsonify({"error": f"No disaster found with ID {disaster_id}"}), 404 + +@app.route('/disasters', methods=['POST']) +def add_disaster(): + # Load existing disasters + disasters = load_data(DISASTERS_FILEPATH) + + # Retrieve the new disaster data from the request + new_disaster = request.json + + # Add the new disaster to the existing list + disasters.append(new_disaster) + + # Save the updated disasters list back to the file + save_data(DISASTERS_FILEPATH, disasters) + + return jsonify({"message": "New disaster added successfully!"}) + +@app.route('/disasters/', methods=['PUT']) +def update_disaster(disaster_id): + # Load existing disasters + disasters = load_data(DISASTERS_FILEPATH) + + # Find the disaster to update + disaster_to_update = next((disaster for disaster in disasters if disaster["id"] == disaster_id), None) + + if disaster_to_update: + # Update the found disaster with data from the request + updated_data = request.json + for key, value in updated_data.items(): + disaster_to_update[key] = value + + # Save the updated disasters list back to the file + save_data(DISASTERS_FILEPATH, disasters) + + return jsonify({"message": f"Disaster with ID {disaster_id} updated successfully!"}) + else: + return jsonify({"error": f"No disaster found with ID {disaster_id}"}), 404 + + +if __name__ == '__main__': + app.run(debug=True) From cf28d7acee1241bdf09f095adb407bf96d6f11d5 Mon Sep 17 00:00:00 2001 From: "Kyungchan Im (Chris)" Date: Sat, 14 Oct 2023 21:40:20 -0700 Subject: [PATCH 14/14] final submission for round 1 --- FEEDBACK.md | 2 + python/data_analysis.ipynb | 252 ++++++++++++++++++++++++++++++++++--- python/requirements.txt | 4 +- python/simple_data_tool.py | 48 ++++++- 4 files changed, 283 insertions(+), 23 deletions(-) diff --git a/FEEDBACK.md b/FEEDBACK.md index e2e610e..bbe32eb 100644 --- a/FEEDBACK.md +++ b/FEEDBACK.md @@ -5,9 +5,11 @@ 3. How many unit tests were you able to pass? 13 4. Document and describe any enhancements included to help the judges properly grade your submission. - We created a new function in the simple_data_tool.py file to map the regional disaster type. We saw the definitions of the region on top of the page that were not being utilized to we thought it would be a good idea to use it for something. + - We created a new function in the simple_data_tool.py file to map the regional total claim. We wanted to do further data analysis project for this competition, so we added extra function that we needed to calculate. - We then created a jupyter notabook that enables the visualization of that data and implements some data analysis to our dataset. We initially thought about creating a neural network and implementing a deep learning program but the dataset is too small for that. - We implemented a REST API in the application.py file that can be used to interact and access the data. We used it locally but one could use Postman to interact with it (that's what's more common in the industry) + 5. Any feedback for the coding competition? Things you would like to see in future events? We had a lot of fun completing it! I just think the project itself would be more descriptive, it took us the first hour just to understand what we were supposed to accomplish. diff --git a/python/data_analysis.ipynb b/python/data_analysis.ipynb index 83272d3..5900e95 100644 --- a/python/data_analysis.ipynb +++ b/python/data_analysis.ipynb @@ -19,7 +19,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 28, "metadata": {}, "outputs": [], "source": [ @@ -45,7 +45,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 29, "metadata": {}, "outputs": [], "source": [ @@ -79,7 +79,34 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "first_name 100\n", + "last_name 100\n", + "state 100\n", + "region 100\n", + "primary_language 100\n", + "secondary_language 59\n", + "years_active 100\n", + "dtype: int64" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "agents.count() # count the number of each columns and missing values." + ] + }, + { + "cell_type": "code", + "execution_count": 31, "metadata": {}, "outputs": [ { @@ -195,7 +222,7 @@ "5 Chinese 48 " ] }, - "execution_count": 18, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } @@ -206,7 +233,36 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "disaster_id 1000\n", + "status 1000\n", + "total_loss 1000\n", + "loss_of_life 1000\n", + "type 1000\n", + "severity_rating 1000\n", + "estimate_cost 1000\n", + "agent_assigned_id 1000\n", + "claim_handler_assigned_id 1000\n", + "dtype: int64" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "claims.count() # count the number of each columns and missing values." + ] + }, + { + "cell_type": "code", + "execution_count": 33, "metadata": {}, "outputs": [ { @@ -336,7 +392,7 @@ "5 979.81 86 103 " ] }, - "execution_count": 21, + "execution_count": 33, "metadata": {}, "output_type": "execute_result" } @@ -347,7 +403,29 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "first_name 156\n", + "last_name 156\n", + "dtype: int64" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "claim_handlers.count() # count the number of each columns and missing values." + ] + }, + { + "cell_type": "code", + "execution_count": 35, "metadata": {}, "outputs": [ { @@ -420,7 +498,7 @@ "5 Elsey Sreenan" ] }, - "execution_count": 22, + "execution_count": 35, "metadata": {}, "output_type": "execute_result" } @@ -431,7 +509,37 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "type 100\n", + "state 100\n", + "name 100\n", + "description 100\n", + "start_date 100\n", + "end_date 100\n", + "declared_date 100\n", + "lat 100\n", + "long 100\n", + "radius_miles 100\n", + "dtype: int64" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "disasters.count() # count the number of each columns and missing values." + ] + }, + { + "cell_type": "code", + "execution_count": 37, "metadata": {}, "outputs": [ { @@ -576,7 +684,7 @@ "5 2023-03-14 39.0663 -94.5674 155 " ] }, - "execution_count": 23, + "execution_count": 37, "metadata": {}, "output_type": "execute_result" } @@ -601,17 +709,34 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 38, + "metadata": {}, + "outputs": [], + "source": [ + "# Create a SimpleDataTool object for use calculation functions inside the class\n", + "data = SimpleDataTool()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Find Regional Disaster Counts #####" + ] + }, + { + "cell_type": "code", + "execution_count": 39, "metadata": {}, "outputs": [], "source": [ - "data = SimpleDataTool()\n", + "# Get the total number of disaster occurrend in the simulation\n", "regional_disaster_map = data.get_regional_disaster_map()" ] }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 40, "metadata": {}, "outputs": [ { @@ -629,22 +754,22 @@ ], "source": [ "# Initialize lists to store the data\n", - "regions = []\n", + "disaster_regions = []\n", "disaster_types = []\n", - "counts = []\n", + "disaster_counts = []\n", "\n", "# Populate the lists with data\n", "for region, disasters in regional_disaster_map.items():\n", " for disaster_type, count in disasters.items():\n", - " regions.append(region)\n", + " disaster_regions.append(region)\n", " disaster_types.append(disaster_type)\n", - " counts.append(count)\n", + " disaster_counts.append(count)\n", "\n", "# Create a DataFrame\n", "disaster_map = pd.DataFrame({\n", - " 'Region': regions,\n", + " 'Region': disaster_regions,\n", " 'Disaster Type': disaster_types,\n", - " 'Count': counts\n", + " 'Count': disaster_counts\n", "})\n", "\n", "# Print the sum of the disaster regionally.\n", @@ -653,7 +778,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 41, "metadata": {}, "outputs": [ { @@ -677,6 +802,93 @@ "plt.show()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Find the number of claims by region #####" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [], + "source": [ + "regional_claim_map = data.get_total_claims_per_regional_disaster()" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Count\n", + "Region \n", + "south 328\n", + "midwest 240\n", + "west 224\n", + "northeast 208\n" + ] + } + ], + "source": [ + "# Initialize lists to store the data\n", + "claim_map_regions = []\n", + "claim_map_state = []\n", + "claim_map_counts = []\n", + "\n", + "# Populate the lists with data\n", + "for region, state in regional_claim_map.items():\n", + " for state_name, count in state.items():\n", + " claim_map_regions.append(region)\n", + " claim_map_state.append(state_name)\n", + " claim_map_counts.append(count)\n", + "\n", + "# Create a DataFrame\n", + "claim_map = pd.DataFrame({\n", + " 'Region': claim_map_regions,\n", + " 'Disaster Type': claim_map_state,\n", + " 'Count': claim_map_counts\n", + "})\n", + "\n", + "\n", + "# Print the sum of the disaster regionally.\n", + "total_claim_map = claim_map.groupby(['Region']).sum().sort_values(by='Count', ascending=False)\n", + "print(total_claim_map)" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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9OkO8202m/7zn//o2WefVYECBfTggw/qxx9/vGPHAADgPAQpAMA9b/v27Vq7dq3c3d0lSdHR0friiy80efJk7dixQ3369NHLL7+sFStWSJISEhLUunVrtWrVSlu2bFG3bt30zjvv3HQbc+bM0Ztvvql+/fpp+/bt6tatm1599VUtX77cod+wYcPUpk0bbd26VS1atFC7du10+vTpO7PjAACnsRljjLOLAADAio4dO+rLL7+Up6en0tPTlZqaKhcXF3399dd68skn5efnpyVLlig0NNS+TOfOnXXhwgXNnDlT//jHPzRv3jxt27bNPn/QoEF6//33debMGfn6+iomJka9e/e2X6Fq0KCBqlSpoilTptiXadOmjc6fP6958+ZJ+uuK1KBBg/Tuu+9Kks6fP6+CBQtq/vz5atas2V04MgCAu4VnpAAA96RGjRpp0qRJOn/+vMaMGaN8+fIpMjJSO3bs0IULF9SkSROH/mlpaapZs6Ykac+ePapTp47D/EceeeSm29u1a5e6du3q0NagQQONGzfOoa169er2f3t5ecnb21snT560vH8AgNyNIAUAuCd5eXmpQoUKkqTPP/9cNWrU0GeffaaqVatKkubNm6cHHnjAYRkPD487Xpebm5vDtM1mU2Zm5h3fLgDg7iJIAQDueS4uLvrnP/+pvn37au/evfLw8NDhw4fVsGHD6/avVKmSfv75Z4e2DRs23HQbwcHBWrNmjTp06GBvW7NmjUJCQm5/BwAA9xyCFADgvvD888/r7bff1ieffKK33npLffr0UWZmph599FElJydrzZo18vb2VocOHdStWzeNHj1aAwYMUKdOnbR582bFxMRI+usK0vW8/fbbatOmjWrWrKnw8HD99NNP+u6777RkyZK7uJcAgNyCIAUAuC/ky5dPPXr00MiRI5WQkKBixYopOjpav/32m3x9ffXwww/rn//8pySpbNmy+uabb9SvXz+NGzdOoaGheuedd9S9e/cb3v7XqlUrjRs3Th9++KHefPNNlS1bVlOnTlVYWNhd3EsAQG7BqH0AAEh6//33NXnyZB05csTZpQAA7gFckQIA5EkTJ05UnTp1VKRIEa1Zs0YffPCBevTo4eyyAAD3CIIUACBP2rdvn9577z2dPn1apUqVUr9+/TRw4EBnlwUAuEdwax8AAAAAWOTi7AIAAAAA4F5DkAIAAAAAiwhSAAAAAGARQQoAAAAALCJIAQAAAIBFBCkAAAAAsIggBQAAAAAWEaQAAAAAwKL/B1QrrzNcCChPAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure(figsize=(10,6))\n", + "sns.barplot(x='Region', y='Count', data=total_claim_map, legend=False, palette='muted', hue='Region')\n", + "\n", + "plt.title('Total Count of Claims by Region')\n", + "plt.ylabel('Count')\n", + "plt.xlabel('Region')\n", + "\n", + "plt.show()" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/python/requirements.txt b/python/requirements.txt index 6bdc9f6..b0afa50 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -6,4 +6,6 @@ pandas==1.3.5 matplotlib==3.6.0 -seaborn==0.13.0 \ No newline at end of file +seaborn==0.13.0 + +flask==3.0.0 \ No newline at end of file diff --git a/python/simple_data_tool.py b/python/simple_data_tool.py index 09cab86..faa7074 100644 --- a/python/simple_data_tool.py +++ b/python/simple_data_tool.py @@ -448,9 +448,13 @@ def get_top_three_months_with_highest_num_of_claims_desc(self): # ------------------------------------ Nice to Have! ------------------------------------ # - # This funcftions maps out each regional disaster by type and counts the number of each type - def get_regional_disaster_map(self): + # This functions maps out each regional disaster by type and counts the number of each type + def get_regional_disaster_map(self):\ + + # Assign dataset to variable disasters = self.get_disaster_data() + + # Initialize a dictionary to store disaster types per region regional_disaster_map = { 'west': {}, 'midwest': {}, @@ -465,6 +469,7 @@ def get_region(state): return region return None + # Accumulate disaster types per region for disaster in disasters: region = get_region(disaster['state']) if region: @@ -477,3 +482,42 @@ def get_region(state): return regional_disaster_map + # This functions maps out each regional claims by type and counts the total claims of each type + def get_total_claims_per_regional_disaster(self): + + # Assign dataset to variable + claims = self.get_claim_data() + disasters = self.get_disaster_data() + + # Map disasters by id for quick lookup + disaster_map = {disaster['id']: disaster for disaster in disasters} + + # Initialize the result data structure + regional_claims = {region: {} for region in self.REGION_MAP.keys()} + + # Helper function to get the region of a state + def get_region(state): + for region, states in self.REGION_MAP.items(): + if state in states: + return region + return None # or 'unknown' + + # Iterate through each claim, map to disaster, and update counts + for claim in claims: + # Map claim to disaster using disaster_id + disaster = disaster_map.get(claim['disaster_id']) + + # Ensure disaster exists + if disaster: + state = disaster['state'] + region = get_region(state) + + # Ensure region is found and update the counts + if region is not None: + if state in regional_claims[region]: + regional_claims[region][state] += 1 + else: + regional_claims[region][state] = 1 + + # Return the resulting data structure + return regional_claims