346 lines
13 KiB
Python
Executable File
346 lines
13 KiB
Python
Executable File
#!/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("<br>")
|
|
else:
|
|
# Echapper les caracteres HTML basiques
|
|
escaped = stripped.replace("&", "&").replace("<", "<").replace(">", ">")
|
|
html_lines.append(
|
|
"<p style='margin:3px 0;font-family:monospace;font-size:14px;'>{}</p>".format(escaped)
|
|
)
|
|
|
|
html_body = """
|
|
<div style="background:#0f0f23;color:#eee;padding:24px;border-radius:8px;
|
|
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
|
|
max-width:620px;margin:0 auto;">
|
|
<h2 style="color:#00d9ff;border-bottom:1px solid #30363d;
|
|
padding-bottom:12px;margin-bottom:16px;font-size:20px;">
|
|
H3R7Tech — Alerte Performance Turf
|
|
</h2>
|
|
<div style="margin-top:12px;background:#161b22;padding:16px;border-radius:6px;
|
|
border:1px solid #30363d;">
|
|
{}
|
|
</div>
|
|
<hr style="border:none;border-top:1px solid #30363d;margin:20px 0;">
|
|
<p style="color:#555;font-size:12px;margin:0;">
|
|
Alerte automatique generee par metrics_alerts.py — H3R7Tech<br>
|
|
Dashboard: <a href="http://localhost:8765/turf/" style="color:#00d9ff;">Turf Dashboard</a>
|
|
</p>
|
|
</div>
|
|
""".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()
|