// ======================================== // VALIDATEUR DE PERSONNALITÉ // Validation de la cohérence avec le profil de personnalité sélectionné // ======================================== // Import du LLMManager pour validation IA let LLMManager; try { const llmModule = require('../../lib/LLMManager'); if (llmModule && typeof llmModule.callLLM === 'function') { LLMManager = llmModule; } } catch (error) { console.warn('LLMManager non disponible pour PersonalityValidator:', error.message); } /** * Validateur spécialisé dans l'évaluation de la cohérence de personnalité */ class PersonalityValidator { // Base de données des indicateurs de personnalité static PERSONALITY_INDICATORS = { 'Marc': { style: 'technique', vocabulary: ['technique', 'précision', 'optimisé', 'performance', 'efficace'], tone: 'professionnel', structure: 'logique', markers: ['il est recommandé', 'la solution optimale', 'techniquement'] }, 'Sophie': { style: 'décoratif', vocabulary: ['élégant', 'harmonieux', 'esthétique', 'raffinement', 'style'], tone: 'inspirant', structure: 'descriptive', markers: ['créer une ambiance', 'apporter du charme', 'sublimer'] }, 'Laurent': { style: 'commercial', vocabulary: ['opportunité', 'avantage', 'investissement', 'rentable', 'performant'], tone: 'persuasif', structure: 'argumentative', markers: ['bénéficier de', 'maximiser', 'opportunité unique'] }, 'Julie': { style: 'architectural', vocabulary: ['structure', 'conception', 'aménagement', 'fonctionnel', 'ergonomie'], tone: 'expert', structure: 'méthodique', markers: ['concevoir', 'structurer', 'aménager'] }, 'Kévin': { style: 'terrain', vocabulary: ['pratique', 'solide', 'durable', 'fiable', 'résistant'], tone: 'direct', structure: 'concrète', markers: ['sur le terrain', 'concrètement', 'en pratique'] }, 'Amara': { style: 'ingénierie', vocabulary: ['innovation', 'technologie', 'développement', 'recherche', 'avancée'], tone: 'scientifique', structure: 'analytique', markers: ['développer', 'innover', 'technologies avancées'] }, 'Mamadou': { style: 'artisan', vocabulary: ['artisanal', 'savoir-faire', 'tradition', 'minutieux', 'authentique'], tone: 'passionné', structure: 'narrative', markers: ['avec passion', 'savoir-faire artisanal', 'tradition'] }, 'Émilie': { style: 'digital', vocabulary: ['numérique', 'connecté', 'innovation', 'tendance', 'moderne'], tone: 'dynamique', structure: 'concise', markers: ['solutions digitales', 'innovation numérique', 'connecté'] }, 'Pierre-Henri': { style: 'patrimoine', vocabulary: ['héritage', 'tradition', 'authentique', 'noble', 'raffinement'], tone: 'distingué', structure: 'élaborée', markers: ['héritage culturel', 'tradition séculaire', 'authentique'] }, 'Yasmine': { style: 'éco', vocabulary: ['durable', 'écologique', 'responsable', 'environnement', 'naturel'], tone: 'engagé', structure: 'consciente', markers: ['respectueux de l\'environnement', 'démarche durable', 'écologique'] }, 'Fabrice': { style: 'métallurgie', vocabulary: ['métallique', 'robuste', 'industriel', 'résistance', 'qualité'], tone: 'technique', structure: 'précise', markers: ['qualité métallurgique', 'résistance optimale', 'fabrication'] }, 'Chloé': { style: 'contenu', vocabulary: ['créatif', 'expressif', 'original', 'inspirant', 'captivant'], tone: 'créatif', structure: 'fluide', markers: ['créer du contenu', 'inspiration créative', 'expression'] }, 'Linh': { style: 'fabrication', vocabulary: ['production', 'qualité', 'processus', 'fabrication', 'contrôle'], tone: 'rigoureux', structure: 'processuelle', markers: ['processus de fabrication', 'contrôle qualité', 'production'] }, 'Minh': { style: 'design', vocabulary: ['design', 'esthétique', 'forme', 'beauté', 'élégance'], tone: 'artistique', structure: 'visuelle', markers: ['design innovant', 'esthétique soignée', 'beauté'] }, 'Thierry': { style: 'créole', vocabulary: ['chaleur', 'convivial', 'authentique', 'tradition', 'accueil'], tone: 'chaleureux', structure: 'conviviale', markers: ['accueil chaleureux', 'tradition créole', 'convivialité'] } }; /** * Validation principale de cohérence personnalité */ static async validatePersonality(content, targetPersonality, context = {}) { // Récupération des indicateurs de la personnalité cible const indicators = this.PERSONALITY_INDICATORS[targetPersonality.nom] || this.PERSONALITY_INDICATORS[targetPersonality]; if (!indicators) { return { score: 50, confidence: 0.1, feedback: `Personnalité "${targetPersonality.nom || targetPersonality}" inconnue`, breakdown: {} }; } // Analyse statique const staticAnalysis = { vocabularyMatch: this.analyzeVocabularyMatch(content, indicators.vocabulary), toneConsistency: this.analyzeToneConsistency(content, indicators.tone), structureAlignment: this.analyzeStructureAlignment(content, indicators.structure), markerPresence: this.analyzeMarkerPresence(content, indicators.markers), overallCoherence: 0 }; // Calcul du score statique global staticAnalysis.overallCoherence = Math.round( staticAnalysis.vocabularyMatch.score * 0.3 + staticAnalysis.toneConsistency.score * 0.25 + staticAnalysis.structureAlignment.score * 0.25 + staticAnalysis.markerPresence.score * 0.2 ); // Validation IA complémentaire (si LLM disponible) let aiAnalysis = null; if (LLMManager && context.enableLLM !== false) { try { const personalityName = targetPersonality.nom || targetPersonality; aiAnalysis = await this.validatePersonalityWithLLM(content, personalityName, indicators); // Fusion des scores (moyenne pondérée) const combinedScore = Math.round(staticAnalysis.overallCoherence * 0.6 + aiAnalysis.score * 0.4); return { score: combinedScore, confidence: Math.max(this.calculateConfidence(staticAnalysis), aiAnalysis.confidence), feedback: aiAnalysis.feedback || this.generatePersonalityFeedback(staticAnalysis, indicators), breakdown: staticAnalysis, aiAnalysis: aiAnalysis, targetPersonality: personalityName, indicators: indicators }; } catch (error) { console.warn('Erreur validation LLM personnalité:', error.message); } } return { score: staticAnalysis.overallCoherence, confidence: this.calculateConfidence(staticAnalysis), feedback: this.generatePersonalityFeedback(staticAnalysis, indicators), breakdown: staticAnalysis, targetPersonality: targetPersonality.nom || targetPersonality, indicators: indicators }; } /** * Validation avec LLM pour analyse qualitative de personnalité */ static async validatePersonalityWithLLM(content, personalityName, indicators) { const prompt = ` MISSION: Évalue si ce contenu correspond au style et à la personnalité de "${personalityName}". PROFIL PERSONNALITÉ CIBLE - ${personalityName}: - Style: ${indicators.style} - Ton: ${indicators.tone} - Structure: ${indicators.structure} - Vocabulaire attendu: ${indicators.vocabulary.join(', ')} - Marqueurs spécifiques: ${indicators.markers.join(', ')} CONTENU À ANALYSER: --- ${content.substring(0, 1200)}${content.length > 1200 ? '...[TRONQUÉ]' : ''} --- CRITÈRES D'ÉVALUATION: 1. VOCABULAIRE: Utilisation du vocabulaire spécialisé attendu 2. TON: Cohérence avec le ton caractéristique de la personnalité 3. STRUCTURE: Alignement avec le style de rédaction 4. AUTHENTICITÉ: Le contenu sonne-t-il vraiment comme cette personnalité? RÉPONSE JSON STRICTE: { "score": 75, "personalityMatch": "good", "strengths": ["éléments bien alignés"], "weaknesses": ["éléments à améliorer"], "feedback": "Analyse personnalisée en 2-3 phrases", "confidence": 0.8 } SCORE: 0-100 (0=pas du tout cette personnalité, 100=parfaitement aligné)`; const response = await LLMManager.callLLM('openai', prompt, { temperature: 0.1, max_tokens: 400 }); try { // Parse JSON de la réponse let jsonStr = response.trim(); const jsonMatch = jsonStr.match(/\{[\s\S]*\}/); if (jsonMatch) { jsonStr = jsonMatch[0]; } const parsed = JSON.parse(jsonStr); return { score: Math.max(0, Math.min(100, parsed.score || 50)), match: parsed.personalityMatch || 'medium', strengths: parsed.strengths || [], weaknesses: parsed.weaknesses || [], feedback: parsed.feedback || 'Analyse non disponible', confidence: Math.max(0, Math.min(1, parsed.confidence || 0.5)), source: 'llm' }; } catch (error) { console.warn('Erreur parsing réponse LLM personnalité:', error.message); return { score: 50, feedback: `Erreur parsing: ${error.message}`, confidence: 0.1, source: 'fallback' }; } } /** * Analyse de correspondance du vocabulaire */ static analyzeVocabularyMatch(content, targetVocabulary) { const contentWords = this.extractSignificantWords(content); const contentLower = content.toLowerCase(); let matchCount = 0; let totalRelevance = 0; const foundWords = []; targetVocabulary.forEach(word => { const occurrences = (contentLower.match(new RegExp(word, 'g')) || []).length; if (occurrences > 0) { matchCount += occurrences; foundWords.push({ word, count: occurrences }); totalRelevance += occurrences * 10; // 10 points par occurrence } }); // Score basé sur la densité de vocabulaire spécialisé const vocabularyDensity = matchCount / (contentWords.length / 100); // Pour 100 mots const score = Math.min(totalRelevance + vocabularyDensity * 10, 100); return { score: Math.round(score), matchCount, foundWords, density: vocabularyDensity }; } /** * Analyse de cohérence du ton */ static analyzeToneConsistency(content, targetTone) { const toneIndicators = { 'professionnel': { positive: ['recommandé', 'conseillé', 'expertise', 'spécialisé'], negative: ['super', 'génial', 'carrément', 'franchement'] }, 'inspirant': { positive: ['créer', 'transformer', 'magnifier', 'sublimer', 'imaginer'], negative: ['technique', 'spécification', 'paramètre'] }, 'persuasif': { positive: ['bénéficier', 'avantage', 'opportunité', 'maximiser'], negative: ['peut-être', 'probablement', 'incertain'] }, 'direct': { positive: ['directement', 'simplement', 'clairement', 'concrètement'], negative: ['sophistiqué', 'complexe', 'nuancé'] }, 'chaleureux': { positive: ['accueillant', 'convivial', 'chaleureux', 'bienveillant'], negative: ['froid', 'technique', 'rigide'] }, 'scientifique': { positive: ['recherche', 'analyse', 'étude', 'développement'], negative: ['ressenti', 'intuition', 'approximatif'] } }; const indicators = toneIndicators[targetTone] || { positive: [], negative: [] }; const contentLower = content.toLowerCase(); let positiveScore = 0; let negativeScore = 0; indicators.positive.forEach(word => { const count = (contentLower.match(new RegExp(word, 'g')) || []).length; positiveScore += count * 15; }); indicators.negative.forEach(word => { const count = (contentLower.match(new RegExp(word, 'g')) || []).length; negativeScore += count * 10; }); const toneScore = Math.max(0, Math.min(100, 50 + positiveScore - negativeScore)); return { score: Math.round(toneScore), positiveMatches: positiveScore / 15, negativeMatches: negativeScore / 10 }; } /** * Analyse d'alignement de structure */ static analyzeStructureAlignment(content, targetStructure) { const structureFeatures = { 'logique': { check: content => { const hasNumberedList = /\d+\.\s/.test(content); const hasLogicalConnectors = /(donc|ainsi|par conséquent)/gi.test(content); const hasMethodicalFlow = /(premièrement|deuxièmement|enfin)/gi.test(content); return (hasNumberedList ? 30 : 0) + (hasLogicalConnectors ? 40 : 0) + (hasMethodicalFlow ? 30 : 0); } }, 'descriptive': { check: content => { const hasDescriptiveWords = /(élégant|beau|harmonieux|raffinement)/gi.test(content); const hasFlowingStructure = !/(1\.|2\.|3\.)/g.test(content); const hasAdjectives = (content.match(/\b\w+ment\b/g) || []).length; return (hasDescriptiveWords ? 40 : 0) + (hasFlowingStructure ? 30 : 0) + Math.min(hasAdjectives * 5, 30); } }, 'argumentative': { check: content => { const hasArguments = /(avantage|bénéfice|preuve|démontrer)/gi.test(content); const hasCounterpoints = /(cependant|néanmoins|mais)/gi.test(content); const hasConclusion = /(en conclusion|finalement|donc)/gi.test(content); return (hasArguments ? 40 : 0) + (hasCounterpoints ? 30 : 0) + (hasConclusion ? 30 : 0); } }, 'narrative': { check: content => { const hasStoryElements = /(histoire|tradition|expérience|passion)/gi.test(content); const hasPersonalTouch = /(nous|notre|mon|ma)/gi.test(content); const hasTemporalMarkers = /(depuis|aujourd'hui|maintenant)/gi.test(content); return (hasStoryElements ? 40 : 0) + (hasPersonalTouch ? 30 : 0) + (hasTemporalMarkers ? 30 : 0); } } }; const feature = structureFeatures[targetStructure]; const score = feature ? feature.check(content) : 50; return { score: Math.min(100, Math.round(score)), targetStructure }; } /** * Analyse de présence des marqueurs spécifiques */ static analyzeMarkerPresence(content, targetMarkers) { const contentLower = content.toLowerCase(); let foundMarkers = 0; let totalScore = 0; const detectedMarkers = []; targetMarkers.forEach(marker => { if (contentLower.includes(marker.toLowerCase())) { foundMarkers++; totalScore += 20; // 20 points par marqueur trouvé detectedMarkers.push(marker); } }); // Bonus si plusieurs marqueurs présents if (foundMarkers > 1) { totalScore += foundMarkers * 5; } return { score: Math.min(100, totalScore), foundCount: foundMarkers, totalCount: targetMarkers.length, detectedMarkers }; } /** * Calcul de la confiance dans l'évaluation */ static calculateConfidence(analysis) { // Confiance basée sur la cohérence des différents indicateurs const scores = [ analysis.vocabularyMatch.score, analysis.toneConsistency.score, analysis.structureAlignment.score, analysis.markerPresence.score ]; const mean = scores.reduce((a, b) => a + b) / scores.length; const variance = scores.reduce((sum, score) => sum + Math.pow(score - mean, 2), 0) / scores.length; const stdDev = Math.sqrt(variance); // Confiance élevée si peu de variance entre les scores const confidence = Math.max(0.1, 1 - (stdDev / 50)); return Math.round(confidence * 100) / 100; } /** * Génération du feedback personnalisé */ static generatePersonalityFeedback(analysis, indicators) { const feedback = []; if (analysis.vocabularyMatch.score >= 70) { feedback.push(`✅ Vocabulaire bien aligné avec le style ${indicators.style}`); } else { feedback.push(`⚠️ Vocabulaire à enrichir (mots-clés: ${indicators.vocabulary.join(', ')})`); } if (analysis.toneConsistency.score >= 70) { feedback.push(`✅ Ton ${indicators.tone} respecté`); } else { feedback.push(`⚠️ Ton à ajuster vers plus ${indicators.tone}`); } if (analysis.structureAlignment.score >= 70) { feedback.push(`✅ Structure ${indicators.structure} appropriée`); } else { feedback.push(`⚠️ Structure à adapter au style ${indicators.structure}`); } if (analysis.markerPresence.foundCount > 0) { feedback.push(`✅ ${analysis.markerPresence.foundCount} marqueur(s) spécifique(s) détecté(s)`); } else { feedback.push(`⚠️ Aucun marqueur spécifique détecté`); } return feedback.join(' | '); } /** * Utilitaires */ static extractSignificantWords(content) { return content .toLowerCase() .replace(/[^\w\s]/g, ' ') .split(/\s+/) .filter(word => word.length > 3); } /** * Validation rapide avec recommandations */ static quickValidation(content, personality) { const validation = this.validatePersonality(content, personality); return { score: validation.score, level: validation.score >= 75 ? 'GOOD' : validation.score >= 50 ? 'MODERATE' : 'POOR', recommendations: this.generateRecommendations(validation), confidence: validation.confidence }; } /** * Génération de recommandations d'amélioration */ static generateRecommendations(validation) { const recs = []; if (validation.breakdown.vocabularyMatch.score < 60) { recs.push(`Intégrer plus de vocabulaire spécialisé: ${validation.indicators.vocabulary.slice(0, 3).join(', ')}`); } if (validation.breakdown.toneConsistency.score < 60) { recs.push(`Adapter le ton pour qu'il soit plus ${validation.indicators.tone}`); } if (validation.breakdown.structureAlignment.score < 60) { recs.push(`Réorganiser selon une structure plus ${validation.indicators.structure}`); } if (validation.breakdown.markerPresence.foundCount === 0) { recs.push(`Utiliser des marqueurs spécifiques: ${validation.indicators.markers.slice(0, 2).join(', ')}`); } return recs; } } module.exports = { PersonalityValidator };