seo-generator-server/lib/selective-smart-touch/SmartTechnicalLayer.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

292 lines
11 KiB
JavaScript

// ========================================
// SMART TECHNICAL LAYER - Améliorations techniques CIBLÉES
// Responsabilité: Appliquer UNIQUEMENT les améliorations techniques identifiées par analyse
// LLM: GPT-4o-mini (précision technique)
// Architecture: Phase 2 de SelectiveSmartTouch (post-analyse)
// ========================================
const { callLLM } = require('../LLMManager');
const { logSh } = require('../ErrorReporting');
const { tracer } = require('../trace');
/**
* SMART TECHNICAL LAYER
* Applique améliorations techniques précises identifiées par SmartAnalysisLayer
*/
class SmartTechnicalLayer {
constructor() {
this.name = 'SmartTechnical';
this.defaultLLM = 'gpt-4o-mini';
}
/**
* APPLIQUER AMÉLIORATIONS TECHNIQUES CIBLÉES
* @param {string} content - Contenu original
* @param {object} analysis - Analyse de SmartAnalysisLayer
* @param {object} context - Contexte (mc0, personality, intensity)
* @returns {object} - { content, modifications }
*/
async applyTargeted(content, analysis, context = {}) {
return await tracer.run('SmartTechnical.applyTargeted()', async () => {
const { mc0, personality, intensity = 1.0, contentContext } = context;
// Si aucune amélioration technique nécessaire, skip
if (!analysis.technical.needed || analysis.improvements.length === 0) {
logSh(`⏭️ SMART TECHNICAL: Aucune amélioration nécessaire (score: ${analysis.technical.score.toFixed(2)})`, 'DEBUG');
return {
content,
modifications: 0,
skipped: true,
reason: 'No technical improvements needed'
};
}
// === GARDE-FOU 1: Détection contenu déjà trop technique ===
if (contentContext?.techLevel === 'too_high') {
logSh(`🛡️ GARDE-FOU: Contenu déjà trop technique (level: ${contentContext.techLevel}), SKIP amélioration`, 'WARN');
return {
content,
modifications: 0,
skipped: true,
reason: 'Content already too technical - avoided over-engineering'
};
}
// === GARDE-FOU 2: Comptage specs techniques existantes ===
const existingSpecs = (content.match(/\d+\s*(mm|cm|kg|°C|%|watt|lumen|J\/cm²|K⁻¹)/g) || []).length;
const existingNorms = (content.match(/(ISO|ASTM|EN\s|DIN|norme)/gi) || []).length;
if (existingSpecs > 6 || existingNorms > 3) {
logSh(`🛡️ GARDE-FOU: Trop de specs existantes (${existingSpecs} specs, ${existingNorms} normes), SKIP pour éviter surcharge`, 'WARN');
return {
content,
modifications: 0,
skipped: true,
reason: `Specs overload (${existingSpecs} specs, ${existingNorms} norms) - avoided adding more`
};
}
// === GARDE-FOU 3: Si B2C + niveau high, limiter portée ===
if (contentContext?.audience === 'B2C' && contentContext.techLevel === 'high') {
logSh(`🛡️ GARDE-FOU: B2C + niveau technique déjà high, limitation de la portée`, 'INFO');
// Réduire nombre d'améliorations à appliquer
analysis.improvements = analysis.improvements.slice(0, 2); // Max 2 améliorations
}
await tracer.annotate({
smartTechnical: true,
contentLength: content.length,
improvementsCount: analysis.improvements.length,
intensity,
guardrailsApplied: true,
existingSpecs,
existingNorms
});
// ✅ Utiliser LLM fourni dans context, sinon fallback sur defaultLLM
const llmToUse = context.llmProvider || this.defaultLLM;
const startTime = Date.now();
logSh(`🔧 SMART TECHNICAL: Application de ${analysis.improvements.length} améliorations ciblées avec ${llmToUse}`, 'DEBUG');
try {
const prompt = this.createTargetedPrompt(content, analysis, context);
const response = await callLLM(llmToUse, prompt, {
temperature: 0.4, // Précision technique
maxTokens: 2500
}, personality);
const improvedContent = this.cleanResponse(response);
// Calculer nombre de modifications
const modifications = this.countModifications(content, improvedContent);
const duration = Date.now() - startTime;
logSh(`✅ SMART TECHNICAL terminé: ${modifications} modifications appliquées (${duration}ms)`, 'DEBUG');
await tracer.event('Smart Technical appliqué', {
duration,
modifications,
improvementsRequested: analysis.improvements.length
});
return {
content: improvedContent,
modifications,
duration,
improvementsApplied: analysis.improvements
};
} catch (error) {
const duration = Date.now() - startTime;
logSh(`❌ SMART TECHNICAL ÉCHOUÉ (${duration}ms): ${error.message}`, 'ERROR');
return {
content, // Fallback: contenu original
modifications: 0,
error: error.message,
fallback: true
};
}
}, { contentLength: content.length, analysis });
}
/**
* CRÉER PROMPT CIBLÉ (instructions précises, exemples génériques)
*/
createTargetedPrompt(content, analysis, context) {
const { mc0, personality, intensity = 1.0, contentContext } = context;
// Extraire uniquement les améliorations techniques de la liste globale
const technicalImprovements = analysis.improvements.filter(imp =>
imp.toLowerCase().includes('technique') ||
imp.toLowerCase().includes('données') ||
imp.toLowerCase().includes('chiffr') ||
imp.toLowerCase().includes('précision') ||
imp.toLowerCase().includes('spécif') ||
analysis.technical.missing.some(missing => imp.includes(missing))
);
// === ADAPTER PROMPT SELON CONTEXTE (B2C vs B2B) ===
const isB2C = contentContext?.audience === 'B2C';
const isTechnicalContent = contentContext?.contentType === 'technical';
let technicalGuidelines = '';
if (isB2C && !isTechnicalContent) {
technicalGuidelines = `
⚠️ CONTEXTE: Contenu B2C grand public - SIMPLICITÉ MAXIMALE
- Ajoute UNIQUEMENT 2-3 spécifications SIMPLES et UTILES pour un client
- ÉVITE absolument: normes ISO/ASTM/EN, coefficients techniques, jargon industriel
- PRIVILÉGIE: dimensions pratiques, matériaux compréhensibles, bénéfices concrets
- INTERDICTION: termes comme "coefficient", "résistance à la corrosion", "norme", "conformité"
- MAX 1-2 données chiffrées pertinentes (ex: taille, poids, durée)`;
} else if (isTechnicalContent) {
technicalGuidelines = `
📋 CONTEXTE: Contenu technique - Précision acceptable
- Ajoute spécifications techniques précises si nécessaire
- Normes et standards acceptables si pertinents
- Garde équilibre entre précision et lisibilité`;
} else {
technicalGuidelines = `
🎯 CONTEXTE: Contenu standard - Équilibre
- Ajoute 2-4 spécifications pertinentes
- Évite jargon technique excessif
- Privilégie clarté et accessibilité`;
}
return `MISSION: Améliore UNIQUEMENT les aspects techniques PRÉCIS listés ci-dessous.
CONTENU ORIGINAL:
"${content}"
${mc0 ? `CONTEXTE SUJET: ${mc0}` : ''}
${personality ? `PERSONNALITÉ: ${personality.nom} (${personality.style})` : ''}
INTENSITÉ: ${intensity.toFixed(1)} (0.5=léger, 1.0=standard, 1.5=intensif)
${technicalGuidelines}
AMÉLIORATIONS TECHNIQUES À APPLIQUER:
${technicalImprovements.map((imp, i) => `${i + 1}. ${imp}`).join('\n')}
${analysis.technical.missing.length > 0 ? `
ÉLÉMENTS MANQUANTS IDENTIFIÉS:
${analysis.technical.missing.map((item, i) => `- ${item}`).join('\n')}
` : ''}
CONSIGNES STRICTES:
⚠️ RÈGLE ABSOLUE: REMPLACE la phrase originale, N'AJOUTE PAS de texte après
- Reformule la phrase en intégrant les améliorations techniques
- NE CHANGE PAS le ton, style ou structure générale
- NE TOUCHE PAS aux aspects non mentionnés
- Garde la même longueur approximative (±20%, PAS +100%)
- ${isB2C ? 'PRIORITÉ ABSOLUE: Reste SIMPLE et ACCESSIBLE' : 'Reste ACCESSIBLE - pas de jargon excessif'}
- ⚠️ Si la phrase est une question, garde-la sous forme de question (réponds DANS la question)
EXEMPLES REMPLACER vs AJOUTER:
✅ BON (REMPLACER):
AVANT: "Est-ce que les plaques sont résistantes aux intempéries ?"
APRÈS: "Les plaques en aluminium résistent-elles aux intempéries (-20°C à 50°C) ?"
→ Phrase REMPLACÉE, même longueur, garde format question
❌ MAUVAIS (AJOUTER):
AVANT: "Est-ce que les plaques sont résistantes aux intempéries ?"
APRÈS: "Est-ce que les plaques sont résistantes aux intempéries ? Les plaques sont en aluminium..."
→ Texte AJOUTÉ après = INTERDIT
EXEMPLES D'AMÉLIORATION TECHNIQUE (génériques):
${isB2C ? `
- ✅ BON (B2C): "Dimensions: 30x20cm, épaisseur 3mm" → clair et utile
- ❌ MAUVAIS (B2C): "Dimensions: 30x20cm, épaisseur 3mm, résistance 1,5 J/cm² (norme EN 12354-2)" → trop technique
- ✅ BON (B2C): "Délai de livraison: 3-5 jours" → simple
- ❌ MAUVAIS (B2C): "Conformité ISO 9001, délai d'expédition optimisé selon norme" → jargon inutile` : `
- ✅ BON: "Dimensions: 30x20cm, épaisseur 3mm" → données concrètes
- ❌ MAUVAIS: "Produit de qualité aux dimensions optimales" → vague
- ✅ BON: "Délai de livraison: 3-5 jours ouvrés" → précis
- ❌ MAUVAIS: "Livraison rapide" → imprécis`}
- ✅ BON: "Compatible avec 95% des systèmes" → chiffre concret
- ❌ MAUVAIS: "Très compatible" → vague
RÈGLES VOCABULAIRE TECHNIQUE:
- Privilégie clarté sur technicité excessive
- ${isB2C ? 'MAX 1-2 détails techniques SIMPLES' : '1-2 détails techniques pertinents par paragraphe MAX'}
- Évite le jargon pompeux inutile
- Vocabulaire accessible ${isB2C ? 'au GRAND PUBLIC' : 'à un public large'}
FORMAT RÉPONSE:
Retourne UNIQUEMENT le contenu amélioré, SANS balises, SANS métadonnées, SANS explications.`;
}
/**
* NETTOYER RÉPONSE LLM
*/
cleanResponse(response) {
if (!response) return response;
let cleaned = response.trim();
// Supprimer balises et préfixes indésirables
cleaned = cleaned.replace(/^TAG:\s*[^\s]+\s+/gi, '');
cleaned = cleaned.replace(/\bTAG:\s*[^\s]+\s+/gi, '');
cleaned = cleaned.replace(/^CONTENU:\s*/gi, '');
cleaned = cleaned.replace(/^CONTENU AMÉLIORÉ:\s*/gi, '');
cleaned = cleaned.replace(/^(voici\s+)?le\s+contenu\s+amélioré\s*[:.]?\s*/gi, '');
cleaned = cleaned.replace(/^(avec\s+)?amélioration[s]?\s+technique[s]?\s*[:.]?\s*/gi, '');
// Nettoyer formatage markdown
cleaned = cleaned.replace(/\*\*([^*]+)\*\*/g, '$1'); // **texte** → texte
cleaned = cleaned.replace(/\s{2,}/g, ' '); // Espaces multiples
cleaned = cleaned.trim();
return cleaned;
}
/**
* COMPTER MODIFICATIONS (comparaison contenu original vs amélioré)
*/
countModifications(original, improved) {
if (original === improved) return 0;
// Méthode simple: compter mots différents
const originalWords = original.toLowerCase().split(/\s+/);
const improvedWords = improved.toLowerCase().split(/\s+/);
let differences = 0;
// Compter ajouts/suppressions
differences += Math.abs(originalWords.length - improvedWords.length);
// Compter modifications (mots communs)
const minLength = Math.min(originalWords.length, improvedWords.length);
for (let i = 0; i < minLength; i++) {
if (originalWords[i] !== improvedWords[i]) {
differences++;
}
}
return differences;
}
}
module.exports = { SmartTechnicalLayer };