refactoring

This commit is contained in:
2026-05-02 16:24:42 +09:00
parent 296adf3073
commit 859c39fe0c
194 changed files with 5267 additions and 0 deletions

150
core/normalizer.py Normal file
View File

@@ -0,0 +1,150 @@
"""
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"