- 일정·결과·컨퍼런스 순위·프리뷰(폼/시즌전적/맞대결/머니라인) 수집 - 라이브 카드: 스코어·경기시간·득점/카드·선발 라인업(피치 뷰)·교체 현황 - 문자중계: ESPN commentary 규칙 기반 한글 변환 (미지원 템플릿은 팀명만 한글화) - MLS 레코드는 야구와 키 구조가 같아 sync_baseball_schedule/settle 재사용 - AI 예측 데이터 블록에 MLS 전용 컨텍스트 추가 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
71 lines
2.3 KiB
Python
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)
|