feat(bdd): réorganiser features en catégories api/ui/e2e et créer ADR-024

Résolution des incohérences #10, #11, et #12 de l'analyse d'architecture.

## Phase 1 : Réorganisation Features BDD (Point #10 - RÉSOLU)

- Créer structure features/{api,ui,e2e}
- Déplacer 83 features en 3 catégories via git mv (historique préservé)
  - features/api/ : 53 features (tests API backend)
  - features/ui/ : 22 features (tests UI mobile)
  - features/e2e/ : 8 features (tests end-to-end)

Domaines déplacés :
- API : authentication, recommendation, rgpd-compliance, content-creation,
  moderation, monetisation, premium, radio-live, publicites
- UI : audio-guides, navigation, interest-gauges, mode-offline,
  partage, profil, recherche
- E2E : abonnements, error-handling

## Phase 2 : Mise à jour Documentation

### ADR-007 - Tests BDD
- Ajouter section "Convention de Catégorisation des Features"
- Documenter règles api/ui/e2e avec exemples concrets
- Spécifier step definitions (backend Go, mobile Dart)

### ADR-024 - Stratégie CI/CD Monorepo (NOUVEAU)
- Créer ADR dédié pour stratégie CI/CD avec path filters
- Architecture workflows séparés (backend.yml, mobile.yml, shared.yml)
- Configuration path filters détaillée avec exemples YAML
- Matrice de déclenchement et optimisations (~70% gain temps CI)
- Plan d'implémentation (~2h, reporté jusqu'au développement)

### ADR-016 - Organisation Monorepo
- Simplifier en retirant section CI/CD détaillée
- Ajouter référence vers ADR-024 pour stratégie CI/CD

### INCONSISTENCIES-ANALYSIS.md
- Point #10 (Tests BDD synchronisés) :  RÉSOLU
  - Catégorisation features implémentée
  - ADR-007 mis à jour avec convention complète
- Point #11 (70/30 Split paiements) :  ANNULÉ (faux problème)
  - ADR-009 et Règle 18 parfaitement cohérents
  - Documentation exhaustive existante (formule, SQL, comparaisons)
- Point #12 (Monorepo path filters) : ⏸️ DOCUMENTÉ
  - Architecture CI/CD complète dans ADR-024
  - Implémentation reportée (projet en phase documentation)
- Métriques mises à jour :
  - MODERATE : 6/9 traités (4 résolus + 1 annulé + 1 documenté)
  - ADR à jour : 100% (19/19 avec ADR-024)

## Phase 3 : Validation

- Structure features validée (api/ui/e2e, aucun répertoire restant)
- Historique Git préservé (git mv, renommages détectés)
- 83 features total (API: 53, UI: 22, E2E: 8)

Closes: Point #10 (résolu), Point #11 (annulé), Point #12 (documenté)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
jpgiannetti
2026-02-01 11:31:41 +01:00
parent 841028d1b2
commit 37c62206ad
88 changed files with 625 additions and 67 deletions

View File

@@ -0,0 +1,240 @@
# Tests Gherkin - Algorithme de Recommandation
Tests BDD pour la section [04-algorithme-recommandation.md](../../docs/regles-metier/04-algorithme-recommandation.md)
## Fichiers de tests
### [classification-geo.feature](classification-geo.feature)
**Couverture** : Section 2.1 des règles métier
- ✅ Classification 3 types (Géo-ancré 70%, Géo-contextuel 50%, Géo-neutre 20%)
- ✅ Choix par créateur
- ✅ Reclassification par modérateur
- ✅ Modification après publication
- ✅ Impact sur pondération algorithme
**Scénarios** : 11
---
### [scoring-recommandation.feature](scoring-recommandation.feature)
**Couverture** : Sections 2.2, 2.3, 2.4 des règles métier
- ✅ Calcul score géographique linéaire (1 - distance/200km)
- ✅ Calcul score d'intérêts (moyenne jauges tags)
- ✅ Calcul score engagement (complétion 50%, likes 30%, abonnements 20%)
- ✅ Seuil minimum 50 écoutes
- ✅ Score final combiné selon type contenu
- ✅ Bonus aléatoire 10% configurable
- ✅ Contenu viral peut être recommandé loin
- ✅ Pré-calcul 5 contenus suivants
- ✅ Recalcul si >10 km ou >10 min
**Scénarios** : 23
---
### [contenu-politique.feature](contenu-politique.feature)
**Couverture** : Section 2.5 des règles métier (MVP simplifié)
- ✅ Tag simple "Politique" sans classification gauche/droite
- ✅ Filtrage utilisateur "Masquer contenu politique"
- ✅ Par défaut tous contenus visibles
- ✅ Mode Kids filtre automatiquement le politique
- ✅ Pas d'équilibrage imposé en MVP
**Scénarios** : 13
---
### [mode-kids.feature](mode-kids.feature)
**Couverture** : Section 2.6 des règles métier
- ✅ Activation manuelle (pas automatique car âge min 13 ans)
- ✅ Filtrage contenus "Tous publics" uniquement
- ✅ Exclusion automatique contenu politique
- ✅ Pas de publicité (ou validée manuellement)
- ✅ Interface standard (pas d'UI enfant)
- ✅ Désactivation possible à tout moment
**Scénarios** : 15
---
### [declenchement-geo.feature](declenchement-geo.feature)
**Couverture** : Section 2.7 des règles métier
- ✅ Notification sonore + visuelle au passage <500m
- ✅ Délai réaction 5 secondes
- ✅ Pas d'interruption contenu en cours
- ✅ Logos différenciés (📍🏛️🍴🎭)
- ✅ Publicité uniquement entre contenus
- ✅ Gestion demi-tour (pas de répétition avant 24h)
- ✅ Rayon configurable par admin
**Scénarios** : 17
---
### [historique-reproposition.feature](historique-reproposition.feature)
**Couverture** : Section 2.8 des règles métier
- ✅ Contenu >80% jamais reproposé (sauf replayable=true)
- ✅ Contenu <10s ne pas reproposer (signal négatif)
- ✅ Contenu 10-80% reproposer avec reprise position
- ✅ Stockage illimité PostgreSQL
- ✅ Algorithme considère 100 derniers pour performance
- ✅ Export complet RGPD
**Scénarios** : 17
---
### [parametrabilite-admin.feature](parametrabilite-admin.feature)
**Couverture** : Section 2.9 des règles métier
- ✅ Dashboard admin avec tous paramètres configurables à chaud
- ✅ Validation plages de valeurs
- ✅ Aucun recalcul batch (économie CPU)
- ✅ Versioning configurations (git-like)
- ✅ Rollback 1 clic
- ✅ A/B testing avec split 50/50
- ✅ Métriques comparatives temps réel
- ✅ Graphiques évolution engagement
- ✅ Export CSV analyse externe
**Scénarios** : 17
---
### [parametrabilite-utilisateur.feature](parametrabilite-utilisateur.feature)
**Couverture** : Section 2.10 des règles métier
- ✅ 3 curseurs (Géolocalisation, Découverte, Politique)
- ✅ Profils sauvegardables (Trajet quotidien, Road trip, Enfants)
- ✅ Synchronisation multi-devices
- ✅ Auto-switch selon contexte GPS
- ✅ Blocage modification si vitesse >10 km/h
- ✅ Warning avant de prendre la route
- ✅ Limite 10 profils par utilisateur
**Scénarios** : 22
---
### [medias-traditionnels.feature](medias-traditionnels.feature)
**Couverture** : Section 2.11 des règles métier
- ✅ Compte média vérifié (badge ✓)
- ✅ Pas de validation 3 premiers contenus
- ✅ Modération a posteriori uniquement
- ✅ Formats: flash info, chroniques, éditos, reportages
- ✅ Classification âge obligatoire
- ✅ Monétisation standard (3€/1000 écoutes)
- ✅ Sponsoring direct autorisé
- ✅ Statistiques détaillées
**Scénarios** : 19
---
## Statistiques
| Métrique | Valeur |
|----------|--------|
| **Fichiers** | 9 |
| **Scénarios** | 154 |
| **Règles métier** | 100% couverture section 2 |
## Formules mathématiques testées
### Score géographique
```
score_geo = 1 - (distance_km / distance_max_km)
```
### Score intérêts
```
score_interets = moyenne(jauges_tags_correspondants)
```
### Score engagement
```
score_engagement = (taux_completion × 0.5) + (ratio_likes × 0.3) + (ratio_abonnements × 0.2)
```
### Score final
```
score_final = (score_geo × poids_geo_type)
+ (score_interets × poids_interets_type)
+ (score_engagement × 0.2)
+ bonus_aleatoire
```
## Paramètres par défaut
| Paramètre | Défaut | Plage |
|-----------|--------|-------|
| poids_geo_ancre | 0.7 | 0.5 - 1.0 |
| poids_geo_contextuel | 0.5 | 0.3 - 0.7 |
| poids_geo_neutre | 0.2 | 0.0 - 0.4 |
| poids_engagement | 0.2 | 0.0 - 0.5 |
| part_aleatoire_global | 0.1 | 0.0 - 0.3 |
| distance_max_km | 200 | 50 - 500 |
| rayon_gps_point_m | 500 | 100 - 2000 |
| seuil_min_ecoutes_engagement | 50 | 10 - 200 |
## Exécution des tests
### Tous les tests de recommandation
```bash
godog run features/recommendation/
```
### Un fichier spécifique
```bash
godog run features/recommendation/scoring-recommandation.feature
```
### Tests calculs mathématiques uniquement
```bash
godog run features/recommendation/scoring-recommandation.feature --tags=@calcul
```
## CI/CD
Ces tests sont exécutés :
- ✅ Avant chaque release
- ✅ Sur les PRs modifiant l'algorithme de recommandation
- ✅ Nightly builds (tous les tests)
## Implémentation des steps
Les steps definitions seront implémentées dans :
```
features/steps/recommendation_steps.go
```
Exemple avec calculs :
```go
func (s *RecommendationSteps) lalgorithmeCalculeLeScoreGeo(expectedScore float64) error {
actualScore := 1.0 - (s.distance / s.distanceMax)
if math.Abs(actualScore - expectedScore) > 0.01 {
return fmt.Errorf("score_geo: attendu %.2f, obtenu %.2f", expectedScore, actualScore)
}
return nil
}
```
## Prochaines étapes
1. ⏳ Implémenter les steps definitions en Go
2. ⏳ Tester les formules mathématiques avec valeurs edge cases
3. ⏳ Configurer Testcontainers pour PostgreSQL + PostGIS
4. ⏳ Tests de performance (calcul score pour 1000 contenus <100ms)
5. ⏳ Créer les Gherkin pour les 14 autres sections
---
**Statut** : ✅ Spécifications complètes
**Dernière mise à jour** : 2026-01-21

View File

@@ -0,0 +1,100 @@
# language: fr
Fonctionnalité: Classification de géo-pertinence des contenus
En tant que plateforme de contenu géolocalisé
Je veux classifier les contenus selon leur pertinence géographique
Afin d'adapter l'algorithme de recommandation
Contexte:
Étant donné que l'API RoadWave est disponible
Scénario: Créateur choisit le type géo-ancré pour un audio-guide
Étant donné que je suis un créateur connecté
Quand je publie un audio-guide de la Tour Eiffel
Et que je choisis la classification "Géo-ancré"
Alors le contenu est enregistré avec:
| champ | valeur |
| type_geo | geo_ancre |
| ponderation_geo | 0.7 |
| ponderation_interets | 0.1 |
Scénario: Créateur choisit le type géo-contextuel pour actualité régionale
Étant donné que je suis un créateur connecté
Quand je publie une actualité régionale en Bretagne
Et que je choisis la classification "Géo-contextuel"
Alors le contenu est enregistré avec:
| champ | valeur |
| type_geo | geo_contextuel |
| ponderation_geo | 0.5 |
| ponderation_interets | 0.3 |
Scénario: Créateur choisit le type géo-neutre pour un podcast philosophie
Étant donné que je suis un créateur connecté
Quand je publie un podcast de philosophie
Et que je choisis la classification "Géo-neutre"
Alors le contenu est enregistré avec:
| champ | valeur |
| type_geo | geo_neutre |
| ponderation_geo | 0.2 |
| ponderation_interets | 0.6 |
Scénario: Publication impossible sans classification géographique
Étant donné que je crée un contenu audio
Quand j'essaie de publier sans sélectionner de type géographique
Alors la publication échoue
Et je vois le message "Vous devez sélectionner un type de géo-pertinence"
Scénario: Modérateur reclassifie un contenu mal catégorisé
Étant donné qu'un contenu podcast générique est classifié "Géo-ancré"
Et que le modérateur examine le contenu
Quand le modérateur le reclassifie en "Géo-neutre"
Alors la nouvelle classification est appliquée immédiatement
Et l'algorithme utilise la pondération géo = 0.2
Et le créateur reçoit une notification de reclassification
Scénario: Créateur modifie la classification après publication
Étant donné que j'ai publié un contenu classifié "Géo-contextuel"
Et que je réalise qu'il devrait être "Géo-neutre"
Quand je modifie la classification en "Géo-neutre"
Alors la modification est enregistrée
Et l'algorithme utilise la nouvelle pondération
Et je vois le message "Classification modifiée avec succès"
Scénario: Statistiques de classification dans le profil créateur
Étant donné que je suis un créateur
Et que j'ai publié 30 contenus:
| type | nombre |
| Géo-ancré | 10 |
| Géo-contextuel | 15 |
| Géo-neutre | 5 |
Quand je consulte mes statistiques
Alors je vois la répartition de mes classifications
Et des suggestions pour optimiser la portée
Scénario: Contenu géo-ancré fortement pondéré par la proximité
Étant donné qu'un audio-guide "Géo-ancré" existe à la Tour Eiffel
Et qu'un utilisateur est à 100m de la Tour Eiffel
Quand l'algorithme calcule le score
Alors la pondération géo est de 0.7
Et le score géo est proche de 1 (très proche)
Et le contenu a un score final élevé
Scénario: Contenu géo-neutre moins sensible à la distance
Étant donné qu'un podcast philosophie "Géo-neutre" existe à Paris
Et qu'un utilisateur est à Marseille (750 km)
Quand l'algorithme calcule le score
Alors la pondération géo est de 0.2
Et le score géo est bas (distance élevée)
Mais le score intérêts (0.6) peut compenser
Et le contenu peut quand même être recommandé si intérêts match
Scénario: Comparaison scores entre types géo pour même distance
Étant donné 3 contenus au même endroit (Paris):
| type | ponderation_geo |
| Géo-ancré | 0.7 |
| Géo-contextuel | 0.5 |
| Géo-neutre | 0.2 |
Et qu'un utilisateur est à 50 km de Paris
Quand l'algorithme calcule les scores
Alors le contenu "Géo-ancré" a le score géo le plus élevé
Et le contenu "Géo-neutre" a le score géo le plus faible
Mais peut avoir un score final plus élevé si forte correspondance intérêts

View File

@@ -0,0 +1,100 @@
# language: fr
Fonctionnalité: Gestion du contenu politique (MVP simplifié)
En tant qu'utilisateur
Je veux pouvoir filtrer le contenu politique
Afin de contrôler mon exposition à ce type de contenu
Contexte:
Étant donné que l'API RoadWave est disponible
Scénario: Créateur tagge son contenu comme "Politique"
Étant donné que je suis un créateur connecté
Quand je publie un contenu sur un débat politique
Et que je sélectionne le tag "Politique"
Alors le contenu est enregistré avec le tag "Politique"
Et aucune classification gauche/droite n'est demandée (MVP)
Scénario: Tag "Politique" au même niveau que les autres tags
Étant donné que je crée un contenu
Quand je consulte la liste des tags disponibles
Alors je vois les tags suivants au même niveau:
| tag |
| Économie |
| Sport |
| Culture |
| Politique |
| Automobile |
| Voyage |
| Musique |
Scénario: Par défaut, tous les contenus politiques sont visibles
Étant donné que je suis un nouvel utilisateur
Et que je n'ai pas modifié les paramètres de contenu politique
Quand je demande des recommandations
Alors les contenus tagués "Politique" sont inclus normalement
Et aucun filtrage n'est appliqué
Scénario: Activer le filtrage "Masquer contenu politique"
Étant donné que je suis connecté
Quand j'active l'option "Masquer contenu politique" dans les paramètres
Alors tous les contenus tagués "Politique" sont exclus de mes recommandations
Et je vois le message "Contenu politique masqué"
Scénario: Filtrage politique actif - aucun contenu politique recommandé
Étant donné que j'ai activé "Masquer contenu politique"
Et qu'il existe 100 contenus dont 20 tagués "Politique"
Quand je demande 50 recommandations
Alors je reçois 50 contenus parmi les 80 non-politiques
Et 0% de contenus politiques sont proposés
Scénario: Désactiver le filtrage "Masquer contenu politique"
Étant donné que j'ai activé "Masquer contenu politique"
Quand je désactive cette option dans les paramètres
Alors les contenus politiques sont à nouveau inclus dans mes recommandations
Et le filtrage est levé immédiatement
Scénario: Mode Kids filtre automatiquement le contenu politique
Étant donné que je suis un utilisateur de 14 ans
Et que le mode Kids est activé
Quand je demande des recommandations
Alors tous les contenus tagués "Politique" sont automatiquement exclus
Et ce indépendamment du paramètre "Masquer contenu politique"
Scénario: Statistiques créateur sur contenu politique
Étant donné que je suis un créateur
Et que j'ai publié 20 contenus dont 5 tagués "Politique"
Quand je consulte mes statistiques
Alors je vois le nombre d'utilisateurs ayant masqué le contenu politique
Et le taux d'engagement comparé aux autres tags
Scénario: Recherche avec tag "Politique"
Étant donné que je recherche du contenu
Quand je filtre par tag "Politique"
Alors seuls les contenus tagués "Politique" sont affichés
Et ce même si j'ai activé "Masquer contenu politique" (recherche explicite)
Scénario: Partage de contenu politique avec filtre actif
Étant donné que j'ai activé "Masquer contenu politique"
Et qu'un ami me partage un lien vers un contenu tagué "Politique"
Quand j'ouvre le lien
Alors je peux accéder au contenu (partage explicite)
Et je vois un avertissement "Ce contenu est tagué Politique"
Scénario: Pas de classification gauche/droite en MVP
Étant donné que je suis un créateur
Quand je publie un contenu tagué "Politique"
Alors aucune option de classification idéologique n'est proposée
Et je ne peux pas indiquer "Gauche", "Droite", "Centre", etc.
Scénario: Pas d'équilibrage imposé en MVP
Étant donné qu'un utilisateur écoute majoritairement du contenu politique de gauche
Quand l'algorithme génère des recommandations
Alors aucun équilibrage droite/gauche n'est appliqué
Et les recommandations suivent l'algorithme standard (intérêts, géo, engagement)
Scénario: Notification post-MVP pour classification avancée
Étant donné que RoadWave passe en phase post-MVP
Et que la classification politique avancée est activée
Quand je me connecte
Alors je reçois une notification m'informant des nouvelles options
Et je peux configurer mes préférences d'équilibrage politique

View File

@@ -0,0 +1,327 @@
# language: fr
Fonctionnalité: Contenus géolocalisés en mode voiture
En tant qu'utilisateur en voiture
Je veux recevoir des notifications de contenus géolocalisés au bon moment
Afin de découvrir du contenu contextuel sans distraction au volant
Contexte:
Étant donné que l'API RoadWave est disponible
Et que l'application est ouverte (premier plan)
Et que le GPS est activé
Et que l'utilisateur est en mode voiture (vitesse 5 km/h)
# 17.2 - Détection et notification (Calcul ETA)
Scénario: Calcul ETA et notification 7 secondes avant le point GPS
Étant donné qu'un contenu géolocalisé existe à la Tour Eiffel (48.8584, 2.2945)
Et que je me déplace à 50 km/h vers ce point
Et que je suis à 98 mètres du point (ETA = 7 secondes)
Quand le système calcule l'ETA
Alors une notification est déclenchée immédiatement
Et le compteur "7" s'affiche avec l'icône 🏛
Et une notification sonore (bip court) est jouée
Plan du Scénario: Calcul ETA à différentes vitesses
Étant donné qu'un contenu géolocalisé existe à un point GPS
Et que je me déplace à <vitesse> km/h
Quand je suis à <distance> mètres du point
Alors l'ETA calculé est <eta> secondes
Et la notification est déclenchée : <notification>
Exemples:
| vitesse | distance | eta | notification |
| 10 | 19 | 7 | Oui |
| 50 | 98 | 7 | Oui |
| 130 | 252 | 7 | Oui |
| 50 | 200 | 14 | Non |
| 10 | 50 | 18 | Non |
Scénario: Notification immédiate si vitesse <5 km/h ET distance <50m
Étant donné qu'un contenu géolocalisé existe à 30m de ma position
Et que ma vitesse est 3 km/h (arrêté à un feu rouge)
Quand le système détecte cette situation
Alors une notification est déclenchée immédiatement
Et je n'ai pas besoin d'attendre le calcul ETA
# 17.2.2 - Format de notification minimaliste
Scénario: Notification minimaliste sans texte (sécurité routière)
Étant donné qu'une notification géolocalisée est déclenchée
Quand la notification s'affiche
Alors les éléments suivants sont visibles:
| élément | présent |
| Icône du tag | |
| Compteur 71 | |
| Son bref (bip) | |
| Titre texte | |
| Description | |
| Cover image | |
| Bouton Annuler | |
Plan du Scénario: Icônes selon le tag du contenu
Étant donné qu'un contenu géolocalisé avec le tag <tag> est disponible
Quand la notification s'affiche
Alors l'icône <icone> est affichée
Exemples:
| tag | icone |
| Culture générale | 🏛 |
| Histoire | 📜 |
| Voyage | |
| Famille | 👨👩👧 |
| Musique | 🎵 |
| Sport | |
| Technologie | 💻 |
| Automobile | 🚗 |
Scénario: Compteur décrémentant de 7 à 1
Étant donné qu'une notification géolocalisée s'affiche
Quand le compteur démarre
Alors le compteur affiche "7"
Et après 1 seconde, il affiche "6"
Et après 2 secondes, il affiche "5"
Et après 3 secondes, il affiche "4"
Et après 4 secondes, il affiche "3"
Et après 5 secondes, il affiche "2"
Et après 6 secondes, il affiche "1"
Et après 7 secondes, la notification disparaît
# 17.2.2b - Conformité CarPlay / Android Auto
Scénario: Notification sonore uniquement en mode CarPlay
Étant donné que l'application est connectée à CarPlay
Et qu'un contenu géolocalisé est détecté (ETA 7s)
Quand la notification est déclenchée
Alors seule la notification sonore (bip) est jouée
Et aucun overlay visuel n'est affiché (icône, compteur)
Et l'utilisateur peut valider via le bouton "Suivant" au volant
Scénario: Notification sonore uniquement en mode Android Auto
Étant donné que l'application est connectée à Android Auto
Et qu'un contenu géolocalisé est détecté (ETA 7s)
Quand la notification est déclenchée
Alors seule la notification sonore (bip) est jouée
Et aucun overlay visuel n'est affiché
Et l'utilisateur peut valider via le bouton "Suivant" au volant
Scénario: Notification complète (sonore + visuelle) en mode normal
Étant donné que l'application n'est PAS connectée à CarPlay/Android Auto
Et qu'un contenu géolocalisé est détecté
Quand la notification est déclenchée
Alors la notification sonore (bip) est jouée
Et l'overlay visuel s'affiche (icône + compteur 71)
# 17.2.3 - Décompte après validation
Scénario: Validation via bouton "Suivant" et décompte 5 secondes
Étant donné qu'une notification géolocalisée est affichée (compteur à 5)
Et que j'écoute un podcast
Quand j'appuie sur le bouton "Suivant"
Alors le compteur bascule à "5" (décompte final)
Et le contenu actuel continue de jouer normalement
Et le compteur décrémente: 54321
Et après 5 secondes, le contenu géolocalisé démarre (fade in 0.3s)
Scénario: Transition fluide avec fade out/in
Étant donné que le décompte atteint "0"
Quand le contenu géolocalisé doit démarrer
Alors le contenu actuel fait un fade out de 0.3s
Et le contenu géolocalisé fait un fade in de 0.3s
Et il n'y a pas de silence entre les deux
Scénario: Contenu actuel se termine pendant le décompte
Étant donné que j'ai validé la notification (décompte 5s démarre)
Et que mon contenu actuel se termine après 2 secondes
Quand le contenu actuel se termine
Alors le contenu suivant du buffer démarre immédiatement
Et le décompte continue (321)
Et à la fin du décompte, le contenu géolocalisé remplace le buffer
Scénario: Ignorance de la notification (pas de clic pendant 7s)
Étant donné qu'une notification géolocalisée s'affiche (compteur 7)
Quand 7 secondes s'écoulent sans que j'appuie sur "Suivant"
Alors la notification disparaît automatiquement
Et le contenu géolocalisé est perdu (pas d'insertion dans la file)
Et un cooldown de 10 minutes est activé
# 17.3 - Limitation anti-spam
Scénario: Quota de 6 contenus géolocalisés par heure
Étant donné que j'ai validé 6 notifications géolocalisées dans la dernière heure
Quand un 7ème contenu géolocalisé est détecté
Alors aucune notification n'est envoyée
Et le contenu n'est pas inséré dans la file
Scénario: Fenêtre glissante de 60 minutes
Étant donné que j'ai validé 6 contenus géolocalisés
Et que le premier contenu a été validé il y a 61 minutes
Quand un nouveau contenu géolocalisé est détecté
Alors la notification est envoyée (quota libéré : 5/6)
Et le compteur horaire est mis à jour
Plan du Scénario: Gestion du quota horaire
Étant donné que <nb_valides> notifications ont été validées dans la dernière heure
Quand un nouveau contenu géolocalisé est détecté
Alors la notification est <action>
Exemples:
| nb_valides | action |
| 0 | envoyée |
| 3 | envoyée |
| 5 | envoyée |
| 6 | non envoyée |
| 7 | non envoyée |
Scénario: Exception audio-guides multi-séquences (comptent comme 1)
Étant donné que j'ai démarré un audio-guide avec 8 séquences
Et que cet audio-guide compte comme 1 contenu dans le quota
Quand toutes les séquences de l'audio-guide sont lues
Alors mon quota reste à 1/6
Et je peux encore valider 5 contenus géolocalisés simples
# 17.3.2 - Cooldown après ignorance
Scénario: Cooldown de 10 minutes après notification ignorée
Étant donné qu'une notification géolocalisée a été ignorée (pas de clic)
Et qu'un cooldown de 10 minutes est activé
Quand 5 minutes s'écoulent
Et qu'un nouveau contenu géolocalisé est détecté
Alors aucune notification n'est envoyée (cooldown actif)
Scénario: Cooldown expire après 10 minutes
Étant donné qu'un cooldown a été activé il y a 10 minutes
Quand un nouveau contenu géolocalisé est détecté
Alors la notification est envoyée (cooldown expiré)
Scénario: Pas de cooldown si notification validée
Étant donné qu'une notification géolocalisée est affichée
Quand j'appuie sur "Suivant" dans les 7 secondes
Alors aucun cooldown n'est activé
Et la prochaine notification pourra être envoyée normalement
# 17.4 - Navigation avec contenus géolocalisés
Scénario: Contenu géolocalisé dans l'historique de navigation
Étant donné que j'écoute un contenu du buffer
Et que j'ai validé un contenu géolocalisé "Tour Eiffel"
Et que j'ai écouté 42 secondes du contenu géolocalisé
Quand j'appuie sur "Suivant" (skip)
Et que j'appuie ensuite sur "Précédent"
Alors le contenu géolocalisé reprend à 42 secondes
Scénario: Contenu ignoré n'entre pas dans l'historique
Étant donné qu'une notification géolocalisée a été ignorée
Quand j'appuie sur "Précédent"
Alors le contenu géolocalisé ignoré n'apparaît PAS dans l'historique
Et je reviens au contenu d'avant
Scénario: Skip pendant le décompte annule l'insertion
Étant donné que j'ai validé une notification (décompte 5s en cours)
Et que le compteur affiche "3"
Quand j'appuie à nouveau sur "Suivant"
Alors le décompte est annulé
Et le contenu suivant du buffer démarre
Et le contenu géolocalisé n'entre PAS dans l'historique
# 17.5 - Basculement automatique voiture ↔ piéton
Scénario: Détection mode piéton (vitesse <5 km/h stable 10s)
Étant donné que je suis en mode voiture
Et que ma vitesse passe à 3 km/h
Quand cette vitesse reste stable pendant 10 secondes
Alors le mode piéton est activé automatiquement
Et les notifications passent en mode push arrière-plan (si permission accordée)
Scénario: Détection mode voiture (vitesse ≥5 km/h stable 10s)
Étant donné que je suis en mode piéton
Et que ma vitesse passe à 15 km/h
Quand cette vitesse reste stable pendant 10 secondes
Alors le mode voiture est activé automatiquement
Et les notifications passent en mode sonore + icône (app premier plan requise)
Scénario: Hysteresis pour éviter basculements intempestifs
Étant donné que ma vitesse passe de 20 km/h à 3 km/h (arrêt feu rouge)
Et que ma vitesse remonte à 20 km/h après 8 secondes
Quand le système vérifie le mode
Alors aucun basculement n'a lieu (hysteresis de 10s non atteinte)
Et je reste en mode voiture
Plan du Scénario: Effets du basculement voiture → piéton
Étant donné que je bascule de voiture à piéton
Quand le basculement est effectué
Alors les paramètres suivants changent:
| paramètre | voiture | piéton |
| App requise | Premier plan | Arrière-plan OK |
| Notification | Sonore + icône + compteur| Push système |
| Rayon détection | ETA 7s (variable) | 200m fixes |
| Type contenu | Tous géolocalisés | Audio-guides uniquement |
# 17.6 - Edge cases
Scénario: Haute vitesse (130 km/h sur autoroute)
Étant donné que je roule à 130 km/h (36.1 m/s)
Et qu'un contenu géolocalisé est à 252 mètres
Quand l'ETA de 7s est atteint
Et que je valide la notification
Alors le décompte 5s démarre
Et le contenu géolocalisé démarre encore avant le point GPS (72m avant)
Scénario: Multiples points géolocalisés proches (route touristique)
Étant donné que 3 châteaux sont espacés de 800m chacun
Et que je valide la notification du Château A
Quand j'arrive près du Château B (57s plus tard à 50 km/h)
Alors la notification du Château B est envoyée (quota 2/6, pas de cooldown)
Scénario: Mode stationnement (vitesse <1 km/h pendant 2 min)
Étant donné que je me gare à 30m d'un château
Et que ma vitesse est <1 km/h pendant 2 minutes
Quand le mode stationnement est détecté
Alors aucune notification de contenu géolocalisé n'est envoyée
Et le système bascule automatiquement en mode piéton
Scénario: Reprise conduite après stationnement
Étant donné que je suis en mode stationnement
Et que ma vitesse passe à 20 km/h pendant 10 secondes
Quand le système détecte la reprise de conduite
Alors le mode voiture est réactivé
Et les notifications géolocalisées reprennent (si quota non atteint)
# Distinction contenus géolocalisés simples vs audio-guides
Scénario: Contenu géolocalisé simple (1 séquence unique)
Étant donné qu'un contenu géolocalisé simple existe à un point GPS
Quand la notification est déclenchée (ETA 7s)
Et que je valide
Alors le contenu démarre après décompte 5s
Et à la fin du contenu, le buffer normal reprend
Et ce contenu compte 1/6 dans le quota
Scénario: Audio-guide multi-séquences (2+ séquences enchaînées)
Étant donné qu'un audio-guide avec 8 séquences existe
Quand je démarre l'audio-guide
Et que les séquences s'enchaînent automatiquement (GPS ou manuel)
Alors l'audio-guide entier compte 1/6 dans le quota
Et les séquences ne déclenchent PAS de notification avec compteur 7s
Et elles se déclenchent au point GPS exact (rayon 30m)
# Gestion erreurs
Scénario: GPS désactivé en mode voiture
Étant donné que je suis en mode voiture
Quand le GPS est désactivé
Alors aucune notification géolocalisée ne peut être envoyée
Et un message d'erreur s'affiche: "GPS requis pour les contenus géolocalisés"
Scénario: App en arrière-plan en mode voiture
Étant donné que je suis en mode voiture
Et que l'app passe en arrière-plan
Quand un contenu géolocalisé est détecté
Alors aucune notification n'est envoyée (app premier plan requise)
Et le contenu n'est pas perdu (sera proposé si app rouverte dans le rayon)
Scénario: Permission "Always Location" refusée (mode piéton indisponible)
Étant donné que je refuse la permission "Always Location"
Quand ma vitesse passe <5 km/h
Alors le mode piéton n'est PAS activé
Et le mode voiture reste actif (avec permission "When In Use")
Et aucune notification arrière-plan n'est envoyée

View File

@@ -0,0 +1,140 @@
# language: fr
Fonctionnalité: Gestion de l'historique et reproposition
En tant que système de recommandation
Je veux gérer l'historique d'écoute intelligemment
Afin d'éviter les répétitions et offrir une découverte maximale
Contexte:
Étant donné que l'API RoadWave est disponible
Scénario: Contenu écouté complètement (>80%) - jamais reproposé
Étant donné qu'un utilisateur a écouté un contenu à 85%
Quand l'algorithme génère les recommandations
Alors ce contenu n'est jamais reproposé
Et il est marqué comme "écouté" dans l'historique
Scénario: Contenu écouté à 80% exactement - jamais reproposé
Étant donné qu'un utilisateur a écouté un contenu exactement à 80%
Quand l'algorithme génère les recommandations
Alors ce contenu n'est pas reproposé (seuil >= 80%)
Scénario: Contenu skippé rapidement (<10s) - ne pas reproposer
Étant donné qu'un utilisateur a skippé un contenu après 8 secondes
Quand l'algorithme génère les recommandations
Alors ce contenu n'est pas reproposé (signal négatif fort)
Et la jauge d'intérêt correspondante est réduite de 0.5%
Scénario: Contenu skippé exactement à 10s - ne pas reproposer
Étant donné qu'un utilisateur a skippé un contenu après exactement 10 secondes
Quand l'algorithme génère les recommandations
Alors ce contenu n'est pas reproposé (seuil < 10s strict)
Scénario: Contenu partiellement écouté (10-80%) - reproposer avec reprise
Étant donné qu'un utilisateur a écouté un contenu à 45%
Et qu'il est arrivé à la position 2:30 (150 secondes)
Quand l'algorithme propose à nouveau ce contenu
Alors le contenu peut être reproposé
Et la position de reprise est 150 secondes
Et l'utilisateur voit "Reprendre à 2:30"
Scénario: Contenu écouté à 11% - reproposition possible
Étant donné qu'un utilisateur a écouté un contenu à 11%
Quand l'algorithme génère les recommandations
Alors ce contenu peut être reproposé (>10%)
Et la position de reprise est sauvegardée
Scénario: Contenu écouté à 79% - reproposition possible
Étant donné qu'un utilisateur a écouté un contenu à 79%
Quand l'algorithme génère les recommandations
Alors ce contenu peut être reproposé (<80%)
Et l'utilisateur peut terminer l'écoute
Scénario: Audio-guide avec flag replayable=true
Étant donné qu'un audio-guide a le flag "replayable = true"
Et qu'un utilisateur l'a écouté à 95%
Quand l'algorithme génère les recommandations
Alors l'audio-guide peut être reproposé
Et il est marqué comme "Écouté - Rejouable"
Scénario: Podcast standard sans flag replayable
Étant donné qu'un podcast n'a pas de flag replayable
Et qu'un utilisateur l'a écouté à 90%
Quand l'algorithme génère les recommandations
Alors le podcast n'est jamais reproposé
Scénario: Stockage dans user_content_history
Étant donné qu'un utilisateur écoute un contenu
Quand l'écoute se termine ou est skippée
Alors les données suivantes sont enregistrées:
| champ | exemple |
| user_id | user-123 |
| content_id | content-456 |
| completion_rate | 0.45 (45%) |
| last_position | 150 (secondes) |
| listened_at | 2026-01-21 14:30:00 |
Scénario: Historique illimité stocké
Étant donné qu'un utilisateur a écouté 5000 contenus
Quand il consulte son historique
Alors tous les 5000 contenus sont disponibles
Et aucun contenu n'est supprimé automatiquement
Scénario: Algorithme considère les 100 derniers pour performance
Étant donné qu'un utilisateur a écouté 500 contenus
Quand l'algorithme génère les recommandations
Alors il vérifie uniquement les 100 derniers contenus pour exclusion
Et cette limite est une optimisation de requête SQL
Scénario: Export historique complet (RGPD)
Étant donné qu'un utilisateur demande l'export RGPD
Quand l'export est généré
Alors l'historique complet est inclus avec:
| information | inclus |
| Tous les contenus | |
| Dates d'écoute | |
| Taux complétion | |
| Positions reprise | |
Scénario: Reprise automatique d'un contenu partiellement écouté
Étant donné que j'ai écouté un podcast à 60% (position 10:00)
Quand ce podcast est reproposé par l'algorithme
Et que je lance la lecture
Alors l'écoute reprend automatiquement à 10:00
Et je vois une notification "Reprise à 10:00"
Scénario: Option "Reprendre du début" pour contenu partiellement écouté
Étant donné que j'ai écouté un podcast à 60%
Quand ce podcast est reproposé
Alors je vois deux options:
| option | action |
| Reprendre à 10:00 | Lecture à partir de 10:00 |
| Depuis le début | Lecture à partir de 0:00 |
Scénario: Contenu écouté il y a 6 mois - toujours en historique
Étant donné qu'un utilisateur a écouté un contenu il y a 6 mois à 90%
Quand l'algorithme génère les recommandations
Alors ce contenu n'est toujours pas reproposé
Et l'historique n'a pas de limite temporelle
Scénario: Nouveau contenu du même créateur après écoute complète
Étant donné qu'un utilisateur a écouté un contenu de "Créateur A" à 90%
Et que "Créateur A" publie un nouveau contenu
Quand l'algorithme génère les recommandations
Alors le nouveau contenu peut être recommandé
Et seul l'ancien contenu est exclu (pas tout le créateur)
Scénario: Statistiques personnelles d'historique
Étant donné que je consulte mon profil
Quand j'accède à la section "Historique"
Alors je vois:
| métrique | exemple |
| Nombre total d'écoutes | 1,234 |
| Heures écoutées | 456h |
| Taux complétion moyen | 72% |
| Top 5 catégories | Voyage, Sport |
Scénario: Filtrer l'historique par date
Étant donné que je consulte mon historique
Quand je filtre par "Dernière semaine"
Alors seuls les contenus écoutés dans les 7 derniers jours sont affichés
Et je peux exporter cette sélection

View File

@@ -0,0 +1,162 @@
# language: fr
Fonctionnalité: Médias traditionnels sur RoadWave
En tant que média établi
Je veux publier du contenu géolocalisé sur RoadWave
Afin d'atteindre une audience locale et mobile
Contexte:
Étant donné que l'API RoadWave est disponible
Scénario: Création d'un compte média vérifié
Étant donné que je représente Le Monde
Quand je crée un compte média
Et que je fournis les justificatifs (SIRET, documents officiels)
Alors mon compte est créé en attente de vérification
Et l'équipe RoadWave examine ma demande sous 48-72h
Scénario: Validation compte média par l'équipe RoadWave
Étant donné qu'un compte média "Le Parisien" est en attente
Quand l'équipe RoadWave valide le compte
Alors le compte reçoit le badge vérifié
Et le média peut publier sans validation des 3 premiers contenus
Et je vois le message "Compte média vérifié avec succès"
Scénario: Badge vérifié visible sur profil média
Étant donné que "France Inter" a un compte vérifié
Quand un utilisateur consulte le profil
Alors il voit le badge à côté du nom
Et une mention "Média vérifié"
Scénario: Pas de validation des 3 premiers contenus pour médias
Étant donné que je suis un média vérifié
Quand je publie mon premier contenu
Alors le contenu est publié immédiatement sans validation
Et il est visible pour tous les utilisateurs
Et je ne passe pas par la modération initiale
Scénario: Modération a posteriori uniquement
Étant donné que "Libération" publie un contenu
Quand le contenu est publié
Alors il est immédiatement disponible
Mais peut être signalé et modéré a posteriori
Et suit les mêmes règles de modération que les créateurs
Scénario: Publication flash info géolocalisé
Étant donné que je suis "Ouest-France" (média régional)
Quand je publie un flash info sur un événement à Rennes
Et que je le géolocalise en Bretagne (géo-contextuel)
Alors le contenu est publié immédiatement
Et il est recommandé aux utilisateurs en Bretagne
Scénario: Publication chronique thématique
Étant donné que je suis "France Culture"
Quand je publie une chronique philosophie (géo-neutre)
Alors le contenu est disponible partout en France
Et suit l'algorithme de recommandation standard
Scénario: Publication édito politique
Étant donné que je suis "Le Figaro"
Quand je publie un édito politique
Et que je le tague "Politique"
Alors le contenu est publié immédiatement
Et la classification politique MVP s'applique (pas gauche/droite)
Et les utilisateurs ayant activé "Masquer politique" ne le voient pas
Scénario: Formats de contenu autorisés pour médias
Étant donné que je suis un média vérifié
Quand je publie du contenu
Alors je peux publier:
| format | exemple |
| Flash info géolocalisé | Actualité régionale 2-5 min |
| Chronique thématique | Culture, économie, sport 5-15min|
| Édito et débats | Opinion 10-30 min |
| Reportage | Investigation 15-45 min |
Scénario: Médias suivent les règles standard de classification âge
Étant donné que je suis "RTL"
Quand je publie un contenu sensible
Alors je dois obligatoirement classifier par âge:
| classification | type contenu |
| Tout public | Info générale |
| 13+ | Actualité avec sujets sensibles |
| 16+ | Débats avec violence verbale |
| 18+ | Sujets adultes |
Scénario: Monétisation médias - partage revenus pub standard
Étant donné que je suis un média vérifié
Et que mes contenus génèrent des écoutes
Quand le mois se termine
Alors je reçois 3 / 1000 écoutes complètes (même taux que créateurs)
Et le paiement suit les mêmes règles (seuil 50, mensuel)
Scénario: Sponsoring direct non géré par plateforme
Étant donné que je suis "Europe 1"
Et que je veux intégrer un sponsor dans mon contenu
Quand je mentionne le sponsor dans l'audio
Alors c'est autorisé (sponsoring éditorial)
Mais RoadWave ne gère pas la transaction
Et je gère la relation sponsor directement
Scénario: Médias peuvent avoir plusieurs comptes créateurs
Étant donné que je suis "Le Monde"
Quand je veux créer des sous-comptes par rubrique
Alors je peux créer:
| compte | description |
| @lemonde_politique | Actualité politique |
| @lemonde_economie | Économie et entreprises |
| @lemonde_culture | Culture et spectacles |
Et tous sont liés au compte média principal
Scénario: Médias régionaux privilégiés localement
Étant donné que "Sud-Ouest" publie du contenu géo-contextuel en Nouvelle-Aquitaine
Et qu'un utilisateur est à Bordeaux
Quand l'algorithme calcule les recommandations
Alors le contenu de "Sud-Ouest" a un score géo élevé
Et il est privilégié pour l'audience locale
Scénario: Médias nationaux accessibles partout
Étant donné que "France Inter" publie un podcast géo-neutre
Quand des utilisateurs à Paris, Lyon, Marseille demandent des recommandations
Alors le podcast est accessible partout sans distinction géographique
Et suit l'algorithme de recommandation standard
Scénario: Statistiques détaillées pour médias
Étant donné que je suis un média vérifié
Quand je consulte mes statistiques
Alors je vois:
| métrique | exemple |
| Écoutes par région | Île-de-France: 45% |
| Taux complétion | 72% |
| Démographie auditeurs | 25-34 ans: 35% |
| Top contenus | Flash info Paris|
| Revenus générés | 1,234 |
Scénario: Médias peuvent exporter analytics
Étant donné que je suis "Libération"
Quand je clique sur "Exporter analytics"
Alors je reçois un CSV avec données détaillées
Et je peux analyser les données avec mes outils internes
Scénario: Contact prioritaire équipe RoadWave
Étant donné que je suis un média vérifié
Quand j'ai un problème technique ou question
Alors je peux contacter le support média prioritaire
Et j'obtiens une réponse sous 24h (vs 48-72h standard)
Scénario: Médias peuvent programmer la publication
Étant donné que je suis "France Culture"
Quand je prépare un contenu à l'avance
Alors je peux programmer la publication pour une date/heure future
Et le contenu sera publié automatiquement au moment choisi
Scénario: API dédiée pour médias (post-MVP)
Étant donné que je suis un grand média avec beaucoup de contenus
Quand RoadWave développe l'API médias
Alors je peux automatiser la publication via API
Et intégrer RoadWave dans mon workflow de production
Scénario: Signalement d'un contenu média traité en priorité
Étant donné qu'un contenu de "Le Monde" est signalé
Quand le signalement arrive en modération
Alors il est traité avec la même priorité qu'un créateur standard
Et le badge vérifié ne donne pas d'immunité modération

View File

@@ -0,0 +1,115 @@
# language: fr
Fonctionnalité: Mode Kids pour utilisateurs 13-15 ans
En tant que parent ou adolescent
Je veux activer un mode Kids avec filtrage de contenu
Afin de protéger les mineurs des contenus inappropriés
Contexte:
Étant donné que l'API RoadWave est disponible
Scénario: Activation manuelle du mode Kids
Étant donné que je suis un utilisateur de 14 ans
Et que le mode Kids n'est pas activé par défaut
Quand j'active le mode Kids dans les paramètres
Alors le mode Kids est activé sur mon compte
Et je vois le message "Mode Kids activé - Contenus filtrés pour 13-15 ans"
Scénario: Parent active le mode Kids pour son enfant
Étant donné que je suis le parent d'un utilisateur de 13 ans
Et que j'ai accès au compte de mon enfant
Quand j'active le mode Kids
Alors le mode Kids est activé sur le compte enfant
Et seuls les contenus "Tous publics" sont accessibles
Scénario: Filtrage contenu - uniquement "Tous publics"
Étant donné que le mode Kids est activé sur mon compte
Et qu'il existe des contenus avec les classifications:
| classification | nombre |
| Tous publics | 100 |
| 13+ | 50 |
| 16+ | 30 |
| 18+ | 20 |
Quand je demande des recommandations
Alors seuls les 100 contenus "Tous publics" sont proposés
Et les contenus 13+, 16+, 18+ sont exclus
Scénario: Exclusion automatique du contenu politique
Étant donné que le mode Kids est activé
Et qu'il existe 20 contenus "Tous publics" dont 5 tagués "Politique"
Quand je demande des recommandations
Alors seuls les 15 contenus non-politiques sont proposés
Et les 5 contenus politiques sont automatiquement exclus
Scénario: Pas de publicité en mode Kids
Étant donné que le mode Kids est activé
Et que je suis un utilisateur gratuit
Quand j'écoute du contenu
Alors aucune publicité n'est diffusée
Et je n'ai pas d'insertion publicitaire (règle 1/5 désactivée)
Scénario: Publicité validée manuellement en mode Kids (post-MVP)
Étant donné que le mode Kids est activé
Et qu'une publicité a été validée manuellement pour le mode Kids
Quand j'écoute du contenu
Alors cette publicité peut être diffusée
Mais la fréquence reste inférieure au mode standard
Scénario: Interface standard même en mode Kids
Étant donné que le mode Kids est activé
Quand j'ouvre l'application
Alors l'interface est identique au mode normal
Et seul le filtrage de contenu est actif (pas d'UI enfant)
Scénario: Désactivation du mode Kids
Étant donné que le mode Kids est activé
Quand je désactive le mode Kids dans les paramètres
Alors tous les contenus sont à nouveau accessibles selon mon âge
Et je vois le message "Mode Kids désactivé"
Scénario: Utilisateur 16 ans ne peut pas activer le mode Kids 13-15 ans
Étant donné que je suis un utilisateur de 16 ans
Quand j'essaie d'activer le mode Kids
Alors l'activation réussit
Et le mode Kids filtre les contenus 16+ et 18+ (pas seulement 13+)
Et je vois uniquement les contenus "Tous publics"
Scénario: Tentative d'accès direct à contenu 16+ en mode Kids
Étant donné que le mode Kids est activé
Et qu'un ami me partage un contenu 16+
Quand j'essaie d'accéder au contenu via le lien
Alors l'accès est refusé
Et je vois le message "Ce contenu n'est pas accessible en mode Kids"
Scénario: Recherche en mode Kids filtre automatiquement
Étant donné que le mode Kids est activé
Quand je recherche "débat"
Alors seuls les contenus "Tous publics" apparaissent dans les résultats
Et les contenus 13+, 16+, 18+ sont exclus de la recherche
Scénario: Audio-guide en mode Kids
Étant donné que le mode Kids est activé
Et qu'un audio-guide "Tous publics" existe au musée du Louvre
Quand je suis à proximité du Louvre
Alors l'audio-guide est proposé normalement
Et toutes les séquences sont accessibles
Scénario: Statistiques créateur - audience mode Kids
Étant donné que je suis un créateur
Et que mes contenus "Tous publics" sont écoutés par des utilisateurs mode Kids
Quand je consulte mes statistiques
Alors je vois le pourcentage d'écoutes en mode Kids
Et je peux adapter mes contenus en conséquence
Scénario: Notification lors de l'activation du mode Kids
Quand j'active le mode Kids
Alors je reçois une notification explicative:
| information | description |
| Contenu | Seuls les contenus "Tous publics" accessibles |
| Politique | Contenus politiques automatiquement masqués |
| Publicité | Aucune publicité affichée |
Scénario: Badge mode Kids visible dans le profil
Étant donné que le mode Kids est activé
Quand je consulte mon profil
Alors je vois un badge "Mode Kids actif 🛡"
Et je peux le désactiver en un clic

View File

@@ -0,0 +1,163 @@
# language: fr
Fonctionnalité: Paramétrabilité admin et A/B testing
En tant qu'administrateur RoadWave
Je veux configurer les paramètres de l'algorithme à chaud
Afin d'optimiser l'engagement sans redéploiement
Contexte:
Étant donné que l'API RoadWave est disponible
Et que je suis connecté en tant qu'admin
Scénario: Accès au dashboard admin
Quand j'accède au dashboard admin
Alors je vois tous les paramètres configurables de l'algorithme
Et je vois les valeurs actuelles et par défaut
Scénario: Modifier le poids géo pour contenu ancré
Étant donné que le poids_geo_ancre est à 0.7 (défaut)
Quand je modifie le poids_geo_ancre à 0.8
Et que je sauvegarde
Alors la nouvelle valeur est appliquée immédiatement
Et tous les nouveaux calculs utilisent 0.8
Et je vois le message "Paramètre mis à jour avec succès"
Scénario: Validation des plages de valeurs
Quand j'essaie de configurer poids_geo_ancre à 1.5 (hors plage 0.5-1.0)
Alors la modification échoue
Et je vois le message "Valeur hors plage autorisée (0.5 - 1.0)"
Plan du Scénario: Modification de tous les paramètres configurables
Quand je modifie "<parametre>" à "<nouvelle_valeur>"
Alors la modification est appliquée immédiatement
Et la nouvelle valeur respecte la plage "<plage>"
Exemples:
| parametre | nouvelle_valeur | plage |
| poids_geo_ancre | 0.8 | 0.5 - 1.0 |
| poids_geo_contextuel | 0.6 | 0.3 - 0.7 |
| poids_geo_neutre | 0.3 | 0.0 - 0.4 |
| poids_engagement | 0.3 | 0.0 - 0.5 |
| part_aleatoire_global | 0.15 | 0.0 - 0.3 |
| distance_max_km | 150 | 50 - 500 |
| rayon_gps_point_m | 1000 | 100 - 2000 |
| seuil_min_ecoutes_engagement | 100 | 10 - 200 |
Scénario: Aucun recalcul batch après modification
Étant donné que le poids_geo_ancre est modifié de 0.7 à 0.8
Quand la modification est appliquée
Alors aucun recalcul batch n'est lancé (économie CPU)
Et seuls les nouveaux calculs utilisent la valeur 0.8
Scénario: Versioning des configurations
Étant donné que je modifie plusieurs paramètres
Quand je sauvegarde la configuration
Alors une nouvelle version est créée (ex: v1.2.3)
Et je peux voir l'historique des versions
Et je peux comparer deux versions
Scénario: Rollback en 1 clic
Étant donné que la configuration actuelle est v1.2.3
Et que la version précédente était v1.2.2
Quand je clique sur "Restaurer v1.2.2"
Alors tous les paramètres de v1.2.2 sont réappliqués
Et je vois le message "Configuration restaurée à v1.2.2"
Scénario: Créer une variante A/B testing
Quand je crée une nouvelle variante "Test engagement élevé"
Et que je configure:
| parametre | valeur |
| poids_engagement | 0.4 |
| poids_geo_ancre | 0.6 |
Et que je lance le test A/B
Alors 50% des utilisateurs reçoivent la Config A (défaut)
Et 50% des utilisateurs reçoivent la Config B (test)
Scénario: Split utilisateurs aléatoire pour A/B test
Étant donné qu'un test A/B est actif
Quand 1000 nouveaux utilisateurs se connectent
Alors environ 500 sont assignés à la Config A
Et environ 500 sont assignés à la Config B
Et l'assignation est aléatoire et équilibrée
Scénario: Utilisateur reste dans la même variante
Étant donné qu'un utilisateur est assigné à la Config B
Quand il se reconnecte plusieurs fois
Alors il reste toujours dans la Config B
Et il ne change pas de variante pendant le test
Scénario: Métriques comparatives A/B testing
Étant donné qu'un test A/B est actif depuis 7 jours
Quand je consulte le dashboard A/B testing
Alors je vois les métriques suivantes pour chaque config:
| metrique | Config A | Config B |
| Taux complétion moyen | 68% | 72% |
| Engagement (likes) | 15% | 18% |
| Durée session moyenne | 23 min | 27 min |
| Taux skip rapide (<10s) | 12% | 9% |
Scénario: Dashboard graphique temps réel
Étant donné qu'un test A/B est actif
Quand je consulte le dashboard
Alors je vois des graphiques temps réel:
| graphique | type |
| Évolution engagement | Ligne |
| Répartition utilisateurs| Camembert |
| Taux complétion | Barres |
| Durée session | Ligne |
Scénario: Terminer un test A/B et appliquer la meilleure config
Étant donné qu'un test A/B montre que Config B est meilleure
Quand je clique sur "Appliquer Config B pour tous"
Alors la Config B devient la configuration par défaut
Et tous les utilisateurs utilisent maintenant Config B
Et l'ancien test est archivé
Scénario: Audit engagement global
Quand je consulte la section "Audit engagement"
Alors je vois:
| metrique | valeur |
| Temps écoute moyen/session | 25 min |
| Temps écoute médian/session | 18 min |
| Taux complétion moyen | 70% |
| % sessions avec 1 like | 35% |
Scénario: Graphiques évolution engagement selon config
Étant donné que plusieurs modifications de config ont été faites
Quand je consulte les graphiques d'évolution
Alors je vois l'impact de chaque changement de config
Et je peux corréler changements config avec métriques
Scénario: Export CSV pour analyse externe
Quand je clique sur "Exporter données"
Alors je peux télécharger un CSV avec:
| colonne | exemple |
| date | 2026-01-21 |
| version_config | v1.2.3 |
| taux_completion | 0.72 |
| engagement_moyen | 0.45 |
| duree_session_min | 27 |
Scénario: Alerte automatique si métrique critique baisse
Étant donné que le taux de complétion moyen est à 70%
Quand une nouvelle config fait baisser le taux à 55%
Alors je reçois une alerte email "Baisse critique du taux de complétion"
Et je peux rollback rapidement
Scénario: Prévisualisation impact avant application
Étant donné que je modifie poids_geo_ancre de 0.7 à 0.9
Quand je clique sur "Prévisualiser impact"
Alors je vois une simulation sur échantillon de 1000 utilisateurs
Et je vois l'estimation d'impact sur les métriques clés
Scénario: Notes et commentaires sur modifications config
Quand je modifie une configuration
Alors je peux ajouter une note "Test pour améliorer contenu local"
Et cette note est visible dans l'historique des versions
Et l'équipe peut comprendre le contexte des changements
Scénario: Permissions admin pour modification config
Étant donné que je suis un admin junior
Quand j'essaie de modifier un paramètre critique
Alors l'accès est refusé
Et je vois "Permission admin senior requise"
Et seuls les admins seniors peuvent modifier les paramètres

View File

@@ -0,0 +1,188 @@
# language: fr
Fonctionnalité: Paramétrabilité utilisateur et profils
En tant qu'utilisateur
Je veux personnaliser mon expérience de recommandation
Afin d'adapter l'application à mes différents contextes d'usage
Contexte:
Étant donné que l'API RoadWave est disponible
Et que je suis connecté
Scénario: Accès aux paramètres de personnalisation
Quand j'ouvre les paramètres de personnalisation
Alors je vois trois curseurs disponibles:
| curseur | description |
| Géolocalisation | Local slider National |
| Découverte | 0% slider 50% |
| Politique | Masquer / Équilibré / Mes préférences |
Scénario: Modifier le curseur Géolocalisation vers Local
Étant donné que le curseur Géolocalisation est au centre (défaut)
Quand je déplace le curseur vers "Local" (gauche)
Alors l'algorithme privilégie fortement les contenus proches
Et la pondération géographique augmente
Et je vois le message "Recommandations locales privilégiées"
Scénario: Modifier le curseur Géolocalisation vers National
Étant donné que le curseur Géolocalisation est au centre
Quand je déplace le curseur vers "National" (droite)
Alors l'algorithme privilégie la découverte nationale
Et la pondération géographique diminue
Et je reçois des contenus de toute la France
Scénario: Curseur Découverte à 0% - aucun aléatoire
Quand je règle le curseur Découverte à 0%
Alors 0% de contenus aléatoires dans mes recommandations
Et 100% de contenus calculés selon score combiné
Et je vois le message "Personnalisation maximale"
Scénario: Curseur Découverte à 10% - défaut équilibré
Quand je règle le curseur Découverte à 10%
Alors 10% de contenus aléatoires
Et 90% de contenus calculés
Et je vois le message "Équilibre découverte/personnalisation"
Scénario: Curseur Découverte à 30% - découverte élevée
Quand je règle le curseur Découverte à 30%
Alors 30% de contenus aléatoires
Et 70% de contenus calculés
Et je vois le message "Découverte élevée activée"
Scénario: Curseur Découverte à 50% - découverte maximale
Quand je règle le curseur Découverte à 50%
Alors 50% de contenus aléatoires
Et 50% de contenus calculés
Et je vois le message "Découverte maximale (équivaut à national)"
Scénario: Créer un profil personnalisé "Trajet quotidien"
Quand je crée un nouveau profil nommé "🚗 Trajet quotidien"
Et que je configure:
| parametre | valeur |
| Géolocalisation | Local |
| Découverte | 5% |
| Politique | Masquer |
Et que je sauvegarde
Alors le profil "🚗 Trajet quotidien" est créé
Et je peux l'activer en un clic
Scénario: Créer un profil "Road trip"
Quand je crée un profil "🛣 Road trip"
Et que je configure:
| parametre | valeur |
| Géolocalisation | Régional |
| Découverte | 30% |
| Politique | Équilibré |
Alors le profil est sauvegardé
Et je peux switcher entre profils facilement
Scénario: Créer un profil "Enfants"
Quand je crée un profil "👶 Enfants"
Et que j'active le Mode Kids
Alors tous les paramètres sont adaptés pour enfants:
| parametre | valeur |
| Mode Kids | Activé |
| Politique | Masquer (forcé) |
| Publicité | Aucune |
Scénario: Activer un profil existant
Étant donné que j'ai créé un profil "🚗 Trajet quotidien"
Quand je clique sur "Activer" pour ce profil
Alors tous les paramètres du profil sont appliqués
Et je vois le message "Profil 'Trajet quotidien' activé"
Et l'algorithme utilise ces paramètres immédiatement
Scénario: Synchronisation profils entre devices
Étant donné que j'ai créé 3 profils sur mon iPhone
Quand je me connecte sur mon iPad
Alors mes 3 profils sont automatiquement synchronisés
Et je peux les utiliser sur l'iPad
Scénario: Modification d'un profil synchronisée
Étant donné que j'ai un profil "Road trip" sur iPhone
Quand je modifie ce profil sur iPhone
Alors la modification est synchronisée sur tous mes devices
Et le profil est mis à jour partout en temps réel
Scénario: Pas de partage de profils entre utilisateurs
Étant donné que j'ai créé des profils personnalisés
Et que ma conjointe a un compte RoadWave
Quand elle se connecte sur son compte
Alors elle ne voit pas mes profils
Et chaque utilisateur a ses propres profils
Scénario: Auto-switch selon contexte (détection trajet récurrent)
Étant donné que j'utilise toujours le profil "Trajet quotidien"
Et que je pars de chez moi vers mon travail tous les matins à 8h
Quand le système détecte ce trajet récurrent
Alors le profil "Trajet quotidien" est activé automatiquement
Et je reçois une notification "Profil 'Trajet quotidien' activé"
Scénario: Désactiver l'auto-switch
Étant donné que l'auto-switch de profil est actif
Quand je désactive cette option dans les paramètres
Alors les profils ne changent plus automatiquement
Et je dois les activer manuellement
Scénario: Blocage modification si vitesse GPS >10 km/h
Étant donné que je conduis à 50 km/h
Quand j'essaie de modifier un curseur
Alors la modification est bloquée
Et je vois le message "Modification impossible pendant la conduite"
Et je dois m'arrêter ou être passager pour modifier
Scénario: Modification possible si vitesse <10 km/h
Étant donné que je suis arrêté à un feu rouge (5 km/h)
Quand j'essaie de modifier un curseur
Alors la modification est autorisée
Et je peux ajuster les paramètres
Scénario: Warning au lancement app
Quand je lance l'application pour la première fois
Alors je vois un warning "Configurez vos préférences avant de prendre la route"
Et un bouton "Configurer maintenant"
Et je peux accéder rapidement aux paramètres
Scénario: Modification uniquement app arrêtée ou mode passager
Étant donné que je suis passager dans une voiture
Et que le mode passager est activé
Quand j'essaie de modifier les paramètres
Alors la modification est autorisée
Et le blocage vitesse GPS ne s'applique pas
Scénario: Statistiques d'utilisation des profils
Étant donné que j'utilise plusieurs profils
Quand je consulte mes statistiques
Alors je vois:
| metrique | exemple |
| Profil le plus utilisé | Trajet quotidien |
| Heures par profil | 25h / 10h / 5h |
| Dernier profil actif | Road trip |
Scénario: Supprimer un profil
Étant donné que j'ai créé un profil "Test"
Quand je supprime ce profil
Alors le profil est définitivement supprimé
Et je vois le message "Profil 'Test' supprimé"
Et il disparaît de tous mes devices
Scénario: Limite de profils par utilisateur
Étant donné que j'ai créé 10 profils
Quand j'essaie de créer un 11ème profil
Alors la création échoue
Et je vois le message "Maximum 10 profils par utilisateur"
Scénario: Dupliquer un profil existant
Étant donné que j'ai un profil "Trajet quotidien"
Quand je clique sur "Dupliquer"
Alors un nouveau profil "Trajet quotidien (copie)" est créé
Et il a les mêmes paramètres que l'original
Et je peux le modifier indépendamment
Scénario: Réinitialiser un profil aux valeurs par défaut
Étant donné que j'ai modifié un profil
Quand je clique sur "Réinitialiser"
Alors tous les paramètres reviennent aux valeurs par défaut:
| parametre | valeur défaut |
| Géolocalisation | Équilibré |
| Découverte | 10% |
| Politique | Équilibré |

View File

@@ -0,0 +1,180 @@
# 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