From 30b72071c0f0d245886c3af2edbf329620fa1a29 Mon Sep 17 00:00:00 2001 From: Jerry Date: Tue, 18 Feb 2025 19:06:23 -0500 Subject: [PATCH] Everything except compare page fixed --- app/app.py | 11 + app/scout/routes.py | 360 ++++++++++++++------------ app/scout/scouting_utils.py | 414 +++++++++++++++++------------- app/static/icons/icon-128x128.png | Bin 0 -> 5531 bytes app/static/icons/icon-144x144.png | Bin 0 -> 6589 bytes app/static/icons/icon-152x152.png | Bin 0 -> 7123 bytes app/static/icons/icon-192x192.png | Bin 0 -> 9603 bytes app/static/icons/icon-384x384.png | Bin 0 -> 26379 bytes app/static/icons/icon-512x512.png | Bin 0 -> 42816 bytes app/static/icons/icon-72x72.png | Bin 0 -> 2456 bytes app/static/icons/icon-96x96.png | Bin 0 -> 3692 bytes app/static/js/compare.js | 136 ++++++---- app/static/js/scout.add.js | 24 +- app/static/js/search.js | 154 ++++++++--- app/static/js/service-worker.js | 53 ++++ app/static/manifest.json | 52 ++++ app/templates/base.html | 18 ++ app/templates/compare.html | 28 +- app/templates/search.html | 15 +- 19 files changed, 817 insertions(+), 448 deletions(-) create mode 100644 app/static/icons/icon-128x128.png create mode 100644 app/static/icons/icon-144x144.png create mode 100644 app/static/icons/icon-152x152.png create mode 100644 app/static/icons/icon-192x192.png create mode 100644 app/static/icons/icon-384x384.png create mode 100644 app/static/icons/icon-512x512.png create mode 100644 app/static/icons/icon-72x72.png create mode 100644 app/static/icons/icon-96x96.png create mode 100644 app/static/js/service-worker.js create mode 100644 app/static/manifest.json diff --git a/app/app.py b/app/app.py index 4dc1e1f..ab01d2b 100644 --- a/app/app.py +++ b/app/app.py @@ -95,6 +95,17 @@ def handle_exception(e): def rate_limit_error(e): return render_template("429.html"), 429 + @app.route('/static/manifest.json') + def serve_manifest(): + return send_from_directory(app.static_folder, 'manifest.json') + + @app.route('/static/js/service-worker.js') + def serve_service_worker(): + response = make_response(send_from_directory(app.static_folder, 'js/service-worker.js')) + response.headers['Service-Worker-Allowed'] = '/' + response.headers['Cache-Control'] = 'no-cache' + return response + return app diff --git a/app/scout/routes.py b/app/scout/routes.py index 3479e55..b213cae 100644 --- a/app/scout/routes.py +++ b/app/scout/routes.py @@ -35,9 +35,13 @@ def add(): return render_template("scouting/add.html") data = request.get_json() if request.is_json else request.form.to_dict() - if "auto_path" in data and isinstance(data["auto_path"], str): + if "auto_path" in data: try: - data["auto_path"] = json.loads(data["auto_path"]) + if isinstance(data["auto_path"], str): + if data["auto_path"].strip(): + data["auto_path"] = json.loads(data["auto_path"]) + else: + data["auto_path"] = [] except json.JSONDecodeError: flash("Invalid path coordinates format", "error") return redirect(url_for("scouting.home")) @@ -58,7 +62,10 @@ def add(): @login_required def home(): try: - team_data = scouting_manager.get_all_scouting_data() + team_data = scouting_manager.get_all_scouting_data( + current_user.teamNumber, + current_user.get_id() + ) return render_template("scouting/list.html", team_data=team_data) except Exception as e: current_app.logger.error(f"Error fetching scouting data: {str(e)}", exc_info=True) @@ -168,47 +175,24 @@ def compare_teams(): teams_data = {} for team_num in teams: try: - # Get team stats from database + # Add team access filter to pipeline pipeline = [ {"$match": {"team_number": int(team_num)}}, - {"$group": { - "_id": "$team_number", - "matches_played": {"$sum": 1}, - "auto_coral_level1": {"$avg": {"$ifNull": ["$auto_coral_level1", 0]}}, - "auto_coral_level2": {"$avg": {"$ifNull": ["$auto_coral_level2", 0]}}, - "auto_coral_level3": {"$avg": {"$ifNull": ["$auto_coral_level3", 0]}}, - "auto_coral_level4": {"$avg": {"$ifNull": ["$auto_coral_level4", 0]}}, - "auto_algae_net": {"$avg": {"$ifNull": ["$auto_algae_net", 0]}}, - "auto_algae_processor": {"$avg": {"$ifNull": ["$auto_algae_processor", 0]}}, - "teleop_coral_level1": {"$avg": {"$ifNull": ["$teleop_coral_level1", 0]}}, - "teleop_coral_level2": {"$avg": {"$ifNull": ["$teleop_coral_level2", 0]}}, - "teleop_coral_level3": {"$avg": {"$ifNull": ["$teleop_coral_level3", 0]}}, - "teleop_coral_level4": {"$avg": {"$ifNull": ["$teleop_coral_level4", 0]}}, - "teleop_algae_net": {"$avg": {"$ifNull": ["$teleop_algae_net", 0]}}, - "teleop_algae_processor": {"$avg": {"$ifNull": ["$teleop_algae_processor", 0]}}, - "auto_path": {"$last": "$auto_path"}, - "climb_success_rate": { - "$avg": {"$cond": [{"$eq": ["$climb_success", True]}, 100, 0]} - }, - "preferred_climb_type": {"$last": "$climb_type"}, - "total_coral": {"$sum": { - "$add": [ - "$auto_coral_level1", "$auto_coral_level2", - "$auto_coral_level3", "$auto_coral_level4", - "$teleop_coral_level1", "$teleop_coral_level2", - "$teleop_coral_level3", "$teleop_coral_level4" - ] - }}, - "total_algae": {"$sum": { - "$add": [ - "$auto_algae_net", "$auto_algae_processor", - "$teleop_algae_net", "$teleop_algae_processor" - ] - }}, - "defense_rating": {"$avg": "$defense_rating"}, - "successful_climbs": { - "$sum": {"$cond": ["$climb_success", 1, 0]} - }, + {"$lookup": { + "from": "users", + "localField": "scouter_id", + "foreignField": "_id", + "as": "scouter" + }}, + {"$unwind": "$scouter"}, + # Fix team access filter + {"$match": { + "$or": [ + {"scouter.teamNumber": current_user.teamNumber}, + {"scouter._id": ObjectId(current_user.get_id())} + ] if current_user.teamNumber else { + "scouter._id": ObjectId(current_user.get_id()) + } }} ] @@ -309,7 +293,6 @@ def compare_teams(): } except Exception as team_error: - # print(f"Error processing team {team_num}: {str(team_error)}") teams_data[team_num] = { "team_number": int(team_num), "error": str(team_error) @@ -318,7 +301,6 @@ def compare_teams(): return json.loads(json_util.dumps(teams_data)) except Exception as e: - # print(f"Error in compare_teams: {str(e)}") return jsonify({"error": "An error occurred while comparing teams"}), 500 @scouting_bp.route("/search") @@ -369,15 +351,20 @@ async def search_teams(): # Fetch scouting data from our database pipeline = [ {"$match": {"team_number": team_number}}, - { - "$lookup": { - "from": "users", - "localField": "scouter_id", - "foreignField": "_id", - "as": "scouter" - } - }, - {"$unwind": {"path": "$scouter", "preserveNullAndEmptyArrays": True}}, + {"$lookup": { + "from": "users", + "localField": "scouter_id", + "foreignField": "_id", + "as": "scouter" + }}, + {"$unwind": {"path": "$scouter"}}, + # Add team access filter + {"$match": { + "$or": [ + {"scouter.teamNumber": current_user.teamNumber} if current_user.teamNumber else {"scouter._id": ObjectId(current_user.get_id())}, + {"scouter._id": ObjectId(current_user.get_id())} + ] + }}, {"$sort": {"event_code": 1, "match_number": 1}}, { "$project": { @@ -597,36 +584,49 @@ def matches(): try: pipeline = [ { - "$group": { - "_id": { - "event": "$event_code", - "match": "$match_number" - }, - "teams": { - "$push": { - "number": "$team_number", - "alliance": "$alliance", - # Auto period - "auto_coral_level1": {"$ifNull": ["$auto_coral_level1", 0]}, - "auto_coral_level2": {"$ifNull": ["$auto_coral_level2", 0]}, - "auto_coral_level3": {"$ifNull": ["$auto_coral_level3", 0]}, - "auto_coral_level4": {"$ifNull": ["$auto_coral_level4", 0]}, - "auto_algae_net": {"$ifNull": ["$auto_algae_net", 0]}, - "auto_algae_processor": {"$ifNull": ["$auto_algae_processor", 0]}, - # Teleop period - "teleop_coral_level1": {"$ifNull": ["$teleop_coral_level1", 0]}, - "teleop_coral_level2": {"$ifNull": ["$teleop_coral_level2", 0]}, - "teleop_coral_level3": {"$ifNull": ["$teleop_coral_level3", 0]}, - "teleop_coral_level4": {"$ifNull": ["$teleop_coral_level4", 0]}, - "teleop_algae_net": {"$ifNull": ["$teleop_algae_net", 0]}, - "teleop_algae_processor": {"$ifNull": ["$teleop_algae_processor", 0]}, - "climb_type": "$climb_type", - "climb_success": "$climb_success" - } - }, + "$lookup": { + "from": "users", + "localField": "scouter_id", + "foreignField": "_id", + "as": "scouter" } }, - {"$sort": {"_id.event": 1, "_id.match": 1}} + {"$unwind": "$scouter"}, + # Add team access filter + {"$match": { + "$or": [ + {"scouter.teamNumber": current_user.teamNumber} if current_user.teamNumber else {"scouter._id": ObjectId(current_user.get_id())}, + {"scouter._id": ObjectId(current_user.get_id())} + ] + }}, + {"$group": { + "_id": { + "event": "$event_code", + "match": "$match_number" + }, + "teams": { + "$push": { + "number": "$team_number", + "alliance": "$alliance", + # Auto period + "auto_coral_level1": {"$ifNull": ["$auto_coral_level1", 0]}, + "auto_coral_level2": {"$ifNull": ["$auto_coral_level2", 0]}, + "auto_coral_level3": {"$ifNull": ["$auto_coral_level3", 0]}, + "auto_coral_level4": {"$ifNull": ["$auto_coral_level4", 0]}, + "auto_algae_net": {"$ifNull": ["$auto_algae_net", 0]}, + "auto_algae_processor": {"$ifNull": ["$auto_algae_processor", 0]}, + # Teleop period + "teleop_coral_level1": {"$ifNull": ["$teleop_coral_level1", 0]}, + "teleop_coral_level2": {"$ifNull": ["$teleop_coral_level2", 0]}, + "teleop_coral_level3": {"$ifNull": ["$teleop_coral_level3", 0]}, + "teleop_coral_level4": {"$ifNull": ["$teleop_coral_level4", 0]}, + "teleop_algae_net": {"$ifNull": ["$teleop_algae_net", 0]}, + "teleop_algae_processor": {"$ifNull": ["$teleop_algae_processor", 0]}, + "climb_type": "$climb_type", + "climb_success": "$climb_success" + } + }, + }} ] match_data = list(scouting_manager.db.team_data.aggregate(pipeline)) @@ -698,7 +698,7 @@ def matches(): }) return render_template("scouting/matches.html", matches=matches) - + except Exception as e: current_app.logger.error(f"Error fetching matches: {str(e)}", exc_info=True) flash("An internal error has occurred.", "error") @@ -710,22 +710,41 @@ def check_team(): team_number = request.args.get('team') event_code = request.args.get('event') match_number = request.args.get('match') - current_id = request.args.get('current_id') # ID of the entry being edited + current_id = request.args.get('current_id') try: - query = { - "team_number": int(team_number), - "event_code": event_code, - "match_number": int(match_number) - } + # Get current user's team number + current_user_team = current_user.teamNumber + + pipeline = [ + { + "$match": { + "team_number": int(team_number), + "event_code": event_code, + "match_number": int(match_number) + } + }, + { + "$lookup": { + "from": "users", + "localField": "scouter_id", + "foreignField": "_id", + "as": "scouter" + } + }, + {"$unwind": "$scouter"}, + ] - # If editing, exclude the current entry from the check if current_id: - query["_id"] = {"$ne": ObjectId(current_id)} + pipeline[0]["$match"]["_id"] = {"$ne": ObjectId(current_id)} - existing = scouting_manager.db.team_data.find_one(query) + existing = list(scouting_manager.db.team_data.aggregate(pipeline)) - return jsonify({"exists": existing is not None}) + # Check if any existing entry is from the same team + exists = any(entry.get("scouter", {}).get("teamNumber") == current_user_team + for entry in existing) + + return jsonify({"exists": exists}) except Exception as e: current_app.logger.error(f"Error checking team data: {str(e)}", exc_info=True) return jsonify({"error": "An internal error has occurred."}), 500 @@ -735,7 +754,11 @@ def check_team(): @limiter.limit("30 per minute") def pit_scouting(): try: - pit_data_list = list(scouting_manager.get_all_pit_scouting()) + # Update to use filtered pit scouting data + pit_data_list = list(scouting_manager.get_all_pit_scouting( + current_user.teamNumber, + current_user.get_id() + )) return render_template("scouting/pit-scouting.html", pit_data=pit_data_list) except Exception as e: current_app.logger.error(f"Error fetching pit scouting data: {str(e)}", exc_info=True) @@ -747,82 +770,87 @@ def pit_scouting(): @limiter.limit("10 per minute") def pit_scouting_add(): if request.method == "POST": - # Process form data - pit_data = { - "team_number": int(request.form.get("team_number")), - "scouter_id": current_user.id, - - # Drive base information - "drive_type": { - "swerve": "swerve" in request.form.getlist("drive_type"), - "tank": "tank" in request.form.getlist("drive_type"), - "other": request.form.get("drive_type_other", "") - }, - "swerve_modules": request.form.get("swerve_modules", ""), - - # Motor details - "motor_details": { - "falcons": "falcons" in request.form.getlist("motors"), - "neos": "neos" in request.form.getlist("motors"), - "krakens": "krakens" in request.form.getlist("motors"), - "vortex": "vortex" in request.form.getlist("motors"), - "other": request.form.get("motors_other", "") - }, - "motor_count": int(request.form.get("motor_count", 0)), - - # Dimensions - "dimensions": { - "length": float(request.form.get("length", 0)), - "width": float(request.form.get("width", 0)), - "height": float(request.form.get("height", 0)) - }, - - # Mechanisms - "mechanisms": { - "coral_scoring": { - "enabled": request.form.get("coral_scoring_enabled") == "true", - "notes": request.form.get("coral_scoring_notes", "") if request.form.get("coral_scoring_enabled") == "true" else "" + try: + # Process form data + pit_data = { + "team_number": int(request.form.get("team_number")), + "scouter_id": current_user.id, + + # Drive base information + "drive_type": { + "swerve": "swerve" in request.form.getlist("drive_type"), + "tank": "tank" in request.form.getlist("drive_type"), + "other": request.form.get("drive_type_other", "") + }, + "swerve_modules": request.form.get("swerve_modules", ""), + + # Motor details + "motor_details": { + "falcons": "falcons" in request.form.getlist("motors"), + "neos": "neos" in request.form.getlist("motors"), + "krakens": "krakens" in request.form.getlist("motors"), + "vortex": "vortex" in request.form.getlist("motors"), + "other": request.form.get("motors_other", "") }, - "algae_scoring": { - "enabled": request.form.get("algae_scoring_enabled") == "true", - "notes": request.form.get("algae_scoring_notes", "") if request.form.get("algae_scoring_enabled") == "true" else "" + "motor_count": int(request.form.get("motor_count", 0)), + + # Dimensions + "dimensions": { + "length": float(request.form.get("length", 0)), + "width": float(request.form.get("width", 0)), + "height": float(request.form.get("height", 0)) }, - "climber": { - "has_climber": "has_climber" in request.form, - "type_climber": request.form.get("climber_type", ""), - "notes": request.form.get("climber_notes", "") - } - }, - - # Programming and Autonomous - "programming_language": request.form.get("programming_language", ""), - "autonomous_capabilities": { - "has_auto": request.form.get("has_auto") == "true", - "num_routes": int(request.form.get("auto_routes", 0)) if request.form.get("has_auto") == "true" else 0, - "preferred_start": request.form.get("auto_preferred_start", "") if request.form.get("has_auto") == "true" else "", - "notes": request.form.get("auto_notes", "") if request.form.get("has_auto") == "true" else "" - }, - - # Driver Experience - "driver_experience": { - "years": int(request.form.get("driver_years", 0)), - "notes": request.form.get("driver_notes", "") - }, - - # General Notes - "notes": request.form.get("notes", ""), - - # Timestamps - "created_at": datetime.now(timezone.utc), - "updated_at": datetime.now(timezone.utc) - } - - # Add to database - if scouting_manager.add_pit_scouting(pit_data): - flash("Pit scouting data added successfully!", "success") + + # Mechanisms + "mechanisms": { + "coral_scoring": { + "enabled": request.form.get("coral_scoring_enabled") == "true", + "notes": request.form.get("coral_scoring_notes", "") if request.form.get("coral_scoring_enabled") == "true" else "" + }, + "algae_scoring": { + "enabled": request.form.get("algae_scoring_enabled") == "true", + "notes": request.form.get("algae_scoring_notes", "") if request.form.get("algae_scoring_enabled") == "true" else "" + }, + "climber": { + "has_climber": "has_climber" in request.form, + "type_climber": request.form.get("climber_type", ""), + "notes": request.form.get("climber_notes", "") + } + }, + + # Programming and Autonomous + "programming_language": request.form.get("programming_language", ""), + "autonomous_capabilities": { + "has_auto": request.form.get("has_auto") == "true", + "num_routes": int(request.form.get("auto_routes", 0)) if request.form.get("has_auto") == "true" else 0, + "preferred_start": request.form.get("auto_preferred_start", "") if request.form.get("has_auto") == "true" else "", + "notes": request.form.get("auto_notes", "") if request.form.get("has_auto") == "true" else "" + }, + + # Driver Experience + "driver_experience": { + "years": int(request.form.get("driver_years", 0)), + "notes": request.form.get("driver_notes", "") + }, + + # General Notes + "notes": request.form.get("notes", ""), + + # Timestamps + "created_at": datetime.now(timezone.utc), + "updated_at": datetime.now(timezone.utc) + } + + # Add to database + if scouting_manager.add_pit_scouting(pit_data): + flash("Pit scouting data added successfully!", "success") + return redirect(url_for("scouting.pit_scouting")) + else: + flash("Error adding pit scouting data. Please try again.", "error") + except Exception as e: + flash("An error occurred while adding pit scouting data.", "error") + current_app.logger.error(f"Error adding pit scouting data: {str(e)}", exc_info=True) return redirect(url_for("scouting.pit_scouting")) - else: - flash("Error adding pit scouting data. Please try again.", "error") return render_template("scouting/pit-scouting-add.html") diff --git a/app/scout/scouting_utils.py b/app/scout/scouting_utils.py index 4d08ae0..edb4c79 100644 --- a/app/scout/scouting_utils.py +++ b/app/scout/scouting_utils.py @@ -67,14 +67,39 @@ def add_scouting_data(self, data, scouter_id): if team_number <= 0: return False, "Invalid team number" - if existing_entry := self.db.team_data.find_one( + # Lookup to check if this team is already scouted in this match by someone from the same team + pipeline = [ + { + "$match": { + "event_code": data["event_code"], + "match_number": int(data["match_number"]), + "team_number": team_number + } + }, { - "event_code": data["event_code"], - "match_number": int(data["match_number"]), - "team_number": team_number, + "$lookup": { + "from": "users", + "localField": "scouter_id", + "foreignField": "_id", + "as": "scouter" + } + }, + {"$unwind": "$scouter"}, + { + "$lookup": { + "from": "users", + "localField": "scouter.teamNumber", + "foreignField": "teamNumber", + "as": "team_scouters" + } } - ): - return False, f"Team {team_number} already exists in match {data['match_number']} for event {data['event_code']}" + ] + + if existing_entries := list(self.db.team_data.aggregate(pipeline)): + current_user = self.db.users.find_one({"_id": ObjectId(scouter_id)}) + for entry in existing_entries: + if entry.get("scouter", {}).get("teamNumber") == current_user.get("teamNumber"): + return False, f"Team {team_number} has already been scouted by your team in match {data['match_number']}" # Get existing match data to validate alliance sizes and calculate scores match_data = list(self.db.team_data.find({ @@ -96,42 +121,42 @@ def add_scouting_data(self, data, scouter_id): "event_code": data["event_code"], "match_number": int(data["match_number"]), "alliance": alliance, - + # Auto Coral scoring "auto_coral_level1": int(data.get("auto_coral_level1", 0)), "auto_coral_level2": int(data.get("auto_coral_level2", 0)), "auto_coral_level3": int(data.get("auto_coral_level3", 0)), "auto_coral_level4": int(data.get("auto_coral_level4", 0)), - + # Teleop Coral scoring "teleop_coral_level1": int(data.get("teleop_coral_level1", 0)), "teleop_coral_level2": int(data.get("teleop_coral_level2", 0)), "teleop_coral_level3": int(data.get("teleop_coral_level3", 0)), "teleop_coral_level4": int(data.get("teleop_coral_level4", 0)), - + # Auto Algae scoring "auto_algae_net": int(data.get("auto_algae_net", 0)), "auto_algae_processor": int(data.get("auto_algae_processor", 0)), - + # Teleop Algae scoring "teleop_algae_net": int(data.get("teleop_algae_net", 0)), "teleop_algae_processor": int(data.get("teleop_algae_processor", 0)), - + # Climb "climb_type": data.get("climb_type", ""), "climb_success": bool(data.get("climb_success", False)), - + # Defense "defense_rating": int(data.get("defense_rating", 1)), "defense_notes": data.get("defense_notes", ""), - + # Auto "auto_path": data.get("auto_path", ""), "auto_notes": data.get("auto_notes", ""), - + # Notes "notes": data.get("notes", ""), - + # Metadata "scouter_id": ObjectId(scouter_id), "created_at": datetime.now(timezone.utc), @@ -145,9 +170,10 @@ def add_scouting_data(self, data, scouter_id): return False, "An internal error has occurred." @with_mongodb_retry(retries=3, delay=2) - def get_all_scouting_data(self): - """Get all scouting data with user information""" + def get_all_scouting_data(self, user_team_number=None, user_id=None): + """Get all scouting data with user information, filtered by team access""" try: + # Base pipeline for user lookup pipeline = [ { "$lookup": { @@ -158,38 +184,60 @@ def get_all_scouting_data(self): } }, {"$unwind": "$scouter"}, - { - "$project": { - "_id": 1, - "team_number": 1, - "match_number": 1, - "event_code": 1, - "auto_coral_level1": 1, - "auto_coral_level2": 1, - "auto_coral_level3": 1, - "auto_coral_level4": 1, - "teleop_coral_level1": 1, - "teleop_coral_level2": 1, - "teleop_coral_level3": 1, - "teleop_coral_level4": 1, - "auto_algae_net": 1, - "auto_algae_processor": 1, - "teleop_algae_net": 1, - "teleop_algae_processor": 1, - "climb_type": 1, - "climb_success": 1, - "defense_rating": 1, - "defense_notes": 1, - "auto_path": 1, - "auto_notes": 1, - "notes": 1, - "alliance": 1, - "scouter_id": 1, - "scouter_name": "$scouter.username", - "scouter_team": "$scouter.teamNumber" + ] + + # Add match stage for filtering based on team number or user ID + if user_team_number: + # If user has a team number, show data from their team and their own data + pipeline.append({ + "$match": { + "$or": [ + {"scouter.teamNumber": user_team_number}, + {"scouter._id": ObjectId(user_id)} + ] + } + }) + else: + # If user has no team, only show their own data + pipeline.append({ + "$match": { + "scouter._id": ObjectId(user_id) } + }) + + # Project the needed fields + pipeline.append({ + "$project": { + "_id": 1, + "team_number": 1, + "match_number": 1, + "event_code": 1, + "auto_coral_level1": 1, + "auto_coral_level2": 1, + "auto_coral_level3": 1, + "auto_coral_level4": 1, + "teleop_coral_level1": 1, + "teleop_coral_level2": 1, + "teleop_coral_level3": 1, + "teleop_coral_level4": 1, + "auto_algae_net": 1, + "auto_algae_processor": 1, + "teleop_algae_net": 1, + "teleop_algae_processor": 1, + "climb_type": 1, + "climb_success": 1, + "defense_rating": 1, + "defense_notes": 1, + "auto_path": 1, + "auto_notes": 1, + "notes": 1, + "alliance": 1, + "scouter_id": 1, + "scouter_name": "$scouter.username", + "scouter_team": "$scouter.teamNumber", + "device_type": 1 } - ] + }) team_data = list(self.db.team_data.aggregate(pipeline)) return team_data @@ -230,22 +278,67 @@ def update_team_data(self, team_id, data, scouter_id): """Update existing team data if user is the owner""" self.ensure_connected() try: - # First verify ownership + # First verify ownership and get current data existing_data = self.db.team_data.find_one( - {"_id": ObjectId(team_id), "scouter_id": ObjectId(scouter_id)} + {"_id": ObjectId(team_id)} ) if not existing_data: - logger.warning( - f"Update attempted by non-owner scouter_id: {scouter_id}" - ) + logger.warning(f"Data not found for team_id: {team_id}") + return False + + # Check if the team is already scouted by someone else from the same team + pipeline = [ + { + "$match": { + "event_code": data["event_code"], + "match_number": int(data["match_number"]), + "team_number": int(data["team_number"]), + "_id": {"$ne": ObjectId(team_id)} # Exclude current entry + } + }, + { + "$lookup": { + "from": "users", + "localField": "scouter_id", + "foreignField": "_id", + "as": "scouter" + } + }, + {"$unwind": "$scouter"} + ] + + existing_entries = list(self.db.team_data.aggregate(pipeline)) + current_user = self.db.users.find_one({"_id": ObjectId(scouter_id)}) + + for entry in existing_entries: + if entry.get("scouter", {}).get("teamNumber") == current_user.get("teamNumber"): + logger.warning( + f"Update attempted for team {data['team_number']} in match {data['match_number']} " + f"which is already scouted by team {current_user.get('teamNumber')}" + ) + return False + + # Get match data to validate alliance sizes + match_data = list(self.db.team_data.find({ + "event_code": data["event_code"], + "match_number": int(data["match_number"]), + "_id": {"$ne": ObjectId(team_id)} # Exclude current entry + })) + + # Count teams per alliance + alliance = data.get("alliance", "red") + red_teams = [m for m in match_data if m["alliance"] == "red"] + blue_teams = [m for m in match_data if m["alliance"] == "blue"] + + if ((alliance == "red" and len(red_teams) >= 3) or (alliance == "blue" and len(blue_teams) >= 3)) and existing_data.get("alliance") != alliance: return False updated_data = { "team_number": int(data["team_number"]), "event_code": data["event_code"], "match_number": int(data["match_number"]), - "alliance": data.get("alliance", "red"), + "alliance": alliance, # Coral scoring "coral_level1": int(data.get("coral_level1", 0)), @@ -295,7 +388,7 @@ def update_team_data(self, team_id, data, scouter_id): } result = self.db.team_data.update_one( - {"_id": ObjectId(team_id), "scouter_id": ObjectId(scouter_id)}, + {"_id": ObjectId(team_id)}, {"$set": updated_data}, ) return result.modified_count > 0 @@ -434,87 +527,41 @@ def get_auto_paths(self, team_number): @with_mongodb_retry(retries=3, delay=2) def add_pit_scouting(self, data): - """Add new pit scouting data""" + """Add new pit scouting data with team validation""" self.ensure_connected() try: - if existing := self.db.pit_scouting.find_one( - { - "team_number": data["team_number"], - "scouter_id": data["scouter_id"], - } - ): - return False - - # Ensure required fields are present - pit_data = { - "team_number": int(data["team_number"]), - "scouter_id": ObjectId(data["scouter_id"]), - - # Drive base information - "drive_type": { - "swerve": data.get("drive_type", {}).get("swerve", False), - "tank": data.get("drive_type", {}).get("tank", False), - "other": data.get("drive_type", {}).get("other", "") - }, - "swerve_modules": data.get("swerve_modules", ""), - - # Motor details - "motor_details": { - "falcons": data.get("motor_details", {}).get("falcons", False), - "neos": data.get("motor_details", {}).get("neos", False), - "krakens": data.get("motor_details", {}).get("krakens", False), - "vortex": data.get("motor_details", {}).get("vortex", False), - "other": data.get("motor_details", {}).get("other", "") - }, - "motor_count": data.get("motor_count", 0), - - # Dimensions - "dimensions": { - "length": data.get("dimensions", {}).get("length", 0), - "width": data.get("dimensions", {}).get("width", 0), - "height": data.get("dimensions", {}).get("height", 0), - }, + team_number = int(data["team_number"]) + scouter_id = data["scouter_id"] - # Mechanisms - "mechanisms": { - "coral_scoring": { - "notes": data.get("mechanisms", {}).get("coral_scoring", {}).get("notes", "") - }, - "algae_scoring": { - "notes": data.get("mechanisms", {}).get("algae_scoring", {}).get("notes", "") - }, - "climber": { - "has_climber": data.get("mechanisms", {}).get("climber", {}).get("has_climber", False), - "type_climber": data.get("mechanisms", {}).get("climber", {}).get("type_climber", ""), - "notes": data.get("mechanisms", {}).get("climber", {}).get("notes", "") + # Check if this team is already scouted by someone from the same team + pipeline = [ + { + "$match": { + "team_number": team_number } }, - - # Programming and Autonomous - "programming_language": data.get("programming_language", ""), - "autonomous_capabilities": { - "has_auto": data.get("autonomous_capabilities", {}).get("has_auto", False), - "num_routes": data.get("autonomous_capabilities", {}).get("num_routes", 0), - "preferred_start": data.get("autonomous_capabilities", {}).get("preferred_start", ""), - "notes": data.get("autonomous_capabilities", {}).get("notes", "") - }, - - # Driver Experience - "driver_experience": { - "years": data.get("driver_experience", {}).get("years", 0), - "notes": data.get("driver_experience", {}).get("notes", "") + { + "$lookup": { + "from": "users", + "localField": "scouter_id", + "foreignField": "_id", + "as": "scouter" + } }, + {"$unwind": "$scouter"} + ] + + existing_entries = list(self.db.pit_scouting.aggregate(pipeline)) + current_user = self.db.users.find_one({"_id": ObjectId(scouter_id)}) + + for entry in existing_entries: + if entry.get("scouter", {}).get("teamNumber") == current_user.get("teamNumber"): + logger.warning(f"Team {team_number} has already been pit scouted by team {current_user.get('teamNumber')}") + return False - # Analysis - "notes": data.get("notes", ""), - - # Metadata - "created_at": datetime.now(timezone.utc), - "updated_at": datetime.now(timezone.utc) - } + result = self.db.pit_scouting.insert_one(data) + return bool(result.inserted_id) - result = self.db.pit_scouting.insert_one(pit_data) - return result.inserted_id is not None except Exception as e: logger.error(f"Error adding pit scouting data: {str(e)}") return False @@ -572,9 +619,8 @@ def get_pit_scouting(self, team_number): return None @with_mongodb_retry(retries=3, delay=2) - def get_all_pit_scouting(self): - """Get all pit scouting data with scouter information""" - self.ensure_connected() + def get_all_pit_scouting(self, user_team_number=None, user_id=None): + """Get all pit scouting data with team-based access control""" try: pipeline = [ { @@ -585,68 +631,80 @@ def get_all_pit_scouting(self): "as": "scouter" } }, - { - "$unwind": { - "path": "$scouter", - "preserveNullAndEmptyArrays": True + {"$unwind": "$scouter"}, + ] + + # Add match stage for filtering based on team number or user ID + if user_team_number: + pipeline.append({ + "$match": { + "$or": [ + {"scouter.teamNumber": user_team_number}, + {"scouter._id": ObjectId(user_id)} + ] } - }, - { - "$project": { - "_id": 1, - "team_number": 1, - "drive_type": 1, - "swerve_modules": 1, - "motor_details": 1, - "motor_count": 1, - "dimensions": 1, - "mechanisms": 1, - "programming_language": 1, - "autonomous_capabilities": 1, - "driver_experience": 1, - "notes": 1, - "scouter_id": "$scouter._id", - "scouter_name": "$scouter.username", - "scouter_team": "$scouter.teamNumber" + }) + else: + pipeline.append({ + "$match": { + "scouter._id": ObjectId(user_id) } - } - ] - + }) + return list(self.db.pit_scouting.aggregate(pipeline)) except Exception as e: - logger.error(f"Error fetching all pit scouting data: {str(e)}") + logger.error(f"Error fetching pit scouting data: {str(e)}") return [] @with_mongodb_retry(retries=3, delay=2) def update_pit_scouting(self, team_number, data, scouter_id): - """Update pit scouting data""" + """Update pit scouting data with team validation""" self.ensure_connected() try: - # Use the same data structure as add_pit_scouting - pit_data = { - "team_number": int(team_number), - "scouter_id": ObjectId(scouter_id), - "drive_type": data.get("drive_type", {}), - "swerve_modules": data.get("swerve_modules", ""), - "motor_details": data.get("motor_details", {}), - "motor_count": data.get("motor_count", 0), - "dimensions": data.get("dimensions", {}), - "mechanisms": data.get("mechanisms", {}), - "programming_language": data.get("programming_language", ""), - "autonomous_capabilities": data.get("autonomous_capabilities", {}), - "driver_experience": data.get("driver_experience", {}), - "notes": data.get("notes", ""), - "updated_at": datetime.now(timezone.utc) - } - - result = self.db.pit_scouting.update_one( + # First verify ownership and get current data + existing_data = self.db.pit_scouting.find_one( + {"team_number": team_number} + ) + + if not existing_data: + logger.warning(f"Pit data not found for team: {team_number}") + return False + + # Check if this team is already scouted by someone else from the same team + pipeline = [ { - "team_number": int(team_number), - "scouter_id": ObjectId(scouter_id) + "$match": { + "team_number": team_number, + "_id": {"$ne": existing_data["_id"]} # Exclude current entry + } }, - {"$set": pit_data} + { + "$lookup": { + "from": "users", + "localField": "scouter_id", + "foreignField": "_id", + "as": "scouter" + } + }, + {"$unwind": "$scouter"} + ] + + existing_entries = list(self.db.pit_scouting.aggregate(pipeline)) + current_user = self.db.users.find_one({"_id": ObjectId(scouter_id)}) + + for entry in existing_entries: + if entry.get("scouter", {}).get("teamNumber") == current_user.get("teamNumber"): + logger.warning( + f"Update attempted for team {team_number} which is already pit scouted by team {current_user.get('teamNumber')}" + ) + return False + + result = self.db.pit_scouting.update_one( + {"team_number": team_number}, + {"$set": data} ) return result.modified_count > 0 + except Exception as e: logger.error(f"Error updating pit scouting data: {str(e)}") return False diff --git a/app/static/icons/icon-128x128.png b/app/static/icons/icon-128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..d2a9e45c9d12ed34ae5029972d7030d54d57b1eb GIT binary patch literal 5531 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 literal 0 HcmV?d00001 diff --git a/app/static/icons/icon-144x144.png b/app/static/icons/icon-144x144.png new file mode 100644 index 0000000000000000000000000000000000000000..06f0c6b4387e07120fbd7b124a1c119060e1a29a GIT binary patch literal 6589 zcmbVRhf@>Z_gz#J5F{cXARt9)QbJLBuc9Qh(1aicfe487-Xx$vXc9rXbO}Z2(m|Sl zbRiTeN(mT>l+ep>KlA$!zHerCGrMozX71j5?m6d0>ch0@XxL}~0HAxQqhSbM0aOp=#`tzMkP9Z%hazwh+^ggFFT^HZn6gBUrZ}7sIge05vyNLjLd(BV7-HZ%orhOjMy0%kY15 zF%X#G61M!D3-c*MnU7bs1V2vg8BT^ z-06^Stf*YvNWdKpYCaf{OZlUx5Dn!z(rtiXZc>#wl}uiivy2WnkEox4HpxqR)n(G zNy5lRGgHQ;x$9~##MccT-(kp#L&rUGeaku|fsU{VNHMgC9g{|-E~}^S==cl9D^|zm zuaj&mC!esO#JV}2cTHT)BnB=j#!48aOQMTeQOc=HPlJ^cdrH!0m>=2??O9*Gd|APY zaQ@oRA{r^0F9oGVb$7+wirFVcw70j%qEoHY?tXlZK(y4noRKrNb#zqkc~2eoPhN(o zK_IVV%hiXkGKrCWyIse%DcQRpikVm35ovCAG^%tM&N)nZ+(luLDw^_mwvEOA`M6>* zo^(`Wt|%|4V+wJOxk0)cs628RkU^E$vUIiFW@Q})o`OS_FR_k412#vj-)PFo^j_Fy z0HU6o@7q^S!gqIfe~rz}&Cvn3q+d#TSe>J~CX6lP1@Z^^5_{xPXUw6yab>gu`TrC) zy>wU4qcD6D=3qp}rl%W(mq}l2C4B995)OJj30AcR1hTb;QzMaq8=&p0c+j6Fy_vh} zaRg@;Hv4jEbGIRhRNw5F1aZ|gIOEQMxnQZ^ElQ{Yt*+_TMhLRG+S|Af^U$unhO*-= zbH$RqHXZ&K>5?A2%S91{jE0Qgw=Wjo1UhKEZr$ z)EZ?wbiBJ)N6FSgD177;n6h{yzE^-w&6;lXtz_yWY&?HnY3@%NFMR-X*Q_5qO`kPc zitgW28~Qm(9z06B(7N28CPMJstaU#29$nqF8<4I09dWBd-fK<91v2&D!@i23Cj@dJ z&hVPJ&qKk}ci*SW!{RcW!l(hTBp<0lO877*I-^xC$lz0Qa8vO4S;hBu;F1Bd+cilr z%B?U*fUK1(zT`VQ_`qr6UnMcqf5S_+$Viy^X1I;dxKw|zg8TAgPGTlKLCQJOhQ(W& zEoQZ>tgOO@g@63Bb@0={Ohw1PWWSn8r#*D4NqS1k;mc|ZMWz|a@yiOkL+Z=Y&08_> zh^zjx~pL}EK0AFfWzB=Dic90fe1ux^Q!3#jgBV9^IPo*Z6 zDjYue4N+`zw{p@64rag%_`B#J_`X?#Uea@CwMSor;CbF9m;c&YB${B?na<$!c89WC z0X?>E*+k#k1qQPQul4?&<^BexqYdBLdf%O8vbFByW4;|n=~*8Xf$|*_5LJL~!6X5J zN?uL8s}m907@LWXI#E~KGST?^#uQx1eIm|MY>yBK1c@x?X+`*KQY@MB*lx2e>$r^A->-ZL#b*;rg$ zG^(@QIKQL{VUF&!9<-GpSrO=Re*-|HrRbH;XTv&$qiOtnd`cU~X9uq-uX@2@Fs15% z!)--Od=Hd1c+54O4ig_uLy3A!0klK&04tabaXuaVV^BSJnDdKNJIJLrei>q+ZvF^i zFkQB?MPJ}xy2hC#L7S@}F22=dR0umoR8HOzYdP62OzpRe@3Cc;jZOTW?)ZlO_E`)U zRcuU5jH2K2kCdCNr~okcduAHE;Y>8)#P05HZ?}OM-TVsInR?Iat5RWrN)wU*d@L@$ z*Xzw4P?tb~?g1EW5Wz^jeS0&{kCet!N*`c8jTjx9V> zAqBie?eFg|*}PetT5$TMxR}%#pp@l%@cRvKr5|@2RE8*!+(&f&ve+I<0if2zbp@>} zYjoL@Z5RuY!<`=ZxBvV1g;`7<3XLbNug^G4@2M9V3n}@^-J%2p^0TxZqn2V1Tvj#Y zEG}MG`$+ODGAYhRejdEvFhJ3|e;Q@OBDS=1Xu5rx3YrI4m;oL=Fj_PqRhvwI-j`?L9vB9hbIXEN^oGlo>X>gH}L$&)cE4TagB+II@p6SHOS#%{L4|#;uotObf0~as{E-AQ+KMop5D^v@)?{R43_U{k^54Ov-ZVI4$?)>D!_H1jYa+u6$R=?y$^MWdl^7gSXk&% zOdyHXl^vHjPYVLvA@L4(gc)i0Z$x(R%c4s31$LFr4i@g=$Nok~1Rfqs4 zUT?|maA0d|Yjwr1a*`H|ZhX9%q@-j=MTro1-n47FF?csOpgWeMcx|$hnAmT(qorw( z<2NV1>BY%oG}S4qt&}BZ&1Ky^)N`SOVji4y1DHP!GPI0~yP_$Un45pU*8vUD*R|%u zFtWvTITRVEcGTIo*@1(vknc7xo=qlJQZ{Mud{Z}{L0U3z*nz#2K&rN@JS^BeR=x;m z8Uz56GWPH6t{QvH@M4T9jP0#9W#r650HDtD!AB34B+<=KMHeuP|*Uj37Dp)5s$|@?J?vwpam2c97vjSA@ z(1rSqZqpL8^7%;q%%@uqxR`gfsCr%v@KfmUA4zb0kuL)*>gGZz;JlZe;^QP7lC3mXTFb zYJLjhN!G^tI>!&Y%GxsXO1`^Ow<2tGSW(=lk}laR%ba5Me^wV?DqIQS)sAI1M36)C zA0-F5%ryu8+G!+5YUBiaczA$tNB_6}EE;V0Tk3DF#T+$f<=_V5#)cbJW)@TIGmH2c zBICu!8AsWOi8-J8U=%6_IE!q38GsAq%f-cGf9++7%AB`uJ+iAbha<*r8|l*T?(YYe z7z_3DVlc#_%G?zPx~jlquj<;`{RohsX0d~Pxz^9$Ut3}Dk?50}wP}}@)4hG6_r20y z8@~?i-(2Hhqf`mw8cOZ2`Edz^fYW;>C0{!Czi$3y0mlm|HdwVqKOG%iJ{5P&gr67? z;ykSwyO76yB4jWQs1z0yAOrukT{-vh^E-;X!4FeFP4A7WLVkVdu??y-DL#!VBjJg@ z(m%>7M^}D!83EY~GJPIa)@#1Aj}8OQ{6?kPwK;ulH_17E*m225RxO3d&*u=4IX($JgFF=(1%UShTCJJE%pTtjTb*@*Z4SS$StzRCTSj>;S5|mN{Ia zbd6I%=HmPn0IH-E8yXrKw&YfaDijYHcwhrSj+$NOEj#8qgWyjVF00O)6pvjWl}ZLa zW!#(Q!cqoRTOb1GHeZ4tge77ac$Nm1e-T(D!*-Q*f>altd!4%fQl_crZ{BoVQ7OGq zI7&%X{xt(qiQKkasXwC!UxXB9o>S>#jg1+Dj{PwSHJp-{l{I&_s*Unix@Rc#J_-b1 zEW|BIe6PZxuXE$}=D52W70B^;G|Qai#xR|}wdY~#fWUj*&lEoW`_tuX>wb&^pQK4s z70>&;$h*4-F3pF_kdQAX{`Mc@Hjf7>d^Q3+Erpic?CG_v=`QRdBZH0)rA&34@h#iE z_l$s7r(}X-^JW%YCWkU|Q}@_P&!Y;s&qnv-ey{g+(UTD-6_-;c%2F%nfJJ~E9APO5 zKBk`COy8+W5^iOw z>o4aiRHWDK_|dNksqIuhf8iDI$)uQ0clFQ@$*$t=<1-=ax4WXN_8rWtQRO%W$7(jG zX&A_pre%l?RNQTp8w(fTPD$RR06+$}RZyNwsWU2-54P!lWepoN{QUfAmWVr47cKyJ ze8eyVs+{NkuO|Qf){YL1D%534NSQ^oZbnK<54&>E$vBuDcR>|3C~EfQqkfU`GCEa@ zE#~rP0!bmUhuvo(M%u(qBIPrv!IMo&%(nbQlhb^*d-!uBpFq3nBO)S_N~{`cR*sdZ zUxyqt!&CeDo@2KhW-P$>XTXld8vlK3EGjCB?A!h~%P)rrX8;sZ9{Uv;zpyejO`da* zVnLOFlq}30R!~^T(Z#}zc{R76jkeG)H3{sRK#CVp17=@e*$n>t`4hjoy4neXp}w`X zb@s5s3Q%QOgwsciNz>+*p)E?mDqU_3y*T-zI@PK<;IR3ob%3iiOQw-SU|`@*LqkJy zYC(?-F^>sb@W3NuEZ{cRowqeJ>iLGQy-N~OddFuV#g#X?;fV(_l8uGNq(KxPlIpi8 zHStGioKzbyE}0C}(W!{j^0KhApV*u%8R-6&8ThN4pR-RUb0`Fz9N3nuSEmxxQ=_-%dn^zRkp0Dp3L9?l4Qj z%nz0{=ILoy1j0L-!WpHl!Q+*~|0OeO9;Q(7V*ri4zHU2OI zKWot^kGynn|XVW;^k|QUQ~dD zgv2)%)Nv;###0xaXxN+Bv-tLVA@%xpt%K~B&;s;a)CkZDvU2L*Te*Sx2*hdZ1jQm> z!Z_nn@gHO`J&^Xmxp$>r=^B?lVcR>77x%4|jhA)tu8 zVRd44m05V=qd@Pi0`wVPU;y9(6+M4%^T32WqqbPc4$aW~{6|$P*&i$mQ8`Y4g}bBa zRnzcYP~^)21G3$_yHz=)BhhYGC4*9vbLKyqN6mBetzjQBL9&4;9y{Xhajn;h73Iqo~yzn8!8k2l8_ zECd4pNRTlr43b^j;uJI#@sjofnDiaKAtWmsnj!dbdx-)_8YB)1TYi1Ti&~>1vV(2g zKs5Q>7*nKG$h@q{!K8~8tdA@}-@wEdOo*>nef@!QE{=9v$n*S#J5yDn@5vvXRbX#@ zScRNo0}ZL~uIb8V)i2&thD0t1wJ=Q!Dvd}4^J4TULOe<1b)cY?M>+9ZG!-m7BQgJ{ zl>!+=Zm%GIU(}Ru^}4RsHM|{a!pl4lr=rN;CFYp!-va9_rT}dnRcq~b_!g-`Q;KAP z^CjM+SbK)uIy*ZP16psu723e0$50EtU6M_QY^R#F!widlzUZ1T=&Lw;!k1`{O@hYe zjI~D}=v7^E$eQTATOWMwTcins%m>K4nh&`QfN?L#5qJ~s+i($JL480ytIho?6m)4o zMEqJ>|MCoOqzp=%rV8<(Xf zNgF=CRW)IgB5I<>0O&c@4l}%~?HD!~hNvj#S25hqx5GB+QMHYiK5<4Mx*<<`dC$&p z4lV3DjX(6k`7U)6)n~$}wJIByU`~!8HLAl+OgyVDdKii&_t4Vum^5wKse9P%se^vdayw7FhKyQ^Cc64eb2^5Wv+23MX^ zd?LEZ{v>(Q15$d9SdhVmB(bD&cPE`{3MZZL7|6@i<;9g12POb) z7=5zvpD(JR2|5RvMDxVN#P{XpR49 z;7PcycGdyKPS2`yB-3(V0yVX%-ss*ymU7GHpSyQV*(z&sxqyyb29n2av|FU_Z>Kzb zFr_q{I7R%YhNb~PlZf*7d4Sac%*iC>KzoE63cRw~uY4xF9Q+pj#z*?({LT17Pkenk z#IOLzd7F*R%@y|Q4#zc$){cAe<4^j}L#F$$Uxkjy`MobZa^sVq7oHzk|Y zc!MUyCsR7hFtreOR9`kHGBQj0DHF=FWQSE0^_n_>ZzVYQjA=@3`O$OV$-~7ql=&Cy z_}7Z1f2@{N1g*)d;v(C-d+XD=dh+>k?(e?=pg(@4Gkrp^ufim%>Jd}U9(yt#Z+!!{ z0|%9X#?OkqD^g-F9eDu*vrljgb)^ zM?%?sk-j7KE;GR>B=!u!xFkR6|4^=4&8Z|136zmO2Q@59o+u literal 0 HcmV?d00001 diff --git a/app/static/icons/icon-152x152.png b/app/static/icons/icon-152x152.png new file mode 100644 index 0000000000000000000000000000000000000000..c02360010f5d4d65a8cee0587eb08925b62a1b1f GIT binary patch literal 7123 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 literal 0 HcmV?d00001 diff --git a/app/static/icons/icon-192x192.png b/app/static/icons/icon-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..5acc6db0159350d811ac1279e30642d711c5d645 GIT binary patch literal 9603 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 literal 0 HcmV?d00001 diff --git a/app/static/icons/icon-384x384.png b/app/static/icons/icon-384x384.png new file mode 100644 index 0000000000000000000000000000000000000000..ffd0cc00da94c78aab3ea8c0992ec918ec0cc68b GIT binary patch literal 26379 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 literal 0 HcmV?d00001 diff --git a/app/static/icons/icon-512x512.png b/app/static/icons/icon-512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..55856d39bd131d3ffc92fb6c79aa7d461dfaa2e7 GIT binary patch literal 42816 zcmeF3l@7LB*txlpg5$X0A{91)4rNL2mbZ{A}ne9e;CcCA<9P=G~eIY-y-tD3d%9 ztsc=sxDRee^jhb2A5-~ja9Ug1$F$>l(IN7*M#k^2si_}{IgN~Owk#cWPfE9F4vDR# zY6ShQ7;S#7^Pxm|JUIEZ67o#K}57)1SwS!VM`B@Y*(L` zvTq0bcU}0vA;Z)KR{!NZ-P)11>k5Hwm6dLAh$dupx&y!Q##)tn#5dp9iuLvNA+3x? zEMT@=s~D8I&*D29Hv9BjxY3<>82CZ``P;|JYB3MONPh3Kb59Lto=4vbxOi$5~Nd$x#NHSoVA){cM# zDG9?!Y2;MYPG~v=e_dp?(ks{&VyLOk9D>#4`3jo{JeUhHb>HT8+!V+-rmxH-{NGhb5< zQ9cRE#VA-?FT8#A>Xi#QXCnq>Og2m48UZ&}E1A_M3@?UoqY=^s+3>Ga=A=*E`01sa z3JVLrO|vJG;8*4R!)HBhhr6q*t1n>3S(c0@=Ol8>lY^u_mud*7^djoJg!_~o19@HM zgaa2I<#4V!WSi%IZ-p%0-M9G(H|J~zTj}6Fmx7<4UsX}jq9fNS1H>znqpkGdd3gl< zkBBCfygH&TmM{eQgrkuE8xdSb(0=B$V%%6q$8GO}PC*eQ=D^N9KQ0j;d)|Vo**8F{UL@pkXa>`i4jqfqa{1v90D_vY%J+I=m71E8l2ulVJ)iqGBadHHB**a=v{JYszczIby_=Wc}j+q$=tg9 z?`Hnf+m{o5te1qpeq9DL&*Z^@ zfo&LcM5=xq4sURMA1vXH6j^7FBf*V|{HO@p%b0fD7PotP)@4MHgi=#QBtB7_x)pBw z;5Hk_eS!p;WqL-a=2! zq5VeE6=JGtYol36PMfT^AYdCY#eagAkM{jPi5+Lsi;ZL=9Vv@i-x0c+={2+veo?cb(*q)*|AzH)Dk`j`0 z$`tDTsVcQmj%PZ^TU>ncd$bqb&FW+N$j|R21=5Ge)JV6I#5tBB#z@+7z8M1F>+L6d zi$6XM8+-ik_+zgUT_zYgIO*x@XXj&1`Dkk+BLB8*+67jYDbB!pzG#o>8XNo1oh;=b z_ad2{mmR*c5$y6&{X|~ke$jF(2abvSXya}ZZoiqo-F)dSN%~cn=%Qc$M&!;KsBw=j zu#ypi;2svakH7dTg?G>(1AT?x;UewQFTY~_0>y1{9r5VpPq9vXu)kLo0-ZJ|8w#AH z4fCg$l+&Oj_P`G>`m<&G1ml>P83bACAIGKOb*OkB;P=i|i^7J0-g;{X@%g#fclL&cvPNg((6j{B;EP11t z3|9JEwqob5LGXBG+QjZQd!vy)^iqLq=Im^+BRk*BX&-GZT)`yupvAHyQq$SlSxk|S z*jEG`zOhScPNI7a^RJV=yu2(3wqIK^-rL*M+9Ld(qmNPAe2v2HU(*|H(dj&-`be)(`dICJ+O30{3m zqOng+=9VM2aut7{)i_NUBNpxyR$W|^*CN{~_S;+3r1^0~dM25)M8rvy?fdVFXwd0C zO?uHupzAuSHxvGKEjyiYLW(HjI?=`6*4v`-z`1yF>O<-KB=wCPV7<7mE6rS`on6du z!Sda?qs5zlhNBEHR$Ea;Ol|MVh10FWlWo#pJpQ6hXlQ0^d>Z!s4R61o=KV31;3v}* zOLreX?@o}t-p(FVFc86T;>eR>CCBGFlWh5X%7^lclXk^9N2^Z(Y$o63J#Od}sNb$u1? zQsNRaR`ZKT*+06is&h4&!HSPE#|GY?i|IYlB^X!b*?nqfx6&O+%_{YEYo3VYYlAQ- zXVMSl(e-#4^cZw#gwnnQmTHS3wbLt@XmIaA5N6v^iuMc1IqMQ&>q9IGX8gE@s zMq3YSlbcOJ9~hI@yX~#0^r5UQCpvj7 zGJ{#`Kf}TPKpT*S-=`$J%BxjMuMqe|?Surout<}StNXObV9unXbZ_?rSBr6X0&9E# zYFPTOyKgLnMExTeqd^U?&dZx6;_@exFAi=^n7%QV_wnfmkC_N`_H_lB=M#r2E{!Wi z;p{w&MDq9tMv4hXPRiQ-dXMT5{28ZQ;6pd4>}Ybh_+^VMeD^5o{+S+57SD6{LIJ6`|({hPhk=?u+fzqho{iYUCAJuOaI zaK;oYFj;>ivQi0gkRlMYqE3~p$}`q4Pjk5T$@M2hAo!!Ilzj}_^3g;-m*JaAxQd#> zZ(&z-8n zH3emUWa;>{$+O-(i7PEysu0tUNlnduVWe-#AW_dOK)`Xnb{q5+xx3-MW@O1S+k!ntMo&z%(Py| zRJxpF0(3Es;(@GeBh<1o5TE7{?m$O{)vJEwG*~Dp9-4V)lO4l?KAznY*W9}YG0$2Q zY}OKmu8k?$&vICNC4rE#XY1984YQHV`R5*L;9IEaBRIr3i4r9KML&wP#Hk?;BH>)D ztwS{AC!Q9oQGqX6`UNS)_p6JaSB^;pagfJ#c8OGJEt$IEA3|)RllI3h$muMFSrA~?~oQ2+$u#E^Az9DVSycj zaltUMinSxSeQs#O#nT%mNjMe6b&-p$R17AtuBOKGR}62qt(&(rl=t=(P*%QPUI(Q> z-)F!)$vA63zk})mG(fkF2qyI-{(QG6zf&-dHv*o61l@N{QS@qkJ^swGT_^aBlU!8= zMUB*P!rYco+2KRp(PyV0=woV;$=eNTB`jD`Lv3EVm_2PErmDi8`c1d$*Hfd^GW}Oe zxIcdSR8i%0mNJtLPR!(XE%m}nqXIc(oH=gQsapv=5gGdrbM@G>0hwI~1vey`- z3@b#zJ^oEXk@O9%l29>uX2bt&L1HHlY^ZlnR_Zc5HrXZ_elE~(6kz;QqFW_h{x&O~ zJ5jhUN2?+8cOf@r$5u*pl{Sw?egF0)eW+dfTiJj^mcrqjbN<*_;qS8gdX!I|@LOHo56;$C zo059=En3n7_mAOIMtVAr0JBwG;3JEyvcY24&~O0u?22_;WTz|7a)ffvM_Mmh6Npt&u9kGd|Rm9j@W|HlQ3Hm|Fcq~>Z1Mq z3$AOK&x1f4C{rtGBB5amRJgrEI}TlwJgP4`NO17P24y@X^5B zZ$S^S5XIX4jw)A4?TP@+HU*B1b4EhzGZzQr*OBZcKP8@Bi`&nED*bM?o0^c2a8@W~8;XDCq+ceL9If@;O87t_I`;uY zd!_AV$Ow}f;yB)~olstZY*&T`)ob81aS#0#7SKK48%%$WRay6uOkk&DmAG7fW=Z>e2ZgmbMGevIHI?P0{ndhM#xMJc zfq@Uo#AM{ifuG;MpZA92G!^t@`R~Xlc3yz; zC@L-c5n}Y)v&u7k*V!k-5qJ)ol&n&oSf>dj z@n_1Au)w()$h9Lr@JO%qfoR*dX%$A8yD;#t$dA6@lRk@tgVBSdB6oJ9)QG_Xm0&fp z%4;WDJ-_e2`0s6DZ9>F-0x@y%3{>wLWrxJl(^Y5;8^l9<{$ixm=r`Hjh+*Sc{DX_H zG+akPu^7}RaA+qd`#fmMWot$}&ksh~!5IAtwQAj0_0_pe2WnR)N zSi$&XJD;_&h&xX({S$lXGU1d?s%X1?8j{zYW({igUD{PGr_%P#5xK zZlArNMO8$wu8E1s4crW9v7Z_48tX}e+vJ9svL|*=3sKIM-Tx^u=Ll_w-8@rrxXY`R z>1zx6XO>dUBei!FHV)yJ4S%p^%alZkn>j6(jM=C=R8JlF>6=z+hO`_^+t5LQTLM== zUuvP9Jiq>{(Y{O_{}Jy|t7{6;HZiy3o%@a&#W%lZ%;Yj5Ih-M+*xJM*!ej+$r2- zP{{NX^nw5}d2w}65kfyUH%HruFr9OUumiOsASNb;_Qj(ynWIUM?~fbjWp$2C-(!yd zF|;u{*xJ6OkL{GBnaLSDl%Aza<_q8cD#ZRk?&agzJdQ)|%s&mCRizB->gvH%GofJc zrTW|V{6(D52(tp&w>dZ4vd-KGVK1O#Q_%i3!S=I0PC5O&vFX&b-!c^ydfpJ!-!^d+ znxj%MXp=u^yEpSCY{|atd7kK6KtPni+iDVM0=x4sRzMjR_o=*GZc5}wKe*zYjG~i# zyL3%bT~dx@)&F?%)4O~w{Ew^PT+_32o6arOfuHW=A=m$ex`(whC3Y})7XL-$M-=S+SDh(L1?N_Kn*+ttt1a)SrIU!DujBtl zRJf9nA|fIzPK+c9KHd=;L1&@U?bpc(l5;lo5fNZga{gd#`_8 zbLs!KxQ1ytxSWhey!V<>y{W1^RXtUupO6cY-zNHtr*FI460bwdV3 z2zIkx&iH5(_COM%o@o5lyqxjI8C+~T3R-#k$ZsC!MG=c;i_ZpYE!lKc(j9m@J4!c{gVzq2lEg+5w?2;NHcw%f!qGwC*K!xx| zAohK&mZ|69!=V(E0nGPD;^DMq3K2;-PhCyL_wK z^oEjW!p|kwo5(j7oE;fCe`+}xUmVJ46Q1n48?W6%ESLZTqm_>?2DcDOanJXN&*DU_ z7*L#xi=g+#HSKVP8Bnl39rp)c=qC3qlzOd=boTZ3zIJkQN+ah?rDPIW)*iGKsYail z4u6ZeD(%v=fxQbpadXu>?hETa^#l^~mk}tdJp~oX+-*ox{AO=oANR(_hI?P?U6taY zl{aeOfEnoPV}-M{dnAe6hGj(TaES3c_; zFJDX0RqG55YAUBF6FvD-^dP@z14XHhNdEBx#&I=9>8E-jibApbCn!Y%Hz0gbFo5Sx z4Y`*MbSR?LI7r)fmQKY9eL>k+btq6t%$5l_T>pmF*rc=iL}5Q+V!WpGp`8-;xX2Fu zkZgx5q>Y^Of@B5_T?%UM2|-5`oNOYRdk+eS35&D+S4q#0Tw>o&pdaPGJa4;pSE3?Y z`If4KCWA%b&KQ{fR0@Z=AvT`;HkdC_`w(=oh};m;jtUo>)4FQb5%6-%FACDvB=5Kt z+k%Zc1Vc-hTPvSEe6VqGGwz`2X<6HVafcvfeK`eJD4eV3z&!fU$1DvuO5Qfm&kg3T z;5z=b&Jx|?R{u+I`TUJN^B*Hc%Q>`t3W=akLFHP&--Mc}pSnOf)Kx>?% zm`RStErWPQp??A;dTK>OD;Rh_cd{yDiQdO=*bQ5;46o_Hy&tzA#ik9y?^f=H4i0Oa z;B0I2o7R8sxNP`_)f#A}^W-;$5-b@%l$K`BHExaoP1wA-HFT59)cyO-?|OHhSm^5? z{s-!$ftR7ou@rDjdut64sOjmm4yC4(;l!4#v!o9X)X zErYr5Y(CIPD)@^)Mk$l>96HHei5#_dq+uI*wQQmFTVN z_3u$aNhp6~2Wu$sI{jw2Omz@FW>P^l#6ElnQi|xM;zjJakuV%MfgdK=Qe0z7?E(WhH#2uZo;C z2W-$I*l`qQcL_W=-b~+rG2aJv);|q~e1(4xC8n@4Gn;Z85^1B4SI&9W`HY<^p|gq~ z^d|fd-hYn~@6VL1Zf$O6+3{_5H3yP>$Cm|=8;MKYUKC+{Yk9e|8b7^al1(1KPQFkH zsBWe~LFaY$4i1UK#>}&r?`JcaU%x&o482Y`DCx1NQOm#f2UJ<@LOo8y&u3eis5{3# zT4ZjAXes5cLkc$7(}#*=I&TxH7B8EEat4iEs`wowLm0q4(6a{PcC`Px_ubvF$@aVT z*=gCocwzA{G!>QK+#HxR;%^=haCW9S~dx8=TWu-unLaTVk~|0y5GrwHQ_Meyvdv zC^5#pPfYrqb34nxd6Ai_;&%Zewqz+gpuG5YFs!`^;9^f6x)?BZ9sK}{Y}NVd`a4mF zky3j*yAmPIJ>2FBlYTgR>7s3x~@TqWONxovkDCyl@fATjzcuO5ty`<^0}bcA22d8mBXHJeHl# zQRM-9)SoE?NpKvA+4V;(unyYpvvmlf%?u30J98#z*tuc3MMbwtYKs87u?Jhd54#bN zKe)^ap@p0$>-&MEy13H5b+2i?DhAMOi)55sA;$k(RcU|VVRcR!g|)!soX5wqvUmcC z_-N-kr!%9zDzxux1cO#3|JpMye^as@9j;TkkZB-{nt&$M1;pyQ@!H#w^u~whS8;|g z$Qo={N&48S3QS|4$dBn8k#6yr-BDna$6}r4s`~C_=$o5g$Z~%3fY9P#$l_MGnRb*h zu%y%T+qLg*-Ebna_$IfKCpWFr{?a)`ThxVKXG7ke6(FKC^Xgl)Qxw5yX;s`DDe=7= z5xZXvm$)V5CxOQ0e8Q}ZlyEqnmolZG>aK8RYw^&rH=Q3&@Xcypff%ziZn@~Fom02- zg7&9%Jhg=uKVWD(?o1jr5BM=TMGatNn-T-Rq2NDpF!@>**=2vkG-{}wc=T2MeWG?w z5@PuI>C2ODpg!Q>#A+!C=ouJXdIC;(@I~nGAQPI|?Gq5twAg&>6aUd#S3Nq|8f zJjkpr#4uw}AYy?-LhLwjwZY%O(eL_;KHIRa$w>l#6+Xz&*dY(I>h*;OxkF3OZ^%JP!^ZLs6#ZOvffmni_xojZ zZ2e<*?uumxU19%8SEBE1L8IU*v z(DrN8>YZT5wo#uS3S^XQGRH(v|3j{17jw|5qksj*XE)pZV3#)Gx@t2Zzeb zJ33HO+oD)5C01)jG7^9vNb26eQZq9990u*mXxEqX;Upa^Njt{AOg0P{Ghi3|*oYm5 zYDeb%vU#{{0EDVb00zg1uMGOXKeY}jr`=Uems>eV8!#)6S0tyX70ao6nyZ> z!pY6d;Pi~9T);1zm!+E)y`3>&Oxiu>khjJWx)D*XZfTJ)H-Hbe6c=LD-($QkUQx#r zj*XFXdWtapRv?>F{PjUWkn?$Gs5De+3p$^8eaE2;&BZoS0cLa_8vRwq&D6o3mv4 z$3i+;A+edeMpa6L@(vE4uJRKSVGB>G%KGGHqRGnFUL)@&?+MQ;L!Bw;SPJy;6sLnNXz%beZ_;`@IYCPGnaT$qFMx-C#5+<|&*vd5kI^9KYu$1(_jn|dZ7Z4_`bt#4!9zkUV* z>09AH_>q=Fyv8lX@-_OZZdzIeql?qnDkqc8MXe^m>3FBL3m+eqX5K9rSDt zZ>yZ3_qt&mo2)UAW*V^eX1_h}qf}YhF1Z#P;AL4Er7rLit};YguCKck%^?~2m@=;< zU4Nrdzninjt z+@@Vii=d)l0(RtVV>urw6_ZF!S((!_U}o^p$5H`{`}gmAu^UPvOl5|(_P=xW?uZ(x*-^5~1^NLF@dkPF zU7M}x_+P9^(}qhE$?H?P^CbXqLNQfm>9}n<87O;13Jf<%9ZXD2i+2i(JiWZ$z0 z7XW*?0OAmN<0HYCZfzHOZT%!mb1>+=io?bq?1s0u_hJ7_AuK8WL`0%`uaI97>B8jf zjP%=^c(DCFU&smG6x6avkzS3?r4 zgjY`Z$jMnV!1=fh2|?C?eU)1@dajs4-K7?7Xlh!`%P#R&A*Y@sP8z(=STW_%FI)D7 zb>;OpvcV}x#OLdTdrJ_Ko}TWYF~olj z7TPA36>Tn}=@EDNhj3hqsP4jscF-b0_Haz-fpt9qtjAzoiaMWFZjo`)gHX?TA#mDt z@o5t*|EBe^cH&2Zla5EWd}DNr^O^4p#70_!18 z&lW#cW2bvQ|NYG+`AofYchh|$2p_$g%9QlD@SIOp$!2R#jCSpy5@+mY3s~tcbZ59xFDf>E^RXCXsQE89%$I$Zha2K8VFBzM<)6(kU=2Y zff5I&0SN$KCR_6sz)iQaz%;R<6sTEvC1t3N?zw`WLO_Fy=j?HSSGl`_$w_2=xL*kydTc_A6j z@MFG^5@QD=5FiCqx1|*+1u2E&Wg_7=`7Tvktkcbs%YZ4bQB4Z-fRKck%9gJ}Kv&e4 zXDFi^ZpicxeQ}qEtQaH7Y_7;WRv0JCN=TsQW=pgf8XOd=ZfrbN0m&HOV_?ht!Hqpn zgiVBwhp@hxeeB_JA7}xqsK|oKN}c{3WLq(ZggZ;ONxiFOl(o?%*rbcQK=p5QYTVk3 zIooQx1Y?u|wd8z~jY5S=-Bycp@_NLaNgG=B=9@=&gPgTrX79h_il7Y#BnG9gj&K1& zeVOp`xErY-j=DJXG!K%^+4vb7ErVEC{Z!t#T0Y^@c3yU!Pu~0S{aaUF%pmp!|}OM_g^;6?HNGJkayM&3?Vp zY<6Yer>UdKA}wc<6sg})%~V8M%HUl{J`m1kOF^++{R4=t2uPhIW1aeiHLG=Pp02Fp>BLtMa*7#VBvu5#+tJ&ys#hkFF?)#w?lKwq=?&O|CVCA^%rcD!n z50>%#W83pHl95@dZQ&pvyIf?Qmzs>xI zvE*k5N3(WiG11@`IzTng22;Me@fx3gnX%VLc>YhU({bNW&Bz}{Y43G6W^rd3nJg_A zqQ~aCy4ARyZuZMQp>CF;yxJ~MHkvzQ8PZz7mpUL|*{BA|wugkqwtS{D7L9ks+=q??^{hSc)0~rn`p&aq7q$NM$wgBfhOJ*_0`sG)AFy6PgY>o{h5TTX3gCf6W8>N!L{l+KzV#RFnazX8{}^@9&@^ zsso47^gb|0{@98bvjKttw9AB;l$4aP4}d!j`_4k927~i0_-%<5=DGwL-GDI}=*w}%WK6Xb~USAI{ zFuGFjnl!%0u+eg6{D|~`srFSJs^?BQ`(`g$f>HMp;GqUtpn&!C^x`=43#Yc=vi$|x z!KXvXpwHWEAGIE}C{lGuXpjk+kmR!8UFK!X=Qo&L;r&N zO(mW}3@e~aec3^W;}3zu>9^RpR&ElsUfErw82=(kAq{Yw&!+bbNM=$KL0wfxU+m>5 zI>ZAV9$00`2qg76)K1UkpB()Ya8vRA!2Lm$E$P$cf-GP~x0M0*>x1g;M_wGyJ=aE_ z?-x=q+?&k1@3B&tW(;;X|2dw4AeiE_XZ-%=z0)XKEvx(MZct|Yd$Vyf9XR9E9fB;~ zZt_Ztjb3XBo-D;E#C0J`ZoC3z0B-7jKM?JgcvAVv8Km+6zX}ZL7Tc^HH8(;3eqs=x z*gZ#sM>*6;IOfzC?dw9!4P78R+FT_$l>`X}D#pogJYRpx1?7Tfa=TFP6LM?y3TG%| z$dPg)TTmu@3OY9cMg)QCX-L;z5xtxU4%PxhzPD%W;00i?c8qd?g&5_W5hQShlMMuz z&dvdGIEudtiqzPbFw!&wJv|;V%0wmk7FuTXwhcRS1$KQDEp-hH&<*j%nuFG5bZNOw>&-DEDXbB&APyjGp zG-TsvF=+85x$`~TiJ9JAH*5mE0a$F~%5Nj^O^BvfM+D=qlDlR^-!@nQK|CZ<5#9ML z*xBCx<8&RnMFlMckmb12@JpwxyYYch%R446Ew#@Km_bT*#D@S>O2Xxw21_n3{Daa3Hkk(qhm(e}XS(+y6blAz@p0%NkYfT#*!@c_iP`W( zK!#o@D=Ky*fdZZP4Y`@Mt0MI+S%uALWMIIx$1;j_1f$AR4yq>&pgR2^nP}0BXKacv ze~TLawT3I8C6m3nc|sa9UTu>@Jac0LoA_957TmB5c*&>YHR|u*$;sxatPT7|d4fXi z!fB^uffS_U$np038h%9+)jI*oD&Sllz~e9%@md|WgFe=d=+{}vODCb8fy~Ds-pl=1 zQX;wkp&;S39b9nh?Lu4${Fx=ta@ulx@A_G81ZLv{1GmEKZ5ID~x3uLvaYhT1mto6M zRI+o7-Plgrf^g?RrbQo(egUob%PS}sfb3?wbY_@sq26Oih0qWQ8spGuH6bKFh6U55 z9Cu-}xr0+0KKPivxPf)L^an#i4qgeY_X)H$i@ejwk5abS{HG(qi@2FTc>9)iwy7u( zg-1Yi_XB_IFY3vY1C|@$m)Zfp;k|V0ZTei&cE|_wOkX7%pc6J^fLtHPxu`C$?Up zt<^wmiz?oouq`V9HFqwO@`gJahy^z%r;QxY&htmoDLd*PtN9Iqv!e4#4x{+DJM; z!R{SjuH^cHs;b=)@O2&n;Ozk6LOqGXN+ooJyh|?)=@9pwKsn&>KNA8Dcibx_t{mW( z$rTP?2kBc-27;}`Cnl%KAW&JaTx^+APxwHahi|Up!$VPI&@HV@L#n5>Ip_U*SG#N> z6B;CL+rw#Mb2N-do8tS4LXmpRflXV5|<~oN_V8V$@<<`O1aA{x3)R1f-l#TQIvMTngZk`wrwj@DULcOFjC`H#FT%Tx3@cd=TV-aa2@8 zNBo6(miC7*?vTKx7oac{AFo}ef!9C+)?uc5(^t8;!132jcG<2T!SJ_a>3 zR|Wd)zR>I5A((pRHzqq@J#GhWfZ3%{kS=C_OJ@;*SfGidVMnI~%RK17wi!p|-UsO~ z_~36~B~4QnF@l$m0OK9R7fo>y5z2Q!9j7QnFW|c^?HX7FiGpI|c$HT8M5<2`dB#RPd?Jw$mYSOSBhs=4Bv8;sN7Ebi9EFocxfb0p z8TzinD?fnC_m%C-4p}!ESrhJ)c7?qtX7uTL42f8K@NcYbn6o-=1G6FE1aAv{X8L@#>*kj4Ds4rxaDUn|}}*r8euA zeR%)IAs=$ez2q_|k8ri(*Rsrj*KNiL6QH0;ng_C4c*AW8cY}S+MkCmhtobckWnQZl zl6-?h-v3>;-B(as0cSF9z}RA5;^$G!ay96vUSnZh>`+yLVyg8$6KAH>Xs) z^IxflWjQDRbq4#LETmm}0pGujUMXS8?-MK+)TM~%M$l|_yg?d8h0LvEe`V-W&VPS7 zWyRA!@J>qOSL{Y#?MMtrw#ivHQVz)BT`N5iBS*jMW8rvp*9PCrTb-%lCH z$o)zPmL%JyVEAV z{QG?^I{JU17ch_|qb`Q3N^?L}IeYzm-lZ#^*&z0>4^Jo+LHq9*!+*wQwXBsJt{6FJ zz{n;`)I7QQhk$*ULq!Yr4qc3lu-m_H)i_21V7yKOym1}$wYPZQfda7uX4>-uU|BK$ ziCUBIF|hjvUc+kzLX-`}%x*Kq|7DKtyuiO7QQxNFX0yZ#YZv)31UxZye)>;Woa!uq zMfeFI8Ve5OkGW%uFkejH*;+k#B@aLG#LW5qrAOS1zMVwA|G`o6a}oq;!QbBB9{KH z`(l627HzWli*-X`{=T0tswIJo!bcVI9`gt&RVZPE3GC>vZY&-kqw9WtdQgO+i?rNd zIrYz00b$rv!I0p_^0Vsy`GwNb(w8E@c4jsk4Y~yB)So*t71OA*0~k!++PaXk6M}9N z<@tkIc8MC|lfpYU9d0ZDd*3Zzvo8~6>CSr^J>fOg1Lipr)bpz9uGi4A={;WXsoF%3 z-}EhP5+sr2f}a8iK;}kbw{+Wfga9RRE(q`iOl`jqzJdC>&)-K*H+;>u%+ABaq#^0o ze^$cONi;>CW(EQ#*izxNzJ*u>d<6_fr0?j>3Nhfn!4nAZqP*}Aq6wjmQR)RT**2DL zP6(uKbVZoV=KcQOf{M|L_j9iNVOoLNn;cX@^40Ienv?4(b@^a1~9%LgbCT6|^ zri{AxXOi!S?8dN~0iVe^4C7~y-IzJJT6}l@)fHm;z_KxeSNH~L(X_>8&c!)zAQ+fzC+)T)+lcq5MhE-wprI zni*mbVS0}c02r9)PN5ya7e@qEy_5T1RNampi_YCi+p)#zUIwkT8I+sUITy@HAdOlB zPhMQ!op0Rqd5@vN#}>Qsb7b4$8Q7Y++mfI%SXCQMP0)tGRmfn71#i}BkREvtrqgDj zj2~T26Zfc!+`TJVgL3;W4!?*J@3chV(lo;bURq^#zf&v;-=qH zR$40Rg-n0g2$K>~nP|;ODqMjo$x6QIW}zPBSmrg*LjmPCG#G)p@CWMz^rHy$AD~#H z6W7(+_hbgJ%)Z*=hm`Gn{Z<|o-kzSGMyXF$xB9Ot*p_+4Ect=0U;^^t87>VAsn16% zEZ9}hDa5~4L@=o34Kn%d8+g|)ON$RtxZ-F2Ia8Yjth|+xzOutz?&Rnv{5eMa^wkk? zU`fFhJB`I4kUVB2 zkZ;@V<#R(UbPK=JFaj?GOq8!l^`!}1wu2W17EiaL<|M#t`m%bsiR;N)#e?JH?-Dwn zVc9pfpzS++d`+gK8%lDEmT5Ug$kfZJFF-!d^^B_A#%-Rk>iB><_U=MPtm}{nFcY2e zW%i=;)rW=|TDOU-3ko`jXY9&+ z-kGYZUEx#;rlGZkme<#T@lTaIu6`850h)Q)+~DF>;@riwh|s zsI25&Ny!W1FZ{H%_GJ!rj^n!zIKPcYT4wqk{5HtZ0hy6ksNU`VPuh0zfQWc!CF;zn zl)V#i`z0%I54}@uBz`_Vq$Rxp*5s#P918@*34^>dAmUr4rVYCI)H>Y@o+ zR_rI4c&zqiuesHHjN^&3id0tiUZ((hM-c==Pm|#PO4$J!wQ-uPcVh{Oo0BLpD;%Jb z5~(ySO>CMLpxeHD5t#;rpBT_0>pF|dU8h$^37$Uebj!Tg?S_J$GLl4-jXTmCuSHuM zJ={B+{Pin8=EIa=T**6)7p<0z#ts{%K2u)rz$+KV;N=>{mw`d@ObQ`n15D3${&hMq zJl|+4rj7O>|2Xj#AQ?0WljhjM;^rT@oJ*`ehIlb58cB%DXVDOnl=lmrA!a-E(9+(EkgqffEs4qZk~14-V;-= z>+0$N_$~l#Qy}UezUI+@*B9c!G4}!p?h%5NIZ~(|*qhNKr+^)ftgqguNDZJ4e-Y## zEku>ng{huqi~VcdyeF+oYKXwSrEFu$^=2Ves-ZrCRJvY~O3-7A!-mN2qA z1j?t}WP2l+=lw_UMbgV9yY}_-SKk37v_)7~ z>_O}EpNV#tij$DjGtEAJ*%!j{Hee2PQXq?pHl45VdVKrQamNnnGYm(J)0g;J1--Im z#oSxZ35oGQ_??uOeSn}iWk@KAKw96;!@cq_Tc#bwP?`{4rm_GBcq`U?h?h^(NE#-7t4%xL6M!LviCfsP|Ao7*+RzQ zkiGrh_xJyQuIGBL=W=!BocsQa_xrWpogp3;5c_;Xqxp1*D~b@9bTj zuYkYOx5`Z9bAVx|JY@D=5bqi&1^7 z#OsOc2q(rA6-n6~jmENv&c8LxECTT4HGom9?~58YhlXN#6c}9{9eJP%YSR;EN;Hxh zGi)yb(=Ii&Vpm)kvpHFbq^|fCZKLD2p+lZfCRR|KqYr~CZ|SQ9rco0>`TO9%kA&{f zdILJHhQD>hpU`KqA8mykRo{nd@dMzQ{ktIMuhXVBTyCF;zrd)ZLlQ+ge1?QRSfGkn zd>~t;;SMqbJ}7j(_$i*=-e=TGpY{9Htz4Ziwk}^&E?EP13s%%@R+FcnVt^jHr%=7* zuPSG{KDlApSKsRae^83+8-stOJw9bjA$K1f70OE@Z*3RcCdP?GD`<`mo$mNt`CI61 zx#a@FS#v`4nKNfRis2DlJ)btJ41iS9_%6QMDqwd$-#SCZhZw<6%H0}`-P}HbFS2xG zygi`buNadNoa)=sf`M-L_3Lz zAn5h7D_tNqz^>G6Hi|{$(dBr4%US?1wo&5wJ~%^jL;gLp{$Bex)$uNpj6%p;t_}a% z%2zZLoCIE|CMqPU*wSoGV{lZS*E6(XSw7A8>V@Y7Q|%T^hS!piN)S%1oRd9j8joo4%XLENKQ!^e+(~Rd}HB5@!chUpj zq!nQQi<{EzUYn>Z0_s2C;LKBge#Esm=OTZCp+{Qh%r|LZe6>)ps}(D5d^pYZW@H|0%eXjlnJtfk%%TdQ&u?Cd*?mt#@oWfY!}jL z%1)6s6N+(+M?*{N8v%9uuC6TLpo5!SDb9{0A#Mufu@l6g#-nWomV;}KYTzjJ5)!b8 zLapf{;~)f<1~4=jAQIFw?ZmD0csfMDZ#ppv#0w+n&K~fZjNeCwU81~S?t*V!9~9nK zC6Z!P;*7H^i~e~Sc%n9|`3IRn$DgzFpd#nl9}43m)Pf7e)_@yG0g(4xWjYa^VnQ*%cA6`It-w*7G zAM}^0!NbngCqW0+3V$SC?97>sWNGUnkb^WiNza+pJ= zOg95zZcdT4xgRC56Dl1CGAisllj+nq3Jfh(p$9X(B%J66cpL*n|J;bi9y8MGju-fy zy@v1a@0Weh_1fC8uo$wAG~&P9-J%h?MI$seT;p!X(TwOwDF2Q*@JoUbB+p zUNWoqcHRR{xa!WO$ytHzV&*?MP!KT{TQv@4$k=`={q0m`rB^F&#$1vKmcKnV^tSZ_ zU{4_>B?VWUmuIhr9JKMc`UY#I5vAD?lpi)sGRi>tKNi>r3!s@8x3IK4*aaHJuWx0t zHFJ4k!MqyU--)hnvINqSp|ys|V%*{G2qXX;@j`4)h(*2kQpo;u@A`YGZ_m@)S!a3| zFTN*Zmiq&>G4v$}#^l4T7B5SXb)}!T0yhrg)@+CDv(Sm~zlSB`(qryQx+003g(ElI`{3%e-8nXtaj7LsB9lSF5BgPpzpxD9k95hr7?FX6NrHj$y4M< zD6GD9#E~Lg1in)XSshKE(%9)j9|10wU#-pwNTyhjxLEoF zOGz?(i`=)kX?GzRy}IXfL42E3Jszy01iM5C@bOmE39khF2HUg>Eg#&IFJX~Z;AM|R zE6#f=TQ~|wu`?h3dxU%OMT7(usI&huiK5n~))mjje!9qE*)Icb_%$#{;UKBSiygYoKXXM7Dpp1{(t=d8iIz`g7Xt`q|46vlL$< zNr=;zs=YUz!Ov5bN6Pqj$%iB41c~6W=s+;YTB8VMdS}sQsWaJ*Q=*Y0pGBYG(>3pc zE!>Qa>AJeJ^SIQX?e80a?0Gdktz?VS;@`TGGAP)x`I9-9luDu@8wOcoK6o8z&jdVWQO_i$!0_L*Paw;Ocn0@i+O zGtfH%@r5iixoe;b1zgOh{>FgUUYRS?P6Hz;2Ee!7Cz+EYjFa7|3DV0S%6@+o%;ClS zfC4d173i)P2z~u>)7~-gAN#^e6?H5`?HKpffpE`+qYpTokTJO*KsNI%}k3uxFHMRJg|Nv1jVk?dIfX9 z;fH*JD_C7Le0v+P>dnN5a@4|0M*xUohU!Nd&=b8XP!P0}`x|MfBZ7*>cKPIjf*0awYbs zle4p0@Irqh*B~-l+gvmIHK}ZsA4&R44*Vb93AG?ChpapelAiB%Cc4f?-!u6 zJ^<*)xSlx9A@y%0WOOk7GH(w(=>)tY?)Ep zV`G?)NVdvI3Fi#M$=tjImITs5FXpiRzOQ-+7!k?z3dRgb7Uc`Q_Fn_GLfU*8WKKTE zqCa0lTsX8F?xy}thq~Z!H^dzf|5!c{gb}-jab|7-UWHASMf; zSn&tL^-bU=&BQBfKo;7dChksXza%&8Ef+-s`WTkz6E384y*fF8e`ivx)w!CNB|Euk z5GK_XO|Xzq0cYwvmeRDIr=?e*p(lQH7NBGI7VjjZ%VK$(Z^#4@O+CNo*j4-Cs*qd2 z_iMGv9J+9}m4VHjb${|8qv!_C6Cn*25jfcC4zV(|v{WN$J_)bV^iiS6VDe2`Bvdi% zKPb4aoxir`EpGqfXz!~Q-br%`zy-FeeIw>@hbJ37`Y`J^sq567iJmh9AfiP#s>>!w zaML&>xKtCE5w%DCVgwLk9aJ#i1x}>_lCYM(0?Q-x;%kq}a>H8mJ=q;;#P#kSgFP!2 z?eN?UzP$dgrDi#IM{4G+RhBAVSeXNnN5*mk;8EFE^q@h)j$`tI_ooDkK}$1o1RK}l z=%>DP(DSIKqRI%P7=S4Vvdw{X@7Ug8XCIXR-h;Y_Nn<^mDK9g4cfLAB+B&#(cY}bF z^<9}zfHyqK-m(zKUsZ11xleqKd;f+`GX?K|26NOl(VYB?iT%S18Zz>+se|i z@emYeQ}$0jin-Avrzt@=C-LH|*nk&Hoyu5|tr?(Hv+xAcA~Mf(EsL%182E>EAaWuFGD^M%e77Gv3$F&2EisNr~n-FUP(8?+OvAC5z430m;2_09CMZ3PPFaDfV`hGVb;$8%qQ8G93lgk2CModN9MNH>ki+LOu#cp%dOPS{*iR>g^niN=>^yYq~xo{_@CY4T5dVb|&bg={cn)!tX~msuSvD#O zEXf~mKtR<_E_j;iF8{+ee-P-OpSYcTQLn%gToIPu>4LifS@OHX;J2X_5)H_h1!Up} z+-IDJn`p0oPnK)OZ;b!JD9w1SzQaRtAB@|X_%mLGBl4dk}u5+#7k&uoQw9Jzn0D_$izphu1Ro>Umx|#69#W6pe8Gv>z znu)8TWCLE+1i`1vO%P2hesOZvc^Gc=04$A6N~e8->wZ}MT?WniB7$#53JLRvj~_=& zXl6;|P27Lfn6X$ zApCCWgmCn}9z(N+;c!(sQid#ANDZVpMPIa)W)(f_t@+RO^y%Z=bD8fw12Enu^x#>( z%A5!Wc;4={$H7y2h+w#wJ8wJ49tj62TMp#FtGrt}G<7^f!c~zCAObSCy84(D>681~ zZK0d365J}2sVrTK0sHX7ZjrfB|h&j{v){n;*QQ0_+)>XbOD|;-4u{9?wKqJCX={zh;pKEWu zsINCKVP7z@04JD!tIRimd*72pfrS2Jf7Oz&>?^q^fp+>j-_{x$8^^dYZoE|Hgb}y} zE z^WFUFLKLVU1;O%r1`NZ5hvPtznlyj>GZ5>4B-FK*|8~-Lh+jjyYQA z{NKm5&k1#4X?&Iua=3FrSw#i-n4RQVKP_3I-ZPP(GWf}Md_J>xLrAI*ph6md{4z+U z^Oce3lq-6!?H5vIV&EM_@SctbDtR1Oz%OsC>^CaukLJ{ut52T4T3S3nnY=p$~g z?bxVAT`HPxP>l@Q3~%lN${n{ytS|Pm{vO1dcuSA{+a#?}1KJ(9qTg&1dQ?+jgNM*E z1oy9vhi|2v@lm0@z=xHk=X3~k-Z*H`YgCR!V}al9KlS44H{ib?3BOMcit+jGFw-UeJZ)|vm zA3Qhdr*D0%vC{K1h8QLsbKMjK0S0v9OsUGj&%lWAL6^sXISEzP(@YBezuH_|b<5@N z)ICBL@&_-!A3UPYAhjX0D*Y0*as9vQQ(E3i3zMbY(-s3B9vPZV7qUqb2z3>JK%I)7 z`wEM@8f)})o>CaRYn}wBrDv3d`D-JV1!Fm_T9}V7!FI*qdpNWaCPXPH*&&r5;Jdiw zO^3bC{HXrv6K)J#!~~7q-uN+9=%pSU@Y0b_PvI-@!X31nz&eBOxlySqqwAdFQP?NA z277<~l23TVEmNgGVrhRd*X%8_n@#RM=zXClZq|X}TDJI8!O81*uhGck(pHOTD8Wh~ zGx=Pdi!XO*LbvmEwgDUtz>NvC+{74M_+iHB18Xbg>pj7|a9d8Y(uQRWYeo?BN(FPm zXmKiTTAJNo;x)G!NOuDhyF8d=yNF%9PlO)iq2j9CV( z-F)F)6{}4R^p+M=jBAa?_;(&zFU6;ldz@-qK+u>h(x(Ms`+Wy2fmd|gEPXOLbcZs1 z$uaD(@@2deXt;%Nz24XQ7L7#~s7NC2$TE^Hp0vvQ6hq^Ka1OUe=`w}Cxp+v8nf0Oe z4^ln!e5QcmE+_F2tT!??4kMbdaq)D(@j9sf*Dr(EJ!3Qa=cK1~WS&~L2;!ebZpD}t z*WvO{0=ac{yE>`#Aa~sSj1c)7tXAv%M&*siypcyzi$WUrpOoe_AN6vnL_s8+pOvCe z?4{ss+yKK1Th11_OWO{z^kF;-ZH&TjlP(JWY<0tJrfh5!{3DL-mXD$a5o?A2$;Jm; zer(v0`u`&7F$W;R%bEhgd>|njfS5Dk0{R<%o51%`?IsLN%j*>^8J(&xIt`%1BeI>> zh}M6ZZWl}x@t~HS05hL zLZ~#Q*5x^NtI3-E0Bw7nC~~<9iVRq-bb^`TSM+FxtM+CkW5_wk?tY253@8mJwId-Q zdY54Kl-Z5V1d>8$dR;3_8+ETh7p^GVQ1eBfUGx7ojaX zN8;23a6D}zXNZGMh0~T|GqL?=aM~LHtSa+`Jtk*@xZftjVPM)8o!H~g_nFm zuK*;BF=lzGsoavF*TFG`m0F#SIWl|B&Z4Emf><7uc?i{k3g(u$R|I}{5%Tw?5{!xl z%sDPy-%ku(;-8$g9QXos8Id$AYHkg>9jLA>)6P3TW}L3w{)G&an860yPD|>cM(i#= zf&FF1);X#nq(PdX*1VV05kKcVzqqJsCGK?yS$o6-REX*Oj`O;!z#nLFEagGamIf$5 zoTIaIBl*rR(%T^#RMX`vDrX#y_HwrGpj-Q_c+AmF__(Bqu=4d@8}bnT%qXahM342J z5TDUN3(K@{X3ImqCSqsnnyNn#9s(bVi*c`_F0uB4hrv8HP>N)t`Cv&bh8NKcrfn|! zc&JX_<{ztqJYNd%M2Kc#5!PvM=dWd`*6KMPlwf z2Kn*2`F$yI{A#;%z*Q^2{U188l)JWYqx9Z@CG2LdyTpjr-GzL zxqV?bz4$7wy5#Fdq&4ih)EAoZUvkd6SV2Ver#SX9@ z!8@RQe~dT9?f&8p(b#j2Qt!~_jMmv4klK6&s|WJ6$0J+Jicxa$MH6}1#=r$63vS*6 zuPy}hOsekTI*QW#K*NY2mu~}QU2yQ@xnPYD+}PKEt@X*;qGu>Jzy5?NaDtF*f6$nh zH?s)FfQ>r4IHmgr55L;Qx7R6PjRb`!ImEbo;-QC2ob@}AH+=&}QVi>BQF$q1G8wsK zrxSuzlgEx3eOm^a;Pw1bfMJBxr)d9GLu!&jc|IUqOaQ=m0TR{u)zwuBqiar_vi)X{*ZYaB>6}H{jXc-rGk`KR+@kVR50B@9O`5BM6IDl6Grf}Mxo0-1Q7iBGwI|8NUu z4TsX!ea{~v!rL@%-wc&zuw~#EdRGKBcpwDVu$U8iiwH%)9qqM3F=?$69Bsw0KZvn% zvcB8iCO+_Ni_)ix3pq+x^;;E4k+MvMDCT!iU6ulw00N=IH3;N${l#u-QOebPN%-+> zcjh>Oby+G&?+TJw>oFUbs_Iwf4nYF1Kq0=OVaFF}@Z`V$j={(N?vRfhD z<)Y||ka)vAoM8vm+Ok|urqeKf-ZK>xQ6&(OoEzWPt+q9?>SlDZjZ*gTj6K;aWFp<| z6HO&5XT&Ax<^7pzkGBSbDZ3JH^et_f>T%1P7QCSVMdU3h!(oyPQ!z;4d0R z$!Esfm5)p|AUSUV2E#<+Kq_|ubas)fz09pL5e|g|kmLUb`*;WbK#q}KL8UiX>SXBC zAss>Mg^}K4UaGeal=7qyCK2nG@u0To!@c6>fl(R|S57K7fyD2Kh{1XC9~JVNZiK*%ZvS9|ysX7Xlw7r5lde-ylMImK1a~ zUXr8+9(CkLv(i5)Gcz;Gei_w%z;Q$z`2l5eCR{A=KGGrc+-*`=_&+yG4j>OwIuT|! zOC~))#AgKU4-aZB7$8zQR|vbJ(rpW2aPJ`^|k{{kJ`QwnJRTR}#b za^}t@_+Oc8tbnmVuEO;vpb;M00}u^qc2}KNVPp)y?#6q0_AwVT zm$^7a_nK!gkIIjQ{IH43xO19tM_582*;n9-Hril@3mJGqnx_(x^9lc4Po1XKrZ!8i z?9BdUGbdl;u@szNbxzI1ZFuy&zrXrxBZU`V*B_0m(QNXKr8uNQ2*vQsB2&il;qEA( zqpym&BYEzr7F_hh`mONGYN50L)1Adlu?0htVL0>CEf6lZy92pvW%BjhHCCfs+liQP3Ys-ZzmH$+kEaVf}>7ZD@`*UD?V638fZ{eu< z(42N)c#5#JA7oA6Pahbt`)Y}>_WQRxeLtaX&>JdP#!I6q*&FLrwR_!Lf8#gD4mE~` zgIw!_mUd=s8yne~s_N_Q=%kfKeszaBZ~Wy9$SofDSUua9vc!HZ^=_O0CqBfxgarW& zHh}nFP+i9qs2t9Mtil;c`TyGD1>#ezgO%z*>&=S{R4u*MP>}3t-`U0Bicve)ac5Uo zu&QaoL1kv*n`%cLt4lKA_2XclhSZxfNzl+zG(%jY$W49yXEX>{J3E}m8kDF6 z9d9QSy7#su#CkPO1%M%{7fJ*3Mt}MLzTTT0ITlThun7~NEaei(fKbJLz4sav(G{#W z;`fq_K0)ig{ckE4*i2>qc#d0-Aw=z?RU@~#<0B`h{&+Cc<~yX$3yPIi@0+k+3JMA; zYii;j?s{OlJoGOzW40IckZArd2-Ci=Zzyu|=%PiU3AU)u2? zs2qgMbPqTRYXkpp2pigU42+iY0SPw@frU~7W2OCr*Y0iKVL;C|Xsj0?{X`FI6k*E= zindxX@uNXk-Vmin2lrPoORqUjy}3)7gcPFy1sV&gaD60^^!}@~YzI}{hPRXkwcfhv z4PUrsdr<><{Hb(LqdRl<R1j6uJ9qX9%e$06w76F9{S?2YC$1}Q zc%kVg(dEP9j{an5dz-JSdb_LS-hT?1C~9DN{VzZkU7*nCaviV;OFE6qJYNZ}BK75N zj%Nf@Rc~->UHKXofh^}|Yw>#D8o+B*8uH4~v%MmJ9)Fr;Bx42EFb ztq&p}kIYOlS^$bX9zmmR{L5pakqMwqGAVnM#Tgi=&*&VU)>QFZ+}#gncIYgN`?uBd zp#T!=HO`ooXrczkT;x_bH~5cawWFRJN!X>{r_{UzS- z(m||A(82yA3_S4OWY)eHC3Z5rgEQja2>7E>uQ=NA1C{U0au`(JXYq!RTnTp{_!lL@d)_+Kfn07U;>Mk1lbW#bkVx^o z^{5UO?HUBUh$fkIGnM0o6O~}7oy(34eiBtB?7216N4B#~4w0*Ma13P5y5<_zu2tf5 zKr~pcO-+T-*I{JwTUkyHtB3I<5w;J-^Qf9lKsr4H)Ns@L%Jbf3hF5ezjiEMf_)ZLf=sO@yt zlYfpw9Eb1r7=Eon)nzc(*>xc4Rs*9TBES~J5k~o#BR(s;sXO!8vT>8||Dr?G(xb+h zwIAZva?W%P**+XQ^7GvlQS7ch(@D!cMck8q4sk1@x!9jNH(x5x|6tR6*UK1}&8{+! zAkAs6O%4zuvxp}SGZ|7a_r~u#R$YcP1&Bz|-*=`ch~L|#mL+#Uv>K^U?B???=(t1# zGsh3m6hE5ZPTv0NlXUZf)IW+vsiXy#AopE4rL)ETwf0YLA@ZHr?;UVZzHX}jtZa+A z*mk~pUM;MgOd%OFx%X<5Zci7~YU}@q<6f4fz_GAa;_VLlKa*#II%@9<{ z$lNs37r(3+w{{li7Eg3I(vH$z<*(7YAS|9vH%^2!Y=A0Jx?)D9|jlK`ZEhrIU z!J+S0g&(?fNQ?`OVAAXnG;*tU;f&Z+El!Z&)XdHv0oxP{Dk&@eK!9Y9aKr{c?+f&; zVxyLnwOLQfB|of!G)A0a0^-NF>V$Ss8Quo5ewm*|t_RWm7T6SkOB_HtiYc-Vx(wa; z1jARtnZX$ynF$!Q9pbAp9O4#B-wHn*LXBkFt+nS4C89nl`?b4gVLug(5KZh1V*P^IJ2T#HKxB zs?+(jkx@bg{!JxJ{5l!3-74@XF}`Qni^>MX6d+AqchisvssE${>NnRn;FbVTPj+sa zoB;}RZO1~u-hbG|tDPrlPBT>Bxo6lpSTXA-#iE2<#j#Fq=hCPg)K7N*u0JFsS)Xco zVYC2_!VhcxAZb6Db^T4jio7o)W@SowGiscL4JlcR2{LzVbdJ7P?b-Eh+yIZuXyjtB zb!tYXp^vcLO4cM<%Dz$$*DVGZBC78ItKGKNknD7x2NDX(k$%eB;jjoz$XAUj1CmaG zlVosdX#Xi42iTxMb`GlXr6qUJQ2#uY;hV$Q{4-sySmdIG$%D4rZ0BqB;MjXSJZ~~A07D>M)Le*4vvatM+>X8k2#mue+xOaGdw<%?kA)3 zv9bz|`Jye6Rv#{%w^&-zf65H4H9f9TyFU|n4R!dmY^)S@8F#ir(Uh4(TZqx=&=sHY zIZ)YlL$YS6*8D(YWG#PPg;j- zVjsISgTHi)3&4DkRT#t-kJ{CvP+81?ITQS=hJME@;>$AE$}fmlUew!B4;da|v3Xuf zc6FN%Gezmo11uFS5>n+{ zufVdJffImW$M5kv!Y$tZDhzHpFl{I1`M{_$Sv85K9QPNf_@pnWZ#8=}gt$q9uId}V zGF+rDAcl(@xEh7_Se~sAhy>O?lOPa*TP*8*OX5of2d=L8(Z7i>revyOs2y7K!3%lt z5#lX)3pb>A^n~k*0wO8*O2`l-**<-)ILhr)CwpQs-$xS3b!*pkx4`YOttL|+7zkmZ z25Prr7*{3DKX%^kOGFr)%DFDqJ`;ZYfBw~|Lp>MfZnacblx%eT?-4XQs=LhdRwcmy zl4Ev(6Ak%sVDRegmBV0=#k~U@GDcXSx`%K;(LQ=Ge(1s7p>V6pc{X#!nxGhWNnHT* zVA#6|Q$lM{-HL1_@QE)34Huzi&_>$ej5*D}NmS6%%octl7d$q3Rk5@%GV zIO4%?oCW^B1%Th4MJiONz<94FreYiFyU6|trT*Stt;14ZJ#ka<8OlC}!?GqE`06Ex zewBTJHH~we{Pa{Dmq=gq+a8A9LQh9;K^f|l_9ZyiuSIyX`T2zep)XG@vVbY!qv zVI{Cej|x@L;H)!^qgB{r|9k_Z@O9*3pD7k^GGB)%JWyXBfC>BdpQCrCd>D9NFvwL| zS}Tl;o81i&e0BAahC|#;U6Cj6Wrsp9K&{3>Z<7KCQ&A9r8%G4(g9o|CwQ_Q_W$p=l z7^l#Bd8~2!j)!3ALaY8!XmJriTaWAJ$q`~gC>2YKwCbA!Zt?(Cj+Q}9fF`|rAk_)3 zw8?y3$E6!hs)dN4#lSTnad8&lo$rJ(9}52U&bUd=zjZFcubb~b2xXx)M1 z`F#0O^)g0{{j)uN)FVH5YwBcz<3e;rQ(eTv$@qH@3zP8XlI9g1TGDzF=g#+_5aYQe zh46Z(tU85~U|B9L9X-b-8X>E5*VZ`yLlqv-11qhKzV6x3xnE9w%yz@1jc7RrMa)=^R|6R;o#h3l{^KaAMtFJs^ zdhHHeC|^Bz0y@;yWZHIVq^)hK8r`zetXU^4g}>Dn5lDO+nwk!GL0t0h3$Vkst(HEc z>|MK6r^5rwc#(Htv#%U@rn~hi5QkT0%o62}-Fz8Wtx_m|tH|P2nR7jOM_1v_4RfaO zDb3fy>S;U+5DhakXz5>;N?RMVr52_^3$n?DDILJO%a`r?T# zCq(%@jj$GH_oNhy%3HD1&Z@ix7yI5EnpEot7neM!dZbBxv%$r=-ADSI{UllizQtIca>?%(!09N`4O&zh z$TSL+2b6zw2?wpBTHyu(c}pIf5+eEZ2GZ{S?G4smO9Z%_i|5nQ!5C|Vdr@|o;T_%_ zTIrdRXT7wA^524R1*cQQwj#e`&sJ{sm6jm%`A^EgePO87$tFxlLvxUUYO(^AF1 zAm*5#t^4IWCJqc|pt3z^DjcW-a0hSW_`=JmG*6N9M+Z-owab0vU0 z-zOa+dsqWl>2-C`2}AKwKaIZeBHiQOLT9hfDKYKrQdvP%XR)@&KmgmGU8*Rc?6V2d zOZDaV)LfI2leG$~P=!1VAK0!``d-^U`uSWgb4&D2v6zKxxV1PpR{v7Vuzq; zD&|)8byM0Mbni3ogee1k;5iqwX6vA{H|#(37_#R2k45|5g?Qfmt`jU?oDp!IqXi*3 z0+QQQH2Z{SnkSame-7CW8RSEt*s1$1kUV}GO~V7CW>Ji9+JZDEPeSI*a~HDtNNL;Nj3F=!$pj$}181s}uwnxxcteM}Ks zWU4Q&pf7H?GCoeI+(yT~tw0cu$PxH%`f>@LHbXl(76SF8Vs;T%^K4-Xp1AhVE7>pLJx6 z^X%QvFG2qa#O>jz$JPy6mY6`21LLj5& zaFu0^<5s3MU+kssEuLzt(Tk3S_MlzfiLiD06gN<_Hzk0)-MIT+i2ChSfR2KY^huSY ziPVU{GvzRfas}*$MT|I{LWbYpS5eG=`j9HIJV0{4X-rDgoC#8_D{d{Cb6j{8Ys84TCG@#{ z-}y0AT=H??IQ!wwJ89ZMaKAg`8a1LY=G%(F1qTA73=qh6KTu_t`3xj0zpucbpIQ6tYI+y%`tGJwivF=1BWtRY;~xIP6Nf>ba!}j!<+yD!JkKxwUZD2|Es-}yG&NXtp;e?T2;ui zOSxg83UEJJQBGL)$yuW*qt(zC$u9$vD^3?ny{ISSS_p}{kJCf)m$o~Jv_6H0Y#6HM zL?agt5$3v12H{Q>9v+@4ht{N%TMFA`4iSayZe7hstC7tkZ z-@a|_@d@V-H-&oC8tc)t$^es!HByA`@IxLJ2N}!PRQau;uvcy$7d0h^(VfAC1unlIV;XN$>y| z_pJ5nt{jLU5#$493Mvpu-sjy1lV&vD8!2|FZ)|UlgQB^Wqr;_@sG0Q(etM*1rg)*`6~8Kf>WG`_jQ7Ur5Z~jb7-im z^DreMme(c?vjt)Rp7nED`I+vla4h-+(odeaJvvb2Sm-5{a$4s_ebEPo*1>E!_Z9vD zFLb`^tM%EpK3r7rn)xB~yUztiU}Zv;9(+g7uR*|AkfHS_ zfjeD98&k!^(2|7VC*mJS*|lr`t1)Cl90v$j2zcI-7Y^(Et+`f~X;6?F zxY^AiS!m8Lg9;3|ii~ByB`1y&eJp)y2z6nh14O6#f4-_DS?1J}G(x(L46QP>Eshzp z?8;m)!vOnV=(nKJs*8v2w;=g1Y0)Km7;-ru!699ZrlGky7S7fd-C6R{tY9Vu()%g? zw+#KzugwZPwTbXz5`>8A=6DG1wEQ>tb%%OdI(23 z*@sH}_R-R4fT!iJpi?W$uFFAPL34Vh1OG|huCm`hALm?Rf7h6w`>NpI9}9x?_c3iJ z(z|57haQu@ns!OZ;o%jtY`Xu9irF6Wzo3bzB9G}4znY$`$bYJV^rb1^e@K;Ag{=0tJSxH76MV!_e^xxiBMMB%_QH&;8Hr1OP zax6|)2lXyHObK%Yhqwl6?Dd{JNIE5|{HKOovptmTu#|H5D6zPxsLLvNXU3sW9!$>} z#}cou$_J0vYM26L8pVeJf6cj$*|okY#bmLV6TPoWh_8b{lS&{_WwZ%myQo3A;tOKt z`vPPMP8H6tKnEc%Vma{R=K5!M-fY!xDPl1bWK%l-Zys$W{(lln6EaS3*WAkUW4Mr` zs4eB~UFd|}ln$9rA3Z;4bSShfOahSeJ`6>zT1Ayt*U(nc3;hqfJ1^7Ta_ZT)vI|=~ zM?nlv72{y;M^A^yCZ-EJwQHp>v}d(+2f zi%sDeF*BaXge;E22baPg9jzLFbC*gPj#%~dddxkX$>GjxxW@Nic1_o(G%*Fm85}}gR#!^RB;k%r7Zu(hKA@6|fy~|qfWHN4?EUVJs2$Z(kPb!rYrye%ei1aT z66?*)#Mq>6=|O;MX=y3+)C#4=#mn)rXJ6!q#RT~Ki{|pSj7Nq&|6Yj~JN_)`0mnra zZ{moO`_DI5GV1`Zqyr}L?6|!%O260X z5`BcG(4}5=!dp>jQjNE5kO{*ZaW(YTzz%;h)GK z9b_T{P7h}o-6t;8OEO0w1pF|+L(05+sl8pG2J=}7a7|j6&sQKM(mOIcl7p4_;_kHn z?pLw(4yQDJ5N4 z*e}E-#A>`Grh@=_&ISH`rEz~kp{b>$_7T&!f+s=#lS;tT6n>&+7=$T_2X_?cY@Qtc ztUdIAjX!<$GbTW;t$F5Tf*|$ca`0CDz;&sdH~Xt!balwT+`Ykf@4&jsb^I_mXDaA2 zjd196@|B~V1Fr)QL-mlXRmsZ@y4Kd#wd6TzO8@HV{_Fmp*-AyVn|bQ4YQ#v_6*>Hh z>)BZQ<1@I{fY`GGukwm5^QBk|%h~iR3pD5Wj2e$eFd#^PvNRu)6%DpN5n_Z{t z#&Mo7&#a4&s4W}{gHJ5QH{zoyg>DCRla|0(!D5gz%e=F##6QaGH*PL@Ma6HYSKZv6)9Pc<`2pQq#1qGw+}UChc(JtasTBQ`*?y$Q0zgw{EI;b&(lVG{RY zw{TfNO~7@%Mj2EnnwuW8Ra}aHTY(?fe`{>;)>M6P(qkvmcAM4FnLC<`A|zdqS_ZZQ z!Qy_Kp2P%8Q+zg~^>_=VmRo^}LW)szR2(xqIf>}CRL|0=ZESNXUfEk|>|nE$b0+SI zfKap&`+oZc6OsSx~NA z`e3v$zN{L@Y$wS=bj|W7tsAA%{SV_s0|WCX~;b6FNfTvWdTvB;rPj3#0ZQ zL$Ft-=Pa)Yd41IOdV73~u0u@#Qo_+}4S}N#Z4|crA6W&pm~DU(o|VY6+URcKG~PMf z4|?v2MCqJhY;_Ckq>-5z5(X5oW`fn6aB5`da$RI%9JOX|l8@D-K{q49(#s+&25}Q` zF>XMw%}>z z6D_zAFjI?yMQ3@(k;!ZOH*H?OJ_wCf$PiS0?vzFM^wY9KAJMV$A&Z3iFFBIA&_nGd7rGjl8&9u9U(3pefmv#w)o)}@=_#z^-L z&u9F=%)WS8pfpIYxp-guus0|^*x>>k*a5`bFpK_o=6E-0?7AhrhYliK=;d=hO>JP5 zN(GSvw z=zEz@h%S?q8BDSob4yE>4u!wed)!vM>jkObm`S#2G4IR7)wEqb98%~OKFbJqcMkq&01GdBc`qLk##6z>QP^7 z2zTHp49$GI3H|L(CYUMm$v6v&np-~OhtPq$nGJ`#1lK!_{$BaG_$?3F9Fb@~L|N3K zuoMo^_fQ$4)xHUrou^dFi@T){;amKH!Y%`n9o`TC?i)Qfq_z|r8*74c7*L(lU$EAB zCrp0<{Qn@G)e8KO+h{1eWrS(Yzkb^YJy6A*Re)0T2L>yvI-AFtqu>~6&1BXB^b6eT+P(9 zO!7(QGl+Mm;R7wDs|W2?p%QltA%+^BGWHWxZrq<_u>aTIxxYiThJAbuYuh@8?cqrz5$N=!Ly72`aNLuPD;B5f6oA`B^#Q^x%7SL5W3T)9sC`|%z1!iKINC=5qew;xUfD8IX2=yi zWrO9}=jX2Ty-aS*-yzR+SFUjt!(1kB?R*q5-x~EpY%sq@6A|-ooy{3(s*a3oT=~yq zc5@70_`l3Ue`D`n3|xDv^w@>V^JpZZSLSB&ToTQ$3>hGIrAo}GX>76IH<1r^sy0Kb zb|Ug)N7XEEPCo+)!!!5E^5Ei0WA(15k$k)AthJ)`%ZGp#(b1D`UcoQ{io&|}!F}(e z`R~f<#D)m`vGWMDa|hC%C&C!Fy5mfZlZsxY}35zdTvqlAce@6 zuN^)}7NAq+Qut3$^osQ5mBBStUihGxSKgcRdxzmO2k<^d}${GQ)k z$5bjfWcys|GjDD=cJjZG+1-UMRXeWKs(!*~;IotO?rd=btV=iYN#&ONFbU(5Ud9pn z^aC-FmEWDd z*>96lc)Y23oQ42gdV=!0w>8#iPBHQGITx2}-q|%8W|*8DX@%yJ(_(d>8t7Qh7cIZFkW&~NipKwkgB&IUL8N_d1wp17aIBX?Vx_gRmb7}!1XBI z@He?u37JCCujIZ*40XmlY~G^YpU&8~SL5$>oOcQcL>pQTby+5TX!pzt|AxjkKl)c# zsq_V6jZ+J1mIvX^@YvXgQ2085)??T;#^mjIj?%H6EuSEP4uMLjPrUo+K;td4h8F$- z#N9o(@GdQ9T|c@fopHGR#p>L6c}G_J^BA~}@;18h%+e}8uZwu3UxF(y-2B{0mqciW zWnvMkWs6&miKttj`AP#1JaPpG=tWw%1YVhB+aCqH0VNbo`=tPb)#yDw4kFzS`4q)L zJcrlANcM0hSU?z>+thw)fQViKAj49~TG$cCY0tA9(00{m6SL}C?0MzCwDH+FcT_T? z*Rvf5Fqb6abax5iXXwyRRMq2sjZc$7p;CO9%sVLUzeuMj^lfCn z1)pTUkdB{U`@za1JBPY0&j4UQ^8XslF@$t85t=%}4lF1raMqIh9Qha&#@6xf6oGqi zmf`oK%og>F%dR@dh1>uiUhLD66_wCydjNii)3MWJ&~jAVd0nvMwnZFySdJAP4)m9a z9mlw@{I*)x;;lLXTsVLkwF|Fo3QHmsRoX86B_V$?xK|sK#804%Zqr7(K!<$;R`vbR z*yx}q#-uiA;YuM7X+2eb{!b!SL~S*1;*(KhfDdy7k7@%~S!O6g7 z&8k(HcPH^k7c@%jLQJWom46KibIU@mU)Q4g2xWn6Ki>klpA9%(Q~Y(x6VOaVKv`ix zXw=>#9`)d{h-fq%spW>ZD8+tMNTB%MJ&JgNE${J`2iJIM)~?3y{-~EP@rJ*QsJ|ii z;UT1Myel?s!BQAHZ{r`_OEdhwCU8Ufb27kx75@DY2`}W?E{6+mb6)q?Pw_>1+~i^v zfbN(dwIof`{;Au5z?~h=H2i)CiGcciDnZN4w)76>Q-4?2mG!e*WZ_=45wD3Jh7Dvz zitIF)f1H|~6PeF89g&XX!qzrs^-#COo{c0?GQtGA(=kY4<=N|Fup|@jygqmlJ1#tMZ^M*_XM1u5XYDzi{+79r-2534 zvA*d}0qN_1(B-zE?Ad>Cl5(k+x4M&E1TFnmet<<~zPC~l%%QE~2-YUn-=vm+o+IXw zPM`$K1AlK1!BKNfR{##%!a04_`{391hr5V}@;e6{y->aCQ^{@TVvt&BzF{$= zdCQNQR$$FZ+9bq2N$!MD(}C2b7lzRs6r61LUVqmKj4g{d_Wx>k1R2Rcc`O$W@|9Wp z;TX&)a2g3;oV{yl6l$Et;?InWH9AxMR%xcDD2m3T0{<2x@clAla8_2I4NS=yv@G^v zg0|jYssclpXkf_xX|eHz_-%2J=(mH<##LL>%; zwNmBnc0kcNj5rd>`{`P&42o7oWIR-JjgW*;RUFh+7f%3WXbj+n`Gda6NduJuE+*2A z17g$CZJCd}N?e1}a(b0!S#VyK&#WpFO#WmsoDC?A>|hM0Bjfga4;(pw79La{RykBa zjc_4Hb^Aa$=2VgVlb|blV&Z2m41h=_h@f?DjEio)57NbeZ)w#3ma2&XJyI(|>EIdVYxYObl! zP!S!)LRxSP#H3M3s77g(I2LtE@d;kZ+~p8WE|p?8NykF z>$s$nYQH>V4X0z#Eha6CFJ~YZumVwoVjp;gJ=Am}oov`(-c#akr_3hNe=nf9yA7MC z!i=28)R0jCDAluocsFK99f_q-Mx#2BOK*+4?`~I+bUi@`S^gdDc}QAuuZ+e_rV!!+ z%WaGmv`cMOYx#^=jm2}6q3MC<Pk8={N-~kQD`4!n?Eo@L8(DO;II6@V<88Dj zn6Y;zmuX2usM90}$1*BS?zcutumjI(o<6mILE;9LWWV4O_$|1)7|?;fe(-WGF@>;n z#sxu>Cfp#OgoZLZP7?elX3ID7%UKpcVY7l)JR;e8O$#o%6Ql97ySP{)j;o94>*fs^ zNYg2>5FPuxZ{)nFN1l^ntPh-K6^)LLz8=!d!uSRRyj2VP5~P^$dQIfr&6#m@*h~jc z(izL4-cB?u7H0BcBU31v57C2iYh>YrqU6mbL~Z|GZR-Ol-s;7CqTU&}&3-8^9}p$y z>Fc;dQqv8}U)rd!#{?t23&PV2?n0kSLpB#)+in(Vmg&Pc%_I;bhy2sikwxw=F$i7Q-6TaEn9UkXA*+d;nm66+rpYtW zq1Nb-hByIPK9kcY<(5NPZQeWbc-MfUB4q8EmOpaukAWsF{HP^uO$E`{`XHm2Xb7P3 z!)ZE|33+1M@wFA4g)9KT&VoRPUB`#7QMbf30I)U#^QDM7W{02HamMhe*Eb7S7q-t= zsmAF6Sm1d}0NB<)1HQH*u*sc5>K?uM7IANCQ)CjV&7ML&eFg`vWiSA>*|n!fTabZQ zzg~U>B1I{fJ8GgO+L?|n>7m`uI91u_G~agEvooBbIaH}r2fxC*QXlhDe_t+RK1K}= zO<35H?b40vu$^B$2h-5RZ~`7Vs{XXi*M*!2?iC$S?@6SX|4<3fbD|NORj}6sSGV81 z8JTcSTD9*LGSnn(>wi~AD^wl&QdoLtYvz)n&b?>X`{`Yuk|DYn=sF9LBn5owfQuLF zOO9-dJciaJ8m97=S{}R(?Cm_g;>*9ssnIVDvzqsdXK*Z2h~B4c&`N>N69Y(Q3gm#c zx7JRfvVzopP-~l5|K{{J?x1%Ifvrp$R9p&47p#`6#82lr4HwLh9AxZ z!PxGep8%&NAdN<-S^Fs6f*!jlvB&Wc1X_|XE^`$eCJ3+}fm;9xp%oSsWLyTLR%7YX zbk`tJ2>rg}vo7=N^|0^OE(@Ei!drj!5B$h=8kgdOSUx#|VetQ-|7%acD0fZ9vEi>H Thn4OtAn-Y0?}!%Jy2bquj8Ot; literal 0 HcmV?d00001 diff --git a/app/static/icons/icon-72x72.png b/app/static/icons/icon-72x72.png new file mode 100644 index 0000000000000000000000000000000000000000..93dbf0488b41f75010a5622eb291720d63884235 GIT binary patch literal 2456 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 NOs` literal 0 HcmV?d00001 diff --git a/app/static/js/compare.js b/app/static/js/compare.js index e23fb05..d8e9223 100644 --- a/app/static/js/compare.js +++ b/app/static/js/compare.js @@ -568,20 +568,94 @@ function displayRawData(teamsData) { }); } -// Make sure showAutoPath function is defined globally -window.showAutoPath = function(pathData, autoNotes = '') { +let modalCanvas, modalCoordSystem; +let currentPathData = null; + +// Update the showAutoPath function +function showAutoPath(pathData, autoNotes = '') { + currentPathData = pathData; + const modal = document.getElementById('autoPathModal'); - const image = document.getElementById('modalAutoPathImage'); - const notes = document.getElementById('modalAutoNotes'); + modal.classList.remove('hidden'); + + if (!modalCanvas) { + modalCanvas = document.getElementById('modalAutoPathCanvas'); + modalCoordSystem = new CanvasCoordinateSystem(modalCanvas); + + resizeModalCanvas(); + window.addEventListener('resize', resizeModalCanvas); + } + + redrawPaths(); - if (modal && image && notes) { - image.src = pathData; - notes.textContent = autoNotes || 'No auto notes provided'; - modal.classList.remove('hidden'); - } else { - console.error('Modal elements not found'); + const notesElement = document.getElementById('modalAutoNotes'); + if (notesElement) { + notesElement.textContent = autoNotes || 'No notes available'; + } +} + +function resizeModalCanvas() { + const container = modalCanvas.parentElement; + modalCanvas.width = container.clientWidth; + modalCanvas.height = container.clientHeight; + modalCoordSystem.updateTransform(); + redrawPaths(); +} + +function redrawPaths() { + if (!modalCoordSystem || !currentPathData) return; + + modalCoordSystem.clear(); + + let paths = currentPathData; + if (typeof paths === 'string') { + try { + paths = JSON.parse(paths); + } catch (e) { + console.error('Failed to parse path data:', e); + return; + } + } + + if (Array.isArray(paths)) { + paths.forEach(path => { + if (Array.isArray(path) && path.length > 0) { + const formattedPath = path.map(point => { + if (typeof point === 'object' && 'x' in point && 'y' in point) { + return { + x: (point.x / 1000) * modalCanvas.width, + y: (point.y / 300) * modalCanvas.height + }; + } + return null; + }).filter(point => point !== null); + + if (formattedPath.length > 0) { + modalCoordSystem.drawPath(formattedPath, '#3b82f6', 3); + } + } + }); + } +} + +function closeAutoPathModal() { + const modal = document.getElementById('autoPathModal'); + if (modal) { + modal.classList.add('hidden'); + } +} + +// Initialize modal click handler +window.addEventListener('load', function() { + const modal = document.getElementById('autoPathModal'); + if (modal) { + modal.addEventListener('click', function(e) { + if (e.target === modal) { + closeAutoPathModal(); + } + }); } -}; +}); function createRadarChart(data) { // Extract teams for comparison @@ -658,42 +732,4 @@ function getTeamColor(index) { '#FF9F40' ]; return colors[index % colors.length]; -} - -function showAutoPath(pathData, autoNotes = '') { - const modal = document.getElementById('autoPathModal'); - const image = document.getElementById('modalAutoPathImage'); - const notes = document.getElementById('modalAutoNotes'); - - image.src = pathData; - notes.textContent = autoNotes || 'No auto notes provided'; - modal.classList.remove('hidden'); -} - -function closeAutoPathModal() { - document.getElementById('autoPathModal').classList.add('hidden'); -} - -document.addEventListener('DOMContentLoaded', function() { - // ... existing initialization code ... - - // Add modal event listeners - const modal = document.getElementById('autoPathModal'); - if (modal) { - // Close on clicking outside the modal - modal.addEventListener('click', function(e) { - if (e.target === this) { - closeAutoPathModal(); - } - }); - - // Close on Escape key - document.addEventListener('keydown', function(e) { - if (e.key === 'Escape') { - closeAutoPathModal(); - } - }); - } - - // ... rest of your initialization code ... -}); \ No newline at end of file +} \ No newline at end of file diff --git a/app/static/js/scout.add.js b/app/static/js/scout.add.js index a8f4597..9d37197 100644 --- a/app/static/js/scout.add.js +++ b/app/static/js/scout.add.js @@ -59,7 +59,9 @@ function startDrawing(e) { } function draw(e) { - if (!isDrawing) return; + if (!isDrawing) { + return; + } e.preventDefault(); const point = getPointFromEvent(e); currentPath.push(point); @@ -67,7 +69,9 @@ function draw(e) { } function stopDrawing(e) { - if (!isDrawing) return; + if (!isDrawing) { + return; + } e.preventDefault(); isDrawing = false; if (currentPath.length > 1) { @@ -123,7 +127,7 @@ function redrawPaths() { function updateHiddenInput() { const input = document.getElementById('auto_path'); - input.value = JSON.stringify(paths); + input.value = paths.length > 0 ? JSON.stringify(paths) : JSON.stringify([]); } function undoLastPath() { @@ -145,7 +149,9 @@ function resetZoom() { } function zoomIn(event) { - if (!coordSystem) return; + if (!coordSystem) { + return; + } const rect = canvas.getBoundingClientRect(); let mouseX, mouseY; @@ -165,7 +171,9 @@ function zoomIn(event) { } function zoomOut(event) { - if (!coordSystem) return; + if (!coordSystem) { + return; + } const rect = canvas.getBoundingClientRect(); let mouseX, mouseY; @@ -210,6 +218,12 @@ document.addEventListener('DOMContentLoaded', function() { const eventCode = form.querySelector('input[name="event_code"]').value; const matchNumber = form.querySelector('input[name="match_number"]').value; + // Initialize auto_path with empty array if not set + const autoPathInput = form.querySelector('input[name="auto_path"]'); + if (!autoPathInput.value) { + autoPathInput.value = JSON.stringify([]); + } + try { const response = await fetch(`/scouting/check_team?team=${teamNumber}&event=${eventCode}&match=${matchNumber}`); const data = await response.json(); diff --git a/app/static/js/search.js b/app/static/js/search.js index 4ff9b07..f7e1aff 100644 --- a/app/static/js/search.js +++ b/app/static/js/search.js @@ -1,21 +1,91 @@ -// Define showAutoPath globally +let modalCanvas, modalCoordSystem; +let currentPathData = null; +let searchInput; +let selectedTeamInfo; + +function initializeCanvas() { + modalCanvas = document.getElementById('modalAutoPathCanvas'); + if (modalCanvas) { + modalCanvas.width = 500; + modalCanvas.height = 500; + modalCoordSystem = new CanvasCoordinateSystem(modalCanvas); + window.addEventListener('resize', resizeModalCanvas); + } +} + function showAutoPath(pathData, autoNotes = '') { - const modal = document.getElementById('autoPathModal'); - const image = document.getElementById('modalAutoPathImage'); - const notes = document.getElementById('modalAutoNotes'); + currentPathData = pathData; - image.src = pathData; - notes.textContent = autoNotes || 'No auto notes provided'; + const modal = document.getElementById('autoPathModal'); modal.classList.remove('hidden'); + + if (!modalCanvas) { + modalCanvas = document.getElementById('modalAutoPathCanvas'); + modalCoordSystem = new CanvasCoordinateSystem(modalCanvas); + + resizeModalCanvas(); + window.addEventListener('resize', resizeModalCanvas); + } + + redrawPaths(); + + const notesElement = document.getElementById('modalAutoNotes'); + if (notesElement) { + notesElement.textContent = autoNotes || 'No notes available'; + } } -function closeAutoPathModal() { - document.getElementById('autoPathModal').classList.add('hidden'); +function resizeModalCanvas() { + const container = modalCanvas.parentElement; + modalCanvas.width = container.clientWidth; + modalCanvas.height = container.clientHeight; + modalCoordSystem.updateTransform(); + redrawPaths(); } -let debounceTimer; -let searchInput; -let selectedTeamInfo; +function redrawPaths() { + if (!modalCoordSystem || !currentPathData) return; + + modalCoordSystem.clear(); + + let paths = currentPathData; + if (typeof currentPathData === 'string') { + try { + paths = JSON.parse(currentPathData); + } catch (e) { + console.error('Error parsing path data:', e); + return; + } + } + + if (Array.isArray(paths)) { + paths.forEach(path => { + if (Array.isArray(path) && path.length > 0) { + const formattedPath = path.map(point => { + if (typeof point === 'object' && 'x' in point && 'y' in point) { + return { + x: (point.x / 1000) * modalCanvas.width, + y: (point.y / 300) * modalCanvas.height + }; + } + return null; + }).filter(point => point !== null); + + if (formattedPath.length > 0) { + modalCoordSystem.drawPath(formattedPath, '#3b82f6', 3); + } + } + }); + } +} + +function closeAutoPathModal() { + const modal = document.getElementById('autoPathModal'); + modal.classList.add('hidden'); + if (modalCoordSystem) { + modalCoordSystem.resetView(); + } +} const init = (inputElement, teamInfoElement) => { if (!inputElement || !teamInfoElement) { @@ -60,6 +130,28 @@ const performSearch = async (query) => { } }; +const createPathCell = (entry) => { + const pathCell = document.createElement('td'); + pathCell.className = 'px-6 py-4 whitespace-nowrap'; + + if (entry.auto_path && entry.auto_path.length > 0) { + const pathButton = document.createElement('button'); + pathButton.className = 'text-blue-600 hover:text-blue-900'; + pathButton.textContent = 'View Path'; + pathButton.addEventListener('click', () => { + showAutoPath(entry.auto_path, entry.auto_notes); + }); + pathCell.appendChild(pathButton); + } else { + const noPath = document.createElement('span'); + noPath.className = 'text-gray-400'; + noPath.textContent = 'No path'; + pathCell.appendChild(noPath); + } + + return pathCell; +}; + const displayTeamInfo = (team) => { // Update basic team info document.getElementById('team-number').textContent = team.team_number; @@ -113,24 +205,8 @@ const displayTeamInfo = (team) => { climbSpan.textContent = `${entry.climb_success ? '✓' : '✗'} ${entry.climb_type || 'Failed'}`; // Create auto path cell - const pathCell = document.createElement('td'); - pathCell.className = 'px-6 py-4 whitespace-nowrap'; - if (entry.auto_path) { - const pathButton = document.createElement('button'); - pathButton.className = 'text-blue-600 hover:text-blue-900'; - pathButton.textContent = 'View Path'; - pathButton.addEventListener('click', () => { - showAutoPath(entry.auto_path, entry.auto_notes); - }); - pathCell.appendChild(pathButton); - } else { - const noPath = document.createElement('span'); - noPath.className = 'text-gray-400'; - noPath.textContent = 'No path'; - pathCell.appendChild(noPath); - } + const pathCell = createPathCell(entry); - // Add all cells to the row row.append( createCell(entry.event_code), createCell(entry.match_number), @@ -160,9 +236,23 @@ const displayTeamInfo = (team) => { selectedTeamInfo.classList.remove('hidden'); }; +// Initialize search on page load document.addEventListener('DOMContentLoaded', function() { - const searchInput = document.querySelector('#team-search'); - const selectedTeamInfo = document.querySelector('#selected-team-info'); - + const searchInput = document.getElementById('team-search'); + const selectedTeamInfo = document.getElementById('selected-team-info'); init(searchInput, selectedTeamInfo); -}); \ No newline at end of file +}); + +// Initialize modal click handler +window.addEventListener('load', function() { + const modal = document.getElementById('autoPathModal'); + if (modal) { + modal.addEventListener('click', function(e) { + if (e.target === modal) { + closeAutoPathModal(); + } + }); + } +}); + +let debounceTimer; \ No newline at end of file diff --git a/app/static/js/service-worker.js b/app/static/js/service-worker.js new file mode 100644 index 0000000..2498b65 --- /dev/null +++ b/app/static/js/service-worker.js @@ -0,0 +1,53 @@ +const CACHE_NAME = 'scouting-app-v1'; +const ASSETS_TO_CACHE = [ + '/', + '/static/css/global.css', + '/static/css/index.css', + '/static/js/CanvasCoordinateSystem.js', + '/static/images/field-2025.png', + '/static/images/default_profile.png', +]; + +self.addEventListener('install', (event) => { + event.waitUntil( + caches.open(CACHE_NAME) + .then((cache) => cache.addAll(ASSETS_TO_CACHE)) + ); +}); + +self.addEventListener('fetch', (event) => { + event.respondWith( + caches.match(event.request) + .then((response) => { + if (response) { + return response; + } + return fetch(event.request) + .then((response) => { + if (!response || response.status !== 200 || response.type !== 'basic') { + return response; + } + const responseToCache = response.clone(); + caches.open(CACHE_NAME) + .then((cache) => { + cache.put(event.request, responseToCache); + }); + return response; + }); + }) + ); +}); + +self.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys().then((cacheNames) => { + return Promise.all( + cacheNames.map((cacheName) => { + if (cacheName !== CACHE_NAME) { + return caches.delete(cacheName); + } + }) + ); + }) + ); +}); \ No newline at end of file diff --git a/app/static/manifest.json b/app/static/manifest.json new file mode 100644 index 0000000..c7efd8f --- /dev/null +++ b/app/static/manifest.json @@ -0,0 +1,52 @@ +{ + "name": "Castle Scouting App", + "short_name": "Castle", + "description": "FRC Team Scouting App", + "start_url": "/", + "display": "standalone", + "background_color": "#ffffff", + "theme_color": "#3b82f6", + + "icons": [ + { + "src": "/static/icons/icon-72x72.png", + "sizes": "72x72", + "type": "image/png" + }, + { + "src": "/static/icons/icon-96x96.png", + "sizes": "96x96", + "type": "image/png" + }, + { + "src": "/static/icons/icon-128x128.png", + "sizes": "128x128", + "type": "image/png" + }, + { + "src": "/static/icons/icon-144x144.png", + "sizes": "144x144", + "type": "image/png" + }, + { + "src": "/static/icons/icon-152x152.png", + "sizes": "152x152", + "type": "image/png" + }, + { + "src": "/static/icons/icon-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/static/icons/icon-384x384.png", + "sizes": "384x384", + "type": "image/png" + }, + { + "src": "/static/icons/icon-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} \ No newline at end of file diff --git a/app/templates/base.html b/app/templates/base.html index fe1130a..b6e4609 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -52,6 +52,12 @@ + + + + + + @@ -281,6 +287,18 @@ } }); } + + if ('serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker.register('/static/js/service-worker.js') + .then((registration) => { + console.log('ServiceWorker registration successful'); + }) + .catch((err) => { + console.log('ServiceWorker registration failed: ', err); + }); + }); + } \ No newline at end of file diff --git a/app/templates/compare.html b/app/templates/compare.html index 7e8b179..e6bb068 100644 --- a/app/templates/compare.html +++ b/app/templates/compare.html @@ -433,27 +433,33 @@

Raw Scouting Data

-