- 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
681 lines
25 KiB
JavaScript
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 }; |