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,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
|
||||
}
|
||||
"""
|
||||
Reference in New Issue
Block a user