seo-generator-server/tests/validators/PersonalityValidator.js
Trouve Alexis 870cfb0340 [200~add step-by-step versioning system with Google Sheets integration
- Add intermediate saves (v1.0-v1.4) to Generated_Articles_Versioned
  - Fix compiled_text pipeline (generatedTexts object structure)
  - Add /api/workflow-modulaire endpoint with version tracking
  - Create test-modulaire.html interface with real-time logs
  - Support parent-child linking via Parent_Article_ID
2025-09-06 16:38:20 +08:00

681 lines
25 KiB
JavaScript

// ========================================
// 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 PERSONNALITÉ - ORCHESTRATEUR GLOBAL
*
* CE QUI EST TESTÉ :
* ✅ Récupération profil cible depuis PERSONALITY_INDICATORS (15 profils)
* ✅ Analyse statique 4 métriques : vocabulaire, ton, structure, marqueurs
* ✅ Score pondéré : vocabulaire(30%) + ton(25%) + structure(25%) + marqueurs(20%)
* ✅ Validation LLM complémentaire (fusion 60% statique + 40% IA)
*
* ALGORITHMES EXÉCUTÉS :
* - Lookup profil par nom dans base 15 personnalités
* - 4 analyses parallèles avec indicateurs spécialisés
* - Calcul confiance via variance des scores (cohérence interne)
* - Génération feedback personnalisé par 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 LLM QUALITATIVE - ANALYSE STYLE PERSONNALITÉ IA
*
* CE QUI EST TESTÉ :
* ✅ Appel OpenAI avec profil complet personnalié en contexte
* ✅ Évaluation 4 critères : vocabulaire, ton, structure, authenticité
* ✅ Match qualitatif (good/medium/poor) + points forts/faibles
*
* ALGORITHMES EXÉCUTÉS :
* - Prompt enrichi avec profil complet (style, ton, vocabulaire, marqueurs)
* - Demande JSON : score + match + strengths + weaknesses + feedback
* - Parse robuste avec extraction regex JSON
* - Clamp score [0,100] et confidence [0,1]
*/
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 CORRESPONDANCE VOCABULAIRE - MATCHING LEXICAL
*
* CE QUI EST TESTÉ :
* ✅ Recherche mots-clés spécialisés personnalié dans contenu
* ✅ Comptage occurrences avec pondération (10pts par occurrence)
* ✅ Calcul densité vocabulaire spécialisé (pour 100 mots)
*
* ALGORITHMES EXÉCUTÉS :
* - Regex search : new RegExp(word, 'g') pour chaque mot-clé
* - Score = totalRelevance + density * 10 (max 100pts)
* - Densité = matchCount / (totalWords / 100)
* - Return : score + détail mots trouvés + densité
*/
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 COHÉRENCE TON - DÉTECTEUR REGISTRE LINGUISTIQUE
*
* CE QUI EST TESTÉ :
* ✅ Dictionnaire 6 tons : professionnel, inspirant, persuasif, direct, chaleureux, scientifique
* ✅ Mots positifs/négatifs par ton avec pondération différentiée
* ✅ Score = base(50) + positifs(+15 chacun) - négatifs(-10 chacun)
*
* ALGORITHMES EXÉCUTÉS :
* - Lookup toneIndicators[targetTone] pour listes positive/negative
* - Comptage regex occurrences : (contentLower.match(word, 'g') || []).length
* - Scoring : positiveScore = count * 15, negativeScore = count * 10
* - Final = Math.max(0, Math.min(100, 50 + positive - negative))
*/
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 ALIGNEMENT STRUCTURE - PATTERNS ORGANISATIONNELS
*
* CE QUI EST TESTÉ :
* ✅ 4 structures : logique, descriptive, argumentative, narrative
* ✅ Détection patterns spécifiques par structure (regex + comptages)
* ✅ Scoring cumul selon présence éléments structurels
*
* ALGORITHMES EXÉCUTÉS :
* - Logique : listes numérotées(30pts) + connecteurs logiques(40pts) + flux méthodique(30pts)
* - Descriptive : mots descriptifs(40pts) + structure fluide(30pts) + adverbes(5pts/mot, max 30)
* - Argumentative : arguments(40pts) + contre-points(30pts) + conclusion(30pts)
* - Narrative : éléments histoire(40pts) + touch personnelle(30pts) + marqueurs temporels(30pts)
*/
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 PRÉSENCE MARQUEURS - DÉTECTEUR SIGNATURES
*
* CE QUI EST TESTÉ :
* ✅ Recherche expressions-signatures spécifiques personnalié
* ✅ Comptage exact avec scoring linéaire (20pts par marqueur)
* ✅ Bonus multiplicateur si plusieurs marqueurs (effet cumulatif)
*
* ALGORITHMES EXÉCUTÉS :
* - Search : contentLower.includes(marker.toLowerCase()) pour chaque
* - Base scoring : foundMarkers * 20pts
* - Bonus : if (foundMarkers > 1) totalScore += foundMarkers * 5
* - Return : score + count + marqueurs détectés list
*/
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 CONFIANCE ÉVALUATION - MESURE COHÉRENCE INTERNE
*
* CE QUI EST TESTÉ :
* ✅ Variance des 4 scores métriques (vocabulaire, ton, structure, marqueurs)
* ✅ Confidence inverse de dispersion (écart-type faible = confiance haute)
* ✅ Normalisation [0.1, 1.0] pour éviter confiance zéro
*
* ALGORITHMES EXÉCUTÉS :
* - Calcul moyenne : mean = Σ(scores) / 4
* - Variance : variance = Σ((score-mean)²) / 4
* - Écart-type : stdDev = sqrt(variance)
* - Confidence : Math.max(0.1, 1 - (stdDev / 50))
* - Return arrondi à 2 décimales
*/
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 FEEDBACK PERSONNALISÉ - RAPPORT ADAPTÉ
*
* CE QUI EST TESTÉ :
* ✅ Évaluation seuil 70pts pour validation/amélioration par métrique
* ✅ Messages contextualisés selon personnalité (vocabulaire, ton, etc.)
* ✅ Feedback actionnable avec suggestions concrètes
*
* ALGORITHMES EXÉCUTÉS :
* - If score >= 70 : message ✅ validation
* - If score < 70 : message ⚠️ avec suggestions
* - Join messages avec ' | ' pour format compact
* - Intégration indicateurs.vocabulary/tone/style dans conseils
*/
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(' | ');
}
/**
* 🔢 UTILITAIRE EXTRACTION MOTS SIGNIFICATIFS - PARSER LEXICAL
*
* CE QUI EST TESTÉ :
* ✅ Nettoyage ponctuation et normalisation lowercase
* ✅ Split sur espaces avec filtre longueur >3 chars
* ✅ Exclusion mots courts (articles, prépositions) pour focus contenu
*
* ALGORITHME EXÉCUTÉ :
* - toLowerCase() pour normalisation
* - replace(/[^\w\s]/g, ' ') pour supprimer ponctuation
* - split(/\s+/) pour mots individuels
* - filter(word => word.length > 3) pour significatifs
*/
static extractSignificantWords(content) {
return content
.toLowerCase()
.replace(/[^\w\s]/g, ' ')
.split(/\s+/)
.filter(word => word.length > 3);
}
/**
* ⚡ VALIDATION RAPIDE - PRESET PERFORMANCE OPTIMISÉ
*
* CE QUI EST TESTÉ :
* ✅ Validation complète simplifiée sans LLM (plus rapide)
* ✅ Classification 3 niveaux : GOOD(≥75), MODERATE(≥50), POOR(<50)
* ✅ Recommandations automatiques selon faiblesses détectées
*
* ALGORITHMES EXÉCUTÉS :
* - Appel validatePersonality() en mode statique only
* - Classification par seuils : 75/50 pour GOOD/MODERATE/POOR
* - Génération recommandations via generateRecommendations()
* - Structure optimisée pour dashboard temps réel
*/
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 RECOMMANDATIONS AMÉLIORATION - CONSEILS AUTOMATIQUES
*
* CE QUI EST TESTÉ :
* ✅ Analyse scores <60 pour identifier défauts prioritaires
* ✅ Recommandations spécifiques avec exemples concrets
* ✅ Suggestions actionables adaptées à chaque métrique
*
* ALGORITHMES EXÉCUTÉS :
* - If vocabularyMatch<60 : suggère top-3 mots-clés à intégrer
* - If toneConsistency<60 : conseil adaptation ton cible
* - If structureAlignment<60 : guidance réorganisation
* - If markerPresence=0 : propose top-2 marqueurs à utiliser
* - Return array pour affichage UI
*/
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 };