""" core/normalizer.py — 모든 정규화 함수의 단일 진입점 팀명, 구장, 포지션, 선수명, 경기유형 등의 정규화를 담당합니다. Playwright 의존성 없이 순수 파이썬 로직만 포함합니다. """ from __future__ import annotations import re from typing import Any from core.config_loader import ( team_name_map, team_code_map, stadium_name_map, game_type_map, position_number_map, position_to_defense_no, ) # ────────────────────────────────────────────── # 팀/구장/경기유형 정규화 # ────────────────────────────────────────────── def normalize_team_name(name: str) -> str: """팀명 정규화 (네이버 표기 → 관리자 사이트 표기)""" return team_name_map().get(name, name) def normalize_team_code(code: str) -> str: """팀 코드 → 한글 팀명""" return team_code_map().get(code, code) def normalize_game_type(name: str) -> str: """경기 유형 정규화""" return game_type_map().get(name, name) def normalize_stadium_name(name: str) -> str: """구장명 정규화 (네이버 표기 → 관리자 사이트 select 라벨)""" return stadium_name_map().get(name, name) def normalize_position_to_number(position: str) -> str: """포지션명 → 번호 문자열 (투수→1, 포수→2, ...)""" return position_number_map().get(position, "") def normalize_position_to_defense_no(position: str) -> str: """포지션명 → 수비번호 (라인업 select 옵션 value)""" return position_to_defense_no().get(position, "") def position_label_from_number(number: str) -> str: """수비번호 → 포지션명 (역매핑)""" pos_map = position_number_map() reverse = {v: k for k, v in pos_map.items()} return reverse.get(number, "") # ────────────────────────────────────────────── # 선수명/번호 정규화 # ────────────────────────────────────────────── def normalize_player_name(name: str | None) -> str: """선수명 정규화: *, 괄호 내용 제거""" text = (name or "").replace("*", "").strip() text = re.sub(r"\([^)]*\)\s*$", "", text).strip() return text def normalize_lineup_text(text: str) -> str: """라인업 텍스트에서 순수 이름만 추출 '[10] 문보경' / '문보경 [10번]' 등 → '문보경' """ text = (text or "").strip() text = text.replace("*", "") text = re.sub(r"\[\d+(?:번)?\]", "", text) text = re.sub(r"\s*\(.*?\)\s*", "", text) text = "".join(re.findall(r"[가-힣A-Za-z]+", text)) return text.strip() def normalize_number_text(number: str | int | None) -> str: """등번호 정규화: 숫자만 추출""" text = str(number or "").strip() digits = "".join(char for char in text if char.isdigit()) if not digits: return "" return str(int(digits)) def normalize_option_player_text(text: str) -> tuple[str, str]: """select option 텍스트에서 선수명과 번호 분리 '문보경 [10번]' → ('문보경', '10') """ stripped = " ".join(text.split()) matched = re.match(r"^(.*?)\s*\[(\d+)번\]$", stripped) if matched: return normalize_player_name(matched.group(1)), normalize_number_text(matched.group(2)) return normalize_player_name(stripped), "" # ────────────────────────────────────────────── # 시간 유틸 # ────────────────────────────────────────────── def split_time(iso_time: str | None) -> tuple[str, str]: """ISO 시간 문자열에서 시/분 분리 '2026-04-14T18:30:00' → ('18', '30') """ if not iso_time: return "00", "00" from datetime import datetime dt = datetime.fromisoformat(iso_time) return f"{dt.hour:02d}", f"{dt.minute:02d}" # ────────────────────────────────────────────── # 텍스트 추론 유틸 # ────────────────────────────────────────────── def infer_option_role_hint(text: str) -> str: """select option 텍스트에서 역할 힌트 추출 '문보경 (투) [10번]' → 'pitcher' '문보경 (타)' → 'batter' """ stripped = " ".join(text.split()) matched = re.search(r"\(([^)]*)\)\s*(?:\[\d+번\])?$", stripped) if not matched: return "" hint = matched.group(1).strip() if hint == "투": return "pitcher" if hint == "타": return "batter" return "" def infer_target_role_hint(position_name: str | None) -> str: """포지션명에서 역할 힌트 추론""" if position_name == "투수": return "pitcher" return "batter"