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:
jpgiannetti
2026-02-07 17:15:02 +01:00
parent 78422bb2c0
commit 5e5fcf4714
227 changed files with 1413 additions and 1967 deletions

View File

@@ -0,0 +1,296 @@
# language: fr
Fonctionnalité: API - Pas de dégradation temporelle
En tant qu'API backend
Je veux que les jauges n'évoluent que par les actions utilisateur
Afin d'avoir un comportement prévisible sans automatisme caché
Contexte:
Étant donné que l'API RoadWave est disponible
Et que la base de données PostgreSQL est accessible
Scénario: API ne dégrade jamais les jauges automatiquement
Étant donné qu'un utilisateur "user123" a une jauge "Économie" à 80% en base
Et que la colonne updated_at = "2026-01-01T10:00:00Z"
Quand 30 jours s'écoulent sans activité
Et je GET /api/v1/users/user123/interest-gauges le "2026-02-01T10:00:00Z"
Alors le statut de réponse est 200
Et la jauge "Économie" est toujours à 80%
Et aucune dégradation temporelle n'a été appliquée
Et la colonne updated_at n'a pas changé
Scénario: Aucun cron job de dégradation n'existe
Étant donné que le système vérifie les tâches cron planifiées
Quand je liste tous les cron jobs du backend
Alors aucun job nommé "degrade_interest_gauges" n'existe
Et aucun job périodique ne modifie la table interest_gauges
Et aucune ressource CPU n'est consommée pour la dégradation
Scénario: API GET jauges après 6 mois d'inactivité
Étant donné qu'un utilisateur "user123" a les jauges suivantes:
| catégorie | niveau | updated_at |
| Automobile | 75% | 2025-08-01T10:00:00 |
| Voyage | 60% | 2025-08-01T10:00:00 |
| Musique | 45% | 2025-08-01T10:00:00 |
Et qu'il ne se connecte pas pendant 6 mois
Quand je GET /api/v1/users/user123/interest-gauges le "2026-02-01T10:00:00Z"
Alors le statut de réponse est 200
Et les jauges sont exactement les mêmes:
| catégorie | niveau |
| Automobile | 75% |
| Voyage | 60% |
| Musique | 45% |
Et aucune modification n'a été appliquée
Scénario: Évolution par actions utilisateur uniquement
Étant donné qu'un utilisateur "user123" a une jauge "Économie" à 80%
Et qu'il skip 50 contenus "Économie" en 1 an
Quand je calcule l'évolution via les events
Alors la jauge "Économie" descend via les skips:
| action | impact | nouveau_niveau |
| 50 skips × -0.5%| -25% | 55% |
Et la dégradation vient des actions, pas du temps
Et la colonne updated_at reflète la date du dernier skip
Scénario: API POST réinitialiser centres d'intérêt
Étant donné qu'un utilisateur "user123" a des jauges personnalisées:
| catégorie | niveau |
| Automobile | 75% |
| Voyage | 60% |
| Économie | 34% |
| Sport | 88% |
Quand je POST /api/v1/users/user123/interest-gauges/reset
"""json
{
"confirmation": true
}
"""
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"message": "Vos centres d'intérêt ont été réinitialisés",
"previous_gauges_saved": true,
"new_gauges": {
"all_categories": 50
}
}
"""
Et en base de données, toutes les jauges de "user123" sont à 50%:
| catégorie | niveau |
| Automobile | 50 |
| Voyage | 50 |
| Économie | 50 |
| Sport | 50 |
| Musique | 50 |
| Technologie | 50 |
| Santé | 50 |
| Politique | 50 |
| Cryptomonnaie | 50 |
| Culture générale | 50 |
| Famille | 50 |
| Amour | 50 |
Scénario: API sauvegarde jauges précédentes avant réinitialisation
Étant donné qu'un utilisateur "user123" a des jauges personnalisées
Quand je POST /api/v1/users/user123/interest-gauges/reset
"""json
{
"confirmation": true
}
"""
Alors le statut de réponse est 200
Et une ligne est insérée dans interest_gauges_snapshots:
| user_id | snapshot_type | snapshot_date | gauges_json |
| user123 | manual_reset | 2026-02-02T14:00:00 | {"Automobile": 75, "Voyage": 60, ...} |
Et l'historique permet de restaurer si besoin
Scénario: API rejette réinitialisation sans confirmation
Quand je POST /api/v1/users/user123/interest-gauges/reset
"""json
{
"confirmation": false
}
"""
Alors le statut de réponse est 400
Et la réponse contient:
"""json
{
"error": "CONFIRMATION_REQUIRED",
"message": "Vous devez confirmer la réinitialisation"
}
"""
Et les jauges ne sont pas modifiées
Scénario: API recommandations après réinitialisation
Étant donné qu'un utilisateur "user123" avait "Économie" à 85%
Et qu'il réinitialise ses jauges (toutes à 50%)
Quand je POST /api/v1/recommendations
"""json
{
"user_id": "user123",
"latitude": 48.8566,
"longitude": 2.3522,
"limit": 10
}
"""
Alors le statut de réponse est 200
Et les recommandations utilisent 50% pour toutes les catégories
Et plus aucun biais "Économie" n'est appliqué
Et la géolocalisation redevient le critère principal
Scénario: Historique d'écoute conservé après réinitialisation
Étant donné qu'un utilisateur "user123" a écouté 500 contenus
Et que la table listening_history contient 500 lignes pour "user123"
Quand je POST /api/v1/users/user123/interest-gauges/reset
Alors le statut de réponse est 200
Et la table listening_history conserve toujours les 500 lignes
Et aucune donnée d'historique n'est supprimée
Et l'utilisateur peut toujours consulter ses anciens contenus écoutés
Scénario: API GET historique après réinitialisation
Étant donné qu'un utilisateur "user123" a réinitialisé ses jauges
Quand je GET /api/v1/users/user123/listening-history
Alors le statut de réponse est 200
Et la réponse contient tous les anciens contenus écoutés
Et l'historique est intact
Scénario: API enregistre timestamp réinitialisation
Étant donné qu'un utilisateur "user123" réinitialise ses jauges
Quand je POST /api/v1/users/user123/interest-gauges/reset
Alors en base de données, la table users est mise à jour:
| user_id | interest_gauges_reset_at | reset_count |
| user123 | 2026-02-02T14:00:00Z | 1 |
Et un compteur permet de tracker les réinitialisations multiples
Scénario: API permet réinitialisations multiples
Étant donné qu'un utilisateur "user123" a déjà réinitialisé une fois
Quand je POST /api/v1/users/user123/interest-gauges/reset une 2ème fois
Alors le statut de réponse est 200
Et le reset_count passe à 2
Et toutes les jauges reviennent à 50%
Scénario: API n'envoie jamais de suggestion de réinitialisation
Étant donné qu'un utilisateur "user123" n'a pas utilisé l'app depuis 1 an
Quand je GET /api/v1/users/user123/notifications
Alors le statut de réponse est 200
Et aucune notification "Réinitialiser vos centres d'intérêt" n'est présente
Et le système ne suggère jamais de réinitialisation automatique
Scénario: API GET statistiques respect historique utilisateur
Étant donné qu'un utilisateur "user123" aime "Cryptomonnaie" depuis 2 ans
Et que sa jauge est à 90%
Et qu'il n'a pas écouté de contenu "Cryptomonnaie" depuis 6 mois
Quand je GET /api/v1/users/user123/interest-gauges
Alors le statut de réponse est 200
Et la jauge "Cryptomonnaie" est toujours à 90%
Et la réponse contient:
"""json
{
"gauges": [
{
"category": "Cryptomonnaie",
"level": 90,
"last_updated": "2025-08-02T10:00:00Z",
"days_since_update": 183,
"preserved": true
}
]
}
"""
Et le système respecte l'historique des goûts
Scénario: API métrique temps écoulé sans modifier la jauge
Étant donné qu'un utilisateur "user123" a une jauge "Sport" à 65%
Et que la dernière modification date de 90 jours
Quand je GET /api/v1/users/user123/interest-gauges/sport
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"category": "Sport",
"level": 65,
"last_updated": "2025-11-03T12:00:00Z",
"days_since_update": 90
}
"""
Et la métrique days_since_update est informative uniquement
Et elle ne modifie jamais la jauge
Scénario: Requête SQL n'utilise jamais de calcul temporel
Étant donné que je trace les requêtes SQL du backend
Quand je GET /api/v1/users/user123/interest-gauges
Alors la requête SQL exécutée est:
"""sql
SELECT category, level, updated_at
FROM interest_gauges
WHERE user_id = $1
"""
Et aucune clause WHERE avec date/timestamp n'est présente
Et aucune fonction NOW(), CURRENT_TIMESTAMP, ou DATEDIFF n'est utilisée
Et le calcul est minimal (simple SELECT)
Scénario: API coût CPU minimal - pas de calcul de dates
Étant donné que 10000 utilisateurs consultent leurs jauges simultanément
Quand les requêtes /api/v1/users/{id}/interest-gauges sont exécutées
Alors aucun calcul de date n'est nécessaire
Et aucun appel à time.Now() ou time.Since() n'est fait
Et le coût CPU par requête est < 1ms
Et aucune dégradation de performance liée aux dates
Scénario: API pas de risque de bug fuseau horaire
Étant donné qu'aucune logique temporelle n'existe
Quand un utilisateur change de fuseau horaire (Paris Tokyo)
Alors ses jauges ne sont pas affectées
Et aucun bug de conversion UTC/local ne peut survenir
Et le comportement reste déterministe
Scénario: Audit log réinitialisation manuelle
Étant donné qu'un utilisateur "user123" réinitialise ses jauges
Quand je POST /api/v1/users/user123/interest-gauges/reset
Alors une ligne est insérée dans audit_log:
| user_id | action | timestamp | details |
| user123 | interest_gauges_reset | 2026-02-02T14:00:00 | {"previous_snapshot_id": 42} |
Et l'audit permet de tracer toutes les réinitialisations
Scénario: API empêche réinitialisation trop fréquente
Étant donné qu'un utilisateur "user123" a réinitialisé il y a 10 minutes
Quand je POST /api/v1/users/user123/interest-gauges/reset
Alors le statut de réponse est 429
Et la réponse contient:
"""json
{
"error": "RATE_LIMIT_EXCEEDED",
"message": "Vous ne pouvez réinitialiser qu'une fois par heure",
"retry_after_seconds": 3000
}
"""
Scénario: API documentation endpoints réinitialisation
Quand je GET /api/v1/openapi.json
Alors le endpoint POST /api/v1/users/{id}/interest-gauges/reset est documenté:
"""yaml
/users/{id}/interest-gauges/reset:
post:
summary: Réinitialise toutes les jauges à 50%
description: |
Remet toutes les jauges d'intérêt à leur valeur par défaut (50%).
Cette action est manuelle et requiert une confirmation.
Les jauges précédentes sont sauvegardées.
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
confirmation:
type: boolean
description: Doit être true
responses:
200:
description: Réinitialisation réussie
400:
description: Confirmation manquante
429:
description: Trop de réinitialisations
"""

View File

@@ -0,0 +1,567 @@
# language: fr
Fonctionnalité: API - Évolution des jauges d'intérêt
En tant qu'API backend
Je veux calculer et persister les évolutions de jauges d'intérêt
Afin d'alimenter l'algorithme de recommandation
Contexte:
Étant donné que l'API RoadWave est disponible
Et que la base de données PostgreSQL est accessible
Et qu'un utilisateur "user123" existe avec token JWT valide
Scénario: API calcule like automatique renforcé (≥80% écoute)
Étant donné que l'utilisateur "user123" a une jauge "Automobile" à 45% en base
Et qu'un contenu "content456" de 5 minutes est tagué "Automobile"
Quand je POST /api/v1/listening-events
"""json
{
"user_id": "user123",
"content_id": "content456",
"listened_duration_seconds": 270,
"total_duration_seconds": 300,
"completion_percentage": 90
}
"""
Alors le statut de réponse est 201
Et la réponse contient:
"""json
{
"like_type": "automatic_reinforced",
"gauge_updates": [
{
"category": "Automobile",
"previous_value": 45,
"delta": 2,
"new_value": 47
}
]
}
"""
Et en base de données, la jauge "Automobile" de "user123" est à 47%
Scénario: API calcule like automatique standard (30-79% écoute)
Étant donné que l'utilisateur "user123" a une jauge "Voyage" à 60% en base
Et qu'un contenu "content789" de 10 minutes est tagué "Voyage"
Quand je POST /api/v1/listening-events
"""json
{
"user_id": "user123",
"content_id": "content789",
"listened_duration_seconds": 300,
"total_duration_seconds": 600,
"completion_percentage": 50
}
"""
Alors le statut de réponse est 201
Et la réponse contient:
"""json
{
"like_type": "automatic_standard",
"gauge_updates": [
{
"category": "Voyage",
"previous_value": 60,
"delta": 1,
"new_value": 61
}
]
}
"""
Et en base de données, la jauge "Voyage" de "user123" est à 61%
Scénario: API applique like manuel explicite (+2%)
Étant donné que l'utilisateur "user123" a une jauge "Musique" à 55% en base
Et qu'un contenu "content999" est tagué "Musique"
Quand je POST /api/v1/likes
"""json
{
"user_id": "user123",
"content_id": "content999",
"like_type": "manual"
}
"""
Alors le statut de réponse est 201
Et la réponse contient:
"""json
{
"like_id": "<uuid>",
"gauge_updates": [
{
"category": "Musique",
"previous_value": 55,
"delta": 2,
"new_value": 57
}
]
}
"""
Et en base de données, la jauge "Musique" de "user123" est à 57%
Scénario: API applique unlike (retire like manuel)
Étant donné que l'utilisateur "user123" a une jauge "Sport" à 57% en base
Et qu'il a liké manuellement le contenu "content888" tagué "Sport"
Et que le like a l'ID "like_abc123"
Quand je DELETE /api/v1/likes/like_abc123
Alors le statut de réponse est 204
Et en base de données, la jauge "Sport" de "user123" est à 55%
Et le like "like_abc123" est supprimé de la table likes
Scénario: API refuse unlike d'un like automatique
Étant donné que l'utilisateur "user123" a écouté un contenu à 90%
Et qu'il a reçu un like automatique "like_auto456"
Quand je DELETE /api/v1/likes/like_auto456
Alors le statut de réponse est 403
Et la réponse contient:
"""json
{
"error": "CANNOT_UNLIKE_AUTOMATIC",
"message": "Les likes automatiques ne peuvent pas être retirés"
}
"""
Et en base de données, le like "like_auto456" existe toujours
Scénario: API cumule like automatique + like manuel
Étant donné que l'utilisateur "user123" a une jauge "Technologie" à 50% en base
Et qu'un contenu "content777" de 10 minutes est tagué "Technologie"
Quand je POST /api/v1/listening-events
"""json
{
"user_id": "user123",
"content_id": "content777",
"completion_percentage": 50
}
"""
Alors la jauge "Technologie" passe à 51% (+1% auto)
Quand je POST ensuite /api/v1/likes
"""json
{
"user_id": "user123",
"content_id": "content777",
"like_type": "manual"
}
"""
Alors la jauge "Technologie" passe à 53% (+2% manuel)
Et le delta total est de 3%
Scénario: API applique bonus abonnement créateur (+5% tous tags)
Étant donné que l'utilisateur "user123" a les jauges suivantes:
| catégorie | niveau |
| Automobile | 50% |
| Technologie | 45% |
Et qu'un créateur "creator456" publie des contenus tagués "Automobile" et "Technologie"
Quand je POST /api/v1/subscriptions
"""json
{
"user_id": "user123",
"creator_id": "creator456"
}
"""
Alors le statut de réponse est 201
Et en base de données:
| catégorie | niveau |
| Automobile | 55% |
| Technologie | 50% |
Et l'abonnement est créé avec bonus appliqué
Scénario: API retire bonus désabonnement créateur (-5% tous tags)
Étant donné que l'utilisateur "user123" a les jauges suivantes:
| catégorie | niveau |
| Voyage | 65% |
| Culture | 58% |
Et qu'il est abonné au créateur "creator789" qui publie "Voyage" et "Culture"
Quand je DELETE /api/v1/subscriptions/creator789
Alors le statut de réponse est 204
Et en base de données:
| catégorie | niveau |
| Voyage | 60% |
| Culture | 53% |
Scénario: API applique pénalité skip rapide (<10s)
Étant donné que l'utilisateur "user123" a une jauge "Politique" à 50% en base
Et qu'un contenu "content555" de 300 secondes est tagué "Politique"
Quand je POST /api/v1/skip-events
"""json
{
"user_id": "user123",
"content_id": "content555",
"listened_duration_seconds": 5,
"total_duration_seconds": 300
}
"""
Alors le statut de réponse est 201
Et la réponse contient:
"""json
{
"skip_type": "early",
"gauge_updates": [
{
"category": "Politique",
"previous_value": 50,
"delta": -0.5,
"new_value": 49.5
}
]
}
"""
Et en base de données, la jauge "Politique" de "user123" est à 49.5%
Scénario: API n'applique pas de pénalité pour skip ≥10s et <30%
Étant donné que l'utilisateur "user123" a une jauge "Économie" à 60% en base
Et qu'un contenu "content333" de 600 secondes est tagué "Économie"
Quand je POST /api/v1/skip-events
"""json
{
"user_id": "user123",
"content_id": "content333",
"listened_duration_seconds": 120,
"total_duration_seconds": 600,
"completion_percentage": 20
}
"""
Alors le statut de réponse est 201
Et la réponse contient:
"""json
{
"skip_type": "neutral",
"gauge_updates": []
}
"""
Et en base de données, la jauge "Économie" de "user123" reste à 60%
Scénario: API n'applique pas de pénalité pour skip ≥30%
Étant donné que l'utilisateur "user123" a une jauge "Sport" à 55% en base
Et qu'un contenu "content222" de 600 secondes est tagué "Sport"
Quand je POST /api/v1/skip-events
"""json
{
"user_id": "user123",
"content_id": "content222",
"listened_duration_seconds": 300,
"total_duration_seconds": 600,
"completion_percentage": 50
}
"""
Alors le statut de réponse est 201
Et la réponse contient:
"""json
{
"skip_type": "late",
"gauge_updates": []
}
"""
Et en base de données, la jauge "Sport" de "user123" reste à 55%
Scénario: API applique évolution sur plusieurs tags simultanément
Étant donné que l'utilisateur "user123" a les jauges suivantes:
| catégorie | niveau |
| Automobile | 45% |
| Voyage | 60% |
Et qu'un contenu "content111" est tagué "Automobile" et "Voyage"
Quand je POST /api/v1/listening-events
"""json
{
"user_id": "user123",
"content_id": "content111",
"completion_percentage": 90
}
"""
Alors le statut de réponse est 201
Et la réponse contient:
"""json
{
"like_type": "automatic_reinforced",
"gauge_updates": [
{
"category": "Automobile",
"previous_value": 45,
"delta": 2,
"new_value": 47
},
{
"category": "Voyage",
"previous_value": 60,
"delta": 2,
"new_value": 62
}
]
}
"""
Et en base de données:
| catégorie | niveau |
| Automobile | 47% |
| Voyage | 62% |
Scénario: API respecte la borne maximum 100%
Étant donné que l'utilisateur "user123" a une jauge "Cryptomonnaie" à 99% en base
Et qu'un contenu "content_crypto" est tagué "Cryptomonnaie"
Quand je POST /api/v1/listening-events
"""json
{
"user_id": "user123",
"content_id": "content_crypto",
"completion_percentage": 95
}
"""
Alors le statut de réponse est 201
Et la réponse contient:
"""json
{
"like_type": "automatic_reinforced",
"gauge_updates": [
{
"category": "Cryptomonnaie",
"previous_value": 99,
"delta": 2,
"new_value": 100,
"capped": true
}
]
}
"""
Et en base de données, la jauge "Cryptomonnaie" de "user123" est à 100%
Et la jauge n'a pas dépassé 100%
Scénario: API respecte la borne minimum 0%
Étant donné que l'utilisateur "user123" a une jauge "Politique" à 0.3% en base
Et qu'un contenu "content_pol" est tagué "Politique"
Quand je POST /api/v1/skip-events
"""json
{
"user_id": "user123",
"content_id": "content_pol",
"listened_duration_seconds": 3,
"total_duration_seconds": 300
}
"""
Alors le statut de réponse est 201
Et la réponse contient:
"""json
{
"skip_type": "early",
"gauge_updates": [
{
"category": "Politique",
"previous_value": 0.3,
"delta": -0.5,
"new_value": 0,
"capped": true
}
]
}
"""
Et en base de données, la jauge "Politique" de "user123" est à 0%
Et la jauge n'est pas devenue négative
Scénario: API respecte borne minimum lors désabonnement
Étant donné que l'utilisateur "user123" a une jauge "Économie" à 3% en base
Et qu'il est abonné au créateur "creator_eco" qui publie "Économie"
Quand je DELETE /api/v1/subscriptions/creator_eco
Alors le statut de réponse est 204
Et en base de données, la jauge "Économie" de "user123" est à 0% (et non -2%)
Scénario: API GET retourne toutes les jauges utilisateur
Étant donné que l'utilisateur "user123" a les jauges suivantes en base:
| catégorie | niveau |
| Automobile | 67% |
| Voyage | 82% |
| Économie | 34% |
| Sport | 50% |
| Musique | 45% |
| Technologie | 71% |
Quand je GET /api/v1/users/user123/interest-gauges
Alors le statut de réponse est 200
Et la réponse contient les 12 catégories avec leurs niveaux:
"""json
{
"user_id": "user123",
"gauges": [
{"category": "Automobile", "level": 67},
{"category": "Voyage", "level": 82},
{"category": "Économie", "level": 34},
{"category": "Sport", "level": 50},
{"category": "Musique", "level": 45},
{"category": "Technologie", "level": 71}
]
}
"""
Scénario: API calcule évolution immédiate (pas de batch différé)
Étant donné que l'utilisateur "user123" a une jauge "Voyage" à 50% en base
Quand je POST /api/v1/listening-events à 12:00:00
"""json
{
"user_id": "user123",
"content_id": "content_travel",
"completion_percentage": 85
}
"""
Alors le statut de réponse est 201
Quand je GET /api/v1/users/user123/interest-gauges à 12:00:01 (1 seconde après)
Alors la jauge "Voyage" est à 52%
Et la mise à jour est visible immédiatement
Scénario: API rejette token JWT invalide
Quand je POST /api/v1/listening-events sans token JWT
Alors le statut de réponse est 401
Et la réponse contient:
"""json
{
"error": "UNAUTHORIZED",
"message": "Token JWT manquant ou invalide"
}
"""
Scénario: API valide format des données d'entrée
Quand je POST /api/v1/listening-events
"""json
{
"user_id": "user123",
"content_id": "content456",
"completion_percentage": 150
}
"""
Alors le statut de réponse est 400
Et la réponse contient:
"""json
{
"error": "VALIDATION_ERROR",
"message": "completion_percentage doit être entre 0 et 100"
}
"""
Scénario: API gère contenu avec tags inexistants en base
Étant donné qu'un contenu "content_new" est tagué "NouvelleCategorie" (non encore en base)
Quand je POST /api/v1/listening-events
"""json
{
"user_id": "user123",
"content_id": "content_new",
"completion_percentage": 90
}
"""
Alors le statut de réponse est 201
Et une nouvelle ligne est créée dans la table interest_gauges:
| user_id | category | level |
| user123 | NouvelleCategorie | 52 |
Et l'initialisation démarre à 50% + 2% de like auto = 52%
Scénario: API persiste historique des modifications de jauges
Étant donné que l'utilisateur "user123" a une jauge "Sport" à 50%
Quand je POST /api/v1/listening-events qui applique +2%
Alors une ligne est insérée dans interest_gauge_history:
| user_id | category | previous_value | delta | new_value | event_type | event_id | timestamp |
| user123 | Sport | 50 | 2 | 52 | listening_event| <event_uuid> | 2026-02-02T12:00:00 |
Et cet historique permet d'auditer les évolutions
Scénario: API retourne métriques d'évolution utilisateur
Étant donné que l'utilisateur "user123" a un historique d'évolution en base
Quand je GET /api/v1/users/user123/interest-gauges/evolution?since=7d
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"period": "7d",
"evolution": [
{
"category": "Automobile",
"start_value": 60,
"end_value": 67,
"delta": 7,
"events_count": 15
},
{
"category": "Voyage",
"start_value": 80,
"end_value": 82,
"delta": 2,
"events_count": 3
}
]
}
"""
# Architecture backend - Services séparés
Scénario: Gauge Calculation Service calcule l'ajustement (stateless)
Étant donné un événement d'écoute avec 85% de complétion
Quand le Gauge Calculation Service calcule l'ajustement
Alors le service retourne:
"""json
{
"adjustment_type": "automatic_reinforced",
"adjustment_value": 2.0,
"reason": "completion_percentage >= 80%"
}
"""
Et le service est stateless (aucune lecture DB)
Et le service est testable unitairement
Scénario: Gauge Update Service applique l'ajustement (stateful)
Étant donné qu'un ajustement de +2% a été calculé
Et que la jauge "Automobile" de "user123" est à 45%
Quand le Gauge Update Service applique l'ajustement
Alors la nouvelle valeur est 47% (45 + 2)
Et la borne [0, 100] est respectée via fonction clamp
Et la mise à jour est persistée en Redis (immédiat)
Et la mise à jour est persistée en PostgreSQL (batch async)
Scénario: Pattern de calcul - Addition de points absolus
Étant donné qu'une jauge est à 60%
Et qu'un ajustement de +2% est calculé
Quand le calcul est effectué
Alors la formule est: newValue = currentValue + adjustment
Et la formule est: newValue = clamp(newValue, 0.0, 100.0)
Et la nouvelle valeur est 62%
Scénario: Pattern de calcul - Éviter multiplication relative
Étant donné qu'une jauge est à 50%
Et qu'un ajustement de +2% est calculé
Quand le calcul est effectué
Alors la formule utilisée est: 50 + 2 = 52
Et la formule utilisée n'est PAS: 50 * (1 + 2/100) = 51
Car l'ajustement est en points absolus, pas relatifs
Scénario: Multi-tags - Mise à jour de N jauges simultanément
Étant donné qu'un contenu a 3 tags: ["Automobile", "Voyage", "Technologie"]
Et qu'un ajustement de +2% est calculé (écoute 85%)
Quand le Gauge Update Service applique
Alors 3 jauges sont mises à jour:
| catégorie | ajustement |
| Automobile | +2% |
| Voyage | +2% |
| Technologie | +2% |
Et toutes les mises à jour sont effectuées en une seule transaction
Scénario: Persistance Redis immédiate (latence <10ms)
Étant donné qu'un ajustement doit être persisté
Quand le Gauge Update Service écrit en Redis
Alors la latence est < 10ms
Et la jauge est immédiatement disponible pour recommandations
Et Redis sert de cache haute performance
Scénario: Persistance PostgreSQL asynchrone (batch 5 min)
Étant donné que 100 ajustements ont été appliqués en Redis
Quand le batch async s'exécute toutes les 5 minutes
Alors les 100 ajustements sont écrits en PostgreSQL en batch
Et la cohérence finale est garantie
Et l'application reste performante (pas de write sync)
Scénario: Séparation responsabilités - Calculation vs Update
Étant donné un événement d'écoute
Quand il est traité
Alors le Gauge Calculation Service calcule l'ajustement (logique métier pure)
Et le Gauge Update Service applique l'ajustement (persistance)
Et les deux services sont indépendants
Et chaque service a une responsabilité unique (SRP)
Scénario: Réutilisabilité Calculation Service
Étant donné le Gauge Calculation Service
Quand il est utilisé pour:
| contexte |
| Like automatique |
| Skip rapide |
| Like manuel |
| Abonnement créateur |
Alors le même service calcule tous les ajustements
Et la logique métier est centralisée
Et il n'y a pas de duplication de code

View File

@@ -0,0 +1,337 @@
# language: fr
Fonctionnalité: API - Jauge initiale et cold start
En tant qu'API backend
Je veux initialiser toutes les jauges à 50% lors de l'inscription
Afin de garantir un démarrage neutre et équitable
Contexte:
Étant donné que l'API RoadWave est disponible
Et que la base de données PostgreSQL est accessible
Scénario: API initialise toutes les jauges à 50% lors inscription
Quand je POST /api/v1/auth/register
"""json
{
"email": "nouveau@example.com",
"password": "SecureP@ss123",
"birth_date": "1990-01-15",
"username": "nouveau_user"
}
"""
Alors le statut de réponse est 201
Et la réponse contient:
"""json
{
"user_id": "<uuid>",
"email": "nouveau@example.com",
"username": "nouveau_user"
}
"""
Et en base de données, la table interest_gauges contient 12 lignes pour ce user_id:
| category | level |
| Automobile | 50 |
| Voyage | 50 |
| Famille | 50 |
| Amour | 50 |
| Musique | 50 |
| Économie | 50 |
| Cryptomonnaie | 50 |
| Politique | 50 |
| Culture générale | 50 |
| Sport | 50 |
| Technologie | 50 |
| Santé | 50 |
Scénario: API retourne les 12 catégories disponibles
Quand je GET /api/v1/interest-gauges/categories
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"categories": [
{"id": "automobile", "name": "Automobile", "icon": "car"},
{"id": "voyage", "name": "Voyage", "icon": "plane"},
{"id": "famille", "name": "Famille", "icon": "users"},
{"id": "amour", "name": "Amour", "icon": "heart"},
{"id": "musique", "name": "Musique", "icon": "music"},
{"id": "economie", "name": "Économie", "icon": "chart"},
{"id": "cryptomonnaie", "name": "Cryptomonnaie", "icon": "bitcoin"},
{"id": "politique", "name": "Politique", "icon": "landmark"},
{"id": "culture-generale", "name": "Culture générale", "icon": "book"},
{"id": "sport", "name": "Sport", "icon": "running"},
{"id": "technologie", "name": "Technologie", "icon": "cpu"},
{"id": "sante", "name": "Santé", "icon": "heart-pulse"}
]
}
"""
Scénario: API GET retourne jauges utilisateur nouvellement inscrit
Étant donné qu'un utilisateur "user_new" vient de s'inscrire
Quand je GET /api/v1/users/user_new/interest-gauges
Alors le statut de réponse est 200
Et la réponse contient 12 jauges toutes à 50%:
"""json
{
"user_id": "user_new",
"gauges": [
{"category": "Automobile", "level": 50, "evolution_since_signup": 0},
{"category": "Voyage", "level": 50, "evolution_since_signup": 0},
{"category": "Famille", "level": 50, "evolution_since_signup": 0},
{"category": "Amour", "level": 50, "evolution_since_signup": 0},
{"category": "Musique", "level": 50, "evolution_since_signup": 0},
{"category": "Économie", "level": 50, "evolution_since_signup": 0},
{"category": "Cryptomonnaie", "level": 50, "evolution_since_signup": 0},
{"category": "Politique", "level": 50, "evolution_since_signup": 0},
{"category": "Culture générale", "level": 50, "evolution_since_signup": 0},
{"category": "Sport", "level": 50, "evolution_since_signup": 0},
{"category": "Technologie", "level": 50, "evolution_since_signup": 0},
{"category": "Santé", "level": 50, "evolution_since_signup": 0}
]
}
"""
Scénario: API calcule recommandations avec jauges à 50% - pas de biais
Étant donné qu'un utilisateur "user_new" vient de s'inscrire
Et que toutes ses jauges sont à 50%
Et qu'il est à la position GPS (48.8566, 2.3522) - Paris
Quand je POST /api/v1/recommendations
"""json
{
"user_id": "user_new",
"latitude": 48.8566,
"longitude": 2.3522,
"limit": 10
}
"""
Alors le statut de réponse est 200
Et la réponse contient 10 contenus
Et le scoring est basé uniquement sur:
| critère | poids |
| Distance géographique| 100% |
| Intérêts (50% égal) | 0% |
Et aucune catégorie n'a d'avantage initial
Scénario: API permet ajout de nouvelles catégories
Étant donné qu'un admin ajoute une nouvelle catégorie "Gastronomie"
Quand je POST /api/v1/admin/interest-gauges/categories
"""json
{
"id": "gastronomie",
"name": "Gastronomie",
"icon": "utensils"
}
"""
Alors le statut de réponse est 201
Et pour tous les utilisateurs existants:
| action |
| Une ligne est créée dans interest_gauges |
| category = "Gastronomie" |
| level = 50 |
Et les nouveaux utilisateurs auront aussi cette catégorie à 50%
Scénario: API calcule évolution depuis inscription
Étant donné qu'un utilisateur "user123" s'est inscrit il y a 30 jours
Et qu'il a les jauges suivantes en base:
| catégorie | niveau | initial |
| Automobile | 67% | 50% |
| Voyage | 82% | 50% |
| Économie | 34% | 50% |
| Sport | 50% | 50% |
Quand je GET /api/v1/users/user123/interest-gauges
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"user_id": "user123",
"signup_date": "2026-01-03T10:00:00Z",
"gauges": [
{
"category": "Automobile",
"level": 67,
"evolution_since_signup": 17
},
{
"category": "Voyage",
"level": 82,
"evolution_since_signup": 32
},
{
"category": "Économie",
"level": 34,
"evolution_since_signup": -16
},
{
"category": "Sport",
"level": 50,
"evolution_since_signup": 0
}
]
}
"""
Scénario: API transaction atomique lors inscription
Quand je POST /api/v1/auth/register
"""json
{
"email": "test@example.com",
"password": "SecureP@ss123",
"birth_date": "1995-03-20",
"username": "test_user"
}
"""
Alors l'insertion en base de données est atomique:
| action |
| INSERT INTO users |
| INSERT INTO interest_gauges (12 lignes) |
| Tout ou rien (transaction) |
Et si une erreur survient, aucune donnée partielle n'est créée
Scénario: API rollback si initialisation jauges échoue
Étant donné que la table interest_gauges a une contrainte violée
Quand je POST /api/v1/auth/register avec données valides
Alors le statut de réponse est 500
Et aucune ligne n'est créée dans la table users
Et aucune ligne n'est créée dans la table interest_gauges
Et la transaction est rollback complètement
Scénario: API POST questionnaire optionnel post-MVP
Étant donné qu'un utilisateur "user123" a écouté 3 contenus
Et qu'il décide de remplir le questionnaire optionnel
Quand je POST /api/v1/users/user123/interest-gauges/quick-setup
"""json
{
"selected_categories": ["Automobile", "Voyage", "Sport"]
}
"""
Alors le statut de réponse est 200
Et en base de données:
| catégorie | niveau |
| Automobile | 70 |
| Voyage | 70 |
| Sport | 70 |
| Musique | 30 |
| Économie | 30 |
| Cryptomonnaie | 30 |
| Politique | 30 |
| Culture générale | 30 |
| Technologie | 30 |
| Santé | 30 |
| Famille | 30 |
| Amour | 30 |
Et un flag quick_setup_completed = true est enregistré
Scénario: API rejette questionnaire optionnel si déjà rempli
Étant donné qu'un utilisateur "user123" a déjà rempli le questionnaire optionnel
Quand je POST /api/v1/users/user123/interest-gauges/quick-setup
"""json
{
"selected_categories": ["Musique", "Technologie"]
}
"""
Alors le statut de réponse est 409
Et la réponse contient:
"""json
{
"error": "QUICK_SETUP_ALREADY_COMPLETED",
"message": "Le questionnaire a déjà été rempli"
}
"""
Scénario: API valide nombre de catégories sélectionnées
Quand je POST /api/v1/users/user123/interest-gauges/quick-setup
"""json
{
"selected_categories": ["Automobile"]
}
"""
Alors le statut de réponse est 400
Et la réponse contient:
"""json
{
"error": "VALIDATION_ERROR",
"message": "Vous devez sélectionner entre 2 et 5 catégories"
}
"""
Scénario: API déterministe - deux users identiques
Étant donné que l'utilisateur "userA" s'inscrit à 10:00:00
Et que l'utilisateur "userB" s'inscrit à 10:00:01
Quand je GET /api/v1/users/userA/interest-gauges
Et je GET /api/v1/users/userB/interest-gauges
Alors les deux réponses ont des jauges identiques (toutes à 50%)
Et le comportement est déterministe
Scénario: API retourne statistiques cold start
Étant donné qu'un utilisateur "user_new" vient de s'inscrire
Quand je GET /api/v1/users/user_new/stats
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"user_id": "user_new",
"signup_date": "2026-02-02T14:00:00Z",
"total_listened_content": 0,
"gauges_summary": {
"all_at_default": true,
"default_value": 50,
"total_categories": 12,
"personalization_level": "none"
}
}
"""
Scénario: API recommandations cold start priorité géo
Étant donné qu'un utilisateur "user_new" vient de s'inscrire
Et qu'il est à Paris avec 100 contenus disponibles dans un rayon de 5km
Et que ces contenus ont des catégories variées
Quand je POST /api/v1/recommendations
"""json
{
"user_id": "user_new",
"latitude": 48.8566,
"longitude": 2.3522,
"limit": 10
}
"""
Alors le statut de réponse est 200
Et les 10 contenus retournés sont les plus proches géographiquement
Et les catégories sont variées (pas de biais intérêts)
Et la réponse contient:
"""json
{
"recommendations": [
{
"content_id": "<uuid>",
"distance_meters": 150,
"interest_match": 50,
"final_score": 0.95
}
],
"cold_start": true,
"personalization_level": "none"
}
"""
Scénario: API index optimisé pour lecture jauges
Étant donné que la table interest_gauges a 1 million de lignes
Quand je GET /api/v1/users/user123/interest-gauges
Alors la requête SQL utilise l'index (user_id, category)
Et le temps de réponse est < 50ms
Et le plan d'exécution confirme l'utilisation de l'index
Scénario: API cache jauges utilisateur en Redis
Étant donné qu'un utilisateur "user123" a ses jauges en base
Quand je GET /api/v1/users/user123/interest-gauges
Alors le backend vérifie d'abord Redis avec clé "user:user123:gauges"
Et si absent, lit depuis PostgreSQL
Et met en cache dans Redis avec TTL = 300 secondes
Quand je GET à nouveau dans les 5 minutes
Alors la réponse vient directement de Redis
Et aucune requête PostgreSQL n'est faite
Scénario: API invalide cache Redis lors mise à jour jauge
Étant donné que les jauges de "user123" sont en cache Redis
Quand je POST /api/v1/listening-events qui modifie une jauge
Alors le cache Redis "user:user123:gauges" est supprimé
Et le prochain GET recharge depuis PostgreSQL
Et remet en cache avec nouvelles valeurs

View File

@@ -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)