seo-generator-server/lib/human-simulation/SpellingErrors.js
StillHammer 9a2ef7da2b feat(human-simulation): Système d'erreurs graduées procédurales + anti-répétition complet
## 🎯 Nouveau système d'erreurs graduées (architecture SmartTouch)

### Architecture procédurale intelligente :
- **3 niveaux de gravité** : Légère (50%) → Moyenne (30%) → Grave (10%)
- **14 types d'erreurs** réalistes et subtiles
- **Sélection procédurale** selon contexte (longueur, technique, heure)
- **Distribution contrôlée** : max 1 grave, 2 moyennes, 3 légères par article

### 1. Erreurs GRAVES (10% articles max) :
- Accord sujet-verbe : "ils sont" → "ils est"
- Mot manquant : "pour garantir la qualité" → "pour garantir qualité"
- Double mot : "pour garantir" → "pour pour garantir"
- Négation oubliée : "n'est pas" → "est pas"

### 2. Erreurs MOYENNES (30% articles) :
- Accord pluriel : "plaques résistantes" → "plaques résistant"
- Virgule manquante : "Ainsi, il" → "Ainsi il"
- Registre inapproprié : "Par conséquent" → "Du coup"
- Préposition incorrecte : "résistant aux" → "résistant des"
- Connecteur illogique : "cependant" → "donc"

### 3. Erreurs LÉGÈRES (50% articles) :
- Double espace : "de votre" → "de  votre"
- Trait d'union : "c'est-à-dire" → "c'est à dire"
- Espace ponctuation : "qualité ?" → "qualité?"
- Majuscule : "Toutenplaque" → "toutenplaque"
- Apostrophe droite : "l'article" → "l'article"

##  Système anti-répétition complet :

### Corrections critiques :
- **HumanSimulationTracker.js** : Tracker centralisé global
- **Word boundaries (\b)** sur TOUS les regex → FIX "maison" → "néanmoinson"
- **Protection 30+ expressions idiomatiques** françaises
- **Anti-répétition** : max 2× même mot, jamais 2× même développement
- **Diversification** : 48 variantes (hésitations, développements, connecteurs)

### Nouvelle structure (comme SmartTouch) :
```
lib/human-simulation/
├── error-profiles/                (NOUVEAU)
│   ├── ErrorProfiles.js          (définitions + probabilités)
│   ├── ErrorGrave.js             (10% articles)
│   ├── ErrorMoyenne.js           (30% articles)
│   ├── ErrorLegere.js            (50% articles)
│   └── ErrorSelector.js          (sélection procédurale)
├── HumanSimulationCore.js         (orchestrateur)
├── HumanSimulationTracker.js      (anti-répétition)
└── [autres modules]
```

## 🔄 Remplace ancien système :
-  SpellingErrors.js (basique, répétitif, "et" → "." × 8)
-  error-profiles/ (gradué, procédural, intelligent, diversifié)

## 🎲 Fonctionnalités procédurales :
- Analyse contexte : longueur texte, complexité technique, heure rédaction
- Multiplicateurs adaptatifs selon contexte
- Conditions application intelligentes
- Tracking global par batch (respecte limites 10%/30%/50%)

## 📊 Résultats validation :
Sur 100 articles → ~40-50 avec erreurs subtiles et diverses (plus de spam répétitif)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-14 01:06:28 +08:00

313 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ========================================
// 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
};