#!/usr/bin/env python3 """ SaaS API v1 Blueprint — /api/v1/* Stats, prédictions, résumés pour le dashboard SaaS. Sprint 4-5 — HRT-30 """ from flask import Blueprint, request, jsonify import sqlite3 import os from datetime import datetime from saas_auth import require_auth DB_PATH = os.environ.get("TURF_SAAS_DB", "/home/h3r7/turf_saas/turf_saas.db") api_v1_bp = Blueprint("api_v1", __name__, url_prefix="/api/v1") def get_db(): conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row return conn def plan_allows(user_plan: str, required: str) -> bool: order = {"free": 0, "premium": 1, "pro": 2} return order.get(user_plan, 0) >= order.get(required, 0) # ─── Stats ──────────────────────────────────────────────────────────────────── @api_v1_bp.route("/stats/summary", methods=["GET"]) @require_auth def stats_summary(): """GET /api/v1/stats/summary — résumé dashboard.""" today = datetime.now().strftime("%Y-%m-%d") conn = get_db() try: # Courses today courses_today = ( conn.execute( "SELECT COUNT(DISTINCT num_reunion||'-'||num_course) FROM ml_predictions_cache WHERE date=?", (today,), ).fetchone()[0] or 0 ) # Value bets today value_bets_today = ( conn.execute( "SELECT COUNT(*) FROM ml_predictions_cache WHERE date=? AND is_value_bet=1", (today,), ).fetchone()[0] or 0 ) # Accuracy top3 (30 days) acc_row = conn.execute(""" SELECT CAST(SUM(CASE WHEN p.ordre_arrivee BETWEEN 1 AND 3 AND m.recommendation='top3' THEN 1 ELSE 0 END) AS FLOAT) / NULLIF(COUNT(CASE WHEN m.recommendation='top3' THEN 1 END), 0) * 100 AS acc FROM ml_predictions_cache m JOIN pmu_partants p ON m.horse_name=p.nom AND m.date=p.date_programme WHERE m.date >= date('now', '-30 days') """).fetchone() accuracy_top3 = round(acc_row[0], 1) if acc_row and acc_row[0] else None # Next race next_race = conn.execute( "SELECT heure, hippodrome FROM ml_predictions_cache WHERE date=? AND heure IS NOT NULL ORDER BY heure LIMIT 1", (today,), ).fetchone() conn.close() return jsonify( { "courses_today": courses_today, "value_bets_today": value_bets_today, "accuracy_top3": accuracy_top3, "next_race_time": next_race["heure"] if next_race else None, "next_race_hippodrome": next_race["hippodrome"] if next_race else None, } ), 200 except Exception as e: conn.close() return jsonify( {"error": str(e), "courses_today": 0, "value_bets_today": 0} ), 200 # ─── Predictions ────────────────────────────────────────────────────────────── @api_v1_bp.route("/predictions/today", methods=["GET"]) @require_auth def predictions_today(): """GET /api/v1/predictions/today — prédictions du jour selon le plan.""" user = request.current_user plan = user.get("plan", "free") today = datetime.now().strftime("%Y-%m-%d") conn = get_db() try: rows = conn.execute( """ SELECT horse_name, horse_number, odds, prob_top1, prob_top3, ml_score, recommendation, is_value_bet, is_outlier, race_label, race_name, hippodrome, discipline, distance, heure, risque_label, risque_score, num_reunion, num_course FROM ml_predictions_cache WHERE date=? ORDER BY num_reunion, num_course, ml_score DESC """, (today,), ).fetchall() conn.close() predictions = [dict(r) for r in rows] # Free plan: return only 1 race if plan == "free": if predictions: first = predictions[0] first_key = (first["num_reunion"], first["num_course"]) predictions = [ p for p in predictions if (p["num_reunion"], p["num_course"]) == first_key ] # Mask value bet flag in free for p in predictions: p["is_value_bet"] = 0 # Premium/Pro: full predictions return jsonify( { "date": today, "plan": plan, "count": len(predictions), "predictions": predictions, } ), 200 except Exception as e: conn.close() return jsonify({"error": str(e), "predictions": []}), 200 @api_v1_bp.route("/predictions/race/", methods=["GET"]) @require_auth def predictions_race(race_label): """GET /api/v1/predictions/race/