Files
turf_saas/api_v1/routes/metrics.py
DevOps Engineer b8ef1ed35d feat: Sprint 3-4 — Refacto API /v1/ (HRT-29)
- Blueprint Flask api_v1 avec prefix /api/v1/
- GET /api/v1/health — healthcheck public
- GET /api/v1/courses/today — courses du jour (paginé, filtré)
- GET /api/v1/courses/{id}/predictions — prédictions ML pour une course
- GET /api/v1/predictions/top3 — top 3 global (free tier)
- GET /api/v1/predictions/all — toutes prédictions (premium+)
- GET /api/v1/valuebets — value bets du jour (premium+)
- GET /api/v1/backtest — résultats backtest historiques (pro)
- GET /api/v1/export/csv — export CSV prédictions/paris (pro)
- GET /api/v1/metrics — métriques perf ML (premium+)
- Swagger/OpenAPI via flasgger à /api/v1/docs
- Erreurs uniformes {status, message, code}
- Pagination limit/offset sur toutes les listes
- 42 tests d'intégration passants

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-25 18:00:54 +02:00

145 lines
5.0 KiB
Python

#!/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 auth import jwt_required_middleware, plan_required
metrics_bp = Blueprint("v1_metrics", __name__, url_prefix="/api/v1")
@metrics_bp.route("/metrics", methods=["GET"])
@jwt_required_middleware
@plan_required("premium", "pro")
def metrics():
"""
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()