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

131
core/review_parser.py Normal file
View File

@@ -0,0 +1,131 @@
"""
core/review_parser.py — 합의판정/비디오판독 파싱
판독 텍스트에서 항목, 원래 판정, 최종 판정 등을 추출합니다.
"""
from __future__ import annotations
import re
from typing import Any
from core.config_loader import review_result_groups
def infer_review_item(detail_text: str) -> str:
"""판독 텍스트에서 사이트 표준 항목 추론
'홈런 파울 판정''홈런타구 페어 파울'
"""
dt = detail_text.replace(" ", "")
if "홈런" in dt:
return "홈런타구 페어 파울"
if "아웃" in dt or "세이프" in dt or "포스" in dt or "태그" in dt or "견제" in dt or "도루" in dt:
return "포수/태그플레이 아웃/세이프"
if "페어" in dt or "파울" in dt:
return "외야타구 페어 파울"
if "포구" in dt or "노바운드" in dt or "바운드" in dt:
return "야수의 포구"
if "몸에맞" in dt or "데드볼" in dt:
return "몸에 맞는 볼"
if "헛스윙" in dt or "스윙" in dt:
return "헛스윙"
return "기타"
def normalize_review_result_token(token: str, review_item: str) -> str | None:
"""판독 결과 토큰을 정규화
'세이프''세이프', '노스윙''불인정'
"""
token = (token or "").strip()
if not token:
return None
if review_item in {"홈런타구 페어 파울", "외야타구 페어 파울"}:
if "페어" in token:
return "페어"
if "파울" in token:
return "파울"
elif review_item in {"포수/태그플레이 아웃/세이프", "야수의 포구"}:
if "아웃" in token:
return "아웃"
if "세이프" in token:
return "세이프"
elif review_item == "헛스윙":
# '노스윙'에도 '스윙'이 포함되므로 먼저 체크
if "불인정" in token or "노스윙" in token or "공포" in token or "노 스윙" in token:
return "불인정"
if "스윙" in token or "인정" in token:
return "인정"
else:
if "불인정" in token or "실패" in token:
return "불인정"
if "인정" in token:
return "인정"
return token # 모르는 키워드 → 원문 그대로
return None
def parse_review_event_text(text: str) -> dict[str, Any]:
"""판독 텍스트를 파싱하여 구조화된 dict로 변환
입력 예: '6회초 8번타순 1구 후 18:45 ~ 18:46 (1분간) LG요청
비디오 판독: 안중열 포스아웃 관련 세이프→세이프'
"""
inning_match = re.search(r"(\d+)회(초|말)", text)
request_team_match = re.search(r"([가-힣A-Za-z]+)요청\s*(?:비디오 판독|합의 판정)", text)
# '→노 스윙' 같은 공백 정규화
normalized = re.sub(r"→([가-힣]+)\s+([가-힣]+)", r"\1\2", text)
detail_match = re.search(
r"(?:비디오 판독|합의 판정):\s*(.+?)\s*([가-힣]+)→([가-힣]+)\s*$",
normalized,
)
detail_text = detail_match.group(1).strip() if detail_match else text
review_item = infer_review_item(detail_text)
before_result = normalize_review_result_token(detail_match.group(2), review_item) if detail_match else None
after_result = normalize_review_result_token(detail_match.group(3), review_item) if detail_match else None
return {
"type": "video_review",
"text": text,
"requestInningLabel": (
f"{inning_match.group(1)}{'' if inning_match.group(2) == '' else ''}"
if inning_match else None
),
"requestTeam": request_team_match.group(1) if request_team_match else None,
"reviewItem": review_item,
"beforeResult": before_result,
"finalResult": after_result,
"isSuccess": (
"성공" if before_result and after_result and before_result != after_result
else "실패"
),
"timing": "before_pitch" if "초구 전" in text else "after_pitch",
}
def normalize_review_event(review_event: dict[str, Any]) -> dict[str, Any]:
"""판독 이벤트를 정규화
beforeResult/finalResult가 누락된 경우 텍스트에서 재파싱
"""
has_results = (
review_event.get("beforeResult") is not None
and review_event.get("finalResult") is not None
)
if review_event.get("requestInningLabel") and review_event.get("reviewItem") and has_results:
return review_event
text = review_event.get("text") or ""
parsed = parse_review_event_text(text)
parsed.update({k: v for k, v in review_event.items() if k not in parsed})
return parsed
def get_review_result_group(review_item: str) -> dict[str, Any] | None:
"""사이트에서 판독항목에 대응하는 결과 그룹 정보 반환"""
groups = review_result_groups()
return groups.get(review_item)