269 lines
8.5 KiB
Python
269 lines
8.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Update Recommendation Results - Compare recommendations with actual results
|
|
This script fills in the 'resultat' field in the recommendations table.
|
|
"""
|
|
|
|
import sqlite3
|
|
import os
|
|
from datetime import datetime
|
|
|
|
DB_PATH = os.environ.get("DB_PATH", "/home/h3r7/turf_scraper/turf.db")
|
|
|
|
|
|
def get_race_results(conn, date, race_name):
|
|
"""Get all race results for a specific date and race."""
|
|
c = conn.cursor()
|
|
c.execute('''
|
|
SELECT horse_name, position
|
|
FROM results
|
|
WHERE date = ? AND race_name LIKE ?
|
|
ORDER BY position
|
|
''', (date, f"%{race_name[:20]}%"))
|
|
results = c.fetchall()
|
|
|
|
if not results:
|
|
return None
|
|
|
|
return {
|
|
'first': results[0][0] if len(results) > 0 else None,
|
|
'second': results[1][0] if len(results) > 1 else None,
|
|
'third': results[2][0] if len(results) > 2 else None,
|
|
'top3': [r[0] for r in results[:3]],
|
|
'top5': [r[0] for r in results[:5]],
|
|
'all': {r[0]: r[1] for r in results}
|
|
}
|
|
|
|
|
|
def evaluate_bet(type_pari, cheval1, cheval2, race_results):
|
|
"""Evaluate if a bet won based on type_pari and race results."""
|
|
if not race_results:
|
|
return None
|
|
|
|
cheval1 = cheval1.strip().upper() if cheval1 else None
|
|
cheval2 = cheval2.strip().upper() if cheval2 else None
|
|
|
|
if type_pari == 'simple_gagnant':
|
|
if race_results['first'] and cheval1 == race_results['first'].upper():
|
|
return 'GAGNE'
|
|
return 'PERDU'
|
|
|
|
elif type_pari == 'simple_place':
|
|
if cheval1 and cheval1 in [c.upper() for c in race_results['top3']]:
|
|
return 'GAGNE'
|
|
return 'PERDU'
|
|
|
|
elif type_pari == 'couple_gagnant':
|
|
if not cheval1 or not cheval2:
|
|
return None
|
|
top2 = [race_results['first'], race_results['second']]
|
|
top2_upper = [c.upper() for c in top2 if c]
|
|
if cheval1 in top2_upper and cheval2 in top2_upper:
|
|
return 'GAGNE'
|
|
return 'PERDU'
|
|
|
|
elif type_pari == 'couple_place':
|
|
if not cheval1 or not cheval2:
|
|
return None
|
|
if cheval1 in [c.upper() for c in race_results['top3']] and \
|
|
cheval2 in [c.upper() for c in race_results['top3']]:
|
|
return 'GAGNE'
|
|
return 'PERDU'
|
|
|
|
return None
|
|
|
|
|
|
def evaluate_ze5_bet(type_pari, cheval1, race_results):
|
|
"""
|
|
Evaluate ZE5 bet (ZE5 B4/B3, ZE5 Conservateur, ZE5 Audacieux)
|
|
Returns: 'B5', 'B4', 'B3', 'PERDU'
|
|
"""
|
|
if not race_results or not cheval1:
|
|
return None
|
|
|
|
# Parse combo: "2 KROONER-7 JE TE CHERCHE-9 JOLIVERT..."
|
|
horses = [h.strip().split(' ', 1)[-1].upper() for h in cheval1.split('-') if h.strip()]
|
|
top5 = [c.upper() for c in race_results['top5']]
|
|
|
|
# Count how many of our horses are in top 5
|
|
matches = sum(1 for h in horses if h in top5)
|
|
|
|
if matches >= 5:
|
|
return 'B5' # 5/5 - Ordre or Désordre
|
|
elif matches >= 4:
|
|
return 'B4' # 4/5
|
|
elif matches >= 3:
|
|
return 'B3' # 3/5
|
|
else:
|
|
return 'PERDU'
|
|
|
|
|
|
def evaluate_ze4_bet(type_pari, cheval1, race_results):
|
|
"""
|
|
Evaluate ZE4 bet (Multi 4)
|
|
Returns: 'G4', 'G3', 'G2', 'PERDU'
|
|
"""
|
|
if not race_results or not cheval1:
|
|
return None
|
|
|
|
# Parse combo: "7 JE TE CHERCHE-15 IALTO-9 JOLIVERT-5 JENTIL"
|
|
horses = [h.strip().split(' ', 1)[-1].upper() for h in cheval1.split('-') if h.strip()]
|
|
top4 = [c.upper() for c in race_results['top5'][:4]]
|
|
|
|
# Count how many of our horses are in top 4
|
|
matches = sum(1 for h in horses if h in top4)
|
|
|
|
if matches >= 4:
|
|
return 'G4' # 4/4 - Gagnant!
|
|
elif matches >= 3:
|
|
return 'G3' # 3/4
|
|
elif matches >= 2:
|
|
return 'G2' # 2/4
|
|
else:
|
|
return 'PERDU'
|
|
|
|
|
|
def get_ze5_results(conn, date, race_name=None):
|
|
"""Get race results from pmu_partants for ZE5 evaluation"""
|
|
# Try to find Marseille/Borely race (course 1, reunion 1)
|
|
c = conn.execute("""
|
|
SELECT pp.nom as horse_name, pp.ordre_arrivee as position
|
|
FROM pmu_partants pp
|
|
JOIN pmu_reunions pr ON pr.date_programme=pp.date_programme AND pr.num_reunion=pp.num_reunion
|
|
WHERE pp.date_programme=? AND pp.ordre_arrivee > 0 AND pp.ordre_arrivee <= 5
|
|
AND pr.pays_code='FRA' AND pp.num_course=1
|
|
AND (pr.hippodrome_court LIKE '%BOR%' OR pr.hippodrome_long LIKE '%MARSEILLE%')
|
|
ORDER BY pp.num_reunion, pp.ordre_arrivee
|
|
""", (date,))
|
|
results = c.fetchall()
|
|
|
|
if not results:
|
|
# Fallback: first French race
|
|
c = conn.execute("""
|
|
SELECT pp.nom as horse_name, pp.ordre_arrivee as position
|
|
FROM pmu_partants pp
|
|
JOIN pmu_reunions pr ON pr.date_programme=pp.date_programme AND pr.num_reunion=pp.num_reunion
|
|
WHERE pp.date_programme=? AND pp.ordre_arrivee > 0 AND pp.ordre_arrivee <= 5
|
|
AND pr.pays_code='FRA' AND pp.num_course=1
|
|
ORDER BY pp.num_reunion, pp.ordre_arrivee
|
|
LIMIT 5
|
|
""", (date,))
|
|
results = c.fetchall()
|
|
|
|
if not results:
|
|
return None
|
|
|
|
return {
|
|
'first': results[0][0] if len(results) > 0 else None,
|
|
'second': results[1][0] if len(results) > 1 else None,
|
|
'third': results[2][0] if len(results) > 2 else None,
|
|
'top3': [r[0] for r in results[:3]],
|
|
'top5': [r[0] for r in results[:5]],
|
|
}
|
|
|
|
|
|
def update_recommendations():
|
|
"""Main function to update recommendations with results."""
|
|
conn = sqlite3.connect(DB_PATH)
|
|
c = conn.cursor()
|
|
|
|
c.execute('''
|
|
SELECT id, date, race_name, type_pari, cheval1, cheval2, resultat
|
|
FROM recommendations
|
|
WHERE resultat IS NULL OR resultat = ''
|
|
ORDER BY date DESC
|
|
''')
|
|
recommendations = c.fetchall()
|
|
|
|
if not recommendations:
|
|
print("No recommendations to update.")
|
|
conn.close()
|
|
return
|
|
|
|
updated = 0
|
|
skipped = 0
|
|
|
|
for rec in recommendations:
|
|
rec_id, date, race_name, type_pari, cheval1, cheval2, current_result = rec
|
|
|
|
# Handle ZE5 and ZE4 bets differently
|
|
if type_pari.startswith('ZE5') or 'ze5' in type_pari.lower():
|
|
race_results = get_ze5_results(conn, date)
|
|
if not race_results:
|
|
skipped += 1
|
|
continue
|
|
new_result = evaluate_ze5_bet(type_pari, cheval1, race_results)
|
|
elif 'ZE4' in type_pari or 'ze4' in type_pari.lower():
|
|
race_results = get_ze5_results(conn, date)
|
|
if not race_results:
|
|
skipped += 1
|
|
continue
|
|
new_result = evaluate_ze4_bet(type_pari, cheval1, race_results)
|
|
else:
|
|
race_results = get_race_results(conn, date, race_name)
|
|
if not race_results:
|
|
print(f"⚠️ No results found for {date} - {race_name}")
|
|
skipped += 1
|
|
continue
|
|
new_result = evaluate_bet(type_pari, cheval1, cheval2, race_results)
|
|
|
|
if new_result:
|
|
c.execute('''
|
|
UPDATE recommendations
|
|
SET resultat = ?
|
|
WHERE id = ?
|
|
''', (new_result, rec_id))
|
|
updated += 1
|
|
status = "✅" if new_result in ["GAGNE", "B5", "B4", "B3"] else "❌"
|
|
print(f"{status} {new_result} | {date} | {type_pari:20} | {cheval1[:40]}")
|
|
else:
|
|
skipped += 1
|
|
|
|
conn.commit()
|
|
|
|
print(f"\n{'='*50}")
|
|
print(f"Mise à jour terminée:")
|
|
print(f" - Recommandations mises à jour: {updated}")
|
|
print(f" - Ignorées (pas de résultats): {skipped}")
|
|
|
|
stats(conn)
|
|
|
|
conn.close()
|
|
|
|
|
|
def stats(conn):
|
|
"""Display statistics about recommendations."""
|
|
c = conn.cursor()
|
|
|
|
c.execute('''
|
|
SELECT
|
|
resultat,
|
|
COUNT(*) as count
|
|
FROM recommendations
|
|
WHERE resultat IS NOT NULL AND resultat != ''
|
|
GROUP BY resultat
|
|
''')
|
|
|
|
print(f"\n{'='*50}")
|
|
print("Statistiques des recommandations:")
|
|
|
|
total = 0
|
|
gagne = 0
|
|
for row in c.fetchall():
|
|
resultat, count = row
|
|
print(f" - {resultat}: {count}")
|
|
total += count
|
|
if resultat == 'GAGNE':
|
|
gagne = count
|
|
|
|
if total > 0:
|
|
print(f"\n ROI: {((gagne / total) * 100) - 100:.1f}%")
|
|
print(f" Taux de réussite: {(gagne / total) * 100:.1f}%")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print(f"Starting recommendation results update at {datetime.now()}")
|
|
print(f"Database: {DB_PATH}")
|
|
print("="*50)
|
|
update_recommendations()
|