# Story 2.1: Implémenter le scraper Twitter avec rate limiting

Status: review

## Story

As a développeur,
I want implémenter un scraper Twitter avec gestion des rate limits,
So que je peux collecter des tweets sur les matchs de football sans dépasser les limites API.

## Acceptance Criteria

**Given** une clé API Twitter est configurée
**When** le scraper Twitter est exécuté
**Then** il collecte des tweets pour un match donné avec mots-clés pertinents
**And** il respecte la limite de 1000 requêtes/heure
**And** il gère les erreurs de rate limit avec retry avec backoff exponentiel
**And** les tweets collectés sont stockés avec timestamp, texte, engagement (retweets, likes)

**Given** le scraper détecte un rate limit
**When** la limite est atteinte (>90% utilisation)
**Then** une alerte est loggée
**And** le scraper passe en mode priorisation (matchs VIP uniquement)
**And** les données collectées sont sauvegardées avant arrêt

## Tasks / Subtasks

- [x] Installer les dépendances Twitter API (AC: #1)
  - [x] Installer `tweepy` ou `tweepy-async` pour Twitter API
  - [x] Configurer les credentials Twitter API
  - [x] Créer le module scraper dans `backend/app/scrapers/`
  - [x] Configurer la connexion Twitter API
  - [x] Vérifier l'authentification Twitter

- [x] Implémenter le collecteur de tweets (AC: #1)
  - [x] Créer la fonction de recherche de tweets par mots-clés
  - [x] Extraire les données: texte, timestamp, retweets, likes
  - [x] Implémenter le parsing des données Twitter
  - [x] Stocker les tweets dans la base de données
  - [x] Gérer les erreurs de connexion/timeout

- [x] Implémenter le rate limiting (AC: #1, #2)
  - [x] Configurer le rate limiter pour 1000 req/heure
  - [x] Implémenter le suivi de l'utilisation API
  - [x] Implémenter l'alerte quand utilisation > 90%
  - [x] Implémenter le retry avec backoff exponentiel
  - [x] Configurer le mode priorisation (matchs VIP)

- [x] Implémenter le mode dégradé (AC: #2)
  - [x] Définir les matchs VIP dans la configuration
  - [x] Implémenter la priorisation dynamique des matchs
  - [x] Sauvegarder les données collectées avant arrêt
  - [x] Logger l'alerte de mode dégradé
  - [x] Vérifier que le scraper continue avec autres sources

- [x] Créer les schémas de base de données pour tweets (AC: #1)
  - [x] Créer la table `tweets` dans SQLite
  - [x] Définir les colonnes: id, text, created_at, retweet_count, like_count
  - [x] Ajouter les colonnes pour match_id et source
  - [x] Créer les indexes appropriés
  - [x] Générer et appliquer les migrations

- [x] Tester le scraper Twitter (AC: #1, #2)
  - [x] Tester la collecte de tweets pour un match
  - [x] Vérifier que le rate limiting fonctionne
  - [x] Tester le mode priorisation VIP
  - [x] Vérifier que les données sont stockées correctement
  - [x] Tester le retry avec backoff exponentiel

## Dev Notes

### Architecture Patterns et Contraintes

**Stack Technique Imposé:**
- **Twitter API:** Tweepy ou tweepy-async
- **Rate Limiting:** 1000 requêtes/heure (gratuit)
- **Database:** SQLite avec Drizzle (Next.js) et SQLAlchemy (FastAPI)
- **Queue:** RabbitMQ pour traitement asynchrone (Phase 2+)
- **Logging:** Structuré avec alertes pour rate limits

**Configuration Requise:**
- Credentials Twitter API: Bearer token ou OAuth 1.0a
- Rate limiter: 1000 req/heure avec alerte à 90%
- Mode dégradé: Priorisation des matchs VIP
- Stockage: Table `tweets` dans SQLite

**Intégration avec Architecture Globale:**
- Part de la pondération: Twitter 60% (Reddit 25%, RSS 15%)
- Queue RabbitMQ pour découplage scraping et analyse
- Même structure de données que Reddit et RSS
- Format cohérent pour fusion multi-sources

**Conventions de Nommage:**
- Table: `tweets` (snake_case pluriel)
- Colonnes: `tweet_id`, `text`, `created_at`, `match_id` (snake_case)
- Functions: `scrape_twitter_match()`, `handle_rate_limit()` (snake_case)
- Variables: `max_tweets_per_hour`, `is_vip_match` (snake_case/UPPER_SNAKE_CASE)

### Source Tree Components à Toucher

**Fichiers à créer:**
1. `backend/app/scrapers/` (répertoire scrapers)
2. `backend/app/scrapers/__init__.py`
3. `backend/app/scrapers/twitter_scraper.py` (module Twitter)
4. `backend/app/models/tweet.py` (modèle SQLAlchemy pour tweets)
5. `backend/app/schemas/tweet.py` (schéma Pydantic pour tweets)

**Fichiers à modifier:**
1. `backend/requirements.txt` (ajouter tweepy)
2. `src/db/schema.ts` (ajouter schéma Drizzle pour tweets - Next.js)

### Project Structure Notes

**Alignment with unified project structure:**
- ✅ Twitter scraper dans `backend/app/scrapers/` comme spécifié
- ✅ Rate limiting à 1000 req/heure comme spécifié
- ✅ Pondération Twitter 60% comme spécifié dans epics
- ✅ Mode dégradé avec priorisation VIP comme spécifié

**Conventions de code à respecter:**
- Gestion asynchrone avec async/await (si tweepy-async)
- Logging structuré pour monitoring
- Retry avec backoff exponentiel
- Mode dégradé avec priorisation

**Intégration avec architecture existante:**
- SQLite partagé avec Next.js et FastAPI
- Même convention de nommage `snake_case`
- Préparation pour intégration RabbitMQ (Phase 2+)

### Technical Requirements

**Configuration Twitter API:**
```python
import tweepy

# Configuration Twitter API
auth = tweepy.BearerToken("YOUR_BEARER_TOKEN")
client = tweepy.Client(bearer_token=auth)

# Rate limiting configuration
MAX_TWEETS_PER_HOUR = 1000
RATE_LIMIT_ALERT_THRESHOLD = 0.9  # 90%
VIP_MATCH_IDS = [1, 2, 3]  # IDs des matchs VIP
```

**Scraper Twitter:**
```python
async def scrape_twitter_match(match_id: str, keywords: List[str]):
    # Collecte des tweets pour un match
    tweets = client.search_recent_tweets(
        query=f"{' OR '.join(keywords)}",
        max_results=100,
        tweet_fields=['created_at', 'public_metrics', 'text']
    )
    
    # Stockage dans la base de données
    for tweet in tweets.data:
        save_tweet_to_db(tweet, match_id)
```

**Rate Limiting:**
```python
from time import sleep
from math import exp

async def handle_rate_limit(api_calls_remaining: int):
    if api_calls_remaining < (MAX_TWEETS_PER_HOUR * (1 - RATE_LIMIT_ALERT_THRESHOLD)):
        log_alert("Rate limit approaching 90%")
        
    if api_calls_remaining == 0:
        # Mode dégradé: priorisation VIP
        enable_vip_mode_only()
        wait_time = 3600  # Attendre 1 heure
        sleep(wait_time)
```

**Schéma Table Tweets:**
```python
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey
from sqlalchemy.orm import relationship

class Tweet(Base):
    __tablename__ = "tweets"
    
    id = Column(Integer, primary_key=True)
    tweet_id = Column(String, unique=True, index=True)
    text = Column(String, nullable=False)
    created_at = Column(DateTime, nullable=False, index=True)
    retweet_count = Column(Integer, default=0)
    like_count = Column(Integer, default=0)
    match_id = Column(Integer, ForeignKey("matches.id"))
    source = Column(String, default="twitter")  # twitter, reddit, rss
```

### Architecture Compliance

**Conformité avec Architecture Decision Document:**

✅ **External API Management:**
- Rate limiting à 1000 req/heure
- Alertes prédictives (>90% utilisation)
- Mode dégradé avec priorisation VIP

✅ **Data Architecture:**
- Table `tweets` dans SQLite
- Conventions `snake_case`
- Indexes optimisés

✅ **Code Organization:**
- Module dans `backend/app/scrapers/`
- Séparation clear des responsabilités

### Library/Framework Requirements

**Packages:**
- `tweepy` ou `tweepy-async`

### File Structure Requirements

```
backend/
├── app/
│   ├── scrapers/
│   │   ├── __init__.py
│   │   └── twitter_scraper.py
│   ├── models/
│   │   └── tweet.py
│   └── schemas/
│       └── tweet.py
```

### Testing Requirements

- Tests de collecte de tweets
- Tests de rate limiting
- Tests de mode dégradé
- Tests de stockage en base de données

### References

- [Source: _bmad-output/planning-artifacts/epics.md#Story-2.1]

## Dev Agent Record

### Agent Model Used

GLM-4.7

### Completion Notes List

- ✅ **Dependencies Twitter API**: Tweepy 4.14.0 ajouté à requirements.txt
- ✅ **Twitter Scraper Module**: Module complet avec rate limiting et mode dégradé
  - Classe `TwitterScraper` avec authentification Twitter API
  - Tracking en temps réel des appels API (max 1000/heure)
  - Alertes prédictives à 90% d'utilisation
  - Mode dégradé automatique pour matchs VIP uniquement
  - Retry avec backoff exponentiel
  - Logging structuré pour monitoring
- ✅ **Database Schema**: Table `tweets` créée dans SQLite
  - Colonnes: id, tweet_id, text, created_at, retweet_count, like_count, match_id, source
  - Indexes: tweet_id (unique), created_at, match_id, composite (match_id, source)
  - Migration Alembic: 20260117_0001_create_tweets_table.py
- ✅ **Pydantic Schemas**: Schémas de validation pour tweets
  - TweetBase, TweetCreate, TweetResponse
  - TweetListResponse avec pagination
  - TweetStatsResponse pour statistiques
- ✅ **Drizzle Schema**: Schéma pour Next.js (src/db/schema.ts)
  - Table `tweets` avec conventions TypeScript
  - Mapping snake_case ↔ camelCase
- ✅ **Tests Unitaires**: Tests complets pour scraper et modèle
  - test_twitter_scraper.py: 10+ tests pour rate limiting, mode VIP, scraping
  - test_tweet_model.py: 8+ tests pour modèle SQLAlchemy
- ✅ **Documentation**: README complet dans backend/app/scrapers/
  - Guide d'installation et configuration
  - Exemples d'utilisation
  - Documentation API et dépannage

### Technical Decisions

**Pourquoi Tweepy vs tweepy-async:**
- Choisie `tweepy` synchrone pour simplicité initiale
- Peut migrer vers `tweepy-async` dans Phase 2+ si performance nécessaire
- Rate limiting natif de Tweepy suffisant pour Phase 1

**Rate Limiting Strategy:**
- Tracking côté client (self.api_calls_made)
- Alertes prédictives à 90% pour éviter blocages
- Mode dégradé automatique: matchs VIP seulement
- Backoff exponentiel: minimum 1 minute, maximum 1 heure

**Database Schema Design:**
- Convention `snake_case` cohérente avec SQLAlchemy
- Indexes optimisés pour requêtes fréquentes:
  - `tweet_id` unique pour éviter doublons
  - `created_at` pour tri temporel
  - `match_id` et `match_id+source` pour filtrage
- `source` colonne pour multi-source future (Reddit, RSS)

**Mode Dégradé Implementation:**
- VIP match IDs configurables dans scraper
- Activation automatique quand rate limit atteint
- Alertes loggées avec niveau WARNING
- Sauvegarde automatique avant arrêt

### Integration Points

**Backend Integration:**
- Module accessible via `app.scrapers.twitter_scraper`
- Factory function: `create_twitter_scraper()`
- Compatible avec SQLAlchemy sessions via `save_tweets_to_db()`

**Frontend Integration (Next.js):**
- Schéma Drizzle dans `src/db/schema.ts`
- Partage de la même base SQLite
- Conventions TypeScript (camelCase) automatiques

**Future Integration (Phase 2+):**
- RabbitMQ queue pour découplage scraping/analyse
- Workers asynchrones pour multi-source
- Dashboard monitoring temps réel

### File List

**Fichiers créés:**
- `backend/app/scrapers/__init__.py`
- `backend/app/scrapers/twitter_scraper.py`
- `backend/app/scrapers/README.md`
- `backend/app/models/tweet.py`
- `backend/app/schemas/tweet.py`
- `backend/alembic/versions/20260117_0001_create_tweets_table.py`
- `backend/tests/__init__.py`
- `backend/tests/test_twitter_scraper.py`
- `backend/tests/test_tweet_model.py`
- `backend/tests/run_tests.py`

**Fichiers modifiés:**
- `backend/requirements.txt` (ajouté tweepy==4.14.0)
- `backend/app/models/__init__.py` (ajouté Tweet import)
- `backend/app/schemas/__init__.py` (ajouté Tweet schemas)
- `chartbastan/src/db/schema.ts` (ajouté tweets table)
