95 lines
4.1 KiB
Python
95 lines
4.1 KiB
Python
"""수집한 일정을 DB에 반영 — 워커(스케줄링 서버)가 매일 호출.
|
|
|
|
전체 조별리그 모드: 12개조 72경기를 적재. 결과/스코어는 전 경기 표시.
|
|
- 기존 경기: (팀쌍, 순서 무관) 매칭 → 킥오프/venue/투표시간만 갱신. 결과·예측·crowd·팀명·라운드 보존.
|
|
- 신규 경기: teams_data(48개국)로 팀 구성 + matchId 생성 + crowd 초기화하여 삽입.
|
|
종료(result) 경기는 시각 변경하지 않음.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from datetime import timedelta, timezone
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from ..config import settings
|
|
from ..models import CrowdStats, Match
|
|
from ..schedule_data import lock_at, opens_at, parse_kickoff
|
|
from ..teams_data import team_info
|
|
|
|
log = logging.getLogger("triplepick.schedule")
|
|
KST = timezone(timedelta(hours=9))
|
|
|
|
# 녹아웃 라운드 라벨 → match_id 접두어(조가 없는 토너먼트 경기 식별용)
|
|
KO_PREFIX = {"32강": "R32", "16강": "R16", "8강": "QF", "4강": "SF", "3·4위전": "P3", "결승": "F"}
|
|
|
|
|
|
def _stage_prefix(group: str, round_label: str) -> str:
|
|
"""경기 식별 접두어 — 조별리그는 조 문자, 녹아웃은 라운드 코드.
|
|
같은 두 팀이 조별리그와 결승에서 다시 만나도 서로 다른 경기로 구분된다."""
|
|
return group or KO_PREFIX.get(round_label, "KO")
|
|
|
|
|
|
async def sync_schedule(db: AsyncSession, records: list[dict]) -> dict:
|
|
if not records:
|
|
return {"updated": 0, "inserted": 0, "skipped": 0}
|
|
|
|
existing = (await db.execute(select(Match))).scalars().all()
|
|
# 순서 무관 매칭 — 소스의 홈/원정 순서가 우리와 달라도 기존 경기를 찾음(중복 방지).
|
|
# 단계(조/라운드)도 키에 포함 — 같은 두 팀의 조별리그 경기와 토너먼트 경기를 구분.
|
|
by_pair: dict[tuple, Match] = {}
|
|
for m in existing:
|
|
p = _stage_prefix(m.group, m.round_label)
|
|
by_pair[(p, m.team_a_code, m.team_b_code)] = m
|
|
by_pair[(p, m.team_b_code, m.team_a_code)] = m
|
|
|
|
updated = inserted = skipped = 0
|
|
for rec in records:
|
|
a, b = rec["teamA"], rec["teamB"]
|
|
round_label = rec.get("roundLabel", "")
|
|
# 조별리그는 조 문자, 녹아웃은 조 없음(""). roundLabel 이 있으면 토너먼트 경기.
|
|
group = "" if round_label else rec.get("group", settings.featured_group)
|
|
prefix = _stage_prefix(group, round_label)
|
|
kickoff = parse_kickoff(rec["kickoffKst"])
|
|
m = by_pair.get((prefix, a, b))
|
|
if m is not None:
|
|
if m.result_outcome is not None:
|
|
skipped += 1
|
|
continue
|
|
m.kickoff_at = kickoff
|
|
m.opens_at = opens_at(kickoff)
|
|
m.lock_at = lock_at(kickoff)
|
|
if rec.get("venue"):
|
|
m.venue = rec["venue"]
|
|
updated += 1
|
|
else:
|
|
ta, tb = team_info(a), team_info(b)
|
|
kst_date = kickoff.astimezone(KST).strftime("%Y%m%d")
|
|
match_id = f"{prefix}_{a}_{b}_{kst_date}"
|
|
new = Match(
|
|
match_id=match_id,
|
|
round_label=round_label,
|
|
group=group,
|
|
team_a_name=ta["name"], team_a_short=ta["shortName"],
|
|
team_a_code=ta["code"], team_a_flag=ta["flag"],
|
|
team_b_name=tb["name"], team_b_short=tb["shortName"],
|
|
team_b_code=tb["code"], team_b_flag=tb["flag"],
|
|
venue=rec.get("venue", ""),
|
|
hook_text=f"{ta['shortName']} vs {tb['shortName']}",
|
|
kickoff_at=kickoff,
|
|
opens_at=opens_at(kickoff),
|
|
lock_at=lock_at(kickoff),
|
|
status="scheduled",
|
|
)
|
|
db.add(new)
|
|
db.add(CrowdStats(match_id=match_id, total=0, team_a_win=0, draw=0, team_b_win=0))
|
|
by_pair[(prefix, a, b)] = new
|
|
by_pair[(prefix, b, a)] = new
|
|
inserted += 1
|
|
|
|
await db.commit()
|
|
result = {"updated": updated, "inserted": inserted, "skipped": skipped}
|
|
log.info("schedule sync: %s", result)
|
|
return result
|