Files
roadwave/features/api/audio-guides/declenchement-gps.feature
jpgiannetti a19a901ed4 feat(gherkin): ajouter features API et UI pour audio-guides multi-séquences
Créer 5 nouvelles features API :
- creation-gestion.feature : création, modification, publication d'audio-guides
- declenchement-gps.feature : calculs GPS, rayons, déclenchement automatique
- progression-sync.feature : sauvegarde progression, sync cloud, multi-device
- publicites.feature : insertion pub, fréquence, métriques créateur
- metriques-analytics.feature : statistiques, heatmaps, analytics créateur

Adapter mode-voiture.feature :
- ajouter section publicités en mode voiture (auto-play, pas de pause)

Corriger .gitignore :
- remplacer "api" par "/api" pour ne pas ignorer features/api/
2026-02-02 22:52:10 +01:00

340 lines
13 KiB
Gherkin

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