From 21cefdf76b7952ec24d8fcd48e1479ac373031a0 Mon Sep 17 00:00:00 2001 From: Shayaan Wadkar <82843611+Shom770@users.noreply.github.com> Date: Sat, 21 Mar 2026 13:16:05 -0400 Subject: [PATCH] viz (#52) --- src/Teams.py | 49 +- src/data/2026vache_match_data.json | 4714 +++++++++++++++++ src/data/statbotics_2026vache.json | 215 + src/page_managers/event_manager.py | 236 +- src/page_managers/match_manager.py | 869 +-- src/page_managers/picklist_manager.py | 255 +- .../ranking_simulator_manager.py | 2 +- .../scouting_accuracy_manager.py | 362 +- src/page_managers/team_manager.py | 747 +-- src/pages/1_Match.py | 116 +- src/pages/2_Hypothetical_Match.py | 118 +- src/pages/3_Event.py | 26 +- src/pages/7_Scouting_Accuracy.py | 26 +- src/requirements.txt | 18 +- src/utils/__init__.py | 1 + src/utils/calculated_stats.py | 374 +- src/utils/constants.py | 108 +- src/utils/functions.py | 120 +- src/utils/statbotics.py | 108 + 19 files changed, 6342 insertions(+), 2122 deletions(-) create mode 100644 src/data/2026vache_match_data.json create mode 100644 src/data/statbotics_2026vache.json create mode 100644 src/utils/statbotics.py diff --git a/src/Teams.py b/src/Teams.py index 71eea8e..a9b2959 100644 --- a/src/Teams.py +++ b/src/Teams.py @@ -14,10 +14,8 @@ team_manager = TeamManager() if __name__ == '__main__': - # Write the title of the page. st.write("# Teams") - # Generate the input section of the `Teams` page. team_number = team_manager.generate_input_section() metric_tab, auto_graphs_tab, teleop_graphs_tab, qualitative_graphs_tab = st.tabs( @@ -25,53 +23,22 @@ ) with metric_tab: - st.write("### Metrics") + st.write("### Quantitative Metrics") + team_manager.generate_quantitative_metrics(team_number) - # Generate metrics (cards with information surrounding teams) + st.divider() + + st.write("### Qualitative Metrics") team_manager.generate_metrics(team_number) with auto_graphs_tab: st.write("#### 🤖 Autonomous Graphs") - - # Create fuel-scored and point-contribution graph tabs. - auto_fuel_tab, auto_point_tab = st.tabs( - ["⛽ Fuel vs Match Index", "🧮 Points vs Match Index"] - ) - - with auto_fuel_tab: - team_manager.generate_autonomous_graphs( - team_number, - type_of_graph=GraphType.FUEL_CONTRIBUTIONS - ) - - with auto_point_tab: - team_manager.generate_autonomous_graphs( - team_number, - type_of_graph=GraphType.POINT_CONTRIBUTIONS - ) + team_manager.generate_autonomous_graphs(team_number) with teleop_graphs_tab: st.write("#### 🎮 Teleop + Endgame Graphs") - - # Create climb-points graph tabs. - teleop_climb_tab, teleop_climb_alt_tab = st.tabs( - ["🧗 Climb Pts vs Match Index", "🧗 Climb Pts vs Match Index"] - ) - - with teleop_climb_tab: - team_manager.generate_teleop_graphs( - team_number, - type_of_graph=GraphType.FUEL_CONTRIBUTIONS - ) - - with teleop_climb_alt_tab: - team_manager.generate_teleop_graphs( - team_number, - type_of_graph=GraphType.POINT_CONTRIBUTIONS - ) + team_manager.generate_teleop_graphs(team_number) with qualitative_graphs_tab: st.write("#### 📝 Qualitative Graphs") - team_manager.generate_qualitative_graphs( - team_number, - ) + team_manager.generate_qualitative_graphs(team_number) diff --git a/src/data/2026vache_match_data.json b/src/data/2026vache_match_data.json new file mode 100644 index 0000000..31e606e --- /dev/null +++ b/src/data/2026vache_match_data.json @@ -0,0 +1,4714 @@ +[ + { + "ScoutId": "Nikhil", + "MatchKey": "qm60", + "Alliance": "red", + "DriverStation": 1, + "TeamNumber": 4099, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near Driver Station", + "Centered, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "true", + "AutoNotes": "", + "TeleopScoringSide": [ + "Centered, near Driver Station", + "Centered, flush with HUB", + "Depot Side, near Driver Station" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "L2", + "ClimbSpeed": "10-20 seconds", + "TeleopNotes": "Incredible scoring pace, back-to-back cycles", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, sprinter" + ], + "DriverRating": "Very Fluid", + "IntakeSpeed": "Very Fast", + "ThroughputSpeed": "Very Good", + "DefenseRating": "Very Good", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Very Good", + "RatingNotes": "Consistent elite performance" + }, + { + "ScoutId": "Nikhil", + "MatchKey": "qm60", + "Alliance": "red", + "DriverStation": 2, + "TeamNumber": 5549, + "StartingPosition": "Human-Player Side, on BUMP", + "AutoScoringSide": [ + "Centered, flush with HUB", + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "Shot preload only", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Through the TRENCH" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Beached on bump", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Good", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Ryan", + "MatchKey": "qm60", + "Alliance": "red", + "DriverStation": 3, + "TeamNumber": 2363, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Human Player Side, near Driver Station" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "Shot preload, missed many", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Missed many shots", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Very Poor", + "IntakeSpeed": "Very Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Inaccurate, choppy driving" + }, + { + "ScoutId": "Nathan", + "MatchKey": "qm60", + "Alliance": "blue", + "DriverStation": 1, + "TeamNumber": 1731, + "StartingPosition": "Center", + "AutoScoringSide": [ + "Centered, near Driver Station", + "Centered, flush with HUB" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "true", + "AutoNotes": "Very fast passing, consistent outpost shots", + "TeleopScoringSide": [ + "Centered, near Driver Station", + "Depot Side, near Driver Station" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "TeleopClimb": "L1", + "ClimbSpeed": "<5 seconds", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, sprinter" + ], + "DriverRating": "Very Fluid", + "IntakeSpeed": "Fast", + "ThroughputSpeed": "Very Good", + "DefenseRating": "Good", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Very fast passing robot, elite throughput" + }, + { + "ScoutId": "Aadhavan", + "MatchKey": "qm60", + "Alliance": "blue", + "DriverStation": 2, + "TeamNumber": 4472, + "StartingPosition": "Center", + "AutoScoringSide": [ + "Human Player Side, near Driver Station" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "Unload preloaded balls, good accuracy", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH", + "Human Player Side, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Push pass cycles", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Passer" + ], + "DriverRating": "Average", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Insane accuracy on passing, slow movement" + }, + { + "ScoutId": "Nathan", + "MatchKey": "qm60", + "Alliance": "blue", + "DriverStation": 3, + "TeamNumber": 5338, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Depot Side, near TRENCH" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Depot Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "true", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Very Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Could not shoot, could not intake, got stuck on ramp" + }, + { + "ScoutId": "Megan", + "MatchKey": "qm61", + "Alliance": "red", + "DriverStation": 1, + "TeamNumber": 9072, + "StartingPosition": "Human-Player Side, on BUMP", + "AutoScoringSide": [ + "Centered, near Driver Station", + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "Good passing cycles in auto", + "TeleopScoringSide": [ + "Centered, near Driver Station", + "Depot Side, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Strong plow strategy working", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper", + "Passer" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Fast", + "ThroughputSpeed": "Good", + "DefenseRating": "Good", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Okay", + "RatingNotes": "Solid passer/shooter, reliable cycles" + }, + { + "ScoutId": "Arnav", + "MatchKey": "qm61", + "Alliance": "red", + "DriverStation": 2, + "TeamNumber": 449, + "StartingPosition": "Depot Side, under TRENCH", + "AutoScoringSide": [ + "Depot Side, near TRENCH" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Human Player Side, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Heavy defense, targeted top scorers", + "Disabled": "true", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shover", + "Defense" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Very Good", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Aadhavan", + "MatchKey": "qm61", + "Alliance": "red", + "DriverStation": 3, + "TeamNumber": 5338, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Human Player Side, near Driver Station" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "No auto movement", + "TeleopScoringSide": [ + "Depot Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Got stuck on bump repeatedly", + "Disabled": "false", + "StabilityRating": "Very tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Very Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Jai", + "MatchKey": "qm61", + "Alliance": "blue", + "DriverStation": 1, + "TeamNumber": 1908, + "StartingPosition": "Human-Player Side, on BUMP", + "AutoScoringSide": [ + "Centered, flush with HUB" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "Plow strat working", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "TeleopClimb": "L1", + "ClimbSpeed": ">20 seconds", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Average", + "IntakeSpeed": "Fast", + "ThroughputSpeed": "Okay", + "DefenseRating": "Poor", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Megan", + "MatchKey": "qm61", + "Alliance": "blue", + "DriverStation": 2, + "TeamNumber": 4099, + "StartingPosition": "Human-Player Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near Driver Station", + "Centered, flush with HUB" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "true", + "AutoNotes": "Outstanding auto, cleared full hopper", + "TeleopScoringSide": [ + "Centered, near Driver Station", + "Depot Side, near Driver Station", + "Centered, flush with HUB" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "TeleopClimb": "L3", + "ClimbSpeed": "10-20 seconds", + "TeleopNotes": "Dominant throughput all match", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, sprinter", + "Shooter, dumper" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Very Fast", + "ThroughputSpeed": "Good", + "DefenseRating": "Good", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Consistent elite performance" + }, + { + "ScoutId": "Nikhil", + "MatchKey": "qm61", + "Alliance": "blue", + "DriverStation": 3, + "TeamNumber": 2363, + "StartingPosition": "Center", + "AutoScoringSide": [ + "Depot Side, near TRENCH" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "true", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Very Poor", + "IntakeSpeed": "Average", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Inaccurate, choppy driving" + }, + { + "ScoutId": "Megan", + "MatchKey": "qm62", + "Alliance": "red", + "DriverStation": 1, + "TeamNumber": 1731, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Centered, flush with HUB", + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "true", + "AutoNotes": "Very fast passing, consistent outpost shots", + "TeleopScoringSide": [ + "Centered, near Driver Station", + "Depot Side, near Driver Station", + "Centered, flush with HUB" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "TeleopClimb": "L1", + "ClimbSpeed": "<5 seconds", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, sprinter" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Fast", + "ThroughputSpeed": "Very Good", + "DefenseRating": "Good", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Very Good", + "RatingNotes": "" + }, + { + "ScoutId": "Aaruj", + "MatchKey": "qm62", + "Alliance": "red", + "DriverStation": 2, + "TeamNumber": 5549, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near TRENCH" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Centered, flush with HUB", + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "TeleopClimb": "L1", + "ClimbSpeed": "10-20 seconds", + "TeleopNotes": "Stopped moving briefly", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Average", + "IntakeSpeed": "Average", + "ThroughputSpeed": "Okay", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Aaruj", + "MatchKey": "qm62", + "Alliance": "red", + "DriverStation": 3, + "TeamNumber": 449, + "StartingPosition": "Human-Player Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near TRENCH" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Depot Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Through the TRENCH" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Defense", + "Shover" + ], + "DriverRating": "Average", + "IntakeSpeed": "Very Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Nikhil", + "MatchKey": "qm62", + "Alliance": "blue", + "DriverStation": 1, + "TeamNumber": 9072, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near Driver Station", + "Centered, flush with HUB", + "Centered, near Driver Station" + ], + "AutoTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Depot Side, near Driver Station", + "Centered, near Driver Station", + "Centered, flush with HUB" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "TeleopClimb": "L2", + "ClimbSpeed": "5-10 seconds", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Passer" + ], + "DriverRating": "Very Fluid", + "IntakeSpeed": "Fast", + "ThroughputSpeed": "Very Good", + "DefenseRating": "Good", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Okay", + "RatingNotes": "Solid passer/shooter, reliable cycles" + }, + { + "ScoutId": "Nikhil", + "MatchKey": "qm62", + "Alliance": "blue", + "DriverStation": 2, + "TeamNumber": 1908, + "StartingPosition": "Depot Side, under TRENCH", + "AutoScoringSide": [ + "Depot Side, near TRENCH" + ], + "AutoTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "Plow strat working", + "TeleopScoringSide": [ + "Depot Side, near TRENCH", + "Human Player Side, near Driver Station" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Average", + "ThroughputSpeed": "Good", + "DefenseRating": "Poor", + "IntakeDefenseRating": "Okay", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Ryan", + "MatchKey": "qm62", + "Alliance": "blue", + "DriverStation": 3, + "TeamNumber": 5338, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Human Player Side, near TRENCH" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Very Poor", + "IntakeSpeed": "Very Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Arnav", + "MatchKey": "qm63", + "Alliance": "red", + "DriverStation": 1, + "TeamNumber": 4099, + "StartingPosition": "Center", + "AutoScoringSide": [ + "Depot Side, near Driver Station", + "Centered, near Driver Station", + "Centered, flush with HUB" + ], + "AutoTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "AutoClimb": "true", + "AutoNotes": "Outstanding auto, cleared full hopper", + "TeleopScoringSide": [ + "Centered, flush with HUB", + "Centered, near Driver Station" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Through the TRENCH" + ], + "TeleopClimb": "L2", + "ClimbSpeed": "5-10 seconds", + "TeleopNotes": "Incredible scoring pace, back-to-back cycles", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper", + "Shooter, sprinter" + ], + "DriverRating": "Very Fluid", + "IntakeSpeed": "Very Fast", + "ThroughputSpeed": "Very Good", + "DefenseRating": "Good", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Aaruj", + "MatchKey": "qm63", + "Alliance": "red", + "DriverStation": 2, + "TeamNumber": 4472, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Centered, near Driver Station", + "Depot Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Passer" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Insane accuracy on passing, slow movement" + }, + { + "ScoutId": "Nikhil", + "MatchKey": "qm63", + "Alliance": "red", + "DriverStation": 3, + "TeamNumber": 5338, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Human Player Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "No auto movement", + "TeleopScoringSide": [ + "Human Player Side, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Very tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Very Slow", + "ThroughputSpeed": "Very Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Could not shoot, could not intake, got stuck on ramp" + }, + { + "ScoutId": "Aaruj", + "MatchKey": "qm63", + "Alliance": "blue", + "DriverStation": 1, + "TeamNumber": 1731, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Centered, near Driver Station", + "Centered, flush with HUB" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "Very fast passing, consistent outpost shots", + "TeleopScoringSide": [ + "Centered, flush with HUB", + "Depot Side, near Driver Station" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "TeleopClimb": "L1", + "ClimbSpeed": ">20 seconds", + "TeleopNotes": "High-speed continuous cycles", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, sprinter" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Fast", + "ThroughputSpeed": "Very Good", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Very fast passing robot, elite throughput" + }, + { + "ScoutId": "Aaruj", + "MatchKey": "qm63", + "Alliance": "blue", + "DriverStation": 2, + "TeamNumber": 9072, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Depot Side, near Driver Station", + "Centered, flush with HUB" + ], + "AutoTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Centered, flush with HUB", + "Depot Side, near Driver Station", + "Centered, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "TeleopClimb": "L1", + "ClimbSpeed": "5-10 seconds", + "TeleopNotes": "Strong plow strategy working", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper", + "Passer" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Very Fast", + "ThroughputSpeed": "Good", + "DefenseRating": "Good", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Jai", + "MatchKey": "qm63", + "Alliance": "blue", + "DriverStation": 3, + "TeamNumber": 449, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near TRENCH", + "Human Player Side, near Driver Station" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "Stalled in neutral zone", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shover", + "Defense" + ], + "DriverRating": "Average", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Okay", + "DefenseRating": "Good", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Aadhavan", + "MatchKey": "qm64", + "Alliance": "red", + "DriverStation": 1, + "TeamNumber": 1908, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Centered, near Driver Station", + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "Plow strat working", + "TeleopScoringSide": [ + "Centered, flush with HUB" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Very Fluid", + "IntakeSpeed": "Fast", + "ThroughputSpeed": "Good", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Reliable mid-tier scorer" + }, + { + "ScoutId": "Megan", + "MatchKey": "qm64", + "Alliance": "red", + "DriverStation": 2, + "TeamNumber": 2363, + "StartingPosition": "Depot Side, under TRENCH", + "AutoScoringSide": [ + "Human Player Side, near TRENCH" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "Shot preload, missed many", + "TeleopScoringSide": [ + "Human Player Side, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Choppy driving, poor accuracy", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Average", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Inaccurate, choppy driving" + }, + { + "ScoutId": "Jai", + "MatchKey": "qm64", + "Alliance": "red", + "DriverStation": 3, + "TeamNumber": 5338, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Human Player Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Human Player Side, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Through the TRENCH" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Got stuck on bump repeatedly", + "Disabled": "true", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Very Poor", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Very Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Could not shoot, could not intake, got stuck on ramp" + }, + { + "ScoutId": "Aadhavan", + "MatchKey": "qm64", + "Alliance": "blue", + "DriverStation": 1, + "TeamNumber": 4099, + "StartingPosition": "Center", + "AutoScoringSide": [ + "Centered, flush with HUB", + "Centered, near Driver Station" + ], + "AutoTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "AutoClimb": "true", + "AutoNotes": "", + "TeleopScoringSide": [ + "Depot Side, near Driver Station", + "Centered, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "L2", + "ClimbSpeed": "5-10 seconds", + "TeleopNotes": "Incredible scoring pace, back-to-back cycles", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper", + "Shooter, sprinter" + ], + "DriverRating": "Very Fluid", + "IntakeSpeed": "Very Fast", + "ThroughputSpeed": "Good", + "DefenseRating": "Good", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Very Good", + "RatingNotes": "" + }, + { + "ScoutId": "Megan", + "MatchKey": "qm64", + "Alliance": "blue", + "DriverStation": 2, + "TeamNumber": 5549, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "Shot preload only", + "TeleopScoringSide": [ + "Depot Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "L1", + "ClimbSpeed": "10-20 seconds", + "TeleopNotes": "Beached on bump", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Average", + "IntakeSpeed": "Average", + "ThroughputSpeed": "Okay", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Nathan", + "MatchKey": "qm64", + "Alliance": "blue", + "DriverStation": 3, + "TeamNumber": 449, + "StartingPosition": "Center", + "AutoScoringSide": [ + "Depot Side, near Driver Station", + "Centered, near Driver Station" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "Didn't move", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Heavy defense, targeted top scorers", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shover", + "Defense" + ], + "DriverRating": "Average", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Okay", + "DefenseRating": "Very Good", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Very fluid defense, low scoring contribution" + }, + { + "ScoutId": "Ryan", + "MatchKey": "qm65", + "Alliance": "red", + "DriverStation": 1, + "TeamNumber": 9072, + "StartingPosition": "Depot Side, under TRENCH", + "AutoScoringSide": [ + "Centered, near Driver Station", + "Centered, flush with HUB" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "Good passing cycles in auto", + "TeleopScoringSide": [ + "Centered, flush with HUB", + "Depot Side, near Driver Station" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "TeleopClimb": "L1", + "ClimbSpeed": "10-20 seconds", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper", + "Passer" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Fast", + "ThroughputSpeed": "Very Good", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Okay", + "RatingNotes": "Solid passer/shooter, reliable cycles" + }, + { + "ScoutId": "Nikhil", + "MatchKey": "qm65", + "Alliance": "red", + "DriverStation": 2, + "TeamNumber": 4472, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Depot Side, near Driver Station", + "Human Player Side, near TRENCH" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Depot Side, near Driver Station", + "Centered, flush with HUB" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Push pass cycles", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Passer" + ], + "DriverRating": "Average", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Okay", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Jai", + "MatchKey": "qm65", + "Alliance": "red", + "DriverStation": 3, + "TeamNumber": 2363, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Human Player Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Depot Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Choppy driving, poor accuracy", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Average", + "IntakeSpeed": "Very Slow", + "ThroughputSpeed": "Okay", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Arnav", + "MatchKey": "qm65", + "Alliance": "blue", + "DriverStation": 1, + "TeamNumber": 1731, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Depot Side, near Driver Station", + "Centered, flush with HUB" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "Very fast passing, consistent outpost shots", + "TeleopScoringSide": [ + "Centered, flush with HUB", + "Centered, near Driver Station" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "L1", + "ClimbSpeed": ">20 seconds", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, sprinter" + ], + "DriverRating": "Very Fluid", + "IntakeSpeed": "Very Fast", + "ThroughputSpeed": "Good", + "DefenseRating": "Good", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Ryan", + "MatchKey": "qm65", + "Alliance": "blue", + "DriverStation": 2, + "TeamNumber": 1908, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Depot Side, near Driver Station", + "Depot Side, near TRENCH" + ], + "AutoTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Depot Side, near TRENCH", + "Depot Side, near Driver Station" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Fast", + "ThroughputSpeed": "Good", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Okay", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Megan", + "MatchKey": "qm65", + "Alliance": "blue", + "DriverStation": 3, + "TeamNumber": 5338, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Human Player Side, near TRENCH" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "No auto movement", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Could not intake", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Average", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Very Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Could not shoot, could not intake, got stuck on ramp" + }, + { + "ScoutId": "Megan", + "MatchKey": "qm66", + "Alliance": "red", + "DriverStation": 1, + "TeamNumber": 4099, + "StartingPosition": "Depot Side, under TRENCH", + "AutoScoringSide": [ + "Centered, near Driver Station", + "Centered, flush with HUB" + ], + "AutoTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "AutoClimb": "true", + "AutoNotes": "", + "TeleopScoringSide": [ + "Centered, near Driver Station", + "Centered, flush with HUB", + "Depot Side, near Driver Station" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "TeleopClimb": "L3", + "ClimbSpeed": "10-20 seconds", + "TeleopNotes": "Incredible scoring pace, back-to-back cycles", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, sprinter", + "Shooter, dumper" + ], + "DriverRating": "Very Fluid", + "IntakeSpeed": "Very Fast", + "ThroughputSpeed": "Very Good", + "DefenseRating": "Good", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Very Good", + "RatingNotes": "Best robot at the event" + }, + { + "ScoutId": "Arnav", + "MatchKey": "qm66", + "Alliance": "red", + "DriverStation": 2, + "TeamNumber": 449, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Centered, near Driver Station" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "Didn't move", + "TeleopScoringSide": [ + "Human Player Side, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Heavy defense, targeted top scorers", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Defense" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Good", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Very fluid defense, low scoring contribution" + }, + { + "ScoutId": "Luke", + "MatchKey": "qm66", + "Alliance": "red", + "DriverStation": 3, + "TeamNumber": 5338, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Human Player Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "Missed every shot", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Through the TRENCH" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Broke almost immediately", + "Disabled": "false", + "StabilityRating": "Very tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Could not shoot, could not intake, got stuck on ramp" + }, + { + "ScoutId": "Aaruj", + "MatchKey": "qm66", + "Alliance": "blue", + "DriverStation": 1, + "TeamNumber": 9072, + "StartingPosition": "Center", + "AutoScoringSide": [ + "Centered, near Driver Station", + "Centered, flush with HUB" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "Good passing cycles in auto", + "TeleopScoringSide": [ + "Depot Side, near Driver Station", + "Centered, near Driver Station" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "TeleopClimb": "L1", + "ClimbSpeed": "10-20 seconds", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Fast", + "ThroughputSpeed": "Very Good", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Okay", + "RatingNotes": "Solid passer/shooter, reliable cycles" + }, + { + "ScoutId": "Arnav", + "MatchKey": "qm66", + "Alliance": "blue", + "DriverStation": 2, + "TeamNumber": 5549, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Depot Side, near TRENCH", + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "Shot preload only", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH", + "Depot Side, near TRENCH" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Stopped moving briefly", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Average", + "IntakeSpeed": "Average", + "ThroughputSpeed": "Okay", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Arnav", + "MatchKey": "qm66", + "Alliance": "blue", + "DriverStation": 3, + "TeamNumber": 2363, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Human Player Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "Shot preload, missed many", + "TeleopScoringSide": [ + "Human Player Side, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Missed many shots", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Okay", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Inaccurate, choppy driving" + }, + { + "ScoutId": "Nikhil", + "MatchKey": "qm67", + "Alliance": "red", + "DriverStation": 1, + "TeamNumber": 1731, + "StartingPosition": "Center", + "AutoScoringSide": [ + "Centered, flush with HUB", + "Centered, near Driver Station", + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "Very fast passing, consistent outpost shots", + "TeleopScoringSide": [ + "Centered, near Driver Station", + "Depot Side, near Driver Station", + "Centered, flush with HUB" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "TeleopClimb": "L2", + "ClimbSpeed": "5-10 seconds", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, sprinter" + ], + "DriverRating": "Very Fluid", + "IntakeSpeed": "Very Fast", + "ThroughputSpeed": "Very Good", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Very fast passing robot, elite throughput" + }, + { + "ScoutId": "Megan", + "MatchKey": "qm67", + "Alliance": "red", + "DriverStation": 2, + "TeamNumber": 4472, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Centered, near Driver Station", + "Centered, flush with HUB" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "Unload preloaded balls, good accuracy", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Beached at trench", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Passer" + ], + "DriverRating": "Average", + "IntakeSpeed": "Average", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Insane accuracy on passing, slow movement" + }, + { + "ScoutId": "Jai", + "MatchKey": "qm67", + "Alliance": "red", + "DriverStation": 3, + "TeamNumber": 5338, + "StartingPosition": "Center", + "AutoScoringSide": [ + "Depot Side, near TRENCH" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Depot Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Through the TRENCH" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Got stuck on bump repeatedly", + "Disabled": "false", + "StabilityRating": "Very tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Very Poor", + "IntakeSpeed": "Very Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Could not shoot, could not intake, got stuck on ramp" + }, + { + "ScoutId": "Ryan", + "MatchKey": "qm67", + "Alliance": "blue", + "DriverStation": 1, + "TeamNumber": 4099, + "StartingPosition": "Human-Player Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near Driver Station", + "Centered, flush with HUB" + ], + "AutoTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "AutoClimb": "true", + "AutoNotes": "Outstanding auto, cleared full hopper", + "TeleopScoringSide": [ + "Depot Side, near Driver Station", + "Centered, near Driver Station", + "Centered, flush with HUB" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Through the TRENCH" + ], + "TeleopClimb": "L2", + "ClimbSpeed": "10-20 seconds", + "TeleopNotes": "Incredible scoring pace, back-to-back cycles", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper", + "Shooter, sprinter" + ], + "DriverRating": "Very Fluid", + "IntakeSpeed": "Very Fast", + "ThroughputSpeed": "Good", + "DefenseRating": "Very Good", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Very Good", + "RatingNotes": "Consistent elite performance" + }, + { + "ScoutId": "Jai", + "MatchKey": "qm67", + "Alliance": "blue", + "DriverStation": 2, + "TeamNumber": 1908, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Depot Side, near Driver Station", + "Centered, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Centered, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Consistent cycles, good accuracy", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Average", + "ThroughputSpeed": "Good", + "DefenseRating": "Poor", + "IntakeDefenseRating": "Okay", + "ShooterDefenseRating": "Good", + "RatingNotes": "Reliable mid-tier scorer" + }, + { + "ScoutId": "Jai", + "MatchKey": "qm67", + "Alliance": "blue", + "DriverStation": 3, + "TeamNumber": 449, + "StartingPosition": "Center", + "AutoScoringSide": [ + "Depot Side, near TRENCH" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "Stalled in neutral zone", + "TeleopScoringSide": [ + "Human Player Side, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Through the TRENCH" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Push passing bot", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Defense" + ], + "DriverRating": "Average", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Okay", + "DefenseRating": "Good", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Nikhil", + "MatchKey": "qm68", + "Alliance": "red", + "DriverStation": 1, + "TeamNumber": 4099, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Centered, flush with HUB", + "Centered, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Centered, near Driver Station", + "Depot Side, near Driver Station" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "TeleopClimb": "L1", + "ClimbSpeed": ">20 seconds", + "TeleopNotes": "Dominant throughput all match", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, sprinter", + "Shooter, dumper" + ], + "DriverRating": "Very Fluid", + "IntakeSpeed": "Very Fast", + "ThroughputSpeed": "Good", + "DefenseRating": "Very Good", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Best robot at the event" + }, + { + "ScoutId": "Nathan", + "MatchKey": "qm68", + "Alliance": "red", + "DriverStation": 2, + "TeamNumber": 9072, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Centered, flush with HUB", + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "true", + "AutoNotes": "", + "TeleopScoringSide": [ + "Depot Side, near Driver Station", + "Centered, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "TeleopClimb": "L1", + "ClimbSpeed": "5-10 seconds", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Passer" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Fast", + "ThroughputSpeed": "Good", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Solid passer/shooter, reliable cycles" + }, + { + "ScoutId": "Jai", + "MatchKey": "qm68", + "Alliance": "red", + "DriverStation": 3, + "TeamNumber": 5338, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near TRENCH" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Could not intake", + "Disabled": "true", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Very Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Ryan", + "MatchKey": "qm68", + "Alliance": "blue", + "DriverStation": 1, + "TeamNumber": 1731, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Centered, flush with HUB", + "Centered, near Driver Station" + ], + "AutoTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Depot Side, near Driver Station", + "Centered, near Driver Station", + "Centered, flush with HUB" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "TeleopClimb": "L1", + "ClimbSpeed": "<5 seconds", + "TeleopNotes": "High-speed continuous cycles", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, sprinter" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Very Fast", + "ThroughputSpeed": "Very Good", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Very Good", + "RatingNotes": "" + }, + { + "ScoutId": "Aaruj", + "MatchKey": "qm68", + "Alliance": "blue", + "DriverStation": 2, + "TeamNumber": 5549, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Centered, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH", + "Centered, flush with HUB" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Beached on bump", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Average", + "IntakeSpeed": "Average", + "ThroughputSpeed": "Good", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Nikhil", + "MatchKey": "qm68", + "Alliance": "blue", + "DriverStation": 3, + "TeamNumber": 2363, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Human Player Side, near TRENCH" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Depot Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "true", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Very Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Inaccurate, choppy driving" + }, + { + "ScoutId": "Aadhavan", + "MatchKey": "qm69", + "Alliance": "red", + "DriverStation": 1, + "TeamNumber": 1908, + "StartingPosition": "Center", + "AutoScoringSide": [ + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "Plow strat working", + "TeleopScoringSide": [ + "Human Player Side, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "TeleopClimb": "L1", + "ClimbSpeed": "5-10 seconds", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Fast", + "ThroughputSpeed": "Okay", + "DefenseRating": "Poor", + "IntakeDefenseRating": "Okay", + "ShooterDefenseRating": "Okay", + "RatingNotes": "" + }, + { + "ScoutId": "Jai", + "MatchKey": "qm69", + "Alliance": "red", + "DriverStation": 2, + "TeamNumber": 449, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "Didn't move", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Defense", + "Shover" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Average", + "ThroughputSpeed": "Okay", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Nikhil", + "MatchKey": "qm69", + "Alliance": "red", + "DriverStation": 3, + "TeamNumber": 5338, + "StartingPosition": "Center", + "AutoScoringSide": [ + "Human Player Side, near TRENCH" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "No auto movement", + "TeleopScoringSide": [ + "Depot Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Through the TRENCH" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Got stuck on bump repeatedly", + "Disabled": "false", + "StabilityRating": "Very tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Average", + "IntakeSpeed": "Very Slow", + "ThroughputSpeed": "Very Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Luke", + "MatchKey": "qm69", + "Alliance": "blue", + "DriverStation": 1, + "TeamNumber": 4099, + "StartingPosition": "Depot Side, under TRENCH", + "AutoScoringSide": [ + "Depot Side, near Driver Station", + "Centered, flush with HUB" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "true", + "AutoNotes": "Outstanding auto, cleared full hopper", + "TeleopScoringSide": [ + "Centered, near Driver Station", + "Depot Side, near Driver Station" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "TeleopClimb": "L3", + "ClimbSpeed": "5-10 seconds", + "TeleopNotes": "Dominant throughput all match", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, sprinter", + "Shooter, dumper" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Very Fast", + "ThroughputSpeed": "Very Good", + "DefenseRating": "Very Good", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Very Good", + "RatingNotes": "Best robot at the event" + }, + { + "ScoutId": "Jai", + "MatchKey": "qm69", + "Alliance": "blue", + "DriverStation": 2, + "TeamNumber": 4472, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Human Player Side, near Driver Station", + "Centered, near Driver Station" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH", + "Depot Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Passer" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Average", + "ThroughputSpeed": "Okay", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Nikhil", + "MatchKey": "qm69", + "Alliance": "blue", + "DriverStation": 3, + "TeamNumber": 2363, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Human Player Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Depot Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Missed many shots", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Inaccurate, choppy driving" + }, + { + "ScoutId": "Nikhil", + "MatchKey": "qm70", + "Alliance": "red", + "DriverStation": 1, + "TeamNumber": 9072, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Centered, near Driver Station", + "Depot Side, near Driver Station", + "Centered, flush with HUB" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "true", + "AutoNotes": "", + "TeleopScoringSide": [ + "Centered, near Driver Station", + "Depot Side, near Driver Station" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Through the TRENCH" + ], + "TeleopClimb": "L1", + "ClimbSpeed": "<5 seconds", + "TeleopNotes": "Strong plow strategy working", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Passer", + "Shooter, dumper" + ], + "DriverRating": "Very Fluid", + "IntakeSpeed": "Fast", + "ThroughputSpeed": "Good", + "DefenseRating": "Good", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Jai", + "MatchKey": "qm70", + "Alliance": "red", + "DriverStation": 2, + "TeamNumber": 1731, + "StartingPosition": "Human-Player Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near Driver Station", + "Centered, near Driver Station", + "Centered, flush with HUB" + ], + "AutoTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "Very fast passing, consistent outpost shots", + "TeleopScoringSide": [ + "Depot Side, near Driver Station", + "Centered, flush with HUB", + "Centered, near Driver Station" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "L2", + "ClimbSpeed": ">20 seconds", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, sprinter" + ], + "DriverRating": "Very Fluid", + "IntakeSpeed": "Very Fast", + "ThroughputSpeed": "Very Good", + "DefenseRating": "Good", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Nathan", + "MatchKey": "qm70", + "Alliance": "red", + "DriverStation": 3, + "TeamNumber": 449, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "Didn't move", + "TeleopScoringSide": [ + "Depot Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Heavy defense, targeted top scorers", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shover" + ], + "DriverRating": "Average", + "IntakeSpeed": "Average", + "ThroughputSpeed": "Okay", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Ryan", + "MatchKey": "qm70", + "Alliance": "blue", + "DriverStation": 1, + "TeamNumber": 4099, + "StartingPosition": "Human-Player Side, on BUMP", + "AutoScoringSide": [ + "Centered, near Driver Station", + "Depot Side, near Driver Station", + "Centered, flush with HUB" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "Precision auto routing", + "TeleopScoringSide": [ + "Centered, flush with HUB", + "Centered, near Driver Station", + "Depot Side, near Driver Station" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "TeleopClimb": "L3", + "ClimbSpeed": "5-10 seconds", + "TeleopNotes": "Dominant throughput all match", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, sprinter", + "Shooter, dumper" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Very Fast", + "ThroughputSpeed": "Very Good", + "DefenseRating": "Good", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Very Good", + "RatingNotes": "Best robot at the event" + }, + { + "ScoutId": "Luke", + "MatchKey": "qm70", + "Alliance": "blue", + "DriverStation": 2, + "TeamNumber": 5549, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near TRENCH" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "Shot preload only", + "TeleopScoringSide": [ + "Centered, near Driver Station" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Beached on bump", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Average", + "IntakeSpeed": "Average", + "ThroughputSpeed": "Okay", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Megan", + "MatchKey": "qm70", + "Alliance": "blue", + "DriverStation": 3, + "TeamNumber": 5338, + "StartingPosition": "Depot Side, under TRENCH", + "AutoScoringSide": [ + "Human Player Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Human Player Side, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Through the TRENCH" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Got stuck on bump repeatedly", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Jai", + "MatchKey": "qm71", + "Alliance": "red", + "DriverStation": 1, + "TeamNumber": 1908, + "StartingPosition": "Depot Side, under TRENCH", + "AutoScoringSide": [ + "Depot Side, near Driver Station", + "Centered, flush with HUB" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "Plow strat working", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH", + "Centered, flush with HUB" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Average", + "IntakeSpeed": "Fast", + "ThroughputSpeed": "Good", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Okay", + "RatingNotes": "" + }, + { + "ScoutId": "Nathan", + "MatchKey": "qm71", + "Alliance": "red", + "DriverStation": 2, + "TeamNumber": 4472, + "StartingPosition": "Center", + "AutoScoringSide": [ + "Centered, flush with HUB", + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Beached at trench", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Passer" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Nathan", + "MatchKey": "qm71", + "Alliance": "red", + "DriverStation": 3, + "TeamNumber": 2363, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near TRENCH" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Human Player Side, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Through the TRENCH" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Very Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Inaccurate, choppy driving" + }, + { + "ScoutId": "Aaruj", + "MatchKey": "qm71", + "Alliance": "blue", + "DriverStation": 1, + "TeamNumber": 9072, + "StartingPosition": "Center", + "AutoScoringSide": [ + "Centered, flush with HUB", + "Centered, near Driver Station", + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "true", + "AutoNotes": "Good passing cycles in auto", + "TeleopScoringSide": [ + "Centered, near Driver Station", + "Centered, flush with HUB" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "TeleopClimb": "L1", + "ClimbSpeed": "5-10 seconds", + "TeleopNotes": "Strong plow strategy working", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Average", + "ThroughputSpeed": "Good", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Okay", + "RatingNotes": "" + }, + { + "ScoutId": "Aaruj", + "MatchKey": "qm71", + "Alliance": "blue", + "DriverStation": 2, + "TeamNumber": 449, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "Stalled in neutral zone", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Through the TRENCH" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Push passing bot", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shover", + "Defense" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Okay", + "DefenseRating": "Good", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Luke", + "MatchKey": "qm71", + "Alliance": "blue", + "DriverStation": 3, + "TeamNumber": 5338, + "StartingPosition": "Depot Side, under TRENCH", + "AutoScoringSide": [ + "Human Player Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "No auto movement", + "TeleopScoringSide": [ + "Depot Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Broke almost immediately", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Very Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Could not shoot, could not intake, got stuck on ramp" + }, + { + "ScoutId": "Arnav", + "MatchKey": "qm72", + "Alliance": "red", + "DriverStation": 1, + "TeamNumber": 4099, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near Driver Station", + "Centered, flush with HUB", + "Centered, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "AutoClimb": "true", + "AutoNotes": "", + "TeleopScoringSide": [ + "Depot Side, near Driver Station", + "Centered, near Driver Station", + "Centered, flush with HUB" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Through the TRENCH" + ], + "TeleopClimb": "L3", + "ClimbSpeed": "5-10 seconds", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper", + "Shooter, sprinter" + ], + "DriverRating": "Very Fluid", + "IntakeSpeed": "Very Fast", + "ThroughputSpeed": "Good", + "DefenseRating": "Very Good", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Very Good", + "RatingNotes": "" + }, + { + "ScoutId": "Aadhavan", + "MatchKey": "qm72", + "Alliance": "red", + "DriverStation": 2, + "TeamNumber": 5338, + "StartingPosition": "Human-Player Side, on BUMP", + "AutoScoringSide": [ + "Human Player Side, near Driver Station" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "Missed every shot", + "TeleopScoringSide": [ + "Human Player Side, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "true", + "StabilityRating": "Very tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Very Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Could not shoot, could not intake, got stuck on ramp" + }, + { + "ScoutId": "Nikhil", + "MatchKey": "qm72", + "Alliance": "red", + "DriverStation": 3, + "TeamNumber": 2363, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near TRENCH" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Through the TRENCH" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Choppy driving, poor accuracy", + "Disabled": "true", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Very Poor", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Okay", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Inaccurate, choppy driving" + }, + { + "ScoutId": "Nathan", + "MatchKey": "qm72", + "Alliance": "blue", + "DriverStation": 1, + "TeamNumber": 1731, + "StartingPosition": "Human-Player Side, on BUMP", + "AutoScoringSide": [ + "Centered, near Driver Station", + "Depot Side, near Driver Station", + "Centered, flush with HUB" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "true", + "AutoNotes": "", + "TeleopScoringSide": [ + "Centered, flush with HUB", + "Centered, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "High-speed continuous cycles", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, sprinter" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Fast", + "ThroughputSpeed": "Good", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Very Good", + "RatingNotes": "Very fast passing robot, elite throughput" + }, + { + "ScoutId": "Arnav", + "MatchKey": "qm72", + "Alliance": "blue", + "DriverStation": 2, + "TeamNumber": 1908, + "StartingPosition": "Human-Player Side, on BUMP", + "AutoScoringSide": [ + "Centered, flush with HUB", + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Centered, flush with HUB", + "Depot Side, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Consistent cycles, good accuracy", + "Disabled": "true", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Fast", + "ThroughputSpeed": "Okay", + "DefenseRating": "Poor", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Okay", + "RatingNotes": "" + }, + { + "ScoutId": "Aaruj", + "MatchKey": "qm72", + "Alliance": "blue", + "DriverStation": 3, + "TeamNumber": 5549, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "Shot preload only", + "TeleopScoringSide": [ + "Centered, near Driver Station", + "Human Player Side, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "TeleopClimb": "L1", + "ClimbSpeed": "10-20 seconds", + "TeleopNotes": "Beached on bump", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Average", + "IntakeSpeed": "Average", + "ThroughputSpeed": "Good", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "All ratings based on auto" + }, + { + "ScoutId": "Aadhavan", + "MatchKey": "qm73", + "Alliance": "red", + "DriverStation": 1, + "TeamNumber": 9072, + "StartingPosition": "Center", + "AutoScoringSide": [ + "Centered, near Driver Station", + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "Good passing cycles in auto", + "TeleopScoringSide": [ + "Centered, near Driver Station", + "Depot Side, near Driver Station", + "Centered, flush with HUB" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "TeleopClimb": "L1", + "ClimbSpeed": "10-20 seconds", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Fast", + "ThroughputSpeed": "Very Good", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Solid passer/shooter, reliable cycles" + }, + { + "ScoutId": "Arnav", + "MatchKey": "qm73", + "Alliance": "red", + "DriverStation": 2, + "TeamNumber": 4472, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Centered, flush with HUB", + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Beached at trench", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Passer" + ], + "DriverRating": "Average", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Arnav", + "MatchKey": "qm73", + "Alliance": "red", + "DriverStation": 3, + "TeamNumber": 5338, + "StartingPosition": "Depot Side, under TRENCH", + "AutoScoringSide": [ + "Human Player Side, near TRENCH" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "Missed every shot", + "TeleopScoringSide": [ + "Depot Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Could not intake", + "Disabled": "true", + "StabilityRating": "Very tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Very Slow", + "ThroughputSpeed": "Very Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Arnav", + "MatchKey": "qm73", + "Alliance": "blue", + "DriverStation": 1, + "TeamNumber": 4099, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near Driver Station", + "Centered, flush with HUB" + ], + "AutoTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "AutoClimb": "true", + "AutoNotes": "Precision auto routing", + "TeleopScoringSide": [ + "Centered, flush with HUB", + "Centered, near Driver Station", + "Depot Side, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "L3", + "ClimbSpeed": "5-10 seconds", + "TeleopNotes": "Dominant throughput all match", + "Disabled": "true", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, sprinter", + "Shooter, dumper" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Very Fast", + "ThroughputSpeed": "Very Good", + "DefenseRating": "Good", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Best robot at the event" + }, + { + "ScoutId": "Nikhil", + "MatchKey": "qm73", + "Alliance": "blue", + "DriverStation": 2, + "TeamNumber": 449, + "StartingPosition": "Center", + "AutoScoringSide": [ + "Human Player Side, near Driver Station" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "Stalled in neutral zone", + "TeleopScoringSide": [ + "Depot Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Through the TRENCH" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Defense" + ], + "DriverRating": "Average", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Okay", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Very fluid defense, low scoring contribution" + }, + { + "ScoutId": "Jai", + "MatchKey": "qm73", + "Alliance": "blue", + "DriverStation": 3, + "TeamNumber": 2363, + "StartingPosition": "Human-Player Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near TRENCH" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Human Player Side, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Average", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Okay", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Inaccurate, choppy driving" + }, + { + "ScoutId": "Nikhil", + "MatchKey": "qm74", + "Alliance": "red", + "DriverStation": 1, + "TeamNumber": 1731, + "StartingPosition": "Center", + "AutoScoringSide": [ + "Centered, flush with HUB", + "Centered, near Driver Station", + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "Very fast passing, consistent outpost shots", + "TeleopScoringSide": [ + "Centered, near Driver Station", + "Depot Side, near Driver Station", + "Centered, flush with HUB" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, sprinter" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Fast", + "ThroughputSpeed": "Very Good", + "DefenseRating": "Good", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Very Good", + "RatingNotes": "" + }, + { + "ScoutId": "Megan", + "MatchKey": "qm74", + "Alliance": "red", + "DriverStation": 2, + "TeamNumber": 5549, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Centered, near Driver Station", + "Human Player Side, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Stopped moving briefly", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Average", + "IntakeSpeed": "Average", + "ThroughputSpeed": "Good", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Megan", + "MatchKey": "qm74", + "Alliance": "red", + "DriverStation": 3, + "TeamNumber": 5338, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Human Player Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "Missed every shot", + "TeleopScoringSide": [ + "Human Player Side, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "true", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Very Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Ryan", + "MatchKey": "qm74", + "Alliance": "blue", + "DriverStation": 1, + "TeamNumber": 1908, + "StartingPosition": "Human-Player Side, on BUMP", + "AutoScoringSide": [ + "Centered, flush with HUB" + ], + "AutoTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "Plow strat working", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH", + "Centered, flush with HUB" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "TeleopClimb": "L1", + "ClimbSpeed": "5-10 seconds", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Average", + "ThroughputSpeed": "Okay", + "DefenseRating": "Poor", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Jai", + "MatchKey": "qm74", + "Alliance": "blue", + "DriverStation": 2, + "TeamNumber": 9072, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Centered, near Driver Station", + "Depot Side, near Driver Station", + "Centered, flush with HUB" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Centered, near Driver Station", + "Depot Side, near Driver Station", + "Centered, flush with HUB" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Strong plow strategy working", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Passer", + "Shooter, dumper" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Very Fast", + "ThroughputSpeed": "Good", + "DefenseRating": "Good", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Solid passer/shooter, reliable cycles" + }, + { + "ScoutId": "Megan", + "MatchKey": "qm74", + "Alliance": "blue", + "DriverStation": 3, + "TeamNumber": 449, + "StartingPosition": "Human-Player Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near Driver Station", + "Depot Side, near TRENCH" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "Stalled in neutral zone", + "TeleopScoringSide": [ + "Human Player Side, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Push passing bot", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shover" + ], + "DriverRating": "Average", + "IntakeSpeed": "Very Slow", + "ThroughputSpeed": "Okay", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Very fluid defense, low scoring contribution" + }, + { + "ScoutId": "Ryan", + "MatchKey": "qm75", + "Alliance": "red", + "DriverStation": 1, + "TeamNumber": 4099, + "StartingPosition": "Human-Player Side, on BUMP", + "AutoScoringSide": [ + "Centered, flush with HUB", + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "Precision auto routing", + "TeleopScoringSide": [ + "Depot Side, near Driver Station", + "Centered, near Driver Station" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "TeleopClimb": "L3", + "ClimbSpeed": "5-10 seconds", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper", + "Shooter, sprinter" + ], + "DriverRating": "Very Fluid", + "IntakeSpeed": "Very Fast", + "ThroughputSpeed": "Good", + "DefenseRating": "Very Good", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Very Good", + "RatingNotes": "Consistent elite performance" + }, + { + "ScoutId": "Arnav", + "MatchKey": "qm75", + "Alliance": "red", + "DriverStation": 2, + "TeamNumber": 2363, + "StartingPosition": "Center", + "AutoScoringSide": [ + "Human Player Side, near TRENCH" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Depot Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Choppy driving, poor accuracy", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Very Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Luke", + "MatchKey": "qm75", + "Alliance": "red", + "DriverStation": 3, + "TeamNumber": 449, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near TRENCH", + "Human Player Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "Stalled in neutral zone", + "TeleopScoringSide": [ + "Human Player Side, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Push passing bot", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Defense" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Very Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Good", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Arnav", + "MatchKey": "qm75", + "Alliance": "blue", + "DriverStation": 1, + "TeamNumber": 1731, + "StartingPosition": "Human-Player Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near Driver Station", + "Centered, near Driver Station", + "Centered, flush with HUB" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "true", + "AutoNotes": "", + "TeleopScoringSide": [ + "Depot Side, near Driver Station", + "Centered, near Driver Station", + "Centered, flush with HUB" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Through the TRENCH" + ], + "TeleopClimb": "L2", + "ClimbSpeed": "<5 seconds", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, sprinter" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Fast", + "ThroughputSpeed": "Very Good", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Very Good", + "RatingNotes": "Very fast passing robot, elite throughput" + }, + { + "ScoutId": "Arnav", + "MatchKey": "qm75", + "Alliance": "blue", + "DriverStation": 2, + "TeamNumber": 4472, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Centered, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Human Player Side, near Driver Station", + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Passer" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Aaruj", + "MatchKey": "qm75", + "Alliance": "blue", + "DriverStation": 3, + "TeamNumber": 5338, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Human Player Side, near Driver Station" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "Missed every shot", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "true", + "StabilityRating": "Very tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Arnav", + "MatchKey": "qm76", + "Alliance": "red", + "DriverStation": 1, + "TeamNumber": 9072, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Centered, flush with HUB", + "Depot Side, near Driver Station", + "Centered, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Depot Side, near Driver Station", + "Centered, flush with HUB" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Through the TRENCH" + ], + "TeleopClimb": "L1", + "ClimbSpeed": ">20 seconds", + "TeleopNotes": "Strong plow strategy working", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Passer" + ], + "DriverRating": "Very Fluid", + "IntakeSpeed": "Average", + "ThroughputSpeed": "Very Good", + "DefenseRating": "Good", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Ryan", + "MatchKey": "qm76", + "Alliance": "red", + "DriverStation": 2, + "TeamNumber": 5549, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Centered, flush with HUB", + "Depot Side, near TRENCH" + ], + "AutoTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "Shot preload only", + "TeleopScoringSide": [ + "Centered, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "TeleopClimb": "L1", + "ClimbSpeed": "5-10 seconds", + "TeleopNotes": "Stopped moving briefly", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Average", + "IntakeSpeed": "Fast", + "ThroughputSpeed": "Good", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Jai", + "MatchKey": "qm76", + "Alliance": "red", + "DriverStation": 3, + "TeamNumber": 5338, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Depot Side, near TRENCH" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Got stuck on bump repeatedly", + "Disabled": "true", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Very Slow", + "ThroughputSpeed": "Very Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Ryan", + "MatchKey": "qm76", + "Alliance": "blue", + "DriverStation": 1, + "TeamNumber": 4099, + "StartingPosition": "Human-Player Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near Driver Station", + "Centered, near Driver Station", + "Centered, flush with HUB" + ], + "AutoTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "AutoClimb": "true", + "AutoNotes": "Outstanding auto, cleared full hopper", + "TeleopScoringSide": [ + "Depot Side, near Driver Station", + "Centered, flush with HUB" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "TeleopClimb": "L2", + "ClimbSpeed": "<5 seconds", + "TeleopNotes": "Incredible scoring pace, back-to-back cycles", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, sprinter", + "Shooter, dumper" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Very Fast", + "ThroughputSpeed": "Very Good", + "DefenseRating": "Very Good", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Very Good", + "RatingNotes": "Best robot at the event" + }, + { + "ScoutId": "Nathan", + "MatchKey": "qm76", + "Alliance": "blue", + "DriverStation": 2, + "TeamNumber": 1908, + "StartingPosition": "Center", + "AutoScoringSide": [ + "Centered, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "Plow strat working", + "TeleopScoringSide": [ + "Human Player Side, near Driver Station", + "Depot Side, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "L1", + "ClimbSpeed": "<5 seconds", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Average", + "ThroughputSpeed": "Okay", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Reliable mid-tier scorer" + }, + { + "ScoutId": "Jai", + "MatchKey": "qm76", + "Alliance": "blue", + "DriverStation": 3, + "TeamNumber": 2363, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near TRENCH" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "Shot preload, missed many", + "TeleopScoringSide": [ + "Depot Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Average", + "IntakeSpeed": "Very Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Nathan", + "MatchKey": "qm77", + "Alliance": "red", + "DriverStation": 1, + "TeamNumber": 1731, + "StartingPosition": "Human-Player Side, on BUMP", + "AutoScoringSide": [ + "Centered, flush with HUB", + "Centered, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Depot Side, near Driver Station", + "Centered, flush with HUB", + "Centered, near Driver Station" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "TeleopClimb": "L2", + "ClimbSpeed": "<5 seconds", + "TeleopNotes": "High-speed continuous cycles", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, sprinter" + ], + "DriverRating": "Very Fluid", + "IntakeSpeed": "Very Fast", + "ThroughputSpeed": "Good", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Very fast passing robot, elite throughput" + }, + { + "ScoutId": "Aaruj", + "MatchKey": "qm77", + "Alliance": "red", + "DriverStation": 2, + "TeamNumber": 449, + "StartingPosition": "Center", + "AutoScoringSide": [ + "Depot Side, near TRENCH" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Human Player Side, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Heavy defense, targeted top scorers", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Defense" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Average", + "ThroughputSpeed": "Okay", + "DefenseRating": "Very Good", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Nikhil", + "MatchKey": "qm77", + "Alliance": "red", + "DriverStation": 3, + "TeamNumber": 2363, + "StartingPosition": "Center", + "AutoScoringSide": [ + "Depot Side, near TRENCH" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "Shot preload, missed many", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Inaccurate, choppy driving" + }, + { + "ScoutId": "Jai", + "MatchKey": "qm77", + "Alliance": "blue", + "DriverStation": 1, + "TeamNumber": 9072, + "StartingPosition": "Human-Player Side, on BUMP", + "AutoScoringSide": [ + "Centered, flush with HUB", + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "true", + "AutoNotes": "Good passing cycles in auto", + "TeleopScoringSide": [ + "Depot Side, near Driver Station", + "Centered, flush with HUB" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "TeleopClimb": "L1", + "ClimbSpeed": "10-20 seconds", + "TeleopNotes": "Strong plow strategy working", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Passer" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Very Fast", + "ThroughputSpeed": "Very Good", + "DefenseRating": "Good", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Solid passer/shooter, reliable cycles" + }, + { + "ScoutId": "Luke", + "MatchKey": "qm77", + "Alliance": "blue", + "DriverStation": 2, + "TeamNumber": 4472, + "StartingPosition": "Center", + "AutoScoringSide": [ + "Depot Side, near TRENCH", + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "Unload preloaded balls, good accuracy", + "TeleopScoringSide": [ + "Centered, flush with HUB" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Through the TRENCH" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Passer" + ], + "DriverRating": "Average", + "IntakeSpeed": "Average", + "ThroughputSpeed": "Okay", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Megan", + "MatchKey": "qm77", + "Alliance": "blue", + "DriverStation": 3, + "TeamNumber": 5338, + "StartingPosition": "Depot Side, under TRENCH", + "AutoScoringSide": [ + "Human Player Side, near TRENCH" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "Missed every shot", + "TeleopScoringSide": [ + "Human Player Side, near Driver Station" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Got stuck on bump repeatedly", + "Disabled": "true", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Very Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Could not shoot, could not intake, got stuck on ramp" + }, + { + "ScoutId": "Nikhil", + "MatchKey": "qm78", + "Alliance": "red", + "DriverStation": 1, + "TeamNumber": 4099, + "StartingPosition": "Depot Side, under TRENCH", + "AutoScoringSide": [ + "Centered, near Driver Station", + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "true", + "AutoNotes": "", + "TeleopScoringSide": [ + "Centered, near Driver Station", + "Depot Side, near Driver Station", + "Centered, flush with HUB" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "TeleopClimb": "L1", + "ClimbSpeed": "10-20 seconds", + "TeleopNotes": "Dominant throughput all match", + "Disabled": "true", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Very Fast", + "ThroughputSpeed": "Good", + "DefenseRating": "Very Good", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Very Good", + "RatingNotes": "Consistent elite performance" + }, + { + "ScoutId": "Ryan", + "MatchKey": "qm78", + "Alliance": "red", + "DriverStation": 2, + "TeamNumber": 1908, + "StartingPosition": "Center", + "AutoScoringSide": [ + "Centered, flush with HUB", + "Depot Side, near TRENCH" + ], + "AutoTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "Plow strat working", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "L1", + "ClimbSpeed": "5-10 seconds", + "TeleopNotes": "Consistent cycles, good accuracy", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Average", + "IntakeSpeed": "Fast", + "ThroughputSpeed": "Okay", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Okay", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Megan", + "MatchKey": "qm78", + "Alliance": "red", + "DriverStation": 3, + "TeamNumber": 5338, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near TRENCH" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "Missed every shot", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Got stuck on bump repeatedly", + "Disabled": "true", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Average", + "IntakeSpeed": "Very Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Could not shoot, could not intake, got stuck on ramp" + }, + { + "ScoutId": "Jai", + "MatchKey": "qm78", + "Alliance": "blue", + "DriverStation": 1, + "TeamNumber": 1731, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Centered, near Driver Station", + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "true", + "AutoNotes": "Very fast passing, consistent outpost shots", + "TeleopScoringSide": [ + "Centered, near Driver Station", + "Centered, flush with HUB" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "TeleopClimb": "L1", + "ClimbSpeed": "<5 seconds", + "TeleopNotes": "High-speed continuous cycles", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, sprinter" + ], + "DriverRating": "Very Fluid", + "IntakeSpeed": "Very Fast", + "ThroughputSpeed": "Good", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Very Good", + "RatingNotes": "Very fast passing robot, elite throughput" + }, + { + "ScoutId": "Nikhil", + "MatchKey": "qm78", + "Alliance": "blue", + "DriverStation": 2, + "TeamNumber": 5549, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Depot Side, near Driver Station", + "Centered, near Driver Station" + ], + "AutoTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "L1", + "ClimbSpeed": "5-10 seconds", + "TeleopNotes": "Stopped moving briefly", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Average", + "ThroughputSpeed": "Good", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "All ratings based on auto" + }, + { + "ScoutId": "Jai", + "MatchKey": "qm78", + "Alliance": "blue", + "DriverStation": 3, + "TeamNumber": 449, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near Driver Station", + "Centered, flush with HUB" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Depot Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Defense" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Okay", + "DefenseRating": "Very Good", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Very fluid defense, low scoring contribution" + }, + { + "ScoutId": "Aaruj", + "MatchKey": "qm79", + "Alliance": "red", + "DriverStation": 1, + "TeamNumber": 9072, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Centered, near Driver Station", + "Centered, flush with HUB" + ], + "AutoTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "AutoClimb": "true", + "AutoNotes": "Good passing cycles in auto", + "TeleopScoringSide": [ + "Centered, flush with HUB", + "Centered, near Driver Station", + "Depot Side, near Driver Station" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Over the BUMP", + "Through the TRENCH" + ], + "TeleopClimb": "L1", + "ClimbSpeed": "5-10 seconds", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Passer", + "Shooter, dumper" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Average", + "ThroughputSpeed": "Good", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Very Good", + "ShooterDefenseRating": "Okay", + "RatingNotes": "Solid passer/shooter, reliable cycles" + }, + { + "ScoutId": "Luke", + "MatchKey": "qm79", + "Alliance": "red", + "DriverStation": 2, + "TeamNumber": 2363, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Human Player Side, near Driver Station" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Over the BUMP" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Average", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Inaccurate, choppy driving" + }, + { + "ScoutId": "Nathan", + "MatchKey": "qm79", + "Alliance": "red", + "DriverStation": 3, + "TeamNumber": 4472, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Human Player Side, near TRENCH" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Depot Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "Beached at trench", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Passer" + ], + "DriverRating": "Average", + "IntakeSpeed": "Slow", + "ThroughputSpeed": "Okay", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Nathan", + "MatchKey": "qm79", + "Alliance": "blue", + "DriverStation": 1, + "TeamNumber": 4099, + "StartingPosition": "Human-Player Side, on BUMP", + "AutoScoringSide": [ + "Centered, flush with HUB", + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Over the BUMP" + ], + "AutoClimb": "true", + "AutoNotes": "", + "TeleopScoringSide": [ + "Depot Side, near Driver Station", + "Centered, near Driver Station", + "Centered, flush with HUB" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Through the TRENCH", + "Over the BUMP" + ], + "TeleopClimb": "L2", + "ClimbSpeed": "5-10 seconds", + "TeleopNotes": "Dominant throughput all match", + "Disabled": "false", + "StabilityRating": "Moderately tippy", + "RobotStyleType": [ + "Shooter, dumper", + "Shooter, sprinter" + ], + "DriverRating": "Fluid", + "IntakeSpeed": "Very Fast", + "ThroughputSpeed": "Good", + "DefenseRating": "Good", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "Best robot at the event" + }, + { + "ScoutId": "Arnav", + "MatchKey": "qm79", + "Alliance": "blue", + "DriverStation": 2, + "TeamNumber": 1908, + "StartingPosition": "Depot Side, on BUMP", + "AutoScoringSide": [ + "Depot Side, near Driver Station" + ], + "AutoTrenchBump": [ + "Through the TRENCH" + ], + "AutoClimb": "false", + "AutoNotes": "", + "TeleopScoringSide": [ + "Centered, near Driver Station" + ], + "ShootOnTheMove": "true", + "TeleopTrenchBump": [ + "Through the TRENCH" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Stable", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Average", + "IntakeSpeed": "Fast", + "ThroughputSpeed": "Okay", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Okay", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + }, + { + "ScoutId": "Nikhil", + "MatchKey": "qm79", + "Alliance": "blue", + "DriverStation": 3, + "TeamNumber": 5338, + "StartingPosition": "Human-Player Side on TRENCH", + "AutoScoringSide": [ + "Human Player Side, near Driver Station" + ], + "AutoTrenchBump": [], + "AutoClimb": "false", + "AutoNotes": "No auto movement", + "TeleopScoringSide": [ + "Human Player Side, near TRENCH" + ], + "ShootOnTheMove": "false", + "TeleopTrenchBump": [ + "Through the TRENCH" + ], + "TeleopClimb": "No climb", + "ClimbSpeed": "", + "TeleopNotes": "", + "Disabled": "false", + "StabilityRating": "Very tippy", + "RobotStyleType": [ + "Shooter, dumper" + ], + "DriverRating": "Poor", + "IntakeSpeed": "Very Slow", + "ThroughputSpeed": "Poor", + "DefenseRating": "Okay", + "IntakeDefenseRating": "Good", + "ShooterDefenseRating": "Good", + "RatingNotes": "" + } +] diff --git a/src/data/statbotics_2026vache.json b/src/data/statbotics_2026vache.json new file mode 100644 index 0000000..cd90268 --- /dev/null +++ b/src/data/statbotics_2026vache.json @@ -0,0 +1,215 @@ +{ + "last_updated": "2026-03-18T13:45:13.954477", + "teams": { + "122": { + "total_epa": 25.41, + "total_epa_sd": 9.66, + "auto_fuel": 5.57, + "teleop_endgame_fuel": 19.54, + "tower_points": 0.61 + }, + "404": { + "total_epa": 33.66, + "total_epa_sd": 8.19, + "auto_fuel": 4.79, + "teleop_endgame_fuel": 28.55, + "tower_points": 0.31 + }, + "422": { + "total_epa": 101.24, + "total_epa_sd": 5.59, + "auto_fuel": 21.3, + "teleop_endgame_fuel": 77.75, + "tower_points": 1.4 + }, + "526": { + "total_epa": 32.94, + "total_epa_sd": 13.59, + "auto_fuel": 7.72, + "teleop_endgame_fuel": 26.19, + "tower_points": -0.58 + }, + "539": { + "total_epa": 33.9, + "total_epa_sd": 9.85, + "auto_fuel": 8.07, + "teleop_endgame_fuel": 18.92, + "tower_points": 5.75 + }, + "611": { + "total_epa": 33.46, + "total_epa_sd": 14.73, + "auto_fuel": 7.64, + "teleop_endgame_fuel": 24.39, + "tower_points": 0.93 + }, + "612": { + "total_epa": 30.57, + "total_epa_sd": 6.36, + "auto_fuel": 7.06, + "teleop_endgame_fuel": 23.23, + "tower_points": 0.92 + }, + "620": { + "total_epa": 34.76, + "total_epa_sd": 11.64, + "auto_fuel": 6.45, + "teleop_endgame_fuel": 26.84, + "tower_points": 0.9 + }, + "1086": { + "total_epa": 59.01, + "total_epa_sd": 21.56, + "auto_fuel": 10.01, + "teleop_endgame_fuel": 49.06, + "tower_points": -0.09 + }, + "1599": { + "total_epa": 42.43, + "total_epa_sd": 10.5, + "auto_fuel": 5.15, + "teleop_endgame_fuel": 37.57, + "tower_points": -0.33 + }, + "1793": { + "total_epa": 9.37, + "total_epa_sd": 13.47, + "auto_fuel": 1.4, + "teleop_endgame_fuel": 8.33, + "tower_points": -0.02 + }, + "1895": { + "total_epa": 35.39, + "total_epa_sd": 7.97, + "auto_fuel": 9.58, + "teleop_endgame_fuel": 25.62, + "tower_points": 0.41 + }, + "1908": { + "total_epa": 89.86, + "total_epa_sd": 15.52, + "auto_fuel": 16.49, + "teleop_endgame_fuel": 73.43, + "tower_points": 0.12 + }, + "2028": { + "total_epa": 29.7, + "total_epa_sd": 12.72, + "auto_fuel": 3.72, + "teleop_endgame_fuel": 26.32, + "tower_points": -0.45 + }, + "2068": { + "total_epa": 27.14, + "total_epa_sd": 12.59, + "auto_fuel": 9.11, + "teleop_endgame_fuel": 17.56, + "tower_points": 0.01 + }, + "2363": { + "total_epa": 42.89, + "total_epa_sd": 10.53, + "auto_fuel": 7.06, + "teleop_endgame_fuel": 34.59, + "tower_points": 0.11 + }, + "2890": { + "total_epa": 30.97, + "total_epa_sd": 12.54, + "auto_fuel": 0.54, + "teleop_endgame_fuel": 16.75, + "tower_points": 6.27 + }, + "2998": { + "total_epa": 12.3, + "total_epa_sd": 10.42, + "auto_fuel": 5.86, + "teleop_endgame_fuel": 6.4, + "tower_points": 0.1 + }, + "3359": { + "total_epa": 15.25, + "total_epa_sd": 16.02, + "auto_fuel": 2.7, + "teleop_endgame_fuel": 11.56, + "tower_points": 0.47 + }, + "4099": { + "total_epa": 82.48, + "total_epa_sd": 11.98, + "auto_fuel": 25.93, + "teleop_endgame_fuel": 53.91, + "tower_points": 2.91 + }, + "4286": { + "total_epa": 8.66, + "total_epa_sd": 8.06, + "auto_fuel": 2.31, + "teleop_endgame_fuel": 6.13, + "tower_points": 0.24 + }, + "6326": { + "total_epa": 24.84, + "total_epa_sd": 11.68, + "auto_fuel": 6.1, + "teleop_endgame_fuel": 18.73, + "tower_points": 0.0 + }, + "8230": { + "total_epa": 30.74, + "total_epa_sd": 10.92, + "auto_fuel": 4.49, + "teleop_endgame_fuel": 25.27, + "tower_points": 0.64 + }, + "9003": { + "total_epa": 40.29, + "total_epa_sd": 5.59, + "auto_fuel": 8.48, + "teleop_endgame_fuel": 30.94, + "tower_points": 0.56 + }, + "9214": { + "total_epa": 13.01, + "total_epa_sd": 9.15, + "auto_fuel": 4.81, + "teleop_endgame_fuel": 7.95, + "tower_points": 0.26 + }, + "9403": { + "total_epa": 21.34, + "total_epa_sd": 7.09, + "auto_fuel": 6.03, + "teleop_endgame_fuel": 14.56, + "tower_points": 0.36 + }, + "10224": { + "total_epa": 32.07, + "total_epa_sd": 7.54, + "auto_fuel": 8.12, + "teleop_endgame_fuel": 24.14, + "tower_points": -0.17 + }, + "11252": { + "total_epa": 23.74, + "total_epa_sd": 5.59, + "auto_fuel": 5.0, + "teleop_endgame_fuel": 18.24, + "tower_points": 0.33 + }, + "11415": { + "total_epa": 23.74, + "total_epa_sd": 5.59, + "auto_fuel": 5.0, + "teleop_endgame_fuel": 18.24, + "tower_points": 0.33 + }, + "11425": { + "total_epa": 27.97, + "total_epa_sd": 22.05, + "auto_fuel": 9.58, + "teleop_endgame_fuel": 18.2, + "tower_points": 0.2 + } + } +} \ No newline at end of file diff --git a/src/page_managers/event_manager.py b/src/page_managers/event_manager.py index c895b68..c071736 100644 --- a/src/page_managers/event_manager.py +++ b/src/page_managers/event_manager.py @@ -2,7 +2,7 @@ import streamlit as st from numpy import mean -from pandas import Series, to_numeric +from pandas import Series from .page_manager import PageManager from utils import ( @@ -21,7 +21,7 @@ class EventManager(PageManager): """The page manager for the `Event` page.""" - TEAMS_TO_SPLIT_BY = 10 # Number of teams to split the plots by. + TEAMS_TO_SPLIT_BY = 10 def __init__(self): self.calculated_stats = CalculatedStats( @@ -29,222 +29,186 @@ def __init__(self): ) @st.cache_data(ttl=GeneralConstants.SECONDS_TO_CACHE) - def _retrieve_fuel_distributions(_self, mode: str) -> list: - """Retrieves fuel distributions across an event for autonomous/teleop. - - :param mode: The mode to retrieve fuel data for (autonomous/teleop). - :return: A list containing the fuel distributions for each team. - """ + def _retrieve_driver_rating_distributions(_self) -> list: + """Retrieves numeric driver rating distributions across all teams at the event.""" + from utils.constants import Criteria teams = retrieve_team_list() - fuel_distributions = [] - + distributions = [] for team in teams: team_data = _self.calculated_stats.data[ _self.calculated_stats.data[Queries.TEAM_NUMBER] == team ] - if team_data.empty: - fuel_distributions.append(Series(dtype=float)) + distributions.append(Series(dtype=float)) continue - - magazine_size = ( - to_numeric(team_data[Queries.MAGAZINE_SIZE], errors="coerce").fillna(0) - if Queries.MAGAZINE_SIZE in team_data - else Series(0, index=team_data.index) + distributions.append( + team_data[Queries.DRIVER_RATING].apply( + lambda v: Criteria.DRIVER_RATING_CRITERIA.get(v, float("nan")) + ).dropna() ) - - if mode == Queries.AUTO: - singular = to_numeric(team_data[Queries.AUTO_SINGULAR_COUNT], errors="coerce").fillna(0) - batch = to_numeric(team_data[Queries.AUTO_BATCH_COUNT], errors="coerce").fillna(0) - else: - singular = to_numeric(team_data[Queries.TELEOP_SINGULAR_COUNT], errors="coerce").fillna(0) - batch = to_numeric(team_data[Queries.TELEOP_BATCH_COUNT], errors="coerce").fillna(0) - - fuel_distributions.append(singular + (batch * magazine_size)) - - return fuel_distributions + return distributions @st.cache_data(ttl=GeneralConstants.SECONDS_TO_CACHE) - def _retrieve_point_distributions(_self, mode: str) -> list: - """Retrieves point distributions across an event for autonomous/teleop. - - :param mode: The mode to retrieve point contribution data for (autonomous/teleop). - :return: A list containing the point distributions for each team. - """ + def _retrieve_throughput_distributions(_self) -> list: + """Retrieves numeric throughput speed distributions across all teams at the event.""" + from utils.constants import Criteria teams = retrieve_team_list() - return [ - _self.calculated_stats.points_contributed_by_match(team, mode) - for team in teams - ] + distributions = [] + for team in teams: + team_data = _self.calculated_stats.data[ + _self.calculated_stats.data[Queries.TEAM_NUMBER] == team + ] + if team_data.empty: + distributions.append(Series(dtype=float)) + continue + distributions.append( + team_data[Queries.THROUGHPUT_SPEED].apply( + lambda v: Criteria.BASIC_RATING_CRITERIA.get(v, float("nan")) + ).dropna() + ) + return distributions def generate_input_section(self) -> None: - """Defines that there are no inputs for the event page, showing event-wide graphs.""" + """Defines that there are no inputs for the event page.""" return def generate_event_breakdown(self) -> None: - """Creates metrics that breakdown the event and display average points contributed of the top 8, 16 and 24 teams.""" + """Creates metrics showing average driver rating and throughput of the top 8, 16 and 24 teams.""" top_8_col, top_16_col, top_24_col = st.columns(3) - average_points_per_team = sorted( - [ - self.calculated_stats.average_points_contributed(team) - for team in retrieve_team_list() - ], + teams = retrieve_team_list() + + avg_driver_per_team = sorted( + [self.calculated_stats.average_driver_rating(team) for team in teams], reverse=True ) - # Metric displaying average points of the top 8 teams/likely alliance captains with top_8_col: colored_metric( - "Avg. Points (Top 8)", - round(mean(average_points_per_team[:8]), 2), + "Avg. Driver Rating (Top 8)", + round(mean(avg_driver_per_team[:8]), 2), background_color=GeneralConstants.PRIMARY_COLOR, opacity=0.5 ) - # Metric displaying average points of the top 16 teams/likely alliance captains with top_16_col: colored_metric( - "Avg. Points (Top 16)", - round(mean(average_points_per_team[:16]), 2), + "Avg. Driver Rating (Top 16)", + round(mean(avg_driver_per_team[:16]), 2), background_color=GeneralConstants.PRIMARY_COLOR, opacity=0.4, border_opacity=0.75, ) - # Metric displaying average points of the top 24 teams/likely alliance captains with top_24_col: colored_metric( - "Avg. Points (Top 24)", - round(mean(average_points_per_team[:24]), 2), + "Avg. Driver Rating (Top 24)", + round(mean(avg_driver_per_team[:24]), 2), background_color=GeneralConstants.PRIMARY_COLOR, opacity=0.3, border_opacity=0.5, ) def generate_event_graphs(self, type_of_graph: str) -> None: - """Create event-wide graphs. + """Create event-wide box-plot distributions for driver rating and throughput speed. - :param type_of_graph: The type of graphs to display (fuel scored/point contribution). + :param type_of_graph: Unused; kept for API compatibility. """ - display_fuel_scored = type_of_graph == GraphType.FUEL_CONTRIBUTIONS teams = retrieve_team_list() - auto_fuel_col, teleop_fuel_col = st.columns(2, gap="large") + driver_col, throughput_col = st.columns(2, gap="large") - # Display event-wide graph surrounding each team and their fuel distributions in the Autonomous period. - with auto_fuel_col: - variable_key = f"auto_fuel_col_{type_of_graph}" + with driver_col: + variable_key = "driver_rating_dist" - auto_distributions = ( - self._retrieve_fuel_distributions(Queries.AUTO) - if display_fuel_scored - else self._retrieve_point_distributions(Queries.AUTO) - ) - auto_sorted_distributions = dict( + driver_distributions = self._retrieve_driver_rating_distributions() + sorted_dist = dict( sorted( - zip(teams, auto_distributions), - key=lambda pair: (pair[1].median(), pair[1].mean()), + zip(teams, driver_distributions), + key=lambda pair: (pair[1].median() if len(pair[1]) else 0, + pair[1].mean() if len(pair[1]) else 0), reverse=True ) ) - auto_sorted_teams = list(auto_sorted_distributions.keys()) - auto_distributions = list(auto_sorted_distributions.values()) + sorted_teams = list(sorted_dist.keys()) + sorted_driver = list(sorted_dist.values()) - if not st.session_state.get(variable_key): + if variable_key not in st.session_state: st.session_state[variable_key] = 0 + offset = st.session_state[variable_key] plotly_chart( box_plot( - auto_sorted_teams[ - st.session_state[variable_key]:st.session_state[variable_key] + self.TEAMS_TO_SPLIT_BY - ], - auto_distributions[ - st.session_state[variable_key]:st.session_state[variable_key] + self.TEAMS_TO_SPLIT_BY - ], + sorted_teams[offset:offset + self.TEAMS_TO_SPLIT_BY], + sorted_driver[offset:offset + self.TEAMS_TO_SPLIT_BY], x_axis_label="Teams", - y_axis_label="Fuel Distribution" if display_fuel_scored else "Point Distribution", - title="Fuel Scored in Auto" if display_fuel_scored else "Point Contributions in Auto" - ).update_layout( - showlegend=False - ) + y_axis_label="Driver Rating (1–5)", + title="Driver Rating Distribution by Team" + ).update_layout(showlegend=False) ) - previous_col, next_col = st.columns(2) - - if previous_col.button( - f"Previous {self.TEAMS_TO_SPLIT_BY} Teams", - use_container_width=True, - key=f"prevAuto{type_of_graph}", - disabled=(st.session_state[variable_key] - self.TEAMS_TO_SPLIT_BY < 0) + prev_col, next_col = st.columns(2) + if prev_col.button( + f"Previous {self.TEAMS_TO_SPLIT_BY} Teams", + use_container_width=True, + key="prevDriver", + disabled=(offset - self.TEAMS_TO_SPLIT_BY < 0) ): st.session_state[variable_key] -= self.TEAMS_TO_SPLIT_BY - st.experimental_rerun() - + st.rerun() if next_col.button( - f"Next {self.TEAMS_TO_SPLIT_BY} Teams", - use_container_width=True, - key=f"nextAuto{type_of_graph}", - disabled=(st.session_state[variable_key] + self.TEAMS_TO_SPLIT_BY >= len(teams)) + f"Next {self.TEAMS_TO_SPLIT_BY} Teams", + use_container_width=True, + key="nextDriver", + disabled=(offset + self.TEAMS_TO_SPLIT_BY >= len(teams)) ): st.session_state[variable_key] += self.TEAMS_TO_SPLIT_BY - st.experimental_rerun() + st.rerun() - # Display event-wide graph surrounding each team and their fuel distributions in the Teleop period. - with teleop_fuel_col: - variable_key = f"teleop_fuel_col_{type_of_graph}" + with throughput_col: + variable_key = "throughput_dist" - teleop_distributions = ( - self._retrieve_fuel_distributions(Queries.TELEOP) - if display_fuel_scored - else self._retrieve_point_distributions(Queries.TELEOP) - ) - teleop_sorted_distributions = dict( + throughput_distributions = self._retrieve_throughput_distributions() + sorted_tp = dict( sorted( - zip(teams, teleop_distributions), - key=lambda pair: (pair[1].median(), pair[1].mean()), + zip(teams, throughput_distributions), + key=lambda pair: (pair[1].median() if len(pair[1]) else 0, + pair[1].mean() if len(pair[1]) else 0), reverse=True ) ) - teleop_sorted_teams = list(teleop_sorted_distributions.keys()) - teleop_distributions = list(teleop_sorted_distributions.values()) + sorted_teams_tp = list(sorted_tp.keys()) + sorted_throughput = list(sorted_tp.values()) - if not st.session_state.get(variable_key): + if variable_key not in st.session_state: st.session_state[variable_key] = 0 + offset = st.session_state[variable_key] plotly_chart( box_plot( - teleop_sorted_teams[ - st.session_state[variable_key]:st.session_state[variable_key] + self.TEAMS_TO_SPLIT_BY - ], - teleop_distributions[ - st.session_state[variable_key]:st.session_state[variable_key] + self.TEAMS_TO_SPLIT_BY - ], + sorted_teams_tp[offset:offset + self.TEAMS_TO_SPLIT_BY], + sorted_throughput[offset:offset + self.TEAMS_TO_SPLIT_BY], x_axis_label="Teams", - y_axis_label="Fuel Distribution" if display_fuel_scored else "Point Distribution", - title="Fuel Scored in Teleop" if display_fuel_scored else "Point Contributions in Teleop" - ).update_layout( - showlegend=False - ) + y_axis_label="Throughput Speed (1–5)", + title="Throughput Speed Distribution by Team" + ).update_layout(showlegend=False) ) - previous_col, next_col = st.columns(2) - - if previous_col.button( - f"Previous {self.TEAMS_TO_SPLIT_BY} Teams", - use_container_width=True, - key=f"prevTele{type_of_graph}", - disabled=(st.session_state[variable_key] - self.TEAMS_TO_SPLIT_BY < 0) + prev_col, next_col = st.columns(2) + if prev_col.button( + f"Previous {self.TEAMS_TO_SPLIT_BY} Teams", + use_container_width=True, + key="prevThroughput", + disabled=(offset - self.TEAMS_TO_SPLIT_BY < 0) ): st.session_state[variable_key] -= self.TEAMS_TO_SPLIT_BY - st.experimental_rerun() - + st.rerun() if next_col.button( - f"Next {self.TEAMS_TO_SPLIT_BY} Teams", - use_container_width=True, - key=f"nextTele{type_of_graph}", - disabled=(st.session_state[variable_key] + self.TEAMS_TO_SPLIT_BY >= len(teams)) + f"Next {self.TEAMS_TO_SPLIT_BY} Teams", + use_container_width=True, + key="nextThroughput", + disabled=(offset + self.TEAMS_TO_SPLIT_BY >= len(teams)) ): st.session_state[variable_key] += self.TEAMS_TO_SPLIT_BY - st.experimental_rerun() + st.rerun() diff --git a/src/page_managers/match_manager.py b/src/page_managers/match_manager.py index 2acdda8..a23ae7d 100644 --- a/src/page_managers/match_manager.py +++ b/src/page_managers/match_manager.py @@ -2,9 +2,6 @@ import numpy as np import streamlit as st -from pandas import to_numeric -from scipy.integrate import quad -from scipy.stats import norm from .page_manager import PageManager from utils import ( @@ -15,13 +12,13 @@ colored_metric, Criteria, GeneralConstants, + get_team_statbotics, GraphType, multi_line_graph, plotly_chart, populate_missing_data, Queries, retrieve_match_schedule, - retrieve_pit_scouting_data, retrieve_team_list, retrieve_scouting_data, scouting_data_for_team, @@ -35,42 +32,14 @@ class MatchManager(PageManager): def __init__(self): self.calculated_stats = CalculatedStats(retrieve_scouting_data()) - # self.pit_scouting_data = retrieve_pit_scouting_data() - - def _fuel_by_match(self, team_number: int, mode: str = ""): - team_data = scouting_data_for_team(team_number, self.calculated_stats.data) - magazine_size = to_numeric(team_data[Queries.MAGAZINE_SIZE]).fillna(0) - auto_fuel = ( - to_numeric(team_data[Queries.AUTO_SINGULAR_COUNT]).fillna(0) - + to_numeric(team_data[Queries.AUTO_BATCH_COUNT]).fillna(0) * magazine_size - ) - teleop_fuel = ( - to_numeric(team_data[Queries.TELEOP_SINGULAR_COUNT]).fillna(0) - + to_numeric(team_data[Queries.TELEOP_BATCH_COUNT]).fillna(0) * magazine_size - ) - if mode == Queries.AUTO: - return auto_fuel - if mode == Queries.TELEOP: - return teleop_fuel - return auto_fuel + teleop_fuel - - def _average_fuel(self, team_number: int, mode: str = ""): - return self._fuel_by_match(team_number, mode).mean() - - def _fuel_index(self, team_number: int) -> float: - counter_defense_skill = self.calculated_stats.average_counter_defense_skill(team_number) - return 0 if np.isnan(counter_defense_skill) else counter_defense_skill def generate_input_section(self) -> list[list, list]: """Creates the input section for the `Match` page. - Creates a dropdown to choose a match for and a dropdown to filter matches that a team played in. - :return: Returns a 2D list with the lists being the three teams for the Red and Blue alliances. """ match_schedule = retrieve_match_schedule() - # Create columns to make the input section more structured. filter_teams_col, match_selector_col = st.columns(2) filter_by_team_number = str( @@ -80,7 +49,6 @@ def generate_input_section(self) -> list[list, list]: ) if filter_by_team_number != "—": - # Filter through matches where the selected team plays in. match_schedule = match_schedule[ match_schedule["red_alliance"] .apply(lambda alliance: ",".join(map(str, alliance))) @@ -100,52 +68,23 @@ def generate_input_section(self) -> list[list, list]: def generate_hypothetical_input_section(self) -> list[list, list]: """Creates the input section for the `Hypothetical Match` page. - Creates six dropdowns to choose teams for each alliance separately. - :return: Returns a 2D list with the lists being the three teams for the Red and Blue alliances. """ team_list = retrieve_team_list() - # Create the separate columns for submitting teams. red_alliance_form, blue_alliance_form = st.columns(2, gap="medium") - # Create the different dropdowns to choose the three teams for Red Alliance. with red_alliance_form: red_1_col, red_2_col, red_3_col = st.columns(3) - red_1 = red_1_col.selectbox( - ":red[Red 1]", - team_list, - index=0 - ) - red_2 = red_2_col.selectbox( - ":red[Red 2]", - team_list, - index=1 - ) - red_3 = red_3_col.selectbox( - ":red[Red 3]", - team_list, - index=2 - ) + red_1 = red_1_col.selectbox(":red[Red 1]", team_list, index=0) + red_2 = red_2_col.selectbox(":red[Red 2]", team_list, index=1) + red_3 = red_3_col.selectbox(":red[Red 3]", team_list, index=2) - # Create the different dropdowns to choose the three teams for Blue Alliance. with blue_alliance_form: blue_1_col, blue_2_col, blue_3_col = st.columns(3) - blue_1 = blue_1_col.selectbox( - ":blue[Blue 1]", - team_list, - index=3 - ) - blue_2 = blue_2_col.selectbox( - ":blue[Blue 2]", - team_list, - index=4 - ) - blue_3 = blue_3_col.selectbox( - ":blue[Blue 3]", - team_list, - index=5 - ) + blue_1 = blue_1_col.selectbox(":blue[Blue 1]", team_list, index=3) + blue_2 = blue_2_col.selectbox(":blue[Blue 2]", team_list, index=4) + blue_3 = blue_3_col.selectbox(":blue[Blue 3]", team_list, index=5) return [ [red_1, red_2, red_3], @@ -155,358 +94,199 @@ def generate_hypothetical_input_section(self) -> list[list, list]: def generate_match_prediction_dashboard( self, red_alliance: list[int], blue_alliance: list[int] ) -> None: - """Generates metrics for match predictions (Red vs. Blue Tab). + """Generates metrics for match predictions (Red vs. Blue tab). - :param red_alliance: A list of three integers, each integer representing a team on the Red Alliance - :param blue_alliance: A list of three integers, each integer representing a team on the Blue Alliance. + :param red_alliance: A list of three integers for the Red Alliance. + :param blue_alliance: A list of three integers for the Blue Alliance. """ (chance_of_winning_col,) = st.columns(1) predicted_red_score_col, red_alliance_breakdown_col = st.columns(2) predicted_blue_score_col, blue_alliance_breakdown_col = st.columns(2) - # Calculates each alliance's chance of winning. - with chance_of_winning_col: - red_alliance_points = [ - self.calculated_stats.points_contributed_by_match(team) - for team in red_alliance - ] - blue_alliance_points = [ - self.calculated_stats.points_contributed_by_match(team) - for team in blue_alliance - ] - - # Calculate mean and standard deviation of the point distribution of the red alliance. - red_alliance_std = ( - sum( - [ - np.std(team_distribution) ** 2 - for team_distribution in red_alliance_points - ] - ) - ** 0.5 - ) - red_alliance_mean = sum( - [ - np.mean(team_distribution) - for team_distribution in red_alliance_points - ] - ) - - # Calculate mean and standard deviation of the point distribution of the blue alliance. - blue_alliance_std = ( - sum( - [ - np.std(team_distribution) ** 2 - for team_distribution in blue_alliance_points - ] - ) - ** 0.5 - ) - blue_alliance_mean = sum( - [ - np.mean(team_distribution) - for team_distribution in blue_alliance_points - ] - ) + odds_red, odds_blue, red_mean, blue_mean = self.calculated_stats.chance_of_winning( + red_alliance, blue_alliance + ) - # Calculate mean and standard deviation of the point distribution of red alliance - blue alliance - compared_std = (red_alliance_std ** 2 + blue_alliance_std ** 2) ** 0.5 - compared_mean = red_alliance_mean - blue_alliance_mean - - # Use sentinel value if there isn't enough of a distribution yet to determine standard deviation. - if not compared_std and compared_mean: - compared_std = abs(compared_mean) - elif not compared_std: - compared_std = 0.5 - - compared_distribution = norm(loc=compared_mean, scale=compared_std) - - # Calculate odds of red/blue winning using integrals. - odds_of_red_winning = quad( - lambda x: compared_distribution.pdf(x), 0, np.inf - )[0] - odds_of_blue_winning = quad( - lambda x: compared_distribution.pdf(x), -np.inf, 0 - )[0] - - # Create the stacked bar comparing the odds of the red alliance and blue alliance winning. - win_percentages( - red_odds=odds_of_red_winning, blue_odds=odds_of_blue_winning - ) + with chance_of_winning_col: + win_percentages(red_odds=odds_red, blue_odds=odds_blue) - # Calculates the predicted scores for each alliance with predicted_red_score_col: colored_metric( - "Predicted Score (Red)", - round( - red_alliance_mean - * ( - GeneralConstants.AVERAGE_FOUL_RATE - if GeneralConstants.AVERAGE_FOUL_RATE - else 1 - ), - 1 - ), + "Predicted Composite (Red)", + round(red_mean, 1), background_color=GeneralConstants.DARK_RED, opacity=0.5, ) with predicted_blue_score_col: colored_metric( - "Predicted Score (Blue)", - round( - blue_alliance_mean - * ( - GeneralConstants.AVERAGE_FOUL_RATE - if GeneralConstants.AVERAGE_FOUL_RATE - else 1 - ), - 1 - ), + "Predicted Composite (Blue)", + round(blue_mean, 1), background_color=GeneralConstants.DARK_BLUE, opacity=0.5, ) - # Alliance breakdowns by team with red_alliance_breakdown_col: - average_points_contributed = [ - round(np.mean(team_distribution), 1) - for team_distribution in red_alliance_points + avg_composites_red = [ + round(get_team_statbotics(team).get("total_epa") or 0, 1) + for team in red_alliance ] - - best_to_defend = sorted( - [ - ( - team, - self.calculated_stats.average_driver_rating(team), - self.calculated_stats.average_counter_defense_skill(team) - ) - for idx, team in enumerate(red_alliance) - ], - key=lambda info: info[1] / info[2], - )[-1][0] - + best_to_defend_red = self._best_to_defend(red_alliance) alliance_breakdown( red_alliance, - average_points_contributed, - best_to_defend, + avg_composites_red, + best_to_defend_red, Queries.RED_ALLIANCE, ) with blue_alliance_breakdown_col: - average_points_contributed = [ - round(np.mean(team_distribution), 1) - for team_distribution in blue_alliance_points + avg_composites_blue = [ + round(get_team_statbotics(team).get("total_epa") or 0, 1) + for team in blue_alliance ] - best_to_defend = sorted( - [ - ( - team, - self.calculated_stats.average_driver_rating(team), - self.calculated_stats.average_counter_defense_skill(team) - ) - for idx, team in enumerate(blue_alliance) - ], - key=lambda info: info[1] / info[2], - )[-1][0] - + best_to_defend_blue = self._best_to_defend(blue_alliance) alliance_breakdown( blue_alliance, - average_points_contributed, - best_to_defend, + avg_composites_blue, + best_to_defend_blue, Queries.BLUE_ALLIANCE, ) + def _best_to_defend(self, alliance: list[int]) -> int: + """Returns the team on the alliance that is hardest to defend against (highest throughput / counter-defense ratio).""" + rankings = sorted( + [ + ( + team, + self.calculated_stats.average_throughput_speed(team), + max(self.calculated_stats.average_counter_defense_skill(team), 0.01) + ) + for team in alliance + ], + key=lambda info: info[1] / info[2], + ) + return rankings[-1][0] + + @staticmethod + def _alliance_sorted_bar( + red_alliance: list[int], + blue_alliance: list[int], + values: list[float], + x_axis_label: str, + y_axis_label: str, + title: str, + red_color: str, + blue_color: str, + ): + """Returns a bar graph sorted highest→lowest and colored by alliance. + + :param red_alliance: Teams on the red alliance. + :param blue_alliance: Teams on the blue alliance. + :param values: One value per team (red first, then blue, 6 total). + :param red_color: Hex color for red alliance bars. + :param blue_color: Hex color for blue alliance bars. + :return: A Plotly Figure. + """ + combined = red_alliance + blue_alliance + # Use string keys so Plotly treats the column as categorical (not a continuous gradient) + color_map = { + **{str(team): red_color for team in red_alliance}, + **{str(team): blue_color for team in blue_alliance}, + } + pairs = sorted(zip(combined, values), key=lambda p: p[1], reverse=True) + sorted_team_strs = [str(p[0]) for p in pairs] + sorted_values = [p[1] for p in pairs] + return bar_graph( + sorted_team_strs, sorted_values, + x_axis_label=x_axis_label, + y_axis_label=y_axis_label, + title=title, + color={t: color_map[t] for t in sorted_team_strs}, + color_indicator=x_axis_label, + ) + def generate_match_prediction_graphs( self, red_alliance: list[int], blue_alliance: list[int], type_of_graph: str ) -> None: """Generate graphs for match prediction (Red vs. Blue tab). - :param red_alliance: A list of three integers, each integer representing a team on the Red Alliance - :param blue_alliance: A list of three integers, each integer representing a team on the Blue Alliance. - :param type_of_graph: The type of graphs to display (fuel contributions / point contributions). + :param red_alliance: A list of three integers for the Red Alliance. + :param blue_alliance: A list of three integers for the Blue Alliance. + :param type_of_graph: Unused; kept for API compatibility. """ combined_teams = red_alliance + blue_alliance - display_fuel_contributions = type_of_graph == GraphType.FUEL_CONTRIBUTIONS - color_sequence = ["#781212", "#163ba1"] # Bright red # Bright blue - - structure_breakdown_col, auto_fuel_col = st.columns(2) - teleop_fuel_col, cumulative_fuel_col = st.columns(2) - - # Breaks down where the different teams scored among the six teams - with structure_breakdown_col: - structure_breakdown = [ - [ - self._fuel_by_match(team).sum() - for team in combined_teams - ] - ] - - plotly_chart( - stacked_bar_graph( - combined_teams, - structure_breakdown, - "Teams", - ["# of Total Fuel"], - "Total Fuel Scored", - title="Structure Breakdown", - color_map={ - "# of Total Fuel": GeneralConstants.GOLD_GRADIENT[0], - }, - ).update_layout(xaxis={"categoryorder": "total descending"}) - ) - - # Breaks down fuel/point contributions among both alliances in Autonomous. - with auto_fuel_col: - auto_alliance_distributions = [] - - for alliance in (red_alliance, blue_alliance): - fuel_in_alliance = [ - ( - self._fuel_by_match(team, Queries.AUTO) - if display_fuel_contributions - else self.calculated_stats.points_contributed_by_match( - team, Queries.AUTO - ) - ) - for team in alliance - ] - auto_alliance_distributions.append( - self.calculated_stats.cartesian_product( - *fuel_in_alliance, reduce_with_sum=True - ) - ) - plotly_chart( - box_plot( - ["Red Alliance", "Blue Alliance"], - auto_alliance_distributions, - y_axis_label=( - "Fuel Scored" - if display_fuel_contributions - else "Points Contributed" - ), - title=( - f"Fuel During Autonomous (N={len(auto_alliance_distributions[0])})" - if display_fuel_contributions - else f"Points Contributed During Autonomous (N={len(auto_alliance_distributions[0])})" - ), - color_sequence=color_sequence, - ) - ) - - # Breaks down fuel/point contributions among both alliances in Teleop. - with teleop_fuel_col: - teleop_alliance_distributions = [] - - for alliance in (red_alliance, blue_alliance): - fuel_in_alliance = [ - ( - self._fuel_by_match(team, Queries.TELEOP) - if display_fuel_contributions - else self.calculated_stats.points_contributed_by_match( - team, Queries.TELEOP - ) - ) - for team in alliance - ] - teleop_alliance_distributions.append( - self.calculated_stats.cartesian_product( - *fuel_in_alliance, reduce_with_sum=True - ) - ) - - plotly_chart( - box_plot( - ["Red Alliance", "Blue Alliance"], - teleop_alliance_distributions, - y_axis_label=( - "Fuel Scored" - if display_fuel_contributions - else "Points Contributed" - ), - title=( - f"Fuel During Teleop (N={len(teleop_alliance_distributions[0])})" - if display_fuel_contributions - else f"Points Contributed During Teleop (N={len(teleop_alliance_distributions[0])})" - ), - color_sequence=color_sequence, - ) - ) - - # Show cumulative fuel/point contributions (auto and teleop) - with cumulative_fuel_col: - cumulative_alliance_distributions = [ - auto_distribution + teleop_distribution - for auto_distribution, teleop_distribution in zip( - auto_alliance_distributions, teleop_alliance_distributions - ) - ] - - plotly_chart( - box_plot( - ["Red Alliance", "Blue Alliance"], - cumulative_alliance_distributions, - y_axis_label=( - "Fuel Scored" - if display_fuel_contributions - else "Points Contributed" - ), - title=( - f"Fuel During Auto + Teleop (N={len(cumulative_alliance_distributions[0])})" - if display_fuel_contributions - else f"Points Contributed During Auto + Teleop (N={len(cumulative_alliance_distributions[0])})" - ), - color_sequence=color_sequence, - ) - ) + driver_col, throughput_col = st.columns(2) + intake_col, defense_col = st.columns(2) + + _RED = GeneralConstants.RED_ALLIANCE_GRADIENT[1] # #b04949 + _BLUE = GeneralConstants.BLUE_ALLIANCE_GRADIENT[2] # #7da0d1 + + with driver_col: + plotly_chart(self._alliance_sorted_bar( + red_alliance, blue_alliance, + [self.calculated_stats.average_driver_rating(t) for t in combined_teams], + x_axis_label="Team", y_axis_label="Avg. Driver Rating (1–5)", + title="Driver Rating Comparison", + red_color=_RED, blue_color=_BLUE, + )) + + with throughput_col: + plotly_chart(self._alliance_sorted_bar( + red_alliance, blue_alliance, + [self.calculated_stats.average_throughput_speed(t) for t in combined_teams], + x_axis_label="Team", y_axis_label="Avg. Throughput (1–5)", + title="Throughput Speed Comparison", + red_color=_RED, blue_color=_BLUE, + )) + + with intake_col: + plotly_chart(self._alliance_sorted_bar( + red_alliance, blue_alliance, + [self.calculated_stats.average_intake_speed_rating(t) for t in combined_teams], + x_axis_label="Team", y_axis_label="Avg. Intake Speed (1–5)", + title="Intake Speed Comparison", + red_color=_RED, blue_color=_BLUE, + )) + + with defense_col: + plotly_chart(self._alliance_sorted_bar( + red_alliance, blue_alliance, + [self.calculated_stats.average_defense_rating(t) for t in combined_teams], + x_axis_label="Team", y_axis_label="Avg. Defense (1–5)", + title="Defense Rating Comparison", + red_color=_RED, blue_color=_BLUE, + )) def generate_alliance_dashboard(self, team_numbers: list[int], color_gradient: list[str]) -> None: - """Generates an alliance dashboard in the `Match` page. + """Generates an alliance dashboard in the `Match` page, ranking teams by composite score. :param team_numbers: The teams to generate the alliance dashboard for. - :param color_gradient: The color gradient to use for graphs, depending on the alliance. - :return: + :param color_gradient: The color gradient depending on alliance. """ - highest_fuel_index_col, second_highest_fuel_index_col, lowest_fuel_index_col = st.columns(3) - - fuel_index_rankings = sorted( + rankings = sorted( { - team: self._fuel_index(team) for team in team_numbers + team: float(get_team_statbotics(team).get("total_epa") or 0) + for team in team_numbers }.items(), key=lambda pair: pair[1], reverse=True ) - # Colored metric displaying the highest fuel index in the alliance. - with highest_fuel_index_col: - colored_metric( - "Highest Fuel Index", - fuel_index_rankings[0][0], - background_color=color_gradient[0], - opacity=0.4, - border_opacity=0.9 - ) - - # Colored metric displaying the second highest fuel index in the alliance. - with second_highest_fuel_index_col: - colored_metric( - "Second Highest Fuel Index", - fuel_index_rankings[1][0], - background_color=color_gradient[1], - opacity=0.4, - border_opacity=0.9 - ) - - # Colored metric displaying the lowest fuel index in the alliance. - with lowest_fuel_index_col: - colored_metric( - "Lowest Fuel Index", - fuel_index_rankings[2][0], - background_color=color_gradient[2], - opacity=0.4, - border_opacity=0.9 - ) + col1, col2, col3 = st.columns(3) + labels = ["Highest Composite", "Second Composite", "Lowest Composite"] + + for col, label, (team, _), color in zip( + [col1, col2, col3], labels, rankings, color_gradient + ): + with col: + colored_metric( + label, + team, + background_color=color, + opacity=0.4, + border_opacity=0.9 + ) def generate_autonomous_graphs( self, @@ -517,83 +297,44 @@ def generate_autonomous_graphs( """Generates the autonomous graphs for the `Match` page. :param team_numbers: The teams to generate the graphs for. - :param type_of_graph: The type of graph to make (fuel contributions/point contributions). - :param color_gradient: The color gradient to use for graphs, depending on the alliance. - :return: + :param type_of_graph: Unused; kept for API compatibility. + :param color_gradient: The color gradient depending on alliance. """ - display_fuel_contributions = type_of_graph == GraphType.FUEL_CONTRIBUTIONS - - best_auto_config_col, auto_fuel_breakdown_col = st.columns(2) - - - # Best auto configuration graph - with best_auto_config_col: - if display_fuel_contributions: - best_autos_by_team = sorted( - [ - (team_number, self._fuel_by_match(team_number, Queries.AUTO).max()) - for team_number in team_numbers - ], - key=lambda pair: pair[1], - reverse=True - ) - else: - best_autos_by_team = sorted( - [ - ( - team_number, - self.calculated_stats.points_contributed_by_match(team_number, Queries.AUTO).max()) - for team_number in team_numbers - ], - key=lambda pair: pair[1], - reverse=True - ) + auto_climb_col, auto_scoring_side_col = st.columns(2) - plotly_chart( - bar_graph( - [pair[0] for pair in best_autos_by_team], - [pair[1] for pair in best_autos_by_team], - x_axis_label="Teams", - y_axis_label=( - "# of Fuel in Auto" - if display_fuel_contributions - else "# of Points in Auto" - ), - title="Best Auto Configuration", - color=color_gradient - ) - ) - - # Auto fuel breakdown graph - with auto_fuel_breakdown_col: - if display_fuel_contributions: - average_auto_fuel_by_team = [ - self._average_fuel(team, Queries.AUTO) - for team in team_numbers - ] + with auto_climb_col: + auto_climb_rates = [ + round(self.calculated_stats.auto_climb_rate(team) * 100, 1) + for team in team_numbers + ] + plotly_chart(bar_graph( + team_numbers, auto_climb_rates, + x_axis_label="Team", y_axis_label="Auto Climb Rate (%)", + title="Auto Climb Rate by Team", + color=color_gradient + )) + + with auto_scoring_side_col: + # Aggregate all scoring sides across teams in the alliance + all_sides: dict[str, int] = {} + for team in team_numbers: + team_data = scouting_data_for_team(team) + for sides in team_data[Queries.AUTO_SCORING_SIDE]: + if isinstance(sides, list): + for s in sides: + all_sides[s] = all_sides.get(s, 0) + 1 + + if all_sides: + sorted_sides = dict(sorted(all_sides.items(), key=lambda x: x[1], reverse=True)) + plotly_chart(bar_graph( + list(sorted_sides.keys()), + list(sorted_sides.values()), + x_axis_label="Side", y_axis_label="# of Occurrences", + title="Auto Scoring Sides (Alliance)", + color=color_gradient[0] + )) else: - average_auto_fuel_by_team = [ - self._average_fuel(team, Queries.AUTO) - for team in team_numbers - ] - - plotly_chart( - stacked_bar_graph( - team_numbers, - [average_auto_fuel_by_team], - "Teams", - [ - ("Avg. Auto Fuel" if display_fuel_contributions else "Avg. Auto Points"), - ], - ("Total Auto Fuel" if display_fuel_contributions else "Total Auto Points"), - title="Auto Scoring Breakdown", - color_map={ - ("Avg. Auto Fuel" if display_fuel_contributions else "Avg. Auto Points"): - color_gradient[0], - } - ).update_layout(xaxis={"categoryorder": "total descending"}) - ) - + st.info("No auto scoring side data.") def generate_teleop_graphs( self, @@ -604,112 +345,92 @@ def generate_teleop_graphs( """Generates the teleop graphs for the `Match` page. :param team_numbers: The teams to generate the graphs for. - :param type_of_graph: The type of graph to make (fuel contributions/point contributions). - :param color_gradient: The color gradient to use for graphs, depending on the alliance. - :return: + :param type_of_graph: Unused; kept for API compatibility. + :param color_gradient: The color gradient depending on alliance. """ teams_data = [scouting_data_for_team(team) for team in team_numbers] - display_fuel_contributions = type_of_graph == GraphType.FUEL_CONTRIBUTIONS - st.write("## ⭕ Fuel") - (fuel_over_time_col,) = st.columns(1, gap="small") + st.write("## 🧗 Endgame") + climb_breakdown_col, climb_speed_col = st.columns(2) st.divider() - st.write("## ⛓️ Endgame") - climb_breakdown_by_team_col, climb_speed_by_team = st.columns(2, gap="large") + st.write("## 🎯 Teleop") + teleop_side_col, shoot_move_col = st.columns(2) - short_gradient = [ - GeneralConstants.LIGHT_RED, - GeneralConstants.RED_TO_GREEN_GRADIENT[2], - GeneralConstants.LIGHT_GREEN - ] + climb_levels = ["L1", "L2", "L3"] + level_colors = { + f"Level {Criteria.CLIMBING_CRITERIA[lvl]} Climbs": color + for lvl, color in zip(climb_levels, GeneralConstants.LEVEL_GRADIENT) + } - # Display the teleop fuel of each team over time. - with fuel_over_time_col: - fuel_by_team = [ - self._fuel_by_match(team, Queries.TELEOP) - for team in team_numbers - ] - best_teams = sorted(zip(team_numbers, fuel_by_team), key=lambda pair: pair[1].mean()) - color_map = { - pair[0]: color - for pair, color in zip(best_teams, short_gradient) - } - - plotly_chart( - multi_line_graph( - *populate_missing_data(fuel_by_team), - x_axis_label="Match Index", - y_axis_label=team_numbers, - y_axis_title=( - "# of Fuel Scored" - if display_fuel_contributions - else "Points Contributed" - ), - title=( - "Teleop Fuel Over Time" - if display_fuel_contributions - else "Points Contributed through Teleop Fuel Over Time" - ), - color_map=color_map - ) - ) - - with climb_breakdown_by_team_col: - climb_levels = [ - level - for level in Criteria.CLIMBING_CRITERIA - if level is not None and level != "None" - ] + with climb_breakdown_col: climbs_by_level = [ - [ - (team_data[Queries.TELEOP_CLIMB] == level).sum() - for team_data in teams_data - ] + [(td[Queries.TELEOP_CLIMB] == level).sum() for td in teams_data] for level in climb_levels ] - climb_level_labels = [ - f"Level {Criteria.CLIMBING_CRITERIA[level]} Climbs" - for level in climb_levels - ] - - plotly_chart( - stacked_bar_graph( - team_numbers, - climbs_by_level, - x_axis_label="Teams", - y_axis_label=climb_level_labels, - y_axis_title="# of Climbs by Level", - title="Climbs by Team", - color_map={ - label: color - for label, color in zip(climb_level_labels, GeneralConstants.LEVEL_GRADIENT) - } - ) - ) - - with climb_speed_by_team: - slow_climbs = [ - (team_data[Queries.CLIMB_SPEED] == "Slow").sum() - for team_data in teams_data + climb_level_labels = [f"Level {Criteria.CLIMBING_CRITERIA[lvl]} Climbs" for lvl in climb_levels] + plotly_chart(stacked_bar_graph( + team_numbers, + climbs_by_level, + x_axis_label="Teams", + y_axis_label=climb_level_labels, + y_axis_title="# of Climbs by Level", + title="Climbs by Team", + color_map=level_colors + )) + + with climb_speed_col: + speed_buckets = ["<5 seconds", "5-10 seconds", "10-20 seconds", ">20 seconds"] + counts_by_speed = [ + [(td[Queries.CLIMB_SPEED] == speed).sum() for td in teams_data] + for speed in speed_buckets ] + speed_colors = dict(zip( + speed_buckets, + GeneralConstants.RED_TO_GREEN_GRADIENT[:len(speed_buckets)][::-1] + )) + plotly_chart(stacked_bar_graph( + team_numbers, + counts_by_speed, + x_axis_label="Teams", + y_axis_label=speed_buckets, + y_axis_title="# of Matches by Speed", + title="Climb Speeds by Team", + color_map=speed_colors + )) + + with teleop_side_col: + all_sides: dict[str, int] = {} + for team in team_numbers: + team_data = scouting_data_for_team(team) + for sides in team_data[Queries.TELEOP_SCORING_SIDE]: + if isinstance(sides, list): + for s in sides: + all_sides[s] = all_sides.get(s, 0) + 1 + + if all_sides: + sorted_sides = dict(sorted(all_sides.items(), key=lambda x: x[1], reverse=True)) + plotly_chart(bar_graph( + list(sorted_sides.keys()), + list(sorted_sides.values()), + x_axis_label="Side", y_axis_label="# of Occurrences", + title="Teleop Scoring Sides (Alliance)", + color=color_gradient[0] + )) + else: + st.info("No teleop scoring side data.") - fast_climbs = [ - (team_data[Queries.CLIMB_SPEED] == "Fast").sum() - for team_data in teams_data + with shoot_move_col: + sotm_rates = [ + round(self.calculated_stats.shoot_on_the_move_rate(team) * 100, 1) + for team in team_numbers ] - - plotly_chart( - stacked_bar_graph( - team_numbers, - [slow_climbs, fast_climbs], - x_axis_label="Teams", - y_axis_label=["Slow Climbs", "Fast Climbs"], - y_axis_title="# of Climb Speeds", - title="Climb Speeds by Team", - color_map={"Slow Climbs": GeneralConstants.LIGHT_RED, "Fast Climbs": GeneralConstants.LIGHT_GREEN} - ) - ) + plotly_chart(bar_graph( + team_numbers, sotm_rates, + x_axis_label="Team", y_axis_label="Shoot-on-the-Move Rate (%)", + title="Shoot on the Move Rate", + color=color_gradient[1] + )) def generate_qualitative_graphs( self, @@ -719,58 +440,40 @@ def generate_qualitative_graphs( """Generates the qualitative graphs for the `Match` page. :param team_numbers: The teams to generate the graphs for. - :param color_gradient: The color gradient to use for graphs, depending on the alliance. - :return: + :param color_gradient: The color gradient depending on alliance. """ - driver_rating_by_team_col, counter_defense_rating_by_team_col, disables_by_team_col = st.columns(3) + driver_rating_col, throughput_col, disables_col = st.columns(3) - with driver_rating_by_team_col: - driver_rating_by_team = [ - self.calculated_stats.average_driver_rating(team) - for team in team_numbers + with driver_rating_col: + driver_ratings = [ + self.calculated_stats.average_driver_rating(team) for team in team_numbers ] - - plotly_chart( - bar_graph( - team_numbers, - driver_rating_by_team, - x_axis_label="Teams", - y_axis_label="Driver Rating (1-5)", - title="Average Driver Rating by Team", - color=color_gradient[0] - ) - ) - - with counter_defense_rating_by_team_col: - counter_defense_rating_by_team = [ - self.calculated_stats.average_counter_defense_skill(team) - for team in team_numbers + plotly_chart(bar_graph( + team_numbers, driver_ratings, + x_axis_label="Teams", y_axis_label="Driver Rating (1–5)", + title="Average Driver Rating by Team", + color=color_gradient[0] + )) + + with throughput_col: + throughput_ratings = [ + self.calculated_stats.average_throughput_speed(team) for team in team_numbers ] - - plotly_chart( - bar_graph( - team_numbers, - counter_defense_rating_by_team, - x_axis_label="Teams", - y_axis_label="Intake Defense Rating (1-5)", - title="Average Intake Defense Rating by Team", - color=color_gradient[1] - ) - ) - - with disables_by_team_col: - disables_by_team = [ - self.calculated_stats.cumulative_stat(team, Queries.DISABLE, Criteria.BOOLEAN_CRITERIA) + plotly_chart(bar_graph( + team_numbers, throughput_ratings, + x_axis_label="Teams", y_axis_label="Throughput Speed (1–5)", + title="Average Throughput Speed by Team", + color=color_gradient[1] + )) + + with disables_col: + disable_rates = [ + round(self.calculated_stats.disabled_rate(team) * 100, 1) for team in team_numbers ] - - plotly_chart( - bar_graph( - team_numbers, - disables_by_team, - x_axis_label="Teams", - y_axis_label="Disables", - title="Disables by Team", - color=color_gradient[2] - ) - ) + plotly_chart(bar_graph( + team_numbers, disable_rates, + x_axis_label="Teams", y_axis_label="Disabled Rate (%)", + title="Disabled Rate by Team", + color=color_gradient[2] if len(color_gradient) > 2 else GeneralConstants.LIGHT_RED + )) diff --git a/src/page_managers/picklist_manager.py b/src/page_managers/picklist_manager.py index 82b6c95..db80a16 100644 --- a/src/page_managers/picklist_manager.py +++ b/src/page_managers/picklist_manager.py @@ -17,7 +17,7 @@ class PicklistManager(PageManager): """The page manager for the `Picklist` page.""" - TRUNCATE_AT_DIGIT = 2 # Round the decimal to two places + TRUNCATE_AT_DIGIT = 2 def __init__(self): self.calculated_stats = CalculatedStats( @@ -26,53 +26,38 @@ def __init__(self): self.teams = retrieve_team_list() self.client = Client(auth=os.getenv("NOTION_TOKEN")) - # Requested stats is used to define the stats wanted in the picklist generation. self.requested_stats = { - "Average Points Contributed": self.calculated_stats.average_points_contributed, - "# of Times Auto Climbed": partial( - self.calculated_stats.cumulative_stat, - stat=Queries.AUTO_CLIMB, - criteria=Criteria.BOOLEAN_CRITERIA - ), - "# of Times Teleop Climbed": partial( - self.calculated_stats.cumulative_stat, - stat=Queries.TELEOP_CLIMB, - criteria=Criteria.CLIMBING_CRITERIA - ), - "# of Disables": partial( - self.calculated_stats.cumulative_stat, - stat=Queries.DISABLE, - criteria=Criteria.BOOLEAN_CRITERIA - ), - "Average Driver Rating": self.calculated_stats.average_driver_rating, - "Average Counter Defense Skill": self.calculated_stats.average_counter_defense_skill, - "Average Defense Skill": self.calculated_stats.average_defense_rating, - "Average Shooter Defense Skill": self.calculated_stats.average_shooter_defense_skill, - "Average Intake Speed": self.calculated_stats.average_intake_speed_rating, - "Average Throughput Speed": self.calculated_stats.average_throughput_speed, + "Avg. Driver Rating (1–5)": self.calculated_stats.average_driver_rating, + "Avg. Throughput Speed (1–5)": self.calculated_stats.average_throughput_speed, + "Avg. Intake Speed (1–5)": self.calculated_stats.average_intake_speed_rating, + "Avg. Defense Rating (1–5)": self.calculated_stats.average_defense_rating, + "Avg. Counter Defense (1–5)": self.calculated_stats.average_counter_defense_skill, + "Avg. Shooter Defense (1–5)": self.calculated_stats.average_shooter_defense_skill, + "Teleop Climb Rate": self.calculated_stats.teleop_climb_rate, + "Auto Climb Rate": self.calculated_stats.auto_climb_rate, + "Disabled Rate": self.calculated_stats.disabled_rate, + "Shoot-on-the-Move Rate": self.calculated_stats.shoot_on_the_move_rate, } - def generate_input_section(self) -> list[list, list]: - """Creates the input section for the `Picklist` page. + def generate_input_section(self) -> list[str]: + """Creates a multiselect box for choosing picklist fields. - Creates a multiselect box to choose the different fields for the picklist table. - - :return: Returns a list containing the different fields chosen. + :return: The list of chosen stat names. """ return st.multiselect( "Picklist Fields", - self.requested_stats.keys(), + list(self.requested_stats.keys()), default=list(self.requested_stats.keys())[0] ) def generate_picklist(self, stats_requested: list[str]) -> DataFrame: - """Generates the picklist containing the statistics requested and the team number. + """Generates the picklist containing the requested statistics per team. - :param stats_requested: The name of the statistics requested (matches the keys in `self.requested_stats` + :param stats_requested: The names of the statistics to include. """ requested_picklist = [ { - "Team Number": f"FRC {team}" # We make it a string because otherwise Notion won't recognize the value. + "Team Number": f"FRC {team}" } | { stat_name: round(self.requested_stats[stat_name](team), self.TRUNCATE_AT_DIGIT) for stat_name in stats_requested @@ -82,12 +67,10 @@ def generate_picklist(self, stats_requested: list[str]) -> DataFrame: return DataFrame.from_dict(requested_picklist) def write_to_notion(self, dataframe: DataFrame) -> None: - """Writes to a Notion picklist entered by the user in the constants file. + """Writes the picklist to a Notion database. :param dataframe: The dataframe containing all the statistics of each team. - :return: """ - # Generate Notion Database first properties = { "Team Name": {"title": {}} } | { @@ -95,21 +78,19 @@ def write_to_notion(self, dataframe: DataFrame) -> None: } icon = {"type": "emoji", "emoji": "🗒️"} self.client.databases.update( - database_id=(db_id := get_id(EventSpecificConstants.PICKLIST_URL)), properties=properties, icon=icon + database_id=(db_id := get_id(EventSpecificConstants.PICKLIST_URL)), + properties=properties, + icon=icon ) - # Find percentiles across all teams percentile_75 = self.calculated_stats.quantile_stat( - 0.75, - lambda self_, team: self_.average_cycles(team) + 0.75, lambda self_, team: self_.average_driver_rating(team) ) percentile_50 = self.calculated_stats.quantile_stat( - 0.5, - lambda self_, team: self_.average_cycles(team) + 0.5, lambda self_, team: self_.average_driver_rating(team) ) percentile_25 = self.calculated_stats.quantile_stat( - 0.25, - lambda self_, team: self_.average_cycles(team) + 0.25, lambda self_, team: self_.average_driver_rating(team) ) for _, row in dataframe.iterrows(): @@ -118,80 +99,35 @@ def write_to_notion(self, dataframe: DataFrame) -> None: database_id=db_id, filter={ "property": "Team Name", - "title": { - "contains": team_name, - }, + "title": {"contains": team_name} } ) - # Based off of the percentile between all their stats team_number = int(team_name.split()[1]) - team_cycles = self.calculated_stats.average_cycles(team_number) + team_driver = self.calculated_stats.average_driver_rating(team_number) - if team_cycles > percentile_75: + if team_driver > percentile_75: emoji = "🔵" - elif percentile_50 <= team_cycles < percentile_75: + elif percentile_50 <= team_driver < percentile_75: emoji = "🟢" - elif percentile_25 <= team_cycles < percentile_50: + elif percentile_25 <= team_driver < percentile_50: emoji = "🟠" else: emoji = "🔴" - autonomous_notes = self.calculated_stats.stat_per_match(team_number, Queries.AUTO_NOTES) - - if not autonomous_notes.empty and any(note for note in autonomous_notes): - autonomous_block = [ - { - "object": "block", - "type": "heading_3", - "heading_3": { - "rich_text": [ - { - "text": { - "content": "Autonomous Notes", - "link": None - } - } - ], - "color": "default", - "is_toggleable": False - } - } - ] + [ - { - "type": "bulleted_list_item", - "bulleted_list_item": { - "rich_text": [ - { - "type": "text", - "text": { - "content": note, - "link": None - } - } - ] - } - } - for note in autonomous_notes if note - ] - else: - autonomous_block = [] - + auto_notes = self.calculated_stats.stat_per_match(team_number, Queries.AUTO_NOTES) teleop_notes = self.calculated_stats.stat_per_match(team_number, Queries.TELEOP_NOTES) + rating_notes = self.calculated_stats.stat_per_match(team_number, Queries.RATING_NOTES) - if not teleop_notes.empty and any(note for note in teleop_notes): - teleop_block = [ + def _notes_block(heading: str, notes_series) -> list: + notes_list = [n for n in notes_series if n] + if not notes_list: + return [] + return [ { "object": "block", "type": "heading_3", "heading_3": { - "rich_text": [ - { - "text": { - "content": "Teleop Notes", - "link": None - } - } - ], + "rich_text": [{"text": {"content": heading, "link": None}}], "color": "default", "is_toggleable": False } @@ -200,107 +136,50 @@ def write_to_notion(self, dataframe: DataFrame) -> None: { "type": "bulleted_list_item", "bulleted_list_item": { - "rich_text": [ - { - "type": "text", - "text": { - "content": note, - "link": None - } - } - ] + "rich_text": [{"type": "text", "text": {"content": note, "link": None}}] } } - for note in teleop_notes if note + for note in notes_list ] - else: - teleop_block = [] - endgame_notes = self.calculated_stats.stat_per_match(team_number, Queries.ENDGAME_NOTES) - if not endgame_notes.empty and any(note for note in endgame_notes): - endgame_block = [ - { - "object": "block", - "type": "heading_3", - "heading_3": { - "rich_text": [ - { - "text": { - "content": "Endgame Notes", - "link": None - } - } - ], - "color": "default", - "is_toggleable": False - } + children = ( + [{ + "object": "block", + "type": "callout", + "callout": { + "rich_text": [{"type": "text", "text": {"content": f"Notes of {team_name}", "link": None}}], + "icon": {"emoji": "📝"}, + "color": "default" } - ] + [ - { - "type": "bulleted_list_item", - "bulleted_list_item": { - "rich_text": [ - { - "type": "text", - "text": { - "content": note, - "link": None - } - } - ] - } - } - for note in endgame_notes if note - ] - else: - endgame_block = [] + }] + + _notes_block("Autonomous Notes", auto_notes) + + _notes_block("Teleop Notes", teleop_notes) + + _notes_block("Rating Notes", rating_notes) + ) + + page_props = { + column: { + "number": data if notna( + data := dataframe[dataframe["Team Number"] == team_name][column].tolist()[0] + ) else 0 + } + for column in dataframe.columns if column != "Team Number" + } | { + "Team Name": {"id": "title", "title": [{"text": {"content": team_name}}]}, + } - # No page created yet. if not query_page["results"]: self.client.pages.create( database_id=db_id, icon={"type": "emoji", "emoji": emoji}, parent={"type": "database_id", "database_id": db_id}, - properties={ - column: { - "number": data if notna(data := dataframe[dataframe["Team Number"] == team_name][column].tolist()[0]) else 0 - } for column in dataframe.columns if column != "Team Number" - } | { - "Team Name": {"id": "title", "title": [{"text": {"content": team_name}}]}, - }, - children=[ - { - "object": "block", - "type": "callout", - "callout": { - "rich_text": [ - { - "type": "text", - "text": { - "content": f"Notes of {team_name}", - "link": None - } - } - ], - "icon": { - "emoji": "📝" - }, - "color": "default" - } - } - ] + autonomous_block + teleop_block + endgame_block + properties=page_props, + children=children ) - # Page already created else: self.client.pages.update( page_id=query_page["results"][0]["id"], icon={"type": "emoji", "emoji": emoji}, parent={"type": "database_id", "database_id": db_id}, - properties={ - column: { - "number": data if notna(data := dataframe[dataframe["Team Number"] == team_name][column].tolist()[0]) else 0 - } for column in dataframe.columns if column != "Team Number" - } | { - "Team Name": {"id": "title", "title": [{"text": {"content": team_name}}]}, - } + properties=page_props ) diff --git a/src/page_managers/ranking_simulator_manager.py b/src/page_managers/ranking_simulator_manager.py index c75c455..2d60d4c 100644 --- a/src/page_managers/ranking_simulator_manager.py +++ b/src/page_managers/ranking_simulator_manager.py @@ -125,4 +125,4 @@ def generate_simulated_rankings(self, to_match: int) -> None: sorted(new_rankings, key=lambda ranking: ranking[1:], reverse=True), columns=("Team", "Average Ranking Points", "Average Match Score") ) - st.table(ranking_df.applymap(lambda value: f"{value:.2f}" if isinstance(value, float) else value)) + st.table(ranking_df.map(lambda value: f"{value:.2f}" if isinstance(value, float) else value)) diff --git a/src/page_managers/scouting_accuracy_manager.py b/src/page_managers/scouting_accuracy_manager.py index c15e829..699fb1d 100644 --- a/src/page_managers/scouting_accuracy_manager.py +++ b/src/page_managers/scouting_accuracy_manager.py @@ -1,4 +1,4 @@ -"""Creates the `ScoutingAccuracyManager` class used to set up the Scouting Accuracy page and generate its table.""" +"""Creates the `ScoutingAccuracyManager` class used to set up the Scouting Coverage page.""" import pandas as pd import streamlit as st from .page_manager import PageManager @@ -6,10 +6,6 @@ CalculatedStats, Queries, retrieve_scouting_data, - retrieve_match_schedule, - retrieve_match_data, - retrieve_match_data_raw, - Criteria ) from dotenv import load_dotenv from pandas import DataFrame @@ -18,318 +14,82 @@ class ScoutingAccuracyManager(PageManager): - """The scouting accuracy page manager for the `Scouting Accuracy` page.""" + """Page manager for the `Scouting Coverage` page. + + Since the current dataset is qualitative-only, this page shows scouting + coverage statistics (matches scouted per person, teams covered) rather than + numerical accuracy against TBA scores. + """ + def __init__(self): self.calculated_stats = CalculatedStats(retrieve_scouting_data()) self.raw_scouting_data = retrieve_scouting_data() - self.match_schedule = retrieve_match_schedule() - self.match_data = retrieve_match_data() def generate_input_section(self) -> str: - """Generates the input section of the `Scouting Accuracy` page. - - Provides a text box for the user to input a custom string value. + """Provides a text input for filtering by scouter name. - :return: Returns the string entered by the user. + :return: The scout name entered by the user (empty string shows all). """ - return st.text_input( - "Enter the name of the member", - placeholder="Type here..." + "Filter by scouter name (leave blank to see all)", + placeholder="e.g. Nikhil" ) - - #Method to create table sorted by scouter - def generate_scouting_accuracy_table(self, member_name: str) -> DataFrame: - """Generates the scouting accuracy table for the `Scouting Accuracy` page.""" - - accuracy_dict = { - 'ScoutersNames': [], - 'CumulativeAccuracy': [], - 'AutoAccuracy': [], - 'TeleopAccuracy': [], - 'NumberOfScoutedMatches': [] - } - - matches = retrieve_match_data_raw() - - for index, row in self.match_data.iterrows(): - match_key = row["match_key"] - red_alliance = row["red_alliance"] - blue_alliance = row["blue_alliance"] - - # Red alliance score from TBA - team_list = red_alliance.split(",") - for match in matches: - if (match["comp_level"] + str(match["match_number"])) == match_key: - red_total_score = match["score_breakdown"]["red"]["totalPoints"] - red_foul_score = match["score_breakdown"]["red"]["foulPoints"] - red_auto_score = match["score_breakdown"]["red"]["autoPoints"] - red_teleop_score = match["score_breakdown"]["red"]["teleopPoints"] - red_calculated_score = red_total_score - red_foul_score - break - - red_scouting_alliance_score = 0 - red_scouting_auto_score = 0 - red_scouting_teleop_score = 0 - - scouters_names_list_r = [] - - for team_key in team_list: - scouting_team_filter = self.raw_scouting_data[self.raw_scouting_data[Queries.TEAM_NUMBER] == int(team_key)] - scouting_team_filter = scouting_team_filter.reset_index(drop=True) - match_index_list = scouting_team_filter.index[scouting_team_filter[Queries.MATCH_KEY] == match_key].tolist() - if len(match_index_list) != 0: - match_index = match_index_list[0] - scouting_row = scouting_team_filter.iloc[match_index] - - auto_singular_count = int(float(scouting_row.get(Queries.AUTO_SINGULAR_COUNT, 0))) - auto_batch_count = int(float(scouting_row.get(Queries.AUTO_BATCH_COUNT, 0))) - teleop_singular_count = int(float(scouting_row.get(Queries.TELEOP_SINGULAR_COUNT, 0))) - teleop_batch_count = int(float(scouting_row.get(Queries.TELEOP_BATCH_COUNT, 0))) - magazine_size = int(float(scouting_row.get(Queries.MAGAZINE_SIZE, 0))) - auto_climb_points = Criteria.BOOLEAN_CRITERIA.get(scouting_row.get(Queries.AUTO_CLIMB), 0) * 15 - teleop_climb_points = Criteria.CLIMBING_CRITERIA.get(scouting_row.get(Queries.TELEOP_CLIMB), 0) * 10 - - team_auto_score = auto_singular_count + (auto_batch_count * magazine_size) + auto_climb_points - team_teleop_score = teleop_singular_count + (teleop_batch_count * magazine_size) - team_total_score = team_auto_score + team_teleop_score + teleop_climb_points - - red_scouting_auto_score += team_auto_score - red_scouting_teleop_score += team_teleop_score - red_scouting_alliance_score += team_total_score - - scout_name = scouting_team_filter.iloc[match_index][Queries.SCOUT_ID] - scouters_names_list_r.append(scout_name.title().replace(" ", "")) - - # Red alliance accuracy - if red_calculated_score == 0: - if red_scouting_alliance_score == 0: - red_alliance_accuracy = 100.0 - else: - red_alliance_accuracy = 0.0 - else: - red_alliance_accuracy = (1 - abs((red_scouting_alliance_score - red_calculated_score) / red_calculated_score)) * 100 - # red auto accuracy - if red_auto_score == 0: - if red_scouting_auto_score == 0: - red_auto_accuracy = 100.0 - else: - red_auto_accuracy = 0.0 - else: - red_auto_accuracy = (1 - abs((red_scouting_auto_score - red_auto_score) / red_auto_score)) * 100 - # red teleop accuracy - if red_teleop_score == 0: - if red_scouting_teleop_score == 0: - red_teleop_accuracy = 100.0 - else: - red_teleop_accuracy = 0.0 - else: - red_teleop_accuracy = (1 - abs((red_scouting_teleop_score - red_teleop_score) / red_teleop_score)) * 100 - - scouters_names = ", ".join(scouters_names_list_r) - - if member_name.replace(" ", "").lower() in scouters_names.replace(" ", "").lower(): - if scouters_names not in accuracy_dict['ScoutersNames']: - accuracy_dict['ScoutersNames'].append(scouters_names) - accuracy_dict['CumulativeAccuracy'].append(red_alliance_accuracy) - accuracy_dict['AutoAccuracy'].append(red_auto_accuracy) - accuracy_dict['TeleopAccuracy'].append(red_teleop_accuracy) - accuracy_dict['NumberOfScoutedMatches'].append(1) - else: - accuracy_scouts_index = accuracy_dict['ScoutersNames'].index(scouters_names) - accuracy_dict['CumulativeAccuracy'][accuracy_scouts_index] += red_alliance_accuracy - accuracy_dict['AutoAccuracy'][accuracy_scouts_index] += red_auto_accuracy - accuracy_dict['TeleopAccuracy'][accuracy_scouts_index] += red_teleop_accuracy - accuracy_dict['NumberOfScoutedMatches'][accuracy_scouts_index] += 1 - - # Blue Alliance score from TBA - for match in matches: - if (match["comp_level"] + str(match["match_number"])) == match_key: - blue_total_score = match["score_breakdown"]["blue"]["totalPoints"] - blue_foul_score = match["score_breakdown"]["blue"]["foulPoints"] - blue_auto_score = match["score_breakdown"]["blue"]["autoPoints"] - blue_teleop_score = match["score_breakdown"]["blue"]["teleopPoints"] - blue_calculated_score = blue_total_score - blue_foul_score - break - - blue_scouting_alliance_score = 0 - blue_scouting_auto_score = 0 - blue_scouting_teleop_score = 0 - - scouters_names_list_b = [] - - for team_key in blue_alliance.split(","): - scouting_team_filter = self.raw_scouting_data[self.raw_scouting_data[Queries.TEAM_NUMBER] == int(team_key)] - scouting_team_filter = scouting_team_filter.reset_index(drop=True) - match_index_list = scouting_team_filter.index[scouting_team_filter[Queries.MATCH_KEY] == match_key].tolist() - if len(match_index_list) != 0: - match_index = match_index_list[0] - scouting_row = scouting_team_filter.iloc[match_index] - auto_singular_count = int(float(scouting_row.get(Queries.AUTO_SINGULAR_COUNT, 0))) - auto_batch_count = int(float(scouting_row.get(Queries.AUTO_BATCH_COUNT, 0))) - teleop_singular_count = int(float(scouting_row.get(Queries.TELEOP_SINGULAR_COUNT, 0))) - teleop_batch_count = int(float(scouting_row.get(Queries.TELEOP_BATCH_COUNT, 0))) - magazine_size = int(float(scouting_row.get(Queries.MAGAZINE_SIZE, 0))) - auto_climb_points = Criteria.BOOLEAN_CRITERIA.get(scouting_row.get(Queries.AUTO_CLIMB), 0) * 15 - teleop_climb_points = Criteria.CLIMBING_CRITERIA.get(scouting_row.get(Queries.TELEOP_CLIMB), 0) * 10 - - team_auto_score = auto_singular_count + (auto_batch_count * magazine_size) + auto_climb_points - team_teleop_score = teleop_singular_count + (teleop_batch_count * magazine_size) - team_total_score = team_auto_score + team_teleop_score + teleop_climb_points + def generate_scouting_accuracy_table(self, member_name: str) -> DataFrame: + """Generates a per-scouter coverage breakdown. - blue_scouting_auto_score += team_auto_score - blue_scouting_teleop_score += team_teleop_score - blue_scouting_alliance_score += team_total_score + Shows how many matches each scouter covered, which teams they scouted, + and how many unique teams they observed. - scout_name = scouting_team_filter.iloc[match_index][Queries.SCOUT_ID] - scouters_names_list_b.append(scout_name.title().replace(" ", "")) + :param member_name: Optional filter — only rows whose ScoutId contains this string are shown. + :return: A DataFrame summarising each scouter's coverage. + """ + data = self.raw_scouting_data.copy() - # blue alliance accuracy - if blue_calculated_score == 0: - if blue_scouting_alliance_score == 0: - blue_alliance_accuracy = 100.0 - else: - blue_alliance_accuracy = 0.0 - else: - blue_alliance_accuracy = (1 - abs((blue_scouting_alliance_score - blue_calculated_score) / blue_calculated_score)) * 100 - # blue auto accuracy - if blue_auto_score == 0: - if blue_scouting_auto_score == 0: - blue_auto_accuracy = 100.0 - else: - blue_auto_accuracy = 0.0 - else: - blue_auto_accuracy = (1 - abs((blue_scouting_auto_score - blue_auto_score) / blue_auto_score)) * 100 - # blue teleop accuracy - if blue_teleop_score == 0: - if blue_scouting_teleop_score == 0: - blue_teleop_accuracy = 100.0 - else: - blue_teleop_accuracy = 0.0 - else: - blue_teleop_accuracy = (1 - abs((blue_scouting_teleop_score - blue_teleop_score) / blue_teleop_score)) * 100 + if member_name.strip(): + data = data[ + data[Queries.SCOUT_ID].str.lower().str.contains(member_name.strip().lower(), na=False) + ] - scouters_names = ", ".join(scouters_names_list_b) + if data.empty: + return DataFrame(columns=["Scouter", "Matches Scouted", "Unique Teams", "Teams Scouted"]) - if member_name.replace(" ", "").lower() in scouters_names.lower(): - if scouters_names not in accuracy_dict['ScoutersNames']: - accuracy_dict['ScoutersNames'].append(scouters_names) - accuracy_dict['CumulativeAccuracy'].append(blue_alliance_accuracy) - accuracy_dict['AutoAccuracy'].append(blue_auto_accuracy) - accuracy_dict['TeleopAccuracy'].append(blue_teleop_accuracy) - accuracy_dict['NumberOfScoutedMatches'].append(1) - else: - accuracy_scouts_index = accuracy_dict['ScoutersNames'].index(scouters_names) - accuracy_dict['CumulativeAccuracy'][accuracy_scouts_index] += blue_alliance_accuracy - accuracy_dict['AutoAccuracy'][accuracy_scouts_index] += blue_auto_accuracy - accuracy_dict['TeleopAccuracy'][accuracy_scouts_index] += blue_teleop_accuracy - accuracy_dict['NumberOfScoutedMatches'][accuracy_scouts_index] += 1 + rows = [] + for scout_id, group in data.groupby(Queries.SCOUT_ID): + teams = sorted(group[Queries.TEAM_NUMBER].unique().tolist()) + rows.append({ + "Scouter": scout_id, + "Matches Scouted": len(group), + "Unique Teams": len(teams), + "Teams Scouted": ", ".join(str(t) for t in teams), + }) - df = pd.DataFrame(data={ - 'Scouters': accuracy_dict['ScoutersNames'], - 'Average Accuracy %': [round(accuracy_dict['CumulativeAccuracy'][scouter_set]/accuracy_dict['NumberOfScoutedMatches'][scouter_set], 2) for scouter_set in range(len(accuracy_dict['NumberOfScoutedMatches']))], - 'Average Auto Accuracy %': [round(accuracy_dict['AutoAccuracy'][scouter_set]/accuracy_dict['NumberOfScoutedMatches'][scouter_set], 2) for scouter_set in range(len(accuracy_dict['NumberOfScoutedMatches']))], - 'Average Teleop Accuracy %': [round(accuracy_dict['TeleopAccuracy'][scouter_set]/accuracy_dict['NumberOfScoutedMatches'][scouter_set], 2) for scouter_set in range(len(accuracy_dict['NumberOfScoutedMatches']))], - 'NumberOfScoutedMatches': accuracy_dict['NumberOfScoutedMatches'] - }) + return DataFrame(rows).sort_values("Matches Scouted", ascending=False).reset_index(drop=True) - return df - #Method to create table sorted by match def generate_match_accuracy_table(self) -> DataFrame: - """Generates the match accuracy table for all matches.""" - accuracy_rows = [] - matches = retrieve_match_data_raw() - - with st.spinner("Calculating match accuracy..."): - for index, row in self.match_data.iterrows(): - match_key = row["match_key"] - red_alliance = row["red_alliance"] - blue_alliance = row["blue_alliance"] - - # --- Red Alliance --- - red_team_list = red_alliance.split(",") - - red_calculated_score = None - for match in matches: - if (match["comp_level"] + str(match["match_number"])) == match_key: - red_total_score = match["score_breakdown"]["red"]["totalPoints"] - red_foul_score = match["score_breakdown"]["red"]["foulPoints"] - red_calculated_score = red_total_score - red_foul_score - break - - if red_calculated_score is None: - continue # skip if match not found - - red_scouting_alliance_score = 0 - scouters_names_r = [] - - for team_key in red_team_list: - scouting_team_filter = self.raw_scouting_data[ - self.raw_scouting_data[Queries.TEAM_NUMBER] == int(team_key) - ].reset_index(drop=True) - - match_indices = scouting_team_filter.index[ - scouting_team_filter[Queries.MATCH_KEY] == match_key - ].tolist() - - if len(match_indices) > 0: - idx = match_indices[0] - points_per_match = self.calculated_stats.points_contributed_by_match(int(team_key)).values - red_scouting_alliance_score += points_per_match[idx] - scout_name = scouting_team_filter.iloc[idx][Queries.SCOUT_ID] - scouters_names_r.append(scout_name.title().replace(" ", "")) - - red_accuracy = (1 - abs((red_scouting_alliance_score - red_calculated_score) / red_calculated_score)) * 100 + """Generates a per-match coverage breakdown showing which teams were scouted in each match. - # --- Blue Alliance --- - blue_team_list = blue_alliance.split(",") - - blue_calculated_score = None - for match in matches: - if (match["comp_level"] + str(match["match_number"])) == match_key: - blue_total_score = match["score_breakdown"]["blue"]["totalPoints"] - blue_foul_score = match["score_breakdown"]["blue"]["foulPoints"] - blue_calculated_score = blue_total_score - blue_foul_score - break - - if blue_calculated_score is None: - continue # skip if match not found - - blue_scouting_alliance_score = 0 - scouters_names_b = [] - - for team_key in blue_team_list: - scouting_team_filter = self.raw_scouting_data[ - self.raw_scouting_data[Queries.TEAM_NUMBER] == int(team_key) - ].reset_index(drop=True) - - match_indices = scouting_team_filter.index[ - scouting_team_filter[Queries.MATCH_KEY] == match_key - ].tolist() - - if len(match_indices) > 0: - idx = match_indices[0] - points_per_match = self.calculated_stats.points_contributed_by_match(int(team_key)).values - blue_scouting_alliance_score += points_per_match[idx] - scout_name = scouting_team_filter.iloc[idx][Queries.SCOUT_ID] - scouters_names_b.append(scout_name.title().replace(" ", "")) - - blue_accuracy = (1 - abs((blue_scouting_alliance_score - blue_calculated_score) / blue_calculated_score)) * 100 - - total_scouts = len(set(scouters_names_r + scouters_names_b)) - average_accuracy = round((red_accuracy + blue_accuracy) / 2, 2) - - accuracy_rows.append({ - "Match": match_key, - "# of Scouters": total_scouts, - "Accuracy (%)": f"{average_accuracy}%", - "# of Red Scouters": len(scouters_names_r), - "Red Accuracy (%)": f"{round(red_accuracy, 2)}%", - "# of Blue Scouters": len(scouters_names_b), - "Blue Accuracy (%)": f"{round(blue_accuracy, 2)}%" - }) - - accuracy_rows.sort(key = lambda row: int(row["Match"][2:])) - df = pd.DataFrame(accuracy_rows) - return df + :return: A DataFrame with one row per match and scouting coverage info. + """ + data = self.raw_scouting_data.copy() + + rows = [] + for match_key, group in data.groupby(Queries.MATCH_KEY): + num = group[Queries.MATCH_NUMBER].iloc[0] if Queries.MATCH_NUMBER in group.columns else None + teams = sorted(group[Queries.TEAM_NUMBER].unique().tolist()) + scouts = sorted(group[Queries.SCOUT_ID].unique().tolist()) + rows.append({ + "Match": match_key, + "Match #": num, + "Teams Scouted": len(teams), + "Team Numbers": ", ".join(str(t) for t in teams), + "Scouters": ", ".join(scouts), + }) + + if not rows: + return DataFrame() + + df = DataFrame(rows) + if "Match #" in df.columns and df["Match #"].notna().any(): + df = df.sort_values("Match #").reset_index(drop=True) + return df.drop(columns=["Match #"], errors="ignore") diff --git a/src/page_managers/team_manager.py b/src/page_managers/team_manager.py index a309323..5fe0a41 100644 --- a/src/page_managers/team_manager.py +++ b/src/page_managers/team_manager.py @@ -20,13 +20,13 @@ plotly_chart, Queries, retrieve_team_list, - retrieve_pit_scouting_data, - retrieve_match_data_raw, retrieve_scouting_data, scouting_data_for_team, stacked_bar_graph, colored_metric_with_two_values, - populate_missing_data + populate_missing_data, + get_team_statbotics, + statbotics_quantile, ) @@ -37,7 +37,6 @@ def __init__(self): self.calculated_stats = CalculatedStats( retrieve_scouting_data() ) - # self.pit_scouting_data = retrieve_pit_scouting_data() def generate_input_section(self) -> int: """Creates the input section for the `Teams` page. @@ -46,7 +45,7 @@ def generate_input_section(self) -> int: :return: The team number selected to create graphs for. """ - queried_team = int(st.experimental_get_query_params().get("team_number", [0])[0]) or 4099 + queried_team = int(st.query_params.get("team_number", 0)) or 4099 return st.selectbox( "Team Number", (team_list := retrieve_team_list()), @@ -58,284 +57,346 @@ def generate_metrics(self, team_number: int) -> None: :param team_number: The team number to calculate the metrics for. """ - points_contributed_col, points_scaled_col, accuracy_col = st.columns(3) - iqr_col, climbs_col, disables_col = st.columns(3) - all_scouting_data = retrieve_scouting_data() - tba_matches = retrieve_match_data_raw() - tba_match_lookup = { - f"{match['comp_level']}{match['match_number']}": match - for match in tba_matches - if match.get("score_breakdown") is not None - } - tba_scaled_points_by_team = {} - tba_accuracy_by_team = {} - tba_scaled_points_by_team_match = {} - team_scouted_points_by_team_match = {} - regular_points_by_team_match = {} - - def _as_float(value) -> float: - try: - return float(value) - except (TypeError, ValueError): - return 0.0 - - def _row_points(row) -> float: - magazine_size = _as_float(row.get(Queries.MAGAZINE_SIZE)) - auto_points = ( - _as_float(row.get(Queries.AUTO_SINGULAR_COUNT)) - + (_as_float(row.get(Queries.AUTO_BATCH_COUNT)) * magazine_size) - + (Criteria.BOOLEAN_CRITERIA.get(row.get(Queries.AUTO_CLIMB), 0) * 15) + driver_rating_col, throughput_col, climb_rate_col = st.columns(3) + auto_climb_col, disabled_col, shoot_move_col = st.columns(3) + + with driver_rating_col: + avg_driver = self.calculated_stats.average_driver_rating(team_number) + driver_percentile = self.calculated_stats.quantile_stat( + 0.5, lambda self, team: self.average_driver_rating(team) ) - teleop_points = ( - _as_float(row.get(Queries.TELEOP_SINGULAR_COUNT)) - + (_as_float(row.get(Queries.TELEOP_BATCH_COUNT)) * magazine_size) + colored_metric( + "Avg. Driver Rating (1–5)", + round(avg_driver, 2), + threshold=driver_percentile ) - climb_points = Criteria.CLIMBING_CRITERIA.get(row.get(Queries.TELEOP_CLIMB), 0) * 10 - return auto_points + teleop_points + climb_points - - def _team_scouted_and_scaled_points_for_match( - queried_team: int, - row - ) -> tuple[float | None, float | None, float | None]: - match_key = row[Queries.MATCH_KEY] - alliance = str(row.get("Alliance", "")).lower() - cache_key = (queried_team, match_key, alliance) - if cache_key in tba_scaled_points_by_team_match: - return ( - team_scouted_points_by_team_match.get(cache_key), - tba_scaled_points_by_team_match[cache_key], - regular_points_by_team_match.get(cache_key) - ) - - if alliance not in ("red", "blue"): - team_scouted_points_by_team_match[cache_key] = None - tba_scaled_points_by_team_match[cache_key] = None - regular_points_by_team_match[cache_key] = None - return (None, None, None) - - match = tba_match_lookup.get(match_key) - if match is None: - team_scouted_points_by_team_match[cache_key] = None - tba_scaled_points_by_team_match[cache_key] = None - regular_points_by_team_match[cache_key] = None - return (None, None, None) - - alliance_score_breakdown = match["score_breakdown"][alliance] - alliance_non_foul_points = alliance_score_breakdown["totalPoints"] - alliance_score_breakdown["foulPoints"] - regular_points = alliance_non_foul_points / 3 - - alliance_rows = all_scouting_data[ - (all_scouting_data[Queries.MATCH_KEY] == match_key) - & (all_scouting_data["Alliance"].str.lower() == alliance) - ].copy() - - if alliance_rows.empty: - team_scouted_points_by_team_match[cache_key] = _row_points(row) - tba_scaled_points_by_team_match[cache_key] = None - regular_points_by_team_match[cache_key] = regular_points - return (team_scouted_points_by_team_match[cache_key], None, regular_points) - - alliance_rows["scouted_points"] = alliance_rows.apply(_row_points, axis=1) - team_points = alliance_rows.groupby(Queries.TEAM_NUMBER)["scouted_points"].mean() - alliance_total_scouted_points = float(team_points.sum()) - queried_team_scouted_points = float(team_points.get(queried_team, _row_points(row))) - all_three_teams_recorded = len(team_points.index) == 3 - - if not all_three_teams_recorded: - scaled_points = None - elif alliance_total_scouted_points == 0: - scaled_points = 0.0 - else: - contribution_ratio = queried_team_scouted_points / alliance_total_scouted_points - scaled_points = alliance_non_foul_points * contribution_ratio - - team_scouted_points_by_team_match[cache_key] = queried_team_scouted_points - tba_scaled_points_by_team_match[cache_key] = scaled_points - regular_points_by_team_match[cache_key] = regular_points - return (queried_team_scouted_points, scaled_points, regular_points) - - def _average_tba_scaled_points(queried_team: int) -> float: - if queried_team in tba_scaled_points_by_team: - return tba_scaled_points_by_team[queried_team] - - queried_team_data = scouting_data_for_team(queried_team).reset_index(drop=True) - scaled_points = [] - for _, row in queried_team_data.iterrows(): - _, tba_scaled_points, _ = _team_scouted_and_scaled_points_for_match(queried_team, row) - if tba_scaled_points is not None: - scaled_points.append(tba_scaled_points) - - average_scaled_points = (sum(scaled_points) / len(scaled_points)) if scaled_points else 0 - tba_scaled_points_by_team[queried_team] = average_scaled_points - return average_scaled_points - - def _average_tba_accuracy(queried_team: int) -> float: - if queried_team in tba_accuracy_by_team: - return tba_accuracy_by_team[queried_team] - - queried_team_data = scouting_data_for_team(queried_team).reset_index(drop=True) - accuracies = [] - - for _, row in queried_team_data.iterrows(): - scouted_points, tba_scaled_points, regular_points = _team_scouted_and_scaled_points_for_match(queried_team, row) - if scouted_points is None: - continue - - points_for_accuracy = tba_scaled_points if tba_scaled_points is not None else regular_points - if points_for_accuracy is None: - continue - if points_for_accuracy == 0: - accuracies.append(0.0 if scouted_points == 0 else 1.0) - else: - accuracies.append(abs((scouted_points - points_for_accuracy) / points_for_accuracy)) - - average_accuracy = (sum(accuracies) / len(accuracies)) if accuracies else 0 - tba_accuracy_by_team[queried_team] = average_accuracy - return average_accuracy - # Metric for avg. points contributed (fuel scored). - with points_contributed_col: - average_points_contributed = self.calculated_stats.average_points_contributed(team_number) - points_contributed_for_percentile = self.calculated_stats.quantile_stat( - 0.5, lambda self, team: self.average_points_contributed(team) + with throughput_col: + avg_throughput = self.calculated_stats.average_throughput_speed(team_number) + throughput_percentile = self.calculated_stats.quantile_stat( + 0.5, lambda self, team: self.average_throughput_speed(team) ) colored_metric( - "Average Points Contributed (Fuel Scored)", - round(average_points_contributed, 2), - threshold=points_contributed_for_percentile + "Avg. Throughput Speed (1–5)", + round(avg_throughput, 2), + threshold=throughput_percentile ) - # Metric for scaled points contributed compared to TBA. - with points_scaled_col: - scaled_points = _average_tba_scaled_points(team_number) - scaled_points_for_percentile = self.calculated_stats.quantile_stat( - 0.5, lambda self, team: _average_tba_scaled_points(team) + pct_formatter = lambda v: f"{round(v * 100, 1)}%" + + with climb_rate_col: + climb_rate = self.calculated_stats.teleop_climb_rate(team_number) + climb_rate_percentile = self.calculated_stats.quantile_stat( + 0.5, lambda self, team: self.teleop_climb_rate(team) ) colored_metric( - "Average Points Contributed, Scaled", - round(scaled_points, 2), - threshold=scaled_points_for_percentile + "Teleop Climb Rate", + climb_rate, + threshold=climb_rate_percentile, + value_formatter=pct_formatter ) - # Metric for average scoring accuracy compared to TBA. - with accuracy_col: - average_accuracy = _average_tba_accuracy(team_number) - accuracy_for_percentile = self.calculated_stats.quantile_stat( - 0.5, lambda self, team: _average_tba_accuracy(team) + with auto_climb_col: + auto_climb_rate = self.calculated_stats.auto_climb_rate(team_number) + auto_climb_percentile = self.calculated_stats.quantile_stat( + 0.5, lambda self, team: self.auto_climb_rate(team) ) colored_metric( - "Average Accuracy", - round(average_accuracy, 2), - threshold=accuracy_for_percentile + "Auto Climb Rate", + auto_climb_rate, + threshold=auto_climb_percentile, + value_formatter=pct_formatter ) - # Metric for IQR of points contributed (consistency). - with iqr_col: - points_by_match = self.calculated_stats.points_contributed_by_match(team_number) - iqr_of_points_contributed = self.calculated_stats.calculate_iqr(points_by_match) - iqr_for_percentile = self.calculated_stats.quantile_stat( - 0.5, lambda self, team: self.calculate_iqr(self.points_contributed_by_match(team)) + with disabled_col: + disabled_rate = self.calculated_stats.disabled_rate(team_number) + disabled_percentile = self.calculated_stats.quantile_stat( + 0.5, lambda self, team: self.disabled_rate(team) ) colored_metric( - "IQR", - round(iqr_of_points_contributed, 2), - threshold=iqr_for_percentile, - invert_threshold=True + "Disabled Rate", + disabled_rate, + threshold=disabled_percentile, + invert_threshold=True, + value_formatter=pct_formatter ) - # Metric for number of times robot climbed. - with climbs_col: - times_climbed = self.calculated_stats.cumulative_stat( - team_number, Queries.TELEOP_CLIMB, {"L1": 1, "L2": 1, "L3": 1} + with shoot_move_col: + sotm_rate = self.calculated_stats.shoot_on_the_move_rate(team_number) + sotm_percentile = self.calculated_stats.quantile_stat( + 0.5, lambda self, team: self.shoot_on_the_move_rate(team) ) - times_climbed_for_percentile = self.calculated_stats.quantile_stat( - 0.5, - lambda self, team: self.cumulative_stat( - team, Queries.TELEOP_CLIMB, {"L1": 1, "L2": 1, "L3": 1} - ) + colored_metric( + "Shoot-on-the-Move Rate", + sotm_rate, + threshold=sotm_percentile, + value_formatter=pct_formatter ) + + def generate_quantitative_metrics(self, team_number: int) -> None: + """Creates Statbotics EPA metrics for the `Teams` page. + + Displays pre-event EPA estimates from Statbotics alongside the qualitative + scouting metrics. When online the data is fetched and cached locally so + the section remains usable offline. + + :param team_number: The team number to display EPA metrics for. + """ + epa = get_team_statbotics(team_number) + + if not epa: + st.info("No quantitative EPA data available for this team.") + return + + pt_formatter = lambda v: f"{v:.1f}" + + total_col, auto_fuel_col, tele_end_fuel_col, tower_col = st.columns(4) + + with total_col: colored_metric( - "# of Times Climbed", - times_climbed, - threshold=times_climbed_for_percentile, + "Total EPA", + epa.get("total_epa", 0), + threshold=statbotics_quantile("total_epa"), + value_formatter=pt_formatter, ) - # Metric for number of disables. - with disables_col: - times_disabled = self.calculated_stats.cumulative_stat( - team_number, Queries.DISABLE, Criteria.BOOLEAN_CRITERIA + with auto_fuel_col: + colored_metric( + "Auto Fuel", + epa.get("auto_fuel", 0), + threshold=statbotics_quantile("auto_fuel"), + value_formatter=pt_formatter, ) - times_disabled_for_percentile = self.calculated_stats.quantile_stat( - 0.5, lambda self, team: self.cumulative_stat(team, Queries.DISABLE, Criteria.BOOLEAN_CRITERIA) + + with tele_end_fuel_col: + colored_metric( + "Teleop + Endgame Fuel", + epa.get("teleop_endgame_fuel", 0), + threshold=statbotics_quantile("teleop_endgame_fuel"), + value_formatter=pt_formatter, ) + + with tower_col: colored_metric( - "# of Times Disabled", - times_disabled, - threshold=times_disabled_for_percentile, - invert_threshold=True + "Tower Points", + epa.get("tower_points", 0), + threshold=statbotics_quantile("tower_points"), + value_formatter=pt_formatter, ) def generate_autonomous_graphs( self, team_number: int, - type_of_graph: GraphType + type_of_graph: GraphType = GraphType.RATING_CONTRIBUTIONS ) -> None: """Generates the autonomous graphs for the `Team` page. :param team_number: The team to generate the graphs for. - :param type_of_graph: The type of graph to use for the graphs on said page (fuel scored / point contributions). - :return: + :param type_of_graph: Unused; kept for API compatibility. """ - points_by_match = self.calculated_stats.points_contributed_by_match(team_number) - - plotly_chart( - line_graph( - range(len(points_by_match)), - points_by_match, - x_axis_label="Match Index", - y_axis_label=( - "Points Contributed" - if type_of_graph == GraphType.POINT_CONTRIBUTIONS - else "Fuel Scored" - ), - title="Points vs Match Index", + scouting_data = scouting_data_for_team(team_number) + + auto_climb_col, scoring_side_col = st.columns(2) + trench_bump_col, _ = st.columns(2) + + with auto_climb_col: + climb_counts = {"Climbed": 0, "Did Not Climb": 0} + for val in scouting_data[Queries.AUTO_CLIMB]: + if Criteria.BOOLEAN_CRITERIA.get(val, 0): + climb_counts["Climbed"] += 1 + else: + climb_counts["Did Not Climb"] += 1 + + plotly_chart( + bar_graph( + list(climb_counts.keys()), + list(climb_counts.values()), + x_axis_label="Result", + y_axis_label="# of Matches", + title="Auto Climb", + color={ + "Climbed": GeneralConstants.LIGHT_GREEN, + "Did Not Climb": GeneralConstants.LIGHT_RED, + }, + color_indicator="Result" + ) ) - ) + + with scoring_side_col: + all_sides = [] + for sides in scouting_data[Queries.AUTO_SCORING_SIDE]: + if isinstance(sides, list): + all_sides.extend(sides) + elif isinstance(sides, str) and sides: + all_sides.append(sides) + + side_counts: dict[str, int] = {} + for side in all_sides: + side_counts[side] = side_counts.get(side, 0) + 1 + + if side_counts: + sorted_sides = dict(sorted(side_counts.items(), key=lambda x: x[1], reverse=True)) + plotly_chart( + bar_graph( + list(sorted_sides.keys()), + list(sorted_sides.values()), + x_axis_label="Scoring Side", + y_axis_label="# of Occurrences", + title="Auto Scoring Sides", + color=GeneralConstants.GOLD_GRADIENT[0] + ) + ) + else: + st.info("No auto scoring side data available.") + + with trench_bump_col: + all_paths = [] + for paths in scouting_data[Queries.AUTO_TRENCH_BUMP]: + if isinstance(paths, list): + all_paths.extend(paths) + elif isinstance(paths, str) and paths: + all_paths.append(paths) + + path_counts: dict[str, int] = {} + for path in all_paths: + path_counts[path] = path_counts.get(path, 0) + 1 + + if path_counts: + sorted_paths = dict(sorted(path_counts.items(), key=lambda x: x[1], reverse=True)) + plotly_chart( + bar_graph( + list(sorted_paths.keys()), + list(sorted_paths.values()), + x_axis_label="Path", + y_axis_label="# of Occurrences", + title="Auto Trench / Bump Usage", + color=GeneralConstants.BLUE_ALLIANCE_GRADIENT[1] + ) + ) + else: + st.info("No auto trench/bump path data available.") def generate_teleop_graphs( self, team_number: int, - type_of_graph: GraphType + type_of_graph: GraphType = GraphType.RATING_CONTRIBUTIONS ) -> None: - """Generates the teleop graphs for the `Team` page. + """Generates the teleop + endgame graphs for the `Team` page. :param team_number: The team to generate the graphs for. - :param type_of_graph: The type of graph to use for the graphs on said page (fuel scored / point contributions). - :return: + :param type_of_graph: Unused; kept for API compatibility. """ - climb_points_by_match = self.calculated_stats.stat_per_match( - team_number, - Queries.TELEOP_CLIMB, - Criteria.CLIMBING_POINTAGE - ) + scouting_data = scouting_data_for_team(team_number) - plotly_chart( - line_graph( - range(len(climb_points_by_match)), - climb_points_by_match, - x_axis_label="Match Index", - y_axis_label="Climb Points", - title="Climb Pts vs Match Index", + climb_level_col, climb_speed_col = st.columns(2) + scoring_side_col, trench_bump_col = st.columns(2) + + with climb_level_col: + climb_order = ["L3", "L2", "L1", "No climb"] + climb_counts = {level: int((scouting_data[Queries.TELEOP_CLIMB] == level).sum()) for level in climb_order} + level_colors = dict(zip(climb_order, [ + GeneralConstants.LEVEL_GRADIENT[2], + GeneralConstants.LEVEL_GRADIENT[1], + GeneralConstants.LEVEL_GRADIENT[0], + GeneralConstants.LIGHT_RED + ])) + plotly_chart( + bar_graph( + list(climb_counts.keys()), + list(climb_counts.values()), + x_axis_label="Climb Level", + y_axis_label="# of Matches", + title="Teleop Climb Level Breakdown", + color=level_colors, + color_indicator="Climb Level" + ) ) - ) + + with climb_speed_col: + speed_order = ["<5 seconds", "5-10 seconds", "10-20 seconds", ">20 seconds"] + speed_counts = { + speed: int((scouting_data[Queries.CLIMB_SPEED] == speed).sum()) + for speed in speed_order + } + # Only show non-zero rows + speed_counts = {k: v for k, v in speed_counts.items() if v > 0} + if speed_counts: + plotly_chart( + bar_graph( + list(speed_counts.keys()), + list(speed_counts.values()), + x_axis_label="Climb Speed", + y_axis_label="# of Matches", + title="Climb Speed Breakdown", + color=dict(zip( + speed_counts.keys(), + GeneralConstants.RED_TO_GREEN_GRADIENT[:len(speed_counts)][::-1] + )), + color_indicator="Climb Speed" + ) + ) + else: + st.info("No climb speed data recorded.") + + with scoring_side_col: + all_sides = [] + for sides in scouting_data[Queries.TELEOP_SCORING_SIDE]: + if isinstance(sides, list): + all_sides.extend(sides) + elif isinstance(sides, str) and sides: + all_sides.append(sides) + + side_counts: dict[str, int] = {} + for side in all_sides: + side_counts[side] = side_counts.get(side, 0) + 1 + + if side_counts: + sorted_sides = dict(sorted(side_counts.items(), key=lambda x: x[1], reverse=True)) + plotly_chart( + bar_graph( + list(sorted_sides.keys()), + list(sorted_sides.values()), + x_axis_label="Scoring Side", + y_axis_label="# of Occurrences", + title="Teleop Scoring Sides", + color=GeneralConstants.GOLD_GRADIENT[1] + ) + ) + else: + st.info("No teleop scoring side data available.") + + with trench_bump_col: + all_paths = [] + for paths in scouting_data[Queries.TELEOP_TRENCH_BUMP]: + if isinstance(paths, list): + all_paths.extend(paths) + elif isinstance(paths, str) and paths: + all_paths.append(paths) + + path_counts: dict[str, int] = {} + for path in all_paths: + path_counts[path] = path_counts.get(path, 0) + 1 + + if path_counts: + sorted_paths = dict(sorted(path_counts.items(), key=lambda x: x[1], reverse=True)) + plotly_chart( + bar_graph( + list(sorted_paths.keys()), + list(sorted_paths.values()), + x_axis_label="Path", + y_axis_label="# of Occurrences", + title="Teleop Trench / Bump Usage", + color=GeneralConstants.BLUE_ALLIANCE_GRADIENT[2] + ) + ) + else: + st.info("No teleop trench/bump path data available.") def generate_qualitative_graphs(self, team_number: int) -> None: """Generates the qualitative graphs for the `Team` page. :param team_number: The team to generate the graphs for. - :return: """ - # Constants used for the sentiment analysis ml_weight = 1 estimate_weight = 1 @@ -343,120 +404,167 @@ def generate_qualitative_graphs(self, team_number: int) -> None: positivity_scores = [] scouting_data = scouting_data_for_team(team_number) - # Split into two tabs qualitative_graphs_tab, note_scouting_analysis_tab = st.tabs( ["📊 Qualitative Graphs", "✏️ Note Scouting Analysis"] ) with qualitative_graphs_tab: - driver_rating_col, intake_defense_skill_col = st.columns(2) - intake_speed_col, defense_skill_col = st.columns(2) - - with driver_rating_col: - driver_rating_types = Criteria.DRIVER_RATING_CRITERIA.keys() - driver_rating_by_type = [ - self.calculated_stats.cumulative_stat(team_number, Queries.DRIVER_RATING, {driver_rating_type: 1}) - for driver_rating_type in driver_rating_types + row1_left, row1_right = st.columns(2) + row2_left, row2_right = st.columns(2) + row3_left, row3_right = st.columns(2) + row4_col, _ = st.columns(2) + + with row1_left: + driver_rating_types = list(Criteria.DRIVER_RATING_CRITERIA.keys()) + driver_counts = [ + self.calculated_stats.cumulative_stat(team_number, Queries.DRIVER_RATING, {t: 1}) + for t in driver_rating_types ] - - plotly_chart( - bar_graph( - driver_rating_types, - driver_rating_by_type, - x_axis_label="Driver Rating", - y_axis_label="# of Occurrences", - title="Driver Rating Breakdown", - color=dict(zip(driver_rating_types, GeneralConstants.RED_TO_GREEN_GRADIENT[::-1])), - color_indicator="Driver Rating" - ) - ) - - with intake_defense_skill_col: - intake_defense_skill_types = Criteria.BASIC_RATING_CRITERIA.keys() - intake_defense_skill_by_type = [ - self.calculated_stats.cumulative_stat(team_number, Queries.INTAKE_DEFENSE_RATING, {intake_defense_skill_type: 1}) - for intake_defense_skill_type in intake_defense_skill_types + plotly_chart(bar_graph( + driver_rating_types, driver_counts, + x_axis_label="Driver Rating", y_axis_label="# of Matches", + title="Driver Rating Breakdown", + color=dict(zip(driver_rating_types, GeneralConstants.RED_TO_GREEN_GRADIENT[::-1])), + color_indicator="Driver Rating" + )) + + with row1_right: + throughput_types = list(Criteria.BASIC_RATING_CRITERIA.keys()) + throughput_counts = [ + self.calculated_stats.cumulative_stat(team_number, Queries.THROUGHPUT_SPEED, {t: 1}) + for t in throughput_types ] - - plotly_chart( - bar_graph( - intake_defense_skill_types, - intake_defense_skill_by_type, - x_axis_label="Counter Defense Skill", - y_axis_label="# of Occurrences", - title="Counter Defense Skill Breakdown", - color=dict(zip(intake_defense_skill_types, GeneralConstants.RED_TO_GREEN_GRADIENT[::-1])), - color_indicator="Counter Defense Skill" - ) - ) - - with defense_skill_col: - defense_skill_types = Criteria.BASIC_RATING_CRITERIA.keys() - defense_skill_by_type = [ - self.calculated_stats.cumulative_stat(team_number, Queries.DEFENSE_RATING, {defense_skill_type: 1}) - for defense_skill_type in defense_skill_types + plotly_chart(bar_graph( + throughput_types, throughput_counts, + x_axis_label="Throughput Speed", y_axis_label="# of Matches", + title="Throughput Speed Breakdown", + color=dict(zip(throughput_types, GeneralConstants.RED_TO_GREEN_GRADIENT[::-1])), + color_indicator="Throughput Speed" + )) + + with row2_left: + intake_types = list(Criteria.INTAKE_SPEED_CRITERIA.keys()) + intake_counts = [ + self.calculated_stats.cumulative_stat(team_number, Queries.INTAKE_SPEED, {t: 1}) + for t in intake_types ] - - plotly_chart( - bar_graph( - defense_skill_types, - defense_skill_by_type, - x_axis_label="Defense Skill", - y_axis_label="# of Occurrences", - title="Defense Skill Breakdown", - color=dict(zip(defense_skill_types, GeneralConstants.RED_TO_GREEN_GRADIENT[::-1])), - color_indicator="Defense Skill" - ) - ) - - with intake_speed_col: - intake_speed_types = Criteria.INTAKE_SPEED_CRITERIA.keys() - intake_speed_by_type = [ - self.calculated_stats.cumulative_stat(team_number, Queries.INTAKE_SPEED, {intake_speed_type: 1}) - for intake_speed_type in intake_speed_types + plotly_chart(bar_graph( + intake_types, intake_counts, + x_axis_label="Intake Speed", y_axis_label="# of Matches", + title="Intake Speed Breakdown", + color=dict(zip(intake_types, GeneralConstants.RED_TO_GREEN_GRADIENT[::-1])), + color_indicator="Intake Speed" + )) + + with row2_right: + defense_types = list(Criteria.BASIC_RATING_CRITERIA.keys()) + defense_counts = [ + self.calculated_stats.cumulative_stat(team_number, Queries.DEFENSE_RATING, {t: 1}) + for t in defense_types ] + plotly_chart(bar_graph( + defense_types, defense_counts, + x_axis_label="Defense Rating", y_axis_label="# of Matches", + title="Defense Rating Breakdown", + color=dict(zip(defense_types, GeneralConstants.RED_TO_GREEN_GRADIENT[::-1])), + color_indicator="Defense Rating" + )) + + with row3_left: + intake_defense_types = list(Criteria.BASIC_RATING_CRITERIA.keys()) + intake_defense_counts = [ + self.calculated_stats.cumulative_stat(team_number, Queries.INTAKE_DEFENSE_RATING, {t: 1}) + for t in intake_defense_types + ] + plotly_chart(bar_graph( + intake_defense_types, intake_defense_counts, + x_axis_label="Intake Defense Rating", y_axis_label="# of Matches", + title="Intake Defense Rating Breakdown", + color=dict(zip(intake_defense_types, GeneralConstants.RED_TO_GREEN_GRADIENT[::-1])), + color_indicator="Intake Defense Rating" + )) + + with row3_right: + shooter_defense_types = list(Criteria.BASIC_RATING_CRITERIA.keys()) + shooter_defense_counts = [ + self.calculated_stats.cumulative_stat(team_number, Queries.SHOOTER_DEFENSE_RATING, {t: 1}) + for t in shooter_defense_types + ] + plotly_chart(bar_graph( + shooter_defense_types, shooter_defense_counts, + x_axis_label="Shooter Defense Rating", y_axis_label="# of Matches", + title="Shooter Defense Rating Breakdown", + color=dict(zip(shooter_defense_types, GeneralConstants.RED_TO_GREEN_GRADIENT[::-1])), + color_indicator="Shooter Defense Rating" + )) + + with row4_col: + stability_types = list(Criteria.STABILITY_CRITERIA.keys()) + stability_counts = [ + int((scouting_data[Queries.STABILITY] == t).sum()) + for t in stability_types + ] + stability_colors = { + "Stable": GeneralConstants.LIGHT_GREEN, + "Moderately tippy": GeneralConstants.GOLD_GRADIENT[0], + "Very tippy": GeneralConstants.LIGHT_RED, + } + plotly_chart(bar_graph( + stability_types, stability_counts, + x_axis_label="Stability", y_axis_label="# of Matches", + title="Stability Rating Breakdown", + color=stability_colors, + color_indicator="Stability" + )) + + # Robot style type breakdown (list field — flatten across all matches) + st.divider() + st.write("##### Robot Style Breakdown") + all_styles = [] + for styles in scouting_data[Queries.ROBOT_STYLE_TYPE]: + if isinstance(styles, list): + all_styles.extend(styles) + elif isinstance(styles, str) and styles: + all_styles.append(styles) + style_counts: dict[str, int] = {} + for style in all_styles: + style_counts[style] = style_counts.get(style, 0) + 1 + if style_counts: + sorted_styles = dict(sorted(style_counts.items(), key=lambda x: x[1], reverse=True)) + plotly_chart(bar_graph( + list(sorted_styles.keys()), + list(sorted_styles.values()), + x_axis_label="Robot Style", + y_axis_label="# of Occurrences", + title="Robot Style Type Distribution", + color=GeneralConstants.PRIMARY_COLOR + )) + else: + st.info("No robot style type data available.") - plotly_chart( - bar_graph( - intake_speed_types, - intake_speed_by_type, - x_axis_label="Intake Speed", - y_axis_label="# of Occurrences", - title="Intake Speed Breakdown", - color=dict(zip(intake_speed_types, GeneralConstants.RED_TO_GREEN_GRADIENT[::-1])), - color_indicator="Intake Speed" - ) - ) with note_scouting_analysis_tab: notes_col, metrics_col = st.columns(2, gap="medium") notes_by_match = dict( zip( scouting_data[Queries.MATCH_KEY], ( - scouting_data[Queries.AUTO_NOTES].apply(lambda note: (note + " ").lower() if note else "") + - scouting_data[Queries.TELEOP_NOTES].apply( - lambda note: (note + " ").lower() if note else "") + - scouting_data[Queries.ENDGAME_NOTES].apply( - lambda note: (note + " ").lower() if note else "") + - scouting_data[Queries.RATING_NOTES].apply(lambda note: note.lower()) - + scouting_data[Queries.AUTO_NOTES].apply(lambda n: (n + " ").lower() if n else "") + + scouting_data[Queries.TELEOP_NOTES].apply(lambda n: (n + " ").lower() if n else "") + + scouting_data[Queries.RATING_NOTES].apply(lambda n: n.lower() if n else "") ) ) ) with notes_col: st.write("##### Notes") - st.markdown("