Skip to content

Commit 978cb8b

Browse files
Merge pull request #35 from team4099/2025-backend-revamp
2025 backend revamp
2 parents c96d62a + 4a9959a commit 978cb8b

File tree

5 files changed

+176
-1
lines changed

5 files changed

+176
-1
lines changed

src/page_managers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
from .match_manager import MatchManager
44
from .picklist_manager import PicklistManager
55
from .ranking_simulator_manager import RankingSimulatorManager
6+
from .scouting_accuracy_manager import ScoutingAccuracyManager
67
from .team_manager import TeamManager
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
"""Creates the `ScoutingAccuracyManager` class used to set up the Scouting Accuracy page and generate its table."""
2+
import pandas as pd
3+
import streamlit as st
4+
from .page_manager import PageManager
5+
from utils import (
6+
CalculatedStats,
7+
EventSpecificConstants,
8+
Queries,
9+
retrieve_scouting_data,
10+
retrieve_match_schedule,
11+
retrieve_match_data
12+
)
13+
import requests
14+
import os
15+
from dotenv import load_dotenv
16+
from pandas import DataFrame
17+
18+
load_dotenv()
19+
20+
21+
class ScoutingAccuracyManager(PageManager):
22+
"""The scouting accuracy page manager for the `Scouting Accuracy` page."""
23+
def __init__(self):
24+
self.calculated_stats = CalculatedStats(retrieve_scouting_data())
25+
self.raw_scouting_data = retrieve_scouting_data()
26+
self.match_schedule = retrieve_match_schedule()
27+
self.match_data = retrieve_match_data()
28+
29+
def generate_input_section(self) -> str:
30+
"""Generates the input section of the `Scouting Accuracy` page.
31+
32+
Provides a text box for the user to input a custom string value.
33+
34+
:return: Returns the string entered by the user.
35+
"""
36+
37+
return st.text_input(
38+
"Enter the name of the member",
39+
placeholder="Type here..."
40+
)
41+
42+
def generate_accuracy_table(self, member_name) -> DataFrame:
43+
"""Generates the scouting accuracy table for the `Scouting Accuracy` page."""
44+
45+
accuracy_dict = {
46+
'ScoutersNames': [],
47+
'CumulativeAccuracy': [],
48+
'NumberOfScoutedMatches': []
49+
}
50+
51+
for index, row in self.match_data.iterrows():
52+
match_key = row["match_key"]
53+
red_alliance = row["red_alliance"]
54+
blue_alliance = row["blue_alliance"]
55+
headers = {
56+
"X-TBA-Auth-Key": os.getenv("HEADERS")
57+
}
58+
59+
scouting_match_filter = self.raw_scouting_data[self.raw_scouting_data[Queries.MATCH_KEY] == match_key]
60+
61+
# Red alliance score from TBA
62+
team_list = red_alliance.split(",")
63+
red_tba_matches = requests.get(f"https://www.thebluealliance.com/api/v3/team/frc{team_list[0]}/event/{EventSpecificConstants.EVENT_CODE}/matches", headers=headers).json()
64+
for match in red_tba_matches:
65+
if (match["comp_level"] + str(match["match_number"])) == match_key:
66+
red_total_score = match["score_breakdown"]["red"]["totalPoints"]
67+
red_foul_score = match["score_breakdown"]["red"]["foulPoints"]
68+
red_calculated_score = red_total_score - red_foul_score
69+
break
70+
71+
red_scouting_alliance_score = 0
72+
73+
scouters_names_list_r = []
74+
75+
for team_key in team_list:
76+
scouting_team_filter = self.raw_scouting_data[self.raw_scouting_data[Queries.TEAM_NUMBER] == int(team_key)]
77+
scouting_team_filter = scouting_team_filter.reset_index(drop=True)
78+
match_index_list = scouting_team_filter.index[scouting_team_filter[Queries.MATCH_KEY] == match_key].tolist()
79+
if len(match_index_list) != 0:
80+
match_index = match_index_list[0]
81+
points_per_match = self.calculated_stats.points_contributed_by_match(int(team_key)).values
82+
red_scouting_alliance_score += points_per_match[match_index]
83+
scout_name = scouting_team_filter.iloc[match_index][Queries.SCOUT_ID]
84+
scouters_names_list_r.append(scout_name.title().replace(" ", ""))
85+
86+
red_alliance_accuracy = (1 - abs((red_scouting_alliance_score-red_calculated_score)/red_calculated_score)) * 100
87+
scouters_names = ", ".join(scouters_names_list_r)
88+
89+
if member_name.replace(" ", "").lower() in scouters_names.lower():
90+
if scouters_names not in accuracy_dict['ScoutersNames']:
91+
accuracy_dict['ScoutersNames'].append(scouters_names)
92+
accuracy_dict['CumulativeAccuracy'].append(red_alliance_accuracy)
93+
accuracy_dict['NumberOfScoutedMatches'].append(1)
94+
else:
95+
accuracy_scouts_index = accuracy_dict['ScoutersNames'].index(scouters_names)
96+
accuracy_dict['CumulativeAccuracy'][accuracy_scouts_index] += red_alliance_accuracy
97+
accuracy_dict['NumberOfScoutedMatches'][accuracy_scouts_index] += 1
98+
99+
# Blue Alliance score from TBA
100+
team_list = blue_alliance.split(",")
101+
blue_tba_matches = requests.get(f"https://www.thebluealliance.com/api/v3/team/frc{team_list[0]}/event/{EventSpecificConstants.EVENT_CODE}/matches", headers=headers).json()
102+
for match in blue_tba_matches:
103+
if (match["comp_level"] + str(match["match_number"])) == match_key:
104+
blue_total_score = match["score_breakdown"]["blue"]["totalPoints"]
105+
blue_foul_score = match["score_breakdown"]["blue"]["foulPoints"]
106+
blue_calculated_score = blue_total_score - blue_foul_score
107+
break
108+
109+
blue_scouting_alliance_score = 0
110+
111+
scouters_names_list_b = []
112+
113+
for team_key in blue_alliance.split(","):
114+
scouting_team_filter = self.raw_scouting_data[self.raw_scouting_data[Queries.TEAM_NUMBER] == int(team_key)]
115+
scouting_team_filter = scouting_team_filter.reset_index(drop=True)
116+
match_index_list = scouting_team_filter.index[scouting_team_filter[Queries.MATCH_KEY] == match_key].tolist()
117+
if len(match_index_list) != 0:
118+
match_index = match_index_list[0]
119+
points_per_match = self.calculated_stats.points_contributed_by_match(int(team_key)).values
120+
blue_scouting_alliance_score += points_per_match[match_index]
121+
scout_name = scouting_team_filter.iloc[match_index][Queries.SCOUT_ID]
122+
scouters_names_list_b.append(scout_name.title().replace(" ", ""))
123+
124+
self.calculated_stats.points_contributed_by_match(team_key)
125+
blue_scouting_alliance_score += self.calculated_stats.points_contributed_by_match(team_key).sum()
126+
127+
blue_alliance_accuracy = (1 - abs((blue_scouting_alliance_score-blue_calculated_score)/blue_calculated_score)) * 100
128+
129+
scouters_names = ", ".join(scouters_names_list_b)
130+
if member_name.replace(" ", "").lower() in scouters_names.lower():
131+
if scouters_names not in accuracy_dict['ScoutersNames']:
132+
accuracy_dict['ScoutersNames'].append(scouters_names)
133+
accuracy_dict['CumulativeAccuracy'].append(blue_alliance_accuracy)
134+
accuracy_dict['NumberOfScoutedMatches'].append(1)
135+
else:
136+
accuracy_scouts_index = accuracy_dict['ScoutersNames'].index(scouters_names)
137+
accuracy_dict['CumulativeAccuracy'][accuracy_scouts_index] += blue_alliance_accuracy
138+
accuracy_dict['NumberOfScoutedMatches'][accuracy_scouts_index] += 1
139+
140+
df = pd.DataFrame(data={
141+
'Scouters': accuracy_dict['ScoutersNames'],
142+
'Average Accuracy %': [round(accuracy_dict['CumulativeAccuracy'][scouter_set]/accuracy_dict['NumberOfScoutedMatches'][scouter_set], 2) for scouter_set in range(len(accuracy_dict['NumberOfScoutedMatches']))],
143+
'NumberOfScoutedMatches': accuracy_dict['NumberOfScoutedMatches']
144+
})
145+
146+
return df

src/pages/7_Scouting_Accuracy.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""Page for displaying a scouting team's accuracy based on TBA data"""
2+
3+
import streamlit as st
4+
from page_managers import ScoutingAccuracyManager
5+
from pandas import DataFrame
6+
7+
# Configuration for Streamlit
8+
st.set_page_config(
9+
layout="wide",
10+
page_title="Scouting Accuracy",
11+
page_icon="🫂",
12+
)
13+
scouting_accuracy_manager = ScoutingAccuracyManager()
14+
15+
if __name__ == '__main__':
16+
# Write the name of the page.
17+
st.write("# Scouting Accuracy")
18+
19+
# Name input Section
20+
member_name = scouting_accuracy_manager.generate_input_section()
21+
22+
# Generate the Scouting Accuracy Table
23+
24+
generated_scouting_accuracy: DataFrame = scouting_accuracy_manager.generate_accuracy_table(member_name)
25+
26+
# teams, scouted, tba, accuracy ?
27+
returned_dataframe = st.dataframe(generated_scouting_accuracy, hide_index = True)

src/utils/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class Queries:
7474
"""Constants specific to fields in the scouting data."""
7575

7676
# Constants relating to fields
77+
SCOUT_ID = "ScoutId"
7778
MATCH_KEY = "MatchKey"
7879
MATCH_NUMBER = "MatchNumber"
7980
TEAM_NUMBER = "TeamNumber"

src/utils/functions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ def retrieve_match_data() -> DataFrame:
150150
"red_score": match["alliances"]["red"]["score"],
151151
"blue_score": match["alliances"]["blue"]["score"],
152152
"reached_coop": (
153-
match["score_breakdown"]["coopertitionBonusAchieved"]
153+
match["score_breakdown"]["red"]["coopertitionCriteriaMet"]
154154
)
155155
}
156156
for match in event_matches if match["score_breakdown"] is not None

0 commit comments

Comments
 (0)