f1-mcp-server / openf1_registry.py
arre99's picture
clean up the openf1 tab
fa395d9
from enum import Enum
from dataclasses import dataclass
from typing import Dict, Set, List, Optional
api_endpoints = {
"sessions": "sessions?",
"weather": "weather?",
"locations": "locations?",
"drivers": "drivers?",
"intervals": "intervals?",
"laps": "laps?",
"car_data": "car_data?",
"pit": "pit?",
"position": "position?",
"race_control": "race_control?",
"stints": "stints?",
"team_radio": "team_radio?",
}
class FilterType(Enum):
EQUALITY = "equality" # exact match
COMPARISON = "comparison" # >, <, >=, <=
class DataType(Enum):
STRING = "string"
INTEGER = "integer"
DATETIME = "datetime"
BINARY = "binary" # true/false filters
@dataclass
class FilterSpec:
"""Specification for an API filter parameter"""
name: str
filter_type: FilterType
data_type: DataType
description: str = ""
allowed_values: Optional[List[str]] = None # For equality filters with restricted values
def get_query_examples(self) -> List[str]:
"""Generate example query parameters for this filter"""
examples = []
if self.data_type == DataType.BINARY:
examples = [f"{self.name}=true", f"{self.name}=false"]
elif self.filter_type == FilterType.EQUALITY:
if self.allowed_values:
examples = [f"{self.name}={val}" for val in self.allowed_values[:2]]
elif self.data_type == DataType.STRING:
examples = [f"{self.name}=example_value"]
elif self.data_type == DataType.INTEGER:
examples = [f"{self.name}=42"]
elif self.data_type == DataType.DATETIME:
examples = [f"{self.name}=2024-01-01T00:00:00Z", f"{self.name}=2024-01-01T10:30:00Z"]
elif self.filter_type == FilterType.COMPARISON:
if self.data_type == DataType.INTEGER:
examples = [f"{self.name}>=10", f"{self.name}<100"]
elif self.data_type == DataType.DATETIME:
examples = [f"{self.name}>=2024-01-01T00:00:00Z", f"{self.name}<2024-12-31T00:00:00Z"]
elif self.data_type == DataType.STRING:
examples = [f"{self.name}>M", f"{self.name}<Z"] # alphabetical comparison
return examples
def help_text(self) -> str:
"""Generate help text for this filter"""
text = f"Filter: {self.name}\n"
text += f" Type: {self.filter_type.value} ({self.data_type.value})\n"
if self.description:
text += f" Description: {self.description}\n"
if self.allowed_values:
text += f" Allowed values: {', '.join(self.allowed_values)}\n"
examples = self.get_query_examples()
if examples:
text += f" Examples: {', '.join(examples)}"
return text
class APIEndpointRegistry:
"""Registry for API endpoints and their supported filters"""
def __init__(self, base_url: str = ""):
self.base_url = base_url
self.endpoints: Dict[str, Set[str]] = {} # endpoint -> filter names
self.filters: Dict[str, FilterSpec] = {} # global filter definitions
def define_filter(
self,
name: str,
filter_type: FilterType,
data_type: DataType,
description: str = "",
allowed_values: Optional[List[str]] = None
) -> 'APIEndpointRegistry':
"""Define a filter that can be used by endpoints"""
filter_spec = FilterSpec(name, filter_type, data_type, description, allowed_values)
self.filters[name] = filter_spec
return self
def register_endpoint(self, endpoint: str, *filter_names: str) -> 'APIEndpointRegistry':
"""Register an API endpoint with its supported filters"""
# Validate all filters exist
for filter_name in filter_names:
if filter_name not in self.filters:
raise ValueError(f"Filter '{filter_name}' not defined. Use define_filter() first.")
# Store endpoint -> filters mapping
self.endpoints[endpoint] = set(filter_names)
return self
def get_endpoint_filters(self, endpoint: str) -> Dict[str, FilterSpec]:
"""Get all filters supported by an endpoint"""
filter_names = self.endpoints.get(endpoint, set())
return {name: self.filters[name] for name in filter_names}
def get_filter_help(self, filter_name: str) -> str:
"""Get help text for a specific filter"""
if filter_name in self.filters:
return self.filters[filter_name].help_text()
return f"Filter '{filter_name}' not found."
def get_endpoint_help(self, endpoint: str) -> str:
"""Get help text for all filters supported by an endpoint"""
filters = self.get_endpoint_filters(endpoint)
if not filters:
return f"Endpoint '{endpoint}' has no registered filters."
help_text = f"API Endpoint: {self.base_url}{endpoint}\n"
help_text += f"Supported filters ({len(filters)}):\n\n"
for name, spec in sorted(filters.items()):
help_text += spec.help_text() + "\n\n"
return help_text.strip()
def list_all_endpoints(self) -> List[str]:
"""Get list of all registered endpoints"""
return sorted(self.endpoints.keys())
def list_all_filters(self) -> List[str]:
"""Get list of all defined filters"""
return sorted(self.filters.keys())
# Create registry with base URL
f1_api = APIEndpointRegistry("https://api.openf1.org/v1/")
# Define filters with their specifications
f1_api.define_filter("date", FilterType.COMPARISON, DataType.DATETIME, "The UTC date and time, in ISO 8601 format.")
f1_api.define_filter("driver_number", FilterType.EQUALITY, DataType.INTEGER, "The unique number assigned to an F1 driver")
f1_api.define_filter("meeting_key", FilterType.EQUALITY, DataType.STRING, "The unique identifier for the meeting. Use 'latest' to identify the latest or current meeting.")
f1_api.define_filter("session_key", FilterType.EQUALITY, DataType.STRING, "The unique identifier for the session. Use 'latest' to identify the latest or current session.")
f1_api.define_filter("speed", FilterType.COMPARISON, DataType.INTEGER, "Velocity of the car in km/h.")
f1_api.define_filter("country_code", FilterType.EQUALITY, DataType.STRING, "A code that uniquely identifies the country.")
f1_api.define_filter("first_name", FilterType.EQUALITY, DataType.STRING, "The first name of the driver.")
f1_api.define_filter("last_name", FilterType.EQUALITY, DataType.STRING, "The last name of the driver.")
f1_api.define_filter("full_name", FilterType.EQUALITY, DataType.STRING, "The full name of the driver.")
f1_api.define_filter("name_acronym", FilterType.EQUALITY, DataType.STRING, "Three-letter acronym of the driver's name.")
f1_api.define_filter("team_name", FilterType.EQUALITY, DataType.STRING, "The name of the driver's team.")
f1_api.define_filter("gap_to_leader", FilterType.COMPARISON, DataType.INTEGER, "The time gap to the race leader in seconds, +1 LAP if lapped, or null for the race leader.")
f1_api.define_filter("interval", FilterType.COMPARISON, DataType.INTEGER, "The time gap to the car ahead in seconds, +1 LAP if lapped, or null for the race leader.")
f1_api.define_filter("date_start", FilterType.COMPARISON, DataType.DATETIME, "The UTC starting date and time, in ISO 8601 format.")
f1_api.define_filter("date_end", FilterType.COMPARISON, DataType.DATETIME, "The UTC ending date and time, in ISO 8601 format.")
f1_api.define_filter("is_pit_out_lap", FilterType.EQUALITY, DataType.BINARY, "A boolean value indicating whether the lap is an out lap from the pit (true if it is, false otherwise).")
f1_api.define_filter("lap_duration", FilterType.COMPARISON, DataType.INTEGER, "The total time taken, in seconds, to complete the entire lap.")
f1_api.define_filter("lap_number", FilterType.EQUALITY, DataType.INTEGER, "The sequential number of the lap within the session (starts at 1).")
f1_api.define_filter("circuit_key", FilterType.EQUALITY, DataType.STRING, "The unique identifier for the circuit where the event takes place.")
f1_api.define_filter("circuit_short_name", FilterType.EQUALITY, DataType.STRING, "The short or common name of the circuit where the event takes place.")
f1_api.define_filter("country_key", FilterType.EQUALITY, DataType.STRING, "The unique identifier for the country where the event takes place.")
f1_api.define_filter("country_name", FilterType.EQUALITY, DataType.STRING, "The name of the country where the event takes place.")
f1_api.define_filter("location", FilterType.EQUALITY, DataType.STRING, "The city or geographical location where the event takes place.")
f1_api.define_filter("meeting_name", FilterType.EQUALITY, DataType.STRING, "The name of the meeting.")
f1_api.define_filter("meeting_official_name", FilterType.EQUALITY, DataType.STRING, "The official name of the meeting.")
f1_api.define_filter("year", FilterType.EQUALITY, DataType.INTEGER, "The year of the event.")
f1_api.define_filter("pit_duration", FilterType.COMPARISON, DataType.INTEGER, "The time spent in the pit, from entering to leaving the pit lane, in seconds.")
f1_api.define_filter("position", FilterType.EQUALITY, DataType.INTEGER, "Position of the driver (starts at 1).", [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20])
f1_api.define_filter("category", FilterType.EQUALITY, DataType.STRING, "The category of the event (CarEvent, Drs, Flag, SafetyCar)", ["CarEvent", "Drs", "Flag", "SafetyCar"])
f1_api.define_filter("flag", FilterType.EQUALITY, DataType.STRING, "The flag displayed to the drivers.", ["Green", "Yellow", "Red", "Black", "White", "Blue", "Checkered", "White"])
f1_api.define_filter("message", FilterType.EQUALITY, DataType.STRING, "Description of the event or action.")
f1_api.define_filter("session_name", FilterType.EQUALITY, DataType.STRING, "The name of the session (Practice 1, Qualifying, Race, ...).")
f1_api.define_filter("session_type", FilterType.EQUALITY, DataType.STRING, "The type of the session (Practice, Qualifying, Race, ...).")
f1_api.define_filter("compound", FilterType.EQUALITY, DataType.STRING, "The specific compound of tyre used during the stint (SOFT, MEDIUM, HARD, ...).", ["SOFT", "MEDIUM", "HARD", "INTERMEDIATE", "WET"])
f1_api.define_filter("lap_end", FilterType.COMPARISON, DataType.INTEGER, "Number of the last completed lap in this stint.")
f1_api.define_filter("lap_start", FilterType.COMPARISON, DataType.INTEGER, "Number of the initial lap in this stint (starts at 1).")
f1_api.define_filter("stint_number", FilterType.EQUALITY, DataType.INTEGER, "The sequential number of the stint within the session (starts at 1).")
f1_api.define_filter("tyre_age_at_start", FilterType.COMPARISON, DataType.INTEGER, "The age of the tyres at the start of the stint, in laps completed.")
f1_api.define_filter("air_temperature", FilterType.COMPARISON, DataType.INTEGER, "Air temperature (°C).")
f1_api.define_filter("humidity", FilterType.COMPARISON, DataType.INTEGER, "Humidity percentage.")
f1_api.define_filter("pressure", FilterType.COMPARISON, DataType.INTEGER, "Air pressure (mbar).")
f1_api.define_filter("rainfall", FilterType.COMPARISON, DataType.INTEGER, "Whether there is rainfall.")
f1_api.define_filter("track_temperature", FilterType.COMPARISON, DataType.INTEGER, "Track temperature (°C).")
f1_api.define_filter("wind_direction", FilterType.COMPARISON, DataType.INTEGER, "Wind direction (°), from 0° to 359°.")
f1_api.define_filter("wind_speed", FilterType.COMPARISON, DataType.INTEGER, "Wind speed (m/s).")
# Register API endpoints with their supported filters
f1_api.register_endpoint("car_data", "date", "driver_number", "meeting_key", "session_key", "speed")
f1_api.register_endpoint("drivers", "session_key", "meeting_key", "country_code", "driver_number", "first_name", "last_name", "full_name", "name_acronym", "team_name")
f1_api.register_endpoint("intervals", "date", "driver_number", "meeting_key", "session_key", "gap_to_leader", "interval")
f1_api.register_endpoint("laps", "date_start", "driver_number", "meeting_key", "session_key", "lap_duration", "lap_number", "is_pit_out_lap")
f1_api.register_endpoint("location", "date", "driver_number", "meeting_key", "session_key")
f1_api.register_endpoint("meetings", "circuit_key", "circuit_short_name", "country_code", "country_key", "country_name", "date_start", "location", "meeting_key", "meeting_name", "meeting_official_name", "year")
f1_api.register_endpoint("pit", "date", "driver_number", "lap_number", "meeting_key", "session_key", "pit_duration")
f1_api.register_endpoint("position", "date", "driver_number", "meeting_key", "session_key", "position")
f1_api.register_endpoint("race_control", "category", "date", "driver_number", "meeting_key", "session_key", "flag", "message", "lap_number")
f1_api.register_endpoint("sessions", "circuit_key", "circuit_short_name", "country_code", "country_key", "country_name", "date_start", "date_end", "location", "session_name", "session_type", "session_key", "meeting_key", "year")
f1_api.register_endpoint("stints", "compound", "driver_number", "lap_end", "lap_start", "meeting_key", "session_key", "stint_number", "tyre_age_at_start")
f1_api.register_endpoint("team_radio", "date", "driver_number", "meeting_key", "session_key")
f1_api.register_endpoint("weather", "air_temperature", "date", "humidity", "pressure", "rainfall", "track_temperature", "wind_direction", "wind_speed", "meeting_key", "session_key")