- 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>
164 lines
5.0 KiB
Python
164 lines
5.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Predictions routes for API v1.
|
|
|
|
GET /api/v1/predictions/top3 — Top 3 global du jour (free tier, 1/day limit)
|
|
GET /api/v1/predictions/all — Toutes prédictions (premium+)
|
|
"""
|
|
|
|
from datetime import datetime, timedelta
|
|
from flask import Blueprint, jsonify, request
|
|
|
|
from api_v1.utils import (
|
|
get_db,
|
|
table_exists,
|
|
internal_error,
|
|
not_found,
|
|
get_pagination_params,
|
|
paginate_query,
|
|
)
|
|
from auth import jwt_required_middleware, plan_required, free_daily_limit_check
|
|
|
|
predictions_bp = Blueprint("v1_predictions", __name__, url_prefix="/api/v1/predictions")
|
|
|
|
|
|
def _fetch_ml_predictions(conn, date: str, limit: int = None, offset: int = 0):
|
|
"""Shared helper — returns rows from ml_predictions_cache."""
|
|
if not table_exists(conn, "ml_predictions_cache"):
|
|
return [], 0
|
|
|
|
count_row = conn.execute(
|
|
"SELECT COUNT(*) as cnt FROM ml_predictions_cache WHERE date = ?",
|
|
(date,),
|
|
).fetchone()
|
|
total = count_row["cnt"] if count_row else 0
|
|
|
|
sql = """SELECT
|
|
race_label, hippodrome, discipline, distance, heure,
|
|
horse_name, horse_number, odds, prob_top1, prob_top3,
|
|
ml_score, recommendation, is_value_bet, risque_label, risque_score
|
|
FROM ml_predictions_cache
|
|
WHERE date = ?
|
|
ORDER BY ml_score DESC"""
|
|
params = [date]
|
|
|
|
if limit is not None:
|
|
sql += " LIMIT ? OFFSET ?"
|
|
params += [limit, offset]
|
|
|
|
rows = conn.execute(sql, params).fetchall()
|
|
return [dict(r) for r in rows], total
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────
|
|
# GET /api/v1/predictions/top3
|
|
# ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
@predictions_bp.route("/top3", methods=["GET"])
|
|
@jwt_required_middleware
|
|
@free_daily_limit_check
|
|
def predictions_top3():
|
|
"""
|
|
Top 3 prédictions du jour
|
|
---
|
|
tags:
|
|
- Prédictions
|
|
summary: Top 3 chevaux avec le meilleur score ML du jour (free tier inclus)
|
|
security:
|
|
- Bearer: []
|
|
parameters:
|
|
- name: date
|
|
in: query
|
|
type: string
|
|
format: date
|
|
description: Date au format YYYY-MM-DD (défaut aujourd'hui)
|
|
responses:
|
|
200:
|
|
description: Top 3 prédictions ML du jour
|
|
401:
|
|
description: Token invalide
|
|
429:
|
|
description: Limite quotidienne free tier atteinte
|
|
"""
|
|
date_param = request.args.get("date", datetime.now().strftime("%Y-%m-%d"))
|
|
|
|
conn = get_db()
|
|
try:
|
|
predictions, _ = _fetch_ml_predictions(conn, date_param, limit=3, offset=0)
|
|
|
|
return jsonify(
|
|
{
|
|
"status": "ok",
|
|
"date": date_param,
|
|
"top3": predictions,
|
|
}
|
|
), 200
|
|
except Exception as e:
|
|
return internal_error(str(e))
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────
|
|
# GET /api/v1/predictions/all
|
|
# ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
@predictions_bp.route("/all", methods=["GET"])
|
|
@jwt_required_middleware
|
|
@plan_required("premium", "pro")
|
|
def predictions_all():
|
|
"""
|
|
Toutes les prédictions du jour
|
|
---
|
|
tags:
|
|
- Prédictions
|
|
summary: Toutes les prédictions ML du jour — accès premium et pro uniquement
|
|
security:
|
|
- Bearer: []
|
|
parameters:
|
|
- name: date
|
|
in: query
|
|
type: string
|
|
format: date
|
|
description: Date au format YYYY-MM-DD (défaut aujourd'hui)
|
|
- name: limit
|
|
in: query
|
|
type: integer
|
|
default: 20
|
|
- name: offset
|
|
in: query
|
|
type: integer
|
|
default: 0
|
|
responses:
|
|
200:
|
|
description: Toutes les prédictions ML
|
|
401:
|
|
description: Token invalide
|
|
403:
|
|
description: Plan insuffisant (premium ou pro requis)
|
|
"""
|
|
date_param = request.args.get("date", datetime.now().strftime("%Y-%m-%d"))
|
|
limit, offset = get_pagination_params(default_limit=50, max_limit=500)
|
|
|
|
conn = get_db()
|
|
try:
|
|
predictions, total = _fetch_ml_predictions(
|
|
conn, date_param, limit=limit, offset=offset
|
|
)
|
|
pagination = paginate_query(predictions, total, limit, offset)
|
|
|
|
return jsonify(
|
|
{
|
|
"status": "ok",
|
|
"date": date_param,
|
|
"predictions": predictions,
|
|
**pagination,
|
|
}
|
|
), 200
|
|
except Exception as e:
|
|
return internal_error(str(e))
|
|
finally:
|
|
conn.close()
|