diff --git a/FEEDBACK.md b/FEEDBACK.md index 010fecd..d5666a2 100644 --- a/FEEDBACK.md +++ b/FEEDBACK.md @@ -1,13 +1,31 @@ # Feedback -1. Your team: -2. Name of each individual participating: -3. How many unit tests were you able to pass? -4. Document and describe any enhancements included to help the judges properly grade your submission. - - Example One - - Example Two - - Example Three +1. Your team: **OutOfThisWorld** -5. Any feedback for the coding competition? Things you would like to see in future events? + - Pull Request: [Team OutOfThisWorld](https://github.com/StateFarmInsCodingCompetition/2023-StateFarm-CodingCompetition/pull/33) -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. +2. Name of each individual participating: + + - [x] Arnold Bhebhe (GitHub: SirArnoldB) + - [x] Lucky Chitudu (GitHub: luckychitudu) + - [x] Onwell Mazoredzw (GitHub: Onwell03) + +3. How many unit tests were you able to pass? **All 13 unit tests were passed.** + +4. Document and describe any enhancements included to help the judges properly grade your submission. + + - Code was refactored to make it more readable and maintainable. + - Added flask app with endpoints to handle requests from a potential frontend application. + + - Key Points to note before running our code: + + - Packages not in requirements.txt (but are required to run the code): + - flask: `pip install flask` + - flask_cors: `pip install flask_cors` + - pandas: `pip install pandas` + +5. Any feedback for the coding competition? Things you would like to see in future events? + + - The competition was well organized and the problem statement was clear and concise. + - The competition was a great learning experience and we look forward to participating in future events. + - Test cases were very helpful in guiding us to the correct solution. But, we had issues with one test case that was not clear. We would like to see more clarity in the test cases in future events. diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..d8b2106 --- /dev/null +++ b/python/server.py @@ -0,0 +1,77 @@ +from flask import Flask, jsonify, request +from flask_cors import CORS +from simple_data_tool import SimpleDataTool + +# Create Flask App +app = Flask(__name__) + +# Enable CORS for the Flask app +CORS(app, supports_credentials=True) + +# Create Simple Data Tool +data_tool = SimpleDataTool() + +# Test Set One +@app.route('/disasters/count', methods=['GET']) +def get_disaster_count_by_state(): + state = request.args.get('state') + count = data_tool.get_disaster_count_by_state(state) + return jsonify({'count': count}) + +# Test Set Two +@app.route('/disasters/total-claim-cost', methods=['GET']) +def get_total_claim_cost_for_disaster(): + disaster_id = request.args.get('disaster_id') + total_claim_cost = data_tool.get_total_claim_cost_for_disaster(disaster_id) + return jsonify({'total_claim_cost': total_claim_cost}) + +@app.route('/claims/average-claim-cost', methods=['GET']) +def get_average_claim_cost_for_claim_handler(): + claim_handler_id = request.args.get('claim_handler_id') + average_claim_cost = data_tool.get_average_claim_cost_for_claim_handler(claim_handler_id) + return jsonify({'average_claim_cost': average_claim_cost}) + +@app.route('/disasters/most', methods=['GET']) +def get_state_with_most_disasters(): + state = data_tool.get_state_with_most_disasters() + return jsonify({'state': state}) + +@app.route('/disasters/least', methods=['GET']) +def get_state_with_least_disasters(): + state = data_tool.get_state_with_least_disasters() + return jsonify({'state': state}) + +@app.route('/agents/most-spoken-language', methods=['GET']) +def get_most_spoken_agent_language_by_state(): + state = request.args.get('state') + language = data_tool.get_most_spoken_agent_language_by_state(state) + return jsonify({'language': language}) + +@app.route('/claims/open-count', methods=['GET']) +def get_num_of_open_claims_for_agent_and_severity(): + agent_id = request.args.get('agent_id') + min_severity_rating = request.args.get('min_severity_rating') + num_of_open_claims = data_tool.get_num_of_open_claims_for_agent_and_severity(agent_id, min_severity_rating) + return jsonify({'num_of_open_claims': num_of_open_claims}) + +@app.route('/disasters/declared-after-end-date-count', methods=['GET']) +def get_num_disasters_declared_after_end_date(): + count = data_tool.get_num_disasters_declared_after_end_date() + return jsonify({'count': count}) + +@app.route('/agents/total-claim-cost', methods=['GET']) +def build_map_of_agents_to_total_claim_cost(): + agent_total_claim_cost = data_tool.build_map_of_agents_to_total_claim_cost() + return jsonify(agent_total_claim_cost) + +@app.route('/disasters/claim-density', methods=['GET']) +def calculate_disaster_claim_density(): + disaster_id = request.args.get('disaster_id') + claim_density = data_tool.calculate_disaster_claim_density(disaster_id) + return jsonify({'claim_density': claim_density}) + +# Test Set Four +@app.route('/claims/top-three-months', methods=['GET']) +def get_top_three_months_with_highest_num_of_claims_desc(): + top_three_months = data_tool.get_top_three_months_with_highest_num_of_claims_desc() + return jsonify({'top_three_months': top_three_months}) diff --git a/python/simple_data_tool.py b/python/simple_data_tool.py index f57ad2f..1c17915 100644 --- a/python/simple_data_tool.py +++ b/python/simple_data_tool.py @@ -1,7 +1,9 @@ import json import math +import pandas as pd from statistics import mean +from datetime import datetime as dt @@ -59,7 +61,12 @@ def get_num_closed_claims(self): Returns: int: number of closed claims """ - pass + claims = self.get_claim_data() + count = 0 + for claim in claims: + if claim['status'] == 'Closed': + count += 1 + return count def get_num_claims_for_claim_handler_id(self, claim_handler_id): """Calculates the number of claims assigned to a specific claim handler @@ -70,7 +77,12 @@ 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() + count = 0 + for claim in claims: + if claim['claim_handler_assigned_id'] == claim_handler_id: + count += 1 + return count def get_num_disasters_for_state(self, state): """Calculates the number of disasters for a specific state @@ -82,7 +94,13 @@ def get_num_disasters_for_state(self, state): Returns: int: number of disasters for state """ - pass + + disasters = self.get_disaster_data() + count = 0 + for disaster in disasters: + if disaster['state'] == state: + count += 1 + return count # endregion @@ -99,7 +117,16 @@ def get_total_claim_cost_for_disaster(self, disaster_id): returns None if no claims are found """ - pass + claims = self.get_claim_data() + total = 0 + for row in claims: + if row['disaster_id'] == disaster_id: + total += row['estimate_cost'] + + if total == 0: + return None + else: + return round(total, 2) 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 @@ -112,7 +139,19 @@ def get_average_claim_cost_for_claim_handler(self, claim_handler_id): or None if no claims are found """ - pass + claims = self.get_claim_data() + count = 0 + total_cost = 0 + for claim in claims: + if claim["claim_handler_assigned_id"] == claim_handler_id: + count += 1 + total_cost += claim["estimate_cost"] + if count == 0: + return None + else: + average = total_cost/count + + return round(average, 2) def get_state_with_most_disasters(self): """Returns the name of the state with the most disasters based on disaster data @@ -127,7 +166,27 @@ def get_state_with_most_disasters(self): Returns: string: single name of state """ - pass + + disatsers = self.get_disaster_data() + + # Keeps track of the state and the number of disasters + state_disaster_count = {} + + for disaster in disatsers: + state = disaster['state'] + state_disaster_count[state] = state_disaster_count.get(state, 0) + 1 + + # Get maximum disaster count + max_value = max(state_disaster_count.values()) + + # Get list of states with max value + states_with_max_disasters = [key for key, value in state_disaster_count.items() if value == max_value] + + # Sort list of states + states_with_max_disasters.sort() + + # Return first state + return states_with_max_disasters[0] def get_state_with_least_disasters(self): """Returns the name of the state with the least disasters based on disaster data @@ -142,7 +201,27 @@ def get_state_with_least_disasters(self): Returns: string: single name of state """ - pass + + disatsers = self.get_disaster_data() + + # Keeps track of the state and the number of disasters + state_disaster_count = {} + + for disaster in disatsers: + state = disaster['state'] + state_disaster_count[state] = state_disaster_count.get(state, 0) + 1 + + # Get minimum disaster count + min_value = min(state_disaster_count.values()) + + # Get list of states with min value + states_with_min_disasters = [key for key, value in state_disaster_count.items() if value == min_value] + + # Sort list of states + states_with_min_disasters.sort() + + # Return first state + return states_with_min_disasters[0] 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 @@ -154,7 +233,29 @@ def get_most_spoken_agent_language_by_state(self, state): string: name of language or empty string if state doesn't exist """ - pass + + agents = self.get_agent_data() + + # Keeps track of the language and the number of agents + language_agent_count = {} + + for agent in agents: + if agent['state'] == state: + language = agent['secondary_language'] + print(language) + language_agent_count[language] = language_agent_count.get(language, 0) + 1 + + if language_agent_count: + # Get maximum agent count + max_value = max(language_agent_count.values()) + + # Get list of languages with max value + languages_with_max_agents = [key for key, value in language_agent_count.items() if value == max_value] + + # Sort list of languages + languages_with_max_agents.sort() + + return languages_with_max_agents[0] if language_agent_count else '' 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 @@ -171,7 +272,59 @@ def get_num_of_open_claims_for_agent_and_severity(self, agent_id, min_severity_r None if agent does not exist, or agent has no claims (open or not) """ - pass + def check_agent_exists(agent_id, agents): + """Checks if agent exists + + Args: + agent_id (int): ID of the agent + agents (list): list of agents + + Returns: + bool: True if agent exists, False otherwise + """ + for agent in agents: + if agent['id'] == agent_id: + return True + return False + + def claim_has_min_severity_rating(claim, min_severity_rating): + """Checks if claim has minimum severity rating + + Args: + claim (dict): claim data + min_severity_rating (int): minimum claim severity rating + + Returns: + bool: True if claim has minimum severity rating, False otherwise + """ + return claim['severity_rating'] >= min_severity_rating + + def claim_is_open(claim): + """Checks if claim is open + + Args: + claim (dict): claim data + + Returns: + bool: True if claim is open, False otherwise + """ + return claim['status'] != 'Closed' + + agents = self.get_agent_data() + claims = self.get_claim_data() + + # Check if agent exists + if not check_agent_exists(agent_id, agents) or (min_severity_rating < 1 or min_severity_rating > 10): + return -1 + + # Keeps track of the number of open claims with at least the minimum severity rating + count = 0 + + for claim in claims: + if claim['agent_assigned_id'] == agent_id and claim_has_min_severity_rating(claim, min_severity_rating) and claim_is_open(claim): + count += 1 + + return count if count > 0 else None # endregion @@ -184,7 +337,14 @@ def get_num_disasters_declared_after_end_date(self): int: number of disasters where the declared date is after the end date """ - pass + disasters = self.get_disaster_data() + count = 0 + for disaster in disasters: + end_date = dt.strptime(disaster["end_date"],"%Y-%m-%d").date() + declared_date = dt.strptime(disaster["declared_date"], "%Y-%m-%d").date() + if declared_date > end_date: + count += 1 + return count def build_map_of_agents_to_total_claim_cost(self): """Builds a map of agent and their total claim cost @@ -198,7 +358,26 @@ def build_map_of_agents_to_total_claim_cost(self): dict: key is agent id, value is total cost of claims associated to the agent """ - pass + # Create dataframes for agents and claims data to make it easier to work with the data + agents_df = pd.DataFrame(self.get_agent_data()) + claims_df = pd.DataFrame(self.get_claim_data()) + + # Create a dataframe with the agent id and the total claim cost + agents_total_claim_cost = pd.DataFrame(agents_df['id']) + agents_total_claim_cost['total_claim_cost'] = 0 + + # Calculate the total claim cost for each agent + for agent_id in agents_total_claim_cost['id']: + # Get the total claim cost for the agent + # by summing the estimate cost of all claims associated to the agent + total_claim_cost = claims_df[claims_df['agent_assigned_id'] == agent_id]['estimate_cost'].sum() + # Set the total claim cost for the agent in the dataframe + agents_total_claim_cost.loc[agents_total_claim_cost['id'] == agent_id, 'total_claim_cost'] = total_claim_cost + + # Round the total claim cost to the nearest hundredths + agents_total_claim_cost['total_claim_cost'] = agents_total_claim_cost['total_claim_cost'].round(2) + + return agents_total_claim_cost.set_index('id').to_dict()['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 @@ -214,7 +393,28 @@ def calculate_disaster_claim_density(self, disaster_id): float: density of claims to disaster area, rounded to the nearest thousandths place None if disaster does not exist """ - pass + claims = self.get_claim_data() + disasters = self.get_disaster_data() + + num_of_claims = 0 + for claim in claims: + if claim["disaster_id"] == disaster_id: + num_of_claims += 1 + + for disaster in disasters: + if disaster["id"] == disaster_id: + miles_radius = disaster["radius_miles"] + + if num_of_claims == 0: + return None + else: + impact_area = math.pi * math.pow(miles_radius, 2) + claim_density = num_of_claims/impact_area + return round(claim_density, 5) + + + + # endregion @@ -232,6 +432,27 @@ def get_top_three_months_with_highest_num_of_claims_desc(self): list: three strings of month and year, descending order of highest claims """ - pass + claims_df = pd.DataFrame(self.get_claim_data()) + open_claims_df = claims_df[claims_df['status'] != 'Closed'] + + disasters_df = pd.DataFrame(self.get_disaster_data()) + + merged_df = pd.merge(open_claims_df, disasters_df, left_on='disaster_id', right_on='id', how='inner') + + merged_df['declared_date'] = pd.to_datetime(merged_df['declared_date']) + merged_df['month_year'] = merged_df['declared_date'].dt.strftime('%B %Y') + + grouped_df = merged_df.groupby('month_year')['estimate_cost'].sum() + + top_three_months = grouped_df.nlargest(3) + + print(top_three_months) + + return top_three_months.index.tolist() + + + + + # endregion