"""공개 읽기 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). 15초 TTL 캐시.""" m = await db.get(Match, match_id) if not m: raise HTTPException(status_code=404, detail="MATCH_NOT_FOUND") if m.league not in ("kbo", "mlb"): return {"available": False} return await fetch_live(m)