# 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é)
