refactor(docs): réorganiser la documentation selon principes DDD
Réorganise la documentation du projet selon les principes du Domain-Driven Design (DDD) pour améliorer la cohésion, la maintenabilité et l'alignement avec l'architecture modulaire du backend. **Structure cible:** ``` docs/domains/ ├── README.md (Context Map) ├── _shared/ (Core Domain) ├── recommendation/ (Supporting Subdomain) ├── content/ (Supporting Subdomain) ├── moderation/ (Supporting Subdomain) ├── advertising/ (Generic Subdomain) ├── premium/ (Generic Subdomain) └── monetization/ (Generic Subdomain) ``` **Changements effectués:** Phase 1: Création de l'arborescence des 7 bounded contexts Phase 2: Déplacement des règles métier (01-19) vers domains/*/rules/ Phase 3: Déplacement des diagrammes d'entités vers domains/*/entities/ Phase 4: Déplacement des diagrammes flux/états/séquences vers domains/*/ Phase 5: Création des README.md pour chaque domaine Phase 6: Déplacement des features Gherkin vers domains/*/features/ Phase 7: Création du Context Map (domains/README.md) Phase 8: Mise à jour de mkdocs.yml pour la nouvelle navigation Phase 9: Correction automatique des liens internes (script fix-markdown-links.sh) Phase 10: Nettoyage de l'ancienne structure (regles-metier/, diagrammes/, features/) **Configuration des tests:** - Makefile: godog run docs/domains/*/features/ - scripts/generate-bdd-docs.py: features_dir → docs/domains **Avantages:** ✅ Cohésion forte: toute la doc d'un domaine au même endroit ✅ Couplage faible: domaines indépendants, dépendances explicites ✅ Navigabilité améliorée: README par domaine = entrée claire ✅ Alignement code/docs: miroir de backend/internal/ ✅ Onboarding facilité: exploration domaine par domaine ✅ Tests BDD intégrés: features au plus près des règles métier Voir docs/REFACTOR-DDD.md pour le plan complet.
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
# language: fr
|
||||
Fonctionnalité: Neutralisation des pénalités de skip pour abonnés
|
||||
En tant que système de jauges d'intérêt
|
||||
Je veux neutraliser les pénalités de skip pour les abonnés d'un créateur
|
||||
Afin de reconnaître l'affinité globale malgré des skips ponctuels contextuels
|
||||
|
||||
Contexte:
|
||||
Étant donné qu'un utilisateur existe avec les jauges suivantes:
|
||||
| catégorie | niveau |
|
||||
| Automobile | 45% |
|
||||
| Voyage | 60% |
|
||||
Et qu'un créateur "CreateurA" publie des contenus tagués "Automobile"
|
||||
|
||||
# Skip <10s - Utilisateur NON abonné
|
||||
|
||||
Scénario: Skip rapide <10s par non-abonné - Pénalité -0.5%
|
||||
Étant donné que l'utilisateur n'est PAS abonné à "CreateurA"
|
||||
Et qu'un contenu "Podcast Auto" de "CreateurA" est tagué "Automobile"
|
||||
Et que la jauge "Automobile" est à 45%
|
||||
Quand l'utilisateur skip le contenu après 5 secondes
|
||||
Alors la jauge "Automobile" descend de -0.5%
|
||||
Et la jauge "Automobile" passe de 45% à 44.5%
|
||||
Et cela indique un désintérêt marqué pour ce contenu
|
||||
|
||||
Scénario: Skip rapide <10s par non-abonné - Colonne is_subscribed=false
|
||||
Étant donné que l'utilisateur n'est PAS abonné à "CreateurA"
|
||||
Et qu'un contenu est skippé après 8 secondes
|
||||
Quand l'événement est enregistré dans user_listening_history
|
||||
Alors la colonne is_subscribed = false
|
||||
Et la colonne completion_rate = 0.05 (8s sur 160s)
|
||||
Et la colonne source = "recommendation"
|
||||
Et ce skip compte dans les métriques d'engagement du contenu
|
||||
|
||||
# Skip <10s - Utilisateur ABONNÉ
|
||||
|
||||
Scénario: Skip rapide <10s par abonné - Pénalité neutre 0%
|
||||
Étant donné que l'utilisateur EST abonné à "CreateurA"
|
||||
Et qu'un contenu "Podcast Auto" de "CreateurA" est tagué "Automobile"
|
||||
Et que la jauge "Automobile" est à 45%
|
||||
Quand l'utilisateur skip le contenu après 5 secondes
|
||||
Alors la jauge "Automobile" reste à 45% (pénalité 0%)
|
||||
Et aucune pénalité n'est appliquée
|
||||
Et cela reflète que l'abonnement indique une affinité globale
|
||||
|
||||
Scénario: Skip rapide <10s par abonné - Colonne is_subscribed=true
|
||||
Étant donné que l'utilisateur EST abonné à "CreateurA"
|
||||
Et qu'un contenu est skippé après 7 secondes
|
||||
Quand l'événement est enregistré dans user_listening_history
|
||||
Alors la colonne is_subscribed = true
|
||||
Et la colonne completion_rate = 0.04 (7s sur 180s)
|
||||
Et la colonne source = "recommendation"
|
||||
Et ce skip NE compte PAS dans les métriques d'engagement du contenu
|
||||
|
||||
# Calcul métriques engagement créateur
|
||||
|
||||
Scénario: Métriques engagement - Skip d'abonné ne pénalise pas
|
||||
Étant donné qu'un contenu "Podcast A" de "CreateurA" a reçu:
|
||||
| utilisateur | abonné ? | action | source | completion_rate |
|
||||
| User1 | Non | Skip <10s | recommendation | 0.05 |
|
||||
| User2 | Oui | Skip <10s | recommendation | 0.04 |
|
||||
| User3 | Non | Écoute complète | recommendation | 0.90 |
|
||||
| User4 | Oui | Skip <10s | recommendation | 0.03 |
|
||||
| User5 | Non | Écoute partielle | recommendation | 0.60 |
|
||||
Quand le système calcule les métriques d'engagement du contenu
|
||||
Alors les écoutes pertinentes comptabilisées sont:
|
||||
| utilisateur | comptabilisé ? | raison |
|
||||
| User1 | ✅ Oui | Non-abonné, source pertinente |
|
||||
| User2 | ❌ Non | Abonné, skip contextuel |
|
||||
| User3 | ✅ Oui | Non-abonné, source pertinente |
|
||||
| User4 | ❌ Non | Abonné, skip contextuel |
|
||||
| User5 | ✅ Oui | Non-abonné, source pertinente |
|
||||
Et le total écoutes pertinentes = 3 (User1, User3, User5)
|
||||
Et le taux de complétion = 2 complètes (User3, User5 >80%) / 3 = 66.7%
|
||||
|
||||
# Sources d'écoute et neutralisation
|
||||
|
||||
Scénario: Skip d'abonné via "recommendation" - Ne compte pas
|
||||
Étant donné que l'utilisateur EST abonné à "CreateurA"
|
||||
Et qu'un contenu est recommandé via l'algorithme (source="recommendation")
|
||||
Quand l'utilisateur skip après 5s
|
||||
Alors l'écoute NE compte PAS dans "total écoutes" pour métriques
|
||||
Et la pénalité jauge est neutralisée (0%)
|
||||
|
||||
Scénario: Skip d'abonné via "live_notification" - Ne compte pas
|
||||
Étant donné que l'utilisateur EST abonné à "CreateurA"
|
||||
Et que "CreateurA" publie un contenu en live
|
||||
Et qu'une notification live est envoyée (source="live_notification")
|
||||
Quand l'utilisateur skip après 6s
|
||||
Alors l'écoute NE compte PAS dans "total écoutes" pour métriques
|
||||
Et la pénalité jauge est neutralisée (0%)
|
||||
|
||||
Scénario: Skip d'abonné via "search" - Ne compte pas (indépendamment abonnement)
|
||||
Étant donné que l'utilisateur EST abonné à "CreateurA"
|
||||
Et qu'il trouve un contenu via la recherche (source="search")
|
||||
Quand l'utilisateur skip après 4s
|
||||
Alors l'écoute NE compte PAS dans "total écoutes" (source non pertinente)
|
||||
Et la pénalité jauge est neutralisée (0%)
|
||||
Et cela s'applique à tous users (abonnés ou non) pour source="search"
|
||||
|
||||
Scénario: Skip non-abonné via "direct_link" - Ne compte pas
|
||||
Étant donné que l'utilisateur N'est PAS abonné à "CreateurA"
|
||||
Et qu'il clique sur un lien direct partagé (source="direct_link")
|
||||
Quand l'utilisateur skip après 3s
|
||||
Alors l'écoute NE compte PAS dans "total écoutes" (source non pertinente)
|
||||
Mais la pénalité jauge s'applique quand même (-0.5%) pour non-abonné
|
||||
|
||||
# Reproposition
|
||||
|
||||
Scénario: Skip <10s non-abonné - Pas de reproposition
|
||||
Étant donné que l'utilisateur N'est PAS abonné à "CreateurA"
|
||||
Et qu'un contenu est skippé après 8 secondes
|
||||
Quand l'algorithme calcule les prochaines recommandations
|
||||
Alors ce contenu n'est jamais reproposé à cet utilisateur
|
||||
Car c'est un signal négatif clair de désintérêt
|
||||
|
||||
Scénario: Skip <10s abonné - Reproposition possible
|
||||
Étant donné que l'utilisateur EST abonné à "CreateurA"
|
||||
Et qu'un contenu est skippé après 7 secondes
|
||||
Quand l'algorithme calcule les prochaines recommandations plusieurs jours plus tard
|
||||
Alors ce contenu PEUT être reproposé à cet utilisateur
|
||||
Car l'abonnement indique une affinité globale
|
||||
Et le skip peut être contextuel ("pas maintenant", "pas ce sujet")
|
||||
|
||||
Scénario: Stockage is_subscribed dans user_content_history
|
||||
Étant donné qu'un utilisateur EST abonné à "CreateurA" au moment de l'écoute
|
||||
Quand un contenu de "CreateurA" est écouté/skippé
|
||||
Alors la table user_content_history enregistre:
|
||||
| colonne | valeur |
|
||||
| user_id | 123 |
|
||||
| content_id | 456 |
|
||||
| creator_id | 789 (CreateurA) |
|
||||
| is_subscribed | true |
|
||||
| completion_rate| 0.05 |
|
||||
| source | "recommendation" |
|
||||
| listened_at | 2026-02-07 10:30:00 |
|
||||
|
||||
# Cohérence UX
|
||||
|
||||
Scénario: Abonnement = Signal affinité fort malgré skip ponctuel
|
||||
Étant donné que l'utilisateur est abonné à "CreateurA" depuis 6 mois
|
||||
Et qu'il a écouté 50 contenus de "CreateurA" avec 90% de complétion moyenne
|
||||
Quand il skip 1 contenu après 5 secondes aujourd'hui
|
||||
Alors ce skip ponctuel ne pénalise pas:
|
||||
| aspect | impact |
|
||||
| Jauges d'intérêt user | 0% (neutre) |
|
||||
| Métriques engagement créateur | Ne compte pas dans total écoutes |
|
||||
| Reproposition future | Contenu peut être reproposé |
|
||||
Et cela reflète que le skip est contextuel, pas un rejet du créateur
|
||||
|
||||
# Anti-raid naturel
|
||||
|
||||
Scénario: Raid malveillant via liens directs - Inefficace
|
||||
Étant donné qu'un groupe malveillant veut nuire à "CreateurA"
|
||||
Et qu'ils partagent des liens directs pour inciter au skip massif
|
||||
Quand 1000 personnes cliquent sur le lien et skip après 2s
|
||||
Alors ces 1000 skips NE comptent PAS dans les métriques engagement
|
||||
Car source="direct_link" n'est pas une source pertinente
|
||||
Et "CreateurA" est protégé contre ce type de raid
|
||||
|
||||
Scénario: Raid malveillant via recherche - Inefficace
|
||||
Étant donné qu'un groupe cherche à nuire à "CreateurA"
|
||||
Et qu'ils trouvent le contenu via recherche et skip massivement
|
||||
Quand 500 skips rapides arrivent via source="search"
|
||||
Alors ces 500 skips NE comptent PAS dans les métriques engagement
|
||||
Car source="search" n'est pas une source pertinente
|
||||
Et "CreateurA" est protégé
|
||||
|
||||
# Cas limites
|
||||
|
||||
Scénario: Utilisateur s'abonne pendant l'écoute d'un contenu
|
||||
Étant donné qu'un utilisateur N'est PAS abonné à "CreateurA"
|
||||
Et qu'il démarre l'écoute d'un contenu de "CreateurA"
|
||||
Et que is_subscribed=false est enregistré au démarrage
|
||||
Quand l'utilisateur s'abonne à "CreateurA" pendant l'écoute
|
||||
Et qu'il skip le contenu après 8 secondes
|
||||
Alors is_subscribed=false reste enregistré (état au moment du démarrage)
|
||||
Et la pénalité -0.5% s'applique (car non-abonné au démarrage)
|
||||
|
||||
Scénario: Utilisateur se désabonne puis écoute ancien contenu
|
||||
Étant donné qu'un utilisateur ÉTAIT abonné à "CreateurA"
|
||||
Et qu'il se désabonne aujourd'hui
|
||||
Quand il écoute un ancien contenu de "CreateurA" demain
|
||||
Et qu'il skip après 6 secondes
|
||||
Alors is_subscribed=false (état au moment de l'écoute)
|
||||
Et la pénalité -0.5% s'applique
|
||||
Et l'écoute compte dans les métriques d'engagement
|
||||
|
||||
# Comparaison tableaux sources
|
||||
|
||||
Scénario: Table récapitulative sources et abonnements
|
||||
Étant donné les règles de comptabilisation définies
|
||||
Quand on résume le comportement par source et abonnement
|
||||
Alors le tableau complet est:
|
||||
| Source | Abonné ? | Skip <10s pénalise ? | Compte "total écoutes" ? |
|
||||
| recommendation | Non | ✅ Oui (-0.5%) | ✅ Oui |
|
||||
| recommendation | Oui | ❌ Non (0%) | ❌ Non |
|
||||
| search | Peu imp. | Variable* | ❌ Non |
|
||||
| direct_link | Peu imp. | Variable* | ❌ Non |
|
||||
| profile | Peu imp. | Variable* | ❌ Non |
|
||||
| history | Peu imp. | Variable* | ❌ Non |
|
||||
| live_notification | Non | ✅ Oui (-0.5%) | ✅ Oui |
|
||||
| live_notification | Oui | ❌ Non (0%) | ❌ Non |
|
||||
| audio_guide | Peu imp. | ❌ Non | ❌ Non |
|
||||
(* Variable = -0.5% si non-abonné, 0% si abonné, mais source non pertinente donc pas dans métriques)
|
||||
Reference in New Issue
Block a user