Réseau social audio géolocalisé pour les usagers de la route.
RoadWave permet aux conducteurs d'écouter du contenu audio contextuel pendant leurs trajets. La navigation se fait par commandes au volant (suivant/précédent), inspirée des réseaux à scroll infini.
Le contenu est diffusé en fonction de la position géographique de l'utilisateur et de ses centres d'intérêt.
| Utilisateur | Scénario |
|---|---|
| Conducteur | Écoute contenu audio en conduisant, navigation par commandes au volant (suivant/précédent), reçoit notifications géolocalisées en passant près de points d'intérêt |
| Routier | Écoute podcasts et radios live pendant ses trajets longue distance |
| Touriste à pied | Visite guidée audio d'un musée, monument ou ville : choisit parmi plusieurs guides, navigue entre séquences à son rythme (tactile/vocal), reçoit notification push quand un audio-guide est disponible à proximité |
| Commerçant | Diffuse une publicité audio ciblée GPS devant son commerce |
| Passionné auto | Découvre du contenu automobile près de circuits ou concessionnaires |
| Habitant local | Partage anecdotes ou bons plans géolocalisés dans son quartier |
| Média traditionnel | Le Monde, Le Parisien diffusent actualités géolocalisées ou nationales |
Tout utilisateur peut écouter et créer du contenu (rôle flexible).
| Rôle | Description |
|---|---|
| Auditeur | Écoute, like, s'abonne à des créateurs, signale des contenus |
| Créateur | Publie du contenu audio géolocalisé (individus, médias traditionnels) |
| Publicitaire | Diffuse des publicités ciblées géographiquement |
| Modérateur | Valide et modère les contenus signalés |
| Type | Description |
|---|---|
| Contenu court | Audio de quelques secondes à quelques minutes |
| Podcast | Épisodes plus longs, séries thématiques |
| Radio live | Diffusion en direct avec synchronisation approximative entre auditeurs |
| Audio-guide | Visite guidée multiséquence (musée, monument, ville) : plusieurs séquences numérotées, navigation manuelle entre pistes, liste complète visible, guidage vocal entre points d'intérêt |
Le créateur définit la zone de diffusion de son contenu :
| Niveau | Portée |
|---|---|
| Point GPS | Rayon précis autour d'une coordonnée |
| Ville | Diffusion dans une ville |
| Département | Diffusion départementale |
| Région | Diffusion régionale |
| Pays | Diffusion nationale |
Priorité de diffusion : plus la zone est précise, plus le contenu a de chances d'être diffusé (GPS > ville > département > région > pays).
Le contenu proposé est calculé via un score combiné :
Lorsque plusieurs contenus sont disponibles dans une zone, seul le plus pertinent est diffusé.
Chaque utilisateur possède des jauges d'intérêt qui évoluent dynamiquement :
| Action | Effet |
|---|---|
| Temps d'écoute long | Augmente la jauge |
| Like | Augmente la jauge |
| Abonnement | Augmente fortement la jauge |
| Skip rapide | Diminue la jauge |
Les créateurs taguent leur contenu avec des centres d'intérêt. L'algorithme privilégie les correspondances mais n'exclut pas les utilisateurs sans correspondance.
Interactions simplifiées pour sécurité routière maximale :
| Commande | Action |
|---|---|
| Suivant | Passer au contenu suivant |
| Précédent | Revenir au contenu précédent |
| Play/Pause | Mettre en pause / reprendre la lecture |
Like automatique : Le système détecte automatiquement vos préférences selon votre temps d'écoute : - Écoute ≥80% du contenu → Like renforcé (+2 points jauge) - Écoute 30-79% du contenu → Like standard (+1 point jauge) - Skip après <10s → Signal négatif (-0.5 point)
Voir ADR-010 pour les détails techniques
| Action | Description |
|---|---|
| Like explicite | Bouton cœur pour liker manuellement |
| S'abonner | Suivre un créateur |
| Signaler | Signaler un contenu inapproprié |
| Unlike | Retirer un like |
Approche hybride combinant participation communautaire, IA et modérateurs dédiés.
| Catégorie | Description |
|---|---|
| Haine et violence | Incitation à la haine, violence, discrimination |
| Contenu sexuel | Pornographie ou contenu sexuellement explicite |
| Illégalité | Apologie du terrorisme, actes criminels |
| Désinformation dangereuse | Fausses informations sur la santé, sécurité routière |
| Harcèlement | Menaces, intimidation, doxxing |
| Droits d'auteur | Violation de propriété intellectuelle |
| Fraude | Arnaques, escroqueries |
| Rôle | Capacités |
|---|---|
| Auditeur lambda | Signaler un contenu (1 clic) |
| Auditeur de confiance | Signalements priorisés après historique positif |
| Modérateur junior | Traiter signalements simples (spam, contenu évident) |
| Modérateur senior | Cas complexes, appels, décisions de ban |
| Admin modération | Définir les règles, superviser l'équipe |
1. Auditeur signale → File d'attente
2. IA pré-filtre → Cas évidents traités automatiquement
3. Modérateur junior → Traite 80% des cas restants
4. Modérateur senior → Cas complexes + recours
| Outil | Fonction |
|---|---|
| Transcription audio | Conversion automatique en texte pour analyse |
| Analyse vocale IA | Détection de ton agressif, cris, insultes |
| Empreinte audio | Détection de contenus déjà modérés (réupload) |
| Détection droits d'auteur | Identification automatique de musique protégée |
| Filtrage mots-clés | Liste noire de termes inappropriés |
| Strike | Sanction |
|---|---|
| Strike 1 | Avertissement + formation modération |
| Strike 2 | Suspension 7 jours + contenu supprimé |
| Strike 3 | Suspension 30 jours |
| Strike 4 | Ban définitif |
| Priorité | Type de contenu |
|---|---|
| CRITIQUE | Violence, suicide, mise en danger immédiate |
| HAUTE | Harcèlement, haine, désinformation |
| MOYENNE | Spam, contenu inapproprié |
| BASSE | Qualité audio, tags incorrects |
| Formule | Description |
|---|---|
| Gratuit | Accès complet avec publicités entre les contenus |
| Premium | Sans publicité + accès aux contenus exclusifs |
| Donnée | Finalité | Base légale |
|---|---|---|
| Position GPS | Diffusion de contenu géolocalisé | Consentement |
| Historique d'écoute | Personnalisation des recommandations | Intérêt légitime |
| Centres d'intérêt | Algorithme de recommandation | Consentement |
| Identité créateur | Publication de contenu | Exécution du contrat |
Les décisions techniques sont documentées dans docs/adr/
| Composant | Technologie | ADR |
|---|---|---|
| Backend | Go + Fiber | ADR-001 |
| Architecture Backend | Monolithe Modulaire | ADR-012 |
| Authentification | Zitadel | ADR-008 |
| Streaming | HLS | ADR-002 |
| Codec | Opus | ADR-003 |
| CDN | Bunny CDN | ADR-004 |
| Base de données | PostgreSQL + PostGIS | ADR-005 |
| ORM/Accès données | sqlc | ADR-013 |
| Cache | Redis Cluster | ADR-005 |
| Chiffrement | TLS 1.3 | ADR-006 |
| Live | WebRTC | ADR-002 |
| Frontend Mobile | Flutter | ADR-014 |
| Tests | Testify + Godog (Gherkin) | ADR-015, ADR-007 |
| Paiements | Mangopay | ADR-009 |
| Commandes volant | Like automatique | ADR-010 |
| Conformité stores | CarPlay, Android Auto, App/Play Store | ADR-011 |
Optimisé pour la voix en environnement bruyant (voiture).
| Qualité | Bitrate | Usage |
|---|---|---|
| Basse | 24 kbps | 2G/Edge |
| Standard | 48 kbps | 3G |
| Haute | 64 kbps | 4G/5G |
Fallback AAC-LC pour appareils legacy.
| Réseau | Buffer min | Buffer cible | Buffer max |
|---|---|---|---|
| WiFi | 5s | 30s | 120s |
| 4G/5G | 10s | 45s | 120s |
| 3G | 30s | 90s | 300s |
-- Requête géolocalisée typique
SELECT id, ST_Distance(location::geography, ST_MakePoint($lon, $lat)::geography) as distance
FROM contents
WHERE ST_DWithin(location::geography, ST_MakePoint($lon, $lat)::geography, 50000)
ORDER BY distance
LIMIT 20;
GEOADD contents:geo longitude latitude content_id
GEORADIUS contents:geo user_lon user_lat 50 km WITHDIST COUNT 20 ASC
TTL cache : 5 minutes (le contenu ne bouge pas).
┌─────────────────┐
│ Bunny CDN │ Cache HLS, distribution globale
└────────┬────────┘
│
┌────────┴────────┐
│ Nginx │ SSL, rate limiting, reverse proxy
└────────┬────────┘
│
┌────────┴────────┐
│ API Gateway │ Go + Fiber
└────────┬────────┘
│
┌────┴────┬─────────────┐
│ │ │
┌───▼───┐ ┌───▼───┐ ┌───────▼───────┐
│ Auth │ │ User │ │ Content/Geo │
│Service│ │Service│ │ Service │
└───────┘ └───────┘ └───────────────┘
│ │ │
└─────────┴─────────────┘
│
┌─────────┴─────────┐
│ │
┌───▼───┐ ┌─────▼─────┐
│ Redis │ │ PostgreSQL│
│Cluster│ │ + PostGIS │
└───────┘ └───────────┘
| Phase | Utilisateurs | Infra | Coût estimé |
|---|---|---|---|
| MVP | 0-100K | Monolithe Go, PostgreSQL managé + Zitadel, Bunny CDN/Storage | 50-150€/mois |
| Growth | 100K-1M | Kubernetes managé, replicas multi-région | 2-5K€/mois |
| Scale | 1M-10M | Multi-région, Nginx origin shield, Bunny CDN | 20-50K€/mois |
| Métrique | Objectif |
|---|---|
| Latence API p99 | < 100ms |
| Temps de démarrage audio | < 3s |
| Disponibilité | 99.9% |
| Connexions/serveur | 100K+ |
| UDP | HLS/TCP |
|---|---|
| Latence minimale | Latence acceptable (5-30s) |
| Problèmes NAT/firewall | Passe partout |
| Perte de paquets = artefacts | Retransmission automatique |
| Pas de cache CDN | Cache CDN = économies |
| Complexité++ | Standard de l'industrie |
Pour du contenu non-interactif (podcasts, audio-guides), la latence HLS est acceptable. WebRTC réservé à la radio live uniquement.
Statut : Accepté Date : 2025-01-17
RoadWave doit gérer 10M d'utilisateurs avec des connexions concurrentes massives pour le streaming audio géolocalisé.
Go avec le framework Fiber.
| Option | Performance | Simplicité | Écosystème |
|---|---|---|---|
| Go + Fiber | 1M+ conn/serveur | Élevée | Excellent cloud-native |
| Rust + Tokio | 2M+ conn/serveur | Faible | Bon |
| Node.js | 100-500K conn | Élevée | Excellent |
| Elixir/Phoenix | 2M+ conn | Moyenne | Bon temps réel |
Statut : Accepté Date : 2025-01-17
Streaming audio vers des utilisateurs mobiles en voiture, avec réseaux instables (tunnels, zones rurales, handoff cellulaire).
HLS (HTTP Live Streaming) pour le contenu à la demande. WebRTC réservé à la radio live.
| Option | Latence | Fiabilité mobile | Cache CDN | Complexité |
|---|---|---|---|---|
| HLS | 5-30s | Excellente | Oui | Faible |
| DASH | 5-30s | Bonne | Oui | Moyenne |
| WebRTC | <500ms | Moyenne | Non | Élevée |
| UDP brut | Minimale | Faible | Non | Très élevée |
Statut : Accepté Date : 2025-01-17
Audio diffusé en voiture : environnement bruyant, réseau mobile variable, qualité studio non nécessaire.
Opus comme codec principal, AAC-LC en fallback.
| Qualité | Bitrate | Usage |
|---|---|---|
| Basse | 24 kbps | 2G/Edge |
| Standard | 48 kbps | 3G |
| Haute | 64 kbps | 4G/5G |
| Codec | Bitrate | Qualité voix | Support mobile |
|---|---|---|---|
| Opus | 24-64 kbps | Excellente | Android natif, iOS via libs |
| AAC-LC | 64-128 kbps | Bonne | Universel |
| AAC-HE v2 | 32-64 kbps | Très bonne | Bon |
| MP3 | 128-320 kbps | Correcte | Universel (legacy) |
Statut : Accepté Date : 2025-01-17
Distribution audio HLS à 10M d'utilisateurs, besoin de performance, coût maîtrisé, et indépendance vis-à-vis des géants du cloud.
Bunny CDN comme CDN principal.
| Solution | Coût/mois (100TB) | Setup | Performance | Dépendance |
|---|---|---|---|---|
| Bunny CDN | ~1 000€ | 15 min | Très bon | Faible |
| Cloudflare | 0-5 000€ | 5 min | Excellent | Moyenne |
| CloudFront | ~9 750€ | 1h | Excellent | Forte (AWS) |
| Fastly | ~12-20 000€ | 2h | Exceptionnel | Moyenne |
| Nginx self-hosted | ~2-5 000€ | 1 jour | Excellent | Aucune |
.m3u8 (TTL court) et .ts (TTL long)Statut : Accepté Date : 2025-01-17
Requêtes géolocalisées intensives (contenus à proximité), données utilisateurs, historiques d'écoute.
Requête → Redis Cache → [HIT] → Réponse
↓
[MISS]
↓
PostGIS → Cache → Réponse
| Usage | Option choisie | Alternatives |
|---|---|---|
| Données utilisateurs | PostgreSQL | MySQL, MongoDB |
| Géolocalisation | PostGIS | MongoDB Geo, Elasticsearch |
| Cache | Redis | Memcached, KeyDB |
| Analytics (futur) | ClickHouse | TimescaleDB |
GEORADIUS) : 100K+ requêtes/secSELECT id, name,
ST_Distance(location::geography, ST_MakePoint($lon, $lat)::geography) as distance
FROM contents
WHERE ST_DWithin(location::geography, ST_MakePoint($lon, $lat)::geography, 50000)
ORDER BY distance
LIMIT 20;
Statut : Accepté Date : 2025-01-17
Streaming audio sur réseaux mobiles, conformité RGPD, protection du contenu.
| Méthode | Overhead | Usage |
|---|---|---|
| TLS 1.3 | ~1-2% CPU | HTTPS streaming |
| DTLS-SRTP | ~3-5% CPU | WebRTC temps réel |
| AES-128-CBC | Minimal | Chiffrement segments HLS |
| Widevine/FairPlay | Modéré | DRM (si licences l'exigent) |
Statut : Accepté Date : 2025-01-17
RoadWave nécessite une documentation des use cases qui soit à la fois lisible par tous les stakeholders et vérifiable automatiquement. Les scénarios utilisateurs (touriste, routier, commerçant) doivent être validés en continu.
Gherkin pour les spécifications avec Godog comme runner de tests.
| Option | Lisibilité | Intégration Go | Maintenance |
|---|---|---|---|
| Gherkin + Godog | Excellente | Native | Faible |
| Gauge (Markdown) | Bonne | Plugin | Moyenne |
| Tests Go natifs | Faible (devs only) | Native | Faible |
| Concordion | Bonne | Java-centric | Élevée |
.feature servent de documentation ET de testsfeatures/
├── recommendation/
│ ├── geolocalisation.feature
│ └── interets.feature
├── streaming/
│ ├── lecture.feature
│ └── buffering.feature
├── moderation/
│ └── signalement.feature
└── steps/
└── steps.go
Feature: Recommandation géolocalisée
Scenario: Touriste près d'un monument
Given un utilisateur avec l'intérêt "tourisme" à 80%
And une position GPS à 100m de la Tour Eiffel
When le système calcule les recommandations
Then l'audio guide "Histoire de la Tour Eiffel" est en première position
github.com/cucumber/godog.featuregodog run avant chaque mergeStatut : Accepté Date : 2025-01-18
RoadWave nécessite un système d'authentification sécurisé pour mobile (iOS/Android), scalable jusqu'à 10M utilisateurs, avec contraintes de coût réduit et conformité RGPD.
Zitadel (self-hosted) pour l'IAM avec validation JWT locale côté API Go.
| Solution | Coût (10M users) | Performance | Simplicité | Intégration Go |
|---|---|---|---|---|
| Zitadel | 200-500€/mois | Excellente | Élevée | SDK natif |
| Supabase Auth | 32K€/mois | Excellente | Élevée | REST API |
| Keycloak | 200-800€/mois | Bonne | Faible | Lib tierce |
| Auth0 | 50K€+/mois | Excellente | Élevée | SDK natif |
| JWT Custom | 0€ (dev) | Excellente | Moyenne | Natif |
┌─────────────────┐
│ Mobile Apps │ OAuth2 PKCE + Refresh tokens
└────────┬────────┘
│
┌────────▼────────┐
│ Zitadel IdP │ PostgreSQL + Redis
│ (self-hosted) │ MFA, passkeys, SSO
└────────┬────────┘
│ JWT token
┌────────▼────────┐
│ Go + Fiber API │ Validation JWT locale
│ (RoadWave) │ github.com/zitadel/zitadel-go
└─────────────────┘
import "github.com/zitadel/zitadel-go/v3/pkg/authorization/oauth"
// Validation JWT locale haute performance
verifier := oauth.WithJWT(config)
app.Use(verifier.Middleware())
// Accès aux claims
userID := ctx.Locals("sub").(string)
Statut : Accepté Date : 2025-01-19
RoadWave nécessite une solution de paiement pour gérer les abonnements Premium (4.99€/mois) et reverser 70% des revenus aux créateurs de contenu. Besoin de marketplace natif (split payments), KYC automatique, conformité RGPD, et coûts maîtrisés.
Mangopay (France/Luxembourg) comme solution unique pour paiements, marketplace et abonnements.
| Solution | Coût transaction | Marketplace | KYC | Souveraineté |
|---|---|---|---|---|
| Mangopay | 1.8% + 0.18€ | ✅ Natif | ✅ Gratuit | 🇪🇺 France/LU |
| Stripe Connect | 2.9% + 0.30€ | ✅ Natif | ❌ 1.20€ | 🇺🇸 USA |
| Mollie | 2.9% + 0.29€ | ❌ Non | ❌ Non | 🇪🇺 Pays-Bas |
| Paddle | 5% + 0.50€ | ✅ Natif | ✅ Inclus | 🇬🇧 UK |
┌────────────────────────┐
│ Utilisateurs Premium │ 4.99€/mois
└───────────┬────────────┘
│
┌───────▼───────┐
│ Mangopay │ - Abonnements récurrents
│ │ - KYC créateurs (gratuit)
│ │ - E-wallets automatiques
└───────┬───────┘ - Payouts SEPA (gratuits)
│
┌─────────┼─────────┐
│ │ │
┌─▼───┐ ┌─▼───┐ ┌─▼────┐
│Créa │ │Créa │ │Plate-│
│teur │ │teur │ │forme │
│ A │ │ B │ │(30%) │
│(70%)│ │(70%)│ │ │
└─────┘ └─────┘ └──────┘
// Abonnement récurrent
POST /v2.01/{ClientId}/recurringpayinregistrations
{
"AuthorId": "{UserId}",
"FirstTransactionDebitedFunds": {"Currency": "EUR", "Amount": 499}
}
// Transfer vers créateur (70%)
POST /v2.01/{ClientId}/transfers
{
"DebitedWalletId": "{PlatformWalletId}",
"CreditedWalletId": "{CreatorWalletId}",
"DebitedFunds": {"Currency": "EUR", "Amount": 349}
}
// Payout SEPA gratuit
POST /v2.01/{ClientId}/payouts/bankwire
Statut : Accepté Date : 2026-01-20
RoadWave est utilisée en conduisant. Les utilisateurs doivent pouvoir liker du contenu pour améliorer les recommandations, mais les commandes au volant ont des limitations : - 40% des véhicules n'ont que Suivant/Précédent/Mute - iOS/Android ne supportent pas nativement les appuis longs ou doubles-appuis - La sécurité impose des interactions minimales
Like automatique basé sur le temps d'écoute.
Règles : - ≥80% d'écoute → Like renforcé (+2 points) - 30-79% d'écoute → Like standard (+1 point) - <30% d'écoute → Pas de like - Skip <10s → Signal négatif (-0.5 point)
| Option | Compatibilité | Sécurité | Complexité |
|---|---|---|---|
| Like automatique | 100% | Maximale | Faible |
| Double-tap Pause | ~80% | Moyenne | Moyenne |
| Appui long Suivant | ~95% | Faible | Élevée |
| Configuration paramétrable | 100% | Variable | Très élevée |
completion_rateStatut : Accepté avec actions requises Date : 2026-01-20
RoadWave est une app audio géolocalisée utilisée en conduite (CarPlay/Android Auto) avec : - Contenu généré par utilisateurs (UGC) - Monétisation : publicités géolocalisées + Premium (4.99€ web / 5.99€ IAP) - GPS en arrière-plan - Partage de revenus avec créateurs (70/30)
Stratégie de conformité multi-plateforme avec : - Modération UGC robuste (IA + humain) - Prix différenciés selon région (US/EU/Monde) - GPS avec disclosure complète - Paiements créateurs externes (Mangopay)
| Plateforme | Conformité | Points critiques |
|---|---|---|
| Android Auto | ✅ Conforme | API Level 35+ (Android 15+) |
| CarPlay | ✅ Conforme | Entitlement audio à demander |
| Google Play | ⚠️ Actions requises | Déclaration GPS + UGC modération |
| App Store | ⚠️ Actions requises | Prix différenciés US/EU |
UGC (critique) : - Modération hybride IA + humain ✅ - 3 premiers contenus validés manuellement ✅ - Système de strikes (4 = ban) ✅ - Signalement + blocage utilisateurs ✅
GPS Background (critique) : - Permission "Always Location" = OPTIONNELLE - Demandée uniquement pour mode piéton (notifications arrière-plan audio-guides) - Justification Play Console :
"RoadWave permet aux utilisateurs de recevoir des alertes audio-guides lorsqu'ils passent à pied près de monuments/musées, même quand l'app est en arrière-plan. Cette fonctionnalité est optionnelle et peut être désactivée dans les paramètres." - In-app disclosure obligatoire (écran dédié avant demande permission) - Si refusée : app fonctionne en mode voiture uniquement - Action : Remplir formulaire background location Play Console avec justification
Réponses formulaire Play Console :
| Question | Réponse |
|---|---|
| Why does your app need background location? | "RoadWave offers optional pedestrian mode: users receive push notifications when passing near audio-guide points (museums, monuments) even when app is in background. This feature is opt-in and can be disabled in settings." |
| Is this feature core to your app? | "No. This is an optional feature. Users can use RoadWave without background location permission (in-car mode works with foreground location only)." |
| What user value does this provide? | "Pedestrian users (tourists, museum visitors) can keep phone in pocket and receive audio-guide alerts automatically without opening the app." |
| Does a less invasive alternative exist? | "Yes. Users can use manual navigation (open app, select audio-guide). Background location is a convenience feature for hands-free experience." |
Prix différenciés (légaux depuis 2025-2026) : - 🇺🇸 US : Lien externe autorisé (0% commission) - 🇪🇺 EU : Paiement externe DMA (7-20% commission réduite) - 🌍 Monde : IAP obligatoire (30% commission)
UGC : - Mode Kids obligatoire (filtrage selon âge) ✅ - Système de modération + signalement ✅
GPS Background (critique) :
- Permission "Always Location" = OPTIONNELLE
- Deux strings Info.plist requises :
- NSLocationWhenInUseUsageDescription : explication mode voiture
- NSLocationAlwaysAndWhenInUseUsageDescription : explication mode piéton (optionnel)
- In-app disclosure obligatoire avant demande "Always"
- Flux two-step : When In Use → Always (si user active mode piéton)
- Si refusée : app fonctionne en mode voiture uniquement
- Action : Voir strings détaillés dans 05-interactions-navigation.md
Position : Paiements créateurs = "services" (comme YouTube/Uber), pas IAP - Paiement via Mangopay Connect (externe) - Commission stores uniquement sur Premium (IAP) - Comparables : YouTube AdSense, TikTok Creator Fund, Uber
| Action | Plateforme | Deadline | Complexité |
|---|---|---|---|
| Demander CarPlay Audio Entitlement | Apple | Avant soumission iOS | Faible |
| Remplir formulaire background location avec justification | Google Play | Avant soumission Android | Faible |
| Implémenter disclosure GPS (écran dédié mode piéton) | iOS + Android | MVP | Moyenne |
| Rendre permission "Always Location" optionnelle | iOS + Android | MVP | Moyenne |
| Désactiver overlay visuel notification en CarPlay/Android Auto | iOS + Android | MVP | Moyenne |
| Mettre à jour strings Info.plist avec justifications détaillées | iOS | MVP | Faible |
| Finaliser système modération UGC | Google + Apple | MVP | Élevée |
Estimation totale : +5 jours développement avant soumission stores
Phase 1 - MVP : - IAP uniquement (5.99€/mois mondial) - Modération UGC active - GPS avec disclosure - CarPlay/Android Auto basique
Phase 2 - Post-validation : - Prix différenciés US (lien externe 4.99€) - Paiement externe EU (DMA) - Monétisation créateurs (Mangopay)
Statut : Accepté Date : 2025-01-20
RoadWave nécessite une architecture backend évolutive tout en gardant la simplicité opérationnelle pour un MVP. Le système doit supporter une croissance progressive de 0 à 10M utilisateurs.
Monolithe modulaire avec séparation claire en modules internes.
| Architecture | Complexité | Coûts infra | Time to market | Évolutivité |
|---|---|---|---|---|
| Monolithe modulaire | Faible | Faible | Rapide | 0-1M users |
| Microservices | Élevée | Élevée | Lent | 1M+ users |
| Hybrid (Mono + Workers) | Moyenne | Moyenne | Moyen | 100K-5M users |
internal/
├── auth/ # Validation JWT, intégration Zitadel
├── user/ # Profils, centres d'intérêt
├── content/ # CRUD contenus, métadonnées
├── geo/ # Recherche géospatiale, algorithme
├── streaming/ # Génération HLS, transcoding
├── moderation/ # Signalements, workflow
├── payment/ # Intégration Mangopay
└── analytics/ # Métriques écoute, jauges
Chaque module suit : handler.go → service.go → repository.go.
Statut : Accepté Date : 2025-01-20
RoadWave nécessite des requêtes SQL complexes (PostGIS géospatiales) avec performance optimale et type safety. Le choix entre ORM, query builder ou SQL brut impacte maintenabilité et performance.
sqlc pour génération de code Go type-safe depuis SQL.
| Solution | Performance | Type Safety | Contrôle SQL | Courbe apprentissage |
|---|---|---|---|---|
| sqlc | Excellente | Très haute | Total | Faible |
| GORM | Moyenne | Moyenne | Limité | Faible |
| pgx + SQL brut | Excellente | Faible | Total | Moyenne |
| sqlx | Bonne | Faible | Total | Faible |
sqlc generate → code mis à jour-- queries/content.sql
-- name: GetContentNearby :many
SELECT id, title, ST_Distance(location, $1::geography) as distance
FROM contents
WHERE ST_DWithin(location, $1::geography, $2)
ORDER BY distance
LIMIT $3;
sqlc generate
// Code Go type-safe généré automatiquement
contents, err := q.GetContentNearby(ctx, location, radius, limit)
github.com/sqlc-dev/sqlcsqlc.yaml à la racine pour configurationgolang-migratesqlc generate pour valider cohérence SQL/GoStatut : Accepté Date : 2025-01-20
RoadWave nécessite applications iOS et Android avec support CarPlay/Android Auto, lecture audio HLS avancée, géolocalisation temps réel. Le choix du framework impacte vélocité développement et performances.
Flutter pour iOS et Android avec codebase unique.
| Framework | Codebase | Performance | Audio/CarPlay | Communauté |
|---|---|---|---|---|
| Flutter | Unique | Native | Excellente | Large |
| React Native | Unique | Bonne | Modules natifs requis | Très large |
| Native (Swift+Kotlin) | Double | Excellente | Native | Large |
| Ionic/Capacitor | Unique | Moyenne | Limitée | Moyenne |
just_audio mature avec support HLS, buffering adaptatifflutter_carplay, android_auto_flutter)geolocator robuste avec gestion permissionsdependencies:
flutter_bloc: ^8.1.3 # State management
just_audio: ^0.9.36 # Lecture audio HLS
geolocator: ^11.0.0 # GPS temps réel (mode voiture)
geofence_service: ^5.2.0 # Geofencing arrière-plan (mode piéton)
flutter_local_notifications: ^17.0.0 # Notifications géolocalisées
dio: ^5.4.0 # HTTP client
flutter_secure_storage: ^9.0.0 # Tokens JWT
cached_network_image: ^3.3.1 # Cache images
Nouveaux packages (contenus géolocalisés) :
geofence_service : Détection entrée/sortie rayon 200m en arrière-plan (mode piéton)Supporte notifications push même app fermée
flutter_local_notifications : Notifications locales avec compteur dynamique
lib/
├── core/ # Config, DI, routes
├── data/ # Repositories, API clients
├── domain/ # Models, business logic
├── presentation/ # UI (screens, widgets, blocs)
└── main.dart
flutter_test pour widgets, integration_test pour E2EStatut : Accepté Date : 2025-01-20
RoadWave nécessite une couverture tests robuste avec documentation vivante des use cases. La stratégie doit équilibrer vélocité développement et qualité.
Approche multi-niveaux : unitaires, intégration, BDD (Gherkin), E2E, load testing.
| Type | Framework | Cible | Fréquence |
|---|---|---|---|
| Unitaires | Testify | 80%+ couverture | Chaque commit |
| Intégration DB | Testify + Testcontainers | Repositories critiques | Avant merge PR |
| BDD (Gherkin) | Godog | User stories | Avant release |
| E2E Mobile | Flutter integration_test | Parcours critiques | Nightly |
| Load | k6 | N/A | Avant mise en prod |
// internal/user/service_test.go
func TestGetUserByID(t *testing.T) {
mockRepo := new(MockRepository)
service := NewService(mockRepo)
mockRepo.On("FindByID", "123").Return(&User{ID: "123"}, nil)
user, err := service.GetByID("123")
assert.NoError(t, err)
assert.Equal(t, "123", user.ID)
mockRepo.AssertExpectations(t)
}
Couverture minimale : 80% sur packages internal/*/service.go
Voir ADR-007 pour contexte complet.
# features/recommendation.feature
Feature: Recommandation géolocalisée
Scenario: Contenu proche prioritaire
Given je suis à Paris (48.8566, 2.3522)
And un contenu existe à 500m avec tag "tourisme"
And mon intérêt "tourisme" est à 85%
When je demande des recommandations
Then le contenu est en première position
And le score de pertinence est supérieur à 0.8
Couverture : Tous les cas d'usage du README.md traduits en .feature.
// internal/geo/repository_integration_test.go
func TestFindContentNearby(t *testing.T) {
container := testcontainers.RunPostGISContainer(t)
defer container.Terminate()
repo := NewRepository(container.DB())
// Insert test data
repo.CreateContent(testContent)
// Query
results := repo.FindNearby(48.8566, 2.3522, 5000)
assert.Len(t, results, 1)
}
// integration_test/player_test.dart
testWidgets('Play audio and skip', (tester) async {
await tester.pumpWidget(MyApp());
await tester.tap(find.byIcon(Icons.play_arrow));
await tester.pumpAndSettle();
expect(find.text('Now Playing'), findsOneWidget);
await tester.tap(find.byIcon(Icons.skip_next));
expect(find.text('Next Content'), findsOneWidget);
});
// tests/load/streaming.js
import http from 'k6/http';
import { check } from 'k6';
export let options = {
stages: [
{ duration: '2m', target: 1000 },
{ duration: '5m', target: 10000 },
],
};
export default function () {
let res = http.get('https://api.roadwave.com/v1/content/nearby');
check(res, { 'status is 200': (r) => r.status === 200 });
}
Objectif : API p99 < 100ms à 10K RPS.
# .github/workflows/ci.yml
- name: Unit tests
run: go test -race -coverprofile=coverage.out ./...
- name: BDD tests
run: godog run features/
- name: Integration tests
run: go test -tags=integration ./...
- name: Coverage gate
run: |
coverage=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//')
if (( $(echo "$coverage < 80" | bc -l) )); then
echo "Coverage $coverage% < 80%"
exit 1
fi
github.com/stretchr/testifygithub.com/cucumber/godoggithub.com/testcontainers/testcontainers-gografana/k6Documentation complète des règles métier validées pour l'application RoadWave. Chaque section détaille les comportements, flux et décisions techniques.
Contenu : Inscription, connexion, récupération de compte
Contenu : Scoring, géolocalisation, orientation politique, mode Kids
Contenu : Évolution jauges, valeurs initiales
Contenu : Upload, métadonnées, validation, modification
Contenu : Commandes Suivant/Précédent, interactions volant, lecture en boucle
Contenu : Campagnes, fréquence, insertion, facturation
Contenu : Démarrage, arrêt, comportement auditeur
Contenu : Impact algorithme, notifications, audio-guides, limites
Contenu : Activation, KYC, sources revenus, paiement
Contenu : Offre, multi-devices, avantages, gestion abonnement
Contenu : Téléchargement, validité, synchronisation
Contenu : Aucun contenu, contenu supprimé, perte réseau, GPS désactivé
Contenu : Consentements, anonymisation, export, suppression
Contenu : Signalement, traitement, sanctions
Contenu : Partage, profil créateur, recherche
roadwave.fr/share/c/[id], web player + deep linkContenu : Modes déplacement, navigation, déclenchement GPS, publicités
Chaque fichier de règles métier suit la structure :
Ces documents servent de référence unique pour :
Prochaine étape : Création des fichiers .feature Gherkin dans features/ basés sur ces règles.
Dernière mise à jour : Janvier 2026 Statut : ✅ Toutes sections validées
Décision : Email/Password uniquement (pas d'OAuth tiers)
Justification : - Souveraineté : pas de dépendance externe - RGPD : données 100% contrôlées - Coût : 0€ (Zitadel intégré)
Décision : Différenciée selon le rôle utilisateur
| État | Capacités |
|---|---|
| Email non vérifié | Lecture illimitée + création max 5 contenus |
| Email vérifié | Toutes fonctionnalités débloquées |
Paramètres : - Lien de vérification expire après 7 jours - Possibilité de renvoyer le lien (max 3 fois/jour) - Rappel in-app après création du 3ème contenu
Justification : - Friction minimale à l'inscription - Anti-spam sans bloquer l'essai du produit - Incitation naturelle à vérifier (déblocage)
Vérification obligatoire sous 7 jours pour : - Accès au programme de monétisation - KYC et reversement des revenus (conformité Mangopay) - Publication illimitée de contenus
Justification : - Conformité légale : KYC obligatoire pour transferts financiers - Anti-fraude : Vérification identité réelle pour paiements - Responsabilité : RoadWave doit pouvoir prouver identité créateurs monétisés
Obligatoires : - ✅ Email (format validé) - ✅ Mot de passe (voir règles ci-dessous) - ✅ Pseudo (3-30 caractères, alphanumérique + underscore) - ✅ Date de naissance (vérification âge minimum)
Optionnelles : - ❌ Nom complet (privacy by design) - ❌ Photo de profil (avatar par défaut généré) - ❌ Bio (ajout ultérieur)
Âge minimum : - 13 ans minimum (conformité réglementation réseaux sociaux EU) - Vérification à l'inscription via date de naissance - Blocage inscription si <13 ans avec message explicite
Justification : - RGPD minimal data - Friction réduite (4 champs max) - Protection mineurs (obligation légale)
Décision : Classification obligatoire des contenus
Catégories : - 🟢 Tout public (défaut) - 🟡 13+ : contenu mature léger (débats, actualité sensible) - 🟠 16+ : contenu mature (violence verbale, sujets sensibles) - 🔴 18+ : contenu adulte (langage explicite, sujets réservés)
Règles de diffusion : - Utilisateur 13-15 ans → contenus 🟢 uniquement - Utilisateur 16-17 ans → contenus 🟢 🟡 - Utilisateur 18+ → tous contenus
Modération : - Vérification obligatoire de la classification lors de la validation - Reclassification possible par modérateurs - Strike si classification volontairement incorrecte
Justification : - Protection mineurs (obligation légale) - Responsabilité plateforme - Coût : champ supplémentaire + règle algo
Règles : - ✅ Minimum 8 caractères - ✅ Au moins 1 majuscule - ✅ Au moins 1 chiffre - ❌ Pas de symbole obligatoire (simplicité)
Validation : - Côté client (feedback temps réel) - Côté backend (sécurité) - Message d'erreur explicite par règle non respectée
Justification : - Standard industrie - Bloque 95% des mots de passe faibles - UX acceptable (pas trop restrictif)
Décision : Optionnel mais recommandé
Méthodes disponibles : - ✅ TOTP (Time-based One-Time Password) via app (Google Authenticator, Authy) - ✅ Email (code 6 chiffres, expire 10 min) - ❌ SMS (coût élevé ~0.05€/SMS)
Appareil de confiance : - Option "Ne plus demander sur cet appareil" → bypass 2FA pendant 30 jours - Révocable depuis paramètres compte - Liste des appareils de confiance visible
Justification : - Sécurité renforcée sans coût SMS - UX : appareil de confiance évite friction quotidienne - Zitadel natif (0€)
Règles : - Maximum 5 tentatives par période de 15 minutes - Blocage temporaire après 5 échecs - Compteur reset automatique après 15 min - Notification email si blocage (tentative suspecte)
Déblocage : - Automatique après 15 min - Ou via lien "Mot de passe oublié"
Justification : - Anti brute-force - Standard industrie (équilibre sécurité/UX) - Zitadel natif (0€)
Durée de vie : - Access token : 15 minutes - Refresh token : 30 jours
Rotation : - Refresh token rotatif (nouveau token à chaque refresh) - Ancien token invalidé immédiatement - Détection token replay attack
Extension automatique : - Si app utilisée, session prolongée automatiquement - Inactivité 30 jours → déconnexion
Justification : - Sécurité (token court-vie) - UX (pas de reconnexion fréquente) - Standard OAuth2/OIDC
Décision : Sessions simultanées illimitées
Gestion : - Liste des devices connectés visible (OS, navigateur, dernière connexion, IP/ville) - Révocation individuelle possible - Révocation globale "Déconnecter tous les appareils"
Alertes : - Notification push + email si connexion depuis nouveau device - Détection localisation suspecte (IP pays différent)
Justification : - UX maximale (écoute voiture + tablette maison + web) - Sécurité via transparence (utilisateur voit tout) - Coût : table sessions PostgreSQL
Méthode : Email uniquement
Processus : 1. Utilisateur clique "Mot de passe oublié" 2. Email avec lien de reset envoyé 3. Lien expire après 1 heure 4. Page de reset : nouveau mot de passe (validation règles) 5. Confirmation + déconnexion tous devices (sauf celui en cours)
Notifications : - Email immédiat si changement mot de passe - Push si changement depuis appareil non reconnu
Limite : - Maximum 3 demandes/heure (anti-spam)
Justification : - Standard sécurité - Pas de coût SMS - Protection contre attaque sociale
Décision : 3 types de contenus selon leur pertinence géographique
| Type | Description | Exemple | Pondération géo |
|---|---|---|---|
| Géo-ancré | Contenu lié à un lieu précis | Audio-guide monument, pub restaurant local | 70% |
| Géo-contextuel | Pertinent dans une zone | Actualité régionale, événement local | 50% |
| Géo-neutre | Universel, pas de lien géo | Podcast philosophie, musique | 20% |
Qui décide : - ✅ Créateur choisit le type à la publication - ✅ Modération peut reclassifier après validation - ✅ Modification possible après publication (tout le monde a le droit de se tromper)
Justification : - Différencie audio-guide (hyper-local) des podcasts génériques - Algorithme adapte automatiquement la pondération - Coût : champ supplémentaire en DB + règle algo
Décision : Score combiné dynamique selon type de contenu
score_final = (score_geo * poids_geo_type)
+ (score_interets * poids_interets_type)
+ (score_engagement * 0.2)
+ (bonus_aleatoire)
où :
- score_geo = 1 - (distance_km / distance_max_km)
- score_interets = moyenne des jauges utilisateur pour les tags du contenu
- score_engagement = (taux_completion * 0.5) + (ratio_likes * 0.3) + (ratio_abonnements * 0.2)
- bonus_aleatoire = 10% des recommandations tirées aléatoirement
Pondérations par type :
| Type | Poids géo | Poids intérêts |
|---|---|---|
| Géo-ancré | 0.7 | 0.1 |
| Géo-contextuel | 0.5 | 0.3 |
| Géo-neutre | 0.2 | 0.6 |
Paramètres : - Distance max recommandée : 200 km - Dégradation : linéaire (1 - distance/200km) - Rayon point GPS : 500m (adapté au volume de contenu local)
Tous ces paramètres sont configurables à chaud via interface admin.
Justification : - Flexibilité totale selon type de contenu - Linéaire = rattrapage naturel du contenu viral ancien - Auditable via métriques engagement (moyenne/médiane)
Décision : Intégration popularité avec poids 0.2
Métriques : - Taux de complétion : écoutes >80% / total écoutes (poids 0.5) - Ratio likes : likes / écoutes (poids 0.3) - Ratio abonnements : nouveaux abonnés après écoute / écoutes (poids 0.2)
Seuil minimum : - Minimum 50 écoutes avant de considérer l'engagement - Contenu <50 écoutes : score engagement = 0.5 (neutre)
Contenu viral : - Un contenu viral à Paris peut être proposé à Marseille - Score géo faible compensé par score engagement élevé - Paramétrable admin
Dépréciation temporelle : - Pas de dépréciation automatique - Ratio linéaire = contenu ancien mais toujours apprécié reste pertinent
Justification : - Équilibre découverte / qualité - Pas de pénalisation arbitraire des contenus anciens - Coût : calculs sur métriques existantes
Décision : 10% par défaut, paramétrable utilisateur
Fonctionnement : - 1 contenu sur 10 = tirage aléatoire (hors historique déjà écouté) - Utilisateur peut ajuster : curseur 0% (aucun aléatoire) à 50% (exploration max)
Curseur utilisateur : - 🎯 0% : Personnalisé max (recommandations strictes) - ⚖️ 10% : Équilibré (défaut) - 🎲 30% : Découverte élevée - 🌍 50% : Découverte max (équivaut à national = découverte)
Justification : - Évite la bulle de filtre - Laisse l'utilisateur maître de son expérience - Coût : variable aléatoire en algo
⚠️ Note : La classification politique avancée (échelle gauche/droite, équilibrage imposé) a été reportée post-MVP. Voir ANNEXE-POST-MVP.md pour la version complète.
Décision MVP : Tag simple "Politique" sans classification idéologique
Tagging : - Créateur peut taguer son contenu comme "Politique" (optionnel) - Tag "Politique" au même niveau que "Économie", "Sport", "Culture", etc. - Pas de classification gauche/droite - Pas d'équilibrage imposé
Filtrage utilisateur : - Option paramètres : "Masquer contenu politique" - Si activé → 0% de contenus tagués "Politique" dans le feed - Par défaut : désactivé (tous contenus visibles)
Justification MVP : - Simplicité : Pas de modération politique coûteuse (~2000€/mois économisés) - Neutralité technique : Aucun jugement éditorial sur orientation - Risque minimal : Évite controverses et contentieux DSA au lancement - Fonctionnel : Utilisateurs peuvent filtrer si souhaité
Post-MVP : - Classification avancée possible si forte demande utilisateurs - Nécessite ressources modération dédiées et audit DSA
Décision : Mode optionnel pour adolescents 13-15 ans uniquement
⚠️ Note : Âge minimum d'inscription = 13 ans (obligation légale EU). Pas d'utilisateurs <13 ans sur la plateforme.
Tranche concernée :
| Tranche | Description | Contenus autorisés | Restrictions |
|---|---|---|---|
| 13-15 ans | Collège | Contenus "Tous publics" uniquement | Filtrage 16+ et 18+ |
Activation : - ❌ Pas d'activation automatique (tous les utilisateurs ont ≥13 ans) - ✅ Activation manuelle via toggle paramètres - ✅ Parents peuvent activer pour leurs enfants 13-15 ans - ✅ Utilisateur peut désactiver à tout moment
Filtrage quand Mode Kids activé : - ✅ Contenus "Tous publics" uniquement - ❌ Exclusion contenus 16+ et 18+ - ❌ Pas de contenu politique (automatiquement filtré) - ❌ Pas de publicité (ou uniquement pub validée manuellement)
Interface : - Interface standard (pas d'interface dédiée enfants pour MVP) - Filtrage algorithmique des contenus inappropriés
Justification : - Conformité légale : Âge minimum 13 ans (RGPD, DSA) - Simplicité MVP : Un seul mode optionnel vs 4 tranches d'âge - Protection mineurs : Filtrage contenus adultes pour 13-15 ans - Flexibilité : Parents décident d'activer ou non
Décision : Notification au passage, pas d'anticipation
Fonctionnement : 1. Utilisateur passe à <500m d'un point GPS (contenu géo-ancré) 2. Notification sonore (bip court) + visuelle (logo selon type) 3. Types de logos : 📍 Info, 🏛️ Culturel, 🍴 Commercial, 🎭 Événement 4. Délai réaction utilisateur : 5 secondes pour accepter (bouton volant ou commande vocale) 5. Si accepté → lecture immédiate 6. Si ignoré → contenu proposé normalement en file d'attente
Publicités : - ⚠️ Jamais d'interruption de contenu en cours - Pub s'intercale entre deux séquences uniquement - Notification pub : son différent (facultatif selon paramètres)
Gestion demi-tour : - Si utilisateur repart du point après notification → pas de nouvelle notification (déjà proposé) - Réinitialisation après 24h
Justification : - Respect écoute en cours (pas de coupure brutale) - UX fluide (utilisateur garde contrôle) - Simplicité technique (pas de prédiction trajectoire)
Décision : Pas de reproposition sauf contenu partiel
Règles :
| État écoute | Completion | Action |
|---|---|---|
| Écouté complètement | >80% | ❌ Ne jamais reproposer (sauf flag replayable = true pour audio-guides) |
| Skippé rapidement | <10s | ❌ Ne pas reproposer |
| Partiellement écouté | 10-80% | ✅ Reproposer avec reprise position (last_position_seconds) |
Stockage historique :
- Table user_content_history (user_id, content_id, completion_rate, last_position, listened_at)
- Historique illimité (PostgreSQL)
- Algorithme considère les 100 derniers pour optimisation requêtes
- Export complet disponible (RGPD)
Justification : - Découverte maximale (pas de redites) - Respect erreurs de clic (contenu partiel = 2nde chance) - Coût stockage négligeable (PostgreSQL scalable)
Décision : Tous paramètres scoring exposés + A/B testing
Paramètres configurables à chaud :
| Paramètre | Plage | Défaut | Unité |
|---|---|---|---|
poids_geo_ancre |
0.5 - 1.0 | 0.7 | % |
poids_geo_contextuel |
0.3 - 0.7 | 0.5 | % |
poids_geo_neutre |
0.0 - 0.4 | 0.2 | % |
poids_engagement |
0.0 - 0.5 | 0.2 | % |
part_aleatoire_global |
0.0 - 0.3 | 0.1 | % |
distance_max_km |
50 - 500 | 200 | km |
rayon_gps_point_m |
100 - 2000 | 500 | m |
seuil_min_ecoutes_engagement |
10 - 200 | 50 | nb |
Application changements : - Immédiat : nouveaux calculs utilisent nouvelle config - Aucun recalcul batch (coût CPU) - Version config trackée (git-like) - Rollback 1 clic
A/B Testing : - Création variantes (Config A vs Config B) - Split utilisateurs 50/50 aléatoire - Métriques comparatives : taux complétion, engagement, session duration - Dashboard graphique temps réel
Audit engagement : - Métriques clés : moyenne/médiane temps d'écoute par session - Graphiques : évolution engagement selon config - Export CSV pour analyse externe
Justification : - Optimisation continue sans redéploiement - Data-driven decisions (métriques objectives) - Coût : dashboard admin à développer (one-time)
Décision : Curseurs avancés avec profils sauvegardables
Niveaux de personnalisation :
Curseurs disponibles : - 📍 Géolocalisation : Local ← slider → National (découverte = national) - 🎲 Découverte : 0% ← slider → 50% (part aléatoire) - ⚖️ Politique : Masquer / Équilibré / Mes préférences
Profils sauvegardables : - 🚗 Trajet quotidien (boulot) : géo local, découverte 5%, politique masqué - 🛣️ Road trip : géo régional, découverte 30%, politique équilibré - 👶 Enfants : Mode Kids activé
Synchronisation : - ✅ Sync profils entre devices (cloud PostgreSQL) - ❌ Pas de partage profils entre utilisateurs (famille) - Auto-switch selon context (détection trajet récurrent via GPS)
Sécurité conduite : - ⚠️ Blocage modification si vitesse GPS >10 km/h - Warning au lancement app : "Configurez avant de prendre la route" - Modifications uniquement app arrêtée/passager
Justification : - Utilisateur maître de son expérience - Contextes d'usage différents (quotidien vs voyage) - Sécurité routière (pas de distraction)
Décision : Ouverture aux médias établis
Médias autorisés : - Presse nationale : Le Monde, Le Parisien, Libération, Le Figaro, etc. - Radios : France Inter, RTL, Europe 1, etc. - Médias régionaux : Ouest-France, Sud-Ouest, etc.
Format contenus : - Flashs info géolocalisés (actualité régionale) - Chroniques thématiques (culture, économie, sport) - Éditos et débats (classification politique appliquée)
Validation : - Compte média vérifié (badge ✓) - Pas de validation 3 premiers contenus (confiance établie) - Modération a posteriori uniquement
Monétisation : - Partage revenus pub standard (même conditions créateurs) - Possibilité sponsoring direct (pas via plateforme)
Justification : - Crédibilité plateforme (contenus professionnels) - Diversité éditoriale - Attractivité grand public (noms reconnus)
Décision : Système simple avec valeurs fixes
| Action | Impact jauge | Justification |
|---|---|---|
| Like automatique renforcé (≥80% écoute) | +2% | Signal fort d'intérêt (écoute quasi-complète) |
| Like automatique standard (30-79% écoute) | +1% | Signal modéré d'intérêt |
| Like explicite (manuel) | +2% | Signal fort, cumulable avec auto |
| Abonnement créateur | +5% sur tous ses tags | Signal très fort d'affinité |
| Skip rapide (<10s) | -0.5% | Désintérêt marqué |
| Skip tardif (≥30%) | 0% | Neutre (contenu essayé suffisamment) |
Paramètres techniques : - Les jauges sont bornées strictement entre 0% et 100% - Calcul immédiat à chaque action (pas de batch différé) - Les tags du contenu sont définis par le créateur à la publication - Si un contenu a plusieurs tags, chaque jauge correspondante est impactée
Exemple de calcul :
Contenu de 5 minutes tagué "Automobile" + "Voyage"
Scénario 1 : Écoute 4min30 (90%)
→ Like automatique renforcé (+2%)
→ Jauge Automobile : 45% → 47%
→ Jauge Voyage : 60% → 62%
Scénario 2 : Écoute 2min30 (50%)
→ Like automatique standard (+1%)
→ Jauge Automobile : 45% → 46%
→ Jauge Voyage : 60% → 61%
Scénario 3 : Écoute 2min30 (50%) + Like manuel
→ Like auto +1% puis like manuel +2% = +3% total
→ Jauge Automobile : 45% → 48%
→ Jauge Voyage : 60% → 63%
Scénario 4 : Skip après 5s
→ Signal négatif (-0.5%)
→ Jauge Automobile : 45% → 44.5%
→ Jauge Voyage : 60% → 59.5%
Justification : - Like automatique : Reflète l'engagement réel (voir ADR-010) - Sécurité routière : Pas d'action complexe en conduite - Prévisibilité : Règles claires et déterministes - Coût minimal : Calculs simples en backend - Fiabilité : Pas d'edge cases complexes - Ajustable : Valeurs modifiables via dashboard admin si besoin
Décision : Démarrage neutre à 50%, pas de questionnaire
À l'inscription : - Toutes les jauges d'intérêt sont initialisées à 50% - Pas de questionnaire onboarding (friction zéro) - L'algorithme apprend naturellement via les premières écoutes
Catégories disponibles : - Automobile - Voyage - Famille - Amour - Musique - Économie - Cryptomonnaie - Politique - Culture générale - Sport - Technologie - Santé - ... (extensible)
Cold start (premiers jours) : 1. Nouvel utilisateur s'inscrit → toutes jauges à 50% 2. Écoute premier podcast "Automobile" → jauge Auto monte à 51% 3. Skip un contenu "Économie" → jauge Éco descend à 48% 4. Après 10-15 écoutes, profil commence à se dessiner clairement
Alternative optionnelle (post-MVP) : - Questionnaire optionnel proposé après 3 écoutes (in-app) - Message : "Améliorez vos recommandations en sélectionnant vos centres d'intérêt" - Si rempli : jauges sélectionnées passent à 70%, non sélectionnées à 30% - Si skip : conserve 50% partout
Justification : - Inscription ultra-rapide : pas de questionnaire = moins de churn - Découverte naturelle : l'algorithme apprend en quelques écoutes - Équitable : pas de biais initial vers certains créateurs - Comportement déterministe : facile à tester et débugger - Cold start acceptable : à 50%, tous les contenus ont une chance égale initialement
Décision : Pas de dégradation automatique
Les jauges ne diminuent jamais avec le temps de manière automatique.
Règle : - Une jauge ne change que par les actions utilisateur (like, écoute, skip) - Pas de cron job de dégradation périodique - Pas de "rafraîchissement" artificiel
Scénario illustratif :
Utilisateur aimait "Économie" (jauge 80%) il y a 1 an
→ Depuis, skip tous les contenus Éco
→ Jauge descend naturellement à 40% via les skips
→ Pas besoin de dégradation temporelle
Si utilisateur inactif longtemps : - Utilisateur part en vacances 6 mois → jauges conservées - Au retour : ses jauges reflètent toujours ses goûts d'avant - Comportement cohérent et prévisible
Alternative utilisateur (contrôle explicite) : - Bouton "Réinitialiser mes centres d'intérêt" dans paramètres - Action manuelle : remet toutes les jauges à 50% - Permet nouveau départ si souhaité (changement de vie, etc.)
Justification : - Principe KISS (Keep It Simple, Stupid) - Coût 0 : pas de batch nocturne, pas de calculs temporels - Fiabilité maximale : pas de bugs de fuseaux horaires, dates, etc. - UX prévisible : jauge = reflet des actions, pas d'automatisme caché - Respect historique : si utilisateur aimait X depuis 2 ans, pourquoi "oublier" ? - Évolution naturelle : les actions récentes suffisent à faire évoluer les jauges
Décision : Formats universels avec encodage asynchrone
Formats acceptés :
- ✅ MP3 (.mp3)
- ✅ AAC (.aac, .m4a)
- ❌ WAV, FLAC (trop lourds, inutiles en voiture)
Limites :
| Paramètre | Valeur | Justification |
|---|---|---|
| Taille maximale | 200 MB | ~4h de podcast à 128 kbps |
| Durée maximale | 4 heures | Suffisant pour podcasts longs |
| Validation format | Client + backend | Double sécurité |
Pipeline d'encodage :
1. Upload fichier (MP3/AAC) → Bunny Storage temporaire
2. Job asynchrone (worker Go + FFmpeg) :
- Validation format et intégrité
- Réencodage Opus 3 profils (24/48/64 kbps)
- Génération segments HLS (.m3u8 + .ts)
- Génération image couverture par défaut
3. Suppression fichier original (économie stockage)
4. Notification créateur : "Contenu prêt à publier"
Temps d'encodage estimé : - Contenu 5 min → ~30 secondes - Podcast 1h → ~5 minutes - Podcast 4h → ~20 minutes
Profils Opus générés :
| Qualité | Bitrate | Usage |
|---|---|---|
| Basse | 24 kbps | 2G/Edge |
| Standard | 48 kbps | 3G (défaut) |
| Haute | 64 kbps | 4G/5G |
Écoute accélérée :
| Vitesse | Usage |
|---|---|
| 0.75x | Compréhension difficile (accent, technique) |
| 1.0x | Normal (défaut) |
| 1.25x | Gain léger |
| 1.5x | Podcasts longs |
| 2.0x | Survol rapide (modérateurs) |
Disponible pour : - ✅ Modérateurs (validation rapide : 30s → 15s à 2x) - ✅ Auditeurs (tous les contenus) - ✅ Standard industrie (YouTube, Spotify, Apple Podcasts)
Justification : - Simplicité : 2 formats couvrent 95% des cas d'usage - Coût optimisé : pas de conversion WAV/FLAC lourds - Stockage réduit : suppression original après encodage - Scalabilité : workers horizontalement (Kubernetes jobs) - Productivité : écoute accélérée = double productivité modération
Décision : Minimaliste pour réduire friction
Champs obligatoires :
| Champ | Format | Validation |
|---|---|---|
| Titre | 5-100 caractères | Alphanumérique + ponctuation basique |
| Type géo | Enum | Ancré / Contextuel / Neutre |
| Zone diffusion | Composite | Voir détails ci-dessous |
| Tags | Enum | 1 à 3 parmi liste prédéfinie |
| Classification âge | Enum | Tout public / 13+ / 16+ / 18+ |
Zone de diffusion (obligatoire) :
Options mutuellement exclusives : - Point GPS : latitude + longitude + rayon (100m à 10km) - Ville : sélection dans référentiel INSEE - Département : sélection liste - Région : sélection liste - National : France entière
Tags disponibles (1 à 3 obligatoires) : - Automobile - Voyage - Famille - Amour - Musique - Économie - Cryptomonnaie - Politique - Culture générale - Sport - Technologie - Santé
Champs optionnels : - ❌ Description (ajout ultérieur) - ❌ Image couverture (génération auto)
Image de couverture par défaut :
Génération automatique selon règles : - Icône selon type géo : 📍 Ancré / 🌍 Contextuel / 🎧 Neutre - Couleur selon tag principal : bleu (Auto), vert (Voyage), rouge (Musique), etc. - Format 800×800px, PNG - Personnalisable ultérieurement (post-MVP)
Exemple de publication :
Titre : "Histoire de la Tour Eiffel"
Type géo : Ancré
Zone : Point GPS (48.8584, 2.2945, rayon 500m)
Tags : Voyage, Culture générale
Classification : Tout public
→ Image auto : 📍 fond bleu-vert (Voyage)
Justification : - Friction minimale : 5 champs max = 2 min de publication - Publication rapide : pas de blocage sur description/image - Coût 0 : pas de génération IA au MVP - Évolutif : champs optionnels ajoutables ultérieurement
Décision : Validation manuelle par équipe modération RoadWave
Processus nouveau créateur :
Critères de validation :
| Critère | Détails |
|---|---|
| Qualité audio | Compréhensible (pas de grésillement excessif) |
| Respect règles | Pas de contenu prohibé évident (haine, spam, illégal) |
| Classification âge | Cohérente avec contenu écouté |
| Tags pertinents | Correspondance minimale avec contenu |
| Zone diffusion | Cohérente (pas "Tour Eiffel" avec zone "National") |
Délai de validation : - Objectif : 24-48h (jours ouvrés) - Priorité : FIFO (First In First Out) - Weekend : délai peut atteindre 72h - Message au créateur : "Validation en cours, délai estimé 24-48h"
Notification créateur :
Si accepté : - Email + push : "✅ Votre contenu '[Titre]' est en ligne !" - Lien direct vers le contenu - Compteur : "2/3 contenus validés pour devenir créateur vérifié"
Si refusé : - Email + push : "❌ Contenu '[Titre]' refusé" - Raison détaillée : "Qualité audio insuffisante" / "Tags non pertinents" / "Classification incorrecte" / etc. - Lien vers règles de publication - Possibilité de correction + resoumission
Après 3 validations :
Créateur obtient statut "Vérifié" : - Badge ✓ visible sur profil - Contenus futurs publiés immédiatement (modération a posteriori uniquement) - Modération seulement si signalé par utilisateurs
Outils modérateur : - Écoute accélérée (1.5x ou 2x) = double productivité - Interface dédiée : queue de contenus à valider - Raccourcis clavier : A (Accepter), R (Rejeter), Espace (Pause) - Historique créateur visible (si déjà 1-2 contenus validés)
Modération communautaire (post-MVP) :
⚠️ Non implémenté au MVP (complexité juridique)
Vision future (envisageable) : - Créateurs établis peuvent opt-in "Modérateur communautaire" - Formation obligatoire (30 min) + quiz (80%) - Pré-validation uniquement (validation finale toujours par équipe RoadWave) - Compensation : badges, premium offert - Attribution aléatoire (pas de collusion)
Justification décision MVP : - Responsabilité juridique : plateforme reste responsable (DSA EU) - Qualité garantie : modérateurs formés et mandatés - Anti-spam efficace : bloque 95% des abus dès le début - Coût raisonnable : 30s × 3 contenus = 1.5 min/créateur - UX acceptable : délai 24-48h expliqué clairement - Pas de validation par pairs au MVP = évite risques juridiques (collusion, compétence, conflits)
Décision : Modification métadonnées uniquement, suppression immédiate
Modification autorisée :
| Élément | Modifiable | Justification |
|---|---|---|
| Titre | ✅ | Correction coquilles |
| Description | ✅ | Si ajoutée ultérieurement |
| Tags | ✅ | Ajustement pertinence |
| Image couverture | ✅ | Personnalisation |
| Audio | ❌ | Intégrité contenu |
| Zone diffusion | ❌ | Évite manipulation algo |
| Type géo | ❌ | Évite manipulation algo |
| Classification âge | ❌ | Sécurité mineurs |
Raisons restrictions :
Audio non modifiable : - Évite fraude : uploader contenu validé → remplacer par spam - Intégrité : auditeurs doivent écouter ce qui a été validé
Zone/Type non modifiables : - Évite manipulation : créer "Local Paris" → changer en "National" pour boost visibilité - Évite abus : créer "Neutre" (faible pondération géo) → changer en "Ancré" (forte pondération)
Classification non modifiable : - Évite contournement : uploader "Tout public" → passer en "18+" sans revalidation - Sécurité : garantit que classification a été vérifiée
Si besoin de changer audio/zone/classification : - Action : Supprimer contenu + republier - Si créateur <3 contenus validés : retourne en file validation - Si créateur ≥3 contenus validés : publication immédiate
Suppression de contenu :
| Aspect | Comportement |
|---|---|
| Délai | Immédiat |
| Réversibilité | Non |
| Historique auditeurs | Marqué "Contenu supprimé par créateur" |
| Analytics plateforme | Anonymisé et conservé |
| Fichiers CDN | Supprimés sous 24h |
Exemple scénario suppression :
Créateur supprime podcast écouté par 1000 personnes
→ CDN : fichiers purgés sous 24h
→ BDD : entrée marquée "deleted", auteur anonymisé
→ Historique auditeurs : "Contenu supprimé" (conserve durée écoute pour stats)
→ Analytics : métriques globales conservées (anonymes, RGPD OK)
Notifications suppression : - Pas de notification aux auditeurs (pour éviter effet Streisand) - Historique reste consultable : "Vous avez écouté ce contenu le [date]" - Si auditeur tente de réécouter : "Ce contenu n'est plus disponible"
Justification : - Simplicité : règles claires et non-ambiguës - Sécurité : évite manipulations algorithme et contournements modération - Contrôle créateur : liberté totale de supprimer (RGPD) - Traçabilité : historique conservé pour analytics (anonymisé) - Coût 0 : pas de revalidation métadonnées
Décision : Pré-calcul 5 contenus avec insertion prioritaire pour points géographiques
File d'attente : - 5 contenus pré-calculés en cache (Redis) - Recalcul automatique si : - Déplacement >10km - Toutes les 10 minutes (rafraîchissement contenu) - File d'attente <3 contenus restants
Insertion prioritaire géo-ancrée (mode voiture uniquement) :
Détection : - Calcul ETA (Estimated Time of Arrival) via API GPS native iOS/Android - Notification déclenchée 7 secondes avant d'arriver au point GPS - Si vitesse < 5 km/h ET distance < 50m → notification immédiate - ⚠️ App doit être ouverte (pas de détection en arrière-plan en mode voiture)
Notification : - Sonore uniquement : bip court ou son personnalisé RoadWave - Visuelle minimale : icône selon type de contenu (🏛️ culture, 👨👩👧 famille, 🎵 musique, etc.) - Compteur visible : 7...6...5...4...3...2...1 (décompte des secondes) - Pas de texte affiché (éviter distraction conducteur) - Pas de bouton "Annuler" : seul le bouton "Suivant" permet validation
Actions utilisateur : 1. User entend notification sonore + voit icône et compteur 2. User appuie "Suivant" dans les 7 secondes → décompte 5s démarre 3. Pendant décompte : contenu actuel continue, compteur visible (5...4...3...2...1) 4. Si contenu actuel se termine pendant décompte → contenu suivant du buffer démarre 5. À la fin du décompte → contenu géolocalisé démarre (fade out/in 0.3s)
Si user n'appuie pas sur "Suivant" : - Notification disparaît après 7 secondes - Contenu géolocalisé est perdu (pas d'insertion dans file) - Pas de nouveau contenu géolocalisé pendant 10 minutes (éviter spam)
Limitation anti-spam : - Maximum 6 contenus géolocalisés par heure - Timer reset toutes les heures (rolling window) - Exception : séquences d'un même audio-guide multi-séquences (comptent comme 1) - Si quota atteint : notifications suivantes ignorées jusqu'à libération du quota
Invalidation immédiate : - Utilisateur change ses préférences (curseurs géo/découverte/politique) - ⚠️ Modification bloquée si vitesse GPS >10 km/h (sécurité routière) - Live démarre d'un créateur suivi dans la zone
Implémentation :
Redis cache :
- Clé : user:{user_id}:queue
- Structure : [content_1, content_2, ..., content_5]
- Métadonnées : {last_lat, last_lon, computed_at, mode: "voiture"|"pieton"}
- TTL : 15 minutes
Tracking GPS temps réel (mobile) :
- Vérification toutes les 1 seconde
- Calcul ETA vers points géolocalisés proches (rayon 500m)
- Si ETA ≤ 7s → trigger notification
- Historique GPS : 30 derniers points pour calcul vitesse moyenne
Quota anti-spam (Redis) :
- Clé : user:{user_id}:geo_quota
- Structure : sorted set avec timestamps des 6 derniers contenus
- TTL : 1 heure
- Vérification avant notification : ZCOUNT pour compter contenus dernière heure
Cooldown après ignorance (Redis) :
- Clé : user:{user_id}:geo_cooldown
- TTL : 10 minutes
- Set après notification ignorée
Justification : - Expérience fluide : pas de latence au clic "Suivant" - Réactivité géo : contenu local inséré immédiatement - Coût optimisé : recalcul uniquement si nécessaire - Sécurité : pas de modification en conduite
Décision : Notifications push en arrière-plan avec rayon large
Contexte : - Mode piéton détecté automatiquement si vitesse moyenne < 5 km/h - Cas d'usage : visites à pied, musées, monuments, quartiers historiques - User n'a pas besoin d'avoir l'app ouverte - ⚠️ Fonctionnalité optionnelle : requiert permission "localisation en arrière-plan" (activée par user)
Détection : - App peut être en arrière-plan (si permission accordée) - Rayon de détection : 200 mètres autour du point GPS - Geofencing iOS/Android pour minimiser consommation batterie - Permission demandée uniquement si user active "Notifications audio-guides piéton" dans settings
Notification push système :
Format :
Titre : "Audio-guide à proximité"
Body : "[Nom du contenu] - [Nom créateur]"
Action : Tap → ouvre app sur le contenu
Exemple :
Audio-guide à proximité
Musée du Louvre : La Joconde - @paris_museum
Permissions requises :
⚠️ Important : Permission "Always Location" est optionnelle et demandée uniquement si user active le mode piéton dans settings.
iOS (Info.plist) :
<key>NSLocationWhenInUseUsageDescription</key>
<string>RoadWave utilise votre position pour vous proposer des contenus audio géolocalisés adaptés à votre trajet en temps réel.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Si vous activez les notifications audio-guides piéton, RoadWave peut vous alerter lorsque vous passez près d'un monument ou musée, même quand l'app est en arrière-plan. Cette fonctionnalité est optionnelle et peut être désactivée à tout moment dans les réglages.</string>
Android (AndroidManifest.xml) :
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
Disclosure avant demande permission (Android requis, iOS recommandé) :
Écran affiché avant demande permission "Always Location" :
┌────────────────────────────────────────┐
│ 📍 Notifications audio-guides piéton │
├────────────────────────────────────────┤
│ Pour vous alerter d'audio-guides à │
│ proximité même quand vous marchez avec │
│ l'app fermée, RoadWave a besoin de │
│ votre position en arrière-plan. │
│ │
│ Votre position sera utilisée pour : │
│ ✅ Détecter monuments à 200m │
│ ✅ Vous envoyer une notification │
│ │
│ Votre position ne sera jamais : │
│ ❌ Vendue à des tiers │
│ ❌ Utilisée pour de la publicité │
│ │
│ Cette fonctionnalité est optionnelle. │
│ Vous pouvez utiliser RoadWave sans │
│ cette permission. │
│ │
│ [Continuer] [Non merci] │
│ │
│ Plus d'infos : Politique confidentialité│
└────────────────────────────────────────┘
Si user refuse : - Mode piéton désactivé (uniquement mode voiture disponible) - App fonctionne normalement avec permission "When In Use" - Audio-guides accessibles en mode manuel (user ouvre app, sélectionne contenu)
Comportement après tap sur notification : 1. User tap notification push 2. App s'ouvre sur la page du contenu 3. User peut démarrer la lecture manuellement 4. Navigation libre (voir section 16.2 pour audio-guides piéton)
Basculement automatique voiture ↔ piéton :
Détection par vitesse GPS moyenne sur 30 secondes : - Vitesse < 5 km/h (stable 10s) → mode piéton - Vitesse ≥ 5 km/h (stable 10s) → mode voiture
Changements de mode :
| Mode actuel | Vitesse détectée | Nouveau mode | Effet |
|---|---|---|---|
| Piéton | ≥ 5 km/h | Voiture | Notifications push → sonores + icône (app ouverte requise) |
| Voiture | < 5 km/h | Piéton | Notifications sonores → push arrière-plan |
Pas de popup confirmation : - Basculement transparent et automatique - User n'a rien à faire - Hysteresis (10s) pour éviter basculements intempestifs
Quota anti-spam mode piéton : - Même limitation que mode voiture : 6 contenus/heure - Cooldown 10 min si notification ignorée (app pas ouverte après tap)
Justification : - ✅ Expérience adaptée aux visites à pied (rayon large, pas de timing précis) - ✅ Économie batterie (geofencing natif iOS/Android) - ✅ User peut garder téléphone en poche - ✅ Basculement automatique = pas de friction
Décision : Comportement smart selon progression écoute
Règles :
| Situation | Temps écouté | Action "Précédent" |
|---|---|---|
| Début de contenu | <10 secondes | Retour au contenu précédent (position exacte) |
| Milieu/fin | ≥10 secondes | Replay contenu actuel depuis le début |
| Premier de session | N/A | Replay depuis début (rien avant) |
Historique de navigation :
- 10 contenus maximum en mémoire (Redis List)
- Structure : [{content_id, position_seconds, listened_at}, ...]
- FIFO : au-delà de 10, suppression du plus ancien
Exemple scénario :
Utilisateur écoute :
1. Contenu A → écoute 5s → "Suivant"
2. Contenu B → écoute 2min30 → "Suivant"
3. Contenu C → écoute 5s → "Précédent"
→ Retour Contenu B à 2min30 (car >10s)
4. Sur Contenu B → "Précédent"
→ Retour Contenu A à 5s (position exacte)
Interface (responsabilité front) : - ❌ Pas de message UI - ✅ Progress bar revient au début ou à position exacte - ✅ Animation fluide (transition 0.3s)
Justification : - UX intuitive : comportement standard Spotify/YouTube - Pas de frustration : si début, vraiment revenir en arrière - Simplicité : règle unique (seuil 10s)
⚠️ Architecture Decision Record : Voir ADR-010 pour les détails techniques complets
Décision : Like automatique basé sur le temps d'écoute
Problème technique identifié : - iOS et Android ne supportent pas nativement les appuis longs ou doubles-appuis sur les commandes média - Les commandes physiques au volant varient selon les véhicules (pas de bouton "Pause" dédié sur beaucoup de modèles) - Système de double-appui/appui long = non-intuitif et risques sécurité (regarder écran pour feedback)
Actions disponibles (100% compatibles tous véhicules) :
| Commande physique | Action RoadWave |
|---|---|
| Suivant | Passer au contenu suivant |
| Précédent | Revenir au contenu précédent (règle 10s, voir section 5.2) |
| Play/Pause | Pause/reprise lecture (fade out 0.3s) |
Aucune action complexe au volant → Sécurité routière maximale.
Principe : Le système détecte automatiquement l'intérêt utilisateur selon le temps d'écoute.
Règles d'attribution :
| Durée écoutée | Action automatique | Points jauge | Justification |
|---|---|---|---|
| ≥ 80% du contenu | Like renforcé | +2.0 | Écoute quasi-complète = fort intérêt |
| 30-79% du contenu | Like standard | +1.0 | Écoute significative = intérêt |
| < 30% du contenu | Pas de like | 0 | Écoute trop courte |
| Skip après <10s | Signal négatif | -0.5 | Désintérêt marqué |
Exemples concrets :
Contenu de 3 minutes (180s) :
- Écoute 2min30 (83%) → Like renforcé (+2 points)
- Écoute 1min15 (42%) → Like standard (+1 point)
- Écoute 30s (17%) puis skip → Pas de like
- Skip après 5s → Signal négatif (-0.5 point)
Contenu de 15 minutes (900s) :
- Écoute 13min (87%) → Like renforcé (+2 points)
- Écoute 6min (40%) → Like standard (+1 point)
Interface mobile (véhicule arrêté uniquement) :
| Action | Moyen | Effet |
|---|---|---|
| Like explicite | Bouton cœur | +2 points jauge (même si déjà liké auto) |
| Unlike | Re-clic cœur (toggle) | -2 points jauge |
| Abonnement | Bouton "S'abonner" profil créateur | +5 points toutes jauges tags créateur |
| Désabonnement | Bouton "Se désabonner" | -5 points |
| Signalement | Menu contextuel "⋮" | Ouverture flux modération |
Feedback visuel : - Like automatique : Badge discret "♥ Ajouté à vos favoris" (2s, bas de l'écran) - Like explicite : Animation cœur rouge + vibration courte - Abonnement : Animation étoile dorée + badge "Abonné ✓"
Disponible uniquement avec : - Apple CarPlay (Siri) - Android Auto (Google Assistant) - ~30-40% du parc automobile EU (2026)
Exemples de commandes :
"Hey Siri, like ce podcast"
"OK Google, abonne-moi à ce créateur"
"Hey Siri, passe au contenu suivant"
"OK Google, signale ce contenu"
Implémentation : Intents iOS/Android personnalisés (Sprint 5, post-MVP)
Like automatique : - Like renforcé (≥80%) → +2% jauges de tous les tags du contenu - Like standard (30-79%) → +1% jauges des tags du contenu - Signal négatif (skip <10s) → -0.5% jauges des tags du contenu
Actions explicites : - Like manuel → +2% jauges (cumulable avec like auto) - Unlike → -2% jauges - Abonnement → +5% toutes jauges tags créateur - Désabonnement → -5% toutes jauges
Persistance :
- Événements stockés en base (table listen_events)
- Mise à jour jauges : immédiate (Redis) + async batch (PostgreSQL)
Backend (Go) :
type ListenEvent struct {
UserID string
ContentID string
StartedAt time.Time
StoppedAt time.Time
Duration int // secondes écoutées
ContentTotal int // durée totale contenu
Percentage float64 // duration / contentTotal * 100
Action string // "completed", "skipped", "paused"
}
func ProcessListenEvent(event ListenEvent) {
percentage := event.Percentage
// Signal négatif fort
if event.Action == "skipped" && event.Duration < 10 {
UpdateJauges(event.UserID, event.ContentID, -0.5)
return
}
// Like automatique
if percentage >= 80 {
AutoLike(event.UserID, event.ContentID, 2.0) // Renforcé
} else if percentage >= 30 {
AutoLike(event.UserID, event.ContentID, 1.0) // Standard
}
// < 30% : pas de like
}
Mobile (iOS/Android) :
// iOS - Tracking écoute
class AudioPlayerManager {
var startTime: Date?
let contentDuration: TimeInterval
func onPlay() {
startTime = Date()
}
func onStop(action: String) { // "completed" | "skipped" | "paused"
guard let start = startTime else { return }
let duration = Date().timeIntervalSince(start)
let percentage = (duration / contentDuration) * 100
// API call
API.track(ListenEvent(
contentId: currentContentId,
duration: Int(duration),
percentage: percentage,
action: action
))
}
}
Avantages : - ✅ Sécurité routière maximale : aucune action complexe au volant - ✅ UX intuitive : comportement standard industrie (Spotify, YouTube Music, Deezer) - ✅ Compatibilité 100% : fonctionne sur tous véhicules, tous OS - ✅ Engagement amélioré : tous les contenus écoutés génèrent des signaux - ✅ Algorithme plus précis : données granulaires (30%, 50%, 80%, 100%) - ✅ Simplicité développement : pas de workarounds complexes iOS/Android
Inconvénients mitigés : - ⚠️ Pas de like explicite en conduite → Mitigation : like automatique + vocal (CarPlay/Android Auto) - ⚠️ Pas d'abonnement en conduite → Mitigation : liste "Créateurs à découvrir" dans app - ⚠️ Like automatique peut surprendre → Mitigation : onboarding clair + unlike possible
Écran onboarding 1 :
🚗 Conduite sécurisée
RoadWave détecte automatiquement vos goûts
selon vos écoutes.
Plus vous écoutez longtemps, plus
l'algorithme s'améliore !
[Suivant]
Écran onboarding 2 :
❤️ Likes automatiques
Pas besoin de liker manuellement :
si vous écoutez >50% d'un contenu,
on comprend que vous aimez !
[Suivant]
Écran onboarding 3 :
⏸️ Commandes simples
Utilisez les boutons au volant :
• Suivant → Prochain contenu
• Précédent → Contenu d'avant
• Pause → Mettre en pause
[Commencer]
Décision : Passage automatique après 2s + insertion pub paramétrable
Fin de contenu : 1. Audio termine → Timer 2 secondes démarre 2. UI overlay : "Contenu suivant dans 2s..." + barre décompte 3. Possibilité annuler : bouton "Rester sur ce contenu" (optionnel) 4. Timer atteint 0 → passage automatique au contenu suivant
Délai selon contexte :
| Mode | Délai | Justification |
|---|---|---|
| Standard | 2 secondes | Temps réaction confortable |
| Mode Kids | 1 seconde | Attention courte enfants |
| Live | 0 seconde | Enchaînement immédiat |
Insertion publicité : - Pub s'insère pendant le délai de 2s (transition naturelle) - Fréquence : paramétrable admin (défaut : 1 pub / 5 contenus) - Message : "Publicité (15s)" puis lecture pub - ⚠️ Jamais d'interruption d'un contenu en cours
Publicité skippable : - Durée minimale visionnage : paramétrable (défaut : 5 secondes) - Bouton "Passer" apparaît après délai - Métriques engagement : taux skip, durée écoute moyenne - Like et abonnement autorisés sur pub (engagement créateur pub)
Si aucun contenu disponible : 1. Message : "Aucun contenu disponible dans cette zone" 2. Proposition : "Élargir la zone de recherche ?" (bouton) 3. Si accepté → relance algo avec rayon +50km 4. Sinon → lecture en pause, attente action utilisateur
Gestion erreurs : - Échec chargement contenu suivant → retry 3× avec backoff exponentiel - Si 3 échecs → message "Connexion instable, basculement mode offline" - Mode offline → lecture contenus téléchargés uniquement
Justification : - Fluidité : enchaînement naturel sans action utilisateur - Contrôle : possibilité annuler pendant délai - Paramétrabilité pub : évite frustration excès publicité - Engagement pub : like/abonnement autorisé = monétisation créateurs pub
Décision : Interface self-service avec maîtrise budget et métriques détaillées
Fonctionnalités publicitaire :
Paramètres configurables :
| Paramètre | Options | Justification |
|---|---|---|
| Budget total | Montant libre (min 50€) | Maîtrise coût total |
| Durée campagne | Date début/fin + étalement | Ex: 300€ sur 2 semaines |
| Ciblage géographique | Point GPS / Ville / Département / Région / National | Précision selon besoin |
| Ciblage horaire | Plages horaires (ex: 7h-9h, 17h-19h) | Optimisation trajet domicile-travail |
| Centres d'intérêt | Tags (ex: Automobile, Voyage) | Ciblage thématique |
| Tranche d'âge | Tout public / 13+ / 16+ / 18+ | Respect classifications |
Étalement budget :
Exemple campagne :
- Budget : 300€
- Durée : 14 jours
- Zone : Département du Var
- Horaires : 7h-9h + 17h-19h (rush)
Calcul automatique :
→ Budget/jour = 300€ / 14 = 21.43€/jour
→ Diffusions/jour estimées : ~430 (0.05€/écoute)
→ Alerte si budget épuisé avant fin (réajustement possible)
Mode de paiement : - ✅ Prépaiement obligatoire (évite impayés) - ✅ Carte bancaire uniquement (Mangopay) - ✅ Recharge automatique optionnelle (si budget <10%)
Processus : 1. Publicitaire upload audio pub (formats : MP3, AAC) 2. Validation manuelle obligatoire (modérateur RoadWave) - Délai : 24-48h ouvrées - Critères : respect réglementation, qualité audio, classification correcte 3. Si accepté → campagne démarre à la date choisie 4. Si refusé → email avec raison + remboursement automatique
Contenus interdits en pub : - ❌ Alcool, tabac (réglementation française) - ❌ Jeux d'argent - ❌ Contenu politique (pendant campagnes électorales) - ❌ Contenu sexuel ou violence - ✅ Tous commerces/services légaux
Indicateurs temps réel :
| Métrique | Description | Utilité |
|---|---|---|
| Impressions | Nombre de diffusions | Volume exposition |
| Écoutes complètes | Pub écoutée >80% | Engagement réel |
| Taux de skip | % skip après délai min | Qualité contenu |
| Durée moyenne écoute | Secondes écoutées | Rétention attention |
| Likes | Nombre de likes | Appréciation contenu |
| Abonnements | Abonnements au créateur pub | Conversion forte |
| Coût par écoute | Budget / écoutes complètes | ROI campagne |
| Répartition géographique | Heatmap diffusions | Validation ciblage |
| Répartition horaire | Graphique par heure | Optimisation horaires |
Métriques engagement avancées : - Taux complétion par tranche d'âge : identifier audience réceptive - Carte de chaleur GPS : visualiser zones forte écoute - Comparatif campagnes : A/B testing créatifs publicitaires
Export données : - ✅ CSV/Excel pour analyse externe - ✅ Graphiques interactifs (Chart.js) - ✅ Rapport PDF automatique fin de campagne
Suivi temps réel : - Dashboard : Budget restant, % consommé, jours restants - Projection : "À ce rythme, budget épuisé dans X jours" - Alerte email/push si : - Budget consommé à 80% - Budget consommé à 90% - Budget épuisé - Campagne terminée (rapport final)
Ajustements en cours : - ✅ Pause campagne (budget conservé) - ✅ Prolonger campagne (recharge budget) - ✅ Modifier ciblage horaire/géo (si <50% budget consommé) - ❌ Modifier audio (nécessite nouvelle validation)
Optionnel future : - Enchère au CPM (coût pour 1000 impressions) - Priorité selon prix : pub prix élevé → diffusion privilégiée - Floor price : 2€ CPM minimum - Évite surcharge pub : max 1 pub / 5 contenus stricte
Justification décision MVP : - Tarif fixe simple : 0.05€/écoute complète - Pas de complexité enchères immédiatement - Scalable : passage enchères ultérieur si demande forte
Décision : Paramétrable admin + respect expérience utilisateur
Fréquence d'insertion : - Défaut : 1 pub / 5 contenus (utilisateurs gratuits) - Paramétrable admin : curseur 1/3 à 1/10 - Utilisateurs Premium : 0 pub (modèle sans publicité)
Règles strictes : - ⚠️ Jamais d'interruption contenu en cours - Pub s'insère uniquement entre deux contenus (pendant délai 2s) - Rotation : même pub max 3 fois/jour par utilisateur (évite saturation) - Limite : max 6 pubs/heure par utilisateur (évite spam)
Ciblage intelligent : - Géolocalisation prioritaire (point GPS > ville > département > région > national) - Centres d'intérêt secondaires (tags utilisateur) - Horaire (campagne 7h-9h → diffusion uniquement pendant plage)
Volume audio normalisé : - Pub normalisée à -14 LUFS (standard broadcast) - Évite effet "pub trop forte" (frustration utilisateur) - Validation automatique via FFmpeg lors encodage
Durée : - Minimum : 10 secondes - Maximum : 60 secondes - Recommandé : 15-30 secondes (sweet spot engagement)
Skippable : - Délai minimum obligatoire : 5 secondes (paramétrable admin : 3-10s) - Bouton "Passer la publicité" apparaît après délai - Durée minimale comptabilisée pour facturation
Facturation : - Écoute complète (>80%) : 0.05€ facturé publicitaire - Skip après délai min : 0.02€ (exposition partielle) - Skip immédiat (<5s) : 0€ (pas d'engagement)
Justification modèle tarif : - Incitatif qualité : pub engageante = coût réduit - Équitable : publicitaire paie pour attention réelle - Transparent : dashboard montre écoutes complètes vs skips
Décision : Buffer 15s + notification abonnés + limite 8h
Processus de démarrage :
Notification abonnés : - ✅ Push notification immédiate à tous les abonnés dans la zone géographique - Message : "🔴 [Nom créateur] est en direct : [Titre live]" - Tap notification → ouverture app + lecture live immédiate - Filtrage géographique : si abonné hors zone, pas de notif (évite frustration)
Limite de durée : - Maximum 8 heures par session live - Warning créateur à 7h30 : "Votre live se terminera dans 30 min" - Si besoin continuer → arrêt + redémarrage nouveau live (évite abus ressources serveur)
Métadonnées obligatoires :
| Champ | Format | Validation |
|---|---|---|
| Titre | 5-100 caractères | Ex: "Discussion politique en direct" |
| Tags | 1-3 centres d'intérêt | Sélection liste prédéfinie |
| Classification âge | Enum | Tout public / 13+ / 16+ / 18+ |
| Zone diffusion | Geo | Ville / Département / Région / National |
Contenus interdits en live :
| Type | Description | Sanction |
|---|---|---|
| Concert/spectacle | Diffusion concert en direct depuis la salle | Strike 2 immédiat + ban temporaire |
| Événement sportif payant | Match, compétition avec droits TV | Strike 2 immédiat |
| Œuvre protégée | Film, série, musique en fond sans droits | Strike 1 + suppression live |
| Contenu violent | Agression, violence physique | Ban immédiat |
| Contenu illégal | Apologie terrorisme, pédopornographie | Ban définitif + signalement autorités |
Exemple usecase interdit :
❌ Utilisateur dans salle de concert diffuse live performance
→ Violation droits d'auteur + droits de diffusion
→ Détection : modération réactive (signalements) + IA audio fingerprint
→ Sanction : Strike 2 (suspension 7 jours) + suppression live + suppression replay
Détection violations : - Signalement utilisateurs : bouton "Signaler" accessible pendant live - IA audio fingerprint : détection musique protégée en arrière-plan (post-MVP) - Modération réactive : modérateurs peuvent écouter lives signalés en temps réel - Coupure immédiate : modérateur peut arrêter live si contenu illégal évident
Justification : - Buffer 15s : équilibre entre test qualité et friction minimale - Notification abonnés : engagement maximal, valeur ajoutée live - 8h max : couvre 99% cas usage (podcasts longs, émissions radio) sans abus - Interdictions strictes : protection juridique plateforme (DSA EU, droits d'auteur) - Coût : WebRTC ingestion + HLS distribution (réutilise infra existante)
Décision : Compte à rebours 5s + tolérance déconnexion 60s + enregistrement auto
Fin manuelle créateur :
Fin automatique si déconnexion :
| Durée coupure | Comportement |
|---|---|
| <60 secondes | Message auditeurs : "Connexion créateur perdue, reconnexion en cours..." |
| ≥60 secondes | Arrêt automatique live + message : "Le live est terminé suite à une coupure de connexion" |
Enregistrement automatique :
✅ Obligatoire et automatique (valeur ajoutée énorme)
Processus : 1. Pendant live : enregistrement continu serveur (format Opus raw) 2. Fin live → job asynchrone (worker Go + FFmpeg) : - Conversion MP3 256 kbps (qualité optimale) - Génération segments HLS (comme contenu classique) - Normalisation volume -14 LUFS - Détection silences prolongés (nettoyage) 3. Publication automatique du replay : - Titre : "[REPLAY] [Titre live original]" - Même zone diffusion, tags, classification - Disponible sous 5-10 minutes après fin live - Type géo : automatiquement "Géo-neutre" (replay = contenu pérenne)
Options créateur :
| Option | Défaut | Description |
|---|---|---|
| Publier replay automatiquement | ✅ OUI | Désactivable avant démarrage live |
| Supprimer replay après coup | ✅ Possible | Suppression standard contenu |
| Modifier replay | ❌ Non | Intégrité enregistrement |
Conservation fichier source : - Opus raw conservé 7 jours après fin live (backup) - Suppression automatique après 7j (économie stockage) - Si replay supprimé par créateur → fichier raw supprimé immédiatement
Justification : - Compte à rebours 5s : outro propre, pas de coupure brutale - Tolérance 60s : évite arrêts intempestifs (tunnel, changement cellule) - Enregistrement auto : valorisation contenu éphémère, génération contenu pérenne - MP3 256 kbps : qualité optimale pour replay (vs 48 kbps live) - Coût : stockage minimal (Opus → MP3 1× par live, puis suppression raw après 7j)
Décision : Buffer 15s + continuation hors zone + reconnexion au live actuel + écoute passive uniquement
Buffer de synchronisation :
Comparaison buffers :
| Buffer | Avantages | Inconvénients | Décision |
|---|---|---|---|
| 5s | Quasi temps réel | Instable 3G, coupures fréquentes | ❌ |
| 10s | Bon compromis | Légèrement juste pour 3G | ❌ |
| 15s | Stabilité optimale 3G/4G | Léger décalage acceptable | ✅ |
| 20s+ | Très stable | Décalage trop perceptible | ❌ |
Zone géographique pendant live :
Reconnexion après coupure réseau :
| Durée coupure | Comportement |
|---|---|
| <90 secondes | Reprend au live actuel (pas au buffer ancien) + saut temporel transparent |
| ≥90 secondes | Message : "Live en cours perdu, passage au contenu suivant" + algo propose contenu normal |
Interactions disponibles :
Décision ferme : ❌ Aucun chat en direct, ni maintenant ni dans le futur
Raisons : - Sécurité routière : pas de distraction en voiture (focus UX) - Harcèlement : évite contenu haineux, insultes, trolling - Modération : pas de coût modération temps réel (impossible à scale) - Simplicité : écoute passive = expérience uniforme
Actions autorisées pendant live :
| Action | Disponible | Effet |
|---|---|---|
| Like | ✅ | Bouton cœur interface mobile (véhicule arrêté) |
| Abonnement créateur | ✅ | Bouton profil créateur (interface mobile) |
| Skip | ✅ | Passe au contenu suivant, sort du live |
| Précédent | ❌ | Pas de sens sur live (flux temps réel) |
| Chat | ❌ | Jamais implémenté (décision définitive) |
| Réactions emoji | ❌ | Jamais implémenté (décision définitive) |
Messages utilisateur : - "💬 Les discussions ne sont pas disponibles sur RoadWave pour garantir votre sécurité en voiture et éviter le harcèlement."
Justification décision définitive : - UX cohérente : RoadWave = écoute en conduisant, pas réseau social interactif - Bien-être : évite toxicité, harcèlement, haine (fléau réseaux sociaux) - Juridique : pas de risque contentieux modération chat (DSA EU) - Coût : 0€ infra chat, 0€ modération temps réel - Différenciation : positionnement "audio safe" vs plateformes toxiques
Stack :
Créateur (App mobile)
↓ WebRTC (OPUS 48 kbps)
Serveur Ingestion (Go + Pion WebRTC)
↓ Conversion temps réel
Serveur HLS (segments .ts)
↓ CDN (Bunny)
Auditeurs (App mobile, HLS natif)
Flux détaillé : 1. Créateur → WebRTC OPUS 48 kbps vers serveur Go 2. Serveur Go → Conversion temps réel OPUS → segments HLS (.m3u8 + .ts) 3. Bunny CDN → Distribution HLS avec cache 4. Auditeurs → Lecture HLS native iOS/Android (buffer 15s) 5. Enregistrement parallèle → Opus raw stocké temporairement 6. Post-live → Job async : Opus → MP3 256 kbps → Publication replay
Dépendances : - ✅ Pion WebRTC (Go library, open source, MIT license) - ✅ FFmpeg (conversion audio, LGPL/GPL) - ✅ Bunny CDN (distribution HLS, pas Google/Cloudflare) - ✅ PostgreSQL + Redis (métadonnées live + cache)
Avantages : - ✅ Pas de dépendance Google/Facebook/Cloudflare (souveraineté) - ✅ WebRTC standard ouvert (Pion = lib Go pure) - ✅ Réutilise infra HLS existante (pas de doublon) - ✅ CDN cache les segments (coût réduit) - ✅ Scalable horizontalement (workers Go)
Coût estimé :
| Phase | Utilisateurs | Infra live | Coût/mois |
|---|---|---|---|
| MVP | 0-100K | 1 instance Go (ingestion 100 lives simultanés) | +50€ (serveur) + bande passante CDN |
| Growth | 100K-1M | 3-5 instances Go (500 lives simultanés) | +200€ + bande passante |
| Scale | 1M-10M | Kubernetes auto-scale (2000+ lives) | +1K€ + bande passante |
Bande passante : - Live : 48 kbps × nb_auditeurs (via CDN, cache segments) - Exemple : 100 auditeurs = 4.8 Mbps = ~2 Go/heure via CDN - Coût Bunny : ~0.01€/GB = 0.02€/heure pour 100 auditeurs
Décision : Boost +30% au score + reste dans le mix
Boost de score abonnements : - +30% au score final pour contenus d'un créateur suivi - Application : multiplicateur sur le score calculé
score_final_avec_boost = score_final × 1.3
Reste dans le mix : - ❌ Pas de priorité absolue (pas de file dédiée abonnements) - ✅ Contenu suivi entre en compétition avec autres contenus - ✅ Si créateur suivi publie contenu faible engagement → peut être battu par contenu viral non-suivi
Exemple concret :
Utilisateur à Paris, 2 contenus disponibles :
Contenu A (créateur NON suivi) :
- Score géo : 0.9 (très proche)
- Score intérêts : 0.8
- Score engagement : 0.7
→ Score final : 0.80
Contenu B (créateur suivi) :
- Score géo : 0.5 (moyennement proche)
- Score intérêts : 0.6
- Score engagement : 0.5
→ Score final : 0.53
→ Score avec boost : 0.53 × 1.3 = 0.69
→ Contenu A proposé en premier (0.80 > 0.69)
Cas où abonnement fait la différence :
Contenu A (non suivi) : score 0.70
Contenu B (suivi) : score 0.60 → avec boost 0.78
→ Contenu B proposé (boost fait pencher la balance)
Justification : - Équilibre : valorise abonnements sans enfermer utilisateur - Découverte : contenus viraux/locaux peuvent toujours émerger - Prévisible : boost fixe, pas de logique opaque - Coût 0 : multiplicateur simple dans l'algo
Décision : Push adapté selon contexte (voiture vs à pied) + limite 10/jour
Détection contexte utilisateur :
| Contexte | Détection | Comportement |
|---|---|---|
| En voiture | Vitesse GPS >10 km/h | Notifications silencieuses (in-app uniquement) + commandes volant |
| À pied | Vitesse GPS <5 km/h | Notifications push actives + interface tactile/vocale |
Notifications activées :
| Événement | Notification | Comportement |
|---|---|---|
| Nouveau contenu créateur suivi | In-app uniquement | Badge compteur, pas de push (sécurité) |
| Live créateur suivi | In-app uniquement | Badge compteur, pas de push |
| Point d'intérêt proche | Audio notification | Bip + annonce vocale : "Audio-guide disponible" |
| Événement | Notification | Comportement |
|---|---|---|
| Nouveau contenu créateur suivi | ✅ Push | Si utilisateur dans zone géo du contenu |
| Live créateur suivi | ✅ Push | Si utilisateur dans zone géo |
| Audio-guide disponible | ✅ Push | "📍 Audio-guide disponible : [Lieu]" |
| Séquence suivante suggérée | Audio notification | Annonce vocale : "Pièce suivante disponible" |
Format notifications :
Nouveau contenu :
🎧 [Nom créateur] a publié : "[Titre contenu]"
Tap pour écouter
Live en direct :
🔴 [Nom créateur] est en direct : "[Titre live]"
Tap pour rejoindre
Audio-guide à pied :
📍 Audio-guide disponible : [Nom du lieu]
Choisissez parmi 3 guides pour [Musée du Louvre]
Tap pour explorer
Filtrage géographique : - Si contenu/live hors zone utilisateur → pas de notification - Évite frustration : "notification pour contenu que je ne peux pas écouter" - Exception : contenu national → notifie tous les abonnés
Fréquence maximale : - Maximum 10 notifications push/jour par utilisateur (tous types confondus) - Si dépassement : notifications regroupées - Message groupé : "🎧 3 nouveaux contenus de créateurs suivis"
Plages horaires : - Mode silencieux : 22h-8h (pas de push, sauf live) - Paramétrable utilisateur (désactivation totale possible) - Option "Notifications importantes uniquement" (lives uniquement)
Gestion préférences :
| Préférence | Défaut | Description |
|---|---|---|
| Nouveaux contenus | ✅ Activé | Push à chaque nouveau contenu (à pied uniquement) |
| Lives | ✅ Activé | Push au démarrage live (à pied uniquement) |
| Audio-guides proximité | ✅ Activé | Push quand audio-guide détecté à <100m |
| Mode silencieux | ✅ Activé (22h-8h) | Pas de push nocturne |
| Limite quotidienne | 10 | Modifiable 5-20 |
Justification : - Sécurité routière : pas de push en conduite (distraction) - Engagement piéton : push actifs pour audio-guides (valeur ajoutée tourisme) - Pas de spam : limite 10/jour + mode silencieux - Filtrage géo : pertinence maximale (pas de notif inutiles) - Coût : Firebase Cloud Messaging (gratuit jusqu'à volume élevé)
Décision : Navigation manuelle multiséquence + choix parmi plusieurs guides
Fonctionnement :
Affichage :
📍 Musée du Louvre
Choisissez votre guide :
┌─────────────────────────────────┐
│ 🎨 Visite complète (45 min) │
│ Par [Créateur A] • 12 séquences│
│ ⭐ 4.8 • 1.2K écoutes │
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ 🏛️ Œuvres majeures (20 min) │
│ Par [Créateur B] • 5 séquences │
│ ⭐ 4.9 • 3.5K écoutes │
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ 👶 Visite famille (30 min) │
│ Par [Créateur C] • 8 séquences │
│ ⭐ 4.7 • 850 écoutes │
└─────────────────────────────────┘
Après sélection :
🎨 Visite complète • Musée du Louvre
Piste actuelle : 2/12
"La Joconde - Histoire et mystères"
[████████────────────] 3:24 / 6:50
Liste des séquences :
✅ 1. Introduction et architecture
▶️ 2. La Joconde - Histoire et mystères
⏸️ 3. Vénus de Milo
⏸️ 4. Victoire de Samothrace
⏸️ 5. Peintures Renaissance
...
⏸️ 12. Conclusion et boutique
Navigation :
| Action | Geste | Effet |
|---|---|---|
| Séquence suivante | Tap "Suivant" ou commande vocale "Suivant" | Passe à séquence N+1 |
| Séquence précédente | Tap "Précédent" ou commande vocale "Précédent" | Revient à séquence N-1 |
| Saut direct | Tap séquence dans liste | Lecture séquence choisie |
| Pause | Tap bouton pause | Met en pause, reprise position exacte |
| Quitter | Tap "×" | Sauvegarde progression, sortie guide |
Guidage vocal automatique : - Entre 2 séquences : "Vous avez terminé la séquence 2. Dirigez-vous vers la Vénus de Milo pour la séquence 3." - Si utilisateur s'éloigne (>50m de la prochaine pièce) : "Vous vous éloignez de la prochaine étape. Consultez le plan."
Sauvegarde progression : - Position dans guide sauvegardée automatiquement - Retour ultérieur : "Reprendre à la séquence 5 ?" ou "Recommencer depuis le début" - Historique : guide marqué "Terminé" si toutes séquences écoutées
Création audio-guide multiséquence :
Processus créateur : 1. Créateur upload plusieurs fichiers audio (1 par séquence) 2. Numérote les séquences : "Séquence 1", "Séquence 2", etc. 3. Titre chaque séquence : "Introduction", "La Joconde", etc. 4. Définit point GPS unique pour tout le guide (centre du lieu) 5. Métadonnées : durée totale calculée automatiquement
Format stockage :
{
"guide_id": "abc123",
"title": "Visite complète Musée du Louvre",
"location": {"lat": 48.8606, "lon": 2.3376, "radius": 200},
"sequences": [
{
"sequence_number": 1,
"title": "Introduction et architecture",
"audio_url": "https://cdn.../seq1.mp3",
"duration_seconds": 180
},
{
"sequence_number": 2,
"title": "La Joconde - Histoire et mystères",
"audio_url": "https://cdn.../seq2.mp3",
"duration_seconds": 410
},
...
],
"total_duration_seconds": 2700,
"creator_id": "creator_xyz"
}
Justification : - UX piéton : navigation tactile adaptée (pas de commandes volant) - Autonomie : utilisateur maître de son rythme (pas d'enchaînement forcé) - Choix : plusieurs guides = diversité styles (famille, expert, rapide) - Engagement : sauvegarde progression = incitation terminer - Coût : réutilise infra contenu standard (juste métadonnées séquences)
Décision : 200 abonnements max + désabonnement -5% jauges
Nombre maximum d'abonnements : - 200 créateurs maximum par utilisateur - Raisons : - Évite spam : au-delà de 200, notifications ingérables - Usage réaliste : 200 créateurs = déjà énorme (vs 100-150 sur YouTube/Twitter) - Performance : requêtes SQL optimisées (index sur 200 max)
Si limite atteinte : - Message : "Vous suivez déjà 200 créateurs. Désabonnez-vous d'un créateur pour en suivre un nouveau." - Liste triable : par date abonnement, nb contenus écoutés, dernière activité - Suggestion : "Vous n'avez pas écouté [Créateur X] depuis 6 mois, le désabonner ?"
Abonnement initial : - Impact : +5% toutes jauges tags du créateur (défini en ADR-010) - Action : Bouton "S'abonner" dans profil créateur (interface mobile) - Immédiat à l'action
Désabonnement : - Impact : -5% toutes jauges tags du créateur (symétrique) - Action : Bouton "Se désabonner" dans profil créateur - Immédiat à l'action - Pas de confirmation (action réversible)
Exemple :
Créateur tague ses contenus : Automobile, Voyage
Abonnement :
→ Jauge Automobile : 60% → 65% (+5%)
→ Jauge Voyage : 55% → 60% (+5%)
3 mois plus tard, désabonnement :
→ Jauge Automobile : 65% → 60% (-5%)
→ Jauge Voyage : 60% → 55% (-5%)
Gestion multi-tags : - Si créateur a 3 tags → +5% sur chacun des 3 tags - Logique : abonnement = signal fort d'affinité à TOUS les sujets du créateur
Abonnements réciproques : - ❌ Pas d'abonnement mutuel visible - Créateur ne voit pas qui est abonné (privacy) - Créateur voit uniquement : nombre total abonnés (métrique globale)
Justification : - Limite 200 : équilibre entre liberté et gestion spam - Symétrie +5%/-5% : cohérence mathématique, prévisibilité - Privacy : pas de liste publique abonnés (évite stalking) - Coût : table abonnements PostgreSQL standard
Décision : ❌ Fonctionnalité abandonnée pour le MVP
Raisons : - Complexité juridique (collecte pour compte de tiers, TVA variable) - Frais de transaction élevés sur petits montants (Mangopay ~1.8% + 0.18€) - UX additionnelle à développer (wallet, transactions, confirmations) - Charge comptable importante pour la plateforme
Post-MVP : Possible réintégration avec crypto (Bitcoin/Lightning Network) si législation UE l'autorise clairement (régulation MiCA en cours).
Décision : 5 critères cumulatifs obligatoires
| Critère | Seuil | Justification |
|---|---|---|
| Ancienneté | Compte créé depuis ≥ 3 mois | Anti-fraude : temps de détecter comportements suspects |
| Popularité | ≥ 500 abonnés | Garantit audience réelle et engagée |
| Engagement | ≥ 10 000 écoutes complètes cumulées | Créateurs produisant du contenu de qualité |
| Fiabilité | Aucun strike actif, 0 contenu modéré dans les 6 derniers mois | Historique propre requis |
| Régularité | ≥ 5 contenus publiés dans les 90 derniers jours | Activité constante |
Vérification : Automatique via requêtes SQL lors de la demande d'activation
Affichage : - Bouton "Demander la monétisation" dans profil créateur - Si critères non remplis → affichage progression vers objectifs - Si critères remplis → redirection vers KYC Mangopay
Justification : - Anti-fraude : Le délai de 3 mois permet de détecter les comptes suspects - Qualité : Seuls les créateurs sérieux avec audience réelle sont monétisés - Coût administratif : Réduit le nombre de comptes à gérer (KYC, comptabilité, virements) - Légitimité : Audience organique prouvée
Décision : Statut juridique professionnel obligatoire
Statuts acceptés : - Auto-entrepreneur (micro-BNC pour artistes/créateurs de contenu) - SARL/SAS/SASU (sociétés)
Documents requis :
| Document | Obligatoire | Format | Validité |
|---|---|---|---|
| SIRET | ✅ | 14 chiffres | Permanent |
| RIB professionnel | ✅ | IBAN FR | Permanent |
| Pièce d'identité | ✅ | CNI/Passeport | En cours de validité |
| Numéro TVA intracommunautaire | ⚠️ Si applicable | FR + 11 chiffres | Permanent |
| Kbis <3 mois | ⚠️ Si société | <3 mois |
Vérification : Via Mangopay (KYC intégré + vérification bancaire)
Délai : 24-72h si documents conformes
Rejet possible si : - Documents invalides/illisibles - Identité ne correspond pas au compte RoadWave - Liste noire anti-blanchiment (vérification automatique Mangopay) - RIB non professionnel (particulier)
Base légale : - Conformité fiscale : L'État français impose déclaration revenus >1200€/an (DAS2) - Anti-blanchiment : Directive EU 2018/843 (5ème directive LCB-FT) - RGPD : Données hébergées EU via Mangopay (conforme)
Justification : - Responsabilité légale : RoadWave doit pouvoir prouver identité réelle créateurs monétisés - Automatisation : Mangopay gère tout (KYC, vérifications, conformité, e-wallets) - KYC gratuit : inclus dans l'offre Mangopay (vs 1.20€ chez Stripe) - Souveraineté EU : Mangopay est européen (France/Luxembourg), régulé ACPR
Formule : 3€ / 1000 écoutes complètes (CPM créateur)
Répartition économique :
Publicité facturée par RoadWave : 0.05€/écoute complète = 50€ CPM
├─ Créateur touche : 3€ (6% du CA pub)
└─ Plateforme garde : 47€ (94%)
├─ CDN + infrastructure : ~10-15€
├─ Modération + support : ~5-10€
├─ Développement + R&D : ~10-15€
└─ Marge opérationnelle : ~10-15€
Exemple concret : - 10 000 écoutes/mois → créateur touche 30€ - 50 000 écoutes/mois → créateur touche 150€ - 100 000 écoutes/mois → créateur touche 300€
Comparaison industrie : - YouTube : 3-5€/1000 vues - Spotify : 3-4€/1000 écoutes - RoadWave : 3€/1000 écoutes (aligné)
Règles comptabilisation : - ✅ Écoute complète = ≥80% du contenu écouté - ✅ Utilisateur gratuit uniquement - ❌ Écoutes Premium ne comptent pas ici (autre système) - ❌ Bots détectés exclus (rate limiting + analyse patterns)
Formule : 70% au créateur, 30% à la plateforme
Répartition proportionnelle au temps d'écoute effectif :
Utilisateur Premium = 4.99€/mois
├─ 3.49€ reversés aux créateurs (70%)
└─ 1.50€ gardés par plateforme (30%)
Si l'utilisateur écoute 3 créateurs ce mois :
- Créateur A : 10h d'écoute (50%) → 1.75€
- Créateur B : 6h d'écoute (30%) → 1.05€
- Créateur C : 4h d'écoute (20%) → 0.70€
Calcul technique :
-- Pour chaque utilisateur Premium
SELECT
creator_id,
SUM(listen_duration_seconds) AS total_seconds,
(SUM(listen_duration_seconds) / total_user_seconds) AS ratio,
(4.99 * 0.70 * ratio) AS revenue_euros
FROM premium_listens
WHERE user_id = :user_id
AND month = :current_month
GROUP BY creator_id;
Comparaison industrie : - YouTube Premium : 70/30 - Spotify : 70/30 - Apple Music : 52/48 (moins avantageux) - RoadWave : 70/30 (standard)
Justification : - Standard industrie : ratio équitable éprouvé - Incitation qualité : créateurs les plus écoutés gagnent plus - Équité : pas de "winner takes all", chaque créateur écouté reçoit sa part - Marge plateforme : 30% couvre absence revenus pub sur Premium
Seuil minimum : 50€
Fréquence : Mensuelle
| Date | Action |
|---|---|
| Dernier jour du mois (ex: 31 janvier) | Calcul revenus du mois via SQL |
| 1-14 du mois suivant | Traitement contestations/fraudes éventuelles |
| 15 du mois suivant (ex: 15 février) | Virement SEPA via Mangopay (Payout) |
| 16-18 du mois suivant | Réception virement (1-3 jours ouvrés SEPA) |
Virement via Mangopay : - SEPA pour comptes EU (gratuit, 1-3 jours) - Virement international hors EU (frais variables selon pays, rare en pratique) - E-wallets automatiques : chaque créateur possède un wallet Mangopay où ses revenus sont transférés automatiquement
Tableau de bord créateur (temps réel) :
| Métrique | Description | Mise à jour |
|---|---|---|
| Revenus pub | Écoutes × CPM | Temps réel |
| Revenus premium | Abonnés actifs × ratio écoute | Temps réel |
| Solde disponible | Total revenus mois en cours | Temps réel |
| Solde en attente | Revenus mois précédent (paiement le 15) | Figé fin de mois |
| Historique virements | Liste des paiements reçus | Permanent |
| Export comptable CSV | Données pour expert-comptable | Téléchargement |
Gestion échecs virement : 1. Tentative 1 (15 du mois) → échec 2. Retry automatique J+3 3. Retry automatique J+7 4. Si 3 échecs → suspension monétisation + email créateur (RIB invalide)
Décision : Créateur décide individuellement pour chaque contenu
Fonctionnement : - Toggle "Réservé Premium" lors création/édition contenu - Aucune limite imposée : créateur peut mettre 0%, 50% ou 100% en premium - Badge 👑 visible sur interface utilisateur
Comportement utilisateurs gratuits : - Contenu premium visible dans liste/algo - Tentative lecture → overlay bloquant - Message : "Ce contenu est réservé aux abonnés Premium" - CTA : "Passez Premium pour 4.99€/mois"
Comportement algorithme : - Contenus premium inclus dans recommandations - Si user gratuit → contenu skippé automatiquement (ne consomme pas de slot) - Si user premium → diffusé normalement
Métadonnées :
- Champ is_premium (boolean) en base
- Index sur ce champ pour requêtes rapides
- Cache Redis : content:{id}:premium (TTL 1h)
Justification : - Liberté créateur : chaque créateur choisit sa stratégie (freemium, tout gratuit, tout premium) - Incitation Premium : contenu exclusif = argument fort pour s'abonner - Équité : un petit créateur peut tout mettre en premium, un gros peut tout offrir gratuitement
RoadWave génère automatiquement :
| Document | Fréquence | Destinataire | Base légale |
|---|---|---|---|
| Relevé mensuel PDF | Chaque mois | Créateur | Transparence |
| Export CSV comptable | À la demande | Créateur + expert-comptable | Facilitation déclarations |
| DAS2 annuel | Si >1200€/an | Impôts (DGFIP) | Obligation légale France |
Créateur responsable de : - Déclarer ses revenus à l'URSSAF (cotisations sociales auto-entrepreneur ou IS/IR) - Déclarer ses revenus aux impôts (IR ou IS selon statut) - Gérer sa TVA si applicable (franchise en base jusqu'à ~37K€/an en micro-BNC) - Conserver justificatifs 10 ans (obligation légale comptable)
Mangopay transmet automatiquement : - Données aux autorités fiscales EU via DAC7 (directive 2021/514) - Justificatif de chaque virement (preuve bancaire pour comptabilité créateur)
Exemple DAS2 :
Si créateur a touché 2500€ en 2026 :
→ RoadWave envoie DAS2 aux impôts en janvier 2027
→ Créateur reçoit copie par email
→ Créateur doit déclarer ces 2500€ dans sa déclaration annuelle
Justification : - Conformité légale : RoadWave doit déclarer revenus versés (DAS2, DAC7) - Responsabilité fiscale : Le créateur reste responsable de sa déclaration (impossible de gérer pour lui) - Automatisation : Minimise charge administrative côtés créateur et plateforme
Créateur peut : - Désactiver temporairement (vacances, pause création) - Réactiver sans refaire KYC si données à jour (<2 ans) - Solde conservé pendant désactivation
Plateforme suspend automatiquement si :
| Motif | Action | Réversible |
|---|---|---|
| Strike 3+ actif | Suspension immédiate | Oui, après résolution strikes |
| Compte bancaire invalide | Suspension après 3 échecs virement | Oui, après mise à jour RIB |
| Documents KYC expirés | Suspension avec préavis 30j | Oui, après renouvellement docs |
| Fraude détectée | Suspension immédiate + enquête | Cas par cas |
Suppression définitive si : - Demande du créateur (solde versé sous 30 jours) - Inactivité 24 mois + solde <50€ (purge RGPD) - Ban définitif compte (Strike 4)
Notification : - Email + in-app pour toute suspension - Raison explicite fournie - Procédure de réactivation indiquée
Justification : - Flexibilité : créateur peut faire pause sans perdre statut - Sécurité : plateforme doit pouvoir suspendre en cas problème légal/technique - RGPD : suppression auto données inactives après délai raisonnable
Décision : Deux formules sans essai gratuit
| Formule | Prix | Économie | Prix effectif |
|---|---|---|---|
| Mensuel | 4.99€/mois | - | 4.99€/mois |
| Annuel | 49.99€/an | 2 mois offerts | 4.16€/mois |
❌ Pas d'essai gratuit
Raisons : - Anti-abus vacances : évite inscriptions opportunistes (essai 14j avant road trip vacances, puis annulation) - Protection revenus créateurs : les écoutes Premium rémunèrent créateurs dès jour 1 - Simplicité : pas de gestion période trial + conversion - Engagement : utilisateur qui paie dès début = plus engagé
❌ Pas de partage familial (MVP)
Raisons : - Complexité technique (gestion invitations, validation liens, limite devices) - Risque abus ("familles" de 6 inconnus) - Coût dev/support élevé pour ROI incertain - La plupart des users RoadWave sont individuels (conducteurs) - Post-MVP : Si forte demande, offre "Famille" à 9.99€/mois pour 5 comptes
Justification tarif : - Aligné marché bas : Spotify = 10.99€, YouTube Premium = 11.99€, Apple Music = 10.99€ - Prix accessible : cible conducteurs quotidiens (budget raisonnable) - Incitation annuel : 2 mois offerts = engagement long terme + réduction churn
Décision : 1 seul stream actif par compte à tout moment
Détection connexion simultanée :
User A écoute sur iPhone
→ User A lance sur iPad
→ Détection : session active iPhone existe
→ Action : Arrêt lecture iPhone (WebSocket close)
→ Message iPhone : "Lecture interrompue : votre compte est utilisé sur un autre appareil"
→ Lecture démarre iPad
Implémentation technique :
Redis : active_streams:{user_id} → {device_id, started_at}
TTL : 5 minutes (refresh à chaque heartbeat)
Heartbeat toutes les 30s depuis app :
→ Si autre device détecté : kill session actuelle
→ Si pas de heartbeat pendant 5 min : considérer session morte
Exceptions : - Contenus téléchargés (offline) ne comptent pas comme stream actif - Transition rapide device (<10s) tolérée (changement voiture → maison)
Justification : - Anti-partage compte : empêche 2 personnes d'utiliser même compte Premium - Protection revenus créateurs : 1 abonnement = 1 personne = 1 écoute - UX claire : message explicite, pas de coupure brutale
Décision : Créateur décide (déjà couvert section 9.6)
Rappel règles : - Toggle "Réservé Premium" par contenu - Aucune limite de ratio gratuit/premium - Badge 👑 visible - Users gratuits : lecture bloquée avec CTA "Passez Premium"
Impact algorithme : - Contenus premium inclus dans recommandations - Si user gratuit → skip automatique (ne consomme pas slot) - Si user premium → diffusé normalement selon score
Inclus dans l'abonnement :
| Avantage | Gratuit | Premium |
|---|---|---|
| Publicités | 1/5 contenus | 0 (aucune) |
| Contenus exclusifs | ❌ Bloqués | ✅ Accès complet |
| Qualité audio | 48 kbps Opus | 64 kbps Opus |
| Mode offline | 50 contenus max | Illimité |
| Historique écoute | 100 derniers | Illimité |
Qualité audio : - Gratuit : 48 kbps Opus (~20 MB/h) = très correct pour voix - Premium : 64 kbps Opus (~30 MB/h) = excellente qualité
Justification différences : - 0 pub = argument principal (confort écoute) - Qualité audio = avantage tangible audiophiles - Offline illimité = use case road trips longs - Pas d'over-engineering : pas de badges cosmétiques, fonctionnalités sociales, etc. (focus essentiel)
Souscription :
| Canal | Prestataire | Prix | Commission |
|---|---|---|---|
| Web (desktop/mobile) | Mangopay | 4.99€ | 1.8% + 0.18€ = 0.27€ |
| iOS App | Apple In-App Purchase | 5.99€ | 30% (Apple) |
| Android App | Google Play Billing | 5.99€ | 30% (Google) |
Majoration mobile (5.99€) : - Apple/Google prennent 30% de commission - RoadWave majore prix de 20% pour compenser - Incitation web : Email aux users "Abonnez-vous sur roadwave.com pour 4.99€/mois" (38% moins cher en frais !)
Renouvellement automatique : - Email rappel 7 jours avant renouvellement - Email confirmation après renouvellement réussi - Retry automatique si échec paiement (3 tentatives sur 7 jours) - Annulation automatique après 3 échecs
Annulation : - Self-service dans Settings app : "Abonnement > Annuler" - Accès Premium maintenu jusqu'à fin période payée - Pas de remboursement prorata (standard industrie) - Email confirmation annulation avec date fin d'accès
Réabonnement : - Possibilité immédiate - ❌ Pas de nouvelle période d'essai (pas d'essai du tout)
Architecture données :
CREATE TABLE subscriptions (
id UUID PRIMARY KEY,
user_id UUID NOT NULL REFERENCES users(id) UNIQUE,
mangopay_recurring_payin_id VARCHAR(255), -- Null si IAP
mangopay_user_id VARCHAR(255), -- Null si IAP
apple_transaction_id VARCHAR(255), -- Null si Mangopay
google_purchase_token VARCHAR(255), -- Null si Mangopay
status VARCHAR(50) NOT NULL, -- 'active', 'cancelled', 'expired', 'past_due'
plan VARCHAR(50) NOT NULL, -- 'monthly', 'yearly'
current_period_start TIMESTAMP NOT NULL,
current_period_end TIMESTAMP NOT NULL,
cancelled_at TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
Vérification Premium en temps réel :
Cache Redis : premium:{user_id} → boolean (TTL 1h)
Refresh via webhooks :
- Mangopay : PAYIN_NORMAL_SUCCEEDED, PAYIN_NORMAL_FAILED
- Apple : App Store Server Notifications
- Google : Real-time Developer Notifications
Zone géographique : Choix manuel utilisateur
Options prédéfinies : - "Autour de moi" (rayon 50 km position actuelle) - "Ma ville" (limite administrative détectée) - "Mon département" (sélection liste) - "Ma région" (sélection liste) - Recherche manuelle : "Paris", "Lyon", "Marseille", etc.
Nombre de contenus téléchargeables :
| Statut | Limite | Affichage |
|---|---|---|
| Gratuit | 50 contenus max | "12/50 contenus téléchargés" |
| Premium | Illimité | "245 contenus (3.2 GB)" |
Calcul temps disponible : - 50 contenus × 5 min moyenne = 250 min = 4h d'écoute (suffisant pour gratuits) - Premium illimité = limité uniquement par espace disque device
Connexion WiFi/Mobile :
Par défaut : WiFi uniquement
Sur données mobiles : 1. User clique "Télécharger" 2. Détection : pas de WiFi 3. Popup : "Vous n'êtes pas connecté en WiFi. Télécharger via données mobiles consommera environ X MB. Continuer ?" 4. Boutons : "Attendre WiFi" / "Continuer"
Calcul estimation :
Nombre contenus × durée moyenne × bitrate qualité
Exemple : 20 contenus × 5 min × 48 kbps = ~72 MB
Qualité audio téléchargement :
| Qualité | Bitrate | Taille | Disponibilité |
|---|---|---|---|
| Basse | 24 kbps | ~10 MB/h | Gratuit + Premium |
| Standard | 48 kbps | ~20 MB/h | Gratuit + Premium (défaut) |
| Haute | 64 kbps | ~30 MB/h | Premium uniquement |
Justification : - Standard = bon compromis qualité/taille (Opus 48 kbps = très correct pour voix) - Haute réservée Premium = incitation upgrade - User peut réduire à "basse" si espace limité
Durée de validité : 30 jours après téléchargement
Standard industrie : - Spotify : 30 jours - YouTube Music : 30 jours - Deezer : 30 jours
Renouvellement automatique :
App détecte WiFi + contenus >25 jours
→ Requête API : GET /offline/contents/refresh
→ Backend vérifie pour chaque contenu :
- Abonnement Premium toujours actif ?
- Contenu pas modéré/supprimé ?
- Métadonnées à jour ?
→ Renouvelle validité à 30 jours supplémentaires
→ Mise à jour métadonnées (titre, créateur, statut)
→ Pas de re-téléchargement audio (sauf si fichier corrompu)
Notification avant expiration : - J-3 : "X contenus expirent dans 3 jours. Connectez-vous en WiFi pour les renouveler" - J-0 : Suppression automatique - J+0 : Toast "15 contenus expirés ont été supprimés"
Justification : - Force reconnexion : vérifier abonnement actif, contenus légaux - Évite stockage obsolète : contenus supprimés/modérés ne restent pas - UX transparente : renouvellement silencieux si WiFi régulier
Actions stockées localement (SQLite) : - Likes/unlikes - Abonnements/désabonnements - Signalements - Progression audio-guides
Sync automatique à la reconnexion :
1. App détecte reconnexion Internet
2. Récupération queue locale : SELECT * FROM pending_actions ORDER BY created_at
3. Envoi batch API : POST /sync/actions
4. Backend traite chaque action
5. Confirmation réception : DELETE FROM pending_actions WHERE id IN (...)
6. Toast : "3 likes et 1 abonnement synchronisés"
Gestion erreurs sync : - Si échec après 3 tentatives → notification : "Impossible de synchroniser. Réessayez plus tard" - Actions conservées jusqu'à sync réussie (pas de perte) - Rétention max 7 jours : après = purge (évite queue infinie)
Conflits contenus supprimés :
Backend retourne : {deleted_content_ids: [123, 456]}
→ App supprime fichiers locaux
→ Si contenu 123 en cours d'écoute :
- Attendre fin lecture actuelle
- Passage auto suivant après 2s
→ Toast : "1 contenu téléchargé a été retiré (violation règles)"
Justification : - Pas de conflit possible : actions unilatérales user (likes/abonnements) - UX fluide : pas de blocage offline - Batch = économie : requêtes HTTP groupées - Conformité modération : contenu illégal disparaît même offline
| Aspect | Décision | Valeur |
|---|---|---|
| Zone téléchargement | Choix | Manuel (autour/ville/département/région/recherche) |
| Limite gratuit | Contenus | 50 max |
| Limite Premium | Contenus | Illimité (espace disque) |
| Connexion | Par défaut | WiFi (mobile avec confirmation) |
| Qualité Standard | Bitrate | 48 kbps Opus |
| Qualité Haute | Bitrate | 64 kbps (Premium uniquement) |
| Validité | Durée | 30 jours |
| Renouvellement | Mode | Automatique si WiFi |
| Notification expiration | Délai | J-3 |
| Sync actions | Mode | Batch automatique reconnexion |
| Rétention queue | Durée | 7 jours max |
Stratégie : Élargissement automatique progressif
Flow :
1. Recherche rayon 50 km → aucun résultat
2. Élargissement auto 100 km
3. Si toujours rien → département
4. Si toujours rien → région
5. Dernier recours → contenu national (toujours disponible)
Messages adaptatifs :
| Cas | Message |
|---|---|
| Trouvé à 100 km | "Aucun contenu dans votre zone immédiate. Voici du contenu à proximité (100 km)" |
| Trouvé département | "Aucun contenu local disponible. Voici du contenu dans votre département" |
| Contenu national | "Aucun contenu local disponible. Voici du contenu national qui pourrait vous intéresser" |
Justification : - UX fluide : pas de message d'erreur bloquant "Aucun contenu" - User ne reste jamais sans contenu - Contenu national = filet de sécurité : actualités Le Monde, podcasts génériques
Décision : Pas d'interruption brutale
Flow :
1. Contenu supprimé côté backend (modération)
2. Si contenu en écoute → laisser terminer lecture en cours
3. Après fin lecture → désactiver bouton "Précédent" pour ce contenu
4. Passage automatique suivant après 2s
5. Toast notification discrète : "Contenu précédent retiré (violation règles)"
Si tentative "Précédent" manuellement : - Message : "Ce contenu n'est plus disponible" - Retour au contenu actuel
Justification : - Sécurité routière : pas d'interruption brutale pendant conduite - User informé mais pas alarmé : message discret - Empêche réécoute : contenu modéré inaccessible
Buffer adaptatif (cf. TECHNICAL.md) :
| Réseau | Buffer min | Buffer cible | Buffer max |
|---|---|---|---|
| WiFi | 5s | 30s | 120s |
| 4G/5G | 10s | 45s | 120s |
| 3G | 30s | 90s | 300s |
Comportement détaillé :
Phase 1 : Connexion instable (latence élevée, paquets perdus) - Aucun message immédiat - Lecture continue sur buffer - Si > 10s latence : toast discret "Connexion instable"
Phase 2 : Perte totale réseau - Lecture continue jusqu'à épuisement buffer - Toast : "Hors ligne, lecture sur buffer (30s restantes)" - Compte à rebours visible
Phase 3 : Buffer épuisé sans reconnexion - Pause automatique - Overlay : "Connexion perdue. Reconnexion en cours..." - Retry automatique toutes les 5s (max 6 tentatives = 30s)
Phase 4 : Basculement mode offline (après 30s échec) - Popup : "Voulez-vous continuer avec vos contenus téléchargés ?" - Boutons : "Réessayer" / "Mode offline" - Si "Mode offline" → lecture contenus téléchargés
Reconnexion réussie : - Reprise automatique lecture au point d'arrêt exact - Toast : "Connexion rétablie"
Justification : - Expérience fluide zones blanches (tunnels, campagne) - Buffer généreux : absorbe fluctuations réseau mobile - Mode offline secours : si coupure prolongée
Mode dégradé automatique
Contenu disponible :
| Type contenu | Disponible |
|---|---|
| Contenu national (podcasts, actualités) | ✅ |
| Contenu téléchargé (offline) | ✅ |
| Contenus "Neutre" géographiquement | ✅ |
| Contenu géolocalisé (Ancré/Contextuel) | ❌ |
| Audio-guides | ❌ |
| Notifications push géo-déclenchées | ❌ |
Popup au lancement : - Apparition : Premier lancement après refus géolocalisation - Message : "RoadWave fonctionne mieux avec la géolocalisation activée. Sans elle, seul le contenu national sera disponible." - Boutons : - "Activer" → Redirection paramètres OS - "Continuer sans" → Mode dégradé - Checkbox : "Ne plus me demander"
Banner permanent si refus : - Bandeau haut écran : "Mode limité : géolocalisation désactivée. [Activer]" - Pas intrusif mais rappel constant - Disparaît si géolocalisation réactivée
Justification : - App reste fonctionnelle sans GPS (pas de blocage) - Incitation forte à activer (meilleure UX) - Respecte choix user (RGPD : consentement libre)
Décision : Tarteaucitron.js + PostgreSQL backend
Implémentation web : - ✅ Tarteaucitron.js (opensource, self-hosted) - ✅ Banner RGPD français, customisable - ✅ Granularité : fonctionnel / analytique / marketing
Implémentation backend :
- Table user_consents avec versioning
- Champs : user_id, consent_type, version, accepted, timestamp
- Historique complet conservé (preuve légale)
Consentements requis : - Géolocalisation précise : obligatoire (banner + permission OS) - Analytics : optionnel (Matomo) - Notifications push : optionnel (permission OS)
Justification : - Opensource, 0€, conformité RGPD garantie - Historique backend = preuve légale en cas de contrôle - Granularité conforme recommandations CNIL
Décision : Geohash après 24h
Processus : 1. Données précises conservées 24h (recommandation personnalisée) 2. Après 24h : conversion en geohash précision 5 (~5km²) 3. Coordonnées originales supprimées définitivement
Implémentation PostGIS :
-- Job quotidien
UPDATE location_history
SET location = ST_SetSRID(ST_GeomFromGeoHash(ST_GeoHash(location::geography, 5)), 4326)::geography,
anonymized = true
WHERE created_at < NOW() - INTERVAL '24 hours' AND anonymized = false;
Exceptions : - ✅ Historique personnel visible (liste trajets) : conservation intégrale tant que compte actif - ❌ Analytics globales : uniquement geohash anonyme
Justification : - Vraie anonymisation RGPD (CNIL compliant) - Permet analytics agrégées (heatmaps trafic) - PostGIS natif, 0€
Décision : JSON + HTML + ZIP, génération asynchrone
Contenu de l'export :
export-roadwave-[user_id]-[date].zip
├── export.json # Machine-readable
├── index.html # Human-readable (stylé)
├── audio/
│ ├── content-123.opus
│ ├── content-456.opus
│ └── ...
└── README.txt # Instructions
Données exportées : - Profil utilisateur (email, pseudo, date inscription, bio) - Historique d'écoute (titres, dates, durées) - Contenus créés (audio + métadonnées) - Abonnements et likes - Centres d'intérêt (jauges) - Historique consentements
Processus : 1. Demande via paramètres compte 2. Génération asynchrone (worker background) 3. Email avec lien download (expire 7 jours) 4. Délai : 48h maximum (conformité RGPD)
Limite : - Maximum 1 export/mois (anti-abus)
Justification : - Conformité article 20 RGPD (portabilité) - Double format (human + machine) - Worker asynchrone évite timeout
Décision : Grace period 30j + anonymisation contenus
Processus : 1. Utilisateur clique "Supprimer mon compte" 2. Compte désactivé immédiatement (login impossible) 3. Contenus cachés pendant 30 jours (non diffusés) 4. Email confirmation + lien annulation (valide 30j) 5. Après 30j sans annulation : suppression effective
Suppression effective : - ✅ Compte utilisateur supprimé (données personnelles) - ✅ Historique d'écoute supprimé - ✅ GPS historique supprimé - ✅ Sessions et tokens révoqués - ⚠️ Contenus créés anonymisés (créateur = "Utilisateur supprimé") - ⚠️ Likes et abonnements supprimés (mais compteurs préservés)
Contenus conservés anonymement : - Audio files (CDN) - Métadonnées (titre, description, tags, géolocalisation) - Statistiques d'écoute
Justification : - Grace period évite suppressions impulsives - Anonymisation contenus = intérêt légitime communauté - Conforme RGPD si créateur = donnée supprimée
Décision : GeoIP par défaut, GPS optionnel
Niveaux de précision :
| Niveau | Technologie | Contenus accessibles | Consentement |
|---|---|---|---|
| Pays | Aucune géoloc | Contenus nationaux uniquement | ❌ Non requis |
| Ville | GeoIP (MaxMind) | Contenus régionaux/ville | ❌ Non requis |
| Précis | GPS | Tous contenus (hyperlocaux inclus) | ✅ Requis |
Implémentation : - Démarrage app : GeoIP automatique (IP → ville) - Banner in-app : "Activez la géolocalisation pour découvrir du contenu près de chez vous" - Upgrade volontaire vers GPS
API GeoIP : - MaxMind GeoLite2 (gratuit, self-hosted) - Update DB mensuelle automatique - Précision ~80% au niveau ville
Justification : - RGPD : pas de consentement requis pour GeoIP (pas de donnée personnelle) - UX dégradée acceptable (contenus disponibles) - Progressive disclosure (upgrade optionnel)
Décision : 5 ans inactivité → purge automatique
Règles :
| Type de compte | Seuil inactivité | Action |
|---|---|---|
| Auditeur uniquement | 5 ans sans connexion | Suppression automatique |
| Créateur avec contenus actifs | Jamais (tant qu'écoutes) | Conservation indéfinie |
| Créateur inactif | 5 ans sans connexion + 2 ans sans écoute | Suppression automatique |
Notifications avant suppression : - Email + push : 90 jours avant - Email + push : 30 jours avant - Email + push : 7 jours avant - Toute connexion = reset compteur inactivité
Contenu conservé : - Contenus créés par comptes supprimés (anonymisés) : conservation indéfinie
Justification : - Conformité principe minimisation RGPD - 5 ans = équilibre raisonnable (standard industrie) - Exception créateurs actifs = intérêt légitime plateforme
Décision : Matomo self-hosted, zéro cookie tiers
Cookies utilisés :
| Cookie | Type | Durée | Finalité | Consentement |
|---|---|---|---|---|
session |
Technique | 30j | Authentification | ❌ Non requis |
refresh_token |
Technique | 30j | Session persistante | ❌ Non requis |
_pk_id |
Analytique | 13 mois | Matomo (IP anonyme) | ✅ Requis |
Analytics : Matomo self-hosted : - Hébergé sur nos serveurs (Docker) - IP anonymisées automatiquement (2 derniers octets) - Pas de cookie si consentement refusé - Alternative : Plausible (SaaS EU, 9€/mois)
Trackers interdits : - ❌ Google Analytics - ❌ Facebook Pixel - ❌ Hotjar, Mixpanel, etc.
Justification : - Souveraineté données (pas de transfert US) - Conformité RGPD max (CNIL compatible) - Matomo = opensource, 0€ infra
Décision : Document Markdown versionné Git (MVP)
Emplacement :
- docs/rgpd/registre-traitements.md
- Versionné Git (historique modifications)
Contenu obligatoire par traitement : - Nom et finalité du traitement - Catégories de données collectées - Base légale (consentement / contrat / intérêt légitime) - Durée de conservation - Destinataires (sous-traitants, CDN, etc.) - Transferts hors UE (aucun prévu)
Responsable : - DPO / Fondateur - Review trimestrielle obligatoire - Update immédiate si nouveau traitement
Migration future : - Si > 100K utilisateurs : interface admin PostgreSQL
Justification : - Obligation RGPD Article 30 - Markdown = simple, versionné, auditable - 0€
Décision : Monitoring + alertes + runbook
Détection automatique :
| Événement | Outil | Alerte |
|---|---|---|
| Erreurs backend critiques | Sentry | Discord/Slack immédiat |
| Pic requêtes anormal | Grafana | Email équipe |
| Accès non autorisé DB | PostgreSQL logs | SMS fondateur |
| Authentification suspecte | Zitadel alerts | Email équipe |
Procédure breach :
- Runbook : docs/rgpd/procedure-breach.md
- Checklist 72h CNIL :
1. H+0 : Détection et confinement
2. H+24 : Évaluation gravité (données concernées, utilisateurs impactés)
3. H+48 : Notification CNIL si risque pour utilisateurs
4. H+72 : Notification utilisateurs si risque élevé
Contact CNIL : - Email pré-rédigé (template) - Formulaire en ligne (account CNIL créé)
Justification : - Obligation RGPD Article 33 (notification 72h) - Monitoring proactif évite découverte tardive - Sentry gratuit < 5K events/mois
Décision : Fondateur = DPO temporaire (MVP)
Raison légale : - Non obligatoire si : - < 250 employés - Pas de traitement à grande échelle de données sensibles - RoadWave : données localisation = sensible MAIS échelle MVP
Formation : - CNIL : formation gratuite en ligne (4h) - Certification CNIL "Atelier RGPD" (gratuit)
Contact : - Email : dpo@roadwave.fr - Publié dans CGU et mentions légales - Délai réponse : 1 mois (RGPD)
Migration future : - Si > 100K utilisateurs : DPO externe mutualisé (~200€/mois) - Ou recrutement DPO interne si > 10 employés
Justification : - Conforme RGPD (non obligatoire en phase MVP) - 0€, contrôle total - Bonne pratique : avoir un contact identifié
| Mesure | Implémentation | Coût |
|---|---|---|
| Consentement | Tarteaucitron.js + PostgreSQL | 0€ |
| Anonymisation GPS | Geohash PostGIS (24h) | 0€ |
| Export données | JSON+HTML+ZIP asynchrone | 0€ |
| Suppression compte | Grace period 30j + anonymisation | 0€ |
| Mode dégradé | GeoIP MaxMind + GPS optionnel | 0€ |
| Conservation | Purge auto 5 ans inactivité | 0€ |
| Analytics | Matomo self-hosted | ~5€/mois |
| Registre traitements | Markdown Git | 0€ |
| Breach detection | Sentry + Grafana + runbook | 0€ |
| DPO | Fondateur formé CNIL | 0€ |
Coût total RGPD : ~5€/mois
Décision : Formulaire simple avec 7 catégories prédéfinies
Liste déroulante avec 7 options :
| Catégorie | Description |
|---|---|
| 🚫 Haine & violence | Incitation à la haine, discrimination, menaces |
| 🔞 Contenu sexuel | Pornographie, contenu explicite |
| ⚖️ Illégalité | Terrorisme, apologie de crimes |
| 🎵 Droits d'auteur | Musique/contenu protégé non autorisé |
| 📧 Spam | Publicité non sollicitée, répétition |
| ❌ Fausse information | Désinformation sur santé, sécurité routière |
| 🔧 Autre | Champ texte obligatoire si sélectionné |
Justification : - Équilibre entre simplicité (pas trop de choix) et précision (aide les modérateurs) - Coût : 0€ (liste déroulante standard)
Décision : Optionnel avec incitation
Justification : - Encourage la qualité des signalements sans créer de friction - Aide les modérateurs à comprendre le contexte - Pas de risque d'abandon du processus
Décision : Toast in-app avec lien historique
Affichage : - Toast notification : "✓ Signalement envoyé. Nous l'examinerons sous 24-48h." - Durée affichage : 5 secondes - Bouton optionnel "Voir mes signalements" (accès historique)
Historique personnel : - Liste des signalements envoyés par l'utilisateur - Statut : En cours / Traité / Rejeté - Notification in-app si action prise (contenu retiré, signalement rejeté)
Justification : - Transparence maximale - Coût : 0€ (aucun email automatique) - Bonne UX
Décision : OpenAI Whisper open source + NLP
Stack technique :
| Composant | Technologie | Hébergement |
|---|---|---|
| Transcription | Whisper large-v3 | Self-hosted (CPU MVP, GPU scale) |
| Analyse sentiment | distilbert-base-uncased | Self-hosted |
| Détection haine | facebook/roberta-hate-speech | Self-hosted |
| Mots-clés | Liste noire FR/EN + regex | PostgreSQL |
Processus : 1. Signalement reçu → ajout file d'attente asynchrone 2. Transcription audio (1-10 minutes selon durée) 3. Analyse automatique : - Score de confiance : 0-100% - Catégorie détectée - Timestamps des passages problématiques 4. Priorisation automatique selon score
Délais : - Audio <5 min : 1-3 minutes - Audio 5-30 min : 3-10 minutes - Audio >30 min : 10-20 minutes
Coût : - MVP : 0€ (CPU standard, processing asynchrone) - Scale : 50-200€/mois (GPU VPS si >1000 signalements/jour)
Justification : - 100% open source, pas de dépendance GAFAM - Coût maîtrisé (scaling progressif) - Gain productivité modérateurs ×3-5
Décision : SLA progressif selon priorité
| Priorité | Délai cible | Traitement |
|---|---|---|
| CRITIQUE | <2h (24/7) | Violence, suicide, mise en danger → Astreinte modérateur senior |
| HAUTE | <24h (jours ouvrés) | Haine, harcèlement, désinformation → Modérateur junior/senior |
| MOYENNE | <24h (jours ouvrés) | Spam, contenu inapproprié → Modérateur junior |
| BASSE | <72h (jours ouvrés) | Qualité audio, tags incorrects → Modérateur junior |
Traitement automatique : - Score IA >95% + catégorie évidente (ex: spam répété) → Action automatique immédiate - Notification créateur + possibilité d'appel
Justification : - Réaliste et conforme DSA (Digital Services Act) - Scalable : priorisation automatique - Ressources humaines optimisées
Décision : File d'attente intelligente basée sur score IA
Calcul de priorité :
Priorité = (Score_IA × 0.7) + (Signalements_cumulés × 0.2) + (Fiabilité_signaleur × 0.1)
Détails : - Score_IA : 0-100% (confiance analyse automatique) - Signalements_cumulés : nombre de signalements du même contenu (boost priorité) - Fiabilité_signaleur : score utilisateur (historique signalements pertinents)
Classification résultante : - Priorité ≥90 → CRITIQUE (traitement immédiat) - Priorité 70-89 → HAUTE (file prioritaire) - Priorité 40-69 → MOYENNE (file normale) - Priorité <40 → BASSE (file différée)
Justification : - Optimise le temps des modérateurs - Traite les cas graves en priorité - Coût : 0€ (algorithme simple)
Décision : Multi-canal (email + push + in-app)
Canaux utilisés :
| Canal | Timing | Contenu |
|---|---|---|
| Push notification | Immédiat | Alerte courte : "Votre contenu a été modéré" |
| In-app | Au prochain lancement | Popup détaillée avec bouton "Voir détails" |
| Dans l'heure | Notification complète avec lien vers formulaire d'appel |
Contenu email :
Objet : Modération de votre contenu "[Titre du contenu]"
Bonjour [Pseudo],
Votre contenu "[Titre]" publié le [Date] a été modéré.
Catégorie violée : [Catégorie]
Raison : [Explication détaillée]
Sanction : [Strike X / Suspension X jours / Suppression contenu]
Extrait audio concerné : [Timestamp]
Transcription : "[Passage problématique surligné]"
Vous pouvez contester cette décision sous 7 jours :
[Lien formulaire d'appel]
L'équipe RoadWave
Coût : - Email : ~0.001€/notification (Brevo, Resend) - Push : 0€ (Firebase Cloud Messaging / APNs) - In-app : 0€
Justification : - Conformité DSA (transparence obligatoire) - Multi-canal garantit réception - Coût négligeable
Décision : Notification complète avec preuves
Éléments inclus obligatoirement :
Exemple visuel in-app :
┌─────────────────────────────────────┐
│ ⚠️ Contenu modéré │
├─────────────────────────────────────┤
│ Titre : "Mon podcast #42" │
│ Publié le : 15/01/2026 │
│ │
│ Catégorie violée : │
│ 🚫 Haine & violence (Article 3.2) │
│ │
│ Passage problématique : 3:42-4:15 │
│ "[Transcription surlignée]" │
│ │
│ Sanction : Strike 2/4 │
│ Suspension : 7 jours │
│ │
│ [Contester cette décision] │
└─────────────────────────────────────┘
Justification : - Transparence maximale (obligation DSA) - Créateur comprend l'erreur → amélioration future - Réduit les appels non fondés
Décision : Formulaire in-app structuré
Accès : - Bouton "Contester cette décision" dans notification - Section "Mes sanctions" dans profil créateur
Formulaire d'appel :
| Champ | Type | Obligatoire |
|---|---|---|
| Sanction contestée | Pré-rempli (non modifiable) | ✅ |
| Raison de l'appel | Texte libre (50-1000 caractères) | ✅ |
| Arguments | Zone texte enrichie | ✅ |
| Preuves | Upload fichiers (max 5, 10 MB total) | ❌ |
Après soumission :
- Génération numéro de ticket unique (ex: #MOD-2026-00142)
- Email confirmation : "Votre appel sera traité sous 72h"
- Statut visible dans l'app : "En cours d'examen"
Délai de soumission : - Maximum 7 jours après notification de sanction - Après 7 jours : appel automatiquement refusé
Justification : - Professionnel et traçable - Intégration complète avec système modération - Coût : 0€ (formulaire custom backend)
Décision : SLA 72h garanti
Délais :
| Type d'appel | Délai | Responsable |
|---|---|---|
| Standard | 72h max (3 jours ouvrés) | Modérateur senior |
| Complexe | 5 jours ouvrés + notification intermédiaire J+3 | Modérateur senior + Admin modération |
| Critique | 24h (cas suspension longue/ban) | Admin modération |
Notification intermédiaire (si délai >72h) : - Email J+3 : "Votre appel #MOD-XXX est en cours d'examen approfondi. Réponse sous 2 jours."
Réponse finale :
Email détaillé avec : 1. Décision : Maintien / Annulation / Réduction de sanction 2. Justification : explication de la décision d'appel 3. Actions : Strike retiré / Suspension annulée / Contenu rétabli (si applicable) 4. Définitif : mention "Cette décision est définitive" (pas de second appel)
Suivi in-app : - Mise à jour statut : "Appel accepté ✓" ou "Appel rejeté ✗" - Badge notification
Justification : - Équilibre entre rapidité et qualité de traitement - Conforme pratiques industrie (YouTube, TikTok : 5-7 jours) - Ressources humaines réalistes
Stack technique complète :
| Outil | Technologie | Fonction |
|---|---|---|
| Dashboard | React + TanStack Table | Interface modération |
| File signalements | PostgreSQL + Redis | Priorisation temps réel |
| Player audio | Wavesurfer.js | Lecture avec waveform + annotations |
| Transcription | Whisper large-v3 | Conversion audio → texte |
| Historique créateur | Vue 360° | Contenus, strikes, appels, métriques |
| Actions rapides | Shortcuts clavier | Approuver (A), Rejeter (R), Escalade (E) |
| Logs audit | PostgreSQL + export | Traçabilité complète (DSA) |
| Collaboration | Système de commentaires | Modérateurs peuvent s'entraider sur cas complexes |
Fonctionnalités clés :
Coût infrastructure : - MVP : 0-50€/mois (serveur CPU) - Scale : 50-200€/mois (GPU + Redis Cluster)
Nouveaux créateurs : - Validation manuelle des 3 premiers contenus - Délai : 24-48h (jours ouvrés) - Transcription automatique pour aide modérateur
Score de confiance : - Évolution dynamique selon historique - Créateur fiable (0 strike depuis 6 mois) → validation automatique - Créateur suspect (strikes récents) → validation manuelle systématique
Publicités : - Validation manuelle obligatoire 24-48h (responsabilité juridique) - Transcription + analyse métadonnées (ciblage, durée, volume)
Justification : - Prévention > réaction (économie modération) - Qualité plateforme préservée dès le début
| Point | Décision | Coût |
|---|---|---|
| Catégories signalement | 7 catégories prédéfinies + champ libre | 0€ |
| Commentaire signaleur | Optionnel avec incitation | 0€ |
| Confirmation | Toast in-app + historique personnel | 0€ |
| IA pré-filtre | Whisper (CPU MVP, GPU scale) + NLP open source | 0-200€/mois |
| Délais traitement | SLA progressif : 2h/24h/72h selon priorité | Dépend équipe |
| Priorisation | File intelligente basée score IA | 0€ |
| Notification sanction | Email + push + in-app (multi-canal) | ~0.001€/notif |
| Détail sanction | Complet : raison + extrait + transcription | 0€ |
| Processus appel | Formulaire in-app structuré | 0€ |
| Délai appel | 72h garanti (standard) | Dépend équipe |
| Outils modérateurs | Dashboard React + Whisper + Wavesurfer.js | 0-200€/mois |
Coût total MVP : 0-200€/mois (infrastructure IA optionnelle)
Conformité : - ✅ DSA (Digital Services Act) : transparence, traçabilité, délais - ✅ RGPD : données modération anonymisées après 3 ans - ✅ Logs audit : toutes actions tracées (obligation légale plateforme)
Scalabilité : - 0-1000 signalements/mois : équipe 1-2 modérateurs junior + 1 senior - 1000-10K signalements/mois : équipe 5-10 modérateurs + IA GPU - 10K+ signalements/mois : équipe dédiée + IA optimisée + modération communautaire
Prochaine section à clarifier : Section 11 (Mode offline) ou Section 12 (Gestion des erreurs)
Décision : Système de partage complet avec web player
Disponibilité : Partout dans l'application
Emplacements : - Player en lecture (bouton dans contrôles) - Page profil créateur (sur chaque contenu) - Liste de recherche (menu contextuel) - Historique personnel
Icône : ⬆️ (universelle iOS/Android)
Menu options : - Copier le lien - WhatsApp - Email - SMS - Plus... (sheet natif OS)
Justification : - Viralité = croissance organique gratuite - Aucune friction, partage universel
Format URL : https://roadwave.fr/share/c/[content_id]
Comportement multi-plateforme :
User clique lien partagé
↓
Page web responsive
↓
┌─────────────────────────────────┐
│ Si app installée │
│ → Deep link (ouverture directe) │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ Si app non installée │
│ → Web player + CTA téléchargement│
└─────────────────────────────────┘
Contenu de la page web :
┌───────────────────────────────────────┐
│ RoadWave │
├───────────────────────────────────────┤
│ [Image cover 16:9] │
│ │
│ 📻 Titre du contenu │
│ Par @créateur · 12 min · 🎧 2.3K │
│ │
│ 📍 Paris 5e · Ancré │
│ 🏷️ #Voyage #Histoire │
│ │
│ Description : Lorem ipsum... │
│ │
│ [▶️ Écouter maintenant] │
│ (Player HTML5 si contenu public) │
│ │
│ ────────────────────────────────── │
│ │
│ 📱 Télécharger l'app RoadWave │
│ [App Store] [Google Play] │
│ │
│ [Voir le profil de @créateur] │
└───────────────────────────────────────┘
Métadonnées Open Graph (SEO) :
<meta property="og:title" content="[Titre contenu] - RoadWave">
<meta property="og:description" content="[Description ou extrait]">
<meta property="og:image" content="[URL cover image]">
<meta property="og:audio" content="[URL audio si public]">
<meta property="og:type" content="music.song">
<meta property="og:site_name" content="RoadWave">
<meta name="twitter:card" content="player">
<meta name="twitter:player" content="https://roadwave.fr/player/[content_id]">
Deep linking :
- iOS : Universal Links (configuration apple-app-site-association)
- Android : App Links (configuration assetlinks.json)
- URL scheme : roadwave://content/[content_id]
Justification : - Meilleure viralité (partage social optimisé) - SEO (contenus indexés Google) - UX optimale (web + app) - Coût : 0€ (backend simple + CDN existant)
Décision : Preview 30 secondes + paywall
Comportement :
┌─────────────────────────────────┐
│ 👑 Contenu réservé Premium │
│ │
│ Profitez de ce contenu complet │
│ et de milliers d'autres │
│ sans publicité │
│ │
│ [Passer Premium - 4.99€/mois] │
│ [Télécharger l'app] │
└─────────────────────────────────┘
Tracking : - Métriques créateur : "Partages Premium" + "Conversions Premium" - Créateur touche sa part si conversion (70%)
Justification : - Équilibre viralité / monétisation - 30s = assez pour donner envie, pas assez pour satisfaire - Protège revenus créateurs
Décision : Profil public complet et transparent
URL : https://roadwave.fr/@[pseudo]
Layout :
┌────────────────────────────────────────┐
│ [Photo profil 120×120] │
│ @pseudo ✓ │
│ [Badge vérifié si applicable] │
│ │
│ Bio : Lorem ipsum dolor sit amet... │
│ (300 caractères max) │
│ │
│ 🎧 1.2K abonnés │
│ 📻 42 contenus │
│ ⏱️ 18h de contenu créé │
│ 🔊 54K écoutes totales │
│ │
│ [S'abonner] [Partager profil] [•••] │
│ │
│ ──────────────────────────────────── │
│ │
│ Contenus ▼ [Plus récents ▼] │
│ │
│ ┌──────────────────────────────────┐ │
│ │ [Cover] Titre contenu 1 │ │
│ │ 12 min · 🎧 2.3K · 📍 Paris │ │
│ │ [▶️] │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ [Cover] Titre contenu 2 │ │
│ │ 8 min · 🎧 5.1K · 📍 Lyon │ │
│ │ [▶️] │ │
│ └──────────────────────────────────┘ │
│ │
│ [Charger plus] │
└────────────────────────────────────────┘
Informations affichées :
| Élément | Visibilité | Détails |
|---|---|---|
| Photo + pseudo | ✅ Public | Identité visuelle |
| Badge vérifié ✓ | ✅ Public (si applicable) | Compte authentique |
| Bio | ✅ Public | 0-300 caractères, markdown basique (gras, italique, liens) |
| Nombre abonnés | ✅ Public | Arrondi si >1000 (ex: 1.2K, 54K) |
| Nombre contenus | ✅ Public | Exact |
| Durée totale créée | ✅ Public | Arrondi en heures (ex: 18h, 142h) |
| Écoutes totales | ✅ Public | Arrondi (ex: 54K, 1.2M) |
| Liste abonnés | ❌ Privé | Protection vie privée (RGPD) |
| Revenus | ❌ Privé | Confidentialité financière |
| Localisation précise | ❌ Privé | Sécurité |
| ❌ Privé | Anti-spam |
Tri des contenus :
| Option | Comportement |
|---|---|
| Plus récents | Date publication DESC (défaut) |
| Plus populaires | Écoutes complètes × (1 + (date_publication - now) / 90 jours) |
| Plus anciens | Date publication ASC |
| Par tag | Filtre multi-sélection tags |
Recherche locale : - Barre recherche dans profil : "Rechercher dans les contenus de @pseudo" - Recherche full-text sur titres + descriptions
Actions menu [•••] : - Partager profil - Signaler profil (spam, usurpation) - Bloquer créateur (masque tous ses contenus)
Décision : Stats arrondies et motivantes
Affichage public :
| Métrique | Format affichage | Exemple |
|---|---|---|
| Abonnés | Exact si <1000, arrondi sinon | 342 / 1.2K / 54K / 1.2M |
| Écoutes totales | Arrondi dès 1000 | 842 / 5.4K / 142K / 2.1M |
| Contenus publiés | Exact | 42 contenus |
| Durée totale | Arrondi en heures | 18h / 142h de contenu |
Métriques PRIVÉES (créateur uniquement) :
| Métrique | Disponible dans dashboard créateur |
|---|---|
| Taux complétion moyen | 78% (écoutes >80% / écoutes totales) |
| Évolution abonnés | Graphique 30j / 90j / 1 an |
| Écoutes par contenu | Tableau détaillé |
| Revenus | Dashboard monétisation dédié |
| Taux conversion Premium | Partages → conversions |
| Démographie | Âge / zone géo (agrégée, anonymisée) |
Justification : - Arrondi = évite comparaisons anxiogènes - Preuve sociale pour nouveaux auditeurs (trust) - Gamification douce (motivation créateurs) - Privacy by design
Décision : Badge unique ✓ (vérifié officiel)
Critères d'attribution (au moins UN des critères) :
Affichage : - Badge bleu ✓ accolé au pseudo (partout : profil, player, recherche) - Tooltip au survol/appui long : "Compte vérifié"
Processus d'obtention :
| Type | Processus |
|---|---|
| Automatique (KYC) | Badge attribué dès validation documents Mangopay |
| Manuel (célébrité) | Formulaire demande → équipe vérifie identité → validation 48-72h |
| Automatique (10K) | Badge attribué automatiquement à 10K abonnés si compte >6 mois |
Retrait du badge : - Suspension monétisation → badge retiré temporairement - Strikes multiples → badge retiré définitivement - Usurpation identité détectée → ban + retrait
Justification :
- Combat usurpations d'identité
- Trust auditeurs (surtout pour médias/personnalités)
- Simplicité (1 seul badge, pas de gamification excessive)
- Coût : 0€ (champ boolean verified en DB)
Décision : Recherche full-text + géo + filtres avancés
Implémentation : PostgreSQL full-text search (français)
Configuration technique :
-- Index full-text optimisé français
CREATE INDEX idx_content_search ON contents
USING GIN(
to_tsvector('french',
coalesce(title, '') || ' ' ||
coalesce(description, '') || ' ' ||
coalesce(creator_pseudo, '')
)
);
-- Recherche avec ranking
SELECT
c.*,
ts_rank(
to_tsvector('french', c.title || ' ' || c.description),
plainto_tsquery('french', $search_query)
) AS rank
FROM contents c
WHERE to_tsvector('french', c.title || ' ' || c.description)
@@ plainto_tsquery('french', $search_query)
ORDER BY rank DESC, listen_count DESC
LIMIT 20;
Champs indexés : - Titre du contenu (poids × 3) - Description (poids × 1) - Pseudo créateur (poids × 2) - Tags (poids × 1.5)
Fonctionnalités :
| Feature | Description |
|---|---|
| Stemming français | "voyages" trouve "voyage", "voyager", etc. |
| Correction auto | Suggestion si 0 résultat |
| Recherches populaires | "Essayez plutôt : balade paris, audio-guide louvre" |
| Historique personnel | 10 dernières recherches sauvegardées |
| Autocomplete | Suggestions pendant frappe (top 5) |
Coût : 0€ (PostgreSQL natif)
Migration future : - Si >100K contenus : Meilisearch (typo-tolerance avancée, ~20-50€/mois) - Si >1M contenus : Elasticsearch cluster
Justification : - PostgreSQL full-text = performant jusqu'à 500K contenus - Stemming français natif - 0€, aucune dépendance externe
Décision : Recherche lieu + rayon paramétrable
Interface utilisateur :
┌─────────────────────────────────────┐
│ 🔍 Recherche contenu... │
├─────────────────────────────────────┤
│ �� Lieu │
│ [Paris, France ▼] │
│ · Autour de moi (GPS actuel) │
│ · Entrer une adresse/ville │
│ │
│ 📏 Rayon de recherche │
│ [●─────────────────] 50 km │
│ (curseur 5 km → 500 km) │
│ │
│ 🗺️ [Afficher sur carte] │
└─────────────────────────────────────┘
Géocodage :
| Service | Usage | Coût |
|---|---|---|
| Nominatim (OSM) | MVP (API publique) | 0€ (rate limit 1 req/s) |
| Nominatim self-hosted | Scale (Docker) | 20-50€/mois VPS |
| Mapbox Geocoding | Fallback premium | 0.50€ / 1000 requêtes |
Processus de recherche géo :
SELECT c.*,
ST_Distance(c.location::geography, ST_Point($lon, $lat)::geography) AS distance
FROM contents c
WHERE ST_DWithin(
c.location::geography,
ST_Point($lon, $lat)::geography,
$radius_meters
)
ORDER BY distance ASC;
Affichage résultats : - Tri par défaut : distance croissante - Indication distance : "À 2.3 km" / "À 15 km" / "À 142 km" - Option carte : markers cliquables (clustering si >50 résultats)
Coût : - MVP : 0€ (Nominatim public) - Scale : 20-50€/mois (Nominatim self-hosted Docker)
Justification : - Essentiel pour tourisme / planification trajet - OpenStreetMap = pas de dépendance Google - PostGIS = performant (index GIST natif)
Décision : 7 catégories de filtres combinables
Interface filtres :
┌─────────────────────────────────────┐
│ Filtres [×] │
├─────────────────────────────────────┤
│ Type de contenu │
│ ☐ Contenu court (<5 min) │
│ ☐ Podcast (>5 min) │
│ ☐ Radio live │
│ ☐ Audio-guide │
│ │
│ Durée │
│ ○ Toutes durées │
│ ○ <5 min │
│ ○ 5-15 min │
│ ○ 15-30 min │
│ ○ >30 min │
│ │
│ Classification âge │
│ ☐ Tout public │
│ ☐ 13+ │
│ ☐ 16+ │
│ ☐ 18+ │
│ │
│ Géo-pertinence │
│ ☐ Ancré (lieu précis) │
│ ☐ Contextuel (zone large) │
│ ☐ Neutre (national) │
│ │
│ Tags (multi-sélection) │
│ ☐ Automobile ☐ Voyage │
│ ☐ Famille ☐ Histoire │
│ ☐ Économie ☐ Sciences │
│ ... (liste complète tags) │
│ │
│ Date de publication │
│ ○ Toutes dates │
│ ○ Dernières 24h │
│ ○ Cette semaine │
│ ○ Ce mois │
│ ○ Cette année │
│ │
│ Abonnement │
│ ○ Tous les contenus │
│ ○ Gratuits uniquement │
│ ○ Premium uniquement 👑 │
│ │
│ ────────────────────────────── │
│ [Réinitialiser] [Appliquer] │
└─────────────────────────────────────┘
Options de tri :
| Tri | Algorithme |
|---|---|
| Pertinence | Score recherche × (1 + log(listen_count + 1)) |
| Popularité | Écoutes complètes derniers 30j DESC |
| Récent | Date publication DESC |
| Proximité | Distance GPS ASC (si recherche géo active) |
| Durée | Durée audio ASC ou DESC |
Sauvegarde de recherches :
Performances :
-- Index composites pour filtres
CREATE INDEX idx_content_filters ON contents (
content_type,
duration,
age_rating,
geo_type,
published_at
);
-- Index GIN pour tags
CREATE INDEX idx_content_tags ON contents USING GIN(tags);
Coût : 0€ (PostgreSQL + index standards)
Justification : - Filtres essentiels pour découvrabilité - Combinables = puissance maximale - Sauvegarde = gain temps utilisateurs réguliers
Décision : Liste avec previews enrichies
Layout résultats :
┌─────────────────────────────────────────┐
│ 🔍 "voyage paris" │
│ 42 résultats · Tri : Pertinence ▼ │
│ [Filtres] [Carte] │
├─────────────────────────────────────────┤
│ ┌─────────────────────────────────────┐ │
│ │ [Cover ] Balade à Paris │ │
│ │ [16:9 ] @paris_stories ✓ │ │
│ │ [Image ] 12 min · 🎧 2.3K │ │
│ │ 📍 Paris 5e · Ancré │ │
│ │ 🏷️ #Voyage #Histoire │ │
│ │ [▶️ Écouter] [⋮] │ │
│ └─────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ [Cover ] Secrets Montmartre │ │
│ │ [16:9 ] @explore_paris │ │
│ │ [Image ] 8 min · 🎧 5.1K │ │
│ │ 📍 Paris 18e · Guide │ │
│ │ 🏷️ #Voyage #Art │ │
│ │ [▶️ Écouter] [⋮] │ │
│ └─────────────────────────────────────┘ │
│ │
│ [Charger plus] (20 suivants) │
└─────────────────────────────────────────┘
Informations par résultat :
| Élément | Affichage |
|---|---|
| Cover image | 16:9, 120×68 px, lazy loading |
| Titre | Tronqué 2 lignes max |
| Créateur | @pseudo + badge ✓ si vérifié, cliquable → profil |
| Durée | Format : "3 min" / "12 min" / "1h 24 min" |
| Écoutes | Arrondi : "2.3K" / "54K" / "1.2M" |
| Localisation | Ville + type géo (Ancré/Contextuel/Neutre) |
| Tags | Maximum 3 premiers tags |
| Badge Premium | 👑 si contenu premium |
| Distance | Si recherche géo : "À 2.3 km" |
Actions contextuelles [⋮] : - Partager - Ajouter à une playlist (future feature) - Télécharger (offline) - Signaler
Pagination : - 20 résultats par page - Infinite scroll (charger automatiquement si scroll >80%) - Bouton "Charger 20 suivants" en bas (fallback si scroll auto désactivé)
Vue carte (alternative) : - Bouton toggle "Liste / Carte" - Map Leaflet (OpenStreetMap) - Markers cliquables → popup avec preview - Clustering si >50 résultats proches
Coût : 0€ (Leaflet open source + OSM tiles gratuit)
Justification : - Équilibre information / compacité - Lazy loading = performances - Infinite scroll = UX moderne
| Point | Décision | Coût | Complexité |
|---|---|---|---|
| 15.1.1 Bouton partager | Disponible partout (⬆️), menu natif OS | 0€ | Faible |
| 15.1.2 Lien partagé | Web player + deep link + Open Graph SEO | 0€ | Moyenne |
| 15.1.3 Premium partagé | Preview 30s + paywall overlay | 0€ | Faible |
| 15.2.1 Page profil | Profil public complet (stats + bio + contenus + tri) | 0€ | Faible |
| 15.2.2 Stats publiques | Arrondies (abonnés, écoutes, durée totale) | 0€ | Faible |
| 15.2.3 Badge vérifié | ✓ si KYC/célébrité/>10K abonnés | 0€ | Faible |
| 15.3.1 Recherche texte | PostgreSQL full-text french + stemming | 0€ | Moyenne |
| 15.3.2 Recherche géo | Lieu + rayon (Nominatim OSM) | 0-50€/mois | Moyenne |
| 15.3.3 Filtres | 7 catégories combinables + sauvegarde recherches | 0€ | Moyenne |
| 15.3.4 Page résultats | Liste enrichie + vue carte Leaflet + infinite scroll | 0€ | Moyenne |
Coût total MVP : 0-50€/mois (Nominatim self-hosted optionnel)
Décision : 4 modes distincts avec détection automatique
| Mode | Vitesse détection | Déclenchement | Use case |
|---|---|---|---|
| 🚶 Piéton | <5 km/h | Manuel (bouton "Suivant") | Musées, visites urbaines, monuments |
| 🚗 Voiture | >10 km/h | Auto GPS + Manuel possible | Safari-parc, routes touristiques, circuits auto |
| 🚴 Vélo | 5-25 km/h | Auto GPS + Manuel possible | Pistes cyclables, circuits vélo, parcours nature |
| 🚌 Transport | Variable | Auto GPS + Manuel possible | Bus touristiques, trains panoramiques |
Détection automatique : - Vitesse moyenne calculée sur 30 secondes - Suggestion mode au démarrage : "Détection : 🚗 Voiture. Est-ce correct ? [Oui] [Changer]" - User peut forcer mode manuellement (settings)
Justification : - Flexibilité maximale créateurs et utilisateurs - Expériences optimisées par type de déplacement - Gestion cas limites (vélo lent vs piéton rapide)
Formulaire création :
┌────────────────────────────────────────┐
│ Nouvel audio-guide multi-séquences │
├────────────────────────────────────────┤
│ Titre : [Safari du Paugre] │
│ Description : [Découvrez les animaux │
│ du parc en voiture...] │
│ │
│ Mode de déplacement : *obligatoire │
│ ○ 🚶 Piéton (navigation manuelle) │
│ ● 🚗 Voiture (GPS auto + manuel) │
│ ○ 🚴 Vélo (GPS auto + manuel) │
│ ○ 🚌 Transport (GPS auto + manuel) │
│ │
│ Vitesse recommandée : 30-50 km/h │
│ (si voiture/vélo/transport) │
│ │
│ ──────────────────────────────────── │
│ │
│ Séquences (ordre lecture) : │
│ │
│ 1. [📍] Introduction - Point d'accueil │
│ Lat: 43.1234, Lon: 2.5678 │
│ Rayon déclenchement : 30m │
│ Durée : 2:15 │
│ [🎵 Audio uploadé] [✏️] [🗑️] │
│ │
│ 2. [📍] Enclos des lions │
│ Lat: 43.1245, Lon: 2.5690 │
│ Rayon déclenchement : 30m │
│ Durée : 3:42 │
│ [📤 Upload audio] [✏️] [🗑️] │
│ │
│ 3. [📍] Enclos des girafes │
│ [+ Ajouter point GPS] │
│ │
│ [+ Ajouter séquence] │
│ │
│ 📊 Statistiques : │
│ · 2 séquences complètes │
│ · 5:57 durée totale │
│ · 320m distance totale │
│ │
│ [🗺️ Aperçu sur carte] │
│ [✅ Publier audio-guide] │
└────────────────────────────────────────┘
Métadonnées obligatoires :
| Champ | Requis | Détails |
|---|---|---|
| Titre audio-guide | ✅ | 5-100 caractères |
| Description | ✅ | 10-500 caractères |
| Mode déplacement | ✅ | Piéton / Voiture / Vélo / Transport |
| Nombre séquences | ✅ | Minimum 2, maximum 50 |
| Point GPS par séquence | ✅ (sauf piéton) | Latitude, longitude (WGS84) |
| Rayon déclenchement | ✅ (sauf piéton) | 10-100m selon mode |
| Vitesse recommandée | ❌ | Optionnel, affichée utilisateur |
| Tags | ✅ | 1-3 parmi liste prédéfinie |
| Classification âge | ✅ | Tout public / 13+ / 16+ / 18+ |
| Zone diffusion | ✅ | Polygon géographique |
Wizard de création : - Étape 1 : Infos générales (titre, description, mode) - Étape 2 : Ajout séquences une par une - Étape 3 : Preview carte (trace + points) - Étape 4 : Validation modération (3 premiers audio-guides)
Justification : - Contrôle total créateur sur expérience - Carte preview aide visualiser parcours - Wizard guidé = réduction friction création
Décision : Navigation manuelle avec pub auto-play
Séquence normale (sans pub) :
Séquence avec publicité (1 pub / 5 séquences) :
Schéma flux :
Séquence 1 [fin] → PAUSE → User clique → Séquence 2 [fin] → PUB AUTO-PLAY → PAUSE → User clique → Séquence 3
Fréquence pub : - Gratuits : 1 pub toutes les 5 séquences (paramétrable admin 1/3 à 1/10) - Premium : 0 pub
Justification : - Pub s'insère naturellement (pas d'attente utilisateur pour déclencher) - User garde contrôle rythme visite (pause après pub) - Monétisation effective créateurs - Premium reste attractif (0 interruption)
Décision : Liberté totale utilisateur
Contrôles disponibles :
| Bouton | Fonction | Comportement |
|---|---|---|
| [▶|] Suivant | Passe séquence suivante | Immédiat, même si séquence actuelle pas terminée |
| [|◀] Précédent | Retour séquence précédente | Saut direct séquence avant (pas de logique "replay si >10s") |
| [⏸️] Pause | Pause temporaire | Reprend à position exacte |
| [▶️] Play | Reprend lecture | Continue position actuelle |
| Liste séquences | Navigation libre | Tap séquence → saut direct (même séquences non écoutées) |
Interface liste séquences :
┌────────────────────────────────────────┐
│ 🚶 Audio-guide Piéton │
│ Musée du Louvre │
├────────────────────────────────────────┤
│ [Cover image] │
│ │
│ ▶️ 0:00 ──●────────── 3:42 │
│ │
│ Séquence 3/12 : La Joconde │
│ │
│ [|◀] [⏸️] [▶|] │
│ │
│ ──────────────────────────────────── │
│ │
│ 📋 Liste des séquences │
│ │
│ ✅ 1. Introduction (2:15) │
│ Écouté le 15/01/2026 │
│ │
│ ✅ 2. Pyramide du Louvre (1:48) │
│ Écouté le 15/01/2026 │
│ │
│ ▶️ 3. La Joconde (3:42) - EN COURS │
│ ──●──────────── 1:22/3:42 │
│ │
│ ⭕ 4. Vénus de Milo (2:58) │
│ │
│ ⭕ 5. Code d'Hammurabi (4:12) │
│ │
│ ⭕ 6. Victoire de Samothrace (3:25) │
│ │
│ ... +6 séquences │
│ │
│ [Tout afficher ▼] │
└────────────────────────────────────────┘
Navigation libre : - User peut sauter séquences déjà connues - User peut revenir en arrière à tout moment - User peut aller directement à séquence 8 (même si 4-7 non écoutées)
Sauvegarde progression : - Checkmarks ✅ sur séquences écoutées >80% - Position exacte sauvegardée dans séquence en cours
Justification : - Utilisateur contrôle 100% son rythme - Adapté musées : visitor peut voir physiquement une œuvre lointaine et vouloir écouter sa description - Pas de frustration (liberté totale)
Décision : GPS auto avec navigation manuelle conservée
Distinction audio-guides vs contenus géolocalisés simples :
⚠️ Important : Les audio-guides multi-séquences fonctionnent différemment des contenus géolocalisés simples.
| Type | Séquences | Déclenchement | Notification | Enchaînement | Comptabilité quota |
|---|---|---|---|---|---|
| Contenu géolocalisé simple | 1 séquence unique | Notification 7s avant (temps ETA) | Sonore + icône | Fin → retour buffer normal | 1 contenu = 1 quota |
| Audio-guide multi-séquences | 2 à 50 séquences | Au point GPS exact (distance 30m) | Ding + toast 2s | Séquences s'enchaînent auto | 1 audio-guide = 1 quota (toutes séquences) |
Fonctionnement GPS automatique :
Pas de système "7 secondes avant" pour les audio-guides : - Contrairement aux contenus géolocalisés simples (voir 05-interactions-navigation.md) - Les séquences se déclenchent au point GPS exact (rayon 30m) - Raison : expérience guidée continue, user sait qu'il suit un parcours
Navigation manuelle CONSERVÉE :
| Bouton | État | Comportement |
|---|---|---|
| [▶|] Suivant | ✅ Toujours actif | Passe séquence suivante immédiatement (même hors point GPS) |
| [|◀] Précédent | ✅ Toujours actif | Retour séquence précédente (même hors point GPS) |
| [⏸️] Pause | ✅ | Pause temporaire |
| Liste séquences | ✅ | Saut direct possible |
Use cases navigation manuelle :
| Situation | Solution manuelle |
|---|---|
| Embouteillage (séquence finie, point GPS loin) | User clique Suivant → avance manuellement |
| Point GPS inaccessible (route fermée) | User clique Suivant → skip point |
| Envie réécouter séquence précédente | User clique Précédent → retour |
| Passager manipule l'app | Passager navigue librement |
Avertissement sécurité :
Schéma flux :
Point GPS 1 (30m) → Séquence 1 AUTO → User roule → Distance affichée → Point GPS 2 (30m) → Séquence 2 AUTO
↓
User clique Suivant (manuel) → Séquence 2 immédiate
Justification : - Flexibilité maximale : GPS optimise expérience MAIS user garde contrôle - Gestion cas limites : routes fermées, détours, embouteillages - Sécurité : warning sensibilise sans bloquer (passager légitime)
Décision : Distance + direction (PAS de carte miniature)
Interface en conduite :
┌────────────────────────────────────────┐
│ 🚗 Audio-guide Voiture │
│ Safari du Paugre │
├────────────────────────────────────────┤
│ │
│ ▶️ 0:00 ──●────────── 2:15 │
│ │
│ Séquence 2/8 : Les lions │
│ │
│ ──────────────────────────────────── │
│ │
│ 📍 Prochain point │
│ │
│ Enclos des girafes │
│ │
│ ┌────────────────────────────────┐ │
│ │ │ │
│ │ ↗️ │ │
│ │ (direction) │ │
│ │ │ │
│ │ 320 mètres │ │
│ │ ≈ 40 secondes │ │
│ │ │ │
│ └────────────────────────────────┘ │
│ │
│ Vitesse actuelle : 28 km/h │
│ Vitesse recommandée : 20-30 km/h │
│ │
│ [|◀] [⏸️] [▶|] [📋 Liste] │
└────────────────────────────────────────┘
Affichage entre deux séquences :
Quand une séquence se termine et qu'il reste un point GPS suivant, l'interface bascule en mode "attente prochain point" :
┌────────────────────────────────────────┐
│ 🚗 Audio-guide Voiture │
│ Safari du Paugre │
├────────────────────────────────────────┤
│ │
│ ✅ Séquence 2/8 terminée │
│ Les lions │
│ │
│ ──────────────────────────────────── │
│ │
│ 📍 Prochain point │
│ │
│ Enclos des girafes │
│ │
│ ┌────────────────────────────────┐ │
│ │ [Progress bar] │ │
│ │ ████████░░░░░░░░░ 65% │ │
│ │ │ │
│ │ ↗️ │ │
│ │ (direction) │ │
│ │ │ │
│ │ 320 mètres │ │
│ │ ≈ 40 secondes │ │
│ │ │ │
│ └────────────────────────────────┘ │
│ │
│ Vitesse actuelle : 28 km/h │
│ │
│ [|◀] [▶️ Rejouer séq.] [▶|] │
└────────────────────────────────────────┘
Progress bar dynamique :
- Se remplit au fur et à mesure qu'on se rapproche du point
- Calcul : progress = 100 - (distance_actuelle / distance_initiale * 100)
- Exemple : distance initiale 500m, distance actuelle 175m → progress = 65%
- Couleur : vert (#4CAF50) pour la partie remplie, gris (#E0E0E0) pour le reste
Bouton "Rejouer séq." : - Permet de réécouter la séquence qui vient de se terminer - User clique → séquence actuelle redémarre depuis 0:00 - Utile si distraction pendant l'écoute
Informations affichées :
| Info | Mise à jour | Format |
|---|---|---|
| Distance | Chaque seconde | "320 m" / "1.2 km" |
| ETA | Chaque seconde | "≈ 40 secondes" / "≈ 2 minutes" |
| Direction | Chaque 5s | Flèche indique direction (8 directions : ↑ ↗ → ↘ ↓ ↙ ← ↖) |
| Vitesse actuelle | Chaque seconde | "28 km/h" |
| Vitesse recommandée | Statique | "20-30 km/h" (définie par créateur) |
| Progress bar | Chaque seconde | Pourcentage parcouru vers prochain point |
Calcul direction :
// Calcul angle entre position actuelle et prochain point
const currentGPS = getCurrentLocation();
const nextPoint = audioGuide.sequences[currentIndex + 1].location;
const angle = calculateBearing(currentGPS, nextPoint); // 0-360°
// Conversion en flèche (8 directions)
const arrows = ['↑', '↗', '→', '↘', '↓', '↙', '←', '↖'];
const index = Math.round(angle / 45) % 8;
const direction = arrows[index];
Calcul ETA :
const distance = calculateDistance(currentGPS, nextPoint); // mètres
const currentSpeed = getCurrentSpeed(); // km/h
if (currentSpeed > 5) {
const eta = (distance / 1000) / currentSpeed * 3600; // secondes
return formatETA(eta); // "≈ 40 secondes" ou "≈ 2 minutes"
} else {
return "En attente de déplacement";
}
Justification : - Distance + ETA = info essentielle sans surcharge visuelle - Direction (flèche) = aide se repérer sans carte complexe - Simplicité = moins distraction conducteur - Économie batterie (pas de rendu carte)
Décision : Rayon configurable créateur avec défauts intelligents
Rayons par défaut :
| Mode | Rayon déclenchement | Rayon "point manqué" | Justification |
|---|---|---|---|
| 🚗 Voiture | 30 mètres | 100 mètres | Vitesse élevée = anticipation |
| 🚴 Vélo | 50 mètres | 75 mètres | Vitesse variable, arrêts fréquents |
| 🚌 Transport | 100 mètres | 150 mètres | Arrêts bus/train, moins précis |
Configuration créateur :
Gestion point manqué :
User passe à 110m du point GPS
(hors rayon déclenchement 30m MAIS dans rayon tolérance 100m)
↓
Toast : "⚠️ Point manqué : Enclos des girafes"
↓
Popup 5 secondes :
┌────────────────────────────────────┐
│ Point manqué │
│ │
│ "Enclos des girafes" │
│ Vous êtes passé à 110m du point │
│ │
│ [🔊 Écouter quand même] │
│ [⏭️ Passer au suivant] │
│ [🔙 Faire demi-tour] │
└────────────────────────────────────┘
Actions popup :
| Bouton | Comportement |
|---|---|
| Écouter quand même | Lance séquence immédiatement (même hors zone) |
| Passer au suivant | Skip séquence, continue vers prochain point |
| Faire demi-tour | Lance navigation GPS externe (Google Maps / Waze) vers point manqué |
Si user au-delà rayon tolérance (>100m) : - Aucun popup (point trop loin, probablement hors itinéraire) - User peut naviguer manuellement (bouton Suivant)
Justification : - Flexibilité créateur (ajuste selon terrain, vitesse prévue) - Gestion intelligente imprévus (détours, routes fermées) - User pas bloqué (toujours moyen avancer)
Décision : Même logique voiture avec tolérances ajustées
Différences par rapport à mode voiture :
| Paramètre | Voiture | Vélo | Transport |
|---|---|---|---|
| Rayon déclenchement | 30m | 50m | 100m |
| Rayon tolérance "point manqué" | 100m | 75m | 150m |
| Vitesse recommandée affichée | 20-50 km/h | 10-25 km/h | Variable (selon ligne) |
| Warning sécurité | >10 km/h | >5 km/h | Désactivé |
Mode Vélo spécificités :
Mode Transport spécificités :
Comportement identique voiture :
Justification : - Vélo : moins de contrôle qu'auto (obstacles, arrêts), nécessite tolérance - Transport : moins de contrôle utilisateur (suit ligne fixe), rayon large compense - Même UX globale = cohérence
Décision : Pub auto-play entre séquences TOUS modes
Insertion publicité :
Comportement MODE PIÉTON :
Séquence 2 [fin]
→ Pub AUTO-PLAY
→ Pub se termine
→ PAUSE AUTO
→ Message "Séquence 3 prête. Appuyez sur Suivant."
→ User clique [▶|]
→ Séquence 3 démarre
Comportement MODE VOITURE/VÉLO/TRANSPORT :
Séquence 2 [fin]
→ Pub AUTO-PLAY
→ Pub se termine
→ ATTENTE point GPS suivant OU user clique Suivant
→ Séquence 3 démarre
Schéma complet :
| Mode | Après séquence normale | Après pub |
|---|---|---|
| Piéton | Pause + attente user | Pause + attente user |
| Voiture | Attente GPS OU user clique Suivant | Attente GPS OU user clique Suivant |
| Vélo | Attente GPS OU user clique Suivant | Attente GPS OU user clique Suivant |
| Transport | Attente GPS OU user clique Suivant | Attente GPS OU user clique Suivant |
Justification : - Monétisation équitable créateurs (tous modes participent) - Pub s'insère naturellement (auto-play, pas d'attente utilisateur) - User garde contrôle : piéton clique Suivant, voiture peut skip manuel - Premium reste attractif (expérience 0 interruption) - Modèle économique viable
Dashboard créateur :
| Métrique | Affichage |
|---|---|
| Impressions pub | Nombre de pubs insérées dans audio-guides |
| Écoutes complètes pub | Nombre de pubs écoutées >80% |
| Taux skip pub | % pubs skippées avant 5s vs après |
| Revenus pub audio-guides | 3€ / 1000 écoutes complètes (6% CA pub) |
Distinction contenus normaux vs audio-guides : - Dashboard sépare : "Revenus contenus classiques" / "Revenus audio-guides" - Permet créateur voir performance par type
Justification : - Transparence créateur (comprend revenus) - Incite création audio-guides (nouvelle source revenus)
Décision : Sauvegarde complète automatique avec popup intelligente
Données sauvegardées :
| Info | Détail | Utilité |
|---|---|---|
| Audio-guide ID | Identifiant unique | Retrouver audio-guide |
| Séquence actuelle | Index (ex: 3/12) | Reprise position |
| Position dans séquence | Timestamp exact (ex: 1:42/3:20) | Reprise exacte |
| Séquences écoutées | Liste avec checkmarks ✅ | Historique progression |
| Date dernière écoute | Timestamp | Proposer reprise si <30j |
| GPS dernière position | Coordonnées optionnelles | Info contextuelle (non utilisée pour reprise) |
Stockage :
| Environnement | Technologie | Utilité |
|---|---|---|
| Local | SQLite mobile | Fonctionnement offline |
| Cloud | PostgreSQL (sync auto) | Multi-device (reprendre sur autre appareil) |
Synchronisation : - Sauvegarde locale : chaque fin de séquence + chaque 30s - Sync cloud : à la reconnexion réseau (batch)
Justification : - Expérience fluide (pas de perte progression) - Multi-device (démarrer sur iPhone, continuer sur iPad) - Offline-first (fonctionne sans réseau)
Conditions popup : - Dernière écoute <30 jours - Progression >0% et <100% (pas terminé)
Popup reprise :
┌────────────────────────────────────────┐
│ Reprendre l'audio-guide ? │
├────────────────────────────────────────┤
│ 🚗 Safari du Paugre │
│ @safari_createur │
│ │
│ Progression : 3/8 séquences écoutées │
│ Dernière écoute : il y a 2 jours │
│ │
│ Vous étiez à : │
│ "Les lions" (1:42/3:20) │
│ │
│ [▶️ Reprendre] [🔄 Recommencer] │
│ [📋 Voir toutes les séquences] │
└────────────────────────────────────────┘
Actions :
| Bouton | Comportement |
|---|---|
| Reprendre | Continue séquence 3 à position 1:42 exacte |
| Recommencer | Reset progression, démarre séquence 1 depuis 0:00 |
| Voir séquences | Affiche liste complète, user choisit séquence départ |
Expiration progression : - Progression conservée 30 jours - Après 30j : popup "Audio-guide expiré. Recommencez depuis le début ?" - Suppression données progression (mais historique "écouté" préservé)
Justification : - Contexte clair : user sait exactement où il en est - Flexibilité : reprendre OU recommencer (choix utilisateur) - 30 jours = raisonnable pour tourisme multi-jours ou retour ultérieur
Scénario :
Conflit de version : - Si modifications simultanées 2 appareils (rare) : dernière modification gagne - Toast : "Progression mise à jour depuis votre autre appareil"
Justification : - Confort utilisateur (change d'appareil librement) - Use case réel : planning trajet sur tablette, écoute sur smartphone en voiture
| Point | Décision | Coût | Complexité |
|---|---|---|---|
| 16.1 Types audio-guides | 4 modes (piéton/voiture/vélo/transport) avec détection auto | 0€ | Moyenne |
| 16.1.2 Création | Formulaire séquences + GPS + rayon + wizard guidé | 0€ | Moyenne |
| 16.2.1 Piéton - Passages | Manuel AVEC pub auto-play entre séquences, pause après | 0€ | Faible |
| 16.2.2 Piéton - Navigation | Liberté totale (skip, retour, saut direct liste) | 0€ | Faible |
| 16.3.1 Voiture - Déclenchement | GPS auto + boutons manuels actifs (warning sécurité si >10 km/h) | 0€ | Moyenne |
| 16.3.2 Voiture - Affichage | Distance + ETA + direction (flèche) + vitesse (PAS de carte) | 0€ | Faible |
| 16.3.3 Voiture - Rayon | Configurable créateur (défauts 30m/50m/100m selon mode) | 0€ | Faible |
| 16.4 Vélo & Transport | Mêmes règles avec tolérances ajustées + warning adapté | 0€ | Faible |
| 16.5 Publicités | 1/5 séquences, auto-play TOUS modes, skippable 5s | 0€ | Faible |
| 16.6.1 Sauvegarde | Complète (séquence + position + historique) local + cloud | 0€ | Faible |
| 16.6.2 Reprise | Popup intelligente avec choix (reprendre/recommencer), expiration 30j | 0€ | Faible |
| 16.6.3 Multi-device | Sync cloud PostgreSQL (reprendre sur autre appareil) | 0€ | Faible |
Coût total MVP : 0€ (GPS natif, calcul distance PostGIS)
Date : 2026-01-19 Statut : Fonctionnalités validées mais reportées après le MVP
⚠️ Reporté post-MVP pour raisons de coût, complexité et risques juridiques.
Raisons : - Coût modération : Classification manuelle humaine très coûteuse (~2000€/mois pour 1-2 modérateurs senior full-time) - Risque juridique : Accusations de biais éditorial, contentieux DSA - Complexité technique : Dashboard audit, logs 3 ans, alertes déséquilibre - Controverse : Peut créer polémique dès le lancement - Pas essentiel MVP : L'application fonctionne sans ce système
Version MVP (actuelle) : - Tag "Politique" simple (comme "Économie", "Sport") - Pas de classification gauche/droite - Pas d'équilibrage imposé - Option utilisateur "Masquer politique" → 0% contenus politiques
Échelle de classification (5 niveaux) : - 🔴 Extrême gauche (anticapitalisme radical, révolution) - 🟠 Gauche (écologie, social, critique capitalisme modérée) - ⚪ Centre/Neutre (pas de positionnement politique clair) - 🔵 Droite (sécurité, tradition, économie libérale) - 🟣 Extrême droite (nationalisme radical, conservatisme extrême) - 🟢 Non politique (enfants, musique, fiction, culture générale)
Qui classifie : - ❌ Pas de classification automatique IA (outil informatif uniquement, jamais décisionnaire) - ✅ Modérateurs senior après transcription - ✅ Créateur peut contester via processus d'appel
Affichage : - Badge politique visible : au choix de l'utilisateur (paramètre "Afficher orientation politique") - Par défaut : badges masqués (UX neutre)
Règles de diffusion (équilibre imposé) :
| Préférence utilisateur | Répartition | Justification |
|---|---|---|
| Équilibré (défaut) | 35% gauche / 35% droite / 30% centre-neutre | Neutralité plateforme |
| Plutôt gauche | 50% gauche / 20% droite / 30% centre-neutre | Préférence respectée avec minimum opposition |
| Plutôt droite | 50% droite / 20% gauche / 30% centre-neutre | Préférence respectée avec minimum opposition |
| Masquer politique | 0% gauche / 0% droite / 100% centre-neutre + non politique | Option apolitique |
Audit et conformité DSA : - Rapport hebdomadaire automatique : % gauche/droite/centre diffusé par utilisateur - Alerte si déséquilibre global plateforme (>55% d'un bord) - Logs conservés 3 ans (exigence Digital Services Act EU) - Dashboard admin : visualisation répartition temps réel
Sanctions mauvaise classification : - Classification volontairement incorrecte = Strike 1 - Récidive = Strike 2 (suspension 7j) - Détection via signalements utilisateurs + audit modération
Justification : - Conformité juridique DSA (obligation neutralité plateforme EU) - Protection contre accusations de biais éditorial - Transparence auditable - Coût : temps modération humaine (incompressible)
Prérequis : 1. Base utilisateurs stable et revenus suffisants pour financer modération 2. Équipe modération dédiée (2+ modérateurs senior formés) 3. Dashboard admin audit DSA opérationnel 4. Système de logs et archivage 3 ans en place 5. Validation juridique du processus de classification
Chronologie estimée : - Phase 1 (Post-MVP+3 mois) : Validation demande utilisateurs via sondages - Phase 2 (Post-MVP+6 mois) : Recrutement modérateurs + développement dashboard - Phase 3 (Post-MVP+9 mois) : Tests bêta avec utilisateurs volontaires - Phase 4 (Post-MVP+12 mois) : Déploiement progressif si résultats positifs
⚠️ Reporté post-MVP - Fonctionnalité crypto (Lightning Network) prévue ultérieurement.
Raisons : - Complexité technique : Intégration Lightning Network, gestion wallets crypto - Réglementation : Incertitude juridique crypto en EU (MiCA 2025) - Focus MVP : Priorité sur monétisation via abonnements Premium et publicités - Adoption utilisateurs : Nécessite éducation et adoption crypto préalables
Version MVP (actuelle) : - Monétisation créateurs via : - Partage revenus publicités (3€ CPM) - 70% revenus abonnements Premium
Système prévu : Micro-dons via Lightning Network (Bitcoin Layer 2)
Fonctionnement : 1. Auditeur peut envoyer pourboire pendant ou après écoute 2. Montants suggérés : 0.10€, 0.50€, 1€, 5€ (personnalisable) 3. Transaction instantanée via Lightning Network (frais <0.01€) 4. Créateur reçoit directement dans wallet Lightning 5. Conversion EUR/BTC automatique (optionnelle)
Avantages Lightning Network : - ✅ Frais quasi-nuls (<1%) vs 1.8% Mangopay - ✅ Transactions instantanées (<1 seconde) - ✅ Micropaiements possibles (dès 0.01€) - ✅ International sans frais supplémentaires - ✅ Pas d'intermédiaire (peer-to-peer)
Contraintes : - ❌ Adoption crypto limitée (2-5% population EU en 2026) - ❌ Volatilité BTC (nécessite conversion EUR immédiate) - ❌ UX complexe pour utilisateurs non-crypto - ❌ Réglementation MiCA en évolution
Alternatives étudiées : - Ko-fi / Buy Me a Coffee : simple mais frais 5% - PayPal/Stripe : frais 2.9% + 0.30€ (non viable pour micropaiements) - Mangopay : déjà utilisé, mais frais élevés pour petits montants
Prérequis : 1. Réglementation MiCA stabilisée et conforme 2. Adoption crypto suffisante dans la base utilisateurs (>10%) 3. Intégration Lightning Network validée techniquement 4. UX simplifiée pour utilisateurs non-crypto (onboarding dédié) 5. Demande créateurs confirmée via sondages
Chronologie estimée : - Phase 1 (Post-MVP+6 mois) : Étude de marché et demande utilisateurs - Phase 2 (Post-MVP+12 mois) : Développement intégration Lightning - Phase 3 (Post-MVP+15 mois) : Tests bêta avec créateurs volontaires - Phase 4 (Post-MVP+18 mois) : Déploiement public si résultats positifs
Liste non exhaustive de fonctionnalités évoquées mais non encore spécifiées :
Ces fonctionnalités seront spécifiées et priorisées selon les retours utilisateurs MVP.
Responsable : Product Owner Révision : Trimestrielle Critères de priorisation : 1. Demande utilisateurs (votes, sondages) 2. Impact business (revenus, rétention) 3. Faisabilité technique (complexité, ressources) 4. Conformité légale (RGPD, DSA, MiCA) 5. Différenciation concurrentielle
En tant qu'auditeur à pied Je veux profiter d'audio-guides structurés lors de mes visites Afin de découvrir des lieux de manière autonome et à mon rythme
29 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et que je suis connecté en tant qu'auditeur Et que je suis en mode piéton (vitesse <5 km/h)
Étant donné que je me trouve à 80 mètres du Musée du Louvre Et que 3 audio-guides sont disponibles pour ce lieu
Quand le système détecte ma position
Alors je reçois une notification push:
Étant donné qu'un audio-guide est centré aux coordonnées GPS du Louvre
Quand je suis à exactement 100m du centre
Alors la notification est déclenchée Et quand je suis à 101m, aucune notification n'est envoyée
Étant donné que j'ai tapé sur la notification audio-guide
Quand la page de sélection s'affiche
Alors je vois une liste de guides disponibles:
| titre | créateur | nb_sequences | durée | note | écoutes |
|---|---|---|---|---|---|
| Visite complète | Créateur A | 12 | 45 min | 4.8 | 1.2K |
| Œuvres majeures | Créateur B | 5 | 20 min | 4.9 | 3.5K |
| Visite famille | Créateur C | 8 | 30 min | 4.7 | 850 |
Étant donné que je suis sur la page de sélection
Quand je tape sur "Visite complète (45 min)"
Alors l'interface de lecture d'audio-guide s'ouvre Et la séquence 1 commence automatiquement Et je vois la liste complète des 12 séquences
Étant donné que j'ai sélectionné un audio-guide de 12 séquences
Quand l'interface s'affiche
Alors je vois:
| élément | exemple |
|---|---|
| Titre guide | 🎨 Visite complète • Musée du Louvre |
| Piste actuelle | Piste 2/12 |
| Titre séquence | "La Joconde - Histoire et mystères" |
| Barre de progression | 3:24 / 6:50 |
| Liste séquences | ✅ 1. Intro, ▶️ 2. Joconde, ⏸️ 3. Vénus... |
| Boutons navigation | Précédent, Play/Pause, Suivant |
Étant donné que j'écoute la séquence 2
Quand je tape sur "Suivant"
Alors la séquence 3 commence immédiatement Et le titre de la séquence s'affiche: "Vénus de Milo" Et la barre de progression se réinitialise
Étant donné que j'écoute la séquence 5
Quand je tape sur "Précédent"
Alors la séquence 4 recommence depuis le début Et je peux réécouter cette séquence
Étant donné que j'écoute la séquence 2 Et que la liste des séquences est affichée
Quand je tape sur "7. Peintures Renaissance"
Alors la séquence 7 démarre immédiatement Et je passe directement de la séquence 2 à la 7
Étant donné que j'écoute la séquence 3
Quand je dis "Suivant" via la commande vocale
Alors la séquence 4 démarre Et la commande vocale fonctionne même si l'écran est verrouillé
Étant donné que j'écoute la séquence 6
Quand je dis "Précédent" via la commande vocale
Alors la séquence 5 démarre depuis le début
Étant donné que j'écoute la séquence 4 à la position 2:30
Quand je mets en pause Et que j'attends 5 minutes Et que je reprends la lecture
Alors la séquence reprend exactement à 2:30 Et aucune donnée n'est perdue
Étant donné que la séquence 2 se termine
Quand la transition vers la séquence 3 se produit
Alors j'entends un message vocal: Et la séquence 3 ne démarre pas automatiquement (navigation manuelle)
Étant donné que je suis dans le guide du Louvre Et que je devrais être devant la Vénus de Milo (séquence 3)
Quand je m'éloigne de plus de 50m de ce point
Alors j'entends un message vocal: Et un bouton "Voir le plan" apparaît dans l'interface
Étant donné que j'écoute la séquence 5 à la position 1:45
Quand je ferme l'application brutalement Et que je la rouvre 10 minutes plus tard
Alors je vois une popup "Reprendre la visite du Musée du Louvre ?" Et si je choisis "Reprendre", je retourne à la séquence 5 à 1:45
Étant donné que j'ai une progression sauvegardée à la séquence 7
Quand je rouvre le guide
Alors je vois 2 options:
| option | action |
|---|---|
| Reprendre à la séquence 7 | Reprend à la position exacte |
| Recommencer depuis le début | Retourne à la séquence 1 |
Étant donné que j'ai une progression sauvegardée depuis 30 jours
Quand j'essaie de reprendre le guide
Alors la sauvegarde est considérée comme expirée Et je recommence depuis la séquence 1 Et je vois le message "Votre précédente visite date de plus de 30 jours. Recommençons depuis le début."
Étant donné que j'écoute un guide sur mon iPhone à la séquence 4
Quand je ferme l'app et ouvre sur mon iPad
Alors je vois la progression synchronisée Et je peux reprendre à la séquence 4 sur l'iPad
Étant donné que j'écoute la dernière séquence (12/12)
Quand cette séquence se termine
Alors le guide est marqué "✅ Terminé" dans mon historique Et je vois un message de félicitation: Et le créateur gagne les statistiques d'écoute complète
Étant donné que je suis un créateur
Quand je crée un nouvel audio-guide
Alors je dois:
| étape | détail |
|---|---|
| Uploader plusieurs fichiers | 1 fichier MP3 par séquence |
| Numéroter les séquences | Séquence 1, Séquence 2, etc. |
| Titrer chaque séquence | "Introduction", "La Joconde", etc. |
| Définir point GPS unique | Centre du lieu (ex: Louvre) |
| Définir rayon de détection | Par défaut 100m |
Et la durée totale est calculée automatiquement
Étant donné qu'un créateur publie un audio-guide du Louvre
Quand les métadonnées sont stockées en base
Alors le format JSON contient:
Étant donné que je crée un audio-guide
Quand j'essaie d'ajouter plus de 50 séquences
Alors je vois le message "Maximum 50 séquences par audio-guide" Et je dois structurer mon contenu différemment ou créer plusieurs guides
Étant donné que j'écoute la séquence 6
Quand je tape sur le bouton "×" (fermer)
Alors je vois une confirmation: Et si je confirme, la progression est enregistrée Et je retourne à l'écran principal
Étant donné que je suis créateur d'un audio-guide
Quand je consulte mes statistiques
Alors je vois:
| métrique | exemple valeur |
|---|---|
| Nombre de démarrages | 1250 |
| Nombre de complétions (100%) | 387 (31%) |
| Séquence la plus skippée | Séquence 8 |
| Durée moyenne d'écoute | 28 min (sur 45) |
Étant donné qu'un créateur peut publier plusieurs versions linguistiques
Quand un touriste anglophone visite le Louvre
Alors il voit les guides disponibles en anglais Et peut choisir parmi les guides traduits Mais cette fonctionnalité n'est pas disponible en MVP
Étant donné que je suis un utilisateur gratuit Et que j'écoute un audio-guide
Quand je passe de la séquence 5 à la séquence 6
Alors une publicité peut être insérée (1 pub toutes les 5 séquences) Et la publicité est skippable après 5 secondes Et les utilisateurs Premium ne voient pas de publicité
Étant donné que j'ai téléchargé un audio-guide complet
Quand je visite le lieu sans connexion internet
Alors toutes les séquences sont disponibles hors ligne Et la navigation fonctionne normalement Et seule la sauvegarde cloud est différée jusqu'à reconnexion
Étant donné que j'ai terminé un audio-guide
Quand je ferme l'interface
Alors je vois une popup "Notez cette visite" Et je peux donner une note de 1 à 5 étoiles Et cette note contribue à la note globale visible par les autres utilisateurs
Étant donné que plusieurs audio-guides sont disponibles en différentes langues
Quand j'accède à la page de sélection
Alors je peux filtrer par langue Et par défaut, les guides dans ma langue système sont affichés en premier
Étant donné qu'un audio-guide est techniquement un contenu structuré
Alors il réutilise:
| composant | usage |
|---|---|
| Stockage Bunny | Hébergement fichiers MP3 séquences |
| Streaming HLS | Diffusion audio adaptative |
| Cache Redis | Métadonnées guides + progressions |
| PostgreSQL | Stockage structure JSON guides |
Et aucune infrastructure dédiée n'est nécessaire
En tant qu'auditeur Je veux que les contenus de mes créateurs suivis soient favorisés Afin de ne pas rater leurs publications tout en découvrant de nouveaux contenus
16 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et que je suis connecté en tant qu'auditeur Et que je suis abonné au créateur "JeanDupont"
Étant donné un contenu du créateur "JeanDupont" avec:
| score_geo | 0.5 |
|---|---|
| score_interet | 0.6 |
| score_engage | 0.5 |
Quand le score final est calculé
Alors le score de base est 0.53 Et le boost abonnement de +30% est appliqué Et le score final avec boost est 0.69
Étant donné que je suis à Paris Et que 2 contenus sont disponibles:
| contenu | createur_suivi | score_geo | score_interet | score_engage | score_final_base | score_avec_boost |
|---|---|---|---|---|---|---|
| Contenu A | Non | 0.9 | 0.8 | 0.7 | 0.80 | 0.80 |
| Contenu B | Oui | 0.5 | 0.6 | 0.5 | 0.53 | 0.69 |
Quand l'algorithme sélectionne le prochain contenu
Alors le Contenu A est proposé en premier
Étant donné que je suis à Paris Et que 2 contenus sont disponibles:
| contenu | createur_suivi | score_final_base | score_avec_boost |
|---|---|---|---|
| Contenu A | Non | 0.70 | 0.70 |
| Contenu B | Oui | 0.60 | 0.78 |
Quand l'algorithme sélectionne le prochain contenu
Alors le Contenu B est proposé en premier
Étant donné que je suis abonné au créateur "CreateurMoyen" Et qu'il publie un contenu avec très faible engagement (score 0.30) Et qu'un contenu viral d'un créateur non-suivi a un score de 0.85
Quand l'algorithme sélectionne le prochain contenu
Alors le contenu viral est proposé en premier (0.85)
Étant donné que je suis abonné à 50 créateurs
Quand l'algorithme génère ma file d'attente de 5 contenus
Alors les contenus suivis et non-suivis sont mélangés Et tous entrent en compétition selon leurs scores (avec boost si abonnement) Et il n'y a pas de section séparée "Contenus de vos abonnements"
Étant donné un contenu d'un créateur suivi Et que le score final de base est calculé à 0.65
Quand le boost abonnement est appliqué
Alors le multiplicateur utilisé est exactement 1.3 Et le score final avec boost est 0.845 (0.65 × 1.3) Et le résultat est arrondi à 2 décimales: 0.85
Étant donné que je suis abonné au créateur "JeanDupont" Et qu'il a publié 10 contenus différents
Quand l'algorithme évalue chacun de ces contenus
Alors le boost de +30% est appliqué à tous les 10 contenus Et chaque contenu bénéficie du même multiplicateur 1.3
Étant donné que je suis abonné à "Créateur A" et "Créateur B" Et que les 2 ont des contenus disponibles dans ma zone:
| createur | score_base | score_avec_boost |
|---|---|---|
| Créateur A | 0.70 | 0.91 |
| Créateur B | 0.65 | 0.85 |
Quand l'algorithme sélectionne le prochain contenu
Alors le contenu du Créateur A est proposé en premier (0.91 > 0.85) Et les 2 bénéficient du boost, mais le meilleur score gagne
Étant donné que je suis abonné à "MediaNational" Et qu'il publie un contenu de type "National" (score_geo 0.2)
Quand le score est calculé avec:
| score_geo | score_interet | score_engage |
|---|---|---|
| 0.2 | 0.7 | 0.6 |
Alors le score de base est environ 0.50 Et avec le boost abonnement, le score devient 0.65 Et le contenu peut être proposé malgré son score géo faible
Quand j'accède aux paramètres de l'algorithme de recommandation
Alors je vois l'information: "Les contenus de vos créateurs suivis bénéficient d'un boost de +30%" Et je comprends que ce n'est pas une priorité absolue Et que la découverte de nouveaux contenus reste possible
Étant donné que je suis abonné au créateur "JeanDupont" Et qu'un de ses contenus bénéficiait du boost +30%
Quand je me désabonne de "JeanDupont"
Alors ses contenus n'ont plus le boost Et leur score revient au score de base sans multiplicateur
Étant donné que je viens de m'abonner à "NouveauCreateur" Et qu'il a publié un contenu il y a 2 jours
Quand l'algorithme recalcule les scores
Alors le boost de +30% est immédiatement appliqué à ce contenu Et il peut apparaître dans ma prochaine file d'attente
Étant donné que je suis abonné à 30 créateurs Et que j'écoute 100 contenus sur une semaine
Quand j'analyse la répartition
Alors environ 40-50% des contenus proviennent de créateurs suivis Et 50-60% proviennent de créateurs non-suivis (découverte)
Étant donné que je suis à Paris Et que je suis abonné à un créateur de Marseille Et qu'il publie un contenu ancré à Marseille (hors de portée)
Quand l'algorithme évalue ce contenu
Alors le score géo est quasi nul (0.05) Et même avec boost +30%, le score reste très faible Et le contenu n'est probablement pas proposé
Étant donné que je suis abonné à 100 créateurs Et que l'algorithme évalue 1000 contenus potentiels
Quand le calcul des scores avec boost est effectué
Alors le temps de calcul reste inférieur à 50ms Et la requête SQL utilise un JOIN sur la table abonnements
Étant donné un contenu d'un créateur suivi Et que le contenu bénéficie aussi de:
| facteur | impact |
|---|---|
| Score d'engagement élevé | +20% |
| Contenu récent (<24h) | +10% |
| Boost abonnement | +30% |
Quand le score final est calculé
Alors le boost abonnement s'applique au score final (après tous les autres calculs) Et les boosts ne s'additionnent pas, le boost abonnement est un multiplicateur final
En tant qu'auditeur Je veux gérer mes abonnements de manière équilibrée Afin de suivre mes créateurs préférés sans être submergé
27 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et que je suis connecté en tant qu'auditeur
Étant donné que je suis abonné à 199 créateurs
Quand j'essaie de m'abonner à un 200ème créateur
Alors l'abonnement réussit Et je suis maintenant abonné à 200 créateurs
Étant donné que je suis déjà abonné à 200 créateurs
Quand j'essaie de m'abonner à un nouveau créateur
Alors l'action échoue Et je vois le message:
Étant donné que je suis abonné à 200 créateurs Et que j'essaie de m'abonner à un nouveau créateur
Quand je vois le message de limite atteinte
Alors je vois aussi une suggestion: Et un bouton "Désabonner" est proposé pour ce créateur
Étant donné que je suis abonné à 150 créateurs
Quand j'accède à ma liste d'abonnements
Alors je peux trier par:
| critère | ordre |
|---|---|
| Date d'abonnement | Plus récent / Plus ancien |
| Nombre de contenus écoutés | Plus écoutés / Moins écoutés |
| Dernière activité créateur | Plus récent / Plus ancien |
| Ordre alphabétique | A-Z / Z-A |
Étant donné que mes jauges d'intérêt sont:
| catégorie | valeur initiale |
|---|---|
| Automobile | 60% |
| Voyage | 55% |
Et qu'un créateur tague ses contenus "Automobile" et "Voyage"
Quand je m'abonne à ce créateur
Alors mes jauges évoluent:
| catégorie | nouvelle valeur |
|---|---|
| Automobile | 65% (+5%) |
| Voyage | 60% (+5%) |
Étant donné qu'un créateur tague ses contenus:
| tags |
|---|
| Automobile, Voyage, Technologie |
Et que mes jauges sont toutes à 50%
Quand je m'abonne à ce créateur
Alors les 3 jauges augmentent de +5%:
| catégorie | nouvelle valeur |
|---|---|
| Automobile | 55% |
| Voyage | 55% |
| Technologie | 55% |
Étant donné que je suis abonné à un créateur avec tags "Politique" et "Économie" Et que mes jauges sont:
| catégorie | valeur actuelle |
|---|---|
| Politique | 70% |
| Économie | 65% |
Quand je me désabonne de ce créateur
Alors mes jauges évoluent:
| catégorie | nouvelle valeur |
|---|---|
| Politique | 65% (-5%) |
| Économie | 60% (-5%) |
Étant donné que je consulte le profil d'un créateur suivi
Quand je clique sur "Se désabonner"
Alors le désabonnement est immédiat Et aucune popup de confirmation n'apparaît
Étant donné que je viens de me désabonner d'un créateur
Quand je consulte à nouveau son profil
Alors le bouton "S'abonner" est affiché Et je peux me réabonner immédiatement Et mes jauges augmentent à nouveau de +5%
Étant donné qu'un créateur a les tags "Musique" et "Culture" Et que ma jauge Musique est à 50%
Quand je m'abonne puis me désabonne immédiatement
Alors ma jauge revient exactement à 50% Et il n'y a pas de perte ou gain net
Étant donné que ma jauge Automobile est à 97% Et qu'un créateur tague ses contenus "Automobile"
Quand je m'abonne à ce créateur
Alors ma jauge Automobile passe à 100% (limite max) Et l'augmentation effective est de +3% seulement
Étant donné que ma jauge Politique est à 3% Et que je suis abonné à un créateur avec tag "Politique"
Quand je me désabonne de ce créateur
Alors ma jauge Politique passe à 0% (limite min) Et la diminution effective est de -3% seulement
Étant donné que je suis abonné au créateur "JeanDupont"
Quand "JeanDupont" consulte ses statistiques
Alors il voit le nombre total d'abonnés (ex: "1,247 abonnés") Mais il ne voit pas la liste des utilisateurs abonnés Et mon identité reste privée
Étant donné que je suis créateur Et que j'ai 523 abonnés
Quand je consulte mes statistiques
Alors je vois "523 abonnés" Mais je ne peux pas:
| action interdite |
|---|
| Voir la liste des abonnés |
| Contacter mes abonnés individuellement |
| Voir leurs profils |
Étant donné que je suis abonné au créateur "Alice" Et qu'"Alice" est abonnée à mon compte créateur
Quand je consulte le profil d'"Alice"
Alors je ne vois pas d'indication qu'elle est abonnée à moi Et il n'y a pas de badge "Abonné mutuellement"
Étant donné que je suis abonné à 200 créateurs
Quand l'algorithme calcule ma recommandation
Alors la requête SQL utilise un JOIN sur la table abonnements Et la table est indexée sur user_id et creator_id Et le temps de calcul reste inférieur à 50ms
Étant donné que je suis abonné à 150 créateurs très actifs Et qu'ils publient collectivement 100 contenus par jour
Quand l'algorithme génère ma file de 5 contenus
Alors environ 60-70% des contenus proviennent de créateurs suivis (grâce au boost +30%) Mais 30-40% proviennent de nouveaux créateurs (découverte)
Étant donné que je me désabonne d'un créateur
Alors le créateur ne reçoit aucune notification Et il ne peut pas savoir qui s'est désabonné
Étant donné que je suis abonné à 87 créateurs
Quand j'accède à mes statistiques d'abonnements
Alors je vois:
| métrique | exemple valeur |
|---|---|
| Nombre total d'abonnements | 87 / 200 |
| Créateurs les plus écoutés | Top 10 avec % écoute |
| Créateurs non écoutés depuis 6 mois | 12 créateurs |
| Nouveaux contenus non écoutés | 23 contenus |
Étant donné que je suis abonné à 120 créateurs
Quand j'accède à ma liste d'abonnements
Alors je peux chercher par nom de créateur Et les résultats sont filtrés en temps réel Et je trouve rapidement un créateur spécifique
Étant donné que je demande l'export de mes données
Quand l'export est généré
Alors la liste de mes abonnements est incluse:
Étant donné que je suis abonné à 50 créateurs
Quand je supprime définitivement mon compte
Alors tous mes abonnements sont supprimés Et le compteur d'abonnés de chaque créateur est décrémenté de -1 Et les jauges n'existent plus (données supprimées)
Étant donné que je suis abonné au créateur "Bob"
Quand "Bob" supprime son compte créateur
Alors je suis automatiquement désabonné Et mes jauges diminuent de -5% pour les tags de "Bob" Et je ne vois plus "Bob" dans ma liste d'abonnements
Étant donné que la moyenne d'abonnements sur YouTube est de ~50-100 chaînes Et que Twitter limite à 5000 follows (mais moyenne ~150)
Quand RoadWave fixe la limite à 200
Alors cela couvre largement 99% des utilisateurs Et évite les abus (comptes spam suivant tout le monde)
Étant donné la structure de table subscriptions:
Alors les requêtes d'abonnements sont O(1) avec index Et le count d'abonnés par créateur est rapide Et la vérification "est abonné ?" est instantanée
Étant donné qu'un utilisateur s'abonne à 200 créateurs en moins de 5 minutes
Quand le système détecte cette activité suspecte
Alors un rate limiting est appliqué (max 10 abonnements/minute) Et l'utilisateur voit "Trop d'actions rapides. Veuillez réessayer dans 1 minute" Et cela prévient les bots de spam
Étant donné que je suis abonné à 3 créateurs dont 1 vérifié
Quand je consulte ma liste d'abonnements
Alors le créateur vérifié a un badge ✓ bleu Et les créateurs non vérifiés n'ont pas de badge
En tant qu'auditeur Je veux recevoir des notifications adaptées à mon contexte Afin d'être informé sans être distrait en conduisant
28 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et que je suis connecté en tant qu'auditeur Et que j'ai activé les notifications
Étant donné que ma vitesse GPS est de 50 km/h
Quand le système détecte mon contexte
Alors je suis identifié comme "En voiture" Et les notifications push sont désactivées Et seules les notifications in-app sont actives
Étant donné que ma vitesse GPS est de 3 km/h
Quand le système détecte mon contexte
Alors je suis identifié comme "À pied" Et les notifications push sont activées Et l'interface tactile et vocale sont disponibles
Étant donné que ma vitesse GPS varie entre 5 et 10 km/h
Quand le système détecte mon contexte
Alors un algorithme de lissage est appliqué sur 30 secondes Et le mode est déterminé selon la vitesse moyenne Et les changements de mode ne sont pas trop fréquents
Étant donné que je suis en voiture (vitesse >10 km/h) Et que je suis abonné au créateur "JeanDupont"
Quand "JeanDupont" publie un nouveau contenu dans ma zone
Alors je ne reçois pas de notification push Mais je vois un badge compteur in-app Et le contenu apparaît dans ma file avec boost +30%
Étant donné que je suis à pied (vitesse <5 km/h) Et que je suis abonné au créateur "JeanDupont" Et que je suis situé en Île-de-France
Quand "JeanDupont" publie un contenu géolocalisé en Île-de-France
Alors je reçois une notification push:
Étant donné que je suis en voiture Et que je suis abonné au créateur "RadioLive"
Quand "RadioLive" démarre un live dans ma zone
Alors je ne reçois pas de notification push Mais je vois un badge compteur in-app Et le live peut apparaître dans ma recommandation automatiquement
Étant donné que je suis à pied Et que je suis abonné au créateur "RadioLive" Et que je suis situé dans la zone du live
Quand "RadioLive" démarre un live
Alors je reçois une notification push:
Étant donné que je suis à pied
Quand je passe à moins de 100m d'un lieu avec audio-guides
Alors je reçois une notification push:
Étant donné que je suis en voiture
Quand je passe à moins de 100m d'un lieu avec audio-guides
Alors je reçois une notification audio (bip) Et une annonce vocale: "Audio-guide disponible" Mais pas de notification push (sécurité)
Étant donné que je suis abonné au créateur "CreateurMarseille" Et que je suis situé à Paris
Quand "CreateurMarseille" publie un contenu ancré à Marseille
Alors je ne reçois pas de notification Et cela évite la frustration de contenus non écoutables
Étant donné que je suis abonné au créateur "MediaNational" Et que je suis situé n'importe où en France
Quand "MediaNational" publie un contenu de type "National"
Alors je reçois une notification (si mode piéton)
Étant donné que je suis abonné à 50 créateurs actifs Et que j'ai déjà reçu 10 notifications push aujourd'hui
Quand un 11ème contenu est publié
Alors je ne reçois pas de notification push individuelle Mais une notification groupée: "🎧 3 nouveaux contenus de créateurs suivis"
Étant donné que la limite par défaut est de 10 notifications/jour
Quand j'accède aux paramètres de notifications
Alors je peux modifier la limite entre 5 et 20 Et si je choisis 15, je recevrai jusqu'à 15 notifications/jour
Étant donné que le mode silencieux est activé de 22h à 8h par défaut Et qu'il est 23h30
Quand un créateur suivi publie un contenu
Alors je ne reçois pas de notification push Mais les notifications sont empilées Et je les vois le lendemain matin à 8h01
Étant donné que le mode silencieux est activé (22h-8h) Et qu'il est 23h00 Et que j'ai activé "Notifications importantes uniquement" (lives uniquement)
Quand un créateur suivi démarre un live
Alors je reçois quand même la notification push du live
Étant donné que j'accède aux paramètres de notifications
Quand je désactive toutes les notifications
Alors je ne reçois plus aucune notification push Et les badges in-app sont également désactivés Et seule la recommandation algorithmique reste active
Étant donné que je crée un nouveau compte Et que je m'abonne à mon premier créateur
Quand je consulte les préférences de notifications
Alors "Nouveaux contenus" est activé par défaut Et "Lives" est activé par défaut Et "Audio-guides proximité" est activé par défaut
Étant donné que j'ai activé toutes les notifications
Quand je désactive uniquement "Nouveaux contenus"
Alors je ne reçois plus de notifications pour nouveaux contenus Mais je reçois toujours les notifications de lives Et les notifications d'audio-guides restent actives
Étant donné que j'ai reçu 10 notifications push aujourd'hui Et que 5 nouveaux contenus sont publiés dans l'heure suivante
Quand la 11ème notification devrait être envoyée
Alors les 5 contenus sont regroupés en une seule notification:
Étant donné que j'ai reçu une notification groupée "3 nouveaux contenus"
Quand je tape sur la notification
Alors l'app s'ouvre sur une liste des 3 contenus:
| créateur | titre |
|---|---|
| JeanDupont | "Actualité du jour" |
| MarieDurand | "Podcast économie" |
| PaulMartin | "Anecdote historique" |
Et je peux choisir lequel écouter en premier
Étant donné que le mode silencieux est 22h-8h par défaut
Quand j'accède aux paramètres
Alors je peux modifier les heures: par exemple 23h-7h Et le mode silencieux s'applique dans la nouvelle plage horaire
Étant donné que je suis à pied Et qu'un créateur suivi publie un contenu
Quand je reçois la notification push
Alors elle contient:
| élément | exemple |
|---|---|
| Emoji | 🎧 |
| Créateur | JeanDupont |
| Action | a publié |
| Titre | "Les secrets du Louvre" |
| CTA | Tap pour écouter |
Étant donné que je suis à pied Et qu'un créateur suivi démarre un live
Quand je reçois la notification push
Alors elle contient:
| élément | exemple |
|---|---|
| Emoji | 🔴 |
| Créateur | RadioLive |
| Action | est en direct |
| Titre | "Débat politique ce soir" |
| CTA | Tap pour rejoindre |
Étant donné que j'ai reçu une notification pour un contenu Et que je n'ai pas encore tapé dessus
Quand le créateur supprime le contenu
Alors la notification est automatiquement retirée de mon centre de notifications Et si je tape dessus par erreur, je vois "Contenu non disponible"
Étant donné que je suis en voiture Et que 5 créateurs suivis publient des contenus
Quand j'ouvre l'application
Alors je vois un badge "5" sur l'onglet "Nouveautés" Et en consultant l'onglet, je vois les 5 nouveaux contenus Et le badge disparaît après consultation
Étant donné que je reçois 10 notifications push par jour Et que je suis actif 365 jours par an
Quand le système calcule le coût
Alors 3650 notifications/an sont envoyées Et Firebase Cloud Messaging est gratuit jusqu'à plusieurs millions de notifications Et le coût reste 0€ pour le volume MVP/Growth
Étant donné que je reçois une notification push pour un contenu
Quand je tape sur la notification
Alors l'app s'ouvre directement sur le contenu Et la lecture démarre automatiquement (si j'étais à pied)
Étant donné que j'ai désactivé les notifications dans les paramètres iOS/Android
Quand un créateur suivi publie un contenu
Alors aucune notification push n'est envoyée Et l'app propose de réactiver les permissions dans les paramètres Mais les badges in-app continuent de fonctionner
En tant que créateur de contenu Je veux créer des audio-guides avec plusieurs séquences géolocalisées Afin d'offrir des expériences guidées adaptées aux différents modes de déplacement
35 scénarios (32 standards, 3 plans)
Contexte commun à tous les scénarios
Étant donné que l'application RoadWave est démarrée Et que le créateur "guide@example.com" est connecté Et que son compte est vérifié
Étant donné que l'utilisateur se déplace à
Quand la vitesse est calculée sur 30 secondes
Alors le mode
📊 Exemples de données:
| vitesse | mode |
|---|---|
| 3 | Piéton |
| 15 | Vélo |
| 35 | Voiture |
| 50 | Voiture |
Étant donné qu'un audio-guide "Safari du Paugre" est disponible Et que l'utilisateur se déplace à 35 km/h
Quand l'audio-guide démarre
Alors une popup s'affiche:
Étant donné que le mode "Voiture" est suggéré automatiquement
Quand l'utilisateur clique sur "Changer"
Alors les 4 modes sont proposés:
| mode | emoji |
|---|---|
| Piéton | 🚶 |
| Voiture | 🚗 |
| Vélo | 🚴 |
| Transport | 🚌 |
Étant donné un audio-guide configuré en mode
Alors les paramètres suivants sont appliqués:
| paramètre | valeur |
|---|---|
| Vitesse détection | <vitesse_detection> |
| Déclenchement | <declenchement> |
📊 Exemples de données:
| mode | vitesse_detection | declenchement |
|---|---|---|
| Piéton | <5 km/h | Manuel (bouton Suivant) |
| Voiture | >10 km/h | Auto GPS + Manuel |
| Vélo | 5-25 km/h | Auto GPS + Manuel |
| Transport | Variable | Auto GPS + Manuel |
Étant donné que le créateur est sur son dashboard
Quand il clique sur "Créer un audio-guide"
Alors le formulaire de création s'affiche Et le wizard guidé en 4 étapes est visible:
| étape | description |
|---|---|
| 1 | Infos générales |
| 2 | Ajout séquences |
| 3 | Preview carte |
| 4 | Validation modération |
Étant donné que le créateur est sur l'étape 1 du wizard
Quand il complète le formulaire
Alors les champs suivants sont obligatoires:
| champ | contrainte |
|---|---|
| Titre | 5-100 caractères |
| Description | 10-500 caractères |
| Mode de déplacement | Choix parmi 4 |
| Tags | 1-3 tags |
| Classification âge | Tout public/13+/16+/18+ |
Étant donné que le créateur crée un audio-guide
Quand il sélectionne le mode "🚗 Voiture (GPS auto + manuel)"
Alors le champ "Vitesse recommandée" s'affiche Et la plage suggérée est "30-50 km/h"
Étant donné que le créateur entre un titre
Quand le titre contient moins de 5 caractères
Alors un message d'erreur "Minimum 5 caractères" s'affiche Et le bouton "Suivant" est désactivé
Étant donné que le créateur entre une description
Quand la description contient 520 caractères
Alors un message d'erreur "Maximum 500 caractères" s'affiche Et les 20 caractères en trop sont surlignés en rouge
Étant donné que le créateur est sur l'étape 2 "Ajout séquences"
Quand il clique sur "Ajouter séquence"
Alors le formulaire de séquence s'affiche avec:
| champ | requis | note |
|---|---|---|
| Titre séquence | ✅ | 5-80 caractères |
| Audio | ✅ | Upload MP3/AAC, max 200 MB |
| Point GPS | ✅* | *Sauf mode piéton |
| Rayon déclenchement | ✅* | *Sauf mode piéton, 10-200m |
Étant donné que le créateur ajoute une séquence en mode "Voiture"
Quand il clique sur "📍 Ajouter point GPS"
Alors une carte s'affiche Et il peut:
| action |
|---|
| Cliquer sur la carte |
| Entrer coordonnées manuelles |
| Utiliser sa position actuelle |
Étant donné qu'un point GPS est défini à (43.1234, 2.5678)
Quand le créateur ajuste le curseur de rayon
Alors le rayon varie de 10m à 200m Et un cercle visuel est affiché sur la carte Et la valeur actuelle s'affiche "30m"
Étant donné un audio-guide en mode
Quand le créateur ajoute un point GPS
Alors le rayon par défaut est
📊 Exemples de données:
| mode | rayon_defaut |
|---|---|
| Voiture | 30m |
| Vélo | 50m |
| Transport | 100m |
Étant donné un audio-guide en mode "Voiture" avec vitesse recommandée 30 km/h
Quand le créateur ajoute un point GPS
Alors une suggestion s'affiche: "Recommandé : 30m pour voiture à 30 km/h"
Étant donné que le créateur crée une séquence "Introduction"
Quand il upload un fichier audio de 5 MB
Alors le fichier est vérifié:
| vérification | règle |
|---|---|
| Format | MP3, AAC, M4A |
| Taille max | 200 MB |
| Durée max | 15 minutes |
Étant donné un audio-guide avec 5 séquences:
| ordre | titre |
|---|---|
| 1 | Introduction |
| 2 | Les lions |
| 3 | Les girafes |
| 4 | Les éléphants |
| 5 | Conclusion |
Quand le créateur glisse "Les éléphants" en position 2
Alors l'ordre devient:
| ordre | titre |
|---|---|
| 1 | Introduction |
| 2 | Les éléphants |
| 3 | Les lions |
| 4 | Les girafes |
| 5 | Conclusion |
Étant donné un audio-guide avec seulement 1 séquence
Quand le créateur tente de passer à l'étape suivante
Alors un message d'erreur s'affiche: "Minimum 2 séquences requis" Et le bouton "Suivant" est désactivé
Étant donné un audio-guide avec 50 séquences
Quand le créateur tente d'ajouter une 51ème séquence
Alors un message d'erreur s'affiche: "Maximum 50 séquences par audio-guide" Et le bouton "+ Ajouter séquence" est désactivé
Étant donné un audio-guide avec 5 séquences géolocalisées
Quand le créateur accède à l'étape 3 "Preview carte"
Alors une carte Leaflet s'affiche Et les éléments suivants sont visibles:
| élément | description |
|---|---|
| Markers numérotés | 1, 2, 3, 4, 5 sur chaque point |
| Tracé entre points | Ligne pointillée connectant les points |
| Cercles de déclenchement | Rayon visuel autour de chaque point |
Étant donné un audio-guide avec les séquences suivantes:
| séquence | durée | distance_au_suivant |
|---|---|---|
| 1 | 2:15 | 150m |
| 2 | 3:42 | 200m |
| 3 | 4:10 | 320m |
Quand les statistiques sont calculées
Alors le résumé suivant est affiché:
| métrique | valeur |
|---|---|
| Séquences | 3 complètes |
| Durée totale | 10:07 |
| Distance totale | 670m |
Étant donné que la preview carte est affichée
Quand le créateur clique sur le marker "2"
Alors une popup s'affiche avec:
| information |
|---|
| Titre: "Les lions" |
| Durée: 3:42 |
| Rayon: 30m |
| [✏️ Modifier] |
| [🗑️ Supprimer] |
Étant donné un audio-guide avec des points dans Paris
Quand le créateur définit la zone de diffusion
Alors il peut choisir:
| type | exemple |
|---|---|
| Polygon | Tracé manuel sur carte |
| Ville | Paris (API Nominatim) |
| Département | 75 - Paris |
| Région | Île-de-France |
Étant donné un créateur qui publie ses 3 premiers audio-guides
Quand il clique sur "✅ Publier audio-guide"
Alors un message s'affiche:
Étant donné un créateur ayant publié 5 audio-guides validés Et aucun strike actif
Quand il publie un nouvel audio-guide
Alors l'audio-guide est publié immédiatement Et il devient visible pour les utilisateurs Et aucune validation manuelle n'est requise
Étant donné un audio-guide en mode "🚶 Piéton"
Quand le créateur ajoute une séquence
Alors le champ "Point GPS" est optionnel Et le champ "Rayon déclenchement" est masqué Et un message info s'affiche: "Mode manuel : les séquences se déclenchent au clic utilisateur"
Étant donné que le créateur édite un audio-guide depuis 5 minutes
Quand il ajoute une nouvelle séquence
Alors l'audio-guide est sauvegardé en brouillon automatiquement Et un toast "Brouillon sauvegardé" s'affiche brièvement
Étant donné un audio-guide en brouillon "Safari du Paugre" Et qu'il contient 3 séquences complètes
Quand le créateur retourne sur son dashboard
Alors le brouillon est visible avec le statut "📝 Brouillon" Et un bouton "Continuer" est disponible Et la progression "3/5 séquences" est affichée
Étant donné un audio-guide en brouillon
Quand le créateur clique sur "🗑️ Supprimer"
Alors une confirmation s'affiche:
Étant donné un audio-guide publié "Safari du Paugre"
Quand le créateur clique sur "✏️ Modifier"
Alors il peut modifier:
| élément modifiable | élément non modifiable |
|---|---|
| Titre | Mode de déplacement |
| Description | Points GPS |
| Tags | Rayons déclenchement |
| Séquences (ordre) | |
Et un avertissement s'affiche: "Les modifications structurelles nécessitent une nouvelle publication"
Étant donné un audio-guide publié "Visite Paris"
Quand le créateur clique sur "📋 Dupliquer"
Alors une copie est créée avec le titre "Visite Paris (copie)" Et toutes les séquences sont copiées Et le statut est "📝 Brouillon" Et le créateur peut modifier avant publication
Étant donné que le créateur upload un fichier "audio.wav"
Quand le format est vérifié
Alors un message d'erreur s'affiche: "Format non supporté. Utilisez MP3, AAC ou M4A" Et le fichier est rejeté
Étant donné que le créateur upload un fichier de 250 MB
Quand la taille est vérifiée
Alors un message d'erreur s'affiche: "Fichier trop volumineux. Maximum 200 MB" Et le fichier est rejeté
Étant donné un audio-guide en mode "Piéton" Et une séquence au Louvre (Paris)
Quand le créateur ajoute une séquence à Lyon
Alors un avertissement s'affiche:
Étant donné que le créateur édite un audio-guide Et que la connexion réseau est perdue
Quand il tente de sauvegarder
Alors le brouillon est sauvegardé localement Et un message s'affiche: "Sauvegarde locale. Sera synchronisée à la reconnexion" Et une icône "☁️ Hors ligne" s'affiche
Étant donné un brouillon sauvegardé localement
Quand la connexion réseau est rétablie
Alors le brouillon est synchronisé automatiquement Et un toast "✅ Audio-guide synchronisé" s'affiche
En tant qu'utilisateur Je veux utiliser les audio-guides avec toutes les fonctionnalités de l'app Afin d'avoir une expérience complète et cohérente
39 scénarios (38 standards, 1 plan)
Contexte commun à tous les scénarios
Étant donné que l'application RoadWave est démarrée Et que l'utilisateur "jean@example.com" est connecté
Étant donné un audio-guide "Visite du Louvre" avec 12 séquences
Quand l'utilisateur clique sur "⬇️ Télécharger pour écouter hors ligne"
Alors toutes les 12 séquences sont téléchargées Et les métadonnées (titres, descriptions, GPS) sont sauvegardées Et les images (cover, miniatures) sont mises en cache
Étant donné qu'un téléchargement d'audio-guide est en cours
Quand l'utilisateur consulte l'état
Alors la progression s'affiche:
Étant donné que l'option "Télécharger uniquement en WiFi" est activée
Quand l'utilisateur lance un téléchargement sur réseau mobile
Alors un avertissement s'affiche:
Étant donné que l'appareil a 500 MB d'espace libre Et qu'un audio-guide pèse 380 MB
Quand l'utilisateur lance le téléchargement
Alors un avertissement s'affiche:
Étant donné que l'utilisateur a téléchargé 3 audio-guides
Quand il accède à "Bibliothèque > Téléchargés"
Alors il voit:
| audio_guide | taille | date_telechargement |
|---|---|---|
| Visite du Louvre | 380 MB | 2026-01-20 |
| Safari du Paugre | 245 MB | 2026-01-18 |
| Circuit Loire à Vélo | 520 MB | 2026-01-15 |
Étant donné qu'un audio-guide est téléchargé Et que l'utilisateur active le mode avion
Quand il lance l'audio-guide
Alors toutes les séquences sont lisibles Et les métadonnées sont accessibles Et les images s'affichent normalement Et la progression est sauvegardée localement
Étant donné qu'un audio-guide voiture est téléchargé Et que le mode avion est activé (avec GPS actif)
Quand l'utilisateur se déplace
Alors les déclenchements GPS fonctionnent normalement Et la distance/ETA sont calculés
Étant donné qu'un audio-guide téléchargé pèse 380 MB
Quand l'utilisateur clique sur "🗑️ Supprimer téléchargement"
Alors une confirmation s'affiche Et si confirmé, les 380 MB sont libérés Et l'audio-guide reste accessible en streaming
Étant donné qu'un audio-guide téléchargé a été mis à jour par le créateur
Quand l'utilisateur se connecte en WiFi
Alors une notification s'affiche:
Étant donné que l'utilisateur consulte un audio-guide
Quand il clique sur "➕ Ajouter à une playlist"
Alors ses playlists s'affichent:
| playlist |
|---|
| 🗺️ Voyages en France |
| 🏛️ Musées parisiens |
| + Créer nouvelle playlist |
Étant donné une playlist contenant 2 audio-guides et 1 podcast
Quand la lecture atteint un audio-guide
Alors l'audio-guide démarre à la séquence 1 (ou progression sauvegardée) Et les séquences se jouent normalement
Quand l'audio-guide se termine (dernière séquence)
Alors le contenu suivant de la playlist démarre
Étant donné qu'un utilisateur aime un audio-guide
Quand il clique sur "⭐ Ajouter aux favoris"
Alors l'audio-guide est ajouté à la section "Favoris" Et il est facilement accessible depuis le menu principal
Étant donné que RoadWave propose des collections éditoriales
Quand l'utilisateur accède à "Collections"
Alors il voit des collections comme:
| collection | nombre_audio_guides |
|---|---|
| 🏛️ Musées de France | 12 |
| 🦁 Parcs animaliers | 8 |
| 🚴 Circuits vélo | 15 |
| 🚗 Routes touristiques | 10 |
Étant donné qu'un utilisateur consulte un audio-guide
Quand il clique sur "⬆️ Partager"
Alors le menu de partage natif s'ouvre Et le lien généré est "https://roadwave.fr/share/ag/louvre_123"
Étant donné qu'un lien d'audio-guide partagé est ouvert sur le web
Quand la page se charge
Alors elle affiche:
| élément | exemple |
|---|---|
| Cover image 16:9 | Photo du Louvre |
| Titre | "Visite du Louvre" |
| Créateur | "@art_guide ✓" |
| Badge type | "🎧 Audio-guide • 12 séquences" |
| Durée totale | "45 minutes" |
| Mode | "🚶 Piéton" |
| Description | Texte complet |
| Preview séquence 1 | Player HTML5 (séquence intro) |
| Carte avec points GPS | Leaflet avec 12 markers |
| CTA téléchargement | Boutons App Store / Google Play |
Étant donné que l'app est installée Et qu'un lien "https://roadwave.fr/share/ag/louvre_123" est cliqué
Quand le système détecte l'app
Alors l'app s'ouvre directement sur l'audio-guide Et l'utilisateur peut démarrer immédiatement
Étant donné qu'un utilisateur est sur la séquence 5 "La Joconde"
Quand il partage l'audio-guide
Alors le lien généré est "https://roadwave.fr/share/ag/louvre_123?seq=5" Et le destinataire est dirigé vers la séquence 5 directement
Étant donné qu'un utilisateur termine un audio-guide
Quand la dernière séquence se termine
Alors une popup de notation s'affiche:
Étant donné qu'un audio-guide a reçu 150 notes Et que la moyenne est 4.3/5
Quand la page est affichée
Alors la note "⭐ 4.3 (150 avis)" est visible
Étant donné qu'un audio-guide a 50 commentaires
Quand l'utilisateur consulte les avis
Alors les commentaires sont triés par défaut selon:
| critère | poids |
|---|---|
| Note élevée | 30% |
| Récent | 30% |
| Likes reçus | 40% |
Étant donné qu'un utilisateur laisse un commentaire négatif
Quand le créateur consulte son dashboard
Alors il peut répondre au commentaire Et sa réponse apparaît en dessous avec badge "Créateur"
Étant donné qu'un utilisateur termine "Visite du Louvre"
Quand il consulte les recommandations
Alors l'algorithme suggère des audio-guides basés sur:
| critère | exemple |
|---|---|
| Tags similaires | #Art #Histoire #Musée |
| Créateur identique | Autres audio-guides de @art_guide |
| Localisation proche | Autres musées parisiens |
| Mode de déplacement | Autres audio-guides piéton |
Étant donné qu'un utilisateur est à Paris (GPS détecté)
Quand il ouvre l'onglet "Audio-guides"
Alors les audio-guides parisiens sont mis en avant Et un filtre "🗺️ Autour de moi" est pré-appliqué
Étant donné qu'un audio-guide a >100 écoutes dans la région Île-de-France Et que l'utilisateur est en Île-de-France
Quand l'audio-guide est affiché
Alors un badge "🔥 Populaire près de chez vous" est visible
Étant donné que la séquence 3 est en cours à 2:30/3:42
Quand il reste 60 secondes de lecture
Alors la séquence 4 est préchargée en arrière-plan Et la transition est instantanée (0 latence)
Étant donné qu'un utilisateur est sur réseau 4G
Quand la séquence démarre
Alors 30 secondes d'audio sont bufferisées initialement Et le buffering continue en arrière-plan
Étant donné qu'un utilisateur est sur réseau
Quand une séquence démarre
Alors
📊 Exemples de données:
| reseau | buffer_secondes |
|---|---|
| WiFi | 60 |
| 5G | 45 |
| 4G | 30 |
| 3G | 20 |
Étant donné qu'un utilisateur est sur connexion lente (3G)
Quand une séquence est streamée
Alors le CDN sert la version 64 kbps (au lieu de 128 kbps) Et la qualité reste acceptable pour la voix
Étant donné qu'un utilisateur a écouté les séquences 1-5
Quand il clique sur "Précédent" pour réécouter la séquence 4
Alors la séquence 4 est chargée depuis le cache local Et le chargement est instantané (pas de stream)
Étant donné que le cache audio occupe 500 MB Et que la limite configurée est 300 MB
Quand le nettoyage automatique s'exécute
Alors les séquences les plus anciennes (non téléchargées) sont supprimées Et le cache revient à 280 MB
Étant donné qu'un utilisateur écoute un audio-guide
Quand il interagit avec l'application
Alors les événements suivants sont trackés:
| é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 |
| paywall_displayed | audio_guide_id, sequence_number |
| premium_conversion | source: audio_guide_paywall |
Étant donné qu'un audio-guide a été écouté 1000 fois
Quand le créateur consulte la heatmap
Alors il voit pour chaque séquence:
| sequence | starts | completions | abandon_rate |
|---|---|---|---|
| 1 | 1000 | 950 | 5% |
| 2 | 950 | 920 | 3% |
| 3 | 920 | 850 | 8% |
| ... | ... | ... | ... |
| 12 | 650 | 580 | 11% |
Étant donné un audio-guide voiture avec 8 points GPS
Quand les statistiques sont calculées
Alors le créateur voit:
| mode_declenchement | nombre |
|---|---|
| GPS automatique | 542 |
| Manuel | 123 |
| Point manqué | 89 |
Étant donné un audio-guide avec seulement 1 séquence
Quand il est publié
Alors un avertissement s'affiche:
Étant donné qu'une séquence 5 a un fichier audio corrompu
Quand l'utilisateur tente de la lire
Alors un message d'erreur s'affiche Et un bouton "⏭️ Passer à la suivante" est disponible Et le créateur reçoit une notification de l'erreur
Étant donné un audio-guide voiture en cours Et que l'utilisateur désactive le GPS
Quand il le réactive 10 minutes plus tard
Alors le déclenchement automatique reprend Et les points GPS manqués entre-temps ne déclenchent pas de popup
Étant donné qu'un audio-guide a 50 utilisateurs en cours d'écoute
Quand le créateur modifie une séquence
Alors les utilisateurs actuels conservent l'ancienne version Et les nouveaux utilisateurs obtiennent la nouvelle version Et un message informe les utilisateurs lors de la prochaine ouverture
Étant donné qu'un audio-guide a 20 utilisateurs avec progression
Quand le créateur supprime l'audio-guide
Alors une confirmation stricte est demandée Et si confirmé, les progressions utilisateurs sont archivées (30 jours) Et l'audio-guide devient inaccessible
Étant donné qu'un utilisateur signale un audio-guide
Quand le signalement est modéré Et jugé valide
Alors l'audio-guide est dépublié temporairement Et le créateur reçoit une notification d'explication Et il peut corriger puis republier
En tant qu'utilisateur à pied Je veux naviguer manuellement entre les séquences d'un audio-guide Afin de contrôler mon rythme de visite
29 scénarios (28 standards, 1 plan)
Contexte commun à tous les scénarios
Étant donné que l'application RoadWave est démarrée Et que l'utilisateur "jean@example.com" est connecté (gratuit) Et qu'un audio-guide piéton "Visite du Louvre" est disponible avec 12 séquences
Étant donné que la séquence 1 "Introduction" est en cours de lecture
Quand la séquence se termine à 2:15
Alors le player se met en pause automatiquement Et le message suivant s'affiche: "Séquence 1 terminée. Appuyez sur Suivant quand vous êtes prêt." Et la barre de progression indique "1/12 complétée"
Étant donné que la séquence 1 est terminée et le player en pause
Quand l'utilisateur appuie sur le bouton [▶|] "Suivant"
Alors la séquence 2 "Pyramide du Louvre" démarre immédiatement Et aucune latence n'est observée
Étant donné que la séquence 5 se termine Et que c'est la 5ème séquence (1 pub toutes les 5)
Quand la séquence se termine
Alors la publicité s'enchaîne automatiquement (sans attente bouton) Et la publicité se lit normalement Et elle est skippable après 5 secondes
Étant donné qu'une publicité est en cours de lecture
Quand la publicité se termine
Alors le player se met en pause automatiquement Et le message suivant s'affiche: "Séquence 6 prête. Appuyez sur Suivant." Et l'utilisateur doit cliquer sur [▶|] pour continuer
Étant donné que la séquence 5 démarre
Quand la séquence 5 se termine
Alors la publicité démarre automatiquement
Quand la publicité se termine
Alors le player se met en pause
Quand l'utilisateur clique sur [▶|]
Alors la séquence 6 démarre
Étant donné que l'utilisateur gratuit écoute un audio-guide
Et que la fréquence pub est configurée à
Quand il termine la séquence
Alors une publicité est insérée :
📊 Exemples de données:
| frequence | numero_sequence | pub_inseree |
|---|---|---|
| 1/5 | 5 | Oui |
| 1/5 | 10 | Oui |
| 1/5 | 4 | Non |
| 1/3 | 3 | Oui |
| 1/3 | 6 | Oui |
Étant donné que l'utilisateur "premium@example.com" est abonné Premium Et qu'il écoute un audio-guide piéton
Quand il termine la séquence 5
Alors aucune publicité n'est insérée Et le player se met en pause immédiatement Et le message "Séquence 6 prête. Appuyez sur Suivant." s'affiche
Étant donné qu'un audio-guide piéton est en lecture
Quand l'utilisateur consulte les contrôles
Alors les boutons suivants sont visibles:
| bouton | fonction |
|---|---|
| [▶\ | ] Suivant | Passe à la séquence suivante |
| [\ | ◀] Précédent | Retour à la séquence précédente |
| [⏸️] Pause | Pause temporaire |
| [▶️] Play | Reprend la lecture |
| [📋] Liste | Affiche toutes les séquences |
Étant donné que la séquence 3 "La Joconde" est en cours à 1:42/3:42
Quand l'utilisateur clique sur [▶|] "Suivant"
Alors la séquence 4 "Vénus de Milo" démarre immédiatement Et la séquence 3 n'est pas marquée comme écoutée (car <80%)
Étant donné que la séquence 5 est en cours de lecture
Quand l'utilisateur clique sur [|◀] "Précédent"
Alors la séquence 4 démarre depuis le début (0:00) Et il n'y a pas de logique "replay si >10s" (contrairement au contenu classique)
Étant donné que la séquence 2 est en cours à 1:15/1:48
Quand l'utilisateur clique sur [⏸️] "Pause"
Alors la lecture se met en pause Et la position 1:15 est conservée
Quand l'utilisateur clique sur [▶️] "Play"
Alors la lecture reprend exactement à 1:15
Étant donné qu'un audio-guide de 12 séquences est en cours
Quand l'utilisateur clique sur [📋] "Liste séquences"
Alors une liste complète s'affiche avec:
| élément | exemple |
|---|---|
| Numéro et titre | "3. La Joconde" |
| Durée | (3:42) |
| État | ✅ Écouté / ▶️ En cours / ⭕ À écouter |
| Date écoute (si écouté) | "Écouté le 15/01/2026" |
Étant donné que la séquence 3 est en cours à 1:22/3:42
Quand la liste des séquences est affichée
Alors la séquence 3 affiche:
Étant donné que l'utilisateur est sur la séquence 3 Et que les séquences 4 à 12 n'ont pas été écoutées
Quand l'utilisateur clique sur "8. Les Appartements de Napoléon"
Alors la séquence 8 démarre immédiatement depuis 0:00 Et les séquences 4 à 7 restent marquées ⭕ "À écouter"
Étant donné que la séquence 2 "Pyramide du Louvre" a été écoutée à 100% Et qu'elle est marquée ✅ "Écouté"
Quand l'utilisateur clique dessus dans la liste
Alors la séquence 2 démarre depuis 0:00 Et le statut ✅ est conservé
Étant donné que l'utilisateur écoute la séquence 2 de durée 1:48
Quand il écoute jusqu'à 1:30 (83% de complétion) Et qu'il passe à la séquence suivante
Alors la séquence 2 est marquée ✅ "Écouté" Et la date d'écoute est enregistrée
Étant donné que l'utilisateur écoute la séquence 3 de durée 3:42
Quand il écoute jusqu'à 1:30 (40% de complétion) Et qu'il passe à la séquence suivante
Alors la séquence 3 reste marquée ⭕ "À écouter"
Étant donné un audio-guide avec 12 séquences
Quand la liste est affichée
Alors seules les 6 premières séquences sont visibles initialement Et un bouton "Tout afficher ▼" est présent
Quand l'utilisateur clique sur "Tout afficher ▼"
Alors les 6 séquences restantes sont affichées
Étant donné qu'un audio-guide est en cours
Quand l'utilisateur clique sur "3/12" dans la barre de progression
Alors la liste des séquences s'ouvre Et la séquence en cours (3) est mise en surbrillance
Étant donné que la séquence 5 est en cours à 2:34/4:10
Quand l'utilisateur quitte l'application
Alors la position 2:34 dans la séquence 5 est sauvegardée Et la sauvegarde est effectuée localement (SQLite) Et la sauvegarde est synchronisée sur le cloud (PostgreSQL)
Étant donné que l'utilisateur a quitté l'app à la séquence 5 position 2:34
Quand il rouvre l'audio-guide
Alors une popup de reprise s'affiche
Quand il clique sur "▶️ Reprendre"
Alors la lecture reprend à la séquence 5 position 2:34 exacte
Étant donné qu'un visiteur du Louvre démarre l'audio-guide Et qu'il connaît déjà "La Joconde" (séquence 3)
Quand il arrive à la séquence 3 Et qu'il clique sur [▶|] "Suivant" après 10 secondes
Alors la séquence 4 démarre immédiatement Et la séquence 3 n'est pas marquée comme écoutée
Étant donné qu'un visiteur est à la séquence 2 Et qu'il aperçoit "La Victoire de Samothrace" (séquence 8) physiquement
Quand il ouvre la liste et clique sur la séquence 8
Alors la séquence 8 démarre immédiatement Et il peut écouter la description même si les séquences 3-7 ne sont pas écoutées
Étant donné qu'un visiteur écoute la séquence 6
Quand il clique sur [⏸️] "Pause" Et qu'il ferme l'application pendant 30 minutes Quand il rouvre l'application
Alors la séquence 6 reprend à la position exacte où il s'était arrêté
Étant donné qu'un visiteur a écouté les séquences 1-5 hier Et qu'il revient au musée aujourd'hui
Quand il ouvre l'audio-guide
Alors une popup propose "▶️ Reprendre" (séquence 6) Et les séquences 1-5 sont marquées ✅ "Écouté"
Étant donné que la séquence 7 a un fichier audio corrompu
Quand l'utilisateur tente de la lire
Alors un message d'erreur s'affiche:
Étant donné que l'utilisateur lance la séquence 4 Et que la connexion réseau est perdue
Quand le chargement échoue
Alors un message s'affiche: "Connexion perdue. Vérifiez votre réseau." Et un bouton "🔄 Réessayer" est disponible
Étant donné que la batterie de l'appareil est à 5%
Quand l'utilisateur écoute une séquence
Alors une notification système s'affiche: "Batterie faible. Progression sauvegardée." Et la position est sauvegardée localement toutes les 10 secondes
Étant donné un audio-guide en mode piéton Et que le GPS est désactivé
Quand l'utilisateur démarre l'audio-guide
Alors aucune alerte GPS ne s'affiche Et l'audio-guide fonctionne normalement (navigation 100% manuelle)
En tant qu'utilisateur en voiture Je veux que les séquences se déclenchent automatiquement selon ma position GPS Afin de profiter d'une expérience guidée hands-free
45 scénarios (40 standards, 5 plans)
Contexte commun à tous les scénarios
Étant donné que l'application RoadWave est démarrée Et que l'utilisateur "jean@example.com" est connecté (gratuit) Et qu'un audio-guide voiture "Safari du Paugre" est disponible avec 8 séquences Et que le GPS est activé
Étant donné que l'utilisateur est en mode voiture
Quand il écoute un contenu géolocalisé simple (1 séquence unique)
Alors une notification avec compteur 7→1 est affichée 7s avant le point Et il doit valider avec "Suivant" + décompte 5s Et ce contenu compte 1/6 dans le quota horaire
Quand il démarre un audio-guide multi-séquences
Alors les séquences se déclenchent au point GPS exact (rayon 30m) Et aucun compteur 7s n'est affiché (juste notification "Ding" + toast 2s) Et l'audio-guide entier compte 1/6 dans le quota
Étant donné que l'utilisateur démarre l'audio-guide "Safari du Paugre" Et que le point de départ est à (43.1234, 2.5678) avec rayon 30m
Quand l'utilisateur entre dans le rayon de 30m
Alors la séquence 1 "Introduction - Point d'accueil" démarre automatiquement Et une notification sonore "Ding" est jouée (non intrusif) Et un toast s'affiche brièvement pendant 2s: "Introduction - Point d'accueil" Et aucun compteur 7→1 n'est affiché (contrairement aux contenus géolocalisés simples)
Étant donné que la séquence 1 est terminée Et que l'utilisateur se déplace vers le point GPS 2 (43.1245, 2.5690)
Quand l'utilisateur entre dans le rayon de 30m du point 2
Alors la séquence 2 "Enclos des lions" démarre automatiquement Et une notification "Ding" + toast "Enclos des lions" s'affiche
Étant donné que la séquence 1 est en cours Et que l'utilisateur est encore loin du point GPS 2 (distance 500m)
Quand l'utilisateur clique sur [▶|] "Suivant"
Alors la séquence 2 démarre immédiatement Et aucune vérification GPS n'est effectuée
Étant donné que la séquence 3 est en cours
Quand l'utilisateur clique sur [|◀] "Précédent"
Alors la séquence 2 démarre depuis le début Et aucune vérification GPS n'est effectuée
Étant donné qu'un audio-guide voiture est en cours
Quand l'utilisateur consulte les contrôles
Alors les boutons suivants sont actifs:
| bouton | état | comportement |
|---|---|---|
| [▶\ | ] Suivant | ✅ | Passe séquence suivante immédiate |
| [\ | ◀] Précédent | ✅ | Retour séquence précédente |
| [⏸️] Pause | ✅ | Pause temporaire |
| [📋] Liste | ✅ | Saut direct possible |
Étant donné que la séquence 3 "Enclos des girafes" est terminée Et que le point GPS 4 est à 2 km de distance (embouteillage)
Quand l'utilisateur clique manuellement sur [▶|] "Suivant"
Alors la séquence 4 démarre immédiatement Et l'utilisateur peut continuer l'expérience sans attendre d'atteindre le point GPS
Étant donné que le point GPS 5 est sur une route fermée Et que l'utilisateur ne peut pas s'en approcher
Quand l'utilisateur clique sur [▶|] "Suivant"
Alors la séquence 5 démarre quand même Et l'audio-guide continue normalement
Étant donné que l'utilisateur est passager (non conducteur) Et que la vitesse du véhicule est 45 km/h
Quand le passager clique sur [▶|] "Suivant"
Alors la séquence suivante démarre Et un avertissement s'affiche pendant 3 secondes
Étant donné que la vitesse actuelle est 35 km/h
Quand l'utilisateur clique sur un bouton (Suivant ou Précédent)
Alors l'action est exécutée immédiatement (pas de blocage) Et un toast s'affiche pendant 3 secondes:
Étant donné que la vitesse actuelle est
Quand l'utilisateur clique sur un bouton de navigation
Alors l'avertissement est affiché :
📊 Exemples de données:
| vitesse | avertissement |
|---|---|
| 5 | Non |
| 10 | Non |
| 11 | Oui |
| 35 | Oui |
| 90 | Oui |
Étant donné que la séquence 2 "Les lions" vient de se terminer Et que le prochain point GPS 3 "Enclos des girafes" est à 500m
Quand l'interface bascule en mode "attente prochain point"
Alors l'écran affiche:
| élément | description |
|---|---|
| Statut séquence | "✅ Séquence 2/8 terminée" |
| Nom séquence | "Les lions" |
| Progress bar | Barre dynamique remplie selon distance (0%) |
| Distance prochain point | "500 mètres" |
| ETA | "≈ 1 minute 30" |
| Direction | ↗️ |
| Vitesse actuelle | "28 km/h" |
| Bouton "Rejouer séq." | Permet de réécouter la séquence qui vient de finir |
Étant donné que la distance initiale vers le prochain point était 500m Et que la séquence précédente est terminée
Quand l'utilisateur se rapproche du prochain point Et que la distance actuelle est 175m
Alors la progress bar affiche "65%" remplie Et le calcul est: 100 - (175 / 500 * 100) = 65% Et la barre se met à jour chaque seconde
Étant donné que la séquence 3 vient de se terminer Et que l'interface "attente prochain point" est affichée
Quand l'utilisateur clique sur [▶️ Rejouer séq.]
Alors la séquence 3 redémarre depuis 0:00 Et l'utilisateur peut la réécouter (utile si distraction)
Étant donné que la séquence 2 est en cours Et que le prochain point GPS 3 "Enclos des girafes" est à 320m Et que la vitesse actuelle est 28 km/h
Quand l'interface est affichée
Alors les informations suivantes sont visibles:
| information | valeur |
|---|---|
| Nom prochain point | "Enclos des girafes" |
| Distance | "320 mètres" |
| ETA | "≈ 40 secondes" |
| Direction | ↗️ (flèche direction) |
| Vitesse actuelle | "28 km/h" |
| Vitesse recommandée | "20-30 km/h" |
Étant donné que la distance au prochain point est 500m
Quand 10 secondes s'écoulent et que l'utilisateur se rapproche
Alors la distance est mise à jour chaque seconde Et la nouvelle distance "450m" s'affiche
Étant donné que l'ETA est "≈ 2 minutes" Et que la vitesse est constante à 30 km/h
Quand l'utilisateur se rapproche du point
Alors l'ETA est recalculé chaque seconde Et il diminue progressivement: "≈ 1 minute 50", "≈ 1 minute 40", etc.
Étant donné que la distance au prochain point est
Quand l'interface est mise à jour
Alors la distance affichée est "
📊 Exemples de données:
| distance_metres | affichage |
|---|---|
| 50 | 50 m |
| 320 | 320 m |
| 980 | 980 m |
| 1200 | 1.2 km |
| 5400 | 5.4 km |
Étant donné que l'ETA calculé est
Quand l'interface est mise à jour
Alors l'ETA affiché est "
📊 Exemples de données:
| secondes | affichage |
|---|---|
| 30 | ≈ 30 secondes |
| 75 | ≈ 1 minute |
| 150 | ≈ 2 minutes |
| 400 | ≈ 6 minutes |
Étant donné que la position actuelle est (43.1234, 2.5678) Et que le prochain point est au nord-est (angle 45°)
Quand la direction est calculée
Alors la flèche "↗" est affichée
Étant donné que l'angle vers le prochain point est
Quand la direction est calculée
Alors la flèche "
📊 Exemples de données:
| angle | fleche |
|---|---|
| 0 | ↑ |
| 45 | ↗ |
| 90 | → |
| 135 | ↘ |
| 180 | ↓ |
| 225 | ↙ |
| 270 | ← |
| 315 | ↖ |
Étant donné que la direction actuelle est ↑ (nord) Et que l'utilisateur tourne vers l'est
Quand 5 secondes s'écoulent
Alors la direction est recalculée Et la nouvelle flèche ↗ (nord-est) s'affiche
Étant donné que la vitesse actuelle est 2 km/h (arrêté)
Quand l'ETA est calculé
Alors le message "En attente de déplacement" s'affiche Et l'ETA n'est pas calculé (car vitesse insuffisante)
Étant donné qu'un audio-guide voiture est en cours
Quand l'interface est affichée
Alors aucune carte miniature n'est présente Et seuls les éléments essentiels sont affichés:
| élément |
|---|
| Distance |
| ETA |
| Direction (flèche) |
| Vitesse |
| Contrôles audio |
Étant donné un audio-guide voiture
Quand un point GPS est défini
Alors le rayon de déclenchement est 30 mètres par défaut Et le rayon de tolérance "point manqué" est 100 mètres
Étant donné que le point GPS 3 est défini avec rayon 30m
Quand l'utilisateur entre à 25m du point
Alors la séquence 3 se déclenche automatiquement
Étant donné que le point GPS 3 a un rayon de 30m
Quand l'utilisateur passe à 45m du point
Alors la séquence 3 ne se déclenche pas automatiquement
Étant donné que l'utilisateur passe à 60m du point GPS 4 (hors rayon 30m) Et que 60m < 100m (rayon tolérance)
Quand le point est détecté comme manqué
Alors un toast s'affiche: "⚠️ Point manqué : Enclos des éléphants" Et une popup s'affiche pendant 5 secondes avec 3 options
Étant donné qu'un point GPS a été manqué (distance 60m)
Quand la popup s'affiche
Alors les options suivantes sont disponibles:
| bouton | icône | comportement |
|---|---|---|
| Écouter quand même | 🔊 | Lance séquence immédiatement (même hors zone) |
| Passer au suivant | ⏭️ | Skip séquence, continue vers prochain point |
| Faire demi-tour | 🔙 | Ouvre GPS externe (Google Maps/Waze) vers point |
Étant donné qu'un point GPS est manqué
Quand l'utilisateur clique sur "🔊 Écouter quand même"
Alors la séquence correspondante démarre immédiatement Et l'utilisateur peut continuer sa route
Étant donné qu'un point GPS 5 est manqué
Quand l'utilisateur clique sur "⏭️ Passer au suivant"
Alors la séquence 5 est ignorée (non écoutée) Et l'application attend le point GPS 6 Et la distance vers le point 6 s'affiche
Étant donné qu'un point GPS est manqué à (43.1250, 2.5700)
Quand l'utilisateur clique sur "🔙 Faire demi-tour"
Alors l'application détecte l'app GPS installée (Google Maps ou Waze) Et ouvre la navigation GPS externe vers (43.1250, 2.5700)
Étant donné que l'utilisateur passe à 150m du point GPS 6
Quand la distance est détectée
Alors aucune popup ne s'affiche (point trop loin) Et l'utilisateur peut naviguer manuellement avec [▶|]
Étant donné un point GPS avec rayon 30m et tolérance 100m
Quand l'utilisateur passe à
Alors le comportement est
📊 Exemples de données:
| distance | comportement |
|---|---|
| 20m | Déclenchement automatique séquence |
| 40m | Rien (hors rayon, pas encore tolérance) |
| 60m | Popup "Point manqué" avec 3 options |
| 110m | Rien (trop loin, hors tolérance) |
Étant donné qu'un créateur définit un rayon de 50m (au lieu de 30m)
Quand un utilisateur entre à 45m du point
Alors la séquence se déclenche automatiquement Et le rayon personnalisé est respecté
Étant donné qu'un créateur configure un rayon
Quand il ajuste le curseur
Alors les valeurs disponibles sont de 10m à 200m Et le rayon par défaut suggéré est 30m pour la voiture
Étant donné qu'un utilisateur roule dans un safari à 20 km/h
Quand il passe devant "Enclos des lions" (point GPS 2)
Alors la séquence 2 démarre automatiquement sans intervention Et il peut se concentrer sur la conduite et l'observation
Étant donné qu'un utilisateur prend un détour à cause de travaux Et que le point GPS 4 devient inaccessible
Quand il est loin du point (>100m) Et qu'il clique manuellement sur [▶|]
Alors la séquence 4 démarre quand même Et l'expérience continue sans blocage
Étant donné qu'un passager utilise l'application Et que le conducteur roule à 50 km/h
Quand le passager clique sur "Précédent" pour réécouter
Alors l'action est exécutée immédiatement Et un warning apparaît brièvement (sensibilisation)
Étant donné que la séquence 3 est terminée depuis 10 minutes Et que l'utilisateur est bloqué dans un embouteillage Et que le point GPS 4 est encore à 1.5 km
Quand l'utilisateur clique sur [▶|]
Alors la séquence 4 démarre immédiatement Et l'utilisateur peut passer le temps en écoutant
Étant donné qu'un audio-guide voiture est démarré Et que le GPS est désactivé
Quand l'application détecte l'absence de GPS
Alors une alerte s'affiche:
Étant donné que le GPS est désactivé
Quand l'utilisateur clique sur "Passer en mode Manuel"
Alors l'audio-guide bascule en navigation 100% manuelle Et les boutons [▶|] et [|◀] permettent de naviguer Et aucun déclenchement GPS n'est tenté
Étant donné que le signal GPS a une précision de ±150m
Quand l'utilisateur approche d'un point GPS avec rayon 30m
Alors un avertissement s'affiche:
Étant donné qu'un audio-guide voiture est en cours
Quand le signal GPS est perdu (tunnel, parking souterrain)
Alors un toast s'affiche: "Signal GPS perdu. Navigation manuelle active." Et les boutons de navigation restent actifs
Quand le signal GPS revient
Alors un toast s'affiche: "Signal GPS rétabli" Et le déclenchement automatique est réactivé
Étant donné qu'un audio-guide recommande 20-30 km/h Et que l'utilisateur roule à 65 km/h
Quand la vitesse est détectée
Alors l'affichage vitesse est en orange: "⚠️ 65 km/h" Et un message info s'affiche: "Vitesse élevée. Risque de manquer des points."
En tant qu'utilisateur à vélo ou en transport en commun Je veux profiter d'un guidage GPS adapté à mon mode de déplacement Afin d'avoir une expérience optimisée avec tolérances appropriées
27 scénarios (24 standards, 3 plans)
Contexte commun à tous les scénarios
Étant donné que l'application RoadWave est démarrée Et que l'utilisateur "jean@example.com" est connecté Et que le GPS est activé
Étant donné un audio-guide configuré en mode
Alors les paramètres suivants sont appliqués:
| paramètre | valeur |
|---|---|
| Rayon déclenchement | <rayon_declenchement> |
| Rayon tolérance "point manqué" | <rayon_tolerance> |
| Vitesse recommandée | <vitesse_recommandee> |
| Seuil warning sécurité | <seuil_warning> |
📊 Exemples de données:
| mode | rayon_declenchement | rayon_tolerance | vitesse_recommandee | seuil_warning |
|---|---|---|---|---|
| Voiture | 30m | 100m | 20-50 km/h | >10 km/h |
| Vélo | 50m | 75m | 10-25 km/h | >5 km/h |
| Transport | 100m | 150m | Variable | Désactivé |
Étant donné un audio-guide vélo "Circuit des châteaux de la Loire" Et que le point GPS 3 a un rayon de 50m
Quand l'utilisateur à vélo entre à 45m du point
Alors la séquence 3 "Château de Chambord" se déclenche automatiquement
Étant donné qu'un cycliste roule sur piste cyclable Et que sa vitesse varie entre 8 et 22 km/h (arrêts fréquents) Et que le tracé est moins prévisible qu'en voiture
Quand un point GPS avec rayon 50m est défini
Alors le rayon plus large compense la variabilité de trajectoire
Étant donné un audio-guide vélo en cours Et que la vitesse actuelle est 12 km/h
Quand l'utilisateur clique sur [▶|] "Suivant"
Alors l'action est exécutée Et un warning s'affiche: "⚠️ Manipulation en déplacement détecté. Pour votre sécurité, arrêtez-vous."
Étant donné que la vitesse actuelle à vélo est
Quand l'utilisateur clique sur un bouton de navigation
Alors le warning est affiché :
📊 Exemples de données:
| vitesse | warning |
|---|---|
| 0 | Non |
| 4 | Non |
| 6 | Oui |
| 15 | Oui |
| 25 | Oui |
Étant donné qu'un cycliste passe à 65m du point GPS 4 Et que le rayon de déclenchement est 50m Et que le rayon de tolérance est 75m
Quand la distance est détectée
Alors la popup "Point manqué" s'affiche avec 3 options Et le système tolère l'écart (trajectoire vélo moins prévisible)
Étant donné un audio-guide vélo en cours
Quand l'interface est affichée
Alors les informations suivantes sont visibles:
| information | valeur |
|---|---|
| Icône mode | 🚴 |
| Distance prochain point | "450 m" |
| ETA | "≈ 2 minutes" |
| Direction | ↗️ |
| Vitesse actuelle | "18 km/h" |
| Vitesse recommandée | "10-25 km/h" |
Étant donné qu'un cycliste suit un circuit nature Et qu'il s'arrête régulièrement (feux, photos, fatigue)
Quand il s'arrête à 40m d'un point GPS (rayon 50m)
Alors la séquence se déclenche automatiquement Et le rayon large permet le déclenchement malgré l'arrêt
Étant donné qu'un cycliste roule sur voie partagée Et qu'il doit ralentir fréquemment pour éviter les piétons
Quand sa vitesse varie entre 5 et 20 km/h
Alors le système s'adapte avec le rayon 50m Et le déclenchement reste fiable
Étant donné un audio-guide transport "Ligne touristique Paris" Et que le point GPS "Tour Eiffel" a un rayon de 100m
Quand le bus touristique entre à 85m du point
Alors la séquence "Tour Eiffel" se déclenche automatiquement
Étant donné qu'un bus touristique suit une ligne fixe Et qu'il effectue des arrêts fréquents (stations) Et que l'utilisateur n'a aucun contrôle sur la trajectoire
Quand un point GPS avec rayon 100m est défini
Alors le rayon large compense les arrêts et la ligne fixe
Étant donné un audio-guide transport en cours Et que le bus roule à 50 km/h
Quand l'utilisateur clique sur [▶|] "Suivant"
Alors l'action est exécutée immédiatement Et aucun warning n'est affiché
Étant donné un audio-guide transport
Quand l'interface est affichée
Alors la vitesse recommandée indique "Selon ligne" Et aucune valeur fixe n'est affichée (car ligne de transport varie)
Étant donné qu'un bus touristique est en retard de 3 minutes Et qu'il arrive au point GPS "Musée du Louvre" avec retard
Quand il entre dans le rayon de 100m
Alors la séquence se déclenche normalement Et le système tolère le retard (pas de pénalité temporelle)
Étant donné qu'un bus passe à 120m du point GPS "Arc de Triomphe" Et que le rayon de déclenchement est 100m Et que le rayon de tolérance est 150m
Quand la distance est détectée
Alors la popup "Point manqué" s'affiche avec 3 options
Étant donné un audio-guide transport en cours
Quand l'interface est affichée
Alors les informations suivantes sont visibles:
| information | valeur |
|---|---|
| Icône mode | 🚌 |
| Distance prochain point | "1.2 km" |
| ETA | "≈ 3 minutes" |
| Direction | → |
| Vitesse actuelle | "35 km/h" |
| Vitesse recommandée | "Selon ligne" |
Étant donné un bus touristique "Paris Open Tour" Et qu'il suit un circuit fixe avec 15 arrêts
Quand il approche de chaque arrêt
Alors la séquence correspondante se déclenche automatiquement Et l'utilisateur n'a rien à faire (expérience passive)
Étant donné un train touristique "Ligne des Alpes" Et qu'il roule à vitesse variable (20-80 km/h)
Quand il passe près de points d'intérêt
Alors les séquences se déclenchent avec rayon 100m Et le système compense la vitesse élevée
Étant donné un audio-guide en mode
Quand l'utilisateur clique sur [▶|] ou [|◀]
Alors les boutons manuels fonctionnent normalement Et aucune vérification GPS n'est effectuée
Étant donné un audio-guide en mode
Quand l'interface est affichée
Alors les informations distance, ETA et direction sont affichées Et le format est identique au mode voiture
Étant donné un audio-guide en mode
Quand un point GPS est manqué (dans rayon tolérance)
Alors la popup avec 3 options s'affiche:
| option |
|---|
| 🔊 Écouter quand même |
| ⏭️ Passer au suivant |
| 🔙 Faire demi-tour |
Étant donné un utilisateur gratuit écoute un audio-guide en mode
Quand la séquence 5 se termine (1 pub / 5 séquences)
Alors la publicité s'enchaîne automatiquement Et elle est skippable après 5 secondes
📊 Exemples de données:
| mode |
|---|
| Voiture |
| Vélo |
| Transport |
| Piéton |
Étant donné un cycliste dans une forêt dense Et que la précision GPS est ±80m
Quand il approche d'un point GPS avec rayon 50m
Alors un avertissement s'affiche:
Étant donné un bus touristique avec déviation Et que plusieurs points GPS deviennent inaccessibles
Quand l'utilisateur est informé
Alors un message s'affiche:
Étant donné un audio-guide démarré en mode "Vélo"
Quand l'utilisateur décide de continuer à pied Et qu'il ouvre les paramètres
Alors il peut changer le mode vers "Piéton" Et les rayons sont reconfigurés automatiquement Et une confirmation s'affiche:
Étant donné qu'un utilisateur marche rapidement (7 km/h) Et que le système détecte "Vélo" par erreur
Quand la suggestion s'affiche
Alors l'utilisateur peut cliquer sur "Changer" Et sélectionner manuellement "Piéton"
Étant donné un circuit vélo de 50 km avec 20 séquences Et que l'utilisateur roule pendant 3 heures
Quand la batterie atteint 15%
Alors une notification suggère:
En tant que créateur Je veux pouvoir proposer des audio-guides Premium Afin de monétiser mon contenu de qualité
31 scénarios
Contexte commun à tous les scénarios
Étant donné que l'application RoadWave est démarrée Et que le créateur "guide@example.com" est connecté et vérifié
Étant donné que le créateur crée un audio-guide "Visite VIP Versailles"
Quand il accède aux paramètres de monétisation (étape 4)
Alors il peut choisir:
| option | description |
|---|---|
| Gratuit | Accessible à tous (avec pubs) |
| Premium | Réservé abonnés Premium |
Étant donné un audio-guide configuré en Premium
Quand il est affiché dans les résultats de recherche
Alors un badge "👑 Premium" est visible Et la cover image a un cadre doré subtil
Étant donné un audio-guide Premium "Visite VIP Versailles" avec 15 séquences Et qu'un utilisateur gratuit ouvre l'audio-guide
Quand il consulte la liste des séquences
Alors les séquences affichent:
| séquence | état |
|---|---|
| 1 | ✅ Accessible (preview) |
| 2 | ✅ Accessible (preview) |
| 3 | ✅ Accessible (preview) |
| 4 | 🔒 Réservé Premium |
| 5-15 | 🔒 Réservé Premium |
Étant donné un utilisateur gratuit Et un audio-guide Premium avec preview
Quand il écoute les séquences 1, 2 et 3
Alors aucune publicité n'est insérée (preview = teasing) Et l'écoute est fluide
Étant donné qu'un utilisateur gratuit termine la séquence 3
Quand la séquence se termine
Alors un overlay paywall s'affiche immédiatement:
Étant donné que l'overlay paywall Premium est affiché
Quand l'utilisateur clique sur "Passer Premium"
Alors il est redirigé vers la page d'abonnement Mangopay Et l'audio-guide actuel est marqué en "pending" (reprise après souscription)
Étant donné qu'un utilisateur s'est abonné Premium depuis un paywall audio-guide
Quand l'abonnement est activé
Alors il est redirigé vers l'audio-guide automatiquement Et la séquence 4 démarre immédiatement Et un toast de bienvenue s'affiche: "✨ Bienvenue Premium ! Profitez de votre audio-guide"
Étant donné qu'un utilisateur Premium ouvre un audio-guide Premium
Quand il consulte la liste des séquences
Alors toutes les 15 séquences sont accessibles Et aucun paywall ne s'affiche Et aucune publicité n'est insérée
Étant donné un audio-guide Premium avec seulement 2 séquences
Quand un utilisateur gratuit tente de l'ouvrir
Alors un paywall s'affiche immédiatement (avant lecture) Et aucune preview n'est disponible
Étant donné un créateur avec un audio-guide Premium Et que 50 utilisateurs Premium ont écouté l'audio-guide ce mois
Quand la répartition des revenus est calculée
Alors le créateur reçoit 70% des revenus proportionnels Et la formule est: (Écoutes créateur / Total écoutes Premium) × 70% pool Premium
Étant donné qu'un créateur a 3 audio-guides Premium publiés
Quand il consulte son dashboard revenus
Alors il voit pour chaque audio-guide:
| audio_guide | ecoutes_mois | revenus_estime |
|---|---|---|
| Visite VIP Versailles | 142 | 45.20 € |
| Secrets du Louvre | 89 | 28.50 € |
| Châteaux de la Loire | 203 | 64.80 € |
Étant donné qu'un créateur a publié 2 audio-guides:
| titre | type | ecoutes_mois | revenus |
|---|---|---|---|
| Tour de Paris | Gratuit | 1200 | 12.50 € |
| Visite VIP Versailles | Premium | 142 | 45.20 € |
Quand il consulte son dashboard
Alors il peut comparer les performances Et constater que Premium génère plus de revenus par écoute
Étant donné qu'un créateur a généré 18€ de revenus ce mois
Quand le paiement mensuel est traité
Alors le montant est reporté au mois suivant Et un message s'affiche: "Seuil minimum non atteint (20€). Montant reporté."
Étant donné qu'un créateur a généré 138.50€ de revenus en janvier
Quand le 5 février arrive
Alors le paiement est initié automatiquement via Mangopay Et le créateur reçoit une notification: "Paiement de 138.50€ en cours" Et les fonds arrivent sous 2-3 jours ouvrés
Étant donné un audio-guide gratuit avec 12 séquences Et un utilisateur gratuit
Quand il termine la séquence 5
Alors une publicité démarre automatiquement
Quand il termine la séquence 10
Alors une deuxième publicité démarre
Étant donné un audio-guide piéton gratuit
Quand la séquence 5 se termine
Alors la publicité démarre automatiquement (pas d'attente bouton) Et la pub est skippable après 5 secondes
Quand la publicité se termine
Alors le player se met en pause Et l'utilisateur doit cliquer sur [▶|] pour continuer
Étant donné un audio-guide voiture gratuit
Quand la séquence 5 se termine
Alors la publicité démarre automatiquement
Quand la publicité se termine
Alors la séquence 6 démarre automatiquement (pas de pause)
Étant donné un audio-guide dans la région "Île-de-France"
Quand une publicité doit être insérée
Alors l'API publicitaire filtre par:
| critère | valeur |
|---|---|
| Géolocalisation | Île-de-France |
| Catégorie | Tourisme, Culture |
| Langue | Français |
Étant donné qu'un audio-guide gratuit génère 200 écoutes complètes Et que chaque écoute complète = 2 publicités (séq. 5 et 10)
Quand les revenus pub sont calculés
Alors 400 impressions sont comptabilisées Et le créateur reçoit 0.80€ (400 × 0.002€)
Étant donné qu'un utilisateur gratuit complète un audio-guide gratuit
Quand il termine la dernière séquence
Alors un overlay s'affiche:
Étant donné qu'un utilisateur termine un audio-guide gratuit "Tour de Paris"
Quand l'overlay de fin s'affiche
Alors 3 audio-guides Premium similaires sont suggérés:
| titre | type | créateur |
|---|---|---|
| Secrets de Montmartre | Premium | @paris_stories |
| Visite VIP Musée d'Orsay | Premium | @art_guide |
| Paris hors des sentiers | Premium | @explore_paris |
Étant donné un audio-guide Premium avec >500 écoutes et note >4.5/5
Quand il est affiché dans les résultats de recherche
Alors un badge "⭐ Premium recommandé" est visible Et il est mis en avant dans les résultats
Étant donné qu'un utilisateur découvre Premium via un audio-guide créateur
Quand il s'abonne
Alors la conversion est trackée:
| donnée | valeur |
|---|---|
| source_conversion | audio_guide_paywall |
| audio_guide_id | visite_vip_versailles_123 |
| creator_id | guide_versailles_456 |
Et le créateur bénéficie d'un bonus de conversion
Étant donné qu'un utilisateur gratuit atteint le paywall d'un audio-guide Premium Et qu'il n'a jamais essayé Premium
Quand l'overlay s'affiche
Alors une offre d'essai est proposée:
Étant donné qu'un utilisateur démarre un essai gratuit 7 jours
Quand l'essai est activé
Alors l'audio-guide Premium démarre immédiatement Et toutes les séquences sont débloquées Et aucune publicité n'est insérée
Étant donné qu'un utilisateur a démarré un essai gratuit le 15/01
Quand le 20/01 arrive (J-2)
Alors une notification est envoyée:
Étant donné qu'un créateur a publié 5 audio-guides:
| titre | type |
|---|---|
| Découverte de Paris | Gratuit |
| Visite VIP Louvre | Premium |
| Balade Montmartre | Gratuit |
| Secrets Versailles | Premium |
| Visite express Orsay | Gratuit |
Quand un utilisateur découvre son profil
Alors les audio-guides gratuits servent de teasing Et les audio-guides Premium sont mis en avant avec badge
Étant donné qu'un utilisateur atteint le paywall d'un audio-guide Premium Et qu'il clique sur "Découvrir d'autres audio-guides gratuits"
Quand il revient 2 jours plus tard sur le même audio-guide
Alors le paywall s'affiche à nouveau Et une réduction temporaire est proposée: "Offre spéciale : -20% premier mois"
Étant donné qu'un utilisateur tente de s'abonner Premium
Quand le paiement Mangopay échoue
Alors un message d'erreur s'affiche:
Étant donné qu'un utilisateur Premium écoute un audio-guide Premium Et que son abonnement expire pendant l'écoute (séquence 8/15)
Quand l'expiration est détectée
Alors l'écoute continue jusqu'à la fin de la séquence en cours Et un overlay s'affiche ensuite:
Étant donné qu'un audio-guide gratuit a 50 utilisateurs avec progression
Quand le créateur le passe en Premium
Alors les utilisateurs ayant déjà commencé gardent l'accès complet Et seuls les nouveaux utilisateurs sont soumis au paywall Et un message de transparence s'affiche:
En tant qu'utilisateur Je veux que ma progression soit sauvegardée automatiquement Afin de pouvoir reprendre mon audio-guide là où je me suis arrêté
32 scénarios (31 standards, 1 plan)
Contexte commun à tous les scénarios
Étant donné que l'application RoadWave est démarrée Et que l'utilisateur "jean@example.com" est connecté
Étant donné qu'un audio-guide "Visite du Louvre" est en cours Et que la séquence 3 est à la position 1:24
Quand 10 secondes s'écoulent
Alors la progression est sauvegardée automatiquement:
| donnée | valeur |
|---|---|
| audio_guide_id | louvre_123 |
| sequence_actuelle | 3 |
| position_audio | 1:24 |
| timestamp | 2026-01-22 14:35:42 |
| sequences_ecoutees | [1, 2] |
Étant donné qu'une sauvegarde est déclenchée
Quand la progression est enregistrée
Alors les données sont écrites en SQLite local Et l'écriture prend moins de 50ms Et l'application reste fluide
Étant donné qu'une sauvegarde locale est effectuée
Quand 30 secondes s'écoulent
Alors la progression est synchronisée vers PostgreSQL cloud Et la synchronisation s'effectue en arrière-plan Et elle n'impacte pas les performances
Étant donné qu'un audio-guide est en cours à la séquence 4 position 2:15
Quand l'utilisateur ferme l'application
Alors la progression est sauvegardée immédiatement (local + cloud) Et les données sont écrites avant la fermeture complète
Étant donné qu'un audio-guide de 12 séquences est en cours Et que les séquences 1, 2, 4, 5 ont été écoutées à >80%
Quand la progression est sauvegardée
Alors les séquences complétées sont enregistrées:
Étant donné qu'un utilisateur a écouté 3 séquences d'un audio-guide
Quand les données sont sauvegardées
Alors l'historique d'écoute inclut:
| sequence_id | started_at | completed_at | completion_rate |
|---|---|---|---|
| 1 | 2026-01-22 14:10:00 | 2026-01-22 14:12:15 | 100% |
| 2 | 2026-01-22 14:12:20 | 2026-01-22 14:14:08 | 100% |
| 3 | 2026-01-22 14:14:15 | 2026-01-22 14:17:45 | 92% |
Étant donné que l'utilisateur a quitté l'app à la séquence 6 position 2:34
Quand il rouvre l'audio-guide "Visite du Louvre"
Alors une popup s'affiche:
Étant donné qu'une popup de reprise est affichée
Quand l'utilisateur clique sur "▶️ Reprendre"
Alors la séquence 6 "Vénus de Milo" se charge Et la position exacte 2:34 est restaurée Et la lecture démarre automatiquement après 1 seconde
Étant donné qu'une popup de reprise est affichée
Quand l'utilisateur clique sur "🔄 Recommencer"
Alors l'audio-guide redémarre depuis la séquence 1 position 0:00 Et toutes les séquences sont marquées ⭕ "À écouter" Et l'historique d'écoute est réinitialisé pour cette session
Étant donné qu'un utilisateur a arrêté un audio-guide le 15/01/2026 Et qu'il le rouvre le 22/01/2026 (7 jours plus tard)
Quand l'audio-guide se charge
Alors la popup de reprise s'affiche normalement Et toutes les données de progression sont conservées
Étant donné qu'un utilisateur écoute un audio-guide sur iPhone Et qu'il quitte à la séquence 4 position 1:20
Quand il ouvre le même audio-guide sur iPad
Alors la popup de reprise s'affiche avec la progression iPhone Et il peut reprendre exactement où il s'était arrêté
Étant donné qu'un utilisateur écoute sur iPhone à la séquence 3 Et simultanément sur iPad à la séquence 7
Quand les deux appareils synchronisent
Alors la progression la plus récente (timestamp) est conservée Et l'appareil avec ancienne progression affiche une notification:
Étant donné qu'un utilisateur écoute un audio-guide hors connexion Et qu'il atteint la séquence 5
Quand la progression est sauvegardée
Alors les données sont écrites localement (SQLite) Et une icône "☁️ Non synchronisé" s'affiche discrètement
Étant donné que l'utilisateur a écouté hors ligne jusqu'à la séquence 8 Et que 5 progressions locales ne sont pas synchronisées
Quand la connexion réseau est rétablie
Alors les 5 progressions sont synchronisées automatiquement Et un toast s'affiche brièvement: "✅ Progression synchronisée"
Étant donné qu'un utilisateur est à la séquence 10/12
Quand il ouvre les paramètres de l'audio-guide Et qu'il clique sur "🔄 Réinitialiser progression"
Alors une confirmation s'affiche: Et si confirmé, la progression est effacée
Étant donné un audio-guide de 12 séquences Et que l'utilisateur a écouté complètement 8 séquences Et partiellement 1 séquence (45%)
Quand les statistiques sont calculées
Alors le taux de complétion affiché est "67%" (8/12)
Étant donné un audio-guide de 12 séquences
Quand l'utilisateur écoute la 12ème séquence à 100%
Alors un badge "✅ Audio-guide complété" s'affiche Et une notification de félicitations est envoyée Et le statut "Complété le 22/01/2026" est visible dans l'historique
Étant donné qu'un utilisateur a écouté un audio-guide sur 2 sessions:
| session | durée |
|---|---|
| 1 | 25 min |
| 2 | 18 min |
Quand les statistiques sont calculées
Alors le temps total est "43 minutes" Et il est affiché dans l'historique personnel
Étant donné qu'un utilisateur a 3 audio-guides en cours:
| audio_guide | progression |
|---|---|
| Visite du Louvre | 6/12 |
| Safari du Paugre | 3/8 |
| Circuit Loire à Vélo | 12/15 |
Quand il consulte son profil "Audio-guides"
Alors la section "📍 En cours" affiche les 3 audio-guides Et chaque élément montre la progression sous forme de barre
Étant donné qu'un utilisateur a complété 2 audio-guides:
| audio_guide | date_completion |
|---|---|
| Tour de Paris | 2026-01-15 |
| Découverte de Lyon | 2026-01-20 |
Quand il consulte son profil "Audio-guides"
Alors la section "✅ Complétés" affiche les 2 audio-guides Et la date de complétion est visible
Étant donné qu'un utilisateur complète son 10ème audio-guide
Quand la complétion est enregistrée
Alors un badge "🏆 Complétiste" est débloqué Et il apparaît sur son profil Et une notification est envoyée:
Étant donné qu'un utilisateur complète
Quand le badge est attribué
Alors il reçoit le badge "
📊 Exemples de données:
| nombre | badge |
|---|---|
| 1 | 🎧 Premier audio-guide |
| 5 | 🗺️ Explorateur |
| 10 | 🏆 Complétiste |
| 25 | 🌟 Expert |
| 50 | 💎 Maître audio-guideur |
Étant donné qu'un créateur a publié l'audio-guide "Visite du Louvre"
Quand il consulte son dashboard
Alors les métriques suivantes sont affichées:
| métrique | valeur |
|---|---|
| Écoutes totales | 1542 |
| Écoutes complètes (>80%) | 892 |
| Taux de complétion moyen | 58% |
| Temps d'écoute total | 423h |
| Séquence la plus écoutée | Séq. 3 |
| Séquence la moins écoutée | Séq. 11 |
Étant donné un audio-guide de 12 séquences
Quand le créateur consulte les statistiques détaillées
Alors un graphique en barres affiche:
| séquence | taux_completion |
|---|---|
| 1 | 100% |
| 2 | 95% |
| 3 | 89% |
| ... | ... |
| 12 | 58% |
Étant donné qu'un audio-guide a un taux de complétion de 58% Et que 35% des utilisateurs abandonnent à la séquence 7
Quand le créateur consulte les insights
Alors un avertissement s'affiche:
Étant donné un audio-guide géolocalisé
Quand le créateur consulte la heatmap
Alors une carte affiche:
| élément | description |
|---|---|
| Densité d'écoutes | Zones rouge/orange/jaune selon écoutes |
| Points GPS | Marqueurs sur chaque point |
| Statistiques par point | Nombre d'écoutes par zone |
Étant donné qu'un créateur analyse son audio-guide
Quand il consulte les statistiques temporelles
Alors il voit pour chaque séquence:
| séquence | durée_audio | temps_ecoute_moyen | ecart |
|---|---|---|---|
| 1 | 2:15 | 2:10 | -5s |
| 2 | 1:48 | 1:30 | -18s |
| 3 | 3:42 | 3:40 | -2s |
Étant donné qu'un audio-guide atteint 1000 écoutes
Quand le seuil est franchi
Alors une notification est envoyée au créateur:
Étant donné qu'une sauvegarde locale (SQLite) est corrompue
Quand l'application tente de charger la progression
Alors une récupération depuis le cloud est tentée Et si réussie, les données cloud sont restaurées Et la base locale est reconstruite
Étant donné que l'API cloud est indisponible
Quand une tentative de synchronisation est effectuée
Alors l'application continue avec sauvegarde locale uniquement Et un retry automatique est programmé dans 5 minutes Et l'icône "☁️ Non synchronisé" reste affichée
Étant donné qu'un utilisateur réinitialise un audio-guide par erreur
Quand il contacte le support dans les 7 jours
Alors l'équipe peut restaurer la progression depuis les backups Et les données sont récupérables (backup quotidien conservé 30 jours)
Étant donné qu'une progression n'a pas été mise à jour depuis 6 mois
Quand le nettoyage automatique s'exécute
Alors la progression est archivée (mais pas supprimée) Et l'utilisateur peut la restaurer via l'historique
En tant que plateforme responsable Je veux classifier les contenus par tranche d'âge Afin de protéger les mineurs et respecter les obligations légales
13 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible
Étant donné que je suis un créateur connecté
Quand je crée un nouveau contenu audio
Alors je dois obligatoirement choisir une classification d'âge parmi:
| classification | description |
|---|---|
| Tout public | Contenu adapté à tous les âges |
| 13+ | Contenu mature léger |
| 16+ | Contenu mature |
| 18+ | Contenu adulte |
Étant donné que je crée un contenu audio
Quand j'essaie de publier sans sélectionner de classification
Alors la publication échoue Et je vois le message "Vous devez sélectionner une classification d'âge"
Étant donné que je suis un utilisateur de 14 ans Et qu'il existe des contenus avec les classifications suivantes:
| classification | nombre |
|---|---|
| Tout public | 20 |
| 13+ | 15 |
| 16+ | 10 |
| 18+ | 5 |
Quand je demande des recommandations
Alors je vois uniquement les 20 contenus "Tout public" Et les autres contenus ne sont jamais proposés
Étant donné que je suis un utilisateur de 17 ans Et qu'il existe des contenus avec les classifications suivantes:
| classification | nombre |
|---|---|
| Tout public | 20 |
| 13+ | 15 |
| 16+ | 10 |
| 18+ | 5 |
Quand je demande des recommandations
Alors je vois 35 contenus (Tout public + 13+) Et les contenus 16+ et 18+ ne sont pas proposés
Étant donné que je suis un utilisateur de 25 ans Et qu'il existe des contenus avec toutes les classifications
Quand je demande des recommandations
Alors je vois tous les contenus sans restriction Et aucun filtre d'âge n'est appliqué
Étant donné que je m'inscris avec une date de naissance "2013-01-21"
Alors le mode Kids est activé automatiquement Et je vois uniquement du contenu "Tout public" Et des protections supplémentaires sont appliquées
Étant donné qu'un contenu est publié avec la classification "Tout public" Et que ce contenu contient du langage inapproprié détecté en modération
Quand le modérateur reclassifie ce contenu en "16+"
Alors la nouvelle classification est appliquée immédiatement Et le contenu n'est plus visible pour les utilisateurs de moins de 16 ans Et le créateur reçoit une notification de reclassification
Étant donné qu'un créateur a publié un contenu "18+" classifié comme "Tout public" Et que ce contenu a été signalé
Quand le modérateur confirme la mauvaise classification volontaire
Alors le créateur reçoit 1 strike Et le contenu est reclassifié en "18+" Et le créateur reçoit une notification explicative
Étant donné que je suis un créateur Et que j'ai publié des contenus avec différentes classifications
Quand je consulte mes statistiques
Alors je vois la répartition des âges de mes auditeurs:
| tranche_age | pourcentage |
|---|---|
| 13-15 ans | 15% |
| 16-17 ans | 20% |
| 18+ ans | 65% |
Étant donné que je suis un utilisateur de 16 ans
Quand je recherche des contenus
Alors les résultats incluent uniquement:
| classification |
|---|
| Tout public |
| 13+ |
Et je ne vois pas les contenus 16+ et 18+ dans les résultats
Étant donné que je suis un utilisateur de 14 ans Et qu'un contenu "16+" est partagé avec moi via un lien direct
Quand j'essaie d'accéder au contenu
Alors l'accès est refusé Et je vois le message "Ce contenu est réservé aux utilisateurs de 16 ans et plus"
Étant donné que je suis un nouveau créateur Et que je publie mon premier contenu classifié "18+"
Quand le modérateur valide mon contenu
Alors il vérifie que la classification "18+" est appropriée Et peut la modifier si nécessaire avant validation
Étant donné que je suis un créateur
Quand je consulte mes contenus publiés
Alors je vois pour chaque contenu:
| information | exemple |
|---|---|
| Classification actuelle | 13+ |
| Nombre de signalements | 2 |
| Reclassifications | Aucune / 1× par modérateur |
En tant qu'utilisateur existant Je veux me connecter à mon compte Afin d'accéder à mes contenus et paramètres
11 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et qu'un utilisateur existe avec: | email | mot_de_passe | |---|---| | user@test.fr | Password123 |
Quand je me connecte avec:
| email | mot_de_passe |
|---|---|
| user@test.fr | Password123 |
Alors je suis connecté avec succès Et je reçois un access token valide pour 15 minutes Et je reçois un refresh token valide pour 30 jours
Quand je me connecte avec l'email "inexistant@test.fr"
Alors la connexion échoue Et je vois le message "Email ou mot de passe incorrect"
Quand je me connecte avec:
| email | mot_de_passe |
|---|---|
| user@test.fr | MauvaisPass1 |
Alors la connexion échoue Et je vois le message "Email ou mot de passe incorrect"
Étant donné que j'ai échoué 4 tentatives de connexion
Quand j'échoue une 5ème tentative de connexion
Alors mon compte est temporairement bloqué Et je vois le message "Compte bloqué pour 15 minutes après 5 tentatives échouées" Et je reçois un email de notification de blocage
Étant donné que mon compte est bloqué suite à 5 tentatives échouées Et que seulement 5 minutes se sont écoulées
Quand j'essaie de me connecter avec les bons identifiants
Alors la connexion échoue Et je vois le message "Compte bloqué. Réessayez dans 10 minutes"
Étant donné que mon compte est bloqué suite à 5 tentatives échouées Et que 15 minutes se sont écoulées
Quand je me connecte avec les bons identifiants
Alors je suis connecté avec succès Et le compteur de tentatives est réinitialisé
Étant donné que j'ai échoué 3 tentatives de connexion
Quand je me connecte avec les bons identifiants
Alors je suis connecté avec succès Et le compteur de tentatives est remis à 0
Étant donné que j'ai échoué 3 tentatives de connexion Et que 15 minutes se sont écoulées sans nouvelle tentative
Quand je consulte mon compteur de tentatives
Alors le compteur est réinitialisé à 0
Étant donné que mon compte est bloqué suite à 5 tentatives échouées
Quand j'utilise la fonction "Mot de passe oublié" Et que je réinitialise mon mot de passe
Alors le blocage est levé immédiatement Et je peux me connecter avec le nouveau mot de passe
Étant donné que j'ai échoué 5 tentatives de connexion
Alors je reçois un email avec:
| sujet | Tentatives de connexion suspectes détectées |
|---|---|
| contenu_contient | Votre compte a été temporairement bloqué |
| lien_mot_de_passe | présent |
Étant donné que je suis connecté sur un appareil iOS
Quand je me connecte également sur un appareil Android
Alors les deux sessions sont actives simultanément Et je peux utiliser l'application sur les deux appareils
En tant que nouvel utilisateur Je veux créer un compte avec email et mot de passe Afin d'accéder à l'application RoadWave
15 scénarios (14 standards, 1 plan)
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et que Zitadel est configuré
Étant donné que l'email "nouveau@example.com" n'existe pas
Quand je m'inscris avec les données suivantes:
| champ | valeur |
|---|---|
| email | nouveau@example.com |
| mot_de_passe | Password123 |
| pseudo | nouveau_user |
| date_naissance | 1995-06-15 |
Alors mon compte est créé avec succès Et je reçois un email de vérification Et le lien de vérification expire dans 7 jours Et je suis redirigé vers l'application
Étant donné qu'un utilisateur existe avec l'email "existant@example.com"
Quand je m'inscris avec l'email "existant@example.com"
Alors l'inscription échoue Et je vois le message "Cet email est déjà utilisé"
Quand je m'inscris avec un mot de passe de moins de 8 caractères "Pass1"
Alors l'inscription échoue Et je vois le message "Le mot de passe doit contenir au moins 8 caractères"
Quand je m'inscris avec un mot de passe sans majuscule "password123"
Alors l'inscription échoue Et je vois le message "Le mot de passe doit contenir au moins une majuscule"
Quand je m'inscris avec un mot de passe sans chiffre "Password"
Alors l'inscription échoue Et je vois le message "Le mot de passe doit contenir au moins un chiffre"
Quand je m'inscris avec un pseudo de 2 caractères "ab"
Alors l'inscription échoue Et je vois le message "Le pseudo doit contenir entre 3 et 30 caractères"
Quand je m'inscris avec un pseudo contenant des caractères spéciaux "user@123"
Alors l'inscription échoue Et je vois le message "Le pseudo ne peut contenir que des lettres, chiffres et underscores"
Quand je m'inscris avec un email invalide "email.invalide"
Alors l'inscription échoue Et je vois le message "Format d'email invalide"
Étant donné la date du jour est "2026-01-21"
Quand je m'inscris avec une date de naissance "
Alors l'inscription échoue Et je vois le message "Vous devez avoir au moins 13 ans pour créer un compte"
📊 Exemples de données:
| date_naissance | age |
|---|---|
| 2013-01-22 | 12 |
| 2015-06-15 | 10 |
| 2020-01-01 | 6 |
Étant donné la date du jour est "2026-01-21"
Quand je m'inscris avec une date de naissance "2013-01-21"
Alors mon compte est créé avec succès Et le mode Kids est activé automatiquement
Étant donné la date du jour est "2026-01-21"
Quand je m'inscris avec une date de naissance "1990-06-15"
Alors mon compte est créé avec succès Et j'ai accès à tous les contenus sans restriction d'âge
Quand je m'inscris sans fournir de nom complet Et sans fournir de photo de profil Et sans fournir de bio
Alors mon compte est créé avec succès Et un avatar par défaut est généré Et les champs optionnels sont vides
Étant donné que je me suis inscrit avec l'email "nouveau@example.com" Et que je n'ai pas vérifié mon email
Quand je demande à renvoyer l'email de vérification
Alors un nouvel email de vérification est envoyé Et le précédent lien est invalidé
Étant donné que je me suis inscrit avec l'email "nouveau@example.com" Et que j'ai déjà renvoyé l'email de vérification 3 fois aujourd'hui
Quand je demande à renvoyer l'email de vérification une 4ème fois
Alors la demande échoue Et je vois le message "Vous avez atteint la limite de 3 renvois par jour"
Étant donné que je me suis inscrit il y a 8 jours Et que je n'ai pas vérifié mon email
Quand j'essaie d'utiliser le lien de vérification
Alors la vérification échoue Et je vois le message "Ce lien a expiré" Et je peux demander un nouveau lien
En tant qu'utilisateur ayant oublié son mot de passe Je veux pouvoir réinitialiser mon mot de passe via email Afin de récupérer l'accès à mon compte
14 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et qu'un utilisateur existe avec l'email "user@test.fr"
Quand je clique sur "Mot de passe oublié" Et que je saisis mon email "user@test.fr"
Alors je reçois un email avec un lien de réinitialisation Et le lien expire dans 1 heure Et je vois le message "Email de réinitialisation envoyé"
Quand je demande une réinitialisation pour l'email "inexistant@test.fr"
Alors je vois le même message "Email de réinitialisation envoyé" Mais aucun email n'est envoyé (sécurité - pas d'énumération d'emails)
Étant donné que j'ai demandé une réinitialisation de mot de passe Et que j'ai reçu le lien de réinitialisation
Quand je clique sur le lien Et que je saisis un nouveau mot de passe "NouveauPass123" Et que je confirme le nouveau mot de passe "NouveauPass123"
Alors mon mot de passe est modifié avec succès Et je suis déconnecté de tous mes appareils sauf celui en cours Et je reçois un email de confirmation de changement
Étant donné que j'ai demandé une réinitialisation il y a 2 heures
Quand j'essaie d'utiliser le lien
Alors je vois le message "Ce lien a expiré" Et je peux demander un nouveau lien
Étant donné que j'ai un lien de réinitialisation valide
Quand je saisis un nouveau mot de passe "faible"
Alors la réinitialisation échoue Et je vois le message "Le mot de passe doit contenir au moins 8 caractères, 1 majuscule et 1 chiffre"
Étant donné que j'ai un lien de réinitialisation valide
Quand je saisis un nouveau mot de passe "NouveauPass123" Et que je confirme avec un mot de passe différent "AutrePass123"
Alors la réinitialisation échoue Et je vois le message "Les mots de passe ne correspondent pas"
Étant donné que j'ai déjà demandé 3 réinitialisations dans la dernière heure
Quand je demande une 4ème réinitialisation
Alors la demande échoue Et je vois le message "Maximum 3 demandes par heure. Réessayez plus tard."
Étant donné que j'ai demandé 3 réinitialisations Et que 1 heure s'est écoulée
Quand je demande une nouvelle réinitialisation
Alors la demande réussit Et je reçois un email avec un nouveau lien
Étant donné que je viens de réinitialiser mon mot de passe
Alors je reçois un email de confirmation avec:
| sujet | Votre mot de passe a été modifié |
|---|---|
| contenu_contient | Votre mot de passe a été modifié |
| date_heure | présente |
| appareil | présent |
| localisation | présente |
| action_urgence | Lien si ce n'était pas vous |
Étant donné que je me suis toujours connecté depuis mon iPhone Et que je réinitialise mon mot de passe depuis un PC Windows
Alors je reçois une notification push sur mon iPhone avec:
| titre | Mot de passe modifié |
|---|---|
| message | Depuis Windows - Paris, France |
| action | Sécuriser le compte si ce n'est pas vous |
Étant donné que je suis connecté sur 4 appareils différents Et que je réinitialise mon mot de passe depuis un navigateur web
Alors les 3 autres appareils sont déconnectés immédiatement Et seule la session du navigateur web reste active Et je vois le message "Vous avez été déconnecté des autres appareils par sécurité"
Étant donné que j'ai réinitialisé mon mot de passe avec un lien
Quand j'essaie de réutiliser le même lien
Alors je vois le message "Ce lien a déjà été utilisé" Et je peux demander un nouveau lien si nécessaire
Étant donné que j'ai demandé une réinitialisation et reçu un lien
Quand je demande une nouvelle réinitialisation
Alors l'ancien lien est invalidé Et seul le nouveau lien fonctionne
Étant donné que mon compte est bloqué après 5 tentatives de connexion
Quand je réinitialise mon mot de passe via email
Alors le blocage est levé immédiatement Et je peux me connecter avec le nouveau mot de passe Et le compteur de tentatives est remis à 0
En tant qu'utilisateur connecté Je veux que mes sessions soient sécurisées et gérées automatiquement Afin de maintenir l'accès à l'application sans friction
13 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et que je suis connecté avec succès
Étant donné que j'ai reçu un access token Et que 15 minutes se sont écoulées
Quand je fais une requête API avec cet access token
Alors la requête échoue avec le code 401 Et je vois le message "Token expiré"
Étant donné que mon access token a expiré Et que mon refresh token est valide
Quand l'application demande un nouveau access token
Alors je reçois un nouvel access token valide pour 15 minutes Et je reçois un nouveau refresh token (rotation) Et l'ancien refresh token est invalidé
Étant donné que je me suis connecté il y a 30 jours Et que je n'ai pas utilisé l'application depuis
Quand j'essaie d'utiliser mon refresh token
Alors la requête échoue Et je dois me reconnecter avec email/password
Étant donné que je me suis connecté il y a 25 jours Et que j'utilise l'application régulièrement
Quand je fais une requête API
Alors ma session est automatiquement prolongée Et mon refresh token reste valide
Étant donné que j'ai rafraîchi mon token Et que j'ai reçu un nouveau refresh token
Quand j'essaie de réutiliser l'ancien refresh token
Alors la requête échoue Et je vois le message "Token invalide ou révoqué" Et toutes mes sessions sont révoquées par sécurité
Étant donné que je suis connecté sur 3 appareils différents
Quand je consulte la liste de mes appareils connectés
Alors je vois 3 appareils avec les informations suivantes:
| information | exemple |
|---|---|
| OS | iOS 17.1 |
| Navigateur | Safari |
| Dernière connexion | Il y a 2 heures |
| Localisation | Paris, France (IP visible) |
Étant donné que je suis connecté sur mon iPhone et mon iPad
Quand je révoque la session de mon iPad depuis les paramètres
Alors la session iPad est immédiatement déconnectée Et ma session iPhone reste active
Étant donné que je suis connecté sur 4 appareils
Quand je clique sur "Déconnecter tous les appareils"
Alors les 3 autres appareils sont déconnectés Et seul l'appareil actuel reste connecté
Étant donné que je me suis toujours connecté depuis Paris
Quand je me connecte depuis un nouvel appareil à Lyon
Alors je reçois une notification push sur mes autres appareils Et je reçois un email avec:
| sujet | Nouvelle connexion détectée |
|---|---|
| localisation | Lyon, France |
| appareil | Android 14 - Chrome |
| action | Lien pour révoquer la session |
Étant donné que je me suis toujours connecté depuis la France
Quand je me connecte depuis un appareil aux États-Unis
Alors je reçois une notification push immédiate Et je reçois un email d'alerte de sécurité Et la nouvelle session nécessite une validation 2FA même si désactivée
Étant donné que je ne me suis pas connecté depuis 30 jours
Quand j'ouvre l'application
Alors je suis automatiquement déconnecté Et je dois me reconnecter avec email/password Et je vois le message "Session expirée après 30 jours d'inactivité"
Étant donné que je suis connecté sur:
| appareil |
|---|
| iPhone |
| iPad |
| PC Windows (Web) |
Quand je fais des actions sur les 3 appareils simultanément
Alors toutes les sessions fonctionnent sans conflit Et chaque appareil maintient sa propre session
Étant donné que j'ai reçu un access token JWT
Quand l'API RoadWave valide le token
Alors la validation est faite localement avec la clé publique Zitadel Et aucune requête externe n'est effectuée (performance) Et le token contient les claims suivants:
| claim | valeur_exemple |
|---|---|
| sub | user-id-123 |
| email | user@test.fr |
| exp | timestamp + 15 minutes |
| iss | zitadel.roadwave.com |
En tant qu'utilisateur soucieux de sécurité Je veux activer la 2FA sur mon compte Afin de protéger mon accès même si mon mot de passe est compromis
16 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et que je suis connecté à mon compte
Étant donné que la 2FA n'est pas activée sur mon compte
Quand je choisis d'activer la 2FA TOTP
Alors je vois un QR code à scanner Et je vois le secret partagé en texte clair (backup) Et je dois entrer un code de vérification depuis mon app authenticator
Quand je saisis un code TOTP valide
Alors la 2FA TOTP est activée avec succès Et je reçois des codes de backup (10 codes)
Étant donné que la 2FA TOTP est activée sur mon compte
Quand je me connecte avec email/password
Alors je suis redirigé vers la page de saisie du code 2FA
Quand je saisis un code TOTP valide de mon authenticator
Alors je suis connecté avec succès
Étant donné que la 2FA TOTP est activée
Quand je me connecte avec email/password Et que je saisis un code TOTP invalide "000000"
Alors la connexion échoue Et je vois le message "Code d'authentification invalide" Et je peux réessayer
Étant donné que la 2FA TOTP est activée Et que j'ai perdu l'accès à mon authenticator
Quand je me connecte avec email/password Et que je clique sur "Utiliser un code de backup" Et que je saisis un code de backup valide
Alors je suis connecté avec succès Et le code de backup utilisé est invalidé Et il me reste 9 codes de backup
Étant donné que la 2FA n'est pas activée
Quand je choisis d'activer la 2FA par email
Alors la 2FA email est activée immédiatement Et je vois le message "2FA email activée. Vous recevrez un code à chaque connexion"
Étant donné que la 2FA email est activée
Quand je me connecte avec email/password
Alors je reçois un email avec un code à 6 chiffres Et le code expire dans 10 minutes Et je dois saisir ce code pour terminer la connexion
Étant donné que la 2FA email est activée Et que je me suis connecté avec email/password Et que j'ai reçu un code 2FA par email il y a 11 minutes
Quand je saisis ce code
Alors la connexion échoue Et je vois le message "Code expiré. Demandez un nouveau code."
Étant donné que la 2FA email est activée Et que je suis sur la page de saisie du code 2FA
Quand je clique sur "Renvoyer le code"
Alors je reçois un nouveau code par email Et l'ancien code est invalidé
Étant donné que la 2FA TOTP est activée
Quand je me connecte avec email/password et code TOTP Et que je coche "Ne plus demander sur cet appareil"
Alors je suis connecté avec succès Et cet appareil est enregistré comme "appareil de confiance"
Quand je me reconnecte dans les 30 jours suivants sur ce même appareil
Alors je ne dois pas saisir de code 2FA
Étant donné que j'ai enregistré un appareil de confiance il y a 31 jours
Quand je me connecte depuis cet appareil
Alors je dois saisir un code 2FA Et je vois le message "Appareil de confiance expiré. Veuillez vous authentifier"
Étant donné que j'ai enregistré 3 appareils de confiance
Quand je consulte mes paramètres de sécurité
Alors je vois la liste de mes 3 appareils de confiance avec:
| information | exemple |
|---|---|
| Nom | iPhone 13 - Safari |
| Date ajout | 15 janvier 2026 |
| Dernière vue | Il y a 2 heures |
| Expire le | 14 février 2026 |
Étant donné que j'ai un iPhone enregistré comme appareil de confiance
Quand je révoque cet appareil depuis les paramètres
Alors l'appareil est supprimé de la liste
Quand je me reconnecte depuis cet iPhone
Alors je dois saisir un code 2FA
Étant donné que j'ai 5 appareils de confiance enregistrés
Quand je clique sur "Révoquer tous les appareils de confiance"
Alors tous les appareils sont révoqués Et je vois le message "Tous les appareils de confiance ont été révoqués"
Étant donné que j'ai un appareil de confiance enregistré en France Et que je me connecte depuis ce même appareil mais avec une IP américaine
Quand je tente de me connecter
Alors la 2FA est requise malgré l'appareil de confiance Et je vois le message "Connexion suspecte détectée. Authentification requise."
Étant donné que la 2FA TOTP est activée
Quand je désactive la 2FA depuis mes paramètres Et que je confirme avec mon mot de passe
Alors la 2FA est désactivée Et tous les codes de backup sont invalidés Et tous les appareils de confiance sont révoqués
Étant donné que la 2FA est activée Et que j'ai utilisé 8 codes de backup sur 10
Quand je demande à régénérer les codes de backup
Alors je reçois 10 nouveaux codes Et tous les anciens codes (utilisés ou non) sont invalidés
En tant qu'utilisateur inscrit Je veux vérifier mon adresse email Afin d'accéder à toutes les fonctionnalités selon mon rôle
10 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible
Étant donné que je suis un auditeur avec email non vérifié
Quand j'essaie d'écouter du contenu
Alors je peux écouter tous les contenus sans limite
Étant donné que je suis un auditeur avec email non vérifié Et que j'ai créé 4 contenus
Quand je crée un 5ème contenu
Alors le contenu est créé avec succès Mais quand j'essaie de créer un 6ème contenu Alors la création échoue Et je vois le message "Vérifiez votre email pour créer plus de contenus"
Étant donné que je suis un auditeur avec email non vérifié Et que j'ai créé 2 contenus
Quand je crée mon 3ème contenu
Alors le contenu est créé avec succès Et je vois une notification in-app "Vérifiez votre email pour débloquer la création illimitée"
Étant donné que je suis un auditeur avec email non vérifié Et que j'ai reçu un lien de vérification
Quand je clique sur le lien de vérification dans l'email
Alors mon email est marqué comme vérifié Et je vois le message "Email vérifié avec succès" Et toutes les fonctionnalités sont débloquées
Étant donné que je suis inscrit comme créateur Et que mon email n'est pas vérifié Et que je remplis les conditions de monétisation
Quand j'essaie d'accéder au programme de monétisation
Alors l'accès est refusé Et je vois le message "Vérifiez votre email pour accéder à la monétisation"
Étant donné que je suis un créateur avec email non vérifié Et que j'ai créé 5 contenus
Quand j'essaie de créer un 6ème contenu
Alors la création échoue Et je vois le message "Vérifiez votre email pour publier des contenus illimités"
Étant donné que je suis un créateur avec email non vérifié Et que j'ai reçu un lien de vérification
Quand je clique sur le lien de vérification
Alors mon email est marqué comme vérifié Et je peux publier des contenus illimités Et je peux accéder au programme de monétisation si j'en remplis les conditions
Étant donné que je suis un créateur avec email non vérifié
Quand j'essaie de compléter le KYC via Mangopay
Alors l'accès au KYC est refusé Et je vois le message "Vérifiez votre email avant de procéder au KYC"
Étant donné que j'ai déjà vérifié mon email avec un lien
Quand j'essaie de réutiliser le même lien de vérification
Alors la vérification échoue Et je vois le message "Ce lien a déjà été utilisé"
Étant donné que je suis un auditeur avec email vérifié Et que j'ai créé 10 contenus
Quand je crée un 11ème contenu
Alors le contenu est créé avec succès Et il n'y a pas de limite de création
En tant que créateur Je veux remplir les métadonnées de mon contenu Afin de le publier sur RoadWave
34 scénarios (32 standards, 2 plans)
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et que je suis un créateur connecté Et que mon fichier audio est encodé et prêt
Quand je remplis les métadonnées suivantes:
| champ | valeur |
|---|---|
| Titre | Histoire de la Tour Eiffel |
| Type géo | Ancré |
| Zone | Point GPS (48.8584, 2.2945, 500m) |
| Tags | Voyage, Culture générale |
| Classification âge | Tout public |
Alors la publication réussit Et mon contenu est soumis pour validation
Quand je saisis un titre de 50 caractères
Alors le titre est accepté Et la validation passe
Quand je saisis un titre de 4 caractères "Test"
Alors la publication échoue Et je vois le message "Le titre doit contenir entre 5 et 100 caractères"
Quand je saisis un titre de 101 caractères
Alors la publication échoue Et je vois le message "Le titre doit contenir entre 5 et 100 caractères"
Quand je saisis un titre de exactement 5 caractères "Titre"
Alors le titre est accepté
Quand je saisis un titre de exactement 100 caractères
Alors le titre est accepté
Quand je sélectionne le type géo "Ancré"
Alors le système applique une pondération géo de 0.7 Et je dois définir une zone de diffusion précise
Quand je sélectionne le type géo "Contextuel"
Alors le système applique une pondération géo de 0.5 Et je peux définir une zone ville/département/région
Quand je sélectionne le type géo "Neutre"
Alors le système applique une pondération géo de 0.2 Et je peux définir une zone nationale
Quand je choisis "Point GPS" Et que je définis les coordonnées (48.8584, 2.2945) Et que je définis un rayon de 500 mètres
Alors la zone est validée Et le contenu sera diffusé dans un rayon de 500m autour du point
Quand je définis un rayon de 50 mètres (< 100m)
Alors la validation échoue Et je vois le message "Le rayon doit être entre 100m et 10km"
Quand je définis un rayon de 15 km (> 10km)
Alors la validation échoue Et je vois le message "Le rayon doit être entre 100m et 10km"
Quand je choisis "Ville"
Alors je vois une liste de villes du référentiel INSEE
Quand je sélectionne "Paris (75000)"
Alors la zone est définie sur toute la ville de Paris
Quand je choisis "Département" Et que je sélectionne "Ille-et-Vilaine (35)"
Alors la zone couvre tout le département 35
Quand je choisis "Région" Et que je sélectionne "Bretagne"
Alors la zone couvre toute la région Bretagne
Quand je choisis "National"
Alors la zone couvre toute la France Et aucune restriction géographique n'est appliquée
Étant donné que j'ai sélectionné "Point GPS"
Quand j'essaie de sélectionner également "Ville"
Alors la première sélection est remplacée Et seule "Ville" reste active
Quand je sélectionne 1 tag "Voyage"
Alors la validation passe Et le contenu est tagué "Voyage"
Quand je sélectionne 3 tags "Automobile", "Technologie", "Sport"
Alors la validation passe Et le contenu est tagué avec les 3 tags
Quand j'essaie de publier sans sélectionner de tag
Alors la publication échoue Et je vois le message "Vous devez sélectionner entre 1 et 3 tags"
Quand j'essaie de sélectionner 4 tags
Alors le 4ème tag ne peut pas être ajouté Et je vois le message "Maximum 3 tags"
Quand je consulte la liste des tags
Alors je vois les tags suivants:
| tag |
|---|
| Automobile |
| Voyage |
| Famille |
| Amour |
| Musique |
| Économie |
| Cryptomonnaie |
| Politique |
| Culture générale |
| Sport |
| Technologie |
| Santé |
Quand j'essaie de publier sans classification âge
Alors la publication échoue Et je vois le message "Vous devez sélectionner une classification d'âge"
Quand je sélectionne la classification "
Alors le contenu sera visible pour "
📊 Exemples de données:
| classification | public_cible |
|---|---|
| Tout public | Tous les utilisateurs |
| 13+ | Utilisateurs 13 ans et plus |
| 16+ | Utilisateurs 16 ans et plus |
| 18+ | Utilisateurs 18 ans et plus |
Étant donné que je choisis le type géo "Ancré" Et que mon tag principal est "Voyage"
Quand la publication est soumise
Alors une image de couverture est générée automatiquement:
| paramètre | valeur |
|---|---|
| Icône | 📍 (Ancré) |
| Couleur | Bleu-vert (Voyage) |
| Format | 800×800px PNG |
Étant donné que je choisis "Contextuel"
Quand l'image est générée
Alors l'icône est 🌍 (Contextuel)
Étant donné que je choisis "Neutre"
Quand l'image est générée
Alors l'icône est 🎧 (Neutre)
Étant donné que mon tag principal est "
Quand l'image est générée
Alors la couleur de fond est "
📊 Exemples de données:
| tag | couleur |
|---|---|
| Automobile | Bleu |
| Voyage | Vert |
| Musique | Rouge |
| Économie | Gris |
| Sport | Orange |
Quand je publie sans description Et sans image de couverture personnalisée
Alors la publication réussit Et les champs optionnels restent vides Et une image par défaut est générée
Étant donné que mon fichier audio est prêt
Quand je commence à remplir les métadonnées
Alors je peux publier en environ 2 minutes
Quand je publie mon premier contenu
Alors aucun champ complexe n'est demandé Et je ne suis pas bloqué sur description ou image Et la publication est fluide
Étant donné que j'ai rempli toutes les métadonnées
Quand je clique sur "Prévisualiser"
Alors je vois un aperçu de mon contenu:
| élément | affiché |
|---|---|
| Titre | ✅ |
| Image couverture | ✅ |
| Tags | ✅ |
| Zone diffusion | ✅ |
| Durée audio | ✅ |
| Classification | ✅ |
Étant donné que j'ai commencé à remplir les métadonnées
Quand je clique sur "Enregistrer brouillon"
Alors mes métadonnées sont sauvegardées Et je peux reprendre la publication plus tard
Étant donné que j'ai un brouillon sauvegardé
Quand j'accède à mes contenus
Alors je vois le brouillon avec statut "📝 Brouillon" Et je peux reprendre la publication
En tant que créateur Je veux pouvoir modifier ou supprimer mes contenus Afin de garder le contrôle sur mes publications
30 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et que je suis un créateur connecté Et que j'ai publié un contenu
Étant donné que mon contenu a le titre "Histoire de Paris"
Quand je modifie le titre en "Histoire fascinante de Paris"
Alors la modification est enregistrée immédiatement Et je vois le message "Titre modifié avec succès"
Étant donné que mon titre contient une faute "Histoore de Paris"
Quand je corrige en "Histoire de Paris"
Alors la modification est acceptée Et le titre corrigé est affiché
Étant donné que j'ai publié sans description
Quand j'ajoute une description "Découvrez l'histoire de la capitale"
Alors la description est enregistrée Et elle est visible sur la page du contenu
Étant donné que mon contenu a déjà une description
Quand je modifie la description
Alors la nouvelle description remplace l'ancienne Et la modification est immédiate
Étant donné que mon contenu est tagué "Sport", "Musique"
Quand je change les tags en "Sport", "Santé"
Alors les nouveaux tags sont appliqués Et l'algorithme utilise les nouveaux tags pour recommandations
Étant donné que mon contenu a une image auto-générée
Quand j'uploade une image personnalisée 800×800px
Alors l'image personnalisée remplace l'image par défaut Et elle est visible sur le contenu
Étant donné que mon contenu audio est publié
Quand j'essaie de remplacer le fichier audio
Alors la modification est refusée Et je vois le message "L'audio ne peut pas être modifié après publication"
Étant donné que je veux changer l'audio après validation
Quand j'essaie de modifier
Alors le système refuse pour éviter:
| risque |
|---|
| Uploader contenu validé puis remplacer spam |
| Fraude sur l'intégrité du contenu |
Étant donné que mon contenu est diffusé à Paris
Quand j'essaie de changer la zone en "National"
Alors la modification est refusée Et je vois le message "La zone de diffusion ne peut pas être modifiée"
Étant donné que je veux changer ma zone
Quand j'essaie de modifier
Alors le système refuse pour éviter:
| manipulation |
|---|
| Créer "Local Paris" puis changer en "National" |
| Boost artificiel de visibilité |
Étant donné que mon contenu est type "Neutre" (pondération 0.2)
Quand j'essaie de changer en "Ancré" (pondération 0.7)
Alors la modification est refusée Et je vois le message "Le type géographique ne peut pas être modifié"
Étant donné que je veux changer le type géo
Quand j'essaie de modifier
Alors le système refuse pour éviter:
| abus |
|---|
| Créer "Neutre" puis passer en "Ancré" |
| Manipulation de la pondération algorithme |
Étant donné que mon contenu est classé "Tout public"
Quand j'essaie de changer en "18+"
Alors la modification est refusée Et je vois le message "La classification d'âge ne peut pas être modifiée"
Étant donné que je veux changer la classification
Quand j'essaie de modifier
Alors le système refuse pour garantir:
| protection |
|---|
| Classification vérifiée en modération |
| Pas de contournement validation |
| Sécurité des mineurs |
Étant donné que je veux absolument changer l'audio
Quand je consulte les options
Alors je vois "Supprimer et republier le contenu" Et c'est la seule solution disponible
Étant donné que je suis un nouveau créateur (2 contenus validés) Et que je supprime puis republie un contenu
Quand je republie avec les modifications
Alors le contenu repasse en file de validation Et une nouvelle validation est effectuée
Étant donné que je suis créateur vérifié (≥3 contenus validés) Et que je supprime puis republie un contenu
Quand je republie avec les modifications
Alors le contenu est publié immédiatement Et aucune validation préalable n'est requise
Quand je clique sur "Supprimer le contenu" Et que je confirme la suppression
Alors le contenu est supprimé immédiatement Et disparaît de la liste publique
Quand je clique sur "Supprimer"
Alors je vois un message de confirmation:
| titre | Êtes-vous sûr ? |
|---|---|
| message | Cette action est définitive |
| warning | Le contenu sera supprimé définitivement |
| actions | Confirmer / Annuler |
Étant donné que j'ai supprimé un contenu
Quand j'essaie de le récupérer
Alors la récupération est impossible Et le contenu est définitivement perdu
Quand je supprime un contenu
Alors l'entrée en base de données est marquée "deleted" Et les fichiers CDN sont marqués pour suppression Et la suppression effective a lieu sous 5 minutes
Étant donné que 1000 personnes ont écouté mon contenu
Quand je supprime le contenu
Alors leur historique est conservé Mais marqué "Contenu supprimé par créateur" Et la durée d'écoute est conservée pour leurs stats
Étant donné que mon contenu a généré 10K écoutes
Quand je supprime le contenu
Alors les métriques globales sont conservées anonymement:
| métrique | conservée |
|---|---|
| Total écoutes | ✅ (anonyme) |
| Durée totale | ✅ (anonyme) |
| Catégorie | ✅ (anonyme) |
| Auteur | ❌ (anonymisé) |
Et c'est conforme RGPD
Étant donné que mon contenu est supprimé
Quand 24 heures s'écoulent
Alors tous les fichiers audio sont purgés du CDN Bunny Et l'espace de stockage est libéré
Étant donné que 500 utilisateurs ont écouté mon contenu
Quand je supprime le contenu
Alors aucune notification n'est envoyée aux auditeurs Et il n'y a pas d'effet Streisand
Étant donné qu'un auditeur a écouté mon contenu Et que j'ai supprimé ce contenu
Quand l'auditeur tente de le réécouter depuis son historique
Alors il voit le message "Ce contenu n'est plus disponible" Et la lecture est impossible
Étant donné qu'un auditeur a écouté mon contenu le 15 janvier Et que je supprime le contenu le 20 janvier
Quand l'auditeur consulte son historique
Alors il voit "Vous avez écouté ce contenu le 15 janvier 2026" Et le titre est remplacé par "Contenu supprimé" Et la date d'écoute est conservée
Étant donné que j'ai publié 10 contenus Et que je supprime 2 contenus
Quand je consulte mes statistiques globales
Alors je vois:
| métrique | valeur |
|---|---|
| Contenus publiés | 8 (actifs) |
| Total historique | 10 |
| Suppressions | 2 |
Et l'historique des suppressions est visible
Étant donné que j'ai modifié un titre 10 fois
Quand j'essaie de modifier une 11ème fois
Alors la modification est acceptée
Étant donné que j'ai modifié un contenu plusieurs fois
Quand je consulte l'historique
Alors je vois:
| date | modification |
|---|---|
| 21/01/2026 | Titre changé |
| 20/01/2026 | Tags modifiés |
| 19/01/2026 | Description ajoutée |
Et je peux tracer toutes les modifications
En tant que créateur Je veux uploader mon contenu audio Afin qu'il soit encodé et disponible pour les auditeurs
29 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et que je suis un créateur connecté
Quand j'uploade un fichier MP3 de 50 MB et 30 minutes
Alors l'upload réussit Et le fichier est envoyé vers Bunny Storage temporaire Et un job d'encodage asynchrone est lancé
Quand j'uploade un fichier AAC de 80 MB et 1 heure
Alors l'upload réussit Et le fichier est accepté Et l'encodage démarre
Quand j'uploade un fichier M4A de 100 MB et 2 heures
Alors l'upload réussit Et le fichier est traité comme AAC Et l'encodage démarre
Quand j'essaie d'uploader un fichier WAV
Alors l'upload échoue Et je vois le message "Format non supporté. Utilisez MP3 ou AAC (.mp3, .aac, .m4a)"
Quand j'essaie d'uploader un fichier FLAC
Alors l'upload échoue Et je vois le message "Format non supporté. Utilisez MP3 ou AAC (.mp3, .aac, .m4a)"
Quand j'essaie d'uploader un fichier MP3 de 201 MB
Alors l'upload échoue Et je vois le message "Fichier trop volumineux (max 200 MB)"
Quand j'uploade un fichier MP3 de exactement 200 MB
Alors l'upload réussit Et le fichier est accepté
Quand j'essaie d'uploader un fichier de 4h 10min
Alors l'upload échoue Et je vois le message "Durée trop longue (max 4 heures)"
Quand j'uploade un fichier de exactement 4 heures
Alors l'upload réussit Et le fichier est accepté
Quand je sélectionne un fichier dans l'interface
Alors la validation du format est faite immédiatement côté client Et je suis informé avant même de lancer l'upload si le format est invalide
Étant donné qu'un fichier a passé la validation client
Quand le backend reçoit le fichier
Alors une validation supplémentaire est effectuée Et le format et l'intégrité sont vérifiés
Quand j'uploade un fichier MP3 valide
Alors le fichier est stocké temporairement dans Bunny Storage Et un job d'encodage est mis en file d'attente
Étant donné qu'un job d'encodage est lancé
Quand le worker Go traite le fichier
Alors le format est validé avec FFmpeg Et l'intégrité du fichier est vérifiée
Étant donné qu'un fichier audio est validé
Quand l'encodage démarre
Alors 3 profils Opus sont générés:
| qualité | bitrate | usage |
|---|---|---|
| Basse | 24 kbps | 2G/Edge |
| Standard | 48 kbps | 3G |
| Haute | 64 kbps | 4G/5G |
Étant donné que les profils Opus sont générés
Quand l'encodage continue
Alors un fichier manifest .m3u8 est créé Et des segments .ts sont générés Et le contenu est prêt pour streaming HLS
Étant donné que l'encodage est en cours
Quand les métadonnées sont traitées
Alors une image de couverture par défaut est générée Et l'image fait 800×800px au format PNG
Étant donné que l'encodage est terminé avec succès
Quand tous les fichiers de sortie sont générés
Alors le fichier original MP3/AAC est supprimé Et seuls les profils Opus et HLS sont conservés Et l'espace de stockage est économisé
Étant donné qu'un fichier de 5 minutes est uploadé
Quand l'encodage démarre
Alors l'encodage prend environ 30 secondes Et je reçois une notification "Contenu prêt à publier"
Étant donné qu'un fichier de 1 heure est uploadé
Quand l'encodage démarre
Alors l'encodage prend environ 5 minutes Et une barre de progression est affichée
Étant donné qu'un fichier de 4 heures est uploadé
Quand l'encodage démarre
Alors l'encodage prend environ 20 minutes Et je peux fermer l'app (traitement asynchrone)
Étant donné que mon contenu est en cours d'encodage
Quand l'encodage se termine avec succès
Alors je reçois une notification push "✅ Votre contenu est prêt à publier" Et je peux accéder à l'interface de publication
Étant donné qu'un fichier MP3 corrompu est uploadé
Quand l'encodage démarre
Alors l'encodage échoue Et je reçois une notification "❌ Erreur d'encodage: fichier corrompu" Et le fichier temporaire est supprimé
Étant donné qu'un contenu est publié
Quand un auditeur écoute le contenu
Alors il peut choisir parmi les vitesses:
| vitesse | usage |
|---|---|
| 0.75x | Compréhension difficile |
| 1.0x | Normal (défaut) |
| 1.25x | Gain léger |
| 1.5x | Podcasts longs |
| 2.0x | Survol rapide |
Étant donné que je suis un modérateur Et qu'un contenu de 30 secondes est à valider
Quand je l'écoute à 2.0x
Alors je termine l'écoute en 15 secondes Et ma productivité est doublée
Étant donné que je suis un auditeur Et qu'un podcast de 1 heure est disponible
Quand je configure la vitesse à 1.5x
Alors j'écoute le podcast en 40 minutes Et je gagne 20 minutes
Étant donné que je configure la vitesse à 1.5x
Quand j'écoute plusieurs contenus
Alors tous les contenus sont lus à 1.5x par défaut Et ma préférence est sauvegardée
Étant donné que 100 contenus sont uploadés simultanément
Quand les jobs d'encodage sont distribués
Alors plusieurs workers Go traitent les jobs en parallèle Et Kubernetes scale automatiquement les pods Et tous les contenus sont encodés sans délai excessif
Étant donné que mon contenu est en cours d'encodage
Quand je consulte mes contenus
Alors je vois le statut:
| état | affichage |
|---|---|
| En attente | ⏳ File d'attente |
| En cours | ⚙️ Encodage en cours (45%) |
| Terminé | ✅ Prêt à publier |
| Échec | ❌ Erreur - Réessayer |
Étant donné que l'encodage de mon contenu a échoué
Quand je clique sur "Réessayer"
Alors un nouveau job d'encodage est lancé Et je peux tenter à nouveau
En tant que nouveau créateur Je veux que mes 3 premiers contenus soient validés Afin de devenir créateur vérifié
30 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et que je suis un nouveau créateur
Quand je publie mon premier contenu
Alors le contenu passe en file d'attente modération Et je vois le message "Votre contenu est en cours de validation (24-48h)" Et le contenu n'est pas encore visible publiquement
Étant donné que mon premier contenu a été validé
Quand je publie mon deuxième contenu
Alors le contenu passe en file d'attente modération Et le délai estimé est 24-48h
Étant donné que mes 2 premiers contenus ont été validés
Quand je publie mon troisième contenu
Alors le contenu passe en file d'attente modération Et je vois "Dernière validation avant statut vérifié ✓"
Étant donné qu'un contenu est en file de validation
Quand le modérateur junior l'examine
Alors il écoute les 30 premières secondes Et il vérifie les métadonnées
Étant donné qu'un contenu a une qualité audio claire
Quand le modérateur l'écoute
Alors il vérifie que l'audio est compréhensible Et qu'il n'y a pas de grésillement excessif
Étant donné qu'un contenu a un audio très grésillant
Quand le modérateur l'écoute
Alors le contenu est rejeté Et la raison est "Qualité audio insuffisante"
Étant donné qu'un contenu respecte les règles
Quand le modérateur l'examine
Alors il vérifie qu'il n'y a pas de contenu prohibé:
| type prohibé |
|---|
| Haine |
| Violence |
| Spam |
| Illégalité |
Étant donné qu'un contenu contient des propos haineux
Quand le modérateur l'écoute
Alors le contenu est rejeté immédiatement Et la raison est "Contenu haineux (violation des règles)" Et le créateur peut recevoir un strike
Étant donné qu'un contenu familial est classé "Tout public"
Quand le modérateur l'écoute
Alors il vérifie que la classification correspond au contenu Et le contenu est accepté
Étant donné qu'un contenu adulte est classé "Tout public"
Quand le modérateur détecte l'incohérence
Alors le contenu est rejeté Et la raison est "Classification d'âge incorrecte"
Étant donné qu'un contenu sur l'automobile est tagué "Automobile", "Technologie"
Quand le modérateur vérifie les tags
Alors il confirme que les tags correspondent au contenu Et le contenu est accepté
Étant donné qu'un contenu musical est tagué "Automobile", "Sport"
Quand le modérateur détecte l'incohérence
Alors le contenu est rejeté Et la raison est "Tags non pertinents avec le contenu"
Étant donné qu'un audio-guide de la Tour Eiffel est en "Point GPS" Paris
Quand le modérateur vérifie la cohérence
Alors la zone est appropriée Et le contenu est accepté
Étant donné qu'un audio-guide de la Tour Eiffel est en zone "National"
Quand le modérateur détecte l'incohérence
Alors le contenu est rejeté Et la raison est "Zone de diffusion incohérente (devrait être Point GPS)"
Étant donné que je publie un contenu un lundi
Quand le contenu entre en file de validation
Alors le délai estimé est 24-48h (mercredi maximum)
Étant donné que je publie un contenu un vendredi soir
Quand le contenu entre en file de validation
Alors le délai peut atteindre 72h (lundi) Et je vois "Validation en cours, délai 24-72h (weekend)"
Étant donné que 10 contenus sont en file de validation
Quand les modérateurs traitent la file
Alors les contenus sont traités dans l'ordre d'arrivée Et pas de traitement prioritaire
Étant donné que mon contenu est validé et accepté
Alors je reçois un email "✅ Votre contenu '[Titre]' est en ligne !" Et je reçois une notification push Et je vois un lien direct vers le contenu
Étant donné que mon premier contenu est accepté
Alors je vois "1/3 contenus validés pour devenir créateur vérifié"
Quand mon deuxième contenu est accepté
Alors je vois "2/3 contenus validés pour devenir créateur vérifié"
Étant donné que mon contenu est rejeté
Alors je reçois un email "❌ Contenu '[Titre]' refusé" Et je reçois une notification push Et je vois la raison exacte: "Qualité audio insuffisante" Et je vois un lien vers les règles de publication
Étant donné que mon contenu a été rejeté pour "Tags non pertinents"
Quand je corrige les tags Et que je resoumets le contenu
Alors le contenu repasse en file de validation Et une nouvelle validation est effectuée
Étant donné que mes 3 premiers contenus ont été validés
Alors j'obtiens le statut "Créateur Vérifié" Et je reçois une notification "🎉 Vous êtes maintenant créateur vérifié !" Et un badge ✓ apparaît sur mon profil
Étant donné que j'ai le statut vérifié
Quand un utilisateur consulte mon profil
Alors il voit le badge ✓ à côté de mon pseudo Et une mention "Créateur vérifié"
Étant donné que je suis créateur vérifié
Quand je publie un 4ème contenu
Alors le contenu est publié immédiatement Et il n'y a pas de validation préalable Et je vois "✅ Contenu publié"
Étant donné que je suis créateur vérifié Et que je publie un contenu
Quand le contenu est en ligne
Alors il peut être signalé par les utilisateurs Et sera modéré uniquement si signalé
Étant donné que je suis un modérateur junior
Quand j'accède à l'interface de modération
Alors je vois la file des contenus à valider Et je vois le nombre total en attente Et les contenus sont triés par ordre FIFO
Étant donné que je suis un modérateur
Quand j'écoute un contenu de 30 secondes
Alors je peux choisir la vitesse 1.5x ou 2.0x Et je termine l'écoute en 15 secondes à 2x Et ma productivité est doublée
Étant donné que je modère un contenu
Quand j'utilise les raccourcis clavier
Alors je peux:
| touche | action |
|---|---|
| A | Accepter |
| R | Rejeter |
| Espace | Play/Pause |
Et la modération est accélérée
Étant donné qu'un créateur soumet son 2ème contenu
Quand le modérateur examine le contenu
Alors il voit l'historique:
| contenu | statut |
|---|---|
| Contenu 1 | Validé |
| Contenu 2 | En cours |
Et il peut juger la cohérence du créateur
Étant donné qu'un créateur soumet 3 contenus
Quand les modérateurs traitent ces contenus
Alors le temps total est environ:
| action | temps |
|---|---|
| Écoute 30s × 3 | 90s |
| Vérification metadata | 15s |
| Décision | 5s |
| Total | 110s |
9 scénarios
Contexte commun à tous les scénarios
Étant donné que je suis un utilisateur connecté Et que la géolocalisation est activée Et que je suis en mode écoute
Étant donné que je suis situé à la position GPS 48.8566, 2.3522 Et qu'aucun contenu n'existe dans un rayon de 50 km autour de ma position Mais qu'au moins 1 contenu existe dans un rayon de 100 km
Quand le système recherche du contenu à me proposer
Alors le système élargit automatiquement la zone de recherche à 100 km Et je reçois un message "Aucun contenu dans votre zone immédiate. Voici du contenu à proximité (100 km)" Et un contenu dans le rayon de 100 km m'est proposé
Étant donné que je suis situé dans le département "75" (Paris) Et qu'aucun contenu n'existe dans un rayon de 100 km autour de ma position Mais qu'au moins 1 contenu existe avec la zone "département" pour "75"
Quand le système recherche du contenu à me proposer
Alors le système élargit automatiquement la zone de recherche au département Et je reçois un message "Aucun contenu local disponible. Voici du contenu dans votre département" Et un contenu départemental m'est proposé
Étant donné que je suis situé dans la région "Île-de-France" Et qu'aucun contenu n'existe dans un rayon de 100 km autour de ma position Et qu'aucun contenu départemental n'existe pour mon département Mais qu'au moins 1 contenu existe avec la zone "région" pour "Île-de-France"
Quand le système recherche du contenu à me proposer
Alors le système élargit automatiquement la zone de recherche à la région Et je reçois un message "Aucun contenu local disponible. Voici du contenu dans votre région" Et un contenu régional m'est proposé
Étant donné que je suis situé en France Et qu'aucun contenu n'existe dans un rayon de 100 km autour de ma position Et qu'aucun contenu départemental n'existe pour mon département Et qu'aucun contenu régional n'existe pour ma région
Quand le système recherche du contenu à me proposer
Alors le système bascule automatiquement sur du contenu national Et je reçois un message "Aucun contenu local disponible. Voici du contenu national qui pourrait vous intéresser" Et un contenu national m'est proposé Et je ne reste jamais sans contenu disponible
Étant donné que je suis situé dans une zone rurale isolée Et qu'aucun contenu n'existe dans un rayon de 50 km Et qu'aucun contenu n'existe dans un rayon de 100 km Et qu'aucun contenu départemental n'existe Et qu'aucun contenu régional n'existe
Quand le système recherche du contenu à me proposer
Alors le système essaie d'abord 50 km Et tout ce processus se fait de manière transparente et automatique Et je reçois le message correspondant au dernier niveau trouvé
Étant donné que je suis situé à la position GPS 43.6047, 1.4442
Et que
Quand le système me propose du contenu
Alors je reçois le message "
Étant donné que le système a épuisé toutes les zones géographiques locales
Quand le système bascule sur du contenu national
Alors je dois toujours avoir au moins 1 contenu disponible Et ce contenu peut être:
| type_contenu |
|---|
| Actualités Le Monde |
| Podcasts génériques |
| Contenu éducatif national |
| Contenu culturel national |
Étant donné que je lance l'application Et qu'aucun contenu local n'est disponible dans ma zone
Quand le système recherche du contenu
Alors je ne dois jamais voir un message d'erreur "Aucun contenu disponible" Et je ne dois jamais voir un écran vide Et un contenu doit toujours m'être proposé, même si c'est du contenu national
Étant donné que je suis situé dans une zone rurale Et qu'aucun contenu n'existe dans un rayon de 50 km Et que mes centres d'intérêt incluent "Automobile" à 80% et "Voyage" à 70% Et qu'un contenu national existe avec le tag "Automobile" Et qu'un contenu national existe avec le tag "Politique"
Quand le système bascule sur du contenu national
Alors le contenu national proposé prend en compte mes centres d'intérêt Et le contenu "Automobile" a un score supérieur au contenu "Politique"
11 scénarios
Contexte commun à tous les scénarios
Étant donné que je suis un utilisateur connecté Et que je suis en mode écoute Et qu'un contenu "C123" est en cours de lecture
Étant donné que j'écoute le contenu "C123" depuis 30 secondes Et que la durée totale du contenu est de 120 secondes
Quand le contenu est supprimé par la modération côté backend
Alors la lecture du contenu continue sans interruption Et je peux écouter le contenu jusqu'à la fin Et aucune interruption brutale ne se produit
Étant donné que le contenu "C123" a été supprimé pendant ma lecture Et que j'ai écouté le contenu jusqu'à la fin
Quand le contenu se termine
Alors le système attend 2 secondes Et passe automatiquement au contenu suivant Et je reçois une notification toast discrète "Contenu précédent retiré (violation règles)"
Étant donné que le contenu "C123" a été supprimé pendant ma lecture Et que je suis passé au contenu suivant "C456"
Quand j'essaie d'appuyer sur le bouton "Précédent"
Alors le bouton "Précédent" ne me ramène pas au contenu supprimé Et je reçois un message "Ce contenu n'est plus disponible" Et la lecture du contenu actuel "C456" continue
Étant donné que je suis sur le contenu "C456" Et que le contenu précédent "C123" a été supprimé
Quand j'appuie sur le bouton "Précédent" pour revenir au contenu supprimé
Alors je reçois un message "Ce contenu n'est plus disponible" Et la lecture reste sur le contenu actuel "C456" Et aucune action n'est effectuée
Étant donné que je conduis à une vitesse de 60 km/h Et que le contenu "C123" est supprimé pendant ma lecture
Quand le contenu se termine
Alors la notification "Contenu précédent retiré (violation règles)" s'affiche en toast discret Et la notification disparaît automatiquement après 5 secondes Et aucune popup modale n'interrompt ma conduite Et le contenu suivant démarre automatiquement après 2 secondes
Étant donné que le contenu "C123" a été supprimé Et que je passe au contenu suivant
Quand la notification s'affiche
Alors le message doit être informatif: "Contenu précédent retiré (violation règles)" Et le ton ne doit pas être alarmiste Et le message doit être bref et compréhensible Et aucun détail technique n'est affiché pendant la conduite
Étant donné que le contenu "C123" a été supprimé
Quand je consulte mon historique d'écoute
Alors le contenu "C123" n'apparaît plus dans mon historique Et je ne peux pas relancer la lecture de ce contenu Et l'historique affiche "[Contenu retiré]" à la place du titre
Étant donné que le contenu "C123" a été supprimé Et que j'ai un lien de partage "roadwave.fr/share/c/C123"
Quand je clique sur le lien de partage
Alors je reçois un message "Ce contenu a été retiré pour violation des règles de la communauté" Et je suis redirigé vers l'accueil de l'application Et aucune lecture n'est possible
Étant donné que j'ai écouté les contenus suivants:
| id | statut |
|---|---|
| C123 | supprimé |
| C456 | actif |
| C789 | supprimé |
Et que je suis actuellement sur le contenu "C456"
Quand j'appuie plusieurs fois sur "Précédent"
Alors je ne peux pas revenir aux contenus "C123" ou "C789" Et le système saute automatiquement les contenus supprimés Et je reviens au dernier contenu actif disponible avant "C456"
Étant donné que je suis à l'arrêt Et que le contenu "C123" a été supprimé pendant ma session
Quand j'ouvre les détails de la notification de suppression
Alors je peux voir les informations suivantes:
| information |
|---|
| Titre du contenu |
| Créateur |
| Raison de suppression |
| Date de suppression |
Et je peux signaler une erreur de modération si je pense qu'elle est injustifiée
Étant donné que j'ai écouté le contenu "C123" pendant 80 secondes (66%) Et que mes jauges d'intérêt ont été mises à jour pendant l'écoute
Quand le contenu est supprimé après mon écoute
Alors les modifications de mes jauges d'intérêt sont conservées Et l'écoute déjà effectuée reste comptabilisée Et seules les futures écoutes de ce contenu sont bloquées
19 scénarios
Contexte commun à tous les scénarios
Étant donné que je suis un utilisateur connecté Et que j'ai refusé ou désactivé l'accès à la géolocalisation
Étant donné que la géolocalisation est désactivée
Quand j'ouvre l'application
Alors les types de contenu suivants sont disponibles:
| type_contenu | disponible |
|---|---|
| Contenu national | oui |
| Contenu téléchargé (offline) | oui |
| Contenus "Neutre" géographiquement | oui |
| Contenu géolocalisé Ancré | non |
| Contenu géolocalisé Contextuel | non |
| Audio-guides | non |
| Notifications push géo-déclenchées | non |
Étant donné que c'est mon premier lancement de l'application Et que j'ai refusé l'accès à la géolocalisation
Quand l'application détecte que le GPS est désactivé
Alors une popup s'affiche avec le message: Et la popup contient les boutons suivants:
| bouton | action |
|---|---|
| Activer | Redirection vers paramètres OS |
| Continuer sans | Ferme popup et lance en mode dégradé |
Et une checkbox "Ne plus me demander" est disponible
Étant donné que j'ai déjà vu la popup de géolocalisation Et que j'ai coché "Ne plus me demander"
Quand je lance l'application avec le GPS désactivé
Alors la popup de géolocalisation ne s'affiche pas Et l'application démarre directement en mode dégradé Et le banner permanent de rappel s'affiche
Étant donné que la popup de géolocalisation est affichée
Quand je clique sur "Activer"
Alors je suis redirigé vers les paramètres de géolocalisation de mon OS Et sur iOS, j'arrive dans "Réglages > RoadWave > Localisation" Et sur Android, j'arrive dans "Paramètres > Applications > RoadWave > Autorisations > Position"
Étant donné que j'ai cliqué sur "Continuer sans" géolocalisation
Quand l'application s'affiche
Alors un bandeau s'affiche en haut de l'écran Et le bandeau contient le texte: "Mode limité : géolocalisation désactivée. [Activer]" Et le bandeau a un fond de couleur avertissement (jaune/orange) Et le bandeau n'est pas intrusif mais reste visible Et le bandeau reste affiché sur toutes les pages de l'application
Étant donné que le banner "Mode limité" est affiché
Quand je clique sur le lien "[Activer]" dans le banner
Alors je suis redirigé vers les paramètres de géolocalisation de mon OS
Étant donné que le banner "Mode limité" est affiché Et que je reviens dans l'application après avoir activé le GPS dans les paramètres
Quand l'application détecte que la géolocalisation est maintenant active
Alors le banner disparaît automatiquement Et l'application bascule en mode normal avec contenu géolocalisé Et un toast de confirmation s'affiche: "Géolocalisation activée"
Étant donné que la géolocalisation est désactivée Et que du contenu national existe (actualités Le Monde, podcasts génériques)
Quand je lance la lecture
Alors je peux écouter le contenu national sans restriction Et l'algorithme de recommandation se base uniquement sur:
| critère |
|---|
| Mes centres d'intérêt |
| Mon historique d'écoute |
| Popularité générale |
Et la proximité géographique n'est pas prise en compte
Étant donné que la géolocalisation est désactivée Et que j'ai téléchargé 30 contenus quand j'avais le GPS activé
Quand j'accède à mes contenus téléchargés
Alors je peux lire tous mes contenus téléchargés normalement Et les contenus géolocalisés téléchargés restent accessibles Et le filtre géographique n'est pas appliqué pour les contenus offline
Étant donné que la géolocalisation est désactivée Et qu'un créateur a publié du contenu avec la classification géographique "Neutre"
Quand je recherche du contenu
Alors les contenus "Neutre" sont inclus dans les résultats Et ils sont mélangés avec le contenu national Et l'algorithme les priorise selon mes centres d'intérêt
Étant donné que la géolocalisation est désactivée
Quand je recherche un audio-guide spécifique
Alors les audio-guides apparaissent dans les résultats de recherche Mais un badge "GPS requis" est affiché sur chaque audio-guide Et quand je clique sur un audio-guide, un message s'affiche: Et je peux choisir "Activer" ou "Annuler"
Étant donné que la géolocalisation est désactivée Et que je suis abonné à un créateur qui diffuse du contenu géolocalisé
Quand le créateur publie un nouveau contenu géolocalisé
Alors je ne reçois pas de notification push géo-déclenchée Mais je reçois une notification push standard (non géo-déclenchée) si le créateur publie du contenu national Et la notification précise: "Nouveau contenu national de [Créateur]"
Étant donné que la géolocalisation est désactivée
Quand le système génère mon feed de contenu
Alors aucun contenu "Ancré" ou "Contextuel" n'est inclus Et seuls les contenus "Neutre" et "National" sont proposés Et mon feed contient au minimum 20 contenus disponibles
Étant donné que la géolocalisation est désactivée
Quand j'utilise l'application
Alors je ne suis jamais bloqué par un écran "GPS requis" Et toutes les fonctionnalités non-géolocalisées restent accessibles:
| fonctionnalité |
|---|
| Écoute contenu national |
| Gestion profil |
| Abonnements créateurs |
| Recherche textuelle |
| Historique d'écoute |
| Paramètres |
| Mode offline |
Et je peux créer et publier du contenu national
Étant donné que j'ai coché "Ne plus me demander" pour la géolocalisation
Quand j'utilise l'application pendant plusieurs semaines
Alors la popup de demande GPS ne s'affiche plus jamais automatiquement Et seul le banner permanent reste affiché Et l'application ne force jamais l'activation du GPS
Étant donné que j'utilise l'application en mode dégradé depuis 1 semaine Et que je décide d'activer la géolocalisation
Quand l'application détecte que le GPS est maintenant actif
Alors le mode dégradé est désactivé automatiquement Et le banner "Mode limité" disparaît Et le contenu géolocalisé devient disponible immédiatement Et mon feed se rafraîchit avec du contenu local pertinent Et un toast de confirmation s'affiche: "Géolocalisation activée - Contenu local disponible"
Étant donné que la géolocalisation est désactivée
Quand j'essaie d'accéder à une fonctionnalité nécessitant le GPS (ex: audio-guide)
Alors une popup contextuelle s'affiche: Et je peux accepter ou refuser Et si j'accepte, je suis redirigé vers les paramètres OS Et si je refuse, je reste en mode dégradé sans message d'erreur répétitif
Étant donné que la géolocalisation est désactivée
Quand je navigue dans l'application
Alors le banner peut afficher occasionnellement: Et ce message incitatif change tous les 3 jours Et il reste non intrusif (pas de popup, juste le banner)
Étant donné que c'est ma première utilisation de RoadWave Et que j'ai refusé la géolocalisation
Quand l'onboarding se termine
Alors un écran explicatif s'affiche: Et je peux continuer avec un bouton "Compris"
17 scénarios (16 standards, 1 plan)
Contexte commun à tous les scénarios
Étant donné que je suis un utilisateur connecté Et que je suis en mode écoute Et qu'un contenu est en cours de lecture
Étant donné que je suis connecté en "
Quand le système initialise le buffer audio
Alors le buffer minimum est de
📊 Exemples de données:
| type_reseau | buffer_min | buffer_cible | buffer_max |
|---|---|---|---|
| WiFi | 5 | 30 | 120 |
| 4G | 10 | 45 | 120 |
| 5G | 10 | 45 | 120 |
| 3G | 30 | 90 | 300 |
Étant donné que je suis connecté en 4G Et que le buffer contient 45 secondes de contenu
Quand la latence réseau dépasse 500ms
Alors aucun message n'est affiché immédiatement Et la lecture continue normalement sur le buffer Et le système tente de continuer le téléchargement en arrière-plan
Étant donné que je suis connecté en 4G Et que la latence réseau dépasse 500ms depuis 10 secondes
Quand le système détecte la latence prolongée
Alors un toast discret s'affiche: "Connexion instable" Et le toast disparaît automatiquement après 3 secondes Et la lecture continue normalement
Étant donné que je suis connecté en WiFi Et que le buffer contient 30 secondes de contenu
Quand je perds totalement la connexion réseau
Alors la lecture continue sur le buffer disponible Et un toast s'affiche: "Hors ligne, lecture sur buffer (30s restantes)" Et un compte à rebours du temps de buffer restant est visible
Étant donné que je suis hors ligne Et que le buffer contient 30 secondes de contenu
Quand le contenu continue de jouer
Alors le compte à rebours diminue en temps réel Et le toast affiche "Hors ligne, lecture sur buffer (15s restantes)" après 15 secondes Et le toast affiche "Hors ligne, lecture sur buffer (5s restantes)" après 25 secondes
Étant donné que je suis hors ligne depuis 30 secondes Et que le buffer est complètement épuisé
Quand il n'y a plus de contenu audio à lire
Alors la lecture se met en pause automatiquement Et un overlay s'affiche: "Connexion perdue. Reconnexion en cours..." Et le système tente de se reconnecter automatiquement
Étant donné que la lecture est en pause suite à l'épuisement du buffer
Quand le système tente de se reconnecter
Alors une tentative de reconnexion est effectuée toutes les 5 secondes Et un maximum de 6 tentatives sont effectuées (30 secondes au total) Et l'overlay affiche "Tentative de reconnexion... (X/6)"
Étant donné que 6 tentatives de reconnexion ont échoué Et que cela fait 30 secondes que je suis déconnecté
Quand la 6ème tentative échoue
Alors une popup s'affiche: "Voulez-vous continuer avec vos contenus téléchargés ?" Et la popup contient deux boutons:
| bouton | action |
|---|---|
| Réessayer | Nouvelle série de 6 tentatives |
| Mode offline | Bascule sur contenus téléchargés |
Étant donné que la popup de mode offline est affichée Et que j'ai téléchargé 20 contenus dans ma zone géographique
Quand je clique sur "Mode offline"
Alors le système bascule sur les contenus téléchargés Et un nouveau contenu téléchargé démarre automatiquement Et un bandeau permanent indique "Mode hors ligne - Contenus téléchargés"
Étant donné que la popup de mode offline est affichée Et que je n'ai aucun contenu téléchargé
Quand je clique sur "Mode offline"
Alors un message s'affiche: "Aucun contenu téléchargé disponible" Et je suis invité à me connecter en WiFi pour télécharger du contenu Et le bouton "Réessayer" reste la seule option
Étant donné que la lecture est en pause depuis 15 secondes Et que j'étais à 02:35 du contenu en cours
Quand la connexion réseau est rétablie
Alors la lecture reprend automatiquement au point d'arrêt exact (02:35) Et un toast s'affiche: "Connexion rétablie" Et le toast disparaît après 3 secondes Et le buffer se remplit progressivement selon le type de réseau
Étant donné que j'étais connecté en WiFi Et que j'ai perdu la connexion
Quand je me reconnecte en 4G
Alors le système ajuste automatiquement les paramètres de buffer Et le buffer minimum passe de 5s à 10s Et le buffer cible passe de 30s à 45s Et la lecture reprend normalement
Étant donné que je conduis à 90 km/h sur autoroute Et que je suis connecté en 4G avec un buffer de 45 secondes
Quand j'entre dans un tunnel et perds le signal
Alors la lecture continue sur le buffer pendant 45 secondes maximum Et aucune notification n'est affichée pendant les 10 premières secondes Et un toast discret s'affiche après 10 secondes: "Connexion instable"
Étant donné que je suis dans un tunnel depuis 30 secondes Et qu'il reste 15 secondes de buffer
Quand je sors du tunnel et récupère le signal 4G
Alors la lecture continue sans interruption Et le buffer se remplit à nouveau Et un toast s'affiche: "Connexion rétablie"
Étant donné que je conduis et change de cellule mobile toutes les 5-10 minutes Et que le buffer contient 45 secondes de contenu
Quand un handoff de cellule se produit
Alors la lecture continue sans interruption grâce au buffer Et la connexion à la nouvelle cellule se fait de manière transparente Et aucune notification n'est affichée si le handoff réussit en moins de 5 secondes
Étant donné que je suis connecté en WiFi Et que j'ai activé le téléchargement automatique
Quand le système détecte que je suis à l'arrêt en WiFi
Alors le système me propose de télécharger du contenu pour mon trajet Et je peux sélectionner une zone géographique à télécharger Et le téléchargement se fait en arrière-plan
Étant donné que je perds la connexion réseau
Quand l'événement de perte est détecté
Alors le système enregistre les métriques suivantes:
| métrique |
|---|
| Type de réseau avant perte |
| Durée de la coupure |
| Buffer disponible |
| Position GPS approximative |
| Heure de la journée |
Et ces métriques sont anonymisées et envoyées en batch lors de la prochaine connexion WiFi Et les données servent à améliorer les paramètres de buffer
Cette documentation est générée automatiquement à partir des fichiers Gherkin (.feature).
| Métrique | Valeur |
|---|---|
| Fonctionnalités | 83 |
| Scénarios | 2112 |
| Domaines métier | 18 |
4 fonctionnalités • 100 scénarios
7 fonctionnalités • 238 scénarios
| Fonctionnalité | Scénarios |
|---|---|
| Authentification à deux facteurs (2FA) | 16 |
| Classification des contenus par âge | 13 |
| Connexion utilisateur | 11 |
| Gestion des sessions et tokens | 13 |
| Inscription utilisateur | 15 |
| Récupération de compte | 14 |
| Vérification d'email | 10 |
7 fonctionnalités • 92 scénarios
| Fonctionnalité | Scénarios |
|---|---|
| Modification et suppression de contenu | 30 |
| Métadonnées et publication de contenu | 34 |
| Upload et encodage de contenu audio | 29 |
| Validation des 3 premiers contenus | 30 |
4 fonctionnalités • 123 scénarios
4 fonctionnalités • 56 scénarios
| Fonctionnalité | Scénarios |
|---|---|
| Jauge initiale et cold start | 15 |
| Pas de dégradation temporelle des jauges | 16 |
| Évolution des jauges d'intérêt | 21 |
3 fonctionnalités • 52 scénarios
| Fonctionnalité | Scénarios |
|---|---|
| Synchronisation actions offline | 45 |
| Téléchargement de contenus offline | 49 |
| Validité et renouvellement contenus offline | 38 |
3 fonctionnalités • 132 scénarios
| Fonctionnalité | Scénarios |
|---|---|
| Modération préventive | 22 |
| Sanctions et notifications de modération | 27 |
| Signalement de contenu inapproprié | 23 |
| Traitement des signalements par l'IA et les modérateurs | 25 |
4 fonctionnalités • 97 scénarios
7 fonctionnalités • 233 scénarios
6 fonctionnalités • 135 scénarios
| Fonctionnalité | Scénarios |
|---|---|
| Partage de contenu | 22 |
1 fonctionnalités • 22 scénarios
| Fonctionnalité | Scénarios |
|---|---|
| Avantages Premium | 37 |
| Gestion abonnement Premium | 41 |
| Multi-devices et détection simultanée | 30 |
| Offre et tarification Premium | 31 |
4 fonctionnalités • 139 scénarios
| Fonctionnalité | Scénarios |
|---|---|
| Profil créateur | 31 |
1 fonctionnalités • 31 scénarios
6 fonctionnalités • 179 scénarios
| Fonctionnalité | Scénarios |
|---|---|
| Architecture technique radio live | 24 |
| Arrêt du live | 19 |
| Comportement auditeur pendant un live | 27 |
| Démarrage d'un live | 20 |
4 fonctionnalités • 90 scénarios
| Fonctionnalité | Scénarios |
|---|---|
| Recherche de contenu | 55 |
1 fonctionnalités • 55 scénarios
9 fonctionnalités • 180 scénarios
8 fonctionnalités • 158 scénarios
En tant que système de recommandation Je veux que les jauges n'évoluent que par les actions utilisateur Afin d'avoir un comportement prévisible et fiable
16 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et qu'un utilisateur est connecté
Étant donné que ma jauge "Économie" est à 80% Et que je n'écoute aucun contenu pendant 30 jours
Quand je me reconnecte après 30 jours
Alors ma jauge "Économie" est toujours à 80% Et aucune dégradation temporelle n'a été appliquée
Étant donné que mes jauges sont:
| catégorie | niveau |
|---|---|
| Automobile | 75% |
| Voyage | 60% |
| Musique | 45% |
Et que je pars en vacances pendant 6 mois sans utiliser l'app
Quand je me reconnecte après 6 mois
Alors mes jauges sont exactement les mêmes:
| catégorie | niveau |
|---|---|
| Automobile | 75% |
| Voyage | 60% |
| Musique | 45% |
Étant donné que j'aimais "Économie" il y a 1 an (jauge 80%) Et que depuis, je skip tous les contenus "Économie" Et que j'ai skippé 50 contenus "Économie" en 1 an
Alors ma jauge "Économie" descend naturellement via les skips Et atteint environ 55% (80% - 50 × 0.5% = 55%) Et la dégradation vient des actions, pas du temps
Étant donné que le système vérifie les jauges quotidiennement
Quand un utilisateur n'a pas d'activité depuis 90 jours
Alors aucun job de dégradation n'est exécuté Et les jauges restent inchangées Et aucune ressource CPU n'est consommée pour la dégradation
Étant donné que ma jauge "Sport" était à 70% Et que je n'utilise pas l'app pendant 1 an
Quand je reviens et demande des recommandations
Alors mes recommandations reflètent toujours mes goûts d'avant Et je reçois du contenu "Sport" prioritaire Et le comportement est cohérent et prévisible
Étant donné que je veux repartir de zéro
Quand je vais dans les paramètres Et que je clique sur "Réinitialiser mes centres d'intérêt" Et que je confirme l'action
Alors toutes mes jauges reviennent à 50% Et je vois le message "Vos centres d'intérêt ont été réinitialisés"
Étant donné que je suis dans les paramètres
Quand je clique sur "Réinitialiser mes centres d'intérêt"
Alors je vois un message de confirmation:
| titre | Êtes-vous sûr ? |
|---|---|
| message | Cette action remettra toutes vos jauges à 50% |
| actions | Confirmer / Annuler |
Étant donné que j'ai cliqué sur "Réinitialiser mes centres d'intérêt" Et que la confirmation est affichée
Quand je clique sur "Annuler"
Alors mes jauges ne sont pas modifiées Et je reviens aux paramètres
Étant donné que j'utilisais RoadWave pour mes trajets professionnels Et que mes jauges reflétaient "Économie" (85%) et "Technologie" (75%) Et que je change de vie et deviens musicien
Quand je réinitialise mes centres d'intérêt
Alors je peux repartir avec toutes les jauges à 50% Et découvrir du contenu "Musique" et "Culture" sans biais
Étant donné que je n'ai pas utilisé l'app depuis 1 an
Quand je me reconnecte
Alors aucune suggestion de réinitialisation n'est affichée Et mes jauges sont conservées telles quelles Et je garde le contrôle total
Étant donné que j'ai écouté 500 contenus
Quand je réinitialise mes centres d'intérêt
Alors mes jauges reviennent à 50% Mais mon historique d'écoute est conservé Et je peux toujours consulter mes anciens contenus écoutés
Étant donné que j'ai réinitialisé mes jauges à 50%
Quand j'écoute 5 contenus "Voyage" à >80%
Alors ma jauge "Voyage" monte à 60% (50% + 5 × 2%) Et l'algorithme recommence à apprendre mes nouvelles préférences
Étant donné qu'un utilisateur aime "Cryptomonnaie" depuis 2 ans Et que sa jauge est à 90%
Quand 2 ans s'écoulent sans dégradation temporelle
Alors sa jauge reste à 90% Et le système ne fait pas d'"oubli" artificiel
Étant donné qu'aucune dégradation temporelle n'existe
Quand le système calcule les jauges
Alors aucun calcul de date n'est nécessaire Et aucun batch nocturne ne tourne Et aucun bug de fuseau horaire ne peut survenir Et le coût CPU est minimal
Étant donné qu'un utilisateur consulte sa jauge "Sport" à 65%
Quand il se demande pourquoi elle est à 65%
Alors il peut retracer ses actions:
| action | impact |
|---|---|
| 10 likes automatiques | +10% |
| 3 abonnements Sport | +15% |
| 5 skips de contenu non-Sport | 0% |
Et il comprend que c'est le reflet exact de ses actions Et il n'y a pas de mystère ou automatisme caché
Étant donné que je consulte mes centres d'intérêt
Quand je vois mes jauges
Alors je vois:
| information | affiché |
|---|---|
| Niveau actuel | ✅ 75% |
| Évolution depuis début | ✅ +25% |
| Dernière mise à jour | ❌ |
Et aucune date n'est affichée car non pertinente Et seules les actions comptent
En tant que système de recommandation Je veux faire évoluer les jauges d'intérêt selon les actions utilisateur Afin d'affiner les recommandations personnalisées
21 scénarios (20 standards, 1 plan)
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et qu'un utilisateur est connecté
Étant donné qu'un contenu de 5 minutes est tagué "Automobile" Et que ma jauge "Automobile" est à 45%
Quand j'écoute le contenu pendant 4 minutes 30 secondes (90%)
Alors je reçois un like automatique renforcé Et ma jauge "Automobile" augmente de 2% Et ma jauge "Automobile" est maintenant à 47%
Étant donné qu'un contenu de 10 minutes est tagué "Voyage" Et que ma jauge "Voyage" est à 60%
Quand j'écoute le contenu pendant exactement 8 minutes (80%)
Alors je reçois un like automatique renforcé Et ma jauge "Voyage" augmente de 2% Et ma jauge "Voyage" est maintenant à 62%
Étant donné qu'un contenu de 5 minutes est tagué "Automobile" Et que ma jauge "Automobile" est à 45%
Quand j'écoute le contenu pendant 2 minutes 30 secondes (50%)
Alors je reçois un like automatique standard Et ma jauge "Automobile" augmente de 1% Et ma jauge "Automobile" est maintenant à 46%
Étant donné qu'un contenu de 10 minutes est tagué "Musique" Et que ma jauge "Musique" est à 40%
Quand j'écoute le contenu pendant exactement 3 minutes (30%)
Alors je reçois un like automatique standard Et ma jauge "Musique" augmente de 1%
Étant donné qu'un contenu de 10 minutes est tagué "Sport" Et que ma jauge "Sport" est à 55%
Quand j'écoute le contenu pendant 7 minutes 54 secondes (79%)
Alors je reçois un like automatique standard Et ma jauge "Sport" augmente de 1% Et ma jauge "Sport" est maintenant à 56%
Étant donné qu'un contenu est tagué "Économie" Et que ma jauge "Économie" est à 70%
Quand j'écoute le contenu partiellement Et que je clique manuellement sur le bouton "Like"
Alors ma jauge "Économie" augmente de 2% Et ma jauge "Économie" est maintenant à 72%
Étant donné qu'un contenu de 5 minutes est tagué "Automobile" Et que ma jauge "Automobile" est à 45%
Quand j'écoute le contenu pendant 2 minutes 30 secondes (50%)
Alors je reçois un like automatique standard (+1%)
Quand je clique ensuite sur le bouton "Like"
Alors ma jauge augmente encore de 2% (like manuel) Et ma jauge "Automobile" a augmenté de 3% au total Et ma jauge "Automobile" est maintenant à 48%
Étant donné qu'un créateur publie des contenus tagués "Automobile" et "Technologie" Et que mes jauges sont:
| catégorie | niveau |
|---|---|
| Automobile | 50% |
| Technologie | 45% |
Quand je m'abonne à ce créateur
Alors ma jauge "Automobile" augmente de 5% Et ma jauge "Technologie" augmente de 5% Et mes nouvelles jauges sont:
| catégorie | niveau |
|---|---|
| Automobile | 55% |
| Technologie | 50% |
Étant donné qu'un contenu est tagué "Économie" Et que ma jauge "Économie" est à 45%
Quand je skip le contenu après 5 secondes
Alors ma jauge "Économie" diminue de 0.5% Et ma jauge "Économie" est maintenant à 44.5%
Étant donné qu'un contenu est tagué "Politique" Et que ma jauge "Politique" est à 50%
Quand je skip le contenu après exactement 10 secondes
Alors ma jauge "Politique" ne change pas Et reste à 50%
Étant donné qu'un contenu de 10 minutes est tagué "Musique" Et que ma jauge "Musique" est à 60%
Quand j'écoute pendant 3 minutes (30%) Et que je skip ensuite
Alors ma jauge "Musique" ne diminue pas (signal neutre) Et ma jauge reste à 60% (plus le +1% de like auto si applicable)
Étant donné qu'un contenu est tagué "Automobile" et "Voyage" Et que mes jauges sont:
| catégorie | niveau |
|---|---|
| Automobile | 45% |
| Voyage | 60% |
Quand j'écoute le contenu à 90%
Alors les deux jauges augmentent de 2% Et mes nouvelles jauges sont:
| catégorie | niveau |
|---|---|
| Automobile | 47% |
| Voyage | 62% |
Étant donné qu'un contenu est tagué "Sport", "Santé" et "Technologie" Et que mes jauges sont à 50% pour chaque catégorie
Quand je skip rapidement après 5 secondes
Alors les 3 jauges diminuent de 0.5% Et toutes passent à 49.5%
Étant donné que ma jauge "Cryptomonnaie" est à 99% Et qu'un contenu tagué "Cryptomonnaie" est disponible
Quand j'écoute le contenu à 95% (like auto renforcé +2%)
Alors ma jauge "Cryptomonnaie" passe à 100% (maximum) Et ne dépasse pas 100%
Étant donné que ma jauge "Politique" est à 0.3% Et qu'un contenu tagué "Politique" est disponible
Quand je skip rapidement après 3 secondes (-0.5%)
Alors ma jauge "Politique" passe à 0% (minimum) Et ne devient pas négative
Étant donné que ma jauge "Voyage" est à 50%
Quand j'écoute un contenu "Voyage" à 85%
Alors la jauge est mise à jour immédiatement (pas de batch) Et passe à 52%
Quand je demande mes recommandations dans la seconde suivante
Alors l'algorithme utilise déjà la valeur 52%
Étant donné qu'un contenu de 10 minutes est tagué "Culture" Et que ma jauge "Culture" est à 60%
Quand j'écoute pendant 2 minutes (20%)
Alors je ne reçois pas de like automatique
Quand je clique sur le bouton "Like"
Alors ma jauge "Culture" augmente de 2% uniquement Et ma jauge "Culture" est maintenant à 62%
Étant donné que j'ai liké manuellement un contenu "Sport" Et que ma jauge "Sport" est passée de 55% à 57% (+2%)
Quand je clique sur "Unlike"
Alors ma jauge "Sport" diminue de 2% Et ma jauge "Sport" revient à 55%
Étant donné que j'ai écouté un contenu "Musique" à 90% Et que j'ai reçu un like automatique renforcé (+2%) Et que ma jauge "Musique" est à 52%
Quand j'essaie de faire "Unlike"
Alors l'action n'est pas disponible Et ma jauge reste à 52%
Étant donné que je suis un créateur
Quand je publie un contenu
Alors je dois sélectionner 1 à 3 tags Et ces tags sont fixés après publication Et impacteront les jauges de tous les auditeurs
Étant donné qu'un contenu de 10 minutes est tagué "Voyage" Et que ma jauge "Voyage" est à 50%
Quand j'écoute pendant
Alors ma jauge évolue de
📊 Exemples de données:
| duree | pourcentage | impact | nouveau_niveau |
|---|---|---|---|
| 1 min | 10% | 0% | 50% |
| 3 min | 30% | +1% | 51% |
| 5 min | 50% | +1% | 51% |
| 7.9 min | 79% | +1% | 51% |
| 8 min | 80% | +2% | 52% |
| 9.5 min | 95% | +2% | 52% |
| 5 sec | <1% | -0.5% | 49.5% |
En tant que nouvel utilisateur Je veux que mes jauges d'intérêt démarrent de manière neutre Afin de découvrir du contenu sans biais initial
15 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible
Quand je m'inscris sur RoadWave
Alors toutes mes jauges d'intérêt sont initialisées à 50% Et je ne dois pas remplir de questionnaire Et l'inscription est ultra-rapide
Étant donné que je suis un nouvel utilisateur
Quand je consulte mes centres d'intérêt
Alors je vois les catégories suivantes à 50%:
| catégorie |
|---|
| Automobile |
| Voyage |
| Famille |
| Amour |
| Musique |
| Économie |
| Cryptomonnaie |
| Politique |
| Culture générale |
| Sport |
| Technologie |
| Santé |
Étant donné que je viens de m'inscrire Et que toutes mes jauges sont à 50%
Quand j'écoute mon premier podcast "Automobile" à 90%
Alors ma jauge "Automobile" monte à 52% (+2%) Et toutes les autres jauges restent à 50%
Étant donné que je viens de m'inscrire Et que toutes mes jauges sont à 50%
Quand je skip rapidement un contenu "Économie"
Alors ma jauge "Économie" descend à 49.5% (-0.5%) Et toutes les autres jauges restent à 50%
Étant donné que je suis un nouvel utilisateur Et que j'ai écouté:
| contenu | tags | completion |
|---|---|---|
| Contenu 1 | Automobile | 90% |
| Contenu 2 | Automobile, Sport | 85% |
| Contenu 3 | Voyage | 75% |
| Contenu 4 | Économie | skip 5s |
| Contenu 5 | Automobile | 95% |
| Contenu 6 | Sport | 80% |
| Contenu 7 | Politique | skip 8s |
| Contenu 8 | Voyage | 88% |
| Contenu 9 | Automobile | 92% |
| Contenu 10 | Technologie | 40% |
Alors mes jauges reflètent mes préférences:
| catégorie | tendance |
|---|---|
| Automobile | Forte hausse (>55%) |
| Voyage | Hausse modérée (~53%) |
| Sport | Hausse modérée (~53%) |
| Économie | Baisse légère (~49.5%) |
| Politique | Baisse légère (~49.5%) |
| Technologie | Neutre (~51%) |
Quand je termine l'inscription
Alors aucun questionnaire de centres d'intérêt n'est affiché Et je peux commencer à écouter immédiatement Et l'algorithme apprend naturellement
Étant donné que toutes mes jauges sont à 50%
Quand l'algorithme calcule les recommandations
Alors tous les types de contenus ont une chance égale Et aucun biais initial n'est appliqué Et la géolocalisation prime sur les intérêts
Étant donné que j'ai écouté 3 contenus
Quand je termine ma 3ème écoute
Alors je vois une notification in-app optionnelle:
| titre | Améliorez vos recommandations |
|---|---|
| message | Sélectionnez vos centres d'intérêt |
| actions | Configurer maintenant / Plus tard |
Étant donné que le questionnaire optionnel est affiché
Quand je sélectionne les centres d'intérêt suivants:
| catégorie |
|---|
| Automobile |
| Voyage |
| Sport |
Alors les jauges sélectionnées passent à 70% Et les jauges non sélectionnées passent à 30% Et je vois le message "Vos préférences ont été enregistrées"
Étant donné que le questionnaire optionnel est affiché
Quand je clique sur "Plus tard"
Alors toutes mes jauges conservent 50% Et l'algorithme continue d'apprendre naturellement Et je ne suis plus sollicité
Étant donné deux nouveaux utilisateurs A et B
Quand les deux s'inscrivent au même moment
Alors leurs jauges sont identiques (toutes à 50%) Et leurs recommandations initiales sont identiques (basées sur géo uniquement)
Étant donné qu'un nouvel utilisateur s'inscrit Et qu'il existe 1000 contenus de catégories variées dans sa zone
Quand l'algorithme génère les premières recommandations
Alors tous les contenus ont une pondération intérêts identique (50%) Et seuls la géolocalisation et l'engagement différencient les contenus Et aucun créateur n'a d'avantage initial
Étant donné que RoadWave ajoute une nouvelle catégorie "Gastronomie"
Quand je consulte mes centres d'intérêt
Alors je vois la nouvelle catégorie "Gastronomie" à 50% Et je peux commencer à l'explorer normalement
Étant donné que je suis un utilisateur avec historique
Quand je consulte mes centres d'intérêt dans les paramètres
Alors je vois mes jauges actuelles:
| catégorie | niveau | evolution |
|---|---|---|
| Automobile | 67% | +17% |
| Voyage | 82% | +32% |
| Économie | 34% | -16% |
| Sport | 50% | 0% |
Et je comprends mes préférences actuelles
Étant donné que je veux m'inscrire rapidement
Quand je remplis les 4 champs obligatoires Et que je clique sur "S'inscrire"
Alors mon compte est créé immédiatement Et je peux commencer à écouter dans les 30 secondes Et aucune configuration supplémentaire n'est requise
En tant qu'utilisateur Je veux que mes actions offline soient synchronisées quand je me reconnecte Afin de ne perdre aucune interaction même sans connexion
45 scénarios
Contexte commun à tous les scénarios
Étant donné que j'utilise l'application RoadWave
Étant donné que je n'ai aucune connexion Internet
Quand je like un contenu téléchargé
Alors l'action est enregistrée localement dans SQLite: Et l'UI affiche immédiatement le like (optimistic update)
Étant donné que je n'ai aucune connexion Internet Et que j'avais liké un contenu
Quand je retire mon like
Alors l'action est enregistrée localement: Et l'UI retire immédiatement le like
Étant donné que je n'ai aucune connexion Internet
Quand je m'abonne à un créateur
Alors l'action est enregistrée localement: Et l'UI affiche immédiatement "Abonné ✓"
Étant donné que je n'ai aucune connexion Internet Et que j'étais abonné à un créateur
Quand je me désabonne
Alors l'action est enregistrée localement: Et l'UI affiche "S'abonner"
Étant donné que je n'ai aucune connexion Internet
Quand je signale un contenu pour "Contenu inapproprié"
Alors l'action est enregistrée localement: Et je vois "Signalement enregistré. Sera envoyé à la reconnexion."
Étant donné que je n'ai aucune connexion Internet Et que j'écoute un audio-guide multi-séquences
Quand je termine la séquence 3/10
Alors la progression est enregistrée localement: Et ma progression est sauvegardée
Étant donné que je n'ai aucune connexion Internet pendant 2 jours
Quand j'effectue plusieurs actions:
| action | cible |
|---|---|
| like | contenu A |
| like | contenu B |
| subscribe | créateur X |
| unlike | contenu C |
| report | contenu D |
Alors les 5 actions sont stockées dans pending_actions Et elles seront synchronisées dans l'ordre à la reconnexion
Étant donné que j'étais en mode offline
Quand l'app détecte une reconnexion Internet
Alors le processus de synchronisation démarre automatiquement Et je vois une notification "Synchronisation en cours..."
Étant donné que la synchronisation démarre
Quand l'app récupère les actions en attente
Alors une requête SQL est exécutée: Et toutes les actions sont récupérées dans l'ordre chronologique
Étant donné que 15 actions sont en attente
Quand le batch est envoyé au backend
Alors une requête POST /sync/actions est faite: Et toutes les actions sont groupées en une seule requête
Étant donné que le backend reçoit le batch d'actions
Quand il traite chaque action
Alors pour chaque action:
| étape | détail |
|---|---|
| Validation | Vérifier user_id, content_id valides |
| Vérification existence | Contenu/créateur existe toujours ? |
| Application action | INSERT/UPDATE/DELETE en base |
| Mise à jour compteurs | Likes, abonnés, etc. |
| Impact sur algorithme | Mise à jour jauges si nécessaire |
Étant donné que le backend a traité toutes les actions avec succès
Quand la confirmation est reçue par l'app
Alors les actions sont supprimées de la queue locale: Et la table pending_actions est vidée
Étant donné que 15 actions ont été synchronisées
Quand la synchronisation se termine
Alors je vois un toast:
Étant donné que j'ai seulement 2 actions en attente
Quand la synchronisation se termine
Alors aucun toast n'est affiché (sync silencieuse) Et l'expérience reste fluide Mais je peux voir le détail dans l'historique des syncs
Étant donné que la synchronisation échoue (erreur réseau)
Quand l'échec est détecté
Alors un retry automatique est programmé dans 30 secondes Et les actions restent dans pending_actions
Étant donné que 3 tentatives de synchronisation ont échoué
Quand la 3ème tentative échoue
Alors je reçois une notification:
Étant donné que la synchronisation échoue plusieurs fois
Quand les tentatives continuent d'échouer
Alors les actions restent dans pending_actions Et aucune action n'est perdue Et elles seront envoyées dès que la connexion sera stable
Étant donné qu'une action est en attente depuis 7 jours
Quand le système détecte cette ancienneté
Alors l'action est automatiquement supprimée de la queue Et je vois "1 action trop ancienne supprimée (>7 jours)" Et cela évite une queue infinie
Étant donné qu'un utilisateur ne se connecte jamais pendant 2 semaines
Quand ses actions ont >7 jours
Alors elles sont purgées automatiquement Et évite une queue qui grandit indéfiniment
Étant donné que la synchronisation a échoué
Quand je clique sur "Réessayer maintenant"
Alors une nouvelle tentative de synchronisation est lancée immédiatement Et si elle réussit, les actions sont synchronisées
Étant donné que j'ai liké un contenu offline Mais que le contenu a été supprimé entre temps
Quand le backend traite la synchronisation
Alors il retourne:
Étant donné que le backend retourne deleted_content_ids: [123, 456]
Quand l'app traite la réponse
Alors elle supprime les fichiers locaux des contenus 123 et 456 Et libère l'espace disque Et les actions associées sont retirées de la queue
Étant donné que j'écoute le contenu 123 en offline Et que la sync détecte que le contenu a été supprimé
Quand la lecture actuelle se termine
Alors l'app attend 2 secondes Et passe automatiquement au contenu suivant Et le fichier du contenu 123 est supprimé en arrière-plan
Étant donné que 2 contenus téléchargés ont été supprimés
Quand la synchronisation se termine
Alors je vois un toast:
Étant donné que j'ai téléchargé un contenu qui est ensuite modéré
Quand la synchronisation détecte la modération
Alors le contenu est immédiatement supprimé du device Et je ne peux plus l'écouter Et cela garantit la conformité même offline
Étant donné que les actions offline sont unilatérales (likes, abonnements)
Quand elles sont synchronisées
Alors il n'y a pas de conflit de version possible Et pas de merge complexe nécessaire
Étant donné que toutes les actions fonctionnent offline
Quand l'utilisateur interagit sans connexion
Alors l'expérience est identique au mode online Et l'utilisateur n'est pas bloqué Et peut utiliser l'app normalement
Étant donné que 15 actions sont en attente
Quand elles sont synchronisées en batch
Alors 1 seule requête HTTP est envoyée (vs 15 si individuelles) Et cela économise la bande passante et la batterie Et réduit la charge serveur
Étant donné qu'un contenu illégal est modéré pendant qu'un user est offline
Quand le user se reconnecte
Alors le contenu est immédiatement supprimé de son device Et cela garantit que les contenus illégaux disparaissent même offline
Étant donné que j'accède à "Paramètres > Synchronisation"
Quand je consulte l'historique
Alors je vois:
| date | actions sync | statut |
|---|---|---|
| 15/06/2025 14:30:00 | 15 | Réussi ✅ |
| 14/06/2025 09:15:00 | 7 | Réussi ✅ |
| 13/06/2025 18:45:00 | 3 | Échec ❌ |
Étant donné que je clique sur une ligne de l'historique
Quand le détail s'affiche
Alors je vois:
Étant donné que j'ai 12 actions en attente de synchronisation
Quand j'accède à l'onglet Profil
Alors je vois un badge "12" sur l'icône de synchronisation Et je sais qu'il y a des actions en attente
Étant donné que je veux forcer une synchronisation immédiate
Quand je vais dans "Paramètres > Synchronisation" Et que je clique sur "Synchroniser maintenant"
Alors la synchronisation démarre immédiatement Et toutes les actions en attente sont envoyées
Étant donné que j'accède à mes statistiques
Quand je consulte la section Synchronisation
Alors je vois:
| métrique | valeur |
|---|---|
| Synchronisations depuis début | 87 |
| Actions synchronisées total | 1,234 |
| Taux de succès | 94% |
| Dernière sync | Il y a 2h |
Étant donné qu'un admin consulte les métriques de synchronisation
Quand il accède au dashboard
Alors il voit:
| métrique | valeur |
|---|---|
| Synchronisations/jour | 45,678 |
| Actions synchronisées/jour | 234,567 |
| Taux succès sync | 96.5% |
| Temps moyen traitement batch | 0.8s |
| Actions en attente (global) | 12,345 |
Étant donné que le taux d'échec sync dépasse 10%
Quand le système détecte cette anomalie
Alors une alerte est envoyée:
Étant donné que j'ai 20 actions en attente
Quand la synchronisation démarre
Alors le traitement prend <2 secondes Et je ne remarque aucun ralentissement de l'app
Étant donné que je n'ai pas synchronisé pendant 1 semaine Et que j'ai 100 actions en attente
Quand la synchronisation démarre
Alors le batch de 100 actions est traité en <5 secondes Et toutes les actions sont synchronisées avec succès
Étant donné que 10 000 utilisateurs se reconnectent simultanément
Quand chacun envoie un batch de 20 actions
Alors le serveur traite 200 000 actions Et grâce au traitement asynchrone (queue Redis), le temps de réponse reste <3s Et aucun timeout n'est constaté
Étant donné que la table pending_actions stocke des centaines d'actions
Quand des requêtes sont exécutées
Alors la table est indexée sur created_at Et les requêtes SELECT et DELETE sont instantanées (<10ms) Et l'expérience utilisateur reste fluide
Étant donné que la table pending_actions grossit avec le temps
Quand les actions sont synchronisées et supprimées
Alors la table est automatiquement optimisée (VACUUM sur SQLite) Et l'espace disque est libéré Et les performances restent optimales
Étant donné que j'ai liké un contenu offline Et que la sync échoue et retry
Quand le backend reçoit 2 fois le même like
Alors il applique l'idempotence (1 seul like enregistré) Et le compteur de likes n'est pas faussé
Étant donné que j'ai liké puis unliké un contenu offline
Quand les 2 actions sont synchronisées
Alors le backend applique les 2 actions dans l'ordre Et le résultat final est "pas de like" (état correct)
Étant donné que je me suis abonné puis désabonné d'un créateur offline
Quand les 2 actions sont synchronisées
Alors le backend applique les 2 actions dans l'ordre Et le résultat final est "pas abonné" Et les jauges évoluent correctement (+5% puis -5% = 0% net)
Étant donné que je me suis abonné à un créateur offline Mais que le créateur a supprimé son compte entre temps
Quand la sync traite l'abonnement
Alors le backend retourne "creator_deleted" Et l'action est ignorée silencieusement Et aucune erreur n'est affichée à l'utilisateur
En tant qu'utilisateur Je veux télécharger des contenus pour les écouter sans connexion Afin de profiter de RoadWave même dans les zones sans réseau
49 scénarios
Contexte commun à tous les scénarios
Étant donné que je suis connecté à l'application RoadWave
Étant donné que je suis à Paris (position GPS détectée)
Quand je sélectionne "Télécharger > Autour de moi"
Alors l'app recherche tous les contenus géolocalisés dans un rayon de 50 km Et je vois une liste de contenus de Paris et banlieue proche Et l'estimation affiche "~150 contenus disponibles"
Étant donné que je suis à Lyon (position GPS détectée)
Quand je sélectionne "Télécharger > Ma ville"
Alors l'app détecte automatiquement "Lyon" comme ville Et recherche tous les contenus géolocalisés "Lyon" Et je vois uniquement les contenus de la ville de Lyon (pas banlieue)
Étant donné que je veux télécharger des contenus pour un département
Quand je sélectionne "Télécharger > Mon département"
Alors je vois une liste de tous les départements français:
| département |
|---|
| 01 - Ain |
| 02 - Aisne |
| 75 - Paris |
| 69 - Rhône |
| ... |
Et je peux choisir un département
Étant donné que je sélectionne "75 - Paris" dans la liste des départements
Quand la sélection est confirmée
Alors l'app recherche tous les contenus géolocalisés "Paris" Et je vois "~234 contenus disponibles pour Paris"
Étant donné que je veux télécharger des contenus pour une région
Quand je sélectionne "Télécharger > Ma région"
Alors je vois une liste de toutes les régions françaises:
| région |
|---|
| Auvergne-Rhône-Alpes |
| Bretagne |
| Île-de-France |
| Nouvelle-Aquitaine |
| Occitanie |
| ... |
Et je peux choisir une région
Étant donné que je sélectionne "Bretagne" dans la liste des régions
Quand la sélection est confirmée
Alors l'app recherche tous les contenus géolocalisés des départements bretons:
| département |
|---|
| Côtes-d'Armor (22) |
| Finistère (29) |
| Ille-et-Vilaine (35) |
| Morbihan (56) |
Et je vois "~487 contenus disponibles pour Bretagne"
Étant donné que je veux télécharger des contenus pour une ville spécifique
Quand je tape "Marseille" dans la barre de recherche
Alors l'app propose des suggestions:
| suggestion |
|---|
| Marseille (13) |
| Marseille-en-Beauvaisis |
Et je peux sélectionner "Marseille (13)"
Étant donné que je tape "Ly" dans la barre de recherche
Quand l'autocomplétion s'active
Alors je vois des suggestions:
| suggestion |
|---|
| Lyon (69) |
| Lys-lez-Lannoy |
Et je peux affiner ma recherche
Étant donné que je suis un utilisateur gratuit Et que j'ai déjà téléchargé 45 contenus
Quand j'accède à la page Téléchargements
Alors je vois "45 / 50 contenus téléchargés" Et je peux télécharger 5 contenus supplémentaires maximum
Étant donné que je suis gratuit et j'ai déjà 50 contenus téléchargés
Quand j'essaie de télécharger un 51ème contenu
Alors le téléchargement est refusé Et je vois le message:
Étant donné que je suis un utilisateur Premium Et que j'ai déjà téléchargé 245 contenus
Quand j'accède à la page Téléchargements
Alors je vois "245 contenus (3.2 GB)" Et aucune limite n'est affichée Et je peux télécharger autant de contenus que je veux
Étant donné que je suis Premium Et que mon device a 500 MB d'espace disque disponible
Quand j'essaie de télécharger 100 contenus (2 GB)
Alors le téléchargement échoue après ~50 contenus (500 MB) Et je vois "Espace disque insuffisant. Libérez de l'espace pour continuer."
Étant donné que je suis gratuit avec 50 contenus téléchargés Et que la durée moyenne d'un contenu est 5 minutes
Quand je calcule le temps d'écoute disponible
Alors 50 contenus × 5 min = 250 minutes = 4h10 d'écoute Et cela suffit pour un trajet quotidien ou road trip court
Étant donné que je suis Premium avec 300 contenus téléchargés Et que la durée moyenne est 5 minutes
Quand je calcule le temps d'écoute disponible
Alors 300 contenus × 5 min = 1500 minutes = 25h d'écoute Et cela suffit pour un road trip de plusieurs jours
Étant donné que je suis connecté en WiFi
Quand je clique sur "Télécharger 20 contenus"
Alors le téléchargement démarre immédiatement Et aucune popup de confirmation n'apparaît
Étant donné que je suis connecté en 4G (pas de WiFi)
Quand je clique sur "Télécharger 20 contenus"
Alors une popup apparaît:
Étant donné que je veux télécharger 20 contenus Et que la durée moyenne est 5 minutes Et que la qualité Standard est 48 kbps Opus
Quand l'estimation est calculée
Alors consommation = 20 contenus × 5 min × 48 kbps / 8 = 72 MB Et ce montant est affiché dans la popup
Étant donné que je vois la popup de confirmation données mobiles
Quand je clique sur "Continuer quand même"
Alors le téléchargement démarre immédiatement via 4G Et la consommation data est comptabilisée sur mon forfait mobile
Étant donné que je vois la popup de confirmation données mobiles
Quand je clique sur "Attendre WiFi"
Alors les téléchargements sont mis en file d'attente Et ils démarreront automatiquement quand le WiFi sera détecté
Étant donné que j'ai mis 20 contenus en file d'attente (attente WiFi)
Quand l'app détecte une connexion WiFi
Alors les téléchargements démarrent automatiquement Et je reçois une notification "Téléchargements en cours via WiFi"
Étant donné que je configure mes téléchargements
Quand j'accède aux paramètres de qualité
Alors la qualité "Standard (48 kbps - ~20 MB/h)" est sélectionnée par défaut Et elle est disponible pour tous (gratuit + Premium)
Étant donné que j'ai peu d'espace disque disponible
Quand je sélectionne qualité "Basse (24 kbps - ~10 MB/h)"
Alors mes prochains téléchargements seront en 24 kbps Et l'espace utilisé sera divisé par 2 par rapport à Standard Et cette option est disponible pour gratuit + Premium
Étant donné que je suis un utilisateur gratuit
Quand je consulte les options de qualité
Alors l'option "Haute (64 kbps - ~30 MB/h)" est grisée Et je vois "👑 Premium uniquement" Et je ne peux pas la sélectionner
Étant donné que je suis un utilisateur Premium
Quand je consulte les options de qualité
Alors l'option "Haute (64 kbps - ~30 MB/h)" est disponible Et je peux la sélectionner pour mes téléchargements Et la qualité audio sera excellente (meilleure restitution voix et ambiances)
Étant donné que je veux télécharger 50 contenus de 5 min chacun
Quand je compare les qualités
Alors les tailles totales sont:
| qualité | bitrate | taille totale |
|---|---|---|
| Basse | 24 kbps | ~250 MB |
| Standard | 48 kbps | ~500 MB |
| Haute | 64 kbps | ~650 MB |
Étant donné que le contenu RoadWave est principalement de la voix
Quand la qualité Standard (48 kbps Opus) est utilisée
Alors la qualité est très correcte pour la voix Et équivalente à la radio FM Et le compromis qualité/taille est optimal
Étant donné qu'un utilisateur gratuit veut la meilleure qualité
Quand il voit que Haute est réservée Premium
Alors cela l'incite à passer Premium pour 4.99€/mois Et c'est un avantage tangible supplémentaire de Premium
Étant donné que j'ai déjà téléchargé 30 contenus en qualité Standard
Quand je change la qualité vers Haute (si Premium)
Alors les 30 contenus existants restent en Standard Et seuls les nouveaux téléchargements seront en Haute Et je peux manuellement re-télécharger les 30 contenus pour les avoir en Haute
Étant donné que je consulte la page d'un contenu
Quand je clique sur l'icône de téléchargement 📥
Alors le téléchargement démarre Et une barre de progression apparaît Et l'icône devient ✅ quand terminé
Étant donné que je consulte une liste de contenus pour "Paris"
Quand je sélectionne 15 contenus manuellement Et que je clique sur "Télécharger la sélection"
Alors les 15 contenus sont téléchargés en parallèle (max 3 simultanés) Et une notification affiche "15 contenus téléchargés"
Étant donné que je sélectionne "Autour de moi" (Paris)
Quand je clique sur "Télécharger les 50 meilleurs contenus"
Alors l'algorithme sélectionne automatiquement les 50 contenus les mieux notés/récents Et les télécharge tous Et je n'ai pas besoin de choisir manuellement
Étant donné que je télécharge 20 contenus
Quand les téléchargements sont en cours
Alors je vois une barre de progression globale:
Étant donné que je lance le téléchargement de 30 contenus
Quand je ferme l'app ou passe à une autre activité
Alors les téléchargements continuent en arrière-plan Et je reçois une notification quand tous sont terminés
Étant donné que je télécharge 20 contenus
Quand je clique sur "Pause"
Alors les téléchargements en cours se terminent Et les téléchargements en attente sont mis en pause Et je peux cliquer sur "Reprendre" plus tard
Étant donné que je télécharge 20 contenus
Quand je clique sur "Annuler"
Alors tous les téléchargements sont arrêtés Et les fichiers partiels sont supprimés Et l'espace disque est libéré
Étant donné que je télécharge un contenu Mais que la connexion Internet coupe au milieu
Quand la connexion revient
Alors le téléchargement reprend automatiquement où il s'était arrêté Et aucune perte de progression n'a lieu
Étant donné qu'un téléchargement échoue 3 fois consécutives
Quand l'échec est détecté
Alors le contenu est marqué "Échec" Et je vois une notification "3 contenus n'ont pas pu être téléchargés" Et je peux retry manuellement en cliquant sur "Réessayer"
Étant donné que j'ai téléchargé 45 contenus
Quand j'accède à "Téléchargements"
Alors je vois la liste complète de mes 45 contenus Et pour chaque contenu: titre, créateur, durée, taille, date téléchargement
Étant donné que je consulte ma liste de téléchargements
Quand je clique sur "Trier par"
Alors je peux trier par:
| critère | ordre |
|---|---|
| Date téléchargement | Plus récent / Plus ancien |
| Titre | A-Z / Z-A |
| Créateur | A-Z / Z-A |
| Durée | Plus long / Plus court |
| Taille | Plus gros / Plus petit |
Étant donné que j'ai 200 contenus téléchargés
Quand je tape "Tesla" dans la barre de recherche
Alors seuls les contenus contenant "Tesla" s'affichent Et je peux rapidement trouver un contenu spécifique
Étant donné que je veux supprimer un contenu téléchargé
Quand je swipe left (iOS) ou long press (Android) sur le contenu Et que je clique sur "Supprimer"
Alors le fichier est supprimé du device Et l'espace disque est libéré Et le compteur est décrémenté (ex: 45/50 → 44/50)
Étant donné que je veux supprimer plusieurs contenus
Quand je sélectionne 10 contenus Et que je clique sur "Supprimer la sélection"
Alors les 10 fichiers sont supprimés Et ~100 MB d'espace disque sont libérés Et une notification confirme "10 contenus supprimés"
Étant donné que j'ai 45 contenus téléchargés
Quand je clique sur "Supprimer tout" Et que je confirme l'action
Alors tous les 45 contenus sont supprimés Et l'espace disque total est libéré (~450 MB) Et le compteur repasse à 0/50
Étant donné que j'ai téléchargé 45 contenus
Quand j'accède à la page Téléchargements
Alors je vois l'espace disque utilisé:
Étant donné que j'accède à mes statistiques
Quand je consulte la section Téléchargements
Alors je vois:
| métrique | valeur |
|---|---|
| Contenus actuellement téléchargés | 45 |
| Espace disque utilisé | 478 MB |
| Contenus téléchargés depuis début | 287 |
| Total data téléchargée | 3.2 GB |
| Téléchargements via WiFi | 92% |
| Téléchargements via mobile | 8% |
Étant donné que je n'ai aucune connexion Internet (mode avion) Et que j'ai des contenus téléchargés
Quand je lance un contenu téléchargé
Alors la lecture démarre normalement depuis le fichier local Et aucune erreur de connexion n'apparaît
Étant donné que j'ai téléchargé certains contenus
Quand je consulte une liste de contenus
Alors les contenus téléchargés ont un badge ✅ "Offline" Et je sais immédiatement lesquels sont disponibles sans connexion
Étant donné que je veux voir uniquement mes contenus offline
Quand j'active le filtre "Téléchargés uniquement"
Alors seuls les contenus téléchargés s'affichent Et je peux facilement naviguer dans mon catalogue offline
Étant donné que j'ai téléchargé 45 contenus
Quand j'accède à "Téléchargements"
Alors je peux lancer une playlist aléatoire de mes 45 contenus Et profiter d'une écoute continue offline
En tant qu'utilisateur Je veux que mes contenus téléchargés restent valides un certain temps Afin de garantir la légalité et la fraîcheur du contenu
38 scénarios
Contexte commun à tous les scénarios
Étant donné que je suis connecté à l'application RoadWave Et que j'ai des contenus téléchargés
Étant donné que je télécharge un contenu le 1er juin 2025
Quand le téléchargement est terminé
Alors le contenu est valide jusqu'au 1er juillet 2025 (30 jours) Et la date d'expiration est stockée en local
Étant donné que j'ai téléchargé un contenu il y a 20 jours
Quand je consulte les détails du contenu
Alors je vois "Expire dans 10 jours" Et je sais combien de temps il reste avant expiration
Étant donné que Spotify, YouTube Music et Deezer utilisent 30 jours
Quand RoadWave fixe également 30 jours
Alors c'est le standard accepté par les utilisateurs Et il n'y a pas de confusion avec les autres plateformes
Étant donné qu'un utilisateur ne se connecte jamais
Quand ses contenus expirent après 30 jours
Alors il est obligé de se reconnecter pour les renouveler Et le système peut vérifier:
| vérification |
|---|
| Abonnement Premium toujours actif |
| Contenus non modérés/supprimés |
| Métadonnées à jour |
Étant donné qu'un contenu a été modéré après téléchargement
Quand le contenu expire après 30 jours maximum
Alors le contenu illégal est automatiquement supprimé Et ne reste pas indéfiniment sur le device
Étant donné que j'ai des contenus téléchargés il y a 26 jours
Quand l'app détecte une connexion WiFi
Alors une requête GET /offline/contents/refresh est envoyée Et le backend vérifie chaque contenu
Étant donné qu'un contenu téléchargé en Premium est à renouveler
Quand le backend vérifie le statut Et que l'abonnement Premium est toujours actif
Alors la validité est renouvelée à 30 jours supplémentaires
Étant donné qu'un contenu Premium téléchargé est à renouveler
Quand le backend vérifie le statut Et que l'abonnement Premium a expiré
Alors le contenu n'est pas renouvelé Et il sera supprimé à l'expiration (J-0) Et l'utilisateur voit "Contenu Premium expiré (abonnement inactif)"
Étant donné qu'un contenu téléchargé est à renouveler
Quand le backend vérifie le statut Et que le contenu a été modéré ou supprimé entre temps
Alors le contenu n'est pas renouvelé Et sera supprimé immédiatement du device Et l'utilisateur voit "1 contenu retiré (violation règles)"
Étant donné qu'un contenu téléchargé est renouvelé
Quand le backend traite le renouvellement
Alors les métadonnées sont mises à jour:
| métadonnée | mise à jour si changée |
|---|---|
| Titre | ✅ |
| Nom créateur | ✅ |
| Description | ✅ |
| Tags | ✅ |
| Statut Premium | ✅ |
Et l'utilisateur voit les infos à jour
Étant donné qu'un contenu est renouvelé
Quand le fichier audio local est intact
Alors seules les métadonnées sont mises à jour Et le fichier audio n'est pas re-téléchargé Et cela économise la bande passante
Étant donné qu'un contenu est renouvelé
Quand le fichier audio local est corrompu (checksum invalide)
Alors le fichier audio est re-téléchargé entièrement Et le nouveau fichier remplace le corrompu
Étant donné que je me connecte en WiFi tous les jours
Quand mes contenus atteignent 25-30 jours
Alors ils sont automatiquement renouvelés en arrière-plan Et je ne vois aucune notification (processus transparent) Et mes contenus restent valides indéfiniment
Étant donné que j'ai 30 contenus à renouveler
Quand le renouvellement automatique se déclenche
Alors une requête batch est envoyée: Et le backend traite les 30 contenus en une seule requête Et cela économise les requêtes HTTP
Étant donné que 30 contenus sont à renouveler
Quand la requête batch est traitée
Alors le backend répond en <2 secondes Et les métadonnées sont mises à jour localement Et l'utilisateur ne remarque aucun ralentissement
Étant donné que j'ai 15 contenus qui expirent dans 3 jours
Quand le système vérifie les expirations
Alors je reçois une notification: Et je peux agir avant l'expiration
Étant donné que je me connecte en WiFi tous les jours Et que mes contenus sont automatiquement renouvelés
Quand le système vérifie les expirations
Alors aucune notification J-3 n'est envoyée
Étant donné que j'ai 20 contenus dont 15 renouvelés et 5 non renouvelés
Quand le J-3 arrive pour les 5 non renouvelés
Alors je reçois "5 contenus expirent dans 3 jours" Et seuls les contenus à risque sont mentionnés
Étant donné que je reçois la notification J-3
Quand je clique sur la notification
Alors l'app s'ouvre sur la page Téléchargements Et je vois les contenus qui vont expirer en rouge Et je peux me connecter en WiFi pour les renouveler
Étant donné qu'un contenu n'a pas été renouvelé
Quand le jour d'expiration arrive (J-0)
Alors le fichier est automatiquement supprimé du device Et l'espace disque est libéré Et le compteur est décrémenté (ex: 45/50 → 44/50)
Étant donné que 15 contenus viennent d'expirer
Quand l'utilisateur ouvre l'app
Alors il voit un toast:
Étant donné que 15 contenus ont expiré
Quand je consulte l'historique des suppressions
Alors je vois la liste des 15 contenus supprimés:
| titre | créateur | date expiration |
|---|---|---|
| Mon épisode préféré | JeanDupont | 15 juin 2025 |
| Road trip Bretagne | MarieLambert | 15 juin 2025 |
| ... | ... | ... |
Et je peux les re-télécharger si je veux
Étant donné qu'un contenu a expiré et été supprimé
Quand je retrouve ce contenu dans l'app
Alors le badge ✅ "Offline" n'est plus affiché Et je peux le re-télécharger normalement Et la validité repart à 30 jours
Étant donné que je télécharge 50 contenus le 1er juin Mais que je ne me connecte jamais en WiFi pendant 30 jours
Quand le 1er juillet arrive
Alors tous les 50 contenus expirent Et sont automatiquement supprimés Et je n'ai plus aucun contenu offline
Étant donné que je télécharge 50 contenus avant de partir en zone sans réseau Et que je reste 45 jours sans connexion
Quand les contenus expirent après 30 jours
Alors ils sont supprimés même si je ne peux pas me connecter Et je perds l'accès à mes contenus offline
Étant donné que je prépare un road trip de 60 jours
Quand je consulte la FAQ
Alors je vois la recommandation:
Étant donné que je suis Premium et j'ai téléchargé 200 contenus
Quand mon abonnement Premium expire Et que je repasse en gratuit
Alors au prochain renouvellement, seulement 50 contenus sont conservés Et les 150 autres sont supprimés (limite gratuit) Et je vois "Limite gratuit (50 contenus) appliquée. 150 contenus supprimés."
Étant donné que je repasse en gratuit avec 200 contenus téléchargés
Quand le système applique la limite de 50
Alors les 50 contenus les plus récemment écoutés sont conservés Et les 150 autres sont supprimés Et cela maximise les chances de garder les contenus que j'aime
Étant donné que j'ai téléchargé 20 contenus Premium exclusifs
Quand mon abonnement Premium expire
Alors les 20 contenus Premium sont immédiatement supprimés Et je vois "20 contenus Premium supprimés (abonnement expiré)"
Étant donné que j'ai 45 contenus téléchargés
Quand je consulte la page Téléchargements
Alors je vois pour chaque contenu:
| contenu | temps restant |
|---|---|
| Mon épisode (récent) | Expire dans 28 jours |
| Road trip (ancien) | Expire dans 3 jours |
Et je sais lesquels sont prioritaires pour renouvellement
Étant donné que j'ai 45 contenus avec différentes dates d'expiration
Quand je trie par "Expiration"
Alors les contenus qui expirent le plus tôt apparaissent en premier Et je peux voir rapidement lesquels nécessitent une reconnexion urgente
Étant donné qu'un contenu expire dans 2 jours
Quand je consulte la liste des téléchargements
Alors le contenu a un badge rouge "⚠️ Expire bientôt" Et il est visuellement mis en avant
Étant donné que j'accède à mes statistiques
Quand je consulte la section Téléchargements
Alors je vois:
| métrique | valeur |
|---|---|
| Contenus actuels | 45 |
| Contenus expirés depuis début | 87 |
| Contenus renouvelés (auto) | 234 |
| Taux renouvellement automatique | 73% |
Étant donné qu'un admin consulte les métriques offline
Quand il accède au dashboard
Alors il voit:
| métrique | valeur |
|---|---|
| Contenus téléchargés actifs | 1,234,567 |
| Expirations ce mois | 45,678 |
| Taux expiration | 3.7% |
| Renouvellements automatiques/mois | 234,567 |
Étant donné que le taux d'expiration mensuel dépasse 10%
Quand le système détecte cette anomalie
Alors une alerte est envoyée:
Étant donné que je n'ai pas connecté l'app en WiFi depuis 20 jours Et que j'ai 45 contenus téléchargés
Quand le système détecte cette inactivité WiFi
Alors je reçois un email:
Étant donné que 10 000 utilisateurs se connectent en WiFi simultanément
Quand chacun demande le renouvellement de 50 contenus
Alors le serveur traite 500 000 vérifications Et grâce au cache Redis et index PostgreSQL, le temps de réponse reste <3s Et les serveurs gèrent la charge sans problème
Étant donné qu'un contenu est renouvelé
Quand l'opération se termine
Alors un log est enregistré:
| timestamp | user_id | content_id | action | résultat |
|---|---|---|---|---|
| 2025-06-15 14:30:00 | abc123 | xyz789 | renew | success (+30d) |
| 2025-06-15 14:30:01 | abc123 | def456 | renew | failed (deleted) |
Et ces logs aident à débugger les problèmes
22 scénarios
Contexte commun à tous les scénarios
Étant donné que le système de modération préventive est actif
Étant donné que je viens de créer un compte créateur Et que je n'ai jamais publié de contenu
Quand j'examine mon statut de créateur
Alors mon compte est marqué comme "Nouveau créateur" Et mes 3 premiers contenus devront être validés manuellement Et je suis informé de ce processus lors de l'onboarding
Étant donné que je suis un nouveau créateur Et que je n'ai publié aucun contenu auparavant
Quand je publie mon premier contenu
Alors le contenu entre en file d'attente de validation manuelle Et le statut du contenu est "En attente de validation" Et le contenu n'est pas diffusé sur la plateforme Et je reçois une notification:
Étant donné que j'ai publié mon premier contenu Et que le contenu est en attente de validation
Quand un modérateur examine mon contenu
Alors le modérateur utilise la transcription automatique Whisper Et le modérateur vérifie:
| critère | conforme |
|---|---|
| Respect des règles communauté | oui |
| Pas de contenu inapproprié | oui |
| Qualité audio acceptable | oui |
| Métadonnées cohérentes | oui |
| Tags appropriés | oui |
Et si tout est conforme, le contenu est validé
Étant donné que j'ai publié mon premier contenu lundi à 10:00
Quand le contenu entre en file de validation
Alors le contenu est validé avant mercredi 10:00 (48h jours ouvrés) Et dans la plupart des cas, la validation est effectuée sous 24h Et je reçois une notification dès que le contenu est validé
Étant donné que mon premier contenu a été validé par un modérateur
Quand la validation est approuvée
Alors je reçois une notification: Et le statut du contenu passe à "Publié" Et le contenu devient visible pour tous les utilisateurs Et il entre dans l'algorithme de recommandation
Étant donné que mon premier contenu viole les règles de la communauté
Quand le modérateur examine le contenu
Alors le contenu est refusé Et je reçois une notification détaillée: Et le contenu reste en statut "Refusé" Et je peux modifier et republier
Étant donné que je suis un nouveau créateur
Quand je publie mes contenus
Alors les contenus suivants nécessitent une validation manuelle:
| contenu | validation manuelle |
|---|---|
| 1er | oui |
| 2ème | oui |
| 3ème | oui |
| 4ème | non (auto) |
Et après 3 contenus validés, mes futurs contenus sont publiés automatiquement
Étant donné que mes 3 premiers contenus ont été validés avec succès
Quand je publie mon 4ème contenu
Alors le contenu est publié automatiquement Et aucune validation manuelle n'est requise Et le statut passe directement à "Publié" Et je reçois une notification:
Étant donné que je suis un créateur établi
Quand le système évalue mon historique
Alors un score de confiance est calculé basé sur:
| critère | poids |
|---|---|
| Nombre de contenus publiés | 20% |
| Strikes reçus | 40% |
| Signalements infondés | 20% |
| Ancienneté du compte | 10% |
| Taux d'engagement positif | 10% |
Et le score évolue dynamiquement
Étant donné que je suis un créateur Et que j'ai 0 strike depuis 6 mois Et que tous mes contenus précédents ont été conformes
Quand mon score de confiance est calculé
Alors je suis classé comme "Créateur fiable" Et tous mes nouveaux contenus sont publiés automatiquement Et aucune validation manuelle n'est nécessaire Et je bénéficie d'une publication instantanée
Étant donné que je suis un créateur Et que j'ai reçu 2 strikes récents (< 3 mois)
Quand mon score de confiance est recalculé
Alors je suis classé comme "Créateur suspect" Et tous mes nouveaux contenus nécessitent une validation manuelle Et chaque contenu est examiné avant publication Et je suis notifié de ce changement de statut:
Étant donné que j'étais un "Créateur suspect" Et que je publie 10 contenus conformes sur 6 mois Et que je ne reçois aucun nouveau strike
Quand le système réévalue mon score de confiance
Alors je passe en "Créateur fiable" Et la publication automatique est rétablie Et je reçois une notification de réhabilitation:
Étant donné qu'un annonceur soumet une publicité audio
Quand la publicité est créée
Alors elle entre automatiquement en file de validation manuelle Et aucune publicité n'est diffusée sans validation préalable Et cela est obligatoire pour des raisons de responsabilité juridique
Étant donné qu'une publicité est en attente de validation
Quand un modérateur senior examine la publicité
Alors le modérateur vérifie:
| critère | conforme |
|---|---|
| Transcription automatique Whisper | effectuée |
| Contenu conforme aux règles | oui |
| Pas de fausse publicité / arnaque | oui |
| Respect du ciblage géographique | oui |
| Durée conforme (10-60s) | oui |
| Volume audio acceptable (pas trop fort) | oui |
| Métadonnées correctes | oui |
Et si tout est conforme, la publicité est validée
Étant donné qu'un annonceur soumet une publicité lundi à 10:00
Quand la publicité entre en file de validation
Alors la publicité est validée avant mercredi 10:00 (48h jours ouvrés) Et l'annonceur est notifié dès la validation Et la campagne publicitaire peut alors démarrer
Étant donné qu'une publicité contient des éléments non conformes
Quand le modérateur examine la publicité
Alors la publicité est refusée Et l'annonceur reçoit une notification détaillée: Et l'annonceur peut modifier et resoumettre la publicité Et aucun remboursement n'est effectué pour une publicité refusée
Étant donné que la modération préventive est active
Quand on analyse l'efficacité du système
Alors 80% des contenus inappropriés sont détectés avant publication Et cela réduit le nombre de signalements de 70% Et les ressources de modération sont optimisées Et la qualité de la plateforme est préservée dès le début
Étant donné que tous les nouveaux créateurs sont vérifiés
Quand on analyse la qualité globale des contenus
Alors le taux de contenus inappropriés est <1% Et les utilisateurs font confiance à la plateforme Et la réputation de RoadWave est préservée Et l'expérience utilisateur est optimale
Étant donné que je suis un nouveau créateur
Quand je consulte la page d'aide "Validation des contenus"
Alors j'apprends que: Et le processus est clair et transparent
Étant donné que mes 3 premiers contenus ont été validés avec succès
Quand je consulte mon profil créateur
Alors un badge discret "✓ Créateur vérifié" s'affiche Et ce badge rassure les auditeurs sur la qualité de mes contenus Et il améliore ma crédibilité sur la plateforme
Étant donné que la modération préventive est en place
Quand on évalue les bénéfices
Alors les avantages suivants sont constatés:
| bénéfice |
|---|
| Prévention meilleure que réaction |
| Économie de ressources de modération (×3-5) |
| Qualité de la plateforme préservée dès le début |
| Confiance des utilisateurs renforcée |
| Moins de contenus inappropriés signalés |
| Réputation de la plateforme protégée |
Et l'investissement dans la prévention est rentable
Étant donné que 100 nouveaux créateurs publient 3 contenus chacun Et que 50 publicités sont soumises par mois
Quand on calcule le coût de modération préventive
Alors le coût en temps modérateur est:
| type | nombre | temps/contenu | total |
|---|---|---|---|
| Nouveaux créateurs | 300 | 5 min | 25h |
| Publicités | 50 | 10 min | 8.3h |
Et le coût total est d'environ 33h de modération/mois Et c'est largement compensé par la réduction des signalements réactifs
27 scénarios
Contexte commun à tous les scénarios
Étant donné que je suis un créateur de contenu Et que j'ai publié un contenu
Étant donné que mon contenu a été modéré
Quand la sanction est appliquée
Alors je reçois une notification sur 3 canaux:
| canal | timing | contenu |
|---|---|---|
| Push | Immédiat | "Votre contenu a été modéré" |
| In-app | Au prochain lancement | Popup détaillée avec bouton "Voir détails" |
| Email | Dans l'heure | Notification complète avec lien d'appel |
Et chaque canal contient un lien vers les détails complets
Étant donné que mon contenu vient d'être modéré
Quand la sanction est appliquée
Alors je reçois une notification push immédiate Et le message est court: "⚠️ Votre contenu a été modéré" Et je peux cliquer pour voir les détails Et la notification utilise Firebase Cloud Messaging (Android) ou APNs (iOS) Et le coût est de 0€
Étant donné que mon contenu a été modéré
Quand j'ouvre l'application
Alors une popup détaillée s'affiche automatiquement Et la popup contient:
| élément | description |
|---|---|
| Titre du contenu | "Mon podcast #42" |
| Icône d'avertissement | ⚠️ |
| Catégorie violée | 🚫 Haine & violence |
| Sanction | Strike 2/4 - Suspension 7 jours |
| Bouton "Voir détails" | Redirige vers page détaillée |
| Bouton "Compris" | Ferme la popup |
Et je ne peux pas fermer la popup sans l'avoir vue
Étant donné que mon contenu a été modéré à 14:00
Quand la sanction est appliquée
Alors je reçois un email avant 15:00 (dans l'heure) Et l'objet de l'email est "Modération de votre contenu \"[Titre du contenu]\"" Et l'email contient toutes les informations détaillées Et le coût est d'environ 0.001€ par email (Brevo, Resend)
Étant donné que mon contenu "Mon podcast #42" a été modéré
Quand je reçois l'email de notification
Alors l'email contient la structure suivante:
Étant donné que je clique sur "Voir détails" dans la notification
Quand la page détaillée s'affiche
Alors je vois les 6 éléments obligatoires:
| élément | contenu |
|---|---|
| 1. Catégorie violée | 🚫 Haine & violence (Article 3.2 CGU) |
| 2. Raison détaillée | Explication claire et non juridique |
| 3. Extrait audio | Timestamp exact: 3:42-4:15 |
| 4. Transcription | Texte problématique surligné en rouge |
| 5. Gravité | Strike actuel + conséquences (Strike 2/4, 7j susp) |
| 6. Recours | Lien formulaire d'appel + délai 7j |
Étant donné que la page détaillée de la sanction est affichée
Quand je consulte l'extrait audio concerné
Alors le timestamp exact est affiché: "3:42-4:15" Et je peux écouter uniquement cette portion de l'audio Et un player audio intégré permet l'écoute du passage Et la transcription correspondante est affichée en dessous Et les mots/phrases problématiques sont surlignés en rouge
Étant donné que la sanction fait référence à l'Article 3.2 des CGU
Quand je clique sur "Article 3.2"
Alors je suis redirigé vers la section correspondante des CGU Et la section "Haine & violence" est mise en évidence Et je peux lire exactement ce qui est interdit Et cela m'aide à comprendre mon erreur
Étant donné que c'est mon 2ème strike
Quand je consulte les détails de la sanction
Alors je vois clairement "Strike 2/4" Et les conséquences sont explicitées: Et je comprends l'escalade des sanctions
Étant donné que j'ai reçu une notification de modération
Quand je clique sur "Contester cette décision"
Alors je suis redirigé vers le formulaire d'appel Et le formulaire est pré-rempli avec les informations de la sanction Et je peux commencer à rédiger mon appel
Étant donné que j'ai reçu une sanction il y a 2 jours
Quand j'ouvre "Profil créateur > Mes sanctions"
Alors je vois la liste de mes sanctions Et chaque sanction a un bouton "Faire appel" (si délai <7j) Et je peux accéder au formulaire d'appel
Étant donné que j'ouvre le formulaire d'appel
Quand le formulaire s'affiche
Alors je vois les champs suivants:
| champ | type | obligatoire | description |
|---|---|---|---|
| Sanction contestée | Pré-rempli (readonly) | oui | "Strike 2 - Podcast #42" |
| Raison de l'appel | Texte (50-1000 car) | oui | Explication courte de la contestation |
| Arguments détaillés | Zone texte enrichie | oui | Arguments complets |
| Preuves | Upload fichiers | non | Max 5 fichiers, 10 MB total |
Et tous les champs obligatoires sont marqués d'un astérisque
Étant donné que je remplis le formulaire d'appel
Quand je clique sur "Soumettre l'appel"
Alors le système valide les champs obligatoires Et si un champ obligatoire est vide, une erreur s'affiche Et si la raison fait moins de 50 caractères, une erreur s'affiche Et si tout est valide, l'appel est soumis
Étant donné que j'ai soumis un appel valide
Quand l'appel est enregistré
Alors un numéro de ticket unique est généré: "#MOD-2026-00142" Et un email de confirmation est envoyé: Et le statut de l'appel est "En cours d'examen" Et je peux suivre le statut dans "Mes sanctions"
Étant donné que j'ai reçu une sanction le 2026-01-15
Quand j'essaie de faire appel le 2026-01-25 (10 jours plus tard)
Alors le formulaire d'appel est désactivé Et un message s'affiche: Et je ne peux plus contester la sanction
Étant donné que j'ai reçu une sanction il y a 3 jours
Quand je consulte "Mes sanctions"
Alors le bouton "Faire appel" est actif Et un compteur indique "4 jours restants pour faire appel" Et je peux cliquer pour soumettre un appel
Étant donné que j'ai soumis un appel standard le lundi à 10:00
Quand l'appel est en cours de traitement
Alors un modérateur senior est assigné Et l'appel doit être traité avant jeudi 10:00 (72h - 3 jours ouvrés) Et je reçois une réponse dans ce délai
Étant donné que j'ai soumis un appel complexe Et que le traitement nécessite plus de 72h
Quand 3 jours se sont écoulés
Alors je reçois un email de notification intermédiaire: Et l'appel est traité sous 5 jours ouvrés au total Et un modérateur senior + admin modération examinent le cas
Étant donné que j'ai reçu une suspension longue ou un ban Et que je soumets un appel
Quand l'appel est classé en priorité CRITIQUE
Alors l'admin modération traite l'appel sous 24h Et je reçois une réponse rapide Et le cas est examiné en priorité absolue
Étant donné que mon appel est accepté
Quand je reçois la réponse finale
Alors l'email contient:
| élément | contenu |
|---|---|
| Décision | Annulation de la sanction |
| Justification | Explication de pourquoi l'appel est accepté |
| Actions | Strike retiré, suspension annulée, contenu rétabli |
| Définitif | "Cette décision est définitive" |
Et le strike est retiré de mon compte Et le contenu est rétabli sur la plateforme Et je peux continuer normalement
Étant donné que mon appel est rejeté
Quand je reçois la réponse finale
Alors l'email contient:
| élément | contenu |
|---|---|
| Décision | Maintien de la sanction |
| Justification | Explication de pourquoi l'appel est rejeté |
| Actions | Sanction maintenue, strike conservé |
| Définitif | "Cette décision est définitive" |
Et la sanction reste active Et je ne peux pas faire de second appel Et je dois respecter la suspension
Étant donné que mon appel est partiellement accepté
Quand je reçois la réponse finale
Alors la décision est "Réduction de sanction" Et l'email explique: Et le strike est réduit Et la suspension est raccourcie Et je suis notifié de la nouvelle date de fin
Étant donné que j'ai soumis un appel
Quand je consulte "Mes sanctions"
Alors je vois le statut actuel de l'appel:
| statut | badge | couleur |
|---|---|---|
| En cours d'examen | En cours 🔍 | orange |
| Appel accepté | Accepté ✓ | vert |
| Appel rejeté | Rejeté ✗ | rouge |
| Sanction réduite | Partiellement accepté | bleu |
Et une notification badge m'alerte quand le statut change
Étant donné que je suis un créateur
Quand j'ouvre "Profil créateur > Mes sanctions"
Alors je vois la liste complète de mes sanctions passées:
| colonne | description |
|---|---|
| Date | 15/01/2026 |
| Contenu | "Mon podcast #42" |
| Catégorie | 🚫 Haine & violence |
| Sanction | Strike 2 - Suspension 7j |
| Statut | Active / Terminée / Annulée |
| Appel | Aucun / Accepté / Rejeté |
Et les sanctions sont triées par date décroissante
Étant donné que le système de sanction est en place
Quand un audit DSA est effectué
Alors chaque sanction contient:
| élément DSA | présent |
|---|---|
| Référence précise à la règle violée | oui |
| Explication claire et compréhensible | oui |
| Preuve (extrait + transcription) | oui |
| Possibilité de recours (appel) | oui |
| Délai de recours clairement indiqué | oui |
| Réponse motivée au recours | oui |
Et le système est conforme au Digital Services Act
Étant donné que mon premier appel a été rejeté
Quand j'essaie de faire un second appel
Alors le bouton "Faire appel" est désactivé Et un message s'affiche: "Cette décision est définitive. Aucun second appel n'est possible." Et je ne peux plus contester la sanction Et je dois respecter la décision finale
Étant donné que 100 sanctions sont appliquées en un mois
Quand on calcule le coût des notifications
Alors le coût total est d'environ 0.10€:
| canal | coût unitaire | coût pour 100 |
|---|---|---|
| Email | 0.001€ | 0.10€ |
| Push | 0€ | 0€ |
| In-app | 0€ | 0€ |
Et le coût est négligeable même à grande échelle
23 scénarios (22 standards, 1 plan)
Contexte commun à tous les scénarios
Étant donné que je suis un utilisateur connecté Et que je suis en train d'écouter un contenu
Étant donné que j'écoute un contenu inapproprié
Quand j'ouvre le menu du contenu Et que je clique sur "Signaler"
Alors un formulaire de signalement s'affiche Et le formulaire contient une liste déroulante "Catégorie du problème" Et le formulaire contient un champ texte "Commentaire (optionnel)" Et le formulaire contient un bouton "Envoyer le signalement"
Étant donné que le formulaire de signalement est affiché
Quand je clique sur la liste déroulante "Catégorie du problème"
Alors je vois les 7 catégories suivantes:
| icône | catégorie | description |
|---|---|---|
| 🚫 | Haine & violence | Incitation à la haine, discrimination, menaces |
| 🔞 | Contenu sexuel | Pornographie, contenu explicite |
| ⚖️ | Illégalité | Terrorisme, apologie de crimes |
| 🎵 | Droits d'auteur | Musique/contenu protégé non autorisé |
| 📧 | Spam | Publicité non sollicitée, répétition |
| ❌ | Fausse information | Désinformation sur santé, sécurité routière |
| 🔧 | Autre | Champ texte obligatoire si sélectionné |
Et chaque catégorie a une description claire
Étant donné que le formulaire de signalement est affiché
Quand je sélectionne la catégorie "🚫 Haine & violence"
Alors la catégorie est sélectionnée Et la description "Incitation à la haine, discrimination, menaces" s'affiche Et je peux passer au champ commentaire
Étant donné que le formulaire de signalement est affiché
Quand je sélectionne la catégorie "🔧 Autre"
Alors le champ "Commentaire" devient obligatoire Et un message s'affiche: "Veuillez décrire le problème (obligatoire)" Et le placeholder change en "Décrivez le problème rencontré" Et je ne peux pas envoyer le signalement sans commentaire
Étant donné que le formulaire de signalement est affiché Et que j'ai sélectionné une catégorie autre que "Autre"
Quand je consulte le champ "Commentaire"
Alors le champ est optionnel (pas d'astérisque rouge) Et le placeholder indique "Décrivez le problème (optionnel mais recommandé)" Et la limite de caractères est de 500 Et un compteur affiche "0/500"
Étant donné que j'ai sélectionné la catégorie "📧 Spam" Et que je n'ai pas rempli le champ commentaire
Quand je clique sur "Envoyer le signalement"
Alors le signalement est envoyé avec succès Et aucune erreur de validation ne s'affiche Et le commentaire est enregistré comme vide
Étant donné que j'ai sélectionné la catégorie "🚫 Haine & violence" Et que j'ai saisi le commentaire "Le créateur tient des propos discriminatoires à 2:30"
Quand je clique sur "Envoyer le signalement"
Alors le signalement est envoyé avec succès Et le commentaire est enregistré avec le signalement Et il sera visible par les modérateurs
Étant donné que le formulaire de signalement est affiché
Quand je saisis un commentaire de 501 caractères
Alors le champ limite automatiquement à 500 caractères Et le compteur affiche "500/500" Et les caractères supplémentaires ne sont pas acceptés
Étant donné que j'ai envoyé un signalement
Quand le signalement est enregistré
Alors un toast notification s'affiche Et le toast contient le message "✓ Signalement envoyé. Nous l'examinerons sous 24-48h." Et le toast s'affiche pendant 5 secondes Et le toast contient un bouton "Voir mes signalements" Et je peux fermer le toast manuellement avec un bouton X
Étant donné que le toast de confirmation est affiché
Quand je clique sur "Voir mes signalements"
Alors je suis redirigé vers la page "Mes signalements" Et je vois la liste de tous mes signalements Et le signalement que je viens d'envoyer apparaît en premier
Étant donné que j'ai envoyé 3 signalements précédemment
Quand j'ouvre "Profil > Mes signalements"
Alors je vois la liste de mes 3 signalements Et chaque signalement affiche:
| information | description |
|---|---|
| Titre du contenu | "Podcast #42" |
| Créateur | @pseudo_createur |
| Catégorie | 🚫 Haine & violence |
| Date | 15/01/2026 |
| Statut | En cours / Traité / Rejeté |
| Mon commentaire | Texte que j'ai saisi |
Et les signalements sont triés par date décroissante
Étant donné que j'ai envoyé un signalement
Quand le statut du signalement est "
Alors le badge affiché est "
📊 Exemples de données:
| statut | badge | couleur |
|---|---|---|
| En cours | En cours | orange |
| Traité | Traité ✓ | vert |
| Rejeté | Rejeté ✗ | rouge |
Étant donné que j'ai signalé un contenu il y a 24h
Quand le modérateur traite mon signalement Et que le contenu est effectivement retiré
Alors je reçois une notification in-app Et la notification indique "Votre signalement a été traité. Le contenu a été retiré." Et le statut de mon signalement passe à "Traité ✓" Et je peux voir les détails de l'action prise
Étant donné que j'ai signalé un contenu
Quand le modérateur rejette mon signalement
Alors je reçois une notification in-app Et la notification indique "Votre signalement a été examiné. Le contenu ne viole pas les règles de la communauté." Et le statut de mon signalement passe à "Rejeté ✗" Et je peux voir la raison du rejet
Étant donné qu'un contenu a déjà été signalé par 5 autres utilisateurs
Quand je signale le même contenu
Alors mon signalement est enregistré indépendamment Et le compteur de signalements du contenu passe à 6 Et mon signalement rejoint la file d'attente de modération Et les signalements cumulés augmentent la priorité de traitement
Étant donné que j'ai déjà signalé le même contenu il y a 2 jours
Quand j'essaie de signaler à nouveau le même contenu
Alors un message m'informe "Vous avez déjà signalé ce contenu" Et le formulaire de signalement n'est pas affiché Et je peux consulter le statut de mon signalement précédent
Étant donné que j'ai envoyé 10 signalements ce mois-ci Et que 8 d'entre eux ont été rejetés comme infondés
Quand j'essaie d'envoyer un nouveau signalement
Alors mon compte est marqué comme "signaleur suspect" Et un avertissement s'affiche: Et je peux toujours envoyer le signalement Mais mes futurs signalements auront une priorité réduite
Étant donné que j'ai envoyé 20 signalements abusifs en 1 mois Et que tous ont été rejetés comme volontairement faux
Quand le modérateur détecte le pattern abusif
Alors mon compte reçoit un avertissement formel Et je perds la possibilité de signaler pendant 30 jours Et je reçois un email m'expliquant la sanction
Étant donné que j'écoute un contenu
Quand j'ouvre le menu "⋮" du player
Alors je vois l'option "Signaler" Et je peux ouvrir le formulaire de signalement
Étant donné que je consulte la page de détails d'un contenu
Quand je clique sur le bouton "⋮" en haut à droite
Alors je vois l'option "Signaler" Et je peux ouvrir le formulaire de signalement
Étant donné que je consulte mon historique d'écoute
Quand je clique sur "⋮" à côté d'un contenu passé
Alors je vois l'option "Signaler" Et je peux signaler ce contenu même si je ne l'écoute plus actuellement
Étant donné que j'ai signalé un contenu
Quand le créateur est notifié de la modération
Alors mon identité reste anonyme Et le créateur ne peut pas savoir qui a signalé Et seuls les modérateurs ont accès à l'identité du signaleur
Étant donné que le système de signalement est en place
Quand on calcule le coût
Alors le coût est de 0€ Et le formulaire est développé en interne Et aucun service tiers n'est utilisé Et les notifications in-app sont gratuites
25 scénarios (21 standards, 4 plans)
Contexte commun à tous les scénarios
Étant donné que le système de modération est actif
Étant donné qu'un utilisateur envoie un signalement pour un contenu audio
Quand le signalement est reçu
Alors le signalement est ajouté à la file d'attente asynchrone Et un worker de traitement est déclenché Et le traitement se fait en arrière-plan sans bloquer l'utilisateur
Étant donné qu'un contenu audio signalé dure 5 minutes
Quand le worker de traitement démarre
Alors le système utilise Whisper large-v3 pour transcrire l'audio Et la transcription est en self-hosted (pas de service cloud) Et le texte transcrit est enregistré en base de données Et le délai de transcription est de 1-3 minutes
Étant donné qu'un contenu audio signalé dure
Quand le système transcrit l'audio
Alors la transcription prend environ
📊 Exemples de données:
| duree | delai |
|---|---|
| 2 | 1-3 minutes |
| 10 | 3-10 minutes |
| 45 | 10-20 minutes |
Étant donné que la transcription audio est terminée
Quand le système analyse le texte transcrit
Alors les analyses suivantes sont effectuées:
| analyse | technologie |
|---|---|
| Analyse de sentiment | distilbert-base-uncased |
| Détection de haine | facebook/roberta-hate-speech |
| Mots-clés interdits | Liste noire FR/EN + regex |
Et chaque analyse génère un score de confiance (0-100%)
Étant donné que toutes les analyses sont terminées
Quand le système calcule le score final
Alors un score de confiance IA entre 0-100% est généré Et le score indique la probabilité que le contenu viole les règles Et la catégorie la plus probable est identifiée Et les timestamps des passages problématiques sont extraits
Étant donné qu'un contenu contient des insultes graves et répétées
Quand l'IA analyse la transcription
Alors le score de confiance IA est >95% Et la catégorie détectée est "Haine & violence" Et les passages problématiques sont identifiés avec timestamps:
| timestamp | texte problématique |
|---|---|
| 02:15 | [insulte discriminatoire] |
| 03:42 | [propos haineux] |
Et le signalement est classé en priorité CRITIQUE
Étant donné qu'un signalement a une priorité "
Quand le signalement entre en file d'attente
Alors le délai de traitement cible est "
📊 Exemples de données:
| priorite | delai | responsable |
|---|---|---|
| CRITIQUE | <2h (24/7) | Modérateur senior (astreinte) |
| HAUTE | <24h (jours ouvrés) | Modérateur junior/senior |
| MOYENNE | <24h (jours ouvrés) | Modérateur junior |
| BASSE | <72h (jours ouvrés) | Modérateur junior |
Étant donné qu'un signalement a un score IA de 97% Et que la catégorie détectée est "Spam" (évidente)
Quand le système évalue le signalement
Alors une action automatique immédiate est déclenchée Et le contenu est retiré automatiquement Et le créateur est notifié de la modération Et le créateur peut faire appel de la décision Et un modérateur senior vérifie l'action a posteriori
Étant donné qu'un signalement de priorité CRITIQUE est reçu à 14:00 Et que le contenu concerne une menace de violence
Quand le signalement est assigné à un modérateur senior d'astreinte
Alors le modérateur est alerté immédiatement (push + SMS) Et le signalement est traité avant 16:00 (2h) Et une décision est prise et appliquée Et les autorités peuvent être contactées si nécessaire
Étant donné qu'un signalement CRITIQUE est reçu un dimanche à 03:00
Quand le signalement est classé en priorité CRITIQUE
Alors le modérateur senior d'astreinte est alerté Et le signalement est traité dans les 2h (avant 05:00) Et le service d'astreinte garantit une disponibilité 24/7
Étant donné qu'un signalement de priorité HAUTE est reçu lundi à 10:00 Et que le contenu concerne du harcèlement
Quand le signalement entre en file d'attente
Alors le signalement est assigné à un modérateur (junior ou senior) Et le signalement est traité avant mardi 10:00 (24h jours ouvrés) Et une décision est prise et appliquée
Étant donné qu'un signalement de priorité BASSE est reçu lundi à 10:00 Et que le contenu concerne des tags incorrects
Quand le signalement entre en file d'attente
Alors le signalement est traité avant jeudi 10:00 (72h jours ouvrés) Et un modérateur junior peut traiter ce type de signalement
Étant donné qu'un signalement a les caractéristiques suivantes:
| caractéristique | valeur |
|---|---|
| Score IA | 85% |
| Signalements cumulés | 3 |
| Fiabilité du signaleur | 75% |
Quand le système calcule la priorité
Alors la formule appliquée est: Et le score de priorité est: (85 × 0.7) + (3 × 0.2) + (75 × 0.1) = 67.5 Et le signalement est classé en priorité MOYENNE
Étant donné qu'un signalement a un score de priorité de
Quand le système classe le signalement
Alors la priorité assignée est "
📊 Exemples de données:
| score | priorite | file |
|---|---|---|
| 95 | CRITIQUE | Immédiate |
| 82 | HAUTE | Prioritaire |
| 55 | MOYENNE | Normale |
| 25 | BASSE | Différée |
Étant donné qu'un contenu a été signalé par 1 utilisateur avec un score IA de 60% Et que le signalement est classé en priorité MOYENNE (score 42)
Quand 5 autres utilisateurs signalent le même contenu
Alors le nombre de signalements cumulés passe à 6 Et le score de priorité augmente significativement Et le signalement peut passer en priorité HAUTE Et le traitement est accéléré
Étant donné qu'un utilisateur de confiance (90% fiabilité) envoie un signalement Et qu'un utilisateur suspect (20% fiabilité) envoie un signalement similaire
Quand le système calcule les priorités
Alors le signalement de l'utilisateur de confiance a un score plus élevé Et son signalement est traité en priorité Et le signalement de l'utilisateur suspect est traité plus tard
Étant donné qu'un utilisateur a envoyé 10 signalements Et que 8 d'entre eux ont été acceptés par les modérateurs
Quand le système calcule son score de fiabilité
Alors le score est de 80% (8 acceptés / 10 total) Et ses futurs signalements auront plus de poids Et il peut devenir "utilisateur de confiance"
Étant donné que 50 signalements sont en attente
Quand le système organise la file d'attente
Alors les signalements sont répartis dans les files suivantes:
| file | nombre | priorité |
|---|---|---|
| Immédiate (24/7) | 5 | CRITIQUE |
| Prioritaire | 15 | HAUTE |
| Normale | 20 | MOYENNE |
| Différée | 10 | BASSE |
Et les modérateurs traitent en priorité la file Immédiate
Étant donné qu'un signalement complexe de harcèlement est reçu
Quand le système assigne un modérateur
Alors un modérateur senior est prioritairement assigné Et les modérateurs juniors peuvent traiter les cas simples (spam, tags) Et les modérateurs seniors traitent les cas complexes (haine, violence, appels)
Étant donné que le système de modération IA est déployé
Quand on analyse les technologies utilisées
Alors toutes les technologies sont opensource:
| composant | technologie | hébergement |
|---|---|---|
| Transcription | Whisper large-v3 | Self-hosted |
| Analyse sentiment | distilbert-base-uncased | Self-hosted |
| Détection haine | facebook/roberta-hate-speech | Self-hosted |
| Mots-clés interdits | Liste noire FR/EN + regex | PostgreSQL |
Et aucune dépendance à Google, AWS, Azure
Étant donné que RoadWave est en phase "
Quand on calcule le coût de l'infrastructure IA
Alors le coût mensuel est "
📊 Exemples de données:
| phase | cout |
|---|---|
| MVP | 0-50€ (CPU) |
| Scale | 50-200€ (GPU VPS) |
Étant donné que RoadWave est en phase MVP Et que le volume est <1000 signalements/mois
Quand le système traite les signalements
Alors un serveur CPU standard est suffisant Et le coût est de 0€ (serveur existant) Et le processing asynchrone absorbe les pics de charge Et les délais restent acceptables (1-20 minutes)
Étant donné que RoadWave reçoit >1000 signalements/jour
Quand le système nécessite un scaling
Alors un VPS avec GPU est requis Et le coût passe à 50-200€/mois Et les délais de transcription sont divisés par 5-10 Et le système peut gérer 10 000+ signalements/mois
Étant donné qu'un signalement est traité
Quand une action est prise (rejet, acceptation, sanction)
Alors un log d'audit complet est créé:
| champ | description |
|---|---|
| signalement_id | ID unique du signalement |
| content_id | ID du contenu signalé |
| ia_score | Score de confiance IA |
| ia_category | Catégorie détectée par IA |
| priority | CRITIQUE / HAUTE / MOYENNE / BASSE |
| moderator_id | ID du modérateur assigné |
| action_taken | Retiré / Rejeté / Strike |
| processing_time | Durée du traitement |
| timestamp | Date et heure de la décision |
Et le log est conservé pour conformité DSA Et les logs sont anonymisés après 3 ans (RGPD)
Étant donné que le système de modération est actif
Quand un audit DSA est effectué
Alors toutes les actions de modération sont tracées Et les délais de traitement sont mesurés et respectés Et les décisions sont justifiées et documentées Et la transparence vis-à-vis des utilisateurs est garantie Et le système est conforme au Digital Services Act
En tant que créateur Je veux pouvoir activer la monétisation quand je remplis les critères Afin de générer des revenus avec mes contenus
28 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et que je suis connecté en tant que créateur
Étant donné que mon compte a été créé il y a 91 jours
Quand je consulte les critères de monétisation
Alors le critère "Ancienneté ≥ 3 mois" est validé ✅
Étant donné que mon compte a été créé il y a 60 jours
Quand je consulte les critères de monétisation
Alors le critère "Ancienneté ≥ 3 mois" n'est pas validé ❌ Et je vois "Encore 30 jours avant d'être éligible"
Étant donné que j'ai exactement 500 abonnés
Quand je consulte les critères de monétisation
Alors le critère "≥ 500 abonnés" est validé ✅
Étant donné que j'ai 347 abonnés
Quand je consulte les critères de monétisation
Alors le critère "≥ 500 abonnés" n'est pas validé ❌ Et je vois "Encore 153 abonnés nécessaires"
Étant donné que mes contenus ont cumulé 10 487 écoutes complètes
Quand je consulte les critères de monétisation
Alors le critère "≥ 10 000 écoutes complètes" est validé ✅
Étant donné que mes contenus ont:
| type écoute | nombre |
|---|---|
| Écoutes complètes | 8 500 |
| Écoutes <80% | 3 000 |
Quand je consulte les critères de monétisation
Alors seules les 8 500 écoutes complètes comptent Et je vois "Encore 1 500 écoutes complètes nécessaires"
Étant donné que je n'ai aucun strike actif Et que je n'ai eu aucun contenu modéré dans les 6 derniers mois
Quand je consulte les critères de monétisation
Alors le critère "Fiabilité" est validé ✅
Étant donné que j'ai 1 strike actif pour contenu inapproprié
Quand je consulte les critères de monétisation
Alors le critère "Fiabilité" n'est pas validé ❌ Et je vois "Vous devez résoudre votre strike avant d'être éligible"
Étant donné que je n'ai pas de strike actif Mais qu'un de mes contenus a été modéré il y a 4 mois
Quand je consulte les critères de monétisation
Alors le critère "Fiabilité" n'est pas validé ❌ Et je vois "Attendre 2 mois après le dernier contenu modéré"
Étant donné que j'ai publié:
| date de publication | titre |
|---|---|
| Il y a 15 jours | Contenu 1 |
| Il y a 30 jours | Contenu 2 |
| Il y a 45 jours | Contenu 3 |
| Il y a 60 jours | Contenu 4 |
| Il y a 75 jours | Contenu 5 |
Quand je consulte les critères de monétisation
Alors le critère "≥ 5 contenus publiés dans les 90 derniers jours" est validé ✅
Étant donné que j'ai publié:
| date de publication | titre |
|---|---|
| Il y a 15 jours | Contenu 1 |
| Il y a 30 jours | Contenu 2 |
| Il y a 95 jours | Contenu 3 |
| Il y a 120 jours | Contenu 4 |
Quand je consulte les critères de monétisation
Alors seuls 2 contenus comptent (dans les 90 jours) Et je vois "Encore 3 contenus à publier dans les 90 prochains jours"
Étant donné que tous mes critères sont validés:
| critère | statut |
|---|---|
| Ancienneté ≥ 3 mois | ✅ |
| ≥ 500 abonnés | ✅ |
| ≥ 10 000 écoutes | ✅ |
| Fiabilité | ✅ |
| Régularité (5 contenus) | ✅ |
Quand j'accède à mon profil créateur
Alors le bouton "Demander la monétisation" est actif Et je peux cliquer pour démarrer le KYC
Étant donné que mes critères sont:
| critère | statut | progression |
|---|---|---|
| Ancienneté ≥ 3 mois | ✅ | 100% |
| ≥ 500 abonnés | ❌ | 347/500 (69%) |
| ≥ 10 000 écoutes | ❌ | 8500/10000 (85%) |
| Fiabilité | ✅ | 100% |
| Régularité (5 contenus) | ✅ | 100% |
Quand j'accède à mon profil créateur
Alors le bouton "Demander la monétisation" est grisé Et je vois la progression détaillée de chaque critère
Étant donné que je clique sur "Demander la monétisation"
Quand le système vérifie mes critères
Alors une requête SQL est exécutée: Et si tous les critères sont TRUE, je suis redirigé vers le KYC
Étant donné que je viens d'atteindre 500 abonnés Et que c'était mon dernier critère manquant
Quand le système détecte l'éligibilité
Alors je reçois un email:
Étant donné que je remplis tous les critères Mais que je n'ai pas encore activé la monétisation
Quand un utilisateur consulte mon profil
Alors il voit un badge "Éligible monétisation 💰" Et cela renforce ma crédibilité de créateur
Étant donné qu'un compte suspect crée du contenu frauduleux
Quand le compte est détecté dans les 2 premiers mois
Alors le compte est banni avant d'atteindre les 3 mois Et le créateur n'a jamais été éligible à la monétisation Et aucun paiement n'a été effectué
Étant donné qu'un créateur produit du contenu de mauvaise qualité
Quand ses contenus ne génèrent que 2 000 écoutes complètes
Alors il ne peut pas activer la monétisation Et seuls les créateurs avec contenu apprécié sont monétisés
Étant donné que RoadWave a 10 000 créateurs inscrits Et que seuls 500 remplissent tous les critères
Quand le système calcule le coût administratif
Alors seulement 500 KYC sont à gérer (vs 10 000) Et seulement 500 virements mensuels (vs 10 000) Et la charge comptable est réduite de 95%
Quand un utilisateur consulte la page "Devenir créateur"
Alors il voit les statistiques:
| métrique | valeur exemple |
|---|---|
| Nombre créateurs monétisés | 1 247 |
| Revenus moyens par créateur | 127€/mois |
| Top créateur (anonymisé) | 2 450€/mois |
| Critères d'éligibilité à remplir | 5 critères |
Et cela permet de fixer des attentes réalistes
Étant donné que je consulte mes critères de monétisation
Quand le système charge la page
Alors les compteurs sont récupérés depuis Redis:
| clé Redis | exemple valeur |
|---|---|
| creator:[id]:subscribers_count | 347 |
| creator:[id]:complete_listens_total | 8500 |
| creator:[id]:recent_contents_count | 7 |
Et le temps de réponse est <50ms
Étant donné que je viens de publier un nouveau contenu
Quand un utilisateur écoute ce contenu en entier
Alors le compteur "complete_listens_total" est incrémenté immédiatement Et si je rafraîchis la page critères, je vois la nouvelle valeur Et cela encourage les créateurs à continuer de produire
Étant donné que j'ai tenté d'activer la monétisation il y a 2 mois Mais que les critères n'étaient pas remplis
Quand j'accède à mes logs d'activité
Alors je vois:
| date | action | résultat | raison |
|---|---|---|---|
| 2025-11-15 | Demande monétisation | Refusée | Seulement 300 abonnés |
Et cela m'aide à suivre ma progression
Étant donné que RoadWave a 100 000 créateurs Et que chacun consulte ses critères 1 fois par jour
Quand le système traite ces requêtes
Alors la table users est indexée sur created_at Et la table subscriptions est indexée sur creator_id Et la table contents est indexée sur creator_id et published_at Et chaque requête reste <50ms grâce aux index
Étant donné que je contacte le support car je pense être éligible
Quand l'agent support consulte mon compte
Alors il voit un export JSON complet: Et l'agent peut expliquer précisément pourquoi je ne suis pas éligible
Étant donné que mes critères sont:
| critère | statut | progression |
|---|---|---|
| Ancienneté ≥ 3 mois | ❌ | 60/90 jours |
| Tous les autres critères | ✅ | 100% |
Quand il reste exactement 30 jours avant les 90 jours
Alors je reçois une notification:
Étant donné qu'un créateur influent me contacte directement Et qu'il demande un bypass des critères
Quand je consulte la politique RoadWave
Alors la réponse est "Aucune exception possible, critères automatiques uniquement" Et cela garantit l'équité pour tous les créateurs
Étant donné que RoadWave veut tester des seuils différents
Quand un A/B test est lancé en 2027
Alors groupe A voit: 500 abonnés, 10 000 écoutes Et groupe B voit: 300 abonnés, 5 000 écoutes Et les métriques (taux activation, fraude, qualité) sont comparées Et le meilleur seuil est déployé définitivement
En tant que créateur monétisé Je veux pouvoir rendre certains contenus exclusifs aux abonnés Premium Afin d'inciter les utilisateurs à s'abonner
34 scénarios
Contexte commun à tous les scénarios
Étant donné que je suis un créateur avec la monétisation activée
Étant donné que je crée un nouveau contenu
Quand j'accède aux options de publication
Alors je vois un toggle "Réservé aux abonnés Premium 👑" Et je peux l'activer ou le désactiver
Étant donné que je crée un nouveau contenu
Quand j'active le toggle "Réservé Premium" Et que je publie le contenu
Alors le champ is_premium en base est mis à true
Et le contenu est visible uniquement pour les utilisateurs Premium
Étant donné que je crée un nouveau contenu
Quand je ne touche pas au toggle "Réservé Premium" Et que je publie le contenu
Alors le champ is_premium en base est mis à false (défaut)
Et le contenu est accessible à tous les utilisateurs
Étant donné que j'ai publié un contenu gratuit il y a 2 jours
Quand je modifie le contenu et active le toggle "Réservé Premium" Et que j'enregistre les modifications
Alors le contenu devient immédiatement Premium Et les utilisateurs gratuits ne peuvent plus y accéder
Étant donné que j'ai publié un contenu Premium il y a 1 mois
Quand je modifie le contenu et désactive le toggle "Réservé Premium" Et que j'enregistre les modifications
Alors le contenu devient immédiatement gratuit Et tous les utilisateurs peuvent maintenant y accéder
Étant donné que je publie 10 nouveaux contenus
Quand je décide de rendre les 10 contenus Premium (100%)
Alors le système accepte sans limitation Et je peux avoir 100% de mon catalogue en Premium
Étant donné que je publie 10 nouveaux contenus
Quand je décide de rendre 5 contenus Premium et 5 gratuits (50/50)
Alors le système accepte cette stratégie Et je peux tester différents mix pour optimiser mes revenus
Étant donné que je suis monétisé via publicités
Quand je décide de ne mettre aucun contenu en Premium (0%)
Alors le système accepte cette stratégie Et je génère des revenus uniquement via les publicités
Étant donné qu'un utilisateur consulte ma liste de contenus
Quand il voit un contenu Premium
Alors un badge 👑 "Premium" est affiché Et le contenu est clairement identifiable comme réservé
Étant donné que je suis un utilisateur gratuit
Quand je consulte les contenus d'un créateur
Alors je vois aussi les contenus Premium dans la liste Et ils sont affichés avec un badge 👑 Mais je ne peux pas les lire
Étant donné que je suis un utilisateur gratuit
Quand je clique sur un contenu Premium pour le lire
Alors un overlay bloquant apparaît Et je vois le message: Et un bouton "Passer Premium" est affiché
Étant donné que je vois l'overlay de contenu Premium bloqué
Quand je clique sur "Passer Premium"
Alors je suis redirigé vers la page d'abonnement Premium Et je peux m'abonner pour 4.99€/mois
Étant donné que je suis un utilisateur Premium actif
Quand je clique sur un contenu Premium
Alors le contenu se lance immédiatement Et je n'ai aucun overlay bloquant Et je peux profiter pleinement du contenu exclusif
Étant donné que l'algorithme génère ma file de 5 contenus
Quand je suis un utilisateur gratuit
Alors les contenus Premium peuvent apparaître dans les recommandations Et cela me fait découvrir qu'il existe du contenu exclusif
Étant donné que je suis un utilisateur gratuit Et qu'un contenu Premium apparaît dans ma file de recommandation
Quand j'écoute le contenu précédent jusqu'à la fin
Alors le contenu Premium est automatiquement skippé Et le contenu suivant (gratuit) est lancé Et le slot Premium ne compte pas dans ma file de 5 contenus
Étant donné que je suis un utilisateur Premium Et qu'un contenu Premium apparaît dans ma file de recommandation
Quand j'écoute le contenu précédent jusqu'à la fin
Alors le contenu Premium est lancé normalement Et je profite du contenu exclusif sans interruption
is_premium boolean en base PostgreSQLÉtant donné qu'un contenu est créé
Quand il est stocké en base de données
Alors la table contents contient un champ is_premium BOOLEAN DEFAULT FALSE
Et ce champ est indexé pour requêtes rapides
is_premiumÉtant donné que l'algorithme doit filtrer les contenus selon le statut Premium
Quand une requête SQL est exécutée:
Alors l'index sur is_premium accélère la requête
Et le temps de réponse reste <20ms
Étant donné qu'un contenu Premium est consulté fréquemment
Quand l'API vérifie le statut Premium
Alors la valeur est récupérée depuis Redis: Et le cache a un TTL de 1 heure Et cela évite des requêtes SQL inutiles
Étant donné qu'un contenu est passé de gratuit à Premium
Quand le créateur enregistre la modification
Alors le cache Redis content:[id]:premium est invalidé immédiatement
Et la nouvelle valeur est mise à jour
Et les utilisateurs voient le changement en temps réel
Étant donné que chaque créateur a une audience différente
Quand un créateur décide de sa stratégie Premium
Alors il peut tester différentes approches:
| stratégie | % Premium | objectif |
|---|---|---|
| Tout gratuit | 0% | Maximiser audience + revenus pub |
| Mix 50/50 | 50% | Équilibrer audience et exclusivité |
| Premium majoritaire | 80% | Cibler abonnés fidèles |
| 100% Premium | 100% | Contenu ultra-exclusif |
Étant donné qu'un utilisateur gratuit voit beaucoup de contenus Premium
Quand il consulte les profils de ses créateurs préférés
Alors il voit que 60% de leur contenu est réservé Premium Et cela l'incite à s'abonner pour 4.99€/mois Et RoadWave augmente son taux de conversion vers Premium
Étant donné que je suis un petit créateur avec 600 abonnés Et que 50 sont abonnés Premium
Quand je mets 100% de mon contenu en Premium
Alors je génère des revenus uniquement via mes 50 abonnés Premium Et cela me permet de vivre de mon contenu malgré une petite audience
Étant donné que je suis un gros créateur avec 50 000 abonnés Et que je génère déjà beaucoup de revenus publicitaires
Quand je laisse 100% de mon contenu gratuit
Alors je maximise mon audience et mes revenus pub Et je n'ai pas besoin de mettre du contenu en Premium
Étant donné que j'accède à mon tableau de bord créateur
Quand je consulte mes statistiques de contenus
Alors je vois:
| métrique | valeur |
|---|---|
| Contenus totaux | 47 |
| Contenus gratuits | 32 (68%) |
| Contenus Premium | 15 (32%) |
| Écoutes Premium ce mois | 12,345 |
| Écoutes gratuites ce mois | 28,901 |
Étant donné que j'ai des contenus gratuits et Premium
Quand je consulte mes revenus détaillés
Alors je vois:
| source | montant |
|---|---|
| Revenus pub (gratuit) | 86.70€ |
| Revenus Premium (exclusifs) | 34.20€ |
| Revenus Premium (tout contenu) | 78.90€ |
Et je peux comparer l'efficacité de chaque stratégie
Étant donné que j'ai publié un contenu Premium il y a 3 jours Et qu'il a généré 5 000 écoutes Premium (très élevé)
Quand le système détecte cette performance
Alors je reçois une notification:
Étant donné que RoadWave veut optimiser le taux de conversion Premium
Quand un A/B test est lancé
Alors groupe A voit le badge 👑 "Premium" Et groupe B voit le badge 💎 "Exclusif" Et les taux de clic et conversion sont mesurés Et le badge le plus performant est déployé définitivement
Étant donné que RoadWave suit l'adoption de la fonctionnalité
Quand un admin consulte les métriques
Alors il voit:
| métrique | valeur |
|---|---|
| Créateurs utilisant Premium | 847 (68%) |
| % moyen contenus Premium | 23% |
| Taux conversion vers Premium (users) | 8.5% |
| Revenus Premium/mois | 47,890€ |
Étant donné qu'un utilisateur Premium envisage de résilier Mais qu'il a accès à 150 contenus Premium de ses créateurs préférés
Quand il voit la valeur exclusive qu'il perdrait
Alors il est moins susceptible de résilier (churn réduit de ~30%) Et les contenus Premium augmentent la rétention
Étant donné que j'accède à mon profil créateur
Quand je consulte mes contenus
Alors je peux filtrer par statut:
| filtre | résultats |
|---|---|
| Tous | 47 |
| Gratuits | 32 |
| Premium 👑 | 15 |
Et je peux facilement gérer mon catalogue
Étant donné que je demande l'export de mes données
Quand l'export est généré
Alors la liste de mes contenus inclut le statut Premium:
Étant donné que je supprime définitivement mon compte créateur
Quand la suppression est confirmée
Alors tous mes contenus (gratuits et Premium) sont supprimés Et les utilisateurs Premium ne peuvent plus y accéder Et les fichiers audio sont supprimés du CDN sous 7 jours
Étant donné que RoadWave a 1 million de contenus dont 300 000 Premium
Quand l'algorithme génère une recommandation
Alors la requête SQL filtre efficacement avec l'index is_premium
Et le temps de réponse reste <50ms
Et la scalabilité est garantie
En tant que créateur ou plateforme Je veux pouvoir désactiver ou suspendre la monétisation selon certaines conditions Afin de gérer les pauses, problèmes techniques ou violations des règles
35 scénarios
Contexte commun à tous les scénarios
Étant donné que je suis un créateur avec la monétisation activée
Étant donné que je veux faire une pause dans ma création de contenu
Quand j'accède à "Paramètres > Monétisation" Et que je clique sur "Désactiver temporairement la monétisation"
Alors ma monétisation est désactivée immédiatement Et je ne génère plus de revenus à partir de maintenant
Étant donné que je clique sur "Désactiver temporairement"
Quand une popup de confirmation apparaît
Alors je vois le message: Et je dois confirmer pour continuer
Étant donné que mon solde actuel est 87.45€
Quand je désactive ma monétisation le 15 du mois
Alors mon solde de 87.45€ est conservé Et il sera reporté au mois suivant Et si le total dépasse 50€, il sera versé normalement le 15 du mois prochain
Étant donné que j'ai désactivé ma monétisation
Quand des utilisateurs écoutent mes contenus
Alors mes contenus restent accessibles normalement Mais je ne génère aucun revenu (ni pub ni Premium)
Étant donné que j'ai désactivé ma monétisation il y a 8 mois Et que mes documents KYC sont toujours valides
Quand je clique sur "Réactiver la monétisation"
Alors la réactivation est immédiate Et je n'ai pas besoin de refaire le KYC Et je recommence à générer des revenus dès maintenant
Étant donné que j'ai désactivé ma monétisation il y a 25 mois
Quand j'essaie de réactiver
Alors le système demande un nouveau KYC Et je vois: Et je dois soumettre à nouveau mes documents
Étant donné que j'ai désactivé et réactivé ma monétisation plusieurs fois
Quand j'accède à "Paramètres > Monétisation > Historique"
Alors je vois la liste complète:
| date | action | raison |
|---|---|---|
| 15/06/2025 | Réactivation | Reprise création contenu |
| 01/03/2025 | Désactivation | Pause vacances |
| 20/01/2025 | Activation | KYC validé |
Étant donné que je reçois un 3ème strike pour violation des règles
Quand le strike devient actif
Alors ma monétisation est suspendue automatiquement Et je vois:
Étant donné que ma monétisation est suspendue pour 3 strikes
Quand je résous tous mes strikes (après expiration ou contestation) Et que mon compteur de strikes passe à 0
Alors ma monétisation est réactivée automatiquement Et je reçois un email de confirmation
Étant donné que 3 tentatives de virement ont échoué (15, 18, 22 du mois)
Quand le 3ème échec est confirmé
Alors ma monétisation est suspendue automatiquement Et je vois:
Étant donné que ma monétisation est suspendue pour RIB invalide
Quand je mets à jour mon RIB avec un compte bancaire valide Et que Mangopay valide le nouveau RIB
Alors ma monétisation est réactivée automatiquement Et un virement est tenté immédiatement pour le solde en attente
Étant donné que ma carte d'identité expire dans 30 jours
Quand je reçois un email de rappel de mise à jour Mais que je ne mets pas à jour mes documents Et que ma CNI expire
Alors ma monétisation est suspendue automatiquement après 30 jours de grâce
Étant donné que ma CNI expire le 15 juin 2025
Quand le 15 mai 2025 arrive (30 jours avant)
Alors je reçois un email d'alerte:
Étant donné que ma monétisation est suspendue pour CNI expirée
Quand je soumets une nouvelle CNI valide Et que Mangopay valide le document sous 24-72h
Alors ma monétisation est réactivée automatiquement Et je recommence à générer des revenus
Étant donné que le système détecte une activité frauduleuse (bots, écoutes artificielles)
Quand l'équipe modération confirme la fraude
Alors ma monétisation est suspendue immédiatement Et mon compte est mis sous enquête Et je reçois un email m'informant de la suspension
Étant donné que ma monétisation est suspendue pour suspicion de fraude
Quand l'équipe modération enquête
Alors elle analyse:
| élément à vérifier | outil |
|---|---|
| Patterns d'écoute suspects | Analytics + logs |
| Origine géographique | Logs IP |
| Vitesse de croissance anormale | Graphiques statistiques |
| Plaintes utilisateurs | Système de signalement |
Étant donné que mon compte était suspendu pour suspicion de fraude
Quand l'enquête conclut qu'il n'y a pas eu de fraude
Alors ma monétisation est réactivée Et les revenus suspendus pendant l'enquête sont versés normalement Et je reçois un email d'excuses avec explication
Étant donné que l'enquête confirme une fraude avérée
Quand l'équipe modération prend la décision
Alors ma monétisation est définitivement désactivée Et mon solde en attente est gelé (non versé) Et je peux recevoir un strike 4 (ban définitif du compte)
Étant donné que je veux arrêter définitivement la monétisation
Quand j'accède à "Paramètres > Monétisation > Supprimer définitivement"
Alors une confirmation stricte est demandée Et je dois taper "SUPPRIMER" pour confirmer
Étant donné que je supprime définitivement ma monétisation Et que mon solde en attente est 127.45€
Quand la suppression est confirmée
Alors mon solde sera versé sous 30 jours Et je reçois un dernier virement de clôture Et mon e-wallet Mangopay est clôturé
Étant donné que je n'ai plus publié de contenu depuis 24 mois Et que mon solde en attente est 12.30€ (<50€)
Quand le processus de purge RGPD s'exécute
Alors ma monétisation est automatiquement supprimée Et mon solde de 12.30€ est perdu (trop faible pour virement) Et mes données KYC sont archivées puis supprimées selon la législation
Étant donné que je suis inactif depuis 22 mois
Quand le système détecte l'inactivité
Alors je reçois un email:
Étant donné que je reçois un 4ème strike (violation grave ou répétée)
Quand l'équipe modération applique le strike 4
Alors mon compte est banni définitivement Et ma monétisation est supprimée définitivement Et mon solde en attente est gelé (non versé) Et je ne peux plus créer de nouveau compte (blacklist email/SIRET)
Étant donné que ma monétisation est suspendue (quelle qu'en soit la raison)
Quand la suspension devient effective
Alors je reçois immédiatement un email:
Étant donné que ma monétisation est suspendue
Quand je me connecte à l'application
Alors je vois une bannière en haut de mon dashboard:
Étant donné que ma monétisation était suspendue
Quand elle est réactivée (automatiquement ou manuellement)
Alors je reçois un email:
Étant donné qu'un admin RoadWave consulte les suspensions
Quand il accède au dashboard admin "Monétisation > Suspensions"
Alors il voit:
| raison suspension | nombre actif | taux |
|---|---|---|
| Strikes (3+) | 23 | 1.8% |
| RIB invalide | 12 | 0.9% |
| Documents KYC expirés | 8 | 0.6% |
| Fraude sous enquête | 3 | 0.2% |
| TOTAL | 46 | 3.7% |
Étant donné que le taux de suspension dépasse 5%
Quand le système détecte cette anomalie
Alors une alerte est envoyée à l'équipe:
Étant donné que j'accède à mon dashboard créateur
Quand je consulte "Statistiques > Monétisation"
Alors je vois:
| métrique | valeur |
|---|---|
| Date activation monétisation | 20 janvier 2025 |
| Temps actif total | 8 mois |
| Périodes de désactivation | 2 (3 mois total) |
| Suspensions subies | 0 |
| Statut actuel | ✅ Actif |
Étant donné que je demande l'export de mes données
Quand l'export est généré
Alors l'historique des suspensions est inclus:
Étant donné que je supprime définitivement mon compte RoadWave
Quand la suppression est confirmée
Alors toutes mes données de monétisation sont supprimées:
| donnée | action |
|---|---|
| Solde en attente | Versé sous 30 jours puis supprimé |
| Historique revenus | Archivé 10 ans (obligation légale) |
| Documents KYC | Archivés 10 ans chez Mangopay puis supprimés |
| E-wallet Mangopay | Clôturé après versement final |
Étant donné que je supprime mon compte
Quand mes données sont archivées
Alors RoadWave conserve 10 ans:
| donnée archivée | raison |
|---|---|
| Relevés mensuels PDF | Obligation comptable France |
| Déclarations DAS2 | Obligation fiscale France |
| Justificatifs virements | Preuve paiement en cas d'audit |
Et après 10 ans, tout est supprimé définitivement
Étant donné que Mangopay effectue une maintenance planifiée
Quand la maintenance est programmée
Alors tous les créateurs reçoivent un email préventif 7 jours avant:
Étant donné qu'un incident technique majeur suspend toutes les monétisations
Quand l'incident est résolu
Alors les réactivations se font progressivement:
| vague | critère | % créateurs |
|---|---|---|
| 1 | Top 10% créateurs (revenus) | 10% |
| 2 | Créateurs vérifiés | 30% |
| 3 | Tous les autres créateurs | 60% |
Et cela évite une surcharge système lors de la reprise
Étant donné que ma monétisation est suspendue Et que je pense que c'est une erreur
Quand je contacte le support avec tag "Suspension monétisation"
Alors mon ticket est traité en priorité (SLA 24h) Et un agent expert examine mon cas Et si suspension injustifiée, je suis réactivé immédiatement avec excuses
En tant que créateur éligible Je veux compléter le KYC pour activer la monétisation Afin de recevoir des paiements légalement
37 scénarios
Contexte commun à tous les scénarios
Étant donné que je remplis tous les critères de monétisation Et que j'ai cliqué sur "Demander la monétisation"
Quand je démarre le processus d'activation
Alors je suis redirigé vers un formulaire KYC Et le formulaire est fourni par Mangopay (iframe sécurisée) Et toutes les données sont chiffrées et hébergées en EU
Étant donné que je suis auto-entrepreneur
Quand je renseigne mon statut juridique
Alors l'option "Auto-entrepreneur (micro-BNC)" est disponible Et je peux continuer le processus
Étant donné que j'ai créé une société
Quand je renseigne mon statut juridique
Alors les options suivantes sont disponibles:
| statut juridique |
|---|
| SARL |
| SAS |
| SASU |
Et je peux continuer le processus
Étant donné que je n'ai pas de statut professionnel
Quand j'essaie de m'inscrire en tant que "Particulier"
Alors le formulaire affiche: Et je ne peux pas continuer sans statut professionnel
Étant donné que je renseigne mon SIRET
Quand je saisis "12345678901234" (14 chiffres)
Alors le format est validé Et Mangopay vérifie l'existence du SIRET auprès du répertoire SIRENE Et si valide, le document est accepté
Étant donné que je renseigne un SIRET inexistant
Quand je saisis "99999999999999"
Alors Mangopay rejette le SIRET Et je vois "SIRET non trouvé dans le répertoire SIRENE. Vérifiez le numéro." Et je dois corriger avant de continuer
Étant donné que j'upload mon RIB
Quand le RIB est scanné par Mangopay
Alors le système vérifie que le titulaire correspond à mon SIRET Et que l'IBAN commence par "FR" (compte français) Et si valide, le document est accepté
Étant donné que j'upload un RIB de compte particulier
Quand Mangopay détecte que le compte n'est pas professionnel
Alors le RIB est rejeté Et je vois:
Étant donné que j'upload ma carte nationale d'identité
Quand Mangopay analyse le document
Alors la date d'expiration est vérifiée Et si la CNI est valide, le document est accepté Et mon identité est vérifiée par OCR + vérification manuelle
Étant donné que j'upload une CNI expirée depuis 2 ans
Quand Mangopay analyse le document
Alors le document est rejeté Et je vois "Pièce d'identité expirée. Veuillez fournir un document en cours de validité."
Étant donné que je n'ai pas de CNI
Quand j'upload mon passeport en cours de validité
Alors Mangopay accepte le passeport Et mon identité est vérifiée de la même manière
Étant donné que mon CA dépasse 37 000€/an Et que je suis sorti de la franchise en base
Quand je renseigne mon numéro TVA intracommunautaire
Alors le format "FR + 11 chiffres" est validé Et Mangopay vérifie l'existence auprès de la Commission Européenne (VIES)
Étant donné que je suis auto-entrepreneur sous franchise en base Et que mon CA est <37 000€/an
Quand je remplis le formulaire KYC
Alors le champ "Numéro TVA" est optionnel Et je peux continuer sans TVA
Étant donné que je suis gérant d'une SARL
Quand j'upload mon extrait Kbis
Alors Mangopay vérifie que le Kbis date de moins de 3 mois Et que le SIRET correspond Et si valide, le document est accepté
Étant donné que j'upload un Kbis de 5 mois
Quand Mangopay analyse le document
Alors le Kbis est rejeté Et je vois "Le Kbis doit dater de moins de 3 mois. Téléchargez un extrait récent sur infogreffe.fr"
Étant donné que mon compte RoadWave est au nom de "Jean Dupont" Mais que ma CNI est au nom de "Pierre Martin"
Quand Mangopay compare les identités
Alors le KYC est rejeté Et je vois:
Étant donné que mon identité apparaît sur une liste anti-blanchiment
Quand Mangopay effectue la vérification AML (Anti-Money Laundering)
Alors le KYC est automatiquement rejeté Et je vois "Votre demande ne peut être acceptée pour des raisons de conformité légale" Et mon compte créateur peut être suspendu
Étant donné que j'ai soumis tous les documents valides
Quand Mangopay traite ma demande
Alors je reçois un email "KYC en cours de vérification (24-72h)" Et mon statut est "En attente de validation" Et je peux continuer à publier des contenus en attendant
Étant donné que mes documents sont conformes
Quand Mangopay valide mon KYC après 48h
Alors je reçois un email "Monétisation activée !" Et mon statut passe à "Monétisé" Et je commence à générer des revenus dès maintenant
Étant donné que j'ai soumis une CNI floue et illisible
Quand Mangopay analyse les documents
Alors le KYC est rejeté après 24h Et je reçois un email détaillant les documents à refournir:
Étant donné que mon KYC est validé
Quand Mangopay finalise mon inscription
Alors un e-wallet Mangopay est créé automatiquement à mon nom Et tous mes futurs revenus seront transférés vers ce wallet Et les virements SEPA vers mon RIB seront effectués depuis ce wallet
Étant donné que je fournis mes documents KYC
Quand Mangopay stocke mes données
Alors toutes les données sont hébergées en Union Européenne Et Mangopay est régulé par l'ACPR (Autorité de Contrôle Prudentiel) Et mes données sont protégées selon le RGPD
Étant donné que je complète le KYC
Quand le processus se termine
Alors aucun frais ne m'est facturé (0€) Et aucun frais n'est facturé à RoadWave (inclus dans l'offre Mangopay)
Étant donné que RoadWave est une plateforme française
Quand je génère des revenus >1200€/an
Alors RoadWave doit déclarer ces revenus aux impôts (DAS2) Et le KYC permet de garantir l'identité réelle du bénéficiaire Et cela respecte la réglementation fiscale française
Étant donné que RoadWave verse de l'argent aux créateurs
Quand le KYC est effectué
Alors RoadWave respecte la 5ème directive anti-blanchiment EU Et Mangopay effectue les vérifications requises (identité, liste noire, origine fonds)
Étant donné que ma CNI va expirer dans 30 jours
Quand le système détecte l'expiration proche
Alors je reçois un email:
Étant donné que ma CNI est expirée depuis 10 jours Et que je n'ai pas mis à jour mes documents
Quand le système vérifie mon statut KYC
Alors ma monétisation est suspendue automatiquement Et je ne génère plus de revenus jusqu'à mise à jour
Étant donné que j'ai désactivé temporairement ma monétisation il y a 6 mois Et que mes documents KYC sont toujours valides
Quand je réactive la monétisation
Alors je n'ai pas besoin de refaire le KYC Et la réactivation est immédiate
Étant donné que j'ai désactivé ma monétisation il y a 25 mois
Quand j'essaie de réactiver
Alors le système demande un nouveau KYC Et je dois soumettre des documents à jour (CNI peut avoir changé)
Étant donné que mon KYC est rejeté et je ne comprends pas pourquoi
Quand je contacte le support RoadWave
Alors un agent peut consulter les raisons du rejet Mangopay Et m'aider à fournir les bons documents
Étant donné que je demande l'export de mes données personnelles
Quand l'export est généré
Alors les informations KYC sont incluses: Et les documents scannés (CNI, RIB) sont exclus pour sécurité
Étant donné que je supprime définitivement mon compte RoadWave
Quand la suppression est confirmée
Alors mes données KYC chez Mangopay sont archivées 10 ans (obligation légale) Mais supprimées de la base RoadWave immédiatement Et mon e-wallet est clôturé après versement du solde final
Étant donné que RoadWave suit la qualité du processus KYC
Quand un admin consulte les métriques
Alors il voit:
| métrique | valeur exemple |
|---|---|
| Demandes KYC ce mois | 247 |
| Taux de validation | 87% |
| Délai moyen validation | 36h |
| Taux de rejet (documents invalides) | 13% |
Et cela permet d'optimiser le processus
Étant donné que je saisis mon SIRET
Quand le système le valide
Alors une requête est faite à l'API SIRENE de l'INSEE Et le système vérifie que le SIRET existe et est actif Et récupère le nom de l'entreprise pour pré-remplir le formulaire
Étant donné qu'un SIRET "12345678901234" est déjà utilisé par un autre créateur
Quand j'essaie d'utiliser le même SIRET
Alors le système détecte la duplication Et affiche "Ce SIRET est déjà utilisé par un autre compte RoadWave" Et je dois contacter le support si c'est une erreur
Étant donné que des données KYC sensibles transitent dans le système
Quand les logs sont enregistrés
Alors les numéros SIRET, IBAN et données CNI sont masqués: Et seule l'équipe sécurité peut accéder aux données complètes
Étant donné que mes documents KYC sont stockés chez Mangopay
Quand un audit est demandé par les autorités
Alors Mangopay peut fournir les documents originaux Et RoadWave n'a pas besoin de stocker ces documents (réduction risque RGPD)
En tant que créateur monétisé Je veux que RoadWave génère automatiquement les documents fiscaux requis Afin de faciliter ma comptabilité et respecter la loi
30 scénarios
Contexte commun à tous les scénarios
Étant donné que je suis un créateur avec la monétisation activée Et que je génère des revenus sur RoadWave
Étant donné que le mois de janvier se termine
Quand le système calcule mes revenus du mois
Alors un relevé mensuel PDF est généré automatiquement Et le PDF est disponible dans mon tableau de bord
Étant donné que mon relevé de janvier est généré
Quand je télécharge le PDF
Alors le document contient:
Étant donné que je suis sur mon tableau de bord créateur
Quand j'accède à l'onglet "Revenus > Historique"
Alors je vois la liste de mes relevés mensuels:
| mois | montant | actions |
|---|---|---|
| Janvier 2025 | 150.00€ | 📄 Télécharger PDF |
| Décembre 2024 | 123.50€ | 📄 Télécharger PDF |
| Novembre 2024 | 98.75€ | 📄 Télécharger PDF |
Étant donné que j'ai commencé la monétisation en janvier 2025
Quand je consulte mes relevés en janvier 2035 (10 ans plus tard)
Alors tous les relevés depuis 2025 sont toujours accessibles Et je peux télécharger n'importe quel relevé historique Et cela respecte l'obligation de conservation comptable de 10 ans
Étant donné que je clique sur "Exporter pour comptable"
Quand je choisis la période "Année 2025"
Alors un fichier CSV est généré et téléchargé
Étant donné que j'exporte mes données comptables 2025
Quand je télécharge le fichier CSV
Alors le fichier contient:
Étant donné que j'ai téléchargé mon export CSV 2025
Quand je l'envoie à mon expert-comptable
Alors il peut importer le fichier dans son logiciel comptable Et il saisit rapidement mes revenus RoadWave Et cela facilite ma déclaration fiscale annuelle
Étant donné que mes revenus 2025 totalisent 2,450€
Quand l'année 2025 se termine
Alors RoadWave génère automatiquement une DAS2 pour les impôts Et la DAS2 est transmise à la DGFIP en janvier 2026
Étant donné que RoadWave génère ma DAS2 pour 2025
Quand la DGFIP reçoit la déclaration
Alors le document contient:
Étant donné que RoadWave transmet ma DAS2 aux impôts
Quand la transmission est confirmée
Alors je reçois un email avec une copie de la DAS2 en pièce jointe Et je peux consulter le document dans mon tableau de bord
Étant donné que mes revenus 2025 totalisent seulement 890€
Quand l'année 2025 se termine
Alors aucune DAS2 n'est générée car le seuil de 1200€ n'est pas atteint Mais je dois quand même déclarer mes revenus dans ma déclaration personnelle
Étant donné que RoadWave verse des honoraires à des prestataires
Quand les revenus dépassent 1200€/an
Alors la déclaration DAS2 est obligatoire selon l'article 87 du Code Général des Impôts Et le non-respect entraîne une amende de 15€ par bénéficiaire non déclaré
Étant donné que RoadWave génère 1,247 DAS2 pour l'année 2025
Quand la transmission aux impôts est effectuée
Alors la transmission se fait via le portail EDI-TDFC de la DGFIP Et la transmission est automatisée (pas de saisie manuelle) Et un accusé de réception est reçu sous 48h
Étant donné que j'ai reçu 2,450€ de revenus RoadWave en 2025
Quand je fais ma déclaration fiscale en mai 2026
Alors je dois déclarer ces 2,450€ dans ma déclaration annuelle Et si je suis auto-entrepreneur, je déclare en BNC (Bénéfices Non Commerciaux)
Étant donné que je suis auto-entrepreneur Et que j'ai reçu 2,450€ de revenus RoadWave en 2025
Quand je fais ma déclaration URSSAF trimestrielle
Alors je dois déclarer ces revenus à l'URSSAF Et je paie ~22% de cotisations sociales (soit ~539€)
Étant donné que je suis auto-entrepreneur en micro-BNC Et que mon chiffre d'affaires est <37,800€/an
Quand je génère des revenus sur RoadWave
Alors je bénéficie de la franchise en base de TVA Et je ne facture pas de TVA à RoadWave Et je ne récupère pas la TVA sur mes achats
Étant donné que mon chiffre d'affaires total 2025 est 45,000€
Quand je dépasse le seuil de franchise en base (37,800€)
Alors je dois facturer de la TVA (20%) à RoadWave Et je dois obtenir un numéro TVA intracommunautaire Et je dois déclarer ma TVA mensuellement ou trimestriellement
Étant donné que je génère des revenus sur RoadWave
Quand je télécharge mes relevés mensuels et exports CSV
Alors je dois les conserver 10 ans (obligation comptable France) Et en cas de contrôle fiscal, je dois pouvoir les fournir
Étant donné que je suis créateur monétisé sur RoadWave
Quand l'année se termine
Alors Mangopay transmet automatiquement mes revenus aux autorités fiscales EU Et cela respecte la directive DAC7 (2021/514) sur la transparence fiscale des plateformes
Étant donné que RoadWave est une plateforme facilitant des transactions
Quand Mangopay gère les paiements
Alors Mangopay transmet automatiquement:
| information | destinataire |
|---|---|
| Identité créateur (SIRET) | Autorités fiscales pays EU |
| Revenus annuels | Autorités fiscales pays EU |
| Nombre transactions | Autorités fiscales pays EU |
Et RoadWave n'a pas besoin de faire cette transmission manuellement
Étant donné que je reçois un virement de 150.00€ de Mangopay
Quand je consulte mon relevé bancaire
Alors je vois le virement avec la référence MANGOPAY-ABC123 Et ce relevé bancaire sert de justificatif comptable Et je peux le fournir à mon expert-comptable ou aux impôts
Étant donné que je suis créateur monétisé
Quand le mois d'avril 2026 arrive (période déclaration impôts France)
Alors je reçois un email de rappel:
Étant donné que je suis créateur monétisé
Quand j'accède à "Aide > Fiscalité"
Alors je vois une page avec:
| ressource | description |
|---|---|
| Guide auto-entrepreneur RoadWave | PDF expliquant démarches et déclarations |
| FAQ fiscalité | Questions fréquentes sur TVA, cotisations, etc. |
| Liens URSSAF et impots.gouv.fr | Portails officiels |
| Contact expert-comptable partenaire | Recommandations d'experts connaissant RoadWave |
Étant donné que je consulte mon dashboard en décembre 2025
Quand j'accède à "Revenus > Récapitulatif annuel"
Alors je vois:
Étant donné que tous les documents fiscaux sont générés automatiquement
Quand un créateur télécharge ses documents
Alors les montants sont garantis corrects (issus de la base de données) Et il n'y a pas d'erreur de saisie manuelle Et cela réduit les risques de contrôle fiscal
Étant donné que les documents fiscaux contiennent des données sensibles (SIRET, revenus)
Quand les documents sont stockés
Alors ils sont chiffrés au repos (encryption AES-256) Et seul le créateur et les admins autorisés peuvent y accéder Et les logs d'accès sont conservés pour audit
Étant donné qu'un document fiscal est généré
Quand il est stocké dans la base de données
Alors une copie est sauvegardée sur S3 (stockage durable) Et les backups sont répliqués sur 3 zones de disponibilité Et la conservation est garantie 10 ans minimum
Étant donné que 1,247 DAS2 sont générées en janvier 2026
Quand un audit est demandé
Alors tous les événements sont loggés:
| événement | timestamp | détails |
|---|---|---|
| Calcul revenus annuels | 2025-12-31 23:59:00 | 1,247 créateurs éligibles |
| Génération fichier EDI | 2026-01-10 08:00:00 | Format EDI-TDFC |
| Transmission DGFIP | 2026-01-10 10:30:00 | Via portail EDI-TDFC |
| Accusé réception DGFIP | 2026-01-11 14:20:00 | Transmission confirmée |
| Email créateurs | 2026-01-11 16:00:00 | 1,247 emails envoyés |
Étant donné qu'un admin RoadWave consulte les métriques fiscales
Quand il accède au dashboard admin
Alors il voit:
| métrique | valeur 2025 |
|---|---|
| Créateurs monétisés | 1,247 |
| Créateurs éligibles DAS2 (>1200€) | 847 (68%) |
| Revenus totaux versés | 1,890,345€ |
| DAS2 transmises à la DGFIP | 847 |
| Taux conformité | 100% |
Étant donné que j'ai une question sur ma déclaration fiscale
Quand je contacte le support RoadWave
Alors l'agent peut consulter mes documents fiscaux Et m'aider à comprendre ce que je dois déclarer Mais il ne peut pas me conseiller fiscalement (pas expert-comptable) Et il me recommande de consulter un expert-comptable si nécessaire
En tant que créateur monétisé Je veux recevoir mes paiements mensuels de manière fiable Afin d'être rémunéré pour mon travail
35 scénarios
Contexte commun à tous les scénarios
Étant donné que je suis un créateur avec la monétisation activée Et que mon KYC est validé
Étant donné que mes revenus du mois sont 73.45€
Quand le dernier jour du mois arrive
Alors mon solde de 73.45€ est transféré vers "en attente de paiement" Et le paiement sera effectué le 15 du mois prochain
Étant donné que mes revenus du mois sont 32.17€
Quand le dernier jour du mois arrive
Alors mon solde de 32.17€ est reporté au mois suivant Et je vois "Solde insuffisant pour paiement (<50€). Report mois prochain."
Étant donné que mes revenus sont:
| mois | revenus | solde cumulé |
|---|---|---|
| Janvier | 18.50€ | 18.50€ |
| Février | 22.30€ | 40.80€ |
| Mars | 15.70€ | 56.50€ |
Quand la fin du mois de mars arrive
Alors le solde cumulé de 56.50€ dépasse les 50€ Et un paiement de 56.50€ est effectué le 15 avril
Étant donné que nous sommes le 31 janvier à 23h59
Quand le système calcule les revenus du mois
Alors une requête SQL agrège tous les revenus pub et premium Et le solde final du mois est figé dans monthly_revenues Et le compteur du mois en cours repart à 0€ le 1er février
Étant donné que mes revenus de janvier sont calculés à 150.00€
Quand la période du 1-14 février arrive
Alors RoadWave analyse les éventuelles fraudes ou contestations Et si une fraude est détectée, les revenus concernés sont retirés du solde Et le solde final est validé le 14 février
Étant donné que mes revenus de janvier validés sont 150.00€
Quand le 15 février arrive
Alors Mangopay initie un virement SEPA depuis mon e-wallet vers mon RIB Et le statut du paiement passe à "En cours"
Étant donné qu'un virement SEPA a été initié le 15 février
Quand 1-3 jours ouvrés s'écoulent
Alors je reçois le virement sur mon compte bancaire entre le 16 et 18 février Et je peux consulter l'historique des paiements dans mon dashboard
Étant donné que mon RIB est français (IBAN FR)
Quand Mangopay effectue le virement
Alors aucun frais n'est prélevé (virement SEPA gratuit) Et je reçois 100% du montant annoncé
Étant donné que je suis créateur expatrié avec RIB hors Union Européenne
Quand Mangopay effectue le virement international
Alors des frais variables s'appliquent selon le pays Et les frais sont déduits du montant final Et je vois le détail des frais dans mon historique
Étant donné que mon KYC est validé
Quand mes revenus sont calculés
Alors les revenus sont automatiquement transférés vers mon e-wallet Mangopay Et l'e-wallet est débité lors du virement SEPA vers mon RIB Et je n'ai aucune action manuelle à faire
Étant donné que j'accède à mon tableau de bord créateur
Quand je consulte l'onglet "Revenus"
Alors je vois:
| métrique | valeur exemple |
|---|---|
| Revenus pub ce mois | 123.45€ |
| Revenus premium ce mois | 67.89€ |
| Solde disponible ce mois | 191.34€ |
| Prochain paiement | 15 mars 2025 |
Et ces valeurs sont mises à jour en temps réel (cache Redis, refresh 10 min)
Étant donné que mes revenus de janvier sont calculés et validés Et que nous sommes le 10 février
Quand je consulte mon tableau de bord
Alors je vois:
| métrique | valeur exemple |
|---|---|
| Solde en attente | 150.00€ |
| Date de paiement | 15 février 2025 |
| Statut | En attente |
Étant donné que je suis monétisé depuis 6 mois
Quand je consulte l'historique des paiements
Alors je vois la liste complète:
| date paiement | montant | statut | référence virement |
|---|---|---|---|
| 15/02/2025 | 150.00€ | Payé | MANGOPAY-ABC123 |
| 15/01/2025 | 123.50€ | Payé | MANGOPAY-XYZ789 |
| 15/12/2024 | 98.75€ | Payé | MANGOPAY-DEF456 |
| ... | ... | ... | ... |
Étant donné que je clique sur "Télécharger export comptable"
Quand le fichier CSV est généré
Alors je télécharge un fichier contenant: Et je peux transmettre ce fichier à mon expert-comptable
Étant donné qu'un virement est initié le 15 février Mais que mon RIB est invalide ou le compte est fermé
Quand Mangopay détecte l'échec
Alors le statut passe à "Échec - Retry programmé le 18 février" Et je reçois un email m'alertant du problème
Étant donné que le virement du 15 février a échoué
Quand le 18 février arrive (J+3)
Alors Mangopay tente automatiquement un nouveau virement Et si le RIB est toujours invalide, le virement échoue à nouveau
Étant donné que les 2 premières tentatives ont échoué
Quand le 22 février arrive (J+7)
Alors Mangopay tente une 3ème et dernière fois Et si le virement échoue encore, la monétisation est suspendue
Étant donné que les 3 tentatives de virement ont échoué
Quand le système détecte le 3ème échec
Alors ma monétisation est suspendue automatiquement Et je reçois un email:
Étant donné que ma monétisation est suspendue pour RIB invalide Et que mon solde en attente est 150.00€
Quand je mets à jour mon RIB avec un compte valide
Alors Mangopay tente immédiatement un nouveau virement Et si le virement réussit, ma monétisation est réactivée automatiquement
Étant donné qu'un virement de 150.00€ est effectué le 15 février
Quand le virement est confirmé par Mangopay
Alors je reçois un email:
Étant donné que Mangopay facture des frais fixes par virement Et que les banques peuvent facturer des frais de réception
Quand un créateur génère seulement 5€/mois
Alors un virement mensuel coûterait proportionnellement trop cher Et le seuil de 50€ garantit des frais proportionnels raisonnables
Étant donné que YouTube fixe le seuil à 100$ (~90€)
Quand RoadWave fixe le seuil à 50€
Alors RoadWave est plus accessible pour petits créateurs Et les paiements arrivent plus rapidement
Étant donné que Twitch fixe le seuil à 50$ (~45€)
Quand RoadWave fixe le seuil à 50€
Alors le seuil est aligné sur Twitch Et les créateurs comprennent facilement le système
Étant donné que Spotify a un seuil bas de 10€ mais verse tous les 3 mois
Quand RoadWave a un seuil de 50€ mais verse chaque mois
Alors les créateurs reçoivent leurs paiements plus régulièrement Et la trésorerie est plus prévisible
Étant donné que mes revenus de janvier sont calculés
Quand le 1er février arrive
Alors un relevé mensuel PDF est généré automatiquement: Et le PDF est téléchargeable depuis mon tableau de bord
Étant donné que je génère des revenus sur RoadWave
Quand je télécharge mes relevés mensuels
Alors je dois les conserver 10 ans (obligation légale France) Et RoadWave conserve également une copie pendant 10 ans pour audit
Étant donné qu'un admin RoadWave consulte les paiements du mois
Quand il accède au dashboard admin
Alors il voit:
| métrique | valeur exemple |
|---|---|
| Créateurs payés ce mois | 1,247 |
| Montant total versé | 127,345€ |
| Paiements en attente | 34 |
| Échecs virements | 3 |
| Délai moyen réception (jours) | 1.8 |
Étant donné que 8% des virements du mois ont échoué
Quand le système détecte le taux d'échec élevé
Alors une alerte est envoyée à l'équipe technique:
Étant donné que je suis monétisé depuis 6 mois
Quand je consulte mes statistiques
Alors je vois:
| métrique | valeur |
|---|---|
| Revenus moyens/mois | 134.50€ |
| Meilleur mois | 189.00€ |
| Mois le plus bas | 87.30€ |
| Tendance | +12% ↗ |
Et cela m'aide à suivre ma progression
Étant donné que mes revenus moyens sont 134.50€/mois
Quand je consulte les projections
Alors le système estime mes revenus annuels à ~1,614€ Et je peux anticiper mes déclarations fiscales
Étant donné que mes revenus cumulés depuis inscription atteignent 1000€
Quand le paiement qui franchit ce seuil est effectué
Alors je reçois une notification:
Étant donné que RoadWave a 100 000 créateurs monétisés
Quand le calcul des paiements du 15 du mois est lancé
Alors un job asynchrone traite les paiements par batch de 1000 Et tous les virements sont initiés en 2-4 heures Et les serveurs Mangopay gèrent la charge sans problème
Étant donné que les paiements sont critiques pour les créateurs
Quand un paiement est effectué
Alors les données sont sauvegardées dans PostgreSQL (principal) Et répliquées vers une base de backup (replica) Et une copie d'archive est stockée sur S3 (conservation 10 ans)
Étant donné qu'un paiement est initié, traité et complété
Quand un audit est demandé
Alors tous les événements sont loggés:
| événement | timestamp | détails |
|---|---|---|
| Calcul revenus mois | 2025-01-31 23:59:00 | Montant: 150.00€ |
| Validation période fraude | 2025-02-14 23:59:00 | Aucune fraude détectée |
| Initiation virement | 2025-02-15 09:00:00 | Mangopay ref: ABC123 |
| Confirmation virement | 2025-02-16 14:30:00 | Reçu par banque créateur |
Et ces logs sont conservés 10 ans pour conformité
Étant donné qu'un créateur génère subitement 10 000€ de revenus en 1 mois
Alors que sa moyenne est de 50€/mois
Quand le système détecte cette anomalie
Alors le paiement est mis en attente pour vérification manuelle Et l'équipe modération analyse le compte avant validation
En tant que créateur monétisé Je veux générer des revenus via publicités et abonnés Premium Afin d'être rémunéré pour mon travail
34 scénarios
Contexte commun à tous les scénarios
Étant donné que je suis un créateur avec la monétisation activée Et que mon KYC est validé
Étant donné que mes contenus ont généré 1000 écoutes complètes par des utilisateurs gratuits
Quand le calcul des revenus du mois est effectué
Alors je touche 3.00€ pour ces 1000 écoutes Et ce montant est ajouté à mon solde disponible
Étant donné que mes contenus ont généré 10 000 écoutes complètes (utilisateurs gratuits)
Quand le mois se termine
Alors je touche 30.00€ de revenus publicitaires Et ces revenus sont visibles en temps réel dans mon tableau de bord
Étant donné que mes contenus ont généré 50 000 écoutes complètes (utilisateurs gratuits)
Quand le mois se termine
Alors je touche 150.00€ de revenus publicitaires
Étant donné que mes contenus ont généré 100 000 écoutes complètes (utilisateurs gratuits)
Quand le mois se termine
Alors je touche 300.00€ de revenus publicitaires
Étant donné qu'une publicité facturée 0.05€/écoute génère 50€ CPM
Quand la plateforme calcule la répartition
Alors le créateur touche 3€ (6% du CA pub) Et la plateforme garde 47€ (94%) pour:
| poste budgétaire | coût estimé |
|---|---|
| CDN + infrastructure | 10-15€ |
| Modération + support | 5-10€ |
| Développement + R&D | 10-15€ |
| Marge opérationnelle | 10-15€ |
Étant donné qu'un utilisateur gratuit écoute mon contenu de 10 minutes
Quand il écoute 8 minutes (80%)
Alors l'écoute compte comme "complète" Et je génère 0.003€ de revenus pub (3€/1000)
Étant donné qu'un utilisateur gratuit écoute mon contenu de 10 minutes Mais il skip après 5 minutes (50%)
Quand le calcul des revenus est effectué
Alors cette écoute ne compte pas comme "complète" Et je ne génère aucun revenu publicitaire pour cette écoute
Étant donné qu'un utilisateur Premium écoute 100% de mon contenu
Quand le calcul des revenus publicitaires est effectué
Alors cette écoute ne compte pas dans les revenus pub Mais elle compte dans les revenus Premium (système séparé)
Étant donné qu'un bot génère 10 000 écoutes artificielles sur mes contenus
Quand le système détecte le pattern suspect (rate limiting, IP unique, etc.)
Alors ces écoutes sont marquées comme frauduleuses Et elles sont exclues du calcul des revenus publicitaires
Étant donné que YouTube paie 3-5€/1000 vues
Quand RoadWave fixe le CPM créateur à 3€/1000 écoutes
Alors le tarif est aligné sur le bas de la fourchette YouTube Et cela est compétitif pour un MVP sans marché publicitaire mature
Étant donné que Spotify paie ~3-4€/1000 écoutes
Quand RoadWave fixe le CPM créateur à 3€/1000 écoutes
Alors le tarif est aligné sur l'industrie musicale Et les créateurs audio peuvent anticiper des revenus similaires
Étant donné que j'accède à mon tableau de bord créateur
Quand je consulte mes revenus publicitaires
Alors je vois:
| métrique | valeur exemple |
|---|---|
| Écoutes complètes ce mois (gratuit) | 23 456 |
| Revenus pub ce mois | 70.37€ |
| CPM effectif | 3.00€ |
Et ces valeurs sont mises à jour toutes les 10 minutes
Étant donné qu'un utilisateur Premium paie 4.99€/mois
Quand la répartition est calculée
Alors 3.49€ sont reversés aux créateurs écoutés (70%) Et 1.50€ sont gardés par la plateforme (30%)
Étant donné qu'un utilisateur Premium paie 4.99€/mois Et qu'il écoute 3 créateurs ce mois:
| créateur | temps écoute | ratio |
|---|---|---|
| Créateur A | 10h | 50% |
| Créateur B | 6h | 30% |
| Créateur C | 4h | 20% |
Quand le calcul des revenus Premium est effectué
Alors la répartition est:
| créateur | revenus |
|---|---|
| Créateur A | 1.75€ |
| Créateur B | 1.05€ |
| Créateur C | 0.70€ |
Et la somme totale versée aux créateurs est 3.50€ (70% de 4.99€)
Étant donné qu'un utilisateur Premium a écouté plusieurs créateurs
Quand le système calcule les revenus du mois
Alors la requête SQL suivante est exécutée:
Étant donné qu'un utilisateur Premium paie 4.99€/mois Et qu'il n'écoute qu'un seul créateur (moi)
Quand le mois se termine
Alors je touche 3.49€ (70% de 4.99€) Et je reçois 100% de la part créateurs
Étant donné qu'un utilisateur Premium paie 4.99€/mois Mais qu'il n'écoute aucun contenu ce mois
Quand le calcul des revenus Premium est effectué
Alors aucun créateur ne reçoit de revenus de cet utilisateur Et les 3.49€ de la part créateurs restent à la plateforme Et cela couvre les coûts d'infrastructure
Étant donné que YouTube Premium reverse 70% aux créateurs
Quand RoadWave fixe également 70/30
Alors le modèle est aligné sur le standard industrie Et les créateurs ont confiance dans l'équité du système
Étant donné que Spotify reverse 70% aux artistes
Quand RoadWave fixe également 70/30
Alors le modèle est identique à Spotify Et les créateurs audio comprennent facilement le système
Étant donné qu'Apple Music ne reverse que 52% aux artistes
Quand RoadWave offre 70% aux créateurs
Alors RoadWave est plus avantageux de 18 points Et cela devient un argument marketing fort
Étant donné que 2 créateurs ont le même nombre d'abonnés Premium Mais que le Créateur A est écouté 20h/mois et le Créateur B seulement 2h/mois
Quand les revenus Premium sont calculés
Alors le Créateur A gagne 10× plus que le Créateur B Et cela récompense la qualité et l'engagement (pas juste l'abonnement)
Étant donné qu'un utilisateur Premium écoute 10 créateurs différents
Quand les revenus sont calculés
Alors chacun des 10 créateurs reçoit sa part proportionnelle Et il n'y a pas de système où un seul créateur prend tout
Étant donné qu'un utilisateur Premium ne voit aucune publicité
Quand la plateforme calcule ses revenus
Alors elle ne touche que les 30% de l'abonnement Premium (1.50€) Et cette marge compense la perte des revenus publicitaires (qui auraient été ~47€/1000 écoutes)
Étant donné que j'accède à mon tableau de bord créateur
Quand je consulte mes revenus Premium
Alors je vois:
| métrique | valeur exemple |
|---|---|
| Abonnés Premium actifs ayant écouté | 47 |
| Heures d'écoute Premium ce mois | 234h |
| Revenus Premium ce mois | 89.23€ |
Et ces valeurs sont mises à jour toutes les 10 minutes
Étant donné que j'ai généré ce mois:
| source | montant |
|---|---|
| Revenus pub | 150.00€ |
| Revenus Premium | 89.23€ |
Quand je consulte mon solde disponible
Alors le total est 239.23€ Et ce solde sera versé le 15 du mois prochain (si ≥50€)
Étant donné que j'accède à mon tableau de bord créateur
Quand je consulte la page revenus
Alors je vois:
Étant donné que je clique sur "Exporter pour comptable"
Quand l'export est généré
Alors je télécharge un fichier CSV: Et je peux transmettre ce fichier à mon expert-comptable
Étant donné que je suis créateur monétisé
Quand chaque lundi matin arrive
Alors je reçois un email récapitulatif:
Étant donné que je suis monétisé depuis 12 mois
Quand j'accède à mes statistiques
Alors je vois un graphique en courbes montrant:
| mois | revenus pub | revenus premium | total |
|---|---|---|---|
| Jan 25 | 150€ | 89€ | 239€ |
| Déc 24 | 123€ | 55€ | 178€ |
| Nov 24 | 100€ | 56€ | 156€ |
| ... | ... | ... | ... |
Et cela m'aide à suivre ma progression
Étant donné que j'ai publié 20 contenus ce mois
Quand je consulte mes statistiques détaillées
Alors je vois mon top 3 contenus:
| titre | écoutes | revenus pub | revenus premium | total |
|---|---|---|---|---|
| Mon meilleur épisode | 12,345 | 37.04€ | 23.45€ | 60.49€ |
| Discussion tech | 8,901 | 26.70€ | 15.67€ | 42.37€ |
| Road trip Bretagne | 7,234 | 21.70€ | 12.34€ | 34.04€ |
Et cela m'aide à comprendre quel type de contenu plaît le plus
Étant donné que j'ai activé les notifications de seuils
Quand mes revenus du mois dépassent 100€ pour la première fois
Alors je reçois une notification:
Étant donné que RoadWave a 100 000 créateurs monétisés
Quand le calcul des revenus mensuels est lancé le dernier jour du mois
Alors un job asynchrone traite tous les créateurs Et le calcul prend environ 2-4 heures pour tous les créateurs Et les résultats sont stockés dans la table monthly_revenues
Étant donné que je consulte mon dashboard plusieurs fois par jour
Quand la page se charge
Alors les compteurs sont récupérés depuis Redis:
| clé Redis | valeur exemple |
|---|---|
| creator:[id]:complete_listens:202501 | 50234 |
| creator:[id]:premium_hours:202501 | 234 |
| creator:[id]:revenue_ads:202501 | 150.70 |
| creator:[id]:revenue_premium:202501 | 89.23 |
Et le temps de réponse est <30ms
Étant donné que nous sommes le 20 du mois Et que mes revenus actuels sont 160€
Quand le système calcule la projection
Alors il estime les revenus fin de mois à ~240€ (extrapolation linéaire) Et affiche "Projection fin de mois: ~240€" Et cela m'aide à anticiper mes revenus
En tant qu'auditeur avec véhicule arrêté Je veux accéder à des actions avancées depuis l'application mobile Afin de liker explicitement, m'abonner ou signaler du contenu
23 scénarios (21 standards, 2 plans)
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et qu'un utilisateur est connecté Et que le véhicule est à l'arrêt (vitesse GPS = 0 km/h)
Étant donné que j'écoute un contenu tagué "Automobile" Et que ma jauge "Automobile" est à 60%
Quand je clique sur le bouton cœur "Like"
Alors ma jauge "Automobile" augmente de 2% Et une animation de cœur rouge s'affiche Et une vibration courte est déclenchée Et ma jauge "Automobile" est maintenant à 62%
Étant donné que j'ai écouté un contenu "Voyage" à 85% Et que j'ai reçu un like automatique renforcé (+2%) Et que ma jauge "Voyage" est à 52%
Quand je clique sur le bouton cœur "Like"
Alors ma jauge "Voyage" augmente encore de 2% Et ma jauge "Voyage" passe à 54% Et les deux likes sont cumulés
Étant donné que j'ai liké manuellement un contenu "Sport" Et que ma jauge "Sport" est à 57%
Quand je clique à nouveau sur le bouton cœur (toggle)
Alors le cœur redevient vide (unlike) Et ma jauge "Sport" diminue de 2% Et ma jauge "Sport" revient à 55%
Étant donné que j'ai écouté un contenu "Musique" à 90% Et que j'ai reçu un like automatique renforcé (+2%) Et que ma jauge "Musique" est à 52% Et que je n'ai PAS liké manuellement
Quand je consulte l'interface
Alors le bouton "Unlike" n'est pas disponible Et le cœur reste grisé (aucun like manuel) Et ma jauge reste à 52%
Étant donné qu'un créateur publie des contenus tagués "Automobile" et "Technologie" Et que mes jauges sont:
| catégorie | niveau |
|---|---|
| Automobile | 50% |
| Technologie | 45% |
Quand je clique sur "S'abonner" sur le profil du créateur
Alors ma jauge "Automobile" augmente de 5% Et ma jauge "Technologie" augmente de 5% Et une animation d'étoile dorée s'affiche Et un badge "Abonné ✓" apparaît sur le profil Et mes nouvelles jauges sont:
| catégorie | niveau |
|---|---|
| Automobile | 55% |
| Technologie | 50% |
Étant donné que je suis abonné à un créateur Et que mes jauges "Automobile" et "Technologie" sont à 55% et 50%
Quand je clique sur "Se désabonner"
Alors ma jauge "Automobile" diminue de 5% Et ma jauge "Technologie" diminue de 5% Et le badge "Abonné ✓" disparaît Et mes nouvelles jauges sont:
| catégorie | niveau |
|---|---|
| Automobile | 50% |
| Technologie | 45% |
Étant donné que j'écoute un contenu
Quand je clique sur le menu contextuel "⋮" Et que je sélectionne "Signaler"
Alors un formulaire de signalement s'ouvre Et je dois sélectionner une catégorie:
| Catégorie |
|---|
| Haine et violence |
| Contenu sexuel |
| Illégalité |
| Droits d'auteur |
| Spam |
| Désinformation (fake news) |
| Autre |
Et je peux ajouter un commentaire optionnel Et le signalement est envoyé au flux de modération
Étant donné que je clique sur le bouton cœur
Quand le like est enregistré
Alors une animation de cœur rouge se lance (0.5s) Et le cœur reste rouge plein Et une vibration haptique courte est déclenchée (iOS: .light, Android: 50ms) Et un badge "♥ Ajouté à vos favoris" s'affiche 2 secondes
Étant donné que je clique sur "S'abonner"
Quand l'abonnement est enregistré
Alors une animation d'étoile dorée se lance (0.8s) Et le bouton devient "Abonné ✓" avec badge doré Et une notification "Abonné à [Créateur]" s'affiche Et les contenus du créateur seront boostés +30% dans l'algo
Étant donné que je conduis à 50 km/h
Quand j'essaie d'accéder au bouton cœur dans l'app mobile
Alors le bouton est grisé et non cliquable Et un message "Arrêtez-vous pour liker" s'affiche si clic tenté Et seules les commandes au volant physiques fonctionnent
Étant donné que je conduis à 40 km/h
Quand j'essaie d'accéder au profil créateur dans l'app
Alors le bouton "S'abonner" est désactivé Et un message "Arrêtez-vous pour vous abonner" s'affiche Et la navigation dans l'app est limitée aux fonctions lecture
Étant donné que je conduis à 60 km/h Et que j'utilise CarPlay avec Siri
Quand je dis "Hey Siri, signale ce contenu"
Alors Siri demande "Quelle catégorie ?" Et je peux répondre vocalement "Spam" ou autre catégorie Et le signalement est enregistré sans toucher l'écran
Étant donné que je conduis avec CarPlay activé
Quand je dis "Hey Siri, like ce podcast"
Alors un like explicite (+2%) est enregistré Et Siri confirme "J'ai ajouté ce contenu à vos favoris"
Quand je dis "OK Google, abonne-moi à ce créateur"
Alors l'abonnement est enregistré (+5% toutes jauges) Et Google Assistant confirme "Vous êtes maintenant abonné"
Étant donné que le véhicule est à l'arrêt
Quand je clique sur le menu "⋮" (3 points verticaux)
Alors les options disponibles sont:
| Option |
|---|
| Like (cœur) |
| S'abonner au créateur |
| Signaler |
| Partager |
| Voir le profil du créateur |
| Télécharger (mode offline) |
Et toutes les options sont cliquables
Étant donné que je conduis à 30 km/h
Quand j'essaie d'ouvrir le menu "⋮"
Alors seules 2 options sont disponibles:
| Option |
|---|
| Signaler (vocal possible) |
| Suivant |
Et les actions complexes sont désactivées
Étant donné que je like manuellement 5 contenus
Quand je ferme l'application Et que je me reconnecte plus tard
Alors tous mes likes manuels sont toujours présents Et les cœurs rouges sont affichés sur les contenus likés Et mes jauges reflètent toujours l'impact (+2% × 5 likes)
Étant donné que j'ai liké manuellement 10 contenus
Quand j'accède à mon profil utilisateur
Alors je vois une section "❤️ Mes favoris" Et la liste affiche les 10 contenus likés Et je peux cliquer pour réécouter Et je peux retirer un like (unlike) depuis cette liste
Étant donné que je suis abonné à 5 créateurs
Quand j'accède à mon profil utilisateur
Alors je vois une section "⭐ Mes abonnements" Et la liste affiche les 5 créateurs avec leurs avatars Et je peux accéder au profil de chaque créateur Et je peux me désabonner depuis cette liste
Étant donné qu'un créateur a publié des contenus avec ces tags:
| Contenu | Tags |
|---|---|
| C1 | Automobile, Voyage |
| C2 | Automobile, Technologie |
| C3 | Voyage, Famille |
Et que mes jauges sont toutes à 50%
Quand je m'abonne à ce créateur
Alors les jauges impactées sont:
| Tag | Impact |
|---|---|
| Automobile | +5% |
| Voyage | +5% |
| Technologie | +5% |
| Famille | +5% |
Et toutes les autres jauges restent à 50%
Étant donné que je suis abonné à 200 créateurs
Quand j'essaie de m'abonner à un 201ème créateur
Alors un message "Limite de 200 abonnements atteinte" s'affiche Et je dois me désabonner d'un créateur existant pour en ajouter un nouveau
Étant donné que je suis abonné à un créateur
Quand je clique sur "Se désabonner"
Alors une popup de confirmation s'affiche: Et je dois confirmer pour valider Et je peux annuler pour conserver l'abonnement
Étant donné qu'un contenu est tagué "Sport" Et que ma jauge "Sport" est à 50%
Quand j'écoute à
Alors l'impact total est
📊 Exemples de données:
| pourcentage | auto | total | nouveau_niveau |
|---|---|---|---|
| 10 | 0 | +2% | 52% |
| 30 | +1% | +3% | 53% |
| 50 | +1% | +3% | 53% |
| 80 | +2% | +4% | 54% |
| 95 | +2% | +4% | 54% |
Étant donné que je roule à
Quand j'essaie d'accéder à
Alors l'action est
📊 Exemples de données:
| vitesse | action | disponibilite |
|---|---|---|
| 0 | Like manuel | disponible |
| 0 | Abonnement | disponible |
| 0 | Signalement | disponible |
| 5 | Like manuel | disponible |
| 5 | Abonnement | disponible |
| 10 | Like manuel | désactivée |
| 10 | Abonnement | désactivée |
| 50 | Like manuel | désactivée |
| 50 | Abonnement | désactivée |
| 50 | Signalement vocal | disponible |
En tant qu'auditeur Je veux que le bouton "Précédent" ait un comportement intelligent Afin de rejouer le contenu actuel ou revenir au précédent selon la progression
19 scénarios (17 standards, 2 plans)
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et qu'un utilisateur est connecté
Étant donné que j'ai écouté le contenu "A" pendant 2 minutes Et que j'écoute maintenant le contenu "B" depuis 5 secondes
Quand j'appuie sur "Précédent"
Alors la lecture revient au contenu "A" Et la position de lecture est à 2 minutes (position exacte sauvegardée) Et le contenu "B" reste en historique
Étant donné que j'écoute le contenu "C" depuis 15 secondes
Quand j'appuie sur "Précédent"
Alors le contenu "C" rejoue depuis le début (position 0:00) Et la lecture ne revient pas au contenu précédent Et la progress bar revient à 0%
Étant donné que j'écoute le contenu "D" depuis exactement 10 secondes
Quand j'appuie sur "Précédent"
Alors le contenu "D" rejoue depuis le début Et la lecture ne revient pas au contenu précédent
Étant donné que je viens de démarrer l'application Et que j'écoute le contenu "Premier" depuis 3 secondes
Quand j'appuie sur "Précédent"
Alors le contenu "Premier" rejoue depuis le début Et aucun contenu précédent n'existe
Étant donné que j'ai écouté 10 contenus [C1, C2, ..., C10] Et que l'historique Redis contient 10 entrées
Quand je passe au contenu C11
Alors le contenu C1 est supprimé de l'historique (FIFO) Et l'historique contient [C2, C3, ..., C10, C11] Et la taille reste à 10 contenus maximum
Étant donné que j'écoute le contenu "A" (durée 5 minutes)
Quand j'atteins 2 minutes 30 secondes Et que j'appuie sur "Suivant"
Alors l'historique enregistre:
| content_id | position_seconds | listened_at |
|---|---|---|
| A | 150 | 2026-01-21T10:30:00 |
Quand je reviens au contenu "A" via "Précédent"
Alors la lecture reprend exactement à 2 minutes 30 secondes
Étant donné que j'ai écouté dans l'ordre: A (2min), B (30s), C (3min) Et que j'écoute maintenant D depuis 1 seconde
Quand j'appuie sur "Précédent" (1ère fois)
Alors je reviens au contenu C à la position 3 minutes
Quand j'appuie sur "Précédent" (<10s sur C)
Alors je reviens au contenu B à la position 30 secondes
Quand j'appuie sur "Précédent" (<10s sur B)
Alors je reviens au contenu A à la position 2 minutes
Étant donné que j'écoute un contenu de 5 minutes
Quand j'atteins 2 minutes 30 secondes (milieu) Et que j'appuie sur "Précédent"
Alors le contenu actuel rejoue depuis 0:00 Et je ne reviens pas au contenu précédent
Étant donné que j'écoute le contenu "A" depuis 1 minute
Quand j'appuie sur "Suivant"
Alors le contenu "B" démarre
Quand j'appuie immédiatement sur "Précédent" (2s après)
Alors je reviens au contenu "A" à la position 1 minute Et le contenu "B" reste dans l'historique
Étant donné que j'appuie sur "Précédent"
Quand le changement de contenu se produit
Alors la transition audio utilise un fade out/in de 0.3 secondes Et la progress bar revient avec une animation fluide Et l'interface ne montre aucun message de confirmation
Étant donné que j'ai un historique de 5 contenus en cache Redis
Quand je perds la connexion réseau temporairement Et que je reviens en ligne
Alors l'historique de navigation est toujours disponible Et je peux toujours utiliser "Précédent"
Étant donné que j'ai écouté 3 contenus
Quand je consulte le cache Redis
Alors la structure est: Et l'ordre est du plus récent au plus ancien
Étant donné que je démarre une session avec le contenu "Initial" Et que j'écoute depuis 3 secondes
Quand j'appuie sur "Précédent"
Alors le contenu "Initial" rejoue depuis le début Et aucune erreur n'est générée Et l'historique reste vide
Étant donné que j'écoute un contenu
Quand le temps écoulé est de 9.9 secondes Et que j'appuie sur "Précédent"
Alors je reviens au contenu précédent
Quand le temps écoulé est de 10.0 secondes Et que j'appuie sur "Précédent"
Alors le contenu actuel rejoue depuis le début
Étant donné que j'ai écouté le contenu "A" jusqu'à 75% (3min45 sur 5min) Et que je suis passé au contenu "B"
Quand je reviens au contenu "A" via "Précédent"
Alors la progress bar affiche 75% Et l'indicateur de temps affiche "3:45 / 5:00" Et la lecture reprend exactement à cet endroit
Étant donné que j'écoute un contenu "X" pendant 45 secondes à 10:30:15
Quand je passe au contenu suivant
Alors l'historique enregistre:
| content_id | position_seconds | listened_at |
|---|---|---|
| X | 45 | 2026-01-21T10:30:15Z |
Et le timestamp précis permet l'analyse d'usage
Étant donné un historique de [C1@10:00, C2@10:02, ..., C10@10:20]
Quand j'ajoute C11 à 10:22
Alors C1 (le plus ancien) est supprimé Et l'historique contient [C2@10:02, ..., C11@10:22] Et la taille reste exactement 10 entrées
Étant donné que j'écoute un contenu depuis
Quand j'appuie sur "Précédent"
Alors l'action est
📊 Exemples de données:
| temps | comportement |
|---|---|
| 1 | revenir au contenu précédent |
| 5 | revenir au contenu précédent |
| 9 | revenir au contenu précédent |
| 10 | rejouer le contenu actuel depuis 0:00 |
| 11 | rejouer le contenu actuel depuis 0:00 |
| 30 | rejouer le contenu actuel depuis 0:00 |
| 180 | rejouer le contenu actuel depuis 0:00 |
Étant donné que j'écoute un contenu de 10 minutes
Quand j'atteins
Alors la lecture reprend exactement à
📊 Exemples de données:
| position |
|---|
| 0:15 |
| 1:30 |
| 3:45 |
| 5:00 |
| 7:23 |
| 9:50 |
En tant que conducteur avec CarPlay ou Android Auto Je veux utiliser des commandes vocales pour interagir avec l'application Afin de garder les mains sur le volant et les yeux sur la route
25 scénarios (23 standards, 2 plans)
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et qu'un utilisateur est connecté Et que CarPlay ou Android Auto est activé
Étant donné que je conduis avec CarPlay activé
Quand je dis "Hey Siri"
Alors Siri est disponible pour les commandes RoadWave
Étant donné que je conduis avec Android Auto activé
Quand je dis "OK Google"
Alors Google Assistant est disponible pour les commandes RoadWave
Étant donné que nous sommes en 2026
Quand je consulte les statistiques du parc automobile EU
Alors environ 30-40% des véhicules ont CarPlay ou Android Auto Et ces utilisateurs peuvent utiliser les commandes vocales Et les 60-70% restants utilisent les commandes au volant uniquement
Étant donné que j'écoute un contenu tagué "Automobile" Et que ma jauge "Automobile" est à 60%
Quand je dis "Hey Siri, like ce podcast"
Alors un like explicite (+2%) est enregistré Et ma jauge "Automobile" passe à 62% Et Siri confirme vocalement "J'ai ajouté ce contenu à vos favoris" Et aucune interaction écran n'est requise
Étant donné que j'écoute un contenu tagué "Voyage"
Quand je dis "OK Google, like ce contenu"
Alors un like explicite est enregistré (+2%) Et Google Assistant confirme "J'ai liké ce contenu pour vous" Et la commande fonctionne sans toucher l'écran
Étant donné que j'écoute un contenu d'un créateur tagué "Automobile" et "Technologie" Et que mes jauges sont à 50% et 45%
Quand je dis "Hey Siri, abonne-moi à ce créateur"
Alors l'abonnement est enregistré Et mes jauges augmentent de 5% chacune (55% et 50%) Et Siri confirme "Vous êtes maintenant abonné à [Nom du créateur]"
Étant donné que j'écoute un contenu "A"
Quand je dis "Hey Siri, passe au contenu suivant"
Alors le contenu "B" démarre immédiatement Et la commande a le même effet que le bouton physique "Suivant"
Étant donné que j'écoute un contenu inapproprié
Quand je dis "OK Google, signale ce contenu"
Alors Google Assistant demande "Quelle catégorie ?" Et je réponds vocalement "Spam" Alors le signalement est enregistré avec la catégorie "Spam" Et Google Assistant confirme "J'ai signalé ce contenu"
Étant donné que j'écoute un contenu
Quand je dis "Hey Siri, signale ce contenu pour haine"
Alors le signalement est enregistré avec la catégorie "Haine et violence" Et Siri confirme "J'ai signalé ce contenu pour haine et violence" Et le flux de modération reçoit le signalement
Étant donné que je dis "signale ce contenu pour [catégorie]"
Quand la catégorie est:
| Mot-clé vocal | Catégorie mappée |
|---|---|
| "haine" | Haine et violence |
| "sexuel" | Contenu sexuel |
| "illégalité" | Illégalité |
| "droits d'auteur" | Droits d'auteur |
| "spam" | Spam |
| "fake news" | Désinformation |
| "autre" | Autre |
Alors le signalement est enregistré avec la bonne catégorie
Étant donné que je dis "Hey Siri, super ce podcast"
Quand Siri ne reconnaît pas l'intent RoadWave
Alors Siri répond "Je ne comprends pas cette commande RoadWave" Et elle suggère "Dites 'like ce podcast' ou 'passe au suivant'"
Étant donné que je roule à 50 km/h
Quand j'utilise les commandes vocales
Alors toutes les commandes sont disponibles:
| Commande | Action |
|---|---|
| "Like ce podcast" | Like explicite +2% |
| "Abonne-moi à ce créateur" | Abonnement +5% |
| "Passe au suivant" | Contenu suivant |
| "Reviens au précédent" | Contenu précédent (règle 10s) |
| "Pause" | Pause lecture |
| "Reprends la lecture" | Play |
| "Signale ce contenu" | Signalement |
Étant donné que l'app iOS implémente les Intents
Quand je configure les Shortcuts iOS
Alors les intents suivants sont disponibles:
| Intent Name | Action |
|---|---|
| LikeCurrentContentIntent | Like explicite |
| SubscribeToCreatorIntent | Abonnement |
| ReportContentIntent | Signalement |
| SkipToNextContentIntent | Suivant |
Et Siri les reconnaît automatiquement
Étant donné que l'app Android implémente les Voice Actions
Quand je configure les actions Google Assistant
Alors les actions suivantes sont disponibles:
| Action Name | Action |
|---|---|
| com.roadwave.LIKE_CONTENT | Like explicite |
| com.roadwave.SUBSCRIBE_CREATOR | Abonnement |
| com.roadwave.REPORT_CONTENT | Signalement |
| com.roadwave.SKIP_NEXT | Suivant |
Et Google Assistant les reconnaît
Étant donné que je dis "Hey Siri, like ce podcast"
Quand l'action est enregistrée avec succès
Alors Siri répond immédiatement avec confirmation: Et la réponse est naturelle et concise Et elle ne distrait pas de la conduite
Étant donné que je dis "Hey Siri, abonne-moi à ce créateur" Et que j'ai atteint la limite de 200 abonnements
Quand Siri essaie d'enregistrer l'abonnement
Alors l'action échoue Et Siri répond "Impossible de s'abonner, limite de 200 abonnements atteinte" Et elle suggère "Désabonnez-vous d'un créateur pour continuer"
Étant donné que mon Siri est configuré en français
Quand je dis "Hey Siri, j'aime ce podcast"
Alors la commande est reconnue (variante de "like ce podcast")
Quand je dis "Hey Siri, mets une étoile"
Alors la commande est reconnue (variante de "like")
Étant donné que les commandes vocales sont une feature Sprint 5
Quand le MVP est lancé
Alors seules les commandes au volant physiques sont disponibles
Quand le Sprint 5 est déployé
Alors les intents iOS/Android sont activés Et les commandes vocales deviennent disponibles
Étant donné que je conduis avec CarPlay Et que j'ai accès aux boutons physiques ET aux commandes vocales
Quand je veux liker un contenu
Alors je peux soit: Et les 3 méthodes sont valides
Étant donné que 100 utilisateurs avec CarPlay utilisent RoadWave
Quand je consulte les analytics
Alors je peux voir:
| Métrique | Exemple valeur |
|---|---|
| Taux d'utilisation commandes vocal | 15% |
| Commande la plus utilisée | "Like" |
| Taux de reconnaissance réussie | 92% |
| Taux d'échec / incompréhension | 8% |
Étant donné que je like un contenu via commande vocale
Quand l'action est enregistrée
Alors aucune vibration haptique n'est déclenchée Et seule la confirmation vocale est donnée
Étant donné que je dis "Hey Siri, like ce podcast"
Quand l'action est enregistrée
Alors le badge "♥ Ajouté à vos favoris" s'affiche sur l'écran CarPlay Et le cœur devient rouge plein dans l'interface Et la mise à jour est visible même sans toucher l'écran
Étant donné que j'écoute un contenu anonyme (créateur supprimé)
Quand je dis "Hey Siri, abonne-moi à ce créateur"
Alors Siri répond "Ce créateur n'est plus disponible" Et aucun abonnement n'est enregistré
Étant donné que je dis "Hey Siri, like ce podcast" Et que le contenu change 1 seconde après
Quand Siri traite la commande 2 secondes plus tard
Alors la commande s'applique au contenu qui était en lecture au moment de la commande Et non au contenu actuel (système de timestamp)
Étant donné que j'utilise
Quand je dis
Alors l'action
📊 Exemples de données:
| assistant | commande | action | confirmation |
|---|---|---|---|
| Siri | "Like ce podcast" | Like +2% | "Ajouté à vos favoris" |
| Google Assistant | "Like ce contenu" | Like +2% | "J'ai liké ce contenu" |
| Siri | "Abonne-moi à ce créateur" | Abonnement +5% | "Vous êtes abonné" |
| Google Assistant | "Abonne-moi à ce créateur" | Abonnement +5% | "Abonnement enregistré" |
| Siri | "Signale ce contenu" | Signalement | "J'ai signalé ce contenu" |
| Google Assistant | "Signale ce contenu" | Signalement | "Contenu signalé" |
Étant donné que je dis "signale ce contenu pour
Quand
Alors la catégorie mappée est
📊 Exemples de données:
| mot_cle | categorie |
|---|---|
| haine | Haine et violence |
| violence | Haine et violence |
| sexuel | Contenu sexuel |
| porno | Contenu sexuel |
| illégal | Illégalité |
| terrorisme | Illégalité |
| copyright | Droits d'auteur |
| droits auteur | Droits d'auteur |
| spam | Spam |
| fake news | Désinformation |
| fausse info | Désinformation |
En tant que conducteur en sécurité Je veux utiliser uniquement les commandes simplifiées au volant Afin de naviguer sans distraction et en toute sécurité
21 scénarios (19 standards, 2 plans)
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et qu'un utilisateur est connecté Et que l'application est connectée via CarPlay ou Android Auto
Étant donné que je conduis à 50 km/h
Quand je consulte les commandes physiques disponibles
Alors seules 3 actions sont disponibles:
| Commande | Action |
|---|---|
| Suivant | Passer au contenu suivant |
| Précédent | Revenir au précédent (règle 10s) |
| Play/Pause | Pause/reprise avec fade 0.3s |
Et aucune commande complexe n'est proposée
Étant donné que j'écoute un contenu "A"
Quand j'appuie sur le bouton physique "Suivant" au volant
Alors le contenu "B" démarre immédiatement Et aucune action supplémentaire n'est requise Et l'interface ne demande aucune confirmation
Étant donné que j'écoute un contenu depuis 5 secondes
Quand j'appuie sur "Précédent" au volant
Alors je reviens au contenu précédent (règle <10s)
Étant donné que j'écoute un contenu depuis 15 secondes
Quand j'appuie sur "Précédent" au volant
Alors le contenu actuel rejoue depuis le début (règle ≥10s)
Étant donné qu'un contenu est en lecture
Quand j'appuie sur "Pause" au volant
Alors la lecture se met en pause avec un fade out de 0.3 secondes Et la position de lecture est sauvegardée
Quand j'appuie sur "Play" au volant
Alors la lecture reprend avec un fade in de 0.3 secondes Et la reprise se fait à la position exacte
Étant donné que je conduis
Quand j'essaie un appui long sur "Suivant"
Alors l'action n'est pas détectée (non supporté iOS/Android)
Quand j'essaie un double-appui sur "Pause"
Alors l'action n'est pas détectée Et seules les actions simples (clic simple) fonctionnent
Étant donné que je conduis une voiture avec commandes basiques Et que mon véhicule a seulement Suivant/Précédent/Pause
Quand j'utilise RoadWave
Alors toutes les fonctions essentielles sont accessibles Et je n'ai pas besoin de boutons supplémentaires
Étant donné que j'appuie sur "Suivant"
Quand le contenu change
Alors l'interface CarPlay/Android Auto affiche le nouveau titre Et aucune popup ne bloque la vue Et le changement est fluide et immédiat
Étant donné que j'écoute un contenu de 5 minutes tagué "Automobile"
Quand j'écoute pendant 4 minutes 30 secondes (90%)
Alors un like automatique renforcé (+2 points) est enregistré Et un badge discret "♥ Ajouté à vos favoris" s'affiche 2 secondes Et aucune action manuelle n'est requise
Étant donné que j'écoute un contenu de 5 minutes tagué "Voyage"
Quand j'écoute pendant 2 minutes (40%) Et que j'appuie sur "Suivant"
Alors un like automatique standard (+1 point) est enregistré Et un badge discret s'affiche brièvement Et je peux continuer à conduire sans interruption
Étant donné que j'écoute un contenu tagué "Politique"
Quand j'appuie sur "Suivant" après seulement 5 secondes
Alors un signal négatif (-0.5 point) est enregistré Et la jauge "Politique" diminue légèrement Et aucun message n'est affiché (transparence)
Étant donné que j'écoute un contenu de 10 minutes
Quand j'écoute pendant 2 minutes (20%) Et que j'appuie sur "Suivant"
Alors aucun like n'est enregistré Et les jauges ne changent pas Et le système considère l'écoute comme neutre
Étant donné que je reçois un like automatique
Quand le badge "♥ Ajouté à vos favoris" apparaît
Alors il reste visible 2 secondes en bas de l'écran Et il disparaît automatiquement sans action Et il ne bloque pas la vue de la route
Étant donné que je démarre la lecture d'un contenu
Quand le player audio iOS/Android enregistre le temps
Alors le startTime est enregistré à la milliseconde
Quand j'arrête la lecture (Suivant, Pause, ou fin)
Alors la durée exacte écoutée est calculée Et le pourcentage (durée / durée_totale * 100) est envoyé à l'API
Étant donné que j'écoute un contenu de 5 minutes à 80%
Quand l'événement est envoyé à l'API
Alors le backend reçoit: Et le backend calcule le like automatique (+2 points) Et les jauges sont mises à jour immédiatement (Redis + PostgreSQL)
Étant donné que j'écoute un contenu
Quand j'appuie sur "Suivant"
Alors l'action envoyée est "skipped"
Quand le contenu se termine naturellement
Alors l'action envoyée est "completed"
Quand j'appuie sur "Pause"
Alors l'action envoyée est "paused" Et le backend traite chaque action différemment
Étant donné que l'API reçoit un événement d'écoute
Quand le backend traite l'événement
Alors les jauges sont mises à jour immédiatement (< 100ms) Et les nouvelles recommandations utilisent les valeurs actualisées Et il n'y a aucun batch différé
Étant donné que l'app iOS utilise AVPlayer
Quand les commandes physiques sont interceptées
Alors les événements MPRemoteCommandCenter sont capturés:
| Commande | Événement iOS |
|---|---|
| Suivant | nextTrackCommand |
| Précédent | previousTrackCommand |
| Play/Pause | playCommand / pauseCommand |
Et le tracking du temps utilise CMTime
Étant donné que l'app Android utilise MediaPlayer
Quand les commandes physiques sont interceptées
Alors les événements MediaSession sont capturés:
| Commande | Action Android |
|---|---|
| Suivant | ACTION_SKIP_TO_NEXT |
| Précédent | ACTION_SKIP_TO_PREVIOUS |
| Play/Pause | ACTION_PLAY / ACTION_PAUSE |
Et le tracking du temps utilise SystemClock.elapsedRealtime()
Étant donné que je conduis à 80 km/h
Quand j'utilise RoadWave avec les commandes au volant
Alors je n'ai jamais besoin de regarder mon téléphone Et je n'ai jamais besoin de toucher l'écran CarPlay/Android Auto Et toutes les actions sont accessibles via boutons physiques Et les likes sont enregistrés automatiquement
Étant donné que j'écoute un contenu tagué "Sport"
Quand j'écoute pendant
Alors le like automatique est
📊 Exemples de données:
| pourcentage | type | points |
|---|---|---|
| 10 | aucun | 0 |
| 25 | aucun | 0 |
| 29 | aucun | 0 |
| 30 | standard | +1 |
| 50 | standard | +1 |
| 79 | standard | +1 |
| 80 | renforcé | +2 |
| 90 | renforcé | +2 |
| 100 | renforcé | +2 |
Étant donné que j'écoute un contenu
Quand je skip après
Alors le signal est
📊 Exemples de données:
| secondes | type | points |
|---|---|---|
| 3 | négatif | -0.5 |
| 5 | négatif | -0.5 |
| 9 | négatif | -0.5 |
| 10 | neutre | 0 |
| 15 | neutre | 0 |
| 30 | neutre | 0 |
En tant qu'auditeur en déplacement Je veux que l'application pré-calcule intelligemment les prochains contenus Afin d'avoir une navigation fluide sans latence
20 scénarios (19 standards, 1 plan)
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et qu'un utilisateur est connecté Et que la géolocalisation est activée
Étant donné que je viens de démarrer l'application Et que je suis situé à Paris (48.8566, 2.3522) Et que je suis en mode voiture (vitesse ≥ 5 km/h)
Quand l'application initialise la lecture
Alors une file d'attente de 5 contenus est pré-calculée Et la file est stockée en cache Redis avec la clé "user:{user_id}:queue" Et les métadonnées incluent ma position, le timestamp de calcul et le mode Et le cache a un TTL de 15 minutes
Étant donné qu'une file d'attente de 5 contenus est en cache Et que j'écoute actuellement le contenu "A"
Quand j'appuie sur le bouton "Suivant"
Alors le contenu suivant démarre immédiatement (< 100ms) Et le contenu est retiré de la file d'attente Et il reste 4 contenus dans la file
Étant donné que la file a été calculée à Paris (48.8566, 2.3522) Et que j'ai 5 contenus en cache
Quand je me déplace à Versailles (48.8049, 2.1204) soit 12km
Alors la file d'attente est invalidée automatiquement Et une nouvelle file de 5 contenus est recalculée Et elle est basée sur ma nouvelle position
Étant donné qu'une file a été calculée il y a 10 minutes Et que ma position n'a pas changé
Quand le timer de rafraîchissement expire
Alors une nouvelle file de 5 contenus est recalculée Et les anciens contenus non écoutés sont remplacés Et les nouveaux contenus publiés depuis sont inclus
Étant donné qu'il reste 3 contenus dans ma file d'attente
Quand j'appuie sur "Suivant"
Alors il reste 2 contenus Et un recalcul asynchrone est déclenché en arrière-plan Et 3 nouveaux contenus sont ajoutés à la file Et la file contient maintenant 5 contenus
Étant donné que j'ai une file de 5 contenus pré-calculée Et que je suis en mode voiture Et que je me déplace à 50 km/h vers un point avec contenu géolocalisé
Quand je suis à 98m du point (ETA = 7 secondes)
Alors une notification est envoyée (icône + compteur 7→1 + son) Et je dois appuyer sur "Suivant" dans les 7 secondes pour valider
Quand j'appuie sur "Suivant"
Alors un décompte de 5 secondes démarre Et après 5 secondes, le contenu géolocalisé s'insère et démarre Et il remplace le contenu actuel dans la lecture
Étant donné qu'une notification géolocalisée est affichée (compteur 7→1)
Quand je ne clique pas sur "Suivant" pendant les 7 secondes
Alors la notification disparaît Et le contenu géolocalisé est perdu (pas d'insertion dans la file) Et un cooldown de 10 minutes est activé Et aucune nouvelle notification géolocalisée ne sera envoyée pendant 10 minutes
Étant donné qu'une notification géolocalisée est affichée (compteur à 5) Et que j'écoute un podcast
Quand j'appuie sur "Suivant"
Alors le compteur bascule à "5" (décompte final) Et le podcast actuel continue de jouer Et après 5 secondes, le contenu géolocalisé démarre Et le podcast est mis en pause et sauvegardé dans l'historique
Étant donné que j'ai une file de 5 contenus en cache Et que ma vitesse GPS est de 5 km/h (piéton)
Quand je modifie mes curseurs de préférences (géo/découverte/politique)
Alors la file d'attente est invalidée immédiatement Et une nouvelle file est recalculée avec les nouvelles préférences Et les anciens contenus en cache sont supprimés
Étant donné que ma vitesse GPS est de 50 km/h (en voiture)
Quand j'essaie d'accéder aux réglages de préférences
Alors l'interface affiche "Paramètres verrouillés en conduite" Et je ne peux pas modifier les curseurs géo/découverte/politique Et un message "Arrêtez-vous pour modifier vos préférences" s'affiche
Étant donné que je suis abonné au créateur "RadioVoyage" Et que j'ai une file de 5 contenus en cache Et que je suis dans la zone géographique du créateur
Quand le créateur "RadioVoyage" démarre une radio live
Alors je reçois une notification push Et le contenu live s'insère en tête de la file d'attente Et la file d'attente est recalculée
Étant donné qu'une file d'attente est calculée
Quand elle est stockée dans Redis
Alors la clé est "user:{user_id}:queue" Et les métadonnées incluent:
| champ | valeur |
|---|---|
| last_lat | 48.8566 |
| last_lon | 2.3522 |
| computed_at | 2026-01-21T10:30:00Z |
| mode | voiture |
Et le TTL est de 15 minutes (900 secondes)
Étant donné que j'écoute le contenu C2 de ma file [C1, C2, C3, C4, C5] Et qu'une notification géolocalisée "Tour Eiffel" est déclenchée
Quand je valide la notification Et que le décompte de 5s se termine
Alors le contenu "Tour Eiffel" remplace C2 et démarre Et C2 est sauvegardé dans l'historique de navigation Et la file reste [C3, C4, C5] (pas de contenu retiré) Et quand "Tour Eiffel" se termine, C3 démarre
Étant donné que la file a été calculée à une position donnée
Quand je me déplace d'exactement 10.0 km
Alors la file d'attente n'est PAS invalidée (seuil strict >10km) Et les contenus en cache restent valides
Quand je me déplace de 10.1 km supplémentaires (total 10.1km)
Alors la file d'attente est invalidée Et une nouvelle file est calculée
Étant donné qu'une file a été calculée à 10:00:00
Quand l'heure actuelle est 10:10:00
Alors le timer de rafraîchissement expire Et une nouvelle file de 5 contenus est recalculée Et le timestamp "computed_at" est mis à jour
Étant donné qu'il reste 2 contenus dans la file Et que j'appuie sur "Suivant"
Quand le recalcul asynchrone démarre
Alors la lecture du contenu actuel n'est pas interrompue Et le recalcul se fait en arrière-plan Et les nouveaux contenus sont ajoutés dès disponibles (< 500ms) Et l'utilisateur ne perçoit aucune latence
Étant donné qu'un contenu géolocalisé existe à un point GPS Et que je roule à 130 km/h
Quand je suis à 252m du point (ETA = 7 secondes)
Alors une notification est envoyée
Quand je suis à 300m du point (ETA = 8 secondes)
Alors aucune notification n'est envoyée (ETA >7s)
Étant donné qu'une file a été calculée à une position donnée
Quand je me déplace de
Alors la file est
📊 Exemples de données:
| 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 |
Étant donné que j'ai validé 6 notifications géolocalisées dans la dernière heure
Quand un 7ème contenu géolocalisé est détecté (ETA 7s)
Alors aucune notification n'est envoyée Et le quota horaire est respecté
Étant donné que je suis en mode piéton (vitesse <5 km/h) Et qu'un audio-guide géolocalisé existe à 150m
Quand je passe dans le rayon de 200m
Alors une notification push système est envoyée Et aucun compteur 7s n'est affiché Et je peux ouvrir l'app en tapant sur la notification
En tant qu'auditeur Je veux que les contenus s'enchaînent automatiquement avec un délai paramétrable Afin d'avoir une expérience fluide sans interruption
27 scénarios (24 standards, 3 plans)
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et qu'un utilisateur est connecté
Étant donné que j'écoute un contenu "A" en mode standard
Quand la lecture se termine naturellement
Alors un timer de 2 secondes démarre Et un overlay s'affiche: "Contenu suivant dans 2s..." Et une barre de décompte visuelle s'affiche
Quand le timer atteint 0
Alors le contenu "B" démarre automatiquement Et l'overlay disparaît
Étant donné que je suis en mode Kids Et que j'écoute un contenu pour enfants
Quand la lecture se termine
Alors un timer de 1 seconde démarre Et le message "Contenu suivant dans 1s..." s'affiche
Quand le timer expire
Alors le contenu suivant démarre automatiquement
Étant donné que j'écoute une radio live
Quand le créateur arrête la diffusion
Alors le passage au contenu suivant est immédiat (0s de délai) Et aucun overlay de décompte n'est affiché Et la transition est fluide
Étant donné qu'un contenu se termine Et que le timer de 2 secondes démarre
Quand je clique sur "Rester sur ce contenu" pendant le décompte
Alors le timer est annulé Et le contenu actuel reste en pause à la fin Et le contenu suivant n'est pas lancé
Étant donné que j'ai écouté 4 contenus sans publicité Et que le 5ème contenu se termine
Quand le délai de 2 secondes démarre
Alors une publicité s'insère dans la file d'attente Et le message devient "Publicité (15s)" Et la publicité démarre après les 2 secondes Et elle ne coupe jamais un contenu en cours
Étant donné que la fréquence pub est configurée à "1/5 contenus"
Quand j'écoute 10 contenus
Alors 2 publicités sont insérées (après les contenus 5 et 10)
Étant donné que l'admin change la fréquence à "1/3 contenus"
Quand j'écoute 9 contenus
Alors 3 publicités sont insérées (après les contenus 3, 6 et 9)
Étant donné qu'une publicité de 30 secondes démarre Et que le délai minimal de visionnage est configuré à 5 secondes
Quand j'écoute pendant 3 secondes
Alors le bouton "Passer" n'est pas encore visible
Quand j'atteins 5 secondes d'écoute
Alors le bouton "Passer" apparaît Et je peux cliquer pour passer au contenu suivant
Étant donné qu'une publicité démarre Et que l'admin a configuré le délai à 10 secondes
Quand j'écoute pendant 9 secondes
Alors le bouton "Passer" n'est pas visible
Quand j'atteins 10 secondes
Alors le bouton "Passer" apparaît Et je peux skipper la publicité
Étant donné qu'une publicité est en lecture
Quand je clique sur le bouton cœur (véhicule arrêté)
Alors la publicité reçoit un like (+2% jauges tags pub)
Quand je clique sur "S'abonner" au créateur de la pub
Alors je suis abonné (+5% jauges tags créateur) Et le créateur de pub bénéficie de l'engagement
Étant donné qu'une publicité de 30s est diffusée à 100 auditeurs
Quand 40 auditeurs écoutent entièrement (30s) Et que 50 auditeurs skippent après 10s Et que 10 auditeurs skippent avant 5s
Alors les métriques sont:
| Métrique | Valeur |
|---|---|
| Taux d'écoute complète | 40% |
| Taux de skip après seuil | 50% |
| Taux de skip immédiat | 10% |
| Durée moyenne d'écoute | 18s |
Étant donné que la file d'attente est vide Et qu'aucun contenu n'est disponible dans ma zone
Quand le contenu actuel se termine
Alors un message s'affiche: "Aucun contenu disponible dans cette zone" Et une proposition apparaît: "Élargir la zone de recherche ?" Et un bouton "Élargir" est disponible Et la lecture se met en pause automatiquement
Étant donné que le message "Aucun contenu disponible" s'affiche
Quand je clique sur "Élargir la zone"
Alors l'algorithme relance une recherche avec rayon +50km Et une notification "Recherche élargie à 50km" s'affiche Et la file d'attente est recalculée Et la lecture reprend automatiquement
Étant donné que le message "Aucun contenu disponible" s'affiche
Quand je clique sur "Annuler"
Alors la lecture reste en pause Et l'interface affiche "En attente de contenu" Et je peux manuellement naviguer ou chercher du contenu
Étant donné que le contenu suivant échoue au chargement
Quand la première tentative échoue
Alors le système retente après 1 seconde (backoff 1s)
Quand la 2ème tentative échoue
Alors le système retente après 2 secondes (backoff 2s)
Quand la 3ème tentative échoue
Alors le système retente après 4 secondes (backoff 4s) Et après 3 échecs totaux, le système bascule en mode offline
Étant donné que j'ai eu 3 échecs de chargement consécutifs
Quand le 3ème échec se produit
Alors un message "Connexion instable, basculement mode offline" s'affiche Et la lecture continue avec les contenus téléchargés uniquement Et les contenus en ligne sont temporairement désactivés
Quand la connexion revient
Alors le mode en ligne est automatiquement rétabli
Étant donné qu'un contenu se termine
Quand le timer de 2 secondes démarre
Alors un overlay semi-transparent s'affiche en bas de l'écran Et le texte "Contenu suivant dans 2s..." est visible Et une barre de progression décroît de 100% à 0% en 2 secondes Et la couleur de la barre passe de vert à orange Et l'overlay disparaît automatiquement après le décompte
Étant donné que le décompte de 2 secondes est actif
Quand l'overlay s'affiche
Alors un bouton "Rester sur ce contenu" est visible Et il est cliquable pendant les 2 secondes
Quand je clique dessus
Alors le timer est annulé immédiatement Et l'overlay disparaît Et le contenu actuel reste affiché en pause
Étant donné que j'écoute un contenu de 10 minutes Et que je suis à 5 minutes de lecture
Quand une publicité devrait s'insérer (fréquence 1/5)
Alors la publicité n'interrompt jamais le contenu en cours Et elle attend la fin du contenu actuel Et elle s'insère pendant le délai de transition (2s)
Étant donné que je suis un utilisateur gratuit
Quand j'écoute 5 contenus
Alors une publicité est insérée après le 5ème contenu
Étant donné que je passe en compte Premium
Quand j'écoute 100 contenus
Alors aucune publicité n'est insérée Et l'enchaînement est direct (2s de transition seulement)
Étant donné qu'une publicité va démarrer
Quand le délai de transition démarre
Alors le message affiché est: "Publicité (15s)" Et la durée totale de la pub est indiquée Et l'utilisateur sait qu'il s'agit d'une pub Et la transparence est maximale
Étant donné qu'un contenu se termine Et que le suivant est pré-chargé en cache
Quand le timer de 2s expire
Alors la transition audio utilise un crossfade de 0.3s Et il n'y a aucun blanc ou coupure Et l'expérience est fluide
Étant donné que le contenu suivant échoue au chargement
Quand la 1ère tentative échoue
Alors une notification "Chargement..." s'affiche Et le système retente automatiquement
Quand la 2ème tentative réussit
Alors la lecture démarre normalement Et aucune action utilisateur n'est requise
Étant donné que j'ai 50 contenus téléchargés en mode offline Et que j'ai eu 3 échecs réseau consécutifs
Quand le mode offline s'active
Alors seuls les contenus téléchargés sont disponibles Et un badge "Mode offline" s'affiche en haut de l'écran Et la lecture continue sans interruption
Étant donné que la fréquence pub est 1/5 contenus Et que j'ai écouté 3 contenus depuis la dernière pub
Quand je consulte l'interface
Alors un indicateur discret affiche "2 contenus avant pub" Et l'utilisateur sait quand attendre la prochaine publicité
Étant donné que je suis en mode
Quand un contenu se termine
Alors le délai de transition est
📊 Exemples de données:
| mode | delai | message |
|---|---|---|
| Standard | 2 | "Contenu suivant dans 2s..." |
| Kids | 1 | "Contenu suivant dans 1s..." |
| Live | 0 | (aucun message) |
Étant donné que la fréquence pub est configurée à
Quand j'écoute
Alors
📊 Exemples de données:
| frequence | contenus | pubs |
|---|---|---|
| 1/3 | 6 | 2 |
| 1/3 | 9 | 3 |
| 1/5 | 10 | 2 |
| 1/5 | 15 | 3 |
| 1/7 | 14 | 2 |
| 1/7 | 21 | 3 |
Étant donné que le chargement échoue
Quand je suis à la tentative
Alors le délai de retry est
📊 Exemples de données:
| tentative | delai |
|---|---|
| 1 | 1 |
| 2 | 2 |
| 3 | 4 |
En tant qu'utilisateur de RoadWave Je veux pouvoir partager du contenu audio Afin de faire découvrir l'application à d'autres personnes
22 scénarios (20 standards, 2 plans)
Contexte commun à tous les scénarios
Étant donné que l'application RoadWave est démarrée Et que l'utilisateur "jean@example.com" est connecté
Étant donné que le contenu "Balade à Paris" est en cours de lecture
Quand l'utilisateur consulte les contrôles du player
Alors le bouton "Partager" ⬆️ est visible
Étant donné que l'utilisateur consulte le profil de "@paris_stories"
Quand l'utilisateur consulte un contenu dans la liste
Alors le bouton "Partager" est disponible pour chaque contenu
Étant donné que l'utilisateur effectue une recherche "voyage paris"
Quand l'utilisateur ouvre le menu contextuel d'un résultat
Alors l'option "Partager" est disponible
Étant donné que l'utilisateur consulte son historique d'écoute
Quand l'utilisateur sélectionne un contenu de l'historique
Alors le bouton "Partager" est accessible
Étant donné que le contenu "
Quand l'utilisateur clique sur le bouton "Partager"
Alors le menu natif OS s'ouvre Et les options suivantes sont disponibles:
| option |
|---|
| Copier le lien |
| WhatsApp |
| Email |
| SMS |
| Plus... |
📊 Exemples de données:
| contenu |
|---|
| Balade à Paris |
| Secrets de Montmartre |
Étant donné un contenu avec l'ID "content_12345"
Quand l'utilisateur copie le lien de partage
Alors le lien généré est "https://roadwave.fr/share/c/content_12345"
Étant donné que l'application RoadWave est installée sur l'appareil Et qu'un lien "https://roadwave.fr/share/c/content_12345" est partagé
Quand l'utilisateur clique sur le lien
Alors l'application RoadWave s'ouvre automatiquement Et le contenu "content_12345" commence à jouer
Étant donné que l'application RoadWave n'est pas installée Et qu'un lien "https://roadwave.fr/share/c/content_12345" est partagé
Quand l'utilisateur clique sur le lien
Alors une page web responsive s'affiche Et le web player HTML5 est visible Et les boutons de téléchargement App Store et Google Play sont affichés
Étant donné un contenu public avec les métadonnées suivantes:
| champ | valeur |
|---|---|
| titre | Balade à Paris |
| créateur | @paris_stories |
| durée | 12 min |
| écoutes | 2300 |
| localisation | Paris 5e |
| type_geo | Ancré |
| tags | Voyage, Histoire |
Quand la page de partage est affichée
Alors la page contient:
| élément |
|---|
| Cover image 16:9 |
| Titre "Balade à Paris" |
| "@paris_stories" |
| "12 min · 🎧 2.3K" |
| "📍 Paris 5e · Ancré" |
| "🏷️ #Voyage #Histoire" |
| Description |
| Player HTML5 |
| Bouton App Store |
| Bouton Google Play |
Étant donné un contenu "Balade à Paris" par "@paris_stories"
Quand la page de partage est générée
Alors les métadonnées Open Graph incluent:
| propriété | valeur |
|---|---|
| og:title | Balade à Paris - RoadWave |
| og:description | Écoutez ce contenu par @paris_stories |
| og:type | music.song |
| og:site_name | RoadWave |
| twitter:card | player |
Et l'aperçu s'affiche correctement sur WhatsApp Et l'aperçu s'affiche correctement sur Facebook Et l'aperçu s'affiche correctement sur Twitter
Étant donné que l'application RoadWave est installée sur
Quand le système détecte l'application
Alors l'application s'ouvre via
📊 Exemples de données:
| plateforme | mécanisme |
|---|---|
| iOS | Universal Links |
| Android | App Links |
Étant donné que les App Links ne fonctionnent pas
Quand le système tente d'ouvrir le contenu
Alors l'URL scheme "roadwave://content/content_12345" est utilisé
Étant donné un contenu Premium "Visite VIP Louvre"
Quand l'utilisateur non-premium clique sur le lien partagé
Alors la page web affiche le badge "👑 Contenu Premium"
Étant donné un contenu Premium "Visite VIP Louvre" de 15 minutes Et qu'un utilisateur non-premium ouvre le lien partagé
Quand le player démarre automatiquement
Alors l'audio joue pendant 30 secondes exactement Et un fade out de 2 secondes est appliqué Et un overlay "Contenu réservé Premium" s'affiche après 32 secondes
Étant donné qu'un contenu Premium a atteint la limite de 30 secondes
Quand l'overlay paywall s'affiche
Alors le texte suivant est visible:
Étant donné que l'overlay paywall Premium est affiché
Quand l'utilisateur consulte les options
Alors les actions suivantes sont disponibles:
| action | comportement |
|---|---|
| Passer Premium | Redirection vers paiement Mangopay web |
| Télécharger l'app | Redirection vers App Store/Google Play |
| Rejouer les 30 premières sec | Relecture illimitée du preview |
Étant donné un contenu Premium partagé Et que l'utilisateur a écouté les 30 premières secondes
Quand l'utilisateur clique sur "Rejouer"
Alors les 30 premières secondes sont rejouées Et cette action est possible de manière illimitée
Étant donné un créateur "@guide_louvre" avec un contenu Premium
Quand son contenu est partagé
Alors les métriques suivantes sont enregistrées:
| métrique | valeur |
|---|---|
| Partages Premium | +1 |
| Ouvertures lien | compteur |
| Conversions Premium | si souscription |
Étant donné un contenu Premium partagé par "@guide_louvre"
Quand un utilisateur s'abonne via le lien partagé
Alors le créateur reçoit 70% des revenus de cet abonnement Et la conversion est trackée dans son dashboard
Étant donné qu'un lien de partage "https://roadwave.fr/share/c/deleted_content" est ouvert Et que le contenu n'existe plus
Quand la page web se charge
Alors un message "Ce contenu n'est plus disponible" s'affiche Et les boutons de téléchargement de l'app sont affichés
Étant donné un contenu en cours de validation modération
Quand un lien de partage est ouvert
Alors le message "Ce contenu est en cours de validation" s'affiche
Étant donné que l'utilisateur n'a pas de connexion réseau
Quand l'utilisateur tente de partager un contenu
Alors le lien est copié dans le presse-papiers Et un message "Lien copié (nécessite connexion pour ouvrir)" s'affiche
En tant qu'abonné Premium Je veux bénéficier d'avantages exclusifs Afin de profiter d'une expérience audio améliorée sans publicité
37 scénarios
Contexte commun à tous les scénarios
Étant donné que je suis connecté à l'application RoadWave
Étant donné que je suis un utilisateur gratuit
Quand j'écoute ma file de contenus
Alors je vois une publicité tous les 5 contenus Et la publicité dure 30 secondes en moyenne Et je ne peux pas la skip
Étant donné que je suis un utilisateur Premium
Quand j'écoute mes contenus
Alors aucune publicité n'est diffusée Et je passe directement d'un contenu à l'autre Et l'expérience d'écoute est fluide et ininterrompue
Étant donné que je consulte la page des avantages Premium
Quand je lis la liste des avantages
Alors je vois en premier: Et c'est l'argument principal mis en avant
Étant donné que je suis un utilisateur gratuit
Quand je consulte les contenus d'un créateur
Alors je vois les contenus marqués Premium avec badge 👑 Mais je ne peux pas les lire (overlay bloquant)
Étant donné que je suis un utilisateur Premium
Quand je consulte les contenus d'un créateur
Alors tous les contenus Premium sont accessibles Et je peux les lire sans restriction Et j'ai accès à 100% du catalogue (gratuit + Premium)
Étant donné que je suis Premium
Quand je consulte les statistiques
Alors je vois combien de contenus Premium sont disponibles sur la plateforme Et par exemple: "8,547 contenus Premium exclusifs disponibles" Et cela justifie la valeur de l'abonnement
Étant donné que je suis un utilisateur gratuit
Quand je lance un contenu
Alors l'audio est streamé en 48 kbps Opus Et cela consomme environ 20 MB/heure Et la qualité est très correcte pour de la voix
Étant donné que je suis un utilisateur Premium
Quand je lance un contenu
Alors l'audio est streamé en 64 kbps Opus Et cela consomme environ 30 MB/heure Et la qualité est excellente (détails audio supérieurs)
Étant donné que je consulte la page Premium
Quand je lis la section qualité audio
Alors je vois l'explication:
Étant donné que le contenu RoadWave est principalement de la voix
Quand la qualité est fixée à 48 kbps pour gratuit
Alors c'est largement suffisant pour comprendre clairement Et équivalent à la qualité radio FM Et les utilisateurs gratuits ne sont pas frustrés
Étant donné que les audiophiles et créateurs audio sont exigeants
Quand la qualité Premium est à 64 kbps
Alors la différence est perceptible à l'oreille Et les ambiances, musiques de fond, nuances de voix sont mieux rendues Et cela justifie l'abonnement Premium
Étant donné que je suis gratuit et j'écoute en 48 kbps
Quand je souscris à Premium
Alors dès le contenu suivant, je passe automatiquement en 64 kbps Et je peux entendre la différence de qualité immédiatement
Étant donné que je roule 1 heure par jour
Quand je calcule la consommation mensuelle
Alors en gratuit: 20 MB/h × 1h × 22 jours = 440 MB/mois Et en Premium: 30 MB/h × 1h × 22 jours = 660 MB/mois Et la différence est de 220 MB/mois (acceptable pour 4G/5G illimitée)
Étant donné que je suis un utilisateur gratuit
Quand j'accède au mode offline
Alors je peux télécharger jusqu'à 50 contenus maximum Et si j'essaie de télécharger un 51ème, je vois:
Étant donné que je suis un utilisateur Premium
Quand j'accède au mode offline
Alors je peux télécharger autant de contenus que je veux Et la seule limite est l'espace de stockage de mon device Et par exemple 500 contenus × 10 MB = 5 GB
Étant donné que 50 contenus de 10 minutes = ~8 heures d'écoute
Quand un utilisateur gratuit prépare un road trip
Alors 8 heures couvrent largement une journée de trajet Et cela permet un usage offline raisonnable sans abuser
Étant donné qu'un road trip de plusieurs jours nécessite 20-50h de contenu
Quand un utilisateur Premium télécharge 200 contenus
Alors il peut partir serein sans connexion internet pendant 1 semaine Et cela justifie pleinement l'abonnement Premium
Étant donné que je suis gratuit et j'ai téléchargé 37 contenus
Quand j'accède à la page Téléchargements
Alors je vois:
Étant donné que je suis Premium et j'ai téléchargé 187 contenus
Quand j'accède à la page Téléchargements
Alors je vois simplement: Et aucune limite n'est affichée
Étant donné que je suis un utilisateur gratuit
Quand j'accède à mon historique d'écoute
Alors je vois les 100 derniers contenus écoutés Et les contenus plus anciens ne sont pas affichés Et je vois un message "Historique limité à 100 contenus. Passez Premium pour un historique illimité."
Étant donné que je suis un utilisateur Premium
Quand j'accède à mon historique d'écoute
Alors je vois tous les contenus que j'ai écoutés depuis mon inscription Et je peux scroller jusqu'au premier contenu jamais écouté Et l'historique est complet et permanent
Étant donné que je suis Premium et j'ai 2 000 contenus dans mon historique
Quand je recherche "Tesla" dans mon historique
Alors tous les contenus écoutés contenant "Tesla" sont affichés Et je peux retrouver facilement un contenu écouté il y a 6 mois
Étant donné que 100 contenus de 10 min = ~16 heures d'écoute
Quand un utilisateur gratuit écoute 1h/jour
Alors l'historique couvre les 16 derniers jours Et cela suffit pour retrouver un contenu récent
Étant donné qu'un power user écoute 3h/jour depuis 2 ans
Quand il veut retrouver un contenu spécifique écouté il y a 1 an
Alors l'historique illimité Premium lui permet de retrouver ce contenu Et cela apporte une vraie valeur ajoutée
Étant donné que je suis Premium
Quand je demande l'export de mes données
Alors l'historique complet est inclus dans l'export:
Étant donné que je consulte la page Premium
Quand je vois le tableau comparatif
Alors il affiche:
Étant donné qu'une publicité de 30s tous les 5 contenus = 6 min/h de pub
Quand un utilisateur écoute 1h/jour
Alors il subit 180 min de pub/mois (3 heures !) Et payer 4.99€ pour éviter 3h de pub/mois est très rentable Et c'est l'argument de conversion n°1
Étant donné que la différence 48 kbps → 64 kbps est audible
Quand un audiophile compare les deux
Alors il entend clairement la différence sur un bon système audio voiture Et cela justifie l'abonnement pour les exigeants
Étant donné qu'un road trip de 2 semaines nécessite 50-100h de contenu
Quand un utilisateur Premium télécharge 300 contenus avant de partir
Alors il peut partir en zone sans réseau sereinement Et cela apporte une vraie valeur pratique
Étant donné que RoadWave se concentre sur l'essentiel
Quand les avantages Premium sont définis
Alors il n'y a pas de:
| fonctionnalité superflue | raison exclusion |
|---|---|
| Badges cosmétiques | Pas de valeur réelle |
| Avatar Premium exclusif | Inutile pour audio |
| Fonctionnalités sociales avancées | Pas prioritaire MVP |
| Early access nouveaux contenus | Complexité > bénéfice |
Et cela réduit la complexité et le coût de développement
Étant donné que je suis gratuit et je viens d'entendre ma 5ème pub
Quand la publicité se termine
Alors je vois un message:
Étant donné que je suis gratuit et j'ai atteint 50 téléchargements
Quand j'essaie de télécharger un 51ème contenu
Alors je vois une popup:
Étant donné que je suis gratuit et je clique sur un contenu Premium
Quand l'overlay bloquant apparaît
Alors je vois:
Étant donné qu'un admin consulte les statistiques de conversion
Quand il analyse les sources de conversion
Alors il voit:
| source de conversion | % conversions |
|---|---|
| CTA après 5ème pub | 42% |
| CTA contenu Premium bloqué | 28% |
| CTA limite 50 téléchargements | 18% |
| Page Premium directe | 12% |
Et cela aide à optimiser le placement des CTA
Étant donné que RoadWave veut optimiser les conversions
Quand un A/B test est lancé sur le CTA après pub
Alors groupe A voit "Marre des pubs ?" (focus négatif) Et groupe B voit "Profitez de 0 publicité" (focus positif) Et le taux de conversion est mesuré Et le message le plus performant est déployé
Étant donné que je suis utilisateur gratuit depuis 30 jours Et que j'écoute régulièrement (15h cumulées)
Quand le 30ème jour arrive
Alors je reçois une notification:
Étant donné qu'il n'y a pas de trial gratuit
Quand un nouvel utilisateur s'inscrit
Alors un onboarding explique clairement les avantages Premium Et il peut comparer gratuit vs Premium dès le premier lancement Et cela l'aide à décider rapidement s'il veut payer
En tant qu'utilisateur Je veux gérer facilement mon abonnement Premium Afin de souscrire, renouveler ou annuler en toute transparence
41 scénarios
Contexte commun à tous les scénarios
Étant donné que je suis connecté à l'application RoadWave
Étant donné que je consulte la page Premium sur le site web
Quand je clique sur "S'abonner - Mensuel 4.99€"
Alors je suis redirigé vers le formulaire de paiement Mangopay Et je saisis mes informations de carte bancaire Et le paiement de 4.99€ est prélevé immédiatement Et la commission Mangopay est de 1.8% + 0.18€ = 0.27€ Et RoadWave reçoit 4.72€ net
Étant donné qu'un utilisateur paie 4.99€ via Mangopay
Quand la commission est calculée
Alors la commission est : 4.99€ × 1.8% + 0.18€ = 0.09€ + 0.18€ = 0.27€ Et RoadWave reçoit : 4.99€ - 0.27€ = 4.72€ Et la commission représente 5.4% du prix
Étant donné que j'utilise l'app iOS
Quand je clique sur "S'abonner - Mensuel 5.99€"
Alors je suis redirigé vers l'interface Apple In-App Purchase Et le prix affiché est 5.99€ (majoré de 20%) Et le paiement est effectué via mon compte Apple Et Apple prend 30% de commission = 1.80€ Et RoadWave reçoit 4.19€ net
Étant donné que j'utilise l'app Android
Quand je clique sur "S'abonner - Mensuel 5.99€"
Alors je suis redirigé vers l'interface Google Play Billing Et le prix affiché est 5.99€ (majoré de 20%) Et le paiement est effectué via mon compte Google Et Google prend 30% de commission = 1.80€ Et RoadWave reçoit 4.19€ net
Étant donné que Apple/Google prennent 30% de commission
Quand RoadWave fixe le prix mobile
Alors le prix web est 4.99€ (commission Mangopay 5.4%) Et le prix mobile est 5.99€ (commission Apple/Google 30%) Et la majoration est de 1€ (+20%) Et cela compense partiellement la commission excessive
Étant donné que je consulte Premium depuis l'app mobile
Quand je vois le prix 5.99€
Alors je vois aussi un message: Et un lien vers le site web est fourni
Étant donné que le prix web est 4.99€/mois Et que le prix mobile est 5.99€/mois
Quand je calcule l'économie annuelle
Alors web : 4.99€ × 12 = 59.88€/an Et mobile : 5.99€ × 12 = 71.88€/an Et économie : 12€/an (soit 20% d'économie)
Étant donné que je viens de payer mon abonnement Premium
Quand le paiement est confirmé
Alors mon statut passe immédiatement à "Premium" Et je peux accéder aux avantages Premium dès maintenant Et je reçois un email de confirmation
Étant donné que j'ai souscrit à Premium
Quand la souscription est confirmée
Alors je reçois un email:
Étant donné que mon abonnement mensuel se renouvelle le 15 juillet
Quand le 8 juillet arrive (7 jours avant)
Alors je reçois un email de rappel:
Étant donné que mon abonnement mensuel arrive à échéance le 15 juillet
Quand le 15 juillet arrive
Alors Mangopay/Apple/Google prélève automatiquement 4.99€ (ou 5.99€) Et mon abonnement est renouvelé pour 1 mois supplémentaire Et je reçois un email de confirmation
Étant donné que mon abonnement vient d'être renouvelé
Quand le paiement est confirmé
Alors je reçois un email:
Étant donné que mon abonnement doit se renouveler le 15 juillet Mais que ma carte bancaire est expirée ou sans fonds
Quand le prélèvement échoue
Alors je reçois un email:
Étant donné que le paiement a échoué le 15 juillet
Quand le 18 juillet arrive (J+3)
Alors Mangopay/Apple/Google tente automatiquement un nouveau prélèvement Et si le paiement réussit, l'abonnement est renouvelé normalement Et si le paiement échoue encore, un 2ème retry est programmé
Étant donné que 2 tentatives ont échoué (15 juillet et 18 juillet)
Quand le 22 juillet arrive (J+7)
Alors une 3ème et dernière tentative est effectuée Et si le paiement réussit, l'abonnement est sauvé Et si le paiement échoue, l'abonnement est annulé automatiquement
Étant donné que les 3 tentatives de renouvellement ont échoué (J+0, J+3, J+7)
Quand la 3ème tentative échoue
Alors mon abonnement Premium est annulé automatiquement Et mon statut repasse à "Gratuit" Et je perds accès aux avantages Premium Et je reçois un email d'annulation
Étant donné que mon abonnement a été annulé pour échec paiement
Quand l'annulation devient effective
Alors je reçois un email:
Étant donné que je veux annuler mon abonnement
Quand j'accède à "Paramètres > Abonnement"
Alors je vois un bouton "Annuler l'abonnement" Et je peux annuler en 2 clics sans contacter le support
Étant donné que je clique sur "Annuler l'abonnement"
Quand une popup de confirmation apparaît
Alors je vois:
Étant donné que j'ai annulé mon abonnement le 1er juillet Et que mon abonnement mensuel était valable jusqu'au 15 juillet
Quand l'annulation est confirmée
Alors je garde l'accès Premium jusqu'au 15 juillet Et à partir du 16 juillet, je repasse en gratuit Et je ne suis pas remboursé pour les 14 jours restants
Étant donné que l'industrie (Spotify, Netflix, YouTube) ne rembourse pas prorata
Quand RoadWave applique la même règle
Alors c'est le standard accepté par les utilisateurs Et cela simplifie la gestion comptable Et évite les abus (souscription puis annulation immédiate pour remboursement)
Étant donné que j'ai annulé mon abonnement
Quand l'annulation est enregistrée
Alors je reçois un email:
Étant donné que j'ai annulé mon abonnement le 1er juillet
Quand le 15 juillet arrive (date de renouvellement prévue)
Alors aucun prélèvement n'est effectué Et mon statut passe automatiquement à "Gratuit" Et je ne reçois pas d'email de renouvellement
Étant donné que j'ai annulé mon abonnement il y a 5 jours
Quand j'accède à la page Premium
Alors je peux me réabonner immédiatement Et le processus de paiement est le même que la première fois
Étant donné que j'ai annulé mon abonnement il y a 3 mois
Quand je me réabonne
Alors je paie immédiatement 4.99€ (pas d'essai gratuit)
Étant donné que j'ai annulé mon abonnement il y a 1 mois
Quand je reçois un email de win-back
Alors je vois une offre spéciale:
Étant donné qu'un utilisateur souscrit à Premium
Quand les données sont enregistrées
Alors la table subscriptions contient:
Étant donné qu'un abonnement peut avoir différents statuts
Quand le statut est stocké en base
Alors les valeurs possibles sont:
| statut | description |
|---|---|
| active | Abonnement actif et payé |
| cancelled | Annulé par utilisateur (accès jusqu'à fin période) |
| expired | Période terminée, pas renouvelé |
| past_due | Échec paiement, en retry automatique |
Étant donné qu'un utilisateur lance un contenu
Quand l'app vérifie s'il est Premium
Alors une clé Redis est consultée: Et si la clé n'existe pas, elle est recalculée depuis PostgreSQL Et cela garantit des performances <10ms
Étant donné qu'un paiement est confirmé par Mangopay/Apple/Google
Quand un webhook est reçu par RoadWave
Alors le cache Redis premium:{user_id} est mis à jour immédiatement Et l'utilisateur voit son statut Premium activé sans délai
Étant donné qu'un paiement Mangopay réussit
Quand Mangopay envoie le webhook PAYIN_NORMAL_SUCCEEDED
Alors RoadWave met à jour subscriptions.status = 'active' Et met à jour current_period_end = NOW() + 1 mois Et refresh le cache Redis premium:{user_id} = true
Étant donné qu'un paiement Mangopay échoue
Quand Mangopay envoie le webhook PAYIN_NORMAL_FAILED
Alors RoadWave met à jour subscriptions.status = 'past_due' Et programme un retry automatique dans 3 jours Et envoie un email à l'utilisateur
Étant donné qu'un paiement Apple IAP change de statut
Quand Apple envoie une notification serveur
Alors RoadWave parse la notification (JSON) Et met à jour la subscription en conséquence Et refresh le cache Redis
Étant donné qu'un paiement Google Play change de statut
Quand Google envoie une notification temps réel
Alors RoadWave parse la notification (JSON) Et met à jour la subscription en conséquence Et refresh le cache Redis
Étant donné qu'un admin consulte les métriques Premium
Quand il accède au dashboard
Alors il voit:
| métrique | valeur |
|---|---|
| Abonnés actifs | 12,547 |
| Nouveaux abonnements ce mois | 1,234 |
| Annulations ce mois | 287 (2.3%) |
| Churn rate mensuel | 2.3% |
| MRR (Revenus mensuels récurrents) | 58,890€ |
| Taux conversion gratuit → Premium | 8.5% |
Étant donné que 287 utilisateurs ont annulé ce mois Et qu'il y avait 12,547 abonnés au début du mois
Quand le churn rate est calculé
Alors churn = 287 / 12,547 = 2.3% Et un churn <5% est considéré comme excellent Et RoadWave surveille cette métrique de près
Étant donné que le churn rate mensuel dépasse 5%
Quand le système détecte cette anomalie
Alors une alerte est envoyée à l'équipe:
Étant donné que je viens d'annuler mon abonnement
Quand l'annulation est confirmée
Alors je vois un questionnaire rapide: Et les réponses aident à améliorer l'offre Premium
Étant donné qu'un admin analyse les canaux de souscription
Quand il consulte les statistiques
Alors il voit:
| canal | abonnés | % total | revenus/mois |
|---|---|---|---|
| Web (Mangopay) | 8,234 | 65.6% | 41,088€ |
| iOS (Apple) | 2,845 | 22.7% | 17,042€ |
| Android (Google) | 1,468 | 11.7% | 8,793€ |
Et cela aide à orienter les efforts marketing (inciter web = moins de commission)
Étant donné que 100 000 utilisateurs consultent des contenus simultanément
Quand chaque requête vérifie le statut Premium via Redis
Alors le temps de réponse moyen est <10ms Et Redis gère facilement 100 000 requêtes/seconde Et l'expérience utilisateur est fluide
Étant donné que les données d'abonnements sont critiques
Quand un backup est effectué
Alors PostgreSQL est répliqué en temps réel sur un replica Et un snapshot quotidien est stocké sur S3 Et en cas de crash, les données peuvent être restaurées <5 minutes
En tant qu'abonné Premium Je veux utiliser mon compte sur plusieurs appareils Mais limité à 1 seul stream actif à la fois pour éviter le partage abusif
30 scénarios
Contexte commun à tous les scénarios
Étant donné que je suis un utilisateur Premium actif Et que mon compte est valide
Étant donné que je n'écoute rien actuellement
Quand je lance un contenu sur mon iPhone
Alors le stream démarre normalement Et Redis enregistre: active_streams:{user_id} = {device_id: "iPhone", started_at: timestamp}
Étant donné que j'écoute un contenu sur mon iPhone
Quand je lance un contenu sur mon iPad
Alors le système détecte une session active sur iPhone Et la lecture sur iPhone est arrêtée immédiatement (WebSocket close) Et je vois sur iPhone: "Lecture interrompue : votre compte est utilisé sur un autre appareil" Et la lecture démarre sur iPad normalement
Étant donné que ma lecture sur iPhone vient d'être interrompue
Quand je regarde l'écran de mon iPhone
Alors je vois une overlay avec le message: Et un bouton "Reprendre ici" est disponible
Étant donné que ma lecture sur iPhone a été interrompue Et que je veux reprendre sur iPhone
Quand je clique sur "Reprendre ici"
Alors la lecture démarre sur iPhone Et l'iPad est à son tour interrompu avec le même message Et le "ping-pong" entre devices est possible (mais pénible)
Étant donné que je lance un contenu sur mon iPhone
Quand la lecture démarre
Alors une entrée Redis est créée:
Étant donné que j'écoute un contenu sur mon iPhone
Quand 30 secondes s'écoulent
Alors l'app envoie un heartbeat au serveur Et le serveur refresh le TTL Redis à 300 secondes Et la session reste active
Étant donné que j'écoute un contenu sur mon iPhone Mais que l'app crash ou que le réseau coupe
Quand 5 minutes s'écoulent sans heartbeat
Alors l'entrée Redis expire automatiquement (TTL atteint) Et je peux relancer sur n'importe quel device sans conflit
Étant donné que je veux lancer un contenu sur mon iPad
Quand j'appuie sur Play
Alors le serveur vérifie Redis: active_streams:{user_id} Et si une session existe sur un autre device, elle est tuée Et la nouvelle session iPad est enregistrée dans Redis
Étant donné que 100 000 utilisateurs Premium écoutent simultanément
Quand Redis stocke 100 000 entrées active_streams
Alors chaque entrée a un TTL de 5 minutes Et Redis gère facilement cette charge (~10 MB de RAM) Et les vérifications sont quasi-instantanées (O(1))
Étant donné que j'ai téléchargé 20 contenus en mode offline
Quand j'écoute un contenu téléchargé sur mon iPhone sans réseau
Alors aucune session active n'est enregistrée dans Redis Et je peux écouter offline pendant qu'un autre device stream online
Étant donné que j'écoute dans ma voiture sur mon iPhone Et que j'arrive chez moi
Quand je lance la lecture sur mon iPad dans les 10 secondes
Alors la transition est considérée comme un changement de device légitime Et aucun message d'erreur n'est affiché sur iPhone Et la lecture reprend exactement où j'étais sur iPad
Étant donné que la session iPhone a started_at = 14:30:00
Quand je lance sur iPad à 14:30:05 (5 secondes après)
Alors le serveur détecte: diff = 5s < 10s Et applique une "graceful transition" (pas de message d'erreur iPhone) Et Redis met à jour: active_streams:{user_id} = {device_id: "iPad", ...}
Étant donné que je possède:
| device | status |
|---|---|
| iPhone | Installé |
| iPad | Installé |
| MacBook (web) | Connecté |
| Android (conjoint) | Installé |
Quand je lance un stream sur n'importe quel device
Alors seulement 1 peut être actif à la fois Et les autres devices sont en "standby"
Étant donné qu'un utilisateur Premium partage son compte avec un ami
Quand les 2 personnes essaient d'écouter simultanément
Alors la lecture est constamment interrompue sur l'un ou l'autre Et l'expérience devient inutilisable Et cela décourage fortement le partage de compte
Étant donné que 1 abonnement Premium = 4.99€/mois
Quand 70% sont reversés aux créateurs (3.49€)
Alors les créateurs sont rémunérés pour 1 personne Et si 2 personnes utilisent le même compte simultanément, c'est injuste Et la limite 1 stream protège l'équité du système
Étant donné qu'un stream est interrompu sur un device
Quand l'utilisateur voit le message explicite
Alors il comprend immédiatement pourquoi (autre device actif) Et il peut choisir de reprendre sur le device actuel ou l'autre Et il n'y a pas de confusion ou frustration
Étant donné que Spotify Premium limite aussi à 1 stream actif
Quand RoadWave applique la même règle
Alors les utilisateurs connaissent déjà ce comportement Et cela paraît normal et accepté par l'industrie
Étant donné que Netflix permet 1-4 streams selon la formule
Quand RoadWave limite à 1 stream pour tous
Alors c'est plus strict que Netflix Mais Netflix cible le foyer familial (TV partagée) Alors que RoadWave cible l'individu conducteur (usage personnel)
Étant donné qu'un utilisateur change de device 50 fois en 1 heure
Quand le système détecte ce pattern anormal
Alors une alerte est générée pour l'équipe modération Et le compte peut être marqué pour surveillance Et si abus confirmé, suspension possible
Étant donné que je change de device plusieurs fois par jour
Quand les changements sont loggés
Alors chaque événement est enregistré:
| timestamp | from_device | to_device | content_id |
|---|---|---|---|
| 2025-06-15 08:30:00 | null | iPhone | abc123 |
| 2025-06-15 09:15:00 | iPhone | iPad | def456 |
| 2025-06-15 18:30:00 | iPad | iPhone | ghi789 |
Et ces logs aident à détecter les partages de compte
Étant donné qu'un admin consulte les métriques de streaming
Quand il accède au dashboard
Alors il voit:
| métrique | valeur |
|---|---|
| Utilisateurs Premium actifs | 12,547 |
| Changements de device/jour (médiane) | 2 |
| Utilisateurs >10 changements/jour | 47 (0.4%) |
| Comptes suspects (>20 changements/j) | 3 |
Étant donné que je change de device 30 fois par jour pendant 3 jours
Quand le système détecte ce pattern
Alors je reçois un email d'avertissement:
Étant donné que j'ai reçu un email d'avertissement il y a 7 jours Mais que je continue à changer de device 30 fois par jour
Quand l'équipe modération examine le compte
Alors mon compte Premium peut être suspendu pour partage abusif Et je reçois un email de suspension avec justification
Étant donné que je consulte la FAQ Premium
Quand je cherche "lecture interrompue"
Alors je trouve la réponse:
Étant donné qu'un utilisateur voit constamment "Lecture interrompue" Et qu'il pense que son compte est piraté
Quand il contacte le support
Alors le support vérifie les logs de changements de device Et peut identifier les devices (iPhone, iPad perso vs iPhone inconnu) Et conseille de changer le mot de passe si device inconnu détecté
Étant donné que je pense que mon compte est compromis
Quand je change mon mot de passe
Alors tous mes devices sont déconnectés immédiatement Et les sessions actives dans Redis sont supprimées Et je dois me reconnecter sur chaque device Et cela sécurise mon compte
Étant donné que 100 000 utilisateurs Premium lancent des contenus
Quand chaque lancement vérifie Redis (GET active_streams:{user_id})
Alors Redis peut gérer facilement 100 000 requêtes/seconde Et le temps de réponse moyen est <1ms Et aucun ralentissement n'est constaté
Étant donné que le serveur Redis principal tombe en panne
Quand le failover automatique vers le replica Redis s'active
Alors les sessions actives peuvent être perdues temporairement (max 5 min) Mais les utilisateurs peuvent relancer immédiatement Et l'impact est minimal (pas de perte de données critiques)
Étant donné que je lance exactement au même instant sur iPhone et iPad
Quand les 2 requêtes arrivent en parallèle au serveur
Alors Redis utilise un lock (SETNX) pour atomicité Et 1 seul device gagne (par exemple iPhone) Et l'autre device (iPad) reçoit immédiatement une erreur Et l'utilisateur peut retry sur iPad si souhaité
Étant donné que 1000 sessions Redis ont expiré (TTL atteint)
Quand Redis supprime automatiquement ces entrées
Alors la mémoire est libérée Et les nouveaux streams peuvent démarrer sans conflit Et aucune intervention manuelle n'est nécessaire
En tant qu'utilisateur Je veux pouvoir souscrire à un abonnement Premium Afin de profiter d'une expérience sans publicité avec des avantages exclusifs
31 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et que je suis connecté en tant qu'utilisateur
Étant donné que je consulte les offres Premium
Quand je vois la formule mensuelle
Alors le prix affiché est 4.99€/mois Et il n'y a aucune réduction Et le prix effectif par mois est 4.99€
Étant donné que je consulte les offres Premium
Quand je vois la formule annuelle
Alors le prix affiché est 49.99€/an Et l'économie affichée est "2 mois offerts" Et le prix effectif par mois est 4.16€ Et je vois le badge "Meilleure offre"
Étant donné que la formule mensuelle coûte 4.99€/mois
Quand je calcule le coût annuel en mensuel
Alors 12 mois × 4.99€ = 59.88€/an Et la formule annuelle coûte 49.99€ Et l'économie est de 9.89€ (≈ 2 mois gratuits) Et la réduction est de 16.5%
Étant donné que je consulte les offres Premium
Quand je recherche une option "Essai gratuit"
Alors aucune option d'essai gratuit n'est proposée Et je dois payer dès le premier jour pour accéder au Premium
Étant donné que RoadWave ne propose pas d'essai gratuit
Quand un utilisateur envisage un road trip de 14 jours
Alors il ne peut pas s'abonner pour l'essai gratuit puis annuler Et cela évite les inscriptions opportunistes Et protège les revenus des créateurs
Étant donné qu'un utilisateur Premium écoute des contenus
Quand il génère des écoutes dès le jour 1
Alors les créateurs sont rémunérés immédiatement (70% de 4.99€) Et il n'y a pas de "période gratuite" sans rémunération créateurs
Étant donné que RoadWave gère les abonnements
Quand il n'y a pas d'essai gratuit
Alors pas de gestion complexe de période trial Et pas de workflow de conversion trial → payant Et cela réduit la complexité technique
Étant donné qu'un utilisateur paie dès le début
Quand il souscrit à Premium
Alors il est plus engagé qu'un utilisateur en essai gratuit Et le taux de churn est généralement plus faible Et la lifetime value (LTV) est plus élevée
Étant donné que je consulte les offres Premium
Quand je recherche une option "Famille" ou "Partage"
Alors aucune option de partage familial n'est disponible Et seuls les abonnements individuels sont proposés
Étant donné que le partage familial nécessite:
| fonctionnalité | complexité |
|---|---|
| Gestion invitations | Moyenne |
| Validation liens famille | Moyenne |
| Limite devices par membre | Élevée |
| Dashboard admin famille | Élevée |
Quand RoadWave évalue le ROI
Alors le coût dev/support est trop élevé pour le MVP Et la fonctionnalité est reportée post-MVP
Étant donné qu'une offre famille permet 5-6 membres
Quand il n'y a pas de vérification stricte de lien familial
Alors des "familles" de 6 inconnus pourraient se former Et cela réduirait fortement les revenus (6 personnes pour 1 abonnement)
Étant donné que RoadWave cible principalement les conducteurs
Quand chaque conducteur utilise l'app individuellement en voiture
Alors le besoin de partage familial est limité Et la plupart des utilisateurs sont des individus (pas des familles)
Étant donné que RoadWave envisage une offre Famille post-MVP
Quand la fonctionnalité est spécifiée
Alors le prix serait 9.99€/mois pour 5 comptes Et cela représente 2€/mois/personne Mais cette offre n'est pas disponible au MVP
Étant donné que Spotify Premium coûte 10.99€/mois
Quand RoadWave fixe son prix à 4.99€/mois
Alors RoadWave est 54.5% moins cher que Spotify Et cela positionne RoadWave comme très accessible
Étant donné que YouTube Premium coûte 11.99€/mois
Quand RoadWave fixe son prix à 4.99€/mois
Alors RoadWave est 58.4% moins cher que YouTube Premium Et cela est un argument commercial fort
Étant donné qu'Apple Music coûte 10.99€/mois
Quand RoadWave fixe son prix à 4.99€/mois
Alors RoadWave est 54.5% moins cher qu'Apple Music Et cela attire les utilisateurs sensibles au prix
Étant donné que RoadWave cible les trajets quotidiens domicile-travail
Quand le prix est fixé à 4.99€/mois
Alors c'est un budget raisonnable pour un conducteur Et équivalent à ~1-2 cafés/mois Et psychologiquement acceptable pour un usage quotidien
Étant donné que la formule annuelle offre 2 mois gratuits
Quand un utilisateur souscrit pour 1 an
Alors il s'engage sur le long terme Et RoadWave sécurise 49.99€ de revenus immédiatement Et le cash flow est amélioré
Étant donné qu'un utilisateur paie 49.99€ pour l'année
Quand il envisage d'arrêter après 3 mois
Alors il a déjà payé pour 12 mois Et il continuera probablement à utiliser l'app Et le taux de churn est réduit significativement
Étant donné que je consulte la page Premium
Quand je vois les deux formules côte à côte
Alors je vois:
Étant donné que je consulte la page Premium
Quand je vois les deux formules
Alors la formule annuelle a un badge "Meilleure offre" ⭐ Et elle est visuellement mise en avant (bordure colorée, taille plus grande) Et l'économie de 2 mois est affichée en gros Et cela incite à choisir la formule annuelle
Étant donné que je consulte la page Premium
Quand je clique sur "FAQ"
Alors je vois une question "Pourquoi pas d'essai gratuit ?" Et la réponse explique:
Étant donné que RoadWave veut optimiser la conversion annuelle
Quand un A/B test est lancé
Alors groupe A voit "2 mois offerts" (économie en durée) Et groupe B voit "Économisez 9.89€" (économie en argent) Et les taux de souscription sont mesurés Et le message le plus performant est déployé
Étant donné que c'est le Black Friday
Quand une promo temporaire est activée
Alors la formule annuelle peut passer à 39.99€/an (au lieu de 49.99€) Et l'économie affichée est "4 mois offerts !" Et la promo dure 3 jours uniquement Et cela génère un pic de souscriptions
Étant donné qu'un influenceur promeut RoadWave
Quand il partage un code promo "INFLUENCEUR20"
Alors les utilisateurs obtiennent -20% sur le premier mois (3.99€ au lieu de 4.99€) Et le code est valable 1 mois Et les conversions sont trackées par code promo
Étant donné qu'un admin consulte les métriques d'abonnements
Quand il accède au dashboard
Alors il voit:
| métrique | valeur |
|---|---|
| Abonnés Premium total | 12,547 |
| Abonnés mensuels | 7,234 (58%) |
| Abonnés annuels | 5,313 (42%) |
| Revenus mensuels récurrents | 58,890€ |
Et ces données aident à piloter la stratégie tarifaire
Étant donné que RoadWave a:
| formule | nombre abonnés | prix |
|---|---|---|
| Mensuel | 7,234 | 4.99€/mois |
| Annuel | 5,313 | 49.99€/an |
Quand le MRR est calculé
Alors MRR mensuel = 7,234 × 4.99€ = 36,098€ Et MRR annuel ramené au mois = 5,313 × 49.99€ / 12 = 22,139€ Et MRR total = 58,237€/mois
Étant donné que le MRR est de 58,237€
Quand l'ARR est calculé
Alors ARR = 58,237€ × 12 = 698,844€/an Et cela aide à évaluer la valorisation de l'entreprise
Étant donné que RoadWave est une plateforme française
Quand les prix sont affichés
Alors tous les prix sont TTC (TVA 20% incluse) Et le prix 4.99€ inclut déjà la TVA Et cela respecte la réglementation française
Étant donné que la page Premium est consultée fréquemment
Quand un utilisateur charge la page
Alors les prix et avantages sont servis depuis un cache CDN Et le temps de chargement est <200ms Et cela garantit une expérience fluide
Étant donné que RoadWave se lance à l'international post-MVP
Quand un utilisateur se connecte depuis l'Allemagne
Alors les prix peuvent être ajustés (ex: 4.99€ en France, 4.49€ en Pologne) Et cela respecte le pouvoir d'achat local Mais cette fonctionnalité n'est pas au MVP (France uniquement)
En tant qu'utilisateur de RoadWave Je veux consulter les profils des créateurs Afin de découvrir leur contenu et décider de m'abonner
31 scénarios (28 standards, 3 plans)
Contexte commun à tous les scénarios
Étant donné que l'application RoadWave est démarrée
Étant donné un créateur avec le pseudo "paris_stories"
Quand l'utilisateur accède au profil
Alors l'URL est "https://roadwave.fr/@paris_stories"
Étant donné un créateur "@paris_stories" avec les informations suivantes:
| champ | valeur |
|---|---|
| photo | avatar_120x120.jpg |
| pseudo | paris_stories |
| badge_vérifié | true |
| bio | Histoires et anecdotes de Paris |
| abonnés | 1200 |
| contenus | 42 |
| durée_totale | 18h |
| écoutes_totales | 54000 |
Quand le profil est affiché
Alors les éléments suivants sont visibles:
| élément | valeur affichée |
|---|---|
| Photo profil | 120×120 px |
| @pseudo | @paris_stories |
| Badge vérifié | ✓ |
| Bio | Histoires et... |
| Nombre abonnés | 1.2K abonnés |
| Nombre contenus | 42 contenus |
| Durée totale | 18h de contenu créé |
| Écoutes totales | 54K écoutes totales |
Étant donné un créateur avec
Quand le profil est affiché
Alors la valeur affichée est "
📊 Exemples de données:
| métrique | valeur_exacte | valeur_affichée |
|---|---|---|
| abonnés | 342 | 342 |
| abonnés | 1200 | 1.2K |
| abonnés | 54000 | 54K |
| abonnés | 1200000 | 1.2M |
| écoutes | 842 | 842 |
| écoutes | 5400 | 5.4K |
| écoutes | 142000 | 142K |
| écoutes | 2100000 | 2.1M |
| durée (heures) | 18 | 18h |
| durée (heures) | 142 | 142h |
Étant donné un créateur avec la bio suivante en markdown:
Quand le profil est affiché
Alors le texte en gras "Histoires de Paris" est formaté Et le texte en italique "Nouveau contenu chaque semaine" est formaté Et le lien "https://paris-stories.fr" est cliquable
Étant donné un créateur qui entre une bio de 350 caractères
Quand la bio est sauvegardée
Alors seuls les 300 premiers caractères sont conservés Et un message "Maximum 300 caractères" s'affiche
Étant donné que l'utilisateur consulte un profil créateur
Quand la page est chargée
Alors les boutons suivants sont visibles:
| bouton | action |
|---|---|
| S'abonner | Abonnement au créateur |
| Partager profil | Menu de partage |
| ••• | Menu contextuel |
Étant donné que l'utilisateur clique sur le bouton [•••]
Quand le menu s'ouvre
Alors les options suivantes sont disponibles:
| option | description |
|---|---|
| Partager profil | Partager le lien du profil |
| Signaler profil | Signaler spam ou usurpation d'identité |
| Bloquer créateur | Masquer tous les contenus du créateur |
Étant donné un créateur avec 3 contenus publiés
Quand le profil est affiché
Alors chaque contenu affiche:
| élément | exemple |
|---|---|
| Cover image | Image 16:9 |
| Titre | Balade à Paris |
| Durée et écoutes | 12 min · 🎧 2.3K |
| Localisation | 📍 Paris |
| Bouton lecture | ▶️ |
Étant donné un créateur avec 10 contenus publiés
Quand l'utilisateur sélectionne le tri "
Alors les contenus sont triés par
📊 Exemples de données:
| option_tri | critère |
|---|---|
| Plus récents | Date publication DESC (défaut) |
| Plus populaires | Écoutes × facteur temporel (90 jours) |
| Plus anciens | Date publication ASC |
Étant donné un créateur avec des contenus taggés "Voyage", "Histoire", "Gastronomie"
Quand l'utilisateur filtre par tags "Voyage, Histoire"
Alors seuls les contenus avec ces tags sont affichés Et le nombre de résultats est indiqué "12 contenus"
Étant donné que l'utilisateur consulte le profil de "@paris_stories" Et que le créateur a publié 50 contenus
Quand l'utilisateur entre "Montmartre" dans la barre de recherche
Alors la recherche s'effectue sur les titres et descriptions Et seuls les contenus correspondants sont affichés Et le placeholder indique "Rechercher dans les contenus de @paris_stories"
Étant donné un créateur avec 100 contenus publiés
Quand le profil est affiché
Alors 20 contenus sont chargés initialement Et un bouton "Charger plus" est visible en bas de page
Quand l'utilisateur clique sur "Charger plus"
Alors 20 contenus supplémentaires sont chargés
Étant donné que l'utilisateur consulte un profil créateur
Alors les informations suivantes sont publiques:
| information | visible |
|---|---|
| Photo et pseudo | ✅ |
| Badge vérifié | ✅ |
| Bio | ✅ |
| Nombre abonnés | ✅ |
| Nombre contenus | ✅ |
| Durée totale créée | ✅ |
| Écoutes totales | ✅ |
Étant donné que l'utilisateur consulte un profil créateur
Alors les informations suivantes sont privées:
| information | visible |
|---|---|
| Liste des abonnés | ❌ |
| Revenus | ❌ |
| Localisation précise | ❌ |
| Email | ❌ |
Étant donné que le créateur "@paris_stories" consulte son propre dashboard
Quand la page statistiques est affichée
Alors les métriques suivantes sont accessibles:
| métrique | type |
|---|---|
| Taux complétion moyen | 78% |
| Évolution abonnés | Graphique |
| Écoutes par contenu | Tableau |
| Revenus | Dashboard |
| Taux conversion Premium | Pourcentage |
| Démographie (âge/zone) | Agrégée |
Étant donné que le créateur consulte son dashboard
Quand il sélectionne la période "30 jours"
Alors un graphique d'évolution des abonnés est affiché Et les périodes disponibles sont:
| période |
|---|
| 30j |
| 90j |
| 1 an |
Étant donné un créateur avec 10 contenus publiés
Quand il consulte le tableau des performances
Alors chaque contenu affiche:
| métrique | exemple |
|---|---|
| Titre | Balade |
| Écoutes totales | 2300 |
| Écoutes complètes >80% | 1840 |
| Taux complétion | 80% |
| Likes | 420 |
| Partages | 56 |
Étant donné un créateur vérifié "@paris_stories"
Quand son profil est affiché
Alors le badge bleu "✓" est accolé au pseudo Et un tooltip "Compte vérifié" s'affiche au survol
Étant donné un créateur vérifié "@paris_stories"
Alors le badge "✓" est affiché dans:
| emplacement |
|---|
| Page profil |
| Player en lecture |
| Résultats de recherche |
| Notifications |
Étant donné un créateur avec
Quand les conditions sont validées
Alors le badge vérifié est attribué
📊 Exemples de données:
| critère | automatique |
|---|---|
| KYC Mangopay validé | Oui |
| ≥10K abonnés + compte >6 mois | Oui |
| Célébrité / Média officiel | Manuel |
Étant donné un créateur qui complète son KYC Mangopay
Quand les documents sont validés
Alors le badge vérifié est attribué automatiquement Et une notification "Votre compte est maintenant vérifié ✓" est envoyée
Étant donné un créateur avec 9999 abonnés et un compte de 7 mois
Quand il atteint 10000 abonnés
Alors le badge vérifié est attribué automatiquement Et une notification de félicitations est envoyée
Étant donné un créateur reconnu publiquement
Quand il soumet le formulaire de demande de vérification
Alors une requête est créée pour l'équipe RoadWave Et l'équipe vérifie l'identité sous 48-72h Et le badge est attribué si validation réussie
Étant donné un créateur vérifié avec le badge "✓"
Quand sa monétisation est suspendue
Alors le badge vérifié est retiré temporairement Et le badge est restauré après levée de la suspension
Étant donné un créateur vérifié avec 3 strikes actifs
Quand un 4ème strike est appliqué (ban)
Alors le badge vérifié est retiré définitivement Et le compte est banni
Étant donné un créateur vérifié qui usurpe l'identité d'une célébrité
Quand la fraude est détectée
Alors le badge est retiré immédiatement Et le compte est banni Et une enquête est ouverte
Étant donné qu'un utilisateur tente d'accéder à "@deleted_user"
Quand la page est chargée
Alors un message "Ce profil n'existe pas ou a été supprimé" s'affiche
Étant donné que l'utilisateur bloque le créateur "@spam_account"
Quand l'utilisateur consulte son flux de recommandations
Alors aucun contenu de "@spam_account" n'est affiché Et le créateur n'apparaît plus dans les recherches
Étant donné que l'utilisateur a bloqué "@paris_stories"
Quand il accède à ses paramètres "Comptes bloqués" Et qu'il débloque "@paris_stories"
Alors les contenus du créateur réapparaissent dans les recommandations
Étant donné que l'utilisateur signale le profil "@spam_account"
Quand il sélectionne la raison "Spam"
Alors le signalement est envoyé à la modération Et un message de confirmation s'affiche Et le profil reste visible jusqu'à décision de modération
Étant donné que l'utilisateur signale le profil "@fake_celebrity"
Quand il sélectionne "Usurpation d'identité" Et qu'il fournit une preuve
Alors le signalement est priorisé (priorité HAUTE) Et l'équipe modération traite sous 24h
En tant que publicitaire Je veux créer des campagnes avec ciblage précis et maîtrise du budget Afin d'optimiser mes investissements publicitaires
30 scénarios (27 standards, 3 plans)
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et qu'un compte publicitaire est créé et vérifié
Étant donné que je suis connecté en tant que publicitaire
Quand je crée une nouvelle campagne avec les paramètres:
| Paramètre | Valeur |
|---|---|
| Budget total | 300€ |
| Date début | 2026-02-01 |
| Date fin | 2026-02-14 |
| Zone géographique | Département du Var |
| Plages horaires | 7h-9h, 17h-19h |
| Tags ciblés | Automobile, Voyage |
| Tranche d'âge | 18+ |
Alors la campagne est créée avec succès Et le budget quotidien calculé est de 21.43€/jour Et les diffusions estimées sont de ~430 écoutes complètes Et un statut "En attente de validation" est assigné
Étant donné que je crée une nouvelle campagne
Quand je définis un budget de 40€
Alors une erreur s'affiche: "Budget minimum requis: 50€" Et la campagne n'est pas créée
Étant donné que je crée une nouvelle campagne
Quand je définis un budget de 50€
Alors la campagne est créée avec succès Et aucune erreur n'est affichée
Étant donné une campagne avec:
| Budget total | 300€ |
|---|---|
| Durée | 14 j |
Quand le système calcule le budget quotidien
Alors le budget/jour est de 21.43€ Et le nombre estimé de diffusions/jour est de 430 (à 0.05€/écoute)
Étant donné que je crée une campagne
Quand je sélectionne "Point GPS" avec coordonnées (43.1234, 5.9234) Et que je définis un rayon de 5km
Alors la campagne cible uniquement les utilisateurs dans ce rayon Et la zone est représentée par un cercle sur la carte
Étant donné que je crée une campagne
Quand je sélectionne "Ville" et choisis "Marseille"
Alors la campagne cible tous les utilisateurs dans la commune de Marseille Et les limites administratives sont affichées sur la carte
Étant donné que je crée une campagne
Quand je sélectionne "Département" et choisis "Var (83)"
Alors la campagne cible tout le département du Var Et une estimation de population cible est affichée
Étant donné que je crée une campagne
Quand je sélectionne "Région" et choisis "Provence-Alpes-Côte d'Azur"
Alors la campagne cible toute la région PACA Et l'estimation de population cible est mise à jour
Étant donné que je crée une campagne
Quand je sélectionne "National"
Alors la campagne cible tous les utilisateurs en France Et aucune limite géographique n'est appliquée
Étant donné que je crée une campagne
Quand je définis les plages horaires:
| Plage |
|---|
| 7h-9h |
| 12h-14h |
| 17h-19h |
Alors la publicité est diffusée uniquement pendant ces plages Et elle n'est jamais diffusée en dehors (ex: 10h, 15h, 20h)
Étant donné que je crée une campagne
Quand je ne définis aucune plage horaire spécifique
Alors la publicité est diffusée 24h/24 Et aucune restriction horaire n'est appliquée
Étant donné que je crée une campagne pour un garage automobile
Quand je sélectionne les tags:
| Tag |
|---|
| Automobile |
| Mécanique |
| Sport |
Alors la publicité est prioritaire pour les utilisateurs avec jauges élevées sur ces tags Et elle peut quand même être diffusée à d'autres utilisateurs (ciblage non exclusif)
Étant donné que je crée une campagne
Quand j'essaie de valider sans sélectionner une tranche d'âge
Alors une erreur s'affiche: "Classification d'âge obligatoire" Et les options proposées sont:
| Option |
|---|
| Tout public |
| 13+ |
| 16+ |
| 18+ |
Étant donné que je crée une campagne
Quand j'upload un fichier audio format MP3
Alors le fichier est accepté
Quand j'upload un fichier audio format AAC (.aac ou .m4a)
Alors le fichier est accepté
Quand j'upload un fichier audio format WAV
Alors une erreur s'affiche: "Format non supporté. Utilisez MP3 ou AAC"
Étant donné que je crée une campagne
Quand j'upload un audio de 8 secondes
Alors une erreur s'affiche: "Durée minimale: 10 secondes"
Quand j'upload un audio de 65 secondes
Alors une erreur s'affiche: "Durée maximale: 60 secondes"
Quand j'upload un audio de 30 secondes
Alors le fichier est accepté
Étant donné que j'ai configuré une campagne à 300€
Quand j'arrive à l'étape de paiement
Alors je dois payer les 300€ avant validation Et le paiement est traité via Mangopay Et seule la carte bancaire est acceptée
Étant donné que j'ai une campagne active
Quand je configure la recharge automatique à 10% du budget
Alors si le budget restant passe sous 30€ (10% de 300€) Et que la campagne recharge automatiquement 100€ Et ma carte bancaire est débitée de 100€ Et le budget total passe à 130€
Étant donné que j'ai activé la recharge automatique
Quand je désactive cette option
Alors aucune recharge ne se produit automatiquement Et la campagne s'arrête quand le budget atteint 0€
Étant donné une campagne avec:
| Budget total | 1000€ |
|---|---|
| Durée | 30 j |
Quand le système calcule l'étalement
Alors le budget/jour est de 33.33€ Et si le budget se consomme plus vite (ex: 50€/jour) Alors une alerte "Budget épuisé dans 10 jours" est envoyée
Étant donné que je sélectionne la zone "Marseille"
Quand le système calcule la population cible
Alors l'estimation affichée est "~15 000 utilisateurs potentiels" Et un message "Estimation basée sur utilisateurs actifs dans la zone" s'affiche
Étant donné que je crée une campagne
Quand je définis la date de début au 2026-03-01 (dans 1 mois)
Alors la campagne a le statut "Programmée" Et elle démarre automatiquement le 2026-03-01 à 00h00 Et le budget n'est pas consommé avant cette date
Étant donné que je suis un publicitaire
Quand j'accède à l'interface publicitaire
Alors je peux créer une campagne sans contact commercial RoadWave Et toutes les options sont configurables en autonomie Et un tutoriel guidé est disponible (première utilisation)
Étant donné que je configure une zone géographique
Quand je sélectionne "Département du Var"
Alors une carte Leaflet affiche les limites du département en surbrillance Et un compteur "~50 000 utilisateurs actifs" est affiché Et je peux zoomer/dézoomer pour visualiser la zone
Étant donné que je crée une campagne pour un restaurant
Quand je sélectionne les tags:
| Tag |
|---|
| Gastronomie |
| Tourisme |
| Famille |
Alors la publicité est prioritaire pour utilisateurs intéressés par ces 3 thèmes Et le score de ciblage combine les 3 jauges d'intérêt
Étant donné que je crée une campagne
Quand je définis une date de début postérieure à la date de fin
Alors une erreur s'affiche: "Date de fin doit être après date de début" Et la campagne n'est pas créée
Étant donné que je crée une campagne
Quand je définis une durée de moins de 24 heures
Alors une erreur s'affiche: "Durée minimale: 1 jour" Et je dois ajuster les dates
Étant donné que je crée une campagne
Quand je définis une durée de plus de 90 jours
Alors une erreur s'affiche: "Durée maximale: 90 jours" Et je dois ajuster les dates ou créer plusieurs campagnes
Étant donné une campagne avec un budget de
Quand la durée est de
Alors le budget quotidien est de
📊 Exemples de données:
| budget | duree | budget_jour |
|---|---|---|
| 100 | 10 | 10.00 |
| 300 | 14 | 21.43 |
| 500 | 30 | 16.67 |
| 1000 | 60 | 16.67 |
Étant donné un budget quotidien de
Quand le coût par écoute complète est 0.05€
Alors le nombre estimé de diffusions/jour est
📊 Exemples de données:
| budget_jour | diffusions |
|---|---|
| 10.00 | 200 |
| 21.43 | 429 |
| 50.00 | 1000 |
| 100.00 | 2000 |
Étant donné que j'upload un fichier
Quand le format est
Alors le résultat est
📊 Exemples de données:
| fichier | format | resultat |
|---|---|---|
| pub.mp3 | MP3 | accepté |
| pub.aac | AAC | accepté |
| pub.m4a | AAC | accepté |
| pub.wav | WAV | rejeté |
| pub.ogg | OGG | rejeté |
| pub.flac | FLAC | rejeté |
En tant que système RoadWave Je veux appliquer des règles précises de durée, skippabilité et facturation Afin d'équilibrer expérience utilisateur et rentabilité publicitaire
32 scénarios (29 standards, 3 plans)
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et qu'un utilisateur gratuit écoute du contenu
Étant donné qu'un publicitaire uploade une publicité de 8 secondes
Quand le système valide la durée
Alors une erreur s'affiche: "Durée minimale: 10 secondes" Et l'upload est rejeté
Étant donné qu'un publicitaire uploade une publicité de 65 secondes
Quand le système valide la durée
Alors une erreur s'affiche: "Durée maximale: 60 secondes" Et l'upload est rejeté
Étant donné qu'un publicitaire crée une campagne
Quand il voit les recommandations
Alors un message s'affiche:
Étant donné qu'un publicitaire uploade une publicité de 10 secondes
Quand le système valide la durée
Alors le fichier est accepté Et aucune erreur n'est affichée
Étant donné qu'un publicitaire uploade une publicité de 60 secondes
Quand le système valide la durée
Alors le fichier est accepté Et un avertissement s'affiche: "⚠️ Durée longue: taux de skip potentiellement élevé"
Étant donné qu'une publicité de 30 secondes démarre Et que le délai minimal est configuré à 5 secondes
Quand j'écoute pendant 3 secondes
Alors le bouton "Passer" n'est pas visible Et je dois attendre 2 secondes supplémentaires
Quand j'atteins 5 secondes d'écoute
Alors le bouton "Passer" apparaît Et je peux cliquer pour passer au contenu suivant
Étant donné que l'admin configure le délai à 3 secondes Et qu'une publicité démarre
Quand j'écoute pendant 3 secondes
Alors le bouton "Passer" apparaît immédiatement Et je peux skipper
Étant donné que l'admin configure le délai à 10 secondes Et qu'une publicité démarre
Quand j'écoute pendant 9 secondes
Alors le bouton "Passer" n'est toujours pas visible
Quand j'atteins 10 secondes
Alors le bouton "Passer" apparaît
Étant donné qu'une publicité de 30 secondes est diffusée
Quand j'écoute pendant 25 secondes (83%)
Alors l'écoute est considérée comme "complète" Et le publicitaire est facturé 0.05€ Et le compteur "écoutes complètes" s'incrémente
Étant donné qu'une publicité de 30 secondes est diffusée
Quand j'écoute pendant exactement 24 secondes (80%)
Alors l'écoute est considérée comme "complète" Et le publicitaire est facturé 0.05€
Étant donné qu'une publicité de 30 secondes est diffusée Et que le délai minimal est 5 secondes
Quand j'écoute pendant 10 secondes (33%) Et que je clique sur "Passer"
Alors l'écoute est considérée comme "partielle" Et le publicitaire est facturé 0.02€
Étant donné qu'une publicité de 30 secondes est diffusée Et que le délai minimal est 5 secondes
Quand j'écoute pendant 3 secondes Et que je clique sur "Suivant" (pas de bouton skip encore)
Alors l'écoute est considérée comme "non engagée" Et le publicitaire n'est PAS facturé (0€)
Étant donné qu'une publicité de 30 secondes est diffusée
Quand j'écoute pendant 23 secondes (77%)
Alors l'écoute est considérée comme "partielle" (pas complète) Et le publicitaire est facturé 0.02€
Étant donné qu'une publicité de 30 secondes est diffusée
Quand j'écoute les 30 secondes complètes (100%)
Alors l'écoute est considérée comme "complète" Et le publicitaire est facturé 0.05€
Étant donné qu'une campagne à 300€ a généré:
| Type écoute | Nombre | Coût unitaire | Total |
|---|---|---|---|
| Complète (>80%) | 4000 | 0.05€ | 200€ |
| Partielle (5-80%) | 2000 | 0.02€ | 40€ |
| Skip immédiat | 1000 | 0€ | 0€ |
Quand je calcule le budget consommé
Alors le total est 240€ Et il reste 60€ de budget disponible
Étant donné qu'une publicité de 30s démarre Et que le délai minimal est 5s
Quand j'écoute pendant 2 secondes
Alors un compteur s'affiche: "Passer dans 3s..."
Quand j'atteins 5 secondes
Alors le compteur disparaît Et le bouton "Passer la publicité" s'affiche
Étant donné qu'une publicité de 30s est en lecture
Quand 10 secondes se sont écoulées
Alors la progress bar affiche 33% (10/30) Et l'indicateur temporel affiche "0:10 / 0:30" Et l'utilisateur visualise la progression
Étant donné qu'une publicité démarre
Quand l'audio commence
Alors un badge "Publicité" est affiché en haut de l'écran Et la durée totale est indiquée: "Publicité (30s)" Et la transparence est maximale (utilisateur sait que c'est une pub)
Étant donné qu'une publicité de 30s se termine
Quand la lecture atteint 30 secondes
Alors le délai de transition de 2s démarre Et le contenu normal suivant est annoncé Et l'enchaînement est naturel (même UX que entre contenus)
Étant donné qu'une publicité est en lecture Et que le véhicule est à l'arrêt
Quand je clique sur le bouton cœur
Alors un like explicite (+2%) est enregistré Et mes jauges d'intérêt sont mises à jour selon les tags de la pub Et le publicitaire voit un compteur "Likes" incrémenté
Étant donné qu'une publicité est diffusée par un créateur Et que le véhicule est à l'arrêt
Quand je clique sur "S'abonner"
Alors l'abonnement est enregistré (+5% jauges) Et le publicitaire bénéficie de l'engagement fort Et cela compte comme une conversion majeure
Étant donné qu'une publicité a dépassé le délai minimal
Quand le bouton "Passer" s'affiche
Alors il est positionné en bas à droite de l'écran Et il a une taille de clic confortable (44×44px minimum iOS) Et il est clairement visible (contraste élevé)
Étant donné qu'une publicité est diffusée
Quand un événement se produit
Alors il est tracké en temps réel:
| Événement | Données enregistrées |
|---|---|
| Impression | timestamp, user_id, pub_id, zone_geo |
| Écoute complète | durée_ecoutee, pourcentage, coût (0.05€) |
| Skip après délai | durée_ecoutee, pourcentage, coût (0.02€) |
| Skip immédiat | durée_ecoutee, pourcentage, coût (0€) |
| Like | timestamp, tags impactés |
| Abonnement | timestamp, creator_id |
Étant donné les statistiques RoadWave globales:
| Durée pub | Taux complétion moyen |
|---|---|
| 10s | 65% |
| 15s | 55% |
| 30s | 45% |
| 45s | 30% |
| 60s | 20% |
Quand un publicitaire consulte les recommandations
Alors le sweet spot affiché est "15-30 secondes" Et l'explication est "Meilleur compromis engagement/message"
Étant donné qu'une campagne de 60s a un taux de skip de 85%
Quand le publicitaire consulte les recommandations
Alors le système suggère:
Étant donné une campagne avec:
| Type écoute | Nombre | Coût unitaire | Total |
|---|---|---|---|
| Complète | 2000 | 0.05€ | 100€ |
| Partielle | 3000 | 0.02€ | 60€ |
| Skip immédiat | 1000 | 0€ | 0€ |
Quand je calcule le coût effectif moyen
Alors CEM = 160€ / 6000 impressions = 0.027€/impression Et cette métrique aide à comparer avec CPM industrie
Étant donné qu'un publicitaire demande "Publicité non skippable"
Quand il configure sa campagne
Alors cette option n'existe pas Et toutes les publicités sont obligatoirement skippables après 5s minimum
Étant donné qu'un admin essaie de configurer le délai à 2 secondes
Quand il valide le paramètre
Alors une erreur s'affiche: "Délai minimal: 3 secondes minimum"
Étant donné qu'un admin essaie de configurer le délai à 15 secondes
Quand il valide le paramètre
Alors une erreur s'affiche: "Délai maximal: 10 secondes maximum"
Étant donné qu'une publicité de 30s est diffusée
Quand j'écoute pendant
Alors le type d'écoute est
📊 Exemples de données:
| duree | pourcentage | type | cout |
|---|---|---|---|
| 3 | 10 | skip immédiat | 0 |
| 5 | 17 | partielle | 0.02 |
| 10 | 33 | partielle | 0.02 |
| 20 | 67 | partielle | 0.02 |
| 24 | 80 | complète | 0.05 |
| 27 | 90 | complète | 0.05 |
| 30 | 100 | complète | 0.05 |
Étant donné
Quand je calcule le budget total consommé
Alors le résultat est
📊 Exemples de données:
| completes | partielles | skips | budget_total |
|---|---|---|---|
| 1000 | 500 | 100 | 60 |
| 2000 | 1000 | 500 | 120 |
| 5000 | 2000 | 1000 | 290 |
| 0 | 1000 | 0 | 20 |
| 1000 | 0 | 0 | 50 |
Étant donné que le délai minimal est configuré à
Quand j'écoute pendant
Alors le bouton "Passer" est
📊 Exemples de données:
| delai | temps_ecoute | visible |
|---|---|---|
| 5 | 3 | non visible |
| 5 | 5 | visible |
| 5 | 10 | visible |
| 10 | 8 | non visible |
| 10 | 10 | visible |
| 3 | 2 | non visible |
| 3 | 3 | visible |
En tant que publicitaire Je veux suivre en temps réel mon budget et recevoir des alertes Afin de maîtriser mes dépenses et optimiser mes campagnes
30 scénarios (27 standards, 3 plans)
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et qu'un compte publicitaire est connecté Et qu'une campagne active est en cours
Étant donné que ma campagne a un budget de 300€ Et que j'ai consommé 220€
Quand je consulte le dashboard budget
Alors je vois:
| Métrique | Valeur |
|---|---|
| Budget total | 300€ |
| Budget consommé | 220€ |
| Budget restant | 80€ |
| Pourcentage | 73% consommé |
Étant donné que j'ai consommé 220€ sur 300€
Quand je consulte le dashboard
Alors une jauge de progression affiche 73% Et la couleur est orange (seuil 50-80%) Et un indicateur "80€ restants" est affiché clairement
Étant donné un budget de 300€
Quand j'ai consommé 150€ (50%)
Alors la jauge est verte
Quand j'ai consommé 240€ (80%)
Alors la jauge est orange
Quand j'ai consommé 285€ (95%)
Alors la jauge est rouge Et un message "Budget presque épuisé" s'affiche
Étant donné que j'ai consommé 220€ en 10 jours Et qu'il reste 4 jours de campagne
Quand le système calcule la projection
Alors la consommation quotidienne moyenne est 22€/jour Et la projection affiche "Budget épuisé dans 3.6 jours" Et un avertissement "Campagne s'arrêtera avant la fin prévue" s'affiche
Étant donné que j'ai consommé 100€ en 10 jours Et qu'il reste 4 jours de campagne Et que le budget total est 300€
Quand le système calcule la projection
Alors la consommation quotidienne moyenne est 10€/jour Et la projection affiche "Budget suffisant pour toute la campagne" Et le budget restant estimé à la fin est 160€
Étant donné que mon budget est de 300€
Quand je consomme 240€ (80%)
Alors je reçois immédiatement un email: Et une notification push est envoyée Et une notification in-app s'affiche
Étant donné que mon budget est de 300€
Quand je consomme 270€ (90%)
Alors je reçois immédiatement un email:
Étant donné que mon budget est de 300€
Quand je consomme les 300€ (100%)
Alors je reçois immédiatement un email: Et la campagne est automatiquement mise en pause Et plus aucune diffusion ne se produit
Étant donné que ma campagne est active Et qu'il reste 150€ de budget
Quand je clique sur "Mettre en pause"
Alors le statut passe à "En pause" Et les diffusions s'arrêtent immédiatement Et le budget de 150€ est conservé Et je peux réactiver la campagne plus tard
Étant donné que ma campagne est en pause Et qu'il reste 150€ de budget
Quand je clique sur "Reprendre la campagne"
Alors le statut passe à "Active" Et les diffusions reprennent immédiatement Et le budget restant de 150€ continue de se consommer
Étant donné que ma campagne se termine dans 2 jours Et qu'il reste 20€ de budget
Quand je clique sur "Prolonger la campagne" Et que j'ajoute 200€ supplémentaires
Alors le budget total passe à 220€ Et la date de fin peut être prolongée de 10 jours Et un nouveau paiement Mangopay de 200€ est traité
Étant donné que j'ai configuré la recharge automatique Et que le seuil est fixé à 10% (30€ sur budget 300€) Et que le montant de recharge est 100€
Quand le budget restant passe sous 30€
Alors une recharge automatique de 100€ est déclenchée Et ma carte bancaire est débitée via Mangopay Et le budget total passe à budget_restant + 100€ Et je reçois un email de confirmation
Étant donné que la recharge automatique est activée Et que ma carte bancaire a expiré
Quand le budget passe sous le seuil de 10%
Alors la recharge automatique échoue Et je reçois un email urgent: Et la campagne continue jusqu'à épuisement du budget restant
Étant donné que j'ai consommé 120€ sur 300€ (40%)
Quand j'essaie de modifier le ciblage géographique
Alors la modification est autorisée Et le ciblage est mis à jour immédiatement Et les nouvelles diffusions utilisent le nouveau ciblage
Étant donné que j'ai consommé 180€ sur 300€ (60%)
Quand j'essaie de modifier le ciblage géographique
Alors une erreur s'affiche:
Étant donné que ma campagne est active
Quand je veux modifier le fichier audio
Alors un message s'affiche:
Étant donné que ma campagne cible 7h-9h et 17h-19h
Quand je modifie pour cibler 12h-14h aussi
Alors la modification est appliquée immédiatement Et les diffusions suivantes incluent la nouvelle plage Et aucune re-validation n'est nécessaire
Étant donné que ma campagne a duré 10 jours
Quand je consulte l'historique
Alors je vois un graphique avec:
| Jour | Consommation | Cumulé |
|---|---|---|
| 1 | 22€ | 22€ |
| 2 | 25€ | 47€ |
| 3 | 20€ | 67€ |
| ... | ... | ... |
| 10 | 18€ | 220€ |
Et je peux identifier les pics de consommation
Étant donné que ma campagne se termine le 14/02
Quand la date de fin est atteinte
Alors je reçois un email:
Étant donné que ma campagne avait 300€ de budget Et qu'elle s'est terminée avec 280€ consommés
Quand la campagne se termine (date ou épuisement)
Alors un remboursement de 20€ est initié via Mangopay Et le délai est de 5-7 jours ouvrés Et je reçois une notification de confirmation
Étant donné que ma campagne avait 300€ de budget Et qu'elle s'est terminée avec 300€ consommés
Quand la campagne se termine
Alors aucun remboursement n'est initié Et le message final indique "Budget entièrement utilisé"
Étant donné que j'avais défini un objectif de 5000 impressions Et que mon budget était 300€
Quand je consulte les statistiques finales
Alors je vois:
| Métrique | Objectif | Réalisé | Écart |
|---|---|---|---|
| Impressions | 5000 | 6000 | +20% |
| Budget | 300€ | 280€ | -7% |
| Coût/impression | 0.06€ | 0.047€ | -22% |
Et une analyse "✅ Objectifs dépassés avec budget optimisé"
Étant donné que je veux analyser mes dépenses
Quand je clique sur "Exporter rapport financier"
Alors je télécharge un CSV avec:
| Colonne |
|---|
| Date/Heure |
| Type écoute |
| Coût unitaire |
| Zone géographique |
| Utilisateur (anonyme) |
| Durée écoutée |
Et je peux l'importer dans Excel pour analyses
Étant donné que j'ai 3 campagnes actives
Quand je consulte la vue d'ensemble
Alors je vois un tableau récapitulatif:
| Campagne | Budget | Consommé | % | Jours restants | Projection |
|---|---|---|---|---|---|
| A | 300€ | 220€ | 73 | 4j | Suffisant |
| B | 500€ | 480€ | 96 | 10j | Épuisé 2j |
| C | 200€ | 50€ | 25 | 20j | Suffisant |
Et un badge alerte rouge sur la campagne B
Étant donné que j'ai 5 campagnes actives Et que 2 campagnes ont >80% budget consommé
Quand je reçois les notifications
Alors un email consolidé unique est envoyé: Et je ne reçois pas 2 emails séparés (évite spam)
Étant donné que je configure mes préférences d'alerte
Quand je définis les seuils:
| Seuil | Valeur |
|---|---|
| Alerte 1 | 70% |
| Alerte 2 | 85% |
| Alerte 3 | 95% |
Alors je reçois des alertes à 70%, 85% et 95% Et non aux seuils par défaut 80%, 90%, 100%
Étant donné que je préfère uniquement les notifications in-app
Quand je désactive les alertes email dans mes préférences
Alors je ne reçois plus d'emails d'alerte budget Mais les notifications in-app continuent Et les alertes critiques (échec paiement) sont toujours envoyées par email
Étant donné un budget de 300€
Quand j'ai consommé
Alors la couleur de la jauge est
📊 Exemples de données:
| montant | pourcentage | couleur |
|---|---|---|
| 100 | 33 | verte |
| 150 | 50 | verte |
| 180 | 60 | orange |
| 240 | 80 | orange |
| 270 | 90 | rouge |
| 285 | 95 | rouge |
| 300 | 100 | rouge |
Étant donné un budget de 300€
Et une consommation actuelle de
Quand je calcule la consommation quotidienne moyenne
Alors elle est de
📊 Exemples de données:
| consomme | jours_ecoules | conso_jour | jours_restants |
|---|---|---|---|
| 100 | 5 | 20 | 10 |
| 200 | 10 | 20 | 5 |
| 150 | 10 | 15 | 10 |
| 270 | 12 | 22.5 | 1.3 |
Étant donné un budget de 500€
Quand je consomme
Alors je reçois une alerte
📊 Exemples de données:
| montant | pourcentage | niveau |
|---|---|---|
| 350 | 70 | aucune |
| 400 | 80 | alerte 80% |
| 450 | 90 | alerte 90% |
| 500 | 100 | budget épuisé |
En tant que système RoadWave Je veux insérer les publicités de manière équilibrée et non intrusive Afin de préserver l'expérience utilisateur tout en monétisant
31 scénarios (28 standards, 3 plans)
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et qu'un utilisateur gratuit est connecté
Étant donné que la fréquence par défaut est configurée à 1/5 Et que je suis un utilisateur gratuit
Quand j'écoute 5 contenus
Alors 1 publicité est insérée après le 5ème contenu
Quand j'écoute 10 contenus
Alors 2 publicités sont insérées (après les contenus 5 et 10)
Étant donné que je suis un utilisateur Premium
Quand j'écoute 100 contenus
Alors aucune publicité n'est insérée Et je bénéficie d'une expérience sans interruption publicitaire
Étant donné que l'admin configure la fréquence à 1/3 Et que je suis un utilisateur gratuit
Quand j'écoute 6 contenus
Alors 2 publicités sont insérées (après contenus 3 et 6)
Étant donné que l'admin configure la fréquence à 1/10 Et que je suis un utilisateur gratuit
Quand j'écoute 20 contenus
Alors 2 publicités sont insérées (après contenus 10 et 20)
Étant donné que j'écoute un contenu de 10 minutes Et que je suis à 5 minutes de lecture Et qu'une publicité devrait être insérée selon la fréquence
Quand le système vérifie l'insertion
Alors la publicité attend la fin du contenu actuel Et elle s'insère pendant le délai de transition (2s) Et le contenu n'est jamais interrompu
Étant donné que le contenu "A" se termine Et que le délai de transition de 2s démarre
Quand le système détecte qu'une publicité doit être insérée
Alors le message "Publicité (30s)" s'affiche Et la publicité démarre après les 2 secondes Et l'enchaînement est naturel et fluide
Étant donné qu'un utilisateur a entendu la publicité "A" 3 fois aujourd'hui
Quand le système sélectionne une nouvelle publicité à diffuser
Alors la publicité "A" n'est plus éligible pour cet utilisateur aujourd'hui Et une autre publicité "B" est sélectionnée Et cela évite la saturation publicitaire
Étant donné qu'un utilisateur écoute la pub "RestaurantX"
Quand la diffusion se termine
Alors un compteur Redis "pub:RestaurantX:user:123:count" s'incrémente Et le TTL est de 24h (reset à minuit)
Quand le compteur atteint 3
Alors la pub "RestaurantX" est exclue des prochaines sélections aujourd'hui
Étant donné qu'un utilisateur a entendu 6 publicités dans la dernière heure
Quand le système devrait insérer une 7ème pub
Alors l'insertion est reportée à l'heure suivante Et un compteur horaire Redis "pub:user:123:hourly" est vérifié Et cela évite le spam publicitaire
Étant donné qu'une publicité cible un point GPS à 2km de ma position Et qu'une autre publicité cible ma ville entière
Quand le système sélectionne une publicité
Alors la publicité point GPS est priorisée (score géo plus élevé) Et le ciblage précis est favorisé
Étant donné que 4 publicités sont éligibles:
| Publicité | Zone | Distance |
|---|---|---|
| A | Point GPS | 1km |
| B | Ville | 0km |
| C | Département | 0km |
| D | National | N/A |
Quand le système sélectionne selon priorité géographique
Alors l'ordre de priorité est: A > B > C > D Et la publicité A (Point GPS, la plus précise) est diffusée
Étant donné que 2 publicités ciblent ma zone géographique:
| Publicité | Tags | Mes jauges |
|---|---|---|
| A | Automobile | 80% |
| B | Voyage | 40% |
Quand le système applique le score centres d'intérêt
Alors la publicité A est favorisée (meilleur match jauges) Et le ciblage thématique affine la sélection
Étant donné qu'une campagne cible uniquement 7h-9h Et qu'il est 10h30
Quand le système sélectionne une publicité
Alors cette campagne n'est PAS éligible Et seules les campagnes "toute la journée" ou avec plage horaire actuelle sont considérées
Étant donné qu'une campagne cible 7h-9h et 17h-19h Et qu'il est 8h15
Quand le système sélectionne une publicité
Alors cette campagne est éligible Et elle peut être diffusée
Étant donné qu'une publicité est uploadée avec volume trop élevé (-6 LUFS)
Quand le système encode l'audio via FFmpeg
Alors le volume est normalisé automatiquement à -14 LUFS Et le publicitaire reçoit une notification "Volume audio ajusté pour conformité" Et cela évite l'effet "pub trop forte" frustrant
Étant donné qu'une publicité est soumise
Quand FFmpeg encode le fichier
Alors une commande loudnorm est appliquée: Et le fichier final respecte le standard broadcast -14 LUFS
Étant donné que 3 publicités ont le même score géo Et qu'elles ont toutes des jauges centres d'intérêt équivalentes Et qu'aucune n'a été diffusée 3 fois aujourd'hui
Quand le système sélectionne une publicité
Alors une sélection aléatoire équitable est faite Et chaque campagne a 33% de chances d'être diffusée
Étant donné qu'une campagne "A" a épuisé son budget Et qu'une campagne "B" a encore du budget disponible
Quand le système sélectionne une publicité
Alors seule la campagne "B" est éligible Et la campagne "A" est automatiquement exclue
Étant donné qu'une campagne "A" est programmée du 01/02 au 14/02 Et que nous sommes le 20/01
Quand le système sélectionne une publicité
Alors la campagne "A" n'est pas éligible Et seules les campagnes actives aujourd'hui sont considérées
Étant donné qu'une publicité cible "Marseille uniquement" Et que je suis à Lyon
Quand le système sélectionne une publicité
Alors cette publicité n'est jamais éligible pour moi Et je ne la verrai jamais tant que je reste à Lyon
Étant donné qu'un utilisateur entend une pub à 10h05
Quand le compteur horaire est incrémenté
Alors la clé Redis "pub:user:123:hourly:2026012110" est créée Et le TTL est de 1 heure (expire à 11h05) Et le système compte les pubs dans la fenêtre glissante d'1h
Étant donné qu'un utilisateur a entendu la pub "A" 3 fois le 20/01
Quand minuit passe et on est le 21/01
Alors le compteur "pub:A:user:123:count" est expiré (TTL 24h) Et l'utilisateur peut à nouveau entendre la pub "A" jusqu'à 3 fois
Étant donné qu'aucune campagne n'a de budget disponible
Quand le système devrait insérer une publicité
Alors aucune pub n'est insérée Et l'enchaînement de contenus continue normalement Et le prochain contenu démarre directement
Étant donné que 2 campagnes sont éligibles:
| Campagne | Budget restant | Jours restants |
|---|---|---|
| A | 500€ | 2j |
| B | 50€ | 10j |
Quand le système applique la priorisation budgétaire
Alors la campagne A est légèrement favorisée (urgence dépense) Et cela aide à épuiser les budgets avant fin de campagne
Étant donné qu'une publicité "RestaurantX" est sélectionnée
Quand elle est diffusée à l'utilisateur "123"
Alors un événement est loggé en base:
| Champ | Valeur |
|---|---|
| pub_id | RestaurantX |
| user_id | 123 |
| timestamp | 2026-01-21 10:30 |
| zone_geo | Marseille |
| score_geo | 0.85 |
| score_interet | 0.70 |
Et cela permet l'analytics publicitaire
Étant donné que je suis un utilisateur gratuit Et que j'entends des publicités
Quand je souscris à Premium
Alors le système détecte le changement de statut immédiatement Et plus aucune publicité n'est insérée dès le prochain contenu Et mon expérience devient sans pub instantanément
Étant donné que je suis admin RoadWave
Quand j'accède aux paramètres publicitaires
Alors je peux ajuster le curseur de fréquence:
| Option | Fréquence |
|---|---|
| 1/3 | Haute (agressif) |
| 1/5 | Standard (défaut) |
| 1/7 | Modérée |
| 1/10 | Faible |
Et le changement s'applique en temps réel à tous les utilisateurs
Étant donné que l'admin active un test A/B
Quand 50% des utilisateurs ont fréquence 1/5 Et 50% des utilisateurs ont fréquence 1/7
Alors les métriques sont trackées séparément:
| Cohorte | Fréquence | Taux désabonnement | Revenus/user |
|---|---|---|---|
| A | 1/5 | 2.5% | 0.50€ |
| B | 1/7 | 1.8% | 0.40€ |
Et l'admin peut identifier la fréquence optimale
Étant donné que la fréquence est
Quand j'écoute
Alors
📊 Exemples de données:
| frequence | contenus | pubs |
|---|---|---|
| 1/3 | 9 | 3 |
| 1/5 | 10 | 2 |
| 1/5 | 25 | 5 |
| 1/7 | 14 | 2 |
| 1/10 | 30 | 3 |
Étant donné qu'une publicité cible
Quand le système calcule le score géographique
Alors la priorité est
📊 Exemples de données:
| type_zone | score |
|---|---|
| Point GPS | 1.0 |
| Ville | 0.8 |
| Département | 0.6 |
| Région | 0.4 |
| National | 0.2 |
Étant donné qu'une publicité a été entendue
Quand le système vérifie l'éligibilité
Alors la publicité est
📊 Exemples de données:
| fois | eligible |
|---|---|
| 0 | éligible |
| 1 | éligible |
| 2 | éligible |
| 3 | non éligible |
| 4 | non éligible |
En tant que publicitaire Je veux consulter des métriques détaillées en temps réel Afin d'optimiser mes campagnes et mesurer leur ROI
27 scénarios (24 standards, 3 plans)
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et qu'un compte publicitaire est connecté Et qu'une campagne active est en cours
Étant donné que ma campagne a généré 1000 diffusions
Quand je consulte le dashboard
Alors je vois les métriques suivantes mises à jour en temps réel:
| Métrique | Valeur |
|---|---|
| Impressions | 1000 |
| Écoutes complètes (>80%) | 400 |
| Taux d'écoute complète | 40% |
| Taux de skip | 60% |
| Durée moyenne d'écoute | 18s |
| Likes | 25 |
| Abonnements | 5 |
| Coût par écoute | 0.05€ |
Étant donné que ma publicité a été diffusée 2500 fois
Quand je consulte le dashboard
Alors le compteur "Impressions" affiche 2500 Et il s'incrémente en temps réel à chaque nouvelle diffusion
Étant donné que ma publicité de 30s a été:
| Durée écoutée | Nombre |
|---|---|
| 25s (83%) | 300 |
| 20s (67%) | 200 |
| 10s (33%) | 150 |
| 5s (17%) | 50 |
Quand je consulte les écoutes complètes
Alors le compteur affiche 300 (uniquement ≥80%) Et le taux d'écoute complète est de 43% (300/700)
Étant donné 1000 diffusions totales Et 400 écoutes complètes
Quand je consulte le taux de skip
Alors il affiche 60% ((1000-400)/1000) Et il est calculé comme (total - complètes) / total
Étant donné que ma publicité de 30s a été écoutée:
| Durée | Nombre d'utilisateurs |
|---|---|
| 30s | 400 |
| 20s | 300 |
| 10s | 200 |
| 5s | 100 |
Quand je consulte la durée moyenne
Alors le calcul est: (30×400 + 20×300 + 10×200 + 5×100) / 1000 Et le résultat affiché est 21s
Étant donné que 50 utilisateurs ont liké ma publicité
Quand je consulte le dashboard
Alors le compteur "Likes" affiche 50 Et un taux de like de 5% est calculé (50/1000 impressions) Et cela indique une forte appréciation du contenu
Étant donné que 10 utilisateurs se sont abonnés après avoir entendu ma pub
Quand je consulte le dashboard
Alors le compteur "Abonnements" affiche 10 Et un taux de conversion de 1% est calculé (10/1000) Et cela représente un engagement très fort
Étant donné que j'ai dépensé 200€ Et obtenu 4000 écoutes complètes
Quand je consulte le coût par écoute
Alors le CPE affiché est 0.05€ (200/4000) Et il correspond au tarif standard RoadWave
Étant donné que ma campagne cible le département du Var Et que j'ai 1000 diffusions réparties:
| Zone | Diffusions | Pourcentage |
|---|---|---|
| Toulon | 400 | 40% |
| Hyères | 250 | 25% |
| Fréjus | 200 | 20% |
| Autres | 150 | 15% |
Quand je consulte la heatmap géographique
Alors une carte Leaflet affiche les zones avec intensité proportionnelle Et Toulon apparaît en rouge foncé (forte concentration) Et les autres villes en dégradé orange/jaune
Étant donné que ma campagne cible les plages 7h-9h et 17h-19h Et que j'ai 1000 diffusions:
| Plage horaire | Diffusions |
|---|---|
| 7h-8h | 300 |
| 8h-9h | 250 |
| 17h-18h | 280 |
| 18h-19h | 170 |
Quand je consulte le graphique horaire
Alors un histogramme Chart.js affiche les 4 barres Et je peux identifier que 7h-8h est le pic d'écoute Et optimiser mes futures campagnes sur cette plage
Étant donné que ma campagne est Tout Public Et que j'ai des écoutes sur différentes tranches:
| Tranche d'âge | Écoutes complètes | Total diffusions | Taux |
|---|---|---|---|
| 18-24 ans | 120 | 400 | 30% |
| 25-34 ans | 200 | 400 | 50% |
| 35-44 ans | 80 | 200 | 40% |
Quand je consulte l'analyse par âge
Alors je vois que les 25-34 ans ont le meilleur taux (50%) Et je peux cibler cette tranche pour mes prochaines campagnes
Étant donné que j'ai 2 campagnes actives:
| Campagne | Budget | Écoutes complètes | Taux | CPE |
|---|---|---|---|---|
| A | 300€ | 4000 | 40% | 0.075€ |
| B | 300€ | 6000 | 60% | 0.05€ |
Quand je consulte le comparatif
Alors je vois que la campagne B performe mieux Et le tableau recommande "Campagne B: +50% écoutes, -33% CPE" Et je peux allouer plus de budget à la campagne B
Étant donné que je veux analyser mes données dans Excel
Quand je clique sur "Exporter CSV"
Alors je télécharge un fichier avec les colonnes:
| Colonne |
|---|
| Date |
| Heure |
| Zone géographique |
| Tranche d'âge |
| Durée écoute |
| Skip (Oui/Non) |
| Like (Oui/Non) |
| Abonnement (Oui/Non) |
Et je peux faire des analyses personnalisées
Étant donné que je consulte le dashboard
Quand je clique sur un graphique Chart.js
Alors je peux zoomer/filtrer interactivement Et je peux exporter le graphique en PNG Et l'image est en haute résolution pour présentations
Étant donné que ma campagne de 14 jours se termine
Quand la date de fin est atteinte
Alors un rapport PDF est généré automatiquement Et il contient:
| Section |
|---|
| Résumé exécutif |
| Métriques clés |
| Graphiques de performance |
| Heatmap géographique |
| Répartition horaire |
| Analyse tranches d'âge |
| Recommandations optimisation |
Et je reçois un email avec le PDF en pièce jointe
Étant donné que je consulte le dashboard à 10h00
Quand une nouvelle diffusion se produit à 10h01
Alors les métriques sont rafraîchies automatiquement (polling 30s) Et je vois les nouveaux chiffres sans recharger la page Et un badge "Mis à jour il y a 15s" s'affiche
Étant donné que je configure une alerte "Taux de skip >70%" Et que ma campagne atteint 72% de skip
Quand le seuil est dépassé
Alors je reçois un email d'alerte:
Étant donné que ma campagne a 45% d'écoutes complètes
Quand je consulte le benchmark
Alors je vois "Votre taux: 45% | Moyenne RoadWave: 40%" Et un badge "📊 Performance: +12% vs moyenne" s'affiche Et je sais que ma campagne performe au-dessus de la moyenne
Étant donné que j'ai un budget de 300€ Et que j'ai consommé 220€
Quand je consulte le dashboard
Alors je vois une jauge "Budget consommé: 73%" (220/300) Et le montant restant "80€ restants" Et une projection "Épuisé dans 3 jours à ce rythme"
Étant donné que j'ai dépensé 200€ avec:
| Type d'écoute | Nombre | Coût unitaire | Total |
|---|---|---|---|
| Écoute complète | 3000 | 0.05€ | 150€ |
| Skip après 5s | 2000 | 0.02€ | 40€ |
| Skip immédiat | 500 | 0€ | 0€ |
Quand je consulte la répartition
Alors un graphique camembert affiche:
| Segment | Pourcentage |
|---|---|
| Écoutes complètes | 75% (150€) |
| Skips partiels | 20% (40€) |
| Skips immédiats | 5% (0€) |
Étant donné une campagne de 30 jours
Quand je consulte le graphique d'évolution
Alors je vois une courbe Chart.js avec:
| Axe | Donnée |
|---|---|
| X | Jours (1-30) |
| Y | Taux d'écoute complète (%) |
Et je peux identifier les tendances (amélioration/dégradation) Et les jours avec pics d'engagement
Étant donné qu'un utilisateur a entendu ma pub 3 fois Et qu'il l'a écoutée complètement les 3 fois
Quand je consulte les métriques avancées
Alors le "Taux de réécoute" affiche 100% Et cela indique que le contenu n'est pas perçu comme spam Et les utilisateurs tolèrent bien la répétition
Étant donné que ma campagne a un taux de skip de 75% Et que la durée moyenne d'écoute est de 8s sur 30s
Quand je consulte les recommandations
Alors le système suggère:
Étant donné que j'ai 3 campagnes actives simultanément
Quand je consulte la vue consolidée
Alors je vois un tableau récapitulatif:
| Campagne | Budget | Dépensé | Diffusions | Taux complète | CPE |
|---|---|---|---|---|---|
| A | 300€ | 220€ | 4000 | 40% | 0.05€ |
| B | 500€ | 150€ | 3000 | 60% | 0.05€ |
| C | 200€ | 180€ | 3600 | 35% | 0.05€ |
Et je peux comparer les performances d'un coup d'œil
Étant donné
Quand je calcule le taux
Alors le résultat est
📊 Exemples de données:
| total | completes | taux |
|---|---|---|
| 1000 | 400 | 40 |
| 2000 | 1200 | 60 |
| 500 | 100 | 20 |
| 1000 | 850 | 85 |
Étant donné un budget dépensé de
Quand je calcule le CPE
Alors le résultat est
📊 Exemples de données:
| depense | ecoutes | cpe |
|---|---|---|
| 100 | 2000 | 0.05 |
| 300 | 6000 | 0.05 |
| 50 | 1000 | 0.05 |
| 500 | 10000 | 0.05 |
Étant donné un taux d'écoute complète de
Quand je compare à la moyenne
Alors la performance est
📊 Exemples de données:
| taux | classification |
|---|---|
| 60 | Excellente (+50%) |
| 50 | Bonne (+25%) |
| 40 | Moyenne |
| 30 | Faible (-25%) |
| 20 | Très faible (-50%) |
En tant que modérateur RoadWave Je veux valider manuellement toutes les publicités avant diffusion Afin de garantir la qualité et la légalité des contenus publicitaires
29 scénarios (27 standards, 2 plans)
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et qu'un modérateur RoadWave est connecté
Étant donné qu'un publicitaire a créé une campagne Et que le paiement de 300€ a été effectué
Quand la campagne est soumise
Alors elle passe en statut "En attente de validation" Et elle est ajoutée à la file d'attente des modérateurs Et la diffusion ne démarre PAS avant validation manuelle Et le publicitaire reçoit un email "Votre campagne est en cours de validation (24-48h)"
Étant donné qu'une campagne est soumise le lundi 10h
Quand le modérateur la valide le mardi 15h
Alors le délai est de 29h (dans les 48h ouvrées) Et le publicitaire reçoit une notification "Votre campagne est approuvée"
Étant donné qu'une campagne est soumise le lundi 10h
Quand 48h ouvrées se sont écoulées Et que la campagne n'est toujours pas validée
Alors le publicitaire reçoit un email automatique: Et un modérateur senior est assigné automatiquement
Étant donné qu'une campagne est en attente de validation Et que l'audio respecte toutes les règles
Quand le modérateur clique sur "Approuver"
Alors le statut passe à "Approuvée" Et la campagne démarre à la date programmée Et le publicitaire reçoit un email de confirmation Et le budget commence à être consommé dès le début
Étant donné qu'une campagne contient du contenu alcool
Quand le modérateur clique sur "Refuser" Et qu'il sélectionne le motif "Contenu interdit: Alcool" Et qu'il ajoute le commentaire "La publicité pour l'alcool est interdite en France"
Alors le statut passe à "Refusée" Et le publicitaire reçoit un email détaillé avec:
| Champ | Valeur |
|---|---|
| Motif | Contenu interdit: Alcool |
| Commentaire | La publicité pour l'alcool est interdite en France |
| Action requise | Modifier votre contenu et soumettre à nouveau |
Et un remboursement automatique de 300€ est déclenché
Étant donné qu'une campagne à 500€ est refusée
Quand le statut passe à "Refusée"
Alors un remboursement Mangopay de 500€ est initié automatiquement Et le délai de remboursement est de 5-7 jours ouvrés Et le publicitaire reçoit un email "Remboursement en cours"
Étant donné qu'une publicité mentionne "Whisky premium 40°"
Quand le modérateur écoute l'audio
Alors il doit refuser la campagne Et sélectionner le motif "Contenu interdit: Alcool"
Étant donné qu'une publicité mentionne "Cigarettes électroniques"
Quand le modérateur écoute l'audio
Alors il doit refuser la campagne Et sélectionner le motif "Contenu interdit: Tabac/Vape"
Étant donné qu'une publicité mentionne "Gagnez 10 000€ - Paris sportifs"
Quand le modérateur écoute l'audio
Alors il doit refuser la campagne Et sélectionner le motif "Contenu interdit: Jeux d'argent"
Étant donné qu'une publicité politique est soumise Et que nous sommes en période de campagne électorale officielle
Quand le modérateur écoute l'audio
Alors il doit refuser la campagne Et sélectionner le motif "Contenu interdit: Publicité politique (période électorale)"
Étant donné qu'une publicité contient des propos sexuellement explicites
Quand le modérateur écoute l'audio
Alors il doit refuser la campagne Et sélectionner le motif "Contenu interdit: Contenu sexuel"
Étant donné qu'une publicité contient des descriptions violentes
Quand le modérateur écoute l'audio
Alors il doit refuser la campagne Et sélectionner le motif "Contenu interdit: Violence"
Étant donné qu'une publicité pour un restaurant local dit "Découvrez notre menu du jour"
Quand le modérateur écoute l'audio
Alors il doit approuver la campagne
Étant donné qu'une publicité pour un garage dit "Révision complète à partir de 99€"
Quand le modérateur écoute l'audio
Alors il doit approuver la campagne
Étant donné qu'une publicité a une qualité audio très basse (bruits, saturation)
Quand le modérateur écoute l'audio
Alors il peut refuser avec le motif "Qualité audio insuffisante" Et recommander "Veuillez soumettre un fichier audio de meilleure qualité"
Étant donné qu'une publicité contient du langage familier Et qu'elle est classée "Tout public"
Quand le modérateur écoute l'audio
Alors il peut refuser avec le motif "Classification d'âge incorrecte" Et recommander "Reclasser en 13+ minimum"
Étant donné qu'une publicité fait des promesses mensongères "Perdez 10kg en 1 semaine"
Quand le modérateur écoute l'audio
Alors il doit refuser avec le motif "Non-conformité réglementaire: Publicité mensongère"
Étant donné que 10 campagnes sont en attente de validation Et que la campagne A a été soumise il y a 40h Et que la campagne B a été soumise il y a 2h
Quand le modérateur consulte sa file
Alors la campagne A apparaît en premier (priorité temporelle) Et un badge "Urgente - >40h" est affiché
Étant donné que je suis modérateur
Quand j'accède au dashboard modération publicités
Alors je vois:
| Métrique | Exemple valeur |
|---|---|
| Campagnes en attente | 5 |
| Délai moyen de validation | 28h |
| Campagnes validées aujourd'hui | 12 |
| Campagnes refusées aujourd'hui | 3 |
| Taux d'acceptation | 80% |
Étant donné qu'une publicité audio est soumise
Quand le système traite l'audio
Alors une transcription automatique est générée via Whisper Et elle est affichée au modérateur pour faciliter la revue Et elle permet une recherche par mots-clés (alcool, tabac, etc.)
Étant donné qu'une publicité audio est soumise
Quand la transcription contient "whisky" ou "vodka"
Alors un flag automatique "⚠️ Alcool détecté" est ajouté Et la campagne est priorisée pour validation manuelle rapide Et le modérateur est alerté du contenu potentiellement interdit
Étant donné qu'un publicitaire a eu 2 campagnes refusées
Quand il soumet une 3ème campagne
Alors le modérateur voit l'historique:
| Date | Statut | Motif |
|---|---|---|
| 2026-01-15 | Refusée | Contenu interdit: Alcool |
| 2026-01-20 | Refusée | Qualité audio faible |
Et il peut en tenir compte dans sa décision
Étant donné que ma campagne a été refusée pour "Classification incorrecte"
Quand je conteste la décision via le formulaire d'appel
Alors un modérateur senior revoit la campagne Et il peut approuver si la classification est en fait correcte Et le délai de réponse est de 48-72h
Étant donné que je suis modérateur connecté
Quand une nouvelle campagne est soumise
Alors je reçois une notification in-app Et le compteur "Campagnes en attente" s'incrémente en temps réel Et je peux cliquer pour consulter immédiatement
Étant donné que je suis admin modération
Quand je consulte les statistiques mensuelles
Alors je vois les motifs de refus:
| Motif | Nombre | Pourcentage |
|---|---|---|
| Alcool | 15 | 30% |
| Qualité audio | 12 | 24% |
| Classification erronée | 10 | 20% |
| Publicité mensongère | 8 | 16% |
| Autres | 5 | 10% |
Étant donné que je suis modérateur senior
Quand j'exporte le rapport mensuel
Alors je reçois un fichier CSV avec:
| Colonne |
|---|
| Campagne ID |
| Publicitaire |
| Date soumission |
| Date décision |
| Statut |
| Motif (si refus) |
| Modérateur |
Et je peux l'analyser dans Excel
Étant donné qu'une campagne a un contenu acceptable Mais que la classification d'âge est incorrecte
Quand le modérateur clique sur "Demander modification"
Alors le publicitaire reçoit un email: Et le statut devient "Modification requise" Et le publicitaire peut modifier sans repayer
Étant donné qu'une publicité contient le mot
Quand la transcription automatique est analysée
Alors un flag
📊 Exemples de données:
| mot_cle | flag | motif |
|---|---|---|
| whisky | ⚠️ Alcool | Contenu interdit: Alcool |
| vodka | ⚠️ Alcool | Contenu interdit: Alcool |
| cigarette | ⚠️ Tabac | Contenu interdit: Tabac |
| casino | ⚠️ Jeux argent | Contenu interdit: Jeux |
| paris sportifs | ⚠️ Jeux argent | Contenu interdit: Jeux |
Étant donné qu'une campagne est soumise
Quand elle est validée
Alors le statut est
📊 Exemples de données:
| jour | heure | delai | conformite |
|---|---|---|---|
| Lundi | 10h | 24 | Dans les délais (24h) |
| Lundi | 10h | 48 | Dans les délais (48h) |
| Lundi | 10h | 50 | Hors délais (>48h) |
| Vendredi | 16h | 72 | Dans les délais (we) |
En tant que système Je veux gérer efficacement les flux audio en temps réel Afin d'assurer une diffusion stable et scalable des lives
24 scénarios
Contexte commun à tous les scénarios
Étant donné que l'infrastructure RoadWave est opérationnelle Et que les serveurs Go avec Pion WebRTC sont actifs
Étant donné qu'un créateur démarre un live depuis son application mobile
Quand le flux audio WebRTC (Opus 48 kbps) arrive sur le serveur
Alors le serveur Go avec Pion WebRTC accepte la connexion Et le flux est traité en temps réel
Étant donné qu'un flux WebRTC Opus est reçu par le serveur
Quand le serveur traite le flux
Alors FFmpeg convertit en segments HLS (.ts) Et un fichier manifest .m3u8 est généré et mis à jour régulièrement Et les segments ont une durée de 2 secondes chacun
Étant donné que les segments HLS sont générés
Quand un auditeur demande à rejoindre le live
Alors le manifest .m3u8 est servi via Bunny CDN Et les segments .ts sont cachés sur le CDN Et la distribution est globale avec latence minimale
Étant donné qu'un auditeur iOS rejoint un live
Quand l'application charge le flux HLS
Alors le player natif AVPlayer gère la lecture Et le buffer de 15 secondes est appliqué automatiquement Et la qualité s'adapte selon la connexion
Étant donné qu'un auditeur Android rejoint un live
Quand l'application charge le flux HLS
Alors le player natif ExoPlayer gère la lecture Et le buffer de 15 secondes est configuré Et la qualité s'adapte selon la connexion
Étant donné qu'un live est en cours
Alors un processus parallèle enregistre le flux Opus raw Et l'enregistrement est stocké temporairement sur le serveur Et l'enregistrement est indépendant de la diffusion HLS
Étant donné qu'un live vient de se terminer
Quand le processus post-live démarre
Alors un job asynchrone est créé dans la queue Redis Et un worker Go récupère le job Et le worker exécute FFmpeg pour les conversions
Étant donné qu'un worker traite un job post-live
Quand la conversion démarre
Alors FFmpeg convertit Opus raw en MP3 256 kbps Et la normalisation audio à -14 LUFS est appliquée Et les silences prolongés (>3 secondes) sont détectés et nettoyés
Étant donné que le MP3 256 kbps est généré
Quand le worker crée les segments HLS
Alors des segments .ts de 10 secondes sont créés Et un manifest .m3u8 est généré Et les segments sont uploadés vers le stockage Bunny
Étant donné que tous les segments HLS sont uploadés
Quand le worker finalise le job
Alors une entrée de contenu "replay" est créée en base PostgreSQL Et le titre est "[REPLAY] [Titre live original]" Et le type géographique est "Géo-neutre" Et le replay est immédiatement disponible pour les auditeurs
Étant donné qu'un replay est publié depuis 7 jours
Quand le job de nettoyage quotidien s'exécute
Alors le fichier Opus raw est supprimé du stockage Et seul le MP3 256 kbps et les segments HLS sont conservés Et l'espace de stockage est libéré
Étant donné que 50 lives se terminent simultanément
Quand les jobs post-live sont créés
Alors les workers Go disponibles traitent les jobs en parallèle Et si tous les workers sont occupés, les jobs attendent en queue Redis Et de nouveaux workers peuvent être lancés automatiquement (Kubernetes)
Étant donné que l'infrastructure MVP est configurée pour 100 lives simultanés Et que 100 lives sont actuellement en cours
Quand un nouveau créateur essaie de démarrer un live
Alors la demande est refusée avec le code erreur 503 Et le message "Capacité maximale atteinte. Veuillez réessayer dans quelques minutes" est retourné Et la demande peut être mise en queue prioritaire si créateur Premium
Étant donné que plusieurs lives sont en cours
Alors le système monitore en temps réel:
| métrique | seuil alerte |
|---|---|
| CPU utilisation | >80% |
| Mémoire utilisation | >85% |
| Bande passante upload | >80% capacité |
| Nombre connexions WebRTC | >90 |
| Latence moyenne CDN | >200ms |
Et si un seuil est dépassé, une alerte est envoyée à l'équipe technique
Étant donné qu'un live a 100 auditeurs simultanés Et que la qualité est 48 kbps Opus
Quand le live dure 1 heure
Alors la bande passante totale est d'environ 2.16 GB Et le coût Bunny CDN est d'environ 0.02€ (tarif ~0.01€/GB) Et ces métriques sont enregistrées pour facturation créateur si nécessaire
Étant donné qu'un live est diffusé via Bunny CDN
Quand un segment .ts est généré
Alors le segment est uploadé vers Bunny origin Et Bunny CDN cache le segment sur ses edge servers Et les auditeurs suivants récupèrent le segment depuis le cache Et la charge sur le serveur origin est réduite de ~90%
Étant donné qu'un créateur diffuse avec une connexion 4G
Quand la latence réseau augmente ponctuellement
Alors le buffer côté serveur absorbe les fluctuations Et la qualité peut être réduite temporairement (48 kbps → 32 kbps) Et un warning est affiché au créateur si la connexion est trop instable
Étant donné qu'un live contient de la musique en arrière-plan
Quand le système d'audio fingerprint analyse le flux
Alors une empreinte audio est calculée toutes les 30 secondes Et l'empreinte est comparée à une base de données de contenus protégés Et si une correspondance est trouvée, un warning est envoyé au créateur Et si le créateur ne corrige pas sous 30 secondes, le live peut être arrêté
Étant donné qu'un créateur démarre un live
Alors les métadonnées suivantes sont enregistrées:
| champ | exemple valeur |
|---|---|
| live_id | uuid v4 |
| creator_id | uuid créateur |
| title | "Mon super live" |
| started_at | timestamp UTC |
| zone_geo | "Île-de-France" |
| tags | ["Actualité", "Tech"] |
| classification_age | "Tout public" |
Et ces données sont indexées pour recherche et analytics
Étant donné qu'un live est en cours
Alors Redis stocke les compteurs temps réel:
| clé Redis | valeur exemple |
|---|---|
| live:[live_id]:listeners | 247 |
| live:[live_id]:likes | 89 |
| live:[live_id]:reports | 0 |
Et ces compteurs sont mis à jour toutes les 2 secondes Et les compteurs sont persistés en PostgreSQL toutes les 60 secondes
Étant donné qu'un auditeur écoute un live
Alors l'application envoie un heartbeat toutes les 10 secondes Et le heartbeat met à jour le timestamp dans Redis Et si aucun heartbeat n'est reçu pendant 30 secondes, l'auditeur est retiré du compteur
Étant donné qu'un live est en cours sur serveur A
Quand le serveur A tombe en panne
Alors Kubernetes redémarre automatiquement un pod Mais le live en cours est perdu (pas de failover temps réel en MVP) Et le créateur voit le message "Connexion perdue. Veuillez redémarrer le live" Et les auditeurs voient "Le live est terminé suite à un problème technique"
Étant donné qu'un live est enregistré en Opus raw
Quand l'enregistrement dépasse 10 minutes
Alors un backup incrémental est créé toutes les 10 minutes Et le backup est stocké sur un stockage secondaire (S3-compatible) Et en cas de crash serveur, le live peut être récupéré jusqu'au dernier backup
Étant donné qu'un live démarre, se déroule et se termine
Alors tous les événements sont loggés:
| événement | détails enregistrés |
|---|---|
| Démarrage live | timestamp, creator_id, zone_geo |
| Auditeur rejoint | timestamp, user_id, position GPS |
| Auditeur quitte | timestamp, user_id, durée écoute |
| Signalement | timestamp, user_id, catégorie |
| Fin live | timestamp, durée totale, stats finales |
Et ces logs sont conservés 90 jours pour analytics et conformité RGPD
En tant que créateur Je veux arrêter ma diffusion en direct de manière contrôlée Afin de terminer proprement mon live et générer un replay automatiquement
19 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et que je suis connecté en tant que créateur Et que je diffuse actuellement un live
Quand j'appuie sur le bouton "Arrêter live"
Alors un compte à rebours de 5 secondes démarre Et je vois le message "Ce live se termine dans 5... 4... 3... 2... 1" Et un bouton "Annuler" est affiché pendant le décompte Et l'audio du compte à rebours est diffusé aux auditeurs
Étant donné que j'ai appuyé sur "Arrêter live" Et que le compte à rebours affiche "3 secondes"
Quand j'appuie sur "Annuler"
Alors le compte à rebours s'arrête Et le live continue normalement Et aucune notification n'est envoyée aux auditeurs
Étant donné que le compte à rebours est à 0
Alors le live s'arrête Et la diffusion aux auditeurs se termine Et le message "Live terminé" s'affiche Et le processus de traitement post-live démarre automatiquement
Étant donné que je diffuse un live
Quand ma connexion est perdue pendant 30 secondes
Alors les auditeurs voient le message "Connexion créateur perdue, reconnexion en cours..." Et le live continue de bufferer Et quand ma connexion revient, le live reprend normalement
Étant donné que je diffuse un live
Quand ma connexion est perdue pendant 60 secondes
Alors le live s'arrête automatiquement Et les auditeurs voient le message "Le live est terminé suite à une coupure de connexion" Et le processus de traitement post-live démarre
Étant donné que je diffuse un live
Alors mon flux audio est enregistré en continu Et le format d'enregistrement est Opus raw Et l'enregistrement est stocké temporairement sur le serveur
Étant donné que mon live vient de se terminer Et que l'option "Publier replay automatiquement" est activée (par défaut)
Quand le traitement post-live démarre
Alors un job asynchrone est créé Et le job effectue les opérations suivantes:
| opération | détail |
|---|---|
| Conversion format | Opus raw → MP3 256 kbps |
| Génération segments HLS | Segments .ts pour streaming |
| Normalisation volume | -14 LUFS |
| Détection silences prolongés | Nettoyage automatique |
Étant donné que le traitement post-live est terminé
Alors le replay est publié automatiquement sous 5 à 10 minutes Et le titre est "[REPLAY] [Titre live original]" Et la zone de diffusion est la même que le live Et les tags sont identiques au live Et la classification d'âge est identique Et le type géographique est "Géo-neutre" (contenu pérenne)
Étant donné que le replay de mon live est publié
Quand un auditeur qui a écouté le live se reconnecte
Alors il voit une notification in-app "Le replay de [Titre] est disponible"
Étant donné que je configure un nouveau live
Quand je désactive l'option "Publier replay automatiquement" Et que je démarre puis arrête le live
Alors le live est enregistré Mais le replay n'est pas publié automatiquement Et je peux décider manuellement de le publier plus tard
Étant donné que mon live a généré un replay publié
Quand j'accède à mes contenus
Alors je vois le replay dans ma liste Et je peux le supprimer comme n'importe quel contenu
Quand je supprime le replay
Alors le fichier source Opus raw est supprimé immédiatement
Étant donné que mon live est terminé Et que le replay est publié
Alors le fichier Opus raw est conservé pendant 7 jours Et après 7 jours, le fichier raw est supprimé automatiquement Et seul le MP3 256 kbps est conservé
Étant donné que mon live a généré un replay publié
Quand j'essaie de modifier l'audio du replay
Alors l'action est refusée Et je vois le message "Les replays ne peuvent pas être modifiés pour garantir l'intégrité de l'enregistrement" Et je peux uniquement modifier les métadonnées (titre, description)
Étant donné que mon live est terminé
Quand j'accède aux statistiques
Alors je vois:
| métrique | exemple valeur |
|---|---|
| Durée totale | 1h 23min |
| Nombre d'auditeurs max | 247 |
| Nombre d'auditeurs moyen | 183 |
| Nombre de likes | 89 |
| Nombre d'abonnements | 12 |
| Signalements reçus | 0 |
Étant donné que mon live a reçu 3 signalements pendant la diffusion
Quand le live se termine
Alors le replay n'est pas publié automatiquement Et le contenu est en attente de modération Et je vois le message "Votre replay sera publié après vérification suite aux signalements reçus" Et un modérateur doit valider ou refuser le replay sous 24h
Étant donné que je diffuse un live Et qu'un modérateur détecte du contenu interdit
Quand le modérateur clique sur "Arrêter le live immédiatement"
Alors le live s'arrête sans compte à rebours Et je vois le message "Votre live a été interrompu par la modération" Et je reçois une notification détaillant la raison Et le replay n'est pas publié Et le fichier source est conservé 30 jours pour appel
Étant donné que je diffuse un live Et que 100 auditeurs écoutent simultanément
Alors la bande passante consommée est d'environ 4.8 Mbps via CDN Et le coût estimé Bunny CDN est d'environ 0.02€ par heure de diffusion Et je peux voir ces métriques en temps réel dans l'interface créateur
Étant donné que je diffuse un live Et qu'aucun auditeur n'écoute depuis 5 minutes
Alors je vois un message d'information "Aucun auditeur actuellement connecté" Mais le live continue normalement Et je peux choisir de continuer ou d'arrêter
Étant donné que mon live était diffusé en Opus 48 kbps
Quand le replay est généré
Alors le replay est encodé en MP3 256 kbps Et la qualité audio du replay est supérieure au live Et la taille du fichier est optimisée pour le stockage long terme
En tant qu'auditeur Je veux écouter des lives de manière stable Afin de profiter du contenu en temps réel sans coupures
27 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et que je suis connecté en tant qu'auditeur Et qu'un créateur diffuse actuellement un live
Quand je clique sur "Rejoindre le live"
Alors la connexion au flux HLS s'établit Et je commence à écouter avec un décalage de 15 secondes par rapport au créateur Et le buffer de 15 secondes garantit une lecture stable
Étant donné les alternatives de buffer possibles:
| buffer | stabilité 3G | stabilité 4G | décalage perceptible | décision |
|---|---|---|---|---|
| 5s | Faible | Moyenne | Non | ❌ |
| 10s | Moyenne | Bonne | Non | ❌ |
| 15s | Bonne | Excellente | Léger acceptable | ✅ |
| 20s+ | Excellente | Excellente | Oui | ❌ |
Alors le buffer optimal est 15 secondes
Étant donné que je suis sur réseau 3G Et que j'écoute un live
Quand des micro-coupures réseau surviennent
Alors le buffer de 15 secondes absorbe les coupures Et la lecture continue sans interruption perceptible
Étant donné que je suis sur réseau 4G Et que j'écoute un live
Alors la lecture est fluide Et le buffer de 15 secondes prévient les coupures lors de changement de cellule
Étant donné que j'écoute un live régional "Île-de-France" Et que je suis situé en Île-de-France
Quand je me déplace et sors du département
Alors le live continue de jouer normalement Et je peux écouter jusqu'à la fin naturelle du live Et après la fin du live, l'algorithme propose du contenu correspondant à ma nouvelle position
Étant donné que je suis abonné au créateur "JeanDupont" Et que je suis situé en Île-de-France
Quand "JeanDupont" démarre un live en Île-de-France
Alors je reçois une notification push "🔴 JeanDupont est en direct : [Titre du live]" Et quand je tape sur la notification, l'app s'ouvre et le live démarre immédiatement
Étant donné que je suis abonné au créateur "JeanDupont" Et que je suis situé à Lyon
Quand "JeanDupont" démarre un live en Île-de-France
Alors je ne reçois pas de notification push Et cela évite la frustration de ne pas pouvoir écouter un live hors zone
Étant donné que je suis dans la zone géographique du live Et que je navigue dans l'app avec "Suivant"
Quand l'algorithme propose un live en cours
Alors je vois l'indicateur "🔴 EN DIRECT" Et je peux choisir de le rejoindre ou de passer au suivant
Étant donné que j'écoute un live
Quand je perds ma connexion réseau pendant 45 secondes Et que je retrouve ma connexion
Alors je reprends le live au moment actuel (pas au buffer ancien) Et le saut temporel est transparent (pas de message d'erreur) Et je ne rate que quelques secondes de contenu
Étant donné que j'écoute un live
Quand je perds ma connexion réseau pendant 90 secondes Et que je retrouve ma connexion
Alors je vois le message "Live en cours perdu, passage au contenu suivant" Et l'algorithme propose automatiquement le contenu suivant Et je peux manuellement revenir au live s'il est toujours en cours
Étant donné que j'écoute un live Et que mon véhicule est à l'arrêt
Quand je clique sur le bouton "❤️ Like"
Alors le like est enregistré immédiatement Et le compteur de likes visible par le créateur s'incrémente Et ma jauge d'intérêt pour les tags du live augmente de +2%
Étant donné que j'écoute un live Et que je ne suis pas encore abonné au créateur
Quand je clique sur le bouton "S'abonner"
Alors je m'abonne au créateur Et ma jauge d'intérêt pour tous les tags du créateur augmente de +5% Et je recevrai des notifications pour ses prochains lives
Étant donné que j'écoute un live
Quand j'appuie sur "Suivant" (ou commande au volant)
Alors je quitte le live immédiatement Et l'algorithme propose le contenu suivant Et si j'ai écouté moins de 10 secondes, ma jauge d'intérêt diminue de -0.5%
Étant donné que j'écoute un live
Quand j'appuie sur "Précédent" (ou commande au volant)
Alors rien ne se passe Et un message d'information s'affiche brièvement "Précédent non disponible sur les lives"
Étant donné que j'écoute un live
Alors aucune interface de chat n'est disponible Et je ne peux pas envoyer de messages au créateur Et je ne peux pas voir de messages d'autres auditeurs Et cette fonctionnalité ne sera jamais implémentée
Étant donné que j'écoute un live
Alors aucune réaction emoji n'est disponible Et je ne peux pas envoyer d'emoji en temps réel Et cette fonctionnalité ne sera jamais implémentée
Étant donné que j'écoute mon premier live
Quand j'accède à l'interface du live
Alors je vois un bandeau informatif "💬 Les discussions ne sont pas disponibles sur RoadWave pour garantir votre sécurité en voiture et éviter le harcèlement." Et ce bandeau n'apparaît qu'une seule fois (première expérience)
Étant donné que j'écoute un live Et que le contenu me semble inapproprié
Quand je clique sur le bouton "Signaler"
Alors je vois les catégories de signalement:
| catégorie |
|---|
| Haine et violence |
| Contenu sexuel |
| Illégalité |
| Droits d'auteur |
| Désinformation dangereuse |
| Harcèlement |
| Autre |
Et quand je sélectionne une catégorie Alors le signalement est envoyé en priorité selon la catégorie Et un modérateur peut écouter le live en temps réel si besoin
Étant donné que j'écoute un live
Quand je consulte les informations du live
Alors je vois:
| information | exemple valeur |
|---|---|
| Nombre d'auditeurs | 247 personnes |
| Durée du live | 1h 23min |
| Nom du créateur | @JeanDupont |
| Zone de diffusion | Île-de-France |
| Tags | Actualité, Société |
Mais je ne vois pas les likes ou autres métriques détaillées
Étant donné que j'écoute un live avec exactement 247 auditeurs
Quand je consulte le nombre d'auditeurs
Alors je vois "~250 auditeurs" (arrondi à la dizaine supérieure)
Étant donné que j'écoute un live
Quand ma connexion passe de 4G à 3G
Alors la qualité audio s'adapte automatiquement Et je passe de 48 kbps à 24 kbps Opus Et la transition est transparente sans coupure
Étant donné que j'écoute un live en qualité standard 48 kbps Et que j'écoute pendant 1 heure
Alors j'ai consommé environ 21.6 MB de données mobiles Et cette consommation est affichée dans les paramètres de l'app
Étant donné que j'écoute un live depuis 30 minutes
Quand le créateur arrête le live
Alors je vois le message "Le live est terminé. Le replay sera disponible dans quelques minutes" Et le contenu suivant est automatiquement proposé après 2 secondes
Étant donné que j'ai écouté un live jusqu'à la fin Et que le replay est publié 8 minutes plus tard
Quand je rouvre l'application
Alors je vois une notification in-app "Le replay de [Titre] est maintenant disponible" Et je peux cliquer pour l'écouter immédiatement
Étant donné que je suis un utilisateur gratuit Et que j'écoute un live
Alors aucune publicité n'est insérée pendant le live Et la publicité apparaît seulement entre le live et le contenu suivant
Étant donné que j'écoute un live Et que ma vitesse est supérieure à 10 km/h
Alors l'interface tactile est désactivée pour la sécurité Et seules les commandes au volant sont actives (Play/Pause/Suivant)
Étant donné que j'écoute un live Et que ma vitesse est inférieure à 5 km/h
Alors l'interface tactile complète est disponible Et je peux liker, m'abonner, signaler via l'écran tactile
En tant que créateur Je veux démarrer une diffusion en direct Afin de partager du contenu audio en temps réel avec mes auditeurs
20 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et que je suis connecté en tant que créateur vérifié Et que j'ai les permissions de diffusion live
Étant donné que ma connexion upload est supérieure à 1 Mbps Et que j'ai autorisé l'accès au microphone Et que j'ai défini une zone de diffusion "Île-de-France"
Quand je lance les vérifications pré-live
Alors toutes les vérifications sont validées Et je peux démarrer le live
Étant donné que ma connexion upload est de 0.5 Mbps
Quand je lance les vérifications pré-live
Alors je vois un warning "Connexion insuffisante pour garantir une diffusion stable (minimum 1 Mbps)" Et je peux choisir de continuer quand même ou d'annuler
Étant donné que je n'ai pas autorisé l'accès au microphone
Quand j'essaie de démarrer un live
Alors je vois le message "Accès au microphone requis pour démarrer un live" Et je suis redirigé vers les paramètres système
Étant donné que je n'ai pas défini de zone de diffusion
Quand j'essaie de démarrer un live
Alors je vois le message "Veuillez définir une zone de diffusion avant de démarrer" Et je suis redirigé vers le formulaire de configuration du live
Étant donné que toutes les vérifications pré-live sont validées
Quand j'appuie sur "Démarrer live"
Alors je vois le message "Live démarre dans 15s... Testez votre micro" Et un compte à rebours de 15 secondes s'affiche Et mon flux audio est enregistré pendant ces 15 secondes Et le live n'est pas encore visible publiquement
Étant donné que j'ai démarré un live Et que le buffer de 15 secondes s'est écoulé
Alors le live devient public Et les auditeurs peuvent le rejoindre Et les abonnés dans la zone reçoivent une notification push
Étant donné que j'ai 1000 abonnés au total Et que 300 abonnés sont situés en Île-de-France Et que 700 abonnés sont situés hors Île-de-France
Quand mon live en Île-de-France devient public
Alors 300 abonnés reçoivent une notification push "🔴 [Mon pseudo] est en direct : [Titre live]" Et 700 abonnés ne reçoivent pas de notification
Quand je configure un nouveau live
Alors je dois renseigner:
| champ | format | validation |
|---|---|---|
| Titre | 5-100 caractères | Obligatoire |
| Tags | 1-3 centres intérêt | Sélection liste prédéfinie |
| Classification âge | Enum | Tout public / 13+ / 16+ / 18+ |
| Zone diffusion | Geo | Ville / Département / Région / National |
Quand j'essaie de créer un live avec le titre "Live"
Alors la validation échoue Et je vois le message "Le titre doit contenir entre 5 et 100 caractères"
Étant donné que j'ai rempli tous les champs sauf les tags
Quand j'essaie de démarrer le live
Alors la validation échoue Et je vois le message "Veuillez sélectionner entre 1 et 3 centres d'intérêt"
Étant donné que mon live dure depuis 7 heures et 30 minutes
Alors je vois un warning "Votre live se terminera dans 30 min" Et le message est affiché de manière non intrusive
Étant donné que mon live dure depuis 8 heures
Alors le live s'arrête automatiquement Et je vois le message "Durée maximale atteinte (8 heures). Vous pouvez redémarrer un nouveau live si nécessaire" Et le processus de traitement post-live démarre
Étant donné que je diffuse un concert en direct depuis une salle Et qu'un auditeur signale le contenu pour "Violation droits d'auteur"
Quand un modérateur écoute le live Et qu'il confirme la violation
Alors le live est arrêté immédiatement Et je reçois un Strike 2 (suspension 7 jours) Et je vois le message "Votre live a été interrompu pour violation des droits d'auteur" Et le replay n'est pas publié
Étant donné que je diffuse un match de football avec droits TV Et que le contenu est détecté par l'IA audio fingerprint
Quand la détection est confirmée
Alors le live est arrêté immédiatement Et je reçois un Strike 2 (suspension 7 jours)
Étant donné que je diffuse du contenu violent (agression physique) Et que 5 auditeurs signalent le contenu
Quand un modérateur vérifie en temps réel Et confirme la violence
Alors le live est coupé immédiatement Et mon compte est banni définitivement Et les autorités sont notifiées
Étant donné que mon live contient de la musique protégée en fond
Quand l'IA audio fingerprint détecte la violation après 2 minutes
Alors je reçois un avertissement en direct "Musique protégée détectée. Veuillez couper le son ou risquez un arrêt du live" Et j'ai 30 secondes pour corriger Et si je ne corrige pas, le live est arrêté avec Strike 1
Étant donné que je diffuse un live Et qu'un auditeur clique sur "Signaler"
Quand l'auditeur sélectionne la catégorie "Harcèlement"
Alors le signalement est envoyé en priorité HAUTE Et un modérateur peut écouter le live en temps réel Et le live continue pendant l'écoute de vérification
Étant donné que la plateforme héberge actuellement 2000 lives simultanés Et que c'est la limite de l'infrastructure actuelle
Quand j'essaie de démarrer un nouveau live
Alors je vois le message "Capacité maximale atteinte. Veuillez réessayer dans quelques minutes" Et ma demande est mise en file d'attente prioritaire si je suis créateur Premium
Étant donné que je n'ai jamais diffusé de live auparavant Et que j'ai moins de 3 contenus validés
Quand j'essaie de démarrer mon premier live
Alors je vois le message "Les lives sont disponibles après validation de vos 3 premiers contenus" Et le bouton "Démarrer live" est désactivé
Étant donné que j'ai 2 strikes actifs
Quand j'essaie de démarrer un live
Alors je vois le message "Fonctionnalité live temporairement indisponible suite à vos sanctions" Et je dois attendre la fin de ma suspension
En tant qu'utilisateur de RoadWave Je veux rechercher des contenus audio par mots-clés, localisation et filtres Afin de trouver facilement le contenu qui m'intéresse
55 scénarios (49 standards, 6 plans)
Contexte commun à tous les scénarios
Étant donné que l'application RoadWave est démarrée Et que l'utilisateur "jean@example.com" est connecté
Étant donné que la base contient les contenus suivants:
| titre | description | créateur |
|---|---|---|
| Balade à Paris | Visite du quartier Latin | @paris_stories |
| Secrets de Montmartre | Histoire de la butte | @explore_paris |
| Voyage en Normandie | Découverte des plages | @voyages_fr |
Quand l'utilisateur recherche "paris"
Alors 2 résultats sont retournés Et les résultats incluent "Balade à Paris" Et les résultats incluent "Secrets de Montmartre"
Étant donné un contenu avec le titre "Voyage en Bretagne"
Quand l'utilisateur recherche "voyages"
Alors le contenu "Voyage en Bretagne" est trouvé Et le stemming a transformé "voyages" en racine "voyag"
Étant donné un contenu avec le mot "
Quand l'utilisateur recherche "
Alors le contenu est trouvé grâce au stemming français
📊 Exemples de données:
| mot_original | recherche |
|---|---|
| voyage | voyages |
| voyager | voyage |
| balades | balade |
| historique | histoire |
Étant donné un contenu avec le titre "Découverte de l'Élysée"
Quand l'utilisateur recherche "decouverte elysee"
Alors le contenu est trouvé Et les accents sont normalisés automatiquement
Étant donné les contenus suivants:
| titre | description | créateur | tags |
|---|---|---|---|
| Voyage Paris | Balade sympa | @user1 | Tourisme |
| Balade Lyon | Voyage en ville | @paris_guide | Voyage |
Quand l'utilisateur recherche "paris"
Alors "Voyage Paris" est en première position Et "@paris_guide" apparaît en second
Étant donné les contenus suivants:
| titre | écoutes | rang_texte |
|---|---|---|
| Balade Paris | 50000 | 0.8 |
| Paris la nuit | 1000 | 0.9 |
Quand l'utilisateur recherche "paris"
Alors le score final combine rang_texte × (1 + log(écoutes + 1)) Et "Balade Paris" est mieux classé grâce à sa popularité
Étant donné que l'utilisateur commence à taper "par"
Quand 3 caractères sont saisis
Alors des suggestions apparaissent:
| suggestion |
|---|
| paris |
| parc naturel |
| parvis notre-dame |
Et le top 5 des suggestions est affiché
Étant donné que l'utilisateur a effectué les recherches suivantes:
| recherche | date |
|---|---|
| voyage paris | 2026-01-20 |
| audio-guide louvre | 2026-01-19 |
| podcast automobile | 2026-01-18 |
Quand l'utilisateur ouvre la barre de recherche
Alors les 10 dernières recherches sont affichées Et elles sont triées par date décroissante
Étant donné que l'utilisateur recherche "ballade paris" (faute d'orthographe) Et qu'aucun résultat n'est trouvé
Quand la page de résultats s'affiche
Alors une suggestion "Essayez plutôt : balade paris" est affichée
Étant donné qu'aucun résultat n'est trouvé pour une recherche
Quand la page s'affiche
Alors des suggestions populaires sont affichées:
| suggestion |
|---|
| balade paris |
| audio-guide louvre |
| visite montmartre |
Étant donné que l'utilisateur ouvre le filtre "Lieu"
Quand il tape "Louv"
Alors Nominatim retourne des suggestions:
| suggestion | type |
|---|---|
| Musée du Louvre, Paris | monument |
| Louvres, Val-d'Oise | commune |
Étant donné que l'utilisateur sélectionne "Paris, France" Et que les coordonnées sont (48.8566, 2.3522)
Quand il définit un rayon de 50 km
Alors la recherche PostGIS utilise ST_DWithin avec 50000 mètres
Étant donné un contenu à 30 km de Paris
Quand l'utilisateur recherche autour de Paris avec un rayon de
Alors le contenu est
📊 Exemples de données:
| rayon | résultat |
|---|---|
| 20 km | non trouvé |
| 50 km | trouvé |
| 100 km | trouvé |
Étant donné que l'utilisateur active le GPS Et que sa position est (48.8566, 2.3522)
Quand il sélectionne "Autour de moi"
Alors la recherche utilise ses coordonnées GPS actuelles Et un rayon par défaut de 10 km est appliqué
Étant donné que l'utilisateur ouvre le curseur de rayon
Quand il ajuste le curseur
Alors les valeurs disponibles vont de 5 km à 500 km Et la valeur s'affiche en temps réel "50 km"
Étant donné une recherche géographique autour de Paris Et un contenu à 2.3 km de distance
Quand les résultats sont affichés
Alors la distance "À 2.3 km" est indiquée pour chaque résultat
Étant donné des contenus à différentes distances de Paris:
| contenu | distance |
|---|---|
| Louvre Guide | 0.5 km |
| Tour Eiffel | 2.0 km |
| Versailles | 20 km |
Quand l'utilisateur trie par "Proximité"
Alors les résultats sont affichés dans l'ordre:
| position | contenu |
|---|---|
| 1 | Louvre Guide |
| 2 | Tour Eiffel |
| 3 | Versailles |
Étant donné que l'application est en phase MVP
Quand une requête de géocodage est effectuée
Alors l'API publique Nominatim est utilisée Et le rate limit de 1 req/s est respecté
Étant donné que Nominatim ne retourne aucun résultat
Quand l'application tente un fallback
Alors l'API Mapbox Geocoding est utilisée Et le coût de 0.50€ / 1000 requêtes est appliqué
Étant donné que l'utilisateur est sur la page de recherche
Quand il clique sur "Filtres"
Alors un panneau latéral s'ouvre Et 7 catégories de filtres sont affichées:
| catégorie |
|---|
| Type de contenu |
| Durée |
| Classification âge |
| Géo-pertinence |
| Tags |
| Date de publication |
| Abonnement |
Étant donné que l'utilisateur ouvre les filtres
Quand il sélectionne:
| type |
|---|
| Contenu court |
| Audio-guide |
Alors seuls ces types de contenus sont recherchés Et les podcasts et radios live sont exclus
Étant donné un contenu de
Quand l'utilisateur filtre par "
Alors le contenu est
📊 Exemples de données:
| durée | tranche | résultat |
|---|---|---|
| 3 | <5 min | trouvé |
| 3 | 5-15 min | non trouvé |
| 10 | 5-15 min | trouvé |
| 20 | 15-30 min | trouvé |
| 45 | >30 min | trouvé |
Étant donné des contenus avec différentes classifications:
| contenu | classification |
|---|---|
| Conte enfants | Tout public |
| Podcast news | 13+ |
| Débat politique | 16+ |
Quand l'utilisateur filtre "Tout public"
Alors seul "Conte enfants" est affiché
Étant donné des contenus avec différents types géo:
| contenu | type_geo |
|---|---|
| Guide Louvre | Ancré |
| Podcast Paris | Contextuel |
| News nationales | Neutre |
Quand l'utilisateur filtre "Ancré, Contextuel"
Alors "Guide Louvre" et "Podcast Paris" sont affichés Et "News nationales" est exclu
Étant donné des contenus taggés:
| contenu | tags |
|---|---|
| Voyage en Italie | Voyage, Gastronomie |
| Histoire de Rome | Voyage, Histoire |
| Économie italienne | Économie |
Quand l'utilisateur sélectionne les tags "Voyage, Histoire"
Alors "Histoire de Rome" est en priorité (2 tags correspondants) Et "Voyage en Italie" est affiché (1 tag correspondant) Et "Économie italienne" est exclu
Étant donné un contenu publié il y a
Quand l'utilisateur filtre par "
"
Alors le contenu est
📊 Exemples de données:
| délai | période | résultat |
|---|---|---|
| 12 heures | Dernières 24h | trouvé |
| 3 jours | Cette semaine | trouvé |
| 15 jours | Ce mois | trouvé |
| 8 mois | Cette année | trouvé |
| 2 ans | Toutes dates | trouvé |
| 2 ans | Cette année | non trouvé |
Étant donné des contenus gratuits et Premium:
| contenu | type |
|---|---|
| Balade Paris | Gratuit |
| Visite VIP Louvre | Premium |
Quand l'utilisateur filtre "Premium uniquement 👑"
Alors seul "Visite VIP Louvre" est affiché
Étant donné que l'utilisateur applique les filtres:
| filtre | valeur |
|---|---|
| Type | Audio-guide |
| Durée | 5-15 min |
| Tags | Voyage |
| Classification | Tout public |
Quand la recherche est lancée
Alors seuls les contenus respectant TOUS les critères sont affichés
Étant donné que l'utilisateur a appliqué 5 filtres différents
Quand il clique sur "Réinitialiser"
Alors tous les filtres sont désactivés Et la recherche affiche tous les résultats
Étant donné que l'utilisateur a appliqué plusieurs filtres
Quand il clique sur "💾 Sauvegarder cette recherche" Et qu'il entre le nom "Podcasts voyage Paris"
Alors la recherche est sauvegardée Et elle apparaît dans l'onglet "Recherches sauvegardées"
Étant donné que l'utilisateur a déjà 5 recherches sauvegardées
Quand il tente de sauvegarder une 6ème recherche
Alors un message d'erreur s'affiche Et il doit supprimer une recherche existante avant d'en ajouter une nouvelle
Étant donné une recherche sauvegardée "Podcasts voyage Paris" Et que l'utilisateur a activé les notifications
Quand 3 nouveaux contenus correspondants sont publiés
Alors une notification "3 nouveaux contenus dans 'Podcasts voyage Paris'" est envoyée
Étant donné une recherche avec plusieurs résultats
Quand l'utilisateur sélectionne le tri "
Alors les résultats sont triés selon
📊 Exemples de données:
| option | algorithme |
|---|---|
| Pertinence | Score recherche × (1 + log(écoutes + 1)) |
| Popularité | Écoutes complètes derniers 30j DESC |
| Récent | Date publication DESC |
| Proximité | Distance GPS ASC (si recherche géo) |
| Durée | Durée audio ASC ou DESC |
Étant donné un résultat de recherche
Quand la page est affichée
Alors chaque résultat contient:
| élément | exemple |
|---|---|
| Cover image | 120×68 px (16:9) |
| Titre | Balade à Paris (2 lignes max) |
| Créateur | @paris_stories ✓ |
| Durée | 12 min |
| Écoutes | 🎧 2.3K |
| Localisation | 📍 Paris 5e · Ancré |
| Tags | 🏷️ #Voyage #Histoire |
| Badge Premium | 👑 (si applicable) |
| Distance | À 2.3 km (si recherche géo) |
| Bouton lecture | ▶️ Écouter |
| Menu contextuel | ⋮ |
Étant donné une page avec 20 résultats de recherche
Quand la page se charge
Alors seules les 5 premières images sont chargées Et les images suivantes se chargent au scroll
Étant donné un contenu avec un titre de 120 caractères
Quand le résultat est affiché
Alors le titre est tronqué après 2 lignes Et "..." est ajouté à la fin
Étant donné un résultat de recherche pour "@paris_stories"
Quand l'utilisateur clique sur "@paris_stories"
Alors il est redirigé vers "https://roadwave.fr/@paris_stories"
Étant donné que l'utilisateur clique sur [⋮] pour un résultat
Quand le menu s'ouvre
Alors les actions suivantes sont disponibles:
| action |
|---|
| Partager |
| Ajouter à une playlist |
| Télécharger (offline) |
| Signaler |
Étant donné une recherche retournant 100 résultats
Quand la page est affichée
Alors 20 résultats sont chargés initialement Et un indicateur "1-20 sur 100 résultats" est visible
Étant donné que l'utilisateur scroll dans les résultats
Quand il atteint 80% de la page
Alors les 20 résultats suivants sont chargés automatiquement Et un loader est affiché pendant le chargement
Étant donné que l'infinite scroll est désactivé (paramètres)
Quand l'utilisateur atteint la fin de la page
Alors un bouton "Charger 20 suivants" est affiché Et les résultats se chargent au clic
Étant donné que l'utilisateur est sur la page de résultats
Quand il clique sur le toggle "Liste / Carte"
Alors la vue carte Leaflet s'affiche Et les résultats sont affichés comme markers sur la carte
Étant donné que la vue carte est activée
Quand la carte se charge
Alors la carte utilise les tuiles OpenStreetMap Et le centre est la position de recherche (ou GPS utilisateur) Et le zoom initial montre tous les résultats
Étant donné que 10 résultats sont affichés sur la carte
Quand l'utilisateur clique sur un marker
Alors une popup s'affiche avec:
| élément |
|---|
| Titre |
| Créateur |
| Durée |
| Distance |
| Bouton ▶️ Écouter |
Étant donné que 50 résultats sont très proches géographiquement
Quand la carte est affichée
Alors les markers proches sont groupés en clusters Et le nombre de contenus est affiché sur le cluster Et le cluster se décompose au zoom
Étant donné que l'utilisateur est en vue carte
Quand il clique sur un marker et écoute le contenu Et qu'il rebascule en vue liste
Alors le contenu écouté est marqué dans la liste Et la position de scroll est maintenue
Étant donné que la base contient 100K contenus
Quand une recherche full-text est effectuée
Alors l'index GIN sur to_tsvector est utilisé Et la requête retourne en moins de 100ms
Étant donné une recherche géographique avec rayon 50 km
Quand la requête PostGIS ST_DWithin est exécutée
Alors l'index GIST sur la colonne location est utilisé Et la requête retourne en moins de 50ms
Étant donné une recherche avec filtres multiples
Quand les filtres type, durée, âge, géo, date sont appliqués
Alors l'index composite idx_content_filters est utilisé Et les performances restent optimales
Étant donné une recherche filtrée par tags "Voyage, Histoire"
Quand la requête est exécutée
Alors l'index GIN sur la colonne tags est utilisé Et la recherche est performante même avec 500K contenus
Étant donné que l'utilisateur recherche "xyzabc123"
Quand aucun résultat n'est trouvé
Alors un message "Aucun résultat pour 'xyzabc123'" s'affiche Et des suggestions de recherches populaires sont proposées
Étant donné que l'utilisateur clique sur "Rechercher" sans saisir de texte
Quand la recherche est lancée
Alors un message "Veuillez entrer au moins 2 caractères" s'affiche
Étant donné que l'API Nominatim est indisponible
Quand l'utilisateur tente une recherche géographique
Alors un message "Service de localisation temporairement indisponible" s'affiche Et la recherche continue sans filtre géographique
Étant donné que l'utilisateur a désactivé le GPS
Quand il sélectionne "Autour de moi"
Alors un message "Veuillez activer la localisation" s'affiche Et un bouton "Activer" ouvre les paramètres système
Étant donné qu'une recherche complexe est lancée
Quand la requête dépasse 10 secondes
Alors la recherche est annulée Et un message "La recherche a pris trop de temps, veuillez réessayer" s'affiche
En tant que plateforme de contenu géolocalisé Je veux classifier les contenus selon leur pertinence géographique Afin d'adapter l'algorithme de recommandation
10 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible
Étant donné que je suis un créateur connecté
Quand je publie un audio-guide de la Tour Eiffel Et que je choisis la classification "Géo-ancré"
Alors le contenu est enregistré avec:
| champ | valeur |
|---|---|
| type_geo | geo_ancre |
| ponderation_geo | 0.7 |
| ponderation_interets | 0.1 |
Étant donné que je suis un créateur connecté
Quand je publie une actualité régionale en Bretagne Et que je choisis la classification "Géo-contextuel"
Alors le contenu est enregistré avec:
| champ | valeur |
|---|---|
| type_geo | geo_contextuel |
| ponderation_geo | 0.5 |
| ponderation_interets | 0.3 |
Étant donné que je suis un créateur connecté
Quand je publie un podcast de philosophie Et que je choisis la classification "Géo-neutre"
Alors le contenu est enregistré avec:
| champ | valeur |
|---|---|
| type_geo | geo_neutre |
| ponderation_geo | 0.2 |
| ponderation_interets | 0.6 |
Étant donné que je crée un contenu audio
Quand j'essaie de publier sans sélectionner de type géographique
Alors la publication échoue Et je vois le message "Vous devez sélectionner un type de géo-pertinence"
Étant donné qu'un contenu podcast générique est classifié "Géo-ancré" Et que le modérateur examine le contenu
Quand le modérateur le reclassifie en "Géo-neutre"
Alors la nouvelle classification est appliquée immédiatement Et l'algorithme utilise la pondération géo = 0.2 Et le créateur reçoit une notification de reclassification
Étant donné que j'ai publié un contenu classifié "Géo-contextuel" Et que je réalise qu'il devrait être "Géo-neutre"
Quand je modifie la classification en "Géo-neutre"
Alors la modification est enregistrée Et l'algorithme utilise la nouvelle pondération Et je vois le message "Classification modifiée avec succès"
Étant donné que je suis un créateur Et que j'ai publié 30 contenus:
| type | nombre |
|---|---|
| Géo-ancré | 10 |
| Géo-contextuel | 15 |
| Géo-neutre | 5 |
Quand je consulte mes statistiques
Alors je vois la répartition de mes classifications Et des suggestions pour optimiser la portée
Étant donné qu'un audio-guide "Géo-ancré" existe à la Tour Eiffel Et qu'un utilisateur est à 100m de la Tour Eiffel
Quand l'algorithme calcule le score
Alors la pondération géo est de 0.7 Et le score géo est proche de 1 (très proche) Et le contenu a un score final élevé
Étant donné qu'un podcast philosophie "Géo-neutre" existe à Paris Et qu'un utilisateur est à Marseille (750 km)
Quand l'algorithme calcule le score
Alors la pondération géo est de 0.2 Et le score géo est bas (distance élevée) Mais le score intérêts (0.6) peut compenser Et le contenu peut quand même être recommandé si intérêts match
Étant donné 3 contenus au même endroit (Paris):
| type | ponderation_geo |
|---|---|
| Géo-ancré | 0.7 |
| Géo-contextuel | 0.5 |
| Géo-neutre | 0.2 |
Et qu'un utilisateur est à 50 km de Paris
Quand l'algorithme calcule les scores
Alors le contenu "Géo-ancré" a le score géo le plus élevé Et le contenu "Géo-neutre" a le score géo le plus faible Mais peut avoir un score final plus élevé si forte correspondance intérêts
En tant qu'utilisateur Je veux pouvoir filtrer le contenu politique Afin de contrôler mon exposition à ce type de contenu
13 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible
Étant donné que je suis un créateur connecté
Quand je publie un contenu sur un débat politique Et que je sélectionne le tag "Politique"
Alors le contenu est enregistré avec le tag "Politique" Et aucune classification gauche/droite n'est demandée (MVP)
Étant donné que je crée un contenu
Quand je consulte la liste des tags disponibles
Alors je vois les tags suivants au même niveau:
| tag |
|---|
| Économie |
| Sport |
| Culture |
| Politique |
| Automobile |
| Voyage |
| Musique |
Étant donné que je suis un nouvel utilisateur Et que je n'ai pas modifié les paramètres de contenu politique
Quand je demande des recommandations
Alors les contenus tagués "Politique" sont inclus normalement Et aucun filtrage n'est appliqué
Étant donné que je suis connecté
Quand j'active l'option "Masquer contenu politique" dans les paramètres
Alors tous les contenus tagués "Politique" sont exclus de mes recommandations Et je vois le message "Contenu politique masqué"
Étant donné que j'ai activé "Masquer contenu politique" Et qu'il existe 100 contenus dont 20 tagués "Politique"
Quand je demande 50 recommandations
Alors je reçois 50 contenus parmi les 80 non-politiques Et 0% de contenus politiques sont proposés
Étant donné que j'ai activé "Masquer contenu politique"
Quand je désactive cette option dans les paramètres
Alors les contenus politiques sont à nouveau inclus dans mes recommandations Et le filtrage est levé immédiatement
Étant donné que je suis un utilisateur de 14 ans Et que le mode Kids est activé
Quand je demande des recommandations
Alors tous les contenus tagués "Politique" sont automatiquement exclus Et ce indépendamment du paramètre "Masquer contenu politique"
Étant donné que je suis un créateur Et que j'ai publié 20 contenus dont 5 tagués "Politique"
Quand je consulte mes statistiques
Alors je vois le nombre d'utilisateurs ayant masqué le contenu politique Et le taux d'engagement comparé aux autres tags
Étant donné que je recherche du contenu
Quand je filtre par tag "Politique"
Alors seuls les contenus tagués "Politique" sont affichés Et ce même si j'ai activé "Masquer contenu politique" (recherche explicite)
Étant donné que j'ai activé "Masquer contenu politique" Et qu'un ami me partage un lien vers un contenu tagué "Politique"
Quand j'ouvre le lien
Alors je peux accéder au contenu (partage explicite) Et je vois un avertissement "Ce contenu est tagué Politique"
Étant donné que je suis un créateur
Quand je publie un contenu tagué "Politique"
Alors aucune option de classification idéologique n'est proposée Et je ne peux pas indiquer "Gauche", "Droite", "Centre", etc.
Étant donné qu'un utilisateur écoute majoritairement du contenu politique de gauche
Quand l'algorithme génère des recommandations
Alors aucun équilibrage droite/gauche n'est appliqué Et les recommandations suivent l'algorithme standard (intérêts, géo, engagement)
Étant donné que RoadWave passe en phase post-MVP Et que la classification politique avancée est activée
Quand je me connecte
Alors je reçois une notification m'informant des nouvelles options Et je peux configurer mes préférences d'équilibrage politique
En tant qu'utilisateur en voiture Je veux recevoir des notifications de contenus géolocalisés au bon moment Afin de découvrir du contenu contextuel sans distraction au volant
36 scénarios (32 standards, 4 plans)
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et que l'application est ouverte (premier plan) Et que le GPS est activé Et que l'utilisateur est en mode voiture (vitesse ≥ 5 km/h)
Étant donné qu'un contenu géolocalisé existe à la Tour Eiffel (48.8584, 2.2945) Et que je me déplace à 50 km/h vers ce point Et que je suis à 98 mètres du point (ETA = 7 secondes)
Quand le système calcule l'ETA
Alors une notification est déclenchée immédiatement Et le compteur "7" s'affiche avec l'icône 🏛️ Et une notification sonore (bip court) est jouée
Étant donné qu'un contenu géolocalisé existe à un point GPS
Et que je me déplace à
Quand je suis à
Alors l'ETA calculé est
📊 Exemples de données:
| vitesse | distance | eta | notification |
|---|---|---|---|
| 10 | 19 | 7 | Oui |
| 50 | 98 | 7 | Oui |
| 130 | 252 | 7 | Oui |
| 50 | 200 | 14 | Non |
| 10 | 50 | 18 | Non |
Étant donné qu'un contenu géolocalisé existe à 30m de ma position Et que ma vitesse est 3 km/h (arrêté à un feu rouge)
Quand le système détecte cette situation
Alors une notification est déclenchée immédiatement Et je n'ai pas besoin d'attendre le calcul ETA
Étant donné qu'une notification géolocalisée est déclenchée
Quand la notification s'affiche
Alors les éléments suivants sont visibles:
| élément | présent |
|---|---|
| Icône du tag | ✅ |
| Compteur 7→1 | ✅ |
| Son bref (bip) | ✅ |
| Titre texte | ❌ |
| Description | ❌ |
| Cover image | ❌ |
| Bouton Annuler | ❌ |
Étant donné qu'un contenu géolocalisé avec le tag
Quand la notification s'affiche
Alors l'icône
📊 Exemples de données:
| tag | icone |
|---|---|
| Culture générale | 🏛️ |
| Histoire | 📜 |
| Voyage | ✈️ |
| Famille | 👨👩👧 |
| Musique | 🎵 |
| Sport | ⚽ |
| Technologie | 💻 |
| Automobile | 🚗 |
Étant donné qu'une notification géolocalisée s'affiche
Quand le compteur démarre
Alors le compteur affiche "7" Et après 1 seconde, il affiche "6" Et après 2 secondes, il affiche "5" Et après 3 secondes, il affiche "4" Et après 4 secondes, il affiche "3" Et après 5 secondes, il affiche "2" Et après 6 secondes, il affiche "1" Et après 7 secondes, la notification disparaît
Étant donné que l'application est connectée à CarPlay Et qu'un contenu géolocalisé est détecté (ETA 7s)
Quand la notification est déclenchée
Alors seule la notification sonore (bip) est jouée Et aucun overlay visuel n'est affiché (icône, compteur) Et l'utilisateur peut valider via le bouton "Suivant" au volant
Étant donné que l'application est connectée à Android Auto Et qu'un contenu géolocalisé est détecté (ETA 7s)
Quand la notification est déclenchée
Alors seule la notification sonore (bip) est jouée Et aucun overlay visuel n'est affiché Et l'utilisateur peut valider via le bouton "Suivant" au volant
Étant donné que l'application n'est PAS connectée à CarPlay/Android Auto Et qu'un contenu géolocalisé est détecté
Quand la notification est déclenchée
Alors la notification sonore (bip) est jouée Et l'overlay visuel s'affiche (icône + compteur 7→1)
Étant donné qu'une notification géolocalisée est affichée (compteur à 5) Et que j'écoute un podcast
Quand j'appuie sur le bouton "Suivant"
Alors le compteur bascule à "5" (décompte final) Et le contenu actuel continue de jouer normalement Et le compteur décrémente: 5→4→3→2→1 Et après 5 secondes, le contenu géolocalisé démarre (fade in 0.3s)
Étant donné que le décompte atteint "0"
Quand le contenu géolocalisé doit démarrer
Alors le contenu actuel fait un fade out de 0.3s Et le contenu géolocalisé fait un fade in de 0.3s Et il n'y a pas de silence entre les deux
Étant donné que j'ai validé la notification (décompte 5s démarre) Et que mon contenu actuel se termine après 2 secondes
Quand le contenu actuel se termine
Alors le contenu suivant du buffer démarre immédiatement Et le décompte continue (3→2→1) Et à la fin du décompte, le contenu géolocalisé remplace le buffer
Étant donné qu'une notification géolocalisée s'affiche (compteur 7)
Quand 7 secondes s'écoulent sans que j'appuie sur "Suivant"
Alors la notification disparaît automatiquement Et le contenu géolocalisé est perdu (pas d'insertion dans la file) Et un cooldown de 10 minutes est activé
Étant donné que j'ai validé 6 notifications géolocalisées dans la dernière heure
Quand un 7ème contenu géolocalisé est détecté
Alors aucune notification n'est envoyée Et le contenu n'est pas inséré dans la file
Étant donné que j'ai validé 6 contenus géolocalisés Et que le premier contenu a été validé il y a 61 minutes
Quand un nouveau contenu géolocalisé est détecté
Alors la notification est envoyée (quota libéré : 5/6) Et le compteur horaire est mis à jour
Étant donné que
Quand un nouveau contenu géolocalisé est détecté
Alors la notification est
📊 Exemples de données:
| nb_valides | action |
|---|---|
| 0 | envoyée |
| 3 | envoyée |
| 5 | envoyée |
| 6 | non envoyée |
| 7 | non envoyée |
Étant donné que j'ai démarré un audio-guide avec 8 séquences Et que cet audio-guide compte comme 1 contenu dans le quota
Quand toutes les séquences de l'audio-guide sont lues
Alors mon quota reste à 1/6 Et je peux encore valider 5 contenus géolocalisés simples
Étant donné qu'une notification géolocalisée a été ignorée (pas de clic) Et qu'un cooldown de 10 minutes est activé
Quand 5 minutes s'écoulent Et qu'un nouveau contenu géolocalisé est détecté
Alors aucune notification n'est envoyée (cooldown actif)
Étant donné qu'un cooldown a été activé il y a 10 minutes
Quand un nouveau contenu géolocalisé est détecté
Alors la notification est envoyée (cooldown expiré)
Étant donné qu'une notification géolocalisée est affichée
Quand j'appuie sur "Suivant" dans les 7 secondes
Alors aucun cooldown n'est activé Et la prochaine notification pourra être envoyée normalement
Étant donné que j'écoute un contenu du buffer Et que j'ai validé un contenu géolocalisé "Tour Eiffel" Et que j'ai écouté 42 secondes du contenu géolocalisé
Quand j'appuie sur "Suivant" (skip) Et que j'appuie ensuite sur "Précédent"
Alors le contenu géolocalisé reprend à 42 secondes
Étant donné qu'une notification géolocalisée a été ignorée
Quand j'appuie sur "Précédent"
Alors le contenu géolocalisé ignoré n'apparaît PAS dans l'historique Et je reviens au contenu d'avant
Étant donné que j'ai validé une notification (décompte 5s en cours) Et que le compteur affiche "3"
Quand j'appuie à nouveau sur "Suivant"
Alors le décompte est annulé Et le contenu suivant du buffer démarre Et le contenu géolocalisé n'entre PAS dans l'historique
Étant donné que je suis en mode voiture Et que ma vitesse passe à 3 km/h
Quand cette vitesse reste stable pendant 10 secondes
Alors le mode piéton est activé automatiquement Et les notifications passent en mode push arrière-plan (si permission accordée)
Étant donné que je suis en mode piéton Et que ma vitesse passe à 15 km/h
Quand cette vitesse reste stable pendant 10 secondes
Alors le mode voiture est activé automatiquement Et les notifications passent en mode sonore + icône (app premier plan requise)
Étant donné que ma vitesse passe de 20 km/h à 3 km/h (arrêt feu rouge) Et que ma vitesse remonte à 20 km/h après 8 secondes
Quand le système vérifie le mode
Alors aucun basculement n'a lieu (hysteresis de 10s non atteinte) Et je reste en mode voiture
Étant donné que je bascule de voiture à piéton
Quand le basculement est effectué
Alors les paramètres suivants changent:
| paramètre | voiture | piéton |
|---|---|---|
| App requise | Premier plan | Arrière-plan OK |
| Notification | Sonore + icône + compteur | Push système |
| Rayon détection | ETA 7s (variable) | 200m fixes |
| Type contenu | Tous géolocalisés | Audio-guides uniquement |
Étant donné que je roule à 130 km/h (36.1 m/s) Et qu'un contenu géolocalisé est à 252 mètres
Quand l'ETA de 7s est atteint Et que je valide la notification
Alors le décompte 5s démarre Et le contenu géolocalisé démarre encore avant le point GPS (72m avant)
Étant donné que 3 châteaux sont espacés de 800m chacun Et que je valide la notification du Château A
Quand j'arrive près du Château B (57s plus tard à 50 km/h)
Alors la notification du Château B est envoyée (quota 2/6, pas de cooldown)
Étant donné que je me gare à 30m d'un château Et que ma vitesse est <1 km/h pendant 2 minutes
Quand le mode stationnement est détecté
Alors aucune notification de contenu géolocalisé n'est envoyée Et le système bascule automatiquement en mode piéton
Étant donné que je suis en mode stationnement Et que ma vitesse passe à 20 km/h pendant 10 secondes
Quand le système détecte la reprise de conduite
Alors le mode voiture est réactivé Et les notifications géolocalisées reprennent (si quota non atteint)
Étant donné qu'un contenu géolocalisé simple existe à un point GPS
Quand la notification est déclenchée (ETA 7s) Et que je valide
Alors le contenu démarre après décompte 5s Et à la fin du contenu, le buffer normal reprend Et ce contenu compte 1/6 dans le quota
Étant donné qu'un audio-guide avec 8 séquences existe
Quand je démarre l'audio-guide Et que les séquences s'enchaînent automatiquement (GPS ou manuel)
Alors l'audio-guide entier compte 1/6 dans le quota Et les séquences ne déclenchent PAS de notification avec compteur 7s Et elles se déclenchent au point GPS exact (rayon 30m)
Étant donné que je suis en mode voiture
Quand le GPS est désactivé
Alors aucune notification géolocalisée ne peut être envoyée Et un message d'erreur s'affiche: "GPS requis pour les contenus géolocalisés"
Étant donné que je suis en mode voiture Et que l'app passe en arrière-plan
Quand un contenu géolocalisé est détecté
Alors aucune notification n'est envoyée (app premier plan requise) Et le contenu n'est pas perdu (sera proposé si app rouverte dans le rayon)
Étant donné que je refuse la permission "Always Location"
Quand ma vitesse passe <5 km/h
Alors le mode piéton n'est PAS activé Et le mode voiture reste actif (avec permission "When In Use") Et aucune notification arrière-plan n'est envoyée
En tant que système de recommandation Je veux gérer l'historique d'écoute intelligemment Afin d'éviter les répétitions et offrir une découverte maximale
19 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible
Étant donné qu'un utilisateur a écouté un contenu à 85%
Quand l'algorithme génère les recommandations
Alors ce contenu n'est jamais reproposé Et il est marqué comme "écouté" dans l'historique
Étant donné qu'un utilisateur a écouté un contenu exactement à 80%
Quand l'algorithme génère les recommandations
Alors ce contenu n'est pas reproposé (seuil >= 80%)
Étant donné qu'un utilisateur a skippé un contenu après 8 secondes
Quand l'algorithme génère les recommandations
Alors ce contenu n'est pas reproposé (signal négatif fort) Et la jauge d'intérêt correspondante est réduite de 0.5%
Étant donné qu'un utilisateur a skippé un contenu après exactement 10 secondes
Quand l'algorithme génère les recommandations
Alors ce contenu n'est pas reproposé (seuil < 10s strict)
Étant donné qu'un utilisateur a écouté un contenu à 45% Et qu'il est arrivé à la position 2:30 (150 secondes)
Quand l'algorithme propose à nouveau ce contenu
Alors le contenu peut être reproposé Et la position de reprise est 150 secondes Et l'utilisateur voit "Reprendre à 2:30"
Étant donné qu'un utilisateur a écouté un contenu à 11%
Quand l'algorithme génère les recommandations
Alors ce contenu peut être reproposé (>10%) Et la position de reprise est sauvegardée
Étant donné qu'un utilisateur a écouté un contenu à 79%
Quand l'algorithme génère les recommandations
Alors ce contenu peut être reproposé (<80%) Et l'utilisateur peut terminer l'écoute
Étant donné qu'un audio-guide a le flag "replayable = true" Et qu'un utilisateur l'a écouté à 95%
Quand l'algorithme génère les recommandations
Alors l'audio-guide peut être reproposé Et il est marqué comme "Écouté - Rejouable"
Étant donné qu'un podcast n'a pas de flag replayable Et qu'un utilisateur l'a écouté à 90%
Quand l'algorithme génère les recommandations
Alors le podcast n'est jamais reproposé
Étant donné qu'un utilisateur écoute un contenu
Quand l'écoute se termine ou est skippée
Alors les données suivantes sont enregistrées:
| champ | exemple |
|---|---|
| user_id | user-123 |
| content_id | content-456 |
| completion_rate | 0.45 (45%) |
| last_position | 150 (secondes) |
| listened_at | 2026-01-21 14:30:00 |
Étant donné qu'un utilisateur a écouté 5000 contenus
Quand il consulte son historique
Alors tous les 5000 contenus sont disponibles Et aucun contenu n'est supprimé automatiquement
Étant donné qu'un utilisateur a écouté 500 contenus
Quand l'algorithme génère les recommandations
Alors il vérifie uniquement les 100 derniers contenus pour exclusion Et cette limite est une optimisation de requête SQL
Étant donné qu'un utilisateur demande l'export RGPD
Quand l'export est généré
Alors l'historique complet est inclus avec:
| information | inclus |
|---|---|
| Tous les contenus | ✅ |
| Dates d'écoute | ✅ |
| Taux complétion | ✅ |
| Positions reprise | ✅ |
Étant donné que j'ai écouté un podcast à 60% (position 10:00)
Quand ce podcast est reproposé par l'algorithme Et que je lance la lecture
Alors l'écoute reprend automatiquement à 10:00 Et je vois une notification "Reprise à 10:00"
Étant donné que j'ai écouté un podcast à 60%
Quand ce podcast est reproposé
Alors je vois deux options:
| option | action |
|---|---|
| Reprendre à 10:00 | Lecture à partir de 10:00 |
| Depuis le début | Lecture à partir de 0:00 |
Étant donné qu'un utilisateur a écouté un contenu il y a 6 mois à 90%
Quand l'algorithme génère les recommandations
Alors ce contenu n'est toujours pas reproposé Et l'historique n'a pas de limite temporelle
Étant donné qu'un utilisateur a écouté un contenu de "Créateur A" à 90% Et que "Créateur A" publie un nouveau contenu
Quand l'algorithme génère les recommandations
Alors le nouveau contenu peut être recommandé Et seul l'ancien contenu est exclu (pas tout le créateur)
Étant donné que je consulte mon profil
Quand j'accède à la section "Historique"
Alors je vois:
| métrique | exemple |
|---|---|
| Nombre total d'écoutes | 1,234 |
| Heures écoutées | 456h |
| Taux complétion moyen | 72% |
| Top 5 catégories | Voyage, Sport |
Étant donné que je consulte mon historique
Quand je filtre par "Dernière semaine"
Alors seuls les contenus écoutés dans les 7 derniers jours sont affichés Et je peux exporter cette sélection
En tant que média établi Je veux publier du contenu géolocalisé sur RoadWave Afin d'atteindre une audience locale et mobile
21 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible
Étant donné que je représente Le Monde
Quand je crée un compte média Et que je fournis les justificatifs (SIRET, documents officiels)
Alors mon compte est créé en attente de vérification Et l'équipe RoadWave examine ma demande sous 48-72h
Étant donné qu'un compte média "Le Parisien" est en attente
Quand l'équipe RoadWave valide le compte
Alors le compte reçoit le badge vérifié ✓ Et le média peut publier sans validation des 3 premiers contenus Et je vois le message "Compte média vérifié avec succès"
Étant donné que "France Inter" a un compte vérifié
Quand un utilisateur consulte le profil
Alors il voit le badge ✓ à côté du nom Et une mention "Média vérifié"
Étant donné que je suis un média vérifié
Quand je publie mon premier contenu
Alors le contenu est publié immédiatement sans validation Et il est visible pour tous les utilisateurs Et je ne passe pas par la modération initiale
Étant donné que "Libération" publie un contenu
Quand le contenu est publié
Alors il est immédiatement disponible Mais peut être signalé et modéré a posteriori Et suit les mêmes règles de modération que les créateurs
Étant donné que je suis "Ouest-France" (média régional)
Quand je publie un flash info sur un événement à Rennes Et que je le géolocalise en Bretagne (géo-contextuel)
Alors le contenu est publié immédiatement Et il est recommandé aux utilisateurs en Bretagne
Étant donné que je suis "France Culture"
Quand je publie une chronique philosophie (géo-neutre)
Alors le contenu est disponible partout en France Et suit l'algorithme de recommandation standard
Étant donné que je suis "Le Figaro"
Quand je publie un édito politique Et que je le tague "Politique"
Alors le contenu est publié immédiatement Et la classification politique MVP s'applique (pas gauche/droite) Et les utilisateurs ayant activé "Masquer politique" ne le voient pas
Étant donné que je suis un média vérifié
Quand je publie du contenu
Alors je peux publier:
| format | exemple |
|---|---|
| Flash info géolocalisé | Actualité régionale 2-5 min |
| Chronique thématique | Culture, économie, sport 5-15min |
| Édito et débats | Opinion 10-30 min |
| Reportage | Investigation 15-45 min |
Étant donné que je suis "RTL"
Quand je publie un contenu sensible
Alors je dois obligatoirement classifier par âge:
| classification | type contenu |
|---|---|
| Tout public | Info générale |
| 13+ | Actualité avec sujets sensibles |
| 16+ | Débats avec violence verbale |
| 18+ | Sujets adultes |
Étant donné que je suis un média vérifié Et que mes contenus génèrent des écoutes
Quand le mois se termine
Alors je reçois 3€ / 1000 écoutes complètes (même taux que créateurs) Et le paiement suit les mêmes règles (seuil 50€, mensuel)
Étant donné que je suis "Europe 1" Et que je veux intégrer un sponsor dans mon contenu
Quand je mentionne le sponsor dans l'audio
Alors c'est autorisé (sponsoring éditorial) Mais RoadWave ne gère pas la transaction Et je gère la relation sponsor directement
Étant donné que je suis "Le Monde"
Quand je veux créer des sous-comptes par rubrique
Alors je peux créer:
| compte | description |
|---|---|
| @lemonde_politique | Actualité politique |
| @lemonde_economie | Économie et entreprises |
| @lemonde_culture | Culture et spectacles |
Et tous sont liés au compte média principal
Étant donné que "Sud-Ouest" publie du contenu géo-contextuel en Nouvelle-Aquitaine Et qu'un utilisateur est à Bordeaux
Quand l'algorithme calcule les recommandations
Alors le contenu de "Sud-Ouest" a un score géo élevé Et il est privilégié pour l'audience locale
Étant donné que "France Inter" publie un podcast géo-neutre
Quand des utilisateurs à Paris, Lyon, Marseille demandent des recommandations
Alors le podcast est accessible partout sans distinction géographique Et suit l'algorithme de recommandation standard
Étant donné que je suis un média vérifié
Quand je consulte mes statistiques
Alors je vois:
| métrique | exemple |
|---|---|
| Écoutes par région | Île-de-France: 45% |
| Taux complétion | 72% |
| Démographie auditeurs | 25-34 ans: 35% |
| Top contenus | Flash info Paris |
| Revenus générés | 1,234€ |
Étant donné que je suis "Libération"
Quand je clique sur "Exporter analytics"
Alors je reçois un CSV avec données détaillées Et je peux analyser les données avec mes outils internes
Étant donné que je suis un média vérifié
Quand j'ai un problème technique ou question
Alors je peux contacter le support média prioritaire Et j'obtiens une réponse sous 24h (vs 48-72h standard)
Étant donné que je suis "France Culture"
Quand je prépare un contenu à l'avance
Alors je peux programmer la publication pour une date/heure future Et le contenu sera publié automatiquement au moment choisi
Étant donné que je suis un grand média avec beaucoup de contenus
Quand RoadWave développe l'API médias
Alors je peux automatiser la publication via API Et intégrer RoadWave dans mon workflow de production
Étant donné qu'un contenu de "Le Monde" est signalé
Quand le signalement arrive en modération
Alors il est traité avec la même priorité qu'un créateur standard Et le badge vérifié ne donne pas d'immunité modération
En tant que parent ou adolescent Je veux activer un mode Kids avec filtrage de contenu Afin de protéger les mineurs des contenus inappropriés
15 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible
Étant donné que je suis un utilisateur de 14 ans Et que le mode Kids n'est pas activé par défaut
Quand j'active le mode Kids dans les paramètres
Alors le mode Kids est activé sur mon compte Et je vois le message "Mode Kids activé - Contenus filtrés pour 13-15 ans"
Étant donné que je suis le parent d'un utilisateur de 13 ans Et que j'ai accès au compte de mon enfant
Quand j'active le mode Kids
Alors le mode Kids est activé sur le compte enfant Et seuls les contenus "Tous publics" sont accessibles
Étant donné que le mode Kids est activé sur mon compte Et qu'il existe des contenus avec les classifications:
| classification | nombre |
|---|---|
| Tous publics | 100 |
| 13+ | 50 |
| 16+ | 30 |
| 18+ | 20 |
Quand je demande des recommandations
Alors seuls les 100 contenus "Tous publics" sont proposés Et les contenus 13+, 16+, 18+ sont exclus
Étant donné que le mode Kids est activé Et qu'il existe 20 contenus "Tous publics" dont 5 tagués "Politique"
Quand je demande des recommandations
Alors seuls les 15 contenus non-politiques sont proposés Et les 5 contenus politiques sont automatiquement exclus
Étant donné que le mode Kids est activé Et que je suis un utilisateur gratuit
Quand j'écoute du contenu
Alors aucune publicité n'est diffusée Et je n'ai pas d'insertion publicitaire (règle 1/5 désactivée)
Étant donné que le mode Kids est activé Et qu'une publicité a été validée manuellement pour le mode Kids
Quand j'écoute du contenu
Alors cette publicité peut être diffusée Mais la fréquence reste inférieure au mode standard
Étant donné que le mode Kids est activé
Quand j'ouvre l'application
Alors l'interface est identique au mode normal Et seul le filtrage de contenu est actif (pas d'UI enfant)
Étant donné que le mode Kids est activé
Quand je désactive le mode Kids dans les paramètres
Alors tous les contenus sont à nouveau accessibles selon mon âge Et je vois le message "Mode Kids désactivé"
Étant donné que je suis un utilisateur de 16 ans
Quand j'essaie d'activer le mode Kids
Alors l'activation réussit Et le mode Kids filtre les contenus 16+ et 18+ (pas seulement 13+) Et je vois uniquement les contenus "Tous publics"
Étant donné que le mode Kids est activé Et qu'un ami me partage un contenu 16+
Quand j'essaie d'accéder au contenu via le lien
Alors l'accès est refusé Et je vois le message "Ce contenu n'est pas accessible en mode Kids"
Étant donné que le mode Kids est activé
Quand je recherche "débat"
Alors seuls les contenus "Tous publics" apparaissent dans les résultats Et les contenus 13+, 16+, 18+ sont exclus de la recherche
Étant donné que le mode Kids est activé Et qu'un audio-guide "Tous publics" existe au musée du Louvre
Quand je suis à proximité du Louvre
Alors l'audio-guide est proposé normalement Et toutes les séquences sont accessibles
Étant donné que je suis un créateur Et que mes contenus "Tous publics" sont écoutés par des utilisateurs mode Kids
Quand je consulte mes statistiques
Alors je vois le pourcentage d'écoutes en mode Kids Et je peux adapter mes contenus en conséquence
Quand j'active le mode Kids
Alors je reçois une notification explicative:
| information | description |
|---|---|
| Contenu | Seuls les contenus "Tous publics" accessibles |
| Politique | Contenus politiques automatiquement masqués |
| Publicité | Aucune publicité affichée |
Étant donné que le mode Kids est activé
Quand je consulte mon profil
Alors je vois un badge "Mode Kids actif 🛡️" Et je peux le désactiver en un clic
En tant qu'administrateur RoadWave Je veux configurer les paramètres de l'algorithme à chaud Afin d'optimiser l'engagement sans redéploiement
20 scénarios (19 standards, 1 plan)
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et que je suis connecté en tant qu'admin
Quand j'accède au dashboard admin
Alors je vois tous les paramètres configurables de l'algorithme Et je vois les valeurs actuelles et par défaut
Étant donné que le poids_geo_ancre est à 0.7 (défaut)
Quand je modifie le poids_geo_ancre à 0.8 Et que je sauvegarde
Alors la nouvelle valeur est appliquée immédiatement Et tous les nouveaux calculs utilisent 0.8 Et je vois le message "Paramètre mis à jour avec succès"
Quand j'essaie de configurer poids_geo_ancre à 1.5 (hors plage 0.5-1.0)
Alors la modification échoue Et je vois le message "Valeur hors plage autorisée (0.5 - 1.0)"
Quand je modifie "
Alors la modification est appliquée immédiatement
Et la nouvelle valeur respecte la plage "
📊 Exemples de données:
| parametre | nouvelle_valeur | plage |
|---|---|---|
| poids_geo_ancre | 0.8 | 0.5 - 1.0 |
| poids_geo_contextuel | 0.6 | 0.3 - 0.7 |
| poids_geo_neutre | 0.3 | 0.0 - 0.4 |
| poids_engagement | 0.3 | 0.0 - 0.5 |
| part_aleatoire_global | 0.15 | 0.0 - 0.3 |
| distance_max_km | 150 | 50 - 500 |
| rayon_gps_point_m | 1000 | 100 - 2000 |
| seuil_min_ecoutes_engagement | 100 | 10 - 200 |
Étant donné que le poids_geo_ancre est modifié de 0.7 à 0.8
Quand la modification est appliquée
Alors aucun recalcul batch n'est lancé (économie CPU) Et seuls les nouveaux calculs utilisent la valeur 0.8
Étant donné que je modifie plusieurs paramètres
Quand je sauvegarde la configuration
Alors une nouvelle version est créée (ex: v1.2.3) Et je peux voir l'historique des versions Et je peux comparer deux versions
Étant donné que la configuration actuelle est v1.2.3 Et que la version précédente était v1.2.2
Quand je clique sur "Restaurer v1.2.2"
Alors tous les paramètres de v1.2.2 sont réappliqués Et je vois le message "Configuration restaurée à v1.2.2"
Quand je crée une nouvelle variante "Test engagement élevé" Et que je configure:
| parametre | valeur |
|---|---|
| poids_engagement | 0.4 |
| poids_geo_ancre | 0.6 |
Et que je lance le test A/B
Alors 50% des utilisateurs reçoivent la Config A (défaut) Et 50% des utilisateurs reçoivent la Config B (test)
Étant donné qu'un test A/B est actif
Quand 1000 nouveaux utilisateurs se connectent
Alors environ 500 sont assignés à la Config A Et environ 500 sont assignés à la Config B Et l'assignation est aléatoire et équilibrée
Étant donné qu'un utilisateur est assigné à la Config B
Quand il se reconnecte plusieurs fois
Alors il reste toujours dans la Config B Et il ne change pas de variante pendant le test
Étant donné qu'un test A/B est actif depuis 7 jours
Quand je consulte le dashboard A/B testing
Alors je vois les métriques suivantes pour chaque config:
| metrique | Config A | Config B |
|---|---|---|
| Taux complétion moyen | 68% | 72% |
| Engagement (likes) | 15% | 18% |
| Durée session moyenne | 23 min | 27 min |
| Taux skip rapide (<10s) | 12% | 9% |
Étant donné qu'un test A/B est actif
Quand je consulte le dashboard
Alors je vois des graphiques temps réel:
| graphique | type |
|---|---|
| Évolution engagement | Ligne |
| Répartition utilisateurs | Camembert |
| Taux complétion | Barres |
| Durée session | Ligne |
Étant donné qu'un test A/B montre que Config B est meilleure
Quand je clique sur "Appliquer Config B pour tous"
Alors la Config B devient la configuration par défaut Et tous les utilisateurs utilisent maintenant Config B Et l'ancien test est archivé
Quand je consulte la section "Audit engagement"
Alors je vois:
| metrique | valeur |
|---|---|
| Temps écoute moyen/session | 25 min |
| Temps écoute médian/session | 18 min |
| Taux complétion moyen | 70% |
| % sessions avec ≥1 like | 35% |
Étant donné que plusieurs modifications de config ont été faites
Quand je consulte les graphiques d'évolution
Alors je vois l'impact de chaque changement de config Et je peux corréler changements config avec métriques
Quand je clique sur "Exporter données"
Alors je peux télécharger un CSV avec:
| colonne | exemple |
|---|---|
| date | 2026-01-21 |
| version_config | v1.2.3 |
| taux_completion | 0.72 |
| engagement_moyen | 0.45 |
| duree_session_min | 27 |
Étant donné que le taux de complétion moyen est à 70%
Quand une nouvelle config fait baisser le taux à 55%
Alors je reçois une alerte email "Baisse critique du taux de complétion" Et je peux rollback rapidement
Étant donné que je modifie poids_geo_ancre de 0.7 à 0.9
Quand je clique sur "Prévisualiser impact"
Alors je vois une simulation sur échantillon de 1000 utilisateurs Et je vois l'estimation d'impact sur les métriques clés
Quand je modifie une configuration
Alors je peux ajouter une note "Test pour améliorer contenu local" Et cette note est visible dans l'historique des versions Et l'équipe peut comprendre le contexte des changements
Étant donné que je suis un admin junior
Quand j'essaie de modifier un paramètre critique
Alors l'accès est refusé Et je vois "Permission admin senior requise" Et seuls les admins seniors peuvent modifier les paramètres
En tant qu'utilisateur Je veux personnaliser mon expérience de recommandation Afin d'adapter l'application à mes différents contextes d'usage
25 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible Et que je suis connecté
Quand j'ouvre les paramètres de personnalisation
Alors je vois trois curseurs disponibles:
| curseur | description |
|---|---|
| Géolocalisation | Local ← slider → National |
| Découverte | 0% ← slider → 50% |
| Politique | Masquer / Équilibré / Mes préférences |
Étant donné que le curseur Géolocalisation est au centre (défaut)
Quand je déplace le curseur vers "Local" (gauche)
Alors l'algorithme privilégie fortement les contenus proches Et la pondération géographique augmente Et je vois le message "Recommandations locales privilégiées"
Étant donné que le curseur Géolocalisation est au centre
Quand je déplace le curseur vers "National" (droite)
Alors l'algorithme privilégie la découverte nationale Et la pondération géographique diminue Et je reçois des contenus de toute la France
Quand je règle le curseur Découverte à 0%
Alors 0% de contenus aléatoires dans mes recommandations Et 100% de contenus calculés selon score combiné Et je vois le message "Personnalisation maximale"
Quand je règle le curseur Découverte à 10%
Alors 10% de contenus aléatoires Et 90% de contenus calculés Et je vois le message "Équilibre découverte/personnalisation"
Quand je règle le curseur Découverte à 30%
Alors 30% de contenus aléatoires Et 70% de contenus calculés Et je vois le message "Découverte élevée activée"
Quand je règle le curseur Découverte à 50%
Alors 50% de contenus aléatoires Et 50% de contenus calculés Et je vois le message "Découverte maximale (équivaut à national)"
Quand je crée un nouveau profil nommé "🚗 Trajet quotidien" Et que je configure:
| parametre | valeur |
|---|---|
| Géolocalisation | Local |
| Découverte | 5% |
| Politique | Masquer |
Et que je sauvegarde
Alors le profil "🚗 Trajet quotidien" est créé Et je peux l'activer en un clic
Quand je crée un profil "🛣️ Road trip" Et que je configure:
| parametre | valeur |
|---|---|
| Géolocalisation | Régional |
| Découverte | 30% |
| Politique | Équilibré |
Alors le profil est sauvegardé Et je peux switcher entre profils facilement
Quand je crée un profil "👶 Enfants" Et que j'active le Mode Kids
Alors tous les paramètres sont adaptés pour enfants:
| parametre | valeur |
|---|---|
| Mode Kids | Activé |
| Politique | Masquer (forcé) |
| Publicité | Aucune |
Étant donné que j'ai créé un profil "🚗 Trajet quotidien"
Quand je clique sur "Activer" pour ce profil
Alors tous les paramètres du profil sont appliqués Et je vois le message "Profil 'Trajet quotidien' activé" Et l'algorithme utilise ces paramètres immédiatement
Étant donné que j'ai créé 3 profils sur mon iPhone
Quand je me connecte sur mon iPad
Alors mes 3 profils sont automatiquement synchronisés Et je peux les utiliser sur l'iPad
Étant donné que j'ai un profil "Road trip" sur iPhone
Quand je modifie ce profil sur iPhone
Alors la modification est synchronisée sur tous mes devices Et le profil est mis à jour partout en temps réel
Étant donné que j'ai créé des profils personnalisés Et que ma conjointe a un compte RoadWave
Quand elle se connecte sur son compte
Alors elle ne voit pas mes profils Et chaque utilisateur a ses propres profils
Étant donné que j'utilise toujours le profil "Trajet quotidien" Et que je pars de chez moi vers mon travail tous les matins à 8h
Quand le système détecte ce trajet récurrent
Alors le profil "Trajet quotidien" est activé automatiquement Et je reçois une notification "Profil 'Trajet quotidien' activé"
Étant donné que l'auto-switch de profil est actif
Quand je désactive cette option dans les paramètres
Alors les profils ne changent plus automatiquement Et je dois les activer manuellement
Étant donné que je conduis à 50 km/h
Quand j'essaie de modifier un curseur
Alors la modification est bloquée Et je vois le message "Modification impossible pendant la conduite" Et je dois m'arrêter ou être passager pour modifier
Étant donné que je suis arrêté à un feu rouge (5 km/h)
Quand j'essaie de modifier un curseur
Alors la modification est autorisée Et je peux ajuster les paramètres
Quand je lance l'application pour la première fois
Alors je vois un warning "Configurez vos préférences avant de prendre la route" Et un bouton "Configurer maintenant" Et je peux accéder rapidement aux paramètres
Étant donné que je suis passager dans une voiture Et que le mode passager est activé
Quand j'essaie de modifier les paramètres
Alors la modification est autorisée Et le blocage vitesse GPS ne s'applique pas
Étant donné que j'utilise plusieurs profils
Quand je consulte mes statistiques
Alors je vois:
| metrique | exemple |
|---|---|
| Profil le plus utilisé | Trajet quotidien |
| Heures par profil | 25h / 10h / 5h |
| Dernier profil actif | Road trip |
Étant donné que j'ai créé un profil "Test"
Quand je supprime ce profil
Alors le profil est définitivement supprimé Et je vois le message "Profil 'Test' supprimé" Et il disparaît de tous mes devices
Étant donné que j'ai créé 10 profils
Quand j'essaie de créer un 11ème profil
Alors la création échoue Et je vois le message "Maximum 10 profils par utilisateur"
Étant donné que j'ai un profil "Trajet quotidien"
Quand je clique sur "Dupliquer"
Alors un nouveau profil "Trajet quotidien (copie)" est créé Et il a les mêmes paramètres que l'original Et je peux le modifier indépendamment
Étant donné que j'ai modifié un profil
Quand je clique sur "Réinitialiser"
Alors tous les paramètres reviennent aux valeurs par défaut:
| parametre | valeur défaut |
|---|---|
| Géolocalisation | Équilibré |
| Découverte | 10% |
| Politique | Équilibré |
En tant que système de recommandation Je veux calculer un score combiné pour chaque contenu Afin de proposer les contenus les plus pertinents à l'utilisateur
21 scénarios
Contexte commun à tous les scénarios
Étant donné que l'API RoadWave est disponible
Étant donné qu'un contenu existe à Paris Et que la distance_max_km est configurée à 200 km
Quand un utilisateur est à 50 km du contenu
Alors le score_geo = 1 - (50 / 200) = 0.75
Étant donné qu'un contenu existe à un point GPS précis
Quand un utilisateur est exactement au même point (0 km)
Alors le score_geo = 1.0 (maximum)
Étant donné qu'un contenu existe à Paris
Quand un utilisateur est à 200 km du contenu
Alors le score_geo = 1 - (200 / 200) = 0.0
Étant donné qu'un contenu existe à Paris
Quand un utilisateur est à 250 km du contenu (au-delà de 200 km max)
Alors le score_geo = 0.0 (minimum) Et le contenu a peu de chances d'être recommandé sauf engagement très élevé
Étant donné qu'un utilisateur a les jauges suivantes:
| categorie | niveau |
|---|---|
| Automobile | 80% |
| Voyage | 60% |
| Musique | 40% |
Et qu'un contenu est tagué "Automobile" et "Voyage"
Quand l'algorithme calcule le score_interets
Alors score_interets = (0.8 + 0.6) / 2 = 0.7
Étant donné qu'un utilisateur a la jauge "Économie" à 90% Et qu'un contenu est tagué uniquement "Économie"
Quand l'algorithme calcule le score_interets
Alors score_interets = 0.9
Étant donné qu'un utilisateur a des jauges "Sport" et "Politique" élevées Et qu'un contenu est tagué "Musique" et "Philosophie" Et que l'utilisateur n'a pas ces catégories
Quand l'algorithme calcule le score_interets
Alors score_interets = 0.5 (neutre par défaut pour catégories inconnues)
Étant donné qu'un contenu a:
| metrique | valeur |
|---|---|
| ecoutes | 1000 |
| ecoutes_completes | 700 |
| likes | 300 |
| abonnements_apres | 50 |
Quand l'algorithme calcule le score_engagement
Alors taux_completion = 700 / 1000 = 0.7 Et ratio_likes = 300 / 1000 = 0.3 Et ratio_abonnements = 50 / 1000 = 0.05 Et score_engagement = (0.7 × 0.5) + (0.3 × 0.3) + (0.05 × 0.2) = 0.35 + 0.09 + 0.01 = 0.45
Étant donné qu'un contenu a seulement 30 écoutes
Quand l'algorithme calcule le score_engagement
Alors score_engagement = 0.5 (neutre par défaut) Et le contenu n'est pas pénalisé pour manque de données
Étant donné qu'un contenu a exactement 50 écoutes Et des métriques d'engagement complètes
Quand l'algorithme calcule le score_engagement
Alors le score est calculé normalement (pas de seuil neutre)
Étant donné qu'un utilisateur demande 10 recommandations Et que la part_aleatoire_global est à 10%
Quand l'algorithme génère les recommandations
Alors 1 contenu sur 10 est tiré aléatoirement Et 9 contenus sont calculés avec le score combiné Et le contenu aléatoire n'est pas dans l'historique déjà écouté
Étant donné qu'un utilisateur configure le curseur découverte à 0%
Quand l'utilisateur demande 20 recommandations
Alors les 20 contenus sont calculés avec le score combiné Et aucun contenu aléatoire n'est proposé
Étant donné qu'un utilisateur configure le curseur découverte à 50%
Quand l'utilisateur demande 20 recommandations
Alors 10 contenus sont tirés aléatoirement Et 10 contenus sont calculés avec le score combiné
Étant donné qu'un contenu "Géo-ancré" a:
| parametre | valeur |
|---|---|
| score_geo | 0.9 |
| score_interets | 0.6 |
| score_engagement | 0.45 |
| poids_geo | 0.7 |
| poids_interets | 0.1 |
| poids_engagement | 0.2 |
Quand l'algorithme calcule le score_final
Alors score_final = (0.9 × 0.7) + (0.6 × 0.1) + (0.45 × 0.2) Et score_final = 0.63 + 0.06 + 0.09 = 0.78
Étant donné qu'un contenu "Géo-neutre" a:
| parametre | valeur |
|---|---|
| score_geo | 0.3 |
| score_interets | 0.9 |
| score_engagement | 0.6 |
| poids_geo | 0.2 |
| poids_interets | 0.6 |
| poids_engagement | 0.2 |
Quand l'algorithme calcule le score_final
Alors score_final = (0.3 × 0.2) + (0.9 × 0.6) + (0.6 × 0.2) Et score_final = 0.06 + 0.54 + 0.12 = 0.72 Et le contenu peut être recommandé malgré la distance
Étant donné qu'un contenu viral existe à Paris Et qu'il a un score_engagement très élevé de 0.95 Et qu'un utilisateur est à Marseille (score_geo = 0.1)
Quand l'algorithme calcule le score_final
Alors le score_engagement élevé compense le score_geo faible Et le contenu peut apparaître dans les recommandations
Étant donné 5 contenus avec les scores suivants:
| contenu | score_final |
|---|---|
| Contenu A | 0.85 |
| Contenu B | 0.72 |
| Contenu C | 0.90 |
| Contenu D | 0.65 |
| Contenu E | 0.78 |
Quand l'utilisateur demande des recommandations
Alors l'ordre de proposition est:
| position | contenu |
|---|---|
| 1 | Contenu C |
| 2 | Contenu A |
| 3 | Contenu E |
| 4 | Contenu B |
| 5 | Contenu D |
Étant donné qu'un utilisateur a écouté les contenus suivants:
| contenu | completion |
|---|---|
| Contenu A | 85% |
| Contenu B | 95% |
| Contenu C | 30% |
Quand l'algorithme génère les recommandations
Alors "Contenu A" et "Contenu B" ne sont jamais proposés Mais "Contenu C" peut être reproposé
Étant donné qu'un utilisateur écoute un contenu
Quand l'algorithme prépare les contenus suivants
Alors 5 contenus sont pré-calculés selon le score Et ces contenus sont mis en cache pour performance
Étant donné que 5 contenus suivants sont pré-calculés Et que l'utilisateur se déplace de 12 km
Quand l'utilisateur demande le contenu suivant
Alors l'algorithme recalcule les scores avec la nouvelle position Et propose de nouveaux contenus plus pertinents géographiquement
Étant donné que 5 contenus suivants sont pré-calculés Et que 11 minutes se sont écoulées sans action
Quand l'utilisateur demande le contenu suivant
Alors l'algorithme recalcule les scores Et prend en compte les nouveaux contenus publiés
18 scénarios
Contexte commun à tous les scénarios
Étant donné que je suis un utilisateur avec le GPS activé Et que j'utilise l'application depuis plusieurs jours
Étant donné que j'écoute un contenu à la position GPS 48.8566, 2.3522 (Paris, Tour Eiffel) Et qu'il est 10:00 le 2025-01-20
Quand l'événement d'écoute est enregistré en base de données
Alors les coordonnées précises 48.8566, 2.3522 sont stockées
Et le champ anonymized est à false
Et le champ created_at contient "2025-01-20 10:00:00"
Et ces données précises servent à la recommandation personnalisée
Étant donné que j'ai écouté un contenu le 2025-01-20 à 10:00 à la position 48.8566, 2.3522
Quand le job quotidien d'anonymisation s'exécute le 2025-01-21 à 02:00
Alors les coordonnées précises sont converties en geohash précision 5
Et le geohash correspond à une zone d'environ 5km²
Et les coordonnées originales 48.8566, 2.3522 sont supprimées définitivement
Et le champ anonymized passe à true
Et il est impossible de retrouver la position précise d'origine
Étant donné que le job quotidien d'anonymisation s'exécute
Quand la requête SQL suivante est exécutée:
Alors toutes les positions vieilles de plus de 24h sont anonymisées Et le processus est automatique et irréversible Et les données sont conformes RGPD
Étant donné qu'une position GPS est convertie en geohash précision 5
Quand on analyse la zone couverte
Alors la zone fait environ 5km² (4.9km × 4.9km) Et cette précision est suffisante pour des analytics agrégées Et cette précision ne permet pas d'identifier un individu (conformité CNIL)
Étant donné que ma position précise est 48.8566, 2.3522 (Tour Eiffel)
Quand la conversion en geohash précision 5 est appliquée
Alors le geohash généré est "u09wh" Et ce geohash couvre une zone de ~5km² autour de la Tour Eiffel Et toutes les positions dans cette zone partagent le même geohash Et il est impossible de distinguer deux utilisateurs dans cette zone
Étant donné que j'ai écouté des contenus aux positions suivantes:
| date | heure | latitude | longitude | lieu |
|---|---|---|---|---|
| 2025-01-15 | 08:30 | 48.8566 | 2.3522 | Paris |
| 2025-01-16 | 14:00 | 43.6047 | 1.4442 | Toulouse |
| 2025-01-17 | 19:00 | 45.7640 | 4.8357 | Lyon |
Quand j'ouvre mon historique personnel dans "Profil > Mes trajets"
Alors je vois mes trajets avec les positions précises intégrales Et ces données ne sont pas anonymisées tant que mon compte est actif Et seul moi peut accéder à ces données Et elles ne sont pas utilisées pour des analytics globales
Étant donné que RoadWave génère des analytics agrégées
Quand l'équipe analyse les zones géographiques populaires
Alors seules les données anonymisées (geohash) sont utilisées Et les positions précises de l'historique personnel ne sont jamais agrégées Et les heatmaps de trafic utilisent uniquement les geohash ~5km²
Étant donné que le système est en production
Quand on consulte les jobs planifiés (cron)
Alors un job "anonymize_gps_data" est configuré Et le job s'exécute tous les jours à 02:00 (heure creuse) Et le job traite toutes les positions vieilles de plus de 24h Et un log est généré pour traçabilité
Étant donné que le job d'anonymisation s'exécute le 2025-01-21 à 02:00
Quand le job se termine
Alors un rapport est généré avec:
| métrique | valeur |
|---|---|
| Nombre de positions traitées | 15420 |
| Nombre de positions anonymisées | 15420 |
| Durée d'exécution | 3.5s |
| Erreurs | 0 |
Et le rapport est loggé dans Sentry/Grafana Et une alerte est envoyée si le job échoue
Étant donné que 100 000 positions doivent être anonymisées
Quand le job s'exécute
Alors le traitement se fait en moins de 30 secondes Et la requête PostGIS est optimisée avec index Et aucun impact sur les performances de l'application en production
Étant donné qu'une position a été anonymisée en geohash "u09wh"
Quand un attaquant tente de retrouver la position précise d'origine
Alors il est impossible de déterminer la position exacte Et des milliers de positions précises correspondent au même geohash Et il n'y a aucune traçabilité vers la position originale Et cette anonymisation est irréversible
Étant donné que les positions sont converties en geohash précision 5
Quand un auditeur CNIL vérifie la conformité
Alors les données sont considérées comme véritablement anonymisées Et elles ne sont plus considérées comme des données personnelles Et aucun consentement n'est requis pour leur traitement analytique Et elles peuvent être conservées indéfiniment
Étant donné que RoadWave génère une heatmap des zones populaires
Quand on analyse les données utilisées
Alors seules les positions anonymisées (geohash) sont agrégées Et la heatmap montre des zones de ~5km² Et aucune position précise n'est révélée Et cette analyse ne nécessite pas de consentement utilisateur (données anonymes)
Étant donné que RoadWave analyse l'utilisation par département
Quand les statistiques sont générées
Alors les données anonymisées sont agrégées par département Et les résultats montrent: "Paris (75): 12 500 écoutes, Lyon (69): 8 300 écoutes" Et aucune donnée personnelle n'est révélée Et les statistiques sont RGPD-compliant
Étant donné que PostGIS est utilisé pour l'anonymisation GPS
Quand on calcule le coût de la solution
Alors le coût est de 0€ (PostGIS inclus dans PostgreSQL) Et aucune librairie tierce n'est nécessaire Et la solution est entièrement maîtrisée (self-hosted)
Étant donné que je suis en train d'écouter du contenu actuellement Et que certaines de mes positions ont plus de 24h
Quand le job d'anonymisation s'exécute
Alors mes positions de plus de 24h sont anonymisées Mais ma position actuelle (session en cours) reste précise Et la recommandation continue de fonctionner normalement
Étant donné que je demande la suppression de mon compte
Quand le compte est supprimé (après grace period de 30j)
Alors toutes mes positions GPS (précises et anonymisées) sont supprimées Et mon historique personnel de trajets est supprimé Et aucune donnée GPS ne subsiste, même anonymisée
Étant donné que je demande un export de mes données Et que certaines de mes positions ont été anonymisées
Quand l'export est généré
Alors les positions précises de mon historique personnel sont incluses Mais les positions déjà anonymisées (>24h, analytics) apparaissent en geohash Et l'export précise quelles données ont été anonymisées et pourquoi
22 scénarios (21 standards, 1 plan)
Étant donné que RoadWave doit tenir un registre des traitements
Quand on consulte la documentation
Alors un fichier docs/rgpd/registre-traitements.md existe
Et le fichier est versionné dans Git
Et l'historique des modifications est traçable via Git
Et chaque traitement est documenté dans une section dédiée
Étant donné que le registre des traitements contient le traitement "Géolocalisation utilisateurs"
Quand on lit la section correspondante
Alors les informations suivantes sont présentes:
| information obligatoire | exemple |
|---|---|
| Nom du traitement | Géolocalisation utilisateurs |
| Finalité | Recommandation de contenu géolocalisé |
| Catégories de données | Coordonnées GPS, historique de position |
| Base légale | Consentement (Article 6.1.a RGPD) |
| Durée de conservation | 24h (précis), puis geohash anonymisé |
| Destinataires | Aucun tiers |
| Transferts hors UE | Aucun |
| Mesures de sécurité | TLS 1.3, anonymisation après 24h |
Étant donné que le registre des traitements est complet
Quand on liste tous les traitements
Alors le traitement "
📊 Exemples de données:
| traitement | base_legale |
|---|---|
| Géolocalisation utilisateurs | Consentement |
| Historique d'écoute | Intérêt légitime |
| Création de contenu | Exécution du contrat |
| Analytics (Matomo) | Consentement |
| Paiements (Mangopay) | Exécution du contrat |
| Modération contenus | Intérêt légitime |
| Notifications push | Consentement |
Étant donné que le registre des traitements existe
Quand on consulte l'historique Git
Alors une mise à jour est effectuée au moins tous les 3 mois Et chaque mise à jour a un commit avec message explicite Et un tag Git marque chaque review trimestrielle Et les modifications sont traçables (auteur, date, changements)
Étant donné qu'une nouvelle fonctionnalité nécessite un traitement de données
Quand la fonctionnalité est développée
Alors le registre est mis à jour AVANT le déploiement en production Et le nouveau traitement est documenté complètement Et un commit Git enregistre l'ajout Et le DPO valide la conformité RGPD du nouveau traitement
Étant donné que RoadWave dépasse 100 000 utilisateurs
Quand la complexité du registre augmente
Alors une interface admin PostgreSQL est développée Et le registre Markdown est migré vers la base de données Et l'historique Git est conservé pour audit Et l'interface permet une gestion plus efficace des traitements
Étant donné que le système de monitoring est actif
Quand un événement critique se produit
Alors une alerte est envoyée selon le type d'événement:
| événement | outil | alerte |
|---|---|---|
| Erreur backend critique | Sentry | Discord/Slack immédiat |
| Pic requêtes anormal | Grafana | Email équipe |
| Accès non autorisé DB | PostgreSQL logs | SMS fondateur |
| Authentification suspecte | Zitadel alerts | Email équipe |
Et les alertes permettent une réaction rapide
Étant donné qu'une violation de données potentielle est détectée
Quand l'équipe consulte la documentation
Alors un runbook docs/rgpd/procedure-breach.md existe
Et le runbook contient une checklist 72h CNIL
Et chaque étape est clairement documentée
Et les contacts d'urgence sont listés
Étant donné qu'une violation de données est confirmée
Quand l'équipe suit la procédure breach
Alors les étapes suivantes sont exécutées dans les délais:
| délai | étape |
|---|---|
| H+0 | Détection et confinement immédiat |
| H+24 | Évaluation gravité (données concernées, users impactés) |
| H+48 | Notification CNIL si risque pour utilisateurs |
| H+72 | Notification utilisateurs si risque élevé |
Et chaque étape est documentée pour audit
Étant donné qu'une violation de données est détectée
Quand l'équipe évalue la gravité
Alors les critères suivants sont analysés:
| critère | exemple |
|---|---|
| Type de données concernées | Emails, mots de passe, GPS, etc. |
| Nombre d'utilisateurs impactés | 10, 100, 10000, etc. |
| Mesures de sécurité existantes | Chiffrement, hachage, anonymisation |
| Risque pour les droits et libertés | Faible, modéré, élevé |
Et si le risque est élevé, la CNIL est notifiée sous 72h
Étant donné qu'un breach avec risque élevé est confirmé à 10:00 le 2025-01-20
Quand la gravité est évaluée comme nécessitant une notification
Alors la CNIL est notifiée avant 10:00 le 2025-01-23 (72h) Et la notification contient:
| information |
|---|
| Nature de la violation |
| Données concernées |
| Nombre d'utilisateurs impactés |
| Conséquences probables |
| Mesures prises |
| Mesures de remédiation |
Et un email pré-rédigé (template) est utilisé pour gagner du temps
Étant donné qu'un breach impacte 5000 utilisateurs Et que le risque est élevé (mots de passe non chiffrés exposés)
Quand la CNIL est notifiée
Alors les utilisateurs impactés sont notifiés dans les 72h Et l'email contient: Et un lien de réinitialisation de mot de passe est inclus
Étant donné qu'un breach mineur est détecté (logs techniques exposés, aucune donnée personnelle)
Quand l'équipe évalue la gravité
Alors le risque est jugé faible Et aucune notification CNIL n'est requise (Article 33.1 RGPD) Et aucune notification utilisateur n'est envoyée Et un log interne est créé pour traçabilité
Étant donné que Sentry et Grafana sont configurés
Quand un comportement anormal est détecté
Alors une alerte est envoyée en temps réel Et l'équipe peut réagir avant qu'un breach majeur ne se produise Et les logs sont analysés quotidiennement pour détecter des anomalies Et cette approche proactive limite les risques de découverte tardive
Étant donné que RoadWave est en phase MVP Et que l'entreprise a moins de 250 employés
Quand on vérifie l'obligation légale d'avoir un DPO
Alors le DPO n'est pas obligatoire selon le RGPD Article 37 Et le fondateur assume temporairement le rôle de DPO Et le fondateur suit la formation CNIL gratuite (4h)
Étant donné que le fondateur est DPO temporaire
Quand on vérifie sa formation
Alors le fondateur a suivi la formation CNIL en ligne (4h) Et le fondateur a obtenu la certification "Atelier RGPD" (gratuit) Et le certificat est conservé pour audit Et la formation couvre:
| sujet |
|---|
| Principes fondamentaux du RGPD |
| Droits des personnes |
| Sécurité des données |
| Violations de données (breach) |
| Registre des traitements |
Étant donné que je consulte les mentions légales de RoadWave
Quand je cherche le contact du DPO
Alors l'email "dpo@roadwave.fr" est clairement affiché Et cet email est également dans les CGU Et le délai de réponse garanti est de 1 mois maximum (RGPD Article 12.3) Et une adresse postale est également fournie
Étant donné que je veux exercer mon droit d'accès à mes données
Quand j'envoie un email à dpo@roadwave.fr
Alors je reçois un accusé de réception dans les 48h Et ma demande est traitée dans un délai maximum de 1 mois Et si le délai dépasse 1 mois, je suis informé de la prolongation (max 2 mois supplémentaires) Et la réponse est complète et conforme au RGPD
Étant donné que je contacte le DPO
Quand j'envoie une demande
Alors le DPO peut traiter les demandes suivantes:
| type de demande |
|---|
| Droit d'accès (Article 15) |
| Droit de rectification (Article 16) |
| Droit à l'effacement (Article 17) |
| Droit à la portabilité (Article 20) |
| Droit d'opposition (Article 21) |
| Plainte RGPD |
| Question sur le traitement des données |
Et chaque demande reçoit une réponse personnalisée
Étant donné que RoadWave dépasse 100 000 utilisateurs
Quand la charge de travail DPO augmente
Alors un DPO externe mutualisé est engagé Et le coût est d'environ 200€/mois Et le DPO externe a les certifications CNIL requises Et un contrat de sous-traitance RGPD est signé
Étant donné que RoadWave a plus de 10 employés
Quand l'entreprise se structure
Alors un DPO interne peut être recruté Et le DPO interne a une certification CNIL (AFCDP ou équivalent) Et le DPO est indépendant et ne peut être licencié pour ses fonctions Et le DPO a un accès direct à la direction
Étant donné que toutes les mesures RGPD sont en place
Quand on calcule le coût total mensuel
Alors le récapitulatif est le suivant:
| mesure | implémentation | coût |
|---|---|---|
| Consentement | Tarteaucitron.js + PostgreSQL | 0€ |
| Anonymisation GPS | Geohash PostGIS (24h) | 0€ |
| Export données | JSON+HTML+ZIP asynchrone | 0€ |
| Suppression compte | Grace period 30j + anonymisation | 0€ |
| Mode dégradé | GeoIP MaxMind + GPS optionnel | 0€ |
| Conservation | Purge auto 5 ans inactivité | 0€ |
| Analytics | Matomo self-hosted | ~5€/mois |
| Registre traitements | Markdown Git | 0€ |
| Breach detection | Sentry + Grafana + runbook | 0€ (< 5K events) |
| DPO | Fondateur formé CNIL | 0€ |
Et le coût total est d'environ 5€/mois Et cette conformité est 100% opensource et maîtrisée
16 scénarios
Contexte commun à tous les scénarios
Étant donné que je suis un nouvel utilisateur Et que j'accède à l'application pour la première fois
Étant donné que j'accède à l'application web pour la première fois
Quand la page se charge
Alors un banner RGPD Tarteaucitron.js s'affiche Et le banner est en français Et le banner propose les options suivantes:
| option | description |
|---|---|
| Tout accepter | Active tous les consentements |
| Tout refuser | Refuse tous les consentements optionnels |
| Personnaliser | Ouvre le panneau de personnalisation |
Et le banner est customisé aux couleurs de RoadWave
Étant donné que le banner RGPD est affiché
Quand je clique sur "Personnaliser"
Alors je vois les catégories de consentements suivantes:
| catégorie | type | requis |
|---|---|---|
| Fonctionnel | Nécessaire | oui |
| Analytique | Optionnel | non |
| Marketing | Optionnel | non |
Et chaque catégorie a une description claire de son usage Et je peux accepter ou refuser chaque catégorie individuellement
Étant donné que je suis sur l'application mobile Et que l'onboarding est terminé
Quand l'application a besoin d'accéder à ma position précise
Alors un écran de demande de consentement s'affiche Et le message explique clairement l'usage: Et je peux accepter ou refuser Et si je refuse, l'application bascule en mode dégradé (GeoIP uniquement)
Étant donné que je veux activer la géolocalisation précise
Quand j'accepte le consentement dans l'application
Alors l'application demande également la permission au système d'exploitation Et sur iOS, la popup système s'affiche: "Autoriser RoadWave à accéder à votre position ?" Et sur Android, la popup système s'affiche avec les options "Toujours autoriser / Autoriser seulement pendant l'utilisation / Refuser" Et les deux consentements (app + OS) doivent être acceptés pour activer le GPS précis
Étant donné que j'ai accepté les consentements suivants:
| type | accepté |
|---|---|
| Fonctionnel | oui |
| Analytique | oui |
| Marketing | non |
| GPS précis | oui |
Quand je valide mes choix
Alors un enregistrement est créé dans la table user_consents
Et l'enregistrement contient les champs suivants:
| champ | valeur |
|---|---|
| user_id | [mon ID utilisateur] |
| consent_type | fonctionnel / analytique / gps |
| version | 1 |
| accepted | true / false |
| timestamp | [date et heure exacte] |
Et chaque type de consentement a un enregistrement séparé
Étant donné que j'ai accepté le consentement "Analytique" version 1 le 2025-01-01 Et que les CGU sont mises à jour le 2025-06-01
Quand je me connecte après la mise à jour
Alors un nouveau consentement version 2 m'est demandé Et mon ancien consentement version 1 reste dans l'historique Et je dois accepter la nouvelle version pour continuer à utiliser les analytics
Étant donné que j'ai modifié mes consentements plusieurs fois:
| date | consent_type | accepted | version |
|---|---|---|---|
| 2025-01-01 | Analytique | oui | 1 |
| 2025-03-15 | Analytique | non | 1 |
| 2025-06-01 | Analytique | oui | 2 |
Quand un auditeur CNIL consulte mon historique de consentements
Alors tous les enregistrements sont conservés Et l'historique prouve que chaque consentement a été donné librement Et les timestamps permettent de prouver la conformité à tout moment
Étant donné que je refuse le consentement "Analytique"
Quand j'utilise l'application
Alors aucun cookie Matomo _pk_id n'est déposé
Et aucune donnée d'usage n'est envoyée à Matomo
Et l'application fonctionne normalement sans analytics
Étant donné que je refuse le consentement "Notifications push"
Quand un créateur que je suis publie un nouveau contenu
Alors je ne reçois pas de notification push Mais je peux voir le nouveau contenu dans l'application Et l'application fonctionne normalement
Étant donné que je refuse le consentement "GPS précis"
Quand j'utilise l'application
Alors je peux accéder aux contenus nationaux Mais les contenus géolocalisés précis (Ancré, Contextuel) ne sont pas disponibles Et les audio-guides nécessitent l'activation du GPS Et un banner permanent me rappelle que l'activation du GPS améliore l'expérience
Étant donné que j'ai accepté le consentement "Analytique" Et que j'utilise l'application depuis 3 mois
Quand j'ouvre "Paramètres > Confidentialité > Gérer mes consentements"
Alors je vois la liste de tous mes consentements actuels Et je peux révoquer le consentement "Analytique"
Quand je révoque le consentement
Alors un nouvel enregistrement est créé avec accepted = false
Et le cookie Matomo est supprimé immédiatement
Et les analytics sont désactivées à partir de ce moment
Étant donné que j'avais refusé le consentement "GPS précis"
Quand j'ouvre "Paramètres > Confidentialité > Gérer mes consentements" Et que je clique sur "Activer la géolocalisation précise"
Alors un nouvel enregistrement est créé avec accepted = true
Et la permission OS est demandée si ce n'est pas déjà fait
Et l'application bascule en mode géolocalisation précise
Et les contenus géolocalisés deviennent disponibles immédiatement
Étant donné qu'un contrôle CNIL est en cours
Quand l'équipe RoadWave exporte l'historique des consentements
Alors l'export contient pour chaque utilisateur:
| champ | description |
|---|---|
| user_id | ID anonymisé |
| consent_type | Type de consentement |
| version | Version des CGU/consentement |
| accepted | Accepté ou refusé |
| timestamp | Date et heure exacte |
| ip_address | IP (anonymisée) au moment du consentement |
| user_agent | Navigateur/app utilisé |
Et l'export est au format CSV pour analyse Et les données prouvent la conformité RGPD
Étant donné que le système de consentement est implémenté
Quand un auditeur CNIL vérifie la conformité
Alors le système respecte les critères suivants:
| critère CNIL | respecté |
|---|---|
| Consentement libre | oui |
| Consentement spécifique (granulaire) | oui |
| Consentement éclairé (information claire) | oui |
| Consentement univoque (action positive) | oui |
| Révocable à tout moment | oui |
| Preuve du consentement conservée | oui |
Étant donné que l'application web utilise Tarteaucitron.js
Quand je consulte les sources JavaScript chargées
Alors le script Tarteaucitron.js est hébergé sur les serveurs RoadWave Et aucun script tiers (CDN externe) n'est chargé Et le code source de Tarteaucitron.js est vérifiable Et aucune donnée n'est envoyée à un tiers lors de l'affichage du banner
Étant donné que Tarteaucitron.js est opensource Et que PostgreSQL est utilisé pour le backend
Quand on calcule le coût de la solution de consentement
Alors le coût est de 0€ Et la solution est entièrement maîtrisée (self-hosted) Et aucune dépendance à un service SaaS tiers
19 scénarios
Contexte commun à tous les scénarios
Étant donné que le système de purge automatique est actif
Étant donné que je suis un auditeur (sans contenu créé) Et que je ne me suis pas connecté depuis le 2020-01-01 Et que la date actuelle est 2025-01-02 (>5 ans)
Quand le job de purge automatique s'exécute
Alors mon compte est automatiquement supprimé Et toutes mes données personnelles sont effacées Et aucune trace ne subsiste dans la base de données
Étant donné que je suis un créateur Et que j'ai créé 10 contenus qui reçoivent encore des écoutes Et que je ne me suis pas connecté depuis 6 ans
Quand le job de purge automatique s'exécute
Alors mon compte n'est pas supprimé Et mes données personnelles sont conservées tant que mes contenus sont écoutés Et mes contenus continuent d'être diffusés normalement
Étant donné que je suis un créateur Et que j'ai créé 5 contenus Et que je ne me suis pas connecté depuis 5 ans (depuis 2020-01-01) Et que mes contenus n'ont reçu aucune écoute depuis 2 ans (depuis 2023-01-01) Et que la date actuelle est 2025-01-02
Quand le job de purge automatique s'exécute
Alors mon compte est automatiquement supprimé Et mes contenus sont anonymisés (créateur = "Utilisateur supprimé") Et les fichiers audio restent disponibles mais anonymisés
Étant donné que je suis inactif depuis 4 ans et 9 mois
Quand le système détecte que je suis éligible à la purge dans 90 jours
Alors je reçois un email avec le sujet "Votre compte RoadWave sera supprimé dans 90 jours" Et l'email contient: Et un lien de connexion est inclus dans l'email
Étant donné que je suis éligible à la purge automatique
Quand les délais s'écoulent
Alors je reçois les emails suivants:
| délai | sujet email |
|---|---|
| 90 jours | Votre compte sera supprimé dans 90 jours |
| 30 jours | Rappel: Votre compte sera supprimé dans 30 jours |
| 7 jours | Dernière alerte: suppression dans 7 jours |
Et chaque email contient un lien de connexion pour réactiver le compte Et les notifications push sont également envoyées si activées
Étant donné que je suis éligible à la purge dans 15 jours Et que j'ai reçu plusieurs emails d'avertissement
Quand je me connecte à mon compte
Alors la suppression programmée est annulée immédiatement Et le compteur d'inactivité est remis à zéro Et je reçois un email de confirmation: "Votre compte a été réactivé" Et je peux continuer à utiliser l'application normalement
Étant donné que le système est en production
Quand on consulte les jobs planifiés
Alors un job "purge_inactive_accounts" est configuré Et le job s'exécute tous les jours à 03:00 (heure creuse) Et le job identifie les comptes éligibles à la purge Et le job traite les suppressions automatiques
Étant donné que le job de purge s'exécute
Quand le système identifie les comptes éligibles
Alors les critères suivants sont appliqués:
| type_compte | critères |
|---|---|
| Auditeur uniquement | 5 ans sans connexion |
| Créateur avec contenus actifs | Jamais (tant qu'écoutes) |
| Créateur inactif | 5 ans sans connexion + 2 ans sans écoute |
Et seuls les comptes remplissant tous les critères sont supprimés
Étant donné que le job de purge s'exécute le 2025-01-15
Quand le job se termine
Alors un rapport est généré avec:
| métrique | exemple |
|---|---|
| Comptes analysés | 150 000 |
| Comptes éligibles à la purge | 350 |
| Auditeurs supprimés | 300 |
| Créateurs inactifs supprimés | 50 |
| Créateurs conservés (actifs) | 0 |
| Erreurs | 0 |
| Durée d'exécution | 45s |
Et le rapport est loggé pour audit
Étant donné que mon compte créateur est purgé automatiquement
Quand la suppression est effective
Alors mes contenus créés sont conservés indéfiniment Et les contenus sont anonymisés (créateur = "Utilisateur supprimé") Et les fichiers audio restent sur le CDN Et les statistiques d'écoute sont préservées Et les utilisateurs peuvent toujours écouter mes contenus
Étant donné que je suis un créateur inactif depuis 6 ans Mais que mes contenus reçoivent 500+ écoutes par mois
Quand le job de purge s'exécute
Alors mon compte n'est pas supprimé Et je continue de recevoir les emails d'avertissement tous les 6 mois Et mes contenus continuent d'être diffusés Et je peux me reconnecter à tout moment
Étant donné que je suis un créateur
Quand le système calcule si mes contenus sont "actifs"
Alors une "écoute" est comptabilisée si:
| condition | comptabilisée |
|---|---|
| Écoute complète (>80%) | oui |
| Écoute partielle (>30%) | oui |
| Skip rapide (<30%) | non |
| Écoute par un bot (détecté) | non |
Et au moins 1 écoute valide dans les 2 dernières années maintient le compte actif
Étant donné que le système de purge automatique est en place
Quand un auditeur RGPD vérifie la conformité
Alors le système respecte le principe de minimisation:
| principe | respecté |
|---|---|
| Conservation limitée dans le temps | oui |
| Suppression automatique après inactivité | oui |
| Délai raisonnable (5 ans) | oui |
| Notifications préalables | oui |
| Exception justifiée (contenus actifs) | oui |
Et le délai de 5 ans est conforme aux standards de l'industrie
Étant donné que je suis inactif depuis 4 ans
Quand j'effectue l'une des actions suivantes:
| action |
|---|
| Connexion à l'application |
| Publication d'un nouveau contenu |
| Like d'un contenu |
| Abonnement à un créateur |
| Modification de mon profil |
Alors le compteur d'inactivité est remis à zéro Et la suppression programmée est annulée Et je ne suis plus éligible à la purge pour 5 ans
Étant donné qu'un compte est supprimé automatiquement
Quand la suppression est effective
Alors un log d'audit est créé avec:
| champ | valeur |
|---|---|
| user_id | [ID anonymisé] |
| account_type | auditeur / créateur |
| last_login | 2020-01-15T10:00:00Z |
| last_content_listen | 2023-06-01T14:30:00Z |
| purge_date | 2025-01-15T03:00:00Z |
| notifications_sent | 3 (90j, 30j, 7j) |
| reason | 5_years_inactivity |
Et le log est conservé 5 ans pour audit RGPD Et l'user_id est pseudonymisé pour anonymat
Étant donné que je suis un utilisateur Premium Et que je suis inactif depuis 5 ans
Quand le job de purge s'exécute
Alors mon compte est supprimé comme un compte gratuit Et l'abonnement Premium ne prolonge pas la durée de conservation Et aucun remboursement n'est effectué (compte inactif depuis 5 ans)
Étant donné que je suis éligible à la purge Mais que j'ai des signalements de modération en cours
Quand le job de purge s'exécute
Alors ma purge est différée de 90 jours Et les signalements sont traités en priorité Et si les signalements aboutissent à un ban, le compte est supprimé immédiatement Et si les signalements sont infondés, la purge automatique reprend son cours
Étant donné que le délai de purge est fixé à 5 ans
Quand on justifie ce choix
Alors les raisons suivantes sont avancées:
| justification |
|---|
| Standard de l'industrie (Google, Facebook: 2-3 ans) |
| Équilibre raisonnable entre minimisation et utilité |
| Conforme aux recommandations CNIL |
| Laisse une marge de réactivation pour utilisateurs |
| Exception pour créateurs = intérêt légitime communauté |
Étant donné que je consulte les CGU de RoadWave
Quand je lis la section "Conservation des données"
Alors la politique de purge automatique est clairement expliquée: Et les utilisateurs sont informés dès l'inscription
20 scénarios
Contexte commun à tous les scénarios
Étant donné que je suis un utilisateur de l'application web RoadWave
Étant donné que j'accède à l'application web
Quand je me connecte
Alors les cookies techniques suivants sont déposés:
| cookie | type | durée | finalité | consentement |
|---|---|---|---|---|
| session | Technique | 30j | Authentification | Non requis |
| refresh_token | Technique | 30j | Session persistante | Non requis |
Et ces cookies sont essentiels au fonctionnement de l'application Et ils sont exemptés de consentement selon l'article 82 de la loi Informatique et Libertés
Étant donné que j'ai accepté le consentement "Analytique"
Quand je navigue sur l'application web
Alors le cookie _pk_id est déposé
Et la durée de conservation est de 13 mois
Et ce cookie sert à Matomo pour analytics
Et mon IP est automatiquement anonymisée (2 derniers octets)
Étant donné que j'ai refusé le consentement "Analytique"
Quand je navigue sur l'application web
Alors aucun cookie _pk_id n'est déposé
Et aucune donnée d'usage n'est collectée
Et l'application fonctionne normalement sans analytics
Étant donné que RoadWave utilise Matomo pour les analytics
Quand on analyse l'infrastructure
Alors Matomo est installé sur les serveurs RoadWave (Docker) Et aucune donnée n'est envoyée à un service tiers Et toutes les données restent dans l'UE Et l'accès à Matomo est restreint à l'équipe RoadWave
Étant donné que Matomo collecte des données d'usage
Quand une requête est enregistrée
Alors l'adresse IP est automatiquement anonymisée Et les 2 derniers octets sont remplacés par des zéros Et une IP 192.168.1.100 devient 192.168.0.0 Et cette anonymisation est irréversible Et elle est conforme aux recommandations CNIL
Étant donné que Matomo est configuré pour RoadWave
Quand on vérifie les paramètres
Alors les configurations suivantes sont activées:
| paramètre | valeur |
|---|---|
| Anonymisation IP (2 octets) | activé |
| Respect Do Not Track | activé |
| Suppression auto anciens logs (25 mois) | activé |
| Géolocalisation IP désactivée | activé |
| User ID anonymisé | activé |
Et la configuration est RGPD-compliant
Étant donné que j'accède à l'application web
Quand j'inspecte les requêtes réseau avec les DevTools
Alors aucune requête n'est envoyée vers les domaines suivants:
| domaine tiers interdit |
|---|
| google-analytics.com |
| facebook.com (Pixel) |
| hotjar.com |
| mixpanel.com |
| segment.io |
| amplitude.com |
Et toutes les requêtes analytics vont uniquement vers matomo.roadwave.fr
Étant donné que j'analyse les cookies déposés sur roadwave.fr
Quand je consulte la liste des cookies
Alors tous les cookies sont first-party (domaine roadwave.fr) Et aucun cookie tiers (third-party) n'est présent Et cette politique respecte les recommandations CNIL 2020
Étant donné que RoadWave pourrait utiliser Plausible au lieu de Matomo
Quand on compare les deux solutions
Alors Plausible a les caractéristiques suivantes:
| caractéristique | valeur |
|---|---|
| Hébergement | UE (Allemagne) |
| Conformité RGPD | Natif (pas de cookie) |
| Coût | 9€/mois (50K pageviews) |
| IP anonymisées | Automatique |
| Consentement requis | Non (selon CNIL 2020) |
Mais Matomo self-hosted reste le choix prioritaire (0€, contrôle total)
Étant donné que Matomo est self-hosted
Quand on analyse les flux de données
Alors aucune donnée d'analytics n'est transférée hors de l'UE Et les serveurs sont localisés en France Et aucun transfert vers les US (pas de Privacy Shield / DPF requis) Et la souveraineté des données est garantie
Étant donné que Matomo est hébergé sur l'infrastructure RoadWave
Quand on calcule le coût mensuel
Alors le coût est d'environ 5€/mois:
| composant | coût |
|---|---|
| Serveur supplémentaire | 0€ (mutualisé) |
| Base de données MySQL | 0€ (mutualisé) |
| Stockage logs (25 mois) | ~5€/mois |
| License Matomo | 0€ (opensource) |
Et ce coût est marginal comparé à un SaaS tiers (9-50€/mois)
Étant donné que mon navigateur envoie le header "DNT: 1"
Quand j'accède à l'application web
Alors Matomo détecte le signal DNT
Et aucune donnée d'usage n'est collectée
Et aucun cookie _pk_id n'est déposé
Et l'application fonctionne normalement
Et un message discret s'affiche: "Vos préférences de confidentialité sont respectées (DNT activé)"
Étant donné que Matomo collecte des données d'usage
Quand les logs atteignent 25 mois d'ancienneté
Alors un job automatique supprime les anciens logs Et seules les données agrégées (rapports) sont conservées Et les données brutes (logs) sont supprimées définitivement Et cette politique respecte le principe de minimisation RGPD
Étant donné que j'ai accepté le consentement "Analytique"
Quand je navigue sur l'application web
Alors Matomo collecte les données suivantes:
| donnée collectée | anonymisée |
|---|---|
| Pages visitées | non |
| Durée de visite | non |
| Navigateur / OS | non |
| Résolution écran | non |
| Provenance (referrer) | non |
| IP (2 derniers octets) | oui |
| User ID (hashé) | oui |
Et aucune donnée personnelle identifiable n'est collectée
Étant donné que je suis connecté à l'application Et que j'ai accepté le consentement "Analytique"
Quand Matomo enregistre mes actions
Alors mon user_id est hashé (SHA-256) Et le hash est 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 Et il est impossible de retrouver mon user_id original depuis ce hash Et ce processus garantit l'anonymat
Étant donné que RoadWave utilise Matomo self-hosted
Quand un auditeur CNIL vérifie la conformité
Alors le système respecte les recommandations CNIL 2020:
| recommandation CNIL | respecté |
|---|---|
| Consentement requis pour cookies analytics | oui |
| IP anonymisées | oui |
| Pas de transfert hors UE | oui |
| Durée conservation limitée (25 mois) | oui |
| Respect Do Not Track | oui |
| Transparence (liste cookies dans CGU) | oui |
Étant donné que Tarteaucitron.js gère les consentements
Quand je personnalise mes consentements
Alors je vois l'option "Analytique (Matomo)"
Et une description est affichée:
Et je peux activer ou désactiver Matomo indépendamment
Et si je désactive, le cookie _pk_id est supprimé immédiatement
Étant donné que j'utilise l'application mobile
Quand j'accepte le consentement "Analytique"
Alors l'app utilise le SDK Matomo Mobile Et les données sont envoyées à la même instance Matomo self-hosted Et les mêmes règles d'anonymisation s'appliquent Et aucun SDK tiers (Google Analytics, Firebase) n'est utilisé
Étant donné que j'ai refusé le consentement "Analytique" sur mobile
Quand j'utilise l'application
Alors aucune donnée d'usage n'est collectée Et le SDK Matomo est désactivé Et l'application fonctionne normalement sans différence d'UX
Étant donné que Matomo est opensource
Quand on consulte le code source
Alors le code est disponible publiquement sur GitHub Et le code peut être audité par des experts indépendants Et aucune backdoor ou collecte cachée n'est possible Et cette transparence renforce la confiance utilisateur
20 scénarios (19 standards, 1 plan)
Contexte commun à tous les scénarios
Étant donné que je suis un nouvel utilisateur Et que je lance l'application pour la première fois
Étant donné que j'utilise le niveau de géolocalisation "
Quand le système détermine ma position
Alors la technologie utilisée est "
📊 Exemples de données:
| niveau | technologie | contenus | consentement |
|---|---|---|---|
| Pays | Aucune géoloc | Contenus nationaux uniquement | Non requis |
| Ville | GeoIP (MaxMind) | Contenus régionaux/ville | Non requis |
| Précis | GPS | Tous contenus (hyperlocaux inclus) | Requis |
Étant donné que je lance l'application pour la première fois Et que je n'ai pas encore accepté le GPS précis
Quand l'application démarre
Alors le système utilise automatiquement GeoIP basé sur mon adresse IP Et ma position est détectée au niveau ville: "Paris, France" Et aucun consentement n'est requis (GeoIP ne collecte pas de données personnelles) Et je peux accéder aux contenus régionaux et de ville
Étant donné que mon adresse IP est 93.184.216.34
Quand le système utilise GeoIP MaxMind GeoLite2
Alors ma ville est détectée: "Paris" Et ma région est détectée: "Île-de-France" Et mon pays est détecté: "France" Et la précision est d'environ 80% au niveau ville Et aucune coordonnée GPS précise n'est révélée
Étant donné que j'utilise l'application en mode GeoIP
Quand je suis sur l'écran principal
Alors un banner discret s'affiche en haut: Et le banner n'est pas intrusif (pas de popup modale) Et je peux le fermer temporairement avec un bouton X Et le banner réapparaît tous les 7 jours si je ne l'active pas
Étant donné que le banner d'invitation au GPS est affiché
Quand je clique sur "Activer"
Alors un écran de consentement GPS s'affiche Et l'écran explique les avantages: Et je peux accepter ou refuser Et si j'accepte, la permission OS est demandée
Étant donné que je n'autorise aucune géolocalisation
Quand le système recherche du contenu à me proposer
Alors seuls les contenus "National" sont disponibles Et les contenus géolocalisés (Ancré, Contextuel) ne sont pas proposés Et je vois un message: "Activez la géolocalisation pour plus de contenu local"
Étant donné que j'utilise le mode GeoIP et que je suis détecté à Paris
Quand le système recherche du contenu à me proposer
Alors les contenus suivants sont disponibles:
| type_contenu | disponible |
|---|---|
| National | oui |
| Région Île-de-France | oui |
| Ville Paris | oui |
| Hyperlocal (GPS) | non |
| Audio-guides | non |
Et je reçois des recommandations pertinentes pour Paris
Étant donné que j'ai activé la géolocalisation précise
Quand le système recherche du contenu à me proposer
Alors tous les types de contenus sont disponibles:
| type_contenu | disponible |
|---|---|
| National | oui |
| Régional | oui |
| Ville | oui |
| Hyperlocal (Ancré) | oui |
| Contextuel | oui |
| Audio-guides | oui |
Étant donné que j'utilise le mode GeoIP
Quand un auditeur CNIL vérifie la conformité
Alors GeoIP n'est pas considéré comme une donnée personnelle Et l'adresse IP n'est pas conservée après détection de la ville Et seule la ville est stockée (non identifiant) Et aucun consentement n'est requis conformément au RGPD
Étant donné que RoadWave utilise MaxMind GeoLite2
Quand on analyse l'infrastructure
Alors la base de données GeoLite2 est hébergée sur les serveurs RoadWave Et aucune requête n'est envoyée à un service tiers Et la base de données est mise à jour automatiquement chaque mois Et le coût est de 0€ (GeoLite2 est gratuit)
Étant donné que MaxMind publie des mises à jour mensuelles
Quand le 1er du mois arrive
Alors un job automatique télécharge la nouvelle base GeoLite2 Et la base est mise à jour sans interruption de service Et un log est créé pour traçabilité Et si la mise à jour échoue, une alerte est envoyée
Étant donné que j'utilise le mode GeoIP à Paris
Quand je parcours l'application
Alors je peux écouter du contenu pertinent pour Paris et l'Île-de-France Et l'expérience est satisfaisante même sans GPS précis Et je ne suis pas bloqué dans l'utilisation de l'application Et je peux choisir d'activer le GPS quand je le souhaite
Étant donné que j'utilise le mode GeoIP depuis 2 semaines Et que je n'ai pas activé le GPS
Quand je consulte un audio-guide dans les résultats de recherche
Alors un message s'affiche: Et si je clique "Plus tard", je peux continuer à utiliser l'app normalement Et l'incitation reste douce et non intrusive
Étant donné que j'utilise le mode GeoIP
Quand j'active la géolocalisation précise
Alors le système bascule immédiatement en mode GPS Et les contenus hyperlocaux deviennent disponibles Et mon feed se rafraîchit avec du contenu plus précis Et un toast de confirmation s'affiche: "Géolocalisation activée"
Étant donné que j'utilise le mode GPS précis
Quand je désactive la géolocalisation dans les paramètres OS
Alors le système bascule automatiquement en mode GeoIP Et les contenus hyperlocaux ne sont plus proposés Et un banner s'affiche: "Géolocalisation désactivée. Seul le contenu régional est disponible." Et l'application continue de fonctionner normalement
Étant donné que j'ouvre l'application
Quand l'app vérifie les permissions de géolocalisation
Alors le système détecte automatiquement le mode disponible:
| permission GPS | consentement app | mode activé |
|---|---|---|
| Refusée | Non demandé | Pays |
| Refusée | Accepté | GeoIP |
| Accordée | Accepté | GPS précis |
Et le mode est appliqué sans interaction utilisateur
Étant donné que j'habite à Lyon Et que mon IP est une IP résidentielle standard
Quand le système utilise GeoIP pour me localiser
Alors la ville détectée est "Lyon" (correct à 80%) Et dans 20% des cas, la ville peut être légèrement erronée (banlieue proche) Et cette précision est suffisante pour proposer du contenu régional pertinent
Étant donné que j'utilise un VPN avec une IP sortante à Paris Mais que je suis physiquement à Lyon
Quand le système utilise GeoIP
Alors la ville détectée est "Paris" (IP du VPN) Et les contenus proposés sont pour Paris Et je peux activer le GPS précis pour corriger la localisation
Étant donné que j'utilise le mode GeoIP
Quand le système détermine ma ville via mon IP
Alors l'adresse IP n'est pas conservée après détection Et seule la ville "Paris" est stockée en base de données Et la ville seule n'est pas une donnée personnelle (RGPD) Et aucun consentement n'est donc requis
Étant donné que RoadWave utilise MaxMind GeoLite2
Quand on calcule le coût de la solution
Alors le coût est de 0€ Et la solution est opensource Et la base de données est hébergée sur les serveurs RoadWave Et aucun coût SaaS tiers
22 scénarios (21 standards, 1 plan)
Contexte commun à tous les scénarios
Étant donné que je suis un utilisateur connecté Et que j'ai utilisé l'application depuis 6 mois
Étant donné que je suis dans "Paramètres > Confidentialité"
Quand je clique sur "Exporter mes données"
Alors une page d'information s'affiche expliquant: Et un bouton "Confirmer l'export" est disponible
Étant donné que je clique sur "Confirmer l'export"
Quand la demande est validée
Alors un message de confirmation s'affiche: Et un worker background démarre la génération de l'export Et le statut de l'export est "En cours de génération" Et je peux voir le statut dans "Paramètres > Confidentialité > Mes exports"
Étant donné que mon export est généré
Quand je télécharge et ouvre l'archive
Alors l'archive a la structure suivante: Et tous les fichiers sont inclus
Étant donné que j'ouvre le fichier export.json
Quand j'analyse le contenu
Alors le JSON contient les sections suivantes:
| section | description |
|---|---|
| profile | Email, pseudo, date inscription, bio |
| listening_history | Historique complet d'écoute |
| created_contents | Métadonnées des contenus créés |
| subscriptions | Liste des créateurs suivis |
| likes | Liste des contenus likés |
| interest_gauges | Valeurs des jauges d'intérêt |
| consent_history | Historique des consentements |
| premium_subscription | Informations abonnement Premium |
Et le JSON est formaté de manière lisible (indentation) Et toutes les dates sont au format ISO 8601
Étant donné que j'ouvre le fichier index.html dans un navigateur
Quand la page se charge
Alors je vois un site web stylé avec navigation Et les sections suivantes sont affichées:
| section | contenu |
|---|---|
| Mon profil | Email, pseudo, date inscription, statistiques |
| Historique d'écoute | Liste paginée avec dates, titres, durées |
| Mes contenus | Liste avec lectures audio intégrées |
| Mes abonnements | Grille des créateurs suivis |
| Mes likes | Liste des contenus likés avec liens |
| Centres d'intérêt | Graphiques des jauges |
| Consentements | Historique des acceptations/refus |
Et la navigation est intuitive (menu latéral) Et le design est responsive (mobile/desktop)
Étant donné que j'ai créé 5 contenus audio
Quand mon export est généré
Alors le dossier audio/ contient mes 5 fichiers
Et les fichiers sont au format Opus original
Et chaque fichier est nommé: content-[id].opus
Et les fichiers audio correspondent aux métadonnées dans export.json
Étant donné que j'ouvre le fichier README.txt
Quand je lis le contenu
Alors le fichier explique:
Étant donné que mon export est généré
Quand j'ouvre export.json et lis la section "profile"
Alors je trouve les données suivantes:
| champ | exemple |
|---|---|
| email | user@example.com |
| pseudo | @roadwave_user |
| date_inscription | 2025-01-15T10:30:00Z |
| bio | Passionné d'automobile... |
| avatar_url | https://cdn.roadwave.fr/... |
| compte_verifie | false |
| premium | true |
Étant donné que j'ai écouté 150 contenus depuis 6 mois
Quand mon export est généré
Alors la section "listening_history" contient 150 entrées Et chaque entrée contient:
| champ | exemple |
|---|---|
| content_id | C123 |
| content_title | Histoire de la Tour Eiffel |
| creator_name | @historien_paris |
| listened_at | 2025-01-20T15:30:00Z |
| duration_listened | 180 (secondes) |
| completion_rate | 0.85 (85%) |
| location | [geohash ou coords précises] |
Et les contenus sont triés par date décroissante
Étant donné que mes jauges d'intérêt sont:
| catégorie | valeur |
|---|---|
| Automobile | 78% |
| Voyage | 65% |
| Musique | 52% |
| Politique | 30% |
Quand mon export est généré
Alors la section "interest_gauges" contient ces valeurs Et chaque jauge indique la date de dernière modification
Étant donné que j'ai modifié mes consentements plusieurs fois
Quand mon export est généré
Alors la section "consent_history" contient:
| date | consent_type | accepted | version |
|---|---|---|---|
| 2025-01-15T10:00 | Fonctionnel | oui | 1 |
| 2025-01-15T10:00 | Analytique | oui | 1 |
| 2025-01-15T10:00 | Marketing | non | 1 |
| 2025-03-20T14:30 | Analytique | non | 1 |
Et l'historique complet est visible
Étant donné que j'ai beaucoup de données (500 contenus créés, 10 000 écoutes)
Quand je demande un export
Alors la génération se fait en arrière-plan via un worker Et la page web ne timeout pas Et je peux continuer à utiliser l'application pendant la génération Et je reçois un email quand l'export est prêt
Étant donné que je demande un export le 2025-01-20 à 10:00
Quand le worker génère l'export
Alors l'export est disponible maximum 48h plus tard (avant le 2025-01-22 à 10:00) Et la plupart des exports sont prêts en moins de 6h Et le délai respecte l'article 20 du RGPD
Étant donné que mon export est terminé
Quand le worker finalise la génération
Alors je reçois un email avec le sujet "Votre export de données RoadWave est prêt" Et l'email contient: Et le lien de téléchargement est sécurisé (token unique)
Étant donné que mon export a été généré le 2025-01-20 Et que je reçois le lien de téléchargement
Quand j'essaie d'accéder au lien le 2025-01-28 (8 jours plus tard)
Alors le lien est expiré Et je reçois un message "Ce lien a expiré. Veuillez demander un nouvel export." Et je peux demander un nouvel export si nécessaire
Étant donné que j'ai demandé un export le 2025-01-15
Quand j'essaie de demander un nouvel export le 2025-01-20
Alors je reçois un message d'erreur: Et le bouton "Confirmer l'export" est désactivé Et la date du prochain export possible est affichée
Étant donné que j'ai demandé un export le 2025-01-15
Quand la date atteint le 2025-02-15
Alors je peux demander un nouvel export Et le bouton "Confirmer l'export" est actif Et aucune limite ne s'applique
Étant donné que mon export est prêt
Quand je reçois le lien de téléchargement
Alors le lien contient un token unique et non devinable
Et le format du lien est: https://roadwave.fr/exports/download/[token_unique]
Et le token est valide uniquement pour mon compte
Et le token expire après 7 jours ou après 3 téléchargements
Étant donné que je reçois le lien d'export
Quand je clique sur le lien
Alors le système vérifie que je suis connecté Et si je ne suis pas connecté, je suis redirigé vers la page de connexion Et après connexion, le téléchargement démarre automatiquement Et seul le propriétaire du compte peut télécharger l'export
Étant donné que mon export est généré
Quand un auditeur RGPD vérifie la conformité
Alors l'export respecte les exigences de l'article 20:
| exigence RGPD | respecté |
|---|---|
| Format structuré (JSON) | oui |
| Format couramment utilisé | oui |
| Format lisible par machine | oui |
| Format interopérable | oui |
| Délai raisonnable (48h max) | oui |
| Exhaustivité des données | oui |
| Gratuité pour l'utilisateur | oui |
Étant donné que je demande un export de mes données
Quand l'export est généré et téléchargé
Alors aucun coût n'est facturé Et l'export est entièrement gratuit Et aucune inscription Premium n'est requise Et le droit à la portabilité est accessible à tous les utilisateurs
Étant donné que j'ai demandé un export
Quand j'ouvre "Paramètres > Confidentialité > Mes exports"
Alors je vois le statut actuel:
| statut | description |
|---|---|
| En cours de génération | Worker en train de générer l'archive |
| Prêt au téléchargement | Lien de téléchargement disponible |
| Expiré | Lien expiré (>7j), nouvel export requis |
Et la date de demande est affichée Et la taille estimée de l'archive est visible
21 scénarios
Contexte commun à tous les scénarios
Étant donné que je suis un utilisateur connecté Et que j'ai utilisé l'application depuis plusieurs mois
Étant donné que je suis dans "Paramètres > Compte"
Quand je clique sur "Supprimer mon compte"
Alors une page d'avertissement s'affiche avec le message: Et deux boutons sont disponibles: "Annuler" et "Confirmer la suppression"
Étant donné que je clique sur "Confirmer la suppression"
Quand un formulaire de confirmation s'affiche
Alors je dois entrer mon mot de passe pour confirmer Et je dois cocher "Je comprends que cette action est définitive" Et un captcha peut être requis pour éviter les suppressions automatisées
Quand je valide le formulaire
Alors la suppression est initiée
Étant donné que j'ai confirmé la suppression de mon compte
Quand la demande est traitée
Alors mon compte est désactivé immédiatement Et je suis déconnecté de toutes mes sessions Et je ne peux plus me reconnecter Et si j'essaie de me connecter, je reçois le message:
Étant donné que mon compte est en cours de suppression
Quand un autre utilisateur recherche mes contenus
Alors mes contenus ne sont plus diffusés dans l'application Et mes contenus n'apparaissent plus dans les recherches Et mes contenus ne sont plus recommandés Mais mes contenus ne sont pas encore supprimés définitivement
Étant donné que j'ai confirmé la suppression de mon compte
Quand la demande est traitée
Alors je reçois un email avec le sujet "Confirmation de suppression de votre compte RoadWave" Et l'email contient: Et le lien d'annulation est valide 30 jours
Étant donné que j'ai demandé la suppression de mon compte le 2025-01-20 Et que je reçois l'email de confirmation
Quand je clique sur le lien "Annuler la suppression" le 2025-02-05 (16 jours plus tard)
Alors mon compte est réactivé immédiatement Et je peux me reconnecter normalement Et mes contenus redeviennent visibles dans l'application Et toutes mes données sont restaurées Et je reçois un email de confirmation: "Votre compte a été réactivé"
Étant donné que j'ai demandé la suppression de mon compte le 2025-01-20
Quand j'essaie de cliquer sur le lien d'annulation le 2025-02-25 (36 jours plus tard)
Alors le lien est expiré Et je reçois un message "Ce lien a expiré. Votre compte a été définitivement supprimé." Et la suppression effective a déjà eu lieu
Étant donné que j'ai demandé la suppression de mon compte le 2025-01-20 Et que je n'ai pas cliqué sur le lien d'annulation
Quand la date atteint le 2025-02-19 (30 jours plus tard)
Alors un job automatique exécute la suppression définitive Et toutes mes données personnelles sont supprimées
Étant donné que la suppression effective est exécutée
Quand le job de suppression se termine
Alors les données suivantes sont supprimées:
| données | supprimé |
|---|---|
| Compte utilisateur (email, mdp) | oui |
| Profil (pseudo, bio, avatar) | oui |
| Historique d'écoute | oui |
| Historique GPS | oui |
| Centres d'intérêt (jauges) | oui |
| Sessions et tokens | oui |
| Likes et abonnements | oui |
| Notifications non lues | oui |
| Historique consentements | oui |
| Données de paiement | oui |
Et ces suppressions sont irréversibles
Étant donné que j'ai créé 10 contenus audio
Quand la suppression effective est exécutée
Alors mes contenus audio restent disponibles dans l'application Et le nom du créateur devient "Utilisateur supprimé" Et mon pseudo n'est plus visible Et les métadonnées (titre, description, tags, géolocalisation) sont conservées Et les fichiers audio restent sur le CDN Et les statistiques d'écoute sont conservées
Étant donné que mes contenus sont conservés anonymement
Quand un auditeur RGPD vérifie la conformité
Alors la conservation est justifiée par l'intérêt légitime de la communauté Et les contenus ne contiennent plus de données personnelles identifiables Et la suppression complète nuirait à l'expérience des autres utilisateurs Et cette pratique est conforme au RGPD si anonymisation réelle
Étant donné que mon compte a été supprimé Et que mes contenus ont été anonymisés
Quand un utilisateur consulte un de mes anciens contenus
Alors le créateur affiché est "Utilisateur supprimé" Et le profil du créateur n'est plus accessible Et le contenu reste écoutable normalement Et les likes et statistiques sont conservés
Étant donné que j'avais liké 50 contenus
Quand la suppression effective est exécutée
Alors mes likes sont supprimés de la base de données Mais les compteurs de likes sur les contenus sont préservés Et les créateurs ne perdent pas leurs statistiques Et seule la relation "user X a liké content Y" est supprimée
Étant donné que je suivais 20 créateurs
Quand la suppression effective est exécutée
Alors mes abonnements sont supprimés Et les compteurs d'abonnés des créateurs sont décrémentés de 1 Et les créateurs ne reçoivent pas de notification de désabonnement
Étant donné que je suis connecté sur 3 appareils (mobile, tablette, web)
Quand je demande la suppression de mon compte
Alors tous mes tokens d'authentification sont révoqués immédiatement Et je suis déconnecté de tous mes appareils Et toute tentative de reconnexion échoue
Étant donné que j'ai demandé la suppression de mon compte le 2025-01-20
Quand le grace period s'écoule
Alors je reçois des emails de rappel:
| date | jours restants | sujet email |
|---|---|---|
| 2025-02-04 | 15 jours | Plus que 15 jours pour annuler la suppression |
| 2025-02-12 | 7 jours | Dernière semaine pour annuler la suppression |
| 2025-02-17 | 2 jours | Attention: suppression définitive dans 2 jours |
Et chaque email contient le lien d'annulation
Étant donné que la suppression de mon compte est complète
Quand un auditeur RGPD vérifie la conformité
Alors le processus respecte l'article 17 du RGPD:
| exigence RGPD | respecté |
|---|---|
| Suppression de toutes les données personnelles | oui |
| Délai raisonnable (30j grace period acceptable) | oui |
| Possibilité d'annulation (bonne pratique) | oui |
| Anonymisation des contenus (intérêt légitime) | oui |
| Révocation des tokens et sessions | oui |
| Suppression irréversible | oui |
Étant donné que j'ai un abonnement Premium actif
Quand je demande la suppression de mon compte
Alors mon abonnement est annulé immédiatement Et aucun remboursement n'est effectué (conformément aux CGV) Et je reçois un email de confirmation d'annulation de l'abonnement Et le reste du processus de suppression se déroule normalement
Étant donné que je suis un créateur avec 75€ de revenus en attente de paiement
Quand je demande la suppression de mon compte
Alors un message m'informe: Et je peux choisir "Recevoir le paiement et attendre" ou "Renoncer au paiement" Et si je choisis "Recevoir le paiement", la suppression est repoussée de 7 jours
Étant donné que j'ai 2 signalements en cours de traitement
Quand je demande la suppression de mon compte
Alors les signalements sont automatiquement clôturés Et les contenus signalés sont masqués immédiatement Et aucune sanction n'est appliquée (compte déjà en suppression)
Étant donné que la suppression effective est exécutée
Quand le job de suppression se termine
Alors un log est créé avec:
| champ | valeur |
|---|---|
| user_id | [ID anonymisé] |
| deletion_requested_at | 2025-01-20T10:00:00Z |
| deletion_executed_at | 2025-02-19T02:00:00Z |
| deletion_cancelled | false |
| data_deleted | [liste des tables] |
| contents_anonymized | 10 |
Et ce log est conservé 5 ans pour audit RGPD Et l'user_id est pseudonymisé pour anonymat