feat(settle): 킥오프+3h 대기 제거 — 소스가 FINISHED 주면 즉시 정산

settle 게이트를 '킥오프 지남'으로 변경. football-data가 FINISHED 보고 시
다음 폴링(최대 5분) 내 자동 종료·채점·메일. 경기 중(IN_PLAY)엔 자동 스킵.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
develop
hbyang 2026-06-12 10:25:56 +09:00
parent 54ed8bf10d
commit 7a2affe10a
1 changed files with 9 additions and 9 deletions

View File

@ -129,19 +129,20 @@ async def generate_ai_predictions(only_missing: bool = True) -> None:
# ── 2.5) 결과 자동 정산 (관리자 입력 불필요) ──────────────── # ── 2.5) 결과 자동 정산 (관리자 입력 불필요) ────────────────
async def settle_matches() -> None: async def settle_matches() -> None:
"""킥오프 + result_settle_hours 경과한 미정산 경기를 외부 스코어로 자동 채점. """킥오프가 지난 미정산 경기를, 소스가 FINISHED 로 보고하는 즉시 채점·종료.
외부 소스(openfootball/football-data)에서 확정 스코어를 수집해 apply_result 킥오프 이후의 미정산 경기를 폴링하되, 외부 소스(football-data) FINISHED
호출 채점·유저 포인트 집계·finished_at 기록. 직후 결과 메일을 발송한다. 확정 스코어를 때만 apply_result 채점·유저 포인트 집계·finished_at·메일.
소스에 스코어가 아직 없으면 다음 틱에 재시도. 경기 (IN_PLAY)에는 FINISHED 아니라 결과에 잡혀 자동 스킵된다(조기 종료 없음).
아직 FINISHED 아니면 다음 (5) 재시도 종료 최대 1 반영.
""" """
now = now_utc() now = now_utc()
cutoff = now - timedelta(hours=settings.result_settle_hours)
async with SessionLocal() as db: async with SessionLocal() as db:
pending = ( pending = (
await db.execute(select(Match).where(Match.result_outcome.is_(None))) await db.execute(select(Match).where(Match.result_outcome.is_(None)))
).scalars().all() ).scalars().all()
due = [m.match_id for m in pending if ensure_aware(m.kickoff_at) <= cutoff] # 킥오프가 지난 경기만 (소스가 FINISHED 줄 수 있는 시점)
due = [m.match_id for m in pending if ensure_aware(m.kickoff_at) <= now]
if due: if due:
results = await fetch_results() results = await fetch_results()
@ -163,7 +164,7 @@ async def settle_matches() -> None:
settled += 1 settled += 1
log.info("settle: %s 자동 정산 %d-%d", mid, sc[0], sc[1]) log.info("settle: %s 자동 정산 %d-%d", mid, sc[0], sc[1])
if settled == 0: if settled == 0:
log.info("settle: %d경기 정산 대기 — 외부 스코어 아직 없음", len(due)) log.info("settle: %d경기 진행/대기 — 아직 FINISHED 아님", len(due))
await maybe_send_result_emails() await maybe_send_result_emails()
@ -284,14 +285,13 @@ async def main() -> None:
sched.start() sched.start()
log.info( log.info(
"scheduling-server started — schedule sync daily %02d:%02d · status every %ss · " "scheduling-server started — schedule sync daily %02d:%02d · status every %ss · "
"AI gen daily %02d:%02d %s · auto-settle 킥오프+%dh (every %ss) · 맞춘사람만=%s", "AI gen daily %02d:%02d %s · auto-settle on FINISHED (poll every %ss) · 맞춘사람만=%s",
settings.schedule_sync_hour, settings.schedule_sync_hour,
settings.schedule_sync_minute, settings.schedule_sync_minute,
settings.status_tick_seconds, settings.status_tick_seconds,
settings.ai_generate_hour, settings.ai_generate_hour,
settings.ai_generate_minute, settings.ai_generate_minute,
settings.timezone, settings.timezone,
settings.result_settle_hours,
settings.settle_tick_seconds, settings.settle_tick_seconds,
settings.result_email_correct_only, settings.result_email_correct_only,
) )