# 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": "<uuid>",
        "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| <event_uuid> | 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
