// ======================================== // FICHIER: ErrorProfiles.js // RESPONSABILITÉ: Définitions des profils d'erreurs par gravité // Système procédural intelligent avec probabilités graduées // ======================================== const { logSh } = require('../../ErrorReporting'); /** * PROFILS D'ERREURS PAR GRAVITÉ * Système à 3 niveaux : Légère (50%) → Moyenne (30%) → Grave (10%) */ const ERROR_SEVERITY_PROFILES = { // ======================================== // ERREURS GRAVES (10% articles max - ULTRA RARE) // ======================================== grave: { name: 'Erreurs Graves', globalProbability: 0.10, // 10% des articles maxPerArticle: 1, // MAX 1 erreur grave par article weight: 10, // Poids pour scoring description: 'Erreurs visibles mais crédibles - fatigué/distrait', conditions: { minArticleLength: 300, // Seulement textes longs maxPerBatch: 0.10, // Max 10% du batch avoidTechnical: true // Éviter contenus techniques }, types: [ 'accord_sujet_verbe', 'mot_manquant', 'double_mot', 'negation_oubliee' ] }, // ======================================== // ERREURS MOYENNES (30% articles) // ======================================== moyenne: { name: 'Erreurs Moyennes', globalProbability: 0.30, // 30% des articles maxPerArticle: 2, // MAX 2 erreurs moyennes weight: 5, // Poids moyen description: 'Erreurs subtiles mais détectables', conditions: { minArticleLength: 150, // Textes moyens+ maxPerBatch: 0.30, // Max 30% du batch avoidTechnical: false }, types: [ 'accord_pluriel', 'virgule_manquante', 'registre_changement', 'preposition_incorrecte', 'connecteur_inapproprie' ] }, // ======================================== // ERREURS LÉGÈRES (50% articles) // ======================================== legere: { name: 'Erreurs Légères', globalProbability: 0.50, // 50% des articles maxPerArticle: 3, // MAX 3 erreurs légères weight: 1, // Poids faible description: 'Micro-erreurs très subtiles - quasi indétectables', conditions: { minArticleLength: 50, // Tous textes maxPerBatch: 0.50, // Max 50% du batch avoidTechnical: false }, types: [ 'double_espace', 'trait_union_oublie', 'espace_avant_ponctuation', 'majuscule_incorrecte', 'apostrophe_droite' ] } }; /** * CARACTÉRISTIQUES DE TEXTE POUR SÉLECTION PROCÉDURALE */ const TEXT_CHARACTERISTICS = { // Longueur texte length: { short: { min: 0, max: 150, errorMultiplier: 0.7 }, // Moins d'erreurs medium: { min: 150, max: 500, errorMultiplier: 1.0 }, // Normal long: { min: 500, max: Infinity, errorMultiplier: 1.3 } // Plus d'erreurs (fatigue) }, // Complexité technique technical: { high: { keywords: ['technique', 'système', 'processus', 'méthode'], errorMultiplier: 0.5 }, medium: { keywords: ['qualité', 'standard', 'professionnel'], errorMultiplier: 1.0 }, low: { keywords: ['simple', 'facile', 'pratique'], errorMultiplier: 1.2 } }, // Période temporelle (heure) temporal: { morning: { hours: [6, 11], errorMultiplier: 0.8 }, // Moins fatigué afternoon: { hours: [12, 17], errorMultiplier: 1.0 }, // Normal evening: { hours: [18, 23], errorMultiplier: 1.2 }, // Légère fatigue night: { hours: [0, 5], errorMultiplier: 1.5 } // Très fatigué } }; /** * OBTENIR PROFIL PAR GRAVITÉ * @param {string} severity - 'grave', 'moyenne', 'legere' * @returns {object} - Profil d'erreur */ function getErrorProfile(severity) { const profile = ERROR_SEVERITY_PROFILES[severity.toLowerCase()]; if (!profile) { logSh(`⚠️ Profil erreur non trouvé: ${severity}`, 'WARNING'); return ERROR_SEVERITY_PROFILES.legere; // Fallback } return profile; } /** * DÉTERMINER GRAVITÉ SELON PROBABILITÉ * Tire aléatoirement selon distribution : 50% légère, 30% moyenne, 10% grave * @returns {string|null} - 'grave', 'moyenne', 'legere' ou null (pas d'erreur) */ function determineSeverityLevel() { const roll = Math.random(); // 10% chance erreur grave if (roll < 0.10) { logSh(`🎲 Gravité déterminée: GRAVE (roll: ${roll.toFixed(3)})`, 'DEBUG'); return 'grave'; } // 30% chance erreur moyenne (10% + 30% = 40%) if (roll < 0.40) { logSh(`🎲 Gravité déterminée: MOYENNE (roll: ${roll.toFixed(3)})`, 'DEBUG'); return 'moyenne'; } // 50% chance erreur légère (40% + 50% = 90%) if (roll < 0.90) { logSh(`🎲 Gravité déterminée: LÉGÈRE (roll: ${roll.toFixed(3)})`, 'DEBUG'); return 'legere'; } // 10% chance aucune erreur logSh(`🎲 Aucune erreur pour cet article (roll: ${roll.toFixed(3)})`, 'DEBUG'); return null; } /** * ANALYSER CARACTÉRISTIQUES TEXTE * @param {string} content - Contenu à analyser * @param {number} currentHour - Heure actuelle * @returns {object} - Caractéristiques détectées */ function analyzeTextCharacteristics(content, currentHour = new Date().getHours()) { const wordCount = content.split(/\s+/).length; const contentLower = content.toLowerCase(); // Déterminer longueur let lengthCategory = 'medium'; let lengthMultiplier = 1.0; for (const [category, config] of Object.entries(TEXT_CHARACTERISTICS.length)) { if (wordCount >= config.min && wordCount < config.max) { lengthCategory = category; lengthMultiplier = config.errorMultiplier; break; } } // Déterminer complexité technique let technicalCategory = 'medium'; let technicalMultiplier = 1.0; for (const [category, config] of Object.entries(TEXT_CHARACTERISTICS.technical)) { const keywordMatches = config.keywords.filter(kw => contentLower.includes(kw)).length; if (keywordMatches >= 2) { technicalCategory = category; technicalMultiplier = config.errorMultiplier; break; } } // Déterminer période temporelle let temporalCategory = 'afternoon'; let temporalMultiplier = 1.0; for (const [category, config] of Object.entries(TEXT_CHARACTERISTICS.temporal)) { if (currentHour >= config.hours[0] && currentHour <= config.hours[1]) { temporalCategory = category; temporalMultiplier = config.errorMultiplier; break; } } // Multiplicateur global const globalMultiplier = lengthMultiplier * technicalMultiplier * temporalMultiplier; logSh(`📊 Caractéristiques: longueur=${lengthCategory}, technique=${technicalCategory}, temporel=${temporalCategory}, mult=${globalMultiplier.toFixed(2)}`, 'DEBUG'); return { wordCount, lengthCategory, lengthMultiplier, technicalCategory, technicalMultiplier, temporalCategory, temporalMultiplier, globalMultiplier }; } /** * VÉRIFIER SI ERREUR AUTORISÉE SELON CONDITIONS * @param {string} severity - Gravité * @param {object} textCharacteristics - Caractéristiques texte * @returns {boolean} - true si autorisée */ function isErrorAllowed(severity, textCharacteristics) { const profile = getErrorProfile(severity); // Vérifier longueur minimale if (textCharacteristics.wordCount < profile.conditions.minArticleLength) { logSh(`🚫 Erreur ${severity} bloquée: texte trop court (${textCharacteristics.wordCount} mots < ${profile.conditions.minArticleLength})`, 'DEBUG'); return false; } // Vérifier si contenu technique et grave if (profile.conditions.avoidTechnical && textCharacteristics.technicalCategory === 'high') { logSh(`🚫 Erreur ${severity} bloquée: contenu trop technique`, 'DEBUG'); return false; } return true; } // ============= EXPORTS ============= module.exports = { ERROR_SEVERITY_PROFILES, TEXT_CHARACTERISTICS, getErrorProfile, determineSeverityLevel, analyzeTextCharacteristics, isErrorAllowed };