seo-generator-server/lib/selective-smart-touch/SmartTouchCore.js
StillHammer 0244521f5c feat(selective-smart-touch): Add intelligent analysis-driven enhancement system + validation spec
## SelectiveSmartTouch (NEW)
- Architecture révolutionnaire: Analyse intelligente → Améliorations ciblées précises
- 5 modules: SmartAnalysisLayer, SmartTechnicalLayer, SmartStyleLayer, SmartReadabilityLayer, SmartTouchCore
- Système 10% segments: amélioration uniquement des segments les plus faibles (intensity-based)
- Détection contexte globale pour prompts adaptatifs multi-secteurs
- Intégration complète dans PipelineExecutor et PipelineDefinition

## Pipeline Validator Spec (NEW)
- Spécification complète système validation qualité par LLM
- 5 critères universels: Qualité, Verbosité, SEO, Répétitions, Naturalité
- Échantillonnage intelligent par filtrage balises (pas XML)
- Évaluation multi-versions avec justifications détaillées
- Coût estimé: ~$1/validation (260 appels LLM)

## Optimizations
- Réduction intensités fullEnhancement (technical 1.0→0.7, style 0.8→0.5)
- Ajout gardes-fous anti-familiarité excessive dans StyleLayer
- Sauvegarde étapes intermédiaires activée par défaut (pipeline-runner)

## Fixes
- Fix typo critique SmartTouchCore.js:110 (determineLayers ToApply → determineLayersToApply)
- Prompts généralisés multi-secteurs (e-commerce, SaaS, services, informatif)

🚀 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-13 15:01:02 +08:00

380 lines
14 KiB
JavaScript

// ========================================
// SMART TOUCH CORE - Orchestrateur SelectiveSmartTouch
// Responsabilité: Orchestration complète Analyse → Améliorations ciblées
// Architecture: Analyse intelligente PUIS améliorations précises (contrôle total)
// ========================================
const { logSh } = require('../ErrorReporting');
const { tracer } = require('../trace');
const { SmartAnalysisLayer } = require('./SmartAnalysisLayer');
const { SmartTechnicalLayer } = require('./SmartTechnicalLayer');
const { SmartStyleLayer } = require('./SmartStyleLayer');
const { SmartReadabilityLayer } = require('./SmartReadabilityLayer');
/**
* SMART TOUCH CORE
* Orchestrateur principal: Analyse → Technical → Style → Readability (ciblé)
*/
class SmartTouchCore {
constructor() {
this.name = 'SelectiveSmartTouch';
// Instancier les layers
this.analysisLayer = new SmartAnalysisLayer();
this.technicalLayer = new SmartTechnicalLayer();
this.styleLayer = new SmartStyleLayer();
this.readabilityLayer = new SmartReadabilityLayer();
}
/**
* APPLIQUER SMART TOUCH COMPLET
* @param {object} content - Map {tag: texte}
* @param {object} config - Configuration
* @returns {object} - Résultat avec stats détaillées
*/
async apply(content, config = {}) {
return await tracer.run('SmartTouchCore.apply()', async () => {
const {
mode = 'full', // 'analysis_only', 'technical_only', 'style_only', 'readability_only', 'full'
intensity = 1.0,
csvData = null,
llmProvider = 'gpt-4o-mini', // ✅ LLM à utiliser (extrait du pipeline config)
skipAnalysis = false, // Si true, applique sans analyser (mode legacy)
layersOrder = ['technical', 'style', 'readability'] // Ordre d'application personnalisable
} = config;
await tracer.annotate({
selectiveSmartTouch: true,
mode,
intensity,
elementsCount: Object.keys(content).length,
personality: csvData?.personality?.nom
});
const startTime = Date.now();
logSh(`🧠 SELECTIVE SMART TOUCH START: ${Object.keys(content).length} éléments | Mode: ${mode}`, 'INFO');
try {
let currentContent = { ...content };
const stats = {
mode,
analysisResults: {},
layersApplied: [],
totalModifications: 0,
elementsProcessed: Object.keys(content).length,
elementsImproved: 0,
duration: 0
};
// ========================================
// PHASE 1: ANALYSE INTELLIGENTE
// ========================================
if (!skipAnalysis) {
logSh(`\n📊 === PHASE 1: ANALYSE INTELLIGENTE ===`, 'INFO');
const analysisResults = await this.analysisLayer.analyzeBatch(currentContent, {
mc0: csvData?.mc0,
personality: csvData?.personality,
llmProvider // ✅ Passer LLM à l'analyse batch
});
stats.analysisResults = analysisResults;
// Résumer analyse
const summary = this.analysisLayer.summarizeBatchAnalysis(analysisResults);
logSh(` 📋 Résumé analyse: ${summary.needsImprovement}/${summary.totalElements} éléments nécessitent amélioration`, 'INFO');
logSh(` 📊 Score moyen: ${summary.averageScore.toFixed(2)} | Améliorations totales: ${summary.totalImprovements}`, 'INFO');
logSh(` 🎯 Besoins: Technical=${summary.commonIssues.technical} | Style=${summary.commonIssues.style} | Readability=${summary.commonIssues.readability}`, 'INFO');
// Si mode analysis_only, retourner ici
if (mode === 'analysis_only') {
const duration = Date.now() - startTime;
logSh(`✅ SELECTIVE SMART TOUCH (ANALYSIS ONLY) terminé: ${duration}ms`, 'INFO');
return {
content: currentContent,
stats: {
...stats,
duration,
analysisOnly: true
},
modifications: 0,
analysisResults
};
}
// ========================================
// PHASE 2: AMÉLIORATIONS CIBLÉES
// ========================================
logSh(`\n🔧 === PHASE 2: AMÉLIORATIONS CIBLÉES ===`, 'INFO');
// Déterminer quelles couches appliquer
const layersToApply = this.determineLayersToApply(mode, layersOrder);
// === DÉTECTION CONTEXTE GLOBALE (1 seule fois) ===
const contentContext = this.analysisLayer.detectContentContext(
Object.values(currentContent).join(' '),
csvData?.personality
);
for (const layerName of layersToApply) {
const layerStartTime = Date.now();
logSh(`\n 🎯 Couche: ${layerName}`, 'INFO');
let layerModifications = 0;
const layerResults = {};
// Appliquer la couche sur chaque élément
for (const [tag, text] of Object.entries(currentContent)) {
const analysis = analysisResults[tag];
if (!analysis) continue;
try {
// === SYSTÈME 10% SEGMENTS ===
// Calculer pourcentage de texte à améliorer selon intensity
// intensity 1.0 = 10%, 0.5 = 5%, 1.5 = 15%
const percentageToImprove = intensity * 0.1;
// Analyser par segments pour identifier les plus faibles
const segments = this.analysisLayer.analyzeBySegments(text, {
mc0: csvData?.mc0,
personality: csvData?.personality
});
// Sélectionner les X% segments les plus faibles
const weakestSegments = this.analysisLayer.selectWeakestSegments(
segments,
percentageToImprove
);
logSh(` 📊 [${tag}] ${segments.length} segments, ${weakestSegments.length} sélectionnés (${(percentageToImprove * 100).toFixed(0)}%)`, 'DEBUG');
// Appliquer amélioration UNIQUEMENT sur segments sélectionnés
const result = await this.applyLayerToSegments(
layerName,
segments,
weakestSegments,
analysis,
{
mc0: csvData?.mc0,
personality: csvData?.personality,
intensity,
contentContext, // Passer contexte aux layers
llmProvider // ✅ Passer LLM choisi dans pipeline
}
);
if (!result.skipped && result.content !== text) {
currentContent[tag] = result.content;
layerModifications += result.modifications || 0;
stats.elementsImproved++;
}
layerResults[tag] = result;
} catch (error) {
logSh(` ❌ [${tag}] Échec ${layerName}: ${error.message}`, 'ERROR');
}
}
const layerDuration = Date.now() - layerStartTime;
stats.layersApplied.push({
name: layerName,
modifications: layerModifications,
duration: layerDuration
});
stats.totalModifications += layerModifications;
logSh(`${layerName} terminé: ${layerModifications} modifications (${layerDuration}ms)`, 'INFO');
}
} else {
// Mode skipAnalysis: appliquer sans analyse (legacy fallback)
logSh(`⚠️ Mode skipAnalysis activé: application directe sans analyse préalable`, 'WARNING');
// TODO: Implémenter mode legacy si nécessaire
logSh(`❌ Mode skipAnalysis non implémenté pour SmartTouch (requiert analyse)`, 'ERROR');
}
// ========================================
// RÉSULTATS FINAUX
// ========================================
const duration = Date.now() - startTime;
stats.duration = duration;
logSh(`\n✅ === SELECTIVE SMART TOUCH TERMINÉ ===`, 'INFO');
logSh(` 📊 ${stats.elementsImproved}/${stats.elementsProcessed} éléments améliorés`, 'INFO');
logSh(` 🔄 ${stats.totalModifications} modifications totales`, 'INFO');
logSh(` ⏱️ Durée: ${duration}ms`, 'INFO');
logSh(` 🎯 Couches appliquées: ${stats.layersApplied.map(l => l.name).join(' → ')}`, 'INFO');
await tracer.event('SelectiveSmartTouch terminé', stats);
return {
content: currentContent,
stats,
modifications: stats.totalModifications,
analysisResults: stats.analysisResults
};
} catch (error) {
const duration = Date.now() - startTime;
logSh(`❌ SELECTIVE SMART TOUCH ÉCHOUÉ après ${duration}ms: ${error.message}`, 'ERROR');
return {
content, // Fallback: contenu original
stats: {
error: error.message,
duration,
fallback: true
},
modifications: 0,
fallback: true
};
}
}, { content: Object.keys(content), config });
}
/**
* DÉTERMINER COUCHES À APPLIQUER
*/
determineLayersToApply(mode, layersOrder) {
switch (mode) {
case 'technical_only':
return ['technical'];
case 'style_only':
return ['style'];
case 'readability_only':
return ['readability'];
case 'full':
default:
return layersOrder; // Ordre personnalisable
}
}
/**
* APPLIQUER UNE COUCHE SPÉCIFIQUE
*/
async applyLayer(layerName, content, analysis, context) {
switch (layerName) {
case 'technical':
return await this.technicalLayer.applyTargeted(content, analysis, context);
case 'style':
return await this.styleLayer.applyTargeted(content, analysis, context);
case 'readability':
return await this.readabilityLayer.applyTargeted(content, analysis, context);
default:
throw new Error(`Couche inconnue: ${layerName}`);
}
}
/**
* APPLIQUER COUCHE SUR SEGMENTS SÉLECTIONNÉS UNIQUEMENT (10% système)
* @param {string} layerName - Nom de la couche
* @param {array} allSegments - Tous les segments du texte
* @param {array} weakestSegments - Segments sélectionnés à améliorer
* @param {object} analysis - Analyse globale
* @param {object} context - Contexte
* @returns {object} - { content: texte réassemblé, modifications, ... }
*/
async applyLayerToSegments(layerName, allSegments, weakestSegments, analysis, context) {
// Si aucun segment à améliorer, retourner texte original
if (weakestSegments.length === 0) {
const originalContent = allSegments.map(s => s.content).join(' ');
return {
content: originalContent,
modifications: 0,
skipped: true,
reason: 'No weak segments identified'
};
}
// Créer Map des indices des segments à améliorer pour lookup rapide
const weakIndices = new Set(weakestSegments.map(s => s.index));
// === AMÉLIORER UNIQUEMENT LES SEGMENTS FAIBLES ===
const improvedSegments = [];
let totalModifications = 0;
for (const segment of allSegments) {
if (weakIndices.has(segment.index)) {
// AMÉLIORER ce segment
try {
const result = await this.applyLayer(layerName, segment.content, analysis, context);
improvedSegments.push({
...segment,
content: result.skipped ? segment.content : result.content,
improved: !result.skipped
});
totalModifications += result.modifications || 0;
} catch (error) {
logSh(` ⚠️ Échec amélioration segment ${segment.index}: ${error.message}`, 'WARN');
// Fallback: garder segment original
improvedSegments.push({ ...segment, improved: false });
}
} else {
// GARDER segment intact
improvedSegments.push({ ...segment, improved: false });
}
}
// === RÉASSEMBLER TEXTE COMPLET ===
const reassembledContent = improvedSegments.map(s => s.content).join(' ');
// Nettoyer espaces multiples
const cleanedContent = reassembledContent.replace(/\s{2,}/g, ' ').trim();
const improvedCount = improvedSegments.filter(s => s.improved).length;
logSh(`${improvedCount}/${allSegments.length} segments améliorés (${totalModifications} modifs)`, 'DEBUG');
return {
content: cleanedContent,
modifications: totalModifications,
segmentsImproved: improvedCount,
segmentsTotal: allSegments.length,
skipped: false
};
}
/**
* MODES DISPONIBLES
*/
static getAvailableModes() {
return [
{
name: 'analysis_only',
description: 'Analyse uniquement (sans amélioration)',
layers: []
},
{
name: 'technical_only',
description: 'Améliorations techniques ciblées uniquement',
layers: ['technical']
},
{
name: 'style_only',
description: 'Améliorations style ciblées uniquement',
layers: ['style']
},
{
name: 'readability_only',
description: 'Améliorations lisibilité ciblées uniquement',
layers: ['readability']
},
{
name: 'full',
description: 'Analyse + toutes améliorations ciblées (recommandé)',
layers: ['technical', 'style', 'readability']
}
];
}
}
module.exports = { SmartTouchCore };