# Système de Scoring Intelligent SourceFinder ## Vue d'Ensemble Le système de scoring de SourceFinder évalue intelligemment la pertinence des articles d'actualités canines selon quatre critères pondérés, conformément aux spécifications du CDC (Cahier des Charges). Chaque article reçoit un score final de 0 à 100 points selon la formule : ``` Score Final = (Spécificité × 0.4) + (Fraîcheur × 0.3) + (Qualité × 0.2) + (Réutilisabilité × 0.1) ``` Cette approche multi-critères garantit une sélection équilibrée entre pertinence thématique, actualité, fiabilité des sources et optimisation de la réutilisation du contenu. ## Architecture Modulaire ### Organisation des Composants ``` BasicScoringEngine (Orchestrateur principal) ├── SpecificityCalculator (40% du score) ├── FreshnessCalculator (30% du score) ├── QualityCalculator (20% du score) └── ReuseCalculator (10% du score) ``` Chaque calculateur est indépendant et interchangeable, respectant le principe d'architecture modulaire du système. ### Interface IScoringEngine Tous les moteurs de scoring implémentent cette interface standardisée : ```javascript interface IScoringEngine { async scoreArticle(newsItem, context): Promise async batchScore(newsItems, context): Promise> explainScore(scoredArticle): Object } ``` ## 1. Calculateur de Spécificité (40% du Score) ### Principe La spécificité évalue la pertinence du contenu par rapport à la race de chien recherchée. C'est le critère le plus important car il détermine directement l'utilité de l'article pour le client final. ### Hiérarchie de Scoring | Niveau | Score | Critère | Exemple | |--------|--------|---------|---------| | **Mention Exacte** | 100 pts | Nom exact de la race trouvé | "Berger Allemand", "Golden Retriever" | | **Groupe/Famille** | 70 pts | Famille de race mentionnée | "Chiens de berger", "Retrievers" | | **Taille Similaire** | 50 pts | Catégorie de taille | "Grands chiens", "Petite race" | | **Usage Similaire** | 40 pts | Usage/fonction similaire | "Chien de garde", "Chien de famille" | | **Générique Chiens** | 25 pts | Mention générale canine | "Chiens", "Compagnons" | | **Animaux Domestiques** | 10 pts | Contexte animal général | "Animaux de compagnie" | ### Base de Données des Races Le système intègre une base de données des races FCI avec : ```javascript // Exemple : Berger Allemand (352-1) { name: 'berger allemand', variants: ['german shepherd', 'berger d\'allemagne'], group: 'chiens de berger', families: ['bergers', 'chiens de troupeau'], size: 'grands chiens', usages: ['chien de garde', 'chien de travail', 'chien policier'] } ``` ### Algorithme de Détection 1. **Normalisation du contenu** : Conversion en minuscules, suppression de la ponctuation 2. **Recherche par regex** : Détection des mots-clés avec délimiteurs (`\b`) 3. **Scoring hiérarchique** : Attribution du score le plus élevé trouvé 4. **Traçabilité** : Enregistrement des termes correspondants pour audit ### Cas Spéciaux - **Races composées** : "Berger Allemand à poil long" → Détection du nom principal - **Synonymes multiples** : "Labrador" → "Labrador Retriever" - **Variantes linguistiques** : "German Shepherd" → "Berger Allemand" ## 2. Calculateur de Fraîcheur (30% du Score) ### Principe La fraîcheur évalue la récence de l'article. Plus un article est récent, plus il est susceptible d'être pertinent pour la génération de contenu actualisé. ### Seuils d'Évaluation | Catégorie | Âge | Score | Usage Recommandé | |-----------|-----|--------|------------------| | **Excellent** | < 7 jours | 100 pts | Actualités urgentes | | **Bon** | 7-30 jours | 70 pts | Contenu récent | | **Correct** | 30-90 jours | 40 pts | Informations générales | | **Ancien** | 90-180 jours | 20 pts | Contenu de référence | | **Obsolète** | > 180 jours | 5 pts | Archives uniquement | ### Gestion des Dates #### Formats Supportés - **ISO 8601** : `2024-01-15T10:30:00Z` - **Français** : `15/01/2024`, `15-01-2024`, `15.01.2024` - **Timestamps** : Unix timestamp (secondes ou millisecondes) - **Objets Date** : Instances JavaScript Date #### Validation et Sécurité ```javascript // Plage de dates valides : 1990 à (année actuelle + 5) isValidDate(date) { const year = date.getFullYear(); const currentYear = new Date().getFullYear(); return year >= 1990 && year <= currentYear + 5; } ``` ### Ajustements Contextuels #### Bonus Contenu "Evergreen" (+20 pts max) Articles à valeur permanente identifiés par mots-clés : - Guides : "guide", "comment", "conseils" - Éducation : "dressage", "formation", "méthode" - Santé générale : "prévention", "bien-être" #### Malus Actualités Périmées (-30% du score) Articles d'actualité urgente devenus obsolètes : - Mots-clés : "actualité", "urgent", "breaking", "annonce" - Appliqué si score de base < 40 points #### Bonus Recherche d'Archives (+15 pts max) Si `context.allowOldContent = true`, améliore la valorisation du contenu ancien. ### Calcul de l'Âge ```javascript calculateAgeInDays(publishDate, searchDate) { const diffMs = searchDate.getTime() - publishDate.getTime(); return Math.floor(diffMs / (1000 * 60 * 60 * 24)); } ``` #### Gestion des Cas d'Erreur - **Date future** : Score = 0 (erreur de publication) - **Date manquante** : Score = 0 (non fiable) - **Date invalide** : Score = 0 (format incorrect) ## 3. Calculateur de Qualité (20% du Score) ### Principe La qualité évalue la fiabilité et l'autorité de la source de publication. Ce critère garantit la crédibilité du contenu généré. ### Classification des Sources #### Sources Premium (90-100 pts) **Organismes Officiels et Institutions** - `centrale-canine.fr` (Société Centrale Canine) : 100 pts - `fci.be` (Fédération Cynologique Internationale) : 100 pts - `veterinaire.fr` (Ordre des Vétérinaires) : 95 pts - Sites universitaires vétérinaires : 95 pts **Critères d'identification :** - Extension `.edu` ou `.fr` officielle - Mentions légales complètes - Références scientifiques - Autorité reconnue dans le domaine #### Sources Spécialisées (70-85 pts) **Médias Spécialisés Canins** - `30millionsdamis.fr` : 85 pts - `wamiz.com` : 80 pts - `woopets.fr` : 80 pts - Clubs de race officiels : 85 pts **Caractéristiques :** - Spécialisation exclusive dans le domaine canin - Équipe éditoriale identifiée - Historique de publication - Partenariats avec organismes officiels #### Sources Standard (50-70 pts) **Médias Généralistes de Qualité** - `lefigaro.fr/animaux` : 65 pts - `ouest-france.fr/animaux` : 60 pts - Magazines lifestyle avec section animaux : 55 pts **Évaluation :** - Réputation générale du média - Qualité éditoriale - Processus de vérification - Expertise occasionnelle sur les animaux #### Sources Fallback (20-50 pts) **Contenu Généraliste ou Non-Vérifié** - Blogs personnels : 30 pts - Forums : 25 pts - Réseaux sociaux : 20 pts - Sources inconnues : 25 pts ### Indicateurs de Qualité #### Indicateurs Positifs (+5 à +15 pts) ```javascript qualityIndicators = { hasAuthor: +10, // Auteur identifié hasPublishDate: +10, // Date de publication hasReferences: +15, // Références citées hasVetReview: +15, // Validation vétérinaire hasCitations: +10, // Citations scientifiques isRecent: +5, // Publication récente hasImages: +5, // Illustrations présentes hasStructure: +5 // Contenu bien structuré } ``` #### Indicateurs Négatifs (-5 à -20 pts) ```javascript qualityPenalties = { hasAds: -5, // Publicités excessives poorWriting: -10, // Qualité rédactionnelle noContact: -10, // Pas de contact noLegal: -15, // Pas de mentions légales anonymousContent: -10, // Contenu anonyme clickbait: -15, // Titre aguicheur outdatedInfo: -20 // Informations obsolètes } ``` ### Détection Automatique #### Analyse du Contenu ```javascript // Détection de qualité par analyse textuelle analyzeContentQuality(content) { const wordCount = content.split(/\s+/).length; const sentenceCount = content.split(/[.!?]+/).length; const avgSentenceLength = wordCount / sentenceCount; return { isSubstantial: wordCount > 200, isWellStructured: avgSentenceLength > 8 && avgSentenceLength < 25, hasVariety: this.calculateLexicalDiversity(content) > 0.6 }; } ``` #### Analyse des Métadonnées - Présence d'auteur et date - Structure HTML appropriée - Balises meta descriptions - Schema.org markup ### Pondération Contextuelle Le score de qualité peut être ajusté selon le contexte : ```javascript // Bonus pour recherche spécialisée if (context.requireHighQuality) { // Réduction des scores sources non-premium if (baseScore < 70) baseScore *= 0.8; } // Malus cumul sources faibles if (context.lowQualityCount > 3) { baseScore *= 0.9; } ``` ## 4. Calculateur de Réutilisabilité (10% du Score) ### Principe La réutilisabilité optimise l'usage du stock d'articles en évitant la sur-utilisation et en respectant les périodes de rotation. Ce critère assure la diversité du contenu généré. ### Scoring par Usage | Catégorie | Utilisations | Score | Statut | |-----------|--------------|--------|---------| | **Neuf** | 0 | 100 pts | Priorité maximale | | **Peu utilisé** | 1-2 | 80 pts | Recommandé | | **Modérément utilisé** | 3-5 | 60 pts | Acceptable | | **Très utilisé** | 6-10 | 40 pts | Limité | | **Saturé** | > 10 | 20 pts | À éviter | ### Périodes de Rotation Le système respecte des périodes de rotation selon le type de source : ```javascript rotationPeriods = { premium: 90, // 3 mois - Sources premium (coût élevé, qualité maximale) standard: 60, // 2 mois - Sources standard (équilibre qualité/coût) fallback: 30 // 1 mois - Sources fallback (renouvellement rapide) } ``` ### Ajustements Temporels #### Bonus Période de Rotation Respectée (+10 à +20 pts) ```javascript // Calcul du bonus temporel if (daysSinceLastUse >= rotationPeriod) { const bonus = Math.min(20, daysSinceLastUse - rotationPeriod + 10); return bonus; } ``` #### Malus Utilisation Récente (-10 à -20 pts) Articles utilisés dans les 7 derniers jours subissent une pénalité pour favoriser la diversité. ```javascript // Malus utilisation récente if (daysSinceLastUse < 7) { const penalty = -Math.max(10, 20 - daysSinceLastUse * 2); return penalty; } ``` ### Ajustements Contextuels #### Bonus Client Différent (+10 pts) Si l'article est utilisé par un client différent du précédent : ```javascript if (context.clientId && article.lastClientId && context.clientId !== article.lastClientId) { adjustment += 10; } ``` #### Bonus Contexte Différent (+15 pts max) Évaluation de la similarité avec le contexte précédent : ```javascript calculateContextSimilarity(context1, context2) { const ctx1Words = context1.toLowerCase().split(/\s+/); const ctx2Words = context2.toLowerCase().split(/\s+/); const intersection = ctx1Words.filter(word => ctx2Words.includes(word)); const union = [...new Set([...ctx1Words, ...ctx2Words])]; return intersection.length / union.length; } ``` #### Bonus Contenu Evergreen (+5 pts) Articles à valeur permanente (guides, conseils) bénéficient d'un bonus de réutilisabilité. #### Malus Sur-utilisation Race (-10 pts) Pénalité si l'article a été trop utilisé pour la même race (≥ 5 utilisations). ### Statuts de Rotation ```javascript getRotationStatus(lastUsed, sourceType, now) { const daysSinceLastUse = calculateDaysDifference(lastUsed, now); const rotationPeriod = this.rotationPeriods[sourceType]; if (daysSinceLastUse >= rotationPeriod) return 'available'; if (daysSinceLastUse >= rotationPeriod * 0.7) return 'soon_available'; return 'in_rotation'; } ``` ### Statistiques de Collection Le calculateur fournit des statistiques globales sur l'état de réutilisation du stock : ```javascript getCollectionReuseStats(articles) { return { totalArticles: articles.length, byUsageCategory: { fresh: X, low: Y, ... }, byRotationStatus: { available: A, in_rotation: B, ... }, averageUsage: averageUsageCount, reuseEfficiency: percentageAvailable, recommendations: ['action1', 'action2', ...] }; } ``` ## Orchestration par BasicScoringEngine ### Calcul Principal Le `BasicScoringEngine` coordonne les quatre calculateurs : ```javascript async scoreArticle(newsItem, context) { // Exécution en parallèle pour optimiser les performances const [specificityResult, freshnessResult, qualityResult, reuseResult] = await Promise.all([ this.specificityCalculator.calculateSpecificity(newsItem, context), this.freshnessCalculator.calculateFreshness(newsItem, context), this.qualityCalculator.calculateQuality(newsItem, context), this.reuseCalculator.calculateReuse(newsItem, context) ]); // Application de la formule CDC const finalScore = Math.round( (specificityResult.score * 0.4) + // 40% (freshnessResult.score * 0.3) + // 30% (qualityResult.score * 0.2) + // 20% (reuseResult.score * 0.1) // 10% ); return { finalScore, specificityScore: specificityResult.score, freshnessScore: freshnessResult.score, qualityScore: qualityResult.score, reuseScore: reuseResult.score, scoringDetails: { /* détails complets */ }, scoreCategory: this.categorizeScore(finalScore), usageRecommendation: this.generateUsageRecommendation(...) }; } ``` ### Catégorisation des Scores | Catégorie | Plage | Recommandation | Usage | |-----------|-------|----------------|--------| | **Excellent** | 80-100 | `priority_use` | Utilisation prioritaire | | **Bon** | 65-79 | `recommended` | Recommandé | | **Correct** | 50-64 | `conditional_use` | Usage conditionnel | | **Faible** | 30-49 | `limited_use` | Usage limité | | **Rejeté** | 0-29 | `avoid` | À éviter | ### Scoring par Lot Pour optimiser les performances, le système support le scoring en lot avec limitation de concurrence : ```javascript async batchScore(newsItems, context) { const batchSize = 10; // Limitation pour éviter la surcharge const results = []; for (let i = 0; i < newsItems.length; i += batchSize) { const batch = newsItems.slice(i, i + batchSize); const batchResults = await Promise.all( batch.map(item => this.scoreArticle(item, context)) ); results.push(...batchResults); } // Tri par score décroissant return results.sort((a, b) => (b.finalScore || 0) - (a.finalScore || 0)); } ``` ### Explication des Scores Le moteur peut expliquer en détail comment un score a été calculé : ```javascript explainScore(scoredArticle) { return { scoreBreakdown: { finalScore: scoredArticle.finalScore, components: { specificity: { score: scoredArticle.specificityScore, weight: 0.4, contribution: Math.round(scoredArticle.specificityScore * 0.4), reason: scoredArticle.scoringDetails.specificity.reason, details: scoredArticle.scoringDetails.specificity.details }, // ... autres composants } }, strengths: this.identifyStrengths(scoredArticle), weaknesses: this.identifyWeaknesses(scoredArticle), improvementSuggestions: this.generateImprovementSuggestions(scoredArticle), usageGuideline: { category: scoredArticle.scoreCategory, recommendation: scoredArticle.usageRecommendation, confidence: this.calculateConfidence(scoredArticle) } }; } ``` ## Performance et Optimisation ### Exécution Parallèle Les quatre calculateurs s'exécutent en parallèle pour minimiser la latence : ```javascript // ✅ Optimal : 4 calculs en parallèle const results = await Promise.all([calc1, calc2, calc3, calc4]); // ❌ Suboptimal : 4 calculs séquentiels const result1 = await calc1; const result2 = await calc2; const result3 = await calc3; const result4 = await calc4; ``` ### Cache et Mémorisation - **Base de données des races** : Chargée en mémoire au démarrage - **Sources quality** : Index en mémoire pour accès O(1) - **Calculs récents** : Cache des scores pour éviter les recalculs ### Métriques de Performance Le système collecte des métriques de performance : ```javascript { totalScored: 1250, averageScore: 67.3, scoreDistribution: { excellent: 156, good: 234, fair: 345, poor: 289, reject: 226 }, calculationTime: { total: 45678, // ms average: 36.5 // ms par article } } ``` ## Cas d'Usage et Exemples ### Exemple 1 : Article Premium Spécialisé ```json { "title": "Nouvelle étude génétique sur la dysplasie chez les Bergers Allemands", "content": "Une équipe de chercheurs de l'École Vétérinaire de Maisons-Alfort...", "url": "https://centrale-canine.fr/etudes/dysplasie-berger-allemand-2024", "publishDate": "2024-01-10T08:00:00Z", "sourceType": "premium", "sourceDomain": "centrale-canine.fr" } // Contexte { "raceCode": "352-1", // Berger Allemand "clientId": "client-123", "searchDate": "2024-01-12T10:00:00Z" } // Résultat de scoring { "finalScore": 91, "specificityScore": 100, // Mention exacte "Bergers Allemands" "freshnessScore": 95, // 2 jours, très récent "qualityScore": 100, // centrale-canine.fr = source premium "reuseScore": 80, // Article neuf, jamais utilisé "scoreCategory": "excellent", "usageRecommendation": "priority_use" } ``` ### Exemple 2 : Article Standard Généraliste ```json { "title": "5 conseils pour l'alimentation des grands chiens", "content": "Les chiens de grande taille ont des besoins nutritionnels spécifiques...", "url": "https://wamiz.com/conseils-alimentation-grands-chiens", "publishDate": "2023-12-15T14:30:00Z", "sourceType": "standard", "sourceDomain": "wamiz.com", "usageCount": 3, "lastUsed": "2024-01-05T10:00:00Z" } // Contexte { "raceCode": "352-1", // Berger Allemand (grand chien) "clientId": "client-456", "searchDate": "2024-01-12T10:00:00Z" } // Résultat de scoring { "finalScore": 64, "specificityScore": 50, // "grands chiens" = taille similaire "freshnessScore": 40, // 28 jours, dans la catégorie "fair" "qualityScore": 80, // wamiz.com = source spécialisée "reuseScore": 60, // 3 utilisations = modérément utilisé "scoreCategory": "fair", "usageRecommendation": "conditional_use" } ``` ### Exemple 3 : Article Fallback Sur-utilisé ```json { "title": "Les animaux de compagnie et la famille", "content": "Avoir un animal de compagnie apporte de nombreux bénéfices...", "url": "https://blog-perso.com/animaux-famille", "publishDate": "2023-10-20T16:00:00Z", "sourceType": "fallback", "sourceDomain": "blog-perso.com", "usageCount": 12, "lastUsed": "2024-01-10T08:00:00Z" } // Résultat de scoring { "finalScore": 23, "specificityScore": 10, // "animaux de compagnie" = très généraliste "freshnessScore": 20, // 84 jours = ancien "qualityScore": 30, // Blog personnel = faible qualité "reuseScore": 20, // > 10 utilisations = saturé "scoreCategory": "reject", "usageRecommendation": "avoid" } ``` ## Extensibilité et Personnalisation ### Ajout de Nouveaux Calculateurs L'architecture modulaire permet d'ajouter facilement de nouveaux critères : ```javascript // Exemple : Calculateur de sentiment class SentimentCalculator { async calculateSentiment(article, context) { // Logique d'analyse de sentiment return { score: sentimentScore, reason: 'positive_sentiment', details: 'Contenu majoritairement positif' }; } } // Intégration dans BasicScoringEngine constructor() { this.sentimentCalculator = new SentimentCalculator(); this.weights = { specificity: 0.35, // Réduction pour faire place au sentiment freshness: 0.25, quality: 0.2, reuse: 0.1, sentiment: 0.1 // Nouveau critère }; } ``` ### Personnalisation des Poids Les poids peuvent être ajustés selon le contexte d'usage : ```javascript // Profil "News" : Privilégier fraîcheur et spécificité const newsWeights = { specificity: 0.5, freshness: 0.4, quality: 0.1, reuse: 0.0 }; // Profil "Evergreen" : Équilibrer qualité et réutilisabilité const evergreenWeights = { specificity: 0.3, freshness: 0.1, quality: 0.4, reuse: 0.2 }; ``` ### Configuration Dynamique Le système support la configuration dynamique via le contexte : ```javascript const context = { raceCode: "352-1", scoringProfile: "premium", // news, evergreen, premium, balanced qualityThreshold: 70, freshnessBonus: 1.2, customWeights: { /* poids spécifiques */ } }; ``` ## Monitoring et Observabilité ### Logs Structurés Chaque opération de scoring génère des logs détaillés : ```javascript logger.info('Article scored successfully', { articleId: 'art-123', finalScore: 85, breakdown: { specificity: 90, freshness: 95, quality: 80, reuse: 70 }, calculationTime: 45, raceCode: '352-1', category: 'excellent' }); ``` ### Métriques Business - **Distribution des scores** : Répartition par catégorie - **Performance moyenne** : Score moyen par race/source - **Efficacité de réutilisation** : Taux d'articles disponibles - **Qualité des sources** : Évolution de la qualité du stock ### Alertes Automatiques Le système peut déclencher des alertes : ```javascript // Alerte qualité dégradée if (averageQualityScore < threshold) { alerting.trigger('quality_degradation', { currentScore: averageQualityScore, threshold: threshold, recommendation: 'Renouveler sources premium' }); } ``` ## Évolutions Futures ### Machine Learning Integration future d'un modèle ML pour affiner les scores : ```javascript class MLScoringEngine extends BasicScoringEngine { constructor() { super(); this.mlModel = new ContentQualityModel(); } async scoreArticle(newsItem, context) { const baseScore = await super.scoreArticle(newsItem, context); const mlAdjustment = await this.mlModel.predict(newsItem, context); return { ...baseScore, finalScore: this.adjustWithML(baseScore.finalScore, mlAdjustment), mlConfidence: mlAdjustment.confidence }; } } ``` ### Scoring Adaptatif Ajustement automatique des poids selon les performances : ```javascript class AdaptiveScoringEngine extends BasicScoringEngine { updateWeights(feedbackData) { // Apprentissage des poids optimaux selon feedback utilisateur this.weights = this.optimizeWeights(feedbackData); } } ``` ### Intégration Multi-langues Support de scoring multi-langues avec détection automatique : ```javascript const languageSpecificCalculators = { 'fr': new FrenchSpecificityCalculator(), 'en': new EnglishSpecificityCalculator(), 'de': new GermanSpecificityCalculator() }; ``` ## Conclusion Le système de scoring SourceFinder offre une évaluation sophistiquée et équilibrée du contenu canin, combinant pertinence thématique, actualité, qualité des sources et optimisation de la réutilisation. Son architecture modulaire garantit : - **Flexibilité** : Ajout facile de nouveaux critères - **Performance** : Calculs parallèles et optimisations - **Transparence** : Explication détaillée des scores - **Fiabilité** : Gestion d'erreurs et logging complet - **Évolutivité** : Support de personnalisations avancées Cette approche multi-critères assure une sélection de contenu optimale pour tous les cas d'usage, de la génération d'actualités urgentes aux guides permanents de référence.