# language: fr Fonctionnalité: API - Évolution des jauges d'intérêt En tant qu'API backend Je veux calculer et persister les évolutions de jauges d'intérêt Afin d'alimenter l'algorithme de recommandation Contexte: Étant donné que l'API RoadWave est disponible Et que la base de données PostgreSQL est accessible Et qu'un utilisateur "user123" existe avec token JWT valide Scénario: API calcule like automatique renforcé (≥80% écoute) Étant donné que l'utilisateur "user123" a une jauge "Automobile" à 45% en base Et qu'un contenu "content456" de 5 minutes est tagué "Automobile" Quand je POST /api/v1/listening-events """json { "user_id": "user123", "content_id": "content456", "listened_duration_seconds": 270, "total_duration_seconds": 300, "completion_percentage": 90 } """ Alors le statut de réponse est 201 Et la réponse contient: """json { "like_type": "automatic_reinforced", "gauge_updates": [ { "category": "Automobile", "previous_value": 45, "delta": 2, "new_value": 47 } ] } """ Et en base de données, la jauge "Automobile" de "user123" est à 47% Scénario: API calcule like automatique standard (30-79% écoute) Étant donné que l'utilisateur "user123" a une jauge "Voyage" à 60% en base Et qu'un contenu "content789" de 10 minutes est tagué "Voyage" Quand je POST /api/v1/listening-events """json { "user_id": "user123", "content_id": "content789", "listened_duration_seconds": 300, "total_duration_seconds": 600, "completion_percentage": 50 } """ Alors le statut de réponse est 201 Et la réponse contient: """json { "like_type": "automatic_standard", "gauge_updates": [ { "category": "Voyage", "previous_value": 60, "delta": 1, "new_value": 61 } ] } """ Et en base de données, la jauge "Voyage" de "user123" est à 61% Scénario: API applique like manuel explicite (+2%) Étant donné que l'utilisateur "user123" a une jauge "Musique" à 55% en base Et qu'un contenu "content999" est tagué "Musique" Quand je POST /api/v1/likes """json { "user_id": "user123", "content_id": "content999", "like_type": "manual" } """ Alors le statut de réponse est 201 Et la réponse contient: """json { "like_id": "", "gauge_updates": [ { "category": "Musique", "previous_value": 55, "delta": 2, "new_value": 57 } ] } """ Et en base de données, la jauge "Musique" de "user123" est à 57% Scénario: API applique unlike (retire like manuel) Étant donné que l'utilisateur "user123" a une jauge "Sport" à 57% en base Et qu'il a liké manuellement le contenu "content888" tagué "Sport" Et que le like a l'ID "like_abc123" Quand je DELETE /api/v1/likes/like_abc123 Alors le statut de réponse est 204 Et en base de données, la jauge "Sport" de "user123" est à 55% Et le like "like_abc123" est supprimé de la table likes Scénario: API refuse unlike d'un like automatique Étant donné que l'utilisateur "user123" a écouté un contenu à 90% Et qu'il a reçu un like automatique "like_auto456" Quand je DELETE /api/v1/likes/like_auto456 Alors le statut de réponse est 403 Et la réponse contient: """json { "error": "CANNOT_UNLIKE_AUTOMATIC", "message": "Les likes automatiques ne peuvent pas être retirés" } """ Et en base de données, le like "like_auto456" existe toujours Scénario: API cumule like automatique + like manuel Étant donné que l'utilisateur "user123" a une jauge "Technologie" à 50% en base Et qu'un contenu "content777" de 10 minutes est tagué "Technologie" Quand je POST /api/v1/listening-events """json { "user_id": "user123", "content_id": "content777", "completion_percentage": 50 } """ Alors la jauge "Technologie" passe à 51% (+1% auto) Quand je POST ensuite /api/v1/likes """json { "user_id": "user123", "content_id": "content777", "like_type": "manual" } """ Alors la jauge "Technologie" passe à 53% (+2% manuel) Et le delta total est de 3% Scénario: API applique bonus abonnement créateur (+5% tous tags) Étant donné que l'utilisateur "user123" a les jauges suivantes: | catégorie | niveau | | Automobile | 50% | | Technologie | 45% | Et qu'un créateur "creator456" publie des contenus tagués "Automobile" et "Technologie" Quand je POST /api/v1/subscriptions """json { "user_id": "user123", "creator_id": "creator456" } """ Alors le statut de réponse est 201 Et en base de données: | catégorie | niveau | | Automobile | 55% | | Technologie | 50% | Et l'abonnement est créé avec bonus appliqué Scénario: API retire bonus désabonnement créateur (-5% tous tags) Étant donné que l'utilisateur "user123" a les jauges suivantes: | catégorie | niveau | | Voyage | 65% | | Culture | 58% | Et qu'il est abonné au créateur "creator789" qui publie "Voyage" et "Culture" Quand je DELETE /api/v1/subscriptions/creator789 Alors le statut de réponse est 204 Et en base de données: | catégorie | niveau | | Voyage | 60% | | Culture | 53% | Scénario: API applique pénalité skip rapide (<10s) Étant donné que l'utilisateur "user123" a une jauge "Politique" à 50% en base Et qu'un contenu "content555" de 300 secondes est tagué "Politique" Quand je POST /api/v1/skip-events """json { "user_id": "user123", "content_id": "content555", "listened_duration_seconds": 5, "total_duration_seconds": 300 } """ Alors le statut de réponse est 201 Et la réponse contient: """json { "skip_type": "early", "gauge_updates": [ { "category": "Politique", "previous_value": 50, "delta": -0.5, "new_value": 49.5 } ] } """ Et en base de données, la jauge "Politique" de "user123" est à 49.5% Scénario: API n'applique pas de pénalité pour skip ≥10s et <30% Étant donné que l'utilisateur "user123" a une jauge "Économie" à 60% en base Et qu'un contenu "content333" de 600 secondes est tagué "Économie" Quand je POST /api/v1/skip-events """json { "user_id": "user123", "content_id": "content333", "listened_duration_seconds": 120, "total_duration_seconds": 600, "completion_percentage": 20 } """ Alors le statut de réponse est 201 Et la réponse contient: """json { "skip_type": "neutral", "gauge_updates": [] } """ Et en base de données, la jauge "Économie" de "user123" reste à 60% Scénario: API n'applique pas de pénalité pour skip ≥30% Étant donné que l'utilisateur "user123" a une jauge "Sport" à 55% en base Et qu'un contenu "content222" de 600 secondes est tagué "Sport" Quand je POST /api/v1/skip-events """json { "user_id": "user123", "content_id": "content222", "listened_duration_seconds": 300, "total_duration_seconds": 600, "completion_percentage": 50 } """ Alors le statut de réponse est 201 Et la réponse contient: """json { "skip_type": "late", "gauge_updates": [] } """ Et en base de données, la jauge "Sport" de "user123" reste à 55% Scénario: API applique évolution sur plusieurs tags simultanément Étant donné que l'utilisateur "user123" a les jauges suivantes: | catégorie | niveau | | Automobile | 45% | | Voyage | 60% | Et qu'un contenu "content111" est tagué "Automobile" et "Voyage" Quand je POST /api/v1/listening-events """json { "user_id": "user123", "content_id": "content111", "completion_percentage": 90 } """ Alors le statut de réponse est 201 Et la réponse contient: """json { "like_type": "automatic_reinforced", "gauge_updates": [ { "category": "Automobile", "previous_value": 45, "delta": 2, "new_value": 47 }, { "category": "Voyage", "previous_value": 60, "delta": 2, "new_value": 62 } ] } """ Et en base de données: | catégorie | niveau | | Automobile | 47% | | Voyage | 62% | Scénario: API respecte la borne maximum 100% Étant donné que l'utilisateur "user123" a une jauge "Cryptomonnaie" à 99% en base Et qu'un contenu "content_crypto" est tagué "Cryptomonnaie" Quand je POST /api/v1/listening-events """json { "user_id": "user123", "content_id": "content_crypto", "completion_percentage": 95 } """ Alors le statut de réponse est 201 Et la réponse contient: """json { "like_type": "automatic_reinforced", "gauge_updates": [ { "category": "Cryptomonnaie", "previous_value": 99, "delta": 2, "new_value": 100, "capped": true } ] } """ Et en base de données, la jauge "Cryptomonnaie" de "user123" est à 100% Et la jauge n'a pas dépassé 100% Scénario: API respecte la borne minimum 0% Étant donné que l'utilisateur "user123" a une jauge "Politique" à 0.3% en base Et qu'un contenu "content_pol" est tagué "Politique" Quand je POST /api/v1/skip-events """json { "user_id": "user123", "content_id": "content_pol", "listened_duration_seconds": 3, "total_duration_seconds": 300 } """ Alors le statut de réponse est 201 Et la réponse contient: """json { "skip_type": "early", "gauge_updates": [ { "category": "Politique", "previous_value": 0.3, "delta": -0.5, "new_value": 0, "capped": true } ] } """ Et en base de données, la jauge "Politique" de "user123" est à 0% Et la jauge n'est pas devenue négative Scénario: API respecte borne minimum lors désabonnement Étant donné que l'utilisateur "user123" a une jauge "Économie" à 3% en base Et qu'il est abonné au créateur "creator_eco" qui publie "Économie" Quand je DELETE /api/v1/subscriptions/creator_eco Alors le statut de réponse est 204 Et en base de données, la jauge "Économie" de "user123" est à 0% (et non -2%) Scénario: API GET retourne toutes les jauges utilisateur Étant donné que l'utilisateur "user123" a les jauges suivantes en base: | catégorie | niveau | | Automobile | 67% | | Voyage | 82% | | Économie | 34% | | Sport | 50% | | Musique | 45% | | Technologie | 71% | Quand je GET /api/v1/users/user123/interest-gauges Alors le statut de réponse est 200 Et la réponse contient les 12 catégories avec leurs niveaux: """json { "user_id": "user123", "gauges": [ {"category": "Automobile", "level": 67}, {"category": "Voyage", "level": 82}, {"category": "Économie", "level": 34}, {"category": "Sport", "level": 50}, {"category": "Musique", "level": 45}, {"category": "Technologie", "level": 71} ] } """ Scénario: API calcule évolution immédiate (pas de batch différé) Étant donné que l'utilisateur "user123" a une jauge "Voyage" à 50% en base Quand je POST /api/v1/listening-events à 12:00:00 """json { "user_id": "user123", "content_id": "content_travel", "completion_percentage": 85 } """ Alors le statut de réponse est 201 Quand je GET /api/v1/users/user123/interest-gauges à 12:00:01 (1 seconde après) Alors la jauge "Voyage" est à 52% Et la mise à jour est visible immédiatement Scénario: API rejette token JWT invalide Quand je POST /api/v1/listening-events sans token JWT Alors le statut de réponse est 401 Et la réponse contient: """json { "error": "UNAUTHORIZED", "message": "Token JWT manquant ou invalide" } """ Scénario: API valide format des données d'entrée Quand je POST /api/v1/listening-events """json { "user_id": "user123", "content_id": "content456", "completion_percentage": 150 } """ Alors le statut de réponse est 400 Et la réponse contient: """json { "error": "VALIDATION_ERROR", "message": "completion_percentage doit être entre 0 et 100" } """ Scénario: API gère contenu avec tags inexistants en base Étant donné qu'un contenu "content_new" est tagué "NouvelleCategorie" (non encore en base) Quand je POST /api/v1/listening-events """json { "user_id": "user123", "content_id": "content_new", "completion_percentage": 90 } """ Alors le statut de réponse est 201 Et une nouvelle ligne est créée dans la table interest_gauges: | user_id | category | level | | user123 | NouvelleCategorie | 52 | Et l'initialisation démarre à 50% + 2% de like auto = 52% Scénario: API persiste historique des modifications de jauges Étant donné que l'utilisateur "user123" a une jauge "Sport" à 50% Quand je POST /api/v1/listening-events qui applique +2% Alors une ligne est insérée dans interest_gauge_history: | user_id | category | previous_value | delta | new_value | event_type | event_id | timestamp | | user123 | Sport | 50 | 2 | 52 | listening_event| | 2026-02-02T12:00:00 | Et cet historique permet d'auditer les évolutions Scénario: API retourne métriques d'évolution utilisateur Étant donné que l'utilisateur "user123" a un historique d'évolution en base Quand je GET /api/v1/users/user123/interest-gauges/evolution?since=7d Alors le statut de réponse est 200 Et la réponse contient: """json { "period": "7d", "evolution": [ { "category": "Automobile", "start_value": 60, "end_value": 67, "delta": 7, "events_count": 15 }, { "category": "Voyage", "start_value": 80, "end_value": 82, "delta": 2, "events_count": 3 } ] } """ # Architecture backend - Services séparés Scénario: Gauge Calculation Service calcule l'ajustement (stateless) Étant donné un événement d'écoute avec 85% de complétion Quand le Gauge Calculation Service calcule l'ajustement Alors le service retourne: """json { "adjustment_type": "automatic_reinforced", "adjustment_value": 2.0, "reason": "completion_percentage >= 80%" } """ Et le service est stateless (aucune lecture DB) Et le service est testable unitairement Scénario: Gauge Update Service applique l'ajustement (stateful) Étant donné qu'un ajustement de +2% a été calculé Et que la jauge "Automobile" de "user123" est à 45% Quand le Gauge Update Service applique l'ajustement Alors la nouvelle valeur est 47% (45 + 2) Et la borne [0, 100] est respectée via fonction clamp Et la mise à jour est persistée en Redis (immédiat) Et la mise à jour est persistée en PostgreSQL (batch async) Scénario: Pattern de calcul - Addition de points absolus Étant donné qu'une jauge est à 60% Et qu'un ajustement de +2% est calculé Quand le calcul est effectué Alors la formule est: newValue = currentValue + adjustment Et la formule est: newValue = clamp(newValue, 0.0, 100.0) Et la nouvelle valeur est 62% Scénario: Pattern de calcul - Éviter multiplication relative Étant donné qu'une jauge est à 50% Et qu'un ajustement de +2% est calculé Quand le calcul est effectué Alors la formule utilisée est: 50 + 2 = 52 Et la formule utilisée n'est PAS: 50 * (1 + 2/100) = 51 Car l'ajustement est en points absolus, pas relatifs Scénario: Multi-tags - Mise à jour de N jauges simultanément Étant donné qu'un contenu a 3 tags: ["Automobile", "Voyage", "Technologie"] Et qu'un ajustement de +2% est calculé (écoute 85%) Quand le Gauge Update Service applique Alors 3 jauges sont mises à jour: | catégorie | ajustement | | Automobile | +2% | | Voyage | +2% | | Technologie | +2% | Et toutes les mises à jour sont effectuées en une seule transaction Scénario: Persistance Redis immédiate (latence <10ms) Étant donné qu'un ajustement doit être persisté Quand le Gauge Update Service écrit en Redis Alors la latence est < 10ms Et la jauge est immédiatement disponible pour recommandations Et Redis sert de cache haute performance Scénario: Persistance PostgreSQL asynchrone (batch 5 min) Étant donné que 100 ajustements ont été appliqués en Redis Quand le batch async s'exécute toutes les 5 minutes Alors les 100 ajustements sont écrits en PostgreSQL en batch Et la cohérence finale est garantie Et l'application reste performante (pas de write sync) Scénario: Séparation responsabilités - Calculation vs Update Étant donné un événement d'écoute Quand il est traité Alors le Gauge Calculation Service calcule l'ajustement (logique métier pure) Et le Gauge Update Service applique l'ajustement (persistance) Et les deux services sont indépendants Et chaque service a une responsabilité unique (SRP) Scénario: Réutilisabilité Calculation Service Étant donné le Gauge Calculation Service Quand il est utilisé pour: | contexte | | Like automatique | | Skip rapide | | Like manuel | | Abonnement créateur | Alors le même service calcule tous les ajustements Et la logique métier est centralisée Et il n'y a pas de duplication de code