Files
baseball-automation/automation/game_end_input.py
2026-05-02 16:24:42 +09:00

142 lines
5.7 KiB
Python

"""
automation/game_end_input.py — 경기 종료 처리
투수들의 승패/홀드/세이브 기록 등을 경기 종료 팝업에 입력하고 저장합니다.
"""
from __future__ import annotations
from typing import Any
from playwright.sync_api import Page
from automation.page_helpers import show_debug_overlay
from automation.lineup_input import normalize_lineup_text
def _open_game_end_popup(page: Page) -> None:
"""경기 종료 팝업 열기"""
page.evaluate("""() => { window.confirm = () => true; window.alert = () => {}; }""")
page.locator("#gameEndBtn").click(force=True)
page.wait_for_selector("#btnGameEnd", timeout=5000)
page.wait_for_selector("input[name^='homeTeamPitcher_'], input[name^='awayTeamPitcher_']", timeout=5000)
def _get_game_end_pitcher_rows(page: Page) -> dict[str, list[dict[str, Any]]]:
"""팝업 내 홈/원정 투수 리스트 추출"""
return page.evaluate(
"""() => {
const rowsFor = (nameAttr) => {
return [...document.querySelectorAll(`input[name='${nameAttr}']`)].map((input, idx) => {
const tr = input.closest('tr');
const firstTd = tr ? tr.querySelector('td') : null;
return {
idx,
name: firstTd ? firstTd.textContent.trim() : '',
};
});
};
return {
home: rowsFor('home_player_id'),
away: rowsFor('away_player_id'),
};
}"""
)
def _select_game_end_role(page: Page, side: str, idx: int, role_value: str) -> None:
"""특정 투수의 역할(승/패/홀/세 등) 라디오 버튼 선택"""
selector = f"input[name='{side}TeamPitcher_{idx}'][value='{role_value}']"
ok = page.evaluate(
"""(selector) => {
const node = document.querySelector(selector);
if (!node) return false;
node.disabled = false;
node.checked = true;
node.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
node.dispatchEvent(new Event('change', { bubbles: true }));
return node.checked === true;
}""",
selector,
)
if not ok:
page.locator(selector).click(force=True)
def _check_game_end_blown_save(page: Page, side: str, idx: int) -> None:
"""블론세이브 체크박스 선택"""
selector = f"input[name='{side}BlownSave_{idx}']"
ok = page.evaluate(
"""(selector) => {
const node = document.querySelector(selector);
if (!node) return false;
node.disabled = false;
node.checked = true;
node.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
node.dispatchEvent(new Event('change', { bubbles: true }));
return node.checked === true;
}""",
selector,
)
if not ok:
page.locator(selector).check(force=True)
def fill_game_end_pitching(page: Page, report: dict[str, Any]) -> None:
"""리포트의 투수 요약을 바탕으로 경기 종료 팝업 폼 채우기"""
_open_game_end_popup(page)
rows = _get_game_end_pitcher_rows(page)
summary = report.get("pitching_summary") or {}
lineups = report.get("lineups") or {}
home_starter = normalize_lineup_text(((lineups.get("home_team") or {}).get("starter_pitcher") or {}).get("name") or "")
away_starter = normalize_lineup_text(((lineups.get("away_team") or {}).get("starter_pitcher") or {}).get("name") or "")
winners = {normalize_lineup_text(name) for name in (summary.get("승리투수") or [])}
losers = {normalize_lineup_text(name) for name in (summary.get("패전투수") or [])}
holds = {normalize_lineup_text(name) for name in (summary.get("홀드") or [])}
saves = {normalize_lineup_text(name) for name in (summary.get("세이브") or [])}
blown_saves = {normalize_lineup_text(name) for name in (summary.get("블론세이브") or [])}
fixed_roles = winners | losers | holds | saves
for side, side_rows in rows.items():
starter_name = home_starter if side == "home" else away_starter
for row in side_rows:
name = normalize_lineup_text(row.get("name") or "")
idx = int(row["idx"])
if name in winners:
_select_game_end_role(page, side, idx, "wins")
elif name in losers:
_select_game_end_role(page, side, idx, "loses")
elif name in saves:
_select_game_end_role(page, side, idx, "save")
elif name in holds:
_select_game_end_role(page, side, idx, "holds")
elif name and name != starter_name and name not in fixed_roles:
_select_game_end_role(page, side, idx, "re")
if name in blown_saves:
_check_game_end_blown_save(page, side, idx)
page.wait_for_timeout(300)
show_debug_overlay(
page,
[
"게임종료 팝업 입력 준비 완료",
f"승리: {', '.join(summary.get('승리투수') or []) or '-'}",
f"패전: {', '.join(summary.get('패전투수') or []) or '-'}",
f"홀드: {', '.join(summary.get('홀드') or []) or '-'}",
f"세이브: {', '.join(summary.get('세이브') or []) or '-'}",
f"블론세이브: {', '.join(summary.get('블론세이브') or []) or '-'}",
],
)
def submit_game_end(page: Page) -> None:
"""경기 종료 최종 완료(저장) 버튼 클릭"""
page.evaluate("""() => { window.confirm = () => true; window.alert = () => {}; }""")
page.locator("#btnGameEnd").click(force=True)
page.wait_for_timeout(1500)