"""backend/api/skills.py — P17-B2: Endpoint /api/skills/patterns. Espone i pattern di tool-usage aggregati dal SkillTracker per sync frontend. GET /api/skills/patterns → aggregato globale (tutte le sessioni) GET /api/skills/patterns/{session_id} → singola sessione fix(backend): corregge IndentationError presente al commit 1e1fa65 """ from __future__ import annotations import logging import math from fastapi import APIRouter, Depends, Query from .auth_guard import require_role, AuthRole _logger = logging.getLogger("agente_ai.api.skills") router = APIRouter(prefix="/api/skills", tags=["skills"], dependencies=[Depends(require_role(AuthRole.MACHINE))]) # GAP-1-fix: router-level auth def _wilson(success: int, total: int) -> float: if total == 0: return 0.5 p, z = success / total, 1.96 num = p + z*z/(2*total) - z*math.sqrt((p*(1-p) + z*z/(4*total))/total) return max(0.0, min(1.0, num / (1 + z*z/total))) @router.get("/patterns") async def get_skill_patterns( limit: int = Query(default=50, ge=1, le=200), min_uses: int = Query(default=1, ge=1, description="Minimo utilizzi totali"), ): """Pattern aggregati da tutte le sessioni in memoria, ordinati per Wilson score.""" try: from agents.skill_tracker import get_skill_tracker tracker = get_skill_tracker() agg: dict[str, dict] = {} for _sid, sess in tracker._stats.items(): for tool, stats in sess.items(): if tool not in agg: agg[tool] = {"s": 0, "f": 0, "lat": 0.0, "sessions": 0, "last": 0.0} agg[tool]["s"] += stats.success_count agg[tool]["f"] += stats.fail_count agg[tool]["lat"] += stats.total_latency_ms agg[tool]["sessions"] += 1 agg[tool]["last"] = max(agg[tool]["last"], stats.last_used) out = [] for tool, a in agg.items(): total = a["s"] + a["f"] if total < min_uses: continue out.append({ "tool": tool, "total": total, "success": a["s"], "fail": a["f"], "success_rate": round(a["s"] / total if total else 1.0, 4), "wilson_score": round(_wilson(a["s"], total), 4), "avg_latency_ms": round(a["lat"] / total if total else 0.0, 1), "sessions": a["sessions"], "last_used": a["last"], }) out.sort(key=lambda x: x["wilson_score"], reverse=True) return {"patterns": out[:limit], "total": len(out)} except Exception as exc: _logger.error("[skills/patterns] %s", exc) return {"patterns": [], "total": 0, "error": str(exc)[:200]} @router.get("/patterns/{session_id}") async def get_session_patterns(session_id: str): """Pattern per una singola sessione.""" try: from agents.skill_tracker import get_skill_tracker tracker = get_skill_tracker() sess = tracker._stats.get(session_id, {}) out = [] for tool, stats in sess.items(): total = stats.total_count out.append({ "tool": tool, "total": total, "success": stats.success_count, "fail": stats.fail_count, "success_rate": round(stats.success_rate, 4), "wilson_score": round(stats.wilson_score(), 4), "avg_latency_ms": round(stats.avg_latency_ms, 1), "last_used": stats.last_used, }) out.sort(key=lambda x: x["wilson_score"], reverse=True) return {"session_id": session_id, "patterns": out, "total": len(out)} except Exception as exc: _logger.error("[skills/patterns/%s] %s", session_id, exc) return {"session_id": session_id, "patterns": [], "total": 0, "error": str(exc)[:200]}