// ======================================== // FICHIER: SpellingErrors.js // RESPONSABILITÉ: Fautes d'orthographe et grammaire réalistes // Probabilité MINUSCULE (1-3%) pour réalisme maximal // ======================================== const { logSh } = require('../ErrorReporting'); /** * FAUTES D'ORTHOGRAPHE COURANTES EN FRANÇAIS * Basées sur erreurs humaines fréquentes */ const COMMON_SPELLING_ERRORS = [ // Doubles consonnes manquantes { correct: 'appeler', wrong: 'apeler' }, { correct: 'apparaître', wrong: 'aparaître' }, { correct: 'occurrence', wrong: 'occurence' }, { correct: 'connexion', wrong: 'connection' }, { correct: 'professionnel', wrong: 'profesionnel' }, { correct: 'efficace', wrong: 'éficace' }, { correct: 'différent', wrong: 'différant' }, { correct: 'développement', wrong: 'dévelopement' }, // Accents oubliés/inversés { correct: 'élément', wrong: 'element' }, { correct: 'système', wrong: 'systeme' }, { correct: 'intéressant', wrong: 'interessant' }, { correct: 'qualité', wrong: 'qualite' }, { correct: 'créer', wrong: 'creer' }, { correct: 'dépôt', wrong: 'depot' }, // Homophones { correct: 'et', wrong: 'est', context: 'coord' }, // et/est { correct: 'a', wrong: 'à', context: 'verb' }, // a/à { correct: 'ce', wrong: 'se', context: 'demo' }, // ce/se { correct: 'leur', wrong: 'leurs', context: 'sing' }, // leur/leurs { correct: 'son', wrong: 'sont', context: 'poss' }, // son/sont // Terminaisons -é/-er { correct: 'utilisé', wrong: 'utiliser', context: 'past' }, { correct: 'développé', wrong: 'développer', context: 'past' }, { correct: 'créé', wrong: 'créer', context: 'past' }, // Pluriels oubliés { correct: 'les éléments', wrong: 'les élément' }, { correct: 'des solutions', wrong: 'des solution' }, { correct: 'nos services', wrong: 'nos service' } ]; /** * ERREURS DE GRAMMAIRE COURANTES */ const COMMON_GRAMMAR_ERRORS = [ // Accord sujet-verbe oublié { correct: /nous (sommes|avons|faisons)/gi, wrong: (match) => match.replace(/sommes|avons|faisons/, m => m.slice(0, -1)) }, { correct: /ils (sont|ont|font)/gi, wrong: (match) => match.replace(/sont|ont|font/, m => m === 'sont' ? 'est' : m === 'ont' ? 'a' : 'fait') }, // Participes passés non accordés { correct: /les solutions sont (\w+)ées/gi, wrong: (match) => match.replace(/ées/, 'é') }, { correct: /qui sont (\w+)és/gi, wrong: (match) => match.replace(/és/, 'é') }, // Virgules manquantes { correct: /(Néanmoins|Cependant|Toutefois|Par ailleurs),/gi, wrong: (match) => match.replace(',', '') }, { correct: /(Ainsi|Donc|En effet),/gi, wrong: (match) => match.replace(',', '') } ]; /** * FAUTES DE FRAPPE RÉALISTES * Touches proches sur clavier AZERTY */ const TYPO_ERRORS = [ { correct: 'q', wrong: 'a' }, // Touches adjacentes { correct: 's', wrong: 'd' }, { correct: 'e', wrong: 'r' }, { correct: 'o', wrong: 'p' }, { correct: 'i', wrong: 'u' }, { correct: 'n', wrong: 'b' }, { correct: 'm', wrong: 'n' } ]; /** * INJECTION FAUTES D'ORTHOGRAPHE * Probabilité TRÈS FAIBLE (1-2%) - MAXIMUM 1 FAUTE PAR TEXTE * @param {string} content - Contenu à modifier * @param {number} intensity - Intensité (0-1) * @param {object} tracker - HumanSimulationTracker instance (optionnel) * @returns {object} - { content, modifications } */ function injectSpellingErrors(content, intensity = 0.5, tracker = null) { if (!content || typeof content !== 'string') { return { content, modifications: 0 }; } // ✅ LIMITE 1 FAUTE: Vérifier avec tracker if (tracker && !tracker.canApplySpellingError()) { logSh(`🚫 Faute spelling bloquée: déjà ${tracker.spellingErrorsApplied} faute(s) appliquée(s)`, 'DEBUG'); return { content, modifications: 0 }; } let modified = content; let count = 0; // Probabilité MINUSCULE: 1-2% base × intensité const spellingErrorChance = 0.01 * intensity; // 1% max logSh(`🔤 Injection fautes orthographe (chance: ${(spellingErrorChance * 100).toFixed(1)}%)`, 'DEBUG'); // Parcourir les fautes courantes - STOPPER APRÈS PREMIÈRE FAUTE for (const error of COMMON_SPELLING_ERRORS) { if (count > 0) break; // ✅ STOPPER après 1 faute if (Math.random() < spellingErrorChance) { // Vérifier présence du mot correct const regex = new RegExp(`\\b${error.correct}\\b`, 'gi'); if (modified.match(regex)) { // Remplacer UNE SEULE occurrence (pas toutes) modified = modified.replace(regex, error.wrong); count++; // ✅ Enregistrer dans tracker if (tracker) { tracker.trackSpellingError(); } logSh(` 📝 Faute ortho: "${error.correct}" → "${error.wrong}"`, 'DEBUG'); } } } return { content: modified, modifications: count }; } /** * INJECTION FAUTES DE GRAMMAIRE * Probabilité TRÈS FAIBLE (0.5-1%) * @param {string} content - Contenu à modifier * @param {number} intensity - Intensité (0-1) * @returns {object} - { content, modifications } */ function injectGrammarErrors(content, intensity = 0.5) { if (!content || typeof content !== 'string') { return { content, modifications: 0 }; } let modified = content; let count = 0; // Probabilité MINUSCULE: 0.5% base × intensité const grammarErrorChance = 0.005 * intensity; // 0.5% max logSh(`📐 Injection fautes grammaire (chance: ${(grammarErrorChance * 100).toFixed(1)}%)`, 'DEBUG'); // Virgules manquantes (plus fréquent) if (Math.random() < grammarErrorChance * 3) { // 1.5% max const commaPattern = /(Néanmoins|Cependant|Toutefois|Par ailleurs|Ainsi|Donc),/gi; if (modified.match(commaPattern)) { modified = modified.replace(commaPattern, (match) => match.replace(',', '')); count++; logSh(` 📝 Virgule oubliée après connecteur`, 'DEBUG'); } } // Accords sujet-verbe (rare) if (Math.random() < grammarErrorChance) { const subjectVerbPattern = /nous (sommes|avons|faisons)/gi; if (modified.match(subjectVerbPattern)) { modified = modified.replace(subjectVerbPattern, (match) => { return match.replace(/sommes|avons|faisons/, m => { if (m === 'sommes') return 'est'; if (m === 'avons') return 'a'; return 'fait'; }); }); count++; logSh(` 📝 Accord sujet-verbe incorrect`, 'DEBUG'); } } return { content: modified, modifications: count }; } /** * INJECTION FAUTES DE FRAPPE * Probabilité ULTRA MINUSCULE (0.1-0.5%) * @param {string} content - Contenu à modifier * @param {number} intensity - Intensité (0-1) * @returns {object} - { content, modifications } */ function injectTypoErrors(content, intensity = 0.5) { if (!content || typeof content !== 'string') { return { content, modifications: 0 }; } let modified = content; let count = 0; // Probabilité ULTRA MINUSCULE: 0.1% base × intensité const typoChance = 0.001 * intensity; // 0.1% max if (Math.random() > typoChance) { return { content: modified, modifications: count }; } logSh(`⌨️ Injection faute de frappe (chance: ${(typoChance * 100).toFixed(2)}%)`, 'DEBUG'); // Sélectionner UN mot au hasard const words = modified.split(/\s+/); if (words.length === 0) return { content: modified, modifications: count }; const targetWordIndex = Math.floor(Math.random() * words.length); let targetWord = words[targetWordIndex]; // Appliquer UNE faute de frappe (touche adjacente) if (targetWord.length > 3) { // Seulement sur mots > 3 lettres const charIndex = Math.floor(Math.random() * targetWord.length); const char = targetWord[charIndex].toLowerCase(); // Trouver remplacement touche adjacente const typoError = TYPO_ERRORS.find(t => t.correct === char); if (typoError) { const newWord = targetWord.substring(0, charIndex) + typoError.wrong + targetWord.substring(charIndex + 1); words[targetWordIndex] = newWord; modified = words.join(' '); count++; logSh(` ⌨️ Faute de frappe: "${targetWord}" → "${newWord}"`, 'DEBUG'); } } return { content: modified, modifications: count }; } /** * APPLICATION COMPLÈTE FAUTES * Orchestre tous les types de fautes * @param {string} content - Contenu à modifier * @param {object} options - { intensity, spellingEnabled, grammarEnabled, typoEnabled, tracker } * @returns {object} - { content, modifications } */ function applySpellingErrors(content, options = {}) { if (!content || typeof content !== 'string') { return { content, modifications: 0 }; } const { intensity = 0.5, spellingEnabled = true, grammarEnabled = true, typoEnabled = false, // Désactivé par défaut (trop risqué) tracker = null } = options; let modified = content; let totalModifications = 0; // 1. Fautes d'orthographe (1-2% chance) - MAX 1 FAUTE if (spellingEnabled) { const spellingResult = injectSpellingErrors(modified, intensity, tracker); modified = spellingResult.content; totalModifications += spellingResult.modifications; } // 2. Fautes de grammaire (0.5-1% chance) if (grammarEnabled) { const grammarResult = injectGrammarErrors(modified, intensity); modified = grammarResult.content; totalModifications += grammarResult.modifications; } // 3. Fautes de frappe (0.1% chance - ULTRA RARE) if (typoEnabled) { const typoResult = injectTypoErrors(modified, intensity); modified = typoResult.content; totalModifications += typoResult.modifications; } if (totalModifications > 0) { logSh(`✅ Fautes injectées: ${totalModifications} modification(s)`, 'DEBUG'); } return { content: modified, modifications: totalModifications }; } /** * OBTENIR STATISTIQUES FAUTES */ function getSpellingErrorStats() { return { totalSpellingErrors: COMMON_SPELLING_ERRORS.length, totalGrammarErrors: COMMON_GRAMMAR_ERRORS.length, totalTypoErrors: TYPO_ERRORS.length, defaultProbabilities: { spelling: '1-2%', grammar: '0.5-1%', typo: '0.1%' } }; } // ============= EXPORTS ============= module.exports = { applySpellingErrors, injectSpellingErrors, injectGrammarErrors, injectTypoErrors, getSpellingErrorStats, COMMON_SPELLING_ERRORS, COMMON_GRAMMAR_ERRORS, TYPO_ERRORS };