Files
2026-05-12 23:01:12 +02:00

133 lines
3.9 KiB
Python

"""Sport name normalisation."""
import re
_MAPPING: dict[str, str] = {
# cycling variants (FIT enums, GPX types, Strava API/CSV types)
"cycling": "cycling",
"biking": "cycling",
"bike": "cycling",
"ride": "cycling",
"road_biking": "cycling",
"road_cycling": "cycling",
"mountain_biking": "cycling",
"mountain_bike_ride": "cycling",
"gravel_cycling": "cycling",
"gravel_ride": "cycling",
"cyclocross": "cycling",
"indoor_cycling": "cycling",
"indoor_ride": "cycling",
"virtual_ride": "cycling",
"e_biking": "cycling",
"ebikeride": "cycling",
"e_bike_ride": "cycling",
"ebike_ride": "cycling",
"handcycle": "cycling",
"velomobile": "cycling",
# running
"running": "running",
"run": "running",
"trail_running": "running",
"trail_run": "running",
"treadmill_running": "running",
"treadmill": "running",
"virtual_run": "running",
"outdoor_run": "running",
"indoor_run": "running",
"track_run": "running",
# hiking
"hiking": "hiking",
"hike": "hiking",
"outdoor_hike": "hiking",
# walking
"walking": "walking",
"walk": "walking",
"outdoor_walk": "walking",
# skiing
"skiing": "skiing",
"cross_country_skiing": "skiing",
"nordic_skiing": "skiing",
"nordic_ski": "skiing",
"downhill_skiing": "skiing",
"alpine_skiing": "skiing",
"alpine_ski": "skiing",
"skate_skiing": "skiing",
"backcountry_skiing": "skiing",
"backcountry_ski": "skiing",
# swimming
"swimming": "swimming",
"swim": "swimming",
"open_water_swimming": "swimming",
"lap_swimming": "swimming",
}
_SUB_SPORT_MAPPING: dict[str, str] = {
# cycling
"ride": "road",
"road_biking": "road",
"road_cycling": "road",
"mountain_biking": "mountain",
"mountain_bike_ride": "mountain",
"gravel_cycling": "gravel",
"gravel_ride": "gravel",
"cyclocross": "gravel",
"indoor_cycling": "indoor",
"indoor_ride": "indoor",
"virtual_ride": "indoor",
# running
"trail_running": "trail",
"trail_run": "trail",
"treadmill_running": "indoor",
"treadmill": "indoor",
"indoor_run": "indoor",
"virtual_run": "indoor",
"track_run": "track",
# skiing
"cross_country_skiing": "nordic",
"nordic_skiing": "nordic",
"nordic_ski": "nordic",
"skate_skiing": "nordic",
"backcountry_skiing": "nordic",
"backcountry_ski": "nordic",
"downhill_skiing": "alpine",
"alpine_skiing": "alpine",
"alpine_ski": "alpine",
# swimming
"open_water_swimming": "open_water",
"lap_swimming": "pool",
}
BAS_SPORTS = {"cycling", "running", "hiking", "walking", "swimming", "skiing", "other"}
# Valid sub_sport values per sport, in display order.
SUB_SPORTS: dict[str, list[str]] = {
"cycling": ["road", "mountain", "gravel", "indoor"],
"running": ["trail", "track", "indoor"],
"swimming": ["open_water", "pool"],
"skiing": ["nordic", "alpine"],
}
def _normalise_key(raw: object) -> str:
key = str(raw).strip()
# CamelCase → snake_case ("MountainBikeRide" → "mountain_bike_ride")
key = re.sub(r"([A-Z])", r"_\1", key).lower().lstrip("_")
key = key.replace(" ", "_").replace("-", "_")
return re.sub(r"^\d+", "", key)
def normalise_sport(raw: object) -> str:
if raw is None:
return "other"
return _MAPPING.get(_normalise_key(raw), "other")
def normalise_sub_sport(raw: object) -> str | None:
"""Infer sub_sport from a raw sport type string (e.g. 'mountain_bike_ride''mountain').
Returns None when no sub_sport is implied (e.g. plain 'ride', 'run').
"""
if raw is None:
return None
return _SUB_SPORT_MAPPING.get(_normalise_key(raw))