190 lines
6.9 KiB
Markdown
190 lines
6.9 KiB
Markdown
# 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é)
|