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

631 lines
21 KiB
JavaScript

// ========================================
// FICHIER: TemporalStyles.js
// RESPONSABILITÉ: Variations temporelles d'écriture
// Simulation comportement humain selon l'heure
// ========================================
const { logSh } = require('../ErrorReporting');
/**
* STYLES TEMPORELS PAR TRANCHES HORAIRES
* Simule l'énergie et style d'écriture selon l'heure
*/
const TEMPORAL_STYLES = {
// ========================================
// MATIN (6h-11h) - Énergique et Direct
// ========================================
morning: {
period: 'matin',
timeRange: [6, 11],
energy: 'high',
characteristics: {
sentenceLength: 'short', // Phrases plus courtes
vocabulary: 'dynamic', // Mots énergiques
connectors: 'direct', // Connecteurs simples
rhythm: 'fast' // Rythme soutenu
},
vocabularyPreferences: {
energy: ['dynamique', 'efficace', 'rapide', 'direct', 'actif', 'performant'],
connectors: ['donc', 'puis', 'ensuite', 'maintenant', 'immédiatement'],
modifiers: ['très', 'vraiment', 'particulièrement', 'nettement'],
actions: ['optimiser', 'accélérer', 'améliorer', 'développer', 'créer']
},
styleTendencies: {
shortSentencesBias: 0.7, // 70% chance phrases courtes
directConnectorsBias: 0.8, // 80% connecteurs simples
energyWordsBias: 0.6 // 60% mots énergiques
}
},
// ========================================
// APRÈS-MIDI (12h-17h) - Équilibré et Professionnel
// ========================================
afternoon: {
period: 'après-midi',
timeRange: [12, 17],
energy: 'medium',
characteristics: {
sentenceLength: 'medium', // Phrases équilibrées
vocabulary: 'professional', // Vocabulaire standard
connectors: 'balanced', // Connecteurs variés
rhythm: 'steady' // Rythme régulier
},
vocabularyPreferences: {
energy: ['professionnel', 'efficace', 'qualité', 'standard', 'adapté'],
connectors: ['par ailleurs', 'de plus', 'également', 'ainsi', 'cependant'],
modifiers: ['assez', 'plutôt', 'relativement', 'suffisamment'],
actions: ['réaliser', 'développer', 'analyser', 'étudier', 'concevoir']
},
styleTendencies: {
shortSentencesBias: 0.4, // 40% phrases courtes
directConnectorsBias: 0.5, // 50% connecteurs simples
energyWordsBias: 0.3 // 30% mots énergiques
}
},
// ========================================
// SOIR (18h-23h) - Détendu et Réflexif
// ========================================
evening: {
period: 'soir',
timeRange: [18, 23],
energy: 'low',
characteristics: {
sentenceLength: 'long', // Phrases plus longues
vocabulary: 'nuanced', // Vocabulaire nuancé
connectors: 'complex', // Connecteurs élaborés
rhythm: 'relaxed' // Rythme posé
},
vocabularyPreferences: {
energy: ['approfondi', 'réfléchi', 'considéré', 'nuancé', 'détaillé'],
connectors: ['néanmoins', 'cependant', 'par conséquent', 'en outre', 'toutefois'],
modifiers: ['quelque peu', 'relativement', 'dans une certaine mesure', 'assez'],
actions: ['examiner', 'considérer', 'réfléchir', 'approfondir', 'explorer']
},
styleTendencies: {
shortSentencesBias: 0.2, // 20% phrases courtes
directConnectorsBias: 0.2, // 20% connecteurs simples
energyWordsBias: 0.1 // 10% mots énergiques
}
},
// ========================================
// NUIT (0h-5h) - Fatigue et Simplicité
// ========================================
night: {
period: 'nuit',
timeRange: [0, 5],
energy: 'very_low',
characteristics: {
sentenceLength: 'short', // Phrases courtes par fatigue
vocabulary: 'simple', // Vocabulaire basique
connectors: 'minimal', // Connecteurs rares
rhythm: 'slow' // Rythme lent
},
vocabularyPreferences: {
energy: ['simple', 'basique', 'standard', 'normal', 'classique'],
connectors: ['et', 'mais', 'ou', 'donc', 'puis'],
modifiers: ['assez', 'bien', 'pas mal', 'correct'],
actions: ['faire', 'utiliser', 'prendre', 'mettre', 'avoir']
},
styleTendencies: {
shortSentencesBias: 0.8, // 80% phrases courtes
directConnectorsBias: 0.9, // 90% connecteurs simples
energyWordsBias: 0.1 // 10% mots énergiques
}
}
};
/**
* DÉTERMINER STYLE TEMPOREL SELON L'HEURE
* @param {number} currentHour - Heure actuelle (0-23)
* @returns {object} - Style temporel correspondant
*/
function getTemporalStyle(currentHour) {
// Validation heure
const hour = Math.max(0, Math.min(23, Math.floor(currentHour || new Date().getHours())));
logSh(`⏰ Détermination style temporel pour ${hour}h`, 'DEBUG');
// Déterminer période
let selectedStyle;
if (hour >= 6 && hour <= 11) {
selectedStyle = TEMPORAL_STYLES.morning;
} else if (hour >= 12 && hour <= 17) {
selectedStyle = TEMPORAL_STYLES.afternoon;
} else if (hour >= 18 && hour <= 23) {
selectedStyle = TEMPORAL_STYLES.evening;
} else {
selectedStyle = TEMPORAL_STYLES.night;
}
logSh(`⏰ Style temporel sélectionné: ${selectedStyle.period} (énergie: ${selectedStyle.energy})`, 'DEBUG');
return {
...selectedStyle,
currentHour: hour,
timestamp: new Date().toISOString()
};
}
/**
* APPLICATION STYLE TEMPOREL
* @param {string} content - Contenu à modifier
* @param {object} temporalStyle - Style temporel à appliquer
* @param {object} options - Options { intensity, tracker }
* @returns {object} - { content, modifications }
*/
function applyTemporalStyle(content, temporalStyle, options = {}) {
if (!content || !temporalStyle) {
return { content, modifications: 0 };
}
const intensity = options.intensity || 1.0;
const tracker = options.tracker || null;
logSh(`⏰ Application style temporel: ${temporalStyle.period} (intensité: ${intensity})`, 'DEBUG');
let modifiedContent = content;
let modifications = 0;
// ========================================
// 1. AJUSTEMENT LONGUEUR PHRASES
// ========================================
const sentenceResult = adjustSentenceLength(modifiedContent, temporalStyle, intensity, tracker);
modifiedContent = sentenceResult.content;
modifications += sentenceResult.count;
// ========================================
// 2. ADAPTATION VOCABULAIRE
// ========================================
const vocabularyResult = adaptVocabulary(modifiedContent, temporalStyle, intensity);
modifiedContent = vocabularyResult.content;
modifications += vocabularyResult.count;
// ========================================
// 3. MODIFICATION CONNECTEURS
// ========================================
const connectorResult = adjustConnectors(modifiedContent, temporalStyle, intensity);
modifiedContent = connectorResult.content;
modifications += connectorResult.count;
// ========================================
// 4. AJUSTEMENT RYTHME
// ========================================
const rhythmResult = adjustRhythm(modifiedContent, temporalStyle, intensity);
modifiedContent = rhythmResult.content;
modifications += rhythmResult.count;
logSh(`⏰ Style temporel appliqué: ${modifications} modifications`, 'DEBUG');
return {
content: modifiedContent,
modifications
};
}
/**
* AJUSTEMENT LONGUEUR PHRASES
* @param {object} tracker - HumanSimulationTracker instance (optionnel)
*/
function adjustSentenceLength(content, temporalStyle, intensity, tracker = null) {
let modified = content;
let count = 0;
const bias = temporalStyle.styleTendencies.shortSentencesBias * intensity;
const sentences = modified.split('. ');
// Probabilité d'appliquer - ÉQUILIBRÉ (25% max)
if (Math.random() > (intensity * 0.25)) { // FIXÉ V3.1: ÉQUILIBRÉ - 25% max
return { content: modified, count };
}
const processedSentences = sentences.map(sentence => {
if (sentence.length < 20) return sentence; // Ignorer phrases très courtes
// Style MATIN/NUIT - Raccourcir phrases longues
if ((temporalStyle.period === 'matin' || temporalStyle.period === 'nuit') &&
sentence.length > 100 && Math.random() < bias) {
// Chercher point de coupe naturel
const cutPoints = [', qui', ', que', ', dont', ' et ', ' car ', ' mais '];
for (const cutPoint of cutPoints) {
const cutIndex = sentence.indexOf(cutPoint);
if (cutIndex > 30 && cutIndex < sentence.length - 30) {
count++;
logSh(` ✂️ Phrase raccourcie (${temporalStyle.period}): ${sentence.length}${cutIndex} chars`, 'DEBUG');
return sentence.substring(0, cutIndex) + '. ' +
sentence.substring(cutIndex + cutPoint.length);
}
}
}
// Style SOIR - Allonger phrases courtes
if (temporalStyle.period === 'soir' &&
sentence.length > 30 && sentence.length < 80 &&
Math.random() < (1 - bias)) {
// Ajouter développements - ÉLARGI 20+ variantes
const developments = [
// Avantages et bénéfices
', ce qui constitue un avantage notable',
', permettant ainsi d\'optimiser les résultats',
', contribuant à l\'efficacité globale',
', offrant ainsi des perspectives intéressantes',
', garantissant une meilleure qualité',
// Processus et démarches
', dans une démarche d\'amélioration continue',
', dans le cadre d\'une stratégie cohérente',
', selon une approche méthodique',
', grâce à une mise en œuvre rigoureuse',
// Contexte et perspective
', tout en respectant les standards en vigueur',
', conformément aux attentes du marché',
', en adéquation avec les besoins identifiés',
', dans le respect des contraintes établies',
// Résultats et impacts
', avec des résultats mesurables',
', pour un impact significatif',
', assurant une performance optimale',
', favorisant ainsi la satisfaction client',
// Approches techniques
', en utilisant des méthodes éprouvées',
', par le biais d\'une expertise reconnue',
', moyennant une analyse approfondie',
// Continuité et évolution
', dans une perspective d\'évolution constante',
', tout en maintenant un haut niveau d\'exigence',
', en veillant à la pérennité du système',
', garantissant une adaptation progressive'
];
// ✅ ANTI-RÉPÉTITION: Filtrer développements déjà utilisés
let availableDevelopments = developments;
if (tracker) {
availableDevelopments = developments.filter(d => tracker.canUseDevelopment(d));
}
// Si aucun développement disponible, skip
if (availableDevelopments.length === 0) {
logSh(` 🚫 Aucun développement disponible (tous déjà utilisés)`, 'DEBUG');
return sentence;
}
const development = availableDevelopments[Math.floor(Math.random() * availableDevelopments.length)];
// ✅ Enregistrer dans tracker
if (tracker) {
tracker.trackDevelopment(development);
}
count++;
logSh(` 📝 Phrase allongée (soir): ${sentence.length}${sentence.length + development.length} chars`, 'DEBUG');
return sentence + development;
}
return sentence;
});
modified = processedSentences.join('. ');
return { content: modified, count };
}
/**
* ADAPTATION VOCABULAIRE
*/
function adaptVocabulary(content, temporalStyle, intensity) {
let modified = content;
let count = 0;
const vocabularyPrefs = temporalStyle.vocabularyPreferences;
const energyBias = temporalStyle.styleTendencies.energyWordsBias * intensity;
// Probabilité d'appliquer - ÉQUILIBRÉ (20% max)
if (Math.random() > (intensity * 0.2)) { // FIXÉ V3.1: ÉQUILIBRÉ - 20% max
return { content: modified, count };
}
// Remplacements selon période
const replacements = buildVocabularyReplacements(temporalStyle.period, vocabularyPrefs);
replacements.forEach(replacement => {
if (Math.random() < (energyBias * 0.3)) { // FIXÉ V3.1: ÉQUILIBRÉ - 30%
// ✅ PROTECTION: Vérifier expressions idiomatiques
if (isInProtectedExpression(modified, replacement.from)) {
logSh(` 🛡️ Remplacement bloqué: "${replacement.from}" dans expression protégée`, 'DEBUG');
return; // Skip ce remplacement
}
const regex = new RegExp(`\\b${replacement.from}\\b`, 'gi');
if (modified.match(regex)) {
modified = modified.replace(regex, replacement.to);
count++;
logSh(` 📚 Vocabulaire adapté (${temporalStyle.period}): "${replacement.from}" → "${replacement.to}"`, 'DEBUG');
}
}
});
// FIXÉ V3: PAS de fallback garanti - SUBTILITÉ MAXIMALE
// Aucune modification forcée
return { content: modified, count };
}
/**
* EXPRESSIONS IDIOMATIQUES FRANÇAISES PROTÉGÉES
* Ne JAMAIS remplacer de mots dans ces expressions
*/
const PROTECTED_EXPRESSIONS = [
// Expressions courantes avec "faire"
'faire toute la différence',
'faire attention',
'faire les choses',
'faire face',
'faire preuve',
'faire partie',
'faire confiance',
'faire référence',
'faire appel',
'faire en sorte',
// Expressions courantes avec "prendre"
'prendre en compte',
'prendre en charge',
'prendre conscience',
'prendre part',
'prendre soin',
// Expressions courantes avec "mettre"
'mettre en œuvre',
'mettre en place',
'mettre en avant',
'mettre l\'accent',
// Autres expressions figées
'avoir lieu',
'donner suite',
'tenir compte',
'bien entendu',
'en effet',
'en fait',
'tout à fait',
'par exemple'
];
/**
* VÉRIFIER SI UN MOT EST DANS UNE EXPRESSION PROTÉGÉE
* @param {string} content - Contenu complet
* @param {string} word - Mot à remplacer
* @returns {boolean} - true si dans expression protégée
*/
function isInProtectedExpression(content, word) {
const lowerContent = content.toLowerCase();
const lowerWord = word.toLowerCase();
return PROTECTED_EXPRESSIONS.some(expr => {
const exprLower = expr.toLowerCase();
// Vérifier si l'expression existe ET contient le mot
return exprLower.includes(lowerWord) && lowerContent.includes(exprLower);
});
}
/**
* CONSTRUCTION REMPLACEMENTS VOCABULAIRE
*/
function buildVocabularyReplacements(period, vocabPrefs) {
const replacements = [];
switch (period) {
case 'matin':
replacements.push(
{ from: 'bon', to: 'excellent' },
{ from: 'intéressant', to: 'dynamique' },
{ from: 'utiliser', to: 'optimiser' },
{ from: 'faire', to: 'créer' }
);
break;
case 'soir':
replacements.push(
{ from: 'bon', to: 'considérable' },
{ from: 'faire', to: 'examiner' },
{ from: 'utiliser', to: 'exploiter' },
{ from: 'voir', to: 'considérer' }
);
break;
case 'nuit':
replacements.push(
{ from: 'excellent', to: 'bien' },
{ from: 'optimiser', to: 'utiliser' },
{ from: 'considérable', to: 'correct' },
{ from: 'examiner', to: 'regarder' }
);
break;
default: // après-midi
// Vocabulaire équilibré - pas de remplacements drastiques
break;
}
return replacements;
}
/**
* AJUSTEMENT CONNECTEURS
*/
function adjustConnectors(content, temporalStyle, intensity) {
let modified = content;
let count = 0;
const connectorBias = temporalStyle.styleTendencies.directConnectorsBias * intensity;
const preferredConnectors = temporalStyle.vocabularyPreferences.connectors;
if (Math.random() > intensity * 0.5) {
return { content: modified, count };
}
// Connecteurs selon période - FIXÉ: Word boundaries pour éviter "maison" → "néanmoinson"
const connectorMappings = {
matin: [
{ from: /\bpar conséquent\b/gi, to: 'donc' },
{ from: /\bnéanmoins\b/gi, to: 'mais' },
{ from: /\ben outre\b/gi, to: 'aussi' }
],
soir: [
{ from: /\bdonc\b/gi, to: 'par conséquent' },
{ from: /\bmais\b/gi, to: 'néanmoins' }, // ✅ FIXÉ: \b empêche match dans "maison"
{ from: /\baussi\b/gi, to: 'en outre' }
],
nuit: [
{ from: /\bpar conséquent\b/gi, to: 'donc' },
{ from: /\bnéanmoins\b/gi, to: 'mais' },
{ from: /\bcependant\b/gi, to: 'mais' }
]
};
const mappings = connectorMappings[temporalStyle.period] || [];
mappings.forEach(mapping => {
if (Math.random() < connectorBias) {
if (modified.match(mapping.from)) {
modified = modified.replace(mapping.from, mapping.to);
count++;
logSh(` 🔗 Connecteur adapté (${temporalStyle.period}): "${mapping.from}" → "${mapping.to}"`, 'DEBUG');
}
}
});
return { content: modified, count };
}
/**
* AJUSTEMENT RYTHME
*/
function adjustRhythm(content, temporalStyle, intensity) {
let modified = content;
let count = 0;
// Le rythme affecte la ponctuation et les pauses
if (Math.random() > intensity * 0.3) {
return { content: modified, count };
}
switch (temporalStyle.characteristics.rhythm) {
case 'fast': // Matin - moins de virgules, plus direct
if (Math.random() < 0.4) {
// Supprimer quelques virgules non essentielles
const originalCommas = (modified.match(/,/g) || []).length;
modified = modified.replace(/, qui /gi, ' qui ');
modified = modified.replace(/, que /gi, ' que ');
const newCommas = (modified.match(/,/g) || []).length;
count = originalCommas - newCommas;
if (count > 0) {
logSh(` ⚡ Rythme accéléré: ${count} virgules supprimées`, 'DEBUG');
}
}
break;
case 'relaxed': // Soir - plus de pauses
if (Math.random() < 0.3) {
// Ajouter quelques pauses réflexives - 15+ variantes
const reflexivePauses = [
'Ainsi',
'Par ailleurs',
'De plus',
'En outre',
'D\'ailleurs',
'Également',
'Qui plus est',
'De surcroît',
'Par conséquent',
'Effectivement',
'En effet',
'Cela dit',
'Toutefois',
'Néanmoins',
'Pour autant'
];
const pause = reflexivePauses[Math.floor(Math.random() * reflexivePauses.length)];
modified = modified.replace(/\. ([A-Z])/g, `. ${pause}, $1`);
count++;
logSh(` 🧘 Rythme ralenti: pause "${pause}" ajoutée`, 'DEBUG');
}
break;
case 'slow': // Nuit - simplification
if (Math.random() < 0.5) {
// Simplifier structures complexes
modified = modified.replace(/ ; /g, '. ');
count++;
logSh(` 😴 Rythme simplifié: structures allégées`, 'DEBUG');
}
break;
}
return { content: modified, count };
}
/**
* ANALYSE COHÉRENCE TEMPORELLE
* @param {string} content - Contenu à analyser
* @param {object} temporalStyle - Style appliqué
* @returns {object} - Métriques de cohérence
*/
function analyzeTemporalCoherence(content, temporalStyle) {
const sentences = content.split('. ');
const avgSentenceLength = sentences.reduce((sum, s) => sum + s.length, 0) / sentences.length;
const energyWords = temporalStyle.vocabularyPreferences.energy;
const energyWordCount = energyWords.reduce((count, word) => {
const regex = new RegExp(`\\b${word}\\b`, 'gi');
return count + (content.match(regex) || []).length;
}, 0);
return {
avgSentenceLength,
energyWordDensity: energyWordCount / sentences.length,
period: temporalStyle.period,
coherenceScore: calculateCoherenceScore(avgSentenceLength, temporalStyle),
expectedCharacteristics: temporalStyle.characteristics
};
}
/**
* CALCUL SCORE COHÉRENCE
*/
function calculateCoherenceScore(avgLength, temporalStyle) {
let score = 1.0;
// Vérifier cohérence longueur phrases avec période
const expectedLength = {
'matin': { min: 40, max: 80 },
'après-midi': { min: 60, max: 120 },
'soir': { min: 80, max: 150 },
'nuit': { min: 30, max: 70 }
};
const expected = expectedLength[temporalStyle.period];
if (expected) {
if (avgLength < expected.min || avgLength > expected.max) {
score *= 0.7;
}
}
return Math.max(0, Math.min(1, score));
}
// ============= EXPORTS =============
module.exports = {
getTemporalStyle,
applyTemporalStyle,
adjustSentenceLength,
adaptVocabulary,
adjustConnectors,
adjustRhythm,
analyzeTemporalCoherence,
calculateCoherenceScore,
buildVocabularyReplacements,
isInProtectedExpression,
TEMPORAL_STYLES,
PROTECTED_EXPRESSIONS
};