"""모델 성능 메트릭 API.""" from __future__ import annotations from datetime import date, timedelta from fastapi import APIRouter, HTTPException, Query from sqlalchemy import text from app.db.connection import get_engine router = APIRouter(prefix="/api/metrics", tags=["metrics"]) @router.get("/{code}") def code_metrics( code: str, window_days: int = Query(default=30, ge=1, le=365), ) -> dict: """code 의 최근 window_days 윈도우 prediction_outcomes 집계.""" eng = get_engine() end = date.today() start = end - timedelta(days=window_days) with eng.connect() as conn: sym = conn.execute( text("SELECT code, name FROM symbols WHERE code = :c"), {"c": code}, ).first() if not sym: raise HTTPException(status_code=404, detail=f"unknown code: {code}") rows = conn.execute( text( """ SELECT model, horizon, COUNT(*) AS n, AVG(CASE WHEN direction_hit THEN 1.0 ELSE 0.0 END) AS hit_rate, AVG(abs_error) AS mae FROM prediction_outcomes WHERE code = :c AND resolved_at >= :s GROUP BY model, horizon ORDER BY model, horizon """ ), {"c": code, "s": start}, ).all() return { "code": sym[0], "name": sym[1], "window_days": window_days, "range": {"from": str(start), "to": str(end)}, "by_model_horizon": [ { "model": r[0], "horizon": int(r[1]), "n": int(r[2]), "hit_rate": float(r[3]) if r[3] is not None else None, "mae": float(r[4]) if r[4] is not None else None, } for r in rows ], } @router.get("") def overall_metrics( window_days: int = Query(default=30, ge=1, le=365), ) -> dict: """전체 시드 종목 누적 메트릭.""" eng = get_engine() end = date.today() start = end - timedelta(days=window_days) with eng.connect() as conn: rows = conn.execute( text( """ SELECT po.model, po.horizon, COUNT(*) AS n, AVG(CASE WHEN po.direction_hit THEN 1.0 ELSE 0.0 END) AS hit_rate, AVG(po.abs_error) AS mae FROM prediction_outcomes po WHERE po.resolved_at >= :s GROUP BY po.model, po.horizon ORDER BY po.model, po.horizon """ ), {"s": start}, ).all() return { "window_days": window_days, "range": {"from": str(start), "to": str(end)}, "by_model_horizon": [ { "model": r[0], "horizon": int(r[1]), "n": int(r[2]), "hit_rate": float(r[3]) if r[3] is not None else None, "mae": float(r[4]) if r[4] is not None else None, } for r in rows ], }