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

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

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

**Changements effectués:**

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

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

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

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

View File

@@ -0,0 +1,234 @@
# language: fr
@api @navigation @mode-detection @mvp
Fonctionnalité: Basculement automatique entre modes voiture et piéton
En tant que système de navigation
Je veux détecter automatiquement le mode de déplacement de l'utilisateur
Et basculer entre mode voiture et mode piéton selon la vitesse GPS
Afin d'optimiser l'expérience utilisateur sans action manuelle
Contexte:
Étant donné un utilisateur authentifié avec l'application active
Et la géolocalisation est activée et autorisée
Et les permissions de géolocalisation en arrière-plan sont accordées
# ============================================================================
# DÉTECTION INITIALE DU MODE AU DÉMARRAGE
# ============================================================================
Scénario: Démarrage application à vitesse piéton (0-4 km/h)
Étant donné l'utilisateur ouvre l'application
Et les 3 premières lectures GPS montrent des vitesses de [0, 2, 3] km/h
Quand le système détermine le mode initial
Alors le mode "piéton" doit être activé par défaut
Et les notifications push géolocalisées doivent être activées
Et un rayon de détection de 200m doit être appliqué
Scénario: Démarrage application en mouvement (≥5 km/h)
Étant donné l'utilisateur ouvre l'application
Et les 3 premières lectures GPS montrent des vitesses de [30, 35, 32] km/h
Quand le système détermine le mode initial
Alors le mode "voiture" doit être activé par défaut
Et les notifications in-app avec compteur 7s doivent être activées
Et l'ETA doit être calculé pour les contenus géolocalisés
Scénario: Démarrage avec GPS instable - mode par défaut
Étant donné l'utilisateur ouvre l'application
Et les lectures GPS sont erratiques : [0, 45, 2, 60, 1] km/h
Quand le système ne peut pas déterminer le mode avec confiance
Alors le mode "piéton" doit être activé par défaut (mode le plus sûr)
Et une modal doit demander à l'utilisateur de confirmer son mode
Et le choix utilisateur doit être mémorisé pour la session
# ============================================================================
# BASCULEMENT VOITURE → PIÉTON (vitesse <5 km/h soutenue)
# ============================================================================
Scénario: Arrêt prolongé déclenche basculement piéton
Étant donné l'utilisateur est en mode voiture
Et la vitesse moyenne sur 2 minutes est de 2 km/h (embouteillage ou arrêt)
Quand le système détecte cette condition pendant 2 minutes consécutives
Alors le mode doit basculer automatiquement vers "piéton"
Et une notification toast doit informer : "Mode piéton activé"
Et les notifications push géolocalisées doivent être activées
Et le cooldown voiture actif doit être annulé
Scénario: Stationnement confirmé (vitesse = 0 pendant 2 minutes)
Étant donné l'utilisateur est en mode voiture
Et la vitesse GPS est de 0 km/h pendant 2 minutes consécutives
Et la précision GPS est <20m (pas de perte signal)
Quand le système détecte l'arrêt prolongé
Alors le mode doit basculer vers "piéton"
Et une notification doit proposer : "Voulez-vous activer le mode piéton ?"
Et si l'utilisateur ne répond pas, le basculement doit être automatique après 30 secondes
Scénario: Embouteillage prolongé (vitesse <5 km/h mais en mouvement)
Étant donné l'utilisateur est en mode voiture à 35 km/h
Et la vitesse chute à 3 km/h et oscille entre 0-4 km/h
Et cette condition dure 3 minutes
Quand le système détecte un mouvement résiduel (pas totalement arrêté)
Alors le mode "voiture" doit être maintenu temporairement
Mais le calcul ETA doit passer en mode "vitesse lente"
Et après 5 minutes <5 km/h, le mode piéton doit être proposé
Scénario: Feu rouge ou arrêt temporaire ne déclenche PAS de basculement
Étant donné l'utilisateur est en mode voiture à 50 km/h
Et la vitesse chute à 0 km/h pendant 45 secondes (feu rouge)
Quand le système évalue les conditions
Alors le mode "voiture" doit être maintenu
Car la durée <2 minutes (seuil de basculement)
Et aucune notification ne doit être affichée
# ============================================================================
# BASCULEMENT PIÉTON → VOITURE (vitesse ≥5 km/h soutenue)
# ============================================================================
Scénario: Démarrage voiture déclenche basculement automatique
Étant donné l'utilisateur est en mode piéton
Et la vitesse passe de 0 km/h à 15 km/h en 10 secondes
Et la vitesse reste 10 km/h pendant 30 secondes
Quand le système détecte une accélération soutenue
Alors le mode doit basculer automatiquement vers "voiture"
Et une notification toast doit informer : "Mode voiture activé"
Et les notifications in-app avec ETA doivent être activées
Et les notifications push piéton doivent être désactivées
Scénario: Course à pied ou vélo lent ne déclenche PAS de basculement voiture
Étant donné l'utilisateur est en mode piéton
Et la vitesse monte à 8 km/h et oscille entre 6-10 km/h
Quand le système évalue les conditions
Alors le mode "piéton" doit être maintenu
Car la vitesse reste <15 km/h (seuil de confiance pour voiture)
Et l'utilisateur peut basculer manuellement si nécessaire
Scénario: Trajet en bus ou vélo rapide (15-25 km/h)
Étant donné l'utilisateur est en mode piéton
Et la vitesse passe à 20 km/h et reste stable
Quand le système détecte une vitesse 15 km/h pendant 1 minute
Alors une notification doit proposer : "Passer en mode véhicule ?"
Et si l'utilisateur accepte, basculer en mode "voiture"
Et si l'utilisateur refuse, mémoriser le choix pour 30 minutes
Scénario: Sortie de transport en commun
Étant donné l'utilisateur est en mode piéton
Et la vitesse était de 0 km/h (dans le bus)
Et la vitesse monte soudainement à 40 km/h (bus démarre)
Puis retombe à 2 km/h après 5 minutes (descente du bus)
Quand le système détecte ce pattern
Alors le mode "piéton" doit être maintenu
Et aucun basculement automatique ne doit être déclenché
Car les transports en commun sont ambigus
# ============================================================================
# BASCULEMENT AVEC AUDIO-GUIDE EN COURS
# ============================================================================
Scénario: Basculement voiture → piéton pendant audio-guide voiture
Étant donné l'utilisateur écoute un audio-guide en mode voiture
Et l'utilisateur se gare (vitesse = 0 km/h pendant 2 minutes)
Quand le système bascule en mode piéton
Alors l'audio-guide doit continuer en mode piéton
Et les séquences restantes doivent s'adapter au mode piéton :
| voiture | piéton |
| Déclenchement automatique GPS | Navigation manuelle |
| Distance affichée en mètres | Distance masquée |
| Flèche direction | Pas de flèche |
Et une notification doit informer : "Audio-guide adapté au mode piéton"
Scénario: Basculement piéton → voiture pendant audio-guide piéton
Étant donné l'utilisateur écoute un audio-guide en mode piéton
Et l'audio-guide est configuré pour "piéton uniquement"
Et l'utilisateur monte en voiture (vitesse passe à 30 km/h)
Quand le système bascule en mode voiture
Alors l'audio-guide doit être mis en pause automatiquement
Et une notification doit proposer : "Mettre l'audio-guide en pause ?"
Et si confirmé, sauvegarder la progression pour reprise ultérieure
# ============================================================================
# GESTION DES TRANSITIONS ET EDGE CASES
# ============================================================================
Scénario: Basculement rapide voiture → piéton → voiture (indécision)
Étant donné l'utilisateur est en mode voiture
Et la vitesse oscille : 40 km/h 0 km/h (1 min) 35 km/h 0 km/h (1 min)
Quand le système détecte une instabilité de mode
Alors le mode "voiture" doit être maintenu par défaut
Et un délai de stabilisation de 3 minutes doit être appliqué
Et aucune notification de basculement ne doit être envoyée
Scénario: Tunnel ou perte GPS ne déclenche PAS de basculement
Étant donné l'utilisateur est en mode voiture à 90 km/h
Et le signal GPS est perdu pendant 2 minutes (tunnel)
Quand le système détecte l'absence de signal GPS
Alors le mode "voiture" doit être maintenu
Et la dernière vitesse connue doit être utilisée
Et aucun basculement ne doit être déclenché
Scénario: Utilisateur force le mode manuellement
Étant donné l'utilisateur est en mode voiture automatique
Et l'utilisateur bascule manuellement en mode piéton via l'interface
Quand le basculement manuel est effectué
Alors le mode manuel doit être prioritaire pendant 30 minutes
Et les basculements automatiques doivent être désactivés pendant cette période
Et un flag "manual_override" doit être loggé
Scénario: Reprise après override manuel
Étant donné l'utilisateur a forcé le mode piéton il y a 35 minutes
Et la vitesse actuelle est de 60 km/h depuis 5 minutes
Quand la période d'override (30 min) expire
Alors le système doit reprendre la détection automatique
Et basculer en mode voiture car vitesse 5 km/h
Et notifier l'utilisateur du basculement
# ============================================================================
# IMPACT SUR LES AUTRES FONCTIONNALITÉS
# ============================================================================
Scénario: Basculement annule le cooldown notification voiture
Étant donné l'utilisateur est en mode voiture
Et un cooldown de 10 minutes est actif (notification ignorée)
Quand le mode bascule vers piéton
Alors le cooldown doit être immédiatement annulé
Et les notifications piéton doivent être activées
Car les mécanismes de notification sont différents entre modes
Scénario: Basculement adapte le rayon de détection géolocalisée
Étant donné l'utilisateur est en mode voiture
Et le rayon de détection pour contenus géolocalisés est adaptatif (basé sur vitesse)
Quand le mode bascule vers piéton
Alors le rayon de détection doit être fixé à 200m
Et les contenus hors rayon doivent être retirés de la file d'attente
Et une nouvelle recherche géospatiale doit être effectuée
Scénario: Quota notifications indépendant du mode
Étant donné l'utilisateur a reçu 4 notifications en mode voiture (quota 4/6)
Quand le mode bascule vers piéton
Alors le compteur de quota doit être conservé (toujours 4/6)
Car le quota de 6/heure s'applique globalement (tous modes confondus)
# ============================================================================
# MÉTRIQUES & ANALYTICS
# ============================================================================
Scénario: Logging des basculements de mode pour analytics
Étant donné l'utilisateur bascule de voiture à piéton
Quand le basculement est effectué
Alors un événement analytics doit être loggé :
| event_type | mode_switch |
| from_mode | car |
| to_mode | pedestrian |
| trigger | speed_threshold |
| speed_kmh | 1.5 |
| duration_previous_mode | 1800 |
| manual_override | false |
| timestamp | 2026-02-03 14:30:00 |
Et ces métriques doivent alimenter le dashboard de monitoring
Scénario: Détection pattern utilisateur (majoritairement piéton vs voiture)
Étant donné l'utilisateur utilise l'application depuis 30 jours
Et 80% du temps d'écoute est en mode piéton
Quand le système analyse le comportement
Alors un flag "primary_mode: pedestrian" doit être défini
Et le mode par défaut au démarrage doit être "piéton"
Et l'algorithme de recommandation doit favoriser les audio-guides piétons

View File

@@ -0,0 +1,56 @@
# language: fr
@api @navigation @transitions @mvp
Fonctionnalité: Décompte 5 secondes pour transitions séquences
En tant qu'utilisateur
Je veux un décompte avant le début d'une nouvelle séquence
Afin de me préparer mentalement à l'écoute du nouveau contenu
Scénario: Décompte visuel et audio avant nouvelle séquence
Étant donné un utilisateur "alice@roadwave.fr" qui termine une séquence
Quand la suivante est sur le point de démarrer
Alors un décompte de 5 secondes est affiché: "5... 4... 3... 2... 1..."
Et un son subtil accompagne chaque seconde
Et un événement "TRANSITION_COUNTDOWN_STARTED" est enregistré
Scénario: Possibilité de skip le décompte
Étant donné un utilisateur "bob@roadwave.fr" pendant un décompte
Quand il appuie sur "Passer"
Alors la séquence suivante démarre immédiatement
Et le décompte est interrompu
Et un événement "TRANSITION_COUNTDOWN_SKIPPED" est enregistré
Scénario: Annulation du décompte si utilisateur s'éloigne
Étant donné un utilisateur "charlie@roadwave.fr" avec décompte en cours
Quand il s'éloigne du point d'intérêt pendant le décompte
Alors le décompte est annulé
Et la séquence ne démarre pas
Et un événement "TRANSITION_COUNTDOWN_CANCELLED" est enregistré
Scénario: Prévisualisation du prochain point pendant le décompte
Étant donné un utilisateur "david@roadwave.fr" pendant un décompte
Alors il voit une carte de prévisualisation:
| Élément | Contenu |
| Nom de la séquence| Panthéon |
| Durée | 8 min 30s |
| Distance | Vous y êtes |
| Image | Photo du Panthéon |
Et un événement "TRANSITION_PREVIEW_DISPLAYED" est enregistré
Scénario: Désactivation du décompte dans les paramètres
Étant donné un utilisateur "eve@roadwave.fr"
Quand elle désactive "Décompte de transition" dans les paramètres
Alors les séquences démarrent immédiatement
Sans décompte de 5 secondes
Et un événement "TRANSITION_COUNTDOWN_DISABLED" est enregistré
Scénario: Métriques d'utilisation du décompte
Étant donné que 10 000 transitions ont eu lieu
Alors les indicateurs suivants sont disponibles:
| Métrique | Valeur |
| Taux de skip du décompte | 25% |
| Taux de complétion du décompte| 70% |
| Taux d'annulation | 5% |
| Satisfaction utilisateur | 4.2/5 |
Et les métriques sont exportées vers le monitoring

View File

@@ -0,0 +1,163 @@
# language: fr
@api @navigation @geolocation @mvp
Fonctionnalité: Calcul ETA et notification contenus géolocalisés
En tant que système de recommandation
Je veux calculer le temps d'arrivée estimé (ETA) pour les contenus géolocalisés
Afin de déclencher la notification au bon moment (7 secondes avant)
Contexte:
Étant donné un utilisateur authentifié en mode voiture
Et la géolocalisation est activée et autorisée
# ============================================================================
# CALCUL ETA VITESSE NORMALE (≥5 km/h)
# ============================================================================
Scénario: Calcul ETA avec vitesse constante 50 km/h
Étant donné un contenu géolocalisé situé à 300m de l'utilisateur
Et l'utilisateur se déplace à 50 km/h en direction du point
Quand le système calcule l'ETA
Alors l'ETA estimé doit être d'environ 21 secondes
Et la notification doit être déclenchée dans 14 secondes (21s - 7s)
Scénario: Recalcul ETA en temps réel avec variation de vitesse
Étant donné un contenu géolocalisé situé à 500m
Et l'utilisateur se déplace initialement à 70 km/h
Et l'ETA initial est de 25 secondes
Quand l'utilisateur ralentit à 30 km/h
Alors l'ETA doit être recalculé à 60 secondes
Et le délai de notification doit être ajusté en conséquence
Scénario: Notification déclenchée exactement 7 secondes avant l'arrivée
Étant donné un contenu géolocalisé situé à 150m
Et l'utilisateur se déplace à 60 km/h
Et l'ETA calculé est de 9 secondes
Quand l'ETA atteint 7 secondes
Alors une notification push doit être envoyée
Et la notification doit contenir un compteur visuel de 7 à 1
Et un son de notification doit être joué
Scénario: Pas de notification si l'utilisateur dévie de la trajectoire
Étant donné un contenu géolocalisé situé à 200m au nord
Et l'utilisateur se déplace à 50 km/h en direction du contenu
Et l'ETA est de 14 secondes
Quand l'utilisateur change de direction vers l'est
Et la distance au contenu commence à augmenter
Alors la notification ne doit pas être déclenchée
Et l'ETA doit être invalidé
# ============================================================================
# CAS VITESSE LENTE (<5 km/h)
# ============================================================================
Scénario: Notification immédiate si vitesse <5 km/h et distance <50m
Étant donné un contenu géolocalisé situé à 30m
Et l'utilisateur se déplace à 3 km/h (piéton ou embouteillage)
Quand le système détecte la vitesse <5 km/h
Alors la notification doit être déclenchée immédiatement
Et le message doit indiquer "Contenu disponible à proximité"
Scénario: Pas de notification si vitesse <5 km/h mais distance >50m
Étant donné un contenu géolocalisé situé à 80m
Et l'utilisateur se déplace à 4 km/h
Quand le système évalue les conditions de notification
Alors aucune notification ne doit être envoyée
Et le système doit attendre que la distance soit <50m ou vitesse ≥5 km/h
Scénario: Basculement vitesse normale → lente avec recalcul
Étant donné un contenu géolocalisé situé à 100m
Et l'utilisateur se déplace à 40 km/h (ETA = 9s, notification dans 2s)
Quand l'utilisateur ralentit brutalement à 2 km/h (embouteillage)
Et la vitesse reste <5 km/h pendant 5 secondes
Et la distance est maintenant 85m
Alors le mode de calcul ETA doit basculer en "vitesse lente"
Et la notification immédiate ne doit pas être envoyée (distance >50m)
Scénario: Notification immédiate en approche lente continue
Étant donné un contenu géolocalisé situé à 60m
Et l'utilisateur se déplace à 3 km/h
Quand l'utilisateur atteint 45m du point (distance <50m)
Alors la notification doit être déclenchée immédiatement
Et le compteur visuel doit afficher "À proximité"
# ============================================================================
# GESTION TRAJECTOIRES COMPLEXES
# ============================================================================
Scénario: Route sinueuse avec distance GPS ≠ distance réelle
Étant donné un contenu géolocalisé situé à 250m à vol d'oiseau
Mais la route sinueuse fait 400m de trajet réel
Et l'utilisateur se déplace à 50 km/h
Quand le système calcule l'ETA
Alors l'ETA doit utiliser la distance GPS (250m) par défaut
Et l'ETA estimé doit être d'environ 18 secondes
Et une marge d'erreur de ±3 secondes doit être tolérée
Scénario: Contenu sur autoroute parallèle non accessible
Étant donné un contenu géolocalisé sur une autoroute parallèle à 100m
Et l'utilisateur roule sur une route secondaire sans accès direct
Et la distance GPS est de 100m mais l'accès réel est à 2 km
Quand le système détecte que l'utilisateur s'éloigne après 10 secondes
Alors la notification ne doit pas être déclenchée
Et le contenu doit être retiré de la file d'attente
# ============================================================================
# EDGE CASES & ERREURS
# ============================================================================
Scénario: GPS imprécis (précision >50m)
Étant donné un contenu géolocalisé situé à 150m
Et la précision GPS actuelle est de 65m
Quand le système calcule l'ETA
Alors l'ETA doit être calculé avec une marge d'erreur élevée
Et la notification doit être différée de 2 secondes supplémentaires
Et un flag "low_accuracy" doit être ajouté aux métriques
Scénario: Vitesse nulle (stationnement) avec contenu proche
Étant donné un contenu géolocalisé situé à 25m
Et l'utilisateur est à l'arrêt (vitesse = 0 km/h) depuis 10 secondes
Quand le système évalue les conditions
Alors aucune notification ne doit être envoyée automatiquement
Et le contenu doit être affiché dans la file d'attente manuelle
Et l'utilisateur peut le sélectionner manuellement
Scénario: Perte signal GPS pendant le calcul ETA
Étant donné un contenu géolocalisé avec ETA de 12 secondes
Et une notification prévue dans 5 secondes
Quand le signal GPS est perdu (tunnel)
Alors le calcul ETA doit être gelé à la dernière valeur connue
Et la notification doit être déclenchée selon l'ETA gelé
Et un fallback de 10 secondes max doit être appliqué
Scénario: Vitesse erratique (GPS instable)
Étant donné un contenu géolocalisé situé à 200m
Et les lectures GPS montrent : 50 km/h 5 km/h 60 km/h 10 km/h en 10 secondes
Quand le système détecte une instabilité GPS
Alors l'ETA doit être calculé avec une moyenne mobile sur 5 secondes
Et la notification ne doit être déclenchée qu'avec une confiance >70%
# ============================================================================
# MÉTRIQUES & LOGS
# ============================================================================
Scénario: Logging des calculs ETA pour analytics
Étant donné un contenu géolocalisé avec notification déclenchée
Quand la notification est envoyée
Alors un événement analytics doit être loggé avec :
| distance_meters | 180 |
| speed_kmh | 55 |
| eta_seconds | 11.7 |
| notification_offset | 7 |
| gps_accuracy_meters | 12 |
| calculation_method | standard_eta |
Et la timestamp de notification doit être enregistrée
Scénario: Comparaison ETA prédit vs ETA réel
Étant donné un contenu géolocalisé avec ETA prédit de 15 secondes
Et une notification déclenchée à 8 secondes avant
Quand l'utilisateur atteint réellement le point
Alors l'ETA réel doit être calculé et comparé
Et l'écart doit être loggé pour amélioration de l'algorithme
Et si l'écart >5 secondes, un flag "prediction_error" doit être levé

View File

@@ -0,0 +1,314 @@
# language: fr
Fonctionnalité: API - File d'attente et pré-calcul des contenus
En tant qu'API backend
Je veux pré-calculer et gérer la file d'attente de contenus
Afin d'assurer une navigation fluide sans latence
Contexte:
Étant donné que l'API RoadWave est disponible
Et que Redis est accessible
Et que PostgreSQL avec PostGIS est accessible
Et qu'un utilisateur "user123" existe avec token JWT valide
# Pré-calcul initial
Scénario: API pré-calcule 5 contenus au démarrage de session
Étant donné que l'utilisateur "user123" démarre une session
Et qu'il est situé à Paris (48.8566, 2.3522)
Et qu'il est en mode voiture (vitesse 5 km/h)
Quand je POST /api/v1/queue/initialize
"""json
{
"user_id": "user123",
"latitude": 48.8566,
"longitude": 2.3522,
"mode": "voiture"
}
"""
Alors le statut de réponse est 201
Et la réponse contient:
"""json
{
"queue_size": 5,
"contents": [
{"id": "content1", "title": "...", "position": 1},
{"id": "content2", "title": "...", "position": 2},
{"id": "content3", "title": "...", "position": 3},
{"id": "content4", "title": "...", "position": 4},
{"id": "content5", "title": "...", "position": 5}
]
}
"""
Et en Redis, la clé "user:user123:queue" contient 5 contenus
Et les métadonnées incluent:
| champ | valeur |
| last_lat | 48.8566 |
| last_lon | 2.3522 |
| mode | voiture |
| computed_at | (timestamp actuel) |
Et le TTL est de 900 secondes (15 minutes)
Scénario: API GET retourne la file d'attente en cache
Étant donné qu'une file de 5 contenus existe en cache Redis pour "user123"
Quand je GET /api/v1/queue
Alors le statut de réponse est 200
Et la réponse contient les 5 contenus pré-calculés
Et la latence de réponse est < 50ms (lecture Redis)
Scénario: API retire un contenu de la file après lecture
Étant donné qu'une file de 5 contenus [C1, C2, C3, C4, C5] existe pour "user123"
Quand je POST /api/v1/queue/consume
"""json
{
"user_id": "user123",
"content_id": "C1"
}
"""
Alors le statut de réponse est 200
Et la file d'attente devient [C2, C3, C4, C5]
Et la taille de la file est 4
# Recalcul automatique
Scénario: API recalcule après déplacement >10km
Étant donné qu'une file a été calculée à Paris (48.8566, 2.3522)
Et que l'utilisateur se déplace à Versailles (48.8049, 2.1204) soit 12km
Quand je POST /api/v1/queue/update-location
"""json
{
"user_id": "user123",
"latitude": 48.8049,
"longitude": 2.1204
}
"""
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"queue_invalidated": true,
"reason": "distance_threshold_exceeded",
"distance_km": 12.1,
"new_queue_size": 5
}
"""
Et la nouvelle file est basée sur la position Versailles
Et l'ancienne file a été supprimée de Redis
Scénario: API ne recalcule pas si déplacement ≤10km
Étant donné qu'une file a été calculée à Paris (48.8566, 2.3522)
Et que l'utilisateur se déplace de 8 km
Quand je POST /api/v1/queue/update-location
"""json
{
"user_id": "user123",
"latitude": 48.8500,
"longitude": 2.3600
}
"""
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"queue_invalidated": false,
"distance_km": 8.2,
"threshold": 10
}
"""
Et la file en cache reste inchangée
Scénario: API recalcule après 10 minutes
Étant donné qu'une file a été calculée à 10:00:00
Et que l'heure actuelle est 10:10:01
Quand je GET /api/v1/queue
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"queue_invalidated": true,
"reason": "time_threshold_exceeded",
"elapsed_minutes": 10,
"new_queue_size": 5
}
"""
Et une nouvelle file de 5 contenus est recalculée
Et le timestamp "computed_at" est mis à jour
Scénario: API recalcule quand il reste <3 contenus
Étant donné qu'il reste 3 contenus [C3, C4, C5] dans la file
Quand je POST /api/v1/queue/consume (consomme C3)
Alors le statut de réponse est 200
Et la file devient [C4, C5]
Et un recalcul asynchrone est déclenché
Et 3 nouveaux contenus [C6, C7, C8] sont ajoutés
Et la file finale est [C4, C5, C6, C7, C8]
# Invalidation immédiate
Scénario: API invalide après modification préférences utilisateur
Étant donné qu'une file de 5 contenus existe pour "user123"
Et que l'utilisateur est en mode piéton (vitesse < 5 km/h)
Quand je PUT /api/v1/users/user123/preferences
"""json
{
"geo_radius": 20,
"discovery_factor": 0.7,
"political_content": false
}
"""
Alors le statut de réponse est 200
Et la file d'attente est invalidée immédiatement
Et une nouvelle file est recalculée avec les nouvelles préférences
Et en Redis, l'ancienne file a été supprimée
Scénario: API refuse modification préférences si vitesse >10 km/h
Étant donné que l'utilisateur roule à 50 km/h
Quand je PUT /api/v1/users/user123/preferences
"""json
{
"geo_radius": 30
}
"""
Alors le statut de réponse est 403
Et la réponse contient:
"""json
{
"error": "MODIFICATION_BLOCKED_WHILE_DRIVING",
"message": "Modification des préférences interdite en conduite (vitesse > 10 km/h)",
"current_speed_kmh": 50
}
"""
Et les préférences ne sont pas modifiées
Scénario: API invalide après démarrage live d'un créateur suivi
Étant donné que l'utilisateur "user123" suit le créateur "creator456"
Et qu'une file de 5 contenus existe
Et que l'utilisateur est dans la zone du créateur
Quand le créateur "creator456" démarre un live
Alors une notification push est envoyée à "user123"
Et la file d'attente est recalculée
Et le contenu live est inséré en tête de file
Et la nouvelle file commence par le live
# Métadonnées et persistence
Scénario: API stocke métadonnées complètes en Redis
Étant donné qu'une file est calculée à 10:30:00
Quand je consulte Redis avec la clé "user:user123:queue"
Alors la structure est:
"""json
{
"contents": [
{"id": "C1", "position": 1},
{"id": "C2", "position": 2},
{"id": "C3", "position": 3},
{"id": "C4", "position": 4},
{"id": "C5", "position": 5}
],
"metadata": {
"last_lat": 48.8566,
"last_lon": 2.3522,
"computed_at": "2026-02-02T10:30:00Z",
"mode": "voiture"
}
}
"""
Et le TTL est exactement 900 secondes
Scénario: API calcule distance avec PostGIS
Étant donné que l'ancienne position est Paris (48.8566, 2.3522)
Et que la nouvelle position est Versailles (48.8049, 2.1204)
Quand l'API calcule la distance
Alors la requête SQL utilise:
"""sql
SELECT ST_Distance(
ST_MakePoint(2.3522, 48.8566)::geography,
ST_MakePoint(2.1204, 48.8049)::geography
) / 1000 AS distance_km
"""
Et le résultat est 12.1 km
# Gestion erreurs
Scénario: API gère échec Redis gracieusement
Étant donné que Redis est indisponible
Quand je GET /api/v1/queue
Alors le statut de réponse est 503
Et la réponse contient:
"""json
{
"error": "CACHE_UNAVAILABLE",
"message": "Service de cache temporairement indisponible",
"fallback": "Calcul direct sans cache"
}
"""
Et une nouvelle file est calculée directement depuis PostgreSQL
Scénario: API gère aucun contenu disponible
Étant donné qu'aucun contenu n'existe dans la zone de l'utilisateur
Quand je POST /api/v1/queue/initialize
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"queue_size": 0,
"contents": [],
"message": "Aucun contenu disponible dans cette zone",
"suggested_action": "expand_radius"
}
"""
Scénario: API élargit la zone de recherche si aucun contenu
Étant donné qu'aucun contenu n'existe dans un rayon de 20km
Quand je POST /api/v1/queue/expand-radius
"""json
{
"user_id": "user123",
"additional_radius_km": 50
}
"""
Alors le statut de réponse est 200
Et la recherche utilise un rayon de 70km (20 + 50)
Et une nouvelle file est calculée avec ce rayon
# Performance
Scénario: API répond en <100ms pour lecture cache
Étant donné qu'une file existe en Redis
Quand je GET /api/v1/queue à 10:30:00.000
Alors la réponse est reçue à 10:30:00.050 (50ms)
Et la latence est < 100ms
Scénario: API recalcule en arrière-plan sans bloquer
Étant donné qu'il reste 2 contenus dans la file
Quand je POST /api/v1/queue/consume
Alors le statut de réponse est 200 (immédiat)
Et la réponse est retournée en < 100ms
Et le recalcul asynchrone démarre en parallèle
Et le client ne perçoit aucune latence
Plan du Scénario: Conditions de recalcul selon distance
Étant donné qu'une file a été calculée à une position donnée
Quand l'utilisateur se déplace de <distance> km
Alors la file est <action>
Exemples:
| distance | action |
| 5 | conservée |
| 9.9 | conservée |
| 10.0 | conservée |
| 10.1 | invalidée et recalculée |
| 15 | invalidée et recalculée |
| 50 | invalidée et recalculée |
Plan du Scénario: Conditions de recalcul selon temps écoulé
Étant donné qu'une file a été calculée il y a <temps> minutes
Quand je consulte la file
Alors la file est <action>
Exemples:
| temps | action |
| 5 | conservée |
| 9 | conservée |
| 10 | invalidée et recalculée |
| 15 | invalidée et recalculée |
| 60 | invalidée et recalculée |

View File

@@ -0,0 +1,58 @@
# language: fr
@api @navigation @history @mvp
Fonctionnalité: Historique géolocalisé des contenus écoutés
En tant qu'utilisateur
Je veux consulter l'historique de mes écoutes avec localisation
Afin de me souvenir de mes découvertes et parcours
Scénario: Enregistrement automatique de l'historique
Étant donné un utilisateur "alice@roadwave.fr" qui écoute un contenu
Quand l'écoute est terminée
Alors l'historique enregistre:
| Donnée | Exemple |
| Contenu | Audio-guide Quartier Latin |
| Date/heure | 2026-02-03 14:30 |
| Position GPS | 48.8534, 2.3488 |
| Durée d'écoute | 42 min |
Et un événement "HISTORY_ENTRY_CREATED" est enregistré
Scénario: Visualisation de l'historique sur une carte
Étant donné un utilisateur "bob@roadwave.fr"
Quand il accède à "Mon historique"
Alors une carte affiche tous les points écoutés
Et chaque marqueur est cliquable pour voir les détails
Et un événement "HISTORY_MAP_VIEWED" est enregistré
Scénario: Filtrage de l'historique par période
Étant donné un utilisateur "charlie@roadwave.fr"
Quand il filtre par "Ce mois-ci"
Alors seuls les contenus du mois courant sont affichés
Et un compteur indique: "23 contenus écoutés ce mois"
Et un événement "HISTORY_FILTERED" est enregistré
Scénario: Export de l'historique pour souvenirs
Étant donné un utilisateur "david@roadwave.fr"
Quand il exporte son historique
Alors il reçoit un fichier GPX avec tous ses parcours
Et peut l'importer dans d'autres applications
Et un événement "HISTORY_EXPORTED" est enregistré
Scénario: Suppression d'entrées d'historique
Étant donné un utilisateur "eve@roadwave.fr"
Quand elle supprime une entrée
Alors elle est retirée de l'historique
Et ne compte plus dans les statistiques
Et un événement "HISTORY_ENTRY_DELETED" est enregistré
Scénario: Statistiques annuelles basées sur l'historique
Étant donné un utilisateur "frank@roadwave.fr" en fin d'année
Alors il voit son "Rétrospective RoadWave 2026":
| Métrique | Valeur |
| Contenus écoutés | 142 |
| Distance parcourue | 523 km |
| Villes visitées | 18 |
| Pays visités | 3 |
| Top catégorie | Tourisme |
Et un événement "YEARLY_RETROSPECTIVE_VIEWED" est enregistré

View File

@@ -0,0 +1,52 @@
# language: fr
@api @navigation @parking @mvp
Fonctionnalité: Mode stationnement et pause automatique
En tant qu'utilisateur conducteur
Je veux que l'application détecte quand je me gare
Afin d'adapter l'expérience et proposer la suite à pied
Scénario: Détection automatique du stationnement
Étant donné un utilisateur "alice@roadwave.fr" en mode voiture
Quand la vitesse passe à 0 km/h pendant plus de 2 minutes
Et le Bluetooth CarPlay se déconnecte
Alors le mode "Stationnement" est activé
Et un événement "PARKING_MODE_DETECTED" est enregistré
Scénario: Proposition de basculement en mode piéton
Étant donné un utilisateur "bob@roadwave.fr" en mode stationnement
Quand il sort de la voiture (détecté par capteurs)
Alors une notification propose: "Continuer à pied ?"
Et deux options: [Mode piéton] [Rester en voiture]
Et un événement "PEDESTRIAN_MODE_SUGGESTED" est enregistré
Scénario: Mémorisation de la position de stationnement
Étant donné un utilisateur "charlie@roadwave.fr" qui se gare
Quand le mode stationnement est activé
Alors la position GPS est sauvegardée
Et un marqueur "Votre voiture" est placé sur la carte
Et un événement "PARKING_LOCATION_SAVED" est enregistré
Scénario: Navigation retour vers la voiture
Étant donné un utilisateur "david@roadwave.fr" qui a fini sa visite à pied
Quand il clique sur "Retour à ma voiture"
Alors un itinéraire piéton est calculé
Et la distance est affichée: "650m - 8 min"
Et un événement "NAVIGATION_TO_PARKING_STARTED" est enregistré
Scénario: Alerte d'expiration du stationnement payant
Étant donné un utilisateur "eve@roadwave.fr" en stationnement
Et elle a configuré une durée de stationnement: 2 heures
Quand il reste 10 minutes
Alors une notification push est envoyée: "Stationnement expire dans 10 min"
Et un événement "PARKING_EXPIRY_WARNING" est enregistré
Scénario: Statistiques de stationnement
Étant donné un utilisateur "frank@roadwave.fr"
Alors il peut voir dans ses stats:
| Métrique | Valeur |
| Nombre de stationnements | 45 |
| Durée moyenne | 1h 30min |
| Distance voiture-POI moyen | 320m |
Et un événement "PARKING_STATS_VIEWED" est enregistré

View File

@@ -0,0 +1,76 @@
# language: fr
@api @navigation @car-mode @ui @mvp
Fonctionnalité: Notifications minimalistes en mode voiture
En tant qu'utilisateur conducteur
Je veux des notifications visuelles minimales et audio prioritaires
Afin de rester concentré sur la route en toute sécurité
Scénario: Affichage minimal des notifications visuelles
Étant donné un utilisateur "alice@roadwave.fr" en mode voiture
Quand une notification est déclenchée
Alors elle s'affiche pendant 3 secondes maximum
Et occupe < 20% de l'écran
Et disparaît automatiquement
Et un événement "CAR_NOTIFICATION_MINIMAL_DISPLAYED" est enregistré
Scénario: Notifications audio prioritaires sur visuel
Étant donné un utilisateur "bob@roadwave.fr" en mode voiture
Quand un point d'intérêt approche
Alors une notification audio est jouée
Et la notification visuelle est optionnelle
Et l'audio contient toutes les informations nécessaires
Et un événement "CAR_AUDIO_NOTIFICATION_PRIORITY" est enregistré
Scénario: Pas de popups ou modales en mode voiture
Étant donné un utilisateur "charlie@roadwave.fr" en mode voiture
Quand une action nécessite une confirmation
Alors aucune modale bloquante n'apparaît
Et la confirmation est reportée ou faite par commande vocale
Et un événement "CAR_MODAL_PREVENTED" est enregistré
Scénario: Gros boutons tactiles espacés pour conduite
Étant donné un utilisateur "david@roadwave.fr" en mode voiture
Alors tous les boutons ont une taille minimale de 60x60 pixels
Et un espacement minimal de 20 pixels entre boutons
Et sont facilement accessibles d'une main
Et un événement "CAR_UI_TOUCH_OPTIMIZED" est enregistré
Scénario: Mode nuit automatique pour réduire éblouissement
Étant donné un utilisateur "eve@roadwave.fr" en mode voiture la nuit
Quand le capteur de luminosité détecte l'obscurité
Alors l'interface passe en mode nuit (fond noir)
Et la luminosité est réduite de 50%
Et un événement "CAR_NIGHT_MODE_AUTO" est enregistré
Scénario: Notifications groupées pour éviter la surcharge
Étant donné un utilisateur "frank@roadwave.fr" en mode voiture
Quand plusieurs événements se produisent en 30 secondes
Alors une seule notification résume les événements
Et les détails sont accessibles après l'arrêt
Et un événement "CAR_NOTIFICATIONS_GROUPED" est enregistré
Scénario: Intégration CarPlay avec notifications système
Étant donné un utilisateur "grace@roadwave.fr" avec CarPlay
Quand une notification RoadWave arrive
Alors elle utilise le système de notification CarPlay natif
Et respecte les guidelines Apple pour sécurité
Et un événement "CARPLAY_NOTIFICATION_NATIVE" est enregistré
Scénario: Commandes vocales pour interactions sans les mains
Étant donné un utilisateur "henry@roadwave.fr" en mode voiture
Quand il dit "Dis Siri, mets en pause"
Alors l'audio-guide se met en pause sans interaction tactile
Et aucune confirmation visuelle n'est requise
Et un événement "CAR_VOICE_COMMAND_EXECUTED" est enregistré
Scénario: Métriques de sécurité des notifications
Étant donné que 50 000 notifications ont été affichées en mode voiture
Alors les indicateurs suivants sont respectés:
| Métrique | Valeur cible |
| Temps d'affichage moyen | < 3s |
| Taux d'utilisation audio vs visuel| 80% audio |
| Taille moyenne des notifications | < 20% écran |
| Taux d'accidents rapportés | 0 |
Et les métriques sont exportées vers le monitoring

View File

@@ -0,0 +1,489 @@
# language: fr
Fonctionnalité: API - Notifications géolocalisées et quota anti-spam
En tant qu'API backend
Je veux gérer les notifications de contenus géolocalisés
Afin de respecter les quotas et détecter le bon timing
Contexte:
Étant donné que l'API RoadWave est disponible
Et que Redis est accessible
Et qu'un utilisateur "user123" existe en mode voiture
# Calcul ETA et déclenchement notification
Scénario: API calcule ETA vers point géolocalisé
Étant donné qu'un contenu géolocalisé existe à la position (48.8584, 2.2945) Tour Eiffel
Et que l'utilisateur roule à 50 km/h vers ce point
Et qu'il est actuellement à 98 mètres
Quand je POST /api/v1/geo-notifications/check-eta
"""json
{
"user_id": "user123",
"current_lat": 48.8577,
"current_lon": 2.2950,
"speed_kmh": 50,
"target_content_id": "content_tower"
}
"""
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"eta_seconds": 7,
"distance_meters": 98,
"should_notify": true,
"notification_trigger": "eta_threshold"
}
"""
Scénario: API déclenche notification 7 secondes avant arrivée
Étant donné qu'un contenu géolocalisé existe au point GPS
Et que l'ETA calculé est 7 secondes
Quand je POST /api/v1/geo-notifications/trigger
"""json
{
"user_id": "user123",
"content_id": "content_tower",
"eta_seconds": 7
}
"""
Alors le statut de réponse est 201
Et une notification est envoyée au mobile
Et la notification contient:
| champ | valeur |
| type | geo_anchored |
| countdown | 7 |
| icon | 🏛 (selon type contenu) |
| sound | bip court |
Scénario: API ne déclenche pas si ETA >7 secondes
Étant donné qu'un contenu géolocalisé existe
Et que l'ETA calculé est 10 secondes
Quand je POST /api/v1/geo-notifications/check-eta
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"eta_seconds": 10,
"should_notify": false,
"reason": "eta_above_threshold"
}
"""
Et aucune notification n'est envoyée
Scénario: API déclenche notification immédiate si vitesse faible et proche
Étant donné qu'un contenu géolocalisé existe
Et que l'utilisateur roule à 3 km/h
Et qu'il est à 40 mètres du point
Quand je POST /api/v1/geo-notifications/check-eta
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"eta_seconds": 0,
"distance_meters": 40,
"speed_kmh": 3,
"should_notify": true,
"notification_trigger": "proximity_and_low_speed"
}
"""
Et une notification est envoyée immédiatement
# Quota anti-spam
Scénario: API vérifie quota avant notification (6 contenus max/heure)
Étant donné que l'utilisateur a déjà reçu 5 notifications géo dans la dernière heure
Quand je POST /api/v1/geo-notifications/check-quota
"""json
{
"user_id": "user123"
}
"""
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"quota_available": true,
"notifications_count": 5,
"quota_limit": 6,
"remaining": 1
}
"""
Scénario: API refuse notification si quota atteint (6/6)
Étant donné que l'utilisateur a déjà reçu 6 notifications géo dans la dernière heure
Quand je POST /api/v1/geo-notifications/trigger
"""json
{
"user_id": "user123",
"content_id": "content_x"
}
"""
Alors le statut de réponse est 429
Et la réponse contient:
"""json
{
"error": "QUOTA_EXCEEDED",
"message": "Quota horaire de notifications géolocalisées atteint (6/6)",
"notifications_count": 6,
"quota_limit": 6,
"reset_in_seconds": 1800
}
"""
Et aucune notification n'est envoyée
Scénario: API incrémente quota après notification envoyée
Étant donné que l'utilisateur a reçu 3 notifications géo
Quand je POST /api/v1/geo-notifications/trigger (succès)
Alors le quota passe à 4/6
Et en Redis, la clé "user:user123:geo_quota" contient 4 entrées
Et le sorted set contient les timestamps des 4 notifications
Scénario: API utilise rolling window pour quota horaire
Étant donné que l'utilisateur a reçu 6 notifications:
| timestamp |
| 10:00:00 |
| 10:15:00 |
| 10:30:00 |
| 10:45:00 |
| 11:00:00 |
| 11:05:00 |
Et que l'heure actuelle est 11:10:00
Quand je vérifie le quota
Alors les notifications avant 10:10:00 sont expirées
Et seules 4 notifications sont comptées (10:15, 10:30, 10:45, 11:00, 11:05 = 5)
Et le quota disponible est 1/6
Scénario: API exclut séquences d'audio-guide multi-séquences du quota
Étant donné qu'un audio-guide "Visite Louvre" a 12 séquences
Et que l'utilisateur a écouté les 12 séquences
Quand je calcule le quota
Alors l'audio-guide compte pour 1 seule notification
Et les 12 séquences ne consomment pas 12 quota
Et le quota utilisé est 1/6
# Cooldown après ignorance
Scénario: API active cooldown 10 min si notification ignorée
Étant donné qu'une notification géo a été envoyée à 10:00:00
Et que l'utilisateur n'a pas appuyé sur "Suivant" dans les 7 secondes
Quand je POST /api/v1/geo-notifications/ignored
"""json
{
"user_id": "user123",
"notification_id": "notif123"
}
"""
Alors le statut de réponse est 200
Et un cooldown de 10 minutes est activé
Et en Redis, la clé "user:user123:geo_cooldown" est créée
Et le TTL est 600 secondes (10 minutes)
Scénario: API refuse notification si cooldown actif
Étant donné qu'un cooldown est actif depuis 5 minutes
Et qu'il reste 5 minutes de cooldown
Quand je POST /api/v1/geo-notifications/trigger
Alors le statut de réponse est 429
Et la réponse contient:
"""json
{
"error": "COOLDOWN_ACTIVE",
"message": "Cooldown actif après notification ignorée",
"cooldown_remaining_seconds": 300
}
"""
Et aucune notification n'est envoyée
Scénario: API autorise notification après expiration cooldown
Étant donné qu'un cooldown a été activé à 10:00:00
Et que l'heure actuelle est 10:11:00 (11 minutes après)
Quand je POST /api/v1/geo-notifications/check-quota
Alors le cooldown a expiré
Et les notifications sont à nouveau autorisées
# Tracking GPS temps réel
Scénario: API reçoit positions GPS toutes les secondes
Étant donné que le mobile envoie des positions GPS
Quand je POST /api/v1/gps/track
"""json
{
"user_id": "user123",
"latitude": 48.8577,
"longitude": 2.2950,
"speed_kmh": 50,
"timestamp": "2026-02-02T10:30:00Z"
}
"""
Alors le statut de réponse est 200
Et la position est stockée dans l'historique GPS (Redis)
Et l'historique conserve les 30 derniers points
Scénario: API calcule vitesse moyenne sur 30 derniers points
Étant donné que l'historique GPS contient 30 points
Et que les vitesses enregistrées sont variables
Quand je GET /api/v1/gps/average-speed
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"average_speed_kmh": 48.5,
"samples_count": 30,
"period_seconds": 30
}
"""
Scénario: API détecte contenus géolocalisés dans rayon 500m
Étant donné que l'utilisateur est à la position (48.8577, 2.2950)
Et qu'un contenu géolocalisé existe à 300m
Quand je GET /api/v1/geo-notifications/nearby
"""json
{
"user_id": "user123",
"latitude": 48.8577,
"longitude": 2.2950,
"radius_meters": 500
}
"""
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"nearby_contents": [
{
"id": "content_tower",
"distance_meters": 300,
"eta_seconds": 21,
"should_notify_soon": false
}
]
}
"""
# Validation contenu géolocalisé
Scénario: API valide que contenu géolocalisé accepté
Étant donné qu'une notification géo a été envoyée
Et que l'utilisateur a appuyé sur "Suivant" dans les 7 secondes
Quand je POST /api/v1/geo-notifications/accepted
"""json
{
"user_id": "user123",
"notification_id": "notif123",
"content_id": "content_tower"
}
"""
Alors le statut de réponse est 200
Et le contenu géolocalisé est inséré en priorité
Et un décompte de 5 secondes est déclenché côté client
Et le quota n'est PAS incrémenté (acceptation = pas de spam)
Scénario: API enregistre contenu géolocalisé perdu
Étant donné qu'une notification géo a été envoyée
Et que l'utilisateur n'a pas appuyé sur "Suivant"
Quand je POST /api/v1/geo-notifications/ignored
Alors le contenu géolocalisé est perdu
Et il n'est PAS inséré dans la file d'attente
Et une métrique "geo_notification_ignored" est enregistrée
Et le cooldown 10 min est activé
# Métriques
Scénario: API stocke métriques de notifications géolocalisées
Étant donné que l'API envoie des notifications géo
Quand je consulte les métriques
Alors les données suivantes sont disponibles:
| métrique | description |
| geo_notifications_sent | Nombre total envoyées |
| geo_notifications_accepted | Nombre acceptées (Suivant) |
| geo_notifications_ignored | Nombre ignorées |
| geo_acceptance_rate | % acceptation |
| average_eta_seconds | ETA moyen au déclenchement |
| quota_exceeded_count | Nombre de refus quota |
# Redis structures
Scénario: API utilise sorted set Redis pour quota
Étant donné que l'utilisateur a reçu 3 notifications
Quand je consulte Redis "user:user123:geo_quota"
Alors la structure est un sorted set:
"""
ZADD user:user123:geo_quota
1707047400 "notif1"
1707047700 "notif2"
1707048000 "notif3"
"""
Et les scores sont les timestamps Unix
Et le TTL est 3600 secondes (1 heure)
Scénario: API compte notifications avec ZCOUNT
Étant donné que le sorted set contient plusieurs notifications
Et que l'heure actuelle est 11:00:00 (timestamp 1707048000)
Quand l'API vérifie le quota
Alors la commande Redis est:
"""
ZCOUNT user:user123:geo_quota 1707044400 1707048000
"""
Et seules les notifications des 60 dernières minutes sont comptées
# Gestion erreurs
Scénario: API gère échec calcul ETA gracieusement
Étant donné qu'une position GPS est invalide
Quand je POST /api/v1/geo-notifications/check-eta
"""json
{
"user_id": "user123",
"current_lat": 200,
"current_lon": 300
}
"""
Alors le statut de réponse est 400
Et la réponse contient:
"""json
{
"error": "INVALID_GPS_COORDINATES",
"message": "Coordonnées GPS invalides"
}
"""
Scénario: API gère vitesse nulle sans crash
Étant donné que l'utilisateur est à l'arrêt (vitesse = 0)
Et qu'un contenu géolocalisé est à 100m
Quand je POST /api/v1/geo-notifications/check-eta
Alors l'ETA est incalculable (vitesse nulle)
Et aucune notification n'est envoyée
Et la réponse contient:
"""json
{
"should_notify": false,
"reason": "speed_zero"
}
"""
Plan du Scénario: Déclenchement selon ETA
Étant donné qu'un contenu géolocalisé existe
Et que l'ETA calculé est <eta> secondes
Quand je vérifie si notification doit être envoyée
Alors la décision est <decision>
Exemples:
| eta | decision |
| 3 | notifier |
| 5 | notifier |
| 7 | notifier |
| 8 | ne pas notifier |
| 10 | ne pas notifier |
| 15 | ne pas notifier |
Plan du Scénario: Quota selon nombre notifications
Étant donné que l'utilisateur a reçu <count> notifications dans l'heure
Quand je vérifie le quota
Alors le quota est <status>
Exemples:
| count | status |
| 0 | disponible |
| 3 | disponible |
| 5 | disponible |
| 6 | atteint |
| 7 | atteint |
# Edge cases : haute vitesse
Scénario: API calcule ETA correctement à 130 km/h
Étant donné qu'un contenu géolocalisé existe
Et que l'utilisateur roule à 130 km/h (36.1 m/s)
Et qu'il est à 252 mètres du point
Quand je POST /api/v1/geo-notifications/check-eta
"""json
{
"user_id": "user123",
"speed_kmh": 130,
"distance_meters": 252
}
"""
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"eta_seconds": 7,
"speed_ms": 36.1,
"should_notify": true,
"notification_trigger": "eta_threshold"
}
"""
Et une notification est envoyée 252m avant le point
Scénario: API calcule distance notification selon vitesse
Étant donné qu'un contenu géolocalisé existe
Quand l'utilisateur roule à 180 km/h (50 m/s)
Alors la notification est déclenchée à 350 mètres avant le point
Et après décompte 5s, user a parcouru 250m
Et le contenu démarre 100m avant le point
Et le système fonctionne même à vitesse extrême
# Edge cases : multiples points proches
Scénario: API gère multiples points géolocalisés proches (800m chacun)
Étant donné que 3 châteaux existent espacés de 800m chacun
| contenu | position_km |
| Château A | 0 |
| Château B | 0.8 |
| Château C | 1.6 |
Et que l'utilisateur roule à 50 km/h
Quand une notification Château A est envoyée et acceptée
Alors le quota passe à 1/6
Et aucun cooldown n'est activé (notification acceptée)
Quand 57 secondes s'écoulent (temps pour parcourir 800m)
Et que l'utilisateur atteint Château B
Alors une notification Château B est envoyée
Car le quota n'est pas atteint (1/6)
Et il n'y a pas de cooldown actif
Scénario: API active cooldown réduit après validations multiples
Étant donné que l'utilisateur a validé 2 notifications consécutives
Et que les notifications ont toutes été acceptées (clic "Suivant")
Quand une 3ème notification est ignorée
Alors le cooldown activé est 5 minutes (réduit)
Et non 10 minutes (standard)
Car l'utilisateur a montré de l'engagement précédemment
Scénario: API ignore notifications si cooldown actif après ignorance
Étant donné qu'une notification a été ignorée à 10:00:00
Et qu'un cooldown de 10 minutes est actif
Et que 3 contenus géolocalisés existent à 10:05, 10:08, 10:11
Quand l'utilisateur passe devant le contenu à 10:05
Alors aucune notification n'est envoyée (cooldown actif, reste 5 min)
Quand l'utilisateur passe devant le contenu à 10:08
Alors aucune notification n'est envoyée (cooldown actif, reste 2 min)
Quand l'utilisateur passe devant le contenu à 10:11
Alors une notification est envoyée (cooldown expiré après 10 min)
# Edge cases : mode stationnement
Scénario: API détecte mode stationnement (vitesse < 1 km/h pendant 2 min)
Étant donné que l'utilisateur roule à 50 km/h
Quand la vitesse passe à 0.5 km/h (arrêt complet)
Et que la vitesse reste < 1 km/h pendant 2 minutes consécutives
Alors le mode "stationnement" est activé
Et aucune notification géolocalisée n'est envoyée
Et le système bascule automatiquement en mode piéton
Et un événement "mode_stationnement_detected" est enregistré
Scénario: API sort du mode stationnement quand vitesse > 5 km/h
Étant donné que le mode stationnement est actif depuis 30 minutes
Et que l'utilisateur était à l'arrêt près d'un château
Quand la vitesse passe à 20 km/h pendant 10 secondes
Alors le mode voiture est réactivé
Et les notifications géolocalisées reprennent
Et le quota horaire est vérifié avant nouvelle notification
Scénario: API refuse notification si user arrêté longtemps près d'un point
Étant donné qu'un contenu géolocalisé existe à 30 mètres
Et que l'utilisateur est à l'arrêt (vitesse 0 km/h) depuis 3 minutes
Quand le système détecte la proximité
Alors le mode stationnement est actif
Et aucune notification n'est envoyée
Car l'utilisateur est probablement stationné (parking)
Et pas en mode conduite

View File

@@ -0,0 +1,226 @@
# language: fr
@api @navigation @anti-spam @mvp
Fonctionnalité: Quota et cooldown pour contenus géolocalisés
En tant que système de navigation
Je veux limiter les notifications géolocalisées à 6 par heure
Et appliquer un cooldown de 10 minutes après refus
Afin d'éviter le spam et préserver l'expérience utilisateur
Contexte:
Étant donné un utilisateur authentifié en mode voiture
Et la géolocalisation est activée
# ============================================================================
# QUOTA 6 NOTIFICATIONS / HEURE (fenêtre glissante)
# ============================================================================
Scénario: Première notification de l'heure sans quota atteint
Étant donné l'utilisateur n'a reçu aucune notification géolocalisée dans les 60 dernières minutes
Et un contenu géolocalisé déclenche une notification (ETA = 7s)
Quand la notification est envoyée
Alors le compteur de quota doit être incrémenté à 1/6
Et un timestamp doit être enregistré pour cette notification
Et la notification doit être affichée normalement
Scénario: Notifications multiples sous le quota
Étant donné l'utilisateur a reçu 3 notifications dans les 60 dernières minutes :
| timestamp | contenu_id |
| 2026-02-03 14:10:00 | content-1 |
| 2026-02-03 14:25:00 | content-2 |
| 2026-02-03 14:40:00 | content-3 |
Et il est maintenant 14:50:00
Et un nouveau contenu géolocalisé déclenche une notification
Quand la notification est évaluée
Alors le compteur doit afficher 4/6
Et la notification doit être envoyée normalement
Scénario: 6ème notification - dernière autorisée
Étant donné l'utilisateur a reçu 5 notifications dans l'heure
Et un nouveau contenu géolocalisé déclenche une notification
Quand la notification est envoyée
Alors le compteur doit afficher 6/6
Et la notification doit être affichée normalement
Et un flag "quota_limit_reached" doit être ajouté aux métriques
Scénario: 7ème notification - quota dépassé
Étant donné l'utilisateur a reçu 6 notifications dans les 60 dernières minutes
Et un nouveau contenu géolocalisé déclenche une notification
Quand le système évalue le quota
Alors la notification ne doit PAS être envoyée
Et le contenu doit être ajouté silencieusement à la file d'attente
Et un événement "notification_blocked_quota" doit être loggé
Et l'utilisateur ne doit voir aucun indicateur visuel de blocage
Scénario: Fenêtre glissante - libération progressive du quota
Étant donné l'utilisateur a reçu 6 notifications :
| timestamp | contenu_id |
| 2026-02-03 13:00:00 | content-1 |
| 2026-02-03 13:15:00 | content-2 |
| 2026-02-03 13:30:00 | content-3 |
| 2026-02-03 13:45:00 | content-4 |
| 2026-02-03 14:00:00 | content-5 |
| 2026-02-03 14:10:00 | content-6 |
Et il est maintenant 14:05:00 (la 1ère notification a >60 min)
Et un nouveau contenu déclenche une notification
Quand le système calcule le quota sur les 60 dernières minutes
Alors seules 5 notifications doivent être comptées (13:15 à 14:10)
Et le compteur doit afficher 6/6 (5 + nouvelle = 6)
Et la notification doit être envoyée
Scénario: Reset complet après 60 minutes sans notification
Étant donné l'utilisateur a reçu 6 notifications entre 12:00 et 12:50
Et il est maintenant 13:55 (>60 min après la dernière)
Quand un nouveau contenu déclenche une notification
Alors le compteur doit être reset à 1/6
Et la notification doit être envoyée normalement
# ============================================================================
# EXCEPTION AUDIO-GUIDES (1 quota = toutes les séquences)
# ============================================================================
Scénario: Audio-guide multi-séquences compte pour 1 seul quota
Étant donné l'utilisateur a reçu 5 notifications normales dans l'heure
Et l'utilisateur démarre un audio-guide avec 8 séquences géolocalisées
Quand la 1ère séquence de l'audio-guide déclenche une notification
Alors le quota doit être incrémenté à 6/6
Quand les 7 séquences suivantes déclenchent des notifications
Alors le quota doit rester à 6/6
Et toutes les notifications d'audio-guide doivent être envoyées
Et un flag "audio_guide_exception" doit être loggé
Scénario: Audio-guide suivi de contenus normaux bloqués
Étant donné l'utilisateur a reçu 5 notifications normales
Et l'utilisateur a terminé un audio-guide de 6 séquences (compte pour 1 quota)
Et le quota est maintenant 6/6
Quand un contenu normal déclenche une notification
Alors la notification doit être bloquée (quota dépassé)
Et le contenu doit être ajouté à la file d'attente
Scénario: Plusieurs audio-guides dans l'heure
Étant donné l'utilisateur a démarré 3 audio-guides dans l'heure :
| timestamp | audio_guide_id | sequences |
| 2026-02-03 13:00:00 | guide-1 | 5 |
| 2026-02-03 13:30:00 | guide-2 | 8 |
| 2026-02-03 14:00:00 | guide-3 | 4 |
Quand le système calcule le quota
Alors chaque audio-guide doit compter pour 1 quota
Et le compteur doit afficher 3/6
Et 3 contenus normaux supplémentaires peuvent être notifiés
# ============================================================================
# COOLDOWN 10 MINUTES APRÈS REFUS
# ============================================================================
Scénario: Notification ignorée déclenche cooldown de 10 minutes
Étant donné une notification géolocalisée est affichée à 14:00:00
Et le compteur de 7 secondes se termine sans action utilisateur
Quand le délai de 7 secondes expire
Alors un cooldown de 10 minutes doit être activé
Et un timestamp "cooldown_until: 14:10:00" doit être enregistré
Et la notification doit disparaître
Scénario: Notifications bloquées pendant le cooldown
Étant donné un cooldown est actif jusqu'à 14:10:00
Et il est maintenant 14:05:00
Et 3 contenus géolocalisés déclenchent des notifications
Quand le système évalue les conditions
Alors aucune notification ne doit être affichée
Et les 3 contenus doivent être ajoutés silencieusement à la file d'attente
Et un événement "notification_blocked_cooldown" doit être loggé pour chacun
Scénario: Fin du cooldown - reprise normale
Étant donné un cooldown actif jusqu'à 14:10:00
Et il est maintenant 14:10:05 (cooldown expiré)
Et un contenu géolocalisé déclenche une notification
Quand le système évalue les conditions
Alors le cooldown doit être considéré comme expiré
Et la notification doit être envoyée normalement
Et le flag "post_cooldown" doit être ajouté aux métriques
Scénario: Validation notification pendant le countdown annule le cooldown
Étant donné une notification affichée avec compteur à 4 secondes restantes
Quand l'utilisateur appuie sur "Écouter maintenant"
Alors le contenu doit démarrer
Et aucun cooldown ne doit être appliqué
Et la prochaine notification peut être envoyée normalement (sous réserve du quota)
Scénario: Cooldown + Quota simultanés
Étant donné l'utilisateur a reçu 6 notifications dans l'heure (quota atteint)
Et la dernière notification a été ignorée (cooldown actif pour 10 min)
Et il est maintenant 5 minutes plus tard
Quand un nouveau contenu déclenche une notification
Alors la notification doit être bloquée pour les 2 raisons :
| raison | statut |
| quota_exceeded | true |
| cooldown_active | true |
Et le contenu doit être ajouté à la file d'attente
Et les deux raisons doivent être loggées
# ============================================================================
# CAS LIMITES & EDGE CASES
# ============================================================================
Scénario: Cooldown ne s'applique pas en mode piéton
Étant donné l'utilisateur est en mode piéton
Et une notification push géolocalisée est ignorée
Quand la notification expire
Alors aucun cooldown ne doit être appliqué
Car en mode piéton les notifications sont opt-in et moins intrusives
Scénario: Changement de mode pendant cooldown (voiture → piéton)
Étant donné un cooldown actif en mode voiture jusqu'à 14:20:00
Et il est maintenant 14:12:00 (cooldown encore actif)
Quand l'utilisateur bascule en mode piéton
Alors le cooldown doit être immédiatement annulé
Et les notifications piéton doivent être activées normalement
Scénario: Déconnexion/reconnexion pendant cooldown
Étant donné un cooldown actif jusqu'à 15:30:00
Quand l'utilisateur se déconnecte puis se reconnecte à 15:20:00
Alors le cooldown doit être persisté (Redis ou DB)
Et le cooldown doit toujours être actif jusqu'à 15:30:00
Et les notifications doivent rester bloquées
Scénario: Quota reset à minuit vs fenêtre glissante
Étant donné le système utilise une fenêtre glissante de 60 minutes
Et l'utilisateur a reçu 6 notifications entre 23:30 et 23:55
Quand minuit passe et il est 00:05
Alors le quota doit toujours être calculé sur les 60 dernières minutes
Et les 6 notifications doivent encore être comptées
Et aucune notification supplémentaire ne peut être envoyée avant 00:30
# ============================================================================
# MÉTRIQUES & ANALYTICS
# ============================================================================
Scénario: Logging des blocages quota pour optimisation
Étant donné le quota est atteint (6/6)
Et 5 contenus tentent de déclencher une notification dans l'heure suivante
Quand ces notifications sont bloquées
Alors un événement analytics doit être loggé pour chacune :
| event_type | notification_blocked_quota |
| content_id | content-xyz |
| user_quota_current | 6 |
| user_quota_max | 6 |
| timestamp_blocked | 2026-02-03 14:30:00 |
| added_to_queue | true |
Et ces métriques doivent alimenter le dashboard de monitoring
Scénario: Métriques de cooldown par utilisateur
Étant donné un utilisateur ignore 3 notifications dans la journée
Quand le système analyse le comportement
Alors les métriques suivantes doivent être calculées :
| cooldowns_triggered_today | 3 |
| avg_cooldown_per_user | 2.1 |
| ignore_rate | 50% |
Et ces données doivent être utilisées pour ajuster l'algorithme de recommandation
Scénario: A/B testing - quota 6 vs 8 vs 10 par heure
Étant donné l'utilisateur est dans le groupe de test "quota_8"
Et le système teste différentes valeurs de quota
Quand le système évalue les notifications
Alors le quota max doit être de 8 au lieu de 6
Et le groupe de test doit être loggé dans les événements
Et les métriques d'engagement doivent être comparées entre groupes