Initial commit: existing turf_saas codebase

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
ML Engineer
2026-04-25 17:18:43 +02:00
commit ed07c8a3d1
137 changed files with 36398 additions and 0 deletions

347
agent_turf.py Executable file
View File

@@ -0,0 +1,347 @@
#!/usr/bin/env python3
"""
Agent Turf Autonome - Prédictions quotidiennes
Usage: python3 agent_turf.py [--dry-run]
"""
import sqlite3
import subprocess
import sys
import json
import os
from datetime import datetime, timedelta
from pathlib import Path
DB_PATH = "/home/h3r7/turf_scraper/turf.db"
LOG_FILE = "/home/h3r7/turf_scraper/logs/agent_turf.log"
VPS_HOST = "10.0.1.1"
def log(msg: str):
"""Log avec timestamp"""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
line = f"[{timestamp}] {msg}"
print(line)
os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
with open(LOG_FILE, "a") as f:
f.write(line + "\n")
def sync_database():
"""Sync base de données depuis VPS"""
log("=== SYNC DATABASE ===")
# Check remote size
cmd = f"sshpass -p 'Cronstadt35*' ssh -o StrictHostKeyChecking=no h3r7@{VPS_HOST} stat -c %s {DB_PATH}"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if result.returncode != 0:
log(f"ERROR: Cannot connect to VPS")
return False
remote_size = int(result.stdout.strip())
local_size = os.path.getsize(DB_PATH) if os.path.exists(DB_PATH) else 0
if remote_size == local_size:
log(f"Database up to date ({remote_size} bytes)")
return True
log(f"Syncing database (remote: {remote_size}, local: {local_size})...")
cmd = f"sshpass -p 'Cronstadt35*' scp -o StrictHostKeyChecking=no h3r7@{VPS_HOST}:{DB_PATH} {DB_PATH}.tmp"
result = subprocess.run(cmd, shell=True, capture_output=True)
if result.returncode == 0:
os.rename(DB_PATH + ".tmp", DB_PATH)
log(f"Database synced successfully")
return True
else:
log(f"ERROR: Sync failed")
return False
def generate_predictions():
"""Génère les prédictions du jour"""
log("=== GENERATE PREDICTIONS ===")
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
today = datetime.now().strftime("%Y-%m-%d")
# Get today's races
cursor.execute(
"""
SELECT DISTINCT c.date_programme, c.num_reunion, c.num_course,
c.libelle, c.heure_depart_str, r.hippodrome_court
FROM pmu_courses c
JOIN pmu_reunions r ON r.date_programme = c.date_programme AND r.num_reunion = c.num_reunion
WHERE c.date_programme = ? AND r.pays_code = 'FRA'
ORDER BY c.heure_depart_str
""",
(today,),
)
races = cursor.fetchall()
log(f"Found {len(races)} races today")
predictions = []
for race in races:
date, num_reunion, num_course, libelle, heure, hippodrome = race
# Get scoring
cursor.execute(
"""
SELECT horse_name, horse_number, score, cote, rang_scoring
FROM scoring
WHERE date = ? AND race_name LIKE ?
ORDER BY rang_scoring
LIMIT 4
""",
(date, f"%{libelle}%"),
)
scores = cursor.fetchall()
if scores:
pred = {
"race": f"{heure} - {hippodrome} - {libelle}",
"top4": [dict(s) for s in scores],
}
predictions.append(pred)
log(
f" {pred['race'][:50]} -> {', '.join([s['horse_name'] for s in scores[:3]])}"
)
conn.close()
return predictions
def save_recommendations(predictions):
"""Sauvegarde les recommandations en base"""
log("=== SAVE RECOMMENDATIONS ===")
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
today = datetime.now().strftime("%Y-%m-%d")
# Clear today's recommendations
cursor.execute("DELETE FROM recommendations WHERE date = ?", (today,))
for pred in predictions:
race_name = pred["race"]
# Add top 4
for i, horse in enumerate(pred["top4"]):
if i < 4: # TOP 4
cursor.execute(
"""
INSERT INTO recommendations
(date, race_name, type_pari, cheval1, numero1, cote, mise, confiance, justification)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
today,
race_name,
f"top{i + 1}",
horse["horse_name"],
horse["horse_number"],
horse["cote"],
2.0,
f"TOP {i + 1}",
f"Score: {horse['score']}, Rang scoring: {horse['rang_scoring']}",
),
)
# Add simple winner (top 1)
top1 = pred["top4"][0]
cursor.execute(
"""
INSERT INTO recommendations
(date, race_name, type_pari, cheval1, numero1, cote, mise, confiance, justification)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
today,
race_name,
"simple_gagnant",
top1["horse_name"],
top1["horse_number"],
top1["cote"],
2.0,
"🔥 FORTE" if top1["score"] > 60 else "✅ BONNE",
f"Score: {top1['score']}, Favorite prediction",
),
)
conn.commit()
conn.close()
log(f"Saved {len(predictions)} race recommendations")
def notify_telegram(predictions):
"""Envoie les prédictions du jour via Telegram"""
import urllib.request
config_path = "/home/h3r7/turf_scraper/config_telegram.json"
if not os.path.exists(config_path):
log("No Telegram config found, skipping notification")
return
try:
with open(config_path, "r") as f:
cfg = json.load(f)
token = cfg.get("token", "")
chat_id = cfg.get("chat_id", "")
if not token or not chat_id:
log("Telegram config missing token or chat_id")
return
except Exception as e:
log(f"Error reading Telegram config: {e}")
return
today = datetime.now().strftime("%d/%m/%Y")
lines = [f"🏇 *Prédictions Turf — {today}*", ""]
if not predictions:
lines.append("Aucune prédiction disponible aujourd'hui.")
else:
for pred in predictions:
race_label = pred.get("race", "Course inconnue")
top4 = pred.get("top4", [])
lines.append(f"📍 *{race_label}*")
for i, horse in enumerate(top4, 1):
name = horse.get("horse_name", "?")
score = horse.get("score", 0)
cote = horse.get("cote", "?")
icon = "🥇" if i == 1 else ("🥈" if i == 2 else ("🥉" if i == 3 else "4"))
lines.append(f" {icon} {name} — score: {score} — cote: {cote}")
lines.append("")
text = "\n".join(lines)
url = f"https://api.telegram.org/bot{token}/sendMessage"
payload = json.dumps({
"chat_id": chat_id,
"text": text,
"parse_mode": "Markdown"
}).encode("utf-8")
try:
req = urllib.request.Request(
url,
data=payload,
headers={"Content-Type": "application/json"},
method="POST"
)
with urllib.request.urlopen(req, timeout=10) as resp:
result = json.loads(resp.read().decode("utf-8"))
if result.get("ok"):
log(f"Telegram notification sent ({len(predictions)} races)")
else:
log(f"Telegram API error: {result}")
except Exception as e:
log(f"Error sending Telegram notification: {e}")
def run_backtest_yesterday():
"""Lance le backtest sur les résultats d'hier"""
log("=== RUN BACKTEST ===")
yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
# Check if we have results for yesterday
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute(
"""
SELECT COUNT(*) FROM pmu_partants
WHERE date_programme = ? AND ordre_arrivee > 0
""",
(yesterday,),
)
count = cursor.fetchone()[0]
conn.close()
if count > 0:
log(f"Running backtest for {yesterday}...")
# Run backtest script
cmd = f"cd /home/h3r7/turf_scraper && python3 backtest_analyzer.py --date {yesterday}"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if result.returncode == 0:
log("Backtest completed")
else:
log(f"Backtest failed: {result.stderr}")
else:
log(f"No results for {yesterday}, skipping backtest")
def update_paris_results():
"""Met à jour les paris avec les résultats PMU"""
log("=== UPDATE PARIS RESULTS ===")
today = datetime.now().strftime("%Y-%m-%d")
cmd = (
f"cd /home/h3r7/turf_scraper && python3 update_paris_results.py --date {today}"
)
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if result.returncode == 0:
log(f"Paris updated: {result.stdout.strip()}")
else:
log(f"Paris update failed: {result.stderr}")
def main():
"""Point d'entrée principal"""
dry_run = "--dry-run" in sys.argv
log("=" * 50)
log("🤖 AGENT TURF AUTONOME - DEMARRAGE")
log("=" * 50)
try:
# 1. Sync database
if not dry_run:
sync_database()
# 2. Generate predictions
predictions = generate_predictions()
# 3. Save recommendations
if not dry_run and predictions:
save_recommendations(predictions)
# 3b. Send Telegram notification
if not dry_run:
notify_telegram(predictions)
# 4. Create paris from recommendations
if not dry_run and predictions:
update_paris_results()
# 5. Run backtest on yesterday
if not dry_run:
run_backtest_yesterday()
log("=" * 50)
log("✅ AGENT TURF - TERMINÉ")
log("=" * 50)
except Exception as e:
log(f"❌ ERREUR: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()