#!/usr/bin/env python3 """ ml_feedback.py — API routes pour le feedback loop ML (turf_saas). Routes: POST /api/v1/ml/feedback/run — Déclenche le feedback loop (admin uniquement) GET /api/v1/ml/feedback/stats — Stats performances par stratégie Sécurité admin : token via variable d'environnement ML_ADMIN_TOKEN ou plan "pro" en fallback pour les stats. """ import os import sys from datetime import datetime from flask import Blueprint, jsonify, request, g # Ajoute le répertoire parent de api_v1 dans le path pour importer ml_feedback_saas sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) from api_v1.utils import get_db, internal_error, bad_request from auth import jwt_required_middleware, plan_required ml_feedback_bp = Blueprint("v1_ml_feedback", __name__, url_prefix="/api/v1/ml/feedback") # Token admin interne — configurable via variable d'environnement ML_ADMIN_TOKEN = os.environ.get("ML_ADMIN_TOKEN", "") def _check_admin(req): """Vérifie le token admin via header X-Admin-Token ou Authorization Bearer (plan pro).""" # 1. Token interne (scheduler/cron) admin_token = req.headers.get("X-Admin-Token", "").strip() if ML_ADMIN_TOKEN and admin_token == ML_ADMIN_TOKEN: return True, None # 2. Pas de token admin configuré → autoriser les utilisateurs "pro" authentifiés user = getattr(g, "current_user", None) if user and user.get("plan") == "pro": return True, None return False, jsonify({"error": "Accès admin requis", "code": 403}), 403 @ml_feedback_bp.route("/run", methods=["POST"]) @jwt_required_middleware def feedback_run(): """ Déclenche le feedback loop ML pour une date donnée. --- tags: - ML Feedback summary: Déclenche le feedback loop XGBoost (admin only) security: - Bearer: [] - AdminToken: [] parameters: - name: body in: body schema: type: object properties: date: type: string description: Date YYYY-MM-DD (défaut aujourd'hui) example: "2026-04-25" mode: type: string description: "run (défaut) ou backfill" enum: [run, backfill] example: run responses: 200: description: Feedback loop exécuté avec succès 400: description: Paramètre invalide 403: description: Accès refusé 500: description: Erreur interne """ # Vérification admin user = getattr(g, "current_user", None) admin_token = request.headers.get("X-Admin-Token", "").strip() is_admin = (ML_ADMIN_TOKEN and admin_token == ML_ADMIN_TOKEN) or ( user and user.get("plan") == "pro" ) if not is_admin: return jsonify({"error": "Accès admin requis", "code": 403}), 403 body = request.get_json(silent=True) or {} date_str = body.get("date") or datetime.now().strftime("%Y-%m-%d") mode = body.get("mode", "run") # Validation date try: datetime.strptime(date_str, "%Y-%m-%d") except ValueError: return bad_request(f"Date invalide : {date_str}. Format attendu : YYYY-MM-DD") if mode not in ("run", "backfill"): return bad_request("mode doit être 'run' ou 'backfill'") try: import ml_feedback_saas if mode == "backfill": inseres, maj = ml_feedback_saas.backfill(date_str) total_inseres = inseres else: result = ml_feedback_saas.run(date_str) total_inseres = sum(result["inseres"].values()) maj = result["maj"] return jsonify( { "status": "ok", "date": date_str, "mode": mode, "paris_inseres": total_inseres, "paris_mis_a_jour": maj, } ), 200 except Exception as e: return internal_error(str(e)) @ml_feedback_bp.route("/stats", methods=["GET"]) @jwt_required_middleware @plan_required("premium", "pro") def feedback_stats(): """ Stats performances ML par stratégie. --- tags: - ML Feedback summary: Stats paris ML par stratégie (premium+) security: - Bearer: [] parameters: - name: date_debut in: query type: string description: Date de début YYYY-MM-DD - name: date_fin in: query type: string description: Date de fin YYYY-MM-DD responses: 200: description: Stats par stratégie 401: description: Token invalide 403: description: Plan insuffisant (premium ou pro requis) """ date_debut = request.args.get("date_debut") date_fin = request.args.get("date_fin") # Validation optionnelle des dates for d_str, label in [(date_debut, "date_debut"), (date_fin, "date_fin")]: if d_str: try: datetime.strptime(d_str, "%Y-%m-%d") except ValueError: return bad_request(f"{label} invalide : {d_str}. Format : YYYY-MM-DD") conn = get_db() try: import ml_feedback_saas stats = ml_feedback_saas.get_feedback_stats(conn, date_debut, date_fin) return jsonify( { "status": "ok", "strategies": stats, "filters": { "date_debut": date_debut, "date_fin": date_fin, }, "total_strategies": len(stats), } ), 200 except Exception as e: return internal_error(str(e)) finally: conn.close()