test(gherkin): ajouter tests BDD pour toutes clarifications règles métier

Ajoute/modifie tests Gherkin pour couvrir les 7 sections clarifiées :

1. Algorithme recommandation (scoring intérêts nuls) :
   - Ajout scénarios scoring-recommandation.feature
   - Cas contenu géo-ancré proche avec intérêts nuls = recommandable
   - Comparaison scores géo vs intérêts

2. Audio-guides mode voiture (système double clic) :
   - Nouveau fichier systeme-double-clic-sortie.feature
   - Premier clic : passage mode manuel + séquence suivante
   - Deuxième clic <10s : sortie audio-guide
   - Détection hors itinéraire + reprise

3. Monétisation créateurs (soldes dormants + DAS2) :
   - Nouveau fichier soldes-dormants-inactifs.feature
   - Conservation indéfinie si actif
   - Emails 12/18 mois + versement forcé 18 mois + 30j
   - Exception soldes <10€ avec proposition don
   - Modification obligations-fiscales.feature
   - DAS2 systématique tous montants (même <1200€)

4. Skip et abonnement (neutralisation pénalités) :
   - Nouveau fichier skip-abonnes-neutralisation.feature
   - Skip <10s non-abonné : -0.5%
   - Skip <10s abonné : 0% (neutre)
   - Métriques engagement : abonnés ne pénalisent pas
   - Anti-raid naturel (sources non pertinentes)

5. Premium multi-devices (KISS) :
   - Nouveau fichier multi-devices-dernier-priorite.feature
   - Règle simple : dernier device prend toujours priorité
   - Offline connecté vs déconnecté
   - Détection abus post-MVP (pas automatique)

6. Mode offline (contenus supprimés) :
   - Nouveau fichier contenus-supprimes-pendant-offline.feature
   - Suppression immédiate à reconnexion
   - Modal si contenu en cours d'écoute
   - Popup récapitulative si 2+ contenus supprimés

7. Publicités (ciblage horaire + fuseaux horaires) :
   - Nouveau fichier ciblage-horaire-fuseaux-horaires.feature
   - Ciblage horaire = heure locale utilisateur
   - France entière = Métropole + DOM
   - Détection fuseau GPS/device/IP
   - Cas d'usage restaurant Guadeloupe, assureur national

Couverture complète de toutes les règles métier clarifiées.
This commit is contained in:
jpgiannetti
2026-02-07 11:14:17 +01:00
parent e82ed63904
commit f6a5b9afce
8 changed files with 1501 additions and 0 deletions

View File

@@ -0,0 +1,204 @@
# language: fr
Fonctionnalité: Neutralisation des pénalités de skip pour abonnés
En tant que système de jauges d'intérêt
Je veux neutraliser les pénalités de skip pour les abonnés d'un créateur
Afin de reconnaître l'affinité globale malgré des skips ponctuels contextuels
Contexte:
Étant donné qu'un utilisateur existe avec les jauges suivantes:
| catégorie | niveau |
| Automobile | 45% |
| Voyage | 60% |
Et qu'un créateur "CreateurA" publie des contenus tagués "Automobile"
# Skip <10s - Utilisateur NON abonné
Scénario: Skip rapide <10s par non-abonné - Pénalité -0.5%
Étant donné que l'utilisateur n'est PAS abonné à "CreateurA"
Et qu'un contenu "Podcast Auto" de "CreateurA" est tagué "Automobile"
Et que la jauge "Automobile" est à 45%
Quand l'utilisateur skip le contenu après 5 secondes
Alors la jauge "Automobile" descend de -0.5%
Et la jauge "Automobile" passe de 45% à 44.5%
Et cela indique un désintérêt marqué pour ce contenu
Scénario: Skip rapide <10s par non-abonné - Colonne is_subscribed=false
Étant donné que l'utilisateur n'est PAS abonné à "CreateurA"
Et qu'un contenu est skippé après 8 secondes
Quand l'événement est enregistré dans user_listening_history
Alors la colonne is_subscribed = false
Et la colonne completion_rate = 0.05 (8s sur 160s)
Et la colonne source = "recommendation"
Et ce skip compte dans les métriques d'engagement du contenu
# Skip <10s - Utilisateur ABONNÉ
Scénario: Skip rapide <10s par abonné - Pénalité neutre 0%
Étant donné que l'utilisateur EST abonné à "CreateurA"
Et qu'un contenu "Podcast Auto" de "CreateurA" est tagué "Automobile"
Et que la jauge "Automobile" est à 45%
Quand l'utilisateur skip le contenu après 5 secondes
Alors la jauge "Automobile" reste à 45% (pénalité 0%)
Et aucune pénalité n'est appliquée
Et cela reflète que l'abonnement indique une affinité globale
Scénario: Skip rapide <10s par abonné - Colonne is_subscribed=true
Étant donné que l'utilisateur EST abonné à "CreateurA"
Et qu'un contenu est skippé après 7 secondes
Quand l'événement est enregistré dans user_listening_history
Alors la colonne is_subscribed = true
Et la colonne completion_rate = 0.04 (7s sur 180s)
Et la colonne source = "recommendation"
Et ce skip NE compte PAS dans les métriques d'engagement du contenu
# Calcul métriques engagement créateur
Scénario: Métriques engagement - Skip d'abonné ne pénalise pas
Étant donné qu'un contenu "Podcast A" de "CreateurA" a reçu:
| utilisateur | abonné ? | action | source | completion_rate |
| User1 | Non | Skip <10s | recommendation | 0.05 |
| User2 | Oui | Skip <10s | recommendation | 0.04 |
| User3 | Non | Écoute complète | recommendation | 0.90 |
| User4 | Oui | Skip <10s | recommendation | 0.03 |
| User5 | Non | Écoute partielle | recommendation | 0.60 |
Quand le système calcule les métriques d'engagement du contenu
Alors les écoutes pertinentes comptabilisées sont:
| utilisateur | comptabilisé ? | raison |
| User1 | Oui | Non-abonné, source pertinente |
| User2 | Non | Abonné, skip contextuel |
| User3 | Oui | Non-abonné, source pertinente |
| User4 | Non | Abonné, skip contextuel |
| User5 | Oui | Non-abonné, source pertinente |
Et le total écoutes pertinentes = 3 (User1, User3, User5)
Et le taux de complétion = 2 complètes (User3, User5 >80%) / 3 = 66.7%
# Sources d'écoute et neutralisation
Scénario: Skip d'abonné via "recommendation" - Ne compte pas
Étant donné que l'utilisateur EST abonné à "CreateurA"
Et qu'un contenu est recommandé via l'algorithme (source="recommendation")
Quand l'utilisateur skip après 5s
Alors l'écoute NE compte PAS dans "total écoutes" pour métriques
Et la pénalité jauge est neutralisée (0%)
Scénario: Skip d'abonné via "live_notification" - Ne compte pas
Étant donné que l'utilisateur EST abonné à "CreateurA"
Et que "CreateurA" publie un contenu en live
Et qu'une notification live est envoyée (source="live_notification")
Quand l'utilisateur skip après 6s
Alors l'écoute NE compte PAS dans "total écoutes" pour métriques
Et la pénalité jauge est neutralisée (0%)
Scénario: Skip d'abonné via "search" - Ne compte pas (indépendamment abonnement)
Étant donné que l'utilisateur EST abonné à "CreateurA"
Et qu'il trouve un contenu via la recherche (source="search")
Quand l'utilisateur skip après 4s
Alors l'écoute NE compte PAS dans "total écoutes" (source non pertinente)
Et la pénalité jauge est neutralisée (0%)
Et cela s'applique à tous users (abonnés ou non) pour source="search"
Scénario: Skip non-abonné via "direct_link" - Ne compte pas
Étant donné que l'utilisateur N'est PAS abonné à "CreateurA"
Et qu'il clique sur un lien direct partagé (source="direct_link")
Quand l'utilisateur skip après 3s
Alors l'écoute NE compte PAS dans "total écoutes" (source non pertinente)
Mais la pénalité jauge s'applique quand même (-0.5%) pour non-abonné
# Reproposition
Scénario: Skip <10s non-abonné - Pas de reproposition
Étant donné que l'utilisateur N'est PAS abonné à "CreateurA"
Et qu'un contenu est skippé après 8 secondes
Quand l'algorithme calcule les prochaines recommandations
Alors ce contenu n'est jamais reproposé à cet utilisateur
Car c'est un signal négatif clair de désintérêt
Scénario: Skip <10s abonné - Reproposition possible
Étant donné que l'utilisateur EST abonné à "CreateurA"
Et qu'un contenu est skippé après 7 secondes
Quand l'algorithme calcule les prochaines recommandations plusieurs jours plus tard
Alors ce contenu PEUT être reproposé à cet utilisateur
Car l'abonnement indique une affinité globale
Et le skip peut être contextuel ("pas maintenant", "pas ce sujet")
Scénario: Stockage is_subscribed dans user_content_history
Étant donné qu'un utilisateur EST abonné à "CreateurA" au moment de l'écoute
Quand un contenu de "CreateurA" est écouté/skippé
Alors la table user_content_history enregistre:
| colonne | valeur |
| user_id | 123 |
| content_id | 456 |
| creator_id | 789 (CreateurA) |
| is_subscribed | true |
| completion_rate| 0.05 |
| source | "recommendation" |
| listened_at | 2026-02-07 10:30:00 |
# Cohérence UX
Scénario: Abonnement = Signal affinité fort malgré skip ponctuel
Étant donné que l'utilisateur est abonné à "CreateurA" depuis 6 mois
Et qu'il a écouté 50 contenus de "CreateurA" avec 90% de complétion moyenne
Quand il skip 1 contenu après 5 secondes aujourd'hui
Alors ce skip ponctuel ne pénalise pas:
| aspect | impact |
| Jauges d'intérêt user | 0% (neutre) |
| Métriques engagement créateur | Ne compte pas dans total écoutes |
| Reproposition future | Contenu peut être reproposé |
Et cela reflète que le skip est contextuel, pas un rejet du créateur
# Anti-raid naturel
Scénario: Raid malveillant via liens directs - Inefficace
Étant donné qu'un groupe malveillant veut nuire à "CreateurA"
Et qu'ils partagent des liens directs pour inciter au skip massif
Quand 1000 personnes cliquent sur le lien et skip après 2s
Alors ces 1000 skips NE comptent PAS dans les métriques engagement
Car source="direct_link" n'est pas une source pertinente
Et "CreateurA" est protégé contre ce type de raid
Scénario: Raid malveillant via recherche - Inefficace
Étant donné qu'un groupe cherche à nuire à "CreateurA"
Et qu'ils trouvent le contenu via recherche et skip massivement
Quand 500 skips rapides arrivent via source="search"
Alors ces 500 skips NE comptent PAS dans les métriques engagement
Car source="search" n'est pas une source pertinente
Et "CreateurA" est protégé
# Cas limites
Scénario: Utilisateur s'abonne pendant l'écoute d'un contenu
Étant donné qu'un utilisateur N'est PAS abonné à "CreateurA"
Et qu'il démarre l'écoute d'un contenu de "CreateurA"
Et que is_subscribed=false est enregistré au démarrage
Quand l'utilisateur s'abonne à "CreateurA" pendant l'écoute
Et qu'il skip le contenu après 8 secondes
Alors is_subscribed=false reste enregistré (état au moment du démarrage)
Et la pénalité -0.5% s'applique (car non-abonné au démarrage)
Scénario: Utilisateur se désabonne puis écoute ancien contenu
Étant donné qu'un utilisateur ÉTAIT abonné à "CreateurA"
Et qu'il se désabonne aujourd'hui
Quand il écoute un ancien contenu de "CreateurA" demain
Et qu'il skip après 6 secondes
Alors is_subscribed=false (état au moment de l'écoute)
Et la pénalité -0.5% s'applique
Et l'écoute compte dans les métriques d'engagement
# Comparaison tableaux sources
Scénario: Table récapitulative sources et abonnements
Étant donné les règles de comptabilisation définies
Quand on résume le comportement par source et abonnement
Alors le tableau complet est:
| Source | Abonné ? | Skip <10s pénalise ? | Compte "total écoutes" ? |
| recommendation | Non | Oui (-0.5%) | Oui |
| recommendation | Oui | Non (0%) | Non |
| search | Peu imp. | Variable* | Non |
| direct_link | Peu imp. | Variable* | Non |
| profile | Peu imp. | Variable* | Non |
| history | Peu imp. | Variable* | Non |
| live_notification | Non | Oui (-0.5%) | Oui |
| live_notification | Oui | Non (0%) | Non |
| audio_guide | Peu imp. | Non | Non |
(* Variable = -0.5% si non-abonné, 0% si abonné, mais source non pertinente donc pas dans métriques)

View File

@@ -319,3 +319,119 @@ Fonctionnalité: Obligations fiscales
Et m'aider à comprendre ce que je dois déclarer
Mais il ne peut pas me conseiller fiscalement (pas expert-comptable)
Et il me recommande de consulter un expert-comptable si nécessaire
# Règle: DAS2 systématique tous montants (même <1200€)
Scénario: DAS2 systématique - Créateur avec revenus <1200€
Étant donné que j'ai touché 450.00 en 2025
Et que le seuil légal DAS2 est 1200/an
Quand RoadWave génère les DAS2 en janvier 2026
Alors ma DAS2 est quand même envoyée à la DGFIP
Et le montant déclaré est 450.00
Et je reçois une copie par email
Et la DAS2 est disponible dans mon dashboard
Scénario: DAS2 systématique - Créateur avec 50€ seulement
Étant donné que j'ai touché seulement 50.00 en 2025
Quand RoadWave génère les DAS2 en janvier 2026
Alors ma DAS2 est envoyée à la DGFIP avec 50.00
Et je reçois un email de confirmation:
"""
Objet : Votre déclaration fiscale 2025 RoadWave
Bonjour [Créateur],
Vos revenus RoadWave 2025 ont été déclarés aux impôts (DAS2) :
- Revenus publicité : 30.00
- Revenus Premium : 20.00
- Total déclaré : 50.00
Cette déclaration a été transmise à la DGFIP.
Vous devez inclure ce montant dans votre déclaration personnelle.
Télécharger le justificatif : [Lien PDF]
Cordialement,
L'équipe RoadWave
"""
Scénario: Justification juridique DAS2 systématique
Étant donné que le seuil légal DAS2 est 1200/an
Et que RoadWave déclare tous montants (même <1200)
Quand un créateur demande pourquoi sa DAS2 <1200 est envoyée
Alors la justification est:
"""
Bien que le seuil légal DAS2 soit 1200/an,
rien n'interdit de déclarer les montants inférieurs.
Au contraire, cela renforce la transparence et
protège RoadWave en cas de contrôle fiscal.
"""
Scénario: Avantages DAS2 systématique pour RoadWave
Étant donné que RoadWave déclare tous montants
Quand un audit fiscal a lieu
Alors les avantages sont:
| avantage | description |
| Conformité maximale | Aucune zone grise, 100% transparent |
| Protection juridique RoadWave | Traçabilité totale de tous les paiements |
| Simplicité technique | Même processus pour tous (pas de filtrage) |
| Créateur a justificatif | Justificatif fourni même pour petits montants |
| Coût 0 | DAS2 = déclaration obligatoire gratuite |
Scénario: Avantages DAS2 systématique pour créateurs
Étant donné que j'ai touché 800 en 2025
Et que je reçois une DAS2 même si <1200
Quand je fais ma déclaration d'impôts
Alors j'ai un justificatif officiel pour déclarer mes revenus
Et je peux prouver l'origine de mes revenus en cas de contrôle
Et je suis protégé même si les montants sont faibles
Scénario: Statistiques admin - DAS2 systématique
Étant donné qu'un admin RoadWave consulte les métriques DAS2 2025
Quand il accède au dashboard admin
Alors il voit:
| métrique | valeur 2025 |
| Créateurs monétisés totaux | 1,247 |
| DAS2 transmises (tous montants) | 1,247 |
| Dont DAS2 <1200 | 400 (32%) |
| Dont DAS2 1200 | 847 (68%) |
| Revenus totaux déclarés | 1,890,345 |
| Taux conformité | 100% |
Scénario: Comparaison autres plateformes - Twitch, YouTube
Étant donné que Twitch et YouTube ont un seuil DAS2 1200
Et que RoadWave déclare tous montants (même <1200)
Quand on compare les pratiques
Alors RoadWave est plus transparent:
| Plateforme | Seuil DAS2 | Montants <1200 déclarés ? |
| Twitch | 1200 | Non (non documenté) |
| YouTube | 1200 | Non (non documenté) |
| RoadWave | 0 | Oui (tous montants) |
Scénario: Email créateur DAS2 <1200€ - Clarification obligation
Étant donné que j'ai touché 800 en 2025
Quand je reçois l'email DAS2
Alors l'email contient une clarification:
"""
Bien que vos revenus soient inférieurs au seuil légal DAS2 (1200),
RoadWave déclare tous les montants pour assurer une transparence maximale.
Vous devez déclarer ces 800 dans votre déclaration d'impôts personnelle
(formulaire 2042 C PRO pour auto-entrepreneurs ou déclaration IS/IR selon votre statut).
Ce justificatif vous protège en cas de contrôle fiscal.
"""
Scénario: Créateur avec plusieurs plateformes - Cumul seuil 1200€
Étant donné que j'ai touché:
| plateforme | revenus 2025 |
| RoadWave | 800 |
| YouTube | 600 |
| Twitch | 400 |
Et que le seuil DAS2 légal est 1200 par plateforme
Quand les DAS2 sont envoyées
Alors RoadWave envoie une DAS2 pour 800
Mais YouTube et Twitch n'envoient pas de DAS2 (<1200)
Et je dois quand même déclarer les 1800 totaux aux impôts
Et la DAS2 RoadWave me donne un justificatif partiel

View File

@@ -0,0 +1,235 @@
# language: fr
Fonctionnalité: Gestion des soldes dormants et créateurs inactifs
En tant que plateforme RoadWave
Je veux gérer les soldes des créateurs inactifs de manière équitable
Afin de restituer l'argent aux créateurs plutôt que de le confisquer
Contexte:
Étant donné que je suis un créateur monétisé
Et que mon KYC est validé
Et que mon solde actuel est 45.00
# Conservation du solde pour créateurs actifs
Scénario: Créateur actif - Solde conservé indéfiniment
Étant donné que je publie au moins 1 contenu par mois
Ou que je me connecte au dashboard au moins 1 fois par mois
Quand 24 mois se sont écoulés
Alors mon solde de 45.00 est toujours conservé
Et aucun email de préavis n'est envoyé
Et le solde reste visible dans mon dashboard
Scénario: Créateur actif sporadique - Pas de pénalité
Étant donné que je publie 1 contenu tous les 2-3 mois
Ou que je me connecte au dashboard tous les 2 mois
Quand 18 mois d'activité sporadique se sont écoulés
Alors mon solde est conservé normalement
Et je ne reçois aucun email d'avertissement
Et mon statut reste "Créateur actif"
# Créateur inactif - Emails préventifs
Scénario: Inactivité 12 mois - Email préventif 1
Étant donné que je n'ai publié aucun contenu depuis 12 mois
Et que je ne me suis pas connecté au dashboard depuis 12 mois
Quand le système détecte 12 mois d'inactivité
Alors je reçois un email avec le sujet:
"""
Votre solde RoadWave (45.00) - Action requise
"""
Et l'email contient:
"""
Vous n'avez pas publié de contenu depuis 12 mois.
Votre solde actuel : 45.00
🔔 Si vous restez inactif 6 mois supplémentaires :
Versement automatique (frais bancaires déduits)
Montant net estimé : 44.64
💡 Pour éviter le versement anticipé :
- Publiez un nouveau contenu, OU
- Connectez-vous à votre dashboard créateur
"""
Et un lien vers le dashboard est fourni
Scénario: Inactivité 18 mois - Email préventif 2 (préavis final)
Étant donné que je n'ai publié aucun contenu depuis 18 mois
Et que je ne me suis pas connecté au dashboard depuis 18 mois
Quand le système détecte 18 mois d'inactivité
Alors je reçois un email avec le sujet:
"""
📢 Versement automatique dans 30 jours
"""
Et l'email contient:
"""
Vous n'avez pas publié de contenu depuis 18 mois.
Votre solde sera versé automatiquement dans 30 jours :
- Solde actuel : 45.00
- Frais bancaires Mangopay SEPA : 0.36 (1.8% + 0.18)
- Montant net : 44.64
Ce versement est automatique pour restituer votre argent.
💡 Pour conserver le solde jusqu'à 50 :
- Publiez un nouveau contenu, OU
- Connectez-vous à votre dashboard
"""
Scénario: Inactivité 18 mois + 30 jours - Versement forcé
Étant donné que je n'ai publié aucun contenu depuis 18 mois + 30 jours
Et que je ne me suis pas connecté au dashboard depuis 18 mois + 30 jours
Quand le système détecte 18 mois + 30 jours d'inactivité
Alors un versement SEPA automatique est initié vers mon RIB
Et le montant versé est 45.00 - 0.36 = 44.64
Et je reçois un email de confirmation:
"""
Versement automatique effectué
Votre solde RoadWave a été versé :
- Montant brut : 45.00
- Frais bancaires : 0.36
- Montant net versé : 44.64
Le virement devrait arriver sur votre compte dans 1-3 jours.
"""
Scénario: Inactivité 18 mois + 37 jours - Purge données comptables
Étant donné qu'un versement forcé a été effectué il y a 7 jours
Quand le système détecte 18 mois + 37 jours d'inactivité
Alors les données comptables temporaires sont purgées
Mais les logs sont conservés 10 ans (obligation RGPD)
Et l'historique de paiement reste visible dans mon profil
# Exception soldes <10€
Scénario: Solde <10€ après 18 mois - Proposition de don
Étant donné que mon solde actuel est 8.50
Et que je suis inactif depuis 18 mois
Quand le système détecte 18 mois d'inactivité
Alors je reçois un email avec proposition:
"""
Votre solde actuel : 8.50
Les frais bancaires (0.36) représentent 4.2% du montant.
💡 Options disponibles :
1. Don à une association (frais 0, 100% reversé)
2. Conservation jusqu'à atteindre 50
3. Versement avec frais déduits (8.14 net)
Choisissez votre option : [Lien formulaire]
"""
Scénario: Solde <10€ - Choix option "Don association"
Étant donné que mon solde est 8.50
Et que j'ai choisi "Don à une association"
Quand le don est effectué
Alors 8.50 (100%) sont reversés à l'association
Et je reçois un reçu fiscal si applicable
Et mon solde est remis à 0
Scénario: Solde <10€ - Choix option "Conservation jusqu'à 50€"
Étant donné que mon solde est 8.50
Et que j'ai choisi "Conservation jusqu'à 50"
Quand l'option est validée
Alors mon solde est conservé indéfiniment
Et je peux me reconnecter à tout moment pour publier du contenu
Et atteindre les 50 pour un paiement standard
Scénario: Solde <10€ - Choix option "Versement avec frais"
Étant donné que mon solde est 8.50
Et que j'ai choisi "Versement avec frais déduits"
Quand le versement est initié
Alors je reçois 8.50 - 0.36 = 8.14 net
Et un email de confirmation est envoyé
Scénario: Solde <10€ - Pas de réponse + inactivité continue - Versement forcé
Étant donné que mon solde est 8.50
Et que je n'ai pas répondu à l'email de proposition
Et que l'inactivité continue pendant 30 jours supplémentaires
Quand le délai de 18 mois + 30 jours est atteint
Alors le versement est effectué quand même (équité)
Et je reçois 8.14 net après frais
# Frais bancaires
Scénario: Calcul frais bancaires Mangopay SEPA
Étant donné que mon solde est de <montant>
Quand le versement forcé est initié
Alors les frais Mangopay SEPA sont: 1.8% + 0.18
Et le montant net est calculé comme suit:
| Solde brut | Frais (1.8% + 0.18) | Montant net |
| 45.00 | 0.36 | 44.64 |
| 30.00 | 0.36 | 29.64 |
| 100.00 | 1.98 | 98.02 |
| 8.50 | 0.36 | 8.14 |
Scénario: Transparence frais bancaires dans emails
Étant donné qu'un email de préavis est envoyé
Quand je lis l'email
Alors les frais bancaires sont explicitement mentionnés:
"""
Frais bancaires Mangopay SEPA : 0.36 (1.8% + 0.18)
"""
Et le montant net final est clairement indiqué
# Comparaison Twitch
Scénario: Comparaison avec Twitch - Versement forcé vs Forfeiture
Étant donné que Twitch confisque (forfeiture) les soldes après 24 mois
Et que RoadWave verse (restitue) les soldes après 18 mois
Alors RoadWave est plus équitable:
| Critère | Twitch | RoadWave | Avantage |
| Seuil paiement | 50-100$ | 50 | Aligné |
| Délai inactivité | 24 mois | 18 mois | Plus court |
| Action fin délai | Forfeiture (perte argent) | Versement forcé (récupère) | RoadWave |
| Emails préventifs | Non documenté | 12 mois + 18 mois + 30j | RoadWave |
| Frais bancaires | Non documenté | Déduits + annoncés | RoadWave |
# Réactivation après inactivité
Scénario: Reconnexion après 12 mois d'inactivité - Solde conservé
Étant donné que je suis inactif depuis 12 mois
Et que mon solde est 45.00
Quand je me connecte au dashboard
Alors mon solde est toujours de 45.00
Et le compteur d'inactivité est remis à 0
Et aucun versement forcé n'aura lieu
Scénario: Publication contenu après 17 mois d'inactivité - Annulation versement
Étant donné que je suis inactif depuis 17 mois
Et qu'un email de préavis a été envoyé
Quand je publie un nouveau contenu
Alors le processus de versement forcé est annulé
Et mon solde reste conservé normalement
Et je redeviens "Créateur actif"
Scénario: Reconnexion 1 jour avant versement forcé - Annulation
Étant donné que je suis inactif depuis 18 mois + 29 jours
Et que le versement forcé est prévu demain
Quand je me connecte au dashboard aujourd'hui
Alors le versement forcé est annulé
Et mon solde est conservé
Et un message confirme: "Versement automatique annulé. Solde conservé."
# Cas limites
Scénario: Créateur avec plusieurs contenus anciens mais inactif
Étant donné que j'ai publié 50 contenus il y a 2 ans
Et que ces contenus génèrent encore des revenus passifs
Mais que je ne me connecte plus au dashboard depuis 18 mois
Quand le système détecte 18 mois d'inactivité
Alors je suis considéré "inactif" (aucune connexion dashboard)
Et je reçois les emails de préavis normalement
Et le versement forcé s'applique après 18 mois + 30 jours
Scénario: Créateur avec abonnés fidèles mais inactif
Étant donné que j'ai 500 abonnés
Et que mes anciens contenus génèrent encore des revenus
Mais que je ne publie plus de contenus depuis 18 mois
Et que je ne me connecte plus au dashboard depuis 18 mois
Quand le système détecte l'inactivité
Alors je reçois les emails de préavis
Et le versement forcé s'applique normalement
Car l'activité comptabilisée = connexion dashboard OU publication contenu

View File

@@ -0,0 +1,233 @@
# language: fr
Fonctionnalité: Multi-devices Premium - Dernier device prend priorité (KISS)
En tant que système Premium
Je veux appliquer une règle simple: le dernier device à démarrer prend toujours la priorité
Afin d'éviter la complexité des exceptions temporelles et géographiques
Contexte:
Étant donné qu'un utilisateur "UserA" a un abonnement Premium actif
Et que l'utilisateur possède 2 devices:
| device | type |
| iPhone_123 | iOS |
| iPad_456 | iOS |
# Règle KISS : Dernier device = Priorité
Scénario: Transition normale - Device 2 démarre après Device 1
Étant donné que "UserA" écoute un contenu sur iPhone_123
Et que le heartbeat iPhone_123 est actif (toutes les 30s)
Quand "UserA" démarre la lecture sur iPad_456 (5 secondes après)
Alors le serveur détecte une session active (iPhone_123)
Et envoie un WebSocket close à iPhone_123
Et iPhone_123 affiche le message:
"""
Lecture interrompue
Votre compte est utilisé sur un autre appareil.
Un seul appareil peut écouter à la fois avec votre compte Premium.
Le partage de compte viole nos CGU et peut entraîner une suspension.
[Reprendre ici] [Sécuriser mon compte]
"""
Et Redis met à jour active_streams:UserA = {device_id: "iPad_456", started_at: timestamp}
Et la lecture démarre sur iPad_456
# Offline connecté internet
Scénario: Device 1 offline MAIS connecté WiFi - Device 2 coupe Device 1
Étant donné que "UserA" écoute un contenu téléchargé en offline sur iPhone_123
Mais que iPhone_123 est connecté au WiFi
Et que le heartbeat iPhone_123 est envoyé toutes les 30s
Quand "UserA" démarre la lecture online sur iPad_456
Alors le serveur détecte une session active (iPhone_123, mode offline)
Et envoie un WebSocket close à iPhone_123
Et iPhone_123 affiche le même message d'interruption
Et iPad_456 démarre normalement
# Offline déconnecté internet (exception technique)
Scénario: Device 1 vraiment offline (mode avion) - Pas de détection possible
Étant donné que "UserA" écoute un contenu offline sur iPhone_123 en mode avion
Et qu'aucun heartbeat n'est envoyé (pas de connexion internet)
Quand "UserA" démarre la lecture online sur iPad_456
Alors le serveur ne détecte AUCUNE session active
Car aucun heartbeat depuis iPhone_123
Et iPad_456 démarre normalement
Et iPhone_123 continue en offline (pas de coupure possible)
Et c'est une exception technique acceptable ("Tant pis")
Scénario: Device 1 offline reconnecte - Heartbeat reprend
Étant donné que "UserA" écoutait offline sur iPhone_123 en mode avion
Quand iPhone_123 sort du mode avion et se connecte au WiFi
Alors le heartbeat reprend immédiatement
Et Redis crée/met à jour active_streams:UserA = {device_id: "iPhone_123", started_at: timestamp}
Et si iPad_456 démarre, iPhone_123 sera coupé normalement
# Heartbeat et TTL Redis
Scénario: Heartbeat toutes les 30s maintient la session active
Étant donné que "UserA" écoute sur iPhone_123
Quand iPhone_123 envoie un heartbeat toutes les 30 secondes
Alors Redis met à jour active_streams:UserA avec TTL 5 minutes
Et la session reste active tant que les heartbeats arrivent
Scénario: Pas de heartbeat pendant 5 min - Session expirée
Étant donné que "UserA" écoutait sur iPhone_123
Mais que iPhone_123 a été éteint brutalement (pas de heartbeat final)
Quand 5 minutes se sont écoulées sans heartbeat
Alors Redis expire automatiquement l'entrée active_streams:UserA (TTL)
Et iPad_456 peut démarrer sans détecter de conflit
# Boutons message coupure
Scénario: Bouton "Reprendre ici" sur device coupé
Étant donné que iPhone_123 a été coupé par iPad_456
Et que le message d'interruption est affiché sur iPhone_123
Quand "UserA" clique sur [Reprendre ici] sur iPhone_123
Alors le serveur détecte que iPad_456 est actif
Et envoie un WebSocket close à iPad_456
Et iPhone_123 reprend la lecture
Et iPad_456 affiche le même message d'interruption
Scénario: Bouton "Sécuriser mon compte" sur device coupé
Étant donné que iPhone_123 a été coupé par iPad_456
Et que le message d'interruption est affiché sur iPhone_123
Quand "UserA" clique sur [Sécuriser mon compte]
Alors l'utilisateur est redirigé vers:
- Page changement mot de passe
- Option "Déconnecter tous les appareils"
Et un email de sécurité est envoyé
Et un log de sécurité est créé (suspicion piratage)
# Limite offline 30 jours
Scénario: Contenus offline expirés >30j - Force reconnexion
Étant donné que "UserA" a téléchargé 50 contenus il y a 32 jours
Et qu'il n'a pas reconnecté son device depuis 32 jours
Quand "UserA" essaie d'écouter un contenu offline
Alors un message s'affiche:
"""
Contenus expirés (>30 jours)
Reconnectez-vous au WiFi pour renouveler vos contenus.
"""
Et la lecture est bloquée
Et cela force la reconnexion (détection multi-devices redevient possible)
Scénario: Notification J-3 avant expiration contenus offline
Étant donné que "UserA" a des contenus offline qui expirent dans 3 jours
Quand le système détecte l'approche de l'expiration
Alors une notification push est envoyée:
"""
10 contenus offline expirent dans 3 jours
Reconnectez-vous au WiFi pour renouveler automatiquement.
"""
# Détection abus post-MVP
Scénario: Monitoring patterns suspects - Flag modération manuelle
Étant donné que le système monitore les patterns d'utilisation
Quand "UserA" change de device >10 fois par jour pendant 7 jours
Alors un flag de suspicion est créé dans le dashboard modération
Et un email d'investigation est envoyé à "UserA":
"""
Activité suspecte détectée sur votre compte
Nous avons détecté des changements fréquents d'appareil.
Si ce n'est pas vous, sécurisez votre compte immédiatement.
[Sécuriser mon compte] [Contacter le support]
"""
Mais aucune action automatique n'est prise (évite faux positifs)
Scénario: Connexions alternées villes éloignées - Flag modération
Étant donné que le système monitore les localisations approximatives
Quand "UserA" se connecte à Paris à 10h puis Marseille à 10h30 (même jour)
Et que ce pattern se répète 5 fois en 1 semaine
Alors un flag de suspicion est créé
Et un modérateur humain enquête manuellement
Mais aucun blocage automatique (évite faux positifs: TGV légitime)
Scénario: Partage de compte confirmé - Suspension 7 jours
Étant donné qu'un modérateur confirme manuellement un partage de compte
Et que les preuves sont claires (signalements users + patterns suspects)
Quand le modérateur applique la sanction
Alors le compte est suspendu 7 jours
Et un email de warning est envoyé:
"""
Suspension de compte (7 jours)
Le partage de compte Premium viole nos CGU.
Votre compte est suspendu jusqu'au [Date].
En cas de récidive, le compte sera définitivement fermé.
"""
Scénario: Récidive partage de compte - Ban définitif
Étant donné qu'un compte a déjà été suspendu 7 jours pour partage
Et que le partage continue après la réactivation
Quand le modérateur confirme la récidive
Alors le compte est définitivement fermé (ban)
Et un email de fermeture définitive est envoyé
Et le remboursement pro-rata de l'abonnement est effectué
# Cas d'usage réels
Scénario: User change de pièce (voiture → maison) rapidement
Étant donné que "UserA" écoute dans sa voiture sur iPhone_123
Quand il arrive chez lui et passe sur iPad_456 après 30 secondes
Alors iPhone_123 est coupé immédiatement
Et iPad_456 prend le relai
Et l'utilisateur comprend le message (device unique)
Scénario: User oublie son téléphone en lecture et part
Étant donné que "UserA" écoute sur iPhone_123
Et qu'il part en laissant iPhone_123 en lecture
Quand il démarre la lecture sur iPad_456 au travail
Alors iPhone_123 est coupé à distance (WebSocket close)
Et iPad_456 prend la priorité
Scénario: TGV Paris → Lyon (légitime) - Pas de faux positif
Étant donné que "UserA" prend le TGV Paris Lyon
Et qu'il écoute pendant tout le trajet sur iPhone_123
Quand le système détecte un changement de localisation (GPS)
Alors aucun flag de suspicion n'est créé
Car c'est un déplacement continu légitime
Et aucune action automatique n'est prise
# Implémentation technique Redis
Scénario: Stockage Redis - Structure active_streams
Étant donné que "UserA" démarre la lecture sur iPhone_123
Quand le heartbeat est envoyé au serveur
Alors Redis stocke:
| clé | valeur |
| active_streams:UserA | {"device_id": "iPhone_123", "started_at": 1707311400} |
Et un TTL de 5 minutes (300 secondes) est défini
Et la clé expire automatiquement si pas de heartbeat
Scénario: Mise à jour Redis à chaque heartbeat
Étant donné qu'une session active existe sur iPhone_123
Quand un heartbeat est envoyé toutes les 30 secondes
Alors Redis met à jour le TTL à 5 minutes
Et started_at reste inchangé (timestamp initial)
Et la session reste active indéfiniment tant que les heartbeats arrivent
# Justification KISS
Scénario: Comparaison avec règle complexe - Avantages KISS
Étant donné qu'on compare la règle KISS avec une règle complexe (exceptions temporelles, GPS, etc.)
Quand on évalue les avantages
Alors les avantages KISS sont:
| avantage | description |
| Simplicité technique | Pas de tracking GPS précis, pas de calcul distances |
| Pas de faux positifs | TGV légitime ne déclenche pas d'alerte automatique |
| Assume bonne foi | Majorité users honnêtes, gestion réactive suffit |
| Message dissuasif clair | Avertissement CGU dans message, option sécurité |
| Protection revenus créateurs | 1 abonnement = 1 personne = 1 écoute active |
| UX claire | User comprend immédiatement le comportement |

View File

@@ -0,0 +1,238 @@
# language: fr
Fonctionnalité: Ciblage horaire publicités et gestion fuseaux horaires
En tant que publicitaire
Je veux cibler mes publicités sur des plages horaires en heure locale utilisateur
Afin d'optimiser mes campagnes selon les moments de la journée (rush matin/soir)
Contexte:
Étant donné qu'un publicitaire crée une campagne publicitaire
# Règle 1 : Ciblage horaire = Heure locale utilisateur
Scénario: Campagne "7h-9h" diffuse selon heure locale de chaque utilisateur
Étant donné qu'une campagne est configurée avec plage horaire "7h-9h"
Et que nous sommes le 7 février 2026 à 8h00 UTC
Quand le système vérifie la diffusion pour différents utilisateurs:
| utilisateur | localisation | fuseau horaire | heure locale | diffusion ? |
| User Marseille | Marseille | Europe/Paris | 9h00 | Oui |
| User Guadeloupe | Pointe-à-Pitre | America/Guadeloupe | 4h00 | Non |
| User Réunion | Saint-Denis | Indian/Reunion | 12h00 | Non |
| User Lyon | Lyon | Europe/Paris | 9h00 | Oui |
Alors la pub est diffusée uniquement aux utilisateurs dans la plage 7h-9h leur heure locale
Scénario: Campagne "17h-19h" (rush soir) - Heure locale de chaque utilisateur
Étant donné qu'une campagne est configurée avec plage horaire "17h-19h"
Et que nous sommes le 7 février 2026 à 17h30 UTC
Quand le système vérifie la diffusion pour:
| utilisateur | fuseau horaire | heure locale | diffusion ? |
| User Paris | Europe/Paris | 18h30 | Oui |
| User Martinique | America/Martinique | 13h30 | Non |
| User Réunion | Indian/Reunion | 21h30 | Non |
Alors la pub est diffusée uniquement à User Paris (18h30 dans 17h-19h)
Scénario: Campagne 24/7 (toute la journée) - Pas de restriction horaire
Étant donné qu'une campagne n'a AUCUNE restriction horaire
Quand le système vérifie la diffusion
Alors la pub est diffusée à tout moment (0h-23h)
Et tous les utilisateurs sont éligibles quelle que soit l'heure locale
# Détection fuseau horaire utilisateur
Scénario: Détection fuseau via GPS (méthode primaire)
Étant donné qu'un utilisateur a le GPS activé
Et que sa position GPS est (latitude: 48.8566, longitude: 2.3522)
Quand le système détecte le fuseau horaire
Alors le fuseau horaire déterminé est "Europe/Paris"
Et l'heure locale est calculée avec ce fuseau
Scénario: Détection fuseau via paramètres device (si GPS désactivé)
Étant donné qu'un utilisateur a le GPS désactivé
Mais que les paramètres OS indiquent fuseau "America/Guadeloupe"
Quand le système détecte le fuseau horaire
Alors le fuseau horaire utilisé est "America/Guadeloupe"
Et l'heure locale est calculée avec ce fuseau
Scénario: Fallback IP geolocation (si GPS désactivé ET paramètres indisponibles)
Étant donné qu'un utilisateur a le GPS désactivé
Et que les paramètres device ne sont pas accessibles
Mais que l'IP est géolocalisée à La Réunion
Quand le système détecte le fuseau horaire
Alors le fuseau horaire approximatif est "Indian/Reunion"
Et l'heure locale est calculée avec ce fuseau
# Règle 2 : Ciblage "France" = Métropole + DOM
Scénario: Ciblage "France entière" inclut Métropole + DOM
Étant donné qu'une campagne cible "France (nationale)"
Quand le système vérifie les utilisateurs éligibles
Alors les utilisateurs inclus sont:
| zone | départements / territoires |
| France métropolitaine | 96 départements (01 à 95, 2A, 2B, etc.) |
| Guadeloupe | 971 |
| Martinique | 972 |
| Guyane | 973 |
| Réunion | 974 |
| Mayotte | 976 |
Scénario: Publicitaire affine ciblage "Région Provence-Alpes-Côte d'Azur"
Étant donné qu'une campagne cible "Région Provence-Alpes-Côte d'Azur"
Quand le système vérifie les utilisateurs éligibles
Alors seuls les utilisateurs en Métropole dans cette région sont ciblés
Et les utilisateurs DOM (Guadeloupe, Réunion, etc.) ne sont PAS ciblés
Scénario: Publicitaire affine ciblage "Département 971 (Guadeloupe)"
Étant donné qu'une campagne cible "Département 971"
Quand le système vérifie les utilisateurs éligibles
Alors seuls les utilisateurs en Guadeloupe sont ciblés
Et les utilisateurs Métropole ne sont PAS ciblés
Scénario: Publicitaire affine ciblage "Ville Pointe-à-Pitre"
Étant donné qu'une campagne cible "Ville Pointe-à-Pitre"
Quand le système vérifie les utilisateurs éligibles
Alors seuls les utilisateurs à Pointe-à-Pitre (Guadeloupe) sont ciblés
# Interface publicitaire
Scénario: Interface création campagne - Note explicite sur inclusion DOM
Étant donné qu'un publicitaire accède au formulaire de création campagne
Quand il sélectionne "National (France entière)"
Alors une note informative s'affiche:
"""
Note : "National (France entière)" inclut les DOM
(Guadeloupe, Martinique, Réunion, Guyane, Mayotte)
"""
Scénario: Interface - Liste déroulante ciblage géographique
Étant donné qu'un publicitaire configure le ciblage géographique
Quand il consulte la liste déroulante
Alors les options disponibles sont:
| option | description |
| National (France entière) | Métropole + DOM |
| Région | Ex: Provence-Alpes-Côte d'Azur (Métropole)|
| Département | Ex: 13 (Métropole) ou 971 (Guadeloupe) |
| Ville | Ex: Marseille, Pointe-à-Pitre |
| Point GPS + rayon | Latitude/Longitude + rayon en km |
# Cas d'usage publicitaire
Scénario: Restaurant local Guadeloupe - Ciblage département 971 + horaires 12h-14h
Étant donné qu'un restaurant à Pointe-à-Pitre crée une campagne
Et que le ciblage est "Département 971 (Guadeloupe)"
Et que les horaires sont "12h-14h" (rush déjeuner)
Quand le système diffuse les pubs à 12h30
Alors les utilisateurs Guadeloupe à 12h30 heure locale reçoivent la pub
Et les utilisateurs Métropole ne reçoivent PAS la pub (hors zone géo)
Et les utilisateurs Martinique ne reçoivent PAS la pub (hors zone géo)
Scénario: Assureur national - Ciblage France + horaires 7h-9h + 17h-19h
Étant donné qu'un assureur national crée une campagne
Et que le ciblage est "France (nationale)"
Et que les horaires sont "7h-9h" et "17h-19h" (rush matin/soir)
Quand le système diffuse les pubs
Alors à 8h locale Marseille, User Marseille reçoit la pub
Et à 8h locale Réunion, User Réunion reçoit la pub
Et User Réunion reçoit la pub à 8h locale (= 5h métropole, mais c'est son rush matin)
Scénario: Utilisateur en vacances change de fuseau horaire
Étant donné qu'un utilisateur habite Paris (Europe/Paris)
Et qu'il part en vacances à La Réunion
Et qu'il télécharge 50 contenus + pubs avant de partir
Quand l'utilisateur écoute à 8h locale Réunion (device détecte fuseau)
Alors le filtrage des pubs utilise l'heure locale Réunion (8h)
Et les pubs ciblées "7h-9h" sont diffusées normalement
# Implémentation technique
Scénario: Calcul heure locale via PostgreSQL AT TIME ZONE
Étant donné qu'une campagne a une plage horaire "7h-9h"
Et qu'un utilisateur a le fuseau "Indian/Reunion" (UTC+4)
Quand le backend vérifie l'éligibilité à 08h00 UTC
Alors la requête SQL utilise:
"""sql
SELECT
EXTRACT(HOUR FROM NOW() AT TIME ZONE 'Indian/Reunion') AS local_hour
-- Résultat: 12 (8h UTC + 4h = 12h locale)
"""
Et local_hour = 12 n'est PAS dans [7, 8, 9], donc pas de diffusion
Scénario: Base IANA Time Zone pour conversion GPS → fuseau
Étant donné qu'un utilisateur a GPS (48.8566, 2.3522)
Quand le système convertit GPS fuseau horaire
Alors la base IANA Time Zone est utilisée
Et le fuseau déterminé est "Europe/Paris"
Et la base est mise à jour régulièrement (changements DST, fuseaux)
# Justification
Scénario: Comparaison avec standard industrie - Google Ads, Facebook Ads
Étant donné qu'on compare avec Google Ads et Facebook Ads
Quand on évalue le comportement du ciblage horaire
Alors RoadWave suit le standard:
| plateforme | ciblage horaire | référence temporelle | norme |
| Google Ads | 7h-9h | Heure locale user | Standard |
| Facebook Ads | 7h-9h | Heure locale user | Standard |
| RoadWave | 7h-9h | Heure locale user | Standard |
Scénario: Avantages UX intuitive pour publicitaires
Étant donné qu'un publicitaire configure "7h-9h"
Quand il pense "rush matin"
Alors il n'a pas besoin de comprendre UTC
Et "7h-9h" signifie "matin partout en France"
Et l'UX est intuitive:
| avantage | description |
| UX intuitive publicitaires | "7h-9h" = matin partout, pas UTC compliqué |
| Équité géographique | Pas de discrimination DOM-TOM |
| Simplicité technique | Détection automatique fuseau (GPS/device) |
| Standard industrie | Même comportement Google/Facebook |
# Cas limites
Scénario: Utilisateur change de fuseau pendant campagne active
Étant donné qu'un utilisateur écoute des pubs en métropole (Europe/Paris)
Et qu'une campagne cible "7h-9h"
Quand l'utilisateur part en vacances Réunion (Indian/Reunion)
Alors le système détecte le nouveau fuseau horaire automatiquement
Et les pubs "7h-9h" sont filtrées selon l'heure locale Réunion
Et l'utilisateur reçoit les pubs à 7h-9h heure locale Réunion
Scénario: Changement heure d'été/hiver (DST) - Gestion automatique
Étant donné qu'une campagne cible "7h-9h" en Europe/Paris
Et que le changement heure d'été arrive (dernier dimanche mars)
Quand l'heure passe de 2h à 3h (UTC+1 UTC+2)
Alors le système utilise automatiquement le nouveau décalage UTC
Et les pubs "7h-9h" continuent de se diffuser à 7h-9h heure locale
Et PostgreSQL AT TIME ZONE gère automatiquement le DST
# Métriques dashboard publicitaire
Scénario: Dashboard publicitaire - Répartition géographique diffusions
Étant donné qu'une campagne "France (nationale)" + "7h-9h" est active
Quand le publicitaire consulte le dashboard
Alors la répartition géographique affiche:
| zone | impressions | % total |
| Île-de-France | 45,000 | 60% |
| Provence-Alpes | 15,000 | 20% |
| Guadeloupe | 3,000 | 4% |
| Réunion | 4,500 | 6% |
| Autres | 7,500 | 10% |
Scénario: Dashboard publicitaire - Répartition horaire diffusions
Étant donné qu'une campagne "7h-9h" est active
Quand le publicitaire consulte le dashboard
Alors un graphique horaire affiche:
| heure locale | impressions |
| 7h | 12,000 |
| 8h | 18,000 |
| 9h | 5,000 |
Et les impressions hors plage (autres heures) = 0
Scénario: Validation création campagne - Cohérence géo + horaires
Étant donné qu'un publicitaire crée une campagne
Et qu'il sélectionne "Département 971 (Guadeloupe)"
Et qu'il configure horaires "7h-9h"
Quand il valide la campagne
Alors le système confirme:
"""
Votre campagne sera diffusée à 7h-9h heure locale Guadeloupe (UTC-4).
Estimation: 2,500 impressions/jour.
"""

View File

@@ -178,3 +178,44 @@ Fonctionnalité: Formule de scoring et recommandation
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