mirror of
https://github.com/MoonlitJolteon/frc-stat-predictor.git
synced 2025-11-01 13:40:21 +00:00
Forgot to commit in small portions again
This commit is contained in:
4
.gitignore
vendored
4
.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