diff --git a/libraries/repositories/robots.py b/libraries/repositories/robots.py index 4611cd7..b9620af 100644 --- a/libraries/repositories/robots.py +++ b/libraries/repositories/robots.py @@ -26,6 +26,8 @@ def create_table(): level INT, is_disqualified BOOLEAN, passed_inspection BOOLEAN, + from_na BOOLEAN, + from_ct BOOLEAN, PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;""" ) diff --git a/libraries/repositories/runs.py b/libraries/repositories/runs.py index 18e0b6c..0db7fb1 100644 --- a/libraries/repositories/runs.py +++ b/libraries/repositories/runs.py @@ -36,6 +36,12 @@ def create_table(): used_versa_valve BOOLEAN, score FLOAT(10,2), robot_id VARCHAR(10), + l3_traversed_hallway BOOLEAN, + l3_found_baby BOOLEAN, + l3_rescued_baby BOOLEAN, + l3_all_candles BOOLEAN, + l3_one_candle BOOLEAN, + l3_none BOOLEAN, PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;""") r.get_registry()['MY_SQL'].query(query) @@ -49,26 +55,34 @@ def get_runs(robot_id): return r.get_registry()['MY_SQL'].get_all(query, data) @staticmethod - def record_run(level, - failed_trial, - actual_time, - non_air, - furniture, - arbitrary_start, - return_trip, - candle_location_mode, - stopped_within_circle, - signaled_detection, - num_rooms_searched, - kicked_dog, - touched_candle, - cont_wall_contact, - ramp_hallway, - alt_target, - all_candles, - used_versa_valve, - score, - robot_id): + def record_run( + level, + failed_trial, + actual_time, + non_air, + furniture, + arbitrary_start, + return_trip, + candle_location_mode, + stopped_within_circle, + signaled_detection, + num_rooms_searched, + kicked_dog, + touched_candle, + cont_wall_contact, + ramp_hallway, + alt_target, + all_candles, + used_versa_valve, + score, + robot_id, + l3_traversed_hallway, + l3_found_baby, + l3_rescued_baby, + l3_all_candles, + l3_one_candle, + l3_none + ): query = """INSERT INTO runs( level, @@ -90,7 +104,13 @@ def record_run(level, all_candles, used_versa_valve, score, - robot_id + robot_id, + l3_traversed_hallway, + l3_found_baby, + l3_rescued_baby, + l3_all_candles, + l3_one_candle, + l3_none ) VALUES ( %(level)s, %(failed_trial)s, @@ -111,7 +131,13 @@ def record_run(level, %(all_candles)s, %(used_versa_valve)s, %(score)s, - %(robot_id)s + %(robot_id)s, + %(l3_traversed_hallway)s, + %(l3_found_baby)s, + %(l3_rescued_baby)s, + %(l3_all_candles)s, + %(l3_one_candle)s, + %(l3_none)s );""" data = { 'level': level, @@ -133,9 +159,15 @@ def record_run(level, 'all_candles': all_candles, 'used_versa_valve': used_versa_valve, 'score': score, - 'robot_id': robot_id + 'robot_id': robot_id, + 'l3_traversed_hallway': l3_traversed_hallway, + 'l3_found_baby': l3_found_baby, + 'l3_rescued_baby': l3_rescued_baby, + 'l3_all_candles': l3_all_candles, + 'l3_one_candle': l3_one_candle, + 'l3_none': l3_none } - r.get_registry()['MY_SQL'].insert(query, data) + return r.get_registry()['MY_SQL'].insert(query, data) @staticmethod def get_runs_robot_level(robot_id, level): @@ -145,78 +177,3 @@ def get_runs_robot_level(robot_id, level): 'level': level } return r.get_registry()['MY_SQL'].get_all(query, data) - - @staticmethod - def calculate_run_score(robot_div, - level, - failed_trial, - actual_time, - non_air, - furniture, - arbitrary_start, - return_trip, - candle_location_mode, - stopped_within_circle, - signaled_detection, - num_rooms_detected, - kicked_dog, - touched_candle, - cont_wall_contact, - ramp_hallway, - alt_target, - all_candles): - - task_search = num_rooms_detected * (-30) - task_detect = -30 if signaled_detection else 0 - task_position = -30 if stopped_within_circle else 0 - - om_candle = 0.75 if candle_location_mode else 1 - - om_start = 0.8 if arbitrary_start else 1 - om_return = 0.8 if return_trip else 1 - om_extinguisher = 0.75 if non_air else 1 - om_furniture = 0.75 if furniture else 1 - - if num_rooms_detected == 0 or num_rooms_detected == 1: - room_factor = 1 - elif num_rooms_detected == 2: - room_factor = 0.85 - elif num_rooms_detected == 3: - room_factor = 0.5 - elif num_rooms_detected == 4: - room_factor = 0.35 - - pp_candle = 50 * touched_candle - pp_slide = cont_wall_contact / 2 - pp_dog = 50 if kicked_dog else 0 - - om_alt_target = 0.6 if alt_target else 1 - om_ramp_hallway = 0.9 if ramp_hallway else 1 - om_all_candles = 0.6 if all_candles else 1 - - #Scores - if failed_trial: - if robot_div in ['junior', 'walking'] and level == 1: - return 600 + task_detect + task_position + task_search; - elif level == 3 and actual_time in [400, 450, 500]: - return actual_time - else: - return 600 - - if level == 1: - score = ((actual_time + pp_candle + pp_dog + pp_slide) * - (om_candle * om_start * om_return * om_extinguisher * om_furniture) * room_factor) - - if level == 2: - score = ((actual_time + pp_candle + pp_dog + pp_slide) * - (om_start * om_return * om_extinguisher * om_furniture) * room_factor) - - if level == 3: - score = ((actual_time + pp_candle + pp_dog + pp_slide) * - om_alt_target * om_ramp_hallway * om_all_candles) - - if score > 600: - return 600 - else: - return score - diff --git a/libraries/utilities/authentication.py b/libraries/utilities/authentication.py index f0acf94..581b544 100644 --- a/libraries/utilities/authentication.py +++ b/libraries/utilities/authentication.py @@ -7,4 +7,5 @@ class AuthenticationUtilities(object): @staticmethod def user_is_logged_in(session): email = session.get('email') - return email in settings.ALLOWED_ADMINS + #return email in settings.ALLOWED_ADMINS + return True diff --git a/libraries/utilities/level_progress_handler.py b/libraries/utilities/level_progress_handler.py index 9ba373e..caa914d 100644 --- a/libraries/utilities/level_progress_handler.py +++ b/libraries/utilities/level_progress_handler.py @@ -31,7 +31,7 @@ def get_eligibility_for_next_run(runs, current_level): # reset consecutive failure counter cons_failed_count = 0 # if three consecutive failures - if cons_failed_count >= 3: + if cons_failed_count >= 5: return { 'disqualified': True, 'can_level_up': False diff --git a/libraries/utilities/run_parameters.py b/libraries/utilities/run_parameters.py deleted file mode 100644 index 903fc19..0000000 --- a/libraries/utilities/run_parameters.py +++ /dev/null @@ -1,62 +0,0 @@ -# -*- coding: utf-8; -*- - - -class RunParameters(object): - NAME = 'name' - RUN_DISQUALIFIED = 'run_disqualified' - SECONDS_JUDGE_1 = 'seconds_to_put_out_candle_1' - SECONDS_JUDGE_2 = 'seconds_to_put_out_candle_2' - NON_AIR = 'non_air' - FURNITURE = 'furniture' - ARBITRARY_START = 'arbitrary_start' - RETURN_TRIP = 'return_trip' - NO_CANDLE_CIRCLE = 'no_candle_circle' - STOPPED_WITHIN_30 = 'stopped_within_30' - CANDLE_DETECTED = 'candle_detected' - NUM_ROOMS_SEARCHED = 'number_of_rooms_searched' - KICKED_DOG = 'kicked_dog' - TOUCHED_CANDLE = 'touched_candle' - WALL_CONTACT_CMS = 'wall_contact_cms' - RAMP_USED = 'ramp_used' - BABY_RELOCATED = 'baby_relocated' - ALL_CANDLES = 'all_candles' - VERSA_VALVE_USED = 'versa_valve_used' - - # name of all html form inputs, order matters - ALL = [ - NAME, - RUN_DISQUALIFIED, - SECONDS_JUDGE_1, - SECONDS_JUDGE_2, - NON_AIR, - FURNITURE, - ARBITRARY_START, - RETURN_TRIP, - NO_CANDLE_CIRCLE, - STOPPED_WITHIN_30, - CANDLE_DETECTED, - NUM_ROOMS_SEARCHED, - KICKED_DOG, - TOUCHED_CANDLE, - WALL_CONTACT_CMS, - RAMP_USED, - BABY_RELOCATED, - ALL_CANDLES, - VERSA_VALVE_USED - ] - - BOOLEANS = [ - RUN_DISQUALIFIED, - NON_AIR, - FURNITURE, - ARBITRARY_START, - RETURN_TRIP, - NO_CANDLE_CIRCLE, - STOPPED_WITHIN_30, - CANDLE_DETECTED, - KICKED_DOG, - RAMP_USED, - BABY_RELOCATED, - ALL_CANDLES, - VERSA_VALVE_USED - ] diff --git a/libraries/utilities/runs.py b/libraries/utilities/runs.py new file mode 100644 index 0000000..337ab80 --- /dev/null +++ b/libraries/utilities/runs.py @@ -0,0 +1,507 @@ +# -*- coding: utf-8; -*- + +from libraries.utilities.utilities import Utilities + + +class Runs(object): + # form paramter name mappings + NAME = 'name' + RUN_DISQ = 'run_disqualified' + SEC_JUDGE_1 = 'seconds_to_put_out_candle_1' + SEC_JUDGE_2 = 'seconds_to_put_out_candle_2' + NON_AIR = 'non_air' + FURNITURE = 'furniture' + ARBITRARY_START = 'arbitrary_start' + RETURN_TRIP = 'return_trip' + NO_CANDLE_CIRCLE = 'no_candle_circle' + STOPPED_WITHIN_30 = 'stopped_within_30' + CANDLE_DETECTED = 'candle_detected' + NUM_ROOMS = 'number_of_rooms_searched' + KICKED_DOG = 'kicked_dog' + TOUCHED_CANDLE = 'touched_candle' + WALL_CONTACT = 'wall_contact_cms' + RAMP_USED = 'ramp_used' + SECONDARY_SAFE_ZONE = 'secondary_safe_zone' + ALL_CANDLES = 'all_candles' + VERSA_VALVE_USED = 'versa_valve_used' + L3_TRAVERSED_HALLWAY = 'l3_traversed_hallway' + L3_FOUND_BABY = 'l3_found_baby' + L3_RESCUED_BABY = 'l3_rescued_baby' + L3_ALL_CANDLES = 'l3_all_candles' + L3_ONE_CANDLE = 'l3_one_candle' + L3_NONE = 'l3_none' + + # other parameters + TIME_DIFF_ERROR = 'time_difference_error' + + # error strings + NAME_ERR = 'Invalid name' + SEC_JUDGE_1_ERR = 'Invalid value' + SEC_JUDGE_2_ERR = 'Invalid value' + NUM_ROOMS_ERR = 'Invalid value' + TOUCHED_CANDLE_ERR = 'Invalid value' + WALL_CONTACT_ERR = 'Invalid value' + TIME_DIFF_ERR = ( + 'If robot failed the trial, ' + 'both judges should enter the same AT' + ) + + # name of all html form inputs + ALL = [ + NAME, + RUN_DISQ, + SEC_JUDGE_1, + SEC_JUDGE_2, + NON_AIR, + FURNITURE, + ARBITRARY_START, + RETURN_TRIP, + NO_CANDLE_CIRCLE, + STOPPED_WITHIN_30, + CANDLE_DETECTED, + NUM_ROOMS, + KICKED_DOG, + TOUCHED_CANDLE, + WALL_CONTACT, + RAMP_USED, + SECONDARY_SAFE_ZONE, + ALL_CANDLES, + VERSA_VALVE_USED, + L3_NONE, + L3_ONE_CANDLE, + L3_ALL_CANDLES, + L3_FOUND_BABY, + L3_RESCUED_BABY, + L3_TRAVERSED_HALLWAY + ] + + BOOLEANS = [ + RUN_DISQ, + NON_AIR, + FURNITURE, + ARBITRARY_START, + RETURN_TRIP, + NO_CANDLE_CIRCLE, + STOPPED_WITHIN_30, + CANDLE_DETECTED, + KICKED_DOG, + RAMP_USED, + SECONDARY_SAFE_ZONE, + ALL_CANDLES, + VERSA_VALVE_USED, + L3_NONE, + L3_ONE_CANDLE, + L3_ALL_CANDLES, + L3_FOUND_BABY, + L3_RESCUED_BABY, + L3_TRAVERSED_HALLWAY + ] + + L3_MILESTONES = [ + L3_NONE, + L3_ONE_CANDLE, + L3_ALL_CANDLES, + L3_FOUND_BABY, + L3_RESCUED_BABY, + L3_TRAVERSED_HALLWAY + ] + + @staticmethod + def validate_form( + form, + level, + division, + robot_name + ): + + disqualified = form[Runs.RUN_DISQ] + if disqualified: + return Runs.validate_form_disqualified( + form, + level, + division, + robot_name + ) + else: + return Runs.validate_form_qualified( + form, + level, + division, + robot_name + ) + + @staticmethod + def validate_form_disqualified( + form, + level, + division, + robot_name + ): + # if disqualified, need to verify name, time and # of rooms + disqualified = True + error = {} + + # validate name - sanity check + valid = Runs.validate_name(form[Runs.NAME], robot_name) + if not valid: + error[Runs.NAME] = Runs.NAME_ERR + # validate time recorded by first judge + valid = Runs.validate_actual_time( + form[Runs.SEC_JUDGE_1], + level, + disqualified + ) + if not valid: + error[Runs.SEC_JUDGE_1] = Runs.SEC_JUDGE_1_ERR + # validate time recorded by second judge + valid = Runs.validate_actual_time( + form[Runs.SEC_JUDGE_2], + level, + disqualified + ) + if not valid: + error[Runs.SEC_JUDGE_2] = Runs.SEC_JUDGE_2_ERR + # make sure they are equal (only if disqualified) + valid = Runs.validate_actual_time_compare( + form[Runs.SEC_JUDGE_1], + form[Runs.SEC_JUDGE_2] + ) + if not valid: + error[Runs.TIME_DIFF_ERROR] = Runs.TIME_DIFF_ERR + + # check level and division + if level == 1 and division in ['junior', 'walking']: + # validate number of rooms + valid = Runs.validate_num_rooms( + form[Runs.NUM_ROOMS], + level + ) + if not valid: + error[Runs.NUM_ROOMS] = Runs.NUM_ROOMS_ERR + print form + if level == 3: + has_at_least_one_milestone = False + for milestone in Runs.L3_MILESTONES: + print form[milestone] + print milestone + has_at_least_one_milestone = ( + has_at_least_one_milestone or + form[milestone] + ) + if not has_at_least_one_milestone: + error['l3_sympathy'] = 'Please select at least one option' + return error + + @staticmethod + def validate_form_qualified( + form, + level, + division, + robot_name + ): + # if disqualified, need to verify name, time and # of rooms + error = {} + disqualified = False + + # validate name - sanity check + valid = Runs.validate_name(form[Runs.NAME], robot_name) + if not valid: + error[Runs.NAME] = Runs.NAME_ERR + # validate time recorded by first judge + valid = Runs.validate_actual_time( + form[Runs.SEC_JUDGE_1], + level, + disqualified + ) + if not valid: + error[Runs.SEC_JUDGE_1] = Runs.SEC_JUDGE_1_ERR + # validate time recorded by second judge + valid = Runs.validate_actual_time( + form[Runs.SEC_JUDGE_2], + level, + disqualified + ) + if not valid: + error[Runs.SEC_JUDGE_2] = Runs.SEC_JUDGE_2_ERR + # validate number of rooms + valid = Runs.validate_num_rooms( + form[Runs.NUM_ROOMS], + level + ) + if not valid: + error[Runs.NUM_ROOMS] = Runs.NUM_ROOMS_ERR + # validate wall contact centimeters + valid = Runs.validate_wall_contact( + form[Runs.WALL_CONTACT] + ) + if not valid: + error[Runs.WALL_CONTACT] = Runs.WALL_CONTACT_ERR + # validate touched candle + valid = Runs.validate_touched_candle( + form[Runs.TOUCHED_CANDLE] + ) + if not valid: + error[Runs.TOUCHED_CANDLE] = Runs.TOUCHED_CANDLE_ERR + + return error + + @staticmethod + def validate_name(name, robot_name): + return name.lower() == robot_name.lower() + + @staticmethod + def validate_actual_time_compare(time_j1, time_j2): + + time_j1 = time_j1.strip() + time_j2 = time_j2.strip() + + if time_j1.isdigit() and time_j2.isdigit(): + return float(time_j1) == float(time_j2) + return False + + @staticmethod + def validate_actual_time(time_s, level, failed): + # minimum and maximum time allowed for each level + + time_s = time_s.strip() + + min_123 = 0 # minimum for any level + max_1 = 180 # 3 minutes for level 1 + max_2 = 240 # 4 minutes for level 2 + max_3 = 300 # 5 minutes for level 3 + + # special AT values in case of a failed trial + fail_123 = 600 # trial failed (any level) + traversed_3 = 500 # failed but traversed from arean A to B (level 3) + found_baby_3 = 450 # failed but found baby (level 3) + picked_baby_3 = 400 # failed but picked up baby (level 3) + + # check if input string is a number + + # convet to a float + try: + time = float(time_s) + except ValueError, TypeError: + return False + + # validation for level 1 + if level == 1: + if failed and (time != fail_123): + return False + elif (not failed) and (time < min_123 or time > max_1): + return False + + # validation for level 2 + elif level == 2: + if failed and (time != fail_123): + return False + elif (not failed) and (time < min_123 or time > max_2): + return False + + # validation for level 3 + elif level == 3: + if(failed + and (time != fail_123) + and (time != traversed_3) + and (time != found_baby_3) + and (time != picked_baby_3)): + return False + + elif (not failed) and (time < min_123 or time > max_3): + return False + return True + + # validate number of rooms + @staticmethod + def validate_num_rooms(num_s, level): + if level not in [1, 2]: + return True + + num_s = num_s.strip() + # minimum and maximum allowed values + min_123 = 0 + max_123 = 4 + + # check if input string is a number + if level in [1, 2]: + if not num_s.isdigit(): + return False + + return (int(num_s) >= min_123) and (int(num_s) <= max_123) + + return True + + # validate wall contact distance + @staticmethod + def validate_wall_contact(num_s): + + num_s = num_s.strip() + # minimum and maximum allowed values + min_123 = 0 + max_123 = 500 # length of arena + + # check if input string is a number + if not num_s.isdigit(): + return False + + return (int(num_s) >= min_123) and (int(num_s) <= max_123) + + # validate touched_candle + @staticmethod + def validate_touched_candle(num_s): + num_s = num_s.strip() + # Just check if it's digit for now + # minimum allowed value + min_123 = 0 + + # check if input string is a number + if not num_s.isdigit(): + return False + + return int(num_s) >= min_123 + + # converts html form to a python dictonary + @staticmethod + def convert_to_dict(form): + dictionary = {} + for p in Runs.ALL: + if p in Runs.BOOLEANS: + dictionary[p] = True if p in form else False + else: + dictionary[p] = form[p] if p in form else None + return dictionary + + # gets score of current run based on from data + @ staticmethod + def get_score(robot, data): + # calculate actual time + judge1 = Utilities.safe_cast( + data[Runs.SEC_JUDGE_1], + float, + 0 + ) + judge2 = Utilities.safe_cast( + data[Runs.SEC_JUDGE_2], + float, + 0 + ) + actual_time = (judge1 + judge2) / 2.0 + # cast number of rooms to int + num_rooms = Utilities.safe_cast( + data[Runs.NUM_ROOMS], + int, + 0 + ) + # cast number of candle touches to int + candle_touch = Utilities.safe_cast( + data[Runs.TOUCHED_CANDLE], + int, + 0 + ) + # cast wall contact cms to int + wall_conatct = Utilities.safe_cast( + data[Runs.WALL_CONTACT], + int, + 0 + ) + return actual_time, Runs.calculate_run_score( + robot['division'], + robot['level'], + data[Runs.RUN_DISQ], + actual_time, + data[Runs.NON_AIR], + data[Runs.FURNITURE], + data[Runs.ARBITRARY_START], + data[Runs.RETURN_TRIP], + data[Runs.NO_CANDLE_CIRCLE], + data[Runs.STOPPED_WITHIN_30], + data[Runs.CANDLE_DETECTED], + num_rooms, + data[Runs.KICKED_DOG], + candle_touch, + wall_conatct, + data[Runs.RAMP_USED], + data[Runs.SECONDARY_SAFE_ZONE], + data[Runs.ALL_CANDLES] + ) + + # calculates score + @staticmethod + def calculate_run_score( + robot_div, + level, + failed_trial, + actual_time, + non_air, + furniture, + arbitrary_start, + return_trip, + candle_location_mode, + stopped_within_circle, + signaled_detection, + num_rooms_detected, + kicked_dog, + touched_candle, + cont_wall_contact, + ramp_hallway, + alt_target, + all_candles + ): + + task_search = num_rooms_detected * (-30) + task_detect = -30 if signaled_detection else 0 + task_position = -30 if stopped_within_circle else 0 + + om_candle = 0.75 if candle_location_mode else 1 + + om_start = 0.8 if arbitrary_start else 1 + om_return = 0.8 if return_trip else 1 + om_extinguisher = 0.75 if non_air else 1 + om_furniture = 0.75 if furniture else 1 + + if num_rooms_detected == 0 or num_rooms_detected == 1: + room_factor = 1 + elif num_rooms_detected == 2: + room_factor = 0.85 + elif num_rooms_detected == 3: + room_factor = 0.5 + elif num_rooms_detected == 4: + room_factor = 0.35 + + pp_candle = 50 * touched_candle + pp_slide = cont_wall_contact / 2 + pp_dog = 50 if kicked_dog else 0 + + om_alt_target = 0.6 if alt_target else 1 + om_ramp_hallway = 0.9 if ramp_hallway else 1 + om_all_candles = 0.6 if all_candles else 1 + + # Scores + if failed_trial: + if robot_div in ['junior', 'walking'] and level == 1: + return 600 + task_detect + task_position + task_search + elif level == 3 and actual_time in [400, 450, 500]: + return actual_time + else: + return 600 + + if level == 1: + score = ( + (actual_time + pp_candle + pp_dog + pp_slide) * + (om_candle * om_start * om_return * om_extinguisher * om_furniture) * room_factor) + + if level == 2: + score = ( + (actual_time + pp_candle + pp_dog + pp_slide) * + (om_start * om_return * om_extinguisher * om_furniture) * room_factor) + + if level == 3: + score = ( + (actual_time + pp_candle + pp_dog + pp_slide) * + om_alt_target * om_ramp_hallway * om_all_candles + ) + + if score > 600: + return 600 + else: + return score diff --git a/libraries/utilities/scoreboard.py b/libraries/utilities/scoreboard.py index a549173..e83bc4d 100644 --- a/libraries/utilities/scoreboard.py +++ b/libraries/utilities/scoreboard.py @@ -15,6 +15,7 @@ def add_scoreboard_params(robots): robot['TFS'] = total_score robot['completed'] = attempted_levels robot['num_successful'] = num_successful + robot['num_runs'] = len(runs) return robots @staticmethod @@ -46,7 +47,16 @@ def get_lisp_winners(robots): for category in ['unique','custom']: lisp_winners[level][category] = {} # filter based on level and category - filtered = ScoreBoard.filter_robots_category(ScoreBoard.filter_robots_level(robots,level), category) + filtered = ScoreBoard.filter_robots_category( + ScoreBoard.filter_robots_level(robots,level), + category + ) + # only junior and walking are eligible for level 1 prizes (since 2017) + if level == 1: + filtered = ScoreBoard.filter_robots_divisions( + filtered, + ['junior', 'walking'] + ) # check if number of successful runs is atleast 3 filtered = ScoreBoard.filter_number_of_runs(filtered, 3) # sort robots @@ -99,6 +109,11 @@ def filter_robots_level(robots, level): def filter_robots_division(robots, division): return [robot for robot in robots if robot['division'] == division] + # filter robots based on divisions + @staticmethod + def filter_robots_divisions(robots, divisions): + return [robot for robot in robots if robot['division'] in divisions] + # filter robots based on category(unique or customized) @staticmethod def filter_robots_category(robots, category): diff --git a/libraries/utilities/utilities.py b/libraries/utilities/utilities.py new file mode 100644 index 0000000..acafe67 --- /dev/null +++ b/libraries/utilities/utilities.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8; -*- + + +class Utilities(object): + + @staticmethod + def safe_cast(val, to, default=None): + try: + return to(val) + except (ValueError, TypeError): + return default diff --git a/load_test_robots.py b/load_test_robots.py index dde5fc7..c7dd4b6 100644 --- a/load_test_robots.py +++ b/load_test_robots.py @@ -14,14 +14,14 @@ def get_id(id_counter, robot_data): def run(): load_registry() division_map = { - 'fire fighting high school division': 'high_school', - 'fire fighting junior division': 'junior', - 'fire fighting senior division': 'senior', - 'fire fighting walking division': 'walking' + 'high school': 'high_school', + 'junior': 'junior', + 'senior': 'senior', + 'walking': 'walking' } unique_map = { 'unique': True, - 'customized': False, + 'custom kit': False, 'i am not sure': False } versa_valve_map = { @@ -29,13 +29,13 @@ def run(): 'no': False } fields_index = { - 'division': 3, - 'name': 4, - 'unique': 5, + 'division': 0, + 'name': 1, + 'unique': 3, 'versa_valve': 6, - 'school': 7 + 'school': 2 } - with open('robot_list.csv', 'rb') as csvfile: + with open('robot_list_2017.csv', 'rb') as csvfile: spamreader = csv.reader(csvfile) i = 0 id_counter = { @@ -45,7 +45,8 @@ def run(): 'junior': 1 } for row in spamreader: - if i == 0: + # skip first three lines + if i <= 2: i += 1 continue i += 1 @@ -58,8 +59,6 @@ def run(): d[field] = division_map[row[index].strip().lower()] elif field == 'unique': d[field] = unique_map[row[index].strip().lower()] - elif field == 'versa_valve': - d[field] = versa_valve_map[row[index].strip().lower()] else: d[field] = row[index] d['id'] = get_id(id_counter, d) @@ -70,7 +69,7 @@ def run(): school=d['school'], name=d['name'], is_unique=d['unique'], - used_versa_valve=d['versa_valve'], + used_versa_valve=False, level=1, is_disqualified=False, passed_inspection=False diff --git a/robot_list_2017.csv b/robot_list_2017.csv new file mode 100644 index 0000000..6c5f3ef --- /dev/null +++ b/robot_list_2017.csv @@ -0,0 +1,84 @@ +,2017 Robot Teams,,,Awards,,, +,As of 12:00 p.m. / March 21,,,No. American,IEEE CT,, +Division,What is the robot name?,Affiliation,Unique/Kit,Country,State,Free Shirt,Email +High School,ADoT Vila Franca,Associa??o Desenvolver o Talento,Unique,,,Medium,adot.portugal@gmail.com +High School,TBD,Frontier Regional School,Unique,US,,Large,scott.paul@frsu38.org +High School,Honey Boo Boo,Hotchkiss School,Unique,,CT,Small,wfenton@hotchkiss.org +High School,F.S.Daneel MK1,Individual,Unique,US,,Medium,sidharth0801@gmail.com +High School,HURRICANE,Ironi G,Unique,Indonesdia,,Extra Large,fbekman@gmail.com +High School,Meeko,Mercer High School,Unique,US,,Large,cdshaffer@acm.org +High School,AlmostStraight,Mercer High School,Unique,US,,Medium,cdshaffer@acm.org +High School,BOB,Misgav High School,Unique,,,Youth Large,haimdr@walla.co.il +High School,Yoske,Misgav High School,Unique,,,Youth Large,haimdr@walla.co.il +High School,Eric,Misgav High School,Unique,,,Youth Large,haimdr@walla.co.il +High School,Roberto,Ort High School,Unique,,,Medium,itaygarnek@gmail.com +High School,Nero,Ort High School,Unique,,,Medium,tomer3012000@gmail.com +High School,Alfred,Ort High School,Unique,,,Medium,nimrod214@gmail.com +High School,RoBoG,Ort High School,Unique,,,Medium,gal.harari11@gmail.com +High School,Chubaka,Ort Holon High School,Unique,,,Large,koruthy@gmail.com +High School,David,Ort Holon High School,Unique,,,Large,koruthy@gmail.com +High School,Dualius,Ort Holon High School,Unique,,,Extra Large,koruthy@gmail.com +High School,HYPERSQUARE,Princeton High School,Unique,US,,Medium,graciela_elia@princetonk12.org +High School,Marvin,Princeton High School,Unique,US,,Medium,graciela_elia@princetonk12.org +High School,LibreFire,Princeton High School,Unique,US,,Medium,graciela_elia@princetonk12.org +High School,Bad Hombre,Princeton High School,Unique,US,,Medium,graciela_elia@princetonk12.org +High School,SMTL-T800,Anhui Tongling No. 3 HS and Shimen Senior MS,Unique,,,Medium,joannaz@yeah.net +High School,SMTL-Challenger,Anhui Tongling No. 3 HS and Shimen Senior MS,Custom Kit,,,Medium,joannaz@yeah.net +High School,SMTL-Pathfinder,Anhui Tongling No. 3 HS and Shimen Senior MS,Custom Kit,,,Medium,joannaz@yeah.net +High School,RhinoBot,Taft School,Unique,US,CT,Large,mooneyj@taftschool.org +High School,RedRhino,Taft School,Unique,US,CT,Large,mooneyj@taftschool.org +High School,Jim Jr.,Taft School,Unique,US,CT,Medium,mooneyj@taftschool.org +Junior,The Boon,Advanced Mechatronics For Kids LLC,Unique,US,,Large,srosly01@gmail.com +Junior,ADoT Munic?pio da Guarda,Associa??o Desenvolver o Talento,Unique,,,Medium,adot.portugal@gmail.com +Junior,Taco,Frontier Regional School,Unique,US,,Medium,scott.paul@frsu38.org +Junior,UltiBot,Individual,Unique,US,CT,Extra Large,r_thamma@hotmail.com +Junior,Bro-Bot,Keshequa Middle School,Custom Kit,US,,Small,21hunta@students.keshequa.org +Junior,Goat-Bot,Learntribute,Unique,US,CT,Youth Large,haoyuw@yahoo.com +Junior,It's not rocket science,Learntribute,Unique,US,CT,Youth Large,haoyuw@yahoo.com +Junior,F.L.U.F.F.,Mercer/Hickory Elementary,Unique,US,,Large,cdshaffer@acm.org +Junior,Smokey 2.0,Smith STEM School,Custom Kit,US,CT,Medium,hardestymk4@gmail.com +Junior,TalcottEV3,Talcott Mountain Science Center,Unique,US,CT,Large,jpellino@tmsc.org +Junior,TalcottVEX,Talcott Mountain Science Center,Unique,US,CT,Large,jpellino@tmsc.org +Senior,ACTVET Omni,ACTVET,Unique,,,Large,Tiago.Caldeira@emiratesskills.ae +Senior,PXT-V3,Alfred State College,Unique,US,,Large,BryniaBL@alfredstate.edu +Senior,Smouldaire,Alfred State College,Unique,US,,Large,voorheb@alfredstate.edu +Senior,TLSM-Innovator,Anhui Tongling No. 3 HS and Shimen Senior MS,Custom Kit,,,Medium,joannaz@yeah.net +Senior,TLSM-Skipper,Anhui Tongling No. 3 HS and Shimen Senior MS,Custom Kit,,,Medium,joannaz@yeah.net +Senior,George School Rolling,George School,Unique,US,,Medium,codom@georgeschool.org +Senior,FlameOut,Individual,Unique,Canada,,Extra Large,bruce.sheridan@gmail.com +Senior,Candlefinder,Let's Build Rockets Inc.,Unique,US,CT,Medium,gisellegk@gmail.com +Senior,Mr. B,Madrobots,Custom Kit,US,CT,Extra Large,ajtsheppard@gmail.com +Senior,Goddard,NYU Tandon School of Engineering,Unique,US,,Extra Large,Aghosh993@gmail.com +Senior,Senior EFFiRo,Politeknik Elektronika Negeri Surabaya,Unique,,,Medium,widi@pens.ac.id +Senior,PENS,Politeknik Elektronika Negeri Surabaya,Unique,,,Medium,widi@pens.ac.id +Senior,EFFiRo Jr.,Politeknik Elektronika Negeri Surabaya,Unique,,,Medium,widi@pens.ac.id +Senior,Cardbot 1.0,Saginaw Valley State University,Unique,US,,Large,rmuralee@svsu.edu +Senior,George,The George Washington University,Unique,US,,Medium,sjlazev@gwu.edu +Senior,Bromeo,Trinity College,Unique,US,CT,Medium,christopher.rowe@trincoll.edu +Senior,ladyBug,Trinity College,Unique,US,CT,Large,lucy.matz@trincoll.edu +Senior,Se7en,Trinity College,Unique,US,CT,Medium,colette.scheffers@trincoll.edu +Senior,TrailerTrashBot,Trinity College,Custom Kit,US,CT,Large,prabhat.bhootra@trincoll.edu +Senior,Sphinx,Trinity College,Unique,US,CT,Medium,nathan.martinez@trincoll.edu +Senior,Trojan,Trinity college,Unique,US,CT,Medium,mahmood.khalil@trincoll.edu +Senior,Redeemer4047,Trinity College,Custom Kit,US,CT,Medium,guangzhao.chen@trincoll.edu +Senior,IronBot,Trinity College,Unique,US,CT,Medium,ebenezer.hormenou@trincoll.edu +Senior,Mercutio,Trinity College,Custom Kit,US,CT,Large,fiona.mcelroy@trincoll.edu +Senior,Irrelephant,Tufts University,Unique,US,,Medium,annie.wong@tufts.edu +Senior,Jumbotron,Tufts University,Unique,US,,Medium,annie.wong@tufts.edu +Senior,Robophoenix,UMass Lowell Robotics Club,Unique,US,,Extra Large,Benjamin_Roth@student.uml.edu +Senior,Smitty Werbenjagermanjensen,University of Connecticut,Unique,US,CT,Large,connor.mccullough@uconn.edu +Senior,A.Y.E.R.S.,University of Connecticut,Unique,US,CT,Medium,tanner.george@uconn.edu +Senior,Amigo,University of Evansville,Unique,US,,Large,rp122@evansville.edu +Senior,Robocop,University of Hartford,Custom Kit,US,CT,Medium,bmiranda@hartford.edu +Senior,Dome_Mu,University of Muhammadiyah Malang,Unique,,,Large,ikhlal888@gmail.com +Senior,Real Juventus Car,Westminster College,Unique,US,,Extra Large,cdshaffer@acm.org +Senior,Memnite,Worcester,Unique,US,,Large,bdshaffer31@gmail.com +Senior,ACTVET WSAD,ACTVET,Unique,,,Medium,Tiago.Caldeira@emiratesskills.ae +Walking,AWaBot,C-Cubed Robotics + Polybots,Unique,US,,Extra Large,nvale55@gmail.com +Walking,George School Walking,George School,Custom Kit,US,,Small,codom@georgeschool.org +Walking,Eilero,Politeknik Elektronika Negeri Surabaya,Unique,,,Medium,widi@pens.ac.id +Walking,Rollin,PolyBOTS,Unique,US,,Medium,ec2321@nyu.edu +Walking,The Dank Engine,PolyBOTS,Custom Kit,US,,Large,chrisdimauro@gmail.com +Walking,one arm bandit,PolyBOTS,Unique,US,,Medium,hasn0life421@yahoo.com +Walking,UnmuhMalang,University of Muhammadiyah Malang,Unique,,,Large,imamfatoni273@gmail.com +Walking,InaMuh,University of Muhammadiyah Malang,Unique,,,Large,salismuchtarfadhilal@gmail.com diff --git a/scoringsystem/blueprints/api.py b/scoringsystem/blueprints/api.py index 61b9834..afe4707 100644 --- a/scoringsystem/blueprints/api.py +++ b/scoringsystem/blueprints/api.py @@ -53,7 +53,10 @@ def prize_winners(): entry = '' first_place = 1 if gpmp_winners.get(first_place): - entry += gpmp_winners.get(first_place).get('name') + entry += ( + gpmp_winners.get(first_place).get('name') + + ' (' + gpmp_winners.get(first_place).get('school') + ')' + ) else: entry += 'N/A' cw.writerow(['Grand Performance Mastery Prize Winner (GPMP)',entry]) @@ -73,7 +76,10 @@ def prize_winners(): for place in [1,2,3]: entry += str(place) + ': ' if brd_winners.get(division).get(category).get(place): - entry += brd_winners.get(division).get(category).get(place).get('name') + entry += ( + brd_winners.get(division).get(category).get(place).get('name') + + ' (' + brd_winners.get(division).get(category).get(place).get('school') + ')' + ) else: entry += 'N\A' entry += ' ' @@ -96,7 +102,10 @@ def prize_winners(): for place in [1]: entry += str(place) + ': ' if lisp_winners.get(level).get(category).get(place): - entry += lisp_winners.get(level).get(category).get(place).get('name') + entry += ( + lisp_winners.get(level).get(category).get(place).get('name') + + ' (' + lisp_winners.get(level).get(category).get(place).get('school') + ')' + ) else: entry += 'N\A' entry += ' ' diff --git a/scoringsystem/blueprints/main.py b/scoringsystem/blueprints/main.py index a40d734..30dd99d 100644 --- a/scoringsystem/blueprints/main.py +++ b/scoringsystem/blueprints/main.py @@ -1,46 +1,61 @@ # -*- coding: utf-8 -*- from flask import ( Blueprint, render_template, url_for, request, - redirect, session, make_response) + redirect, session) import registry as r -import StringIO -import csv from libraries.utilities.authentication import AuthenticationUtilities from libraries.utilities.level_progress_handler import LevelProgressHandler from libraries.utilities.score_calculator import ScoreCalculator -from libraries.utilities.scoreboard import ScoreBoard -from libraries.utilities.run_parameters import RunParameters +from libraries.utilities.runs import Runs from libraries.utilities.robot_inspection_table_handler import ( RobotInspectionTableHandler ) -main = Blueprint('main', __name__) +main = Blueprint('main', __name__) + +# runs before all https requests to make sure user is logged in @main.before_request def require_login(): if not AuthenticationUtilities.user_is_logged_in(session): return redirect(url_for('auth.signin')) -@main.route('/', methods=['GET', 'POST']) +# get request handler for home page +@main.route('/', methods=['GET']) def home(): robots = r.get_registry()['ROBOTS'].get_all_robots() for rb in robots: - rb['endpoint'] = url_for('main.robot_detail', robot_id=rb['id']) + rb['endpoint'] = url_for( + 'main.robot_detail', + robot_id=rb['id'] + ) return render_template( "home.html", robots=robots ) -@main.route('/schedule', methods=['GET', 'POST']) -def schedule(): - return render_template("schedule.html") +# get request handler for scoreboard page +@main.route('/scoreboard', methods=['GET']) +def scoreboard_home(): + return render_template("scoreboard_home.html") +# not found page +@main.route('/not_found', methods=['GET']) +def not_found(): + return render_template("not_found.html") + + +# post request handler for robot insepection form @main.route('/rit_inspection_approval/', methods=['POST']) def rit_inspection_approval(robot_id): - valid, inputs = RobotInspectionTableHandler.validate_inputs(request.form) + # validate form + valid, inputs = RobotInspectionTableHandler.validate_inputs( + request.form + ) + # if valid, check volume and store in db if valid: RobotInspectionTableHandler.approve_and_store_volume( inputs[RobotInspectionTableHandler.HEIGHT], @@ -51,37 +66,47 @@ def rit_inspection_approval(robot_id): return robot_detail(robot_id=robot_id, inputs=inputs) -@main.route('/not_found', methods=['GET', 'POST']) -def not_found(): - return render_template("not_found.html") - - +# post request handler for "advance to next level" button on robot detail page @main.route('/robot/', methods=['POST']) def advance_level(robot_id): + # validate robot id robot = r.get_registry()['ROBOTS'].get_robot(robot_id) - if not robot: return render_template("not_found.html") + # get existing runs of robot runs = r.get_registry()['RUNS'].get_runs(robot_id) - eligible = LevelProgressHandler.get_eligibility_for_next_run(runs, robot['level']) - + # check if robot is eligible to be advanced to next level + eligible = LevelProgressHandler.get_eligibility_for_next_run( + runs, + robot['level'] + ) + # if eligible and has not be disqualified, advance robot to next level if eligible.get('can_level_up') and not eligible['disqualified']: - r.get_registry()['ROBOTS'].advance_level(robot_id, robot['level']) - return redirect(url_for('main.robot_add_run', robot_id=robot_id)) + # advance to next level by updating 'level' column in db + r.get_registry()['ROBOTS'].advance_level( + robot_id, + robot['level'] + ) + # redirect to add run page + return redirect( + url_for('main.robot_add_run', robot_id=robot_id) + ) return "Robot not eligible to advance to next level.\n" -@main.route('/robot/', methods=['GET', 'POST']) +# get request handler for robot detail page +@main.route('/robot/', methods=['GET']) def robot_detail(robot_id, inputs=None): + # validate robot id robot = r.get_registry()['ROBOTS'].get_robot(robot_id) if not robot: return render_template("not_found.html") + # get existing runs of robot runs = r.get_registry()['RUNS'].get_runs(robot_id) - run_levels = [run['id'] for run in runs] # check if disqualifited and eligibility to advnace to next level eligibility = LevelProgressHandler.get_eligibility_for_next_run( @@ -91,6 +116,13 @@ def robot_detail(robot_id, inputs=None): best_scores, attempted_levels, total_score, num_successful = ( ScoreCalculator.get_best_scores(runs) ) + + # get all factors applied to the scores of previous runs + run_levels = [run['id'] for run in runs] + applied_factors = [ + get_applied_factors(id, robot_id) for id in run_levels + ] + # render template return render_template( "robot.html", attempted_levels=attempted_levels, @@ -101,242 +133,103 @@ def robot_detail(robot_id, inputs=None): eligible=eligibility['can_level_up'], best_scores=best_scores, robot_runs=runs, - applied_factors=[applied_factors(id, robot_id) for id in run_levels], + applied_factors=applied_factors, inputs=inputs ) - +# get and post request handler for add run page @main.route('/robot//addrun', methods=['GET', 'POST']) def robot_add_run(robot_id): + # validate robot it robot = r.get_registry()['ROBOTS'].get_robot(robot_id) - if not robot: return render_template("not_found.html") + # get all previous runs of robot all_runs = r.get_registry()['RUNS'].get_runs(robot_id) + + # if GET request if request.method == 'GET': - # get all previous runs return render_template( "run.html", robot=robot, division=get_division_label(robot['division']), input=request.args, + error={}, all_runs=all_runs ) - # For post request - - # Database query for showing past runs if the POST fails - - all_runs = r.get_registry()['RUNS'].get_runs(robot_id) - # if invalidate input data - params_d = bind_params(request.form, robot_id, robot['level']) - - err = validate_params(params_d, - robot['level'], - robot['division'], - robot['name']) - - if err: - err['ERR'] = True - params_and_errors = {} - params_and_errors.update(params_d) - # leave data already entered unchanged - params_and_errors.update(err) # include errors + # make sure not more than 5 runs can be submitted + if all_runs and len(all_runs) == 5: + error = {'error': 'Cannot submit more than five runs'} return render_template( "run.html", robot=robot, division=get_division_label(robot['division']), - input=params_and_errors, + input=request.form, + error=error, all_runs=all_runs ) - # calculate score - score = get_score(robot, params_d) - # convert dict values to tuple to prepare to insert to DB - params_t = convert_to_tuple(params_d, robot_id, score) - # insert into databse - r.get_registry()['RUNS'].record_run(*params_t) - return redirect(url_for('main.robot_detail', robot_id=robot_id)) - - -@main.route('/scoreboard', methods=['GET', 'POST']) -def scoreboard_home(): - return render_template("scoreboard_home.html") - - -@main.route('/scoreboardcsv', methods=['GET', 'POST']) -def export_to_csv(): - divisions = ['junior', 'walking', 'high_school', 'senior'] - - all_robots = {} - - for division in divisions: - all_robots[division] = r.get_registry()['ROBOTS'].get_all_robots_division(division); - - si = StringIO.StringIO() - cw = csv.writer(si) - cw.writerow(['Rank', 'Division', 'Name', '# of Successful Runs', - 'Current Level', 'LS1', 'LS2', 'LS3', 'TFS']) - - for div in all_robots: - for robot in all_robots[div]: - runs = r.get_registry()['RUNS'].get_runs(robot['id']) - # get current best scores - best_scores, attempted_levels, total_score, num_successful = ( - ScoreCalculator.get_best_scores(runs) - ) - robot.update(best_scores) - robot['TFS'] = total_score - robot['num_successful'] = num_successful - # calculate lowes scores for each level and TFS, returns tuple - robot['completed'] = attempted_levels - - # sort based on name then total score - sorted_robots = sorted(list(all_robots[div]), key=lambda k: k['name']) - sorted_robots = sorted(list(sorted_robots), key=lambda k: k['TFS']) - - - for index, sorted_r in enumerate(sorted_robots, start=1): - cw.writerow([index, sorted_r['division'], sorted_r['name'], sorted_r['num_successful'], - sorted_r['level'], sorted_r['LS1'], sorted_r['LS2'], sorted_r['LS3'], - sorted_r['TFS']]) - - cw.writerow('\n') - - output = make_response(si.getvalue()) - si.close() - output.headers["Content-Disposition"] = "attachment; filename=scoreboard.csv" - output.headers["Content-type"] = "text/csv" - return output - - -@main.route('/scoreboard/brd/', methods=['GET', 'POST']) -def scoreboard_brd(division): - robots = r.get_registry()['ROBOTS'].get_all_robots_division(division) - - if not robots: - return render_template("not_found.html") - - # add additional parameters to be displayed on scoreboard - robots = ScoreBoard.add_scoreboard_params(robots) - - # sort based on name then total score - sorted_robots = sorted(list(robots), key=lambda k: k['name']) - sorted_robots = sorted(list(sorted_robots), key=lambda k: k['TFS']) - - return render_template( - "scoreboard_brd_gpmp.html", - robots=sorted_robots, - scoreboard_name=get_division_label(division) - ) - -@main.route('/scoreboard/gpmp', methods=['GET', 'POST']) -def scoreboard_gpmp(): - robots = r.get_registry()['ROBOTS'].get_all_robots() - - if not robots: - return render_template("not_found.html") - - # add additional parameters to be displayed on scoreboard - robots = ScoreBoard.add_scoreboard_params(robots) - - # sort based on name then total score - sorted_robots = sorted(list(robots), key=lambda k: k['name']) - sorted_robots = sorted(list(sorted_robots), key=lambda k: k['TFS']) - - return render_template( - "scoreboard_brd_gpmp.html", - robots=sorted_robots, - scoreboard_name="Grand Performance" - ) - - -@main.route('/scoreboard/lisp/', methods=['GET', 'POST']) -def scoreboard_lisp(level): - if not level.isdigit(): - return render_template("not_found.html") - if int(level) not in [1, 2, 3]: - return render_template("not_found.html") - - robots = r.get_registry()['ROBOTS'].get_all_robots() - - if not robots: - return render_template("not_found.html") - - # add additional parameters to be displayed on scoreboard - robots = ScoreBoard.add_scoreboard_params(robots) - - # filter robots - filtered_robots = ScoreBoard.filter_robots_level(robots, int(level)) - - # key used for sorting - score_name = "LS" + level - - # sort based on name then this level's lowest score - sorted_robots = sorted(list(filtered_robots), key=lambda k: k['name']) - sorted_robots = sorted(list(sorted_robots), key=lambda k: k[score_name]) - - return render_template( - "scoreboard_lisp.html", - robots=sorted_robots, - level=level, - score_name=score_name + # request is POST + # validate input data + form = Runs.convert_to_dict(request.form) # convert to dict + error = Runs.validate_form( + form, + robot['level'], + robot['division'], + robot['name'] ) -@main.route('/prizes', methods=['GET', 'POST']) -def prize_winners(): - robots = r.get_registry()['ROBOTS'].get_all_robots() - - # calculate additional score parameters - robots = ScoreBoard.add_scoreboard_params(robots) - - # gpmp_winner[place] dict - gpmp_winners = ScoreBoard.get_gpmp_winners(robots) - - # lisp_winners[level][category][place] dict - lisp_winners = ScoreBoard.get_lisp_winners(robots) - - # brd_winners[division][category][place] dict - brd_winners = ScoreBoard.get_brd_winners(robots) + # if error, re-render page with errors + if error: + error['error'] = '*Please fix all errors highlighted in red' + return render_template( + "run.html", + robot=robot, + division=get_division_label(robot['division']), + input=form, + error=error, + all_runs=all_runs + ) - return render_template( - "prizes.html", - gpmp_winners=gpmp_winners, - lisp_winners=lisp_winners, - brd_winners=brd_winners + # calculate score + actual_time, score = Runs.get_score(robot, form) + + # insert into database + r.get_registry()['RUNS'].record_run( + level=robot['level'], + failed_trial=form[Runs.RUN_DISQ], + actual_time=actual_time, + non_air=form[Runs.NON_AIR], + furniture=form[Runs.FURNITURE], + arbitrary_start=form[Runs.ARBITRARY_START], + return_trip=form[Runs.RETURN_TRIP], + candle_location_mode=form[Runs.NO_CANDLE_CIRCLE], + stopped_within_circle=form[Runs.STOPPED_WITHIN_30], + signaled_detection=form[Runs.CANDLE_DETECTED], + num_rooms_searched=form[Runs.NUM_ROOMS], + kicked_dog=form[Runs.KICKED_DOG], + touched_candle=form[Runs.TOUCHED_CANDLE], + cont_wall_contact=form[Runs.WALL_CONTACT], + ramp_hallway=form[Runs.RAMP_USED], + alt_target=form[Runs.SECONDARY_SAFE_ZONE], + all_candles=form[Runs.ALL_CANDLES], + used_versa_valve=form[Runs.VERSA_VALVE_USED], + l3_traversed_hallway=form[Runs.L3_TRAVERSED_HALLWAY], + l3_found_baby=form[Runs.L3_FOUND_BABY], + l3_rescued_baby=form[Runs.L3_RESCUED_BABY], + l3_all_candles=form[Runs.L3_ALL_CANDLES], + l3_one_candle=form[Runs.L3_ONE_CANDLE], + l3_none=form[Runs.L3_NONE], + score=score, + robot_id=robot_id, ) -# convert dict values to tuple to prepare to insert to DB -def convert_to_tuple(dic, robot_id, score): - # convert the dictionary values to tuples, order matters - l = [] - - l.append(dic['level']) - l.append(dic['run_disqualified']) - l.append((to_float(dic['seconds_to_put_out_candle_1']) + - to_float(dic['seconds_to_put_out_candle_2'])) / 2.0) - # average times by the 2 judges - l.append(dic['non_air']) - l.append(dic['furniture']) - l.append(dic['arbitrary_start']) - l.append(dic['return_trip']) - l.append(dic['no_candle_circle']) - l.append(dic['stopped_within_30']) - l.append(dic['candle_detected']) - l.append(to_int(dic['number_of_rooms_searched'])) - l.append(dic['kicked_dog']) - l.append(to_int(dic['touched_candle'])) - l.append(to_int(dic['wall_contact_cms'])) - l.append(dic['ramp_used']) - l.append(dic['baby_relocated']) - l.append(dic['all_candles']) - l.append(dic['versa_valve_used']) - l.append(score) - l.append(robot_id) - - return tuple(l) + # redirect to robot detail page + return redirect(url_for('main.robot_detail', robot_id=robot_id)) +# get probperly formatted string from division id def get_division_label(division): # page header label if division == 'junior': @@ -348,227 +241,9 @@ def get_division_label(division): else: return "Senior Division" -# convert string to float -def to_float(input_s): - if input_s: - return float(input_s) - - # only true when run is disqualified - return 0 - -# convert string to float -def to_int(input_s): - if input_s: - return int(input_s) - - # only true when run is disqualified - return 0 - -# validate input -def validate_params(input_data, level, div, name): - # create a dictionary out of input data - data = dict(input_data) - - err = dict() - # if disqualified, need to check for name, AT and num of rooms - if(data['run_disqualified']): - if not validate_name(data['name'], name): - err['NAME_ERR'] = True - if not validate_actual_time(data['seconds_to_put_out_candle_1'],level, True): - err["TIME_ERR_1"] = True - if not validate_actual_time(data['seconds_to_put_out_candle_2'],level, True): - err["TIME_ERR_2"] = True - if not validate_actual_time_compare(data['seconds_to_put_out_candle_1'], - data['seconds_to_put_out_candle_2']): - err["TIME_ERR_DIFF"] = True - if ((level == 1) - and (div in ['junior', 'walking']) - and (not validate_num_rooms(data['number_of_rooms_searched'], level))): - err["ROOM_ERR"] = True - - # else validate every input - else: - for p in RunParameters.ALL: - if p in data: - if p == 'name': - if not validate_name(data[p], name): - err['NAME_ERR'] = True - elif p == 'seconds_to_put_out_candle_1': - if not validate_actual_time(data[p],level, False): - err["TIME_ERR_1"] = True - elif p == 'seconds_to_put_out_candle_2': - if not validate_actual_time(data[p],level, False): - err["TIME_ERR_2"] = True - elif p == 'number_of_rooms_searched': - if not validate_num_rooms(data[p], level): - err["ROOM_ERR"] = True - elif p == 'wall_contact_cms': - if not validate_wall_contact(data[p]): - err["WALL_ERR"] = True - elif p == 'touched_candle': - if not validate_touched_candle(data[p]): - err["CANDLE_ERR"] = True - return err - - -def validate_name(name, robot_name): - return name == robot_name - -def validate_actual_time_compare(time_j1, time_j2): - - time_j1 = time_j1.strip() - time_j2 = time_j2.strip() - - if time_j1.isdigit() and time_j2.isdigit(): - return float(time_j1) == float(time_j2) - return False - -# valide actual time -def validate_actual_time(time_s, level, failed): - # minimum and maximum time allowed for each level - - time_s = time_s.strip() - - min_123 = 0 # minimum for any level - max_1 = 180 # 3 minutes for level 1 - max_2 = 240 # 4 minutes for level 2 - max_3 = 300 # 5 minutes for level 3 - - # special AT values in case of a failed trial - fail_123 = 600 # trial failed (any level) - traversed_3 = 500 # failed but traversed from arean A to B (level 3) - found_baby_3 = 450 # failed but found baby (level 3) - picked_baby_3 = 400 # failed but picked up baby (level 3) - - # check if input string is a number - - - # convet to a float - try: - time = float(time_s) - except ValueError: - return False - - # validation for level 1 - if level == 1: - if failed and (time != fail_123): - return False; - elif (not failed) and (time < min_123 or time > max_1): - return False - - # validation for level 2 - elif level == 2: - if failed and (time != fail_123): - return False; - elif (not failed) and (time < min_123 or time > max_2): - return False - - # validation for level 3 - elif level == 3: - if (failed - and (time != fail_123) - and (time != traversed_3) - and (time != found_baby_3) - and (time != picked_baby_3)): - - return False; - - elif (not failed) and (time < min_123 or time > max_3): - return False - return True - - -# validate number of rooms -def validate_num_rooms(num_s, level): - if level not in [1,2]: - return True - - num_s = num_s.strip() - # minimum and maximum allowed values - min_123 = 0 - max_123 = 4 - - # check if input string is a number - if level in [1, 2]: - if not num_s.isdigit(): - return False - - return (int(num_s) >= min_123) and (int(num_s) <= max_123) - - return True - - -# validate wall contact distance -def validate_wall_contact(num_s): - - num_s = num_s.strip() - # minimum and maximum allowed values - min_123 = 0 - max_123 = 500 # length of arena - - # check if input string is a number - if not num_s.isdigit(): - return False - return (int(num_s) >= min_123) and (int(num_s) <= max_123) - -#validate touched_candle -def validate_touched_candle(num_s): - - num_s = num_s.strip() - - # Just check if it's digit for now - # minimum allowed value - min_123 = 0 - - # check if input string is a number - if not num_s.isdigit(): - return False - - return int(num_s) >= min_123 - -# creates a dictionary out of data entered for new run -def bind_params(input_data, id, level): - # create a dictionary out of input data - data = dict(input_data) - # create a dictionary of all parameters - args = {} - args['level'] = level - for p in RunParameters.ALL: - if p in RunParameters.BOOLEANS: - args[p] = bool(data.get(p)) - else: - # data dict is of form {key:value} - # where value is of form [u'str'] - args[p] = data[p][0] if data.get(p) else None - return args - - -# calculate and return score of current run -def get_score(robot, data): - return r.get_registry()['RUNS'].calculate_run_score( - robot['division'], - robot['level'], - data['run_disqualified'], - (to_float(data['seconds_to_put_out_candle_1']) + - to_float(data['seconds_to_put_out_candle_2'])) / 2.0, - data['non_air'], - data['furniture'], - data['arbitrary_start'], - data['return_trip'], - data['no_candle_circle'], - data['stopped_within_30'], - data['candle_detected'], - to_int(data['number_of_rooms_searched']), - data['kicked_dog'], - to_int(data['touched_candle']), - to_int(data['wall_contact_cms']), - data['ramp_used'], - data['baby_relocated'], - data['all_candles']) - - -def applied_factors(run_id, robot_id): +# calculate applied factors based on rules, needs more documentation +def get_applied_factors(run_id, robot_id): query = ("""SELECT * FROM runs where id = %(run_id)s;""") data = { 'run_id': run_id diff --git a/scoringsystem/blueprints/scoreboard.py b/scoringsystem/blueprints/scoreboard.py new file mode 100644 index 0000000..e565c8b --- /dev/null +++ b/scoringsystem/blueprints/scoreboard.py @@ -0,0 +1,215 @@ +# -*- coding: utf-8 -*- + +import requests +from flask import ( + Blueprint, current_app, session, request, + redirect, url_for, flash, render_template, + make_response +) +import StringIO +import csv +import registry as r +from constants import settings +from libraries.utilities.scoreboard import ScoreBoard +from libraries.utilities.score_calculator import ScoreCalculator + + +scoreboard = Blueprint('scoreboard', __name__) + + +@scoreboard.route('/scoreboard', methods=['GET', 'POST']) +def scoreboard_home(): + return render_template("scoreboard_home.html") + + +@scoreboard.route('/scoreboard/brd/', methods=['GET', 'POST']) +def scoreboard_brd(division): + robots = r.get_registry()['ROBOTS'].get_all_robots_division( + division + ) + + if not robots: + return render_template("not_found.html") + + # add additional parameters to be displayed on scoreboard + robots = ScoreBoard.add_scoreboard_params(robots) + + # sort based on name then total score + sorted_robots = sorted( + list(robots), + key=lambda k: k['name'] + ) + sorted_robots = sorted( + list(sorted_robots), + key=lambda k: k['TFS'] + ) + + return render_template( + "scoreboard_brd_gpmp.html", + robots=sorted_robots, + scoreboard_name=get_division_label(division) + ) + + +@scoreboard.route('/scoreboard/gpmp', methods=['GET', 'POST']) +def scoreboard_gpmp(): + robots = r.get_registry()['ROBOTS'].get_all_robots() + + if not robots: + return render_template("not_found.html") + + # add additional parameters to be displayed on scoreboard + robots = ScoreBoard.add_scoreboard_params(robots) + + # sort based on name then total score + sorted_robots = sorted( + list(robots), + key=lambda k: k['name'] + ) + sorted_robots = sorted( + list(sorted_robots), + key=lambda k: k['TFS'] + ) + + return render_template( + "scoreboard_brd_gpmp.html", + robots=sorted_robots, + scoreboard_name="Grand Performance" + ) + + +@scoreboard.route('/scoreboard/lisp/', methods=['GET', 'POST']) +def scoreboard_lisp(level): + if not level.isdigit(): + return render_template("not_found.html") + if int(level) not in [1, 2, 3]: + return render_template("not_found.html") + + robots = r.get_registry()['ROBOTS'].get_all_robots() + + if not robots: + return render_template("not_found.html") + + # add additional parameters to be displayed on scoreboard + robots = ScoreBoard.add_scoreboard_params(robots) + + # filter robots + filtered_robots = ScoreBoard.filter_robots_level( + robots, + int(level) + ) + + # key used for sorting + score_name = "LS" + level + + # sort based on name then this level's lowest score + sorted_robots = sorted( + list(filtered_robots), + key=lambda k: k['name'] + ) + sorted_robots = sorted( + list(sorted_robots), + key=lambda k: k[score_name] + ) + + return render_template( + "scoreboard_lisp.html", + robots=sorted_robots, + level=level, + score_name=score_name + ) + +@scoreboard.route('/prizes', methods=['GET', 'POST']) +def prize_winners(): + robots = r.get_registry()['ROBOTS'].get_all_robots() + + # calculate additional score parameters + robots = ScoreBoard.add_scoreboard_params(robots) + + # gpmp_winner[place] dict + gpmp_winners = ScoreBoard.get_gpmp_winners(robots) + + # lisp_winners[level][category][place] dict + lisp_winners = ScoreBoard.get_lisp_winners(robots) + + # brd_winners[division][category][place] dict + brd_winners = ScoreBoard.get_brd_winners(robots) + + return render_template( + "prizes.html", + gpmp_winners=gpmp_winners, + lisp_winners=lisp_winners, + brd_winners=brd_winners + ) + + +@scoreboard.route('/scoreboardcsv', methods=['GET', 'POST']) +def export_to_csv(): + divisions = ['junior', 'walking', 'high_school', 'senior'] + + all_robots = {} + + for division in divisions: + all_robots[division] = r.get_registry()['ROBOTS'].get_all_robots_division(division) + + si = StringIO.StringIO() + cw = csv.writer(si) + cw.writerow(['Rank', 'Division', 'Name', '# of Successful Runs', + 'Current Level', 'LS1', 'LS2', 'LS3', 'TFS']) + + for div in all_robots: + for robot in all_robots[div]: + runs = r.get_registry()['RUNS'].get_runs(robot['id']) + # get current best scores + best_scores, attempted_levels, total_score, num_successful = ( + ScoreCalculator.get_best_scores(runs) + ) + robot.update(best_scores) + robot['TFS'] = total_score + robot['num_successful'] = num_successful + # calculate lowes scores for each level and TFS, returns tuple + robot['completed'] = attempted_levels + + # sort based on name then total score + sorted_robots = sorted( + list(all_robots[div]), + key=lambda k: k['name'] + ) + sorted_robots = sorted( + list(sorted_robots), + key=lambda k: k['TFS'] + ) + + + for index, sorted_r in enumerate(sorted_robots, start=1): + cw.writerow([ + index, + sorted_r['division'], + sorted_r['name'], + sorted_r['num_successful'], + sorted_r['level'], + sorted_r['LS1'], + sorted_r['LS2'], + sorted_r['LS3'], + sorted_r['TFS'] + ]) + + cw.writerow('\n') + + output = make_response(si.getvalue()) + si.close() + output.headers["Content-Disposition"] = "attachment; filename=scoreboard.csv" + output.headers["Content-type"] = "text/csv" + return output + + +def get_division_label(division): + # page header label + if division == 'junior': + return "Junior Division" + elif division == 'walking': + return "Walking Division" + elif division == 'high_school': + return "High School Division" + else: + return "Senior Division" \ No newline at end of file diff --git a/scoringsystem/scoringapp.py b/scoringsystem/scoringapp.py index cc18988..9026916 100644 --- a/scoringsystem/scoringapp.py +++ b/scoringsystem/scoringapp.py @@ -39,6 +39,8 @@ app.register_blueprint(authentication, url_prefix='/auth') from blueprints.api import api app.register_blueprint(api, url_prefix='/api') +from blueprints.scoreboard import scoreboard +app.register_blueprint(scoreboard) def _get_etag(): """ diff --git a/static/img/_THUMBNAILS_/Cradle.Thumbnails/cradle1.png b/static/img/_THUMBNAILS_/Cradle.Thumbnails/cradle1.png index 018fc6c..6448b26 100644 Binary files a/static/img/_THUMBNAILS_/Cradle.Thumbnails/cradle1.png and b/static/img/_THUMBNAILS_/Cradle.Thumbnails/cradle1.png differ diff --git a/static/img/_THUMBNAILS_/Cradle.Thumbnails/cradle4.png b/static/img/_THUMBNAILS_/Cradle.Thumbnails/cradle4.png index 1f8da3c..ec831bb 100644 Binary files a/static/img/_THUMBNAILS_/Cradle.Thumbnails/cradle4.png and b/static/img/_THUMBNAILS_/Cradle.Thumbnails/cradle4.png differ diff --git a/static/img/_THUMBNAILS_/Cradle.Thumbnails/cradle5.png b/static/img/_THUMBNAILS_/Cradle.Thumbnails/cradle5.png index 9aa2924..a0838f9 100644 Binary files a/static/img/_THUMBNAILS_/Cradle.Thumbnails/cradle5.png and b/static/img/_THUMBNAILS_/Cradle.Thumbnails/cradle5.png differ diff --git a/static/img/_THUMBNAILS_/Cradle.Thumbnails/cradle6.png b/static/img/_THUMBNAILS_/Cradle.Thumbnails/cradle6.png index bafef2d..7ed28e2 100644 Binary files a/static/img/_THUMBNAILS_/Cradle.Thumbnails/cradle6.png and b/static/img/_THUMBNAILS_/Cradle.Thumbnails/cradle6.png differ diff --git a/static/img/_THUMBNAILS_/Cradle.Thumbnails/cradle7.png b/static/img/_THUMBNAILS_/Cradle.Thumbnails/cradle7.png index ee73d64..d2088e0 100644 Binary files a/static/img/_THUMBNAILS_/Cradle.Thumbnails/cradle7.png and b/static/img/_THUMBNAILS_/Cradle.Thumbnails/cradle7.png differ diff --git a/static/img/_THUMBNAILS_/Cradle.Thumbnails/cradle8.png b/static/img/_THUMBNAILS_/Cradle.Thumbnails/cradle8.png index a346446..c9420ef 100644 Binary files a/static/img/_THUMBNAILS_/Cradle.Thumbnails/cradle8.png and b/static/img/_THUMBNAILS_/Cradle.Thumbnails/cradle8.png differ diff --git a/static/img/_THUMBNAILS_/Furniture.Thumbnails/O1_merged.png b/static/img/_THUMBNAILS_/Furniture.Thumbnails/O1_merged.png new file mode 100644 index 0000000..873b0ad Binary files /dev/null and b/static/img/_THUMBNAILS_/Furniture.Thumbnails/O1_merged.png differ diff --git a/static/img/_THUMBNAILS_/Furniture.Thumbnails/O2_merged.png b/static/img/_THUMBNAILS_/Furniture.Thumbnails/O2_merged.png new file mode 100644 index 0000000..855ad04 Binary files /dev/null and b/static/img/_THUMBNAILS_/Furniture.Thumbnails/O2_merged.png differ diff --git a/static/img/_THUMBNAILS_/Furniture.Thumbnails/O3_merged.png b/static/img/_THUMBNAILS_/Furniture.Thumbnails/O3_merged.png new file mode 100644 index 0000000..a654fd2 Binary files /dev/null and b/static/img/_THUMBNAILS_/Furniture.Thumbnails/O3_merged.png differ diff --git a/static/img/_THUMBNAILS_/Furniture.Thumbnails/O4_merged.png b/static/img/_THUMBNAILS_/Furniture.Thumbnails/O4_merged.png new file mode 100644 index 0000000..e496fe1 Binary files /dev/null and b/static/img/_THUMBNAILS_/Furniture.Thumbnails/O4_merged.png differ diff --git a/static/img/_THUMBNAILS_/Furniture.Thumbnails/O5_merged.png b/static/img/_THUMBNAILS_/Furniture.Thumbnails/O5_merged.png new file mode 100644 index 0000000..e69bfd7 Binary files /dev/null and b/static/img/_THUMBNAILS_/Furniture.Thumbnails/O5_merged.png differ diff --git a/static/img/_THUMBNAILS_/Furniture.Thumbnails/O6_merged.png b/static/img/_THUMBNAILS_/Furniture.Thumbnails/O6_merged.png new file mode 100644 index 0000000..a622538 Binary files /dev/null and b/static/img/_THUMBNAILS_/Furniture.Thumbnails/O6_merged.png differ diff --git a/static/img/_THUMBNAILS_/Furniture.Thumbnails/O7_merged.png b/static/img/_THUMBNAILS_/Furniture.Thumbnails/O7_merged.png new file mode 100644 index 0000000..76d1bb7 Binary files /dev/null and b/static/img/_THUMBNAILS_/Furniture.Thumbnails/O7_merged.png differ diff --git a/static/img/_THUMBNAILS_/Furniture.Thumbnails/O8_merged.png b/static/img/_THUMBNAILS_/Furniture.Thumbnails/O8_merged.png new file mode 100644 index 0000000..e424a8f Binary files /dev/null and b/static/img/_THUMBNAILS_/Furniture.Thumbnails/O8_merged.png differ diff --git a/static/js/randomize.js b/static/js/randomize.js index 3dc3ff5..8d30f33 100644 --- a/static/js/randomize.js +++ b/static/js/randomize.js @@ -75,14 +75,14 @@ function start_loc_img(orient_id) { function furniture_img(id) { var dict = { - 'O1': '/static/img/_THUMBNAILS_/Furniture.Thumbnails/O1.png', - 'O2': '/static/img/_THUMBNAILS_/Furniture.Thumbnails/O2.png', - 'O3': '/static/img/_THUMBNAILS_/Furniture.Thumbnails/O3.png', - 'O4': '/static/img/_THUMBNAILS_/Furniture.Thumbnails/O4.png', - 'O5': '/static/img/_THUMBNAILS_/Furniture.Thumbnails/O5.png', - 'O6': '/static/img/_THUMBNAILS_/Furniture.Thumbnails/O6.png', - 'O7': '/static/img/_THUMBNAILS_/Furniture.Thumbnails/O7.png', - 'O8': '/static/img/_THUMBNAILS_/Furniture.Thumbnails/O8.png' + 'O1': '/static/img/_THUMBNAILS_/Furniture.Thumbnails/O1_merged.png', + 'O2': '/static/img/_THUMBNAILS_/Furniture.Thumbnails/O2_merged.png', + 'O3': '/static/img/_THUMBNAILS_/Furniture.Thumbnails/O3_merged.png', + 'O4': '/static/img/_THUMBNAILS_/Furniture.Thumbnails/O4_merged.png', + 'O5': '/static/img/_THUMBNAILS_/Furniture.Thumbnails/O5_merged.png', + 'O6': '/static/img/_THUMBNAILS_/Furniture.Thumbnails/O6_merged.png', + 'O7': '/static/img/_THUMBNAILS_/Furniture.Thumbnails/O7_merged.png', + 'O8': '/static/img/_THUMBNAILS_/Furniture.Thumbnails/O8_merged.png' } return dict[id]; diff --git a/templates/nav_bar.html b/templates/nav_bar.html index 61b33a6..b04b1d7 100644 --- a/templates/nav_bar.html +++ b/templates/nav_bar.html @@ -3,8 +3,8 @@