Coverage for src/ecnl/domain/classification.py: 100%
24 statements
« prev ^ index » next coverage.py v7.14.2, created at 2026-06-28 08:19 +0000
« prev ^ index » next coverage.py v7.14.2, created at 2026-06-28 08:19 +0000
1"""Event-name classification — pure domain logic.
3The AthleteOne feed has no field that says "this event is ECNL girls in the
4Southeast conference for 2025-26". That information is encoded in the event
5*name*, e.g.:
7 "ECNL Girls Southeast 2025-26" -> ECNL / girls / Southeast / 2025-26
8 "ECNL Boys Northern Cal 2025-26" -> ECNL / boys / Northern Cal / 2025-26
9 "ECNL RL Girls STXCL 2025-26" -> ECRL / girls / STXCL / 2025-26
10 "ECNL RL Boys Golden State 2025-26" -> ECRL / boys / Golden State / 2025-26
12ECRL is spelled "ECNL RL" in the source. This module is the single place that
13understands that convention; both the outbound adapter and discovery rely on it.
14"""
16import re
18# Trailing season token like "2025-26" or "2025-2026".
19_SEASON_RE = re.compile(r"\b(\d{4}-\d{2,4})\b")
22def classify_event_name(name: str) -> tuple[str, str, str, str]:
23 """Parse an event name into (league, gender, conference, season).
25 Args:
26 name: The raw event name from the feed.
28 Returns:
29 A ``(league, gender, conference, season)`` tuple. ``league`` is "ECNL"
30 or "ECRL"; ``gender`` is "girls" or "boys". ``conference`` and ``season``
31 fall back to "" when they cannot be parsed, so callers always get four
32 strings and can decide how to treat an unrecognized name.
33 """
34 text = name.strip()
35 league = "ECRL" if re.search(r"\bECNL\s+RL\b", text, re.IGNORECASE) else "ECNL"
37 lowered = text.lower()
38 if "girls" in lowered:
39 gender = "girls"
40 elif "boys" in lowered:
41 gender = "boys"
42 else:
43 gender = ""
45 season_match = _SEASON_RE.search(text)
46 season = season_match.group(1) if season_match else ""
48 conference = _extract_conference(text, gender, season)
49 return league, gender, conference, season
52def _extract_conference(text: str, gender: str, season: str) -> str:
53 """Return the conference slice between the gender token and the season."""
54 remainder = text
55 if gender:
56 # Cut everything up to and including the gender word (Girls/Boys).
57 match = re.search(gender, remainder, re.IGNORECASE)
58 if match:
59 remainder = remainder[match.end() :]
60 if season:
61 remainder = remainder.replace(season, "")
62 return remainder.strip()