# language: fr
Fonctionnalité: Formule de scoring et recommandation
  En tant que système de recommandation
  Je veux calculer un score combiné pour chaque contenu
  Afin de proposer les contenus les plus pertinents à l'utilisateur

  Contexte:
    Étant donné que l'API RoadWave est disponible

  Scénario: Calcul du score géographique linéaire
    Étant donné qu'un contenu existe à Paris
    Et que la distance_max_km est configurée à 200 km
    Quand un utilisateur est à 50 km du contenu
    Alors le score_geo = 1 - (50 / 200) = 0.75

  Scénario: Score géo à distance nulle (sur place)
    Étant donné qu'un contenu existe à un point GPS précis
    Quand un utilisateur est exactement au même point (0 km)
    Alors le score_geo = 1.0 (maximum)

  Scénario: Score géo à distance_max (200 km)
    Étant donné qu'un contenu existe à Paris
    Quand un utilisateur est à 200 km du contenu
    Alors le score_geo = 1 - (200 / 200) = 0.0

  Scénario: Score géo au-delà de distance_max
    Étant donné qu'un contenu existe à Paris
    Quand un utilisateur est à 250 km du contenu (au-delà de 200 km max)
    Alors le score_geo = 0.0 (minimum)
    Et le contenu a peu de chances d'être recommandé sauf engagement très élevé

  Scénario: Calcul du score d'intérêts avec jauges utilisateur
    Étant donné qu'un utilisateur a les jauges suivantes:
      | categorie    | niveau |
      | Automobile   | 80%    |
      | Voyage       | 60%    |
      | Musique      | 40%    |
    Et qu'un contenu est tagué "Automobile" et "Voyage"
    Quand l'algorithme calcule le score_interets
    Alors score_interets = (0.8 + 0.6) / 2 = 0.7

  Scénario: Score d'intérêts avec un seul tag
    Étant donné qu'un utilisateur a la jauge "Économie" à 90%
    Et qu'un contenu est tagué uniquement "Économie"
    Quand l'algorithme calcule le score_interets
    Alors score_interets = 0.9

  Scénario: Score d'intérêts avec tags non matchés
    Étant donné qu'un utilisateur a des jauges "Sport" et "Politique" élevées
    Et qu'un contenu est tagué "Musique" et "Philosophie"
    Et que l'utilisateur n'a pas ces catégories
    Quand l'algorithme calcule le score_interets
    Alors score_interets = 0.5 (neutre par défaut pour catégories inconnues)

  Scénario: Calcul du score d'engagement avec métriques
    Étant donné qu'un contenu a:
      | metrique           | valeur |
      | ecoutes            | 1000   |
      | ecoutes_completes  | 700    |
      | likes              | 300    |
      | abonnements_apres  | 50     |
    Quand l'algorithme calcule le score_engagement
    Alors taux_completion = 700 / 1000 = 0.7
    Et ratio_likes = 300 / 1000 = 0.3
    Et ratio_abonnements = 50 / 1000 = 0.05
    Et score_engagement = (0.7 × 0.5) + (0.3 × 0.3) + (0.05 × 0.2) = 0.35 + 0.09 + 0.01 = 0.45

  Scénario: Contenu avec moins de 50 écoutes - score neutre
    Étant donné qu'un contenu a seulement 30 écoutes
    Quand l'algorithme calcule le score_engagement
    Alors score_engagement = 0.5 (neutre par défaut)
    Et le contenu n'est pas pénalisé pour manque de données

  Scénario: Contenu avec exactement 50 écoutes - calcul réel
    Étant donné qu'un contenu a exactement 50 écoutes
    Et des métriques d'engagement complètes
    Quand l'algorithme calcule le score_engagement
    Alors le score est calculé normalement (pas de seuil neutre)

  Scénario: Bonus aléatoire - 10% des recommandations
    Étant donné qu'un utilisateur demande 10 recommandations
    Et que la part_aleatoire_global est à 10%
    Quand l'algorithme génère les recommandations
    Alors 1 contenu sur 10 est tiré aléatoirement
    Et 9 contenus sont calculés avec le score combiné
    Et le contenu aléatoire n'est pas dans l'historique déjà écouté

  Scénario: Curseur utilisateur découverte à 0% - aucun aléatoire
    Étant donné qu'un utilisateur configure le curseur découverte à 0%
    Quand l'utilisateur demande 20 recommandations
    Alors les 20 contenus sont calculés avec le score combiné
    Et aucun contenu aléatoire n'est proposé

  Scénario: Curseur utilisateur découverte à 50% - découverte max
    Étant donné qu'un utilisateur configure le curseur découverte à 50%
    Quand l'utilisateur demande 20 recommandations
    Alors 10 contenus sont tirés aléatoirement
    Et 10 contenus sont calculés avec le score combiné

  Scénario: Score final combiné pour contenu géo-ancré
    Étant donné qu'un contenu "Géo-ancré" a:
      | parametre         | valeur |
      | score_geo         | 0.9    |
      | score_interets    | 0.6    |
      | score_engagement  | 0.45   |
      | poids_geo         | 0.7    |
      | poids_interets    | 0.1    |
      | poids_engagement  | 0.2    |
    Quand l'algorithme calcule le score_final
    Alors score_final = (0.9 × 0.7) + (0.6 × 0.1) + (0.45 × 0.2)
    Et score_final = 0.63 + 0.06 + 0.09 = 0.78

  Scénario: Score final combiné pour contenu géo-neutre
    Étant donné qu'un contenu "Géo-neutre" a:
      | parametre         | valeur |
      | score_geo         | 0.3    |
      | score_interets    | 0.9    |
      | score_engagement  | 0.6    |
      | poids_geo         | 0.2    |
      | poids_interets    | 0.6    |
      | poids_engagement  | 0.2    |
    Quand l'algorithme calcule le score_final
    Alors score_final = (0.3 × 0.2) + (0.9 × 0.6) + (0.6 × 0.2)
    Et score_final = 0.06 + 0.54 + 0.12 = 0.72
    Et le contenu peut être recommandé malgré la distance

  Scénario: Contenu viral lointain peut être recommandé
    Étant donné qu'un contenu viral existe à Paris
    Et qu'il a un score_engagement très élevé de 0.95
    Et qu'un utilisateur est à Marseille (score_geo = 0.1)
    Quand l'algorithme calcule le score_final
    Alors le score_engagement élevé compense le score_geo faible
    Et le contenu peut apparaître dans les recommandations

  Scénario: Ordre de recommandation par score décroissant
    Étant donné 5 contenus avec les scores suivants:
      | contenu    | score_final |
      | Contenu A  | 0.85        |
      | Contenu B  | 0.72        |
      | Contenu C  | 0.90        |
      | Contenu D  | 0.65        |
      | Contenu E  | 0.78        |
    Quand l'utilisateur demande des recommandations
    Alors l'ordre de proposition est:
      | position | contenu    |
      | 1        | Contenu C  |
      | 2        | Contenu A  |
      | 3        | Contenu E  |
      | 4        | Contenu B  |
      | 5        | Contenu D  |

  Scénario: Exclusion de l'historique déjà écouté >80%
    Étant donné qu'un utilisateur a écouté les contenus suivants:
      | contenu    | completion |
      | Contenu A  | 85%        |
      | Contenu B  | 95%        |
      | Contenu C  | 30%        |
    Quand l'algorithme génère les recommandations
    Alors "Contenu A" et "Contenu B" ne sont jamais proposés
    Mais "Contenu C" peut être reproposé

  Scénario: Pré-calcul de 5 contenus suivants
    Étant donné qu'un utilisateur écoute un contenu
    Quand l'algorithme prépare les contenus suivants
    Alors 5 contenus sont pré-calculés selon le score
    Et ces contenus sont mis en cache pour performance

  Scénario: Recalcul si déplacement >10 km
    Étant donné que 5 contenus suivants sont pré-calculés
    Et que l'utilisateur se déplace de 12 km
    Quand l'utilisateur demande le contenu suivant
    Alors l'algorithme recalcule les scores avec la nouvelle position
    Et propose de nouveaux contenus plus pertinents géographiquement

  Scénario: Recalcul après 10 minutes d'inactivité
    Étant donné que 5 contenus suivants sont pré-calculés
    Et que 11 minutes se sont écoulées sans action
    Quand l'utilisateur demande le contenu suivant
    Alors l'algorithme recalcule les scores
    Et prend en compte les nouveaux contenus publiés

  # Règle: Score géo excellent + intérêts nuls = recommandation possible (MVP)
  Scénario: Contenu géo-ancré proche avec intérêts nuls reste recommandable
    Étant donné qu'un contenu géo-ancré "Info trafic local" est à 100m de l'utilisateur
    Et que le contenu est tagué "Actualités" et "Trafic"
    Et que l'utilisateur a des jauges à 0% pour ces tags (aucun intérêt marqué)
    Et que le score_geo = 1.0 (distance 100m, excellent)
    Et que le score_interets = 0.0 (jauges nulles)
    Et que le score_engagement = 0.6 (contenu récent, peu d'historique)
    Quand l'algorithme calcule le score_final pour un contenu géo-ancré
    Alors score_final = (1.0 × 0.7) + (0.0 × 0.1) + (0.6 × 0.2)
    Et score_final = 0.7 + 0.0 + 0.12 = 0.82
    Et le contenu peut être recommandé malgré l'intérêt nul
    Et ce comportement est accepté pour MVP car:
      | justification                                           |
      | Le quota 6 contenus géolocalisés/h protège du spam     |
      | L'info peut être utile contextuellement                |
      | La distinction info/divertissement est reportée post-MVP|

  Scénario: Contenu géo-neutre loin avec intérêts élevés recommandé
    Étant donné qu'un contenu géo-neutre "Podcast philosophie" est à 150 km
    Et que le contenu est tagué "Philosophie" et "Culture"
    Et que l'utilisateur a des jauges à 90% pour ces tags
    Et que le score_geo = 0.25 (150 km de distance)
    Et que le score_interets = 0.9 (jauges élevées)
    Et que le score_engagement = 0.7
    Quand l'algorithme calcule le score_final pour un contenu géo-neutre
    Alors score_final = (0.25 × 0.2) + (0.9 × 0.6) + (0.7 × 0.2)
    Et score_final = 0.05 + 0.54 + 0.14 = 0.73
    Et le contenu est bien recommandé grâce aux intérêts élevés

  Scénario: Comparaison scores - géo proche vs intérêts élevés
    Étant donné deux contenus:
      | contenu              | type         | distance | score_geo | tags          | jauges_user | score_interets | score_engagement |
      | Info trafic locale   | Géo-ancré    | 100m     | 1.0       | Trafic        | 0%          | 0.0            | 0.6              |
      | Podcast philosophie  | Géo-neutre   | 150 km   | 0.25      | Philosophie   | 90%         | 0.9            | 0.7              |
    Quand l'algorithme calcule les scores finaux
    Alors score_final("Info trafic locale") = 0.82
    Et score_final("Podcast philosophie") = 0.73
    Et "Info trafic locale" sera proposé avant "Podcast philosophie"
    Et les deux contenus sont recommandables selon leurs critères différents
