#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ metrics_alerts.py - Alertes Telegram + Email pour les métriques de performance Usage: python3 metrics_alerts.py --daily # Alerte quotidienne (si notable) python3 metrics_alerts.py --weekly # Rapport hebdomadaire python3 metrics_alerts.py --test # Test formatage python3 metrics_alerts.py --daily --email # Alerte + email si ROI > 1.0€ python3 metrics_alerts.py --weekly --email # Rapport hebdo par email """ import sqlite3 import argparse import requests from datetime import datetime, timedelta from pathlib import Path DB_PATH = "/home/h3r7/turf_scraper/turf.db" TELEGRAM_DIR = "/home/h3r7/turf_scraper" # Email alert configuration (via combined_api Resend endpoint on port 8765) COMBINED_API_URL = "http://localhost:8765" ALERT_EMAIL_TO = "ronanyves26@gmail.com" # destinataire des alertes email # Seuil ROI pour déclencher une alerte email automatique (euros par mise) ROI_ALERT_THRESHOLD = 1.0 # ============================================================================= # FONCTIONS UTILITAIRES # ============================================================================= def get_db(): """Connexion a la base de donnees""" conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row return conn def save_telegram_message(message, filename): """Sauvegarde le message Telegram dans un fichier""" filepath = Path(TELEGRAM_DIR) / filename with open(filepath, 'w', encoding='utf-8') as f: f.write(message) print("Message Telegram sauvegarde: {}".format(filepath)) return filepath def send_email_alert(subject, message_text): """ Envoie une alerte email via le endpoint /api/send-email de combined_api (port 8765). Convertit le message texte en HTML style. Retourne True si envoi reussi, False sinon. """ # Convertir le texte brut en HTML lisible html_lines = [] for line in message_text.splitlines(): stripped = line.strip() if not stripped: html_lines.append("
") else: # Echapper les caracteres HTML basiques escaped = stripped.replace("&", "&").replace("<", "<").replace(">", ">") html_lines.append( "

{}

".format(escaped) ) html_body = """

H3R7Tech — Alerte Performance Turf

{}

Alerte automatique generee par metrics_alerts.py — H3R7Tech
Dashboard: Turf Dashboard

""".format("".join(html_lines)) try: resp = requests.post( "{}/api/send-email".format(COMBINED_API_URL), json={ "to": ALERT_EMAIL_TO, "subject": subject, "html": html_body, }, timeout=15, ) if resp.status_code in (200, 201): result = resp.json() print("Email envoye a {} — id: {}".format(ALERT_EMAIL_TO, result.get("id", "?"))) return True else: try: data = resp.json() error_msg = data.get("error", data.get("details", resp.text[:300])) except Exception: error_msg = resp.text[:300] print("Erreur Resend ({}): {}".format(resp.status_code, error_msg)) return False except Exception as e: print("Impossible d'envoyer l'email: {}".format(e)) return False # ============================================================================= # ALERTE QUOTIDIENNE # ============================================================================= def check_daily_alerts(date_str): """Verifie les evenements notables du jour. Retourne (message, has_roi_alert) ou None.""" conn = get_db() # Recuperer les metriques du jour metrics = conn.execute(""" SELECT source, SUM(nb_predictions) as total_pred, SUM(nb_gagnants) as total_gagn, SUM(nb_places) as total_place, SUM(nb_top5) as total_top5, AVG(taux_gagnant) as taux_g, AVG(taux_place) as taux_p, AVG(roi_sg_net) as roi_sg, SUM(quinte_5sur5) as q5 FROM prediction_metrics WHERE date = ? GROUP BY source """, (date_str,)).fetchall() if not metrics: print("Aucune metrique pour {}".format(date_str)) return None # Verifier les criteres d'alerte alerts = [] has_roi_alert = False for m in metrics: # Quinte 5/5 if m['q5'] and m['q5'] > 0: alerts.append("QUINTE 5/5 {}: Quinte 5/5 trouve!".format(m['source'])) # ROI exceptionnel (> seuil ROI_ALERT_THRESHOLD) if m['roi_sg'] and m['roi_sg'] > ROI_ALERT_THRESHOLD: alerts.append("ROI ELEVE {}: ROI SG +{:.2f}€/mise".format(m['source'], m['roi_sg'])) has_roi_alert = True # Taux place excellent if m['taux_p'] and m['taux_p'] > 70: alerts.append("TAUX PLACE {}: {:.1f}% place (excellent)".format(m['source'], m['taux_p'])) if not alerts: print("Aucun evenement notable aujourd'hui") return None # Formater le message date_fmt = datetime.strptime(date_str, '%Y-%m-%d').strftime('%d/%m/%Y') message = "ALERTE PERFORMANCE\nDate: {}\n{}\n\n{}\n\nDetails: Dashboard Turf".format( date_fmt, "=" * 30, "\n".join(alerts) ) conn.close() return message, has_roi_alert # ============================================================================= # RAPPORT HEBDOMADAIRE # ============================================================================= def generate_weekly_report(): """Genere le rapport hebdomadaire de performance. Retourne (message, total_roi_sg).""" conn = get_db() # Dates de la semaine today = datetime.now() start_date = (today - timedelta(days=7)).strftime('%Y-%m-%d') end_date = today.strftime('%Y-%m-%d') # Periode precedente pour comparaison prev_start = (today - timedelta(days=14)).strftime('%Y-%m-%d') prev_end = start_date # Metriques de la semaine current_metrics = conn.execute(""" SELECT source, COUNT(*) as nb_courses, SUM(nb_predictions) as total_pred, SUM(nb_gagnants) as total_gagn, SUM(nb_places) as total_place, ROUND(AVG(taux_gagnant), 1) as taux_g, ROUND(AVG(taux_place), 1) as taux_p, ROUND(SUM(roi_sg_net), 3) as roi_sg_cumul, ROUND(SUM(roi_sp_net), 3) as roi_sp_cumul, SUM(quinte_5sur5) as q5, SUM(quinte_4sur5) as q4 FROM prediction_metrics WHERE date BETWEEN ? AND ? GROUP BY source ORDER BY taux_p DESC """, (start_date, end_date)).fetchall() # Metriques semaine precedente prev_metrics = conn.execute(""" SELECT source, ROUND(AVG(taux_gagnant), 1) as taux_g, ROUND(AVG(taux_place), 1) as taux_p FROM prediction_metrics WHERE date BETWEEN ? AND ? GROUP BY source """, (prev_start, prev_end)).fetchall() prev_dict = {m['source']: m for m in prev_metrics} # Formater le message start_fmt = datetime.strptime(start_date, '%Y-%m-%d').strftime('%d/%m') end_fmt = datetime.strptime(end_date, '%Y-%m-%d').strftime('%d/%m') message = "RAPPORT PERFORMANCE\nSemaine {} - {}\n{}\n\n".format( start_fmt, end_fmt, "=" * 30 ) # Bilan global total_roi_sg = sum(m['roi_sg_cumul'] or 0 for m in current_metrics) total_roi_sp = sum(m['roi_sp_cumul'] or 0 for m in current_metrics) total_q5 = sum(m['q5'] or 0 for m in current_metrics) total_q4 = sum(m['q4'] or 0 for m in current_metrics) # Detail par source for m in current_metrics: source_name = m['source'].replace('canalturf_', '').replace('_', ' ').title() # Comparaison avec semaine precedente prev = prev_dict.get(m['source']) diff_g = 0 diff_p = 0 if prev: diff_g = m['taux_g'] - prev['taux_g'] diff_p = m['taux_p'] - prev['taux_p'] arrow_g = 'hausse' if diff_g > 0 else 'baisse' if diff_g < 0 else 'stable' arrow_p = 'hausse' if diff_p > 0 else 'baisse' if diff_p < 0 else 'stable' message += "{}\n Courses: {}\n Taux gagnant: {}% ({})\n Taux place: {}% ({})\n ROI SG: {:+.2f}€\n ROI SP: {:+.2f}€\n\n".format( source_name, m['nb_courses'], m['taux_g'], arrow_g, m['taux_p'], arrow_p, m['roi_sg_cumul'], m['roi_sp_cumul'] ) # Bilan global bilan = "Semaine positive" if total_roi_sg > 0 else "Semaine negative" message += "{}\nQuinte: 5/5 = {} courses | 4/5 = {} courses\n\nBilan global:\n ROI SG cumule: {:+.2f}€\n ROI SP cumule: {:+.2f}€\n\n{}\n{}".format( "=" * 30, total_q5, total_q4, total_roi_sg, total_roi_sp, bilan, "=" * 30 ) conn.close() return message, total_roi_sg # ============================================================================= # POINT D'ENTREE # ============================================================================= if __name__ == "__main__": parser = argparse.ArgumentParser(description="Alertes metriques de performance") parser.add_argument("--daily", "-d", action="store_true", help="Alerte quotidienne") parser.add_argument("--weekly", "-w", action="store_true", help="Rapport hebdomadaire") parser.add_argument("--test", "-t", action="store_true", help="Test formatage") parser.add_argument( "--email", "-e", action="store_true", help="Envoyer par email (auto si ROI > {:.1f}€)".format(ROI_ALERT_THRESHOLD) ) parser.add_argument("--date", help="Date YYYY-MM-DD (defaut: hier)") args = parser.parse_args() if args.test: date_str = args.date or (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d') result = check_daily_alerts(date_str) if result: msg, has_roi = result print(msg) if args.email: print("\n--- Test envoi email ---") send_email_alert("[TEST] Alerte Performance Turf — {}".format(date_str), msg) result_w = generate_weekly_report() if result_w: msg_w, roi_w = result_w print(msg_w) elif args.daily: date_str = args.date or (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d') result = check_daily_alerts(date_str) if result: msg, has_roi = result filename = "telegram_metrics_daily_{}.txt".format(date_str.replace('-', '')) save_telegram_message(msg, filename) # Envoi email si ROI > seuil (automatique) OU si flag --email passe if args.email or has_roi: date_fmt = datetime.strptime(date_str, '%Y-%m-%d').strftime('%d/%m/%Y') subject = "Alerte Turf — ROI exceptionnel {}".format(date_fmt) sent = send_email_alert(subject, msg) if not sent: print("Email non envoye (voir logs ci-dessus)") elif args.weekly: result = generate_weekly_report() if result: msg, total_roi = result filename = "telegram_metrics_weekly_{}.txt".format(datetime.now().strftime('%Y%m%d')) save_telegram_message(msg, filename) # Envoi email du rapport hebdo si --email active if args.email: start_fmt = (datetime.now() - timedelta(days=7)).strftime('%d/%m') end_fmt = datetime.now().strftime('%d/%m') roi_status = "positif" if total_roi > 0 else "negatif" subject = "Rapport Hebdo Turf {}-{} — ROI {} ({:+.2f}€)".format( start_fmt, end_fmt, roi_status, total_roi ) sent = send_email_alert(subject, msg) if not sent: print("Email non envoye (voir logs ci-dessus)") else: parser.print_help()