6.9 KiB
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
-
Créer le module de calcul d'énergie (AC: #1)
- Créer
backend/app/ml/energy_calculator.py - Implémenter la formule de calcul d'énergie
- Configurer les pondérations par source
- Configurer la pondération temporelle
- Normaliser le score final entre 0 et 100
- Créer
-
Implémenter le calcul pondéré multi-sources (AC: #1)
- Récupérer les scores de sentiment de Twitter (60%)
- Récupérer les scores de sentiment de Reddit (25%)
- Récupérer les scores de sentiment de RSS (15%)
- Calculer le score pondéré final
- Stocker le score d'énergie par équipe/match
-
Implémenter la pondération temporelle (AC: #1)
- Configurer la fonction de décroissance temporelle
- Tweets récents (1h) = poids 1.0
- Tweets anciens (24h) = poids 0.5
- Appliquer la pondération temporelle au calcul
- Ajuster le score final
-
Créer les schémas de base de données pour scores d'énergie (AC: #1)
- Créer la table
energy_scoresdans SQLite - Définir les colonnes: id, match_id, team_id, score, confidence, sources_used
- Ajouter les colonnes pour les pondérations et métriques
- Créer les indexes appropriés
- Générer et appliquer les migrations
- Créer la table
-
Implémenter le mode dégradé (AC: #2)
- Détecter les sources indisponibles
- Ajuster les pondérations proportionnellement
- Réduire le niveau de confiance
- Logger les sources utilisées/absentes
- Tester le mode dégradé
-
Tester le calcul d'énergie (AC: #1, #2)
- Tester le calcul avec toutes les sources
- Tester le mode dégradé (source indisponible)
- Vérifier la pondération temporelle
- Valider que le score est entre 0 et 100
- 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:
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.pybackend/app/models/energy_score.pybackend/app/schemas/energy_score.pybackend/app/services/energy_service.pybackend/alembic/versions/20260117_0004_create_energy_scores_table.pybackend/tests/test_energy_calculator.pybackend/tests/test_energy_service.pybackend/tests/conftest.pybackend/tests/test_energy_manual.pybackend/app/models/__init__.py(modifié)backend/app/schemas/__init__.py(modifié)