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/
This commit is contained in:
357
features/api/audio-guides/publicites.feature
Normal file
357
features/api/audio-guides/publicites.feature
Normal file
@@ -0,0 +1,357 @@
|
||||
# language: fr
|
||||
|
||||
Fonctionnalité: API - Publicités dans audio-guides
|
||||
En tant que système backend
|
||||
Je veux gérer l'insertion et la diffusion de publicités dans les audio-guides
|
||||
Afin de monétiser les contenus gratuits
|
||||
|
||||
Contexte:
|
||||
Étant donné que l'API RoadWave est démarrée
|
||||
Et que l'utilisateur "user@example.com" est authentifié (gratuit)
|
||||
|
||||
# 16.5.1 - Insertion publicité
|
||||
|
||||
Scénario: Calcul insertion publicité (1 pub toutes les 5 séquences)
|
||||
Étant donné un audio-guide gratuit avec 12 séquences
|
||||
Et que la fréquence pub est configurée à "1/5"
|
||||
Quand je fais un GET sur "/api/v1/audio-guides/ag_123/ad-schedule"
|
||||
Alors le code HTTP de réponse est 200
|
||||
Et le corps de réponse contient:
|
||||
"""json
|
||||
{
|
||||
"ad_frequency": "1/5",
|
||||
"ad_insertions": [
|
||||
{"after_sequence": 5, "position": "after"},
|
||||
{"after_sequence": 10, "position": "after"}
|
||||
],
|
||||
"total_ads": 2
|
||||
}
|
||||
"""
|
||||
|
||||
Plan du Scénario: Fréquence publicité configurable admin
|
||||
Étant donné que la fréquence pub est configurée à <frequence>
|
||||
Et un audio-guide avec 12 séquences
|
||||
Quand les insertions pub sont calculées
|
||||
Alors le nombre de pubs insérées est <nombre_pubs>
|
||||
|
||||
Exemples:
|
||||
| frequence | nombre_pubs |
|
||||
| 1/3 | 4 |
|
||||
| 1/5 | 2 |
|
||||
| 1/10 | 1 |
|
||||
|
||||
Scénario: Utilisateur Premium - Aucune publicité
|
||||
Étant donné un utilisateur Premium
|
||||
Et un audio-guide gratuit
|
||||
Quand je fais un GET sur "/api/v1/audio-guides/ag_123/ad-schedule"
|
||||
Alors le code HTTP de réponse est 200
|
||||
Et le corps de réponse contient:
|
||||
"""json
|
||||
{
|
||||
"ad_frequency": "0",
|
||||
"ad_insertions": [],
|
||||
"total_ads": 0,
|
||||
"reason": "premium_user"
|
||||
}
|
||||
"""
|
||||
|
||||
Scénario: POST /api/v1/audio-guides/playback/next-ad - Récupération pub suivante
|
||||
Étant donné qu'un utilisateur termine la séquence 5
|
||||
Et qu'une pub doit être insérée
|
||||
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/playback/next-ad":
|
||||
"""json
|
||||
{
|
||||
"sequence_completed": 5,
|
||||
"user_position": {
|
||||
"latitude": 43.1234,
|
||||
"longitude": 2.5678
|
||||
},
|
||||
"mode": "voiture"
|
||||
}
|
||||
"""
|
||||
Alors le code HTTP de réponse est 200
|
||||
Et le corps de réponse contient:
|
||||
"""json
|
||||
{
|
||||
"should_play_ad": true,
|
||||
"ad": {
|
||||
"ad_id": "ad_789",
|
||||
"audio_url": "https://cdn.roadwave.fr/ads/ad_789.m4a",
|
||||
"duration_seconds": 30,
|
||||
"skippable_after": 5,
|
||||
"advertiser": "Brand X"
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
Scénario: Sélection pub géolocalisée
|
||||
Étant donné que l'utilisateur est en Île-de-France (43.1234, 2.5678)
|
||||
Et que des publicités géolocalisées existent pour cette région
|
||||
Quand la pub suivante est sélectionnée
|
||||
Alors l'API filtre les pubs par:
|
||||
| critère | valeur |
|
||||
| Géolocalisation | Île-de-France |
|
||||
| Catégorie | Tourisme, Culture |
|
||||
| Langue | Français |
|
||||
| Budget actif | true |
|
||||
Et une pub correspondante est retournée
|
||||
|
||||
Scénario: Fallback pub nationale si pas de pub locale
|
||||
Étant donné que l'utilisateur est dans une région sans pubs locales
|
||||
Quand la pub suivante est sélectionnée
|
||||
Alors l'API sélectionne une pub nationale (France entière)
|
||||
Et la pub est retournée normalement
|
||||
|
||||
Scénario: Pas de pub si séquence non multiple de 5
|
||||
Étant donné qu'un utilisateur termine la séquence 4
|
||||
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/playback/next-ad"
|
||||
Alors le code HTTP de réponse est 200
|
||||
Et le corps de réponse contient:
|
||||
"""json
|
||||
{
|
||||
"should_play_ad": false,
|
||||
"reason": "not_ad_sequence"
|
||||
}
|
||||
"""
|
||||
|
||||
# Comportement selon mode
|
||||
|
||||
Scénario: Pub en mode piéton (auto-play puis pause)
|
||||
Étant donné un audio-guide en mode piéton
|
||||
Et qu'une pub doit être insérée après séquence 5
|
||||
Quand la pub est récupérée
|
||||
Alors le mode_behavior retourné est:
|
||||
"""json
|
||||
{
|
||||
"auto_play": true,
|
||||
"pause_after": true,
|
||||
"reason": "pedestrian_mode"
|
||||
}
|
||||
"""
|
||||
|
||||
Scénario: Pub en mode voiture/vélo/transport (auto-play puis séquence suivante)
|
||||
Étant donné un audio-guide en mode voiture
|
||||
Et qu'une pub doit être insérée
|
||||
Quand la pub est récupérée
|
||||
Alors le mode_behavior retourné est:
|
||||
"""json
|
||||
{
|
||||
"auto_play": true,
|
||||
"pause_after": false,
|
||||
"continue_to_next": true,
|
||||
"reason": "vehicle_mode"
|
||||
}
|
||||
"""
|
||||
|
||||
# 16.5.2 - Métriques et tracking
|
||||
|
||||
Scénario: POST /api/v1/ads/{ad_id}/impressions - Enregistrement impression
|
||||
Étant donné qu'une pub "ad_789" démarre
|
||||
Quand je fais un POST sur "/api/v1/ads/ad_789/impressions":
|
||||
"""json
|
||||
{
|
||||
"audio_guide_id": "ag_123",
|
||||
"sequence_after": 5,
|
||||
"user_id": "user_456",
|
||||
"timestamp": "2026-01-22T14:35:00Z"
|
||||
}
|
||||
"""
|
||||
Alors le code HTTP de réponse est 201
|
||||
Et l'impression est enregistrée dans ad_impressions
|
||||
Et le compteur impressions_count est incrémenté
|
||||
|
||||
Scénario: POST /api/v1/ads/{ad_id}/completions - Enregistrement écoute complète
|
||||
Étant donné qu'une pub de 30 secondes est écoutée à 25 secondes (83%)
|
||||
Quand je fais un POST sur "/api/v1/ads/ad_789/completions":
|
||||
"""json
|
||||
{
|
||||
"audio_guide_id": "ag_123",
|
||||
"listened_seconds": 25,
|
||||
"total_duration": 30,
|
||||
"completion_rate": 83
|
||||
}
|
||||
"""
|
||||
Alors le code HTTP de réponse est 201
|
||||
Et l'écoute complète est enregistrée (>80%)
|
||||
Et le créateur de l'audio-guide reçoit 0.003€ de revenus
|
||||
|
||||
Scénario: POST /api/v1/ads/{ad_id}/skips - Enregistrement skip
|
||||
Étant donné qu'une pub est skippée après 6 secondes
|
||||
Quand je fais un POST sur "/api/v1/ads/ad_789/skips":
|
||||
"""json
|
||||
{
|
||||
"audio_guide_id": "ag_123",
|
||||
"skipped_at_second": 6
|
||||
}
|
||||
"""
|
||||
Alors le code HTTP de réponse est 201
|
||||
Et le skip est enregistré dans ad_skips
|
||||
Et le taux de skip est mis à jour
|
||||
|
||||
Scénario: Validation écoute complète (>80%)
|
||||
Étant donné qu'une pub de 30 secondes est écoutée 20 secondes (66%)
|
||||
Quand je fais un POST sur "/api/v1/ads/ad_789/completions"
|
||||
Alors le code HTTP de réponse est 400
|
||||
Et le message d'erreur est "completion_rate: minimum 80% requis pour écoute complète"
|
||||
Et aucun revenu n'est attribué
|
||||
|
||||
# Métriques créateur
|
||||
|
||||
Scénario: GET /api/v1/creators/me/audio-guides/{id}/ad-metrics - Métriques pub
|
||||
Étant donné un audio-guide "ag_123" avec publicités
|
||||
Et les métriques suivantes sur 30 jours:
|
||||
| impressions | ecoutes_completes | skips | revenus |
|
||||
| 1000 | 650 | 350 | 1.95€ |
|
||||
Quand je fais un GET sur "/api/v1/creators/me/audio-guides/ag_123/ad-metrics"
|
||||
Alors le code HTTP de réponse est 200
|
||||
Et le corps de réponse contient:
|
||||
"""json
|
||||
{
|
||||
"period": "30_days",
|
||||
"impressions": 1000,
|
||||
"completions": 650,
|
||||
"skips": 350,
|
||||
"completion_rate": 65,
|
||||
"revenue": 1.95,
|
||||
"cpm": 1.95
|
||||
}
|
||||
"""
|
||||
|
||||
Scénario: Distinction revenus contenus classiques vs audio-guides
|
||||
Étant donné un créateur avec contenus classiques et audio-guides
|
||||
Quand je fais un GET sur "/api/v1/creators/me/revenue-breakdown"
|
||||
Alors le code HTTP de réponse est 200
|
||||
Et le corps de réponse contient:
|
||||
"""json
|
||||
{
|
||||
"total_revenue": 85.40,
|
||||
"breakdown": {
|
||||
"classic_content": {
|
||||
"revenue": 45.20,
|
||||
"impressions": 15000
|
||||
},
|
||||
"audio_guides": {
|
||||
"revenue": 40.20,
|
||||
"impressions": 13000
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
# Répartition revenus
|
||||
|
||||
Scénario: Calcul revenus créateur (3€ / 1000 écoutes complètes)
|
||||
Étant donné un audio-guide avec 1000 écoutes complètes pub ce mois
|
||||
Quand les revenus sont calculés
|
||||
Alors le créateur reçoit 3€
|
||||
Et le revenu par écoute complète est 0.003€
|
||||
|
||||
Scénario: POST /api/v1/ads/revenue/process - Calcul revenus batch mensuel
|
||||
Étant donné le 1er du mois
|
||||
Et que 500 créateurs ont des revenus pub à calculer
|
||||
Quand le job batch s'exécute
|
||||
Alors pour chaque créateur:
|
||||
| creator_id | ecoutes_completes | revenus |
|
||||
| creator_1 | 5000 | 15.00€ |
|
||||
| creator_2 | 2000 | 6.00€ |
|
||||
| creator_3 | 1200 | 3.60€ |
|
||||
Et les revenus sont ajoutés au solde creator_balance
|
||||
|
||||
# Normalisation audio
|
||||
|
||||
Scénario: Validation volume pub (-14 LUFS)
|
||||
Étant donné qu'une pub est uploadée avec volume -10 LUFS
|
||||
Quand la pub est validée
|
||||
Alors un processus de normalisation est déclenché
|
||||
Et le volume est ajusté à -14 LUFS (standard RoadWave)
|
||||
Et la pub normalisée est stockée sur le CDN
|
||||
|
||||
Scénario: Validation durée pub (max 60 secondes)
|
||||
Étant donné qu'une pub de 75 secondes est uploadée
|
||||
Quand la validation est effectuée
|
||||
Alors le code HTTP de réponse est 400
|
||||
Et le message d'erreur est "duration: maximum 60 secondes pour une publicité"
|
||||
|
||||
# Cas d'erreur
|
||||
|
||||
Scénario: Aucune pub disponible (stock épuisé)
|
||||
Étant donné qu'aucune campagne pub n'est active dans la région
|
||||
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/playback/next-ad"
|
||||
Alors le code HTTP de réponse est 200
|
||||
Et le corps de réponse contient:
|
||||
"""json
|
||||
{
|
||||
"should_play_ad": false,
|
||||
"reason": "no_ads_available"
|
||||
}
|
||||
"""
|
||||
Et aucune pub n'est insérée (séquence suivante démarre directement)
|
||||
|
||||
Scénario: Budget campagne épuisé
|
||||
Étant donné qu'une campagne pub a un budget de 1000€
|
||||
Et que le budget est épuisé
|
||||
Quand la pub est sélectionnée
|
||||
Alors cette campagne est exclue de la sélection
|
||||
Et une autre campagne active est choisie
|
||||
|
||||
Scénario: Pub corrompue ou indisponible
|
||||
Étant donné qu'une pub sélectionnée a un fichier audio corrompu
|
||||
Quand le client tente de la charger
|
||||
Alors une pub de fallback (backup) est retournée
|
||||
Et un log d'erreur est créé pour investigation
|
||||
|
||||
# Filtrage et ciblage
|
||||
|
||||
Scénario: Ciblage par catégorie audio-guide
|
||||
Étant donné un audio-guide tagué "tourisme", "culture", "musée"
|
||||
Et une campagne pub ciblée "tourisme + culture"
|
||||
Quand la pub est sélectionnée
|
||||
Alors cette campagne a une priorité élevée (matching tags)
|
||||
Et elle est préférée aux pubs génériques
|
||||
|
||||
Scénario: Filtrage par classification âge
|
||||
Étant donné un audio-guide classifié "tout_public"
|
||||
Et une campagne pub classifiée "18+"
|
||||
Quand la pub est sélectionnée
|
||||
Alors cette campagne est exclue
|
||||
Et seules les pubs "tout_public" sont éligibles
|
||||
|
||||
Scénario: Limite fréquence pub par utilisateur (cap frequency)
|
||||
Étant donné qu'un utilisateur a déjà entendu la pub "ad_789" 3 fois ce jour
|
||||
Et que le cap frequency est configuré à 3/jour
|
||||
Quand la pub est sélectionnée
|
||||
Alors "ad_789" est exclue
|
||||
Et une autre pub est choisie
|
||||
|
||||
Scénario: GET /api/v1/audio-guides/{id}/ad-policy - Politique pub
|
||||
Étant donné un audio-guide
|
||||
Quand je fais un GET sur "/api/v1/audio-guides/ag_123/ad-policy"
|
||||
Alors le code HTTP de réponse est 200
|
||||
Et le corps de réponse contient:
|
||||
"""json
|
||||
{
|
||||
"has_ads": true,
|
||||
"frequency": "1/5",
|
||||
"skippable_after_seconds": 5,
|
||||
"average_ad_duration": 30,
|
||||
"revenue_share": {
|
||||
"creator": "100%",
|
||||
"platform": "0%"
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
# Performance
|
||||
|
||||
Scénario: Cache Redis pour pubs actives
|
||||
Étant donné que les campagnes actives sont en cache Redis
|
||||
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/playback/next-ad"
|
||||
Alors les pubs sont récupérées depuis Redis (pas PostgreSQL)
|
||||
Et le temps de réponse est < 30ms
|
||||
|
||||
Scénario: Pre-fetching pub suivante (client)
|
||||
Étant donné que l'utilisateur est à la séquence 3
|
||||
Et qu'une pub sera insérée après la séquence 5
|
||||
Quand le client détecte l'approche de la séquence 5
|
||||
Alors il peut pré-charger la pub via GET "/api/v1/audio-guides/ag_123/ad-prefetch?after_sequence=5"
|
||||
Et la transition sera instantanée
|
||||
Reference in New Issue
Block a user