// ======================================== // SELECTIVE CORE - MOTEUR MODULAIRE // Responsabilité: Moteur selective enhancement réutilisable sur tout contenu // Architecture: Couches applicables à la demande // ======================================== const { logSh } = require('../ErrorReporting'); const { tracer } = require('../trace'); /** * MAIN ENTRY POINT - APPLICATION COUCHE SELECTIVE ENHANCEMENT * Input: contenu existant + configuration selective * Output: contenu avec couche selective appliquée */ async function applySelectiveLayer(existingContent, config = {}) { return await tracer.run('SelectiveCore.applySelectiveLayer()', async () => { const { layerType = 'technical', // 'technical' | 'transitions' | 'style' | 'all' llmProvider = 'auto', // 'claude' | 'gpt4' | 'gemini' | 'mistral' | 'auto' analysisMode = true, // Analyser avant d'appliquer preserveStructure = true, csvData = null, context = {} } = config; await tracer.annotate({ selectiveLayer: true, layerType, llmProvider, analysisMode, elementsCount: Object.keys(existingContent).length }); const startTime = Date.now(); logSh(`🔧 APPLICATION COUCHE SELECTIVE: ${layerType} (${llmProvider})`, 'INFO'); logSh(` 📊 ${Object.keys(existingContent).length} éléments | Mode: ${analysisMode ? 'analysé' : 'direct'}`, 'INFO'); try { let enhancedContent = {}; let layerStats = {}; // Sélection automatique du LLM si 'auto' const selectedLLM = selectOptimalLLM(layerType, llmProvider); // Application selon type de couche switch (layerType) { case 'technical': const technicalResult = await applyTechnicalEnhancement(existingContent, { ...config, llmProvider: selectedLLM }); enhancedContent = technicalResult.content; layerStats = technicalResult.stats; break; case 'transitions': const transitionResult = await applyTransitionEnhancement(existingContent, { ...config, llmProvider: selectedLLM }); enhancedContent = transitionResult.content; layerStats = transitionResult.stats; break; case 'style': const styleResult = await applyStyleEnhancement(existingContent, { ...config, llmProvider: selectedLLM }); enhancedContent = styleResult.content; layerStats = styleResult.stats; break; case 'all': const allResult = await applyAllSelectiveLayers(existingContent, config); enhancedContent = allResult.content; layerStats = allResult.stats; break; default: throw new Error(`Type de couche selective inconnue: ${layerType}`); } const duration = Date.now() - startTime; const stats = { layerType, llmProvider: selectedLLM, elementsProcessed: Object.keys(existingContent).length, elementsEnhanced: countEnhancedElements(existingContent, enhancedContent), duration, ...layerStats }; logSh(`✅ COUCHE SELECTIVE APPLIQUÉE: ${stats.elementsEnhanced}/${stats.elementsProcessed} améliorés (${duration}ms)`, 'INFO'); await tracer.event('Couche selective appliquée', stats); return { content: enhancedContent, stats, original: existingContent, config: { ...config, llmProvider: selectedLLM } }; } catch (error) { const duration = Date.now() - startTime; logSh(`❌ COUCHE SELECTIVE ÉCHOUÉE après ${duration}ms: ${error.message}`, 'ERROR'); // Fallback: retourner contenu original logSh(`🔄 Fallback: contenu original conservé`, 'WARNING'); return { content: existingContent, stats: { fallback: true, duration }, original: existingContent, config, error: error.message }; } }, { existingContent: Object.keys(existingContent), config }); } /** * APPLICATION TECHNIQUE MODULAIRE */ async function applyTechnicalEnhancement(content, config = {}) { const { TechnicalLayer } = require('./TechnicalLayer'); const layer = new TechnicalLayer(); return await layer.apply(content, config); } /** * APPLICATION TRANSITIONS MODULAIRE */ async function applyTransitionEnhancement(content, config = {}) { const { TransitionLayer } = require('./TransitionLayer'); const layer = new TransitionLayer(); return await layer.apply(content, config); } /** * APPLICATION STYLE MODULAIRE */ async function applyStyleEnhancement(content, config = {}) { const { StyleLayer } = require('./StyleLayer'); const layer = new StyleLayer(); return await layer.apply(content, config); } /** * APPLICATION TOUTES COUCHES SÉQUENTIELLES */ async function applyAllSelectiveLayers(content, config = {}) { logSh(`🔄 Application séquentielle toutes couches selective`, 'DEBUG'); let currentContent = content; const allStats = { steps: [], totalDuration: 0, totalEnhancements: 0 }; const steps = [ { name: 'technical', llm: 'gpt4' }, { name: 'transitions', llm: 'gemini' }, { name: 'style', llm: 'mistral' } ]; for (const step of steps) { try { logSh(` 🔧 Étape: ${step.name} (${step.llm})`, 'DEBUG'); const stepResult = await applySelectiveLayer(currentContent, { ...config, layerType: step.name, llmProvider: step.llm }); currentContent = stepResult.content; allStats.steps.push({ name: step.name, llm: step.llm, ...stepResult.stats }); allStats.totalDuration += stepResult.stats.duration; allStats.totalEnhancements += stepResult.stats.elementsEnhanced; } catch (error) { logSh(` ❌ Étape ${step.name} échouée: ${error.message}`, 'ERROR'); allStats.steps.push({ name: step.name, llm: step.llm, error: error.message, duration: 0, elementsEnhanced: 0 }); } } return { content: currentContent, stats: allStats }; } /** * ANALYSE BESOIN D'ENHANCEMENT */ async function analyzeEnhancementNeeds(content, config = {}) { logSh(`🔍 Analyse besoins selective enhancement`, 'DEBUG'); const analysis = { technical: { needed: false, score: 0, elements: [] }, transitions: { needed: false, score: 0, elements: [] }, style: { needed: false, score: 0, elements: [] }, recommendation: 'none' }; // Analyser chaque élément Object.entries(content).forEach(([tag, text]) => { // Analyse technique (termes techniques manquants) const technicalNeed = assessTechnicalNeed(text, config.csvData); if (technicalNeed.score > 0.3) { analysis.technical.needed = true; analysis.technical.score += technicalNeed.score; analysis.technical.elements.push({ tag, score: technicalNeed.score, reason: technicalNeed.reason }); } // Analyse transitions (fluidité) const transitionNeed = assessTransitionNeed(text); if (transitionNeed.score > 0.4) { analysis.transitions.needed = true; analysis.transitions.score += transitionNeed.score; analysis.transitions.elements.push({ tag, score: transitionNeed.score, reason: transitionNeed.reason }); } // Analyse style (personnalité) const styleNeed = assessStyleNeed(text, config.csvData?.personality); if (styleNeed.score > 0.3) { analysis.style.needed = true; analysis.style.score += styleNeed.score; analysis.style.elements.push({ tag, score: styleNeed.score, reason: styleNeed.reason }); } }); // 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; // Recommandation const scores = [ { type: 'technical', score: analysis.technical.score }, { type: 'transitions', score: analysis.transitions.score }, { type: 'style', score: analysis.style.score } ].sort((a, b) => b.score - a.score); if (scores[0].score > 0.6) { analysis.recommendation = scores[0].type; } else if (scores[0].score > 0.4) { analysis.recommendation = 'light_' + scores[0].type; } logSh(` 📊 Analyse: Tech=${analysis.technical.score.toFixed(2)} | Trans=${analysis.transitions.score.toFixed(2)} | Style=${analysis.style.score.toFixed(2)}`, 'DEBUG'); logSh(` 💡 Recommandation: ${analysis.recommendation}`, 'DEBUG'); return analysis; } // ============= HELPER FUNCTIONS ============= /** * Sélectionner LLM optimal selon type de couche */ function selectOptimalLLM(layerType, llmProvider) { if (llmProvider !== 'auto') return llmProvider; const optimalMapping = { 'technical': 'openai', // OpenAI GPT-4 excellent pour précision technique 'transitions': 'gemini', // Gemini bon pour fluidité 'style': 'mistral', // Mistral excellent pour style personnalité 'all': 'claude' // Claude polyvalent pour tout }; return optimalMapping[layerType] || 'claude'; } /** * Compter éléments améliorés */ function countEnhancedElements(original, enhanced) { let count = 0; Object.keys(original).forEach(tag => { if (enhanced[tag] && enhanced[tag] !== original[tag]) { count++; } }); return count; } /** * Évaluer besoin technique */ function assessTechnicalNeed(content, csvData) { let score = 0; let reason = []; // Manque de termes techniques spécifiques if (csvData?.mc0) { const technicalTerms = ['dibond', 'pmma', 'aluminium', 'fraisage', 'impression', 'gravure', 'découpe']; const contentLower = content.toLowerCase(); const foundTerms = technicalTerms.filter(term => contentLower.includes(term)); if (foundTerms.length === 0 && content.length > 100) { score += 0.4; reason.push('manque_termes_techniques'); } } // Vocabulaire trop générique const genericWords = ['produit', 'solution', 'service', 'qualité', 'offre']; const genericCount = genericWords.filter(word => content.toLowerCase().includes(word)).length; if (genericCount > 2) { score += 0.3; reason.push('vocabulaire_générique'); } // Manque de précision dimensionnelle/technique if (content.length > 50 && !(/\d+\s*(mm|cm|m|%|°)/.test(content))) { score += 0.2; reason.push('manque_précision_technique'); } return { score: Math.min(1, score), reason: reason.join(',') }; } /** * Évaluer besoin transitions */ function assessTransitionNeed(content) { let score = 0; let reason = []; const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 10); if (sentences.length < 2) return { score: 0, reason: '' }; // Connecteurs répétitifs const connectors = ['par ailleurs', 'en effet', 'de plus', 'cependant']; let repetitiveConnectors = 0; connectors.forEach(connector => { const matches = (content.match(new RegExp(connector, 'gi')) || []); if (matches.length > 1) repetitiveConnectors++; }); if (repetitiveConnectors > 1) { score += 0.4; reason.push('connecteurs_répétitifs'); } // Transitions abruptes (phrases sans connecteurs logiques) let abruptTransitions = 0; for (let i = 1; i < sentences.length; i++) { const sentence = sentences[i].trim().toLowerCase(); const hasConnector = connectors.some(conn => sentence.startsWith(conn)) || /^(puis|ensuite|également|aussi|donc|ainsi)/.test(sentence); if (!hasConnector && sentence.length > 30) { abruptTransitions++; } } if (abruptTransitions / sentences.length > 0.6) { score += 0.3; reason.push('transitions_abruptes'); } return { score: Math.min(1, score), reason: reason.join(',') }; } /** * Évaluer besoin style */ function assessStyleNeed(content, personality) { let score = 0; let reason = []; if (!personality) { score += 0.2; reason.push('pas_personnalité'); return { score, reason: reason.join(',') }; } // Style générique (pas de personnalité visible) const personalityWords = (personality.vocabulairePref || '').toLowerCase().split(','); const contentLower = content.toLowerCase(); const personalityFound = personalityWords.some(word => word.trim() && contentLower.includes(word.trim()) ); if (!personalityFound && content.length > 50) { score += 0.4; reason.push('style_générique'); } // Niveau technique inadapté if (personality.niveauTechnique === 'accessible' && /\b(optimisation|implémentation|méthodologie)\b/i.test(content)) { score += 0.3; reason.push('trop_technique'); } return { score: Math.min(1, score), reason: reason.join(',') }; } module.exports = { applySelectiveLayer, // ← MAIN ENTRY POINT MODULAIRE applyTechnicalEnhancement, applyTransitionEnhancement, applyStyleEnhancement, applyAllSelectiveLayers, analyzeEnhancementNeeds, selectOptimalLLM };