Files
roadwave/docs/regles-metier/05-interactions-navigation.md
jpgiannetti 852f6d5e16 refactor(docs): réorganiser ADR et règles métier pour clarté
**Changements majeurs** :

1. **Suppression ADR-010 (Commandes volant et likes)** :
   - Contenu consolidé dans Règle 05 (section 5.3)
   - Raison : ADR-010 était du métier déguisé en architecture
   - Section "Implémentation Technique" ajoutée à Règle 05
   - Pattern correct (addition) vs incorrect (multiplication)

2. **Déplacement ADR-011 → Compliance** :
   - `docs/adr/011-conformite-stores.md` → `docs/compliance/stores-submission.md`
   - Raison : Nature opérationnelle/légale, pas architecture technique
   - Nouveau dossier `/docs/compliance/` créé

3. **Renumérotation ADR (010-022)** :
   - Combler les trous de numérotation (010 et 011)
   - ADR-012→010, ADR-013→011, ..., ADR-024→022
   - 22 ADR numérotés en continu (001-022)
   - Historique Git préservé (git mv)

4. **Mise à jour références** :
   - Règle 03 : ADR-010 → Règle 05 (section 5.3)
   - Règle 09 : ADR-010 → Règle 05 (section 5.3)
   - INCONSISTENCIES-ANALYSIS.md : toutes références mises à jour
   - Incohérence #15 annulée (faux problème : modes séparés)

**Résultat** :
-  Séparation claire ADR (technique) vs Règles métier (fonctionnel)
-  Documentation compliance séparée
-  Numérotation ADR continue sans trous
-  Single Source of Truth (pas de redondance)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-01 14:34:12 +01:00

21 KiB
Raw Blame History

5. Interactions et navigation

5.1 File d'attente et commande "Suivant"

Décision : Pré-calcul 5 contenus avec insertion prioritaire pour points géographiques

File d'attente :

  • 5 contenus pré-calculés en cache (Redis)
  • Recalcul automatique si :
    • Déplacement >10km
    • Toutes les 10 minutes (rafraîchissement contenu)
    • File d'attente <3 contenus restants

Insertion prioritaire géo-ancrée (mode voiture uniquement) :

Détection :

  • Calcul ETA (Estimated Time of Arrival) via API GPS native iOS/Android
  • Notification déclenchée 7 secondes avant d'arriver au point GPS
  • Si vitesse < 5 km/h ET distance < 50m → notification immédiate
  • ⚠️ App doit être ouverte (pas de détection en arrière-plan en mode voiture)

Notification :

  • Sonore uniquement : bip court ou son personnalisé RoadWave
  • Visuelle minimale : icône selon type de contenu (🏛️ culture, 👨‍👩‍👧 famille, 🎵 musique, etc.)
  • Compteur visible : 7...6...5...4...3...2...1 (décompte des secondes)
  • Pas de texte affiché (éviter distraction conducteur)
  • Pas de bouton "Annuler" : seul le bouton "Suivant" permet validation

Actions utilisateur :

  1. User entend notification sonore + voit icône et compteur
  2. User appuie "Suivant" dans les 7 secondes → décompte 5s démarre
  3. Pendant décompte : contenu actuel continue, compteur visible (5...4...3...2...1)
  4. Si contenu actuel se termine pendant décompte → contenu suivant du buffer démarre
  5. À la fin du décompte → contenu géolocalisé démarre (fade out/in 0.3s)

Si user n'appuie pas sur "Suivant" :

  • Notification disparaît après 7 secondes
  • Contenu géolocalisé est perdu (pas d'insertion dans file)
  • Pas de nouveau contenu géolocalisé pendant 10 minutes (éviter spam)

Limitation anti-spam :

  • Maximum 6 contenus géolocalisés par heure
  • Timer reset toutes les heures (rolling window)
  • Exception : séquences d'un même audio-guide multi-séquences (comptent comme 1)
  • Si quota atteint : notifications suivantes ignorées jusqu'à libération du quota

Invalidation immédiate :

  • Utilisateur change ses préférences (curseurs géo/découverte/politique)
    • ⚠️ Modification bloquée si vitesse GPS >10 km/h (sécurité routière)
  • Live démarre d'un créateur suivi dans la zone

Implémentation :

Redis cache :
  - Clé : user:{user_id}:queue
  - Structure : [content_1, content_2, ..., content_5]
  - Métadonnées : {last_lat, last_lon, computed_at, mode: "voiture"|"pieton"}
  - TTL : 15 minutes

Tracking GPS temps réel (mobile) :
  - Vérification toutes les 1 seconde
  - Calcul ETA vers points géolocalisés proches (rayon 500m)
  - Si ETA ≤ 7s → trigger notification
  - Historique GPS : 30 derniers points pour calcul vitesse moyenne

Quota anti-spam (Redis) :
  - Clé : user:{user_id}:geo_quota
  - Structure : sorted set avec timestamps des 6 derniers contenus
  - TTL : 1 heure
  - Vérification avant notification : ZCOUNT pour compter contenus dernière heure

Cooldown après ignorance (Redis) :
  - Clé : user:{user_id}:geo_cooldown
  - TTL : 10 minutes
  - Set après notification ignorée

Justification :

  • Expérience fluide : pas de latence au clic "Suivant"
  • Réactivité géo : contenu local inséré immédiatement
  • Coût optimisé : recalcul uniquement si nécessaire
  • Sécurité : pas de modification en conduite

5.1.2 Mode piéton (audio-guides)

Décision : Notifications push en arrière-plan avec rayon large

Contexte :

  • Mode piéton détecté automatiquement si vitesse moyenne < 5 km/h
  • Cas d'usage : visites à pied, musées, monuments, quartiers historiques
  • User n'a pas besoin d'avoir l'app ouverte
  • ⚠️ Fonctionnalité optionnelle : requiert permission "localisation en arrière-plan" (activée par user)

Détection :

  • App peut être en arrière-plan (si permission accordée)
  • Rayon de détection : 200 mètres autour du point GPS
  • Geofencing iOS/Android pour minimiser consommation batterie
  • Permission demandée uniquement si user active "Notifications audio-guides piéton" dans settings

Notification push système :

Format :

Titre : "Audio-guide à proximité"
Body : "[Nom du contenu] - [Nom créateur]"
Action : Tap → ouvre app sur le contenu

Exemple :

Audio-guide à proximité
Musée du Louvre : La Joconde - @paris_museum

Permissions requises :

⚠️ Important : 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
  • Précédée : Écran d'éducation expliquant l'usage (requis stores)
  • 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)
  • Garantie RGPD : App utilisable sans permission arrière-plan

iOS (Info.plist) :

<key>NSLocationWhenInUseUsageDescription</key>
<string>RoadWave utilise votre position pour vous proposer des contenus audio géolocalisés adaptés à votre trajet en temps réel.</string>

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Si vous activez les notifications audio-guides piéton, RoadWave peut vous alerter lorsque vous passez près d'un monument ou musée, même quand l'app est en arrière-plan. Cette fonctionnalité est optionnelle et peut être désactivée à tout moment dans les réglages.</string>

Android (AndroidManifest.xml) :

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />

📋 Référence technique : Voir ADR-010 - Stratégie de Permissions pour détails d'implémentation.

Disclosure avant demande permission (Android requis, iOS recommandé) :

Écran affiché avant demande permission "Always Location" :

┌────────────────────────────────────────┐
│ 📍 Notifications audio-guides piéton   │
├────────────────────────────────────────┤
│ Pour vous alerter d'audio-guides à     │
│ proximité même quand vous marchez avec │
│ l'app fermée, RoadWave a besoin de     │
│ votre position en arrière-plan.        │
│                                        │
│ Votre position sera utilisée pour :   │
│ ✅ Détecter monuments à 200m           │
│ ✅ Vous envoyer une notification       │
│                                        │
│ Votre position ne sera jamais :        │
│ ❌ Vendue à des tiers                  │
│ ❌ Utilisée pour de la publicité       │
│                                        │
│ Cette fonctionnalité est optionnelle.  │
│ Vous pouvez utiliser RoadWave sans     │
│ cette permission.                      │
│                                        │
│ [Continuer] [Non merci]                │
│                                        │
│ Plus d'infos : Politique confidentialité│
└────────────────────────────────────────┘

Si user refuse :

  • Mode piéton désactivé (uniquement mode voiture disponible)
  • App fonctionne normalement avec permission "When In Use"
  • Audio-guides accessibles en mode manuel (user ouvre app, sélectionne contenu)

Comportement après tap sur notification :

  1. User tap notification push
  2. App s'ouvre sur la page du contenu
  3. User peut démarrer la lecture manuellement
  4. Navigation libre (voir section 16.2 pour audio-guides piéton)

Basculement automatique voiture ↔ piéton :

Détection par vitesse GPS moyenne sur 30 secondes :

  • Vitesse < 5 km/h (stable 10s) → mode piéton
  • Vitesse ≥ 5 km/h (stable 10s) → mode voiture

Changements de mode :

Mode actuel Vitesse détectée Nouveau mode Effet
Piéton ≥ 5 km/h Voiture Notifications push → sonores + icône (app ouverte requise)
Voiture < 5 km/h Piéton Notifications sonores → push arrière-plan

Pas de popup confirmation :

  • Basculement transparent et automatique
  • User n'a rien à faire
  • Hysteresis (10s) pour éviter basculements intempestifs

Quota anti-spam mode piéton :

  • Même limitation que mode voiture : 6 contenus/heure
  • Cooldown 10 min si notification ignorée (app pas ouverte après tap)

Justification :

  • Expérience adaptée aux visites à pied (rayon large, pas de timing précis)
  • Économie batterie (geofencing natif iOS/Android)
  • User peut garder téléphone en poche
  • Basculement automatique = pas de friction

5.2 Commande "Précédent"

Décision : Comportement smart selon progression écoute

Règles :

Situation Temps écouté Action "Précédent"
Début de contenu <10 secondes Retour au contenu précédent (position exacte)
Milieu/fin ≥10 secondes Replay contenu actuel depuis le début
Premier de session N/A Replay depuis début (rien avant)

Historique de navigation :

  • 10 contenus maximum en mémoire (Redis List)
  • Structure : [{content_id, position_seconds, listened_at}, ...]
  • FIFO : au-delà de 10, suppression du plus ancien

Exemple scénario :

Utilisateur écoute :
1. Contenu A → écoute 5s → "Suivant"
2. Contenu B → écoute 2min30 → "Suivant"
3. Contenu C → écoute 5s → "Précédent"
   → Retour Contenu B à 2min30 (car >10s)
4. Sur Contenu B → "Précédent"
   → Retour Contenu A à 5s (position exacte)

Interface (responsabilité front) :

  • Pas de message UI
  • Progress bar revient au début ou à position exacte
  • Animation fluide (transition 0.3s)

Justification :

  • UX intuitive : comportement standard Spotify/YouTube
  • Pas de frustration : si début, vraiment revenir en arrière
  • Simplicité : règle unique (seuil 10s)

5.3 Interactions au volant : Like automatique et engagement

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)

Commandes au volant simplifiées

Actions disponibles (100% compatibles tous véhicules) :

Commande physique Action RoadWave
Suivant Passer au contenu suivant
Précédent Revenir au contenu précédent (règle 10s, voir section 5.2)
Play/Pause Pause/reprise lecture (fade out 0.3s)

Aucune action complexe au volant → Sécurité routière maximale.


Like automatique implicite

Principe : Le système détecte automatiquement l'intérêt utilisateur selon le temps d'écoute.

Règles d'attribution :

Durée écoutée Action automatique Points jauge Justification
≥ 80% du contenu Like renforcé +2.0 Écoute quasi-complète = fort intérêt
30-79% du contenu Like standard +1.0 Écoute significative = intérêt
< 30% du contenu Pas de like 0 Écoute trop courte
Skip après <10s Signal négatif -0.5 Désintérêt marqué

Exemples concrets :

Contenu de 3 minutes (180s) :
- Écoute 2min30 (83%) → Like renforcé (+2 points)
- Écoute 1min15 (42%) → Like standard (+1 point)
- Écoute 30s (17%) puis skip → Pas de like
- Skip après 5s → Signal négatif (-0.5 point)

Contenu de 15 minutes (900s) :
- Écoute 13min (87%) → Like renforcé (+2 points)
- Écoute 6min (40%) → Like standard (+1 point)

Actions complémentaires (mode piéton uniquement)

Interface mobile (vitesse < 5 km/h) :

Action Moyen Effet
Like explicite Bouton cœur +2 points jauge (même si déjà liké auto)
Unlike Re-clic cœur (toggle) -2 points jauge
Abonnement Bouton "S'abonner" profil créateur +5 points toutes jauges tags créateur
Désabonnement Bouton "Se désabonner" -5 points
Signalement Menu contextuel "⋮" Ouverture flux modération

Feedback visuel :

  • Like automatique : Badge discret "♥ Ajouté à vos favoris" (2s, bas de l'écran)
  • Like explicite : Animation cœur rouge + vibration courte
  • Abonnement : Animation étoile dorée + badge "Abonné ✓"

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)

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)

Implémentation technique

Backend (Go) :

type ListenEvent struct {
    UserID       string
    ContentID    string
    StartedAt    time.Time
    StoppedAt    time.Time
    Duration     int     // secondes écoutées
    ContentTotal int     // durée totale contenu
    Percentage   float64 // duration / contentTotal * 100
    Action       string  // "completed", "skipped", "paused"
}

func ProcessListenEvent(event ListenEvent) {
    percentage := event.Percentage

    // Signal négatif fort
    if event.Action == "skipped" && event.Duration < 10 {
        UpdateJauges(event.UserID, event.ContentID, -0.5)
        return
    }

    // Like automatique
    if percentage >= 80 {
        AutoLike(event.UserID, event.ContentID, 2.0) // Renforcé
    } else if percentage >= 30 {
        AutoLike(event.UserID, event.ContentID, 1.0) // Standard
    }
    // < 30% : pas de like
}

Mobile (iOS/Android) :

// iOS - Tracking écoute
class AudioPlayerManager {
    var startTime: Date?
    let contentDuration: TimeInterval

    func onPlay() {
        startTime = Date()
    }

    func onStop(action: String) { // "completed" | "skipped" | "paused"
        guard let start = startTime else { return }
        let duration = Date().timeIntervalSince(start)
        let percentage = (duration / contentDuration) * 100

        // API call
        API.track(ListenEvent(
            contentId: currentContentId,
            duration: Int(duration),
            percentage: percentage,
            action: action
        ))
    }
}

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
  • Engagement amélioré : tous les contenus écoutés génèrent des signaux
  • Algorithme plus précis : données granulaires (30%, 50%, 80%, 100%)
  • Simplicité développement : pas de workarounds complexes iOS/Android

Inconvénients mitigés :

  • ⚠️ Pas de like explicite en conduite → Mitigation : like automatique + vocal (CarPlay/Android Auto)
  • ⚠️ Pas d'abonnement en conduite → Mitigation : liste "Créateurs à découvrir" dans app
  • ⚠️ Like automatique peut surprendre → Mitigation : onboarding clair + unlike possible

Communication utilisateurs (onboarding)

Écran onboarding 1 :

🚗 Conduite sécurisée

RoadWave détecte automatiquement vos goûts
selon vos écoutes.

Plus vous écoutez longtemps, plus
l'algorithme s'améliore !

[Suivant]

Écran onboarding 2 :

❤️ Likes automatiques

Pas besoin de liker manuellement :
si vous écoutez >50% d'un contenu,
on comprend que vous aimez !

[Suivant]

Écran onboarding 3 :

⏸️ Commandes simples

Utilisez les boutons au volant :
• Suivant → Prochain contenu
• Précédent → Contenu d'avant
• Pause → Mettre en pause

[Commencer]

Implémentation Technique (Backend)

Architecture : 2 services séparés pour responsabilités distinctes

  1. Gauge Calculation Service :

    • Calcule l'ajustement basé sur % écoute (≥80% → +2%, 30-79% → +1%, skip <10s → -0.5%)
    • Retourne un ajustement absolu (float64, en points de pourcentage)
    • Stateless : logique métier pure, pas d'accès DB
    • Testable : unitairement sans dépendances
  2. Gauge Update Service :

    • Applique l'ajustement aux jauges concernées (addition/soustraction)
    • Applique les bornes [0, 100] (via fonction clamp)
    • Gère les multi-tags : si contenu a N tags → mise à jour de N jauges
    • 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

Pattern de calcul :

// ✅ CORRECT : Addition de points absolus
newValue := currentValue + adjustment
newValue = clamp(newValue, 0.0, 100.0)

// ❌ INCORRECT : Multiplication (pourcentage relatif)
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)

5.4 Lecture en boucle et enchaînement

Décision : Passage automatique après 2s + insertion pub paramétrable

Fin de contenu :

  1. Audio termine → Timer 2 secondes démarre
  2. UI overlay : "Contenu suivant dans 2s..." + barre décompte
  3. Possibilité annuler : bouton "Rester sur ce contenu" (optionnel)
  4. Timer atteint 0 → passage automatique au contenu suivant

Délai selon contexte :

Mode Délai Justification
Standard 2 secondes Temps réaction confortable
Mode Kids 1 seconde Attention courte enfants
Live 0 seconde Enchaînement immédiat

Insertion publicité :

  • Pub s'insère pendant le délai de 2s (transition naturelle)
  • Fréquence : paramétrable admin (défaut : 1 pub / 5 contenus)
  • Message : "Publicité (15s)" puis lecture pub
  • ⚠️ Jamais d'interruption d'un contenu en cours

Publicité skippable :

  • Durée minimale visionnage : paramétrable (défaut : 5 secondes)
  • Bouton "Passer" apparaît après délai
  • Métriques engagement : taux skip, durée écoute moyenne
  • Like et abonnement autorisés sur pub (engagement créateur pub)

Si aucun contenu disponible :

  1. Message : "Aucun contenu disponible dans cette zone"
  2. Proposition : "Élargir la zone de recherche ?" (bouton)
  3. Si accepté → relance algo avec rayon +50km
  4. Sinon → lecture en pause, attente action utilisateur

Gestion erreurs :

  • Échec chargement contenu suivant → retry 3× avec backoff exponentiel
  • Si 3 échecs → message "Connexion instable, basculement mode offline"
  • Mode offline → lecture contenus téléchargés uniquement

Justification :

  • Fluidité : enchaînement naturel sans action utilisateur
  • Contrôle : possibilité annuler pendant délai
  • Paramétrabilité pub : évite frustration excès publicité
  • Engagement pub : like/abonnement autorisé = monétisation créateurs pub

Récapitulatif Section 5