# 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= Alors les stats de la période 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 écoutes Quand le milestone est vérifié Alors une notification "" 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)