o2o-triple-pick/backend/app/routers/matches.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

71 lines
2.3 KiB
Python

"""공개 읽기 API — 경기 목록 / 경기 상세 (AI예측 + crowd 포함)."""
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from ..database import get_db
from ..domain import match_out
from ..models import Match
from ..schemas import MatchOut
from ..services.baseball_details import fetch_live, get_extras
router = APIRouter(prefix="/api/matches", tags=["matches"])
@router.get("", response_model=list[MatchOut])
async def list_matches(
lang: str = Query("ko"),
league: str = Query("", description="wc | kbo | mlb — 빈값이면 전체"),
db: AsyncSession = Depends(get_db),
) -> list[MatchOut]:
q = (
select(Match)
.options(selectinload(Match.predictions), selectinload(Match.crowd))
.order_by(Match.kickoff_at)
)
if league:
q = q.where(Match.league == league)
rows = (await db.execute(q)).scalars().all()
extras = await get_extras(db, rows)
return [match_out(m, lang, extras=extras.get(m.match_id)) for m in rows]
@router.get("/{match_id}", response_model=MatchOut)
async def get_match(
match_id: str,
lang: str = Query("ko"),
db: AsyncSession = Depends(get_db),
) -> MatchOut:
m = (
await db.execute(
select(Match)
.where(Match.match_id == match_id)
.options(selectinload(Match.predictions), selectinload(Match.crowd))
)
).scalars().first()
if not m:
raise HTTPException(status_code=404, detail="MATCH_NOT_FOUND")
extras = await get_extras(db, [m])
return match_out(m, lang, extras=extras.get(m.match_id))
@router.get("/{match_id}/live")
async def get_live(
match_id: str,
db: AsyncSession = Depends(get_db),
) -> dict:
"""라이브 뷰 (kbo=네이버 relay, mlb=공식 feed/live, mls=ESPN). 15초 TTL 캐시."""
m = await db.get(Match, match_id)
if not m:
raise HTTPException(status_code=404, detail="MATCH_NOT_FOUND")
if m.league == "mls":
from ..services.mls_espn import fetch_live_mls
return await fetch_live_mls(m)
if m.league not in ("kbo", "mlb"):
return {"available": False}
return await fetch_live(m)