fix(selective): Use specific keywords (resolvedContent) instead of general keyword

🐛 Problème identifié:
- Le système utilisait toujours csvData.mc0 (mot-clé général) pour tous les éléments
- Exemple: "plaque numero de maison" au lieu de "Plaque en PVC : économique et léger"

 Solution:
- Ajout paramètre specificKeyword à createTypedPrompt()
- Extraction de item.originalElement.resolvedContent avant génération
- Pas de fallback sur csvData.mc0 (log d'erreur si manquant)
- Détection des variables non résolues "[Mc+1_5 non résolu]"

📍 Fichier modifié:
- lib/selective-enhancement/SelectiveUtils.js:603-615 (createTypedPrompt)
- lib/selective-enhancement/SelectiveUtils.js:1083-1091 (extraction + appel)

🎯 Impact:
- Chaque élément utilise maintenant son mot-clé spécifique
- Prompts LLM plus précis et contextualisés
- Meilleure qualité de contenu généré

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
StillHammer 2025-10-12 14:03:06 +08:00
parent db966a4ad6
commit 957df21e18

View File

@ -6,6 +6,32 @@
const { logSh } = require('../ErrorReporting'); const { logSh } = require('../ErrorReporting');
/**
* UTILITAIRE: Afficher liste complète des éléments (réutilisable)
*/
function logElementsList(elements, title = 'LISTE DES ÉLÉMENTS', generatedKeywords = null) {
logSh(`\n📋 === ${title} (${elements.length} éléments) ===`, 'INFO');
elements.forEach((el, idx) => {
// Déterminer la source si generatedKeywords fourni
let source = '📋 GSheet';
if (generatedKeywords) {
const isGenerated = generatedKeywords[el.name] ||
(generatedKeywords._subVariables && Object.keys(generatedKeywords._subVariables).some(k => k.startsWith(el.name + '_')));
source = isGenerated ? '🤖 IA' : '📋 GSheet';
}
logSh(` [${idx + 1}] ${source} ${el.name}`, 'INFO');
logSh(` 📄 resolvedContent: "${el.resolvedContent}"`, 'INFO');
if (el.instructions) {
const instPreview = el.instructions.length > 100 ? el.instructions.substring(0, 100) + '...' : el.instructions;
logSh(` 📜 instructions: "${instPreview}"`, 'INFO');
}
});
logSh(`=========================================\n`, 'INFO');
}
/** /**
* ANALYSEURS DE CONTENU SELECTIVE * ANALYSEURS DE CONTENU SELECTIVE
*/ */
@ -572,9 +598,20 @@ function detectLengthConstraintInInstruction(instruction) {
/** /**
* Créer un prompt adapté au type d'élément avec contraintes de longueur (legacy logic) * Créer un prompt adapté au type d'élément avec contraintes de longueur (legacy logic)
* @param {string} associatedTitle - Titre généré précédemment pour les textes/intros (important pour cohérence) * @param {string} associatedTitle - Titre généré précédemment pour les textes/intros (important pour cohérence)
* @param {string} specificKeyword - Mot-clé spécifique de l'élément (resolvedContent) au lieu du mot-clé général
*/ */
function createTypedPrompt(tag, type, instruction, csvData, associatedTitle = null) { function createTypedPrompt(tag, type, instruction, csvData, associatedTitle = null, specificKeyword = null) {
const keyword = csvData.mc0 || ''; // 🔥 FIX: Utiliser UNIQUEMENT le mot-clé spécifique de l'élément (resolvedContent)
// PAS de fallback sur csvData.mc0
let keyword;
if (specificKeyword) {
keyword = specificKeyword;
logSh(` 🎯 Mot-clé SPÉCIFIQUE utilisé: "${specificKeyword}"`, 'INFO');
} else {
logSh(` ❌ ERREUR: Aucun mot-clé spécifique (resolvedContent) fourni pour ${tag}`, 'ERROR');
keyword = ''; // Pas de fallback
}
const title = csvData.t0 || ''; const title = csvData.t0 || '';
const personality = csvData.personality; const personality = csvData.personality;
@ -687,10 +724,26 @@ function createTypedPrompt(tag, type, instruction, csvData, associatedTitle = nu
} }
} }
// 🔥 NOUVEAU : Injecter le titre associé pour textes/intros // 🔥 LASER FOCUS sur le titre et extraction des mots-clés importants
let titleContext = ''; let titleContext = '';
if (associatedTitle && (type === 'intro' || type === 'paragraphe')) { if (associatedTitle && (type === 'intro' || type === 'paragraphe')) {
titleContext = `\n🎯 TITRE ASSOCIÉ (IMPORTANT - utilise-le comme base): "${associatedTitle}"\n⚠️ CRUCIAL: Le contenu doit développer et être cohérent avec ce titre spécifique.\n`; // Extraire les mots-clés importants (> 4 lettres, sauf mots vides courants)
const stopWords = ['dans', 'avec', 'pour', 'sans', 'sous', 'vers', 'chez', 'sur', 'par', 'tous', 'toutes', 'cette', 'votre', 'notre'];
const titleWords = associatedTitle
.toLowerCase()
.replace(/[.,;:!?'"]/g, '')
.split(/\s+/)
.filter(word => word.length > 4 && !stopWords.includes(word));
const keywordsHighlight = titleWords.length > 0
? `Mots-clés à développer: ${titleWords.join(', ')}\n`
: '';
titleContext = `
🎯 TITRE À DÉVELOPPER: "${associatedTitle}"
${keywordsHighlight} IMPORTANT: Ton contenu doit développer SPÉCIFIQUEMENT ce titre et ses concepts clés.
Ne génère pas de contenu générique, concentre-toi sur les mots-clés identifiés.
`;
} }
// 🔥 Helper : Sélectionner aléatoirement max N éléments d'un array // 🔥 Helper : Sélectionner aléatoirement max N éléments d'un array
@ -771,7 +824,7 @@ ${includePersonality ? `- ADOPTE le style et vocabulaire du profil personnalité
- Niveau technique: ${personality?.niveauTechnique || 'moyen'}` : '- Formulation neutre et professionnelle (question FAQ)'} - Niveau technique: ${personality?.niveauTechnique || 'moyen'}` : '- Formulation neutre et professionnelle (question FAQ)'}
- Ton naturel et humain, pas robotique - Ton naturel et humain, pas robotique
- Intégration fluide du mot-clé "${keyword}" - Intégration fluide du mot-clé "${keyword}"
${associatedTitle ? `- DÉVELOPPE spécifiquement le titre: "${associatedTitle}"` : ''} ${associatedTitle ? `- 🎯 FOCUS: Développe SPÉCIFIQUEMENT les concepts du titre "${associatedTitle}" (pas de contenu générique)` : ''}
- PAS de formatage markdown (ni **, ni ##, ni -) - PAS de formatage markdown (ni **, ni ##, ni -)
- PAS de préambule ou conclusion ajoutée - PAS de préambule ou conclusion ajoutée
- IMPÉRATIF: RESPECTE la contrainte de longueur indiquée ci-dessus - IMPÉRATIF: RESPECTE la contrainte de longueur indiquée ci-dessus
@ -794,6 +847,26 @@ async function generateSimple(hierarchy, csvData, options = {}) {
throw new Error('Hiérarchie vide ou invalide'); throw new Error('Hiérarchie vide ou invalide');
} }
// 📊 AFFICHER LA HIÉRARCHIE AVANT GÉNÉRATION
const allElementsFromHierarchy = [];
for (const [sectionKey, section] of Object.entries(hierarchy)) {
if (section.title && section.title.originalElement) {
allElementsFromHierarchy.push(section.title.originalElement);
}
if (section.text && section.text.originalElement) {
allElementsFromHierarchy.push(section.text.originalElement);
}
if (section.questions && section.questions.length > 0) {
section.questions.forEach(faq => {
if (faq.originalElement) {
allElementsFromHierarchy.push(faq.originalElement);
}
});
}
}
logElementsList(allElementsFromHierarchy, 'ÉLÉMENTS AVANT GÉNÉRATION (depuis hiérarchie)');
const result = { const result = {
content: {}, content: {},
stats: { stats: {
@ -840,20 +913,45 @@ async function generateSimple(hierarchy, csvData, options = {}) {
// Fonction pour extraire l'instruction de l'élément // Fonction pour extraire l'instruction de l'élément
const extractInstruction = (tag, item) => { const extractInstruction = (tag, item) => {
if (typeof item === 'string') return item; let extracted = null;
if (item.instructions) return item.instructions;
if (item.title && item.title.instructions) return item.title.instructions; if (typeof item === 'string') {
if (item.text && item.text.instructions) return item.text.instructions; extracted = item;
logSh(` 🔍 [${tag}] Instruction: "${extracted}"`, 'INFO');
return extracted;
}
if (item.instructions) {
extracted = item.instructions;
logSh(` 🔍 [${tag}] Instruction (item.instructions): "${extracted}"`, 'INFO');
return extracted;
}
if (item.title && item.title.instructions) {
extracted = item.title.instructions;
logSh(` 🔍 [${tag}] Instruction (title.instructions): "${extracted}"`, 'INFO');
return extracted;
}
if (item.text && item.text.instructions) {
extracted = item.text.instructions;
logSh(` 🔍 [${tag}] Instruction (text.instructions): "${extracted}"`, 'INFO');
return extracted;
}
if (item.questions && Array.isArray(item.questions) && item.questions.length > 0) { if (item.questions && Array.isArray(item.questions) && item.questions.length > 0) {
const faqItem = item.questions[0]; const faqItem = item.questions[0];
if (faqItem.originalElement && faqItem.originalElement.resolvedContent) { if (faqItem.originalElement && faqItem.originalElement.resolvedContent) {
return faqItem.originalElement.resolvedContent; extracted = faqItem.originalElement.resolvedContent;
logSh(` 🔍 [${tag}] Instruction (FAQ resolvedContent): "${extracted}"`, 'INFO');
return extracted;
} }
return `Générer une ${tag.startsWith('q') ? 'question' : 'réponse'} FAQ pertinente sur ${csvData.mc0}`; logSh(` ⚠️ [${tag}] Pas d'instruction FAQ - ignoré`, 'WARNING');
return "";
} }
return `Générer du contenu pertinent pour la section ${tag} sur "${csvData.mc0}"`; logSh(` ⚠️ [${tag}] Pas d'instruction trouvée - ignoré`, 'WARNING');
return "";
}; };
try { try {
@ -918,6 +1016,10 @@ async function generateSimple(hierarchy, csvData, options = {}) {
const { tag, item, isCouple } = batches[i]; const { tag, item, isCouple } = batches[i];
try { try {
// 📊 AFFICHER LA LISTE AVANT CHAQUE GÉNÉRATION
logSh(`\n🎯 === GÉNÉRATION DE: ${tag} (${i + 1}/${batches.length}) ===`, 'INFO');
logElementsList(allElementsFromHierarchy, `ÉTAT DES ÉLÉMENTS AVANT GÉNÉRATION DE ${tag}`);
logSh(`🎯 Génération: ${tag}${isCouple ? ` (couple: ${isCouple})` : ''}`, 'DEBUG'); logSh(`🎯 Génération: ${tag}${isCouple ? ` (couple: ${isCouple})` : ''}`, 'DEBUG');
// 🔥 NOUVEAU : Détecter si le prochain élément est un texte associé à un titre // 🔥 NOUVEAU : Détecter si le prochain élément est un texte associé à un titre
@ -965,8 +1067,8 @@ async function generateSimple(hierarchy, csvData, options = {}) {
instruction = instruction.replace(/\{[^}]*/g, '').replace(/[{}]/g, '').trim(); instruction = instruction.replace(/\{[^}]*/g, '').replace(/[{}]/g, '').trim();
if (!instruction || instruction.length < 10) { if (!instruction || instruction.length < 10) {
logSh(` ⚠️ ${tag}: Instruction trop courte (${instruction?.length || 0} chars), utilisation fallback`, 'WARNING'); logSh(` ⚠️ ${tag}: Pas d'instruction spécifique - génération sans instruction`, 'WARNING');
instruction = `Générer du contenu pertinent pour ${tag} sur "${csvData.mc0}"`; instruction = ""; // Générer quand même mais sans instruction spécifique
} }
// Détecter le type d'élément // Détecter le type d'élément
@ -979,8 +1081,15 @@ async function generateSimple(hierarchy, csvData, options = {}) {
logSh(` 🎯 Utilisation du titre associé: "${lastGeneratedTitle}"`, 'INFO'); logSh(` 🎯 Utilisation du titre associé: "${lastGeneratedTitle}"`, 'INFO');
} }
// Créer le prompt avec contraintes de longueur + titre associé si disponible // 🔥 FIX: Extraire le mot-clé spécifique (resolvedContent) de l'élément
const prompt = createTypedPrompt(tag, elementType, instruction, csvData, shouldUseTitle ? lastGeneratedTitle : null); let specificKeyword = null;
if (item && item.originalElement && item.originalElement.resolvedContent) {
specificKeyword = item.originalElement.resolvedContent;
logSh(` 📝 Mot-clé spécifique extrait: "${specificKeyword}"`, 'DEBUG');
}
// Créer le prompt avec contraintes de longueur + titre associé + mot-clé spécifique
const prompt = createTypedPrompt(tag, elementType, instruction, csvData, shouldUseTitle ? lastGeneratedTitle : null, specificKeyword);
// Appeler le LLM avec maxTokens augmenté // Appeler le LLM avec maxTokens augmenté
let maxTokens = 1000; // Défaut augmenté let maxTokens = 1000; // Défaut augmenté
@ -1183,27 +1292,30 @@ function generateImprovementReport(originalContent, enhancedContent, layerType =
} }
module.exports = { module.exports = {
// Utilitaires logging
logElementsList,
// Analyseurs // Analyseurs
analyzeTechnicalQuality, analyzeTechnicalQuality,
analyzeTransitionFluidity, analyzeTransitionFluidity,
analyzeStyleConsistency, analyzeStyleConsistency,
// Comparateurs // Comparateurs
compareContentImprovement, compareContentImprovement,
// Utilitaires contenu // Utilitaires contenu
cleanGeneratedContent, cleanGeneratedContent,
validateSelectiveContent, validateSelectiveContent,
// Utilitaires techniques // Utilitaires techniques
chunkArray, chunkArray,
sleep, sleep,
measurePerformance, measurePerformance,
formatDuration, formatDuration,
// Génération simple (remplace ContentGeneration.js) // Génération simple (remplace ContentGeneration.js)
generateSimple, generateSimple,
// Rapports // Rapports
generateImprovementReport generateImprovementReport
}; };