refactor(docs): réorganiser la documentation selon principes DDD

Réorganise la documentation du projet selon les principes du Domain-Driven Design (DDD) pour améliorer la cohésion, la maintenabilité et l'alignement avec l'architecture modulaire du backend.

**Structure cible:**
```
docs/domains/
├── README.md (Context Map)
├── _shared/ (Core Domain)
├── recommendation/ (Supporting Subdomain)
├── content/ (Supporting Subdomain)
├── moderation/ (Supporting Subdomain)
├── advertising/ (Generic Subdomain)
├── premium/ (Generic Subdomain)
└── monetization/ (Generic Subdomain)
```

**Changements effectués:**

Phase 1: Création de l'arborescence des 7 bounded contexts
Phase 2: Déplacement des règles métier (01-19) vers domains/*/rules/
Phase 3: Déplacement des diagrammes d'entités vers domains/*/entities/
Phase 4: Déplacement des diagrammes flux/états/séquences vers domains/*/
Phase 5: Création des README.md pour chaque domaine
Phase 6: Déplacement des features Gherkin vers domains/*/features/
Phase 7: Création du Context Map (domains/README.md)
Phase 8: Mise à jour de mkdocs.yml pour la nouvelle navigation
Phase 9: Correction automatique des liens internes (script fix-markdown-links.sh)
Phase 10: Nettoyage de l'ancienne structure (regles-metier/, diagrammes/, features/)

**Configuration des tests:**
- Makefile: godog run docs/domains/*/features/
- scripts/generate-bdd-docs.py: features_dir → docs/domains

**Avantages:**
 Cohésion forte: toute la doc d'un domaine au même endroit
 Couplage faible: domaines indépendants, dépendances explicites
 Navigabilité améliorée: README par domaine = entrée claire
 Alignement code/docs: miroir de backend/internal/
 Onboarding facilité: exploration domaine par domaine
 Tests BDD intégrés: features au plus près des règles métier

Voir docs/REFACTOR-DDD.md pour le plan complet.
This commit is contained in:
jpgiannetti
2026-02-07 17:15:02 +01:00
parent 78422bb2c0
commit 5e5fcf4714
227 changed files with 1413 additions and 1967 deletions

View File

@@ -0,0 +1,205 @@
# language: fr
@api @audio-guides @navigation @ui @mvp
Fonctionnalité: Affichage avancé distance, direction et ETA
En tant qu'utilisateur
Je veux voir la distance, la direction et le temps d'arrivée estimé vers les points d'intérêt
Afin de planifier mon déplacement et anticiper les prochaines séquences
Contexte:
Étant donné que le système affiche les informations suivantes:
| Information | Format | Mise à jour |
| Distance | Mètres / Kilomètres | Temps réel |
| Direction | Boussole + Flèche | Temps réel |
| ETA | Minutes / Heures | Dynamique |
| Vitesse utilisateur | km/h (mode voiture) | Temps réel |
Scénario: Affichage de la distance en mètres pour proximité < 1km
Étant donné un utilisateur "alice@roadwave.fr" en mode piéton
Et elle se trouve à 450m du Panthéon
Quand elle consulte l'écran de l'audio-guide
Alors la distance affichée est: "450 m"
Et la précision de la distance est de ±10m
Et un événement "DISTANCE_DISPLAYED" est enregistré avec unité: "meters", valeur: 450
Et la métrique "distance.displayed.meters" est incrémentée
Scénario: Affichage de la distance en kilomètres pour distance > 1km
Étant donné un utilisateur "bob@roadwave.fr" en mode voiture
Et il se trouve à 12.5 km du Château de Chambord
Quand il consulte l'écran de l'audio-guide
Alors la distance affichée est: "12.5 km"
Et la précision de la distance est de ±100m
Et un événement "DISTANCE_DISPLAYED" est enregistré avec unité: "kilometers", valeur: 12.5
Et la métrique "distance.displayed.kilometers" est incrémentée
Scénario: Mise à jour en temps réel de la distance pendant le déplacement
Étant donné un utilisateur "charlie@roadwave.fr" en mode piéton
Et il marche vers la Sainte-Chapelle initialement à 800m
Quand il marche à une vitesse de 5 km/h
Alors la distance est mise à jour toutes les 2 secondes:
| Temps | Distance affichée |
| T+0s | 800 m |
| T+30s | 760 m |
| T+60s | 720 m |
| T+90s | 680 m |
Et la barre de progression visuelle se remplit progressivement
Et un événement "DISTANCE_UPDATED" est enregistré toutes les 10 secondes
Et la métrique "distance.real_time_updates" est incrémentée
Scénario: Affichage de la direction avec boussole et flèche
Étant donné un utilisateur "david@roadwave.fr" en mode piéton
Et il se trouve face au nord
Et le Panthéon est au sud-est de sa position
Quand il consulte l'écran de l'audio-guide
Alors une boussole s'affiche avec:
| Élément | Affichage |
| Orientation boussole | Nord en haut |
| Flèche vers POI | Pointe vers 135° (sud-est) |
| Angle cardinal | "SE" (sud-est) |
| Rotation dynamique | Suit l'orientation du téléphone|
Et la flèche est colorée selon la distance:
| Distance | Couleur |
| < 100m | Vert |
| 100m - 500m | Orange |
| > 500m | Bleu |
Et un événement "DIRECTION_DISPLAYED" est enregistré avec angle: 135
Et la métrique "direction.displayed" est incrémentée
Scénario: Mise à jour de la direction en temps réel lors de la rotation
Étant donné un utilisateur "eve@roadwave.fr" en mode piéton
Et elle se trouve face au nord avec le Panthéon au sud-est
Quand elle tourne son téléphone vers l'est
Alors la boussole pivote dynamiquement
Et la flèche vers le POI reste fixée sur la direction réelle (135°)
Et l'affichage est fluide à 60 FPS
Et un événement "COMPASS_ROTATED" est enregistré
Et la métrique "compass.rotations" est incrémentée
Scénario: Calcul de l'ETA en mode piéton (vitesse moyenne 5 km/h)
Étant donné un utilisateur "frank@roadwave.fr" en mode piéton
Et il se trouve à 600m du Jardin du Luxembourg
Quand le système calcule l'ETA avec vitesse piéton moyenne: 5 km/h
Alors l'ETA affiché est: "7 min"
Et le calcul utilise la formule: temps = distance / vitesse_moyenne_pieton
Et un événement "ETA_CALCULATED" est enregistré avec mode: "pedestrian", eta: 7
Et la métrique "eta.calculated.pedestrian" est incrémentée
Scénario: Calcul de l'ETA en mode voiture avec vitesse réelle
Étant donné un utilisateur "grace@roadwave.fr" en mode voiture
Et elle se trouve à 15 km du Château de Chenonceau
Et elle roule actuellement à 80 km/h
Quand le système calcule l'ETA
Alors l'ETA affiché est: "11 min"
Et le calcul utilise la vitesse réelle actuelle
Et un événement "ETA_CALCULATED" est enregistré avec mode: "car", vitesse: 80, eta: 11
Et la métrique "eta.calculated.car" est incrémentée
Scénario: Recalcul dynamique de l'ETA en fonction des changements de vitesse
Étant donné un utilisateur "henry@roadwave.fr" en mode voiture
Et l'ETA initial vers le Château d'Amboise est: "15 min" (vitesse: 70 km/h)
Quand il ralentit à 40 km/h à cause d'un bouchon
Alors l'ETA est recalculé et mis à jour: "22 min"
Et une notification discrète s'affiche: "ETA mis à jour : +7 min"
Quand il accélère à nouveau à 90 km/h
Alors l'ETA est recalculé: "12 min"
Et un événement "ETA_UPDATED" est enregistré avec ancienETA: 22, nouveauETA: 12
Et la métrique "eta.recalculated" est incrémentée
Scénario: Affichage du temps d'arrivée absolu en mode voiture
Étant donné un utilisateur "iris@roadwave.fr" en mode voiture
Et il est 14h30
Et l'ETA vers le prochain point est: "25 min"
Quand elle active l'option "Afficher l'heure d'arrivée"
Alors l'affichage change de "25 min" à "Arrivée à 14h55"
Et les deux formats peuvent être basculés par un tap sur l'ETA
Et un événement "ETA_FORMAT_CHANGED" est enregistré avec format: "absolute_time"
Et la métrique "eta.format.absolute" est incrémentée
Scénario: Affichage groupé distance + direction + ETA sur une carte compacte
Étant donné un utilisateur "jack@roadwave.fr" en mode piéton
Et il se trouve à 450m du Panthéon au sud-est
Quand il consulte la carte de l'audio-guide
Alors une carte compacte s'affiche pour chaque point d'intérêt:
| Point d'intérêt | Distance | Direction | ETA |
| Panthéon | 450 m | SE | 5 min |
| Jardin Lux. | 1.2 km | SO | 14 min |
| Sorbonne | 320 m | E | 4 min |
Et les points sont triés par distance (plus proche en premier)
Et un événement "POI_LIST_DISPLAYED" est enregistré
Et la métrique "poi_list.displayed" est incrémentée
Scénario: Indication visuelle "Vous y êtes !" à l'arrivée
Étant donné un utilisateur "kate@roadwave.fr" en mode piéton
Et elle approche du Panthéon
Quand elle entre dans un rayon de 10m du point d'intérêt
Alors l'affichage change de "15 m" à "🎯 Vous y êtes !"
Et une animation de succès est jouée
Et une notification sonore subtile est jouée
Et l'audio de la séquence démarre automatiquement
Et un événement "POI_ARRIVED" est enregistré avec précision: 8m
Et la métrique "poi.arrived" est incrémentée
Scénario: Affichage du trajet à vol d'oiseau vs trajet routier
Étant donné un utilisateur "luke@roadwave.fr" en mode voiture
Et il se trouve à 12 km à vol d'oiseau du Château de Chambord
Mais le trajet routier est de 18 km (détours)
Quand il consulte l'ETA
Alors la distance affichée est celle du trajet routier: "18 km"
Et l'ETA est calculé sur le trajet routier: "15 min"
Et un bouton "Itinéraire" permet de voir le trajet détaillé
Et un événement "ROUTE_DISPLAYED" est enregistré avec routeDistance: 18, airDistance: 12
Et la métrique "route.displayed" est incrémentée
Scénario: Mode d'économie de batterie avec mise à jour moins fréquente
Étant donné un utilisateur "mary@roadwave.fr" avec batterie < 20%
Et le mode économie d'énergie est activé
Quand elle utilise l'audio-guide
Alors la fréquence de mise à jour des distances est réduite:
| Mode normal | Mode économie |
| Toutes les 2s | Toutes les 10s|
Et la précision GPS est réduite (précision: ±30m au lieu de ±10m)
Et un événement "BATTERY_SAVER_ENABLED" est enregistré
Et la métrique "battery_saver.enabled" est incrémentée
Scénario: Affichage de la vitesse actuelle en mode voiture
Étant donné un utilisateur "nathan@roadwave.fr" en mode voiture
Et il roule à 75 km/h
Quand il consulte l'écran de l'audio-guide
Alors sa vitesse actuelle est affichée: "75 km/h"
Et la vitesse est mise à jour en temps réel
Et un événement "SPEED_DISPLAYED" est enregistré avec vitesse: 75
Et la métrique "speed.displayed" est incrémentée
Scénario: Alerte de dépassement de limite de vitesse (optionnel)
Étant donné un utilisateur "olive@roadwave.fr" en mode voiture
Et elle a activé l'option "Alertes de vitesse"
Et la limite de vitesse sur sa route est 80 km/h
Quand elle roule à 95 km/h
Alors une alerte visuelle discrète s'affiche: " 95 km/h (limite: 80)"
Et l'alerte disparaît quand elle ralentit en dessous de 85 km/h
Et un événement "SPEED_LIMIT_EXCEEDED" est enregistré avec vitesse: 95, limite: 80
Et la métrique "speed.limit_exceeded" est incrémentée
Scénario: Indication de zones à forte densité de points d'intérêt
Étant donné un utilisateur "paul@roadwave.fr" en mode piéton
Et il se trouve dans une zone avec 5 points d'intérêt dans un rayon de 200m
Quand il consulte la carte
Alors une indication s'affiche: "Zone dense : 5 points à proximité"
Et les marqueurs sont regroupés en cluster pour éviter la surcharge visuelle
Et en zoomant, le cluster se décompose en marqueurs individuels
Et un événement "HIGH_DENSITY_ZONE_DETECTED" est enregistré avec count: 5
Et la métrique "zones.high_density" est incrémentée
Scénario: Métriques de précision de la localisation GPS
Étant donné un utilisateur "quinn@roadwave.fr" utilisant l'audio-guide
Quand les métriques de précision GPS sont collectées
Alors les indicateurs suivants sont disponibles:
| Métrique | Valeur cible |
| Précision GPS moyenne | ±10m |
| Précision GPS en mode économie | ±30m |
| Fréquence de mise à jour GPS | 1-2 Hz |
| Taux d'erreur de positionnement | < 5% |
| Latence de calcul ETA | < 100ms |
Et les métriques sont exportées vers le système de monitoring
Et des alertes sont déclenchées si précision > ±50m

View File

@@ -0,0 +1,333 @@
# language: fr
Fonctionnalité: Création d'audio-guide multi-séquences
En tant que créateur de contenu
Je veux créer des audio-guides avec plusieurs séquences géolocalisées
Afin d'offrir des expériences guidées adaptées aux différents modes de déplacement
Contexte:
Étant donné que l'application RoadWave est démarrée
Et que le créateur "guide@example.com" est connecté
Et que son compte est vérifié
# 16.1 - Types d'audio-guides et modes de déplacement
Plan du Scénario: Détection automatique du mode selon la vitesse
Étant donné que l'utilisateur se déplace à <vitesse> km/h
Quand la vitesse est calculée sur 30 secondes
Alors le mode <mode> est suggéré automatiquement
Exemples:
| vitesse | mode |
| 3 | Piéton |
| 15 | Vélo |
| 35 | Voiture |
| 50 | Voiture |
Scénario: Suggestion de mode au démarrage avec confirmation
Étant donné qu'un audio-guide "Safari du Paugre" est disponible
Et que l'utilisateur se déplace à 35 km/h
Quand l'audio-guide démarre
Alors une popup s'affiche:
"""
Détection : 🚗 Voiture. Est-ce correct ?
[Oui] [Changer]
"""
Scénario: Changement manuel du mode détecté
Étant donné que le mode "Voiture" est suggéré automatiquement
Quand l'utilisateur clique sur "Changer"
Alors les 4 modes sont proposés:
| mode | emoji |
| Piéton | 🚶 |
| Voiture | 🚗 |
| Vélo | 🚴 |
| Transport | 🚌 |
Plan du Scénario: Caractéristiques par mode de déplacement
Étant donné un audio-guide configuré en mode <mode>
Alors les paramètres suivants sont appliqués:
| paramètre | valeur |
| Vitesse détection | <vitesse_detection> |
| Déclenchement | <declenchement> |
Exemples:
| mode | vitesse_detection | declenchement |
| Piéton | <5 km/h | Manuel (bouton Suivant) |
| Voiture | >10 km/h | Auto GPS + Manuel |
| Vélo | 5-25 km/h | Auto GPS + Manuel |
| Transport | Variable | Auto GPS + Manuel |
# 16.1.2 - Création d'un audio-guide (côté créateur)
Scénario: Accès au formulaire de création d'audio-guide
Étant donné que le créateur est sur son dashboard
Quand il clique sur "Créer un audio-guide"
Alors le formulaire de création s'affiche
Et le wizard guidé en 4 étapes est visible:
| étape | description |
| 1 | Infos générales |
| 2 | Ajout séquences |
| 3 | Preview carte |
| 4 | Validation modération |
Scénario: Étape 1 - Informations générales obligatoires
Étant donné que le créateur est sur l'étape 1 du wizard
Quand il complète le formulaire
Alors les champs suivants sont obligatoires:
| champ | contrainte |
| Titre | 5-100 caractères |
| Description | 10-500 caractères |
| Mode de déplacement | Choix parmi 4 |
| Tags | 1-3 tags |
| Classification âge | Tout public/13+/16+/18+ |
Scénario: Sélection du mode de déplacement
Étant donné que le créateur crée un audio-guide
Quand il sélectionne le mode "🚗 Voiture (GPS auto + manuel)"
Alors le champ "Vitesse recommandée" s'affiche
Et la plage suggérée est "30-50 km/h"
Scénario: Validation du titre
Étant donné que le créateur entre un titre
Quand le titre contient moins de 5 caractères
Alors un message d'erreur "Minimum 5 caractères" s'affiche
Et le bouton "Suivant" est désactivé
Scénario: Validation de la description
Étant donné que le créateur entre une description
Quand la description contient 520 caractères
Alors un message d'erreur "Maximum 500 caractères" s'affiche
Et les 20 caractères en trop sont surlignés en rouge
Scénario: Étape 2 - Ajout de la première séquence
Étant donné que le créateur est sur l'étape 2 "Ajout séquences"
Quand il clique sur "Ajouter séquence"
Alors le formulaire de séquence s'affiche avec:
| champ | requis | note |
| Titre séquence | | 5-80 caractères |
| Audio | | Upload MP3/AAC, max 200 MB |
| Point GPS | * | *Sauf mode piéton |
| Rayon déclenchement | * | *Sauf mode piéton, 10-200m |
Scénario: Ajout du point GPS pour une séquence
Étant donné que le créateur ajoute une séquence en mode "Voiture"
Quand il clique sur "📍 Ajouter point GPS"
Alors une carte s'affiche
Et il peut:
| action |
| Cliquer sur la carte |
| Entrer coordonnées manuelles |
| Utiliser sa position actuelle |
Scénario: Configuration du rayon de déclenchement avec preview
Étant donné qu'un point GPS est défini à (43.1234, 2.5678)
Quand le créateur ajuste le curseur de rayon
Alors le rayon varie de 10m à 200m
Et un cercle visuel est affiché sur la carte
Et la valeur actuelle s'affiche "30m"
Plan du Scénario: Rayon par défaut selon le mode
Étant donné un audio-guide en mode <mode>
Quand le créateur ajoute un point GPS
Alors le rayon par défaut est <rayon_defaut>
Exemples:
| mode | rayon_defaut |
| Voiture | 30m |
| Vélo | 50m |
| Transport | 100m |
Scénario: Suggestion intelligente du rayon
Étant donné un audio-guide en mode "Voiture" avec vitesse recommandée 30 km/h
Quand le créateur ajoute un point GPS
Alors une suggestion s'affiche: "Recommandé : 30m pour voiture à 30 km/h"
Scénario: Upload audio pour une séquence
Étant donné que le créateur crée une séquence "Introduction"
Quand il upload un fichier audio de 5 MB
Alors le fichier est vérifié:
| vérification | règle |
| Format | MP3, AAC, M4A |
| Taille max | 200 MB |
| Durée max | 15 minutes |
Scénario: Ordre des séquences modifiable
Étant donné un audio-guide avec 5 séquences:
| ordre | titre |
| 1 | Introduction |
| 2 | Les lions |
| 3 | Les girafes |
| 4 | Les éléphants |
| 5 | Conclusion |
Quand le créateur glisse "Les éléphants" en position 2
Alors l'ordre devient:
| ordre | titre |
| 1 | Introduction |
| 2 | Les éléphants |
| 3 | Les lions |
| 4 | Les girafes |
| 5 | Conclusion |
Scénario: Nombre minimum de séquences requis
Étant donné un audio-guide avec seulement 1 séquence
Quand le créateur tente de passer à l'étape suivante
Alors un message d'erreur s'affiche: "Minimum 2 séquences requis"
Et le bouton "Suivant" est désactivé
Scénario: Nombre maximum de séquences
Étant donné un audio-guide avec 50 séquences
Quand le créateur tente d'ajouter une 51ème séquence
Alors un message d'erreur s'affiche: "Maximum 50 séquences par audio-guide"
Et le bouton "+ Ajouter séquence" est désactivé
Scénario: Étape 3 - Preview carte avec tracé et points
Étant donné un audio-guide avec 5 séquences géolocalisées
Quand le créateur accède à l'étape 3 "Preview carte"
Alors une carte Leaflet s'affiche
Et les éléments suivants sont visibles:
| élément | description |
| Markers numérotés | 1, 2, 3, 4, 5 sur chaque point |
| Tracé entre points | Ligne pointillée connectant les points |
| Cercles de déclenchement | Rayon visuel autour de chaque point |
Scénario: Statistiques du parcours
Étant donné un audio-guide avec les séquences suivantes:
| séquence | durée | distance_au_suivant |
| 1 | 2:15 | 150m |
| 2 | 3:42 | 200m |
| 3 | 4:10 | 320m |
Quand les statistiques sont calculées
Alors le résumé suivant est affiché:
| métrique | valeur |
| Séquences | 3 complètes |
| Durée totale | 10:07 |
| Distance totale | 670m |
Scénario: Modification d'une séquence depuis la carte
Étant donné que la preview carte est affichée
Quand le créateur clique sur le marker "2"
Alors une popup s'affiche avec:
| information |
| Titre: "Les lions" |
| Durée: 3:42 |
| Rayon: 30m |
| [ Modifier] |
| [🗑 Supprimer] |
Scénario: Zone de diffusion géographique
Étant donné un audio-guide avec des points dans Paris
Quand le créateur définit la zone de diffusion
Alors il peut choisir:
| type | exemple |
| Polygon | Tracé manuel sur carte |
| Ville | Paris (API Nominatim) |
| Département | 75 - Paris |
| Région | Île-de-France |
Scénario: Étape 4 - Publication et validation modération
Étant donné un créateur qui publie ses 3 premiers audio-guides
Quand il clique sur " Publier audio-guide"
Alors un message s'affiche:
"""
Votre audio-guide est en cours de validation.
Notre équipe le vérifiera sous 24-48h.
Vous recevrez une notification dès validation.
"""
Scénario: Publication directe pour créateurs expérimentés
Étant donné un créateur ayant publié 5 audio-guides validés
Et aucun strike actif
Quand il publie un nouvel audio-guide
Alors l'audio-guide est publié immédiatement
Et il devient visible pour les utilisateurs
Et aucune validation manuelle n'est requise
Scénario: Mode piéton sans points GPS obligatoires
Étant donné un audio-guide en mode "🚶 Piéton"
Quand le créateur ajoute une séquence
Alors le champ "Point GPS" est optionnel
Et le champ "Rayon déclenchement" est masqué
Et un message info s'affiche: "Mode manuel : les séquences se déclenchent au clic utilisateur"
Scénario: Sauvegarde brouillon automatique
Étant donné que le créateur édite un audio-guide depuis 5 minutes
Quand il ajoute une nouvelle séquence
Alors l'audio-guide est sauvegardé en brouillon automatiquement
Et un toast "Brouillon sauvegardé" s'affiche brièvement
Scénario: Reprise d'un brouillon
Étant donné un audio-guide en brouillon "Safari du Paugre"
Et qu'il contient 3 séquences complètes
Quand le créateur retourne sur son dashboard
Alors le brouillon est visible avec le statut "📝 Brouillon"
Et un bouton "Continuer" est disponible
Et la progression "3/5 séquences" est affichée
Scénario: Suppression d'un brouillon
Étant donné un audio-guide en brouillon
Quand le créateur clique sur "🗑 Supprimer"
Alors une confirmation s'affiche:
"""
Supprimer ce brouillon ?
Toutes les séquences seront perdues.
[Annuler] [Supprimer définitivement]
"""
Scénario: Modification d'un audio-guide publié
Étant donné un audio-guide publié "Safari du Paugre"
Quand le créateur clique sur " Modifier"
Alors il peut modifier:
| élément modifiable | élément non modifiable |
| Titre | Mode de déplacement |
| Description | Points GPS |
| Tags | Rayons déclenchement |
| Séquences (ordre) | |
Et un avertissement s'affiche: "Les modifications structurelles nécessitent une nouvelle publication"
Scénario: Duplication d'un audio-guide existant
Étant donné un audio-guide publié "Visite Paris"
Quand le créateur clique sur "📋 Dupliquer"
Alors une copie est créée avec le titre "Visite Paris (copie)"
Et toutes les séquences sont copiées
Et le statut est "📝 Brouillon"
Et le créateur peut modifier avant publication
# Cas d'erreur
Scénario: Upload audio échoue (format non supporté)
Étant donné que le créateur upload un fichier "audio.wav"
Quand le format est vérifié
Alors un message d'erreur s'affiche: "Format non supporté. Utilisez MP3, AAC ou M4A"
Et le fichier est rejeté
Scénario: Upload audio échoue (taille trop grande)
Étant donné que le créateur upload un fichier de 250 MB
Quand la taille est vérifiée
Alors un message d'erreur s'affiche: "Fichier trop volumineux. Maximum 200 MB"
Et le fichier est rejeté
Scénario: Points GPS trop éloignés (alerte cohérence)
Étant donné un audio-guide en mode "Piéton"
Et une séquence au Louvre (Paris)
Quand le créateur ajoute une séquence à Lyon
Alors un avertissement s'affiche:
"""
Attention : distance importante entre points (465 km)
Vérifiez que le mode "Piéton" est approprié.
[Modifier le mode] [Continuer]
"""
Scénario: Pas de connexion lors de la sauvegarde
Étant donné que le créateur édite un audio-guide
Et que la connexion réseau est perdue
Quand il tente de sauvegarder
Alors le brouillon est sauvegardé localement
Et un message s'affiche: "Sauvegarde locale. Sera synchronisée à la reconnexion"
Et une icône " Hors ligne" s'affiche
Scénario: Reprise après perte de connexion
Étant donné un brouillon sauvegardé localement
Quand la connexion réseau est rétablie
Alors le brouillon est synchronisé automatiquement
Et un toast " Audio-guide synchronisé" s'affiche

View File

@@ -0,0 +1,402 @@
# language: fr
Fonctionnalité: API - Création et gestion d'audio-guides multi-séquences
En tant que système backend
Je veux exposer des endpoints pour créer et gérer les audio-guides
Afin de permettre aux créateurs de publier des expériences guidées
Contexte:
Étant donné que l'API RoadWave est démarrée
Et que le créateur "guide@example.com" est authentifié avec un token JWT valide
Et que son compte est vérifié (email_verified: true)
# 16.1.2 - Endpoints de création
Scénario: POST /api/v1/audio-guides - Création d'un audio-guide
Étant donné la requête suivante:
"""json
{
"title": "Safari du Paugre",
"description": "Découvrez les animaux du parc en voiture sur un circuit de 5km",
"mode": "voiture",
"vitesse_recommandee": "30-50 km/h",
"tags": ["nature", "animaux", "famille"],
"classification_age": "tout_public",
"zone_diffusion": {
"type": "polygon",
"coordinates": [[2.5678, 43.1234], [2.5690, 43.1245], [2.5700, 43.1250]]
}
}
"""
Quand je fais un POST sur "/api/v1/audio-guides"
Alors le code HTTP de réponse est 201
Et le corps de réponse contient:
| champ | valeur |
| id | UUID généré |
| status | draft |
| creator_id | ID du créateur |
| sequences_count | 0 |
| created_at | Timestamp actuel |
Scénario: Validation du titre (longueur 5-100 caractères)
Étant donné la requête avec titre "ABC"
Quand je fais un POST sur "/api/v1/audio-guides"
Alors le code HTTP de réponse est 400
Et le message d'erreur est "title: doit contenir entre 5 et 100 caractères"
Scénario: Validation de la description (longueur 10-500 caractères)
Étant donné la requête avec description de 8 caractères
Quand je fais un POST sur "/api/v1/audio-guides"
Alors le code HTTP de réponse est 400
Et le message d'erreur est "description: doit contenir entre 10 et 500 caractères"
Plan du Scénario: Validation du mode de déplacement
Étant donné la requête avec mode "<mode>"
Quand je fais un POST sur "/api/v1/audio-guides"
Alors le code HTTP de réponse est <code>
Exemples:
| mode | code |
| pieton | 201 |
| voiture | 201 |
| velo | 201 |
| transport | 201 |
| avion | 400 |
| invalid | 400 |
Scénario: Validation tags (1-3 tags obligatoires)
Étant donné la requête avec 0 tags
Quand je fais un POST sur "/api/v1/audio-guides"
Alors le code HTTP de réponse est 400
Et le message d'erreur est "tags: minimum 1 tag requis, maximum 3"
Scénario: Validation classification âge
Étant donné la requête avec classification_age "invalide"
Quand je fais un POST sur "/api/v1/audio-guides"
Alors le code HTTP de réponse est 400
Et le message d'erreur contient "classification_age: valeurs autorisées [tout_public, 13+, 16+, 18+]"
# Ajout de séquences
Scénario: POST /api/v1/audio-guides/{id}/sequences - Ajout première séquence
Étant donné un audio-guide draft avec ID "ag_123"
Et la requête suivante:
"""json
{
"order": 1,
"title": "Introduction - Point d'accueil",
"audio_file": "base64_encoded_mp3_data",
"gps_point": {
"latitude": 43.1234,
"longitude": 2.5678
},
"rayon_declenchement": 30
}
"""
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/sequences"
Alors le code HTTP de réponse est 201
Et la séquence est créée avec:
| champ | valeur |
| sequence_id | UUID généré |
| order | 1 |
| duration | Calculée depuis audio |
| status | uploaded |
Scénario: Validation format audio (MP3, AAC, M4A uniquement)
Étant donné un audio-guide draft
Et un fichier audio au format WAV
Quand je fais un POST sur "/api/v1/audio-guides/{id}/sequences"
Alors le code HTTP de réponse est 400
Et le message d'erreur est "audio_file: format non supporté. Formats acceptés: MP3, AAC, M4A"
Scénario: Validation taille audio (max 200 MB)
Étant donné un audio-guide draft
Et un fichier audio de 250 MB
Quand je fais un POST sur "/api/v1/audio-guides/{id}/sequences"
Alors le code HTTP de réponse 413
Et le message d'erreur est "audio_file: taille maximale 200 MB dépassée"
Scénario: Validation durée audio (max 15 minutes)
Étant donné un audio-guide draft
Et un fichier audio de 18 minutes
Quand je fais un POST sur "/api/v1/audio-guides/{id}/sequences"
Alors le code HTTP de réponse est 400
Et le message d'erreur est "audio_file: durée maximale 15 minutes dépassée"
Scénario: Point GPS obligatoire sauf mode piéton
Étant donné un audio-guide en mode "voiture"
Et une séquence sans gps_point
Quand je fais un POST sur "/api/v1/audio-guides/{id}/sequences"
Alors le code HTTP de réponse est 400
Et le message d'erreur est "gps_point: obligatoire pour mode voiture"
Scénario: Point GPS optionnel en mode piéton
Étant donné un audio-guide en mode "pieton"
Et une séquence sans gps_point
Quand je fais un POST sur "/api/v1/audio-guides/{id}/sequences"
Alors le code HTTP de réponse est 201
Et la séquence est créée sans point GPS
Plan du Scénario: Rayon de déclenchement par défaut selon mode
Étant donné un audio-guide en mode "<mode>"
Et une séquence sans rayon_declenchement spécifié
Quand je fais un POST sur "/api/v1/audio-guides/{id}/sequences"
Alors le rayon par défaut appliqué est <rayon>
Exemples:
| mode | rayon |
| voiture | 30 |
| velo | 50 |
| transport | 100 |
Scénario: Validation rayon configurable (10-200m)
Étant donné un audio-guide
Et une séquence avec rayon_declenchement 250
Quand je fais un POST sur "/api/v1/audio-guides/{id}/sequences"
Alors le code HTTP de réponse est 400
Et le message d'erreur est "rayon_declenchement: doit être entre 10 et 200 mètres"
Scénario: Nombre maximum de séquences (50)
Étant donné un audio-guide avec 50 séquences
Quand je tente d'ajouter une 51ème séquence
Alors le code HTTP de réponse est 400
Et le message d'erreur est "Maximum 50 séquences par audio-guide atteint"
# Modification et réordonnancement
Scénario: PATCH /api/v1/audio-guides/{id}/sequences/{seq_id} - Modification séquence
Étant donné une séquence existante "seq_456"
Et la requête suivante:
"""json
{
"title": "Introduction - Point d'accueil (édité)",
"rayon_declenchement": 40
}
"""
Quand je fais un PATCH sur "/api/v1/audio-guides/ag_123/sequences/seq_456"
Alors le code HTTP de réponse est 200
Et les champs modifiés sont mis à jour
Et updated_at est mis à jour
Scénario: PUT /api/v1/audio-guides/{id}/sequences/reorder - Réordonnancement
Étant donné un audio-guide avec 5 séquences
Et la requête suivante:
"""json
{
"sequence_orders": [
{"sequence_id": "seq_1", "order": 1},
{"sequence_id": "seq_4", "order": 2},
{"sequence_id": "seq_2", "order": 3},
{"sequence_id": "seq_3", "order": 4},
{"sequence_id": "seq_5", "order": 5}
]
}
"""
Quand je fais un PUT sur "/api/v1/audio-guides/ag_123/sequences/reorder"
Alors le code HTTP de réponse est 200
Et l'ordre des séquences est mis à jour en base
Scénario: DELETE /api/v1/audio-guides/{id}/sequences/{seq_id} - Suppression séquence
Étant donné un audio-guide avec 3 séquences
Quand je fais un DELETE sur "/api/v1/audio-guides/ag_123/sequences/seq_2"
Alors le code HTTP de réponse est 204
Et la séquence est supprimée
Et l'ordre des séquences restantes est réajusté (1, 2)
# Publication et validation
Scénario: POST /api/v1/audio-guides/{id}/publish - Publication (nouveau créateur)
Étant donné un audio-guide draft avec 3 séquences complètes
Et que c'est le 2ème audio-guide du créateur
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/publish"
Alors le code HTTP de réponse est 200
Et le status passe à "pending_moderation"
Et moderation_required est true
Et le corps de réponse contient:
"""json
{
"status": "pending_moderation",
"message": "Votre audio-guide est en cours de validation. Notre équipe le vérifiera sous 24-48h.",
"estimated_validation": "2026-01-24T14:00:00Z"
}
"""
Scénario: Publication directe pour créateur expérimenté (>5 audio-guides validés)
Étant donné un audio-guide draft avec 5 séquences
Et que le créateur a publié 8 audio-guides validés
Et qu'il n'a aucun strike actif
Quand je fais un POST sur "/api/v1/audio-guides/ag_456/publish"
Alors le code HTTP de réponse est 200
Et le status passe à "published"
Et moderation_required est false
Et l'audio-guide est immédiatement visible
Scénario: Validation nombre minimum de séquences (2)
Étant donné un audio-guide draft avec 1 seule séquence
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/publish"
Alors le code HTTP de réponse est 400
Et le message d'erreur est "Minimum 2 séquences requis pour publication"
Scénario: Alerte cohérence - Points GPS trop éloignés
Étant donné un audio-guide en mode "pieton"
Et une séquence au Louvre (Paris)
Et une séquence à Lyon (465 km de distance)
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/publish"
Alors le code HTTP de réponse est 200
Et un warning est retourné:
"""json
{
"status": "pending_moderation",
"warnings": [
{
"type": "distance_incohérence",
"message": "Distance importante entre points (465 km). Vérifiez que le mode 'pieton' est approprié.",
"severity": "warning"
}
]
}
"""
# Gestion des brouillons
Scénario: Sauvegarde automatique brouillon
Étant donné un audio-guide draft non sauvegardé depuis 5 minutes
Quand une modification est apportée via PATCH
Alors le champ updated_at est mis à jour automatiquement
Et le brouillon est sauvegardé en base
Scénario: GET /api/v1/audio-guides/drafts - Liste des brouillons
Étant donné que le créateur a 2 brouillons:
| id | title | sequences_count | updated_at |
| ag_111 | Safari du Paugre | 3 | 2026-01-20 10:00:00 |
| ag_222 | Visite du Louvre | 1 | 2026-01-15 14:30:00 |
Quand je fais un GET sur "/api/v1/audio-guides/drafts"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient les 2 brouillons
Et ils sont triés par updated_at décroissant
Scénario: DELETE /api/v1/audio-guides/{id} - Suppression brouillon
Étant donné un audio-guide draft "ag_123"
Quand je fais un DELETE sur "/api/v1/audio-guides/ag_123"
Alors le code HTTP de réponse est 204
Et l'audio-guide et toutes ses séquences sont supprimés
Et les fichiers audio associés sont marqués pour suppression S3
# Modification audio-guide publié
Scénario: PATCH /api/v1/audio-guides/{id} - Modification métadonnées (publié)
Étant donné un audio-guide publié "ag_789"
Et la requête suivante:
"""json
{
"title": "Safari du Paugre - Version 2",
"description": "Nouvelle description améliorée",
"tags": ["nature", "animaux", "enfants"]
}
"""
Quand je fais un PATCH sur "/api/v1/audio-guides/ag_789"
Alors le code HTTP de réponse est 200
Et les métadonnées sont mises à jour
Et le status reste "published" (pas de revalidation)
Scénario: Interdiction modification mode après publication
Étant donné un audio-guide publié en mode "voiture"
Et la requête avec mode "pieton"
Quand je fais un PATCH sur "/api/v1/audio-guides/{id}"
Alors le code HTTP de réponse est 400
Et le message d'erreur est "mode: impossible de modifier le mode après publication"
Scénario: Interdiction modification GPS après publication
Étant donné un audio-guide publié avec points GPS
Et une tentative de modification des coordonnées GPS
Quand je fais un PATCH sur "/api/v1/audio-guides/{id}/sequences/{seq_id}"
Alors le code HTTP de réponse est 400
Et le message d'erreur est "gps_point: impossible de modifier après publication. Créez une nouvelle version."
# Duplication
Scénario: POST /api/v1/audio-guides/{id}/duplicate - Duplication audio-guide
Étant donné un audio-guide publié "ag_999" avec 12 séquences
Quand je fais un POST sur "/api/v1/audio-guides/ag_999/duplicate"
Alors le code HTTP de réponse est 201
Et un nouvel audio-guide draft est créé
Et le titre est "Safari du Paugre (copie)"
Et toutes les séquences sont copiées avec les mêmes métadonnées
Et les fichiers audio sont référencés (pas de duplication S3)
# Statistiques
Scénario: GET /api/v1/audio-guides/{id}/stats - Statistiques parcours
Étant donné un audio-guide avec les séquences suivantes:
| sequence | duration | gps_point | distance_to_next |
| 1 | 2:15 | (43.1234, 2.5678) | 150m |
| 2 | 3:42 | (43.1245, 2.5690) | 200m |
| 3 | 4:10 | (43.1250, 2.5700) | null |
Quand je fais un GET sur "/api/v1/audio-guides/ag_123/stats"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"sequences_count": 3,
"total_duration": "10:07",
"total_distance": "350m",
"avg_sequence_duration": "3:22"
}
"""
# Gestion zone diffusion
Scénario: Validation zone diffusion (polygon géographique)
Étant donné une zone diffusion de type "polygon"
Et les coordonnées forment un polygon valide (min 3 points)
Quand je fais un POST sur "/api/v1/audio-guides"
Alors la zone est validée avec PostGIS ST_IsValid
Et stockée en type geography
Scénario: Zone diffusion via API Nominatim (ville)
Étant donné une zone diffusion de type "city"
Et la valeur "Paris"
Quand je fais un POST sur "/api/v1/audio-guides"
Alors l'API interroge Nominatim pour récupérer le polygon de Paris
Et le polygon est stocké en base
# Cas d'erreur
Scénario: Authentification requise
Étant donné une requête sans token JWT
Quand je fais un POST sur "/api/v1/audio-guides"
Alors le code HTTP de réponse est 401
Et le message d'erreur est "Authentification requise"
Scénario: Compte non vérifié
Étant donné un créateur avec email_verified: false
Quand je fais un POST sur "/api/v1/audio-guides"
Alors le code HTTP de réponse est 403
Et le message d'erreur est "Vérification email requise pour créer des audio-guides"
Scénario: Modification audio-guide d'un autre créateur (interdite)
Étant donné un audio-guide appartenant au créateur "creator_A"
Et une requête authentifiée par "creator_B"
Quand je fais un PATCH sur "/api/v1/audio-guides/{id}"
Alors le code HTTP de réponse est 403
Et le message d'erreur est "Vous n'êtes pas autorisé à modifier cet audio-guide"
Scénario: Audio-guide inexistant
Quand je fais un GET sur "/api/v1/audio-guides/ag_nonexistant"
Alors le code HTTP de réponse est 404
Et le message d'erreur est "Audio-guide non trouvé"
Scénario: Séquence inexistante
Étant donné un audio-guide "ag_123"
Quand je fais un DELETE sur "/api/v1/audio-guides/ag_123/sequences/seq_nonexistant"
Alors le code HTTP de réponse est 404
Et le message d'erreur est "Séquence non trouvée"
Scénario: Suppression audio-guide avec utilisateurs actifs
Étant donné un audio-guide publié "ag_456"
Et 20 utilisateurs ont une progression active
Quand je fais un DELETE sur "/api/v1/audio-guides/ag_456"
Alors le code HTTP de réponse est 200
Et l'audio-guide est marqué "deleted" (soft delete)
Et les progressions utilisateurs sont archivées pendant 30 jours
Et un warning est retourné: "20 utilisateurs actifs. Progressions archivées 30 jours."

View File

@@ -0,0 +1,247 @@
# language: fr
@api @audio-guides @content-creation @mvp
Fonctionnalité: Wizard complet de création d'audio-guide multi-séquences
En tant que créateur de contenu
Je veux créer un audio-guide avec plusieurs séquences géolocalisées
Afin de proposer une expérience guidée immersive aux utilisateurs
Contexte:
Étant donné que le système supporte les limites suivantes:
| Paramètre | Valeur |
| Nombre max de séquences par guide | 50 |
| Taille max fichier audio | 100 MB |
| Formats audio acceptés | MP3, M4A, WAV |
| Durée max par séquence | 15 minutes |
| Rayon min d'un point d'intérêt | 10 mètres |
| Rayon max d'un point d'intérêt | 500 mètres |
Scénario: Création d'un audio-guide - Étape 1: Informations générales
Étant donné un créateur "alice@roadwave.fr" connecté
Quand le créateur clique sur "Créer un audio-guide"
Alors le wizard s'ouvre sur l'étape 1 "Informations générales"
Et le créateur remplit le formulaire:
| Champ | Valeur |
| Titre | Visite guidée du Quartier Latin |
| Description courte | Découvrez l'histoire du quartier étudiant |
| Description longue | Plongez dans 2000 ans d'histoire... |
| Catégorie | Tourisme |
| Langues disponibles | Français, Anglais |
| Durée estimée | 2 heures |
| Difficulté | Facile |
| Accessibilité PMR | Oui |
Et le créateur clique sur "Suivant"
Alors les données sont validées et enregistrées en brouillon
Et un événement "AUDIO_GUIDE_CREATION_STARTED" est enregistré
Et la métrique "audio_guide.creation.step1_completed" est incrémentée
Scénario: Création d'un audio-guide - Étape 2: Image de couverture
Étant donné un créateur "bob@roadwave.fr" à l'étape 2 du wizard
Quand le créateur upload une image de couverture:
| Propriété | Valeur |
| Fichier | quartier-latin-cover.jpg |
| Taille | 1920x1080 px |
| Format | JPEG |
| Poids | 2.5 MB |
Alors l'image est uploadée vers le stockage S3
Et une miniature est générée automatiquement (300x200 px)
Et un aperçu de l'image est affiché
Et le créateur peut recadrer l'image via un éditeur intégré
Et le créateur clique sur "Suivant"
Alors l'image est associée au brouillon
Et un événement "AUDIO_GUIDE_COVER_UPLOADED" est enregistré
Et la métrique "audio_guide.creation.step2_completed" est incrémentée
Scénario: Création d'un audio-guide - Étape 3: Ajout de séquences via carte
Étant donné un créateur "charlie@roadwave.fr" à l'étape 3 du wizard
Quand le créateur voit une carte interactive centrée sur Paris
Et clique sur "Ajouter un point d'intérêt" sur la carte
Et place un marqueur à la position: 48.8534, 2.3488 (Notre-Dame)
Alors un formulaire de séquence s'ouvre:
| Champ | Valeur par défaut |
| Nom du point | [Vide] |
| Position GPS | 48.8534, 2.3488 |
| Rayon de déclenchement| 50 mètres |
| Ordre dans le parcours| 1 |
| Fichier audio | [Non uploadé] |
Et le créateur remplit les informations:
| Champ | Valeur |
| Nom du point | Cathédrale Notre-Dame de Paris |
| Rayon de déclenchement| 100 mètres |
| Ordre dans le parcours| 1 |
Et le créateur upload un fichier audio "notre-dame.mp3" (12 MB, 8min 30s)
Et le créateur clique sur "Enregistrer le point"
Alors la séquence 1 est créée et affichée sur la carte
Et un événement "AUDIO_GUIDE_SEQUENCE_ADDED" est enregistré
Et la métrique "audio_guide.sequences.added" est incrémentée
Scénario: Ajout de plusieurs séquences consécutives
Étant donné un créateur "david@roadwave.fr" avec 1 séquence créée
Quand le créateur ajoute 4 nouvelles séquences:
| Ordre | Nom | Position GPS | Rayon | Audio |
| 2 | Sainte-Chapelle | 48.8555, 2.3450 | 80m | chapelle.mp3 |
| 3 | Panthéon | 48.8462, 2.3464 | 100m | pantheon.mp3 |
| 4 | Jardin du Luxembourg | 48.8462, 2.3371 | 150m | jardin.mp3 |
| 5 | Sorbonne | 48.8487, 2.3431 | 70m | sorbonne.mp3 |
Alors les 5 séquences sont affichées sur la carte avec des marqueurs numérotés
Et une ligne de parcours relie les points dans l'ordre
Et la distance totale du parcours est calculée: 3.2 km
Et la durée totale des audios est calculée: 42 minutes
Et un panneau latéral liste les séquences avec possibilité de réorganiser
Et un événement "AUDIO_GUIDE_SEQUENCES_BATCH_ADDED" est enregistré
Et la métrique "audio_guide.sequences.count" est mise à jour: 5
Scénario: Réorganisation de l'ordre des séquences par drag & drop
Étant donné un créateur "eve@roadwave.fr" avec 5 séquences créées
Quand le créateur utilise le panneau latéral
Et fait glisser la séquence #3 "Panthéon" vers la position #2
Alors l'ordre des séquences est mis à jour:
| Nouvel ordre | Nom |
| 1 | Cathédrale Notre-Dame |
| 2 | Panthéon |
| 3 | Sainte-Chapelle |
| 4 | Jardin du Luxembourg |
| 5 | Sorbonne |
Et la ligne de parcours sur la carte est recalculée
Et la distance totale est recalculée: 3.5 km
Et un événement "AUDIO_GUIDE_SEQUENCES_REORDERED" est enregistré
Et la métrique "audio_guide.sequences.reordered" est incrémentée
Scénario: Modification d'une séquence existante
Étant donné un créateur "frank@roadwave.fr" avec 5 séquences créées
Quand le créateur clique sur le marqueur #2 "Panthéon" sur la carte
Alors le formulaire d'édition s'ouvre avec les données actuelles
Et le créateur modifie:
| Champ | Ancienne valeur | Nouvelle valeur |
| Rayon de déclenchement| 100m | 120m |
| Fichier audio | pantheon.mp3 | pantheon-v2.mp3 |
Et le créateur clique sur "Enregistrer les modifications"
Alors la séquence est mise à jour
Et le nouveau fichier audio remplace l'ancien
Et l'ancien fichier est supprimé du stockage S3
Et un événement "AUDIO_GUIDE_SEQUENCE_UPDATED" est enregistré
Et la métrique "audio_guide.sequences.updated" est incrémentée
Scénario: Suppression d'une séquence
Étant donné un créateur "grace@roadwave.fr" avec 5 séquences créées
Quand le créateur clique sur l'icône de suppression de la séquence #3
Alors un dialogue de confirmation s'affiche: "Supprimer cette séquence ?"
Et le créateur confirme la suppression
Alors la séquence #3 est supprimée
Et le fichier audio associé est marqué pour suppression différée (30 jours)
Et les séquences suivantes sont renumérotées automatiquement:
| Ancien ordre | Nouveau ordre | Nom |
| 4 | 3 | Jardin du Luxembourg |
| 5 | 4 | Sorbonne |
Et la ligne de parcours est recalculée
Et un événement "AUDIO_GUIDE_SEQUENCE_DELETED" est enregistré
Et la métrique "audio_guide.sequences.deleted" est incrémentée
Scénario: Validation de la distance minimale entre séquences
Étant donné un créateur "henry@roadwave.fr" avec 2 séquences créées
Quand le créateur tente d'ajouter une 3ème séquence à 5 mètres de la séquence #1
Alors un message d'erreur s'affiche: "Ce point est trop proche d'un point existant (min: 20m)"
Et le marqueur est affiché en rouge sur la carte
Et la séquence n'est pas enregistrée tant que le créateur ne déplace pas le marqueur
Et un événement "AUDIO_GUIDE_SEQUENCE_TOO_CLOSE" est enregistré
Et la métrique "audio_guide.validation.sequence_too_close" est incrémentée
Scénario: Création d'un audio-guide - Étape 4: Configuration avancée
Étant donné un créateur "iris@roadwave.fr" à l'étape 4 du wizard
Quand le créateur configure les options avancées:
| Option | Valeur |
| Prix (gratuit ou payant) | Gratuit |
| Visibilité | Publique |
| Mode de lecture | Séquentiel obligatoire |
| Autoriser les avis utilisateurs | Oui |
| Autoriser le téléchargement | Non |
| Activer les sous-titres | Oui |
Et clique sur "Suivant"
Alors les options sont enregistrées
Et un événement "AUDIO_GUIDE_CONFIG_COMPLETED" est enregistré
Et la métrique "audio_guide.creation.step4_completed" est incrémentée
Scénario: Création d'un audio-guide - Étape 5: Prévisualisation et publication
Étant donné un créateur "jack@roadwave.fr" à l'étape 5 du wizard
Quand le créateur voit la prévisualisation complète:
| Section | Contenu |
| Informations | Titre, description, durée |
| Image | Aperçu de la couverture |
| Parcours | Carte avec 5 séquences |
| Audio | Liste des 5 fichiers audio |
| Configuration | Prix, visibilité, options |
Et clique sur "Tester le parcours en simulation"
Alors une simulation GPS est lancée avec lecture des audios
Et le créateur peut naviguer dans le parcours virtuel
Et après validation, le créateur clique sur "Publier l'audio-guide"
Alors l'audio-guide passe du statut "brouillon" à "publié"
Et l'audio-guide devient visible dans les recherches et recommandations
Et un événement "AUDIO_GUIDE_PUBLISHED" est enregistré
Et la métrique "audio_guide.published" est incrémentée
Et un email de confirmation est envoyé au créateur
Scénario: Sauvegarde automatique du brouillon pendant la création
Étant donné un créateur "kate@roadwave.fr" en train de créer un audio-guide
Quand le créateur remplit des informations à chaque étape
Alors le brouillon est automatiquement sauvegardé toutes les 30 secondes
Et un indicateur "Sauvegardé automatiquement à 14:32" s'affiche
Et en cas de fermeture accidentelle, le créateur peut reprendre la création
Et un événement "AUDIO_GUIDE_DRAFT_AUTOSAVED" est enregistré toutes les 30s
Et la métrique "audio_guide.drafts.autosaved" est incrémentée
Scénario: Récupération d'un brouillon après interruption
Étant donné un créateur "luke@roadwave.fr" qui a commencé un audio-guide hier
Et le brouillon a été sauvegardé automatiquement à l'étape 3
Quand le créateur clique sur "Créer un audio-guide"
Alors un message s'affiche: "Vous avez un brouillon en cours. Reprendre la création ?"
Et le créateur clique sur "Reprendre"
Alors le wizard s'ouvre directement à l'étape 3
Et toutes les données saisies sont restaurées
Et un événement "AUDIO_GUIDE_DRAFT_RESUMED" est enregistré
Et la métrique "audio_guide.drafts.resumed" est incrémentée
Scénario: Import d'un parcours GPX pour créer automatiquement les séquences
Étant donné un créateur "mary@roadwave.fr" à l'étape 3 du wizard
Quand le créateur clique sur "Importer un parcours GPX"
Et upload un fichier "parcours-paris.gpx" avec 10 waypoints
Alors le système extrait les coordonnées GPS de chaque waypoint
Et crée automatiquement 10 séquences avec positions GPS pré-remplies
Et les marqueurs sont affichés sur la carte
Et le créateur doit ensuite ajouter les fichiers audio et noms pour chaque séquence
Et un événement "AUDIO_GUIDE_GPX_IMPORTED" est enregistré
Et la métrique "audio_guide.gpx.imported" est incrémentée
Scénario: Validation de la qualité audio avant publication
Étant donné un créateur "nathan@roadwave.fr" qui tente de publier un audio-guide
Quand le système analyse les fichiers audio uploadés
Et détecte que le fichier "sequence-3.mp3" a un bitrate de 32 kbps (trop faible)
Alors un avertissement s'affiche: "Le fichier 'sequence-3.mp3' a une qualité audio faible. Recommandé: 128 kbps minimum"
Et le créateur peut choisir de:
| Action | Conséquence |
| Ignorer et publier quand même | Publication autorisée |
| Remplacer le fichier | Retour à l'édition |
Et un événement "AUDIO_GUIDE_LOW_QUALITY_WARNING" est enregistré
Et la métrique "audio_guide.quality.warnings" est incrémentée
Scénario: Limitation du nombre de brouillons par créateur
Étant donné un créateur "olive@roadwave.fr" avec 10 brouillons en cours
Quand le créateur tente de créer un 11ème audio-guide
Alors un message s'affiche: "Vous avez atteint la limite de 10 brouillons. Veuillez publier ou supprimer des brouillons existants."
Et un lien vers la liste des brouillons est affiché
Et la création est bloquée jusqu'à suppression d'un brouillon
Et un événement "AUDIO_GUIDE_DRAFT_LIMIT_REACHED" est enregistré
Et la métrique "audio_guide.drafts.limit_reached" est incrémentée
Scénario: Métriques de performance du wizard de création
Étant donné que 1000 audio-guides sont créés par mois
Quand les métriques de création sont collectées
Alors les indicateurs suivants sont disponibles:
| Métrique | Valeur cible |
| Taux de complétion du wizard | > 65% |
| Temps moyen de création | < 45 min |
| Nombre moyen de séquences par guide | 5-8 |
| Taux d'abandon à chaque étape | < 15% |
| Taux d'utilisation de l'autosave | 100% |
Et les métriques sont exportées vers le système de monitoring
Et des optimisations UX sont proposées si taux d'abandon > 20%

View File

@@ -0,0 +1,223 @@
# language: fr
@api @audio-guides @car-mode @geolocation @mvp
Fonctionnalité: Déclenchement GPS automatique des audio-guides en mode voiture
En tant qu'utilisateur en voiture
Je veux que les audio-guides se déclenchent automatiquement à l'approche des points d'intérêt
Afin de profiter d'une expérience guidée sans interaction manuelle pendant la conduite
Contexte:
Étant donné que le système de déclenchement en mode voiture respecte:
| Paramètre | Valeur |
| Rayon de déclenchement | 200-500m |
| Vitesse max pour déclenchement | 90 km/h |
| Ordre de séquences | Strict |
| Notification visuelle | Minimale |
| Notification audio | Prioritaire |
| Auto-play | Obligatoire |
Scénario: Démarrage d'un audio-guide en mode voiture
Étant donné un utilisateur "alice@roadwave.fr" en mode voiture
Et elle roule à 50 km/h sur l'autoroute A6
Quand elle lance l'audio-guide "Route des Châteaux de la Loire"
Alors l'audio de la séquence d'introduction démarre automatiquement
Et l'interface minimaliste en mode voiture s'affiche:
| Élément | État |
| Carte | Simplifiée, zoom automatique |
| Notifications visuelles | Minimales |
| Prochain point | Château de Chambord - 25 km |
| ETA | Arrivée dans 18 minutes |
| Contrôles audio | Gros boutons [Pause] [Skip] |
Et un événement "AUDIO_GUIDE_STARTED_CAR_MODE" est enregistré
Et la métrique "audio_guide.started.car_mode" est incrémentée
Scénario: Déclenchement automatique à l'approche d'un point d'intérêt
Étant donné un utilisateur "bob@roadwave.fr" en mode voiture à 60 km/h
Et il écoute l'audio-guide "Route des Châteaux de la Loire"
Et il approche du Château de Chambord
Quand il entre dans un rayon de 400m du château (configuré par le créateur)
Alors l'audio en cours se termine en fondu (3 secondes)
Et l'audio de la séquence "Château de Chambord" démarre automatiquement
Et une notification audio est jouée: "À votre droite, Château de Chambord"
Et une notification visuelle minimale s'affiche brièvement (2s):
| Élément | Contenu |
| Titre | Château de Chambord |
| Direction | À droite |
| Distance | 400m |
Et un événement "SEQUENCE_AUTO_TRIGGERED_CAR" est enregistré
Et la métrique "audio_guide.sequence.car.triggered" est incrémentée
Scénario: Calcul de l'ETA dynamique basé sur la vitesse réelle
Étant donné un utilisateur "charlie@roadwave.fr" en mode voiture
Et il approche du prochain point d'intérêt à 15 km
Et il roule à 80 km/h
Quand le système calcule l'ETA
Alors l'ETA affiché est: "Arrivée dans 11 minutes"
Quand il ralentit à 50 km/h (bouchon)
Alors l'ETA est recalculé en temps réel: "Arrivée dans 18 minutes"
Et un événement "ETA_RECALCULATED" est enregistré
Et la métrique "audio_guide.eta.updated" est incrémentée
Scénario: Notification vocale d'approche 2km avant le point
Étant donné un utilisateur "david@roadwave.fr" en mode voiture à 70 km/h
Et il écoute l'audio-guide "Route des Châteaux de la Loire"
Quand il est à 2 km du Château de Chenonceau
Alors une notification vocale est jouée par-dessus l'audio actuel:
"Dans 2 kilomètres, vous découvrirez le Château de Chenonceau"
Et le volume de l'audio actuel est réduit de 50% pendant la notification (ducking audio)
Et après la notification, le volume reprend normalement
Et un événement "POI_ADVANCE_NOTIFICATION" est enregistré avec distance: 2000m
Et la métrique "audio_guide.advance_notification" est incrémentée
Scénario: Gestion du dépassement d'un point d'intérêt sans déclenchement
Étant donné un utilisateur "eve@roadwave.fr" en mode voiture à 90 km/h
Et elle approche du Château d'Amboise (séquence #3)
Mais elle a manqué la séquence #2 (Château de Chaumont)
Quand elle entre dans le rayon du Château d'Amboise
Alors l'audio de la séquence #2 est automatiquement joué d'abord
Et un message vocal indique: "Séquence précédente: Château de Chaumont"
Et après la fin de la séquence #2, la séquence #3 démarre
Et un événement "SEQUENCE_CATCH_UP" est enregistré
Et la métrique "audio_guide.sequence.catch_up" est incrémentée
Scénario: Marquage automatique d'une séquence comme "manquée"
Étant donné un utilisateur "frank@roadwave.fr" en mode voiture à 100 km/h
Et il a dépassé le Château de Chaumont sans entrer dans son rayon
Et il s'éloigne maintenant à plus de 5 km
Alors la séquence "Château de Chaumont" est marquée comme "Manquée"
Et elle reste disponible dans la liste pour écoute manuelle ultérieure
Et un événement "SEQUENCE_MISSED" est enregistré avec raison: "too_fast"
Et la métrique "audio_guide.sequence.missed" est incrémentée
Scénario: Pause automatique lors d'un appel téléphonique
Étant donné un utilisateur "grace@roadwave.fr" en mode voiture
Et elle écoute l'audio-guide à la position 3min 20s
Quand elle reçoit un appel téléphonique via CarPlay
Alors l'audio-guide se met automatiquement en pause
Et la position de lecture est sauvegardée: 3min 20s
Et un événement "AUDIO_PAUSED_PHONE_CALL" est enregistré
Quand l'appel se termine
Alors l'audio-guide reprend automatiquement à 3min 20s
Et un événement "AUDIO_RESUMED_AFTER_CALL" est enregistré
Et la métrique "audio_guide.interruption.phone" est incrémentée
Scénario: Intégration avec CarPlay pour affichage sur écran véhicule
Étant donné un utilisateur "henry@roadwave.fr" en mode voiture
Et son iPhone est connecté via CarPlay
Quand il lance l'audio-guide "Route des Châteaux de la Loire"
Alors l'interface CarPlay s'affiche sur l'écran du véhicule:
| Élément | Affichage |
| Carte simplifiée | Vue routière optimisée |
| Prochain point | Nom + distance + ETA |
| Contrôles audio | Gros boutons tactiles |
| Progression | Barre 3/10 séquences |
| Commandes vocales | "Dis Siri, suivant" |
Et les contrôles au volant du véhicule fonctionnent (lecture/pause)
Et un événement "CARPLAY_SESSION_STARTED" est enregistré
Et la métrique "audio_guide.carplay.used" est incrémentée
Scénario: Commandes vocales Siri pour contrôle sans les mains
Étant donné un utilisateur "iris@roadwave.fr" en mode voiture
Et elle écoute l'audio-guide via CarPlay
Quand elle dit "Dis Siri, mets en pause"
Alors l'audio-guide se met en pause
Quand elle dit "Dis Siri, reprends la lecture"
Alors l'audio-guide reprend
Quand elle dit "Dis Siri, séquence suivante"
Alors la séquence suivante démarre
Et un événement "VOICE_COMMAND_EXECUTED" est enregistré avec commande: "next"
Et la métrique "audio_guide.voice_commands.used" est incrémentée
Scénario: Adaptation du volume en fonction de la vitesse du véhicule
Étant donné un utilisateur "jack@roadwave.fr" en mode voiture
Et il écoute l'audio-guide avec volume configuré à 70%
Quand il roule à 50 km/h
Alors le volume reste à 70% (bruit ambiant faible)
Quand il accélère à 130 km/h sur autoroute
Alors le volume augmente automatiquement à 85% (compensation du bruit)
Et un événement "VOLUME_AUTO_ADJUSTED" est enregistré avec vitesse: 130, volume: 85
Et la métrique "audio_guide.volume.auto_adjusted" est incrémentée
Scénario: Désactivation temporaire en cas de vitesse excessive
Étant donné un utilisateur "kate@roadwave.fr" en mode voiture
Et elle écoute l'audio-guide sur autoroute
Quand elle dépasse les 110 km/h
Alors l'audio continue de jouer normalement
Mais aucune nouvelle séquence ne se déclenche automatiquement
Et un message vocal indique: "Déclenchements automatiques suspendus à haute vitesse"
Quand elle ralentit en dessous de 90 km/h
Alors les déclenchements automatiques sont réactivés
Et un événement "AUTO_TRIGGER_SPEED_LIMITED" est enregistré
Et la métrique "audio_guide.speed.limited" est incrémentée
Scénario: Mode nuit avec interface sombre automatique
Étant donné un utilisateur "luke@roadwave.fr" en mode voiture
Et il est 22h30 (nuit)
Quand il utilise l'audio-guide
Alors l'interface passe automatiquement en mode nuit:
| Élément | Mode nuit |
| Fond d'écran | Noir |
| Texte | Blanc/Gris clair |
| Carte | Thème sombre |
| Luminosité | Réduite de 40% |
Et les notifications visuelles sont encore plus discrètes
Et un événement "NIGHT_MODE_AUTO_ENABLED" est enregistré
Et la métrique "audio_guide.night_mode.enabled" est incrémentée
Scénario: Connexion automatique via Android Auto
Étant donné un utilisateur "mary@roadwave.fr" avec téléphone Android
Et son téléphone est connecté via Android Auto
Quand elle lance l'audio-guide "Route des Châteaux de la Loire"
Alors l'interface Android Auto s'affiche sur l'écran du véhicule
Et les fonctionnalités sont identiques à CarPlay:
| Fonctionnalité | Disponible |
| Carte simplifiée | Oui |
| Contrôles audio | Oui |
| Commandes vocales | Oui (Google Assistant) |
| Notifications | Oui |
Et un événement "ANDROID_AUTO_SESSION_STARTED" est enregistré
Et la métrique "audio_guide.android_auto.used" est incrémentée
Scénario: Gestion de la perte de signal GPS temporaire
Étant donné un utilisateur "nathan@roadwave.fr" en mode voiture
Et il écoute l'audio-guide dans un tunnel
Quand le signal GPS est perdu pendant 2 minutes
Alors l'audio en cours continue de jouer normalement
Et la position estimée est calculée selon la vitesse et direction précédentes
Et un message discret s'affiche: "Signal GPS perdu - Position estimée"
Quand le signal GPS est retrouvé
Alors la position est recalculée immédiatement
Et les déclenchements automatiques sont réactivés
Et un événement "GPS_SIGNAL_RESTORED" est enregistré
Et la métrique "audio_guide.gps.signal_lost" est incrémentée
Scénario: Statistiques de fin de parcours en mode voiture
Étant donné un utilisateur "olive@roadwave.fr" en mode voiture
Et elle vient de terminer l'audio-guide "Route des Châteaux de la Loire"
Quand elle arrive à destination
Alors un écran de statistiques s'affiche:
| Métrique | Valeur |
| Séquences écoutées | 9/10 |
| Séquences manquées | 1 (trop rapide) |
| Distance parcourue | 142 km |
| Temps total | 2h 15min |
| Temps d'écoute | 1h 05min |
| Vitesse moyenne | 63 km/h |
| Badge débloqué | Voyageur des châteaux |
Et un bouton "Partager mon voyage" est disponible
Et un événement "AUDIO_GUIDE_COMPLETED_CAR" est enregistré
Et la métrique "audio_guide.completed.car_mode" est incrémentée
Scénario: Métriques de performance du mode voiture
Étant donné que 50 000 utilisateurs ont utilisé l'audio-guide en mode voiture
Quand les métriques d'usage sont collectées
Alors les indicateurs suivants sont disponibles:
| Métrique | Valeur cible |
| Taux de déclenchement automatique | > 90% |
| Taux de séquences manquées | < 15% |
| Temps moyen entre déclenchements | 8 minutes |
| Précision du calcul ETA | ±3 minutes |
| Utilisation de CarPlay/Android Auto | 65% |
| Utilisation de commandes vocales | 45% |
Et les métriques sont exportées vers le système de monitoring

View File

@@ -0,0 +1,339 @@
# language: fr
Fonctionnalité: API - Déclenchement GPS et géolocalisation audio-guides
En tant que système backend
Je veux calculer les déclenchements GPS et distances pour audio-guides
Afin de permettre une expérience automatique en mode voiture/vélo/transport
Contexte:
Étant donné que l'API RoadWave est démarrée
Et que l'utilisateur "user@example.com" est authentifié
# 16.3.1 - Calcul de proximité et déclenchement
Scénario: POST /api/v1/audio-guides/{id}/check-proximity - Vérification proximité
Étant donné un audio-guide voiture avec 8 séquences
Et que l'utilisateur est à la position (43.1233, 2.5677)
Et que le prochain point GPS (séquence 2) est à (43.1245, 2.5690) avec rayon 30m
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/check-proximity":
"""json
{
"user_position": {
"latitude": 43.1233,
"longitude": 2.5677
},
"current_sequence": 1
}
"""
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"in_trigger_zone": false,
"next_sequence_id": "seq_2",
"distance_to_next": 145.3,
"eta_seconds": 18,
"direction_degrees": 45,
"should_trigger": false
}
"""
Scénario: Déclenchement automatique dans rayon 30m (voiture)
Étant donné un audio-guide voiture
Et que l'utilisateur entre à 25m du point GPS suivant
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/check-proximity"
Alors le code HTTP de réponse est 200
Et should_trigger est true
Et in_trigger_zone est true
Et le message "Séquence déclenchée automatiquement" est retourné
Plan du Scénario: Rayon de déclenchement selon mode
Étant donné un audio-guide en mode <mode>
Et un point GPS avec rayon par défaut
Quand l'utilisateur entre à <distance> du point
Alors should_trigger est <trigger>
Exemples:
| mode | distance | trigger |
| voiture | 25m | true |
| voiture | 35m | false |
| velo | 45m | true |
| velo | 55m | false |
| transport | 95m | true |
| transport | 105m | false |
# Calcul distance avec PostGIS
Scénario: Calcul distance avec ST_Distance (geography)
Étant donné deux points GPS:
| point | latitude | longitude |
| Position user| 43.1234 | 2.5678 |
| Point séq. 2 | 43.1245 | 2.5690 |
Quand le calcul PostGIS ST_Distance est effectué
Alors la distance retournée est 145.3 mètres
Et le calcul utilise le type geography (WGS84)
Et la précision est au mètre près
Scénario: Calcul ETA basé sur vitesse actuelle
Étant donné que l'utilisateur est à 320m du prochain point
Et que sa vitesse actuelle est 28 km/h
Quand l'ETA est calculé
Alors l'ETA retourné est 41 secondes
Et la formule appliquée est: (distance_m / 1000) / (vitesse_kmh) * 3600
Scénario: ETA non calculé si vitesse < 5 km/h
Étant donné que l'utilisateur est à 200m du prochain point
Et que sa vitesse actuelle est 2 km/h (arrêté)
Quand l'ETA est calculé
Alors l'ETA retourné est null
Et le message "En attente de déplacement" est inclus
Scénario: Calcul direction (bearing) avec PostGIS
Étant donné la position utilisateur (43.1234, 2.5678)
Et le prochain point (43.1245, 2.5690) au nord-est
Quand le bearing est calculé avec ST_Azimuth
Alors l'angle retourné est 45° (nord-est)
Et la flèche correspondante est ""
Plan du Scénario: Conversion angle en flèche (8 directions)
Étant donné un angle de <degrees>°
Quand la flèche est calculée
Alors la direction retournée est "<arrow>"
Exemples:
| degrees | arrow |
| 0 | |
| 45 | |
| 90 | |
| 135 | |
| 180 | |
| 225 | |
| 270 | |
| 315 | |
# 16.3.3 - Gestion point manqué
Scénario: Détection point manqué (hors rayon mais dans tolérance)
Étant donné un audio-guide voiture
Et un point GPS avec rayon 30m et tolérance 100m
Et que l'utilisateur passe à 65m du point
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/check-proximity"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"in_trigger_zone": false,
"missed_point": true,
"distance_to_point": 65,
"tolerance_zone": true,
"actions_available": ["listen_anyway", "skip", "navigate_back"]
}
"""
Scénario: Point manqué au-delà tolérance (>100m en voiture)
Étant donné un audio-guide voiture
Et que l'utilisateur passe à 150m du point GPS
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/check-proximity"
Alors missed_point est false
Et tolerance_zone est false
Et aucune popup "point manqué" n'est déclenchée
Plan du Scénario: Rayon tolérance selon mode
Étant donné un audio-guide en mode <mode>
Et que l'utilisateur passe à <distance> du point
Alors tolerance_zone est <in_tolerance>
Exemples:
| mode | distance | in_tolerance |
| voiture | 60m | true |
| voiture | 110m | false |
| velo | 70m | true |
| velo | 80m | false |
| transport | 120m | true |
| transport | 160m | false |
# Progress bar dynamique
Scénario: Calcul progress bar vers prochain point
Étant donné que la distance initiale vers le prochain point était 500m
Et que l'utilisateur est maintenant à 175m du point
Quand le pourcentage de progression est calculé
Alors le progress_percentage retourné est 65%
Et la formule est: 100 - (distance_actuelle / distance_initiale * 100)
# Gestion trajectoire et itinéraire
Scénario: Calcul distance totale parcours
Étant donné un audio-guide avec les points GPS suivants:
| sequence | latitude | longitude |
| 1 | 43.1234 | 2.5678 |
| 2 | 43.1245 | 2.5690 |
| 3 | 43.1250 | 2.5700 |
Quand je fais un GET sur "/api/v1/audio-guides/ag_123/route-stats"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"total_distance": 350,
"distances_between_points": [
{"from": 1, "to": 2, "distance": 150},
{"from": 2, "to": 3, "distance": 200}
]
}
"""
Scénario: Vérification cohérence itinéraire (alerte distance excessive)
Étant donné un audio-guide en mode "pieton"
Et deux points GPS distants de 465 km (Paris - Lyon)
Quand la cohérence est vérifiée
Alors un warning est retourné:
"""json
{
"warning": "distance_excessive",
"message": "Distance de 465 km entre séquences 2 et 3. Mode 'pieton' inapproprié.",
"suggested_mode": "voiture"
}
"""
# Distinction audio-guides vs contenus géolocalisés simples
Scénario: Pas de notification 7s avant pour audio-guides multi-séquences
Étant donné un audio-guide multi-séquences en mode voiture
Et que l'utilisateur approche du prochain point GPS
Quand la distance et ETA sont calculés
Alors aucun décompte "71" n'est déclenché
Et le déclenchement se fait au point GPS exact (rayon 30m)
Et une notification "Ding" + toast 2s est envoyée
Scénario: Notification 7s avant pour contenus géolocalisés simples (1 séquence)
Étant donné un contenu géolocalisé simple (1 séquence unique)
Et que l'utilisateur approche du point GPS
Quand l'ETA devient 7 secondes
Alors une notification avec compteur "71" est déclenchée
Et l'utilisateur doit valider avec bouton "Suivant"
# Exception quota pour audio-guides multi-séquences
Scénario: Audio-guide multi-séquences compte 1 seul contenu dans quota horaire
Étant donné un audio-guide "Visite Safari" avec 12 séquences
Et que l'utilisateur a un quota de 0/6 contenus géolocalisés
Quand l'utilisateur démarre l'audio-guide (séquence 1)
Alors le quota passe à 1/6
Quand l'utilisateur écoute les 12 séquences complètes
Alors le quota reste à 1/6
Et toutes les séquences ne consomment PAS 12 quotas
Et l'audio-guide entier compte comme 1 seul contenu
Scénario: Contenus géolocalisés simples consomment 1 quota chacun
Étant donné que l'utilisateur a un quota de 0/6
Quand l'utilisateur accepte un contenu géolocalisé simple "Tour Eiffel"
Alors le quota passe à 1/6
Quand l'utilisateur accepte un contenu géolocalisé simple "Arc de Triomphe"
Alors le quota passe à 2/6
Quand l'utilisateur accepte un contenu géolocalisé simple "Louvre"
Alors le quota passe à 3/6
Et chaque contenu simple consomme 1 quota
Scénario: Mixte audio-guides + contenus simples respecte quota 6/h
Étant donné que l'utilisateur a un quota de 0/6
Quand l'utilisateur démarre un audio-guide 8 séquences "Safari"
Alors le quota passe à 1/6
Quand l'utilisateur accepte 5 contenus géolocalisés simples
Alors le quota passe à 6/6
Et le quota horaire est atteint
Quand un 7ème contenu est détecté
Alors aucune notification n'est envoyée (quota atteint)
# Cache et optimisations
Scénario: Cache Redis pour calculs GPS fréquents
Étant donné que les points GPS d'un audio-guide sont en cache Redis
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/check-proximity"
Alors les points GPS sont récupérés depuis Redis (pas PostgreSQL)
Et le temps de réponse est < 50ms
Scénario: Geospatial GEORADIUS Redis pour recherche proximité
Étant donné que tous les audio-guides sont indexés dans Redis (GEOADD)
Et une position utilisateur (43.1234, 2.5678)
Quand je recherche les audio-guides dans un rayon de 5 km
Alors Redis GEORADIUS retourne les audio-guides proches
Et le temps de réponse est < 20ms
# Mise à jour position temps réel
Scénario: WebSocket pour mise à jour position en temps réel
Étant donné une connexion WebSocket active pour l'audio-guide
Quand l'utilisateur envoie sa nouvelle position via WS
Alors le serveur calcule immédiatement la proximité
Et retourne distance + ETA via WS (pas de polling HTTP)
Scénario: Throttling position updates (max 1/seconde)
Étant donné que le client envoie des positions GPS toutes les 200ms
Quand le serveur reçoit les mises à jour
Alors seules les positions espacées de >1 seconde sont traitées
Et les autres sont ignorées (throttling)
# Cas d'erreur
Scénario: Position GPS invalide (coordonnées hors limites)
Étant donné une position avec latitude 95.0000 (invalide)
Quand je fais un POST sur "/api/v1/audio-guides/{id}/check-proximity"
Alors le code HTTP de réponse est 400
Et le message d'erreur est "latitude: doit être entre -90 et 90"
Scénario: Audio-guide sans points GPS (mode piéton)
Étant donné un audio-guide en mode piéton sans points GPS
Quand je fais un POST sur "/api/v1/audio-guides/{id}/check-proximity"
Alors le code HTTP de réponse est 400
Et le message d'erreur est "Audio-guide en mode manuel, pas de déclenchement GPS"
Scénario: Séquence déjà complétée (skip calcul si utilisateur a déjà passé)
Étant donné que l'utilisateur est à la séquence 5
Et qu'il vérifie la proximité du point 3 (déjà écouté)
Quand je fais un POST sur "/api/v1/audio-guides/{id}/check-proximity"
Alors le calcul n'est pas effectué pour les séquences passées
Et le message "Séquence déjà écoutée" est retourné
Scénario: Précision GPS insuffisante
Étant donné une position avec accuracy ±150m
Et un rayon de déclenchement de 30m
Quand la précision est vérifiée
Alors un warning est retourné:
"""json
{
"warning": "low_gps_accuracy",
"message": "Précision GPS insuffisante (±150m). Déclenchement automatique peut être perturbé.",
"accuracy": 150,
"trigger_radius": 30
}
"""
# Performance
Scénario: Optimisation requêtes PostGIS avec index spatial
Étant donné que les points GPS ont un index GIST (PostGIS)
Quand une requête ST_DWithin est exécutée
Alors l'index spatial est utilisé
Et le temps d'exécution est < 10ms
Scénario: Batch proximity check pour tous les points
Étant donné un audio-guide avec 20 séquences
Quand je fais un POST sur "/api/v1/audio-guides/{id}/batch-proximity":
"""json
{
"user_position": {"latitude": 43.1234, "longitude": 2.5678}
}
"""
Alors toutes les distances sont calculées en une seule requête PostGIS
Et le corps de réponse contient:
"""json
{
"sequences": [
{"sequence_id": "seq_1", "distance": 0, "in_zone": true},
{"sequence_id": "seq_2", "distance": 150, "in_zone": false},
{"sequence_id": "seq_3", "distance": 350, "in_zone": false}
],
"current_sequence": 1,
"next_sequence": 2
}
"""

View File

@@ -0,0 +1,239 @@
# language: fr
@api @audio-guides @geolocation @mvp
Fonctionnalité: Détection automatique du mode de déplacement
En tant qu'utilisateur
Je veux que l'application détecte automatiquement mon mode de déplacement
Afin d'adapter l'expérience audio-guide (voiture, piéton, vélo, transports)
Contexte:
Étant donné que le système utilise les capteurs suivants pour la détection:
| Capteur | Utilisation |
| GPS (vitesse) | Vitesse de déplacement |
| Accéléromètre | Détection de la marche |
| Gyroscope | Détection de mouvements |
| Bluetooth | Connexion CarPlay/Android Auto |
| Activité (CoreMotion) | walking, running, cycling, automotive |
Scénario: Détection automatique du mode voiture
Étant donné un utilisateur "alice@roadwave.fr" en déplacement
Quand le système détecte les indicateurs suivants:
| Indicateur | Valeur |
| Vitesse GPS | 45 km/h |
| Accélération longitudinale | Typique d'une voiture|
| Bluetooth connecté | CarPlay |
| Activity Recognition | automotive |
| Stabilité du mouvement | Haute |
Alors le mode de déplacement "voiture" est sélectionné avec confiance: 95%
Et l'interface passe en mode voiture:
| Caractéristique | État |
| Notifications visuelles | Minimales |
| Notifications audio | Prioritaires |
| Affichage des distances | Mètres + temps ETA |
| Auto-play au point d'intérêt | Activé |
Et un événement "TRAVEL_MODE_DETECTED_CAR" est enregistré
Et la métrique "travel_mode.detected.car" est incrémentée
Scénario: Détection automatique du mode piéton
Étant donné un utilisateur "bob@roadwave.fr" en déplacement
Quand le système détecte les indicateurs suivants:
| Indicateur | Valeur |
| Vitesse GPS | 4 km/h |
| Accéléromètre | Pattern de marche |
| Fréquence de pas | 110 pas/min |
| Activity Recognition | walking |
| Bluetooth connecté | Non |
Alors le mode de déplacement "piéton" est sélectionné avec confiance: 92%
Et l'interface passe en mode piéton:
| Caractéristique | État |
| Notifications visuelles | Complètes |
| Navigation libre | Activée |
| Affichage carte | Complet |
| Auto-play publicité | Autorisé |
Et un événement "TRAVEL_MODE_DETECTED_WALKING" est enregistré
Et la métrique "travel_mode.detected.walking" est incrémentée
Scénario: Détection automatique du mode vélo
Étant donné un utilisateur "charlie@roadwave.fr" en déplacement
Quand le système détecte les indicateurs suivants:
| Indicateur | Valeur |
| Vitesse GPS | 18 km/h |
| Accéléromètre | Vibrations régulières|
| Pattern de mouvement | Cyclique |
| Activity Recognition | cycling |
| Variations de vitesse | Moyennes |
Alors le mode de déplacement "vélo" est sélectionné avec confiance: 88%
Et l'interface passe en mode vélo:
| Caractéristique | État |
| Notifications visuelles | Limitées |
| Notifications audio | Prioritaires |
| Affichage des distances | Mètres |
| Auto-play au point d'intérêt | Optionnel |
Et un événement "TRAVEL_MODE_DETECTED_CYCLING" est enregistré
Et la métrique "travel_mode.detected.cycling" est incrémentée
Scénario: Détection automatique du mode transports en commun
Étant donné un utilisateur "david@roadwave.fr" en déplacement
Quand le système détecte les indicateurs suivants:
| Indicateur | Valeur |
| Vitesse GPS | 35 km/h avec arrêts |
| Pattern d'arrêts | Régulier (stations) |
| Accéléromètre | Stationnaire par moments|
| Précision GPS | Variable (tunnels) |
| Activity Recognition | automotive + stationary |
Alors le mode de déplacement "transports" est sélectionné avec confiance: 80%
Et l'interface passe en mode transports:
| Caractéristique | État |
| Notifications visuelles | Complètes |
| Auto-play aux stations | Activé |
| Affichage carte | Complet |
| Prise en compte des tunnels | Activée |
Et un événement "TRAVEL_MODE_DETECTED_TRANSIT" est enregistré
Et la métrique "travel_mode.detected.transit" est incrémentée
Scénario: Changement dynamique de mode détecté (voiture → piéton)
Étant donné un utilisateur "eve@roadwave.fr" en mode voiture
Et il roule à 50 km/h
Quand l'utilisateur se gare et sort de la voiture:
| Temps | Vitesse | Activity | Bluetooth |
| T+0s | 50 km/h | automotive | CarPlay |
| T+30s | 0 km/h | stationary | CarPlay |
| T+60s | 0 km/h | stationary | Déconnecté|
| T+90s | 4 km/h | walking | Non |
Alors le mode bascule automatiquement de "voiture" à "piéton"
Et une notification discrète s'affiche: "Mode piéton activé"
Et l'interface s'adapte instantanément au mode piéton
Et un événement "TRAVEL_MODE_CHANGED" est enregistré avec transition: "car_to_walking"
Et la métrique "travel_mode.transition.car_to_walking" est incrémentée
Scénario: Changement dynamique de mode détecté (piéton → vélo)
Étant donné un utilisateur "frank@roadwave.fr" en mode piéton
Et il marche à 4 km/h
Quand l'utilisateur monte sur un vélo:
| Temps | Vitesse | Activity | Pattern |
| T+0s | 4 km/h | walking | Marche |
| T+10s | 8 km/h | cycling | Cyclique |
| T+20s | 15 km/h | cycling | Cyclique |
| T+30s | 18 km/h | cycling | Cyclique stable|
Alors le mode bascule automatiquement de "piéton" à "vélo"
Et une notification s'affiche: "Mode vélo activé"
Et les paramètres audio sont ajustés pour réduire les notifications visuelles
Et un événement "TRAVEL_MODE_CHANGED" est enregistré avec transition: "walking_to_cycling"
Et la métrique "travel_mode.transition.walking_to_cycling" est incrémentée
Scénario: Détection ambiguë avec faible confiance
Étant donné un utilisateur "grace@roadwave.fr" en déplacement
Quand le système détecte des indicateurs contradictoires:
| Indicateur | Valeur |
| Vitesse GPS | 12 km/h |
| Activity Recognition | unknown |
| Accéléromètre | Pattern irrégulier |
| Confiance de détection | 45% |
Alors le mode actuel est conservé (pas de changement)
Et une icône d'interrogation s'affiche discrètement
Et l'utilisateur peut forcer manuellement le mode via un menu rapide
Et un événement "TRAVEL_MODE_UNCERTAIN" est enregistré
Et la métrique "travel_mode.uncertain" est incrémentée
Scénario: Forçage manuel du mode de déplacement
Étant donné un utilisateur "henry@roadwave.fr" en mode auto-détecté "piéton"
Mais il est en réalité en voiture (passager)
Quand l'utilisateur ouvre le menu rapide et sélectionne "Mode voiture"
Alors le mode "voiture" est forcé manuellement
Et l'auto-détection est temporairement désactivée pour 30 minutes
Et un événement "TRAVEL_MODE_FORCED_MANUAL" est enregistré avec ancienMode: "walking", nouveauMode: "car"
Et la métrique "travel_mode.manual_override" est incrémentée
Et après 30 minutes, l'auto-détection se réactive automatiquement
Scénario: Mode stationnaire détecté (arrêt prolongé)
Étant donné un utilisateur "iris@roadwave.fr" en mode voiture
Et il est arrêté à un feu rouge depuis 2 minutes
Quand le système détecte:
| Indicateur | Valeur |
| Vitesse GPS | 0 km/h |
| Activity Recognition | stationary |
| Durée d'immobilité | 120 secondes |
| Bluetooth connecté | CarPlay |
Alors le mode reste "voiture" (pas de changement)
Mais un flag "stationary" est activé
Et l'audio en cours continue de jouer normalement
Et aucun nouveau contenu n'est déclenché automatiquement
Et un événement "TRAVEL_MODE_STATIONARY" est enregistré
Et la métrique "travel_mode.stationary" est incrémentée
Scénario: Reprise du mouvement après mode stationnaire
Étant donné un utilisateur "jack@roadwave.fr" en mode "voiture stationary"
Et il est arrêté depuis 3 minutes
Quand le système détecte:
| Temps | Vitesse | Activity |
| T+0s | 0 km/h | stationary |
| T+5s | 10 km/h | automotive |
| T+10s | 30 km/h | automotive |
Alors le flag "stationary" est désactivé
Et le mode "voiture" normal est restauré
Et la logique de déclenchement automatique des audio-guides est réactivée
Et un événement "TRAVEL_MODE_RESUMED" est enregistré
Et la métrique "travel_mode.resumed" est incrémentée
Scénario: Gestion des permissions de localisation et capteurs
Étant donné un utilisateur "kate@roadwave.fr" qui lance l'application
Quand les permissions suivantes sont refusées:
| Permission | État |
| Localisation GPS | Refusée |
| Motion & Fitness | Refusée |
Alors l'auto-détection du mode est désactivée
Et un message s'affiche: "Pour bénéficier de l'expérience optimale, activez les permissions de localisation et mouvement"
Et un bouton "Activer les permissions" redirige vers les Réglages
Et l'utilisateur doit sélectionner manuellement son mode de déplacement
Et un événement "TRAVEL_MODE_PERMISSIONS_DENIED" est enregistré
Et la métrique "travel_mode.permissions_denied" est incrémentée
Scénario: Optimisation de la batterie avec détection adaptative
Étant donné un utilisateur "luke@roadwave.fr" avec batterie < 20%
Quand le mode économie d'énergie est activé
Alors la fréquence de détection du mode est réduite:
| Mode normal | Mode économie d'énergie |
| Toutes les 5s | Toutes les 30s |
Et l'utilisation du GPS est optimisée (requêtes moins fréquentes)
Et l'accéléromètre et gyroscope sont consultés moins souvent
Et la précision de détection peut être légèrement réduite
Et un événement "TRAVEL_MODE_BATTERY_SAVER" est enregistré
Et la métrique "travel_mode.battery_saver.enabled" est incrémentée
Scénario: Historique des modes de déplacement pour statistiques
Étant donné un utilisateur "mary@roadwave.fr" qui utilise l'application depuis 1 mois
Quand l'utilisateur accède à "Mon compte > Statistiques > Modes de déplacement"
Alors l'utilisateur voit un graphique avec répartition:
| Mode | Temps total | Pourcentage |
| Voiture | 15h 30min | 45% |
| Piéton | 12h 10min | 35% |
| Vélo | 5h 20min | 15% |
| Transports | 1h 40min | 5% |
Et des insights sont affichés: "Vous utilisez principalement RoadWave en voiture"
Et les données sont conservées de manière agrégée pour respecter le RGPD
Scénario: Métriques de performance de la détection
Étant donné que le système traite 100 000 détections de mode par heure
Quand les métriques de performance sont collectées
Alors les indicateurs suivants sont respectés:
| Métrique | Valeur cible |
| Temps de détection du mode | < 100ms |
| Précision de détection (voiture) | > 95% |
| Précision de détection (piéton) | > 90% |
| Précision de détection (vélo) | > 85% |
| Taux de transitions incorrectes | < 5% |
| Consommation batterie par détection | < 0.01% |
Et les métriques sont exportées vers le système de monitoring
Et des alertes sont déclenchées si la précision < 80%
Scénario: A/B testing des algorithmes de détection
Étant donné que le système teste 2 algorithmes de détection:
| Algorithme | Description |
| A | Basé sur CoreMotion uniquement |
| B | Combinaison capteurs + ML |
Quand un utilisateur "nathan@roadwave.fr" est assigné au groupe B
Alors l'algorithme B est utilisé pour la détection
Et les métriques de précision sont tracées séparément par algorithme
Et les événements incluent le tag "algorithm_version: B"
Et après analyse, l'algorithme le plus performant est déployé à 100%

View File

@@ -0,0 +1,191 @@
# language: fr
@api @audio-guides @navigation @mvp
Fonctionnalité: Gestion des points d'intérêt manqués
En tant qu'utilisateur
Je veux pouvoir gérer les points d'intérêt que j'ai manqués
Afin de compléter mon expérience audio-guide même après avoir dépassé certains points
Contexte:
Étant donné que le système de gestion des points manqués respecte:
| Paramètre | Valeur |
| Distance max pour considérer "manqué" | 1 km |
| Temps max pour considérer "manqué" | 10 minutes |
| Possibilité de retour arrière | Oui |
| Lecture différée autorisée | Oui |
Scénario: Détection automatique d'un point manqué en mode voiture
Étant donné un utilisateur "alice@roadwave.fr" en mode voiture à 90 km/h
Et elle suit l'audio-guide "Route des Châteaux de la Loire"
Et le prochain point d'intérêt est le Château de Chaumont
Quand elle dépasse le château sans entrer dans son rayon de déclenchement (400m)
Et elle s'éloigne à plus de 1 km du point
Alors le système marque le point comme "Manqué"
Et une notification discrète s'affiche: "Point manqué : Château de Chaumont"
Et le point apparaît dans la section "Points manqués" de la liste
Et un événement "POI_MARKED_AS_MISSED" est enregistré avec raison: "out_of_range"
Et la métrique "poi.missed.out_of_range" est incrémentée
Scénario: Affichage de la liste des points manqués
Étant donné un utilisateur "bob@roadwave.fr" qui a manqué 3 points sur 10
Quand il ouvre la liste des séquences
Alors il voit une section dédiée "Points manqués (3)":
| Point d'intérêt | Distance actuelle | Actions |
| Château de Chaumont | 15 km en arrière | [Écouter] [Y retourner] |
| Musée de Cluny | 8 km en arrière | [Écouter] [Y retourner] |
| Rue Mouffetard | 2 km en arrière | [Écouter] [Y retourner] |
Et un compteur global affiche: "7/10 points visités"
Et un événement "MISSED_POIS_LIST_VIEWED" est enregistré
Et la métrique "missed_pois.list_viewed" est incrémentée
Scénario: Écoute différée d'un point manqué sans retour physique
Étant donné un utilisateur "charlie@roadwave.fr" qui a manqué le Château de Chaumont
Et il est maintenant à 20 km du château
Quand il clique sur "Écouter" dans la liste des points manqués
Alors l'audio du Château de Chaumont démarre immédiatement
Et un bandeau indique: "Écoute différée - Vous n'êtes pas sur place"
Et le point reste marqué comme "Manqué mais écouté"
Et un événement "MISSED_POI_LISTENED_REMOTE" est enregistré
Et la métrique "missed_poi.listened.remote" est incrémentée
Scénario: Navigation de retour vers un point manqué
Étant donné un utilisateur "david@roadwave.fr" qui a manqué le Musée de Cluny
Et il est à 5 km du musée
Quand il clique sur "Y retourner" dans la liste des points manqués
Alors l'application lance la navigation GPS vers le Musée de Cluny
Et un itinéraire est calculé et affiché
Et l'ETA est affiché: "12 min en voiture"
Et un événement "NAVIGATION_TO_MISSED_POI_STARTED" est enregistré
Et la métrique "missed_poi.navigation_started" est incrémentée
Scénario: Retour physique et déclenchement automatique d'un point manqué
Étant donné un utilisateur "eve@roadwave.fr" qui a manqué le Château de Chaumont
Et elle a cliqué sur "Y retourner"
Quand elle arrive dans le rayon de déclenchement du château (400m)
Alors l'audio du château démarre automatiquement
Et le point passe du statut "Manqué" à "Visité"
Et une notification de succès s'affiche: " Point complété : Château de Chaumont"
Et un événement "MISSED_POI_COMPLETED" est enregistré
Et la métrique "missed_poi.completed" est incrémentée
Scénario: Proposition automatique de retour pour points manqués à proximité
Étant donné un utilisateur "frank@roadwave.fr" qui a manqué la Rue Mouffetard
Et il continue son parcours et arrive près d'un autre point
Quand le système détecte qu'il est à 800m de la Rue Mouffetard
Alors une notification proactive s'affiche: "Point manqué à proximité : Rue Mouffetard (800m). Y aller ?"
Et deux boutons sont proposés: [Oui, y aller] [Non, continuer]
Et un événement "MISSED_POI_PROXIMITY_SUGGESTION" est enregistré
Et la métrique "missed_poi.proximity_suggestion" est incrémentée
Scénario: Ignorance volontaire d'un point manqué
Étant donné un utilisateur "grace@roadwave.fr" qui a manqué le Musée de Cluny
Et elle ne souhaite pas y retourner
Quand elle fait glisser le point vers la gauche dans la liste
Et clique sur "Ignorer définitivement"
Alors le point est retiré de la liste des points manqués
Et il passe au statut "Ignoré"
Et il ne sera plus proposé dans les suggestions
Et un événement "MISSED_POI_IGNORED" est enregistré
Et la métrique "missed_poi.ignored" est incrémentée
Scénario: Réinitialisation d'un point ignoré
Étant donné un utilisateur "henry@roadwave.fr" qui a ignoré le Musée de Cluny
Quand il accède aux paramètres de l'audio-guide
Et clique sur "Voir les points ignorés (1)"
Alors il voit la liste: "Musée de Cluny - Ignoré"
Quand il clique sur "Réactiver"
Alors le point repasse en statut "Manqué"
Et il réapparaît dans la liste des points manqués
Et un événement "MISSED_POI_REACTIVATED" est enregistré
Et la métrique "missed_poi.reactivated" est incrémentée
Scénario: Marquage automatique comme manqué après délai en mode piéton
Étant donné un utilisateur "iris@roadwave.fr" en mode piéton
Et elle est à 150m du Panthéon depuis 15 minutes (stationnaire)
Et elle n'a pas déclenché le point d'intérêt
Quand elle reprend sa marche et s'éloigne à plus de 500m
Alors le point est marqué comme "Manqué"
Et une notification s'affiche: "Point manqué : Panthéon. Voulez-vous y retourner ?"
Et un événement "POI_MARKED_AS_MISSED" est enregistré avec raison: "timeout_stationary"
Et la métrique "poi.missed.timeout" est incrémentée
Scénario: Statistiques des points manqués en fin de parcours
Étant donné un utilisateur "jack@roadwave.fr" qui a terminé un audio-guide
Et il a visité 7 points sur 10, manqué 2 points et ignoré 1 point
Quand il consulte l'écran de fin de parcours
Alors il voit les statistiques:
| Métrique | Valeur |
| Points visités | 7/10 (70%) |
| Points manqués | 2 (Chaumont, Cluny) |
| Points ignorés | 1 (Rue Mouffetard) |
| Taux de complétion | 70% |
| Badge obtenu | Explorateur Bronze |
Et un bouton "Compléter les points manqués" est proposé
Et un événement "AUDIO_GUIDE_STATS_VIEWED" est enregistré
Scénario: Reprise d'un audio-guide pour compléter les points manqués
Étant donné un utilisateur "kate@roadwave.fr" qui a terminé un audio-guide avec 2 points manqués
Quand elle clique sur "Compléter les points manqués"
Alors l'audio-guide est réactivé en mode "Rattrapage"
Et seuls les 2 points manqués sont actifs sur la carte
Et les points déjà visités sont grisés
Et la navigation se concentre uniquement sur les points manqués
Et un événement "AUDIO_GUIDE_CATCH_UP_MODE" est enregistré
Et la métrique "audio_guide.catch_up.started" est incrémentée
Scénario: Badge de complétion "Perfectionniste" pour 100% de complétion
Étant donné un utilisateur "luke@roadwave.fr" qui a visité 10/10 points
Et il a initialement manqué 2 points mais y est retourné
Quand il termine l'audio-guide avec 100% de complétion
Alors un badge spécial "Perfectionniste" est débloqué
Et une animation de célébration est affichée
Et un événement "BADGE_PERFECTIONIST_UNLOCKED" est enregistré
Et la métrique "badges.perfectionist.unlocked" est incrémentée
Scénario: Notification push après 24h pour rappel des points manqués
Étant donné un utilisateur "mary@roadwave.fr" qui a terminé un audio-guide hier
Et elle a manqué 3 points sur 10
Quand 24 heures se sont écoulées depuis la fin du parcours
Alors une notification push est envoyée:
"Vous avez manqué 3 points lors de votre visite du Quartier Latin. Voulez-vous les découvrir ?"
Et un lien direct vers la liste des points manqués est inclus
Et un événement "MISSED_POIS_REMINDER_SENT" est enregistré
Et la métrique "missed_pois.reminder_sent" est incrémentée
Scénario: Mode "Rattrapage intelligent" avec optimisation de l'itinéraire
Étant donné un utilisateur "nathan@roadwave.fr" avec 3 points manqués:
| Point | Position actuelle | Distance |
| Château de Chaumont | 48.8475, 2.3450 | 12 km |
| Musée de Cluny | 48.8505, 2.3434 | 8 km |
| Rue Mouffetard | 48.8429, 2.3498 | 5 km |
Quand il clique sur "Itinéraire optimisé pour rattrapage"
Alors le système calcule l'itinéraire le plus court pour visiter les 3 points:
| Ordre | Point | Distance cumulée |
| 1 | Rue Mouffetard | 5 km |
| 2 | Musée de Cluny | 8.5 km |
| 3 | Château de Chaumont | 20.5 km |
Et l'ETA total est affiché: "1h 15min en voiture"
Et un événement "OPTIMIZED_CATCH_UP_ROUTE_CALCULATED" est enregistré
Et la métrique "missed_pois.optimized_route" est incrémentée
Scénario: Désactivation de la détection automatique des points manqués
Étant donné un utilisateur "olive@roadwave.fr" qui préfère une expérience libre
Quand elle active l'option "Désactiver la détection des points manqués"
Alors les points ne sont jamais marqués comme "Manqués"
Et aucune notification de point manqué n'est affichée
Et l'utilisateur peut toujours écouter tous les points manuellement
Et un événement "MISSED_POIS_DETECTION_DISABLED" est enregistré
Et la métrique "missed_pois.detection_disabled" est incrémentée
Scénario: Métriques de performance de la gestion des points manqués
Étant donné que 10 000 utilisateurs ont terminé des audio-guides
Quand les métriques sont collectées
Alors les indicateurs suivants sont disponibles:
| Métrique | Valeur moyenne |
| Pourcentage de points manqués par parcours| 18% |
| Taux de retour aux points manqués | 35% |
| Taux d'écoute différée (sans retour) | 55% |
| Taux d'ignorance définitive | 10% |
| Taux de complétion après rattrapage | 92% |
Et les métriques sont exportées vers le système de monitoring

View File

@@ -0,0 +1,349 @@
# language: fr
Fonctionnalité: Intégration audio-guides avec autres fonctionnalités
En tant qu'utilisateur
Je veux utiliser les audio-guides avec toutes les fonctionnalités de l'app
Afin d'avoir une expérience complète et cohérente
Contexte:
Étant donné que l'application RoadWave est démarrée
Et que l'utilisateur "jean@example.com" est connecté
# 16.14 - Téléchargement offline
Scénario: Téléchargement complet d'un audio-guide
Étant donné un audio-guide "Visite du Louvre" avec 12 séquences
Quand l'utilisateur clique sur " Télécharger pour écouter hors ligne"
Alors toutes les 12 séquences sont téléchargées
Et les métadonnées (titres, descriptions, GPS) sont sauvegardées
Et les images (cover, miniatures) sont mises en cache
Scénario: Affichage de la progression du téléchargement
Étant donné qu'un téléchargement d'audio-guide est en cours
Quand l'utilisateur consulte l'état
Alors la progression s'affiche:
"""
Téléchargement en cours...
Séquence 7/12 245 MB / 380 MB
64%
"""
Scénario: Téléchargement uniquement en WiFi (par défaut)
Étant donné que l'option "Télécharger uniquement en WiFi" est activée
Quand l'utilisateur lance un téléchargement sur réseau mobile
Alors un avertissement s'affiche:
"""
Téléchargement nécessite WiFi
Cet audio-guide pèse 380 MB.
[Attendre WiFi] [Télécharger quand même]
"""
Scénario: Gestion de l'espace de stockage
Étant donné que l'appareil a 500 MB d'espace libre
Et qu'un audio-guide pèse 380 MB
Quand l'utilisateur lance le téléchargement
Alors un avertissement s'affiche:
"""
Espace de stockage limité
Après téléchargement : 120 MB restants
[Continuer] [Gérer stockage] [Annuler]
"""
Scénario: Liste des audio-guides téléchargés
Étant donné que l'utilisateur a téléchargé 3 audio-guides
Quand il accède à "Bibliothèque > Téléchargés"
Alors il voit:
| audio_guide | taille | date_telechargement |
| Visite du Louvre | 380 MB | 2026-01-20 |
| Safari du Paugre | 245 MB | 2026-01-18 |
| Circuit Loire à Vélo | 520 MB | 2026-01-15 |
Scénario: Lecture hors connexion complète
Étant donné qu'un audio-guide est téléchargé
Et que l'utilisateur active le mode avion
Quand il lance l'audio-guide
Alors toutes les séquences sont lisibles
Et les métadonnées sont accessibles
Et les images s'affichent normalement
Et la progression est sauvegardée localement
Scénario: GPS fonctionne en mode avion (mode voiture)
Étant donné qu'un audio-guide voiture est téléchargé
Et que le mode avion est activé (avec GPS actif)
Quand l'utilisateur se déplace
Alors les déclenchements GPS fonctionnent normalement
Et la distance/ETA sont calculés
Parce que le GPS ne nécessite pas de connexion internet
Scénario: Suppression d'audio-guide téléchargé
Étant donné qu'un audio-guide téléchargé pèse 380 MB
Quand l'utilisateur clique sur "🗑 Supprimer téléchargement"
Alors une confirmation s'affiche
Et si confirmé, les 380 MB sont libérés
Et l'audio-guide reste accessible en streaming
Scénario: Mise à jour automatique si nouvelle version
Étant donné qu'un audio-guide téléchargé a été mis à jour par le créateur
Quand l'utilisateur se connecte en WiFi
Alors une notification s'affiche:
"""
🔄 Mise à jour disponible
"Visite du Louvre" - Nouvelle version
[Mettre à jour] [Plus tard]
"""
# 16.15 - Playlists et collections
Scénario: Ajout d'audio-guide à une playlist
Étant donné que l'utilisateur consulte un audio-guide
Quand il clique sur " Ajouter à une playlist"
Alors ses playlists s'affichent:
| playlist |
| 🗺 Voyages en France |
| 🏛 Musées parisiens |
| + Créer nouvelle playlist |
Scénario: Comportement audio-guide dans une playlist
Étant donné une playlist contenant 2 audio-guides et 1 podcast
Quand la lecture atteint un audio-guide
Alors l'audio-guide démarre à la séquence 1 (ou progression sauvegardée)
Et les séquences se jouent normalement
Quand l'audio-guide se termine (dernière séquence)
Alors le contenu suivant de la playlist démarre
Scénario: Audio-guide marqué comme "Favori"
Étant donné qu'un utilisateur aime un audio-guide
Quand il clique sur " Ajouter aux favoris"
Alors l'audio-guide est ajouté à la section "Favoris"
Et il est facilement accessible depuis le menu principal
Scénario: Collections thématiques d'audio-guides
Étant donné que RoadWave propose des collections éditoriales
Quand l'utilisateur accède à "Collections"
Alors il voit des collections comme:
| collection | nombre_audio_guides |
| 🏛 Musées de France | 12 |
| 🦁 Parcs animaliers | 8 |
| 🚴 Circuits vélo | 15 |
| 🚗 Routes touristiques | 10 |
# 16.16 - Partage d'audio-guide
Scénario: Bouton partager sur page audio-guide
Étant donné qu'un utilisateur consulte un audio-guide
Quand il clique sur " Partager"
Alors le menu de partage natif s'ouvre
Et le lien généré est "https://roadwave.fr/share/ag/louvre_123"
Scénario: Page web de partage pour audio-guide
Étant donné qu'un lien d'audio-guide partagé est ouvert sur le web
Quand la page se charge
Alors elle affiche:
| élément | exemple |
| Cover image 16:9 | Photo du Louvre |
| Titre | "Visite du Louvre" |
| Créateur | "@art_guide " |
| Badge type | "🎧 Audio-guide 12 séquences" |
| Durée totale | "45 minutes" |
| Mode | "🚶 Piéton" |
| Description | Texte complet |
| Preview séquence 1 | Player HTML5 (séquence intro) |
| Carte avec points GPS | Leaflet avec 12 markers |
| CTA téléchargement | Boutons App Store / Google Play |
Scénario: Deep link vers audio-guide spécifique
Étant donné que l'app est installée
Et qu'un lien "https://roadwave.fr/share/ag/louvre_123" est cliqué
Quand le système détecte l'app
Alors l'app s'ouvre directement sur l'audio-guide
Et l'utilisateur peut démarrer immédiatement
Scénario: Partage avec séquence spécifique
Étant donné qu'un utilisateur est sur la séquence 5 "La Joconde"
Quand il partage l'audio-guide
Alors le lien généré est "https://roadwave.fr/share/ag/louvre_123?seq=5"
Et le destinataire est dirigé vers la séquence 5 directement
# 16.17 - Notations et commentaires
Scénario: Note globale de l'audio-guide
Étant donné qu'un utilisateur termine un audio-guide
Quand la dernière séquence se termine
Alors une popup de notation s'affiche:
"""
Comment avez-vous trouvé cet audio-guide ?
[Ajouter un commentaire (optionnel)]
"""
Scénario: Note moyenne affichée sur la page
Étant donné qu'un audio-guide a reçu 150 notes
Et que la moyenne est 4.3/5
Quand la page est affichée
Alors la note " 4.3 (150 avis)" est visible
Scénario: Commentaires triés par pertinence
Étant donné qu'un audio-guide a 50 commentaires
Quand l'utilisateur consulte les avis
Alors les commentaires sont triés par défaut selon:
| critère | poids |
| Note élevée | 30% |
| Récent | 30% |
| Likes reçus | 40% |
Scénario: Réponse du créateur aux commentaires
Étant donné qu'un utilisateur laisse un commentaire négatif
Quand le créateur consulte son dashboard
Alors il peut répondre au commentaire
Et sa réponse apparaît en dessous avec badge "Créateur"
# 16.18 - Recommandations intelligentes
Scénario: Audio-guides similaires recommandés
Étant donné qu'un utilisateur termine "Visite du Louvre"
Quand il consulte les recommandations
Alors l'algorithme suggère des audio-guides basés sur:
| critère | exemple |
| Tags similaires | #Art #Histoire #Musée |
| Créateur identique | Autres audio-guides de @art_guide |
| Localisation proche | Autres musées parisiens |
| Mode de déplacement | Autres audio-guides piéton |
Scénario: Suggestion géographique contextuelle
Étant donné qu'un utilisateur est à Paris (GPS détecté)
Quand il ouvre l'onglet "Audio-guides"
Alors les audio-guides parisiens sont mis en avant
Et un filtre "🗺 Autour de moi" est pré-appliqué
Scénario: Badge "Populaire dans votre région"
Étant donné qu'un audio-guide a >100 écoutes dans la région Île-de-France
Et que l'utilisateur est en Île-de-France
Quand l'audio-guide est affiché
Alors un badge "🔥 Populaire près de chez vous" est visible
# 16.19 - Optimisations techniques
Scénario: Préchargement de la séquence suivante
Étant donné que la séquence 3 est en cours à 2:30/3:42
Quand il reste 60 secondes de lecture
Alors la séquence 4 est préchargée en arrière-plan
Et la transition est instantanée (0 latence)
Scénario: Buffer adaptatif selon connexion
Étant donné qu'un utilisateur est sur réseau 4G
Quand la séquence démarre
Alors 30 secondes d'audio sont bufferisées initialement
Et le buffering continue en arrière-plan
Plan du Scénario: Buffer selon qualité réseau
Étant donné qu'un utilisateur est sur réseau <reseau>
Quand une séquence démarre
Alors <buffer_secondes> secondes sont bufferisées
Exemples:
| reseau | buffer_secondes |
| WiFi | 60 |
| 5G | 45 |
| 4G | 30 |
| 3G | 20 |
Scénario: Compression audio adaptative
Étant donné qu'un utilisateur est sur connexion lente (3G)
Quand une séquence est streamée
Alors le CDN sert la version 64 kbps (au lieu de 128 kbps)
Et la qualité reste acceptable pour la voix
Scénario: Cache intelligent des séquences jouées
Étant donné qu'un utilisateur a écouté les séquences 1-5
Quand il clique sur "Précédent" pour réécouter la séquence 4
Alors la séquence 4 est chargée depuis le cache local
Et le chargement est instantané (pas de stream)
Scénario: Nettoyage automatique du cache
Étant donné que le cache audio occupe 500 MB
Et que la limite configurée est 300 MB
Quand le nettoyage automatique s'exécute
Alors les séquences les plus anciennes (non téléchargées) sont supprimées
Et le cache revient à 280 MB
# 16.20 - Analytics et tracking
Scénario: Tracking des événements clés
Étant donné qu'un utilisateur écoute un audio-guide
Quand il interagit avec l'application
Alors les événements suivants sont trackés:
| événement | données |
| audio_guide_started | audio_guide_id, mode, user_id |
| sequence_completed | sequence_id, completion_rate, duration |
| audio_guide_completed | audio_guide_id, total_time, sequences_count|
| point_gps_triggered | point_id, distance, auto_or_manual |
| point_gps_missed | point_id, distance, action_taken |
| paywall_displayed | audio_guide_id, sequence_number |
| premium_conversion | source: audio_guide_paywall |
Scénario: Heatmap des abandons par séquence
Étant donné qu'un audio-guide a été écouté 1000 fois
Quand le créateur consulte la heatmap
Alors il voit pour chaque séquence:
| sequence | starts | completions | abandon_rate |
| 1 | 1000 | 950 | 5% |
| 2 | 950 | 920 | 3% |
| 3 | 920 | 850 | 8% |
| ... | ... | ... | ... |
| 12 | 650 | 580 | 11% |
Scénario: Attribution GPS auto vs manuel
Étant donné un audio-guide voiture avec 8 points GPS
Quand les statistiques sont calculées
Alors le créateur voit:
| mode_declenchement | nombre |
| GPS automatique | 542 |
| Manuel | 123 |
| Point manqué | 89 |
# Cas d'erreur et edge cases
Scénario: Audio-guide avec une seule séquence (edge case)
Étant donné un audio-guide avec seulement 1 séquence
Quand il est publié
Alors un avertissement s'affiche:
"""
Un audio-guide doit contenir au minimum 2 séquences
Ajoutez au moins 1 séquence supplémentaire avant publication
"""
Scénario: Séquence manquante ou corrompue
Étant donné qu'une séquence 5 a un fichier audio corrompu
Quand l'utilisateur tente de la lire
Alors un message d'erreur s'affiche
Et un bouton " Passer à la suivante" est disponible
Et le créateur reçoit une notification de l'erreur
Scénario: GPS désactivé puis réactivé en cours de route
Étant donné un audio-guide voiture en cours
Et que l'utilisateur désactive le GPS
Quand il le réactive 10 minutes plus tard
Alors le déclenchement automatique reprend
Et les points GPS manqués entre-temps ne déclenchent pas de popup
Scénario: Modification d'audio-guide avec utilisateurs en cours
Étant donné qu'un audio-guide a 50 utilisateurs en cours d'écoute
Quand le créateur modifie une séquence
Alors les utilisateurs actuels conservent l'ancienne version
Et les nouveaux utilisateurs obtiennent la nouvelle version
Et un message informe les utilisateurs lors de la prochaine ouverture
Scénario: Suppression d'audio-guide par le créateur
Étant donné qu'un audio-guide a 20 utilisateurs avec progression
Quand le créateur supprime l'audio-guide
Alors une confirmation stricte est demandée
Et si confirmé, les progressions utilisateurs sont archivées (30 jours)
Et l'audio-guide devient inaccessible
Scénario: Signalement d'audio-guide pour contenu inapproprié
Étant donné qu'un utilisateur signale un audio-guide
Quand le signalement est modéré
Et jugé valide
Alors l'audio-guide est dépublié temporairement
Et le créateur reçoit une notification d'explication
Et il peut corriger puis republier

View File

@@ -0,0 +1,485 @@
# language: fr
Fonctionnalité: API - Métriques et analytics audio-guides
En tant que système backend
Je veux collecter et exposer les métriques d'écoute des audio-guides
Afin de fournir des insights aux créateurs
Contexte:
Étant donné que l'API RoadWave est démarrée
Et que le créateur "creator@example.com" est authentifié
# Statistiques globales audio-guide
Scénario: GET /api/v1/creators/me/audio-guides/{id}/stats - Statistiques générales
Étant donné un audio-guide "ag_123" avec les métriques suivantes:
| ecoutes_totales | ecoutes_completes | taux_completion | temps_ecoute_total |
| 1542 | 892 | 58% | 423h |
Quand je fais un GET sur "/api/v1/creators/me/audio-guides/ag_123/stats"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"listens_total": 1542,
"listens_complete": 892,
"completion_rate": 58,
"total_listen_time_seconds": 1522800,
"avg_listen_time_seconds": 988,
"unique_listeners": 1124,
"repeat_listeners": 418
}
"""
Scénario: Statistiques par période (7j, 30j, 90j, all-time)
Étant donné un audio-guide avec historique
Quand je fais un GET sur "/api/v1/creators/me/audio-guides/ag_123/stats?period=7d"
Alors le code HTTP de réponse est 200
Et les statistiques sur les 7 derniers jours sont retournées
Plan du Scénario: Périodes disponibles
Quand je fais un GET avec period=<period>
Alors les stats de la période <description> sont retournées
Exemples:
| period | description |
| 7d | 7 derniers jours |
| 30d | 30 derniers jours |
| 90d | 90 derniers jours |
| all | Depuis la création |
# Métriques par séquence
Scénario: GET /api/v1/creators/me/audio-guides/{id}/sequences/stats - Stats par séquence
Étant donné un audio-guide de 12 séquences
Et les métriques suivantes:
| sequence | starts | completions | abandon_rate |
| 1 | 1000 | 950 | 5% |
| 2 | 950 | 920 | 3% |
| 3 | 920 | 850 | 8% |
Quand je fais un GET sur "/api/v1/creators/me/audio-guides/ag_123/sequences/stats"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"sequences": [
{
"sequence_id": "seq_1",
"sequence_number": 1,
"title": "Introduction",
"starts": 1000,
"completions": 950,
"completion_rate": 95,
"abandon_rate": 5,
"avg_listen_time": 132,
"duration": 135
},
{
"sequence_id": "seq_2",
"sequence_number": 2,
"title": "Pyramide du Louvre",
"starts": 950,
"completions": 920,
"completion_rate": 97,
"abandon_rate": 3,
"avg_listen_time": 106,
"duration": 108
}
]
}
"""
Scénario: Identification séquence la plus écoutée
Étant donné les statistiques par séquence
Quand je fais un GET sur "/api/v1/creators/me/audio-guides/ag_123/sequences/top"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"most_listened": {
"sequence_id": "seq_3",
"title": "La Joconde",
"starts": 920,
"reason": "popular_highlight"
},
"least_listened": {
"sequence_id": "seq_11",
"title": "Aile Richelieu",
"starts": 580,
"reason": "late_sequence"
}
}
"""
# Points d'abandon
Scénario: Détection point d'abandon critique
Étant donné un audio-guide avec taux de complétion 58%
Et que 35% des utilisateurs abandonnent à la séquence 7
Quand je fais un GET sur "/api/v1/creators/me/audio-guides/ag_123/abandon-analysis"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"critical_abandon_point": {
"sequence_id": "seq_7",
"sequence_number": 7,
"title": "Aile Richelieu",
"abandon_rate": 35,
"severity": "high",
"suggestion": "Réduire la durée (8 min actuellement) ou rendre plus captivant"
}
}
"""
Scénario: Heatmap des abandons
Étant donné un audio-guide
Quand je fais un GET sur "/api/v1/creators/me/audio-guides/ag_123/abandon-heatmap"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient une heatmap:
"""json
{
"heatmap": [
{"sequence": 1, "abandon_count": 50, "intensity": "low"},
{"sequence": 2, "abandon_count": 30, "intensity": "low"},
{"sequence": 7, "abandon_count": 320, "intensity": "high"},
{"sequence": 12, "abandon_count": 70, "intensity": "medium"}
]
}
"""
# Métriques géographiques
Scénario: GET /api/v1/creators/me/audio-guides/{id}/geographic-stats - Stats géo
Étant donné un audio-guide géolocalisé
Et les écoutes suivantes par région:
| region | listens | completions |
| Île-de-France | 850 | 520 |
| PACA | 320 | 180 |
| Auvergne | 145 | 90 |
Quand je fais un GET sur "/api/v1/creators/me/audio-guides/ag_123/geographic-stats"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"by_region": [
{
"region": "Île-de-France",
"listens": 850,
"completions": 520,
"completion_rate": 61
},
{
"region": "PACA",
"listens": 320,
"completions": 180,
"completion_rate": 56
}
]
}
"""
Scénario: Heatmap géographique des écoutes
Étant donné un audio-guide avec points GPS
Quand je fais un GET sur "/api/v1/creators/me/audio-guides/ag_123/geographic-heatmap"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"points": [
{
"sequence_id": "seq_1",
"gps_point": {"lat": 43.1234, "lon": 2.5678},
"listen_count": 1000,
"density": "high"
},
{
"sequence_id": "seq_2",
"gps_point": {"lat": 43.1245, "lon": 2.5690},
"listen_count": 950,
"density": "high"
}
]
}
"""
# Métriques déclenchement GPS
Scénario: Attribution GPS auto vs manuel
Étant donné un audio-guide voiture avec 8 points GPS
Et les déclenchements suivants:
| type | count |
| GPS auto | 542 |
| Manuel | 123 |
| Point manqué | 89 |
Quand je fais un GET sur "/api/v1/creators/me/audio-guides/ag_123/trigger-stats"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"total_triggers": 754,
"by_type": {
"gps_auto": 542,
"manual": 123,
"missed_point": 89
},
"gps_auto_rate": 72,
"manual_rate": 16,
"missed_rate": 12
}
"""
Scénario: Points GPS les plus manqués
Étant donné les statistiques de points manqués
Quand je fais un GET sur "/api/v1/creators/me/audio-guides/ag_123/missed-points"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"most_missed": [
{
"sequence_id": "seq_5",
"title": "Enclos des éléphants",
"missed_count": 45,
"missed_rate": 12,
"suggestion": "Rayon trop petit (30m) ou point mal placé"
}
]
}
"""
# Temps moyen par séquence
Scénario: Comparaison durée audio vs temps d'écoute moyen
Étant donné les métriques temporelles suivantes:
| sequence | duration | avg_listen_time | ecart |
| 1 | 135 | 130 | -5s |
| 2 | 108 | 90 | -18s |
| 3 | 222 | 220 | -2s |
Quand je fais un GET sur "/api/v1/creators/me/audio-guides/ag_123/time-analysis"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"sequences": [
{
"sequence_id": "seq_1",
"duration_seconds": 135,
"avg_listen_time": 130,
"delta": -5,
"completion_avg": 96
},
{
"sequence_id": "seq_2",
"duration_seconds": 108,
"avg_listen_time": 90,
"delta": -18,
"completion_avg": 83,
"warning": "Séquence souvent skippée ou abandonnée avant la fin"
}
]
}
"""
# Notifications milestones
Scénario: POST /api/v1/audio-guides/{id}/milestones/check - Vérification milestone
Étant donné qu'un audio-guide atteint 1000 écoutes
Quand le système vérifie les milestones
Alors un événement "milestone_reached" est émis
Et une notification est envoyée au créateur:
"""json
{
"type": "milestone",
"milestone_type": "listens_1000",
"audio_guide_id": "ag_123",
"message": "Félicitations ! Votre audio-guide 'Visite du Louvre' a atteint 1000 écoutes !",
"stats": {
"listens": 1000,
"completion_rate": 58
}
}
"""
Plan du Scénario: Milestones prédéfinis
Étant donné qu'un audio-guide atteint <seuil> écoutes
Quand le milestone est vérifié
Alors une notification "<type>" est envoyée
Exemples:
| seuil | type |
| 100 | listens_100 |
| 500 | listens_500 |
| 1000 | listens_1000 |
| 5000 | listens_5000 |
| 10000 | listens_10000 |
# Graphiques et visualisations
Scénario: GET /api/v1/creators/me/audio-guides/{id}/completion-funnel - Entonnoir complétion
Étant donné un audio-guide de 12 séquences
Quand je fais un GET sur "/api/v1/creators/me/audio-guides/ag_123/completion-funnel"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient un graphique en entonnoir:
"""json
{
"funnel": [
{"sequence": 1, "listeners": 1000, "percentage": 100},
{"sequence": 2, "listeners": 950, "percentage": 95},
{"sequence": 3, "listeners": 890, "percentage": 89},
{"sequence": 12, "listeners": 580, "percentage": 58}
]
}
"""
Scénario: GET /api/v1/creators/me/audio-guides/{id}/listens-over-time - Écoutes dans le temps
Étant donné un audio-guide avec historique
Quand je fais un GET sur "/api/v1/creators/me/audio-guides/ag_123/listens-over-time?period=30d"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient une série temporelle:
"""json
{
"period": "30d",
"granularity": "day",
"data_points": [
{"date": "2026-01-01", "listens": 45, "completions": 28},
{"date": "2026-01-02", "listens": 52, "completions": 31},
{"date": "2026-01-03", "listens": 38, "completions": 24}
]
}
"""
# Comparaisons et benchmarks
Scénario: GET /api/v1/creators/me/audio-guides/compare - Comparaison audio-guides
Étant donné que le créateur a 3 audio-guides:
| audio_guide_id | title | listens | completion_rate |
| ag_1 | Tour de Paris | 1200 | 65% |
| ag_2 | Visite Louvre | 1542 | 58% |
| ag_3 | Safari du Paugre | 890 | 72% |
Quand je fais un GET sur "/api/v1/creators/me/audio-guides/compare"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"audio_guides": [
{
"audio_guide_id": "ag_2",
"title": "Visite Louvre",
"listens": 1542,
"completion_rate": 58,
"rank_by_listens": 1,
"rank_by_completion": 2
},
{
"audio_guide_id": "ag_1",
"title": "Tour de Paris",
"listens": 1200,
"completion_rate": 65,
"rank_by_listens": 2,
"rank_by_completion": 1
}
]
}
"""
Scénario: Benchmark par rapport à la moyenne plateforme
Étant donné qu'un audio-guide a un taux de complétion de 58%
Et que la moyenne plateforme pour la catégorie "musée" est 62%
Quand je fais un GET sur "/api/v1/creators/me/audio-guides/ag_123/benchmark"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"your_completion_rate": 58,
"category_avg": 62,
"platform_avg": 60,
"performance": "below_category_avg",
"percentile": 45
}
"""
# Événements trackés
Scénario: POST /api/v1/events/track - Tracking événements utilisateur
Étant donné qu'un utilisateur interagit avec un audio-guide
Quand un événement se produit
Alors il est tracké avec les données suivantes:
| événement | données |
| audio_guide_started | audio_guide_id, mode, user_id |
| sequence_completed | sequence_id, completion_rate, duration |
| audio_guide_completed | audio_guide_id, total_time, sequences_count|
| point_gps_triggered | point_id, distance, auto_or_manual |
| point_gps_missed | point_id, distance, action_taken |
Scénario: Exemple événement audio_guide_started
Quand un audio-guide démarre
Alors l'événement suivant est envoyé:
"""json
{
"event_type": "audio_guide_started",
"timestamp": "2026-01-22T14:00:00Z",
"user_id": "user_456",
"audio_guide_id": "ag_123",
"mode": "voiture",
"device": "ios",
"location": {"lat": 43.1234, "lon": 2.5678}
}
"""
Scénario: Exemple événement point_gps_triggered
Quand un point GPS déclenche une séquence
Alors l'événement suivant est envoyé:
"""json
{
"event_type": "point_gps_triggered",
"timestamp": "2026-01-22T14:05:30Z",
"user_id": "user_456",
"audio_guide_id": "ag_123",
"sequence_id": "seq_2",
"trigger_type": "gps_auto",
"distance_to_point": 25,
"speed_kmh": 28
}
"""
# Export données
Scénario: GET /api/v1/creators/me/audio-guides/{id}/export - Export CSV
Étant donné un audio-guide avec historique complet
Quand je fais un GET sur "/api/v1/creators/me/audio-guides/ag_123/export?format=csv"
Alors le code HTTP de réponse est 200
Et le Content-Type est "text/csv"
Et le fichier CSV contient:
| user_id | sequence_id | started_at | completed_at | completion_rate |
| user_123 | seq_1 | 2026-01-22 14:10:00 | 2026-01-22 14:12:15 | 100 |
| user_123 | seq_2 | 2026-01-22 14:12:20 | 2026-01-22 14:14:08 | 100 |
Scénario: Export JSON pour analyse externe
Quand je fais un GET sur "/api/v1/creators/me/audio-guides/ag_123/export?format=json"
Alors le code HTTP de réponse est 200
Et le Content-Type est "application/json"
Et le fichier JSON contient toutes les métriques détaillées
# Cache et performance
Scénario: Cache Redis pour stats fréquemment consultées
Étant donné que les stats globales d'un audio-guide sont en cache Redis
Quand je fais un GET sur "/api/v1/creators/me/audio-guides/ag_123/stats"
Alors les stats sont récupérées depuis Redis (pas PostgreSQL)
Et le temps de réponse est < 50ms
Et le cache a un TTL de 5 minutes
Scénario: Invalidation cache lors de nouvelles écoutes
Étant donné que les stats sont en cache Redis
Quand une nouvelle écoute complète est enregistrée
Alors le cache Redis est invalidé pour cet audio-guide
Et le prochain GET recalcule les stats depuis PostgreSQL
Scénario: Pré-calcul stats quotidien (job batch)
Étant donné que le job batch s'exécute chaque nuit à 3h
Quand le job démarre
Alors pour chaque audio-guide actif:
- Les stats sont calculées depuis les événements bruts
- Les résultats sont stockés dans audio_guide_stats_daily
- Les agrégations (7j, 30j, 90j) sont pré-calculées
Et les requêtes du lendemain sont instantanées (lecture table pré-calculée)

View File

@@ -0,0 +1,243 @@
# language: fr
Fonctionnalité: Audio-guide mode piéton (navigation manuelle)
En tant qu'utilisateur à pied
Je veux naviguer manuellement entre les séquences d'un audio-guide
Afin de contrôler mon rythme de visite
Contexte:
Étant donné que l'application RoadWave est démarrée
Et que l'utilisateur "jean@example.com" est connecté (gratuit)
Et qu'un audio-guide piéton "Visite du Louvre" est disponible avec 12 séquences
# 16.2.1 - Passage entre séquences
Scénario: Fin de séquence normale avec pause automatique
Étant donné que la séquence 1 "Introduction" est en cours de lecture
Quand la séquence se termine à 2:15
Alors le player se met en pause automatiquement
Et le message suivant s'affiche: "Séquence 1 terminée. Appuyez sur Suivant quand vous êtes prêt."
Et la barre de progression indique "1/12 complétée"
Scénario: Passage manuel à la séquence suivante
Étant donné que la séquence 1 est terminée et le player en pause
Quand l'utilisateur appuie sur le bouton [|] "Suivant"
Alors la séquence 2 "Pyramide du Louvre" démarre immédiatement
Et aucune latence n'est observée
Scénario: Séquence avec publicité (1/5 séquences)
Étant donné que la séquence 5 se termine
Et que c'est la 5ème séquence (1 pub toutes les 5)
Quand la séquence se termine
Alors la publicité s'enchaîne automatiquement (sans attente bouton)
Et la publicité se lit normalement
Et elle est skippable après 5 secondes
Scénario: Fin de publicité avec pause automatique
Étant donné qu'une publicité est en cours de lecture
Quand la publicité se termine
Alors le player se met en pause automatiquement
Et le message suivant s'affiche: "Séquence 6 prête. Appuyez sur Suivant."
Et l'utilisateur doit cliquer sur [|] pour continuer
Scénario: Flux complet séquence → pub → séquence
Étant donné que la séquence 5 démarre
Quand la séquence 5 se termine
Alors la publicité démarre automatiquement
Quand la publicité se termine
Alors le player se met en pause
Quand l'utilisateur clique sur [|]
Alors la séquence 6 démarre
Plan du Scénario: Fréquence de publicité configurable
Étant donné que l'utilisateur gratuit écoute un audio-guide
Et que la fréquence pub est configurée à <frequence>
Quand il termine la séquence <numero_sequence>
Alors une publicité est insérée : <pub_inseree>
Exemples:
| frequence | numero_sequence | pub_inseree |
| 1/5 | 5 | Oui |
| 1/5 | 10 | Oui |
| 1/5 | 4 | Non |
| 1/3 | 3 | Oui |
| 1/3 | 6 | Oui |
Scénario: Utilisateur Premium sans publicités
Étant donné que l'utilisateur "premium@example.com" est abonné Premium
Et qu'il écoute un audio-guide piéton
Quand il termine la séquence 5
Alors aucune publicité n'est insérée
Et le player se met en pause immédiatement
Et le message "Séquence 6 prête. Appuyez sur Suivant." s'affiche
# 16.2.2 - Navigation et contrôles
Scénario: Boutons de contrôle disponibles en mode piéton
Étant donné qu'un audio-guide piéton est en lecture
Quand l'utilisateur consulte les contrôles
Alors les boutons suivants sont visibles:
| bouton | fonction |
| [\|] Suivant | Passe à la séquence suivante |
| [\|] Précédent | Retour à la séquence précédente |
| [] Pause | Pause temporaire |
| [] Play | Reprend la lecture |
| [📋] Liste | Affiche toutes les séquences |
Scénario: Passage à la séquence suivante pendant la lecture
Étant donné que la séquence 3 "La Joconde" est en cours à 1:42/3:42
Quand l'utilisateur clique sur [|] "Suivant"
Alors la séquence 4 "Vénus de Milo" démarre immédiatement
Et la séquence 3 n'est pas marquée comme écoutée (car <80%)
Scénario: Retour à la séquence précédente (saut direct)
Étant donné que la séquence 5 est en cours de lecture
Quand l'utilisateur clique sur [|◀] "Précédent"
Alors la séquence 4 démarre depuis le début (0:00)
Et il n'y a pas de logique "replay si >10s" (contrairement au contenu classique)
Scénario: Pause et reprise pendant une séquence
Étant donné que la séquence 2 est en cours à 1:15/1:48
Quand l'utilisateur clique sur [] "Pause"
Alors la lecture se met en pause
Et la position 1:15 est conservée
Quand l'utilisateur clique sur [] "Play"
Alors la lecture reprend exactement à 1:15
Scénario: Interface liste des séquences
Étant donné qu'un audio-guide de 12 séquences est en cours
Quand l'utilisateur clique sur [📋] "Liste séquences"
Alors une liste complète s'affiche avec:
| élément | exemple |
| Numéro et titre | "3. La Joconde" |
| Durée | (3:42) |
| État | Écouté / En cours / À écouter |
| Date écoute (si écouté) | "Écouté le 15/01/2026" |
Scénario: Séquence en cours dans la liste
Étant donné que la séquence 3 est en cours à 1:22/3:42
Quand la liste des séquences est affichée
Alors la séquence 3 affiche:
"""
3. La Joconde (3:42) - EN COURS
1:22/3:42
"""
Scénario: Navigation libre vers séquence non encore écoutée
Étant donné que l'utilisateur est sur la séquence 3
Et que les séquences 4 à 12 n'ont pas été écoutées
Quand l'utilisateur clique sur "8. Les Appartements de Napoléon"
Alors la séquence 8 démarre immédiatement depuis 0:00
Et les séquences 4 à 7 restent marquées "À écouter"
Scénario: Retour à une séquence déjà écoutée
Étant donné que la séquence 2 "Pyramide du Louvre" a été écoutée à 100%
Et qu'elle est marquée "Écouté"
Quand l'utilisateur clique dessus dans la liste
Alors la séquence 2 démarre depuis 0:00
Et le statut est conservé
Scénario: Checkmarks sur séquences écoutées >80%
Étant donné que l'utilisateur écoute la séquence 2 de durée 1:48
Quand il écoute jusqu'à 1:30 (83% de complétion)
Et qu'il passe à la séquence suivante
Alors la séquence 2 est marquée "Écouté"
Et la date d'écoute est enregistrée
Scénario: Pas de checkmark si séquence écoutée <80%
Étant donné que l'utilisateur écoute la séquence 3 de durée 3:42
Quand il écoute jusqu'à 1:30 (40% de complétion)
Et qu'il passe à la séquence suivante
Alors la séquence 3 reste marquée "À écouter"
Scénario: Bouton "Tout afficher" si plus de 6 séquences
Étant donné un audio-guide avec 12 séquences
Quand la liste est affichée
Alors seules les 6 premières séquences sont visibles initialement
Et un bouton "Tout afficher " est présent
Quand l'utilisateur clique sur "Tout afficher "
Alors les 6 séquences restantes sont affichées
Scénario: Saut vers séquence spécifique depuis la barre de progression
Étant donné qu'un audio-guide est en cours
Quand l'utilisateur clique sur "3/12" dans la barre de progression
Alors la liste des séquences s'ouvre
Et la séquence en cours (3) est mise en surbrillance
# Sauvegarde progression
Scénario: Position exacte sauvegardée automatiquement
Étant donné que la séquence 5 est en cours à 2:34/4:10
Quand l'utilisateur quitte l'application
Alors la position 2:34 dans la séquence 5 est sauvegardée
Et la sauvegarde est effectuée localement (SQLite)
Et la sauvegarde est synchronisée sur le cloud (PostgreSQL)
Scénario: Reprise après fermeture de l'application
Étant donné que l'utilisateur a quitté l'app à la séquence 5 position 2:34
Quand il rouvre l'audio-guide
Alors une popup de reprise s'affiche
Quand il clique sur " Reprendre"
Alors la lecture reprend à la séquence 5 position 2:34 exacte
# Cas d'usage réels
Scénario: Visiteur qui connaît déjà certaines œuvres
Étant donné qu'un visiteur du Louvre démarre l'audio-guide
Et qu'il connaît déjà "La Joconde" (séquence 3)
Quand il arrive à la séquence 3
Et qu'il clique sur [|] "Suivant" après 10 secondes
Alors la séquence 4 démarre immédiatement
Et la séquence 3 n'est pas marquée comme écoutée
Scénario: Visiteur qui veut voir une œuvre éloignée
Étant donné qu'un visiteur est à la séquence 2
Et qu'il aperçoit "La Victoire de Samothrace" (séquence 8) physiquement
Quand il ouvre la liste et clique sur la séquence 8
Alors la séquence 8 démarre immédiatement
Et il peut écouter la description même si les séquences 3-7 ne sont pas écoutées
Scénario: Visiteur qui prend une pause café
Étant donné qu'un visiteur écoute la séquence 6
Quand il clique sur [] "Pause"
Et qu'il ferme l'application pendant 30 minutes
Quand il rouvre l'application
Alors la séquence 6 reprend à la position exacte où il s'était arrêté
Scénario: Visiteur qui revient le lendemain
Étant donné qu'un visiteur a écouté les séquences 1-5 hier
Et qu'il revient au musée aujourd'hui
Quand il ouvre l'audio-guide
Alors une popup propose " Reprendre" (séquence 6)
Et les séquences 1-5 sont marquées "Écouté"
# Cas d'erreur
Scénario: Séquence audio corrompue ou indisponible
Étant donné que la séquence 7 a un fichier audio corrompu
Quand l'utilisateur tente de la lire
Alors un message d'erreur s'affiche:
"""
Cette séquence est temporairement indisponible.
[ Passer à la suivante] [🔄 Réessayer]
"""
Scénario: Perte de connexion pendant le chargement
Étant donné que l'utilisateur lance la séquence 4
Et que la connexion réseau est perdue
Quand le chargement échoue
Alors un message s'affiche: "Connexion perdue. Vérifiez votre réseau."
Et un bouton "🔄 Réessayer" est disponible
Scénario: Batterie faible en cours de visite
Étant donné que la batterie de l'appareil est à 5%
Quand l'utilisateur écoute une séquence
Alors une notification système s'affiche: "Batterie faible. Progression sauvegardée."
Et la position est sauvegardée localement toutes les 10 secondes
Scénario: Mode piéton sans points GPS (pas d'alerte localisation)
Étant donné un audio-guide en mode piéton
Et que le GPS est désactivé
Quand l'utilisateur démarre l'audio-guide
Alors aucune alerte GPS ne s'affiche
Et l'audio-guide fonctionne normalement (navigation 100% manuelle)

View File

@@ -0,0 +1,400 @@
# language: fr
Fonctionnalité: Audio-guide mode voiture (GPS automatique)
En tant qu'utilisateur en voiture
Je veux que les séquences se déclenchent automatiquement selon ma position GPS
Afin de profiter d'une expérience guidée hands-free
Contexte:
Étant donné que l'application RoadWave est démarrée
Et que l'utilisateur "jean@example.com" est connecté (gratuit)
Et qu'un audio-guide voiture "Safari du Paugre" est disponible avec 8 séquences
Et que le GPS est activé
# 16.3.1 - Déclenchement et contrôles
Scénario: Distinction audio-guides vs contenus géolocalisés simples
Étant donné que l'utilisateur est en mode voiture
Quand il écoute un contenu géolocalisé simple (1 séquence unique)
Alors une notification avec compteur 71 est affichée 7s avant le point
Et il doit valider avec "Suivant" + décompte 5s
Et ce contenu compte 1/6 dans le quota horaire
Quand il démarre un audio-guide multi-séquences
Alors les séquences se déclenchent au point GPS exact (rayon 30m)
Et aucun compteur 7s n'est affiché (juste notification "Ding" + toast 2s)
Et l'audio-guide entier compte 1/6 dans le quota
Scénario: Démarrage automatique au premier point GPS
Étant donné que l'utilisateur démarre l'audio-guide "Safari du Paugre"
Et que le point de départ est à (43.1234, 2.5678) avec rayon 30m
Quand l'utilisateur entre dans le rayon de 30m
Alors la séquence 1 "Introduction - Point d'accueil" démarre automatiquement
Et une notification sonore "Ding" est jouée (non intrusif)
Et un toast s'affiche brièvement pendant 2s: "Introduction - Point d'accueil"
Et aucun compteur 71 n'est affiché (contrairement aux contenus géolocalisés simples)
Scénario: Déclenchement automatique séquence suivante
Étant donné que la séquence 1 est terminée
Et que l'utilisateur se déplace vers le point GPS 2 (43.1245, 2.5690)
Quand l'utilisateur entre dans le rayon de 30m du point 2
Alors la séquence 2 "Enclos des lions" démarre automatiquement
Et une notification "Ding" + toast "Enclos des lions" s'affiche
Scénario: Navigation manuelle conservée (bouton Suivant actif)
Étant donné que la séquence 1 est en cours
Et que l'utilisateur est encore loin du point GPS 2 (distance 500m)
Quand l'utilisateur clique sur [|] "Suivant"
Alors la séquence 2 démarre immédiatement
Et aucune vérification GPS n'est effectuée
Scénario: Navigation manuelle conservée (bouton Précédent actif)
Étant donné que la séquence 3 est en cours
Quand l'utilisateur clique sur [|] "Précédent"
Alors la séquence 2 démarre depuis le début
Et aucune vérification GPS n'est effectuée
Scénario: Tous les boutons de contrôle restent actifs
Étant donné qu'un audio-guide voiture est en cours
Quand l'utilisateur consulte les contrôles
Alors les boutons suivants sont actifs:
| bouton | état | comportement |
| [\|] Suivant | | Passe séquence suivante immédiate |
| [\|] Précédent | | Retour séquence précédente |
| [] Pause | | Pause temporaire |
| [📋] Liste | | Saut direct possible |
Scénario: Use case - Embouteillage (séquence finie, point GPS loin)
Étant donné que la séquence 3 "Enclos des girafes" est terminée
Et que le point GPS 4 est à 2 km de distance (embouteillage)
Quand l'utilisateur clique manuellement sur [|] "Suivant"
Alors la séquence 4 démarre immédiatement
Et l'utilisateur peut continuer l'expérience sans attendre d'atteindre le point GPS
Scénario: Use case - Route fermée (point GPS inaccessible)
Étant donné que le point GPS 5 est sur une route fermée
Et que l'utilisateur ne peut pas s'en approcher
Quand l'utilisateur clique sur [|] "Suivant"
Alors la séquence 5 démarre quand même
Et l'audio-guide continue normalement
Scénario: Use case - Passager manipule l'application
Étant donné que l'utilisateur est passager (non conducteur)
Et que la vitesse du véhicule est 45 km/h
Quand le passager clique sur [|] "Suivant"
Alors la séquence suivante démarre
Et un avertissement s'affiche pendant 3 secondes
Scénario: Avertissement sécurité si vitesse >10 km/h
Étant donné que la vitesse actuelle est 35 km/h
Quand l'utilisateur clique sur un bouton (Suivant ou Précédent)
Alors l'action est exécutée immédiatement (pas de blocage)
Et un toast s'affiche pendant 3 secondes:
"""
Manipulation en conduite détectée.
Pour votre sécurité, demandez à un passager.
"""
Plan du Scénario: Avertissement selon la vitesse
Étant donné que la vitesse actuelle est <vitesse> km/h
Quand l'utilisateur clique sur un bouton de navigation
Alors l'avertissement est affiché : <avertissement>
Exemples:
| vitesse | avertissement |
| 5 | Non |
| 10 | Non |
| 11 | Oui |
| 35 | Oui |
| 90 | Oui |
# 16.3.2 - Affichage distance et guidage
Scénario: Affichage entre deux séquences avec progress bar
Étant donné que la séquence 2 "Les lions" vient de se terminer
Et que le prochain point GPS 3 "Enclos des girafes" est à 500m
Quand l'interface bascule en mode "attente prochain point"
Alors l'écran affiche:
| élément | description |
| Statut séquence | " Séquence 2/8 terminée" |
| Nom séquence | "Les lions" |
| Progress bar | Barre dynamique remplie selon distance (0%) |
| Distance prochain point| "500 mètres" |
| ETA | " 1 minute 30" |
| Direction | |
| Vitesse actuelle | "28 km/h" |
| Bouton "Rejouer séq." | Permet de réécouter la séquence qui vient de finir |
Scénario: Progress bar dynamique vers le prochain point
Étant donné que la distance initiale vers le prochain point était 500m
Et que la séquence précédente est terminée
Quand l'utilisateur se rapproche du prochain point
Et que la distance actuelle est 175m
Alors la progress bar affiche "65%" remplie
Et le calcul est: 100 - (175 / 500 * 100) = 65%
Et la barre se met à jour chaque seconde
Scénario: Bouton "Rejouer séq." pour réécouter
Étant donné que la séquence 3 vient de se terminer
Et que l'interface "attente prochain point" est affichée
Quand l'utilisateur clique sur [ Rejouer séq.]
Alors la séquence 3 redémarre depuis 0:00
Et l'utilisateur peut la réécouter (utile si distraction)
Scénario: Interface en conduite avec distance et ETA
Étant donné que la séquence 2 est en cours
Et que le prochain point GPS 3 "Enclos des girafes" est à 320m
Et que la vitesse actuelle est 28 km/h
Quand l'interface est affichée
Alors les informations suivantes sont visibles:
| information | valeur |
| Nom prochain point | "Enclos des girafes" |
| Distance | "320 mètres" |
| ETA | " 40 secondes" |
| Direction | (flèche direction) |
| Vitesse actuelle | "28 km/h" |
| Vitesse recommandée | "20-30 km/h" |
Scénario: Mise à jour de la distance en temps réel
Étant donné que la distance au prochain point est 500m
Quand 10 secondes s'écoulent et que l'utilisateur se rapproche
Alors la distance est mise à jour chaque seconde
Et la nouvelle distance "450m" s'affiche
Scénario: Mise à jour de l'ETA en temps réel
Étant donné que l'ETA est " 2 minutes"
Et que la vitesse est constante à 30 km/h
Quand l'utilisateur se rapproche du point
Alors l'ETA est recalculé chaque seconde
Et il diminue progressivement: " 1 minute 50", " 1 minute 40", etc.
Plan du Scénario: Format d'affichage de la distance
Étant donné que la distance au prochain point est <distance_metres>
Quand l'interface est mise à jour
Alors la distance affichée est "<affichage>"
Exemples:
| distance_metres | affichage |
| 50 | 50 m |
| 320 | 320 m |
| 980 | 980 m |
| 1200 | 1.2 km |
| 5400 | 5.4 km |
Plan du Scénario: Format d'affichage de l'ETA
Étant donné que l'ETA calculé est <secondes> secondes
Quand l'interface est mise à jour
Alors l'ETA affiché est "<affichage>"
Exemples:
| secondes | affichage |
| 30 | 30 secondes |
| 75 | 1 minute |
| 150 | 2 minutes |
| 400 | 6 minutes |
Scénario: Calcul de la direction (flèche 8 directions)
Étant donné que la position actuelle est (43.1234, 2.5678)
Et que le prochain point est au nord-est (angle 45°)
Quand la direction est calculée
Alors la flèche "" est affichée
Plan du Scénario: Flèches de direction selon l'angle
Étant donné que l'angle vers le prochain point est <angle>°
Quand la direction est calculée
Alors la flèche "<fleche>" est affichée
Exemples:
| angle | fleche |
| 0 | |
| 45 | |
| 90 | |
| 135 | |
| 180 | |
| 225 | |
| 270 | |
| 315 | |
Scénario: Mise à jour de la direction toutes les 5 secondes
Étant donné que la direction actuelle est (nord)
Et que l'utilisateur tourne vers l'est
Quand 5 secondes s'écoulent
Alors la direction est recalculée
Et la nouvelle flèche (nord-est) s'affiche
Scénario: Message "En attente de déplacement" si vitesse <5 km/h
Étant donné que la vitesse actuelle est 2 km/h (arrêté)
Quand l'ETA est calculé
Alors le message "En attente de déplacement" s'affiche
Et l'ETA n'est pas calculé (car vitesse insuffisante)
Scénario: Simplicité de l'interface (pas de carte miniature)
Étant donné qu'un audio-guide voiture est en cours
Quand l'interface est affichée
Alors aucune carte miniature n'est présente
Et seuls les éléments essentiels sont affichés:
| élément |
| Distance |
| ETA |
| Direction (flèche) |
| Vitesse |
| Contrôles audio |
# 16.3.3 - Rayon de déclenchement et tolérance
Scénario: Rayon de déclenchement par défaut en mode voiture
Étant donné un audio-guide voiture
Quand un point GPS est défini
Alors le rayon de déclenchement est 30 mètres par défaut
Et le rayon de tolérance "point manqué" est 100 mètres
Scénario: Déclenchement dans le rayon (30m)
Étant donné que le point GPS 3 est défini avec rayon 30m
Quand l'utilisateur entre à 25m du point
Alors la séquence 3 se déclenche automatiquement
Scénario: Pas de déclenchement hors rayon
Étant donné que le point GPS 3 a un rayon de 30m
Quand l'utilisateur passe à 45m du point
Alors la séquence 3 ne se déclenche pas automatiquement
Scénario: Point manqué dans rayon de tolérance (100m)
Étant donné que l'utilisateur passe à 60m du point GPS 4 (hors rayon 30m)
Et que 60m < 100m (rayon tolérance)
Quand le point est détecté comme manqué
Alors un toast s'affiche: "⚠️ Point manqué : Enclos des éléphants"
Et une popup s'affiche pendant 5 secondes avec 3 options
Scénario: Popup "Point manqué" avec 3 actions
Étant donné qu'un point GPS a été manqué (distance 60m)
Quand la popup s'affiche
Alors les options suivantes sont disponibles:
| bouton | icône | comportement |
| Écouter quand même | 🔊 | Lance séquence immédiatement (même hors zone) |
| Passer au suivant | ⏭️ | Skip séquence, continue vers prochain point |
| Faire demi-tour | 🔙 | Ouvre GPS externe (Google Maps/Waze) vers point |
Scénario: Action "Écouter quand même"
Étant donné qu'un point GPS est manqué
Quand l'utilisateur clique sur "🔊 Écouter quand même"
Alors la séquence correspondante démarre immédiatement
Et l'utilisateur peut continuer sa route
Scénario: Action "Passer au suivant"
Étant donné qu'un point GPS 5 est manqué
Quand l'utilisateur clique sur "⏭️ Passer au suivant"
Alors la séquence 5 est ignorée (non écoutée)
Et l'application attend le point GPS 6
Et la distance vers le point 6 s'affiche
Scénario: Action "Faire demi-tour"
Étant donné qu'un point GPS est manqué à (43.1250, 2.5700)
Quand l'utilisateur clique sur "🔙 Faire demi-tour"
Alors l'application détecte l'app GPS installée (Google Maps ou Waze)
Et ouvre la navigation GPS externe vers (43.1250, 2.5700)
Scénario: Point manqué au-delà du rayon de tolérance (>100m)
Étant donné que l'utilisateur passe à 150m du point GPS 6
Quand la distance est détectée
Alors aucune popup ne s'affiche (point trop loin)
Et l'utilisateur peut naviguer manuellement avec [|]
Plan du Scénario: Gestion selon la distance au point
Étant donné un point GPS avec rayon 30m et tolérance 100m
Quand l'utilisateur passe à <distance> du point
Alors le comportement est <comportement>
Exemples:
| distance | comportement |
| 20m | Déclenchement automatique séquence |
| 40m | Rien (hors rayon, pas encore tolérance) |
| 60m | Popup "Point manqué" avec 3 options |
| 110m | Rien (trop loin, hors tolérance) |
Scénario: Configuration rayon personnalisé par le créateur
Étant donné qu'un créateur définit un rayon de 50m (au lieu de 30m)
Quand un utilisateur entre à 45m du point
Alors la séquence se déclenche automatiquement
Et le rayon personnalisé est respecté
Scénario: Rayon minimum et maximum configurables
Étant donné qu'un créateur configure un rayon
Quand il ajuste le curseur
Alors les valeurs disponibles sont de 10m à 200m
Et le rayon par défaut suggéré est 30m pour la voiture
# Cas d'usage réels
Scénario: Safari-parc avec déclenchement automatique fluide
Étant donné qu'un utilisateur roule dans un safari à 20 km/h
Quand il passe devant "Enclos des lions" (point GPS 2)
Alors la séquence 2 démarre automatiquement sans intervention
Et il peut se concentrer sur la conduite et l'observation
Scénario: Détour imprévu (travaux sur la route)
Étant donné qu'un utilisateur prend un détour à cause de travaux
Et que le point GPS 4 devient inaccessible
Quand il est loin du point (>100m)
Et qu'il clique manuellement sur [|]
Alors la séquence 4 démarre quand même
Et l'expérience continue sans blocage
Scénario: Passager qui navigue librement
Étant donné qu'un passager utilise l'application
Et que le conducteur roule à 50 km/h
Quand le passager clique sur "Précédent" pour réécouter
Alors l'action est exécutée immédiatement
Et un warning apparaît brièvement (sensibilisation)
Scénario: Embouteillage prolongé
Étant donné que la séquence 3 est terminée depuis 10 minutes
Et que l'utilisateur est bloqué dans un embouteillage
Et que le point GPS 4 est encore à 1.5 km
Quand l'utilisateur clique sur [|]
Alors la séquence 4 démarre immédiatement
Et l'utilisateur peut passer le temps en écoutant
# Cas d'erreur
Scénario: GPS désactivé en mode voiture
Étant donné qu'un audio-guide voiture est démarré
Et que le GPS est désactivé
Quand l'application détecte l'absence de GPS
Alors une alerte s'affiche:
"""
GPS requis pour le mode Voiture
Activez la localisation pour profiter du déclenchement automatique.
[Activer GPS] [Passer en mode Manuel]
"""
Scénario: Action "Passer en mode Manuel"
Étant donné que le GPS est désactivé
Quand l'utilisateur clique sur "Passer en mode Manuel"
Alors l'audio-guide bascule en navigation 100% manuelle
Et les boutons [|] et [|] permettent de naviguer
Et aucun déclenchement GPS n'est tenté
Scénario: Précision GPS insuffisante
Étant donné que le signal GPS a une précision de ±150m
Quand l'utilisateur approche d'un point GPS avec rayon 30m
Alors un avertissement s'affiche:
"""
Signal GPS imprécis (±150m)
Le déclenchement automatique peut être perturbé.
Utilisez les boutons manuels si nécessaire.
"""
Scénario: Perte signal GPS en cours de route
Étant donné qu'un audio-guide voiture est en cours
Quand le signal GPS est perdu (tunnel, parking souterrain)
Alors un toast s'affiche: "Signal GPS perdu. Navigation manuelle active."
Et les boutons de navigation restent actifs
Quand le signal GPS revient
Alors un toast s'affiche: "Signal GPS rétabli"
Et le déclenchement automatique est réactivé
Scénario: Dépassement de la vitesse recommandée
Étant donné qu'un audio-guide recommande 20-30 km/h
Et que l'utilisateur roule à 65 km/h
Quand la vitesse est détectée
Alors l'affichage vitesse est en orange: " 65 km/h"
Et un message info s'affiche: "Vitesse élevée. Risque de manquer des points."

View File

@@ -0,0 +1,91 @@
# language: fr
@api @audio-guides @cycling @transit @mvp
Fonctionnalité: Modes vélo et transports en commun complets
En tant qu'utilisateur à vélo ou en transports
Je veux une expérience adaptée à mon mode de déplacement
Afin de profiter des audio-guides en toute sécurité
Contexte:
Étant donné les caractéristiques des modes:
| Mode | Vitesse moyenne | Notifications | Auto-play |
| Vélo | 15-20 km/h | Audio priority| Optionnel |
| Transports | 30-40 km/h | Visuelles OK | Aux arrêts|
Scénario: Mode vélo avec notifications audio prioritaires
Étant donné un utilisateur "alice@roadwave.fr" en mode vélo à 18 km/h
Quand elle approche d'un point d'intérêt
Alors une notification audio est jouée (sécurité)
Et les notifications visuelles sont minimales
Et l'auto-play est optionnel (configurable)
Et un événement "CYCLING_MODE_POI_NOTIFICATION" est enregistré
Scénario: Mode transports avec détection des arrêts/stations
Étant donné un utilisateur "bob@roadwave.fr" en mode transports
Quand le système détecte un arrêt prolongé (station)
Alors l'audio-guide peut se déclencher à la station
Et les informations visuelles sont complètes
Et un événement "TRANSIT_STOP_DETECTED" est enregistré
Scénario: Adaptation du rayon de déclenchement en mode vélo
Étant donné un créateur "charlie@roadwave.fr" avec rayon adaptatif activé
Quand un utilisateur en mode vélo approche du POI
Alors le rayon est augmenté de 50% (anticipation)
Et le déclenchement se fait plus tôt
Et un événement "CYCLING_RADIUS_ADAPTED" est enregistré
Scénario: Gestion des tunnels en mode transports
Étant donné un utilisateur "david@roadwave.fr" en métro
Quand il entre dans un tunnel (perte GPS)
Alors la position est estimée selon la ligne de métro
Et les séquences continuent de se jouer normalement
Et un événement "TRANSIT_TUNNEL_MODE" est enregistré
Scénario: Sécurité en mode vélo - pause automatique si danger
Étant donné un utilisateur "eve@roadwave.fr" en mode vélo
Quand une accélération brusque est détectée (freinage)
Alors l'audio se met en pause automatiquement
Et reprend quand la vitesse se stabilise
Et un événement "CYCLING_SAFETY_PAUSE" est enregistré
Scénario: Mode transports avec synchronisation aux horaires
Étant donné un utilisateur "frank@roadwave.fr" en bus
Quand le système détecte les arrêts réguliers
Alors les séquences sont synchronisées aux stations
Et l'ETA est calculé selon les arrêts
Et un événement "TRANSIT_SCHEDULE_SYNC" est enregistré
Scénario: Statistiques spécifiques au mode vélo
Étant donné un utilisateur "grace@roadwave.fr" qui termine un audio-guide à vélo
Alors il voit des statistiques adaptées:
| Métrique | Valeur |
| Distance parcourue | 12.5 km |
| Temps de trajet | 45 min |
| Vitesse moyenne | 16.7 km/h |
| Dénivelé positif | 120m |
Et un événement "CYCLING_STATS_DISPLAYED" est enregistré
Scénario: Détection automatique changement vélo → transports
Étant donné un utilisateur "henry@roadwave.fr" en mode vélo
Quand il monte dans un bus avec son vélo
Alors le mode bascule automatiquement en "transports"
Et l'expérience s'adapte instantanément
Et un événement "MODE_SWITCH_CYCLING_TO_TRANSIT" est enregistré
Scénario: Mode vélo électrique avec détection
Étant donné un utilisateur "iris@roadwave.fr" sur un vélo électrique
Quand la vitesse moyenne est > 25 km/h (VAE)
Alors le système adapte les rayons de déclenchement
Et l'ETA est calculé avec vitesse VAE
Et un événement "EBIKE_MODE_DETECTED" est enregistré
Scénario: Métriques de performance modes vélo et transports
Étant donné que 10 000 parcours ont été effectués en vélo/transports
Alors les indicateurs suivants sont disponibles:
| Métrique | Vélo | Transports |
| Taux d'utilisation | 15% | 10% |
| Taux de complétion | 82% | 75% |
| Vitesse moyenne | 17km/h| 35km/h |
| Satisfaction utilisateur | 4.3/5 | 4.1/5 |
Et les métriques sont exportées vers le monitoring

View File

@@ -0,0 +1,274 @@
# language: fr
Fonctionnalité: Audio-guides modes vélo et transport
En tant qu'utilisateur à vélo ou en transport en commun
Je veux profiter d'un guidage GPS adapté à mon mode de déplacement
Afin d'avoir une expérience optimisée avec tolérances appropriées
Contexte:
Étant donné que l'application RoadWave est démarrée
Et que l'utilisateur "jean@example.com" est connecté
Et que le GPS est activé
# 16.4 - Modes Vélo et Transport
Plan du Scénario: Paramètres par mode de déplacement
Étant donné un audio-guide configuré en mode <mode>
Alors les paramètres suivants sont appliqués:
| paramètre | valeur |
| Rayon déclenchement | <rayon_declenchement> |
| Rayon tolérance "point manqué" | <rayon_tolerance> |
| Vitesse recommandée | <vitesse_recommandee> |
| Seuil warning sécurité | <seuil_warning> |
Exemples:
| mode | rayon_declenchement | rayon_tolerance | vitesse_recommandee | seuil_warning |
| Voiture | 30m | 100m | 20-50 km/h | >10 km/h |
| Vélo | 50m | 75m | 10-25 km/h | >5 km/h |
| Transport | 100m | 150m | Variable | Désactivé |
# Mode Vélo
Scénario: Déclenchement automatique avec rayon 50m (mode vélo)
Étant donné un audio-guide vélo "Circuit des châteaux de la Loire"
Et que le point GPS 3 a un rayon de 50m
Quand l'utilisateur à vélo entre à 45m du point
Alors la séquence 3 "Château de Chambord" se déclenche automatiquement
Scénario: Rayon plus large justifié pour le vélo
Étant donné qu'un cycliste roule sur piste cyclable
Et que sa vitesse varie entre 8 et 22 km/h (arrêts fréquents)
Et que le tracé est moins prévisible qu'en voiture
Quand un point GPS avec rayon 50m est défini
Alors le rayon plus large compense la variabilité de trajectoire
Scénario: Warning sécurité dès 5 km/h en vélo
Étant donné un audio-guide vélo en cours
Et que la vitesse actuelle est 12 km/h
Quand l'utilisateur clique sur [|] "Suivant"
Alors l'action est exécutée
Et un warning s'affiche: " Manipulation en déplacement détecté. Pour votre sécurité, arrêtez-vous."
Plan du Scénario: Warning vélo selon la vitesse
Étant donné que la vitesse actuelle à vélo est <vitesse> km/h
Quand l'utilisateur clique sur un bouton de navigation
Alors le warning est affiché : <warning>
Exemples:
| vitesse | warning |
| 0 | Non |
| 4 | Non |
| 6 | Oui |
| 15 | Oui |
| 25 | Oui |
Scénario: Tolérance GPS moins stricte en vélo
Étant donné qu'un cycliste passe à 65m du point GPS 4
Et que le rayon de déclenchement est 50m
Et que le rayon de tolérance est 75m
Quand la distance est détectée
Alors la popup "Point manqué" s'affiche avec 3 options
Et le système tolère l'écart (trajectoire vélo moins prévisible)
Scénario: Affichage adapté au vélo
Étant donné un audio-guide vélo en cours
Quand l'interface est affichée
Alors les informations suivantes sont visibles:
| information | valeur |
| Icône mode | 🚴 |
| Distance prochain point| "450 m" |
| ETA | " 2 minutes" |
| Direction | |
| Vitesse actuelle | "18 km/h" |
| Vitesse recommandée | "10-25 km/h" |
Scénario: Cas d'usage - Piste cyclable avec arrêts fréquents
Étant donné qu'un cycliste suit un circuit nature
Et qu'il s'arrête régulièrement (feux, photos, fatigue)
Quand il s'arrête à 40m d'un point GPS (rayon 50m)
Alors la séquence se déclenche automatiquement
Et le rayon large permet le déclenchement malgré l'arrêt
Scénario: Cas d'usage - Circulation mixte piétons/vélos
Étant donné qu'un cycliste roule sur voie partagée
Et qu'il doit ralentir fréquemment pour éviter les piétons
Quand sa vitesse varie entre 5 et 20 km/h
Alors le système s'adapte avec le rayon 50m
Et le déclenchement reste fiable
# Mode Transport
Scénario: Déclenchement automatique avec rayon 100m (mode transport)
Étant donné un audio-guide transport "Ligne touristique Paris"
Et que le point GPS "Tour Eiffel" a un rayon de 100m
Quand le bus touristique entre à 85m du point
Alors la séquence "Tour Eiffel" se déclenche automatiquement
Scénario: Rayon très large justifié pour le transport
Étant donné qu'un bus touristique suit une ligne fixe
Et qu'il effectue des arrêts fréquents (stations)
Et que l'utilisateur n'a aucun contrôle sur la trajectoire
Quand un point GPS avec rayon 100m est défini
Alors le rayon large compense les arrêts et la ligne fixe
Scénario: Pas de warning sécurité en mode transport
Étant donné un audio-guide transport en cours
Et que le bus roule à 50 km/h
Quand l'utilisateur clique sur [|] "Suivant"
Alors l'action est exécutée immédiatement
Et aucun warning n'est affiché
Parce que l'utilisateur est passager (pas conducteur)
Scénario: Vitesse recommandée "Selon ligne"
Étant donné un audio-guide transport
Quand l'interface est affichée
Alors la vitesse recommandée indique "Selon ligne"
Et aucune valeur fixe n'est affichée (car ligne de transport varie)
Scénario: Tolérance horaire pour retards
Étant donné qu'un bus touristique est en retard de 3 minutes
Et qu'il arrive au point GPS "Musée du Louvre" avec retard
Quand il entre dans le rayon de 100m
Alors la séquence se déclenche normalement
Et le système tolère le retard (pas de pénalité temporelle)
Scénario: Tolérance spatiale très large (150m)
Étant donné qu'un bus passe à 120m du point GPS "Arc de Triomphe"
Et que le rayon de déclenchement est 100m
Et que le rayon de tolérance est 150m
Quand la distance est détectée
Alors la popup "Point manqué" s'affiche avec 3 options
Scénario: Affichage adapté au transport
Étant donné un audio-guide transport en cours
Quand l'interface est affichée
Alors les informations suivantes sont visibles:
| information | valeur |
| Icône mode | 🚌 |
| Distance prochain point| "1.2 km" |
| ETA | " 3 minutes" |
| Direction | |
| Vitesse actuelle | "35 km/h" |
| Vitesse recommandée | "Selon ligne" |
Scénario: Cas d'usage - Bus touristique hop-on hop-off
Étant donné un bus touristique "Paris Open Tour"
Et qu'il suit un circuit fixe avec 15 arrêts
Quand il approche de chaque arrêt
Alors la séquence correspondante se déclenche automatiquement
Et l'utilisateur n'a rien à faire (expérience passive)
Scénario: Cas d'usage - Train panoramique
Étant donné un train touristique "Ligne des Alpes"
Et qu'il roule à vitesse variable (20-80 km/h)
Quand il passe près de points d'intérêt
Alors les séquences se déclenchent avec rayon 100m
Et le système compense la vitesse élevée
# Comportements identiques à la voiture
Scénario: Navigation manuelle conservée (vélo et transport)
Étant donné un audio-guide en mode <mode>
Quand l'utilisateur clique sur [|] ou [|]
Alors les boutons manuels fonctionnent normalement
Et aucune vérification GPS n'est effectuée
Exemples:
| mode |
| Vélo |
| Transport |
Scénario: Affichage distance + ETA + direction (tous modes)
Étant donné un audio-guide en mode <mode>
Quand l'interface est affichée
Alors les informations distance, ETA et direction sont affichées
Et le format est identique au mode voiture
Exemples:
| mode |
| Vélo |
| Transport |
Scénario: Gestion "Point manqué" identique
Étant donné un audio-guide en mode <mode>
Quand un point GPS est manqué (dans rayon tolérance)
Alors la popup avec 3 options s'affiche:
| option |
| 🔊 Écouter quand même |
| Passer au suivant |
| 🔙 Faire demi-tour |
Exemples:
| mode |
| Vélo |
| Transport |
# Publicités (identique tous modes)
Plan du Scénario: Insertion publicité dans tous les modes
Étant donné un utilisateur gratuit écoute un audio-guide en mode <mode>
Quand la séquence 5 se termine (1 pub / 5 séquences)
Alors la publicité s'enchaîne automatiquement
Et elle est skippable après 5 secondes
Exemples:
| mode |
| Voiture |
| Vélo |
| Transport |
| Piéton |
# Cas d'erreur
Scénario: GPS imprécis en forêt (vélo)
Étant donné un cycliste dans une forêt dense
Et que la précision GPS est ±80m
Quand il approche d'un point GPS avec rayon 50m
Alors un avertissement s'affiche:
"""
Signal GPS imprécis (±80m)
Le déclenchement peut être perturbé.
Utilisez les boutons manuels si nécessaire.
"""
Scénario: Bus dévié de son itinéraire (transport)
Étant donné un bus touristique avec déviation
Et que plusieurs points GPS deviennent inaccessibles
Quand l'utilisateur est informé
Alors un message s'affiche:
"""
Itinéraire modifié
Certains points ne seront pas atteints.
Utilisez la navigation manuelle.
"""
Scénario: Changement de mode en cours de route
Étant donné un audio-guide démarré en mode "Vélo"
Quand l'utilisateur décide de continuer à pied
Et qu'il ouvre les paramètres
Alors il peut changer le mode vers "Piéton"
Et les rayons sont reconfigurés automatiquement
Et une confirmation s'affiche:
"""
Mode changé : 🚶 Piéton
Navigation manuelle activée.
"""
Scénario: Détection automatique incohérente
Étant donné qu'un utilisateur marche rapidement (7 km/h)
Et que le système détecte "Vélo" par erreur
Quand la suggestion s'affiche
Alors l'utilisateur peut cliquer sur "Changer"
Et sélectionner manuellement "Piéton"
Scénario: Batterie en mode vélo longue distance
Étant donné un circuit vélo de 50 km avec 20 séquences
Et que l'utilisateur roule pendant 3 heures
Quand la batterie atteint 15%
Alors une notification suggère:
"""
🔋 Batterie à 15%
Recommandé : activer mode économie d'énergie
(Désactive affichage continu distance)
[Activer] [Ignorer]
"""

View File

@@ -0,0 +1,218 @@
# language: fr
@ui @audio-guides @pedestrian @navigation @mvp
Fonctionnalité: Navigation libre complète en mode piéton
En tant qu'utilisateur piéton
Je veux naviguer librement dans un audio-guide sans contrainte d'ordre
Afin de découvrir les points d'intérêt selon mon itinéraire personnel
Contexte:
Étant donné un audio-guide "Visite du Quartier Latin" avec 8 séquences:
| Ordre | Nom | Position GPS | Rayon |
| 1 | Notre-Dame | 48.8534, 2.3488 | 100m |
| 2 | Sainte-Chapelle | 48.8555, 2.3450 | 80m |
| 3 | Panthéon | 48.8462, 2.3464 | 100m |
| 4 | Jardin du Luxembourg | 48.8462, 2.3371 | 150m |
| 5 | Sorbonne | 48.8487, 2.3431 | 70m |
| 6 | Musée de Cluny | 48.8505, 2.3434 | 60m |
| 7 | Rue Mouffetard | 48.8429, 2.3498 | 50m |
| 8 | Arènes de Lutèce | 48.8456, 2.3523 | 80m |
Scénario: Démarrage d'un audio-guide en mode piéton avec navigation libre
Étant donné un utilisateur "alice@roadwave.fr" en mode piéton
Et elle se trouve près de Notre-Dame
Quand elle lance l'audio-guide "Visite du Quartier Latin"
Alors l'écran principal affiche:
| Élément | Contenu |
| Carte interactive | Affichée avec 8 marqueurs |
| Position utilisateur | Marqueur bleu en temps réel |
| Points d'intérêt | Marqueurs numérotés 1-8 |
| Distances | Affichées sur chaque marqueur |
| Point le plus proche | Surligné en vert (Notre-Dame, 50m) |
| Mode de lecture | "Navigation libre" activé par défaut |
| Bouton "Liste" | Affiche la liste des séquences |
Et un événement "AUDIO_GUIDE_STARTED_FREE_NAVIGATION" est enregistré
Et la métrique "audio_guide.started.free_navigation" est incrémentée
Scénario: Déclenchement automatique au point d'intérêt le plus proche
Étant donné un utilisateur "bob@roadwave.fr" en mode piéton
Et il a lancé l'audio-guide "Visite du Quartier Latin"
Et il marche vers Notre-Dame
Quand il entre dans le rayon de 100m de Notre-Dame
Alors l'audio de la séquence #1 "Notre-Dame" démarre automatiquement
Et une notification s'affiche:
| Élément | Contenu |
| Titre | 1/8 - Cathédrale Notre-Dame |
| Distance | Vous êtes arrivé |
| Progression | Barre de lecture audio |
| Actions | [Pause] [Liste] [Carte] |
Et le marqueur Notre-Dame passe de vert à bleu (en cours)
Et un événement "SEQUENCE_AUTO_TRIGGERED" est enregistré
Et la métrique "audio_guide.sequence.auto_triggered" est incrémentée
Scénario: Écoute d'une séquence dans un ordre différent de l'ordre suggéré
Étant donné un utilisateur "charlie@roadwave.fr" en mode piéton
Et il a lancé l'audio-guide et écouté la séquence #1 "Notre-Dame"
Et il décide de se rendre directement au Panthéon (séquence #3)
Quand il marche vers le Panthéon en ignorant la séquence #2
Et entre dans le rayon de 100m du Panthéon
Alors l'audio de la séquence #3 "Panthéon" démarre automatiquement
Et la séquence #2 "Sainte-Chapelle" reste disponible et non écoutée
Et un événement "SEQUENCE_OUT_OF_ORDER" est enregistré
Et la métrique "audio_guide.sequence.out_of_order" est incrémentée
Et la progression affiche: 2/8 séquences écoutées
Scénario: Affichage de la carte avec points d'intérêt colorés par statut
Étant donné un utilisateur "david@roadwave.fr" en mode piéton
Et il a écouté 3 séquences sur 8
Quand il consulte la carte
Alors les marqueurs sont colorés selon leur statut:
| Séquence | Statut | Couleur | Icône |
| Notre-Dame | Écoutée | Bleu | |
| Sainte-Chapelle | Non écoutée | Gris | 2 |
| Panthéon | Écoutée | Bleu | |
| Jardin du Luxembourg| Non écoutée | Gris | 4 |
| Sorbonne | Écoutée | Bleu | |
| Musée de Cluny | Non écoutée | Gris | 6 |
| Rue Mouffetard | En cours | Orange | |
| Arènes de Lutèce | Non écoutée | Gris | 8 |
Et le point le plus proche est surligné avec un halo vert
Et les distances sont affichées en temps réel
Scénario: Consultation de la liste des séquences avec filtres
Étant donné un utilisateur "eve@roadwave.fr" en mode piéton
Quand elle clique sur le bouton "Liste"
Alors une liste des séquences s'affiche:
| Séquence | Distance | Statut | Actions |
| Notre-Dame | 50m | Écoutée | [Réécouter] |
| Sainte-Chapelle | 200m | Non écoutée | [Y aller] [Lire] |
| Panthéon | 350m | Écoutée | [Réécouter] |
| Jardin du Luxembourg| 450m | Non écoutée | [Y aller] [Lire] |
Et elle peut filtrer par:
| Filtre | Options |
| Statut | Toutes / Écoutées / Restantes |
| Tri | Distance / Ordre suggéré / Durée |
Et un compteur affiche: "3/8 séquences écoutées"
Scénario: Lecture manuelle d'une séquence depuis la liste
Étant donné un utilisateur "frank@roadwave.fr" en mode piéton
Et il consulte la liste des séquences
Quand il clique sur "Lire" pour la séquence #5 "Sorbonne"
Alors l'audio de la Sorbonne démarre immédiatement
Et peu importe la distance actuelle (peut être loin du point)
Et un événement "SEQUENCE_MANUAL_PLAY" est enregistré
Et la métrique "audio_guide.sequence.manual_play" est incrémentée
Et un message d'information s'affiche: "Vous écoutez cette séquence hors localisation"
Scénario: Navigation vers un point d'intérêt depuis la liste
Étant donné un utilisateur "grace@roadwave.fr" en mode piéton
Et elle consulte la liste des séquences
Quand elle clique sur "Y aller" pour la séquence #4 "Jardin du Luxembourg"
Alors l'application lance la navigation GPS vers le Jardin du Luxembourg
Et un itinéraire piéton est calculé et affiché sur la carte
Et la distance et le temps estimé sont affichés: "450m - 6 min"
Et des instructions de navigation vocales sont données:
| Instruction |
| Dirigez-vous vers le sud |
| Tournez à gauche dans 100 mètres |
| Vous arrivez à destination |
Et un événement "NAVIGATION_TO_POI_STARTED" est enregistré
Et la métrique "audio_guide.navigation.started" est incrémentée
Scénario: Réécoute d'une séquence déjà complétée
Étant donné un utilisateur "henry@roadwave.fr" en mode piéton
Et il a déjà écouté la séquence #1 "Notre-Dame" en entier
Quand il clique sur "Réécouter" depuis la liste
Alors l'audio de Notre-Dame redémarre depuis le début
Et un événement "SEQUENCE_REPLAYED" est enregistré
Et la métrique "audio_guide.sequence.replayed" est incrémentée
Et la séquence reste marquée comme "Écoutée" (pas de duplication)
Scénario: Pause et reprise d'une séquence en cours
Étant donné un utilisateur "iris@roadwave.fr" en mode piéton
Et elle écoute la séquence #3 "Panthéon" à la position 2min 30s
Quand elle clique sur le bouton "Pause"
Alors l'audio se met en pause
Et la position de lecture est sauvegardée: 2min 30s
Et un événement "SEQUENCE_PAUSED" est enregistré
Quand elle clique sur le bouton "Lecture"
Alors l'audio reprend exactement à 2min 30s
Et un événement "SEQUENCE_RESUMED" est enregistré
Scénario: Affichage de la progression globale de l'audio-guide
Étant donné un utilisateur "jack@roadwave.fr" en mode piéton
Et il a écouté 5 séquences sur 8
Quand il consulte l'écran principal
Alors il voit la progression:
| Élément | Contenu |
| Barre de progression | 5/8 (62%) |
| Séquences restantes | 3 points à découvrir |
| Temps écouté | 42 minutes |
| Distance parcourue | 2.8 km |
| Badge de complétion | Bronze (50%+) |
Et un bouton "Terminer l'audio-guide" est disponible si toutes les séquences sont écoutées
Scénario: Découverte d'un point bonus caché (Easter egg)
Étant donné un audio-guide avec un point bonus secret non listé
Et un utilisateur "kate@roadwave.fr" en mode piéton
Quand elle passe à proximité du point bonus caché (48.8470, 2.3450)
Alors une notification s'affiche: "🎉 Point bonus découvert !"
Et l'audio du point bonus démarre automatiquement
Et un badge "Explorateur" est débloqué
Et un événement "BONUS_POI_DISCOVERED" est enregistré
Et la métrique "audio_guide.bonus.discovered" est incrémentée
Scénario: Notifications de proximité pour les points à venir
Étant donné un utilisateur "luke@roadwave.fr" en mode piéton
Et il marche vers la Sainte-Chapelle
Quand il est à 150m de la Sainte-Chapelle
Alors une notification discrète s'affiche: "Sainte-Chapelle à 150m"
Et un son subtil de notification est joué
Quand il est à 50m
Alors une notification plus visible s'affiche: "Arrivée imminente - Sainte-Chapelle"
Et un événement "POI_PROXIMITY_NOTIFICATION" est enregistré
Et la métrique "audio_guide.proximity.notified" est incrémentée
Scénario: Mode hors ligne avec téléchargement préalable
Étant donné un utilisateur "mary@roadwave.fr" en mode piéton
Et elle a téléchargé l'audio-guide "Visite du Quartier Latin" avant de partir
Quand elle active le mode avion (hors connexion)
Alors la carte s'affiche en mode hors ligne (tiles pré-téléchargées)
Et tous les audios sont disponibles en local
Et la localisation GPS fonctionne normalement
Et les séquences se déclenchent automatiquement hors ligne
Et un événement "AUDIO_GUIDE_OFFLINE_MODE" est enregistré
Et la métrique "audio_guide.offline.used" est incrémentée
Scénario: Partage de la progression avec des amis
Étant donné un utilisateur "nathan@roadwave.fr" en mode piéton
Et il a écouté 4 séquences sur 8
Quand il clique sur "Partager ma progression"
Alors un écran de partage s'ouvre avec:
| Élément | Contenu |
| Message | Je suis en train de découvrir le Quartier Latin !|
| Progression | 4/8 points visités (50%) |
| Carte visuelle | Capture d'écran de la carte avec progression |
| Lien | https://roadwave.fr/share/abc123 |
Et le partage peut être envoyé via:
| Canal | Disponible |
| SMS | Oui |
| WhatsApp | Oui |
| Facebook | Oui |
| Twitter/X | Oui |
Et un événement "AUDIO_GUIDE_PROGRESS_SHARED" est enregistré
Et la métrique "audio_guide.shared" est incrémentée
Scénario: Métriques de performance de la navigation libre
Étant donné que 10 000 utilisateurs ont terminé l'audio-guide en mode navigation libre
Quand les métriques d'usage sont collectées
Alors les indicateurs suivants sont disponibles:
| Métrique | Valeur moyenne |
| Taux de complétion | 78% |
| Nombre de séquences écoutées | 6.5/8 |
| Temps moyen de visite | 2h 15min |
| Distance moyenne parcourue | 3.2 km |
| Pourcentage d'ordre non-suggéré | 42% |
| Nombre de réécoutes par séquence | 0.8 |
Et les métriques sont exportées vers le système de monitoring

View File

@@ -0,0 +1,221 @@
# language: fr
@api @audio-guides @advertising @pedestrian @mvp
Fonctionnalité: Auto-play publicités en mode piéton uniquement
En tant qu'utilisateur piéton
Je peux recevoir des publicités audio en auto-play à proximité de commerces
Afin que les commerçants puissent promouvoir leurs offres de manière contextualisée
Contexte:
Étant donné que le système de publicité respecte les règles suivantes:
| Règle | Valeur |
| Auto-play autorisé uniquement en mode | Piéton |
| Durée max d'une publicité | 30 secondes |
| Fréquence max par commerce | 1 par jour |
| Distance min entre 2 pubs différentes | 200 mètres |
| Nombre max de pubs par heure | 3 |
| Possibilité de skip après | 5 secondes |
Scénario: Déclenchement automatique d'une publicité en mode piéton
Étant donné un utilisateur "alice@roadwave.fr" en mode piéton
Et elle marche dans la rue avec l'application active
Quand elle passe à 30 mètres du café "Le Parisien" avec publicité active
Alors la publicité audio "Café Le Parisien - 10% de réduction" démarre automatiquement
Et une notification visuelle s'affiche:
| Élément | Contenu |
| Icône | Logo du café |
| Titre | Publicité - Le Parisien |
| Distance | À 30m de vous |
| Action | [Passer] disponible après 5s |
| Durée | 0:25 |
Et l'audio en cours (si existant) est mis en pause
Et un événement "AD_AUTOPLAY_TRIGGERED" est enregistré
Et la métrique "ads.autoplay.triggered" est incrémentée
Scénario: Aucun auto-play en mode voiture
Étant donné un utilisateur "bob@roadwave.fr" en mode voiture
Et il roule à 40 km/h avec l'application active
Quand il passe à 30 mètres d'un commerce avec publicité active
Alors aucune publicité n'est déclenchée automatiquement
Et la publicité peut être affichée dans la liste "Publicités à proximité"
Et l'utilisateur peut choisir manuellement de l'écouter
Et un événement "AD_SKIPPED_CAR_MODE" est enregistré
Et la métrique "ads.skipped.car_mode" est incrémentée
Scénario: Aucun auto-play en mode vélo
Étant donné un utilisateur "charlie@roadwave.fr" en mode vélo
Et il roule à 15 km/h avec l'application active
Quand il passe à 30 mètres d'un commerce avec publicité active
Alors aucune publicité n'est déclenchée automatiquement
Et la sécurité de l'utilisateur à vélo est préservée
Et un événement "AD_SKIPPED_CYCLING_MODE" est enregistré
Et la métrique "ads.skipped.cycling_mode" est incrémentée
Scénario: Skip d'une publicité après 5 secondes
Étant donné un utilisateur "david@roadwave.fr" en mode piéton
Et une publicité a démarré automatiquement il y a 6 secondes
Quand l'utilisateur clique sur le bouton "Passer"
Alors la publicité s'arrête immédiatement
Et l'audio en cours précédent reprend (si existant)
Et un événement "AD_SKIPPED_BY_USER" est enregistré avec temps_ecoute: 6s
Et la métrique "ads.skipped.by_user" est incrémentée
Et le commerçant est facturé pour 6 secondes d'écoute seulement
Scénario: Bouton "Passer" désactivé pendant les 5 premières secondes
Étant donné un utilisateur "eve@roadwave.fr" en mode piéton
Et une publicité vient de démarrer
Quand l'utilisateur clique sur le bouton "Passer" à T+2 secondes
Alors le bouton est grisé et non cliquable
Et un message s'affiche: "Disponible dans 3 secondes"
Et un compteur à rebours est visible: 3... 2... 1...
Alors à T+5 secondes, le bouton devient actif
Et un événement "AD_SKIP_ATTEMPTED_TOO_EARLY" est enregistré
Et la métrique "ads.skip.too_early" est incrémentée
Scénario: Publicité écoutée en entier
Étant donné un utilisateur "frank@roadwave.fr" en mode piéton
Et une publicité de 25 secondes a démarré automatiquement
Quand l'utilisateur écoute la publicité jusqu'à la fin sans cliquer sur "Passer"
Alors la publicité se termine naturellement
Et l'audio en cours précédent reprend automatiquement
Et un événement "AD_COMPLETED" est enregistré avec temps_ecoute: 25s
Et la métrique "ads.completed" est incrémentée
Et le commerçant est facturé pour la publicité complète (tarif plein)
Scénario: Limitation à 3 publicités par heure
Étant donné un utilisateur "grace@roadwave.fr" en mode piéton
Et elle a déjà écouté 3 publicités dans la dernière heure:
| Commerce | Temps écoulé |
| Café Le Parisien | Il y a 10min |
| Boulangerie Paul | Il y a 30min |
| Restaurant Tokyo | Il y a 50min |
Quand elle passe à 30 mètres d'un 4ème commerce avec publicité
Alors aucune publicité n'est déclenchée automatiquement
Et un compteur s'affiche discrètement: "Prochaine pub disponible dans 10 min"
Et un événement "AD_RATE_LIMITED" est enregistré
Et la métrique "ads.rate_limited" est incrémentée
Scénario: Limitation à 1 publicité par commerce par jour
Étant donné un utilisateur "henry@roadwave.fr" en mode piéton
Et il a déjà écouté la publicité du "Café Le Parisien" ce matin à 10h
Quand il repasse devant le même café à 16h
Alors aucune publicité n'est déclenchée automatiquement
Et le café n'apparaît pas dans la liste "Publicités à proximité"
Et un événement "AD_ALREADY_SHOWN_TODAY" est enregistré
Et la métrique "ads.deduplication.same_day" est incrémentée
Scénario: Distance minimale de 200m entre 2 publicités différentes
Étant donné un utilisateur "iris@roadwave.fr" en mode piéton
Et elle vient d'écouter une publicité du "Café Le Parisien" il y a 1 minute
Quand elle marche et passe à 50 mètres de la "Boulangerie Paul" (150m du café)
Alors aucune publicité n'est déclenchée automatiquement
Et un événement "AD_TOO_CLOSE_TO_PREVIOUS" est enregistré
Et la métrique "ads.skipped.too_close" est incrémentée
Quand elle continue et passe à 250 mètres de la "Librairie Gibert" (250m du café)
Alors la publicité de la librairie peut être déclenchée
Scénario: Désactivation complète des publicités (utilisateur Premium)
Étant donné un utilisateur "jack@roadwave.fr" Premium en mode piéton
Et il a désactivé les publicités dans ses paramètres
Quand il passe à 30 mètres de commerces avec publicités actives
Alors aucune publicité n'est jamais déclenchée
Et aucune publicité n'apparaît dans la liste "Publicités à proximité"
Et un événement "AD_BLOCKED_PREMIUM" est enregistré
Et la métrique "ads.blocked.premium" est incrémentée
Scénario: Mise en pause de l'audio en cours lors du déclenchement d'une pub
Étant donné un utilisateur "kate@roadwave.fr" en mode piéton
Et elle écoute un podcast "Histoire de Paris" à la position 12min 30s
Quand une publicité se déclenche automatiquement
Alors le podcast est mis en pause immédiatement
Et la position de lecture est sauvegardée: 12min 30s
Et la publicité démarre
Quand la publicité se termine (skip ou écoute complète)
Alors le podcast reprend automatiquement à la position 12min 30s
Et un événement "AD_CONTENT_PAUSED_RESUMED" est enregistré
Et la métrique "ads.content.paused_resumed" est incrémentée
Scénario: Ciblage géographique précis de la publicité
Étant donné un commerçant "Le Parisien" avec publicité active
Et il a configuré un rayon de déclenchement de 50 mètres
Et un utilisateur "luke@roadwave.fr" en mode piéton
Quand l'utilisateur est à 60 mètres du commerce
Alors aucune publicité n'est déclenchée
Quand l'utilisateur marche et arrive à 45 mètres du commerce
Alors la publicité se déclenche automatiquement
Et un événement "AD_GEO_TRIGGERED" est enregistré avec distance: 45m
Et la métrique "ads.geo.triggered" est incrémentée
Scénario: Publicité contextuelle basée sur les intérêts de l'utilisateur
Étant donné un utilisateur "mary@roadwave.fr" en mode piéton
Et ses jauges d'intérêts sont:
| Catégorie | Niveau |
| Gastronomie | 85% |
| Culture | 60% |
| Sport | 20% |
Et deux commerces ont des publicités actives à proximité:
| Commerce | Catégorie | Distance |
| Restaurant Le Gourmet | Gastronomie | 40m |
| Salle de sport FitClub| Sport | 35m |
Quand l'utilisateur passe à proximité des deux commerces
Alors la publicité du restaurant est priorisée et déclenchée
Et la publicité de la salle de sport est ignorée (faible intérêt)
Et un événement "AD_INTEREST_MATCHED" est enregistré avec categorie: "gastronomie", score: 85
Et la métrique "ads.interest_matching.applied" est incrémentée
Scénario: Affichage d'informations complémentaires pendant la publicité
Étant donné un utilisateur "nathan@roadwave.fr" en mode piéton
Et une publicité du "Café Le Parisien" est en cours de lecture
Quand l'utilisateur consulte l'écran
Alors il voit les informations suivantes:
| Élément | Contenu |
| Logo du commerce | [Image] |
| Nom du commerce | Café Le Parisien |
| Type d'établissement | Café-Brasserie |
| Distance | À 30m de vous |
| Itinéraire | [Bouton "Y aller"] |
| Offre spéciale | 10% de réduction avec ce code: ROADWAVE10|
| Horaires | Ouvert maintenant - Ferme à 22h |
| Note | 4.5/5 (230 avis) |
Et l'utilisateur peut cliquer sur "Y aller" pour lancer la navigation
Et un événement "AD_INFO_DISPLAYED" est enregistré
Scénario: Tracking de la conversion (visite effective du commerce)
Étant donné un utilisateur "olive@roadwave.fr" en mode piéton
Et elle a écouté la publicité du "Café Le Parisien" il y a 5 minutes
Quand elle clique sur "Y aller" et se rend au café
Et entre dans un rayon de 10 mètres du café
Alors un événement "AD_CONVERSION_VISIT" est enregistré
Et la métrique "ads.conversions.visits" est incrémentée
Et le commerçant voit cette conversion dans ses statistiques
Et une notification discrète s'affiche: "Profitez de votre réduction avec le code ROADWAVE10"
Scénario: Métriques de performance des publicités pour les commerçants
Étant donné un commerçant "Le Parisien" avec publicité active depuis 7 jours
Quand le commerçant consulte ses statistiques
Alors il voit les métriques suivantes:
| Métrique | Valeur |
| Nombre d'impressions (déclenchements)| 450 |
| Taux d'écoute complète | 35% |
| Taux de skip moyen | 65% |
| Durée moyenne d'écoute | 12s |
| Nombre de clics "Y aller" | 25 |
| Nombre de visites confirmées | 18 |
| Taux de conversion | 4% |
| Coût total | 45 |
| Coût par visite | 2.50 |
Et les métriques sont mises à jour en temps réel
Scénario: A/B testing des publicités pour optimisation
Étant donné un commerçant "Le Parisien" avec 2 versions de publicité:
| Version | Description | Durée |
| A | Voix masculine, tonalité formelle | 25s |
| B | Voix féminine, tonalité décontractée | 25s |
Quand le système diffuse aléatoirement les 2 versions (50/50)
Alors les métriques sont collectées séparément:
| Métrique | Version A | Version B |
| Taux d'écoute complète | 32% | 42% |
| Taux de conversion | 3.5% | 5.2% |
Et le commerçant peut choisir de diffuser uniquement la version B
Et un événement "AD_AB_TEST_COMPLETED" est enregistré

View File

@@ -0,0 +1,270 @@
# language: fr
Fonctionnalité: Audio-guides Premium et monétisation
En tant que créateur
Je veux pouvoir proposer des audio-guides Premium
Afin de monétiser mon contenu de qualité
Contexte:
Étant donné que l'application RoadWave est démarrée
Et que le créateur "guide@example.com" est connecté et vérifié
# 16.9 - Audio-guides Premium
Scénario: Création d'un audio-guide Premium
Étant donné que le créateur crée un audio-guide "Visite VIP Versailles"
Quand il accède aux paramètres de monétisation (étape 4)
Alors il peut choisir:
| option | description |
| Gratuit | Accessible à tous (avec pubs) |
| Premium | Réservé abonnés Premium |
Scénario: Badge Premium visible sur l'audio-guide
Étant donné un audio-guide configuré en Premium
Quand il est affiché dans les résultats de recherche
Alors un badge "👑 Premium" est visible
Et la cover image a un cadre doré subtil
Scénario: Preview 3 premières séquences pour utilisateurs gratuits
Étant donné un audio-guide Premium "Visite VIP Versailles" avec 15 séquences
Et qu'un utilisateur gratuit ouvre l'audio-guide
Quand il consulte la liste des séquences
Alors les séquences affichent:
| séquence | état |
| 1 | Accessible (preview) |
| 2 | Accessible (preview) |
| 3 | Accessible (preview) |
| 4 | 🔒 Réservé Premium |
| 5-15 | 🔒 Réservé Premium |
Scénario: Écoute des 3 premières séquences sans blocage
Étant donné un utilisateur gratuit
Et un audio-guide Premium avec preview
Quand il écoute les séquences 1, 2 et 3
Alors aucune publicité n'est insérée (preview = teasing)
Et l'écoute est fluide
Scénario: Paywall après la 3ème séquence
Étant donné qu'un utilisateur gratuit termine la séquence 3
Quand la séquence se termine
Alors un overlay paywall s'affiche immédiatement:
"""
👑 Contenu réservé Premium
Continuez cette expérience exclusive
et accédez à 12 séquences supplémentaires
Sans publicité
Accès illimité à tous les contenus Premium
Téléchargement offline
Audio haute qualité
[Passer Premium - 4.99/mois]
[Découvrir d'autres audio-guides gratuits]
"""
Scénario: Bouton "Passer Premium" vers tunnel d'abonnement
Étant donné que l'overlay paywall Premium est affiché
Quand l'utilisateur clique sur "Passer Premium"
Alors il est redirigé vers la page d'abonnement Mangopay
Et l'audio-guide actuel est marqué en "pending" (reprise après souscription)
Scénario: Reprise automatique après souscription Premium
Étant donné qu'un utilisateur s'est abonné Premium depuis un paywall audio-guide
Quand l'abonnement est activé
Alors il est redirigé vers l'audio-guide automatiquement
Et la séquence 4 démarre immédiatement
Et un toast de bienvenue s'affiche: " Bienvenue Premium ! Profitez de votre audio-guide"
Scénario: Utilisateur Premium - Accès complet immédiat
Étant donné qu'un utilisateur Premium ouvre un audio-guide Premium
Quand il consulte la liste des séquences
Alors toutes les 15 séquences sont accessibles
Et aucun paywall ne s'affiche
Et aucune publicité n'est insérée
Scénario: Pas de preview si l'audio-guide a <3 séquences
Étant donné un audio-guide Premium avec seulement 2 séquences
Quand un utilisateur gratuit tente de l'ouvrir
Alors un paywall s'affiche immédiatement (avant lecture)
Et aucune preview n'est disponible
# 16.10 - Revenus créateur
Scénario: Rémunération créateur pour audio-guide Premium
Étant donné un créateur avec un audio-guide Premium
Et que 50 utilisateurs Premium ont écouté l'audio-guide ce mois
Quand la répartition des revenus est calculée
Alors le créateur reçoit 70% des revenus proportionnels
Et la formule est: (Écoutes créateur / Total écoutes Premium) × 70% pool Premium
Scénario: Dashboard revenus par audio-guide
Étant donné qu'un créateur a 3 audio-guides Premium publiés
Quand il consulte son dashboard revenus
Alors il voit pour chaque audio-guide:
| audio_guide | ecoutes_mois | revenus_estime |
| Visite VIP Versailles | 142 | 45.20 |
| Secrets du Louvre | 89 | 28.50 |
| Châteaux de la Loire | 203 | 64.80 |
Scénario: Comparaison gratuit vs Premium
Étant donné qu'un créateur a publié 2 audio-guides:
| titre | type | ecoutes_mois | revenus |
| Tour de Paris | Gratuit | 1200 | 3.60 |
| Visite VIP Versailles| Premium | 142 | 45.20 |
Quand il consulte son dashboard
Alors il peut comparer les performances
Et constater que Premium génère plus de revenus par écoute
Scénario: Seuil minimum de paiement (50€)
Étant donné qu'un créateur a généré 42 de revenus ce mois
Quand le paiement mensuel est traité
Alors le montant est reporté au mois suivant
Et un message s'affiche: "Seuil minimum non atteint (50). Montant reporté."
Scénario: Paiement automatique mensuel
Étant donné qu'un créateur a généré 138.50 de revenus en janvier
Quand le 15 février arrive
Alors le paiement est initié automatiquement via Mangopay
Et le créateur reçoit une notification: "Paiement de 138.50 en cours"
Et les fonds arrivent sous 1-3 jours ouvrés (SEPA)
# 16.11 - Publicités dans audio-guides gratuits
Scénario: Insertion publicité toutes les 5 séquences (gratuit)
Étant donné un audio-guide gratuit avec 12 séquences
Et un utilisateur gratuit
Quand il termine la séquence 5
Alors une publicité démarre automatiquement
Quand il termine la séquence 10
Alors une deuxième publicité démarre
Scénario: Publicité après séquence en mode piéton (avec pause)
Étant donné un audio-guide piéton gratuit
Quand la séquence 5 se termine
Alors la publicité démarre automatiquement (pas d'attente bouton)
Et la pub est skippable après 5 secondes
Quand la publicité se termine
Alors le player se met en pause
Et l'utilisateur doit cliquer sur [|] pour continuer
Scénario: Publicité en mode voiture/vélo/transport (automatique)
Étant donné un audio-guide voiture gratuit
Quand la séquence 5 se termine
Alors la publicité démarre automatiquement
Quand la publicité se termine
Alors la séquence 6 démarre automatiquement (pas de pause)
Parce que l'utilisateur est en conduite (mode hands-free)
Scénario: Publicités géolocalisées dans audio-guides
Étant donné un audio-guide dans la région "Île-de-France"
Quand une publicité doit être insérée
Alors l'API publicitaire filtre par:
| critère | valeur |
| Géolocalisation | Île-de-France |
| Catégorie | Tourisme, Culture |
| Langue | Français |
Scénario: Comptabilisation revenus pub pour créateur
Étant donné qu'un audio-guide gratuit génère 200 écoutes complètes
Et que chaque écoute complète = 2 publicités (séq. 5 et 10)
Quand les revenus pub sont calculés
Alors 400 impressions pub sont diffusées
Et le créateur reçoit 0.60 (200 écoutes × 0.003)
# 16.12 - Stratégies de conversion
Scénario: CTA Premium après audio-guide gratuit complété
Étant donné qu'un utilisateur gratuit complète un audio-guide gratuit
Quand il termine la dernière séquence
Alors un overlay s'affiche:
"""
🎉 Audio-guide complété !
Vous avez aimé cette expérience ?
Découvrez nos audio-guides Premium pour aller plus loin
[Découvrir Premium] [Fermer]
"""
Scénario: Recommandations d'audio-guides Premium après gratuit
Étant donné qu'un utilisateur termine un audio-guide gratuit "Tour de Paris"
Quand l'overlay de fin s'affiche
Alors 3 audio-guides Premium similaires sont suggérés:
| titre | type | créateur |
| Secrets de Montmartre | Premium | @paris_stories |
| Visite VIP Musée d'Orsay | Premium | @art_guide |
| Paris hors des sentiers | Premium | @explore_paris |
Scénario: Badge "Premium recommandé" sur audio-guides populaires
Étant donné un audio-guide Premium avec >500 écoutes et note >4.5/5
Quand il est affiché dans les résultats de recherche
Alors un badge " Premium recommandé" est visible
Et il est mis en avant dans les résultats
Scénario: Conversion tracking pour attribution créateur
Étant donné qu'un utilisateur découvre Premium via un audio-guide créateur
Quand il s'abonne
Alors la conversion est trackée:
| donnée | valeur |
| source_conversion | audio_guide_paywall |
| audio_guide_id | visite_vip_versailles_123 |
| creator_id | guide_versailles_456 |
Et le créateur bénéficie d'un bonus de conversion
# Cas d'usage
Scénario: Créateur mix gratuit + Premium
Étant donné qu'un créateur a publié 5 audio-guides:
| titre | type |
| Découverte de Paris | Gratuit |
| Visite VIP Louvre | Premium |
| Balade Montmartre | Gratuit |
| Secrets Versailles | Premium |
| Visite express Orsay | Gratuit |
Quand un utilisateur découvre son profil
Alors les audio-guides gratuits servent de teasing
Et les audio-guides Premium sont mis en avant avec badge
Scénario: Utilisateur hésite à s'abonner
Étant donné qu'un utilisateur atteint le paywall d'un audio-guide Premium
Et qu'il clique sur "Découvrir d'autres audio-guides gratuits"
Quand il revient 2 jours plus tard sur le même audio-guide
Alors le paywall s'affiche à nouveau
Et une réduction temporaire est proposée: "Offre spéciale : -20% premier mois"
# Cas d'erreur
Scénario: Échec du paiement Premium via paywall
Étant donné qu'un utilisateur tente de s'abonner Premium
Quand le paiement Mangopay échoue
Alors un message d'erreur s'affiche:
"""
Paiement refusé
Vérifiez vos informations bancaires ou contactez votre banque.
[Réessayer] [Annuler]
"""
Scénario: Abonnement Premium expiré pendant écoute
Étant donné qu'un utilisateur Premium écoute un audio-guide Premium
Et que son abonnement expire pendant l'écoute (séquence 8/15)
Quand l'expiration est détectée
Alors l'écoute continue jusqu'à la fin de la séquence en cours
Et un overlay s'affiche ensuite:
"""
Votre abonnement Premium a expiré
Renouvelez pour continuer à profiter des contenus exclusifs
[Renouveler - 4.99/mois] [Plus tard]
"""
Scénario: Créateur change audio-guide de gratuit à Premium
Étant donné qu'un audio-guide gratuit a 50 utilisateurs avec progression
Quand le créateur le passe en Premium
Alors les utilisateurs ayant déjà commencé gardent l'accès complet
Et seuls les nouveaux utilisateurs sont soumis au paywall
Et un message de transparence s'affiche:
"""
Cet audio-guide est maintenant Premium
Vous conservez votre accès car vous l'aviez démarré avant le changement.
"""

View File

@@ -0,0 +1,310 @@
# language: fr
Fonctionnalité: Sauvegarde et reprise de progression audio-guide
En tant qu'utilisateur
Je veux que ma progression soit sauvegardée automatiquement
Afin de pouvoir reprendre mon audio-guide là où je me suis arrêté
Contexte:
Étant donné que l'application RoadWave est démarrée
Et que l'utilisateur "jean@example.com" est connecté
# 16.5 - Sauvegarde de progression
Scénario: Sauvegarde automatique toutes les 10 secondes
Étant donné qu'un audio-guide "Visite du Louvre" est en cours
Et que la séquence 3 est à la position 1:24
Quand 10 secondes s'écoulent
Alors la progression est sauvegardée automatiquement:
| donnée | valeur |
| audio_guide_id | louvre_123 |
| sequence_actuelle | 3 |
| position_audio | 1:24 |
| timestamp | 2026-01-22 14:35:42 |
| sequences_ecoutees | [1, 2] |
Scénario: Sauvegarde locale (SQLite) pour rapidité
Étant donné qu'une sauvegarde est déclenchée
Quand la progression est enregistrée
Alors les données sont écrites en SQLite local
Et l'écriture prend moins de 50ms
Et l'application reste fluide
Scénario: Synchronisation cloud en arrière-plan
Étant donné qu'une sauvegarde locale est effectuée
Quand 30 secondes s'écoulent
Alors la progression est synchronisée vers PostgreSQL cloud
Et la synchronisation s'effectue en arrière-plan
Et elle n'impacte pas les performances
Scénario: Sauvegarde immédiate lors de la fermeture
Étant donné qu'un audio-guide est en cours à la séquence 4 position 2:15
Quand l'utilisateur ferme l'application
Alors la progression est sauvegardée immédiatement (local + cloud)
Et les données sont écrites avant la fermeture complète
Scénario: Sauvegarde des séquences complétées
Étant donné qu'un audio-guide de 12 séquences est en cours
Et que les séquences 1, 2, 4, 5 ont été écoutées à >80%
Quand la progression est sauvegardée
Alors les séquences complétées sont enregistrées:
"""json
{
"audio_guide_id": "louvre_123",
"completed_sequences": [1, 2, 4, 5],
"current_sequence": 6,
"current_position": "0:42",
"last_played_at": "2026-01-22T14:35:42Z"
}
"""
Scénario: Historique des écoutes pour statistiques
Étant donné qu'un utilisateur a écouté 3 séquences d'un audio-guide
Quand les données sont sauvegardées
Alors l'historique d'écoute inclut:
| sequence_id | started_at | completed_at | completion_rate |
| 1 | 2026-01-22 14:10:00 | 2026-01-22 14:12:15 | 100% |
| 2 | 2026-01-22 14:12:20 | 2026-01-22 14:14:08 | 100% |
| 3 | 2026-01-22 14:14:15 | 2026-01-22 14:17:45 | 92% |
# 16.6 - Reprise de progression
Scénario: Popup de reprise au redémarrage
Étant donné que l'utilisateur a quitté l'app à la séquence 6 position 2:34
Quand il rouvre l'audio-guide "Visite du Louvre"
Alors une popup s'affiche:
"""
Continuer où vous vous étiez arrêté ?
📍 Séquence 6/12 : "Vénus de Milo"
Position : 2:34 / 4:10
[ Reprendre] [🔄 Recommencer]
"""
Scénario: Action "Reprendre" - Position exacte restaurée
Étant donné qu'une popup de reprise est affichée
Quand l'utilisateur clique sur " Reprendre"
Alors la séquence 6 "Vénus de Milo" se charge
Et la position exacte 2:34 est restaurée
Et la lecture démarre automatiquement après 1 seconde
Scénario: Action "Recommencer" - Réinitialisation complète
Étant donné qu'une popup de reprise est affichée
Quand l'utilisateur clique sur "🔄 Recommencer"
Alors l'audio-guide redémarre depuis la séquence 1 position 0:00
Et toutes les séquences sont marquées "À écouter"
Et l'historique d'écoute est réinitialisé pour cette session
Scénario: Reprise après 7 jours d'inactivité
Étant donné qu'un utilisateur a arrêté un audio-guide le 15/01/2026
Et qu'il le rouvre le 22/01/2026 (7 jours plus tard)
Quand l'audio-guide se charge
Alors la popup de reprise s'affiche normalement
Et toutes les données de progression sont conservées
Scénario: Reprise sur un autre appareil (synchronisation cloud)
Étant donné qu'un utilisateur écoute un audio-guide sur iPhone
Et qu'il quitte à la séquence 4 position 1:20
Quand il ouvre le même audio-guide sur iPad
Alors la popup de reprise s'affiche avec la progression iPhone
Et il peut reprendre exactement où il s'était arrêté
Scénario: Conflit de synchronisation (dernier appareil gagne)
Étant donné qu'un utilisateur écoute sur iPhone à la séquence 3
Et simultanément sur iPad à la séquence 7
Quand les deux appareils synchronisent
Alors la progression la plus récente (timestamp) est conservée
Et l'appareil avec ancienne progression affiche une notification:
"""
Progression mise à jour
Une écoute plus récente a été détectée.
Séquence 7 restaurée.
"""
Scénario: Mode hors-ligne - Sauvegarde locale uniquement
Étant donné qu'un utilisateur écoute un audio-guide hors connexion
Et qu'il atteint la séquence 5
Quand la progression est sauvegardée
Alors les données sont écrites localement (SQLite)
Et une icône " Non synchronisé" s'affiche discrètement
Scénario: Synchronisation automatique à la reconnexion
Étant donné que l'utilisateur a écouté hors ligne jusqu'à la séquence 8
Et que 5 progressions locales ne sont pas synchronisées
Quand la connexion réseau est rétablie
Alors les 5 progressions sont synchronisées automatiquement
Et un toast s'affiche brièvement: " Progression synchronisée"
Scénario: Suppression de la progression (recommencer proprement)
Étant donné qu'un utilisateur est à la séquence 10/12
Quand il ouvre les paramètres de l'audio-guide
Et qu'il clique sur "🔄 Réinitialiser progression"
Alors une confirmation s'affiche:
"""
Réinitialiser cet audio-guide ?
Toutes les séquences seront marquées comme non écoutées.
[Annuler] [Réinitialiser]
"""
Et si confirmé, la progression est effacée
# 16.7 - Statistiques d'écoute
Scénario: Taux de complétion global de l'audio-guide
Étant donné un audio-guide de 12 séquences
Et que l'utilisateur a écouté complètement 8 séquences
Et partiellement 1 séquence (45%)
Quand les statistiques sont calculées
Alors le taux de complétion affiché est "67%" (8/12)
Scénario: Badge "Audio-guide complété" à 100%
Étant donné un audio-guide de 12 séquences
Quand l'utilisateur écoute la 12ème séquence à 100%
Alors un badge " Audio-guide complété" s'affiche
Et une notification de félicitations est envoyée
Et le statut "Complété le 22/01/2026" est visible dans l'historique
Scénario: Temps total passé sur l'audio-guide
Étant donné qu'un utilisateur a écouté un audio-guide sur 2 sessions:
| session | durée |
| 1 | 25 min |
| 2 | 18 min |
Quand les statistiques sont calculées
Alors le temps total est "43 minutes"
Et il est affiché dans l'historique personnel
Scénario: Liste des audio-guides "En cours" dans le profil
Étant donné qu'un utilisateur a 3 audio-guides en cours:
| audio_guide | progression |
| Visite du Louvre | 6/12 |
| Safari du Paugre | 3/8 |
| Circuit Loire à Vélo | 12/15 |
Quand il consulte son profil "Audio-guides"
Alors la section "📍 En cours" affiche les 3 audio-guides
Et chaque élément montre la progression sous forme de barre
Scénario: Liste des audio-guides "Complétés" dans le profil
Étant donné qu'un utilisateur a complété 2 audio-guides:
| audio_guide | date_completion |
| Tour de Paris | 2026-01-15 |
| Découverte de Lyon | 2026-01-20 |
Quand il consulte son profil "Audio-guides"
Alors la section " Complétés" affiche les 2 audio-guides
Et la date de complétion est visible
Scénario: Badge "Complétiste" pour 10 audio-guides complétés
Étant donné qu'un utilisateur complète son 10ème audio-guide
Quand la complétion est enregistrée
Alors un badge "🏆 Complétiste" est débloqué
Et il apparaît sur son profil
Et une notification est envoyée:
"""
🎉 Badge débloqué : Complétiste
Vous avez complété 10 audio-guides !
"""
Plan du Scénario: Niveaux de badges selon nombre d'audio-guides complétés
Étant donné qu'un utilisateur complète <nombre> audio-guides
Quand le badge est attribué
Alors il reçoit le badge "<badge>"
Exemples:
| nombre | badge |
| 1 | 🎧 Premier audio-guide |
| 5 | 🗺 Explorateur |
| 10 | 🏆 Complétiste |
| 25 | 🌟 Expert |
| 50 | 💎 Maître audio-guideur |
# 16.8 - Métriques créateur
Scénario: Dashboard créateur - Statistiques par audio-guide
Étant donné qu'un créateur a publié l'audio-guide "Visite du Louvre"
Quand il consulte son dashboard
Alors les métriques suivantes sont affichées:
| métrique | valeur |
| Écoutes totales | 1542 |
| Écoutes complètes (>80%) | 892 |
| Taux de complétion moyen | 58% |
| Temps d'écoute total | 423h |
| Séquence la plus écoutée | Séq. 3 |
| Séquence la moins écoutée | Séq. 11 |
Scénario: Graphique de complétion par séquence
Étant donné un audio-guide de 12 séquences
Quand le créateur consulte les statistiques détaillées
Alors un graphique en barres affiche:
| séquence | taux_completion |
| 1 | 100% |
| 2 | 95% |
| 3 | 89% |
| ... | ... |
| 12 | 58% |
Scénario: Détection des points d'abandon
Étant donné qu'un audio-guide a un taux de complétion de 58%
Et que 35% des utilisateurs abandonnent à la séquence 7
Quand le créateur consulte les insights
Alors un avertissement s'affiche:
"""
Point d'abandon détecté
35% des utilisateurs abandonnent à la séquence 7 "Aile Richelieu"
Durée : 8 min
Suggestion : Réduire la durée ou rendre plus captivant
"""
Scénario: Heatmap géographique des écoutes
Étant donné un audio-guide géolocalisé
Quand le créateur consulte la heatmap
Alors une carte affiche:
| élément | description |
| Densité d'écoutes | Zones rouge/orange/jaune selon écoutes |
| Points GPS | Marqueurs sur chaque point |
| Statistiques par point | Nombre d'écoutes par zone |
Scénario: Temps moyen par séquence
Étant donné qu'un créateur analyse son audio-guide
Quand il consulte les statistiques temporelles
Alors il voit pour chaque séquence:
| séquence | durée_audio | temps_ecoute_moyen | ecart |
| 1 | 2:15 | 2:10 | -5s |
| 2 | 1:48 | 1:30 | -18s |
| 3 | 3:42 | 3:40 | -2s |
Scénario: Notification créateur pour milestone
Étant donné qu'un audio-guide atteint 1000 écoutes
Quand le seuil est franchi
Alors une notification est envoyée au créateur:
"""
🎉 Félicitations !
Votre audio-guide "Visite du Louvre" a atteint 1000 écoutes !
Taux de complétion : 58%
"""
# Cas d'erreur et limites
Scénario: Corruption de données de sauvegarde
Étant donné qu'une sauvegarde locale (SQLite) est corrompue
Quand l'application tente de charger la progression
Alors une récupération depuis le cloud est tentée
Et si réussie, les données cloud sont restaurées
Et la base locale est reconstruite
Scénario: Échec de synchronisation cloud
Étant donné que l'API cloud est indisponible
Quand une tentative de synchronisation est effectuée
Alors l'application continue avec sauvegarde locale uniquement
Et un retry automatique est programmé dans 5 minutes
Et l'icône " Non synchronisé" reste affichée
Scénario: Suppression accidentelle de progression (récupération)
Étant donné qu'un utilisateur réinitialise un audio-guide par erreur
Quand il contacte le support dans les 7 jours
Alors l'équipe peut restaurer la progression depuis les backups
Et les données sont récupérables (backup quotidien conservé 30 jours)
Scénario: Nettoyage automatique des vieilles progressions
Étant donné qu'une progression n'a pas été mise à jour depuis 6 mois
Quand le nettoyage automatique s'exécute
Alors la progression est archivée (mais pas supprimée)
Et l'utilisateur peut la restaurer via l'historique

View File

@@ -0,0 +1,394 @@
# language: fr
Fonctionnalité: API - Progression et synchronisation audio-guides
En tant que système backend
Je veux sauvegarder et synchroniser la progression des audio-guides
Afin de permettre une reprise fluide et multi-device
Contexte:
Étant donné que l'API RoadWave est démarrée
Et que l'utilisateur "user@example.com" est authentifié
# 16.6.1 - Sauvegarde progression
Scénario: POST /api/v1/audio-guides/{id}/progress - Sauvegarde progression
Étant donné un audio-guide "ag_123" en cours d'écoute
Et que l'utilisateur est à la séquence 3 position 1:42
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/progress":
"""json
{
"current_sequence_id": "seq_3",
"current_position_seconds": 102,
"completed_sequences": ["seq_1", "seq_2"],
"gps_position": {
"latitude": 43.1234,
"longitude": 2.5678
}
}
"""
Alors le code HTTP de réponse est 200
Et la progression est sauvegardée en PostgreSQL
Et le timestamp last_played_at est mis à jour
Et le corps de réponse contient:
"""json
{
"saved": true,
"synced_to_cloud": true,
"updated_at": "2026-01-22T14:35:42Z"
}
"""
Scénario: Sauvegarde automatique toutes les 30 secondes (client)
Étant donné que le client mobile envoie la progression toutes les 30s
Quand je fais un POST sur "/api/v1/audio-guides/{id}/progress"
Alors la progression précédente est écrasée
Et le timestamp est mis à jour
Et la réponse est retournée en < 100ms
Scénario: Sauvegarde des séquences complétées (>80%)
Étant donné qu'une séquence de durée 180 secondes
Et que l'utilisateur a écouté 150 secondes (83%)
Quand la progression est sauvegardée
Alors la séquence est ajoutée à completed_sequences
Et le completion_rate est enregistré à 83%
Scénario: Séquence non marquée complétée si <80%
Étant donné qu'une séquence de durée 222 secondes
Et que l'utilisateur a écouté 90 secondes (40%)
Quand la progression est sauvegardée
Alors la séquence n'est PAS ajoutée à completed_sequences
Et le current_position est sauvegardé (pour reprise)
Scénario: Structure de données progression en PostgreSQL
Étant donné une sauvegarde de progression
Alors la table audio_guide_progress contient:
| champ | type | description |
| id | UUID | ID progression |
| user_id | UUID | ID utilisateur |
| audio_guide_id | UUID | ID audio-guide |
| current_sequence_id | UUID | Séquence en cours |
| current_position | INTEGER | Position en secondes |
| completed_sequences | UUID[] | Tableau séquences complétées |
| last_played_at | TIMESTAMP | Dernière écoute |
| gps_position | GEOGRAPHY | Position GPS optionnelle |
| created_at | TIMESTAMP | Création |
| updated_at | TIMESTAMP | Dernière MAJ |
# 16.6.2 - Reprise progression
Scénario: GET /api/v1/audio-guides/{id}/progress - Récupération progression
Étant donné une progression sauvegardée pour "ag_123"
Quand je fais un GET sur "/api/v1/audio-guides/ag_123/progress"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"has_progress": true,
"current_sequence_id": "seq_3",
"current_position_seconds": 102,
"completed_sequences": ["seq_1", "seq_2"],
"completion_percentage": 25,
"last_played_at": "2026-01-20T14:35:42Z",
"can_resume": true
}
"""
Scénario: Calcul completion_percentage
Étant donné un audio-guide de 12 séquences
Et que l'utilisateur a complété 3 séquences
Quand le pourcentage de complétion est calculé
Alors completion_percentage est 25% (3/12)
Scénario: Progression inexistante (première écoute)
Étant donné qu'aucune progression n'existe pour "ag_456"
Quand je fais un GET sur "/api/v1/audio-guides/ag_456/progress"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"has_progress": false,
"can_resume": false
}
"""
Scénario: Progression expirée (>30 jours)
Étant donné une progression avec last_played_at à 35 jours
Quand je fais un GET sur "/api/v1/audio-guides/ag_123/progress"
Alors can_resume est false
Et le message "Progression expirée après 30 jours" est retourné
Et les données sont conservées mais marquées "expired"
Scénario: DELETE /api/v1/audio-guides/{id}/progress - Réinitialisation
Étant donné une progression existante
Quand je fais un DELETE sur "/api/v1/audio-guides/ag_123/progress"
Alors le code HTTP de réponse est 204
Et la progression est supprimée
Et l'utilisateur peut recommencer depuis le début
# 16.6.3 - Multi-device et synchronisation
Scénario: Synchronisation cloud automatique
Étant donné qu'une progression est sauvegardée sur iPhone
Quand l'utilisateur ouvre l'app sur iPad
Et fait un GET sur "/api/v1/audio-guides/ag_123/progress"
Alors la progression iPhone est récupérée
Et l'utilisateur peut reprendre exactement où il était
Scénario: Conflit de synchronisation (dernier timestamp gagne)
Étant donné une progression sur iPhone avec timestamp "2026-01-22T14:00:00Z"
Et une progression sur iPad avec timestamp "2026-01-22T14:05:00Z"
Quand les deux appareils synchronisent
Alors la progression avec timestamp le plus récent (iPad) est conservée
Et la progression iPhone est écrasée
Scénario: GET /api/v1/audio-guides/progress/sync - Synchronisation batch
Étant donné que l'utilisateur a 5 progressions locales non synchronisées
Quand je fais un POST sur "/api/v1/audio-guides/progress/sync":
"""json
{
"progressions": [
{
"audio_guide_id": "ag_1",
"current_sequence_id": "seq_3",
"current_position_seconds": 102,
"updated_at": "2026-01-22T14:00:00Z"
},
{
"audio_guide_id": "ag_2",
"current_sequence_id": "seq_5",
"current_position_seconds": 45,
"updated_at": "2026-01-22T14:10:00Z"
}
]
}
"""
Alors le code HTTP de réponse est 200
Et toutes les progressions sont synchronisées
Et le corps de réponse contient:
"""json
{
"synced_count": 2,
"conflicts": 0
}
"""
Scénario: Résolution conflit avec notification
Étant donné une progression locale sur iPhone avec timestamp ancien
Et une progression cloud plus récente (depuis iPad)
Quand le sync est effectué
Alors la progression cloud est conservée
Et un conflit est signalé dans la réponse:
"""json
{
"synced_count": 1,
"conflicts": 1,
"conflict_details": [
{
"audio_guide_id": "ag_123",
"cloud_timestamp": "2026-01-22T15:00:00Z",
"local_timestamp": "2026-01-22T14:30:00Z",
"resolution": "cloud_wins"
}
]
}
"""
# Historique et statistiques
Scénario: GET /api/v1/audio-guides/progress/history - Historique écoutes
Étant donné que l'utilisateur a écouté 3 séquences d'un audio-guide
Quand je fais un GET sur "/api/v1/audio-guides/ag_123/progress/history"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient l'historique:
"""json
{
"listening_sessions": [
{
"sequence_id": "seq_1",
"started_at": "2026-01-22T14:10:00Z",
"completed_at": "2026-01-22T14:12:15Z",
"completion_rate": 100,
"duration_listened": 135
},
{
"sequence_id": "seq_2",
"started_at": "2026-01-22T14:12:20Z",
"completed_at": "2026-01-22T14:14:08Z",
"completion_rate": 100,
"duration_listened": 108
},
{
"sequence_id": "seq_3",
"started_at": "2026-01-22T14:14:15Z",
"completed_at": "2026-01-22T14:17:45Z",
"completion_rate": 92,
"duration_listened": 204
}
],
"total_time_spent": 447
}
"""
Scénario: Détection complétion 100% de l'audio-guide
Étant donné un audio-guide de 12 séquences
Et que l'utilisateur complète la 12ème et dernière séquence à >80%
Quand la progression est sauvegardée
Alors is_completed passe à true
Et completed_at est mis à jour avec le timestamp actuel
Et un événement "audio_guide_completed" est émis
Scénario: GET /api/v1/users/me/audio-guides/completed - Liste des complétés
Étant donné que l'utilisateur a complété 2 audio-guides
Quand je fais un GET sur "/api/v1/users/me/audio-guides/completed"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"completed_count": 2,
"audio_guides": [
{
"audio_guide_id": "ag_1",
"title": "Tour de Paris",
"completed_at": "2026-01-15T10:00:00Z",
"total_sequences": 10,
"total_duration": 3600
},
{
"audio_guide_id": "ag_2",
"title": "Découverte de Lyon",
"completed_at": "2026-01-20T14:00:00Z",
"total_sequences": 8,
"total_duration": 2700
}
]
}
"""
Scénario: GET /api/v1/users/me/audio-guides/in-progress - Liste en cours
Étant donné que l'utilisateur a 3 audio-guides en cours
Quand je fais un GET sur "/api/v1/users/me/audio-guides/in-progress"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"in_progress_count": 3,
"audio_guides": [
{
"audio_guide_id": "ag_123",
"title": "Visite du Louvre",
"current_sequence": 6,
"total_sequences": 12,
"completion_percentage": 50,
"last_played_at": "2026-01-22T14:35:42Z"
}
]
}
"""
# Badges et achievements
Scénario: Attribution badge "Premier audio-guide"
Étant donné qu'un utilisateur complète son 1er audio-guide
Quand le système détecte la complétion
Alors un badge "first_audio_guide" est attribué
Et une notification est envoyée:
"""json
{
"type": "badge_unlocked",
"badge_id": "first_audio_guide",
"title": "🎧 Premier audio-guide",
"message": "Félicitations ! Vous avez complété votre premier audio-guide"
}
"""
Plan du Scénario: Attribution badges selon nombre complétés
Étant donné qu'un utilisateur complète son <nombre>ème audio-guide
Quand le système détecte la complétion
Alors le badge "<badge_id>" est attribué
Exemples:
| nombre | badge_id |
| 1 | first_audio_guide |
| 5 | explorer |
| 10 | completist |
| 25 | expert |
| 50 | master |
# Nettoyage et archivage
Scénario: Archivage progressions inactives (>6 mois)
Étant donné une progression avec last_played_at à 200 jours
Quand le job de nettoyage automatique s'exécute
Alors la progression est déplacée vers la table audio_guide_progress_archive
Et elle reste récupérable via l'API pendant 30 jours supplémentaires
Et après 7 mois total, elle est supprimée définitivement
Scénario: GET /api/v1/audio-guides/{id}/progress/archived - Récupération archivée
Étant donné une progression archivée
Quand je fais un GET sur "/api/v1/audio-guides/ag_123/progress/archived"
Alors le code HTTP de réponse est 200
Et la progression archivée est retournée
Et un message indique: "Progression archivée. Vous pouvez la restaurer."
Scénario: POST /api/v1/audio-guides/{id}/progress/restore - Restauration
Étant donné une progression archivée
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/progress/restore"
Alors le code HTTP de réponse est 200
Et la progression est déplacée de archive vers la table active
Et l'utilisateur peut reprendre son écoute
# Cas d'erreur
Scénario: Sauvegarde progression audio-guide inexistant
Quand je fais un POST sur "/api/v1/audio-guides/ag_nonexistant/progress"
Alors le code HTTP de réponse est 404
Et le message d'erreur est "Audio-guide non trouvé"
Scénario: Sauvegarde progression séquence invalide
Étant donné un audio-guide "ag_123" avec 8 séquences
Et une requête avec current_sequence_id "seq_99" (inexistant)
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/progress"
Alors le code HTTP de réponse est 400
Et le message d'erreur est "Séquence inexistante pour cet audio-guide"
Scénario: Récupération progression sans authentification
Étant donné une requête sans token JWT
Quand je fais un GET sur "/api/v1/audio-guides/ag_123/progress"
Alors le code HTTP de réponse est 401
Et le message d'erreur est "Authentification requise"
Scénario: Corruption de données progression (récupération)
Étant donné une progression avec données corrompues (JSON invalide)
Quand je fais un GET sur "/api/v1/audio-guides/ag_123/progress"
Alors le système tente une récupération depuis le backup quotidien
Et si récupération réussie, les données sont restaurées
Et un log d'erreur est créé pour investigation
Scénario: Échec synchronisation cloud (mode dégradé)
Étant donné que la base PostgreSQL est temporairement indisponible
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/progress"
Alors le code HTTP de réponse est 503
Et le message d'erreur est "Service temporairement indisponible. Réessayez dans quelques instants."
Et le client doit conserver la progression localement et réessayer
# Performance et optimisations
Scénario: Index sur user_id + audio_guide_id pour requêtes rapides
Étant donné un index composite (user_id, audio_guide_id)
Quand je fais un GET sur "/api/v1/audio-guides/ag_123/progress"
Alors la requête PostgreSQL utilise l'index
Et le temps de réponse est < 20ms
Scénario: Cache Redis pour progressions actives
Étant donné qu'une progression est fréquemment mise à jour (toutes les 30s)
Quand la progression est sauvegardée
Alors elle est également cachée dans Redis avec TTL 1h
Et les GET suivants lisent depuis Redis (pas PostgreSQL)
Et la latence est < 10ms
Scénario: Invalidation cache Redis lors de réinitialisation
Étant donné qu'une progression est en cache Redis
Quand je fais un DELETE sur "/api/v1/audio-guides/ag_123/progress"
Alors l'entrée cache Redis est supprimée
Et la base PostgreSQL est mise à jour
Et la cohérence est garantie

View File

@@ -0,0 +1,111 @@
# language: fr
@api @audio-guides @advertising @mvp
Fonctionnalité: Système de publicités complet
En tant que plateforme
Je veux gérer un système publicitaire équilibré et non intrusif
Afin de monétiser la plateforme tout en préservant l'expérience utilisateur
Contexte:
Étant donné les règles publicitaires:
| Règle | Valeur |
| Durée max publicité | 30s |
| Fréquence max par heure | 3 |
| Skip autorisé après | 5s |
| Mode auto-play | Piéton only |
| Premium sans pub | Oui |
Scénario: Insertion intelligente de publicité entre séquences
Étant donné un utilisateur "alice@roadwave.fr" Free en mode piéton
Quand elle termine l'écoute d'une séquence
Et marche vers la suivante (temps de trajet: 5 min)
Alors une publicité peut être insérée pendant le trajet
Et elle ne coupe jamais une séquence en cours
Et un événement "AD_INSERTED_BETWEEN_SEQUENCES" est enregistré
Scénario: Ciblage géographique et contextuel des publicités
Étant donné un utilisateur "bob@roadwave.fr" près de restaurants
Et ses intérêts incluent "Gastronomie" à 80%
Quand une publicité doit être affichée
Alors le système priorise les restaurants à proximité
Et match les intérêts de l'utilisateur
Et un événement "AD_TARGETED" est enregistré avec score_match: 95
Scénario: Format publicitaire audio + visuel
Étant donné une publicité pour le "Café Le Parisien"
Quand elle est diffusée
Alors l'audio est joué (max 30s)
Et une carte visuelle s'affiche avec:
| Élément | Contenu |
| Logo | Image du commerce |
| Offre spéciale | -10% avec code ROAD10 |
| Distance | À 50m |
| Bouton CTA | [Y aller] [Sauvegarder]|
Et un événement "AD_DISPLAYED_FULL" est enregistré
Scénario: Facturation au CPM et CPC pour annonceurs
Étant donné un commerce "Le Parisien" avec budget pub
Quand sa publicité est diffusée 1000 fois (impressions)
Alors il est facturé selon le modèle CPM: 5 pour 1000 impressions
Quand 50 utilisateurs cliquent sur "Y aller"
Alors il est facturé selon le CPC: 0.50 par clic
Et un événement "AD_BILLING_CALCULATED" est enregistré
Scénario: Dashboard annonceur avec statistiques détaillées
Étant donné un annonceur "Restaurant Tokyo" connecté
Quand il consulte son dashboard
Alors il voit les métriques en temps réel:
| Métrique | Valeur |
| Impressions (7 jours) | 2 450 |
| Taux d'écoute complète | 38% |
| Clics "Y aller" | 125 |
| Visites confirmées | 45 |
| Taux de conversion | 1.8% |
| Budget dépensé | 42.50 |
| Coût par visite | 0.94 |
Et un événement "AD_DASHBOARD_VIEWED" est enregistré
Scénario: A/B testing automatisé des créatives publicitaires
Étant donné un annonceur avec 3 versions de publicité
Quand le système diffuse les pubs
Alors chaque version est diffusée à 33% du trafic
Et les performances sont comparées après 1000 impressions
Et la meilleure version est automatiquement privilégiée
Et un événement "AD_AB_TEST_WINNER_SELECTED" est enregistré
Scénario: Limite de fréquence stricte pour éviter la saturation
Étant donné un utilisateur "charlie@roadwave.fr"
Et il a déjà entendu 3 pubs dans la dernière heure
Quand le système tente d'insérer une 4ème pub
Alors elle est bloquée
Et l'utilisateur voit: "Prochaine pub dans 25 min"
Et un événement "AD_FREQUENCY_CAP_REACHED" est enregistré
Scénario: Publicités Premium sponsorisées prioritaires
Étant donné un annonceur "Musée du Louvre" avec campagne premium
Quand un utilisateur passe à proximité
Alors sa publicité est priorisée sur les autres
Et elle a un format étendu (45s autorisées)
Et un badge "Partenaire officiel" s'affiche
Et un événement "AD_PREMIUM_DISPLAYED" est enregistré
Scénario: Sauvegarde d'offres publicitaires pour utilisation ultérieure
Étant donné un utilisateur "david@roadwave.fr" qui entend une pub
Quand il clique sur "Sauvegarder l'offre"
Alors l'offre est ajoutée à "Mes offres sauvegardées"
Et il peut la consulter plus tard
Et la validité de l'offre est affichée: "Valable jusqu'au 31/03"
Et un événement "AD_OFFER_SAVED" est enregistré
Scénario: Métriques de performance du système publicitaire
Étant donné que 100 000 pubs ont été diffusées
Alors les indicateurs suivants sont disponibles:
| Métrique | Valeur |
| Taux de skip moyen | 62% |
| Taux d'écoute complète | 38% |
| CTR (Click-Through Rate) | 5.2% |
| Taux de conversion (visites) | 3.1% |
| Revenu moyen par utilisateur | 2.40/an |
| Satisfaction utilisateurs | 3.8/5 |
Et les métriques sont exportées vers le monitoring

View File

@@ -0,0 +1,357 @@
# language: fr
Fonctionnalité: API - Publicités dans audio-guides
En tant que système backend
Je veux gérer l'insertion et la diffusion de publicités dans les audio-guides
Afin de monétiser les contenus gratuits
Contexte:
Étant donné que l'API RoadWave est démarrée
Et que l'utilisateur "user@example.com" est authentifié (gratuit)
# 16.5.1 - Insertion publicité
Scénario: Calcul insertion publicité (1 pub toutes les 5 séquences)
Étant donné un audio-guide gratuit avec 12 séquences
Et que la fréquence pub est configurée à "1/5"
Quand je fais un GET sur "/api/v1/audio-guides/ag_123/ad-schedule"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"ad_frequency": "1/5",
"ad_insertions": [
{"after_sequence": 5, "position": "after"},
{"after_sequence": 10, "position": "after"}
],
"total_ads": 2
}
"""
Plan du Scénario: Fréquence publicité configurable admin
Étant donné que la fréquence pub est configurée à <frequence>
Et un audio-guide avec 12 séquences
Quand les insertions pub sont calculées
Alors le nombre de pubs insérées est <nombre_pubs>
Exemples:
| frequence | nombre_pubs |
| 1/3 | 4 |
| 1/5 | 2 |
| 1/10 | 1 |
Scénario: Utilisateur Premium - Aucune publicité
Étant donné un utilisateur Premium
Et un audio-guide gratuit
Quand je fais un GET sur "/api/v1/audio-guides/ag_123/ad-schedule"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"ad_frequency": "0",
"ad_insertions": [],
"total_ads": 0,
"reason": "premium_user"
}
"""
Scénario: POST /api/v1/audio-guides/playback/next-ad - Récupération pub suivante
Étant donné qu'un utilisateur termine la séquence 5
Et qu'une pub doit être insérée
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/playback/next-ad":
"""json
{
"sequence_completed": 5,
"user_position": {
"latitude": 43.1234,
"longitude": 2.5678
},
"mode": "voiture"
}
"""
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"should_play_ad": true,
"ad": {
"ad_id": "ad_789",
"audio_url": "https://cdn.roadwave.fr/ads/ad_789.m4a",
"duration_seconds": 30,
"skippable_after": 5,
"advertiser": "Brand X"
}
}
"""
Scénario: Sélection pub géolocalisée
Étant donné que l'utilisateur est en Île-de-France (43.1234, 2.5678)
Et que des publicités géolocalisées existent pour cette région
Quand la pub suivante est sélectionnée
Alors l'API filtre les pubs par:
| critère | valeur |
| Géolocalisation | Île-de-France |
| Catégorie | Tourisme, Culture |
| Langue | Français |
| Budget actif | true |
Et une pub correspondante est retournée
Scénario: Fallback pub nationale si pas de pub locale
Étant donné que l'utilisateur est dans une région sans pubs locales
Quand la pub suivante est sélectionnée
Alors l'API sélectionne une pub nationale (France entière)
Et la pub est retournée normalement
Scénario: Pas de pub si séquence non multiple de 5
Étant donné qu'un utilisateur termine la séquence 4
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/playback/next-ad"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"should_play_ad": false,
"reason": "not_ad_sequence"
}
"""
# Comportement selon mode
Scénario: Pub en mode piéton (auto-play puis pause)
Étant donné un audio-guide en mode piéton
Et qu'une pub doit être insérée après séquence 5
Quand la pub est récupérée
Alors le mode_behavior retourné est:
"""json
{
"auto_play": true,
"pause_after": true,
"reason": "pedestrian_mode"
}
"""
Scénario: Pub en mode voiture/vélo/transport (auto-play puis séquence suivante)
Étant donné un audio-guide en mode voiture
Et qu'une pub doit être insérée
Quand la pub est récupérée
Alors le mode_behavior retourné est:
"""json
{
"auto_play": true,
"pause_after": false,
"continue_to_next": true,
"reason": "vehicle_mode"
}
"""
# 16.5.2 - Métriques et tracking
Scénario: POST /api/v1/ads/{ad_id}/impressions - Enregistrement impression
Étant donné qu'une pub "ad_789" démarre
Quand je fais un POST sur "/api/v1/ads/ad_789/impressions":
"""json
{
"audio_guide_id": "ag_123",
"sequence_after": 5,
"user_id": "user_456",
"timestamp": "2026-01-22T14:35:00Z"
}
"""
Alors le code HTTP de réponse est 201
Et l'impression est enregistrée dans ad_impressions
Et le compteur impressions_count est incrémenté
Scénario: POST /api/v1/ads/{ad_id}/completions - Enregistrement écoute complète
Étant donné qu'une pub de 30 secondes est écoutée à 25 secondes (83%)
Quand je fais un POST sur "/api/v1/ads/ad_789/completions":
"""json
{
"audio_guide_id": "ag_123",
"listened_seconds": 25,
"total_duration": 30,
"completion_rate": 83
}
"""
Alors le code HTTP de réponse est 201
Et l'écoute complète est enregistrée (>80%)
Et le créateur de l'audio-guide reçoit 0.003 de revenus
Scénario: POST /api/v1/ads/{ad_id}/skips - Enregistrement skip
Étant donné qu'une pub est skippée après 6 secondes
Quand je fais un POST sur "/api/v1/ads/ad_789/skips":
"""json
{
"audio_guide_id": "ag_123",
"skipped_at_second": 6
}
"""
Alors le code HTTP de réponse est 201
Et le skip est enregistré dans ad_skips
Et le taux de skip est mis à jour
Scénario: Validation écoute complète (>80%)
Étant donné qu'une pub de 30 secondes est écoutée 20 secondes (66%)
Quand je fais un POST sur "/api/v1/ads/ad_789/completions"
Alors le code HTTP de réponse est 400
Et le message d'erreur est "completion_rate: minimum 80% requis pour écoute complète"
Et aucun revenu n'est attribué
# Métriques créateur
Scénario: GET /api/v1/creators/me/audio-guides/{id}/ad-metrics - Métriques pub
Étant donné un audio-guide "ag_123" avec publicités
Et les métriques suivantes sur 30 jours:
| impressions | ecoutes_completes | skips | revenus |
| 1000 | 650 | 350 | 1.95 |
Quand je fais un GET sur "/api/v1/creators/me/audio-guides/ag_123/ad-metrics"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"period": "30_days",
"impressions": 1000,
"completions": 650,
"skips": 350,
"completion_rate": 65,
"revenue": 1.95,
"cpm": 1.95
}
"""
Scénario: Distinction revenus contenus classiques vs audio-guides
Étant donné un créateur avec contenus classiques et audio-guides
Quand je fais un GET sur "/api/v1/creators/me/revenue-breakdown"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"total_revenue": 85.40,
"breakdown": {
"classic_content": {
"revenue": 45.20,
"impressions": 15000
},
"audio_guides": {
"revenue": 40.20,
"impressions": 13000
}
}
}
"""
# Répartition revenus
Scénario: Calcul revenus créateur (3€ / 1000 écoutes complètes)
Étant donné un audio-guide avec 1000 écoutes complètes pub ce mois
Quand les revenus sont calculés
Alors le créateur reçoit 3
Et le revenu par écoute complète est 0.003
Scénario: POST /api/v1/ads/revenue/process - Calcul revenus batch mensuel
Étant donné le 1er du mois
Et que 500 créateurs ont des revenus pub à calculer
Quand le job batch s'exécute
Alors pour chaque créateur:
| creator_id | ecoutes_completes | revenus |
| creator_1 | 5000 | 15.00 |
| creator_2 | 2000 | 6.00 |
| creator_3 | 1200 | 3.60 |
Et les revenus sont ajoutés au solde creator_balance
# Normalisation audio
Scénario: Validation volume pub (-14 LUFS)
Étant donné qu'une pub est uploadée avec volume -10 LUFS
Quand la pub est validée
Alors un processus de normalisation est déclenché
Et le volume est ajusté à -14 LUFS (standard RoadWave)
Et la pub normalisée est stockée sur le CDN
Scénario: Validation durée pub (max 60 secondes)
Étant donné qu'une pub de 75 secondes est uploadée
Quand la validation est effectuée
Alors le code HTTP de réponse est 400
Et le message d'erreur est "duration: maximum 60 secondes pour une publicité"
# Cas d'erreur
Scénario: Aucune pub disponible (stock épuisé)
Étant donné qu'aucune campagne pub n'est active dans la région
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/playback/next-ad"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"should_play_ad": false,
"reason": "no_ads_available"
}
"""
Et aucune pub n'est insérée (séquence suivante démarre directement)
Scénario: Budget campagne épuisé
Étant donné qu'une campagne pub a un budget de 1000
Et que le budget est épuisé
Quand la pub est sélectionnée
Alors cette campagne est exclue de la sélection
Et une autre campagne active est choisie
Scénario: Pub corrompue ou indisponible
Étant donné qu'une pub sélectionnée a un fichier audio corrompu
Quand le client tente de la charger
Alors une pub de fallback (backup) est retournée
Et un log d'erreur est créé pour investigation
# Filtrage et ciblage
Scénario: Ciblage par catégorie audio-guide
Étant donné un audio-guide tagué "tourisme", "culture", "musée"
Et une campagne pub ciblée "tourisme + culture"
Quand la pub est sélectionnée
Alors cette campagne a une priorité élevée (matching tags)
Et elle est préférée aux pubs génériques
Scénario: Filtrage par classification âge
Étant donné un audio-guide classifié "tout_public"
Et une campagne pub classifiée "18+"
Quand la pub est sélectionnée
Alors cette campagne est exclue
Et seules les pubs "tout_public" sont éligibles
Scénario: Limite fréquence pub par utilisateur (cap frequency)
Étant donné qu'un utilisateur a déjà entendu la pub "ad_789" 3 fois ce jour
Et que le cap frequency est configuré à 3/jour
Quand la pub est sélectionnée
Alors "ad_789" est exclue
Et une autre pub est choisie
Scénario: GET /api/v1/audio-guides/{id}/ad-policy - Politique pub
Étant donné un audio-guide
Quand je fais un GET sur "/api/v1/audio-guides/ag_123/ad-policy"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"has_ads": true,
"frequency": "1/5",
"skippable_after_seconds": 5,
"average_ad_duration": 30,
"revenue_share": {
"creator": "100%",
"platform": "0%"
}
}
"""
# Performance
Scénario: Cache Redis pour pubs actives
Étant donné que les campagnes actives sont en cache Redis
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/playback/next-ad"
Alors les pubs sont récupérées depuis Redis (pas PostgreSQL)
Et le temps de réponse est < 30ms
Scénario: Pre-fetching pub suivante (client)
Étant donné que l'utilisateur est à la séquence 3
Et qu'une pub sera insérée après la séquence 5
Quand le client détecte l'approche de la séquence 5
Alors il peut pré-charger la pub via GET "/api/v1/audio-guides/ag_123/ad-prefetch?after_sequence=5"
Et la transition sera instantanée

View File

@@ -0,0 +1,123 @@
# language: fr
@api @audio-guides @content-creation @mvp
Fonctionnalité: Rayon de déclenchement configurable par le créateur
En tant que créateur de contenu
Je veux configurer le rayon de déclenchement de chaque point d'intérêt
Afin d'adapter l'expérience selon le type de lieu et le contexte
Contexte:
Étant donné que les rayons configurables respectent:
| Paramètre | Valeur |
| Rayon minimum | 10 mètres |
| Rayon maximum | 500 mètres |
| Rayon par défaut | 100 mètres |
| Ajustement | Par pas de 10m |
Scénario: Configuration du rayon lors de la création d'une séquence
Étant donné un créateur "alice@roadwave.fr" qui ajoute un point d'intérêt
Quand elle place un marqueur pour "Cathédrale Notre-Dame"
Alors un slider de rayon s'affiche avec:
| Élément | Valeur |
| Rayon actuel | 100m (défaut) |
| Rayon minimum | 10m |
| Rayon maximum | 500m |
| Visualisation | Cercle sur la carte |
Et elle peut ajuster le rayon à 150m
Alors le cercle sur la carte s'agrandit à 150m de rayon
Et un événement "POI_RADIUS_CONFIGURED" est enregistré avec rayon: 150
Et la métrique "poi.radius.configured" est incrémentée
Scénario: Rayon petit pour monuments précis (10-50m)
Étant donné un créateur "bob@roadwave.fr" qui crée un audio-guide urbain
Quand il configure un point pour une statue spécifique
Et définit le rayon à 20m
Alors le déclenchement sera très précis (proximité immédiate)
Et le système valide que le rayon est suffisant
Et un événement "POI_RADIUS_SMALL" est enregistré
Et la métrique "poi.radius.small" est incrémentée
Scénario: Rayon large pour zones étendues (200-500m)
Étant donné un créateur "charlie@roadwave.fr" qui crée un audio-guide de parc
Quand il configure un point pour "Jardin du Luxembourg"
Et définit le rayon à 300m
Alors le déclenchement sera anticipé (approche du parc)
Et le système valide que le rayon n'est pas excessif
Et un événement "POI_RADIUS_LARGE" est enregistré
Et la métrique "poi.radius.large" est incrémentée
Scénario: Visualisation en temps réel du rayon sur la carte
Étant donné un créateur "david@roadwave.fr" qui ajuste un rayon
Quand il déplace le slider de 100m à 250m
Alors le cercle sur la carte s'agrandit en temps réel
Et la zone de déclenchement est colorée en semi-transparent
Et le rayon en mètres est affiché sur la carte
Et un événement "POI_RADIUS_VISUALIZED" est enregistré
Scénario: Suggestions de rayon basées sur le type de lieu
Étant donné un créateur "eve@roadwave.fr" qui ajoute un POI
Quand elle sélectionne le type "Monument"
Alors le système suggère un rayon de 50m
Quand elle sélectionne le type "Parc/Jardin"
Alors le système suggère un rayon de 200m
Quand elle sélectionne le type "Vue panoramique"
Alors le système suggère un rayon de 100m
Et un événement "POI_RADIUS_SUGGESTED" est enregistré
Scénario: Test de simulation du déclenchement
Étant donné un créateur "frank@roadwave.fr" qui configure un rayon de 150m
Quand il clique sur "Tester le déclenchement"
Alors une simulation GPS démarre
Et il peut voir à quelle distance le point se déclencherait
Et ajuster le rayon si nécessaire
Et un événement "POI_RADIUS_TESTED" est enregistré
Scénario: Modification du rayon après publication
Étant donné un créateur "grace@roadwave.fr" avec audio-guide publié
Et elle constate que le rayon de 50m est trop petit (retours utilisateurs)
Quand elle modifie le rayon à 120m
Alors la modification prend effet immédiatement
Et tous les futurs déclenchements utilisent le nouveau rayon
Et un événement "POI_RADIUS_UPDATED" est enregistré
Scénario: Détection de chevauchements entre rayons
Étant donné un créateur "henry@roadwave.fr" avec 2 points proches
Quand les cercles de rayon se chevauchent à plus de 50%
Alors un avertissement s'affiche: "Attention: chevauchement détecté"
Et une suggestion est proposée: "Réduire les rayons ou espacer les points"
Et un événement "POI_RADIUS_OVERLAP_DETECTED" est enregistré
Scénario: Rayons adaptatifs selon le mode de déplacement
Étant donné un créateur "iris@roadwave.fr" qui configure un point
Quand elle active "Rayons adaptatifs"
Alors le système configure automatiquement:
| Mode | Rayon suggéré |
| Piéton | 80m |
| Vélo | 120m |
| Voiture | 300m |
Et les utilisateurs bénéficient du rayon optimal selon leur mode
Et un événement "POI_RADIUS_ADAPTIVE" est enregistré
Scénario: Statistiques d'efficacité des rayons
Étant donné un créateur "jack@roadwave.fr" avec audio-guide publié
Quand il consulte les statistiques de ses POI
Alors il voit pour chaque point:
| Point | Rayon | Taux déclenchement | Taux manqué |
| Notre-Dame | 100m | 95% | 5% |
| Sainte-Chapelle| 50m | 78% | 22% |
| Panthéon | 150m | 98% | 2% |
Et des suggestions d'optimisation sont proposées
Et un événement "POI_RADIUS_STATS_VIEWED" est enregistré
Scénario: Métriques de performance des rayons configurés
Étant donné que 5000 POI ont été configurés
Quand les métriques sont collectées
Alors les indicateurs suivants sont disponibles:
| Métrique | Valeur moyenne |
| Rayon moyen configuré | 125m |
| Rayon le plus petit utilisé | 15m |
| Rayon le plus grand utilisé | 450m |
| Taux d'ajustement après tests | 35% |
| Taux de déclenchement réussi | 88% |
Et les métriques sont exportées vers le monitoring

View File

@@ -0,0 +1,103 @@
# language: fr
@api @audio-guides @progression @mvp
Fonctionnalité: Reprise de progression complète
En tant qu'utilisateur
Je veux reprendre un audio-guide là où je l'ai laissé
Afin de continuer mon expérience sans perdre ma progression
Contexte:
Étant donné que la sauvegarde de progression inclut:
| Donnée | Persistance |
| Séquences écoutées | Permanente |
| Position dans l'audio | 7 jours |
| Points manqués | Permanente |
| Progression globale | Permanente |
Scénario: Sauvegarde automatique de la progression
Étant donné un utilisateur "alice@roadwave.fr" qui écoute une séquence
Quand elle ferme l'application à 3min 20s
Alors la progression est sauvegardée automatiquement
Et la position exacte dans l'audio est conservée
Et un événement "PROGRESS_AUTO_SAVED" est enregistré
Scénario: Reprise après fermeture de l'application
Étant donné un utilisateur "bob@roadwave.fr" qui rouvre l'application
Et il avait un audio-guide en cours (5/10 séquences)
Quand il accède à l'écran d'accueil
Alors une carte "Reprendre votre visite" s'affiche:
| Élément | Contenu |
| Titre audio-guide | Visite du Quartier Latin |
| Progression | 5/10 séquences (50%) |
| Dernière position | Panthéon - 3min 20s |
| Bouton | [Reprendre] |
Et un événement "RESUME_CARD_DISPLAYED" est enregistré
Scénario: Reprise exacte de la position audio
Étant donné un utilisateur "charlie@roadwave.fr" qui reprend un audio-guide
Et il était à 3min 20s dans la séquence "Panthéon"
Quand il clique sur "Reprendre"
Alors l'audio reprend exactement à 3min 20s
Et aucune seconde n'est perdue
Et un événement "AUDIO_POSITION_RESTORED" est enregistré
Scénario: Synchronisation multi-appareils de la progression
Étant donné un utilisateur "david@roadwave.fr" qui écoute sur iPhone
Et il a complété 3 séquences
Quand il passe sur son iPad
Alors la progression est synchronisée automatiquement
Et il peut reprendre là où il s'était arrêté
Et un événement "PROGRESS_SYNCED_CROSS_DEVICE" est enregistré
Scénario: Historique des audio-guides en cours
Étant donné un utilisateur "eve@roadwave.fr" avec 3 audio-guides en cours
Quand elle accède à "Mes audio-guides en cours"
Alors elle voit la liste:
| Audio-guide | Progression | Dernière activité |
| Quartier Latin | 5/10 (50%) | Il y a 2 heures |
| Châteaux de la Loire | 3/8 (37%) | Il y a 3 jours |
| Montmartre | 1/6 (16%) | Il y a 1 semaine |
Et elle peut reprendre n'importe lequel
Et un événement "IN_PROGRESS_LIST_VIEWED" est enregistré
Scénario: Expiration de la position audio après 7 jours
Étant donné un utilisateur "frank@roadwave.fr" avec audio-guide en pause
Et 8 jours se sont écoulés depuis la dernière écoute
Quand il reprend l'audio-guide
Alors la progression globale est conservée (séquences écoutées)
Mais la position exacte dans l'audio est réinitialisée
Et un message s'affiche: "La séquence redémarre depuis le début"
Et un événement "AUDIO_POSITION_EXPIRED" est enregistré
Scénario: Badge "Explorateur assidu" pour reprises régulières
Étant donné un utilisateur "grace@roadwave.fr" qui reprend 10 audio-guides
Quand il complète chacun d'eux après les avoir repris
Alors un badge "Explorateur assidu" est débloqué
Et un événement "BADGE_PERSISTENT_EXPLORER_UNLOCKED" est enregistré
Scénario: Notification push de rappel après 3 jours d'inactivité
Étant donné un utilisateur "henry@roadwave.fr" avec audio-guide en pause
Et 3 jours se sont écoulés sans activité
Quand le système envoie des rappels
Alors une notification push est envoyée:
"Vous avez laissé 'Visite du Quartier Latin' en suspens (5/10). Reprendre ?"
Et un événement "RESUME_REMINDER_SENT" est enregistré
Scénario: Mode hors ligne avec sauvegarde locale
Étant donné un utilisateur "iris@roadwave.fr" en mode hors ligne
Quand elle écoute un audio-guide sans connexion
Alors la progression est sauvegardée localement
Et synchronisée automatiquement lors de la reconnexion
Et un événement "PROGRESS_SYNCED_AFTER_OFFLINE" est enregistré
Scénario: Métriques de reprise de progression
Étant donné que 10 000 audio-guides ont été mis en pause
Alors les indicateurs suivants sont disponibles:
| Métrique | Valeur |
| Taux de reprise dans les 24h | 42% |
| Taux de reprise dans les 7j | 68% |
| Taux d'abandon définitif | 32% |
| Temps moyen avant reprise | 2.5 jours|
| Taux de complétion après reprise| 78% |
Et les métriques sont exportées vers le monitoring

View File

@@ -0,0 +1,75 @@
# language: fr
@api @audio-guides @sync @mvp
Fonctionnalité: Sauvegarde et synchronisation de progression
En tant qu'utilisateur
Je veux que ma progression soit sauvegardée et synchronisée
Afin d'accéder à mon historique sur tous mes appareils
Scénario: Sauvegarde en temps réel dans le cloud
Étant donné un utilisateur "alice@roadwave.fr" connecté
Quand elle complète une séquence
Alors la progression est sauvegardée dans le cloud immédiatement
Et un indicateur "Synchronisé" s'affiche
Et un événement "PROGRESS_CLOUD_SAVED" est enregistré
Scénario: Synchronisation automatique au changement d'appareil
Étant donné un utilisateur "bob@roadwave.fr" sur iPhone
Quand il se connecte sur iPad
Alors la progression est téléchargée automatiquement
Et synchronisée en arrière-plan (< 2s)
Et un événement "PROGRESS_SYNCED_DEVICE_SWITCH" est enregistré
Scénario: Résolution de conflits de synchronisation
Étant donné un utilisateur "charlie@roadwave.fr" avec 2 appareils
Et il écoute hors ligne sur les deux simultanément
Quand les deux se reconnectent avec progressions différentes
Alors le système fusionne intelligemment les données
Et conserve la progression la plus avancée
Et un événement "SYNC_CONFLICT_RESOLVED" est enregistré
Scénario: Indicateur de statut de synchronisation
Étant donné un utilisateur "david@roadwave.fr"
Alors il voit l'icône de statut sync:
| État | Icône | Couleur |
| Synchronisé | ✓ | Vert |
| En cours de sync | ↻ | Orange |
| Non synchronisé | ⚠ | Rouge |
Et un événement "SYNC_STATUS_DISPLAYED" est enregistré
Scénario: Sauvegarde locale en mode hors ligne
Étant donné un utilisateur "eve@roadwave.fr" sans connexion
Quand elle écoute un audio-guide hors ligne
Alors toutes les données sont sauvegardées localement
Et marquées "En attente de synchronisation"
Et synchronisées automatiquement lors de la reconnexion
Et un événement "OFFLINE_PROGRESS_QUEUED" est enregistré
Scénario: Export de l'historique de progression
Étant donné un utilisateur "frank@roadwave.fr"
Quand il demande un export de ses données (RGPD)
Alors il reçoit un fichier JSON avec:
| Donnée | Format |
| Audio-guides écoutés | Liste |
| Séquences par guide | Détail |
| Timestamps | ISO 8601 |
| Positions GPS visitées | Lat/Lon |
Et un événement "PROGRESS_EXPORTED" est enregistré
Scénario: Suppression de progression sur demande
Étant donné un utilisateur "grace@roadwave.fr"
Quand elle supprime un audio-guide de son historique
Alors toutes les données associées sont supprimées
Et la synchronisation propage la suppression
Et un événement "PROGRESS_DELETED" est enregistré
Scénario: Métriques de fiabilité de la synchronisation
Étant donné que 100 000 synchronisations ont eu lieu
Alors les indicateurs suivants sont disponibles:
| Métrique | Valeur cible |
| Taux de succès de sync | > 99.5% |
| Temps moyen de synchronisation| < 2s |
| Taux de conflits | < 0.5% |
| Taux de résolution automatique| > 95% |
Et les métriques sont exportées vers le monitoring

View File

@@ -0,0 +1,192 @@
# language: fr
Fonctionnalité: Système double clic et sortie audio-guide mode voiture
En tant qu'utilisateur en voiture
Je veux pouvoir désactiver le GPS automatique et sortir de l'audio-guide facilement
Afin de gérer les situations d'embouteillage ou de changement de plan
Contexte:
Étant donné qu'un utilisateur est en mode voiture
Et qu'un audio-guide de 8 séquences est actif
Et que le mode GPS automatique est activé par défaut
Et que la séquence 2 est en cours de lecture
# Comportement bouton [▶|] Suivant
Scénario: Premier clic Suivant - Passage en mode manuel
Étant donné que le mode GPS auto est actif
Et que la séquence 2 vient de se terminer
Et que le prochain point GPS (séquence 3) est à 2 km
Quand l'utilisateur clique sur le bouton [|] Suivant
Alors le GPS automatique est désactivé
Et le mode bascule en "mode manuel"
Et la séquence 3 démarre immédiatement
Et un toast s'affiche pendant 3 secondes:
"""
Mode manuel activé. Cliquez à nouveau pour quitter l'audio-guide.
"""
Et un timer de 10 secondes démarre en arrière-plan
Scénario: Deuxième clic Suivant dans les 10 secondes - Sortie audio-guide
Étant donné que le mode manuel vient d'être activé il y a 5 secondes
Et que la séquence 3 est en cours de lecture
Quand l'utilisateur clique à nouveau sur [|] Suivant
Alors l'audio-guide est mis en pause
Et l'historique de progression est conservé (séquence 3 à X:XX)
Et l'application retourne au flux normal de recommandation
Et un toast s'affiche pendant 2 secondes: "Audio-guide en pause"
Scénario: Clic Suivant après 10 secondes - Navigation normale
Étant donné que le mode manuel est actif depuis 12 secondes
Et que la séquence 3 est en cours
Quand l'utilisateur clique sur [|] Suivant
Alors la séquence 4 démarre immédiatement
Et le timer de 10 secondes redémarre
Et le mode reste en "mode manuel"
Et aucune sortie d'audio-guide ne se produit
Scénario: Clics multiples Suivant en mode manuel
Étant donné que le mode manuel est actif
Et que l'utilisateur clique sur [|] pour passer séquence 3 4
Et que 5 secondes se passent
Quand l'utilisateur clique à nouveau sur [|] pour passer séquence 4 5
Alors la séquence 5 démarre
Et le timer de 10 secondes redémarre à chaque clic
Et l'utilisateur peut naviguer normalement entre les séquences
Scénario: Double clic rapide accidentel - sortie immédiate
Étant donné que le mode GPS auto est actif
Et que la séquence 2 vient de se terminer
Quand l'utilisateur clique sur [|] (clic 1)
Et que l'utilisateur clique immédiatement sur [|] (clic 2 à <2s)
Alors l'audio-guide est mis en pause après le clic 2
Et l'utilisateur retourne au flux normal
Et un toast confirme: "Audio-guide en pause"
# Comportement bouton [|◀] Précédent
Scénario: Bouton Précédent dans audio-guide GPS auto
Étant donné que le mode GPS auto est actif
Et que la séquence 3 est en cours
Quand l'utilisateur clique sur [|◀] Précédent
Alors la séquence 2 démarre
Et l'audio-guide reste actif
Et le mode GPS auto reste actif
Scénario: Bouton Précédent dans audio-guide mode manuel
Étant donné que le mode manuel est actif
Et que la séquence 5 est en cours
Quand l'utilisateur clique sur [|◀] Précédent
Alors la séquence 4 démarre
Et l'audio-guide reste actif
Et le mode manuel reste actif
Scénario: Bouton Précédent hors audio-guide - Reprend audio-guide si contenu précédent
Étant donné que l'utilisateur a quitté l'audio-guide "Safari du Paugre"
Et que l'utilisateur écoute un contenu normal "Podcast A"
Quand l'utilisateur clique sur [|◀] Précédent
Alors l'audio-guide "Safari du Paugre" reprend
Et la dernière séquence écoutée (séquence 3) reprend
# Détection et reprise après détour
Scénario: Détection hors itinéraire >1 km pendant >10 min
Étant donné que l'audio-guide est actif (mode GPS auto ou manuel)
Et que l'utilisateur s'éloigne à 1.2 km de tous les points GPS
Et que cette situation dure 11 minutes
Quand le système détecte le hors itinéraire
Alors un toast s'affiche: "Audio-guide en pause (hors itinéraire)"
Et l'icône de l'audio-guide passe en gris (inactif)
Et la lecture continue du contenu en cours s'arrête
Scénario: Retour sur itinéraire <100m d'un point non écouté
Étant donné que l'audio-guide est en pause (hors itinéraire)
Et que l'utilisateur revient à 80m du point GPS séquence 5 (non écoutée)
Quand le système détecte le retour sur itinéraire
Alors une popup s'affiche:
"""
Reprendre l'audio-guide à la séquence 5 ?
[Reprendre] [Voir liste] [Ignorer]
"""
Scénario: Action "Reprendre" après retour sur itinéraire
Étant donné que la popup de reprise est affichée
Quand l'utilisateur clique sur [Reprendre]
Alors la séquence 5 démarre immédiatement
Et l'audio-guide redevient actif
Et l'icône repasse en couleur normale
Scénario: Action "Voir liste" après retour sur itinéraire
Étant donné que la popup de reprise est affichée
Quand l'utilisateur clique sur [Voir liste]
Alors la liste complète des séquences s'affiche
Et l'utilisateur peut choisir manuellement quelle séquence écouter
Scénario: Action "Ignorer" après retour sur itinéraire
Étant donné que la popup de reprise est affichée
Quand l'utilisateur clique sur [Ignorer]
Alors la popup se ferme
Et l'audio-guide reste en pause
Et l'utilisateur continue le flux normal de recommandation
# Respect des clics manuels
Scénario: Séquence skippée manuellement non reproposée automatiquement
Étant donné que l'utilisateur est en mode manuel
Et que l'utilisateur clique [|] pour passer de séquence 3 à séquence 4
Et que la séquence 3 est marquée "skippée volontairement"
Quand l'utilisateur revient à 50m du point GPS séquence 3
Alors aucune popup de reprise automatique ne s'affiche
Et l'utilisateur peut revenir manuellement via liste séquences s'il le souhaite
Scénario: Séquence skippée par GPS (point manqué) reproposable
Étant donné que l'utilisateur a dépassé un point GPS à 110m (rayon 30m)
Et que la séquence 3 a été marquée "point manqué" (pas de skip manuel)
Quand l'utilisateur revient à 80m du point GPS séquence 3
Alors une popup de reprise s'affiche:
"""
Reprendre la séquence 3 ?
[Reprendre] [Voir liste] [Ignorer]
"""
# Mode manuel persistant
Scénario: Mode manuel persiste jusqu'à fin audio-guide
Étant donné que le mode manuel est activé en séquence 3
Quand l'utilisateur navigue jusqu'à la séquence 8 (dernière)
Alors le mode manuel reste actif durant toutes les séquences
Et le GPS automatique n'est jamais réactivé
Scénario: Reset mode GPS auto au redémarrage audio-guide
Étant donné que l'utilisateur a quitté l'audio-guide en mode manuel
Et que plusieurs heures se sont écoulées
Quand l'utilisateur relance l'audio-guide "Safari du Paugre"
Alors le mode GPS automatique est réactivé par défaut
Et l'utilisateur peut à nouveau passer en mode manuel s'il le souhaite
# Cas d'usage réel : embouteillage
Scénario: Embouteillage - Passage manuel puis sortie
Étant donné que l'utilisateur écoute la séquence 2 "Les lions"
Et que la séquence 2 se termine
Et que le prochain point GPS (séquence 3) est à 3 km
Et que l'utilisateur est bloqué dans un embouteillage
Et que l'ETA indique " 30 minutes"
Quand l'utilisateur clique [|] (clic 1) pour passer en mode manuel
Alors la séquence 3 démarre immédiatement
Et le toast indique: "Mode manuel activé. Cliquez à nouveau pour quitter."
Quand l'utilisateur clique [|] (clic 2) dans les 8 secondes
Alors l'audio-guide est mis en pause
Et l'utilisateur retourne au flux normal (podcasts, musique)
Et la progression est sauvegardée (séquence 3 à X:XX)
Scénario: Reprise audio-guide après sortie embouteillage
Étant donné que l'utilisateur a quitté l'audio-guide en séquence 3
Et que plusieurs heures plus tard, l'utilisateur se reconnecte
Et que l'utilisateur est à 80m du point GPS séquence 4
Quand le système détecte la proximité
Alors une popup de reprise s'affiche:
"""
Reprendre l'audio-guide "Safari du Paugre" ?
Progression : 3/8 séquences
[Reprendre] [Recommencer] [Voir liste]
"""