Compare commits

...

1 Commits

Author SHA1 Message Date
DevOps Engineer
0492f06bfd docs(HRT-96): Note Intelligence ML + documentation API v1 finale
- Création POD/Intelligence/ML_Predictions_SaaS.md : architecture ML complète,
  flow ml_predictions_cache → ml_feedback_saas → paris → ROI dashboard,
  schéma données/jointures, décision duplication vs modification turf_scraper,
  documentation des 4 stratégies XGBoost, idempotence, usage CLI
- Mise à jour DOCUMENTATION.md : ajout section Turf SaaS API v1 complète
  avec tous les endpoints documentés dont /api/v1/roi/* et /api/v1/ml/feedback/*
  (HRT-92 ROI backend + HRT-93 ML feedback loop)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-30 21:28:52 +02:00
2 changed files with 620 additions and 0 deletions

View File

@@ -155,3 +155,284 @@ python app.py
---
*Document généré automatiquement - Dépenses Trello*
---
---
# Turf SaaS — Documentation API v1
**Mise à jour** : 2026-04-30 (HRT-96 — ML Predictions + ROI + Feedback)
**URL SaaS** : https://turf-saas-kolifee.duckdns.org
**Port local** : 8792
**Base de données** : `/home/h3r7/turf_saas/turf_saas.db`
---
## Stack Technique Turf SaaS
| Composant | Technologie |
|---|---|
| Backend | Python Flask + Blueprints |
| Auth | JWT (access + refresh tokens) |
| Base de données | SQLite (`turf_saas.db`) |
| ML | XGBoost v1 (prédictions courses PMU) |
| Frontend | HTML5 + Chart.js |
| Hébergement | VPS Linux — https://turf-saas-kolifee.duckdns.org |
---
## Plans d'accès
| Plan | Accès |
|---|---|
| `free` | health, auth, courses/today, predictions/top3 (1/jour) |
| `premium` | + predictions/all, valuebets, metrics, roi (complet), feedback/stats |
| `pro` | + backtest, export/csv, historique illimité, orgs |
---
## Endpoints API v1
### Authentification
| Méthode | Path | Auth | Description |
|---|---|---|---|
| POST | `/api/v1/auth/register` | Non | Créer un compte (plan=free) |
| POST | `/api/v1/auth/login` | Non | Login — retourne access_token + refresh_token |
| POST | `/api/v1/auth/refresh` | Non | Renouveler l'access token |
| POST | `/api/v1/auth/logout` | Oui | Révoquer le refresh token |
### Système
| Méthode | Path | Auth | Description |
|---|---|---|---|
| GET | `/api/v1/health` | Non | Healthcheck public |
| GET | `/api/v1/docs` | Non | Swagger UI (Flasgger) |
### Courses
| Méthode | Path | Plan | Description |
|---|---|---|---|
| GET | `/api/v1/courses/today` | free+ | Courses du jour (paginé) |
| GET | `/api/v1/courses/{id}/predictions` | free+ | Prédictions ML pour une course |
`{id}` format : `{num_reunion}-{num_course}` ex: `1-3`
Query params `courses/today` : `filter=[all|quinte|trot|plat]`, `limit`, `offset`
### Prédictions ML
| Méthode | Path | Plan | Description |
|---|---|---|---|
| GET | `/api/v1/predictions/top3` | free+ | Top 3 chevaux du jour |
| GET | `/api/v1/predictions/all` | premium+ | Toutes les prédictions XGBoost |
Query params : `date=YYYY-MM-DD`, `limit`, `offset`
Source des données : table `ml_predictions_cache` (modèle `xgboost_v1`)
### Value Bets
| Méthode | Path | Plan | Description |
|---|---|---|---|
| GET | `/api/v1/valuebets` | premium+ | Value bets du jour (`is_value_bet=1`) |
Query params : `date`, `min_odds` (défaut 2.0), `limit`, `offset`
### Métriques ML
| Méthode | Path | Plan | Description |
|---|---|---|---|
| GET | `/api/v1/metrics` | premium+ | Métriques perf ML (precision, ROI, top-3 rate) |
Query params : `days` (int, défaut 30, max 365)
### ROI par Modèle/Stratégie (HRT-92)
| Méthode | Path | Plan | Description |
|---|---|---|---|
| GET | `/api/v1/roi/by-model` | premium+ | ROI calculé par stratégie ML XGBoost |
**Query params** :
- `strategy` : filtrer par stratégie (`xgboost_sg`, `xgboost_value`, `xgboost_sp`, `xgboost_2sur4`)
- `days` : période en jours (défaut 30, max 365)
**Réponse** :
```json
{
"period": {"start": "2026-04-01", "end": "2026-04-30", "days": 30},
"models": [
{
"model_source": "xgboost_sg",
"nb_paris": 42,
"mise": 42.0,
"gain": 51.3,
"roi_pct": 22.1,
"win_rate": 28.6
}
]
}
```
**Jointures** : `paris``pmu_partants` (résultats) ← `pmu_rapports` (dividendes)
**Accès plan** : Free = 1 stratégie max, Premium/Pro = complet + historique illimité
### ML Feedback Loop (HRT-93)
| Méthode | Path | Plan | Description |
|---|---|---|---|
| POST | `/api/v1/ml/feedback/run` | Admin | Déclencher ml_feedback_saas.py manuellement |
| GET | `/api/v1/ml/feedback/stats` | premium+ | Stats paris par stratégie XGBoost |
**POST `/api/v1/ml/feedback/run`** — Corps optionnel :
```json
{"date": "2026-04-29"}
```
ou
```json
{"backfill": "2026-04-20"}
```
**GET `/api/v1/ml/feedback/stats`** — Réponse :
```json
{
"stats": [
{
"source_reco": "xgboost_sg",
"nb_paris": 42,
"nb_gagnes": 12,
"win_rate_pct": 28.6,
"mise_totale": 42.0,
"gain_total": 51.3,
"roi_pct": 22.1
}
],
"last_run": "2026-04-29T18:30:00"
}
```
**Stratégies XGBoost** :
| Stratégie | Type pari | Condition | Mise |
|---|---|---|---|
| `xgboost_sg` | simple_gagnant | top1 ML, ml_score >= 70 | 1€ |
| `xgboost_value` | simple_gagnant | is_value_bet = 1 | 1€ |
| `xgboost_sp` | simple_place | top1 ML, ml_score >= 50 | 1€ |
| `xgboost_2sur4` | deux_sur_quatre | top4 ML, 6 combos | 6€ |
### Backtest
| Méthode | Path | Plan | Description |
|---|---|---|---|
| GET | `/api/v1/backtest` | pro | Résultats historiques des paris |
Query params : `start`, `end` (YYYY-MM-DD), `limit`, `offset`
### Export
| Méthode | Path | Plan | Description |
|---|---|---|---|
| GET | `/api/v1/export/csv` | pro | Export CSV |
Query params : `type=[predictions|bets]`, `date`, `start`, `end`
### Historique
| Méthode | Path | Plan | Description |
|---|---|---|---|
| GET | `/api/v1/history` | free+ | Historique prédictions ML |
Limites : Free = 7j, Premium = 90j, Pro = illimité
### Organisations
| Méthode | Path | Plan | Description |
|---|---|---|---|
| GET | `/api/v1/org/` | pro | Détails de l'organisation |
| POST | `/api/v1/org/` | pro | Créer une organisation |
| POST | `/api/v1/org/invite` | pro | Inviter un membre (max 5) |
| DELETE | `/api/v1/org/members/{id}` | pro | Retirer un membre |
### Utilisateur & Tokens
| Méthode | Path | Plan | Description |
|---|---|---|---|
| GET | `/api/v1/user/profile` | free+ | Profil utilisateur |
| PUT | `/api/v1/user/alerts` | premium+ | Config alertes Telegram |
| GET | `/api/v1/user/api-token` | pro | Token API personnel |
| POST | `/api/v1/user/api-token` | pro | Générer/régénérer token API |
| GET | `/api/v1/user/webhook` | pro | Config webhook |
| PUT | `/api/v1/user/webhook` | pro | Modifier webhook |
### Billing (Stripe)
| Méthode | Path | Auth | Description |
|---|---|---|---|
| POST | `/api/v1/billing/checkout` | Oui | Créer session Stripe Checkout |
| POST | `/api/v1/billing/portal` | Oui | Portail Stripe (gestion abonnement) |
| GET | `/api/v1/billing/status` | Oui | Statut abonnement actuel |
| POST | `/api/v1/billing/webhook` | Non | Webhook Stripe (events) |
---
## Format de réponse uniforme
**Erreurs** :
```json
{
"status": "error",
"message": "Description de l'erreur",
"code": 400
}
```
**Listes paginées** :
```json
{
"pagination": {
"total": 150,
"limit": 20,
"offset": 0,
"has_more": true
}
}
```
---
## Architecture ML — Résumé
```
ml_predictions_cache (XGBoost v1)
→ ml_feedback_saas.py
→ table paris (source_reco = xgboost_*)
→ /api/v1/roi/by-model (ROI calculé)
→ /api/v1/ml/feedback/stats (stats)
→ dashboard_saas.html (Section Performance & ROI)
```
Voir documentation complète : `POD/Intelligence/ML_Predictions_SaaS.md`
---
## Démarrage
```bash
cd /home/h3r7/turf_saas
source venv/bin/activate
python app_v1.py
# ou via gunicorn
gunicorn -w 2 -b 0.0.0.0:8792 app_v1:app
```
## Tests
```bash
cd /home/h3r7/turf_saas
source venv/bin/activate
python -m pytest tests/ -v
```
---
*Turf SaaS — H3R7Tech — Mise à jour 2026-04-30 (HRT-96)*

View File

@@ -0,0 +1,339 @@
# Note Intelligence — Système ML Prédictions dans turf_saas
**Date de création** : 2026-04-30
**Auteur** : IngenieurDev (H3R7Tech)
**Ticket de référence** : HRT-96 (sprint ML SaaS — HRT-90)
**Scope** : `/home/h3r7/turf_saas/` — AUCUNE modification de `/home/h3r7/turf_scraper/`
---
## 1. Contexte & Décision architecturale
### 1.1 Deux systèmes, deux DB
H3R7Tech exploite deux dépôts séparés :
| Dépôt | Rôle | Base de données |
|---|---|---|
| `/home/h3r7/turf_scraper/` | Scraping PMU + entraînement XGBoost | `turf.db` |
| `/home/h3r7/turf_saas/` | SaaS utilisateurs + API v1 + dashboard | `turf_saas.db` |
### 1.2 Décision de duplication (vs modification turf_scraper)
**Choix : dupliquer les tables et scripts ML dans turf_saas.db, sans toucher à turf_scraper.**
Justification :
- `turf_scraper` est la source de vérité du scraping PMU et des modèles XGBoost — toute modification risque de casser la chaîne de collecte de données.
- `turf_saas` doit fonctionner de manière autonome, avec ses propres utilisateurs, subscriptions et données.
- La table `ml_predictions_cache` est *pré-peuplée* dans `turf_saas.db` par un processus de synchronisation (scheduler ou copie périodique depuis `turf.db`).
- Le feedback loop (`ml_feedback_saas.py`) écrit dans `paris` de `turf_saas.db` uniquement.
---
## 2. Architecture du système ML dans turf_saas
### 2.1 Vue d'ensemble du flow
```
[turf_scraper/turf.db]
└── ml_predictions_cache (XGBoost v1)
│ [sync périodique / scheduler]
[turf_saas/turf_saas.db]
├── ml_predictions_cache ← prédictions XGBoost importées
├── pmu_partants ← données courses PMU
├── pmu_rapports ← dividendes réels PMU
├── paris ← paris virtuels ML (ml_feedback_saas.py)
└── API v1 ──┬── GET /api/v1/predictions/* (lecture ml_predictions_cache)
├── GET /api/v1/roi/by-model (jointure paris + rapports)
├── POST /api/v1/ml/feedback/run (déclenche ml_feedback_saas)
└── GET /api/v1/ml/feedback/stats (stats par stratégie)
[dashboard_saas.html]
Section "Performance & ROI"
Chart.js — ROI par modèle / évolution
```
### 2.2 Table `ml_predictions_cache` (turf_saas.db)
Table centrale du système ML. Contient les prédictions XGBoost pour chaque cheval/course.
| Colonne | Type | Description |
|---|---|---|
| `date` | TEXT | Date de la course (YYYY-MM-DD) |
| `num_reunion` | INTEGER | Numéro de réunion |
| `num_course` | INTEGER | Numéro de course |
| `horse_name` | TEXT | Nom du cheval |
| `horse_number` | INTEGER | Numéro du cheval |
| `odds` | REAL | Cote au moment de la prédiction |
| `prob_top1` | REAL | Probabilité XGBoost de finir 1er |
| `prob_top3` | REAL | Probabilité XGBoost de finir top 3 |
| `ml_score` | REAL | Score ML composite (0100) |
| `recommendation` | TEXT | `top1` / `top3` / `value_bet` |
| `is_value_bet` | INTEGER | 1 si value bet détecté |
| `is_outlier` | INTEGER | 1 si outlier de cote |
| `race_label` | TEXT | Ex: `R1C3` |
| `model_version` | TEXT | Version du modèle (ex: `xgboost_v1`) |
| `risque_label` | TEXT | Niveau de risque (`low`/`neutral`/`high`) |
| `risque_score` | INTEGER | Score risque (0100) |
**Contrainte d'unicité** : `(date, num_reunion, num_course, horse_name)` — garantit l'idempotence des imports.
**Volume actuel** : ~1 000 entrées (2 dates de courses).
---
## 3. Feedback Loop ML — `ml_feedback_saas.py`
### 3.1 Rôle
Script Python autonome qui :
1. Lit les prédictions XGBoost dans `ml_predictions_cache` de `turf_saas.db`
2. Génère des paris virtuels selon 4 stratégies XGBoost
3. Insère les paris dans la table `paris` de `turf_saas.db`
4. Est **idempotent** : ne duplique pas les paris existants
### 3.2 Stratégies supportées
| Stratégie | Type pari | Condition sélection | Mise |
|---|---|---|---|
| `xgboost_sg` | `simple_gagnant` | top 1 ML par course, `ml_score >= 70` | 1€ |
| `xgboost_value` | `simple_gagnant` | `is_value_bet = 1` | 1€ |
| `xgboost_sp` | `simple_place` | top 1 ML par course, `ml_score >= 50` | 1€ |
| `xgboost_2sur4` | `deux_sur_quatre` | top 4 ML par course, 6 combos générés | 6€ (1€/combo) |
### 3.3 Schéma d'idempotence
```python
# Vérifie avant insertion
SELECT id FROM paris
WHERE date_course = ?
AND source_reco = ? # ex: 'xgboost_sg'
AND type_pari = ?
AND numero1 = ?
AND race_label = ?
```
Si le pari existe déjà → skip (aucune duplication).
### 3.4 Table `paris` — colonnes clés pour le ML
| Colonne | Valeur ML |
|---|---|
| `source_reco` | `xgboost_sg` / `xgboost_value` / `xgboost_sp` / `xgboost_2sur4` |
| `model_source` | `xgboost_v1` (héritée de ml_predictions_cache) |
| `type_pari` | `simple_gagnant` / `simple_place` / `deux_sur_quatre` |
| `statut` | `EN_ATTENTE``GAGNE` / `PERDU` (mise à jour par update_paris_results.py) |
| `gain` | Dividende réel × mise (depuis pmu_rapports) |
### 3.5 Usage CLI
```bash
# Traitement du jour
python3 ml_feedback_saas.py
# Date spécifique
python3 ml_feedback_saas.py --date 2026-04-29
# Backfill
python3 ml_feedback_saas.py --backfill 2026-04-20
```
**Différence avec `turf_scraper/ml_feedback.py`** :
- `DB_PATH` = `/home/h3r7/turf_saas/turf_saas.db` (PAS `/home/h3r7/turf_scraper/turf.db`)
- Logs dans `/home/h3r7/turf_saas/logs/`
- AUCUNE référence à `turf_scraper`
---
## 4. API ROI — `/api/v1/roi/*`
### 4.1 Route principale
**`GET /api/v1/roi/by-model`** — Calcul du ROI par modèle/stratégie
Jointures SQL :
```sql
-- paris ←→ pmu_partants (via race_label + date + numero)
-- paris ←→ pmu_rapports (dividendes réels)
SELECT
p.source_reco AS model_source,
COUNT(p.id) AS nb_paris,
SUM(p.mise) AS mise_totale,
SUM(p.gain) AS gain_total,
(SUM(p.gain) - SUM(p.mise)) / SUM(p.mise) * 100 AS roi_pct,
COUNT(CASE WHEN p.statut='GAGNE' THEN 1 END) * 100.0 / COUNT(p.id) AS win_rate
FROM paris p
WHERE p.date_course BETWEEN :start AND :end
AND (:strategy IS NULL OR p.source_reco = :strategy)
GROUP BY p.source_reco
```
**Paramètres query** :
- `?strategy=xgboost_sg` — filtrer par stratégie (optionnel)
- `?days=30` — fenêtre temporelle en jours (défaut : 30, max : 365)
**Réponse JSON** :
```json
{
"period": {"start": "2026-04-01", "end": "2026-04-30", "days": 30},
"models": [
{
"model_source": "xgboost_sg",
"nb_paris": 42,
"mise": 42.0,
"gain": 51.3,
"roi_pct": 22.1,
"win_rate": 28.6
}
]
}
```
**Accès plan** :
- `free` : 1 stratégie max
- `premium` : complet
- `pro` : complet + historique illimité
### 4.2 Blueprint `api_v1/routes/roi.py`
Enregistré dans `api_v1/__init__.py` avec :
```python
from .routes.roi import roi_bp
app.register_blueprint(roi_bp)
```
---
## 5. API ML Feedback — `/api/v1/ml/feedback/*`
### 5.1 Routes
| Méthode | Path | Auth | Description |
|---|---|---|---|
| `POST` | `/api/v1/ml/feedback/run` | Admin | Déclenche `ml_feedback_saas.py` manuellement |
| `GET` | `/api/v1/ml/feedback/stats` | Premium+ | Stats paris par stratégie XGBoost |
### 5.2 `POST /api/v1/ml/feedback/run`
- Réservé aux admins (token admin requis)
- Déclenche le script `ml_feedback_saas.py` en subprocess
- Corps optionnel : `{"date": "2026-04-29"}` ou `{"backfill": "2026-04-20"}`
### 5.3 `GET /api/v1/ml/feedback/stats`
Retourne les statistiques agrégées par stratégie :
```json
{
"stats": [
{
"source_reco": "xgboost_sg",
"nb_paris": 42,
"nb_gagnes": 12,
"win_rate_pct": 28.6,
"mise_totale": 42.0,
"gain_total": 51.3,
"roi_pct": 22.1
}
],
"last_run": "2026-04-29T18:30:00"
}
```
### 5.4 Blueprint `api_v1/routes/ml_feedback.py`
Enregistré dans `api_v1/__init__.py` avec :
```python
from .routes.ml_feedback import ml_feedback_bp
app.register_blueprint(ml_feedback_bp)
```
---
## 6. Jointures de données — Schéma complet
```
ml_predictions_cache
date, num_reunion, num_course, horse_name, horse_number
ml_score, recommendation, is_value_bet
race_label, model_version
│ [ml_feedback_saas.py]
paris
date_course, race_label, numero1
source_reco (= stratégie XGBoost)
model_source (= xgboost_v1)
type_pari, mise, statut, gain
├──── JOIN pmu_partants ──── date_programme + num_reunion + num_course + num_pmu
│ ordre_arrivee (résultat réel)
└──── JOIN pmu_rapports ──── date_programme + num_reunion + num_course + type_pari
dividende_euro (gain réel calculé)
```
---
## 7. Dashboard SaaS — Section ROI
Le dashboard `dashboard_saas.html` intègre une section **"Performance & ROI"** (implémentée dans HRT-94) :
- Graphique ROI par `model_source` (histogramme Chart.js)
- Évolution ROI dans le temps (line chart, 7j/30j/90j)
- Tableau : `model_source | nb paris | mise | gain | ROI% | win_rate%`
- Filtre dropdown par stratégie
- Gating plan : Free = 1 stratégie, Premium/Pro = complet
Appel API dashboard :
```javascript
fetch('/api/v1/roi/by-model?days=30')
```
---
## 8. Points d'attention & limites
1. **Données ML limitées** : actuellement 1 000 prédictions sur 2 dates (2026-04-24 et 2026-04-25). La pertinence du ROI augmentera avec le volume de données.
2. **Pas de paris XGBoost actifs** : la table `paris` contient des paris `manual`, `scoring_v2`, `canalturf` mais pas encore de paris `xgboost_*`. HRT-93 (ml_feedback_saas.py) doit être complété et exécuté.
3. **Modèle unique** : `model_version = 'xgboost_v1'`. L'évolution vers des versions de modèle multiples est prévue dans la roadmap.
4. **Sync turf_scraper → turf_saas** : le mécanisme de synchronisation de `ml_predictions_cache` n'est pas encore documenté formellement. À documenter dans une prochaine Note Intelligence.
5. **update_paris_results.py** : script de mise à jour des statuts paris (`EN_ATTENTE → GAGNE/PERDU`) à partir de `pmu_rapports` — dépendance critique pour le calcul du ROI réel.
---
## 9. Fichiers clés
| Fichier | Rôle |
|---|---|
| `turf_saas.db` | Base de données principale SaaS |
| `ml_feedback_saas.py` | Feedback loop ML (à créer — HRT-93) |
| `api_v1/routes/roi.py` | Routes API ROI (à créer — HRT-92) |
| `api_v1/routes/ml_feedback.py` | Routes API feedback (à créer — HRT-93) |
| `api_v1/__init__.py` | Enregistrement des blueprints |
| `dashboard_saas.html` | Dashboard SaaS avec section ROI |
| `update_paris_results.py` | MAJ statuts paris depuis résultats PMU |
| `scoring_v2.py` | Scoring engine (stratégie scoring_v2) |
---
## 10. Références tickets
| Ticket | Description | Statut |
|---|---|---|
| HRT-90 | Orchestration ML SaaS (parent) | blocked |
| HRT-92 | Backend: API ROI par modèle | in_progress |
| HRT-93 | ML feedback loop ml_feedback_saas | in_progress |
| HRT-94 | Frontend: Dashboard ROI | in_progress |
| HRT-95 | QA: Tests end-to-end ML + ROI | in_progress |
| HRT-96 | Note Intelligence ML + documentation (ce ticket) | in_progress |