#!/usr/bin/env python3 """ Metrics route for API v1. GET /api/v1/metrics — Métriques performances ML (premium+) """ from datetime import datetime, timedelta from flask import Blueprint, jsonify, request from api_v1.utils import ( get_db, table_exists, internal_error, bad_request, ) from saas_auth import require_auth as jwt_required_middleware from flask import request as _req metrics_bp = Blueprint("v1_metrics", __name__, url_prefix="/api/v1") @metrics_bp.route("/metrics", methods=["GET"]) @jwt_required_middleware def metrics(): # plan check: premium or pro (or TEST_MODE via plan='pro' in DB) user = getattr(_req, 'current_user', None) or {} plan = user.get('plan', 'free') if isinstance(user, dict) else 'free' if plan not in ('premium', 'pro'): from flask import jsonify as _j return _j({'error': 'Plan premium ou pro requis'}), 403 """ Métriques ML --- tags: - Métriques summary: Métriques de performance du modèle ML (precision, ROI, top-3 rate) — premium+ security: - Bearer: [] parameters: - name: days in: query type: integer default: 30 description: Nombre de jours à analyser (max 365) responses: 200: description: Métriques de performance ML 401: description: Token invalide 403: description: Plan insuffisant (premium ou pro requis) """ try: days = int(request.args.get("days", 30)) except (ValueError, TypeError): return bad_request("Paramètre 'days' doit être un entier") days = max(1, min(days, 365)) end_date = datetime.now().strftime("%Y-%m-%d") start_date = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d") conn = get_db() try: # ── Bet-level metrics from bet_results ── bet_metrics = { "available": False, "period": {"start": start_date, "end": end_date, "days": days}, } ml_metrics = {"available": False} daily_stats = [] if table_exists(conn, "bet_results"): row = conn.execute( """SELECT COUNT(*) AS total, SUM(CASE WHEN resultat='GAGNE' THEN 1 ELSE 0 END) AS gagne, SUM(mise) AS mise, SUM(gain) AS gain FROM bet_results WHERE date BETWEEN ? AND ?""", (start_date, end_date), ).fetchone() total = row["total"] or 0 gagne = row["gagne"] or 0 mise = float(row["mise"] or 0) gain = float(row["gain"] or 0) bet_metrics = { "available": True, "period": {"start": start_date, "end": end_date, "days": days}, "total_bets": total, "precision_pct": round(gagne / total * 100, 2) if total > 0 else 0.0, "roi_pct": round((gain - mise) / mise * 100, 2) if mise > 0 else 0.0, "mise_totale": round(mise, 2), "gain_total": round(gain, 2), } # ── ML predictions cache metrics ── if table_exists(conn, "ml_predictions_cache"): cache_row = conn.execute( """SELECT COUNT(*) AS total, SUM(is_value_bet) AS value_bets, AVG(prob_top1) AS avg_prob_top1, AVG(prob_top3) AS avg_prob_top3, AVG(ml_score) AS avg_ml_score FROM ml_predictions_cache WHERE date BETWEEN ? AND ?""", (start_date, end_date), ).fetchone() if cache_row and cache_row["total"]: ml_metrics = { "available": True, "total_predictions": cache_row["total"], "value_bets": cache_row["value_bets"] or 0, "avg_prob_top1": round(float(cache_row["avg_prob_top1"] or 0), 4), "avg_prob_top3": round(float(cache_row["avg_prob_top3"] or 0), 4), "avg_ml_score": round(float(cache_row["avg_ml_score"] or 0), 4), } # ── Daily breakdown ── if table_exists(conn, "daily_stats"): daily_rows = conn.execute( """SELECT date, total_bets, bets_gagne, precision_pct, roi_pct, mise_totale, gain_total FROM daily_stats WHERE date BETWEEN ? AND ? ORDER BY date DESC LIMIT 60""", (start_date, end_date), ).fetchall() daily_stats = [dict(r) for r in daily_rows] return jsonify( { "status": "ok", "period": {"start": start_date, "end": end_date, "days": days}, "bet_metrics": bet_metrics, "ml_metrics": ml_metrics, "daily": daily_stats, } ), 200 except Exception as e: return internal_error(str(e)) finally: conn.close()