o2o-triple-pick/backend/app/routers/standings.py
jwkim 74de57fc1c feat(mls): MLS 리그 추가 — ESPN 비공식 API 연동
- 일정·결과·컨퍼런스 순위·프리뷰(폼/시즌전적/맞대결/머니라인) 수집
- 라이브 카드: 스코어·경기시간·득점/카드·선발 라인업(피치 뷰)·교체 현황
- 문자중계: ESPN commentary 규칙 기반 한글 변환 (미지원 템플릿은 팀명만 한글화)
- MLS 레코드는 야구와 키 구조가 같아 sync_baseball_schedule/settle 재사용
- AI 예측 데이터 블록에 MLS 전용 컨텍스트 추가

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 13:47:13 +09:00

66 lines
2.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""리그 순위표 API — 워커가 캐싱한 standings:{league} 를 팀 정보와 합쳐 서빙.
KBO: 단일 테이블(10팀, 순위순). MLB: 디비전(AL/NL × 동·중·서) 6그룹.
MLS: 컨퍼런스(동/서부) 2그룹 — 승점제.
캐시가 아직 없으면 빈 groups 를 반환한다(프론트는 안내 문구 표시).
"""
from __future__ import annotations
from fastapi import APIRouter, Depends, Query
from sqlalchemy.ext.asyncio import AsyncSession
from ..database import get_db
from ..models import DataCache
from ..teams_baseball import team_info
router = APIRouter(prefix="/api/standings", tags=["standings"])
# MLB 디비전 표시 순서 (AL 동→중→서, NL 동→중→서)
_MLB_DIV_ORDER = ["ALE", "ALC", "ALW", "NLE", "NLC", "NLW"]
# MLS 컨퍼런스 표시 순서 (동부 → 서부)
_MLS_CONF_ORDER = ["EAST", "WEST"]
@router.get("")
async def get_standings(
league: str = Query(..., description="kbo | mlb | mls"),
db: AsyncSession = Depends(get_db),
) -> dict:
if league not in ("kbo", "mlb", "mls"):
return {"league": league, "updatedAt": None, "groups": []}
row = await db.get(DataCache, f"standings:{league}")
table: dict = row.payload if row else {}
rows = [{**team_info(league, code), **st} for code, st in table.items()]
if league == "kbo":
rows.sort(key=lambda r: r.get("rank") or 99)
groups = [{"key": None, "rows": rows}] if rows else []
elif league == "mls":
by_conf: dict[str, list] = {}
for r in rows:
by_conf.setdefault(r.get("div") or "", []).append(r)
for lst in by_conf.values():
lst.sort(key=lambda r: r.get("rank") or 99)
groups = [
{"key": c, "rows": by_conf[c]} for c in _MLS_CONF_ORDER if c in by_conf
]
else:
by_div: dict[str, list] = {}
for r in rows:
by_div.setdefault(r.get("div") or "", []).append(r)
for lst in by_div.values():
lst.sort(key=lambda r: r.get("rank") or 99)
groups = [
{"key": d, "rows": by_div[d]} for d in _MLB_DIV_ORDER if d in by_div
]
if not groups and rows:
# 캐시가 div 주입 이전 버전이면 전체 승률순 단일 그룹으로 폴백
rows.sort(key=lambda r: -float(r.get("wra") or 0))
groups = [{"key": None, "rows": rows}]
return {
"league": league,
"updatedAt": row.fetched_at.isoformat() if row and row.fetched_at else None,
"groups": groups,
}