- 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
563 lines
20 KiB
JavaScript
563 lines
20 KiB
JavaScript
// ========================================
|
||
// MÉTRIQUES DE QUALITÉ - VALIDATION OBJECTIVE
|
||
// Calculs automatisés de qualité textuelle
|
||
// ========================================
|
||
|
||
// Import du LLMManager pour validation IA complémentaire
|
||
let LLMManager;
|
||
try {
|
||
const llmModule = require('../../lib/LLMManager');
|
||
if (llmModule && typeof llmModule.callLLM === 'function') {
|
||
LLMManager = llmModule;
|
||
}
|
||
} catch (error) {
|
||
console.warn('LLMManager non disponible pour QualityMetrics:', error.message);
|
||
}
|
||
|
||
/**
|
||
* Système de métriques objectifs pour évaluer la qualité du contenu
|
||
*/
|
||
class QualityMetrics {
|
||
|
||
/**
|
||
* 📊 CALCUL COMPLET MÉTRIQUES - ORCHESTRATEUR PRINCIPAL
|
||
*
|
||
* CE QUI EST TESTÉ :
|
||
* ✅ Calcul 5 métriques objectives automatiques (lisibilité, vocabulaire, structure, cohérence, SEO)
|
||
* ✅ Score global pondéré équilibré 25% par métrique
|
||
* ✅ Validation IA complémentaire optionnelle (si LLM disponible)
|
||
*
|
||
* ALGORITHMES EXÉCUTÉS :
|
||
* - 5 calculs en parallèle des métriques objectives
|
||
* - Pondération : (readability + vocabulary + structure + coherence) * 0.25 chacun
|
||
* - Si LLM activé : fusion 80% objectif + 20% validation IA
|
||
* - Retour structure unifiée avec breakdown détaillé
|
||
*/
|
||
static async calculateMetrics(content, options = {}) {
|
||
// Métriques objectives (calculs mathématiques)
|
||
const objectiveMetrics = {
|
||
readability: this.calculateReadability(content),
|
||
vocabulary: this.calculateVocabularyRichness(content),
|
||
structure: this.calculateStructureMetrics(content),
|
||
coherence: this.calculateCoherenceMetrics(content),
|
||
seo: this.calculateSEOMetrics(content),
|
||
overall: 0
|
||
};
|
||
|
||
// Score global pondéré objectif
|
||
objectiveMetrics.overall = Math.round(
|
||
objectiveMetrics.readability * 0.25 +
|
||
objectiveMetrics.vocabulary * 0.25 +
|
||
objectiveMetrics.structure * 0.25 +
|
||
objectiveMetrics.coherence * 0.25
|
||
);
|
||
|
||
// Validation IA complémentaire (si LLM disponible)
|
||
if (LLMManager && options.enableLLM !== false) {
|
||
try {
|
||
const aiValidation = await this.validateQualityWithLLM(content, objectiveMetrics);
|
||
|
||
// Fusion des métriques (privilégier les calculs objectifs)
|
||
const enhancedMetrics = {
|
||
...objectiveMetrics,
|
||
aiInsights: aiValidation,
|
||
// Score final: 80% objectif + 20% IA
|
||
overall: Math.round(objectiveMetrics.overall * 0.8 + aiValidation.score * 0.2)
|
||
};
|
||
|
||
return enhancedMetrics;
|
||
|
||
} catch (error) {
|
||
console.warn('Erreur validation LLM qualité:', error.message);
|
||
}
|
||
}
|
||
|
||
return objectiveMetrics;
|
||
}
|
||
|
||
/**
|
||
* 🤖 VALIDATION LLM COMPLÉMENTAIRE - ANALYSE QUALITATIVE IA
|
||
*
|
||
* CE QUI EST TESTÉ :
|
||
* ✅ Appel OpenAI pour validation qualitative des métriques objectives
|
||
* ✅ Évaluation fluidité, clarté, engagement, professionnalisme
|
||
* ✅ Fusion intelligente scores objectifs + perception IA
|
||
*
|
||
* ALGORITHMES EXÉCUTÉS :
|
||
* - Prompt structuré avec métriques objectives en contexte
|
||
* - Demande JSON strict : score + breakdown qualitatif + feedback
|
||
* - Parse robuste avec fallback sur échec JSON
|
||
* - Clamp scores dans [0,100] et confidence dans [0,1]
|
||
*/
|
||
static async validateQualityWithLLM(content, objectiveMetrics) {
|
||
const prompt = `
|
||
MISSION: Valide la qualité de ce contenu et confirme/ajuste les métriques objectives.
|
||
|
||
MÉTRIQUES OBJECTIVES CALCULÉES:
|
||
- Lisibilité: ${objectiveMetrics.readability}/100
|
||
- Vocabulaire: ${objectiveMetrics.vocabulary}/100
|
||
- Structure: ${objectiveMetrics.structure}/100
|
||
- Cohérence: ${objectiveMetrics.coherence}/100
|
||
- Score global: ${objectiveMetrics.overall}/100
|
||
|
||
CONTENU À ANALYSER:
|
||
---
|
||
${content.substring(0, 1000)}${content.length > 1000 ? '...[TRONQUÉ]' : ''}
|
||
---
|
||
|
||
VALIDATION QUALITATIVE:
|
||
1. FLUIDITÉ: Le texte se lit-il naturellement?
|
||
2. CLARTÉ: Les idées sont-elles bien exprimées?
|
||
3. ENGAGEMENT: Le contenu est-il intéressant?
|
||
4. PROFESSIONNALISME: Niveau de qualité rédactionnelle?
|
||
|
||
RÉPONSE JSON STRICTE:
|
||
{
|
||
"score": 75,
|
||
"validation": "confirmed",
|
||
"qualitativeAssessment": {
|
||
"fluidity": 80,
|
||
"clarity": 75,
|
||
"engagement": 70,
|
||
"professionalism": 85
|
||
},
|
||
"feedback": "Analyse qualitative en 2-3 phrases",
|
||
"confidence": 0.9
|
||
}
|
||
|
||
SCORE: 0-100 (qualité globale perçue par un lecteur)`;
|
||
|
||
const response = await LLMManager.callLLM('openai', prompt, {
|
||
temperature: 0.1,
|
||
max_tokens: 300
|
||
});
|
||
|
||
try {
|
||
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)),
|
||
validation: parsed.validation || 'partial',
|
||
qualitative: parsed.qualitativeAssessment || {},
|
||
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 qualité:', error.message);
|
||
return {
|
||
score: objectiveMetrics.overall, // Fallback sur score objectif
|
||
feedback: `Erreur parsing: ${error.message}`,
|
||
confidence: 0.1,
|
||
source: 'fallback'
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 📚 MÉTRIQUES LISIBILITÉ - ALGORITHME FLESCH-KINCAID FRANÇAIS
|
||
*
|
||
* CE QUI EST TESTÉ :
|
||
* ✅ Comptage précis phrases, mots, syllabes avec regex françaises
|
||
* ✅ Formule Flesch adaptée : 206.835 - (1.015 * mots/phrase) - (84.6 * syllabes/mot)
|
||
* ✅ Normalisation [0,100] avec Math.max/Math.min
|
||
*
|
||
* ALGORITHMES EXÉCUTÉS :
|
||
* - Comptage phrases : split(/[.!?]+/) + filter longueur>0
|
||
* - Comptage syllabes : regex voyelles françaises [aeiouyàâäéèêëïîôöùûü]
|
||
* - Ajustements français : -1 si mot finit par 'e', corrections 'eau'/'oui'
|
||
* - Score final arrondi Math.round()
|
||
*/
|
||
static calculateReadability(content) {
|
||
const sentences = this.countSentences(content);
|
||
const words = this.countWords(content);
|
||
const syllables = this.estimateSyllables(content);
|
||
|
||
if (sentences === 0 || words === 0) return 0;
|
||
|
||
// Adaptation française de Flesch-Kincaid
|
||
const avgWordsPerSentence = words / sentences;
|
||
const avgSyllablesPerWord = syllables / words;
|
||
|
||
// Formule adaptée pour le français
|
||
let readabilityScore = 206.835 - (1.015 * avgWordsPerSentence) - (84.6 * avgSyllablesPerWord);
|
||
|
||
// Normalisation 0-100
|
||
readabilityScore = Math.max(0, Math.min(100, readabilityScore));
|
||
|
||
return Math.round(readabilityScore);
|
||
}
|
||
|
||
/**
|
||
* 📝 RICHESSE VOCABULAIRE - ALGORITHME DE DIVERSITÉ LEXICALE
|
||
*
|
||
* CE QUI EST TESTÉ :
|
||
* ✅ Ratio mots uniques vs mots totaux (diversité lexicale)
|
||
* ✅ Bonus longueur moyenne des mots (complexité vocabulaire)
|
||
* ✅ Pénalité répétitions excessives (>3 occurrences)
|
||
*
|
||
* ALGORITHMES EXÉCUTÉS :
|
||
* - Calcul uniqueness = Set(words.lowercase).size / words.length
|
||
* - Bonus longueur = Math.min((avgWordLength - 3) * 10, 20) // max 20pts
|
||
* - Pénalité répétition = (count-3)*2 pour chaque mot>3 occurrences
|
||
* - Score final = (uniqueness * 80) + bonus - penalty, clamp [0,100]
|
||
*/
|
||
static calculateVocabularyRichness(content) {
|
||
const words = this.getWords(content);
|
||
const uniqueWords = new Set(words.map(w => w.toLowerCase()));
|
||
|
||
if (words.length === 0) return 0;
|
||
|
||
// Ratio mots uniques / mots totaux
|
||
const uniquenessRatio = uniqueWords.size / words.length;
|
||
|
||
// Bonus pour la longueur des mots
|
||
const avgWordLength = words.reduce((sum, word) => sum + word.length, 0) / words.length;
|
||
const lengthBonus = Math.min((avgWordLength - 3) * 10, 20); // Max 20 points bonus
|
||
|
||
// Pénalité pour répétitions excessives
|
||
const repetitionPenalty = this.calculateRepetitionPenalty(words);
|
||
|
||
let richness = (uniquenessRatio * 80) + lengthBonus - repetitionPenalty;
|
||
richness = Math.max(0, Math.min(100, richness));
|
||
|
||
return Math.round(richness);
|
||
}
|
||
|
||
/**
|
||
* 🏢 MÉTRIQUES STRUCTURE - ANALYSE ARCHITECTURE TEXTUELLE
|
||
*
|
||
* CE QUI EST TESTÉ :
|
||
* ✅ Présence et qualité des paragraphes (split \n\n)
|
||
* ✅ Longueur optimale paragraphes [100-800 chars]
|
||
* ✅ Détection listes/énumérations (bonus structure)
|
||
* ✅ Variabilité longueur phrases (coefficient variation)
|
||
*
|
||
* ALGORITHMES EXÉCUTÉS :
|
||
* - Score base = 100, décrément selon défauts
|
||
* - -20pts si <2 paragraphes, -15pts si >800 chars/parag, -10pts si <100
|
||
* - +5pts si listes détectées : regex /[-•*]\s/ ou /\d+\.\s/
|
||
* - +15pts max selon variation = écart-type(longueurs) / moyenne
|
||
*/
|
||
static calculateStructureMetrics(content) {
|
||
let score = 100;
|
||
|
||
// Vérification paragraphes
|
||
const paragraphs = content.split('\n\n').filter(p => p.trim().length > 0);
|
||
if (paragraphs.length < 2) score -= 20;
|
||
|
||
// Longueur des paragraphes
|
||
const avgParagraphLength = paragraphs.reduce((sum, p) => sum + p.length, 0) / paragraphs.length;
|
||
if (avgParagraphLength > 800) score -= 15; // Paragraphes trop longs
|
||
if (avgParagraphLength < 100) score -= 10; // Paragraphes trop courts
|
||
|
||
// Présence de listes ou énumérations
|
||
const hasLists = /[-•*]\s/.test(content) || /\d+\.\s/.test(content);
|
||
if (hasLists) score += 5;
|
||
|
||
// Variété dans la longueur des phrases
|
||
const sentences = this.getSentences(content);
|
||
const sentenceLengthVariation = this.calculateVariation(sentences.map(s => s.length));
|
||
score += Math.min(sentenceLengthVariation * 10, 15);
|
||
|
||
return Math.max(0, Math.min(100, Math.round(score)));
|
||
}
|
||
|
||
/**
|
||
* 🔗 MÉTRIQUES COHÉRENCE - ANALYSE CONNECTEURS LOGIQUES
|
||
*
|
||
* CE QUI EST TESTÉ :
|
||
* ✅ Détection 24 connecteurs français (donc, ainsi, cependant, etc.)
|
||
* ✅ Calcul ratio connecteurs/phrases (optimum 0.2-0.4)
|
||
* ✅ Scoring selon courbe : parfait à 0.2-0.4, dégradé en dehors
|
||
*
|
||
* ALGORITHMES EXÉCUTÉS :
|
||
* - Liste connecteurs = ['donc', 'ainsi', 'par conséquent', ...] (24 total)
|
||
* - Comptage = words.filter(w => connectors.includes(w.toLowerCase()))
|
||
* - Ratio = connectorCount / sentences.length
|
||
* - Score : si [0.2,0.4]=100pts, si <0.2=50+(ratio*250), si >0.4=100-((ratio-0.4)*100)
|
||
*/
|
||
static calculateCoherenceMetrics(content) {
|
||
const words = this.getWords(content);
|
||
const sentences = this.getSentences(content);
|
||
|
||
if (sentences.length < 2) return 50;
|
||
|
||
// Mots de liaison français
|
||
const connectors = [
|
||
'donc', 'ainsi', 'par conséquent', 'en effet', 'cependant', 'néanmoins',
|
||
'toutefois', 'd\'ailleurs', 'en outre', 'de plus', 'également', 'aussi',
|
||
'enfin', 'finalement', 'en conclusion', 'premièrement', 'deuxièmement',
|
||
'mais', 'or', 'car', 'parce que', 'puisque', 'comme', 'si', 'bien que'
|
||
];
|
||
|
||
// Comptage des connecteurs
|
||
const connectorCount = words.filter(word =>
|
||
connectors.includes(word.toLowerCase())
|
||
).length;
|
||
|
||
const connectorRatio = connectorCount / sentences.length;
|
||
|
||
// Score basé sur la densité de connecteurs (optimum autour de 0.2-0.4)
|
||
let coherenceScore;
|
||
if (connectorRatio >= 0.2 && connectorRatio <= 0.4) {
|
||
coherenceScore = 100;
|
||
} else if (connectorRatio < 0.2) {
|
||
coherenceScore = 50 + (connectorRatio * 250); // Manque de connecteurs
|
||
} else {
|
||
coherenceScore = 100 - ((connectorRatio - 0.4) * 100); // Trop de connecteurs
|
||
}
|
||
|
||
return Math.max(0, Math.min(100, Math.round(coherenceScore)));
|
||
}
|
||
|
||
/**
|
||
* 🔍 MÉTRIQUES SEO - ANALYSE OPTIMISATION MOTEURS RECHERCHE
|
||
*
|
||
* CE QUI EST TESTÉ :
|
||
* ✅ Longueur contenu optimale [300-2000 mots] selon standards SEO
|
||
* ✅ Présence titres HTML ou Markdown (<h1-6> ou #)
|
||
* ✅ Détection suroptimisation mots-clés (>5% = pénalité)
|
||
*
|
||
* ALGORITHMES EXÉCUTÉS :
|
||
* - Score base = 100, pénalités selon défauts
|
||
* - Longueur : -30pts si <300 mots, -15pts si <500, -10pts si >2000
|
||
* - Titres : -20pts si pas de regex /<h[1-6]>/ ni /^#{1,6}\s/
|
||
* - Densité mots-clés : -15pts si estimateKeywordDensity() > 0.05
|
||
*/
|
||
static calculateSEOMetrics(content) {
|
||
let score = 100;
|
||
|
||
// Longueur du contenu
|
||
const wordCount = this.countWords(content);
|
||
if (wordCount < 300) score -= 30;
|
||
else if (wordCount < 500) score -= 15;
|
||
else if (wordCount > 2000) score -= 10;
|
||
|
||
// Présence de titres (simulation)
|
||
const hasTitles = /<h[1-6]>/.test(content) || /^#{1,6}\s/.test(content);
|
||
if (!hasTitles) score -= 20;
|
||
|
||
// Densité des mots-clés (éviter la suroptimisation)
|
||
const keywordDensity = this.estimateKeywordDensity(content);
|
||
if (keywordDensity > 0.05) score -= 15; // Plus de 5% = suroptimisation
|
||
|
||
return Math.max(0, Math.min(100, Math.round(score)));
|
||
}
|
||
|
||
/**
|
||
* 🔢 COMPTAGE PHRASES - UTILITAIRE PARSING TEXTUEL
|
||
*
|
||
* CE QUI EST TESTÉ :
|
||
* ✅ Split sur ponctuations finales [.!?] avec support multiples
|
||
* ✅ Filtrage phrases vides (trim().length > 0)
|
||
* ✅ Comptage précis pour calculs lisibilité
|
||
*
|
||
* ALGORITHME EXÉCUTÉ :
|
||
* - Regex split : /[.!?]+/ pour gérer '...', '!!', '?!' etc.
|
||
* - Filter : s => s.trim().length > 0 pour ignorer phrases vides
|
||
* - Return count final pour ratios mots/phrases
|
||
*/
|
||
static countSentences(content) {
|
||
return content.split(/[.!?]+/).filter(s => s.trim().length > 0).length;
|
||
}
|
||
|
||
static countWords(content) {
|
||
return this.getWords(content).length;
|
||
}
|
||
|
||
static getWords(content) {
|
||
return content
|
||
.toLowerCase()
|
||
.replace(/[^\w\s-]/g, ' ')
|
||
.split(/\s+/)
|
||
.filter(word => word.length > 0);
|
||
}
|
||
|
||
static getSentences(content) {
|
||
return content.split(/[.!?]+/).filter(s => s.trim().length > 0);
|
||
}
|
||
|
||
/**
|
||
* 🗣️ ESTIMATION SYLLABES - ALGORITHME PHONÉTIQUE FRANÇAIS
|
||
*
|
||
* CE QUI EST TESTÉ :
|
||
* ✅ Comptage voyelles françaises avec accents complets
|
||
* ✅ Ajustements linguistiques : -1 si finit par 'e', corrections diphtongues
|
||
* ✅ Minimum 1 syllabe par mot (pas de mots à 0 syllabe)
|
||
*
|
||
* ALGORITHMES EXÉCUTÉS :
|
||
* - Regex voyelles = /[aeiouyàâäéèêëïîôöùûü]/gi
|
||
* - Correction 'e' final : if (word.endsWith('e') && syllables>1) syllables--
|
||
* - Correction diphtongues : if ('eau'||'oui') syllables--
|
||
* - Math.max(1, syllables) pour éviter 0
|
||
*/
|
||
static estimateSyllables(content) {
|
||
const words = this.getWords(content);
|
||
let totalSyllables = 0;
|
||
|
||
words.forEach(word => {
|
||
// Approximation simple pour le français
|
||
let syllables = word.match(/[aeiouyàâäéèêëïîôöùûü]/gi);
|
||
syllables = syllables ? syllables.length : 1;
|
||
|
||
// Ajustements pour le français
|
||
if (word.endsWith('e') && syllables > 1) syllables--;
|
||
if (word.includes('eau') || word.includes('oui')) syllables--;
|
||
|
||
totalSyllables += Math.max(1, syllables);
|
||
});
|
||
|
||
return totalSyllables;
|
||
}
|
||
|
||
/**
|
||
* ⛔ CALCUL PÉNALITÉ RÉPÉTITIONS - DÉTECTEUR REDONDANCE
|
||
*
|
||
* CE QUI EST TESTÉ :
|
||
* ✅ Comptage fréquence mots significatifs (longueur>3)
|
||
* ✅ Pénalité progressive : 2pts par occurrence au-delà de 3
|
||
* ✅ Plafond max 30pts pour éviter pénalités excessives
|
||
*
|
||
* ALGORITHMES EXÉCUTÉS :
|
||
* - Filter mots : word.length > 3 pour ignorer articles/prépositions
|
||
* - Map fréquences : wordCount[lower] = (wordCount[lower] || 0) + 1
|
||
* - Pénalité = Σ((count-3)*2) pour chaque mot avec count>3
|
||
* - Return Math.min(penalty, 30) pour plafonner
|
||
*/
|
||
static calculateRepetitionPenalty(words) {
|
||
const wordCount = {};
|
||
words.forEach(word => {
|
||
const lower = word.toLowerCase();
|
||
if (lower.length > 3) { // Ignorer les mots trop courts
|
||
wordCount[lower] = (wordCount[lower] || 0) + 1;
|
||
}
|
||
});
|
||
|
||
let penalty = 0;
|
||
Object.values(wordCount).forEach(count => {
|
||
if (count > 3) {
|
||
penalty += (count - 3) * 2; // 2 points de pénalité par répétition excessive
|
||
}
|
||
});
|
||
|
||
return Math.min(penalty, 30); // Max 30 points de pénalité
|
||
}
|
||
|
||
/**
|
||
* 📊 CALCUL VARIATION - COEFFICIENT DE VARIATION STATISTIQUE
|
||
*
|
||
* CE QUI EST TESTÉ :
|
||
* ✅ Calcul écart-type des longueurs de phrases
|
||
* ✅ Coefficient variation = σ/μ (normalisation par moyenne)
|
||
* ✅ Mesure diversité structurelle du texte
|
||
*
|
||
* ALGORITHMES EXÉCUTÉS :
|
||
* - Moyenne : avg = Σ(numbers) / numbers.length
|
||
* - Variance : variance = Σ((n-avg)²) / length
|
||
* - Écart-type : σ = Math.sqrt(variance)
|
||
* - Coefficient : cv = σ/avg (variation relative)
|
||
*/
|
||
static calculateVariation(numbers) {
|
||
if (numbers.length < 2) return 0;
|
||
|
||
const avg = numbers.reduce((sum, n) => sum + n, 0) / numbers.length;
|
||
const variance = numbers.reduce((sum, n) => sum + Math.pow(n - avg, 2), 0) / numbers.length;
|
||
|
||
return Math.sqrt(variance) / avg; // Coefficient de variation
|
||
}
|
||
|
||
/**
|
||
* 🎯 ESTIMATION DENSITÉ MOTS-CLÉS - DÉTECTEUR SUROPTIMISATION
|
||
*
|
||
* CE QUI EST TESTÉ :
|
||
* ✅ Détection mot le plus répété (potentiel mot-clé)
|
||
* ✅ Calcul ratio fréquence_max / total_mots
|
||
* ✅ Seuil alerte >5% pour détecter suroptimisation SEO
|
||
*
|
||
* ALGORITHMES EXÉCUTÉS :
|
||
* - Filter mots significatifs : word.length > 4
|
||
* - Comptage fréquences : wordFreq[word] = count
|
||
* - Recherche maximum : Math.max(...Object.values(wordFreq))
|
||
* - Densité = maxFreq / totalWords (ratio [0,1])
|
||
*/
|
||
static estimateKeywordDensity(content) {
|
||
const words = this.getWords(content);
|
||
const totalWords = words.length;
|
||
|
||
if (totalWords === 0) return 0;
|
||
|
||
// Recherche de répétitions potentielles de mots-clés
|
||
const wordFreq = {};
|
||
words.forEach(word => {
|
||
if (word.length > 4) { // Mots significatifs
|
||
wordFreq[word] = (wordFreq[word] || 0) + 1;
|
||
}
|
||
});
|
||
|
||
// Trouver le mot le plus répété (potentiel mot-clé)
|
||
const maxFreq = Math.max(...Object.values(wordFreq));
|
||
|
||
return maxFreq / totalWords;
|
||
}
|
||
|
||
/**
|
||
* ⚡ ANALYSE RAPIDE DASHBOARD - PRESET PERFORMANCE OPTIMISÉ
|
||
*
|
||
* CE QUI EST TESTÉ :
|
||
* ✅ Calculs basiques sans appel LLM (temps <1 seconde)
|
||
* ✅ Stats essentielles : mots, phrases, lisibilité, ratios
|
||
* ✅ Classification lisibilité textuelle (Très facile à Très difficile)
|
||
*
|
||
* ALGORITHMES EXÉCUTÉS :
|
||
* - Comptages directs : countWords(), countSentences()
|
||
* - Ratio : Math.round(words/sentences) pour moyenne
|
||
* - Lisibilité : calculateReadability() + classification textuelle
|
||
* - Structure optimisée pour affichage dashboard temps réel
|
||
*/
|
||
static quickAnalysis(content) {
|
||
const words = this.countWords(content);
|
||
const sentences = this.countSentences(content);
|
||
const readability = this.calculateReadability(content);
|
||
|
||
return {
|
||
wordCount: words,
|
||
sentenceCount: sentences,
|
||
avgWordsPerSentence: sentences > 0 ? Math.round(words / sentences) : 0,
|
||
readabilityScore: readability,
|
||
readabilityLevel: this.getReadabilityLevel(readability)
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 📈 NIVEAU LISIBILITÉ TEXTUEL - CLASSIFICATEUR SEUILS
|
||
*
|
||
* CE QUI EST TESTÉ :
|
||
* ✅ Conversion score numérique [0-100] en niveau lisible humain
|
||
* ✅ 7 niveaux définis selon standards éducatifs français
|
||
* ✅ Seuils optimisés pour compréhension intuitive
|
||
*
|
||
* ALGORITHME EXÉCUTÉ :
|
||
* - Classification par seuils : ≥90='Très facile', ≥80='Facile', etc.
|
||
* - Ordre décroissant : 90,80,70,60,50,30 puis 'Très difficile'
|
||
* - Correspondance standards Flesch-Kincaid adaptés français
|
||
*/
|
||
static getReadabilityLevel(score) {
|
||
if (score >= 90) return 'Très facile';
|
||
if (score >= 80) return 'Facile';
|
||
if (score >= 70) return 'Assez facile';
|
||
if (score >= 60) return 'Standard';
|
||
if (score >= 50) return 'Assez difficile';
|
||
if (score >= 30) return 'Difficile';
|
||
return 'Très difficile';
|
||
}
|
||
}
|
||
|
||
module.exports = { QualityMetrics }; |