seo-generator-server/lib/human-simulation/error-profiles/ErrorLegere.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

246 lines
7.5 KiB
JavaScript

// ========================================
// FICHIER: ErrorLegere.js
// RESPONSABILITÉ: Erreurs LÉGÈRES (50% articles)
// Micro-erreurs très subtiles - quasi indétectables
// ========================================
const { logSh } = require('../../ErrorReporting');
/**
* DÉFINITIONS ERREURS LÉGÈRES
* Probabilité globale: 50% des articles
* Maximum: 3 erreurs légères par article
*/
const ERREURS_LEGERES = {
// ========================================
// DOUBLE ESPACE
// ========================================
double_espace: {
name: 'Double espace',
probability: 0.30,
examples: [
{ pattern: 'de votre', error: 'de votre' },
{ pattern: 'pour la', error: 'pour la' }
],
apply: (content) => {
let modified = content;
let applied = false;
// Insérer double espace aléatoirement
const words = content.split(' ');
if (words.length < 10) return { content: modified, applied };
const targetIndex = Math.floor(Math.random() * (words.length - 1));
words[targetIndex] = words[targetIndex] + ' '; // Ajouter espace supplémentaire
modified = words.join(' ');
applied = true;
logSh(` · Erreur légère: double espace à position ${targetIndex}`, 'DEBUG');
return { content: modified, applied };
}
},
// ========================================
// TRAIT D'UNION OUBLIÉ
// ========================================
trait_union_oublie: {
name: 'Trait d\'union oublié',
probability: 0.25,
examples: [
{ pattern: "c'est-à-dire", error: "c'est à dire" },
{ pattern: 'peut-être', error: 'peut être' },
{ pattern: 'vis-à-vis', error: 'vis à vis' }
],
apply: (content) => {
let modified = content;
let applied = false;
// Supprimer traits d'union
const patterns = [
{ from: /\bc'est-à-dire\b/gi, to: "c'est à dire" },
{ from: /\bpeut-être\b/gi, to: 'peut être' },
{ from: /\bvis-à-vis\b/gi, to: 'vis à vis' }
];
for (const pattern of patterns) {
if (applied) break;
if (modified.match(pattern.from) && Math.random() < 0.6) {
modified = modified.replace(pattern.from, pattern.to);
applied = true;
logSh(` · Erreur légère: trait d'union oublié`, 'DEBUG');
break;
}
}
return { content: modified, applied };
}
},
// ========================================
// ESPACE AVANT PONCTUATION
// ========================================
espace_avant_ponctuation: {
name: 'Espace avant ponctuation manquante',
probability: 0.20,
examples: [
{ pattern: 'qualité ?', error: 'qualité?' },
{ pattern: 'résistance !', error: 'résistance!' }
],
apply: (content) => {
let modified = content;
let applied = false;
// Supprimer espace avant ? ou !
if (Math.random() < 0.5) {
modified = modified.replace(/ \?/g, '?');
if (modified !== content) {
applied = true;
logSh(` · Erreur légère: espace manquant avant "?"`, 'DEBUG');
}
} else {
modified = modified.replace(/ !/g, '!');
if (modified !== content) {
applied = true;
logSh(` · Erreur légère: espace manquant avant "!"`, 'DEBUG');
}
}
return { content: modified, applied };
}
},
// ========================================
// MAJUSCULE INCORRECTE
// ========================================
majuscule_incorrecte: {
name: 'Majuscule incorrecte',
probability: 0.15,
examples: [
{ pattern: 'la France', error: 'la france' },
{ pattern: 'Toutenplaque', error: 'toutenplaque' }
],
apply: (content) => {
let modified = content;
let applied = false;
// Mettre en minuscule un nom propre
const properNouns = ['France', 'Paris', 'Toutenplaque'];
for (const noun of properNouns) {
if (applied) break;
const regex = new RegExp(`\\b${noun}\\b`, 'g');
if (modified.match(regex) && Math.random() < 0.4) {
modified = modified.replace(regex, noun.toLowerCase());
applied = true;
logSh(` · Erreur légère: majuscule incorrecte sur "${noun}"`, 'DEBUG');
break;
}
}
return { content: modified, applied };
}
},
// ========================================
// APOSTROPHE DROITE (au lieu de courbe)
// ========================================
apostrophe_droite: {
name: 'Apostrophe droite au lieu de courbe',
probability: 0.10,
examples: [
{ pattern: "l'article", error: "l'article" },
{ pattern: "d'une", error: "d'une" }
],
apply: (content) => {
let modified = content;
let applied = false;
// Remplacer apostrophe courbe par droite
const apostropheCourbe = '\u2019'; // ' (apostrophe typographique)
if (modified.includes(apostropheCourbe) && Math.random() < 0.5) {
// Remplacer UNE occurrence seulement
const index = modified.indexOf(apostropheCourbe);
if (index !== -1) {
modified = modified.substring(0, index) + "'" + modified.substring(index + 1);
applied = true;
logSh(` · Erreur légère: apostrophe droite au lieu de courbe`, 'DEBUG');
}
}
return { content: modified, applied };
}
}
};
/**
* APPLIQUER ERREURS LÉGÈRES
* @param {string} content - Contenu à modifier
* @param {number} maxErrors - Maximum erreurs légères (défaut: 3)
* @param {object} tracker - HumanSimulationTracker instance
* @returns {object} - { content, errorsApplied, errorTypes }
*/
function applyErrorsLegeres(content, maxErrors = 3, tracker = null) {
let modified = content;
let errorsApplied = 0;
const errorTypes = [];
// Vérifier avec tracker combien d'erreurs légères déjà appliquées
if (tracker && tracker.legereErrorsApplied >= maxErrors) {
logSh(`🚫 Erreurs légères bloquées: déjà ${tracker.legereErrorsApplied} erreur(s) dans cet article`, 'DEBUG');
return { content: modified, errorsApplied: 0, errorTypes: [] };
}
// Appliquer jusqu'à maxErrors
const availableErrors = Object.keys(ERREURS_LEGERES);
while (errorsApplied < maxErrors && availableErrors.length > 0) {
// Sélectionner type aléatoire
const randomIndex = Math.floor(Math.random() * availableErrors.length);
const selectedType = availableErrors[randomIndex];
const errorDefinition = ERREURS_LEGERES[selectedType];
logSh(`🎲 Tentative erreur légère: ${errorDefinition.name}`, 'DEBUG');
// Appliquer
const result = errorDefinition.apply(modified);
if (result.applied) {
modified = result.content;
errorsApplied++;
errorTypes.push(selectedType);
logSh(`✅ Erreur légère appliquée: ${errorDefinition.name}`, 'DEBUG');
// Marquer dans tracker
if (tracker) {
tracker.legereErrorsApplied = (tracker.legereErrorsApplied || 0) + 1;
}
}
// Retirer de la liste pour éviter doublon
availableErrors.splice(randomIndex, 1);
}
return { content: modified, errorsApplied, errorTypes };
}
/**
* OBTENIR STATISTIQUES ERREURS LÉGÈRES
*/
function getErrorLegereStats() {
return {
totalTypes: Object.keys(ERREURS_LEGERES).length,
types: Object.keys(ERREURS_LEGERES),
globalProbability: '50%',
maxPerArticle: 3
};
}
// ============= EXPORTS =============
module.exports = {
ERREURS_LEGERES,
applyErrorsLegeres,
getErrorLegereStats
};