142 lines
5.7 KiB
Python
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)
|