mirror of
				https://github.com/MoonlitJolteon/frc-stat-predictor.git
				synced 2025-11-03 22:25:02 +00:00 
			
		
		
		
	Compare commits
	
		
			1 Commits
		
	
	
		
			94084084f3
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						ef80aa825d
	
				 | 
					
					
						
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -1,2 +1,4 @@
 | 
				
			|||||||
config.json
 | 
					config.json
 | 
				
			||||||
*/**/__pycache__
 | 
					*/**/__pycache__
 | 
				
			||||||
 | 
					.venv
 | 
				
			||||||
 | 
					__main__
 | 
				
			||||||
							
								
								
									
										44
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					## Installation and Setup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1.  **Prerequisites:**
 | 
				
			||||||
 | 
					    *   Python 3.8+
 | 
				
			||||||
 | 
					    *   [Ollama](https://ollama.com/)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					2.  **Clone the Repository:** (If you have not already, skip if you have the file contents)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					3.  **Create a Virtual Environment:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					    python -m venv .venv
 | 
				
			||||||
 | 
					    source venv/bin/activate # On Windows use `venv\Scripts\activate`
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					4.  **Install Dependencies:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					    pip install -r requirements.txt
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					5.  **Set up Ollama:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    *   Download and install Ollama from [https://ollama.com/](https://ollama.com/).
 | 
				
			||||||
 | 
					    *   Run `ollama pull <model_name>` to download the desired language model (e.g., `ollama pull llama2`).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Configuration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1.  **Configure:**
 | 
				
			||||||
 | 
					    *   The code uses `config.json.example` as a template. Copy this file to `config.json`.
 | 
				
			||||||
 | 
					    *   Edit `config.json` to provide 
 | 
				
			||||||
 | 
					        *   The Blue Alliance (TBA) API key
 | 
				
			||||||
 | 
					        *   Indiana Scouting Alliance (ISA) API key
 | 
				
			||||||
 | 
					        *   Preferred Ollama model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Running the Application
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1.  **Run the Main Script:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					    python main.py
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					2.  [**Open the page in your browser**](http://localhost:5000)
 | 
				
			||||||
							
								
								
									
										101
									
								
								classes.puml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								classes.puml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,101 @@
 | 
				
			|||||||
 | 
					@startuml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					' Data Sources
 | 
				
			||||||
 | 
					class DataSource {
 | 
				
			||||||
 | 
					    {abstract} +get_status() : tuple[DataSourceStatus, dict]
 | 
				
			||||||
 | 
					    {abstract} +get_team_info(team_number: int)
 | 
				
			||||||
 | 
					    {abstract} +get_event_matches(event_code: str, team_number: int | None = None)
 | 
				
			||||||
 | 
					    {abstract} +get_team_performance_metrics(team_number, event_code: str | None = None)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum DataSourceStatus {
 | 
				
			||||||
 | 
					    CONNECTED
 | 
				
			||||||
 | 
					    UNAUTHENTICATED
 | 
				
			||||||
 | 
					    NOT_FOUND
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TheBlueAllianceConnector {
 | 
				
			||||||
 | 
					    +__init__(api_token: str, year: int = datetime.now().year)
 | 
				
			||||||
 | 
					    +get_status() : tuple[DataSourceStatus, dict]
 | 
				
			||||||
 | 
					    +get_team_info(team_number: int) : dict | None
 | 
				
			||||||
 | 
					    +get_event_matches(event_code: str, team_number: int | None = None) : dict | None
 | 
				
			||||||
 | 
					    +get_team_performance_metrics(team_number, event_code: str | None = None) : dict | None
 | 
				
			||||||
 | 
					    -__calculate_auto_performance(performance: dict, match_points: dict, match_record: dict, alliance_data: dict, robot_position: int) : void
 | 
				
			||||||
 | 
					    -__calculate_teleop_performance(performance: dict, match_points: dict, match_record: dict, alliance_data: dict, robot_position: int) : void
 | 
				
			||||||
 | 
					    -__calculate_endgame_performance(performance: dict, match_points: dict, match_record: dict, alliance_data: dict, robot_position: int) : void
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class IndianaScoutingAllianceConnector {
 | 
				
			||||||
 | 
					    +__init__(api_token: str, year=datetime.now().year)
 | 
				
			||||||
 | 
					    +get_status() : tuple[DataSourceStatus, dict]
 | 
				
			||||||
 | 
					    +get_event_matches(event_code: str, team_number: int | None = None)
 | 
				
			||||||
 | 
					    +get_robot_notes(team_number: int, event_code: str | None = None)
 | 
				
			||||||
 | 
					    +get_team_info(team_number: int)
 | 
				
			||||||
 | 
					    +get_team_performance_metrics(team_number, event_code: str | None = None)
 | 
				
			||||||
 | 
					    -__build_ISA_robot_url(include_flags: str, teams: list = [], event_key: str = "") : str
 | 
				
			||||||
 | 
					    -__build_ISA_human_url(include_flags: str, teams: list = [], event_key: str = "") : str
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DataSourceStatus <|.. DataSource
 | 
				
			||||||
 | 
					DataSource <|-- TheBlueAllianceConnector
 | 
				
			||||||
 | 
					DataSource <|-- IndianaScoutingAllianceConnector
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					' LLM Integration
 | 
				
			||||||
 | 
					class AllianceSelectionAssistant {
 | 
				
			||||||
 | 
					    +__init__(llm: OLLAMAConnector)
 | 
				
			||||||
 | 
					    +select_alliance(teams: list, criteria: dict) : list
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class OLLAMAConnector {
 | 
				
			||||||
 | 
					    +__init__(model_name: str)
 | 
				
			||||||
 | 
					    +generate_text(prompt: str) : str
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MatchPredictor {
 | 
				
			||||||
 | 
					    +__init__(llm: OLLAMAConnector)
 | 
				
			||||||
 | 
					    +predict_outcome(match_data: dict) : str
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TeamRatingGenerator {
 | 
				
			||||||
 | 
					    +__init__(llm: OLLAMAConnector)
 | 
				
			||||||
 | 
					    +rate_team(team_data: dict) : str
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					' Utils
 | 
				
			||||||
 | 
					class ConfigurationManager {
 | 
				
			||||||
 | 
					    +get_config(key: str) : any
 | 
				
			||||||
 | 
					    +set_config(key: str, value: any)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Logger {
 | 
				
			||||||
 | 
					    +__init__(name: str)
 | 
				
			||||||
 | 
					    +log(message: str, level: str)
 | 
				
			||||||
 | 
					    +info(message: str)
 | 
				
			||||||
 | 
					    +warning(message: str)
 | 
				
			||||||
 | 
					    +error(message: str)
 | 
				
			||||||
 | 
					    +debug(message: str)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					' Main
 | 
				
			||||||
 | 
					class FRCRatingApp {
 | 
				
			||||||
 | 
					    +__init__()
 | 
				
			||||||
 | 
					    +setup_routes() : void
 | 
				
			||||||
 | 
					    +index()
 | 
				
			||||||
 | 
					    +team_info(team_number: int)
 | 
				
			||||||
 | 
					    +run(debug: bool = True) : void
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					' Relationships
 | 
				
			||||||
 | 
					AllianceSelectionAssistant --> OLLAMAConnector : Has
 | 
				
			||||||
 | 
					MatchPredictor --> OLLAMAConnector : Has
 | 
				
			||||||
 | 
					TeamRatingGenerator --> OLLAMAConnector : Has
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FRCRatingApp --> ConfigurationManager : Uses
 | 
				
			||||||
 | 
					FRCRatingApp --> Logger : Uses
 | 
				
			||||||
 | 
					FRCRatingApp --> TheBlueAllianceConnector : Uses
 | 
				
			||||||
 | 
					FRCRatingApp --> IndianaScoutingAllianceConnector : Uses
 | 
				
			||||||
 | 
					FRCRatingApp --> AllianceSelectionAssistant : Uses
 | 
				
			||||||
 | 
					FRCRatingApp --> MatchPredictor : Uses
 | 
				
			||||||
 | 
					FRCRatingApp --> TeamRatingGenerator : Uses
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@enduml
 | 
				
			||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    "TBA_TOKEN": "Get your read API token here: https://www.thebluealliance.com/account",
 | 
					    "TBA_TOKEN": "Get your read API token here: https://www.thebluealliance.com/account",
 | 
				
			||||||
    "USE_ISA_DATA": false,
 | 
					    "USE_ISA_DATA": false,
 | 
				
			||||||
    "ISA_TOKEN": "If you are a member of the Indiana Scouting Alliance, put your token here. Reach out to the discord if you don't know how to get it",
 | 
					    "ISA_TOKEN": "If you are a member of the Indiana Scouting Alliance,put your token here. Reach out to the discord if you don't know how to get it",
 | 
				
			||||||
    "OLLAMA_MODEL": "dolphin-mixtral:latest"
 | 
					    "OLLAMA_MODEL": "llama2-uncensored:7b-chat-q2_K" 
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										15
									
								
								llm_integration/alliance_selection.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								llm_integration/alliance_selection.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					import json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AllianceSelectionAssistant:
 | 
				
			||||||
 | 
					    """Assists in alliance selection using LLM and team data."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, llm_model):
 | 
				
			||||||
 | 
					        self.llm_model = llm_model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def generate_pick_list(self, teams_data, strategy):
 | 
				
			||||||
 | 
					        teams_data_str = json.dumps(
 | 
				
			||||||
 | 
					            teams_data
 | 
				
			||||||
 | 
					        )  # Serialize the list of dictionaries to a string
 | 
				
			||||||
 | 
					        prompt = f"Given the following teams data: {teams_data_str} and pick strategy: {pick_strategy}, generate an alliance pick list."
 | 
				
			||||||
 | 
					        return self.query_ollama(prompt)
 | 
				
			||||||
							
								
								
									
										32
									
								
								llm_integration/llm_model.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								llm_integration/llm_model.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					import json
 | 
				
			||||||
 | 
					import requests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class OLLAMAConnector:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Abstract class to interact with a local LLM using Ollama for predictions.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(
 | 
				
			||||||
 | 
					        self, model_name: str, ollama_base_url: str = "http://localhost:11434"
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
 | 
					        self.model_name = model_name
 | 
				
			||||||
 | 
					        self.ollama_base_url = ollama_base_url
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def query_ollama(self, prompt: str):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Helper function to query the Ollama API.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        url = f"{self.ollama_base_url}/api/generate"
 | 
				
			||||||
 | 
					        data = {
 | 
				
			||||||
 | 
					            "prompt": prompt,
 | 
				
			||||||
 | 
					            "model": self.model_name,
 | 
				
			||||||
 | 
					            "stream": False,  # Set to False to get the full response at once
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            response = requests.post(url, json=data, stream=False)
 | 
				
			||||||
 | 
					            response.raise_for_status()  # Raise HTTPError for bad responses (4xx or 5xx)
 | 
				
			||||||
 | 
					            return response.json()["response"]
 | 
				
			||||||
 | 
					        except requests.exceptions.RequestException as e:
 | 
				
			||||||
 | 
					            print(f"Error querying Ollama: {e}")
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
							
								
								
									
										9
									
								
								llm_integration/match_outcome_prediction.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								llm_integration/match_outcome_prediction.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					class MatchPredictor:
 | 
				
			||||||
 | 
					    """Predicts match outcomes using LLM."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, llm_model):
 | 
				
			||||||
 | 
					        self.llm_model = llm_model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def predict_outcome(self, blue_alliance_data, red_alliance_data):
 | 
				
			||||||
 | 
					        prompt = f"Given blue alliance data: {blue_alliance_data} and red alliance data: {red_alliance_data}, predict the match outcome."
 | 
				
			||||||
 | 
					        return self.query_ollama(prompt)
 | 
				
			||||||
							
								
								
									
										17
									
								
								llm_integration/team_subjective_rating.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								llm_integration/team_subjective_rating.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					class TeamRatingGenerator:
 | 
				
			||||||
 | 
					    """Generates subjective team ratings using LLM predictions"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, llm_model):
 | 
				
			||||||
 | 
					        self.llm_model = llm_model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def rate_team(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        perfomance_metrics: dict,
 | 
				
			||||||
 | 
					        raw_event_data: dict,
 | 
				
			||||||
 | 
					        isa_data: dict,
 | 
				
			||||||
 | 
					        isa_notes: dict,
 | 
				
			||||||
 | 
					    ) -> str | None:
 | 
				
			||||||
 | 
					        """Rates a team based on available data"""
 | 
				
			||||||
 | 
					        return self.llm_model.query_ollama(
 | 
				
			||||||
 | 
					            prompt=f"The following First Robotics Competition (FRC), data comes from three different sources covering the exact same team and event, please cross reference them to identify possible problems. ```{perfomance_metrics}```, ```{raw_event_data}```, ```{isa_data}``` Do note that while the data source is the same for all three, the presentation of the data doesn't match up perfectly. I also have the following notes about the team in the data: ```{isa_notes}```Once you've done so, please use the data you have collected and referenced to give a comprehensive subjective rating to the team. This is not an interactive conversation, so please give an output that covers everything that you think the user may want in a single message including examples to support the conclusions. THE DATA ONLY CONTAINS ONE TEAM, OUTPUT MUST BE IN HTML FORMAT."
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
							
								
								
									
										154
									
								
								main.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								main.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,154 @@
 | 
				
			|||||||
 | 
					# from flask import Flask, render_template, jsonify
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# from data_sources.tba import TheBlueAllianceConnector
 | 
				
			||||||
 | 
					# from data_sources.isa import IndianaScoutingAllianceConnector
 | 
				
			||||||
 | 
					# from llm_integration.llm_model import OLLAMAConnector
 | 
				
			||||||
 | 
					# from llm_integration.team_subjective_rating import TeamRatingGenerator
 | 
				
			||||||
 | 
					# from llm_integration.match_outcome_prediction import MatchPredictor
 | 
				
			||||||
 | 
					# from llm_integration.alliance_selection import AllianceSelectionAssistant
 | 
				
			||||||
 | 
					# from utils.config_manager import ConfigurationManager
 | 
				
			||||||
 | 
					# from utils.logger import Logger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# app = Flask(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# config = ConfigurationManager()
 | 
				
			||||||
 | 
					# logger = Logger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# # Initialize data sources
 | 
				
			||||||
 | 
					# tba_api_key = config.get("TBA_TOKEN")
 | 
				
			||||||
 | 
					# tba_connector = TheBlueAllianceConnector(tba_api_key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# isa_api_key = config.get("ISA_TOKEN")
 | 
				
			||||||
 | 
					# isa_connector = IndianaScoutingAllianceConnector(isa_api_key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# # Initialize LLM model
 | 
				
			||||||
 | 
					# llm_model = config.get("OLLAMA_MODEL")
 | 
				
			||||||
 | 
					# llm = OLLAMAConnector(llm_model)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# # Initialize prediction and rating services
 | 
				
			||||||
 | 
					# team_rater = TeamRatingGenerator(llm)
 | 
				
			||||||
 | 
					# match_predictor = MatchPredictor(llm)
 | 
				
			||||||
 | 
					# alliance_assistant = AllianceSelectionAssistant(llm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# @app.route("/")
 | 
				
			||||||
 | 
					# def index():
 | 
				
			||||||
 | 
					#     return render_template("index.html")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# @app.route("/team/<int:team_number>")
 | 
				
			||||||
 | 
					# def team_info(team_number):
 | 
				
			||||||
 | 
					#     event_code = "2025incmp"
 | 
				
			||||||
 | 
					#     tba_team_performance_metrics = tba_connector.get_team_performance_metrics(
 | 
				
			||||||
 | 
					#         team_number, event_code
 | 
				
			||||||
 | 
					#     )
 | 
				
			||||||
 | 
					#     tba_raw_event_data = tba_connector.get_event_matches(event_code, team_number)
 | 
				
			||||||
 | 
					#     isa_data = isa_connector.get_event_matches(event_code, team_number)
 | 
				
			||||||
 | 
					#     isa_notes = isa_connector.get_robot_notes(team_number, event_code)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#     if tba_team_performance_metrics:
 | 
				
			||||||
 | 
					#         # logger.info(f"Team {team_number} Metrics: {tba_team_performance_metrics}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#         # Generate subjective team rating
 | 
				
			||||||
 | 
					#         logger.info(f"Generating Team rating...")
 | 
				
			||||||
 | 
					#         team_rating = team_rater.rate_team(
 | 
				
			||||||
 | 
					#             tba_team_performance_metrics, tba_raw_event_data, isa_data, isa_notes
 | 
				
			||||||
 | 
					#         )
 | 
				
			||||||
 | 
					#         output = f"Subjective Team Rating: {team_rating}"
 | 
				
			||||||
 | 
					#         logger.info(output)
 | 
				
			||||||
 | 
					#         return output
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#     else:
 | 
				
			||||||
 | 
					#         output = (
 | 
				
			||||||
 | 
					#             f"Could not retrieve metrics for team {team_number} at event {event_code}"
 | 
				
			||||||
 | 
					#         )
 | 
				
			||||||
 | 
					#         logger.info(output)
 | 
				
			||||||
 | 
					#         return output
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# if __name__ == "__main__":
 | 
				
			||||||
 | 
					#     app.run(debug=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from flask import Flask, render_template, jsonify
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from data_sources.tba import TheBlueAllianceConnector
 | 
				
			||||||
 | 
					from data_sources.isa import IndianaScoutingAllianceConnector
 | 
				
			||||||
 | 
					from llm_integration.llm_model import OLLAMAConnector
 | 
				
			||||||
 | 
					from llm_integration.team_subjective_rating import TeamRatingGenerator
 | 
				
			||||||
 | 
					from llm_integration.match_outcome_prediction import MatchPredictor
 | 
				
			||||||
 | 
					from llm_integration.alliance_selection import AllianceSelectionAssistant
 | 
				
			||||||
 | 
					from utils.config_manager import ConfigurationManager
 | 
				
			||||||
 | 
					from utils.logger import Logger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FrcRatingApp:
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        self.app = Flask(__name__)
 | 
				
			||||||
 | 
					        self.config = ConfigurationManager()
 | 
				
			||||||
 | 
					        self.logger = Logger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Initialize data sources
 | 
				
			||||||
 | 
					        tba_api_key = self.config.get("TBA_TOKEN")
 | 
				
			||||||
 | 
					        self.tba_connector = TheBlueAllianceConnector(tba_api_key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        isa_api_key = self.config.get("ISA_TOKEN")
 | 
				
			||||||
 | 
					        self.isa_connector = IndianaScoutingAllianceConnector(isa_api_key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Initialize LLM model
 | 
				
			||||||
 | 
					        llm_model = self.config.get("OLLAMA_MODEL")
 | 
				
			||||||
 | 
					        self.llm = OLLAMAConnector(llm_model)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Initialize prediction and rating services
 | 
				
			||||||
 | 
					        self.team_rater = TeamRatingGenerator(self.llm)
 | 
				
			||||||
 | 
					        self.match_predictor = MatchPredictor(self.llm)
 | 
				
			||||||
 | 
					        self.alliance_assistant = AllianceSelectionAssistant(self.llm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.setup_routes()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def setup_routes(self):
 | 
				
			||||||
 | 
					        self.app.add_url_rule("/", "index", self.index)
 | 
				
			||||||
 | 
					        self.app.add_url_rule("/team/<int:team_number>", "team_info", self.team_info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def index(self):
 | 
				
			||||||
 | 
					        return render_template("index.html")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def team_info(self, team_number):
 | 
				
			||||||
 | 
					        event_code = "2025incmp"
 | 
				
			||||||
 | 
					        tba_team_performance_metrics = self.tba_connector.get_team_performance_metrics(
 | 
				
			||||||
 | 
					            team_number, event_code
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        tba_raw_event_data = self.tba_connector.get_event_matches(
 | 
				
			||||||
 | 
					            event_code, team_number
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        isa_data = self.isa_connector.get_event_matches(event_code, team_number)
 | 
				
			||||||
 | 
					        isa_notes = self.isa_connector.get_robot_notes(team_number, event_code)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if tba_team_performance_metrics:
 | 
				
			||||||
 | 
					            # Generate subjective team rating
 | 
				
			||||||
 | 
					            self.logger.info(f"Generating Team rating...")
 | 
				
			||||||
 | 
					            team_rating = self.team_rater.rate_team(
 | 
				
			||||||
 | 
					                tba_team_performance_metrics,
 | 
				
			||||||
 | 
					                tba_raw_event_data,
 | 
				
			||||||
 | 
					                isa_data,
 | 
				
			||||||
 | 
					                isa_notes,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            output = f"Subjective Team Rating: {team_rating}"
 | 
				
			||||||
 | 
					            self.logger.info(output)
 | 
				
			||||||
 | 
					            return output
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            output = f"Could not retrieve metrics for team {team_number} at event {event_code}"
 | 
				
			||||||
 | 
					            self.logger.info(output)
 | 
				
			||||||
 | 
					            return output
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def run(self, debug=True):
 | 
				
			||||||
 | 
					        self.app.run(debug=debug)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					    frc_rating_app = FrcRatingApp()
 | 
				
			||||||
 | 
					    frc_rating_app.run()
 | 
				
			||||||
							
								
								
									
										12
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					blinker==1.9.0
 | 
				
			||||||
 | 
					certifi==2025.1.31
 | 
				
			||||||
 | 
					charset-normalizer==3.4.1
 | 
				
			||||||
 | 
					click==8.1.8
 | 
				
			||||||
 | 
					Flask==3.1.0
 | 
				
			||||||
 | 
					idna==3.10
 | 
				
			||||||
 | 
					itsdangerous==2.2.0
 | 
				
			||||||
 | 
					Jinja2==3.1.6
 | 
				
			||||||
 | 
					MarkupSafe==3.0.2
 | 
				
			||||||
 | 
					requests==2.32.3
 | 
				
			||||||
 | 
					urllib3==2.4.0
 | 
				
			||||||
 | 
					Werkzeug==3.1.3
 | 
				
			||||||
							
								
								
									
										290
									
								
								templates/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										290
									
								
								templates/index.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,290 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <title>Team Info Lookup</title>
 | 
				
			||||||
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1">
 | 
				
			||||||
 | 
					    <!-- Bootstrap 4 CDN for quick, nice styling -->
 | 
				
			||||||
 | 
					    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
 | 
				
			||||||
 | 
					    <style>
 | 
				
			||||||
 | 
					        body {
 | 
				
			||||||
 | 
					            background: #f8f9fa;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .main-container {
 | 
				
			||||||
 | 
					            min-width: 50%;
 | 
				
			||||||
 | 
					            max-width: 90%;
 | 
				
			||||||
 | 
					            margin: 60px auto;
 | 
				
			||||||
 | 
					            background: #fff;
 | 
				
			||||||
 | 
					            border-radius: 8px;
 | 
				
			||||||
 | 
					            box-shadow: 0 2px 12px rgba(0, 0, 0, 0.07);
 | 
				
			||||||
 | 
					            padding: 32px 24px 24px 24px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .result-box {
 | 
				
			||||||
 | 
					            margin-top: 32px;
 | 
				
			||||||
 | 
					            padding: 20px;
 | 
				
			||||||
 | 
					            border-radius: 6px;
 | 
				
			||||||
 | 
					            background: #e9ecef;
 | 
				
			||||||
 | 
					            min-height: 60px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .navbar-brand {
 | 
				
			||||||
 | 
					            font-weight: bold;
 | 
				
			||||||
 | 
					            letter-spacing: 1px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					    <nav class="navbar navbar-expand-lg navbar-light bg-light">
 | 
				
			||||||
 | 
					        <a class="navbar-brand" href="#">Team Lookup</a>
 | 
				
			||||||
 | 
					    </nav>
 | 
				
			||||||
 | 
					    <div class="main-container">
 | 
				
			||||||
 | 
					        <h2 class="mb-4 text-center">Lookup FRC Team Info</h2>
 | 
				
			||||||
 | 
					        <form id="team-form">
 | 
				
			||||||
 | 
					            <div class="form-group">
 | 
				
			||||||
 | 
					                <label for="team-number">Enter Team Number</label>
 | 
				
			||||||
 | 
					                <input type="number" class="form-control" id="team-number" name="team_number" min="1" required>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <button type="submit" class="btn btn-primary btn-block">Get Team Info</button>
 | 
				
			||||||
 | 
					        </form>
 | 
				
			||||||
 | 
					        <h2>The following result is generated using an LLM, please note that anything said in the following block of
 | 
				
			||||||
 | 
					            text does not reflect my own personal views and I do not vouch for it's accuracy.</h2>
 | 
				
			||||||
 | 
					        <div id="result" class="result-box mt-4" style="display:none;"></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="team-list-section">
 | 
				
			||||||
 | 
					            <h4 class="mb-3">I have data about the following teams:</h4>
 | 
				
			||||||
 | 
					            <div class="table-responsive">
 | 
				
			||||||
 | 
					                <table class="table table-striped team-table">
 | 
				
			||||||
 | 
					                    <thead class="thead-dark">
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <th scope="col">Team</th>
 | 
				
			||||||
 | 
					                            <th scope="col">Name</th>
 | 
				
			||||||
 | 
					                            <th scope="col">Location</th>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                    </thead>
 | 
				
			||||||
 | 
					                    <tbody>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>45</td>
 | 
				
			||||||
 | 
					                            <td>TechnoKats Robotics Team</td>
 | 
				
			||||||
 | 
					                            <td>Kokomo, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>71</td>
 | 
				
			||||||
 | 
					                            <td>Team Hammond</td>
 | 
				
			||||||
 | 
					                            <td>Hammond, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>135</td>
 | 
				
			||||||
 | 
					                            <td>Penn Robotics Black Knights</td>
 | 
				
			||||||
 | 
					                            <td>Mishawaka, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>234</td>
 | 
				
			||||||
 | 
					                            <td>Cyber Blue</td>
 | 
				
			||||||
 | 
					                            <td>Indianapolis, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>292</td>
 | 
				
			||||||
 | 
					                            <td>PantherTech</td>
 | 
				
			||||||
 | 
					                            <td>Russiaville, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>328</td>
 | 
				
			||||||
 | 
					                            <td>Penn Robotics Golden Rooks</td>
 | 
				
			||||||
 | 
					                            <td>Mishawaka, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>447</td>
 | 
				
			||||||
 | 
					                            <td>Team Roboto</td>
 | 
				
			||||||
 | 
					                            <td>Anderson, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>461</td>
 | 
				
			||||||
 | 
					                            <td>Westside Boiler Invasion</td>
 | 
				
			||||||
 | 
					                            <td>West Lafayette, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>829</td>
 | 
				
			||||||
 | 
					                            <td>The Digital Goats</td>
 | 
				
			||||||
 | 
					                            <td>Indianapolis, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>868</td>
 | 
				
			||||||
 | 
					                            <td>TechHOUNDS</td>
 | 
				
			||||||
 | 
					                            <td>Carmel, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>1018</td>
 | 
				
			||||||
 | 
					                            <td>Pike RoboDevils</td>
 | 
				
			||||||
 | 
					                            <td>Indianapolis, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>1024</td>
 | 
				
			||||||
 | 
					                            <td>Kil-A-Bytes</td>
 | 
				
			||||||
 | 
					                            <td>Indianapolis, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>1501</td>
 | 
				
			||||||
 | 
					                            <td>Team THRUST</td>
 | 
				
			||||||
 | 
					                            <td>Huntington, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>1741</td>
 | 
				
			||||||
 | 
					                            <td>Red Alert</td>
 | 
				
			||||||
 | 
					                            <td>Greenwood, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>1747</td>
 | 
				
			||||||
 | 
					                            <td>Harrison Boiler Robotics</td>
 | 
				
			||||||
 | 
					                            <td>West Lafayette, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>2171</td>
 | 
				
			||||||
 | 
					                            <td>RoboDogs</td>
 | 
				
			||||||
 | 
					                            <td>Crown Point, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>2197</td>
 | 
				
			||||||
 | 
					                            <td>Las Pumas</td>
 | 
				
			||||||
 | 
					                            <td>New Carlisle, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>3176</td>
 | 
				
			||||||
 | 
					                            <td>Purple Precision</td>
 | 
				
			||||||
 | 
					                            <td>Brownsburg, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>3487</td>
 | 
				
			||||||
 | 
					                            <td>Red Pride Robotics</td>
 | 
				
			||||||
 | 
					                            <td>Plainfield, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>3494</td>
 | 
				
			||||||
 | 
					                            <td>The Quadrangles</td>
 | 
				
			||||||
 | 
					                            <td>Bloomington, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>3940</td>
 | 
				
			||||||
 | 
					                            <td>CyberTooth</td>
 | 
				
			||||||
 | 
					                            <td>Kokomo, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>4272</td>
 | 
				
			||||||
 | 
					                            <td>Maverick Robotics</td>
 | 
				
			||||||
 | 
					                            <td>Lafayette, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>4485</td>
 | 
				
			||||||
 | 
					                            <td>Tribe Tech Robotics</td>
 | 
				
			||||||
 | 
					                            <td>Danville, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>4926</td>
 | 
				
			||||||
 | 
					                            <td>GalacTech</td>
 | 
				
			||||||
 | 
					                            <td>Columbus, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>5010</td>
 | 
				
			||||||
 | 
					                            <td>Tiger Dynasty</td>
 | 
				
			||||||
 | 
					                            <td>Fishers, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>5188</td>
 | 
				
			||||||
 | 
					                            <td>Area 5188: Classified Robotics</td>
 | 
				
			||||||
 | 
					                            <td>Terre Haute, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>5402</td>
 | 
				
			||||||
 | 
					                            <td>Wreckless Robotics</td>
 | 
				
			||||||
 | 
					                            <td>Loganpsport/Walton, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>5484</td>
 | 
				
			||||||
 | 
					                            <td>Career Academy Robotics - Wolf Pack</td>
 | 
				
			||||||
 | 
					                            <td>South Bend, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>6721</td>
 | 
				
			||||||
 | 
					                            <td>Tindley Trailblazers</td>
 | 
				
			||||||
 | 
					                            <td>Indianapolis, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>6956</td>
 | 
				
			||||||
 | 
					                            <td>SHAM-ROCK-BOTICS ☘</td>
 | 
				
			||||||
 | 
					                            <td>Westfield, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>7454</td>
 | 
				
			||||||
 | 
					                            <td>Huskies on Hogs</td>
 | 
				
			||||||
 | 
					                            <td>Evansville, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>7457</td>
 | 
				
			||||||
 | 
					                            <td>suPURDUEper Robotics</td>
 | 
				
			||||||
 | 
					                            <td>Indianapolis, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>7617</td>
 | 
				
			||||||
 | 
					                            <td>RoboBlazers</td>
 | 
				
			||||||
 | 
					                            <td>Carmel, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>7657</td>
 | 
				
			||||||
 | 
					                            <td>ThunderBots</td>
 | 
				
			||||||
 | 
					                            <td>Evansville, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>8103</td>
 | 
				
			||||||
 | 
					                            <td>Knight Robotics</td>
 | 
				
			||||||
 | 
					                            <td>Kendallville, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>8430</td>
 | 
				
			||||||
 | 
					                            <td>The Hatch Batch</td>
 | 
				
			||||||
 | 
					                            <td>Washington, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>10021</td>
 | 
				
			||||||
 | 
					                            <td>Guerin Catholic Golden Gears</td>
 | 
				
			||||||
 | 
					                            <td>Noblesville, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>10332</td>
 | 
				
			||||||
 | 
					                            <td>Carroll Charger Robotics</td>
 | 
				
			||||||
 | 
					                            <td>Fort Wayne, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>10492</td>
 | 
				
			||||||
 | 
					                            <td>Bosse BYTEForce</td>
 | 
				
			||||||
 | 
					                            <td>Evansville, Indiana, USA</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                    </tbody>
 | 
				
			||||||
 | 
					                </table>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <script>
 | 
				
			||||||
 | 
					        document.getElementById('team-form').addEventListener('submit', function (e) {
 | 
				
			||||||
 | 
					            e.preventDefault();
 | 
				
			||||||
 | 
					            const teamNumber = document.getElementById('team-number').value;
 | 
				
			||||||
 | 
					            const resultDiv = document.getElementById('result');
 | 
				
			||||||
 | 
					            resultDiv.style.display = 'block';
 | 
				
			||||||
 | 
					            resultDiv.innerHTML = '<div class="text-center text-muted">Loading...</div>';
 | 
				
			||||||
 | 
					            fetch(`/team/${teamNumber}`, { signal: AbortSignal.timeout(50000000000) })
 | 
				
			||||||
 | 
					                .then(response => response.text())
 | 
				
			||||||
 | 
					                .then(rating => {
 | 
				
			||||||
 | 
					                    resultDiv.innerHTML = rating;
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .catch(err => {
 | 
				
			||||||
 | 
					                    resultDiv.innerHTML = `<div class="alert alert-danger">Failed to fetch team info.</div>`;
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    </script>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
		Reference in New Issue
	
	Block a user