from fastapi import APIRouter, Query from services.database import ( get_pattern_reliability, get_all_pattern_reliability_map, get_calibration_data, decay_kb_confidence, get_custom_patterns, # Phase 4 get_bayesian_posteriors, update_bayesian_posteriors, detect_and_save_regime_clusters, get_regime_cluster_history, get_regime_transition_matrix, get_all_pattern_embeddings_summary, ) router = APIRouter(prefix="/api/analytics", tags=["analytics"]) @router.get("/reliability") def reliability_all(): """Pattern reliability scores — all patterns with mature trade history.""" return {"reliability": get_pattern_reliability()} @router.get("/reliability/{pattern_id}") def reliability_one(pattern_id: str): result = get_pattern_reliability(pattern_id=pattern_id) if not result: return {"reliability": None} return {"reliability": result[0]} @router.get("/calibration") def calibration(days: int = Query(default=365, ge=30, le=1000)): """Predicted probability vs realized outcomes (Brier score + buckets).""" return get_calibration_data(days=days) @router.post("/kb/decay") def run_kb_decay(): """Manually trigger KB confidence decay (also runs at cycle start).""" updated = decay_kb_confidence() return {"updated": updated, "message": f"{updated} entrée(s) KB mise(s) à jour"} @router.get("/invalidation-alerts") def invalidation_alerts(): """List all active patterns that have an invalidation trigger defined.""" patterns = get_custom_patterns() alerts = [ { "pattern_id": p["id"], "pattern_name": p["name"], "invalidation_trigger": p.get("invalidation_trigger"), "invalidation_probability": p.get("invalidation_probability"), "counter_thesis": p.get("counter_thesis"), } for p in patterns if p.get("invalidation_trigger") ] return {"alerts": alerts, "count": len(alerts)} # ── Phase 4 endpoints ──────────────────────────────────────────────────────── @router.get("/bayesian") def bayesian_posteriors(): """Posteriors bayésiens Beta(α,β) par pattern avec IC 95%.""" return {"posteriors": get_bayesian_posteriors()} @router.post("/bayesian/update") def trigger_bayesian_update(): """Force la mise à jour des posteriors bayésiens.""" n = update_bayesian_posteriors() return {"updated": n, "message": f"{n} pattern(s) mis à jour"} @router.get("/regime-clusters") def regime_cluster_history(days: int = Query(default=90, ge=7, le=365)): """Historique des assignations de clusters de régimes macro.""" return {"clusters": get_regime_cluster_history(days=days)} @router.get("/regime-transitions") def regime_transition_matrix(days: int = Query(default=180, ge=30, le=730)): """Matrice de transition entre clusters de régimes.""" return get_regime_transition_matrix(days=days) @router.post("/regime-detect") def trigger_regime_detect(n_clusters: int = Query(default=4, ge=2, le=6)): """Force la détection et sauvegarde du cluster de régime courant.""" result = detect_and_save_regime_clusters(n_clusters=n_clusters) return result @router.get("/embeddings") def pattern_embeddings_summary(): """Liste des patterns avec embedding vectoriel disponible.""" return {"embeddings": get_all_pattern_embeddings_summary()}