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:
@@ -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
|
||||
@@ -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
|
||||
@@ -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."
|
||||
@@ -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%
|
||||
@@ -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
|
||||
@@ -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 "7→1" 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 "7→1" 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
|
||||
}
|
||||
"""
|
||||
@@ -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%
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
243
docs/domains/content/features/audio-guides/mode-pieton.feature
Normal file
243
docs/domains/content/features/audio-guides/mode-pieton.feature
Normal 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)
|
||||
400
docs/domains/content/features/audio-guides/mode-voiture.feature
Normal file
400
docs/domains/content/features/audio-guides/mode-voiture.feature
Normal 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 7→1 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 7→1 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."
|
||||
@@ -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
|
||||
@@ -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]
|
||||
"""
|
||||
@@ -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
|
||||
@@ -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é
|
||||
@@ -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.
|
||||
"""
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
357
docs/domains/content/features/audio-guides/publicites.feature
Normal file
357
docs/domains/content/features/audio-guides/publicites.feature
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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]
|
||||
"""
|
||||
Reference in New Issue
Block a user