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

1"""Event-name classification — pure domain logic. 

2 

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.: 

6 

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 

11 

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""" 

15 

16import re 

17 

18# Trailing season token like "2025-26" or "2025-2026". 

19_SEASON_RE = re.compile(r"\b(\d{4}-\d{2,4})\b") 

20 

21 

22def classify_event_name(name: str) -> tuple[str, str, str, str]: 

23 """Parse an event name into (league, gender, conference, season). 

24 

25 Args: 

26 name: The raw event name from the feed. 

27 

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" 

36 

37 lowered = text.lower() 

38 if "girls" in lowered: 

39 gender = "girls" 

40 elif "boys" in lowered: 

41 gender = "boys" 

42 else: 

43 gender = "" 

44 

45 season_match = _SEASON_RE.search(text) 

46 season = season_match.group(1) if season_match else "" 

47 

48 conference = _extract_conference(text, gender, season) 

49 return league, gender, conference, season 

50 

51 

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()