diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index ee164d4..4006fc9 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -2,9 +2,9 @@ name: Build Android App on: push: - branches: [ main, master] + branches: [ deploy ] pull_request: - branches: [ main, master ] + branches: [ deploy ] workflow_dispatch: permissions: diff --git a/.github/workflows/build-flatpak.yml b/.github/workflows/build-flatpak.yml index 62851f0..104f9b0 100644 --- a/.github/workflows/build-flatpak.yml +++ b/.github/workflows/build-flatpak.yml @@ -2,9 +2,9 @@ name: Build Flatpak on: push: - branches: [ main, master, flatpak-testing ] + branches: [ deploy ] pull_request: - branches: [ main, master, flatpak-testing ] + branches: [ deploy ] workflow_dispatch: permissions: @@ -25,7 +25,7 @@ permissions: jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -33,9 +33,9 @@ jobs: - name: Install system dependencies run: | sudo apt-get update - sudo apt-get install -y libgirepository1.0-dev libcairo2-dev pkg-config python3.8 python3.8-dev - # Install WebKit dependencies for Ubuntu 20.04 - sudo apt-get install -y gir1.2-webkit2-4.0 libwebkit2gtk-4.0-dev + sudo apt-get install -y libgirepository1.0-dev libgirepository-2.0-dev libcairo2-dev pkg-config python3-dev + # Install WebKit dependencies for Ubuntu 24.04 + sudo apt-get install -y gir1.2-webkit2-4.1 libwebkit2gtk-4.1-dev # Install system PyGobject sudo apt-get install -y python3-gi python3-gi-cairo # Install GLib dev (needed for GTK applications) @@ -45,28 +45,33 @@ jobs: sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo - - name: Set up Python virtual environment + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12.3' + + - name: Install Python dependencies run: | - # Create a virtual environment with access to system packages - python3 -m venv --system-site-packages venv - source venv/bin/activate - # Upgrade pip within the virtual environment - pip install --upgrade pip setuptools wheel - # Install briefcase in the virtual environment - pip install briefcase + python -m pip install --upgrade pip + python -m pip install briefcase - name: Create Flatpak package + working-directory: ./standalone run: | - # Use the virtual environment - source venv/bin/activate - cd standalone - briefcase create linux flatpak --no-input - briefcase build linux flatpak --no-input --update-resources - briefcase package linux flatpak --no-input + python -m briefcase create linux flatpak --no-input -vv + python -m briefcase build linux flatpak --no-input --update-resources -vv + python -m briefcase package linux flatpak --no-input -vv - name: Upload Flatpak bundle uses: actions/upload-artifact@v4 with: name: castle-flatpak-bundle path: standalone/dist/Castle-*.flatpak - if-no-files-found: warn \ No newline at end of file + if-no-files-found: warn + + - name: Upload build log on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: flatpak-build-logs + path: ./standalone/logs/ \ No newline at end of file diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml index bef9743..b06a413 100644 --- a/.github/workflows/build-ios.yml +++ b/.github/workflows/build-ios.yml @@ -2,9 +2,9 @@ name: Build iOS App on: push: - branches: [ main, master] + branches: [ deploy ] pull_request: - branches: [ main, master] + branches: [ deploy ] workflow_dispatch: permissions: diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 27dc001..7c4ac8f 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -2,92 +2,68 @@ name: Build Linux App on: push: - branches: [ main, master ] + branches: [ deploy ] pull_request: - branches: [ main, master ] + branches: [ deploy ] workflow_dispatch: permissions: - actions: write - checks: write contents: write - deployments: write - discussions: write - id-token: write - issues: write packages: write - pages: write - pull-requests: write - repository-projects: write - security-events: write - statuses: write jobs: build: - runs-on: ubuntu-20.04 - env: - PKG_CONFIG_PATH: /usr/lib/x86_64-linux-gnu/pkgconfig + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.8' - - name: Install system dependencies run: | sudo apt-get update - sudo apt-get install -y libgirepository1.0-dev libcairo2-dev pkg-config python3-dev - # Install WebKit dependencies for Ubuntu 20.04 - sudo apt-get install -y gir1.2-webkit2-4.0 libwebkit2gtk-4.0-dev - # Install system PyGobject - sudo apt-get install -y python3-gi python3-gi-cairo - # Install additional dependencies required for AppImage - sudo apt-get install -y fuse libfuse2 desktop-file-utils libglib2.0-dev + sudo apt-get install -y \ + libgirepository1.0-dev \ + libgirepository-2.0-dev \ + libcairo2-dev \ + pkg-config \ + python3-dev \ + python3-pip \ + python3-cairo \ + gir1.2-webkit2-4.1 \ + libwebkit2gtk-4.1-dev \ + python3-gi \ + python3-gi-cairo \ + libglib2.0-dev \ + desktop-file-utils - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - # Install Briefcase with a specific version known to work well with AppImage packaging - python -m pip install briefcase==0.3.15 - - - name: Prepare custom dependencies - working-directory: ./standalone - run: | - # Create a custom requirements file with pinned versions - cat > briefcase_deps.txt << EOF - # Pinned dependencies for Ubuntu 20.04 - toga==0.4.6 - toga-gtk==0.4.6 - pycairo>=1.17.0 - EOF - - # Display the custom requirements - cat briefcase_deps.txt + - name: Install Briefcase + run: python3 -m pip install --break-system-packages briefcase - # Build the app for Linux - - name: Build Linux app + - name: Build DEB package working-directory: ./standalone + env: + PIP_BREAK_SYSTEM_PACKAGES: "1" run: | - python -m briefcase create - python -m briefcase build --update-resources + python3 -m briefcase create linux system + python3 -m briefcase build linux system + python3 -m briefcase package linux system -p deb - # Create Linux distribution packages - - name: Package for Linux + - name: List build artifacts working-directory: ./standalone run: | - # For AppImage, use Docker for proper dependency handling - python -m briefcase package linux appimage --no-docker - - # For DEB format: - python -m briefcase package linux system -p deb + echo "Contents of dist directory:" + ls -lah dist/ || echo "No dist directory found" - - name: Upload Linux packages + - name: Upload DEB package + if: success() + uses: actions/upload-artifact@v4 + with: + name: castle-deb-package + path: ./standalone/dist/*.deb + + - name: Upload logs on failure + if: failure() uses: actions/upload-artifact@v4 with: - name: linux-packages - path: | - ./standalone/dist/*.AppImage - ./standalone/dist/*.deb + name: build-logs + path: ./standalone/logs/ diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 4383074..7885b80 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -2,9 +2,9 @@ name: Build macOS App on: push: - branches: [ main, master ] + branches: [ deploy ] pull_request: - branches: [ main, master ] + branches: [ deploy ] workflow_dispatch: permissions: diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 5d63392..3dac523 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -2,9 +2,9 @@ name: Build Windows App on: push: - branches: [ main, master ] + branches: [ deploy ] pull_request: - branches: [ main, master ] + branches: [ deploy ] workflow_dispatch: permissions: diff --git a/app/app.py b/app/app.py index a6a63fb..141b3b2 100644 --- a/app/app.py +++ b/app/app.py @@ -35,6 +35,7 @@ def create_app(): SESSION_COOKIE_SECURE=True, WTF_CSRF_ENABLED=True, MONGO_URI=os.getenv("MONGO_URI", "mongodb://localhost:27017/scouting_app"), + RATELIMIT_STORAGE_URI=os.getenv("MONGO_URI", "mongodb://localhost:27017/scouting_app"), VAPID_PUBLIC_KEY=os.getenv("VAPID_PUBLIC_KEY", ""), VAPID_PRIVATE_KEY=os.getenv("VAPID_PRIVATE_KEY", ""), VAPID_CLAIM_EMAIL=os.getenv("VAPID_CLAIM_EMAIL", "team334@gmail.com") @@ -140,7 +141,8 @@ def check_team_access(): request.path == '/' or \ request.path == '/service-worker.js' or \ request.path.startswith('/auth/login') or \ - request.path.startswith('/auth/register'): + request.path.startswith('/auth/register') or \ + request.path.startswith('/auth/forgot-password'): return # Block access for non-authenticated users to protected routes diff --git a/app/auth/routes.py b/app/auth/routes.py index 9313e25..469c632 100644 --- a/app/auth/routes.py +++ b/app/auth/routes.py @@ -140,6 +140,12 @@ async def login(): return render_template("auth/login.html", form_data={}) +@auth_bp.route("/forgot-password") +def forgot_password(): + """Display forgot password page with instructions""" + return render_template("forgot-password.html") + + @auth_bp.route("/register", methods=["GET", "POST"]) @limiter.limit("8 per minute") @async_route diff --git a/app/models.py b/app/models.py index 4783599..b2e44a1 100644 --- a/app/models.py +++ b/app/models.py @@ -74,11 +74,17 @@ def __init__(self, data): self.event_code = data.get('event_code') self.alliance = data.get('alliance', '') - # Algae scoring - self.algae_net = data.get('algae_net', 0) - self.algae_processor = data.get('algae_processor', 0) + # 2026 Game Scoring - Fuel + self.auto_fuel = data.get('auto_fuel', 0) + self.transition_fuel = data.get('transition_fuel', 0) + self.teleop_shift_1_fuel = data.get('teleop_shift_1_fuel', 0) + self.teleop_shift_2_fuel = data.get('teleop_shift_2_fuel', 0) + self.teleop_shift_3_fuel = data.get('teleop_shift_3_fuel', 0) + self.teleop_shift_4_fuel = data.get('teleop_shift_4_fuel', 0) + self.endgame_fuel = data.get('endgame_fuel', 0) + # Climb - self.climb_type = data.get('climb_type', '') # 'shallow', 'deep', 'park', or '' + self.climb_level = data.get('climb_level', 0) # 0=None, 1-3 self.climb_success = data.get('climb_success', False) # Defense @@ -97,26 +103,6 @@ def __init__(self, data): self.scouter_name = data.get('scouter_name') self.scouter_team = data.get('scouter_team') self.is_owner = data.get('is_owner', True) - - # Auto Coral scoring - self.auto_coral_level1 = data.get('auto_coral_level1', 0) - self.auto_coral_level2 = data.get('auto_coral_level2', 0) - self.auto_coral_level3 = data.get('auto_coral_level3', 0) - self.auto_coral_level4 = data.get('auto_coral_level4', 0) - - # Teleop Coral scoring - self.teleop_coral_level1 = data.get('teleop_coral_level1', 0) - self.teleop_coral_level2 = data.get('teleop_coral_level2', 0) - self.teleop_coral_level3 = data.get('teleop_coral_level3', 0) - self.teleop_coral_level4 = data.get('teleop_coral_level4', 0) - - # Auto Algae scoring - self.auto_algae_net = data.get('auto_algae_net', 0) - self.auto_algae_processor = data.get('auto_algae_processor', 0) - - # Teleop Algae scoring - self.teleop_algae_net = data.get('teleop_algae_net', 0) - self.teleop_algae_processor = data.get('teleop_algae_processor', 0) # Robot Disabled Status self.robot_disabled = data.get('robot_disabled', 'None') # 'None', 'Partially', 'Full' @@ -133,19 +119,17 @@ def to_dict(self): 'match_number': self.match_number, 'event_code': self.event_code, 'alliance': self.alliance, - 'auto_coral_level1': self.auto_coral_level1, - 'auto_coral_level2': self.auto_coral_level2, - 'auto_coral_level3': self.auto_coral_level3, - 'auto_coral_level4': self.auto_coral_level4, - 'teleop_coral_level1': self.teleop_coral_level1, - 'teleop_coral_level2': self.teleop_coral_level2, - 'teleop_coral_level3': self.teleop_coral_level3, - 'teleop_coral_level4': self.teleop_coral_level4, - 'auto_algae_net': self.auto_algae_net, - 'auto_algae_processor': self.auto_algae_processor, - 'teleop_algae_net': self.teleop_algae_net, - 'teleop_algae_processor': self.teleop_algae_processor, - 'climb_type': self.climb_type, + + # 2026 Game Scoring + 'auto_fuel': self.auto_fuel, + 'transition_fuel': self.transition_fuel, + 'teleop_shift_1_fuel': self.teleop_shift_1_fuel, + 'teleop_shift_2_fuel': self.teleop_shift_2_fuel, + 'teleop_shift_3_fuel': self.teleop_shift_3_fuel, + 'teleop_shift_4_fuel': self.teleop_shift_4_fuel, + 'endgame_fuel': self.endgame_fuel, + 'climb_level': self.climb_level, + 'climb_success': self.climb_success, 'defense_rating': self.defense_rating, 'defense_notes': self.defense_notes, @@ -168,7 +152,6 @@ def formatted_date(self): - class PitScouting: def __init__(self, data: Dict): self._id = data.get("_id") @@ -200,21 +183,6 @@ def __init__(self, data: Dict): "height": 0, }) - # Mechanisms - self.mechanisms = data.get("mechanisms", { - "coral_scoring": { - "notes": "" - }, - "algae_scoring": { - "notes": "" - }, - "climber": { - "has_climber": False, - "type_climber": "", # deep, shallow, park - "notes": "" - } - }) - # Programming and Autonomous self.programming_language = data.get("programming_language", "") self.autonomous_capabilities = data.get("autonomous_capabilities", { diff --git a/app/scout/routes.py b/app/scout/routes.py index 35ff244..12e3e6d 100644 --- a/app/scout/routes.py +++ b/app/scout/routes.py @@ -122,7 +122,7 @@ def edit(id): scouter_team = team_data.scouter_team # Allow access only if user is the original scouter or on the same team - if current_user.get_id() != team_data.scouter_id and (not current_team or not scouter_team or str(current_team) != str(scouter_team)): + if current_user.get_id() != str(team_data.scouter_id) and (not current_team or not scouter_team or str(current_team) != str(scouter_team)): flash("Access denied: You can only edit scouting data from your own team", "error") return redirect(url_for("scouting.home")) @@ -219,28 +219,20 @@ def auton(): def format_team_stats(stats): """Format team stats with calculated totals""" + avg_teleop_fuel = ( + stats.get("avg_transition_fuel", 0) + + stats.get("avg_teleop_shift_1_fuel", 0) + + stats.get("avg_teleop_shift_2_fuel", 0) + + stats.get("avg_teleop_shift_3_fuel", 0) + + stats.get("avg_teleop_shift_4_fuel", 0) + ) + return { "matches_played": stats.get("matches_played", 0), - "auto_coral_total": sum([ - stats.get("avg_auto_coral_level1", 0), - stats.get("avg_auto_coral_level2", 0), - stats.get("avg_auto_coral_level3", 0), - stats.get("avg_auto_coral_level4", 0) - ]), - "teleop_coral_total": sum([ - stats.get("avg_teleop_coral_level1", 0), - stats.get("avg_teleop_coral_level2", 0), - stats.get("avg_teleop_coral_level3", 0), - stats.get("avg_teleop_coral_level4", 0) - ]), - "auto_algae_total": sum([ - stats.get("avg_auto_algae_net", 0), - stats.get("avg_auto_algae_processor", 0) - ]), - "teleop_algae_total": sum([ - stats.get("avg_teleop_algae_net", 0), - stats.get("avg_teleop_algae_processor", 0) - ]), + "auto_fuel_avg": stats.get("avg_auto_fuel", 0), + "teleop_fuel_avg": avg_teleop_fuel, + "endgame_fuel_avg": stats.get("avg_endgame_fuel", 0), + "climb_level_avg": stats.get("avg_climb_level", 0), "climb_success_rate": stats.get("climb_success_rate", 0) * 100 } @@ -279,20 +271,23 @@ def compare_teams(): {"$group": { "_id": "$team_number", "matches_played": {"$sum": 1}, - "avg_auto_coral_level1": {"$avg": {"$cond": [{"$gt": ["$auto_coral_level1", 0]}, "$auto_coral_level1", None]}}, - "avg_auto_coral_level2": {"$avg": {"$cond": [{"$gt": ["$auto_coral_level2", 0]}, "$auto_coral_level2", None]}}, - "avg_auto_coral_level3": {"$avg": {"$cond": [{"$gt": ["$auto_coral_level3", 0]}, "$auto_coral_level3", None]}}, - "avg_auto_coral_level4": {"$avg": {"$cond": [{"$gt": ["$auto_coral_level4", 0]}, "$auto_coral_level4", None]}}, - "avg_auto_algae_net": {"$avg": {"$cond": [{"$gt": ["$auto_algae_net", 0]}, "$auto_algae_net", None]}}, - "avg_auto_algae_processor": {"$avg": {"$cond": [{"$gt": ["$auto_algae_processor", 0]}, "$auto_algae_processor", None]}}, - "avg_teleop_coral_level1": {"$avg": {"$cond": [{"$gt": ["$teleop_coral_level1", 0]}, "$teleop_coral_level1", None]}}, - "avg_teleop_coral_level2": {"$avg": {"$cond": [{"$gt": ["$teleop_coral_level2", 0]}, "$teleop_coral_level2", None]}}, - "avg_teleop_coral_level3": {"$avg": {"$cond": [{"$gt": ["$teleop_coral_level3", 0]}, "$teleop_coral_level3", None]}}, - "avg_teleop_coral_level4": {"$avg": {"$cond": [{"$gt": ["$teleop_coral_level4", 0]}, "$teleop_coral_level4", None]}}, - "avg_teleop_algae_net": {"$avg": {"$cond": [{"$gt": ["$teleop_algae_net", 0]}, "$teleop_algae_net", None]}}, - "avg_teleop_algae_processor": {"$avg": {"$cond": [{"$gt": ["$teleop_algae_processor", 0]}, "$teleop_algae_processor", None]}}, - # Only count successful climbs in the rate + + # Fuel Stats + "avg_auto_fuel": {"$avg": {"$ifNull": ["$auto_fuel", 0]}}, + "avg_transition_fuel": {"$avg": {"$ifNull": ["$transition_fuel", 0]}}, + "avg_teleop_shift_1_fuel": {"$avg": {"$ifNull": ["$teleop_shift_1_fuel", 0]}}, + "avg_teleop_shift_2_fuel": {"$avg": {"$ifNull": ["$teleop_shift_2_fuel", 0]}}, + "avg_teleop_shift_3_fuel": {"$avg": {"$ifNull": ["$teleop_shift_3_fuel", 0]}}, + "avg_teleop_shift_4_fuel": {"$avg": {"$ifNull": ["$teleop_shift_4_fuel", 0]}}, + "avg_endgame_fuel": {"$avg": {"$ifNull": ["$endgame_fuel", 0]}}, + + # Climb Stats "climb_success_rate": {"$avg": {"$cond": ["$climb_success", 1, 0]}}, + "climb_level_1_count": {"$sum": {"$cond": [{"$eq": ["$climb_level", 1]}, 1, 0]}}, + "climb_level_2_count": {"$sum": {"$cond": [{"$eq": ["$climb_level", 2]}, 1, 0]}}, + "climb_level_3_count": {"$sum": {"$cond": [{"$eq": ["$climb_level", 3]}, 1, 0]}}, + "avg_climb_level": {"$avg": {"$ifNull": ["$climb_level", 0]}}, + "defense_notes": {"$push": "$defense_notes"}, "auto_paths": {"$push": { "path": "$auto_path", @@ -310,23 +305,22 @@ def compare_teams(): if stats := list( scouting_manager.db.team_data.aggregate(pipeline) ): + + # Calculate total teleop average + avg_teleop_total = ( + (stats[0].get("avg_transition_fuel") or 0) + + (stats[0]["avg_teleop_shift_1_fuel"] or 0) + + (stats[0]["avg_teleop_shift_2_fuel"] or 0) + + (stats[0]["avg_teleop_shift_3_fuel"] or 0) + + (stats[0]["avg_teleop_shift_4_fuel"] or 0) + ) + normalized_stats = { - "auto_scoring": ( - (stats[0]["avg_auto_coral_level1"] or 0) + - (stats[0]["avg_auto_coral_level2"] or 0) * 2 + - (stats[0]["avg_auto_coral_level3"] or 0) * 3 + - (stats[0]["avg_auto_coral_level4"] or 0) * 4 + - (stats[0]["avg_auto_algae_net"] or 0) * 2 + - (stats[0]["avg_auto_algae_processor"] or 0) * 3 - ) / 20, - "teleop_scoring": ( - (stats[0]["avg_teleop_coral_level1"] or 0) + - (stats[0]["avg_teleop_coral_level2"] or 0) * 2 + - (stats[0]["avg_teleop_coral_level3"] or 0) * 3 + - (stats[0]["avg_teleop_coral_level4"] or 0) * 4 + - (stats[0]["avg_teleop_algae_net"] or 0) * 2 + - (stats[0]["avg_teleop_algae_processor"] or 0) * 3 - ) / 20, + # TODO + "auto_scoring": min((stats[0]["avg_auto_fuel"] or 0) / 10, 1), # Assumes ~10 max auto fuel + "teleop_scoring": min(avg_teleop_total / 30, 1), # Assumes ~30 max teleop fuel + "endgame_scoring": min((stats[0]["avg_endgame_fuel"] or 0) / 10, 1), # Assumes ~10 max endgame fuel + "climb_level": min((stats[0]["avg_climb_level"] or 0) / 3, 1), # Max level 3 "climb_rating": stats[0]["climb_success_rate"], "defense_rating": (stats[0]["defense_rating"] or 0) / 5 if stats[0].get("defense_rating") is not None else 0 } @@ -420,20 +414,20 @@ async def search_teams(): "_id": {"$toString": "$_id"}, # Convert ObjectId to string "event_code": 1, "match_number": 1, - "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]}, - "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]}, - "auto_algae_net": {"$ifNull": ["$auto_algae_net", 0]}, - "auto_algae_processor": {"$ifNull": ["$auto_algae_processor", 0]}, - "teleop_algae_net": {"$ifNull": ["$teleop_algae_net", 0]}, - "teleop_algae_processor": {"$ifNull": ["$teleop_algae_processor", 0]}, + # Fuel Stats + "transition_fuel": {"$ifNull": ["$transition_fuel", 0]}, + "auto_fuel": {"$ifNull": ["$auto_fuel", 0]}, + "teleop_shift_1_fuel": {"$ifNull": ["$teleop_shift_1_fuel", 0]}, + "teleop_shift_2_fuel": {"$ifNull": ["$teleop_shift_2_fuel", 0]}, + "teleop_shift_3_fuel": {"$ifNull": ["$teleop_shift_3_fuel", 0]}, + "teleop_shift_4_fuel": {"$ifNull": ["$teleop_shift_4_fuel", 0]}, + "endgame_fuel": {"$ifNull": ["$endgame_fuel", 0]}, + + # Climb Stats + "climb_level": {"$ifNull": ["$climb_level", 0]}, "climb_type": 1, "climb_success": 1, + "auto_path": 1, "auto_notes": 1, "defense_rating": {"$ifNull": ["$defense_rating", 0]}, @@ -471,7 +465,7 @@ async def search_teams(): def leaderboard(): try: MIN_MATCHES = 1 - sort_type = request.args.get('sort', 'coral') + sort_type = request.args.get('sort', 'fuel') selected_event = request.args.get('event', 'all') # Get available events from scouting data @@ -535,22 +529,22 @@ def leaderboard(): {"$group": { "_id": "$team_number", "matches_played": {"$sum": 1}, - # Auto Coral - "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]}}, - # Teleop Coral - "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]}}, - # Auto Algae - "auto_algae_net": {"$avg": {"$ifNull": ["$auto_algae_net", 0]}}, - "auto_algae_processor": {"$avg": {"$ifNull": ["$auto_algae_processor", 0]}}, - # Teleop Algae - "teleop_algae_net": {"$avg": {"$ifNull": ["$teleop_algae_net", 0]}}, - "teleop_algae_processor": {"$avg": {"$ifNull": ["$teleop_algae_processor", 0]}}, + + # Auto Fuel + "auto_fuel": {"$avg": {"$ifNull": ["$auto_fuel", 0]}}, + "transition_fuel": {"$avg": {"$ifNull": ["$transition_fuel", 0]}}, + + # Teleop Fuel + "teleop_shift_1_fuel": {"$avg": {"$ifNull": ["$teleop_shift_1_fuel", 0]}}, + "teleop_shift_2_fuel": {"$avg": {"$ifNull": ["$teleop_shift_2_fuel", 0]}}, + "teleop_shift_3_fuel": {"$avg": {"$ifNull": ["$teleop_shift_3_fuel", 0]}}, + "teleop_shift_4_fuel": {"$avg": {"$ifNull": ["$teleop_shift_4_fuel", 0]}}, + + # Endgame Fuel + "endgame_fuel": {"$avg": {"$ifNull": ["$endgame_fuel", 0]}}, + + # Climb Level + "climb_level": {"$avg": {"$ifNull": ["$climb_level", 0]}}, # Defense Rating "defense_rating": {"$avg": {"$ifNull": ["$defense_rating", 0]}}, @@ -558,98 +552,70 @@ def leaderboard(): "robot_disabled_list": {"$push": "$robot_disabled"}, # Climb stats + "climb_level_1_success": {"$sum": {"$cond": [{"$and": [{"$eq": ["$climb_level", 1]}, {"$eq": ["$climb_success", True]}]}, 1, 0]}}, + "climb_level_2_success": {"$sum": {"$cond": [{"$and": [{"$eq": ["$climb_level", 2]}, {"$eq": ["$climb_success", True]}]}, 1, 0]}}, + "climb_level_3_success": {"$sum": {"$cond": [{"$and": [{"$eq": ["$climb_level", 3]}, {"$eq": ["$climb_success", True]}]}, 1, 0]}}, "climb_attempts": {"$sum": 1}, "climb_successes": { "$sum": {"$cond": [{"$eq": ["$climb_success", True]}, 1, 0]} }, - "deep_climb_attempts": { - "$sum": {"$cond": [{"$eq": ["$climb_type", "deep"]}, 1, 0]} - }, - "deep_climb_successes": { - "$sum": { - "$cond": [ - {"$and": [ - {"$eq": ["$climb_type", "deep"]}, - {"$eq": ["$climb_success", True]} - ]}, - 1, - 0 - ] - } - } }}, {"$match": {"matches_played": {"$gte": MIN_MATCHES}}}, {"$project": { "team_number": "$_id", "matches_played": 1, - "auto_coral_stats": { - "level1": "$auto_coral_level1", - "level2": "$auto_coral_level2", - "level3": "$auto_coral_level3", - "level4": "$auto_coral_level4" - }, - "teleop_coral_stats": { - "level1": "$teleop_coral_level1", - "level2": "$teleop_coral_level2", - "level3": "$teleop_coral_level3", - "level4": "$teleop_coral_level4" - }, - "auto_algae_stats": { - "net": "$auto_algae_net", - "processor": "$auto_algae_processor" - }, - "teleop_algae_stats": { - "net": "$teleop_algae_net", - "processor": "$teleop_algae_processor" - }, - # Calculate totals for each category - "total_coral": { + + "auto_fuel": {"$round": ["$auto_fuel", 1]}, + "transition_fuel": {"$round": ["$transition_fuel", 1]}, + "teleop_fuel_total": { "$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" + "$transition_fuel", + "$teleop_shift_1_fuel", + "$teleop_shift_2_fuel", + "$teleop_shift_3_fuel", + "$teleop_shift_4_fuel" ] }, - "total_auto_coral": { + "endgame_fuel": {"$round": ["$endgame_fuel", 1]}, + + # Calculate totals for each category + "total_fuel": { "$add": [ - "$auto_coral_level1", "$auto_coral_level2", - "$auto_coral_level3", "$auto_coral_level4" + "$auto_fuel", + "$transition_fuel", + "$teleop_shift_1_fuel", + "$teleop_shift_2_fuel", + "$teleop_shift_3_fuel", + "$teleop_shift_4_fuel", + "$endgame_fuel" ] }, - "total_teleop_coral": { - "$add": [ - "$teleop_coral_level1", "$teleop_coral_level2", - "$teleop_coral_level3", "$teleop_coral_level4" + + "climb_level_avg": {"$round": ["$climb_level", 1]}, + + "climb_l1_pct": { + "$multiply": [ + {"$divide": ["$climb_level_1_success", "$matches_played"]}, + 100 ] }, - "total_algae": { - "$add": [ - "$auto_algae_net", "$auto_algae_processor", - "$teleop_algae_net", "$teleop_algae_processor" + "climb_l2_pct": { + "$multiply": [ + {"$divide": ["$climb_level_2_success", "$matches_played"]}, + 100 ] }, - "total_auto_algae": { - "$add": ["$auto_algae_net", "$auto_algae_processor"] - }, - "total_teleop_algae": { - "$add": ["$teleop_algae_net", "$teleop_algae_processor"] - }, - "climb_success_rate": { + "climb_l3_pct": { "$multiply": [ - {"$cond": [ - {"$gt": ["$climb_attempts", 0]}, - {"$divide": ["$climb_successes", "$climb_attempts"]}, - 0 - ]}, + {"$divide": ["$climb_level_3_success", "$matches_played"]}, 100 ] }, - "deep_climb_success_rate": { + "climb_success_rate": { "$multiply": [ {"$cond": [ - {"$gt": ["$deep_climb_attempts", 0]}, - {"$divide": ["$deep_climb_successes", "$deep_climb_attempts"]}, + {"$gt": ["$climb_attempts", 0]}, + {"$divide": ["$climb_successes", "$climb_attempts"]}, 0 ]}, 100 @@ -662,22 +628,21 @@ def leaderboard(): # Add sorting based on selected type sort_field = { - 'coral': 'total_coral', - 'auto_coral': 'total_auto_coral', - 'teleop_coral': 'total_teleop_coral', - 'algae': 'total_algae', - 'auto_algae': 'total_auto_algae', - 'teleop_algae': 'total_teleop_algae', - 'deep_climb': 'deep_climb_success_rate', + 'fuel': 'total_fuel', + 'auto_fuel': 'auto_fuel', + 'climb': 'climb_success_rate', + 'climb_l1': 'climb_l1_pct', + 'climb_l2': 'climb_l2_pct', + 'climb_l3': 'climb_l3_pct', 'defense': 'defense_rating' - }.get(sort_type, 'total_coral') + }.get(sort_type, 'total_fuel') - if sort_type == 'deep_climb': - pipeline.insert(-1, { - "$match": { - "deep_climb_attempts": {"$gt": 0} - } - }) + # if sort_type == 'deep_climb': + # pipeline.insert(-1, { + # "$match": { + # # "deep_climb_attempts": {"$gt": 0} + # } + # }) pipeline.append({"$sort": {sort_field: -1}}) teams = list(scouting_manager.db.team_data.aggregate(pipeline)) @@ -686,7 +651,7 @@ def leaderboard(): events=events, selected_event=selected_event) except Exception as e: current_app.logger.error(f"Error in leaderboard: {str(e)}", exc_info=True) - return render_template("scouting/leaderboard.html", teams=[], current_sort='coral', + return render_template("scouting/leaderboard.html", teams=[], current_sort='fuel', events=[], selected_event='all') @scouting_bp.route("/scouter-leaderboard") @@ -827,20 +792,17 @@ def matches(): "$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]}, + + # Fuel stats + "auto_fuel": {"$ifNull": ["$auto_fuel", 0]}, + "transition_fuel": {"$ifNull": ["$transition_fuel", 0]}, + "teleop_shift_1_fuel": {"$ifNull": ["$teleop_shift_1_fuel", 0]}, + "teleop_shift_2_fuel": {"$ifNull": ["$teleop_shift_2_fuel", 0]}, + "teleop_shift_3_fuel": {"$ifNull": ["$teleop_shift_3_fuel", 0]}, + "teleop_shift_4_fuel": {"$ifNull": ["$teleop_shift_4_fuel", 0]}, + "endgame_fuel": {"$ifNull": ["$endgame_fuel", 0]}, + + "climb_level": {"$ifNull": ["$climb_level", 0]}, "climb_type": "$climb_type", "climb_success": "$climb_success" } @@ -855,53 +817,39 @@ def matches(): red_teams = [t for t in match["teams"] if t["alliance"] == "red"] blue_teams = [t for t in match["teams"] if t["alliance"] == "blue"] - # Calculate alliance totals - red_coral = { - "level1": sum(t["auto_coral_level1"] + t["teleop_coral_level1"] for t in red_teams), - "level2": sum(t["auto_coral_level2"] + t["teleop_coral_level2"] for t in red_teams), - "level3": sum(t["auto_coral_level3"] + t["teleop_coral_level3"] for t in red_teams), - "level4": sum(t["auto_coral_level4"] + t["teleop_coral_level4"] for t in red_teams) - } - - red_algae = { - "net": sum(t["auto_algae_net"] + t["teleop_algae_net"] for t in red_teams), - "processor": sum(t["auto_algae_processor"] + t["teleop_algae_processor"] for t in red_teams) - } - - blue_coral = { - "level1": sum(t["auto_coral_level1"] + t["teleop_coral_level1"] for t in blue_teams), - "level2": sum(t["auto_coral_level2"] + t["teleop_coral_level2"] for t in blue_teams), - "level3": sum(t["auto_coral_level3"] + t["teleop_coral_level3"] for t in blue_teams), - "level4": sum(t["auto_coral_level4"] + t["teleop_coral_level4"] for t in blue_teams) + # Calculate alliance totals for Fuel + red_fuel = { + "auto": sum(t["auto_fuel"] for t in red_teams), + "transition": sum(t["transition_fuel"] for t in red_teams), + "teleop": sum(t["teleop_shift_1_fuel"] + t["teleop_shift_2_fuel"] + + t["teleop_shift_3_fuel"] + t["teleop_shift_4_fuel"] for t in red_teams), + "endgame": sum(t["endgame_fuel"] for t in red_teams), + "total": sum(t["auto_fuel"] + t["transition_fuel"] + t["teleop_shift_1_fuel"] + t["teleop_shift_2_fuel"] + + t["teleop_shift_3_fuel"] + t["teleop_shift_4_fuel"] + t["endgame_fuel"] for t in red_teams) } - blue_algae = { - "net": sum(t["auto_algae_net"] + t["teleop_algae_net"] for t in blue_teams), - "processor": sum(t["auto_algae_processor"] + t["teleop_algae_processor"] for t in blue_teams) + blue_fuel = { + "auto": sum(t["auto_fuel"] for t in blue_teams), + "transition": sum(t["transition_fuel"] for t in blue_teams), + "teleop": sum(t["teleop_shift_1_fuel"] + t["teleop_shift_2_fuel"] + + t["teleop_shift_3_fuel"] + t["teleop_shift_4_fuel"] for t in blue_teams), + "endgame": sum(t["endgame_fuel"] for t in blue_teams), + "total": sum(t["auto_fuel"] + t["transition_fuel"] + t["teleop_shift_1_fuel"] + t["teleop_shift_2_fuel"] + + t["teleop_shift_3_fuel"] + t["teleop_shift_4_fuel"] + t["endgame_fuel"] for t in blue_teams) } # Prepare team data for template red_team_data = [{ "number": t["number"], - "coral_level1": t["auto_coral_level1"] + t["teleop_coral_level1"], - "coral_level2": t["auto_coral_level2"] + t["teleop_coral_level2"], - "coral_level3": t["auto_coral_level3"] + t["teleop_coral_level3"], - "coral_level4": t["auto_coral_level4"] + t["teleop_coral_level4"], - "algae_net": t["auto_algae_net"] + t["teleop_algae_net"], - "algae_processor": t["auto_algae_processor"] + t["teleop_algae_processor"], - "climb_type": t["climb_type"], + "fuel_total": t["auto_fuel"] + t["transition_fuel"] + t["teleop_shift_1_fuel"] + t["teleop_shift_2_fuel"] + t["teleop_shift_3_fuel"] + t["teleop_shift_4_fuel"] + t["endgame_fuel"], + "climb_level": t["climb_level"], "climb_success": t["climb_success"] } for t in red_teams] blue_team_data = [{ "number": t["number"], - "coral_level1": t["auto_coral_level1"] + t["teleop_coral_level1"], - "coral_level2": t["auto_coral_level2"] + t["teleop_coral_level2"], - "coral_level3": t["auto_coral_level3"] + t["teleop_coral_level3"], - "coral_level4": t["auto_coral_level4"] + t["teleop_coral_level4"], - "algae_net": t["auto_algae_net"] + t["teleop_algae_net"], - "algae_processor": t["auto_algae_processor"] + t["teleop_algae_processor"], - "climb_type": t["climb_type"], + "fuel_total": t["auto_fuel"] + t["transition_fuel"] + t["teleop_shift_1_fuel"] + t["teleop_shift_2_fuel"] + t["teleop_shift_3_fuel"] + t["teleop_shift_4_fuel"] + t["endgame_fuel"], + "climb_level": t["climb_level"], "climb_success": t["climb_success"] } for t in blue_teams] @@ -910,10 +858,8 @@ def matches(): "match_number": match["_id"]["match"], "red_teams": red_team_data, "blue_teams": blue_team_data, - "red_coral": red_coral, - "red_algae": red_algae, - "blue_coral": blue_coral, - "blue_algae": blue_algae + "red_fuel": red_fuel, + "blue_fuel": blue_fuel }) current_app.logger.info(f"Successfully fetched matches {matches} for user {current_user.username if current_user.is_authenticated else 'Anonymous'}") return render_template("scouting/matches.html", matches=matches) @@ -1023,23 +969,6 @@ def pit_scouting_add(): "height": float(request.form.get("height", 0) if not (request.form.get("height") == '') else 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 "" - }, - "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": { @@ -1116,21 +1045,6 @@ def pit_scouting_edit(team_number): "width": float(request.form.get("width", 0)), "height": float(request.form.get("height", 0)) }, - "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_language": request.form.get("programming_language", ""), "autonomous_capabilities": { "has_auto": request.form.get("has_auto") == "true", diff --git a/app/scout/scouting_utils.py b/app/scout/scouting_utils.py index c85e731..fcd9856 100644 --- a/app/scout/scouting_utils.py +++ b/app/scout/scouting_utils.py @@ -28,26 +28,6 @@ def _ensure_collections(self): self.db.pit_scouting.create_index([("scouter_id", 1)]) logger.info("Created pit_scouting collection and indexes") - # Fix any string scouter_ids in pit_scouting collection - self._migrate_pit_scouting_scouter_ids() - - def _migrate_pit_scouting_scouter_ids(self): - """Migrate string scouter_ids to ObjectId in pit_scouting collection""" - try: - # Find documents where scouter_id is a string - for doc in self.db.pit_scouting.find({"scouter_id": {"$type": "string"}}): - try: - # Convert string to ObjectId - self.db.pit_scouting.update_one( - {"_id": doc["_id"]}, - {"$set": {"scouter_id": ObjectId(doc["scouter_id"])}} - ) - logger.info(f"Migrated pit scouting document {doc['_id']} scouter_id to ObjectId") - except Exception as e: - logger.error(f"Failed to migrate pit scouting document {doc['_id']}: {str(e)}") - except Exception as e: - logger.error(f"Error during pit scouting migration: {str(e)}") - def _create_team_data_collection(self): self.db.create_collection("team_data") self.db.team_data.create_index([("team_number", 1)]) @@ -121,27 +101,17 @@ def add_scouting_data(self, data, scouter_id): "match_number": 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)), - + # Fuel + "auto_fuel": int(data.get('auto_fuel', 0)), + "transition_fuel": int(data.get("transition_fuel", 0)), + "teleop_shift_1_fuel": int(data.get('teleop_shift_1_fuel', 0)), + "teleop_shift_2_fuel": int(data.get('teleop_shift_2_fuel', 0)), + "teleop_shift_3_fuel": int(data.get('teleop_shift_3_fuel', 0)), + "teleop_shift_4_fuel": int(data.get('teleop_shift_4_fuel', 0)), + "endgame_fuel": int(data.get('endgame_fuel', 0)), + # Climb + "climb_level": int(data.get('climb_level', 0)), "climb_type": data.get("climb_type", ""), "climb_success": bool(data.get("climb_success", False)), @@ -214,18 +184,18 @@ def get_all_scouting_data(self, user_team_number=None, user_id=None): "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, + + # Fuel Stats + "auto_fuel": 1, + "transition_fuel": 1, + "teleop_shift_1_fuel": 1, + "teleop_shift_2_fuel": 1, + "teleop_shift_3_fuel": 1, + "teleop_shift_4_fuel": 1, + "endgame_fuel": 1, + + # Climb Stats + "climb_level": 1, "climb_type": 1, "climb_success": 1, "defense_rating": 1, @@ -341,17 +311,17 @@ def update_team_data(self, team_id, data, scouter_id): "match_number": data["match_number"], "alliance": alliance, - # Coral scoring - "coral_level1": int(data.get("coral_level1", 0)), - "coral_level2": int(data.get("coral_level2", 0)), - "coral_level3": int(data.get("coral_level3", 0)), - "coral_level4": int(data.get("coral_level4", 0)), + # Fuel Stats + "auto_fuel": int(data.get("auto_fuel", 0)), + "transition_fuel": int(data.get("transition_fuel", 0)), + "teleop_shift_1_fuel": int(data.get("teleop_shift_1_fuel", 0)), + "teleop_shift_2_fuel": int(data.get("teleop_shift_2_fuel", 0)), + "teleop_shift_3_fuel": int(data.get("teleop_shift_3_fuel", 0)), + "teleop_shift_4_fuel": int(data.get("teleop_shift_4_fuel", 0)), + "endgame_fuel": int(data.get("endgame_fuel", 0)), - # Algae scoring - "algae_net": int(data.get("algae_net", 0)), - "algae_processor": int(data.get("algae_processor", 0)), - - # Climb + # Climb Stats + "climb_level": int(data.get("climb_level", 0)), "climb_type": data.get("climb_type", ""), "climb_success": bool(data.get("climb_success", False)), @@ -368,33 +338,13 @@ def update_team_data(self, team_id, data, scouter_id): # Notes "notes": data.get("notes", ""), - - # 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)), } result = self.db.team_data.update_one( {"_id": ObjectId(team_id)}, {"$set": updated_data}, ) - return result.modified_count > 0 + return result.matched_count > 0 except Exception as e: logger.error(f"Error updating team data: {str(e)}") return False @@ -470,18 +420,20 @@ def get_team_stats(self, team_number): "$group": { "_id": "$team_number", "matches_played": {"$sum": 1}, - "total_coral": { + "total_fuel": { "$sum": { "$add": [ - "$coral_level1", - "$coral_level2", - "$coral_level3", - "$coral_level4" + {"$ifNull": ["$auto_fuel", 0]}, + {"$ifNull": ["$teleop_shift_1_fuel", 0]}, + {"$ifNull": ["$teleop_shift_2_fuel", 0]}, + {"$ifNull": ["$teleop_shift_3_fuel", 0]}, + {"$ifNull": ["$teleop_shift_4_fuel", 0]}, + {"$ifNull": ["$endgame_fuel", 0]} ] } }, - "total_algae": { - "$sum": {"$add": ["$algae_net", "$algae_processor"]} + "avg_climb_level": { + "$avg": {"$ifNull": ["$climb_level", 0]} }, "successful_climbs": { "$sum": {"$cond": ["$climb_success", 1, 0]} @@ -496,8 +448,8 @@ def get_team_stats(self, team_number): if not result: return { "matches_played": 0, - "total_coral": 0, - "total_algae": 0, + "total_fuel": 0, + "avg_climb_level": 0, "successful_climbs": 0, "total_defense": 0, "total_points": 0 @@ -638,7 +590,6 @@ def get_pit_scouting(self, team_number): "motor_details": 1, "motor_count": 1, "dimensions": 1, - "mechanisms": 1, "programming_language": 1, "autonomous_capabilities": 1, "driver_experience": 1, @@ -720,7 +671,6 @@ def get_all_pit_scouting(self, user_team_number=None, user_id=None): "motor_details": 1, "motor_count": 1, "dimensions": 1, - "mechanisms": 1, "programming_language": 1, "autonomous_capabilities": 1, "driver_experience": 1, diff --git a/app/static/images/field-2026.png b/app/static/images/field-2026.png new file mode 100644 index 0000000..8b4f339 Binary files /dev/null and b/app/static/images/field-2026.png differ diff --git a/app/static/images/net-field-2025.png b/app/static/images/net-field-2025.png deleted file mode 100644 index ca038a2..0000000 Binary files a/app/static/images/net-field-2025.png and /dev/null differ diff --git a/app/static/images/processor-field-2025.png b/app/static/images/processor-field-2025.png deleted file mode 100644 index 71c1ed3..0000000 Binary files a/app/static/images/processor-field-2025.png and /dev/null differ diff --git a/app/static/images/reef-field-2025.png b/app/static/images/reef-field-2025.png deleted file mode 100644 index fd33bc6..0000000 Binary files a/app/static/images/reef-field-2025.png and /dev/null differ diff --git a/app/static/js/Canvas.js b/app/static/js/Canvas.js index c127bab..b633410 100644 --- a/app/static/js/Canvas.js +++ b/app/static/js/Canvas.js @@ -104,9 +104,9 @@ class Canvas { }; // Initialize LocalForage - // this.storage = localforage.createInstance({ - // name: 'CanvasField' - // }); + this.storage = localforage.createInstance({ + name: 'CanvasField' + }); // Perfect freehand settings this.pressure = 0.5; @@ -122,8 +122,8 @@ class Canvas { this.lastWidth = 0; this.velocityFilterWeight = 0.7; - // // Auto-save interval (every 30 seconds) - // this.autoSaveInterval = setInterval(() => this.autoSave(), 30000); + // Auto-save interval (every 30 seconds) + this.autoSaveInterval = setInterval(() => this.autoSave(), 30000); // Initialize this.resizeCanvas(); @@ -1727,15 +1727,15 @@ class Canvas { } } - // // LocalForage methods - // async autoSave() { - // try { - // await this.storage.setItem('lastSession', this.saveToJSON()); - // this.showStatus('Auto-saved'); - // } catch (error) { - // console.error('Auto-save failed:', error); - // } - // } + // LocalForage methods + async autoSave() { + try { + await this.storage.setItem('lastSession', this.saveToJSON()); + this.showStatus('Auto-saved'); + } catch (error) { + console.error('Auto-save failed:', error); + } + } // Selection methods isPointInStroke(point, stroke) { diff --git a/app/static/js/compare.js b/app/static/js/compare.js index f2dc96d..9281cca 100644 --- a/app/static/js/compare.js +++ b/app/static/js/compare.js @@ -92,24 +92,24 @@ function updateTeamCards(data) { const stats = teamData.stats || {}; // Update Auto Period stats - document.getElementById(`team${cardNum}-auto-l1`).textContent = (stats.avg_auto_coral_level1 || 0).toFixed(2); - document.getElementById(`team${cardNum}-auto-l2`).textContent = (stats.avg_auto_coral_level2 || 0).toFixed(2); - document.getElementById(`team${cardNum}-auto-l3`).textContent = (stats.avg_auto_coral_level3 || 0).toFixed(2); - document.getElementById(`team${cardNum}-auto-l4`).textContent = (stats.avg_auto_coral_level4 || 0).toFixed(2); - document.getElementById(`team${cardNum}-auto-net`).textContent = (stats.avg_auto_algae_net || 0).toFixed(2); - document.getElementById(`team${cardNum}-auto-processor`).textContent = (stats.avg_auto_algae_processor || 0).toFixed(2); + document.getElementById(`team${cardNum}-auto-fuel`).textContent = (stats.avg_auto_fuel || 0).toFixed(2); // Update Teleop Period stats - document.getElementById(`team${cardNum}-teleop-l1`).textContent = (stats.avg_teleop_coral_level1 || 0).toFixed(2); - document.getElementById(`team${cardNum}-teleop-l2`).textContent = (stats.avg_teleop_coral_level2 || 0).toFixed(2); - document.getElementById(`team${cardNum}-teleop-l3`).textContent = (stats.avg_teleop_coral_level3 || 0).toFixed(2); - document.getElementById(`team${cardNum}-teleop-l4`).textContent = (stats.avg_teleop_coral_level4 || 0).toFixed(2); - document.getElementById(`team${cardNum}-teleop-net`).textContent = (stats.avg_teleop_algae_net || 0).toFixed(2); - document.getElementById(`team${cardNum}-teleop-processor`).textContent = (stats.avg_teleop_algae_processor || 0).toFixed(2); + document.getElementById(`team${cardNum}-transition-fuel`).textContent = (stats.avg_transition_fuel || 0).toFixed(2); + document.getElementById(`team${cardNum}-teleop-s1`).textContent = (stats.avg_teleop_shift_1_fuel || 0).toFixed(2); + document.getElementById(`team${cardNum}-teleop-s2`).textContent = (stats.avg_teleop_shift_2_fuel || 0).toFixed(2); + document.getElementById(`team${cardNum}-teleop-s3`).textContent = (stats.avg_teleop_shift_3_fuel || 0).toFixed(2); + document.getElementById(`team${cardNum}-teleop-s4`).textContent = (stats.avg_teleop_shift_4_fuel || 0).toFixed(2); // Update Endgame stats + document.getElementById(`team${cardNum}-endgame-fuel`).textContent = (stats.avg_endgame_fuel || 0).toFixed(2); document.getElementById(`team${cardNum}-climb-success`).textContent = ((stats.climb_success_rate || 0) * 100).toFixed(1); - document.getElementById(`team${cardNum}-preferred-climb`).textContent = stats.preferred_climb_type || '-'; + + const matches = stats.matches_played || 1; + const l1 = ((stats.climb_level_1_count || 0) / matches * 100).toFixed(0); + const l2 = ((stats.climb_level_2_count || 0) / matches * 100).toFixed(0); + const l3 = ((stats.climb_level_3_count || 0) / matches * 100).toFixed(0); + document.getElementById(`team${cardNum}-preferred-climb`).textContent = `L1: ${l1}% | L2: ${l2}% | L3: ${l3}%`; // Update Defense stats document.getElementById(`team${cardNum}-defense`).textContent = `${(stats.defense_rating || 0).toFixed(1)}/5`; @@ -159,6 +159,8 @@ function updateRadarChart(data) { data: [ normalized.auto_scoring || 0, normalized.teleop_scoring || 0, + normalized.endgame_scoring || 0, + normalized.climb_level || 0, normalized.climb_rating || 0, normalized.defense_rating || 0 ], @@ -175,7 +177,7 @@ function updateRadarChart(data) { radarChart = new Chart(canvas.getContext('2d'), { type: 'radar', data: { - labels: ['Auto Scoring', 'Teleop Scoring', 'Climb Success', 'Defense Rating'], + labels: ['Auto Fuel', 'Teleop Fuel', 'Endgame Fuel', 'Climb Level', 'Climb Success', 'Defense Rating'], datasets: datasets }, options: { @@ -232,19 +234,19 @@ function updateRawDataTable(data) { ${match.match_number || '-'} - ${match.auto_coral_level1 || 0}/${match.auto_coral_level2 || 0}/${match.auto_coral_level3 || 0}/${match.auto_coral_level4 || 0} + ${match.auto_fuel || 0} - ${match.auto_algae_net || 0}/${match.auto_algae_processor || 0} + ${match.transition_fuel || 0} - ${match.teleop_coral_level1 || 0}/${match.teleop_coral_level2 || 0}/${match.teleop_coral_level3 || 0}/${match.teleop_coral_level4 || 0} + ${match.teleop_shift_1_fuel || 0} / ${match.teleop_shift_2_fuel || 0} / ${match.teleop_shift_3_fuel || 0} / ${match.teleop_shift_4_fuel || 0} - ${match.teleop_algae_net || 0}/${match.teleop_algae_processor || 0} + ${match.endgame_fuel || 0} - ${match.climb_success ? `${match.climb_type || 'Yes'}` : 'No'} + Level ${match.climb_level || 0} ${match.auto_path ? @@ -258,7 +260,7 @@ function updateRawDataTable(data) { ${match.notes || '-'} - ${match.scouter_name || '-'} + ${match.scouter?.username || match.scouter_name || '-'} `; tbody.appendChild(row); @@ -287,7 +289,7 @@ function showAutoPath(pathData, autoNotes, deviceType) { const CanvasField = new Canvas({ canvas: document.getElementById('modalAutoPathCanvas'), container: container, - backgroundImage: '/static/images/field-2025.png', + backgroundImage: '/static/images/field-2026.png', maxPanDistance: 1000 }); diff --git a/app/static/js/lighthouse/auton.js b/app/static/js/lighthouse/auton.js index cd58d39..4f7f69c 100644 --- a/app/static/js/lighthouse/auton.js +++ b/app/static/js/lighthouse/auton.js @@ -81,7 +81,7 @@ function initCanvas() { canvasField = new Canvas({ canvas: canvas, container: container, - backgroundImage: '/static/images/field-2025.png', + backgroundImage: '/static/images/field-2026.png', maxPanDistance: 1000, // Add a simple status display function showStatus: (message) => { diff --git a/app/static/js/pit-scouting/add.js b/app/static/js/pit-scouting/add.js index 5f3b449..4e4ab66 100644 --- a/app/static/js/pit-scouting/add.js +++ b/app/static/js/pit-scouting/add.js @@ -21,45 +21,9 @@ function toggleScoringFields(type, enabled) { } } -function toggleClimberFields(enabled) { - const fields = ['climber_type', 'climber_notes']; - fields.forEach(field => { - const element = document.querySelector(`[name="${field}"]`); - if (element) { - element.disabled = !enabled; - if (!enabled) { - if (element.tagName === 'SELECT') { - element.value = ''; - } else { - element.value = ''; - } - } - } - }); -} - // Add event listeners document.querySelectorAll('[name="has_auto"]').forEach(radio => { radio.addEventListener('change', (e) => { toggleAutoFields(e.target.value === 'true'); }); }); - -['coral', 'algae'].forEach(type => { - document.querySelectorAll(`[name="${type}_scoring_enabled"]`).forEach(radio => { - radio.addEventListener('change', (e) => { - toggleScoringFields(type, e.target.value === 'true'); - }); - }); -}); - -document.querySelector('[name="has_climber"]').addEventListener('change', (e) => { - toggleClimberFields(e.target.checked); -}); - -// Initialize fields on page load -toggleAutoFields(document.querySelector('[name="has_auto"][value="true"]').checked); -['coral', 'algae'].forEach(type => { - toggleScoringFields(type, document.querySelector(`[name="${type}_scoring_enabled"][value="true"]`).checked); -}); -toggleClimberFields(document.querySelector('[name="has_climber"]').checked); \ No newline at end of file diff --git a/app/static/js/pit-scouting/edit.js b/app/static/js/pit-scouting/edit.js index 5f3b449..4e4ab66 100644 --- a/app/static/js/pit-scouting/edit.js +++ b/app/static/js/pit-scouting/edit.js @@ -21,45 +21,9 @@ function toggleScoringFields(type, enabled) { } } -function toggleClimberFields(enabled) { - const fields = ['climber_type', 'climber_notes']; - fields.forEach(field => { - const element = document.querySelector(`[name="${field}"]`); - if (element) { - element.disabled = !enabled; - if (!enabled) { - if (element.tagName === 'SELECT') { - element.value = ''; - } else { - element.value = ''; - } - } - } - }); -} - // Add event listeners document.querySelectorAll('[name="has_auto"]').forEach(radio => { radio.addEventListener('change', (e) => { toggleAutoFields(e.target.value === 'true'); }); }); - -['coral', 'algae'].forEach(type => { - document.querySelectorAll(`[name="${type}_scoring_enabled"]`).forEach(radio => { - radio.addEventListener('change', (e) => { - toggleScoringFields(type, e.target.value === 'true'); - }); - }); -}); - -document.querySelector('[name="has_climber"]').addEventListener('change', (e) => { - toggleClimberFields(e.target.checked); -}); - -// Initialize fields on page load -toggleAutoFields(document.querySelector('[name="has_auto"][value="true"]').checked); -['coral', 'algae'].forEach(type => { - toggleScoringFields(type, document.querySelector(`[name="${type}_scoring_enabled"][value="true"]`).checked); -}); -toggleClimberFields(document.querySelector('[name="has_climber"]').checked); \ No newline at end of file diff --git a/app/static/js/pit-scouting/list.js b/app/static/js/pit-scouting/list.js index 1b8efe2..66cc10a 100644 --- a/app/static/js/pit-scouting/list.js +++ b/app/static/js/pit-scouting/list.js @@ -6,11 +6,6 @@ function exportToCSV() { 'Motor Count', 'Motor Types', 'Dimensions (L x W x H)', - 'Coral Scoring', - 'Algae Scoring', - 'Has Climber', - 'Climber Type', - 'Climber Notes', 'Programming Language', 'Has Auto', 'Auto Routes', @@ -48,13 +43,6 @@ function exportToCSV() { // Dimensions const dimensions = row.querySelector('td:nth-child(4)').textContent.trim(); - - // Scoring Mechanisms - const coralScoring = row.querySelector('td:nth-child(5)').textContent.trim(); - const algaeScoring = row.querySelector('td:nth-child(6)').textContent.trim(); - - // Climber - const climberCell = row.querySelector('td:nth-child(7)'); const hasClimber = !climberCell.textContent.includes('🗙'); let climberType = '', climberNotes = ''; if (hasClimber) { @@ -109,11 +97,7 @@ function exportToCSV() { escapeField(motorCount), escapeField(motorTypes), escapeField(dimensions), - escapeField(coralScoring), - escapeField(algaeScoring), hasClimber ? 'Yes' : 'No', - escapeField(climberType), - escapeField(climberNotes), escapeField(programmingLang), hasAuto ? 'Yes' : 'No', escapeField(autoRoutes), diff --git a/app/static/js/radar.js b/app/static/js/radar.js index df9cb8d..196c2e8 100644 --- a/app/static/js/radar.js +++ b/app/static/js/radar.js @@ -119,14 +119,14 @@ function updateRadarCharts(teamsData) { const radarData = { team: teamNum, values: [ - // Auto scoring (combine coral and algae) + // Auto scoring (Fuel) stats.auto_scoring || 0, - // Teleop scoring (combine coral and algae) + // Teleop scoring (Fuel) stats.teleop_scoring || 0, // Climb success rate - stats.climb_rating || 0, + (stats.climb_rating || 0) * 10, // Defense rating - stats.defense_rating || 0, + (stats.defense_rating || 0) * 2 ] }; diff --git a/app/static/js/scout/add.js b/app/static/js/scout/add.js index 88996b4..4fcf188 100644 --- a/app/static/js/scout/add.js +++ b/app/static/js/scout/add.js @@ -86,7 +86,7 @@ document.addEventListener('DOMContentLoaded', function() { initialColor: '#2563eb', initialThickness: 3, maxPanDistance: 1000, - backgroundImage: '/static/images/field-2025.png', + backgroundImage: '/static/images/field-2026.png', readonly: false }); @@ -99,7 +99,7 @@ document.addEventListener('DOMContentLoaded', function() { console.error('Failed to load background image'); CanvasField.showStatus('Error loading field image'); }; - testImage.src = '/static/images/field-2025.png'; + testImage.src = '/static/images/field-2026.png'; // Prevent page scrolling when using mouse wheel on canvas const canvas = document.getElementById('autoPath'); diff --git a/app/static/js/scout/edit.js b/app/static/js/scout/edit.js index 6ed56e6..d28387c 100644 --- a/app/static/js/scout/edit.js +++ b/app/static/js/scout/edit.js @@ -74,7 +74,7 @@ document.addEventListener('DOMContentLoaded', function() { initialColor: '#2563eb', initialThickness: 3, maxPanDistance: 1000, - backgroundImage: '/static/images/field-2025.png', + backgroundImage: '/static/images/field-2026.png', readonly: false }); @@ -87,7 +87,7 @@ document.addEventListener('DOMContentLoaded', function() { console.error('Failed to load background image'); CanvasField.showStatus('Error loading field image'); }; - testImage.src = '/static/images/field-2025.png'; + testImage.src = '/static/images/field-2026.png'; // Load existing path data if available const pathDataInput = document.getElementById('autoPathData'); diff --git a/app/static/js/scout/list.js b/app/static/js/scout/list.js index 25e664a..1e93df3 100644 --- a/app/static/js/scout/list.js +++ b/app/static/js/scout/list.js @@ -56,7 +56,7 @@ function showAutoPath(pathData, autoNotes) { const CanvasField = new Canvas({ canvas: canvas, container: container, - backgroundImage: '/static/images/field-2025.png', + backgroundImage: '/static/images/field-2026.png', maxPanDistance: 1000 }); @@ -108,10 +108,10 @@ function exportToCSV() { 'Match', 'Team Number', 'Alliance', - 'Auto Coral (L1/L2/L3/L4)', - 'Auto Algae (Net/Proc)', - 'Teleop Coral (L1/L2/L3/L4)', - 'Teleop Algae (Net/Proc)', + 'Auto Fuel', + 'Transition Fuel', + 'Teleop Shifts (1-4)', + 'Endgame Fuel', 'Climb', 'Defense Rating', 'Robot Disabled', @@ -128,10 +128,11 @@ function exportToCSV() { const {teamNumber} = row.dataset; const alliance = row.querySelector('td:nth-child(2) span').textContent.trim(); const match = row.querySelector('td:nth-child(3)').textContent.trim(); - const autoCoral = row.querySelector('td:nth-child(4)').textContent.trim(); - const autoAlgae = row.querySelector('td:nth-child(5)').textContent.trim(); - const teleopCoral = row.querySelector('td:nth-child(6)').textContent.trim(); - const teleopAlgae = row.querySelector('td:nth-child(7)').textContent.trim(); + + const autoFuel = row.querySelector('td:nth-child(4)').textContent.trim(); + const transitionFuel = row.querySelector('td:nth-child(5)').textContent.trim(); + const teleopFuel = row.querySelector('td:nth-child(6)').textContent.trim(); + const endgameFuel = row.querySelector('td:nth-child(7)').textContent.trim(); const climb = row.querySelector('td:nth-child(8)').textContent.trim(); const defense = row.querySelector('td:nth-child(10)').textContent.trim(); const robotDisabled = row.querySelector('td:nth-child(11) span').textContent.trim(); @@ -144,10 +145,10 @@ function exportToCSV() { match, teamNumber, alliance, - autoCoral, - autoAlgae, - teleopCoral, - teleopAlgae, + autoFuel, + transitionFuel, + teleopFuel, + endgameFuel, climb, defense, robotDisabled, diff --git a/app/static/js/service-worker.js b/app/static/js/service-worker.js index 7ac2de8..2f9e926 100644 --- a/app/static/js/service-worker.js +++ b/app/static/js/service-worker.js @@ -12,7 +12,7 @@ const STATIC_ASSETS = [ '/static/css/global.css', '/static/css/index.css', '/static/js/Canvas.js', - '/static/images/field-2025.png', + '/static/images/field-2026.png', '/static/images/default_profile.png', '/static/js/notifications.js', '/static/logo.png', diff --git a/app/templates/auth/login.html b/app/templates/auth/login.html index 0c05293..e2d7949 100644 --- a/app/templates/auth/login.html +++ b/app/templates/auth/login.html @@ -56,6 +56,11 @@

Remember me +
+ + Forgot password? + +
@@ -73,9 +78,9 @@

+

diff --git a/app/templates/base.html b/app/templates/base.html index c5ea65d..ec98db4 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -230,7 +230,7 @@
-

© 2024-2025 Team 334

+

© 2024-2026 Team 334

+ + +{% endblock %} \ No newline at end of file diff --git a/app/templates/lighthouse.html b/app/templates/lighthouse.html index 6cb8488..46cb9e6 100644 --- a/app/templates/lighthouse.html +++ b/app/templates/lighthouse.html @@ -114,27 +114,9 @@

Auto Period

-
-
Level 1:
-
0.00 times/match
-
Level 2:
-
0.00 times/match
-
Level 3:
-
0.00 times/match
-
Level 4:
-
0.00 times/match
-
- - -
-

Algae Scoring:

-
-
Net:
-
0.00 times/match
-
Processor:
-
0.00 times/match
-
+
Auto Fuel:
+
0.00 avg
@@ -143,29 +125,29 @@

Algae Scoring:

Teleop Period

- +
-

Coral Scoring:

+

Fuel (Avg per Shift)

-
Level 1:
-
0.00 times/match
-
Level 2:
-
0.00 times/match
-
Level 3:
-
0.00 times/match
-
Level 4:
-
0.00 times/match
+
Transition:
+
0.00
+
Shift 1:
+
0.00
+
Shift 2:
+
0.00
+
Shift 3:
+
0.00
+
Shift 4:
+
0.00
- - + +
-

Algae Scoring:

+

Endgame Fuel

-
Net:
-
0.00 times/match
-
Processor:
-
0.00 times/match
+
Avg:
+
0.00
@@ -221,27 +203,9 @@

Auto Period

-
-
Level 1:
-
0.00 times/match
-
Level 2:
-
0.00 times/match
-
Level 3:
-
0.00 times/match
-
Level 4:
-
0.00 times/match
-
- - -
-

Algae Scoring:

-
-
Net:
-
0.00 times/match
-
Processor:
-
0.00 times/match
-
+
Auto Fuel:
+
0.00 avg
@@ -250,29 +214,29 @@

Algae Scoring:

Teleop Period

- +
-

Coral Scoring:

+

Fuel (Avg per Shift)

-
Level 1:
-
0.00 times/match
-
Level 2:
-
0.00 times/match
-
Level 3:
-
0.00 times/match
-
Level 4:
-
0.00 times/match
+
Transition:
+
0.00
+
Shift 1:
+
0.00
+
Shift 2:
+
0.00
+
Shift 3:
+
0.00
+
Shift 4:
+
0.00
- - + +
-

Algae Scoring:

+

Endgame Fuel

-
Net:
-
0.00 times/match
-
Processor:
-
0.00 times/match
+
Avg:
+
0.00
@@ -328,27 +292,9 @@

Auto Period

-
-
Level 1:
-
0.00 times/match
-
Level 2:
-
0.00 times/match
-
Level 3:
-
0.00 times/match
-
Level 4:
-
0.00 times/match
-
- - -
-

Algae Scoring:

-
-
Net:
-
0.00 times/match
-
Processor:
-
0.00 times/match
-
+
Auto Fuel:
+
0.00 avg
@@ -357,29 +303,29 @@

Algae Scoring:

Teleop Period

- +
-

Coral Scoring:

+

Fuel (Avg per Shift)

-
Level 1:
-
0.00 times/match
-
Level 2:
-
0.00 times/match
-
Level 3:
-
0.00 times/match
-
Level 4:
-
0.00 times/match
+
Transition:
+
0.00
+
Shift 1:
+
0.00
+
Shift 2:
+
0.00
+
Shift 3:
+
0.00
+
Shift 4:
+
0.00
- - + +
-

Algae Scoring:

+

Endgame Fuel

-
Net:
-
0.00 times/match
-
Processor:
-
0.00 times/match
+
Avg:
+
0.00
@@ -472,16 +418,16 @@

Raw Scouting Data

Match - Auto Coral (1-4) + Auto Fuel - Auto Algae (Net/Proc) + Transition Fuel - Teleop Coral (1-4) + Teleop Shifts (1-4) - Teleop Algae (Net/Proc) + Endgame Fuel Climb @@ -492,12 +438,6 @@

Raw Scouting Data

Defense - - Mobility - - - Durability - Notes @@ -547,5 +487,6 @@

Auto Notes

+ {% endblock %} \ No newline at end of file diff --git a/app/templates/lighthouse/auton.html b/app/templates/lighthouse/auton.html index bdf20ce..44bc6ce 100644 --- a/app/templates/lighthouse/auton.html +++ b/app/templates/lighthouse/auton.html @@ -4,6 +4,7 @@ + {% endblock %} diff --git a/app/templates/scouting/add.html b/app/templates/scouting/add.html index 0de12be..b6da9c3 100644 --- a/app/templates/scouting/add.html +++ b/app/templates/scouting/add.html @@ -240,59 +240,17 @@

Auto Path

- +
-

Auto Coral

+

Auto Fuel

- +
- -
-
-
- -
- - - -
-
-
- -
- - - -
-
-
- -
- - @@ -302,66 +260,23 @@

Auto Coral

- - -
-

Auto Algae

-
-
- -
- - - -
-
-
- -
- - - -
-
-
-

- +