Files
roadwave/output/documentation_complete.html
2026-01-31 11:45:11 +01:00

30529 lines
1.7 MiB
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Documentation RoadWave</title>
<style>
@page {
size: A4;
margin: 2cm;
@bottom-center {
content: counter(page);
}
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
font-size: 11pt;
line-height: 1.6;
color: #333;
max-width: 100%;
}
h1 {
color: #1a237e;
border-bottom: 3px solid #3f51b5;
padding-bottom: 10px;
page-break-after: avoid;
font-size: 24pt;
}
h2 {
color: #303f9f;
border-bottom: 1px solid #7986cb;
padding-bottom: 5px;
margin-top: 30px;
page-break-after: avoid;
font-size: 16pt;
}
h3 {
color: #3949ab;
page-break-after: avoid;
font-size: 13pt;
}
/* Couleurs Gherkin */
span[style*="#2196F3"] { color: #1565c0 !important; font-weight: bold; } /* Étant donné - Bleu */
span[style*="#FF9800"] { color: #e65100 !important; font-weight: bold; } /* Quand - Orange */
span[style*="#4CAF50"] { color: #2e7d32 !important; font-weight: bold; } /* Alors - Vert */
span[style*="#9E9E9E"] { color: #616161 !important; } /* Et - Gris */
span[style*="#F44336"] { color: #c62828 !important; font-weight: bold; } /* Mais - Rouge */
table {
border-collapse: collapse;
width: 100%;
margin: 15px 0;
font-size: 10pt;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #3f51b5;
color: white;
}
tr:nth-child(even) {
background-color: #f5f5f5;
}
blockquote {
border-left: 4px solid #3f51b5;
margin: 15px 0;
padding: 10px 20px;
background-color: #e8eaf6;
font-style: italic;
}
code {
background-color: #f5f5f5;
padding: 2px 6px;
border-radius: 3px;
font-family: "Fira Code", "Consolas", monospace;
font-size: 10pt;
}
pre {
background-color: #263238;
color: #aed581;
padding: 15px;
border-radius: 5px;
overflow-x: auto;
font-size: 9pt;
}
pre code {
background-color: transparent;
padding: 0;
color: inherit;
}
hr {
border: none;
border-top: 1px solid #e0e0e0;
margin: 30px 0;
}
a {
color: #1976d2;
text-decoration: none;
}
/* Info box (contexte) */
.info-box {
background-color: #e3f2fd;
border-left: 4px solid #2196f3;
padding: 15px;
margin: 15px 0;
}
/* Page breaks */
.page-break {
page-break-after: always;
}
/* Cover page */
.cover {
text-align: center;
padding-top: 200px;
}
.cover h1 {
font-size: 36pt;
border: none;
}
/* TOC */
.toc {
page-break-after: always;
}
.toc ul {
list-style: none;
padding-left: 20px;
}
.toc li {
margin: 5px 0;
}
</style>
</head>
<body>
<h1 id="documentation-roadwave">Documentation RoadWave</h1>
<hr />
<h2 id="table-des-matieres">Table des matières</h2>
<ul>
<li><a href="#roadwave">RoadWave</a></li>
<li><a href="#roadwave---architecture-technique">RoadWave - Architecture Technique</a></li>
<li><a href="#adr-001--langage-backend">ADR-001 : Langage Backend</a></li>
<li><a href="#adr-002--protocole-de-streaming">ADR-002 : Protocole de Streaming</a></li>
<li><a href="#adr-003--codec-audio">ADR-003 : Codec Audio</a></li>
<li><a href="#adr-004--cdn">ADR-004 : CDN</a></li>
<li><a href="#adr-005--base-de-données">ADR-005 : Base de Données</a></li>
<li><a href="#adr-006--chiffrement">ADR-006 : Chiffrement</a></li>
<li><a href="#adr-007--tests-et-spécifications-exécutables">ADR-007 : Tests et Spécifications Exécutables</a></li>
<li><a href="#adr-008--authentification-et-gestion-didentité">ADR-008 : Authentification et Gestion d'Identité</a></li>
<li><a href="#adr-009--solution-de-paiement-et-gestion-des-abonnements">ADR-009 : Solution de Paiement et Gestion des Abonnements</a></li>
<li><a href="#adr-010--commandes-au-volant-et-likes">ADR-010 : Commandes au volant et likes</a></li>
<li><a href="#adr-011--conformité-app-stores-et-plateformes-auto">ADR-011 : Conformité App Stores et Plateformes Auto</a></li>
<li><a href="#adr-012--architecture-backend">ADR-012 : Architecture Backend</a></li>
<li><a href="#adr-013--orm-et-accès-données">ADR-013 : ORM et Accès Données</a></li>
<li><a href="#adr-014--frontend-mobile">ADR-014 : Frontend Mobile</a></li>
<li><a href="#adr-015--stratégie-tests">ADR-015 : Stratégie Tests</a></li>
<li><a href="#règles-métier-roadwave">Règles métier RoadWave</a></li>
<li><a href="#annexe--fonctionnalités-reportées-post-mvp">Annexe : Fonctionnalités reportées Post-MVP</a></li>
<li><a href="#audio-guides-multi-séquences-pour-piétons">Audio-guides multi-séquences pour piétons</a></li>
<li><a href="#impact-des-abonnements-sur-lalgorithme">Impact des abonnements sur l'algorithme</a></li>
<li><a href="#limites-dabonnements-et-désabonnement">Limites d'abonnements et désabonnement</a></li>
<li><a href="#notifications-contextuelles-selon-le-mode-de-déplacement">Notifications contextuelles selon le mode de déplacement</a></li>
<li><a href="#création-daudio-guide-multi-séquences">Création d'audio-guide multi-séquences</a></li>
<li><a href="#intégration-audio-guides-avec-autres-fonctionnalités">Intégration audio-guides avec autres fonctionnalités</a></li>
<li><a href="#audio-guide-mode-piéton-navigation-manuelle">Audio-guide mode piéton (navigation manuelle)</a></li>
<li><a href="#audio-guide-mode-voiture-gps-automatique">Audio-guide mode voiture (GPS automatique)</a></li>
<li><a href="#audio-guides-modes-vélo-et-transport">Audio-guides modes vélo et transport</a></li>
<li><a href="#audio-guides-premium-et-monétisation">Audio-guides Premium et monétisation</a></li>
<li><a href="#sauvegarde-et-reprise-de-progression-audio-guide">Sauvegarde et reprise de progression audio-guide</a></li>
<li><a href="#classification-des-contenus-par-âge">Classification des contenus par âge</a></li>
<li><a href="#connexion-utilisateur">Connexion utilisateur</a></li>
<li><a href="#inscription-utilisateur">Inscription utilisateur</a></li>
<li><a href="#récupération-de-compte">Récupération de compte</a></li>
<li><a href="#gestion-des-sessions-et-tokens">Gestion des sessions et tokens</a></li>
<li><a href="#authentification-à-deux-facteurs-2fa">Authentification à deux facteurs (2FA)</a></li>
<li><a href="#vérification-demail">Vérification d'email</a></li>
<li><a href="#métadonnées-et-publication-de-contenu">Métadonnées et publication de contenu</a></li>
<li><a href="#modification-et-suppression-de-contenu">Modification et suppression de contenu</a></li>
<li><a href="#upload-et-encodage-de-contenu-audio">Upload et encodage de contenu audio</a></li>
<li><a href="#validation-des-3-premiers-contenus">Validation des 3 premiers contenus</a></li>
<li><a href="#élargissement-automatique-de-zone-quand-aucun-contenu-nest-disponible">Élargissement automatique de zone quand aucun contenu n'est disponible</a></li>
<li><a href="#gestion-dun-contenu-supprimé-pendant-lécoute">Gestion d'un contenu supprimé pendant l'écoute</a></li>
<li><a href="#mode-dégradé-sans-géolocalisation">Mode dégradé sans géolocalisation</a></li>
<li><a href="#gestion-de-la-perte-de-réseau-et-buffering-adaptatif">Gestion de la perte de réseau et buffering adaptatif</a></li>
<li><a href="#tests-bdd---documentation-des-fonctionnalités">Tests BDD - Documentation des fonctionnalités</a></li>
<li><a href="#pas-de-dégradation-temporelle-des-jauges">Pas de dégradation temporelle des jauges</a></li>
<li><a href="#évolution-des-jauges-dintérêt">Évolution des jauges d'intérêt</a></li>
<li><a href="#jauge-initiale-et-cold-start">Jauge initiale et cold start</a></li>
<li><a href="#synchronisation-actions-offline">Synchronisation actions offline</a></li>
<li><a href="#téléchargement-de-contenus-offline">Téléchargement de contenus offline</a></li>
<li><a href="#validité-et-renouvellement-contenus-offline">Validité et renouvellement contenus offline</a></li>
<li><a href="#modération-préventive">Modération préventive</a></li>
<li><a href="#sanctions-et-notifications-de-modération">Sanctions et notifications de modération</a></li>
<li><a href="#signalement-de-contenu-inapproprié">Signalement de contenu inapproprié</a></li>
<li><a href="#traitement-des-signalements-par-lia-et-les-modérateurs">Traitement des signalements par l'IA et les modérateurs</a></li>
<li><a href="#conditions-dactivation-de-la-monétisation">Conditions d'activation de la monétisation</a></li>
<li><a href="#contenus-premium-exclusifs">Contenus Premium exclusifs</a></li>
<li><a href="#désactivation-et-suspension-monétisation">Désactivation et suspension monétisation</a></li>
<li><a href="#kyc-et-inscription-à-la-monétisation">KYC et inscription à la monétisation</a></li>
<li><a href="#obligations-fiscales">Obligations fiscales</a></li>
<li><a href="#paiement-des-créateurs">Paiement des créateurs</a></li>
<li><a href="#sources-de-revenus-créateurs">Sources de revenus créateurs</a></li>
<li><a href="#actions-complémentaires-à-larrêt">Actions complémentaires à l'arrêt</a></li>
<li><a href="#commande-précédent">Commande "Précédent"</a></li>
<li><a href="#commandes-vocales-carplay-et-android-auto">Commandes vocales CarPlay et Android Auto</a></li>
<li><a href="#commandes-au-volant-et-interactions-simplifiées">Commandes au volant et interactions simplifiées</a></li>
<li><a href="#file-dattente-et-commande-suivant">File d'attente et commande "Suivant"</a></li>
<li><a href="#lecture-en-boucle-et-enchaînement-automatique">Lecture en boucle et enchaînement automatique</a></li>
<li><a href="#partage-de-contenu">Partage de contenu</a></li>
<li><a href="#avantages-premium">Avantages Premium</a></li>
<li><a href="#gestion-abonnement-premium">Gestion abonnement Premium</a></li>
<li><a href="#multi-devices-et-détection-simultanée">Multi-devices et détection simultanée</a></li>
<li><a href="#offre-et-tarification-premium">Offre et tarification Premium</a></li>
<li><a href="#profil-créateur">Profil créateur</a></li>
<li><a href="#création-de-campagnes-publicitaires">Création de campagnes publicitaires</a></li>
<li><a href="#caractéristiques-et-facturation-des-publicités">Caractéristiques et facturation des publicités</a></li>
<li><a href="#gestion-du-budget-et-alertes-publicitaires">Gestion du budget et alertes publicitaires</a></li>
<li><a href="#insertion-et-fréquence-des-publicités">Insertion et fréquence des publicités</a></li>
<li><a href="#métriques-dengagement-et-dashboard-publicitaire">Métriques d'engagement et dashboard publicitaire</a></li>
<li><a href="#validation-et-modération-des-publicités">Validation et modération des publicités</a></li>
<li><a href="#architecture-technique-radio-live">Architecture technique radio live</a></li>
<li><a href="#arrêt-du-live">Arrêt du live</a></li>
<li><a href="#comportement-auditeur-pendant-un-live">Comportement auditeur pendant un live</a></li>
<li><a href="#démarrage-dun-live">Démarrage d'un live</a></li>
<li><a href="#recherche-de-contenu">Recherche de contenu</a></li>
<li><a href="#classification-de-géo-pertinence-des-contenus">Classification de géo-pertinence des contenus</a></li>
<li><a href="#gestion-du-contenu-politique-mvp-simplifié">Gestion du contenu politique (MVP simplifié)</a></li>
<li><a href="#contenus-géolocalisés-en-mode-voiture">Contenus géolocalisés en mode voiture</a></li>
<li><a href="#gestion-de-lhistorique-et-reproposition">Gestion de l'historique et reproposition</a></li>
<li><a href="#médias-traditionnels-sur-roadwave">Médias traditionnels sur RoadWave</a></li>
<li><a href="#mode-kids-pour-utilisateurs-13-15-ans">Mode Kids pour utilisateurs 13-15 ans</a></li>
<li><a href="#paramétrabilité-admin-et-ab-testing">Paramétrabilité admin et A/B testing</a></li>
<li><a href="#paramétrabilité-utilisateur-et-profils">Paramétrabilité utilisateur et profils</a></li>
<li><a href="#formule-de-scoring-et-recommandation">Formule de scoring et recommandation</a></li>
<li><a href="#anonymisation-des-données-gps-après-24h">Anonymisation des données GPS après 24h</a></li>
<li><a href="#conformité-administrative-rgpd-registre-breach-dpo">Conformité administrative RGPD (Registre, Breach, DPO)</a></li>
<li><a href="#gestion-du-consentement-rgpd">Gestion du consentement RGPD</a></li>
<li><a href="#durée-de-conservation-des-données-et-purge-automatique">Durée de conservation des données et purge automatique</a></li>
<li><a href="#cookies-et-analytics-avec-matomo-self-hosted">Cookies et analytics avec Matomo self-hosted</a></li>
<li><a href="#mode-dégradé-avec-geoip-sans-gps-précis">Mode dégradé avec GeoIP (sans GPS précis)</a></li>
<li><a href="#portabilité-des-données-article-20-rgpd">Portabilité des données (Article 20 RGPD)</a></li>
<li><a href="#suppression-du-compte-utilisateur-article-17-rgpd---droit-à-leffacement">Suppression du compte utilisateur (Article 17 RGPD - Droit à l'effacement)</a></li>
</ul>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="roadwave">RoadWave</h1>
<p>Réseau social audio géolocalisé pour les usagers de la route.</p>
<h2 id="concept">Concept</h2>
<p>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.</p>
<p>Le contenu est diffusé en fonction de la position géographique de l'utilisateur et de ses centres d'intérêt.</p>
<hr />
<h2 id="cas-dusage">Cas d'usage</h2>
<table>
<thead>
<tr>
<th>Utilisateur</th>
<th>Scénario</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Conducteur</strong></td>
<td>É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</td>
</tr>
<tr>
<td><strong>Routier</strong></td>
<td>Écoute podcasts et radios live pendant ses trajets longue distance</td>
</tr>
<tr>
<td><strong>Touriste à pied</strong></td>
<td>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é</td>
</tr>
<tr>
<td><strong>Commerçant</strong></td>
<td>Diffuse une publicité audio ciblée GPS devant son commerce</td>
</tr>
<tr>
<td><strong>Passionné auto</strong></td>
<td>Découvre du contenu automobile près de circuits ou concessionnaires</td>
</tr>
<tr>
<td><strong>Habitant local</strong></td>
<td>Partage anecdotes ou bons plans géolocalisés dans son quartier</td>
</tr>
<tr>
<td><strong>Média traditionnel</strong></td>
<td>Le Monde, Le Parisien diffusent actualités géolocalisées ou nationales</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="utilisateurs">Utilisateurs</h2>
<p>Tout utilisateur peut écouter et créer du contenu (rôle flexible).</p>
<table>
<thead>
<tr>
<th>Rôle</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Auditeur</strong></td>
<td>Écoute, like, s'abonne à des créateurs, signale des contenus</td>
</tr>
<tr>
<td><strong>Créateur</strong></td>
<td>Publie du contenu audio géolocalisé (individus, médias traditionnels)</td>
</tr>
<tr>
<td><strong>Publicitaire</strong></td>
<td>Diffuse des publicités ciblées géographiquement</td>
</tr>
<tr>
<td><strong>Modérateur</strong></td>
<td>Valide et modère les contenus signalés</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="types-de-contenu">Types de contenu</h2>
<table>
<thead>
<tr>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Contenu court</strong></td>
<td>Audio de quelques secondes à quelques minutes</td>
</tr>
<tr>
<td><strong>Podcast</strong></td>
<td>Épisodes plus longs, séries thématiques</td>
</tr>
<tr>
<td><strong>Radio live</strong></td>
<td>Diffusion en direct avec synchronisation approximative entre auditeurs</td>
</tr>
<tr>
<td><strong>Audio-guide</strong></td>
<td>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</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="geolocalisation">Géolocalisation</h2>
<p>Le créateur définit la zone de diffusion de son contenu :</p>
<table>
<thead>
<tr>
<th>Niveau</th>
<th>Portée</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Point GPS</strong></td>
<td>Rayon précis autour d'une coordonnée</td>
</tr>
<tr>
<td><strong>Ville</strong></td>
<td>Diffusion dans une ville</td>
</tr>
<tr>
<td><strong>Département</strong></td>
<td>Diffusion départementale</td>
</tr>
<tr>
<td><strong>Région</strong></td>
<td>Diffusion régionale</td>
</tr>
<tr>
<td><strong>Pays</strong></td>
<td>Diffusion nationale</td>
</tr>
</tbody>
</table>
<p><strong>Priorité de diffusion</strong> : plus la zone est précise, plus le contenu a de chances d'être diffusé (GPS &gt; ville &gt; département &gt; région &gt; pays).</p>
<hr />
<h2 id="algorithme-de-recommandation">Algorithme de recommandation</h2>
<p>Le contenu proposé est calculé via un <strong>score combiné</strong> :</p>
<ul>
<li><strong>Proximité géographique</strong> : distance entre l'utilisateur et la zone du contenu</li>
<li><strong>Pertinence des intérêts</strong> : correspondance avec les centres d'intérêt de l'utilisateur</li>
</ul>
<p>Lorsque plusieurs contenus sont disponibles dans une zone, <strong>seul le plus pertinent est diffusé</strong>.</p>
<hr />
<h2 id="centres-dinteret">Centres d'intérêt</h2>
<p>Chaque utilisateur possède des <strong>jauges d'intérêt</strong> qui évoluent dynamiquement :</p>
<h3 id="categories">Catégories</h3>
<ul>
<li>Automobile</li>
<li>Voyage</li>
<li>Famille</li>
<li>Amour</li>
<li>Musique</li>
<li>Économie</li>
<li>Cryptomonnaie</li>
<li>Politique</li>
<li><em>... (extensible)</em></li>
</ul>
<h3 id="evolution-des-jauges">Évolution des jauges</h3>
<table>
<thead>
<tr>
<th>Action</th>
<th>Effet</th>
</tr>
</thead>
<tbody>
<tr>
<td>Temps d'écoute long</td>
<td>Augmente la jauge</td>
</tr>
<tr>
<td>Like</td>
<td>Augmente la jauge</td>
</tr>
<tr>
<td>Abonnement</td>
<td>Augmente fortement la jauge</td>
</tr>
<tr>
<td>Skip rapide</td>
<td>Diminue la jauge</td>
</tr>
</tbody>
</table>
<p>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.</p>
<hr />
<h2 id="interactions">Interactions</h2>
<h3 id="commandes-au-volant-conduite">Commandes au volant (conduite)</h3>
<p>Interactions simplifiées pour sécurité routière maximale :</p>
<table>
<thead>
<tr>
<th>Commande</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Suivant</strong></td>
<td>Passer au contenu suivant</td>
</tr>
<tr>
<td><strong>Précédent</strong></td>
<td>Revenir au contenu précédent</td>
</tr>
<tr>
<td><strong>Play/Pause</strong></td>
<td>Mettre en pause / reprendre la lecture</td>
</tr>
</tbody>
</table>
<p><strong>Like automatique</strong> : 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 &lt;10s → Signal négatif (-0.5 point)</p>
<blockquote>
<p>Voir <a href="#docs/adr/010-commandes-volant">ADR-010</a> pour les détails techniques</p>
</blockquote>
<h3 id="actions-complementaires-application-a-larret">Actions complémentaires (application à l'arrêt)</h3>
<table>
<thead>
<tr>
<th>Action</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Like explicite</strong></td>
<td>Bouton cœur pour liker manuellement</td>
</tr>
<tr>
<td><strong>S'abonner</strong></td>
<td>Suivre un créateur</td>
</tr>
<tr>
<td><strong>Signaler</strong></td>
<td>Signaler un contenu inapproprié</td>
</tr>
<tr>
<td><strong>Unlike</strong></td>
<td>Retirer un like</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="publicites">Publicités</h2>
<ul>
<li>Insertion <strong>entre deux contenus</strong> uniquement (jamais d'interruption)</li>
<li>Ciblage géographique : point GPS, ville, département, région ou national</li>
<li>Interface dédiée pour les publicitaires</li>
</ul>
<hr />
<h2 id="radio-live">Radio live</h2>
<ul>
<li>Diffusion en direct par des créateurs</li>
<li><strong>Buffering</strong> pour garantir une écoute fluide</li>
<li><strong>Synchronisation approximative</strong> entre les auditeurs (quelques secondes de décalage possible)</li>
</ul>
<hr />
<h2 id="moderation">Modération</h2>
<p>Approche hybride combinant participation communautaire, IA et modérateurs dédiés.</p>
<h3 id="contenus-prohibes">Contenus prohibés</h3>
<table>
<thead>
<tr>
<th>Catégorie</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Haine et violence</strong></td>
<td>Incitation à la haine, violence, discrimination</td>
</tr>
<tr>
<td><strong>Contenu sexuel</strong></td>
<td>Pornographie ou contenu sexuellement explicite</td>
</tr>
<tr>
<td><strong>Illégalité</strong></td>
<td>Apologie du terrorisme, actes criminels</td>
</tr>
<tr>
<td><strong>Désinformation dangereuse</strong></td>
<td>Fausses informations sur la santé, sécurité routière</td>
</tr>
<tr>
<td><strong>Harcèlement</strong></td>
<td>Menaces, intimidation, doxxing</td>
</tr>
<tr>
<td><strong>Droits d'auteur</strong></td>
<td>Violation de propriété intellectuelle</td>
</tr>
<tr>
<td><strong>Fraude</strong></td>
<td>Arnaques, escroqueries</td>
</tr>
</tbody>
</table>
<h3 id="roles-de-moderation">Rôles de modération</h3>
<table>
<thead>
<tr>
<th>Rôle</th>
<th>Capacités</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Auditeur lambda</strong></td>
<td>Signaler un contenu (1 clic)</td>
</tr>
<tr>
<td><strong>Auditeur de confiance</strong></td>
<td>Signalements priorisés après historique positif</td>
</tr>
<tr>
<td><strong>Modérateur junior</strong></td>
<td>Traiter signalements simples (spam, contenu évident)</td>
</tr>
<tr>
<td><strong>Modérateur senior</strong></td>
<td>Cas complexes, appels, décisions de ban</td>
</tr>
<tr>
<td><strong>Admin modération</strong></td>
<td>Définir les règles, superviser l'équipe</td>
</tr>
</tbody>
</table>
<h3 id="flux-de-moderation">Flux de modération</h3>
<pre><code>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
</code></pre>
<h3 id="outils-de-moderation-automatique">Outils de modération automatique</h3>
<table>
<thead>
<tr>
<th>Outil</th>
<th>Fonction</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Transcription audio</strong></td>
<td>Conversion automatique en texte pour analyse</td>
</tr>
<tr>
<td><strong>Analyse vocale IA</strong></td>
<td>Détection de ton agressif, cris, insultes</td>
</tr>
<tr>
<td><strong>Empreinte audio</strong></td>
<td>Détection de contenus déjà modérés (réupload)</td>
</tr>
<tr>
<td><strong>Détection droits d'auteur</strong></td>
<td>Identification automatique de musique protégée</td>
</tr>
<tr>
<td><strong>Filtrage mots-clés</strong></td>
<td>Liste noire de termes inappropriés</td>
</tr>
</tbody>
</table>
<h3 id="moderation-preventive">Modération préventive</h3>
<ul>
<li><strong>Nouveaux créateurs</strong> : validation manuelle des 3 premiers contenus</li>
<li><strong>Score de confiance</strong> : évolution selon l'historique du créateur</li>
<li><strong>Publicités</strong> : validation manuelle obligatoire avant diffusion</li>
</ul>
<h3 id="systeme-de-strikes">Système de strikes</h3>
<table>
<thead>
<tr>
<th>Strike</th>
<th>Sanction</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Strike 1</strong></td>
<td>Avertissement + formation modération</td>
</tr>
<tr>
<td><strong>Strike 2</strong></td>
<td>Suspension 7 jours + contenu supprimé</td>
</tr>
<tr>
<td><strong>Strike 3</strong></td>
<td>Suspension 30 jours</td>
</tr>
<tr>
<td><strong>Strike 4</strong></td>
<td>Ban définitif</td>
</tr>
</tbody>
</table>
<ul>
<li><strong>Réhabilitation</strong> : -1 strike tous les 6 mois sans incident</li>
</ul>
<h3 id="priorisation-des-signalements">Priorisation des signalements</h3>
<table>
<thead>
<tr>
<th>Priorité</th>
<th>Type de contenu</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>CRITIQUE</strong></td>
<td>Violence, suicide, mise en danger immédiate</td>
</tr>
<tr>
<td><strong>HAUTE</strong></td>
<td>Harcèlement, haine, désinformation</td>
</tr>
<tr>
<td><strong>MOYENNE</strong></td>
<td>Spam, contenu inapproprié</td>
</tr>
<tr>
<td><strong>BASSE</strong></td>
<td>Qualité audio, tags incorrects</td>
</tr>
</tbody>
</table>
<h3 id="transparence-et-recours">Transparence et recours</h3>
<ul>
<li><strong>Notification explicite</strong> lors de suppression (raison détaillée)</li>
<li><strong>Processus d'appel</strong> : le créateur peut contester une décision</li>
<li><strong>Délai de traitement</strong> : 48-72h pour les recours</li>
<li><strong>Historique</strong> : tableau de bord des sanctions pour le créateur</li>
</ul>
<h3 id="moderation-communautaire">Modération communautaire</h3>
<ul>
<li><strong>Utilisateurs de confiance</strong> : signalements priorisés après historique positif</li>
<li><strong>Récompenses</strong> : badges, réduction premium pour signalements pertinents</li>
<li>Lutte contre les signalements abusifs (sanctions possibles)</li>
</ul>
<hr />
<h2 id="modele-economique">Modèle économique</h2>
<h3 id="offres">Offres</h3>
<table>
<thead>
<tr>
<th>Formule</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Gratuit</strong></td>
<td>Accès complet avec publicités entre les contenus</td>
</tr>
<tr>
<td><strong>Premium</strong></td>
<td>Sans publicité + accès aux contenus exclusifs</td>
</tr>
</tbody>
</table>
<h3 id="monetisation-createurs">Monétisation créateurs</h3>
<ul>
<li><strong>Partage des revenus pub</strong> : rémunération basée sur le nombre d'écoutes</li>
<li><strong>Pourboires</strong> : les auditeurs peuvent faire des dons aux créateurs</li>
</ul>
<hr />
<h2 id="conformite-rgpd">Conformité RGPD</h2>
<h3 id="donnees-collectees">Données collectées</h3>
<table>
<thead>
<tr>
<th>Donnée</th>
<th>Finalité</th>
<th>Base légale</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Position GPS</strong></td>
<td>Diffusion de contenu géolocalisé</td>
<td>Consentement</td>
</tr>
<tr>
<td><strong>Historique d'écoute</strong></td>
<td>Personnalisation des recommandations</td>
<td>Intérêt légitime</td>
</tr>
<tr>
<td><strong>Centres d'intérêt</strong></td>
<td>Algorithme de recommandation</td>
<td>Consentement</td>
</tr>
<tr>
<td><strong>Identité créateur</strong></td>
<td>Publication de contenu</td>
<td>Exécution du contrat</td>
</tr>
</tbody>
</table>
<h3 id="droits-des-utilisateurs">Droits des utilisateurs</h3>
<ul>
<li><strong>Accès</strong> : consulter toutes ses données personnelles</li>
<li><strong>Rectification</strong> : modifier ses informations</li>
<li><strong>Suppression</strong> : supprimer son compte et toutes ses données</li>
<li><strong>Portabilité</strong> : exporter ses données dans un format standard</li>
<li><strong>Opposition</strong> : désactiver le profilage publicitaire</li>
</ul>
<h3 id="mesures-techniques">Mesures techniques</h3>
<ul>
<li>Consentement explicite requis pour la géolocalisation</li>
<li>Anonymisation des données de localisation après 24h (sauf historique personnel)</li>
<li>Possibilité d'utiliser l'app en mode dégradé (sans géolocalisation précise)</li>
<li>Données hébergées dans l'UE</li>
</ul>
<div style="page-break-after: always;"></div>
<h1 id="roadwave-architecture-technique">RoadWave - Architecture Technique</h1>
<blockquote>
<p>Les décisions techniques sont documentées dans <a href="#docs/adr/">docs/adr/</a></p>
</blockquote>
<h2 id="stack-technologique">Stack Technologique</h2>
<table>
<thead>
<tr>
<th>Composant</th>
<th>Technologie</th>
<th>ADR</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Backend</strong></td>
<td>Go + Fiber</td>
<td><a href="#docs/adr/001-langage-backend">ADR-001</a></td>
</tr>
<tr>
<td><strong>Architecture Backend</strong></td>
<td>Monolithe Modulaire</td>
<td><a href="#docs/adr/012-architecture-backend">ADR-012</a></td>
</tr>
<tr>
<td><strong>Authentification</strong></td>
<td>Zitadel</td>
<td><a href="#docs/adr/008-authentification">ADR-008</a></td>
</tr>
<tr>
<td><strong>Streaming</strong></td>
<td>HLS</td>
<td><a href="#docs/adr/002-protocole-streaming">ADR-002</a></td>
</tr>
<tr>
<td><strong>Codec</strong></td>
<td>Opus</td>
<td><a href="#docs/adr/003-codec-audio">ADR-003</a></td>
</tr>
<tr>
<td><strong>CDN</strong></td>
<td>Bunny CDN</td>
<td><a href="#docs/adr/004-cdn">ADR-004</a></td>
</tr>
<tr>
<td><strong>Base de données</strong></td>
<td>PostgreSQL + PostGIS</td>
<td><a href="#docs/adr/005-base-de-donnees">ADR-005</a></td>
</tr>
<tr>
<td><strong>ORM/Accès données</strong></td>
<td>sqlc</td>
<td><a href="#docs/adr/013-orm-acces-donnees">ADR-013</a></td>
</tr>
<tr>
<td><strong>Cache</strong></td>
<td>Redis Cluster</td>
<td><a href="#docs/adr/005-base-de-donnees">ADR-005</a></td>
</tr>
<tr>
<td><strong>Chiffrement</strong></td>
<td>TLS 1.3</td>
<td><a href="#docs/adr/006-chiffrement">ADR-006</a></td>
</tr>
<tr>
<td><strong>Live</strong></td>
<td>WebRTC</td>
<td><a href="#docs/adr/002-protocole-streaming">ADR-002</a></td>
</tr>
<tr>
<td><strong>Frontend Mobile</strong></td>
<td>Flutter</td>
<td><a href="#docs/adr/014-frontend-mobile">ADR-014</a></td>
</tr>
<tr>
<td><strong>Tests</strong></td>
<td>Testify + Godog (Gherkin)</td>
<td><a href="#docs/adr/015-strategie-tests">ADR-015</a>, <a href="#docs/adr/007-tests-bdd">ADR-007</a></td>
</tr>
<tr>
<td><strong>Paiements</strong></td>
<td>Mangopay</td>
<td><a href="#docs/adr/009-solution-paiement">ADR-009</a></td>
</tr>
<tr>
<td><strong>Commandes volant</strong></td>
<td>Like automatique</td>
<td><a href="#docs/adr/010-commandes-volant">ADR-010</a></td>
</tr>
<tr>
<td><strong>Conformité stores</strong></td>
<td>CarPlay, Android Auto, App/Play Store</td>
<td><a href="#docs/adr/011-conformite-stores-carplay-android-auto">ADR-011</a></td>
</tr>
</tbody>
</table>
<hr />
<h2 id="streaming-audio">Streaming Audio</h2>
<h3 id="protocole-hls-http-live-streaming">Protocole : HLS (HTTP Live Streaming)</h3>
<ul>
<li>Fonctionne à travers firewalls et réseaux mobiles instables</li>
<li>Cache CDN natif (réduction des coûts)</li>
<li>Bitrate adaptatif automatique (tunnels, zones rurales)</li>
<li>Support natif iOS/Android</li>
</ul>
<h3 id="codec-opus">Codec : Opus</h3>
<p>Optimisé pour la voix en environnement bruyant (voiture).</p>
<table>
<thead>
<tr>
<th>Qualité</th>
<th>Bitrate</th>
<th>Usage</th>
</tr>
</thead>
<tbody>
<tr>
<td>Basse</td>
<td>24 kbps</td>
<td>2G/Edge</td>
</tr>
<tr>
<td>Standard</td>
<td>48 kbps</td>
<td>3G</td>
</tr>
<tr>
<td>Haute</td>
<td>64 kbps</td>
<td>4G/5G</td>
</tr>
</tbody>
</table>
<p>Fallback AAC-LC pour appareils legacy.</p>
<h3 id="buffering-adaptatif">Buffering Adaptatif</h3>
<table>
<thead>
<tr>
<th>Réseau</th>
<th>Buffer min</th>
<th>Buffer cible</th>
<th>Buffer max</th>
</tr>
</thead>
<tbody>
<tr>
<td>WiFi</td>
<td>5s</td>
<td>30s</td>
<td>120s</td>
</tr>
<tr>
<td>4G/5G</td>
<td>10s</td>
<td>45s</td>
<td>120s</td>
</tr>
<tr>
<td>3G</td>
<td>30s</td>
<td>90s</td>
<td>300s</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="securite">Sécurité</h2>
<h3 id="chiffrement">Chiffrement</h3>
<ul>
<li><strong>TLS 1.3</strong> sur tous les endpoints (overhead ~1-2%)</li>
<li><strong>DTLS-SRTP</strong> pour WebRTC (radio live)</li>
<li>Pas de DRM initialement (ajout si licences l'exigent)</li>
</ul>
<h3 id="authentification">Authentification</h3>
<ul>
<li><strong>Zitadel</strong> (self-hosted) pour IAM</li>
<li>JWT validation locale (zitadel-go SDK)</li>
<li>OAuth2 PKCE pour mobile (iOS/Android)</li>
<li>MFA et passkeys disponibles</li>
<li>Rate limiting par IP et par utilisateur (Nginx + Zitadel)</li>
</ul>
<hr />
<h2 id="base-de-donnees">Base de Données</h2>
<h3 id="postgresql-postgis">PostgreSQL + PostGIS</h3>
<pre><code class="language-sql">-- 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;
</code></pre>
<h3 id="redis-geospatial-cache">Redis Geospatial (Cache)</h3>
<pre><code>GEOADD contents:geo longitude latitude content_id
GEORADIUS contents:geo user_lon user_lat 50 km WITHDIST COUNT 20 ASC
</code></pre>
<p>TTL cache : 5 minutes (le contenu ne bouge pas).</p>
<hr />
<h2 id="architecture-services">Architecture Services</h2>
<pre><code>┌─────────────────┐
│ 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 │
└───────┘ └───────────┘
</code></pre>
<hr />
<h2 id="scaling-10m-utilisateurs">Scaling 10M Utilisateurs</h2>
<h3 id="strategie-par-phase">Stratégie par phase</h3>
<table>
<thead>
<tr>
<th>Phase</th>
<th>Utilisateurs</th>
<th>Infra</th>
<th>Coût estimé</th>
</tr>
</thead>
<tbody>
<tr>
<td>MVP</td>
<td>0-100K</td>
<td>Monolithe Go, PostgreSQL managé + Zitadel, Bunny CDN/Storage</td>
<td>50-150€/mois</td>
</tr>
<tr>
<td>Growth</td>
<td>100K-1M</td>
<td>Kubernetes managé, replicas multi-région</td>
<td>2-5K€/mois</td>
</tr>
<tr>
<td>Scale</td>
<td>1M-10M</td>
<td>Multi-région, Nginx origin shield, Bunny CDN</td>
<td>20-50K€/mois</td>
</tr>
</tbody>
</table>
<h3 id="metriques-cibles">Métriques cibles</h3>
<table>
<thead>
<tr>
<th>Métrique</th>
<th>Objectif</th>
</tr>
</thead>
<tbody>
<tr>
<td>Latence API p99</td>
<td>&lt; 100ms</td>
</tr>
<tr>
<td>Temps de démarrage audio</td>
<td>&lt; 3s</td>
</tr>
<tr>
<td>Disponibilité</td>
<td>99.9%</td>
</tr>
<tr>
<td>Connexions/serveur</td>
<td>100K+</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="points-de-vigilance">Points de vigilance</h2>
<ol>
<li><strong>Buffering mobile</strong> : Pré-chargement agressif avant tunnels (détection GPS)</li>
<li><strong>Handoff réseau</strong> : Buffer suffisant pour survivre aux changements de cellule</li>
<li><strong>Mode offline</strong> : Téléchargement complet sur WiFi</li>
<li><strong>Bande passante</strong> : 48 kbps Opus = ~20 MB/heure (faible consommation data)</li>
</ol>
<hr />
<h2 id="pourquoi-pas-udp-brut">Pourquoi pas UDP brut ?</h2>
<table>
<thead>
<tr>
<th>UDP</th>
<th>HLS/TCP</th>
</tr>
</thead>
<tbody>
<tr>
<td>Latence minimale</td>
<td>Latence acceptable (5-30s)</td>
</tr>
<tr>
<td>Problèmes NAT/firewall</td>
<td>Passe partout</td>
</tr>
<tr>
<td>Perte de paquets = artefacts</td>
<td>Retransmission automatique</td>
</tr>
<tr>
<td>Pas de cache CDN</td>
<td>Cache CDN = économies</td>
</tr>
<tr>
<td>Complexité++</td>
<td>Standard de l'industrie</td>
</tr>
</tbody>
</table>
<p>Pour du contenu non-interactif (podcasts, audio-guides), la latence HLS est acceptable. WebRTC réservé à la radio live uniquement.</p>
<div style="page-break-after: always;"></div>
<h1 id="adr-001-langage-backend">ADR-001 : Langage Backend</h1>
<p><strong>Statut</strong> : Accepté
<strong>Date</strong> : 2025-01-17</p>
<h2 id="contexte">Contexte</h2>
<p>RoadWave doit gérer 10M d'utilisateurs avec des connexions concurrentes massives pour le streaming audio géolocalisé.</p>
<h2 id="decision">Décision</h2>
<p><strong>Go</strong> avec le framework <strong>Fiber</strong>.</p>
<h2 id="alternatives-considerees">Alternatives considérées</h2>
<table>
<thead>
<tr>
<th>Option</th>
<th>Performance</th>
<th>Simplicité</th>
<th>Écosystème</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Go + Fiber</strong></td>
<td>1M+ conn/serveur</td>
<td>Élevée</td>
<td>Excellent cloud-native</td>
</tr>
<tr>
<td>Rust + Tokio</td>
<td>2M+ conn/serveur</td>
<td>Faible</td>
<td>Bon</td>
</tr>
<tr>
<td>Node.js</td>
<td>100-500K conn</td>
<td>Élevée</td>
<td>Excellent</td>
</tr>
<tr>
<td>Elixir/Phoenix</td>
<td>2M+ conn</td>
<td>Moyenne</td>
<td>Bon temps réel</td>
</tr>
</tbody>
</table>
<h2 id="justification">Justification</h2>
<ul>
<li><strong>Performance</strong> : Go gère 1M+ connexions par serveur avec ~10KB/connexion</li>
<li><strong>Simplicité</strong> : Syntaxe claire, compilation rapide, facile à recruter</li>
<li><strong>Écosystème</strong> : First-class Kubernetes, tooling natif (profiling, race detection)</li>
<li><strong>Équilibre</strong> : Meilleur compromis performance/simplicité pour une startup</li>
</ul>
<h2 id="consequences">Conséquences</h2>
<ul>
<li>Formation équipe sur Go si nécessaire</li>
<li>Utilisation des bibliothèques : Fiber (HTTP), pgx (PostgreSQL), go-redis</li>
</ul>
<div style="page-break-after: always;"></div>
<h1 id="adr-002-protocole-de-streaming">ADR-002 : Protocole de Streaming</h1>
<p><strong>Statut</strong> : Accepté
<strong>Date</strong> : 2025-01-17</p>
<h2 id="contexte_1">Contexte</h2>
<p>Streaming audio vers des utilisateurs mobiles en voiture, avec réseaux instables (tunnels, zones rurales, handoff cellulaire).</p>
<h2 id="decision_1">Décision</h2>
<p><strong>HLS</strong> (HTTP Live Streaming) pour le contenu à la demande.
<strong>WebRTC</strong> réservé à la radio live.</p>
<h2 id="alternatives-considerees_1">Alternatives considérées</h2>
<table>
<thead>
<tr>
<th>Option</th>
<th>Latence</th>
<th>Fiabilité mobile</th>
<th>Cache CDN</th>
<th>Complexité</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>HLS</strong></td>
<td>5-30s</td>
<td>Excellente</td>
<td>Oui</td>
<td>Faible</td>
</tr>
<tr>
<td>DASH</td>
<td>5-30s</td>
<td>Bonne</td>
<td>Oui</td>
<td>Moyenne</td>
</tr>
<tr>
<td>WebRTC</td>
<td>&lt;500ms</td>
<td>Moyenne</td>
<td>Non</td>
<td>Élevée</td>
</tr>
<tr>
<td>UDP brut</td>
<td>Minimale</td>
<td>Faible</td>
<td>Non</td>
<td>Très élevée</td>
</tr>
</tbody>
</table>
<h2 id="justification_1">Justification</h2>
<ul>
<li><strong>Réseaux mobiles</strong> : HLS gère les coupures et changements de cellule nativement</li>
<li><strong>Cache CDN</strong> : Segments .ts cachables = réduction des coûts</li>
<li><strong>Compatibilité</strong> : Support natif iOS/Android</li>
<li><strong>Bitrate adaptatif</strong> : Ajustement automatique selon la qualité réseau</li>
</ul>
<h2 id="pourquoi-pas-udp">Pourquoi pas UDP ?</h2>
<ul>
<li>Problèmes NAT/firewall sur réseaux mobiles</li>
<li>Perte de paquets = artefacts audio</li>
<li>Impossible à cacher sur CDN</li>
<li>Complexité sans bénéfice pour du contenu non-interactif</li>
</ul>
<h2 id="consequences_1">Conséquences</h2>
<ul>
<li>Latence de 5-30s acceptable pour podcasts/audio-guides</li>
<li>WebRTC à implémenter séparément pour la radio live</li>
</ul>
<div style="page-break-after: always;"></div>
<h1 id="adr-003-codec-audio">ADR-003 : Codec Audio</h1>
<p><strong>Statut</strong> : Accepté
<strong>Date</strong> : 2025-01-17</p>
<h2 id="contexte_2">Contexte</h2>
<p>Audio diffusé en voiture : environnement bruyant, réseau mobile variable, qualité studio non nécessaire.</p>
<h2 id="decision_2">Décision</h2>
<p><strong>Opus</strong> comme codec principal, <strong>AAC-LC</strong> en fallback.</p>
<h2 id="profils-dencodage">Profils d'encodage</h2>
<table>
<thead>
<tr>
<th>Qualité</th>
<th>Bitrate</th>
<th>Usage</th>
</tr>
</thead>
<tbody>
<tr>
<td>Basse</td>
<td>24 kbps</td>
<td>2G/Edge</td>
</tr>
<tr>
<td>Standard</td>
<td>48 kbps</td>
<td>3G</td>
</tr>
<tr>
<td>Haute</td>
<td>64 kbps</td>
<td>4G/5G</td>
</tr>
</tbody>
</table>
<h2 id="alternatives-considerees_2">Alternatives considérées</h2>
<table>
<thead>
<tr>
<th>Codec</th>
<th>Bitrate</th>
<th>Qualité voix</th>
<th>Support mobile</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Opus</strong></td>
<td>24-64 kbps</td>
<td>Excellente</td>
<td>Android natif, iOS via libs</td>
</tr>
<tr>
<td>AAC-LC</td>
<td>64-128 kbps</td>
<td>Bonne</td>
<td>Universel</td>
</tr>
<tr>
<td>AAC-HE v2</td>
<td>32-64 kbps</td>
<td>Très bonne</td>
<td>Bon</td>
</tr>
<tr>
<td>MP3</td>
<td>128-320 kbps</td>
<td>Correcte</td>
<td>Universel (legacy)</td>
</tr>
</tbody>
</table>
<h2 id="justification_2">Justification</h2>
<ul>
<li><strong>Environnement bruyant</strong> : Opus intègre des algorithmes de résilience au bruit</li>
<li><strong>Bande passante</strong> : 48 kbps Opus ≈ qualité 96 kbps AAC pour la voix</li>
<li><strong>Consommation data</strong> : ~20 MB/heure à 48 kbps</li>
<li><strong>Latence</strong> : 2.5-60ms, idéal pour streaming adaptatif</li>
</ul>
<h2 id="consequences_2">Conséquences</h2>
<ul>
<li>Fallback AAC-LC pour appareils legacy</li>
<li>Pipeline d'encodage à prévoir côté ingestion</li>
</ul>
<div style="page-break-after: always;"></div>
<h1 id="adr-004-cdn">ADR-004 : CDN</h1>
<p><strong>Statut</strong> : Accepté
<strong>Date</strong> : 2025-01-17</p>
<h2 id="contexte_3">Contexte</h2>
<p>Distribution audio HLS à 10M d'utilisateurs, besoin de performance, coût maîtrisé, et indépendance vis-à-vis des géants du cloud.</p>
<h2 id="decision_3">Décision</h2>
<p><strong>Bunny CDN</strong> comme CDN principal.</p>
<h2 id="alternatives-considerees_3">Alternatives considérées</h2>
<table>
<thead>
<tr>
<th>Solution</th>
<th>Coût/mois (100TB)</th>
<th>Setup</th>
<th>Performance</th>
<th>Dépendance</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Bunny CDN</strong></td>
<td>~1 000€</td>
<td>15 min</td>
<td>Très bon</td>
<td>Faible</td>
</tr>
<tr>
<td>Cloudflare</td>
<td>0-5 000€</td>
<td>5 min</td>
<td>Excellent</td>
<td>Moyenne</td>
</tr>
<tr>
<td>CloudFront</td>
<td>~9 750€</td>
<td>1h</td>
<td>Excellent</td>
<td>Forte (AWS)</td>
</tr>
<tr>
<td>Fastly</td>
<td>~12-20 000€</td>
<td>2h</td>
<td>Exceptionnel</td>
<td>Moyenne</td>
</tr>
<tr>
<td>Nginx self-hosted</td>
<td>~2-5 000€</td>
<td>1 jour</td>
<td>Excellent</td>
<td>Aucune</td>
</tr>
</tbody>
</table>
<h2 id="justification_3">Justification</h2>
<ul>
<li><strong>Coût</strong> : 10x moins cher que CloudFront</li>
<li><strong>HLS natif</strong> : Support optimisé pour le streaming</li>
<li><strong>Simplicité</strong> : Setup en 15 minutes, zéro maintenance</li>
<li><strong>Européen</strong> : Conforme RGPD, 114 PoPs</li>
<li><strong>Pas de lock-in</strong> : Migration facile si besoin</li>
</ul>
<h2 id="evolution-prevue">Évolution prévue</h2>
<ol>
<li><strong>Phase 1</strong> (0-1M users) : Bunny CDN seul</li>
<li><strong>Phase 2</strong> (1-5M users) : Ajout Nginx origin shield si nécessaire</li>
<li><strong>Phase 3</strong> (5M+) : Évaluation multi-CDN</li>
</ol>
<h2 id="consequences_3">Conséquences</h2>
<ul>
<li>Configuration des règles de cache pour <code>.m3u8</code> (TTL court) et <code>.ts</code> (TTL long)</li>
<li>Token authentication pour protéger les segments</li>
</ul>
<div style="page-break-after: always;"></div>
<h1 id="adr-005-base-de-donnees">ADR-005 : Base de Données</h1>
<p><strong>Statut</strong> : Accepté
<strong>Date</strong> : 2025-01-17</p>
<h2 id="contexte_4">Contexte</h2>
<p>Requêtes géolocalisées intensives (contenus à proximité), données utilisateurs, historiques d'écoute.</p>
<h2 id="decision_4">Décision</h2>
<ul>
<li><strong>PostgreSQL + PostGIS</strong> : Données persistantes et requêtes géospatiales</li>
<li><strong>Redis Cluster</strong> : Cache géolocalisation et sessions</li>
</ul>
<h2 id="architecture">Architecture</h2>
<pre><code>Requête → Redis Cache → [HIT] → Réponse
[MISS]
PostGIS → Cache → Réponse
</code></pre>
<h2 id="alternatives-considerees_4">Alternatives considérées</h2>
<table>
<thead>
<tr>
<th>Usage</th>
<th>Option choisie</th>
<th>Alternatives</th>
</tr>
</thead>
<tbody>
<tr>
<td>Données utilisateurs</td>
<td>PostgreSQL</td>
<td>MySQL, MongoDB</td>
</tr>
<tr>
<td>Géolocalisation</td>
<td>PostGIS</td>
<td>MongoDB Geo, Elasticsearch</td>
</tr>
<tr>
<td>Cache</td>
<td>Redis</td>
<td>Memcached, KeyDB</td>
</tr>
<tr>
<td>Analytics (futur)</td>
<td>ClickHouse</td>
<td>TimescaleDB</td>
</tr>
</tbody>
</table>
<h2 id="justification_4">Justification</h2>
<h3 id="postgresql-postgis_1">PostgreSQL + PostGIS</h3>
<ul>
<li>Requêtes géospatiales complexes et précises</li>
<li>Index GIST pour performance</li>
<li>ACID, fiabilité éprouvée</li>
<li>Écosystème mature</li>
</ul>
<h3 id="redis">Redis</h3>
<ul>
<li>Cache géo natif (<code>GEORADIUS</code>) : 100K+ requêtes/sec</li>
<li>Sessions utilisateurs</li>
<li>Pub/sub pour temps réel</li>
</ul>
<h2 id="exemple-de-requete">Exemple de requête</h2>
<pre><code class="language-sql">SELECT 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;
</code></pre>
<h2 id="consequences_4">Conséquences</h2>
<ul>
<li>TTL cache Redis : 5 minutes (le contenu géolocalisé ne bouge pas)</li>
<li>Index GIST sur colonnes géométriques</li>
<li>Réplication read replicas pour scaling lecture</li>
</ul>
<div style="page-break-after: always;"></div>
<h1 id="adr-006-chiffrement">ADR-006 : Chiffrement</h1>
<p><strong>Statut</strong> : Accepté
<strong>Date</strong> : 2025-01-17</p>
<h2 id="contexte_5">Contexte</h2>
<p>Streaming audio sur réseaux mobiles, conformité RGPD, protection du contenu.</p>
<h2 id="decision_5">Décision</h2>
<ul>
<li><strong>TLS 1.3</strong> sur tous les endpoints</li>
<li><strong>DTLS-SRTP</strong> pour WebRTC (radio live)</li>
<li>Pas de DRM au lancement</li>
</ul>
<h2 id="alternatives-considerees_5">Alternatives considérées</h2>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Overhead</th>
<th>Usage</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>TLS 1.3</strong></td>
<td>~1-2% CPU</td>
<td>HTTPS streaming</td>
</tr>
<tr>
<td>DTLS-SRTP</td>
<td>~3-5% CPU</td>
<td>WebRTC temps réel</td>
</tr>
<tr>
<td>AES-128-CBC</td>
<td>Minimal</td>
<td>Chiffrement segments HLS</td>
</tr>
<tr>
<td>Widevine/FairPlay</td>
<td>Modéré</td>
<td>DRM (si licences l'exigent)</td>
</tr>
</tbody>
</table>
<h2 id="justification_5">Justification</h2>
<h3 id="pourquoi-chiffrer">Pourquoi chiffrer ?</h3>
<ul>
<li><strong>RGPD</strong> : Protection des données utilisateurs obligatoire</li>
<li><strong>Confiance</strong> : Standard attendu en 2025</li>
<li><strong>Intégrité</strong> : Empêche injection de contenu par opérateurs</li>
<li><strong>Overhead minimal</strong> : TLS 1.3 optimisé, impact négligeable</li>
</ul>
<h3 id="pourquoi-pas-de-drm">Pourquoi pas de DRM ?</h3>
<ul>
<li>Contenu généré par utilisateurs (pas de licences)</li>
<li>Complexité et coût d'intégration Widevine/FairPlay</li>
<li>À reconsidérer si partenariats avec labels/éditeurs</li>
</ul>
<h2 id="consequences_5">Conséquences</h2>
<ul>
<li>Certificats SSL gérés par Bunny CDN ou Let's Encrypt</li>
<li>Configuration TLS 1.3 sur Nginx/API</li>
<li>DTLS-SRTP à implémenter pour le module radio live</li>
</ul>
<div style="page-break-after: always;"></div>
<h1 id="adr-007-tests-et-specifications-executables">ADR-007 : Tests et Spécifications Exécutables</h1>
<p><strong>Statut</strong> : Accepté
<strong>Date</strong> : 2025-01-17</p>
<h2 id="contexte_6">Contexte</h2>
<p>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.</p>
<h2 id="decision_6">Décision</h2>
<p><strong>Gherkin</strong> pour les spécifications avec <strong>Godog</strong> comme runner de tests.</p>
<h2 id="alternatives-considerees_6">Alternatives considérées</h2>
<table>
<thead>
<tr>
<th>Option</th>
<th>Lisibilité</th>
<th>Intégration Go</th>
<th>Maintenance</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Gherkin + Godog</strong></td>
<td>Excellente</td>
<td>Native</td>
<td>Faible</td>
</tr>
<tr>
<td>Gauge (Markdown)</td>
<td>Bonne</td>
<td>Plugin</td>
<td>Moyenne</td>
</tr>
<tr>
<td>Tests Go natifs</td>
<td>Faible (devs only)</td>
<td>Native</td>
<td>Faible</td>
</tr>
<tr>
<td>Concordion</td>
<td>Bonne</td>
<td>Java-centric</td>
<td>Élevée</td>
</tr>
</tbody>
</table>
<h2 id="justification_6">Justification</h2>
<ul>
<li><strong>Living Documentation</strong> : Les fichiers <code>.feature</code> servent de documentation ET de tests</li>
<li><strong>Accessibilité</strong> : Syntaxe Given/When/Then lisible par PO, devs, testeurs</li>
<li><strong>Cohérence stack</strong> : Godog est le standard BDD pour Go</li>
<li><strong>CI/CD</strong> : Intégration simple dans les pipelines</li>
</ul>
<h2 id="structure">Structure</h2>
<pre><code>features/
├── recommendation/
│ ├── geolocalisation.feature
│ └── interets.feature
├── streaming/
│ ├── lecture.feature
│ └── buffering.feature
├── moderation/
│ └── signalement.feature
└── steps/
└── steps.go
</code></pre>
<h2 id="exemple">Exemple</h2>
<pre><code class="language-gherkin">Feature: Recommandation géolocalisée
Scenario: Touriste près d'un monument
Given un utilisateur avec l'intérêt &quot;tourisme&quot; à 80%
And une position GPS à 100m de la Tour Eiffel
When le système calcule les recommandations
Then l'audio guide &quot;Histoire de la Tour Eiffel&quot; est en première position
</code></pre>
<h2 id="consequences_6">Conséquences</h2>
<ul>
<li>Dépendance : <code>github.com/cucumber/godog</code></li>
<li>Les use cases du README doivent être traduits en <code>.feature</code></li>
<li>CI exécute <code>godog run</code> avant chaque merge</li>
</ul>
<div style="page-break-after: always;"></div>
<h1 id="adr-008-authentification-et-gestion-didentite">ADR-008 : Authentification et Gestion d'Identité</h1>
<p><strong>Statut</strong> : Accepté
<strong>Date</strong> : 2025-01-18</p>
<h2 id="contexte_7">Contexte</h2>
<p>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.</p>
<h2 id="decision_7">Décision</h2>
<p><strong>Zitadel</strong> (self-hosted) pour l'IAM avec validation JWT locale côté API Go.</p>
<h2 id="alternatives-considerees_7">Alternatives considérées</h2>
<table>
<thead>
<tr>
<th>Solution</th>
<th>Coût (10M users)</th>
<th>Performance</th>
<th>Simplicité</th>
<th>Intégration Go</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Zitadel</strong></td>
<td>200-500€/mois</td>
<td>Excellente</td>
<td>Élevée</td>
<td>SDK natif</td>
</tr>
<tr>
<td>Supabase Auth</td>
<td>32K€/mois</td>
<td>Excellente</td>
<td>Élevée</td>
<td>REST API</td>
</tr>
<tr>
<td>Keycloak</td>
<td>200-800€/mois</td>
<td>Bonne</td>
<td>Faible</td>
<td>Lib tierce</td>
</tr>
<tr>
<td>Auth0</td>
<td>50K€+/mois</td>
<td>Excellente</td>
<td>Élevée</td>
<td>SDK natif</td>
</tr>
<tr>
<td>JWT Custom</td>
<td>0€ (dev)</td>
<td>Excellente</td>
<td>Moyenne</td>
<td>Natif</td>
</tr>
</tbody>
</table>
<h2 id="justification_7">Justification</h2>
<ul>
<li><strong>Coût maîtrisé</strong> : 100x moins cher que Supabase/Auth0 à 10M users</li>
<li><strong>Performance</strong> : JWT validation locale = 0 latence auth sur chaque requête API</li>
<li><strong>Stack alignée</strong> : Go + PostgreSQL + Redis (déjà dans RoadWave)</li>
<li><strong>Scalabilité prouvée</strong> : Clients avec 2.3M tenants, architecture event-sourced</li>
<li><strong>RGPD natif</strong> : Entreprise suisse, data residency EU, DPA fourni</li>
<li><strong>Standards ouverts</strong> : OpenID Connect certifié (pas de vendor lock-in)</li>
</ul>
<h2 id="architecture_1">Architecture</h2>
<pre><code>┌─────────────────┐
│ 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
└─────────────────┘
</code></pre>
<h2 id="exemple-dintegration">Exemple d'intégration</h2>
<pre><code class="language-go">import &quot;github.com/zitadel/zitadel-go/v3/pkg/authorization/oauth&quot;
// Validation JWT locale haute performance
verifier := oauth.WithJWT(config)
app.Use(verifier.Middleware())
// Accès aux claims
userID := ctx.Locals(&quot;sub&quot;).(string)
</code></pre>
<h2 id="consequences_7">Conséquences</h2>
<ul>
<li>Déploiement Docker Compose pour MVP</li>
<li>Migration vers Kubernetes HA en production</li>
<li>Gestion refresh tokens (rotation automatique)</li>
<li>MFA et passkeys disponibles out-of-the-box</li>
<li>Rate limiting intégré à Zitadel</li>
</ul>
<div style="page-break-after: always;"></div>
<h1 id="adr-009-solution-de-paiement-et-gestion-des-abonnements">ADR-009 : Solution de Paiement et Gestion des Abonnements</h1>
<p><strong>Statut</strong> : Accepté
<strong>Date</strong> : 2025-01-19</p>
<h2 id="contexte_8">Contexte</h2>
<p>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.</p>
<h2 id="decision_8">Décision</h2>
<p><strong>Mangopay</strong> (France/Luxembourg) comme solution unique pour paiements, marketplace et abonnements.</p>
<h2 id="alternatives-considerees_8">Alternatives considérées</h2>
<table>
<thead>
<tr>
<th>Solution</th>
<th>Coût transaction</th>
<th>Marketplace</th>
<th>KYC</th>
<th>Souveraineté</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Mangopay</strong></td>
<td>1.8% + 0.18€</td>
<td>✅ Natif</td>
<td>✅ Gratuit</td>
<td>🇪🇺 France/LU</td>
</tr>
<tr>
<td>Stripe Connect</td>
<td>2.9% + 0.30€</td>
<td>✅ Natif</td>
<td>❌ 1.20€</td>
<td>🇺🇸 USA</td>
</tr>
<tr>
<td>Mollie</td>
<td>2.9% + 0.29€</td>
<td>❌ Non</td>
<td>❌ Non</td>
<td>🇪🇺 Pays-Bas</td>
</tr>
<tr>
<td>Paddle</td>
<td>5% + 0.50€</td>
<td>✅ Natif</td>
<td>✅ Inclus</td>
<td>🇬🇧 UK</td>
</tr>
</tbody>
</table>
<h2 id="justification_8">Justification</h2>
<ul>
<li><strong>38% moins cher</strong> que Stripe (1.8% vs 2.9%)</li>
<li><strong>Marketplace natif</strong> : E-wallets automatiques, split payments 70/30, payouts SEPA gratuits</li>
<li><strong>KYC gratuit</strong> : vérification d'identité incluse (vs 1.20€/créateur chez Stripe)</li>
<li><strong>Souveraineté EU</strong> : France/Luxembourg, régulé ACPR, RGPD natif</li>
<li><strong>Conformité DAC7</strong> : reporting fiscal automatique</li>
<li><strong>Spécialisé marketplace</strong> : utilisé par Vinted, Ulule, ManoMano</li>
</ul>
<h2 id="architecture_2">Architecture</h2>
<pre><code>┌────────────────────────┐
│ 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%)│ │ │
└─────┘ └─────┘ └──────┘
</code></pre>
<h2 id="exemple-integration">Exemple intégration</h2>
<pre><code class="language-go">// Abonnement récurrent
POST /v2.01/{ClientId}/recurringpayinregistrations
{
&quot;AuthorId&quot;: &quot;{UserId}&quot;,
&quot;FirstTransactionDebitedFunds&quot;: {&quot;Currency&quot;: &quot;EUR&quot;, &quot;Amount&quot;: 499}
}
// Transfer vers créateur (70%)
POST /v2.01/{ClientId}/transfers
{
&quot;DebitedWalletId&quot;: &quot;{PlatformWalletId}&quot;,
&quot;CreditedWalletId&quot;: &quot;{CreatorWalletId}&quot;,
&quot;DebitedFunds&quot;: {&quot;Currency&quot;: &quot;EUR&quot;, &quot;Amount&quot;: 349}
}
// Payout SEPA gratuit
POST /v2.01/{ClientId}/payouts/bankwire
</code></pre>
<h2 id="consequences_8">Conséquences</h2>
<ul>
<li>Solution tout-en-un : 1 seul prestataire vs 2-3</li>
<li>Économie de 2160€/an sur 1000 abonnés (vs Stripe)</li>
<li>Délai activation compte : 2-5 jours</li>
<li>Intégration Go via REST API (pas de SDK Go officiel)</li>
<li>Apple/Google IAP gérés séparément (comme toute solution de paiement)</li>
</ul>
<div style="page-break-after: always;"></div>
<h1 id="adr-010-commandes-au-volant-et-likes">ADR-010 : Commandes au volant et likes</h1>
<p><strong>Statut</strong> : Accepté
<strong>Date</strong> : 2026-01-20</p>
<h2 id="contexte_9">Contexte</h2>
<p>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</p>
<h2 id="decision_9">Décision</h2>
<p><strong>Like automatique basé sur le temps d'écoute</strong>.</p>
<p>Règles :
- ≥80% d'écoute → Like renforcé (+2 points)
- 30-79% d'écoute → Like standard (+1 point)
- &lt;30% d'écoute → Pas de like
- Skip &lt;10s → Signal négatif (-0.5 point)</p>
<h2 id="alternatives-considerees_9">Alternatives considérées</h2>
<table>
<thead>
<tr>
<th>Option</th>
<th>Compatibilité</th>
<th>Sécurité</th>
<th>Complexité</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Like automatique</strong></td>
<td>100%</td>
<td>Maximale</td>
<td>Faible</td>
</tr>
<tr>
<td>Double-tap Pause</td>
<td>~80%</td>
<td>Moyenne</td>
<td>Moyenne</td>
</tr>
<tr>
<td>Appui long Suivant</td>
<td>~95%</td>
<td>Faible</td>
<td>Élevée</td>
</tr>
<tr>
<td>Configuration paramétrable</td>
<td>100%</td>
<td>Variable</td>
<td>Très élevée</td>
</tr>
</tbody>
</table>
<h2 id="justification_9">Justification</h2>
<ul>
<li><strong>Sécurité maximale</strong> : Aucune action complexe en conduite</li>
<li><strong>Compatibilité universelle</strong> : Fonctionne sur 100% des véhicules</li>
<li><strong>UX intuitive</strong> : Comportement standard (Spotify, YouTube Music)</li>
<li><strong>Engagement</strong> : Tous les contenus génèrent des signaux</li>
<li><strong>Simplicité</strong> : Une seule logique à implémenter et maintenir</li>
</ul>
<h2 id="consequences_9">Conséquences</h2>
<ul>
<li>Tracking du temps d'écoute via le player audio</li>
<li>Calcul du score côté backend basé sur <code>completion_rate</code></li>
<li>Communication onboarding : "Vos likes sont automatiques selon votre temps d'écoute"</li>
<li>Possibilité de like manuel depuis l'app (à l'arrêt)</li>
<li>Métriques à suivre : taux de complétion, distribution des scores, feedbacks utilisateurs</li>
</ul>
<div style="page-break-after: always;"></div>
<h1 id="adr-011-conformite-app-stores-et-plateformes-auto">ADR-011 : Conformité App Stores et Plateformes Auto</h1>
<p><strong>Statut</strong> : Accepté avec actions requises
<strong>Date</strong> : 2026-01-20</p>
<h2 id="contexte_10">Contexte</h2>
<p>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)</p>
<h2 id="decision_10">Décision</h2>
<p><strong>Stratégie de conformité multi-plateforme</strong> 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)</p>
<h2 id="plateformes-analysees">Plateformes analysées</h2>
<table>
<thead>
<tr>
<th>Plateforme</th>
<th>Conformité</th>
<th>Points critiques</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Android Auto</strong></td>
<td>✅ Conforme</td>
<td>API Level 35+ (Android 15+)</td>
</tr>
<tr>
<td><strong>CarPlay</strong></td>
<td>✅ Conforme</td>
<td>Entitlement audio à demander</td>
</tr>
<tr>
<td><strong>Google Play</strong></td>
<td>⚠️ Actions requises</td>
<td>Déclaration GPS + UGC modération</td>
</tr>
<tr>
<td><strong>App Store</strong></td>
<td>⚠️ Actions requises</td>
<td>Prix différenciés US/EU</td>
</tr>
</tbody>
</table>
<h2 id="conformite-detaillee">Conformité détaillée</h2>
<h3 id="android-auto-carplay">Android Auto / CarPlay ✅</h3>
<ul>
<li>100% audio (pas de vidéo)</li>
<li>Commandes standard au volant</li>
<li>Aucun achat in-car</li>
<li>Like automatique = sécurité maximale</li>
<li><strong>Notifications géolocalisées</strong> : sonore uniquement en mode CarPlay/Android Auto (pas d'overlay visuel)</li>
<li><strong>Action</strong> : Demander CarPlay Audio Entitlement (Apple)</li>
</ul>
<h3 id="google-play">Google Play ⚠️</h3>
<p><strong>UGC (critique)</strong> :
- Modération hybride IA + humain ✅
- 3 premiers contenus validés manuellement ✅
- Système de strikes (4 = ban) ✅
- Signalement + blocage utilisateurs ✅</p>
<p><strong>GPS Background (critique)</strong> :
- Permission "Always Location" = <strong>OPTIONNELLE</strong>
- Demandée uniquement pour mode piéton (notifications arrière-plan audio-guides)
- Justification Play Console :</p>
<blockquote>
<p>"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
- <strong>Action</strong> : Remplir formulaire background location Play Console avec justification</p>
</blockquote>
<p><strong>Réponses formulaire Play Console</strong> :</p>
<table>
<thead>
<tr>
<th>Question</th>
<th>Réponse</th>
</tr>
</thead>
<tbody>
<tr>
<td>Why does your app need background location?</td>
<td>"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."</td>
</tr>
<tr>
<td>Is this feature core to your app?</td>
<td>"No. This is an optional feature. Users can use RoadWave without background location permission (in-car mode works with foreground location only)."</td>
</tr>
<tr>
<td>What user value does this provide?</td>
<td>"Pedestrian users (tourists, museum visitors) can keep phone in pocket and receive audio-guide alerts automatically without opening the app."</td>
</tr>
<tr>
<td>Does a less invasive alternative exist?</td>
<td>"Yes. Users can use manual navigation (open app, select audio-guide). Background location is a convenience feature for hands-free experience."</td>
</tr>
</tbody>
</table>
<h3 id="app-store">App Store ⚠️</h3>
<p><strong>Prix différenciés (légaux depuis 2025-2026)</strong> :
- 🇺🇸 US : Lien externe autorisé (0% commission)
- 🇪🇺 EU : Paiement externe DMA (7-20% commission réduite)
- 🌍 Monde : IAP obligatoire (30% commission)</p>
<p><strong>UGC</strong> :
- Mode Kids obligatoire (filtrage selon âge) ✅
- Système de modération + signalement ✅</p>
<p><strong>GPS Background (critique)</strong> :
- Permission "Always Location" = <strong>OPTIONNELLE</strong>
- Deux strings Info.plist requises :
- <code>NSLocationWhenInUseUsageDescription</code> : explication mode voiture
- <code>NSLocationAlwaysAndWhenInUseUsageDescription</code> : 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
- <strong>Action</strong> : Voir strings détaillés dans <a href="#../regles-metier/05-interactions-navigation.md#512-mode-piéton-audio-guides">05-interactions-navigation.md</a></p>
<h3 id="revenus-createurs">Revenus créateurs</h3>
<p><strong>Position</strong> : 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</p>
<h2 id="actions-bloquantes-avant-soumission">Actions bloquantes avant soumission</h2>
<table>
<thead>
<tr>
<th>Action</th>
<th>Plateforme</th>
<th>Deadline</th>
<th>Complexité</th>
</tr>
</thead>
<tbody>
<tr>
<td>Demander CarPlay Audio Entitlement</td>
<td>Apple</td>
<td>Avant soumission iOS</td>
<td>Faible</td>
</tr>
<tr>
<td>Remplir formulaire background location avec justification</td>
<td>Google Play</td>
<td>Avant soumission Android</td>
<td>Faible</td>
</tr>
<tr>
<td>Implémenter disclosure GPS (écran dédié mode piéton)</td>
<td>iOS + Android</td>
<td>MVP</td>
<td>Moyenne</td>
</tr>
<tr>
<td>Rendre permission "Always Location" optionnelle</td>
<td>iOS + Android</td>
<td>MVP</td>
<td>Moyenne</td>
</tr>
<tr>
<td>Désactiver overlay visuel notification en CarPlay/Android Auto</td>
<td>iOS + Android</td>
<td>MVP</td>
<td>Moyenne</td>
</tr>
<tr>
<td>Mettre à jour strings Info.plist avec justifications détaillées</td>
<td>iOS</td>
<td>MVP</td>
<td>Faible</td>
</tr>
<tr>
<td>Finaliser système modération UGC</td>
<td>Google + Apple</td>
<td>MVP</td>
<td>Élevée</td>
</tr>
</tbody>
</table>
<p><strong>Estimation totale</strong> : +5 jours développement avant soumission stores</p>
<h2 id="strategie-de-lancement">Stratégie de lancement</h2>
<p><strong>Phase 1 - MVP</strong> :
- IAP uniquement (5.99€/mois mondial)
- Modération UGC active
- GPS avec disclosure
- CarPlay/Android Auto basique</p>
<p><strong>Phase 2 - Post-validation</strong> :
- Prix différenciés US (lien externe 4.99€)
- Paiement externe EU (DMA)
- Monétisation créateurs (Mangopay)</p>
<h2 id="consequences_10">Conséquences</h2>
<ul>
<li>Formation équipe sur politiques stores</li>
<li>Suivi des métriques modération (% rejet, SLA)</li>
<li>Migration iOS 26 SDK (Avril 2026)</li>
<li>API Level 35 Android (2026)</li>
<li>Communication transparente GPS/publicités</li>
</ul>
<h2 id="sources">Sources</h2>
<ul>
<li><a href="https://developer.android.com/training/cars/media">Android Auto Media Apps</a></li>
<li><a href="https://developer.apple.com/carplay">CarPlay Developer Guide</a></li>
<li><a href="https://support.google.com/googleplay/android-developer/answer/9876937">Google Play UGC Policy</a></li>
<li><a href="https://developer.apple.com/app-store/review/guidelines/">App Store Guidelines</a></li>
<li><a href="https://www.revenuecat.com/blog/growth/apple-eu-dma-update-june-2025/">Apple DMA Update EU</a></li>
<li><a href="https://support.google.com/googleplay/android-developer/answer/9799150">Google Background Location 2026</a></li>
</ul>
<div style="page-break-after: always;"></div>
<h1 id="adr-012-architecture-backend">ADR-012 : Architecture Backend</h1>
<p><strong>Statut</strong> : Accepté
<strong>Date</strong> : 2025-01-20</p>
<h2 id="contexte_11">Contexte</h2>
<p>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.</p>
<h2 id="decision_11">Décision</h2>
<p><strong>Monolithe modulaire</strong> avec séparation claire en modules internes.</p>
<h2 id="alternatives-considerees_10">Alternatives considérées</h2>
<table>
<thead>
<tr>
<th>Architecture</th>
<th>Complexité</th>
<th>Coûts infra</th>
<th>Time to market</th>
<th>Évolutivité</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Monolithe modulaire</strong></td>
<td>Faible</td>
<td>Faible</td>
<td>Rapide</td>
<td>0-1M users</td>
</tr>
<tr>
<td>Microservices</td>
<td>Élevée</td>
<td>Élevée</td>
<td>Lent</td>
<td>1M+ users</td>
</tr>
<tr>
<td>Hybrid (Mono + Workers)</td>
<td>Moyenne</td>
<td>Moyenne</td>
<td>Moyen</td>
<td>100K-5M users</td>
</tr>
</tbody>
</table>
<h2 id="justification_10">Justification</h2>
<ul>
<li><strong>Simplicité</strong> : 1 seul binaire Go, déploiement trivial</li>
<li><strong>Transactions</strong> : Communications inter-modules en mémoire (pas de latence réseau)</li>
<li><strong>Debugging</strong> : Stack traces complètes, profiling unifié</li>
<li><strong>Coûts</strong> : 1 serveur suffit pour 100K users (vs N services)</li>
<li><strong>Refactoring</strong> : Modules internes bien séparés facilitent migration vers microservices si nécessaire</li>
</ul>
<h2 id="structure-modulaire">Structure modulaire</h2>
<pre><code>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
</code></pre>
<p>Chaque module suit : <code>handler.go</code> → <code>service.go</code> → <code>repository.go</code>.</p>
<h2 id="consequences_11">Conséquences</h2>
<ul>
<li>Scaling horizontal : réplication complète du binaire (acceptable jusqu'à 1M users)</li>
<li>Transition vers microservices possible en phase 2 (extraction progressive des modules)</li>
<li>Importance de maintenir découplage fort entre modules (interfaces claires)</li>
</ul>
<div style="page-break-after: always;"></div>
<h1 id="adr-013-orm-et-acces-donnees">ADR-013 : ORM et Accès Données</h1>
<p><strong>Statut</strong> : Accepté
<strong>Date</strong> : 2025-01-20</p>
<h2 id="contexte_12">Contexte</h2>
<p>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.</p>
<h2 id="decision_12">Décision</h2>
<p><strong>sqlc</strong> pour génération de code Go type-safe depuis SQL.</p>
<h2 id="alternatives-considerees_11">Alternatives considérées</h2>
<table>
<thead>
<tr>
<th>Solution</th>
<th>Performance</th>
<th>Type Safety</th>
<th>Contrôle SQL</th>
<th>Courbe apprentissage</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>sqlc</strong></td>
<td>Excellente</td>
<td>Très haute</td>
<td>Total</td>
<td>Faible</td>
</tr>
<tr>
<td>GORM</td>
<td>Moyenne</td>
<td>Moyenne</td>
<td>Limité</td>
<td>Faible</td>
</tr>
<tr>
<td>pgx + SQL brut</td>
<td>Excellente</td>
<td>Faible</td>
<td>Total</td>
<td>Moyenne</td>
</tr>
<tr>
<td>sqlx</td>
<td>Bonne</td>
<td>Faible</td>
<td>Total</td>
<td>Faible</td>
</tr>
</tbody>
</table>
<h2 id="justification_11">Justification</h2>
<ul>
<li><strong>Performance</strong> : Génération compile-time, zero overhead runtime</li>
<li><strong>Type safety</strong> : Structs Go générées automatiquement, erreurs détectées à la compilation</li>
<li><strong>Contrôle SQL</strong> : Requêtes PostGIS complexes écrites en pur SQL (pas de limitations ORM)</li>
<li><strong>Maintenabilité</strong> : Modifications SQL → <code>sqlc generate</code> → code mis à jour</li>
<li><strong>Simplicité</strong> : Pas de magic, code généré lisible et debuggable</li>
</ul>
<h2 id="workflow">Workflow</h2>
<pre><code class="language-sql">-- 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;
</code></pre>
<pre><code class="language-bash">sqlc generate
</code></pre>
<pre><code class="language-go">// Code Go type-safe généré automatiquement
contents, err := q.GetContentNearby(ctx, location, radius, limit)
</code></pre>
<h2 id="consequences_12">Conséquences</h2>
<ul>
<li>Dépendance : <code>github.com/sqlc-dev/sqlc</code></li>
<li>Fichier <code>sqlc.yaml</code> à la racine pour configuration</li>
<li>Migrations gérées séparément avec <code>golang-migrate</code></li>
<li>CI doit exécuter <code>sqlc generate</code> pour valider cohérence SQL/Go</li>
</ul>
<div style="page-break-after: always;"></div>
<h1 id="adr-014-frontend-mobile">ADR-014 : Frontend Mobile</h1>
<p><strong>Statut</strong> : Accepté
<strong>Date</strong> : 2025-01-20</p>
<h2 id="contexte_13">Contexte</h2>
<p>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.</p>
<h2 id="decision_13">Décision</h2>
<p><strong>Flutter</strong> pour iOS et Android avec codebase unique.</p>
<h2 id="alternatives-considerees_12">Alternatives considérées</h2>
<table>
<thead>
<tr>
<th>Framework</th>
<th>Codebase</th>
<th>Performance</th>
<th>Audio/CarPlay</th>
<th>Communauté</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Flutter</strong></td>
<td>Unique</td>
<td>Native</td>
<td>Excellente</td>
<td>Large</td>
</tr>
<tr>
<td>React Native</td>
<td>Unique</td>
<td>Bonne</td>
<td>Modules natifs requis</td>
<td>Très large</td>
</tr>
<tr>
<td>Native (Swift+Kotlin)</td>
<td>Double</td>
<td>Excellente</td>
<td>Native</td>
<td>Large</td>
</tr>
<tr>
<td>Ionic/Capacitor</td>
<td>Unique</td>
<td>Moyenne</td>
<td>Limitée</td>
<td>Moyenne</td>
</tr>
</tbody>
</table>
<h2 id="justification_12">Justification</h2>
<ul>
<li><strong>Codebase unique</strong> : iOS + Android maintenus ensemble, vélocité développement x2</li>
<li><strong>Performance</strong> : Dart compilé en code natif (pas de bridge JS)</li>
<li><strong>Audio HLS</strong> : Package <code>just_audio</code> mature avec support HLS, buffering adaptatif</li>
<li><strong>CarPlay/Android Auto</strong> : Support via packages communautaires (<code>flutter_carplay</code>, <code>android_auto_flutter</code>)</li>
<li><strong>Géolocalisation</strong> : <code>geolocator</code> robuste avec gestion permissions</li>
<li><strong>Écosystème</strong> : Widgets riches (Material/Cupertino), state management mature (Bloc, Riverpod)</li>
</ul>
<h2 id="packages-cles">Packages clés</h2>
<pre><code class="language-yaml">dependencies:
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
</code></pre>
<p><strong>Nouveaux packages (contenus géolocalisés)</strong> :</p>
<ul>
<li><strong><code>geofence_service</code></strong> : Détection entrée/sortie rayon 200m en arrière-plan (mode piéton)</li>
<li>Geofencing natif iOS/Android</li>
<li>Minimise consommation batterie</li>
<li>
<p>Supporte notifications push même app fermée</p>
</li>
<li>
<p><strong><code>flutter_local_notifications</code></strong> : Notifications locales avec compteur dynamique</p>
</li>
<li>Notification avec compteur décroissant (7→1) en mode voiture</li>
<li>Icônes personnalisées selon type contenu</li>
<li>Désactivation overlay en mode CarPlay/Android Auto (conformité)</li>
</ul>
<h2 id="structure-application">Structure application</h2>
<pre><code>lib/
├── core/ # Config, DI, routes
├── data/ # Repositories, API clients
├── domain/ # Models, business logic
├── presentation/ # UI (screens, widgets, blocs)
└── main.dart
</code></pre>
<h2 id="consequences_13">Conséquences</h2>
<ul>
<li>Équipe doit apprendre Dart (syntaxe proche Java/TypeScript)</li>
<li>Taille binaire : 8-15 MB (acceptable)</li>
<li>Tests : <code>flutter_test</code> pour widgets, <code>integration_test</code> pour E2E</li>
<li>CI/CD : Fastlane pour déploiement stores</li>
</ul>
<div style="page-break-after: always;"></div>
<h1 id="adr-015-strategie-tests">ADR-015 : Stratégie Tests</h1>
<p><strong>Statut</strong> : Accepté
<strong>Date</strong> : 2025-01-20</p>
<h2 id="contexte_14">Contexte</h2>
<p>RoadWave nécessite une couverture tests robuste avec documentation vivante des use cases. La stratégie doit équilibrer vélocité développement et qualité.</p>
<h2 id="decision_14">Décision</h2>
<p>Approche <strong>multi-niveaux</strong> : unitaires, intégration, BDD (Gherkin), E2E, load testing.</p>
<h2 id="strategie-par-type">Stratégie par type</h2>
<table>
<thead>
<tr>
<th>Type</th>
<th>Framework</th>
<th>Cible</th>
<th>Fréquence</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Unitaires</strong></td>
<td>Testify</td>
<td>80%+ couverture</td>
<td>Chaque commit</td>
</tr>
<tr>
<td><strong>Intégration DB</strong></td>
<td>Testify + Testcontainers</td>
<td>Repositories critiques</td>
<td>Avant merge PR</td>
</tr>
<tr>
<td><strong>BDD (Gherkin)</strong></td>
<td>Godog</td>
<td>User stories</td>
<td>Avant release</td>
</tr>
<tr>
<td><strong>E2E Mobile</strong></td>
<td>Flutter integration_test</td>
<td>Parcours critiques</td>
<td>Nightly</td>
</tr>
<tr>
<td><strong>Load</strong></td>
<td>k6</td>
<td>N/A</td>
<td>Avant mise en prod</td>
</tr>
</tbody>
</table>
<h2 id="tests-unitaires-testify">Tests unitaires (Testify)</h2>
<pre><code class="language-go">// internal/user/service_test.go
func TestGetUserByID(t *testing.T) {
mockRepo := new(MockRepository)
service := NewService(mockRepo)
mockRepo.On(&quot;FindByID&quot;, &quot;123&quot;).Return(&amp;User{ID: &quot;123&quot;}, nil)
user, err := service.GetByID(&quot;123&quot;)
assert.NoError(t, err)
assert.Equal(t, &quot;123&quot;, user.ID)
mockRepo.AssertExpectations(t)
}
</code></pre>
<p><strong>Couverture minimale</strong> : 80% sur packages <code>internal/*/service.go</code></p>
<h2 id="tests-bdd-gherkin-godog">Tests BDD (Gherkin + Godog)</h2>
<p>Voir <a href="#007-tests-bdd">ADR-007</a> pour contexte complet.</p>
<pre><code class="language-gherkin"># 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 &quot;tourisme&quot;
And mon intérêt &quot;tourisme&quot; 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
</code></pre>
<p><strong>Couverture</strong> : Tous les cas d'usage du <a href="#../../README">README.md</a> traduits en <code>.feature</code>.</p>
<h2 id="tests-integration-testcontainers">Tests intégration (Testcontainers)</h2>
<pre><code class="language-go">// 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)
}
</code></pre>
<h2 id="tests-e2e-mobile-flutter">Tests E2E Mobile (Flutter)</h2>
<pre><code class="language-dart">// 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);
});
</code></pre>
<h2 id="load-testing-k6">Load testing (k6)</h2>
<pre><code class="language-javascript">// 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) =&gt; r.status === 200 });
}
</code></pre>
<p><strong>Objectif</strong> : API p99 &lt; 100ms à 10K RPS.</p>
<h2 id="cicd-pipeline">CI/CD Pipeline</h2>
<pre><code class="language-yaml"># .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 &quot;$coverage &lt; 80&quot; | bc -l) )); then
echo &quot;Coverage $coverage% &lt; 80%&quot;
exit 1
fi
</code></pre>
<h2 id="consequences_14">Conséquences</h2>
<ul>
<li>Dépendances :</li>
<li><code>github.com/stretchr/testify</code></li>
<li><code>github.com/cucumber/godog</code></li>
<li><code>github.com/testcontainers/testcontainers-go</code></li>
<li><code>grafana/k6</code></li>
<li>Temps CI : ~3-5 min (tests unitaires + BDD)</li>
<li>Tests intégration/E2E : nightly builds (15-30 min)</li>
<li>Load tests : avant chaque release majeure</li>
</ul>
<div style="page-break-after: always;"></div>
<h1 id="regles-metier-roadwave">Règles métier RoadWave</h1>
<blockquote>
<p>Documentation complète des règles métier validées pour l'application RoadWave.
Chaque section détaille les comportements, flux et décisions techniques.</p>
</blockquote>
<hr />
<h2 id="table-des-matieres_1">📋 Table des matières</h2>
<h3 id="01-authentification-inscription"><a href="#01-authentification-inscription">01. Authentification &amp; Inscription</a></h3>
<p><strong>Contenu</strong> : Inscription, connexion, récupération de compte</p>
<ul>
<li>Inscription : email/password uniquement (pas d'OAuth tiers)</li>
<li>Vérification email : optionnelle auditeurs (limite 5 contenus), obligatoire créateurs (lien expire 7j)</li>
<li>Connexion : 5 tentatives max, blocage 15 min, refresh token 30j</li>
<li>Récupération mot de passe : email, lien expire 1h</li>
</ul>
<hr />
<h3 id="02-algorithme-de-recommandation"><a href="#02-algorithme-recommandation">02. Algorithme de recommandation</a></h3>
<p><strong>Contenu</strong> : Scoring, géolocalisation, orientation politique, mode Kids</p>
<ul>
<li>Classification géo : Ancré (70%) / Contextuel (50%) / Neutre (20%)</li>
<li>Engagement : 20%, Aléatoire : 10%</li>
<li>Orientation politique : 5 niveaux, équilibre imposé (40/40/20)</li>
<li>Mode Kids : 4 tranches (3-6 / 6-9 / 9-12 / 13-15 ans), activation auto &lt;13 ans</li>
<li>Historique : &gt;80% jamais reproposer, &lt;10s ne pas reproposer</li>
</ul>
<hr />
<h3 id="03-centres-dinteret-et-jauges"><a href="#03-centres-interet-jauges">03. Centres d'intérêt et jauges</a></h3>
<p><strong>Contenu</strong> : Évolution jauges, valeurs initiales</p>
<ul>
<li>Like automatique : écoute ≥80% → +2%, écoute 30-79% → +1%</li>
<li>Like explicite (manuel) : +2% (cumulable avec auto)</li>
<li>Abonnement : +5%</li>
<li>Skip rapide (&lt;10s) : -0.5%</li>
<li>Valeur initiale : 50% (neutre)</li>
<li>Limites : 0-100% stricte, pas de dégradation temporelle</li>
</ul>
<hr />
<h3 id="04-creation-et-publication-de-contenu"><a href="#04-creation-publication-contenu">04. Création et publication de contenu</a></h3>
<p><strong>Contenu</strong> : Upload, métadonnées, validation, modification</p>
<ul>
<li>Formats : MP3, AAC (.mp3, .aac, .m4a), max 200 MB, 4h</li>
<li>Métadonnées obligatoires : titre, type géo, zone, tags (1-3), classification âge</li>
<li>Validation 3 premiers contenus : 24-48h (modération RoadWave)</li>
<li>Modification : métadonnées uniquement, pas audio/zone/classification</li>
</ul>
<hr />
<h3 id="05-interactions-et-navigation"><a href="#05-interactions-navigation">05. Interactions et navigation</a></h3>
<p><strong>Contenu</strong> : Commandes Suivant/Précédent, interactions volant, lecture en boucle</p>
<ul>
<li>Suivant : pré-calcul 5 contenus, recalcul &gt;10km ou 10 min</li>
<li>Précédent : &lt;10s → contenu avant, ≥10s → replay début</li>
<li>Commandes volant : Suivant, Précédent, Play/Pause uniquement</li>
<li>Like automatique : ≥80% écoute → +2 points, 30-79% → +1 point</li>
<li>Actions manuelles : bouton cœur (arrêt véhicule) ou vocal (CarPlay/Android Auto)</li>
<li>Passage auto après 2s (1s mode Kids)</li>
</ul>
<hr />
<h3 id="06-publicites"><a href="#06-publicites">06. Publicités</a></h3>
<p><strong>Contenu</strong> : Campagnes, fréquence, insertion, facturation</p>
<ul>
<li>Interface self-service, budget min 50€, étalement paramétrable</li>
<li>Fréquence : 1/5 contenus (gratuits uniquement)</li>
<li>Durée : 10-60s (recommandé 15-30s), skippable après 5s</li>
<li>Validation manuelle 24-48h, prépaiement Mangopay</li>
<li>Facturation : écoute complète 0.05€, skip après 5s : 0.02€, skip immédiat : 0€</li>
</ul>
<hr />
<h3 id="07-radio-live"><a href="#07-radio-live">07. Radio live</a></h3>
<p><strong>Contenu</strong> : Démarrage, arrêt, comportement auditeur</p>
<ul>
<li>Buffer 15s avant diffusion publique, durée max 8h</li>
<li>Notification push abonnés dans zone géo uniquement</li>
<li>Arrêt : compte à rebours 5s (manuel) ou auto si déco ≥60s</li>
<li>Enregistrement auto MP3 256 kbps → replay sous 5-10 min</li>
<li>Auditeur : buffer 15s, continuation si sortie zone, AUCUN chat</li>
</ul>
<hr />
<h3 id="08-abonnements-et-notifications"><a href="#08-abonnements-notifications">08. Abonnements et notifications</a></h3>
<p><strong>Contenu</strong> : Impact algorithme, notifications, audio-guides, limites</p>
<ul>
<li>Boost +30% au score final (pas priorité absolue)</li>
<li>Détection contexte : &lt;5 km/h piéton, &gt;10 km/h voiture</li>
<li>Voiture : in-app uniquement, Piéton : push actives</li>
<li>Limite 10 notifications push/jour (5-20), mode silencieux 22h-8h</li>
<li>Audio-guide piéton : détection &lt;100m lieu, page sélection, navigation manuelle</li>
<li>Max 200 abonnements, +5% jauges tous tags créateur</li>
</ul>
<hr />
<h3 id="09-monetisation-createurs"><a href="#09-monetisation-createurs">09. Monétisation créateurs</a></h3>
<p><strong>Contenu</strong> : Activation, KYC, sources revenus, paiement</p>
<ul>
<li>Conditions : compte ≥3 mois, ≥500 abonnés, ≥10K écoutes, 0 strike, ≥5 contenus/90j</li>
<li>KYC via Mangopay Connect : SIRET, TVA, RIB pro, pièce ID, Kbis &lt;3 mois</li>
<li>Revenus pub : 3€ / 1000 écoutes complètes (6% CA pub)</li>
<li>Revenus Premium : 70% créateur, 30% plateforme (proportionnel temps écoute)</li>
<li>Paiement : seuil 50€, mensuel (15 du mois suivant), SEPA Mangopay</li>
</ul>
<hr />
<h3 id="10-premium"><a href="#10-premium">10. Premium</a></h3>
<p><strong>Contenu</strong> : Offre, multi-devices, avantages, gestion abonnement</p>
<ul>
<li>Prix : 4.99€/mois OU 49.99€/an (4.16€/mois effectif)</li>
<li>Pas d'essai gratuit, pas de partage familial (MVP)</li>
<li>Multi-devices : 1 seul stream actif, détection connexion simultanée</li>
<li>Avantages : 0 pub, contenus exclusifs 👑, qualité 64 kbps Opus, offline illimité</li>
<li>Paiement : Mangopay (web) ou IAP iOS/Android 5.99€/mois (+30% commission)</li>
</ul>
<hr />
<h3 id="11-mode-offline"><a href="#11-mode-offline">11. Mode offline</a></h3>
<p><strong>Contenu</strong> : Téléchargement, validité, synchronisation</p>
<ul>
<li>Zone géographique : choix manuel (autour de moi / ville / département / région)</li>
<li>Nombre contenus : gratuit 50 max, Premium illimité</li>
<li>WiFi par défaut, mobile avec confirmation + estimation volume</li>
<li>Validité : 30 jours, renouvellement auto si WiFi (contenus &gt;25 jours)</li>
<li>Sync : likes/abonnements batch auto à reconnexion, queue actions 7j max</li>
</ul>
<hr />
<h3 id="12-gestion-des-erreurs"><a href="#12-gestion-erreurs">12. Gestion des erreurs</a></h3>
<p><strong>Contenu</strong> : Aucun contenu, contenu supprimé, perte réseau, GPS désactivé</p>
<ul>
<li>Aucun contenu : élargissement auto 50km → 100km → département → région → national</li>
<li>Contenu supprimé : laisser terminer, passage auto suivant après 2s</li>
<li>Perte réseau : buffer adaptatif (WiFi 5-120s, 4G 10-120s, 3G 30-300s), retry 5s max 6×</li>
<li>GPS désactivé : mode dégradé (contenu national + neutre + téléchargé)</li>
</ul>
<hr />
<h3 id="13-conformite-rgpd"><a href="#13-conformite-rgpd">13. Conformité RGPD</a></h3>
<p><strong>Contenu</strong> : Consentements, anonymisation, export, suppression</p>
<ul>
<li>Consentement : Tarteaucitron.js + PostgreSQL versioning</li>
<li>GPS précis : 24h puis geohash 5 (~5km²)</li>
<li>Export : JSON + HTML + audio → ZIP, génération asynchrone sous 48h, expire 7j</li>
<li>Suppression : grace period 30j, contenus créés anonymisés (créateur = "Utilisateur supprimé")</li>
<li>Analytics : Matomo self-hosted, IP anonymisées, 0 cookie tiers</li>
<li>DPO : fondateur formé CNIL (non obligatoire &lt;250 employés)</li>
</ul>
<hr />
<h3 id="14-moderation-flows-operationnels"><a href="#14-moderation-flows">14. Modération - Flows opérationnels</a></h3>
<p><strong>Contenu</strong> : Signalement, traitement, sanctions</p>
<ul>
<li>Signalement : 7 catégories (haine, sexuel, illégalité, droits auteur, spam, fake news, autre)</li>
<li>IA pré-filtre : Whisper large-v3 (transcription) + NLP open source (1-10 min)</li>
<li>SLA : Critique &lt;2h (24/7), Haute/Moyenne &lt;24h, Basse &lt;72h</li>
<li>Notification sanction : email + push + in-app (détail complet : catégorie, timestamp, transcription)</li>
<li>Appel : formulaire in-app, délai 7j max, réponse 72h garanti (standard)</li>
</ul>
<hr />
<h3 id="15-autres-comportements"><a href="#15-autres-comportements">15. Autres comportements</a></h3>
<p><strong>Contenu</strong> : Partage, profil créateur, recherche</p>
<ul>
<li>Partage : bouton partout, lien <code>roadwave.fr/share/c/[id]</code>, web player + deep link</li>
<li>Profil créateur : @pseudo, bio (300 car), stats publiques arrondies, badge vérifié ✓</li>
<li>Badge vérifié : KYC validé OU célébrité OU &gt;10K abonnés</li>
<li>Recherche : full-text PostgreSQL (français, stemming), recherche géo (Nominatim OSM)</li>
<li>Filtres : type, durée, âge, géo, tags, date, premium (combinables)</li>
<li>Affichage : liste enrichie (20/page, infinite scroll) + vue carte Leaflet</li>
</ul>
<hr />
<h3 id="16-audio-guides-multi-sequences"><a href="#16-audio-guides-multi-sequences">16. Audio-guides multi-séquences</a></h3>
<p><strong>Contenu</strong> : Modes déplacement, navigation, déclenchement GPS, publicités</p>
<ul>
<li><strong>4 modes</strong> : 🚶 Piéton (manuel) / 🚗 Voiture (GPS auto + manuel) / 🚴 Vélo / 🚌 Transport</li>
<li><strong>Mode Piéton</strong> : pause auto après chaque séquence, user clique Suivant, navigation libre</li>
<li><strong>Mode Voiture</strong> : déclenchement GPS auto (rayon 30m), boutons manuels actifs, warning sécurité &gt;10 km/h</li>
<li><strong>Affichage voiture</strong> : distance temps réel + ETA + direction (flèche) + vitesse</li>
<li><strong>Rayons</strong> : Voiture 30m, Vélo 50m, Transport 100m (configurable créateur 10-200m)</li>
<li><strong>Publicités</strong> : 1/5 séquences tous modes, auto-play, skippable 5s</li>
<li><strong>Reprise</strong> : sauvegarde auto (séquence + position exacte), popup si &lt;30j, multi-device (sync cloud)</li>
</ul>
<hr />
<h2 id="organisation">🗂️ Organisation</h2>
<p>Chaque fichier de règles métier suit la structure :</p>
<ol>
<li><strong>Décisions</strong> : choix validés avec justifications</li>
<li><strong>Comportements détaillés</strong> : flux utilisateur, cas limites</li>
<li><strong>Paramètres</strong> : valeurs exactes, seuils, durées</li>
<li><strong>Points d'attention Gherkin</strong> : éléments à tester</li>
</ol>
<hr />
<h2 id="utilisation">🚀 Utilisation</h2>
<p>Ces documents servent de <strong>référence unique</strong> pour :</p>
<ul>
<li>✅ Développement backend/frontend</li>
<li>✅ Écriture des tests Gherkin (BDD)</li>
<li>✅ Validation QA</li>
<li>✅ Documentation produit</li>
</ul>
<p><strong>Prochaine étape</strong> : Création des fichiers <code>.feature</code> Gherkin dans <code>features/</code> basés sur ces règles.</p>
<hr />
<h2 id="statistiques">📊 Statistiques</h2>
<ul>
<li><strong>16 sections</strong> validées</li>
<li><strong>~12 000 lignes</strong> de spécifications détaillées</li>
<li><strong>Coût infrastructure MVP</strong> : ~50-250€/mois (hors salaires)</li>
<li><strong>Technologies</strong> : 100% open source (sauf Mangopay paiements)</li>
</ul>
<hr />
<p><strong>Dernière mise à jour</strong> : Janvier 2026
<strong>Statut</strong> : ✅ Toutes sections validées</p>
<div style="page-break-after: always;"></div>
<h2 id="1-authentification-inscription">1. Authentification &amp; Inscription</h2>
<h3 id="11-methodes-dinscription">1.1 Méthodes d'inscription</h3>
<p><strong>Décision</strong> : Email/Password uniquement (pas d'OAuth tiers)</p>
<ul>
<li>❌ Pas de Google, Apple, Facebook OAuth (dépendance services US/Chine)</li>
<li>✅ Email + mot de passe</li>
<li>✅ 2FA (Two-Factor Authentication) disponible</li>
<li>✅ Option "Appareil de confiance" (skip 2FA pour 30 jours)</li>
</ul>
<p><strong>Justification</strong> :
- Souveraineté : pas de dépendance externe
- RGPD : données 100% contrôlées
- Coût : 0€ (Zitadel intégré)</p>
<hr />
<h3 id="12-verification-email">1.2 Vérification email</h3>
<p><strong>Décision</strong> : Différenciée selon le rôle utilisateur</p>
<h4 id="pour-les-auditeurs-ecoute-uniquement">Pour les auditeurs (écoute uniquement)</h4>
<table>
<thead>
<tr>
<th>État</th>
<th>Capacités</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Email non vérifié</strong></td>
<td>Lecture illimitée + création max 5 contenus</td>
</tr>
<tr>
<td><strong>Email vérifié</strong></td>
<td>Toutes fonctionnalités débloquées</td>
</tr>
</tbody>
</table>
<p><strong>Paramètres</strong> :
- Lien de vérification expire après <strong>7 jours</strong>
- Possibilité de renvoyer le lien (max 3 fois/jour)
- Rappel in-app après création du 3ème contenu</p>
<p><strong>Justification</strong> :
- Friction minimale à l'inscription
- Anti-spam sans bloquer l'essai du produit
- Incitation naturelle à vérifier (déblocage)</p>
<h4 id="pour-les-createurs-monetisation">Pour les créateurs (monétisation)</h4>
<p><strong>Vérification obligatoire sous 7 jours</strong> pour :
- Accès au programme de monétisation
- KYC et reversement des revenus (conformité Mangopay)
- Publication illimitée de contenus</p>
<p><strong>Justification</strong> :
- <strong>Conformité légale</strong> : KYC obligatoire pour transferts financiers
- <strong>Anti-fraude</strong> : Vérification identité réelle pour paiements
- <strong>Responsabilité</strong> : RoadWave doit pouvoir prouver identité créateurs monétisés</p>
<hr />
<h3 id="13-donnees-requises-a-linscription">1.3 Données requises à l'inscription</h3>
<p><strong>Obligatoires</strong> :
- ✅ 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)</p>
<p><strong>Optionnelles</strong> :
- ❌ Nom complet (privacy by design)
- ❌ Photo de profil (avatar par défaut généré)
- ❌ Bio (ajout ultérieur)</p>
<p><strong>Âge minimum</strong> :
- <strong>13 ans minimum</strong> (conformité réglementation réseaux sociaux EU)
- Vérification à l'inscription via date de naissance
- Blocage inscription si &lt;13 ans avec message explicite</p>
<p><strong>Justification</strong> :
- RGPD minimal data
- Friction réduite (4 champs max)
- Protection mineurs (obligation légale)</p>
<hr />
<h3 id="14-tranches-dage-des-contenus">1.4 Tranches d'âge des contenus</h3>
<p><strong>Décision</strong> : Classification obligatoire des contenus</p>
<p><strong>Catégories</strong> :
- 🟢 <strong>Tout public</strong> (défaut)
- 🟡 <strong>13+</strong> : contenu mature léger (débats, actualité sensible)
- 🟠 <strong>16+</strong> : contenu mature (violence verbale, sujets sensibles)
- 🔴 <strong>18+</strong> : contenu adulte (langage explicite, sujets réservés)</p>
<p><strong>Règles de diffusion</strong> :
- Utilisateur 13-15 ans → contenus 🟢 uniquement
- Utilisateur 16-17 ans → contenus 🟢 🟡
- Utilisateur 18+ → tous contenus</p>
<p><strong>Modération</strong> :
- Vérification obligatoire de la classification lors de la validation
- Reclassification possible par modérateurs
- Strike si classification volontairement incorrecte</p>
<p><strong>Justification</strong> :
- Protection mineurs (obligation légale)
- Responsabilité plateforme
- Coût : champ supplémentaire + règle algo</p>
<hr />
<h3 id="15-validation-mot-de-passe">1.5 Validation mot de passe</h3>
<p><strong>Règles</strong> :
- ✅ Minimum <strong>8 caractères</strong>
- ✅ Au moins <strong>1 majuscule</strong>
- ✅ Au moins <strong>1 chiffre</strong>
- ❌ Pas de symbole obligatoire (simplicité)</p>
<p><strong>Validation</strong> :
- Côté client (feedback temps réel)
- Côté backend (sécurité)
- Message d'erreur explicite par règle non respectée</p>
<p><strong>Justification</strong> :
- Standard industrie
- Bloque 95% des mots de passe faibles
- UX acceptable (pas trop restrictif)</p>
<hr />
<h3 id="16-two-factor-authentication-2fa">1.6 Two-Factor Authentication (2FA)</h3>
<p><strong>Décision</strong> : Optionnel mais recommandé</p>
<p><strong>Méthodes disponibles</strong> :
- ✅ 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)</p>
<p><strong>Appareil de confiance</strong> :
- Option "Ne plus demander sur cet appareil" → bypass 2FA pendant <strong>30 jours</strong>
- Révocable depuis paramètres compte
- Liste des appareils de confiance visible</p>
<p><strong>Justification</strong> :
- Sécurité renforcée sans coût SMS
- UX : appareil de confiance évite friction quotidienne
- Zitadel natif (0€)</p>
<hr />
<h3 id="17-tentatives-de-connexion">1.7 Tentatives de connexion</h3>
<p><strong>Règles</strong> :
- Maximum <strong>5 tentatives</strong> par période de <strong>15 minutes</strong>
- Blocage temporaire après 5 échecs
- Compteur reset automatique après 15 min
- Notification email si blocage (tentative suspecte)</p>
<p><strong>Déblocage</strong> :
- Automatique après 15 min
- Ou via lien "Mot de passe oublié"</p>
<p><strong>Justification</strong> :
- Anti brute-force
- Standard industrie (équilibre sécurité/UX)
- Zitadel natif (0€)</p>
<hr />
<h3 id="18-sessions-et-refresh-tokens">1.8 Sessions et refresh tokens</h3>
<p><strong>Durée de vie</strong> :
- <strong>Access token</strong> : 15 minutes
- <strong>Refresh token</strong> : 30 jours</p>
<p><strong>Rotation</strong> :
- Refresh token rotatif (nouveau token à chaque refresh)
- Ancien token invalidé immédiatement
- Détection token replay attack</p>
<p><strong>Extension automatique</strong> :
- Si app utilisée, session prolongée automatiquement
- Inactivité 30 jours → déconnexion</p>
<p><strong>Justification</strong> :
- Sécurité (token court-vie)
- UX (pas de reconnexion fréquente)
- Standard OAuth2/OIDC</p>
<hr />
<h3 id="19-multi-device">1.9 Multi-device</h3>
<p><strong>Décision</strong> : Sessions simultanées illimitées</p>
<p><strong>Gestion</strong> :
- 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"</p>
<p><strong>Alertes</strong> :
- Notification push + email si connexion depuis nouveau device
- Détection localisation suspecte (IP pays différent)</p>
<p><strong>Justification</strong> :
- UX maximale (écoute voiture + tablette maison + web)
- Sécurité via transparence (utilisateur voit tout)
- Coût : table sessions PostgreSQL</p>
<hr />
<h3 id="110-recuperation-de-compte">1.10 Récupération de compte</h3>
<p><strong>Méthode</strong> : Email uniquement</p>
<p><strong>Processus</strong> :
1. Utilisateur clique "Mot de passe oublié"
2. Email avec lien de reset envoyé
3. Lien expire après <strong>1 heure</strong>
4. Page de reset : nouveau mot de passe (validation règles)
5. Confirmation + déconnexion tous devices (sauf celui en cours)</p>
<p><strong>Notifications</strong> :
- Email immédiat si changement mot de passe
- Push si changement depuis appareil non reconnu</p>
<p><strong>Limite</strong> :
- Maximum <strong>3 demandes/heure</strong> (anti-spam)</p>
<p><strong>Justification</strong> :
- Standard sécurité
- Pas de coût SMS
- Protection contre attaque sociale</p>
<hr />
<h2 id="recapitulatif-section-1">Récapitulatif Section 1</h2>
<div style="page-break-after: always;"></div>
<h2 id="2-algorithme-de-recommandation">2. Algorithme de recommandation</h2>
<h3 id="21-classification-de-geo-pertinence">2.1 Classification de géo-pertinence</h3>
<p><strong>Décision</strong> : 3 types de contenus selon leur pertinence géographique</p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Description</th>
<th>Exemple</th>
<th>Pondération géo</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Géo-ancré</strong></td>
<td>Contenu lié à un lieu précis</td>
<td>Audio-guide monument, pub restaurant local</td>
<td>70%</td>
</tr>
<tr>
<td><strong>Géo-contextuel</strong></td>
<td>Pertinent dans une zone</td>
<td>Actualité régionale, événement local</td>
<td>50%</td>
</tr>
<tr>
<td><strong>Géo-neutre</strong></td>
<td>Universel, pas de lien géo</td>
<td>Podcast philosophie, musique</td>
<td>20%</td>
</tr>
</tbody>
</table>
<p><strong>Qui décide</strong> :
- ✅ 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)</p>
<p><strong>Justification</strong> :
- 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</p>
<hr />
<h3 id="22-formule-de-scoring">2.2 Formule de scoring</h3>
<p><strong>Décision</strong> : Score combiné dynamique selon type de contenu</p>
<pre><code>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
</code></pre>
<p><strong>Pondérations par type</strong> :</p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Poids géo</th>
<th>Poids intérêts</th>
</tr>
</thead>
<tbody>
<tr>
<td>Géo-ancré</td>
<td>0.7</td>
<td>0.1</td>
</tr>
<tr>
<td>Géo-contextuel</td>
<td>0.5</td>
<td>0.3</td>
</tr>
<tr>
<td>Géo-neutre</td>
<td>0.2</td>
<td>0.6</td>
</tr>
</tbody>
</table>
<p><strong>Paramètres</strong> :
- Distance max recommandée : <strong>200 km</strong>
- Dégradation : <strong>linéaire</strong> (1 - distance/200km)
- Rayon point GPS : <strong>500m</strong> (adapté au volume de contenu local)</p>
<p><strong>Tous ces paramètres sont configurables à chaud via interface admin.</strong></p>
<p><strong>Justification</strong> :
- Flexibilité totale selon type de contenu
- Linéaire = rattrapage naturel du contenu viral ancien
- Auditable via métriques engagement (moyenne/médiane)</p>
<hr />
<h3 id="23-score-dengagement-et-popularite">2.3 Score d'engagement et popularité</h3>
<p><strong>Décision</strong> : Intégration popularité avec poids 0.2</p>
<p><strong>Métriques</strong> :
- <strong>Taux de complétion</strong> : écoutes &gt;80% / total écoutes (poids 0.5)
- <strong>Ratio likes</strong> : likes / écoutes (poids 0.3)
- <strong>Ratio abonnements</strong> : nouveaux abonnés après écoute / écoutes (poids 0.2)</p>
<p><strong>Seuil minimum</strong> :
- Minimum <strong>50 écoutes</strong> avant de considérer l'engagement
- Contenu &lt;50 écoutes : score engagement = 0.5 (neutre)</p>
<p><strong>Contenu viral</strong> :
- Un contenu viral à Paris <strong>peut</strong> être proposé à Marseille
- Score géo faible compensé par score engagement élevé
- Paramétrable admin</p>
<p><strong>Dépréciation temporelle</strong> :
- Pas de dépréciation automatique
- Ratio linéaire = contenu ancien mais toujours apprécié reste pertinent</p>
<p><strong>Justification</strong> :
- Équilibre découverte / qualité
- Pas de pénalisation arbitraire des contenus anciens
- Coût : calculs sur métriques existantes</p>
<hr />
<h3 id="24-part-daleatoire-exploration">2.4 Part d'aléatoire (exploration)</h3>
<p><strong>Décision</strong> : 10% par défaut, paramétrable utilisateur</p>
<p><strong>Fonctionnement</strong> :
- 1 contenu sur 10 = tirage aléatoire (hors historique déjà écouté)
- Utilisateur peut ajuster : curseur 0% (aucun aléatoire) à 50% (exploration max)</p>
<p><strong>Curseur utilisateur</strong> :
- 🎯 <strong>0%</strong> : Personnalisé max (recommandations strictes)
- ⚖️ <strong>10%</strong> : Équilibré (défaut)
- 🎲 <strong>30%</strong> : Découverte élevée
- 🌍 <strong>50%</strong> : Découverte max (équivaut à national = découverte)</p>
<p><strong>Justification</strong> :
- Évite la bulle de filtre
- Laisse l'utilisateur maître de son expérience
- Coût : variable aléatoire en algo</p>
<hr />
<h3 id="25-contenu-politique-version-mvp-simplifiee">2.5 Contenu politique (version MVP simplifiée)</h3>
<blockquote>
<p>⚠️ <strong>Note</strong> : La classification politique avancée (échelle gauche/droite, équilibrage imposé) a été reportée post-MVP. Voir <a href="#ANNEXE-POST-MVP">ANNEXE-POST-MVP.md</a> pour la version complète.</p>
</blockquote>
<p><strong>Décision MVP</strong> : Tag simple "Politique" sans classification idéologique</p>
<p><strong>Tagging</strong> :
- Créateur peut taguer son contenu comme "Politique" (optionnel)
- Tag "Politique" au même niveau que "Économie", "Sport", "Culture", etc.
- <strong>Pas de classification gauche/droite</strong>
- <strong>Pas d'équilibrage imposé</strong></p>
<p><strong>Filtrage utilisateur</strong> :
- Option paramètres : <strong>"Masquer contenu politique"</strong>
- Si activé → 0% de contenus tagués "Politique" dans le feed
- Par défaut : désactivé (tous contenus visibles)</p>
<p><strong>Justification MVP</strong> :
- <strong>Simplicité</strong> : Pas de modération politique coûteuse (~2000€/mois économisés)
- <strong>Neutralité technique</strong> : Aucun jugement éditorial sur orientation
- <strong>Risque minimal</strong> : Évite controverses et contentieux DSA au lancement
- <strong>Fonctionnel</strong> : Utilisateurs peuvent filtrer si souhaité</p>
<p><strong>Post-MVP</strong> :
- Classification avancée possible si forte demande utilisateurs
- Nécessite ressources modération dédiées et audit DSA</p>
<hr />
<h3 id="26-mode-kids-13-15-ans">2.6 Mode Kids (13-15 ans)</h3>
<p><strong>Décision</strong> : Mode optionnel pour adolescents 13-15 ans uniquement</p>
<blockquote>
<p>⚠️ <strong>Note</strong> : Âge minimum d'inscription = <strong>13 ans</strong> (obligation légale EU). Pas d'utilisateurs &lt;13 ans sur la plateforme.</p>
</blockquote>
<p><strong>Tranche concernée</strong> :</p>
<table>
<thead>
<tr>
<th>Tranche</th>
<th>Description</th>
<th>Contenus autorisés</th>
<th>Restrictions</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>13-15 ans</strong></td>
<td>Collège</td>
<td>Contenus "Tous publics" uniquement</td>
<td>Filtrage 16+ et 18+</td>
</tr>
</tbody>
</table>
<p><strong>Activation</strong> :
- ❌ <strong>Pas d'activation automatique</strong> (tous les utilisateurs ont ≥13 ans)
- ✅ <strong>Activation manuelle</strong> via toggle paramètres
- ✅ Parents peuvent activer pour leurs enfants 13-15 ans
- ✅ Utilisateur peut désactiver à tout moment</p>
<p><strong>Filtrage quand Mode Kids activé</strong> :
- ✅ Contenus "Tous publics" uniquement
- ❌ Exclusion contenus 16+ et 18+
- ❌ Pas de contenu politique (automatiquement filtré)
- ❌ Pas de publicité (ou uniquement pub validée manuellement)</p>
<p><strong>Interface</strong> :
- Interface standard (pas d'interface dédiée enfants pour MVP)
- Filtrage algorithmique des contenus inappropriés</p>
<p><strong>Justification</strong> :
- <strong>Conformité légale</strong> : Âge minimum 13 ans (RGPD, DSA)
- <strong>Simplicité MVP</strong> : Un seul mode optionnel vs 4 tranches d'âge
- <strong>Protection mineurs</strong> : Filtrage contenus adultes pour 13-15 ans
- <strong>Flexibilité</strong> : Parents décident d'activer ou non</p>
<hr />
<h3 id="27-declenchement-geographique">2.7 Déclenchement géographique</h3>
<p><strong>Décision</strong> : Notification au passage, pas d'anticipation</p>
<p><strong>Fonctionnement</strong> :
1. Utilisateur passe à &lt;500m d'un point GPS (contenu géo-ancré)
2. <strong>Notification sonore</strong> (bip court) + <strong>visuelle</strong> (logo selon type)
3. Types de logos : 📍 Info, 🏛️ Culturel, 🍴 Commercial, 🎭 Événement
4. Délai réaction utilisateur : <strong>5 secondes</strong> pour accepter (bouton volant ou commande vocale)
5. Si accepté → lecture immédiate
6. Si ignoré → contenu proposé normalement en file d'attente</p>
<p><strong>Publicités</strong> :
- ⚠️ <strong>Jamais d'interruption</strong> de contenu en cours
- Pub s'intercale <strong>entre deux séquences</strong> uniquement
- Notification pub : son différent (facultatif selon paramètres)</p>
<p><strong>Gestion demi-tour</strong> :
- Si utilisateur repart du point après notification → pas de nouvelle notification (déjà proposé)
- Réinitialisation après 24h</p>
<p><strong>Justification</strong> :
- Respect écoute en cours (pas de coupure brutale)
- UX fluide (utilisateur garde contrôle)
- Simplicité technique (pas de prédiction trajectoire)</p>
<hr />
<h3 id="28-historique-et-repropositon">2.8 Historique et repropositon</h3>
<p><strong>Décision</strong> : Pas de reproposition sauf contenu partiel</p>
<p><strong>Règles</strong> :</p>
<table>
<thead>
<tr>
<th>État écoute</th>
<th>Completion</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Écouté complètement</strong></td>
<td>&gt;80%</td>
<td>❌ Ne jamais reproposer (sauf flag <code>replayable = true</code> pour audio-guides)</td>
</tr>
<tr>
<td><strong>Skippé rapidement</strong></td>
<td>&lt;10s</td>
<td>❌ Ne pas reproposer</td>
</tr>
<tr>
<td><strong>Partiellement écouté</strong></td>
<td>10-80%</td>
<td>✅ Reproposer avec reprise position (<code>last_position_seconds</code>)</td>
</tr>
</tbody>
</table>
<p><strong>Stockage historique</strong> :
- Table <code>user_content_history</code> (user_id, content_id, completion_rate, last_position, listened_at)
- Historique <strong>illimité</strong> (PostgreSQL)
- Algorithme considère les <strong>100 derniers</strong> pour optimisation requêtes
- Export complet disponible (RGPD)</p>
<p><strong>Justification</strong> :
- Découverte maximale (pas de redites)
- Respect erreurs de clic (contenu partiel = 2nde chance)
- Coût stockage négligeable (PostgreSQL scalable)</p>
<hr />
<h3 id="29-parametrabilite-admin-interface-dashboard">2.9 Paramétrabilité admin (interface dashboard)</h3>
<p><strong>Décision</strong> : Tous paramètres scoring exposés + A/B testing</p>
<p><strong>Paramètres configurables à chaud</strong> :</p>
<table>
<thead>
<tr>
<th>Paramètre</th>
<th>Plage</th>
<th>Défaut</th>
<th>Unité</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>poids_geo_ancre</code></td>
<td>0.5 - 1.0</td>
<td>0.7</td>
<td>%</td>
</tr>
<tr>
<td><code>poids_geo_contextuel</code></td>
<td>0.3 - 0.7</td>
<td>0.5</td>
<td>%</td>
</tr>
<tr>
<td><code>poids_geo_neutre</code></td>
<td>0.0 - 0.4</td>
<td>0.2</td>
<td>%</td>
</tr>
<tr>
<td><code>poids_engagement</code></td>
<td>0.0 - 0.5</td>
<td>0.2</td>
<td>%</td>
</tr>
<tr>
<td><code>part_aleatoire_global</code></td>
<td>0.0 - 0.3</td>
<td>0.1</td>
<td>%</td>
</tr>
<tr>
<td><code>distance_max_km</code></td>
<td>50 - 500</td>
<td>200</td>
<td>km</td>
</tr>
<tr>
<td><code>rayon_gps_point_m</code></td>
<td>100 - 2000</td>
<td>500</td>
<td>m</td>
</tr>
<tr>
<td><code>seuil_min_ecoutes_engagement</code></td>
<td>10 - 200</td>
<td>50</td>
<td>nb</td>
</tr>
</tbody>
</table>
<p><strong>Application changements</strong> :
- Immédiat : nouveaux calculs utilisent nouvelle config
- Aucun recalcul batch (coût CPU)
- Version config trackée (git-like)
- Rollback 1 clic</p>
<p><strong>A/B Testing</strong> :
- 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</p>
<p><strong>Audit engagement</strong> :
- Métriques clés : moyenne/médiane temps d'écoute par session
- Graphiques : évolution engagement selon config
- Export CSV pour analyse externe</p>
<p><strong>Justification</strong> :
- Optimisation continue sans redéploiement
- Data-driven decisions (métriques objectives)
- Coût : dashboard admin à développer (one-time)</p>
<hr />
<h3 id="210-parametrabilite-utilisateur">2.10 Paramétrabilité utilisateur</h3>
<p><strong>Décision</strong> : Curseurs avancés avec profils sauvegardables</p>
<p><strong>Niveaux de personnalisation</strong> :</p>
<p><strong>Curseurs disponibles</strong> :
- 📍 <strong>Géolocalisation</strong> : Local ← slider → National (découverte = national)
- 🎲 <strong>Découverte</strong> : 0% ← slider → 50% (part aléatoire)
- ⚖️ <strong>Politique</strong> : Masquer / Équilibré / Mes préférences</p>
<p><strong>Profils sauvegardables</strong> :
- 🚗 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é</p>
<p><strong>Synchronisation</strong> :
- ✅ Sync profils entre devices (cloud PostgreSQL)
- ❌ Pas de partage profils entre utilisateurs (famille)
- Auto-switch selon context (détection trajet récurrent via GPS)</p>
<p><strong>Sécurité conduite</strong> :
- ⚠️ <strong>Blocage modification si vitesse GPS &gt;10 km/h</strong>
- Warning au lancement app : "Configurez avant de prendre la route"
- Modifications uniquement app arrêtée/passager</p>
<p><strong>Justification</strong> :
- Utilisateur maître de son expérience
- Contextes d'usage différents (quotidien vs voyage)
- Sécurité routière (pas de distraction)</p>
<hr />
<h3 id="211-medias-traditionnels">2.11 Médias traditionnels</h3>
<p><strong>Décision</strong> : Ouverture aux médias établis</p>
<p><strong>Médias autorisés</strong> :
- 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.</p>
<p><strong>Format contenus</strong> :
- Flashs info géolocalisés (actualité régionale)
- Chroniques thématiques (culture, économie, sport)
- Éditos et débats (classification politique appliquée)</p>
<p><strong>Validation</strong> :
- Compte média vérifié (badge ✓)
- Pas de validation 3 premiers contenus (confiance établie)
- Modération a posteriori uniquement</p>
<p><strong>Monétisation</strong> :
- Partage revenus pub standard (même conditions créateurs)
- Possibilité sponsoring direct (pas via plateforme)</p>
<p><strong>Justification</strong> :
- Crédibilité plateforme (contenus professionnels)
- Diversité éditoriale
- Attractivité grand public (noms reconnus)</p>
<hr />
<h2 id="recapitulatif-section-2">Récapitulatif Section 2</h2>
<div style="page-break-after: always;"></div>
<h2 id="3-centres-dinteret-et-jauges">3. Centres d'intérêt et jauges</h2>
<h3 id="31-evolution-des-jauges">3.1 Évolution des jauges</h3>
<p><strong>Décision</strong> : Système simple avec valeurs fixes</p>
<table>
<thead>
<tr>
<th>Action</th>
<th>Impact jauge</th>
<th>Justification</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Like automatique renforcé (≥80% écoute)</strong></td>
<td>+2%</td>
<td>Signal fort d'intérêt (écoute quasi-complète)</td>
</tr>
<tr>
<td><strong>Like automatique standard (30-79% écoute)</strong></td>
<td>+1%</td>
<td>Signal modéré d'intérêt</td>
</tr>
<tr>
<td><strong>Like explicite (manuel)</strong></td>
<td>+2%</td>
<td>Signal fort, cumulable avec auto</td>
</tr>
<tr>
<td><strong>Abonnement créateur</strong></td>
<td>+5% sur tous ses tags</td>
<td>Signal très fort d'affinité</td>
</tr>
<tr>
<td><strong>Skip rapide (&lt;10s)</strong></td>
<td>-0.5%</td>
<td>Désintérêt marqué</td>
</tr>
<tr>
<td><strong>Skip tardif (≥30%)</strong></td>
<td>0%</td>
<td>Neutre (contenu essayé suffisamment)</td>
</tr>
</tbody>
</table>
<p><strong>Paramètres techniques</strong> :
- Les jauges sont bornées strictement entre <strong>0% et 100%</strong>
- 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</p>
<p><strong>Exemple de calcul</strong> :</p>
<pre><code>Contenu de 5 minutes tagué &quot;Automobile&quot; + &quot;Voyage&quot;
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%
</code></pre>
<p><strong>Justification</strong> :
- <strong>Like automatique</strong> : Reflète l'engagement réel (voir <a href="#../adr/010-commandes-volant">ADR-010</a>)
- <strong>Sécurité routière</strong> : Pas d'action complexe en conduite
- <strong>Prévisibilité</strong> : Règles claires et déterministes
- <strong>Coût minimal</strong> : Calculs simples en backend
- <strong>Fiabilité</strong> : Pas d'edge cases complexes
- <strong>Ajustable</strong> : Valeurs modifiables via dashboard admin si besoin</p>
<hr />
<h3 id="32-jauge-initiale">3.2 Jauge initiale</h3>
<p><strong>Décision</strong> : Démarrage neutre à 50%, pas de questionnaire</p>
<p><strong>À l'inscription</strong> :
- Toutes les jauges d'intérêt sont initialisées à <strong>50%</strong>
- Pas de questionnaire onboarding (friction zéro)
- L'algorithme apprend naturellement via les premières écoutes</p>
<p><strong>Catégories disponibles</strong> :
- Automobile
- Voyage
- Famille
- Amour
- Musique
- Économie
- Cryptomonnaie
- Politique
- Culture générale
- Sport
- Technologie
- Santé
- <em>... (extensible)</em></p>
<p><strong>Cold start (premiers jours)</strong> :
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</p>
<p><strong>Alternative optionnelle (post-MVP)</strong> :
- Questionnaire <strong>optionnel</strong> 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</p>
<p><strong>Justification</strong> :
- <strong>Inscription ultra-rapide</strong> : pas de questionnaire = moins de churn
- <strong>Découverte naturelle</strong> : l'algorithme apprend en quelques écoutes
- <strong>Équitable</strong> : pas de biais initial vers certains créateurs
- <strong>Comportement déterministe</strong> : facile à tester et débugger
- <strong>Cold start acceptable</strong> : à 50%, tous les contenus ont une chance égale initialement</p>
<hr />
<h3 id="33-degradation-temporelle">3.3 Dégradation temporelle</h3>
<p><strong>Décision</strong> : Pas de dégradation automatique</p>
<p>Les jauges <strong>ne diminuent jamais</strong> avec le temps de manière automatique.</p>
<p><strong>Règle</strong> :
- Une jauge ne change <strong>que par les actions utilisateur</strong> (like, écoute, skip)
- Pas de cron job de dégradation périodique
- Pas de "rafraîchissement" artificiel</p>
<p><strong>Scénario illustratif</strong> :</p>
<pre><code>Utilisateur aimait &quot;Économie&quot; (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
</code></pre>
<p><strong>Si utilisateur inactif longtemps</strong> :
- 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</p>
<p><strong>Alternative utilisateur (contrôle explicite)</strong> :
- 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.)</p>
<p><strong>Justification</strong> :
- <strong>Principe KISS</strong> (Keep It Simple, Stupid)
- <strong>Coût 0</strong> : pas de batch nocturne, pas de calculs temporels
- <strong>Fiabilité maximale</strong> : pas de bugs de fuseaux horaires, dates, etc.
- <strong>UX prévisible</strong> : jauge = reflet des actions, pas d'automatisme caché
- <strong>Respect historique</strong> : si utilisateur aimait X depuis 2 ans, pourquoi "oublier" ?
- <strong>Évolution naturelle</strong> : les actions récentes suffisent à faire évoluer les jauges</p>
<hr />
<h2 id="recapitulatif-section-3">Récapitulatif Section 3</h2>
<div style="page-break-after: always;"></div>
<h2 id="4-creation-et-publication-de-contenu">4. Création et publication de contenu</h2>
<h3 id="41-upload-et-encodage">4.1 Upload et encodage</h3>
<p><strong>Décision</strong> : Formats universels avec encodage asynchrone</p>
<p><strong>Formats acceptés</strong> :
- ✅ MP3 (<code>.mp3</code>)
- ✅ AAC (<code>.aac</code>, <code>.m4a</code>)
- ❌ WAV, FLAC (trop lourds, inutiles en voiture)</p>
<p><strong>Limites</strong> :</p>
<table>
<thead>
<tr>
<th>Paramètre</th>
<th>Valeur</th>
<th>Justification</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Taille maximale</strong></td>
<td>200 MB</td>
<td>~4h de podcast à 128 kbps</td>
</tr>
<tr>
<td><strong>Durée maximale</strong></td>
<td>4 heures</td>
<td>Suffisant pour podcasts longs</td>
</tr>
<tr>
<td><strong>Validation format</strong></td>
<td>Client + backend</td>
<td>Double sécurité</td>
</tr>
</tbody>
</table>
<p><strong>Pipeline d'encodage</strong> :</p>
<pre><code>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 : &quot;Contenu prêt à publier&quot;
</code></pre>
<p><strong>Temps d'encodage estimé</strong> :
- Contenu 5 min → ~30 secondes
- Podcast 1h → ~5 minutes
- Podcast 4h → ~20 minutes</p>
<p><strong>Profils Opus générés</strong> :</p>
<table>
<thead>
<tr>
<th>Qualité</th>
<th>Bitrate</th>
<th>Usage</th>
</tr>
</thead>
<tbody>
<tr>
<td>Basse</td>
<td>24 kbps</td>
<td>2G/Edge</td>
</tr>
<tr>
<td>Standard</td>
<td>48 kbps</td>
<td>3G (défaut)</td>
</tr>
<tr>
<td>Haute</td>
<td>64 kbps</td>
<td>4G/5G</td>
</tr>
</tbody>
</table>
<p><strong>Écoute accélérée</strong> :</p>
<table>
<thead>
<tr>
<th>Vitesse</th>
<th>Usage</th>
</tr>
</thead>
<tbody>
<tr>
<td>0.75x</td>
<td>Compréhension difficile (accent, technique)</td>
</tr>
<tr>
<td>1.0x</td>
<td>Normal (défaut)</td>
</tr>
<tr>
<td>1.25x</td>
<td>Gain léger</td>
</tr>
<tr>
<td>1.5x</td>
<td>Podcasts longs</td>
</tr>
<tr>
<td>2.0x</td>
<td>Survol rapide (modérateurs)</td>
</tr>
</tbody>
</table>
<p><strong>Disponible pour</strong> :
- ✅ Modérateurs (validation rapide : 30s → 15s à 2x)
- ✅ Auditeurs (tous les contenus)
- ✅ Standard industrie (YouTube, Spotify, Apple Podcasts)</p>
<p><strong>Justification</strong> :
- <strong>Simplicité</strong> : 2 formats couvrent 95% des cas d'usage
- <strong>Coût optimisé</strong> : pas de conversion WAV/FLAC lourds
- <strong>Stockage réduit</strong> : suppression original après encodage
- <strong>Scalabilité</strong> : workers horizontalement (Kubernetes jobs)
- <strong>Productivité</strong> : écoute accélérée = double productivité modération</p>
<hr />
<h3 id="42-metadonnees-obligatoires">4.2 Métadonnées obligatoires</h3>
<p><strong>Décision</strong> : Minimaliste pour réduire friction</p>
<p><strong>Champs obligatoires</strong> :</p>
<table>
<thead>
<tr>
<th>Champ</th>
<th>Format</th>
<th>Validation</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Titre</strong></td>
<td>5-100 caractères</td>
<td>Alphanumérique + ponctuation basique</td>
</tr>
<tr>
<td><strong>Type géo</strong></td>
<td>Enum</td>
<td>Ancré / Contextuel / Neutre</td>
</tr>
<tr>
<td><strong>Zone diffusion</strong></td>
<td>Composite</td>
<td>Voir détails ci-dessous</td>
</tr>
<tr>
<td><strong>Tags</strong></td>
<td>Enum</td>
<td>1 à 3 parmi liste prédéfinie</td>
</tr>
<tr>
<td><strong>Classification âge</strong></td>
<td>Enum</td>
<td>Tout public / 13+ / 16+ / 18+</td>
</tr>
</tbody>
</table>
<p><strong>Zone de diffusion (obligatoire)</strong> :</p>
<p>Options mutuellement exclusives :
- <strong>Point GPS</strong> : latitude + longitude + rayon (100m à 10km)
- <strong>Ville</strong> : sélection dans référentiel INSEE
- <strong>Département</strong> : sélection liste
- <strong>Région</strong> : sélection liste
- <strong>National</strong> : France entière</p>
<p><strong>Tags disponibles</strong> (1 à 3 obligatoires) :
- Automobile
- Voyage
- Famille
- Amour
- Musique
- Économie
- Cryptomonnaie
- Politique
- Culture générale
- Sport
- Technologie
- Santé</p>
<p><strong>Champs optionnels</strong> :
- ❌ Description (ajout ultérieur)
- ❌ Image couverture (génération auto)</p>
<p><strong>Image de couverture par défaut</strong> :</p>
<p>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)</p>
<p><strong>Exemple de publication</strong> :</p>
<pre><code>Titre : &quot;Histoire de la Tour Eiffel&quot;
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)
</code></pre>
<p><strong>Justification</strong> :
- <strong>Friction minimale</strong> : 5 champs max = 2 min de publication
- <strong>Publication rapide</strong> : pas de blocage sur description/image
- <strong>Coût 0</strong> : pas de génération IA au MVP
- <strong>Évolutif</strong> : champs optionnels ajoutables ultérieurement</p>
<hr />
<h3 id="43-validation-des-3-premiers-contenus">4.3 Validation des 3 premiers contenus</h3>
<p><strong>Décision</strong> : Validation manuelle par équipe modération RoadWave</p>
<p><strong>Processus nouveau créateur</strong> :</p>
<ol>
<li>Créateur upload ses 3 premiers contenus</li>
<li>Contenus passent en <strong>file d'attente modération</strong></li>
<li>Modérateur junior RoadWave :</li>
<li>Écoute 30 secondes (ou 15s à 2x)</li>
<li>Vérifie métadonnées</li>
<li>Valide ou rejette avec raison</li>
<li>Si accepté : contenu publié + notification créateur</li>
<li>Si refusé : notification avec raison détaillée + lien vers règles</li>
<li>Après 3 contenus validés : créateur passe en <strong>statut vérifié</strong></li>
</ol>
<p><strong>Critères de validation</strong> :</p>
<table>
<thead>
<tr>
<th>Critère</th>
<th>Détails</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Qualité audio</strong></td>
<td>Compréhensible (pas de grésillement excessif)</td>
</tr>
<tr>
<td><strong>Respect règles</strong></td>
<td>Pas de contenu prohibé évident (haine, spam, illégal)</td>
</tr>
<tr>
<td><strong>Classification âge</strong></td>
<td>Cohérente avec contenu écouté</td>
</tr>
<tr>
<td><strong>Tags pertinents</strong></td>
<td>Correspondance minimale avec contenu</td>
</tr>
<tr>
<td><strong>Zone diffusion</strong></td>
<td>Cohérente (pas "Tour Eiffel" avec zone "National")</td>
</tr>
</tbody>
</table>
<p><strong>Délai de validation</strong> :
- Objectif : <strong>24-48h</strong> (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"</p>
<p><strong>Notification créateur</strong> :</p>
<p><strong>Si accepté</strong> :
- 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é"</p>
<p><strong>Si refusé</strong> :
- 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</p>
<p><strong>Après 3 validations</strong> :</p>
<p>Créateur obtient <strong>statut "Vérifié"</strong> :
- Badge ✓ visible sur profil
- Contenus futurs publiés <strong>immédiatement</strong> (modération a posteriori uniquement)
- Modération seulement si signalé par utilisateurs</p>
<p><strong>Outils modérateur</strong> :
- É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)</p>
<p><strong>Modération communautaire (post-MVP)</strong> :</p>
<p>⚠️ <strong>Non implémenté au MVP</strong> (complexité juridique)</p>
<p>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)</p>
<p><strong>Justification décision MVP</strong> :
- <strong>Responsabilité juridique</strong> : plateforme reste responsable (DSA EU)
- <strong>Qualité garantie</strong> : modérateurs formés et mandatés
- <strong>Anti-spam efficace</strong> : bloque 95% des abus dès le début
- <strong>Coût raisonnable</strong> : 30s × 3 contenus = 1.5 min/créateur
- <strong>UX acceptable</strong> : délai 24-48h expliqué clairement
- <strong>Pas de validation par pairs</strong> au MVP = évite risques juridiques (collusion, compétence, conflits)</p>
<hr />
<h3 id="44-modification-et-suppression">4.4 Modification et suppression</h3>
<p><strong>Décision</strong> : Modification métadonnées uniquement, suppression immédiate</p>
<p><strong>Modification autorisée</strong> :</p>
<table>
<thead>
<tr>
<th>Élément</th>
<th>Modifiable</th>
<th>Justification</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Titre</strong></td>
<td>✅</td>
<td>Correction coquilles</td>
</tr>
<tr>
<td><strong>Description</strong></td>
<td>✅</td>
<td>Si ajoutée ultérieurement</td>
</tr>
<tr>
<td><strong>Tags</strong></td>
<td>✅</td>
<td>Ajustement pertinence</td>
</tr>
<tr>
<td><strong>Image couverture</strong></td>
<td>✅</td>
<td>Personnalisation</td>
</tr>
<tr>
<td><strong>Audio</strong></td>
<td>❌</td>
<td>Intégrité contenu</td>
</tr>
<tr>
<td><strong>Zone diffusion</strong></td>
<td>❌</td>
<td>Évite manipulation algo</td>
</tr>
<tr>
<td><strong>Type géo</strong></td>
<td>❌</td>
<td>Évite manipulation algo</td>
</tr>
<tr>
<td><strong>Classification âge</strong></td>
<td>❌</td>
<td>Sécurité mineurs</td>
</tr>
</tbody>
</table>
<p><strong>Raisons restrictions</strong> :</p>
<p><strong>Audio non modifiable</strong> :
- Évite fraude : uploader contenu validé → remplacer par spam
- Intégrité : auditeurs doivent écouter ce qui a été validé</p>
<p><strong>Zone/Type non modifiables</strong> :
- É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)</p>
<p><strong>Classification non modifiable</strong> :
- Évite contournement : uploader "Tout public" → passer en "18+" sans revalidation
- Sécurité : garantit que classification a été vérifiée</p>
<p><strong>Si besoin de changer audio/zone/classification</strong> :
- Action : <strong>Supprimer contenu + republier</strong>
- Si créateur &lt;3 contenus validés : retourne en file validation
- Si créateur ≥3 contenus validés : publication immédiate</p>
<p><strong>Suppression de contenu</strong> :</p>
<table>
<thead>
<tr>
<th>Aspect</th>
<th>Comportement</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Délai</strong></td>
<td>Immédiat</td>
</tr>
<tr>
<td><strong>Réversibilité</strong></td>
<td>Non</td>
</tr>
<tr>
<td><strong>Historique auditeurs</strong></td>
<td>Marqué "Contenu supprimé par créateur"</td>
</tr>
<tr>
<td><strong>Analytics plateforme</strong></td>
<td>Anonymisé et conservé</td>
</tr>
<tr>
<td><strong>Fichiers CDN</strong></td>
<td>Supprimés sous 24h</td>
</tr>
</tbody>
</table>
<p><strong>Exemple scénario suppression</strong> :</p>
<pre><code>Créateur supprime podcast écouté par 1000 personnes
→ CDN : fichiers purgés sous 24h
→ BDD : entrée marquée &quot;deleted&quot;, auteur anonymisé
→ Historique auditeurs : &quot;Contenu supprimé&quot; (conserve durée écoute pour stats)
→ Analytics : métriques globales conservées (anonymes, RGPD OK)
</code></pre>
<p><strong>Notifications suppression</strong> :
- 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"</p>
<p><strong>Justification</strong> :
- <strong>Simplicité</strong> : règles claires et non-ambiguës
- <strong>Sécurité</strong> : évite manipulations algorithme et contournements modération
- <strong>Contrôle créateur</strong> : liberté totale de supprimer (RGPD)
- <strong>Traçabilité</strong> : historique conservé pour analytics (anonymisé)
- <strong>Coût 0</strong> : pas de revalidation métadonnées</p>
<hr />
<h2 id="recapitulatif-section-4">Récapitulatif Section 4</h2>
<div style="page-break-after: always;"></div>
<h2 id="5-interactions-et-navigation">5. Interactions et navigation</h2>
<h3 id="51-file-dattente-et-commande-suivant">5.1 File d'attente et commande "Suivant"</h3>
<p><strong>Décision</strong> : Pré-calcul 5 contenus avec insertion prioritaire pour points géographiques</p>
<p><strong>File d'attente</strong> :
- <strong>5 contenus pré-calculés</strong> en cache (Redis)
- Recalcul automatique si :
- Déplacement &gt;10km
- Toutes les 10 minutes (rafraîchissement contenu)
- File d'attente &lt;3 contenus restants</p>
<p><strong>Insertion prioritaire géo-ancrée (mode voiture uniquement)</strong> :</p>
<p><strong>Détection</strong> :
- Calcul ETA (Estimated Time of Arrival) via API GPS native iOS/Android
- Notification déclenchée <strong>7 secondes avant</strong> d'arriver au point GPS
- Si vitesse &lt; 5 km/h ET distance &lt; 50m → notification immédiate
- ⚠️ <strong>App doit être ouverte</strong> (pas de détection en arrière-plan en mode voiture)</p>
<p><strong>Notification</strong> :
- <strong>Sonore uniquement</strong> : bip court ou son personnalisé RoadWave
- <strong>Visuelle minimale</strong> : icône selon type de contenu (🏛️ culture, 👨‍👩‍👧 famille, 🎵 musique, etc.)
- <strong>Compteur visible</strong> : 7...6...5...4...3...2...1 (décompte des secondes)
- <strong>Pas de texte affiché</strong> (éviter distraction conducteur)
- <strong>Pas de bouton "Annuler"</strong> : seul le bouton "Suivant" permet validation</p>
<p><strong>Actions utilisateur</strong> :
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)</p>
<p><strong>Si user n'appuie pas sur "Suivant"</strong> :
- Notification disparaît après 7 secondes
- Contenu géolocalisé est perdu (pas d'insertion dans file)
- Pas de nouveau contenu géolocalisé pendant <strong>10 minutes</strong> (éviter spam)</p>
<p><strong>Limitation anti-spam</strong> :
- Maximum <strong>6 contenus géolocalisés par heure</strong>
- 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</p>
<p><strong>Invalidation immédiate</strong> :
- Utilisateur change ses préférences (curseurs géo/découverte/politique)
- ⚠️ <strong>Modification bloquée si vitesse GPS &gt;10 km/h</strong> (sécurité routière)
- Live démarre d'un créateur suivi dans la zone</p>
<p><strong>Implémentation</strong> :</p>
<pre><code>Redis cache :
- Clé : user:{user_id}:queue
- Structure : [content_1, content_2, ..., content_5]
- Métadonnées : {last_lat, last_lon, computed_at, mode: &quot;voiture&quot;|&quot;pieton&quot;}
- 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
</code></pre>
<p><strong>Justification</strong> :
- <strong>Expérience fluide</strong> : pas de latence au clic "Suivant"
- <strong>Réactivité géo</strong> : contenu local inséré immédiatement
- <strong>Coût optimisé</strong> : recalcul uniquement si nécessaire
- <strong>Sécurité</strong> : pas de modification en conduite</p>
<hr />
<h3 id="512-mode-pieton-audio-guides">5.1.2 Mode piéton (audio-guides)</h3>
<p><strong>Décision</strong> : Notifications push en arrière-plan avec rayon large</p>
<p><strong>Contexte</strong> :
- Mode piéton détecté automatiquement si vitesse moyenne &lt; 5 km/h
- Cas d'usage : visites à pied, musées, monuments, quartiers historiques
- User n'a pas besoin d'avoir l'app ouverte
- ⚠️ <strong>Fonctionnalité optionnelle</strong> : requiert permission "localisation en arrière-plan" (activée par user)</p>
<p><strong>Détection</strong> :
- App peut être en arrière-plan (si permission accordée)
- Rayon de détection : <strong>200 mètres</strong> 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</p>
<p><strong>Notification push système</strong> :</p>
<p>Format :</p>
<pre><code>Titre : &quot;Audio-guide à proximité&quot;
Body : &quot;[Nom du contenu] - [Nom créateur]&quot;
Action : Tap → ouvre app sur le contenu
</code></pre>
<p>Exemple :</p>
<pre><code>Audio-guide à proximité
Musée du Louvre : La Joconde - @paris_museum
</code></pre>
<p><strong>Permissions requises</strong> :</p>
<p>⚠️ <strong>Important</strong> : Permission "Always Location" est <strong>optionnelle</strong> et demandée uniquement si user active le mode piéton dans settings.</p>
<p>iOS (<code>Info.plist</code>) :</p>
<pre><code class="language-xml">&lt;key&gt;NSLocationWhenInUseUsageDescription&lt;/key&gt;
&lt;string&gt;RoadWave utilise votre position pour vous proposer des contenus audio géolocalisés adaptés à votre trajet en temps réel.&lt;/string&gt;
&lt;key&gt;NSLocationAlwaysAndWhenInUseUsageDescription&lt;/key&gt;
&lt;string&gt;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.&lt;/string&gt;
</code></pre>
<p>Android (<code>AndroidManifest.xml</code>) :</p>
<pre><code class="language-xml">&lt;uses-permission android:name=&quot;android.permission.ACCESS_FINE_LOCATION&quot; /&gt;
&lt;uses-permission android:name=&quot;android.permission.ACCESS_BACKGROUND_LOCATION&quot; /&gt;
</code></pre>
<p><strong>Disclosure avant demande permission</strong> (Android requis, iOS recommandé) :</p>
<p>Écran affiché avant demande permission "Always Location" :</p>
<pre><code>┌────────────────────────────────────────┐
│ 📍 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é│
└────────────────────────────────────────┘
</code></pre>
<p><strong>Si user refuse</strong> :
- 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)</p>
<p><strong>Comportement après tap sur notification</strong> :
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)</p>
<p><strong>Basculement automatique voiture ↔ piéton</strong> :</p>
<p>Détection par vitesse GPS moyenne sur 30 secondes :
- Vitesse &lt; 5 km/h (stable 10s) → mode piéton
- Vitesse ≥ 5 km/h (stable 10s) → mode voiture</p>
<p>Changements de mode :</p>
<table>
<thead>
<tr>
<th>Mode actuel</th>
<th>Vitesse détectée</th>
<th>Nouveau mode</th>
<th>Effet</th>
</tr>
</thead>
<tbody>
<tr>
<td>Piéton</td>
<td>≥ 5 km/h</td>
<td>Voiture</td>
<td>Notifications push → sonores + icône (app ouverte requise)</td>
</tr>
<tr>
<td>Voiture</td>
<td>&lt; 5 km/h</td>
<td>Piéton</td>
<td>Notifications sonores → push arrière-plan</td>
</tr>
</tbody>
</table>
<p><strong>Pas de popup confirmation</strong> :
- Basculement transparent et automatique
- User n'a rien à faire
- Hysteresis (10s) pour éviter basculements intempestifs</p>
<p><strong>Quota anti-spam mode piéton</strong> :
- Même limitation que mode voiture : <strong>6 contenus/heure</strong>
- Cooldown 10 min si notification ignorée (app pas ouverte après tap)</p>
<p><strong>Justification</strong> :
- ✅ 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</p>
<hr />
<h3 id="52-commande-precedent">5.2 Commande "Précédent"</h3>
<p><strong>Décision</strong> : Comportement smart selon progression écoute</p>
<p><strong>Règles</strong> :</p>
<table>
<thead>
<tr>
<th>Situation</th>
<th>Temps écouté</th>
<th>Action "Précédent"</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Début de contenu</strong></td>
<td>&lt;10 secondes</td>
<td>Retour au contenu précédent (position exacte)</td>
</tr>
<tr>
<td><strong>Milieu/fin</strong></td>
<td>≥10 secondes</td>
<td>Replay contenu actuel depuis le début</td>
</tr>
<tr>
<td><strong>Premier de session</strong></td>
<td>N/A</td>
<td>Replay depuis début (rien avant)</td>
</tr>
</tbody>
</table>
<p><strong>Historique de navigation</strong> :
- <strong>10 contenus maximum</strong> en mémoire (Redis List)
- Structure : <code>[{content_id, position_seconds, listened_at}, ...]</code>
- FIFO : au-delà de 10, suppression du plus ancien</p>
<p><strong>Exemple scénario</strong> :</p>
<pre><code>Utilisateur écoute :
1. Contenu A → écoute 5s → &quot;Suivant&quot;
2. Contenu B → écoute 2min30 → &quot;Suivant&quot;
3. Contenu C → écoute 5s → &quot;Précédent&quot;
→ Retour Contenu B à 2min30 (car &gt;10s)
4. Sur Contenu B → &quot;Précédent&quot;
→ Retour Contenu A à 5s (position exacte)
</code></pre>
<p><strong>Interface (responsabilité front)</strong> :
- ❌ Pas de message UI
- ✅ Progress bar revient au début ou à position exacte
- ✅ Animation fluide (transition 0.3s)</p>
<p><strong>Justification</strong> :
- <strong>UX intuitive</strong> : comportement standard Spotify/YouTube
- <strong>Pas de frustration</strong> : si début, vraiment revenir en arrière
- <strong>Simplicité</strong> : règle unique (seuil 10s)</p>
<hr />
<h3 id="53-interactions-au-volant-like-automatique-et-engagement">5.3 Interactions au volant : Like automatique et engagement</h3>
<blockquote>
<p>⚠️ <strong>Architecture Decision Record</strong> : Voir <a href="#../adr/010-commandes-volant">ADR-010</a> pour les détails techniques complets</p>
</blockquote>
<p><strong>Décision</strong> : Like automatique basé sur le temps d'écoute</p>
<p><strong>Problème technique identifié</strong> :
- iOS et Android ne supportent <strong>pas nativement</strong> 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 = <strong>non-intuitif</strong> et <strong>risques sécurité</strong> (regarder écran pour feedback)</p>
<hr />
<h4 id="commandes-au-volant-simplifiees">Commandes au volant simplifiées</h4>
<p><strong>Actions disponibles</strong> (100% compatibles tous véhicules) :</p>
<table>
<thead>
<tr>
<th>Commande physique</th>
<th>Action RoadWave</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Suivant</strong></td>
<td>Passer au contenu suivant</td>
</tr>
<tr>
<td><strong>Précédent</strong></td>
<td>Revenir au contenu précédent (règle 10s, voir section 5.2)</td>
</tr>
<tr>
<td><strong>Play/Pause</strong></td>
<td>Pause/reprise lecture (fade out 0.3s)</td>
</tr>
</tbody>
</table>
<p><strong>Aucune action complexe au volant</strong> → Sécurité routière maximale.</p>
<hr />
<h4 id="like-automatique-implicite">Like automatique implicite</h4>
<p><strong>Principe</strong> : Le système détecte automatiquement l'intérêt utilisateur selon le temps d'écoute.</p>
<p><strong>Règles d'attribution</strong> :</p>
<table>
<thead>
<tr>
<th>Durée écoutée</th>
<th>Action automatique</th>
<th>Points jauge</th>
<th>Justification</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>≥ 80% du contenu</strong></td>
<td>Like renforcé</td>
<td>+2.0</td>
<td>Écoute quasi-complète = fort intérêt</td>
</tr>
<tr>
<td><strong>30-79% du contenu</strong></td>
<td>Like standard</td>
<td>+1.0</td>
<td>Écoute significative = intérêt</td>
</tr>
<tr>
<td><strong>&lt; 30% du contenu</strong></td>
<td>Pas de like</td>
<td>0</td>
<td>Écoute trop courte</td>
</tr>
<tr>
<td><strong>Skip après &lt;10s</strong></td>
<td>Signal négatif</td>
<td>-0.5</td>
<td>Désintérêt marqué</td>
</tr>
</tbody>
</table>
<p><strong>Exemples concrets</strong> :</p>
<pre><code>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)
</code></pre>
<hr />
<h4 id="actions-complementaires-app-a-larret">Actions complémentaires (app à l'arrêt)</h4>
<p><strong>Interface mobile</strong> (véhicule arrêté uniquement) :</p>
<table>
<thead>
<tr>
<th>Action</th>
<th>Moyen</th>
<th>Effet</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Like explicite</strong></td>
<td>Bouton cœur</td>
<td>+2 points jauge (même si déjà liké auto)</td>
</tr>
<tr>
<td><strong>Unlike</strong></td>
<td>Re-clic cœur (toggle)</td>
<td>-2 points jauge</td>
</tr>
<tr>
<td><strong>Abonnement</strong></td>
<td>Bouton "S'abonner" profil créateur</td>
<td>+5 points toutes jauges tags créateur</td>
</tr>
<tr>
<td><strong>Désabonnement</strong></td>
<td>Bouton "Se désabonner"</td>
<td>-5 points</td>
</tr>
<tr>
<td><strong>Signalement</strong></td>
<td>Menu contextuel "⋮"</td>
<td>Ouverture flux modération</td>
</tr>
</tbody>
</table>
<p><strong>Feedback visuel</strong> :
- <strong>Like automatique</strong> : Badge discret "♥ Ajouté à vos favoris" (2s, bas de l'écran)
- <strong>Like explicite</strong> : Animation cœur rouge + vibration courte
- <strong>Abonnement</strong> : Animation étoile dorée + badge "Abonné ✓"</p>
<hr />
<h4 id="commandes-vocales-optionnel-si-carplayandroid-auto">Commandes vocales (optionnel, si CarPlay/Android Auto)</h4>
<p><strong>Disponible uniquement avec</strong> :
- Apple CarPlay (Siri)
- Android Auto (Google Assistant)
- ~30-40% du parc automobile EU (2026)</p>
<p><strong>Exemples de commandes</strong> :</p>
<pre><code>&quot;Hey Siri, like ce podcast&quot;
&quot;OK Google, abonne-moi à ce créateur&quot;
&quot;Hey Siri, passe au contenu suivant&quot;
&quot;OK Google, signale ce contenu&quot;
</code></pre>
<p><strong>Implémentation</strong> : Intents iOS/Android personnalisés (Sprint 5, post-MVP)</p>
<hr />
<h4 id="gestion-impacts-jauges-algorithme">Gestion impacts jauges (algorithme)</h4>
<p><strong>Like automatique</strong> :
- Like renforcé (≥80%) → <strong>+2% jauges</strong> de tous les tags du contenu
- Like standard (30-79%) → <strong>+1% jauges</strong> des tags du contenu
- Signal négatif (skip &lt;10s) → <strong>-0.5% jauges</strong> des tags du contenu</p>
<p><strong>Actions explicites</strong> :
- Like manuel → <strong>+2% jauges</strong> (cumulable avec like auto)
- Unlike → <strong>-2% jauges</strong>
- Abonnement → <strong>+5% toutes jauges</strong> tags créateur
- Désabonnement → <strong>-5% toutes jauges</strong></p>
<p><strong>Persistance</strong> :
- Événements stockés en base (table <code>listen_events</code>)
- Mise à jour jauges : <strong>immédiate</strong> (Redis) + <strong>async batch</strong> (PostgreSQL)</p>
<hr />
<h4 id="implementation-technique">Implémentation technique</h4>
<p><strong>Backend</strong> (Go) :</p>
<pre><code class="language-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 // &quot;completed&quot;, &quot;skipped&quot;, &quot;paused&quot;
}
func ProcessListenEvent(event ListenEvent) {
percentage := event.Percentage
// Signal négatif fort
if event.Action == &quot;skipped&quot; &amp;&amp; event.Duration &lt; 10 {
UpdateJauges(event.UserID, event.ContentID, -0.5)
return
}
// Like automatique
if percentage &gt;= 80 {
AutoLike(event.UserID, event.ContentID, 2.0) // Renforcé
} else if percentage &gt;= 30 {
AutoLike(event.UserID, event.ContentID, 1.0) // Standard
}
// &lt; 30% : pas de like
}
</code></pre>
<p><strong>Mobile</strong> (iOS/Android) :</p>
<pre><code class="language-swift">// iOS - Tracking écoute
class AudioPlayerManager {
var startTime: Date?
let contentDuration: TimeInterval
func onPlay() {
startTime = Date()
}
func onStop(action: String) { // &quot;completed&quot; | &quot;skipped&quot; | &quot;paused&quot;
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
))
}
}
</code></pre>
<hr />
<h4 id="justification_13">Justification</h4>
<p><strong>Avantages</strong> :
- ✅ <strong>Sécurité routière maximale</strong> : aucune action complexe au volant
- ✅ <strong>UX intuitive</strong> : comportement standard industrie (Spotify, YouTube Music, Deezer)
- ✅ <strong>Compatibilité 100%</strong> : fonctionne sur tous véhicules, tous OS
- ✅ <strong>Engagement amélioré</strong> : tous les contenus écoutés génèrent des signaux
- ✅ <strong>Algorithme plus précis</strong> : données granulaires (30%, 50%, 80%, 100%)
- ✅ <strong>Simplicité développement</strong> : pas de workarounds complexes iOS/Android</p>
<p><strong>Inconvénients mitigés</strong> :
- ⚠️ Pas de like explicite en conduite → <strong>Mitigation</strong> : like automatique + vocal (CarPlay/Android Auto)
- ⚠️ Pas d'abonnement en conduite → <strong>Mitigation</strong> : liste "Créateurs à découvrir" dans app
- ⚠️ Like automatique peut surprendre → <strong>Mitigation</strong> : onboarding clair + unlike possible</p>
<hr />
<h4 id="communication-utilisateurs-onboarding">Communication utilisateurs (onboarding)</h4>
<p><strong>Écran onboarding 1</strong> :</p>
<pre><code>🚗 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]
</code></pre>
<p><strong>Écran onboarding 2</strong> :</p>
<pre><code>❤️ Likes automatiques
Pas besoin de liker manuellement :
si vous écoutez &gt;50% d'un contenu,
on comprend que vous aimez !
[Suivant]
</code></pre>
<p><strong>Écran onboarding 3</strong> :</p>
<pre><code>⏸️ Commandes simples
Utilisez les boutons au volant :
• Suivant → Prochain contenu
• Précédent → Contenu d'avant
• Pause → Mettre en pause
[Commencer]
</code></pre>
<hr />
<h3 id="54-lecture-en-boucle-et-enchainement">5.4 Lecture en boucle et enchaînement</h3>
<p><strong>Décision</strong> : Passage automatique après 2s + insertion pub paramétrable</p>
<p><strong>Fin de contenu</strong> :
1. Audio termine → <strong>Timer 2 secondes</strong> 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</p>
<p><strong>Délai selon contexte</strong> :</p>
<table>
<thead>
<tr>
<th>Mode</th>
<th>Délai</th>
<th>Justification</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Standard</strong></td>
<td>2 secondes</td>
<td>Temps réaction confortable</td>
</tr>
<tr>
<td><strong>Mode Kids</strong></td>
<td>1 seconde</td>
<td>Attention courte enfants</td>
</tr>
<tr>
<td><strong>Live</strong></td>
<td>0 seconde</td>
<td>Enchaînement immédiat</td>
</tr>
</tbody>
</table>
<p><strong>Insertion publicité</strong> :
- Pub s'insère <strong>pendant le délai de 2s</strong> (transition naturelle)
- Fréquence : <strong>paramétrable admin</strong> (défaut : 1 pub / 5 contenus)
- Message : "Publicité (15s)" puis lecture pub
- ⚠️ <strong>Jamais d'interruption</strong> d'un contenu en cours</p>
<p><strong>Publicité skippable</strong> :
- Durée minimale visionnage : <strong>paramétrable</strong> (défaut : 5 secondes)
- Bouton "Passer" apparaît après délai
- Métriques engagement : taux skip, durée écoute moyenne
- <strong>Like et abonnement autorisés sur pub</strong> (engagement créateur pub)</p>
<p><strong>Si aucun contenu disponible</strong> :
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</p>
<p><strong>Gestion erreurs</strong> :
- Échec chargement contenu suivant → <strong>retry 3× avec backoff exponentiel</strong>
- Si 3 échecs → message "Connexion instable, basculement mode offline"
- Mode offline → lecture contenus téléchargés uniquement</p>
<p><strong>Justification</strong> :
- <strong>Fluidité</strong> : enchaînement naturel sans action utilisateur
- <strong>Contrôle</strong> : possibilité annuler pendant délai
- <strong>Paramétrabilité pub</strong> : évite frustration excès publicité
- <strong>Engagement pub</strong> : like/abonnement autorisé = monétisation créateurs pub</p>
<hr />
<h2 id="recapitulatif-section-5">Récapitulatif Section 5</h2>
<div style="page-break-after: always;"></div>
<h2 id="6-publicites">6. Publicités</h2>
<h3 id="61-systeme-de-campagnes-publicitaires">6.1 Système de campagnes publicitaires</h3>
<p><strong>Décision</strong> : Interface self-service avec maîtrise budget et métriques détaillées</p>
<p><strong>Fonctionnalités publicitaire</strong> :</p>
<h4 id="creation-de-campagne">Création de campagne</h4>
<p><strong>Paramètres configurables</strong> :</p>
<table>
<thead>
<tr>
<th>Paramètre</th>
<th>Options</th>
<th>Justification</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Budget total</strong></td>
<td>Montant libre (min 50€)</td>
<td>Maîtrise coût total</td>
</tr>
<tr>
<td><strong>Durée campagne</strong></td>
<td>Date début/fin + étalement</td>
<td>Ex: 300€ sur 2 semaines</td>
</tr>
<tr>
<td><strong>Ciblage géographique</strong></td>
<td>Point GPS / Ville / Département / Région / National</td>
<td>Précision selon besoin</td>
</tr>
<tr>
<td><strong>Ciblage horaire</strong></td>
<td>Plages horaires (ex: 7h-9h, 17h-19h)</td>
<td>Optimisation trajet domicile-travail</td>
</tr>
<tr>
<td><strong>Centres d'intérêt</strong></td>
<td>Tags (ex: Automobile, Voyage)</td>
<td>Ciblage thématique</td>
</tr>
<tr>
<td><strong>Tranche d'âge</strong></td>
<td>Tout public / 13+ / 16+ / 18+</td>
<td>Respect classifications</td>
</tr>
</tbody>
</table>
<p><strong>Étalement budget</strong> :</p>
<pre><code>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)
</code></pre>
<p><strong>Mode de paiement</strong> :
- ✅ Prépaiement obligatoire (évite impayés)
- ✅ Carte bancaire uniquement (Mangopay)
- ✅ Recharge automatique optionnelle (si budget &lt;10%)</p>
<h4 id="validation-et-moderation">Validation et modération</h4>
<p><strong>Processus</strong> :
1. Publicitaire upload audio pub (formats : MP3, AAC)
2. <strong>Validation manuelle obligatoire</strong> (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</p>
<p><strong>Contenus interdits en pub</strong> :
- ❌ Alcool, tabac (réglementation française)
- ❌ Jeux d'argent
- ❌ Contenu politique (pendant campagnes électorales)
- ❌ Contenu sexuel ou violence
- ✅ Tous commerces/services légaux</p>
<h4 id="dashboard-metriques-engagement">Dashboard métriques engagement</h4>
<p><strong>Indicateurs temps réel</strong> :</p>
<table>
<thead>
<tr>
<th>Métrique</th>
<th>Description</th>
<th>Utilité</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Impressions</strong></td>
<td>Nombre de diffusions</td>
<td>Volume exposition</td>
</tr>
<tr>
<td><strong>Écoutes complètes</strong></td>
<td>Pub écoutée &gt;80%</td>
<td>Engagement réel</td>
</tr>
<tr>
<td><strong>Taux de skip</strong></td>
<td>% skip après délai min</td>
<td>Qualité contenu</td>
</tr>
<tr>
<td><strong>Durée moyenne écoute</strong></td>
<td>Secondes écoutées</td>
<td>Rétention attention</td>
</tr>
<tr>
<td><strong>Likes</strong></td>
<td>Nombre de likes</td>
<td>Appréciation contenu</td>
</tr>
<tr>
<td><strong>Abonnements</strong></td>
<td>Abonnements au créateur pub</td>
<td>Conversion forte</td>
</tr>
<tr>
<td><strong>Coût par écoute</strong></td>
<td>Budget / écoutes complètes</td>
<td>ROI campagne</td>
</tr>
<tr>
<td><strong>Répartition géographique</strong></td>
<td>Heatmap diffusions</td>
<td>Validation ciblage</td>
</tr>
<tr>
<td><strong>Répartition horaire</strong></td>
<td>Graphique par heure</td>
<td>Optimisation horaires</td>
</tr>
</tbody>
</table>
<p><strong>Métriques engagement avancées</strong> :
- <strong>Taux complétion par tranche d'âge</strong> : identifier audience réceptive
- <strong>Carte de chaleur GPS</strong> : visualiser zones forte écoute
- <strong>Comparatif campagnes</strong> : A/B testing créatifs publicitaires</p>
<p><strong>Export données</strong> :
- ✅ CSV/Excel pour analyse externe
- ✅ Graphiques interactifs (Chart.js)
- ✅ Rapport PDF automatique fin de campagne</p>
<h4 id="gestion-budget-et-alertes">Gestion budget et alertes</h4>
<p><strong>Suivi temps réel</strong> :
- 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)</p>
<p><strong>Ajustements en cours</strong> :
- ✅ Pause campagne (budget conservé)
- ✅ Prolonger campagne (recharge budget)
- ✅ Modifier ciblage horaire/géo (si &lt;50% budget consommé)
- ❌ Modifier audio (nécessite nouvelle validation)</p>
<h4 id="systeme-dencheres-post-mvp">Système d'enchères (post-MVP)</h4>
<p><strong>Optionnel future</strong> :
- 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</p>
<p><strong>Justification décision MVP</strong> :
- Tarif fixe simple : 0.05€/écoute complète
- Pas de complexité enchères immédiatement
- Scalable : passage enchères ultérieur si demande forte</p>
<hr />
<h3 id="62-insertion-et-frequence">6.2 Insertion et fréquence</h3>
<p><strong>Décision</strong> : Paramétrable admin + respect expérience utilisateur</p>
<p><strong>Fréquence d'insertion</strong> :
- <strong>Défaut : 1 pub / 5 contenus</strong> (utilisateurs gratuits)
- <strong>Paramétrable admin</strong> : curseur 1/3 à 1/10
- <strong>Utilisateurs Premium</strong> : 0 pub (modèle sans publicité)</p>
<p><strong>Règles strictes</strong> :
- ⚠️ <strong>Jamais d'interruption</strong> contenu en cours
- Pub s'insère uniquement <strong>entre deux contenus</strong> (pendant délai 2s)
- Rotation : même pub max <strong>3 fois/jour</strong> par utilisateur (évite saturation)
- Limite : max <strong>6 pubs/heure</strong> par utilisateur (évite spam)</p>
<p><strong>Ciblage intelligent</strong> :
- Géolocalisation prioritaire (point GPS &gt; ville &gt; département &gt; région &gt; national)
- Centres d'intérêt secondaires (tags utilisateur)
- Horaire (campagne 7h-9h → diffusion uniquement pendant plage)</p>
<p><strong>Volume audio normalisé</strong> :
- Pub normalisée à <strong>-14 LUFS</strong> (standard broadcast)
- Évite effet "pub trop forte" (frustration utilisateur)
- Validation automatique via FFmpeg lors encodage</p>
<hr />
<h3 id="63-caracteristiques-publicites">6.3 Caractéristiques publicités</h3>
<p><strong>Durée</strong> :
- Minimum : <strong>10 secondes</strong>
- Maximum : <strong>60 secondes</strong>
- Recommandé : <strong>15-30 secondes</strong> (sweet spot engagement)</p>
<p><strong>Skippable</strong> :
- Délai minimum obligatoire : <strong>5 secondes</strong> (paramétrable admin : 3-10s)
- Bouton "Passer la publicité" apparaît après délai
- Durée minimale comptabilisée pour facturation</p>
<p><strong>Facturation</strong> :
- <strong>Écoute complète</strong> (&gt;80%) : 0.05€ facturé publicitaire
- <strong>Skip après délai min</strong> : 0.02€ (exposition partielle)
- <strong>Skip immédiat</strong> (&lt;5s) : 0€ (pas d'engagement)</p>
<p><strong>Justification modèle tarif</strong> :
- Incitatif qualité : pub engageante = coût réduit
- Équitable : publicitaire paie pour attention réelle
- Transparent : dashboard montre écoutes complètes vs skips</p>
<hr />
<h2 id="recapitulatif-section-6">Récapitulatif Section 6</h2>
<div style="page-break-after: always;"></div>
<h2 id="7-radio-live">7. Radio live</h2>
<h3 id="71-demarrage-dun-live">7.1 Démarrage d'un live</h3>
<p><strong>Décision</strong> : Buffer 15s + notification abonnés + limite 8h</p>
<p><strong>Processus de démarrage</strong> :</p>
<ol>
<li>Créateur appuie "Démarrer live" dans l'app</li>
<li><strong>Vérification pré-live</strong> :</li>
<li>Connexion ≥1 Mbps upload (warning si insuffisant)</li>
<li>Micro autorisé</li>
<li>Zone diffusion déjà définie (ville, département, région, national)</li>
<li><strong>Buffer initial 15 secondes</strong> avant diffusion publique</li>
<li>Créateur parle pendant 15s → accumulation buffer serveur</li>
<li>Message créateur : "Live démarre dans 15s... Testez votre micro"</li>
<li>Permet vérifier qualité audio avant diffusion</li>
<li>Après 15s → <strong>Live public</strong>, auditeurs peuvent rejoindre</li>
</ol>
<p><strong>Notification abonnés</strong> :
- ✅ <strong>Push notification immédiate</strong> à 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
- <strong>Filtrage géographique</strong> : si abonné hors zone, pas de notif (évite frustration)</p>
<p><strong>Limite de durée</strong> :
- <strong>Maximum 8 heures</strong> 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)</p>
<p><strong>Métadonnées obligatoires</strong> :</p>
<table>
<thead>
<tr>
<th>Champ</th>
<th>Format</th>
<th>Validation</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Titre</strong></td>
<td>5-100 caractères</td>
<td>Ex: "Discussion politique en direct"</td>
</tr>
<tr>
<td><strong>Tags</strong></td>
<td>1-3 centres d'intérêt</td>
<td>Sélection liste prédéfinie</td>
</tr>
<tr>
<td><strong>Classification âge</strong></td>
<td>Enum</td>
<td>Tout public / 13+ / 16+ / 18+</td>
</tr>
<tr>
<td><strong>Zone diffusion</strong></td>
<td>Geo</td>
<td>Ville / Département / Région / National</td>
</tr>
</tbody>
</table>
<p><strong>Contenus interdits en live</strong> :</p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Description</th>
<th>Sanction</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Concert/spectacle</strong></td>
<td>Diffusion concert en direct depuis la salle</td>
<td>Strike 2 immédiat + ban temporaire</td>
</tr>
<tr>
<td><strong>Événement sportif payant</strong></td>
<td>Match, compétition avec droits TV</td>
<td>Strike 2 immédiat</td>
</tr>
<tr>
<td><strong>Œuvre protégée</strong></td>
<td>Film, série, musique en fond sans droits</td>
<td>Strike 1 + suppression live</td>
</tr>
<tr>
<td><strong>Contenu violent</strong></td>
<td>Agression, violence physique</td>
<td>Ban immédiat</td>
</tr>
<tr>
<td><strong>Contenu illégal</strong></td>
<td>Apologie terrorisme, pédopornographie</td>
<td>Ban définitif + signalement autorités</td>
</tr>
</tbody>
</table>
<p><strong>Exemple usecase interdit</strong> :</p>
<pre><code>❌ 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
</code></pre>
<p><strong>Détection violations</strong> :
- <strong>Signalement utilisateurs</strong> : bouton "Signaler" accessible pendant live
- <strong>IA audio fingerprint</strong> : détection musique protégée en arrière-plan (post-MVP)
- <strong>Modération réactive</strong> : modérateurs peuvent écouter lives signalés en temps réel
- <strong>Coupure immédiate</strong> : modérateur peut arrêter live si contenu illégal évident</p>
<p><strong>Justification</strong> :
- <strong>Buffer 15s</strong> : équilibre entre test qualité et friction minimale
- <strong>Notification abonnés</strong> : engagement maximal, valeur ajoutée live
- <strong>8h max</strong> : couvre 99% cas usage (podcasts longs, émissions radio) sans abus
- <strong>Interdictions strictes</strong> : protection juridique plateforme (DSA EU, droits d'auteur)
- <strong>Coût</strong> : WebRTC ingestion + HLS distribution (réutilise infra existante)</p>
<hr />
<h3 id="72-arret-du-live">7.2 Arrêt du live</h3>
<p><strong>Décision</strong> : Compte à rebours 5s + tolérance déconnexion 60s + enregistrement auto</p>
<p><strong>Fin manuelle créateur</strong> :</p>
<ol>
<li>Créateur appuie "Arrêter live"</li>
<li><strong>Compte à rebours 5 secondes</strong> affiché</li>
<li>Message audio : "Ce live se termine dans 5... 4... 3... 2... 1"</li>
<li>Permet au créateur de faire un outro propre</li>
<li>Annulable pendant décompte (bouton "Annuler")</li>
<li>Timer atteint 0 → arrêt diffusion</li>
<li><strong>Traitement post-live automatique</strong> démarre (voir ci-dessous)</li>
</ol>
<p><strong>Fin automatique si déconnexion</strong> :</p>
<table>
<thead>
<tr>
<th>Durée coupure</th>
<th>Comportement</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>&lt;60 secondes</strong></td>
<td>Message auditeurs : "Connexion créateur perdue, reconnexion en cours..."</td>
</tr>
<tr>
<td><strong>≥60 secondes</strong></td>
<td>Arrêt automatique live + message : "Le live est terminé suite à une coupure de connexion"</td>
</tr>
</tbody>
</table>
<p><strong>Enregistrement automatique</strong> :</p>
<p>✅ <strong>Obligatoire et automatique</strong> (valeur ajoutée énorme)</p>
<p><strong>Processus</strong> :
1. Pendant live : enregistrement continu serveur (format Opus raw)
2. Fin live → <strong>job asynchrone</strong> (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. <strong>Publication automatique</strong> du replay :
- Titre : "[REPLAY] [Titre live original]"
- Même zone diffusion, tags, classification
- Disponible sous <strong>5-10 minutes</strong> après fin live
- Type géo : automatiquement "Géo-neutre" (replay = contenu pérenne)</p>
<p><strong>Options créateur</strong> :</p>
<table>
<thead>
<tr>
<th>Option</th>
<th>Défaut</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Publier replay automatiquement</strong></td>
<td>✅ OUI</td>
<td>Désactivable avant démarrage live</td>
</tr>
<tr>
<td><strong>Supprimer replay après coup</strong></td>
<td>✅ Possible</td>
<td>Suppression standard contenu</td>
</tr>
<tr>
<td><strong>Modifier replay</strong></td>
<td>❌ Non</td>
<td>Intégrité enregistrement</td>
</tr>
</tbody>
</table>
<p><strong>Conservation fichier source</strong> :
- Opus raw conservé <strong>7 jours</strong> après fin live (backup)
- Suppression automatique après 7j (économie stockage)
- Si replay supprimé par créateur → fichier raw supprimé immédiatement</p>
<p><strong>Justification</strong> :
- <strong>Compte à rebours 5s</strong> : outro propre, pas de coupure brutale
- <strong>Tolérance 60s</strong> : évite arrêts intempestifs (tunnel, changement cellule)
- <strong>Enregistrement auto</strong> : valorisation contenu éphémère, génération contenu pérenne
- <strong>MP3 256 kbps</strong> : qualité optimale pour replay (vs 48 kbps live)
- <strong>Coût</strong> : stockage minimal (Opus → MP3 1× par live, puis suppression raw après 7j)</p>
<hr />
<h3 id="73-comportement-auditeur">7.3 Comportement auditeur</h3>
<p><strong>Décision</strong> : Buffer 15s + continuation hors zone + reconnexion au live actuel + écoute passive uniquement</p>
<p><strong>Buffer de synchronisation</strong> :</p>
<ul>
<li><strong>15 secondes</strong> entre créateur et auditeurs</li>
<li>Raisons :</li>
<li>Stabilité réseau mobile (3G/4G fluctuant)</li>
<li>Synchronisation approximative acceptable (pas besoin temps réel strict)</li>
<li>Permet buffering anticiper coupures courtes (tunnels)</li>
</ul>
<p><strong>Comparaison buffers</strong> :</p>
<table>
<thead>
<tr>
<th>Buffer</th>
<th>Avantages</th>
<th>Inconvénients</th>
<th>Décision</th>
</tr>
</thead>
<tbody>
<tr>
<td>5s</td>
<td>Quasi temps réel</td>
<td>Instable 3G, coupures fréquentes</td>
<td>❌</td>
</tr>
<tr>
<td>10s</td>
<td>Bon compromis</td>
<td>Légèrement juste pour 3G</td>
<td>❌</td>
</tr>
<tr>
<td><strong>15s</strong></td>
<td><strong>Stabilité optimale 3G/4G</strong></td>
<td>Léger décalage acceptable</td>
<td>✅</td>
</tr>
<tr>
<td>20s+</td>
<td>Très stable</td>
<td>Décalage trop perceptible</td>
<td>❌</td>
</tr>
</tbody>
</table>
<p><strong>Zone géographique pendant live</strong> :</p>
<ul>
<li>✅ <strong>Continuation si sortie de zone</strong></li>
<li>Scénario : auditeur écoute live régional → sort du département → <strong>live continue</strong></li>
<li>Raisons :</li>
<li>Pas de coupure brutale (mauvaise UX)</li>
<li>Écoute engagée = terminer naturellement</li>
<li>Après fin live → algo normal (pas de contenus hors zone)</li>
</ul>
<p><strong>Reconnexion après coupure réseau</strong> :</p>
<table>
<thead>
<tr>
<th>Durée coupure</th>
<th>Comportement</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>&lt;90 secondes</strong></td>
<td>Reprend au live actuel (pas au buffer ancien) + saut temporel transparent</td>
</tr>
<tr>
<td><strong>≥90 secondes</strong></td>
<td>Message : "Live en cours perdu, passage au contenu suivant" + algo propose contenu normal</td>
</tr>
</tbody>
</table>
<p><strong>Interactions disponibles</strong> :</p>
<p><strong>Décision ferme</strong> : ❌ <strong>Aucun chat en direct, ni maintenant ni dans le futur</strong></p>
<p><strong>Raisons</strong> :
- <strong>Sécurité routière</strong> : pas de distraction en voiture (focus UX)
- <strong>Harcèlement</strong> : évite contenu haineux, insultes, trolling
- <strong>Modération</strong> : pas de coût modération temps réel (impossible à scale)
- <strong>Simplicité</strong> : écoute passive = expérience uniforme</p>
<p><strong>Actions autorisées pendant live</strong> :</p>
<table>
<thead>
<tr>
<th>Action</th>
<th>Disponible</th>
<th>Effet</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Like</strong></td>
<td>✅</td>
<td>Bouton cœur interface mobile (véhicule arrêté)</td>
</tr>
<tr>
<td><strong>Abonnement créateur</strong></td>
<td>✅</td>
<td>Bouton profil créateur (interface mobile)</td>
</tr>
<tr>
<td><strong>Skip</strong></td>
<td>✅</td>
<td>Passe au contenu suivant, sort du live</td>
</tr>
<tr>
<td><strong>Précédent</strong></td>
<td>❌</td>
<td>Pas de sens sur live (flux temps réel)</td>
</tr>
<tr>
<td><strong>Chat</strong></td>
<td>❌</td>
<td>Jamais implémenté (décision définitive)</td>
</tr>
<tr>
<td><strong>Réactions emoji</strong></td>
<td>❌</td>
<td>Jamais implémenté (décision définitive)</td>
</tr>
</tbody>
</table>
<p><strong>Messages utilisateur</strong> :
- "💬 Les discussions ne sont pas disponibles sur RoadWave pour garantir votre sécurité en voiture et éviter le harcèlement."</p>
<p><strong>Justification décision définitive</strong> :
- <strong>UX cohérente</strong> : RoadWave = écoute en conduisant, pas réseau social interactif
- <strong>Bien-être</strong> : évite toxicité, harcèlement, haine (fléau réseaux sociaux)
- <strong>Juridique</strong> : pas de risque contentieux modération chat (DSA EU)
- <strong>Coût</strong> : 0€ infra chat, 0€ modération temps réel
- <strong>Différenciation</strong> : positionnement "audio safe" vs plateformes toxiques</p>
<hr />
<h3 id="74-architecture-technique">7.4 Architecture technique</h3>
<p><strong>Stack</strong> :</p>
<pre><code>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)
</code></pre>
<p><strong>Flux détaillé</strong> :
1. <strong>Créateur</strong> → WebRTC OPUS 48 kbps vers serveur Go
2. <strong>Serveur Go</strong> → Conversion temps réel OPUS → segments HLS (.m3u8 + .ts)
3. <strong>Bunny CDN</strong> → Distribution HLS avec cache
4. <strong>Auditeurs</strong> → Lecture HLS native iOS/Android (buffer 15s)
5. <strong>Enregistrement parallèle</strong> → Opus raw stocké temporairement
6. <strong>Post-live</strong> → Job async : Opus → MP3 256 kbps → Publication replay</p>
<p><strong>Dépendances</strong> :
- ✅ <strong>Pion WebRTC</strong> (Go library, open source, MIT license)
- ✅ <strong>FFmpeg</strong> (conversion audio, LGPL/GPL)
- ✅ <strong>Bunny CDN</strong> (distribution HLS, pas Google/Cloudflare)
- ✅ <strong>PostgreSQL + Redis</strong> (métadonnées live + cache)</p>
<p><strong>Avantages</strong> :
- ✅ 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)</p>
<p><strong>Coût estimé</strong> :</p>
<table>
<thead>
<tr>
<th>Phase</th>
<th>Utilisateurs</th>
<th>Infra live</th>
<th>Coût/mois</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>MVP</strong></td>
<td>0-100K</td>
<td>1 instance Go (ingestion 100 lives simultanés)</td>
<td>+50€ (serveur) + bande passante CDN</td>
</tr>
<tr>
<td><strong>Growth</strong></td>
<td>100K-1M</td>
<td>3-5 instances Go (500 lives simultanés)</td>
<td>+200€ + bande passante</td>
</tr>
<tr>
<td><strong>Scale</strong></td>
<td>1M-10M</td>
<td>Kubernetes auto-scale (2000+ lives)</td>
<td>+1K€ + bande passante</td>
</tr>
</tbody>
</table>
<p><strong>Bande passante</strong> :
- 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</p>
<hr />
<h2 id="recapitulatif-section-7">Récapitulatif Section 7</h2>
<div style="page-break-after: always;"></div>
<h2 id="8-abonnements-et-notifications">8. Abonnements et notifications</h2>
<h3 id="81-impact-sur-lalgorithme">8.1 Impact sur l'algorithme</h3>
<p><strong>Décision</strong> : Boost +30% au score + reste dans le mix</p>
<p><strong>Boost de score abonnements</strong> :
- <strong>+30% au score final</strong> pour contenus d'un créateur suivi
- Application : multiplicateur sur le score calculé</p>
<pre><code>score_final_avec_boost = score_final × 1.3
</code></pre>
<p><strong>Reste dans le mix</strong> :
- ❌ <strong>Pas de priorité absolue</strong> (pas de file dédiée abonnements)
- ✅ Contenu suivi entre en <strong>compétition avec autres contenus</strong>
- ✅ Si créateur suivi publie contenu faible engagement → peut être battu par contenu viral non-suivi</p>
<p><strong>Exemple concret</strong> :</p>
<pre><code>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 &gt; 0.69)
</code></pre>
<p><strong>Cas où abonnement fait la différence</strong> :</p>
<pre><code>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)
</code></pre>
<p><strong>Justification</strong> :
- <strong>Équilibre</strong> : valorise abonnements sans enfermer utilisateur
- <strong>Découverte</strong> : contenus viraux/locaux peuvent toujours émerger
- <strong>Prévisible</strong> : boost fixe, pas de logique opaque
- <strong>Coût 0</strong> : multiplicateur simple dans l'algo</p>
<hr />
<h3 id="82-notifications-contextuelles">8.2 Notifications contextuelles</h3>
<p><strong>Décision</strong> : Push adapté selon contexte (voiture vs à pied) + limite 10/jour</p>
<p><strong>Détection contexte utilisateur</strong> :</p>
<table>
<thead>
<tr>
<th>Contexte</th>
<th>Détection</th>
<th>Comportement</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>En voiture</strong></td>
<td>Vitesse GPS &gt;10 km/h</td>
<td>Notifications silencieuses (in-app uniquement) + commandes volant</td>
</tr>
<tr>
<td><strong>À pied</strong></td>
<td>Vitesse GPS &lt;5 km/h</td>
<td>Notifications push actives + interface tactile/vocale</td>
</tr>
</tbody>
</table>
<p><strong>Notifications activées</strong> :</p>
<h4 id="en-voiture-mode-conduite">En voiture (mode conduite)</h4>
<table>
<thead>
<tr>
<th>Événement</th>
<th>Notification</th>
<th>Comportement</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Nouveau contenu créateur suivi</strong></td>
<td>In-app uniquement</td>
<td>Badge compteur, pas de push (sécurité)</td>
</tr>
<tr>
<td><strong>Live créateur suivi</strong></td>
<td>In-app uniquement</td>
<td>Badge compteur, pas de push</td>
</tr>
<tr>
<td><strong>Point d'intérêt proche</strong></td>
<td>Audio notification</td>
<td>Bip + annonce vocale : "Audio-guide disponible"</td>
</tr>
</tbody>
</table>
<h4 id="a-pied-mode-pieton">À pied (mode piéton)</h4>
<table>
<thead>
<tr>
<th>Événement</th>
<th>Notification</th>
<th>Comportement</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Nouveau contenu créateur suivi</strong></td>
<td>✅ Push</td>
<td>Si utilisateur dans zone géo du contenu</td>
</tr>
<tr>
<td><strong>Live créateur suivi</strong></td>
<td>✅ Push</td>
<td>Si utilisateur dans zone géo</td>
</tr>
<tr>
<td><strong>Audio-guide disponible</strong></td>
<td>✅ Push</td>
<td>"📍 Audio-guide disponible : [Lieu]"</td>
</tr>
<tr>
<td><strong>Séquence suivante suggérée</strong></td>
<td>Audio notification</td>
<td>Annonce vocale : "Pièce suivante disponible"</td>
</tr>
</tbody>
</table>
<p><strong>Format notifications</strong> :</p>
<p><strong>Nouveau contenu</strong> :</p>
<pre><code>🎧 [Nom créateur] a publié : &quot;[Titre contenu]&quot;
Tap pour écouter
</code></pre>
<p><strong>Live en direct</strong> :</p>
<pre><code>🔴 [Nom créateur] est en direct : &quot;[Titre live]&quot;
Tap pour rejoindre
</code></pre>
<p><strong>Audio-guide à pied</strong> :</p>
<pre><code>📍 Audio-guide disponible : [Nom du lieu]
Choisissez parmi 3 guides pour [Musée du Louvre]
Tap pour explorer
</code></pre>
<p><strong>Filtrage géographique</strong> :
- Si contenu/live hors zone utilisateur → <strong>pas de notification</strong>
- Évite frustration : "notification pour contenu que je ne peux pas écouter"
- Exception : contenu national → notifie tous les abonnés</p>
<p><strong>Fréquence maximale</strong> :
- <strong>Maximum 10 notifications push/jour</strong> par utilisateur (tous types confondus)
- Si dépassement : notifications regroupées
- Message groupé : "🎧 3 nouveaux contenus de créateurs suivis"</p>
<p><strong>Plages horaires</strong> :
- <strong>Mode silencieux</strong> : 22h-8h (pas de push, sauf live)
- Paramétrable utilisateur (désactivation totale possible)
- Option "Notifications importantes uniquement" (lives uniquement)</p>
<p><strong>Gestion préférences</strong> :</p>
<table>
<thead>
<tr>
<th>Préférence</th>
<th>Défaut</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Nouveaux contenus</strong></td>
<td>✅ Activé</td>
<td>Push à chaque nouveau contenu (à pied uniquement)</td>
</tr>
<tr>
<td><strong>Lives</strong></td>
<td>✅ Activé</td>
<td>Push au démarrage live (à pied uniquement)</td>
</tr>
<tr>
<td><strong>Audio-guides proximité</strong></td>
<td>✅ Activé</td>
<td>Push quand audio-guide détecté à &lt;100m</td>
</tr>
<tr>
<td><strong>Mode silencieux</strong></td>
<td>✅ Activé (22h-8h)</td>
<td>Pas de push nocturne</td>
</tr>
<tr>
<td><strong>Limite quotidienne</strong></td>
<td>10</td>
<td>Modifiable 5-20</td>
</tr>
</tbody>
</table>
<p><strong>Justification</strong> :
- <strong>Sécurité routière</strong> : pas de push en conduite (distraction)
- <strong>Engagement piéton</strong> : push actifs pour audio-guides (valeur ajoutée tourisme)
- <strong>Pas de spam</strong> : limite 10/jour + mode silencieux
- <strong>Filtrage géo</strong> : pertinence maximale (pas de notif inutiles)
- <strong>Coût</strong> : Firebase Cloud Messaging (gratuit jusqu'à volume élevé)</p>
<hr />
<h3 id="83-mode-audio-guide-pieton">8.3 Mode Audio-guide (piéton)</h3>
<p><strong>Décision</strong> : Navigation manuelle multiséquence + choix parmi plusieurs guides</p>
<p><strong>Fonctionnement</strong> :</p>
<h4 id="detection-et-proposition">Détection et proposition</h4>
<ol>
<li>Utilisateur à pied (&lt;5 km/h) passe à &lt;<strong>100m</strong> d'un lieu avec audio-guides</li>
<li><strong>Notification push</strong> : "📍 Audio-guide disponible : [Musée du Louvre]"</li>
<li>Tap notification → <strong>Page de sélection</strong> audio-guides</li>
</ol>
<h4 id="page-de-selection">Page de sélection</h4>
<p><strong>Affichage</strong> :</p>
<pre><code>📍 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 │
└─────────────────────────────────┘
</code></pre>
<h4 id="interface-audio-guide">Interface audio-guide</h4>
<p><strong>Après sélection</strong> :</p>
<pre><code>🎨 Visite complète • Musée du Louvre
Piste actuelle : 2/12
&quot;La Joconde - Histoire et mystères&quot;
[████████────────────] 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
</code></pre>
<p><strong>Navigation</strong> :</p>
<table>
<thead>
<tr>
<th>Action</th>
<th>Geste</th>
<th>Effet</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Séquence suivante</strong></td>
<td>Tap "Suivant" ou commande vocale "Suivant"</td>
<td>Passe à séquence N+1</td>
</tr>
<tr>
<td><strong>Séquence précédente</strong></td>
<td>Tap "Précédent" ou commande vocale "Précédent"</td>
<td>Revient à séquence N-1</td>
</tr>
<tr>
<td><strong>Saut direct</strong></td>
<td>Tap séquence dans liste</td>
<td>Lecture séquence choisie</td>
</tr>
<tr>
<td><strong>Pause</strong></td>
<td>Tap bouton pause</td>
<td>Met en pause, reprise position exacte</td>
</tr>
<tr>
<td><strong>Quitter</strong></td>
<td>Tap "×"</td>
<td>Sauvegarde progression, sortie guide</td>
</tr>
</tbody>
</table>
<p><strong>Guidage vocal automatique</strong> :
- 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 (&gt;50m de la prochaine pièce) : "Vous vous éloignez de la prochaine étape. Consultez le plan."</p>
<p><strong>Sauvegarde progression</strong> :
- 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</p>
<p><strong>Création audio-guide multiséquence</strong> :</p>
<p><strong>Processus créateur</strong> :
1. Créateur upload <strong>plusieurs fichiers audio</strong> (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 <strong>point GPS unique</strong> pour tout le guide (centre du lieu)
5. Métadonnées : durée totale calculée automatiquement</p>
<p><strong>Format stockage</strong> :</p>
<pre><code class="language-json">{
&quot;guide_id&quot;: &quot;abc123&quot;,
&quot;title&quot;: &quot;Visite complète Musée du Louvre&quot;,
&quot;location&quot;: {&quot;lat&quot;: 48.8606, &quot;lon&quot;: 2.3376, &quot;radius&quot;: 200},
&quot;sequences&quot;: [
{
&quot;sequence_number&quot;: 1,
&quot;title&quot;: &quot;Introduction et architecture&quot;,
&quot;audio_url&quot;: &quot;https://cdn.../seq1.mp3&quot;,
&quot;duration_seconds&quot;: 180
},
{
&quot;sequence_number&quot;: 2,
&quot;title&quot;: &quot;La Joconde - Histoire et mystères&quot;,
&quot;audio_url&quot;: &quot;https://cdn.../seq2.mp3&quot;,
&quot;duration_seconds&quot;: 410
},
...
],
&quot;total_duration_seconds&quot;: 2700,
&quot;creator_id&quot;: &quot;creator_xyz&quot;
}
</code></pre>
<p><strong>Justification</strong> :
- <strong>UX piéton</strong> : navigation tactile adaptée (pas de commandes volant)
- <strong>Autonomie</strong> : utilisateur maître de son rythme (pas d'enchaînement forcé)
- <strong>Choix</strong> : plusieurs guides = diversité styles (famille, expert, rapide)
- <strong>Engagement</strong> : sauvegarde progression = incitation terminer
- <strong>Coût</strong> : réutilise infra contenu standard (juste métadonnées séquences)</p>
<hr />
<h3 id="84-limites-et-desabonnement">8.4 Limites et désabonnement</h3>
<p><strong>Décision</strong> : 200 abonnements max + désabonnement -5% jauges</p>
<p><strong>Nombre maximum d'abonnements</strong> :
- <strong>200 créateurs maximum</strong> par utilisateur
- Raisons :
- <strong>Évite spam</strong> : au-delà de 200, notifications ingérables
- <strong>Usage réaliste</strong> : 200 créateurs = déjà énorme (vs 100-150 sur YouTube/Twitter)
- <strong>Performance</strong> : requêtes SQL optimisées (index sur 200 max)</p>
<p><strong>Si limite atteinte</strong> :
- 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 ?"</p>
<p><strong>Abonnement initial</strong> :
- Impact : <strong>+5% toutes jauges tags du créateur</strong> (défini en <a href="#../adr/010-commandes-volant">ADR-010</a>)
- Action : Bouton "S'abonner" dans profil créateur (interface mobile)
- Immédiat à l'action</p>
<p><strong>Désabonnement</strong> :
- Impact : <strong>-5% toutes jauges tags du créateur</strong> (symétrique)
- Action : Bouton "Se désabonner" dans profil créateur
- Immédiat à l'action
- Pas de confirmation (action réversible)</p>
<p><strong>Exemple</strong> :</p>
<pre><code>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%)
</code></pre>
<p><strong>Gestion multi-tags</strong> :
- Si créateur a 3 tags → <strong>+5% sur chacun des 3 tags</strong>
- Logique : abonnement = signal fort d'affinité à TOUS les sujets du créateur</p>
<p><strong>Abonnements réciproques</strong> :
- ❌ <strong>Pas d'abonnement mutuel visible</strong>
- Créateur ne voit pas qui est abonné (privacy)
- Créateur voit uniquement : nombre total abonnés (métrique globale)</p>
<p><strong>Justification</strong> :
- <strong>Limite 200</strong> : équilibre entre liberté et gestion spam
- <strong>Symétrie +5%/-5%</strong> : cohérence mathématique, prévisibilité
- <strong>Privacy</strong> : pas de liste publique abonnés (évite stalking)
- <strong>Coût</strong> : table abonnements PostgreSQL standard</p>
<hr />
<h2 id="recapitulatif-section-8">Récapitulatif Section 8</h2>
<div style="page-break-after: always;"></div>
<h2 id="9-monetisation-createurs">9. Monétisation créateurs</h2>
<h3 id="91-pourboires">9.1 Pourboires</h3>
<p><strong>Décision</strong> : ❌ Fonctionnalité abandonnée pour le MVP</p>
<p><strong>Raisons</strong> :
- 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</p>
<p><strong>Post-MVP</strong> : Possible réintégration avec crypto (Bitcoin/Lightning Network) si législation UE l'autorise clairement (régulation MiCA en cours).</p>
<hr />
<h3 id="92-conditions-dactivation-de-la-monetisation">9.2 Conditions d'activation de la monétisation</h3>
<p><strong>Décision</strong> : 5 critères cumulatifs obligatoires</p>
<table>
<thead>
<tr>
<th>Critère</th>
<th>Seuil</th>
<th>Justification</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Ancienneté</strong></td>
<td>Compte créé depuis ≥ 3 mois</td>
<td>Anti-fraude : temps de détecter comportements suspects</td>
</tr>
<tr>
<td><strong>Popularité</strong></td>
<td>≥ 500 abonnés</td>
<td>Garantit audience réelle et engagée</td>
</tr>
<tr>
<td><strong>Engagement</strong></td>
<td>≥ 10 000 écoutes complètes cumulées</td>
<td>Créateurs produisant du contenu de qualité</td>
</tr>
<tr>
<td><strong>Fiabilité</strong></td>
<td>Aucun strike actif, 0 contenu modéré dans les 6 derniers mois</td>
<td>Historique propre requis</td>
</tr>
<tr>
<td><strong>Régularité</strong></td>
<td>≥ 5 contenus publiés dans les 90 derniers jours</td>
<td>Activité constante</td>
</tr>
</tbody>
</table>
<p><strong>Vérification</strong> : Automatique via requêtes SQL lors de la demande d'activation</p>
<p><strong>Affichage</strong> :
- 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</p>
<p><strong>Justification</strong> :
- <strong>Anti-fraude</strong> : Le délai de 3 mois permet de détecter les comptes suspects
- <strong>Qualité</strong> : Seuls les créateurs sérieux avec audience réelle sont monétisés
- <strong>Coût administratif</strong> : Réduit le nombre de comptes à gérer (KYC, comptabilité, virements)
- <strong>Légitimité</strong> : Audience organique prouvée</p>
<hr />
<h3 id="93-kyc-know-your-customer-et-inscription">9.3 KYC (Know Your Customer) et inscription</h3>
<p><strong>Décision</strong> : Statut juridique professionnel obligatoire</p>
<p><strong>Statuts acceptés</strong> :
- Auto-entrepreneur (micro-BNC pour artistes/créateurs de contenu)
- SARL/SAS/SASU (sociétés)</p>
<p><strong>Documents requis</strong> :</p>
<table>
<thead>
<tr>
<th>Document</th>
<th>Obligatoire</th>
<th>Format</th>
<th>Validité</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>SIRET</strong></td>
<td>✅</td>
<td>14 chiffres</td>
<td>Permanent</td>
</tr>
<tr>
<td><strong>RIB professionnel</strong></td>
<td>✅</td>
<td>IBAN FR</td>
<td>Permanent</td>
</tr>
<tr>
<td><strong>Pièce d'identité</strong></td>
<td>✅</td>
<td>CNI/Passeport</td>
<td>En cours de validité</td>
</tr>
<tr>
<td><strong>Numéro TVA intracommunautaire</strong></td>
<td>⚠️ Si applicable</td>
<td>FR + 11 chiffres</td>
<td>Permanent</td>
</tr>
<tr>
<td><strong>Kbis &lt;3 mois</strong></td>
<td>⚠️ Si société</td>
<td>PDF</td>
<td>&lt;3 mois</td>
</tr>
</tbody>
</table>
<p><strong>Vérification</strong> : Via Mangopay (KYC intégré + vérification bancaire)</p>
<p><strong>Délai</strong> : 24-72h si documents conformes</p>
<p><strong>Rejet possible si</strong> :
- Documents invalides/illisibles
- Identité ne correspond pas au compte RoadWave
- Liste noire anti-blanchiment (vérification automatique Mangopay)
- RIB non professionnel (particulier)</p>
<p><strong>Base légale</strong> :
- <strong>Conformité fiscale</strong> : L'État français impose déclaration revenus &gt;1200€/an (DAS2)
- <strong>Anti-blanchiment</strong> : Directive EU 2018/843 (5ème directive LCB-FT)
- <strong>RGPD</strong> : Données hébergées EU via Mangopay (conforme)</p>
<p><strong>Justification</strong> :
- <strong>Responsabilité légale</strong> : RoadWave doit pouvoir prouver identité réelle créateurs monétisés
- <strong>Automatisation</strong> : Mangopay gère tout (KYC, vérifications, conformité, e-wallets)
- <strong>KYC gratuit</strong> : inclus dans l'offre Mangopay (vs 1.20€ chez Stripe)
- <strong>Souveraineté EU</strong> : Mangopay est européen (France/Luxembourg), régulé ACPR</p>
<hr />
<h3 id="94-sources-de-revenus-createurs">9.4 Sources de revenus créateurs</h3>
<h4 id="a-publicites-utilisateurs-gratuits">A) Publicités (utilisateurs gratuits)</h4>
<p><strong>Formule</strong> : <strong>3€ / 1000 écoutes complètes</strong> (CPM créateur)</p>
<p><strong>Répartition économique</strong> :</p>
<pre><code>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&amp;D : ~10-15€
└─ Marge opérationnelle : ~10-15€
</code></pre>
<p><strong>Exemple concret</strong> :
- 10 000 écoutes/mois → créateur touche <strong>30€</strong>
- 50 000 écoutes/mois → créateur touche <strong>150€</strong>
- 100 000 écoutes/mois → créateur touche <strong>300€</strong></p>
<p><strong>Comparaison industrie</strong> :
- YouTube : 3-5€/1000 vues
- Spotify : 3-4€/1000 écoutes
- RoadWave : 3€/1000 écoutes (aligné)</p>
<p><strong>Règles comptabilisation</strong> :
- ✅ É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)</p>
<hr />
<h4 id="b-abonnes-premium">B) Abonnés Premium</h4>
<p><strong>Formule</strong> : <strong>70% au créateur, 30% à la plateforme</strong></p>
<p><strong>Répartition proportionnelle au temps d'écoute effectif</strong> :</p>
<pre><code>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€
</code></pre>
<p><strong>Calcul technique</strong> :</p>
<pre><code class="language-sql">-- 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;
</code></pre>
<p><strong>Comparaison industrie</strong> :
- YouTube Premium : 70/30
- Spotify : 70/30
- Apple Music : 52/48 (moins avantageux)
- RoadWave : 70/30 (standard)</p>
<p><strong>Justification</strong> :
- <strong>Standard industrie</strong> : ratio équitable éprouvé
- <strong>Incitation qualité</strong> : créateurs les plus écoutés gagnent plus
- <strong>Équité</strong> : pas de "winner takes all", chaque créateur écouté reçoit sa part
- <strong>Marge plateforme</strong> : 30% couvre absence revenus pub sur Premium</p>
<hr />
<h3 id="95-paiement-des-createurs">9.5 Paiement des créateurs</h3>
<p><strong>Seuil minimum</strong> : 50€</p>
<ul>
<li>En dessous → solde reporté mois suivant</li>
<li>Évite frais bancaires sur micro-sommes</li>
<li>Standard industrie (YouTube/Twitch/Spotify = 50-100€)</li>
</ul>
<p><strong>Fréquence</strong> : Mensuelle</p>
<table>
<thead>
<tr>
<th>Date</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Dernier jour du mois</strong> (ex: 31 janvier)</td>
<td>Calcul revenus du mois via SQL</td>
</tr>
<tr>
<td><strong>1-14 du mois suivant</strong></td>
<td>Traitement contestations/fraudes éventuelles</td>
</tr>
<tr>
<td><strong>15 du mois suivant</strong> (ex: 15 février)</td>
<td>Virement SEPA via Mangopay (Payout)</td>
</tr>
<tr>
<td><strong>16-18 du mois suivant</strong></td>
<td>Réception virement (1-3 jours ouvrés SEPA)</td>
</tr>
</tbody>
</table>
<p><strong>Virement via Mangopay</strong> :
- SEPA pour comptes EU (gratuit, 1-3 jours)
- Virement international hors EU (frais variables selon pays, rare en pratique)
- <strong>E-wallets automatiques</strong> : chaque créateur possède un wallet Mangopay où ses revenus sont transférés automatiquement</p>
<p><strong>Tableau de bord créateur</strong> (temps réel) :</p>
<table>
<thead>
<tr>
<th>Métrique</th>
<th>Description</th>
<th>Mise à jour</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Revenus pub</strong></td>
<td>Écoutes × CPM</td>
<td>Temps réel</td>
</tr>
<tr>
<td><strong>Revenus premium</strong></td>
<td>Abonnés actifs × ratio écoute</td>
<td>Temps réel</td>
</tr>
<tr>
<td><strong>Solde disponible</strong></td>
<td>Total revenus mois en cours</td>
<td>Temps réel</td>
</tr>
<tr>
<td><strong>Solde en attente</strong></td>
<td>Revenus mois précédent (paiement le 15)</td>
<td>Figé fin de mois</td>
</tr>
<tr>
<td><strong>Historique virements</strong></td>
<td>Liste des paiements reçus</td>
<td>Permanent</td>
</tr>
<tr>
<td><strong>Export comptable CSV</strong></td>
<td>Données pour expert-comptable</td>
<td>Téléchargement</td>
</tr>
</tbody>
</table>
<p><strong>Gestion échecs virement</strong> :
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)</p>
<hr />
<h3 id="96-contenus-premium-exclusifs">9.6 Contenus Premium exclusifs</h3>
<p><strong>Décision</strong> : Créateur décide individuellement pour chaque contenu</p>
<p><strong>Fonctionnement</strong> :
- Toggle "Réservé Premium" lors création/édition contenu
- <strong>Aucune limite imposée</strong> : créateur peut mettre 0%, 50% ou 100% en premium
- Badge 👑 visible sur interface utilisateur</p>
<p><strong>Comportement utilisateurs gratuits</strong> :
- 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"</p>
<p><strong>Comportement algorithme</strong> :
- Contenus premium inclus dans recommandations
- Si user gratuit → contenu skippé automatiquement (ne consomme pas de slot)
- Si user premium → diffusé normalement</p>
<p><strong>Métadonnées</strong> :
- Champ <code>is_premium</code> (boolean) en base
- Index sur ce champ pour requêtes rapides
- Cache Redis : <code>content:{id}:premium</code> (TTL 1h)</p>
<p><strong>Justification</strong> :
- <strong>Liberté créateur</strong> : chaque créateur choisit sa stratégie (freemium, tout gratuit, tout premium)
- <strong>Incitation Premium</strong> : contenu exclusif = argument fort pour s'abonner
- <strong>Équité</strong> : un petit créateur peut tout mettre en premium, un gros peut tout offrir gratuitement</p>
<hr />
<h3 id="97-obligations-fiscales">9.7 Obligations fiscales</h3>
<p><strong>RoadWave génère automatiquement</strong> :</p>
<table>
<thead>
<tr>
<th>Document</th>
<th>Fréquence</th>
<th>Destinataire</th>
<th>Base légale</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Relevé mensuel PDF</strong></td>
<td>Chaque mois</td>
<td>Créateur</td>
<td>Transparence</td>
</tr>
<tr>
<td><strong>Export CSV comptable</strong></td>
<td>À la demande</td>
<td>Créateur + expert-comptable</td>
<td>Facilitation déclarations</td>
</tr>
<tr>
<td><strong>DAS2 annuel</strong></td>
<td>Si &gt;1200€/an</td>
<td>Impôts (DGFIP)</td>
<td>Obligation légale France</td>
</tr>
</tbody>
</table>
<p><strong>Créateur responsable de</strong> :
- 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 <strong>10 ans</strong> (obligation légale comptable)</p>
<p><strong>Mangopay transmet automatiquement</strong> :
- Données aux autorités fiscales EU via <strong>DAC7</strong> (directive 2021/514)
- Justificatif de chaque virement (preuve bancaire pour comptabilité créateur)</p>
<p><strong>Exemple DAS2</strong> :</p>
<pre><code>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
</code></pre>
<p><strong>Justification</strong> :
- <strong>Conformité légale</strong> : RoadWave doit déclarer revenus versés (DAS2, DAC7)
- <strong>Responsabilité fiscale</strong> : Le créateur reste responsable de sa déclaration (impossible de gérer pour lui)
- <strong>Automatisation</strong> : Minimise charge administrative côtés créateur et plateforme</p>
<hr />
<h3 id="98-desactivation-et-suspension-monetisation">9.8 Désactivation et suspension monétisation</h3>
<p><strong>Créateur peut</strong> :
- Désactiver temporairement (vacances, pause création)
- Réactiver sans refaire KYC si données à jour (&lt;2 ans)
- Solde conservé pendant désactivation</p>
<p><strong>Plateforme suspend automatiquement si</strong> :</p>
<table>
<thead>
<tr>
<th>Motif</th>
<th>Action</th>
<th>Réversible</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Strike 3+ actif</strong></td>
<td>Suspension immédiate</td>
<td>Oui, après résolution strikes</td>
</tr>
<tr>
<td><strong>Compte bancaire invalide</strong></td>
<td>Suspension après 3 échecs virement</td>
<td>Oui, après mise à jour RIB</td>
</tr>
<tr>
<td><strong>Documents KYC expirés</strong></td>
<td>Suspension avec préavis 30j</td>
<td>Oui, après renouvellement docs</td>
</tr>
<tr>
<td><strong>Fraude détectée</strong></td>
<td>Suspension immédiate + enquête</td>
<td>Cas par cas</td>
</tr>
</tbody>
</table>
<p><strong>Suppression définitive si</strong> :
- Demande du créateur (solde versé sous 30 jours)
- Inactivité 24 mois + solde &lt;50€ (purge RGPD)
- Ban définitif compte (Strike 4)</p>
<p><strong>Notification</strong> :
- Email + in-app pour toute suspension
- Raison explicite fournie
- Procédure de réactivation indiquée</p>
<p><strong>Justification</strong> :
- <strong>Flexibilité</strong> : créateur peut faire pause sans perdre statut
- <strong>Sécurité</strong> : plateforme doit pouvoir suspendre en cas problème légal/technique
- <strong>RGPD</strong> : suppression auto données inactives après délai raisonnable</p>
<hr />
<h2 id="recapitulatif-section-9">Récapitulatif Section 9</h2>
<div style="page-break-after: always;"></div>
<h2 id="10-premium_1">10. Premium</h2>
<h3 id="101-offre-et-tarification">10.1 Offre et tarification</h3>
<p><strong>Décision</strong> : Deux formules sans essai gratuit</p>
<table>
<thead>
<tr>
<th>Formule</th>
<th>Prix</th>
<th>Économie</th>
<th>Prix effectif</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Mensuel</strong></td>
<td>4.99€/mois</td>
<td>-</td>
<td>4.99€/mois</td>
</tr>
<tr>
<td><strong>Annuel</strong></td>
<td>49.99€/an</td>
<td>2 mois offerts</td>
<td>4.16€/mois</td>
</tr>
</tbody>
</table>
<p><strong>❌ Pas d'essai gratuit</strong></p>
<p><strong>Raisons</strong> :
- <strong>Anti-abus vacances</strong> : évite inscriptions opportunistes (essai 14j avant road trip vacances, puis annulation)
- <strong>Protection revenus créateurs</strong> : les écoutes Premium rémunèrent créateurs dès jour 1
- <strong>Simplicité</strong> : pas de gestion période trial + conversion
- <strong>Engagement</strong> : utilisateur qui paie dès début = plus engagé</p>
<p><strong>❌ Pas de partage familial (MVP)</strong></p>
<p><strong>Raisons</strong> :
- 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)
- <strong>Post-MVP</strong> : Si forte demande, offre "Famille" à 9.99€/mois pour 5 comptes</p>
<p><strong>Justification tarif</strong> :
- <strong>Aligné marché bas</strong> : Spotify = 10.99€, YouTube Premium = 11.99€, Apple Music = 10.99€
- <strong>Prix accessible</strong> : cible conducteurs quotidiens (budget raisonnable)
- <strong>Incitation annuel</strong> : 2 mois offerts = engagement long terme + réduction churn</p>
<hr />
<h3 id="102-multi-devices-et-detection-simultanee">10.2 Multi-devices et détection simultanée</h3>
<p><strong>Décision</strong> : 1 seul stream actif par compte à tout moment</p>
<p><strong>Détection connexion simultanée</strong> :</p>
<pre><code>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 : &quot;Lecture interrompue : votre compte est utilisé sur un autre appareil&quot;
→ Lecture démarre iPad
</code></pre>
<p><strong>Implémentation technique</strong> :</p>
<pre><code>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
</code></pre>
<p><strong>Exceptions</strong> :
- Contenus téléchargés (offline) ne comptent pas comme stream actif
- Transition rapide device (&lt;10s) tolérée (changement voiture → maison)</p>
<p><strong>Justification</strong> :
- <strong>Anti-partage compte</strong> : empêche 2 personnes d'utiliser même compte Premium
- <strong>Protection revenus créateurs</strong> : 1 abonnement = 1 personne = 1 écoute
- <strong>UX claire</strong> : message explicite, pas de coupure brutale</p>
<hr />
<h3 id="103-contenus-exclusifs-premium">10.3 Contenus exclusifs Premium</h3>
<p><strong>Décision</strong> : Créateur décide (déjà couvert section 9.6)</p>
<p><strong>Rappel règles</strong> :
- Toggle "Réservé Premium" par contenu
- Aucune limite de ratio gratuit/premium
- Badge 👑 visible
- Users gratuits : lecture bloquée avec CTA "Passez Premium"</p>
<p><strong>Impact algorithme</strong> :
- Contenus premium inclus dans recommandations
- Si user gratuit → skip automatique (ne consomme pas slot)
- Si user premium → diffusé normalement selon score</p>
<hr />
<h3 id="104-avantages-premium">10.4 Avantages Premium</h3>
<p><strong>Inclus dans l'abonnement</strong> :</p>
<table>
<thead>
<tr>
<th>Avantage</th>
<th>Gratuit</th>
<th>Premium</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Publicités</strong></td>
<td>1/5 contenus</td>
<td>0 (aucune)</td>
</tr>
<tr>
<td><strong>Contenus exclusifs</strong></td>
<td>❌ Bloqués</td>
<td>✅ Accès complet</td>
</tr>
<tr>
<td><strong>Qualité audio</strong></td>
<td>48 kbps Opus</td>
<td>64 kbps Opus</td>
</tr>
<tr>
<td><strong>Mode offline</strong></td>
<td>50 contenus max</td>
<td>Illimité</td>
</tr>
<tr>
<td><strong>Historique écoute</strong></td>
<td>100 derniers</td>
<td>Illimité</td>
</tr>
</tbody>
</table>
<p><strong>Qualité audio</strong> :
- Gratuit : 48 kbps Opus (~20 MB/h) = très correct pour voix
- Premium : 64 kbps Opus (~30 MB/h) = excellente qualité</p>
<p><strong>Justification différences</strong> :
- <strong>0 pub</strong> = argument principal (confort écoute)
- <strong>Qualité audio</strong> = avantage tangible audiophiles
- <strong>Offline illimité</strong> = use case road trips longs
- <strong>Pas d'over-engineering</strong> : pas de badges cosmétiques, fonctionnalités sociales, etc. (focus essentiel)</p>
<hr />
<h3 id="105-gestion-abonnement">10.5 Gestion abonnement</h3>
<p><strong>Souscription</strong> :</p>
<table>
<thead>
<tr>
<th>Canal</th>
<th>Prestataire</th>
<th>Prix</th>
<th>Commission</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Web (desktop/mobile)</strong></td>
<td>Mangopay</td>
<td>4.99€</td>
<td>1.8% + 0.18€ = 0.27€</td>
</tr>
<tr>
<td><strong>iOS App</strong></td>
<td>Apple In-App Purchase</td>
<td>5.99€</td>
<td>30% (Apple)</td>
</tr>
<tr>
<td><strong>Android App</strong></td>
<td>Google Play Billing</td>
<td>5.99€</td>
<td>30% (Google)</td>
</tr>
</tbody>
</table>
<p><strong>Majoration mobile (5.99€)</strong> :
- Apple/Google prennent 30% de commission
- RoadWave majore prix de 20% pour compenser
- <strong>Incitation web</strong> : Email aux users "Abonnez-vous sur roadwave.com pour 4.99€/mois" (38% moins cher en frais !)</p>
<p><strong>Renouvellement automatique</strong> :
- Email rappel <strong>7 jours avant</strong> renouvellement
- Email confirmation <strong>après</strong> renouvellement réussi
- Retry automatique si échec paiement (3 tentatives sur 7 jours)
- Annulation automatique après 3 échecs</p>
<p><strong>Annulation</strong> :
- Self-service dans Settings app : "Abonnement &gt; Annuler"
- Accès Premium maintenu jusqu'à <strong>fin période payée</strong>
- Pas de remboursement prorata (standard industrie)
- Email confirmation annulation avec date fin d'accès</p>
<p><strong>Réabonnement</strong> :
- Possibilité immédiate
- ❌ Pas de nouvelle période d'essai (pas d'essai du tout)</p>
<p><strong>Architecture données</strong> :</p>
<pre><code class="language-sql">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()
);
</code></pre>
<p><strong>Vérification Premium en temps réel</strong> :</p>
<pre><code>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
</code></pre>
<hr />
<h2 id="recapitulatif-section-10">Récapitulatif Section 10</h2>
<div style="page-break-after: always;"></div>
<h2 id="11-mode-offline_1">11. Mode offline</h2>
<h3 id="111-telechargement">11.1 Téléchargement</h3>
<p><strong>Zone géographique</strong> : Choix manuel utilisateur</p>
<p><strong>Options prédéfinies</strong> :
- "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.</p>
<p><strong>Nombre de contenus téléchargeables</strong> :</p>
<table>
<thead>
<tr>
<th>Statut</th>
<th>Limite</th>
<th>Affichage</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Gratuit</strong></td>
<td>50 contenus max</td>
<td>"12/50 contenus téléchargés"</td>
</tr>
<tr>
<td><strong>Premium</strong></td>
<td>Illimité</td>
<td>"245 contenus (3.2 GB)"</td>
</tr>
</tbody>
</table>
<p><strong>Calcul temps disponible</strong> :
- 50 contenus × 5 min moyenne = 250 min = <strong>4h d'écoute</strong> (suffisant pour gratuits)
- Premium illimité = limité uniquement par espace disque device</p>
<p><strong>Connexion WiFi/Mobile</strong> :</p>
<p><strong>Par défaut</strong> : WiFi uniquement</p>
<p><strong>Sur données mobiles</strong> :
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 <strong>X MB</strong>. Continuer ?"
4. Boutons : "Attendre WiFi" / "Continuer"</p>
<p><strong>Calcul estimation</strong> :</p>
<pre><code>Nombre contenus × durée moyenne × bitrate qualité
Exemple : 20 contenus × 5 min × 48 kbps = ~72 MB
</code></pre>
<p><strong>Qualité audio téléchargement</strong> :</p>
<table>
<thead>
<tr>
<th>Qualité</th>
<th>Bitrate</th>
<th>Taille</th>
<th>Disponibilité</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Basse</strong></td>
<td>24 kbps</td>
<td>~10 MB/h</td>
<td>Gratuit + Premium</td>
</tr>
<tr>
<td><strong>Standard</strong></td>
<td>48 kbps</td>
<td>~20 MB/h</td>
<td>Gratuit + Premium (défaut)</td>
</tr>
<tr>
<td><strong>Haute</strong></td>
<td>64 kbps</td>
<td>~30 MB/h</td>
<td><strong>Premium uniquement</strong></td>
</tr>
</tbody>
</table>
<p><strong>Justification</strong> :
- 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é</p>
<hr />
<h3 id="112-validite-et-renouvellement">11.2 Validité et renouvellement</h3>
<p><strong>Durée de validité</strong> : 30 jours après téléchargement</p>
<p><strong>Standard industrie</strong> :
- Spotify : 30 jours
- YouTube Music : 30 jours
- Deezer : 30 jours</p>
<p><strong>Renouvellement automatique</strong> :</p>
<pre><code>App détecte WiFi + contenus &gt;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)
</code></pre>
<p><strong>Notification avant expiration</strong> :
- <strong>J-3</strong> : "X contenus expirent dans 3 jours. Connectez-vous en WiFi pour les renouveler"
- <strong>J-0</strong> : Suppression automatique
- <strong>J+0</strong> : Toast "15 contenus expirés ont été supprimés"</p>
<p><strong>Justification</strong> :
- <strong>Force reconnexion</strong> : vérifier abonnement actif, contenus légaux
- <strong>Évite stockage obsolète</strong> : contenus supprimés/modérés ne restent pas
- <strong>UX transparente</strong> : renouvellement silencieux si WiFi régulier</p>
<hr />
<h3 id="113-synchronisation-actions-offline">11.3 Synchronisation actions offline</h3>
<p><strong>Actions stockées localement (SQLite)</strong> :
- Likes/unlikes
- Abonnements/désabonnements
- Signalements
- Progression audio-guides</p>
<p><strong>Sync automatique à la reconnexion</strong> :</p>
<pre><code>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 : &quot;3 likes et 1 abonnement synchronisés&quot;
</code></pre>
<p><strong>Gestion erreurs sync</strong> :
- Si échec après 3 tentatives → notification : "Impossible de synchroniser. Réessayez plus tard"
- Actions conservées jusqu'à sync réussie (pas de perte)
- <strong>Rétention max 7 jours</strong> : après = purge (évite queue infinie)</p>
<p><strong>Conflits contenus supprimés</strong> :</p>
<pre><code>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 : &quot;1 contenu téléchargé a été retiré (violation règles)&quot;
</code></pre>
<p><strong>Justification</strong> :
- <strong>Pas de conflit possible</strong> : actions unilatérales user (likes/abonnements)
- <strong>UX fluide</strong> : pas de blocage offline
- <strong>Batch = économie</strong> : requêtes HTTP groupées
- <strong>Conformité modération</strong> : contenu illégal disparaît même offline</p>
<hr />
<h2 id="recapitulatif-section-11">Récapitulatif Section 11</h2>
<table>
<thead>
<tr>
<th>Aspect</th>
<th>Décision</th>
<th>Valeur</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Zone téléchargement</strong></td>
<td>Choix</td>
<td>Manuel (autour/ville/département/région/recherche)</td>
</tr>
<tr>
<td><strong>Limite gratuit</strong></td>
<td>Contenus</td>
<td>50 max</td>
</tr>
<tr>
<td><strong>Limite Premium</strong></td>
<td>Contenus</td>
<td>Illimité (espace disque)</td>
</tr>
<tr>
<td><strong>Connexion</strong></td>
<td>Par défaut</td>
<td>WiFi (mobile avec confirmation)</td>
</tr>
<tr>
<td><strong>Qualité Standard</strong></td>
<td>Bitrate</td>
<td>48 kbps Opus</td>
</tr>
<tr>
<td><strong>Qualité Haute</strong></td>
<td>Bitrate</td>
<td>64 kbps (Premium uniquement)</td>
</tr>
<tr>
<td><strong>Validité</strong></td>
<td>Durée</td>
<td>30 jours</td>
</tr>
<tr>
<td><strong>Renouvellement</strong></td>
<td>Mode</td>
<td>Automatique si WiFi</td>
</tr>
<tr>
<td><strong>Notification expiration</strong></td>
<td>Délai</td>
<td>J-3</td>
</tr>
<tr>
<td><strong>Sync actions</strong></td>
<td>Mode</td>
<td>Batch automatique reconnexion</td>
</tr>
<tr>
<td><strong>Rétention queue</strong></td>
<td>Durée</td>
<td>7 jours max</td>
</tr>
</tbody>
</table>
<hr />
<div style="page-break-after: always;"></div>
<h2 id="12-gestion-des-erreurs_1">12. Gestion des erreurs</h2>
<h3 id="121-aucun-contenu-disponible">12.1 Aucun contenu disponible</h3>
<p><strong>Stratégie</strong> : Élargissement automatique progressif</p>
<p><strong>Flow</strong> :</p>
<pre><code>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)
</code></pre>
<p><strong>Messages adaptatifs</strong> :</p>
<table>
<thead>
<tr>
<th>Cas</th>
<th>Message</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Trouvé à 100 km</strong></td>
<td>"Aucun contenu dans votre zone immédiate. Voici du contenu à proximité (100 km)"</td>
</tr>
<tr>
<td><strong>Trouvé département</strong></td>
<td>"Aucun contenu local disponible. Voici du contenu dans votre département"</td>
</tr>
<tr>
<td><strong>Contenu national</strong></td>
<td>"Aucun contenu local disponible. Voici du contenu national qui pourrait vous intéresser"</td>
</tr>
</tbody>
</table>
<p><strong>Justification</strong> :
- <strong>UX fluide</strong> : pas de message d'erreur bloquant "Aucun contenu"
- <strong>User ne reste jamais sans contenu</strong>
- <strong>Contenu national = filet de sécurité</strong> : actualités Le Monde, podcasts génériques</p>
<hr />
<h3 id="122-contenu-signalesupprime-pendant-lecoute">12.2 Contenu signalé/supprimé pendant l'écoute</h3>
<p><strong>Décision</strong> : Pas d'interruption brutale</p>
<p><strong>Flow</strong> :</p>
<pre><code>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 &quot;Précédent&quot; pour ce contenu
4. Passage automatique suivant après 2s
5. Toast notification discrète : &quot;Contenu précédent retiré (violation règles)&quot;
</code></pre>
<p><strong>Si tentative "Précédent" manuellement</strong> :
- Message : "Ce contenu n'est plus disponible"
- Retour au contenu actuel</p>
<p><strong>Justification</strong> :
- <strong>Sécurité routière</strong> : pas d'interruption brutale pendant conduite
- <strong>User informé mais pas alarmé</strong> : message discret
- <strong>Empêche réécoute</strong> : contenu modéré inaccessible</p>
<hr />
<h3 id="123-perte-de-reseau">12.3 Perte de réseau</h3>
<p><strong>Buffer adaptatif</strong> (cf. TECHNICAL.md) :</p>
<table>
<thead>
<tr>
<th>Réseau</th>
<th>Buffer min</th>
<th>Buffer cible</th>
<th>Buffer max</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>WiFi</strong></td>
<td>5s</td>
<td>30s</td>
<td>120s</td>
</tr>
<tr>
<td><strong>4G/5G</strong></td>
<td>10s</td>
<td>45s</td>
<td>120s</td>
</tr>
<tr>
<td><strong>3G</strong></td>
<td>30s</td>
<td>90s</td>
<td>300s</td>
</tr>
</tbody>
</table>
<p><strong>Comportement détaillé</strong> :</p>
<p><strong>Phase 1 : Connexion instable</strong> (latence élevée, paquets perdus)
- Aucun message immédiat
- Lecture continue sur buffer
- Si &gt; 10s latence : toast discret "Connexion instable"</p>
<p><strong>Phase 2 : Perte totale réseau</strong>
- Lecture continue jusqu'à épuisement buffer
- Toast : "Hors ligne, lecture sur buffer (30s restantes)"
- Compte à rebours visible</p>
<p><strong>Phase 3 : Buffer épuisé sans reconnexion</strong>
- Pause automatique
- Overlay : "Connexion perdue. Reconnexion en cours..."
- Retry automatique toutes les 5s (max 6 tentatives = 30s)</p>
<p><strong>Phase 4 : Basculement mode offline</strong> (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</p>
<p><strong>Reconnexion réussie</strong> :
- Reprise automatique lecture au point d'arrêt exact
- Toast : "Connexion rétablie"</p>
<p><strong>Justification</strong> :
- <strong>Expérience fluide zones blanches</strong> (tunnels, campagne)
- <strong>Buffer généreux</strong> : absorbe fluctuations réseau mobile
- <strong>Mode offline secours</strong> : si coupure prolongée</p>
<hr />
<h3 id="124-geolocalisation-desactivee">12.4 Géolocalisation désactivée</h3>
<p><strong>Mode dégradé automatique</strong></p>
<p><strong>Contenu disponible</strong> :</p>
<table>
<thead>
<tr>
<th>Type contenu</th>
<th>Disponible</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Contenu national</strong> (podcasts, actualités)</td>
<td>✅</td>
</tr>
<tr>
<td><strong>Contenu téléchargé</strong> (offline)</td>
<td>✅</td>
</tr>
<tr>
<td><strong>Contenus "Neutre"</strong> géographiquement</td>
<td>✅</td>
</tr>
<tr>
<td><strong>Contenu géolocalisé</strong> (Ancré/Contextuel)</td>
<td>❌</td>
</tr>
<tr>
<td><strong>Audio-guides</strong></td>
<td>❌</td>
</tr>
<tr>
<td><strong>Notifications push géo-déclenchées</strong></td>
<td>❌</td>
</tr>
</tbody>
</table>
<p><strong>Popup au lancement</strong> :
- <strong>Apparition</strong> : Premier lancement après refus géolocalisation
- <strong>Message</strong> : "RoadWave fonctionne mieux avec la géolocalisation activée. Sans elle, seul le contenu national sera disponible."
- <strong>Boutons</strong> :
- "Activer" → Redirection paramètres OS
- "Continuer sans" → Mode dégradé
- <strong>Checkbox</strong> : "Ne plus me demander"</p>
<p><strong>Banner permanent si refus</strong> :
- Bandeau haut écran : "Mode limité : géolocalisation désactivée. [Activer]"
- Pas intrusif mais rappel constant
- Disparaît si géolocalisation réactivée</p>
<p><strong>Justification</strong> :
- <strong>App reste fonctionnelle</strong> sans GPS (pas de blocage)
- <strong>Incitation forte</strong> à activer (meilleure UX)
- <strong>Respecte choix user</strong> (RGPD : consentement libre)</p>
<hr />
<h2 id="recapitulatif-section-12">Récapitulatif Section 12</h2>
<div style="page-break-after: always;"></div>
<h2 id="13-conformite-rgpd_1">13. Conformité RGPD</h2>
<h3 id="131-gestion-du-consentement">13.1 Gestion du consentement</h3>
<p><strong>Décision</strong> : Tarteaucitron.js + PostgreSQL backend</p>
<p><strong>Implémentation web</strong> :
- ✅ Tarteaucitron.js (opensource, self-hosted)
- ✅ Banner RGPD français, customisable
- ✅ Granularité : fonctionnel / analytique / marketing</p>
<p><strong>Implémentation backend</strong> :
- Table <code>user_consents</code> avec versioning
- Champs : user_id, consent_type, version, accepted, timestamp
- Historique complet conservé (preuve légale)</p>
<p><strong>Consentements requis</strong> :
- <strong>Géolocalisation précise</strong> : obligatoire (banner + permission OS)
- <strong>Analytics</strong> : optionnel (Matomo)
- <strong>Notifications push</strong> : optionnel (permission OS)</p>
<p><strong>Justification</strong> :
- Opensource, 0€, conformité RGPD garantie
- Historique backend = preuve légale en cas de contrôle
- Granularité conforme recommandations CNIL</p>
<hr />
<h3 id="132-anonymisation-des-donnees-gps">13.2 Anonymisation des données GPS</h3>
<p><strong>Décision</strong> : Geohash après 24h</p>
<p><strong>Processus</strong> :
1. Données précises conservées <strong>24h</strong> (recommandation personnalisée)
2. Après 24h : conversion en geohash précision 5 (~5km²)
3. Coordonnées originales supprimées définitivement</p>
<p><strong>Implémentation PostGIS</strong> :</p>
<pre><code class="language-sql">-- Job quotidien
UPDATE location_history
SET location = ST_SetSRID(ST_GeomFromGeoHash(ST_GeoHash(location::geography, 5)), 4326)::geography,
anonymized = true
WHERE created_at &lt; NOW() - INTERVAL '24 hours' AND anonymized = false;
</code></pre>
<p><strong>Exceptions</strong> :
- ✅ Historique personnel visible (liste trajets) : conservation intégrale tant que compte actif
- ❌ Analytics globales : uniquement geohash anonyme</p>
<p><strong>Justification</strong> :
- Vraie anonymisation RGPD (CNIL compliant)
- Permet analytics agrégées (heatmaps trafic)
- PostGIS natif, 0€</p>
<hr />
<h3 id="133-export-des-donnees-portabilite">13.3 Export des données (portabilité)</h3>
<p><strong>Décision</strong> : JSON + HTML + ZIP, génération asynchrone</p>
<p><strong>Contenu de l'export</strong> :</p>
<pre><code>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
</code></pre>
<p><strong>Données exportées</strong> :
- 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</p>
<p><strong>Processus</strong> :
1. Demande via paramètres compte
2. Génération asynchrone (worker background)
3. Email avec lien download (expire <strong>7 jours</strong>)
4. Délai : <strong>48h maximum</strong> (conformité RGPD)</p>
<p><strong>Limite</strong> :
- Maximum <strong>1 export/mois</strong> (anti-abus)</p>
<p><strong>Justification</strong> :
- Conformité article 20 RGPD (portabilité)
- Double format (human + machine)
- Worker asynchrone évite timeout</p>
<hr />
<h3 id="134-suppression-du-compte">13.4 Suppression du compte</h3>
<p><strong>Décision</strong> : Grace period 30j + anonymisation contenus</p>
<p><strong>Processus</strong> :
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</p>
<p><strong>Suppression effective</strong> :
- ✅ Compte utilisateur supprimé (données personnelles)
- ✅ Historique d'écoute supprimé
- ✅ GPS historique supprimé
- ✅ Sessions et tokens révoqués
- ⚠️ Contenus créés <strong>anonymisés</strong> (créateur = "Utilisateur supprimé")
- ⚠️ Likes et abonnements supprimés (mais compteurs préservés)</p>
<p><strong>Contenus conservés anonymement</strong> :
- Audio files (CDN)
- Métadonnées (titre, description, tags, géolocalisation)
- Statistiques d'écoute</p>
<p><strong>Justification</strong> :
- Grace period évite suppressions impulsives
- Anonymisation contenus = intérêt légitime communauté
- Conforme RGPD si créateur = donnée supprimée</p>
<hr />
<h3 id="135-mode-degrade-sans-gps-precis">13.5 Mode dégradé (sans GPS précis)</h3>
<p><strong>Décision</strong> : GeoIP par défaut, GPS optionnel</p>
<p><strong>Niveaux de précision</strong> :</p>
<table>
<thead>
<tr>
<th>Niveau</th>
<th>Technologie</th>
<th>Contenus accessibles</th>
<th>Consentement</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Pays</strong></td>
<td>Aucune géoloc</td>
<td>Contenus nationaux uniquement</td>
<td>❌ Non requis</td>
</tr>
<tr>
<td><strong>Ville</strong></td>
<td>GeoIP (MaxMind)</td>
<td>Contenus régionaux/ville</td>
<td>❌ Non requis</td>
</tr>
<tr>
<td><strong>Précis</strong></td>
<td>GPS</td>
<td>Tous contenus (hyperlocaux inclus)</td>
<td>✅ Requis</td>
</tr>
</tbody>
</table>
<p><strong>Implémentation</strong> :
- 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</p>
<p><strong>API GeoIP</strong> :
- MaxMind GeoLite2 (gratuit, self-hosted)
- Update DB mensuelle automatique
- Précision ~80% au niveau ville</p>
<p><strong>Justification</strong> :
- RGPD : pas de consentement requis pour GeoIP (pas de donnée personnelle)
- UX dégradée acceptable (contenus disponibles)
- Progressive disclosure (upgrade optionnel)</p>
<hr />
<h3 id="136-duree-de-conservation-des-donnees">13.6 Durée de conservation des données</h3>
<p><strong>Décision</strong> : 5 ans inactivité → purge automatique</p>
<p><strong>Règles</strong> :</p>
<table>
<thead>
<tr>
<th>Type de compte</th>
<th>Seuil inactivité</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Auditeur uniquement</strong></td>
<td>5 ans sans connexion</td>
<td>Suppression automatique</td>
</tr>
<tr>
<td><strong>Créateur avec contenus actifs</strong></td>
<td>Jamais (tant qu'écoutes)</td>
<td>Conservation indéfinie</td>
</tr>
<tr>
<td><strong>Créateur inactif</strong></td>
<td>5 ans sans connexion + 2 ans sans écoute</td>
<td>Suppression automatique</td>
</tr>
</tbody>
</table>
<p><strong>Notifications avant suppression</strong> :
- Email + push : <strong>90 jours</strong> avant
- Email + push : <strong>30 jours</strong> avant
- Email + push : <strong>7 jours</strong> avant
- Toute connexion = reset compteur inactivité</p>
<p><strong>Contenu conservé</strong> :
- Contenus créés par comptes supprimés (anonymisés) : conservation indéfinie</p>
<p><strong>Justification</strong> :
- Conformité principe minimisation RGPD
- 5 ans = équilibre raisonnable (standard industrie)
- Exception créateurs actifs = intérêt légitime plateforme</p>
<hr />
<h3 id="137-cookies-et-trackers-web">13.7 Cookies et trackers web</h3>
<p><strong>Décision</strong> : Matomo self-hosted, zéro cookie tiers</p>
<p><strong>Cookies utilisés</strong> :</p>
<table>
<thead>
<tr>
<th>Cookie</th>
<th>Type</th>
<th>Durée</th>
<th>Finalité</th>
<th>Consentement</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>session</code></td>
<td>Technique</td>
<td>30j</td>
<td>Authentification</td>
<td>❌ Non requis</td>
</tr>
<tr>
<td><code>refresh_token</code></td>
<td>Technique</td>
<td>30j</td>
<td>Session persistante</td>
<td>❌ Non requis</td>
</tr>
<tr>
<td><code>_pk_id</code></td>
<td>Analytique</td>
<td>13 mois</td>
<td>Matomo (IP anonyme)</td>
<td>✅ Requis</td>
</tr>
</tbody>
</table>
<p><strong>Analytics : Matomo self-hosted</strong> :
- 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)</p>
<p><strong>Trackers interdits</strong> :
- ❌ Google Analytics
- ❌ Facebook Pixel
- ❌ Hotjar, Mixpanel, etc.</p>
<p><strong>Justification</strong> :
- Souveraineté données (pas de transfert US)
- Conformité RGPD max (CNIL compatible)
- Matomo = opensource, 0€ infra</p>
<hr />
<h3 id="138-registre-des-traitements">13.8 Registre des traitements</h3>
<p><strong>Décision</strong> : Document Markdown versionné Git (MVP)</p>
<p><strong>Emplacement</strong> :
- <code>docs/rgpd/registre-traitements.md</code>
- Versionné Git (historique modifications)</p>
<p><strong>Contenu obligatoire par traitement</strong> :
- 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)</p>
<p><strong>Responsable</strong> :
- DPO / Fondateur
- Review trimestrielle obligatoire
- Update immédiate si nouveau traitement</p>
<p><strong>Migration future</strong> :
- Si &gt; 100K utilisateurs : interface admin PostgreSQL</p>
<p><strong>Justification</strong> :
- Obligation RGPD Article 30
- Markdown = simple, versionné, auditable
- 0€</p>
<hr />
<h3 id="139-notification-violations-de-donnees-breach">13.9 Notification violations de données (breach)</h3>
<p><strong>Décision</strong> : Monitoring + alertes + runbook</p>
<p><strong>Détection automatique</strong> :</p>
<table>
<thead>
<tr>
<th>Événement</th>
<th>Outil</th>
<th>Alerte</th>
</tr>
</thead>
<tbody>
<tr>
<td>Erreurs backend critiques</td>
<td>Sentry</td>
<td>Discord/Slack immédiat</td>
</tr>
<tr>
<td>Pic requêtes anormal</td>
<td>Grafana</td>
<td>Email équipe</td>
</tr>
<tr>
<td>Accès non autorisé DB</td>
<td>PostgreSQL logs</td>
<td>SMS fondateur</td>
</tr>
<tr>
<td>Authentification suspecte</td>
<td>Zitadel alerts</td>
<td>Email équipe</td>
</tr>
</tbody>
</table>
<p><strong>Procédure breach</strong> :
- Runbook : <code>docs/rgpd/procedure-breach.md</code>
- 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é</p>
<p><strong>Contact CNIL</strong> :
- Email pré-rédigé (template)
- Formulaire en ligne (account CNIL créé)</p>
<p><strong>Justification</strong> :
- Obligation RGPD Article 33 (notification 72h)
- Monitoring proactif évite découverte tardive
- Sentry gratuit &lt; 5K events/mois</p>
<hr />
<h3 id="1310-dpo-delegue-a-la-protection-des-donnees">13.10 DPO (Délégué à la Protection des Données)</h3>
<p><strong>Décision</strong> : Fondateur = DPO temporaire (MVP)</p>
<p><strong>Raison légale</strong> :
- Non obligatoire si :
- &lt; 250 employés
- Pas de traitement à grande échelle de données sensibles
- RoadWave : données localisation = sensible MAIS échelle MVP</p>
<p><strong>Formation</strong> :
- CNIL : formation gratuite en ligne (4h)
- Certification CNIL "Atelier RGPD" (gratuit)</p>
<p><strong>Contact</strong> :
- Email : dpo@roadwave.fr
- Publié dans CGU et mentions légales
- Délai réponse : <strong>1 mois</strong> (RGPD)</p>
<p><strong>Migration future</strong> :
- Si &gt; 100K utilisateurs : DPO externe mutualisé (~200€/mois)
- Ou recrutement DPO interne si &gt; 10 employés</p>
<p><strong>Justification</strong> :
- Conforme RGPD (non obligatoire en phase MVP)
- 0€, contrôle total
- Bonne pratique : avoir un contact identifié</p>
<hr />
<h2 id="recapitulatif-section-13">Récapitulatif Section 13</h2>
<table>
<thead>
<tr>
<th>Mesure</th>
<th>Implémentation</th>
<th>Coût</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Consentement</strong></td>
<td>Tarteaucitron.js + PostgreSQL</td>
<td>0€</td>
</tr>
<tr>
<td><strong>Anonymisation GPS</strong></td>
<td>Geohash PostGIS (24h)</td>
<td>0€</td>
</tr>
<tr>
<td><strong>Export données</strong></td>
<td>JSON+HTML+ZIP asynchrone</td>
<td>0€</td>
</tr>
<tr>
<td><strong>Suppression compte</strong></td>
<td>Grace period 30j + anonymisation</td>
<td>0€</td>
</tr>
<tr>
<td><strong>Mode dégradé</strong></td>
<td>GeoIP MaxMind + GPS optionnel</td>
<td>0€</td>
</tr>
<tr>
<td><strong>Conservation</strong></td>
<td>Purge auto 5 ans inactivité</td>
<td>0€</td>
</tr>
<tr>
<td><strong>Analytics</strong></td>
<td>Matomo self-hosted</td>
<td>~5€/mois</td>
</tr>
<tr>
<td><strong>Registre traitements</strong></td>
<td>Markdown Git</td>
<td>0€</td>
</tr>
<tr>
<td><strong>Breach detection</strong></td>
<td>Sentry + Grafana + runbook</td>
<td>0€</td>
</tr>
<tr>
<td><strong>DPO</strong></td>
<td>Fondateur formé CNIL</td>
<td>0€</td>
</tr>
</tbody>
</table>
<p><strong>Coût total RGPD : ~5€/mois</strong></p>
<hr />
<h2 id="points-dattention-pour-gherkin">Points d'attention pour Gherkin</h2>
<ul>
<li>Tester consentement géolocalisation (accept/refuse → contenus différents)</li>
<li>Tester anonymisation GPS après 24h (job cron)</li>
<li>Tester export données (génération complète + vérification contenu)</li>
<li>Tester grace period suppression (annulation possible)</li>
<li>Tester mode GeoIP (ville détectée correctement)</li>
<li>Tester purge automatique (5 ans inactivité)</li>
<li>Tester notifications avant purge (90j/30j/7j)</li>
</ul>
<div style="page-break-after: always;"></div>
<h2 id="14-moderation-flows-operationnels_1">14. Modération - Flows opérationnels</h2>
<h3 id="141-signalement">14.1 Signalement</h3>
<p><strong>Décision</strong> : Formulaire simple avec 7 catégories prédéfinies</p>
<h4 id="1411-categories-de-signalement">14.1.1 Catégories de signalement</h4>
<p>Liste déroulante avec 7 options :</p>
<table>
<thead>
<tr>
<th>Catégorie</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🚫 <strong>Haine &amp; violence</strong></td>
<td>Incitation à la haine, discrimination, menaces</td>
</tr>
<tr>
<td>🔞 <strong>Contenu sexuel</strong></td>
<td>Pornographie, contenu explicite</td>
</tr>
<tr>
<td>⚖️ <strong>Illégalité</strong></td>
<td>Terrorisme, apologie de crimes</td>
</tr>
<tr>
<td>🎵 <strong>Droits d'auteur</strong></td>
<td>Musique/contenu protégé non autorisé</td>
</tr>
<tr>
<td>📧 <strong>Spam</strong></td>
<td>Publicité non sollicitée, répétition</td>
</tr>
<tr>
<td>❌ <strong>Fausse information</strong></td>
<td>Désinformation sur santé, sécurité routière</td>
</tr>
<tr>
<td>🔧 <strong>Autre</strong></td>
<td>Champ texte obligatoire si sélectionné</td>
</tr>
</tbody>
</table>
<p><strong>Justification</strong> :
- Équilibre entre simplicité (pas trop de choix) et précision (aide les modérateurs)
- Coût : 0€ (liste déroulante standard)</p>
<hr />
<h4 id="1412-commentaire-du-signaleur">14.1.2 Commentaire du signaleur</h4>
<p><strong>Décision</strong> : Optionnel avec incitation</p>
<ul>
<li>Champ texte libre (0-500 caractères)</li>
<li>Placeholder : "Décrivez le problème (optionnel mais recommandé)"</li>
<li>Non bloquant : le signalement peut être envoyé sans commentaire</li>
</ul>
<p><strong>Justification</strong> :
- Encourage la qualité des signalements sans créer de friction
- Aide les modérateurs à comprendre le contexte
- Pas de risque d'abandon du processus</p>
<hr />
<h4 id="1413-confirmation-apres-signalement">14.1.3 Confirmation après signalement</h4>
<p><strong>Décision</strong> : Toast in-app avec lien historique</p>
<p><strong>Affichage</strong> :
- Toast notification : "✓ Signalement envoyé. Nous l'examinerons sous 24-48h."
- Durée affichage : 5 secondes
- Bouton optionnel "Voir mes signalements" (accès historique)</p>
<p><strong>Historique personnel</strong> :
- Liste des signalements envoyés par l'utilisateur
- Statut : En cours / Traité / Rejeté
- Notification in-app si action prise (contenu retiré, signalement rejeté)</p>
<p><strong>Justification</strong> :
- Transparence maximale
- Coût : 0€ (aucun email automatique)
- Bonne UX</p>
<hr />
<h3 id="142-traitement-des-signalements">14.2 Traitement des signalements</h3>
<h4 id="1421-ia-pre-filtre-transcription-analyse">14.2.1 IA pré-filtre (transcription + analyse)</h4>
<p><strong>Décision</strong> : OpenAI Whisper open source + NLP</p>
<p><strong>Stack technique</strong> :</p>
<table>
<thead>
<tr>
<th>Composant</th>
<th>Technologie</th>
<th>Hébergement</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Transcription</strong></td>
<td>Whisper large-v3</td>
<td>Self-hosted (CPU MVP, GPU scale)</td>
</tr>
<tr>
<td><strong>Analyse sentiment</strong></td>
<td>distilbert-base-uncased</td>
<td>Self-hosted</td>
</tr>
<tr>
<td><strong>Détection haine</strong></td>
<td>facebook/roberta-hate-speech</td>
<td>Self-hosted</td>
</tr>
<tr>
<td><strong>Mots-clés</strong></td>
<td>Liste noire FR/EN + regex</td>
<td>PostgreSQL</td>
</tr>
</tbody>
</table>
<p><strong>Processus</strong> :
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</p>
<p><strong>Délais</strong> :
- Audio &lt;5 min : 1-3 minutes
- Audio 5-30 min : 3-10 minutes
- Audio &gt;30 min : 10-20 minutes</p>
<p><strong>Coût</strong> :
- <strong>MVP</strong> : 0€ (CPU standard, processing asynchrone)
- <strong>Scale</strong> : 50-200€/mois (GPU VPS si &gt;1000 signalements/jour)</p>
<p><strong>Justification</strong> :
- 100% open source, pas de dépendance GAFAM
- Coût maîtrisé (scaling progressif)
- Gain productivité modérateurs ×3-5</p>
<hr />
<h4 id="1422-delais-de-traitement-sla">14.2.2 Délais de traitement (SLA)</h4>
<p><strong>Décision</strong> : SLA progressif selon priorité</p>
<table>
<thead>
<tr>
<th>Priorité</th>
<th>Délai cible</th>
<th>Traitement</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>CRITIQUE</strong></td>
<td>&lt;2h (24/7)</td>
<td>Violence, suicide, mise en danger → Astreinte modérateur senior</td>
</tr>
<tr>
<td><strong>HAUTE</strong></td>
<td>&lt;24h (jours ouvrés)</td>
<td>Haine, harcèlement, désinformation → Modérateur junior/senior</td>
</tr>
<tr>
<td><strong>MOYENNE</strong></td>
<td>&lt;24h (jours ouvrés)</td>
<td>Spam, contenu inapproprié → Modérateur junior</td>
</tr>
<tr>
<td><strong>BASSE</strong></td>
<td>&lt;72h (jours ouvrés)</td>
<td>Qualité audio, tags incorrects → Modérateur junior</td>
</tr>
</tbody>
</table>
<p><strong>Traitement automatique</strong> :
- Score IA &gt;95% + catégorie évidente (ex: spam répété) → Action automatique immédiate
- Notification créateur + possibilité d'appel</p>
<p><strong>Justification</strong> :
- Réaliste et conforme DSA (Digital Services Act)
- Scalable : priorisation automatique
- Ressources humaines optimisées</p>
<hr />
<h4 id="1423-priorisation-automatique">14.2.3 Priorisation automatique</h4>
<p><strong>Décision</strong> : File d'attente intelligente basée sur score IA</p>
<p><strong>Calcul de priorité</strong> :</p>
<pre><code>Priorité = (Score_IA × 0.7) + (Signalements_cumulés × 0.2) + (Fiabilité_signaleur × 0.1)
</code></pre>
<p><strong>Détails</strong> :
- <strong>Score_IA</strong> : 0-100% (confiance analyse automatique)
- <strong>Signalements_cumulés</strong> : nombre de signalements du même contenu (boost priorité)
- <strong>Fiabilité_signaleur</strong> : score utilisateur (historique signalements pertinents)</p>
<p><strong>Classification résultante</strong> :
- Priorité ≥90 → <strong>CRITIQUE</strong> (traitement immédiat)
- Priorité 70-89 → <strong>HAUTE</strong> (file prioritaire)
- Priorité 40-69 → <strong>MOYENNE</strong> (file normale)
- Priorité &lt;40 → <strong>BASSE</strong> (file différée)</p>
<p><strong>Justification</strong> :
- Optimise le temps des modérateurs
- Traite les cas graves en priorité
- Coût : 0€ (algorithme simple)</p>
<hr />
<h3 id="143-sanctions">14.3 Sanctions</h3>
<h4 id="1431-notification-au-createur">14.3.1 Notification au créateur</h4>
<p><strong>Décision</strong> : Multi-canal (email + push + in-app)</p>
<p><strong>Canaux utilisés</strong> :</p>
<table>
<thead>
<tr>
<th>Canal</th>
<th>Timing</th>
<th>Contenu</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Push notification</strong></td>
<td>Immédiat</td>
<td>Alerte courte : "Votre contenu a été modéré"</td>
</tr>
<tr>
<td><strong>In-app</strong></td>
<td>Au prochain lancement</td>
<td>Popup détaillée avec bouton "Voir détails"</td>
</tr>
<tr>
<td><strong>Email</strong></td>
<td>Dans l'heure</td>
<td>Notification complète avec lien vers formulaire d'appel</td>
</tr>
</tbody>
</table>
<p><strong>Contenu email</strong> :</p>
<pre><code>Objet : Modération de votre contenu &quot;[Titre du contenu]&quot;
Bonjour [Pseudo],
Votre contenu &quot;[Titre]&quot; 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 : &quot;[Passage problématique surligné]&quot;
Vous pouvez contester cette décision sous 7 jours :
[Lien formulaire d'appel]
L'équipe RoadWave
</code></pre>
<p><strong>Coût</strong> :
- Email : ~0.001€/notification (Brevo, Resend)
- Push : 0€ (Firebase Cloud Messaging / APNs)
- In-app : 0€</p>
<p><strong>Justification</strong> :
- Conformité DSA (transparence obligatoire)
- Multi-canal garantit réception
- Coût négligeable</p>
<hr />
<h4 id="1432-detail-de-la-sanction">14.3.2 Détail de la sanction</h4>
<p><strong>Décision</strong> : Notification complète avec preuves</p>
<p><strong>Éléments inclus obligatoirement</strong> :</p>
<ol>
<li><strong>Catégorie violée</strong> : référence précise CGU (ex: "Article 3.2 - Haine &amp; violence")</li>
<li><strong>Raison détaillée</strong> : explication en langage clair (non juridique)</li>
<li><strong>Extrait audio</strong> : timestamp exact du passage problématique (ex: "3:42-4:15")</li>
<li><strong>Transcription</strong> : texte problématique surligné en rouge</li>
<li><strong>Gravité</strong> : Strike actuel + conséquences (ex: "Strike 2/4 - Suspension 7 jours")</li>
<li><strong>Recours</strong> : lien direct vers formulaire d'appel + délai (7 jours)</li>
</ol>
<p><strong>Exemple visuel in-app</strong> :</p>
<pre><code>┌─────────────────────────────────────┐
│ ⚠️ Contenu modéré │
├─────────────────────────────────────┤
│ Titre : &quot;Mon podcast #42&quot; │
│ Publié le : 15/01/2026 │
│ │
│ Catégorie violée : │
│ 🚫 Haine &amp; violence (Article 3.2) │
│ │
│ Passage problématique : 3:42-4:15 │
│ &quot;[Transcription surlignée]&quot; │
│ │
│ Sanction : Strike 2/4 │
│ Suspension : 7 jours │
│ │
│ [Contester cette décision] │
└─────────────────────────────────────┘
</code></pre>
<p><strong>Justification</strong> :
- Transparence maximale (obligation DSA)
- Créateur comprend l'erreur → amélioration future
- Réduit les appels non fondés</p>
<hr />
<h4 id="1433-processus-dappel">14.3.3 Processus d'appel</h4>
<p><strong>Décision</strong> : Formulaire in-app structuré</p>
<p><strong>Accès</strong> :
- Bouton "Contester cette décision" dans notification
- Section "Mes sanctions" dans profil créateur</p>
<p><strong>Formulaire d'appel</strong> :</p>
<table>
<thead>
<tr>
<th>Champ</th>
<th>Type</th>
<th>Obligatoire</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Sanction contestée</strong></td>
<td>Pré-rempli (non modifiable)</td>
<td>✅</td>
</tr>
<tr>
<td><strong>Raison de l'appel</strong></td>
<td>Texte libre (50-1000 caractères)</td>
<td>✅</td>
</tr>
<tr>
<td><strong>Arguments</strong></td>
<td>Zone texte enrichie</td>
<td>✅</td>
</tr>
<tr>
<td><strong>Preuves</strong></td>
<td>Upload fichiers (max 5, 10 MB total)</td>
<td>❌</td>
</tr>
</tbody>
</table>
<p><strong>Après soumission</strong> :
- Génération numéro de ticket unique (ex: <code>#MOD-2026-00142</code>)
- Email confirmation : "Votre appel sera traité sous 72h"
- Statut visible dans l'app : "En cours d'examen"</p>
<p><strong>Délai de soumission</strong> :
- Maximum <strong>7 jours</strong> après notification de sanction
- Après 7 jours : appel automatiquement refusé</p>
<p><strong>Justification</strong> :
- Professionnel et traçable
- Intégration complète avec système modération
- Coût : 0€ (formulaire custom backend)</p>
<hr />
<h4 id="1434-delai-de-reponse-pour-appel">14.3.4 Délai de réponse pour appel</h4>
<p><strong>Décision</strong> : SLA 72h garanti</p>
<p><strong>Délais</strong> :</p>
<table>
<thead>
<tr>
<th>Type d'appel</th>
<th>Délai</th>
<th>Responsable</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Standard</strong></td>
<td>72h max (3 jours ouvrés)</td>
<td>Modérateur senior</td>
</tr>
<tr>
<td><strong>Complexe</strong></td>
<td>5 jours ouvrés + notification intermédiaire J+3</td>
<td>Modérateur senior + Admin modération</td>
</tr>
<tr>
<td><strong>Critique</strong></td>
<td>24h (cas suspension longue/ban)</td>
<td>Admin modération</td>
</tr>
</tbody>
</table>
<p><strong>Notification intermédiaire</strong> (si délai &gt;72h) :
- Email J+3 : "Votre appel #MOD-XXX est en cours d'examen approfondi. Réponse sous 2 jours."</p>
<p><strong>Réponse finale</strong> :</p>
<p>Email détaillé avec :
1. <strong>Décision</strong> : Maintien / Annulation / Réduction de sanction
2. <strong>Justification</strong> : explication de la décision d'appel
3. <strong>Actions</strong> : Strike retiré / Suspension annulée / Contenu rétabli (si applicable)
4. <strong>Définitif</strong> : mention "Cette décision est définitive" (pas de second appel)</p>
<p><strong>Suivi in-app</strong> :
- Mise à jour statut : "Appel accepté ✓" ou "Appel rejeté ✗"
- Badge notification</p>
<p><strong>Justification</strong> :
- Équilibre entre rapidité et qualité de traitement
- Conforme pratiques industrie (YouTube, TikTok : 5-7 jours)
- Ressources humaines réalistes</p>
<hr />
<h3 id="144-outils-moderateurs">14.4 Outils modérateurs</h3>
<p><strong>Stack technique complète</strong> :</p>
<table>
<thead>
<tr>
<th>Outil</th>
<th>Technologie</th>
<th>Fonction</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Dashboard</strong></td>
<td>React + TanStack Table</td>
<td>Interface modération</td>
</tr>
<tr>
<td><strong>File signalements</strong></td>
<td>PostgreSQL + Redis</td>
<td>Priorisation temps réel</td>
</tr>
<tr>
<td><strong>Player audio</strong></td>
<td>Wavesurfer.js</td>
<td>Lecture avec waveform + annotations</td>
</tr>
<tr>
<td><strong>Transcription</strong></td>
<td>Whisper large-v3</td>
<td>Conversion audio → texte</td>
</tr>
<tr>
<td><strong>Historique créateur</strong></td>
<td>Vue 360°</td>
<td>Contenus, strikes, appels, métriques</td>
</tr>
<tr>
<td><strong>Actions rapides</strong></td>
<td>Shortcuts clavier</td>
<td>Approuver (A), Rejeter (R), Escalade (E)</td>
</tr>
<tr>
<td><strong>Logs audit</strong></td>
<td>PostgreSQL + export</td>
<td>Traçabilité complète (DSA)</td>
</tr>
<tr>
<td><strong>Collaboration</strong></td>
<td>Système de commentaires</td>
<td>Modérateurs peuvent s'entraider sur cas complexes</td>
</tr>
</tbody>
</table>
<p><strong>Fonctionnalités clés</strong> :</p>
<ol>
<li><strong>Lecture accélérée</strong> : 0.75x à 2x (gain productivité)</li>
<li><strong>Marqueurs temporels</strong> : annotation directe sur waveform</li>
<li><strong>Historique créateur</strong> : vue rapide contenus précédents + strikes</li>
<li><strong>Statistiques</strong> : signalements traités/jour, temps moyen, précision</li>
<li><strong>Fil d'activité</strong> : actions récentes équipe (temps réel)</li>
</ol>
<p><strong>Coût infrastructure</strong> :
- MVP : 0-50€/mois (serveur CPU)
- Scale : 50-200€/mois (GPU + Redis Cluster)</p>
<hr />
<h3 id="145-moderation-preventive-rappel">14.5 Modération préventive (rappel)</h3>
<p><strong>Nouveaux créateurs</strong> :
- Validation manuelle des <strong>3 premiers contenus</strong>
- Délai : 24-48h (jours ouvrés)
- Transcription automatique pour aide modérateur</p>
<p><strong>Score de confiance</strong> :
- Évolution dynamique selon historique
- Créateur fiable (0 strike depuis 6 mois) → validation automatique
- Créateur suspect (strikes récents) → validation manuelle systématique</p>
<p><strong>Publicités</strong> :
- Validation manuelle obligatoire 24-48h (responsabilité juridique)
- Transcription + analyse métadonnées (ciblage, durée, volume)</p>
<p><strong>Justification</strong> :
- Prévention &gt; réaction (économie modération)
- Qualité plateforme préservée dès le début</p>
<hr />
<h2 id="recapitulatif-section-14">Récapitulatif Section 14</h2>
<table>
<thead>
<tr>
<th>Point</th>
<th>Décision</th>
<th>Coût</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Catégories signalement</strong></td>
<td>7 catégories prédéfinies + champ libre</td>
<td>0€</td>
</tr>
<tr>
<td><strong>Commentaire signaleur</strong></td>
<td>Optionnel avec incitation</td>
<td>0€</td>
</tr>
<tr>
<td><strong>Confirmation</strong></td>
<td>Toast in-app + historique personnel</td>
<td>0€</td>
</tr>
<tr>
<td><strong>IA pré-filtre</strong></td>
<td>Whisper (CPU MVP, GPU scale) + NLP open source</td>
<td>0-200€/mois</td>
</tr>
<tr>
<td><strong>Délais traitement</strong></td>
<td>SLA progressif : 2h/24h/72h selon priorité</td>
<td>Dépend équipe</td>
</tr>
<tr>
<td><strong>Priorisation</strong></td>
<td>File intelligente basée score IA</td>
<td>0€</td>
</tr>
<tr>
<td><strong>Notification sanction</strong></td>
<td>Email + push + in-app (multi-canal)</td>
<td>~0.001€/notif</td>
</tr>
<tr>
<td><strong>Détail sanction</strong></td>
<td>Complet : raison + extrait + transcription</td>
<td>0€</td>
</tr>
<tr>
<td><strong>Processus appel</strong></td>
<td>Formulaire in-app structuré</td>
<td>0€</td>
</tr>
<tr>
<td><strong>Délai appel</strong></td>
<td>72h garanti (standard)</td>
<td>Dépend équipe</td>
</tr>
<tr>
<td><strong>Outils modérateurs</strong></td>
<td>Dashboard React + Whisper + Wavesurfer.js</td>
<td>0-200€/mois</td>
</tr>
</tbody>
</table>
<p><strong>Coût total MVP</strong> : <strong>0-200€/mois</strong> (infrastructure IA optionnelle)</p>
<p><strong>Conformité</strong> :
- ✅ 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)</p>
<p><strong>Scalabilité</strong> :
- 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</p>
<hr />
<p><strong>Prochaine section à clarifier</strong> : Section 11 (Mode offline) ou Section 12 (Gestion des erreurs)</p>
<div style="page-break-after: always;"></div>
<h2 id="15-autres-comportements_1">15. Autres comportements</h2>
<h3 id="151-partage-de-contenu">15.1 Partage de contenu</h3>
<p><strong>Décision</strong> : Système de partage complet avec web player</p>
<h4 id="1511-bouton-partager">15.1.1 Bouton "Partager"</h4>
<p><strong>Disponibilité</strong> : Partout dans l'application</p>
<p><strong>Emplacements</strong> :
- Player en lecture (bouton dans contrôles)
- Page profil créateur (sur chaque contenu)
- Liste de recherche (menu contextuel)
- Historique personnel</p>
<p><strong>Icône</strong> : ⬆️ (universelle iOS/Android)</p>
<p><strong>Menu options</strong> :
- Copier le lien
- WhatsApp
- Email
- SMS
- Plus... (sheet natif OS)</p>
<p><strong>Justification</strong> :
- Viralité = croissance organique gratuite
- Aucune friction, partage universel</p>
<hr />
<h4 id="1512-comportement-du-lien-partage">15.1.2 Comportement du lien partagé</h4>
<p><strong>Format URL</strong> : <code>https://roadwave.fr/share/c/[content_id]</code></p>
<p><strong>Comportement multi-plateforme</strong> :</p>
<pre><code>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│
└─────────────────────────────────┘
</code></pre>
<p><strong>Contenu de la page web</strong> :</p>
<pre><code class="language-html">┌───────────────────────────────────────┐
│ 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] │
└───────────────────────────────────────┘
</code></pre>
<p><strong>Métadonnées Open Graph (SEO)</strong> :</p>
<pre><code class="language-html">&lt;meta property=&quot;og:title&quot; content=&quot;[Titre contenu] - RoadWave&quot;&gt;
&lt;meta property=&quot;og:description&quot; content=&quot;[Description ou extrait]&quot;&gt;
&lt;meta property=&quot;og:image&quot; content=&quot;[URL cover image]&quot;&gt;
&lt;meta property=&quot;og:audio&quot; content=&quot;[URL audio si public]&quot;&gt;
&lt;meta property=&quot;og:type&quot; content=&quot;music.song&quot;&gt;
&lt;meta property=&quot;og:site_name&quot; content=&quot;RoadWave&quot;&gt;
&lt;meta name=&quot;twitter:card&quot; content=&quot;player&quot;&gt;
&lt;meta name=&quot;twitter:player&quot; content=&quot;https://roadwave.fr/player/[content_id]&quot;&gt;
</code></pre>
<p><strong>Deep linking</strong> :
- iOS : Universal Links (configuration <code>apple-app-site-association</code>)
- Android : App Links (configuration <code>assetlinks.json</code>)
- URL scheme : <code>roadwave://content/[content_id]</code></p>
<p><strong>Justification</strong> :
- Meilleure viralité (partage social optimisé)
- SEO (contenus indexés Google)
- UX optimale (web + app)
- Coût : 0€ (backend simple + CDN existant)</p>
<hr />
<h4 id="1513-contenus-premium-partages">15.1.3 Contenus Premium partagés</h4>
<p><strong>Décision</strong> : Preview 30 secondes + paywall</p>
<p><strong>Comportement</strong> :</p>
<ol>
<li>User clique lien contenu Premium partagé</li>
<li>Page web affiche badge "👑 Contenu Premium"</li>
<li>Player démarre automatiquement</li>
<li>Après <strong>30 secondes exactement</strong> :</li>
<li>Fade out audio (2 secondes)</li>
<li>Overlay apparaît :</li>
</ol>
<pre><code>┌─────────────────────────────────┐
│ 👑 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] │
└─────────────────────────────────┘
</code></pre>
<ol>
<li>Utilisateur peut :</li>
<li>S'abonner Premium (redirection web Mangopay)</li>
<li>Télécharger l'app (redirection stores)</li>
<li>Rejouer les 30 premières secondes (illimité)</li>
</ol>
<p><strong>Tracking</strong> :
- Métriques créateur : "Partages Premium" + "Conversions Premium"
- Créateur touche sa part si conversion (70%)</p>
<p><strong>Justification</strong> :
- Équilibre viralité / monétisation
- 30s = assez pour donner envie, pas assez pour satisfaire
- Protège revenus créateurs</p>
<hr />
<h3 id="152-profil-createur">15.2 Profil créateur</h3>
<p><strong>Décision</strong> : Profil public complet et transparent</p>
<h4 id="1521-structure-de-la-page-profil">15.2.1 Structure de la page profil</h4>
<p><strong>URL</strong> : <code>https://roadwave.fr/@[pseudo]</code></p>
<p><strong>Layout</strong> :</p>
<pre><code>┌────────────────────────────────────────┐
│ [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] │
└────────────────────────────────────────┘
</code></pre>
<p><strong>Informations affichées</strong> :</p>
<table>
<thead>
<tr>
<th>Élément</th>
<th>Visibilité</th>
<th>Détails</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Photo + pseudo</strong></td>
<td>✅ Public</td>
<td>Identité visuelle</td>
</tr>
<tr>
<td><strong>Badge vérifié ✓</strong></td>
<td>✅ Public (si applicable)</td>
<td>Compte authentique</td>
</tr>
<tr>
<td><strong>Bio</strong></td>
<td>✅ Public</td>
<td>0-300 caractères, markdown basique (gras, italique, liens)</td>
</tr>
<tr>
<td><strong>Nombre abonnés</strong></td>
<td>✅ Public</td>
<td>Arrondi si &gt;1000 (ex: 1.2K, 54K)</td>
</tr>
<tr>
<td><strong>Nombre contenus</strong></td>
<td>✅ Public</td>
<td>Exact</td>
</tr>
<tr>
<td><strong>Durée totale créée</strong></td>
<td>✅ Public</td>
<td>Arrondi en heures (ex: 18h, 142h)</td>
</tr>
<tr>
<td><strong>Écoutes totales</strong></td>
<td>✅ Public</td>
<td>Arrondi (ex: 54K, 1.2M)</td>
</tr>
<tr>
<td><strong>Liste abonnés</strong></td>
<td>❌ Privé</td>
<td>Protection vie privée (RGPD)</td>
</tr>
<tr>
<td><strong>Revenus</strong></td>
<td>❌ Privé</td>
<td>Confidentialité financière</td>
</tr>
<tr>
<td><strong>Localisation précise</strong></td>
<td>❌ Privé</td>
<td>Sécurité</td>
</tr>
<tr>
<td><strong>Email</strong></td>
<td>❌ Privé</td>
<td>Anti-spam</td>
</tr>
</tbody>
</table>
<p><strong>Tri des contenus</strong> :</p>
<table>
<thead>
<tr>
<th>Option</th>
<th>Comportement</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Plus récents</strong></td>
<td>Date publication DESC (défaut)</td>
</tr>
<tr>
<td><strong>Plus populaires</strong></td>
<td>Écoutes complètes × (1 + (date_publication - now) / 90 jours)</td>
</tr>
<tr>
<td><strong>Plus anciens</strong></td>
<td>Date publication ASC</td>
</tr>
<tr>
<td><strong>Par tag</strong></td>
<td>Filtre multi-sélection tags</td>
</tr>
</tbody>
</table>
<p><strong>Recherche locale</strong> :
- Barre recherche dans profil : "Rechercher dans les contenus de @pseudo"
- Recherche full-text sur titres + descriptions</p>
<p><strong>Actions menu [•••]</strong> :
- Partager profil
- Signaler profil (spam, usurpation)
- Bloquer créateur (masque tous ses contenus)</p>
<hr />
<h4 id="1522-statistiques-publiques">15.2.2 Statistiques publiques</h4>
<p><strong>Décision</strong> : Stats arrondies et motivantes</p>
<p><strong>Affichage public</strong> :</p>
<table>
<thead>
<tr>
<th>Métrique</th>
<th>Format affichage</th>
<th>Exemple</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Abonnés</strong></td>
<td>Exact si &lt;1000, arrondi sinon</td>
<td>342 / 1.2K / 54K / 1.2M</td>
</tr>
<tr>
<td><strong>Écoutes totales</strong></td>
<td>Arrondi dès 1000</td>
<td>842 / 5.4K / 142K / 2.1M</td>
</tr>
<tr>
<td><strong>Contenus publiés</strong></td>
<td>Exact</td>
<td>42 contenus</td>
</tr>
<tr>
<td><strong>Durée totale</strong></td>
<td>Arrondi en heures</td>
<td>18h / 142h de contenu</td>
</tr>
</tbody>
</table>
<p><strong>Métriques PRIVÉES (créateur uniquement)</strong> :</p>
<table>
<thead>
<tr>
<th>Métrique</th>
<th>Disponible dans dashboard créateur</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Taux complétion moyen</strong></td>
<td>78% (écoutes &gt;80% / écoutes totales)</td>
</tr>
<tr>
<td><strong>Évolution abonnés</strong></td>
<td>Graphique 30j / 90j / 1 an</td>
</tr>
<tr>
<td><strong>Écoutes par contenu</strong></td>
<td>Tableau détaillé</td>
</tr>
<tr>
<td><strong>Revenus</strong></td>
<td>Dashboard monétisation dédié</td>
</tr>
<tr>
<td><strong>Taux conversion Premium</strong></td>
<td>Partages → conversions</td>
</tr>
<tr>
<td><strong>Démographie</strong></td>
<td>Âge / zone géo (agrégée, anonymisée)</td>
</tr>
</tbody>
</table>
<p><strong>Justification</strong> :
- Arrondi = évite comparaisons anxiogènes
- Preuve sociale pour nouveaux auditeurs (trust)
- Gamification douce (motivation créateurs)
- Privacy by design</p>
<hr />
<h4 id="1523-badge-verifie">15.2.3 Badge vérifié</h4>
<p><strong>Décision</strong> : Badge unique ✓ (vérifié officiel)</p>
<p><strong>Critères d'attribution</strong> (au moins UN des critères) :</p>
<ol>
<li><strong>KYC monétisation validé</strong> : identité vérifiée via Mangopay KYC</li>
<li><strong>Célébrité / Média officiel</strong> : validation manuelle équipe RoadWave</li>
<li><strong>Communauté significative</strong> : ≥10K abonnés + compte actif &gt;6 mois</li>
</ol>
<p><strong>Affichage</strong> :
- Badge bleu <strong>✓</strong> accolé au pseudo (partout : profil, player, recherche)
- Tooltip au survol/appui long : "Compte vérifié"</p>
<p><strong>Processus d'obtention</strong> :</p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Processus</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Automatique (KYC)</strong></td>
<td>Badge attribué dès validation documents Mangopay</td>
</tr>
<tr>
<td><strong>Manuel (célébrité)</strong></td>
<td>Formulaire demande → équipe vérifie identité → validation 48-72h</td>
</tr>
<tr>
<td><strong>Automatique (10K)</strong></td>
<td>Badge attribué automatiquement à 10K abonnés si compte &gt;6 mois</td>
</tr>
</tbody>
</table>
<p><strong>Retrait du badge</strong> :
- Suspension monétisation → badge retiré temporairement
- Strikes multiples → badge retiré définitivement
- Usurpation identité détectée → ban + retrait</p>
<p><strong>Justification</strong> :
- 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 <code>verified</code> en DB)</p>
<hr />
<h3 id="153-recherche">15.3 Recherche</h3>
<p><strong>Décision</strong> : Recherche full-text + géo + filtres avancés</p>
<h4 id="1531-recherche-par-mot-cle">15.3.1 Recherche par mot-clé</h4>
<p><strong>Implémentation</strong> : PostgreSQL full-text search (français)</p>
<p><strong>Configuration technique</strong> :</p>
<pre><code class="language-sql">-- 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;
</code></pre>
<p><strong>Champs indexés</strong> :
- Titre du contenu (poids × 3)
- Description (poids × 1)
- Pseudo créateur (poids × 2)
- Tags (poids × 1.5)</p>
<p><strong>Fonctionnalités</strong> :</p>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Stemming français</strong></td>
<td>"voyages" trouve "voyage", "voyager", etc.</td>
</tr>
<tr>
<td><strong>Correction auto</strong></td>
<td>Suggestion si 0 résultat</td>
</tr>
<tr>
<td><strong>Recherches populaires</strong></td>
<td>"Essayez plutôt : balade paris, audio-guide louvre"</td>
</tr>
<tr>
<td><strong>Historique personnel</strong></td>
<td>10 dernières recherches sauvegardées</td>
</tr>
<tr>
<td><strong>Autocomplete</strong></td>
<td>Suggestions pendant frappe (top 5)</td>
</tr>
</tbody>
</table>
<p><strong>Coût</strong> : 0€ (PostgreSQL natif)</p>
<p><strong>Migration future</strong> :
- Si &gt;100K contenus : Meilisearch (typo-tolerance avancée, ~20-50€/mois)
- Si &gt;1M contenus : Elasticsearch cluster</p>
<p><strong>Justification</strong> :
- PostgreSQL full-text = performant jusqu'à 500K contenus
- Stemming français natif
- 0€, aucune dépendance externe</p>
<hr />
<h4 id="1532-recherche-geographique">15.3.2 Recherche géographique</h4>
<p><strong>Décision</strong> : Recherche lieu + rayon paramétrable</p>
<p><strong>Interface utilisateur</strong> :</p>
<pre><code>┌─────────────────────────────────────┐
│ 🔍 Recherche contenu... │
├─────────────────────────────────────┤
<20><> 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] │
└─────────────────────────────────────┘
</code></pre>
<p><strong>Géocodage</strong> :</p>
<table>
<thead>
<tr>
<th>Service</th>
<th>Usage</th>
<th>Coût</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Nominatim (OSM)</strong></td>
<td>MVP (API publique)</td>
<td>0€ (rate limit 1 req/s)</td>
</tr>
<tr>
<td><strong>Nominatim self-hosted</strong></td>
<td>Scale (Docker)</td>
<td>20-50€/mois VPS</td>
</tr>
<tr>
<td><strong>Mapbox Geocoding</strong></td>
<td>Fallback premium</td>
<td>0.50€ / 1000 requêtes</td>
</tr>
</tbody>
</table>
<p><strong>Processus de recherche géo</strong> :</p>
<ol>
<li>User tape "Louvre" ou "Paris"</li>
<li>Autocomplete via Nominatim → liste suggestions</li>
<li>User sélectionne → récupération coordonnées (lat, lon)</li>
<li>Requête PostGIS :</li>
</ol>
<pre><code class="language-sql">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;
</code></pre>
<p><strong>Affichage résultats</strong> :
- Tri par défaut : distance croissante
- Indication distance : "À 2.3 km" / "À 15 km" / "À 142 km"
- Option carte : markers cliquables (clustering si &gt;50 résultats)</p>
<p><strong>Coût</strong> :
- MVP : 0€ (Nominatim public)
- Scale : 20-50€/mois (Nominatim self-hosted Docker)</p>
<p><strong>Justification</strong> :
- Essentiel pour tourisme / planification trajet
- OpenStreetMap = pas de dépendance Google
- PostGIS = performant (index GIST natif)</p>
<hr />
<h4 id="1533-filtres-avances">15.3.3 Filtres avancés</h4>
<p><strong>Décision</strong> : 7 catégories de filtres combinables</p>
<p><strong>Interface filtres</strong> :</p>
<pre><code>┌─────────────────────────────────────┐
│ Filtres [×] │
├─────────────────────────────────────┤
│ Type de contenu │
│ ☐ Contenu court (&lt;5 min) │
│ ☐ Podcast (&gt;5 min) │
│ ☐ Radio live │
│ ☐ Audio-guide │
│ │
│ Durée │
│ ○ Toutes durées │
│ ○ &lt;5 min │
│ ○ 5-15 min │
│ ○ 15-30 min │
│ ○ &gt;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] │
└─────────────────────────────────────┘
</code></pre>
<p><strong>Options de tri</strong> :</p>
<table>
<thead>
<tr>
<th>Tri</th>
<th>Algorithme</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Pertinence</strong></td>
<td>Score recherche × (1 + log(listen_count + 1))</td>
</tr>
<tr>
<td><strong>Popularité</strong></td>
<td>Écoutes complètes derniers 30j DESC</td>
</tr>
<tr>
<td><strong>Récent</strong></td>
<td>Date publication DESC</td>
</tr>
<tr>
<td><strong>Proximité</strong></td>
<td>Distance GPS ASC (si recherche géo active)</td>
</tr>
<tr>
<td><strong>Durée</strong></td>
<td>Durée audio ASC ou DESC</td>
</tr>
</tbody>
</table>
<p><strong>Sauvegarde de recherches</strong> :</p>
<ul>
<li>Bouton "💾 Sauvegarder cette recherche"</li>
<li>Nom personnalisable : "Podcasts voyage Paris"</li>
<li>Maximum <strong>5 recherches sauvegardées</strong></li>
<li>Accès rapide : onglet "Recherches sauvegardées" dans page recherche</li>
<li>Notifications optionnelles : "3 nouveaux contenus dans 'Podcasts voyage Paris'"</li>
</ul>
<p><strong>Performances</strong> :</p>
<pre><code class="language-sql">-- 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);
</code></pre>
<p><strong>Coût</strong> : 0€ (PostgreSQL + index standards)</p>
<p><strong>Justification</strong> :
- Filtres essentiels pour découvrabilité
- Combinables = puissance maximale
- Sauvegarde = gain temps utilisateurs réguliers</p>
<hr />
<h4 id="1534-page-de-resultats">15.3.4 Page de résultats</h4>
<p><strong>Décision</strong> : Liste avec previews enrichies</p>
<p><strong>Layout résultats</strong> :</p>
<pre><code>┌─────────────────────────────────────────┐
│ 🔍 &quot;voyage paris&quot; │
│ 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) │
└─────────────────────────────────────────┘
</code></pre>
<p><strong>Informations par résultat</strong> :</p>
<table>
<thead>
<tr>
<th>Élément</th>
<th>Affichage</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Cover image</strong></td>
<td>16:9, 120×68 px, lazy loading</td>
</tr>
<tr>
<td><strong>Titre</strong></td>
<td>Tronqué 2 lignes max</td>
</tr>
<tr>
<td><strong>Créateur</strong></td>
<td>@pseudo + badge ✓ si vérifié, cliquable → profil</td>
</tr>
<tr>
<td><strong>Durée</strong></td>
<td>Format : "3 min" / "12 min" / "1h 24 min"</td>
</tr>
<tr>
<td><strong>Écoutes</strong></td>
<td>Arrondi : "2.3K" / "54K" / "1.2M"</td>
</tr>
<tr>
<td><strong>Localisation</strong></td>
<td>Ville + type géo (Ancré/Contextuel/Neutre)</td>
</tr>
<tr>
<td><strong>Tags</strong></td>
<td>Maximum 3 premiers tags</td>
</tr>
<tr>
<td><strong>Badge Premium</strong></td>
<td>👑 si contenu premium</td>
</tr>
<tr>
<td><strong>Distance</strong></td>
<td>Si recherche géo : "À 2.3 km"</td>
</tr>
</tbody>
</table>
<p><strong>Actions contextuelles [⋮]</strong> :
- Partager
- Ajouter à une playlist (future feature)
- Télécharger (offline)
- Signaler</p>
<p><strong>Pagination</strong> :
- <strong>20 résultats</strong> par page
- Infinite scroll (charger automatiquement si scroll &gt;80%)
- Bouton "Charger 20 suivants" en bas (fallback si scroll auto désactivé)</p>
<p><strong>Vue carte (alternative)</strong> :
- Bouton toggle "Liste / Carte"
- Map Leaflet (OpenStreetMap)
- Markers cliquables → popup avec preview
- Clustering si &gt;50 résultats proches</p>
<p><strong>Coût</strong> : 0€ (Leaflet open source + OSM tiles gratuit)</p>
<p><strong>Justification</strong> :
- Équilibre information / compacité
- Lazy loading = performances
- Infinite scroll = UX moderne</p>
<hr />
<h2 id="recapitulatif-section-15">Récapitulatif Section 15</h2>
<table>
<thead>
<tr>
<th>Point</th>
<th>Décision</th>
<th>Coût</th>
<th>Complexité</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>15.1.1</strong> Bouton partager</td>
<td>Disponible partout (⬆️), menu natif OS</td>
<td>0€</td>
<td>Faible</td>
</tr>
<tr>
<td><strong>15.1.2</strong> Lien partagé</td>
<td>Web player + deep link + Open Graph SEO</td>
<td>0€</td>
<td>Moyenne</td>
</tr>
<tr>
<td><strong>15.1.3</strong> Premium partagé</td>
<td>Preview 30s + paywall overlay</td>
<td>0€</td>
<td>Faible</td>
</tr>
<tr>
<td><strong>15.2.1</strong> Page profil</td>
<td>Profil public complet (stats + bio + contenus + tri)</td>
<td>0€</td>
<td>Faible</td>
</tr>
<tr>
<td><strong>15.2.2</strong> Stats publiques</td>
<td>Arrondies (abonnés, écoutes, durée totale)</td>
<td>0€</td>
<td>Faible</td>
</tr>
<tr>
<td><strong>15.2.3</strong> Badge vérifié</td>
<td>✓ si KYC/célébrité/&gt;10K abonnés</td>
<td>0€</td>
<td>Faible</td>
</tr>
<tr>
<td><strong>15.3.1</strong> Recherche texte</td>
<td>PostgreSQL full-text french + stemming</td>
<td>0€</td>
<td>Moyenne</td>
</tr>
<tr>
<td><strong>15.3.2</strong> Recherche géo</td>
<td>Lieu + rayon (Nominatim OSM)</td>
<td>0-50€/mois</td>
<td>Moyenne</td>
</tr>
<tr>
<td><strong>15.3.3</strong> Filtres</td>
<td>7 catégories combinables + sauvegarde recherches</td>
<td>0€</td>
<td>Moyenne</td>
</tr>
<tr>
<td><strong>15.3.4</strong> Page résultats</td>
<td>Liste enrichie + vue carte Leaflet + infinite scroll</td>
<td>0€</td>
<td>Moyenne</td>
</tr>
</tbody>
</table>
<p><strong>Coût total MVP : 0-50€/mois</strong> (Nominatim self-hosted optionnel)</p>
<hr />
<h2 id="points-dattention-pour-gherkin_1">Points d'attention pour Gherkin</h2>
<ul>
<li>Tester partage contenu public vs Premium (preview 30s)</li>
<li>Tester deep linking iOS/Android (ouverture app si installée)</li>
<li>Tester Open Graph (aperçu correct sur WhatsApp, Twitter, Facebook)</li>
<li>Tester profil public (stats arrondies, badge vérifié)</li>
<li>Tester recherche full-text français (stemming, accents)</li>
<li>Tester recherche géo + rayon (PostGIS distance)</li>
<li>Tester combinaison filtres multiples (AND logic)</li>
<li>Tester sauvegarde recherches (max 5)</li>
<li>Tester pagination infinite scroll + fallback bouton</li>
<li>Tester vue carte Leaflet (clustering, markers cliquables)</li>
</ul>
<div style="page-break-after: always;"></div>
<h2 id="16-audio-guides-multi-sequences_1">16. Audio-guides multi-séquences</h2>
<h3 id="161-types-daudio-guides-et-modes-de-deplacement">16.1 Types d'audio-guides et modes de déplacement</h3>
<p><strong>Décision</strong> : 4 modes distincts avec détection automatique</p>
<h4 id="1611-classification-par-mode">16.1.1 Classification par mode</h4>
<table>
<thead>
<tr>
<th>Mode</th>
<th>Vitesse détection</th>
<th>Déclenchement</th>
<th>Use case</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>🚶 Piéton</strong></td>
<td>&lt;5 km/h</td>
<td>Manuel (bouton "Suivant")</td>
<td>Musées, visites urbaines, monuments</td>
</tr>
<tr>
<td><strong>🚗 Voiture</strong></td>
<td>&gt;10 km/h</td>
<td>Auto GPS + Manuel possible</td>
<td>Safari-parc, routes touristiques, circuits auto</td>
</tr>
<tr>
<td><strong>🚴 Vélo</strong></td>
<td>5-25 km/h</td>
<td>Auto GPS + Manuel possible</td>
<td>Pistes cyclables, circuits vélo, parcours nature</td>
</tr>
<tr>
<td><strong>🚌 Transport</strong></td>
<td>Variable</td>
<td>Auto GPS + Manuel possible</td>
<td>Bus touristiques, trains panoramiques</td>
</tr>
</tbody>
</table>
<p><strong>Détection automatique</strong> :
- 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)</p>
<p><strong>Justification</strong> :
- 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)</p>
<hr />
<h4 id="1612-creation-dun-audio-guide-cote-createur">16.1.2 Création d'un audio-guide (côté créateur)</h4>
<p><strong>Formulaire création</strong> :</p>
<pre><code>┌────────────────────────────────────────┐
│ 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] │
└────────────────────────────────────────┘
</code></pre>
<p><strong>Métadonnées obligatoires</strong> :</p>
<table>
<thead>
<tr>
<th>Champ</th>
<th>Requis</th>
<th>Détails</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Titre audio-guide</strong></td>
<td>✅</td>
<td>5-100 caractères</td>
</tr>
<tr>
<td><strong>Description</strong></td>
<td>✅</td>
<td>10-500 caractères</td>
</tr>
<tr>
<td><strong>Mode déplacement</strong></td>
<td>✅</td>
<td>Piéton / Voiture / Vélo / Transport</td>
</tr>
<tr>
<td><strong>Nombre séquences</strong></td>
<td>✅</td>
<td>Minimum 2, maximum 50</td>
</tr>
<tr>
<td><strong>Point GPS par séquence</strong></td>
<td>✅ (sauf piéton)</td>
<td>Latitude, longitude (WGS84)</td>
</tr>
<tr>
<td><strong>Rayon déclenchement</strong></td>
<td>✅ (sauf piéton)</td>
<td>10-100m selon mode</td>
</tr>
<tr>
<td><strong>Vitesse recommandée</strong></td>
<td>❌</td>
<td>Optionnel, affichée utilisateur</td>
</tr>
<tr>
<td><strong>Tags</strong></td>
<td>✅</td>
<td>1-3 parmi liste prédéfinie</td>
</tr>
<tr>
<td><strong>Classification âge</strong></td>
<td>✅</td>
<td>Tout public / 13+ / 16+ / 18+</td>
</tr>
<tr>
<td><strong>Zone diffusion</strong></td>
<td>✅</td>
<td>Polygon géographique</td>
</tr>
</tbody>
</table>
<p><strong>Wizard de création</strong> :
- É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)</p>
<p><strong>Justification</strong> :
- Contrôle total créateur sur expérience
- Carte preview aide visualiser parcours
- Wizard guidé = réduction friction création</p>
<hr />
<h3 id="162-mode-pieton-manuel">16.2 Mode Piéton (manuel)</h3>
<p><strong>Décision</strong> : Navigation manuelle avec pub auto-play</p>
<h4 id="1621-passage-entre-sequences">16.2.1 Passage entre séquences</h4>
<p><strong>Séquence normale (sans pub)</strong> :</p>
<ol>
<li>Séquence 1 se termine</li>
<li>Player se met en <strong>pause automatique</strong></li>
<li>Message affiché : "Séquence 1 terminée. Appuyez sur Suivant quand vous êtes prêt."</li>
<li>User appuie sur [▶|] → Séquence 2 démarre immédiatement</li>
</ol>
<p><strong>Séquence avec publicité</strong> (1 pub / 5 séquences) :</p>
<ol>
<li>Séquence 2 se termine</li>
<li><strong>Publicité s'enchaîne automatiquement</strong> (pas d'attente bouton)</li>
<li>Pub se lit (skippable après 5s)</li>
<li>Pub se termine → Player se met en <strong>pause automatique</strong></li>
<li>Message : "Séquence 3 prête. Appuyez sur Suivant."</li>
<li>User appuie sur [▶|] → Séquence 3 démarre</li>
</ol>
<p><strong>Schéma flux</strong> :</p>
<pre><code>Séquence 1 [fin] → PAUSE → User clique → Séquence 2 [fin] → PUB AUTO-PLAY → PAUSE → User clique → Séquence 3
</code></pre>
<p><strong>Fréquence pub</strong> :
- Gratuits : 1 pub toutes les 5 séquences (paramétrable admin 1/3 à 1/10)
- Premium : 0 pub</p>
<p><strong>Justification</strong> :
- 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)</p>
<hr />
<h4 id="1622-navigation-et-controles">16.2.2 Navigation et contrôles</h4>
<p><strong>Décision</strong> : Liberté totale utilisateur</p>
<p><strong>Contrôles disponibles</strong> :</p>
<table>
<thead>
<tr>
<th>Bouton</th>
<th>Fonction</th>
<th>Comportement</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>[▶|] Suivant</strong></td>
<td>Passe séquence suivante</td>
<td>Immédiat, même si séquence actuelle pas terminée</td>
</tr>
<tr>
<td><strong>[|◀] Précédent</strong></td>
<td>Retour séquence précédente</td>
<td>Saut direct séquence avant (pas de logique "replay si &gt;10s")</td>
</tr>
<tr>
<td><strong>[⏸️] Pause</strong></td>
<td>Pause temporaire</td>
<td>Reprend à position exacte</td>
</tr>
<tr>
<td><strong>[▶️] Play</strong></td>
<td>Reprend lecture</td>
<td>Continue position actuelle</td>
</tr>
<tr>
<td><strong>Liste séquences</strong></td>
<td>Navigation libre</td>
<td>Tap séquence → saut direct (même séquences non écoutées)</td>
</tr>
</tbody>
</table>
<p><strong>Interface liste séquences</strong> :</p>
<pre><code>┌────────────────────────────────────────┐
│ 🚶 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 ▼] │
└────────────────────────────────────────┘
</code></pre>
<p><strong>Navigation libre</strong> :
- 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)</p>
<p><strong>Sauvegarde progression</strong> :
- Checkmarks ✅ sur séquences écoutées &gt;80%
- Position exacte sauvegardée dans séquence en cours</p>
<p><strong>Justification</strong> :
- 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)</p>
<hr />
<h3 id="163-mode-voiture-gps-automatique">16.3 Mode Voiture (GPS automatique)</h3>
<p><strong>Décision</strong> : GPS auto avec navigation manuelle conservée</p>
<h4 id="1631-declenchement-et-controles">16.3.1 Déclenchement et contrôles</h4>
<p><strong>Distinction audio-guides vs contenus géolocalisés simples</strong> :</p>
<p>⚠️ <strong>Important</strong> : Les audio-guides multi-séquences fonctionnent différemment des contenus géolocalisés simples.</p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Séquences</th>
<th>Déclenchement</th>
<th>Notification</th>
<th>Enchaînement</th>
<th>Comptabilité quota</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Contenu géolocalisé simple</strong></td>
<td>1 séquence unique</td>
<td>Notification 7s avant (temps ETA)</td>
<td>Sonore + icône</td>
<td>Fin → retour buffer normal</td>
<td>1 contenu = 1 quota</td>
</tr>
<tr>
<td><strong>Audio-guide multi-séquences</strong></td>
<td>2 à 50 séquences</td>
<td>Au point GPS exact (distance 30m)</td>
<td>Ding + toast 2s</td>
<td>Séquences s'enchaînent auto</td>
<td>1 audio-guide = 1 quota (toutes séquences)</td>
</tr>
</tbody>
</table>
<p><strong>Fonctionnement GPS automatique</strong> :</p>
<ol>
<li>User démarre audio-guide en voiture (voir section 16.1 pour démarrage)</li>
<li>Séquence 1 démarre automatiquement au point GPS défini (rayon 30m)</li>
<li>Séquence 1 se termine</li>
<li><strong>Affichage progress bar</strong> : distance temps réel + ETA jusqu'au prochain point</li>
<li>User roule vers point GPS suivant</li>
<li>Arrivée au point GPS suivant (rayon 30m) → <strong>déclenchement automatique</strong> séquence suivante</li>
<li>Notification sonore discrète : "Ding" (0.3s) + toast 2s : "Enclos des girafes"</li>
<li>Séquence suivante démarre immédiatement (pas de décompte)</li>
</ol>
<p><strong>Pas de système "7 secondes avant" pour les audio-guides</strong> :
- Contrairement aux contenus géolocalisés simples (voir <a href="#05-interactions-navigation.md#511-file-dattente-et-commande-suivant">05-interactions-navigation.md</a>)
- Les séquences se déclenchent <strong>au point GPS exact</strong> (rayon 30m)
- Raison : expérience guidée continue, user sait qu'il suit un parcours</p>
<p><strong>Navigation manuelle CONSERVÉE</strong> :</p>
<table>
<thead>
<tr>
<th>Bouton</th>
<th>État</th>
<th>Comportement</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>[▶|] Suivant</strong></td>
<td>✅ Toujours actif</td>
<td>Passe séquence suivante immédiatement (même hors point GPS)</td>
</tr>
<tr>
<td><strong>[|◀] Précédent</strong></td>
<td>✅ Toujours actif</td>
<td>Retour séquence précédente (même hors point GPS)</td>
</tr>
<tr>
<td><strong>[⏸️] Pause</strong></td>
<td>✅</td>
<td>Pause temporaire</td>
</tr>
<tr>
<td><strong>Liste séquences</strong></td>
<td>✅</td>
<td>Saut direct possible</td>
</tr>
</tbody>
</table>
<p><strong>Use cases navigation manuelle</strong> :</p>
<table>
<thead>
<tr>
<th>Situation</th>
<th>Solution manuelle</th>
</tr>
</thead>
<tbody>
<tr>
<td>Embouteillage (séquence finie, point GPS loin)</td>
<td>User clique Suivant → avance manuellement</td>
</tr>
<tr>
<td>Point GPS inaccessible (route fermée)</td>
<td>User clique Suivant → skip point</td>
</tr>
<tr>
<td>Envie réécouter séquence précédente</td>
<td>User clique Précédent → retour</td>
</tr>
<tr>
<td>Passager manipule l'app</td>
<td>Passager navigue librement</td>
</tr>
</tbody>
</table>
<p><strong>Avertissement sécurité</strong> :</p>
<ul>
<li>Si vitesse <strong>&gt;10 km/h</strong> ET user clique bouton (Suivant/Précédent) :</li>
<li>Toast 3 secondes : "⚠️ Manipulation en conduite détectée. Pour votre sécurité, demandez à un passager."</li>
<li><strong>Action quand même exécutée</strong> (pas de blocage)</li>
<li>Justification : sensibilisation sans bloquer (passager peut légitimement manipuler)</li>
</ul>
<p><strong>Schéma flux</strong> :</p>
<pre><code>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
</code></pre>
<p><strong>Justification</strong> :
- 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)</p>
<hr />
<h4 id="1632-affichage-distance-et-guidage">16.3.2 Affichage distance et guidage</h4>
<p><strong>Décision</strong> : Distance + direction (PAS de carte miniature)</p>
<p><strong>Interface en conduite</strong> :</p>
<pre><code>┌────────────────────────────────────────┐
│ 🚗 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] │
└────────────────────────────────────────┘
</code></pre>
<p><strong>Affichage entre deux séquences</strong> :</p>
<p>Quand une séquence se termine et qu'il reste un point GPS suivant, l'interface bascule en mode "attente prochain point" :</p>
<pre><code>┌────────────────────────────────────────┐
│ 🚗 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.] [▶|] │
└────────────────────────────────────────┘
</code></pre>
<p><strong>Progress bar dynamique</strong> :
- Se remplit au fur et à mesure qu'on se rapproche du point
- Calcul : <code>progress = 100 - (distance_actuelle / distance_initiale * 100)</code>
- Exemple : distance initiale 500m, distance actuelle 175m → progress = 65%
- Couleur : vert (#4CAF50) pour la partie remplie, gris (#E0E0E0) pour le reste</p>
<p><strong>Bouton "Rejouer séq."</strong> :
- 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</p>
<hr />
<p><strong>Informations affichées</strong> :</p>
<table>
<thead>
<tr>
<th>Info</th>
<th>Mise à jour</th>
<th>Format</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Distance</strong></td>
<td>Chaque seconde</td>
<td>"320 m" / "1.2 km"</td>
</tr>
<tr>
<td><strong>ETA</strong></td>
<td>Chaque seconde</td>
<td>"≈ 40 secondes" / "≈ 2 minutes"</td>
</tr>
<tr>
<td><strong>Direction</strong></td>
<td>Chaque 5s</td>
<td>Flèche indique direction (8 directions : ↑ ↗ → ↘ ↓ ↙ ← ↖)</td>
</tr>
<tr>
<td><strong>Vitesse actuelle</strong></td>
<td>Chaque seconde</td>
<td>"28 km/h"</td>
</tr>
<tr>
<td><strong>Vitesse recommandée</strong></td>
<td>Statique</td>
<td>"20-30 km/h" (définie par créateur)</td>
</tr>
<tr>
<td><strong>Progress bar</strong></td>
<td>Chaque seconde</td>
<td>Pourcentage parcouru vers prochain point</td>
</tr>
</tbody>
</table>
<p><strong>Calcul direction</strong> :</p>
<pre><code class="language-javascript">// 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];
</code></pre>
<p><strong>Calcul ETA</strong> :</p>
<pre><code class="language-javascript">const distance = calculateDistance(currentGPS, nextPoint); // mètres
const currentSpeed = getCurrentSpeed(); // km/h
if (currentSpeed &gt; 5) {
const eta = (distance / 1000) / currentSpeed * 3600; // secondes
return formatETA(eta); // &quot;≈ 40 secondes&quot; ou &quot;≈ 2 minutes&quot;
} else {
return &quot;En attente de déplacement&quot;;
}
</code></pre>
<p><strong>Justification</strong> :
- 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)</p>
<hr />
<h4 id="1633-rayon-de-declenchement-et-tolerance">16.3.3 Rayon de déclenchement et tolérance</h4>
<p><strong>Décision</strong> : Rayon configurable créateur avec défauts intelligents</p>
<p><strong>Rayons par défaut</strong> :</p>
<table>
<thead>
<tr>
<th>Mode</th>
<th>Rayon déclenchement</th>
<th>Rayon "point manqué"</th>
<th>Justification</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>🚗 Voiture</strong></td>
<td>30 mètres</td>
<td>100 mètres</td>
<td>Vitesse élevée = anticipation</td>
</tr>
<tr>
<td><strong>🚴 Vélo</strong></td>
<td>50 mètres</td>
<td>75 mètres</td>
<td>Vitesse variable, arrêts fréquents</td>
</tr>
<tr>
<td><strong>🚌 Transport</strong></td>
<td>100 mètres</td>
<td>150 mètres</td>
<td>Arrêts bus/train, moins précis</td>
</tr>
</tbody>
</table>
<p><strong>Configuration créateur</strong> :</p>
<ul>
<li>Curseur rayon : <strong>10m → 200m</strong></li>
<li>Défaut pré-sélectionné selon mode choisi</li>
<li>Preview visuel : cercle sur carte (lors création)</li>
<li>Suggestion auto : "Recommandé : 30m pour voiture à 30 km/h"</li>
</ul>
<p><strong>Gestion point manqué</strong> :</p>
<pre><code>User passe à 110m du point GPS
(hors rayon déclenchement 30m MAIS dans rayon tolérance 100m)
Toast : &quot;⚠️ Point manqué : Enclos des girafes&quot;
Popup 5 secondes :
┌────────────────────────────────────┐
│ Point manqué │
│ │
│ &quot;Enclos des girafes&quot; │
│ Vous êtes passé à 110m du point │
│ │
│ [🔊 Écouter quand même] │
│ [⏭️ Passer au suivant] │
│ [🔙 Faire demi-tour] │
└────────────────────────────────────┘
</code></pre>
<p><strong>Actions popup</strong> :</p>
<table>
<thead>
<tr>
<th>Bouton</th>
<th>Comportement</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Écouter quand même</strong></td>
<td>Lance séquence immédiatement (même hors zone)</td>
</tr>
<tr>
<td><strong>Passer au suivant</strong></td>
<td>Skip séquence, continue vers prochain point</td>
</tr>
<tr>
<td><strong>Faire demi-tour</strong></td>
<td>Lance navigation GPS externe (Google Maps / Waze) vers point manqué</td>
</tr>
</tbody>
</table>
<p><strong>Si user au-delà rayon tolérance (&gt;100m)</strong> :
- Aucun popup (point trop loin, probablement hors itinéraire)
- User peut naviguer manuellement (bouton Suivant)</p>
<p><strong>Justification</strong> :
- Flexibilité créateur (ajuste selon terrain, vitesse prévue)
- Gestion intelligente imprévus (détours, routes fermées)
- User pas bloqué (toujours moyen avancer)</p>
<hr />
<h3 id="164-modes-velo-et-transport">16.4 Modes Vélo et Transport</h3>
<p><strong>Décision</strong> : Même logique voiture avec tolérances ajustées</p>
<p><strong>Différences par rapport à mode voiture</strong> :</p>
<table>
<thead>
<tr>
<th>Paramètre</th>
<th>Voiture</th>
<th>Vélo</th>
<th>Transport</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Rayon déclenchement</strong></td>
<td>30m</td>
<td>50m</td>
<td>100m</td>
</tr>
<tr>
<td><strong>Rayon tolérance "point manqué"</strong></td>
<td>100m</td>
<td>75m</td>
<td>150m</td>
</tr>
<tr>
<td><strong>Vitesse recommandée affichée</strong></td>
<td>20-50 km/h</td>
<td>10-25 km/h</td>
<td>Variable (selon ligne)</td>
</tr>
<tr>
<td><strong>Warning sécurité</strong></td>
<td>&gt;10 km/h</td>
<td>&gt;5 km/h</td>
<td>Désactivé</td>
</tr>
</tbody>
</table>
<p><strong>Mode Vélo spécificités</strong> :</p>
<ul>
<li>Rayon plus large : vitesse variable, nombreux arrêts (feux, piétons)</li>
<li>Warning sécurité dès 5 km/h (vélo en mouvement)</li>
<li>Tolérance GPS moins stricte (tracé moins prévisible qu'auto)</li>
</ul>
<p><strong>Mode Transport spécificités</strong> :</p>
<ul>
<li>Rayon très large : arrêts fréquents (bus, train), ligne fixe</li>
<li>Pas de warning sécurité (user = passager, pas conducteur)</li>
<li>Vitesse recommandée = "Selon ligne" (pas de valeur fixe)</li>
<li>Tolérance horaire : si bus en retard, point peut se déclencher avec 2-3 min de délai</li>
</ul>
<p><strong>Comportement identique voiture</strong> :</p>
<ul>
<li>Navigation manuelle conservée (boutons actifs)</li>
<li>Affichage distance + ETA + direction</li>
<li>Gestion point manqué</li>
<li>Pub entre séquences</li>
</ul>
<p><strong>Justification</strong> :
- 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</p>
<hr />
<h3 id="165-publicites-dans-audio-guides">16.5 Publicités dans audio-guides</h3>
<p><strong>Décision</strong> : Pub auto-play entre séquences TOUS modes</p>
<h4 id="1651-regles-universelles">16.5.1 Règles universelles</h4>
<p><strong>Insertion publicité</strong> :</p>
<ul>
<li>Fréquence : <strong>1 pub toutes les 5 séquences</strong> (paramétrable admin 1/3 à 1/10)</li>
<li>Gratuits uniquement, <strong>Premium 0 pub</strong></li>
<li>Pub s'enchaîne <strong>automatiquement</strong> après séquence</li>
<li>Skippable après <strong>5 secondes</strong> (règle standard RoadWave)</li>
<li>Volume normalisé -14 LUFS (comme pubs normales)</li>
</ul>
<p><strong>Comportement MODE PIÉTON</strong> :</p>
<pre><code>Séquence 2 [fin]
→ Pub AUTO-PLAY
→ Pub se termine
→ PAUSE AUTO
→ Message &quot;Séquence 3 prête. Appuyez sur Suivant.&quot;
→ User clique [▶|]
→ Séquence 3 démarre
</code></pre>
<p><strong>Comportement MODE VOITURE/VÉLO/TRANSPORT</strong> :</p>
<pre><code>Séquence 2 [fin]
→ Pub AUTO-PLAY
→ Pub se termine
→ ATTENTE point GPS suivant OU user clique Suivant
→ Séquence 3 démarre
</code></pre>
<p><strong>Schéma complet</strong> :</p>
<table>
<thead>
<tr>
<th>Mode</th>
<th>Après séquence normale</th>
<th>Après pub</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Piéton</strong></td>
<td>Pause + attente user</td>
<td>Pause + attente user</td>
</tr>
<tr>
<td><strong>Voiture</strong></td>
<td>Attente GPS OU user clique Suivant</td>
<td>Attente GPS OU user clique Suivant</td>
</tr>
<tr>
<td><strong>Vélo</strong></td>
<td>Attente GPS OU user clique Suivant</td>
<td>Attente GPS OU user clique Suivant</td>
</tr>
<tr>
<td><strong>Transport</strong></td>
<td>Attente GPS OU user clique Suivant</td>
<td>Attente GPS OU user clique Suivant</td>
</tr>
</tbody>
</table>
<p><strong>Justification</strong> :
- 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</p>
<hr />
<h4 id="1652-metriques-pub-audio-guides">16.5.2 Métriques pub audio-guides</h4>
<p><strong>Dashboard créateur</strong> :</p>
<table>
<thead>
<tr>
<th>Métrique</th>
<th>Affichage</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Impressions pub</strong></td>
<td>Nombre de pubs insérées dans audio-guides</td>
</tr>
<tr>
<td><strong>Écoutes complètes pub</strong></td>
<td>Nombre de pubs écoutées &gt;80%</td>
</tr>
<tr>
<td><strong>Taux skip pub</strong></td>
<td>% pubs skippées avant 5s vs après</td>
</tr>
<tr>
<td><strong>Revenus pub audio-guides</strong></td>
<td>3€ / 1000 écoutes complètes (6% CA pub)</td>
</tr>
</tbody>
</table>
<p><strong>Distinction contenus normaux vs audio-guides</strong> :
- Dashboard sépare : "Revenus contenus classiques" / "Revenus audio-guides"
- Permet créateur voir performance par type</p>
<p><strong>Justification</strong> :
- Transparence créateur (comprend revenus)
- Incite création audio-guides (nouvelle source revenus)</p>
<hr />
<h3 id="166-reprise-et-sauvegarde-progression">16.6 Reprise et sauvegarde progression</h3>
<p><strong>Décision</strong> : Sauvegarde complète automatique avec popup intelligente</p>
<h4 id="1661-sauvegarde-automatique">16.6.1 Sauvegarde automatique</h4>
<p><strong>Données sauvegardées</strong> :</p>
<table>
<thead>
<tr>
<th>Info</th>
<th>Détail</th>
<th>Utilité</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Audio-guide ID</strong></td>
<td>Identifiant unique</td>
<td>Retrouver audio-guide</td>
</tr>
<tr>
<td><strong>Séquence actuelle</strong></td>
<td>Index (ex: 3/12)</td>
<td>Reprise position</td>
</tr>
<tr>
<td><strong>Position dans séquence</strong></td>
<td>Timestamp exact (ex: 1:42/3:20)</td>
<td>Reprise exacte</td>
</tr>
<tr>
<td><strong>Séquences écoutées</strong></td>
<td>Liste avec checkmarks ✅</td>
<td>Historique progression</td>
</tr>
<tr>
<td><strong>Date dernière écoute</strong></td>
<td>Timestamp</td>
<td>Proposer reprise si &lt;30j</td>
</tr>
<tr>
<td><strong>GPS dernière position</strong></td>
<td>Coordonnées optionnelles</td>
<td>Info contextuelle (non utilisée pour reprise)</td>
</tr>
</tbody>
</table>
<p><strong>Stockage</strong> :</p>
<table>
<thead>
<tr>
<th>Environnement</th>
<th>Technologie</th>
<th>Utilité</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Local</strong></td>
<td>SQLite mobile</td>
<td>Fonctionnement offline</td>
</tr>
<tr>
<td><strong>Cloud</strong></td>
<td>PostgreSQL (sync auto)</td>
<td>Multi-device (reprendre sur autre appareil)</td>
</tr>
</tbody>
</table>
<p><strong>Synchronisation</strong> :
- Sauvegarde locale : chaque fin de séquence + chaque 30s
- Sync cloud : à la reconnexion réseau (batch)</p>
<p><strong>Justification</strong> :
- Expérience fluide (pas de perte progression)
- Multi-device (démarrer sur iPhone, continuer sur iPad)
- Offline-first (fonctionne sans réseau)</p>
<hr />
<h4 id="1662-interface-de-reprise">16.6.2 Interface de reprise</h4>
<p><strong>Conditions popup</strong> :
- Dernière écoute <strong>&lt;30 jours</strong>
- Progression <strong>&gt;0%</strong> et <strong>&lt;100%</strong> (pas terminé)</p>
<p><strong>Popup reprise</strong> :</p>
<pre><code>┌────────────────────────────────────────┐
│ 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 à : │
│ &quot;Les lions&quot; (1:42/3:20) │
│ │
│ [▶️ Reprendre] [🔄 Recommencer] │
│ [📋 Voir toutes les séquences] │
└────────────────────────────────────────┘
</code></pre>
<p><strong>Actions</strong> :</p>
<table>
<thead>
<tr>
<th>Bouton</th>
<th>Comportement</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Reprendre</strong></td>
<td>Continue séquence 3 à position 1:42 exacte</td>
</tr>
<tr>
<td><strong>Recommencer</strong></td>
<td>Reset progression, démarre séquence 1 depuis 0:00</td>
</tr>
<tr>
<td><strong>Voir séquences</strong></td>
<td>Affiche liste complète, user choisit séquence départ</td>
</tr>
</tbody>
</table>
<p><strong>Expiration progression</strong> :
- Progression conservée <strong>30 jours</strong>
- Après 30j : popup "Audio-guide expiré. Recommencez depuis le début ?"
- Suppression données progression (mais historique "écouté" préservé)</p>
<p><strong>Justification</strong> :
- 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</p>
<hr />
<h4 id="1663-multi-device">16.6.3 Multi-device</h4>
<p><strong>Scénario</strong> :</p>
<ol>
<li>User démarre audio-guide sur iPhone (séquences 1-3)</li>
<li>Progression sync cloud</li>
<li>Lendemain : user ouvre app sur iPad</li>
<li>Popup : "Reprendre Safari du Paugre sur cet appareil ?"</li>
<li>User clique Reprendre → continue séquence 4</li>
</ol>
<p><strong>Conflit de version</strong> :
- Si modifications simultanées 2 appareils (rare) : <strong>dernière modification gagne</strong>
- Toast : "Progression mise à jour depuis votre autre appareil"</p>
<p><strong>Justification</strong> :
- Confort utilisateur (change d'appareil librement)
- Use case réel : planning trajet sur tablette, écoute sur smartphone en voiture</p>
<hr />
<h2 id="recapitulatif-section-16">Récapitulatif Section 16</h2>
<table>
<thead>
<tr>
<th>Point</th>
<th>Décision</th>
<th>Coût</th>
<th>Complexité</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>16.1</strong> Types audio-guides</td>
<td>4 modes (piéton/voiture/vélo/transport) avec détection auto</td>
<td>0€</td>
<td>Moyenne</td>
</tr>
<tr>
<td><strong>16.1.2</strong> Création</td>
<td>Formulaire séquences + GPS + rayon + wizard guidé</td>
<td>0€</td>
<td>Moyenne</td>
</tr>
<tr>
<td><strong>16.2.1</strong> Piéton - Passages</td>
<td>Manuel AVEC pub auto-play entre séquences, pause après</td>
<td>0€</td>
<td>Faible</td>
</tr>
<tr>
<td><strong>16.2.2</strong> Piéton - Navigation</td>
<td>Liberté totale (skip, retour, saut direct liste)</td>
<td>0€</td>
<td>Faible</td>
</tr>
<tr>
<td><strong>16.3.1</strong> Voiture - Déclenchement</td>
<td>GPS auto + boutons manuels actifs (warning sécurité si &gt;10 km/h)</td>
<td>0€</td>
<td>Moyenne</td>
</tr>
<tr>
<td><strong>16.3.2</strong> Voiture - Affichage</td>
<td>Distance + ETA + direction (flèche) + vitesse (PAS de carte)</td>
<td>0€</td>
<td>Faible</td>
</tr>
<tr>
<td><strong>16.3.3</strong> Voiture - Rayon</td>
<td>Configurable créateur (défauts 30m/50m/100m selon mode)</td>
<td>0€</td>
<td>Faible</td>
</tr>
<tr>
<td><strong>16.4</strong> Vélo &amp; Transport</td>
<td>Mêmes règles avec tolérances ajustées + warning adapté</td>
<td>0€</td>
<td>Faible</td>
</tr>
<tr>
<td><strong>16.5</strong> Publicités</td>
<td>1/5 séquences, auto-play TOUS modes, skippable 5s</td>
<td>0€</td>
<td>Faible</td>
</tr>
<tr>
<td><strong>16.6.1</strong> Sauvegarde</td>
<td>Complète (séquence + position + historique) local + cloud</td>
<td>0€</td>
<td>Faible</td>
</tr>
<tr>
<td><strong>16.6.2</strong> Reprise</td>
<td>Popup intelligente avec choix (reprendre/recommencer), expiration 30j</td>
<td>0€</td>
<td>Faible</td>
</tr>
<tr>
<td><strong>16.6.3</strong> Multi-device</td>
<td>Sync cloud PostgreSQL (reprendre sur autre appareil)</td>
<td>0€</td>
<td>Faible</td>
</tr>
</tbody>
</table>
<p><strong>Coût total MVP : 0€</strong> (GPS natif, calcul distance PostGIS)</p>
<hr />
<h2 id="points-dattention-pour-gherkin_2">Points d'attention pour Gherkin</h2>
<ul>
<li>Tester 4 modes audio-guides (détection vitesse auto)</li>
<li>Tester création séquences avec points GPS + rayon configurable</li>
<li>Tester mode piéton : pause après séquence + pub auto-play + pause après pub + clic Suivant</li>
<li>Tester navigation libre piéton (skip, retour, saut direct liste)</li>
<li>Tester mode voiture : déclenchement GPS auto rayon 30m</li>
<li>Tester navigation manuelle voiture : boutons actifs + warning si vitesse &gt;10 km/h</li>
<li>Tester affichage distance + ETA + direction (flèche 8 directions)</li>
<li>Tester rayon tolérance "point manqué" (popup 3 actions)</li>
<li>Tester mode vélo (rayon 50m) et transport (rayon 100m)</li>
<li>Tester insertion pub 1/5 séquences tous modes avec auto-play</li>
<li>Tester sauvegarde progression locale + sync cloud</li>
<li>Tester popup reprise (3 boutons : reprendre/recommencer/voir liste)</li>
<li>Tester expiration progression 30 jours</li>
<li>Tester multi-device : démarrer iPhone, continuer iPad</li>
<li>Tester gestion conflit progression simultanée 2 appareils</li>
</ul>
<div style="page-break-after: always;"></div>
<h1 id="annexe-fonctionnalites-reportees-post-mvp">Annexe : Fonctionnalités reportées Post-MVP</h1>
<p><strong>Date</strong> : 2026-01-19
<strong>Statut</strong> : Fonctionnalités validées mais reportées après le MVP</p>
<hr />
<h2 id="sommaire">Sommaire</h2>
<ol>
<li><a href="##1-classification-politique-et-équilibre-éditorial">Classification politique et équilibre éditorial</a></li>
<li><a href="##2-système-de-pourboires-créateurs">Système de pourboires créateurs</a></li>
</ol>
<hr />
<h2 id="1-classification-politique-et-equilibre-editorial">1. Classification politique et équilibre éditorial</h2>
<blockquote>
<p>⚠️ <strong>Reporté post-MVP</strong> pour raisons de coût, complexité et risques juridiques.</p>
</blockquote>
<h3 id="contexte-du-report">Contexte du report</h3>
<p><strong>Raisons</strong> :
- <strong>Coût modération</strong> : Classification manuelle humaine très coûteuse (~2000€/mois pour 1-2 modérateurs senior full-time)
- <strong>Risque juridique</strong> : Accusations de biais éditorial, contentieux DSA
- <strong>Complexité technique</strong> : Dashboard audit, logs 3 ans, alertes déséquilibre
- <strong>Controverse</strong> : Peut créer polémique dès le lancement
- <strong>Pas essentiel MVP</strong> : L'application fonctionne sans ce système</p>
<p><strong>Version MVP</strong> (actuelle) :
- Tag "Politique" simple (comme "Économie", "Sport")
- Pas de classification gauche/droite
- Pas d'équilibrage imposé
- Option utilisateur "Masquer politique" → 0% contenus politiques</p>
<hr />
<h3 id="specifications-completes-future-implementation">Spécifications complètes (future implémentation)</h3>
<p><strong>Échelle de classification</strong> (5 niveaux) :
- 🔴 <strong>Extrême gauche</strong> (anticapitalisme radical, révolution)
- 🟠 <strong>Gauche</strong> (écologie, social, critique capitalisme modérée)
- ⚪ <strong>Centre/Neutre</strong> (pas de positionnement politique clair)
- 🔵 <strong>Droite</strong> (sécurité, tradition, économie libérale)
- 🟣 <strong>Extrême droite</strong> (nationalisme radical, conservatisme extrême)
- 🟢 <strong>Non politique</strong> (enfants, musique, fiction, culture générale)</p>
<p><strong>Qui classifie</strong> :
- ❌ 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</p>
<p><strong>Affichage</strong> :
- Badge politique visible : <strong>au choix de l'utilisateur</strong> (paramètre "Afficher orientation politique")
- Par défaut : badges masqués (UX neutre)</p>
<p><strong>Règles de diffusion (équilibre imposé)</strong> :</p>
<table>
<thead>
<tr>
<th>Préférence utilisateur</th>
<th>Répartition</th>
<th>Justification</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Équilibré</strong> (défaut)</td>
<td>35% gauche / 35% droite / 30% centre-neutre</td>
<td>Neutralité plateforme</td>
</tr>
<tr>
<td><strong>Plutôt gauche</strong></td>
<td>50% gauche / 20% droite / 30% centre-neutre</td>
<td>Préférence respectée avec minimum opposition</td>
</tr>
<tr>
<td><strong>Plutôt droite</strong></td>
<td>50% droite / 20% gauche / 30% centre-neutre</td>
<td>Préférence respectée avec minimum opposition</td>
</tr>
<tr>
<td><strong>Masquer politique</strong></td>
<td>0% gauche / 0% droite / 100% centre-neutre + non politique</td>
<td>Option apolitique</td>
</tr>
</tbody>
</table>
<p><strong>Audit et conformité DSA</strong> :
- Rapport hebdomadaire automatique : % gauche/droite/centre diffusé par utilisateur
- Alerte si déséquilibre global plateforme (&gt;55% d'un bord)
- Logs conservés <strong>3 ans</strong> (exigence Digital Services Act EU)
- Dashboard admin : visualisation répartition temps réel</p>
<p><strong>Sanctions mauvaise classification</strong> :
- Classification volontairement incorrecte = Strike 1
- Récidive = Strike 2 (suspension 7j)
- Détection via signalements utilisateurs + audit modération</p>
<p><strong>Justification</strong> :
- <strong>Conformité juridique DSA</strong> (obligation neutralité plateforme EU)
- Protection contre accusations de biais éditorial
- Transparence auditable
- Coût : temps modération humaine (incompressible)</p>
<hr />
<h3 id="conditions-de-reintegration">Conditions de réintégration</h3>
<p><strong>Prérequis</strong> :
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</p>
<p><strong>Chronologie estimée</strong> :
- 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</p>
<hr />
<h2 id="2-systeme-de-pourboires-createurs">2. Système de pourboires créateurs</h2>
<blockquote>
<p>⚠️ <strong>Reporté post-MVP</strong> - Fonctionnalité crypto (Lightning Network) prévue ultérieurement.</p>
</blockquote>
<h3 id="contexte-du-report_1">Contexte du report</h3>
<p><strong>Raisons</strong> :
- <strong>Complexité technique</strong> : Intégration Lightning Network, gestion wallets crypto
- <strong>Réglementation</strong> : Incertitude juridique crypto en EU (MiCA 2025)
- <strong>Focus MVP</strong> : Priorité sur monétisation via abonnements Premium et publicités
- <strong>Adoption utilisateurs</strong> : Nécessite éducation et adoption crypto préalables</p>
<p><strong>Version MVP</strong> (actuelle) :
- Monétisation créateurs via :
- Partage revenus publicités (3€ CPM)
- 70% revenus abonnements Premium</p>
<hr />
<h3 id="specifications-completes-future-implementation_1">Spécifications complètes (future implémentation)</h3>
<p><strong>Système prévu</strong> : Micro-dons via Lightning Network (Bitcoin Layer 2)</p>
<p><strong>Fonctionnement</strong> :
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 &lt;0.01€)
4. Créateur reçoit directement dans wallet Lightning
5. Conversion EUR/BTC automatique (optionnelle)</p>
<p><strong>Avantages Lightning Network</strong> :
- ✅ Frais quasi-nuls (&lt;1%) vs 1.8% Mangopay
- ✅ Transactions instantanées (&lt;1 seconde)
- ✅ Micropaiements possibles (dès 0.01€)
- ✅ International sans frais supplémentaires
- ✅ Pas d'intermédiaire (peer-to-peer)</p>
<p><strong>Contraintes</strong> :
- ❌ 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</p>
<p><strong>Alternatives étudiées</strong> :
- 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</p>
<hr />
<h3 id="conditions-de-reintegration_1">Conditions de réintégration</h3>
<p><strong>Prérequis</strong> :
1. Réglementation MiCA stabilisée et conforme
2. Adoption crypto suffisante dans la base utilisateurs (&gt;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</p>
<p><strong>Chronologie estimée</strong> :
- 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</p>
<hr />
<h2 id="autres-fonctionnalites-candidates-post-mvp">Autres fonctionnalités candidates Post-MVP</h2>
<p>Liste non exhaustive de fonctionnalités évoquées mais non encore spécifiées :</p>
<ul>
<li><strong>Mode offline avancé</strong> : Téléchargement automatique zones fréquentes</li>
<li><strong>Playlists collaboratives</strong> : Co-création de playlists géolocalisées</li>
<li><strong>API publique créateurs</strong> : Intégration RSS, podcasts existants</li>
<li><strong>Gamification</strong> : Badges, défis géolocalisés, leaderboards</li>
<li><strong>Mode nuit</strong> : Interface sombre automatique</li>
<li><strong>Statistiques avancées créateurs</strong> : Démographie, retention, heatmaps GPS</li>
</ul>
<p>Ces fonctionnalités seront spécifiées et priorisées selon les retours utilisateurs MVP.</p>
<hr />
<h2 id="suivi-et-validation">Suivi et validation</h2>
<p><strong>Responsable</strong> : Product Owner
<strong>Révision</strong> : Trimestrielle
<strong>Critères de priorisation</strong> :
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</p>
<div style="page-break-after: always;"></div>
<h1 id="audio-guides-multi-sequences-pour-pietons">Audio-guides multi-séquences pour piétons</h1>
<blockquote>
<p><em>En tant qu'auditeur à pied</em>
<em>Je veux profiter d'audio-guides structurés lors de mes visites</em>
<em>Afin de découvrir des lieux de manière autonome et à mon rythme</em></p>
</blockquote>
<p><strong>29 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis connecté en tant qu'auditeur
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis en mode piéton (vitesse &lt;5 km/h)</p>
</blockquote>
<h2 id="1-detection-daudio-guide-a-proximite">1. Détection d'audio-guide à proximité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je me trouve à 80 mètres du Musée du Louvre
<span style="color: #9E9E9E"><strong>Et</strong></span> que 3 audio-guides sont disponibles pour ce lieu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte ma position</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification push:</p>
<hr />
<h2 id="2-rayon-de-detection-de-100m">2. Rayon de détection de 100m</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide est centré aux coordonnées GPS du Louvre</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je suis à exactement 100m du centre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la notification est déclenchée
<span style="color: #9E9E9E"><strong>Et</strong></span> quand je suis à 101m, aucune notification n'est envoyée</p>
<hr />
<h2 id="3-page-de-selection-des-audio-guides">3. Page de sélection des audio-guides</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai tapé sur la notification audio-guide</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la page de sélection s'affiche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois une liste de guides disponibles:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="4-selection-dun-audio-guide">4. Sélection d'un audio-guide</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis sur la page de sélection</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je tape sur "Visite complète (45 min)"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'interface de lecture d'audio-guide s'ouvre
<span style="color: #9E9E9E"><strong>Et</strong></span> la séquence 1 commence automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois la liste complète des 12 séquences</p>
<hr />
<h2 id="5-interface-de-lecture-audio-guide">5. Interface de lecture audio-guide</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai sélectionné un audio-guide de 12 séquences</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'interface s'affiche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| é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 |
</code></pre>
<hr />
<h2 id="6-navigation-vers-sequence-suivante">6. Navigation vers séquence suivante</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute la séquence 2</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je tape sur "Suivant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 3 commence immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> le titre de la séquence s'affiche: "Vénus de Milo"
<span style="color: #9E9E9E"><strong>Et</strong></span> la barre de progression se réinitialise</p>
<hr />
<h2 id="7-navigation-vers-sequence-precedente">7. Navigation vers séquence précédente</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute la séquence 5</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je tape sur "Précédent"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 4 recommence depuis le début
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux réécouter cette séquence</p>
<hr />
<h2 id="8-saut-direct-a-une-sequence-specifique">8. Saut direct à une séquence spécifique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute la séquence 2
<span style="color: #9E9E9E"><strong>Et</strong></span> que la liste des séquences est affichée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je tape sur "7. Peintures Renaissance"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 7 démarre immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> je passe directement de la séquence 2 à la 7</p>
<hr />
<h2 id="9-commande-vocale-suivant">9. Commande vocale "Suivant"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute la séquence 3</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je dis "Suivant" via la commande vocale</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 4 démarre
<span style="color: #9E9E9E"><strong>Et</strong></span> la commande vocale fonctionne même si l'écran est verrouillé</p>
<hr />
<h2 id="10-commande-vocale-precedent">10. Commande vocale "Précédent"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute la séquence 6</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je dis "Précédent" via la commande vocale</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 5 démarre depuis le début</p>
<hr />
<h2 id="11-pause-et-reprise-a-la-position-exacte">11. Pause et reprise à la position exacte</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute la séquence 4 à la position 2:30</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je mets en pause
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'attends 5 minutes
<span style="color: #9E9E9E"><strong>Et</strong></span> que je reprends la lecture</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence reprend exactement à 2:30
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune donnée n'est perdue</p>
<hr />
<h2 id="12-guidage-vocal-automatique-entre-sequences">12. Guidage vocal automatique entre séquences</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la séquence 2 se termine</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la transition vers la séquence 3 se produit</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> j'entends un message vocal:
<span style="color: #9E9E9E"><strong>Et</strong></span> la séquence 3 ne démarre pas automatiquement (navigation manuelle)</p>
<hr />
<h2 id="13-avertissement-si-eloignement-du-point-dinteret">13. Avertissement si éloignement du point d'intérêt</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis dans le guide du Louvre
<span style="color: #9E9E9E"><strong>Et</strong></span> que je devrais être devant la Vénus de Milo (séquence 3)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je m'éloigne de plus de 50m de ce point</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> j'entends un message vocal:
<span style="color: #9E9E9E"><strong>Et</strong></span> un bouton "Voir le plan" apparaît dans l'interface</p>
<hr />
<h2 id="14-sauvegarde-automatique-de-la-progression">14. Sauvegarde automatique de la progression</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute la séquence 5 à la position 1:45</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je ferme l'application brutalement
<span style="color: #9E9E9E"><strong>Et</strong></span> que je la rouvre 10 minutes plus tard</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois une popup "Reprendre la visite du Musée du Louvre ?"
<span style="color: #9E9E9E"><strong>Et</strong></span> si je choisis "Reprendre", je retourne à la séquence 5 à 1:45</p>
<hr />
<h2 id="15-option-de-recommencer-depuis-le-debut">15. Option de recommencer depuis le début</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai une progression sauvegardée à la séquence 7</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je rouvre le guide</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois 2 options:</p>
<pre><code>| option | action |
|---|---|
| Reprendre à la séquence 7 | Reprend à la position exacte |
| Recommencer depuis le début | Retourne à la séquence 1 |
</code></pre>
<hr />
<h2 id="16-expiration-de-la-sauvegarde-apres-30-jours">16. Expiration de la sauvegarde après 30 jours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai une progression sauvegardée depuis 30 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de reprendre le guide</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la sauvegarde est considérée comme expirée
<span style="color: #9E9E9E"><strong>Et</strong></span> je recommence depuis la séquence 1
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Votre précédente visite date de plus de 30 jours. Recommençons depuis le début."</p>
<hr />
<h2 id="17-synchronisation-multi-device-de-la-progression">17. Synchronisation multi-device de la progression</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un guide sur mon iPhone à la séquence 4</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je ferme l'app et ouvre sur mon iPad</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois la progression synchronisée
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux reprendre à la séquence 4 sur l'iPad</p>
<hr />
<h2 id="18-marquage-termine-apres-toutes-les-sequences">18. Marquage "Terminé" après toutes les séquences</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute la dernière séquence (12/12)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> cette séquence se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le guide est marqué "✅ Terminé" dans mon historique
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois un message de félicitation:
<span style="color: #9E9E9E"><strong>Et</strong></span> le créateur gagne les statistiques d'écoute complète</p>
<hr />
<h2 id="19-creation-daudio-guide-par-un-createur">19. Création d'audio-guide par un créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je crée un nouvel audio-guide</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je dois:</p>
<pre><code>| é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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> la durée totale est calculée automatiquement</p>
<hr />
<h2 id="20-structure-json-de-stockage-audio-guide">20. Structure JSON de stockage audio-guide</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur publie un audio-guide du Louvre</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les métadonnées sont stockées en base</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le format JSON contient:</p>
<hr />
<h2 id="21-limitation-du-nombre-de-sequences">21. Limitation du nombre de séquences</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée un audio-guide</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie d'ajouter plus de 50 séquences</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois le message "Maximum 50 séquences par audio-guide"
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois structurer mon contenu différemment ou créer plusieurs guides</p>
<hr />
<h2 id="22-quitter-le-guide-et-sauvegarder">22. Quitter le guide et sauvegarder</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute la séquence 6</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je tape sur le bouton "×" (fermer)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois une confirmation:
<span style="color: #9E9E9E"><strong>Et</strong></span> si je confirme, la progression est enregistrée
<span style="color: #9E9E9E"><strong>Et</strong></span> je retourne à l'écran principal</p>
<hr />
<h2 id="23-statistiques-createur-pour-audio-guides">23. Statistiques créateur pour audio-guides</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis créateur d'un audio-guide</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mes statistiques</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| 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) |
</code></pre>
<hr />
<h2 id="24-audio-guide-multilingue-post-mvp">24. Audio-guide multilingue (post-MVP)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur peut publier plusieurs versions linguistiques</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un touriste anglophone visite le Louvre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit les guides disponibles en anglais
<span style="color: #9E9E9E"><strong>Et</strong></span> peut choisir parmi les guides traduits
<span style="color: #F44336"><strong>Mais</strong></span> cette fonctionnalité n'est pas disponible en MVP</p>
<hr />
<h2 id="25-publicite-entre-sequences-daudio-guide">25. Publicité entre séquences d'audio-guide</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur gratuit
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'écoute un audio-guide</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je passe de la séquence 5 à la séquence 6</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une publicité peut être insérée (1 pub toutes les 5 séquences)
<span style="color: #9E9E9E"><strong>Et</strong></span> la publicité est skippable après 5 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> les utilisateurs Premium ne voient pas de publicité</p>
<hr />
<h2 id="26-audio-guide-en-mode-offline">26. Audio-guide en mode offline</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai téléchargé un audio-guide complet</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je visite le lieu sans connexion internet</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> toutes les séquences sont disponibles hors ligne
<span style="color: #9E9E9E"><strong>Et</strong></span> la navigation fonctionne normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> seule la sauvegarde cloud est différée jusqu'à reconnexion</p>
<hr />
<h2 id="27-notation-dun-audio-guide-apres-ecoute">27. Notation d'un audio-guide après écoute</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai terminé un audio-guide</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je ferme l'interface</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois une popup "Notez cette visite"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux donner une note de 1 à 5 étoiles
<span style="color: #9E9E9E"><strong>Et</strong></span> cette note contribue à la note globale visible par les autres utilisateurs</p>
<hr />
<h2 id="28-filtrage-par-langue-dans-la-page-de-selection">28. Filtrage par langue dans la page de sélection</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que plusieurs audio-guides sont disponibles en différentes langues</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à la page de sélection</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux filtrer par langue
<span style="color: #9E9E9E"><strong>Et</strong></span> par défaut, les guides dans ma langue système sont affichés en premier</p>
<hr />
<h2 id="29-reutilisation-de-linfrastructure-existante">29. Réutilisation de l'infrastructure existante</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide est techniquement un contenu structuré</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il réutilise:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> aucune infrastructure dédiée n'est nécessaire</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="impact-des-abonnements-sur-lalgorithme">Impact des abonnements sur l'algorithme</h1>
<blockquote>
<p><em>En tant qu'auditeur</em>
<em>Je veux que les contenus de mes créateurs suivis soient favorisés</em>
<em>Afin de ne pas rater leurs publications tout en découvrant de nouveaux contenus</em></p>
</blockquote>
<p><strong>16 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis connecté en tant qu'auditeur
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis abonné au créateur "JeanDupont"</p>
</blockquote>
<h2 id="1-boost-de-30-applique-au-score-final">1. Boost de +30% appliqué au score final</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un contenu du créateur "JeanDupont" avec:</p>
<pre><code>| score_geo | 0.5 |
|---|---|
| score_interet | 0.6 |
| score_engage | 0.5 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le score final est calculé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le score de base est 0.53
<span style="color: #9E9E9E"><strong>Et</strong></span> le boost abonnement de +30% est appliqué
<span style="color: #9E9E9E"><strong>Et</strong></span> le score final avec boost est 0.69</p>
<hr />
<h2 id="2-contenu-non-suivi-peut-battre-contenu-suivi">2. Contenu non-suivi peut battre contenu suivi</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis à Paris
<span style="color: #9E9E9E"><strong>Et</strong></span> que 2 contenus sont disponibles:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme sélectionne le prochain contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le Contenu A est proposé en premier</p>
<hr />
<h2 id="3-contenu-suivi-remporte-grace-au-boost">3. Contenu suivi remporte grâce au boost</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis à Paris
<span style="color: #9E9E9E"><strong>Et</strong></span> que 2 contenus sont disponibles:</p>
<pre><code>| contenu | createur_suivi | score_final_base | score_avec_boost |
|---|---|---|---|
| Contenu A | Non | 0.70 | 0.70 |
| Contenu B | Oui | 0.60 | 0.78 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme sélectionne le prochain contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le Contenu B est proposé en premier</p>
<hr />
<h2 id="4-contenu-suivi-avec-faible-engagement-ne-domine-pas">4. Contenu suivi avec faible engagement ne domine pas</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné au créateur "CreateurMoyen"
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il publie un contenu avec très faible engagement (score 0.30)
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un contenu viral d'un créateur non-suivi a un score de 0.85</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme sélectionne le prochain contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu viral est proposé en premier (0.85)</p>
<hr />
<h2 id="5-pas-de-file-dediee-aux-abonnements">5. Pas de file dédiée aux abonnements</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné à 50 créateurs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme génère ma file d'attente de 5 contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les contenus suivis et non-suivis sont mélangés
<span style="color: #9E9E9E"><strong>Et</strong></span> tous entrent en compétition selon leurs scores (avec boost si abonnement)
<span style="color: #9E9E9E"><strong>Et</strong></span> il n'y a pas de section séparée "Contenus de vos abonnements"</p>
<hr />
<h2 id="6-verification-du-calcul-du-boost">6. Vérification du calcul du boost</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un contenu d'un créateur suivi
<span style="color: #9E9E9E"><strong>Et</strong></span> que le score final de base est calculé à 0.65</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le boost abonnement est appliqué</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le multiplicateur utilisé est exactement 1.3
<span style="color: #9E9E9E"><strong>Et</strong></span> le score final avec boost est 0.845 (0.65 × 1.3)
<span style="color: #9E9E9E"><strong>Et</strong></span> le résultat est arrondi à 2 décimales: 0.85</p>
<hr />
<h2 id="7-boost-applique-a-tous-les-contenus-du-createur-suivi">7. Boost appliqué à tous les contenus du créateur suivi</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné au créateur "JeanDupont"
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il a publié 10 contenus différents</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme évalue chacun de ces contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le boost de +30% est appliqué à tous les 10 contenus
<span style="color: #9E9E9E"><strong>Et</strong></span> chaque contenu bénéficie du même multiplicateur 1.3</p>
<hr />
<h2 id="8-plusieurs-createurs-suivis-en-competition">8. Plusieurs créateurs suivis en compétition</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné à "Créateur A" et "Créateur B"
<span style="color: #9E9E9E"><strong>Et</strong></span> que les 2 ont des contenus disponibles dans ma zone:</p>
<pre><code>| createur | score_base | score_avec_boost |
|---|---|---|
| Créateur A | 0.70 | 0.91 |
| Créateur B | 0.65 | 0.85 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme sélectionne le prochain contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu du Créateur A est proposé en premier (0.91 &gt; 0.85)
<span style="color: #9E9E9E"><strong>Et</strong></span> les 2 bénéficient du boost, mais le meilleur score gagne</p>
<hr />
<h2 id="9-contenu-national-dun-createur-suivi">9. Contenu national d'un créateur suivi</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné à "MediaNational"
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il publie un contenu de type "National" (score_geo 0.2)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le score est calculé avec:</p>
<pre><code>| score_geo | score_interet | score_engage |
|---|---|---|
| 0.2 | 0.7 | 0.6 |
</code></pre>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le score de base est environ 0.50
<span style="color: #9E9E9E"><strong>Et</strong></span> avec le boost abonnement, le score devient 0.65
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu peut être proposé malgré son score géo faible</p>
<hr />
<h2 id="10-transparence-du-boost-dans-les-parametres">10. Transparence du boost dans les paramètres</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède aux paramètres de l'algorithme de recommandation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois l'information: "Les contenus de vos créateurs suivis bénéficient d'un boost de +30%"
<span style="color: #9E9E9E"><strong>Et</strong></span> je comprends que ce n'est pas une priorité absolue
<span style="color: #9E9E9E"><strong>Et</strong></span> que la découverte de nouveaux contenus reste possible</p>
<hr />
<h2 id="11-boost-desactive-si-desabonnement">11. Boost désactivé si désabonnement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné au créateur "JeanDupont"
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un de ses contenus bénéficiait du boost +30%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me désabonne de "JeanDupont"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ses contenus n'ont plus le boost
<span style="color: #9E9E9E"><strong>Et</strong></span> leur score revient au score de base sans multiplicateur</p>
<hr />
<h2 id="12-contenu-dun-createur-nouvellement-suivi">12. Contenu d'un créateur nouvellement suivi</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je viens de m'abonner à "NouveauCreateur"
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il a publié un contenu il y a 2 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme recalcule les scores</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le boost de +30% est immédiatement appliqué à ce contenu
<span style="color: #9E9E9E"><strong>Et</strong></span> il peut apparaître dans ma prochaine file d'attente</p>
<hr />
<h2 id="13-impact-sur-le-taux-de-contenu-suivi-dans-le-feed">13. Impact sur le taux de contenu suivi dans le feed</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné à 30 créateurs
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'écoute 100 contenus sur une semaine</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'analyse la répartition</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> environ 40-50% des contenus proviennent de créateurs suivis
<span style="color: #9E9E9E"><strong>Et</strong></span> 50-60% proviennent de créateurs non-suivis (découverte)</p>
<hr />
<h2 id="14-contenu-suivi-hors-zone-geographique">14. Contenu suivi hors zone géographique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis à Paris
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis abonné à un créateur de Marseille
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il publie un contenu ancré à Marseille (hors de portée)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme évalue ce contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le score géo est quasi nul (0.05)
<span style="color: #9E9E9E"><strong>Et</strong></span> même avec boost +30%, le score reste très faible
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu n'est probablement pas proposé</p>
<hr />
<h2 id="15-performance-de-calcul-du-boost">15. Performance de calcul du boost</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné à 100 créateurs
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'algorithme évalue 1000 contenus potentiels</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le calcul des scores avec boost est effectué</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le temps de calcul reste inférieur à 50ms
<span style="color: #9E9E9E"><strong>Et</strong></span> la requête SQL utilise un JOIN sur la table abonnements</p>
<hr />
<h2 id="16-boost-combine-avec-dautres-facteurs">16. Boost combiné avec d'autres facteurs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un contenu d'un créateur suivi
<span style="color: #9E9E9E"><strong>Et</strong></span> que le contenu bénéficie aussi de:</p>
<pre><code>| facteur | impact |
|---|---|
| Score d'engagement élevé | +20% |
| Contenu récent (&lt;24h) | +10% |
| Boost abonnement | +30% |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le score final est calculé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le boost abonnement s'applique au score final (après tous les autres calculs)
<span style="color: #9E9E9E"><strong>Et</strong></span> les boosts ne s'additionnent pas, le boost abonnement est un multiplicateur final</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="limites-dabonnements-et-desabonnement">Limites d'abonnements et désabonnement</h1>
<blockquote>
<p><em>En tant qu'auditeur</em>
<em>Je veux gérer mes abonnements de manière équilibrée</em>
<em>Afin de suivre mes créateurs préférés sans être submergé</em></p>
</blockquote>
<p><strong>27 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis connecté en tant qu'auditeur</p>
</blockquote>
<h2 id="1-limite-maximale-de-200-abonnements">1. Limite maximale de 200 abonnements</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné à 199 créateurs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de m'abonner à un 200ème créateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'abonnement réussit
<span style="color: #9E9E9E"><strong>Et</strong></span> je suis maintenant abonné à 200 créateurs</p>
<hr />
<h2 id="2-impossible-de-depasser-200-abonnements">2. Impossible de dépasser 200 abonnements</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis déjà abonné à 200 créateurs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de m'abonner à un nouveau créateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'action échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message:</p>
<hr />
<h2 id="3-suggestion-de-desabonnement-de-createurs-inactifs">3. Suggestion de désabonnement de créateurs inactifs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné à 200 créateurs
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'essaie de m'abonner à un nouveau créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je vois le message de limite atteinte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois aussi une suggestion:
<span style="color: #9E9E9E"><strong>Et</strong></span> un bouton "Désabonner" est proposé pour ce créateur</p>
<hr />
<h2 id="4-liste-triable-des-abonnements">4. Liste triable des abonnements</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné à 150 créateurs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à ma liste d'abonnements</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux trier par:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="5-abonnement-initial-augmente-les-jauges-de-5">5. Abonnement initial augmente les jauges de +5%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes jauges d'intérêt sont:</p>
<pre><code>| catégorie | valeur initiale |
|---|---|
| Automobile | 60% |
| Voyage | 55% |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> qu'un créateur tague ses contenus "Automobile" et "Voyage"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je m'abonne à ce créateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mes jauges évoluent:</p>
<pre><code>| catégorie | nouvelle valeur |
|---|---|
| Automobile | 65% (+5%) |
| Voyage | 60% (+5%) |
</code></pre>
<hr />
<h2 id="6-abonnement-avec-createur-ayant-3-tags">6. Abonnement avec créateur ayant 3 tags</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur tague ses contenus:</p>
<pre><code>| tags |
|---|
| Automobile, Voyage, Technologie |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> que mes jauges sont toutes à 50%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je m'abonne à ce créateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les 3 jauges augmentent de +5%:</p>
<pre><code>| catégorie | nouvelle valeur |
|---|---|
| Automobile | 55% |
| Voyage | 55% |
| Technologie | 55% |
</code></pre>
<hr />
<h2 id="7-desabonnement-diminue-les-jauges-de-5">7. Désabonnement diminue les jauges de -5%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné à un créateur avec tags "Politique" et "Économie"
<span style="color: #9E9E9E"><strong>Et</strong></span> que mes jauges sont:</p>
<pre><code>| catégorie | valeur actuelle |
|---|---|
| Politique | 70% |
| Économie | 65% |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me désabonne de ce créateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mes jauges évoluent:</p>
<pre><code>| catégorie | nouvelle valeur |
|---|---|
| Politique | 65% (-5%) |
| Économie | 60% (-5%) |
</code></pre>
<hr />
<h2 id="8-desabonnement-sans-confirmation">8. Désabonnement sans confirmation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte le profil d'un créateur suivi</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Se désabonner"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le désabonnement est immédiat
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune popup de confirmation n'apparaît</p>
<hr />
<h2 id="9-reabonnement-possible-immediatement">9. Réabonnement possible immédiatement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je viens de me désabonner d'un créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte à nouveau son profil</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le bouton "S'abonner" est affiché
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux me réabonner immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> mes jauges augmentent à nouveau de +5%</p>
<hr />
<h2 id="10-effet-symetrique-abonnementdesabonnement">10. Effet symétrique abonnement/désabonnement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur a les tags "Musique" et "Culture"
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma jauge Musique est à 50%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je m'abonne puis me désabonne immédiatement</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma jauge revient exactement à 50%
<span style="color: #9E9E9E"><strong>Et</strong></span> il n'y a pas de perte ou gain net</p>
<hr />
<h2 id="11-abonnement-ne-depasse-pas-100-de-jauge">11. Abonnement ne dépasse pas 100% de jauge</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma jauge Automobile est à 97%
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un créateur tague ses contenus "Automobile"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je m'abonne à ce créateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma jauge Automobile passe à 100% (limite max)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'augmentation effective est de +3% seulement</p>
<hr />
<h2 id="12-desabonnement-ne-descend-pas-sous-0">12. Désabonnement ne descend pas sous 0%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma jauge Politique est à 3%
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis abonné à un créateur avec tag "Politique"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me désabonne de ce créateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma jauge Politique passe à 0% (limite min)
<span style="color: #9E9E9E"><strong>Et</strong></span> la diminution effective est de -3% seulement</p>
<hr />
<h2 id="13-createur-ne-voit-pas-qui-est-abonne-privacy">13. Créateur ne voit pas qui est abonné (privacy)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné au créateur "JeanDupont"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> "JeanDupont" consulte ses statistiques</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit le nombre total d'abonnés (ex: "1,247 abonnés")
<span style="color: #F44336"><strong>Mais</strong></span> il ne voit pas la liste des utilisateurs abonnés
<span style="color: #9E9E9E"><strong>Et</strong></span> mon identité reste privée</p>
<hr />
<h2 id="14-createur-voit-uniquement-le-nombre-total-dabonnes">14. Créateur voit uniquement le nombre total d'abonnés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis créateur
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai 523 abonnés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mes statistiques</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois "523 abonnés"
<span style="color: #F44336"><strong>Mais</strong></span> je ne peux pas:</p>
<pre><code>| action interdite |
|---|
| Voir la liste des abonnés |
| Contacter mes abonnés individuellement |
| Voir leurs profils |
</code></pre>
<hr />
<h2 id="15-pas-dabonnement-mutuel-visible">15. Pas d'abonnement mutuel visible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné au créateur "Alice"
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'"Alice" est abonnée à mon compte créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte le profil d'"Alice"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je ne vois pas d'indication qu'elle est abonnée à moi
<span style="color: #9E9E9E"><strong>Et</strong></span> il n'y a pas de badge "Abonné mutuellement"</p>
<hr />
<h2 id="16-performance-avec-200-abonnements">16. Performance avec 200 abonnements</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné à 200 créateurs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme calcule ma recommandation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la requête SQL utilise un JOIN sur la table abonnements
<span style="color: #9E9E9E"><strong>Et</strong></span> la table est indexée sur user_id et creator_id
<span style="color: #9E9E9E"><strong>Et</strong></span> le temps de calcul reste inférieur à 50ms</p>
<hr />
<h2 id="17-impact-sur-la-recommandation-avec-beaucoup-dabonnements">17. Impact sur la recommandation avec beaucoup d'abonnements</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné à 150 créateurs très actifs
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'ils publient collectivement 100 contenus par jour</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme génère ma file de 5 contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> environ 60-70% des contenus proviennent de créateurs suivis (grâce au boost +30%)
<span style="color: #F44336"><strong>Mais</strong></span> 30-40% proviennent de nouveaux créateurs (découverte)</p>
<hr />
<h2 id="18-notification-de-desabonnement-au-createur-non-implemente">18. Notification de désabonnement au créateur (non implémenté)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je me désabonne d'un créateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le créateur ne reçoit aucune notification
<span style="color: #9E9E9E"><strong>Et</strong></span> il ne peut pas savoir qui s'est désabonné</p>
<hr />
<h2 id="19-statistiques-dabonnements-pour-lutilisateur">19. Statistiques d'abonnements pour l'utilisateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné à 87 créateurs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à mes statistiques d'abonnements</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="20-recherche-dans-la-liste-dabonnements">20. Recherche dans la liste d'abonnements</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné à 120 créateurs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à ma liste d'abonnements</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux chercher par nom de créateur
<span style="color: #9E9E9E"><strong>Et</strong></span> les résultats sont filtrés en temps réel
<span style="color: #9E9E9E"><strong>Et</strong></span> je trouve rapidement un créateur spécifique</p>
<hr />
<h2 id="21-export-de-la-liste-dabonnements-rgpd">21. Export de la liste d'abonnements (RGPD)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je demande l'export de mes données</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'export est généré</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la liste de mes abonnements est incluse:</p>
<hr />
<h2 id="22-suppression-compte-utilisateur-et-impact-sur-abonnements">22. Suppression compte utilisateur et impact sur abonnements</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné à 50 créateurs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je supprime définitivement mon compte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous mes abonnements sont supprimés
<span style="color: #9E9E9E"><strong>Et</strong></span> le compteur d'abonnés de chaque créateur est décrémenté de -1
<span style="color: #9E9E9E"><strong>Et</strong></span> les jauges n'existent plus (données supprimées)</p>
<hr />
<h2 id="23-suppression-compte-createur-et-impact-sur-abonnes">23. Suppression compte créateur et impact sur abonnés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné au créateur "Bob"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> "Bob" supprime son compte créateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je suis automatiquement désabonné
<span style="color: #9E9E9E"><strong>Et</strong></span> mes jauges diminuent de -5% pour les tags de "Bob"
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne vois plus "Bob" dans ma liste d'abonnements</p>
<hr />
<h2 id="24-limite-200-justifiee-par-usage-realiste">24. Limite 200 justifiée par usage réaliste</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la moyenne d'abonnements sur YouTube est de ~50-100 chaînes
<span style="color: #9E9E9E"><strong>Et</strong></span> que Twitter limite à 5000 follows (mais moyenne ~150)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> RoadWave fixe la limite à 200</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> cela couvre largement 99% des utilisateurs
<span style="color: #9E9E9E"><strong>Et</strong></span> évite les abus (comptes spam suivant tout le monde)</p>
<hr />
<h2 id="25-table-postgresql-optimisee-pour-abonnements">25. Table PostgreSQL optimisée pour abonnements</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> la structure de table subscriptions:</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les requêtes d'abonnements sont O(1) avec index
<span style="color: #9E9E9E"><strong>Et</strong></span> le count d'abonnés par créateur est rapide
<span style="color: #9E9E9E"><strong>Et</strong></span> la vérification "est abonné ?" est instantanée</p>
<hr />
<h2 id="26-detection-dabonnements-abusifs">26. Détection d'abonnements abusifs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur s'abonne à 200 créateurs en moins de 5 minutes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte cette activité suspecte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un rate limiting est appliqué (max 10 abonnements/minute)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur voit "Trop d'actions rapides. Veuillez réessayer dans 1 minute"
<span style="color: #9E9E9E"><strong>Et</strong></span> cela prévient les bots de spam</p>
<hr />
<h2 id="27-badge-createur-verifie-visible-dans-abonnements">27. Badge créateur vérifié visible dans abonnements</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné à 3 créateurs dont 1 vérifié</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte ma liste d'abonnements</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le créateur vérifié a un badge ✓ bleu
<span style="color: #9E9E9E"><strong>Et</strong></span> les créateurs non vérifiés n'ont pas de badge</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="notifications-contextuelles-selon-le-mode-de-deplacement">Notifications contextuelles selon le mode de déplacement</h1>
<blockquote>
<p><em>En tant qu'auditeur</em>
<em>Je veux recevoir des notifications adaptées à mon contexte</em>
<em>Afin d'être informé sans être distrait en conduisant</em></p>
</blockquote>
<p><strong>28 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis connecté en tant qu'auditeur
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai activé les notifications</p>
</blockquote>
<h2 id="1-detection-automatique-du-contexte-en-voiture">1. Détection automatique du contexte en voiture</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma vitesse GPS est de 50 km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte mon contexte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je suis identifié comme "En voiture"
<span style="color: #9E9E9E"><strong>Et</strong></span> les notifications push sont désactivées
<span style="color: #9E9E9E"><strong>Et</strong></span> seules les notifications in-app sont actives</p>
<hr />
<h2 id="2-detection-automatique-du-contexte-a-pied">2. Détection automatique du contexte à pied</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma vitesse GPS est de 3 km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte mon contexte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je suis identifié comme "À pied"
<span style="color: #9E9E9E"><strong>Et</strong></span> les notifications push sont activées
<span style="color: #9E9E9E"><strong>Et</strong></span> l'interface tactile et vocale sont disponibles</p>
<hr />
<h2 id="3-zone-de-transition-5-10-kmh">3. Zone de transition 5-10 km/h</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma vitesse GPS varie entre 5 et 10 km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte mon contexte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un algorithme de lissage est appliqué sur 30 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> le mode est déterminé selon la vitesse moyenne
<span style="color: #9E9E9E"><strong>Et</strong></span> les changements de mode ne sont pas trop fréquents</p>
<hr />
<h2 id="4-nouveau-contenu-createur-suivi-mode-voiture">4. Nouveau contenu créateur suivi - Mode voiture</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis en voiture (vitesse &gt;10 km/h)
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis abonné au créateur "JeanDupont"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> "JeanDupont" publie un nouveau contenu dans ma zone</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je ne reçois pas de notification push
<span style="color: #F44336"><strong>Mais</strong></span> je vois un badge compteur in-app
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu apparaît dans ma file avec boost +30%</p>
<hr />
<h2 id="5-nouveau-contenu-createur-suivi-mode-pieton">5. Nouveau contenu créateur suivi - Mode piéton</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis à pied (vitesse &lt;5 km/h)
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis abonné au créateur "JeanDupont"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis situé en Île-de-France</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> "JeanDupont" publie un contenu géolocalisé en Île-de-France</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification push:</p>
<hr />
<h2 id="6-live-createur-suivi-mode-voiture">6. Live créateur suivi - Mode voiture</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis en voiture
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis abonné au créateur "RadioLive"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> "RadioLive" démarre un live dans ma zone</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je ne reçois pas de notification push
<span style="color: #F44336"><strong>Mais</strong></span> je vois un badge compteur in-app
<span style="color: #9E9E9E"><strong>Et</strong></span> le live peut apparaître dans ma recommandation automatiquement</p>
<hr />
<h2 id="7-live-createur-suivi-mode-pieton">7. Live créateur suivi - Mode piéton</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis à pied
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis abonné au créateur "RadioLive"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis situé dans la zone du live</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> "RadioLive" démarre un live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification push:</p>
<hr />
<h2 id="8-audio-guide-disponible-a-proximite-mode-pieton">8. Audio-guide disponible à proximité - Mode piéton</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis à pied</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je passe à moins de 100m d'un lieu avec audio-guides</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification push:</p>
<hr />
<h2 id="9-audio-guide-disponible-a-proximite-mode-voiture">9. Audio-guide disponible à proximité - Mode voiture</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis en voiture</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je passe à moins de 100m d'un lieu avec audio-guides</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification audio (bip)
<span style="color: #9E9E9E"><strong>Et</strong></span> une annonce vocale: "Audio-guide disponible"
<span style="color: #F44336"><strong>Mais</strong></span> pas de notification push (sécurité)</p>
<hr />
<h2 id="10-filtrage-geographique-des-notifications">10. Filtrage géographique des notifications</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné au créateur "CreateurMarseille"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis situé à Paris</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> "CreateurMarseille" publie un contenu ancré à Marseille</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je ne reçois pas de notification
<span style="color: #9E9E9E"><strong>Et</strong></span> cela évite la frustration de contenus non écoutables</p>
<hr />
<h2 id="11-contenu-national-notifie-tous-les-abonnes">11. Contenu national notifie tous les abonnés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné au créateur "MediaNational"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis situé n'importe où en France</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> "MediaNational" publie un contenu de type "National"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification (si mode piéton)</p>
<hr />
<h2 id="12-limite-de-10-notifications-push-par-jour">12. Limite de 10 notifications push par jour</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné à 50 créateurs actifs
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai déjà reçu 10 notifications push aujourd'hui</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un 11ème contenu est publié</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je ne reçois pas de notification push individuelle
<span style="color: #F44336"><strong>Mais</strong></span> une notification groupée: "🎧 3 nouveaux contenus de créateurs suivis"</p>
<hr />
<h2 id="13-parametrage-de-la-limite-quotidienne">13. Paramétrage de la limite quotidienne</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la limite par défaut est de 10 notifications/jour</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède aux paramètres de notifications</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux modifier la limite entre 5 et 20
<span style="color: #9E9E9E"><strong>Et</strong></span> si je choisis 15, je recevrai jusqu'à 15 notifications/jour</p>
<hr />
<h2 id="14-mode-silencieux-nocturne-par-defaut">14. Mode silencieux nocturne par défaut</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le mode silencieux est activé de 22h à 8h par défaut
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il est 23h30</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un créateur suivi publie un contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je ne reçois pas de notification push
<span style="color: #F44336"><strong>Mais</strong></span> les notifications sont empilées
<span style="color: #9E9E9E"><strong>Et</strong></span> je les vois le lendemain matin à 8h01</p>
<hr />
<h2 id="15-exception-du-mode-silencieux-pour-les-lives">15. Exception du mode silencieux pour les lives</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le mode silencieux est activé (22h-8h)
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il est 23h00
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai activé "Notifications importantes uniquement" (lives uniquement)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un créateur suivi démarre un live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois quand même la notification push du live</p>
<hr />
<h2 id="16-desactivation-complete-des-notifications">16. Désactivation complète des notifications</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'accède aux paramètres de notifications</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je désactive toutes les notifications</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je ne reçois plus aucune notification push
<span style="color: #9E9E9E"><strong>Et</strong></span> les badges in-app sont également désactivés
<span style="color: #9E9E9E"><strong>Et</strong></span> seule la recommandation algorithmique reste active</p>
<hr />
<h2 id="17-notification-nouveaux-contenus-activee-par-defaut">17. Notification "Nouveaux contenus" activée par défaut</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée un nouveau compte
<span style="color: #9E9E9E"><strong>Et</strong></span> que je m'abonne à mon premier créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les préférences de notifications</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> "Nouveaux contenus" est activé par défaut
<span style="color: #9E9E9E"><strong>Et</strong></span> "Lives" est activé par défaut
<span style="color: #9E9E9E"><strong>Et</strong></span> "Audio-guides proximité" est activé par défaut</p>
<hr />
<h2 id="18-desactivation-selective-par-type-de-notification">18. Désactivation sélective par type de notification</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai activé toutes les notifications</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je désactive uniquement "Nouveaux contenus"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je ne reçois plus de notifications pour nouveaux contenus
<span style="color: #F44336"><strong>Mais</strong></span> je reçois toujours les notifications de lives
<span style="color: #9E9E9E"><strong>Et</strong></span> les notifications d'audio-guides restent actives</p>
<hr />
<h2 id="19-notification-groupee-apres-limite-depassee">19. Notification groupée après limite dépassée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai reçu 10 notifications push aujourd'hui
<span style="color: #9E9E9E"><strong>Et</strong></span> que 5 nouveaux contenus sont publiés dans l'heure suivante</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la 11ème notification devrait être envoyée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les 5 contenus sont regroupés en une seule notification:</p>
<hr />
<h2 id="20-detail-de-la-notification-groupee">20. Détail de la notification groupée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai reçu une notification groupée "3 nouveaux contenus"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je tape sur la notification</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'app s'ouvre sur une liste des 3 contenus:</p>
<pre><code>| créateur | titre |
|---|---|
| JeanDupont | "Actualité du jour" |
| MarieDurand | "Podcast économie" |
| PaulMartin | "Anecdote historique" |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je peux choisir lequel écouter en premier</p>
<hr />
<h2 id="21-personnalisation-des-plages-horaires-du-mode-silencieux">21. Personnalisation des plages horaires du mode silencieux</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le mode silencieux est 22h-8h par défaut</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède aux paramètres</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux modifier les heures: par exemple 23h-7h
<span style="color: #9E9E9E"><strong>Et</strong></span> le mode silencieux s'applique dans la nouvelle plage horaire</p>
<hr />
<h2 id="22-format-notification-nouveau-contenu-complet">22. Format notification nouveau contenu complet</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis à pied
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un créateur suivi publie un contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je reçois la notification push</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> elle contient:</p>
<pre><code>| élément | exemple |
|---|---|
| Emoji | 🎧 |
| Créateur | JeanDupont |
| Action | a publié |
| Titre | "Les secrets du Louvre" |
| CTA | Tap pour écouter |
</code></pre>
<hr />
<h2 id="23-format-notification-live-complet">23. Format notification live complet</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis à pied
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un créateur suivi démarre un live</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je reçois la notification push</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> elle contient:</p>
<pre><code>| élément | exemple |
|---|---|
| Emoji | 🔴 |
| Créateur | RadioLive |
| Action | est en direct |
| Titre | "Débat politique ce soir" |
| CTA | Tap pour rejoindre |
</code></pre>
<hr />
<h2 id="24-notification-disparait-si-contenu-supprime">24. Notification disparaît si contenu supprimé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai reçu une notification pour un contenu
<span style="color: #9E9E9E"><strong>Et</strong></span> que je n'ai pas encore tapé dessus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur supprime le contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la notification est automatiquement retirée de mon centre de notifications
<span style="color: #9E9E9E"><strong>Et</strong></span> si je tape dessus par erreur, je vois "Contenu non disponible"</p>
<hr />
<h2 id="25-badge-compteur-in-app-en-mode-voiture">25. Badge compteur in-app en mode voiture</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis en voiture
<span style="color: #9E9E9E"><strong>Et</strong></span> que 5 créateurs suivis publient des contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ouvre l'application</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois un badge "5" sur l'onglet "Nouveautés"
<span style="color: #9E9E9E"><strong>Et</strong></span> en consultant l'onglet, je vois les 5 nouveaux contenus
<span style="color: #9E9E9E"><strong>Et</strong></span> le badge disparaît après consultation</p>
<hr />
<h2 id="26-cout-des-notifications-push-firebase">26. Coût des notifications push Firebase</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je reçois 10 notifications push par jour
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis actif 365 jours par an</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système calcule le coût</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 3650 notifications/an sont envoyées
<span style="color: #9E9E9E"><strong>Et</strong></span> Firebase Cloud Messaging est gratuit jusqu'à plusieurs millions de notifications
<span style="color: #9E9E9E"><strong>Et</strong></span> le coût reste 0€ pour le volume MVP/Growth</p>
<hr />
<h2 id="27-deep-link-depuis-notification-push">27. Deep link depuis notification push</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je reçois une notification push pour un contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je tape sur la notification</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'app s'ouvre directement sur le contenu
<span style="color: #9E9E9E"><strong>Et</strong></span> la lecture démarre automatiquement (si j'étais à pied)</p>
<hr />
<h2 id="28-notification-refusee-si-permissions-desactivees-au-niveau-os">28. Notification refusée si permissions désactivées au niveau OS</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai désactivé les notifications dans les paramètres iOS/Android</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un créateur suivi publie un contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune notification push n'est envoyée
<span style="color: #9E9E9E"><strong>Et</strong></span> l'app propose de réactiver les permissions dans les paramètres
<span style="color: #F44336"><strong>Mais</strong></span> les badges in-app continuent de fonctionner</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="creation-daudio-guide-multi-sequences">Création d'audio-guide multi-séquences</h1>
<blockquote>
<p><em>En tant que créateur de contenu</em>
<em>Je veux créer des audio-guides avec plusieurs séquences géolocalisées</em>
<em>Afin d'offrir des expériences guidées adaptées aux différents modes de déplacement</em></p>
</blockquote>
<p><strong>35 scénarios</strong> (32 standards, 3 plans)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'application RoadWave est démarrée
<span style="color: #9E9E9E"><strong>Et</strong></span> que le créateur "guide@example.com" est connecté
<span style="color: #9E9E9E"><strong>Et</strong></span> que son compte est vérifié</p>
</blockquote>
<h2 id="1-plan-detection-automatique-du-mode-selon-la-vitesse">1. 📋 Plan: Détection automatique du mode selon la vitesse</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur se déplace à <vitesse> km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la vitesse est calculée sur 30 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le mode <mode> est suggéré automatiquement</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>vitesse</th>
<th>mode</th>
</tr>
</thead>
<tbody>
<tr>
<td>3</td>
<td>Piéton</td>
</tr>
<tr>
<td>15</td>
<td>Vélo</td>
</tr>
<tr>
<td>35</td>
<td>Voiture</td>
</tr>
<tr>
<td>50</td>
<td>Voiture</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="2-suggestion-de-mode-au-demarrage-avec-confirmation">2. Suggestion de mode au démarrage avec confirmation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide "Safari du Paugre" est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur se déplace à 35 km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'audio-guide démarre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une popup s'affiche:</p>
<hr />
<h2 id="3-changement-manuel-du-mode-detecte">3. Changement manuel du mode détecté</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le mode "Voiture" est suggéré automatiquement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur "Changer"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les 4 modes sont proposés:</p>
<pre><code>| mode | emoji |
|---|---|
| Piéton | 🚶 |
| Voiture | 🚗 |
| Vélo | 🚴 |
| Transport | 🚌 |
</code></pre>
<hr />
<h2 id="4-plan-caracteristiques-par-mode-de-deplacement">4. 📋 Plan: Caractéristiques par mode de déplacement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide configuré en mode <mode></p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les paramètres suivants sont appliqués:</p>
<pre><code>| paramètre | valeur |
|---|---|
| Vitesse détection | &lt;vitesse_detection&gt; |
| Déclenchement | &lt;declenchement&gt; |
</code></pre>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>mode</th>
<th>vitesse_detection</th>
<th>declenchement</th>
</tr>
</thead>
<tbody>
<tr>
<td>Piéton</td>
<td>&lt;5 km/h</td>
<td>Manuel (bouton Suivant)</td>
</tr>
<tr>
<td>Voiture</td>
<td>&gt;10 km/h</td>
<td>Auto GPS + Manuel</td>
</tr>
<tr>
<td>Vélo</td>
<td>5-25 km/h</td>
<td>Auto GPS + Manuel</td>
</tr>
<tr>
<td>Transport</td>
<td>Variable</td>
<td>Auto GPS + Manuel</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="5-acces-au-formulaire-de-creation-daudio-guide">5. Accès au formulaire de création d'audio-guide</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le créateur est sur son dashboard</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il clique sur "Créer un audio-guide"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le formulaire de création s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> le wizard guidé en 4 étapes est visible:</p>
<pre><code>| étape | description |
|---|---|
| 1 | Infos générales |
| 2 | Ajout séquences |
| 3 | Preview carte |
| 4 | Validation modération |
</code></pre>
<hr />
<h2 id="6-etape-1-informations-generales-obligatoires">6. Étape 1 - Informations générales obligatoires</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le créateur est sur l'étape 1 du wizard</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il complète le formulaire</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les champs suivants sont obligatoires:</p>
<pre><code>| 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+ |
</code></pre>
<hr />
<h2 id="7-selection-du-mode-de-deplacement">7. Sélection du mode de déplacement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le créateur crée un audio-guide</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il sélectionne le mode "🚗 Voiture (GPS auto + manuel)"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le champ "Vitesse recommandée" s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> la plage suggérée est "30-50 km/h"</p>
<hr />
<h2 id="8-validation-du-titre">8. Validation du titre</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le créateur entre un titre</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le titre contient moins de 5 caractères</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message d'erreur "Minimum 5 caractères" s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> le bouton "Suivant" est désactivé</p>
<hr />
<h2 id="9-validation-de-la-description">9. Validation de la description</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le créateur entre une description</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la description contient 520 caractères</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message d'erreur "Maximum 500 caractères" s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> les 20 caractères en trop sont surlignés en rouge</p>
<hr />
<h2 id="10-etape-2-ajout-de-la-premiere-sequence">10. Étape 2 - Ajout de la première séquence</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le créateur est sur l'étape 2 "Ajout séquences"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il clique sur "Ajouter séquence"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le formulaire de séquence s'affiche avec:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="11-ajout-du-point-gps-pour-une-sequence">11. Ajout du point GPS pour une séquence</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le créateur ajoute une séquence en mode "Voiture"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il clique sur "📍 Ajouter point GPS"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une carte s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> il peut:</p>
<pre><code>| action |
|---|
| Cliquer sur la carte |
| Entrer coordonnées manuelles |
| Utiliser sa position actuelle |
</code></pre>
<hr />
<h2 id="12-configuration-du-rayon-de-declenchement-avec-preview">12. Configuration du rayon de déclenchement avec preview</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un point GPS est défini à (43.1234, 2.5678)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur ajuste le curseur de rayon</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le rayon varie de 10m à 200m
<span style="color: #9E9E9E"><strong>Et</strong></span> un cercle visuel est affiché sur la carte
<span style="color: #9E9E9E"><strong>Et</strong></span> la valeur actuelle s'affiche "30m"</p>
<hr />
<h2 id="13-plan-rayon-par-defaut-selon-le-mode">13. 📋 Plan: Rayon par défaut selon le mode</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide en mode <mode></p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur ajoute un point GPS</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le rayon par défaut est <rayon_defaut></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>mode</th>
<th>rayon_defaut</th>
</tr>
</thead>
<tbody>
<tr>
<td>Voiture</td>
<td>30m</td>
</tr>
<tr>
<td>Vélo</td>
<td>50m</td>
</tr>
<tr>
<td>Transport</td>
<td>100m</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="14-suggestion-intelligente-du-rayon">14. Suggestion intelligente du rayon</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide en mode "Voiture" avec vitesse recommandée 30 km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur ajoute un point GPS</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une suggestion s'affiche: "Recommandé : 30m pour voiture à 30 km/h"</p>
<hr />
<h2 id="15-upload-audio-pour-une-sequence">15. Upload audio pour une séquence</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le créateur crée une séquence "Introduction"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il upload un fichier audio de 5 MB</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le fichier est vérifié:</p>
<pre><code>| vérification | règle |
|---|---|
| Format | MP3, AAC, M4A |
| Taille max | 200 MB |
| Durée max | 15 minutes |
</code></pre>
<hr />
<h2 id="16-ordre-des-sequences-modifiable">16. Ordre des séquences modifiable</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide avec 5 séquences:</p>
<pre><code>| ordre | titre |
|---|---|
| 1 | Introduction |
| 2 | Les lions |
| 3 | Les girafes |
| 4 | Les éléphants |
| 5 | Conclusion |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur glisse "Les éléphants" en position 2</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'ordre devient:</p>
<pre><code>| ordre | titre |
|---|---|
| 1 | Introduction |
| 2 | Les éléphants |
| 3 | Les lions |
| 4 | Les girafes |
| 5 | Conclusion |
</code></pre>
<hr />
<h2 id="17-nombre-minimum-de-sequences-requis">17. Nombre minimum de séquences requis</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide avec seulement 1 séquence</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur tente de passer à l'étape suivante</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message d'erreur s'affiche: "Minimum 2 séquences requis"
<span style="color: #9E9E9E"><strong>Et</strong></span> le bouton "Suivant" est désactivé</p>
<hr />
<h2 id="18-nombre-maximum-de-sequences">18. Nombre maximum de séquences</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide avec 50 séquences</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur tente d'ajouter une 51ème séquence</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message d'erreur s'affiche: "Maximum 50 séquences par audio-guide"
<span style="color: #9E9E9E"><strong>Et</strong></span> le bouton "+ Ajouter séquence" est désactivé</p>
<hr />
<h2 id="19-etape-3-preview-carte-avec-trace-et-points">19. Étape 3 - Preview carte avec tracé et points</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide avec 5 séquences géolocalisées</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur accède à l'étape 3 "Preview carte"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une carte Leaflet s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> les éléments suivants sont visibles:</p>
<pre><code>| é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 |
</code></pre>
<hr />
<h2 id="20-statistiques-du-parcours">20. Statistiques du parcours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide avec les séquences suivantes:</p>
<pre><code>| séquence | durée | distance_au_suivant |
|---|---|---|
| 1 | 2:15 | 150m |
| 2 | 3:42 | 200m |
| 3 | 4:10 | 320m |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les statistiques sont calculées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le résumé suivant est affiché:</p>
<pre><code>| métrique | valeur |
|---|---|
| Séquences | 3 complètes |
| Durée totale | 10:07 |
| Distance totale | 670m |
</code></pre>
<hr />
<h2 id="21-modification-dune-sequence-depuis-la-carte">21. Modification d'une séquence depuis la carte</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la preview carte est affichée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur clique sur le marker "2"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une popup s'affiche avec:</p>
<pre><code>| information |
|---|
| Titre: "Les lions" |
| Durée: 3:42 |
| Rayon: 30m |
| [✏️ Modifier] |
| [🗑️ Supprimer] |
</code></pre>
<hr />
<h2 id="22-zone-de-diffusion-geographique">22. Zone de diffusion géographique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide avec des points dans Paris</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur définit la zone de diffusion</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il peut choisir:</p>
<pre><code>| type | exemple |
|---|---|
| Polygon | Tracé manuel sur carte |
| Ville | Paris (API Nominatim) |
| Département | 75 - Paris |
| Région | Île-de-France |
</code></pre>
<hr />
<h2 id="23-etape-4-publication-et-validation-moderation">23. Étape 4 - Publication et validation modération</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un créateur qui publie ses 3 premiers audio-guides</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il clique sur "✅ Publier audio-guide"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message s'affiche:</p>
<hr />
<h2 id="24-publication-directe-pour-createurs-experimentes">24. Publication directe pour créateurs expérimentés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un créateur ayant publié 5 audio-guides validés
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun strike actif</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il publie un nouvel audio-guide</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'audio-guide est publié immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> il devient visible pour les utilisateurs
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune validation manuelle n'est requise</p>
<hr />
<h2 id="25-mode-pieton-sans-points-gps-obligatoires">25. Mode piéton sans points GPS obligatoires</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide en mode "🚶 Piéton"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur ajoute une séquence</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le champ "Point GPS" est optionnel
<span style="color: #9E9E9E"><strong>Et</strong></span> le champ "Rayon déclenchement" est masqué
<span style="color: #9E9E9E"><strong>Et</strong></span> un message info s'affiche: "Mode manuel : les séquences se déclenchent au clic utilisateur"</p>
<hr />
<h2 id="26-sauvegarde-brouillon-automatique">26. Sauvegarde brouillon automatique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le créateur édite un audio-guide depuis 5 minutes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il ajoute une nouvelle séquence</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'audio-guide est sauvegardé en brouillon automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> un toast "Brouillon sauvegardé" s'affiche brièvement</p>
<hr />
<h2 id="27-reprise-dun-brouillon">27. Reprise d'un brouillon</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide en brouillon "Safari du Paugre"
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il contient 3 séquences complètes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur retourne sur son dashboard</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le brouillon est visible avec le statut "📝 Brouillon"
<span style="color: #9E9E9E"><strong>Et</strong></span> un bouton "Continuer" est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> la progression "3/5 séquences" est affichée</p>
<hr />
<h2 id="28-suppression-dun-brouillon">28. Suppression d'un brouillon</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide en brouillon</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur clique sur "🗑️ Supprimer"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une confirmation s'affiche:</p>
<hr />
<h2 id="29-modification-dun-audio-guide-publie">29. Modification d'un audio-guide publié</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide publié "Safari du Paugre"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur clique sur "✏️ Modifier"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il peut modifier:</p>
<pre><code>| élément modifiable | élément non modifiable |
|---|---|
| Titre | Mode de déplacement |
| Description | Points GPS |
| Tags | Rayons déclenchement |
| Séquences (ordre) | |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> un avertissement s'affiche: "Les modifications structurelles nécessitent une nouvelle publication"</p>
<hr />
<h2 id="30-duplication-dun-audio-guide-existant">30. Duplication d'un audio-guide existant</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide publié "Visite Paris"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur clique sur "📋 Dupliquer"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une copie est créée avec le titre "Visite Paris (copie)"
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes les séquences sont copiées
<span style="color: #9E9E9E"><strong>Et</strong></span> le statut est "📝 Brouillon"
<span style="color: #9E9E9E"><strong>Et</strong></span> le créateur peut modifier avant publication</p>
<hr />
<h2 id="31-upload-audio-echoue-format-non-supporte">31. Upload audio échoue (format non supporté)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le créateur upload un fichier "audio.wav"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le format est vérifié</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message d'erreur s'affiche: "Format non supporté. Utilisez MP3, AAC ou M4A"
<span style="color: #9E9E9E"><strong>Et</strong></span> le fichier est rejeté</p>
<hr />
<h2 id="32-upload-audio-echoue-taille-trop-grande">32. Upload audio échoue (taille trop grande)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le créateur upload un fichier de 250 MB</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la taille est vérifiée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message d'erreur s'affiche: "Fichier trop volumineux. Maximum 200 MB"
<span style="color: #9E9E9E"><strong>Et</strong></span> le fichier est rejeté</p>
<hr />
<h2 id="33-points-gps-trop-eloignes-alerte-coherence">33. Points GPS trop éloignés (alerte cohérence)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide en mode "Piéton"
<span style="color: #9E9E9E"><strong>Et</strong></span> une séquence au Louvre (Paris)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur ajoute une séquence à Lyon</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un avertissement s'affiche:</p>
<hr />
<h2 id="34-pas-de-connexion-lors-de-la-sauvegarde">34. Pas de connexion lors de la sauvegarde</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le créateur édite un audio-guide
<span style="color: #9E9E9E"><strong>Et</strong></span> que la connexion réseau est perdue</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il tente de sauvegarder</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le brouillon est sauvegardé localement
<span style="color: #9E9E9E"><strong>Et</strong></span> un message s'affiche: "Sauvegarde locale. Sera synchronisée à la reconnexion"
<span style="color: #9E9E9E"><strong>Et</strong></span> une icône "☁️ Hors ligne" s'affiche</p>
<hr />
<h2 id="35-reprise-apres-perte-de-connexion">35. Reprise après perte de connexion</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un brouillon sauvegardé localement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la connexion réseau est rétablie</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le brouillon est synchronisé automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> un toast "✅ Audio-guide synchronisé" s'affiche</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="integration-audio-guides-avec-autres-fonctionnalites">Intégration audio-guides avec autres fonctionnalités</h1>
<blockquote>
<p><em>En tant qu'utilisateur</em>
<em>Je veux utiliser les audio-guides avec toutes les fonctionnalités de l'app</em>
<em>Afin d'avoir une expérience complète et cohérente</em></p>
</blockquote>
<p><strong>39 scénarios</strong> (38 standards, 1 plan)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'application RoadWave est démarrée
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur "jean@example.com" est connecté</p>
</blockquote>
<h2 id="1-telechargement-complet-dun-audio-guide">1. Téléchargement complet d'un audio-guide</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide "Visite du Louvre" avec 12 séquences</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur "⬇️ Télécharger pour écouter hors ligne"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> toutes les 12 séquences sont téléchargées
<span style="color: #9E9E9E"><strong>Et</strong></span> les métadonnées (titres, descriptions, GPS) sont sauvegardées
<span style="color: #9E9E9E"><strong>Et</strong></span> les images (cover, miniatures) sont mises en cache</p>
<hr />
<h2 id="2-affichage-de-la-progression-du-telechargement">2. Affichage de la progression du téléchargement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un téléchargement d'audio-guide est en cours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur consulte l'état</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la progression s'affiche:</p>
<hr />
<h2 id="3-telechargement-uniquement-en-wifi-par-defaut">3. Téléchargement uniquement en WiFi (par défaut)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'option "Télécharger uniquement en WiFi" est activée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur lance un téléchargement sur réseau mobile</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un avertissement s'affiche:</p>
<hr />
<h2 id="4-gestion-de-lespace-de-stockage">4. Gestion de l'espace de stockage</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'appareil a 500 MB d'espace libre
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un audio-guide pèse 380 MB</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur lance le téléchargement</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un avertissement s'affiche:</p>
<hr />
<h2 id="5-liste-des-audio-guides-telecharges">5. Liste des audio-guides téléchargés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur a téléchargé 3 audio-guides</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il accède à "Bibliothèque &gt; Téléchargés"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="6-lecture-hors-connexion-complete">6. Lecture hors connexion complète</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide est téléchargé
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur active le mode avion</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il lance l'audio-guide</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> toutes les séquences sont lisibles
<span style="color: #9E9E9E"><strong>Et</strong></span> les métadonnées sont accessibles
<span style="color: #9E9E9E"><strong>Et</strong></span> les images s'affichent normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> la progression est sauvegardée localement</p>
<hr />
<h2 id="7-gps-fonctionne-en-mode-avion-mode-voiture">7. GPS fonctionne en mode avion (mode voiture)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide voiture est téléchargé
<span style="color: #9E9E9E"><strong>Et</strong></span> que le mode avion est activé (avec GPS actif)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur se déplace</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les déclenchements GPS fonctionnent normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> la distance/ETA sont calculés</p>
<hr />
<h2 id="8-suppression-daudio-guide-telecharge">8. Suppression d'audio-guide téléchargé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide téléchargé pèse 380 MB</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur "🗑️ Supprimer téléchargement"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une confirmation s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> si confirmé, les 380 MB sont libérés
<span style="color: #9E9E9E"><strong>Et</strong></span> l'audio-guide reste accessible en streaming</p>
<hr />
<h2 id="9-mise-a-jour-automatique-si-nouvelle-version">9. Mise à jour automatique si nouvelle version</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide téléchargé a été mis à jour par le créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur se connecte en WiFi</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une notification s'affiche:</p>
<hr />
<h2 id="10-ajout-daudio-guide-a-une-playlist">10. Ajout d'audio-guide à une playlist</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur consulte un audio-guide</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il clique sur " Ajouter à une playlist"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ses playlists s'affichent:</p>
<pre><code>| playlist |
|---|
| 🗺️ Voyages en France |
| 🏛️ Musées parisiens |
| + Créer nouvelle playlist |
</code></pre>
<hr />
<h2 id="11-comportement-audio-guide-dans-une-playlist">11. Comportement audio-guide dans une playlist</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> une playlist contenant 2 audio-guides et 1 podcast</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la lecture atteint un audio-guide</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'audio-guide démarre à la séquence 1 (ou progression sauvegardée)
<span style="color: #9E9E9E"><strong>Et</strong></span> les séquences se jouent normalement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'audio-guide se termine (dernière séquence)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu suivant de la playlist démarre</p>
<hr />
<h2 id="12-audio-guide-marque-comme-favori">12. Audio-guide marqué comme "Favori"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur aime un audio-guide</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il clique sur "⭐ Ajouter aux favoris"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'audio-guide est ajouté à la section "Favoris"
<span style="color: #9E9E9E"><strong>Et</strong></span> il est facilement accessible depuis le menu principal</p>
<hr />
<h2 id="13-collections-thematiques-daudio-guides">13. Collections thématiques d'audio-guides</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave propose des collections éditoriales</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur accède à "Collections"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit des collections comme:</p>
<pre><code>| collection | nombre_audio_guides |
|---|---|
| 🏛️ Musées de France | 12 |
| 🦁 Parcs animaliers | 8 |
| 🚴 Circuits vélo | 15 |
| 🚗 Routes touristiques | 10 |
</code></pre>
<hr />
<h2 id="14-bouton-partager-sur-page-audio-guide">14. Bouton partager sur page audio-guide</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur consulte un audio-guide</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il clique sur "⬆️ Partager"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le menu de partage natif s'ouvre
<span style="color: #9E9E9E"><strong>Et</strong></span> le lien généré est "https://roadwave.fr/share/ag/louvre_123"</p>
<hr />
<h2 id="15-page-web-de-partage-pour-audio-guide">15. Page web de partage pour audio-guide</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un lien d'audio-guide partagé est ouvert sur le web</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la page se charge</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> elle affiche:</p>
<pre><code>| é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 |
</code></pre>
<hr />
<h2 id="16-deep-link-vers-audio-guide-specifique">16. Deep link vers audio-guide spécifique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'app est installée
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un lien "https://roadwave.fr/share/ag/louvre_123" est cliqué</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte l'app</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'app s'ouvre directement sur l'audio-guide
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur peut démarrer immédiatement</p>
<hr />
<h2 id="17-partage-avec-sequence-specifique">17. Partage avec séquence spécifique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur est sur la séquence 5 "La Joconde"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il partage l'audio-guide</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le lien généré est "https://roadwave.fr/share/ag/louvre_123?seq=5"
<span style="color: #9E9E9E"><strong>Et</strong></span> le destinataire est dirigé vers la séquence 5 directement</p>
<hr />
<h2 id="18-note-globale-de-laudio-guide">18. Note globale de l'audio-guide</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur termine un audio-guide</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la dernière séquence se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une popup de notation s'affiche:</p>
<hr />
<h2 id="19-note-moyenne-affichee-sur-la-page">19. Note moyenne affichée sur la page</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide a reçu 150 notes
<span style="color: #9E9E9E"><strong>Et</strong></span> que la moyenne est 4.3/5</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la page est affichée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la note "⭐ 4.3 (150 avis)" est visible</p>
<hr />
<h2 id="20-commentaires-tries-par-pertinence">20. Commentaires triés par pertinence</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide a 50 commentaires</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur consulte les avis</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les commentaires sont triés par défaut selon:</p>
<pre><code>| critère | poids |
|---|---|
| Note élevée | 30% |
| Récent | 30% |
| Likes reçus | 40% |
</code></pre>
<hr />
<h2 id="21-reponse-du-createur-aux-commentaires">21. Réponse du créateur aux commentaires</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur laisse un commentaire négatif</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur consulte son dashboard</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il peut répondre au commentaire
<span style="color: #9E9E9E"><strong>Et</strong></span> sa réponse apparaît en dessous avec badge "Créateur"</p>
<hr />
<h2 id="22-audio-guides-similaires-recommandes">22. Audio-guides similaires recommandés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur termine "Visite du Louvre"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il consulte les recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'algorithme suggère des audio-guides basés sur:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="23-suggestion-geographique-contextuelle">23. Suggestion géographique contextuelle</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur est à Paris (GPS détecté)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il ouvre l'onglet "Audio-guides"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les audio-guides parisiens sont mis en avant
<span style="color: #9E9E9E"><strong>Et</strong></span> un filtre "🗺️ Autour de moi" est pré-appliqué</p>
<hr />
<h2 id="24-badge-populaire-dans-votre-region">24. Badge "Populaire dans votre région"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide a &gt;100 écoutes dans la région Île-de-France
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur est en Île-de-France</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'audio-guide est affiché</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un badge "🔥 Populaire près de chez vous" est visible</p>
<hr />
<h2 id="25-prechargement-de-la-sequence-suivante">25. Préchargement de la séquence suivante</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la séquence 3 est en cours à 2:30/3:42</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il reste 60 secondes de lecture</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 4 est préchargée en arrière-plan
<span style="color: #9E9E9E"><strong>Et</strong></span> la transition est instantanée (0 latence)</p>
<hr />
<h2 id="26-buffer-adaptatif-selon-connexion">26. Buffer adaptatif selon connexion</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur est sur réseau 4G</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la séquence démarre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 30 secondes d'audio sont bufferisées initialement
<span style="color: #9E9E9E"><strong>Et</strong></span> le buffering continue en arrière-plan</p>
<hr />
<h2 id="27-plan-buffer-selon-qualite-reseau">27. 📋 Plan: Buffer selon qualité réseau</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur est sur réseau <reseau></p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> une séquence démarre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> <buffer_secondes> secondes sont bufferisées</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>reseau</th>
<th>buffer_secondes</th>
</tr>
</thead>
<tbody>
<tr>
<td>WiFi</td>
<td>60</td>
</tr>
<tr>
<td>5G</td>
<td>45</td>
</tr>
<tr>
<td>4G</td>
<td>30</td>
</tr>
<tr>
<td>3G</td>
<td>20</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="28-compression-audio-adaptative">28. Compression audio adaptative</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur est sur connexion lente (3G)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> une séquence est streamée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le CDN sert la version 64 kbps (au lieu de 128 kbps)
<span style="color: #9E9E9E"><strong>Et</strong></span> la qualité reste acceptable pour la voix</p>
<hr />
<h2 id="29-cache-intelligent-des-sequences-jouees">29. Cache intelligent des séquences jouées</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a écouté les séquences 1-5</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il clique sur "Précédent" pour réécouter la séquence 4</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 4 est chargée depuis le cache local
<span style="color: #9E9E9E"><strong>Et</strong></span> le chargement est instantané (pas de stream)</p>
<hr />
<h2 id="30-nettoyage-automatique-du-cache">30. Nettoyage automatique du cache</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le cache audio occupe 500 MB
<span style="color: #9E9E9E"><strong>Et</strong></span> que la limite configurée est 300 MB</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le nettoyage automatique s'exécute</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les séquences les plus anciennes (non téléchargées) sont supprimées
<span style="color: #9E9E9E"><strong>Et</strong></span> le cache revient à 280 MB</p>
<hr />
<h2 id="31-tracking-des-evenements-cles">31. Tracking des événements clés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur écoute un audio-guide</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il interagit avec l'application</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les événements suivants sont trackés:</p>
<pre><code>| é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 |
</code></pre>
<hr />
<h2 id="32-heatmap-des-abandons-par-sequence">32. Heatmap des abandons par séquence</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide a été écouté 1000 fois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur consulte la heatmap</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit pour chaque séquence:</p>
<pre><code>| sequence | starts | completions | abandon_rate |
|---|---|---|---|
| 1 | 1000 | 950 | 5% |
| 2 | 950 | 920 | 3% |
| 3 | 920 | 850 | 8% |
| ... | ... | ... | ... |
| 12 | 650 | 580 | 11% |
</code></pre>
<hr />
<h2 id="33-attribution-gps-auto-vs-manuel">33. Attribution GPS auto vs manuel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide voiture avec 8 points GPS</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les statistiques sont calculées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le créateur voit:</p>
<pre><code>| mode_declenchement | nombre |
|---|---|
| GPS automatique | 542 |
| Manuel | 123 |
| Point manqué | 89 |
</code></pre>
<hr />
<h2 id="34-audio-guide-avec-une-seule-sequence-edge-case">34. Audio-guide avec une seule séquence (edge case)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide avec seulement 1 séquence</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il est publié</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un avertissement s'affiche:</p>
<hr />
<h2 id="35-sequence-manquante-ou-corrompue">35. Séquence manquante ou corrompue</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une séquence 5 a un fichier audio corrompu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur tente de la lire</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message d'erreur s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> un bouton "⏭️ Passer à la suivante" est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> le créateur reçoit une notification de l'erreur</p>
<hr />
<h2 id="36-gps-desactive-puis-reactive-en-cours-de-route">36. GPS désactivé puis réactivé en cours de route</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide voiture en cours
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur désactive le GPS</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il le réactive 10 minutes plus tard</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le déclenchement automatique reprend
<span style="color: #9E9E9E"><strong>Et</strong></span> les points GPS manqués entre-temps ne déclenchent pas de popup</p>
<hr />
<h2 id="37-modification-daudio-guide-avec-utilisateurs-en-cours">37. Modification d'audio-guide avec utilisateurs en cours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide a 50 utilisateurs en cours d'écoute</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur modifie une séquence</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les utilisateurs actuels conservent l'ancienne version
<span style="color: #9E9E9E"><strong>Et</strong></span> les nouveaux utilisateurs obtiennent la nouvelle version
<span style="color: #9E9E9E"><strong>Et</strong></span> un message informe les utilisateurs lors de la prochaine ouverture</p>
<hr />
<h2 id="38-suppression-daudio-guide-par-le-createur">38. Suppression d'audio-guide par le créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide a 20 utilisateurs avec progression</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur supprime l'audio-guide</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une confirmation stricte est demandée
<span style="color: #9E9E9E"><strong>Et</strong></span> si confirmé, les progressions utilisateurs sont archivées (30 jours)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'audio-guide devient inaccessible</p>
<hr />
<h2 id="39-signalement-daudio-guide-pour-contenu-inapproprie">39. Signalement d'audio-guide pour contenu inapproprié</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur signale un audio-guide</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le signalement est modéré
<span style="color: #9E9E9E"><strong>Et</strong></span> jugé valide</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'audio-guide est dépublié temporairement
<span style="color: #9E9E9E"><strong>Et</strong></span> le créateur reçoit une notification d'explication
<span style="color: #9E9E9E"><strong>Et</strong></span> il peut corriger puis republier</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="audio-guide-mode-pieton-navigation-manuelle">Audio-guide mode piéton (navigation manuelle)</h1>
<blockquote>
<p><em>En tant qu'utilisateur à pied</em>
<em>Je veux naviguer manuellement entre les séquences d'un audio-guide</em>
<em>Afin de contrôler mon rythme de visite</em></p>
</blockquote>
<p><strong>29 scénarios</strong> (28 standards, 1 plan)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'application RoadWave est démarrée
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur "jean@example.com" est connecté (gratuit)
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un audio-guide piéton "Visite du Louvre" est disponible avec 12 séquences</p>
</blockquote>
<h2 id="1-fin-de-sequence-normale-avec-pause-automatique">1. Fin de séquence normale avec pause automatique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la séquence 1 "Introduction" est en cours de lecture</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la séquence se termine à 2:15</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le player se met en pause automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> le message suivant s'affiche: "Séquence 1 terminée. Appuyez sur Suivant quand vous êtes prêt."
<span style="color: #9E9E9E"><strong>Et</strong></span> la barre de progression indique "1/12 complétée"</p>
<hr />
<h2 id="2-passage-manuel-a-la-sequence-suivante">2. Passage manuel à la séquence suivante</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la séquence 1 est terminée et le player en pause</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur appuie sur le bouton [▶|] "Suivant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 2 "Pyramide du Louvre" démarre immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune latence n'est observée</p>
<hr />
<h2 id="3-sequence-avec-publicite-15-sequences">3. Séquence avec publicité (1/5 séquences)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la séquence 5 se termine
<span style="color: #9E9E9E"><strong>Et</strong></span> que c'est la 5ème séquence (1 pub toutes les 5)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la séquence se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publicité s'enchaîne automatiquement (sans attente bouton)
<span style="color: #9E9E9E"><strong>Et</strong></span> la publicité se lit normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> elle est skippable après 5 secondes</p>
<hr />
<h2 id="4-fin-de-publicite-avec-pause-automatique">4. Fin de publicité avec pause automatique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité est en cours de lecture</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la publicité se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le player se met en pause automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> le message suivant s'affiche: "Séquence 6 prête. Appuyez sur Suivant."
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur doit cliquer sur [▶|] pour continuer</p>
<hr />
<h2 id="5-flux-complet-sequence-pub-sequence">5. Flux complet séquence → pub → séquence</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la séquence 5 démarre</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la séquence 5 se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publicité démarre automatiquement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la publicité se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le player se met en pause</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur [▶|]</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 6 démarre</p>
<hr />
<h2 id="6-plan-frequence-de-publicite-configurable">6. 📋 Plan: Fréquence de publicité configurable</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur gratuit écoute un audio-guide
<span style="color: #9E9E9E"><strong>Et</strong></span> que la fréquence pub est configurée à <frequence></p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il termine la séquence <numero_sequence></p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une publicité est insérée : <pub_inseree></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>frequence</th>
<th>numero_sequence</th>
<th>pub_inseree</th>
</tr>
</thead>
<tbody>
<tr>
<td>1/5</td>
<td>5</td>
<td>Oui</td>
</tr>
<tr>
<td>1/5</td>
<td>10</td>
<td>Oui</td>
</tr>
<tr>
<td>1/5</td>
<td>4</td>
<td>Non</td>
</tr>
<tr>
<td>1/3</td>
<td>3</td>
<td>Oui</td>
</tr>
<tr>
<td>1/3</td>
<td>6</td>
<td>Oui</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="7-utilisateur-premium-sans-publicites">7. Utilisateur Premium sans publicités</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur "premium@example.com" est abonné Premium
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il écoute un audio-guide piéton</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il termine la séquence 5</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune publicité n'est insérée
<span style="color: #9E9E9E"><strong>Et</strong></span> le player se met en pause immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> le message "Séquence 6 prête. Appuyez sur Suivant." s'affiche</p>
<hr />
<h2 id="8-boutons-de-controle-disponibles-en-mode-pieton">8. Boutons de contrôle disponibles en mode piéton</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide piéton est en lecture</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur consulte les contrôles</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les boutons suivants sont visibles:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="9-passage-a-la-sequence-suivante-pendant-la-lecture">9. Passage à la séquence suivante pendant la lecture</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la séquence 3 "La Joconde" est en cours à 1:42/3:42</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur [▶|] "Suivant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 4 "Vénus de Milo" démarre immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> la séquence 3 n'est pas marquée comme écoutée (car &lt;80%)</p>
<hr />
<h2 id="10-retour-a-la-sequence-precedente-saut-direct">10. Retour à la séquence précédente (saut direct)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la séquence 5 est en cours de lecture</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur [|◀] "Précédent"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 4 démarre depuis le début (0:00)
<span style="color: #9E9E9E"><strong>Et</strong></span> il n'y a pas de logique "replay si &gt;10s" (contrairement au contenu classique)</p>
<hr />
<h2 id="11-pause-et-reprise-pendant-une-sequence">11. Pause et reprise pendant une séquence</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la séquence 2 est en cours à 1:15/1:48</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur [⏸️] "Pause"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la lecture se met en pause
<span style="color: #9E9E9E"><strong>Et</strong></span> la position 1:15 est conservée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur [▶️] "Play"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la lecture reprend exactement à 1:15</p>
<hr />
<h2 id="12-interface-liste-des-sequences">12. Interface liste des séquences</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide de 12 séquences est en cours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur [📋] "Liste séquences"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une liste complète s'affiche avec:</p>
<pre><code>| é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" |
</code></pre>
<hr />
<h2 id="13-sequence-en-cours-dans-la-liste">13. Séquence en cours dans la liste</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la séquence 3 est en cours à 1:22/3:42</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la liste des séquences est affichée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 3 affiche:</p>
<hr />
<h2 id="14-navigation-libre-vers-sequence-non-encore-ecoutee">14. Navigation libre vers séquence non encore écoutée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur est sur la séquence 3
<span style="color: #9E9E9E"><strong>Et</strong></span> que les séquences 4 à 12 n'ont pas été écoutées</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur "8. Les Appartements de Napoléon"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 8 démarre immédiatement depuis 0:00
<span style="color: #9E9E9E"><strong>Et</strong></span> les séquences 4 à 7 restent marquées ⭕ "À écouter"</p>
<hr />
<h2 id="15-retour-a-une-sequence-deja-ecoutee">15. Retour à une séquence déjà écoutée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la séquence 2 "Pyramide du Louvre" a été écoutée à 100%
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'elle est marquée ✅ "Écouté"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique dessus dans la liste</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 2 démarre depuis 0:00
<span style="color: #9E9E9E"><strong>Et</strong></span> le statut ✅ est conservé</p>
<hr />
<h2 id="16-checkmarks-sur-sequences-ecoutees-80">16. Checkmarks sur séquences écoutées &gt;80%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur écoute la séquence 2 de durée 1:48</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il écoute jusqu'à 1:30 (83% de complétion)
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il passe à la séquence suivante</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 2 est marquée ✅ "Écouté"
<span style="color: #9E9E9E"><strong>Et</strong></span> la date d'écoute est enregistrée</p>
<hr />
<h2 id="17-pas-de-checkmark-si-sequence-ecoutee-80">17. Pas de checkmark si séquence écoutée &lt;80%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur écoute la séquence 3 de durée 3:42</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il écoute jusqu'à 1:30 (40% de complétion)
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il passe à la séquence suivante</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 3 reste marquée ⭕ "À écouter"</p>
<hr />
<h2 id="18-bouton-tout-afficher-si-plus-de-6-sequences">18. Bouton "Tout afficher" si plus de 6 séquences</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide avec 12 séquences</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la liste est affichée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seules les 6 premières séquences sont visibles initialement
<span style="color: #9E9E9E"><strong>Et</strong></span> un bouton "Tout afficher ▼" est présent</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur "Tout afficher ▼"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les 6 séquences restantes sont affichées</p>
<hr />
<h2 id="19-saut-vers-sequence-specifique-depuis-la-barre-de-progression">19. Saut vers séquence spécifique depuis la barre de progression</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide est en cours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur "3/12" dans la barre de progression</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la liste des séquences s'ouvre
<span style="color: #9E9E9E"><strong>Et</strong></span> la séquence en cours (3) est mise en surbrillance</p>
<hr />
<h2 id="20-position-exacte-sauvegardee-automatiquement">20. Position exacte sauvegardée automatiquement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la séquence 5 est en cours à 2:34/4:10</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur quitte l'application</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la position 2:34 dans la séquence 5 est sauvegardée
<span style="color: #9E9E9E"><strong>Et</strong></span> la sauvegarde est effectuée localement (SQLite)
<span style="color: #9E9E9E"><strong>Et</strong></span> la sauvegarde est synchronisée sur le cloud (PostgreSQL)</p>
<hr />
<h2 id="21-reprise-apres-fermeture-de-lapplication">21. Reprise après fermeture de l'application</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur a quitté l'app à la séquence 5 position 2:34</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il rouvre l'audio-guide</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une popup de reprise s'affiche</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il clique sur "▶️ Reprendre"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la lecture reprend à la séquence 5 position 2:34 exacte</p>
<hr />
<h2 id="22-visiteur-qui-connait-deja-certaines-uvres">22. Visiteur qui connaît déjà certaines œuvres</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un visiteur du Louvre démarre l'audio-guide
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il connaît déjà "La Joconde" (séquence 3)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il arrive à la séquence 3
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il clique sur [▶|] "Suivant" après 10 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 4 démarre immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> la séquence 3 n'est pas marquée comme écoutée</p>
<hr />
<h2 id="23-visiteur-qui-veut-voir-une-uvre-eloignee">23. Visiteur qui veut voir une œuvre éloignée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un visiteur est à la séquence 2
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il aperçoit "La Victoire de Samothrace" (séquence 8) physiquement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il ouvre la liste et clique sur la séquence 8</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 8 démarre immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> il peut écouter la description même si les séquences 3-7 ne sont pas écoutées</p>
<hr />
<h2 id="24-visiteur-qui-prend-une-pause-cafe">24. Visiteur qui prend une pause café</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un visiteur écoute la séquence 6</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il clique sur [⏸️] "Pause"
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il ferme l'application pendant 30 minutes
<span style="color: #FF9800"><strong>Quand</strong></span> il rouvre l'application</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 6 reprend à la position exacte où il s'était arrêté</p>
<hr />
<h2 id="25-visiteur-qui-revient-le-lendemain">25. Visiteur qui revient le lendemain</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un visiteur a écouté les séquences 1-5 hier
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il revient au musée aujourd'hui</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il ouvre l'audio-guide</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une popup propose "▶️ Reprendre" (séquence 6)
<span style="color: #9E9E9E"><strong>Et</strong></span> les séquences 1-5 sont marquées ✅ "Écouté"</p>
<hr />
<h2 id="26-sequence-audio-corrompue-ou-indisponible">26. Séquence audio corrompue ou indisponible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la séquence 7 a un fichier audio corrompu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur tente de la lire</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message d'erreur s'affiche:</p>
<hr />
<h2 id="27-perte-de-connexion-pendant-le-chargement">27. Perte de connexion pendant le chargement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur lance la séquence 4
<span style="color: #9E9E9E"><strong>Et</strong></span> que la connexion réseau est perdue</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le chargement échoue</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message s'affiche: "Connexion perdue. Vérifiez votre réseau."
<span style="color: #9E9E9E"><strong>Et</strong></span> un bouton "🔄 Réessayer" est disponible</p>
<hr />
<h2 id="28-batterie-faible-en-cours-de-visite">28. Batterie faible en cours de visite</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la batterie de l'appareil est à 5%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur écoute une séquence</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une notification système s'affiche: "Batterie faible. Progression sauvegardée."
<span style="color: #9E9E9E"><strong>Et</strong></span> la position est sauvegardée localement toutes les 10 secondes</p>
<hr />
<h2 id="29-mode-pieton-sans-points-gps-pas-dalerte-localisation">29. Mode piéton sans points GPS (pas d'alerte localisation)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide en mode piéton
<span style="color: #9E9E9E"><strong>Et</strong></span> que le GPS est désactivé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur démarre l'audio-guide</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune alerte GPS ne s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> l'audio-guide fonctionne normalement (navigation 100% manuelle)</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="audio-guide-mode-voiture-gps-automatique">Audio-guide mode voiture (GPS automatique)</h1>
<blockquote>
<p><em>En tant qu'utilisateur en voiture</em>
<em>Je veux que les séquences se déclenchent automatiquement selon ma position GPS</em>
<em>Afin de profiter d'une expérience guidée hands-free</em></p>
</blockquote>
<p><strong>45 scénarios</strong> (40 standards, 5 plans)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'application RoadWave est démarrée
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur "jean@example.com" est connecté (gratuit)
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un audio-guide voiture "Safari du Paugre" est disponible avec 8 séquences
<span style="color: #9E9E9E"><strong>Et</strong></span> que le GPS est activé</p>
</blockquote>
<h2 id="1-distinction-audio-guides-vs-contenus-geolocalises-simples">1. Distinction audio-guides vs contenus géolocalisés simples</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur est en mode voiture</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il écoute un contenu géolocalisé simple (1 séquence unique)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une notification avec compteur 7→1 est affichée 7s avant le point
<span style="color: #9E9E9E"><strong>Et</strong></span> il doit valider avec "Suivant" + décompte 5s
<span style="color: #9E9E9E"><strong>Et</strong></span> ce contenu compte 1/6 dans le quota horaire</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il démarre un audio-guide multi-séquences</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les séquences se déclenchent au point GPS exact (rayon 30m)
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun compteur 7s n'est affiché (juste notification "Ding" + toast 2s)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'audio-guide entier compte 1/6 dans le quota</p>
<hr />
<h2 id="2-demarrage-automatique-au-premier-point-gps">2. Démarrage automatique au premier point GPS</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur démarre l'audio-guide "Safari du Paugre"
<span style="color: #9E9E9E"><strong>Et</strong></span> que le point de départ est à (43.1234, 2.5678) avec rayon 30m</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur entre dans le rayon de 30m</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 1 "Introduction - Point d'accueil" démarre automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> une notification sonore "Ding" est jouée (non intrusif)
<span style="color: #9E9E9E"><strong>Et</strong></span> un toast s'affiche brièvement pendant 2s: "Introduction - Point d'accueil"
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun compteur 7→1 n'est affiché (contrairement aux contenus géolocalisés simples)</p>
<hr />
<h2 id="3-declenchement-automatique-sequence-suivante">3. Déclenchement automatique séquence suivante</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la séquence 1 est terminée
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur se déplace vers le point GPS 2 (43.1245, 2.5690)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur entre dans le rayon de 30m du point 2</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 2 "Enclos des lions" démarre automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> une notification "Ding" + toast "Enclos des lions" s'affiche</p>
<hr />
<h2 id="4-navigation-manuelle-conservee-bouton-suivant-actif">4. Navigation manuelle conservée (bouton Suivant actif)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la séquence 1 est en cours
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur est encore loin du point GPS 2 (distance 500m)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur [▶|] "Suivant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 2 démarre immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune vérification GPS n'est effectuée</p>
<hr />
<h2 id="5-navigation-manuelle-conservee-bouton-precedent-actif">5. Navigation manuelle conservée (bouton Précédent actif)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la séquence 3 est en cours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur [|◀] "Précédent"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 2 démarre depuis le début
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune vérification GPS n'est effectuée</p>
<hr />
<h2 id="6-tous-les-boutons-de-controle-restent-actifs">6. Tous les boutons de contrôle restent actifs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide voiture est en cours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur consulte les contrôles</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les boutons suivants sont actifs:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="7-use-case-embouteillage-sequence-finie-point-gps-loin">7. Use case - Embouteillage (séquence finie, point GPS loin)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la séquence 3 "Enclos des girafes" est terminée
<span style="color: #9E9E9E"><strong>Et</strong></span> que le point GPS 4 est à 2 km de distance (embouteillage)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique manuellement sur [▶|] "Suivant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 4 démarre immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur peut continuer l'expérience sans attendre d'atteindre le point GPS</p>
<hr />
<h2 id="8-use-case-route-fermee-point-gps-inaccessible">8. Use case - Route fermée (point GPS inaccessible)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le point GPS 5 est sur une route fermée
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur ne peut pas s'en approcher</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur [▶|] "Suivant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 5 démarre quand même
<span style="color: #9E9E9E"><strong>Et</strong></span> l'audio-guide continue normalement</p>
<hr />
<h2 id="9-use-case-passager-manipule-lapplication">9. Use case - Passager manipule l'application</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur est passager (non conducteur)
<span style="color: #9E9E9E"><strong>Et</strong></span> que la vitesse du véhicule est 45 km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le passager clique sur [▶|] "Suivant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence suivante démarre
<span style="color: #9E9E9E"><strong>Et</strong></span> un avertissement s'affiche pendant 3 secondes</p>
<hr />
<h2 id="10-avertissement-securite-si-vitesse-10-kmh">10. Avertissement sécurité si vitesse &gt;10 km/h</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la vitesse actuelle est 35 km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur un bouton (Suivant ou Précédent)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'action est exécutée immédiatement (pas de blocage)
<span style="color: #9E9E9E"><strong>Et</strong></span> un toast s'affiche pendant 3 secondes:</p>
<hr />
<h2 id="11-plan-avertissement-selon-la-vitesse">11. 📋 Plan: Avertissement selon la vitesse</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la vitesse actuelle est <vitesse> km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur un bouton de navigation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'avertissement est affiché : <avertissement></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>vitesse</th>
<th>avertissement</th>
</tr>
</thead>
<tbody>
<tr>
<td>5</td>
<td>Non</td>
</tr>
<tr>
<td>10</td>
<td>Non</td>
</tr>
<tr>
<td>11</td>
<td>Oui</td>
</tr>
<tr>
<td>35</td>
<td>Oui</td>
</tr>
<tr>
<td>90</td>
<td>Oui</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="12-affichage-entre-deux-sequences-avec-progress-bar">12. Affichage entre deux séquences avec progress bar</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la séquence 2 "Les lions" vient de se terminer
<span style="color: #9E9E9E"><strong>Et</strong></span> que le prochain point GPS 3 "Enclos des girafes" est à 500m</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'interface bascule en mode "attente prochain point"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'écran affiche:</p>
<pre><code>| é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 |
</code></pre>
<hr />
<h2 id="13-progress-bar-dynamique-vers-le-prochain-point">13. Progress bar dynamique vers le prochain point</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la distance initiale vers le prochain point était 500m
<span style="color: #9E9E9E"><strong>Et</strong></span> que la séquence précédente est terminée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur se rapproche du prochain point
<span style="color: #9E9E9E"><strong>Et</strong></span> que la distance actuelle est 175m</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la progress bar affiche "65%" remplie
<span style="color: #9E9E9E"><strong>Et</strong></span> le calcul est: 100 - (175 / 500 * 100) = 65%
<span style="color: #9E9E9E"><strong>Et</strong></span> la barre se met à jour chaque seconde</p>
<hr />
<h2 id="14-bouton-rejouer-seq-pour-reecouter">14. Bouton "Rejouer séq." pour réécouter</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la séquence 3 vient de se terminer
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'interface "attente prochain point" est affichée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur [▶️ Rejouer séq.]</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 3 redémarre depuis 0:00
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur peut la réécouter (utile si distraction)</p>
<hr />
<h2 id="15-interface-en-conduite-avec-distance-et-eta">15. Interface en conduite avec distance et ETA</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la séquence 2 est en cours
<span style="color: #9E9E9E"><strong>Et</strong></span> que le prochain point GPS 3 "Enclos des girafes" est à 320m
<span style="color: #9E9E9E"><strong>Et</strong></span> que la vitesse actuelle est 28 km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'interface est affichée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les informations suivantes sont visibles:</p>
<pre><code>| 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" |
</code></pre>
<hr />
<h2 id="16-mise-a-jour-de-la-distance-en-temps-reel">16. Mise à jour de la distance en temps réel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la distance au prochain point est 500m</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> 10 secondes s'écoulent et que l'utilisateur se rapproche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la distance est mise à jour chaque seconde
<span style="color: #9E9E9E"><strong>Et</strong></span> la nouvelle distance "450m" s'affiche</p>
<hr />
<h2 id="17-mise-a-jour-de-leta-en-temps-reel">17. Mise à jour de l'ETA en temps réel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'ETA est "≈ 2 minutes"
<span style="color: #9E9E9E"><strong>Et</strong></span> que la vitesse est constante à 30 km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur se rapproche du point</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'ETA est recalculé chaque seconde
<span style="color: #9E9E9E"><strong>Et</strong></span> il diminue progressivement: "≈ 1 minute 50", "≈ 1 minute 40", etc.</p>
<hr />
<h2 id="18-plan-format-daffichage-de-la-distance">18. 📋 Plan: Format d'affichage de la distance</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la distance au prochain point est <distance_metres></p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'interface est mise à jour</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la distance affichée est "<affichage>"</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>distance_metres</th>
<th>affichage</th>
</tr>
</thead>
<tbody>
<tr>
<td>50</td>
<td>50 m</td>
</tr>
<tr>
<td>320</td>
<td>320 m</td>
</tr>
<tr>
<td>980</td>
<td>980 m</td>
</tr>
<tr>
<td>1200</td>
<td>1.2 km</td>
</tr>
<tr>
<td>5400</td>
<td>5.4 km</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="19-plan-format-daffichage-de-leta">19. 📋 Plan: Format d'affichage de l'ETA</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'ETA calculé est <secondes> secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'interface est mise à jour</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'ETA affiché est "<affichage>"</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>secondes</th>
<th>affichage</th>
</tr>
</thead>
<tbody>
<tr>
<td>30</td>
<td>≈ 30 secondes</td>
</tr>
<tr>
<td>75</td>
<td>≈ 1 minute</td>
</tr>
<tr>
<td>150</td>
<td>≈ 2 minutes</td>
</tr>
<tr>
<td>400</td>
<td>≈ 6 minutes</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="20-calcul-de-la-direction-fleche-8-directions">20. Calcul de la direction (flèche 8 directions)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la position actuelle est (43.1234, 2.5678)
<span style="color: #9E9E9E"><strong>Et</strong></span> que le prochain point est au nord-est (angle 45°)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la direction est calculée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la flèche "↗" est affichée</p>
<hr />
<h2 id="21-plan-fleches-de-direction-selon-langle">21. 📋 Plan: Flèches de direction selon l'angle</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'angle vers le prochain point est <angle>°</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la direction est calculée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la flèche "<fleche>" est affichée</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>angle</th>
<th>fleche</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>↑</td>
</tr>
<tr>
<td>45</td>
<td>↗</td>
</tr>
<tr>
<td>90</td>
<td>→</td>
</tr>
<tr>
<td>135</td>
<td>↘</td>
</tr>
<tr>
<td>180</td>
<td>↓</td>
</tr>
<tr>
<td>225</td>
<td>↙</td>
</tr>
<tr>
<td>270</td>
<td>←</td>
</tr>
<tr>
<td>315</td>
<td>↖</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="22-mise-a-jour-de-la-direction-toutes-les-5-secondes">22. Mise à jour de la direction toutes les 5 secondes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la direction actuelle est ↑ (nord)
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur tourne vers l'est</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> 5 secondes s'écoulent</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la direction est recalculée
<span style="color: #9E9E9E"><strong>Et</strong></span> la nouvelle flèche ↗ (nord-est) s'affiche</p>
<hr />
<h2 id="23-message-en-attente-de-deplacement-si-vitesse-5-kmh">23. Message "En attente de déplacement" si vitesse &lt;5 km/h</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la vitesse actuelle est 2 km/h (arrêté)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'ETA est calculé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le message "En attente de déplacement" s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> l'ETA n'est pas calculé (car vitesse insuffisante)</p>
<hr />
<h2 id="24-simplicite-de-linterface-pas-de-carte-miniature">24. Simplicité de l'interface (pas de carte miniature)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide voiture est en cours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'interface est affichée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune carte miniature n'est présente
<span style="color: #9E9E9E"><strong>Et</strong></span> seuls les éléments essentiels sont affichés:</p>
<pre><code>| élément |
|---|
| Distance |
| ETA |
| Direction (flèche) |
| Vitesse |
| Contrôles audio |
</code></pre>
<hr />
<h2 id="25-rayon-de-declenchement-par-defaut-en-mode-voiture">25. Rayon de déclenchement par défaut en mode voiture</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide voiture</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un point GPS est défini</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le rayon de déclenchement est 30 mètres par défaut
<span style="color: #9E9E9E"><strong>Et</strong></span> le rayon de tolérance "point manqué" est 100 mètres</p>
<hr />
<h2 id="26-declenchement-dans-le-rayon-30m">26. Déclenchement dans le rayon (30m)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le point GPS 3 est défini avec rayon 30m</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur entre à 25m du point</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 3 se déclenche automatiquement</p>
<hr />
<h2 id="27-pas-de-declenchement-hors-rayon">27. Pas de déclenchement hors rayon</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le point GPS 3 a un rayon de 30m</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur passe à 45m du point</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 3 ne se déclenche pas automatiquement</p>
<hr />
<h2 id="28-point-manque-dans-rayon-de-tolerance-100m">28. Point manqué dans rayon de tolérance (100m)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur passe à 60m du point GPS 4 (hors rayon 30m)
<span style="color: #9E9E9E"><strong>Et</strong></span> que 60m &lt; 100m (rayon tolérance)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le point est détecté comme manqué</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un toast s'affiche: "⚠️ Point manqué : Enclos des éléphants"
<span style="color: #9E9E9E"><strong>Et</strong></span> une popup s'affiche pendant 5 secondes avec 3 options</p>
<hr />
<h2 id="29-popup-point-manque-avec-3-actions">29. Popup "Point manqué" avec 3 actions</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un point GPS a été manqué (distance 60m)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la popup s'affiche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les options suivantes sont disponibles:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="30-action-ecouter-quand-meme">30. Action "Écouter quand même"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un point GPS est manqué</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur "🔊 Écouter quand même"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence correspondante démarre immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur peut continuer sa route</p>
<hr />
<h2 id="31-action-passer-au-suivant">31. Action "Passer au suivant"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un point GPS 5 est manqué</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur "⏭️ Passer au suivant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 5 est ignorée (non écoutée)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'application attend le point GPS 6
<span style="color: #9E9E9E"><strong>Et</strong></span> la distance vers le point 6 s'affiche</p>
<hr />
<h2 id="32-action-faire-demi-tour">32. Action "Faire demi-tour"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un point GPS est manqué à (43.1250, 2.5700)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur "🔙 Faire demi-tour"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'application détecte l'app GPS installée (Google Maps ou Waze)
<span style="color: #9E9E9E"><strong>Et</strong></span> ouvre la navigation GPS externe vers (43.1250, 2.5700)</p>
<hr />
<h2 id="33-point-manque-au-dela-du-rayon-de-tolerance-100m">33. Point manqué au-delà du rayon de tolérance (&gt;100m)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur passe à 150m du point GPS 6</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la distance est détectée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune popup ne s'affiche (point trop loin)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur peut naviguer manuellement avec [▶|]</p>
<hr />
<h2 id="34-plan-gestion-selon-la-distance-au-point">34. 📋 Plan: Gestion selon la distance au point</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un point GPS avec rayon 30m et tolérance 100m</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur passe à <distance> du point</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le comportement est <comportement></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>distance</th>
<th>comportement</th>
</tr>
</thead>
<tbody>
<tr>
<td>20m</td>
<td>Déclenchement automatique séquence</td>
</tr>
<tr>
<td>40m</td>
<td>Rien (hors rayon, pas encore tolérance)</td>
</tr>
<tr>
<td>60m</td>
<td>Popup "Point manqué" avec 3 options</td>
</tr>
<tr>
<td>110m</td>
<td>Rien (trop loin, hors tolérance)</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="35-configuration-rayon-personnalise-par-le-createur">35. Configuration rayon personnalisé par le créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur définit un rayon de 50m (au lieu de 30m)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un utilisateur entre à 45m du point</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence se déclenche automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> le rayon personnalisé est respecté</p>
<hr />
<h2 id="36-rayon-minimum-et-maximum-configurables">36. Rayon minimum et maximum configurables</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur configure un rayon</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il ajuste le curseur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les valeurs disponibles sont de 10m à 200m
<span style="color: #9E9E9E"><strong>Et</strong></span> le rayon par défaut suggéré est 30m pour la voiture</p>
<hr />
<h2 id="37-safari-parc-avec-declenchement-automatique-fluide">37. Safari-parc avec déclenchement automatique fluide</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur roule dans un safari à 20 km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il passe devant "Enclos des lions" (point GPS 2)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 2 démarre automatiquement sans intervention
<span style="color: #9E9E9E"><strong>Et</strong></span> il peut se concentrer sur la conduite et l'observation</p>
<hr />
<h2 id="38-detour-imprevu-travaux-sur-la-route">38. Détour imprévu (travaux sur la route)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur prend un détour à cause de travaux
<span style="color: #9E9E9E"><strong>Et</strong></span> que le point GPS 4 devient inaccessible</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il est loin du point (&gt;100m)
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il clique manuellement sur [▶|]</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 4 démarre quand même
<span style="color: #9E9E9E"><strong>Et</strong></span> l'expérience continue sans blocage</p>
<hr />
<h2 id="39-passager-qui-navigue-librement">39. Passager qui navigue librement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un passager utilise l'application
<span style="color: #9E9E9E"><strong>Et</strong></span> que le conducteur roule à 50 km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le passager clique sur "Précédent" pour réécouter</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'action est exécutée immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> un warning apparaît brièvement (sensibilisation)</p>
<hr />
<h2 id="40-embouteillage-prolonge">40. Embouteillage prolongé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la séquence 3 est terminée depuis 10 minutes
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur est bloqué dans un embouteillage
<span style="color: #9E9E9E"><strong>Et</strong></span> que le point GPS 4 est encore à 1.5 km</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur [▶|]</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 4 démarre immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur peut passer le temps en écoutant</p>
<hr />
<h2 id="41-gps-desactive-en-mode-voiture">41. GPS désactivé en mode voiture</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide voiture est démarré
<span style="color: #9E9E9E"><strong>Et</strong></span> que le GPS est désactivé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'application détecte l'absence de GPS</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une alerte s'affiche:</p>
<hr />
<h2 id="42-action-passer-en-mode-manuel">42. Action "Passer en mode Manuel"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le GPS est désactivé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur "Passer en mode Manuel"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'audio-guide bascule en navigation 100% manuelle
<span style="color: #9E9E9E"><strong>Et</strong></span> les boutons [▶|] et [|◀] permettent de naviguer
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun déclenchement GPS n'est tenté</p>
<hr />
<h2 id="43-precision-gps-insuffisante">43. Précision GPS insuffisante</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le signal GPS a une précision de ±150m</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur approche d'un point GPS avec rayon 30m</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un avertissement s'affiche:</p>
<hr />
<h2 id="44-perte-signal-gps-en-cours-de-route">44. Perte signal GPS en cours de route</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide voiture est en cours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le signal GPS est perdu (tunnel, parking souterrain)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un toast s'affiche: "Signal GPS perdu. Navigation manuelle active."
<span style="color: #9E9E9E"><strong>Et</strong></span> les boutons de navigation restent actifs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le signal GPS revient</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un toast s'affiche: "Signal GPS rétabli"
<span style="color: #9E9E9E"><strong>Et</strong></span> le déclenchement automatique est réactivé</p>
<hr />
<h2 id="45-depassement-de-la-vitesse-recommandee">45. Dépassement de la vitesse recommandée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide recommande 20-30 km/h
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur roule à 65 km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la vitesse est détectée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'affichage vitesse est en orange: "⚠️ 65 km/h"
<span style="color: #9E9E9E"><strong>Et</strong></span> un message info s'affiche: "Vitesse élevée. Risque de manquer des points."</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="audio-guides-modes-velo-et-transport">Audio-guides modes vélo et transport</h1>
<blockquote>
<p><em>En tant qu'utilisateur à vélo ou en transport en commun</em>
<em>Je veux profiter d'un guidage GPS adapté à mon mode de déplacement</em>
<em>Afin d'avoir une expérience optimisée avec tolérances appropriées</em></p>
</blockquote>
<p><strong>27 scénarios</strong> (24 standards, 3 plans)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'application RoadWave est démarrée
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur "jean@example.com" est connecté
<span style="color: #9E9E9E"><strong>Et</strong></span> que le GPS est activé</p>
</blockquote>
<h2 id="1-plan-parametres-par-mode-de-deplacement">1. 📋 Plan: Paramètres par mode de déplacement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide configuré en mode <mode></p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les paramètres suivants sont appliqués:</p>
<pre><code>| paramètre | valeur |
|---|---|
| Rayon déclenchement | &lt;rayon_declenchement&gt; |
| Rayon tolérance "point manqué" | &lt;rayon_tolerance&gt; |
| Vitesse recommandée | &lt;vitesse_recommandee&gt; |
| Seuil warning sécurité | &lt;seuil_warning&gt; |
</code></pre>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>mode</th>
<th>rayon_declenchement</th>
<th>rayon_tolerance</th>
<th>vitesse_recommandee</th>
<th>seuil_warning</th>
</tr>
</thead>
<tbody>
<tr>
<td>Voiture</td>
<td>30m</td>
<td>100m</td>
<td>20-50 km/h</td>
<td>&gt;10 km/h</td>
</tr>
<tr>
<td>Vélo</td>
<td>50m</td>
<td>75m</td>
<td>10-25 km/h</td>
<td>&gt;5 km/h</td>
</tr>
<tr>
<td>Transport</td>
<td>100m</td>
<td>150m</td>
<td>Variable</td>
<td>Désactivé</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="2-declenchement-automatique-avec-rayon-50m-mode-velo">2. Déclenchement automatique avec rayon 50m (mode vélo)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide vélo "Circuit des châteaux de la Loire"
<span style="color: #9E9E9E"><strong>Et</strong></span> que le point GPS 3 a un rayon de 50m</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur à vélo entre à 45m du point</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 3 "Château de Chambord" se déclenche automatiquement</p>
<hr />
<h2 id="3-rayon-plus-large-justifie-pour-le-velo">3. Rayon plus large justifié pour le vélo</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un cycliste roule sur piste cyclable
<span style="color: #9E9E9E"><strong>Et</strong></span> que sa vitesse varie entre 8 et 22 km/h (arrêts fréquents)
<span style="color: #9E9E9E"><strong>Et</strong></span> que le tracé est moins prévisible qu'en voiture</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un point GPS avec rayon 50m est défini</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le rayon plus large compense la variabilité de trajectoire</p>
<hr />
<h2 id="4-warning-securite-des-5-kmh-en-velo">4. Warning sécurité dès 5 km/h en vélo</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide vélo en cours
<span style="color: #9E9E9E"><strong>Et</strong></span> que la vitesse actuelle est 12 km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur [▶|] "Suivant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'action est exécutée
<span style="color: #9E9E9E"><strong>Et</strong></span> un warning s'affiche: "⚠️ Manipulation en déplacement détecté. Pour votre sécurité, arrêtez-vous."</p>
<hr />
<h2 id="5-plan-warning-velo-selon-la-vitesse">5. 📋 Plan: Warning vélo selon la vitesse</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la vitesse actuelle à vélo est <vitesse> km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur un bouton de navigation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le warning est affiché : <warning></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>vitesse</th>
<th>warning</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>Non</td>
</tr>
<tr>
<td>4</td>
<td>Non</td>
</tr>
<tr>
<td>6</td>
<td>Oui</td>
</tr>
<tr>
<td>15</td>
<td>Oui</td>
</tr>
<tr>
<td>25</td>
<td>Oui</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="6-tolerance-gps-moins-stricte-en-velo">6. Tolérance GPS moins stricte en vélo</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un cycliste passe à 65m du point GPS 4
<span style="color: #9E9E9E"><strong>Et</strong></span> que le rayon de déclenchement est 50m
<span style="color: #9E9E9E"><strong>Et</strong></span> que le rayon de tolérance est 75m</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la distance est détectée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la popup "Point manqué" s'affiche avec 3 options
<span style="color: #9E9E9E"><strong>Et</strong></span> le système tolère l'écart (trajectoire vélo moins prévisible)</p>
<hr />
<h2 id="7-affichage-adapte-au-velo">7. Affichage adapté au vélo</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide vélo en cours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'interface est affichée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les informations suivantes sont visibles:</p>
<pre><code>| 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" |
</code></pre>
<hr />
<h2 id="8-cas-dusage-piste-cyclable-avec-arrets-frequents">8. Cas d'usage - Piste cyclable avec arrêts fréquents</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un cycliste suit un circuit nature
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il s'arrête régulièrement (feux, photos, fatigue)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il s'arrête à 40m d'un point GPS (rayon 50m)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence se déclenche automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> le rayon large permet le déclenchement malgré l'arrêt</p>
<hr />
<h2 id="9-cas-dusage-circulation-mixte-pietonsvelos">9. Cas d'usage - Circulation mixte piétons/vélos</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un cycliste roule sur voie partagée
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il doit ralentir fréquemment pour éviter les piétons</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> sa vitesse varie entre 5 et 20 km/h</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système s'adapte avec le rayon 50m
<span style="color: #9E9E9E"><strong>Et</strong></span> le déclenchement reste fiable</p>
<hr />
<h2 id="10-declenchement-automatique-avec-rayon-100m-mode-transport">10. Déclenchement automatique avec rayon 100m (mode transport)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide transport "Ligne touristique Paris"
<span style="color: #9E9E9E"><strong>Et</strong></span> que le point GPS "Tour Eiffel" a un rayon de 100m</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le bus touristique entre à 85m du point</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence "Tour Eiffel" se déclenche automatiquement</p>
<hr />
<h2 id="11-rayon-tres-large-justifie-pour-le-transport">11. Rayon très large justifié pour le transport</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un bus touristique suit une ligne fixe
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il effectue des arrêts fréquents (stations)
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur n'a aucun contrôle sur la trajectoire</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un point GPS avec rayon 100m est défini</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le rayon large compense les arrêts et la ligne fixe</p>
<hr />
<h2 id="12-pas-de-warning-securite-en-mode-transport">12. Pas de warning sécurité en mode transport</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide transport en cours
<span style="color: #9E9E9E"><strong>Et</strong></span> que le bus roule à 50 km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur [▶|] "Suivant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'action est exécutée immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun warning n'est affiché</p>
<hr />
<h2 id="13-vitesse-recommandee-selon-ligne">13. Vitesse recommandée "Selon ligne"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide transport</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'interface est affichée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la vitesse recommandée indique "Selon ligne"
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune valeur fixe n'est affichée (car ligne de transport varie)</p>
<hr />
<h2 id="14-tolerance-horaire-pour-retards">14. Tolérance horaire pour retards</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un bus touristique est en retard de 3 minutes
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il arrive au point GPS "Musée du Louvre" avec retard</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il entre dans le rayon de 100m</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence se déclenche normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> le système tolère le retard (pas de pénalité temporelle)</p>
<hr />
<h2 id="15-tolerance-spatiale-tres-large-150m">15. Tolérance spatiale très large (150m)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un bus passe à 120m du point GPS "Arc de Triomphe"
<span style="color: #9E9E9E"><strong>Et</strong></span> que le rayon de déclenchement est 100m
<span style="color: #9E9E9E"><strong>Et</strong></span> que le rayon de tolérance est 150m</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la distance est détectée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la popup "Point manqué" s'affiche avec 3 options</p>
<hr />
<h2 id="16-affichage-adapte-au-transport">16. Affichage adapté au transport</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide transport en cours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'interface est affichée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les informations suivantes sont visibles:</p>
<pre><code>| 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" |
</code></pre>
<hr />
<h2 id="17-cas-dusage-bus-touristique-hop-on-hop-off">17. Cas d'usage - Bus touristique hop-on hop-off</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un bus touristique "Paris Open Tour"
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il suit un circuit fixe avec 15 arrêts</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il approche de chaque arrêt</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence correspondante se déclenche automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur n'a rien à faire (expérience passive)</p>
<hr />
<h2 id="18-cas-dusage-train-panoramique">18. Cas d'usage - Train panoramique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un train touristique "Ligne des Alpes"
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il roule à vitesse variable (20-80 km/h)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il passe près de points d'intérêt</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les séquences se déclenchent avec rayon 100m
<span style="color: #9E9E9E"><strong>Et</strong></span> le système compense la vitesse élevée</p>
<hr />
<h2 id="19-navigation-manuelle-conservee-velo-et-transport">19. Navigation manuelle conservée (vélo et transport)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide en mode <mode></p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur [▶|] ou [|◀]</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les boutons manuels fonctionnent normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune vérification GPS n'est effectuée</p>
<hr />
<h2 id="20-affichage-distance-eta-direction-tous-modes">20. Affichage distance + ETA + direction (tous modes)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide en mode <mode></p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'interface est affichée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les informations distance, ETA et direction sont affichées
<span style="color: #9E9E9E"><strong>Et</strong></span> le format est identique au mode voiture</p>
<hr />
<h2 id="21-gestion-point-manque-identique">21. Gestion "Point manqué" identique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide en mode <mode></p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un point GPS est manqué (dans rayon tolérance)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la popup avec 3 options s'affiche:</p>
<pre><code>| option |
|---|
| 🔊 Écouter quand même |
| ⏭️ Passer au suivant |
| 🔙 Faire demi-tour |
</code></pre>
<hr />
<h2 id="22-plan-insertion-publicite-dans-tous-les-modes">22. 📋 Plan: Insertion publicité dans tous les modes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un utilisateur gratuit écoute un audio-guide en mode <mode></p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la séquence 5 se termine (1 pub / 5 séquences)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publicité s'enchaîne automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> elle est skippable après 5 secondes</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>mode</th>
</tr>
</thead>
<tbody>
<tr>
<td>Voiture</td>
</tr>
<tr>
<td>Vélo</td>
</tr>
<tr>
<td>Transport</td>
</tr>
<tr>
<td>Piéton</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="23-gps-imprecis-en-foret-velo">23. GPS imprécis en forêt (vélo)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un cycliste dans une forêt dense
<span style="color: #9E9E9E"><strong>Et</strong></span> que la précision GPS est ±80m</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il approche d'un point GPS avec rayon 50m</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un avertissement s'affiche:</p>
<hr />
<h2 id="24-bus-devie-de-son-itineraire-transport">24. Bus dévié de son itinéraire (transport)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un bus touristique avec déviation
<span style="color: #9E9E9E"><strong>Et</strong></span> que plusieurs points GPS deviennent inaccessibles</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur est informé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message s'affiche:</p>
<hr />
<h2 id="25-changement-de-mode-en-cours-de-route">25. Changement de mode en cours de route</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide démarré en mode "Vélo"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur décide de continuer à pied
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il ouvre les paramètres</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il peut changer le mode vers "Piéton"
<span style="color: #9E9E9E"><strong>Et</strong></span> les rayons sont reconfigurés automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> une confirmation s'affiche:</p>
<hr />
<h2 id="26-detection-automatique-incoherente">26. Détection automatique incohérente</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur marche rapidement (7 km/h)
<span style="color: #9E9E9E"><strong>Et</strong></span> que le système détecte "Vélo" par erreur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la suggestion s'affiche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'utilisateur peut cliquer sur "Changer"
<span style="color: #9E9E9E"><strong>Et</strong></span> sélectionner manuellement "Piéton"</p>
<hr />
<h2 id="27-batterie-en-mode-velo-longue-distance">27. Batterie en mode vélo longue distance</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un circuit vélo de 50 km avec 20 séquences
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur roule pendant 3 heures</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la batterie atteint 15%</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une notification suggère:</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="audio-guides-premium-et-monetisation">Audio-guides Premium et monétisation</h1>
<blockquote>
<p><em>En tant que créateur</em>
<em>Je veux pouvoir proposer des audio-guides Premium</em>
<em>Afin de monétiser mon contenu de qualité</em></p>
</blockquote>
<p><strong>31 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'application RoadWave est démarrée
<span style="color: #9E9E9E"><strong>Et</strong></span> que le créateur "guide@example.com" est connecté et vérifié</p>
</blockquote>
<h2 id="1-creation-dun-audio-guide-premium">1. Création d'un audio-guide Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le créateur crée un audio-guide "Visite VIP Versailles"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il accède aux paramètres de monétisation (étape 4)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il peut choisir:</p>
<pre><code>| option | description |
|---|---|
| Gratuit | Accessible à tous (avec pubs) |
| Premium | Réservé abonnés Premium |
</code></pre>
<hr />
<h2 id="2-badge-premium-visible-sur-laudio-guide">2. Badge Premium visible sur l'audio-guide</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide configuré en Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il est affiché dans les résultats de recherche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un badge "👑 Premium" est visible
<span style="color: #9E9E9E"><strong>Et</strong></span> la cover image a un cadre doré subtil</p>
<hr />
<h2 id="3-preview-3-premieres-sequences-pour-utilisateurs-gratuits">3. Preview 3 premières séquences pour utilisateurs gratuits</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide Premium "Visite VIP Versailles" avec 15 séquences
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un utilisateur gratuit ouvre l'audio-guide</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il consulte la liste des séquences</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les séquences affichent:</p>
<pre><code>| séquence | état |
|---|---|
| 1 | ✅ Accessible (preview) |
| 2 | ✅ Accessible (preview) |
| 3 | ✅ Accessible (preview) |
| 4 | 🔒 Réservé Premium |
| 5-15 | 🔒 Réservé Premium |
</code></pre>
<hr />
<h2 id="4-ecoute-des-3-premieres-sequences-sans-blocage">4. Écoute des 3 premières séquences sans blocage</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un utilisateur gratuit
<span style="color: #9E9E9E"><strong>Et</strong></span> un audio-guide Premium avec preview</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il écoute les séquences 1, 2 et 3</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune publicité n'est insérée (preview = teasing)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'écoute est fluide</p>
<hr />
<h2 id="5-paywall-apres-la-3eme-sequence">5. Paywall après la 3ème séquence</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur gratuit termine la séquence 3</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la séquence se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un overlay paywall s'affiche immédiatement:</p>
<hr />
<h2 id="6-bouton-passer-premium-vers-tunnel-dabonnement">6. Bouton "Passer Premium" vers tunnel d'abonnement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'overlay paywall Premium est affiché</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur "Passer Premium"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il est redirigé vers la page d'abonnement Mangopay
<span style="color: #9E9E9E"><strong>Et</strong></span> l'audio-guide actuel est marqué en "pending" (reprise après souscription)</p>
<hr />
<h2 id="7-reprise-automatique-apres-souscription-premium">7. Reprise automatique après souscription Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur s'est abonné Premium depuis un paywall audio-guide</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'abonnement est activé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il est redirigé vers l'audio-guide automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> la séquence 4 démarre immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> un toast de bienvenue s'affiche: "✨ Bienvenue Premium ! Profitez de votre audio-guide"</p>
<hr />
<h2 id="8-utilisateur-premium-acces-complet-immediat">8. Utilisateur Premium - Accès complet immédiat</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur Premium ouvre un audio-guide Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il consulte la liste des séquences</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> toutes les 15 séquences sont accessibles
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun paywall ne s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune publicité n'est insérée</p>
<hr />
<h2 id="9-pas-de-preview-si-laudio-guide-a-3-sequences">9. Pas de preview si l'audio-guide a &lt;3 séquences</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide Premium avec seulement 2 séquences</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un utilisateur gratuit tente de l'ouvrir</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un paywall s'affiche immédiatement (avant lecture)
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune preview n'est disponible</p>
<hr />
<h2 id="10-remuneration-createur-pour-audio-guide-premium">10. Rémunération créateur pour audio-guide Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un créateur avec un audio-guide Premium
<span style="color: #9E9E9E"><strong>Et</strong></span> que 50 utilisateurs Premium ont écouté l'audio-guide ce mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la répartition des revenus est calculée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le créateur reçoit 70% des revenus proportionnels
<span style="color: #9E9E9E"><strong>Et</strong></span> la formule est: (Écoutes créateur / Total écoutes Premium) × 70% pool Premium</p>
<hr />
<h2 id="11-dashboard-revenus-par-audio-guide">11. Dashboard revenus par audio-guide</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur a 3 audio-guides Premium publiés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il consulte son dashboard revenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit pour chaque audio-guide:</p>
<pre><code>| 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 € |
</code></pre>
<hr />
<h2 id="12-comparaison-gratuit-vs-premium">12. Comparaison gratuit vs Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur a publié 2 audio-guides:</p>
<pre><code>| titre | type | ecoutes_mois | revenus |
|---|---|---|---|
| Tour de Paris | Gratuit | 1200 | 12.50 € |
| Visite VIP Versailles | Premium | 142 | 45.20 € |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il consulte son dashboard</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il peut comparer les performances
<span style="color: #9E9E9E"><strong>Et</strong></span> constater que Premium génère plus de revenus par écoute</p>
<hr />
<h2 id="13-seuil-minimum-de-paiement-20">13. Seuil minimum de paiement (20€)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur a généré 18€ de revenus ce mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le paiement mensuel est traité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le montant est reporté au mois suivant
<span style="color: #9E9E9E"><strong>Et</strong></span> un message s'affiche: "Seuil minimum non atteint (20€). Montant reporté."</p>
<hr />
<h2 id="14-paiement-automatique-mensuel">14. Paiement automatique mensuel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur a généré 138.50€ de revenus en janvier</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le 5 février arrive</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le paiement est initié automatiquement via Mangopay
<span style="color: #9E9E9E"><strong>Et</strong></span> le créateur reçoit une notification: "Paiement de 138.50€ en cours"
<span style="color: #9E9E9E"><strong>Et</strong></span> les fonds arrivent sous 2-3 jours ouvrés</p>
<hr />
<h2 id="15-insertion-publicite-toutes-les-5-sequences-gratuit">15. Insertion publicité toutes les 5 séquences (gratuit)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide gratuit avec 12 séquences
<span style="color: #9E9E9E"><strong>Et</strong></span> un utilisateur gratuit</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il termine la séquence 5</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une publicité démarre automatiquement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il termine la séquence 10</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une deuxième publicité démarre</p>
<hr />
<h2 id="16-publicite-apres-sequence-en-mode-pieton-avec-pause">16. Publicité après séquence en mode piéton (avec pause)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide piéton gratuit</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la séquence 5 se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publicité démarre automatiquement (pas d'attente bouton)
<span style="color: #9E9E9E"><strong>Et</strong></span> la pub est skippable après 5 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la publicité se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le player se met en pause
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur doit cliquer sur [▶|] pour continuer</p>
<hr />
<h2 id="17-publicite-en-mode-voiturevelotransport-automatique">17. Publicité en mode voiture/vélo/transport (automatique)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide voiture gratuit</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la séquence 5 se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publicité démarre automatiquement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la publicité se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 6 démarre automatiquement (pas de pause)</p>
<hr />
<h2 id="18-publicites-geolocalisees-dans-audio-guides">18. Publicités géolocalisées dans audio-guides</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide dans la région "Île-de-France"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> une publicité doit être insérée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'API publicitaire filtre par:</p>
<pre><code>| critère | valeur |
|---|---|
| Géolocalisation | Île-de-France |
| Catégorie | Tourisme, Culture |
| Langue | Français |
</code></pre>
<hr />
<h2 id="19-comptabilisation-des-impressions-pub-pour-createur">19. Comptabilisation des impressions pub pour créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide gratuit génère 200 écoutes complètes
<span style="color: #9E9E9E"><strong>Et</strong></span> que chaque écoute complète = 2 publicités (séq. 5 et 10)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les revenus pub sont calculés</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 400 impressions sont comptabilisées
<span style="color: #9E9E9E"><strong>Et</strong></span> le créateur reçoit 0.80€ (400 × 0.002€)</p>
<hr />
<h2 id="20-cta-premium-apres-audio-guide-gratuit-complete">20. CTA Premium après audio-guide gratuit complété</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur gratuit complète un audio-guide gratuit</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il termine la dernière séquence</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un overlay s'affiche:</p>
<hr />
<h2 id="21-recommandations-daudio-guides-premium-apres-gratuit">21. Recommandations d'audio-guides Premium après gratuit</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur termine un audio-guide gratuit "Tour de Paris"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'overlay de fin s'affiche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 3 audio-guides Premium similaires sont suggérés:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="22-badge-premium-recommande-sur-audio-guides-populaires">22. Badge "Premium recommandé" sur audio-guides populaires</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide Premium avec &gt;500 écoutes et note &gt;4.5/5</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il est affiché dans les résultats de recherche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un badge "⭐ Premium recommandé" est visible
<span style="color: #9E9E9E"><strong>Et</strong></span> il est mis en avant dans les résultats</p>
<hr />
<h2 id="23-conversion-tracking-pour-attribution-createur">23. Conversion tracking pour attribution créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur découvre Premium via un audio-guide créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il s'abonne</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la conversion est trackée:</p>
<pre><code>| donnée | valeur |
|---|---|
| source_conversion | audio_guide_paywall |
| audio_guide_id | visite_vip_versailles_123 |
| creator_id | guide_versailles_456 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> le créateur bénéficie d'un bonus de conversion</p>
<hr />
<h2 id="24-essai-gratuit-7-jours-premium-via-audio-guide">24. Essai gratuit 7 jours Premium via audio-guide</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur gratuit atteint le paywall d'un audio-guide Premium
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il n'a jamais essayé Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'overlay s'affiche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une offre d'essai est proposée:</p>
<hr />
<h2 id="25-activation-immediate-apres-essai-gratuit">25. Activation immédiate après essai gratuit</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur démarre un essai gratuit 7 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'essai est activé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'audio-guide Premium démarre immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes les séquences sont débloquées
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune publicité n'est insérée</p>
<hr />
<h2 id="26-rappel-2-jours-avant-fin-dessai">26. Rappel 2 jours avant fin d'essai</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a démarré un essai gratuit le 15/01</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le 20/01 arrive (J-2)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une notification est envoyée:</p>
<hr />
<h2 id="27-createur-mix-gratuit-premium">27. Créateur mix gratuit + Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur a publié 5 audio-guides:</p>
<pre><code>| titre | type |
|---|---|
| Découverte de Paris | Gratuit |
| Visite VIP Louvre | Premium |
| Balade Montmartre | Gratuit |
| Secrets Versailles | Premium |
| Visite express Orsay | Gratuit |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un utilisateur découvre son profil</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les audio-guides gratuits servent de teasing
<span style="color: #9E9E9E"><strong>Et</strong></span> les audio-guides Premium sont mis en avant avec badge</p>
<hr />
<h2 id="28-utilisateur-hesite-a-sabonner">28. Utilisateur hésite à s'abonner</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur atteint le paywall d'un audio-guide Premium
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il clique sur "Découvrir d'autres audio-guides gratuits"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il revient 2 jours plus tard sur le même audio-guide</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le paywall s'affiche à nouveau
<span style="color: #9E9E9E"><strong>Et</strong></span> une réduction temporaire est proposée: "Offre spéciale : -20% premier mois"</p>
<hr />
<h2 id="29-echec-du-paiement-premium-via-paywall">29. Échec du paiement Premium via paywall</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur tente de s'abonner Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le paiement Mangopay échoue</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message d'erreur s'affiche:</p>
<hr />
<h2 id="30-abonnement-premium-expire-pendant-ecoute">30. Abonnement Premium expiré pendant écoute</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur Premium écoute un audio-guide Premium
<span style="color: #9E9E9E"><strong>Et</strong></span> que son abonnement expire pendant l'écoute (séquence 8/15)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'expiration est détectée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'écoute continue jusqu'à la fin de la séquence en cours
<span style="color: #9E9E9E"><strong>Et</strong></span> un overlay s'affiche ensuite:</p>
<hr />
<h2 id="31-createur-change-audio-guide-de-gratuit-a-premium">31. Créateur change audio-guide de gratuit à Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide gratuit a 50 utilisateurs avec progression</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur le passe en Premium</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les utilisateurs ayant déjà commencé gardent l'accès complet
<span style="color: #9E9E9E"><strong>Et</strong></span> seuls les nouveaux utilisateurs sont soumis au paywall
<span style="color: #9E9E9E"><strong>Et</strong></span> un message de transparence s'affiche:</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="sauvegarde-et-reprise-de-progression-audio-guide">Sauvegarde et reprise de progression audio-guide</h1>
<blockquote>
<p><em>En tant qu'utilisateur</em>
<em>Je veux que ma progression soit sauvegardée automatiquement</em>
<em>Afin de pouvoir reprendre mon audio-guide là où je me suis arrêté</em></p>
</blockquote>
<p><strong>32 scénarios</strong> (31 standards, 1 plan)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'application RoadWave est démarrée
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur "jean@example.com" est connecté</p>
</blockquote>
<h2 id="1-sauvegarde-automatique-toutes-les-10-secondes">1. Sauvegarde automatique toutes les 10 secondes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide "Visite du Louvre" est en cours
<span style="color: #9E9E9E"><strong>Et</strong></span> que la séquence 3 est à la position 1:24</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> 10 secondes s'écoulent</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la progression est sauvegardée automatiquement:</p>
<pre><code>| 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] |
</code></pre>
<hr />
<h2 id="2-sauvegarde-locale-sqlite-pour-rapidite">2. Sauvegarde locale (SQLite) pour rapidité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une sauvegarde est déclenchée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la progression est enregistrée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les données sont écrites en SQLite local
<span style="color: #9E9E9E"><strong>Et</strong></span> l'écriture prend moins de 50ms
<span style="color: #9E9E9E"><strong>Et</strong></span> l'application reste fluide</p>
<hr />
<h2 id="3-synchronisation-cloud-en-arriere-plan">3. Synchronisation cloud en arrière-plan</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une sauvegarde locale est effectuée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> 30 secondes s'écoulent</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la progression est synchronisée vers PostgreSQL cloud
<span style="color: #9E9E9E"><strong>Et</strong></span> la synchronisation s'effectue en arrière-plan
<span style="color: #9E9E9E"><strong>Et</strong></span> elle n'impacte pas les performances</p>
<hr />
<h2 id="4-sauvegarde-immediate-lors-de-la-fermeture">4. Sauvegarde immédiate lors de la fermeture</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide est en cours à la séquence 4 position 2:15</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur ferme l'application</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la progression est sauvegardée immédiatement (local + cloud)
<span style="color: #9E9E9E"><strong>Et</strong></span> les données sont écrites avant la fermeture complète</p>
<hr />
<h2 id="5-sauvegarde-des-sequences-completees">5. Sauvegarde des séquences complétées</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide de 12 séquences est en cours
<span style="color: #9E9E9E"><strong>Et</strong></span> que les séquences 1, 2, 4, 5 ont été écoutées à &gt;80%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la progression est sauvegardée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les séquences complétées sont enregistrées:</p>
<hr />
<h2 id="6-historique-des-ecoutes-pour-statistiques">6. Historique des écoutes pour statistiques</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a écouté 3 séquences d'un audio-guide</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les données sont sauvegardées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'historique d'écoute inclut:</p>
<pre><code>| 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% |
</code></pre>
<hr />
<h2 id="7-popup-de-reprise-au-redemarrage">7. Popup de reprise au redémarrage</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur a quitté l'app à la séquence 6 position 2:34</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il rouvre l'audio-guide "Visite du Louvre"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une popup s'affiche:</p>
<hr />
<h2 id="8-action-reprendre-position-exacte-restauree">8. Action "Reprendre" - Position exacte restaurée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une popup de reprise est affichée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur "▶️ Reprendre"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la séquence 6 "Vénus de Milo" se charge
<span style="color: #9E9E9E"><strong>Et</strong></span> la position exacte 2:34 est restaurée
<span style="color: #9E9E9E"><strong>Et</strong></span> la lecture démarre automatiquement après 1 seconde</p>
<hr />
<h2 id="9-action-recommencer-reinitialisation-complete">9. Action "Recommencer" - Réinitialisation complète</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une popup de reprise est affichée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur "🔄 Recommencer"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'audio-guide redémarre depuis la séquence 1 position 0:00
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes les séquences sont marquées ⭕ "À écouter"
<span style="color: #9E9E9E"><strong>Et</strong></span> l'historique d'écoute est réinitialisé pour cette session</p>
<hr />
<h2 id="10-reprise-apres-7-jours-dinactivite">10. Reprise après 7 jours d'inactivité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a arrêté un audio-guide le 15/01/2026
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il le rouvre le 22/01/2026 (7 jours plus tard)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'audio-guide se charge</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la popup de reprise s'affiche normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes les données de progression sont conservées</p>
<hr />
<h2 id="11-reprise-sur-un-autre-appareil-synchronisation-cloud">11. Reprise sur un autre appareil (synchronisation cloud)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur écoute un audio-guide sur iPhone
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il quitte à la séquence 4 position 1:20</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il ouvre le même audio-guide sur iPad</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la popup de reprise s'affiche avec la progression iPhone
<span style="color: #9E9E9E"><strong>Et</strong></span> il peut reprendre exactement où il s'était arrêté</p>
<hr />
<h2 id="12-conflit-de-synchronisation-dernier-appareil-gagne">12. Conflit de synchronisation (dernier appareil gagne)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur écoute sur iPhone à la séquence 3
<span style="color: #9E9E9E"><strong>Et</strong></span> simultanément sur iPad à la séquence 7</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les deux appareils synchronisent</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la progression la plus récente (timestamp) est conservée
<span style="color: #9E9E9E"><strong>Et</strong></span> l'appareil avec ancienne progression affiche une notification:</p>
<hr />
<h2 id="13-mode-hors-ligne-sauvegarde-locale-uniquement">13. Mode hors-ligne - Sauvegarde locale uniquement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur écoute un audio-guide hors connexion
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il atteint la séquence 5</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la progression est sauvegardée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les données sont écrites localement (SQLite)
<span style="color: #9E9E9E"><strong>Et</strong></span> une icône "☁️ Non synchronisé" s'affiche discrètement</p>
<hr />
<h2 id="14-synchronisation-automatique-a-la-reconnexion">14. Synchronisation automatique à la reconnexion</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur a écouté hors ligne jusqu'à la séquence 8
<span style="color: #9E9E9E"><strong>Et</strong></span> que 5 progressions locales ne sont pas synchronisées</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la connexion réseau est rétablie</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les 5 progressions sont synchronisées automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> un toast s'affiche brièvement: "✅ Progression synchronisée"</p>
<hr />
<h2 id="15-suppression-de-la-progression-recommencer-proprement">15. Suppression de la progression (recommencer proprement)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur est à la séquence 10/12</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il ouvre les paramètres de l'audio-guide
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il clique sur "🔄 Réinitialiser progression"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une confirmation s'affiche:
<span style="color: #9E9E9E"><strong>Et</strong></span> si confirmé, la progression est effacée</p>
<hr />
<h2 id="16-taux-de-completion-global-de-laudio-guide">16. Taux de complétion global de l'audio-guide</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide de 12 séquences
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur a écouté complètement 8 séquences
<span style="color: #9E9E9E"><strong>Et</strong></span> partiellement 1 séquence (45%)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les statistiques sont calculées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le taux de complétion affiché est "67%" (8/12)</p>
<hr />
<h2 id="17-badge-audio-guide-complete-a-100">17. Badge "Audio-guide complété" à 100%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide de 12 séquences</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur écoute la 12ème séquence à 100%</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un badge "✅ Audio-guide complété" s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> une notification de félicitations est envoyée
<span style="color: #9E9E9E"><strong>Et</strong></span> le statut "Complété le 22/01/2026" est visible dans l'historique</p>
<hr />
<h2 id="18-temps-total-passe-sur-laudio-guide">18. Temps total passé sur l'audio-guide</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a écouté un audio-guide sur 2 sessions:</p>
<pre><code>| session | durée |
|---|---|
| 1 | 25 min |
| 2 | 18 min |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les statistiques sont calculées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le temps total est "43 minutes"
<span style="color: #9E9E9E"><strong>Et</strong></span> il est affiché dans l'historique personnel</p>
<hr />
<h2 id="19-liste-des-audio-guides-en-cours-dans-le-profil">19. Liste des audio-guides "En cours" dans le profil</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a 3 audio-guides en cours:</p>
<pre><code>| audio_guide | progression |
|---|---|
| Visite du Louvre | 6/12 |
| Safari du Paugre | 3/8 |
| Circuit Loire à Vélo | 12/15 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il consulte son profil "Audio-guides"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la section "📍 En cours" affiche les 3 audio-guides
<span style="color: #9E9E9E"><strong>Et</strong></span> chaque élément montre la progression sous forme de barre</p>
<hr />
<h2 id="20-liste-des-audio-guides-completes-dans-le-profil">20. Liste des audio-guides "Complétés" dans le profil</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a complété 2 audio-guides:</p>
<pre><code>| audio_guide | date_completion |
|---|---|
| Tour de Paris | 2026-01-15 |
| Découverte de Lyon | 2026-01-20 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il consulte son profil "Audio-guides"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la section "✅ Complétés" affiche les 2 audio-guides
<span style="color: #9E9E9E"><strong>Et</strong></span> la date de complétion est visible</p>
<hr />
<h2 id="21-badge-completiste-pour-10-audio-guides-completes">21. Badge "Complétiste" pour 10 audio-guides complétés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur complète son 10ème audio-guide</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la complétion est enregistrée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un badge "🏆 Complétiste" est débloqué
<span style="color: #9E9E9E"><strong>Et</strong></span> il apparaît sur son profil
<span style="color: #9E9E9E"><strong>Et</strong></span> une notification est envoyée:</p>
<hr />
<h2 id="22-plan-niveaux-de-badges-selon-nombre-daudio-guides-completes">22. 📋 Plan: Niveaux de badges selon nombre d'audio-guides complétés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur complète <nombre> audio-guides</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le badge est attribué</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il reçoit le badge "<badge>"</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>nombre</th>
<th>badge</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>🎧 Premier audio-guide</td>
</tr>
<tr>
<td>5</td>
<td>🗺️ Explorateur</td>
</tr>
<tr>
<td>10</td>
<td>🏆 Complétiste</td>
</tr>
<tr>
<td>25</td>
<td>🌟 Expert</td>
</tr>
<tr>
<td>50</td>
<td>💎 Maître audio-guideur</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="23-dashboard-createur-statistiques-par-audio-guide">23. Dashboard créateur - Statistiques par audio-guide</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur a publié l'audio-guide "Visite du Louvre"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il consulte son dashboard</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les métriques suivantes sont affichées:</p>
<pre><code>| métrique | valeur |
|---|---|
| Écoutes totales | 1542 |
| Écoutes complètes (&gt;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 |
</code></pre>
<hr />
<h2 id="24-graphique-de-completion-par-sequence">24. Graphique de complétion par séquence</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide de 12 séquences</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur consulte les statistiques détaillées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un graphique en barres affiche:</p>
<pre><code>| séquence | taux_completion |
|---|---|
| 1 | 100% |
| 2 | 95% |
| 3 | 89% |
| ... | ... |
| 12 | 58% |
</code></pre>
<hr />
<h2 id="25-detection-des-points-dabandon">25. Détection des points d'abandon</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide a un taux de complétion de 58%
<span style="color: #9E9E9E"><strong>Et</strong></span> que 35% des utilisateurs abandonnent à la séquence 7</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur consulte les insights</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un avertissement s'affiche:</p>
<hr />
<h2 id="26-heatmap-geographique-des-ecoutes">26. Heatmap géographique des écoutes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un audio-guide géolocalisé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur consulte la heatmap</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une carte affiche:</p>
<pre><code>| é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 |
</code></pre>
<hr />
<h2 id="27-temps-moyen-par-sequence">27. Temps moyen par séquence</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur analyse son audio-guide</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il consulte les statistiques temporelles</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit pour chaque séquence:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="28-notification-createur-pour-milestone">28. Notification créateur pour milestone</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide atteint 1000 écoutes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le seuil est franchi</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une notification est envoyée au créateur:</p>
<hr />
<h2 id="29-corruption-de-donnees-de-sauvegarde">29. Corruption de données de sauvegarde</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une sauvegarde locale (SQLite) est corrompue</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'application tente de charger la progression</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une récupération depuis le cloud est tentée
<span style="color: #9E9E9E"><strong>Et</strong></span> si réussie, les données cloud sont restaurées
<span style="color: #9E9E9E"><strong>Et</strong></span> la base locale est reconstruite</p>
<hr />
<h2 id="30-echec-de-synchronisation-cloud">30. Échec de synchronisation cloud</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API cloud est indisponible</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> une tentative de synchronisation est effectuée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'application continue avec sauvegarde locale uniquement
<span style="color: #9E9E9E"><strong>Et</strong></span> un retry automatique est programmé dans 5 minutes
<span style="color: #9E9E9E"><strong>Et</strong></span> l'icône "☁️ Non synchronisé" reste affichée</p>
<hr />
<h2 id="31-suppression-accidentelle-de-progression-recuperation">31. Suppression accidentelle de progression (récupération)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur réinitialise un audio-guide par erreur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il contacte le support dans les 7 jours</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'équipe peut restaurer la progression depuis les backups
<span style="color: #9E9E9E"><strong>Et</strong></span> les données sont récupérables (backup quotidien conservé 30 jours)</p>
<hr />
<h2 id="32-nettoyage-automatique-des-vieilles-progressions">32. Nettoyage automatique des vieilles progressions</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une progression n'a pas été mise à jour depuis 6 mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le nettoyage automatique s'exécute</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la progression est archivée (mais pas supprimée)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur peut la restaurer via l'historique</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="classification-des-contenus-par-age">Classification des contenus par âge</h1>
<blockquote>
<p><em>En tant que plateforme responsable</em>
<em>Je veux classifier les contenus par tranche d'âge</em>
<em>Afin de protéger les mineurs et respecter les obligations légales</em></p>
</blockquote>
<p><strong>13 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible</p>
</blockquote>
<h2 id="1-createur-doit-classifier-son-contenu-a-la-publication">1. Créateur doit classifier son contenu à la publication</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur connecté</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je crée un nouveau contenu audio</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je dois obligatoirement choisir une classification d'âge parmi:</p>
<pre><code>| classification | description |
|---|---|
| Tout public | Contenu adapté à tous les âges |
| 13+ | Contenu mature léger |
| 16+ | Contenu mature |
| 18+ | Contenu adulte |
</code></pre>
<hr />
<h2 id="2-publication-impossible-sans-classification">2. Publication impossible sans classification</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée un contenu audio</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de publier sans sélectionner de classification</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publication échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Vous devez sélectionner une classification d'âge"</p>
<hr />
<h2 id="3-utilisateur-13-15-ans-voit-uniquement-du-contenu-tout-public">3. Utilisateur 13-15 ans voit uniquement du contenu "Tout public"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur de 14 ans
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il existe des contenus avec les classifications suivantes:</p>
<pre><code>| classification | nombre |
|---|---|
| Tout public | 20 |
| 13+ | 15 |
| 16+ | 10 |
| 18+ | 5 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je demande des recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois uniquement les 20 contenus "Tout public"
<span style="color: #9E9E9E"><strong>Et</strong></span> les autres contenus ne sont jamais proposés</p>
<hr />
<h2 id="4-utilisateur-16-17-ans-voit-tout-public-et-13">4. Utilisateur 16-17 ans voit "Tout public" et "13+"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur de 17 ans
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il existe des contenus avec les classifications suivantes:</p>
<pre><code>| classification | nombre |
|---|---|
| Tout public | 20 |
| 13+ | 15 |
| 16+ | 10 |
| 18+ | 5 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je demande des recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois 35 contenus (Tout public + 13+)
<span style="color: #9E9E9E"><strong>Et</strong></span> les contenus 16+ et 18+ ne sont pas proposés</p>
<hr />
<h2 id="5-utilisateur-18-voit-tous-les-contenus">5. Utilisateur 18+ voit tous les contenus</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur de 25 ans
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il existe des contenus avec toutes les classifications</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je demande des recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois tous les contenus sans restriction
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun filtre d'âge n'est appliqué</p>
<hr />
<h2 id="6-mode-kids-active-automatiquement-pour-les-moins-de-13-ans">6. Mode Kids activé automatiquement pour les moins de 13 ans</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je m'inscris avec une date de naissance "2013-01-21"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le mode Kids est activé automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois uniquement du contenu "Tout public"
<span style="color: #9E9E9E"><strong>Et</strong></span> des protections supplémentaires sont appliquées</p>
<hr />
<h2 id="7-moderateur-reclassifie-un-contenu-mal-categorise">7. Modérateur reclassifie un contenu mal catégorisé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu est publié avec la classification "Tout public"
<span style="color: #9E9E9E"><strong>Et</strong></span> que ce contenu contient du langage inapproprié détecté en modération</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur reclassifie ce contenu en "16+"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la nouvelle classification est appliquée immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu n'est plus visible pour les utilisateurs de moins de 16 ans
<span style="color: #9E9E9E"><strong>Et</strong></span> le créateur reçoit une notification de reclassification</p>
<hr />
<h2 id="8-strike-si-classification-volontairement-incorrecte">8. Strike si classification volontairement incorrecte</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur a publié un contenu "18+" classifié comme "Tout public"
<span style="color: #9E9E9E"><strong>Et</strong></span> que ce contenu a été signalé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur confirme la mauvaise classification volontaire</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le créateur reçoit 1 strike
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu est reclassifié en "18+"
<span style="color: #9E9E9E"><strong>Et</strong></span> le créateur reçoit une notification explicative</p>
<hr />
<h2 id="9-createur-peut-voir-la-distribution-dage-de-son-audience">9. Créateur peut voir la distribution d'âge de son audience</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai publié des contenus avec différentes classifications</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mes statistiques</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois la répartition des âges de mes auditeurs:</p>
<pre><code>| tranche_age | pourcentage |
|---|---|
| 13-15 ans | 15% |
| 16-17 ans | 20% |
| 18+ ans | 65% |
</code></pre>
<hr />
<h2 id="10-recherche-filtree-par-classification-dage">10. Recherche filtrée par classification d'âge</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur de 16 ans</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je recherche des contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les résultats incluent uniquement:</p>
<pre><code>| classification |
|---|
| Tout public |
| 13+ |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je ne vois pas les contenus 16+ et 18+ dans les résultats</p>
<hr />
<h2 id="11-notification-si-tentative-dacces-a-contenu-non-autorise">11. Notification si tentative d'accès à contenu non autorisé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur de 14 ans
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un contenu "16+" est partagé avec moi via un lien direct</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie d'accéder au contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'accès est refusé
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Ce contenu est réservé aux utilisateurs de 16 ans et plus"</p>
<hr />
<h2 id="12-validation-obligatoire-des-3-premiers-contenus-inclut-la-classification">12. Validation obligatoire des 3 premiers contenus inclut la classification</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un nouveau créateur
<span style="color: #9E9E9E"><strong>Et</strong></span> que je publie mon premier contenu classifié "18+"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur valide mon contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il vérifie que la classification "18+" est appropriée
<span style="color: #9E9E9E"><strong>Et</strong></span> peut la modifier si nécessaire avant validation</p>
<hr />
<h2 id="13-statistiques-de-classification-dans-linterface-createur">13. Statistiques de classification dans l'interface créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mes contenus publiés</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois pour chaque contenu:</p>
<pre><code>| information | exemple |
|---|---|
| Classification actuelle | 13+ |
| Nombre de signalements | 2 |
| Reclassifications | Aucune / 1× par modérateur |
</code></pre>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="connexion-utilisateur">Connexion utilisateur</h1>
<blockquote>
<p><em>En tant qu'utilisateur existant</em>
<em>Je veux me connecter à mon compte</em>
<em>Afin d'accéder à mes contenus et paramètres</em></p>
</blockquote>
<p><strong>11 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un utilisateur existe avec:
| email | mot_de_passe |
|---|---|
| user@test.fr | Password123 |</p>
</blockquote>
<h2 id="1-connexion-reussie-avec-identifiants-valides">1. Connexion réussie avec identifiants valides</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me connecte avec:</p>
<pre><code>| email | mot_de_passe |
|---|---|
| user@test.fr | Password123 |
</code></pre>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je suis connecté avec succès
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un access token valide pour 15 minutes
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un refresh token valide pour 30 jours</p>
<hr />
<h2 id="2-connexion-echouee-avec-email-inexistant">2. Connexion échouée avec email inexistant</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me connecte avec l'email "inexistant@test.fr"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la connexion échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Email ou mot de passe incorrect"</p>
<hr />
<h2 id="3-connexion-echouee-avec-mot-de-passe-incorrect">3. Connexion échouée avec mot de passe incorrect</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me connecte avec:</p>
<pre><code>| email | mot_de_passe |
|---|---|
| user@test.fr | MauvaisPass1 |
</code></pre>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la connexion échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Email ou mot de passe incorrect"</p>
<hr />
<h2 id="4-blocage-apres-5-tentatives-echouees">4. Blocage après 5 tentatives échouées</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai échoué 4 tentatives de connexion</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'échoue une 5ème tentative de connexion</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon compte est temporairement bloqué
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Compte bloqué pour 15 minutes après 5 tentatives échouées"
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un email de notification de blocage</p>
<hr />
<h2 id="5-tentative-de-connexion-pendant-le-blocage">5. Tentative de connexion pendant le blocage</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon compte est bloqué suite à 5 tentatives échouées
<span style="color: #9E9E9E"><strong>Et</strong></span> que seulement 5 minutes se sont écoulées</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de me connecter avec les bons identifiants</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la connexion échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Compte bloqué. Réessayez dans 10 minutes"</p>
<hr />
<h2 id="6-deblocage-automatique-apres-15-minutes">6. Déblocage automatique après 15 minutes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon compte est bloqué suite à 5 tentatives échouées
<span style="color: #9E9E9E"><strong>Et</strong></span> que 15 minutes se sont écoulées</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me connecte avec les bons identifiants</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je suis connecté avec succès
<span style="color: #9E9E9E"><strong>Et</strong></span> le compteur de tentatives est réinitialisé</p>
<hr />
<h2 id="7-reset-du-compteur-apres-connexion-reussie">7. Reset du compteur après connexion réussie</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai échoué 3 tentatives de connexion</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me connecte avec les bons identifiants</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je suis connecté avec succès
<span style="color: #9E9E9E"><strong>Et</strong></span> le compteur de tentatives est remis à 0</p>
<hr />
<h2 id="8-reset-automatique-du-compteur-apres-15-minutes-sans-blocage">8. Reset automatique du compteur après 15 minutes sans blocage</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai échoué 3 tentatives de connexion
<span style="color: #9E9E9E"><strong>Et</strong></span> que 15 minutes se sont écoulées sans nouvelle tentative</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mon compteur de tentatives</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le compteur est réinitialisé à 0</p>
<hr />
<h2 id="9-deblocage-via-lien-mot-de-passe-oublie">9. Déblocage via lien "Mot de passe oublié"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon compte est bloqué suite à 5 tentatives échouées</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'utilise la fonction "Mot de passe oublié"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je réinitialise mon mot de passe</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le blocage est levé immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux me connecter avec le nouveau mot de passe</p>
<hr />
<h2 id="10-email-de-notification-lors-dun-blocage">10. Email de notification lors d'un blocage</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai échoué 5 tentatives de connexion</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email avec:</p>
<pre><code>| sujet | Tentatives de connexion suspectes détectées |
|---|---|
| contenu_contient | Votre compte a été temporairement bloqué |
| lien_mot_de_passe | présent |
</code></pre>
<hr />
<h2 id="11-connexion-multi-device-simultanee-autorisee">11. Connexion multi-device simultanée autorisée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis connecté sur un appareil iOS</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me connecte également sur un appareil Android</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les deux sessions sont actives simultanément
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux utiliser l'application sur les deux appareils</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="inscription-utilisateur">Inscription utilisateur</h1>
<blockquote>
<p><em>En tant que nouvel utilisateur</em>
<em>Je veux créer un compte avec email et mot de passe</em>
<em>Afin d'accéder à l'application RoadWave</em></p>
</blockquote>
<p><strong>15 scénarios</strong> (14 standards, 1 plan)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> que Zitadel est configuré</p>
</blockquote>
<h2 id="1-inscription-reussie-avec-donnees-valides">1. Inscription réussie avec données valides</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'email "nouveau@example.com" n'existe pas</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je m'inscris avec les données suivantes:</p>
<pre><code>| champ | valeur |
|---|---|
| email | nouveau@example.com |
| mot_de_passe | Password123 |
| pseudo | nouveau_user |
| date_naissance | 1995-06-15 |
</code></pre>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon compte est créé avec succès
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un email de vérification
<span style="color: #9E9E9E"><strong>Et</strong></span> le lien de vérification expire dans 7 jours
<span style="color: #9E9E9E"><strong>Et</strong></span> je suis redirigé vers l'application</p>
<hr />
<h2 id="2-inscription-avec-email-deja-existant">2. Inscription avec email déjà existant</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur existe avec l'email "existant@example.com"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je m'inscris avec l'email "existant@example.com"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'inscription échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Cet email est déjà utilisé"</p>
<hr />
<h2 id="3-inscription-avec-mot-de-passe-invalide-trop-court">3. Inscription avec mot de passe invalide - trop court</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je m'inscris avec un mot de passe de moins de 8 caractères "Pass1"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'inscription échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Le mot de passe doit contenir au moins 8 caractères"</p>
<hr />
<h2 id="4-inscription-avec-mot-de-passe-invalide-sans-majuscule">4. Inscription avec mot de passe invalide - sans majuscule</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je m'inscris avec un mot de passe sans majuscule "password123"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'inscription échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Le mot de passe doit contenir au moins une majuscule"</p>
<hr />
<h2 id="5-inscription-avec-mot-de-passe-invalide-sans-chiffre">5. Inscription avec mot de passe invalide - sans chiffre</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je m'inscris avec un mot de passe sans chiffre "Password"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'inscription échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Le mot de passe doit contenir au moins un chiffre"</p>
<hr />
<h2 id="6-inscription-avec-pseudo-invalide-trop-court">6. Inscription avec pseudo invalide - trop court</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je m'inscris avec un pseudo de 2 caractères "ab"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'inscription échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Le pseudo doit contenir entre 3 et 30 caractères"</p>
<hr />
<h2 id="7-inscription-avec-pseudo-invalide-caracteres-speciaux">7. Inscription avec pseudo invalide - caractères spéciaux</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je m'inscris avec un pseudo contenant des caractères spéciaux "user@123"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'inscription échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Le pseudo ne peut contenir que des lettres, chiffres et underscores"</p>
<hr />
<h2 id="8-inscription-avec-email-invalide">8. Inscription avec email invalide</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je m'inscris avec un email invalide "email.invalide"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'inscription échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Format d'email invalide"</p>
<hr />
<h2 id="9-plan-inscription-avec-age-minimum-non-respecte">9. 📋 Plan: Inscription avec âge minimum non respecté</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> la date du jour est "2026-01-21"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je m'inscris avec une date de naissance "<date_naissance>"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'inscription échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Vous devez avoir au moins 13 ans pour créer un compte"</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>date_naissance</th>
<th>age</th>
</tr>
</thead>
<tbody>
<tr>
<td>2013-01-22</td>
<td>12</td>
</tr>
<tr>
<td>2015-06-15</td>
<td>10</td>
</tr>
<tr>
<td>2020-01-01</td>
<td>6</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="10-inscription-avec-age-limite-acceptable-13-ans">10. Inscription avec âge limite acceptable (13 ans)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> la date du jour est "2026-01-21"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je m'inscris avec une date de naissance "2013-01-21"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon compte est créé avec succès
<span style="color: #9E9E9E"><strong>Et</strong></span> le mode Kids est activé automatiquement</p>
<hr />
<h2 id="11-inscription-avec-age-superieur-a-18-ans">11. Inscription avec âge supérieur à 18 ans</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> la date du jour est "2026-01-21"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je m'inscris avec une date de naissance "1990-06-15"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon compte est créé avec succès
<span style="color: #9E9E9E"><strong>Et</strong></span> j'ai accès à tous les contenus sans restriction d'âge</p>
<hr />
<h2 id="12-donnees-minimales-requises-a-linscription">12. Données minimales requises à l'inscription</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je m'inscris sans fournir de nom complet
<span style="color: #9E9E9E"><strong>Et</strong></span> sans fournir de photo de profil
<span style="color: #9E9E9E"><strong>Et</strong></span> sans fournir de bio</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon compte est créé avec succès
<span style="color: #9E9E9E"><strong>Et</strong></span> un avatar par défaut est généré
<span style="color: #9E9E9E"><strong>Et</strong></span> les champs optionnels sont vides</p>
<hr />
<h2 id="13-renvoyer-lemail-de-verification">13. Renvoyer l'email de vérification</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je me suis inscrit avec l'email "nouveau@example.com"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je n'ai pas vérifié mon email</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je demande à renvoyer l'email de vérification</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un nouvel email de vérification est envoyé
<span style="color: #9E9E9E"><strong>Et</strong></span> le précédent lien est invalidé</p>
<hr />
<h2 id="14-limite-de-renvoi-demail-de-verification">14. Limite de renvoi d'email de vérification</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je me suis inscrit avec l'email "nouveau@example.com"
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai déjà renvoyé l'email de vérification 3 fois aujourd'hui</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je demande à renvoyer l'email de vérification une 4ème fois</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la demande échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Vous avez atteint la limite de 3 renvois par jour"</p>
<hr />
<h2 id="15-expiration-du-lien-de-verification">15. Expiration du lien de vérification</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je me suis inscrit il y a 8 jours
<span style="color: #9E9E9E"><strong>Et</strong></span> que je n'ai pas vérifié mon email</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie d'utiliser le lien de vérification</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la vérification échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Ce lien a expiré"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux demander un nouveau lien</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="recuperation-de-compte">Récupération de compte</h1>
<blockquote>
<p><em>En tant qu'utilisateur ayant oublié son mot de passe</em>
<em>Je veux pouvoir réinitialiser mon mot de passe via email</em>
<em>Afin de récupérer l'accès à mon compte</em></p>
</blockquote>
<p><strong>14 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un utilisateur existe avec l'email "user@test.fr"</p>
</blockquote>
<h2 id="1-demander-la-reinitialisation-du-mot-de-passe">1. Demander la réinitialisation du mot de passe</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Mot de passe oublié"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je saisis mon email "user@test.fr"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email avec un lien de réinitialisation
<span style="color: #9E9E9E"><strong>Et</strong></span> le lien expire dans 1 heure
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Email de réinitialisation envoyé"</p>
<hr />
<h2 id="2-email-inexistant-lors-de-la-demande-de-reinitialisation">2. Email inexistant lors de la demande de réinitialisation</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je demande une réinitialisation pour l'email "inexistant@test.fr"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois le même message "Email de réinitialisation envoyé"
<span style="color: #F44336"><strong>Mais</strong></span> aucun email n'est envoyé (sécurité - pas d'énumération d'emails)</p>
<hr />
<h2 id="3-reinitialiser-le-mot-de-passe-avec-un-lien-valide">3. Réinitialiser le mot de passe avec un lien valide</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai demandé une réinitialisation de mot de passe
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai reçu le lien de réinitialisation</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur le lien
<span style="color: #9E9E9E"><strong>Et</strong></span> que je saisis un nouveau mot de passe "NouveauPass123"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je confirme le nouveau mot de passe "NouveauPass123"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon mot de passe est modifié avec succès
<span style="color: #9E9E9E"><strong>Et</strong></span> je suis déconnecté de tous mes appareils sauf celui en cours
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un email de confirmation de changement</p>
<hr />
<h2 id="4-lien-de-reinitialisation-expire">4. Lien de réinitialisation expiré</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai demandé une réinitialisation il y a 2 heures</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie d'utiliser le lien</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois le message "Ce lien a expiré"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux demander un nouveau lien</p>
<hr />
<h2 id="5-nouveau-mot-de-passe-ne-respecte-pas-les-regles">5. Nouveau mot de passe ne respecte pas les règles</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai un lien de réinitialisation valide</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je saisis un nouveau mot de passe "faible"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la réinitialisation échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Le mot de passe doit contenir au moins 8 caractères, 1 majuscule et 1 chiffre"</p>
<hr />
<h2 id="6-confirmation-du-mot-de-passe-ne-correspond-pas">6. Confirmation du mot de passe ne correspond pas</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai un lien de réinitialisation valide</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je saisis un nouveau mot de passe "NouveauPass123"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je confirme avec un mot de passe différent "AutrePass123"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la réinitialisation échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Les mots de passe ne correspondent pas"</p>
<hr />
<h2 id="7-limite-de-demandes-de-reinitialisation">7. Limite de demandes de réinitialisation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai déjà demandé 3 réinitialisations dans la dernière heure</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je demande une 4ème réinitialisation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la demande échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Maximum 3 demandes par heure. Réessayez plus tard."</p>
<hr />
<h2 id="8-compteur-de-demandes-se-reinitialise-apres-1-heure">8. Compteur de demandes se réinitialise après 1 heure</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai demandé 3 réinitialisations
<span style="color: #9E9E9E"><strong>Et</strong></span> que 1 heure s'est écoulée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je demande une nouvelle réinitialisation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la demande réussit
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un email avec un nouveau lien</p>
<hr />
<h2 id="9-email-de-notification-de-changement-de-mot-de-passe">9. Email de notification de changement de mot de passe</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je viens de réinitialiser mon mot de passe</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email de confirmation avec:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="10-notification-push-si-changement-depuis-appareil-non-reconnu">10. Notification push si changement depuis appareil non reconnu</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je me suis toujours connecté depuis mon iPhone
<span style="color: #9E9E9E"><strong>Et</strong></span> que je réinitialise mon mot de passe depuis un PC Windows</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification push sur mon iPhone avec:</p>
<pre><code>| titre | Mot de passe modifié |
|---|---|
| message | Depuis Windows - Paris, France |
| action | Sécuriser le compte si ce n'est pas vous |
</code></pre>
<hr />
<h2 id="11-deconnexion-de-tous-les-appareils-apres-reinitialisation">11. Déconnexion de tous les appareils après réinitialisation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis connecté sur 4 appareils différents
<span style="color: #9E9E9E"><strong>Et</strong></span> que je réinitialise mon mot de passe depuis un navigateur web</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les 3 autres appareils sont déconnectés immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> seule la session du navigateur web reste active
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Vous avez été déconnecté des autres appareils par sécurité"</p>
<hr />
<h2 id="12-lien-de-reinitialisation-invalide-si-deja-utilise">12. Lien de réinitialisation invalide si déjà utilisé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai réinitialisé mon mot de passe avec un lien</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de réutiliser le même lien</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois le message "Ce lien a déjà été utilisé"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux demander un nouveau lien si nécessaire</p>
<hr />
<h2 id="13-nouveau-lien-invalide-lancien">13. Nouveau lien invalide l'ancien</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai demandé une réinitialisation et reçu un lien</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je demande une nouvelle réinitialisation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'ancien lien est invalidé
<span style="color: #9E9E9E"><strong>Et</strong></span> seul le nouveau lien fonctionne</p>
<hr />
<h2 id="14-reinitialisation-debloque-un-compte-bloque">14. Réinitialisation débloque un compte bloqué</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon compte est bloqué après 5 tentatives de connexion</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je réinitialise mon mot de passe via email</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le blocage est levé immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux me connecter avec le nouveau mot de passe
<span style="color: #9E9E9E"><strong>Et</strong></span> le compteur de tentatives est remis à 0</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="gestion-des-sessions-et-tokens">Gestion des sessions et tokens</h1>
<blockquote>
<p><em>En tant qu'utilisateur connecté</em>
<em>Je veux que mes sessions soient sécurisées et gérées automatiquement</em>
<em>Afin de maintenir l'accès à l'application sans friction</em></p>
</blockquote>
<p><strong>13 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis connecté avec succès</p>
</blockquote>
<h2 id="1-access-token-expire-apres-15-minutes">1. Access token expire après 15 minutes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai reçu un access token
<span style="color: #9E9E9E"><strong>Et</strong></span> que 15 minutes se sont écoulées</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je fais une requête API avec cet access token</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la requête échoue avec le code 401
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Token expiré"</p>
<hr />
<h2 id="2-refresh-automatique-du-token-avec-refresh-token">2. Refresh automatique du token avec refresh token</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon access token a expiré
<span style="color: #9E9E9E"><strong>Et</strong></span> que mon refresh token est valide</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'application demande un nouveau access token</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un nouvel access token valide pour 15 minutes
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un nouveau refresh token (rotation)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'ancien refresh token est invalidé</p>
<hr />
<h2 id="3-refresh-token-expire-apres-30-jours-dinactivite">3. Refresh token expire après 30 jours d'inactivité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je me suis connecté il y a 30 jours
<span style="color: #9E9E9E"><strong>Et</strong></span> que je n'ai pas utilisé l'application depuis</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie d'utiliser mon refresh token</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la requête échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois me reconnecter avec email/password</p>
<hr />
<h2 id="4-prolongation-automatique-de-la-session-si-lapp-est-utilisee">4. Prolongation automatique de la session si l'app est utilisée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je me suis connecté il y a 25 jours
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'utilise l'application régulièrement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je fais une requête API</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma session est automatiquement prolongée
<span style="color: #9E9E9E"><strong>Et</strong></span> mon refresh token reste valide</p>
<hr />
<h2 id="5-detection-de-token-replay-attack">5. Détection de token replay attack</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai rafraîchi mon token
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai reçu un nouveau refresh token</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de réutiliser l'ancien refresh token</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la requête échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Token invalide ou révoqué"
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes mes sessions sont révoquées par sécurité</p>
<hr />
<h2 id="6-voir-la-liste-des-appareils-connectes">6. Voir la liste des appareils connectés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis connecté sur 3 appareils différents</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte la liste de mes appareils connectés</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois 3 appareils avec les informations suivantes:</p>
<pre><code>| information | exemple |
|---|---|
| OS | iOS 17.1 |
| Navigateur | Safari |
| Dernière connexion | Il y a 2 heures |
| Localisation | Paris, France (IP visible) |
</code></pre>
<hr />
<h2 id="7-revoquer-un-appareil-specifique">7. Révoquer un appareil spécifique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis connecté sur mon iPhone et mon iPad</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je révoque la session de mon iPad depuis les paramètres</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la session iPad est immédiatement déconnectée
<span style="color: #9E9E9E"><strong>Et</strong></span> ma session iPhone reste active</p>
<hr />
<h2 id="8-deconnecter-tous-les-appareils-sauf-celui-en-cours">8. Déconnecter tous les appareils sauf celui en cours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis connecté sur 4 appareils</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Déconnecter tous les appareils"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les 3 autres appareils sont déconnectés
<span style="color: #9E9E9E"><strong>Et</strong></span> seul l'appareil actuel reste connecté</p>
<hr />
<h2 id="9-alerte-de-connexion-depuis-nouveau-device">9. Alerte de connexion depuis nouveau device</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je me suis toujours connecté depuis Paris</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me connecte depuis un nouvel appareil à Lyon</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification push sur mes autres appareils
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un email avec:</p>
<pre><code>| sujet | Nouvelle connexion détectée |
|---|---|
| localisation | Lyon, France |
| appareil | Android 14 - Chrome |
| action | Lien pour révoquer la session |
</code></pre>
<hr />
<h2 id="10-alerte-de-connexion-suspecte-depuis-pays-different">10. Alerte de connexion suspecte depuis pays différent</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je me suis toujours connecté depuis la France</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me connecte depuis un appareil aux États-Unis</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification push immédiate
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un email d'alerte de sécurité
<span style="color: #9E9E9E"><strong>Et</strong></span> la nouvelle session nécessite une validation 2FA même si désactivée</p>
<hr />
<h2 id="11-deconnexion-apres-30-jours-dinactivite-totale">11. Déconnexion après 30 jours d'inactivité totale</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je ne me suis pas connecté depuis 30 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ouvre l'application</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je suis automatiquement déconnecté
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois me reconnecter avec email/password
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Session expirée après 30 jours d'inactivité"</p>
<hr />
<h2 id="12-sessions-multiples-simultanees-autorisees">12. Sessions multiples simultanées autorisées</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis connecté sur:</p>
<pre><code>| appareil |
|---|
| iPhone |
| iPad |
| PC Windows (Web) |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je fais des actions sur les 3 appareils simultanément</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> toutes les sessions fonctionnent sans conflit
<span style="color: #9E9E9E"><strong>Et</strong></span> chaque appareil maintient sa propre session</p>
<hr />
<h2 id="13-validation-de-jwt-via-zitadel">13. Validation de JWT via Zitadel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai reçu un access token JWT</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'API RoadWave valide le token</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la validation est faite localement avec la clé publique Zitadel
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune requête externe n'est effectuée (performance)
<span style="color: #9E9E9E"><strong>Et</strong></span> le token contient les claims suivants:</p>
<pre><code>| claim | valeur_exemple |
|---|---|
| sub | user-id-123 |
| email | user@test.fr |
| exp | timestamp + 15 minutes |
| iss | zitadel.roadwave.com |
</code></pre>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="authentification-a-deux-facteurs-2fa">Authentification à deux facteurs (2FA)</h1>
<blockquote>
<p><em>En tant qu'utilisateur soucieux de sécurité</em>
<em>Je veux activer la 2FA sur mon compte</em>
<em>Afin de protéger mon accès même si mon mot de passe est compromis</em></p>
</blockquote>
<p><strong>16 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis connecté à mon compte</p>
</blockquote>
<h2 id="1-activer-la-2fa-totp-time-based-one-time-password">1. Activer la 2FA TOTP (Time-based One-Time Password)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la 2FA n'est pas activée sur mon compte</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je choisis d'activer la 2FA TOTP</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois un QR code à scanner
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le secret partagé en texte clair (backup)
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois entrer un code de vérification depuis mon app authenticator</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je saisis un code TOTP valide</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la 2FA TOTP est activée avec succès
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois des codes de backup (10 codes)</p>
<hr />
<h2 id="2-connexion-avec-2fa-totp-activee">2. Connexion avec 2FA TOTP activée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la 2FA TOTP est activée sur mon compte</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me connecte avec email/password</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je suis redirigé vers la page de saisie du code 2FA</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je saisis un code TOTP valide de mon authenticator</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je suis connecté avec succès</p>
<hr />
<h2 id="3-connexion-echouee-avec-code-totp-invalide">3. Connexion échouée avec code TOTP invalide</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la 2FA TOTP est activée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me connecte avec email/password
<span style="color: #9E9E9E"><strong>Et</strong></span> que je saisis un code TOTP invalide "000000"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la connexion échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Code d'authentification invalide"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux réessayer</p>
<hr />
<h2 id="4-utiliser-un-code-de-backup-pour-2fa">4. Utiliser un code de backup pour 2FA</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la 2FA TOTP est activée
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai perdu l'accès à mon authenticator</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me connecte avec email/password
<span style="color: #9E9E9E"><strong>Et</strong></span> que je clique sur "Utiliser un code de backup"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je saisis un code de backup valide</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je suis connecté avec succès
<span style="color: #9E9E9E"><strong>Et</strong></span> le code de backup utilisé est invalidé
<span style="color: #9E9E9E"><strong>Et</strong></span> il me reste 9 codes de backup</p>
<hr />
<h2 id="5-activer-la-2fa-par-email">5. Activer la 2FA par email</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la 2FA n'est pas activée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je choisis d'activer la 2FA par email</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la 2FA email est activée immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "2FA email activée. Vous recevrez un code à chaque connexion"</p>
<hr />
<h2 id="6-connexion-avec-2fa-email">6. Connexion avec 2FA email</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la 2FA email est activée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me connecte avec email/password</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email avec un code à 6 chiffres
<span style="color: #9E9E9E"><strong>Et</strong></span> le code expire dans 10 minutes
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois saisir ce code pour terminer la connexion</p>
<hr />
<h2 id="7-code-2fa-email-expire">7. Code 2FA email expiré</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la 2FA email est activée
<span style="color: #9E9E9E"><strong>Et</strong></span> que je me suis connecté avec email/password
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai reçu un code 2FA par email il y a 11 minutes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je saisis ce code</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la connexion échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Code expiré. Demandez un nouveau code."</p>
<hr />
<h2 id="8-renvoyer-le-code-2fa-email">8. Renvoyer le code 2FA email</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la 2FA email est activée
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis sur la page de saisie du code 2FA</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Renvoyer le code"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un nouveau code par email
<span style="color: #9E9E9E"><strong>Et</strong></span> l'ancien code est invalidé</p>
<hr />
<h2 id="9-ajouter-un-appareil-de-confiance-skip-2fa-pendant-30-jours">9. Ajouter un appareil de confiance (skip 2FA pendant 30 jours)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la 2FA TOTP est activée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me connecte avec email/password et code TOTP
<span style="color: #9E9E9E"><strong>Et</strong></span> que je coche "Ne plus demander sur cet appareil"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je suis connecté avec succès
<span style="color: #9E9E9E"><strong>Et</strong></span> cet appareil est enregistré comme "appareil de confiance"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me reconnecte dans les 30 jours suivants sur ce même appareil</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je ne dois pas saisir de code 2FA</p>
<hr />
<h2 id="10-appareil-de-confiance-expire-apres-30-jours">10. Appareil de confiance expire après 30 jours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai enregistré un appareil de confiance il y a 31 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me connecte depuis cet appareil</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je dois saisir un code 2FA
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Appareil de confiance expiré. Veuillez vous authentifier"</p>
<hr />
<h2 id="11-voir-la-liste-des-appareils-de-confiance">11. Voir la liste des appareils de confiance</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai enregistré 3 appareils de confiance</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mes paramètres de sécurité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois la liste de mes 3 appareils de confiance avec:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="12-revoquer-un-appareil-de-confiance">12. Révoquer un appareil de confiance</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai un iPhone enregistré comme appareil de confiance</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je révoque cet appareil depuis les paramètres</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'appareil est supprimé de la liste</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me reconnecte depuis cet iPhone</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je dois saisir un code 2FA</p>
<hr />
<h2 id="13-revoquer-tous-les-appareils-de-confiance">13. Révoquer tous les appareils de confiance</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai 5 appareils de confiance enregistrés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Révoquer tous les appareils de confiance"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les appareils sont révoqués
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Tous les appareils de confiance ont été révoqués"</p>
<hr />
<h2 id="14-2fa-forcee-pour-connexion-suspecte-malgre-appareil-de-confiance">14. 2FA forcée pour connexion suspecte malgré appareil de confiance</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai un appareil de confiance enregistré en France
<span style="color: #9E9E9E"><strong>Et</strong></span> que je me connecte depuis ce même appareil mais avec une IP américaine</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je tente de me connecter</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la 2FA est requise malgré l'appareil de confiance
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Connexion suspecte détectée. Authentification requise."</p>
<hr />
<h2 id="15-desactiver-la-2fa">15. Désactiver la 2FA</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la 2FA TOTP est activée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je désactive la 2FA depuis mes paramètres
<span style="color: #9E9E9E"><strong>Et</strong></span> que je confirme avec mon mot de passe</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la 2FA est désactivée
<span style="color: #9E9E9E"><strong>Et</strong></span> tous les codes de backup sont invalidés
<span style="color: #9E9E9E"><strong>Et</strong></span> tous les appareils de confiance sont révoqués</p>
<hr />
<h2 id="16-regenerer-les-codes-de-backup">16. Régénérer les codes de backup</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la 2FA est activée
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai utilisé 8 codes de backup sur 10</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je demande à régénérer les codes de backup</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois 10 nouveaux codes
<span style="color: #9E9E9E"><strong>Et</strong></span> tous les anciens codes (utilisés ou non) sont invalidés</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="verification-demail">Vérification d'email</h1>
<blockquote>
<p><em>En tant qu'utilisateur inscrit</em>
<em>Je veux vérifier mon adresse email</em>
<em>Afin d'accéder à toutes les fonctionnalités selon mon rôle</em></p>
</blockquote>
<p><strong>10 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible</p>
</blockquote>
<h2 id="1-auditeur-avec-email-non-verifie-lecture-illimitee">1. Auditeur avec email non vérifié - lecture illimitée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un auditeur avec email non vérifié</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie d'écouter du contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux écouter tous les contenus sans limite</p>
<hr />
<h2 id="2-auditeur-avec-email-non-verifie-creation-limitee-a-5-contenus">2. Auditeur avec email non vérifié - création limitée à 5 contenus</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un auditeur avec email non vérifié
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai créé 4 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je crée un 5ème contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est créé avec succès
<span style="color: #F44336"><strong>Mais</strong></span> quand j'essaie de créer un 6ème contenu
<span style="color: #4CAF50"><strong>Alors</strong></span> la création échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Vérifiez votre email pour créer plus de contenus"</p>
<hr />
<h2 id="3-rappel-de-verification-apres-le-3eme-contenu-cree">3. Rappel de vérification après le 3ème contenu créé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un auditeur avec email non vérifié
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai créé 2 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je crée mon 3ème contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est créé avec succès
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois une notification in-app "Vérifiez votre email pour débloquer la création illimitée"</p>
<hr />
<h2 id="4-auditeur-verifie-son-email">4. Auditeur vérifie son email</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un auditeur avec email non vérifié
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai reçu un lien de vérification</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur le lien de vérification dans l'email</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon email est marqué comme vérifié
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Email vérifié avec succès"
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes les fonctionnalités sont débloquées</p>
<hr />
<h2 id="5-createur-doit-verifier-son-email-sous-7-jours-pour-monetisation">5. Créateur doit vérifier son email sous 7 jours pour monétisation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis inscrit comme créateur
<span style="color: #9E9E9E"><strong>Et</strong></span> que mon email n'est pas vérifié
<span style="color: #9E9E9E"><strong>Et</strong></span> que je remplis les conditions de monétisation</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie d'accéder au programme de monétisation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'accès est refusé
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Vérifiez votre email pour accéder à la monétisation"</p>
<hr />
<h2 id="6-createur-ne-peut-pas-publier-de-contenus-illimites-sans-verification">6. Créateur ne peut pas publier de contenus illimités sans vérification</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur avec email non vérifié
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai créé 5 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de créer un 6ème contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la création échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Vérifiez votre email pour publier des contenus illimités"</p>
<hr />
<h2 id="7-createur-verifie-son-email-et-deboque-tout">7. Créateur vérifie son email et déboque tout</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur avec email non vérifié
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai reçu un lien de vérification</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur le lien de vérification</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon email est marqué comme vérifié
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux publier des contenus illimités
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux accéder au programme de monétisation si j'en remplis les conditions</p>
<hr />
<h2 id="8-kyc-impossible-sans-email-verifie">8. KYC impossible sans email vérifié</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur avec email non vérifié</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de compléter le KYC via Mangopay</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'accès au KYC est refusé
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Vérifiez votre email avant de procéder au KYC"</p>
<hr />
<h2 id="9-tentative-de-verification-avec-un-lien-deja-utilise">9. Tentative de vérification avec un lien déjà utilisé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai déjà vérifié mon email avec un lien</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de réutiliser le même lien de vérification</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la vérification échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Ce lien a déjà été utilisé"</p>
<hr />
<h2 id="10-auditeur-verifie-peut-creer-plus-de-5-contenus">10. Auditeur vérifié peut créer plus de 5 contenus</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un auditeur avec email vérifié
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai créé 10 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je crée un 11ème contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est créé avec succès
<span style="color: #9E9E9E"><strong>Et</strong></span> il n'y a pas de limite de création</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="metadonnees-et-publication-de-contenu">Métadonnées et publication de contenu</h1>
<blockquote>
<p><em>En tant que créateur</em>
<em>Je veux remplir les métadonnées de mon contenu</em>
<em>Afin de le publier sur RoadWave</em></p>
</blockquote>
<p><strong>34 scénarios</strong> (32 standards, 2 plans)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis un créateur connecté
<span style="color: #9E9E9E"><strong>Et</strong></span> que mon fichier audio est encodé et prêt</p>
</blockquote>
<h2 id="1-publication-avec-toutes-les-metadonnees-obligatoires">1. Publication avec toutes les métadonnées obligatoires</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je remplis les métadonnées suivantes:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publication réussit
<span style="color: #9E9E9E"><strong>Et</strong></span> mon contenu est soumis pour validation</p>
<hr />
<h2 id="2-titre-valide-entre-5-et-100-caracteres">2. Titre valide entre 5 et 100 caractères</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je saisis un titre de 50 caractères</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le titre est accepté
<span style="color: #9E9E9E"><strong>Et</strong></span> la validation passe</p>
<hr />
<h2 id="3-titre-trop-court-5-caracteres">3. Titre trop court (&lt;5 caractères)</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je saisis un titre de 4 caractères "Test"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publication échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Le titre doit contenir entre 5 et 100 caractères"</p>
<hr />
<h2 id="4-titre-trop-long-100-caracteres">4. Titre trop long (&gt;100 caractères)</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je saisis un titre de 101 caractères</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publication échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Le titre doit contenir entre 5 et 100 caractères"</p>
<hr />
<h2 id="5-titre-a-exactement-5-caracteres-accepte">5. Titre à exactement 5 caractères accepté</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je saisis un titre de exactement 5 caractères "Titre"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le titre est accepté</p>
<hr />
<h2 id="6-titre-a-exactement-100-caracteres-accepte">6. Titre à exactement 100 caractères accepté</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je saisis un titre de exactement 100 caractères</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le titre est accepté</p>
<hr />
<h2 id="7-selectionner-type-geo-ancre">7. Sélectionner type géo "Ancré"</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne le type géo "Ancré"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système applique une pondération géo de 0.7
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois définir une zone de diffusion précise</p>
<hr />
<h2 id="8-selectionner-type-geo-contextuel">8. Sélectionner type géo "Contextuel"</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne le type géo "Contextuel"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système applique une pondération géo de 0.5
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux définir une zone ville/département/région</p>
<hr />
<h2 id="9-selectionner-type-geo-neutre">9. Sélectionner type géo "Neutre"</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne le type géo "Neutre"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système applique une pondération géo de 0.2
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux définir une zone nationale</p>
<hr />
<h2 id="10-zone-diffusion-point-gps-avec-rayon">10. Zone diffusion - Point GPS avec rayon</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je choisis "Point GPS"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je définis les coordonnées (48.8584, 2.2945)
<span style="color: #9E9E9E"><strong>Et</strong></span> que je définis un rayon de 500 mètres</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la zone est validée
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu sera diffusé dans un rayon de 500m autour du point</p>
<hr />
<h2 id="11-zone-diffusion-rayon-minimum-100m">11. Zone diffusion - Rayon minimum 100m</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je définis un rayon de 50 mètres (&lt; 100m)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la validation échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Le rayon doit être entre 100m et 10km"</p>
<hr />
<h2 id="12-zone-diffusion-rayon-maximum-10km">12. Zone diffusion - Rayon maximum 10km</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je définis un rayon de 15 km (&gt; 10km)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la validation échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Le rayon doit être entre 100m et 10km"</p>
<hr />
<h2 id="13-zone-diffusion-ville-depuis-referentiel-insee">13. Zone diffusion - Ville depuis référentiel INSEE</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je choisis "Ville"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois une liste de villes du référentiel INSEE</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne "Paris (75000)"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la zone est définie sur toute la ville de Paris</p>
<hr />
<h2 id="14-zone-diffusion-departement">14. Zone diffusion - Département</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je choisis "Département"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je sélectionne "Ille-et-Vilaine (35)"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la zone couvre tout le département 35</p>
<hr />
<h2 id="15-zone-diffusion-region">15. Zone diffusion - Région</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je choisis "Région"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je sélectionne "Bretagne"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la zone couvre toute la région Bretagne</p>
<hr />
<h2 id="16-zone-diffusion-national">16. Zone diffusion - National</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je choisis "National"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la zone couvre toute la France
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune restriction géographique n'est appliquée</p>
<hr />
<h2 id="17-zones-mutuellement-exclusives">17. Zones mutuellement exclusives</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai sélectionné "Point GPS"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de sélectionner également "Ville"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la première sélection est remplacée
<span style="color: #9E9E9E"><strong>Et</strong></span> seule "Ville" reste active</p>
<hr />
<h2 id="18-selectionner-1-tag-minimum">18. Sélectionner 1 tag minimum</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne 1 tag "Voyage"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la validation passe
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu est tagué "Voyage"</p>
<hr />
<h2 id="19-selectionner-3-tags-maximum">19. Sélectionner 3 tags maximum</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne 3 tags "Automobile", "Technologie", "Sport"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la validation passe
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu est tagué avec les 3 tags</p>
<hr />
<h2 id="20-impossible-de-selectionner-0-tag">20. Impossible de sélectionner 0 tag</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de publier sans sélectionner de tag</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publication échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Vous devez sélectionner entre 1 et 3 tags"</p>
<hr />
<h2 id="21-impossible-de-selectionner-4-tags">21. Impossible de sélectionner 4 tags</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de sélectionner 4 tags</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le 4ème tag ne peut pas être ajouté
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Maximum 3 tags"</p>
<hr />
<h2 id="22-tags-disponibles-dans-la-liste">22. Tags disponibles dans la liste</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte la liste des tags</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois les tags suivants:</p>
<pre><code>| tag |
|---|
| Automobile |
| Voyage |
| Famille |
| Amour |
| Musique |
| Économie |
| Cryptomonnaie |
| Politique |
| Culture générale |
| Sport |
| Technologie |
| Santé |
</code></pre>
<hr />
<h2 id="23-classification-age-obligatoire">23. Classification âge obligatoire</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de publier sans classification âge</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publication échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Vous devez sélectionner une classification d'âge"</p>
<hr />
<h2 id="24-plan-selectionner-classification-age">24. 📋 Plan: Sélectionner classification âge</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne la classification "<classification>"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu sera visible pour "<public_cible>"</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>classification</th>
<th>public_cible</th>
</tr>
</thead>
<tbody>
<tr>
<td>Tout public</td>
<td>Tous les utilisateurs</td>
</tr>
<tr>
<td>13+</td>
<td>Utilisateurs 13 ans et plus</td>
</tr>
<tr>
<td>16+</td>
<td>Utilisateurs 16 ans et plus</td>
</tr>
<tr>
<td>18+</td>
<td>Utilisateurs 18 ans et plus</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="25-image-de-couverture-auto-generee-selon-type-geo">25. Image de couverture auto-générée selon type géo</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je choisis le type géo "Ancré"
<span style="color: #9E9E9E"><strong>Et</strong></span> que mon tag principal est "Voyage"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la publication est soumise</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une image de couverture est générée automatiquement:</p>
<pre><code>| paramètre | valeur |
|---|---|
| Icône | 📍 (Ancré) |
| Couleur | Bleu-vert (Voyage) |
| Format | 800×800px PNG |
</code></pre>
<hr />
<h2 id="26-image-de-couverture-type-contextuel">26. Image de couverture type Contextuel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je choisis "Contextuel"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'image est générée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'icône est 🌍 (Contextuel)</p>
<hr />
<h2 id="27-image-de-couverture-type-neutre">27. Image de couverture type Neutre</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je choisis "Neutre"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'image est générée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'icône est 🎧 (Neutre)</p>
<hr />
<h2 id="28-plan-couleur-selon-tag-principal">28. 📋 Plan: Couleur selon tag principal</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon tag principal est "<tag>"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'image est générée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la couleur de fond est "<couleur>"</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>tag</th>
<th>couleur</th>
</tr>
</thead>
<tbody>
<tr>
<td>Automobile</td>
<td>Bleu</td>
</tr>
<tr>
<td>Voyage</td>
<td>Vert</td>
</tr>
<tr>
<td>Musique</td>
<td>Rouge</td>
</tr>
<tr>
<td>Économie</td>
<td>Gris</td>
</tr>
<tr>
<td>Sport</td>
<td>Orange</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="29-champs-optionnels-non-obligatoires">29. Champs optionnels non obligatoires</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je publie sans description
<span style="color: #9E9E9E"><strong>Et</strong></span> sans image de couverture personnalisée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publication réussit
<span style="color: #9E9E9E"><strong>Et</strong></span> les champs optionnels restent vides
<span style="color: #9E9E9E"><strong>Et</strong></span> une image par défaut est générée</p>
<hr />
<h2 id="30-temps-de-publication-estime-2-minutes">30. Temps de publication estimé 2 minutes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon fichier audio est prêt</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je commence à remplir les métadonnées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux publier en environ 2 minutes</p>
<hr />
<h2 id="31-publication-rapide-sans-friction">31. Publication rapide sans friction</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je publie mon premier contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucun champ complexe n'est demandé
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne suis pas bloqué sur description ou image
<span style="color: #9E9E9E"><strong>Et</strong></span> la publication est fluide</p>
<hr />
<h2 id="32-previsualisation-avant-publication">32. Prévisualisation avant publication</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai rempli toutes les métadonnées</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Prévisualiser"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois un aperçu de mon contenu:</p>
<pre><code>| élément | affiché |
|---|---|
| Titre | ✅ |
| Image couverture | ✅ |
| Tags | ✅ |
| Zone diffusion | ✅ |
| Durée audio | ✅ |
| Classification | ✅ |
</code></pre>
<hr />
<h2 id="33-enregistrer-brouillon">33. Enregistrer brouillon</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai commencé à remplir les métadonnées</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Enregistrer brouillon"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mes métadonnées sont sauvegardées
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux reprendre la publication plus tard</p>
<hr />
<h2 id="34-reprendre-brouillon">34. Reprendre brouillon</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai un brouillon sauvegardé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à mes contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois le brouillon avec statut "📝 Brouillon"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux reprendre la publication</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="modification-et-suppression-de-contenu">Modification et suppression de contenu</h1>
<blockquote>
<p><em>En tant que créateur</em>
<em>Je veux pouvoir modifier ou supprimer mes contenus</em>
<em>Afin de garder le contrôle sur mes publications</em></p>
</blockquote>
<p><strong>30 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis un créateur connecté
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai publié un contenu</p>
</blockquote>
<h2 id="1-modifier-le-titre-dun-contenu">1. Modifier le titre d'un contenu</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon contenu a le titre "Histoire de Paris"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je modifie le titre en "Histoire fascinante de Paris"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la modification est enregistrée immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Titre modifié avec succès"</p>
<hr />
<h2 id="2-correction-de-coquilles-dans-le-titre">2. Correction de coquilles dans le titre</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon titre contient une faute "Histoore de Paris"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je corrige en "Histoire de Paris"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la modification est acceptée
<span style="color: #9E9E9E"><strong>Et</strong></span> le titre corrigé est affiché</p>
<hr />
<h2 id="3-ajouter-une-description-ulterieurement">3. Ajouter une description ultérieurement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai publié sans description</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ajoute une description "Découvrez l'histoire de la capitale"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la description est enregistrée
<span style="color: #9E9E9E"><strong>Et</strong></span> elle est visible sur la page du contenu</p>
<hr />
<h2 id="4-modifier-la-description-existante">4. Modifier la description existante</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon contenu a déjà une description</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je modifie la description</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la nouvelle description remplace l'ancienne
<span style="color: #9E9E9E"><strong>Et</strong></span> la modification est immédiate</p>
<hr />
<h2 id="5-modifier-les-tags-pour-ajuster-pertinence">5. Modifier les tags pour ajuster pertinence</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon contenu est tagué "Sport", "Musique"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je change les tags en "Sport", "Santé"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les nouveaux tags sont appliqués
<span style="color: #9E9E9E"><strong>Et</strong></span> l'algorithme utilise les nouveaux tags pour recommandations</p>
<hr />
<h2 id="6-personnaliser-limage-de-couverture">6. Personnaliser l'image de couverture</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon contenu a une image auto-générée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'uploade une image personnalisée 800×800px</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'image personnalisée remplace l'image par défaut
<span style="color: #9E9E9E"><strong>Et</strong></span> elle est visible sur le contenu</p>
<hr />
<h2 id="7-impossible-de-modifier-laudio">7. Impossible de modifier l'audio</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon contenu audio est publié</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de remplacer le fichier audio</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la modification est refusée
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "L'audio ne peut pas être modifié après publication"</p>
<hr />
<h2 id="8-raison-eviter-fraude-audio">8. Raison - Éviter fraude audio</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je veux changer l'audio après validation</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de modifier</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système refuse pour éviter:</p>
<pre><code>| risque |
|---|
| Uploader contenu validé puis remplacer spam |
| Fraude sur l'intégrité du contenu |
</code></pre>
<hr />
<h2 id="9-impossible-de-modifier-la-zone-de-diffusion">9. Impossible de modifier la zone de diffusion</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon contenu est diffusé à Paris</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de changer la zone en "National"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la modification est refusée
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "La zone de diffusion ne peut pas être modifiée"</p>
<hr />
<h2 id="10-raison-eviter-manipulation-algorithme">10. Raison - Éviter manipulation algorithme</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je veux changer ma zone</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de modifier</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système refuse pour éviter:</p>
<pre><code>| manipulation |
|---|
| Créer "Local Paris" puis changer en "National" |
| Boost artificiel de visibilité |
</code></pre>
<hr />
<h2 id="11-impossible-de-modifier-le-type-geo">11. Impossible de modifier le type géo</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon contenu est type "Neutre" (pondération 0.2)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de changer en "Ancré" (pondération 0.7)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la modification est refusée
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Le type géographique ne peut pas être modifié"</p>
<hr />
<h2 id="12-raison-eviter-abus-de-ponderation">12. Raison - Éviter abus de pondération</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je veux changer le type géo</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de modifier</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système refuse pour éviter:</p>
<pre><code>| abus |
|---|
| Créer "Neutre" puis passer en "Ancré" |
| Manipulation de la pondération algorithme |
</code></pre>
<hr />
<h2 id="13-impossible-de-modifier-la-classification-age">13. Impossible de modifier la classification âge</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon contenu est classé "Tout public"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de changer en "18+"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la modification est refusée
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "La classification d'âge ne peut pas être modifiée"</p>
<hr />
<h2 id="14-raison-securite-mineurs">14. Raison - Sécurité mineurs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je veux changer la classification</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de modifier</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système refuse pour garantir:</p>
<pre><code>| protection |
|---|
| Classification vérifiée en modération |
| Pas de contournement validation |
| Sécurité des mineurs |
</code></pre>
<hr />
<h2 id="15-solution-si-besoin-de-changer-audiozoneclassification">15. Solution si besoin de changer audio/zone/classification</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je veux absolument changer l'audio</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les options</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois "Supprimer et republier le contenu"
<span style="color: #9E9E9E"><strong>Et</strong></span> c'est la seule solution disponible</p>
<hr />
<h2 id="16-republication-apres-suppression-createur-3-validations">16. Republication après suppression - créateur &lt;3 validations</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un nouveau créateur (2 contenus validés)
<span style="color: #9E9E9E"><strong>Et</strong></span> que je supprime puis republie un contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je republie avec les modifications</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu repasse en file de validation
<span style="color: #9E9E9E"><strong>Et</strong></span> une nouvelle validation est effectuée</p>
<hr />
<h2 id="17-republication-apres-suppression-createur-verifie">17. Republication après suppression - créateur vérifié</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis créateur vérifié (≥3 contenus validés)
<span style="color: #9E9E9E"><strong>Et</strong></span> que je supprime puis republie un contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je republie avec les modifications</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est publié immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune validation préalable n'est requise</p>
<hr />
<h2 id="18-suppression-de-contenu-immediate">18. Suppression de contenu immédiate</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Supprimer le contenu"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je confirme la suppression</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est supprimé immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> disparaît de la liste publique</p>
<hr />
<h2 id="19-confirmation-avant-suppression">19. Confirmation avant suppression</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Supprimer"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois un message de confirmation:</p>
<pre><code>| titre | Êtes-vous sûr ? |
|---|---|
| message | Cette action est définitive |
| warning | Le contenu sera supprimé définitivement |
| actions | Confirmer / Annuler |
</code></pre>
<hr />
<h2 id="20-suppression-definitive-et-non-reversible">20. Suppression définitive et non réversible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai supprimé un contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de le récupérer</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la récupération est impossible
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu est définitivement perdu</p>
<hr />
<h2 id="21-suppression-bdd-cdn-sous-5-minutes">21. Suppression BDD + CDN sous 5 minutes</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je supprime un contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'entrée en base de données est marquée "deleted"
<span style="color: #9E9E9E"><strong>Et</strong></span> les fichiers CDN sont marqués pour suppression
<span style="color: #9E9E9E"><strong>Et</strong></span> la suppression effective a lieu sous 5 minutes</p>
<hr />
<h2 id="22-historique-auditeurs-conserve-anonymise">22. Historique auditeurs conservé anonymisé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 1000 personnes ont écouté mon contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je supprime le contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> leur historique est conservé
<span style="color: #F44336"><strong>Mais</strong></span> marqué "Contenu supprimé par créateur"
<span style="color: #9E9E9E"><strong>Et</strong></span> la durée d'écoute est conservée pour leurs stats</p>
<hr />
<h2 id="23-analytics-plateforme-anonymisees-conservees">23. Analytics plateforme anonymisées conservées</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon contenu a généré 10K écoutes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je supprime le contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les métriques globales sont conservées anonymement:</p>
<pre><code>| métrique | conservée |
|---|---|
| Total écoutes | ✅ (anonyme) |
| Durée totale | ✅ (anonyme) |
| Catégorie | ✅ (anonyme) |
| Auteur | ❌ (anonymisé) |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> c'est conforme RGPD</p>
<hr />
<h2 id="24-fichiers-cdn-supprimes-sous-24h">24. Fichiers CDN supprimés sous 24h</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon contenu est supprimé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> 24 heures s'écoulent</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les fichiers audio sont purgés du CDN Bunny
<span style="color: #9E9E9E"><strong>Et</strong></span> l'espace de stockage est libéré</p>
<hr />
<h2 id="25-pas-de-notification-aux-auditeurs">25. Pas de notification aux auditeurs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 500 utilisateurs ont écouté mon contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je supprime le contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune notification n'est envoyée aux auditeurs
<span style="color: #9E9E9E"><strong>Et</strong></span> il n'y a pas d'effet Streisand</p>
<hr />
<h2 id="26-auditeur-tente-de-reecouter-contenu-supprime">26. Auditeur tente de réécouter contenu supprimé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un auditeur a écouté mon contenu
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai supprimé ce contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'auditeur tente de le réécouter depuis son historique</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit le message "Ce contenu n'est plus disponible"
<span style="color: #9E9E9E"><strong>Et</strong></span> la lecture est impossible</p>
<hr />
<h2 id="27-historique-auditeur-conserve-trace">27. Historique auditeur conserve trace</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un auditeur a écouté mon contenu le 15 janvier
<span style="color: #9E9E9E"><strong>Et</strong></span> que je supprime le contenu le 20 janvier</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'auditeur consulte son historique</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit "Vous avez écouté ce contenu le 15 janvier 2026"
<span style="color: #9E9E9E"><strong>Et</strong></span> le titre est remplacé par "Contenu supprimé"
<span style="color: #9E9E9E"><strong>Et</strong></span> la date d'écoute est conservée</p>
<hr />
<h2 id="28-statistiques-createur-apres-suppression">28. Statistiques créateur après suppression</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai publié 10 contenus
<span style="color: #9E9E9E"><strong>Et</strong></span> que je supprime 2 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mes statistiques globales</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| métrique | valeur |
|---|---|
| Contenus publiés | 8 (actifs) |
| Total historique | 10 |
| Suppressions | 2 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> l'historique des suppressions est visible</p>
<hr />
<h2 id="29-limite-de-modifications-par-contenu">29. Limite de modifications par contenu</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai modifié un titre 10 fois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de modifier une 11ème fois</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la modification est acceptée</p>
<hr />
<h2 id="30-historique-des-modifications-visible">30. Historique des modifications visible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai modifié un contenu plusieurs fois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte l'historique</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| date | modification |
|---|---|
| 21/01/2026 | Titre changé |
| 20/01/2026 | Tags modifiés |
| 19/01/2026 | Description ajoutée |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je peux tracer toutes les modifications</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="upload-et-encodage-de-contenu-audio">Upload et encodage de contenu audio</h1>
<blockquote>
<p><em>En tant que créateur</em>
<em>Je veux uploader mon contenu audio</em>
<em>Afin qu'il soit encodé et disponible pour les auditeurs</em></p>
</blockquote>
<p><strong>29 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis un créateur connecté</p>
</blockquote>
<h2 id="1-upload-fichier-mp3-valide">1. Upload fichier MP3 valide</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'uploade un fichier MP3 de 50 MB et 30 minutes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'upload réussit
<span style="color: #9E9E9E"><strong>Et</strong></span> le fichier est envoyé vers Bunny Storage temporaire
<span style="color: #9E9E9E"><strong>Et</strong></span> un job d'encodage asynchrone est lancé</p>
<hr />
<h2 id="2-upload-fichier-aac-valide-aac">2. Upload fichier AAC valide (.aac)</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'uploade un fichier AAC de 80 MB et 1 heure</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'upload réussit
<span style="color: #9E9E9E"><strong>Et</strong></span> le fichier est accepté
<span style="color: #9E9E9E"><strong>Et</strong></span> l'encodage démarre</p>
<hr />
<h2 id="3-upload-fichier-m4a-valide">3. Upload fichier M4A valide</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'uploade un fichier M4A de 100 MB et 2 heures</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'upload réussit
<span style="color: #9E9E9E"><strong>Et</strong></span> le fichier est traité comme AAC
<span style="color: #9E9E9E"><strong>Et</strong></span> l'encodage démarre</p>
<hr />
<h2 id="4-rejet-fichier-wav-non-supporte">4. Rejet fichier WAV (non supporté)</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie d'uploader un fichier WAV</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'upload échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Format non supporté. Utilisez MP3 ou AAC (.mp3, .aac, .m4a)"</p>
<hr />
<h2 id="5-rejet-fichier-flac-non-supporte">5. Rejet fichier FLAC (non supporté)</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie d'uploader un fichier FLAC</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'upload échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Format non supporté. Utilisez MP3 ou AAC (.mp3, .aac, .m4a)"</p>
<hr />
<h2 id="6-validation-taille-maximale-200-mb">6. Validation taille maximale 200 MB</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie d'uploader un fichier MP3 de 201 MB</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'upload échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Fichier trop volumineux (max 200 MB)"</p>
<hr />
<h2 id="7-upload-a-la-limite-de-200-mb-accepte">7. Upload à la limite de 200 MB accepté</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'uploade un fichier MP3 de exactement 200 MB</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'upload réussit
<span style="color: #9E9E9E"><strong>Et</strong></span> le fichier est accepté</p>
<hr />
<h2 id="8-validation-duree-maximale-4-heures">8. Validation durée maximale 4 heures</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie d'uploader un fichier de 4h 10min</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'upload échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Durée trop longue (max 4 heures)"</p>
<hr />
<h2 id="9-upload-a-la-limite-de-4h-accepte">9. Upload à la limite de 4h accepté</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'uploade un fichier de exactement 4 heures</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'upload réussit
<span style="color: #9E9E9E"><strong>Et</strong></span> le fichier est accepté</p>
<hr />
<h2 id="10-validation-format-cote-client">10. Validation format côté client</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne un fichier dans l'interface</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la validation du format est faite immédiatement côté client
<span style="color: #9E9E9E"><strong>Et</strong></span> je suis informé avant même de lancer l'upload si le format est invalide</p>
<hr />
<h2 id="11-double-validation-cote-backend">11. Double validation côté backend</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un fichier a passé la validation client</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le backend reçoit le fichier</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une validation supplémentaire est effectuée
<span style="color: #9E9E9E"><strong>Et</strong></span> le format et l'intégrité sont vérifiés</p>
<hr />
<h2 id="12-pipeline-dencodage-etape-1-upload">12. Pipeline d'encodage - étape 1 upload</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'uploade un fichier MP3 valide</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le fichier est stocké temporairement dans Bunny Storage
<span style="color: #9E9E9E"><strong>Et</strong></span> un job d'encodage est mis en file d'attente</p>
<hr />
<h2 id="13-pipeline-dencodage-validation-format">13. Pipeline d'encodage - validation format</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un job d'encodage est lancé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le worker Go traite le fichier</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le format est validé avec FFmpeg
<span style="color: #9E9E9E"><strong>Et</strong></span> l'intégrité du fichier est vérifiée</p>
<hr />
<h2 id="14-pipeline-dencodage-generation-3-profils-opus">14. Pipeline d'encodage - génération 3 profils Opus</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un fichier audio est validé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'encodage démarre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 3 profils Opus sont générés:</p>
<pre><code>| qualité | bitrate | usage |
|---|---|---|
| Basse | 24 kbps | 2G/Edge |
| Standard | 48 kbps | 3G |
| Haute | 64 kbps | 4G/5G |
</code></pre>
<hr />
<h2 id="15-pipeline-dencodage-generation-segments-hls">15. Pipeline d'encodage - génération segments HLS</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que les profils Opus sont générés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'encodage continue</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un fichier manifest .m3u8 est créé
<span style="color: #9E9E9E"><strong>Et</strong></span> des segments .ts sont générés
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu est prêt pour streaming HLS</p>
<hr />
<h2 id="16-pipeline-dencodage-generation-image-par-defaut">16. Pipeline d'encodage - génération image par défaut</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'encodage est en cours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les métadonnées sont traitées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une image de couverture par défaut est générée
<span style="color: #9E9E9E"><strong>Et</strong></span> l'image fait 800×800px au format PNG</p>
<hr />
<h2 id="17-pipeline-dencodage-suppression-fichier-original">17. Pipeline d'encodage - suppression fichier original</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'encodage est terminé avec succès</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> tous les fichiers de sortie sont générés</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le fichier original MP3/AAC est supprimé
<span style="color: #9E9E9E"><strong>Et</strong></span> seuls les profils Opus et HLS sont conservés
<span style="color: #9E9E9E"><strong>Et</strong></span> l'espace de stockage est économisé</p>
<hr />
<h2 id="18-temps-dencodage-contenu-5-minutes">18. Temps d'encodage contenu 5 minutes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un fichier de 5 minutes est uploadé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'encodage démarre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'encodage prend environ 30 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois une notification "Contenu prêt à publier"</p>
<hr />
<h2 id="19-temps-dencodage-podcast-1-heure">19. Temps d'encodage podcast 1 heure</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un fichier de 1 heure est uploadé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'encodage démarre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'encodage prend environ 5 minutes
<span style="color: #9E9E9E"><strong>Et</strong></span> une barre de progression est affichée</p>
<hr />
<h2 id="20-temps-dencodage-podcast-4-heures">20. Temps d'encodage podcast 4 heures</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un fichier de 4 heures est uploadé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'encodage démarre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'encodage prend environ 20 minutes
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux fermer l'app (traitement asynchrone)</p>
<hr />
<h2 id="21-notification-contenu-pret-a-publier">21. Notification "Contenu prêt à publier"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon contenu est en cours d'encodage</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'encodage se termine avec succès</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification push "✅ Votre contenu est prêt à publier"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux accéder à l'interface de publication</p>
<hr />
<h2 id="22-echec-dencodage-fichier-corrompu">22. Échec d'encodage - fichier corrompu</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un fichier MP3 corrompu est uploadé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'encodage démarre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'encodage échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois une notification "❌ Erreur d'encodage: fichier corrompu"
<span style="color: #9E9E9E"><strong>Et</strong></span> le fichier temporaire est supprimé</p>
<hr />
<h2 id="23-ecoute-acceleree-vitesses-disponibles">23. Écoute accélérée - vitesses disponibles</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu est publié</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un auditeur écoute le contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il peut choisir parmi les vitesses:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="24-ecoute-acceleree-pour-moderateurs">24. Écoute accélérée pour modérateurs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un modérateur
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un contenu de 30 secondes est à valider</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je l'écoute à 2.0x</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je termine l'écoute en 15 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> ma productivité est doublée</p>
<hr />
<h2 id="25-ecoute-acceleree-pour-auditeurs">25. Écoute accélérée pour auditeurs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un auditeur
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un podcast de 1 heure est disponible</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je configure la vitesse à 1.5x</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> j'écoute le podcast en 40 minutes
<span style="color: #9E9E9E"><strong>Et</strong></span> je gagne 20 minutes</p>
<hr />
<h2 id="26-sauvegarde-preference-vitesse-decoute">26. Sauvegarde préférence vitesse d'écoute</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je configure la vitesse à 1.5x</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute plusieurs contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les contenus sont lus à 1.5x par défaut
<span style="color: #9E9E9E"><strong>Et</strong></span> ma préférence est sauvegardée</p>
<hr />
<h2 id="27-scalabilite-horizontale-des-workers">27. Scalabilité horizontale des workers</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 100 contenus sont uploadés simultanément</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les jobs d'encodage sont distribués</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> plusieurs workers Go traitent les jobs en parallèle
<span style="color: #9E9E9E"><strong>Et</strong></span> Kubernetes scale automatiquement les pods
<span style="color: #9E9E9E"><strong>Et</strong></span> tous les contenus sont encodés sans délai excessif</p>
<hr />
<h2 id="28-statut-dencodage-visible">28. Statut d'encodage visible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon contenu est en cours d'encodage</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mes contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois le statut:</p>
<pre><code>| état | affichage |
|---|---|
| En attente | ⏳ File d'attente |
| En cours | ⚙️ Encodage en cours (45%) |
| Terminé | ✅ Prêt à publier |
| Échec | ❌ Erreur - Réessayer |
</code></pre>
<hr />
<h2 id="29-reessayer-apres-echec-dencodage">29. Réessayer après échec d'encodage</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'encodage de mon contenu a échoué</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Réessayer"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un nouveau job d'encodage est lancé
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux tenter à nouveau</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="validation-des-3-premiers-contenus">Validation des 3 premiers contenus</h1>
<blockquote>
<p><em>En tant que nouveau créateur</em>
<em>Je veux que mes 3 premiers contenus soient validés</em>
<em>Afin de devenir créateur vérifié</em></p>
</blockquote>
<p><strong>30 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis un nouveau créateur</p>
</blockquote>
<h2 id="1-premier-contenu-passe-en-file-de-validation">1. Premier contenu passe en file de validation</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je publie mon premier contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu passe en file d'attente modération
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Votre contenu est en cours de validation (24-48h)"
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu n'est pas encore visible publiquement</p>
<hr />
<h2 id="2-deuxieme-contenu-passe-egalement-en-validation">2. Deuxième contenu passe également en validation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon premier contenu a été validé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je publie mon deuxième contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu passe en file d'attente modération
<span style="color: #9E9E9E"><strong>Et</strong></span> le délai estimé est 24-48h</p>
<hr />
<h2 id="3-troisieme-contenu-derniere-validation">3. Troisième contenu - dernière validation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes 2 premiers contenus ont été validés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je publie mon troisième contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu passe en file d'attente modération
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois "Dernière validation avant statut vérifié ✓"</p>
<hr />
<h2 id="4-moderateur-ecoute-30-secondes-du-contenu">4. Modérateur écoute 30 secondes du contenu</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu est en file de validation</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur junior l'examine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il écoute les 30 premières secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> il vérifie les métadonnées</p>
<hr />
<h2 id="5-validation-qualite-audio-acceptable">5. Validation - Qualité audio acceptable</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu a une qualité audio claire</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur l'écoute</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il vérifie que l'audio est compréhensible
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il n'y a pas de grésillement excessif</p>
<hr />
<h2 id="6-rejet-qualite-audio-insuffisante">6. Rejet - Qualité audio insuffisante</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu a un audio très grésillant</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur l'écoute</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est rejeté
<span style="color: #9E9E9E"><strong>Et</strong></span> la raison est "Qualité audio insuffisante"</p>
<hr />
<h2 id="7-validation-respect-des-regles">7. Validation - Respect des règles</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu respecte les règles</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur l'examine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il vérifie qu'il n'y a pas de contenu prohibé:</p>
<pre><code>| type prohibé |
|---|
| Haine |
| Violence |
| Spam |
| Illégalité |
</code></pre>
<hr />
<h2 id="8-rejet-contenu-haineux-detecte">8. Rejet - Contenu haineux détecté</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu contient des propos haineux</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur l'écoute</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est rejeté immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> la raison est "Contenu haineux (violation des règles)"
<span style="color: #9E9E9E"><strong>Et</strong></span> le créateur peut recevoir un strike</p>
<hr />
<h2 id="9-validation-classification-age-coherente">9. Validation - Classification âge cohérente</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu familial est classé "Tout public"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur l'écoute</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il vérifie que la classification correspond au contenu
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu est accepté</p>
<hr />
<h2 id="10-rejet-classification-incorrecte">10. Rejet - Classification incorrecte</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu adulte est classé "Tout public"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur détecte l'incohérence</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est rejeté
<span style="color: #9E9E9E"><strong>Et</strong></span> la raison est "Classification d'âge incorrecte"</p>
<hr />
<h2 id="11-validation-tags-pertinents">11. Validation - Tags pertinents</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu sur l'automobile est tagué "Automobile", "Technologie"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur vérifie les tags</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il confirme que les tags correspondent au contenu
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu est accepté</p>
<hr />
<h2 id="12-rejet-tags-non-pertinents">12. Rejet - Tags non pertinents</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu musical est tagué "Automobile", "Sport"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur détecte l'incohérence</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est rejeté
<span style="color: #9E9E9E"><strong>Et</strong></span> la raison est "Tags non pertinents avec le contenu"</p>
<hr />
<h2 id="13-validation-zone-diffusion-coherente">13. Validation - Zone diffusion cohérente</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide de la Tour Eiffel est en "Point GPS" Paris</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur vérifie la cohérence</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la zone est appropriée
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu est accepté</p>
<hr />
<h2 id="14-rejet-zone-incoherente">14. Rejet - Zone incohérente</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide de la Tour Eiffel est en zone "National"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur détecte l'incohérence</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est rejeté
<span style="color: #9E9E9E"><strong>Et</strong></span> la raison est "Zone de diffusion incohérente (devrait être Point GPS)"</p>
<hr />
<h2 id="15-delai-de-validation-24-48h-jours-ouvres">15. Délai de validation 24-48h jours ouvrés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je publie un contenu un lundi</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le contenu entre en file de validation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le délai estimé est 24-48h (mercredi maximum)</p>
<hr />
<h2 id="16-delai-etendu-le-weekend">16. Délai étendu le weekend</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je publie un contenu un vendredi soir</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le contenu entre en file de validation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le délai peut atteindre 72h (lundi)
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois "Validation en cours, délai 24-72h (weekend)"</p>
<hr />
<h2 id="17-priorite-fifo-first-in-first-out">17. Priorité FIFO (First In First Out)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 10 contenus sont en file de validation</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les modérateurs traitent la file</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les contenus sont traités dans l'ordre d'arrivée
<span style="color: #9E9E9E"><strong>Et</strong></span> pas de traitement prioritaire</p>
<hr />
<h2 id="18-notification-acceptation">18. Notification acceptation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon contenu est validé et accepté</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email "✅ Votre contenu '[Titre]' est en ligne !"
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois une notification push
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois un lien direct vers le contenu</p>
<hr />
<h2 id="19-compteur-de-validation">19. Compteur de validation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon premier contenu est accepté</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois "1/3 contenus validés pour devenir créateur vérifié"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> mon deuxième contenu est accepté</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois "2/3 contenus validés pour devenir créateur vérifié"</p>
<hr />
<h2 id="20-notification-refus-avec-raison-detaillee">20. Notification refus avec raison détaillée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon contenu est rejeté</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email "❌ Contenu '[Titre]' refusé"
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois une notification push
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois la raison exacte: "Qualité audio insuffisante"
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois un lien vers les règles de publication</p>
<hr />
<h2 id="21-possibilite-de-correction-et-resoumission">21. Possibilité de correction et resoumission</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon contenu a été rejeté pour "Tags non pertinents"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je corrige les tags
<span style="color: #9E9E9E"><strong>Et</strong></span> que je resoumets le contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu repasse en file de validation
<span style="color: #9E9E9E"><strong>Et</strong></span> une nouvelle validation est effectuée</p>
<hr />
<h2 id="22-apres-3-validations-statut-verifie-obtenu">22. Après 3 validations - Statut vérifié obtenu</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes 3 premiers contenus ont été validés</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> j'obtiens le statut "Créateur Vérifié"
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois une notification "🎉 Vous êtes maintenant créateur vérifié !"
<span style="color: #9E9E9E"><strong>Et</strong></span> un badge ✓ apparaît sur mon profil</p>
<hr />
<h2 id="23-badge-verifie-visible-publiquement">23. Badge vérifié visible publiquement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai le statut vérifié</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un utilisateur consulte mon profil</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit le badge ✓ à côté de mon pseudo
<span style="color: #9E9E9E"><strong>Et</strong></span> une mention "Créateur vérifié"</p>
<hr />
<h2 id="24-contenus-futurs-publies-immediatement">24. Contenus futurs publiés immédiatement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis créateur vérifié</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je publie un 4ème contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est publié immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> il n'y a pas de validation préalable
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois "✅ Contenu publié"</p>
<hr />
<h2 id="25-moderation-a-posteriori-uniquement">25. Modération a posteriori uniquement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis créateur vérifié
<span style="color: #9E9E9E"><strong>Et</strong></span> que je publie un contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le contenu est en ligne</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il peut être signalé par les utilisateurs
<span style="color: #9E9E9E"><strong>Et</strong></span> sera modéré uniquement si signalé</p>
<hr />
<h2 id="26-interface-moderateur-queue-de-contenus">26. Interface modérateur - Queue de contenus</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un modérateur junior</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à l'interface de modération</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois la file des contenus à valider
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le nombre total en attente
<span style="color: #9E9E9E"><strong>Et</strong></span> les contenus sont triés par ordre FIFO</p>
<hr />
<h2 id="27-interface-moderateur-ecoute-acceleree">27. Interface modérateur - Écoute accélérée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un modérateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute un contenu de 30 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux choisir la vitesse 1.5x ou 2.0x
<span style="color: #9E9E9E"><strong>Et</strong></span> je termine l'écoute en 15 secondes à 2x
<span style="color: #9E9E9E"><strong>Et</strong></span> ma productivité est doublée</p>
<hr />
<h2 id="28-interface-moderateur-raccourcis-clavier">28. Interface modérateur - Raccourcis clavier</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je modère un contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'utilise les raccourcis clavier</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux:</p>
<pre><code>| touche | action |
|---|---|
| A | Accepter |
| R | Rejeter |
| Espace | Play/Pause |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> la modération est accélérée</p>
<hr />
<h2 id="29-historique-createur-visible">29. Historique créateur visible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur soumet son 2ème contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur examine le contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit l'historique:</p>
<pre><code>| contenu | statut |
|---|---|
| Contenu 1 | Validé |
| Contenu 2 | En cours |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> il peut juger la cohérence du créateur</p>
<hr />
<h2 id="30-temps-de-moderation-estime-15-mincreateur">30. Temps de modération estimé 1.5 min/créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur soumet 3 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les modérateurs traitent ces contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le temps total est environ:</p>
<pre><code>| action | temps |
|---|---|
| Écoute 30s × 3 | 90s |
| Vérification metadata | 15s |
| Décision | 5s |
| Total | 110s |
</code></pre>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="elargissement-automatique-de-zone-quand-aucun-contenu-nest-disponible">Élargissement automatique de zone quand aucun contenu n'est disponible</h1>
<p><strong>9 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur connecté
<span style="color: #9E9E9E"><strong>Et</strong></span> que la géolocalisation est activée
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis en mode écoute</p>
</blockquote>
<h2 id="1-aucun-contenu-dans-rayon-50km-elargissement-a-100km">1. Aucun contenu dans rayon 50km - élargissement à 100km</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis situé à la position GPS 48.8566, 2.3522
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'aucun contenu n'existe dans un rayon de 50 km autour de ma position
<span style="color: #F44336"><strong>Mais</strong></span> qu'au moins 1 contenu existe dans un rayon de 100 km</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système recherche du contenu à me proposer</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système élargit automatiquement la zone de recherche à 100 km
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un message "Aucun contenu dans votre zone immédiate. Voici du contenu à proximité (100 km)"
<span style="color: #9E9E9E"><strong>Et</strong></span> un contenu dans le rayon de 100 km m'est proposé</p>
<hr />
<h2 id="2-aucun-contenu-dans-rayon-100km-elargissement-au-departement">2. Aucun contenu dans rayon 100km - élargissement au département</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis situé dans le département "75" (Paris)
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'aucun contenu n'existe dans un rayon de 100 km autour de ma position
<span style="color: #F44336"><strong>Mais</strong></span> qu'au moins 1 contenu existe avec la zone "département" pour "75"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système recherche du contenu à me proposer</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système élargit automatiquement la zone de recherche au département
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un message "Aucun contenu local disponible. Voici du contenu dans votre département"
<span style="color: #9E9E9E"><strong>Et</strong></span> un contenu départemental m'est proposé</p>
<hr />
<h2 id="3-aucun-contenu-departemental-elargissement-a-la-region">3. Aucun contenu départemental - élargissement à la région</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis situé dans la région "Île-de-France"
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'aucun contenu n'existe dans un rayon de 100 km autour de ma position
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'aucun contenu départemental n'existe pour mon département
<span style="color: #F44336"><strong>Mais</strong></span> qu'au moins 1 contenu existe avec la zone "région" pour "Île-de-France"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système recherche du contenu à me proposer</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système élargit automatiquement la zone de recherche à la région
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un message "Aucun contenu local disponible. Voici du contenu dans votre région"
<span style="color: #9E9E9E"><strong>Et</strong></span> un contenu régional m'est proposé</p>
<hr />
<h2 id="4-aucun-contenu-regional-basculement-sur-contenu-national">4. Aucun contenu régional - basculement sur contenu national</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis situé en France
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'aucun contenu n'existe dans un rayon de 100 km autour de ma position
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'aucun contenu départemental n'existe pour mon département
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'aucun contenu régional n'existe pour ma région</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système recherche du contenu à me proposer</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système bascule automatiquement sur du contenu national
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un message "Aucun contenu local disponible. Voici du contenu national qui pourrait vous intéresser"
<span style="color: #9E9E9E"><strong>Et</strong></span> un contenu national m'est proposé
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne reste jamais sans contenu disponible</p>
<hr />
<h2 id="5-elargissement-progressif-avec-plusieurs-etapes">5. Élargissement progressif avec plusieurs étapes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis situé dans une zone rurale isolée
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'aucun contenu n'existe dans un rayon de 50 km
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'aucun contenu n'existe dans un rayon de 100 km
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'aucun contenu départemental n'existe
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'aucun contenu régional n'existe</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système recherche du contenu à me proposer</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système essaie d'abord 50 km
<span style="color: #9E9E9E"><strong>Et</strong></span> tout ce processus se fait de manière transparente et automatique
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois le message correspondant au dernier niveau trouvé</p>
<hr />
<h2 id="6-message-personnalise-selon-la-distance-trouvee">6. Message personnalisé selon la distance trouvée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis situé à la position GPS 43.6047, 1.4442
<span style="color: #9E9E9E"><strong>Et</strong></span> que <niveau_geo> contenu(s) est/sont trouvé(s)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système me propose du contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois le message "<message_attendu>"</p>
<hr />
<h2 id="7-le-contenu-national-sert-de-filet-de-securite">7. Le contenu national sert de filet de sécurité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le système a épuisé toutes les zones géographiques locales</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système bascule sur du contenu national</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je dois toujours avoir au moins 1 contenu disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> ce contenu peut être:</p>
<pre><code>| type_contenu |
|---|
| Actualités Le Monde |
| Podcasts génériques |
| Contenu éducatif national |
| Contenu culturel national |
</code></pre>
<hr />
<h2 id="8-pas-decran-derreur-aucun-contenu">8. Pas d'écran d'erreur "Aucun contenu"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je lance l'application
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'aucun contenu local n'est disponible dans ma zone</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système recherche du contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je ne dois jamais voir un message d'erreur "Aucun contenu disponible"
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne dois jamais voir un écran vide
<span style="color: #9E9E9E"><strong>Et</strong></span> un contenu doit toujours m'être proposé, même si c'est du contenu national</p>
<hr />
<h2 id="9-elargissement-avec-prise-en-compte-des-centres-dinteret">9. Élargissement avec prise en compte des centres d'intérêt</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis situé dans une zone rurale
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'aucun contenu n'existe dans un rayon de 50 km
<span style="color: #9E9E9E"><strong>Et</strong></span> que mes centres d'intérêt incluent "Automobile" à 80% et "Voyage" à 70%
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un contenu national existe avec le tag "Automobile"
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un contenu national existe avec le tag "Politique"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système bascule sur du contenu national</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu national proposé prend en compte mes centres d'intérêt
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu "Automobile" a un score supérieur au contenu "Politique"</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="gestion-dun-contenu-supprime-pendant-lecoute">Gestion d'un contenu supprimé pendant l'écoute</h1>
<p><strong>11 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur connecté
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis en mode écoute
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un contenu "C123" est en cours de lecture</p>
</blockquote>
<h2 id="1-contenu-supprime-pendant-lecture-fin-de-lecture-sans-interruption">1. Contenu supprimé pendant lecture - fin de lecture sans interruption</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute le contenu "C123" depuis 30 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> que la durée totale du contenu est de 120 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le contenu est supprimé par la modération côté backend</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la lecture du contenu continue sans interruption
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux écouter le contenu jusqu'à la fin
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune interruption brutale ne se produit</p>
<hr />
<h2 id="2-passage-automatique-apres-fin-du-contenu-supprime">2. Passage automatique après fin du contenu supprimé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le contenu "C123" a été supprimé pendant ma lecture
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai écouté le contenu jusqu'à la fin</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le contenu se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système attend 2 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> passe automatiquement au contenu suivant
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois une notification toast discrète "Contenu précédent retiré (violation règles)"</p>
<hr />
<h2 id="3-bouton-precedent-desactive-apres-suppression">3. Bouton Précédent désactivé après suppression</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le contenu "C123" a été supprimé pendant ma lecture
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis passé au contenu suivant "C456"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie d'appuyer sur le bouton "Précédent"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le bouton "Précédent" ne me ramène pas au contenu supprimé
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un message "Ce contenu n'est plus disponible"
<span style="color: #9E9E9E"><strong>Et</strong></span> la lecture du contenu actuel "C456" continue</p>
<hr />
<h2 id="4-tentative-de-retour-manuel-au-contenu-supprime">4. Tentative de retour manuel au contenu supprimé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis sur le contenu "C456"
<span style="color: #9E9E9E"><strong>Et</strong></span> que le contenu précédent "C123" a été supprimé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur le bouton "Précédent" pour revenir au contenu supprimé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un message "Ce contenu n'est plus disponible"
<span style="color: #9E9E9E"><strong>Et</strong></span> la lecture reste sur le contenu actuel "C456"
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune action n'est effectuée</p>
<hr />
<h2 id="5-notification-discrete-pendant-la-conduite">5. Notification discrète pendant la conduite</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je conduis à une vitesse de 60 km/h
<span style="color: #9E9E9E"><strong>Et</strong></span> que le contenu "C123" est supprimé pendant ma lecture</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le contenu se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la notification "Contenu précédent retiré (violation règles)" s'affiche en toast discret
<span style="color: #9E9E9E"><strong>Et</strong></span> la notification disparaît automatiquement après 5 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune popup modale n'interrompt ma conduite
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu suivant démarre automatiquement après 2 secondes</p>
<hr />
<h2 id="6-message-informatif-mais-non-alarmiste">6. Message informatif mais non alarmiste</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le contenu "C123" a été supprimé
<span style="color: #9E9E9E"><strong>Et</strong></span> que je passe au contenu suivant</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la notification s'affiche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le message doit être informatif: "Contenu précédent retiré (violation règles)"
<span style="color: #9E9E9E"><strong>Et</strong></span> le ton ne doit pas être alarmiste
<span style="color: #9E9E9E"><strong>Et</strong></span> le message doit être bref et compréhensible
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun détail technique n'est affiché pendant la conduite</p>
<hr />
<h2 id="7-contenu-supprime-retire-de-lhistorique">7. Contenu supprimé retiré de l'historique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le contenu "C123" a été supprimé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mon historique d'écoute</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu "C123" n'apparaît plus dans mon historique
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne peux pas relancer la lecture de ce contenu
<span style="color: #9E9E9E"><strong>Et</strong></span> l'historique affiche "[Contenu retiré]" à la place du titre</p>
<hr />
<h2 id="8-contenu-supprime-non-accessible-via-lien-direct">8. Contenu supprimé non accessible via lien direct</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le contenu "C123" a été supprimé
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai un lien de partage "roadwave.fr/share/c/C123"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur le lien de partage</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un message "Ce contenu a été retiré pour violation des règles de la communauté"
<span style="color: #9E9E9E"><strong>Et</strong></span> je suis redirigé vers l'accueil de l'application
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune lecture n'est possible</p>
<hr />
<h2 id="9-plusieurs-contenus-supprimes-dans-lhistorique-recent">9. Plusieurs contenus supprimés dans l'historique récent</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai écouté les contenus suivants:</p>
<pre><code>| id | statut |
|---|---|
| C123 | supprimé |
| C456 | actif |
| C789 | supprimé |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> que je suis actuellement sur le contenu "C456"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie plusieurs fois sur "Précédent"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je ne peux pas revenir aux contenus "C123" ou "C789"
<span style="color: #9E9E9E"><strong>Et</strong></span> le système saute automatiquement les contenus supprimés
<span style="color: #9E9E9E"><strong>Et</strong></span> je reviens au dernier contenu actif disponible avant "C456"</p>
<hr />
<h2 id="10-consultation-detaillee-du-contenu-supprime-a-larret">10. Consultation détaillée du contenu supprimé à l'arrêt</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis à l'arrêt
<span style="color: #9E9E9E"><strong>Et</strong></span> que le contenu "C123" a été supprimé pendant ma session</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ouvre les détails de la notification de suppression</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux voir les informations suivantes:</p>
<pre><code>| information |
|---|
| Titre du contenu |
| Créateur |
| Raison de suppression |
| Date de suppression |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je peux signaler une erreur de modération si je pense qu'elle est injustifiée</p>
<hr />
<h2 id="11-pas-dimpact-sur-les-jauges-dinteret-lors-de-la-suppression">11. Pas d'impact sur les jauges d'intérêt lors de la suppression</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai écouté le contenu "C123" pendant 80 secondes (66%)
<span style="color: #9E9E9E"><strong>Et</strong></span> que mes jauges d'intérêt ont été mises à jour pendant l'écoute</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le contenu est supprimé après mon écoute</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les modifications de mes jauges d'intérêt sont conservées
<span style="color: #9E9E9E"><strong>Et</strong></span> l'écoute déjà effectuée reste comptabilisée
<span style="color: #9E9E9E"><strong>Et</strong></span> seules les futures écoutes de ce contenu sont bloquées</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="mode-degrade-sans-geolocalisation">Mode dégradé sans géolocalisation</h1>
<p><strong>19 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur connecté
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai refusé ou désactivé l'accès à la géolocalisation</p>
</blockquote>
<h2 id="1-types-de-contenu-disponibles-sans-geolocalisation">1. Types de contenu disponibles sans géolocalisation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la géolocalisation est désactivée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ouvre l'application</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les types de contenu suivants sont disponibles:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="2-popup-dinformation-au-premier-lancement-sans-gps">2. Popup d'information au premier lancement sans GPS</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que c'est mon premier lancement de l'application
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai refusé l'accès à la géolocalisation</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'application détecte que le GPS est désactivé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une popup s'affiche avec le message:
<span style="color: #9E9E9E"><strong>Et</strong></span> la popup contient les boutons suivants:</p>
<pre><code>| bouton | action |
|---|---|
| Activer | Redirection vers paramètres OS |
| Continuer sans | Ferme popup et lance en mode dégradé |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> une checkbox "Ne plus me demander" est disponible</p>
<hr />
<h2 id="3-popup-non-affichee-si-case-ne-plus-me-demander-cochee">3. Popup non affichée si case "Ne plus me demander" cochée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai déjà vu la popup de géolocalisation
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai coché "Ne plus me demander"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je lance l'application avec le GPS désactivé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la popup de géolocalisation ne s'affiche pas
<span style="color: #9E9E9E"><strong>Et</strong></span> l'application démarre directement en mode dégradé
<span style="color: #9E9E9E"><strong>Et</strong></span> le banner permanent de rappel s'affiche</p>
<hr />
<h2 id="4-redirection-vers-parametres-os-lors-du-clic-sur-activer">4. Redirection vers paramètres OS lors du clic sur "Activer"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la popup de géolocalisation est affichée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Activer"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je suis redirigé vers les paramètres de géolocalisation de mon OS
<span style="color: #9E9E9E"><strong>Et</strong></span> sur iOS, j'arrive dans "Réglages &gt; RoadWave &gt; Localisation"
<span style="color: #9E9E9E"><strong>Et</strong></span> sur Android, j'arrive dans "Paramètres &gt; Applications &gt; RoadWave &gt; Autorisations &gt; Position"</p>
<hr />
<h2 id="5-banner-de-rappel-permanent-sans-gps">5. Banner de rappel permanent sans GPS</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai cliqué sur "Continuer sans" géolocalisation</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'application s'affiche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un bandeau s'affiche en haut de l'écran
<span style="color: #9E9E9E"><strong>Et</strong></span> le bandeau contient le texte: "Mode limité : géolocalisation désactivée. [Activer]"
<span style="color: #9E9E9E"><strong>Et</strong></span> le bandeau a un fond de couleur avertissement (jaune/orange)
<span style="color: #9E9E9E"><strong>Et</strong></span> le bandeau n'est pas intrusif mais reste visible
<span style="color: #9E9E9E"><strong>Et</strong></span> le bandeau reste affiché sur toutes les pages de l'application</p>
<hr />
<h2 id="6-clic-sur-le-bouton-activer-du-banner">6. Clic sur le bouton "Activer" du banner</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le banner "Mode limité" est affiché</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur le lien "[Activer]" dans le banner</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je suis redirigé vers les paramètres de géolocalisation de mon OS</p>
<hr />
<h2 id="7-disparition-du-banner-apres-activation-gps">7. Disparition du banner après activation GPS</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le banner "Mode limité" est affiché
<span style="color: #9E9E9E"><strong>Et</strong></span> que je reviens dans l'application après avoir activé le GPS dans les paramètres</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'application détecte que la géolocalisation est maintenant active</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le banner disparaît automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> l'application bascule en mode normal avec contenu géolocalisé
<span style="color: #9E9E9E"><strong>Et</strong></span> un toast de confirmation s'affiche: "Géolocalisation activée"</p>
<hr />
<h2 id="8-lecture-de-contenu-national-sans-gps">8. Lecture de contenu national sans GPS</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la géolocalisation est désactivée
<span style="color: #9E9E9E"><strong>Et</strong></span> que du contenu national existe (actualités Le Monde, podcasts génériques)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je lance la lecture</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux écouter le contenu national sans restriction
<span style="color: #9E9E9E"><strong>Et</strong></span> l'algorithme de recommandation se base uniquement sur:</p>
<pre><code>| critère |
|---|
| Mes centres d'intérêt |
| Mon historique d'écoute |
| Popularité générale |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> la proximité géographique n'est pas prise en compte</p>
<hr />
<h2 id="9-lecture-de-contenu-telecharge-sans-gps">9. Lecture de contenu téléchargé sans GPS</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la géolocalisation est désactivée
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai téléchargé 30 contenus quand j'avais le GPS activé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à mes contenus téléchargés</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux lire tous mes contenus téléchargés normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> les contenus géolocalisés téléchargés restent accessibles
<span style="color: #9E9E9E"><strong>Et</strong></span> le filtre géographique n'est pas appliqué pour les contenus offline</p>
<hr />
<h2 id="10-contenu-neutre-geographiquement-disponible">10. Contenu "Neutre" géographiquement disponible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la géolocalisation est désactivée
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un créateur a publié du contenu avec la classification géographique "Neutre"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je recherche du contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les contenus "Neutre" sont inclus dans les résultats
<span style="color: #9E9E9E"><strong>Et</strong></span> ils sont mélangés avec le contenu national
<span style="color: #9E9E9E"><strong>Et</strong></span> l'algorithme les priorise selon mes centres d'intérêt</p>
<hr />
<h2 id="11-audio-guides-inaccessibles-sans-gps">11. Audio-guides inaccessibles sans GPS</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la géolocalisation est désactivée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je recherche un audio-guide spécifique</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les audio-guides apparaissent dans les résultats de recherche
<span style="color: #F44336"><strong>Mais</strong></span> un badge "GPS requis" est affiché sur chaque audio-guide
<span style="color: #9E9E9E"><strong>Et</strong></span> quand je clique sur un audio-guide, un message s'affiche:
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux choisir "Activer" ou "Annuler"</p>
<hr />
<h2 id="12-notifications-push-geo-declenchees-desactivees">12. Notifications push géo-déclenchées désactivées</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la géolocalisation est désactivée
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis abonné à un créateur qui diffuse du contenu géolocalisé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur publie un nouveau contenu géolocalisé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je ne reçois pas de notification push géo-déclenchée
<span style="color: #F44336"><strong>Mais</strong></span> je reçois une notification push standard (non géo-déclenchée) si le créateur publie du contenu national
<span style="color: #9E9E9E"><strong>Et</strong></span> la notification précise: "Nouveau contenu national de [Créateur]"</p>
<hr />
<h2 id="13-contenu-geolocalise-non-propose-dans-le-feed">13. Contenu géolocalisé non proposé dans le feed</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la géolocalisation est désactivée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système génère mon feed de contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucun contenu "Ancré" ou "Contextuel" n'est inclus
<span style="color: #9E9E9E"><strong>Et</strong></span> seuls les contenus "Neutre" et "National" sont proposés
<span style="color: #9E9E9E"><strong>Et</strong></span> mon feed contient au minimum 20 contenus disponibles</p>
<hr />
<h2 id="14-application-fonctionnelle-sans-gps-pas-de-blocage">14. Application fonctionnelle sans GPS (pas de blocage)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la géolocalisation est désactivée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'utilise l'application</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je ne suis jamais bloqué par un écran "GPS requis"
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes les fonctionnalités non-géolocalisées restent accessibles:</p>
<pre><code>| fonctionnalité |
|---|
| Écoute contenu national |
| Gestion profil |
| Abonnements créateurs |
| Recherche textuelle |
| Historique d'écoute |
| Paramètres |
| Mode offline |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je peux créer et publier du contenu national</p>
<hr />
<h2 id="15-respect-du-choix-utilisateur-de-ne-pas-activer-gps">15. Respect du choix utilisateur de ne pas activer GPS</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai coché "Ne plus me demander" pour la géolocalisation</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'utilise l'application pendant plusieurs semaines</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la popup de demande GPS ne s'affiche plus jamais automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> seul le banner permanent reste affiché
<span style="color: #9E9E9E"><strong>Et</strong></span> l'application ne force jamais l'activation du GPS</p>
<hr />
<h2 id="16-bascule-automatique-en-mode-normal-apres-activation-gps">16. Bascule automatique en mode normal après activation GPS</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'utilise l'application en mode dégradé depuis 1 semaine
<span style="color: #9E9E9E"><strong>Et</strong></span> que je décide d'activer la géolocalisation</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'application détecte que le GPS est maintenant actif</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le mode dégradé est désactivé automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> le banner "Mode limité" disparaît
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu géolocalisé devient disponible immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> mon feed se rafraîchit avec du contenu local pertinent
<span style="color: #9E9E9E"><strong>Et</strong></span> un toast de confirmation s'affiche: "Géolocalisation activée - Contenu local disponible"</p>
<hr />
<h2 id="17-demande-de-permission-gps-lors-de-lutilisation-dune-fonctionnalite-geo">17. Demande de permission GPS lors de l'utilisation d'une fonctionnalité géo</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la géolocalisation est désactivée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie d'accéder à une fonctionnalité nécessitant le GPS (ex: audio-guide)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une popup contextuelle s'affiche:
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux accepter ou refuser
<span style="color: #9E9E9E"><strong>Et</strong></span> si j'accepte, je suis redirigé vers les paramètres OS
<span style="color: #9E9E9E"><strong>Et</strong></span> si je refuse, je reste en mode dégradé sans message d'erreur répétitif</p>
<hr />
<h2 id="18-statistiques-de-contenu-local-disponible-non-affiche">18. Statistiques de contenu local disponible non affiché</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la géolocalisation est désactivée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je navigue dans l'application</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le banner peut afficher occasionnellement:
<span style="color: #9E9E9E"><strong>Et</strong></span> ce message incitatif change tous les 3 jours
<span style="color: #9E9E9E"><strong>Et</strong></span> il reste non intrusif (pas de popup, juste le banner)</p>
<hr />
<h2 id="19-onboarding-different-pour-utilisateurs-sans-gps">19. Onboarding différent pour utilisateurs sans GPS</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que c'est ma première utilisation de RoadWave
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai refusé la géolocalisation</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'onboarding se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un écran explicatif s'affiche:
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux continuer avec un bouton "Compris"</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="gestion-de-la-perte-de-reseau-et-buffering-adaptatif">Gestion de la perte de réseau et buffering adaptatif</h1>
<p><strong>17 scénarios</strong> (16 standards, 1 plan)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur connecté
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis en mode écoute
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un contenu est en cours de lecture</p>
</blockquote>
<h2 id="1-plan-parametres-de-buffer-selon-le-type-de-reseau">1. 📋 Plan: Paramètres de buffer selon le type de réseau</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis connecté en "<type_reseau>"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système initialise le buffer audio</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le buffer minimum est de <buffer_min> secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> le buffer cible est de <buffer_cible> secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> le buffer maximum est de <buffer_max> secondes</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>type_reseau</th>
<th>buffer_min</th>
<th>buffer_cible</th>
<th>buffer_max</th>
</tr>
</thead>
<tbody>
<tr>
<td>WiFi</td>
<td>5</td>
<td>30</td>
<td>120</td>
</tr>
<tr>
<td>4G</td>
<td>10</td>
<td>45</td>
<td>120</td>
</tr>
<tr>
<td>5G</td>
<td>10</td>
<td>45</td>
<td>120</td>
</tr>
<tr>
<td>3G</td>
<td>30</td>
<td>90</td>
<td>300</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="2-connexion-instable-avec-latence-elevee-aucun-message-immediat">2. Connexion instable avec latence élevée - aucun message immédiat</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis connecté en 4G
<span style="color: #9E9E9E"><strong>Et</strong></span> que le buffer contient 45 secondes de contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la latence réseau dépasse 500ms</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucun message n'est affiché immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> la lecture continue normalement sur le buffer
<span style="color: #9E9E9E"><strong>Et</strong></span> le système tente de continuer le téléchargement en arrière-plan</p>
<hr />
<h2 id="3-connexion-instable-pendant-plus-de-10-secondes-toast-discret">3. Connexion instable pendant plus de 10 secondes - toast discret</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis connecté en 4G
<span style="color: #9E9E9E"><strong>Et</strong></span> que la latence réseau dépasse 500ms depuis 10 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte la latence prolongée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un toast discret s'affiche: "Connexion instable"
<span style="color: #9E9E9E"><strong>Et</strong></span> le toast disparaît automatiquement après 3 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> la lecture continue normalement</p>
<hr />
<h2 id="4-perte-totale-de-reseau-lecture-sur-buffer">4. Perte totale de réseau - lecture sur buffer</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis connecté en WiFi
<span style="color: #9E9E9E"><strong>Et</strong></span> que le buffer contient 30 secondes de contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je perds totalement la connexion réseau</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la lecture continue sur le buffer disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> un toast s'affiche: "Hors ligne, lecture sur buffer (30s restantes)"
<span style="color: #9E9E9E"><strong>Et</strong></span> un compte à rebours du temps de buffer restant est visible</p>
<hr />
<h2 id="5-buffer-qui-sepuise-pendant-la-perte-reseau">5. Buffer qui s'épuise pendant la perte réseau</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis hors ligne
<span style="color: #9E9E9E"><strong>Et</strong></span> que le buffer contient 30 secondes de contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le contenu continue de jouer</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le compte à rebours diminue en temps réel
<span style="color: #9E9E9E"><strong>Et</strong></span> le toast affiche "Hors ligne, lecture sur buffer (15s restantes)" après 15 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> le toast affiche "Hors ligne, lecture sur buffer (5s restantes)" après 25 secondes</p>
<hr />
<h2 id="6-pause-automatique-apres-epuisement-du-buffer">6. Pause automatique après épuisement du buffer</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis hors ligne depuis 30 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> que le buffer est complètement épuisé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il n'y a plus de contenu audio à lire</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la lecture se met en pause automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> un overlay s'affiche: "Connexion perdue. Reconnexion en cours..."
<span style="color: #9E9E9E"><strong>Et</strong></span> le système tente de se reconnecter automatiquement</p>
<hr />
<h2 id="7-tentatives-de-reconnexion-automatique">7. Tentatives de reconnexion automatique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la lecture est en pause suite à l'épuisement du buffer</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système tente de se reconnecter</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une tentative de reconnexion est effectuée toutes les 5 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> un maximum de 6 tentatives sont effectuées (30 secondes au total)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'overlay affiche "Tentative de reconnexion... (X/6)"</p>
<hr />
<h2 id="8-proposition-du-mode-offline-apres-30-secondes-dechec">8. Proposition du mode offline après 30 secondes d'échec</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 6 tentatives de reconnexion ont échoué
<span style="color: #9E9E9E"><strong>Et</strong></span> que cela fait 30 secondes que je suis déconnecté</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la 6ème tentative échoue</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une popup s'affiche: "Voulez-vous continuer avec vos contenus téléchargés ?"
<span style="color: #9E9E9E"><strong>Et</strong></span> la popup contient deux boutons:</p>
<pre><code>| bouton | action |
|---|---|
| Réessayer | Nouvelle série de 6 tentatives |
| Mode offline | Bascule sur contenus téléchargés |
</code></pre>
<hr />
<h2 id="9-basculement-reussi-vers-le-mode-offline">9. Basculement réussi vers le mode offline</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la popup de mode offline est affichée
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai téléchargé 20 contenus dans ma zone géographique</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Mode offline"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système bascule sur les contenus téléchargés
<span style="color: #9E9E9E"><strong>Et</strong></span> un nouveau contenu téléchargé démarre automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> un bandeau permanent indique "Mode hors ligne - Contenus téléchargés"</p>
<hr />
<h2 id="10-aucun-contenu-telecharge-disponible">10. Aucun contenu téléchargé disponible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la popup de mode offline est affichée
<span style="color: #9E9E9E"><strong>Et</strong></span> que je n'ai aucun contenu téléchargé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Mode offline"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message s'affiche: "Aucun contenu téléchargé disponible"
<span style="color: #9E9E9E"><strong>Et</strong></span> je suis invité à me connecter en WiFi pour télécharger du contenu
<span style="color: #9E9E9E"><strong>Et</strong></span> le bouton "Réessayer" reste la seule option</p>
<hr />
<h2 id="11-reprise-automatique-apres-reconnexion">11. Reprise automatique après reconnexion</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la lecture est en pause depuis 15 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'étais à 02:35 du contenu en cours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la connexion réseau est rétablie</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la lecture reprend automatiquement au point d'arrêt exact (02:35)
<span style="color: #9E9E9E"><strong>Et</strong></span> un toast s'affiche: "Connexion rétablie"
<span style="color: #9E9E9E"><strong>Et</strong></span> le toast disparaît après 3 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> le buffer se remplit progressivement selon le type de réseau</p>
<hr />
<h2 id="12-reconnexion-avec-changement-de-type-de-reseau">12. Reconnexion avec changement de type de réseau</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'étais connecté en WiFi
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai perdu la connexion</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me reconnecte en 4G</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système ajuste automatiquement les paramètres de buffer
<span style="color: #9E9E9E"><strong>Et</strong></span> le buffer minimum passe de 5s à 10s
<span style="color: #9E9E9E"><strong>Et</strong></span> le buffer cible passe de 30s à 45s
<span style="color: #9E9E9E"><strong>Et</strong></span> la lecture reprend normalement</p>
<hr />
<h2 id="13-passage-dans-un-tunnel-avec-perte-de-signal">13. Passage dans un tunnel avec perte de signal</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je conduis à 90 km/h sur autoroute
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis connecté en 4G avec un buffer de 45 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'entre dans un tunnel et perds le signal</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la lecture continue sur le buffer pendant 45 secondes maximum
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune notification n'est affichée pendant les 10 premières secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> un toast discret s'affiche après 10 secondes: "Connexion instable"</p>
<hr />
<h2 id="14-sortie-du-tunnel-avant-epuisement-du-buffer">14. Sortie du tunnel avant épuisement du buffer</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis dans un tunnel depuis 30 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il reste 15 secondes de buffer</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sors du tunnel et récupère le signal 4G</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la lecture continue sans interruption
<span style="color: #9E9E9E"><strong>Et</strong></span> le buffer se remplit à nouveau
<span style="color: #9E9E9E"><strong>Et</strong></span> un toast s'affiche: "Connexion rétablie"</p>
<hr />
<h2 id="15-changement-de-cellule-4g-pendant-la-lecture">15. Changement de cellule 4G pendant la lecture</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je conduis et change de cellule mobile toutes les 5-10 minutes
<span style="color: #9E9E9E"><strong>Et</strong></span> que le buffer contient 45 secondes de contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un handoff de cellule se produit</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la lecture continue sans interruption grâce au buffer
<span style="color: #9E9E9E"><strong>Et</strong></span> la connexion à la nouvelle cellule se fait de manière transparente
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune notification n'est affichée si le handoff réussit en moins de 5 secondes</p>
<hr />
<h2 id="16-telechargement-preventif-en-wifi-avant-trajet">16. Téléchargement préventif en WiFi avant trajet</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis connecté en WiFi
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai activé le téléchargement automatique</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte que je suis à l'arrêt en WiFi</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système me propose de télécharger du contenu pour mon trajet
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux sélectionner une zone géographique à télécharger
<span style="color: #9E9E9E"><strong>Et</strong></span> le téléchargement se fait en arrière-plan</p>
<hr />
<h2 id="17-tracking-des-evenements-de-perte-reseau-pour-amelioration">17. Tracking des événements de perte réseau pour amélioration</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je perds la connexion réseau</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'événement de perte est détecté</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système enregistre les métriques suivantes:</p>
<pre><code>| métrique |
|---|
| Type de réseau avant perte |
| Durée de la coupure |
| Buffer disponible |
| Position GPS approximative |
| Heure de la journée |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> ces métriques sont anonymisées et envoyées en batch lors de la prochaine connexion WiFi
<span style="color: #9E9E9E"><strong>Et</strong></span> les données servent à améliorer les paramètres de buffer</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="tests-bdd-documentation-des-fonctionnalites">Tests BDD - Documentation des fonctionnalités</h1>
<p>Cette documentation est générée automatiquement à partir des fichiers Gherkin (<code>.feature</code>).</p>
<h2 id="vue-densemble">Vue d'ensemble</h2>
<table>
<thead>
<tr>
<th>Métrique</th>
<th>Valeur</th>
</tr>
</thead>
<tbody>
<tr>
<td>Fonctionnalités</td>
<td><strong>83</strong></td>
</tr>
<tr>
<td>Scénarios</td>
<td><strong>2112</strong></td>
</tr>
<tr>
<td>Domaines métier</td>
<td><strong>18</strong></td>
</tr>
</tbody>
</table>
<hr />
<h2 id="abonnements">🔔 Abonnements</h2>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th style="text-align: center;">Scénarios</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="#abonnements/audio-guides-pieton">Audio-guides multi-séquences pour piétons</a></td>
<td style="text-align: center;">29</td>
</tr>
<tr>
<td><a href="#abonnements/impact-algorithme">Impact des abonnements sur l'algorithme</a></td>
<td style="text-align: center;">16</td>
</tr>
<tr>
<td><a href="#abonnements/limites-desabonnement">Limites d'abonnements et désabonnement</a></td>
<td style="text-align: center;">27</td>
</tr>
<tr>
<td><a href="#abonnements/notifications-contextuelles">Notifications contextuelles selon le mode de déplacement</a></td>
<td style="text-align: center;">28</td>
</tr>
</tbody>
</table>
<p><em>4 fonctionnalités • 100 scénarios</em></p>
<h2 id="audio-guides">🎧 Audio Guides</h2>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th style="text-align: center;">Scénarios</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="#audio-guides/mode-pieton">Audio-guide mode piéton (navigation manuelle)</a></td>
<td style="text-align: center;">29</td>
</tr>
<tr>
<td><a href="#audio-guides/mode-voiture">Audio-guide mode voiture (GPS automatique)</a></td>
<td style="text-align: center;">45</td>
</tr>
<tr>
<td><a href="#audio-guides/premium-monetisation">Audio-guides Premium et monétisation</a></td>
<td style="text-align: center;">31</td>
</tr>
<tr>
<td><a href="#audio-guides/modes-velo-transport">Audio-guides modes vélo et transport</a></td>
<td style="text-align: center;">27</td>
</tr>
<tr>
<td><a href="#audio-guides/creation-audio-guide">Création d'audio-guide multi-séquences</a></td>
<td style="text-align: center;">35</td>
</tr>
<tr>
<td><a href="#audio-guides/integration-fonctionnalites">Intégration audio-guides avec autres fonctionnalités</a></td>
<td style="text-align: center;">39</td>
</tr>
<tr>
<td><a href="#audio-guides/progression-sauvegarde">Sauvegarde et reprise de progression audio-guide</a></td>
<td style="text-align: center;">32</td>
</tr>
</tbody>
</table>
<p><em>7 fonctionnalités • 238 scénarios</em></p>
<h2 id="authentication">🔐 Authentication</h2>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th style="text-align: center;">Scénarios</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="#authentication/two-factor-authentication">Authentification à deux facteurs (2FA)</a></td>
<td style="text-align: center;">16</td>
</tr>
<tr>
<td><a href="#authentication/classification-age">Classification des contenus par âge</a></td>
<td style="text-align: center;">13</td>
</tr>
<tr>
<td><a href="#authentication/connexion">Connexion utilisateur</a></td>
<td style="text-align: center;">11</td>
</tr>
<tr>
<td><a href="#authentication/sessions-tokens">Gestion des sessions et tokens</a></td>
<td style="text-align: center;">13</td>
</tr>
<tr>
<td><a href="#authentication/inscription">Inscription utilisateur</a></td>
<td style="text-align: center;">15</td>
</tr>
<tr>
<td><a href="#authentication/recuperation-compte">Récupération de compte</a></td>
<td style="text-align: center;">14</td>
</tr>
<tr>
<td><a href="#authentication/verification-email">Vérification d'email</a></td>
<td style="text-align: center;">10</td>
</tr>
</tbody>
</table>
<p><em>7 fonctionnalités • 92 scénarios</em></p>
<h2 id="content-creation">🎨 Content Creation</h2>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th style="text-align: center;">Scénarios</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="#content-creation/modification-suppression">Modification et suppression de contenu</a></td>
<td style="text-align: center;">30</td>
</tr>
<tr>
<td><a href="#content-creation/metadonnees-publication">Métadonnées et publication de contenu</a></td>
<td style="text-align: center;">34</td>
</tr>
<tr>
<td><a href="#content-creation/upload-encodage">Upload et encodage de contenu audio</a></td>
<td style="text-align: center;">29</td>
</tr>
<tr>
<td><a href="#content-creation/validation-premiers-contenus">Validation des 3 premiers contenus</a></td>
<td style="text-align: center;">30</td>
</tr>
</tbody>
</table>
<p><em>4 fonctionnalités • 123 scénarios</em></p>
<h2 id="error-handling">⚠️ Error Handling</h2>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th style="text-align: center;">Scénarios</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="#error-handling/contenu-supprime-pendant-ecoute">Gestion d'un contenu supprimé pendant l'écoute</a></td>
<td style="text-align: center;">11</td>
</tr>
<tr>
<td><a href="#error-handling/perte-reseau">Gestion de la perte de réseau et buffering adaptatif</a></td>
<td style="text-align: center;">17</td>
</tr>
<tr>
<td><a href="#error-handling/geolocalisation-desactivee">Mode dégradé sans géolocalisation</a></td>
<td style="text-align: center;">19</td>
</tr>
<tr>
<td><a href="#error-handling/aucun-contenu-disponible">Élargissement automatique de zone quand aucun contenu n'est disponible</a></td>
<td style="text-align: center;">9</td>
</tr>
</tbody>
</table>
<p><em>4 fonctionnalités • 56 scénarios</em></p>
<h2 id="interest-gauges">📊 Interest Gauges</h2>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th style="text-align: center;">Scénarios</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="#interest-gauges/jauge-initiale">Jauge initiale et cold start</a></td>
<td style="text-align: center;">15</td>
</tr>
<tr>
<td><a href="#interest-gauges/degradation-temporelle">Pas de dégradation temporelle des jauges</a></td>
<td style="text-align: center;">16</td>
</tr>
<tr>
<td><a href="#interest-gauges/evolution-jauges">Évolution des jauges d'intérêt</a></td>
<td style="text-align: center;">21</td>
</tr>
</tbody>
</table>
<p><em>3 fonctionnalités • 52 scénarios</em></p>
<h2 id="mode-offline">📴 Mode Offline</h2>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th style="text-align: center;">Scénarios</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="#mode-offline/synchronisation-actions">Synchronisation actions offline</a></td>
<td style="text-align: center;">45</td>
</tr>
<tr>
<td><a href="#mode-offline/telechargement">Téléchargement de contenus offline</a></td>
<td style="text-align: center;">49</td>
</tr>
<tr>
<td><a href="#mode-offline/validite-renouvellement">Validité et renouvellement contenus offline</a></td>
<td style="text-align: center;">38</td>
</tr>
</tbody>
</table>
<p><em>3 fonctionnalités • 132 scénarios</em></p>
<h2 id="moderation_1">🛡️ Moderation</h2>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th style="text-align: center;">Scénarios</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="#moderation/moderation-preventive">Modération préventive</a></td>
<td style="text-align: center;">22</td>
</tr>
<tr>
<td><a href="#moderation/sanctions-notifications">Sanctions et notifications de modération</a></td>
<td style="text-align: center;">27</td>
</tr>
<tr>
<td><a href="#moderation/signalement">Signalement de contenu inapproprié</a></td>
<td style="text-align: center;">23</td>
</tr>
<tr>
<td><a href="#moderation/traitement-signalements">Traitement des signalements par l'IA et les modérateurs</a></td>
<td style="text-align: center;">25</td>
</tr>
</tbody>
</table>
<p><em>4 fonctionnalités • 97 scénarios</em></p>
<h2 id="monetisation">💰 Monetisation</h2>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th style="text-align: center;">Scénarios</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="#monetisation/conditions-activation">Conditions d'activation de la monétisation</a></td>
<td style="text-align: center;">28</td>
</tr>
<tr>
<td><a href="#monetisation/contenus-premium-exclusifs">Contenus Premium exclusifs</a></td>
<td style="text-align: center;">34</td>
</tr>
<tr>
<td><a href="#monetisation/desactivation-suspension">Désactivation et suspension monétisation</a></td>
<td style="text-align: center;">35</td>
</tr>
<tr>
<td><a href="#monetisation/kyc-inscription">KYC et inscription à la monétisation</a></td>
<td style="text-align: center;">37</td>
</tr>
<tr>
<td><a href="#monetisation/obligations-fiscales">Obligations fiscales</a></td>
<td style="text-align: center;">30</td>
</tr>
<tr>
<td><a href="#monetisation/paiement-createurs">Paiement des créateurs</a></td>
<td style="text-align: center;">35</td>
</tr>
<tr>
<td><a href="#monetisation/sources-revenus">Sources de revenus créateurs</a></td>
<td style="text-align: center;">34</td>
</tr>
</tbody>
</table>
<p><em>7 fonctionnalités • 233 scénarios</em></p>
<h2 id="navigation">🧭 Navigation</h2>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th style="text-align: center;">Scénarios</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="#navigation/actions-complementaires">Actions complémentaires à l'arrêt</a></td>
<td style="text-align: center;">23</td>
</tr>
<tr>
<td><a href="#navigation/commande-precedent">Commande "Précédent"</a></td>
<td style="text-align: center;">19</td>
</tr>
<tr>
<td><a href="#navigation/commandes-volant">Commandes au volant et interactions simplifiées</a></td>
<td style="text-align: center;">21</td>
</tr>
<tr>
<td><a href="#navigation/commandes-vocales">Commandes vocales CarPlay et Android Auto</a></td>
<td style="text-align: center;">25</td>
</tr>
<tr>
<td><a href="#navigation/file-attente-suivant">File d'attente et commande "Suivant"</a></td>
<td style="text-align: center;">20</td>
</tr>
<tr>
<td><a href="#navigation/lecture-enchainement">Lecture en boucle et enchaînement automatique</a></td>
<td style="text-align: center;">27</td>
</tr>
</tbody>
</table>
<p><em>6 fonctionnalités • 135 scénarios</em></p>
<h2 id="partage">🔗 Partage</h2>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th style="text-align: center;">Scénarios</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="#partage/partage-contenu">Partage de contenu</a></td>
<td style="text-align: center;">22</td>
</tr>
</tbody>
</table>
<p><em>1 fonctionnalités • 22 scénarios</em></p>
<h2 id="premium">⭐ Premium</h2>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th style="text-align: center;">Scénarios</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="#premium/avantages-premium">Avantages Premium</a></td>
<td style="text-align: center;">37</td>
</tr>
<tr>
<td><a href="#premium/gestion-abonnement">Gestion abonnement Premium</a></td>
<td style="text-align: center;">41</td>
</tr>
<tr>
<td><a href="#premium/multi-devices-detection">Multi-devices et détection simultanée</a></td>
<td style="text-align: center;">30</td>
</tr>
<tr>
<td><a href="#premium/offre-tarification">Offre et tarification Premium</a></td>
<td style="text-align: center;">31</td>
</tr>
</tbody>
</table>
<p><em>4 fonctionnalités • 139 scénarios</em></p>
<h2 id="profil">👤 Profil</h2>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th style="text-align: center;">Scénarios</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="#profil/profil-createur">Profil créateur</a></td>
<td style="text-align: center;">31</td>
</tr>
</tbody>
</table>
<p><em>1 fonctionnalités • 31 scénarios</em></p>
<h2 id="publicites_1">📢 Publicites</h2>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th style="text-align: center;">Scénarios</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="#publicites/caracteristiques-pub">Caractéristiques et facturation des publicités</a></td>
<td style="text-align: center;">32</td>
</tr>
<tr>
<td><a href="#publicites/campagnes-publicitaires">Création de campagnes publicitaires</a></td>
<td style="text-align: center;">30</td>
</tr>
<tr>
<td><a href="#publicites/gestion-budget-pub">Gestion du budget et alertes publicitaires</a></td>
<td style="text-align: center;">30</td>
</tr>
<tr>
<td><a href="#publicites/insertion-frequence-pub">Insertion et fréquence des publicités</a></td>
<td style="text-align: center;">31</td>
</tr>
<tr>
<td><a href="#publicites/metriques-engagement-pub">Métriques d'engagement et dashboard publicitaire</a></td>
<td style="text-align: center;">27</td>
</tr>
<tr>
<td><a href="#publicites/validation-moderation-pub">Validation et modération des publicités</a></td>
<td style="text-align: center;">29</td>
</tr>
</tbody>
</table>
<p><em>6 fonctionnalités • 179 scénarios</em></p>
<h2 id="radio-live_1">📻 Radio Live</h2>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th style="text-align: center;">Scénarios</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="#radio-live/architecture-technique-live">Architecture technique radio live</a></td>
<td style="text-align: center;">24</td>
</tr>
<tr>
<td><a href="#radio-live/arret-live">Arrêt du live</a></td>
<td style="text-align: center;">19</td>
</tr>
<tr>
<td><a href="#radio-live/comportement-auditeur">Comportement auditeur pendant un live</a></td>
<td style="text-align: center;">27</td>
</tr>
<tr>
<td><a href="#radio-live/demarrage-live">Démarrage d'un live</a></td>
<td style="text-align: center;">20</td>
</tr>
</tbody>
</table>
<p><em>4 fonctionnalités • 90 scénarios</em></p>
<h2 id="recherche">🔍 Recherche</h2>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th style="text-align: center;">Scénarios</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="#recherche/recherche">Recherche de contenu</a></td>
<td style="text-align: center;">55</td>
</tr>
</tbody>
</table>
<p><em>1 fonctionnalités • 55 scénarios</em></p>
<h2 id="recommendation">🎯 Recommendation</h2>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th style="text-align: center;">Scénarios</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="#recommendation/classification-geo">Classification de géo-pertinence des contenus</a></td>
<td style="text-align: center;">10</td>
</tr>
<tr>
<td><a href="#recommendation/declenchement-geo">Contenus géolocalisés en mode voiture</a></td>
<td style="text-align: center;">36</td>
</tr>
<tr>
<td><a href="#recommendation/scoring-recommandation">Formule de scoring et recommandation</a></td>
<td style="text-align: center;">21</td>
</tr>
<tr>
<td><a href="#recommendation/historique-reproposition">Gestion de l'historique et reproposition</a></td>
<td style="text-align: center;">19</td>
</tr>
<tr>
<td><a href="#recommendation/contenu-politique">Gestion du contenu politique (MVP simplifié)</a></td>
<td style="text-align: center;">13</td>
</tr>
<tr>
<td><a href="#recommendation/mode-kids">Mode Kids pour utilisateurs 13-15 ans</a></td>
<td style="text-align: center;">15</td>
</tr>
<tr>
<td><a href="#recommendation/medias-traditionnels">Médias traditionnels sur RoadWave</a></td>
<td style="text-align: center;">21</td>
</tr>
<tr>
<td><a href="#recommendation/parametrabilite-admin">Paramétrabilité admin et A/B testing</a></td>
<td style="text-align: center;">20</td>
</tr>
<tr>
<td><a href="#recommendation/parametrabilite-utilisateur">Paramétrabilité utilisateur et profils</a></td>
<td style="text-align: center;">25</td>
</tr>
</tbody>
</table>
<p><em>9 fonctionnalités • 180 scénarios</em></p>
<h2 id="rgpd-compliance">🔒 Rgpd Compliance</h2>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th style="text-align: center;">Scénarios</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="#rgpd-compliance/anonymisation-gps">Anonymisation des données GPS après 24h</a></td>
<td style="text-align: center;">18</td>
</tr>
<tr>
<td><a href="#rgpd-compliance/compliance-administrative">Conformité administrative RGPD (Registre, Breach, DPO)</a></td>
<td style="text-align: center;">22</td>
</tr>
<tr>
<td><a href="#rgpd-compliance/cookies-analytics">Cookies et analytics avec Matomo self-hosted</a></td>
<td style="text-align: center;">20</td>
</tr>
<tr>
<td><a href="#rgpd-compliance/conservation-donnees">Durée de conservation des données et purge automatique</a></td>
<td style="text-align: center;">19</td>
</tr>
<tr>
<td><a href="#rgpd-compliance/consentement">Gestion du consentement RGPD</a></td>
<td style="text-align: center;">16</td>
</tr>
<tr>
<td><a href="#rgpd-compliance/mode-degrade-geoip">Mode dégradé avec GeoIP (sans GPS précis)</a></td>
<td style="text-align: center;">20</td>
</tr>
<tr>
<td><a href="#rgpd-compliance/portabilite-donnees">Portabilité des données (Article 20 RGPD)</a></td>
<td style="text-align: center;">22</td>
</tr>
<tr>
<td><a href="#rgpd-compliance/suppression-compte">Suppression du compte utilisateur (Article 17 RGPD - Droit à l'effacement)</a></td>
<td style="text-align: center;">21</td>
</tr>
</tbody>
</table>
<p><em>8 fonctionnalités • 158 scénarios</em></p>
<div style="page-break-after: always;"></div>
<h1 id="pas-de-degradation-temporelle-des-jauges">Pas de dégradation temporelle des jauges</h1>
<blockquote>
<p><em>En tant que système de recommandation</em>
<em>Je veux que les jauges n'évoluent que par les actions utilisateur</em>
<em>Afin d'avoir un comportement prévisible et fiable</em></p>
</blockquote>
<p><strong>16 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un utilisateur est connecté</p>
</blockquote>
<h2 id="1-aucune-degradation-automatique-avec-le-temps">1. Aucune dégradation automatique avec le temps</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma jauge "Économie" est à 80%
<span style="color: #9E9E9E"><strong>Et</strong></span> que je n'écoute aucun contenu pendant 30 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me reconnecte après 30 jours</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma jauge "Économie" est toujours à 80%
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune dégradation temporelle n'a été appliquée</p>
<hr />
<h2 id="2-jauges-conservees-apres-6-mois-dinactivite">2. Jauges conservées après 6 mois d'inactivité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes jauges sont:</p>
<pre><code>| catégorie | niveau |
|---|---|
| Automobile | 75% |
| Voyage | 60% |
| Musique | 45% |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> que je pars en vacances pendant 6 mois sans utiliser l'app</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me reconnecte après 6 mois</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mes jauges sont exactement les mêmes:</p>
<pre><code>| catégorie | niveau |
|---|---|
| Automobile | 75% |
| Voyage | 60% |
| Musique | 45% |
</code></pre>
<hr />
<h2 id="3-evolution-naturelle-par-les-actions">3. Évolution naturelle par les actions</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'aimais "Économie" il y a 1 an (jauge 80%)
<span style="color: #9E9E9E"><strong>Et</strong></span> que depuis, je skip tous les contenus "Économie"
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai skippé 50 contenus "Économie" en 1 an</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma jauge "Économie" descend naturellement via les skips
<span style="color: #9E9E9E"><strong>Et</strong></span> atteint environ 55% (80% - 50 × 0.5% = 55%)
<span style="color: #9E9E9E"><strong>Et</strong></span> la dégradation vient des actions, pas du temps</p>
<hr />
<h2 id="4-pas-de-cron-job-de-degradation">4. Pas de cron job de dégradation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le système vérifie les jauges quotidiennement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un utilisateur n'a pas d'activité depuis 90 jours</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucun job de dégradation n'est exécuté
<span style="color: #9E9E9E"><strong>Et</strong></span> les jauges restent inchangées
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune ressource CPU n'est consommée pour la dégradation</p>
<hr />
<h2 id="5-comportement-previsible-apres-absence">5. Comportement prévisible après absence</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma jauge "Sport" était à 70%
<span style="color: #9E9E9E"><strong>Et</strong></span> que je n'utilise pas l'app pendant 1 an</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je reviens et demande des recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mes recommandations reflètent toujours mes goûts d'avant
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois du contenu "Sport" prioritaire
<span style="color: #9E9E9E"><strong>Et</strong></span> le comportement est cohérent et prévisible</p>
<hr />
<h2 id="6-reinitialiser-manuellement-mes-centres-dinteret">6. Réinitialiser manuellement mes centres d'intérêt</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je veux repartir de zéro</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je vais dans les paramètres
<span style="color: #9E9E9E"><strong>Et</strong></span> que je clique sur "Réinitialiser mes centres d'intérêt"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je confirme l'action</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> toutes mes jauges reviennent à 50%
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Vos centres d'intérêt ont été réinitialisés"</p>
<hr />
<h2 id="7-confirmation-avant-reinitialisation">7. Confirmation avant réinitialisation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis dans les paramètres</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Réinitialiser mes centres d'intérêt"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois un message de confirmation:</p>
<pre><code>| titre | Êtes-vous sûr ? |
|---|---|
| message | Cette action remettra toutes vos jauges à 50% |
| actions | Confirmer / Annuler |
</code></pre>
<hr />
<h2 id="8-annuler-la-reinitialisation">8. Annuler la réinitialisation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai cliqué sur "Réinitialiser mes centres d'intérêt"
<span style="color: #9E9E9E"><strong>Et</strong></span> que la confirmation est affichée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Annuler"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mes jauges ne sont pas modifiées
<span style="color: #9E9E9E"><strong>Et</strong></span> je reviens aux paramètres</p>
<hr />
<h2 id="9-raison-de-reinitialisation-changement-de-vie">9. Raison de réinitialisation - changement de vie</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'utilisais RoadWave pour mes trajets professionnels
<span style="color: #9E9E9E"><strong>Et</strong></span> que mes jauges reflétaient "Économie" (85%) et "Technologie" (75%)
<span style="color: #9E9E9E"><strong>Et</strong></span> que je change de vie et deviens musicien</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je réinitialise mes centres d'intérêt</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux repartir avec toutes les jauges à 50%
<span style="color: #9E9E9E"><strong>Et</strong></span> découvrir du contenu "Musique" et "Culture" sans biais</p>
<hr />
<h2 id="10-pas-de-suggestion-automatique-de-reinitialisation">10. Pas de suggestion automatique de réinitialisation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je n'ai pas utilisé l'app depuis 1 an</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me reconnecte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune suggestion de réinitialisation n'est affichée
<span style="color: #9E9E9E"><strong>Et</strong></span> mes jauges sont conservées telles quelles
<span style="color: #9E9E9E"><strong>Et</strong></span> je garde le contrôle total</p>
<hr />
<h2 id="11-historique-conserve-apres-reinitialisation">11. Historique conservé après réinitialisation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai écouté 500 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je réinitialise mes centres d'intérêt</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mes jauges reviennent à 50%
<span style="color: #F44336"><strong>Mais</strong></span> mon historique d'écoute est conservé
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux toujours consulter mes anciens contenus écoutés</p>
<hr />
<h2 id="12-evolution-future-basee-sur-nouvelles-actions">12. Évolution future basée sur nouvelles actions</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai réinitialisé mes jauges à 50%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute 5 contenus "Voyage" à &gt;80%</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma jauge "Voyage" monte à 60% (50% + 5 × 2%)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'algorithme recommence à apprendre mes nouvelles préférences</p>
<hr />
<h2 id="13-respect-de-lhistorique-utilisateur">13. Respect de l'historique utilisateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur aime "Cryptomonnaie" depuis 2 ans
<span style="color: #9E9E9E"><strong>Et</strong></span> que sa jauge est à 90%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> 2 ans s'écoulent sans dégradation temporelle</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> sa jauge reste à 90%
<span style="color: #9E9E9E"><strong>Et</strong></span> le système ne fait pas d'"oubli" artificiel</p>
<hr />
<h2 id="14-cout-infrastructure-zero">14. Coût infrastructure zéro</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'aucune dégradation temporelle n'existe</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système calcule les jauges</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucun calcul de date n'est nécessaire
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun batch nocturne ne tourne
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun bug de fuseau horaire ne peut survenir
<span style="color: #9E9E9E"><strong>Et</strong></span> le coût CPU est minimal</p>
<hr />
<h2 id="15-ux-previsible-jauge-actions">15. UX prévisible - jauge = actions</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur consulte sa jauge "Sport" à 65%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il se demande pourquoi elle est à 65%</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il peut retracer ses actions:</p>
<pre><code>| action | impact |
|---|---|
| 10 likes automatiques | +10% |
| 3 abonnements Sport | +15% |
| 5 skips de contenu non-Sport | 0% |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> il comprend que c'est le reflet exact de ses actions
<span style="color: #9E9E9E"><strong>Et</strong></span> il n'y a pas de mystère ou automatisme caché</p>
<hr />
<h2 id="16-statistiques-affichees-sans-date">16. Statistiques affichées sans date</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte mes centres d'intérêt</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je vois mes jauges</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| information | affiché |
|---|---|
| Niveau actuel | ✅ 75% |
| Évolution depuis début | ✅ +25% |
| Dernière mise à jour | ❌ |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> aucune date n'est affichée car non pertinente
<span style="color: #9E9E9E"><strong>Et</strong></span> seules les actions comptent</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="evolution-des-jauges-dinteret">Évolution des jauges d'intérêt</h1>
<blockquote>
<p><em>En tant que système de recommandation</em>
<em>Je veux faire évoluer les jauges d'intérêt selon les actions utilisateur</em>
<em>Afin d'affiner les recommandations personnalisées</em></p>
</blockquote>
<p><strong>21 scénarios</strong> (20 standards, 1 plan)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un utilisateur est connecté</p>
</blockquote>
<h2 id="1-like-automatique-renforce-apres-ecoute-80">1. Like automatique renforcé après écoute ≥80%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu de 5 minutes est tagué "Automobile"
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma jauge "Automobile" est à 45%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute le contenu pendant 4 minutes 30 secondes (90%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un like automatique renforcé
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge "Automobile" augmente de 2%
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge "Automobile" est maintenant à 47%</p>
<hr />
<h2 id="2-like-automatique-renforce-exactement-a-80">2. Like automatique renforcé exactement à 80%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu de 10 minutes est tagué "Voyage"
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma jauge "Voyage" est à 60%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute le contenu pendant exactement 8 minutes (80%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un like automatique renforcé
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge "Voyage" augmente de 2%
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge "Voyage" est maintenant à 62%</p>
<hr />
<h2 id="3-like-automatique-standard-apres-ecoute-30-79">3. Like automatique standard après écoute 30-79%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu de 5 minutes est tagué "Automobile"
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma jauge "Automobile" est à 45%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute le contenu pendant 2 minutes 30 secondes (50%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un like automatique standard
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge "Automobile" augmente de 1%
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge "Automobile" est maintenant à 46%</p>
<hr />
<h2 id="4-like-automatique-standard-a-30-exactement">4. Like automatique standard à 30% exactement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu de 10 minutes est tagué "Musique"
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma jauge "Musique" est à 40%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute le contenu pendant exactement 3 minutes (30%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un like automatique standard
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge "Musique" augmente de 1%</p>
<hr />
<h2 id="5-like-automatique-standard-a-79">5. Like automatique standard à 79%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu de 10 minutes est tagué "Sport"
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma jauge "Sport" est à 55%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute le contenu pendant 7 minutes 54 secondes (79%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un like automatique standard
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge "Sport" augmente de 1%
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge "Sport" est maintenant à 56%</p>
<hr />
<h2 id="6-like-explicite-manuel-2">6. Like explicite (manuel) +2%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu est tagué "Économie"
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma jauge "Économie" est à 70%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute le contenu partiellement
<span style="color: #9E9E9E"><strong>Et</strong></span> que je clique manuellement sur le bouton "Like"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma jauge "Économie" augmente de 2%
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge "Économie" est maintenant à 72%</p>
<hr />
<h2 id="7-like-manuel-cumulable-avec-like-automatique">7. Like manuel cumulable avec like automatique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu de 5 minutes est tagué "Automobile"
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma jauge "Automobile" est à 45%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute le contenu pendant 2 minutes 30 secondes (50%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un like automatique standard (+1%)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique ensuite sur le bouton "Like"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma jauge augmente encore de 2% (like manuel)
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge "Automobile" a augmenté de 3% au total
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge "Automobile" est maintenant à 48%</p>
<hr />
<h2 id="8-abonnement-createur-impacte-tous-ses-tags">8. Abonnement créateur impacte tous ses tags</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur publie des contenus tagués "Automobile" et "Technologie"
<span style="color: #9E9E9E"><strong>Et</strong></span> que mes jauges sont:</p>
<pre><code>| catégorie | niveau |
|---|---|
| Automobile | 50% |
| Technologie | 45% |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je m'abonne à ce créateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma jauge "Automobile" augmente de 5%
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge "Technologie" augmente de 5%
<span style="color: #9E9E9E"><strong>Et</strong></span> mes nouvelles jauges sont:</p>
<pre><code>| catégorie | niveau |
|---|---|
| Automobile | 55% |
| Technologie | 50% |
</code></pre>
<hr />
<h2 id="9-skip-rapide-10s-diminue-la-jauge">9. Skip rapide (&lt;10s) diminue la jauge</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu est tagué "Économie"
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma jauge "Économie" est à 45%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je skip le contenu après 5 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma jauge "Économie" diminue de 0.5%
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge "Économie" est maintenant à 44.5%</p>
<hr />
<h2 id="10-skip-a-exactement-10s-ne-diminue-pas-la-jauge">10. Skip à exactement 10s ne diminue pas la jauge</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu est tagué "Politique"
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma jauge "Politique" est à 50%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je skip le contenu après exactement 10 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma jauge "Politique" ne change pas
<span style="color: #9E9E9E"><strong>Et</strong></span> reste à 50%</p>
<hr />
<h2 id="11-skip-tardif-30-est-neutre">11. Skip tardif (≥30%) est neutre</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu de 10 minutes est tagué "Musique"
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma jauge "Musique" est à 60%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute pendant 3 minutes (30%)
<span style="color: #9E9E9E"><strong>Et</strong></span> que je skip ensuite</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma jauge "Musique" ne diminue pas (signal neutre)
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge reste à 60% (plus le +1% de like auto si applicable)</p>
<hr />
<h2 id="12-contenu-avec-plusieurs-tags-impacte-toutes-les-jauges">12. Contenu avec plusieurs tags impacte toutes les jauges</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu est tagué "Automobile" et "Voyage"
<span style="color: #9E9E9E"><strong>Et</strong></span> que mes jauges sont:</p>
<pre><code>| catégorie | niveau |
|---|---|
| Automobile | 45% |
| Voyage | 60% |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute le contenu à 90%</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les deux jauges augmentent de 2%
<span style="color: #9E9E9E"><strong>Et</strong></span> mes nouvelles jauges sont:</p>
<pre><code>| catégorie | niveau |
|---|---|
| Automobile | 47% |
| Voyage | 62% |
</code></pre>
<hr />
<h2 id="13-contenu-avec-3-tags-impacte-les-3-jauges">13. Contenu avec 3 tags impacte les 3 jauges</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu est tagué "Sport", "Santé" et "Technologie"
<span style="color: #9E9E9E"><strong>Et</strong></span> que mes jauges sont à 50% pour chaque catégorie</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je skip rapidement après 5 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les 3 jauges diminuent de 0.5%
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes passent à 49.5%</p>
<hr />
<h2 id="14-jauges-bornees-ne-peut-pas-depasser-100">14. Jauges bornées - ne peut pas dépasser 100%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma jauge "Cryptomonnaie" est à 99%
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un contenu tagué "Cryptomonnaie" est disponible</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute le contenu à 95% (like auto renforcé +2%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma jauge "Cryptomonnaie" passe à 100% (maximum)
<span style="color: #9E9E9E"><strong>Et</strong></span> ne dépasse pas 100%</p>
<hr />
<h2 id="15-jauges-bornees-ne-peut-pas-descendre-sous-0">15. Jauges bornées - ne peut pas descendre sous 0%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma jauge "Politique" est à 0.3%
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un contenu tagué "Politique" est disponible</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je skip rapidement après 3 secondes (-0.5%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma jauge "Politique" passe à 0% (minimum)
<span style="color: #9E9E9E"><strong>Et</strong></span> ne devient pas négative</p>
<hr />
<h2 id="16-calcul-immediat-a-chaque-action">16. Calcul immédiat à chaque action</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma jauge "Voyage" est à 50%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute un contenu "Voyage" à 85%</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la jauge est mise à jour immédiatement (pas de batch)
<span style="color: #9E9E9E"><strong>Et</strong></span> passe à 52%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je demande mes recommandations dans la seconde suivante</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'algorithme utilise déjà la valeur 52%</p>
<hr />
<h2 id="17-like-manuel-apres-ecoute-30-pas-de-like-auto">17. Like manuel après écoute &lt;30% (pas de like auto)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu de 10 minutes est tagué "Culture"
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma jauge "Culture" est à 60%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute pendant 2 minutes (20%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je ne reçois pas de like automatique</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur le bouton "Like"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma jauge "Culture" augmente de 2% uniquement
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge "Culture" est maintenant à 62%</p>
<hr />
<h2 id="18-unlike-retire-le-like-manuel">18. Unlike retire le like manuel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai liké manuellement un contenu "Sport"
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma jauge "Sport" est passée de 55% à 57% (+2%)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Unlike"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma jauge "Sport" diminue de 2%
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge "Sport" revient à 55%</p>
<hr />
<h2 id="19-unlike-ne-peut-pas-retirer-un-like-automatique">19. Unlike ne peut pas retirer un like automatique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai écouté un contenu "Musique" à 90%
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai reçu un like automatique renforcé (+2%)
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma jauge "Musique" est à 52%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de faire "Unlike"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'action n'est pas disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge reste à 52%</p>
<hr />
<h2 id="20-tags-definis-par-createur-a-la-publication">20. Tags définis par créateur à la publication</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je publie un contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je dois sélectionner 1 à 3 tags
<span style="color: #9E9E9E"><strong>Et</strong></span> ces tags sont fixés après publication
<span style="color: #9E9E9E"><strong>Et</strong></span> impacteront les jauges de tous les auditeurs</p>
<hr />
<h2 id="21-plan-calculs-avec-differentes-durees-decoute">21. 📋 Plan: Calculs avec différentes durées d'écoute</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu de 10 minutes est tagué "Voyage"
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma jauge "Voyage" est à 50%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute pendant <duree> (<pourcentage>)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma jauge évolue de <impact>
<span style="color: #9E9E9E"><strong>Et</strong></span> ma nouvelle jauge est à <nouveau_niveau></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>duree</th>
<th>pourcentage</th>
<th>impact</th>
<th>nouveau_niveau</th>
</tr>
</thead>
<tbody>
<tr>
<td>1 min</td>
<td>10%</td>
<td>0%</td>
<td>50%</td>
</tr>
<tr>
<td>3 min</td>
<td>30%</td>
<td>+1%</td>
<td>51%</td>
</tr>
<tr>
<td>5 min</td>
<td>50%</td>
<td>+1%</td>
<td>51%</td>
</tr>
<tr>
<td>7.9 min</td>
<td>79%</td>
<td>+1%</td>
<td>51%</td>
</tr>
<tr>
<td>8 min</td>
<td>80%</td>
<td>+2%</td>
<td>52%</td>
</tr>
<tr>
<td>9.5 min</td>
<td>95%</td>
<td>+2%</td>
<td>52%</td>
</tr>
<tr>
<td>5 sec</td>
<td>&lt;1%</td>
<td>-0.5%</td>
<td>49.5%</td>
</tr>
</tbody>
</table>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="jauge-initiale-et-cold-start">Jauge initiale et cold start</h1>
<blockquote>
<p><em>En tant que nouvel utilisateur</em>
<em>Je veux que mes jauges d'intérêt démarrent de manière neutre</em>
<em>Afin de découvrir du contenu sans biais initial</em></p>
</blockquote>
<p><strong>15 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible</p>
</blockquote>
<h2 id="1-inscription-toutes-les-jauges-a-50">1. Inscription - toutes les jauges à 50%</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je m'inscris sur RoadWave</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> toutes mes jauges d'intérêt sont initialisées à 50%
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne dois pas remplir de questionnaire
<span style="color: #9E9E9E"><strong>Et</strong></span> l'inscription est ultra-rapide</p>
<hr />
<h2 id="2-liste-des-categories-disponibles">2. Liste des catégories disponibles</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un nouvel utilisateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mes centres d'intérêt</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois les catégories suivantes à 50%:</p>
<pre><code>| catégorie |
|---|
| Automobile |
| Voyage |
| Famille |
| Amour |
| Musique |
| Économie |
| Cryptomonnaie |
| Politique |
| Culture générale |
| Sport |
| Technologie |
| Santé |
</code></pre>
<hr />
<h2 id="3-cold-start-premier-contenu-ecoute">3. Cold start - premier contenu écouté</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je viens de m'inscrire
<span style="color: #9E9E9E"><strong>Et</strong></span> que toutes mes jauges sont à 50%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute mon premier podcast "Automobile" à 90%</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma jauge "Automobile" monte à 52% (+2%)
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes les autres jauges restent à 50%</p>
<hr />
<h2 id="4-cold-start-premier-skip">4. Cold start - premier skip</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je viens de m'inscrire
<span style="color: #9E9E9E"><strong>Et</strong></span> que toutes mes jauges sont à 50%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je skip rapidement un contenu "Économie"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma jauge "Économie" descend à 49.5% (-0.5%)
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes les autres jauges restent à 50%</p>
<hr />
<h2 id="5-apres-10-ecoutes-profil-commence-a-se-dessiner">5. Après 10 écoutes, profil commence à se dessiner</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un nouvel utilisateur
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai écouté:</p>
<pre><code>| 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% |
</code></pre>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mes jauges reflètent mes préférences:</p>
<pre><code>| catégorie | tendance |
|---|---|
| Automobile | Forte hausse (&gt;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%) |
</code></pre>
<hr />
<h2 id="6-pas-de-questionnaire-onboarding-par-defaut">6. Pas de questionnaire onboarding par défaut</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je termine l'inscription</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucun questionnaire de centres d'intérêt n'est affiché
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux commencer à écouter immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> l'algorithme apprend naturellement</p>
<hr />
<h2 id="7-algorithme-avec-jauges-a-50-chances-egales">7. Algorithme avec jauges à 50% - chances égales</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que toutes mes jauges sont à 50%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme calcule les recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les types de contenus ont une chance égale
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun biais initial n'est appliqué
<span style="color: #9E9E9E"><strong>Et</strong></span> la géolocalisation prime sur les intérêts</p>
<hr />
<h2 id="8-questionnaire-optionnel-apres-3-ecoutes-post-mvp">8. Questionnaire optionnel après 3 écoutes (post-MVP)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai écouté 3 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je termine ma 3ème écoute</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois une notification in-app optionnelle:</p>
<pre><code>| titre | Améliorez vos recommandations |
|---|---|
| message | Sélectionnez vos centres d'intérêt |
| actions | Configurer maintenant / Plus tard |
</code></pre>
<hr />
<h2 id="9-remplir-le-questionnaire-optionnel-post-mvp">9. Remplir le questionnaire optionnel (post-MVP)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le questionnaire optionnel est affiché</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne les centres d'intérêt suivants:</p>
<pre><code>| catégorie |
|---|
| Automobile |
| Voyage |
| Sport |
</code></pre>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les jauges sélectionnées passent à 70%
<span style="color: #9E9E9E"><strong>Et</strong></span> les jauges non sélectionnées passent à 30%
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Vos préférences ont été enregistrées"</p>
<hr />
<h2 id="10-skipper-le-questionnaire-optionnel-post-mvp">10. Skipper le questionnaire optionnel (post-MVP)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le questionnaire optionnel est affiché</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Plus tard"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> toutes mes jauges conservent 50%
<span style="color: #9E9E9E"><strong>Et</strong></span> l'algorithme continue d'apprendre naturellement
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne suis plus sollicité</p>
<hr />
<h2 id="11-comportement-deterministe-et-testable">11. Comportement déterministe et testable</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> deux nouveaux utilisateurs A et B</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les deux s'inscrivent au même moment</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> leurs jauges sont identiques (toutes à 50%)
<span style="color: #9E9E9E"><strong>Et</strong></span> leurs recommandations initiales sont identiques (basées sur géo uniquement)</p>
<hr />
<h2 id="12-equite-entre-createurs-au-cold-start">12. Équité entre créateurs au cold start</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un nouvel utilisateur s'inscrit
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il existe 1000 contenus de catégories variées dans sa zone</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme génère les premières recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les contenus ont une pondération intérêts identique (50%)
<span style="color: #9E9E9E"><strong>Et</strong></span> seuls la géolocalisation et l'engagement différencient les contenus
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun créateur n'a d'avantage initial</p>
<hr />
<h2 id="13-categories-extensibles">13. Catégories extensibles</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave ajoute une nouvelle catégorie "Gastronomie"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mes centres d'intérêt</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois la nouvelle catégorie "Gastronomie" à 50%
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux commencer à l'explorer normalement</p>
<hr />
<h2 id="14-voir-levolution-de-mes-jauges">14. Voir l'évolution de mes jauges</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur avec historique</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mes centres d'intérêt dans les paramètres</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois mes jauges actuelles:</p>
<pre><code>| catégorie | niveau | evolution |
|---|---|---|
| Automobile | 67% | +17% |
| Voyage | 82% | +32% |
| Économie | 34% | -16% |
| Sport | 50% | 0% |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je comprends mes préférences actuelles</p>
<hr />
<h2 id="15-friction-zero-a-linscription">15. Friction zéro à l'inscription</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je veux m'inscrire rapidement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je remplis les 4 champs obligatoires
<span style="color: #9E9E9E"><strong>Et</strong></span> que je clique sur "S'inscrire"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon compte est créé immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux commencer à écouter dans les 30 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune configuration supplémentaire n'est requise</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="synchronisation-actions-offline">Synchronisation actions offline</h1>
<blockquote>
<p><em>En tant qu'utilisateur</em>
<em>Je veux que mes actions offline soient synchronisées quand je me reconnecte</em>
<em>Afin de ne perdre aucune interaction même sans connexion</em></p>
</blockquote>
<p><strong>45 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'utilise l'application RoadWave</p>
</blockquote>
<h2 id="1-like-dun-contenu-en-mode-offline">1. Like d'un contenu en mode offline</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je n'ai aucune connexion Internet</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je like un contenu téléchargé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'action est enregistrée localement dans SQLite:
<span style="color: #9E9E9E"><strong>Et</strong></span> l'UI affiche immédiatement le like (optimistic update)</p>
<hr />
<h2 id="2-unlike-dun-contenu-en-mode-offline">2. Unlike d'un contenu en mode offline</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je n'ai aucune connexion Internet
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'avais liké un contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je retire mon like</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'action est enregistrée localement:
<span style="color: #9E9E9E"><strong>Et</strong></span> l'UI retire immédiatement le like</p>
<hr />
<h2 id="3-abonnement-a-un-createur-en-mode-offline">3. Abonnement à un créateur en mode offline</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je n'ai aucune connexion Internet</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je m'abonne à un créateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'action est enregistrée localement:
<span style="color: #9E9E9E"><strong>Et</strong></span> l'UI affiche immédiatement "Abonné ✓"</p>
<hr />
<h2 id="4-desabonnement-dun-createur-en-mode-offline">4. Désabonnement d'un créateur en mode offline</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je n'ai aucune connexion Internet
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'étais abonné à un créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me désabonne</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'action est enregistrée localement:
<span style="color: #9E9E9E"><strong>Et</strong></span> l'UI affiche "S'abonner"</p>
<hr />
<h2 id="5-signalement-dun-contenu-en-mode-offline">5. Signalement d'un contenu en mode offline</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je n'ai aucune connexion Internet</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je signale un contenu pour "Contenu inapproprié"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'action est enregistrée localement:
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois "Signalement enregistré. Sera envoyé à la reconnexion."</p>
<hr />
<h2 id="6-progression-audio-guide-en-mode-offline">6. Progression audio-guide en mode offline</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je n'ai aucune connexion Internet
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'écoute un audio-guide multi-séquences</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je termine la séquence 3/10</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la progression est enregistrée localement:
<span style="color: #9E9E9E"><strong>Et</strong></span> ma progression est sauvegardée</p>
<hr />
<h2 id="7-multiple-actions-offline-stockees-en-queue">7. Multiple actions offline stockées en queue</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je n'ai aucune connexion Internet pendant 2 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'effectue plusieurs actions:</p>
<pre><code>| action | cible |
|---|---|
| like | contenu A |
| like | contenu B |
| subscribe | créateur X |
| unlike | contenu C |
| report | contenu D |
</code></pre>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les 5 actions sont stockées dans pending_actions
<span style="color: #9E9E9E"><strong>Et</strong></span> elles seront synchronisées dans l'ordre à la reconnexion</p>
<hr />
<h2 id="8-detection-reconnexion-internet">8. Détection reconnexion Internet</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'étais en mode offline</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'app détecte une reconnexion Internet</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le processus de synchronisation démarre automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois une notification "Synchronisation en cours..."</p>
<hr />
<h2 id="9-recuperation-queue-locale-pendant-sync">9. Récupération queue locale pendant sync</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la synchronisation démarre</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'app récupère les actions en attente</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une requête SQL est exécutée:
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes les actions sont récupérées dans l'ordre chronologique</p>
<hr />
<h2 id="10-envoi-batch-api-des-actions">10. Envoi batch API des actions</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 15 actions sont en attente</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le batch est envoyé au backend</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une requête POST /sync/actions est faite:
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes les actions sont groupées en une seule requête</p>
<hr />
<h2 id="11-backend-traite-chaque-action">11. Backend traite chaque action</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le backend reçoit le batch d'actions</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il traite chaque action</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> pour chaque action:</p>
<pre><code>| é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 |
</code></pre>
<hr />
<h2 id="12-confirmation-reception-et-suppression-queue-locale">12. Confirmation réception et suppression queue locale</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le backend a traité toutes les actions avec succès</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la confirmation est reçue par l'app</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les actions sont supprimées de la queue locale:
<span style="color: #9E9E9E"><strong>Et</strong></span> la table pending_actions est vidée</p>
<hr />
<h2 id="13-toast-confirmation-synchronisation">13. Toast confirmation synchronisation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 15 actions ont été synchronisées</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la synchronisation se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois un toast:</p>
<hr />
<h2 id="14-synchronisation-silencieuse-si-peu-dactions">14. Synchronisation silencieuse si peu d'actions</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai seulement 2 actions en attente</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la synchronisation se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucun toast n'est affiché (sync silencieuse)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'expérience reste fluide
<span style="color: #F44336"><strong>Mais</strong></span> je peux voir le détail dans l'historique des syncs</p>
<hr />
<h2 id="15-echec-synchronisation-retry-automatique">15. Échec synchronisation - Retry automatique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la synchronisation échoue (erreur réseau)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'échec est détecté</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un retry automatique est programmé dans 30 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> les actions restent dans pending_actions</p>
<hr />
<h2 id="16-3-tentatives-echouees-notification-utilisateur">16. 3 tentatives échouées - Notification utilisateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 3 tentatives de synchronisation ont échoué</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la 3ème tentative échoue</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification:</p>
<hr />
<h2 id="17-actions-conservees-jusqua-sync-reussie">17. Actions conservées jusqu'à sync réussie</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la synchronisation échoue plusieurs fois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les tentatives continuent d'échouer</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les actions restent dans pending_actions
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune action n'est perdue
<span style="color: #9E9E9E"><strong>Et</strong></span> elles seront envoyées dès que la connexion sera stable</p>
<hr />
<h2 id="18-retention-max-7-jours-purge-automatique">18. Rétention max 7 jours - Purge automatique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une action est en attente depuis 7 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte cette ancienneté</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'action est automatiquement supprimée de la queue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois "1 action trop ancienne supprimée (&gt;7 jours)"
<span style="color: #9E9E9E"><strong>Et</strong></span> cela évite une queue infinie</p>
<hr />
<h2 id="19-justification-retention-7-jours">19. Justification rétention 7 jours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur ne se connecte jamais pendant 2 semaines</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> ses actions ont &gt;7 jours</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> elles sont purgées automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> évite une queue qui grandit indéfiniment</p>
<hr />
<h2 id="20-retry-manuel-apres-echec">20. Retry manuel après échec</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la synchronisation a échoué</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Réessayer maintenant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une nouvelle tentative de synchronisation est lancée immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> si elle réussit, les actions sont synchronisées</p>
<hr />
<h2 id="21-backend-retourne-contenus-supprimes">21. Backend retourne contenus supprimés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai liké un contenu offline
<span style="color: #F44336"><strong>Mais</strong></span> que le contenu a été supprimé entre temps</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le backend traite la synchronisation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il retourne:</p>
<hr />
<h2 id="22-app-supprime-fichiers-locaux-contenus-supprimes">22. App supprime fichiers locaux contenus supprimés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le backend retourne deleted_content_ids: [123, 456]</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'app traite la réponse</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> elle supprime les fichiers locaux des contenus 123 et 456
<span style="color: #9E9E9E"><strong>Et</strong></span> libère l'espace disque
<span style="color: #9E9E9E"><strong>Et</strong></span> les actions associées sont retirées de la queue</p>
<hr />
<h2 id="23-contenu-supprime-en-cours-decoute">23. Contenu supprimé en cours d'écoute</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute le contenu 123 en offline
<span style="color: #9E9E9E"><strong>Et</strong></span> que la sync détecte que le contenu a été supprimé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la lecture actuelle se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'app attend 2 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> passe automatiquement au contenu suivant
<span style="color: #9E9E9E"><strong>Et</strong></span> le fichier du contenu 123 est supprimé en arrière-plan</p>
<hr />
<h2 id="24-toast-notification-contenu-retire">24. Toast notification contenu retiré</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 2 contenus téléchargés ont été supprimés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la synchronisation se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois un toast:</p>
<hr />
<h2 id="25-contenu-modere-apres-telechargement">25. Contenu modéré après téléchargement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai téléchargé un contenu qui est ensuite modéré</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la synchronisation détecte la modération</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est immédiatement supprimé du device
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne peux plus l'écouter
<span style="color: #9E9E9E"><strong>Et</strong></span> cela garantit la conformité même offline</p>
<hr />
<h2 id="26-justification-pas-de-conflit-possible">26. Justification pas de conflit possible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que les actions offline sont unilatérales (likes, abonnements)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> elles sont synchronisées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il n'y a pas de conflit de version possible
<span style="color: #9E9E9E"><strong>Et</strong></span> pas de merge complexe nécessaire</p>
<hr />
<h2 id="27-justification-ux-fluide-offline">27. Justification UX fluide offline</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que toutes les actions fonctionnent offline</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur interagit sans connexion</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'expérience est identique au mode online
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur n'est pas bloqué
<span style="color: #9E9E9E"><strong>Et</strong></span> peut utiliser l'app normalement</p>
<hr />
<h2 id="28-justification-batch-economie-requetes">28. Justification batch = Économie requêtes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 15 actions sont en attente</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> elles sont synchronisées en batch</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 1 seule requête HTTP est envoyée (vs 15 si individuelles)
<span style="color: #9E9E9E"><strong>Et</strong></span> cela économise la bande passante et la batterie
<span style="color: #9E9E9E"><strong>Et</strong></span> réduit la charge serveur</p>
<hr />
<h2 id="29-justification-conformite-moderation-offline">29. Justification conformité modération offline</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu illégal est modéré pendant qu'un user est offline</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le user se reconnecte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est immédiatement supprimé de son device
<span style="color: #9E9E9E"><strong>Et</strong></span> cela garantit que les contenus illégaux disparaissent même offline</p>
<hr />
<h2 id="30-historique-synchronisations">30. Historique synchronisations</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'accède à "Paramètres &gt; Synchronisation"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte l'historique</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| 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 ❌ |
</code></pre>
<hr />
<h2 id="31-detail-dune-synchronisation">31. Détail d'une synchronisation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je clique sur une ligne de l'historique</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le détail s'affiche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<hr />
<h2 id="32-compteur-actions-en-attente-visible">32. Compteur actions en attente visible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai 12 actions en attente de synchronisation</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à l'onglet Profil</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois un badge "12" sur l'icône de synchronisation
<span style="color: #9E9E9E"><strong>Et</strong></span> je sais qu'il y a des actions en attente</p>
<hr />
<h2 id="33-synchronisation-manuelle-forcee">33. Synchronisation manuelle forcée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je veux forcer une synchronisation immédiate</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je vais dans "Paramètres &gt; Synchronisation"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je clique sur "Synchroniser maintenant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la synchronisation démarre immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes les actions en attente sont envoyées</p>
<hr />
<h2 id="34-statistiques-utilisateur-syncs-effectuees">34. Statistiques utilisateur - Syncs effectuées</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'accède à mes statistiques</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte la section Synchronisation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="35-statistiques-admin-volume-synchronisations">35. Statistiques admin - Volume synchronisations</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un admin consulte les métriques de synchronisation</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il accède au dashboard</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="36-alerte-admin-si-taux-echec-sync-10">36. Alerte admin si taux échec sync &gt;10%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le taux d'échec sync dépasse 10%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte cette anomalie</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une alerte est envoyée:</p>
<hr />
<h2 id="37-synchronisation-rapide-2s">37. Synchronisation rapide &lt;2s</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai 20 actions en attente</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la synchronisation démarre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le traitement prend &lt;2 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne remarque aucun ralentissement de l'app</p>
<hr />
<h2 id="38-synchronisation-de-gros-batch-100-actions">38. Synchronisation de gros batch (100 actions)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je n'ai pas synchronisé pendant 1 semaine
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai 100 actions en attente</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la synchronisation démarre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le batch de 100 actions est traité en &lt;5 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes les actions sont synchronisées avec succès</p>
<hr />
<h2 id="39-gestion-charge-serveur-10-000-syncs-simultanees">39. Gestion charge serveur - 10 000 syncs simultanées</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 10 000 utilisateurs se reconnectent simultanément</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> chacun envoie un batch de 20 actions</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le serveur traite 200 000 actions
<span style="color: #9E9E9E"><strong>Et</strong></span> grâce au traitement asynchrone (queue Redis), le temps de réponse reste &lt;3s
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun timeout n'est constaté</p>
<hr />
<h2 id="40-stockage-sqlite-optimise">40. Stockage SQLite optimisé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la table pending_actions stocke des centaines d'actions</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> des requêtes sont exécutées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la table est indexée sur created_at
<span style="color: #9E9E9E"><strong>Et</strong></span> les requêtes SELECT et DELETE sont instantanées (&lt;10ms)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'expérience utilisateur reste fluide</p>
<hr />
<h2 id="41-nettoyage-automatique-table-pending_actions">41. Nettoyage automatique table pending_actions</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la table pending_actions grossit avec le temps</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les actions sont synchronisées et supprimées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la table est automatiquement optimisée (VACUUM sur SQLite)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'espace disque est libéré
<span style="color: #9E9E9E"><strong>Et</strong></span> les performances restent optimales</p>
<hr />
<h2 id="42-action-dupliquee-idempotence">42. Action dupliquée - Idempotence</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai liké un contenu offline
<span style="color: #9E9E9E"><strong>Et</strong></span> que la sync échoue et retry</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le backend reçoit 2 fois le même like</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il applique l'idempotence (1 seul like enregistré)
<span style="color: #9E9E9E"><strong>Et</strong></span> le compteur de likes n'est pas faussé</p>
<hr />
<h2 id="43-sequence-likeunlike-offline">43. Séquence like/unlike offline</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai liké puis unliké un contenu offline</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les 2 actions sont synchronisées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le backend applique les 2 actions dans l'ordre
<span style="color: #9E9E9E"><strong>Et</strong></span> le résultat final est "pas de like" (état correct)</p>
<hr />
<h2 id="44-abonnement-puis-desabonnement-offline">44. Abonnement puis désabonnement offline</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je me suis abonné puis désabonné d'un créateur offline</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les 2 actions sont synchronisées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le backend applique les 2 actions dans l'ordre
<span style="color: #9E9E9E"><strong>Et</strong></span> le résultat final est "pas abonné"
<span style="color: #9E9E9E"><strong>Et</strong></span> les jauges évoluent correctement (+5% puis -5% = 0% net)</p>
<hr />
<h2 id="45-createur-supprime-pendant-offline">45. Créateur supprimé pendant offline</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je me suis abonné à un créateur offline
<span style="color: #F44336"><strong>Mais</strong></span> que le créateur a supprimé son compte entre temps</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la sync traite l'abonnement</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le backend retourne "creator_deleted"
<span style="color: #9E9E9E"><strong>Et</strong></span> l'action est ignorée silencieusement
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune erreur n'est affichée à l'utilisateur</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="telechargement-de-contenus-offline">Téléchargement de contenus offline</h1>
<blockquote>
<p><em>En tant qu'utilisateur</em>
<em>Je veux télécharger des contenus pour les écouter sans connexion</em>
<em>Afin de profiter de RoadWave même dans les zones sans réseau</em></p>
</blockquote>
<p><strong>49 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis connecté à l'application RoadWave</p>
</blockquote>
<h2 id="1-option-autour-de-moi-rayon-50-km">1. Option "Autour de moi" - Rayon 50 km</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis à Paris (position GPS détectée)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne "Télécharger &gt; Autour de moi"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'app recherche tous les contenus géolocalisés dans un rayon de 50 km
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois une liste de contenus de Paris et banlieue proche
<span style="color: #9E9E9E"><strong>Et</strong></span> l'estimation affiche "~150 contenus disponibles"</p>
<hr />
<h2 id="2-option-ma-ville-limite-administrative-detectee">2. Option "Ma ville" - Limite administrative détectée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis à Lyon (position GPS détectée)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne "Télécharger &gt; Ma ville"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'app détecte automatiquement "Lyon" comme ville
<span style="color: #9E9E9E"><strong>Et</strong></span> recherche tous les contenus géolocalisés "Lyon"
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois uniquement les contenus de la ville de Lyon (pas banlieue)</p>
<hr />
<h2 id="3-option-mon-departement-selection-dans-liste">3. Option "Mon département" - Sélection dans liste</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je veux télécharger des contenus pour un département</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne "Télécharger &gt; Mon département"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois une liste de tous les départements français:</p>
<pre><code>| département |
|---|
| 01 - Ain |
| 02 - Aisne |
| 75 - Paris |
| 69 - Rhône |
| ... |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je peux choisir un département</p>
<hr />
<h2 id="4-selection-departement-et-telechargement-contenus">4. Sélection département et téléchargement contenus</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je sélectionne "75 - Paris" dans la liste des départements</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la sélection est confirmée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'app recherche tous les contenus géolocalisés "Paris"
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois "~234 contenus disponibles pour Paris"</p>
<hr />
<h2 id="5-option-ma-region-selection-dans-liste">5. Option "Ma région" - Sélection dans liste</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je veux télécharger des contenus pour une région</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne "Télécharger &gt; Ma région"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois une liste de toutes les régions françaises:</p>
<pre><code>| région |
|---|
| Auvergne-Rhône-Alpes |
| Bretagne |
| Île-de-France |
| Nouvelle-Aquitaine |
| Occitanie |
| ... |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je peux choisir une région</p>
<hr />
<h2 id="6-selection-region-et-telechargement-contenus">6. Sélection région et téléchargement contenus</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je sélectionne "Bretagne" dans la liste des régions</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la sélection est confirmée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'app recherche tous les contenus géolocalisés des départements bretons:</p>
<pre><code>| département |
|---|
| Côtes-d'Armor (22) |
| Finistère (29) |
| Ille-et-Vilaine (35) |
| Morbihan (56) |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je vois "~487 contenus disponibles pour Bretagne"</p>
<hr />
<h2 id="7-recherche-manuelle-ville">7. Recherche manuelle ville</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je veux télécharger des contenus pour une ville spécifique</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je tape "Marseille" dans la barre de recherche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'app propose des suggestions:</p>
<pre><code>| suggestion |
|---|
| Marseille (13) |
| Marseille-en-Beauvaisis |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je peux sélectionner "Marseille (13)"</p>
<hr />
<h2 id="8-recherche-manuelle-avec-autocompletion">8. Recherche manuelle avec autocomplétion</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je tape "Ly" dans la barre de recherche</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'autocomplétion s'active</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois des suggestions:</p>
<pre><code>| suggestion |
|---|
| Lyon (69) |
| Lys-lez-Lannoy |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je peux affiner ma recherche</p>
<hr />
<h2 id="9-utilisateur-gratuit-limite-50-contenus-max">9. Utilisateur gratuit - Limite 50 contenus max</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur gratuit
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai déjà téléchargé 45 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à la page Téléchargements</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois "45 / 50 contenus téléchargés"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux télécharger 5 contenus supplémentaires maximum</p>
<hr />
<h2 id="10-utilisateur-gratuit-tentative-depasser-limite-50">10. Utilisateur gratuit - Tentative dépasser limite 50</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis gratuit et j'ai déjà 50 contenus téléchargés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de télécharger un 51ème contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le téléchargement est refusé
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message:</p>
<hr />
<h2 id="11-utilisateur-premium-telechargements-illimites">11. Utilisateur Premium - Téléchargements illimités</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur Premium
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai déjà téléchargé 245 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à la page Téléchargements</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois "245 contenus (3.2 GB)"
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune limite n'est affichée
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux télécharger autant de contenus que je veux</p>
<hr />
<h2 id="12-limite-premium-espace-disque-disponible">12. Limite Premium = Espace disque disponible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis Premium
<span style="color: #9E9E9E"><strong>Et</strong></span> que mon device a 500 MB d'espace disque disponible</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de télécharger 100 contenus (2 GB)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le téléchargement échoue après ~50 contenus (500 MB)
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois "Espace disque insuffisant. Libérez de l'espace pour continuer."</p>
<hr />
<h2 id="13-calcul-temps-ecoute-disponible-gratuit">13. Calcul temps écoute disponible gratuit</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis gratuit avec 50 contenus téléchargés
<span style="color: #9E9E9E"><strong>Et</strong></span> que la durée moyenne d'un contenu est 5 minutes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je calcule le temps d'écoute disponible</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 50 contenus × 5 min = 250 minutes = 4h10 d'écoute
<span style="color: #9E9E9E"><strong>Et</strong></span> cela suffit pour un trajet quotidien ou road trip court</p>
<hr />
<h2 id="14-calcul-temps-ecoute-disponible-premium-illimite">14. Calcul temps écoute disponible Premium illimité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis Premium avec 300 contenus téléchargés
<span style="color: #9E9E9E"><strong>Et</strong></span> que la durée moyenne est 5 minutes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je calcule le temps d'écoute disponible</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 300 contenus × 5 min = 1500 minutes = 25h d'écoute
<span style="color: #9E9E9E"><strong>Et</strong></span> cela suffit pour un road trip de plusieurs jours</p>
<hr />
<h2 id="15-telechargement-par-defaut-en-wifi-uniquement">15. Téléchargement par défaut en WiFi uniquement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis connecté en WiFi</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Télécharger 20 contenus"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le téléchargement démarre immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune popup de confirmation n'apparaît</p>
<hr />
<h2 id="16-tentative-telechargement-en-donnees-mobiles-popup-confirmation">16. Tentative téléchargement en données mobiles - Popup confirmation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis connecté en 4G (pas de WiFi)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Télécharger 20 contenus"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une popup apparaît:</p>
<hr />
<h2 id="17-calcul-estimation-consommation-data-mobile">17. Calcul estimation consommation data mobile</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je veux télécharger 20 contenus
<span style="color: #9E9E9E"><strong>Et</strong></span> que la durée moyenne est 5 minutes
<span style="color: #9E9E9E"><strong>Et</strong></span> que la qualité Standard est 48 kbps Opus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'estimation est calculée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> consommation = 20 contenus × 5 min × 48 kbps / 8 = 72 MB
<span style="color: #9E9E9E"><strong>Et</strong></span> ce montant est affiché dans la popup</p>
<hr />
<h2 id="18-confirmation-telechargement-en-donnees-mobiles">18. Confirmation téléchargement en données mobiles</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je vois la popup de confirmation données mobiles</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Continuer quand même"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le téléchargement démarre immédiatement via 4G
<span style="color: #9E9E9E"><strong>Et</strong></span> la consommation data est comptabilisée sur mon forfait mobile</p>
<hr />
<h2 id="19-refus-telechargement-donnees-mobiles-attendre-wifi">19. Refus téléchargement données mobiles - Attendre WiFi</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je vois la popup de confirmation données mobiles</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Attendre WiFi"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les téléchargements sont mis en file d'attente
<span style="color: #9E9E9E"><strong>Et</strong></span> ils démarreront automatiquement quand le WiFi sera détecté</p>
<hr />
<h2 id="20-detection-automatique-wifi-et-reprise-telechargements">20. Détection automatique WiFi et reprise téléchargements</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai mis 20 contenus en file d'attente (attente WiFi)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'app détecte une connexion WiFi</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les téléchargements démarrent automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois une notification "Téléchargements en cours via WiFi"</p>
<hr />
<h2 id="21-qualite-standard-48-kbps-par-defaut">21. Qualité Standard (48 kbps) par défaut</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je configure mes téléchargements</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède aux paramètres de qualité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la qualité "Standard (48 kbps - ~20 MB/h)" est sélectionnée par défaut
<span style="color: #9E9E9E"><strong>Et</strong></span> elle est disponible pour tous (gratuit + Premium)</p>
<hr />
<h2 id="22-qualite-basse-24-kbps-disponible-pour-tous">22. Qualité Basse (24 kbps) disponible pour tous</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai peu d'espace disque disponible</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne qualité "Basse (24 kbps - ~10 MB/h)"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mes prochains téléchargements seront en 24 kbps
<span style="color: #9E9E9E"><strong>Et</strong></span> l'espace utilisé sera divisé par 2 par rapport à Standard
<span style="color: #9E9E9E"><strong>Et</strong></span> cette option est disponible pour gratuit + Premium</p>
<hr />
<h2 id="23-qualite-haute-64-kbps-reservee-premium">23. Qualité Haute (64 kbps) réservée Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur gratuit</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les options de qualité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'option "Haute (64 kbps - ~30 MB/h)" est grisée
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois "👑 Premium uniquement"
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne peux pas la sélectionner</p>
<hr />
<h2 id="24-utilisateur-premium-peut-choisir-qualite-haute">24. Utilisateur Premium peut choisir qualité Haute</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les options de qualité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'option "Haute (64 kbps - ~30 MB/h)" est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux la sélectionner pour mes téléchargements
<span style="color: #9E9E9E"><strong>Et</strong></span> la qualité audio sera excellente (meilleure restitution voix et ambiances)</p>
<hr />
<h2 id="25-comparaison-taille-fichiers-selon-qualite">25. Comparaison taille fichiers selon qualité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je veux télécharger 50 contenus de 5 min chacun</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je compare les qualités</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les tailles totales sont:</p>
<pre><code>| qualité | bitrate | taille totale |
|---|---|---|
| Basse | 24 kbps | ~250 MB |
| Standard | 48 kbps | ~500 MB |
| Haute | 64 kbps | ~650 MB |
</code></pre>
<hr />
<h2 id="26-justification-standard-bon-compromis">26. Justification Standard = Bon compromis</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le contenu RoadWave est principalement de la voix</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la qualité Standard (48 kbps Opus) est utilisée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la qualité est très correcte pour la voix
<span style="color: #9E9E9E"><strong>Et</strong></span> équivalente à la radio FM
<span style="color: #9E9E9E"><strong>Et</strong></span> le compromis qualité/taille est optimal</p>
<hr />
<h2 id="27-justification-haute-reservee-premium-incitation-upgrade">27. Justification Haute réservée Premium = Incitation upgrade</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur gratuit veut la meilleure qualité</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il voit que Haute est réservée Premium</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> cela l'incite à passer Premium pour 4.99€/mois
<span style="color: #9E9E9E"><strong>Et</strong></span> c'est un avantage tangible supplémentaire de Premium</p>
<hr />
<h2 id="28-changement-qualite-apres-telechargements-existants">28. Changement qualité après téléchargements existants</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai déjà téléchargé 30 contenus en qualité Standard</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je change la qualité vers Haute (si Premium)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les 30 contenus existants restent en Standard
<span style="color: #9E9E9E"><strong>Et</strong></span> seuls les nouveaux téléchargements seront en Haute
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux manuellement re-télécharger les 30 contenus pour les avoir en Haute</p>
<hr />
<h2 id="29-telechargement-individuel-dun-contenu">29. Téléchargement individuel d'un contenu</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte la page d'un contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur l'icône de téléchargement 📥</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le téléchargement démarre
<span style="color: #9E9E9E"><strong>Et</strong></span> une barre de progression apparaît
<span style="color: #9E9E9E"><strong>Et</strong></span> l'icône devient ✅ quand terminé</p>
<hr />
<h2 id="30-telechargement-batch-de-contenus-selectionnes">30. Téléchargement batch de contenus sélectionnés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte une liste de contenus pour "Paris"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne 15 contenus manuellement
<span style="color: #9E9E9E"><strong>Et</strong></span> que je clique sur "Télécharger la sélection"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les 15 contenus sont téléchargés en parallèle (max 3 simultanés)
<span style="color: #9E9E9E"><strong>Et</strong></span> une notification affiche "15 contenus téléchargés"</p>
<hr />
<h2 id="31-telechargement-automatique-recommandations-zone">31. Téléchargement automatique recommandations zone</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je sélectionne "Autour de moi" (Paris)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Télécharger les 50 meilleurs contenus"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'algorithme sélectionne automatiquement les 50 contenus les mieux notés/récents
<span style="color: #9E9E9E"><strong>Et</strong></span> les télécharge tous
<span style="color: #9E9E9E"><strong>Et</strong></span> je n'ai pas besoin de choisir manuellement</p>
<hr />
<h2 id="32-barre-de-progression-telechargement-global">32. Barre de progression téléchargement global</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je télécharge 20 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les téléchargements sont en cours</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois une barre de progression globale:</p>
<hr />
<h2 id="33-telechargements-en-tache-de-fond">33. Téléchargements en tâche de fond</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je lance le téléchargement de 30 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je ferme l'app ou passe à une autre activité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les téléchargements continuent en arrière-plan
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois une notification quand tous sont terminés</p>
<hr />
<h2 id="34-pause-et-reprise-telechargements">34. Pause et reprise téléchargements</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je télécharge 20 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Pause"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les téléchargements en cours se terminent
<span style="color: #9E9E9E"><strong>Et</strong></span> les téléchargements en attente sont mis en pause
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux cliquer sur "Reprendre" plus tard</p>
<hr />
<h2 id="35-annulation-telechargements">35. Annulation téléchargements</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je télécharge 20 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Annuler"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les téléchargements sont arrêtés
<span style="color: #9E9E9E"><strong>Et</strong></span> les fichiers partiels sont supprimés
<span style="color: #9E9E9E"><strong>Et</strong></span> l'espace disque est libéré</p>
<hr />
<h2 id="36-gestion-erreurs-telechargement">36. Gestion erreurs téléchargement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je télécharge un contenu
<span style="color: #F44336"><strong>Mais</strong></span> que la connexion Internet coupe au milieu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la connexion revient</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le téléchargement reprend automatiquement où il s'était arrêté
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune perte de progression n'a lieu</p>
<hr />
<h2 id="37-retry-automatique-apres-echec">37. Retry automatique après échec</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un téléchargement échoue 3 fois consécutives</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'échec est détecté</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est marqué "Échec"
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois une notification "3 contenus n'ont pas pu être téléchargés"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux retry manuellement en cliquant sur "Réessayer"</p>
<hr />
<h2 id="38-liste-contenus-telecharges">38. Liste contenus téléchargés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai téléchargé 45 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à "Téléchargements"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois la liste complète de mes 45 contenus
<span style="color: #9E9E9E"><strong>Et</strong></span> pour chaque contenu: titre, créateur, durée, taille, date téléchargement</p>
<hr />
<h2 id="39-tri-contenus-telecharges">39. Tri contenus téléchargés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte ma liste de téléchargements</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Trier par"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux trier par:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="40-recherche-dans-contenus-telecharges">40. Recherche dans contenus téléchargés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai 200 contenus téléchargés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je tape "Tesla" dans la barre de recherche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seuls les contenus contenant "Tesla" s'affichent
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux rapidement trouver un contenu spécifique</p>
<hr />
<h2 id="41-suppression-individuelle-contenu-telecharge">41. Suppression individuelle contenu téléchargé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je veux supprimer un contenu téléchargé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je swipe left (iOS) ou long press (Android) sur le contenu
<span style="color: #9E9E9E"><strong>Et</strong></span> que je clique sur "Supprimer"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le fichier est supprimé du device
<span style="color: #9E9E9E"><strong>Et</strong></span> l'espace disque est libéré
<span style="color: #9E9E9E"><strong>Et</strong></span> le compteur est décrémenté (ex: 45/50 → 44/50)</p>
<hr />
<h2 id="42-suppression-batch-contenus-telecharges">42. Suppression batch contenus téléchargés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je veux supprimer plusieurs contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne 10 contenus
<span style="color: #9E9E9E"><strong>Et</strong></span> que je clique sur "Supprimer la sélection"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les 10 fichiers sont supprimés
<span style="color: #9E9E9E"><strong>Et</strong></span> ~100 MB d'espace disque sont libérés
<span style="color: #9E9E9E"><strong>Et</strong></span> une notification confirme "10 contenus supprimés"</p>
<hr />
<h2 id="43-suppression-tous-les-contenus-telecharges">43. Suppression tous les contenus téléchargés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai 45 contenus téléchargés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Supprimer tout"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je confirme l'action</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les 45 contenus sont supprimés
<span style="color: #9E9E9E"><strong>Et</strong></span> l'espace disque total est libéré (~450 MB)
<span style="color: #9E9E9E"><strong>Et</strong></span> le compteur repasse à 0/50</p>
<hr />
<h2 id="44-espace-disque-utilise-visible">44. Espace disque utilisé visible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai téléchargé 45 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à la page Téléchargements</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois l'espace disque utilisé:</p>
<hr />
<h2 id="45-statistiques-telechargements">45. Statistiques téléchargements</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'accède à mes statistiques</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte la section Téléchargements</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| 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% |
</code></pre>
<hr />
<h2 id="46-lecture-contenu-telecharge-sans-connexion">46. Lecture contenu téléchargé sans connexion</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je n'ai aucune connexion Internet (mode avion)
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai des contenus téléchargés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je lance un contenu téléchargé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la lecture démarre normalement depuis le fichier local
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune erreur de connexion n'apparaît</p>
<hr />
<h2 id="47-badge-telecharge-sur-contenus-offline">47. Badge "Téléchargé" sur contenus offline</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai téléchargé certains contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte une liste de contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les contenus téléchargés ont un badge ✅ "Offline"
<span style="color: #9E9E9E"><strong>Et</strong></span> je sais immédiatement lesquels sont disponibles sans connexion</p>
<hr />
<h2 id="48-filtre-telecharges-uniquement">48. Filtre "Téléchargés uniquement"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je veux voir uniquement mes contenus offline</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'active le filtre "Téléchargés uniquement"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seuls les contenus téléchargés s'affichent
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux facilement naviguer dans mon catalogue offline</p>
<hr />
<h2 id="49-playlist-offline-automatique">49. Playlist offline automatique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai téléchargé 45 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à "Téléchargements"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux lancer une playlist aléatoire de mes 45 contenus
<span style="color: #9E9E9E"><strong>Et</strong></span> profiter d'une écoute continue offline</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="validite-et-renouvellement-contenus-offline">Validité et renouvellement contenus offline</h1>
<blockquote>
<p><em>En tant qu'utilisateur</em>
<em>Je veux que mes contenus téléchargés restent valides un certain temps</em>
<em>Afin de garantir la légalité et la fraîcheur du contenu</em></p>
</blockquote>
<p><strong>38 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis connecté à l'application RoadWave
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai des contenus téléchargés</p>
</blockquote>
<h2 id="1-validite-de-30-jours-apres-telechargement">1. Validité de 30 jours après téléchargement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je télécharge un contenu le 1er juin 2025</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le téléchargement est terminé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est valide jusqu'au 1er juillet 2025 (30 jours)
<span style="color: #9E9E9E"><strong>Et</strong></span> la date d'expiration est stockée en local</p>
<hr />
<h2 id="2-affichage-date-expiration-sur-contenu-telecharge">2. Affichage date expiration sur contenu téléchargé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai téléchargé un contenu il y a 20 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les détails du contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois "Expire dans 10 jours"
<span style="color: #9E9E9E"><strong>Et</strong></span> je sais combien de temps il reste avant expiration</p>
<hr />
<h2 id="3-standard-industrie-aligne-spotify-youtube-deezer">3. Standard industrie aligné (Spotify, YouTube, Deezer)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que Spotify, YouTube Music et Deezer utilisent 30 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> RoadWave fixe également 30 jours</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> c'est le standard accepté par les utilisateurs
<span style="color: #9E9E9E"><strong>Et</strong></span> il n'y a pas de confusion avec les autres plateformes</p>
<hr />
<h2 id="4-justification-30-jours-force-reconnexion-reguliere">4. Justification 30 jours - Force reconnexion régulière</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur ne se connecte jamais</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> ses contenus expirent après 30 jours</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il est obligé de se reconnecter pour les renouveler
<span style="color: #9E9E9E"><strong>Et</strong></span> le système peut vérifier:</p>
<pre><code>| vérification |
|---|
| Abonnement Premium toujours actif |
| Contenus non modérés/supprimés |
| Métadonnées à jour |
</code></pre>
<hr />
<h2 id="5-justification-30-jours-evite-stockage-obsolete">5. Justification 30 jours - Évite stockage obsolète</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu a été modéré après téléchargement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le contenu expire après 30 jours maximum</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu illégal est automatiquement supprimé
<span style="color: #9E9E9E"><strong>Et</strong></span> ne reste pas indéfiniment sur le device</p>
<hr />
<h2 id="6-detection-wifi-et-contenus-25-jours">6. Détection WiFi et contenus &gt;25 jours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai des contenus téléchargés il y a 26 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'app détecte une connexion WiFi</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une requête GET /offline/contents/refresh est envoyée
<span style="color: #9E9E9E"><strong>Et</strong></span> le backend vérifie chaque contenu</p>
<hr />
<h2 id="7-verification-abonnement-premium-toujours-actif">7. Vérification abonnement Premium toujours actif</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu téléchargé en Premium est à renouveler</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le backend vérifie le statut
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'abonnement Premium est toujours actif</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la validité est renouvelée à 30 jours supplémentaires</p>
<hr />
<h2 id="8-abonnement-premium-expire-contenu-non-renouvele">8. Abonnement Premium expiré - Contenu non renouvelé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu Premium téléchargé est à renouveler</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le backend vérifie le statut
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'abonnement Premium a expiré</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu n'est pas renouvelé
<span style="color: #9E9E9E"><strong>Et</strong></span> il sera supprimé à l'expiration (J-0)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur voit "Contenu Premium expiré (abonnement inactif)"</p>
<hr />
<h2 id="9-verification-contenu-pas-moderesupprime">9. Vérification contenu pas modéré/supprimé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu téléchargé est à renouveler</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le backend vérifie le statut
<span style="color: #9E9E9E"><strong>Et</strong></span> que le contenu a été modéré ou supprimé entre temps</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu n'est pas renouvelé
<span style="color: #9E9E9E"><strong>Et</strong></span> sera supprimé immédiatement du device
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur voit "1 contenu retiré (violation règles)"</p>
<hr />
<h2 id="10-mise-a-jour-metadonnees-lors-du-renouvellement">10. Mise à jour métadonnées lors du renouvellement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu téléchargé est renouvelé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le backend traite le renouvellement</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les métadonnées sont mises à jour:</p>
<pre><code>| métadonnée | mise à jour si changée |
|---|---|
| Titre | ✅ |
| Nom créateur | ✅ |
| Description | ✅ |
| Tags | ✅ |
| Statut Premium | ✅ |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur voit les infos à jour</p>
<hr />
<h2 id="11-pas-de-re-telechargement-audio-si-fichier-ok">11. Pas de re-téléchargement audio si fichier OK</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu est renouvelé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le fichier audio local est intact</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seules les métadonnées sont mises à jour
<span style="color: #9E9E9E"><strong>Et</strong></span> le fichier audio n'est pas re-téléchargé
<span style="color: #9E9E9E"><strong>Et</strong></span> cela économise la bande passante</p>
<hr />
<h2 id="12-re-telechargement-audio-si-fichier-corrompu">12. Re-téléchargement audio si fichier corrompu</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu est renouvelé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le fichier audio local est corrompu (checksum invalide)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le fichier audio est re-téléchargé entièrement
<span style="color: #9E9E9E"><strong>Et</strong></span> le nouveau fichier remplace le corrompu</p>
<hr />
<h2 id="13-renouvellement-silencieux-si-wifi-regulier">13. Renouvellement silencieux si WiFi régulier</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je me connecte en WiFi tous les jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> mes contenus atteignent 25-30 jours</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ils sont automatiquement renouvelés en arrière-plan
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne vois aucune notification (processus transparent)
<span style="color: #9E9E9E"><strong>Et</strong></span> mes contenus restent valides indéfiniment</p>
<hr />
<h2 id="14-renouvellement-batch-de-plusieurs-contenus">14. Renouvellement batch de plusieurs contenus</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai 30 contenus à renouveler</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le renouvellement automatique se déclenche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une requête batch est envoyée:
<span style="color: #9E9E9E"><strong>Et</strong></span> le backend traite les 30 contenus en une seule requête
<span style="color: #9E9E9E"><strong>Et</strong></span> cela économise les requêtes HTTP</p>
<hr />
<h2 id="15-temps-de-traitement-renouvellement">15. Temps de traitement renouvellement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 30 contenus sont à renouveler</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la requête batch est traitée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le backend répond en &lt;2 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> les métadonnées sont mises à jour localement
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur ne remarque aucun ralentissement</p>
<hr />
<h2 id="16-notification-j-3-avant-expiration">16. Notification J-3 avant expiration</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai 15 contenus qui expirent dans 3 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système vérifie les expirations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification:
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux agir avant l'expiration</p>
<hr />
<h2 id="17-pas-de-notification-si-connexion-wifi-reguliere">17. Pas de notification si connexion WiFi régulière</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je me connecte en WiFi tous les jours
<span style="color: #9E9E9E"><strong>Et</strong></span> que mes contenus sont automatiquement renouvelés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système vérifie les expirations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune notification J-3 n'est envoyée</p>
<hr />
<h2 id="18-notification-uniquement-si-contenus-non-renouveles">18. Notification uniquement si contenus non renouvelés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai 20 contenus dont 15 renouvelés et 5 non renouvelés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le J-3 arrive pour les 5 non renouvelés</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois "5 contenus expirent dans 3 jours"
<span style="color: #9E9E9E"><strong>Et</strong></span> seuls les contenus à risque sont mentionnés</p>
<hr />
<h2 id="19-action-utilisateur-apres-notification-j-3">19. Action utilisateur après notification J-3</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je reçois la notification J-3</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur la notification</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'app s'ouvre sur la page Téléchargements
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois les contenus qui vont expirer en rouge
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux me connecter en WiFi pour les renouveler</p>
<hr />
<h2 id="20-suppression-automatique-j-0-expiration">20. Suppression automatique J-0 (expiration)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu n'a pas été renouvelé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le jour d'expiration arrive (J-0)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le fichier est automatiquement supprimé du device
<span style="color: #9E9E9E"><strong>Et</strong></span> l'espace disque est libéré
<span style="color: #9E9E9E"><strong>Et</strong></span> le compteur est décrémenté (ex: 45/50 → 44/50)</p>
<hr />
<h2 id="21-toast-apres-suppression-automatique-j-0">21. Toast après suppression automatique J-0</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 15 contenus viennent d'expirer</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur ouvre l'app</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit un toast:</p>
<hr />
<h2 id="22-liste-contenus-supprimes-apres-expiration">22. Liste contenus supprimés après expiration</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 15 contenus ont expiré</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte l'historique des suppressions</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois la liste des 15 contenus supprimés:</p>
<pre><code>| titre | créateur | date expiration |
|---|---|---|
| Mon épisode préféré | JeanDupont | 15 juin 2025 |
| Road trip Bretagne | MarieLambert | 15 juin 2025 |
| ... | ... | ... |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je peux les re-télécharger si je veux</p>
<hr />
<h2 id="23-re-telechargement-apres-expiration">23. Re-téléchargement après expiration</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu a expiré et été supprimé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je retrouve ce contenu dans l'app</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le badge ✅ "Offline" n'est plus affiché
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux le re-télécharger normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> la validité repart à 30 jours</p>
<hr />
<h2 id="24-utilisateur-ne-se-connecte-jamais-pendant-30-jours">24. Utilisateur ne se connecte jamais pendant 30 jours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je télécharge 50 contenus le 1er juin
<span style="color: #F44336"><strong>Mais</strong></span> que je ne me connecte jamais en WiFi pendant 30 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le 1er juillet arrive</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les 50 contenus expirent
<span style="color: #9E9E9E"><strong>Et</strong></span> sont automatiquement supprimés
<span style="color: #9E9E9E"><strong>Et</strong></span> je n'ai plus aucun contenu offline</p>
<hr />
<h2 id="25-utilisateur-en-zone-blanche-30-jours">25. Utilisateur en zone blanche 30+ jours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je télécharge 50 contenus avant de partir en zone sans réseau
<span style="color: #9E9E9E"><strong>Et</strong></span> que je reste 45 jours sans connexion</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les contenus expirent après 30 jours</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ils sont supprimés même si je ne peux pas me connecter
<span style="color: #9E9E9E"><strong>Et</strong></span> je perds l'accès à mes contenus offline</p>
<hr />
<h2 id="26-recommandation-telechargement-avant-zone-blanche-longue">26. Recommandation téléchargement avant zone blanche longue</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je prépare un road trip de 60 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte la FAQ</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois la recommandation:</p>
<hr />
<h2 id="27-changement-statut-premium-en-gratuit-pendant-validite">27. Changement statut Premium en gratuit pendant validité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis Premium et j'ai téléchargé 200 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> mon abonnement Premium expire
<span style="color: #9E9E9E"><strong>Et</strong></span> que je repasse en gratuit</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> au prochain renouvellement, seulement 50 contenus sont conservés
<span style="color: #9E9E9E"><strong>Et</strong></span> les 150 autres sont supprimés (limite gratuit)
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois "Limite gratuit (50 contenus) appliquée. 150 contenus supprimés."</p>
<hr />
<h2 id="28-selection-automatique-50-meilleurs-contenus-si-passage-gratuit">28. Sélection automatique 50 meilleurs contenus si passage gratuit</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je repasse en gratuit avec 200 contenus téléchargés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système applique la limite de 50</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les 50 contenus les plus récemment écoutés sont conservés
<span style="color: #9E9E9E"><strong>Et</strong></span> les 150 autres sont supprimés
<span style="color: #9E9E9E"><strong>Et</strong></span> cela maximise les chances de garder les contenus que j'aime</p>
<hr />
<h2 id="29-contenus-premium-exclusifs-supprimes-si-abonnement-expire">29. Contenus Premium exclusifs supprimés si abonnement expire</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai téléchargé 20 contenus Premium exclusifs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> mon abonnement Premium expire</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les 20 contenus Premium sont immédiatement supprimés
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois "20 contenus Premium supprimés (abonnement expiré)"</p>
<hr />
<h2 id="30-affichage-temps-restant-avant-expiration">30. Affichage temps restant avant expiration</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai 45 contenus téléchargés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte la page Téléchargements</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois pour chaque contenu:</p>
<pre><code>| contenu | temps restant |
|---|---|
| Mon épisode (récent) | Expire dans 28 jours |
| Road trip (ancien) | Expire dans 3 jours |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je sais lesquels sont prioritaires pour renouvellement</p>
<hr />
<h2 id="31-tri-par-date-expiration">31. Tri par date expiration</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai 45 contenus avec différentes dates d'expiration</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je trie par "Expiration"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les contenus qui expirent le plus tôt apparaissent en premier
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux voir rapidement lesquels nécessitent une reconnexion urgente</p>
<hr />
<h2 id="32-badge-rouge-si-expiration-3-jours">32. Badge rouge si expiration &lt;3 jours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu expire dans 2 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte la liste des téléchargements</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu a un badge rouge "⚠️ Expire bientôt"
<span style="color: #9E9E9E"><strong>Et</strong></span> il est visuellement mis en avant</p>
<hr />
<h2 id="33-statistiques-utilisateur-taux-de-renouvellement">33. Statistiques utilisateur - Taux de renouvellement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'accède à mes statistiques</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte la section Téléchargements</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| métrique | valeur |
|---|---|
| Contenus actuels | 45 |
| Contenus expirés depuis début | 87 |
| Contenus renouvelés (auto) | 234 |
| Taux renouvellement automatique | 73% |
</code></pre>
<hr />
<h2 id="34-statistiques-admin-taux-expiration-global">34. Statistiques admin - Taux expiration global</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un admin consulte les métriques offline</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il accède au dashboard</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="35-alerte-admin-si-taux-expiration-10">35. Alerte admin si taux expiration &gt;10%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le taux d'expiration mensuel dépasse 10%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte cette anomalie</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une alerte est envoyée:</p>
<hr />
<h2 id="36-email-rappel-si-pas-de-connexion-wifi-depuis-20-jours">36. Email rappel si pas de connexion WiFi depuis 20 jours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je n'ai pas connecté l'app en WiFi depuis 20 jours
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai 45 contenus téléchargés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte cette inactivité WiFi</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email:</p>
<hr />
<h2 id="37-performance-renouvellement-avec-10-000-utilisateurs-simultanes">37. Performance renouvellement avec 10 000 utilisateurs simultanés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 10 000 utilisateurs se connectent en WiFi simultanément</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> chacun demande le renouvellement de 50 contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le serveur traite 500 000 vérifications
<span style="color: #9E9E9E"><strong>Et</strong></span> grâce au cache Redis et index PostgreSQL, le temps de réponse reste &lt;3s
<span style="color: #9E9E9E"><strong>Et</strong></span> les serveurs gèrent la charge sans problème</p>
<hr />
<h2 id="38-logs-audit-renouvellements">38. Logs audit renouvellements</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu est renouvelé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'opération se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un log est enregistré:</p>
<pre><code>| 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) |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> ces logs aident à débugger les problèmes</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="moderation-preventive_1">Modération préventive</h1>
<p><strong>22 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le système de modération préventive est actif</p>
</blockquote>
<h2 id="1-createur-nouvellement-inscrit">1. Créateur nouvellement inscrit</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je viens de créer un compte créateur
<span style="color: #9E9E9E"><strong>Et</strong></span> que je n'ai jamais publié de contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'examine mon statut de créateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon compte est marqué comme "Nouveau créateur"
<span style="color: #9E9E9E"><strong>Et</strong></span> mes 3 premiers contenus devront être validés manuellement
<span style="color: #9E9E9E"><strong>Et</strong></span> je suis informé de ce processus lors de l'onboarding</p>
<hr />
<h2 id="2-publication-du-premier-contenu-par-un-nouveau-createur">2. Publication du premier contenu par un nouveau créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un nouveau créateur
<span style="color: #9E9E9E"><strong>Et</strong></span> que je n'ai publié aucun contenu auparavant</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je publie mon premier contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu entre en file d'attente de validation manuelle
<span style="color: #9E9E9E"><strong>Et</strong></span> le statut du contenu est "En attente de validation"
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu n'est pas diffusé sur la plateforme
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois une notification:</p>
<hr />
<h2 id="3-validation-manuelle-par-un-moderateur">3. Validation manuelle par un modérateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai publié mon premier contenu
<span style="color: #9E9E9E"><strong>Et</strong></span> que le contenu est en attente de validation</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un modérateur examine mon contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le modérateur utilise la transcription automatique Whisper
<span style="color: #9E9E9E"><strong>Et</strong></span> le modérateur vérifie:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> si tout est conforme, le contenu est validé</p>
<hr />
<h2 id="4-delai-de-validation-de-24-48h-jours-ouvres">4. Délai de validation de 24-48h jours ouvrés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai publié mon premier contenu lundi à 10:00</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le contenu entre en file de validation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est validé avant mercredi 10:00 (48h jours ouvrés)
<span style="color: #9E9E9E"><strong>Et</strong></span> dans la plupart des cas, la validation est effectuée sous 24h
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois une notification dès que le contenu est validé</p>
<hr />
<h2 id="5-notification-de-validation-reussie">5. Notification de validation réussie</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon premier contenu a été validé par un modérateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la validation est approuvée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification:
<span style="color: #9E9E9E"><strong>Et</strong></span> le statut du contenu passe à "Publié"
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu devient visible pour tous les utilisateurs
<span style="color: #9E9E9E"><strong>Et</strong></span> il entre dans l'algorithme de recommandation</p>
<hr />
<h2 id="6-refus-de-validation-si-contenu-non-conforme">6. Refus de validation si contenu non conforme</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon premier contenu viole les règles de la communauté</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur examine le contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est refusé
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois une notification détaillée:
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu reste en statut "Refusé"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux modifier et republier</p>
<hr />
<h2 id="7-les-3-premiers-contenus-sont-valides-manuellement">7. Les 3 premiers contenus sont validés manuellement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un nouveau créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je publie mes contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les contenus suivants nécessitent une validation manuelle:</p>
<pre><code>| contenu | validation manuelle |
|---|---|
| 1er | oui |
| 2ème | oui |
| 3ème | oui |
| 4ème | non (auto) |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> après 3 contenus validés, mes futurs contenus sont publiés automatiquement</p>
<hr />
<h2 id="8-passage-en-mode-automatique-apres-3-validations">8. Passage en mode automatique après 3 validations</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes 3 premiers contenus ont été validés avec succès</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je publie mon 4ème contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est publié automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune validation manuelle n'est requise
<span style="color: #9E9E9E"><strong>Et</strong></span> le statut passe directement à "Publié"
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois une notification:</p>
<hr />
<h2 id="9-evolution-du-score-de-confiance">9. Évolution du score de confiance</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur établi</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système évalue mon historique</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un score de confiance est calculé basé sur:</p>
<pre><code>| 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% |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> le score évolue dynamiquement</p>
<hr />
<h2 id="10-createur-fiable-publication-automatique">10. Créateur fiable - Publication automatique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai 0 strike depuis 6 mois
<span style="color: #9E9E9E"><strong>Et</strong></span> que tous mes contenus précédents ont été conformes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> mon score de confiance est calculé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je suis classé comme "Créateur fiable"
<span style="color: #9E9E9E"><strong>Et</strong></span> tous mes nouveaux contenus sont publiés automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune validation manuelle n'est nécessaire
<span style="color: #9E9E9E"><strong>Et</strong></span> je bénéficie d'une publication instantanée</p>
<hr />
<h2 id="11-createur-suspect-validation-manuelle-systematique">11. Créateur suspect - Validation manuelle systématique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai reçu 2 strikes récents (&lt; 3 mois)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> mon score de confiance est recalculé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je suis classé comme "Créateur suspect"
<span style="color: #9E9E9E"><strong>Et</strong></span> tous mes nouveaux contenus nécessitent une validation manuelle
<span style="color: #9E9E9E"><strong>Et</strong></span> chaque contenu est examiné avant publication
<span style="color: #9E9E9E"><strong>Et</strong></span> je suis notifié de ce changement de statut:</p>
<hr />
<h2 id="12-rehabilitation-apres-periode-sans-incident">12. Réhabilitation après période sans incident</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'étais un "Créateur suspect"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je publie 10 contenus conformes sur 6 mois
<span style="color: #9E9E9E"><strong>Et</strong></span> que je ne reçois aucun nouveau strike</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système réévalue mon score de confiance</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je passe en "Créateur fiable"
<span style="color: #9E9E9E"><strong>Et</strong></span> la publication automatique est rétablie
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois une notification de réhabilitation:</p>
<hr />
<h2 id="13-toute-publicite-necessite-validation-manuelle">13. Toute publicité nécessite validation manuelle</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un annonceur soumet une publicité audio</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la publicité est créée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> elle entre automatiquement en file de validation manuelle
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune publicité n'est diffusée sans validation préalable
<span style="color: #9E9E9E"><strong>Et</strong></span> cela est obligatoire pour des raisons de responsabilité juridique</p>
<hr />
<h2 id="14-validation-dune-publicite-processus-complet">14. Validation d'une publicité - Processus complet</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité est en attente de validation</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un modérateur senior examine la publicité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le modérateur vérifie:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> si tout est conforme, la publicité est validée</p>
<hr />
<h2 id="15-delai-de-validation-dune-publicite-24-48h">15. Délai de validation d'une publicité - 24-48h</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un annonceur soumet une publicité lundi à 10:00</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la publicité entre en file de validation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publicité est validée avant mercredi 10:00 (48h jours ouvrés)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'annonceur est notifié dès la validation
<span style="color: #9E9E9E"><strong>Et</strong></span> la campagne publicitaire peut alors démarrer</p>
<hr />
<h2 id="16-refus-de-validation-dune-publicite">16. Refus de validation d'une publicité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité contient des éléments non conformes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur examine la publicité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publicité est refusée
<span style="color: #9E9E9E"><strong>Et</strong></span> l'annonceur reçoit une notification détaillée:
<span style="color: #9E9E9E"><strong>Et</strong></span> l'annonceur peut modifier et resoumettre la publicité
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun remboursement n'est effectué pour une publicité refusée</p>
<hr />
<h2 id="17-economie-de-moderation-grace-a-la-prevention">17. Économie de modération grâce à la prévention</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la modération préventive est active</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on analyse l'efficacité du système</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 80% des contenus inappropriés sont détectés avant publication
<span style="color: #9E9E9E"><strong>Et</strong></span> cela réduit le nombre de signalements de 70%
<span style="color: #9E9E9E"><strong>Et</strong></span> les ressources de modération sont optimisées
<span style="color: #9E9E9E"><strong>Et</strong></span> la qualité de la plateforme est préservée dès le début</p>
<hr />
<h2 id="18-qualite-de-la-plateforme-maintenue">18. Qualité de la plateforme maintenue</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que tous les nouveaux créateurs sont vérifiés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on analyse la qualité globale des contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le taux de contenus inappropriés est &lt;1%
<span style="color: #9E9E9E"><strong>Et</strong></span> les utilisateurs font confiance à la plateforme
<span style="color: #9E9E9E"><strong>Et</strong></span> la réputation de RoadWave est préservée
<span style="color: #9E9E9E"><strong>Et</strong></span> l'expérience utilisateur est optimale</p>
<hr />
<h2 id="19-information-claire-sur-le-processus-de-validation">19. Information claire sur le processus de validation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un nouveau créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte la page d'aide "Validation des contenus"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> j'apprends que:
<span style="color: #9E9E9E"><strong>Et</strong></span> le processus est clair et transparent</p>
<hr />
<h2 id="20-badge-createur-verifie-apres-validation">20. Badge "Créateur vérifié" après validation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes 3 premiers contenus ont été validés avec succès</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mon profil créateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un badge discret "✓ Créateur vérifié" s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> ce badge rassure les auditeurs sur la qualité de mes contenus
<span style="color: #9E9E9E"><strong>Et</strong></span> il améliore ma crédibilité sur la plateforme</p>
<hr />
<h2 id="21-justification-de-la-moderation-preventive">21. Justification de la modération préventive</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la modération préventive est en place</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on évalue les bénéfices</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les avantages suivants sont constatés:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> l'investissement dans la prévention est rentable</p>
<hr />
<h2 id="22-cout-de-la-moderation-preventive">22. Coût de la modération préventive</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 100 nouveaux créateurs publient 3 contenus chacun
<span style="color: #9E9E9E"><strong>Et</strong></span> que 50 publicités sont soumises par mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on calcule le coût de modération préventive</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le coût en temps modérateur est:</p>
<pre><code>| type | nombre | temps/contenu | total |
|---|---|---|---|
| Nouveaux créateurs | 300 | 5 min | 25h |
| Publicités | 50 | 10 min | 8.3h |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> le coût total est d'environ 33h de modération/mois
<span style="color: #9E9E9E"><strong>Et</strong></span> c'est largement compensé par la réduction des signalements réactifs</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="sanctions-et-notifications-de-moderation">Sanctions et notifications de modération</h1>
<p><strong>27 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur de contenu
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai publié un contenu</p>
</blockquote>
<h2 id="1-notification-multi-canal-apres-sanction">1. Notification multi-canal après sanction</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon contenu a été modéré</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la sanction est appliquée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification sur 3 canaux:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> chaque canal contient un lien vers les détails complets</p>
<hr />
<h2 id="2-notification-push-immediate">2. Notification push immédiate</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon contenu vient d'être modéré</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la sanction est appliquée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification push immédiate
<span style="color: #9E9E9E"><strong>Et</strong></span> le message est court: "⚠️ Votre contenu a été modéré"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux cliquer pour voir les détails
<span style="color: #9E9E9E"><strong>Et</strong></span> la notification utilise Firebase Cloud Messaging (Android) ou APNs (iOS)
<span style="color: #9E9E9E"><strong>Et</strong></span> le coût est de 0€</p>
<hr />
<h2 id="3-popup-in-app-au-prochain-lancement">3. Popup in-app au prochain lancement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon contenu a été modéré</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ouvre l'application</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une popup détaillée s'affiche automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> la popup contient:</p>
<pre><code>| élément | description |
|---|---|
| Titre du contenu | "Mon podcast #42" |
| Icône d'avertissement | ⚠️ |
| Catégorie violée | 🚫 Haine &amp; violence |
| Sanction | Strike 2/4 - Suspension 7 jours |
| Bouton "Voir détails" | Redirige vers page détaillée |
| Bouton "Compris" | Ferme la popup |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je ne peux pas fermer la popup sans l'avoir vue</p>
<hr />
<h2 id="4-email-de-notification-complet-dans-lheure">4. Email de notification complet dans l'heure</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon contenu a été modéré à 14:00</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la sanction est appliquée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email avant 15:00 (dans l'heure)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'objet de l'email est "Modération de votre contenu \"[Titre du contenu]\""
<span style="color: #9E9E9E"><strong>Et</strong></span> l'email contient toutes les informations détaillées
<span style="color: #9E9E9E"><strong>Et</strong></span> le coût est d'environ 0.001€ par email (Brevo, Resend)</p>
<hr />
<h2 id="5-email-de-notification-complet-et-structure">5. Email de notification complet et structuré</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon contenu "Mon podcast #42" a été modéré</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je reçois l'email de notification</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'email contient la structure suivante:</p>
<hr />
<h2 id="6-page-detaillee-de-la-sanction-in-app">6. Page détaillée de la sanction in-app</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je clique sur "Voir détails" dans la notification</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la page détaillée s'affiche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois les 6 éléments obligatoires:</p>
<pre><code>| élément | contenu |
|---|---|
| 1. Catégorie violée | 🚫 Haine &amp; 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 |
</code></pre>
<hr />
<h2 id="7-affichage-du-passage-problematique-avec-timestamp">7. Affichage du passage problématique avec timestamp</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la page détaillée de la sanction est affichée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte l'extrait audio concerné</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le timestamp exact est affiché: "3:42-4:15"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux écouter uniquement cette portion de l'audio
<span style="color: #9E9E9E"><strong>Et</strong></span> un player audio intégré permet l'écoute du passage
<span style="color: #9E9E9E"><strong>Et</strong></span> la transcription correspondante est affichée en dessous
<span style="color: #9E9E9E"><strong>Et</strong></span> les mots/phrases problématiques sont surlignés en rouge</p>
<hr />
<h2 id="8-reference-precise-aux-cgu">8. Référence précise aux CGU</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la sanction fait référence à l'Article 3.2 des CGU</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Article 3.2"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je suis redirigé vers la section correspondante des CGU
<span style="color: #9E9E9E"><strong>Et</strong></span> la section "Haine &amp; violence" est mise en évidence
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux lire exactement ce qui est interdit
<span style="color: #9E9E9E"><strong>Et</strong></span> cela m'aide à comprendre mon erreur</p>
<hr />
<h2 id="9-gravite-de-la-sanction-avec-systeme-de-strikes">9. Gravité de la sanction avec système de strikes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que c'est mon 2ème strike</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les détails de la sanction</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois clairement "Strike 2/4"
<span style="color: #9E9E9E"><strong>Et</strong></span> les conséquences sont explicitées:
<span style="color: #9E9E9E"><strong>Et</strong></span> je comprends l'escalade des sanctions</p>
<hr />
<h2 id="10-acces-au-formulaire-dappel-depuis-la-notification">10. Accès au formulaire d'appel depuis la notification</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai reçu une notification de modération</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Contester cette décision"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je suis redirigé vers le formulaire d'appel
<span style="color: #9E9E9E"><strong>Et</strong></span> le formulaire est pré-rempli avec les informations de la sanction
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux commencer à rédiger mon appel</p>
<hr />
<h2 id="11-acces-au-formulaire-dappel-depuis-mes-sanctions">11. Accès au formulaire d'appel depuis "Mes sanctions"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai reçu une sanction il y a 2 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ouvre "Profil créateur &gt; Mes sanctions"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois la liste de mes sanctions
<span style="color: #9E9E9E"><strong>Et</strong></span> chaque sanction a un bouton "Faire appel" (si délai &lt;7j)
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux accéder au formulaire d'appel</p>
<hr />
<h2 id="12-structure-du-formulaire-dappel">12. Structure du formulaire d'appel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ouvre le formulaire d'appel</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le formulaire s'affiche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois les champs suivants:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> tous les champs obligatoires sont marqués d'un astérisque</p>
<hr />
<h2 id="13-validation-du-formulaire-dappel">13. Validation du formulaire d'appel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je remplis le formulaire d'appel</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Soumettre l'appel"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système valide les champs obligatoires
<span style="color: #9E9E9E"><strong>Et</strong></span> si un champ obligatoire est vide, une erreur s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> si la raison fait moins de 50 caractères, une erreur s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> si tout est valide, l'appel est soumis</p>
<hr />
<h2 id="14-confirmation-apres-soumission-de-lappel">14. Confirmation après soumission de l'appel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai soumis un appel valide</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'appel est enregistré</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un numéro de ticket unique est généré: "#MOD-2026-00142"
<span style="color: #9E9E9E"><strong>Et</strong></span> un email de confirmation est envoyé:
<span style="color: #9E9E9E"><strong>Et</strong></span> le statut de l'appel est "En cours d'examen"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux suivre le statut dans "Mes sanctions"</p>
<hr />
<h2 id="15-delai-de-soumission-de-7-jours-maximum">15. Délai de soumission de 7 jours maximum</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai reçu une sanction le 2026-01-15</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de faire appel le 2026-01-25 (10 jours plus tard)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le formulaire d'appel est désactivé
<span style="color: #9E9E9E"><strong>Et</strong></span> un message s'affiche:
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne peux plus contester la sanction</p>
<hr />
<h2 id="16-bouton-faire-appel-visible-si-delai-respecte">16. Bouton "Faire appel" visible si délai respecté</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai reçu une sanction il y a 3 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte "Mes sanctions"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le bouton "Faire appel" est actif
<span style="color: #9E9E9E"><strong>Et</strong></span> un compteur indique "4 jours restants pour faire appel"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux cliquer pour soumettre un appel</p>
<hr />
<h2 id="17-sla-de-72h-garanti-pour-appel-standard">17. SLA de 72h garanti pour appel standard</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai soumis un appel standard le lundi à 10:00</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'appel est en cours de traitement</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un modérateur senior est assigné
<span style="color: #9E9E9E"><strong>Et</strong></span> l'appel doit être traité avant jeudi 10:00 (72h - 3 jours ouvrés)
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois une réponse dans ce délai</p>
<hr />
<h2 id="18-appel-complexe-avec-notification-intermediaire">18. Appel complexe avec notification intermédiaire</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai soumis un appel complexe
<span style="color: #9E9E9E"><strong>Et</strong></span> que le traitement nécessite plus de 72h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> 3 jours se sont écoulés</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email de notification intermédiaire:
<span style="color: #9E9E9E"><strong>Et</strong></span> l'appel est traité sous 5 jours ouvrés au total
<span style="color: #9E9E9E"><strong>Et</strong></span> un modérateur senior + admin modération examinent le cas</p>
<hr />
<h2 id="19-appel-critique-traite-en-24h">19. Appel CRITIQUE traité en 24h</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai reçu une suspension longue ou un ban
<span style="color: #9E9E9E"><strong>Et</strong></span> que je soumets un appel</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'appel est classé en priorité CRITIQUE</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'admin modération traite l'appel sous 24h
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois une réponse rapide
<span style="color: #9E9E9E"><strong>Et</strong></span> le cas est examiné en priorité absolue</p>
<hr />
<h2 id="20-reponse-finale-detaillee-appel-accepte">20. Réponse finale détaillée - Appel accepté</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon appel est accepté</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je reçois la réponse finale</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'email contient:</p>
<pre><code>| é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" |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> le strike est retiré de mon compte
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu est rétabli sur la plateforme
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux continuer normalement</p>
<hr />
<h2 id="21-reponse-finale-detaillee-appel-rejete">21. Réponse finale détaillée - Appel rejeté</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon appel est rejeté</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je reçois la réponse finale</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'email contient:</p>
<pre><code>| é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" |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> la sanction reste active
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne peux pas faire de second appel
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois respecter la suspension</p>
<hr />
<h2 id="22-reponse-finale-reduction-de-sanction">22. Réponse finale - Réduction de sanction</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon appel est partiellement accepté</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je reçois la réponse finale</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la décision est "Réduction de sanction"
<span style="color: #9E9E9E"><strong>Et</strong></span> l'email explique:
<span style="color: #9E9E9E"><strong>Et</strong></span> le strike est réduit
<span style="color: #9E9E9E"><strong>Et</strong></span> la suspension est raccourcie
<span style="color: #9E9E9E"><strong>Et</strong></span> je suis notifié de la nouvelle date de fin</p>
<hr />
<h2 id="23-suivi-du-statut-de-lappel-in-app">23. Suivi du statut de l'appel in-app</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai soumis un appel</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte "Mes sanctions"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois le statut actuel de l'appel:</p>
<pre><code>| statut | badge | couleur |
|---|---|---|
| En cours d'examen | En cours 🔍 | orange |
| Appel accepté | Accepté ✓ | vert |
| Appel rejeté | Rejeté ✗ | rouge |
| Sanction réduite | Partiellement accepté | bleu |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> une notification badge m'alerte quand le statut change</p>
<hr />
<h2 id="24-historique-complet-des-sanctions-visible">24. Historique complet des sanctions visible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ouvre "Profil créateur &gt; Mes sanctions"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois la liste complète de mes sanctions passées:</p>
<pre><code>| colonne | description |
|---|---|
| Date | 15/01/2026 |
| Contenu | "Mon podcast #42" |
| Catégorie | 🚫 Haine &amp; violence |
| Sanction | Strike 2 - Suspension 7j |
| Statut | Active / Terminée / Annulée |
| Appel | Aucun / Accepté / Rejeté |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> les sanctions sont triées par date décroissante</p>
<hr />
<h2 id="25-conformite-dsa-transparence-obligatoire">25. Conformité DSA - Transparence obligatoire</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le système de sanction est en place</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un audit DSA est effectué</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> chaque sanction contient:</p>
<pre><code>| é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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> le système est conforme au Digital Services Act</p>
<hr />
<h2 id="26-decision-definitive-apres-premier-appel">26. Décision définitive après premier appel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon premier appel a été rejeté</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de faire un second appel</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le bouton "Faire appel" est désactivé
<span style="color: #9E9E9E"><strong>Et</strong></span> un message s'affiche: "Cette décision est définitive. Aucun second appel n'est possible."
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne peux plus contester la sanction
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois respecter la décision finale</p>
<hr />
<h2 id="27-cout-des-notifications-multi-canal">27. Coût des notifications multi-canal</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 100 sanctions sont appliquées en un mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on calcule le coût des notifications</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le coût total est d'environ 0.10€:</p>
<pre><code>| canal | coût unitaire | coût pour 100 |
|---|---|---|
| Email | 0.001€ | 0.10€ |
| Push | 0€ | 0€ |
| In-app | 0€ | 0€ |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> le coût est négligeable même à grande échelle</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="signalement-de-contenu-inapproprie">Signalement de contenu inapproprié</h1>
<p><strong>23 scénarios</strong> (22 standards, 1 plan)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur connecté
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis en train d'écouter un contenu</p>
</blockquote>
<h2 id="1-affichage-du-formulaire-de-signalement">1. Affichage du formulaire de signalement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu inapproprié</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ouvre le menu du contenu
<span style="color: #9E9E9E"><strong>Et</strong></span> que je clique sur "Signaler"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un formulaire de signalement s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> le formulaire contient une liste déroulante "Catégorie du problème"
<span style="color: #9E9E9E"><strong>Et</strong></span> le formulaire contient un champ texte "Commentaire (optionnel)"
<span style="color: #9E9E9E"><strong>Et</strong></span> le formulaire contient un bouton "Envoyer le signalement"</p>
<hr />
<h2 id="2-liste-des-7-categories-predefinies">2. Liste des 7 catégories prédéfinies</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le formulaire de signalement est affiché</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur la liste déroulante "Catégorie du problème"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois les 7 catégories suivantes:</p>
<pre><code>| icône | catégorie | description |
|---|---|---|
| 🚫 | Haine &amp; 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é |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> chaque catégorie a une description claire</p>
<hr />
<h2 id="3-selection-de-la-categorie-haine-violence">3. Sélection de la catégorie "Haine &amp; violence"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le formulaire de signalement est affiché</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne la catégorie "🚫 Haine &amp; violence"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la catégorie est sélectionnée
<span style="color: #9E9E9E"><strong>Et</strong></span> la description "Incitation à la haine, discrimination, menaces" s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux passer au champ commentaire</p>
<hr />
<h2 id="4-categorie-autre-necessite-un-commentaire-obligatoire">4. Catégorie "Autre" nécessite un commentaire obligatoire</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le formulaire de signalement est affiché</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne la catégorie "🔧 Autre"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le champ "Commentaire" devient obligatoire
<span style="color: #9E9E9E"><strong>Et</strong></span> un message s'affiche: "Veuillez décrire le problème (obligatoire)"
<span style="color: #9E9E9E"><strong>Et</strong></span> le placeholder change en "Décrivez le problème rencontré"
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne peux pas envoyer le signalement sans commentaire</p>
<hr />
<h2 id="5-champ-commentaire-optionnel-avec-incitation">5. Champ commentaire optionnel avec incitation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le formulaire de signalement est affiché
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai sélectionné une catégorie autre que "Autre"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte le champ "Commentaire"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le champ est optionnel (pas d'astérisque rouge)
<span style="color: #9E9E9E"><strong>Et</strong></span> le placeholder indique "Décrivez le problème (optionnel mais recommandé)"
<span style="color: #9E9E9E"><strong>Et</strong></span> la limite de caractères est de 500
<span style="color: #9E9E9E"><strong>Et</strong></span> un compteur affiche "0/500"</p>
<hr />
<h2 id="6-envoi-de-signalement-sans-commentaire">6. Envoi de signalement sans commentaire</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai sélectionné la catégorie "📧 Spam"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je n'ai pas rempli le champ commentaire</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Envoyer le signalement"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le signalement est envoyé avec succès
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune erreur de validation ne s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> le commentaire est enregistré comme vide</p>
<hr />
<h2 id="7-envoi-de-signalement-avec-commentaire">7. Envoi de signalement avec commentaire</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai sélectionné la catégorie "🚫 Haine &amp; violence"
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai saisi le commentaire "Le créateur tient des propos discriminatoires à 2:30"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Envoyer le signalement"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le signalement est envoyé avec succès
<span style="color: #9E9E9E"><strong>Et</strong></span> le commentaire est enregistré avec le signalement
<span style="color: #9E9E9E"><strong>Et</strong></span> il sera visible par les modérateurs</p>
<hr />
<h2 id="8-limite-de-500-caracteres-pour-le-commentaire">8. Limite de 500 caractères pour le commentaire</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le formulaire de signalement est affiché</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je saisis un commentaire de 501 caractères</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le champ limite automatiquement à 500 caractères
<span style="color: #9E9E9E"><strong>Et</strong></span> le compteur affiche "500/500"
<span style="color: #9E9E9E"><strong>Et</strong></span> les caractères supplémentaires ne sont pas acceptés</p>
<hr />
<h2 id="9-toast-de-confirmation-apres-signalement">9. Toast de confirmation après signalement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai envoyé un signalement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le signalement est enregistré</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un toast notification s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> le toast contient le message "✓ Signalement envoyé. Nous l'examinerons sous 24-48h."
<span style="color: #9E9E9E"><strong>Et</strong></span> le toast s'affiche pendant 5 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> le toast contient un bouton "Voir mes signalements"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux fermer le toast manuellement avec un bouton X</p>
<hr />
<h2 id="10-acces-a-lhistorique-des-signalements-via-le-toast">10. Accès à l'historique des signalements via le toast</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le toast de confirmation est affiché</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Voir mes signalements"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je suis redirigé vers la page "Mes signalements"
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois la liste de tous mes signalements
<span style="color: #9E9E9E"><strong>Et</strong></span> le signalement que je viens d'envoyer apparaît en premier</p>
<hr />
<h2 id="11-historique-personnel-des-signalements">11. Historique personnel des signalements</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai envoyé 3 signalements précédemment</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ouvre "Profil &gt; Mes signalements"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois la liste de mes 3 signalements
<span style="color: #9E9E9E"><strong>Et</strong></span> chaque signalement affiche:</p>
<pre><code>| information | description |
|---|---|
| Titre du contenu | "Podcast #42" |
| Créateur | @pseudo_createur |
| Catégorie | 🚫 Haine &amp; violence |
| Date | 15/01/2026 |
| Statut | En cours / Traité / Rejeté |
| Mon commentaire | Texte que j'ai saisi |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> les signalements sont triés par date décroissante</p>
<hr />
<h2 id="12-plan-statuts-possibles-dun-signalement">12. 📋 Plan: Statuts possibles d'un signalement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai envoyé un signalement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le statut du signalement est "<statut>"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le badge affiché est "<badge>"
<span style="color: #9E9E9E"><strong>Et</strong></span> la couleur du badge est "<couleur>"</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>statut</th>
<th>badge</th>
<th>couleur</th>
</tr>
</thead>
<tbody>
<tr>
<td>En cours</td>
<td>En cours</td>
<td>orange</td>
</tr>
<tr>
<td>Traité</td>
<td>Traité ✓</td>
<td>vert</td>
</tr>
<tr>
<td>Rejeté</td>
<td>Rejeté ✗</td>
<td>rouge</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="13-notification-in-app-si-action-prise">13. Notification in-app si action prise</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai signalé un contenu il y a 24h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur traite mon signalement
<span style="color: #9E9E9E"><strong>Et</strong></span> que le contenu est effectivement retiré</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification in-app
<span style="color: #9E9E9E"><strong>Et</strong></span> la notification indique "Votre signalement a été traité. Le contenu a été retiré."
<span style="color: #9E9E9E"><strong>Et</strong></span> le statut de mon signalement passe à "Traité ✓"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux voir les détails de l'action prise</p>
<hr />
<h2 id="14-notification-si-signalement-rejete">14. Notification si signalement rejeté</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai signalé un contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur rejette mon signalement</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification in-app
<span style="color: #9E9E9E"><strong>Et</strong></span> la notification indique "Votre signalement a été examiné. Le contenu ne viole pas les règles de la communauté."
<span style="color: #9E9E9E"><strong>Et</strong></span> le statut de mon signalement passe à "Rejeté ✗"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux voir la raison du rejet</p>
<hr />
<h2 id="15-un-contenu-peut-etre-signale-plusieurs-fois">15. Un contenu peut être signalé plusieurs fois</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu a déjà été signalé par 5 autres utilisateurs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je signale le même contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon signalement est enregistré indépendamment
<span style="color: #9E9E9E"><strong>Et</strong></span> le compteur de signalements du contenu passe à 6
<span style="color: #9E9E9E"><strong>Et</strong></span> mon signalement rejoint la file d'attente de modération
<span style="color: #9E9E9E"><strong>Et</strong></span> les signalements cumulés augmentent la priorité de traitement</p>
<hr />
<h2 id="16-limite-de-signalements-par-utilisateur">16. Limite de signalements par utilisateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai déjà signalé le même contenu il y a 2 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de signaler à nouveau le même contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message m'informe "Vous avez déjà signalé ce contenu"
<span style="color: #9E9E9E"><strong>Et</strong></span> le formulaire de signalement n'est pas affiché
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux consulter le statut de mon signalement précédent</p>
<hr />
<h2 id="17-detection-de-signalements-abusifs-repetes">17. Détection de signalements abusifs répétés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai envoyé 10 signalements ce mois-ci
<span style="color: #9E9E9E"><strong>Et</strong></span> que 8 d'entre eux ont été rejetés comme infondés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie d'envoyer un nouveau signalement</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon compte est marqué comme "signaleur suspect"
<span style="color: #9E9E9E"><strong>Et</strong></span> un avertissement s'affiche:
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux toujours envoyer le signalement
<span style="color: #F44336"><strong>Mais</strong></span> mes futurs signalements auront une priorité réduite</p>
<hr />
<h2 id="18-sanction-pour-signalements-abusifs-graves">18. Sanction pour signalements abusifs graves</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai envoyé 20 signalements abusifs en 1 mois
<span style="color: #9E9E9E"><strong>Et</strong></span> que tous ont été rejetés comme volontairement faux</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur détecte le pattern abusif</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon compte reçoit un avertissement formel
<span style="color: #9E9E9E"><strong>Et</strong></span> je perds la possibilité de signaler pendant 30 jours
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un email m'expliquant la sanction</p>
<hr />
<h2 id="19-signalement-depuis-le-player-audio">19. Signalement depuis le player audio</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ouvre le menu "⋮" du player</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois l'option "Signaler"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux ouvrir le formulaire de signalement</p>
<hr />
<h2 id="20-signalement-depuis-la-page-de-details-du-contenu">20. Signalement depuis la page de détails du contenu</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte la page de détails d'un contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur le bouton "⋮" en haut à droite</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois l'option "Signaler"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux ouvrir le formulaire de signalement</p>
<hr />
<h2 id="21-signalement-depuis-lhistorique-decoute">21. Signalement depuis l'historique d'écoute</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte mon historique d'écoute</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "⋮" à côté d'un contenu passé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois l'option "Signaler"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux signaler ce contenu même si je ne l'écoute plus actuellement</p>
<hr />
<h2 id="22-identite-du-signaleur-anonyme-pour-le-createur">22. Identité du signaleur anonyme pour le créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai signalé un contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur est notifié de la modération</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon identité reste anonyme
<span style="color: #9E9E9E"><strong>Et</strong></span> le créateur ne peut pas savoir qui a signalé
<span style="color: #9E9E9E"><strong>Et</strong></span> seuls les modérateurs ont accès à l'identité du signaleur</p>
<hr />
<h2 id="23-cout-du-systeme-de-signalement">23. Coût du système de signalement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le système de signalement est en place</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on calcule le coût</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le coût est de 0€
<span style="color: #9E9E9E"><strong>Et</strong></span> le formulaire est développé en interne
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun service tiers n'est utilisé
<span style="color: #9E9E9E"><strong>Et</strong></span> les notifications in-app sont gratuites</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="traitement-des-signalements-par-lia-et-les-moderateurs">Traitement des signalements par l'IA et les modérateurs</h1>
<p><strong>25 scénarios</strong> (21 standards, 4 plans)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le système de modération est actif</p>
</blockquote>
<h2 id="1-signalement-ajoute-a-la-file-dattente-asynchrone">1. Signalement ajouté à la file d'attente asynchrone</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur envoie un signalement pour un contenu audio</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le signalement est reçu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le signalement est ajouté à la file d'attente asynchrone
<span style="color: #9E9E9E"><strong>Et</strong></span> un worker de traitement est déclenché
<span style="color: #9E9E9E"><strong>Et</strong></span> le traitement se fait en arrière-plan sans bloquer l'utilisateur</p>
<hr />
<h2 id="2-transcription-automatique-avec-whisper-large-v3">2. Transcription automatique avec Whisper large-v3</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu audio signalé dure 5 minutes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le worker de traitement démarre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système utilise Whisper large-v3 pour transcrire l'audio
<span style="color: #9E9E9E"><strong>Et</strong></span> la transcription est en self-hosted (pas de service cloud)
<span style="color: #9E9E9E"><strong>Et</strong></span> le texte transcrit est enregistré en base de données
<span style="color: #9E9E9E"><strong>Et</strong></span> le délai de transcription est de 1-3 minutes</p>
<hr />
<h2 id="3-plan-delai-de-transcription-selon-duree-audio">3. 📋 Plan: Délai de transcription selon durée audio</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu audio signalé dure <duree> minutes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système transcrit l'audio</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la transcription prend environ <delai></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>duree</th>
<th>delai</th>
</tr>
</thead>
<tbody>
<tr>
<td>2</td>
<td>1-3 minutes</td>
</tr>
<tr>
<td>10</td>
<td>3-10 minutes</td>
</tr>
<tr>
<td>45</td>
<td>10-20 minutes</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="4-analyse-automatique-du-contenu-transcrit">4. Analyse automatique du contenu transcrit</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la transcription audio est terminée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système analyse le texte transcrit</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les analyses suivantes sont effectuées:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> chaque analyse génère un score de confiance (0-100%)</p>
<hr />
<h2 id="5-generation-du-score-de-confiance-ia">5. Génération du score de confiance IA</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que toutes les analyses sont terminées</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système calcule le score final</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un score de confiance IA entre 0-100% est généré
<span style="color: #9E9E9E"><strong>Et</strong></span> le score indique la probabilité que le contenu viole les règles
<span style="color: #9E9E9E"><strong>Et</strong></span> la catégorie la plus probable est identifiée
<span style="color: #9E9E9E"><strong>Et</strong></span> les timestamps des passages problématiques sont extraits</p>
<hr />
<h2 id="6-detection-automatique-de-contenu-clairement-inapproprie">6. Détection automatique de contenu clairement inapproprié</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu contient des insultes graves et répétées</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'IA analyse la transcription</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le score de confiance IA est &gt;95%
<span style="color: #9E9E9E"><strong>Et</strong></span> la catégorie détectée est "Haine &amp; violence"
<span style="color: #9E9E9E"><strong>Et</strong></span> les passages problématiques sont identifiés avec timestamps:</p>
<pre><code>| timestamp | texte problématique |
|---|---|
| 02:15 | [insulte discriminatoire] |
| 03:42 | [propos haineux] |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> le signalement est classé en priorité CRITIQUE</p>
<hr />
<h2 id="7-plan-sla-selon-priorite-du-signalement">7. 📋 Plan: SLA selon priorité du signalement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un signalement a une priorité "<priorite>"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le signalement entre en file d'attente</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le délai de traitement cible est "<delai>"
<span style="color: #9E9E9E"><strong>Et</strong></span> le responsable du traitement est "<responsable>"</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>priorite</th>
<th>delai</th>
<th>responsable</th>
</tr>
</thead>
<tbody>
<tr>
<td>CRITIQUE</td>
<td>&lt;2h (24/7)</td>
<td>Modérateur senior (astreinte)</td>
</tr>
<tr>
<td>HAUTE</td>
<td>&lt;24h (jours ouvrés)</td>
<td>Modérateur junior/senior</td>
</tr>
<tr>
<td>MOYENNE</td>
<td>&lt;24h (jours ouvrés)</td>
<td>Modérateur junior</td>
</tr>
<tr>
<td>BASSE</td>
<td>&lt;72h (jours ouvrés)</td>
<td>Modérateur junior</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="8-traitement-automatique-pour-score-ia-95">8. Traitement automatique pour score IA &gt;95%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un signalement a un score IA de 97%
<span style="color: #9E9E9E"><strong>Et</strong></span> que la catégorie détectée est "Spam" (évidente)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système évalue le signalement</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une action automatique immédiate est déclenchée
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu est retiré automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> le créateur est notifié de la modération
<span style="color: #9E9E9E"><strong>Et</strong></span> le créateur peut faire appel de la décision
<span style="color: #9E9E9E"><strong>Et</strong></span> un modérateur senior vérifie l'action a posteriori</p>
<hr />
<h2 id="9-signalement-critique-traite-en-moins-de-2h">9. Signalement CRITIQUE traité en moins de 2h</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un signalement de priorité CRITIQUE est reçu à 14:00
<span style="color: #9E9E9E"><strong>Et</strong></span> que le contenu concerne une menace de violence</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le signalement est assigné à un modérateur senior d'astreinte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le modérateur est alerté immédiatement (push + SMS)
<span style="color: #9E9E9E"><strong>Et</strong></span> le signalement est traité avant 16:00 (2h)
<span style="color: #9E9E9E"><strong>Et</strong></span> une décision est prise et appliquée
<span style="color: #9E9E9E"><strong>Et</strong></span> les autorités peuvent être contactées si nécessaire</p>
<hr />
<h2 id="10-astreinte-moderateur-247-pour-signalements-critiques">10. Astreinte modérateur 24/7 pour signalements CRITIQUES</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un signalement CRITIQUE est reçu un dimanche à 03:00</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le signalement est classé en priorité CRITIQUE</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le modérateur senior d'astreinte est alerté
<span style="color: #9E9E9E"><strong>Et</strong></span> le signalement est traité dans les 2h (avant 05:00)
<span style="color: #9E9E9E"><strong>Et</strong></span> le service d'astreinte garantit une disponibilité 24/7</p>
<hr />
<h2 id="11-signalement-haute-priorite-traite-en-moins-de-24h">11. Signalement HAUTE priorité traité en moins de 24h</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un signalement de priorité HAUTE est reçu lundi à 10:00
<span style="color: #9E9E9E"><strong>Et</strong></span> que le contenu concerne du harcèlement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le signalement entre en file d'attente</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le signalement est assigné à un modérateur (junior ou senior)
<span style="color: #9E9E9E"><strong>Et</strong></span> le signalement est traité avant mardi 10:00 (24h jours ouvrés)
<span style="color: #9E9E9E"><strong>Et</strong></span> une décision est prise et appliquée</p>
<hr />
<h2 id="12-signalement-basse-priorite-traite-en-moins-de-72h">12. Signalement BASSE priorité traité en moins de 72h</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un signalement de priorité BASSE est reçu lundi à 10:00
<span style="color: #9E9E9E"><strong>Et</strong></span> que le contenu concerne des tags incorrects</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le signalement entre en file d'attente</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le signalement est traité avant jeudi 10:00 (72h jours ouvrés)
<span style="color: #9E9E9E"><strong>Et</strong></span> un modérateur junior peut traiter ce type de signalement</p>
<hr />
<h2 id="13-calcul-du-score-de-priorite">13. Calcul du score de priorité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un signalement a les caractéristiques suivantes:</p>
<pre><code>| caractéristique | valeur |
|---|---|
| Score IA | 85% |
| Signalements cumulés | 3 |
| Fiabilité du signaleur | 75% |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système calcule la priorité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la formule appliquée est:
<span style="color: #9E9E9E"><strong>Et</strong></span> le score de priorité est: (85 × 0.7) + (3 × 0.2) + (75 × 0.1) = 67.5
<span style="color: #9E9E9E"><strong>Et</strong></span> le signalement est classé en priorité MOYENNE</p>
<hr />
<h2 id="14-plan-classification-selon-score-de-priorite">14. 📋 Plan: Classification selon score de priorité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un signalement a un score de priorité de <score></p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système classe le signalement</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la priorité assignée est "<priorite>"
<span style="color: #9E9E9E"><strong>Et</strong></span> le signalement entre dans la file "<file>"</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>score</th>
<th>priorite</th>
<th>file</th>
</tr>
</thead>
<tbody>
<tr>
<td>95</td>
<td>CRITIQUE</td>
<td>Immédiate</td>
</tr>
<tr>
<td>82</td>
<td>HAUTE</td>
<td>Prioritaire</td>
</tr>
<tr>
<td>55</td>
<td>MOYENNE</td>
<td>Normale</td>
</tr>
<tr>
<td>25</td>
<td>BASSE</td>
<td>Différée</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="15-boost-de-priorite-avec-signalements-cumules">15. Boost de priorité avec signalements cumulés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu a été signalé par 1 utilisateur avec un score IA de 60%
<span style="color: #9E9E9E"><strong>Et</strong></span> que le signalement est classé en priorité MOYENNE (score 42)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> 5 autres utilisateurs signalent le même contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le nombre de signalements cumulés passe à 6
<span style="color: #9E9E9E"><strong>Et</strong></span> le score de priorité augmente significativement
<span style="color: #9E9E9E"><strong>Et</strong></span> le signalement peut passer en priorité HAUTE
<span style="color: #9E9E9E"><strong>Et</strong></span> le traitement est accéléré</p>
<hr />
<h2 id="16-impact-de-la-fiabilite-du-signaleur">16. Impact de la fiabilité du signaleur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur de confiance (90% fiabilité) envoie un signalement
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un utilisateur suspect (20% fiabilité) envoie un signalement similaire</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système calcule les priorités</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le signalement de l'utilisateur de confiance a un score plus élevé
<span style="color: #9E9E9E"><strong>Et</strong></span> son signalement est traité en priorité
<span style="color: #9E9E9E"><strong>Et</strong></span> le signalement de l'utilisateur suspect est traité plus tard</p>
<hr />
<h2 id="17-evolution-du-score-de-fiabilite-du-signaleur">17. Évolution du score de fiabilité du signaleur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a envoyé 10 signalements
<span style="color: #9E9E9E"><strong>Et</strong></span> que 8 d'entre eux ont été acceptés par les modérateurs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système calcule son score de fiabilité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le score est de 80% (8 acceptés / 10 total)
<span style="color: #9E9E9E"><strong>Et</strong></span> ses futurs signalements auront plus de poids
<span style="color: #9E9E9E"><strong>Et</strong></span> il peut devenir "utilisateur de confiance"</p>
<hr />
<h2 id="18-files-dattente-separees-par-priorite">18. Files d'attente séparées par priorité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 50 signalements sont en attente</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système organise la file d'attente</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les signalements sont répartis dans les files suivantes:</p>
<pre><code>| file | nombre | priorité |
|---|---|---|
| Immédiate (24/7) | 5 | CRITIQUE |
| Prioritaire | 15 | HAUTE |
| Normale | 20 | MOYENNE |
| Différée | 10 | BASSE |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> les modérateurs traitent en priorité la file Immédiate</p>
<hr />
<h2 id="19-moderateurs-assignes-selon-competences">19. Modérateurs assignés selon compétences</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un signalement complexe de harcèlement est reçu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système assigne un modérateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un modérateur senior est prioritairement assigné
<span style="color: #9E9E9E"><strong>Et</strong></span> les modérateurs juniors peuvent traiter les cas simples (spam, tags)
<span style="color: #9E9E9E"><strong>Et</strong></span> les modérateurs seniors traitent les cas complexes (haine, violence, appels)</p>
<hr />
<h2 id="20-stack-technique-100-opensource">20. Stack technique 100% opensource</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le système de modération IA est déployé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on analyse les technologies utilisées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> toutes les technologies sont opensource:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> aucune dépendance à Google, AWS, Azure</p>
<hr />
<h2 id="21-plan-cout-selon-phase-du-projet">21. 📋 Plan: Coût selon phase du projet</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave est en phase "<phase>"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on calcule le coût de l'infrastructure IA</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le coût mensuel est "<cout>"</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>phase</th>
<th>cout</th>
</tr>
</thead>
<tbody>
<tr>
<td>MVP</td>
<td>0-50€ (CPU)</td>
</tr>
<tr>
<td>Scale</td>
<td>50-200€ (GPU VPS)</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="22-processing-asynchrone-en-mvp-avec-cpu">22. Processing asynchrone en MVP avec CPU</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave est en phase MVP
<span style="color: #9E9E9E"><strong>Et</strong></span> que le volume est &lt;1000 signalements/mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système traite les signalements</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un serveur CPU standard est suffisant
<span style="color: #9E9E9E"><strong>Et</strong></span> le coût est de 0€ (serveur existant)
<span style="color: #9E9E9E"><strong>Et</strong></span> le processing asynchrone absorbe les pics de charge
<span style="color: #9E9E9E"><strong>Et</strong></span> les délais restent acceptables (1-20 minutes)</p>
<hr />
<h2 id="23-scaling-avec-gpu-pour-gros-volumes">23. Scaling avec GPU pour gros volumes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave reçoit &gt;1000 signalements/jour</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système nécessite un scaling</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un VPS avec GPU est requis
<span style="color: #9E9E9E"><strong>Et</strong></span> le coût passe à 50-200€/mois
<span style="color: #9E9E9E"><strong>Et</strong></span> les délais de transcription sont divisés par 5-10
<span style="color: #9E9E9E"><strong>Et</strong></span> le système peut gérer 10 000+ signalements/mois</p>
<hr />
<h2 id="24-logs-daudit-pour-chaque-traitement">24. Logs d'audit pour chaque traitement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un signalement est traité</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> une action est prise (rejet, acceptation, sanction)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un log d'audit complet est créé:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> le log est conservé pour conformité DSA
<span style="color: #9E9E9E"><strong>Et</strong></span> les logs sont anonymisés après 3 ans (RGPD)</p>
<hr />
<h2 id="25-tracabilite-complete-pour-conformite-dsa">25. Traçabilité complète pour conformité DSA</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le système de modération est actif</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un audit DSA est effectué</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> toutes les actions de modération sont tracées
<span style="color: #9E9E9E"><strong>Et</strong></span> les délais de traitement sont mesurés et respectés
<span style="color: #9E9E9E"><strong>Et</strong></span> les décisions sont justifiées et documentées
<span style="color: #9E9E9E"><strong>Et</strong></span> la transparence vis-à-vis des utilisateurs est garantie
<span style="color: #9E9E9E"><strong>Et</strong></span> le système est conforme au Digital Services Act</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="conditions-dactivation-de-la-monetisation">Conditions d'activation de la monétisation</h1>
<blockquote>
<p><em>En tant que créateur</em>
<em>Je veux pouvoir activer la monétisation quand je remplis les critères</em>
<em>Afin de générer des revenus avec mes contenus</em></p>
</blockquote>
<p><strong>28 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis connecté en tant que créateur</p>
</blockquote>
<h2 id="1-critere-1-anciennete-de-3-mois-validee">1. Critère 1 - Ancienneté de 3 mois validée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon compte a été créé il y a 91 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les critères de monétisation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le critère "Ancienneté ≥ 3 mois" est validé ✅</p>
<hr />
<h2 id="2-critere-1-anciennete-insuffisante">2. Critère 1 - Ancienneté insuffisante</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon compte a été créé il y a 60 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les critères de monétisation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le critère "Ancienneté ≥ 3 mois" n'est pas validé ❌
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois "Encore 30 jours avant d'être éligible"</p>
<hr />
<h2 id="3-critere-2-500-abonnes-atteints">3. Critère 2 - 500 abonnés atteints</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai exactement 500 abonnés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les critères de monétisation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le critère "≥ 500 abonnés" est validé ✅</p>
<hr />
<h2 id="4-critere-2-pas-assez-dabonnes">4. Critère 2 - Pas assez d'abonnés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai 347 abonnés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les critères de monétisation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le critère "≥ 500 abonnés" n'est pas validé ❌
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois "Encore 153 abonnés nécessaires"</p>
<hr />
<h2 id="5-critere-3-10-000-ecoutes-completes-atteintes">5. Critère 3 - 10 000 écoutes complètes atteintes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes contenus ont cumulé 10 487 écoutes complètes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les critères de monétisation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le critère "≥ 10 000 écoutes complètes" est validé ✅</p>
<hr />
<h2 id="6-critere-3-ecoutes-incompletes-non-comptabilisees">6. Critère 3 - Écoutes incomplètes non comptabilisées</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes contenus ont:</p>
<pre><code>| type écoute | nombre |
|---|---|
| Écoutes complètes | 8 500 |
| Écoutes &lt;80% | 3 000 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les critères de monétisation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seules les 8 500 écoutes complètes comptent
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois "Encore 1 500 écoutes complètes nécessaires"</p>
<hr />
<h2 id="7-critere-4-aucun-strike-actif">7. Critère 4 - Aucun strike actif</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je n'ai aucun strike actif
<span style="color: #9E9E9E"><strong>Et</strong></span> que je n'ai eu aucun contenu modéré dans les 6 derniers mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les critères de monétisation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le critère "Fiabilité" est validé ✅</p>
<hr />
<h2 id="8-critere-4-strike-actif-bloque-leligibilite">8. Critère 4 - Strike actif bloque l'éligibilité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai 1 strike actif pour contenu inapproprié</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les critères de monétisation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le critère "Fiabilité" n'est pas validé ❌
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois "Vous devez résoudre votre strike avant d'être éligible"</p>
<hr />
<h2 id="9-critere-4-contenu-modere-dans-les-6-derniers-mois">9. Critère 4 - Contenu modéré dans les 6 derniers mois</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je n'ai pas de strike actif
<span style="color: #F44336"><strong>Mais</strong></span> qu'un de mes contenus a été modéré il y a 4 mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les critères de monétisation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le critère "Fiabilité" n'est pas validé ❌
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois "Attendre 2 mois après le dernier contenu modéré"</p>
<hr />
<h2 id="10-critere-5-5-contenus-publies-dans-les-90-derniers-jours">10. Critère 5 - 5 contenus publiés dans les 90 derniers jours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai publié:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les critères de monétisation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le critère "≥ 5 contenus publiés dans les 90 derniers jours" est validé ✅</p>
<hr />
<h2 id="11-critere-5-contenus-trop-anciens-ne-comptent-pas">11. Critère 5 - Contenus trop anciens ne comptent pas</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai publié:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les critères de monétisation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seuls 2 contenus comptent (dans les 90 jours)
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois "Encore 3 contenus à publier dans les 90 prochains jours"</p>
<hr />
<h2 id="12-tous-les-criteres-valides-bouton-disponible">12. Tous les critères validés - Bouton disponible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que tous mes critères sont validés:</p>
<pre><code>| critère | statut |
|---|---|
| Ancienneté ≥ 3 mois | ✅ |
| ≥ 500 abonnés | ✅ |
| ≥ 10 000 écoutes | ✅ |
| Fiabilité | ✅ |
| Régularité (5 contenus) | ✅ |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à mon profil créateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le bouton "Demander la monétisation" est actif
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux cliquer pour démarrer le KYC</p>
<hr />
<h2 id="13-criteres-incomplets-bouton-grise-avec-progression">13. Critères incomplets - Bouton grisé avec progression</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes critères sont:</p>
<pre><code>| 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% |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à mon profil créateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le bouton "Demander la monétisation" est grisé
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois la progression détaillée de chaque critère</p>
<hr />
<h2 id="14-verification-automatique-sql-lors-de-la-demande">14. Vérification automatique SQL lors de la demande</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je clique sur "Demander la monétisation"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système vérifie mes critères</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une requête SQL est exécutée:
<span style="color: #9E9E9E"><strong>Et</strong></span> si tous les critères sont TRUE, je suis redirigé vers le KYC</p>
<hr />
<h2 id="15-notification-par-email-quand-criteres-atteints">15. Notification par email quand critères atteints</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je viens d'atteindre 500 abonnés
<span style="color: #9E9E9E"><strong>Et</strong></span> que c'était mon dernier critère manquant</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte l'éligibilité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email:</p>
<hr />
<h2 id="16-badge-eligible-monetisation-dans-profil">16. Badge "Éligible monétisation" dans profil</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je remplis tous les critères
<span style="color: #F44336"><strong>Mais</strong></span> que je n'ai pas encore activé la monétisation</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un utilisateur consulte mon profil</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit un badge "Éligible monétisation 💰"
<span style="color: #9E9E9E"><strong>Et</strong></span> cela renforce ma crédibilité de créateur</p>
<hr />
<h2 id="17-justification-anti-fraude-delai-3-mois">17. Justification anti-fraude - Délai 3 mois</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un compte suspect crée du contenu frauduleux</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le compte est détecté dans les 2 premiers mois</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le compte est banni avant d'atteindre les 3 mois
<span style="color: #9E9E9E"><strong>Et</strong></span> le créateur n'a jamais été éligible à la monétisation
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun paiement n'a été effectué</p>
<hr />
<h2 id="18-justification-qualite-10-000-ecoutes">18. Justification qualité - 10 000 écoutes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur produit du contenu de mauvaise qualité</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> ses contenus ne génèrent que 2 000 écoutes complètes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il ne peut pas activer la monétisation
<span style="color: #9E9E9E"><strong>Et</strong></span> seuls les créateurs avec contenu apprécié sont monétisés</p>
<hr />
<h2 id="19-reduction-cout-administratif-plateforme">19. Réduction coût administratif plateforme</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave a 10 000 créateurs inscrits
<span style="color: #9E9E9E"><strong>Et</strong></span> que seuls 500 remplissent tous les critères</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système calcule le coût administratif</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seulement 500 KYC sont à gérer (vs 10 000)
<span style="color: #9E9E9E"><strong>Et</strong></span> seulement 500 virements mensuels (vs 10 000)
<span style="color: #9E9E9E"><strong>Et</strong></span> la charge comptable est réduite de 95%</p>
<hr />
<h2 id="20-statistiques-publiques-pour-transparence">20. Statistiques publiques pour transparence</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un utilisateur consulte la page "Devenir créateur"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit les statistiques:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> cela permet de fixer des attentes réalistes</p>
<hr />
<h2 id="21-cache-redis-pour-calcul-rapide-criteres">21. Cache Redis pour calcul rapide critères</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte mes critères de monétisation</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système charge la page</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les compteurs sont récupérés depuis Redis:</p>
<pre><code>| clé Redis | exemple valeur |
|---|---|
| creator:[id]:subscribers_count | 347 |
| creator:[id]:complete_listens_total | 8500 |
| creator:[id]:recent_contents_count | 7 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> le temps de réponse est &lt;50ms</p>
<hr />
<h2 id="22-mise-a-jour-temps-reel-des-compteurs">22. Mise à jour temps réel des compteurs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je viens de publier un nouveau contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un utilisateur écoute ce contenu en entier</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le compteur "complete_listens_total" est incrémenté immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> si je rafraîchis la page critères, je vois la nouvelle valeur
<span style="color: #9E9E9E"><strong>Et</strong></span> cela encourage les créateurs à continuer de produire</p>
<hr />
<h2 id="23-historique-des-tentatives-dactivation">23. Historique des tentatives d'activation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai tenté d'activer la monétisation il y a 2 mois
<span style="color: #F44336"><strong>Mais</strong></span> que les critères n'étaient pas remplis</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à mes logs d'activité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| date | action | résultat | raison |
|---|---|---|---|
| 2025-11-15 | Demande monétisation | Refusée | Seulement 300 abonnés |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> cela m'aide à suivre ma progression</p>
<hr />
<h2 id="24-performance-avec-100-000-createurs">24. Performance avec 100 000 créateurs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave a 100 000 créateurs
<span style="color: #9E9E9E"><strong>Et</strong></span> que chacun consulte ses critères 1 fois par jour</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système traite ces requêtes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la table users est indexée sur created_at
<span style="color: #9E9E9E"><strong>Et</strong></span> la table subscriptions est indexée sur creator_id
<span style="color: #9E9E9E"><strong>Et</strong></span> la table contents est indexée sur creator_id et published_at
<span style="color: #9E9E9E"><strong>Et</strong></span> chaque requête reste &lt;50ms grâce aux index</p>
<hr />
<h2 id="25-export-des-criteres-pour-support-client">25. Export des critères pour support client</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je contacte le support car je pense être éligible</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'agent support consulte mon compte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit un export JSON complet:
<span style="color: #9E9E9E"><strong>Et</strong></span> l'agent peut expliquer précisément pourquoi je ne suis pas éligible</p>
<hr />
<h2 id="26-notification-30-jours-avant-eligibilite-probable">26. Notification 30 jours avant éligibilité probable</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes critères sont:</p>
<pre><code>| critère | statut | progression |
|---|---|---|
| Ancienneté ≥ 3 mois | ❌ | 60/90 jours |
| Tous les autres critères | ✅ | 100% |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il reste exactement 30 jours avant les 90 jours</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification:</p>
<hr />
<h2 id="27-pas-de-bypass-possible-pour-amisinfluenceurs">27. Pas de bypass possible pour amis/influenceurs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur influent me contacte directement
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il demande un bypass des critères</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte la politique RoadWave</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la réponse est "Aucune exception possible, critères automatiques uniquement"
<span style="color: #9E9E9E"><strong>Et</strong></span> cela garantit l'équité pour tous les créateurs</p>
<hr />
<h2 id="28-ab-test-futur-sur-seuils-post-mvp">28. A/B test futur sur seuils (post-MVP)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave veut tester des seuils différents</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un A/B test est lancé en 2027</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> groupe A voit: 500 abonnés, 10 000 écoutes
<span style="color: #9E9E9E"><strong>Et</strong></span> groupe B voit: 300 abonnés, 5 000 écoutes
<span style="color: #9E9E9E"><strong>Et</strong></span> les métriques (taux activation, fraude, qualité) sont comparées
<span style="color: #9E9E9E"><strong>Et</strong></span> le meilleur seuil est déployé définitivement</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="contenus-premium-exclusifs">Contenus Premium exclusifs</h1>
<blockquote>
<p><em>En tant que créateur monétisé</em>
<em>Je veux pouvoir rendre certains contenus exclusifs aux abonnés Premium</em>
<em>Afin d'inciter les utilisateurs à s'abonner</em></p>
</blockquote>
<p><strong>34 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur avec la monétisation activée</p>
</blockquote>
<h2 id="1-toggle-reserve-premium-lors-de-la-creation">1. Toggle "Réservé Premium" lors de la création</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée un nouveau contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède aux options de publication</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois un toggle "Réservé aux abonnés Premium 👑"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux l'activer ou le désactiver</p>
<hr />
<h2 id="2-contenu-marque-premium-lors-de-la-creation">2. Contenu marqué Premium lors de la création</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée un nouveau contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'active le toggle "Réservé Premium"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je publie le contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le champ <code>is_premium</code> en base est mis à <code>true</code>
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu est visible uniquement pour les utilisateurs Premium</p>
<hr />
<h2 id="3-contenu-gratuit-par-defaut">3. Contenu gratuit par défaut</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée un nouveau contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je ne touche pas au toggle "Réservé Premium"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je publie le contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le champ <code>is_premium</code> en base est mis à <code>false</code> (défaut)
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu est accessible à tous les utilisateurs</p>
<hr />
<h2 id="4-modification-dun-contenu-existant-en-premium">4. Modification d'un contenu existant en Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai publié un contenu gratuit il y a 2 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je modifie le contenu et active le toggle "Réservé Premium"
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'enregistre les modifications</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu devient immédiatement Premium
<span style="color: #9E9E9E"><strong>Et</strong></span> les utilisateurs gratuits ne peuvent plus y accéder</p>
<hr />
<h2 id="5-passage-dun-contenu-premium-en-gratuit">5. Passage d'un contenu Premium en gratuit</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai publié un contenu Premium il y a 1 mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je modifie le contenu et désactive le toggle "Réservé Premium"
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'enregistre les modifications</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu devient immédiatement gratuit
<span style="color: #9E9E9E"><strong>Et</strong></span> tous les utilisateurs peuvent maintenant y accéder</p>
<hr />
<h2 id="6-aucune-limite-sur-pourcentage-de-contenus-premium">6. Aucune limite sur pourcentage de contenus Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je publie 10 nouveaux contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je décide de rendre les 10 contenus Premium (100%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système accepte sans limitation
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux avoir 100% de mon catalogue en Premium</p>
<hr />
<h2 id="7-strategie-freemium-mix-gratuitpremium">7. Stratégie freemium - Mix gratuit/premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je publie 10 nouveaux contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je décide de rendre 5 contenus Premium et 5 gratuits (50/50)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système accepte cette stratégie
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux tester différents mix pour optimiser mes revenus</p>
<hr />
<h2 id="8-strategie-tout-gratuit-possible">8. Stratégie tout gratuit possible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis monétisé via publicités</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je décide de ne mettre aucun contenu en Premium (0%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système accepte cette stratégie
<span style="color: #9E9E9E"><strong>Et</strong></span> je génère des revenus uniquement via les publicités</p>
<hr />
<h2 id="9-badge-visible-sur-linterface-utilisateur">9. Badge 👑 visible sur l'interface utilisateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur consulte ma liste de contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il voit un contenu Premium</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un badge 👑 "Premium" est affiché
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu est clairement identifiable comme réservé</p>
<hr />
<h2 id="10-utilisateur-gratuit-voit-les-contenus-premium-dans-la-liste">10. Utilisateur gratuit voit les contenus Premium dans la liste</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur gratuit</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les contenus d'un créateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois aussi les contenus Premium dans la liste
<span style="color: #9E9E9E"><strong>Et</strong></span> ils sont affichés avec un badge 👑
<span style="color: #F44336"><strong>Mais</strong></span> je ne peux pas les lire</p>
<hr />
<h2 id="11-tentative-de-lecture-premium-par-utilisateur-gratuit-overlay-bloquant">11. Tentative de lecture Premium par utilisateur gratuit - Overlay bloquant</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur gratuit</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur un contenu Premium pour le lire</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un overlay bloquant apparaît
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message:
<span style="color: #9E9E9E"><strong>Et</strong></span> un bouton "Passer Premium" est affiché</p>
<hr />
<h2 id="12-cta-passer-premium-redirige-vers-abonnement">12. CTA "Passer Premium" redirige vers abonnement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je vois l'overlay de contenu Premium bloqué</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Passer Premium"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je suis redirigé vers la page d'abonnement Premium
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux m'abonner pour 4.99€/mois</p>
<hr />
<h2 id="13-utilisateur-premium-peut-lire-tous-les-contenus-premium">13. Utilisateur Premium peut lire tous les contenus Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur Premium actif</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur un contenu Premium</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu se lance immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> je n'ai aucun overlay bloquant
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux profiter pleinement du contenu exclusif</p>
<hr />
<h2 id="14-contenus-premium-inclus-dans-les-recommandations">14. Contenus Premium inclus dans les recommandations</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'algorithme génère ma file de 5 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je suis un utilisateur gratuit</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les contenus Premium peuvent apparaître dans les recommandations
<span style="color: #9E9E9E"><strong>Et</strong></span> cela me fait découvrir qu'il existe du contenu exclusif</p>
<hr />
<h2 id="15-contenu-premium-skippe-automatiquement-pour-utilisateur-gratuit">15. Contenu Premium skippé automatiquement pour utilisateur gratuit</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur gratuit
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un contenu Premium apparaît dans ma file de recommandation</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute le contenu précédent jusqu'à la fin</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu Premium est automatiquement skippé
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu suivant (gratuit) est lancé
<span style="color: #9E9E9E"><strong>Et</strong></span> le slot Premium ne compte pas dans ma file de 5 contenus</p>
<hr />
<h2 id="16-contenu-premium-diffuse-normalement-pour-utilisateur-premium">16. Contenu Premium diffusé normalement pour utilisateur Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur Premium
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un contenu Premium apparaît dans ma file de recommandation</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute le contenu précédent jusqu'à la fin</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu Premium est lancé normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> je profite du contenu exclusif sans interruption</p>
<hr />
<h2 id="17-champ-is_premium-boolean-en-base-postgresql">17. Champ <code>is_premium</code> boolean en base PostgreSQL</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu est créé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il est stocké en base de données</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la table <code>contents</code> contient un champ <code>is_premium BOOLEAN DEFAULT FALSE</code>
<span style="color: #9E9E9E"><strong>Et</strong></span> ce champ est indexé pour requêtes rapides</p>
<hr />
<h2 id="18-index-postgresql-sur-is_premium">18. Index PostgreSQL sur <code>is_premium</code></h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'algorithme doit filtrer les contenus selon le statut Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> une requête SQL est exécutée:</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'index sur <code>is_premium</code> accélère la requête
<span style="color: #9E9E9E"><strong>Et</strong></span> le temps de réponse reste &lt;20ms</p>
<hr />
<h2 id="19-cache-redis-pour-statut-premium">19. Cache Redis pour statut Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu Premium est consulté fréquemment</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'API vérifie le statut Premium</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la valeur est récupérée depuis Redis:
<span style="color: #9E9E9E"><strong>Et</strong></span> le cache a un TTL de 1 heure
<span style="color: #9E9E9E"><strong>Et</strong></span> cela évite des requêtes SQL inutiles</p>
<hr />
<h2 id="20-invalidation-cache-lors-de-modification-statut-premium">20. Invalidation cache lors de modification statut Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu est passé de gratuit à Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur enregistre la modification</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le cache Redis <code>content:[id]:premium</code> est invalidé immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> la nouvelle valeur est mise à jour
<span style="color: #9E9E9E"><strong>Et</strong></span> les utilisateurs voient le changement en temps réel</p>
<hr />
<h2 id="21-justification-liberte-createur-strategie-personnalisee">21. Justification liberté créateur - Stratégie personnalisée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que chaque créateur a une audience différente</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un créateur décide de sa stratégie Premium</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il peut tester différentes approches:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="22-justification-incitation-premium-argument-fort-pour-sabonner">22. Justification incitation Premium - Argument fort pour s'abonner</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur gratuit voit beaucoup de contenus Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il consulte les profils de ses créateurs préférés</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit que 60% de leur contenu est réservé Premium
<span style="color: #9E9E9E"><strong>Et</strong></span> cela l'incite à s'abonner pour 4.99€/mois
<span style="color: #9E9E9E"><strong>Et</strong></span> RoadWave augmente son taux de conversion vers Premium</p>
<hr />
<h2 id="23-justification-equite-petit-createur-peut-tout-mettre-en-premium">23. Justification équité - Petit créateur peut tout mettre en Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un petit créateur avec 600 abonnés
<span style="color: #9E9E9E"><strong>Et</strong></span> que 50 sont abonnés Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je mets 100% de mon contenu en Premium</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je génère des revenus uniquement via mes 50 abonnés Premium
<span style="color: #9E9E9E"><strong>Et</strong></span> cela me permet de vivre de mon contenu malgré une petite audience</p>
<hr />
<h2 id="24-justification-equite-gros-createur-peut-tout-offrir-gratuitement">24. Justification équité - Gros créateur peut tout offrir gratuitement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un gros créateur avec 50 000 abonnés
<span style="color: #9E9E9E"><strong>Et</strong></span> que je génère déjà beaucoup de revenus publicitaires</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je laisse 100% de mon contenu gratuit</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je maximise mon audience et mes revenus pub
<span style="color: #9E9E9E"><strong>Et</strong></span> je n'ai pas besoin de mettre du contenu en Premium</p>
<hr />
<h2 id="25-statistiques-createur-ratio-premiumgratuit">25. Statistiques créateur - Ratio Premium/Gratuit</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'accède à mon tableau de bord créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mes statistiques de contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="26-statistiques-createur-revenus-par-type">26. Statistiques créateur - Revenus par type</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai des contenus gratuits et Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mes revenus détaillés</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| source | montant |
|---|---|
| Revenus pub (gratuit) | 86.70€ |
| Revenus Premium (exclusifs) | 34.20€ |
| Revenus Premium (tout contenu) | 78.90€ |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je peux comparer l'efficacité de chaque stratégie</p>
<hr />
<h2 id="27-notification-createur-contenu-premium-tres-ecoute">27. Notification créateur - Contenu Premium très écouté</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai publié un contenu Premium il y a 3 jours
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il a généré 5 000 écoutes Premium (très élevé)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte cette performance</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification:</p>
<hr />
<h2 id="28-ab-test-utilisateur-impact-badge-premium-sur-conversion">28. A/B test utilisateur - Impact badge Premium sur conversion</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave veut optimiser le taux de conversion Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un A/B test est lancé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> groupe A voit le badge 👑 "Premium"
<span style="color: #9E9E9E"><strong>Et</strong></span> groupe B voit le badge 💎 "Exclusif"
<span style="color: #9E9E9E"><strong>Et</strong></span> les taux de clic et conversion sont mesurés
<span style="color: #9E9E9E"><strong>Et</strong></span> le badge le plus performant est déployé définitivement</p>
<hr />
<h2 id="29-analytics-plateforme-adoption-fonctionnalite-premium">29. Analytics plateforme - Adoption fonctionnalité Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave suit l'adoption de la fonctionnalité</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un admin consulte les métriques</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit:</p>
<pre><code>| 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€ |
</code></pre>
<hr />
<h2 id="30-impact-sur-churn-contenus-premium-reduisent-le-churn-premium">30. Impact sur churn - Contenus Premium réduisent le churn Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur Premium envisage de résilier
<span style="color: #F44336"><strong>Mais</strong></span> qu'il a accès à 150 contenus Premium de ses créateurs préférés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il voit la valeur exclusive qu'il perdrait</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il est moins susceptible de résilier (churn réduit de ~30%)
<span style="color: #9E9E9E"><strong>Et</strong></span> les contenus Premium augmentent la rétention</p>
<hr />
<h2 id="31-transparence-createur-voit-combien-de-contenus-premium-il-a">31. Transparence - Créateur voit combien de contenus Premium il a</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'accède à mon profil créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mes contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux filtrer par statut:</p>
<pre><code>| filtre | résultats |
|---|---|
| Tous | 47 |
| Gratuits | 32 |
| Premium 👑 | 15 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je peux facilement gérer mon catalogue</p>
<hr />
<h2 id="32-export-liste-contenus-avec-statut-premium-rgpd">32. Export liste contenus avec statut Premium (RGPD)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je demande l'export de mes données</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'export est généré</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la liste de mes contenus inclut le statut Premium:</p>
<hr />
<h2 id="33-suppression-compte-createur-et-contenus-premium">33. Suppression compte créateur et contenus Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je supprime définitivement mon compte créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la suppression est confirmée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous mes contenus (gratuits et Premium) sont supprimés
<span style="color: #9E9E9E"><strong>Et</strong></span> les utilisateurs Premium ne peuvent plus y accéder
<span style="color: #9E9E9E"><strong>Et</strong></span> les fichiers audio sont supprimés du CDN sous 7 jours</p>
<hr />
<h2 id="34-performance-avec-1-million-de-contenus-premium">34. Performance avec 1 million de contenus Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave a 1 million de contenus dont 300 000 Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme génère une recommandation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la requête SQL filtre efficacement avec l'index <code>is_premium</code>
<span style="color: #9E9E9E"><strong>Et</strong></span> le temps de réponse reste &lt;50ms
<span style="color: #9E9E9E"><strong>Et</strong></span> la scalabilité est garantie</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="desactivation-et-suspension-monetisation">Désactivation et suspension monétisation</h1>
<blockquote>
<p><em>En tant que créateur ou plateforme</em>
<em>Je veux pouvoir désactiver ou suspendre la monétisation selon certaines conditions</em>
<em>Afin de gérer les pauses, problèmes techniques ou violations des règles</em></p>
</blockquote>
<p><strong>35 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur avec la monétisation activée</p>
</blockquote>
<h2 id="1-desactivation-temporaire-par-le-createur">1. Désactivation temporaire par le créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je veux faire une pause dans ma création de contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à "Paramètres &gt; Monétisation"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je clique sur "Désactiver temporairement la monétisation"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma monétisation est désactivée immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne génère plus de revenus à partir de maintenant</p>
<hr />
<h2 id="2-confirmation-avant-desactivation">2. Confirmation avant désactivation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je clique sur "Désactiver temporairement"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> une popup de confirmation apparaît</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois le message:
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois confirmer pour continuer</p>
<hr />
<h2 id="3-solde-conserve-pendant-desactivation">3. Solde conservé pendant désactivation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon solde actuel est 87.45€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je désactive ma monétisation le 15 du mois</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon solde de 87.45€ est conservé
<span style="color: #9E9E9E"><strong>Et</strong></span> il sera reporté au mois suivant
<span style="color: #9E9E9E"><strong>Et</strong></span> si le total dépasse 50€, il sera versé normalement le 15 du mois prochain</p>
<hr />
<h2 id="4-contenus-restent-accessibles-pendant-desactivation">4. Contenus restent accessibles pendant désactivation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai désactivé ma monétisation</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> des utilisateurs écoutent mes contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mes contenus restent accessibles normalement
<span style="color: #F44336"><strong>Mais</strong></span> je ne génère aucun revenu (ni pub ni Premium)</p>
<hr />
<h2 id="5-reactivation-sans-refaire-le-kyc-si-2-ans">5. Réactivation sans refaire le KYC si &lt;2 ans</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai désactivé ma monétisation il y a 8 mois
<span style="color: #9E9E9E"><strong>Et</strong></span> que mes documents KYC sont toujours valides</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Réactiver la monétisation"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la réactivation est immédiate
<span style="color: #9E9E9E"><strong>Et</strong></span> je n'ai pas besoin de refaire le KYC
<span style="color: #9E9E9E"><strong>Et</strong></span> je recommence à générer des revenus dès maintenant</p>
<hr />
<h2 id="6-nouveau-kyc-requis-si-inactivite-2-ans">6. Nouveau KYC requis si inactivité &gt;2 ans</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai désactivé ma monétisation il y a 25 mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de réactiver</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système demande un nouveau KYC
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois:
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois soumettre à nouveau mes documents</p>
<hr />
<h2 id="7-historique-des-desactivationsreactivations">7. Historique des désactivations/réactivations</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai désactivé et réactivé ma monétisation plusieurs fois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à "Paramètres &gt; Monétisation &gt; Historique"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois la liste complète:</p>
<pre><code>| 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é |
</code></pre>
<hr />
<h2 id="8-suspension-si-3-strikes-actifs">8. Suspension si 3+ strikes actifs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je reçois un 3ème strike pour violation des règles</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le strike devient actif</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma monétisation est suspendue automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois:</p>
<hr />
<h2 id="9-reactivation-apres-resolution-des-strikes">9. Réactivation après résolution des strikes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma monétisation est suspendue pour 3 strikes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je résous tous mes strikes (après expiration ou contestation)
<span style="color: #9E9E9E"><strong>Et</strong></span> que mon compteur de strikes passe à 0</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma monétisation est réactivée automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un email de confirmation</p>
<hr />
<h2 id="10-suspension-si-rib-invalide-apres-3-echecs-de-virement">10. Suspension si RIB invalide après 3 échecs de virement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 3 tentatives de virement ont échoué (15, 18, 22 du mois)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le 3ème échec est confirmé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma monétisation est suspendue automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois:</p>
<hr />
<h2 id="11-reactivation-apres-mise-a-jour-rib-valide">11. Réactivation après mise à jour RIB valide</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma monétisation est suspendue pour RIB invalide</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je mets à jour mon RIB avec un compte bancaire valide
<span style="color: #9E9E9E"><strong>Et</strong></span> que Mangopay valide le nouveau RIB</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma monétisation est réactivée automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> un virement est tenté immédiatement pour le solde en attente</p>
<hr />
<h2 id="12-suspension-si-documents-kyc-expires">12. Suspension si documents KYC expirés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma carte d'identité expire dans 30 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je reçois un email de rappel de mise à jour
<span style="color: #F44336"><strong>Mais</strong></span> que je ne mets pas à jour mes documents
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma CNI expire</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma monétisation est suspendue automatiquement après 30 jours de grâce</p>
<hr />
<h2 id="13-preavis-30-jours-avant-suspension-pour-docs-expires">13. Préavis 30 jours avant suspension pour docs expirés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma CNI expire le 15 juin 2025</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le 15 mai 2025 arrive (30 jours avant)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email d'alerte:</p>
<hr />
<h2 id="14-reactivation-apres-renouvellement-documents-kyc">14. Réactivation après renouvellement documents KYC</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma monétisation est suspendue pour CNI expirée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je soumets une nouvelle CNI valide
<span style="color: #9E9E9E"><strong>Et</strong></span> que Mangopay valide le document sous 24-72h</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma monétisation est réactivée automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> je recommence à générer des revenus</p>
<hr />
<h2 id="15-suspension-si-fraude-detectee">15. Suspension si fraude détectée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le système détecte une activité frauduleuse (bots, écoutes artificielles)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'équipe modération confirme la fraude</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma monétisation est suspendue immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> mon compte est mis sous enquête
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un email m'informant de la suspension</p>
<hr />
<h2 id="16-enquete-fraude-verification-manuelle">16. Enquête fraude - Vérification manuelle</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma monétisation est suspendue pour suspicion de fraude</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'équipe modération enquête</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> elle analyse:</p>
<pre><code>| é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 |
</code></pre>
<hr />
<h2 id="17-levee-suspension-si-fraude-non-confirmee">17. Levée suspension si fraude non confirmée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon compte était suspendu pour suspicion de fraude</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'enquête conclut qu'il n'y a pas eu de fraude</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma monétisation est réactivée
<span style="color: #9E9E9E"><strong>Et</strong></span> les revenus suspendus pendant l'enquête sont versés normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un email d'excuses avec explication</p>
<hr />
<h2 id="18-suspension-definitive-si-fraude-confirmee">18. Suspension définitive si fraude confirmée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'enquête confirme une fraude avérée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'équipe modération prend la décision</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma monétisation est définitivement désactivée
<span style="color: #9E9E9E"><strong>Et</strong></span> mon solde en attente est gelé (non versé)
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux recevoir un strike 4 (ban définitif du compte)</p>
<hr />
<h2 id="19-suppression-definitive-sur-demande-createur">19. Suppression définitive sur demande créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je veux arrêter définitivement la monétisation</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à "Paramètres &gt; Monétisation &gt; Supprimer définitivement"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une confirmation stricte est demandée
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois taper "SUPPRIMER" pour confirmer</p>
<hr />
<h2 id="20-solde-verse-sous-30-jours-apres-suppression">20. Solde versé sous 30 jours après suppression</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je supprime définitivement ma monétisation
<span style="color: #9E9E9E"><strong>Et</strong></span> que mon solde en attente est 127.45€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la suppression est confirmée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon solde sera versé sous 30 jours
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un dernier virement de clôture
<span style="color: #9E9E9E"><strong>Et</strong></span> mon e-wallet Mangopay est clôturé</p>
<hr />
<h2 id="21-suppression-auto-si-inactivite-24-mois-solde-50">21. Suppression auto si inactivité 24 mois + solde &lt;50€</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je n'ai plus publié de contenu depuis 24 mois
<span style="color: #9E9E9E"><strong>Et</strong></span> que mon solde en attente est 12.30€ (&lt;50€)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le processus de purge RGPD s'exécute</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma monétisation est automatiquement supprimée
<span style="color: #9E9E9E"><strong>Et</strong></span> mon solde de 12.30€ est perdu (trop faible pour virement)
<span style="color: #9E9E9E"><strong>Et</strong></span> mes données KYC sont archivées puis supprimées selon la législation</p>
<hr />
<h2 id="22-email-de-preavis-60-jours-avant-purge-rgpd">22. Email de préavis 60 jours avant purge RGPD</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis inactif depuis 22 mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte l'inactivité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email:</p>
<hr />
<h2 id="23-ban-definitif-compte-strike-4">23. Ban définitif compte - Strike 4</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je reçois un 4ème strike (violation grave ou répétée)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'équipe modération applique le strike 4</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon compte est banni définitivement
<span style="color: #9E9E9E"><strong>Et</strong></span> ma monétisation est supprimée définitivement
<span style="color: #9E9E9E"><strong>Et</strong></span> mon solde en attente est gelé (non versé)
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne peux plus créer de nouveau compte (blacklist email/SIRET)</p>
<hr />
<h2 id="24-email-pour-toute-suspension">24. Email pour toute suspension</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma monétisation est suspendue (quelle qu'en soit la raison)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la suspension devient effective</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois immédiatement un email:</p>
<hr />
<h2 id="25-notification-in-app-avec-raison-explicite">25. Notification in-app avec raison explicite</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma monétisation est suspendue</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me connecte à l'application</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois une bannière en haut de mon dashboard:</p>
<hr />
<h2 id="26-email-de-confirmation-lors-de-reactivation">26. Email de confirmation lors de réactivation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma monétisation était suspendue</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> elle est réactivée (automatiquement ou manuellement)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email:</p>
<hr />
<h2 id="27-dashboard-admin-suspensions-actives">27. Dashboard admin - Suspensions actives</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un admin RoadWave consulte les suspensions</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il accède au dashboard admin "Monétisation &gt; Suspensions"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit:</p>
<pre><code>| 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% |
</code></pre>
<hr />
<h2 id="28-alertes-si-taux-de-suspension-5">28. Alertes si taux de suspension &gt;5%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le taux de suspension dépasse 5%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte cette anomalie</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une alerte est envoyée à l'équipe:</p>
<hr />
<h2 id="29-statistiques-personnelles-temps-actif-monetisation">29. Statistiques personnelles - Temps actif monétisation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'accède à mon dashboard créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte "Statistiques &gt; Monétisation"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="30-export-donnees-suspension-rgpd">30. Export données suspension (RGPD)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je demande l'export de mes données</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'export est généré</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'historique des suspensions est inclus:</p>
<hr />
<h2 id="31-suppression-compte-et-donnees-monetisation">31. Suppression compte et données monétisation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je supprime définitivement mon compte RoadWave</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la suppression est confirmée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> toutes mes données de monétisation sont supprimées:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="32-conservation-archives-10-ans-obligation-legale">32. Conservation archives 10 ans obligation légale</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je supprime mon compte</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> mes données sont archivées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> RoadWave conserve 10 ans:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> après 10 ans, tout est supprimé définitivement</p>
<hr />
<h2 id="33-suspension-temporaire-pour-maintenance-technique">33. Suspension temporaire pour maintenance technique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que Mangopay effectue une maintenance planifiée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la maintenance est programmée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les créateurs reçoivent un email préventif 7 jours avant:</p>
<hr />
<h2 id="34-reactivation-progressive-apres-incident-majeur">34. Réactivation progressive après incident majeur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un incident technique majeur suspend toutes les monétisations</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'incident est résolu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les réactivations se font progressivement:</p>
<pre><code>| 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% |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> cela évite une surcharge système lors de la reprise</p>
<hr />
<h2 id="35-support-prioritaire-pour-createurs-suspendus-injustement">35. Support prioritaire pour créateurs suspendus injustement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma monétisation est suspendue
<span style="color: #9E9E9E"><strong>Et</strong></span> que je pense que c'est une erreur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je contacte le support avec tag "Suspension monétisation"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon ticket est traité en priorité (SLA 24h)
<span style="color: #9E9E9E"><strong>Et</strong></span> un agent expert examine mon cas
<span style="color: #9E9E9E"><strong>Et</strong></span> si suspension injustifiée, je suis réactivé immédiatement avec excuses</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="kyc-et-inscription-a-la-monetisation">KYC et inscription à la monétisation</h1>
<blockquote>
<p><em>En tant que créateur éligible</em>
<em>Je veux compléter le KYC pour activer la monétisation</em>
<em>Afin de recevoir des paiements légalement</em></p>
</blockquote>
<p><strong>37 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je remplis tous les critères de monétisation
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai cliqué sur "Demander la monétisation"</p>
</blockquote>
<h2 id="1-redirection-vers-formulaire-kyc-mangopay">1. Redirection vers formulaire KYC Mangopay</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je démarre le processus d'activation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je suis redirigé vers un formulaire KYC
<span style="color: #9E9E9E"><strong>Et</strong></span> le formulaire est fourni par Mangopay (iframe sécurisée)
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes les données sont chiffrées et hébergées en EU</p>
<hr />
<h2 id="2-statut-auto-entrepreneur-accepte">2. Statut auto-entrepreneur accepté</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis auto-entrepreneur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je renseigne mon statut juridique</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'option "Auto-entrepreneur (micro-BNC)" est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux continuer le processus</p>
<hr />
<h2 id="3-statut-societe-sarlsassasu-accepte">3. Statut société SARL/SAS/SASU accepté</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai créé une société</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je renseigne mon statut juridique</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les options suivantes sont disponibles:</p>
<pre><code>| statut juridique |
|---|
| SARL |
| SAS |
| SASU |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je peux continuer le processus</p>
<hr />
<h2 id="4-statut-particulier-refuse">4. Statut particulier refusé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je n'ai pas de statut professionnel</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de m'inscrire en tant que "Particulier"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le formulaire affiche:
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne peux pas continuer sans statut professionnel</p>
<hr />
<h2 id="5-document-siret-obligatoire-et-valide">5. Document SIRET obligatoire et validé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je renseigne mon SIRET</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je saisis "12345678901234" (14 chiffres)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le format est validé
<span style="color: #9E9E9E"><strong>Et</strong></span> Mangopay vérifie l'existence du SIRET auprès du répertoire SIRENE
<span style="color: #9E9E9E"><strong>Et</strong></span> si valide, le document est accepté</p>
<hr />
<h2 id="6-siret-invalide-ou-inexistant">6. SIRET invalide ou inexistant</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je renseigne un SIRET inexistant</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je saisis "99999999999999"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Mangopay rejette le SIRET
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois "SIRET non trouvé dans le répertoire SIRENE. Vérifiez le numéro."
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois corriger avant de continuer</p>
<hr />
<h2 id="7-rib-professionnel-obligatoire">7. RIB professionnel obligatoire</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'upload mon RIB</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le RIB est scanné par Mangopay</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système vérifie que le titulaire correspond à mon SIRET
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'IBAN commence par "FR" (compte français)
<span style="color: #9E9E9E"><strong>Et</strong></span> si valide, le document est accepté</p>
<hr />
<h2 id="8-rib-particulier-refuse">8. RIB particulier refusé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'upload un RIB de compte particulier</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Mangopay détecte que le compte n'est pas professionnel</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le RIB est rejeté
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois:</p>
<hr />
<h2 id="9-piece-didentite-cni-en-cours-de-validite">9. Pièce d'identité CNI en cours de validité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'upload ma carte nationale d'identité</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Mangopay analyse le document</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la date d'expiration est vérifiée
<span style="color: #9E9E9E"><strong>Et</strong></span> si la CNI est valide, le document est accepté
<span style="color: #9E9E9E"><strong>Et</strong></span> mon identité est vérifiée par OCR + vérification manuelle</p>
<hr />
<h2 id="10-piece-didentite-expiree-refusee">10. Pièce d'identité expirée refusée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'upload une CNI expirée depuis 2 ans</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Mangopay analyse le document</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le document est rejeté
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois "Pièce d'identité expirée. Veuillez fournir un document en cours de validité."</p>
<hr />
<h2 id="11-passeport-accepte-comme-alternative">11. Passeport accepté comme alternative</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je n'ai pas de CNI</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'upload mon passeport en cours de validité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Mangopay accepte le passeport
<span style="color: #9E9E9E"><strong>Et</strong></span> mon identité est vérifiée de la même manière</p>
<hr />
<h2 id="12-numero-tva-intracommunautaire-si-applicable">12. Numéro TVA intracommunautaire si applicable</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon CA dépasse 37 000€/an
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis sorti de la franchise en base</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je renseigne mon numéro TVA intracommunautaire</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le format "FR + 11 chiffres" est validé
<span style="color: #9E9E9E"><strong>Et</strong></span> Mangopay vérifie l'existence auprès de la Commission Européenne (VIES)</p>
<hr />
<h2 id="13-tva-non-applicable-pour-micro-bnc-sous-franchise">13. TVA non applicable pour micro-BNC sous franchise</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis auto-entrepreneur sous franchise en base
<span style="color: #9E9E9E"><strong>Et</strong></span> que mon CA est &lt;37 000€/an</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je remplis le formulaire KYC</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le champ "Numéro TVA" est optionnel
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux continuer sans TVA</p>
<hr />
<h2 id="14-kbis-3-mois-pour-societes">14. Kbis &lt;3 mois pour sociétés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis gérant d'une SARL</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'upload mon extrait Kbis</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Mangopay vérifie que le Kbis date de moins de 3 mois
<span style="color: #9E9E9E"><strong>Et</strong></span> que le SIRET correspond
<span style="color: #9E9E9E"><strong>Et</strong></span> si valide, le document est accepté</p>
<hr />
<h2 id="15-kbis-trop-ancien-refuse">15. Kbis trop ancien refusé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'upload un Kbis de 5 mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Mangopay analyse le document</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le Kbis est rejeté
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois "Le Kbis doit dater de moins de 3 mois. Téléchargez un extrait récent sur infogreffe.fr"</p>
<hr />
<h2 id="16-verification-identite-ne-correspond-pas-au-compte">16. Vérification identité ne correspond pas au compte</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon compte RoadWave est au nom de "Jean Dupont"
<span style="color: #F44336"><strong>Mais</strong></span> que ma CNI est au nom de "Pierre Martin"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Mangopay compare les identités</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le KYC est rejeté
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois:</p>
<hr />
<h2 id="17-liste-noire-anti-blanchiment-detectee">17. Liste noire anti-blanchiment détectée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon identité apparaît sur une liste anti-blanchiment</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Mangopay effectue la vérification AML (Anti-Money Laundering)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le KYC est automatiquement rejeté
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois "Votre demande ne peut être acceptée pour des raisons de conformité légale"
<span style="color: #9E9E9E"><strong>Et</strong></span> mon compte créateur peut être suspendu</p>
<hr />
<h2 id="18-delai-de-verification-24-72h-si-documents-conformes">18. Délai de vérification 24-72h si documents conformes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai soumis tous les documents valides</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Mangopay traite ma demande</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email "KYC en cours de vérification (24-72h)"
<span style="color: #9E9E9E"><strong>Et</strong></span> mon statut est "En attente de validation"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux continuer à publier des contenus en attendant</p>
<hr />
<h2 id="19-validation-kyc-reussie">19. Validation KYC réussie</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes documents sont conformes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Mangopay valide mon KYC après 48h</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email "Monétisation activée !"
<span style="color: #9E9E9E"><strong>Et</strong></span> mon statut passe à "Monétisé"
<span style="color: #9E9E9E"><strong>Et</strong></span> je commence à générer des revenus dès maintenant</p>
<hr />
<h2 id="20-rejet-kyc-pour-documents-invalides">20. Rejet KYC pour documents invalides</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai soumis une CNI floue et illisible</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Mangopay analyse les documents</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le KYC est rejeté après 24h
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un email détaillant les documents à refournir:</p>
<hr />
<h2 id="21-e-wallet-mangopay-cree-automatiquement">21. E-wallet Mangopay créé automatiquement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon KYC est validé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Mangopay finalise mon inscription</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un e-wallet Mangopay est créé automatiquement à mon nom
<span style="color: #9E9E9E"><strong>Et</strong></span> tous mes futurs revenus seront transférés vers ce wallet
<span style="color: #9E9E9E"><strong>Et</strong></span> les virements SEPA vers mon RIB seront effectués depuis ce wallet</p>
<hr />
<h2 id="22-conformite-rgpd-donnees-hebergees-eu">22. Conformité RGPD - Données hébergées EU</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je fournis mes documents KYC</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Mangopay stocke mes données</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> toutes les données sont hébergées en Union Européenne
<span style="color: #9E9E9E"><strong>Et</strong></span> Mangopay est régulé par l'ACPR (Autorité de Contrôle Prudentiel)
<span style="color: #9E9E9E"><strong>Et</strong></span> mes données sont protégées selon le RGPD</p>
<hr />
<h2 id="23-kyc-gratuit-inclus-dans-mangopay">23. KYC gratuit inclus dans Mangopay</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je complète le KYC</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le processus se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucun frais ne m'est facturé (0€)
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun frais n'est facturé à RoadWave (inclus dans l'offre Mangopay)</p>
<hr />
<h2 id="24-base-legale-conformite-fiscale-francaise">24. Base légale - Conformité fiscale française</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave est une plateforme française</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je génère des revenus &gt;1200€/an</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> RoadWave doit déclarer ces revenus aux impôts (DAS2)
<span style="color: #9E9E9E"><strong>Et</strong></span> le KYC permet de garantir l'identité réelle du bénéficiaire
<span style="color: #9E9E9E"><strong>Et</strong></span> cela respecte la réglementation fiscale française</p>
<hr />
<h2 id="25-base-legale-directive-anti-blanchiment-eu-2018843">25. Base légale - Directive anti-blanchiment EU 2018/843</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave verse de l'argent aux créateurs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le KYC est effectué</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> RoadWave respecte la 5ème directive anti-blanchiment EU
<span style="color: #9E9E9E"><strong>Et</strong></span> Mangopay effectue les vérifications requises (identité, liste noire, origine fonds)</p>
<hr />
<h2 id="26-notification-de-mise-a-jour-documents-expires">26. Notification de mise à jour documents expirés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma CNI va expirer dans 30 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte l'expiration proche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email:</p>
<hr />
<h2 id="27-suspension-monetisation-si-documents-expires">27. Suspension monétisation si documents expirés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma CNI est expirée depuis 10 jours
<span style="color: #9E9E9E"><strong>Et</strong></span> que je n'ai pas mis à jour mes documents</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système vérifie mon statut KYC</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma monétisation est suspendue automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne génère plus de revenus jusqu'à mise à jour</p>
<hr />
<h2 id="28-reactivation-sans-nouveau-kyc-si-donnees-a-jour">28. Réactivation sans nouveau KYC si données à jour</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai désactivé temporairement ma monétisation il y a 6 mois
<span style="color: #9E9E9E"><strong>Et</strong></span> que mes documents KYC sont toujours valides</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je réactive la monétisation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je n'ai pas besoin de refaire le KYC
<span style="color: #9E9E9E"><strong>Et</strong></span> la réactivation est immédiate</p>
<hr />
<h2 id="29-nouveau-kyc-requis-apres-2-ans-dinactivite">29. Nouveau KYC requis après 2 ans d'inactivité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai désactivé ma monétisation il y a 25 mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de réactiver</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système demande un nouveau KYC
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois soumettre des documents à jour (CNI peut avoir changé)</p>
<hr />
<h2 id="30-support-createur-pour-problemes-kyc">30. Support créateur pour problèmes KYC</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon KYC est rejeté et je ne comprends pas pourquoi</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je contacte le support RoadWave</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un agent peut consulter les raisons du rejet Mangopay
<span style="color: #9E9E9E"><strong>Et</strong></span> m'aider à fournir les bons documents</p>
<hr />
<h2 id="31-export-donnees-kyc-pour-rgpd">31. Export données KYC pour RGPD</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je demande l'export de mes données personnelles</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'export est généré</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les informations KYC sont incluses:
<span style="color: #9E9E9E"><strong>Et</strong></span> les documents scannés (CNI, RIB) sont exclus pour sécurité</p>
<hr />
<h2 id="32-suppression-compte-et-donnees-kyc">32. Suppression compte et données KYC</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je supprime définitivement mon compte RoadWave</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la suppression est confirmée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mes données KYC chez Mangopay sont archivées 10 ans (obligation légale)
<span style="color: #F44336"><strong>Mais</strong></span> supprimées de la base RoadWave immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> mon e-wallet est clôturé après versement du solde final</p>
<hr />
<h2 id="33-statistiques-kyc-pour-monitoring-plateforme">33. Statistiques KYC pour monitoring plateforme</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave suit la qualité du processus KYC</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un admin consulte les métriques</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit:</p>
<pre><code>| métrique | valeur exemple |
|---|---|
| Demandes KYC ce mois | 247 |
| Taux de validation | 87% |
| Délai moyen validation | 36h |
| Taux de rejet (documents invalides) | 13% |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> cela permet d'optimiser le processus</p>
<hr />
<h2 id="34-verification-siret-via-api-insee">34. Vérification SIRET via API INSEE</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je saisis mon SIRET</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système le valide</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une requête est faite à l'API SIRENE de l'INSEE
<span style="color: #9E9E9E"><strong>Et</strong></span> le système vérifie que le SIRET existe et est actif
<span style="color: #9E9E9E"><strong>Et</strong></span> récupère le nom de l'entreprise pour pré-remplir le formulaire</p>
<hr />
<h2 id="35-detection-fraude-meme-siret-utilise-par-plusieurs-comptes">35. Détection fraude - Même SIRET utilisé par plusieurs comptes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un SIRET "12345678901234" est déjà utilisé par un autre créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie d'utiliser le même SIRET</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système détecte la duplication
<span style="color: #9E9E9E"><strong>Et</strong></span> affiche "Ce SIRET est déjà utilisé par un autre compte RoadWave"
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois contacter le support si c'est une erreur</p>
<hr />
<h2 id="36-protection-donnees-sensibles-logs-chiffres">36. Protection données sensibles - Logs chiffrés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que des données KYC sensibles transitent dans le système</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les logs sont enregistrés</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les numéros SIRET, IBAN et données CNI sont masqués:
<span style="color: #9E9E9E"><strong>Et</strong></span> seule l'équipe sécurité peut accéder aux données complètes</p>
<hr />
<h2 id="37-backup-mangopay-des-documents-kyc">37. Backup Mangopay des documents KYC</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes documents KYC sont stockés chez Mangopay</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un audit est demandé par les autorités</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Mangopay peut fournir les documents originaux
<span style="color: #9E9E9E"><strong>Et</strong></span> RoadWave n'a pas besoin de stocker ces documents (réduction risque RGPD)</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="obligations-fiscales">Obligations fiscales</h1>
<blockquote>
<p><em>En tant que créateur monétisé</em>
<em>Je veux que RoadWave génère automatiquement les documents fiscaux requis</em>
<em>Afin de faciliter ma comptabilité et respecter la loi</em></p>
</blockquote>
<p><strong>30 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur avec la monétisation activée
<span style="color: #9E9E9E"><strong>Et</strong></span> que je génère des revenus sur RoadWave</p>
</blockquote>
<h2 id="1-generation-automatique-releve-mensuel-pdf">1. Génération automatique relevé mensuel PDF</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le mois de janvier se termine</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système calcule mes revenus du mois</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un relevé mensuel PDF est généré automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> le PDF est disponible dans mon tableau de bord</p>
<hr />
<h2 id="2-contenu-du-releve-mensuel-pdf">2. Contenu du relevé mensuel PDF</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon relevé de janvier est généré</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je télécharge le PDF</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le document contient:</p>
<hr />
<h2 id="3-telechargement-releve-depuis-tableau-de-bord">3. Téléchargement relevé depuis tableau de bord</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis sur mon tableau de bord créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à l'onglet "Revenus &gt; Historique"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois la liste de mes relevés mensuels:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="4-conservation-releves-accessibles-10-ans">4. Conservation relevés accessibles 10 ans</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai commencé la monétisation en janvier 2025</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mes relevés en janvier 2035 (10 ans plus tard)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les relevés depuis 2025 sont toujours accessibles
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux télécharger n'importe quel relevé historique
<span style="color: #9E9E9E"><strong>Et</strong></span> cela respecte l'obligation de conservation comptable de 10 ans</p>
<hr />
<h2 id="5-export-csv-a-la-demande">5. Export CSV à la demande</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je clique sur "Exporter pour comptable"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je choisis la période "Année 2025"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un fichier CSV est généré et téléchargé</p>
<hr />
<h2 id="6-contenu-export-csv-detaille">6. Contenu export CSV détaillé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'exporte mes données comptables 2025</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je télécharge le fichier CSV</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le fichier contient:</p>
<hr />
<h2 id="7-transmission-a-lexpert-comptable">7. Transmission à l'expert-comptable</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai téléchargé mon export CSV 2025</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je l'envoie à mon expert-comptable</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il peut importer le fichier dans son logiciel comptable
<span style="color: #9E9E9E"><strong>Et</strong></span> il saisit rapidement mes revenus RoadWave
<span style="color: #9E9E9E"><strong>Et</strong></span> cela facilite ma déclaration fiscale annuelle</p>
<hr />
<h2 id="8-das2-genere-automatiquement-si-revenus-1200an">8. DAS2 généré automatiquement si revenus &gt;1200€/an</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes revenus 2025 totalisent 2,450€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'année 2025 se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> RoadWave génère automatiquement une DAS2 pour les impôts
<span style="color: #9E9E9E"><strong>Et</strong></span> la DAS2 est transmise à la DGFIP en janvier 2026</p>
<hr />
<h2 id="9-contenu-de-la-das2">9. Contenu de la DAS2</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave génère ma DAS2 pour 2025</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la DGFIP reçoit la déclaration</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le document contient:</p>
<hr />
<h2 id="10-createur-recoit-une-copie-de-la-das2">10. Créateur reçoit une copie de la DAS2</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave transmet ma DAS2 aux impôts</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la transmission est confirmée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email avec une copie de la DAS2 en pièce jointe
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux consulter le document dans mon tableau de bord</p>
<hr />
<h2 id="11-pas-de-das2-si-revenus-1200an">11. Pas de DAS2 si revenus &lt;1200€/an</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes revenus 2025 totalisent seulement 890€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'année 2025 se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune DAS2 n'est générée car le seuil de 1200€ n'est pas atteint
<span style="color: #F44336"><strong>Mais</strong></span> je dois quand même déclarer mes revenus dans ma déclaration personnelle</p>
<hr />
<h2 id="12-base-legale-das2-obligation-france">12. Base légale DAS2 - Obligation France</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave verse des honoraires à des prestataires</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les revenus dépassent 1200€/an</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la déclaration DAS2 est obligatoire selon l'article 87 du Code Général des Impôts
<span style="color: #9E9E9E"><strong>Et</strong></span> le non-respect entraîne une amende de 15€ par bénéficiaire non déclaré</p>
<hr />
<h2 id="13-transmission-das2-via-edi-tdfc">13. Transmission DAS2 via EDI-TDFC</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave génère 1,247 DAS2 pour l'année 2025</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la transmission aux impôts est effectuée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la transmission se fait via le portail EDI-TDFC de la DGFIP
<span style="color: #9E9E9E"><strong>Et</strong></span> la transmission est automatisée (pas de saisie manuelle)
<span style="color: #9E9E9E"><strong>Et</strong></span> un accusé de réception est reçu sous 48h</p>
<hr />
<h2 id="14-createur-responsable-de-declarer-aux-impots">14. Créateur responsable de déclarer aux impôts</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai reçu 2,450€ de revenus RoadWave en 2025</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je fais ma déclaration fiscale en mai 2026</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je dois déclarer ces 2,450€ dans ma déclaration annuelle
<span style="color: #9E9E9E"><strong>Et</strong></span> si je suis auto-entrepreneur, je déclare en BNC (Bénéfices Non Commerciaux)</p>
<hr />
<h2 id="15-createur-responsable-des-cotisations-urssaf">15. Créateur responsable des cotisations URSSAF</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis auto-entrepreneur
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai reçu 2,450€ de revenus RoadWave en 2025</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je fais ma déclaration URSSAF trimestrielle</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je dois déclarer ces revenus à l'URSSAF
<span style="color: #9E9E9E"><strong>Et</strong></span> je paie ~22% de cotisations sociales (soit ~539€)</p>
<hr />
<h2 id="16-tva-non-applicable-en-franchise-en-base">16. TVA non applicable en franchise en base</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis auto-entrepreneur en micro-BNC
<span style="color: #9E9E9E"><strong>Et</strong></span> que mon chiffre d'affaires est &lt;37,800€/an</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je génère des revenus sur RoadWave</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je bénéficie de la franchise en base de TVA
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne facture pas de TVA à RoadWave
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne récupère pas la TVA sur mes achats</p>
<hr />
<h2 id="17-tva-applicable-si-ca-37800an">17. TVA applicable si CA &gt;37,800€/an</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon chiffre d'affaires total 2025 est 45,000€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je dépasse le seuil de franchise en base (37,800€)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je dois facturer de la TVA (20%) à RoadWave
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois obtenir un numéro TVA intracommunautaire
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois déclarer ma TVA mensuellement ou trimestriellement</p>
<hr />
<h2 id="18-conservation-justificatifs-10-ans-obligation-legale">18. Conservation justificatifs 10 ans - Obligation légale</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je génère des revenus sur RoadWave</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je télécharge mes relevés mensuels et exports CSV</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je dois les conserver 10 ans (obligation comptable France)
<span style="color: #9E9E9E"><strong>Et</strong></span> en cas de contrôle fiscal, je dois pouvoir les fournir</p>
<hr />
<h2 id="19-mangopay-transmet-automatiquement-via-dac7">19. Mangopay transmet automatiquement via DAC7</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis créateur monétisé sur RoadWave</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'année se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Mangopay transmet automatiquement mes revenus aux autorités fiscales EU
<span style="color: #9E9E9E"><strong>Et</strong></span> cela respecte la directive DAC7 (2021/514) sur la transparence fiscale des plateformes</p>
<hr />
<h2 id="20-directive-dac7-obligations-plateforme">20. Directive DAC7 - Obligations plateforme</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave est une plateforme facilitant des transactions</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Mangopay gère les paiements</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Mangopay transmet automatiquement:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> RoadWave n'a pas besoin de faire cette transmission manuellement</p>
<hr />
<h2 id="21-justificatif-virement-preuve-bancaire-comptable">21. Justificatif virement = Preuve bancaire comptable</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je reçois un virement de 150.00€ de Mangopay</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mon relevé bancaire</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois le virement avec la référence MANGOPAY-ABC123
<span style="color: #9E9E9E"><strong>Et</strong></span> ce relevé bancaire sert de justificatif comptable
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux le fournir à mon expert-comptable ou aux impôts</p>
<hr />
<h2 id="22-notification-annuelle-rappel-declaration-fiscale">22. Notification annuelle rappel déclaration fiscale</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis créateur monétisé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le mois d'avril 2026 arrive (période déclaration impôts France)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email de rappel:</p>
<hr />
<h2 id="23-page-ressources-fiscales-pour-createurs">23. Page ressources fiscales pour créateurs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis créateur monétisé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à "Aide &gt; Fiscalité"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois une page avec:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="24-dashboard-createur-recapitulatif-annuel">24. Dashboard créateur - Récapitulatif annuel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte mon dashboard en décembre 2025</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à "Revenus &gt; Récapitulatif annuel"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<hr />
<h2 id="25-generation-automatique-minimise-erreurs">25. Génération automatique minimise erreurs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que tous les documents fiscaux sont générés automatiquement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un créateur télécharge ses documents</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les montants sont garantis corrects (issus de la base de données)
<span style="color: #9E9E9E"><strong>Et</strong></span> il n'y a pas d'erreur de saisie manuelle
<span style="color: #9E9E9E"><strong>Et</strong></span> cela réduit les risques de contrôle fiscal</p>
<hr />
<h2 id="26-conformite-rgpd-donnees-fiscales-chiffrees">26. Conformité RGPD - Données fiscales chiffrées</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que les documents fiscaux contiennent des données sensibles (SIRET, revenus)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les documents sont stockés</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ils sont chiffrés au repos (encryption AES-256)
<span style="color: #9E9E9E"><strong>Et</strong></span> seul le créateur et les admins autorisés peuvent y accéder
<span style="color: #9E9E9E"><strong>Et</strong></span> les logs d'accès sont conservés pour audit</p>
<hr />
<h2 id="27-backup-documents-fiscaux-10-ans">27. Backup documents fiscaux 10 ans</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un document fiscal est généré</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il est stocké dans la base de données</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une copie est sauvegardée sur S3 (stockage durable)
<span style="color: #9E9E9E"><strong>Et</strong></span> les backups sont répliqués sur 3 zones de disponibilité
<span style="color: #9E9E9E"><strong>Et</strong></span> la conservation est garantie 10 ans minimum</p>
<hr />
<h2 id="28-audit-trail-generation-das2">28. Audit trail génération DAS2</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 1,247 DAS2 sont générées en janvier 2026</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un audit est demandé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les événements sont loggés:</p>
<pre><code>| é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 |
</code></pre>
<hr />
<h2 id="29-statistiques-admin-conformite-fiscale">29. Statistiques admin - Conformité fiscale</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un admin RoadWave consulte les métriques fiscales</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il accède au dashboard admin</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit:</p>
<pre><code>| métrique | valeur 2025 |
|---|---|
| Créateurs monétisés | 1,247 |
| Créateurs éligibles DAS2 (&gt;1200€) | 847 (68%) |
| Revenus totaux versés | 1,890,345€ |
| DAS2 transmises à la DGFIP | 847 |
| Taux conformité | 100% |
</code></pre>
<hr />
<h2 id="30-support-createur-pour-questions-fiscales">30. Support créateur pour questions fiscales</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai une question sur ma déclaration fiscale</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je contacte le support RoadWave</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'agent peut consulter mes documents fiscaux
<span style="color: #9E9E9E"><strong>Et</strong></span> m'aider à comprendre ce que je dois déclarer
<span style="color: #F44336"><strong>Mais</strong></span> il ne peut pas me conseiller fiscalement (pas expert-comptable)
<span style="color: #9E9E9E"><strong>Et</strong></span> il me recommande de consulter un expert-comptable si nécessaire</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="paiement-des-createurs">Paiement des créateurs</h1>
<blockquote>
<p><em>En tant que créateur monétisé</em>
<em>Je veux recevoir mes paiements mensuels de manière fiable</em>
<em>Afin d'être rémunéré pour mon travail</em></p>
</blockquote>
<p><strong>35 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur avec la monétisation activée
<span style="color: #9E9E9E"><strong>Et</strong></span> que mon KYC est validé</p>
</blockquote>
<h2 id="1-seuil-minimum-de-50-atteint-paiement-effectue">1. Seuil minimum de 50€ atteint - Paiement effectué</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes revenus du mois sont 73.45€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le dernier jour du mois arrive</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon solde de 73.45€ est transféré vers "en attente de paiement"
<span style="color: #9E9E9E"><strong>Et</strong></span> le paiement sera effectué le 15 du mois prochain</p>
<hr />
<h2 id="2-seuil-minimum-de-50-non-atteint-report-mois-suivant">2. Seuil minimum de 50€ non atteint - Report mois suivant</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes revenus du mois sont 32.17€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le dernier jour du mois arrive</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon solde de 32.17€ est reporté au mois suivant
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois "Solde insuffisant pour paiement (&lt;50€). Report mois prochain."</p>
<hr />
<h2 id="3-cumul-sur-plusieurs-mois-jusqua-atteindre-50">3. Cumul sur plusieurs mois jusqu'à atteindre 50€</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes revenus sont:</p>
<pre><code>| mois | revenus | solde cumulé |
|---|---|---|
| Janvier | 18.50€ | 18.50€ |
| Février | 22.30€ | 40.80€ |
| Mars | 15.70€ | 56.50€ |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la fin du mois de mars arrive</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le solde cumulé de 56.50€ dépasse les 50€
<span style="color: #9E9E9E"><strong>Et</strong></span> un paiement de 56.50€ est effectué le 15 avril</p>
<hr />
<h2 id="4-calcul-des-revenus-le-dernier-jour-du-mois">4. Calcul des revenus le dernier jour du mois</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que nous sommes le 31 janvier à 23h59</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système calcule les revenus du mois</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une requête SQL agrège tous les revenus pub et premium
<span style="color: #9E9E9E"><strong>Et</strong></span> le solde final du mois est figé dans monthly_revenues
<span style="color: #9E9E9E"><strong>Et</strong></span> le compteur du mois en cours repart à 0€ le 1er février</p>
<hr />
<h2 id="5-periode-de-traitement-contestations-1-14-du-mois">5. Période de traitement contestations 1-14 du mois</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes revenus de janvier sont calculés à 150.00€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la période du 1-14 février arrive</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> RoadWave analyse les éventuelles fraudes ou contestations
<span style="color: #9E9E9E"><strong>Et</strong></span> si une fraude est détectée, les revenus concernés sont retirés du solde
<span style="color: #9E9E9E"><strong>Et</strong></span> le solde final est validé le 14 février</p>
<hr />
<h2 id="6-virement-sepa-le-15-du-mois-suivant">6. Virement SEPA le 15 du mois suivant</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes revenus de janvier validés sont 150.00€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le 15 février arrive</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Mangopay initie un virement SEPA depuis mon e-wallet vers mon RIB
<span style="color: #9E9E9E"><strong>Et</strong></span> le statut du paiement passe à "En cours"</p>
<hr />
<h2 id="7-reception-virement-16-18-du-mois-1-3-jours-sepa">7. Réception virement 16-18 du mois (1-3 jours SEPA)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un virement SEPA a été initié le 15 février</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> 1-3 jours ouvrés s'écoulent</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois le virement sur mon compte bancaire entre le 16 et 18 février
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux consulter l'historique des paiements dans mon dashboard</p>
<hr />
<h2 id="8-virement-sepa-gratuit-pour-comptes-eu">8. Virement SEPA gratuit pour comptes EU</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon RIB est français (IBAN FR)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Mangopay effectue le virement</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucun frais n'est prélevé (virement SEPA gratuit)
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois 100% du montant annoncé</p>
<hr />
<h2 id="9-virement-international-hors-eu-avec-frais-variables">9. Virement international hors EU avec frais variables</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis créateur expatrié avec RIB hors Union Européenne</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Mangopay effectue le virement international</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> des frais variables s'appliquent selon le pays
<span style="color: #9E9E9E"><strong>Et</strong></span> les frais sont déduits du montant final
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le détail des frais dans mon historique</p>
<hr />
<h2 id="10-e-wallet-mangopay-automatique">10. E-wallet Mangopay automatique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon KYC est validé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> mes revenus sont calculés</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les revenus sont automatiquement transférés vers mon e-wallet Mangopay
<span style="color: #9E9E9E"><strong>Et</strong></span> l'e-wallet est débité lors du virement SEPA vers mon RIB
<span style="color: #9E9E9E"><strong>Et</strong></span> je n'ai aucune action manuelle à faire</p>
<hr />
<h2 id="11-tableau-de-bord-revenus-pub-temps-reel">11. Tableau de bord - Revenus pub temps réel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'accède à mon tableau de bord créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte l'onglet "Revenus"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> ces valeurs sont mises à jour en temps réel (cache Redis, refresh 10 min)</p>
<hr />
<h2 id="12-tableau-de-bord-solde-en-attente-de-paiement">12. Tableau de bord - Solde en attente de paiement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes revenus de janvier sont calculés et validés
<span style="color: #9E9E9E"><strong>Et</strong></span> que nous sommes le 10 février</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mon tableau de bord</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| métrique | valeur exemple |
|---|---|
| Solde en attente | 150.00€ |
| Date de paiement | 15 février 2025 |
| Statut | En attente |
</code></pre>
<hr />
<h2 id="13-historique-des-virements-permanents">13. Historique des virements permanents</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis monétisé depuis 6 mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte l'historique des paiements</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois la liste complète:</p>
<pre><code>| 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 |
| ... | ... | ... | ... |
</code></pre>
<hr />
<h2 id="14-export-comptable-csv-telechargeable">14. Export comptable CSV téléchargeable</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je clique sur "Télécharger export comptable"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le fichier CSV est généré</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je télécharge un fichier contenant:
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux transmettre ce fichier à mon expert-comptable</p>
<hr />
<h2 id="15-echec-virement-tentative-1-echouee">15. Échec virement - Tentative 1 échouée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un virement est initié le 15 février
<span style="color: #F44336"><strong>Mais</strong></span> que mon RIB est invalide ou le compte est fermé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Mangopay détecte l'échec</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le statut passe à "Échec - Retry programmé le 18 février"
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un email m'alertant du problème</p>
<hr />
<h2 id="16-echec-virement-retry-automatique-j3">16. Échec virement - Retry automatique J+3</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le virement du 15 février a échoué</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le 18 février arrive (J+3)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Mangopay tente automatiquement un nouveau virement
<span style="color: #9E9E9E"><strong>Et</strong></span> si le RIB est toujours invalide, le virement échoue à nouveau</p>
<hr />
<h2 id="17-echec-virement-retry-automatique-j7">17. Échec virement - Retry automatique J+7</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que les 2 premières tentatives ont échoué</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le 22 février arrive (J+7)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Mangopay tente une 3ème et dernière fois
<span style="color: #9E9E9E"><strong>Et</strong></span> si le virement échoue encore, la monétisation est suspendue</p>
<hr />
<h2 id="18-echec-virement-suspension-monetisation-apres-3-echecs">18. Échec virement - Suspension monétisation après 3 échecs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que les 3 tentatives de virement ont échoué</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte le 3ème échec</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma monétisation est suspendue automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un email:</p>
<hr />
<h2 id="19-mise-a-jour-rib-et-reactivation-paiement">19. Mise à jour RIB et réactivation paiement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma monétisation est suspendue pour RIB invalide
<span style="color: #9E9E9E"><strong>Et</strong></span> que mon solde en attente est 150.00€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je mets à jour mon RIB avec un compte valide</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Mangopay tente immédiatement un nouveau virement
<span style="color: #9E9E9E"><strong>Et</strong></span> si le virement réussit, ma monétisation est réactivée automatiquement</p>
<hr />
<h2 id="20-notification-email-lors-de-chaque-paiement">20. Notification email lors de chaque paiement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un virement de 150.00€ est effectué le 15 février</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le virement est confirmé par Mangopay</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email:</p>
<hr />
<h2 id="21-justification-seuil-50-eviter-frais-bancaires-micro-sommes">21. Justification seuil 50€ - Éviter frais bancaires micro-sommes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que Mangopay facture des frais fixes par virement
<span style="color: #9E9E9E"><strong>Et</strong></span> que les banques peuvent facturer des frais de réception</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un créateur génère seulement 5€/mois</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un virement mensuel coûterait proportionnellement trop cher
<span style="color: #9E9E9E"><strong>Et</strong></span> le seuil de 50€ garantit des frais proportionnels raisonnables</p>
<hr />
<h2 id="22-comparaison-avec-youtube-seuil-100">22. Comparaison avec YouTube (seuil 100$)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que YouTube fixe le seuil à 100$ (~90€)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> RoadWave fixe le seuil à 50€</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> RoadWave est plus accessible pour petits créateurs
<span style="color: #9E9E9E"><strong>Et</strong></span> les paiements arrivent plus rapidement</p>
<hr />
<h2 id="23-comparaison-avec-twitch-seuil-50">23. Comparaison avec Twitch (seuil 50$)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que Twitch fixe le seuil à 50$ (~45€)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> RoadWave fixe le seuil à 50€</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le seuil est aligné sur Twitch
<span style="color: #9E9E9E"><strong>Et</strong></span> les créateurs comprennent facilement le système</p>
<hr />
<h2 id="24-comparaison-avec-spotify-seuil-10-mais-delais-longs">24. Comparaison avec Spotify (seuil 10€ mais délais longs)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que Spotify a un seuil bas de 10€ mais verse tous les 3 mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> RoadWave a un seuil de 50€ mais verse chaque mois</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les créateurs reçoivent leurs paiements plus régulièrement
<span style="color: #9E9E9E"><strong>Et</strong></span> la trésorerie est plus prévisible</p>
<hr />
<h2 id="25-releve-mensuel-pdf-automatique">25. Relevé mensuel PDF automatique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes revenus de janvier sont calculés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le 1er février arrive</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un relevé mensuel PDF est généré automatiquement:
<span style="color: #9E9E9E"><strong>Et</strong></span> le PDF est téléchargeable depuis mon tableau de bord</p>
<hr />
<h2 id="26-conservation-releves-10-ans-obligation-comptable">26. Conservation relevés 10 ans (obligation comptable)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je génère des revenus sur RoadWave</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je télécharge mes relevés mensuels</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je dois les conserver 10 ans (obligation légale France)
<span style="color: #9E9E9E"><strong>Et</strong></span> RoadWave conserve également une copie pendant 10 ans pour audit</p>
<hr />
<h2 id="27-dashboard-admin-monitoring-paiements">27. Dashboard admin - Monitoring paiements</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un admin RoadWave consulte les paiements du mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il accède au dashboard admin</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="28-alerte-admin-si-taux-echec-5">28. Alerte admin si taux échec &gt;5%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 8% des virements du mois ont échoué</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte le taux d'échec élevé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une alerte est envoyée à l'équipe technique:</p>
<hr />
<h2 id="29-statistiques-personnelles-moyenne-revenus-sur-6-mois">29. Statistiques personnelles - Moyenne revenus sur 6 mois</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis monétisé depuis 6 mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mes statistiques</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| métrique | valeur |
|---|---|
| Revenus moyens/mois | 134.50€ |
| Meilleur mois | 189.00€ |
| Mois le plus bas | 87.30€ |
| Tendance | +12% ↗ |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> cela m'aide à suivre ma progression</p>
<hr />
<h2 id="30-projection-revenus-annuels">30. Projection revenus annuels</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes revenus moyens sont 134.50€/mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les projections</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système estime mes revenus annuels à ~1,614€
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux anticiper mes déclarations fiscales</p>
<hr />
<h2 id="31-notification-seuil-symbolique-1000-cumules">31. Notification seuil symbolique 1000€ cumulés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes revenus cumulés depuis inscription atteignent 1000€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le paiement qui franchit ce seuil est effectué</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification:</p>
<hr />
<h2 id="32-performance-calcul-avec-100-000-createurs-monetises">32. Performance calcul avec 100 000 créateurs monétisés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave a 100 000 créateurs monétisés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le calcul des paiements du 15 du mois est lancé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un job asynchrone traite les paiements par batch de 1000
<span style="color: #9E9E9E"><strong>Et</strong></span> tous les virements sont initiés en 2-4 heures
<span style="color: #9E9E9E"><strong>Et</strong></span> les serveurs Mangopay gèrent la charge sans problème</p>
<hr />
<h2 id="33-backup-des-donnees-de-paiement">33. Backup des données de paiement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que les paiements sont critiques pour les créateurs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un paiement est effectué</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les données sont sauvegardées dans PostgreSQL (principal)
<span style="color: #9E9E9E"><strong>Et</strong></span> répliquées vers une base de backup (replica)
<span style="color: #9E9E9E"><strong>Et</strong></span> une copie d'archive est stockée sur S3 (conservation 10 ans)</p>
<hr />
<h2 id="34-audit-trail-complet-des-paiements">34. Audit trail complet des paiements</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un paiement est initié, traité et complété</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un audit est demandé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les événements sont loggés:</p>
<pre><code>| é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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> ces logs sont conservés 10 ans pour conformité</p>
<hr />
<h2 id="35-protection-fraude-detection-pattern-suspect">35. Protection fraude - Détection pattern suspect</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur génère subitement 10 000€ de revenus en 1 mois</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> que sa moyenne est de 50€/mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte cette anomalie</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le paiement est mis en attente pour vérification manuelle
<span style="color: #9E9E9E"><strong>Et</strong></span> l'équipe modération analyse le compte avant validation</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="sources-de-revenus-createurs">Sources de revenus créateurs</h1>
<blockquote>
<p><em>En tant que créateur monétisé</em>
<em>Je veux générer des revenus via publicités et abonnés Premium</em>
<em>Afin d'être rémunéré pour mon travail</em></p>
</blockquote>
<p><strong>34 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur avec la monétisation activée
<span style="color: #9E9E9E"><strong>Et</strong></span> que mon KYC est validé</p>
</blockquote>
<h2 id="1-cpm-createur-de-3-1000-ecoutes-completes">1. CPM créateur de 3€ / 1000 écoutes complètes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes contenus ont généré 1000 écoutes complètes par des utilisateurs gratuits</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le calcul des revenus du mois est effectué</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je touche 3.00€ pour ces 1000 écoutes
<span style="color: #9E9E9E"><strong>Et</strong></span> ce montant est ajouté à mon solde disponible</p>
<hr />
<h2 id="2-10-000-ecoutes-gratuits-30-de-revenus-pub">2. 10 000 écoutes gratuits → 30€ de revenus pub</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes contenus ont généré 10 000 écoutes complètes (utilisateurs gratuits)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le mois se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je touche 30.00€ de revenus publicitaires
<span style="color: #9E9E9E"><strong>Et</strong></span> ces revenus sont visibles en temps réel dans mon tableau de bord</p>
<hr />
<h2 id="3-50-000-ecoutes-gratuits-150-de-revenus-pub">3. 50 000 écoutes gratuits → 150€ de revenus pub</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes contenus ont généré 50 000 écoutes complètes (utilisateurs gratuits)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le mois se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je touche 150.00€ de revenus publicitaires</p>
<hr />
<h2 id="4-100-000-ecoutes-gratuits-300-de-revenus-pub">4. 100 000 écoutes gratuits → 300€ de revenus pub</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes contenus ont généré 100 000 écoutes complètes (utilisateurs gratuits)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le mois se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je touche 300.00€ de revenus publicitaires</p>
<hr />
<h2 id="5-repartition-economique-plateforme-garde-94">5. Répartition économique - Plateforme garde 94%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité facturée 0.05€/écoute génère 50€ CPM</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la plateforme calcule la répartition</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le créateur touche 3€ (6% du CA pub)
<span style="color: #9E9E9E"><strong>Et</strong></span> la plateforme garde 47€ (94%) pour:</p>
<pre><code>| poste budgétaire | coût estimé |
|---|---|
| CDN + infrastructure | 10-15€ |
| Modération + support | 5-10€ |
| Développement + R&amp;D | 10-15€ |
| Marge opérationnelle | 10-15€ |
</code></pre>
<hr />
<h2 id="6-ecoute-complete-80-du-contenu-ecoute">6. Écoute complète = ≥80% du contenu écouté</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur gratuit écoute mon contenu de 10 minutes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il écoute 8 minutes (80%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'écoute compte comme "complète"
<span style="color: #9E9E9E"><strong>Et</strong></span> je génère 0.003€ de revenus pub (3€/1000)</p>
<hr />
<h2 id="7-ecoute-incomplete-80-ne-compte-pas">7. Écoute incomplète &lt;80% ne compte pas</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur gratuit écoute mon contenu de 10 minutes
<span style="color: #F44336"><strong>Mais</strong></span> il skip après 5 minutes (50%)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le calcul des revenus est effectué</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> cette écoute ne compte pas comme "complète"
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne génère aucun revenu publicitaire pour cette écoute</p>
<hr />
<h2 id="8-ecoutes-premium-ne-comptent-pas-pour-les-revenus-pub">8. Écoutes Premium ne comptent pas pour les revenus pub</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur Premium écoute 100% de mon contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le calcul des revenus publicitaires est effectué</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> cette écoute ne compte pas dans les revenus pub
<span style="color: #F44336"><strong>Mais</strong></span> elle compte dans les revenus Premium (système séparé)</p>
<hr />
<h2 id="9-detection-bots-ecoutes-exclues">9. Détection bots - Écoutes exclues</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un bot génère 10 000 écoutes artificielles sur mes contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte le pattern suspect (rate limiting, IP unique, etc.)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ces écoutes sont marquées comme frauduleuses
<span style="color: #9E9E9E"><strong>Et</strong></span> elles sont exclues du calcul des revenus publicitaires</p>
<hr />
<h2 id="10-comparaison-avec-youtube-3-51000-vues">10. Comparaison avec YouTube (3-5€/1000 vues)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que YouTube paie 3-5€/1000 vues</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> RoadWave fixe le CPM créateur à 3€/1000 écoutes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le tarif est aligné sur le bas de la fourchette YouTube
<span style="color: #9E9E9E"><strong>Et</strong></span> cela est compétitif pour un MVP sans marché publicitaire mature</p>
<hr />
<h2 id="11-comparaison-avec-spotify-3-41000-ecoutes">11. Comparaison avec Spotify (3-4€/1000 écoutes)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que Spotify paie ~3-4€/1000 écoutes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> RoadWave fixe le CPM créateur à 3€/1000 écoutes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le tarif est aligné sur l'industrie musicale
<span style="color: #9E9E9E"><strong>Et</strong></span> les créateurs audio peuvent anticiper des revenus similaires</p>
<hr />
<h2 id="12-tableau-de-bord-revenus-pub-temps-reel">12. Tableau de bord - Revenus pub temps réel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'accède à mon tableau de bord créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mes revenus publicitaires</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| métrique | valeur exemple |
|---|---|
| Écoutes complètes ce mois (gratuit) | 23 456 |
| Revenus pub ce mois | 70.37€ |
| CPM effectif | 3.00€ |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> ces valeurs sont mises à jour toutes les 10 minutes</p>
<hr />
<h2 id="13-repartition-7030-createur-touche-70">13. Répartition 70/30 - Créateur touche 70%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur Premium paie 4.99€/mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la répartition est calculée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 3.49€ sont reversés aux créateurs écoutés (70%)
<span style="color: #9E9E9E"><strong>Et</strong></span> 1.50€ sont gardés par la plateforme (30%)</p>
<hr />
<h2 id="14-utilisateur-ecoute-3-createurs-repartition-proportionnelle">14. Utilisateur écoute 3 créateurs - Répartition proportionnelle</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur Premium paie 4.99€/mois
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il écoute 3 créateurs ce mois:</p>
<pre><code>| créateur | temps écoute | ratio |
|---|---|---|
| Créateur A | 10h | 50% |
| Créateur B | 6h | 30% |
| Créateur C | 4h | 20% |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le calcul des revenus Premium est effectué</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la répartition est:</p>
<pre><code>| créateur | revenus |
|---|---|
| Créateur A | 1.75€ |
| Créateur B | 1.05€ |
| Créateur C | 0.70€ |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> la somme totale versée aux créateurs est 3.50€ (70% de 4.99€)</p>
<hr />
<h2 id="15-calcul-sql-proportionnel-au-temps-decoute">15. Calcul SQL proportionnel au temps d'écoute</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur Premium a écouté plusieurs créateurs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système calcule les revenus du mois</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la requête SQL suivante est exécutée:</p>
<hr />
<h2 id="16-utilisateur-ecoute-un-seul-createur-100-a-ce-createur">16. Utilisateur écoute un seul créateur - 100% à ce créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur Premium paie 4.99€/mois
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il n'écoute qu'un seul créateur (moi)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le mois se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je touche 3.49€ (70% de 4.99€)
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois 100% de la part créateurs</p>
<hr />
<h2 id="17-utilisateur-premium-inactif-aucun-revenu-genere">17. Utilisateur Premium inactif - Aucun revenu généré</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur Premium paie 4.99€/mois
<span style="color: #F44336"><strong>Mais</strong></span> qu'il n'écoute aucun contenu ce mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le calcul des revenus Premium est effectué</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucun créateur ne reçoit de revenus de cet utilisateur
<span style="color: #9E9E9E"><strong>Et</strong></span> les 3.49€ de la part créateurs restent à la plateforme
<span style="color: #9E9E9E"><strong>Et</strong></span> cela couvre les coûts d'infrastructure</p>
<hr />
<h2 id="18-comparaison-avec-youtube-premium-7030">18. Comparaison avec YouTube Premium (70/30)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que YouTube Premium reverse 70% aux créateurs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> RoadWave fixe également 70/30</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le modèle est aligné sur le standard industrie
<span style="color: #9E9E9E"><strong>Et</strong></span> les créateurs ont confiance dans l'équité du système</p>
<hr />
<h2 id="19-comparaison-avec-spotify-7030">19. Comparaison avec Spotify (70/30)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que Spotify reverse 70% aux artistes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> RoadWave fixe également 70/30</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le modèle est identique à Spotify
<span style="color: #9E9E9E"><strong>Et</strong></span> les créateurs audio comprennent facilement le système</p>
<hr />
<h2 id="20-apple-music-moins-avantageux-5248">20. Apple Music moins avantageux (52/48)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'Apple Music ne reverse que 52% aux artistes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> RoadWave offre 70% aux créateurs</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> RoadWave est plus avantageux de 18 points
<span style="color: #9E9E9E"><strong>Et</strong></span> cela devient un argument marketing fort</p>
<hr />
<h2 id="21-justification-equite-createurs-les-plus-ecoutes-gagnent-plus">21. Justification équité - Créateurs les plus écoutés gagnent plus</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 2 créateurs ont le même nombre d'abonnés Premium
<span style="color: #F44336"><strong>Mais</strong></span> que le Créateur A est écouté 20h/mois et le Créateur B seulement 2h/mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les revenus Premium sont calculés</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le Créateur A gagne 10× plus que le Créateur B
<span style="color: #9E9E9E"><strong>Et</strong></span> cela récompense la qualité et l'engagement (pas juste l'abonnement)</p>
<hr />
<h2 id="22-pas-de-winner-takes-all-equite-totale">22. Pas de "winner takes all" - Équité totale</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur Premium écoute 10 créateurs différents</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les revenus sont calculés</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> chacun des 10 créateurs reçoit sa part proportionnelle
<span style="color: #9E9E9E"><strong>Et</strong></span> il n'y a pas de système où un seul créateur prend tout</p>
<hr />
<h2 id="23-marge-plateforme-30-couvre-absence-revenus-pub-premium">23. Marge plateforme 30% couvre absence revenus pub Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur Premium ne voit aucune publicité</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la plateforme calcule ses revenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> elle ne touche que les 30% de l'abonnement Premium (1.50€)
<span style="color: #9E9E9E"><strong>Et</strong></span> cette marge compense la perte des revenus publicitaires (qui auraient été ~47€/1000 écoutes)</p>
<hr />
<h2 id="24-tableau-de-bord-revenus-premium-temps-reel">24. Tableau de bord - Revenus Premium temps réel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'accède à mon tableau de bord créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mes revenus Premium</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| métrique | valeur exemple |
|---|---|
| Abonnés Premium actifs ayant écouté | 47 |
| Heures d'écoute Premium ce mois | 234h |
| Revenus Premium ce mois | 89.23€ |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> ces valeurs sont mises à jour toutes les 10 minutes</p>
<hr />
<h2 id="25-revenus-cumules-pub-premium">25. Revenus cumulés pub + premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai généré ce mois:</p>
<pre><code>| source | montant |
|---|---|
| Revenus pub | 150.00€ |
| Revenus Premium | 89.23€ |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mon solde disponible</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le total est 239.23€
<span style="color: #9E9E9E"><strong>Et</strong></span> ce solde sera versé le 15 du mois prochain (si ≥50€)</p>
<hr />
<h2 id="26-dashboard-createur-vue-densemble">26. Dashboard créateur - Vue d'ensemble</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'accède à mon tableau de bord créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte la page revenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<hr />
<h2 id="27-export-comptable-csv-pour-expert-comptable">27. Export comptable CSV pour expert-comptable</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je clique sur "Exporter pour comptable"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'export est généré</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je télécharge un fichier CSV:
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux transmettre ce fichier à mon expert-comptable</p>
<hr />
<h2 id="28-notification-hebdomadaire-progression-revenus">28. Notification hebdomadaire progression revenus</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis créateur monétisé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> chaque lundi matin arrive</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email récapitulatif:</p>
<hr />
<h2 id="29-graphique-evolution-revenus-sur-12-mois">29. Graphique évolution revenus sur 12 mois</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis monétisé depuis 12 mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à mes statistiques</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois un graphique en courbes montrant:</p>
<pre><code>| mois | revenus pub | revenus premium | total |
|---|---|---|---|
| Jan 25 | 150€ | 89€ | 239€ |
| Déc 24 | 123€ | 55€ | 178€ |
| Nov 24 | 100€ | 56€ | 156€ |
| ... | ... | ... | ... |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> cela m'aide à suivre ma progression</p>
<hr />
<h2 id="30-top-3-contenus-les-plus-rentables-du-mois">30. Top 3 contenus les plus rentables du mois</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai publié 20 contenus ce mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mes statistiques détaillées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois mon top 3 contenus:</p>
<pre><code>| 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€ |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> cela m'aide à comprendre quel type de contenu plaît le plus</p>
<hr />
<h2 id="31-alertes-seuils-de-revenus">31. Alertes seuils de revenus</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai activé les notifications de seuils</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> mes revenus du mois dépassent 100€ pour la première fois</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification:</p>
<hr />
<h2 id="32-performance-calcul-avec-100-000-createurs">32. Performance calcul avec 100 000 créateurs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave a 100 000 créateurs monétisés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le calcul des revenus mensuels est lancé le dernier jour du mois</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un job asynchrone traite tous les créateurs
<span style="color: #9E9E9E"><strong>Et</strong></span> le calcul prend environ 2-4 heures pour tous les créateurs
<span style="color: #9E9E9E"><strong>Et</strong></span> les résultats sont stockés dans la table monthly_revenues</p>
<hr />
<h2 id="33-cache-redis-pour-metriques-temps-reel">33. Cache Redis pour métriques temps réel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte mon dashboard plusieurs fois par jour</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la page se charge</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les compteurs sont récupérés depuis Redis:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> le temps de réponse est &lt;30ms</p>
<hr />
<h2 id="34-prevision-revenus-fin-de-mois">34. Prévision revenus fin de mois</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que nous sommes le 20 du mois
<span style="color: #9E9E9E"><strong>Et</strong></span> que mes revenus actuels sont 160€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système calcule la projection</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il estime les revenus fin de mois à ~240€ (extrapolation linéaire)
<span style="color: #9E9E9E"><strong>Et</strong></span> affiche "Projection fin de mois: ~240€"
<span style="color: #9E9E9E"><strong>Et</strong></span> cela m'aide à anticiper mes revenus</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="actions-complementaires-a-larret">Actions complémentaires à l'arrêt</h1>
<blockquote>
<p><em>En tant qu'auditeur avec véhicule arrêté</em>
<em>Je veux accéder à des actions avancées depuis l'application mobile</em>
<em>Afin de liker explicitement, m'abonner ou signaler du contenu</em></p>
</blockquote>
<p><strong>23 scénarios</strong> (21 standards, 2 plans)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un utilisateur est connecté
<span style="color: #9E9E9E"><strong>Et</strong></span> que le véhicule est à l'arrêt (vitesse GPS = 0 km/h)</p>
</blockquote>
<h2 id="1-like-explicite-avec-bouton-cur">1. Like explicite avec bouton cœur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu tagué "Automobile"
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma jauge "Automobile" est à 60%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur le bouton cœur "Like"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma jauge "Automobile" augmente de 2%
<span style="color: #9E9E9E"><strong>Et</strong></span> une animation de cœur rouge s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> une vibration courte est déclenchée
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge "Automobile" est maintenant à 62%</p>
<hr />
<h2 id="2-like-explicite-cumulable-avec-like-automatique">2. Like explicite cumulable avec like automatique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai écouté un contenu "Voyage" à 85%
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai reçu un like automatique renforcé (+2%)
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma jauge "Voyage" est à 52%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur le bouton cœur "Like"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma jauge "Voyage" augmente encore de 2%
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge "Voyage" passe à 54%
<span style="color: #9E9E9E"><strong>Et</strong></span> les deux likes sont cumulés</p>
<hr />
<h2 id="3-unlike-retire-le-like-manuel-uniquement">3. Unlike retire le like manuel uniquement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai liké manuellement un contenu "Sport"
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma jauge "Sport" est à 57%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique à nouveau sur le bouton cœur (toggle)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le cœur redevient vide (unlike)
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge "Sport" diminue de 2%
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge "Sport" revient à 55%</p>
<hr />
<h2 id="4-unlike-ne-retire-pas-le-like-automatique">4. Unlike ne retire pas le like automatique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai écouté un contenu "Musique" à 90%
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai reçu un like automatique renforcé (+2%)
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma jauge "Musique" est à 52%
<span style="color: #9E9E9E"><strong>Et</strong></span> que je n'ai PAS liké manuellement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte l'interface</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le bouton "Unlike" n'est pas disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> le cœur reste grisé (aucun like manuel)
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge reste à 52%</p>
<hr />
<h2 id="5-abonnement-a-un-createur">5. Abonnement à un créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur publie des contenus tagués "Automobile" et "Technologie"
<span style="color: #9E9E9E"><strong>Et</strong></span> que mes jauges sont:</p>
<pre><code>| catégorie | niveau |
|---|---|
| Automobile | 50% |
| Technologie | 45% |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "S'abonner" sur le profil du créateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma jauge "Automobile" augmente de 5%
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge "Technologie" augmente de 5%
<span style="color: #9E9E9E"><strong>Et</strong></span> une animation d'étoile dorée s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> un badge "Abonné ✓" apparaît sur le profil
<span style="color: #9E9E9E"><strong>Et</strong></span> mes nouvelles jauges sont:</p>
<pre><code>| catégorie | niveau |
|---|---|
| Automobile | 55% |
| Technologie | 50% |
</code></pre>
<hr />
<h2 id="6-desabonnement-dun-createur">6. Désabonnement d'un créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné à un créateur
<span style="color: #9E9E9E"><strong>Et</strong></span> que mes jauges "Automobile" et "Technologie" sont à 55% et 50%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Se désabonner"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma jauge "Automobile" diminue de 5%
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge "Technologie" diminue de 5%
<span style="color: #9E9E9E"><strong>Et</strong></span> le badge "Abonné ✓" disparaît
<span style="color: #9E9E9E"><strong>Et</strong></span> mes nouvelles jauges sont:</p>
<pre><code>| catégorie | niveau |
|---|---|
| Automobile | 50% |
| Technologie | 45% |
</code></pre>
<hr />
<h2 id="7-signalement-dun-contenu-inapproprie">7. Signalement d'un contenu inapproprié</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur le menu contextuel "⋮"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je sélectionne "Signaler"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un formulaire de signalement s'ouvre
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois sélectionner une catégorie:</p>
<pre><code>| Catégorie |
|---|
| Haine et violence |
| Contenu sexuel |
| Illégalité |
| Droits d'auteur |
| Spam |
| Désinformation (fake news) |
| Autre |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je peux ajouter un commentaire optionnel
<span style="color: #9E9E9E"><strong>Et</strong></span> le signalement est envoyé au flux de modération</p>
<hr />
<h2 id="8-feedback-visuel-pour-like-explicite">8. Feedback visuel pour like explicite</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je clique sur le bouton cœur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le like est enregistré</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une animation de cœur rouge se lance (0.5s)
<span style="color: #9E9E9E"><strong>Et</strong></span> le cœur reste rouge plein
<span style="color: #9E9E9E"><strong>Et</strong></span> une vibration haptique courte est déclenchée (iOS: .light, Android: 50ms)
<span style="color: #9E9E9E"><strong>Et</strong></span> un badge "♥ Ajouté à vos favoris" s'affiche 2 secondes</p>
<hr />
<h2 id="9-feedback-visuel-pour-abonnement">9. Feedback visuel pour abonnement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je clique sur "S'abonner"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'abonnement est enregistré</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une animation d'étoile dorée se lance (0.8s)
<span style="color: #9E9E9E"><strong>Et</strong></span> le bouton devient "Abonné ✓" avec badge doré
<span style="color: #9E9E9E"><strong>Et</strong></span> une notification "Abonné à [Créateur]" s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> les contenus du créateur seront boostés +30% dans l'algo</p>
<hr />
<h2 id="10-bouton-like-desactive-si-vitesse-10-kmh">10. Bouton like désactivé si vitesse &gt;10 km/h</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je conduis à 50 km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie d'accéder au bouton cœur dans l'app mobile</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le bouton est grisé et non cliquable
<span style="color: #9E9E9E"><strong>Et</strong></span> un message "Arrêtez-vous pour liker" s'affiche si clic tenté
<span style="color: #9E9E9E"><strong>Et</strong></span> seules les commandes au volant physiques fonctionnent</p>
<hr />
<h2 id="11-bouton-abonnement-desactive-en-conduite">11. Bouton abonnement désactivé en conduite</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je conduis à 40 km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie d'accéder au profil créateur dans l'app</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le bouton "S'abonner" est désactivé
<span style="color: #9E9E9E"><strong>Et</strong></span> un message "Arrêtez-vous pour vous abonner" s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> la navigation dans l'app est limitée aux fonctions lecture</p>
<hr />
<h2 id="12-signalement-possible-en-conduite-via-vocal">12. Signalement possible en conduite via vocal</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je conduis à 60 km/h
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'utilise CarPlay avec Siri</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je dis "Hey Siri, signale ce contenu"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Siri demande "Quelle catégorie ?"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux répondre vocalement "Spam" ou autre catégorie
<span style="color: #9E9E9E"><strong>Et</strong></span> le signalement est enregistré sans toucher l'écran</p>
<hr />
<h2 id="13-actions-vocales-disponibles-avec-carplayandroid-auto">13. Actions vocales disponibles avec CarPlay/Android Auto</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je conduis avec CarPlay activé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je dis "Hey Siri, like ce podcast"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un like explicite (+2%) est enregistré
<span style="color: #9E9E9E"><strong>Et</strong></span> Siri confirme "J'ai ajouté ce contenu à vos favoris"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je dis "OK Google, abonne-moi à ce créateur"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'abonnement est enregistré (+5% toutes jauges)
<span style="color: #9E9E9E"><strong>Et</strong></span> Google Assistant confirme "Vous êtes maintenant abonné"</p>
<hr />
<h2 id="14-menu-contextuel-accessible-a-larret-uniquement">14. Menu contextuel accessible à l'arrêt uniquement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le véhicule est à l'arrêt</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur le menu "⋮" (3 points verticaux)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les options disponibles sont:</p>
<pre><code>| Option |
|---|
| Like (cœur) |
| S'abonner au créateur |
| Signaler |
| Partager |
| Voir le profil du créateur |
| Télécharger (mode offline) |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> toutes les options sont cliquables</p>
<hr />
<h2 id="15-menu-contextuel-limite-en-conduite">15. Menu contextuel limité en conduite</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je conduis à 30 km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie d'ouvrir le menu "⋮"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seules 2 options sont disponibles:</p>
<pre><code>| Option |
|---|
| Signaler (vocal possible) |
| Suivant |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> les actions complexes sont désactivées</p>
<hr />
<h2 id="16-persistance-des-likes-manuels-en-base-de-donnees">16. Persistance des likes manuels en base de données</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je like manuellement 5 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je ferme l'application
<span style="color: #9E9E9E"><strong>Et</strong></span> que je me reconnecte plus tard</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous mes likes manuels sont toujours présents
<span style="color: #9E9E9E"><strong>Et</strong></span> les cœurs rouges sont affichés sur les contenus likés
<span style="color: #9E9E9E"><strong>Et</strong></span> mes jauges reflètent toujours l'impact (+2% × 5 likes)</p>
<hr />
<h2 id="17-liste-mes-contenus-likes-accessible-dans-profil">17. Liste "Mes contenus likés" accessible dans profil</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai liké manuellement 10 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à mon profil utilisateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois une section "❤️ Mes favoris"
<span style="color: #9E9E9E"><strong>Et</strong></span> la liste affiche les 10 contenus likés
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux cliquer pour réécouter
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux retirer un like (unlike) depuis cette liste</p>
<hr />
<h2 id="18-liste-mes-abonnements-accessible-dans-profil">18. Liste "Mes abonnements" accessible dans profil</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné à 5 créateurs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à mon profil utilisateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois une section "⭐ Mes abonnements"
<span style="color: #9E9E9E"><strong>Et</strong></span> la liste affiche les 5 créateurs avec leurs avatars
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux accéder au profil de chaque créateur
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux me désabonner depuis cette liste</p>
<hr />
<h2 id="19-impact-abonnement-sur-tous-les-tags-du-createur">19. Impact abonnement sur tous les tags du créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur a publié des contenus avec ces tags:</p>
<pre><code>| Contenu | Tags |
|---|---|
| C1 | Automobile, Voyage |
| C2 | Automobile, Technologie |
| C3 | Voyage, Famille |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> que mes jauges sont toutes à 50%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je m'abonne à ce créateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les jauges impactées sont:</p>
<pre><code>| Tag | Impact |
|---|---|
| Automobile | +5% |
| Voyage | +5% |
| Technologie | +5% |
| Famille | +5% |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> toutes les autres jauges restent à 50%</p>
<hr />
<h2 id="20-limite-dabonnements-200-maximum">20. Limite d'abonnements (200 maximum)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné à 200 créateurs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de m'abonner à un 201ème créateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message "Limite de 200 abonnements atteinte" s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois me désabonner d'un créateur existant pour en ajouter un nouveau</p>
<hr />
<h2 id="21-confirmation-avant-desabonnement">21. Confirmation avant désabonnement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné à un créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Se désabonner"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une popup de confirmation s'affiche:
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois confirmer pour valider
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux annuler pour conserver l'abonnement</p>
<hr />
<h2 id="22-plan-cumul-like-automatique-like-manuel">22. 📋 Plan: Cumul like automatique + like manuel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu est tagué "Sport"
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma jauge "Sport" est à 50%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute à <pourcentage>% (like auto <auto>)
<span style="color: #9E9E9E"><strong>Et</strong></span> que je like manuellement (+2%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'impact total est <total>
<span style="color: #9E9E9E"><strong>Et</strong></span> ma nouvelle jauge est <nouveau_niveau></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>pourcentage</th>
<th>auto</th>
<th>total</th>
<th>nouveau_niveau</th>
</tr>
</thead>
<tbody>
<tr>
<td>10</td>
<td>0</td>
<td>+2%</td>
<td>52%</td>
</tr>
<tr>
<td>30</td>
<td>+1%</td>
<td>+3%</td>
<td>53%</td>
</tr>
<tr>
<td>50</td>
<td>+1%</td>
<td>+3%</td>
<td>53%</td>
</tr>
<tr>
<td>80</td>
<td>+2%</td>
<td>+4%</td>
<td>54%</td>
</tr>
<tr>
<td>95</td>
<td>+2%</td>
<td>+4%</td>
<td>54%</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="23-plan-actions-disponibles-selon-vitesse-gps">23. 📋 Plan: Actions disponibles selon vitesse GPS</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je roule à <vitesse> km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie d'accéder à <action></p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'action est <disponibilite></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>vitesse</th>
<th>action</th>
<th>disponibilite</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>Like manuel</td>
<td>disponible</td>
</tr>
<tr>
<td>0</td>
<td>Abonnement</td>
<td>disponible</td>
</tr>
<tr>
<td>0</td>
<td>Signalement</td>
<td>disponible</td>
</tr>
<tr>
<td>5</td>
<td>Like manuel</td>
<td>disponible</td>
</tr>
<tr>
<td>5</td>
<td>Abonnement</td>
<td>disponible</td>
</tr>
<tr>
<td>10</td>
<td>Like manuel</td>
<td>désactivée</td>
</tr>
<tr>
<td>10</td>
<td>Abonnement</td>
<td>désactivée</td>
</tr>
<tr>
<td>50</td>
<td>Like manuel</td>
<td>désactivée</td>
</tr>
<tr>
<td>50</td>
<td>Abonnement</td>
<td>désactivée</td>
</tr>
<tr>
<td>50</td>
<td>Signalement vocal</td>
<td>disponible</td>
</tr>
</tbody>
</table>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="commande-precedent">Commande "Précédent"</h1>
<blockquote>
<p><em>En tant qu'auditeur</em>
<em>Je veux que le bouton "Précédent" ait un comportement intelligent</em>
<em>Afin de rejouer le contenu actuel ou revenir au précédent selon la progression</em></p>
</blockquote>
<p><strong>19 scénarios</strong> (17 standards, 2 plans)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un utilisateur est connecté</p>
</blockquote>
<h2 id="1-precedent-apres-10s-revient-au-contenu-precedent">1. Précédent après &lt;10s revient au contenu précédent</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai écouté le contenu "A" pendant 2 minutes
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'écoute maintenant le contenu "B" depuis 5 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Précédent"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la lecture revient au contenu "A"
<span style="color: #9E9E9E"><strong>Et</strong></span> la position de lecture est à 2 minutes (position exacte sauvegardée)
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu "B" reste en historique</p>
<hr />
<h2 id="2-precedent-apres-10s-rejoue-le-contenu-actuel">2. Précédent après ≥10s rejoue le contenu actuel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute le contenu "C" depuis 15 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Précédent"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu "C" rejoue depuis le début (position 0:00)
<span style="color: #9E9E9E"><strong>Et</strong></span> la lecture ne revient pas au contenu précédent
<span style="color: #9E9E9E"><strong>Et</strong></span> la progress bar revient à 0%</p>
<hr />
<h2 id="3-precedent-exactement-a-10s-rejoue-le-contenu-actuel">3. Précédent exactement à 10s rejoue le contenu actuel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute le contenu "D" depuis exactement 10 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Précédent"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu "D" rejoue depuis le début
<span style="color: #9E9E9E"><strong>Et</strong></span> la lecture ne revient pas au contenu précédent</p>
<hr />
<h2 id="4-precedent-sur-le-premier-contenu-de-session">4. Précédent sur le premier contenu de session</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je viens de démarrer l'application
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'écoute le contenu "Premier" depuis 3 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Précédent"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu "Premier" rejoue depuis le début
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun contenu précédent n'existe</p>
<hr />
<h2 id="5-historique-de-navigation-limite-a-10-contenus">5. Historique de navigation limité à 10 contenus</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai écouté 10 contenus [C1, C2, ..., C10]
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'historique Redis contient 10 entrées</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je passe au contenu C11</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu C1 est supprimé de l'historique (FIFO)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'historique contient [C2, C3, ..., C10, C11]
<span style="color: #9E9E9E"><strong>Et</strong></span> la taille reste à 10 contenus maximum</p>
<hr />
<h2 id="6-position-exacte-sauvegardee-dans-lhistorique">6. Position exacte sauvegardée dans l'historique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute le contenu "A" (durée 5 minutes)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'atteins 2 minutes 30 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'appuie sur "Suivant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'historique enregistre:</p>
<pre><code>| content_id | position_seconds | listened_at |
|---|---|---|
| A | 150 | 2026-01-21T10:30:00 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je reviens au contenu "A" via "Précédent"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la lecture reprend exactement à 2 minutes 30 secondes</p>
<hr />
<h2 id="7-navigation-arriere-sur-plusieurs-contenus">7. Navigation arrière sur plusieurs contenus</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai écouté dans l'ordre: A (2min), B (30s), C (3min)
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'écoute maintenant D depuis 1 seconde</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Précédent" (1ère fois)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reviens au contenu C à la position 3 minutes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Précédent" (&lt;10s sur C)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reviens au contenu B à la position 30 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Précédent" (&lt;10s sur B)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reviens au contenu A à la position 2 minutes</p>
<hr />
<h2 id="8-precedent-apres-milieu-du-contenu-rejoue-depuis-debut">8. Précédent après milieu du contenu rejoue depuis début</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu de 5 minutes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'atteins 2 minutes 30 secondes (milieu)
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'appuie sur "Précédent"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu actuel rejoue depuis 0:00
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne reviens pas au contenu précédent</p>
<hr />
<h2 id="9-enchainement-suivant-puis-precedent-rapide">9. Enchaînement Suivant puis Précédent rapide</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute le contenu "A" depuis 1 minute</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Suivant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu "B" démarre</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie immédiatement sur "Précédent" (2s après)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reviens au contenu "A" à la position 1 minute
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu "B" reste dans l'historique</p>
<hr />
<h2 id="10-transition-fluide-avec-animation-03s">10. Transition fluide avec animation 0.3s</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'appuie sur "Précédent"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le changement de contenu se produit</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la transition audio utilise un fade out/in de 0.3 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> la progress bar revient avec une animation fluide
<span style="color: #9E9E9E"><strong>Et</strong></span> l'interface ne montre aucun message de confirmation</p>
<hr />
<h2 id="11-historique-survit-au-changement-de-reseau">11. Historique survit au changement de réseau</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai un historique de 5 contenus en cache Redis</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je perds la connexion réseau temporairement
<span style="color: #9E9E9E"><strong>Et</strong></span> que je reviens en ligne</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'historique de navigation est toujours disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux toujours utiliser "Précédent"</p>
<hr />
<h2 id="12-historique-stocke-en-redis-avec-structure-complete">12. Historique stocké en Redis avec structure complète</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai écouté 3 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte le cache Redis</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la structure est:
<span style="color: #9E9E9E"><strong>Et</strong></span> l'ordre est du plus récent au plus ancien</p>
<hr />
<h2 id="13-precedent-sur-contenu-en-cours-au-debut-10s-du-premier">13. Précédent sur contenu en cours au début (&lt;10s) du premier</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je démarre une session avec le contenu "Initial"
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'écoute depuis 3 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Précédent"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu "Initial" rejoue depuis le début
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune erreur n'est générée
<span style="color: #9E9E9E"><strong>Et</strong></span> l'historique reste vide</p>
<hr />
<h2 id="14-compteur-de-temps-respecte-les-seuils-exacts">14. Compteur de temps respecte les seuils exacts</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le temps écoulé est de 9.9 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'appuie sur "Précédent"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reviens au contenu précédent</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le temps écoulé est de 10.0 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'appuie sur "Précédent"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu actuel rejoue depuis le début</p>
<hr />
<h2 id="15-progress-bar-visuelle-reflete-le-retour-exact">15. Progress bar visuelle reflète le retour exact</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai écouté le contenu "A" jusqu'à 75% (3min45 sur 5min)
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis passé au contenu "B"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je reviens au contenu "A" via "Précédent"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la progress bar affiche 75%
<span style="color: #9E9E9E"><strong>Et</strong></span> l'indicateur de temps affiche "3:45 / 5:00"
<span style="color: #9E9E9E"><strong>Et</strong></span> la lecture reprend exactement à cet endroit</p>
<hr />
<h2 id="16-metadonnees-dhistorique-incluent-timestamp-precis">16. Métadonnées d'historique incluent timestamp précis</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu "X" pendant 45 secondes à 10:30:15</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je passe au contenu suivant</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'historique enregistre:</p>
<pre><code>| content_id | position_seconds | listened_at |
|---|---|---|
| X | 45 | 2026-01-21T10:30:15Z |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> le timestamp précis permet l'analyse d'usage</p>
<hr />
<h2 id="17-suppression-fifo-respecte-lordre-chronologique">17. Suppression FIFO respecte l'ordre chronologique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un historique de [C1@10:00, C2@10:02, ..., C10@10:20]</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ajoute C11 à 10:22</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> C1 (le plus ancien) est supprimé
<span style="color: #9E9E9E"><strong>Et</strong></span> l'historique contient [C2@10:02, ..., C11@10:22]
<span style="color: #9E9E9E"><strong>Et</strong></span> la taille reste exactement 10 entrées</p>
<hr />
<h2 id="18-plan-comportement-selon-temps-ecoute">18. 📋 Plan: Comportement selon temps écouté</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu depuis <temps> secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Précédent"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'action est <comportement></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>temps</th>
<th>comportement</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>revenir au contenu précédent</td>
</tr>
<tr>
<td>5</td>
<td>revenir au contenu précédent</td>
</tr>
<tr>
<td>9</td>
<td>revenir au contenu précédent</td>
</tr>
<tr>
<td>10</td>
<td>rejouer le contenu actuel depuis 0:00</td>
</tr>
<tr>
<td>11</td>
<td>rejouer le contenu actuel depuis 0:00</td>
</tr>
<tr>
<td>30</td>
<td>rejouer le contenu actuel depuis 0:00</td>
</tr>
<tr>
<td>180</td>
<td>rejouer le contenu actuel depuis 0:00</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="19-plan-positions-de-reprise-exactes">19. 📋 Plan: Positions de reprise exactes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu de 10 minutes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'atteins <position> et passe au suivant
<span style="color: #9E9E9E"><strong>Et</strong></span> que je reviens via "Précédent"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la lecture reprend exactement à <position></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>position</th>
</tr>
</thead>
<tbody>
<tr>
<td>0:15</td>
</tr>
<tr>
<td>1:30</td>
</tr>
<tr>
<td>3:45</td>
</tr>
<tr>
<td>5:00</td>
</tr>
<tr>
<td>7:23</td>
</tr>
<tr>
<td>9:50</td>
</tr>
</tbody>
</table>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="commandes-vocales-carplay-et-android-auto">Commandes vocales CarPlay et Android Auto</h1>
<blockquote>
<p><em>En tant que conducteur avec CarPlay ou Android Auto</em>
<em>Je veux utiliser des commandes vocales pour interagir avec l'application</em>
<em>Afin de garder les mains sur le volant et les yeux sur la route</em></p>
</blockquote>
<p><strong>25 scénarios</strong> (23 standards, 2 plans)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un utilisateur est connecté
<span style="color: #9E9E9E"><strong>Et</strong></span> que CarPlay ou Android Auto est activé</p>
</blockquote>
<h2 id="1-disponibilite-des-commandes-vocales-uniquement-avec-carplayandroid-auto">1. Disponibilité des commandes vocales uniquement avec CarPlay/Android Auto</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je conduis avec CarPlay activé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je dis "Hey Siri"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Siri est disponible pour les commandes RoadWave</p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je conduis avec Android Auto activé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je dis "OK Google"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Google Assistant est disponible pour les commandes RoadWave</p>
<hr />
<h2 id="2-parc-automobile-compatible-avec-vocal-30-40-en-2026">2. Parc automobile compatible avec vocal (30-40% en 2026)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que nous sommes en 2026</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les statistiques du parc automobile EU</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> environ 30-40% des véhicules ont CarPlay ou Android Auto
<span style="color: #9E9E9E"><strong>Et</strong></span> ces utilisateurs peuvent utiliser les commandes vocales
<span style="color: #9E9E9E"><strong>Et</strong></span> les 60-70% restants utilisent les commandes au volant uniquement</p>
<hr />
<h2 id="3-commande-vocale-like-ce-podcast-avec-siri">3. Commande vocale "Like ce podcast" avec Siri</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu tagué "Automobile"
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma jauge "Automobile" est à 60%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je dis "Hey Siri, like ce podcast"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un like explicite (+2%) est enregistré
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge "Automobile" passe à 62%
<span style="color: #9E9E9E"><strong>Et</strong></span> Siri confirme vocalement "J'ai ajouté ce contenu à vos favoris"
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune interaction écran n'est requise</p>
<hr />
<h2 id="4-commande-vocale-like-ce-contenu-avec-google-assistant">4. Commande vocale "Like ce contenu" avec Google Assistant</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu tagué "Voyage"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je dis "OK Google, like ce contenu"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un like explicite est enregistré (+2%)
<span style="color: #9E9E9E"><strong>Et</strong></span> Google Assistant confirme "J'ai liké ce contenu pour vous"
<span style="color: #9E9E9E"><strong>Et</strong></span> la commande fonctionne sans toucher l'écran</p>
<hr />
<h2 id="5-commande-vocale-abonne-moi-a-ce-createur">5. Commande vocale "Abonne-moi à ce créateur"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu d'un créateur tagué "Automobile" et "Technologie"
<span style="color: #9E9E9E"><strong>Et</strong></span> que mes jauges sont à 50% et 45%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je dis "Hey Siri, abonne-moi à ce créateur"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'abonnement est enregistré
<span style="color: #9E9E9E"><strong>Et</strong></span> mes jauges augmentent de 5% chacune (55% et 50%)
<span style="color: #9E9E9E"><strong>Et</strong></span> Siri confirme "Vous êtes maintenant abonné à [Nom du créateur]"</p>
<hr />
<h2 id="6-commande-vocale-passe-au-contenu-suivant">6. Commande vocale "Passe au contenu suivant"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu "A"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je dis "Hey Siri, passe au contenu suivant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu "B" démarre immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> la commande a le même effet que le bouton physique "Suivant"</p>
<hr />
<h2 id="7-commande-vocale-signale-ce-contenu">7. Commande vocale "Signale ce contenu"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu inapproprié</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je dis "OK Google, signale ce contenu"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Google Assistant demande "Quelle catégorie ?"
<span style="color: #9E9E9E"><strong>Et</strong></span> je réponds vocalement "Spam"
<span style="color: #4CAF50"><strong>Alors</strong></span> le signalement est enregistré avec la catégorie "Spam"
<span style="color: #9E9E9E"><strong>Et</strong></span> Google Assistant confirme "J'ai signalé ce contenu"</p>
<hr />
<h2 id="8-commande-vocale-avec-categorie-de-signalement">8. Commande vocale avec catégorie de signalement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je dis "Hey Siri, signale ce contenu pour haine"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le signalement est enregistré avec la catégorie "Haine et violence"
<span style="color: #9E9E9E"><strong>Et</strong></span> Siri confirme "J'ai signalé ce contenu pour haine et violence"
<span style="color: #9E9E9E"><strong>Et</strong></span> le flux de modération reçoit le signalement</p>
<hr />
<h2 id="9-liste-des-categories-de-signalement-vocales-supportees">9. Liste des catégories de signalement vocales supportées</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je dis "signale ce contenu pour [catégorie]"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la catégorie est:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le signalement est enregistré avec la bonne catégorie</p>
<hr />
<h2 id="10-commande-vocale-non-reconnue-fallback">10. Commande vocale non reconnue - fallback</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je dis "Hey Siri, super ce podcast"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Siri ne reconnaît pas l'intent RoadWave</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Siri répond "Je ne comprends pas cette commande RoadWave"
<span style="color: #9E9E9E"><strong>Et</strong></span> elle suggère "Dites 'like ce podcast' ou 'passe au suivant'"</p>
<hr />
<h2 id="11-commandes-vocales-disponibles-en-conduite-uniquement">11. Commandes vocales disponibles en conduite uniquement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je roule à 50 km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'utilise les commandes vocales</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> toutes les commandes sont disponibles:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="12-intent-ios-personnalise-pour-roadwave">12. Intent iOS personnalisé pour RoadWave</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'app iOS implémente les Intents</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je configure les Shortcuts iOS</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les intents suivants sont disponibles:</p>
<pre><code>| Intent Name | Action |
|---|---|
| LikeCurrentContentIntent | Like explicite |
| SubscribeToCreatorIntent | Abonnement |
| ReportContentIntent | Signalement |
| SkipToNextContentIntent | Suivant |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> Siri les reconnaît automatiquement</p>
<hr />
<h2 id="13-intent-android-personnalise-pour-roadwave">13. Intent Android personnalisé pour RoadWave</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'app Android implémente les Voice Actions</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je configure les actions Google Assistant</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les actions suivantes sont disponibles:</p>
<pre><code>| Action Name | Action |
|---|---|
| com.roadwave.LIKE_CONTENT | Like explicite |
| com.roadwave.SUBSCRIBE_CREATOR | Abonnement |
| com.roadwave.REPORT_CONTENT | Signalement |
| com.roadwave.SKIP_NEXT | Suivant |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> Google Assistant les reconnaît</p>
<hr />
<h2 id="14-confirmation-vocale-apres-action-reussie">14. Confirmation vocale après action réussie</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je dis "Hey Siri, like ce podcast"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'action est enregistrée avec succès</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Siri répond immédiatement avec confirmation:
<span style="color: #9E9E9E"><strong>Et</strong></span> la réponse est naturelle et concise
<span style="color: #9E9E9E"><strong>Et</strong></span> elle ne distrait pas de la conduite</p>
<hr />
<h2 id="15-gestion-derreur-vocale-si-action-echoue">15. Gestion d'erreur vocale si action échoue</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je dis "Hey Siri, abonne-moi à ce créateur"
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai atteint la limite de 200 abonnements</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Siri essaie d'enregistrer l'abonnement</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'action échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> Siri répond "Impossible de s'abonner, limite de 200 abonnements atteinte"
<span style="color: #9E9E9E"><strong>Et</strong></span> elle suggère "Désabonnez-vous d'un créateur pour continuer"</p>
<hr />
<h2 id="16-commandes-vocales-multilingues-francais">16. Commandes vocales multilingues (français)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon Siri est configuré en français</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je dis "Hey Siri, j'aime ce podcast"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la commande est reconnue (variante de "like ce podcast")</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je dis "Hey Siri, mets une étoile"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la commande est reconnue (variante de "like")</p>
<hr />
<h2 id="17-implementation-post-mvp-sprint-5">17. Implémentation post-MVP (Sprint 5)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que les commandes vocales sont une feature Sprint 5</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le MVP est lancé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seules les commandes au volant physiques sont disponibles</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le Sprint 5 est déployé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les intents iOS/Android sont activés
<span style="color: #9E9E9E"><strong>Et</strong></span> les commandes vocales deviennent disponibles</p>
<hr />
<h2 id="18-priorisation-commandes-vocales-vs-boutons-physiques">18. Priorisation commandes vocales vs boutons physiques</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je conduis avec CarPlay
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai accès aux boutons physiques ET aux commandes vocales</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je veux liker un contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux soit:
<span style="color: #9E9E9E"><strong>Et</strong></span> les 3 méthodes sont valides</p>
<hr />
<h2 id="19-statistiques-dusage-des-commandes-vocales">19. Statistiques d'usage des commandes vocales</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 100 utilisateurs avec CarPlay utilisent RoadWave</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les analytics</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux voir:</p>
<pre><code>| 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% |
</code></pre>
<hr />
<h2 id="20-feedback-haptique-desactive-pour-commandes-vocales">20. Feedback haptique désactivé pour commandes vocales</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je like un contenu via commande vocale</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'action est enregistrée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune vibration haptique n'est déclenchée
<span style="color: #9E9E9E"><strong>Et</strong></span> seule la confirmation vocale est donnée</p>
<hr />
<h2 id="21-badge-visuel-mis-a-jour-apres-commande-vocale">21. Badge visuel mis à jour après commande vocale</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je dis "Hey Siri, like ce podcast"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'action est enregistrée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le badge "♥ Ajouté à vos favoris" s'affiche sur l'écran CarPlay
<span style="color: #9E9E9E"><strong>Et</strong></span> le cœur devient rouge plein dans l'interface
<span style="color: #9E9E9E"><strong>Et</strong></span> la mise à jour est visible même sans toucher l'écran</p>
<hr />
<h2 id="22-commandes-vocales-avec-contenu-sans-createur">22. Commandes vocales avec contenu sans créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu anonyme (créateur supprimé)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je dis "Hey Siri, abonne-moi à ce créateur"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Siri répond "Ce créateur n'est plus disponible"
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun abonnement n'est enregistré</p>
<hr />
<h2 id="23-limitation-temporelle-des-commandes-vocales">23. Limitation temporelle des commandes vocales</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je dis "Hey Siri, like ce podcast"
<span style="color: #9E9E9E"><strong>Et</strong></span> que le contenu change 1 seconde après</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Siri traite la commande 2 secondes plus tard</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la commande s'applique au contenu qui était en lecture au moment de la commande
<span style="color: #9E9E9E"><strong>Et</strong></span> non au contenu actuel (système de timestamp)</p>
<hr />
<h2 id="24-plan-commandes-vocales-avec-differents-assistants">24. 📋 Plan: Commandes vocales avec différents assistants</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'utilise <assistant></p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je dis <commande></p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'action <action> est exécutée
<span style="color: #9E9E9E"><strong>Et</strong></span> la confirmation est <confirmation></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>assistant</th>
<th>commande</th>
<th>action</th>
<th>confirmation</th>
</tr>
</thead>
<tbody>
<tr>
<td>Siri</td>
<td>"Like ce podcast"</td>
<td>Like +2%</td>
<td>"Ajouté à vos favoris"</td>
</tr>
<tr>
<td>Google Assistant</td>
<td>"Like ce contenu"</td>
<td>Like +2%</td>
<td>"J'ai liké ce contenu"</td>
</tr>
<tr>
<td>Siri</td>
<td>"Abonne-moi à ce créateur"</td>
<td>Abonnement +5%</td>
<td>"Vous êtes abonné"</td>
</tr>
<tr>
<td>Google Assistant</td>
<td>"Abonne-moi à ce créateur"</td>
<td>Abonnement +5%</td>
<td>"Abonnement enregistré"</td>
</tr>
<tr>
<td>Siri</td>
<td>"Signale ce contenu"</td>
<td>Signalement</td>
<td>"J'ai signalé ce contenu"</td>
</tr>
<tr>
<td>Google Assistant</td>
<td>"Signale ce contenu"</td>
<td>Signalement</td>
<td>"Contenu signalé"</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="25-plan-mapping-categories-signalement-vocal">25. 📋 Plan: Mapping catégories signalement vocal</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je dis "signale ce contenu pour <mot_cle>"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> <mot_cle> est reconnu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la catégorie mappée est <categorie></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>mot_cle</th>
<th>categorie</th>
</tr>
</thead>
<tbody>
<tr>
<td>haine</td>
<td>Haine et violence</td>
</tr>
<tr>
<td>violence</td>
<td>Haine et violence</td>
</tr>
<tr>
<td>sexuel</td>
<td>Contenu sexuel</td>
</tr>
<tr>
<td>porno</td>
<td>Contenu sexuel</td>
</tr>
<tr>
<td>illégal</td>
<td>Illégalité</td>
</tr>
<tr>
<td>terrorisme</td>
<td>Illégalité</td>
</tr>
<tr>
<td>copyright</td>
<td>Droits d'auteur</td>
</tr>
<tr>
<td>droits auteur</td>
<td>Droits d'auteur</td>
</tr>
<tr>
<td>spam</td>
<td>Spam</td>
</tr>
<tr>
<td>fake news</td>
<td>Désinformation</td>
</tr>
<tr>
<td>fausse info</td>
<td>Désinformation</td>
</tr>
</tbody>
</table>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="commandes-au-volant-et-interactions-simplifiees">Commandes au volant et interactions simplifiées</h1>
<blockquote>
<p><em>En tant que conducteur en sécurité</em>
<em>Je veux utiliser uniquement les commandes simplifiées au volant</em>
<em>Afin de naviguer sans distraction et en toute sécurité</em></p>
</blockquote>
<p><strong>21 scénarios</strong> (19 standards, 2 plans)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un utilisateur est connecté
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'application est connectée via CarPlay ou Android Auto</p>
</blockquote>
<h2 id="1-trois-commandes-disponibles-au-volant-uniquement">1. Trois commandes disponibles au volant uniquement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je conduis à 50 km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les commandes physiques disponibles</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seules 3 actions sont disponibles:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> aucune commande complexe n'est proposée</p>
<hr />
<h2 id="2-commande-suivant-au-volant">2. Commande "Suivant" au volant</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu "A"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur le bouton physique "Suivant" au volant</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu "B" démarre immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune action supplémentaire n'est requise
<span style="color: #9E9E9E"><strong>Et</strong></span> l'interface ne demande aucune confirmation</p>
<hr />
<h2 id="3-commande-precedent-au-volant-respecte-regle-10s">3. Commande "Précédent" au volant respecte règle 10s</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu depuis 5 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Précédent" au volant</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reviens au contenu précédent (règle &lt;10s)</p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu depuis 15 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Précédent" au volant</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu actuel rejoue depuis le début (règle ≥10s)</p>
<hr />
<h2 id="4-commande-playpause-avec-fade-audio">4. Commande "Play/Pause" avec fade audio</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu est en lecture</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Pause" au volant</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la lecture se met en pause avec un fade out de 0.3 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> la position de lecture est sauvegardée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Play" au volant</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la lecture reprend avec un fade in de 0.3 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> la reprise se fait à la position exacte</p>
<hr />
<h2 id="5-aucune-commande-complexe-supportee">5. Aucune commande complexe supportée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je conduis</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie un appui long sur "Suivant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'action n'est pas détectée (non supporté iOS/Android)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie un double-appui sur "Pause"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'action n'est pas détectée
<span style="color: #9E9E9E"><strong>Et</strong></span> seules les actions simples (clic simple) fonctionnent</p>
<hr />
<h2 id="6-compatibilite-100-tous-vehicules">6. Compatibilité 100% tous véhicules</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je conduis une voiture avec commandes basiques
<span style="color: #9E9E9E"><strong>Et</strong></span> que mon véhicule a seulement Suivant/Précédent/Pause</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'utilise RoadWave</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> toutes les fonctions essentielles sont accessibles
<span style="color: #9E9E9E"><strong>Et</strong></span> je n'ai pas besoin de boutons supplémentaires</p>
<hr />
<h2 id="7-feedback-visuel-discret-apres-action">7. Feedback visuel discret après action</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'appuie sur "Suivant"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le contenu change</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'interface CarPlay/Android Auto affiche le nouveau titre
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune popup ne bloque la vue
<span style="color: #9E9E9E"><strong>Et</strong></span> le changement est fluide et immédiat</p>
<hr />
<h2 id="8-like-automatique-renforce-apres-ecoute-80">8. Like automatique renforcé après écoute ≥80%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu de 5 minutes tagué "Automobile"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute pendant 4 minutes 30 secondes (90%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un like automatique renforcé (+2 points) est enregistré
<span style="color: #9E9E9E"><strong>Et</strong></span> un badge discret "♥ Ajouté à vos favoris" s'affiche 2 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune action manuelle n'est requise</p>
<hr />
<h2 id="9-like-automatique-standard-apres-ecoute-30-79">9. Like automatique standard après écoute 30-79%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu de 5 minutes tagué "Voyage"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute pendant 2 minutes (40%)
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'appuie sur "Suivant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un like automatique standard (+1 point) est enregistré
<span style="color: #9E9E9E"><strong>Et</strong></span> un badge discret s'affiche brièvement
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux continuer à conduire sans interruption</p>
<hr />
<h2 id="10-signal-negatif-apres-skip-rapide-10s">10. Signal négatif après skip rapide &lt;10s</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu tagué "Politique"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Suivant" après seulement 5 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un signal négatif (-0.5 point) est enregistré
<span style="color: #9E9E9E"><strong>Et</strong></span> la jauge "Politique" diminue légèrement
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun message n'est affiché (transparence)</p>
<hr />
<h2 id="11-pas-de-like-si-ecoute-30">11. Pas de like si écoute &lt;30%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu de 10 minutes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute pendant 2 minutes (20%)
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'appuie sur "Suivant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucun like n'est enregistré
<span style="color: #9E9E9E"><strong>Et</strong></span> les jauges ne changent pas
<span style="color: #9E9E9E"><strong>Et</strong></span> le système considère l'écoute comme neutre</p>
<hr />
<h2 id="12-badge-de-feedback-visuel-disparait-apres-2-secondes">12. Badge de feedback visuel disparaît après 2 secondes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je reçois un like automatique</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le badge "♥ Ajouté à vos favoris" apparaît</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il reste visible 2 secondes en bas de l'écran
<span style="color: #9E9E9E"><strong>Et</strong></span> il disparaît automatiquement sans action
<span style="color: #9E9E9E"><strong>Et</strong></span> il ne bloque pas la vue de la route</p>
<hr />
<h2 id="13-tracking-du-temps-decoute-precis-cote-client">13. Tracking du temps d'écoute précis côté client</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je démarre la lecture d'un contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le player audio iOS/Android enregistre le temps</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le startTime est enregistré à la milliseconde</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'arrête la lecture (Suivant, Pause, ou fin)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la durée exacte écoutée est calculée
<span style="color: #9E9E9E"><strong>Et</strong></span> le pourcentage (durée / durée_totale * 100) est envoyé à l'API</p>
<hr />
<h2 id="14-api-recoit-les-evenements-decoute-pour-calcul">14. API reçoit les événements d'écoute pour calcul</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu de 5 minutes à 80%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'événement est envoyé à l'API</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le backend reçoit:
<span style="color: #9E9E9E"><strong>Et</strong></span> le backend calcule le like automatique (+2 points)
<span style="color: #9E9E9E"><strong>Et</strong></span> les jauges sont mises à jour immédiatement (Redis + PostgreSQL)</p>
<hr />
<h2 id="15-actions-differentes-selon-arret-du-contenu">15. Actions différentes selon arrêt du contenu</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Suivant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'action envoyée est "skipped"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le contenu se termine naturellement</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'action envoyée est "completed"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Pause"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'action envoyée est "paused"
<span style="color: #9E9E9E"><strong>Et</strong></span> le backend traite chaque action différemment</p>
<hr />
<h2 id="16-calcul-immediat-cote-backend-sans-delai">16. Calcul immédiat côté backend sans délai</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API reçoit un événement d'écoute</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le backend traite l'événement</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les jauges sont mises à jour immédiatement (&lt; 100ms)
<span style="color: #9E9E9E"><strong>Et</strong></span> les nouvelles recommandations utilisent les valeurs actualisées
<span style="color: #9E9E9E"><strong>Et</strong></span> il n'y a aucun batch différé</p>
<hr />
<h2 id="17-compatibilite-ios-avec-avplayer">17. Compatibilité iOS avec AVPlayer</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'app iOS utilise AVPlayer</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les commandes physiques sont interceptées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les événements MPRemoteCommandCenter sont capturés:</p>
<pre><code>| Commande | Événement iOS |
|---|---|
| Suivant | nextTrackCommand |
| Précédent | previousTrackCommand |
| Play/Pause | playCommand / pauseCommand |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> le tracking du temps utilise CMTime</p>
<hr />
<h2 id="18-compatibilite-android-avec-mediasession">18. Compatibilité Android avec MediaSession</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'app Android utilise MediaPlayer</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les commandes physiques sont interceptées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les événements MediaSession sont capturés:</p>
<pre><code>| Commande | Action Android |
|---|---|
| Suivant | ACTION_SKIP_TO_NEXT |
| Précédent | ACTION_SKIP_TO_PREVIOUS |
| Play/Pause | ACTION_PLAY / ACTION_PAUSE |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> le tracking du temps utilise SystemClock.elapsedRealtime()</p>
<hr />
<h2 id="19-securite-maximale-pas-de-distraction">19. Sécurité maximale - pas de distraction</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je conduis à 80 km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'utilise RoadWave avec les commandes au volant</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je n'ai jamais besoin de regarder mon téléphone
<span style="color: #9E9E9E"><strong>Et</strong></span> je n'ai jamais besoin de toucher l'écran CarPlay/Android Auto
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes les actions sont accessibles via boutons physiques
<span style="color: #9E9E9E"><strong>Et</strong></span> les likes sont enregistrés automatiquement</p>
<hr />
<h2 id="20-plan-calcul-du-like-automatique-selon-pourcentage">20. 📋 Plan: Calcul du like automatique selon pourcentage</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu tagué "Sport"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute pendant <pourcentage>%</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le like automatique est <type>
<span style="color: #9E9E9E"><strong>Et</strong></span> l'impact sur la jauge est <points></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>pourcentage</th>
<th>type</th>
<th>points</th>
</tr>
</thead>
<tbody>
<tr>
<td>10</td>
<td>aucun</td>
<td>0</td>
</tr>
<tr>
<td>25</td>
<td>aucun</td>
<td>0</td>
</tr>
<tr>
<td>29</td>
<td>aucun</td>
<td>0</td>
</tr>
<tr>
<td>30</td>
<td>standard</td>
<td>+1</td>
</tr>
<tr>
<td>50</td>
<td>standard</td>
<td>+1</td>
</tr>
<tr>
<td>79</td>
<td>standard</td>
<td>+1</td>
</tr>
<tr>
<td>80</td>
<td>renforcé</td>
<td>+2</td>
</tr>
<tr>
<td>90</td>
<td>renforcé</td>
<td>+2</td>
</tr>
<tr>
<td>100</td>
<td>renforcé</td>
<td>+2</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="21-plan-signal-negatif-uniquement-si-skip-tres-rapide">21. 📋 Plan: Signal négatif uniquement si skip très rapide</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je skip après <secondes> secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le signal est <type>
<span style="color: #9E9E9E"><strong>Et</strong></span> l'impact est <points></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>secondes</th>
<th>type</th>
<th>points</th>
</tr>
</thead>
<tbody>
<tr>
<td>3</td>
<td>négatif</td>
<td>-0.5</td>
</tr>
<tr>
<td>5</td>
<td>négatif</td>
<td>-0.5</td>
</tr>
<tr>
<td>9</td>
<td>négatif</td>
<td>-0.5</td>
</tr>
<tr>
<td>10</td>
<td>neutre</td>
<td>0</td>
</tr>
<tr>
<td>15</td>
<td>neutre</td>
<td>0</td>
</tr>
<tr>
<td>30</td>
<td>neutre</td>
<td>0</td>
</tr>
</tbody>
</table>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="file-dattente-et-commande-suivant">File d'attente et commande "Suivant"</h1>
<blockquote>
<p><em>En tant qu'auditeur en déplacement</em>
<em>Je veux que l'application pré-calcule intelligemment les prochains contenus</em>
<em>Afin d'avoir une navigation fluide sans latence</em></p>
</blockquote>
<p><strong>20 scénarios</strong> (19 standards, 1 plan)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un utilisateur est connecté
<span style="color: #9E9E9E"><strong>Et</strong></span> que la géolocalisation est activée</p>
</blockquote>
<h2 id="1-pre-calcul-initial-de-5-contenus-en-cache">1. Pré-calcul initial de 5 contenus en cache</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je viens de démarrer l'application
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis situé à Paris (48.8566, 2.3522)
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis en mode voiture (vitesse ≥ 5 km/h)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'application initialise la lecture</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une file d'attente de 5 contenus est pré-calculée
<span style="color: #9E9E9E"><strong>Et</strong></span> la file est stockée en cache Redis avec la clé "user:{user_id}:queue"
<span style="color: #9E9E9E"><strong>Et</strong></span> les métadonnées incluent ma position, le timestamp de calcul et le mode
<span style="color: #9E9E9E"><strong>Et</strong></span> le cache a un TTL de 15 minutes</p>
<hr />
<h2 id="2-commande-suivant-sans-latence">2. Commande "Suivant" sans latence</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une file d'attente de 5 contenus est en cache
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'écoute actuellement le contenu "A"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur le bouton "Suivant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu suivant démarre immédiatement (&lt; 100ms)
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu est retiré de la file d'attente
<span style="color: #9E9E9E"><strong>Et</strong></span> il reste 4 contenus dans la file</p>
<hr />
<h2 id="3-recalcul-automatique-apres-deplacement-10km">3. Recalcul automatique après déplacement &gt;10km</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la file a été calculée à Paris (48.8566, 2.3522)
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai 5 contenus en cache</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me déplace à Versailles (48.8049, 2.1204) soit 12km</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la file d'attente est invalidée automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> une nouvelle file de 5 contenus est recalculée
<span style="color: #9E9E9E"><strong>Et</strong></span> elle est basée sur ma nouvelle position</p>
<hr />
<h2 id="4-recalcul-automatique-toutes-les-10-minutes">4. Recalcul automatique toutes les 10 minutes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une file a été calculée il y a 10 minutes
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma position n'a pas changé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le timer de rafraîchissement expire</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une nouvelle file de 5 contenus est recalculée
<span style="color: #9E9E9E"><strong>Et</strong></span> les anciens contenus non écoutés sont remplacés
<span style="color: #9E9E9E"><strong>Et</strong></span> les nouveaux contenus publiés depuis sont inclus</p>
<hr />
<h2 id="5-recalcul-quand-il-reste-moins-de-3-contenus">5. Recalcul quand il reste moins de 3 contenus</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'il reste 3 contenus dans ma file d'attente</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Suivant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il reste 2 contenus
<span style="color: #9E9E9E"><strong>Et</strong></span> un recalcul asynchrone est déclenché en arrière-plan
<span style="color: #9E9E9E"><strong>Et</strong></span> 3 nouveaux contenus sont ajoutés à la file
<span style="color: #9E9E9E"><strong>Et</strong></span> la file contient maintenant 5 contenus</p>
<hr />
<h2 id="6-insertion-prioritaire-dun-contenu-geolocalise-en-mode-voiture">6. Insertion prioritaire d'un contenu géolocalisé en mode voiture</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai une file de 5 contenus pré-calculée
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis en mode voiture
<span style="color: #9E9E9E"><strong>Et</strong></span> que je me déplace à 50 km/h vers un point avec contenu géolocalisé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je suis à 98m du point (ETA = 7 secondes)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une notification est envoyée (icône + compteur 7→1 + son)
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois appuyer sur "Suivant" dans les 7 secondes pour valider</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Suivant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un décompte de 5 secondes démarre
<span style="color: #9E9E9E"><strong>Et</strong></span> après 5 secondes, le contenu géolocalisé s'insère et démarre
<span style="color: #9E9E9E"><strong>Et</strong></span> il remplace le contenu actuel dans la lecture</p>
<hr />
<h2 id="7-contenu-geolocalise-ignore-est-perdu-cooldown-active">7. Contenu géolocalisé ignoré est perdu (cooldown activé)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une notification géolocalisée est affichée (compteur 7→1)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je ne clique pas sur "Suivant" pendant les 7 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la notification disparaît
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu géolocalisé est perdu (pas d'insertion dans la file)
<span style="color: #9E9E9E"><strong>Et</strong></span> un cooldown de 10 minutes est activé
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune nouvelle notification géolocalisée ne sera envoyée pendant 10 minutes</p>
<hr />
<h2 id="8-validation-dune-notification-geolocalisee">8. Validation d'une notification géolocalisée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une notification géolocalisée est affichée (compteur à 5)
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'écoute un podcast</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Suivant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le compteur bascule à "5" (décompte final)
<span style="color: #9E9E9E"><strong>Et</strong></span> le podcast actuel continue de jouer
<span style="color: #9E9E9E"><strong>Et</strong></span> après 5 secondes, le contenu géolocalisé démarre
<span style="color: #9E9E9E"><strong>Et</strong></span> le podcast est mis en pause et sauvegardé dans l'historique</p>
<hr />
<h2 id="9-invalidation-immediate-apres-modification-des-preferences">9. Invalidation immédiate après modification des préférences</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai une file de 5 contenus en cache
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma vitesse GPS est de 5 km/h (piéton)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je modifie mes curseurs de préférences (géo/découverte/politique)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la file d'attente est invalidée immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> une nouvelle file est recalculée avec les nouvelles préférences
<span style="color: #9E9E9E"><strong>Et</strong></span> les anciens contenus en cache sont supprimés</p>
<hr />
<h2 id="10-blocage-modification-preferences-en-conduite-10-kmh">10. Blocage modification préférences en conduite (&gt;10 km/h)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma vitesse GPS est de 50 km/h (en voiture)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie d'accéder aux réglages de préférences</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'interface affiche "Paramètres verrouillés en conduite"
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne peux pas modifier les curseurs géo/découverte/politique
<span style="color: #9E9E9E"><strong>Et</strong></span> un message "Arrêtez-vous pour modifier vos préférences" s'affiche</p>
<hr />
<h2 id="11-invalidation-lors-du-demarrage-dun-live-suivi">11. Invalidation lors du démarrage d'un live suivi</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné au créateur "RadioVoyage"
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai une file de 5 contenus en cache
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis dans la zone géographique du créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur "RadioVoyage" démarre une radio live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification push
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu live s'insère en tête de la file d'attente
<span style="color: #9E9E9E"><strong>Et</strong></span> la file d'attente est recalculée</p>
<hr />
<h2 id="12-metadonnees-de-cache-redis">12. Métadonnées de cache Redis</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une file d'attente est calculée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> elle est stockée dans Redis</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la clé est "user:{user_id}:queue"
<span style="color: #9E9E9E"><strong>Et</strong></span> les métadonnées incluent:</p>
<pre><code>| champ | valeur |
|---|---|
| last_lat | 48.8566 |
| last_lon | 2.3522 |
| computed_at | 2026-01-21T10:30:00Z |
| mode | voiture |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> le TTL est de 15 minutes (900 secondes)</p>
<hr />
<h2 id="13-contenu-geolocalise-remplace-le-contenu-actuel-pas-dinsertion-en-file">13. Contenu géolocalisé remplace le contenu actuel (pas d'insertion en file)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute le contenu C2 de ma file [C1, C2, C3, C4, C5]
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'une notification géolocalisée "Tour Eiffel" est déclenchée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je valide la notification
<span style="color: #9E9E9E"><strong>Et</strong></span> que le décompte de 5s se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu "Tour Eiffel" remplace C2 et démarre
<span style="color: #9E9E9E"><strong>Et</strong></span> C2 est sauvegardé dans l'historique de navigation
<span style="color: #9E9E9E"><strong>Et</strong></span> la file reste [C3, C4, C5] (pas de contenu retiré)
<span style="color: #9E9E9E"><strong>Et</strong></span> quand "Tour Eiffel" se termine, C3 démarre</p>
<hr />
<h2 id="14-invalidation-apres-deplacement-exactement-10km">14. Invalidation après déplacement exactement 10km</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la file a été calculée à une position donnée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me déplace d'exactement 10.0 km</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la file d'attente n'est PAS invalidée (seuil strict &gt;10km)
<span style="color: #9E9E9E"><strong>Et</strong></span> les contenus en cache restent valides</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me déplace de 10.1 km supplémentaires (total 10.1km)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la file d'attente est invalidée
<span style="color: #9E9E9E"><strong>Et</strong></span> une nouvelle file est calculée</p>
<hr />
<h2 id="15-rafraichissement-exactement-apres-10-minutes">15. Rafraîchissement exactement après 10 minutes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une file a été calculée à 10:00:00</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'heure actuelle est 10:10:00</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le timer de rafraîchissement expire
<span style="color: #9E9E9E"><strong>Et</strong></span> une nouvelle file de 5 contenus est recalculée
<span style="color: #9E9E9E"><strong>Et</strong></span> le timestamp "computed_at" est mis à jour</p>
<hr />
<h2 id="16-recalcul-asynchrone-non-bloquant">16. Recalcul asynchrone non-bloquant</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'il reste 2 contenus dans la file
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'appuie sur "Suivant"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le recalcul asynchrone démarre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la lecture du contenu actuel n'est pas interrompue
<span style="color: #9E9E9E"><strong>Et</strong></span> le recalcul se fait en arrière-plan
<span style="color: #9E9E9E"><strong>Et</strong></span> les nouveaux contenus sont ajoutés dès disponibles (&lt; 500ms)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur ne perçoit aucune latence</p>
<hr />
<h2 id="17-notification-basee-sur-eta-pas-distance-fixe">17. Notification basée sur ETA (pas distance fixe)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu géolocalisé existe à un point GPS
<span style="color: #9E9E9E"><strong>Et</strong></span> que je roule à 130 km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je suis à 252m du point (ETA = 7 secondes)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une notification est envoyée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je suis à 300m du point (ETA = 8 secondes)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune notification n'est envoyée (ETA &gt;7s)</p>
<hr />
<h2 id="18-plan-differentes-distances-de-deplacement-et-invalidation">18. 📋 Plan: Différentes distances de déplacement et invalidation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une file a été calculée à une position donnée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me déplace de <distance> km</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la file est <action></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>distance</th>
<th>action</th>
</tr>
</thead>
<tbody>
<tr>
<td>5</td>
<td>conservée</td>
</tr>
<tr>
<td>9.9</td>
<td>conservée</td>
</tr>
<tr>
<td>10.0</td>
<td>conservée</td>
</tr>
<tr>
<td>10.1</td>
<td>invalidée et recalculée</td>
</tr>
<tr>
<td>15</td>
<td>invalidée et recalculée</td>
</tr>
<tr>
<td>50</td>
<td>invalidée et recalculée</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="19-quota-de-6-contenus-geolocalises-par-heure">19. Quota de 6 contenus géolocalisés par heure</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai validé 6 notifications géolocalisées dans la dernière heure</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un 7ème contenu géolocalisé est détecté (ETA 7s)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune notification n'est envoyée
<span style="color: #9E9E9E"><strong>Et</strong></span> le quota horaire est respecté</p>
<hr />
<h2 id="20-mode-pieton-pas-de-notification-avec-compteur-7s">20. Mode piéton - pas de notification avec compteur 7s</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis en mode piéton (vitesse &lt;5 km/h)
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un audio-guide géolocalisé existe à 150m</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je passe dans le rayon de 200m</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une notification push système est envoyée
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun compteur 7s n'est affiché
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux ouvrir l'app en tapant sur la notification</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="lecture-en-boucle-et-enchainement-automatique">Lecture en boucle et enchaînement automatique</h1>
<blockquote>
<p><em>En tant qu'auditeur</em>
<em>Je veux que les contenus s'enchaînent automatiquement avec un délai paramétrable</em>
<em>Afin d'avoir une expérience fluide sans interruption</em></p>
</blockquote>
<p><strong>27 scénarios</strong> (24 standards, 3 plans)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un utilisateur est connecté</p>
</blockquote>
<h2 id="1-passage-automatique-apres-2-secondes-mode-standard">1. Passage automatique après 2 secondes (mode standard)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu "A" en mode standard</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la lecture se termine naturellement</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un timer de 2 secondes démarre
<span style="color: #9E9E9E"><strong>Et</strong></span> un overlay s'affiche: "Contenu suivant dans 2s..."
<span style="color: #9E9E9E"><strong>Et</strong></span> une barre de décompte visuelle s'affiche</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le timer atteint 0</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu "B" démarre automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> l'overlay disparaît</p>
<hr />
<h2 id="2-passage-automatique-apres-1-seconde-mode-kids">2. Passage automatique après 1 seconde (mode Kids)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis en mode Kids
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'écoute un contenu pour enfants</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la lecture se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un timer de 1 seconde démarre
<span style="color: #9E9E9E"><strong>Et</strong></span> le message "Contenu suivant dans 1s..." s'affiche</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le timer expire</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu suivant démarre automatiquement</p>
<hr />
<h2 id="3-passage-immediat-apres-une-radio-live-0-seconde">3. Passage immédiat après une radio live (0 seconde)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute une radio live</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur arrête la diffusion</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le passage au contenu suivant est immédiat (0s de délai)
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun overlay de décompte n'est affiché
<span style="color: #9E9E9E"><strong>Et</strong></span> la transition est fluide</p>
<hr />
<h2 id="4-annulation-du-passage-automatique">4. Annulation du passage automatique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu se termine
<span style="color: #9E9E9E"><strong>Et</strong></span> que le timer de 2 secondes démarre</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Rester sur ce contenu" pendant le décompte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le timer est annulé
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu actuel reste en pause à la fin
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu suivant n'est pas lancé</p>
<hr />
<h2 id="5-insertion-de-publicite-pendant-le-delai-de-transition">5. Insertion de publicité pendant le délai de transition</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai écouté 4 contenus sans publicité
<span style="color: #9E9E9E"><strong>Et</strong></span> que le 5ème contenu se termine</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le délai de 2 secondes démarre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une publicité s'insère dans la file d'attente
<span style="color: #9E9E9E"><strong>Et</strong></span> le message devient "Publicité (15s)"
<span style="color: #9E9E9E"><strong>Et</strong></span> la publicité démarre après les 2 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> elle ne coupe jamais un contenu en cours</p>
<hr />
<h2 id="6-frequence-de-publicite-parametrable-admin">6. Fréquence de publicité paramétrable admin</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la fréquence pub est configurée à "1/5 contenus"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute 10 contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 2 publicités sont insérées (après les contenus 5 et 10)</p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'admin change la fréquence à "1/3 contenus"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute 9 contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 3 publicités sont insérées (après les contenus 3, 6 et 9)</p>
<hr />
<h2 id="7-publicite-skippable-apres-5-secondes-par-defaut">7. Publicité skippable après 5 secondes par défaut</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité de 30 secondes démarre
<span style="color: #9E9E9E"><strong>Et</strong></span> que le délai minimal de visionnage est configuré à 5 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute pendant 3 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le bouton "Passer" n'est pas encore visible</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'atteins 5 secondes d'écoute</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le bouton "Passer" apparaît
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux cliquer pour passer au contenu suivant</p>
<hr />
<h2 id="8-delai-minimal-de-publicite-parametrable-admin">8. Délai minimal de publicité paramétrable admin</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité démarre
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'admin a configuré le délai à 10 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute pendant 9 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le bouton "Passer" n'est pas visible</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'atteins 10 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le bouton "Passer" apparaît
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux skipper la publicité</p>
<hr />
<h2 id="9-like-et-abonnement-autorises-sur-une-publicite">9. Like et abonnement autorisés sur une publicité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité est en lecture</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur le bouton cœur (véhicule arrêté)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publicité reçoit un like (+2% jauges tags pub)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "S'abonner" au créateur de la pub</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je suis abonné (+5% jauges tags créateur)
<span style="color: #9E9E9E"><strong>Et</strong></span> le créateur de pub bénéficie de l'engagement</p>
<hr />
<h2 id="10-metriques-dengagement-publicite-trackees">10. Métriques d'engagement publicité trackées</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité de 30s est diffusée à 100 auditeurs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> 40 auditeurs écoutent entièrement (30s)
<span style="color: #9E9E9E"><strong>Et</strong></span> que 50 auditeurs skippent après 10s
<span style="color: #9E9E9E"><strong>Et</strong></span> que 10 auditeurs skippent avant 5s</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les métriques sont:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="11-message-aucun-contenu-disponible-si-file-vide">11. Message "Aucun contenu disponible" si file vide</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la file d'attente est vide
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'aucun contenu n'est disponible dans ma zone</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le contenu actuel se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message s'affiche: "Aucun contenu disponible dans cette zone"
<span style="color: #9E9E9E"><strong>Et</strong></span> une proposition apparaît: "Élargir la zone de recherche ?"
<span style="color: #9E9E9E"><strong>Et</strong></span> un bouton "Élargir" est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> la lecture se met en pause automatiquement</p>
<hr />
<h2 id="12-elargissement-automatique-de-la-zone-de-recherche">12. Élargissement automatique de la zone de recherche</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le message "Aucun contenu disponible" s'affiche</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Élargir la zone"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'algorithme relance une recherche avec rayon +50km
<span style="color: #9E9E9E"><strong>Et</strong></span> une notification "Recherche élargie à 50km" s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> la file d'attente est recalculée
<span style="color: #9E9E9E"><strong>Et</strong></span> la lecture reprend automatiquement</p>
<hr />
<h2 id="13-refus-delargissement-laisse-en-pause">13. Refus d'élargissement laisse en pause</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le message "Aucun contenu disponible" s'affiche</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Annuler"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la lecture reste en pause
<span style="color: #9E9E9E"><strong>Et</strong></span> l'interface affiche "En attente de contenu"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux manuellement naviguer ou chercher du contenu</p>
<hr />
<h2 id="14-retry-avec-backoff-exponentiel-en-cas-dechec-reseau">14. Retry avec backoff exponentiel en cas d'échec réseau</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le contenu suivant échoue au chargement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la première tentative échoue</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système retente après 1 seconde (backoff 1s)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la 2ème tentative échoue</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système retente après 2 secondes (backoff 2s)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la 3ème tentative échoue</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système retente après 4 secondes (backoff 4s)
<span style="color: #9E9E9E"><strong>Et</strong></span> après 3 échecs totaux, le système bascule en mode offline</p>
<hr />
<h2 id="15-basculement-mode-offline-apres-3-echecs-reseau">15. Basculement mode offline après 3 échecs réseau</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai eu 3 échecs de chargement consécutifs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le 3ème échec se produit</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message "Connexion instable, basculement mode offline" s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> la lecture continue avec les contenus téléchargés uniquement
<span style="color: #9E9E9E"><strong>Et</strong></span> les contenus en ligne sont temporairement désactivés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la connexion revient</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le mode en ligne est automatiquement rétabli</p>
<hr />
<h2 id="16-overlay-de-decompte-avec-barre-visuelle">16. Overlay de décompte avec barre visuelle</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu se termine</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le timer de 2 secondes démarre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un overlay semi-transparent s'affiche en bas de l'écran
<span style="color: #9E9E9E"><strong>Et</strong></span> le texte "Contenu suivant dans 2s..." est visible
<span style="color: #9E9E9E"><strong>Et</strong></span> une barre de progression décroît de 100% à 0% en 2 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> la couleur de la barre passe de vert à orange
<span style="color: #9E9E9E"><strong>Et</strong></span> l'overlay disparaît automatiquement après le décompte</p>
<hr />
<h2 id="17-bouton-rester-sur-ce-contenu-pendant-decompte">17. Bouton "Rester sur ce contenu" pendant décompte</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le décompte de 2 secondes est actif</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'overlay s'affiche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un bouton "Rester sur ce contenu" est visible
<span style="color: #9E9E9E"><strong>Et</strong></span> il est cliquable pendant les 2 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique dessus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le timer est annulé immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> l'overlay disparaît
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu actuel reste affiché en pause</p>
<hr />
<h2 id="18-pas-dinterruption-dun-contenu-en-cours">18. Pas d'interruption d'un contenu en cours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu de 10 minutes
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis à 5 minutes de lecture</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> une publicité devrait s'insérer (fréquence 1/5)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publicité n'interrompt jamais le contenu en cours
<span style="color: #9E9E9E"><strong>Et</strong></span> elle attend la fin du contenu actuel
<span style="color: #9E9E9E"><strong>Et</strong></span> elle s'insère pendant le délai de transition (2s)</p>
<hr />
<h2 id="19-publicites-uniquement-pour-utilisateurs-gratuits">19. Publicités uniquement pour utilisateurs gratuits</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur gratuit</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute 5 contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une publicité est insérée après le 5ème contenu</p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je passe en compte Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute 100 contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune publicité n'est insérée
<span style="color: #9E9E9E"><strong>Et</strong></span> l'enchaînement est direct (2s de transition seulement)</p>
<hr />
<h2 id="20-message-clair-pour-lutilisateur-lors-de-la-publicite">20. Message clair pour l'utilisateur lors de la publicité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité va démarrer</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le délai de transition démarre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le message affiché est: "Publicité (15s)"
<span style="color: #9E9E9E"><strong>Et</strong></span> la durée totale de la pub est indiquée
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur sait qu'il s'agit d'une pub
<span style="color: #9E9E9E"><strong>Et</strong></span> la transparence est maximale</p>
<hr />
<h2 id="21-transition-fluide-entre-contenus-sans-coupure">21. Transition fluide entre contenus sans coupure</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu se termine
<span style="color: #9E9E9E"><strong>Et</strong></span> que le suivant est pré-chargé en cache</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le timer de 2s expire</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la transition audio utilise un crossfade de 0.3s
<span style="color: #9E9E9E"><strong>Et</strong></span> il n'y a aucun blanc ou coupure
<span style="color: #9E9E9E"><strong>Et</strong></span> l'expérience est fluide</p>
<hr />
<h2 id="22-gestion-des-erreurs-de-chargement-avec-retry">22. Gestion des erreurs de chargement avec retry</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le contenu suivant échoue au chargement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la 1ère tentative échoue</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une notification "Chargement..." s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> le système retente automatiquement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la 2ème tentative réussit</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la lecture démarre normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune action utilisateur n'est requise</p>
<hr />
<h2 id="23-mode-offline-apres-echecs-multiples">23. Mode offline après échecs multiples</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai 50 contenus téléchargés en mode offline
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai eu 3 échecs réseau consécutifs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le mode offline s'active</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seuls les contenus téléchargés sont disponibles
<span style="color: #9E9E9E"><strong>Et</strong></span> un badge "Mode offline" s'affiche en haut de l'écran
<span style="color: #9E9E9E"><strong>Et</strong></span> la lecture continue sans interruption</p>
<hr />
<h2 id="24-compteur-de-contenus-avant-prochaine-publicite">24. Compteur de contenus avant prochaine publicité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la fréquence pub est 1/5 contenus
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai écouté 3 contenus depuis la dernière pub</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte l'interface</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un indicateur discret affiche "2 contenus avant pub"
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur sait quand attendre la prochaine publicité</p>
<hr />
<h2 id="25-plan-delai-de-transition-selon-mode">25. 📋 Plan: Délai de transition selon mode</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis en mode <mode></p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un contenu se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le délai de transition est <delai> secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> le message affiché est <message></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>mode</th>
<th>delai</th>
<th>message</th>
</tr>
</thead>
<tbody>
<tr>
<td>Standard</td>
<td>2</td>
<td>"Contenu suivant dans 2s..."</td>
</tr>
<tr>
<td>Kids</td>
<td>1</td>
<td>"Contenu suivant dans 1s..."</td>
</tr>
<tr>
<td>Live</td>
<td>0</td>
<td>(aucun message)</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="26-plan-frequence-dinsertion-des-publicites">26. 📋 Plan: Fréquence d'insertion des publicités</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la fréquence pub est configurée à <frequence></p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute <contenus> contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> <pubs> publicités sont insérées</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>frequence</th>
<th>contenus</th>
<th>pubs</th>
</tr>
</thead>
<tbody>
<tr>
<td>1/3</td>
<td>6</td>
<td>2</td>
</tr>
<tr>
<td>1/3</td>
<td>9</td>
<td>3</td>
</tr>
<tr>
<td>1/5</td>
<td>10</td>
<td>2</td>
</tr>
<tr>
<td>1/5</td>
<td>15</td>
<td>3</td>
</tr>
<tr>
<td>1/7</td>
<td>14</td>
<td>2</td>
</tr>
<tr>
<td>1/7</td>
<td>21</td>
<td>3</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="27-plan-backoff-exponentiel-retry">27. 📋 Plan: Backoff exponentiel retry</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le chargement échoue</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je suis à la tentative <tentative></p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le délai de retry est <delai> secondes</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>tentative</th>
<th>delai</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td>2</td>
<td>2</td>
</tr>
<tr>
<td>3</td>
<td>4</td>
</tr>
</tbody>
</table>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="partage-de-contenu">Partage de contenu</h1>
<blockquote>
<p><em>En tant qu'utilisateur de RoadWave</em>
<em>Je veux pouvoir partager du contenu audio</em>
<em>Afin de faire découvrir l'application à d'autres personnes</em></p>
</blockquote>
<p><strong>22 scénarios</strong> (20 standards, 2 plans)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'application RoadWave est démarrée
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur "jean@example.com" est connecté</p>
</blockquote>
<h2 id="1-bouton-partager-disponible-dans-le-player-en-lecture">1. Bouton partager disponible dans le player en lecture</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le contenu "Balade à Paris" est en cours de lecture</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur consulte les contrôles du player</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le bouton "Partager" ⬆️ est visible</p>
<hr />
<h2 id="2-bouton-partager-disponible-sur-la-page-profil-createur">2. Bouton partager disponible sur la page profil créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur consulte le profil de "@paris_stories"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur consulte un contenu dans la liste</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le bouton "Partager" est disponible pour chaque contenu</p>
<hr />
<h2 id="3-bouton-partager-dans-la-liste-de-recherche">3. Bouton partager dans la liste de recherche</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur effectue une recherche "voyage paris"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur ouvre le menu contextuel d'un résultat</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'option "Partager" est disponible</p>
<hr />
<h2 id="4-bouton-partager-dans-lhistorique-personnel">4. Bouton partager dans l'historique personnel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur consulte son historique d'écoute</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur sélectionne un contenu de l'historique</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le bouton "Partager" est accessible</p>
<hr />
<h2 id="5-plan-menu-de-partage-avec-options-multiples">5. 📋 Plan: Menu de partage avec options multiples</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le contenu "<contenu>" est disponible</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur le bouton "Partager"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le menu natif OS s'ouvre
<span style="color: #9E9E9E"><strong>Et</strong></span> les options suivantes sont disponibles:</p>
<pre><code>| option |
|---|
| Copier le lien |
| WhatsApp |
| Email |
| SMS |
| Plus... |
</code></pre>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>contenu</th>
</tr>
</thead>
<tbody>
<tr>
<td>Balade à Paris</td>
</tr>
<tr>
<td>Secrets de Montmartre</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="6-generation-du-lien-de-partage">6. Génération du lien de partage</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un contenu avec l'ID "content_12345"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur copie le lien de partage</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le lien généré est "https://roadwave.fr/share/c/content_12345"</p>
<hr />
<h2 id="7-ouverture-du-lien-partage-avec-lapplication-installee-deep-link">7. Ouverture du lien partagé avec l'application installée (Deep link)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'application RoadWave est installée sur l'appareil
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un lien "https://roadwave.fr/share/c/content_12345" est partagé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur le lien</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'application RoadWave s'ouvre automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu "content_12345" commence à jouer</p>
<hr />
<h2 id="8-ouverture-du-lien-partage-sans-lapplication-installee-web-player">8. Ouverture du lien partagé sans l'application installée (Web player)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'application RoadWave n'est pas installée
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un lien "https://roadwave.fr/share/c/content_12345" est partagé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur le lien</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une page web responsive s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> le web player HTML5 est visible
<span style="color: #9E9E9E"><strong>Et</strong></span> les boutons de téléchargement App Store et Google Play sont affichés</p>
<hr />
<h2 id="9-contenu-de-la-page-web-de-partage">9. Contenu de la page web de partage</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un contenu public avec les métadonnées suivantes:</p>
<pre><code>| champ | valeur |
|---|---|
| titre | Balade à Paris |
| créateur | @paris_stories |
| durée | 12 min |
| écoutes | 2300 |
| localisation | Paris 5e |
| type_geo | Ancré |
| tags | Voyage, Histoire |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la page de partage est affichée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la page contient:</p>
<pre><code>| é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 |
</code></pre>
<hr />
<h2 id="10-metadonnees-open-graph-pour-partage-social">10. Métadonnées Open Graph pour partage social</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un contenu "Balade à Paris" par "@paris_stories"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la page de partage est générée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les métadonnées Open Graph incluent:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> l'aperçu s'affiche correctement sur WhatsApp
<span style="color: #9E9E9E"><strong>Et</strong></span> l'aperçu s'affiche correctement sur Facebook
<span style="color: #9E9E9E"><strong>Et</strong></span> l'aperçu s'affiche correctement sur Twitter</p>
<hr />
<h2 id="11-plan-deep-linking-par-plateforme">11. 📋 Plan: Deep linking par plateforme</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'application RoadWave est installée sur <plateforme>
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un lien de partage est ouvert</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte l'application</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'application s'ouvre via <mécanisme></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>plateforme</th>
<th>mécanisme</th>
</tr>
</thead>
<tbody>
<tr>
<td>iOS</td>
<td>Universal Links</td>
</tr>
<tr>
<td>Android</td>
<td>App Links</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="12-fallback-url-scheme-pour-deep-linking">12. Fallback URL scheme pour deep linking</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que les App Links ne fonctionnent pas</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système tente d'ouvrir le contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'URL scheme "roadwave://content/content_12345" est utilisé</p>
<hr />
<h2 id="13-badge-premium-visible-sur-le-lien-partage">13. Badge Premium visible sur le lien partagé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un contenu Premium "Visite VIP Louvre"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur non-premium clique sur le lien partagé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la page web affiche le badge "👑 Contenu Premium"</p>
<hr />
<h2 id="14-preview-30-secondes-dun-contenu-premium-partage">14. Preview 30 secondes d'un contenu Premium partagé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un contenu Premium "Visite VIP Louvre" de 15 minutes
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un utilisateur non-premium ouvre le lien partagé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le player démarre automatiquement</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'audio joue pendant 30 secondes exactement
<span style="color: #9E9E9E"><strong>Et</strong></span> un fade out de 2 secondes est appliqué
<span style="color: #9E9E9E"><strong>Et</strong></span> un overlay "Contenu réservé Premium" s'affiche après 32 secondes</p>
<hr />
<h2 id="15-contenu-de-loverlay-paywall-premium">15. Contenu de l'overlay paywall Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu Premium a atteint la limite de 30 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'overlay paywall s'affiche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le texte suivant est visible:</p>
<hr />
<h2 id="16-actions-disponibles-sur-loverlay-premium">16. Actions disponibles sur l'overlay Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'overlay paywall Premium est affiché</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur consulte les options</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les actions suivantes sont disponibles:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="17-relecture-illimitee-du-preview-premium">17. Relecture illimitée du preview Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un contenu Premium partagé
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur a écouté les 30 premières secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur "Rejouer"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les 30 premières secondes sont rejouées
<span style="color: #9E9E9E"><strong>Et</strong></span> cette action est possible de manière illimitée</p>
<hr />
<h2 id="18-tracking-des-partages-premium">18. Tracking des partages Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un créateur "@guide_louvre" avec un contenu Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> son contenu est partagé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les métriques suivantes sont enregistrées:</p>
<pre><code>| métrique | valeur |
|---|---|
| Partages Premium | +1 |
| Ouvertures lien | compteur |
| Conversions Premium | si souscription |
</code></pre>
<hr />
<h2 id="19-remuneration-createur-sur-conversion-premium-via-partage">19. Rémunération créateur sur conversion Premium via partage</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un contenu Premium partagé par "@guide_louvre"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un utilisateur s'abonne via le lien partagé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le créateur reçoit 70% des revenus de cet abonnement
<span style="color: #9E9E9E"><strong>Et</strong></span> la conversion est trackée dans son dashboard</p>
<hr />
<h2 id="20-partage-dun-contenu-supprime">20. Partage d'un contenu supprimé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un lien de partage "https://roadwave.fr/share/c/deleted_content" est ouvert
<span style="color: #9E9E9E"><strong>Et</strong></span> que le contenu n'existe plus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la page web se charge</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message "Ce contenu n'est plus disponible" s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> les boutons de téléchargement de l'app sont affichés</p>
<hr />
<h2 id="21-partage-dun-contenu-en-attente-de-moderation">21. Partage d'un contenu en attente de modération</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un contenu en cours de validation modération</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un lien de partage est ouvert</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le message "Ce contenu est en cours de validation" s'affiche</p>
<hr />
<h2 id="22-generation-du-lien-hors-connexion">22. Génération du lien hors connexion</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur n'a pas de connexion réseau</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur tente de partager un contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le lien est copié dans le presse-papiers
<span style="color: #9E9E9E"><strong>Et</strong></span> un message "Lien copié (nécessite connexion pour ouvrir)" s'affiche</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="avantages-premium">Avantages Premium</h1>
<blockquote>
<p><em>En tant qu'abonné Premium</em>
<em>Je veux bénéficier d'avantages exclusifs</em>
<em>Afin de profiter d'une expérience audio améliorée sans publicité</em></p>
</blockquote>
<p><strong>37 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis connecté à l'application RoadWave</p>
</blockquote>
<h2 id="1-utilisateur-gratuit-voit-1-publicite-tous-les-5-contenus">1. Utilisateur gratuit voit 1 publicité tous les 5 contenus</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur gratuit</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute ma file de contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois une publicité tous les 5 contenus
<span style="color: #9E9E9E"><strong>Et</strong></span> la publicité dure 30 secondes en moyenne
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne peux pas la skip</p>
<hr />
<h2 id="2-utilisateur-premium-ne-voit-aucune-publicite">2. Utilisateur Premium ne voit aucune publicité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute mes contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune publicité n'est diffusée
<span style="color: #9E9E9E"><strong>Et</strong></span> je passe directement d'un contenu à l'autre
<span style="color: #9E9E9E"><strong>Et</strong></span> l'expérience d'écoute est fluide et ininterrompue</p>
<hr />
<h2 id="3-badge-0-publicite-sur-page-premium">3. Badge "0 publicité" sur page Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte la page des avantages Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je lis la liste des avantages</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois en premier:
<span style="color: #9E9E9E"><strong>Et</strong></span> c'est l'argument principal mis en avant</p>
<hr />
<h2 id="4-utilisateur-gratuit-voit-contenus-premium-bloques">4. Utilisateur gratuit voit contenus Premium bloqués</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur gratuit</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les contenus d'un créateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois les contenus marqués Premium avec badge 👑
<span style="color: #F44336"><strong>Mais</strong></span> je ne peux pas les lire (overlay bloquant)</p>
<hr />
<h2 id="5-utilisateur-premium-accede-a-tous-les-contenus-exclusifs">5. Utilisateur Premium accède à tous les contenus exclusifs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les contenus d'un créateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les contenus Premium sont accessibles
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux les lire sans restriction
<span style="color: #9E9E9E"><strong>Et</strong></span> j'ai accès à 100% du catalogue (gratuit + Premium)</p>
<hr />
<h2 id="6-nombre-de-contenus-premium-disponibles">6. Nombre de contenus Premium disponibles</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les statistiques</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois combien de contenus Premium sont disponibles sur la plateforme
<span style="color: #9E9E9E"><strong>Et</strong></span> par exemple: "8,547 contenus Premium exclusifs disponibles"
<span style="color: #9E9E9E"><strong>Et</strong></span> cela justifie la valeur de l'abonnement</p>
<hr />
<h2 id="7-utilisateur-gratuit-ecoute-en-48-kbps-opus">7. Utilisateur gratuit écoute en 48 kbps Opus</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur gratuit</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je lance un contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'audio est streamé en 48 kbps Opus
<span style="color: #9E9E9E"><strong>Et</strong></span> cela consomme environ 20 MB/heure
<span style="color: #9E9E9E"><strong>Et</strong></span> la qualité est très correcte pour de la voix</p>
<hr />
<h2 id="8-utilisateur-premium-ecoute-en-64-kbps-opus">8. Utilisateur Premium écoute en 64 kbps Opus</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je lance un contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'audio est streamé en 64 kbps Opus
<span style="color: #9E9E9E"><strong>Et</strong></span> cela consomme environ 30 MB/heure
<span style="color: #9E9E9E"><strong>Et</strong></span> la qualité est excellente (détails audio supérieurs)</p>
<hr />
<h2 id="9-comparaison-qualite-48-kbps-vs-64-kbps">9. Comparaison qualité 48 kbps vs 64 kbps</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte la page Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je lis la section qualité audio</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois l'explication:</p>
<hr />
<h2 id="10-justification-48-kbps-suffisant-pour-gratuit">10. Justification 48 kbps suffisant pour gratuit</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le contenu RoadWave est principalement de la voix</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la qualité est fixée à 48 kbps pour gratuit</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> c'est largement suffisant pour comprendre clairement
<span style="color: #9E9E9E"><strong>Et</strong></span> équivalent à la qualité radio FM
<span style="color: #9E9E9E"><strong>Et</strong></span> les utilisateurs gratuits ne sont pas frustrés</p>
<hr />
<h2 id="11-justification-64-kbps-avantage-tangible-premium">11. Justification 64 kbps avantage tangible Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que les audiophiles et créateurs audio sont exigeants</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la qualité Premium est à 64 kbps</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la différence est perceptible à l'oreille
<span style="color: #9E9E9E"><strong>Et</strong></span> les ambiances, musiques de fond, nuances de voix sont mieux rendues
<span style="color: #9E9E9E"><strong>Et</strong></span> cela justifie l'abonnement Premium</p>
<hr />
<h2 id="12-switch-automatique-qualite-selon-abonnement">12. Switch automatique qualité selon abonnement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis gratuit et j'écoute en 48 kbps</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je souscris à Premium</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> dès le contenu suivant, je passe automatiquement en 64 kbps
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux entendre la différence de qualité immédiatement</p>
<hr />
<h2 id="13-consommation-data-premium-vs-gratuit">13. Consommation data Premium vs Gratuit</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je roule 1 heure par jour</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je calcule la consommation mensuelle</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> en gratuit: 20 MB/h × 1h × 22 jours = 440 MB/mois
<span style="color: #9E9E9E"><strong>Et</strong></span> en Premium: 30 MB/h × 1h × 22 jours = 660 MB/mois
<span style="color: #9E9E9E"><strong>Et</strong></span> la différence est de 220 MB/mois (acceptable pour 4G/5G illimitée)</p>
<hr />
<h2 id="14-utilisateur-gratuit-limite-a-50-contenus-telecharges">14. Utilisateur gratuit limité à 50 contenus téléchargés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur gratuit</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède au mode offline</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux télécharger jusqu'à 50 contenus maximum
<span style="color: #9E9E9E"><strong>Et</strong></span> si j'essaie de télécharger un 51ème, je vois:</p>
<hr />
<h2 id="15-utilisateur-premium-telechargements-illimites">15. Utilisateur Premium téléchargements illimités</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède au mode offline</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux télécharger autant de contenus que je veux
<span style="color: #9E9E9E"><strong>Et</strong></span> la seule limite est l'espace de stockage de mon device
<span style="color: #9E9E9E"><strong>Et</strong></span> par exemple 500 contenus × 10 MB = 5 GB</p>
<hr />
<h2 id="16-justification-limite-50-contenus-gratuit">16. Justification limite 50 contenus gratuit</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 50 contenus de 10 minutes = ~8 heures d'écoute</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un utilisateur gratuit prépare un road trip</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 8 heures couvrent largement une journée de trajet
<span style="color: #9E9E9E"><strong>Et</strong></span> cela permet un usage offline raisonnable sans abuser</p>
<hr />
<h2 id="17-justification-illimite-premium-pour-longs-road-trips">17. Justification illimité Premium pour longs road trips</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un road trip de plusieurs jours nécessite 20-50h de contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un utilisateur Premium télécharge 200 contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il peut partir serein sans connexion internet pendant 1 semaine
<span style="color: #9E9E9E"><strong>Et</strong></span> cela justifie pleinement l'abonnement Premium</p>
<hr />
<h2 id="18-affichage-compteur-telechargements-gratuit">18. Affichage compteur téléchargements gratuit</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis gratuit et j'ai téléchargé 37 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à la page Téléchargements</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<hr />
<h2 id="19-pas-de-compteur-pour-premium">19. Pas de compteur pour Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis Premium et j'ai téléchargé 187 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à la page Téléchargements</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois simplement:
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune limite n'est affichée</p>
<hr />
<h2 id="20-utilisateur-gratuit-historique-limite-a-100-derniers">20. Utilisateur gratuit historique limité à 100 derniers</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur gratuit</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à mon historique d'écoute</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois les 100 derniers contenus écoutés
<span style="color: #9E9E9E"><strong>Et</strong></span> les contenus plus anciens ne sont pas affichés
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois un message "Historique limité à 100 contenus. Passez Premium pour un historique illimité."</p>
<hr />
<h2 id="21-utilisateur-premium-historique-illimite">21. Utilisateur Premium historique illimité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à mon historique d'écoute</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois tous les contenus que j'ai écoutés depuis mon inscription
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux scroller jusqu'au premier contenu jamais écouté
<span style="color: #9E9E9E"><strong>Et</strong></span> l'historique est complet et permanent</p>
<hr />
<h2 id="22-recherche-dans-historique-premium">22. Recherche dans historique Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis Premium et j'ai 2 000 contenus dans mon historique</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je recherche "Tesla" dans mon historique</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les contenus écoutés contenant "Tesla" sont affichés
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux retrouver facilement un contenu écouté il y a 6 mois</p>
<hr />
<h2 id="23-justification-limite-100-gratuit-suffisante">23. Justification limite 100 gratuit suffisante</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 100 contenus de 10 min = ~16 heures d'écoute</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un utilisateur gratuit écoute 1h/jour</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'historique couvre les 16 derniers jours
<span style="color: #9E9E9E"><strong>Et</strong></span> cela suffit pour retrouver un contenu récent</p>
<hr />
<h2 id="24-justification-illimite-premium-pour-power-users">24. Justification illimité Premium pour power users</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un power user écoute 3h/jour depuis 2 ans</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il veut retrouver un contenu spécifique écouté il y a 1 an</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'historique illimité Premium lui permet de retrouver ce contenu
<span style="color: #9E9E9E"><strong>Et</strong></span> cela apporte une vraie valeur ajoutée</p>
<hr />
<h2 id="25-export-historique-complet-premium-uniquement">25. Export historique complet (Premium uniquement)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je demande l'export de mes données</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'historique complet est inclus dans l'export:</p>
<hr />
<h2 id="26-affichage-tableau-comparatif-gratuit-vs-premium">26. Affichage tableau comparatif Gratuit vs Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte la page Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je vois le tableau comparatif</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il affiche:</p>
<hr />
<h2 id="27-justification-0-pub-argument-principal">27. Justification 0 pub = argument principal</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité de 30s tous les 5 contenus = 6 min/h de pub</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un utilisateur écoute 1h/jour</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il subit 180 min de pub/mois (3 heures !)
<span style="color: #9E9E9E"><strong>Et</strong></span> payer 4.99€ pour éviter 3h de pub/mois est très rentable
<span style="color: #9E9E9E"><strong>Et</strong></span> c'est l'argument de conversion n°1</p>
<hr />
<h2 id="28-justification-qualite-audio-avantage-tangible">28. Justification qualité audio avantage tangible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la différence 48 kbps → 64 kbps est audible</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un audiophile compare les deux</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il entend clairement la différence sur un bon système audio voiture
<span style="color: #9E9E9E"><strong>Et</strong></span> cela justifie l'abonnement pour les exigeants</p>
<hr />
<h2 id="29-justification-offline-illimite-pour-road-trips">29. Justification offline illimité pour road trips</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un road trip de 2 semaines nécessite 50-100h de contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un utilisateur Premium télécharge 300 contenus avant de partir</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il peut partir en zone sans réseau sereinement
<span style="color: #9E9E9E"><strong>Et</strong></span> cela apporte une vraie valeur pratique</p>
<hr />
<h2 id="30-justification-pas-dover-engineering">30. Justification pas d'over-engineering</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave se concentre sur l'essentiel</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les avantages Premium sont définis</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il n'y a pas de:</p>
<pre><code>| 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é &gt; bénéfice |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> cela réduit la complexité et le coût de développement</p>
<hr />
<h2 id="31-cta-premium-apres-5eme-publicite">31. CTA Premium après 5ème publicité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis gratuit et je viens d'entendre ma 5ème pub</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la publicité se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois un message:</p>
<hr />
<h2 id="32-cta-premium-quand-limite-50-telechargements-atteinte">32. CTA Premium quand limite 50 téléchargements atteinte</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis gratuit et j'ai atteint 50 téléchargements</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de télécharger un 51ème contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois une popup:</p>
<hr />
<h2 id="33-cta-premium-quand-contenu-exclusif-bloque">33. CTA Premium quand contenu exclusif bloqué</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis gratuit et je clique sur un contenu Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'overlay bloquant apparaît</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<hr />
<h2 id="34-statistiques-conversion-quel-avantage-convertit-le-mieux">34. Statistiques conversion - Quel avantage convertit le mieux ?</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un admin consulte les statistiques de conversion</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il analyse les sources de conversion</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit:</p>
<pre><code>| 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% |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> cela aide à optimiser le placement des CTA</p>
<hr />
<h2 id="35-ab-test-message-cta">35. A/B test message CTA</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave veut optimiser les conversions</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un A/B test est lancé sur le CTA après pub</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> groupe A voit "Marre des pubs ?" (focus négatif)
<span style="color: #9E9E9E"><strong>Et</strong></span> groupe B voit "Profitez de 0 publicité" (focus positif)
<span style="color: #9E9E9E"><strong>Et</strong></span> le taux de conversion est mesuré
<span style="color: #9E9E9E"><strong>Et</strong></span> le message le plus performant est déployé</p>
<hr />
<h2 id="36-notification-premium-apres-30-jours-dutilisation-gratuite">36. Notification Premium après 30 jours d'utilisation gratuite</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis utilisateur gratuit depuis 30 jours
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'écoute régulièrement (15h cumulées)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le 30ème jour arrive</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification:</p>
<hr />
<h2 id="37-trial-gratuit-refuse-mais-onboarding-ameliore">37. Trial gratuit refusé mais onboarding amélioré</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'il n'y a pas de trial gratuit</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un nouvel utilisateur s'inscrit</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un onboarding explique clairement les avantages Premium
<span style="color: #9E9E9E"><strong>Et</strong></span> il peut comparer gratuit vs Premium dès le premier lancement
<span style="color: #9E9E9E"><strong>Et</strong></span> cela l'aide à décider rapidement s'il veut payer</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="gestion-abonnement-premium">Gestion abonnement Premium</h1>
<blockquote>
<p><em>En tant qu'utilisateur</em>
<em>Je veux gérer facilement mon abonnement Premium</em>
<em>Afin de souscrire, renouveler ou annuler en toute transparence</em></p>
</blockquote>
<p><strong>41 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis connecté à l'application RoadWave</p>
</blockquote>
<h2 id="1-souscription-via-web-desktopmobile-avec-mangopay">1. Souscription via Web (desktop/mobile) avec Mangopay</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte la page Premium sur le site web</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "S'abonner - Mensuel 4.99€"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je suis redirigé vers le formulaire de paiement Mangopay
<span style="color: #9E9E9E"><strong>Et</strong></span> je saisis mes informations de carte bancaire
<span style="color: #9E9E9E"><strong>Et</strong></span> le paiement de 4.99€ est prélevé immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> la commission Mangopay est de 1.8% + 0.18€ = 0.27€
<span style="color: #9E9E9E"><strong>Et</strong></span> RoadWave reçoit 4.72€ net</p>
<hr />
<h2 id="2-calcul-commission-mangopay">2. Calcul commission Mangopay</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur paie 4.99€ via Mangopay</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la commission est calculée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la commission est : 4.99€ × 1.8% + 0.18€ = 0.09€ + 0.18€ = 0.27€
<span style="color: #9E9E9E"><strong>Et</strong></span> RoadWave reçoit : 4.99€ - 0.27€ = 4.72€
<span style="color: #9E9E9E"><strong>Et</strong></span> la commission représente 5.4% du prix</p>
<hr />
<h2 id="3-souscription-via-ios-app-avec-apple-iap">3. Souscription via iOS App avec Apple IAP</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'utilise l'app iOS</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "S'abonner - Mensuel 5.99€"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je suis redirigé vers l'interface Apple In-App Purchase
<span style="color: #9E9E9E"><strong>Et</strong></span> le prix affiché est 5.99€ (majoré de 20%)
<span style="color: #9E9E9E"><strong>Et</strong></span> le paiement est effectué via mon compte Apple
<span style="color: #9E9E9E"><strong>Et</strong></span> Apple prend 30% de commission = 1.80€
<span style="color: #9E9E9E"><strong>Et</strong></span> RoadWave reçoit 4.19€ net</p>
<hr />
<h2 id="4-souscription-via-android-app-avec-google-play-billing">4. Souscription via Android App avec Google Play Billing</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'utilise l'app Android</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "S'abonner - Mensuel 5.99€"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je suis redirigé vers l'interface Google Play Billing
<span style="color: #9E9E9E"><strong>Et</strong></span> le prix affiché est 5.99€ (majoré de 20%)
<span style="color: #9E9E9E"><strong>Et</strong></span> le paiement est effectué via mon compte Google
<span style="color: #9E9E9E"><strong>Et</strong></span> Google prend 30% de commission = 1.80€
<span style="color: #9E9E9E"><strong>Et</strong></span> RoadWave reçoit 4.19€ net</p>
<hr />
<h2 id="5-majoration-20-sur-mobile-pour-compenser-commission-30">5. Majoration 20% sur mobile pour compenser commission 30%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que Apple/Google prennent 30% de commission</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> RoadWave fixe le prix mobile</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le prix web est 4.99€ (commission Mangopay 5.4%)
<span style="color: #9E9E9E"><strong>Et</strong></span> le prix mobile est 5.99€ (commission Apple/Google 30%)
<span style="color: #9E9E9E"><strong>Et</strong></span> la majoration est de 1€ (+20%)
<span style="color: #9E9E9E"><strong>Et</strong></span> cela compense partiellement la commission excessive</p>
<hr />
<h2 id="6-email-incitation-souscription-web-moins-chere">6. Email incitation souscription web moins chère</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte Premium depuis l'app mobile</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je vois le prix 5.99€</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois aussi un message:
<span style="color: #9E9E9E"><strong>Et</strong></span> un lien vers le site web est fourni</p>
<hr />
<h2 id="7-calcul-economie-souscription-web-vs-mobile">7. Calcul économie souscription web vs mobile</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le prix web est 4.99€/mois
<span style="color: #9E9E9E"><strong>Et</strong></span> que le prix mobile est 5.99€/mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je calcule l'économie annuelle</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> web : 4.99€ × 12 = 59.88€/an
<span style="color: #9E9E9E"><strong>Et</strong></span> mobile : 5.99€ × 12 = 71.88€/an
<span style="color: #9E9E9E"><strong>Et</strong></span> économie : 12€/an (soit 20% d'économie)</p>
<hr />
<h2 id="8-activation-immediate-apres-paiement-reussi">8. Activation immédiate après paiement réussi</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je viens de payer mon abonnement Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le paiement est confirmé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon statut passe immédiatement à "Premium"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux accéder aux avantages Premium dès maintenant
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un email de confirmation</p>
<hr />
<h2 id="9-email-confirmation-souscription">9. Email confirmation souscription</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai souscrit à Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la souscription est confirmée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email:</p>
<hr />
<h2 id="10-email-rappel-7-jours-avant-renouvellement">10. Email rappel 7 jours avant renouvellement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon abonnement mensuel se renouvelle le 15 juillet</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le 8 juillet arrive (7 jours avant)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email de rappel:</p>
<hr />
<h2 id="11-renouvellement-automatique-reussi">11. Renouvellement automatique réussi</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon abonnement mensuel arrive à échéance le 15 juillet</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le 15 juillet arrive</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Mangopay/Apple/Google prélève automatiquement 4.99€ (ou 5.99€)
<span style="color: #9E9E9E"><strong>Et</strong></span> mon abonnement est renouvelé pour 1 mois supplémentaire
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un email de confirmation</p>
<hr />
<h2 id="12-email-confirmation-renouvellement">12. Email confirmation renouvellement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon abonnement vient d'être renouvelé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le paiement est confirmé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email:</p>
<hr />
<h2 id="13-echec-paiement-renouvellement-tentative-1">13. Échec paiement renouvellement - Tentative 1</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon abonnement doit se renouveler le 15 juillet
<span style="color: #F44336"><strong>Mais</strong></span> que ma carte bancaire est expirée ou sans fonds</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le prélèvement échoue</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email:</p>
<hr />
<h2 id="14-retry-automatique-paiement-apres-3-jours">14. Retry automatique paiement après 3 jours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le paiement a échoué le 15 juillet</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le 18 juillet arrive (J+3)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Mangopay/Apple/Google tente automatiquement un nouveau prélèvement
<span style="color: #9E9E9E"><strong>Et</strong></span> si le paiement réussit, l'abonnement est renouvelé normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> si le paiement échoue encore, un 2ème retry est programmé</p>
<hr />
<h2 id="15-retry-automatique-paiement-apres-7-jours">15. Retry automatique paiement après 7 jours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 2 tentatives ont échoué (15 juillet et 18 juillet)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le 22 juillet arrive (J+7)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une 3ème et dernière tentative est effectuée
<span style="color: #9E9E9E"><strong>Et</strong></span> si le paiement réussit, l'abonnement est sauvé
<span style="color: #9E9E9E"><strong>Et</strong></span> si le paiement échoue, l'abonnement est annulé automatiquement</p>
<hr />
<h2 id="16-annulation-automatique-apres-3-echecs-paiement">16. Annulation automatique après 3 échecs paiement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que les 3 tentatives de renouvellement ont échoué (J+0, J+3, J+7)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la 3ème tentative échoue</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon abonnement Premium est annulé automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> mon statut repasse à "Gratuit"
<span style="color: #9E9E9E"><strong>Et</strong></span> je perds accès aux avantages Premium
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un email d'annulation</p>
<hr />
<h2 id="17-email-annulation-automatique-pour-impaye">17. Email annulation automatique pour impayé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon abonnement a été annulé pour échec paiement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'annulation devient effective</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email:</p>
<hr />
<h2 id="18-annulation-self-service-dans-settings">18. Annulation self-service dans Settings</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je veux annuler mon abonnement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à "Paramètres &gt; Abonnement"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois un bouton "Annuler l'abonnement"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux annuler en 2 clics sans contacter le support</p>
<hr />
<h2 id="19-confirmation-avant-annulation">19. Confirmation avant annulation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je clique sur "Annuler l'abonnement"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> une popup de confirmation apparaît</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<hr />
<h2 id="20-acces-premium-maintenu-jusqua-fin-periode-payee">20. Accès Premium maintenu jusqu'à fin période payée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai annulé mon abonnement le 1er juillet
<span style="color: #9E9E9E"><strong>Et</strong></span> que mon abonnement mensuel était valable jusqu'au 15 juillet</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'annulation est confirmée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je garde l'accès Premium jusqu'au 15 juillet
<span style="color: #9E9E9E"><strong>Et</strong></span> à partir du 16 juillet, je repasse en gratuit
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne suis pas remboursé pour les 14 jours restants</p>
<hr />
<h2 id="21-justification-pas-de-remboursement-prorata">21. Justification pas de remboursement prorata</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'industrie (Spotify, Netflix, YouTube) ne rembourse pas prorata</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> RoadWave applique la même règle</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> c'est le standard accepté par les utilisateurs
<span style="color: #9E9E9E"><strong>Et</strong></span> cela simplifie la gestion comptable
<span style="color: #9E9E9E"><strong>Et</strong></span> évite les abus (souscription puis annulation immédiate pour remboursement)</p>
<hr />
<h2 id="22-email-confirmation-annulation">22. Email confirmation annulation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai annulé mon abonnement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'annulation est enregistrée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email:</p>
<hr />
<h2 id="23-pas-de-renouvellement-apres-annulation">23. Pas de renouvellement après annulation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai annulé mon abonnement le 1er juillet</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le 15 juillet arrive (date de renouvellement prévue)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucun prélèvement n'est effectué
<span style="color: #9E9E9E"><strong>Et</strong></span> mon statut passe automatiquement à "Gratuit"
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne reçois pas d'email de renouvellement</p>
<hr />
<h2 id="24-reabonnement-possible-immediatement">24. Réabonnement possible immédiatement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai annulé mon abonnement il y a 5 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à la page Premium</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux me réabonner immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> le processus de paiement est le même que la première fois</p>
<hr />
<h2 id="25-pas-de-nouvelle-periode-dessai-au-reabonnement">25. Pas de nouvelle période d'essai au réabonnement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai annulé mon abonnement il y a 3 mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me réabonne</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je paie immédiatement 4.99€ (pas d'essai gratuit)</p>
<hr />
<h2 id="26-offre-win-back-pour-utilisateurs-ayant-annule">26. Offre win-back pour utilisateurs ayant annulé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai annulé mon abonnement il y a 1 mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je reçois un email de win-back</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois une offre spéciale:</p>
<hr />
<h2 id="27-table-subscriptions-en-base-postgresql">27. Table subscriptions en base PostgreSQL</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur souscrit à Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les données sont enregistrées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la table subscriptions contient:</p>
<hr />
<h2 id="28-statuts-possibles-dans-subscriptionstatus">28. Statuts possibles dans subscription.status</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un abonnement peut avoir différents statuts</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le statut est stocké en base</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les valeurs possibles sont:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="29-cache-redis-pour-verification-premium-temps-reel">29. Cache Redis pour vérification Premium temps réel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur lance un contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'app vérifie s'il est Premium</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une clé Redis est consultée:
<span style="color: #9E9E9E"><strong>Et</strong></span> si la clé n'existe pas, elle est recalculée depuis PostgreSQL
<span style="color: #9E9E9E"><strong>Et</strong></span> cela garantit des performances &lt;10ms</p>
<hr />
<h2 id="30-refresh-cache-redis-via-webhooks">30. Refresh cache Redis via webhooks</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un paiement est confirmé par Mangopay/Apple/Google</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un webhook est reçu par RoadWave</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le cache Redis premium:{user_id} est mis à jour immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur voit son statut Premium activé sans délai</p>
<hr />
<h2 id="31-webhooks-mangopay-payin_normal_succeeded">31. Webhooks Mangopay - PAYIN_NORMAL_SUCCEEDED</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un paiement Mangopay réussit</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Mangopay envoie le webhook PAYIN_NORMAL_SUCCEEDED</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> RoadWave met à jour subscriptions.status = 'active'
<span style="color: #9E9E9E"><strong>Et</strong></span> met à jour current_period_end = NOW() + 1 mois
<span style="color: #9E9E9E"><strong>Et</strong></span> refresh le cache Redis premium:{user_id} = true</p>
<hr />
<h2 id="32-webhooks-mangopay-payin_normal_failed">32. Webhooks Mangopay - PAYIN_NORMAL_FAILED</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un paiement Mangopay échoue</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Mangopay envoie le webhook PAYIN_NORMAL_FAILED</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> RoadWave met à jour subscriptions.status = 'past_due'
<span style="color: #9E9E9E"><strong>Et</strong></span> programme un retry automatique dans 3 jours
<span style="color: #9E9E9E"><strong>Et</strong></span> envoie un email à l'utilisateur</p>
<hr />
<h2 id="33-webhooks-apple-app-store-server-notifications">33. Webhooks Apple - App Store Server Notifications</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un paiement Apple IAP change de statut</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Apple envoie une notification serveur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> RoadWave parse la notification (JSON)
<span style="color: #9E9E9E"><strong>Et</strong></span> met à jour la subscription en conséquence
<span style="color: #9E9E9E"><strong>Et</strong></span> refresh le cache Redis</p>
<hr />
<h2 id="34-webhooks-google-real-time-developer-notifications">34. Webhooks Google - Real-time Developer Notifications</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un paiement Google Play change de statut</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Google envoie une notification temps réel</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> RoadWave parse la notification (JSON)
<span style="color: #9E9E9E"><strong>Et</strong></span> met à jour la subscription en conséquence
<span style="color: #9E9E9E"><strong>Et</strong></span> refresh le cache Redis</p>
<hr />
<h2 id="35-dashboard-admin-metriques-abonnements">35. Dashboard admin - Métriques abonnements</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un admin consulte les métriques Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il accède au dashboard</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit:</p>
<pre><code>| 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% |
</code></pre>
<hr />
<h2 id="36-calcul-churn-rate-mensuel">36. Calcul churn rate mensuel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 287 utilisateurs ont annulé ce mois
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il y avait 12,547 abonnés au début du mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le churn rate est calculé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> churn = 287 / 12,547 = 2.3%
<span style="color: #9E9E9E"><strong>Et</strong></span> un churn &lt;5% est considéré comme excellent
<span style="color: #9E9E9E"><strong>Et</strong></span> RoadWave surveille cette métrique de près</p>
<hr />
<h2 id="37-alerte-si-churn-rate-5">37. Alerte si churn rate &gt;5%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le churn rate mensuel dépasse 5%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte cette anomalie</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une alerte est envoyée à l'équipe:</p>
<hr />
<h2 id="38-enquete-satisfaction-a-lannulation">38. Enquête satisfaction à l'annulation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je viens d'annuler mon abonnement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'annulation est confirmée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois un questionnaire rapide:
<span style="color: #9E9E9E"><strong>Et</strong></span> les réponses aident à améliorer l'offre Premium</p>
<hr />
<h2 id="39-repartition-canaux-souscription">39. Répartition canaux souscription</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un admin analyse les canaux de souscription</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il consulte les statistiques</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit:</p>
<pre><code>| 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€ |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> cela aide à orienter les efforts marketing (inciter web = moins de commission)</p>
<hr />
<h2 id="40-performance-verification-premium-10ms">40. Performance vérification Premium &lt;10ms</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 100 000 utilisateurs consultent des contenus simultanément</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> chaque requête vérifie le statut Premium via Redis</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le temps de réponse moyen est &lt;10ms
<span style="color: #9E9E9E"><strong>Et</strong></span> Redis gère facilement 100 000 requêtes/seconde
<span style="color: #9E9E9E"><strong>Et</strong></span> l'expérience utilisateur est fluide</p>
<hr />
<h2 id="41-backup-donnees-abonnements">41. Backup données abonnements</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que les données d'abonnements sont critiques</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un backup est effectué</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> PostgreSQL est répliqué en temps réel sur un replica
<span style="color: #9E9E9E"><strong>Et</strong></span> un snapshot quotidien est stocké sur S3
<span style="color: #9E9E9E"><strong>Et</strong></span> en cas de crash, les données peuvent être restaurées &lt;5 minutes</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="multi-devices-et-detection-simultanee">Multi-devices et détection simultanée</h1>
<blockquote>
<p><em>En tant qu'abonné Premium</em>
<em>Je veux utiliser mon compte sur plusieurs appareils</em>
<em>Mais limité à 1 seul stream actif à la fois pour éviter le partage abusif</em></p>
</blockquote>
<p><strong>30 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur Premium actif
<span style="color: #9E9E9E"><strong>Et</strong></span> que mon compte est valide</p>
</blockquote>
<h2 id="1-1-seul-stream-actif-autorise-par-compte">1. 1 seul stream actif autorisé par compte</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je n'écoute rien actuellement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je lance un contenu sur mon iPhone</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le stream démarre normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> Redis enregistre: active_streams:{user_id} = {device_id: "iPhone", started_at: timestamp}</p>
<hr />
<h2 id="2-detection-connexion-simultanee-arret-premier-device">2. Détection connexion simultanée - Arrêt premier device</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu sur mon iPhone</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je lance un contenu sur mon iPad</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système détecte une session active sur iPhone
<span style="color: #9E9E9E"><strong>Et</strong></span> la lecture sur iPhone est arrêtée immédiatement (WebSocket close)
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois sur iPhone: "Lecture interrompue : votre compte est utilisé sur un autre appareil"
<span style="color: #9E9E9E"><strong>Et</strong></span> la lecture démarre sur iPad normalement</p>
<hr />
<h2 id="3-message-explicite-sur-device-interrompu">3. Message explicite sur device interrompu</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma lecture sur iPhone vient d'être interrompue</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je regarde l'écran de mon iPhone</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois une overlay avec le message:
<span style="color: #9E9E9E"><strong>Et</strong></span> un bouton "Reprendre ici" est disponible</p>
<hr />
<h2 id="4-reprendre-lecture-sur-device-interrompu">4. Reprendre lecture sur device interrompu</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma lecture sur iPhone a été interrompue
<span style="color: #9E9E9E"><strong>Et</strong></span> que je veux reprendre sur iPhone</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Reprendre ici"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la lecture démarre sur iPhone
<span style="color: #9E9E9E"><strong>Et</strong></span> l'iPad est à son tour interrompu avec le même message
<span style="color: #9E9E9E"><strong>Et</strong></span> le "ping-pong" entre devices est possible (mais pénible)</p>
<hr />
<h2 id="5-enregistrement-session-active-dans-redis">5. Enregistrement session active dans Redis</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je lance un contenu sur mon iPhone</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la lecture démarre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une entrée Redis est créée:</p>
<hr />
<h2 id="6-heartbeat-toutes-les-30-secondes-pour-maintenir-session">6. Heartbeat toutes les 30 secondes pour maintenir session</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu sur mon iPhone</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> 30 secondes s'écoulent</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'app envoie un heartbeat au serveur
<span style="color: #9E9E9E"><strong>Et</strong></span> le serveur refresh le TTL Redis à 300 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> la session reste active</p>
<hr />
<h2 id="7-session-consideree-morte-apres-5-minutes-sans-heartbeat">7. Session considérée morte après 5 minutes sans heartbeat</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu sur mon iPhone
<span style="color: #F44336"><strong>Mais</strong></span> que l'app crash ou que le réseau coupe</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> 5 minutes s'écoulent sans heartbeat</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'entrée Redis expire automatiquement (TTL atteint)
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux relancer sur n'importe quel device sans conflit</p>
<hr />
<h2 id="8-verification-session-avant-demarrage-lecture">8. Vérification session avant démarrage lecture</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je veux lancer un contenu sur mon iPad</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur Play</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le serveur vérifie Redis: active_streams:{user_id}
<span style="color: #9E9E9E"><strong>Et</strong></span> si une session existe sur un autre device, elle est tuée
<span style="color: #9E9E9E"><strong>Et</strong></span> la nouvelle session iPad est enregistrée dans Redis</p>
<hr />
<h2 id="9-gestion-multi-utilisateurs-simultanes">9. Gestion multi-utilisateurs simultanés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 100 000 utilisateurs Premium écoutent simultanément</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Redis stocke 100 000 entrées active_streams</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> chaque entrée a un TTL de 5 minutes
<span style="color: #9E9E9E"><strong>Et</strong></span> Redis gère facilement cette charge (~10 MB de RAM)
<span style="color: #9E9E9E"><strong>Et</strong></span> les vérifications sont quasi-instantanées (O(1))</p>
<hr />
<h2 id="10-contenus-telecharges-offline-ne-comptent-pas-comme-stream">10. Contenus téléchargés (offline) ne comptent pas comme stream</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai téléchargé 20 contenus en mode offline</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute un contenu téléchargé sur mon iPhone sans réseau</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune session active n'est enregistrée dans Redis
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux écouter offline pendant qu'un autre device stream online</p>
<hr />
<h2 id="11-transition-rapide-device-10s-toleree">11. Transition rapide device &lt;10s tolérée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute dans ma voiture sur mon iPhone
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'arrive chez moi</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je lance la lecture sur mon iPad dans les 10 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la transition est considérée comme un changement de device légitime
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun message d'erreur n'est affiché sur iPhone
<span style="color: #9E9E9E"><strong>Et</strong></span> la lecture reprend exactement où j'étais sur iPad</p>
<hr />
<h2 id="12-detection-transition-rapide-via-timestamps">12. Détection transition rapide via timestamps</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la session iPhone a started_at = 14:30:00</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je lance sur iPad à 14:30:05 (5 secondes après)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le serveur détecte: diff = 5s &lt; 10s
<span style="color: #9E9E9E"><strong>Et</strong></span> applique une "graceful transition" (pas de message d'erreur iPhone)
<span style="color: #9E9E9E"><strong>Et</strong></span> Redis met à jour: active_streams:{user_id} = {device_id: "iPad", ...}</p>
<hr />
<h2 id="13-plusieurs-devices-disponibles-mais-1-seul-actif">13. Plusieurs devices disponibles mais 1 seul actif</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je possède:</p>
<pre><code>| device | status |
|---|---|
| iPhone | Installé |
| iPad | Installé |
| MacBook (web) | Connecté |
| Android (conjoint) | Installé |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je lance un stream sur n'importe quel device</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seulement 1 peut être actif à la fois
<span style="color: #9E9E9E"><strong>Et</strong></span> les autres devices sont en "standby"</p>
<hr />
<h2 id="14-justification-anti-partage-compte">14. Justification anti-partage compte</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur Premium partage son compte avec un ami</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les 2 personnes essaient d'écouter simultanément</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la lecture est constamment interrompue sur l'un ou l'autre
<span style="color: #9E9E9E"><strong>Et</strong></span> l'expérience devient inutilisable
<span style="color: #9E9E9E"><strong>Et</strong></span> cela décourage fortement le partage de compte</p>
<hr />
<h2 id="15-justification-protection-revenus-createurs">15. Justification protection revenus créateurs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 1 abonnement Premium = 4.99€/mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> 70% sont reversés aux créateurs (3.49€)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les créateurs sont rémunérés pour 1 personne
<span style="color: #9E9E9E"><strong>Et</strong></span> si 2 personnes utilisent le même compte simultanément, c'est injuste
<span style="color: #9E9E9E"><strong>Et</strong></span> la limite 1 stream protège l'équité du système</p>
<hr />
<h2 id="16-justification-ux-claire">16. Justification UX claire</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un stream est interrompu sur un device</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur voit le message explicite</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il comprend immédiatement pourquoi (autre device actif)
<span style="color: #9E9E9E"><strong>Et</strong></span> il peut choisir de reprendre sur le device actuel ou l'autre
<span style="color: #9E9E9E"><strong>Et</strong></span> il n'y a pas de confusion ou frustration</p>
<hr />
<h2 id="17-comparaison-avec-spotify-limite-1-stream">17. Comparaison avec Spotify (limite 1 stream)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que Spotify Premium limite aussi à 1 stream actif</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> RoadWave applique la même règle</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les utilisateurs connaissent déjà ce comportement
<span style="color: #9E9E9E"><strong>Et</strong></span> cela paraît normal et accepté par l'industrie</p>
<hr />
<h2 id="18-comparaison-avec-netflix-plusieurs-streams-selon-formule">18. Comparaison avec Netflix (plusieurs streams selon formule)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que Netflix permet 1-4 streams selon la formule</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> RoadWave limite à 1 stream pour tous</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> c'est plus strict que Netflix
<span style="color: #F44336"><strong>Mais</strong></span> Netflix cible le foyer familial (TV partagée)
<span style="color: #4CAF50"><strong>Alors</strong></span> que RoadWave cible l'individu conducteur (usage personnel)</p>
<hr />
<h2 id="19-detection-pattern-suspect-changements-devices-frequents">19. Détection pattern suspect - Changements devices fréquents</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur change de device 50 fois en 1 heure</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte ce pattern anormal</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une alerte est générée pour l'équipe modération
<span style="color: #9E9E9E"><strong>Et</strong></span> le compte peut être marqué pour surveillance
<span style="color: #9E9E9E"><strong>Et</strong></span> si abus confirmé, suspension possible</p>
<hr />
<h2 id="20-logs-des-changements-de-device">20. Logs des changements de device</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je change de device plusieurs fois par jour</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les changements sont loggés</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> chaque événement est enregistré:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> ces logs aident à détecter les partages de compte</p>
<hr />
<h2 id="21-metriques-admin-changements-devices-par-utilisateur">21. Métriques admin - Changements devices par utilisateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un admin consulte les métriques de streaming</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il accède au dashboard</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit:</p>
<pre><code>| métrique | valeur |
|---|---|
| Utilisateurs Premium actifs | 12,547 |
| Changements de device/jour (médiane) | 2 |
| Utilisateurs &gt;10 changements/jour | 47 (0.4%) |
| Comptes suspects (&gt;20 changements/j) | 3 |
</code></pre>
<hr />
<h2 id="22-email-davertissement-si-changements-excessifs">22. Email d'avertissement si changements excessifs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je change de device 30 fois par jour pendant 3 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte ce pattern</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email d'avertissement:</p>
<hr />
<h2 id="23-suspension-compte-apres-avertissement-ignore">23. Suspension compte après avertissement ignoré</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai reçu un email d'avertissement il y a 7 jours
<span style="color: #F44336"><strong>Mais</strong></span> que je continue à changer de device 30 fois par jour</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'équipe modération examine le compte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon compte Premium peut être suspendu pour partage abusif
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un email de suspension avec justification</p>
<hr />
<h2 id="24-faq-pourquoi-ma-lecture-sarrete-quand-jutilise-un-autre-device">24. FAQ - Pourquoi ma lecture s'arrête quand j'utilise un autre device ?</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte la FAQ Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je cherche "lecture interrompue"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je trouve la réponse:</p>
<hr />
<h2 id="25-support-utilisateur-pense-etre-pirate">25. Support - Utilisateur pense être piraté</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur voit constamment "Lecture interrompue"
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il pense que son compte est piraté</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il contacte le support</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le support vérifie les logs de changements de device
<span style="color: #9E9E9E"><strong>Et</strong></span> peut identifier les devices (iPhone, iPad perso vs iPhone inconnu)
<span style="color: #9E9E9E"><strong>Et</strong></span> conseille de changer le mot de passe si device inconnu détecté</p>
<hr />
<h2 id="26-changement-mot-de-passe-deconnecte-tous-les-devices">26. Changement mot de passe déconnecte tous les devices</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je pense que mon compte est compromis</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je change mon mot de passe</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous mes devices sont déconnectés immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> les sessions actives dans Redis sont supprimées
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois me reconnecter sur chaque device
<span style="color: #9E9E9E"><strong>Et</strong></span> cela sécurise mon compte</p>
<hr />
<h2 id="27-test-charge-100-000-verificationsseconde">27. Test charge - 100 000 vérifications/seconde</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 100 000 utilisateurs Premium lancent des contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> chaque lancement vérifie Redis (GET active_streams:{user_id})</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Redis peut gérer facilement 100 000 requêtes/seconde
<span style="color: #9E9E9E"><strong>Et</strong></span> le temps de réponse moyen est &lt;1ms
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun ralentissement n'est constaté</p>
<hr />
<h2 id="28-test-failover-redis">28. Test failover Redis</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le serveur Redis principal tombe en panne</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le failover automatique vers le replica Redis s'active</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les sessions actives peuvent être perdues temporairement (max 5 min)
<span style="color: #F44336"><strong>Mais</strong></span> les utilisateurs peuvent relancer immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> l'impact est minimal (pas de perte de données critiques)</p>
<hr />
<h2 id="29-test-concurrence-lancement-simultane-2-devices">29. Test concurrence - Lancement simultané 2 devices</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je lance exactement au même instant sur iPhone et iPad</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les 2 requêtes arrivent en parallèle au serveur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Redis utilise un lock (SETNX) pour atomicité
<span style="color: #9E9E9E"><strong>Et</strong></span> 1 seul device gagne (par exemple iPhone)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'autre device (iPad) reçoit immédiatement une erreur
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur peut retry sur iPad si souhaité</p>
<hr />
<h2 id="30-nettoyage-automatique-sessions-expirees">30. Nettoyage automatique sessions expirées</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 1000 sessions Redis ont expiré (TTL atteint)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Redis supprime automatiquement ces entrées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la mémoire est libérée
<span style="color: #9E9E9E"><strong>Et</strong></span> les nouveaux streams peuvent démarrer sans conflit
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune intervention manuelle n'est nécessaire</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="offre-et-tarification-premium">Offre et tarification Premium</h1>
<blockquote>
<p><em>En tant qu'utilisateur</em>
<em>Je veux pouvoir souscrire à un abonnement Premium</em>
<em>Afin de profiter d'une expérience sans publicité avec des avantages exclusifs</em></p>
</blockquote>
<p><strong>31 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis connecté en tant qu'utilisateur</p>
</blockquote>
<h2 id="1-formule-mensuelle-a-499mois">1. Formule mensuelle à 4.99€/mois</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte les offres Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je vois la formule mensuelle</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le prix affiché est 4.99€/mois
<span style="color: #9E9E9E"><strong>Et</strong></span> il n'y a aucune réduction
<span style="color: #9E9E9E"><strong>Et</strong></span> le prix effectif par mois est 4.99€</p>
<hr />
<h2 id="2-formule-annuelle-a-4999an-2-mois-offerts">2. Formule annuelle à 49.99€/an (2 mois offerts)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte les offres Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je vois la formule annuelle</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le prix affiché est 49.99€/an
<span style="color: #9E9E9E"><strong>Et</strong></span> l'économie affichée est "2 mois offerts"
<span style="color: #9E9E9E"><strong>Et</strong></span> le prix effectif par mois est 4.16€
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le badge "Meilleure offre"</p>
<hr />
<h2 id="3-calcul-economie-formule-annuelle">3. Calcul économie formule annuelle</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la formule mensuelle coûte 4.99€/mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je calcule le coût annuel en mensuel</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 12 mois × 4.99€ = 59.88€/an
<span style="color: #9E9E9E"><strong>Et</strong></span> la formule annuelle coûte 49.99€
<span style="color: #9E9E9E"><strong>Et</strong></span> l'économie est de 9.89€ (≈ 2 mois gratuits)
<span style="color: #9E9E9E"><strong>Et</strong></span> la réduction est de 16.5%</p>
<hr />
<h2 id="4-pas-dessai-gratuit-disponible">4. Pas d'essai gratuit disponible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte les offres Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je recherche une option "Essai gratuit"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune option d'essai gratuit n'est proposée
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois payer dès le premier jour pour accéder au Premium</p>
<hr />
<h2 id="5-justification-absence-essai-gratuit-anti-abus-vacances">5. Justification absence essai gratuit - Anti-abus vacances</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave ne propose pas d'essai gratuit</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un utilisateur envisage un road trip de 14 jours</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il ne peut pas s'abonner pour l'essai gratuit puis annuler
<span style="color: #9E9E9E"><strong>Et</strong></span> cela évite les inscriptions opportunistes
<span style="color: #9E9E9E"><strong>Et</strong></span> protège les revenus des créateurs</p>
<hr />
<h2 id="6-justification-absence-essai-gratuit-protection-revenus-createurs">6. Justification absence essai gratuit - Protection revenus créateurs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur Premium écoute des contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il génère des écoutes dès le jour 1</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les créateurs sont rémunérés immédiatement (70% de 4.99€)
<span style="color: #9E9E9E"><strong>Et</strong></span> il n'y a pas de "période gratuite" sans rémunération créateurs</p>
<hr />
<h2 id="7-justification-absence-essai-gratuit-simplicite">7. Justification absence essai gratuit - Simplicité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave gère les abonnements</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il n'y a pas d'essai gratuit</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> pas de gestion complexe de période trial
<span style="color: #9E9E9E"><strong>Et</strong></span> pas de workflow de conversion trial → payant
<span style="color: #9E9E9E"><strong>Et</strong></span> cela réduit la complexité technique</p>
<hr />
<h2 id="8-justification-absence-essai-gratuit-engagement">8. Justification absence essai gratuit - Engagement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur paie dès le début</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il souscrit à Premium</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il est plus engagé qu'un utilisateur en essai gratuit
<span style="color: #9E9E9E"><strong>Et</strong></span> le taux de churn est généralement plus faible
<span style="color: #9E9E9E"><strong>Et</strong></span> la lifetime value (LTV) est plus élevée</p>
<hr />
<h2 id="9-pas-de-partage-familial-au-mvp">9. Pas de partage familial au MVP</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte les offres Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je recherche une option "Famille" ou "Partage"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune option de partage familial n'est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> seuls les abonnements individuels sont proposés</p>
<hr />
<h2 id="10-justification-absence-partage-familial-complexite-technique">10. Justification absence partage familial - Complexité technique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le partage familial nécessite:</p>
<pre><code>| fonctionnalité | complexité |
|---|---|
| Gestion invitations | Moyenne |
| Validation liens famille | Moyenne |
| Limite devices par membre | Élevée |
| Dashboard admin famille | Élevée |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> RoadWave évalue le ROI</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le coût dev/support est trop élevé pour le MVP
<span style="color: #9E9E9E"><strong>Et</strong></span> la fonctionnalité est reportée post-MVP</p>
<hr />
<h2 id="11-justification-absence-partage-familial-risque-abus">11. Justification absence partage familial - Risque abus</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une offre famille permet 5-6 membres</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il n'y a pas de vérification stricte de lien familial</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> des "familles" de 6 inconnus pourraient se former
<span style="color: #9E9E9E"><strong>Et</strong></span> cela réduirait fortement les revenus (6 personnes pour 1 abonnement)</p>
<hr />
<h2 id="12-justification-absence-partage-familial-cible-individuelle">12. Justification absence partage familial - Cible individuelle</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave cible principalement les conducteurs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> chaque conducteur utilise l'app individuellement en voiture</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le besoin de partage familial est limité
<span style="color: #9E9E9E"><strong>Et</strong></span> la plupart des utilisateurs sont des individus (pas des familles)</p>
<hr />
<h2 id="13-post-mvp-offre-famille-a-999mois-pour-5-comptes">13. Post-MVP - Offre Famille à 9.99€/mois pour 5 comptes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave envisage une offre Famille post-MVP</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la fonctionnalité est spécifiée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le prix serait 9.99€/mois pour 5 comptes
<span style="color: #9E9E9E"><strong>Et</strong></span> cela représente 2€/mois/personne
<span style="color: #F44336"><strong>Mais</strong></span> cette offre n'est pas disponible au MVP</p>
<hr />
<h2 id="14-comparaison-tarif-spotify-a-1099mois">14. Comparaison tarif - Spotify à 10.99€/mois</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que Spotify Premium coûte 10.99€/mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> RoadWave fixe son prix à 4.99€/mois</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> RoadWave est 54.5% moins cher que Spotify
<span style="color: #9E9E9E"><strong>Et</strong></span> cela positionne RoadWave comme très accessible</p>
<hr />
<h2 id="15-comparaison-tarif-youtube-premium-a-1199mois">15. Comparaison tarif - YouTube Premium à 11.99€/mois</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que YouTube Premium coûte 11.99€/mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> RoadWave fixe son prix à 4.99€/mois</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> RoadWave est 58.4% moins cher que YouTube Premium
<span style="color: #9E9E9E"><strong>Et</strong></span> cela est un argument commercial fort</p>
<hr />
<h2 id="16-comparaison-tarif-apple-music-a-1099mois">16. Comparaison tarif - Apple Music à 10.99€/mois</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'Apple Music coûte 10.99€/mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> RoadWave fixe son prix à 4.99€/mois</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> RoadWave est 54.5% moins cher qu'Apple Music
<span style="color: #9E9E9E"><strong>Et</strong></span> cela attire les utilisateurs sensibles au prix</p>
<hr />
<h2 id="17-justification-tarif-bas-cible-conducteurs-quotidiens">17. Justification tarif bas - Cible conducteurs quotidiens</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave cible les trajets quotidiens domicile-travail</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le prix est fixé à 4.99€/mois</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> c'est un budget raisonnable pour un conducteur
<span style="color: #9E9E9E"><strong>Et</strong></span> équivalent à ~1-2 cafés/mois
<span style="color: #9E9E9E"><strong>Et</strong></span> psychologiquement acceptable pour un usage quotidien</p>
<hr />
<h2 id="18-justification-formule-annuelle-engagement-long-terme">18. Justification formule annuelle - Engagement long terme</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la formule annuelle offre 2 mois gratuits</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un utilisateur souscrit pour 1 an</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il s'engage sur le long terme
<span style="color: #9E9E9E"><strong>Et</strong></span> RoadWave sécurise 49.99€ de revenus immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> le cash flow est amélioré</p>
<hr />
<h2 id="19-justification-formule-annuelle-reduction-churn">19. Justification formule annuelle - Réduction churn</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur paie 49.99€ pour l'année</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il envisage d'arrêter après 3 mois</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il a déjà payé pour 12 mois
<span style="color: #9E9E9E"><strong>Et</strong></span> il continuera probablement à utiliser l'app
<span style="color: #9E9E9E"><strong>Et</strong></span> le taux de churn est réduit significativement</p>
<hr />
<h2 id="20-affichage-comparatif-des-deux-formules">20. Affichage comparatif des deux formules</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte la page Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je vois les deux formules côte à côte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<hr />
<h2 id="21-mise-en-avant-formule-annuelle">21. Mise en avant formule annuelle</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte la page Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je vois les deux formules</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la formule annuelle a un badge "Meilleure offre" ⭐
<span style="color: #9E9E9E"><strong>Et</strong></span> elle est visuellement mise en avant (bordure colorée, taille plus grande)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'économie de 2 mois est affichée en gros
<span style="color: #9E9E9E"><strong>Et</strong></span> cela incite à choisir la formule annuelle</p>
<hr />
<h2 id="22-lien-pourquoi-pas-dessai-gratuit-en-faq">22. Lien "Pourquoi pas d'essai gratuit ?" en FAQ</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte la page Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "FAQ"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois une question "Pourquoi pas d'essai gratuit ?"
<span style="color: #9E9E9E"><strong>Et</strong></span> la réponse explique:</p>
<hr />
<h2 id="23-ab-test-formule-annuelle-post-mvp">23. A/B test formule annuelle (post-MVP)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave veut optimiser la conversion annuelle</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un A/B test est lancé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> groupe A voit "2 mois offerts" (économie en durée)
<span style="color: #9E9E9E"><strong>Et</strong></span> groupe B voit "Économisez 9.89€" (économie en argent)
<span style="color: #9E9E9E"><strong>Et</strong></span> les taux de souscription sont mesurés
<span style="color: #9E9E9E"><strong>Et</strong></span> le message le plus performant est déployé</p>
<hr />
<h2 id="24-promo-temporaire-exceptionnelle-black-friday-etc">24. Promo temporaire exceptionnelle (Black Friday, etc.)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que c'est le Black Friday</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> une promo temporaire est activée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la formule annuelle peut passer à 39.99€/an (au lieu de 49.99€)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'économie affichée est "4 mois offerts !"
<span style="color: #9E9E9E"><strong>Et</strong></span> la promo dure 3 jours uniquement
<span style="color: #9E9E9E"><strong>Et</strong></span> cela génère un pic de souscriptions</p>
<hr />
<h2 id="25-code-promo-partenariat-influenceur">25. Code promo partenariat influenceur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un influenceur promeut RoadWave</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il partage un code promo "INFLUENCEUR20"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les utilisateurs obtiennent -20% sur le premier mois (3.99€ au lieu de 4.99€)
<span style="color: #9E9E9E"><strong>Et</strong></span> le code est valable 1 mois
<span style="color: #9E9E9E"><strong>Et</strong></span> les conversions sont trackées par code promo</p>
<hr />
<h2 id="26-statistiques-admin-repartition-formules">26. Statistiques admin - Répartition formules</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un admin consulte les métriques d'abonnements</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il accède au dashboard</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit:</p>
<pre><code>| 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€ |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> ces données aident à piloter la stratégie tarifaire</p>
<hr />
<h2 id="27-calcul-revenus-mensuels-recurrents-mrr">27. Calcul revenus mensuels récurrents (MRR)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave a:</p>
<pre><code>| formule | nombre abonnés | prix |
|---|---|---|
| Mensuel | 7,234 | 4.99€/mois |
| Annuel | 5,313 | 49.99€/an |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le MRR est calculé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> MRR mensuel = 7,234 × 4.99€ = 36,098€
<span style="color: #9E9E9E"><strong>Et</strong></span> MRR annuel ramené au mois = 5,313 × 49.99€ / 12 = 22,139€
<span style="color: #9E9E9E"><strong>Et</strong></span> MRR total = 58,237€/mois</p>
<hr />
<h2 id="28-projection-revenus-annuels-arr">28. Projection revenus annuels (ARR)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le MRR est de 58,237€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'ARR est calculé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ARR = 58,237€ × 12 = 698,844€/an
<span style="color: #9E9E9E"><strong>Et</strong></span> cela aide à évaluer la valorisation de l'entreprise</p>
<hr />
<h2 id="29-affichage-prix-ttc-tva-incluse">29. Affichage prix TTC (TVA incluse)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave est une plateforme française</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les prix sont affichés</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les prix sont TTC (TVA 20% incluse)
<span style="color: #9E9E9E"><strong>Et</strong></span> le prix 4.99€ inclut déjà la TVA
<span style="color: #9E9E9E"><strong>Et</strong></span> cela respecte la réglementation française</p>
<hr />
<h2 id="30-performance-page-premium-avec-cache">30. Performance page Premium avec cache</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la page Premium est consultée fréquemment</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un utilisateur charge la page</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les prix et avantages sont servis depuis un cache CDN
<span style="color: #9E9E9E"><strong>Et</strong></span> le temps de chargement est &lt;200ms
<span style="color: #9E9E9E"><strong>Et</strong></span> cela garantit une expérience fluide</p>
<hr />
<h2 id="31-localisation-prix-selon-pays-post-mvp">31. Localisation prix selon pays (post-MVP)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave se lance à l'international post-MVP</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un utilisateur se connecte depuis l'Allemagne</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les prix peuvent être ajustés (ex: 4.99€ en France, 4.49€ en Pologne)
<span style="color: #9E9E9E"><strong>Et</strong></span> cela respecte le pouvoir d'achat local
<span style="color: #F44336"><strong>Mais</strong></span> cette fonctionnalité n'est pas au MVP (France uniquement)</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="profil-createur">Profil créateur</h1>
<blockquote>
<p><em>En tant qu'utilisateur de RoadWave</em>
<em>Je veux consulter les profils des créateurs</em>
<em>Afin de découvrir leur contenu et décider de m'abonner</em></p>
</blockquote>
<p><strong>31 scénarios</strong> (28 standards, 3 plans)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'application RoadWave est démarrée</p>
</blockquote>
<h2 id="1-url-du-profil-createur">1. URL du profil créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un créateur avec le pseudo "paris_stories"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur accède au profil</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'URL est "https://roadwave.fr/@paris_stories"</p>
<hr />
<h2 id="2-informations-principales-du-profil">2. Informations principales du profil</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un créateur "@paris_stories" avec les informations suivantes:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le profil est affiché</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les éléments suivants sont visibles:</p>
<pre><code>| é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 |
</code></pre>
<hr />
<h2 id="3-plan-arrondi-des-statistiques-publiques">3. 📋 Plan: Arrondi des statistiques publiques</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un créateur avec <valeur_exacte> <métrique></p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le profil est affiché</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la valeur affichée est "<valeur_affichée>"</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>métrique</th>
<th>valeur_exacte</th>
<th>valeur_affichée</th>
</tr>
</thead>
<tbody>
<tr>
<td>abonnés</td>
<td>342</td>
<td>342</td>
</tr>
<tr>
<td>abonnés</td>
<td>1200</td>
<td>1.2K</td>
</tr>
<tr>
<td>abonnés</td>
<td>54000</td>
<td>54K</td>
</tr>
<tr>
<td>abonnés</td>
<td>1200000</td>
<td>1.2M</td>
</tr>
<tr>
<td>écoutes</td>
<td>842</td>
<td>842</td>
</tr>
<tr>
<td>écoutes</td>
<td>5400</td>
<td>5.4K</td>
</tr>
<tr>
<td>écoutes</td>
<td>142000</td>
<td>142K</td>
</tr>
<tr>
<td>écoutes</td>
<td>2100000</td>
<td>2.1M</td>
</tr>
<tr>
<td>durée (heures)</td>
<td>18</td>
<td>18h</td>
</tr>
<tr>
<td>durée (heures)</td>
<td>142</td>
<td>142h</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="4-bio-avec-markdown-basique">4. Bio avec markdown basique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un créateur avec la bio suivante en markdown:</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le profil est affiché</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le texte en gras "Histoires de Paris" est formaté
<span style="color: #9E9E9E"><strong>Et</strong></span> le texte en italique "Nouveau contenu chaque semaine" est formaté
<span style="color: #9E9E9E"><strong>Et</strong></span> le lien "https://paris-stories.fr" est cliquable</p>
<hr />
<h2 id="5-limitation-de-la-bio-a-300-caracteres">5. Limitation de la bio à 300 caractères</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un créateur qui entre une bio de 350 caractères</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la bio est sauvegardée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seuls les 300 premiers caractères sont conservés
<span style="color: #9E9E9E"><strong>Et</strong></span> un message "Maximum 300 caractères" s'affiche</p>
<hr />
<h2 id="6-boutons-daction-principaux">6. Boutons d'action principaux</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur consulte un profil créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la page est chargée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les boutons suivants sont visibles:</p>
<pre><code>| bouton | action |
|---|---|
| S'abonner | Abonnement au créateur |
| Partager profil | Menu de partage |
| ••• | Menu contextuel |
</code></pre>
<hr />
<h2 id="7-menu-contextuel-du-profil">7. Menu contextuel du profil [•••]</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur clique sur le bouton [•••]</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le menu s'ouvre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les options suivantes sont disponibles:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="8-liste-des-contenus-du-createur">8. Liste des contenus du créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un créateur avec 3 contenus publiés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le profil est affiché</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> chaque contenu affiche:</p>
<pre><code>| élément | exemple |
|---|---|
| Cover image | Image 16:9 |
| Titre | Balade à Paris |
| Durée et écoutes | 12 min · 🎧 2.3K |
| Localisation | 📍 Paris |
| Bouton lecture | ▶️ |
</code></pre>
<hr />
<h2 id="9-plan-options-de-tri-des-contenus">9. 📋 Plan: Options de tri des contenus</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un créateur avec 10 contenus publiés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur sélectionne le tri "<option_tri>"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les contenus sont triés par <critère></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>option_tri</th>
<th>critère</th>
</tr>
</thead>
<tbody>
<tr>
<td>Plus récents</td>
<td>Date publication DESC (défaut)</td>
</tr>
<tr>
<td>Plus populaires</td>
<td>Écoutes × facteur temporel (90 jours)</td>
</tr>
<tr>
<td>Plus anciens</td>
<td>Date publication ASC</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="10-filtrage-des-contenus-par-tag">10. Filtrage des contenus par tag</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un créateur avec des contenus taggés "Voyage", "Histoire", "Gastronomie"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur filtre par tags "Voyage, Histoire"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seuls les contenus avec ces tags sont affichés
<span style="color: #9E9E9E"><strong>Et</strong></span> le nombre de résultats est indiqué "12 contenus"</p>
<hr />
<h2 id="11-recherche-locale-dans-le-profil">11. Recherche locale dans le profil</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur consulte le profil de "@paris_stories"
<span style="color: #9E9E9E"><strong>Et</strong></span> que le créateur a publié 50 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur entre "Montmartre" dans la barre de recherche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la recherche s'effectue sur les titres et descriptions
<span style="color: #9E9E9E"><strong>Et</strong></span> seuls les contenus correspondants sont affichés
<span style="color: #9E9E9E"><strong>Et</strong></span> le placeholder indique "Rechercher dans les contenus de @paris_stories"</p>
<hr />
<h2 id="12-chargement-pagine-des-contenus">12. Chargement paginé des contenus</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un créateur avec 100 contenus publiés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le profil est affiché</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 20 contenus sont chargés initialement
<span style="color: #9E9E9E"><strong>Et</strong></span> un bouton "Charger plus" est visible en bas de page</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur "Charger plus"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 20 contenus supplémentaires sont chargés</p>
<hr />
<h2 id="13-informations-publiques-visibles-par-tous">13. Informations publiques visibles par tous</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur consulte un profil créateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les informations suivantes sont publiques:</p>
<pre><code>| information | visible |
|---|---|
| Photo et pseudo | ✅ |
| Badge vérifié | ✅ |
| Bio | ✅ |
| Nombre abonnés | ✅ |
| Nombre contenus | ✅ |
| Durée totale créée | ✅ |
| Écoutes totales | ✅ |
</code></pre>
<hr />
<h2 id="14-informations-privees-non-visibles">14. Informations privées non visibles</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur consulte un profil créateur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les informations suivantes sont privées:</p>
<pre><code>| information | visible |
|---|---|
| Liste des abonnés | ❌ |
| Revenus | ❌ |
| Localisation précise | ❌ |
| Email | ❌ |
</code></pre>
<hr />
<h2 id="15-dashboard-createur-avec-metriques-privees">15. Dashboard créateur avec métriques privées</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le créateur "@paris_stories" consulte son propre dashboard</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la page statistiques est affichée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les métriques suivantes sont accessibles:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="16-graphique-devolution-des-abonnes">16. Graphique d'évolution des abonnés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le créateur consulte son dashboard</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il sélectionne la période "30 jours"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un graphique d'évolution des abonnés est affiché
<span style="color: #9E9E9E"><strong>Et</strong></span> les périodes disponibles sont:</p>
<pre><code>| période |
|---|
| 30j |
| 90j |
| 1 an |
</code></pre>
<hr />
<h2 id="17-tableau-detaille-des-ecoutes-par-contenu">17. Tableau détaillé des écoutes par contenu</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un créateur avec 10 contenus publiés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il consulte le tableau des performances</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> chaque contenu affiche:</p>
<pre><code>| métrique | exemple |
|---|---|
| Titre | Balade |
| Écoutes totales | 2300 |
| Écoutes complètes &gt;80% | 1840 |
| Taux complétion | 80% |
| Likes | 420 |
| Partages | 56 |
</code></pre>
<hr />
<h2 id="18-affichage-du-badge-verifie">18. Affichage du badge vérifié</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un créateur vérifié "@paris_stories"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> son profil est affiché</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le badge bleu "✓" est accolé au pseudo
<span style="color: #9E9E9E"><strong>Et</strong></span> un tooltip "Compte vérifié" s'affiche au survol</p>
<hr />
<h2 id="19-badge-verifie-visible-partout">19. Badge vérifié visible partout</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un créateur vérifié "@paris_stories"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le badge "✓" est affiché dans:</p>
<pre><code>| emplacement |
|---|
| Page profil |
| Player en lecture |
| Résultats de recherche |
| Notifications |
</code></pre>
<hr />
<h2 id="20-plan-attribution-automatique-du-badge-selon-criteres">20. 📋 Plan: Attribution automatique du badge selon critères</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un créateur avec <critère></p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les conditions sont validées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le badge vérifié est attribué <automatique></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>critère</th>
<th>automatique</th>
</tr>
</thead>
<tbody>
<tr>
<td>KYC Mangopay validé</td>
<td>Oui</td>
</tr>
<tr>
<td>≥10K abonnés + compte &gt;6 mois</td>
<td>Oui</td>
</tr>
<tr>
<td>Célébrité / Média officiel</td>
<td>Manuel</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="21-attribution-automatique-via-kyc">21. Attribution automatique via KYC</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un créateur qui complète son KYC Mangopay</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les documents sont validés</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le badge vérifié est attribué automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> une notification "Votre compte est maintenant vérifié ✓" est envoyée</p>
<hr />
<h2 id="22-attribution-automatique-a-10k-abonnes">22. Attribution automatique à 10K abonnés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un créateur avec 9999 abonnés et un compte de 7 mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il atteint 10000 abonnés</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le badge vérifié est attribué automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> une notification de félicitations est envoyée</p>
<hr />
<h2 id="23-demande-manuelle-de-verification-celebrite">23. Demande manuelle de vérification (célébrité)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un créateur reconnu publiquement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il soumet le formulaire de demande de vérification</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une requête est créée pour l'équipe RoadWave
<span style="color: #9E9E9E"><strong>Et</strong></span> l'équipe vérifie l'identité sous 48-72h
<span style="color: #9E9E9E"><strong>Et</strong></span> le badge est attribué si validation réussie</p>
<hr />
<h2 id="24-retrait-du-badge-en-cas-de-suspension">24. Retrait du badge en cas de suspension</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un créateur vérifié avec le badge "✓"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> sa monétisation est suspendue</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le badge vérifié est retiré temporairement
<span style="color: #9E9E9E"><strong>Et</strong></span> le badge est restauré après levée de la suspension</p>
<hr />
<h2 id="25-retrait-definitif-du-badge-pour-strikes-multiples">25. Retrait définitif du badge pour strikes multiples</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un créateur vérifié avec 3 strikes actifs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un 4ème strike est appliqué (ban)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le badge vérifié est retiré définitivement
<span style="color: #9E9E9E"><strong>Et</strong></span> le compte est banni</p>
<hr />
<h2 id="26-retrait-du-badge-pour-usurpation-didentite">26. Retrait du badge pour usurpation d'identité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un créateur vérifié qui usurpe l'identité d'une célébrité</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la fraude est détectée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le badge est retiré immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> le compte est banni
<span style="color: #9E9E9E"><strong>Et</strong></span> une enquête est ouverte</p>
<hr />
<h2 id="27-profil-createur-supprime">27. Profil créateur supprimé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur tente d'accéder à "@deleted_user"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la page est chargée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message "Ce profil n'existe pas ou a été supprimé" s'affiche</p>
<hr />
<h2 id="28-blocage-dun-createur">28. Blocage d'un créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur bloque le créateur "@spam_account"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur consulte son flux de recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucun contenu de "@spam_account" n'est affiché
<span style="color: #9E9E9E"><strong>Et</strong></span> le créateur n'apparaît plus dans les recherches</p>
<hr />
<h2 id="29-deblocage-dun-createur">29. Déblocage d'un créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur a bloqué "@paris_stories"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il accède à ses paramètres "Comptes bloqués"
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il débloque "@paris_stories"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les contenus du créateur réapparaissent dans les recommandations</p>
<hr />
<h2 id="30-signalement-dun-profil-pour-spam">30. Signalement d'un profil pour spam</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur signale le profil "@spam_account"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il sélectionne la raison "Spam"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le signalement est envoyé à la modération
<span style="color: #9E9E9E"><strong>Et</strong></span> un message de confirmation s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> le profil reste visible jusqu'à décision de modération</p>
<hr />
<h2 id="31-signalement-pour-usurpation-didentite">31. Signalement pour usurpation d'identité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur signale le profil "@fake_celebrity"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il sélectionne "Usurpation d'identité"
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il fournit une preuve</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le signalement est priorisé (priorité HAUTE)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'équipe modération traite sous 24h</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="creation-de-campagnes-publicitaires">Création de campagnes publicitaires</h1>
<blockquote>
<p><em>En tant que publicitaire</em>
<em>Je veux créer des campagnes avec ciblage précis et maîtrise du budget</em>
<em>Afin d'optimiser mes investissements publicitaires</em></p>
</blockquote>
<p><strong>30 scénarios</strong> (27 standards, 3 plans)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un compte publicitaire est créé et vérifié</p>
</blockquote>
<h2 id="1-creation-dune-campagne-publicitaire-complete">1. Création d'une campagne publicitaire complète</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis connecté en tant que publicitaire</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je crée une nouvelle campagne avec les paramètres:</p>
<pre><code>| 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+ |
</code></pre>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la campagne est créée avec succès
<span style="color: #9E9E9E"><strong>Et</strong></span> le budget quotidien calculé est de 21.43€/jour
<span style="color: #9E9E9E"><strong>Et</strong></span> les diffusions estimées sont de ~430 écoutes complètes
<span style="color: #9E9E9E"><strong>Et</strong></span> un statut "En attente de validation" est assigné</p>
<hr />
<h2 id="2-budget-minimum-50-requis">2. Budget minimum 50€ requis</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée une nouvelle campagne</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je définis un budget de 40€</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une erreur s'affiche: "Budget minimum requis: 50€"
<span style="color: #9E9E9E"><strong>Et</strong></span> la campagne n'est pas créée</p>
<hr />
<h2 id="3-budget-de-50-exactement-accepte">3. Budget de 50€ exactement accepté</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée une nouvelle campagne</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je définis un budget de 50€</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la campagne est créée avec succès
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune erreur n'est affichée</p>
<hr />
<h2 id="4-calcul-automatique-du-budget-quotidien">4. Calcul automatique du budget quotidien</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> une campagne avec:</p>
<pre><code>| Budget total | 300€ |
|---|---|
| Durée | 14 j |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système calcule le budget quotidien</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le budget/jour est de 21.43€
<span style="color: #9E9E9E"><strong>Et</strong></span> le nombre estimé de diffusions/jour est de 430 (à 0.05€/écoute)</p>
<hr />
<h2 id="5-ciblage-geographique-point-gps-precis">5. Ciblage géographique point GPS précis</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée une campagne</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne "Point GPS" avec coordonnées (43.1234, 5.9234)
<span style="color: #9E9E9E"><strong>Et</strong></span> que je définis un rayon de 5km</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la campagne cible uniquement les utilisateurs dans ce rayon
<span style="color: #9E9E9E"><strong>Et</strong></span> la zone est représentée par un cercle sur la carte</p>
<hr />
<h2 id="6-ciblage-geographique-ville">6. Ciblage géographique ville</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée une campagne</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne "Ville" et choisis "Marseille"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la campagne cible tous les utilisateurs dans la commune de Marseille
<span style="color: #9E9E9E"><strong>Et</strong></span> les limites administratives sont affichées sur la carte</p>
<hr />
<h2 id="7-ciblage-geographique-departement">7. Ciblage géographique département</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée une campagne</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne "Département" et choisis "Var (83)"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la campagne cible tout le département du Var
<span style="color: #9E9E9E"><strong>Et</strong></span> une estimation de population cible est affichée</p>
<hr />
<h2 id="8-ciblage-geographique-region">8. Ciblage géographique région</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée une campagne</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne "Région" et choisis "Provence-Alpes-Côte d'Azur"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la campagne cible toute la région PACA
<span style="color: #9E9E9E"><strong>Et</strong></span> l'estimation de population cible est mise à jour</p>
<hr />
<h2 id="9-ciblage-geographique-national">9. Ciblage géographique national</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée une campagne</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne "National"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la campagne cible tous les utilisateurs en France
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune limite géographique n'est appliquée</p>
<hr />
<h2 id="10-ciblage-horaire-plages-multiples">10. Ciblage horaire plages multiples</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée une campagne</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je définis les plages horaires:</p>
<pre><code>| Plage |
|---|
| 7h-9h |
| 12h-14h |
| 17h-19h |
</code></pre>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publicité est diffusée uniquement pendant ces plages
<span style="color: #9E9E9E"><strong>Et</strong></span> elle n'est jamais diffusée en dehors (ex: 10h, 15h, 20h)</p>
<hr />
<h2 id="11-ciblage-horaire-toute-la-journee">11. Ciblage horaire toute la journée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée une campagne</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je ne définis aucune plage horaire spécifique</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publicité est diffusée 24h/24
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune restriction horaire n'est appliquée</p>
<hr />
<h2 id="12-ciblage-par-centres-dinteret">12. Ciblage par centres d'intérêt</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée une campagne pour un garage automobile</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne les tags:</p>
<pre><code>| Tag |
|---|
| Automobile |
| Mécanique |
| Sport |
</code></pre>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publicité est prioritaire pour les utilisateurs avec jauges élevées sur ces tags
<span style="color: #9E9E9E"><strong>Et</strong></span> elle peut quand même être diffusée à d'autres utilisateurs (ciblage non exclusif)</p>
<hr />
<h2 id="13-classification-dage-obligatoire">13. Classification d'âge obligatoire</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée une campagne</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de valider sans sélectionner une tranche d'âge</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une erreur s'affiche: "Classification d'âge obligatoire"
<span style="color: #9E9E9E"><strong>Et</strong></span> les options proposées sont:</p>
<pre><code>| Option |
|---|
| Tout public |
| 13+ |
| 16+ |
| 18+ |
</code></pre>
<hr />
<h2 id="14-upload-audio-publicitaire-formats-acceptes">14. Upload audio publicitaire formats acceptés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée une campagne</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'upload un fichier audio format MP3</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le fichier est accepté</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'upload un fichier audio format AAC (.aac ou .m4a)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le fichier est accepté</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'upload un fichier audio format WAV</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une erreur s'affiche: "Format non supporté. Utilisez MP3 ou AAC"</p>
<hr />
<h2 id="15-duree-audio-publicitaire-validee">15. Durée audio publicitaire validée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée une campagne</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'upload un audio de 8 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une erreur s'affiche: "Durée minimale: 10 secondes"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'upload un audio de 65 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une erreur s'affiche: "Durée maximale: 60 secondes"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'upload un audio de 30 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le fichier est accepté</p>
<hr />
<h2 id="16-prepaiement-obligatoire-via-mangopay">16. Prépaiement obligatoire via Mangopay</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai configuré une campagne à 300€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'arrive à l'étape de paiement</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je dois payer les 300€ avant validation
<span style="color: #9E9E9E"><strong>Et</strong></span> le paiement est traité via Mangopay
<span style="color: #9E9E9E"><strong>Et</strong></span> seule la carte bancaire est acceptée</p>
<hr />
<h2 id="17-recharge-automatique-optionnelle">17. Recharge automatique optionnelle</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai une campagne active</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je configure la recharge automatique à 10% du budget</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> si le budget restant passe sous 30€ (10% de 300€)
<span style="color: #9E9E9E"><strong>Et</strong></span> que la campagne recharge automatiquement 100€
<span style="color: #9E9E9E"><strong>Et</strong></span> ma carte bancaire est débitée de 100€
<span style="color: #9E9E9E"><strong>Et</strong></span> le budget total passe à 130€</p>
<hr />
<h2 id="18-desactivation-recharge-automatique">18. Désactivation recharge automatique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai activé la recharge automatique</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je désactive cette option</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune recharge ne se produit automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> la campagne s'arrête quand le budget atteint 0€</p>
<hr />
<h2 id="19-etalement-budget-sur-periode-longue">19. Étalement budget sur période longue</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> une campagne avec:</p>
<pre><code>| Budget total | 1000€ |
|---|---|
| Durée | 30 j |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système calcule l'étalement</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le budget/jour est de 33.33€
<span style="color: #9E9E9E"><strong>Et</strong></span> si le budget se consomme plus vite (ex: 50€/jour)
<span style="color: #4CAF50"><strong>Alors</strong></span> une alerte "Budget épuisé dans 10 jours" est envoyée</p>
<hr />
<h2 id="20-estimation-population-cible-selon-zone">20. Estimation population cible selon zone</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je sélectionne la zone "Marseille"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système calcule la population cible</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'estimation affichée est "~15 000 utilisateurs potentiels"
<span style="color: #9E9E9E"><strong>Et</strong></span> un message "Estimation basée sur utilisateurs actifs dans la zone" s'affiche</p>
<hr />
<h2 id="21-campagne-avec-date-de-debut-differee">21. Campagne avec date de début différée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée une campagne</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je définis la date de début au 2026-03-01 (dans 1 mois)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la campagne a le statut "Programmée"
<span style="color: #9E9E9E"><strong>Et</strong></span> elle démarre automatiquement le 2026-03-01 à 00h00
<span style="color: #9E9E9E"><strong>Et</strong></span> le budget n'est pas consommé avant cette date</p>
<hr />
<h2 id="22-interface-self-service-accessible">22. Interface self-service accessible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un publicitaire</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à l'interface publicitaire</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux créer une campagne sans contact commercial RoadWave
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes les options sont configurables en autonomie
<span style="color: #9E9E9E"><strong>Et</strong></span> un tutoriel guidé est disponible (première utilisation)</p>
<hr />
<h2 id="23-apercu-zone-ciblee-sur-carte-interactive">23. Aperçu zone ciblée sur carte interactive</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je configure une zone géographique</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne "Département du Var"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une carte Leaflet affiche les limites du département en surbrillance
<span style="color: #9E9E9E"><strong>Et</strong></span> un compteur "~50 000 utilisateurs actifs" est affiché
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux zoomer/dézoomer pour visualiser la zone</p>
<hr />
<h2 id="24-tags-multiples-pour-ciblage-affine">24. Tags multiples pour ciblage affiné</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée une campagne pour un restaurant</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sélectionne les tags:</p>
<pre><code>| Tag |
|---|
| Gastronomie |
| Tourisme |
| Famille |
</code></pre>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publicité est prioritaire pour utilisateurs intéressés par ces 3 thèmes
<span style="color: #9E9E9E"><strong>Et</strong></span> le score de ciblage combine les 3 jauges d'intérêt</p>
<hr />
<h2 id="25-validation-des-dates-de-campagne">25. Validation des dates de campagne</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée une campagne</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je définis une date de début postérieure à la date de fin</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une erreur s'affiche: "Date de fin doit être après date de début"
<span style="color: #9E9E9E"><strong>Et</strong></span> la campagne n'est pas créée</p>
<hr />
<h2 id="26-duree-minimale-de-campagne">26. Durée minimale de campagne</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée une campagne</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je définis une durée de moins de 24 heures</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une erreur s'affiche: "Durée minimale: 1 jour"
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois ajuster les dates</p>
<hr />
<h2 id="27-duree-maximale-de-campagne">27. Durée maximale de campagne</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée une campagne</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je définis une durée de plus de 90 jours</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une erreur s'affiche: "Durée maximale: 90 jours"
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois ajuster les dates ou créer plusieurs campagnes</p>
<hr />
<h2 id="28-plan-calcul-budget-quotidien-selon-duree">28. 📋 Plan: Calcul budget quotidien selon durée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> une campagne avec un budget de <budget>€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la durée est de <duree> jours</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le budget quotidien est de <budget_jour>€/jour</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>budget</th>
<th>duree</th>
<th>budget_jour</th>
</tr>
</thead>
<tbody>
<tr>
<td>100</td>
<td>10</td>
<td>10.00</td>
</tr>
<tr>
<td>300</td>
<td>14</td>
<td>21.43</td>
</tr>
<tr>
<td>500</td>
<td>30</td>
<td>16.67</td>
</tr>
<tr>
<td>1000</td>
<td>60</td>
<td>16.67</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="29-plan-estimation-diffusions-selon-budget">29. 📋 Plan: Estimation diffusions selon budget</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un budget quotidien de <budget_jour>€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le coût par écoute complète est 0.05€</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le nombre estimé de diffusions/jour est <diffusions></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>budget_jour</th>
<th>diffusions</th>
</tr>
</thead>
<tbody>
<tr>
<td>10.00</td>
<td>200</td>
</tr>
<tr>
<td>21.43</td>
<td>429</td>
</tr>
<tr>
<td>50.00</td>
<td>1000</td>
</tr>
<tr>
<td>100.00</td>
<td>2000</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="30-plan-formats-audio-acceptesrejetes">30. 📋 Plan: Formats audio acceptés/rejetés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'upload un fichier <fichier></p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le format est <format></p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le résultat est <resultat></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>fichier</th>
<th>format</th>
<th>resultat</th>
</tr>
</thead>
<tbody>
<tr>
<td>pub.mp3</td>
<td>MP3</td>
<td>accepté</td>
</tr>
<tr>
<td>pub.aac</td>
<td>AAC</td>
<td>accepté</td>
</tr>
<tr>
<td>pub.m4a</td>
<td>AAC</td>
<td>accepté</td>
</tr>
<tr>
<td>pub.wav</td>
<td>WAV</td>
<td>rejeté</td>
</tr>
<tr>
<td>pub.ogg</td>
<td>OGG</td>
<td>rejeté</td>
</tr>
<tr>
<td>pub.flac</td>
<td>FLAC</td>
<td>rejeté</td>
</tr>
</tbody>
</table>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="caracteristiques-et-facturation-des-publicites">Caractéristiques et facturation des publicités</h1>
<blockquote>
<p><em>En tant que système RoadWave</em>
<em>Je veux appliquer des règles précises de durée, skippabilité et facturation</em>
<em>Afin d'équilibrer expérience utilisateur et rentabilité publicitaire</em></p>
</blockquote>
<p><strong>32 scénarios</strong> (29 standards, 3 plans)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un utilisateur gratuit écoute du contenu</p>
</blockquote>
<h2 id="1-duree-minimale-10-secondes">1. Durée minimale 10 secondes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un publicitaire uploade une publicité de 8 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système valide la durée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une erreur s'affiche: "Durée minimale: 10 secondes"
<span style="color: #9E9E9E"><strong>Et</strong></span> l'upload est rejeté</p>
<hr />
<h2 id="2-duree-maximale-60-secondes">2. Durée maximale 60 secondes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un publicitaire uploade une publicité de 65 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système valide la durée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une erreur s'affiche: "Durée maximale: 60 secondes"
<span style="color: #9E9E9E"><strong>Et</strong></span> l'upload est rejeté</p>
<hr />
<h2 id="3-duree-recommandee-15-30-secondes">3. Durée recommandée 15-30 secondes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un publicitaire crée une campagne</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il voit les recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message s'affiche:</p>
<hr />
<h2 id="4-publicite-de-10-secondes-acceptee">4. Publicité de 10 secondes acceptée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un publicitaire uploade une publicité de 10 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système valide la durée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le fichier est accepté
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune erreur n'est affichée</p>
<hr />
<h2 id="5-publicite-de-60-secondes-acceptee">5. Publicité de 60 secondes acceptée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un publicitaire uploade une publicité de 60 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système valide la durée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le fichier est accepté
<span style="color: #9E9E9E"><strong>Et</strong></span> un avertissement s'affiche: "⚠️ Durée longue: taux de skip potentiellement élevé"</p>
<hr />
<h2 id="6-delai-minimum-skippable-5-secondes-par-defaut">6. Délai minimum skippable 5 secondes par défaut</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité de 30 secondes démarre
<span style="color: #9E9E9E"><strong>Et</strong></span> que le délai minimal est configuré à 5 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute pendant 3 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le bouton "Passer" n'est pas visible
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois attendre 2 secondes supplémentaires</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'atteins 5 secondes d'écoute</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le bouton "Passer" apparaît
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux cliquer pour passer au contenu suivant</p>
<hr />
<h2 id="7-delai-minimum-parametrable-admin-3-secondes">7. Délai minimum paramétrable admin (3 secondes)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'admin configure le délai à 3 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'une publicité démarre</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute pendant 3 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le bouton "Passer" apparaît immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux skipper</p>
<hr />
<h2 id="8-delai-minimum-parametrable-admin-10-secondes">8. Délai minimum paramétrable admin (10 secondes)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'admin configure le délai à 10 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'une publicité démarre</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute pendant 9 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le bouton "Passer" n'est toujours pas visible</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'atteins 10 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le bouton "Passer" apparaît</p>
<hr />
<h2 id="9-facturation-ecoute-complete-80-005">9. Facturation écoute complète (&gt;80%) - 0.05€</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité de 30 secondes est diffusée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute pendant 25 secondes (83%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'écoute est considérée comme "complète"
<span style="color: #9E9E9E"><strong>Et</strong></span> le publicitaire est facturé 0.05€
<span style="color: #9E9E9E"><strong>Et</strong></span> le compteur "écoutes complètes" s'incrémente</p>
<hr />
<h2 id="10-facturation-ecoute-complete-exactement-80">10. Facturation écoute complète exactement 80%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité de 30 secondes est diffusée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute pendant exactement 24 secondes (80%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'écoute est considérée comme "complète"
<span style="color: #9E9E9E"><strong>Et</strong></span> le publicitaire est facturé 0.05€</p>
<hr />
<h2 id="11-facturation-skip-apres-delai-minimal-002">11. Facturation skip après délai minimal - 0.02€</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité de 30 secondes est diffusée
<span style="color: #9E9E9E"><strong>Et</strong></span> que le délai minimal est 5 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute pendant 10 secondes (33%)
<span style="color: #9E9E9E"><strong>Et</strong></span> que je clique sur "Passer"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'écoute est considérée comme "partielle"
<span style="color: #9E9E9E"><strong>Et</strong></span> le publicitaire est facturé 0.02€</p>
<hr />
<h2 id="12-facturation-skip-immediat-5s-0">12. Facturation skip immédiat (&lt;5s) - 0€</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité de 30 secondes est diffusée
<span style="color: #9E9E9E"><strong>Et</strong></span> que le délai minimal est 5 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute pendant 3 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> que je clique sur "Suivant" (pas de bouton skip encore)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'écoute est considérée comme "non engagée"
<span style="color: #9E9E9E"><strong>Et</strong></span> le publicitaire n'est PAS facturé (0€)</p>
<hr />
<h2 id="13-comptabilisation-ecoute-complete-a-79">13. Comptabilisation écoute complète à 79%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité de 30 secondes est diffusée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute pendant 23 secondes (77%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'écoute est considérée comme "partielle" (pas complète)
<span style="color: #9E9E9E"><strong>Et</strong></span> le publicitaire est facturé 0.02€</p>
<hr />
<h2 id="14-comptabilisation-ecoute-complete-a-100">14. Comptabilisation écoute complète à 100%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité de 30 secondes est diffusée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute les 30 secondes complètes (100%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'écoute est considérée comme "complète"
<span style="color: #9E9E9E"><strong>Et</strong></span> le publicitaire est facturé 0.05€</p>
<hr />
<h2 id="15-budget-consomme-selon-mix-ecoutes">15. Budget consommé selon mix écoutes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une campagne à 300€ a généré:</p>
<pre><code>| Type écoute | Nombre | Coût unitaire | Total |
|---|---|---|---|
| Complète (&gt;80%) | 4000 | 0.05€ | 200€ |
| Partielle (5-80%) | 2000 | 0.02€ | 40€ |
| Skip immédiat | 1000 | 0€ | 0€ |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je calcule le budget consommé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le total est 240€
<span style="color: #9E9E9E"><strong>Et</strong></span> il reste 60€ de budget disponible</p>
<hr />
<h2 id="16-affichage-compteur-secondes-restantes">16. Affichage compteur secondes restantes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité de 30s démarre
<span style="color: #9E9E9E"><strong>Et</strong></span> que le délai minimal est 5s</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute pendant 2 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un compteur s'affiche: "Passer dans 3s..."</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'atteins 5 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le compteur disparaît
<span style="color: #9E9E9E"><strong>Et</strong></span> le bouton "Passer la publicité" s'affiche</p>
<hr />
<h2 id="17-progress-bar-publicite-visible">17. Progress bar publicité visible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité de 30s est en lecture</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> 10 secondes se sont écoulées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la progress bar affiche 33% (10/30)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'indicateur temporel affiche "0:10 / 0:30"
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur visualise la progression</p>
<hr />
<h2 id="18-message-publicite-clairement-affiche">18. Message "Publicité" clairement affiché</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité démarre</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'audio commence</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un badge "Publicité" est affiché en haut de l'écran
<span style="color: #9E9E9E"><strong>Et</strong></span> la durée totale est indiquée: "Publicité (30s)"
<span style="color: #9E9E9E"><strong>Et</strong></span> la transparence est maximale (utilisateur sait que c'est une pub)</p>
<hr />
<h2 id="19-transition-fluide-apres-publicite">19. Transition fluide après publicité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité de 30s se termine</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la lecture atteint 30 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le délai de transition de 2s démarre
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu normal suivant est annoncé
<span style="color: #9E9E9E"><strong>Et</strong></span> l'enchaînement est naturel (même UX que entre contenus)</p>
<hr />
<h2 id="20-like-autorise-sur-publicite">20. Like autorisé sur publicité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité est en lecture
<span style="color: #9E9E9E"><strong>Et</strong></span> que le véhicule est à l'arrêt</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur le bouton cœur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un like explicite (+2%) est enregistré
<span style="color: #9E9E9E"><strong>Et</strong></span> mes jauges d'intérêt sont mises à jour selon les tags de la pub
<span style="color: #9E9E9E"><strong>Et</strong></span> le publicitaire voit un compteur "Likes" incrémenté</p>
<hr />
<h2 id="21-abonnement-autorise-sur-publicite">21. Abonnement autorisé sur publicité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité est diffusée par un créateur
<span style="color: #9E9E9E"><strong>Et</strong></span> que le véhicule est à l'arrêt</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "S'abonner"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'abonnement est enregistré (+5% jauges)
<span style="color: #9E9E9E"><strong>Et</strong></span> le publicitaire bénéficie de l'engagement fort
<span style="color: #9E9E9E"><strong>Et</strong></span> cela compte comme une conversion majeure</p>
<hr />
<h2 id="22-bouton-skip-visible-et-accessible">22. Bouton skip visible et accessible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité a dépassé le délai minimal</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le bouton "Passer" s'affiche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il est positionné en bas à droite de l'écran
<span style="color: #9E9E9E"><strong>Et</strong></span> il a une taille de clic confortable (44×44px minimum iOS)
<span style="color: #9E9E9E"><strong>Et</strong></span> il est clairement visible (contraste élevé)</p>
<hr />
<h2 id="23-analytics-tracking-precis-par-type">23. Analytics tracking précis par type</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité est diffusée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un événement se produit</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il est tracké en temps réel:</p>
<pre><code>| É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 |
</code></pre>
<hr />
<h2 id="24-recommandation-sweet-spot-15-30s">24. Recommandation sweet spot 15-30s</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> les statistiques RoadWave globales:</p>
<pre><code>| Durée pub | Taux complétion moyen |
|---|---|
| 10s | 65% |
| 15s | 55% |
| 30s | 45% |
| 45s | 30% |
| 60s | 20% |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un publicitaire consulte les recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le sweet spot affiché est "15-30 secondes"
<span style="color: #9E9E9E"><strong>Et</strong></span> l'explication est "Meilleur compromis engagement/message"</p>
<hr />
<h2 id="25-optimisation-duree-selon-taux-de-skip-campagne">25. Optimisation durée selon taux de skip campagne</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une campagne de 60s a un taux de skip de 85%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le publicitaire consulte les recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système suggère:</p>
<hr />
<h2 id="26-cout-effectif-moyen-cem-calcule">26. Coût effectif moyen (CEM) calculé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> une campagne avec:</p>
<pre><code>| Type écoute | Nombre | Coût unitaire | Total |
|---|---|---|---|
| Complète | 2000 | 0.05€ | 100€ |
| Partielle | 3000 | 0.02€ | 60€ |
| Skip immédiat | 1000 | 0€ | 0€ |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je calcule le coût effectif moyen</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> CEM = 160€ / 6000 impressions = 0.027€/impression
<span style="color: #9E9E9E"><strong>Et</strong></span> cette métrique aide à comparer avec CPM industrie</p>
<hr />
<h2 id="27-publicite-non-skippable-interdite">27. Publicité non skippable interdite</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un publicitaire demande "Publicité non skippable"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il configure sa campagne</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> cette option n'existe pas
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes les publicités sont obligatoirement skippables après 5s minimum</p>
<hr />
<h2 id="28-delai-minimal-jamais-3-secondes">28. Délai minimal jamais &lt;3 secondes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un admin essaie de configurer le délai à 2 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il valide le paramètre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une erreur s'affiche: "Délai minimal: 3 secondes minimum"</p>
<hr />
<h2 id="29-delai-minimal-jamais-10-secondes">29. Délai minimal jamais &gt;10 secondes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un admin essaie de configurer le délai à 15 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il valide le paramètre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une erreur s'affiche: "Délai maximal: 10 secondes maximum"</p>
<hr />
<h2 id="30-plan-facturation-selon-duree-ecoutee">30. 📋 Plan: Facturation selon durée écoutée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité de 30s est diffusée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute pendant <duree>s (<pourcentage>%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le type d'écoute est <type>
<span style="color: #9E9E9E"><strong>Et</strong></span> le coût facturé est <cout>€</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>duree</th>
<th>pourcentage</th>
<th>type</th>
<th>cout</th>
</tr>
</thead>
<tbody>
<tr>
<td>3</td>
<td>10</td>
<td>skip immédiat</td>
<td>0</td>
</tr>
<tr>
<td>5</td>
<td>17</td>
<td>partielle</td>
<td>0.02</td>
</tr>
<tr>
<td>10</td>
<td>33</td>
<td>partielle</td>
<td>0.02</td>
</tr>
<tr>
<td>20</td>
<td>67</td>
<td>partielle</td>
<td>0.02</td>
</tr>
<tr>
<td>24</td>
<td>80</td>
<td>complète</td>
<td>0.05</td>
</tr>
<tr>
<td>27</td>
<td>90</td>
<td>complète</td>
<td>0.05</td>
</tr>
<tr>
<td>30</td>
<td>100</td>
<td>complète</td>
<td>0.05</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="31-plan-budget-consomme-selon-distribution-ecoutes">31. 📋 Plan: Budget consommé selon distribution écoutes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> <completes> écoutes complètes à 0.05€
<span style="color: #9E9E9E"><strong>Et</strong></span> <partielles> écoutes partielles à 0.02€
<span style="color: #9E9E9E"><strong>Et</strong></span> <skips> skips immédiats à 0€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je calcule le budget total consommé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le résultat est <budget_total>€</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>completes</th>
<th>partielles</th>
<th>skips</th>
<th>budget_total</th>
</tr>
</thead>
<tbody>
<tr>
<td>1000</td>
<td>500</td>
<td>100</td>
<td>60</td>
</tr>
<tr>
<td>2000</td>
<td>1000</td>
<td>500</td>
<td>120</td>
</tr>
<tr>
<td>5000</td>
<td>2000</td>
<td>1000</td>
<td>290</td>
</tr>
<tr>
<td>0</td>
<td>1000</td>
<td>0</td>
<td>20</td>
</tr>
<tr>
<td>1000</td>
<td>0</td>
<td>0</td>
<td>50</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="32-plan-apparition-bouton-skip-selon-delai-configure">32. 📋 Plan: Apparition bouton skip selon délai configuré</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le délai minimal est configuré à <delai>s</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute pendant <temps_ecoute>s</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le bouton "Passer" est <visible></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>delai</th>
<th>temps_ecoute</th>
<th>visible</th>
</tr>
</thead>
<tbody>
<tr>
<td>5</td>
<td>3</td>
<td>non visible</td>
</tr>
<tr>
<td>5</td>
<td>5</td>
<td>visible</td>
</tr>
<tr>
<td>5</td>
<td>10</td>
<td>visible</td>
</tr>
<tr>
<td>10</td>
<td>8</td>
<td>non visible</td>
</tr>
<tr>
<td>10</td>
<td>10</td>
<td>visible</td>
</tr>
<tr>
<td>3</td>
<td>2</td>
<td>non visible</td>
</tr>
<tr>
<td>3</td>
<td>3</td>
<td>visible</td>
</tr>
</tbody>
</table>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="gestion-du-budget-et-alertes-publicitaires">Gestion du budget et alertes publicitaires</h1>
<blockquote>
<p><em>En tant que publicitaire</em>
<em>Je veux suivre en temps réel mon budget et recevoir des alertes</em>
<em>Afin de maîtriser mes dépenses et optimiser mes campagnes</em></p>
</blockquote>
<p><strong>30 scénarios</strong> (27 standards, 3 plans)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un compte publicitaire est connecté
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'une campagne active est en cours</p>
</blockquote>
<h2 id="1-dashboard-budget-temps-reel">1. Dashboard budget temps réel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma campagne a un budget de 300€
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai consommé 220€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte le dashboard budget</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| Métrique | Valeur |
|---|---|
| Budget total | 300€ |
| Budget consommé | 220€ |
| Budget restant | 80€ |
| Pourcentage | 73% consommé |
</code></pre>
<hr />
<h2 id="2-jauge-visuelle-budget-consomme">2. Jauge visuelle budget consommé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai consommé 220€ sur 300€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte le dashboard</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une jauge de progression affiche 73%
<span style="color: #9E9E9E"><strong>Et</strong></span> la couleur est orange (seuil 50-80%)
<span style="color: #9E9E9E"><strong>Et</strong></span> un indicateur "80€ restants" est affiché clairement</p>
<hr />
<h2 id="3-couleur-jauge-selon-seuil">3. Couleur jauge selon seuil</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un budget de 300€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ai consommé 150€ (50%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la jauge est verte</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ai consommé 240€ (80%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la jauge est orange</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ai consommé 285€ (95%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la jauge est rouge
<span style="color: #9E9E9E"><strong>Et</strong></span> un message "Budget presque épuisé" s'affiche</p>
<hr />
<h2 id="4-projection-epuisement-budget">4. Projection épuisement budget</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai consommé 220€ en 10 jours
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il reste 4 jours de campagne</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système calcule la projection</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la consommation quotidienne moyenne est 22€/jour
<span style="color: #9E9E9E"><strong>Et</strong></span> la projection affiche "Budget épuisé dans 3.6 jours"
<span style="color: #9E9E9E"><strong>Et</strong></span> un avertissement "Campagne s'arrêtera avant la fin prévue" s'affiche</p>
<hr />
<h2 id="5-projection-avec-budget-suffisant">5. Projection avec budget suffisant</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai consommé 100€ en 10 jours
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il reste 4 jours de campagne
<span style="color: #9E9E9E"><strong>Et</strong></span> que le budget total est 300€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système calcule la projection</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la consommation quotidienne moyenne est 10€/jour
<span style="color: #9E9E9E"><strong>Et</strong></span> la projection affiche "Budget suffisant pour toute la campagne"
<span style="color: #9E9E9E"><strong>Et</strong></span> le budget restant estimé à la fin est 160€</p>
<hr />
<h2 id="6-alerte-80-budget-consomme">6. Alerte 80% budget consommé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon budget est de 300€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consomme 240€ (80%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois immédiatement un email:
<span style="color: #9E9E9E"><strong>Et</strong></span> une notification push est envoyée
<span style="color: #9E9E9E"><strong>Et</strong></span> une notification in-app s'affiche</p>
<hr />
<h2 id="7-alerte-90-budget-consomme">7. Alerte 90% budget consommé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon budget est de 300€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consomme 270€ (90%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois immédiatement un email:</p>
<hr />
<h2 id="8-alerte-budget-epuise-100">8. Alerte budget épuisé (100%)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon budget est de 300€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consomme les 300€ (100%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois immédiatement un email:
<span style="color: #9E9E9E"><strong>Et</strong></span> la campagne est automatiquement mise en pause
<span style="color: #9E9E9E"><strong>Et</strong></span> plus aucune diffusion ne se produit</p>
<hr />
<h2 id="9-pause-manuelle-de-campagne">9. Pause manuelle de campagne</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma campagne est active
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il reste 150€ de budget</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Mettre en pause"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le statut passe à "En pause"
<span style="color: #9E9E9E"><strong>Et</strong></span> les diffusions s'arrêtent immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> le budget de 150€ est conservé
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux réactiver la campagne plus tard</p>
<hr />
<h2 id="10-reprise-campagne-pausee">10. Reprise campagne pausée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma campagne est en pause
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il reste 150€ de budget</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Reprendre la campagne"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le statut passe à "Active"
<span style="color: #9E9E9E"><strong>Et</strong></span> les diffusions reprennent immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> le budget restant de 150€ continue de se consommer</p>
<hr />
<h2 id="11-prolongation-campagne-avec-recharge">11. Prolongation campagne avec recharge</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma campagne se termine dans 2 jours
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il reste 20€ de budget</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Prolonger la campagne"
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ajoute 200€ supplémentaires</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le budget total passe à 220€
<span style="color: #9E9E9E"><strong>Et</strong></span> la date de fin peut être prolongée de 10 jours
<span style="color: #9E9E9E"><strong>Et</strong></span> un nouveau paiement Mangopay de 200€ est traité</p>
<hr />
<h2 id="12-recharge-automatique-activee">12. Recharge automatique activée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai configuré la recharge automatique
<span style="color: #9E9E9E"><strong>Et</strong></span> que le seuil est fixé à 10% (30€ sur budget 300€)
<span style="color: #9E9E9E"><strong>Et</strong></span> que le montant de recharge est 100€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le budget restant passe sous 30€</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une recharge automatique de 100€ est déclenchée
<span style="color: #9E9E9E"><strong>Et</strong></span> ma carte bancaire est débitée via Mangopay
<span style="color: #9E9E9E"><strong>Et</strong></span> le budget total passe à budget_restant + 100€
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un email de confirmation</p>
<hr />
<h2 id="13-echec-recharge-automatique-carte-expiree">13. Échec recharge automatique (carte expirée)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la recharge automatique est activée
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma carte bancaire a expiré</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le budget passe sous le seuil de 10%</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la recharge automatique échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un email urgent:
<span style="color: #9E9E9E"><strong>Et</strong></span> la campagne continue jusqu'à épuisement du budget restant</p>
<hr />
<h2 id="14-modification-ciblage-si-budget-50-consomme">14. Modification ciblage si budget &lt;50% consommé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai consommé 120€ sur 300€ (40%)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de modifier le ciblage géographique</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la modification est autorisée
<span style="color: #9E9E9E"><strong>Et</strong></span> le ciblage est mis à jour immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> les nouvelles diffusions utilisent le nouveau ciblage</p>
<hr />
<h2 id="15-blocage-modification-ciblage-si-budget-50-consomme">15. Blocage modification ciblage si budget &gt;50% consommé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai consommé 180€ sur 300€ (60%)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de modifier le ciblage géographique</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une erreur s'affiche:</p>
<hr />
<h2 id="16-modification-audio-necessite-nouvelle-validation">16. Modification audio nécessite nouvelle validation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma campagne est active</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je veux modifier le fichier audio</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message s'affiche:</p>
<hr />
<h2 id="17-modification-plages-horaires-autorisee">17. Modification plages horaires autorisée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma campagne cible 7h-9h et 17h-19h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je modifie pour cibler 12h-14h aussi</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la modification est appliquée immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> les diffusions suivantes incluent la nouvelle plage
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune re-validation n'est nécessaire</p>
<hr />
<h2 id="18-historique-consommation-budget-jour-par-jour">18. Historique consommation budget jour par jour</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma campagne a duré 10 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte l'historique</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois un graphique avec:</p>
<pre><code>| Jour | Consommation | Cumulé |
|---|---|---|
| 1 | 22€ | 22€ |
| 2 | 25€ | 47€ |
| 3 | 20€ | 67€ |
| ... | ... | ... |
| 10 | 18€ | 220€ |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je peux identifier les pics de consommation</p>
<hr />
<h2 id="19-notification-fin-de-campagne-programmee">19. Notification fin de campagne programmée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma campagne se termine le 14/02</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la date de fin est atteinte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email:</p>
<hr />
<h2 id="20-remboursement-budget-non-utilise">20. Remboursement budget non utilisé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma campagne avait 300€ de budget
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'elle s'est terminée avec 280€ consommés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la campagne se termine (date ou épuisement)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un remboursement de 20€ est initié via Mangopay
<span style="color: #9E9E9E"><strong>Et</strong></span> le délai est de 5-7 jours ouvrés
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois une notification de confirmation</p>
<hr />
<h2 id="21-aucun-remboursement-si-budget-entierement-consomme">21. Aucun remboursement si budget entièrement consommé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma campagne avait 300€ de budget
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'elle s'est terminée avec 300€ consommés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la campagne se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucun remboursement n'est initié
<span style="color: #9E9E9E"><strong>Et</strong></span> le message final indique "Budget entièrement utilisé"</p>
<hr />
<h2 id="22-statistiques-comparatives-budget-vs-objectif">22. Statistiques comparatives budget vs objectif</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'avais défini un objectif de 5000 impressions
<span style="color: #9E9E9E"><strong>Et</strong></span> que mon budget était 300€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les statistiques finales</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| Métrique | Objectif | Réalisé | Écart |
|---|---|---|---|
| Impressions | 5000 | 6000 | +20% |
| Budget | 300€ | 280€ | -7% |
| Coût/impression | 0.06€ | 0.047€ | -22% |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> une analyse "✅ Objectifs dépassés avec budget optimisé"</p>
<hr />
<h2 id="23-export-rapport-financier-detaille">23. Export rapport financier détaillé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je veux analyser mes dépenses</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Exporter rapport financier"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je télécharge un CSV avec:</p>
<pre><code>| Colonne |
|---|
| Date/Heure |
| Type écoute |
| Coût unitaire |
| Zone géographique |
| Utilisateur (anonyme) |
| Durée écoutée |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je peux l'importer dans Excel pour analyses</p>
<hr />
<h2 id="24-tableau-de-bord-multi-campagnes">24. Tableau de bord multi-campagnes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai 3 campagnes actives</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte la vue d'ensemble</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois un tableau récapitulatif:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> un badge alerte rouge sur la campagne B</p>
<hr />
<h2 id="25-alerte-consolidee-multi-campagnes">25. Alerte consolidée multi-campagnes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai 5 campagnes actives
<span style="color: #9E9E9E"><strong>Et</strong></span> que 2 campagnes ont &gt;80% budget consommé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je reçois les notifications</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un email consolidé unique est envoyé:
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne reçois pas 2 emails séparés (évite spam)</p>
<hr />
<h2 id="26-configuration-seuils-alertes-personnalises">26. Configuration seuils alertes personnalisés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je configure mes préférences d'alerte</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je définis les seuils:</p>
<pre><code>| Seuil | Valeur |
|---|---|
| Alerte 1 | 70% |
| Alerte 2 | 85% |
| Alerte 3 | 95% |
</code></pre>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois des alertes à 70%, 85% et 95%
<span style="color: #9E9E9E"><strong>Et</strong></span> non aux seuils par défaut 80%, 90%, 100%</p>
<hr />
<h2 id="27-desactivation-alertes-email">27. Désactivation alertes email</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je préfère uniquement les notifications in-app</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je désactive les alertes email dans mes préférences</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je ne reçois plus d'emails d'alerte budget
<span style="color: #F44336"><strong>Mais</strong></span> les notifications in-app continuent
<span style="color: #9E9E9E"><strong>Et</strong></span> les alertes critiques (échec paiement) sont toujours envoyées par email</p>
<hr />
<h2 id="28-plan-couleur-jauge-selon-pourcentage-consomme">28. 📋 Plan: Couleur jauge selon pourcentage consommé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un budget de 300€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ai consommé <montant>€ (<pourcentage>%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la couleur de la jauge est <couleur></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>montant</th>
<th>pourcentage</th>
<th>couleur</th>
</tr>
</thead>
<tbody>
<tr>
<td>100</td>
<td>33</td>
<td>verte</td>
</tr>
<tr>
<td>150</td>
<td>50</td>
<td>verte</td>
</tr>
<tr>
<td>180</td>
<td>60</td>
<td>orange</td>
</tr>
<tr>
<td>240</td>
<td>80</td>
<td>orange</td>
</tr>
<tr>
<td>270</td>
<td>90</td>
<td>rouge</td>
</tr>
<tr>
<td>285</td>
<td>95</td>
<td>rouge</td>
</tr>
<tr>
<td>300</td>
<td>100</td>
<td>rouge</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="29-plan-projection-epuisement-selon-consommation">29. 📋 Plan: Projection épuisement selon consommation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un budget de 300€
<span style="color: #9E9E9E"><strong>Et</strong></span> une consommation actuelle de <consomme>€
<span style="color: #9E9E9E"><strong>Et</strong></span> une durée écoulée de <jours_ecoules> jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je calcule la consommation quotidienne moyenne</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> elle est de <conso_jour>€/jour
<span style="color: #9E9E9E"><strong>Et</strong></span> le budget sera épuisé dans <jours_restants> jours</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>consomme</th>
<th>jours_ecoules</th>
<th>conso_jour</th>
<th>jours_restants</th>
</tr>
</thead>
<tbody>
<tr>
<td>100</td>
<td>5</td>
<td>20</td>
<td>10</td>
</tr>
<tr>
<td>200</td>
<td>10</td>
<td>20</td>
<td>5</td>
</tr>
<tr>
<td>150</td>
<td>10</td>
<td>15</td>
<td>10</td>
</tr>
<tr>
<td>270</td>
<td>12</td>
<td>22.5</td>
<td>1.3</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="30-plan-alertes-envoyees-selon-seuils">30. 📋 Plan: Alertes envoyées selon seuils</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un budget de 500€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consomme <montant>€ (<pourcentage>%)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une alerte <niveau></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>montant</th>
<th>pourcentage</th>
<th>niveau</th>
</tr>
</thead>
<tbody>
<tr>
<td>350</td>
<td>70</td>
<td>aucune</td>
</tr>
<tr>
<td>400</td>
<td>80</td>
<td>alerte 80%</td>
</tr>
<tr>
<td>450</td>
<td>90</td>
<td>alerte 90%</td>
</tr>
<tr>
<td>500</td>
<td>100</td>
<td>budget épuisé</td>
</tr>
</tbody>
</table>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="insertion-et-frequence-des-publicites">Insertion et fréquence des publicités</h1>
<blockquote>
<p><em>En tant que système RoadWave</em>
<em>Je veux insérer les publicités de manière équilibrée et non intrusive</em>
<em>Afin de préserver l'expérience utilisateur tout en monétisant</em></p>
</blockquote>
<p><strong>31 scénarios</strong> (28 standards, 3 plans)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un utilisateur gratuit est connecté</p>
</blockquote>
<h2 id="1-frequence-par-defaut-1-pub-5-contenus">1. Fréquence par défaut 1 pub / 5 contenus</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la fréquence par défaut est configurée à 1/5
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis un utilisateur gratuit</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute 5 contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 1 publicité est insérée après le 5ème contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute 10 contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 2 publicités sont insérées (après les contenus 5 et 10)</p>
<hr />
<h2 id="2-aucune-publicite-pour-utilisateurs-premium">2. Aucune publicité pour utilisateurs Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur Premium</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute 100 contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune publicité n'est insérée
<span style="color: #9E9E9E"><strong>Et</strong></span> je bénéficie d'une expérience sans interruption publicitaire</p>
<hr />
<h2 id="3-frequence-parametrable-par-admin-13">3. Fréquence paramétrable par admin (1/3)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'admin configure la fréquence à 1/3
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis un utilisateur gratuit</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute 6 contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 2 publicités sont insérées (après contenus 3 et 6)</p>
<hr />
<h2 id="4-frequence-parametrable-par-admin-110">4. Fréquence paramétrable par admin (1/10)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'admin configure la fréquence à 1/10
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis un utilisateur gratuit</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute 20 contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 2 publicités sont insérées (après contenus 10 et 20)</p>
<hr />
<h2 id="5-jamais-dinterruption-dun-contenu-en-cours">5. Jamais d'interruption d'un contenu en cours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu de 10 minutes
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis à 5 minutes de lecture
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'une publicité devrait être insérée selon la fréquence</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système vérifie l'insertion</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publicité attend la fin du contenu actuel
<span style="color: #9E9E9E"><strong>Et</strong></span> elle s'insère pendant le délai de transition (2s)
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu n'est jamais interrompu</p>
<hr />
<h2 id="6-insertion-entre-deux-contenus-uniquement">6. Insertion entre deux contenus uniquement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le contenu "A" se termine
<span style="color: #9E9E9E"><strong>Et</strong></span> que le délai de transition de 2s démarre</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte qu'une publicité doit être insérée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le message "Publicité (30s)" s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> la publicité démarre après les 2 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> l'enchaînement est naturel et fluide</p>
<hr />
<h2 id="7-rotation-limite-3-foisjour-par-utilisateur">7. Rotation limite 3 fois/jour par utilisateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a entendu la publicité "A" 3 fois aujourd'hui</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système sélectionne une nouvelle publicité à diffuser</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publicité "A" n'est plus éligible pour cet utilisateur aujourd'hui
<span style="color: #9E9E9E"><strong>Et</strong></span> une autre publicité "B" est sélectionnée
<span style="color: #9E9E9E"><strong>Et</strong></span> cela évite la saturation publicitaire</p>
<hr />
<h2 id="8-compteur-de-diffusions-par-pub-et-par-utilisateur">8. Compteur de diffusions par pub et par utilisateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur écoute la pub "RestaurantX"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la diffusion se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un compteur Redis "pub:RestaurantX:user:123:count" s'incrémente
<span style="color: #9E9E9E"><strong>Et</strong></span> le TTL est de 24h (reset à minuit)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le compteur atteint 3</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la pub "RestaurantX" est exclue des prochaines sélections aujourd'hui</p>
<hr />
<h2 id="9-limite-max-6-pubsheure-par-utilisateur">9. Limite max 6 pubs/heure par utilisateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a entendu 6 publicités dans la dernière heure</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système devrait insérer une 7ème pub</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'insertion est reportée à l'heure suivante
<span style="color: #9E9E9E"><strong>Et</strong></span> un compteur horaire Redis "pub:user:123:hourly" est vérifié
<span style="color: #9E9E9E"><strong>Et</strong></span> cela évite le spam publicitaire</p>
<hr />
<h2 id="10-ciblage-geographique-prioritaire-point-gps">10. Ciblage géographique prioritaire - Point GPS</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité cible un point GPS à 2km de ma position
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'une autre publicité cible ma ville entière</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système sélectionne une publicité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publicité point GPS est priorisée (score géo plus élevé)
<span style="color: #9E9E9E"><strong>Et</strong></span> le ciblage précis est favorisé</p>
<hr />
<h2 id="11-ciblage-geographique-prioritaire-hierarchie">11. Ciblage géographique prioritaire - Hiérarchie</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 4 publicités sont éligibles:</p>
<pre><code>| Publicité | Zone | Distance |
|---|---|---|
| A | Point GPS | 1km |
| B | Ville | 0km |
| C | Département | 0km |
| D | National | N/A |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système sélectionne selon priorité géographique</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'ordre de priorité est: A &gt; B &gt; C &gt; D
<span style="color: #9E9E9E"><strong>Et</strong></span> la publicité A (Point GPS, la plus précise) est diffusée</p>
<hr />
<h2 id="12-ciblage-centres-dinteret-secondaire">12. Ciblage centres d'intérêt secondaire</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 2 publicités ciblent ma zone géographique:</p>
<pre><code>| Publicité | Tags | Mes jauges |
|---|---|---|
| A | Automobile | 80% |
| B | Voyage | 40% |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système applique le score centres d'intérêt</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publicité A est favorisée (meilleur match jauges)
<span style="color: #9E9E9E"><strong>Et</strong></span> le ciblage thématique affine la sélection</p>
<hr />
<h2 id="13-ciblage-horaire-strict">13. Ciblage horaire strict</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une campagne cible uniquement 7h-9h
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il est 10h30</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système sélectionne une publicité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> cette campagne n'est PAS éligible
<span style="color: #9E9E9E"><strong>Et</strong></span> seules les campagnes "toute la journée" ou avec plage horaire actuelle sont considérées</p>
<hr />
<h2 id="14-ciblage-horaire-pendant-plage-active">14. Ciblage horaire pendant plage active</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une campagne cible 7h-9h et 17h-19h
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il est 8h15</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système sélectionne une publicité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> cette campagne est éligible
<span style="color: #9E9E9E"><strong>Et</strong></span> elle peut être diffusée</p>
<hr />
<h2 id="15-normalisation-volume-audio-14-lufs">15. Normalisation volume audio -14 LUFS</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité est uploadée avec volume trop élevé (-6 LUFS)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système encode l'audio via FFmpeg</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le volume est normalisé automatiquement à -14 LUFS
<span style="color: #9E9E9E"><strong>Et</strong></span> le publicitaire reçoit une notification "Volume audio ajusté pour conformité"
<span style="color: #9E9E9E"><strong>Et</strong></span> cela évite l'effet "pub trop forte" frustrant</p>
<hr />
<h2 id="16-validation-volume-audio-lors-encodage">16. Validation volume audio lors encodage</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité est soumise</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> FFmpeg encode le fichier</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une commande loudnorm est appliquée:
<span style="color: #9E9E9E"><strong>Et</strong></span> le fichier final respecte le standard broadcast -14 LUFS</p>
<hr />
<h2 id="17-selection-aleatoire-si-criteres-equivalents">17. Sélection aléatoire si critères équivalents</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 3 publicités ont le même score géo
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'elles ont toutes des jauges centres d'intérêt équivalentes
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'aucune n'a été diffusée 3 fois aujourd'hui</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système sélectionne une publicité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une sélection aléatoire équitable est faite
<span style="color: #9E9E9E"><strong>Et</strong></span> chaque campagne a 33% de chances d'être diffusée</p>
<hr />
<h2 id="18-exclusion-publicites-avec-budget-epuise">18. Exclusion publicités avec budget épuisé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une campagne "A" a épuisé son budget
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'une campagne "B" a encore du budget disponible</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système sélectionne une publicité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seule la campagne "B" est éligible
<span style="color: #9E9E9E"><strong>Et</strong></span> la campagne "A" est automatiquement exclue</p>
<hr />
<h2 id="19-exclusion-publicites-hors-dates-de-campagne">19. Exclusion publicités hors dates de campagne</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une campagne "A" est programmée du 01/02 au 14/02
<span style="color: #9E9E9E"><strong>Et</strong></span> que nous sommes le 20/01</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système sélectionne une publicité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la campagne "A" n'est pas éligible
<span style="color: #9E9E9E"><strong>Et</strong></span> seules les campagnes actives aujourd'hui sont considérées</p>
<hr />
<h2 id="20-publicite-visible-uniquement-dans-zone-geographique">20. Publicité visible uniquement dans zone géographique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité cible "Marseille uniquement"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis à Lyon</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système sélectionne une publicité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> cette publicité n'est jamais éligible pour moi
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne la verrai jamais tant que je reste à Lyon</p>
<hr />
<h2 id="21-tracking-compteur-horaire-avec-ttl">21. Tracking compteur horaire avec TTL</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur entend une pub à 10h05</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le compteur horaire est incrémenté</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la clé Redis "pub:user:123:hourly:2026012110" est créée
<span style="color: #9E9E9E"><strong>Et</strong></span> le TTL est de 1 heure (expire à 11h05)
<span style="color: #9E9E9E"><strong>Et</strong></span> le système compte les pubs dans la fenêtre glissante d'1h</p>
<hr />
<h2 id="22-reset-compteur-quotidien-a-minuit">22. Reset compteur quotidien à minuit</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a entendu la pub "A" 3 fois le 20/01</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> minuit passe et on est le 21/01</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le compteur "pub:A:user:123:count" est expiré (TTL 24h)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur peut à nouveau entendre la pub "A" jusqu'à 3 fois</p>
<hr />
<h2 id="23-aucune-pub-si-aucune-campagne-eligible">23. Aucune pub si aucune campagne éligible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'aucune campagne n'a de budget disponible</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système devrait insérer une publicité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune pub n'est insérée
<span style="color: #9E9E9E"><strong>Et</strong></span> l'enchaînement de contenus continue normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> le prochain contenu démarre directement</p>
<hr />
<h2 id="24-priorisation-campagnes-avec-budget-important-restant">24. Priorisation campagnes avec budget important restant</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 2 campagnes sont éligibles:</p>
<pre><code>| Campagne | Budget restant | Jours restants |
|---|---|---|
| A | 500€ | 2j |
| B | 50€ | 10j |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système applique la priorisation budgétaire</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la campagne A est légèrement favorisée (urgence dépense)
<span style="color: #9E9E9E"><strong>Et</strong></span> cela aide à épuiser les budgets avant fin de campagne</p>
<hr />
<h2 id="25-log-des-selections-pour-analytics">25. Log des sélections pour analytics</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité "RestaurantX" est sélectionnée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> elle est diffusée à l'utilisateur "123"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un événement est loggé en base:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> cela permet l'analytics publicitaire</p>
<hr />
<h2 id="26-detection-changement-statut-utilisateur-gratuit-premium">26. Détection changement statut utilisateur (gratuit → premium)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur gratuit
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'entends des publicités</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je souscris à Premium</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système détecte le changement de statut immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> plus aucune publicité n'est insérée dès le prochain contenu
<span style="color: #9E9E9E"><strong>Et</strong></span> mon expérience devient sans pub instantanément</p>
<hr />
<h2 id="27-interface-admin-pour-ajuster-frequence-globale">27. Interface admin pour ajuster fréquence globale</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis admin RoadWave</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède aux paramètres publicitaires</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux ajuster le curseur de fréquence:</p>
<pre><code>| Option | Fréquence |
|---|---|
| 1/3 | Haute (agressif) |
| 1/5 | Standard (défaut) |
| 1/7 | Modérée |
| 1/10 | Faible |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> le changement s'applique en temps réel à tous les utilisateurs</p>
<hr />
<h2 id="28-ab-testing-frequence-sur-cohortes-utilisateurs">28. A/B testing fréquence sur cohortes utilisateurs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'admin active un test A/B</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> 50% des utilisateurs ont fréquence 1/5
<span style="color: #9E9E9E"><strong>Et</strong></span> 50% des utilisateurs ont fréquence 1/7</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les métriques sont trackées séparément:</p>
<pre><code>| Cohorte | Fréquence | Taux désabonnement | Revenus/user |
|---|---|---|---|
| A | 1/5 | 2.5% | 0.50€ |
| B | 1/7 | 1.8% | 0.40€ |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> l'admin peut identifier la fréquence optimale</p>
<hr />
<h2 id="29-plan-insertion-publicite-selon-frequence">29. 📋 Plan: Insertion publicité selon fréquence</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la fréquence est <frequence></p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute <contenus> contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> <pubs> publicités sont insérées</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>frequence</th>
<th>contenus</th>
<th>pubs</th>
</tr>
</thead>
<tbody>
<tr>
<td>1/3</td>
<td>9</td>
<td>3</td>
</tr>
<tr>
<td>1/5</td>
<td>10</td>
<td>2</td>
</tr>
<tr>
<td>1/5</td>
<td>25</td>
<td>5</td>
</tr>
<tr>
<td>1/7</td>
<td>14</td>
<td>2</td>
</tr>
<tr>
<td>1/10</td>
<td>30</td>
<td>3</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="30-plan-priorite-geographique-selon-type-zone">30. 📋 Plan: Priorité géographique selon type zone</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité cible <type_zone></p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système calcule le score géographique</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la priorité est <score></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>type_zone</th>
<th>score</th>
</tr>
</thead>
<tbody>
<tr>
<td>Point GPS</td>
<td>1.0</td>
</tr>
<tr>
<td>Ville</td>
<td>0.8</td>
</tr>
<tr>
<td>Département</td>
<td>0.6</td>
</tr>
<tr>
<td>Région</td>
<td>0.4</td>
</tr>
<tr>
<td>National</td>
<td>0.2</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="31-plan-exclusion-publicite-selon-compteur-quotidien">31. 📋 Plan: Exclusion publicité selon compteur quotidien</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité a été entendue <fois> fois aujourd'hui</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système vérifie l'éligibilité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publicité est <eligible></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>fois</th>
<th>eligible</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>éligible</td>
</tr>
<tr>
<td>1</td>
<td>éligible</td>
</tr>
<tr>
<td>2</td>
<td>éligible</td>
</tr>
<tr>
<td>3</td>
<td>non éligible</td>
</tr>
<tr>
<td>4</td>
<td>non éligible</td>
</tr>
</tbody>
</table>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="metriques-dengagement-et-dashboard-publicitaire">Métriques d'engagement et dashboard publicitaire</h1>
<blockquote>
<p><em>En tant que publicitaire</em>
<em>Je veux consulter des métriques détaillées en temps réel</em>
<em>Afin d'optimiser mes campagnes et mesurer leur ROI</em></p>
</blockquote>
<p><strong>27 scénarios</strong> (24 standards, 3 plans)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un compte publicitaire est connecté
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'une campagne active est en cours</p>
</blockquote>
<h2 id="1-dashboard-temps-reel-avec-metriques-essentielles">1. Dashboard temps réel avec métriques essentielles</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma campagne a généré 1000 diffusions</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte le dashboard</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois les métriques suivantes mises à jour en temps réel:</p>
<pre><code>| Métrique | Valeur |
|---|---|
| Impressions | 1000 |
| Écoutes complètes (&gt;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€ |
</code></pre>
<hr />
<h2 id="2-calcul-impressions-totales">2. Calcul impressions totales</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma publicité a été diffusée 2500 fois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte le dashboard</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le compteur "Impressions" affiche 2500
<span style="color: #9E9E9E"><strong>Et</strong></span> il s'incrémente en temps réel à chaque nouvelle diffusion</p>
<hr />
<h2 id="3-calcul-ecoutes-completes-80">3. Calcul écoutes complètes (&gt;80%)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma publicité de 30s a été:</p>
<pre><code>| Durée écoutée | Nombre |
|---|---|
| 25s (83%) | 300 |
| 20s (67%) | 200 |
| 10s (33%) | 150 |
| 5s (17%) | 50 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les écoutes complètes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le compteur affiche 300 (uniquement ≥80%)
<span style="color: #9E9E9E"><strong>Et</strong></span> le taux d'écoute complète est de 43% (300/700)</p>
<hr />
<h2 id="4-calcul-taux-de-skip">4. Calcul taux de skip</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> 1000 diffusions totales
<span style="color: #9E9E9E"><strong>Et</strong></span> 400 écoutes complètes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte le taux de skip</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il affiche 60% ((1000-400)/1000)
<span style="color: #9E9E9E"><strong>Et</strong></span> il est calculé comme (total - complètes) / total</p>
<hr />
<h2 id="5-duree-moyenne-decoute-calculee">5. Durée moyenne d'écoute calculée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma publicité de 30s a été écoutée:</p>
<pre><code>| Durée | Nombre d'utilisateurs |
|---|---|
| 30s | 400 |
| 20s | 300 |
| 10s | 200 |
| 5s | 100 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte la durée moyenne</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le calcul est: (30×400 + 20×300 + 10×200 + 5×100) / 1000
<span style="color: #9E9E9E"><strong>Et</strong></span> le résultat affiché est 21s</p>
<hr />
<h2 id="6-metriques-de-likes-sur-publicite">6. Métriques de likes sur publicité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 50 utilisateurs ont liké ma publicité</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte le dashboard</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le compteur "Likes" affiche 50
<span style="color: #9E9E9E"><strong>Et</strong></span> un taux de like de 5% est calculé (50/1000 impressions)
<span style="color: #9E9E9E"><strong>Et</strong></span> cela indique une forte appréciation du contenu</p>
<hr />
<h2 id="7-metriques-dabonnements-generes">7. Métriques d'abonnements générés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 10 utilisateurs se sont abonnés après avoir entendu ma pub</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte le dashboard</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le compteur "Abonnements" affiche 10
<span style="color: #9E9E9E"><strong>Et</strong></span> un taux de conversion de 1% est calculé (10/1000)
<span style="color: #9E9E9E"><strong>Et</strong></span> cela représente un engagement très fort</p>
<hr />
<h2 id="8-calcul-cout-par-ecoute-cpe">8. Calcul coût par écoute (CPE)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai dépensé 200€
<span style="color: #9E9E9E"><strong>Et</strong></span> obtenu 4000 écoutes complètes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte le coût par écoute</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le CPE affiché est 0.05€ (200/4000)
<span style="color: #9E9E9E"><strong>Et</strong></span> il correspond au tarif standard RoadWave</p>
<hr />
<h2 id="9-repartition-geographique-avec-heatmap">9. Répartition géographique avec heatmap</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma campagne cible le département du Var
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai 1000 diffusions réparties:</p>
<pre><code>| Zone | Diffusions | Pourcentage |
|---|---|---|
| Toulon | 400 | 40% |
| Hyères | 250 | 25% |
| Fréjus | 200 | 20% |
| Autres | 150 | 15% |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte la heatmap géographique</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une carte Leaflet affiche les zones avec intensité proportionnelle
<span style="color: #9E9E9E"><strong>Et</strong></span> Toulon apparaît en rouge foncé (forte concentration)
<span style="color: #9E9E9E"><strong>Et</strong></span> les autres villes en dégradé orange/jaune</p>
<hr />
<h2 id="10-repartition-horaire-avec-graphique">10. Répartition horaire avec graphique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma campagne cible les plages 7h-9h et 17h-19h
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai 1000 diffusions:</p>
<pre><code>| Plage horaire | Diffusions |
|---|---|
| 7h-8h | 300 |
| 8h-9h | 250 |
| 17h-18h | 280 |
| 18h-19h | 170 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte le graphique horaire</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un histogramme Chart.js affiche les 4 barres
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux identifier que 7h-8h est le pic d'écoute
<span style="color: #9E9E9E"><strong>Et</strong></span> optimiser mes futures campagnes sur cette plage</p>
<hr />
<h2 id="11-taux-de-completion-par-tranche-dage">11. Taux de complétion par tranche d'âge</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma campagne est Tout Public
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai des écoutes sur différentes tranches:</p>
<pre><code>| 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% |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte l'analyse par âge</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois que les 25-34 ans ont le meilleur taux (50%)
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux cibler cette tranche pour mes prochaines campagnes</p>
<hr />
<h2 id="12-comparatif-de-campagnes-ab-testing">12. Comparatif de campagnes A/B testing</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai 2 campagnes actives:</p>
<pre><code>| Campagne | Budget | Écoutes complètes | Taux | CPE |
|---|---|---|---|---|
| A | 300€ | 4000 | 40% | 0.075€ |
| B | 300€ | 6000 | 60% | 0.05€ |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte le comparatif</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois que la campagne B performe mieux
<span style="color: #9E9E9E"><strong>Et</strong></span> le tableau recommande "Campagne B: +50% écoutes, -33% CPE"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux allouer plus de budget à la campagne B</p>
<hr />
<h2 id="13-export-donnees-csv-pour-analyse-externe">13. Export données CSV pour analyse externe</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je veux analyser mes données dans Excel</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Exporter CSV"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je télécharge un fichier avec les colonnes:</p>
<pre><code>| Colonne |
|---|
| Date |
| Heure |
| Zone géographique |
| Tranche d'âge |
| Durée écoute |
| Skip (Oui/Non) |
| Like (Oui/Non) |
| Abonnement (Oui/Non) |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je peux faire des analyses personnalisées</p>
<hr />
<h2 id="14-export-graphiques-interactifs">14. Export graphiques interactifs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte le dashboard</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur un graphique Chart.js</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux zoomer/filtrer interactivement
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux exporter le graphique en PNG
<span style="color: #9E9E9E"><strong>Et</strong></span> l'image est en haute résolution pour présentations</p>
<hr />
<h2 id="15-rapport-pdf-automatique-fin-de-campagne">15. Rapport PDF automatique fin de campagne</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma campagne de 14 jours se termine</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la date de fin est atteinte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un rapport PDF est généré automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> il contient:</p>
<pre><code>| Section |
|---|
| Résumé exécutif |
| Métriques clés |
| Graphiques de performance |
| Heatmap géographique |
| Répartition horaire |
| Analyse tranches d'âge |
| Recommandations optimisation |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un email avec le PDF en pièce jointe</p>
<hr />
<h2 id="16-metriques-temps-reel-rafraichies-automatiquement">16. Métriques temps réel rafraîchies automatiquement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte le dashboard à 10h00</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> une nouvelle diffusion se produit à 10h01</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les métriques sont rafraîchies automatiquement (polling 30s)
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois les nouveaux chiffres sans recharger la page
<span style="color: #9E9E9E"><strong>Et</strong></span> un badge "Mis à jour il y a 15s" s'affiche</p>
<hr />
<h2 id="17-alertes-performance-personnalisees">17. Alertes performance personnalisées</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je configure une alerte "Taux de skip &gt;70%"
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma campagne atteint 72% de skip</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le seuil est dépassé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email d'alerte:</p>
<hr />
<h2 id="18-benchmark-vs-moyennes-roadwave">18. Benchmark vs moyennes RoadWave</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma campagne a 45% d'écoutes complètes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte le benchmark</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois "Votre taux: 45% | Moyenne RoadWave: 40%"
<span style="color: #9E9E9E"><strong>Et</strong></span> un badge "📊 Performance: +12% vs moyenne" s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> je sais que ma campagne performe au-dessus de la moyenne</p>
<hr />
<h2 id="19-cout-total-consomme-vs-budget">19. Coût total consommé vs budget</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai un budget de 300€
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai consommé 220€</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte le dashboard</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois une jauge "Budget consommé: 73%" (220/300)
<span style="color: #9E9E9E"><strong>Et</strong></span> le montant restant "80€ restants"
<span style="color: #9E9E9E"><strong>Et</strong></span> une projection "Épuisé dans 3 jours à ce rythme"</p>
<hr />
<h2 id="20-repartition-couts-par-type-decoute">20. Répartition coûts par type d'écoute</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai dépensé 200€ avec:</p>
<pre><code>| 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€ |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte la répartition</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un graphique camembert affiche:</p>
<pre><code>| Segment | Pourcentage |
|---|---|
| Écoutes complètes | 75% (150€) |
| Skips partiels | 20% (40€) |
| Skips immédiats | 5% (0€) |
</code></pre>
<hr />
<h2 id="21-evolution-performance-dans-le-temps">21. Évolution performance dans le temps</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> une campagne de 30 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte le graphique d'évolution</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois une courbe Chart.js avec:</p>
<pre><code>| Axe | Donnée |
|---|---|
| X | Jours (1-30) |
| Y | Taux d'écoute complète (%) |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je peux identifier les tendances (amélioration/dégradation)
<span style="color: #9E9E9E"><strong>Et</strong></span> les jours avec pics d'engagement</p>
<hr />
<h2 id="22-metriques-avancees-taux-de-reecoute">22. Métriques avancées - Taux de réécoute</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a entendu ma pub 3 fois
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il l'a écoutée complètement les 3 fois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les métriques avancées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le "Taux de réécoute" affiche 100%
<span style="color: #9E9E9E"><strong>Et</strong></span> cela indique que le contenu n'est pas perçu comme spam
<span style="color: #9E9E9E"><strong>Et</strong></span> les utilisateurs tolèrent bien la répétition</p>
<hr />
<h2 id="23-recommandations-automatiques-doptimisation">23. Recommandations automatiques d'optimisation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma campagne a un taux de skip de 75%
<span style="color: #9E9E9E"><strong>Et</strong></span> que la durée moyenne d'écoute est de 8s sur 30s</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système suggère:</p>
<hr />
<h2 id="24-suivi-multi-campagnes-avec-vue-consolidee">24. Suivi multi-campagnes avec vue consolidée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai 3 campagnes actives simultanément</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte la vue consolidée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois un tableau récapitulatif:</p>
<pre><code>| 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€ |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je peux comparer les performances d'un coup d'œil</p>
<hr />
<h2 id="25-plan-calcul-taux-decoute-complete">25. 📋 Plan: Calcul taux d'écoute complète</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> <total> diffusions totales
<span style="color: #9E9E9E"><strong>Et</strong></span> <completes> écoutes complètes (≥80%)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je calcule le taux</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le résultat est <taux>%</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>total</th>
<th>completes</th>
<th>taux</th>
</tr>
</thead>
<tbody>
<tr>
<td>1000</td>
<td>400</td>
<td>40</td>
</tr>
<tr>
<td>2000</td>
<td>1200</td>
<td>60</td>
</tr>
<tr>
<td>500</td>
<td>100</td>
<td>20</td>
</tr>
<tr>
<td>1000</td>
<td>850</td>
<td>85</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="26-plan-calcul-cout-par-ecoute-cpe">26. 📋 Plan: Calcul coût par écoute (CPE)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un budget dépensé de <depense>€
<span style="color: #9E9E9E"><strong>Et</strong></span> <ecoutes> écoutes complètes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je calcule le CPE</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le résultat est <cpe>€</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>depense</th>
<th>ecoutes</th>
<th>cpe</th>
</tr>
</thead>
<tbody>
<tr>
<td>100</td>
<td>2000</td>
<td>0.05</td>
</tr>
<tr>
<td>300</td>
<td>6000</td>
<td>0.05</td>
</tr>
<tr>
<td>50</td>
<td>1000</td>
<td>0.05</td>
</tr>
<tr>
<td>500</td>
<td>10000</td>
<td>0.05</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="27-plan-classification-performance-vs-benchmark">27. 📋 Plan: Classification performance vs benchmark</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un taux d'écoute complète de <taux>%
<span style="color: #9E9E9E"><strong>Et</strong></span> une moyenne RoadWave de 40%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je compare à la moyenne</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la performance est <classification></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>taux</th>
<th>classification</th>
</tr>
</thead>
<tbody>
<tr>
<td>60</td>
<td>Excellente (+50%)</td>
</tr>
<tr>
<td>50</td>
<td>Bonne (+25%)</td>
</tr>
<tr>
<td>40</td>
<td>Moyenne</td>
</tr>
<tr>
<td>30</td>
<td>Faible (-25%)</td>
</tr>
<tr>
<td>20</td>
<td>Très faible (-50%)</td>
</tr>
</tbody>
</table>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="validation-et-moderation-des-publicites">Validation et modération des publicités</h1>
<blockquote>
<p><em>En tant que modérateur RoadWave</em>
<em>Je veux valider manuellement toutes les publicités avant diffusion</em>
<em>Afin de garantir la qualité et la légalité des contenus publicitaires</em></p>
</blockquote>
<p><strong>29 scénarios</strong> (27 standards, 2 plans)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un modérateur RoadWave est connecté</p>
</blockquote>
<h2 id="1-validation-manuelle-obligatoire-avant-diffusion">1. Validation manuelle obligatoire avant diffusion</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un publicitaire a créé une campagne
<span style="color: #9E9E9E"><strong>Et</strong></span> que le paiement de 300€ a été effectué</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la campagne est soumise</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> elle passe en statut "En attente de validation"
<span style="color: #9E9E9E"><strong>Et</strong></span> elle est ajoutée à la file d'attente des modérateurs
<span style="color: #9E9E9E"><strong>Et</strong></span> la diffusion ne démarre PAS avant validation manuelle
<span style="color: #9E9E9E"><strong>Et</strong></span> le publicitaire reçoit un email "Votre campagne est en cours de validation (24-48h)"</p>
<hr />
<h2 id="2-delai-de-validation-24-48h-ouvrees">2. Délai de validation 24-48h ouvrées</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une campagne est soumise le lundi 10h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur la valide le mardi 15h</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le délai est de 29h (dans les 48h ouvrées)
<span style="color: #9E9E9E"><strong>Et</strong></span> le publicitaire reçoit une notification "Votre campagne est approuvée"</p>
<hr />
<h2 id="3-validation-depassant-48h-avec-notification">3. Validation dépassant 48h avec notification</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une campagne est soumise le lundi 10h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> 48h ouvrées se sont écoulées
<span style="color: #9E9E9E"><strong>Et</strong></span> que la campagne n'est toujours pas validée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le publicitaire reçoit un email automatique:
<span style="color: #9E9E9E"><strong>Et</strong></span> un modérateur senior est assigné automatiquement</p>
<hr />
<h2 id="4-acceptation-de-campagne-publicitaire">4. Acceptation de campagne publicitaire</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une campagne est en attente de validation
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'audio respecte toutes les règles</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur clique sur "Approuver"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le statut passe à "Approuvée"
<span style="color: #9E9E9E"><strong>Et</strong></span> la campagne démarre à la date programmée
<span style="color: #9E9E9E"><strong>Et</strong></span> le publicitaire reçoit un email de confirmation
<span style="color: #9E9E9E"><strong>Et</strong></span> le budget commence à être consommé dès le début</p>
<hr />
<h2 id="5-refus-de-campagne-avec-motif-detaille">5. Refus de campagne avec motif détaillé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une campagne contient du contenu alcool</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur clique sur "Refuser"
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il sélectionne le motif "Contenu interdit: Alcool"
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il ajoute le commentaire "La publicité pour l'alcool est interdite en France"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le statut passe à "Refusée"
<span style="color: #9E9E9E"><strong>Et</strong></span> le publicitaire reçoit un email détaillé avec:</p>
<pre><code>| Champ | Valeur |
|---|---|
| Motif | Contenu interdit: Alcool |
| Commentaire | La publicité pour l'alcool est interdite en France |
| Action requise | Modifier votre contenu et soumettre à nouveau |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> un remboursement automatique de 300€ est déclenché</p>
<hr />
<h2 id="6-remboursement-automatique-apres-refus">6. Remboursement automatique après refus</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une campagne à 500€ est refusée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le statut passe à "Refusée"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un remboursement Mangopay de 500€ est initié automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> le délai de remboursement est de 5-7 jours ouvrés
<span style="color: #9E9E9E"><strong>Et</strong></span> le publicitaire reçoit un email "Remboursement en cours"</p>
<hr />
<h2 id="7-contenus-interdits-alcool">7. Contenus interdits - Alcool</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité mentionne "Whisky premium 40°"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur écoute l'audio</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il doit refuser la campagne
<span style="color: #9E9E9E"><strong>Et</strong></span> sélectionner le motif "Contenu interdit: Alcool"</p>
<hr />
<h2 id="8-contenus-interdits-tabac">8. Contenus interdits - Tabac</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité mentionne "Cigarettes électroniques"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur écoute l'audio</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il doit refuser la campagne
<span style="color: #9E9E9E"><strong>Et</strong></span> sélectionner le motif "Contenu interdit: Tabac/Vape"</p>
<hr />
<h2 id="9-contenus-interdits-jeux-dargent">9. Contenus interdits - Jeux d'argent</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité mentionne "Gagnez 10 000€ - Paris sportifs"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur écoute l'audio</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il doit refuser la campagne
<span style="color: #9E9E9E"><strong>Et</strong></span> sélectionner le motif "Contenu interdit: Jeux d'argent"</p>
<hr />
<h2 id="10-contenus-interdits-politique-pendant-campagne-electorale">10. Contenus interdits - Politique pendant campagne électorale</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité politique est soumise
<span style="color: #9E9E9E"><strong>Et</strong></span> que nous sommes en période de campagne électorale officielle</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur écoute l'audio</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il doit refuser la campagne
<span style="color: #9E9E9E"><strong>Et</strong></span> sélectionner le motif "Contenu interdit: Publicité politique (période électorale)"</p>
<hr />
<h2 id="11-contenus-interdits-contenu-sexuel">11. Contenus interdits - Contenu sexuel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité contient des propos sexuellement explicites</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur écoute l'audio</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il doit refuser la campagne
<span style="color: #9E9E9E"><strong>Et</strong></span> sélectionner le motif "Contenu interdit: Contenu sexuel"</p>
<hr />
<h2 id="12-contenus-interdits-violence">12. Contenus interdits - Violence</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité contient des descriptions violentes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur écoute l'audio</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il doit refuser la campagne
<span style="color: #9E9E9E"><strong>Et</strong></span> sélectionner le motif "Contenu interdit: Violence"</p>
<hr />
<h2 id="13-contenu-legal-autorise-commerce-local">13. Contenu légal autorisé - Commerce local</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité pour un restaurant local dit "Découvrez notre menu du jour"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur écoute l'audio</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il doit approuver la campagne</p>
<hr />
<h2 id="14-contenu-legal-autorise-service-professionnel">14. Contenu légal autorisé - Service professionnel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité pour un garage dit "Révision complète à partir de 99€"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur écoute l'audio</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il doit approuver la campagne</p>
<hr />
<h2 id="15-criteres-de-validation-qualite-audio">15. Critères de validation - Qualité audio</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité a une qualité audio très basse (bruits, saturation)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur écoute l'audio</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il peut refuser avec le motif "Qualité audio insuffisante"
<span style="color: #9E9E9E"><strong>Et</strong></span> recommander "Veuillez soumettre un fichier audio de meilleure qualité"</p>
<hr />
<h2 id="16-criteres-de-validation-classification-dage-correcte">16. Critères de validation - Classification d'âge correcte</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité contient du langage familier
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'elle est classée "Tout public"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur écoute l'audio</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il peut refuser avec le motif "Classification d'âge incorrecte"
<span style="color: #9E9E9E"><strong>Et</strong></span> recommander "Reclasser en 13+ minimum"</p>
<hr />
<h2 id="17-criteres-de-validation-respect-reglementation-francaise">17. Critères de validation - Respect réglementation française</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité fait des promesses mensongères "Perdez 10kg en 1 semaine"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur écoute l'audio</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il doit refuser avec le motif "Non-conformité réglementaire: Publicité mensongère"</p>
<hr />
<h2 id="18-file-dattente-moderation-priorisee">18. File d'attente modération priorisée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 10 campagnes sont en attente de validation
<span style="color: #9E9E9E"><strong>Et</strong></span> que la campagne A a été soumise il y a 40h
<span style="color: #9E9E9E"><strong>Et</strong></span> que la campagne B a été soumise il y a 2h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur consulte sa file</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la campagne A apparaît en premier (priorité temporelle)
<span style="color: #9E9E9E"><strong>Et</strong></span> un badge "Urgente - &gt;40h" est affiché</p>
<hr />
<h2 id="19-dashboard-moderation-vue-densemble">19. Dashboard modération - Vue d'ensemble</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis modérateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède au dashboard modération publicités</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| 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% |
</code></pre>
<hr />
<h2 id="20-transcription-automatique-pour-aide-moderation">20. Transcription automatique pour aide modération</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité audio est soumise</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système traite l'audio</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une transcription automatique est générée via Whisper
<span style="color: #9E9E9E"><strong>Et</strong></span> elle est affichée au modérateur pour faciliter la revue
<span style="color: #9E9E9E"><strong>Et</strong></span> elle permet une recherche par mots-clés (alcool, tabac, etc.)</p>
<hr />
<h2 id="21-detection-automatique-mots-cles-interdits">21. Détection automatique mots-clés interdits</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité audio est soumise</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la transcription contient "whisky" ou "vodka"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un flag automatique "⚠️ Alcool détecté" est ajouté
<span style="color: #9E9E9E"><strong>Et</strong></span> la campagne est priorisée pour validation manuelle rapide
<span style="color: #9E9E9E"><strong>Et</strong></span> le modérateur est alerté du contenu potentiellement interdit</p>
<hr />
<h2 id="22-historique-moderation-publicitaire">22. Historique modération publicitaire</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un publicitaire a eu 2 campagnes refusées</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il soumet une 3ème campagne</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le modérateur voit l'historique:</p>
<pre><code>| Date | Statut | Motif |
|---|---|---|
| 2026-01-15 | Refusée | Contenu interdit: Alcool |
| 2026-01-20 | Refusée | Qualité audio faible |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> il peut en tenir compte dans sa décision</p>
<hr />
<h2 id="23-appel-possible-apres-refus">23. Appel possible après refus</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma campagne a été refusée pour "Classification incorrecte"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je conteste la décision via le formulaire d'appel</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un modérateur senior revoit la campagne
<span style="color: #9E9E9E"><strong>Et</strong></span> il peut approuver si la classification est en fait correcte
<span style="color: #9E9E9E"><strong>Et</strong></span> le délai de réponse est de 48-72h</p>
<hr />
<h2 id="24-notification-temps-reel-pour-moderateurs">24. Notification temps réel pour modérateurs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis modérateur connecté</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> une nouvelle campagne est soumise</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification in-app
<span style="color: #9E9E9E"><strong>Et</strong></span> le compteur "Campagnes en attente" s'incrémente en temps réel
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux cliquer pour consulter immédiatement</p>
<hr />
<h2 id="25-statistiques-conformite-par-categorie">25. Statistiques conformité par catégorie</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis admin modération</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les statistiques mensuelles</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois les motifs de refus:</p>
<pre><code>| Motif | Nombre | Pourcentage |
|---|---|---|
| Alcool | 15 | 30% |
| Qualité audio | 12 | 24% |
| Classification erronée | 10 | 20% |
| Publicité mensongère | 8 | 16% |
| Autres | 5 | 10% |
</code></pre>
<hr />
<h2 id="26-export-rapport-moderation">26. Export rapport modération</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis modérateur senior</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'exporte le rapport mensuel</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un fichier CSV avec:</p>
<pre><code>| Colonne |
|---|
| Campagne ID |
| Publicitaire |
| Date soumission |
| Date décision |
| Statut |
| Motif (si refus) |
| Modérateur |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je peux l'analyser dans Excel</p>
<hr />
<h2 id="27-validation-partielle-avec-demande-modification">27. Validation partielle avec demande modification</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une campagne a un contenu acceptable
<span style="color: #F44336"><strong>Mais</strong></span> que la classification d'âge est incorrecte</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur clique sur "Demander modification"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le publicitaire reçoit un email:
<span style="color: #9E9E9E"><strong>Et</strong></span> le statut devient "Modification requise"
<span style="color: #9E9E9E"><strong>Et</strong></span> le publicitaire peut modifier sans repayer</p>
<hr />
<h2 id="28-plan-contenus-interdits-automatiquement-detectes">28. 📋 Plan: Contenus interdits automatiquement détectés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une publicité contient le mot <mot_cle></p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la transcription automatique est analysée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un flag <flag> est ajouté
<span style="color: #9E9E9E"><strong>Et</strong></span> le motif de refus suggéré est <motif></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>mot_cle</th>
<th>flag</th>
<th>motif</th>
</tr>
</thead>
<tbody>
<tr>
<td>whisky</td>
<td>⚠️ Alcool</td>
<td>Contenu interdit: Alcool</td>
</tr>
<tr>
<td>vodka</td>
<td>⚠️ Alcool</td>
<td>Contenu interdit: Alcool</td>
</tr>
<tr>
<td>cigarette</td>
<td>⚠️ Tabac</td>
<td>Contenu interdit: Tabac</td>
</tr>
<tr>
<td>casino</td>
<td>⚠️ Jeux argent</td>
<td>Contenu interdit: Jeux</td>
</tr>
<tr>
<td>paris sportifs</td>
<td>⚠️ Jeux argent</td>
<td>Contenu interdit: Jeux</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="29-plan-delais-de-validation-selon-soumission">29. 📋 Plan: Délais de validation selon soumission</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une campagne est soumise <jour> à <heure></p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> elle est validée <delai> heures plus tard</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le statut est <conformite></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>jour</th>
<th>heure</th>
<th>delai</th>
<th>conformite</th>
</tr>
</thead>
<tbody>
<tr>
<td>Lundi</td>
<td>10h</td>
<td>24</td>
<td>Dans les délais (24h)</td>
</tr>
<tr>
<td>Lundi</td>
<td>10h</td>
<td>48</td>
<td>Dans les délais (48h)</td>
</tr>
<tr>
<td>Lundi</td>
<td>10h</td>
<td>50</td>
<td>Hors délais (&gt;48h)</td>
</tr>
<tr>
<td>Vendredi</td>
<td>16h</td>
<td>72</td>
<td>Dans les délais (we)</td>
</tr>
</tbody>
</table>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="architecture-technique-radio-live">Architecture technique radio live</h1>
<blockquote>
<p><em>En tant que système</em>
<em>Je veux gérer efficacement les flux audio en temps réel</em>
<em>Afin d'assurer une diffusion stable et scalable des lives</em></p>
</blockquote>
<p><strong>24 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'infrastructure RoadWave est opérationnelle
<span style="color: #9E9E9E"><strong>Et</strong></span> que les serveurs Go avec Pion WebRTC sont actifs</p>
</blockquote>
<h2 id="1-ingestion-webrtc-du-flux-createur">1. Ingestion WebRTC du flux créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur démarre un live depuis son application mobile</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le flux audio WebRTC (Opus 48 kbps) arrive sur le serveur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le serveur Go avec Pion WebRTC accepte la connexion
<span style="color: #9E9E9E"><strong>Et</strong></span> le flux est traité en temps réel</p>
<hr />
<h2 id="2-conversion-temps-reel-opus-vers-segments-hls">2. Conversion temps réel Opus vers segments HLS</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un flux WebRTC Opus est reçu par le serveur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le serveur traite le flux</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> FFmpeg convertit en segments HLS (.ts)
<span style="color: #9E9E9E"><strong>Et</strong></span> un fichier manifest .m3u8 est généré et mis à jour régulièrement
<span style="color: #9E9E9E"><strong>Et</strong></span> les segments ont une durée de 2 secondes chacun</p>
<hr />
<h2 id="3-distribution-via-bunny-cdn">3. Distribution via Bunny CDN</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que les segments HLS sont générés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un auditeur demande à rejoindre le live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le manifest .m3u8 est servi via Bunny CDN
<span style="color: #9E9E9E"><strong>Et</strong></span> les segments .ts sont cachés sur le CDN
<span style="color: #9E9E9E"><strong>Et</strong></span> la distribution est globale avec latence minimale</p>
<hr />
<h2 id="4-lecture-hls-native-sur-mobile-ios">4. Lecture HLS native sur mobile iOS</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un auditeur iOS rejoint un live</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'application charge le flux HLS</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le player natif AVPlayer gère la lecture
<span style="color: #9E9E9E"><strong>Et</strong></span> le buffer de 15 secondes est appliqué automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> la qualité s'adapte selon la connexion</p>
<hr />
<h2 id="5-lecture-hls-native-sur-mobile-android">5. Lecture HLS native sur mobile Android</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un auditeur Android rejoint un live</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'application charge le flux HLS</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le player natif ExoPlayer gère la lecture
<span style="color: #9E9E9E"><strong>Et</strong></span> le buffer de 15 secondes est configuré
<span style="color: #9E9E9E"><strong>Et</strong></span> la qualité s'adapte selon la connexion</p>
<hr />
<h2 id="6-enregistrement-parallele-du-flux-pour-replay">6. Enregistrement parallèle du flux pour replay</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un live est en cours</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un processus parallèle enregistre le flux Opus raw
<span style="color: #9E9E9E"><strong>Et</strong></span> l'enregistrement est stocké temporairement sur le serveur
<span style="color: #9E9E9E"><strong>Et</strong></span> l'enregistrement est indépendant de la diffusion HLS</p>
<hr />
<h2 id="7-traitement-post-live-asynchrone">7. Traitement post-live asynchrone</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un live vient de se terminer</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le processus post-live démarre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un job asynchrone est créé dans la queue Redis
<span style="color: #9E9E9E"><strong>Et</strong></span> un worker Go récupère le job
<span style="color: #9E9E9E"><strong>Et</strong></span> le worker exécute FFmpeg pour les conversions</p>
<hr />
<h2 id="8-conversion-opus-raw-vers-mp3-256-kbps">8. Conversion Opus raw vers MP3 256 kbps</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un worker traite un job post-live</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la conversion démarre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> FFmpeg convertit Opus raw en MP3 256 kbps
<span style="color: #9E9E9E"><strong>Et</strong></span> la normalisation audio à -14 LUFS est appliquée
<span style="color: #9E9E9E"><strong>Et</strong></span> les silences prolongés (&gt;3 secondes) sont détectés et nettoyés</p>
<hr />
<h2 id="9-generation-segments-hls-pour-le-replay">9. Génération segments HLS pour le replay</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le MP3 256 kbps est généré</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le worker crée les segments HLS</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> des segments .ts de 10 secondes sont créés
<span style="color: #9E9E9E"><strong>Et</strong></span> un manifest .m3u8 est généré
<span style="color: #9E9E9E"><strong>Et</strong></span> les segments sont uploadés vers le stockage Bunny</p>
<hr />
<h2 id="10-publication-automatique-du-replay">10. Publication automatique du replay</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que tous les segments HLS sont uploadés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le worker finalise le job</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une entrée de contenu "replay" est créée en base PostgreSQL
<span style="color: #9E9E9E"><strong>Et</strong></span> le titre est "[REPLAY] [Titre live original]"
<span style="color: #9E9E9E"><strong>Et</strong></span> le type géographique est "Géo-neutre"
<span style="color: #9E9E9E"><strong>Et</strong></span> le replay est immédiatement disponible pour les auditeurs</p>
<hr />
<h2 id="11-suppression-automatique-fichier-opus-raw-apres-7-jours">11. Suppression automatique fichier Opus raw après 7 jours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un replay est publié depuis 7 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le job de nettoyage quotidien s'exécute</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le fichier Opus raw est supprimé du stockage
<span style="color: #9E9E9E"><strong>Et</strong></span> seul le MP3 256 kbps et les segments HLS sont conservés
<span style="color: #9E9E9E"><strong>Et</strong></span> l'espace de stockage est libéré</p>
<hr />
<h2 id="12-scalabilite-horizontale-des-workers-de-conversion">12. Scalabilité horizontale des workers de conversion</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 50 lives se terminent simultanément</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les jobs post-live sont créés</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les workers Go disponibles traitent les jobs en parallèle
<span style="color: #9E9E9E"><strong>Et</strong></span> si tous les workers sont occupés, les jobs attendent en queue Redis
<span style="color: #9E9E9E"><strong>Et</strong></span> de nouveaux workers peuvent être lancés automatiquement (Kubernetes)</p>
<hr />
<h2 id="13-limitation-du-nombre-de-lives-simultanes-mvp">13. Limitation du nombre de lives simultanés (MVP)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'infrastructure MVP est configurée pour 100 lives simultanés
<span style="color: #9E9E9E"><strong>Et</strong></span> que 100 lives sont actuellement en cours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un nouveau créateur essaie de démarrer un live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la demande est refusée avec le code erreur 503
<span style="color: #9E9E9E"><strong>Et</strong></span> le message "Capacité maximale atteinte. Veuillez réessayer dans quelques minutes" est retourné
<span style="color: #9E9E9E"><strong>Et</strong></span> la demande peut être mise en queue prioritaire si créateur Premium</p>
<hr />
<h2 id="14-monitoring-des-ressources-serveur-en-temps-reel">14. Monitoring des ressources serveur en temps réel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que plusieurs lives sont en cours</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système monitore en temps réel:</p>
<pre><code>| métrique | seuil alerte |
|---|---|
| CPU utilisation | &gt;80% |
| Mémoire utilisation | &gt;85% |
| Bande passante upload | &gt;80% capacité |
| Nombre connexions WebRTC | &gt;90 |
| Latence moyenne CDN | &gt;200ms |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> si un seuil est dépassé, une alerte est envoyée à l'équipe technique</p>
<hr />
<h2 id="15-calcul-du-cout-de-bande-passante-cdn">15. Calcul du coût de bande passante CDN</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un live a 100 auditeurs simultanés
<span style="color: #9E9E9E"><strong>Et</strong></span> que la qualité est 48 kbps Opus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le live dure 1 heure</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la bande passante totale est d'environ 2.16 GB
<span style="color: #9E9E9E"><strong>Et</strong></span> le coût Bunny CDN est d'environ 0.02€ (tarif ~0.01€/GB)
<span style="color: #9E9E9E"><strong>Et</strong></span> ces métriques sont enregistrées pour facturation créateur si nécessaire</p>
<hr />
<h2 id="16-cache-cdn-des-segments-hls">16. Cache CDN des segments HLS</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un live est diffusé via Bunny CDN</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un segment .ts est généré</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le segment est uploadé vers Bunny origin
<span style="color: #9E9E9E"><strong>Et</strong></span> Bunny CDN cache le segment sur ses edge servers
<span style="color: #9E9E9E"><strong>Et</strong></span> les auditeurs suivants récupèrent le segment depuis le cache
<span style="color: #9E9E9E"><strong>Et</strong></span> la charge sur le serveur origin est réduite de ~90%</p>
<hr />
<h2 id="17-gestion-de-la-latence-webrtc-createur">17. Gestion de la latence WebRTC créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur diffuse avec une connexion 4G</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la latence réseau augmente ponctuellement</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le buffer côté serveur absorbe les fluctuations
<span style="color: #9E9E9E"><strong>Et</strong></span> la qualité peut être réduite temporairement (48 kbps → 32 kbps)
<span style="color: #9E9E9E"><strong>Et</strong></span> un warning est affiché au créateur si la connexion est trop instable</p>
<hr />
<h2 id="18-detection-automatique-de-la-musique-protegee-post-mvp">18. Détection automatique de la musique protégée (post-MVP)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un live contient de la musique en arrière-plan</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système d'audio fingerprint analyse le flux</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une empreinte audio est calculée toutes les 30 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> l'empreinte est comparée à une base de données de contenus protégés
<span style="color: #9E9E9E"><strong>Et</strong></span> si une correspondance est trouvée, un warning est envoyé au créateur
<span style="color: #9E9E9E"><strong>Et</strong></span> si le créateur ne corrige pas sous 30 secondes, le live peut être arrêté</p>
<hr />
<h2 id="19-stockage-des-metadonnees-de-live-en-postgresql">19. Stockage des métadonnées de live en PostgreSQL</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un créateur démarre un live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les métadonnées suivantes sont enregistrées:</p>
<pre><code>| 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" |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> ces données sont indexées pour recherche et analytics</p>
<hr />
<h2 id="20-cache-redis-pour-compteurs-temps-reel">20. Cache Redis pour compteurs temps réel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un live est en cours</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Redis stocke les compteurs temps réel:</p>
<pre><code>| clé Redis | valeur exemple |
|---|---|
| live:[live_id]:listeners | 247 |
| live:[live_id]:likes | 89 |
| live:[live_id]:reports | 0 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> ces compteurs sont mis à jour toutes les 2 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> les compteurs sont persistés en PostgreSQL toutes les 60 secondes</p>
<hr />
<h2 id="21-heartbeat-auditeurs-pour-compteur-precis">21. Heartbeat auditeurs pour compteur précis</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un auditeur écoute un live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'application envoie un heartbeat toutes les 10 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> le heartbeat met à jour le timestamp dans Redis
<span style="color: #9E9E9E"><strong>Et</strong></span> si aucun heartbeat n'est reçu pendant 30 secondes, l'auditeur est retiré du compteur</p>
<hr />
<h2 id="22-gestion-des-pannes-serveur-pendant-un-live">22. Gestion des pannes serveur pendant un live</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un live est en cours sur serveur A</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le serveur A tombe en panne</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Kubernetes redémarre automatiquement un pod
<span style="color: #F44336"><strong>Mais</strong></span> le live en cours est perdu (pas de failover temps réel en MVP)
<span style="color: #9E9E9E"><strong>Et</strong></span> le créateur voit le message "Connexion perdue. Veuillez redémarrer le live"
<span style="color: #9E9E9E"><strong>Et</strong></span> les auditeurs voient "Le live est terminé suite à un problème technique"</p>
<hr />
<h2 id="23-backup-automatique-des-enregistrements-live">23. Backup automatique des enregistrements live</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un live est enregistré en Opus raw</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'enregistrement dépasse 10 minutes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un backup incrémental est créé toutes les 10 minutes
<span style="color: #9E9E9E"><strong>Et</strong></span> le backup est stocké sur un stockage secondaire (S3-compatible)
<span style="color: #9E9E9E"><strong>Et</strong></span> en cas de crash serveur, le live peut être récupéré jusqu'au dernier backup</p>
<hr />
<h2 id="24-logs-et-audit-trail-des-lives">24. Logs et audit trail des lives</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un live démarre, se déroule et se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les événements sont loggés:</p>
<pre><code>| é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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> ces logs sont conservés 90 jours pour analytics et conformité RGPD</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="arret-du-live">Arrêt du live</h1>
<blockquote>
<p><em>En tant que créateur</em>
<em>Je veux arrêter ma diffusion en direct de manière contrôlée</em>
<em>Afin de terminer proprement mon live et générer un replay automatiquement</em></p>
</blockquote>
<p><strong>19 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis connecté en tant que créateur
<span style="color: #9E9E9E"><strong>Et</strong></span> que je diffuse actuellement un live</p>
</blockquote>
<h2 id="1-arret-manuel-avec-compte-a-rebours-5-secondes">1. Arrêt manuel avec compte à rebours 5 secondes</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur le bouton "Arrêter live"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un compte à rebours de 5 secondes démarre
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Ce live se termine dans 5... 4... 3... 2... 1"
<span style="color: #9E9E9E"><strong>Et</strong></span> un bouton "Annuler" est affiché pendant le décompte
<span style="color: #9E9E9E"><strong>Et</strong></span> l'audio du compte à rebours est diffusé aux auditeurs</p>
<hr />
<h2 id="2-annulation-du-compte-a-rebours">2. Annulation du compte à rebours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai appuyé sur "Arrêter live"
<span style="color: #9E9E9E"><strong>Et</strong></span> que le compte à rebours affiche "3 secondes"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Annuler"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le compte à rebours s'arrête
<span style="color: #9E9E9E"><strong>Et</strong></span> le live continue normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune notification n'est envoyée aux auditeurs</p>
<hr />
<h2 id="3-arret-effectif-apres-compte-a-rebours">3. Arrêt effectif après compte à rebours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le compte à rebours est à 0</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le live s'arrête
<span style="color: #9E9E9E"><strong>Et</strong></span> la diffusion aux auditeurs se termine
<span style="color: #9E9E9E"><strong>Et</strong></span> le message "Live terminé" s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> le processus de traitement post-live démarre automatiquement</p>
<hr />
<h2 id="4-deconnexion-createur-courte-moins-de-60-secondes">4. Déconnexion créateur courte (moins de 60 secondes)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je diffuse un live</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> ma connexion est perdue pendant 30 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les auditeurs voient le message "Connexion créateur perdue, reconnexion en cours..."
<span style="color: #9E9E9E"><strong>Et</strong></span> le live continue de bufferer
<span style="color: #9E9E9E"><strong>Et</strong></span> quand ma connexion revient, le live reprend normalement</p>
<hr />
<h2 id="5-deconnexion-createur-longue-60-secondes-ou-plus">5. Déconnexion créateur longue (60 secondes ou plus)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je diffuse un live</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> ma connexion est perdue pendant 60 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le live s'arrête automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> les auditeurs voient le message "Le live est terminé suite à une coupure de connexion"
<span style="color: #9E9E9E"><strong>Et</strong></span> le processus de traitement post-live démarre</p>
<hr />
<h2 id="6-enregistrement-automatique-pendant-le-live">6. Enregistrement automatique pendant le live</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je diffuse un live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon flux audio est enregistré en continu
<span style="color: #9E9E9E"><strong>Et</strong></span> le format d'enregistrement est Opus raw
<span style="color: #9E9E9E"><strong>Et</strong></span> l'enregistrement est stocké temporairement sur le serveur</p>
<hr />
<h2 id="7-generation-automatique-du-replay-apres-arret">7. Génération automatique du replay après arrêt</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon live vient de se terminer
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'option "Publier replay automatiquement" est activée (par défaut)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le traitement post-live démarre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un job asynchrone est créé
<span style="color: #9E9E9E"><strong>Et</strong></span> le job effectue les opérations suivantes:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="8-publication-du-replay">8. Publication du replay</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le traitement post-live est terminé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le replay est publié automatiquement sous 5 à 10 minutes
<span style="color: #9E9E9E"><strong>Et</strong></span> le titre est "[REPLAY] [Titre live original]"
<span style="color: #9E9E9E"><strong>Et</strong></span> la zone de diffusion est la même que le live
<span style="color: #9E9E9E"><strong>Et</strong></span> les tags sont identiques au live
<span style="color: #9E9E9E"><strong>Et</strong></span> la classification d'âge est identique
<span style="color: #9E9E9E"><strong>Et</strong></span> le type géographique est "Géo-neutre" (contenu pérenne)</p>
<hr />
<h2 id="9-notification-de-disponibilite-du-replay-aux-auditeurs">9. Notification de disponibilité du replay aux auditeurs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le replay de mon live est publié</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un auditeur qui a écouté le live se reconnecte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit une notification in-app "Le replay de [Titre] est disponible"</p>
<hr />
<h2 id="10-option-desactivation-publication-automatique-replay">10. Option désactivation publication automatique replay</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je configure un nouveau live</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je désactive l'option "Publier replay automatiquement"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je démarre puis arrête le live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le live est enregistré
<span style="color: #F44336"><strong>Mais</strong></span> le replay n'est pas publié automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux décider manuellement de le publier plus tard</p>
<hr />
<h2 id="11-suppression-manuelle-du-replay-apres-publication">11. Suppression manuelle du replay après publication</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon live a généré un replay publié</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à mes contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois le replay dans ma liste
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux le supprimer comme n'importe quel contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je supprime le replay</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le fichier source Opus raw est supprimé immédiatement</p>
<hr />
<h2 id="12-conservation-fichier-source-opus-raw">12. Conservation fichier source Opus raw</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon live est terminé
<span style="color: #9E9E9E"><strong>Et</strong></span> que le replay est publié</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le fichier Opus raw est conservé pendant 7 jours
<span style="color: #9E9E9E"><strong>Et</strong></span> après 7 jours, le fichier raw est supprimé automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> seul le MP3 256 kbps est conservé</p>
<hr />
<h2 id="13-modification-du-replay-interdite">13. Modification du replay interdite</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon live a généré un replay publié</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de modifier l'audio du replay</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'action est refusée
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Les replays ne peuvent pas être modifiés pour garantir l'intégrité de l'enregistrement"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux uniquement modifier les métadonnées (titre, description)</p>
<hr />
<h2 id="14-statistiques-du-live-disponibles-apres-arret">14. Statistiques du live disponibles après arrêt</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon live est terminé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède aux statistiques</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="15-live-termine-avec-signalements-en-cours">15. Live terminé avec signalements en cours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon live a reçu 3 signalements pendant la diffusion</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le live se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le replay n'est pas publié automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu est en attente de modération
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Votre replay sera publié après vérification suite aux signalements reçus"
<span style="color: #9E9E9E"><strong>Et</strong></span> un modérateur doit valider ou refuser le replay sous 24h</p>
<hr />
<h2 id="16-arret-force-par-un-moderateur">16. Arrêt forcé par un modérateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je diffuse un live
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un modérateur détecte du contenu interdit</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur clique sur "Arrêter le live immédiatement"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le live s'arrête sans compte à rebours
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Votre live a été interrompu par la modération"
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois une notification détaillant la raison
<span style="color: #9E9E9E"><strong>Et</strong></span> le replay n'est pas publié
<span style="color: #9E9E9E"><strong>Et</strong></span> le fichier source est conservé 30 jours pour appel</p>
<hr />
<h2 id="17-metriques-de-bande-passante-pendant-le-live">17. Métriques de bande passante pendant le live</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je diffuse un live
<span style="color: #9E9E9E"><strong>Et</strong></span> que 100 auditeurs écoutent simultanément</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la bande passante consommée est d'environ 4.8 Mbps via CDN
<span style="color: #9E9E9E"><strong>Et</strong></span> le coût estimé Bunny CDN est d'environ 0.02€ par heure de diffusion
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux voir ces métriques en temps réel dans l'interface créateur</p>
<hr />
<h2 id="18-live-sans-auditeurs-pendant-5-minutes">18. Live sans auditeurs pendant 5 minutes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je diffuse un live
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'aucun auditeur n'écoute depuis 5 minutes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois un message d'information "Aucun auditeur actuellement connecté"
<span style="color: #F44336"><strong>Mais</strong></span> le live continue normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux choisir de continuer ou d'arrêter</p>
<hr />
<h2 id="19-qualite-audio-du-replay-superieure-au-live">19. Qualité audio du replay supérieure au live</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon live était diffusé en Opus 48 kbps</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le replay est généré</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le replay est encodé en MP3 256 kbps
<span style="color: #9E9E9E"><strong>Et</strong></span> la qualité audio du replay est supérieure au live
<span style="color: #9E9E9E"><strong>Et</strong></span> la taille du fichier est optimisée pour le stockage long terme</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="comportement-auditeur-pendant-un-live">Comportement auditeur pendant un live</h1>
<blockquote>
<p><em>En tant qu'auditeur</em>
<em>Je veux écouter des lives de manière stable</em>
<em>Afin de profiter du contenu en temps réel sans coupures</em></p>
</blockquote>
<p><strong>27 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis connecté en tant qu'auditeur
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un créateur diffuse actuellement un live</p>
</blockquote>
<h2 id="1-rejoindre-un-live-avec-buffer-de-synchronisation-15-secondes">1. Rejoindre un live avec buffer de synchronisation 15 secondes</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Rejoindre le live"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la connexion au flux HLS s'établit
<span style="color: #9E9E9E"><strong>Et</strong></span> je commence à écouter avec un décalage de 15 secondes par rapport au créateur
<span style="color: #9E9E9E"><strong>Et</strong></span> le buffer de 15 secondes garantit une lecture stable</p>
<hr />
<h2 id="2-justification-du-buffer-15-secondes">2. Justification du buffer 15 secondes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> les alternatives de buffer possibles:</p>
<pre><code>| 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 | ❌ |
</code></pre>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le buffer optimal est 15 secondes</p>
<hr />
<h2 id="3-lecture-stable-sur-reseau-3g">3. Lecture stable sur réseau 3G</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis sur réseau 3G
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'écoute un live</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> des micro-coupures réseau surviennent</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le buffer de 15 secondes absorbe les coupures
<span style="color: #9E9E9E"><strong>Et</strong></span> la lecture continue sans interruption perceptible</p>
<hr />
<h2 id="4-lecture-stable-sur-reseau-4g">4. Lecture stable sur réseau 4G</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis sur réseau 4G
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'écoute un live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la lecture est fluide
<span style="color: #9E9E9E"><strong>Et</strong></span> le buffer de 15 secondes prévient les coupures lors de changement de cellule</p>
<hr />
<h2 id="5-continuation-du-live-en-sortant-de-la-zone-geographique">5. Continuation du live en sortant de la zone géographique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un live régional "Île-de-France"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis situé en Île-de-France</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me déplace et sors du département</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le live continue de jouer normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux écouter jusqu'à la fin naturelle du live
<span style="color: #9E9E9E"><strong>Et</strong></span> après la fin du live, l'algorithme propose du contenu correspondant à ma nouvelle position</p>
<hr />
<h2 id="6-abonne-dans-la-zone-recoit-notification-push">6. Abonné dans la zone reçoit notification push</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné au créateur "JeanDupont"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis situé en Île-de-France</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> "JeanDupont" démarre un live en Île-de-France</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification push "🔴 JeanDupont est en direct : [Titre du live]"
<span style="color: #9E9E9E"><strong>Et</strong></span> quand je tape sur la notification, l'app s'ouvre et le live démarre immédiatement</p>
<hr />
<h2 id="7-abonne-hors-zone-ne-recoit-pas-de-notification">7. Abonné hors zone ne reçoit pas de notification</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis abonné au créateur "JeanDupont"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis situé à Lyon</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> "JeanDupont" démarre un live en Île-de-France</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je ne reçois pas de notification push
<span style="color: #9E9E9E"><strong>Et</strong></span> cela évite la frustration de ne pas pouvoir écouter un live hors zone</p>
<hr />
<h2 id="8-decouverte-dun-live-via-lalgorithme-de-recommandation">8. Découverte d'un live via l'algorithme de recommandation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis dans la zone géographique du live
<span style="color: #9E9E9E"><strong>Et</strong></span> que je navigue dans l'app avec "Suivant"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme propose un live en cours</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois l'indicateur "🔴 EN DIRECT"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux choisir de le rejoindre ou de passer au suivant</p>
<hr />
<h2 id="9-reconnexion-rapide-apres-coupure-reseau-moins-de-90-secondes">9. Reconnexion rapide après coupure réseau (moins de 90 secondes)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un live</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je perds ma connexion réseau pendant 45 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> que je retrouve ma connexion</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reprends le live au moment actuel (pas au buffer ancien)
<span style="color: #9E9E9E"><strong>Et</strong></span> le saut temporel est transparent (pas de message d'erreur)
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne rate que quelques secondes de contenu</p>
<hr />
<h2 id="10-reconnexion-longue-apres-coupure-reseau-90-secondes-ou-plus">10. Reconnexion longue après coupure réseau (90 secondes ou plus)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un live</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je perds ma connexion réseau pendant 90 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> que je retrouve ma connexion</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois le message "Live en cours perdu, passage au contenu suivant"
<span style="color: #9E9E9E"><strong>Et</strong></span> l'algorithme propose automatiquement le contenu suivant
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux manuellement revenir au live s'il est toujours en cours</p>
<hr />
<h2 id="11-interactions-disponibles-pendant-le-live-like">11. Interactions disponibles pendant le live - Like</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un live
<span style="color: #9E9E9E"><strong>Et</strong></span> que mon véhicule est à l'arrêt</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur le bouton "❤️ Like"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le like est enregistré immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> le compteur de likes visible par le créateur s'incrémente
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge d'intérêt pour les tags du live augmente de +2%</p>
<hr />
<h2 id="12-interactions-disponibles-pendant-le-live-abonnement">12. Interactions disponibles pendant le live - Abonnement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un live
<span style="color: #9E9E9E"><strong>Et</strong></span> que je ne suis pas encore abonné au créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur le bouton "S'abonner"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je m'abonne au créateur
<span style="color: #9E9E9E"><strong>Et</strong></span> ma jauge d'intérêt pour tous les tags du créateur augmente de +5%
<span style="color: #9E9E9E"><strong>Et</strong></span> je recevrai des notifications pour ses prochains lives</p>
<hr />
<h2 id="13-interactions-disponibles-pendant-le-live-skip">13. Interactions disponibles pendant le live - Skip</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un live</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Suivant" (ou commande au volant)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je quitte le live immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> l'algorithme propose le contenu suivant
<span style="color: #9E9E9E"><strong>Et</strong></span> si j'ai écouté moins de 10 secondes, ma jauge d'intérêt diminue de -0.5%</p>
<hr />
<h2 id="14-commande-precedent-desactivee-pendant-un-live">14. Commande Précédent désactivée pendant un live</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un live</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Précédent" (ou commande au volant)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> rien ne se passe
<span style="color: #9E9E9E"><strong>Et</strong></span> un message d'information s'affiche brièvement "Précédent non disponible sur les lives"</p>
<hr />
<h2 id="15-chat-en-direct-desactive-decision-definitive">15. Chat en direct désactivé (décision définitive)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune interface de chat n'est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne peux pas envoyer de messages au créateur
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne peux pas voir de messages d'autres auditeurs
<span style="color: #9E9E9E"><strong>Et</strong></span> cette fonctionnalité ne sera jamais implémentée</p>
<hr />
<h2 id="16-reactions-emoji-desactivees-decision-definitive">16. Réactions emoji désactivées (décision définitive)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune réaction emoji n'est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne peux pas envoyer d'emoji en temps réel
<span style="color: #9E9E9E"><strong>Et</strong></span> cette fonctionnalité ne sera jamais implémentée</p>
<hr />
<h2 id="17-message-dinformation-sur-labsence-de-chat">17. Message d'information sur l'absence de chat</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute mon premier live</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à l'interface du live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 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."
<span style="color: #9E9E9E"><strong>Et</strong></span> ce bandeau n'apparaît qu'une seule fois (première expérience)</p>
<hr />
<h2 id="18-signalement-dun-live-en-cours">18. Signalement d'un live en cours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un live
<span style="color: #9E9E9E"><strong>Et</strong></span> que le contenu me semble inapproprié</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur le bouton "Signaler"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois les catégories de signalement:</p>
<pre><code>| catégorie |
|---|
| Haine et violence |
| Contenu sexuel |
| Illégalité |
| Droits d'auteur |
| Désinformation dangereuse |
| Harcèlement |
| Autre |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> quand je sélectionne une catégorie
<span style="color: #4CAF50"><strong>Alors</strong></span> le signalement est envoyé en priorité selon la catégorie
<span style="color: #9E9E9E"><strong>Et</strong></span> un modérateur peut écouter le live en temps réel si besoin</p>
<hr />
<h2 id="19-statistiques-visibles-par-les-auditeurs-pendant-le-live">19. Statistiques visibles par les auditeurs pendant le live</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un live</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les informations du live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| 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é |
</code></pre>
<p><span style="color: #F44336"><strong>Mais</strong></span> je ne vois pas les likes ou autres métriques détaillées</p>
<hr />
<h2 id="20-compteur-dauditeurs-arrondi-pour-preserver-la-vie-privee">20. Compteur d'auditeurs arrondi pour préserver la vie privée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un live avec exactement 247 auditeurs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte le nombre d'auditeurs</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois "~250 auditeurs" (arrondi à la dizaine supérieure)</p>
<hr />
<h2 id="21-qualite-audio-adaptative-pendant-le-live">21. Qualité audio adaptative pendant le live</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un live</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> ma connexion passe de 4G à 3G</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la qualité audio s'adapte automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> je passe de 48 kbps à 24 kbps Opus
<span style="color: #9E9E9E"><strong>Et</strong></span> la transition est transparente sans coupure</p>
<hr />
<h2 id="22-consommation-de-donnees-pendant-un-live">22. Consommation de données pendant un live</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un live en qualité standard 48 kbps
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'écoute pendant 1 heure</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> j'ai consommé environ 21.6 MB de données mobiles
<span style="color: #9E9E9E"><strong>Et</strong></span> cette consommation est affichée dans les paramètres de l'app</p>
<hr />
<h2 id="23-lecture-du-replay-apres-la-fin-du-live">23. Lecture du replay après la fin du live</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un live depuis 30 minutes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le créateur arrête le live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois le message "Le live est terminé. Le replay sera disponible dans quelques minutes"
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu suivant est automatiquement proposé après 2 secondes</p>
<hr />
<h2 id="24-notification-de-disponibilite-du-replay">24. Notification de disponibilité du replay</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai écouté un live jusqu'à la fin
<span style="color: #9E9E9E"><strong>Et</strong></span> que le replay est publié 8 minutes plus tard</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je rouvre l'application</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois une notification in-app "Le replay de [Titre] est maintenant disponible"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux cliquer pour l'écouter immédiatement</p>
<hr />
<h2 id="25-aucune-publicite-pendant-un-live-pour-utilisateurs-gratuits">25. Aucune publicité pendant un live pour utilisateurs gratuits</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur gratuit
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'écoute un live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune publicité n'est insérée pendant le live
<span style="color: #9E9E9E"><strong>Et</strong></span> la publicité apparaît seulement entre le live et le contenu suivant</p>
<hr />
<h2 id="26-detection-de-contexte-voiture-pendant-un-live">26. Détection de contexte voiture pendant un live</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un live
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma vitesse est supérieure à 10 km/h</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'interface tactile est désactivée pour la sécurité
<span style="color: #9E9E9E"><strong>Et</strong></span> seules les commandes au volant sont actives (Play/Pause/Suivant)</p>
<hr />
<h2 id="27-detection-de-contexte-pieton-pendant-un-live">27. Détection de contexte piéton pendant un live</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un live
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma vitesse est inférieure à 5 km/h</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'interface tactile complète est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux liker, m'abonner, signaler via l'écran tactile</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="demarrage-dun-live">Démarrage d'un live</h1>
<blockquote>
<p><em>En tant que créateur</em>
<em>Je veux démarrer une diffusion en direct</em>
<em>Afin de partager du contenu audio en temps réel avec mes auditeurs</em></p>
</blockquote>
<p><strong>20 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis connecté en tant que créateur vérifié
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai les permissions de diffusion live</p>
</blockquote>
<h2 id="1-verifications-pre-live-reussies">1. Vérifications pré-live réussies</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma connexion upload est supérieure à 1 Mbps
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai autorisé l'accès au microphone
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai défini une zone de diffusion "Île-de-France"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je lance les vérifications pré-live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> toutes les vérifications sont validées
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux démarrer le live</p>
<hr />
<h2 id="2-echec-pre-live-avec-connexion-insuffisante">2. Échec pré-live avec connexion insuffisante</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma connexion upload est de 0.5 Mbps</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je lance les vérifications pré-live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois un warning "Connexion insuffisante pour garantir une diffusion stable (minimum 1 Mbps)"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux choisir de continuer quand même ou d'annuler</p>
<hr />
<h2 id="3-echec-pre-live-sans-autorisation-microphone">3. Échec pré-live sans autorisation microphone</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je n'ai pas autorisé l'accès au microphone</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de démarrer un live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois le message "Accès au microphone requis pour démarrer un live"
<span style="color: #9E9E9E"><strong>Et</strong></span> je suis redirigé vers les paramètres système</p>
<hr />
<h2 id="4-echec-pre-live-sans-zone-de-diffusion-definie">4. Échec pré-live sans zone de diffusion définie</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je n'ai pas défini de zone de diffusion</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de démarrer un live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois le message "Veuillez définir une zone de diffusion avant de démarrer"
<span style="color: #9E9E9E"><strong>Et</strong></span> je suis redirigé vers le formulaire de configuration du live</p>
<hr />
<h2 id="5-demarrage-live-avec-buffer-15-secondes">5. Démarrage live avec buffer 15 secondes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que toutes les vérifications pré-live sont validées</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Démarrer live"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois le message "Live démarre dans 15s... Testez votre micro"
<span style="color: #9E9E9E"><strong>Et</strong></span> un compte à rebours de 15 secondes s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> mon flux audio est enregistré pendant ces 15 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> le live n'est pas encore visible publiquement</p>
<hr />
<h2 id="6-live-devient-public-apres-buffer-initial">6. Live devient public après buffer initial</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai démarré un live
<span style="color: #9E9E9E"><strong>Et</strong></span> que le buffer de 15 secondes s'est écoulé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le live devient public
<span style="color: #9E9E9E"><strong>Et</strong></span> les auditeurs peuvent le rejoindre
<span style="color: #9E9E9E"><strong>Et</strong></span> les abonnés dans la zone reçoivent une notification push</p>
<hr />
<h2 id="7-notification-push-aux-abonnes-dans-la-zone-geographique">7. Notification push aux abonnés dans la zone géographique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai 1000 abonnés au total
<span style="color: #9E9E9E"><strong>Et</strong></span> que 300 abonnés sont situés en Île-de-France
<span style="color: #9E9E9E"><strong>Et</strong></span> que 700 abonnés sont situés hors Île-de-France</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> mon live en Île-de-France devient public</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 300 abonnés reçoivent une notification push "🔴 [Mon pseudo] est en direct : [Titre live]"
<span style="color: #9E9E9E"><strong>Et</strong></span> 700 abonnés ne reçoivent pas de notification</p>
<hr />
<h2 id="8-configuration-metadonnees-obligatoires-pour-un-live">8. Configuration métadonnées obligatoires pour un live</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je configure un nouveau live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je dois renseigner:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="9-validation-echouee-avec-titre-trop-court">9. Validation échouée avec titre trop court</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de créer un live avec le titre "Live"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la validation échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Le titre doit contenir entre 5 et 100 caractères"</p>
<hr />
<h2 id="10-validation-echouee-sans-tags">10. Validation échouée sans tags</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai rempli tous les champs sauf les tags</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de démarrer le live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la validation échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Veuillez sélectionner entre 1 et 3 centres d'intérêt"</p>
<hr />
<h2 id="11-limite-de-duree-8-heures">11. Limite de durée 8 heures</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon live dure depuis 7 heures et 30 minutes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois un warning "Votre live se terminera dans 30 min"
<span style="color: #9E9E9E"><strong>Et</strong></span> le message est affiché de manière non intrusive</p>
<hr />
<h2 id="12-arret-automatique-a-8-heures">12. Arrêt automatique à 8 heures</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon live dure depuis 8 heures</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le live s'arrête automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Durée maximale atteinte (8 heures). Vous pouvez redémarrer un nouveau live si nécessaire"
<span style="color: #9E9E9E"><strong>Et</strong></span> le processus de traitement post-live démarre</p>
<hr />
<h2 id="13-diffusion-contenu-interdit-concert-en-direct">13. Diffusion contenu interdit - Concert en direct</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je diffuse un concert en direct depuis une salle
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un auditeur signale le contenu pour "Violation droits d'auteur"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un modérateur écoute le live
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il confirme la violation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le live est arrêté immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un Strike 2 (suspension 7 jours)
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Votre live a été interrompu pour violation des droits d'auteur"
<span style="color: #9E9E9E"><strong>Et</strong></span> le replay n'est pas publié</p>
<hr />
<h2 id="14-diffusion-contenu-interdit-evenement-sportif-payant">14. Diffusion contenu interdit - Événement sportif payant</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je diffuse un match de football avec droits TV
<span style="color: #9E9E9E"><strong>Et</strong></span> que le contenu est détecté par l'IA audio fingerprint</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la détection est confirmée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le live est arrêté immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un Strike 2 (suspension 7 jours)</p>
<hr />
<h2 id="15-diffusion-contenu-violent">15. Diffusion contenu violent</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je diffuse du contenu violent (agression physique)
<span style="color: #9E9E9E"><strong>Et</strong></span> que 5 auditeurs signalent le contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un modérateur vérifie en temps réel
<span style="color: #9E9E9E"><strong>Et</strong></span> confirme la violence</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le live est coupé immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> mon compte est banni définitivement
<span style="color: #9E9E9E"><strong>Et</strong></span> les autorités sont notifiées</p>
<hr />
<h2 id="16-detection-musique-protegee-en-arriere-plan">16. Détection musique protégée en arrière-plan</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon live contient de la musique protégée en fond</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'IA audio fingerprint détecte la violation après 2 minutes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 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"
<span style="color: #9E9E9E"><strong>Et</strong></span> j'ai 30 secondes pour corriger
<span style="color: #9E9E9E"><strong>Et</strong></span> si je ne corrige pas, le live est arrêté avec Strike 1</p>
<hr />
<h2 id="17-signalement-pendant-un-live">17. Signalement pendant un live</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je diffuse un live
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un auditeur clique sur "Signaler"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'auditeur sélectionne la catégorie "Harcèlement"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le signalement est envoyé en priorité HAUTE
<span style="color: #9E9E9E"><strong>Et</strong></span> un modérateur peut écouter le live en temps réel
<span style="color: #9E9E9E"><strong>Et</strong></span> le live continue pendant l'écoute de vérification</p>
<hr />
<h2 id="18-depassement-nombre-de-lives-simultanes-autorises-limite-plateforme">18. Dépassement nombre de lives simultanés autorisés (limite plateforme)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la plateforme héberge actuellement 2000 lives simultanés
<span style="color: #9E9E9E"><strong>Et</strong></span> que c'est la limite de l'infrastructure actuelle</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de démarrer un nouveau live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois le message "Capacité maximale atteinte. Veuillez réessayer dans quelques minutes"
<span style="color: #9E9E9E"><strong>Et</strong></span> ma demande est mise en file d'attente prioritaire si je suis créateur Premium</p>
<hr />
<h2 id="19-premier-live-dun-nouveau-createur">19. Premier live d'un nouveau créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je n'ai jamais diffusé de live auparavant
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai moins de 3 contenus validés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de démarrer mon premier live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois le message "Les lives sont disponibles après validation de vos 3 premiers contenus"
<span style="color: #9E9E9E"><strong>Et</strong></span> le bouton "Démarrer live" est désactivé</p>
<hr />
<h2 id="20-createur-avec-score-de-confiance-faible">20. Créateur avec score de confiance faible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai 2 strikes actifs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de démarrer un live</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois le message "Fonctionnalité live temporairement indisponible suite à vos sanctions"
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois attendre la fin de ma suspension</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="recherche-de-contenu">Recherche de contenu</h1>
<blockquote>
<p><em>En tant qu'utilisateur de RoadWave</em>
<em>Je veux rechercher des contenus audio par mots-clés, localisation et filtres</em>
<em>Afin de trouver facilement le contenu qui m'intéresse</em></p>
</blockquote>
<p><strong>55 scénarios</strong> (49 standards, 6 plans)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'application RoadWave est démarrée
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur "jean@example.com" est connecté</p>
</blockquote>
<h2 id="1-recherche-full-text-basique">1. Recherche full-text basique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la base contient les contenus suivants:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur recherche "paris"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 2 résultats sont retournés
<span style="color: #9E9E9E"><strong>Et</strong></span> les résultats incluent "Balade à Paris"
<span style="color: #9E9E9E"><strong>Et</strong></span> les résultats incluent "Secrets de Montmartre"</p>
<hr />
<h2 id="2-recherche-avec-stemming-francais">2. Recherche avec stemming français</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un contenu avec le titre "Voyage en Bretagne"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur recherche "voyages"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu "Voyage en Bretagne" est trouvé
<span style="color: #9E9E9E"><strong>Et</strong></span> le stemming a transformé "voyages" en racine "voyag"</p>
<hr />
<h2 id="3-plan-stemming-francais-sur-differentes-formes">3. 📋 Plan: Stemming français sur différentes formes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un contenu avec le mot "<mot_original>"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur recherche "<recherche>"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est trouvé grâce au stemming français</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>mot_original</th>
<th>recherche</th>
</tr>
</thead>
<tbody>
<tr>
<td>voyage</td>
<td>voyages</td>
</tr>
<tr>
<td>voyager</td>
<td>voyage</td>
</tr>
<tr>
<td>balades</td>
<td>balade</td>
</tr>
<tr>
<td>historique</td>
<td>histoire</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="4-recherche-avec-accents-ignores">4. Recherche avec accents ignorés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un contenu avec le titre "Découverte de l'Élysée"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur recherche "decouverte elysee"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est trouvé
<span style="color: #9E9E9E"><strong>Et</strong></span> les accents sont normalisés automatiquement</p>
<hr />
<h2 id="5-champs-indexes-avec-ponderation">5. Champs indexés avec pondération</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> les contenus suivants:</p>
<pre><code>| titre | description | créateur | tags |
|---|---|---|---|
| Voyage Paris | Balade sympa | @user1 | Tourisme |
| Balade Lyon | Voyage en ville | @paris_guide | Voyage |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur recherche "paris"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> "Voyage Paris" est en première position
<span style="color: #9E9E9E"><strong>Et</strong></span> "@paris_guide" apparaît en second</p>
<hr />
<h2 id="6-ranking-par-pertinence-et-popularite">6. Ranking par pertinence et popularité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> les contenus suivants:</p>
<pre><code>| titre | écoutes | rang_texte |
|---|---|---|
| Balade Paris | 50000 | 0.8 |
| Paris la nuit | 1000 | 0.9 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur recherche "paris"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le score final combine rang_texte × (1 + log(écoutes + 1))
<span style="color: #9E9E9E"><strong>Et</strong></span> "Balade Paris" est mieux classé grâce à sa popularité</p>
<hr />
<h2 id="7-autocomplete-pendant-la-frappe">7. Autocomplete pendant la frappe</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur commence à taper "par"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> 3 caractères sont saisis</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> des suggestions apparaissent:</p>
<pre><code>| suggestion |
|---|
| paris |
| parc naturel |
| parvis notre-dame |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> le top 5 des suggestions est affiché</p>
<hr />
<h2 id="8-historique-des-10-dernieres-recherches">8. Historique des 10 dernières recherches</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur a effectué les recherches suivantes:</p>
<pre><code>| recherche | date |
|---|---|
| voyage paris | 2026-01-20 |
| audio-guide louvre | 2026-01-19 |
| podcast automobile | 2026-01-18 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur ouvre la barre de recherche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les 10 dernières recherches sont affichées
<span style="color: #9E9E9E"><strong>Et</strong></span> elles sont triées par date décroissante</p>
<hr />
<h2 id="9-correction-automatique-si-aucun-resultat">9. Correction automatique si aucun résultat</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur recherche "ballade paris" (faute d'orthographe)
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'aucun résultat n'est trouvé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la page de résultats s'affiche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une suggestion "Essayez plutôt : balade paris" est affichée</p>
<hr />
<h2 id="10-recherches-populaires-suggerees">10. Recherches populaires suggérées</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'aucun résultat n'est trouvé pour une recherche</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la page s'affiche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> des suggestions populaires sont affichées:</p>
<pre><code>| suggestion |
|---|
| balade paris |
| audio-guide louvre |
| visite montmartre |
</code></pre>
<hr />
<h2 id="11-saisie-dun-lieu-avec-autocomplete">11. Saisie d'un lieu avec autocomplete</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur ouvre le filtre "Lieu"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il tape "Louv"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Nominatim retourne des suggestions:</p>
<pre><code>| suggestion | type |
|---|---|
| Musée du Louvre, Paris | monument |
| Louvres, Val-d'Oise | commune |
</code></pre>
<hr />
<h2 id="12-selection-dun-lieu-et-definition-du-rayon">12. Sélection d'un lieu et définition du rayon</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur sélectionne "Paris, France"
<span style="color: #9E9E9E"><strong>Et</strong></span> que les coordonnées sont (48.8566, 2.3522)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il définit un rayon de 50 km</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la recherche PostGIS utilise ST_DWithin avec 50000 mètres</p>
<hr />
<h2 id="13-plan-recherche-geographique-avec-differents-rayons">13. 📋 Plan: Recherche géographique avec différents rayons</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un contenu à 30 km de Paris</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur recherche autour de Paris avec un rayon de <rayon></p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est <résultat></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>rayon</th>
<th>résultat</th>
</tr>
</thead>
<tbody>
<tr>
<td>20 km</td>
<td>non trouvé</td>
</tr>
<tr>
<td>50 km</td>
<td>trouvé</td>
</tr>
<tr>
<td>100 km</td>
<td>trouvé</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="14-utilisation-de-autour-de-moi-gps-actuel">14. Utilisation de "Autour de moi" (GPS actuel)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur active le GPS
<span style="color: #9E9E9E"><strong>Et</strong></span> que sa position est (48.8566, 2.3522)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il sélectionne "Autour de moi"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la recherche utilise ses coordonnées GPS actuelles
<span style="color: #9E9E9E"><strong>Et</strong></span> un rayon par défaut de 10 km est appliqué</p>
<hr />
<h2 id="15-curseur-de-rayon-avec-limites">15. Curseur de rayon avec limites</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur ouvre le curseur de rayon</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il ajuste le curseur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les valeurs disponibles vont de 5 km à 500 km
<span style="color: #9E9E9E"><strong>Et</strong></span> la valeur s'affiche en temps réel "50 km"</p>
<hr />
<h2 id="16-affichage-de-la-distance-dans-les-resultats">16. Affichage de la distance dans les résultats</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> une recherche géographique autour de Paris
<span style="color: #9E9E9E"><strong>Et</strong></span> un contenu à 2.3 km de distance</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les résultats sont affichés</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la distance "À 2.3 km" est indiquée pour chaque résultat</p>
<hr />
<h2 id="17-plan-tri-par-proximite-geographique">17. 📋 Plan: Tri par proximité géographique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> des contenus à différentes distances de Paris:</p>
<pre><code>| contenu | distance |
|---|---|
| Louvre Guide | 0.5 km |
| Tour Eiffel | 2.0 km |
| Versailles | 20 km |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur trie par "Proximité"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les résultats sont affichés dans l'ordre:</p>
<pre><code>| position | contenu |
|---|---|
| 1 | Louvre Guide |
| 2 | Tour Eiffel |
| 3 | Versailles |
</code></pre>
<hr />
<h2 id="18-geocodage-avec-nominatim-mvp">18. Géocodage avec Nominatim (MVP)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'application est en phase MVP</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> une requête de géocodage est effectuée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'API publique Nominatim est utilisée
<span style="color: #9E9E9E"><strong>Et</strong></span> le rate limit de 1 req/s est respecté</p>
<hr />
<h2 id="19-geocodage-avec-fallback-mapbox">19. Géocodage avec fallback Mapbox</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que Nominatim ne retourne aucun résultat</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'application tente un fallback</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'API Mapbox Geocoding est utilisée
<span style="color: #9E9E9E"><strong>Et</strong></span> le coût de 0.50€ / 1000 requêtes est appliqué</p>
<hr />
<h2 id="20-ouverture-du-panneau-de-filtres">20. Ouverture du panneau de filtres</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur est sur la page de recherche</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il clique sur "Filtres"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un panneau latéral s'ouvre
<span style="color: #9E9E9E"><strong>Et</strong></span> 7 catégories de filtres sont affichées:</p>
<pre><code>| catégorie |
|---|
| Type de contenu |
| Durée |
| Classification âge |
| Géo-pertinence |
| Tags |
| Date de publication |
| Abonnement |
</code></pre>
<hr />
<h2 id="21-filtre-par-type-de-contenu-multi-selection">21. Filtre par type de contenu (multi-sélection)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur ouvre les filtres</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il sélectionne:</p>
<pre><code>| type |
|---|
| Contenu court |
| Audio-guide |
</code></pre>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seuls ces types de contenus sont recherchés
<span style="color: #9E9E9E"><strong>Et</strong></span> les podcasts et radios live sont exclus</p>
<hr />
<h2 id="22-plan-filtre-par-duree">22. 📋 Plan: Filtre par durée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un contenu de <durée> minutes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur filtre par "<tranche>"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est <résultat></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>durée</th>
<th>tranche</th>
<th>résultat</th>
</tr>
</thead>
<tbody>
<tr>
<td>3</td>
<td>&lt;5 min</td>
<td>trouvé</td>
</tr>
<tr>
<td>3</td>
<td>5-15 min</td>
<td>non trouvé</td>
</tr>
<tr>
<td>10</td>
<td>5-15 min</td>
<td>trouvé</td>
</tr>
<tr>
<td>20</td>
<td>15-30 min</td>
<td>trouvé</td>
</tr>
<tr>
<td>45</td>
<td>&gt;30 min</td>
<td>trouvé</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="23-filtre-par-classification-age">23. Filtre par classification âge</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> des contenus avec différentes classifications:</p>
<pre><code>| contenu | classification |
|---|---|
| Conte enfants | Tout public |
| Podcast news | 13+ |
| Débat politique | 16+ |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur filtre "Tout public"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seul "Conte enfants" est affiché</p>
<hr />
<h2 id="24-filtre-par-geo-pertinence">24. Filtre par géo-pertinence</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> des contenus avec différents types géo:</p>
<pre><code>| contenu | type_geo |
|---|---|
| Guide Louvre | Ancré |
| Podcast Paris | Contextuel |
| News nationales | Neutre |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur filtre "Ancré, Contextuel"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> "Guide Louvre" et "Podcast Paris" sont affichés
<span style="color: #9E9E9E"><strong>Et</strong></span> "News nationales" est exclu</p>
<hr />
<h2 id="25-filtre-par-tags-multi-selection">25. Filtre par tags (multi-sélection)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> des contenus taggés:</p>
<pre><code>| contenu | tags |
|---|---|
| Voyage en Italie | Voyage, Gastronomie |
| Histoire de Rome | Voyage, Histoire |
| Économie italienne | Économie |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur sélectionne les tags "Voyage, Histoire"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> "Histoire de Rome" est en priorité (2 tags correspondants)
<span style="color: #9E9E9E"><strong>Et</strong></span> "Voyage en Italie" est affiché (1 tag correspondant)
<span style="color: #9E9E9E"><strong>Et</strong></span> "Économie italienne" est exclu</p>
<hr />
<h2 id="26-plan-filtre-par-date-de-publication">26. 📋 Plan: Filtre par date de publication</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un contenu publié il y a <délai></p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur filtre par "<période>"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est <résultat></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>délai</th>
<th>période</th>
<th>résultat</th>
</tr>
</thead>
<tbody>
<tr>
<td>12 heures</td>
<td>Dernières 24h</td>
<td>trouvé</td>
</tr>
<tr>
<td>3 jours</td>
<td>Cette semaine</td>
<td>trouvé</td>
</tr>
<tr>
<td>15 jours</td>
<td>Ce mois</td>
<td>trouvé</td>
</tr>
<tr>
<td>8 mois</td>
<td>Cette année</td>
<td>trouvé</td>
</tr>
<tr>
<td>2 ans</td>
<td>Toutes dates</td>
<td>trouvé</td>
</tr>
<tr>
<td>2 ans</td>
<td>Cette année</td>
<td>non trouvé</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="27-filtre-par-type-dabonnement">27. Filtre par type d'abonnement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> des contenus gratuits et Premium:</p>
<pre><code>| contenu | type |
|---|---|
| Balade Paris | Gratuit |
| Visite VIP Louvre | Premium |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur filtre "Premium uniquement 👑"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seul "Visite VIP Louvre" est affiché</p>
<hr />
<h2 id="28-combinaison-de-filtres-multiples-and-logic">28. Combinaison de filtres multiples (AND logic)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur applique les filtres:</p>
<pre><code>| filtre | valeur |
|---|---|
| Type | Audio-guide |
| Durée | 5-15 min |
| Tags | Voyage |
| Classification | Tout public |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la recherche est lancée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seuls les contenus respectant TOUS les critères sont affichés</p>
<hr />
<h2 id="29-reinitialisation-des-filtres">29. Réinitialisation des filtres</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur a appliqué 5 filtres différents</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il clique sur "Réinitialiser"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les filtres sont désactivés
<span style="color: #9E9E9E"><strong>Et</strong></span> la recherche affiche tous les résultats</p>
<hr />
<h2 id="30-sauvegarde-dune-recherche">30. Sauvegarde d'une recherche</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur a appliqué plusieurs filtres</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il clique sur "💾 Sauvegarder cette recherche"
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il entre le nom "Podcasts voyage Paris"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la recherche est sauvegardée
<span style="color: #9E9E9E"><strong>Et</strong></span> elle apparaît dans l'onglet "Recherches sauvegardées"</p>
<hr />
<h2 id="31-limite-de-5-recherches-sauvegardees">31. Limite de 5 recherches sauvegardées</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur a déjà 5 recherches sauvegardées</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il tente de sauvegarder une 6ème recherche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message d'erreur s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> il doit supprimer une recherche existante avant d'en ajouter une nouvelle</p>
<hr />
<h2 id="32-notifications-pour-recherches-sauvegardees">32. Notifications pour recherches sauvegardées</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> une recherche sauvegardée "Podcasts voyage Paris"
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur a activé les notifications</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> 3 nouveaux contenus correspondants sont publiés</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une notification "3 nouveaux contenus dans 'Podcasts voyage Paris'" est envoyée</p>
<hr />
<h2 id="33-plan-options-de-tri-des-resultats">33. 📋 Plan: Options de tri des résultats</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> une recherche avec plusieurs résultats</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur sélectionne le tri "<option>"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les résultats sont triés selon <algorithme></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>option</th>
<th>algorithme</th>
</tr>
</thead>
<tbody>
<tr>
<td>Pertinence</td>
<td>Score recherche × (1 + log(écoutes + 1))</td>
</tr>
<tr>
<td>Popularité</td>
<td>Écoutes complètes derniers 30j DESC</td>
</tr>
<tr>
<td>Récent</td>
<td>Date publication DESC</td>
</tr>
<tr>
<td>Proximité</td>
<td>Distance GPS ASC (si recherche géo)</td>
</tr>
<tr>
<td>Durée</td>
<td>Durée audio ASC ou DESC</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="34-structure-dun-resultat-de-recherche">34. Structure d'un résultat de recherche</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un résultat de recherche</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la page est affichée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> chaque résultat contient:</p>
<pre><code>| é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 | ⋮ |
</code></pre>
<hr />
<h2 id="35-lazy-loading-des-images">35. Lazy loading des images</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> une page avec 20 résultats de recherche</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la page se charge</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seules les 5 premières images sont chargées
<span style="color: #9E9E9E"><strong>Et</strong></span> les images suivantes se chargent au scroll</p>
<hr />
<h2 id="36-troncature-du-titre-sur-2-lignes-maximum">36. Troncature du titre sur 2 lignes maximum</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un contenu avec un titre de 120 caractères</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le résultat est affiché</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le titre est tronqué après 2 lignes
<span style="color: #9E9E9E"><strong>Et</strong></span> "..." est ajouté à la fin</p>
<hr />
<h2 id="37-lien-cliquable-vers-le-profil-createur">37. Lien cliquable vers le profil créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> un résultat de recherche pour "@paris_stories"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur "@paris_stories"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il est redirigé vers "https://roadwave.fr/@paris_stories"</p>
<hr />
<h2 id="38-menu-contextuel-dun-resultat">38. Menu contextuel d'un résultat [⋮]</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur clique sur [⋮] pour un résultat</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le menu s'ouvre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les actions suivantes sont disponibles:</p>
<pre><code>| action |
|---|
| Partager |
| Ajouter à une playlist |
| Télécharger (offline) |
| Signaler |
</code></pre>
<hr />
<h2 id="39-pagination-avec-20-resultats-par-page">39. Pagination avec 20 résultats par page</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> une recherche retournant 100 résultats</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la page est affichée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 20 résultats sont chargés initialement
<span style="color: #9E9E9E"><strong>Et</strong></span> un indicateur "1-20 sur 100 résultats" est visible</p>
<hr />
<h2 id="40-infinite-scroll-automatique">40. Infinite scroll automatique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur scroll dans les résultats</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il atteint 80% de la page</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les 20 résultats suivants sont chargés automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> un loader est affiché pendant le chargement</p>
<hr />
<h2 id="41-bouton-fallback-charger-20-suivants">41. Bouton fallback "Charger 20 suivants"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'infinite scroll est désactivé (paramètres)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur atteint la fin de la page</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un bouton "Charger 20 suivants" est affiché
<span style="color: #9E9E9E"><strong>Et</strong></span> les résultats se chargent au clic</p>
<hr />
<h2 id="42-basculement-entre-vue-liste-et-vue-carte">42. Basculement entre vue liste et vue carte</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur est sur la page de résultats</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il clique sur le toggle "Liste / Carte"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la vue carte Leaflet s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> les résultats sont affichés comme markers sur la carte</p>
<hr />
<h2 id="43-affichage-de-la-carte-leaflet">43. Affichage de la carte Leaflet</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la vue carte est activée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la carte se charge</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la carte utilise les tuiles OpenStreetMap
<span style="color: #9E9E9E"><strong>Et</strong></span> le centre est la position de recherche (ou GPS utilisateur)
<span style="color: #9E9E9E"><strong>Et</strong></span> le zoom initial montre tous les résultats</p>
<hr />
<h2 id="44-markers-cliquables-sur-la-carte">44. Markers cliquables sur la carte</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 10 résultats sont affichés sur la carte</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur clique sur un marker</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une popup s'affiche avec:</p>
<pre><code>| élément |
|---|
| Titre |
| Créateur |
| Durée |
| Distance |
| Bouton ▶️ Écouter |
</code></pre>
<hr />
<h2 id="45-clustering-des-markers-proches">45. Clustering des markers proches</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 50 résultats sont très proches géographiquement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la carte est affichée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les markers proches sont groupés en clusters
<span style="color: #9E9E9E"><strong>Et</strong></span> le nombre de contenus est affiché sur le cluster
<span style="color: #9E9E9E"><strong>Et</strong></span> le cluster se décompose au zoom</p>
<hr />
<h2 id="46-synchronisation-liste-carte">46. Synchronisation liste / carte</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur est en vue carte</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il clique sur un marker et écoute le contenu
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il rebascule en vue liste</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu écouté est marqué dans la liste
<span style="color: #9E9E9E"><strong>Et</strong></span> la position de scroll est maintenue</p>
<hr />
<h2 id="47-index-postgresql-full-text-pour-performances">47. Index PostgreSQL full-text pour performances</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la base contient 100K contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> une recherche full-text est effectuée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'index GIN sur to_tsvector est utilisé
<span style="color: #9E9E9E"><strong>Et</strong></span> la requête retourne en moins de 100ms</p>
<hr />
<h2 id="48-index-postgis-gist-pour-recherche-geo">48. Index PostGIS GIST pour recherche géo</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> une recherche géographique avec rayon 50 km</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la requête PostGIS ST_DWithin est exécutée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'index GIST sur la colonne location est utilisé
<span style="color: #9E9E9E"><strong>Et</strong></span> la requête retourne en moins de 50ms</p>
<hr />
<h2 id="49-index-composites-pour-filtres">49. Index composites pour filtres</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> une recherche avec filtres multiples</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les filtres type, durée, âge, géo, date sont appliqués</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'index composite idx_content_filters est utilisé
<span style="color: #9E9E9E"><strong>Et</strong></span> les performances restent optimales</p>
<hr />
<h2 id="50-index-gin-pour-recherche-par-tags">50. Index GIN pour recherche par tags</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> une recherche filtrée par tags "Voyage, Histoire"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la requête est exécutée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'index GIN sur la colonne tags est utilisé
<span style="color: #9E9E9E"><strong>Et</strong></span> la recherche est performante même avec 500K contenus</p>
<hr />
<h2 id="51-aucun-resultat-trouve">51. Aucun résultat trouvé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur recherche "xyzabc123"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> aucun résultat n'est trouvé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message "Aucun résultat pour 'xyzabc123'" s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> des suggestions de recherches populaires sont proposées</p>
<hr />
<h2 id="52-recherche-vide">52. Recherche vide</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur clique sur "Rechercher" sans saisir de texte</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la recherche est lancée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message "Veuillez entrer au moins 2 caractères" s'affiche</p>
<hr />
<h2 id="53-erreur-de-geocodage-nominatim">53. Erreur de géocodage Nominatim</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API Nominatim est indisponible</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur tente une recherche géographique</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message "Service de localisation temporairement indisponible" s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> la recherche continue sans filtre géographique</p>
<hr />
<h2 id="54-gps-desactive-pour-autour-de-moi">54. GPS désactivé pour "Autour de moi"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'utilisateur a désactivé le GPS</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il sélectionne "Autour de moi"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message "Veuillez activer la localisation" s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> un bouton "Activer" ouvre les paramètres système</p>
<hr />
<h2 id="55-timeout-de-recherche-apres-10-secondes">55. Timeout de recherche après 10 secondes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une recherche complexe est lancée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la requête dépasse 10 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la recherche est annulée
<span style="color: #9E9E9E"><strong>Et</strong></span> un message "La recherche a pris trop de temps, veuillez réessayer" s'affiche</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="classification-de-geo-pertinence-des-contenus">Classification de géo-pertinence des contenus</h1>
<blockquote>
<p><em>En tant que plateforme de contenu géolocalisé</em>
<em>Je veux classifier les contenus selon leur pertinence géographique</em>
<em>Afin d'adapter l'algorithme de recommandation</em></p>
</blockquote>
<p><strong>10 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible</p>
</blockquote>
<h2 id="1-createur-choisit-le-type-geo-ancre-pour-un-audio-guide">1. Créateur choisit le type géo-ancré pour un audio-guide</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur connecté</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je publie un audio-guide de la Tour Eiffel
<span style="color: #9E9E9E"><strong>Et</strong></span> que je choisis la classification "Géo-ancré"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est enregistré avec:</p>
<pre><code>| champ | valeur |
|---|---|
| type_geo | geo_ancre |
| ponderation_geo | 0.7 |
| ponderation_interets | 0.1 |
</code></pre>
<hr />
<h2 id="2-createur-choisit-le-type-geo-contextuel-pour-actualite-regionale">2. Créateur choisit le type géo-contextuel pour actualité régionale</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur connecté</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je publie une actualité régionale en Bretagne
<span style="color: #9E9E9E"><strong>Et</strong></span> que je choisis la classification "Géo-contextuel"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est enregistré avec:</p>
<pre><code>| champ | valeur |
|---|---|
| type_geo | geo_contextuel |
| ponderation_geo | 0.5 |
| ponderation_interets | 0.3 |
</code></pre>
<hr />
<h2 id="3-createur-choisit-le-type-geo-neutre-pour-un-podcast-philosophie">3. Créateur choisit le type géo-neutre pour un podcast philosophie</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur connecté</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je publie un podcast de philosophie
<span style="color: #9E9E9E"><strong>Et</strong></span> que je choisis la classification "Géo-neutre"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est enregistré avec:</p>
<pre><code>| champ | valeur |
|---|---|
| type_geo | geo_neutre |
| ponderation_geo | 0.2 |
| ponderation_interets | 0.6 |
</code></pre>
<hr />
<h2 id="4-publication-impossible-sans-classification-geographique">4. Publication impossible sans classification géographique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée un contenu audio</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de publier sans sélectionner de type géographique</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la publication échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Vous devez sélectionner un type de géo-pertinence"</p>
<hr />
<h2 id="5-moderateur-reclassifie-un-contenu-mal-categorise">5. Modérateur reclassifie un contenu mal catégorisé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu podcast générique est classifié "Géo-ancré"
<span style="color: #9E9E9E"><strong>Et</strong></span> que le modérateur examine le contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le modérateur le reclassifie en "Géo-neutre"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la nouvelle classification est appliquée immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> l'algorithme utilise la pondération géo = 0.2
<span style="color: #9E9E9E"><strong>Et</strong></span> le créateur reçoit une notification de reclassification</p>
<hr />
<h2 id="6-createur-modifie-la-classification-apres-publication">6. Créateur modifie la classification après publication</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai publié un contenu classifié "Géo-contextuel"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je réalise qu'il devrait être "Géo-neutre"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je modifie la classification en "Géo-neutre"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la modification est enregistrée
<span style="color: #9E9E9E"><strong>Et</strong></span> l'algorithme utilise la nouvelle pondération
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Classification modifiée avec succès"</p>
<hr />
<h2 id="7-statistiques-de-classification-dans-le-profil-createur">7. Statistiques de classification dans le profil créateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai publié 30 contenus:</p>
<pre><code>| type | nombre |
|---|---|
| Géo-ancré | 10 |
| Géo-contextuel | 15 |
| Géo-neutre | 5 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mes statistiques</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois la répartition de mes classifications
<span style="color: #9E9E9E"><strong>Et</strong></span> des suggestions pour optimiser la portée</p>
<hr />
<h2 id="8-contenu-geo-ancre-fortement-pondere-par-la-proximite">8. Contenu géo-ancré fortement pondéré par la proximité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide "Géo-ancré" existe à la Tour Eiffel
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un utilisateur est à 100m de la Tour Eiffel</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme calcule le score</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la pondération géo est de 0.7
<span style="color: #9E9E9E"><strong>Et</strong></span> le score géo est proche de 1 (très proche)
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu a un score final élevé</p>
<hr />
<h2 id="9-contenu-geo-neutre-moins-sensible-a-la-distance">9. Contenu géo-neutre moins sensible à la distance</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un podcast philosophie "Géo-neutre" existe à Paris
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un utilisateur est à Marseille (750 km)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme calcule le score</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la pondération géo est de 0.2
<span style="color: #9E9E9E"><strong>Et</strong></span> le score géo est bas (distance élevée)
<span style="color: #F44336"><strong>Mais</strong></span> le score intérêts (0.6) peut compenser
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu peut quand même être recommandé si intérêts match</p>
<hr />
<h2 id="10-comparaison-scores-entre-types-geo-pour-meme-distance">10. Comparaison scores entre types géo pour même distance</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> 3 contenus au même endroit (Paris):</p>
<pre><code>| type | ponderation_geo |
|---|---|
| Géo-ancré | 0.7 |
| Géo-contextuel | 0.5 |
| Géo-neutre | 0.2 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> qu'un utilisateur est à 50 km de Paris</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme calcule les scores</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu "Géo-ancré" a le score géo le plus élevé
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu "Géo-neutre" a le score géo le plus faible
<span style="color: #F44336"><strong>Mais</strong></span> peut avoir un score final plus élevé si forte correspondance intérêts</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="gestion-du-contenu-politique-mvp-simplifie">Gestion du contenu politique (MVP simplifié)</h1>
<blockquote>
<p><em>En tant qu'utilisateur</em>
<em>Je veux pouvoir filtrer le contenu politique</em>
<em>Afin de contrôler mon exposition à ce type de contenu</em></p>
</blockquote>
<p><strong>13 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible</p>
</blockquote>
<h2 id="1-createur-tagge-son-contenu-comme-politique">1. Créateur tagge son contenu comme "Politique"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur connecté</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je publie un contenu sur un débat politique
<span style="color: #9E9E9E"><strong>Et</strong></span> que je sélectionne le tag "Politique"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est enregistré avec le tag "Politique"
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune classification gauche/droite n'est demandée (MVP)</p>
<hr />
<h2 id="2-tag-politique-au-meme-niveau-que-les-autres-tags">2. Tag "Politique" au même niveau que les autres tags</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je crée un contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte la liste des tags disponibles</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois les tags suivants au même niveau:</p>
<pre><code>| tag |
|---|
| Économie |
| Sport |
| Culture |
| Politique |
| Automobile |
| Voyage |
| Musique |
</code></pre>
<hr />
<h2 id="3-par-defaut-tous-les-contenus-politiques-sont-visibles">3. Par défaut, tous les contenus politiques sont visibles</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un nouvel utilisateur
<span style="color: #9E9E9E"><strong>Et</strong></span> que je n'ai pas modifié les paramètres de contenu politique</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je demande des recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les contenus tagués "Politique" sont inclus normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun filtrage n'est appliqué</p>
<hr />
<h2 id="4-activer-le-filtrage-masquer-contenu-politique">4. Activer le filtrage "Masquer contenu politique"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis connecté</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'active l'option "Masquer contenu politique" dans les paramètres</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les contenus tagués "Politique" sont exclus de mes recommandations
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Contenu politique masqué"</p>
<hr />
<h2 id="5-filtrage-politique-actif-aucun-contenu-politique-recommande">5. Filtrage politique actif - aucun contenu politique recommandé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai activé "Masquer contenu politique"
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il existe 100 contenus dont 20 tagués "Politique"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je demande 50 recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois 50 contenus parmi les 80 non-politiques
<span style="color: #9E9E9E"><strong>Et</strong></span> 0% de contenus politiques sont proposés</p>
<hr />
<h2 id="6-desactiver-le-filtrage-masquer-contenu-politique">6. Désactiver le filtrage "Masquer contenu politique"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai activé "Masquer contenu politique"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je désactive cette option dans les paramètres</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les contenus politiques sont à nouveau inclus dans mes recommandations
<span style="color: #9E9E9E"><strong>Et</strong></span> le filtrage est levé immédiatement</p>
<hr />
<h2 id="7-mode-kids-filtre-automatiquement-le-contenu-politique">7. Mode Kids filtre automatiquement le contenu politique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur de 14 ans
<span style="color: #9E9E9E"><strong>Et</strong></span> que le mode Kids est activé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je demande des recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les contenus tagués "Politique" sont automatiquement exclus
<span style="color: #9E9E9E"><strong>Et</strong></span> ce indépendamment du paramètre "Masquer contenu politique"</p>
<hr />
<h2 id="8-statistiques-createur-sur-contenu-politique">8. Statistiques créateur sur contenu politique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai publié 20 contenus dont 5 tagués "Politique"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mes statistiques</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois le nombre d'utilisateurs ayant masqué le contenu politique
<span style="color: #9E9E9E"><strong>Et</strong></span> le taux d'engagement comparé aux autres tags</p>
<hr />
<h2 id="9-recherche-avec-tag-politique">9. Recherche avec tag "Politique"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je recherche du contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je filtre par tag "Politique"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seuls les contenus tagués "Politique" sont affichés
<span style="color: #9E9E9E"><strong>Et</strong></span> ce même si j'ai activé "Masquer contenu politique" (recherche explicite)</p>
<hr />
<h2 id="10-partage-de-contenu-politique-avec-filtre-actif">10. Partage de contenu politique avec filtre actif</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai activé "Masquer contenu politique"
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un ami me partage un lien vers un contenu tagué "Politique"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ouvre le lien</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux accéder au contenu (partage explicite)
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois un avertissement "Ce contenu est tagué Politique"</p>
<hr />
<h2 id="11-pas-de-classification-gauchedroite-en-mvp">11. Pas de classification gauche/droite en MVP</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je publie un contenu tagué "Politique"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune option de classification idéologique n'est proposée
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne peux pas indiquer "Gauche", "Droite", "Centre", etc.</p>
<hr />
<h2 id="12-pas-dequilibrage-impose-en-mvp">12. Pas d'équilibrage imposé en MVP</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur écoute majoritairement du contenu politique de gauche</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme génère des recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucun équilibrage droite/gauche n'est appliqué
<span style="color: #9E9E9E"><strong>Et</strong></span> les recommandations suivent l'algorithme standard (intérêts, géo, engagement)</p>
<hr />
<h2 id="13-notification-post-mvp-pour-classification-avancee">13. Notification post-MVP pour classification avancée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave passe en phase post-MVP
<span style="color: #9E9E9E"><strong>Et</strong></span> que la classification politique avancée est activée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me connecte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification m'informant des nouvelles options
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux configurer mes préférences d'équilibrage politique</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="contenus-geolocalises-en-mode-voiture">Contenus géolocalisés en mode voiture</h1>
<blockquote>
<p><em>En tant qu'utilisateur en voiture</em>
<em>Je veux recevoir des notifications de contenus géolocalisés au bon moment</em>
<em>Afin de découvrir du contenu contextuel sans distraction au volant</em></p>
</blockquote>
<p><strong>36 scénarios</strong> (32 standards, 4 plans)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'application est ouverte (premier plan)
<span style="color: #9E9E9E"><strong>Et</strong></span> que le GPS est activé
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur est en mode voiture (vitesse ≥ 5 km/h)</p>
</blockquote>
<h2 id="1-calcul-eta-et-notification-7-secondes-avant-le-point-gps">1. Calcul ETA et notification 7 secondes avant le point GPS</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu géolocalisé existe à la Tour Eiffel (48.8584, 2.2945)
<span style="color: #9E9E9E"><strong>Et</strong></span> que je me déplace à 50 km/h vers ce point
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis à 98 mètres du point (ETA = 7 secondes)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système calcule l'ETA</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une notification est déclenchée immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> le compteur "7" s'affiche avec l'icône 🏛️
<span style="color: #9E9E9E"><strong>Et</strong></span> une notification sonore (bip court) est jouée</p>
<hr />
<h2 id="2-plan-calcul-eta-a-differentes-vitesses">2. 📋 Plan: Calcul ETA à différentes vitesses</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu géolocalisé existe à un point GPS
<span style="color: #9E9E9E"><strong>Et</strong></span> que je me déplace à <vitesse> km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je suis à <distance> mètres du point</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'ETA calculé est <eta> secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> la notification est déclenchée : <notification></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>vitesse</th>
<th>distance</th>
<th>eta</th>
<th>notification</th>
</tr>
</thead>
<tbody>
<tr>
<td>10</td>
<td>19</td>
<td>7</td>
<td>Oui</td>
</tr>
<tr>
<td>50</td>
<td>98</td>
<td>7</td>
<td>Oui</td>
</tr>
<tr>
<td>130</td>
<td>252</td>
<td>7</td>
<td>Oui</td>
</tr>
<tr>
<td>50</td>
<td>200</td>
<td>14</td>
<td>Non</td>
</tr>
<tr>
<td>10</td>
<td>50</td>
<td>18</td>
<td>Non</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="3-notification-immediate-si-vitesse-5-kmh-et-distance-50m">3. Notification immédiate si vitesse &lt;5 km/h ET distance &lt;50m</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu géolocalisé existe à 30m de ma position
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma vitesse est 3 km/h (arrêté à un feu rouge)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte cette situation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une notification est déclenchée immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> je n'ai pas besoin d'attendre le calcul ETA</p>
<hr />
<h2 id="4-notification-minimaliste-sans-texte-securite-routiere">4. Notification minimaliste sans texte (sécurité routière)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une notification géolocalisée est déclenchée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la notification s'affiche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les éléments suivants sont visibles:</p>
<pre><code>| élément | présent |
|---|---|
| Icône du tag | ✅ |
| Compteur 7→1 | ✅ |
| Son bref (bip) | ✅ |
| Titre texte | ❌ |
| Description | ❌ |
| Cover image | ❌ |
| Bouton Annuler | ❌ |
</code></pre>
<hr />
<h2 id="5-plan-icones-selon-le-tag-du-contenu">5. 📋 Plan: Icônes selon le tag du contenu</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu géolocalisé avec le tag <tag> est disponible</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la notification s'affiche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'icône <icone> est affichée</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>tag</th>
<th>icone</th>
</tr>
</thead>
<tbody>
<tr>
<td>Culture générale</td>
<td>🏛️</td>
</tr>
<tr>
<td>Histoire</td>
<td>📜</td>
</tr>
<tr>
<td>Voyage</td>
<td>✈️</td>
</tr>
<tr>
<td>Famille</td>
<td>👨‍👩‍👧</td>
</tr>
<tr>
<td>Musique</td>
<td>🎵</td>
</tr>
<tr>
<td>Sport</td>
<td>⚽</td>
</tr>
<tr>
<td>Technologie</td>
<td>💻</td>
</tr>
<tr>
<td>Automobile</td>
<td>🚗</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="6-compteur-decrementant-de-7-a-1">6. Compteur décrémentant de 7 à 1</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une notification géolocalisée s'affiche</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le compteur démarre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le compteur affiche "7"
<span style="color: #9E9E9E"><strong>Et</strong></span> après 1 seconde, il affiche "6"
<span style="color: #9E9E9E"><strong>Et</strong></span> après 2 secondes, il affiche "5"
<span style="color: #9E9E9E"><strong>Et</strong></span> après 3 secondes, il affiche "4"
<span style="color: #9E9E9E"><strong>Et</strong></span> après 4 secondes, il affiche "3"
<span style="color: #9E9E9E"><strong>Et</strong></span> après 5 secondes, il affiche "2"
<span style="color: #9E9E9E"><strong>Et</strong></span> après 6 secondes, il affiche "1"
<span style="color: #9E9E9E"><strong>Et</strong></span> après 7 secondes, la notification disparaît</p>
<hr />
<h2 id="7-notification-sonore-uniquement-en-mode-carplay">7. Notification sonore uniquement en mode CarPlay</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'application est connectée à CarPlay
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un contenu géolocalisé est détecté (ETA 7s)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la notification est déclenchée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seule la notification sonore (bip) est jouée
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun overlay visuel n'est affiché (icône, compteur)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur peut valider via le bouton "Suivant" au volant</p>
<hr />
<h2 id="8-notification-sonore-uniquement-en-mode-android-auto">8. Notification sonore uniquement en mode Android Auto</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'application est connectée à Android Auto
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un contenu géolocalisé est détecté (ETA 7s)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la notification est déclenchée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seule la notification sonore (bip) est jouée
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun overlay visuel n'est affiché
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur peut valider via le bouton "Suivant" au volant</p>
<hr />
<h2 id="9-notification-complete-sonore-visuelle-en-mode-normal">9. Notification complète (sonore + visuelle) en mode normal</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'application n'est PAS connectée à CarPlay/Android Auto
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un contenu géolocalisé est détecté</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la notification est déclenchée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la notification sonore (bip) est jouée
<span style="color: #9E9E9E"><strong>Et</strong></span> l'overlay visuel s'affiche (icône + compteur 7→1)</p>
<hr />
<h2 id="10-validation-via-bouton-suivant-et-decompte-5-secondes">10. Validation via bouton "Suivant" et décompte 5 secondes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une notification géolocalisée est affichée (compteur à 5)
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'écoute un podcast</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur le bouton "Suivant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le compteur bascule à "5" (décompte final)
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu actuel continue de jouer normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> le compteur décrémente: 5→4→3→2→1
<span style="color: #9E9E9E"><strong>Et</strong></span> après 5 secondes, le contenu géolocalisé démarre (fade in 0.3s)</p>
<hr />
<h2 id="11-transition-fluide-avec-fade-outin">11. Transition fluide avec fade out/in</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le décompte atteint "0"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le contenu géolocalisé doit démarrer</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu actuel fait un fade out de 0.3s
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu géolocalisé fait un fade in de 0.3s
<span style="color: #9E9E9E"><strong>Et</strong></span> il n'y a pas de silence entre les deux</p>
<hr />
<h2 id="12-contenu-actuel-se-termine-pendant-le-decompte">12. Contenu actuel se termine pendant le décompte</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai validé la notification (décompte 5s démarre)
<span style="color: #9E9E9E"><strong>Et</strong></span> que mon contenu actuel se termine après 2 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le contenu actuel se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu suivant du buffer démarre immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> le décompte continue (3→2→1)
<span style="color: #9E9E9E"><strong>Et</strong></span> à la fin du décompte, le contenu géolocalisé remplace le buffer</p>
<hr />
<h2 id="13-ignorance-de-la-notification-pas-de-clic-pendant-7s">13. Ignorance de la notification (pas de clic pendant 7s)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une notification géolocalisée s'affiche (compteur 7)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> 7 secondes s'écoulent sans que j'appuie sur "Suivant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la notification disparaît automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu géolocalisé est perdu (pas d'insertion dans la file)
<span style="color: #9E9E9E"><strong>Et</strong></span> un cooldown de 10 minutes est activé</p>
<hr />
<h2 id="14-quota-de-6-contenus-geolocalises-par-heure">14. Quota de 6 contenus géolocalisés par heure</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai validé 6 notifications géolocalisées dans la dernière heure</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un 7ème contenu géolocalisé est détecté</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune notification n'est envoyée
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu n'est pas inséré dans la file</p>
<hr />
<h2 id="15-fenetre-glissante-de-60-minutes">15. Fenêtre glissante de 60 minutes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai validé 6 contenus géolocalisés
<span style="color: #9E9E9E"><strong>Et</strong></span> que le premier contenu a été validé il y a 61 minutes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un nouveau contenu géolocalisé est détecté</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la notification est envoyée (quota libéré : 5/6)
<span style="color: #9E9E9E"><strong>Et</strong></span> le compteur horaire est mis à jour</p>
<hr />
<h2 id="16-plan-gestion-du-quota-horaire">16. 📋 Plan: Gestion du quota horaire</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que <nb_valides> notifications ont été validées dans la dernière heure</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un nouveau contenu géolocalisé est détecté</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la notification est <action></p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>nb_valides</th>
<th>action</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>envoyée</td>
</tr>
<tr>
<td>3</td>
<td>envoyée</td>
</tr>
<tr>
<td>5</td>
<td>envoyée</td>
</tr>
<tr>
<td>6</td>
<td>non envoyée</td>
</tr>
<tr>
<td>7</td>
<td>non envoyée</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="17-exception-audio-guides-multi-sequences-comptent-comme-1">17. Exception audio-guides multi-séquences (comptent comme 1)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai démarré un audio-guide avec 8 séquences
<span style="color: #9E9E9E"><strong>Et</strong></span> que cet audio-guide compte comme 1 contenu dans le quota</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> toutes les séquences de l'audio-guide sont lues</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon quota reste à 1/6
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux encore valider 5 contenus géolocalisés simples</p>
<hr />
<h2 id="18-cooldown-de-10-minutes-apres-notification-ignoree">18. Cooldown de 10 minutes après notification ignorée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une notification géolocalisée a été ignorée (pas de clic)
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un cooldown de 10 minutes est activé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> 5 minutes s'écoulent
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un nouveau contenu géolocalisé est détecté</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune notification n'est envoyée (cooldown actif)</p>
<hr />
<h2 id="19-cooldown-expire-apres-10-minutes">19. Cooldown expire après 10 minutes</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un cooldown a été activé il y a 10 minutes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un nouveau contenu géolocalisé est détecté</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la notification est envoyée (cooldown expiré)</p>
<hr />
<h2 id="20-pas-de-cooldown-si-notification-validee">20. Pas de cooldown si notification validée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une notification géolocalisée est affichée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Suivant" dans les 7 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucun cooldown n'est activé
<span style="color: #9E9E9E"><strong>Et</strong></span> la prochaine notification pourra être envoyée normalement</p>
<hr />
<h2 id="21-contenu-geolocalise-dans-lhistorique-de-navigation">21. Contenu géolocalisé dans l'historique de navigation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu du buffer
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai validé un contenu géolocalisé "Tour Eiffel"
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai écouté 42 secondes du contenu géolocalisé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Suivant" (skip)
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'appuie ensuite sur "Précédent"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu géolocalisé reprend à 42 secondes</p>
<hr />
<h2 id="22-contenu-ignore-nentre-pas-dans-lhistorique">22. Contenu ignoré n'entre pas dans l'historique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une notification géolocalisée a été ignorée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie sur "Précédent"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu géolocalisé ignoré n'apparaît PAS dans l'historique
<span style="color: #9E9E9E"><strong>Et</strong></span> je reviens au contenu d'avant</p>
<hr />
<h2 id="23-skip-pendant-le-decompte-annule-linsertion">23. Skip pendant le décompte annule l'insertion</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai validé une notification (décompte 5s en cours)
<span style="color: #9E9E9E"><strong>Et</strong></span> que le compteur affiche "3"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'appuie à nouveau sur "Suivant"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le décompte est annulé
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu suivant du buffer démarre
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu géolocalisé n'entre PAS dans l'historique</p>
<hr />
<h2 id="24-detection-mode-pieton-vitesse-5-kmh-stable-10s">24. Détection mode piéton (vitesse &lt;5 km/h stable 10s)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis en mode voiture
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma vitesse passe à 3 km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> cette vitesse reste stable pendant 10 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le mode piéton est activé automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> les notifications passent en mode push arrière-plan (si permission accordée)</p>
<hr />
<h2 id="25-detection-mode-voiture-vitesse-5-kmh-stable-10s">25. Détection mode voiture (vitesse ≥5 km/h stable 10s)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis en mode piéton
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma vitesse passe à 15 km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> cette vitesse reste stable pendant 10 secondes</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le mode voiture est activé automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> les notifications passent en mode sonore + icône (app premier plan requise)</p>
<hr />
<h2 id="26-hysteresis-pour-eviter-basculements-intempestifs">26. Hysteresis pour éviter basculements intempestifs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma vitesse passe de 20 km/h à 3 km/h (arrêt feu rouge)
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma vitesse remonte à 20 km/h après 8 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système vérifie le mode</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucun basculement n'a lieu (hysteresis de 10s non atteinte)
<span style="color: #9E9E9E"><strong>Et</strong></span> je reste en mode voiture</p>
<hr />
<h2 id="27-plan-effets-du-basculement-voiture-pieton">27. 📋 Plan: Effets du basculement voiture → piéton</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je bascule de voiture à piéton</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le basculement est effectué</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les paramètres suivants changent:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="28-haute-vitesse-130-kmh-sur-autoroute">28. Haute vitesse (130 km/h sur autoroute)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je roule à 130 km/h (36.1 m/s)
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un contenu géolocalisé est à 252 mètres</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'ETA de 7s est atteint
<span style="color: #9E9E9E"><strong>Et</strong></span> que je valide la notification</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le décompte 5s démarre
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu géolocalisé démarre encore avant le point GPS (72m avant)</p>
<hr />
<h2 id="29-multiples-points-geolocalises-proches-route-touristique">29. Multiples points géolocalisés proches (route touristique)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 3 châteaux sont espacés de 800m chacun
<span style="color: #9E9E9E"><strong>Et</strong></span> que je valide la notification du Château A</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'arrive près du Château B (57s plus tard à 50 km/h)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la notification du Château B est envoyée (quota 2/6, pas de cooldown)</p>
<hr />
<h2 id="30-mode-stationnement-vitesse-1-kmh-pendant-2-min">30. Mode stationnement (vitesse &lt;1 km/h pendant 2 min)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je me gare à 30m d'un château
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma vitesse est &lt;1 km/h pendant 2 minutes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le mode stationnement est détecté</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune notification de contenu géolocalisé n'est envoyée
<span style="color: #9E9E9E"><strong>Et</strong></span> le système bascule automatiquement en mode piéton</p>
<hr />
<h2 id="31-reprise-conduite-apres-stationnement">31. Reprise conduite après stationnement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis en mode stationnement
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma vitesse passe à 20 km/h pendant 10 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte la reprise de conduite</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le mode voiture est réactivé
<span style="color: #9E9E9E"><strong>Et</strong></span> les notifications géolocalisées reprennent (si quota non atteint)</p>
<hr />
<h2 id="32-contenu-geolocalise-simple-1-sequence-unique">32. Contenu géolocalisé simple (1 séquence unique)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu géolocalisé simple existe à un point GPS</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la notification est déclenchée (ETA 7s)
<span style="color: #9E9E9E"><strong>Et</strong></span> que je valide</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu démarre après décompte 5s
<span style="color: #9E9E9E"><strong>Et</strong></span> à la fin du contenu, le buffer normal reprend
<span style="color: #9E9E9E"><strong>Et</strong></span> ce contenu compte 1/6 dans le quota</p>
<hr />
<h2 id="33-audio-guide-multi-sequences-2-sequences-enchainees">33. Audio-guide multi-séquences (2+ séquences enchaînées)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide avec 8 séquences existe</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je démarre l'audio-guide
<span style="color: #9E9E9E"><strong>Et</strong></span> que les séquences s'enchaînent automatiquement (GPS ou manuel)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'audio-guide entier compte 1/6 dans le quota
<span style="color: #9E9E9E"><strong>Et</strong></span> les séquences ne déclenchent PAS de notification avec compteur 7s
<span style="color: #9E9E9E"><strong>Et</strong></span> elles se déclenchent au point GPS exact (rayon 30m)</p>
<hr />
<h2 id="34-gps-desactive-en-mode-voiture">34. GPS désactivé en mode voiture</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis en mode voiture</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le GPS est désactivé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune notification géolocalisée ne peut être envoyée
<span style="color: #9E9E9E"><strong>Et</strong></span> un message d'erreur s'affiche: "GPS requis pour les contenus géolocalisés"</p>
<hr />
<h2 id="35-app-en-arriere-plan-en-mode-voiture">35. App en arrière-plan en mode voiture</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis en mode voiture
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'app passe en arrière-plan</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un contenu géolocalisé est détecté</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune notification n'est envoyée (app premier plan requise)
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu n'est pas perdu (sera proposé si app rouverte dans le rayon)</p>
<hr />
<h2 id="36-permission-always-location-refusee-mode-pieton-indisponible">36. Permission "Always Location" refusée (mode piéton indisponible)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je refuse la permission "Always Location"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> ma vitesse passe &lt;5 km/h</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le mode piéton n'est PAS activé
<span style="color: #9E9E9E"><strong>Et</strong></span> le mode voiture reste actif (avec permission "When In Use")
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune notification arrière-plan n'est envoyée</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="gestion-de-lhistorique-et-reproposition">Gestion de l'historique et reproposition</h1>
<blockquote>
<p><em>En tant que système de recommandation</em>
<em>Je veux gérer l'historique d'écoute intelligemment</em>
<em>Afin d'éviter les répétitions et offrir une découverte maximale</em></p>
</blockquote>
<p><strong>19 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible</p>
</blockquote>
<h2 id="1-contenu-ecoute-completement-80-jamais-repropose">1. Contenu écouté complètement (&gt;80%) - jamais reproposé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a écouté un contenu à 85%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme génère les recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ce contenu n'est jamais reproposé
<span style="color: #9E9E9E"><strong>Et</strong></span> il est marqué comme "écouté" dans l'historique</p>
<hr />
<h2 id="2-contenu-ecoute-a-80-exactement-jamais-repropose">2. Contenu écouté à 80% exactement - jamais reproposé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a écouté un contenu exactement à 80%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme génère les recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ce contenu n'est pas reproposé (seuil &gt;= 80%)</p>
<hr />
<h2 id="3-contenu-skippe-rapidement-10s-ne-pas-reproposer">3. Contenu skippé rapidement (&lt;10s) - ne pas reproposer</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a skippé un contenu après 8 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme génère les recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ce contenu n'est pas reproposé (signal négatif fort)
<span style="color: #9E9E9E"><strong>Et</strong></span> la jauge d'intérêt correspondante est réduite de 0.5%</p>
<hr />
<h2 id="4-contenu-skippe-exactement-a-10s-ne-pas-reproposer">4. Contenu skippé exactement à 10s - ne pas reproposer</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a skippé un contenu après exactement 10 secondes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme génère les recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ce contenu n'est pas reproposé (seuil &lt; 10s strict)</p>
<hr />
<h2 id="5-contenu-partiellement-ecoute-10-80-reproposer-avec-reprise">5. Contenu partiellement écouté (10-80%) - reproposer avec reprise</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a écouté un contenu à 45%
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il est arrivé à la position 2:30 (150 secondes)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme propose à nouveau ce contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu peut être reproposé
<span style="color: #9E9E9E"><strong>Et</strong></span> la position de reprise est 150 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur voit "Reprendre à 2:30"</p>
<hr />
<h2 id="6-contenu-ecoute-a-11-reproposition-possible">6. Contenu écouté à 11% - reproposition possible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a écouté un contenu à 11%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme génère les recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ce contenu peut être reproposé (&gt;10%)
<span style="color: #9E9E9E"><strong>Et</strong></span> la position de reprise est sauvegardée</p>
<hr />
<h2 id="7-contenu-ecoute-a-79-reproposition-possible">7. Contenu écouté à 79% - reproposition possible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a écouté un contenu à 79%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme génère les recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ce contenu peut être reproposé (&lt;80%)
<span style="color: #9E9E9E"><strong>Et</strong></span> l'utilisateur peut terminer l'écoute</p>
<hr />
<h2 id="8-audio-guide-avec-flag-replayabletrue">8. Audio-guide avec flag replayable=true</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un audio-guide a le flag "replayable = true"
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un utilisateur l'a écouté à 95%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme génère les recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'audio-guide peut être reproposé
<span style="color: #9E9E9E"><strong>Et</strong></span> il est marqué comme "Écouté - Rejouable"</p>
<hr />
<h2 id="9-podcast-standard-sans-flag-replayable">9. Podcast standard sans flag replayable</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un podcast n'a pas de flag replayable
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un utilisateur l'a écouté à 90%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme génère les recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le podcast n'est jamais reproposé</p>
<hr />
<h2 id="10-stockage-dans-user_content_history">10. Stockage dans user_content_history</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur écoute un contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'écoute se termine ou est skippée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les données suivantes sont enregistrées:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="11-historique-illimite-stocke">11. Historique illimité stocké</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a écouté 5000 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il consulte son historique</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les 5000 contenus sont disponibles
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun contenu n'est supprimé automatiquement</p>
<hr />
<h2 id="12-algorithme-considere-les-100-derniers-pour-performance">12. Algorithme considère les 100 derniers pour performance</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a écouté 500 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme génère les recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il vérifie uniquement les 100 derniers contenus pour exclusion
<span style="color: #9E9E9E"><strong>Et</strong></span> cette limite est une optimisation de requête SQL</p>
<hr />
<h2 id="13-export-historique-complet-rgpd">13. Export historique complet (RGPD)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur demande l'export RGPD</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'export est généré</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'historique complet est inclus avec:</p>
<pre><code>| information | inclus |
|---|---|
| Tous les contenus | ✅ |
| Dates d'écoute | ✅ |
| Taux complétion | ✅ |
| Positions reprise | ✅ |
</code></pre>
<hr />
<h2 id="14-reprise-automatique-dun-contenu-partiellement-ecoute">14. Reprise automatique d'un contenu partiellement écouté</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai écouté un podcast à 60% (position 10:00)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> ce podcast est reproposé par l'algorithme
<span style="color: #9E9E9E"><strong>Et</strong></span> que je lance la lecture</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'écoute reprend automatiquement à 10:00
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois une notification "Reprise à 10:00"</p>
<hr />
<h2 id="15-option-reprendre-du-debut-pour-contenu-partiellement-ecoute">15. Option "Reprendre du début" pour contenu partiellement écouté</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai écouté un podcast à 60%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> ce podcast est reproposé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois deux options:</p>
<pre><code>| option | action |
|---|---|
| Reprendre à 10:00 | Lecture à partir de 10:00 |
| Depuis le début | Lecture à partir de 0:00 |
</code></pre>
<hr />
<h2 id="16-contenu-ecoute-il-y-a-6-mois-toujours-en-historique">16. Contenu écouté il y a 6 mois - toujours en historique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a écouté un contenu il y a 6 mois à 90%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme génère les recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ce contenu n'est toujours pas reproposé
<span style="color: #9E9E9E"><strong>Et</strong></span> l'historique n'a pas de limite temporelle</p>
<hr />
<h2 id="17-nouveau-contenu-du-meme-createur-apres-ecoute-complete">17. Nouveau contenu du même créateur après écoute complète</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a écouté un contenu de "Créateur A" à 90%
<span style="color: #9E9E9E"><strong>Et</strong></span> que "Créateur A" publie un nouveau contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme génère les recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le nouveau contenu peut être recommandé
<span style="color: #9E9E9E"><strong>Et</strong></span> seul l'ancien contenu est exclu (pas tout le créateur)</p>
<hr />
<h2 id="18-statistiques-personnelles-dhistorique">18. Statistiques personnelles d'historique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte mon profil</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à la section "Historique"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| métrique | exemple |
|---|---|
| Nombre total d'écoutes | 1,234 |
| Heures écoutées | 456h |
| Taux complétion moyen | 72% |
| Top 5 catégories | Voyage, Sport |
</code></pre>
<hr />
<h2 id="19-filtrer-lhistorique-par-date">19. Filtrer l'historique par date</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte mon historique</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je filtre par "Dernière semaine"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seuls les contenus écoutés dans les 7 derniers jours sont affichés
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux exporter cette sélection</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="medias-traditionnels-sur-roadwave">Médias traditionnels sur RoadWave</h1>
<blockquote>
<p><em>En tant que média établi</em>
<em>Je veux publier du contenu géolocalisé sur RoadWave</em>
<em>Afin d'atteindre une audience locale et mobile</em></p>
</blockquote>
<p><strong>21 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible</p>
</blockquote>
<h2 id="1-creation-dun-compte-media-verifie">1. Création d'un compte média vérifié</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je représente Le Monde</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je crée un compte média
<span style="color: #9E9E9E"><strong>Et</strong></span> que je fournis les justificatifs (SIRET, documents officiels)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon compte est créé en attente de vérification
<span style="color: #9E9E9E"><strong>Et</strong></span> l'équipe RoadWave examine ma demande sous 48-72h</p>
<hr />
<h2 id="2-validation-compte-media-par-lequipe-roadwave">2. Validation compte média par l'équipe RoadWave</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un compte média "Le Parisien" est en attente</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'équipe RoadWave valide le compte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le compte reçoit le badge vérifié ✓
<span style="color: #9E9E9E"><strong>Et</strong></span> le média peut publier sans validation des 3 premiers contenus
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Compte média vérifié avec succès"</p>
<hr />
<h2 id="3-badge-verifie-visible-sur-profil-media">3. Badge vérifié visible sur profil média</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que "France Inter" a un compte vérifié</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un utilisateur consulte le profil</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il voit le badge ✓ à côté du nom
<span style="color: #9E9E9E"><strong>Et</strong></span> une mention "Média vérifié"</p>
<hr />
<h2 id="4-pas-de-validation-des-3-premiers-contenus-pour-medias">4. Pas de validation des 3 premiers contenus pour médias</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un média vérifié</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je publie mon premier contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est publié immédiatement sans validation
<span style="color: #9E9E9E"><strong>Et</strong></span> il est visible pour tous les utilisateurs
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne passe pas par la modération initiale</p>
<hr />
<h2 id="5-moderation-a-posteriori-uniquement">5. Modération a posteriori uniquement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que "Libération" publie un contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le contenu est publié</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il est immédiatement disponible
<span style="color: #F44336"><strong>Mais</strong></span> peut être signalé et modéré a posteriori
<span style="color: #9E9E9E"><strong>Et</strong></span> suit les mêmes règles de modération que les créateurs</p>
<hr />
<h2 id="6-publication-flash-info-geolocalise">6. Publication flash info géolocalisé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis "Ouest-France" (média régional)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je publie un flash info sur un événement à Rennes
<span style="color: #9E9E9E"><strong>Et</strong></span> que je le géolocalise en Bretagne (géo-contextuel)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est publié immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> il est recommandé aux utilisateurs en Bretagne</p>
<hr />
<h2 id="7-publication-chronique-thematique">7. Publication chronique thématique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis "France Culture"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je publie une chronique philosophie (géo-neutre)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est disponible partout en France
<span style="color: #9E9E9E"><strong>Et</strong></span> suit l'algorithme de recommandation standard</p>
<hr />
<h2 id="8-publication-edito-politique">8. Publication édito politique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis "Le Figaro"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je publie un édito politique
<span style="color: #9E9E9E"><strong>Et</strong></span> que je le tague "Politique"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu est publié immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> la classification politique MVP s'applique (pas gauche/droite)
<span style="color: #9E9E9E"><strong>Et</strong></span> les utilisateurs ayant activé "Masquer politique" ne le voient pas</p>
<hr />
<h2 id="9-formats-de-contenu-autorises-pour-medias">9. Formats de contenu autorisés pour médias</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un média vérifié</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je publie du contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux publier:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="10-medias-suivent-les-regles-standard-de-classification-age">10. Médias suivent les règles standard de classification âge</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis "RTL"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je publie un contenu sensible</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je dois obligatoirement classifier par âge:</p>
<pre><code>| classification | type contenu |
|---|---|
| Tout public | Info générale |
| 13+ | Actualité avec sujets sensibles |
| 16+ | Débats avec violence verbale |
| 18+ | Sujets adultes |
</code></pre>
<hr />
<h2 id="11-monetisation-medias-partage-revenus-pub-standard">11. Monétisation médias - partage revenus pub standard</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un média vérifié
<span style="color: #9E9E9E"><strong>Et</strong></span> que mes contenus génèrent des écoutes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le mois se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois 3€ / 1000 écoutes complètes (même taux que créateurs)
<span style="color: #9E9E9E"><strong>Et</strong></span> le paiement suit les mêmes règles (seuil 50€, mensuel)</p>
<hr />
<h2 id="12-sponsoring-direct-non-gere-par-plateforme">12. Sponsoring direct non géré par plateforme</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis "Europe 1"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je veux intégrer un sponsor dans mon contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je mentionne le sponsor dans l'audio</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> c'est autorisé (sponsoring éditorial)
<span style="color: #F44336"><strong>Mais</strong></span> RoadWave ne gère pas la transaction
<span style="color: #9E9E9E"><strong>Et</strong></span> je gère la relation sponsor directement</p>
<hr />
<h2 id="13-medias-peuvent-avoir-plusieurs-comptes-createurs">13. Médias peuvent avoir plusieurs comptes créateurs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis "Le Monde"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je veux créer des sous-comptes par rubrique</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux créer:</p>
<pre><code>| compte | description |
|---|---|
| @lemonde_politique | Actualité politique |
| @lemonde_economie | Économie et entreprises |
| @lemonde_culture | Culture et spectacles |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> tous sont liés au compte média principal</p>
<hr />
<h2 id="14-medias-regionaux-privilegies-localement">14. Médias régionaux privilégiés localement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que "Sud-Ouest" publie du contenu géo-contextuel en Nouvelle-Aquitaine
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un utilisateur est à Bordeaux</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme calcule les recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le contenu de "Sud-Ouest" a un score géo élevé
<span style="color: #9E9E9E"><strong>Et</strong></span> il est privilégié pour l'audience locale</p>
<hr />
<h2 id="15-medias-nationaux-accessibles-partout">15. Médias nationaux accessibles partout</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que "France Inter" publie un podcast géo-neutre</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> des utilisateurs à Paris, Lyon, Marseille demandent des recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le podcast est accessible partout sans distinction géographique
<span style="color: #9E9E9E"><strong>Et</strong></span> suit l'algorithme de recommandation standard</p>
<hr />
<h2 id="16-statistiques-detaillees-pour-medias">16. Statistiques détaillées pour médias</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un média vérifié</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mes statistiques</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| 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€ |
</code></pre>
<hr />
<h2 id="17-medias-peuvent-exporter-analytics">17. Médias peuvent exporter analytics</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis "Libération"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Exporter analytics"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un CSV avec données détaillées
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux analyser les données avec mes outils internes</p>
<hr />
<h2 id="18-contact-prioritaire-equipe-roadwave">18. Contact prioritaire équipe RoadWave</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un média vérifié</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ai un problème technique ou question</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux contacter le support média prioritaire
<span style="color: #9E9E9E"><strong>Et</strong></span> j'obtiens une réponse sous 24h (vs 48-72h standard)</p>
<hr />
<h2 id="19-medias-peuvent-programmer-la-publication">19. Médias peuvent programmer la publication</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis "France Culture"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je prépare un contenu à l'avance</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux programmer la publication pour une date/heure future
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu sera publié automatiquement au moment choisi</p>
<hr />
<h2 id="20-api-dediee-pour-medias-post-mvp">20. API dédiée pour médias (post-MVP)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un grand média avec beaucoup de contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> RoadWave développe l'API médias</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux automatiser la publication via API
<span style="color: #9E9E9E"><strong>Et</strong></span> intégrer RoadWave dans mon workflow de production</p>
<hr />
<h2 id="21-signalement-dun-contenu-media-traite-en-priorite">21. Signalement d'un contenu média traité en priorité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu de "Le Monde" est signalé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le signalement arrive en modération</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il est traité avec la même priorité qu'un créateur standard
<span style="color: #9E9E9E"><strong>Et</strong></span> le badge vérifié ne donne pas d'immunité modération</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="mode-kids-pour-utilisateurs-13-15-ans">Mode Kids pour utilisateurs 13-15 ans</h1>
<blockquote>
<p><em>En tant que parent ou adolescent</em>
<em>Je veux activer un mode Kids avec filtrage de contenu</em>
<em>Afin de protéger les mineurs des contenus inappropriés</em></p>
</blockquote>
<p><strong>15 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible</p>
</blockquote>
<h2 id="1-activation-manuelle-du-mode-kids">1. Activation manuelle du mode Kids</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur de 14 ans
<span style="color: #9E9E9E"><strong>Et</strong></span> que le mode Kids n'est pas activé par défaut</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'active le mode Kids dans les paramètres</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le mode Kids est activé sur mon compte
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Mode Kids activé - Contenus filtrés pour 13-15 ans"</p>
<hr />
<h2 id="2-parent-active-le-mode-kids-pour-son-enfant">2. Parent active le mode Kids pour son enfant</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis le parent d'un utilisateur de 13 ans
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai accès au compte de mon enfant</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'active le mode Kids</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le mode Kids est activé sur le compte enfant
<span style="color: #9E9E9E"><strong>Et</strong></span> seuls les contenus "Tous publics" sont accessibles</p>
<hr />
<h2 id="3-filtrage-contenu-uniquement-tous-publics">3. Filtrage contenu - uniquement "Tous publics"</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le mode Kids est activé sur mon compte
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il existe des contenus avec les classifications:</p>
<pre><code>| classification | nombre |
|---|---|
| Tous publics | 100 |
| 13+ | 50 |
| 16+ | 30 |
| 18+ | 20 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je demande des recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seuls les 100 contenus "Tous publics" sont proposés
<span style="color: #9E9E9E"><strong>Et</strong></span> les contenus 13+, 16+, 18+ sont exclus</p>
<hr />
<h2 id="4-exclusion-automatique-du-contenu-politique">4. Exclusion automatique du contenu politique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le mode Kids est activé
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il existe 20 contenus "Tous publics" dont 5 tagués "Politique"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je demande des recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seuls les 15 contenus non-politiques sont proposés
<span style="color: #9E9E9E"><strong>Et</strong></span> les 5 contenus politiques sont automatiquement exclus</p>
<hr />
<h2 id="5-pas-de-publicite-en-mode-kids">5. Pas de publicité en mode Kids</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le mode Kids est activé
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis un utilisateur gratuit</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute du contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune publicité n'est diffusée
<span style="color: #9E9E9E"><strong>Et</strong></span> je n'ai pas d'insertion publicitaire (règle 1/5 désactivée)</p>
<hr />
<h2 id="6-publicite-validee-manuellement-en-mode-kids-post-mvp">6. Publicité validée manuellement en mode Kids (post-MVP)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le mode Kids est activé
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'une publicité a été validée manuellement pour le mode Kids</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'écoute du contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> cette publicité peut être diffusée
<span style="color: #F44336"><strong>Mais</strong></span> la fréquence reste inférieure au mode standard</p>
<hr />
<h2 id="7-interface-standard-meme-en-mode-kids">7. Interface standard même en mode Kids</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le mode Kids est activé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ouvre l'application</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'interface est identique au mode normal
<span style="color: #9E9E9E"><strong>Et</strong></span> seul le filtrage de contenu est actif (pas d'UI enfant)</p>
<hr />
<h2 id="8-desactivation-du-mode-kids">8. Désactivation du mode Kids</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le mode Kids est activé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je désactive le mode Kids dans les paramètres</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les contenus sont à nouveau accessibles selon mon âge
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Mode Kids désactivé"</p>
<hr />
<h2 id="9-utilisateur-16-ans-ne-peut-pas-activer-le-mode-kids-13-15-ans">9. Utilisateur 16 ans ne peut pas activer le mode Kids 13-15 ans</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur de 16 ans</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie d'activer le mode Kids</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'activation réussit
<span style="color: #9E9E9E"><strong>Et</strong></span> le mode Kids filtre les contenus 16+ et 18+ (pas seulement 13+)
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois uniquement les contenus "Tous publics"</p>
<hr />
<h2 id="10-tentative-dacces-direct-a-contenu-16-en-mode-kids">10. Tentative d'accès direct à contenu 16+ en mode Kids</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le mode Kids est activé
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un ami me partage un contenu 16+</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie d'accéder au contenu via le lien</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'accès est refusé
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Ce contenu n'est pas accessible en mode Kids"</p>
<hr />
<h2 id="11-recherche-en-mode-kids-filtre-automatiquement">11. Recherche en mode Kids filtre automatiquement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le mode Kids est activé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je recherche "débat"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seuls les contenus "Tous publics" apparaissent dans les résultats
<span style="color: #9E9E9E"><strong>Et</strong></span> les contenus 13+, 16+, 18+ sont exclus de la recherche</p>
<hr />
<h2 id="12-audio-guide-en-mode-kids">12. Audio-guide en mode Kids</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le mode Kids est activé
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un audio-guide "Tous publics" existe au musée du Louvre</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je suis à proximité du Louvre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'audio-guide est proposé normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes les séquences sont accessibles</p>
<hr />
<h2 id="13-statistiques-createur-audience-mode-kids">13. Statistiques créateur - audience mode Kids</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur
<span style="color: #9E9E9E"><strong>Et</strong></span> que mes contenus "Tous publics" sont écoutés par des utilisateurs mode Kids</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mes statistiques</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois le pourcentage d'écoutes en mode Kids
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux adapter mes contenus en conséquence</p>
<hr />
<h2 id="14-notification-lors-de-lactivation-du-mode-kids">14. Notification lors de l'activation du mode Kids</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'active le mode Kids</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une notification explicative:</p>
<pre><code>| information | description |
|---|---|
| Contenu | Seuls les contenus "Tous publics" accessibles |
| Politique | Contenus politiques automatiquement masqués |
| Publicité | Aucune publicité affichée |
</code></pre>
<hr />
<h2 id="15-badge-mode-kids-visible-dans-le-profil">15. Badge mode Kids visible dans le profil</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le mode Kids est activé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mon profil</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois un badge "Mode Kids actif 🛡️"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux le désactiver en un clic</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="parametrabilite-admin-et-ab-testing">Paramétrabilité admin et A/B testing</h1>
<blockquote>
<p><em>En tant qu'administrateur RoadWave</em>
<em>Je veux configurer les paramètres de l'algorithme à chaud</em>
<em>Afin d'optimiser l'engagement sans redéploiement</em></p>
</blockquote>
<p><strong>20 scénarios</strong> (19 standards, 1 plan)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis connecté en tant qu'admin</p>
</blockquote>
<h2 id="1-acces-au-dashboard-admin">1. Accès au dashboard admin</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède au dashboard admin</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois tous les paramètres configurables de l'algorithme
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois les valeurs actuelles et par défaut</p>
<hr />
<h2 id="2-modifier-le-poids-geo-pour-contenu-ancre">2. Modifier le poids géo pour contenu ancré</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le poids_geo_ancre est à 0.7 (défaut)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je modifie le poids_geo_ancre à 0.8
<span style="color: #9E9E9E"><strong>Et</strong></span> que je sauvegarde</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la nouvelle valeur est appliquée immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> tous les nouveaux calculs utilisent 0.8
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Paramètre mis à jour avec succès"</p>
<hr />
<h2 id="3-validation-des-plages-de-valeurs">3. Validation des plages de valeurs</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de configurer poids_geo_ancre à 1.5 (hors plage 0.5-1.0)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la modification échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Valeur hors plage autorisée (0.5 - 1.0)"</p>
<hr />
<h2 id="4-plan-modification-de-tous-les-parametres-configurables">4. 📋 Plan: Modification de tous les paramètres configurables</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je modifie "<parametre>" à "<nouvelle_valeur>"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la modification est appliquée immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> la nouvelle valeur respecte la plage "<plage>"</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>parametre</th>
<th>nouvelle_valeur</th>
<th>plage</th>
</tr>
</thead>
<tbody>
<tr>
<td>poids_geo_ancre</td>
<td>0.8</td>
<td>0.5 - 1.0</td>
</tr>
<tr>
<td>poids_geo_contextuel</td>
<td>0.6</td>
<td>0.3 - 0.7</td>
</tr>
<tr>
<td>poids_geo_neutre</td>
<td>0.3</td>
<td>0.0 - 0.4</td>
</tr>
<tr>
<td>poids_engagement</td>
<td>0.3</td>
<td>0.0 - 0.5</td>
</tr>
<tr>
<td>part_aleatoire_global</td>
<td>0.15</td>
<td>0.0 - 0.3</td>
</tr>
<tr>
<td>distance_max_km</td>
<td>150</td>
<td>50 - 500</td>
</tr>
<tr>
<td>rayon_gps_point_m</td>
<td>1000</td>
<td>100 - 2000</td>
</tr>
<tr>
<td>seuil_min_ecoutes_engagement</td>
<td>100</td>
<td>10 - 200</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="5-aucun-recalcul-batch-apres-modification">5. Aucun recalcul batch après modification</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le poids_geo_ancre est modifié de 0.7 à 0.8</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la modification est appliquée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucun recalcul batch n'est lancé (économie CPU)
<span style="color: #9E9E9E"><strong>Et</strong></span> seuls les nouveaux calculs utilisent la valeur 0.8</p>
<hr />
<h2 id="6-versioning-des-configurations">6. Versioning des configurations</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je modifie plusieurs paramètres</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je sauvegarde la configuration</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une nouvelle version est créée (ex: v1.2.3)
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux voir l'historique des versions
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux comparer deux versions</p>
<hr />
<h2 id="7-rollback-en-1-clic">7. Rollback en 1 clic</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la configuration actuelle est v1.2.3
<span style="color: #9E9E9E"><strong>Et</strong></span> que la version précédente était v1.2.2</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Restaurer v1.2.2"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les paramètres de v1.2.2 sont réappliqués
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Configuration restaurée à v1.2.2"</p>
<hr />
<h2 id="8-creer-une-variante-ab-testing">8. Créer une variante A/B testing</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je crée une nouvelle variante "Test engagement élevé"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je configure:</p>
<pre><code>| parametre | valeur |
|---|---|
| poids_engagement | 0.4 |
| poids_geo_ancre | 0.6 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> que je lance le test A/B</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 50% des utilisateurs reçoivent la Config A (défaut)
<span style="color: #9E9E9E"><strong>Et</strong></span> 50% des utilisateurs reçoivent la Config B (test)</p>
<hr />
<h2 id="9-split-utilisateurs-aleatoire-pour-ab-test">9. Split utilisateurs aléatoire pour A/B test</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un test A/B est actif</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> 1000 nouveaux utilisateurs se connectent</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> environ 500 sont assignés à la Config A
<span style="color: #9E9E9E"><strong>Et</strong></span> environ 500 sont assignés à la Config B
<span style="color: #9E9E9E"><strong>Et</strong></span> l'assignation est aléatoire et équilibrée</p>
<hr />
<h2 id="10-utilisateur-reste-dans-la-meme-variante">10. Utilisateur reste dans la même variante</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur est assigné à la Config B</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> il se reconnecte plusieurs fois</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il reste toujours dans la Config B
<span style="color: #9E9E9E"><strong>Et</strong></span> il ne change pas de variante pendant le test</p>
<hr />
<h2 id="11-metriques-comparatives-ab-testing">11. Métriques comparatives A/B testing</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un test A/B est actif depuis 7 jours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte le dashboard A/B testing</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois les métriques suivantes pour chaque config:</p>
<pre><code>| 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 (&lt;10s) | 12% | 9% |
</code></pre>
<hr />
<h2 id="12-dashboard-graphique-temps-reel">12. Dashboard graphique temps réel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un test A/B est actif</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte le dashboard</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois des graphiques temps réel:</p>
<pre><code>| graphique | type |
|---|---|
| Évolution engagement | Ligne |
| Répartition utilisateurs | Camembert |
| Taux complétion | Barres |
| Durée session | Ligne |
</code></pre>
<hr />
<h2 id="13-terminer-un-test-ab-et-appliquer-la-meilleure-config">13. Terminer un test A/B et appliquer la meilleure config</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un test A/B montre que Config B est meilleure</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Appliquer Config B pour tous"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la Config B devient la configuration par défaut
<span style="color: #9E9E9E"><strong>Et</strong></span> tous les utilisateurs utilisent maintenant Config B
<span style="color: #9E9E9E"><strong>Et</strong></span> l'ancien test est archivé</p>
<hr />
<h2 id="14-audit-engagement-global">14. Audit engagement global</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte la section "Audit engagement"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| 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% |
</code></pre>
<hr />
<h2 id="15-graphiques-evolution-engagement-selon-config">15. Graphiques évolution engagement selon config</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que plusieurs modifications de config ont été faites</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les graphiques d'évolution</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois l'impact de chaque changement de config
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux corréler changements config avec métriques</p>
<hr />
<h2 id="16-export-csv-pour-analyse-externe">16. Export CSV pour analyse externe</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Exporter données"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux télécharger un CSV avec:</p>
<pre><code>| colonne | exemple |
|---|---|
| date | 2026-01-21 |
| version_config | v1.2.3 |
| taux_completion | 0.72 |
| engagement_moyen | 0.45 |
| duree_session_min | 27 |
</code></pre>
<hr />
<h2 id="17-alerte-automatique-si-metrique-critique-baisse">17. Alerte automatique si métrique critique baisse</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le taux de complétion moyen est à 70%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> une nouvelle config fait baisser le taux à 55%</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois une alerte email "Baisse critique du taux de complétion"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux rollback rapidement</p>
<hr />
<h2 id="18-previsualisation-impact-avant-application">18. Prévisualisation impact avant application</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je modifie poids_geo_ancre de 0.7 à 0.9</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Prévisualiser impact"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois une simulation sur échantillon de 1000 utilisateurs
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois l'estimation d'impact sur les métriques clés</p>
<hr />
<h2 id="19-notes-et-commentaires-sur-modifications-config">19. Notes et commentaires sur modifications config</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je modifie une configuration</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux ajouter une note "Test pour améliorer contenu local"
<span style="color: #9E9E9E"><strong>Et</strong></span> cette note est visible dans l'historique des versions
<span style="color: #9E9E9E"><strong>Et</strong></span> l'équipe peut comprendre le contexte des changements</p>
<hr />
<h2 id="20-permissions-admin-pour-modification-config">20. Permissions admin pour modification config</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un admin junior</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de modifier un paramètre critique</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'accès est refusé
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois "Permission admin senior requise"
<span style="color: #9E9E9E"><strong>Et</strong></span> seuls les admins seniors peuvent modifier les paramètres</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="parametrabilite-utilisateur-et-profils">Paramétrabilité utilisateur et profils</h1>
<blockquote>
<p><em>En tant qu'utilisateur</em>
<em>Je veux personnaliser mon expérience de recommandation</em>
<em>Afin d'adapter l'application à mes différents contextes d'usage</em></p>
</blockquote>
<p><strong>25 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis connecté</p>
</blockquote>
<h2 id="1-acces-aux-parametres-de-personnalisation">1. Accès aux paramètres de personnalisation</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ouvre les paramètres de personnalisation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois trois curseurs disponibles:</p>
<pre><code>| curseur | description |
|---|---|
| Géolocalisation | Local ← slider → National |
| Découverte | 0% ← slider → 50% |
| Politique | Masquer / Équilibré / Mes préférences |
</code></pre>
<hr />
<h2 id="2-modifier-le-curseur-geolocalisation-vers-local">2. Modifier le curseur Géolocalisation vers Local</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le curseur Géolocalisation est au centre (défaut)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je déplace le curseur vers "Local" (gauche)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'algorithme privilégie fortement les contenus proches
<span style="color: #9E9E9E"><strong>Et</strong></span> la pondération géographique augmente
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Recommandations locales privilégiées"</p>
<hr />
<h2 id="3-modifier-le-curseur-geolocalisation-vers-national">3. Modifier le curseur Géolocalisation vers National</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le curseur Géolocalisation est au centre</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je déplace le curseur vers "National" (droite)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'algorithme privilégie la découverte nationale
<span style="color: #9E9E9E"><strong>Et</strong></span> la pondération géographique diminue
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois des contenus de toute la France</p>
<hr />
<h2 id="4-curseur-decouverte-a-0-aucun-aleatoire">4. Curseur Découverte à 0% - aucun aléatoire</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je règle le curseur Découverte à 0%</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 0% de contenus aléatoires dans mes recommandations
<span style="color: #9E9E9E"><strong>Et</strong></span> 100% de contenus calculés selon score combiné
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Personnalisation maximale"</p>
<hr />
<h2 id="5-curseur-decouverte-a-10-defaut-equilibre">5. Curseur Découverte à 10% - défaut équilibré</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je règle le curseur Découverte à 10%</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 10% de contenus aléatoires
<span style="color: #9E9E9E"><strong>Et</strong></span> 90% de contenus calculés
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Équilibre découverte/personnalisation"</p>
<hr />
<h2 id="6-curseur-decouverte-a-30-decouverte-elevee">6. Curseur Découverte à 30% - découverte élevée</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je règle le curseur Découverte à 30%</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 30% de contenus aléatoires
<span style="color: #9E9E9E"><strong>Et</strong></span> 70% de contenus calculés
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Découverte élevée activée"</p>
<hr />
<h2 id="7-curseur-decouverte-a-50-decouverte-maximale">7. Curseur Découverte à 50% - découverte maximale</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je règle le curseur Découverte à 50%</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 50% de contenus aléatoires
<span style="color: #9E9E9E"><strong>Et</strong></span> 50% de contenus calculés
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Découverte maximale (équivaut à national)"</p>
<hr />
<h2 id="8-creer-un-profil-personnalise-trajet-quotidien">8. Créer un profil personnalisé "Trajet quotidien"</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je crée un nouveau profil nommé "🚗 Trajet quotidien"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je configure:</p>
<pre><code>| parametre | valeur |
|---|---|
| Géolocalisation | Local |
| Découverte | 5% |
| Politique | Masquer |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> que je sauvegarde</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le profil "🚗 Trajet quotidien" est créé
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux l'activer en un clic</p>
<hr />
<h2 id="9-creer-un-profil-road-trip">9. Créer un profil "Road trip"</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je crée un profil "🛣️ Road trip"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je configure:</p>
<pre><code>| parametre | valeur |
|---|---|
| Géolocalisation | Régional |
| Découverte | 30% |
| Politique | Équilibré |
</code></pre>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le profil est sauvegardé
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux switcher entre profils facilement</p>
<hr />
<h2 id="10-creer-un-profil-enfants">10. Créer un profil "Enfants"</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je crée un profil "👶 Enfants"
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'active le Mode Kids</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les paramètres sont adaptés pour enfants:</p>
<pre><code>| parametre | valeur |
|---|---|
| Mode Kids | Activé |
| Politique | Masquer (forcé) |
| Publicité | Aucune |
</code></pre>
<hr />
<h2 id="11-activer-un-profil-existant">11. Activer un profil existant</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai créé un profil "🚗 Trajet quotidien"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Activer" pour ce profil</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les paramètres du profil sont appliqués
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Profil 'Trajet quotidien' activé"
<span style="color: #9E9E9E"><strong>Et</strong></span> l'algorithme utilise ces paramètres immédiatement</p>
<hr />
<h2 id="12-synchronisation-profils-entre-devices">12. Synchronisation profils entre devices</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai créé 3 profils sur mon iPhone</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me connecte sur mon iPad</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mes 3 profils sont automatiquement synchronisés
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux les utiliser sur l'iPad</p>
<hr />
<h2 id="13-modification-dun-profil-synchronisee">13. Modification d'un profil synchronisée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai un profil "Road trip" sur iPhone</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je modifie ce profil sur iPhone</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la modification est synchronisée sur tous mes devices
<span style="color: #9E9E9E"><strong>Et</strong></span> le profil est mis à jour partout en temps réel</p>
<hr />
<h2 id="14-pas-de-partage-de-profils-entre-utilisateurs">14. Pas de partage de profils entre utilisateurs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai créé des profils personnalisés
<span style="color: #9E9E9E"><strong>Et</strong></span> que ma conjointe a un compte RoadWave</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> elle se connecte sur son compte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> elle ne voit pas mes profils
<span style="color: #9E9E9E"><strong>Et</strong></span> chaque utilisateur a ses propres profils</p>
<hr />
<h2 id="15-auto-switch-selon-contexte-detection-trajet-recurrent">15. Auto-switch selon contexte (détection trajet récurrent)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'utilise toujours le profil "Trajet quotidien"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je pars de chez moi vers mon travail tous les matins à 8h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte ce trajet récurrent</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le profil "Trajet quotidien" est activé automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois une notification "Profil 'Trajet quotidien' activé"</p>
<hr />
<h2 id="16-desactiver-lauto-switch">16. Désactiver l'auto-switch</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'auto-switch de profil est actif</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je désactive cette option dans les paramètres</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les profils ne changent plus automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois les activer manuellement</p>
<hr />
<h2 id="17-blocage-modification-si-vitesse-gps-10-kmh">17. Blocage modification si vitesse GPS &gt;10 km/h</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je conduis à 50 km/h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de modifier un curseur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la modification est bloquée
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Modification impossible pendant la conduite"
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois m'arrêter ou être passager pour modifier</p>
<hr />
<h2 id="18-modification-possible-si-vitesse-10-kmh">18. Modification possible si vitesse &lt;10 km/h</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis arrêté à un feu rouge (5 km/h)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de modifier un curseur</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la modification est autorisée
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux ajuster les paramètres</p>
<hr />
<h2 id="19-warning-au-lancement-app">19. Warning au lancement app</h2>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je lance l'application pour la première fois</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois un warning "Configurez vos préférences avant de prendre la route"
<span style="color: #9E9E9E"><strong>Et</strong></span> un bouton "Configurer maintenant"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux accéder rapidement aux paramètres</p>
<hr />
<h2 id="20-modification-uniquement-app-arretee-ou-mode-passager">20. Modification uniquement app arrêtée ou mode passager</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis passager dans une voiture
<span style="color: #9E9E9E"><strong>Et</strong></span> que le mode passager est activé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de modifier les paramètres</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la modification est autorisée
<span style="color: #9E9E9E"><strong>Et</strong></span> le blocage vitesse GPS ne s'applique pas</p>
<hr />
<h2 id="21-statistiques-dutilisation-des-profils">21. Statistiques d'utilisation des profils</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'utilise plusieurs profils</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte mes statistiques</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois:</p>
<pre><code>| metrique | exemple |
|---|---|
| Profil le plus utilisé | Trajet quotidien |
| Heures par profil | 25h / 10h / 5h |
| Dernier profil actif | Road trip |
</code></pre>
<hr />
<h2 id="22-supprimer-un-profil">22. Supprimer un profil</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai créé un profil "Test"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je supprime ce profil</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le profil est définitivement supprimé
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Profil 'Test' supprimé"
<span style="color: #9E9E9E"><strong>Et</strong></span> il disparaît de tous mes devices</p>
<hr />
<h2 id="23-limite-de-profils-par-utilisateur">23. Limite de profils par utilisateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai créé 10 profils</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de créer un 11ème profil</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la création échoue
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois le message "Maximum 10 profils par utilisateur"</p>
<hr />
<h2 id="24-dupliquer-un-profil-existant">24. Dupliquer un profil existant</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai un profil "Trajet quotidien"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Dupliquer"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un nouveau profil "Trajet quotidien (copie)" est créé
<span style="color: #9E9E9E"><strong>Et</strong></span> il a les mêmes paramètres que l'original
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux le modifier indépendamment</p>
<hr />
<h2 id="25-reinitialiser-un-profil-aux-valeurs-par-defaut">25. Réinitialiser un profil aux valeurs par défaut</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai modifié un profil</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Réinitialiser"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les paramètres reviennent aux valeurs par défaut:</p>
<pre><code>| parametre | valeur défaut |
|---|---|
| Géolocalisation | Équilibré |
| Découverte | 10% |
| Politique | Équilibré |
</code></pre>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="formule-de-scoring-et-recommandation">Formule de scoring et recommandation</h1>
<blockquote>
<p><em>En tant que système de recommandation</em>
<em>Je veux calculer un score combiné pour chaque contenu</em>
<em>Afin de proposer les contenus les plus pertinents à l'utilisateur</em></p>
</blockquote>
<p><strong>21 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'API RoadWave est disponible</p>
</blockquote>
<h2 id="1-calcul-du-score-geographique-lineaire">1. Calcul du score géographique linéaire</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu existe à Paris
<span style="color: #9E9E9E"><strong>Et</strong></span> que la distance_max_km est configurée à 200 km</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un utilisateur est à 50 km du contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le score_geo = 1 - (50 / 200) = 0.75</p>
<hr />
<h2 id="2-score-geo-a-distance-nulle-sur-place">2. Score géo à distance nulle (sur place)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu existe à un point GPS précis</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un utilisateur est exactement au même point (0 km)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le score_geo = 1.0 (maximum)</p>
<hr />
<h2 id="3-score-geo-a-distance_max-200-km">3. Score géo à distance_max (200 km)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu existe à Paris</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un utilisateur est à 200 km du contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le score_geo = 1 - (200 / 200) = 0.0</p>
<hr />
<h2 id="4-score-geo-au-dela-de-distance_max">4. Score géo au-delà de distance_max</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu existe à Paris</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un utilisateur est à 250 km du contenu (au-delà de 200 km max)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le score_geo = 0.0 (minimum)
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu a peu de chances d'être recommandé sauf engagement très élevé</p>
<hr />
<h2 id="5-calcul-du-score-dinterets-avec-jauges-utilisateur">5. Calcul du score d'intérêts avec jauges utilisateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a les jauges suivantes:</p>
<pre><code>| categorie | niveau |
|---|---|
| Automobile | 80% |
| Voyage | 60% |
| Musique | 40% |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> qu'un contenu est tagué "Automobile" et "Voyage"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme calcule le score_interets</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> score_interets = (0.8 + 0.6) / 2 = 0.7</p>
<hr />
<h2 id="6-score-dinterets-avec-un-seul-tag">6. Score d'intérêts avec un seul tag</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a la jauge "Économie" à 90%
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un contenu est tagué uniquement "Économie"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme calcule le score_interets</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> score_interets = 0.9</p>
<hr />
<h2 id="7-score-dinterets-avec-tags-non-matches">7. Score d'intérêts avec tags non matchés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a des jauges "Sport" et "Politique" élevées
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un contenu est tagué "Musique" et "Philosophie"
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur n'a pas ces catégories</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme calcule le score_interets</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> score_interets = 0.5 (neutre par défaut pour catégories inconnues)</p>
<hr />
<h2 id="8-calcul-du-score-dengagement-avec-metriques">8. Calcul du score d'engagement avec métriques</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu a:</p>
<pre><code>| metrique | valeur |
|---|---|
| ecoutes | 1000 |
| ecoutes_completes | 700 |
| likes | 300 |
| abonnements_apres | 50 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme calcule le score_engagement</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> taux_completion = 700 / 1000 = 0.7
<span style="color: #9E9E9E"><strong>Et</strong></span> ratio_likes = 300 / 1000 = 0.3
<span style="color: #9E9E9E"><strong>Et</strong></span> ratio_abonnements = 50 / 1000 = 0.05
<span style="color: #9E9E9E"><strong>Et</strong></span> score_engagement = (0.7 × 0.5) + (0.3 × 0.3) + (0.05 × 0.2) = 0.35 + 0.09 + 0.01 = 0.45</p>
<hr />
<h2 id="9-contenu-avec-moins-de-50-ecoutes-score-neutre">9. Contenu avec moins de 50 écoutes - score neutre</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu a seulement 30 écoutes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme calcule le score_engagement</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> score_engagement = 0.5 (neutre par défaut)
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu n'est pas pénalisé pour manque de données</p>
<hr />
<h2 id="10-contenu-avec-exactement-50-ecoutes-calcul-reel">10. Contenu avec exactement 50 écoutes - calcul réel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu a exactement 50 écoutes
<span style="color: #9E9E9E"><strong>Et</strong></span> des métriques d'engagement complètes</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme calcule le score_engagement</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le score est calculé normalement (pas de seuil neutre)</p>
<hr />
<h2 id="11-bonus-aleatoire-10-des-recommandations">11. Bonus aléatoire - 10% des recommandations</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur demande 10 recommandations
<span style="color: #9E9E9E"><strong>Et</strong></span> que la part_aleatoire_global est à 10%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme génère les recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 1 contenu sur 10 est tiré aléatoirement
<span style="color: #9E9E9E"><strong>Et</strong></span> 9 contenus sont calculés avec le score combiné
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu aléatoire n'est pas dans l'historique déjà écouté</p>
<hr />
<h2 id="12-curseur-utilisateur-decouverte-a-0-aucun-aleatoire">12. Curseur utilisateur découverte à 0% - aucun aléatoire</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur configure le curseur découverte à 0%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur demande 20 recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les 20 contenus sont calculés avec le score combiné
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun contenu aléatoire n'est proposé</p>
<hr />
<h2 id="13-curseur-utilisateur-decouverte-a-50-decouverte-max">13. Curseur utilisateur découverte à 50% - découverte max</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur configure le curseur découverte à 50%</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur demande 20 recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 10 contenus sont tirés aléatoirement
<span style="color: #9E9E9E"><strong>Et</strong></span> 10 contenus sont calculés avec le score combiné</p>
<hr />
<h2 id="14-score-final-combine-pour-contenu-geo-ancre">14. Score final combiné pour contenu géo-ancré</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu "Géo-ancré" a:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme calcule le score_final</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> score_final = (0.9 × 0.7) + (0.6 × 0.1) + (0.45 × 0.2)
<span style="color: #9E9E9E"><strong>Et</strong></span> score_final = 0.63 + 0.06 + 0.09 = 0.78</p>
<hr />
<h2 id="15-score-final-combine-pour-contenu-geo-neutre">15. Score final combiné pour contenu géo-neutre</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu "Géo-neutre" a:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme calcule le score_final</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> score_final = (0.3 × 0.2) + (0.9 × 0.6) + (0.6 × 0.2)
<span style="color: #9E9E9E"><strong>Et</strong></span> score_final = 0.06 + 0.54 + 0.12 = 0.72
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu peut être recommandé malgré la distance</p>
<hr />
<h2 id="16-contenu-viral-lointain-peut-etre-recommande">16. Contenu viral lointain peut être recommandé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contenu viral existe à Paris
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il a un score_engagement très élevé de 0.95
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'un utilisateur est à Marseille (score_geo = 0.1)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme calcule le score_final</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le score_engagement élevé compense le score_geo faible
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu peut apparaître dans les recommandations</p>
<hr />
<h2 id="17-ordre-de-recommandation-par-score-decroissant">17. Ordre de recommandation par score décroissant</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> 5 contenus avec les scores suivants:</p>
<pre><code>| contenu | score_final |
|---|---|
| Contenu A | 0.85 |
| Contenu B | 0.72 |
| Contenu C | 0.90 |
| Contenu D | 0.65 |
| Contenu E | 0.78 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur demande des recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'ordre de proposition est:</p>
<pre><code>| position | contenu |
|---|---|
| 1 | Contenu C |
| 2 | Contenu A |
| 3 | Contenu E |
| 4 | Contenu B |
| 5 | Contenu D |
</code></pre>
<hr />
<h2 id="18-exclusion-de-lhistorique-deja-ecoute-80">18. Exclusion de l'historique déjà écouté &gt;80%</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur a écouté les contenus suivants:</p>
<pre><code>| contenu | completion |
|---|---|
| Contenu A | 85% |
| Contenu B | 95% |
| Contenu C | 30% |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme génère les recommandations</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> "Contenu A" et "Contenu B" ne sont jamais proposés
<span style="color: #F44336"><strong>Mais</strong></span> "Contenu C" peut être reproposé</p>
<hr />
<h2 id="19-pre-calcul-de-5-contenus-suivants">19. Pré-calcul de 5 contenus suivants</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un utilisateur écoute un contenu</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'algorithme prépare les contenus suivants</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> 5 contenus sont pré-calculés selon le score
<span style="color: #9E9E9E"><strong>Et</strong></span> ces contenus sont mis en cache pour performance</p>
<hr />
<h2 id="20-recalcul-si-deplacement-10-km">20. Recalcul si déplacement &gt;10 km</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 5 contenus suivants sont pré-calculés
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'utilisateur se déplace de 12 km</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur demande le contenu suivant</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'algorithme recalcule les scores avec la nouvelle position
<span style="color: #9E9E9E"><strong>Et</strong></span> propose de nouveaux contenus plus pertinents géographiquement</p>
<hr />
<h2 id="21-recalcul-apres-10-minutes-dinactivite">21. Recalcul après 10 minutes d'inactivité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 5 contenus suivants sont pré-calculés
<span style="color: #9E9E9E"><strong>Et</strong></span> que 11 minutes se sont écoulées sans action</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'utilisateur demande le contenu suivant</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'algorithme recalcule les scores
<span style="color: #9E9E9E"><strong>Et</strong></span> prend en compte les nouveaux contenus publiés</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="anonymisation-des-donnees-gps-apres-24h">Anonymisation des données GPS après 24h</h1>
<p><strong>18 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur avec le GPS activé
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'utilise l'application depuis plusieurs jours</p>
</blockquote>
<h2 id="1-conservation-des-donnees-gps-precises-pendant-24h">1. Conservation des données GPS précises pendant 24h</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'écoute un contenu à la position GPS 48.8566, 2.3522 (Paris, Tour Eiffel)
<span style="color: #9E9E9E"><strong>Et</strong></span> qu'il est 10:00 le 2025-01-20</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'événement d'écoute est enregistré en base de données</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les coordonnées précises 48.8566, 2.3522 sont stockées
<span style="color: #9E9E9E"><strong>Et</strong></span> le champ <code>anonymized</code> est à <code>false</code>
<span style="color: #9E9E9E"><strong>Et</strong></span> le champ <code>created_at</code> contient "2025-01-20 10:00:00"
<span style="color: #9E9E9E"><strong>Et</strong></span> ces données précises servent à la recommandation personnalisée</p>
<hr />
<h2 id="2-conversion-en-geohash-apres-24h">2. Conversion en geohash après 24h</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai écouté un contenu le 2025-01-20 à 10:00 à la position 48.8566, 2.3522</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le job quotidien d'anonymisation s'exécute le 2025-01-21 à 02:00</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les coordonnées précises sont converties en geohash précision 5
<span style="color: #9E9E9E"><strong>Et</strong></span> le geohash correspond à une zone d'environ 5km²
<span style="color: #9E9E9E"><strong>Et</strong></span> les coordonnées originales 48.8566, 2.3522 sont supprimées définitivement
<span style="color: #9E9E9E"><strong>Et</strong></span> le champ <code>anonymized</code> passe à <code>true</code>
<span style="color: #9E9E9E"><strong>Et</strong></span> il est impossible de retrouver la position précise d'origine</p>
<hr />
<h2 id="3-requete-sql-danonymisation-postgis">3. Requête SQL d'anonymisation (PostGIS)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le job quotidien d'anonymisation s'exécute</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la requête SQL suivante est exécutée:</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> toutes les positions vieilles de plus de 24h sont anonymisées
<span style="color: #9E9E9E"><strong>Et</strong></span> le processus est automatique et irréversible
<span style="color: #9E9E9E"><strong>Et</strong></span> les données sont conformes RGPD</p>
<hr />
<h2 id="4-precision-du-geohash-niveau-5">4. Précision du geohash niveau 5</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une position GPS est convertie en geohash précision 5</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on analyse la zone couverte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la zone fait environ 5km² (4.9km × 4.9km)
<span style="color: #9E9E9E"><strong>Et</strong></span> cette précision est suffisante pour des analytics agrégées
<span style="color: #9E9E9E"><strong>Et</strong></span> cette précision ne permet pas d'identifier un individu (conformité CNIL)</p>
<hr />
<h2 id="5-exemple-de-conversion-paris">5. Exemple de conversion Paris</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que ma position précise est 48.8566, 2.3522 (Tour Eiffel)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la conversion en geohash précision 5 est appliquée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le geohash généré est "u09wh"
<span style="color: #9E9E9E"><strong>Et</strong></span> ce geohash couvre une zone de ~5km² autour de la Tour Eiffel
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes les positions dans cette zone partagent le même geohash
<span style="color: #9E9E9E"><strong>Et</strong></span> il est impossible de distinguer deux utilisateurs dans cette zone</p>
<hr />
<h2 id="6-conservation-de-lhistorique-personnel-utilisateur">6. Conservation de l'historique personnel utilisateur</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai écouté des contenus aux positions suivantes:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ouvre mon historique personnel dans "Profil &gt; Mes trajets"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois mes trajets avec les positions précises intégrales
<span style="color: #9E9E9E"><strong>Et</strong></span> ces données ne sont pas anonymisées tant que mon compte est actif
<span style="color: #9E9E9E"><strong>Et</strong></span> seul moi peut accéder à ces données
<span style="color: #9E9E9E"><strong>Et</strong></span> elles ne sont pas utilisées pour des analytics globales</p>
<hr />
<h2 id="7-anonymisation-pour-analytics-globales-uniquement">7. Anonymisation pour analytics globales uniquement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave génère des analytics agrégées</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'équipe analyse les zones géographiques populaires</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seules les données anonymisées (geohash) sont utilisées
<span style="color: #9E9E9E"><strong>Et</strong></span> les positions précises de l'historique personnel ne sont jamais agrégées
<span style="color: #9E9E9E"><strong>Et</strong></span> les heatmaps de trafic utilisent uniquement les geohash ~5km²</p>
<hr />
<h2 id="8-planification-du-job-danonymisation">8. Planification du job d'anonymisation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le système est en production</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on consulte les jobs planifiés (cron)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un job "anonymize_gps_data" est configuré
<span style="color: #9E9E9E"><strong>Et</strong></span> le job s'exécute tous les jours à 02:00 (heure creuse)
<span style="color: #9E9E9E"><strong>Et</strong></span> le job traite toutes les positions vieilles de plus de 24h
<span style="color: #9E9E9E"><strong>Et</strong></span> un log est généré pour traçabilité</p>
<hr />
<h2 id="9-execution-du-job-avec-metriques">9. Exécution du job avec métriques</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le job d'anonymisation s'exécute le 2025-01-21 à 02:00</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le job se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un rapport est généré avec:</p>
<pre><code>| métrique | valeur |
|---|---|
| Nombre de positions traitées | 15420 |
| Nombre de positions anonymisées | 15420 |
| Durée d'exécution | 3.5s |
| Erreurs | 0 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> le rapport est loggé dans Sentry/Grafana
<span style="color: #9E9E9E"><strong>Et</strong></span> une alerte est envoyée si le job échoue</p>
<hr />
<h2 id="10-performances-du-job-danonymisation">10. Performances du job d'anonymisation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que 100 000 positions doivent être anonymisées</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le job s'exécute</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le traitement se fait en moins de 30 secondes
<span style="color: #9E9E9E"><strong>Et</strong></span> la requête PostGIS est optimisée avec index
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun impact sur les performances de l'application en production</p>
<hr />
<h2 id="11-impossibilite-de-reidentification">11. Impossibilité de réidentification</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une position a été anonymisée en geohash "u09wh"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un attaquant tente de retrouver la position précise d'origine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> il est impossible de déterminer la position exacte
<span style="color: #9E9E9E"><strong>Et</strong></span> des milliers de positions précises correspondent au même geohash
<span style="color: #9E9E9E"><strong>Et</strong></span> il n'y a aucune traçabilité vers la position originale
<span style="color: #9E9E9E"><strong>Et</strong></span> cette anonymisation est irréversible</p>
<hr />
<h2 id="12-conformite-cnil-donnees-veritablement-anonymisees">12. Conformité CNIL - données véritablement anonymisées</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que les positions sont converties en geohash précision 5</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un auditeur CNIL vérifie la conformité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les données sont considérées comme véritablement anonymisées
<span style="color: #9E9E9E"><strong>Et</strong></span> elles ne sont plus considérées comme des données personnelles
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun consentement n'est requis pour leur traitement analytique
<span style="color: #9E9E9E"><strong>Et</strong></span> elles peuvent être conservées indéfiniment</p>
<hr />
<h2 id="13-heatmap-de-trafic-avec-donnees-anonymisees">13. Heatmap de trafic avec données anonymisées</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave génère une heatmap des zones populaires</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on analyse les données utilisées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seules les positions anonymisées (geohash) sont agrégées
<span style="color: #9E9E9E"><strong>Et</strong></span> la heatmap montre des zones de ~5km²
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune position précise n'est révélée
<span style="color: #9E9E9E"><strong>Et</strong></span> cette analyse ne nécessite pas de consentement utilisateur (données anonymes)</p>
<hr />
<h2 id="14-statistiques-geographiques-par-departement">14. Statistiques géographiques par département</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave analyse l'utilisation par département</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les statistiques sont générées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les données anonymisées sont agrégées par département
<span style="color: #9E9E9E"><strong>Et</strong></span> les résultats montrent: "Paris (75): 12 500 écoutes, Lyon (69): 8 300 écoutes"
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune donnée personnelle n'est révélée
<span style="color: #9E9E9E"><strong>Et</strong></span> les statistiques sont RGPD-compliant</p>
<hr />
<h2 id="15-cout-de-la-solution-danonymisation">15. Coût de la solution d'anonymisation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que PostGIS est utilisé pour l'anonymisation GPS</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on calcule le coût de la solution</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le coût est de 0€ (PostGIS inclus dans PostgreSQL)
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune librairie tierce n'est nécessaire
<span style="color: #9E9E9E"><strong>Et</strong></span> la solution est entièrement maîtrisée (self-hosted)</p>
<hr />
<h2 id="16-anonymisation-respecte-les-positions-en-cours-de-session">16. Anonymisation respecte les positions en cours de session</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis en train d'écouter du contenu actuellement
<span style="color: #9E9E9E"><strong>Et</strong></span> que certaines de mes positions ont plus de 24h</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le job d'anonymisation s'exécute</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mes positions de plus de 24h sont anonymisées
<span style="color: #F44336"><strong>Mais</strong></span> ma position actuelle (session en cours) reste précise
<span style="color: #9E9E9E"><strong>Et</strong></span> la recommandation continue de fonctionner normalement</p>
<hr />
<h2 id="17-suppression-de-compte-et-anonymisation-gps">17. Suppression de compte et anonymisation GPS</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je demande la suppression de mon compte</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le compte est supprimé (après grace period de 30j)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> toutes mes positions GPS (précises et anonymisées) sont supprimées
<span style="color: #9E9E9E"><strong>Et</strong></span> mon historique personnel de trajets est supprimé
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune donnée GPS ne subsiste, même anonymisée</p>
<hr />
<h2 id="18-export-de-donnees-avant-anonymisation">18. Export de données avant anonymisation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je demande un export de mes données
<span style="color: #9E9E9E"><strong>Et</strong></span> que certaines de mes positions ont été anonymisées</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'export est généré</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les positions précises de mon historique personnel sont incluses
<span style="color: #F44336"><strong>Mais</strong></span> les positions déjà anonymisées (&gt;24h, analytics) apparaissent en geohash
<span style="color: #9E9E9E"><strong>Et</strong></span> l'export précise quelles données ont été anonymisées et pourquoi</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="conformite-administrative-rgpd-registre-breach-dpo">Conformité administrative RGPD (Registre, Breach, DPO)</h1>
<p><strong>22 scénarios</strong> (21 standards, 1 plan)</p>
<hr />
<h2 id="1-registre-des-traitements-en-markdown-versionne-git">1. Registre des traitements en Markdown versionné Git</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave doit tenir un registre des traitements</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on consulte la documentation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un fichier <code>docs/rgpd/registre-traitements.md</code> existe
<span style="color: #9E9E9E"><strong>Et</strong></span> le fichier est versionné dans Git
<span style="color: #9E9E9E"><strong>Et</strong></span> l'historique des modifications est traçable via Git
<span style="color: #9E9E9E"><strong>Et</strong></span> chaque traitement est documenté dans une section dédiée</p>
<hr />
<h2 id="2-contenu-obligatoire-pour-chaque-traitement">2. Contenu obligatoire pour chaque traitement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le registre des traitements contient le traitement "Géolocalisation utilisateurs"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on lit la section correspondante</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les informations suivantes sont présentes:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="3-plan-traitements-documentes-dans-le-registre">3. 📋 Plan: Traitements documentés dans le registre</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le registre des traitements est complet</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on liste tous les traitements</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le traitement "<traitement>" est documenté avec la base légale "<base_legale>"</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>traitement</th>
<th>base_legale</th>
</tr>
</thead>
<tbody>
<tr>
<td>Géolocalisation utilisateurs</td>
<td>Consentement</td>
</tr>
<tr>
<td>Historique d'écoute</td>
<td>Intérêt légitime</td>
</tr>
<tr>
<td>Création de contenu</td>
<td>Exécution du contrat</td>
</tr>
<tr>
<td>Analytics (Matomo)</td>
<td>Consentement</td>
</tr>
<tr>
<td>Paiements (Mangopay)</td>
<td>Exécution du contrat</td>
</tr>
<tr>
<td>Modération contenus</td>
<td>Intérêt légitime</td>
</tr>
<tr>
<td>Notifications push</td>
<td>Consentement</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="4-review-trimestrielle-du-registre">4. Review trimestrielle du registre</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le registre des traitements existe</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on consulte l'historique Git</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une mise à jour est effectuée au moins tous les 3 mois
<span style="color: #9E9E9E"><strong>Et</strong></span> chaque mise à jour a un commit avec message explicite
<span style="color: #9E9E9E"><strong>Et</strong></span> un tag Git marque chaque review trimestrielle
<span style="color: #9E9E9E"><strong>Et</strong></span> les modifications sont traçables (auteur, date, changements)</p>
<hr />
<h2 id="5-mise-a-jour-immediate-si-nouveau-traitement">5. Mise à jour immédiate si nouveau traitement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une nouvelle fonctionnalité nécessite un traitement de données</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la fonctionnalité est développée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le registre est mis à jour AVANT le déploiement en production
<span style="color: #9E9E9E"><strong>Et</strong></span> le nouveau traitement est documenté complètement
<span style="color: #9E9E9E"><strong>Et</strong></span> un commit Git enregistre l'ajout
<span style="color: #9E9E9E"><strong>Et</strong></span> le DPO valide la conformité RGPD du nouveau traitement</p>
<hr />
<h2 id="6-migration-future-vers-interface-admin-postgresql">6. Migration future vers interface admin PostgreSQL</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave dépasse 100 000 utilisateurs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la complexité du registre augmente</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une interface admin PostgreSQL est développée
<span style="color: #9E9E9E"><strong>Et</strong></span> le registre Markdown est migré vers la base de données
<span style="color: #9E9E9E"><strong>Et</strong></span> l'historique Git est conservé pour audit
<span style="color: #9E9E9E"><strong>Et</strong></span> l'interface permet une gestion plus efficace des traitements</p>
<hr />
<h2 id="7-detection-automatique-devenements-critiques">7. Détection automatique d'événements critiques</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le système de monitoring est actif</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un événement critique se produit</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une alerte est envoyée selon le type d'événement:</p>
<pre><code>| é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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> les alertes permettent une réaction rapide</p>
<hr />
<h2 id="8-runbook-de-procedure-breach-disponible">8. Runbook de procédure breach disponible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une violation de données potentielle est détectée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'équipe consulte la documentation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un runbook <code>docs/rgpd/procedure-breach.md</code> existe
<span style="color: #9E9E9E"><strong>Et</strong></span> le runbook contient une checklist 72h CNIL
<span style="color: #9E9E9E"><strong>Et</strong></span> chaque étape est clairement documentée
<span style="color: #9E9E9E"><strong>Et</strong></span> les contacts d'urgence sont listés</p>
<hr />
<h2 id="9-checklist-72h-en-cas-de-breach">9. Checklist 72h en cas de breach</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une violation de données est confirmée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'équipe suit la procédure breach</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les étapes suivantes sont exécutées dans les délais:</p>
<pre><code>| 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é |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> chaque étape est documentée pour audit</p>
<hr />
<h2 id="10-evaluation-de-la-gravite-du-breach">10. Évaluation de la gravité du breach</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'une violation de données est détectée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'équipe évalue la gravité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les critères suivants sont analysés:</p>
<pre><code>| 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é |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> si le risque est élevé, la CNIL est notifiée sous 72h</p>
<hr />
<h2 id="11-notification-cnil-dans-les-72h">11. Notification CNIL dans les 72h</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un breach avec risque élevé est confirmé à 10:00 le 2025-01-20</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la gravité est évaluée comme nécessitant une notification</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la CNIL est notifiée avant 10:00 le 2025-01-23 (72h)
<span style="color: #9E9E9E"><strong>Et</strong></span> la notification contient:</p>
<pre><code>| information |
|---|
| Nature de la violation |
| Données concernées |
| Nombre d'utilisateurs impactés |
| Conséquences probables |
| Mesures prises |
| Mesures de remédiation |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> un email pré-rédigé (template) est utilisé pour gagner du temps</p>
<hr />
<h2 id="12-notification-des-utilisateurs-si-risque-eleve">12. Notification des utilisateurs si risque élevé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un breach impacte 5000 utilisateurs
<span style="color: #9E9E9E"><strong>Et</strong></span> que le risque est élevé (mots de passe non chiffrés exposés)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la CNIL est notifiée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les utilisateurs impactés sont notifiés dans les 72h
<span style="color: #9E9E9E"><strong>Et</strong></span> l'email contient:
<span style="color: #9E9E9E"><strong>Et</strong></span> un lien de réinitialisation de mot de passe est inclus</p>
<hr />
<h2 id="13-aucune-notification-si-risque-faible">13. Aucune notification si risque faible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un breach mineur est détecté (logs techniques exposés, aucune donnée personnelle)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'équipe évalue la gravité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le risque est jugé faible
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune notification CNIL n'est requise (Article 33.1 RGPD)
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune notification utilisateur n'est envoyée
<span style="color: #9E9E9E"><strong>Et</strong></span> un log interne est créé pour traçabilité</p>
<hr />
<h2 id="14-monitoring-proactif-pour-eviter-decouverte-tardive">14. Monitoring proactif pour éviter découverte tardive</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que Sentry et Grafana sont configurés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un comportement anormal est détecté</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une alerte est envoyée en temps réel
<span style="color: #9E9E9E"><strong>Et</strong></span> l'équipe peut réagir avant qu'un breach majeur ne se produise
<span style="color: #9E9E9E"><strong>Et</strong></span> les logs sont analysés quotidiennement pour détecter des anomalies
<span style="color: #9E9E9E"><strong>Et</strong></span> cette approche proactive limite les risques de découverte tardive</p>
<hr />
<h2 id="15-fondateur-dpo-temporaire-mvp">15. Fondateur = DPO temporaire (MVP)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave est en phase MVP
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'entreprise a moins de 250 employés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on vérifie l'obligation légale d'avoir un DPO</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le DPO n'est pas obligatoire selon le RGPD Article 37
<span style="color: #9E9E9E"><strong>Et</strong></span> le fondateur assume temporairement le rôle de DPO
<span style="color: #9E9E9E"><strong>Et</strong></span> le fondateur suit la formation CNIL gratuite (4h)</p>
<hr />
<h2 id="16-formation-cnil-du-dpo-temporaire">16. Formation CNIL du DPO temporaire</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le fondateur est DPO temporaire</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on vérifie sa formation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le fondateur a suivi la formation CNIL en ligne (4h)
<span style="color: #9E9E9E"><strong>Et</strong></span> le fondateur a obtenu la certification "Atelier RGPD" (gratuit)
<span style="color: #9E9E9E"><strong>Et</strong></span> le certificat est conservé pour audit
<span style="color: #9E9E9E"><strong>Et</strong></span> la formation couvre:</p>
<pre><code>| sujet |
|---|
| Principes fondamentaux du RGPD |
| Droits des personnes |
| Sécurité des données |
| Violations de données (breach) |
| Registre des traitements |
</code></pre>
<hr />
<h2 id="17-contact-dpo-publie-et-accessible">17. Contact DPO publié et accessible</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte les mentions légales de RoadWave</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je cherche le contact du DPO</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'email "dpo@roadwave.fr" est clairement affiché
<span style="color: #9E9E9E"><strong>Et</strong></span> cet email est également dans les CGU
<span style="color: #9E9E9E"><strong>Et</strong></span> le délai de réponse garanti est de 1 mois maximum (RGPD Article 12.3)
<span style="color: #9E9E9E"><strong>Et</strong></span> une adresse postale est également fournie</p>
<hr />
<h2 id="18-demande-dexercice-de-droits-rgpd-au-dpo">18. Demande d'exercice de droits RGPD au DPO</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je veux exercer mon droit d'accès à mes données</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'envoie un email à dpo@roadwave.fr</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un accusé de réception dans les 48h
<span style="color: #9E9E9E"><strong>Et</strong></span> ma demande est traitée dans un délai maximum de 1 mois
<span style="color: #9E9E9E"><strong>Et</strong></span> si le délai dépasse 1 mois, je suis informé de la prolongation (max 2 mois supplémentaires)
<span style="color: #9E9E9E"><strong>Et</strong></span> la réponse est complète et conforme au RGPD</p>
<hr />
<h2 id="19-types-de-demandes-gerees-par-le-dpo">19. Types de demandes gérées par le DPO</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je contacte le DPO</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'envoie une demande</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le DPO peut traiter les demandes suivantes:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> chaque demande reçoit une réponse personnalisée</p>
<hr />
<h2 id="20-migration-vers-dpo-externe-si-croissance">20. Migration vers DPO externe si croissance</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave dépasse 100 000 utilisateurs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la charge de travail DPO augmente</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un DPO externe mutualisé est engagé
<span style="color: #9E9E9E"><strong>Et</strong></span> le coût est d'environ 200€/mois
<span style="color: #9E9E9E"><strong>Et</strong></span> le DPO externe a les certifications CNIL requises
<span style="color: #9E9E9E"><strong>Et</strong></span> un contrat de sous-traitance RGPD est signé</p>
<hr />
<h2 id="21-recrutement-dpo-interne-si-10-employes">21. Recrutement DPO interne si &gt;10 employés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave a plus de 10 employés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'entreprise se structure</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un DPO interne peut être recruté
<span style="color: #9E9E9E"><strong>Et</strong></span> le DPO interne a une certification CNIL (AFCDP ou équivalent)
<span style="color: #9E9E9E"><strong>Et</strong></span> le DPO est indépendant et ne peut être licencié pour ses fonctions
<span style="color: #9E9E9E"><strong>Et</strong></span> le DPO a un accès direct à la direction</p>
<hr />
<h2 id="22-recapitulatif-des-couts-rgpd">22. Récapitulatif des coûts RGPD</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que toutes les mesures RGPD sont en place</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on calcule le coût total mensuel</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le récapitulatif est le suivant:</p>
<pre><code>| 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€ (&lt; 5K events) |
| DPO | Fondateur formé CNIL | 0€ |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> le coût total est d'environ 5€/mois
<span style="color: #9E9E9E"><strong>Et</strong></span> cette conformité est 100% opensource et maîtrisée</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="gestion-du-consentement-rgpd">Gestion du consentement RGPD</h1>
<p><strong>16 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un nouvel utilisateur
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'accède à l'application pour la première fois</p>
</blockquote>
<h2 id="1-affichage-du-banner-de-consentement-au-premier-lancement-web">1. Affichage du banner de consentement au premier lancement web</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'accède à l'application web pour la première fois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la page se charge</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un banner RGPD Tarteaucitron.js s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> le banner est en français
<span style="color: #9E9E9E"><strong>Et</strong></span> le banner propose les options suivantes:</p>
<pre><code>| option | description |
|---|---|
| Tout accepter | Active tous les consentements |
| Tout refuser | Refuse tous les consentements optionnels |
| Personnaliser | Ouvre le panneau de personnalisation |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> le banner est customisé aux couleurs de RoadWave</p>
<hr />
<h2 id="2-granularite-des-consentements">2. Granularité des consentements</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le banner RGPD est affiché</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Personnaliser"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois les catégories de consentements suivantes:</p>
<pre><code>| catégorie | type | requis |
|---|---|---|
| Fonctionnel | Nécessaire | oui |
| Analytique | Optionnel | non |
| Marketing | Optionnel | non |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> chaque catégorie a une description claire de son usage
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux accepter ou refuser chaque catégorie individuellement</p>
<hr />
<h2 id="3-consentement-geolocalisation-precise-obligatoire">3. Consentement géolocalisation précise - obligatoire</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis sur l'application mobile
<span style="color: #9E9E9E"><strong>Et</strong></span> que l'onboarding est terminé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'application a besoin d'accéder à ma position précise</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un écran de demande de consentement s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> le message explique clairement l'usage:
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux accepter ou refuser
<span style="color: #9E9E9E"><strong>Et</strong></span> si je refuse, l'application bascule en mode dégradé (GeoIP uniquement)</p>
<hr />
<h2 id="4-double-consentement-gps-banner-app-permission-os">4. Double consentement GPS - banner app + permission OS</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je veux activer la géolocalisation précise</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accepte le consentement dans l'application</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'application demande également la permission au système d'exploitation
<span style="color: #9E9E9E"><strong>Et</strong></span> sur iOS, la popup système s'affiche: "Autoriser RoadWave à accéder à votre position ?"
<span style="color: #9E9E9E"><strong>Et</strong></span> sur Android, la popup système s'affiche avec les options "Toujours autoriser / Autoriser seulement pendant l'utilisation / Refuser"
<span style="color: #9E9E9E"><strong>Et</strong></span> les deux consentements (app + OS) doivent être acceptés pour activer le GPS précis</p>
<hr />
<h2 id="5-enregistrement-du-consentement-en-base-de-donnees">5. Enregistrement du consentement en base de données</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai accepté les consentements suivants:</p>
<pre><code>| type | accepté |
|---|---|
| Fonctionnel | oui |
| Analytique | oui |
| Marketing | non |
| GPS précis | oui |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je valide mes choix</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un enregistrement est créé dans la table <code>user_consents</code>
<span style="color: #9E9E9E"><strong>Et</strong></span> l'enregistrement contient les champs suivants:</p>
<pre><code>| champ | valeur |
|---|---|
| user_id | [mon ID utilisateur] |
| consent_type | fonctionnel / analytique / gps |
| version | 1 |
| accepted | true / false |
| timestamp | [date et heure exacte] |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> chaque type de consentement a un enregistrement séparé</p>
<hr />
<h2 id="6-versioning-des-consentements">6. Versioning des consentements</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai accepté le consentement "Analytique" version 1 le 2025-01-01
<span style="color: #9E9E9E"><strong>Et</strong></span> que les CGU sont mises à jour le 2025-06-01</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me connecte après la mise à jour</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un nouveau consentement version 2 m'est demandé
<span style="color: #9E9E9E"><strong>Et</strong></span> mon ancien consentement version 1 reste dans l'historique
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois accepter la nouvelle version pour continuer à utiliser les analytics</p>
<hr />
<h2 id="7-historique-complet-conserve-pour-preuve-legale">7. Historique complet conservé pour preuve légale</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai modifié mes consentements plusieurs fois:</p>
<pre><code>| date | consent_type | accepted | version |
|---|---|---|---|
| 2025-01-01 | Analytique | oui | 1 |
| 2025-03-15 | Analytique | non | 1 |
| 2025-06-01 | Analytique | oui | 2 |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un auditeur CNIL consulte mon historique de consentements</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les enregistrements sont conservés
<span style="color: #9E9E9E"><strong>Et</strong></span> l'historique prouve que chaque consentement a été donné librement
<span style="color: #9E9E9E"><strong>Et</strong></span> les timestamps permettent de prouver la conformité à tout moment</p>
<hr />
<h2 id="8-consentement-analytique-optionnel">8. Consentement analytique - optionnel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je refuse le consentement "Analytique"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'utilise l'application</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucun cookie Matomo <code>_pk_id</code> n'est déposé
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune donnée d'usage n'est envoyée à Matomo
<span style="color: #9E9E9E"><strong>Et</strong></span> l'application fonctionne normalement sans analytics</p>
<hr />
<h2 id="9-consentement-notifications-push-optionnel">9. Consentement notifications push - optionnel</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je refuse le consentement "Notifications push"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un créateur que je suis publie un nouveau contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je ne reçois pas de notification push
<span style="color: #F44336"><strong>Mais</strong></span> je peux voir le nouveau contenu dans l'application
<span style="color: #9E9E9E"><strong>Et</strong></span> l'application fonctionne normalement</p>
<hr />
<h2 id="10-consentement-gps-precis-requis-pour-fonctionnalites-geo">10. Consentement GPS précis - requis pour fonctionnalités géo</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je refuse le consentement "GPS précis"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'utilise l'application</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux accéder aux contenus nationaux
<span style="color: #F44336"><strong>Mais</strong></span> les contenus géolocalisés précis (Ancré, Contextuel) ne sont pas disponibles
<span style="color: #9E9E9E"><strong>Et</strong></span> les audio-guides nécessitent l'activation du GPS
<span style="color: #9E9E9E"><strong>Et</strong></span> un banner permanent me rappelle que l'activation du GPS améliore l'expérience</p>
<hr />
<h2 id="11-revocation-dun-consentement-depuis-les-parametres">11. Révocation d'un consentement depuis les paramètres</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai accepté le consentement "Analytique"
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'utilise l'application depuis 3 mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ouvre "Paramètres &gt; Confidentialité &gt; Gérer mes consentements"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois la liste de tous mes consentements actuels
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux révoquer le consentement "Analytique"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je révoque le consentement</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un nouvel enregistrement est créé avec <code>accepted = false</code>
<span style="color: #9E9E9E"><strong>Et</strong></span> le cookie Matomo est supprimé immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> les analytics sont désactivées à partir de ce moment</p>
<hr />
<h2 id="12-acceptation-dun-consentement-precedemment-refuse">12. Acceptation d'un consentement précédemment refusé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'avais refusé le consentement "GPS précis"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ouvre "Paramètres &gt; Confidentialité &gt; Gérer mes consentements"
<span style="color: #9E9E9E"><strong>Et</strong></span> que je clique sur "Activer la géolocalisation précise"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un nouvel enregistrement est créé avec <code>accepted = true</code>
<span style="color: #9E9E9E"><strong>Et</strong></span> la permission OS est demandée si ce n'est pas déjà fait
<span style="color: #9E9E9E"><strong>Et</strong></span> l'application bascule en mode géolocalisation précise
<span style="color: #9E9E9E"><strong>Et</strong></span> les contenus géolocalisés deviennent disponibles immédiatement</p>
<hr />
<h2 id="13-export-de-lhistorique-des-consentements-pour-audit">13. Export de l'historique des consentements pour audit</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un contrôle CNIL est en cours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'équipe RoadWave exporte l'historique des consentements</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'export contient pour chaque utilisateur:</p>
<pre><code>| 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é |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> l'export est au format CSV pour analyse
<span style="color: #9E9E9E"><strong>Et</strong></span> les données prouvent la conformité RGPD</p>
<hr />
<h2 id="14-conformite-recommandations-cnil">14. Conformité recommandations CNIL</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le système de consentement est implémenté</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un auditeur CNIL vérifie la conformité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système respecte les critères suivants:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="15-tarteaucitronjs-self-hosted">15. Tarteaucitron.js self-hosted</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que l'application web utilise Tarteaucitron.js</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte les sources JavaScript chargées</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le script Tarteaucitron.js est hébergé sur les serveurs RoadWave
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun script tiers (CDN externe) n'est chargé
<span style="color: #9E9E9E"><strong>Et</strong></span> le code source de Tarteaucitron.js est vérifiable
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune donnée n'est envoyée à un tiers lors de l'affichage du banner</p>
<hr />
<h2 id="16-cout-de-la-solution-0">16. Coût de la solution - 0€</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que Tarteaucitron.js est opensource
<span style="color: #9E9E9E"><strong>Et</strong></span> que PostgreSQL est utilisé pour le backend</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on calcule le coût de la solution de consentement</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le coût est de 0€
<span style="color: #9E9E9E"><strong>Et</strong></span> la solution est entièrement maîtrisée (self-hosted)
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune dépendance à un service SaaS tiers</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="duree-de-conservation-des-donnees-et-purge-automatique">Durée de conservation des données et purge automatique</h1>
<p><strong>19 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le système de purge automatique est actif</p>
</blockquote>
<h2 id="1-auditeur-inactif-depuis-5-ans-suppression-automatique">1. Auditeur inactif depuis 5 ans - suppression automatique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un auditeur (sans contenu créé)
<span style="color: #9E9E9E"><strong>Et</strong></span> que je ne me suis pas connecté depuis le 2020-01-01
<span style="color: #9E9E9E"><strong>Et</strong></span> que la date actuelle est 2025-01-02 (&gt;5 ans)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le job de purge automatique s'exécute</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon compte est automatiquement supprimé
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes mes données personnelles sont effacées
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune trace ne subsiste dans la base de données</p>
<hr />
<h2 id="2-createur-avec-contenus-actifs-conservation-indefinie">2. Créateur avec contenus actifs - conservation indéfinie</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai créé 10 contenus qui reçoivent encore des écoutes
<span style="color: #9E9E9E"><strong>Et</strong></span> que je ne me suis pas connecté depuis 6 ans</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le job de purge automatique s'exécute</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon compte n'est pas supprimé
<span style="color: #9E9E9E"><strong>Et</strong></span> mes données personnelles sont conservées tant que mes contenus sont écoutés
<span style="color: #9E9E9E"><strong>Et</strong></span> mes contenus continuent d'être diffusés normalement</p>
<hr />
<h2 id="3-createur-inactif-sans-ecoutes-suppression-automatique">3. Créateur inactif sans écoutes - suppression automatique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai créé 5 contenus
<span style="color: #9E9E9E"><strong>Et</strong></span> que je ne me suis pas connecté depuis 5 ans (depuis 2020-01-01)
<span style="color: #9E9E9E"><strong>Et</strong></span> que mes contenus n'ont reçu aucune écoute depuis 2 ans (depuis 2023-01-01)
<span style="color: #9E9E9E"><strong>Et</strong></span> que la date actuelle est 2025-01-02</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le job de purge automatique s'exécute</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon compte est automatiquement supprimé
<span style="color: #9E9E9E"><strong>Et</strong></span> mes contenus sont anonymisés (créateur = "Utilisateur supprimé")
<span style="color: #9E9E9E"><strong>Et</strong></span> les fichiers audio restent disponibles mais anonymisés</p>
<hr />
<h2 id="4-notifications-par-email-avant-purge">4. Notifications par email avant purge</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis inactif depuis 4 ans et 9 mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détecte que je suis éligible à la purge dans 90 jours</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email avec le sujet "Votre compte RoadWave sera supprimé dans 90 jours"
<span style="color: #9E9E9E"><strong>Et</strong></span> l'email contient:
<span style="color: #9E9E9E"><strong>Et</strong></span> un lien de connexion est inclus dans l'email</p>
<hr />
<h2 id="5-rappels-a-90j-30j-et-7j-avant-suppression">5. Rappels à 90j, 30j et 7j avant suppression</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis éligible à la purge automatique</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les délais s'écoulent</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois les emails suivants:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> chaque email contient un lien de connexion pour réactiver le compte
<span style="color: #9E9E9E"><strong>Et</strong></span> les notifications push sont également envoyées si activées</p>
<hr />
<h2 id="6-connexion-annule-la-suppression-programmee">6. Connexion annule la suppression programmée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis éligible à la purge dans 15 jours
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai reçu plusieurs emails d'avertissement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me connecte à mon compte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la suppression programmée est annulée immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> le compteur d'inactivité est remis à zéro
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un email de confirmation: "Votre compte a été réactivé"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux continuer à utiliser l'application normalement</p>
<hr />
<h2 id="7-execution-quotidienne-du-job-de-purge">7. Exécution quotidienne du job de purge</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le système est en production</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on consulte les jobs planifiés</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un job "purge_inactive_accounts" est configuré
<span style="color: #9E9E9E"><strong>Et</strong></span> le job s'exécute tous les jours à 03:00 (heure creuse)
<span style="color: #9E9E9E"><strong>Et</strong></span> le job identifie les comptes éligibles à la purge
<span style="color: #9E9E9E"><strong>Et</strong></span> le job traite les suppressions automatiques</p>
<hr />
<h2 id="8-criteres-deligibilite-a-la-purge">8. Critères d'éligibilité à la purge</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le job de purge s'exécute</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système identifie les comptes éligibles</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les critères suivants sont appliqués:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> seuls les comptes remplissant tous les critères sont supprimés</p>
<hr />
<h2 id="9-metriques-du-job-de-purge">9. Métriques du job de purge</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le job de purge s'exécute le 2025-01-15</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le job se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un rapport est généré avec:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> le rapport est loggé pour audit</p>
<hr />
<h2 id="10-contenus-de-comptes-purges-conserves-anonymement">10. Contenus de comptes purgés conservés anonymement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon compte créateur est purgé automatiquement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la suppression est effective</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mes contenus créés sont conservés indéfiniment
<span style="color: #9E9E9E"><strong>Et</strong></span> les contenus sont anonymisés (créateur = "Utilisateur supprimé")
<span style="color: #9E9E9E"><strong>Et</strong></span> les fichiers audio restent sur le CDN
<span style="color: #9E9E9E"><strong>Et</strong></span> les statistiques d'écoute sont préservées
<span style="color: #9E9E9E"><strong>Et</strong></span> les utilisateurs peuvent toujours écouter mes contenus</p>
<hr />
<h2 id="11-createur-inactif-mais-contenus-populaires-pas-de-purge">11. Créateur inactif mais contenus populaires - pas de purge</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur inactif depuis 6 ans
<span style="color: #F44336"><strong>Mais</strong></span> que mes contenus reçoivent 500+ écoutes par mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le job de purge s'exécute</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon compte n'est pas supprimé
<span style="color: #9E9E9E"><strong>Et</strong></span> je continue de recevoir les emails d'avertissement tous les 6 mois
<span style="color: #9E9E9E"><strong>Et</strong></span> mes contenus continuent d'être diffusés
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux me reconnecter à tout moment</p>
<hr />
<h2 id="12-quest-ce-quune-ecoute-pour-le-calcul-dinactivite">12. Qu'est-ce qu'une "écoute" pour le calcul d'inactivité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système calcule si mes contenus sont "actifs"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une "écoute" est comptabilisée si:</p>
<pre><code>| condition | comptabilisée |
|---|---|
| Écoute complète (&gt;80%) | oui |
| Écoute partielle (&gt;30%) | oui |
| Skip rapide (&lt;30%) | non |
| Écoute par un bot (détecté) | non |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> au moins 1 écoute valide dans les 2 dernières années maintient le compte actif</p>
<hr />
<h2 id="13-conformite-principe-de-minimisation">13. Conformité principe de minimisation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le système de purge automatique est en place</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un auditeur RGPD vérifie la conformité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système respecte le principe de minimisation:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> le délai de 5 ans est conforme aux standards de l'industrie</p>
<hr />
<h2 id="14-actions-qui-reinitialisent-le-compteur-dinactivite">14. Actions qui réinitialisent le compteur d'inactivité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis inactif depuis 4 ans</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'effectue l'une des actions suivantes:</p>
<pre><code>| action |
|---|
| Connexion à l'application |
| Publication d'un nouveau contenu |
| Like d'un contenu |
| Abonnement à un créateur |
| Modification de mon profil |
</code></pre>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le compteur d'inactivité est remis à zéro
<span style="color: #9E9E9E"><strong>Et</strong></span> la suppression programmée est annulée
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne suis plus éligible à la purge pour 5 ans</p>
<hr />
<h2 id="15-tracabilite-des-suppressions-automatiques">15. Traçabilité des suppressions automatiques</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> qu'un compte est supprimé automatiquement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la suppression est effective</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un log d'audit est créé avec:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> le log est conservé 5 ans pour audit RGPD
<span style="color: #9E9E9E"><strong>Et</strong></span> l'user_id est pseudonymisé pour anonymat</p>
<hr />
<h2 id="16-compte-premium-inactif-pas-de-privilege-special">16. Compte Premium inactif - pas de privilège spécial</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur Premium
<span style="color: #9E9E9E"><strong>Et</strong></span> que je suis inactif depuis 5 ans</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le job de purge s'exécute</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon compte est supprimé comme un compte gratuit
<span style="color: #9E9E9E"><strong>Et</strong></span> l'abonnement Premium ne prolonge pas la durée de conservation
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun remboursement n'est effectué (compte inactif depuis 5 ans)</p>
<hr />
<h2 id="17-compte-avec-signalements-de-moderation-purge-differee">17. Compte avec signalements de modération - purge différée</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis éligible à la purge
<span style="color: #F44336"><strong>Mais</strong></span> que j'ai des signalements de modération en cours</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le job de purge s'exécute</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma purge est différée de 90 jours
<span style="color: #9E9E9E"><strong>Et</strong></span> les signalements sont traités en priorité
<span style="color: #9E9E9E"><strong>Et</strong></span> si les signalements aboutissent à un ban, le compte est supprimé immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> si les signalements sont infondés, la purge automatique reprend son cours</p>
<hr />
<h2 id="18-pourquoi-5-ans-dinactivite">18. Pourquoi 5 ans d'inactivité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le délai de purge est fixé à 5 ans</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on justifie ce choix</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les raisons suivantes sont avancées:</p>
<pre><code>| 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é |
</code></pre>
<hr />
<h2 id="19-politique-de-conservation-visible-dans-les-cgu">19. Politique de conservation visible dans les CGU</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je consulte les CGU de RoadWave</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je lis la section "Conservation des données"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la politique de purge automatique est clairement expliquée:
<span style="color: #9E9E9E"><strong>Et</strong></span> les utilisateurs sont informés dès l'inscription</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="cookies-et-analytics-avec-matomo-self-hosted">Cookies et analytics avec Matomo self-hosted</h1>
<p><strong>20 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur de l'application web RoadWave</p>
</blockquote>
<h2 id="1-cookies-strictement-necessaires-pas-de-consentement-requis">1. Cookies strictement nécessaires - pas de consentement requis</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'accède à l'application web</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je me connecte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les cookies techniques suivants sont déposés:</p>
<pre><code>| cookie | type | durée | finalité | consentement |
|---|---|---|---|---|
| session | Technique | 30j | Authentification | Non requis |
| refresh_token | Technique | 30j | Session persistante | Non requis |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> ces cookies sont essentiels au fonctionnement de l'application
<span style="color: #9E9E9E"><strong>Et</strong></span> ils sont exemptés de consentement selon l'article 82 de la loi Informatique et Libertés</p>
<hr />
<h2 id="2-cookie-analytique-matomo-consentement-requis">2. Cookie analytique Matomo - consentement requis</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai accepté le consentement "Analytique"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je navigue sur l'application web</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le cookie <code>_pk_id</code> est déposé
<span style="color: #9E9E9E"><strong>Et</strong></span> la durée de conservation est de 13 mois
<span style="color: #9E9E9E"><strong>Et</strong></span> ce cookie sert à Matomo pour analytics
<span style="color: #9E9E9E"><strong>Et</strong></span> mon IP est automatiquement anonymisée (2 derniers octets)</p>
<hr />
<h2 id="3-refus-du-consentement-analytique-pas-de-cookie-matomo">3. Refus du consentement analytique - pas de cookie Matomo</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai refusé le consentement "Analytique"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je navigue sur l'application web</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucun cookie <code>_pk_id</code> n'est déposé
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune donnée d'usage n'est collectée
<span style="color: #9E9E9E"><strong>Et</strong></span> l'application fonctionne normalement sans analytics</p>
<hr />
<h2 id="4-matomo-heberge-sur-les-serveurs-roadwave">4. Matomo hébergé sur les serveurs RoadWave</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave utilise Matomo pour les analytics</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on analyse l'infrastructure</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Matomo est installé sur les serveurs RoadWave (Docker)
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune donnée n'est envoyée à un service tiers
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes les données restent dans l'UE
<span style="color: #9E9E9E"><strong>Et</strong></span> l'accès à Matomo est restreint à l'équipe RoadWave</p>
<hr />
<h2 id="5-ip-anonymisees-automatiquement">5. IP anonymisées automatiquement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que Matomo collecte des données d'usage</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> une requête est enregistrée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'adresse IP est automatiquement anonymisée
<span style="color: #9E9E9E"><strong>Et</strong></span> les 2 derniers octets sont remplacés par des zéros
<span style="color: #9E9E9E"><strong>Et</strong></span> une IP 192.168.1.100 devient 192.168.0.0
<span style="color: #9E9E9E"><strong>Et</strong></span> cette anonymisation est irréversible
<span style="color: #9E9E9E"><strong>Et</strong></span> elle est conforme aux recommandations CNIL</p>
<hr />
<h2 id="6-configuration-matomo-conforme-rgpd">6. Configuration Matomo conforme RGPD</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que Matomo est configuré pour RoadWave</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on vérifie les paramètres</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les configurations suivantes sont activées:</p>
<pre><code>| 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é |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> la configuration est RGPD-compliant</p>
<hr />
<h2 id="7-aucun-tracker-tiers-utilise">7. Aucun tracker tiers utilisé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'accède à l'application web</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'inspecte les requêtes réseau avec les DevTools</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune requête n'est envoyée vers les domaines suivants:</p>
<pre><code>| domaine tiers interdit |
|---|
| google-analytics.com |
| facebook.com (Pixel) |
| hotjar.com |
| mixpanel.com |
| segment.io |
| amplitude.com |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> toutes les requêtes analytics vont uniquement vers matomo.roadwave.fr</p>
<hr />
<h2 id="8-conformite-zero-cookie-tiers">8. Conformité zéro cookie tiers</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'analyse les cookies déposés sur roadwave.fr</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte la liste des cookies</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les cookies sont first-party (domaine roadwave.fr)
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun cookie tiers (third-party) n'est présent
<span style="color: #9E9E9E"><strong>Et</strong></span> cette politique respecte les recommandations CNIL 2020</p>
<hr />
<h2 id="9-alternative-plausible-saas-eu-hosted">9. Alternative Plausible SaaS (EU-hosted)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave pourrait utiliser Plausible au lieu de Matomo</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on compare les deux solutions</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Plausible a les caractéristiques suivantes:</p>
<pre><code>| 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) |
</code></pre>
<p><span style="color: #F44336"><strong>Mais</strong></span> Matomo self-hosted reste le choix prioritaire (0€, contrôle total)</p>
<hr />
<h2 id="10-aucun-transfert-de-donnees-hors-ue">10. Aucun transfert de données hors UE</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que Matomo est self-hosted</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on analyse les flux de données</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune donnée d'analytics n'est transférée hors de l'UE
<span style="color: #9E9E9E"><strong>Et</strong></span> les serveurs sont localisés en France
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun transfert vers les US (pas de Privacy Shield / DPF requis)
<span style="color: #9E9E9E"><strong>Et</strong></span> la souveraineté des données est garantie</p>
<hr />
<h2 id="11-matomo-self-hosted-cout-estime">11. Matomo self-hosted - coût estimé</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que Matomo est hébergé sur l'infrastructure RoadWave</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on calcule le coût mensuel</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le coût est d'environ 5€/mois:</p>
<pre><code>| 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) |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> ce coût est marginal comparé à un SaaS tiers (9-50€/mois)</p>
<hr />
<h2 id="12-respect-du-signal-do-not-track-dnt">12. Respect du signal Do Not Track (DNT)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon navigateur envoie le header "DNT: 1"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accède à l'application web</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Matomo détecte le signal DNT
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune donnée d'usage n'est collectée
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun cookie <code>_pk_id</code> n'est déposé
<span style="color: #9E9E9E"><strong>Et</strong></span> l'application fonctionne normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> un message discret s'affiche: "Vos préférences de confidentialité sont respectées (DNT activé)"</p>
<hr />
<h2 id="13-logs-matomo-supprimes-apres-25-mois">13. Logs Matomo supprimés après 25 mois</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que Matomo collecte des données d'usage</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> les logs atteignent 25 mois d'ancienneté</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un job automatique supprime les anciens logs
<span style="color: #9E9E9E"><strong>Et</strong></span> seules les données agrégées (rapports) sont conservées
<span style="color: #9E9E9E"><strong>Et</strong></span> les données brutes (logs) sont supprimées définitivement
<span style="color: #9E9E9E"><strong>Et</strong></span> cette politique respecte le principe de minimisation RGPD</p>
<hr />
<h2 id="14-donnees-collectees-par-matomo">14. Données collectées par Matomo</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai accepté le consentement "Analytique"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je navigue sur l'application web</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> Matomo collecte les données suivantes:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> aucune donnée personnelle identifiable n'est collectée</p>
<hr />
<h2 id="15-user-id-hashe-pour-analytics">15. User ID hashé pour analytics</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis connecté à l'application
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai accepté le consentement "Analytique"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> Matomo enregistre mes actions</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon user_id est hashé (SHA-256)
<span style="color: #9E9E9E"><strong>Et</strong></span> le hash est 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
<span style="color: #9E9E9E"><strong>Et</strong></span> il est impossible de retrouver mon user_id original depuis ce hash
<span style="color: #9E9E9E"><strong>Et</strong></span> ce processus garantit l'anonymat</p>
<hr />
<h2 id="16-conformite-recommandations-cnil-sur-les-cookies">16. Conformité recommandations CNIL sur les cookies</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave utilise Matomo self-hosted</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un auditeur CNIL vérifie la conformité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système respecte les recommandations CNIL 2020:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="17-integration-tarteaucitronjs-pour-gerer-matomo">17. Intégration Tarteaucitron.js pour gérer Matomo</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que Tarteaucitron.js gère les consentements</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je personnalise mes consentements</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois l'option "Analytique (Matomo)"
<span style="color: #9E9E9E"><strong>Et</strong></span> une description est affichée:
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux activer ou désactiver Matomo indépendamment
<span style="color: #9E9E9E"><strong>Et</strong></span> si je désactive, le cookie <code>_pk_id</code> est supprimé immédiatement</p>
<hr />
<h2 id="18-analytics-sur-application-mobile">18. Analytics sur application mobile</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'utilise l'application mobile</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'accepte le consentement "Analytique"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'app utilise le SDK Matomo Mobile
<span style="color: #9E9E9E"><strong>Et</strong></span> les données sont envoyées à la même instance Matomo self-hosted
<span style="color: #9E9E9E"><strong>Et</strong></span> les mêmes règles d'anonymisation s'appliquent
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun SDK tiers (Google Analytics, Firebase) n'est utilisé</p>
<hr />
<h2 id="19-refus-analytics-sur-mobile">19. Refus analytics sur mobile</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai refusé le consentement "Analytique" sur mobile</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'utilise l'application</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucune donnée d'usage n'est collectée
<span style="color: #9E9E9E"><strong>Et</strong></span> le SDK Matomo est désactivé
<span style="color: #9E9E9E"><strong>Et</strong></span> l'application fonctionne normalement sans différence d'UX</p>
<hr />
<h2 id="20-matomo-opensource-et-auditable">20. Matomo opensource et auditable</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que Matomo est opensource</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on consulte le code source</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le code est disponible publiquement sur GitHub
<span style="color: #9E9E9E"><strong>Et</strong></span> le code peut être audité par des experts indépendants
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune backdoor ou collecte cachée n'est possible
<span style="color: #9E9E9E"><strong>Et</strong></span> cette transparence renforce la confiance utilisateur</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="mode-degrade-avec-geoip-sans-gps-precis">Mode dégradé avec GeoIP (sans GPS précis)</h1>
<p><strong>20 scénarios</strong> (19 standards, 1 plan)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un nouvel utilisateur
<span style="color: #9E9E9E"><strong>Et</strong></span> que je lance l'application pour la première fois</p>
</blockquote>
<h2 id="1-plan-trois-niveaux-de-geolocalisation-disponibles">1. 📋 Plan: Trois niveaux de géolocalisation disponibles</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'utilise le niveau de géolocalisation "<niveau>"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détermine ma position</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la technologie utilisée est "<technologie>"
<span style="color: #9E9E9E"><strong>Et</strong></span> les contenus accessibles sont "<contenus>"
<span style="color: #9E9E9E"><strong>Et</strong></span> le consentement RGPD est "<consentement>"</p>
<p><strong>📊 Exemples de données:</strong></p>
<table>
<thead>
<tr>
<th>niveau</th>
<th>technologie</th>
<th>contenus</th>
<th>consentement</th>
</tr>
</thead>
<tbody>
<tr>
<td>Pays</td>
<td>Aucune géoloc</td>
<td>Contenus nationaux uniquement</td>
<td>Non requis</td>
</tr>
<tr>
<td>Ville</td>
<td>GeoIP (MaxMind)</td>
<td>Contenus régionaux/ville</td>
<td>Non requis</td>
</tr>
<tr>
<td>Précis</td>
<td>GPS</td>
<td>Tous contenus (hyperlocaux inclus)</td>
<td>Requis</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="2-geoip-active-par-defaut-au-premier-lancement">2. GeoIP activé par défaut au premier lancement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je lance l'application pour la première fois
<span style="color: #9E9E9E"><strong>Et</strong></span> que je n'ai pas encore accepté le GPS précis</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'application démarre</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système utilise automatiquement GeoIP basé sur mon adresse IP
<span style="color: #9E9E9E"><strong>Et</strong></span> ma position est détectée au niveau ville: "Paris, France"
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun consentement n'est requis (GeoIP ne collecte pas de données personnelles)
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux accéder aux contenus régionaux et de ville</p>
<hr />
<h2 id="3-detection-de-ville-avec-maxmind-geolite2">3. Détection de ville avec MaxMind GeoLite2</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon adresse IP est 93.184.216.34</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système utilise GeoIP MaxMind GeoLite2</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> ma ville est détectée: "Paris"
<span style="color: #9E9E9E"><strong>Et</strong></span> ma région est détectée: "Île-de-France"
<span style="color: #9E9E9E"><strong>Et</strong></span> mon pays est détecté: "France"
<span style="color: #9E9E9E"><strong>Et</strong></span> la précision est d'environ 80% au niveau ville
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune coordonnée GPS précise n'est révélée</p>
<hr />
<h2 id="4-banner-dinvitation-a-activer-le-gps">4. Banner d'invitation à activer le GPS</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'utilise l'application en mode GeoIP</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je suis sur l'écran principal</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un banner discret s'affiche en haut:
<span style="color: #9E9E9E"><strong>Et</strong></span> le banner n'est pas intrusif (pas de popup modale)
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux le fermer temporairement avec un bouton X
<span style="color: #9E9E9E"><strong>Et</strong></span> le banner réapparaît tous les 7 jours si je ne l'active pas</p>
<hr />
<h2 id="5-upgrade-volontaire-vers-gps-depuis-le-banner">5. Upgrade volontaire vers GPS depuis le banner</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que le banner d'invitation au GPS est affiché</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Activer"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un écran de consentement GPS s'affiche
<span style="color: #9E9E9E"><strong>Et</strong></span> l'écran explique les avantages:
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux accepter ou refuser
<span style="color: #9E9E9E"><strong>Et</strong></span> si j'accepte, la permission OS est demandée</p>
<hr />
<h2 id="6-contenus-disponibles-en-mode-pays-aucune-geoloc">6. Contenus disponibles en mode Pays (aucune géoloc)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je n'autorise aucune géolocalisation</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système recherche du contenu à me proposer</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> seuls les contenus "National" sont disponibles
<span style="color: #9E9E9E"><strong>Et</strong></span> les contenus géolocalisés (Ancré, Contextuel) ne sont pas proposés
<span style="color: #9E9E9E"><strong>Et</strong></span> je vois un message: "Activez la géolocalisation pour plus de contenu local"</p>
<hr />
<h2 id="7-contenus-disponibles-en-mode-ville-geoip">7. Contenus disponibles en mode Ville (GeoIP)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'utilise le mode GeoIP et que je suis détecté à Paris</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système recherche du contenu à me proposer</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les contenus suivants sont disponibles:</p>
<pre><code>| type_contenu | disponible |
|---|---|
| National | oui |
| Région Île-de-France | oui |
| Ville Paris | oui |
| Hyperlocal (GPS) | non |
| Audio-guides | non |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> je reçois des recommandations pertinentes pour Paris</p>
<hr />
<h2 id="8-tous-contenus-disponibles-en-mode-precis-gps">8. Tous contenus disponibles en mode Précis (GPS)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai activé la géolocalisation précise</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système recherche du contenu à me proposer</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous les types de contenus sont disponibles:</p>
<pre><code>| type_contenu | disponible |
|---|---|
| National | oui |
| Régional | oui |
| Ville | oui |
| Hyperlocal (Ancré) | oui |
| Contextuel | oui |
| Audio-guides | oui |
</code></pre>
<hr />
<h2 id="9-geoip-ne-necessite-pas-de-consentement-rgpd">9. GeoIP ne nécessite pas de consentement RGPD</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'utilise le mode GeoIP</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un auditeur CNIL vérifie la conformité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> GeoIP n'est pas considéré comme une donnée personnelle
<span style="color: #9E9E9E"><strong>Et</strong></span> l'adresse IP n'est pas conservée après détection de la ville
<span style="color: #9E9E9E"><strong>Et</strong></span> seule la ville est stockée (non identifiant)
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun consentement n'est requis conformément au RGPD</p>
<hr />
<h2 id="10-base-de-donnees-maxmind-self-hosted">10. Base de données MaxMind self-hosted</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave utilise MaxMind GeoLite2</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on analyse l'infrastructure</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la base de données GeoLite2 est hébergée sur les serveurs RoadWave
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune requête n'est envoyée à un service tiers
<span style="color: #9E9E9E"><strong>Et</strong></span> la base de données est mise à jour automatiquement chaque mois
<span style="color: #9E9E9E"><strong>Et</strong></span> le coût est de 0€ (GeoLite2 est gratuit)</p>
<hr />
<h2 id="11-mise-a-jour-mensuelle-de-la-base-geoip">11. Mise à jour mensuelle de la base GeoIP</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que MaxMind publie des mises à jour mensuelles</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le 1er du mois arrive</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un job automatique télécharge la nouvelle base GeoLite2
<span style="color: #9E9E9E"><strong>Et</strong></span> la base est mise à jour sans interruption de service
<span style="color: #9E9E9E"><strong>Et</strong></span> un log est créé pour traçabilité
<span style="color: #9E9E9E"><strong>Et</strong></span> si la mise à jour échoue, une alerte est envoyée</p>
<hr />
<h2 id="12-ux-acceptable-en-mode-geoip">12. UX acceptable en mode GeoIP</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'utilise le mode GeoIP à Paris</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je parcours l'application</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux écouter du contenu pertinent pour Paris et l'Île-de-France
<span style="color: #9E9E9E"><strong>Et</strong></span> l'expérience est satisfaisante même sans GPS précis
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne suis pas bloqué dans l'utilisation de l'application
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux choisir d'activer le GPS quand je le souhaite</p>
<hr />
<h2 id="13-incitation-progressive-a-activer-le-gps">13. Incitation progressive à activer le GPS</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'utilise le mode GeoIP depuis 2 semaines
<span style="color: #9E9E9E"><strong>Et</strong></span> que je n'ai pas activé le GPS</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je consulte un audio-guide dans les résultats de recherche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message s'affiche:
<span style="color: #9E9E9E"><strong>Et</strong></span> si je clique "Plus tard", je peux continuer à utiliser l'app normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> l'incitation reste douce et non intrusive</p>
<hr />
<h2 id="14-upgrade-geoip-vers-gps">14. Upgrade GeoIP vers GPS</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'utilise le mode GeoIP</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'active la géolocalisation précise</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système bascule immédiatement en mode GPS
<span style="color: #9E9E9E"><strong>Et</strong></span> les contenus hyperlocaux deviennent disponibles
<span style="color: #9E9E9E"><strong>Et</strong></span> mon feed se rafraîchit avec du contenu plus précis
<span style="color: #9E9E9E"><strong>Et</strong></span> un toast de confirmation s'affiche: "Géolocalisation activée"</p>
<hr />
<h2 id="15-downgrade-gps-vers-geoip">15. Downgrade GPS vers GeoIP</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'utilise le mode GPS précis</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je désactive la géolocalisation dans les paramètres OS</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système bascule automatiquement en mode GeoIP
<span style="color: #9E9E9E"><strong>Et</strong></span> les contenus hyperlocaux ne sont plus proposés
<span style="color: #9E9E9E"><strong>Et</strong></span> un banner s'affiche: "Géolocalisation désactivée. Seul le contenu régional est disponible."
<span style="color: #9E9E9E"><strong>Et</strong></span> l'application continue de fonctionner normalement</p>
<hr />
<h2 id="16-detection-automatique-au-demarrage-de-lapp">16. Détection automatique au démarrage de l'app</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ouvre l'application</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'app vérifie les permissions de géolocalisation</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système détecte automatiquement le mode disponible:</p>
<pre><code>| permission GPS | consentement app | mode activé |
|---|---|---|
| Refusée | Non demandé | Pays |
| Refusée | Accepté | GeoIP |
| Accordée | Accepté | GPS précis |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> le mode est appliqué sans interaction utilisateur</p>
<hr />
<h2 id="17-precision-acceptable-pour-la-plupart-des-cas">17. Précision acceptable pour la plupart des cas</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'habite à Lyon
<span style="color: #9E9E9E"><strong>Et</strong></span> que mon IP est une IP résidentielle standard</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système utilise GeoIP pour me localiser</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la ville détectée est "Lyon" (correct à 80%)
<span style="color: #9E9E9E"><strong>Et</strong></span> dans 20% des cas, la ville peut être légèrement erronée (banlieue proche)
<span style="color: #9E9E9E"><strong>Et</strong></span> cette précision est suffisante pour proposer du contenu régional pertinent</p>
<hr />
<h2 id="18-geoip-avec-vpn-ou-proxy">18. GeoIP avec VPN ou proxy</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'utilise un VPN avec une IP sortante à Paris
<span style="color: #F44336"><strong>Mais</strong></span> que je suis physiquement à Lyon</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système utilise GeoIP</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la ville détectée est "Paris" (IP du VPN)
<span style="color: #9E9E9E"><strong>Et</strong></span> les contenus proposés sont pour Paris
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux activer le GPS précis pour corriger la localisation</p>
<hr />
<h2 id="19-pas-de-donnee-personnelle-collectee-avec-geoip">19. Pas de donnée personnelle collectée avec GeoIP</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'utilise le mode GeoIP</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le système détermine ma ville via mon IP</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'adresse IP n'est pas conservée après détection
<span style="color: #9E9E9E"><strong>Et</strong></span> seule la ville "Paris" est stockée en base de données
<span style="color: #9E9E9E"><strong>Et</strong></span> la ville seule n'est pas une donnée personnelle (RGPD)
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun consentement n'est donc requis</p>
<hr />
<h2 id="20-solution-geoip-gratuite-et-self-hosted">20. Solution GeoIP gratuite et self-hosted</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que RoadWave utilise MaxMind GeoLite2</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> on calcule le coût de la solution</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le coût est de 0€
<span style="color: #9E9E9E"><strong>Et</strong></span> la solution est opensource
<span style="color: #9E9E9E"><strong>Et</strong></span> la base de données est hébergée sur les serveurs RoadWave
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun coût SaaS tiers</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="portabilite-des-donnees-article-20-rgpd">Portabilité des données (Article 20 RGPD)</h1>
<p><strong>22 scénarios</strong> (21 standards, 1 plan)</p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur connecté
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai utilisé l'application depuis 6 mois</p>
</blockquote>
<h2 id="1-demande-dexport-depuis-les-parametres">1. Demande d'export depuis les paramètres</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis dans "Paramètres &gt; Confidentialité"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Exporter mes données"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une page d'information s'affiche expliquant:
<span style="color: #9E9E9E"><strong>Et</strong></span> un bouton "Confirmer l'export" est disponible</p>
<hr />
<h2 id="2-confirmation-et-demarrage-de-lexport">2. Confirmation et démarrage de l'export</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je clique sur "Confirmer l'export"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la demande est validée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message de confirmation s'affiche:
<span style="color: #9E9E9E"><strong>Et</strong></span> un worker background démarre la génération de l'export
<span style="color: #9E9E9E"><strong>Et</strong></span> le statut de l'export est "En cours de génération"
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux voir le statut dans "Paramètres &gt; Confidentialité &gt; Mes exports"</p>
<hr />
<h2 id="3-contenu-de-larchive-zip">3. Contenu de l'archive ZIP</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon export est généré</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je télécharge et ouvre l'archive</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'archive a la structure suivante:
<span style="color: #9E9E9E"><strong>Et</strong></span> tous les fichiers sont inclus</p>
<hr />
<h2 id="4-contenu-du-fichier-exportjson">4. Contenu du fichier export.json</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ouvre le fichier export.json</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'analyse le contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le JSON contient les sections suivantes:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> le JSON est formaté de manière lisible (indentation)
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes les dates sont au format ISO 8601</p>
<hr />
<h2 id="5-contenu-du-fichier-indexhtml">5. Contenu du fichier index.html</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ouvre le fichier index.html dans un navigateur</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la page se charge</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois un site web stylé avec navigation
<span style="color: #9E9E9E"><strong>Et</strong></span> les sections suivantes sont affichées:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> la navigation est intuitive (menu latéral)
<span style="color: #9E9E9E"><strong>Et</strong></span> le design est responsive (mobile/desktop)</p>
<hr />
<h2 id="6-fichiers-audio-inclus-dans-lexport">6. Fichiers audio inclus dans l'export</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai créé 5 contenus audio</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> mon export est généré</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le dossier <code>audio/</code> contient mes 5 fichiers
<span style="color: #9E9E9E"><strong>Et</strong></span> les fichiers sont au format Opus original
<span style="color: #9E9E9E"><strong>Et</strong></span> chaque fichier est nommé: <code>content-[id].opus</code>
<span style="color: #9E9E9E"><strong>Et</strong></span> les fichiers audio correspondent aux métadonnées dans export.json</p>
<hr />
<h2 id="7-fichier-readmetxt-explicatif">7. Fichier README.txt explicatif</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ouvre le fichier README.txt</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je lis le contenu</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le fichier explique:</p>
<hr />
<h2 id="8-plan-donnees-de-profil-exportees">8. 📋 Plan: Données de profil exportées</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon export est généré</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ouvre export.json et lis la section "profile"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je trouve les données suivantes:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="9-historique-decoute-exporte">9. Historique d'écoute exporté</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai écouté 150 contenus depuis 6 mois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> mon export est généré</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la section "listening_history" contient 150 entrées
<span style="color: #9E9E9E"><strong>Et</strong></span> chaque entrée contient:</p>
<pre><code>| 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] |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> les contenus sont triés par date décroissante</p>
<hr />
<h2 id="10-centres-dinteret-exportes">10. Centres d'intérêt exportés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes jauges d'intérêt sont:</p>
<pre><code>| catégorie | valeur |
|---|---|
| Automobile | 78% |
| Voyage | 65% |
| Musique | 52% |
| Politique | 30% |
</code></pre>
<p><span style="color: #FF9800"><strong>Quand</strong></span> mon export est généré</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la section "interest_gauges" contient ces valeurs
<span style="color: #9E9E9E"><strong>Et</strong></span> chaque jauge indique la date de dernière modification</p>
<hr />
<h2 id="11-historique-des-consentements-exporte">11. Historique des consentements exporté</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai modifié mes consentements plusieurs fois</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> mon export est généré</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la section "consent_history" contient:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> l'historique complet est visible</p>
<hr />
<h2 id="12-generation-asynchrone-pour-eviter-timeout">12. Génération asynchrone pour éviter timeout</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai beaucoup de données (500 contenus créés, 10 000 écoutes)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je demande un export</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la génération se fait en arrière-plan via un worker
<span style="color: #9E9E9E"><strong>Et</strong></span> la page web ne timeout pas
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux continuer à utiliser l'application pendant la génération
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un email quand l'export est prêt</p>
<hr />
<h2 id="13-delai-de-generation-conforme-rgpd">13. Délai de génération conforme RGPD</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je demande un export le 2025-01-20 à 10:00</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le worker génère l'export</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'export est disponible maximum 48h plus tard (avant le 2025-01-22 à 10:00)
<span style="color: #9E9E9E"><strong>Et</strong></span> la plupart des exports sont prêts en moins de 6h
<span style="color: #9E9E9E"><strong>Et</strong></span> le délai respecte l'article 20 du RGPD</p>
<hr />
<h2 id="14-email-de-notification-avec-lien-de-telechargement">14. Email de notification avec lien de téléchargement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon export est terminé</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le worker finalise la génération</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email avec le sujet "Votre export de données RoadWave est prêt"
<span style="color: #9E9E9E"><strong>Et</strong></span> l'email contient:
<span style="color: #9E9E9E"><strong>Et</strong></span> le lien de téléchargement est sécurisé (token unique)</p>
<hr />
<h2 id="15-lien-de-telechargement-expire-apres-7-jours">15. Lien de téléchargement expire après 7 jours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon export a été généré le 2025-01-20
<span style="color: #9E9E9E"><strong>Et</strong></span> que je reçois le lien de téléchargement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie d'accéder au lien le 2025-01-28 (8 jours plus tard)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le lien est expiré
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un message "Ce lien a expiré. Veuillez demander un nouvel export."
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux demander un nouvel export si nécessaire</p>
<hr />
<h2 id="16-limite-de-1-export-par-mois">16. Limite de 1 export par mois</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai demandé un export le 2025-01-15</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de demander un nouvel export le 2025-01-20</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un message d'erreur:
<span style="color: #9E9E9E"><strong>Et</strong></span> le bouton "Confirmer l'export" est désactivé
<span style="color: #9E9E9E"><strong>Et</strong></span> la date du prochain export possible est affichée</p>
<hr />
<h2 id="17-nouvel-export-possible-apres-1-mois">17. Nouvel export possible après 1 mois</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai demandé un export le 2025-01-15</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la date atteint le 2025-02-15</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je peux demander un nouvel export
<span style="color: #9E9E9E"><strong>Et</strong></span> le bouton "Confirmer l'export" est actif
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune limite ne s'applique</p>
<hr />
<h2 id="18-lien-de-telechargement-securise-avec-token-unique">18. Lien de téléchargement sécurisé avec token unique</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon export est prêt</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je reçois le lien de téléchargement</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le lien contient un token unique et non devinable
<span style="color: #9E9E9E"><strong>Et</strong></span> le format du lien est: <code>https://roadwave.fr/exports/download/[token_unique]</code>
<span style="color: #9E9E9E"><strong>Et</strong></span> le token est valide uniquement pour mon compte
<span style="color: #9E9E9E"><strong>Et</strong></span> le token expire après 7 jours ou après 3 téléchargements</p>
<hr />
<h2 id="19-verification-de-lauthentification-avant-telechargement">19. Vérification de l'authentification avant téléchargement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je reçois le lien d'export</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur le lien</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le système vérifie que je suis connecté
<span style="color: #9E9E9E"><strong>Et</strong></span> si je ne suis pas connecté, je suis redirigé vers la page de connexion
<span style="color: #9E9E9E"><strong>Et</strong></span> après connexion, le téléchargement démarre automatiquement
<span style="color: #9E9E9E"><strong>Et</strong></span> seul le propriétaire du compte peut télécharger l'export</p>
<hr />
<h2 id="20-conformite-portabilite-des-donnees">20. Conformité portabilité des données</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon export est généré</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un auditeur RGPD vérifie la conformité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> l'export respecte les exigences de l'article 20:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="21-gratuite-de-lexport">21. Gratuité de l'export</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je demande un export de mes données</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> l'export est généré et téléchargé</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> aucun coût n'est facturé
<span style="color: #9E9E9E"><strong>Et</strong></span> l'export est entièrement gratuit
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune inscription Premium n'est requise
<span style="color: #9E9E9E"><strong>Et</strong></span> le droit à la portabilité est accessible à tous les utilisateurs</p>
<hr />
<h2 id="22-suivi-du-statut-de-generation">22. Suivi du statut de génération</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai demandé un export</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'ouvre "Paramètres &gt; Confidentialité &gt; Mes exports"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je vois le statut actuel:</p>
<pre><code>| 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é (&gt;7j), nouvel export requis |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> la date de demande est affichée
<span style="color: #9E9E9E"><strong>Et</strong></span> la taille estimée de l'archive est visible</p>
<hr />
<div style="page-break-after: always;"></div>
<h1 id="suppression-du-compte-utilisateur-article-17-rgpd-droit-a-leffacement">Suppression du compte utilisateur (Article 17 RGPD - Droit à l'effacement)</h1>
<p><strong>21 scénarios</strong></p>
<hr />
<blockquote>
<p><strong>Contexte commun à tous les scénarios</strong></p>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un utilisateur connecté
<span style="color: #9E9E9E"><strong>Et</strong></span> que j'ai utilisé l'application depuis plusieurs mois</p>
</blockquote>
<h2 id="1-demande-de-suppression-depuis-les-parametres">1. Demande de suppression depuis les paramètres</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis dans "Paramètres &gt; Compte"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur "Supprimer mon compte"</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> une page d'avertissement s'affiche avec le message:
<span style="color: #9E9E9E"><strong>Et</strong></span> deux boutons sont disponibles: "Annuler" et "Confirmer la suppression"</p>
<hr />
<h2 id="2-confirmation-de-suppression-avec-mot-de-passe">2. Confirmation de suppression avec mot de passe</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je clique sur "Confirmer la suppression"</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un formulaire de confirmation s'affiche</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je dois entrer mon mot de passe pour confirmer
<span style="color: #9E9E9E"><strong>Et</strong></span> je dois cocher "Je comprends que cette action est définitive"
<span style="color: #9E9E9E"><strong>Et</strong></span> un captcha peut être requis pour éviter les suppressions automatisées</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je valide le formulaire</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la suppression est initiée</p>
<hr />
<h2 id="3-compte-desactive-immediatement-apres-confirmation">3. Compte désactivé immédiatement après confirmation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai confirmé la suppression de mon compte</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la demande est traitée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon compte est désactivé immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> je suis déconnecté de toutes mes sessions
<span style="color: #9E9E9E"><strong>Et</strong></span> je ne peux plus me reconnecter
<span style="color: #9E9E9E"><strong>Et</strong></span> si j'essaie de me connecter, je reçois le message:</p>
<hr />
<h2 id="4-contenus-caches-pendant-le-grace-period">4. Contenus cachés pendant le grace period</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon compte est en cours de suppression</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un autre utilisateur recherche mes contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mes contenus ne sont plus diffusés dans l'application
<span style="color: #9E9E9E"><strong>Et</strong></span> mes contenus n'apparaissent plus dans les recherches
<span style="color: #9E9E9E"><strong>Et</strong></span> mes contenus ne sont plus recommandés
<span style="color: #F44336"><strong>Mais</strong></span> mes contenus ne sont pas encore supprimés définitivement</p>
<hr />
<h2 id="5-email-de-confirmation-envoye-immediatement">5. Email de confirmation envoyé immédiatement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai confirmé la suppression de mon compte</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la demande est traitée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois un email avec le sujet "Confirmation de suppression de votre compte RoadWave"
<span style="color: #9E9E9E"><strong>Et</strong></span> l'email contient:
<span style="color: #9E9E9E"><strong>Et</strong></span> le lien d'annulation est valide 30 jours</p>
<hr />
<h2 id="6-annulation-de-la-suppression-dans-les-30-jours">6. Annulation de la suppression dans les 30 jours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai demandé la suppression de mon compte le 2025-01-20
<span style="color: #9E9E9E"><strong>Et</strong></span> que je reçois l'email de confirmation</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je clique sur le lien "Annuler la suppression" le 2025-02-05 (16 jours plus tard)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon compte est réactivé immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux me reconnecter normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> mes contenus redeviennent visibles dans l'application
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes mes données sont restaurées
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un email de confirmation: "Votre compte a été réactivé"</p>
<hr />
<h2 id="7-lien-dannulation-expire-apres-30-jours">7. Lien d'annulation expire après 30 jours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai demandé la suppression de mon compte le 2025-01-20</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> j'essaie de cliquer sur le lien d'annulation le 2025-02-25 (36 jours plus tard)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le lien est expiré
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un message "Ce lien a expiré. Votre compte a été définitivement supprimé."
<span style="color: #9E9E9E"><strong>Et</strong></span> la suppression effective a déjà eu lieu</p>
<hr />
<h2 id="8-suppression-effective-sans-annulation">8. Suppression effective sans annulation</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai demandé la suppression de mon compte le 2025-01-20
<span style="color: #9E9E9E"><strong>Et</strong></span> que je n'ai pas cliqué sur le lien d'annulation</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la date atteint le 2025-02-19 (30 jours plus tard)</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un job automatique exécute la suppression définitive
<span style="color: #9E9E9E"><strong>Et</strong></span> toutes mes données personnelles sont supprimées</p>
<hr />
<h2 id="9-liste-des-donnees-supprimees-definitivement">9. Liste des données supprimées définitivement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la suppression effective est exécutée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le job de suppression se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les données suivantes sont supprimées:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> ces suppressions sont irréversibles</p>
<hr />
<h2 id="10-anonymisation-des-contenus-crees">10. Anonymisation des contenus créés</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai créé 10 contenus audio</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la suppression effective est exécutée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mes contenus audio restent disponibles dans l'application
<span style="color: #9E9E9E"><strong>Et</strong></span> le nom du créateur devient "Utilisateur supprimé"
<span style="color: #9E9E9E"><strong>Et</strong></span> mon pseudo n'est plus visible
<span style="color: #9E9E9E"><strong>Et</strong></span> les métadonnées (titre, description, tags, géolocalisation) sont conservées
<span style="color: #9E9E9E"><strong>Et</strong></span> les fichiers audio restent sur le CDN
<span style="color: #9E9E9E"><strong>Et</strong></span> les statistiques d'écoute sont conservées</p>
<hr />
<h2 id="11-justification-de-lanonymisation-interet-legitime">11. Justification de l'anonymisation (intérêt légitime)</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mes contenus sont conservés anonymement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un auditeur RGPD vérifie la conformité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> la conservation est justifiée par l'intérêt légitime de la communauté
<span style="color: #9E9E9E"><strong>Et</strong></span> les contenus ne contiennent plus de données personnelles identifiables
<span style="color: #9E9E9E"><strong>Et</strong></span> la suppression complète nuirait à l'expérience des autres utilisateurs
<span style="color: #9E9E9E"><strong>Et</strong></span> cette pratique est conforme au RGPD si anonymisation réelle</p>
<hr />
<h2 id="12-contenu-anonymise-visible-pour-les-autres-utilisateurs">12. Contenu anonymisé visible pour les autres utilisateurs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que mon compte a été supprimé
<span style="color: #9E9E9E"><strong>Et</strong></span> que mes contenus ont été anonymisés</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un utilisateur consulte un de mes anciens contenus</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le créateur affiché est "Utilisateur supprimé"
<span style="color: #9E9E9E"><strong>Et</strong></span> le profil du créateur n'est plus accessible
<span style="color: #9E9E9E"><strong>Et</strong></span> le contenu reste écoutable normalement
<span style="color: #9E9E9E"><strong>Et</strong></span> les likes et statistiques sont conservés</p>
<hr />
<h2 id="13-suppression-de-mes-likes-avec-conservation-des-compteurs">13. Suppression de mes likes avec conservation des compteurs</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'avais liké 50 contenus</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la suppression effective est exécutée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mes likes sont supprimés de la base de données
<span style="color: #F44336"><strong>Mais</strong></span> les compteurs de likes sur les contenus sont préservés
<span style="color: #9E9E9E"><strong>Et</strong></span> les créateurs ne perdent pas leurs statistiques
<span style="color: #9E9E9E"><strong>Et</strong></span> seule la relation "user X a liké content Y" est supprimée</p>
<hr />
<h2 id="14-suppression-de-mes-abonnements">14. Suppression de mes abonnements</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suivais 20 créateurs</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> la suppression effective est exécutée</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mes abonnements sont supprimés
<span style="color: #9E9E9E"><strong>Et</strong></span> les compteurs d'abonnés des créateurs sont décrémentés de 1
<span style="color: #9E9E9E"><strong>Et</strong></span> les créateurs ne reçoivent pas de notification de désabonnement</p>
<hr />
<h2 id="15-revocation-de-tous-les-tokens-immediatement">15. Révocation de tous les tokens immédiatement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis connecté sur 3 appareils (mobile, tablette, web)</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je demande la suppression de mon compte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> tous mes tokens d'authentification sont révoqués immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> je suis déconnecté de tous mes appareils
<span style="color: #9E9E9E"><strong>Et</strong></span> toute tentative de reconnexion échoue</p>
<hr />
<h2 id="16-rappels-par-email-pendant-le-grace-period">16. Rappels par email pendant le grace period</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai demandé la suppression de mon compte le 2025-01-20</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le grace period s'écoule</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> je reçois des emails de rappel:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> chaque email contient le lien d'annulation</p>
<hr />
<h2 id="17-conformite-droit-a-leffacement">17. Conformité droit à l'effacement</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la suppression de mon compte est complète</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> un auditeur RGPD vérifie la conformité</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> le processus respecte l'article 17 du RGPD:</p>
<pre><code>| 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 |
</code></pre>
<hr />
<h2 id="18-suppression-dun-compte-premium">18. Suppression d'un compte Premium</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai un abonnement Premium actif</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je demande la suppression de mon compte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> mon abonnement est annulé immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> aucun remboursement n'est effectué (conformément aux CGV)
<span style="color: #9E9E9E"><strong>Et</strong></span> je reçois un email de confirmation d'annulation de l'abonnement
<span style="color: #9E9E9E"><strong>Et</strong></span> le reste du processus de suppression se déroule normalement</p>
<hr />
<h2 id="19-suppression-dun-compte-createur-avec-revenus-en-attente">19. Suppression d'un compte créateur avec revenus en attente</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que je suis un créateur avec 75€ de revenus en attente de paiement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je demande la suppression de mon compte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un message m'informe:
<span style="color: #9E9E9E"><strong>Et</strong></span> je peux choisir "Recevoir le paiement et attendre" ou "Renoncer au paiement"
<span style="color: #9E9E9E"><strong>Et</strong></span> si je choisis "Recevoir le paiement", la suppression est repoussée de 7 jours</p>
<hr />
<h2 id="20-suppression-avec-signalements-de-moderation-en-cours">20. Suppression avec signalements de modération en cours</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que j'ai 2 signalements en cours de traitement</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> je demande la suppression de mon compte</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> les signalements sont automatiquement clôturés
<span style="color: #9E9E9E"><strong>Et</strong></span> les contenus signalés sont masqués immédiatement
<span style="color: #9E9E9E"><strong>Et</strong></span> aucune sanction n'est appliquée (compte déjà en suppression)</p>
<hr />
<h2 id="21-log-de-la-suppression-pour-tracabilite">21. Log de la suppression pour traçabilité</h2>
<p><span style="color: #2196F3"><strong>Étant donné</strong></span> que la suppression effective est exécutée</p>
<p><span style="color: #FF9800"><strong>Quand</strong></span> le job de suppression se termine</p>
<p><span style="color: #4CAF50"><strong>Alors</strong></span> un log est créé avec:</p>
<pre><code>| 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 |
</code></pre>
<p><span style="color: #9E9E9E"><strong>Et</strong></span> ce log est conservé 5 ans pour audit RGPD
<span style="color: #9E9E9E"><strong>Et</strong></span> l'user_id est pseudonymisé pour anonymat</p>
<hr />
<div style="page-break-after: always;"></div>
</body>
</html>