refactoring
This commit is contained in:
233
automation/review_input.py
Normal file
233
automation/review_input.py
Normal file
@@ -0,0 +1,233 @@
|
||||
"""
|
||||
automation/review_input.py — 비디오 판독/합의 판정 입력
|
||||
|
||||
비디오 판독 이벤트를 관리자 사이트 팝업에 입력합니다.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from playwright.sync_api import Page
|
||||
|
||||
from core.review_parser import parse_review_event_text
|
||||
from automation.page_helpers import (
|
||||
set_select_by_partial_text,
|
||||
set_select_by_text_or_value,
|
||||
)
|
||||
|
||||
|
||||
def _normalize_review_event(review_event: dict[str, Any]) -> dict[str, Any]:
|
||||
"""텍스트 기반 이벤트 파싱 및 정규화"""
|
||||
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 _open_challenge_popup(page: Page) -> Page:
|
||||
"""비디오 판독 팝업 열기"""
|
||||
with page.expect_popup(timeout=5000) as popup_info:
|
||||
page.locator("#challengeBtn").click()
|
||||
popup = popup_info.value
|
||||
popup.wait_for_load_state("domcontentloaded")
|
||||
popup.wait_for_selector("#requestInning_0", timeout=5000)
|
||||
return popup
|
||||
|
||||
|
||||
def _select_review_final_result(
|
||||
popup: Page, row_index: int, review_item: str, final_result: str | None,
|
||||
) -> None:
|
||||
"""판독 결과 선택"""
|
||||
from core.config_loader import review_result_groups
|
||||
groups = review_result_groups()
|
||||
|
||||
# 설정에 매핑된 그룹/기본값 찾기
|
||||
group_info = groups.get(review_item)
|
||||
if group_info:
|
||||
group_key = group_info["type"]
|
||||
default_a = group_info["options"][0]
|
||||
else:
|
||||
group_key = "type3"
|
||||
default_a = "인정"
|
||||
|
||||
result_value = final_result or default_a
|
||||
|
||||
# 셀렉터 찾기 시도
|
||||
select_selector = f"#finalResult_{group_key}_{row_index}"
|
||||
if not popup.locator(select_selector).count():
|
||||
select_selector = f"#finalResult_{group_key[-1]}_{row_index}"
|
||||
if not popup.locator(select_selector).count():
|
||||
select_selector = f"#finalResult_type{group_key[-1]}_{row_index}"
|
||||
|
||||
set_select_by_text_or_value(popup, select_selector, result_value)
|
||||
|
||||
# JS 강제 이벤트 발생
|
||||
try:
|
||||
popup.locator(f"#finalResult_{row_index}").evaluate(
|
||||
"""(node, value) => {
|
||||
node.value = value;
|
||||
node.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}""",
|
||||
result_value,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _fill_review_row(popup: Page, row_index: int, review_event: dict[str, Any]) -> None:
|
||||
"""팝업의 한 행에 판독 이벤트 입력"""
|
||||
request_inning = review_event.get("requestInningLabel") or "1초"
|
||||
request_team = review_event.get("requestTeam") or popup.locator(f"#requestTeamId_{row_index} option").first.text_content() or ""
|
||||
review_item = review_event.get("reviewItem") or "기타"
|
||||
final_result = review_event.get("finalResult")
|
||||
is_success_val = "Y" if (review_event.get("isSuccess") == "성공") else "N"
|
||||
|
||||
popup.wait_for_selector(f"#requestInning_{row_index}", timeout=3000)
|
||||
set_select_by_text_or_value(popup, f"#requestInning_{row_index}", request_inning)
|
||||
|
||||
try:
|
||||
set_select_by_partial_text(popup, f"#requestTeamId_{row_index}", request_team)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if popup.is_closed(): return
|
||||
try:
|
||||
set_select_by_partial_text(popup, f"#forWhat_{row_index}", review_item)
|
||||
except Exception:
|
||||
set_select_by_text_or_value(popup, f"#forWhat_{row_index}", "기타")
|
||||
|
||||
popup.wait_for_timeout(100)
|
||||
|
||||
if popup.is_closed(): return
|
||||
_select_review_final_result(popup, row_index, review_item, final_result)
|
||||
|
||||
if popup.is_closed(): return
|
||||
set_select_by_text_or_value(popup, f"#isSuccess_{row_index}", is_success_val)
|
||||
|
||||
|
||||
def _append_review_row(popup: Page) -> int:
|
||||
"""신규 판독 행 추가"""
|
||||
before_count = popup.locator("select[id^='requestInning_']").count()
|
||||
popup.get_by_role("button", name="신규추가").click()
|
||||
popup.wait_for_function(
|
||||
"""(expectedCount) => {
|
||||
return document.querySelectorAll("select[id^='requestInning_']").length > expectedCount;
|
||||
}""",
|
||||
arg=before_count,
|
||||
timeout=3000,
|
||||
)
|
||||
row_index = popup.evaluate(
|
||||
"""() => {
|
||||
const ids = [...document.querySelectorAll("select[id^='requestInning_']")]
|
||||
.map((el) => el.id)
|
||||
.map((id) => Number(id.split("_").pop()))
|
||||
.filter((num) => Number.isFinite(num));
|
||||
return ids.length ? Math.max(...ids) : 0;
|
||||
}"""
|
||||
)
|
||||
popup.wait_for_selector(f"#requestInning_{row_index}", timeout=5000)
|
||||
return int(row_index)
|
||||
|
||||
|
||||
def _can_reuse_initial_review_row(popup: Page) -> bool:
|
||||
"""초기화된 0번 행 재사용 가능 여부"""
|
||||
try:
|
||||
row_count = popup.locator("select[id^='requestInning_']").count()
|
||||
if row_count != 1:
|
||||
return False
|
||||
|
||||
hidden_id = (popup.locator("#id_0").input_value() or "").strip()
|
||||
if hidden_id:
|
||||
return False
|
||||
|
||||
request_inning = popup.locator("#requestInning_0").input_value()
|
||||
request_team = popup.locator("#requestTeamId_0").input_value()
|
||||
review_item = popup.locator("#forWhat_0").input_value()
|
||||
final_result = (popup.locator("#finalResult_0").input_value() or "").strip()
|
||||
is_success = popup.locator("#isSuccess_0").input_value()
|
||||
|
||||
return (
|
||||
request_inning == "1"
|
||||
and review_item == "홈런타구 페어 파울"
|
||||
and final_result == "페어"
|
||||
and is_success == "Y"
|
||||
and bool(request_team)
|
||||
)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _save_review_popup(popup: Page) -> None:
|
||||
"""팝업 저장 및 닫기"""
|
||||
if popup.is_closed():
|
||||
return
|
||||
|
||||
popup.evaluate("""() => {
|
||||
window.confirm = () => true;
|
||||
window.alert = () => {};
|
||||
}""")
|
||||
|
||||
try:
|
||||
with popup.expect_response(
|
||||
re.compile(r"/manager/game/status/challenge/ajax"),
|
||||
timeout=3000,
|
||||
) as _:
|
||||
save_btn = popup.locator("#saveLog")
|
||||
if save_btn.count() > 0:
|
||||
save_btn.click(force=True)
|
||||
else:
|
||||
popup.evaluate("""() => {
|
||||
const btn = document.querySelector('#btnAdd')
|
||||
|| document.querySelector('#btnSave')
|
||||
|| [...document.querySelectorAll('button, a')].find(
|
||||
el => el.innerText.includes('입력완료') || el.innerText.includes('저장')
|
||||
);
|
||||
if (btn) btn.click();
|
||||
}""")
|
||||
except Exception:
|
||||
try:
|
||||
popup.evaluate("""() => {
|
||||
const btn = document.querySelector('#saveLog')
|
||||
|| document.querySelector('#btnAdd');
|
||||
if (btn) btn.click();
|
||||
}""")
|
||||
popup.wait_for_timeout(1000)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
if not popup.is_closed():
|
||||
popup.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def record_review_events(page: Page, review_events: list[dict[str, Any]]) -> None:
|
||||
"""비디오 판독 이벤트 전체 처리 파이프라인"""
|
||||
normalized_events = [_normalize_review_event(event) for event in (review_events or [])]
|
||||
if not normalized_events:
|
||||
return
|
||||
|
||||
popup = _open_challenge_popup(page)
|
||||
reuse_initial_row = _can_reuse_initial_review_row(popup)
|
||||
|
||||
for index, review_event in enumerate(normalized_events):
|
||||
if index == 0 and reuse_initial_row:
|
||||
row_index = 0
|
||||
else:
|
||||
row_index = _append_review_row(popup)
|
||||
|
||||
_fill_review_row(popup, row_index, review_event)
|
||||
|
||||
_save_review_popup(popup)
|
||||
page.wait_for_timeout(300)
|
||||
|
||||
try:
|
||||
page.bring_to_front()
|
||||
except Exception:
|
||||
pass
|
||||
Reference in New Issue
Block a user