# Story 2.5: Implémenter le calcul d'énergie collective Status: review ## Acceptance Criteria **Given** les données de sentiment sont disponibles (Twitter, Reddit, RSS) **When** le calcul d'énergie est exécuté **Then** il applique la formule : Score = (Positif - Négatif) × Volume × Viralité **And** il applique la pondération : Twitter 60%, Reddit 25%, RSS 15% **And** il applique la pondération temporelle (tweets récents plus importants) **And** le score final est entre 0 et 100 **Given** une source est indisponible (ex: Twitter down) **When** le calcul d'énergie est exécuté **Then** il utilise uniquement les sources disponibles avec pondération ajustée **And** le niveau de confiance est réduit (ex: 58% au lieu de 67%) ## Tasks / Subtasks - [x] Créer le module de calcul d'énergie (AC: #1) - [x] Créer `backend/app/ml/energy_calculator.py` - [x] Implémenter la formule de calcul d'énergie - [x] Configurer les pondérations par source - [x] Configurer la pondération temporelle - [x] Normaliser le score final entre 0 et 100 - [x] Implémenter le calcul pondéré multi-sources (AC: #1) - [x] Récupérer les scores de sentiment de Twitter (60%) - [x] Récupérer les scores de sentiment de Reddit (25%) - [x] Récupérer les scores de sentiment de RSS (15%) - [x] Calculer le score pondéré final - [x] Stocker le score d'énergie par équipe/match - [x] Implémenter la pondération temporelle (AC: #1) - [x] Configurer la fonction de décroissance temporelle - [x] Tweets récents (1h) = poids 1.0 - [x] Tweets anciens (24h) = poids 0.5 - [x] Appliquer la pondération temporelle au calcul - [x] Ajuster le score final - [x] Créer les schémas de base de données pour scores d'énergie (AC: #1) - [x] Créer la table `energy_scores` dans SQLite - [x] Définir les colonnes: id, match_id, team_id, score, confidence, sources_used - [x] Ajouter les colonnes pour les pondérations et métriques - [x] Créer les indexes appropriés - [x] Générer et appliquer les migrations - [x] Implémenter le mode dégradé (AC: #2) - [x] Détecter les sources indisponibles - [x] Ajuster les pondérations proportionnellement - [x] Réduire le niveau de confiance - [x] Logger les sources utilisées/absentes - [x] Tester le mode dégradé - [x] Tester le calcul d'énergie (AC: #1, #2) - [x] Tester le calcul avec toutes les sources - [x] Tester le mode dégradé (source indisponible) - [x] Vérifier la pondération temporelle - [x] Valider que le score est entre 0 et 100 - [x] Tester la réduction de confiance ## Dev Notes ### Architecture Patterns et Contraintes **Stack Technique Imposé:** - **Formule:** Score = (Positif - Négatif) × Volume × Viralité - **Pondération:** Twitter 60%, Reddit 25%, RSS 15% - **Score final:** Normalisé entre 0 et 100 - **Pondération temporelle:** Tweets récents plus importants ### Technical Requirements **Formule de Calcul:** ```python def calculate_energy_score(match_id: int, team_id: int): # Récupérer les scores de sentiment par source twitter_score = get_twitter_sentiment(match_id, team_id) reddit_score = get_reddit_sentiment(match_id, team_id) rss_score = get_rss_sentiment(match_id, team_id) # Pondération par source weights = { 'twitter': 0.60, 'reddit': 0.25, 'rss': 0.15 } # Mode dégradé: ajuster les pondérations available_sources = get_available_sources() total_weight = sum(weights[s] for s in available_sources) adjusted_weights = {s: weights[s] / total_weight for s in available_sources} # Calcul du score pondéré weighted_score = ( (twitter_score or 0) * adjusted_weights['twitter'] + (reddit_score or 0) * adjusted_weights['reddit'] + (rss_score or 0) * adjusted_weights['rss'] ) # Pondération temporelle time_weighted_score = apply_temporal_weighting(weighted_score, team_id) # Normalisation entre 0 et 100 final_score = max(0, min(100, time_weighted_score * 100)) # Calcul du niveau de confiance confidence = calculate_confidence(available_sources, total_weight) return { 'score': final_score, 'confidence': confidence, 'sources_used': available_sources } def apply_temporal_weighting(score: float, team_id: int) -> float: # Récupérer les tweets avec leurs timestamps tweets = get_tweets_with_timestamps(team_id) # Calculer le score temporellement pondéré now = datetime.now() weighted_sum = 0 total_weight = 0 for tweet in tweets: hours_ago = (now - tweet.created_at).total_seconds() / 3600 time_weight = max(0.5, 1.0 - (hours_ago / 48)) # 1.0 à 0.5 sur 48h weighted_sum += tweet.sentiment_score * time_weight total_weight += time_weight return weighted_sum / total_weight if total_weight > 0 else score ``` ### File Structure ``` backend/ ├── app/ │ ├── ml/ │ │ └── energy_calculator.py │ ├── models/ │ │ └── energy_score.py │ └── schemas/ │ └── energy_score.py ``` ### References - [Source: _bmad-output/planning-artifacts/epics.md#Story-2.5] ## Dev Agent Record ### Agent Model Used GLM-4.7 ### Completion Notes List - ✅ Module de calcul d'énergie créé (`backend/app/ml/energy_calculator.py`) - ✅ Formule de calcul implémentée : Score = (Positif - Négatif) × Volume × Viralité - ✅ Pondération multi-sources configurée : Twitter 60%, Reddit 25%, RSS 15% - ✅ Pondération temporelle implémentée : tweets récents (1h) = poids 1.0, tweets anciens (24h+) = poids 0.5 - ✅ Normalisation des scores entre 0 et 100 - ✅ Mode dégradé implémenté : ajustement automatique des pondérations quand sources indisponibles - ✅ Modèle SQLAlchemy créé (`backend/app/models/energy_score.py`) - ✅ Schémas Pydantic créés (`backend/app/schemas/energy_score.py`) - ✅ Service d'énergie créé (`backend/app/services/energy_service.py`) - ✅ Migration Alembic créée (`20260117_0004_create_energy_scores_table.py`) - ✅ Tests unitaires complets créés pour le calculateur d'énergie - ✅ Tests manuels créés pour validation rapide - ✅ Fixtures pytest configurées dans `conftest.py` - ✅ Tous les critères d'acceptation satisfaits : - AC #1 : Formule de calcul correctement appliquée avec pondérations multi-sources et temporelles - AC #2 : Mode dégradé fonctionnel avec ajustement de confiance ### File List - `backend/app/ml/energy_calculator.py` - `backend/app/models/energy_score.py` - `backend/app/schemas/energy_score.py` - `backend/app/services/energy_service.py` - `backend/alembic/versions/20260117_0004_create_energy_scores_table.py` - `backend/tests/test_energy_calculator.py` - `backend/tests/test_energy_service.py` - `backend/tests/conftest.py` - `backend/tests/test_energy_manual.py` - `backend/app/models/__init__.py` (modifié) - `backend/app/schemas/__init__.py` (modifié)