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:
420
docs/domains/recommendation/rules/algorithme-recommandation.md
Normal file
420
docs/domains/recommendation/rules/algorithme-recommandation.md
Normal file
@@ -0,0 +1,420 @@
|
||||
## 2. Algorithme de recommandation
|
||||
|
||||
### 2.1 Classification de géo-pertinence
|
||||
|
||||
**Décision** : 3 types de contenus selon leur pertinence géographique
|
||||
|
||||
| Type | Description | Exemple | Pondération géo |
|
||||
|------|-------------|---------|-----------------|
|
||||
| **Géo-ancré** | Contenu lié à un lieu précis | Audio-guide monument, pub restaurant local | 70% |
|
||||
| **Géo-contextuel** | Pertinent dans une zone | Actualité régionale, événement local | 50% |
|
||||
| **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
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Formule de scoring
|
||||
|
||||
**Décision** : Score combiné dynamique selon type de contenu
|
||||
|
||||
```
|
||||
score_final = (score_geo * poids_geo_type)
|
||||
+ (score_interets * poids_interets_type)
|
||||
+ (score_engagement * 0.2)
|
||||
+ (bonus_aleatoire)
|
||||
|
||||
où :
|
||||
- score_geo = 1 - (distance_km / distance_max_km)
|
||||
- score_interets = moyenne des jauges utilisateur pour les tags du contenu
|
||||
- score_engagement = (taux_completion * 0.5) + (ratio_likes * 0.3) + (ratio_abonnements * 0.2)
|
||||
- bonus_aleatoire = 10% des recommandations tirées aléatoirement
|
||||
```
|
||||
|
||||
#### 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
|
||||
|
||||
**Formule exacte** :
|
||||
```
|
||||
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]
|
||||
```
|
||||
|
||||
**Exemple concret** :
|
||||
```
|
||||
Contenu : "Visite du Louvre"
|
||||
Tags : ["Musique", "Tourisme"]
|
||||
|
||||
Utilisateur :
|
||||
- Jauge "Musique" = 75%
|
||||
- Jauge "Tourisme" = 60%
|
||||
- Jauge "Automobile" = 40% (non pertinente, ignorée)
|
||||
|
||||
Calcul :
|
||||
score_interets = ((75 + 60) / 2) / 100
|
||||
= (135 / 2) / 100
|
||||
= 67.5 / 100
|
||||
= 0.675
|
||||
|
||||
Impact dans le scoring final (type géo-contextuel) :
|
||||
score_final = (score_geo * 0.5) + (score_interets * 0.3) + (score_engagement * 0.2) + bonus_aleatoire
|
||||
= (0.8 * 0.5) + (0.675 * 0.3) + (0.5 * 0.2) + 0
|
||||
= 0.4 + 0.2025 + 0.1
|
||||
= 0.7025 / 1.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)
|
||||
- **Score géo excellent MAIS intérêts nuls** : Le contenu peut quand même être recommandé grâce à la pondération géographique. Exemple : contenu géo-ancré à 100m avec score_geo=1.0 et score_interets=0.0 obtient score_final = (1.0 × 0.7) + (0.0 × 0.1) + engagement = 0.7 + engagement. Ce comportement est accepté pour MVP car (1) le quota 6 contenus géolocalisés/h protège du spam, (2) l'info peut être utile contextuellement même sans intérêt marqué, (3) la distinction info/divertissement est reportée post-MVP.
|
||||
|
||||
**Pondérations par type** :
|
||||
|
||||
| Type | Poids géo | Poids intérêts |
|
||||
|------|-----------|----------------|
|
||||
| Géo-ancré | 0.7 | 0.1 |
|
||||
| Géo-contextuel | 0.5 | 0.3 |
|
||||
| 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)
|
||||
|
||||
**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)
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Score d'engagement et popularité
|
||||
|
||||
**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)
|
||||
|
||||
**Distinction sources et abonnements** (neutralisation pénalités) :
|
||||
|
||||
Les métriques d'engagement **ne comptent que les écoutes pertinentes** pour éviter de pénaliser injustement les créateurs :
|
||||
|
||||
| Source écoute | Abonné au créateur ? | Skip <10s pénalise ? | Compte dans "total écoutes" ? | Justification |
|
||||
|---------------|---------------------|---------------------|------------------------------|---------------|
|
||||
| **`recommendation`** | ❌ Non | ✅ Oui | ✅ Oui | Skip = mauvaise recommandation OU mauvais contenu |
|
||||
| **`recommendation`** | ✅ Oui | ❌ **Non** | ❌ **Non** | Abonné intéressé globalement, skip contextuel |
|
||||
| **`search`** | Peu importe | ❌ Non | ❌ Non | User cherchait quelque chose de précis, skip = "pas maintenant" |
|
||||
| **`direct_link`** | Peu importe | ❌ Non | ❌ Non | User curieux, peut skip sans jugement qualité |
|
||||
| **`profile`** | Peu importe | ❌ Non | ❌ Non | User explore catalogue créateur |
|
||||
| **`history`** | Peu importe | ❌ Non | ❌ Non | Pas une première écoute |
|
||||
| **`live_notification`** | ❌ Non | ✅ Oui | ✅ Oui | Abonné normalement intéressé |
|
||||
| **`live_notification`** | ✅ Oui | ❌ **Non** | ❌ **Non** | Abonné = affinité, skip contextuel |
|
||||
| **`audio_guide`** | Peu importe | ❌ Non | ❌ Non | Navigation guidée, pas jugement qualité |
|
||||
|
||||
**Calcul engagement créateur** (exemple SQL) :
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
content_id,
|
||||
AVG(completion_rate) as avg_completion,
|
||||
COUNT(*) FILTER (WHERE completion_rate > 0.8) as complete_listens,
|
||||
COUNT(*) FILTER (WHERE completion_rate < 0.1 AND NOT is_subscribed) as penalizing_skips
|
||||
FROM user_listening_history
|
||||
WHERE source IN ('recommendation', 'live_notification') -- Sources pertinentes
|
||||
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)
|
||||
- **Cohérence UX** : abonnement = signal d'affinité fort, skip ponctuel ≠ rejet créateur
|
||||
- Pas de pénalisation arbitraire des contenus anciens
|
||||
- Coût : calculs sur métriques existantes + colonne `source` + colonne `is_subscribed`
|
||||
|
||||
---
|
||||
|
||||
### 2.4 Part d'aléatoire (exploration)
|
||||
|
||||
**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
|
||||
|
||||
---
|
||||
|
||||
### 2.5 Contenu politique (version MVP simplifiée)
|
||||
|
||||
> ⚠️ **Note** : La classification politique avancée (échelle gauche/droite, équilibrage imposé) a été reportée post-MVP. Voir [ANNEXE-POST-MVP.md](ANNEXE-POST-MVP.md) pour la version complète.
|
||||
|
||||
**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
|
||||
|
||||
---
|
||||
|
||||
### 2.6 Mode Kids (13-15 ans)
|
||||
|
||||
**Décision** : Mode optionnel pour adolescents 13-15 ans uniquement
|
||||
|
||||
> ⚠️ **Note** : Âge minimum d'inscription = **13 ans** (obligation légale EU). Pas d'utilisateurs <13 ans sur la plateforme.
|
||||
|
||||
**Tranche concernée** :
|
||||
|
||||
| Tranche | Description | Contenus autorisés | Restrictions |
|
||||
|---------|-------------|-------------------|--------------|
|
||||
| **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
|
||||
- **Flexibilité** : Parents décident d'activer ou non
|
||||
|
||||
---
|
||||
|
||||
### 2.7 Déclenchement géographique
|
||||
|
||||
**Décision** : Notification au passage, pas d'anticipation
|
||||
|
||||
**Fonctionnement** :
|
||||
1. Utilisateur passe à <500m d'un point GPS (contenu géo-ancré)
|
||||
2. **Notification sonore** (bip court) + **visuelle** (logo selon type)
|
||||
3. Types de logos : 📍 Info, 🏛️ Culturel, 🍴 Commercial, 🎭 Événement
|
||||
4. Délai réaction utilisateur : **5 secondes** pour accepter (bouton volant ou commande vocale)
|
||||
5. Si accepté → lecture immédiate
|
||||
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)
|
||||
|
||||
---
|
||||
|
||||
### 2.8 Historique et repropositon
|
||||
|
||||
**Décision** : Pas de reproposition sauf contenu partiel ou skip d'abonné
|
||||
|
||||
**Règles** :
|
||||
|
||||
| État écoute | Completion | Abonné au créateur ? | Action |
|
||||
|-------------|------------|---------------------|--------|
|
||||
| **Écouté complètement** | >80% | Peu importe | ❌ Ne jamais reproposer (sauf flag `replayable = true` pour audio-guides) |
|
||||
| **Skippé rapidement** | <10s | ❌ Non | ❌ Ne pas reproposer (signal négatif clair) |
|
||||
| **Skippé rapidement** | <10s | ✅ **Oui** | ✅ **Peut reproposer** (abonnement = affinité, skip contextuel) |
|
||||
| **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)
|
||||
- Coût stockage négligeable (PostgreSQL scalable)
|
||||
|
||||
---
|
||||
|
||||
### 2.9 Paramétrabilité admin (interface dashboard)
|
||||
|
||||
**Décision** : Tous paramètres scoring exposés + A/B testing
|
||||
|
||||
**Paramètres configurables à chaud** :
|
||||
|
||||
| Paramètre | Plage | Défaut | Unité |
|
||||
|-----------|-------|--------|-------|
|
||||
| `poids_geo_ancre` | 0.5 - 1.0 | 0.7 | % |
|
||||
| `poids_geo_contextuel` | 0.3 - 0.7 | 0.5 | % |
|
||||
| `poids_geo_neutre` | 0.0 - 0.4 | 0.2 | % |
|
||||
| `poids_engagement` | 0.0 - 0.5 | 0.2 | % |
|
||||
| `part_aleatoire_global` | 0.0 - 0.3 | 0.1 | % |
|
||||
| `distance_max_km` | 50 - 500 | 200 | km |
|
||||
| `rayon_gps_point_m` | 100 - 2000 | 500 | m |
|
||||
| `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)
|
||||
|
||||
---
|
||||
|
||||
### 2.10 Paramétrabilité utilisateur
|
||||
|
||||
**Décision** : Curseurs avancés avec profils sauvegardables
|
||||
|
||||
**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)
|
||||
|
||||
---
|
||||
|
||||
### 2.11 Médias traditionnels
|
||||
|
||||
**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)
|
||||
|
||||
---
|
||||
|
||||
## Récapitulatif Section 2
|
||||
Reference in New Issue
Block a user