F1 circuit layouts with year-by-year SVGs — manually traced track variations
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

88 Zeilen
3.5KB

  1. import re
  2. import logging
  3. import urllib.request
  4. import json
  5. from functools import lru_cache
  6. from typing import Optional, TYPE_CHECKING
  7. if TYPE_CHECKING:
  8. from circuits_service import CircuitService
  9. def slugify(text: str) -> str:
  10. text = text.lower().strip()
  11. replacements = {
  12. 'ü': 'u', 'ö': 'o', 'ä': 'a', 'é': 'e', 'è': 'e', 'ê': 'e', 'ë': 'e',
  13. 'à': 'a', 'â': 'a', 'î': 'i', 'ï': 'i', 'ô': 'o', 'û': 'u', 'ñ': 'n',
  14. 'ã': 'a', 'á': 'a', 'í': 'i', 'ó': 'o', 'ú': 'u',
  15. }
  16. for k, v in replacements.items():
  17. text = text.replace(k, v)
  18. text = re.sub(r'[^a-z0-9]+', '-', text)
  19. return text.strip('-')
  20. class ErgastService:
  21. JOLPICA_BASE = 'https://api.jolpi.ca/ergast/f1'
  22. # Country name normalization: Jolpica country → our country slug
  23. country_map = {
  24. 'UK': 'uk',
  25. 'UAE': 'united-arab-emirates',
  26. 'USA': 'united-states',
  27. 'Korea': 'korea',
  28. }
  29. def __init__(self, circuits_service: 'CircuitService' = None):
  30. self.circuits_service = circuits_service
  31. self.logger = logging.getLogger(__name__)
  32. self._circuit_name_index = self._build_circuit_name_index()
  33. def _build_circuit_name_index(self) -> dict[str, tuple[str, str, str]]:
  34. """Build a lookup from slugified circuit name → (country_slug, locality_slug, circuit_slug)."""
  35. index = {}
  36. if not self.circuits_service:
  37. return index
  38. for country_slug, country in self.circuits_service.circuit_data.items():
  39. for locality_slug, locality in country.localities.items():
  40. for circuit_slug, circuit in locality.circuits.items():
  41. # Index by our circuit slug (which is the slugified circuit name)
  42. index[circuit_slug] = (country_slug, locality_slug, circuit_slug)
  43. # Also index by ASCII-slugified version for unicode mismatches
  44. ascii_slug = slugify(circuit.name)
  45. if ascii_slug not in index:
  46. index[ascii_slug] = (country_slug, locality_slug, circuit_slug)
  47. return index
  48. @lru_cache(maxsize=128)
  49. def _fetch_season_schedule(self, season: int) -> list[dict]:
  50. """Fetch and cache the race schedule for a season from Jolpica."""
  51. url = f'{self.JOLPICA_BASE}/{season}.json?limit=100'
  52. try:
  53. resp = urllib.request.urlopen(url, timeout=10)
  54. data = json.loads(resp.read())
  55. return data['MRData']['RaceTable']['Races']
  56. except Exception as e:
  57. self.logger.error(f"Failed to fetch Jolpica schedule for {season}: {e}")
  58. return []
  59. def find_slugs_for_grand_prix(self, grand_prix_name: str, season: int) -> Optional[tuple[str, str, str]]:
  60. """Resolve a grand prix name + season to (country_slug, locality_slug, circuit_slug)."""
  61. races = self._fetch_season_schedule(season)
  62. # Find the matching race
  63. race = next((r for r in races if r['raceName'] == grand_prix_name), None)
  64. if not race:
  65. self.logger.error(f"Race '{grand_prix_name}' not found in {season} schedule")
  66. return None, None, None
  67. circuit = race['Circuit']
  68. circuit_name_slug = slugify(circuit['circuitName'])
  69. # Try matching by slugified circuit name
  70. if circuit_name_slug in self._circuit_name_index:
  71. return self._circuit_name_index[circuit_name_slug]
  72. self.logger.warning(f"Circuit '{circuit['circuitName']}' (slug: {circuit_name_slug}) not found in index")
  73. return None, None, None