// ======================================== // FICHIER: SyntaxVariations.js // RESPONSABILITÉ: Variations syntaxiques pour casser patterns LLM // Techniques: découpage, fusion, restructuration phrases // ======================================== const { logSh } = require('../ErrorReporting'); /** * BINÔMES COURANTS À PRÉSERVER * Paires de mots qui doivent rester ensemble (cohésion sémantique) */ const COMMON_BINOMES = [ // Binômes avec "et" 'esthétique et praticité', 'esthétique et pratique', 'style et durabilité', 'design et fonctionnalité', 'élégance et performance', 'qualité et prix', 'rapidité et efficacité', 'simplicité et efficacité', 'confort et sécurité', 'robustesse et légèreté', 'durabilité et résistance', 'performance et fiabilité', 'innovation et tradition', 'modernité et authenticité', 'sur mesure et fiable', 'faciles à manipuler et à installer', 'manipuler et à installer', 'à manipuler et à installer', // ✅ NOUVEAU: Compléments de nom (nom + adjectif possessif) 'son éclat et sa lisibilité', 'son éclat et sa', 'sa lisibilité et son', 'votre adresse et votre', 'leur durabilité et leur', 'notre gamme et nos', // ✅ NOUVEAU: Couples nom + complément descriptif 'personnalisation et élégance', 'qualité et performance', 'résistance et esthétique', 'praticité et design', 'fonctionnalité et style', 'efficacité et confort', 'solidité et légèreté', 'authenticité et modernité' ]; /** * PATTERNS REGEX POUR DÉTECTER COMPLÉMENTS DE NOM * Patterns dynamiques à ne jamais couper */ const COMPLEMENT_PATTERNS = [ // Possessifs + nom + et + possessif + nom /\b(son|sa|ses|votre|vos|leur|leurs|notre|nos)\s+\w+\s+et\s+(son|sa|ses|votre|vos|leur|leurs|notre|nos)\s+\w+\b/gi, // Nom abstrait + et + nom abstrait (max 20 chars chacun) /\b(personnalisation|durabilité|résistance|esthétique|élégance|qualité|performance|praticité|fonctionnalité|efficacité|solidité|authenticité|modernité)\s+et\s+(personnalisation|durabilité|résistance|esthétique|élégance|qualité|performance|praticité|fonctionnalité|efficacité|solidité|authenticité|modernité)\b/gi ]; /** * VALIDATION BINÔMES * Vérifie si un texte contient un binôme à préserver (liste + patterns regex) */ function containsBinome(text) { const lowerText = text.toLowerCase(); // 1. Vérifier liste statique de binômes const hasStaticBinome = COMMON_BINOMES.some(binome => lowerText.includes(binome.toLowerCase()) ); if (hasStaticBinome) { return true; } // 2. Vérifier patterns regex dynamiques (compléments de nom) const hasDynamicPattern = COMPLEMENT_PATTERNS.some(pattern => { // Reset regex (important pour réutilisation) pattern.lastIndex = 0; return pattern.test(text); }); return hasDynamicPattern; } /** * PATTERNS SYNTAXIQUES TYPIQUES LLM À ÉVITER */ const LLM_SYNTAX_PATTERNS = { // Structures trop prévisibles repetitiveStarts: [ /^Il est important de/gi, /^Il convient de/gi, /^Il faut noter que/gi, /^Dans ce contexte/gi, /^Par ailleurs/gi ], // Phrases trop parfaites perfectStructures: [ /^De plus, .+ En outre, .+ Enfin,/gi, /^Premièrement, .+ Deuxièmement, .+ Troisièmement,/gi ], // Longueurs trop régulières (détection pattern) uniformLengths: true // Détecté dynamiquement }; /** * VARIATION STRUCTURES SYNTAXIQUES - FONCTION PRINCIPALE * @param {string} text - Texte à varier * @param {number} intensity - Intensité variation (0-1) * @param {object} options - Options { preserveReadability, maxModifications } * @returns {object} - { content, modifications, stats } */ function varyStructures(text, intensity = 0.3, options = {}) { if (!text || text.trim().length === 0) { return { content: text, modifications: 0 }; } const config = { preserveReadability: true, maxModifications: 3, ...options }; logSh(`📝 Variation syntaxique: intensité ${intensity}, préservation: ${config.preserveReadability}`, 'DEBUG'); let modifiedText = text; let totalModifications = 0; const stats = { sentencesSplit: 0, sentencesMerged: 0, structuresReorganized: 0, repetitiveStartsFixed: 0 }; try { // 1. Analyser structure phrases const sentences = analyzeSentenceStructure(modifiedText); logSh(` 📊 ${sentences.length} phrases analysées`, 'DEBUG'); // 2. Découper phrases longues if (Math.random() < intensity) { const splitResult = splitLongSentences(modifiedText, intensity); modifiedText = splitResult.content; totalModifications += splitResult.modifications; stats.sentencesSplit = splitResult.modifications; } // 3. Fusionner phrases courtes if (Math.random() < intensity * 0.7) { const mergeResult = mergeShorter(modifiedText, intensity); modifiedText = mergeResult.content; totalModifications += mergeResult.modifications; stats.sentencesMerged = mergeResult.modifications; } // 4. Réorganiser structures prévisibles if (Math.random() < intensity * 0.8) { const reorganizeResult = reorganizeStructures(modifiedText, intensity); modifiedText = reorganizeResult.content; totalModifications += reorganizeResult.modifications; stats.structuresReorganized = reorganizeResult.modifications; } // 5. Corriger débuts répétitifs if (Math.random() < intensity * 0.6) { const repetitiveResult = fixRepetitiveStarts(modifiedText); modifiedText = repetitiveResult.content; totalModifications += repetitiveResult.modifications; stats.repetitiveStartsFixed = repetitiveResult.modifications; } // 6. Limitation sécurité if (totalModifications > config.maxModifications) { logSh(` ⚠️ Limitation appliquée: ${totalModifications} → ${config.maxModifications} modifications`, 'DEBUG'); totalModifications = config.maxModifications; } logSh(`📝 Syntaxe modifiée: ${totalModifications} changements (${stats.sentencesSplit} splits, ${stats.sentencesMerged} merges)`, 'DEBUG'); } catch (error) { logSh(`❌ Erreur variation syntaxique: ${error.message}`, 'WARNING'); return { content: text, modifications: 0, stats: {} }; } return { content: modifiedText, modifications: totalModifications, stats }; } /** * ANALYSE STRUCTURE PHRASES */ function analyzeSentenceStructure(text) { const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 0); return sentences.map((sentence, index) => ({ index, content: sentence.trim(), length: sentence.trim().length, wordCount: sentence.trim().split(/\s+/).length, isLong: sentence.trim().length > 120, isShort: sentence.trim().length < 40, hasComplexStructure: sentence.includes(',') && sentence.includes(' qui ') || sentence.includes(' que ') })); } /** * DÉCOUPAGE PHRASES LONGUES */ function splitLongSentences(text, intensity) { let modified = text; let modifications = 0; const sentences = modified.split('. '); const processedSentences = sentences.map(sentence => { // ✅ VALIDATION BINÔME: Ne pas découper si contient binôme if (containsBinome(sentence)) { return sentence; } // Phrases longues (>100 chars) et probabilité selon intensité - PLUS AGRESSIF if (sentence.length > 100 && Math.random() < (intensity * 0.6)) { // Points de découpe naturels - ✅ Connecteurs variés (SANS "Ajoutons que") const connectorsPool = [ 'Également', 'Aussi', 'En outre', 'Par ailleurs', 'Qui plus est', 'Mieux encore', 'À cela s\'ajoute' // ❌ RETIRÉ: 'Ajoutons que' ]; const randomConnector = connectorsPool[Math.floor(Math.random() * connectorsPool.length)]; const cutPoints = [ { pattern: /, qui (.+)/, replacement: '. Celui-ci $1' }, { pattern: /, que (.+)/, replacement: '. Cela $1' }, { pattern: /, dont (.+)/, replacement: '. Celui-ci $1' }, { pattern: / et (.{30,})/, replacement: `. ${randomConnector}, $1` }, // ✅ Connecteur aléatoire { pattern: /, car (.+)/, replacement: '. En effet, $1' }, { pattern: /, mais (.+)/, replacement: '. Cependant, $1' } ]; for (const cutPoint of cutPoints) { if (sentence.match(cutPoint.pattern)) { const newSentence = sentence.replace(cutPoint.pattern, cutPoint.replacement); if (newSentence !== sentence) { modifications++; logSh(` ✂️ Phrase découpée: ${sentence.length} → ${newSentence.length} chars`, 'DEBUG'); return newSentence; } } } } return sentence; }); return { content: processedSentences.join('. '), modifications }; } /** * FUSION PHRASES COURTES */ function mergeShorter(text, intensity) { let modified = text; let modifications = 0; const sentences = modified.split('. '); const processedSentences = []; for (let i = 0; i < sentences.length; i++) { const current = sentences[i]; const next = sentences[i + 1]; // Si phrase courte (<50 chars) et phrase suivante existe - PLUS AGRESSIF if (current && current.length < 50 && next && next.length < 70 && Math.random() < (intensity * 0.5)) { // ✅ VALIDATION BINÔME: Ne pas fusionner si binôme présent const combined = current + ' ' + next; if (containsBinome(combined)) { processedSentences.push(current); continue; } // Connecteurs pour fusion naturelle - ✅ Variés et originaux const connectors = [ ', également,', ', aussi,', ', mais également,', ' et', ' ;', ', tout en', ', sans oublier', ', voire même', ', qui plus est,', ', d\'autant plus que' // ✅ Originaux ]; const connector = connectors[Math.floor(Math.random() * connectors.length)]; const merged = current + connector + ' ' + next.toLowerCase(); processedSentences.push(merged); modifications++; logSh(` 🔗 Phrases fusionnées: ${current.length} + ${next.length} → ${merged.length} chars`, 'DEBUG'); i++; // Passer la phrase suivante car fusionnée } else { processedSentences.push(current); } } return { content: processedSentences.join('. '), modifications }; } /** * RÉORGANISATION STRUCTURES PRÉVISIBLES */ function reorganizeStructures(text, intensity) { let modified = text; let modifications = 0; // Détecter énumérations prévisibles const enumerationPatterns = [ { pattern: /Premièrement, (.+?)\. Deuxièmement, (.+?)\. Troisièmement, (.+?)\./gi, replacement: 'D\'abord, $1. Ensuite, $2. Enfin, $3.' }, { pattern: /D\'une part, (.+?)\. D\'autre part, (.+?)\./gi, replacement: 'Tout d\'abord, $1. Par ailleurs, $2.' }, { pattern: /En premier lieu, (.+?)\. En second lieu, (.+?)\./gi, replacement: 'Dans un premier temps, $1. Puis, $2.' } ]; enumerationPatterns.forEach(pattern => { if (modified.match(pattern.pattern) && Math.random() < intensity) { modified = modified.replace(pattern.pattern, pattern.replacement); modifications++; logSh(` 🔄 Structure réorganisée: énumération variée`, 'DEBUG'); } }); return { content: modified, modifications }; } /** * CORRECTION DÉBUTS RÉPÉTITIFS */ function fixRepetitiveStarts(text) { let modified = text; let modifications = 0; const sentences = modified.split('. '); const startWords = []; // Analyser débuts de phrases sentences.forEach(sentence => { const words = sentence.trim().split(/\s+/); if (words.length > 0) { startWords.push(words[0].toLowerCase()); } }); // Détecter répétitions const startCounts = {}; startWords.forEach(word => { startCounts[word] = (startCounts[word] || 0) + 1; }); // Remplacer débuts répétitifs const alternatives = { 'il': ['Cet élément', 'Cette solution', 'Ce produit'], 'cette': ['Cette option', 'Cette approche', 'Cette méthode'], 'pour': ['Afin de', 'Dans le but de', 'En vue de'], 'avec': ['Grâce à', 'Au moyen de', 'En utilisant'], 'dans': ['Au sein de', 'À travers', 'Parmi'] }; const processedSentences = sentences.map(sentence => { const firstWord = sentence.trim().split(/\s+/)[0]?.toLowerCase(); if (firstWord && startCounts[firstWord] > 2 && alternatives[firstWord] && Math.random() < 0.4) { const replacement = alternatives[firstWord][Math.floor(Math.random() * alternatives[firstWord].length)]; const newSentence = sentence.replace(/^\w+/, replacement); modifications++; logSh(` 🔄 Début varié: "${firstWord}" → "${replacement}"`, 'DEBUG'); return newSentence; } return sentence; }); return { content: processedSentences.join('. '), modifications }; } /** * DÉTECTION UNIFORMITÉ LONGUEURS (Pattern LLM) */ function detectUniformLengths(text) { const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 0); if (sentences.length < 3) return { uniform: false, variance: 0 }; const lengths = sentences.map(s => s.trim().length); const avgLength = lengths.reduce((sum, len) => sum + len, 0) / lengths.length; // Calculer variance const variance = lengths.reduce((sum, len) => sum + Math.pow(len - avgLength, 2), 0) / lengths.length; const standardDev = Math.sqrt(variance); // Uniformité si écart-type faible par rapport à moyenne const coefficientVariation = standardDev / avgLength; const uniform = coefficientVariation < 0.3; // Seuil arbitraire return { uniform, variance: coefficientVariation, avgLength, standardDev, sentenceCount: sentences.length }; } /** * AJOUT VARIATIONS MICRO-SYNTAXIQUES */ function addMicroVariations(text, intensity) { let modified = text; let modifications = 0; // Micro-variations subtiles const microPatterns = [ { from: /\btrès (.+?)\b/g, to: 'particulièrement $1', probability: 0.3 }, { from: /\bassez (.+?)\b/g, to: 'plutôt $1', probability: 0.4 }, { from: /\bbeaucoup de/g, to: 'de nombreux', probability: 0.3 }, { from: /\bpermets de/g, to: 'permet de', probability: 0.8 }, // Correction fréquente { from: /\bien effet\b/g, to: 'effectivement', probability: 0.2 } ]; microPatterns.forEach(pattern => { if (Math.random() < (intensity * pattern.probability)) { const before = modified; modified = modified.replace(pattern.from, pattern.to); if (modified !== before) { modifications++; logSh(` 🔧 Micro-variation: ${pattern.from} → ${pattern.to}`, 'DEBUG'); } } }); return { content: modified, modifications }; } // ============= EXPORTS ============= module.exports = { varyStructures, splitLongSentences, mergeShorter, reorganizeStructures, fixRepetitiveStarts, analyzeSentenceStructure, detectUniformLengths, addMicroVariations, LLM_SYNTAX_PATTERNS };