""" core/runner_classifier.py — 주루 이벤트 분류 네이버 리포트의 주루 이벤트를 분석하여 관리자 사이트에서 선택해야 할 라디오 버튼 라벨을 결정합니다. """ from __future__ import annotations from typing import Any from core.config_loader import runner_event_map def classify_runner_event(event_type: str) -> str | None: """주루 이벤트 타입 → 사이트 라벨 (기본 매핑)""" return runner_event_map().get(event_type or "") def infer_runner_action_label( event: dict[str, Any], runner_event: dict[str, Any], ) -> str | None: """주루 이벤트를 종합적으로 추론하여 사이트 라벨 반환 리포트 action_label, event_type, event_text, result_type 등을 모두 분석. """ # 0. 리포트에 명시된 라벨이 있으면 최우선 if "action_label" in runner_event: return runner_event["action_label"] event_type = runner_event.get("type") or "" event_text = runner_event.get("text") or "" result_type = ((event.get("result") or {}).get("type") or "") result_text = ((event.get("result") or {}).get("text") or "") # 이중도루 실패 + 진루 if "이중도루 실패" in event_text and "진루" in event_text: return "기타 진루" if "도루" in event_text and "실패" in event_text and "진루" in event_text: return "기타 진루" # 견제 아웃 if event_type == "pickoff_out" or "견제사" in event_text: return "견제 아웃" # 도루 실패 if event_type == "steal_fail": return "도루시도 아웃" if "이중도루 실패" in event_text and "아웃" in event_text: return "도루시도 아웃" # 도루 + 실책 진루 if "도루" in event_text and "실책" in event_text and ("진루" in event_text or event_type == "error_advance"): return "도루성공&실책" # 도루 if "도루" in event_text: if "실패" in event_text: return "도루시도 아웃" return "도루성공" # 낫아웃 + 폭투/포일 if "낫아웃" in result_text and event_type == "wild_pitch_advance": return "폭투 낫아웃 진루" if "낫아웃" in result_text and event_type == "passed_ball_advance": return "포일 낫아웃 진루" # 포일 진루 if "포일" in event_text and ("진루" in event_text or event_type == "passed_ball_advance"): return "포일-진루성공" # 실책으로 진루 if "실책으로" in event_text: return "수비 실책" # 안타/아웃 상황 → 일반 진루 play_types = { "single", "double", "triple", "home_run", "out", "strikeout", "play", "sacrifice_fly", "sacrifice_bunt", "ground_out", "fly_out", } if result_type in play_types and event_type in {"advance", "score"}: return "일반 진루" # 볼넷 상황 → 볼넷 진루 walk_types = {"walk", "intentional_walk", "hit_by_pitch"} if result_type in walk_types and event_type in {"advance", "score"}: return "볼넷 진루" # 기본: 일반 진루 if event_type in {"advance", "score"}: return "일반 진루" # 최종 폴백: config 매핑 return classify_runner_event(event_type) def get_runner_area_type(event: dict[str, Any], runner_event: dict[str, Any]) -> int: """주루 이벤트의 입력 영역 타입 결정 1 = 진루 영역 (일반 진루, 볼넷 진루 등) 2 = 액션 영역 (도루, 견제, 폭투, 포일 등) """ event_text = runner_event.get("text") or "" action_keywords = ["도루", "견제", "폭투", "포일", "태그아웃", "포스아웃"] if any(k in event_text for k in action_keywords): return 2 return 1 def split_complex_runner_event( runner_event: dict[str, Any], ) -> tuple[dict[str, Any], dict[str, Any] | None]: """복합 주루 이벤트를 두 개로 분리 예: '도루성공 후 수비실책 진루' → (도루, 실책진루) """ text = runner_event.get("text") or "" if "실책" not in text and "/" not in text: return runner_event, None # '도루성공&실책' 같은 패턴 if "도루" in text and "실책" in text and "진루" in text: first = dict(runner_event) first["type"] = "steal" first["text"] = text second = dict(runner_event) second["type"] = "error_advance" second["text"] = text return first, second return runner_event, None