docs: améliorer rendu markdown et navigation mkdocs
- Ajouter ADR-018 (librairies Go) dans TECHNICAL.md - Transformer Shared en menu dépliable dans mkdocs (cohérence avec autres domaines) - Corriger listes markdown (ajout lignes vides avant listes) - Corriger line breaks dans génération BDD (étapes "Et" sur nouvelles lignes) - Ajouter script fix-markdown-lists.sh pour corrections futures Impacte 86 fichiers de documentation et 164 fichiers BDD générés.
This commit is contained in:
@@ -31,6 +31,7 @@ Le domaine **Recommendation** gère le système de recommandation de contenus ba
|
||||
## Ubiquitous Language
|
||||
|
||||
**Termes métier du domaine** :
|
||||
|
||||
- **Interest Gauge** : Jauge de centre d'intérêt (score de 0 à 100)
|
||||
- **Interest Category** : Catégorie d'intérêt (automobile, voyage, musique, etc.)
|
||||
- **Recommendation Score** : Score combinant distance géographique et affinité d'intérêt
|
||||
|
||||
@@ -204,6 +204,7 @@ godog run features/recommendation/scoring-recommandation.feature --tags=@calcul
|
||||
## CI/CD
|
||||
|
||||
Ces tests sont exécutés :
|
||||
|
||||
- ✅ Avant chaque release
|
||||
- ✅ Sur les PRs modifiant l'algorithme de recommandation
|
||||
- ✅ Nightly builds (tous les tests)
|
||||
|
||||
@@ -11,11 +11,13 @@
|
||||
| **Géo-neutre** | Universel, pas de lien géo | Podcast philosophie, musique | 20% |
|
||||
|
||||
**Qui décide** :
|
||||
|
||||
- ✅ Créateur choisit le type à la publication
|
||||
- ✅ Modération peut reclassifier après validation
|
||||
- ✅ Modification possible après publication (tout le monde a le droit de se tromper)
|
||||
|
||||
**Justification** :
|
||||
|
||||
- Différencie audio-guide (hyper-local) des podcasts génériques
|
||||
- Algorithme adapte automatiquement la pondération
|
||||
- Coût : champ supplémentaire en DB + règle algo
|
||||
@@ -33,6 +35,7 @@ score_final = (score_geo * poids_geo_type)
|
||||
+ (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)
|
||||
@@ -42,6 +45,7 @@ où :
|
||||
#### Calcul détaillé du score_interets
|
||||
|
||||
**Domaine des données** :
|
||||
|
||||
- Jauges utilisateur : stockées en pourcentage [0-100]
|
||||
- score_interets : normalisé dans l'intervalle [0.0-1.0] pour pondération
|
||||
|
||||
@@ -50,6 +54,7 @@ où :
|
||||
score_interets = (SUM(gauge_values_for_tags) / NB_TAGS) / 100
|
||||
|
||||
où :
|
||||
|
||||
- gauge_values_for_tags = valeurs des jauges correspondant aux tags du contenu
|
||||
- NB_TAGS = nombre de tags du contenu (minimum 1, maximum 3)
|
||||
- Division par 100 pour normaliser [0-100] → [0.0-1.0]
|
||||
@@ -61,6 +66,7 @@ Contenu : "Visite du Louvre"
|
||||
Tags : ["Musique", "Tourisme"]
|
||||
|
||||
Utilisateur :
|
||||
|
||||
- Jauge "Musique" = 75%
|
||||
- Jauge "Tourisme" = 60%
|
||||
- Jauge "Automobile" = 40% (non pertinente, ignorée)
|
||||
@@ -79,6 +85,7 @@ score_final = (score_geo * 0.5) + (score_interets * 0.3) + (score_engagement * 0
|
||||
```
|
||||
|
||||
**Cas limites** :
|
||||
|
||||
- Utilisateur n'a aucune jauge pour les tags du contenu → score_interets = 0.5 (valeur neutre par défaut)
|
||||
- Contenu avec 1 seul tag → score_interets = gauge_value / 100
|
||||
- Jauges multiples → moyenne arithmétique simple (pas de pondération différente par tag)
|
||||
@@ -93,6 +100,7 @@ score_final = (score_geo * 0.5) + (score_interets * 0.3) + (score_engagement * 0
|
||||
| Géo-neutre | 0.2 | 0.6 |
|
||||
|
||||
**Paramètres** :
|
||||
|
||||
- Distance max recommandée : **200 km**
|
||||
- Dégradation : **linéaire** (1 - distance/200km)
|
||||
- Rayon point GPS : **500m** (adapté au volume de contenu local)
|
||||
@@ -100,6 +108,7 @@ score_final = (score_geo * 0.5) + (score_interets * 0.3) + (score_engagement * 0
|
||||
**Tous ces paramètres sont configurables à chaud via interface admin.**
|
||||
|
||||
**Justification** :
|
||||
|
||||
- Flexibilité totale selon type de contenu
|
||||
- Linéaire = rattrapage naturel du contenu viral ancien
|
||||
- Auditable via métriques engagement (moyenne/médiane)
|
||||
@@ -111,6 +120,7 @@ score_final = (score_geo * 0.5) + (score_interets * 0.3) + (score_engagement * 0
|
||||
**Décision** : Intégration popularité avec poids 0.2
|
||||
|
||||
**Métriques** :
|
||||
|
||||
- **Taux de complétion** : écoutes >80% / total écoutes pertinentes (poids 0.5)
|
||||
- **Ratio likes** : likes / écoutes (poids 0.3)
|
||||
- **Ratio abonnements** : nouveaux abonnés après écoute / écoutes (poids 0.2)
|
||||
@@ -145,19 +155,23 @@ GROUP BY content_id;
|
||||
```
|
||||
|
||||
**Seuil minimum** :
|
||||
|
||||
- Minimum **50 écoutes pertinentes** avant de considérer l'engagement
|
||||
- Contenu <50 écoutes : score engagement = 0.5 (neutre)
|
||||
|
||||
**Contenu viral** :
|
||||
|
||||
- Un contenu viral à Paris **peut** être proposé à Marseille
|
||||
- Score géo faible compensé par score engagement élevé
|
||||
- Paramétrable admin
|
||||
|
||||
**Dépréciation temporelle** :
|
||||
|
||||
- Pas de dépréciation automatique
|
||||
- Ratio linéaire = contenu ancien mais toujours apprécié reste pertinent
|
||||
|
||||
**Justification** :
|
||||
|
||||
- Équilibre découverte / qualité
|
||||
- **Protection créateur** : abonnés fidèles ne pénalisent pas les métriques
|
||||
- **Anti-raid naturel** : skips via search/direct_link ne comptent pas (raid inefficace)
|
||||
@@ -172,16 +186,19 @@ GROUP BY content_id;
|
||||
**Décision** : 10% par défaut, paramétrable utilisateur
|
||||
|
||||
**Fonctionnement** :
|
||||
|
||||
- 1 contenu sur 10 = tirage aléatoire (hors historique déjà écouté)
|
||||
- Utilisateur peut ajuster : curseur 0% (aucun aléatoire) à 50% (exploration max)
|
||||
|
||||
**Curseur utilisateur** :
|
||||
|
||||
- 🎯 **0%** : Personnalisé max (recommandations strictes)
|
||||
- ⚖️ **10%** : Équilibré (défaut)
|
||||
- 🎲 **30%** : Découverte élevée
|
||||
- 🌍 **50%** : Découverte max (équivaut à national = découverte)
|
||||
|
||||
**Justification** :
|
||||
|
||||
- Évite la bulle de filtre
|
||||
- Laisse l'utilisateur maître de son expérience
|
||||
- Coût : variable aléatoire en algo
|
||||
@@ -195,23 +212,27 @@ GROUP BY content_id;
|
||||
**Décision MVP** : Tag simple "Politique" sans classification idéologique
|
||||
|
||||
**Tagging** :
|
||||
|
||||
- Créateur peut taguer son contenu comme "Politique" (optionnel)
|
||||
- Tag "Politique" au même niveau que "Économie", "Sport", "Culture", etc.
|
||||
- **Pas de classification gauche/droite**
|
||||
- **Pas d'équilibrage imposé**
|
||||
|
||||
**Filtrage utilisateur** :
|
||||
|
||||
- Option paramètres : **"Masquer contenu politique"**
|
||||
- Si activé → 0% de contenus tagués "Politique" dans le feed
|
||||
- Par défaut : désactivé (tous contenus visibles)
|
||||
|
||||
**Justification MVP** :
|
||||
|
||||
- **Simplicité** : Pas de modération politique coûteuse (~2000€/mois économisés)
|
||||
- **Neutralité technique** : Aucun jugement éditorial sur orientation
|
||||
- **Risque minimal** : Évite controverses et contentieux DSA au lancement
|
||||
- **Fonctionnel** : Utilisateurs peuvent filtrer si souhaité
|
||||
|
||||
**Post-MVP** :
|
||||
|
||||
- Classification avancée possible si forte demande utilisateurs
|
||||
- Nécessite ressources modération dédiées et audit DSA
|
||||
|
||||
@@ -230,22 +251,26 @@ GROUP BY content_id;
|
||||
| **13-15 ans** | Collège | Contenus "Tous publics" uniquement | Filtrage 16+ et 18+ |
|
||||
|
||||
**Activation** :
|
||||
|
||||
- ❌ **Pas d'activation automatique** (tous les utilisateurs ont ≥13 ans)
|
||||
- ✅ **Activation manuelle** via toggle paramètres
|
||||
- ✅ Parents peuvent activer pour leurs enfants 13-15 ans
|
||||
- ✅ Utilisateur peut désactiver à tout moment
|
||||
|
||||
**Filtrage quand Mode Kids activé** :
|
||||
|
||||
- ✅ Contenus "Tous publics" uniquement
|
||||
- ❌ Exclusion contenus 16+ et 18+
|
||||
- ❌ Pas de contenu politique (automatiquement filtré)
|
||||
- ❌ Pas de publicité (ou uniquement pub validée manuellement)
|
||||
|
||||
**Interface** :
|
||||
|
||||
- Interface standard (pas d'interface dédiée enfants pour MVP)
|
||||
- Filtrage algorithmique des contenus inappropriés
|
||||
|
||||
**Justification** :
|
||||
|
||||
- **Conformité légale** : Âge minimum 13 ans (RGPD, DSA)
|
||||
- **Simplicité MVP** : Un seul mode optionnel vs 4 tranches d'âge
|
||||
- **Protection mineurs** : Filtrage contenus adultes pour 13-15 ans
|
||||
@@ -266,15 +291,18 @@ GROUP BY content_id;
|
||||
6. Si ignoré → contenu proposé normalement en file d'attente
|
||||
|
||||
**Publicités** :
|
||||
|
||||
- ⚠️ **Jamais d'interruption** de contenu en cours
|
||||
- Pub s'intercale **entre deux séquences** uniquement
|
||||
- Notification pub : son différent (facultatif selon paramètres)
|
||||
|
||||
**Gestion demi-tour** :
|
||||
|
||||
- Si utilisateur repart du point après notification → pas de nouvelle notification (déjà proposé)
|
||||
- Réinitialisation après 24h
|
||||
|
||||
**Justification** :
|
||||
|
||||
- Respect écoute en cours (pas de coupure brutale)
|
||||
- UX fluide (utilisateur garde contrôle)
|
||||
- Simplicité technique (pas de prédiction trajectoire)
|
||||
@@ -295,17 +323,20 @@ GROUP BY content_id;
|
||||
| **Partiellement écouté** | 10-80% | Peu importe | ✅ Reproposer avec reprise position (`last_position_seconds`) |
|
||||
|
||||
**Stockage historique** :
|
||||
|
||||
- Table `user_content_history` (user_id, content_id, creator_id, **is_subscribed**, completion_rate, last_position, listened_at)
|
||||
- Historique **illimité** (PostgreSQL)
|
||||
- Algorithme considère les **100 derniers** pour optimisation requêtes
|
||||
- Export complet disponible (RGPD)
|
||||
|
||||
**Colonne `is_subscribed`** :
|
||||
|
||||
- Booléen stockant si l'utilisateur était abonné au créateur **au moment de l'écoute**
|
||||
- Permet de distinguer les skips d'abonnés (contextuels) des skips de non-abonnés (désintérêt)
|
||||
- Utilisé pour décisions de reproposition et calculs d'engagement
|
||||
|
||||
**Justification** :
|
||||
|
||||
- Découverte maximale (pas de redites)
|
||||
- **Cohérence abonnement** : un skip ponctuel d'un abonné ≠ rejet du créateur (peut être contextuel : "pas maintenant", "pas ce sujet", "mauvais timing")
|
||||
- Respect erreurs de clic (contenu partiel = 2nde chance)
|
||||
@@ -331,23 +362,27 @@ GROUP BY content_id;
|
||||
| `seuil_min_ecoutes_engagement` | 10 - 200 | 50 | nb |
|
||||
|
||||
**Application changements** :
|
||||
|
||||
- Immédiat : nouveaux calculs utilisent nouvelle config
|
||||
- Aucun recalcul batch (coût CPU)
|
||||
- Version config trackée (git-like)
|
||||
- Rollback 1 clic
|
||||
|
||||
**A/B Testing** :
|
||||
|
||||
- Création variantes (Config A vs Config B)
|
||||
- Split utilisateurs 50/50 aléatoire
|
||||
- Métriques comparatives : taux complétion, engagement, session duration
|
||||
- Dashboard graphique temps réel
|
||||
|
||||
**Audit engagement** :
|
||||
|
||||
- Métriques clés : moyenne/médiane temps d'écoute par session
|
||||
- Graphiques : évolution engagement selon config
|
||||
- Export CSV pour analyse externe
|
||||
|
||||
**Justification** :
|
||||
|
||||
- Optimisation continue sans redéploiement
|
||||
- Data-driven decisions (métriques objectives)
|
||||
- Coût : dashboard admin à développer (one-time)
|
||||
@@ -361,26 +396,31 @@ GROUP BY content_id;
|
||||
**Niveaux de personnalisation** :
|
||||
|
||||
**Curseurs disponibles** :
|
||||
|
||||
- 📍 **Géolocalisation** : Local ← slider → National (découverte = national)
|
||||
- 🎲 **Découverte** : 0% ← slider → 50% (part aléatoire)
|
||||
- ⚖️ **Politique** : Masquer / Équilibré / Mes préférences
|
||||
|
||||
**Profils sauvegardables** :
|
||||
|
||||
- 🚗 Trajet quotidien (boulot) : géo local, découverte 5%, politique masqué
|
||||
- 🛣️ Road trip : géo régional, découverte 30%, politique équilibré
|
||||
- 👶 Enfants : Mode Kids activé
|
||||
|
||||
**Synchronisation** :
|
||||
|
||||
- ✅ Sync profils entre devices (cloud PostgreSQL)
|
||||
- ❌ Pas de partage profils entre utilisateurs (famille)
|
||||
- Auto-switch selon context (détection trajet récurrent via GPS)
|
||||
|
||||
**Sécurité conduite** :
|
||||
|
||||
- ⚠️ **Blocage modification si vitesse GPS >10 km/h**
|
||||
- Warning au lancement app : "Configurez avant de prendre la route"
|
||||
- Modifications uniquement app arrêtée/passager
|
||||
|
||||
**Justification** :
|
||||
|
||||
- Utilisateur maître de son expérience
|
||||
- Contextes d'usage différents (quotidien vs voyage)
|
||||
- Sécurité routière (pas de distraction)
|
||||
@@ -392,25 +432,30 @@ GROUP BY content_id;
|
||||
**Décision** : Ouverture aux médias établis
|
||||
|
||||
**Médias autorisés** :
|
||||
|
||||
- Presse nationale : Le Monde, Le Parisien, Libération, Le Figaro, etc.
|
||||
- Radios : France Inter, RTL, Europe 1, etc.
|
||||
- Médias régionaux : Ouest-France, Sud-Ouest, etc.
|
||||
|
||||
**Format contenus** :
|
||||
|
||||
- Flashs info géolocalisés (actualité régionale)
|
||||
- Chroniques thématiques (culture, économie, sport)
|
||||
- Éditos et débats (classification politique appliquée)
|
||||
|
||||
**Validation** :
|
||||
|
||||
- Compte média vérifié (badge ✓)
|
||||
- Pas de validation 3 premiers contenus (confiance établie)
|
||||
- Modération a posteriori uniquement
|
||||
|
||||
**Monétisation** :
|
||||
|
||||
- Partage revenus pub standard (même conditions créateurs)
|
||||
- Possibilité sponsoring direct (pas via plateforme)
|
||||
|
||||
**Justification** :
|
||||
|
||||
- Crédibilité plateforme (contenus professionnels)
|
||||
- Diversité éditoriale
|
||||
- Attractivité grand public (noms reconnus)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
**Note importante** : Les pourcentages indiqués sont des **points de pourcentage absolus**, **PAS des pourcentages relatifs**.
|
||||
|
||||
**Calcul** :
|
||||
|
||||
- Si jauge "Automobile" = 45%
|
||||
- Like renforcé (+2%) → 45 + 2 = **47%** ✅
|
||||
- **NOT** 45 × 1.02 = 45.9% ❌
|
||||
@@ -24,6 +25,7 @@
|
||||
Cette approche garantit une **progression linéaire** et **équitable** pour tous les utilisateurs, indépendamment de leur niveau actuel dans une jauge.
|
||||
|
||||
**Paramètres techniques** :
|
||||
|
||||
- Les jauges sont bornées strictement entre **0% et 100%**
|
||||
- Calcul immédiat à chaque action (pas de batch différé)
|
||||
- Les tags du contenu sont définis par le créateur à la publication
|
||||
@@ -61,6 +63,7 @@ Scénario 5 : Skip après 5s (ABONNÉ au créateur)
|
||||
```
|
||||
|
||||
**Justification** :
|
||||
|
||||
- **Like automatique** : Reflète l'engagement réel (voir [Règle 05 - Section 5.3](interactions-navigation.md#53-interactions-au-volant--like-automatique-et-engagement))
|
||||
- **Sécurité routière** : Pas d'action complexe en conduite
|
||||
- **Prévisibilité** : Règles claires et déterministes
|
||||
@@ -78,11 +81,13 @@ Scénario 5 : Skip après 5s (ABONNÉ au créateur)
|
||||
**Décision** : Démarrage neutre à 50%, pas de questionnaire
|
||||
|
||||
**À l'inscription** :
|
||||
|
||||
- Toutes les jauges d'intérêt sont initialisées à **50%**
|
||||
- Pas de questionnaire onboarding (friction zéro)
|
||||
- L'algorithme apprend naturellement via les premières écoutes
|
||||
|
||||
**Catégories disponibles** :
|
||||
|
||||
- Automobile
|
||||
- Voyage
|
||||
- Famille
|
||||
@@ -104,12 +109,14 @@ Scénario 5 : Skip après 5s (ABONNÉ au créateur)
|
||||
4. Après 10-15 écoutes, profil commence à se dessiner clairement
|
||||
|
||||
**Alternative optionnelle (post-MVP)** :
|
||||
|
||||
- Questionnaire **optionnel** proposé après 3 écoutes (in-app)
|
||||
- Message : "Améliorez vos recommandations en sélectionnant vos centres d'intérêt"
|
||||
- Si rempli : jauges sélectionnées passent à 70%, non sélectionnées à 30%
|
||||
- Si skip : conserve 50% partout
|
||||
|
||||
**Justification** :
|
||||
|
||||
- **Inscription ultra-rapide** : pas de questionnaire = moins de churn
|
||||
- **Découverte naturelle** : l'algorithme apprend en quelques écoutes
|
||||
- **Équitable** : pas de biais initial vers certains créateurs
|
||||
@@ -125,6 +132,7 @@ Scénario 5 : Skip après 5s (ABONNÉ au créateur)
|
||||
Les jauges **ne diminuent jamais** avec le temps de manière automatique.
|
||||
|
||||
**Règle** :
|
||||
|
||||
- Une jauge ne change **que par les actions utilisateur** (like, écoute, skip)
|
||||
- Pas de cron job de dégradation périodique
|
||||
- Pas de "rafraîchissement" artificiel
|
||||
@@ -138,16 +146,19 @@ Utilisateur aimait "Économie" (jauge 80%) il y a 1 an
|
||||
```
|
||||
|
||||
**Si utilisateur inactif longtemps** :
|
||||
|
||||
- Utilisateur part en vacances 6 mois → jauges conservées
|
||||
- Au retour : ses jauges reflètent toujours ses goûts d'avant
|
||||
- Comportement cohérent et prévisible
|
||||
|
||||
**Alternative utilisateur (contrôle explicite)** :
|
||||
|
||||
- Bouton "Réinitialiser mes centres d'intérêt" dans paramètres
|
||||
- Action manuelle : remet toutes les jauges à 50%
|
||||
- Permet nouveau départ si souhaité (changement de vie, etc.)
|
||||
|
||||
**Justification** :
|
||||
|
||||
- **Principe KISS** (Keep It Simple, Stupid)
|
||||
- **Coût 0** : pas de batch nocturne, pas de calculs temporels
|
||||
- **Fiabilité maximale** : pas de bugs de fuseaux horaires, dates, etc.
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
**Décision** : Pré-calcul 5 contenus avec insertion prioritaire pour points géographiques
|
||||
|
||||
**File d'attente** :
|
||||
|
||||
- **5 contenus pré-calculés** en cache (Redis)
|
||||
- Recalcul automatique si :
|
||||
- Déplacement >10km
|
||||
@@ -14,12 +15,14 @@
|
||||
**Insertion prioritaire géo-ancrée (mode voiture uniquement)** :
|
||||
|
||||
**Détection** :
|
||||
|
||||
- Calcul ETA (Estimated Time of Arrival) via API GPS native iOS/Android
|
||||
- Notification déclenchée **7 secondes avant** d'arriver au point GPS
|
||||
- Si vitesse < 5 km/h ET distance < 50m → notification immédiate
|
||||
- ⚠️ **App doit être ouverte** (pas de détection en arrière-plan en mode voiture)
|
||||
|
||||
**Notification** :
|
||||
|
||||
- **Sonore uniquement** : bip court ou son personnalisé RoadWave
|
||||
- **Visuelle minimale** : icône selon type de contenu (🏛️ culture, 👨👩👧 famille, 🎵 musique, etc.)
|
||||
- **Compteur visible** : 7...6...5...4...3...2...1 (décompte des secondes)
|
||||
@@ -34,19 +37,23 @@
|
||||
5. À la fin du décompte → contenu géolocalisé démarre (fade out/in 0.3s)
|
||||
|
||||
**Si user n'appuie pas sur "Suivant"** :
|
||||
|
||||
- Notification disparaît après 7 secondes
|
||||
- Contenu géolocalisé est perdu (pas d'insertion dans file)
|
||||
- Pas de nouveau contenu géolocalisé pendant **10 minutes** (éviter spam)
|
||||
|
||||
**Limitation anti-spam** :
|
||||
|
||||
- Maximum **6 contenus géolocalisés par heure**
|
||||
- Timer reset toutes les heures (rolling window)
|
||||
- Exception : séquences d'un même audio-guide multi-séquences (comptent comme 1)
|
||||
- Si quota atteint : notifications suivantes ignorées jusqu'à libération du quota
|
||||
|
||||
**Invalidation immédiate** :
|
||||
|
||||
- Utilisateur change ses préférences (curseurs géo/découverte/politique)
|
||||
- ⚠️ **Modification bloquée si vitesse GPS >10 km/h** (sécurité routière)
|
||||
|
||||
- Live démarre d'un créateur suivi dans la zone
|
||||
|
||||
**Implémentation** :
|
||||
@@ -76,6 +83,7 @@ Cooldown après ignorance (Redis) :
|
||||
```
|
||||
|
||||
**Justification** :
|
||||
|
||||
- **Expérience fluide** : pas de latence au clic "Suivant"
|
||||
- **Réactivité géo** : contenu local inséré immédiatement
|
||||
- **Coût optimisé** : recalcul uniquement si nécessaire
|
||||
@@ -88,12 +96,14 @@ Cooldown après ignorance (Redis) :
|
||||
**Décision** : Notifications push en arrière-plan avec rayon large
|
||||
|
||||
**Contexte** :
|
||||
|
||||
- Mode piéton détecté automatiquement si vitesse moyenne < 5 km/h
|
||||
- Cas d'usage : visites à pied, musées, monuments, quartiers historiques
|
||||
- User n'a pas besoin d'avoir l'app ouverte
|
||||
- ⚠️ **Fonctionnalité optionnelle** : requiert permission "localisation en arrière-plan" (activée par user)
|
||||
|
||||
**Détection** :
|
||||
|
||||
- App peut être en arrière-plan (si permission accordée)
|
||||
- Rayon de détection : **200 mètres** autour du point GPS
|
||||
- Geofencing iOS/Android pour minimiser consommation batterie
|
||||
@@ -119,12 +129,14 @@ Musée du Louvre : La Joconde - @paris_museum
|
||||
⚠️ **Important** : RoadWave utilise une **stratégie de permissions progressive** pour maximiser l'acceptation utilisateur et la validation stores.
|
||||
|
||||
**Étape 1 - Permission de base (tous utilisateurs)** :
|
||||
|
||||
- iOS : "Allow While Using App" (`locationWhenInUse`)
|
||||
- Android : `ACCESS_FINE_LOCATION`
|
||||
- **Demandée** : Au premier lancement (onboarding)
|
||||
- **Permet** : Mode voiture complet ✅
|
||||
|
||||
**Étape 2 - Permission arrière-plan (optionnelle, mode piéton uniquement)** :
|
||||
|
||||
- iOS : "Allow Always" (`locationAlways`)
|
||||
- Android : `ACCESS_BACKGROUND_LOCATION`
|
||||
- **Demandée** : Uniquement si user active "Notifications audio-guides piéton" dans settings
|
||||
@@ -132,6 +144,7 @@ Musée du Louvre : La Joconde - @paris_museum
|
||||
- **Permet** : Mode piéton avec notifications push en arrière-plan ✅
|
||||
|
||||
**Si permission arrière-plan refusée** :
|
||||
|
||||
- Mode piéton **désactivé** (toggle grisé dans settings)
|
||||
- Mode voiture reste **pleinement fonctionnel**
|
||||
- Audio-guides accessibles en mode **manuel** (user ouvre app, lance contenu)
|
||||
@@ -188,6 +201,7 @@ Android (`AndroidManifest.xml`) :
|
||||
```
|
||||
|
||||
**Si user refuse** :
|
||||
|
||||
- Mode piéton désactivé (uniquement mode voiture disponible)
|
||||
- App fonctionne normalement avec permission "When In Use"
|
||||
- Audio-guides accessibles en mode manuel (user ouvre app, sélectionne contenu)
|
||||
@@ -201,6 +215,7 @@ Android (`AndroidManifest.xml`) :
|
||||
**Basculement automatique voiture ↔ piéton** :
|
||||
|
||||
Détection par vitesse GPS moyenne sur 30 secondes :
|
||||
|
||||
- Vitesse < 5 km/h (stable 10s) → mode piéton
|
||||
- Vitesse ≥ 5 km/h (stable 10s) → mode voiture
|
||||
|
||||
@@ -212,15 +227,18 @@ Changements de mode :
|
||||
| Voiture | < 5 km/h | Piéton | Notifications sonores → push arrière-plan |
|
||||
|
||||
**Pas de popup confirmation** :
|
||||
|
||||
- Basculement transparent et automatique
|
||||
- User n'a rien à faire
|
||||
- Hysteresis (10s) pour éviter basculements intempestifs
|
||||
|
||||
**Quota anti-spam mode piéton** :
|
||||
|
||||
- Même limitation que mode voiture : **6 contenus/heure**
|
||||
- Cooldown 10 min si notification ignorée (app pas ouverte après tap)
|
||||
|
||||
**Justification** :
|
||||
|
||||
- ✅ Expérience adaptée aux visites à pied (rayon large, pas de timing précis)
|
||||
- ✅ Économie batterie (geofencing natif iOS/Android)
|
||||
- ✅ User peut garder téléphone en poche
|
||||
@@ -241,6 +259,7 @@ Changements de mode :
|
||||
| **Premier de session** | N/A | Replay depuis début (rien avant) |
|
||||
|
||||
**Historique de navigation** :
|
||||
|
||||
- **10 contenus maximum** en mémoire (Redis List)
|
||||
- Structure : `[{content_id, position_seconds, listened_at}, ...]`
|
||||
- FIFO : au-delà de 10, suppression du plus ancien
|
||||
@@ -257,11 +276,13 @@ Utilisateur écoute :
|
||||
```
|
||||
|
||||
**Interface (responsabilité front)** :
|
||||
|
||||
- ❌ Pas de message UI
|
||||
- ✅ Progress bar revient au début ou à position exacte
|
||||
- ✅ Animation fluide (transition 0.3s)
|
||||
|
||||
**Justification** :
|
||||
|
||||
- **UX intuitive** : comportement standard Spotify/YouTube
|
||||
- **Pas de frustration** : si début, vraiment revenir en arrière
|
||||
- **Simplicité** : règle unique (seuil 10s)
|
||||
@@ -273,6 +294,7 @@ Utilisateur écoute :
|
||||
**Décision** : Like automatique basé sur le temps d'écoute
|
||||
|
||||
**Problème technique identifié** :
|
||||
|
||||
- iOS et Android ne supportent **pas nativement** les appuis longs ou doubles-appuis sur les commandes média
|
||||
- Les commandes physiques au volant varient selon les véhicules (pas de bouton "Pause" dédié sur beaucoup de modèles)
|
||||
- Système de double-appui/appui long = **non-intuitif** et **risques sécurité** (regarder écran pour feedback)
|
||||
@@ -309,12 +331,14 @@ Utilisateur écoute :
|
||||
**Exemples concrets** :
|
||||
```
|
||||
Contenu de 3 minutes (180s) :
|
||||
|
||||
- Écoute 2min30 (83%) → Like renforcé (+2 points)
|
||||
- Écoute 1min15 (42%) → Like standard (+1 point)
|
||||
- Écoute 30s (17%) puis skip → Pas de like
|
||||
- Skip après 5s → Signal négatif (-0.5 point)
|
||||
|
||||
Contenu de 15 minutes (900s) :
|
||||
|
||||
- Écoute 13min (87%) → Like renforcé (+2 points)
|
||||
- Écoute 6min (40%) → Like standard (+1 point)
|
||||
```
|
||||
@@ -334,11 +358,13 @@ Contenu de 15 minutes (900s) :
|
||||
| **Signalement** | Menu contextuel "⋮" | Ouverture flux modération |
|
||||
|
||||
**Feedback visuel** :
|
||||
|
||||
- **Like automatique** : Badge discret "♥ Ajouté à vos favoris" (2s, bas de l'écran)
|
||||
- **Like explicite** : Animation cœur rouge + vibration courte
|
||||
- **Abonnement** : Animation étoile dorée + badge "Abonné ✓"
|
||||
|
||||
**Disponibilité** :
|
||||
|
||||
- ✅ Mode piéton (vitesse < 5 km/h) : toutes les actions disponibles
|
||||
- ❌ Mode voiture (vitesse ≥ 5 km/h) : aucune de ces actions (sauf like automatique)
|
||||
|
||||
@@ -347,17 +373,20 @@ Contenu de 15 minutes (900s) :
|
||||
#### Gestion impacts jauges (algorithme)
|
||||
|
||||
**Like automatique** :
|
||||
|
||||
- Like renforcé (≥80%) → **+2% jauges** de tous les tags du contenu
|
||||
- Like standard (30-79%) → **+1% jauges** des tags du contenu
|
||||
- Signal négatif (skip <10s) → **-0.5% jauges** des tags du contenu
|
||||
|
||||
**Actions explicites** :
|
||||
|
||||
- Like manuel → **+2% jauges** (cumulable avec like auto)
|
||||
- Unlike → **-2% jauges**
|
||||
- Abonnement → **+5% toutes jauges** tags créateur
|
||||
- Désabonnement → **-5% toutes jauges**
|
||||
|
||||
**Persistance** :
|
||||
|
||||
- Événements stockés en base (table `listen_events`)
|
||||
- Mise à jour jauges : **immédiate** (Redis) + **async batch** (PostgreSQL)
|
||||
|
||||
@@ -431,6 +460,7 @@ class AudioPlayerManager {
|
||||
#### Justification
|
||||
|
||||
**Avantages** :
|
||||
|
||||
- ✅ **Sécurité routière maximale** : aucune action complexe au volant
|
||||
- ✅ **UX intuitive** : comportement standard industrie (Spotify, YouTube Music, Deezer)
|
||||
- ✅ **Compatibilité 100%** : fonctionne sur tous véhicules, tous OS
|
||||
@@ -439,6 +469,7 @@ class AudioPlayerManager {
|
||||
- ✅ **Simplicité développement** : pas de workarounds complexes iOS/Android
|
||||
|
||||
**Inconvénients mitigés** :
|
||||
|
||||
- ⚠️ Pas de like explicite en conduite → **Mitigation** : like automatique + vocal (CarPlay/Android Auto)
|
||||
- ⚠️ Pas d'abonnement en conduite → **Mitigation** : liste "Créateurs à découvrir" dans app
|
||||
- ⚠️ Like automatique peut surprendre → **Mitigation** : onboarding clair + unlike possible
|
||||
@@ -502,6 +533,7 @@ Utilisez les boutons au volant :
|
||||
- Persiste : Redis (immédiat) + PostgreSQL (batch async)
|
||||
|
||||
**Séparation des responsabilités** :
|
||||
|
||||
- ✅ **Calculation** : Logique métier pure (réutilisable pour auto-like, skip, actions manuelles)
|
||||
- ✅ **Update** : Persistance et contraintes techniques
|
||||
|
||||
@@ -516,6 +548,7 @@ newValue := currentValue * (1 + adjustment/100) // NE PAS FAIRE
|
||||
```
|
||||
|
||||
**Persistance** :
|
||||
|
||||
- **Redis** : Mise à jour immédiate (latence <10ms)
|
||||
- **PostgreSQL** : Batch async toutes les 5 minutes (cohérence finale)
|
||||
- Raison : Jauges consultées fréquemment (recommandations temps réel)
|
||||
@@ -541,12 +574,14 @@ newValue := currentValue * (1 + adjustment/100) // NE PAS FAIRE
|
||||
| **Live** | 0 seconde | Enchaînement immédiat |
|
||||
|
||||
**Insertion publicité** :
|
||||
|
||||
- Pub s'insère **pendant le délai de 2s** (transition naturelle)
|
||||
- Fréquence : **paramétrable admin** (défaut : 1 pub / 5 contenus)
|
||||
- Message : "Publicité (15s)" puis lecture pub
|
||||
- ⚠️ **Jamais d'interruption** d'un contenu en cours
|
||||
|
||||
**Publicité skippable** :
|
||||
|
||||
- Durée minimale visionnage : **paramétrable** (défaut : 5 secondes)
|
||||
- Bouton "Passer" apparaît après délai
|
||||
- Métriques engagement : taux skip, durée écoute moyenne
|
||||
@@ -559,11 +594,13 @@ newValue := currentValue * (1 + adjustment/100) // NE PAS FAIRE
|
||||
4. Sinon → lecture en pause, attente action utilisateur
|
||||
|
||||
**Gestion erreurs** :
|
||||
|
||||
- Échec chargement contenu suivant → **retry 3× avec backoff exponentiel**
|
||||
- Si 3 échecs → message "Connexion instable, basculement mode offline"
|
||||
- Mode offline → lecture contenus téléchargés uniquement
|
||||
|
||||
**Justification** :
|
||||
|
||||
- **Fluidité** : enchaînement naturel sans action utilisateur
|
||||
- **Contrôle** : possibilité annuler pendant délai
|
||||
- **Paramétrabilité pub** : évite frustration excès publicité
|
||||
|
||||
Reference in New Issue
Block a user