424 lines
15 KiB
Python
424 lines
15 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Analytics et Rapports Automatisés - Turf Scraper
|
|
Génère des rapports quotidiens, hebdomadaires et mensuels
|
|
"""
|
|
import sqlite3
|
|
import json
|
|
from datetime import datetime, timedelta
|
|
from typing import Dict, List, Optional
|
|
from pathlib import Path
|
|
|
|
DB_PATH = "/home/h3r7/turf_scraper/turf.db"
|
|
|
|
|
|
def get_db():
|
|
"""Connexion à la base"""
|
|
conn = sqlite3.connect(DB_PATH)
|
|
conn.row_factory = sqlite3.Row
|
|
return conn
|
|
|
|
|
|
def get_daily_report(date: str = None) -> dict:
|
|
"""
|
|
Génère un rapport quotidien
|
|
"""
|
|
if date is None:
|
|
date = datetime.now().strftime('%Y-%m-%d')
|
|
|
|
conn = get_db()
|
|
c = conn.cursor()
|
|
|
|
report = {
|
|
'date': date,
|
|
'generated_at': datetime.now().isoformat(),
|
|
'races': {},
|
|
'predictions': {},
|
|
'results': {}
|
|
}
|
|
|
|
try:
|
|
c.execute("""
|
|
SELECT id, num_reunion, num_course, libelle, libelle_court,
|
|
discipline, distance, heure_depart_str
|
|
FROM pmu_courses
|
|
WHERE date_programme = ?
|
|
ORDER BY num_reunion, num_course
|
|
""", (date,))
|
|
|
|
races = c.fetchall()
|
|
report['races']['count'] = len(races)
|
|
report['races']['details'] = [dict(r) for r in races[:10]]
|
|
|
|
c.execute("""
|
|
SELECT race_name, race_hippodrome, COUNT(*) as pred_count
|
|
FROM predictions
|
|
WHERE date = ?
|
|
GROUP BY race_name
|
|
""", (date,))
|
|
|
|
preds = c.fetchall()
|
|
report['predictions']['count'] = sum(p['pred_count'] for p in preds)
|
|
report['predictions']['by_race'] = [dict(p) for p in preds]
|
|
|
|
c.execute("""
|
|
SELECT p.nom as cheval, p.num_pmu as numero, p.ordre_arrivee as position,
|
|
p.cote_direct as cote, p.driver, c.libelle as course
|
|
FROM pmu_partants p
|
|
JOIN pmu_courses c ON p.date_programme = c.date_programme
|
|
AND p.num_reunion = c.num_reunion AND p.num_course = c.num_course
|
|
WHERE p.date_programme = ? AND p.ordre_arrivee > 0
|
|
ORDER BY c.num_reunion, c.num_course, p.ordre_arrivee
|
|
""", (date,))
|
|
|
|
results = c.fetchall()
|
|
report['results']['count'] = len(results)
|
|
report['results']['details'] = [dict(r) for r in results[:20]]
|
|
|
|
c.execute("""
|
|
SELECT COUNT(*) as total,
|
|
SUM(CASE WHEN ordre_arrivee = 1 THEN 1 ELSE 0 END) as wins,
|
|
SUM(CASE WHEN ordre_arrivee IN (1,2,3) THEN 1 ELSE 0 END) as podiums,
|
|
AVG(CASE WHEN ordre_arrivee > 0 THEN cote_direct ELSE NULL END) as avg_cote
|
|
FROM pmu_partants
|
|
WHERE date_programme = ? AND ordre_arrivee > 0
|
|
""", (date,))
|
|
|
|
stats = c.fetchone()
|
|
total = stats['total'] or 0
|
|
wins = stats['wins'] or 0
|
|
podiums = stats['podiums'] or 0
|
|
|
|
report['stats'] = {
|
|
'total_partants': total,
|
|
'wins': wins,
|
|
'podiums': podiums,
|
|
'win_rate': round(wins / total * 100, 1) if total > 0 else 0,
|
|
'podium_rate': round(podiums / total * 100, 1) if total > 0 else 0,
|
|
'avg_cote': round(stats['avg_cote'], 2) if stats['avg_cote'] else 0
|
|
}
|
|
|
|
except Exception as e:
|
|
report['error'] = str(e)
|
|
finally:
|
|
conn.close()
|
|
|
|
return report
|
|
|
|
|
|
def get_weekly_report(start_date: str = None, end_date: str = None) -> dict:
|
|
"""
|
|
Génère un rapport hebdomadaire
|
|
"""
|
|
if end_date is None:
|
|
end_date = datetime.now()
|
|
else:
|
|
end_date = datetime.strptime(end_date, '%Y-%m-%d')
|
|
|
|
if start_date is None:
|
|
start_date = end_date - timedelta(days=7)
|
|
else:
|
|
start_date = datetime.strptime(start_date, '%Y-%m-%d')
|
|
|
|
start_str = start_date.strftime('%Y-%m-%d')
|
|
end_str = end_date.strftime('%Y-%m-%d')
|
|
|
|
conn = get_db()
|
|
c = conn.cursor()
|
|
|
|
report = {
|
|
'start_date': start_str,
|
|
'end_date': end_str,
|
|
'generated_at': datetime.now().isoformat()
|
|
}
|
|
|
|
try:
|
|
c.execute("""
|
|
SELECT COUNT(DISTINCT date_programme) as race_days,
|
|
COUNT(DISTINCT id) as total_races
|
|
FROM pmu_courses
|
|
WHERE date_programme BETWEEN ? AND ?
|
|
""", (start_str, end_str))
|
|
|
|
overview = c.fetchone()
|
|
report['overview'] = {
|
|
'race_days': overview['race_days'] or 0,
|
|
'total_races': overview['total_races'] or 0
|
|
}
|
|
|
|
c.execute("""
|
|
SELECT COUNT(*) as total,
|
|
SUM(CASE WHEN ordre_arrivee = 1 THEN 1 ELSE 0 END) as wins,
|
|
SUM(CASE WHEN ordre_arrivee IN (1,2,3) THEN 1 ELSE 0 END) as podiums,
|
|
AVG(CASE WHEN ordre_arrivee > 0 THEN cote_direct ELSE NULL END) as avg_cote
|
|
FROM pmu_partants
|
|
WHERE date_programme BETWEEN ? AND ? AND ordre_arrivee > 0
|
|
""", (start_str, end_str))
|
|
|
|
stats = c.fetchone()
|
|
total = stats['total'] or 0
|
|
wins = stats['wins'] or 0
|
|
podiums = stats['podiums'] or 0
|
|
|
|
report['stats'] = {
|
|
'total_partants': total,
|
|
'wins': wins,
|
|
'podiums': podiums,
|
|
'win_rate': round(wins / total * 100, 1) if total > 0 else 0,
|
|
'podium_rate': round(podiums / total * 100, 1) if total > 0 else 0,
|
|
'avg_cote': round(stats['avg_cote'], 2) if stats['avg_cote'] else 0
|
|
}
|
|
|
|
c.execute("""
|
|
SELECT driver, COUNT(*) as wins,
|
|
AVG(CASE WHEN ordre_arrivee > 0 THEN ordre_arrivee ELSE NULL END) as avg_pos
|
|
FROM pmu_partants
|
|
WHERE date_programme BETWEEN ? AND ? AND ordre_arrivee = 1
|
|
GROUP BY driver
|
|
ORDER BY wins DESC
|
|
LIMIT 10
|
|
""", (start_str, end_str))
|
|
|
|
report['top_jockeys'] = [dict(r) for r in c.fetchall()]
|
|
|
|
c.execute("""
|
|
SELECT discipline, COUNT(DISTINCT c.id) as races,
|
|
COUNT(*) as partants,
|
|
SUM(CASE WHEN ordre_arrivee = 1 THEN 1 ELSE 0 END) as wins
|
|
FROM pmu_courses c
|
|
LEFT JOIN pmu_partants p ON c.date_programme = p.date_programme
|
|
AND c.num_reunion = p.num_reunion AND c.num_course = p.num_course
|
|
WHERE c.date_programme BETWEEN ? AND ?
|
|
GROUP BY discipline
|
|
ORDER BY races DESC
|
|
""", (start_str, end_str))
|
|
|
|
report['by_discipline'] = [dict(r) for r in c.fetchall()]
|
|
|
|
c.execute("""
|
|
SELECT date_programme, COUNT(*) as partants,
|
|
SUM(CASE WHEN ordre_arrivee = 1 THEN 1 ELSE 0 END) as wins
|
|
FROM pmu_partants
|
|
WHERE date_programme BETWEEN ? AND ?
|
|
GROUP BY date_programme
|
|
ORDER BY date_programme
|
|
""", (start_str, end_str))
|
|
|
|
report['by_day'] = [dict(r) for r in c.fetchall()]
|
|
|
|
except Exception as e:
|
|
report['error'] = str(e)
|
|
finally:
|
|
conn.close()
|
|
|
|
return report
|
|
|
|
|
|
def get_monthly_report(year: int = None, month: int = None) -> dict:
|
|
"""
|
|
Génère un rapport mensuel
|
|
"""
|
|
if year is None:
|
|
year = datetime.now().year
|
|
if month is None:
|
|
month = datetime.now().month
|
|
|
|
start_date = datetime(year, month, 1)
|
|
if month == 12:
|
|
end_date = datetime(year + 1, 1, 1) - timedelta(days=1)
|
|
else:
|
|
end_date = datetime(year, month + 1, 1) - timedelta(days=1)
|
|
|
|
start_str = start_date.strftime('%Y-%m-%d')
|
|
end_str = end_date.strftime('%Y-%m-%d')
|
|
|
|
conn = get_db()
|
|
c = conn.cursor()
|
|
|
|
report = {
|
|
'year': year,
|
|
'month': month,
|
|
'start_date': start_str,
|
|
'end_date': end_str,
|
|
'generated_at': datetime.now().isoformat()
|
|
}
|
|
|
|
try:
|
|
c.execute("""
|
|
SELECT COUNT(DISTINCT date_programme) as race_days,
|
|
COUNT(DISTINCT id) as total_races,
|
|
COUNT(*) as total_partants
|
|
FROM pmu_courses c
|
|
LEFT JOIN pmu_partants p ON c.date_programme = p.date_programme
|
|
AND c.num_reunion = p.num_reunion AND c.num_course = p.num_course
|
|
WHERE c.date_programme BETWEEN ? AND ?
|
|
""", (start_str, end_str))
|
|
|
|
overview = c.fetchone()
|
|
report['overview'] = {
|
|
'race_days': overview['race_days'] or 0,
|
|
'total_races': overview['total_races'] or 0,
|
|
'total_partants': overview['total_partants'] or 0
|
|
}
|
|
|
|
c.execute("""
|
|
SELECT COUNT(*) as total,
|
|
SUM(CASE WHEN ordre_arrivee = 1 THEN 1 ELSE 0 END) as wins,
|
|
SUM(CASE WHEN ordre_arrivee IN (1,2,3) THEN 1 ELSE 0 END) as podiums,
|
|
AVG(CASE WHEN ordre_arrivee > 0 THEN cote_direct ELSE NULL END) as avg_cote
|
|
FROM pmu_partants
|
|
WHERE date_programme BETWEEN ? AND ? AND ordre_arrivee > 0
|
|
""", (start_str, end_str))
|
|
|
|
stats = c.fetchone()
|
|
total = stats['total'] or 0
|
|
wins = stats['wins'] or 0
|
|
podiums = stats['podiums'] or 0
|
|
|
|
report['stats'] = {
|
|
'total_partants': total,
|
|
'wins': wins,
|
|
'podiums': podiums,
|
|
'win_rate': round(wins / total * 100, 1) if total > 0 else 0,
|
|
'podium_rate': round(podiums / total * 100, 1) if total > 0 else 0,
|
|
'avg_cote': round(stats['avg_cote'], 2) if stats['avg_cote'] else 0
|
|
}
|
|
|
|
c.execute("""
|
|
SELECT discipline, COUNT(DISTINCT c.id) as races,
|
|
SUM(CASE WHEN ordre_arrivee = 1 THEN 1 ELSE 0 END) as wins,
|
|
AVG(CASE WHEN ordre_arrivee > 0 THEN ordre_arrivee ELSE NULL END) as avg_pos
|
|
FROM pmu_courses c
|
|
LEFT JOIN pmu_partants p ON c.date_programme = p.date_programme
|
|
AND c.num_reunion = p.num_reunion AND c.num_course = p.num_course
|
|
WHERE c.date_programme BETWEEN ? AND ?
|
|
GROUP BY discipline
|
|
ORDER BY races DESC
|
|
LIMIT 20
|
|
""", (start_str, end_str))
|
|
|
|
report['by_discipline'] = [dict(r) for r in c.fetchall()]
|
|
|
|
c.execute("""
|
|
SELECT date_programme, COUNT(*) as partants,
|
|
SUM(CASE WHEN ordre_arrivee = 1 THEN 1 ELSE 0 END) as wins
|
|
FROM pmu_partants
|
|
WHERE date_programme BETWEEN ? AND ?
|
|
GROUP BY date_programme
|
|
ORDER BY date_programme
|
|
""", (start_str, end_str))
|
|
|
|
report['daily_breakdown'] = [dict(r) for r in c.fetchall()]
|
|
|
|
except Exception as e:
|
|
report['error'] = str(e)
|
|
finally:
|
|
conn.close()
|
|
|
|
return report
|
|
|
|
|
|
def format_report_markdown(report: dict, report_type: str = 'daily') -> str:
|
|
"""
|
|
Formate un rapport en Markdown
|
|
"""
|
|
lines = []
|
|
|
|
if report_type == 'daily':
|
|
lines.append(f"# 📊 Rapport Quotidien - {report.get('date', 'N/A')}")
|
|
lines.append("")
|
|
lines.append(f"*Généré le {report.get('generated_at', '')}*")
|
|
lines.append("")
|
|
|
|
if 'error' in report:
|
|
lines.append(f"❌ Erreur: {report['error']}")
|
|
return "\n".join(lines)
|
|
|
|
races = report.get('races', {})
|
|
stats = report.get('stats', {})
|
|
|
|
lines.append("## Résumé")
|
|
lines.append(f"- 🏇 Courses: {races.get('count', 0)}")
|
|
lines.append(f"- 🎯 Partants: {stats.get('total_partants', 0)}")
|
|
lines.append(f"- 🏆 Victoires: {stats.get('wins', 0)} ({stats.get('win_rate', 0)}%)")
|
|
lines.append(f"- 🥉 Podiums: {stats.get('podiums', 0)} ({stats.get('podium_rate', 0)}%)")
|
|
lines.append("")
|
|
|
|
elif report_type == 'weekly':
|
|
lines.append(f"# 📈 Rapport Hebdomadaire")
|
|
lines.append(f"**{report.get('start_date', '')}** au **{report.get('end_date', '')}**")
|
|
lines.append("")
|
|
|
|
if 'error' in report:
|
|
lines.append(f"❌ Erreur: {report['error']}")
|
|
return "\n".join(lines)
|
|
|
|
overview = report.get('overview', {})
|
|
stats = report.get('stats', {})
|
|
|
|
lines.append("## Résumé")
|
|
lines.append(f"- Jours de course: {overview.get('race_days', 0)}")
|
|
lines.append(f"- Courses totales: {overview.get('total_races', 0)}")
|
|
lines.append(f"- Victoires: {stats.get('wins', 0)} ({stats.get('win_rate', 0)}%)")
|
|
lines.append(f"- Podiums: {stats.get('podiums', 0)} ({stats.get('podium_rate', 0)}%)")
|
|
lines.append(f"- Cote moyenne: {stats.get('avg_cote', 0)}")
|
|
lines.append("")
|
|
|
|
if report.get('top_jockeys'):
|
|
lines.append("## Top Jockeys")
|
|
for j in report['top_jockeys'][:5]:
|
|
lines.append(f"- {j['driver']}: {j['wins']} victoires")
|
|
lines.append("")
|
|
|
|
elif report_type == 'monthly':
|
|
month_name = ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin',
|
|
'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre']
|
|
m = report.get('month', 1)
|
|
lines.append(f"# 📅 Rapport Mensuel - {month_name[m-1]} {report.get('year', '')}")
|
|
lines.append("")
|
|
|
|
if 'error' in report:
|
|
lines.append(f"❌ Erreur: {report['error']}")
|
|
return "\n".join(lines)
|
|
|
|
overview = report.get('overview', {})
|
|
stats = report.get('stats', {})
|
|
|
|
lines.append("## Résumé du Mois")
|
|
lines.append(f"- Jours de course: {overview.get('race_days', 0)}")
|
|
lines.append(f"- Courses: {overview.get('total_races', 0)}")
|
|
lines.append(f"- Partants: {overview.get('total_partants', 0)}")
|
|
lines.append(f"- Victoires: {stats.get('wins', 0)} ({stats.get('win_rate', 0)}%)")
|
|
lines.append(f"- Podiums: {stats.get('podiums', 0)} ({stats.get('podium_rate', 0)}%)")
|
|
lines.append(f"- Cote moyenne: {stats.get('avg_cote', 0)}")
|
|
lines.append("")
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import sys
|
|
|
|
if len(sys.argv) < 2:
|
|
print("Usage: python analytics_reports.py [daily|weekly|monthly] [date]")
|
|
sys.exit(1)
|
|
|
|
report_type = sys.argv[1]
|
|
date_arg = sys.argv[2] if len(sys.argv) > 2 else None
|
|
|
|
if report_type == 'daily':
|
|
report = get_daily_report(date_arg)
|
|
print(format_report_markdown(report, 'daily'))
|
|
elif report_type == 'weekly':
|
|
report = get_weekly_report(date_arg)
|
|
print(format_report_markdown(report, 'weekly'))
|
|
elif report_type == 'monthly':
|
|
if date_arg:
|
|
year, month = map(int, date_arg.split('-'))
|
|
else:
|
|
year, month = None, None
|
|
report = get_monthly_report(year, month)
|
|
print(format_report_markdown(report, 'monthly'))
|
|
else:
|
|
print(f"Type de rapport inconnu: {report_type}")
|