"""관리자 API — Bearer 토큰 인증. 경기/AI예측 upsert + 결과 입력(채점).""" from __future__ import annotations from fastapi import APIRouter, Depends, Header, HTTPException from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from ..config import settings from ..database import get_db from ..domain import now_utc from ..models import AIPrediction, Match from ..schemas import AdminAIPredictionIn, AdminSetResultIn, GenericOk from ..services.grading import apply_result router = APIRouter(prefix="/api/admin", tags=["admin"]) def require_admin(authorization: str = Header(default="")) -> None: token = authorization.removeprefix("Bearer ").strip() if not token or token != settings.admin_api_token: raise HTTPException(status_code=401, detail="UNAUTHORIZED") @router.post("/ai-predictions", response_model=GenericOk, dependencies=[Depends(require_admin)]) async def upsert_ai_prediction( body: AdminAIPredictionIn, db: AsyncSession = Depends(get_db) ) -> GenericOk: match = ( await db.execute(select(Match).where(Match.match_id == body.matchId)) ).scalars().first() if not match: raise HTTPException(status_code=404, detail="MATCH_NOT_FOUND") pred = ( await db.execute( select(AIPrediction).where( AIPrediction.match_id == body.matchId, AIPrediction.model == body.model ) ) ).scalars().first() if pred is None: pred = AIPrediction(match_id=body.matchId, model=body.model) db.add(pred) pred.outcome = body.outcome pred.score_a = body.scoreA pred.score_b = body.scoreB pred.confidence_pct = body.confidencePct pred.reason_ko = body.reasonKo pred.reason_en = body.reasonEn pred.generated_at = now_utc() pred.source = "admin" await db.commit() return GenericOk(ok=True, detail="upserted") @router.post("/result", response_model=GenericOk, dependencies=[Depends(require_admin)]) async def set_result( body: AdminSetResultIn, db: AsyncSession = Depends(get_db) ) -> GenericOk: match = ( await db.execute(select(Match).where(Match.match_id == body.matchId)) ).scalars().first() if not match: raise HTTPException(status_code=404, detail="MATCH_NOT_FOUND") graded = await apply_result(db, match, body.scoreA, body.scoreB) # 결과 메일은 워커가 result_email_delay_minutes 경과 후 발송(@finished_at 기준). return GenericOk( ok=True, detail="result set & graded", extra={"gradedPicks": graded, "emailsSentBy": "worker"}, )