정산 조회 범위 동적 확장 — 오래 방치된 미정산 경기가 영원히 live로 남는 버그 수정
settle_baseball 이 결과를 조회하는 윈도우(KBO/MLB=result_recheck_days 기본 3일, MLS=mls_days_back 기본 30일)가 고정폭이라, 최초 수집에 실패한 경기가 그 윈도우 밖으로 밀려나면 이후 영원히 매칭 대상에서 빠져 status=live로 고정되는 버그가 있었다(예: MLB_PHI_BAL_20260803, 8/2 이후 3주+ live 고착). 미정산 경기 중 가장 오래된 킥오프 날짜까지 덮도록 조회 범위를 매 틱 동적 계산(최대 120일 소급)하도록 수정. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
746eaced80
commit
11dd968ced
@ -115,13 +115,19 @@ async def _fetch_mlb(days_back: int, days_ahead: int) -> list[dict]:
|
|||||||
|
|
||||||
|
|
||||||
# ── 공개 API ────────────────────────────────────────────────────
|
# ── 공개 API ────────────────────────────────────────────────────
|
||||||
async def fetch_baseball_schedule(league: str) -> list[dict]:
|
async def fetch_baseball_schedule(league: str, days_back: int | None = None) -> list[dict]:
|
||||||
"""리그 일정+결과 수집 (취소 포함). 실패 시 빈 리스트 — 기존 일정 유지."""
|
"""리그 일정+결과 수집 (취소 포함). 실패 시 빈 리스트 — 기존 일정 유지.
|
||||||
|
|
||||||
|
days_back 기본값은 result_recheck_days(짧은 재확인 윈도우). settle_baseball 은
|
||||||
|
미정산 경기 중 가장 오래된 날짜를 덮도록 더 큰 값을 넘겨, 오래 방치된
|
||||||
|
미정산 경기(수집 실패 등으로 첫 정산을 놓친 경기)도 결국 잡히게 한다.
|
||||||
|
"""
|
||||||
|
days_back = settings.result_recheck_days if days_back is None else days_back
|
||||||
try:
|
try:
|
||||||
if league == "kbo":
|
if league == "kbo":
|
||||||
records = await _fetch_kbo(settings.result_recheck_days, settings.baseball_days_ahead)
|
records = await _fetch_kbo(days_back, settings.baseball_days_ahead)
|
||||||
elif league == "mlb":
|
elif league == "mlb":
|
||||||
records = await _fetch_mlb(settings.result_recheck_days, settings.baseball_days_ahead)
|
records = await _fetch_mlb(days_back, settings.baseball_days_ahead)
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
# 더블헤더 차수 부여 — sync·정산이 같은 seq 로 경기를 식별한다.
|
# 더블헤더 차수 부여 — sync·정산이 같은 seq 로 경기를 식별한다.
|
||||||
@ -135,6 +141,6 @@ async def fetch_baseball_schedule(league: str) -> list[dict]:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
async def fetch_baseball_results(league: str) -> list[dict]:
|
async def fetch_baseball_results(league: str, days_back: int | None = None) -> list[dict]:
|
||||||
"""확정 스코어만 → [{league, teamA, teamB, dateKst, scoreA, scoreB}]."""
|
"""확정 스코어만 → [{league, teamA, teamB, dateKst, scoreA, scoreB}]."""
|
||||||
return [r for r in await fetch_baseball_schedule(league) if "scoreA" in r]
|
return [r for r in await fetch_baseball_schedule(league, days_back) if "scoreA" in r]
|
||||||
|
|||||||
@ -105,14 +105,18 @@ def _clean(rec: dict) -> dict:
|
|||||||
return {k: v for k, v in rec.items() if not k.startswith("_")}
|
return {k: v for k, v in rec.items() if not k.startswith("_")}
|
||||||
|
|
||||||
|
|
||||||
async def fetch_mls_schedule() -> list[dict]:
|
async def fetch_mls_schedule(days_back: int | None = None) -> list[dict]:
|
||||||
"""일정+결과 수집 (취소 포함). 실패 시 빈 리스트 — 기존 일정 유지."""
|
"""일정+결과 수집 (취소 포함). 실패 시 빈 리스트 — 기존 일정 유지.
|
||||||
|
|
||||||
|
days_back 기본값은 mls_days_back. settle_baseball 이 미정산 경기 중
|
||||||
|
가장 오래된 날짜를 덮도록 더 큰 값을 넘길 수 있다(오래 방치된 미정산 경기 구제).
|
||||||
|
"""
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
today = datetime.now(KST).date()
|
today = datetime.now(KST).date()
|
||||||
# 시작 = 백필 윈도우(주말 위주 편성 공백 방지). ESPN dates 는 미국 동부 날짜라
|
# 시작 = 백필 윈도우(주말 위주 편성 공백 방지). ESPN dates 는 미국 동부 날짜라
|
||||||
# KST 범위를 놓치지 않게 하루 여유를 둔다.
|
# KST 범위를 놓치지 않게 하루 여유를 둔다.
|
||||||
start = today - timedelta(days=settings.mls_days_back + 1)
|
start = today - timedelta(days=(days_back if days_back is not None else settings.mls_days_back) + 1)
|
||||||
end = today + timedelta(days=settings.baseball_days_ahead)
|
end = today + timedelta(days=settings.baseball_days_ahead)
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient(timeout=20) as c:
|
async with httpx.AsyncClient(timeout=20) as c:
|
||||||
@ -130,9 +134,9 @@ async def fetch_mls_schedule() -> list[dict]:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
async def fetch_mls_results() -> list[dict]:
|
async def fetch_mls_results(days_back: int | None = None) -> list[dict]:
|
||||||
"""확정 스코어만 → 정산(settle) 입력."""
|
"""확정 스코어만 → 정산(settle) 입력."""
|
||||||
return [r for r in await fetch_mls_schedule() if "scoreA" in r]
|
return [r for r in await fetch_mls_schedule(days_back) if "scoreA" in r]
|
||||||
|
|
||||||
|
|
||||||
# ── 순위 (동/서부 컨퍼런스, 승점제) ─────────────────────────────
|
# ── 순위 (동/서부 컨퍼런스, 승점제) ─────────────────────────────
|
||||||
|
|||||||
@ -19,7 +19,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
from datetime import timedelta, timezone
|
||||||
|
|
||||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
@ -344,13 +344,22 @@ async def settle_baseball() -> None:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
).scalars().all()
|
).scalars().all()
|
||||||
due = [m.match_id for m in pending if ensure_aware(m.kickoff_at) <= now]
|
due_matches = [m for m in pending if ensure_aware(m.kickoff_at) <= now]
|
||||||
if not due:
|
if not due_matches:
|
||||||
continue
|
continue
|
||||||
|
due = [m.match_id for m in due_matches]
|
||||||
|
# 미정산 경기 중 가장 오래된 킥오프까지 덮도록 조회 범위를 동적 확장.
|
||||||
|
# 기본 윈도우(며칠)만 쓰면 첫 수집에 실패한 경기가 윈도우 밖으로
|
||||||
|
# 밀려나 영원히 "live"에 고정되는 문제가 있었다 — 최대 120일까지 소급.
|
||||||
|
oldest_kst_date = min(
|
||||||
|
ensure_aware(m.kickoff_at).astimezone(timezone(_KST)).date()
|
||||||
|
for m in due_matches
|
||||||
|
)
|
||||||
|
days_back = min((now.astimezone(timezone(_KST)).date() - oldest_kst_date).days + 1, 120)
|
||||||
results = (
|
results = (
|
||||||
await mls_espn.fetch_mls_results()
|
await mls_espn.fetch_mls_results(days_back=days_back)
|
||||||
if league == "mls"
|
if league == "mls"
|
||||||
else await fetch_baseball_results(league)
|
else await fetch_baseball_results(league, days_back=days_back)
|
||||||
)
|
)
|
||||||
by_key: dict[tuple, tuple[int, int]] = {}
|
by_key: dict[tuple, tuple[int, int]] = {}
|
||||||
for r in results:
|
for r in results:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user