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

326 lines
12 KiB
JavaScript

// ========================================
// FICHIER: HumanSimulationCore.js
// RESPONSABILITÉ: Orchestrateur principal Human Simulation
// Niveau 5: Temporal & Personality Injection
// ========================================
const { logSh } = require('../ErrorReporting');
const { tracer } = require('../trace');
const { calculateFatigue, injectFatigueMarkers, getFatigueProfile } = require('./FatiguePatterns');
const { injectPersonalityErrors, getPersonalityErrorPatterns } = require('./PersonalityErrors');
const { applyTemporalStyle, getTemporalStyle } = require('./TemporalStyles');
const { HumanSimulationTracker } = require('./HumanSimulationTracker');
const { selectAndApplyErrors } = require('./error-profiles/ErrorSelector'); // ✅ NOUVEAU: Système erreurs graduées
const {
analyzeContentComplexity,
calculateReadabilityScore,
preserveKeywords,
validateSimulationQuality
} = require('./HumanSimulationUtils');
/**
* CONFIGURATION PAR DÉFAUT
* ⚠️ VALIDATION DÉSACTIVÉE - Imperfections volontaires acceptées
*/
const DEFAULT_CONFIG = {
fatigueEnabled: true,
personalityErrorsEnabled: true,
temporalStyleEnabled: true,
graduatedErrorsEnabled: true, // ✅ NOUVEAU: Système erreurs graduées (grave/moyenne/légère)
imperfectionIntensity: 0.5,
naturalRepetitions: true,
qualityThreshold: 0, // ✅ VALIDATION DÉSACTIVÉE (threshold=0)
maxModificationsPerElement: 3
};
/**
* ORCHESTRATEUR PRINCIPAL - Human Simulation Layer
* @param {object} content - Contenu généré à simuler
* @param {object} options - Options de simulation
* @returns {object} - { content, stats, fallback }
*/
async function applyHumanSimulationLayer(content, options = {}) {
return await tracer.run('HumanSimulationCore.applyHumanSimulationLayer()', async () => {
const startTime = Date.now();
await tracer.annotate({
contentKeys: Object.keys(content).length,
elementIndex: options.elementIndex,
totalElements: options.totalElements,
currentHour: options.currentHour,
personality: options.csvData?.personality?.nom
});
logSh(`🧠 HUMAN SIMULATION - Début traitement`, 'INFO');
logSh(` 📊 ${Object.keys(content).length} éléments | Position: ${options.elementIndex}/${options.totalElements}`, 'DEBUG');
try {
// Configuration fusionnée
const config = { ...DEFAULT_CONFIG, ...options };
// ✅ INITIALISATION TRACKER ANTI-RÉPÉTITION
const tracker = new HumanSimulationTracker();
logSh(`🧠 Tracker anti-répétition initialisé`, 'DEBUG');
// Stats de simulation
const simulationStats = {
elementsProcessed: 0,
fatigueModifications: 0,
personalityModifications: 0,
temporalModifications: 0,
spellingModifications: 0,
totalModifications: 0,
qualityScore: 0,
fallbackUsed: false
};
// Contenu simulé
let simulatedContent = { ...content };
// ========================================
// 1. ANALYSE CONTEXTE GLOBAL
// ========================================
const globalContext = await analyzeGlobalContext(content, config);
logSh(` 🔍 Contexte: fatigue=${globalContext.fatigueLevel.toFixed(2)}, heure=${globalContext.currentHour}h, personnalité=${globalContext.personalityName}`, 'DEBUG');
// ========================================
// 2. TRAITEMENT PAR ÉLÉMENT
// ========================================
for (const [elementKey, elementContent] of Object.entries(content)) {
await tracer.run(`HumanSimulation.processElement(${elementKey})`, async () => {
logSh(` 🎯 Traitement élément: ${elementKey}`, 'DEBUG');
let processedContent = elementContent;
let elementModifications = 0;
try {
// 2a. Simulation Fatigue Cognitive
if (config.fatigueEnabled && globalContext.fatigueLevel > 0.1) { // FIXÉ: Seuil plus bas (était 0.3)
const fatigueResult = await applyFatigueSimulation(processedContent, globalContext, config);
processedContent = fatigueResult.content;
elementModifications += fatigueResult.modifications;
simulationStats.fatigueModifications += fatigueResult.modifications;
logSh(` 💤 Fatigue: ${fatigueResult.modifications} modifications (niveau: ${globalContext.fatigueLevel.toFixed(2)})`, 'DEBUG');
}
// 2b. Erreurs Personnalité
if (config.personalityErrorsEnabled && globalContext.personalityProfile) {
const personalityResult = await applyPersonalitySimulation(processedContent, globalContext, config, tracker);
processedContent = personalityResult.content;
elementModifications += personalityResult.modifications;
simulationStats.personalityModifications += personalityResult.modifications;
logSh(` 🎭 Personnalité: ${personalityResult.modifications} erreurs injectées`, 'DEBUG');
}
// 2c. Style Temporel
if (config.temporalStyleEnabled && globalContext.temporalStyle) {
const temporalResult = await applyTemporalSimulation(processedContent, globalContext, config, tracker);
processedContent = temporalResult.content;
elementModifications += temporalResult.modifications;
simulationStats.temporalModifications += temporalResult.modifications;
logSh(` ⏰ Temporel: ${temporalResult.modifications} ajustements (${globalContext.temporalStyle.period})`, 'DEBUG');
}
// 2d. Erreurs Graduées Procédurales (NOUVEAU - grave 10% / moyenne 30% / légère 50%)
if (config.graduatedErrorsEnabled) {
const errorResult = selectAndApplyErrors(processedContent, {
currentHour: globalContext.currentHour,
tracker
});
processedContent = errorResult.content;
elementModifications += errorResult.errorsApplied;
simulationStats.graduatedErrors = (simulationStats.graduatedErrors || 0) + errorResult.errorsApplied;
if (errorResult.errorsApplied > 0) {
logSh(` 🎲 Erreurs graduées: ${errorResult.errorsApplied} (${errorResult.errorDetails.severity})`, 'DEBUG');
}
}
// 2e. Validation Qualité
const qualityCheck = validateSimulationQuality(elementContent, processedContent, config.qualityThreshold);
if (qualityCheck.acceptable) {
simulatedContent[elementKey] = processedContent;
simulationStats.elementsProcessed++;
simulationStats.totalModifications += elementModifications;
logSh(` ✅ Élément simulé: ${elementModifications} modifications totales`, 'DEBUG');
} else {
// Fallback: garder contenu original
simulatedContent[elementKey] = elementContent;
simulationStats.fallbackUsed = true;
logSh(` ⚠️ Qualité insuffisante, fallback vers contenu original`, 'WARNING');
}
} catch (elementError) {
logSh(` ❌ Erreur simulation élément ${elementKey}: ${elementError.message}`, 'WARNING');
simulatedContent[elementKey] = elementContent; // Fallback
simulationStats.fallbackUsed = true;
}
}, { elementKey, originalLength: elementContent?.length });
}
// ========================================
// 3. CALCUL SCORE QUALITÉ GLOBAL
// ========================================
simulationStats.qualityScore = calculateGlobalQualityScore(content, simulatedContent);
// ========================================
// 4. RÉSULTATS FINAUX
// ========================================
const duration = Date.now() - startTime;
const success = simulationStats.elementsProcessed > 0 && !simulationStats.fallbackUsed;
logSh(`🧠 HUMAN SIMULATION - Terminé (${duration}ms)`, 'INFO');
logSh(`${simulationStats.elementsProcessed}/${Object.keys(content).length} éléments simulés`, 'INFO');
logSh(` 📊 ${simulationStats.fatigueModifications} fatigue | ${simulationStats.personalityModifications} personnalité | ${simulationStats.temporalModifications} temporel | ${simulationStats.spellingModifications || 0} fautes`, 'INFO');
logSh(` 🎯 Score qualité: ${simulationStats.qualityScore.toFixed(2)} | Fallback: ${simulationStats.fallbackUsed ? 'OUI' : 'NON'}`, 'INFO');
await tracer.event('Human Simulation terminée', {
success,
duration,
stats: simulationStats
});
return {
content: simulatedContent,
stats: simulationStats,
modifications: simulationStats.totalModifications, // ✅ AJOUTÉ: Mapping pour PipelineExecutor
fallback: simulationStats.fallbackUsed,
qualityScore: simulationStats.qualityScore,
duration
};
} catch (error) {
const duration = Date.now() - startTime;
logSh(`❌ HUMAN SIMULATION ÉCHOUÉE (${duration}ms): ${error.message}`, 'ERROR');
await tracer.event('Human Simulation échouée', {
error: error.message,
duration,
contentKeys: Object.keys(content).length
});
// Fallback complet
return {
content,
stats: { fallbackUsed: true, error: error.message },
fallback: true,
qualityScore: 0,
duration
};
}
}, {
contentElements: Object.keys(content).length,
elementIndex: options.elementIndex,
personality: options.csvData?.personality?.nom
});
}
/**
* ANALYSE CONTEXTE GLOBAL
*/
async function analyzeGlobalContext(content, config) {
const elementIndex = config.elementIndex || 0;
const totalElements = config.totalElements || Object.keys(content).length;
const currentHour = config.currentHour || new Date().getHours();
const personality = config.csvData?.personality;
return {
fatigueLevel: calculateFatigue(elementIndex, totalElements),
fatigueProfile: personality ? getFatigueProfile(personality.nom) : null,
personalityName: personality?.nom || 'unknown',
personalityProfile: personality ? getPersonalityErrorPatterns(personality.nom) : null,
temporalStyle: getTemporalStyle(currentHour),
currentHour,
elementIndex,
totalElements,
contentComplexity: analyzeContentComplexity(content)
};
}
/**
* APPLICATION SIMULATION FATIGUE
*/
async function applyFatigueSimulation(content, globalContext, config) {
const fatigueResult = injectFatigueMarkers(content, globalContext.fatigueLevel, {
profile: globalContext.fatigueProfile,
intensity: config.imperfectionIntensity
});
return {
content: fatigueResult.content,
modifications: fatigueResult.modifications || 0
};
}
/**
* APPLICATION SIMULATION PERSONNALITÉ
*/
async function applyPersonalitySimulation(content, globalContext, config, tracker) {
const personalityResult = injectPersonalityErrors(
content,
globalContext.personalityProfile,
config.imperfectionIntensity,
tracker
);
return {
content: personalityResult.content,
modifications: personalityResult.modifications || 0
};
}
/**
* APPLICATION SIMULATION TEMPORELLE
*/
async function applyTemporalSimulation(content, globalContext, config, tracker) {
const temporalResult = applyTemporalStyle(content, globalContext.temporalStyle, {
intensity: config.imperfectionIntensity,
tracker
});
return {
content: temporalResult.content,
modifications: temporalResult.modifications || 0
};
}
/**
* CALCUL SCORE QUALITÉ GLOBAL
*/
function calculateGlobalQualityScore(originalContent, simulatedContent) {
let totalScore = 0;
let elementCount = 0;
for (const [key, original] of Object.entries(originalContent)) {
const simulated = simulatedContent[key];
if (simulated) {
const elementScore = calculateReadabilityScore(simulated) * 0.7 +
preserveKeywords(original, simulated) * 0.3;
totalScore += elementScore;
elementCount++;
}
}
return elementCount > 0 ? totalScore / elementCount : 0;
}
// ============= EXPORTS =============
module.exports = {
applyHumanSimulationLayer,
analyzeGlobalContext,
applyFatigueSimulation,
applyPersonalitySimulation,
applyTemporalSimulation,
calculateGlobalQualityScore,
DEFAULT_CONFIG
};