291 lines
8.8 KiB
Python
291 lines
8.8 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Prompts LLM Centralisés - Turf Scraper
|
|
"""
|
|
import os
|
|
|
|
TURF_DB_SCHEMA = """
|
|
Tables disponibles dans turf.db:
|
|
|
|
1. predictions
|
|
- id (INTEGER PRIMARY KEY)
|
|
- date (TEXT) - format YYYY-MM-DD
|
|
- race_name (TEXT) - ex: "Quinté+"
|
|
- race_hippodrome (TEXT) - ex: "Vincennes"
|
|
- race_time (TEXT) - ex: "13:55"
|
|
- horse_number (INTEGER)
|
|
- horse_name (TEXT)
|
|
- odds (REAL) - cote
|
|
- prediction_rank (INTEGER) - position prédite (1-5)
|
|
- source (TEXT) - origine du prono
|
|
|
|
2. pmu_courses (table des courses)
|
|
- id (INTEGER PRIMARY KEY)
|
|
- date_programme (TEXT) - YYYY-MM-DD
|
|
- num_reunion (INTEGER)
|
|
- num_course (INTEGER)
|
|
- libelle (TEXT) - nom de la course
|
|
- libelle_court (TEXT)
|
|
- discipline (TEXT) - PLAT, TROT, ATTELE, MONTE
|
|
- distance (INTEGER)
|
|
- heure_depart_str (TEXT) - ex: "13:55"
|
|
|
|
3. pmu_partants (table des partants/chevaux)
|
|
- id (INTEGER PRIMARY KEY)
|
|
- date_programme (TEXT) - YYYY-MM-DD
|
|
- num_reunion (INTEGER)
|
|
- num_course (INTEGER)
|
|
- num_pmu (INTEGER) - numéro du cheval
|
|
- nom (TEXT) - nom du cheval
|
|
- driver (TEXT) - jockey
|
|
- entraineur (TEXT)
|
|
- cote_direct (REAL) - cote
|
|
- ordre_arrivee (INTEGER) - position finale (0 = pas couru)
|
|
- favoris (INTEGER) - 1 si favori
|
|
- tx_victoire (REAL) - taux de victoire %
|
|
- tx_place (REAL) - taux podium %
|
|
- forme_recente (REAL)
|
|
|
|
4. odds_history
|
|
- id (INTEGER PRIMARY KEY)
|
|
- date (TEXT)
|
|
- id_race (TEXT)
|
|
- scraped_at (TEXT)
|
|
- odds_json (TEXT) - cotes au moment X
|
|
"""
|
|
|
|
SAMPLE_DATA = """
|
|
Exemples de données réelles (2026-03-26):
|
|
- Hippodrome: CHANTILLY
|
|
- Course: PRIX DE LA JOURNEE DES PLANTES
|
|
- Type: PLAT
|
|
- Partants: TORTISAMBERT (3) @ 4.5, WIT (8) @ 7.1, BIN ZARAK (7) @ 12.7
|
|
- Résultat: TORTISAMBERT 1er, WIT 2ème, BIN ZARAK 3ème
|
|
"""
|
|
|
|
SYSTEM_PROMPT = """Tu es un expert en paris hippiques et turf français.
|
|
Tu as accès à une base de données SQLite (turf.db) contenant:
|
|
- Prédictions de pronostics (table: predictions)
|
|
- Résultats de courses PMU (table: pmu_partants)
|
|
- Historique des cotes (table: odds_history)
|
|
- Statistiques de performance (table: performance_stats)
|
|
|
|
{schema}
|
|
|
|
Règles importantes:
|
|
1. Tu génères ONLY des requêtes SQL SELECT (jamais INSERT/UPDATE/DELETE)
|
|
2. Les dates sont au format YYYY-MM-DD (ex: 2026-03-26)
|
|
3. Le taux de réussite se calcule: (victoires / total) * 100
|
|
4. Le ROI se calcule: (gains misés - mises) / mises * 100
|
|
5. Un cheval "placé" est sur le podium (positions 1, 2 ou 3)
|
|
6. Les positions 0 = pas encore couru
|
|
|
|
Réponds en français. Sois précis et concis."""
|
|
|
|
SQL_GENERATION_PROMPT = """
|
|
Génère une requête SQL SQLite pour répondre à cette question.
|
|
|
|
Question: {question}
|
|
|
|
Contraintes:
|
|
- ONLY SELECT queries (pas de INSERT/UPDATE/DELETE)
|
|
- Utilise les noms de colonnes exactes du schéma fourni
|
|
- Retourne uniquement le SQL, pas d'explication
|
|
- Si impossible, retourne "IMPOSSIBLE: [raison]"
|
|
|
|
Exemples de requêtes valides:
|
|
|
|
Q: "taux de réussite des favoris"
|
|
SQL: SELECT
|
|
AVG(CASE WHEN position = 1 THEN 100.0 ELSE 0.0 END) as win_rate,
|
|
AVG(CASE WHEN position IN (1,2,3) THEN 100.0 ELSE 0.0 END) as podium_rate,
|
|
COUNT(*) as total_races
|
|
FROM pmu_partants
|
|
WHERE numero IN (1,2,3)
|
|
AND date_armee >= date('now', '-30 days')
|
|
AND position > 0
|
|
|
|
Q: "meilleurs jockeys par victoires"
|
|
SQL: SELECT driver, COUNT(*) as wins,
|
|
AVG(CASE WHEN position IN (1,2,3) THEN 100.0 ELSE 0.0 END) as podium_rate,
|
|
AVG(position) as avg_position
|
|
FROM pmu_partants
|
|
WHERE position = 1
|
|
AND date_armee >= date('now', '-30 days')
|
|
GROUP BY driver
|
|
ORDER BY wins DESC
|
|
LIMIT 10
|
|
|
|
Q: "performances du cheval TORTISAMBERT"
|
|
SQL: SELECT * FROM performance_stats WHERE horse_name LIKE '%TORTISAMBERT%'
|
|
|
|
Q: "{question}"
|
|
SQL:"""
|
|
|
|
CONVERSATION_PROMPT = """
|
|
Historique de la conversation:
|
|
{history}
|
|
|
|
Dernière question: {question}
|
|
|
|
Analyse cette question en tenant compte du contexte précédent.
|
|
Si la question fait référence à un élément de l'historique, utilise-le.
|
|
Si la question est vague, fournis une réponse utile basée sur les données disponibles.
|
|
|
|
Contexte DB:
|
|
- predictions: tes prédictions
|
|
- pmu_partants: résultats réels
|
|
- odds_history: évolution des cotes
|
|
|
|
Réponds de façon conversationnelle en français."""
|
|
|
|
REPORT_PROMPTS = {
|
|
"daily": """
|
|
Génère un rapport quotidien de performance pour la date {date}.
|
|
|
|
Inclure:
|
|
1. Nombre de courses aujourd'hui
|
|
2. Résultats (positions des partants)
|
|
3. Taux de réussite des prédictions
|
|
4. ROI du jour
|
|
5. Meilleure cote réalisée
|
|
|
|
Format: Markdown français concis.
|
|
""",
|
|
"weekly": """
|
|
Génère un rapport hebdomadaire pour la semaine du {start_date} au {end_date}.
|
|
|
|
Inclure:
|
|
1. Total courses, paris, gains
|
|
2. Taux de réussite global
|
|
3. Meilleurs jockeys/chiens cette semaine
|
|
4. Évolution vs semaine précédente
|
|
5. Recommandations
|
|
|
|
Format: Markdown français concis.
|
|
""",
|
|
"monthly": """
|
|
Génère un rapport mensuel pour {month} {year}.
|
|
|
|
Inclure:
|
|
1. Bilan全局 (courses, paris, gains, ROI)
|
|
2. Meilleures performances par hippodrome
|
|
3. Trends (amélioration/détérioration)
|
|
4. Statistiques détaillées
|
|
|
|
Format: Markdown français concis.
|
|
"""
|
|
}
|
|
|
|
SUGGESTIONS_PROMPT = """
|
|
Basé sur les données disponibles dans turf.db, génère 5 suggestions de questions
|
|
que l'utilisateur pourrait poser.
|
|
|
|
Les suggestions doivent couvrir différents aspects:
|
|
- Statistiques de performance
|
|
- Analyse de courses/chevaux spécifiques
|
|
- Historique et tendances
|
|
- ROI et gains
|
|
|
|
Retourne SEULEMENT une liste de questions, une par ligne, sans numérotation."""
|
|
|
|
KEYWORD_PATTERNS = {
|
|
"favoris": """
|
|
SELECT
|
|
AVG(CASE WHEN ordre_arrivee = 1 THEN 100.0 ELSE 0.0 END) as win_rate,
|
|
AVG(CASE WHEN ordre_arrivee IN (1,2,3) THEN 100.0 ELSE 0.0 END) as podium_rate,
|
|
COUNT(*) as total_races
|
|
FROM pmu_partants
|
|
WHERE favoris = 1 AND ordre_arrivee > 0
|
|
AND date_programme >= date('now', '-7 days')""",
|
|
|
|
"jockeys": """
|
|
SELECT driver, COUNT(*) as wins,
|
|
AVG(CASE WHEN ordre_arrivee IN (1,2,3) THEN 100.0 ELSE 0.0 END) as podium_rate,
|
|
AVG(ordre_arrivee) as avg_position
|
|
FROM pmu_partants
|
|
WHERE ordre_arrivee = 1 AND date_programme >= date('now', '-30 days')
|
|
GROUP BY driver ORDER BY wins DESC LIMIT 10""",
|
|
|
|
"entraineurs": """
|
|
SELECT entraineur, COUNT(*) as wins,
|
|
AVG(CASE WHEN ordre_arrivee IN (1,2,3) THEN 100.0 ELSE 0.0 END) as podium_rate
|
|
FROM pmu_partants
|
|
WHERE ordre_arrivee = 1 AND date_programme >= date('now', '-30 days')
|
|
GROUP BY entraineur ORDER BY wins DESC LIMIT 10""",
|
|
|
|
"programme": """
|
|
SELECT c.id, c.num_reunion, c.num_course, c.libelle, c.discipline, c.distance, c.heure_depart_str,
|
|
COUNT(p.id) as 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 = date('now')
|
|
GROUP BY c.id
|
|
ORDER BY c.num_reunion, c.num_course""",
|
|
|
|
"resultats": """
|
|
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 = date('now', '-1 day')
|
|
AND p.ordre_arrivee > 0
|
|
ORDER BY c.num_reunion, c.num_course, p.ordre_arrivee""",
|
|
|
|
"gagnants": """
|
|
SELECT p.nom as cheval, p.cote_direct as cote, c.libelle as course, p.date_programme
|
|
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.ordre_arrivee = 1 AND p.cote_direct > 0
|
|
ORDER BY p.date_programme DESC
|
|
LIMIT 10""",
|
|
|
|
"cote": """
|
|
SELECT p.nom as cheval, p.cote_direct as cote_initiale,
|
|
p.ordre_arrivee as position, p.date_programme
|
|
FROM pmu_partants p
|
|
WHERE p.date_programme >= date('now', '-7 days')
|
|
AND p.ordre_arrivee > 0
|
|
ORDER BY p.date_programme DESC""",
|
|
}
|
|
|
|
|
|
def get_system_prompt() -> str:
|
|
"""Retourne le prompt système complet"""
|
|
return SYSTEM_PROMPT.format(schema=TURF_DB_SCHEMA)
|
|
|
|
|
|
def get_sql_prompt(question: str) -> str:
|
|
"""Retourne le prompt pour génération SQL"""
|
|
return SQL_GENERATION_PROMPT.format(question=question)
|
|
|
|
|
|
def get_conversation_prompt(question: str, history: str = "") -> str:
|
|
"""Retourne le prompt pour conversation"""
|
|
if history:
|
|
return CONVERSATION_PROMPT.format(question=question, history=history)
|
|
return f"Question: {question}\n\n{get_sql_prompt(question)}"
|
|
|
|
|
|
def get_report_prompt(report_type: str, **kwargs) -> str:
|
|
"""Retourne le prompt pour un rapport"""
|
|
template = REPORT_PROMPTS.get(report_type, "")
|
|
return template.format(**kwargs)
|
|
|
|
|
|
def get_suggestions_prompt() -> str:
|
|
"""Retourne le prompt pour suggestions"""
|
|
return SUGGESTIONS_PROMPT
|
|
|
|
|
|
def get_keyword_sql(question: str) -> str | None:
|
|
"""Retourne SQL basé sur mots-clés (fallback)"""
|
|
q = question.lower()
|
|
for keyword, sql in KEYWORD_PATTERNS.items():
|
|
if keyword in q:
|
|
return sql
|
|
return None
|