// ======================================== // SELECTIVE LAYERS - COUCHES COMPOSABLES // Responsabilité: Stacks prédéfinis et couches adaptatives pour selective enhancement // Architecture: Composable layers avec orchestration intelligente // ======================================== const { logSh } = require('../ErrorReporting'); const { tracer } = require('../trace'); const { applySelectiveLayer } = require('./SelectiveCore'); /** * STACKS PRÉDÉFINIS SELECTIVE ENHANCEMENT */ const PREDEFINED_STACKS = { // Stack léger - Amélioration technique uniquement lightEnhancement: { name: 'lightEnhancement', description: 'Amélioration technique légère avec GPT-4', layers: [ { type: 'technical', llm: 'gpt4', intensity: 0.7 } ], layersCount: 1 }, // Stack standard - Technique + Transitions standardEnhancement: { name: 'standardEnhancement', description: 'Amélioration technique et fluidité (GPT-4 + Gemini)', layers: [ { type: 'technical', llm: 'gpt4', intensity: 0.9 }, { type: 'transitions', llm: 'gemini', intensity: 0.8 } ], layersCount: 2 }, // Stack complet - Toutes couches séquentielles fullEnhancement: { name: 'fullEnhancement', description: 'Enhancement complet multi-LLM (GPT-4 + Gemini + Mistral)', layers: [ { type: 'technical', llm: 'gpt4', intensity: 1.0 }, { type: 'transitions', llm: 'gemini', intensity: 0.9 }, { type: 'style', llm: 'mistral', intensity: 0.8 } ], layersCount: 3 }, // Stack personnalité - Style prioritaire personalityFocus: { name: 'personalityFocus', description: 'Focus personnalité et style avec Mistral + technique légère', layers: [ { type: 'style', llm: 'mistral', intensity: 1.2 }, { type: 'technical', llm: 'gpt4', intensity: 0.6 } ], layersCount: 2 }, // Stack fluidité - Transitions prioritaires fluidityFocus: { name: 'fluidityFocus', description: 'Focus fluidité avec Gemini + enhancements légers', layers: [ { type: 'transitions', llm: 'gemini', intensity: 1.1 }, { type: 'technical', llm: 'gpt4', intensity: 0.7 }, { type: 'style', llm: 'mistral', intensity: 0.6 } ], layersCount: 3 } }; /** * APPLIQUER STACK PRÉDÉFINI */ async function applyPredefinedStack(content, stackName, config = {}) { return await tracer.run('SelectiveLayers.applyPredefinedStack()', async () => { const stack = PREDEFINED_STACKS[stackName]; if (!stack) { throw new Error(`Stack selective prédéfini inconnu: ${stackName}. Disponibles: ${Object.keys(PREDEFINED_STACKS).join(', ')}`); } await tracer.annotate({ selectivePredefinedStack: true, stackName, layersCount: stack.layersCount, elementsCount: Object.keys(content).length }); const startTime = Date.now(); logSh(`📦 APPLICATION STACK SELECTIVE: ${stack.name} (${stack.layersCount} couches)`, 'INFO'); logSh(` 📊 ${Object.keys(content).length} éléments | Description: ${stack.description}`, 'INFO'); try { let currentContent = content; const stackStats = { stackName, layers: [], totalModifications: 0, totalDuration: 0, success: true }; // Appliquer chaque couche séquentiellement for (let i = 0; i < stack.layers.length; i++) { const layer = stack.layers[i]; try { logSh(` 🔧 Couche ${i + 1}/${stack.layersCount}: ${layer.type} (${layer.llm})`, 'DEBUG'); const layerResult = await applySelectiveLayer(currentContent, { ...config, layerType: layer.type, llmProvider: layer.llm, intensity: layer.intensity, analysisMode: true }); currentContent = layerResult.content; stackStats.layers.push({ order: i + 1, type: layer.type, llm: layer.llm, intensity: layer.intensity, elementsEnhanced: layerResult.stats.elementsEnhanced, duration: layerResult.stats.duration, success: !layerResult.stats.fallback }); stackStats.totalModifications += layerResult.stats.elementsEnhanced; stackStats.totalDuration += layerResult.stats.duration; logSh(` ✅ Couche ${layer.type}: ${layerResult.stats.elementsEnhanced} améliorations`, 'DEBUG'); } catch (layerError) { logSh(` ❌ Couche ${layer.type} échouée: ${layerError.message}`, 'ERROR'); stackStats.layers.push({ order: i + 1, type: layer.type, llm: layer.llm, error: layerError.message, duration: 0, success: false }); // Continuer avec les autres couches } } const duration = Date.now() - startTime; const successfulLayers = stackStats.layers.filter(l => l.success).length; logSh(`✅ STACK SELECTIVE ${stackName}: ${successfulLayers}/${stack.layersCount} couches | ${stackStats.totalModifications} modifications (${duration}ms)`, 'INFO'); await tracer.event('Stack selective appliqué', { ...stackStats, totalDuration: duration }); return { content: currentContent, stats: { ...stackStats, totalDuration: duration }, original: content, stackApplied: stackName }; } catch (error) { const duration = Date.now() - startTime; logSh(`❌ STACK SELECTIVE ${stackName} ÉCHOUÉ après ${duration}ms: ${error.message}`, 'ERROR'); return { content, stats: { stackName, error: error.message, duration, success: false }, original: content, fallback: true }; } }, { content: Object.keys(content), stackName, config }); } /** * APPLIQUER COUCHES ADAPTATIVES */ async function applyAdaptiveLayers(content, config = {}) { return await tracer.run('SelectiveLayers.applyAdaptiveLayers()', async () => { const { maxIntensity = 1.0, analysisThreshold = 0.4, csvData = null } = config; await tracer.annotate({ selectiveAdaptiveLayers: true, maxIntensity, analysisThreshold, elementsCount: Object.keys(content).length }); const startTime = Date.now(); logSh(`🧠 APPLICATION COUCHES ADAPTATIVES SELECTIVE`, 'INFO'); logSh(` 📊 ${Object.keys(content).length} éléments | Seuil: ${analysisThreshold}`, 'INFO'); try { // 1. Analyser besoins de chaque type de couche const needsAnalysis = await analyzeSelectiveNeeds(content, csvData); logSh(` 📋 Analyse besoins: Tech=${needsAnalysis.technical.score.toFixed(2)} | Trans=${needsAnalysis.transitions.score.toFixed(2)} | Style=${needsAnalysis.style.score.toFixed(2)}`, 'DEBUG'); // 2. Déterminer couches à appliquer selon scores const layersToApply = []; if (needsAnalysis.technical.needed && needsAnalysis.technical.score > analysisThreshold) { layersToApply.push({ type: 'technical', llm: 'gpt4', intensity: Math.min(maxIntensity, needsAnalysis.technical.score * 1.2), priority: 1 }); } if (needsAnalysis.transitions.needed && needsAnalysis.transitions.score > analysisThreshold) { layersToApply.push({ type: 'transitions', llm: 'gemini', intensity: Math.min(maxIntensity, needsAnalysis.transitions.score * 1.1), priority: 2 }); } if (needsAnalysis.style.needed && needsAnalysis.style.score > analysisThreshold) { layersToApply.push({ type: 'style', llm: 'mistral', intensity: Math.min(maxIntensity, needsAnalysis.style.score), priority: 3 }); } if (layersToApply.length === 0) { logSh(`✅ COUCHES ADAPTATIVES: Aucune amélioration nécessaire`, 'INFO'); return { content, stats: { adaptive: true, layersApplied: 0, analysisOnly: true, duration: Date.now() - startTime } }; } // 3. Appliquer couches par ordre de priorité layersToApply.sort((a, b) => a.priority - b.priority); logSh(` 🎯 Couches sélectionnées: ${layersToApply.map(l => `${l.type}(${l.intensity.toFixed(1)})`).join(' → ')}`, 'INFO'); let currentContent = content; const adaptiveStats = { layersAnalyzed: 3, layersApplied: layersToApply.length, layers: [], totalModifications: 0, adaptive: true }; for (const layer of layersToApply) { try { logSh(` 🔧 Couche adaptative: ${layer.type} (intensité: ${layer.intensity.toFixed(1)})`, 'DEBUG'); const layerResult = await applySelectiveLayer(currentContent, { ...config, layerType: layer.type, llmProvider: layer.llm, intensity: layer.intensity, analysisMode: true }); currentContent = layerResult.content; adaptiveStats.layers.push({ type: layer.type, llm: layer.llm, intensity: layer.intensity, elementsEnhanced: layerResult.stats.elementsEnhanced, duration: layerResult.stats.duration, success: !layerResult.stats.fallback }); adaptiveStats.totalModifications += layerResult.stats.elementsEnhanced; } catch (layerError) { logSh(` ❌ Couche adaptative ${layer.type} échouée: ${layerError.message}`, 'ERROR'); adaptiveStats.layers.push({ type: layer.type, error: layerError.message, success: false }); } } const duration = Date.now() - startTime; const successfulLayers = adaptiveStats.layers.filter(l => l.success).length; logSh(`✅ COUCHES ADAPTATIVES: ${successfulLayers}/${layersToApply.length} appliquées | ${adaptiveStats.totalModifications} modifications (${duration}ms)`, 'INFO'); await tracer.event('Couches adaptatives appliquées', { ...adaptiveStats, totalDuration: duration }); return { content: currentContent, stats: { ...adaptiveStats, totalDuration: duration }, original: content }; } catch (error) { const duration = Date.now() - startTime; logSh(`❌ COUCHES ADAPTATIVES ÉCHOUÉES après ${duration}ms: ${error.message}`, 'ERROR'); return { content, stats: { adaptive: true, error: error.message, duration }, original: content, fallback: true }; } }, { content: Object.keys(content), config }); } /** * PIPELINE COUCHES PERSONNALISÉ */ async function applyLayerPipeline(content, layerSequence, config = {}) { return await tracer.run('SelectiveLayers.applyLayerPipeline()', async () => { if (!Array.isArray(layerSequence) || layerSequence.length === 0) { throw new Error('Séquence de couches invalide ou vide'); } await tracer.annotate({ selectiveLayerPipeline: true, pipelineLength: layerSequence.length, elementsCount: Object.keys(content).length }); const startTime = Date.now(); logSh(`🔄 PIPELINE COUCHES SELECTIVE PERSONNALISÉ: ${layerSequence.length} étapes`, 'INFO'); try { let currentContent = content; const pipelineStats = { pipelineLength: layerSequence.length, steps: [], totalModifications: 0, success: true }; for (let i = 0; i < layerSequence.length; i++) { const step = layerSequence[i]; try { logSh(` 📍 Étape ${i + 1}/${layerSequence.length}: ${step.type} (${step.llm || 'auto'})`, 'DEBUG'); const stepResult = await applySelectiveLayer(currentContent, { ...config, ...step }); currentContent = stepResult.content; pipelineStats.steps.push({ order: i + 1, ...step, elementsEnhanced: stepResult.stats.elementsEnhanced, duration: stepResult.stats.duration, success: !stepResult.stats.fallback }); pipelineStats.totalModifications += stepResult.stats.elementsEnhanced; } catch (stepError) { logSh(` ❌ Étape ${i + 1} échouée: ${stepError.message}`, 'ERROR'); pipelineStats.steps.push({ order: i + 1, ...step, error: stepError.message, success: false }); } } const duration = Date.now() - startTime; const successfulSteps = pipelineStats.steps.filter(s => s.success).length; logSh(`✅ PIPELINE SELECTIVE: ${successfulSteps}/${layerSequence.length} étapes | ${pipelineStats.totalModifications} modifications (${duration}ms)`, 'INFO'); await tracer.event('Pipeline selective appliqué', { ...pipelineStats, totalDuration: duration }); return { content: currentContent, stats: { ...pipelineStats, totalDuration: duration }, original: content }; } catch (error) { const duration = Date.now() - startTime; logSh(`❌ PIPELINE SELECTIVE ÉCHOUÉ après ${duration}ms: ${error.message}`, 'ERROR'); return { content, stats: { error: error.message, duration, success: false }, original: content, fallback: true }; } }, { content: Object.keys(content), layerSequence, config }); } // ============= HELPER FUNCTIONS ============= /** * Analyser besoins selective enhancement */ async function analyzeSelectiveNeeds(content, csvData) { const analysis = { technical: { needed: false, score: 0, elements: [] }, transitions: { needed: false, score: 0, elements: [] }, style: { needed: false, score: 0, elements: [] } }; // Analyser chaque élément pour tous types de besoins Object.entries(content).forEach(([tag, text]) => { // Analyse technique (import depuis SelectiveCore logic) const technicalNeed = assessTechnicalNeed(text, csvData); if (technicalNeed.score > 0.3) { analysis.technical.needed = true; analysis.technical.score += technicalNeed.score; analysis.technical.elements.push({ tag, score: technicalNeed.score }); } // Analyse transitions const transitionNeed = assessTransitionNeed(text); if (transitionNeed.score > 0.3) { analysis.transitions.needed = true; analysis.transitions.score += transitionNeed.score; analysis.transitions.elements.push({ tag, score: transitionNeed.score }); } // Analyse style const styleNeed = assessStyleNeed(text, csvData?.personality); if (styleNeed.score > 0.3) { analysis.style.needed = true; analysis.style.score += styleNeed.score; analysis.style.elements.push({ tag, score: styleNeed.score }); } }); // Normaliser scores const elementCount = Object.keys(content).length; analysis.technical.score = analysis.technical.score / elementCount; analysis.transitions.score = analysis.transitions.score / elementCount; analysis.style.score = analysis.style.score / elementCount; return analysis; } /** * Évaluer besoin technique (simplifié de SelectiveCore) */ function assessTechnicalNeed(content, csvData) { let score = 0; // Manque de termes techniques spécifiques if (csvData?.mc0) { const technicalTerms = ['dibond', 'pmma', 'aluminium', 'fraisage', 'impression', 'gravure']; const foundTerms = technicalTerms.filter(term => content.toLowerCase().includes(term)); if (foundTerms.length === 0 && content.length > 100) { score += 0.4; } } // Vocabulaire générique const genericWords = ['produit', 'solution', 'service', 'qualité']; const genericCount = genericWords.filter(word => content.toLowerCase().includes(word)).length; if (genericCount > 2) score += 0.3; return { score: Math.min(1, score) }; } /** * Évaluer besoin transitions (simplifié) */ function assessTransitionNeed(content) { const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 10); if (sentences.length < 2) return { score: 0 }; let score = 0; // Connecteurs répétitifs const connectors = ['par ailleurs', 'en effet', 'de plus']; let repetitions = 0; connectors.forEach(connector => { const matches = (content.match(new RegExp(connector, 'gi')) || []); if (matches.length > 1) repetitions++; }); if (repetitions > 1) score += 0.4; return { score: Math.min(1, score) }; } /** * Évaluer besoin style (simplifié) */ function assessStyleNeed(content, personality) { let score = 0; if (!personality) { score += 0.2; return { score }; } // Style générique const personalityWords = (personality.vocabulairePref || '').toLowerCase().split(','); const personalityFound = personalityWords.some(word => word.trim() && content.toLowerCase().includes(word.trim()) ); if (!personalityFound && content.length > 50) score += 0.4; return { score: Math.min(1, score) }; } /** * Obtenir stacks disponibles */ function getAvailableStacks() { return Object.values(PREDEFINED_STACKS); } module.exports = { // Main functions applyPredefinedStack, applyAdaptiveLayers, applyLayerPipeline, // Utils getAvailableStacks, analyzeSelectiveNeeds, // Constants PREDEFINED_STACKS };