seogeneratorserver/lib/human-simulation/TemporalStyles.js
StillHammer dbf1a3de8c Add technical plan for multi-format export system
Added plan.md with complete architecture for format-agnostic content generation:
- Support for Markdown, HTML, Plain Text, JSON formats
- New FormatExporter module with neutral data structure
- Integration strategy with existing ContentAssembly and ArticleStorage
- Bonus features: SEO metadata generation, readability scoring, WordPress Gutenberg format
- Implementation roadmap with 4 phases (6h total estimated)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 16:14:29 +08:00

511 lines
17 KiB
JavaScript

// ========================================
// FICHIER: TemporalStyles.js
// RESPONSABILITÉ: Variations temporelles d'écriture
// Simulation comportement humain selon l'heure
// ========================================
const { logSh } = require('../ErrorReporting');
/**
* STYLES TEMPORELS PAR TRANCHES HORAIRES
* Simule l'énergie et style d'écriture selon l'heure
*/
const TEMPORAL_STYLES = {
// ========================================
// MATIN (6h-11h) - Énergique et Direct
// ========================================
morning: {
period: 'matin',
timeRange: [6, 11],
energy: 'high',
characteristics: {
sentenceLength: 'short', // Phrases plus courtes
vocabulary: 'dynamic', // Mots énergiques
connectors: 'direct', // Connecteurs simples
rhythm: 'fast' // Rythme soutenu
},
vocabularyPreferences: {
energy: ['dynamique', 'efficace', 'rapide', 'direct', 'actif', 'performant'],
connectors: ['donc', 'puis', 'ensuite', 'maintenant', 'immédiatement'],
modifiers: ['très', 'vraiment', 'particulièrement', 'nettement'],
actions: ['optimiser', 'accélérer', 'améliorer', 'développer', 'créer']
},
styleTendencies: {
shortSentencesBias: 0.7, // 70% chance phrases courtes
directConnectorsBias: 0.8, // 80% connecteurs simples
energyWordsBias: 0.6 // 60% mots énergiques
}
},
// ========================================
// APRÈS-MIDI (12h-17h) - Équilibré et Professionnel
// ========================================
afternoon: {
period: 'après-midi',
timeRange: [12, 17],
energy: 'medium',
characteristics: {
sentenceLength: 'medium', // Phrases équilibrées
vocabulary: 'professional', // Vocabulaire standard
connectors: 'balanced', // Connecteurs variés
rhythm: 'steady' // Rythme régulier
},
vocabularyPreferences: {
energy: ['professionnel', 'efficace', 'qualité', 'standard', 'adapté'],
connectors: ['par ailleurs', 'de plus', 'également', 'ainsi', 'cependant'],
modifiers: ['assez', 'plutôt', 'relativement', 'suffisamment'],
actions: ['réaliser', 'développer', 'analyser', 'étudier', 'concevoir']
},
styleTendencies: {
shortSentencesBias: 0.4, // 40% phrases courtes
directConnectorsBias: 0.5, // 50% connecteurs simples
energyWordsBias: 0.3 // 30% mots énergiques
}
},
// ========================================
// SOIR (18h-23h) - Détendu et Réflexif
// ========================================
evening: {
period: 'soir',
timeRange: [18, 23],
energy: 'low',
characteristics: {
sentenceLength: 'long', // Phrases plus longues
vocabulary: 'nuanced', // Vocabulaire nuancé
connectors: 'complex', // Connecteurs élaborés
rhythm: 'relaxed' // Rythme posé
},
vocabularyPreferences: {
energy: ['approfondi', 'réfléchi', 'considéré', 'nuancé', 'détaillé'],
connectors: ['néanmoins', 'cependant', 'par conséquent', 'en outre', 'toutefois'],
modifiers: ['quelque peu', 'relativement', 'dans une certaine mesure', 'assez'],
actions: ['examiner', 'considérer', 'réfléchir', 'approfondir', 'explorer']
},
styleTendencies: {
shortSentencesBias: 0.2, // 20% phrases courtes
directConnectorsBias: 0.2, // 20% connecteurs simples
energyWordsBias: 0.1 // 10% mots énergiques
}
},
// ========================================
// NUIT (0h-5h) - Fatigue et Simplicité
// ========================================
night: {
period: 'nuit',
timeRange: [0, 5],
energy: 'very_low',
characteristics: {
sentenceLength: 'short', // Phrases courtes par fatigue
vocabulary: 'simple', // Vocabulaire basique
connectors: 'minimal', // Connecteurs rares
rhythm: 'slow' // Rythme lent
},
vocabularyPreferences: {
energy: ['simple', 'basique', 'standard', 'normal', 'classique'],
connectors: ['et', 'mais', 'ou', 'donc', 'puis'],
modifiers: ['assez', 'bien', 'pas mal', 'correct'],
actions: ['faire', 'utiliser', 'prendre', 'mettre', 'avoir']
},
styleTendencies: {
shortSentencesBias: 0.8, // 80% phrases courtes
directConnectorsBias: 0.9, // 90% connecteurs simples
energyWordsBias: 0.1 // 10% mots énergiques
}
}
};
/**
* DÉTERMINER STYLE TEMPOREL SELON L'HEURE
* @param {number} currentHour - Heure actuelle (0-23)
* @returns {object} - Style temporel correspondant
*/
function getTemporalStyle(currentHour) {
// Validation heure
const hour = Math.max(0, Math.min(23, Math.floor(currentHour || new Date().getHours())));
logSh(`⏰ Détermination style temporel pour ${hour}h`, 'DEBUG');
// Déterminer période
let selectedStyle;
if (hour >= 6 && hour <= 11) {
selectedStyle = TEMPORAL_STYLES.morning;
} else if (hour >= 12 && hour <= 17) {
selectedStyle = TEMPORAL_STYLES.afternoon;
} else if (hour >= 18 && hour <= 23) {
selectedStyle = TEMPORAL_STYLES.evening;
} else {
selectedStyle = TEMPORAL_STYLES.night;
}
logSh(`⏰ Style temporel sélectionné: ${selectedStyle.period} (énergie: ${selectedStyle.energy})`, 'DEBUG');
return {
...selectedStyle,
currentHour: hour,
timestamp: new Date().toISOString()
};
}
/**
* APPLICATION STYLE TEMPOREL
* @param {string} content - Contenu à modifier
* @param {object} temporalStyle - Style temporel à appliquer
* @param {object} options - Options { intensity }
* @returns {object} - { content, modifications }
*/
function applyTemporalStyle(content, temporalStyle, options = {}) {
if (!content || !temporalStyle) {
return { content, modifications: 0 };
}
const intensity = options.intensity || 1.0;
logSh(`⏰ Application style temporel: ${temporalStyle.period} (intensité: ${intensity})`, 'DEBUG');
let modifiedContent = content;
let modifications = 0;
// ========================================
// 1. AJUSTEMENT LONGUEUR PHRASES
// ========================================
const sentenceResult = adjustSentenceLength(modifiedContent, temporalStyle, intensity);
modifiedContent = sentenceResult.content;
modifications += sentenceResult.count;
// ========================================
// 2. ADAPTATION VOCABULAIRE
// ========================================
const vocabularyResult = adaptVocabulary(modifiedContent, temporalStyle, intensity);
modifiedContent = vocabularyResult.content;
modifications += vocabularyResult.count;
// ========================================
// 3. MODIFICATION CONNECTEURS
// ========================================
const connectorResult = adjustConnectors(modifiedContent, temporalStyle, intensity);
modifiedContent = connectorResult.content;
modifications += connectorResult.count;
// ========================================
// 4. AJUSTEMENT RYTHME
// ========================================
const rhythmResult = adjustRhythm(modifiedContent, temporalStyle, intensity);
modifiedContent = rhythmResult.content;
modifications += rhythmResult.count;
logSh(`⏰ Style temporel appliqué: ${modifications} modifications`, 'DEBUG');
return {
content: modifiedContent,
modifications
};
}
/**
* AJUSTEMENT LONGUEUR PHRASES
*/
function adjustSentenceLength(content, temporalStyle, intensity) {
let modified = content;
let count = 0;
const bias = temporalStyle.styleTendencies.shortSentencesBias * intensity;
const sentences = modified.split('. ');
// Probabilité d'appliquer les modifications
if (Math.random() > intensity * 0.9) { // FIXÉ: Presque toujours appliquer (était 0.7)
return { content: modified, count };
}
const processedSentences = sentences.map(sentence => {
if (sentence.length < 20) return sentence; // Ignorer phrases très courtes
// Style MATIN/NUIT - Raccourcir phrases longues
if ((temporalStyle.period === 'matin' || temporalStyle.period === 'nuit') &&
sentence.length > 100 && Math.random() < bias) {
// Chercher point de coupe naturel
const cutPoints = [', qui', ', que', ', dont', ' et ', ' car ', ' mais '];
for (const cutPoint of cutPoints) {
const cutIndex = sentence.indexOf(cutPoint);
if (cutIndex > 30 && cutIndex < sentence.length - 30) {
count++;
logSh(` ✂️ Phrase raccourcie (${temporalStyle.period}): ${sentence.length}${cutIndex} chars`, 'DEBUG');
return sentence.substring(0, cutIndex) + '. ' +
sentence.substring(cutIndex + cutPoint.length);
}
}
}
// Style SOIR - Allonger phrases courtes
if (temporalStyle.period === 'soir' &&
sentence.length > 30 && sentence.length < 80 &&
Math.random() < (1 - bias)) {
// Ajouter développements
const developments = [
', ce qui constitue un avantage notable',
', permettant ainsi d\'optimiser les résultats',
', dans une démarche d\'amélioration continue',
', contribuant à l\'efficacité globale'
];
const development = developments[Math.floor(Math.random() * developments.length)];
count++;
logSh(` 📝 Phrase allongée (soir): ${sentence.length}${sentence.length + development.length} chars`, 'DEBUG');
return sentence + development;
}
return sentence;
});
modified = processedSentences.join('. ');
return { content: modified, count };
}
/**
* ADAPTATION VOCABULAIRE
*/
function adaptVocabulary(content, temporalStyle, intensity) {
let modified = content;
let count = 0;
const vocabularyPrefs = temporalStyle.vocabularyPreferences;
const energyBias = temporalStyle.styleTendencies.energyWordsBias * intensity;
// Probabilité d'appliquer
if (Math.random() > intensity * 0.9) { // FIXÉ: Presque toujours appliquer (était 0.6)
return { content: modified, count };
}
// Remplacements selon période
const replacements = buildVocabularyReplacements(temporalStyle.period, vocabularyPrefs);
replacements.forEach(replacement => {
if (Math.random() < Math.max(0.6, energyBias)) { // FIXÉ: Minimum 60% chance
const regex = new RegExp(`\\b${replacement.from}\\b`, 'gi');
if (modified.match(regex)) {
modified = modified.replace(regex, replacement.to);
count++;
logSh(` 📚 Vocabulaire adapté (${temporalStyle.period}): "${replacement.from}" → "${replacement.to}"`, 'DEBUG');
}
}
});
// AJOUT FIX: Si aucun remplacement, forcer au moins une modification temporelle basique
if (count === 0 && Math.random() < 0.5) {
// Modification basique selon période
if (temporalStyle.period === 'matin' && modified.includes('utiliser')) {
modified = modified.replace(/\butiliser\b/gi, 'optimiser');
count++;
logSh(` 📚 Modification temporelle forcée: utiliser → optimiser`, 'DEBUG');
}
}
return { content: modified, count };
}
/**
* CONSTRUCTION REMPLACEMENTS VOCABULAIRE
*/
function buildVocabularyReplacements(period, vocabPrefs) {
const replacements = [];
switch (period) {
case 'matin':
replacements.push(
{ from: 'bon', to: 'excellent' },
{ from: 'intéressant', to: 'dynamique' },
{ from: 'utiliser', to: 'optimiser' },
{ from: 'faire', to: 'créer' }
);
break;
case 'soir':
replacements.push(
{ from: 'bon', to: 'considérable' },
{ from: 'faire', to: 'examiner' },
{ from: 'utiliser', to: 'exploiter' },
{ from: 'voir', to: 'considérer' }
);
break;
case 'nuit':
replacements.push(
{ from: 'excellent', to: 'bien' },
{ from: 'optimiser', to: 'utiliser' },
{ from: 'considérable', to: 'correct' },
{ from: 'examiner', to: 'regarder' }
);
break;
default: // après-midi
// Vocabulaire équilibré - pas de remplacements drastiques
break;
}
return replacements;
}
/**
* AJUSTEMENT CONNECTEURS
*/
function adjustConnectors(content, temporalStyle, intensity) {
let modified = content;
let count = 0;
const connectorBias = temporalStyle.styleTendencies.directConnectorsBias * intensity;
const preferredConnectors = temporalStyle.vocabularyPreferences.connectors;
if (Math.random() > intensity * 0.5) {
return { content: modified, count };
}
// Connecteurs selon période
const connectorMappings = {
matin: [
{ from: /par conséquent/gi, to: 'donc' },
{ from: /néanmoins/gi, to: 'mais' },
{ from: /en outre/gi, to: 'aussi' }
],
soir: [
{ from: /donc/gi, to: 'par conséquent' },
{ from: /mais/gi, to: 'néanmoins' },
{ from: /aussi/gi, to: 'en outre' }
],
nuit: [
{ from: /par conséquent/gi, to: 'donc' },
{ from: /néanmoins/gi, to: 'mais' },
{ from: /cependant/gi, to: 'mais' }
]
};
const mappings = connectorMappings[temporalStyle.period] || [];
mappings.forEach(mapping => {
if (Math.random() < connectorBias) {
if (modified.match(mapping.from)) {
modified = modified.replace(mapping.from, mapping.to);
count++;
logSh(` 🔗 Connecteur adapté (${temporalStyle.period}): "${mapping.from}" → "${mapping.to}"`, 'DEBUG');
}
}
});
return { content: modified, count };
}
/**
* AJUSTEMENT RYTHME
*/
function adjustRhythm(content, temporalStyle, intensity) {
let modified = content;
let count = 0;
// Le rythme affecte la ponctuation et les pauses
if (Math.random() > intensity * 0.3) {
return { content: modified, count };
}
switch (temporalStyle.characteristics.rhythm) {
case 'fast': // Matin - moins de virgules, plus direct
if (Math.random() < 0.4) {
// Supprimer quelques virgules non essentielles
const originalCommas = (modified.match(/,/g) || []).length;
modified = modified.replace(/, qui /gi, ' qui ');
modified = modified.replace(/, que /gi, ' que ');
const newCommas = (modified.match(/,/g) || []).length;
count = originalCommas - newCommas;
if (count > 0) {
logSh(` ⚡ Rythme accéléré: ${count} virgules supprimées`, 'DEBUG');
}
}
break;
case 'relaxed': // Soir - plus de pauses
if (Math.random() < 0.3) {
// Ajouter quelques pauses réflexives
modified = modified.replace(/\. ([A-Z])/g, '. Ainsi, $1');
count++;
logSh(` 🧘 Rythme ralenti: pauses ajoutées`, 'DEBUG');
}
break;
case 'slow': // Nuit - simplification
if (Math.random() < 0.5) {
// Simplifier structures complexes
modified = modified.replace(/ ; /g, '. ');
count++;
logSh(` 😴 Rythme simplifié: structures allégées`, 'DEBUG');
}
break;
}
return { content: modified, count };
}
/**
* ANALYSE COHÉRENCE TEMPORELLE
* @param {string} content - Contenu à analyser
* @param {object} temporalStyle - Style appliqué
* @returns {object} - Métriques de cohérence
*/
function analyzeTemporalCoherence(content, temporalStyle) {
const sentences = content.split('. ');
const avgSentenceLength = sentences.reduce((sum, s) => sum + s.length, 0) / sentences.length;
const energyWords = temporalStyle.vocabularyPreferences.energy;
const energyWordCount = energyWords.reduce((count, word) => {
const regex = new RegExp(`\\b${word}\\b`, 'gi');
return count + (content.match(regex) || []).length;
}, 0);
return {
avgSentenceLength,
energyWordDensity: energyWordCount / sentences.length,
period: temporalStyle.period,
coherenceScore: calculateCoherenceScore(avgSentenceLength, temporalStyle),
expectedCharacteristics: temporalStyle.characteristics
};
}
/**
* CALCUL SCORE COHÉRENCE
*/
function calculateCoherenceScore(avgLength, temporalStyle) {
let score = 1.0;
// Vérifier cohérence longueur phrases avec période
const expectedLength = {
'matin': { min: 40, max: 80 },
'après-midi': { min: 60, max: 120 },
'soir': { min: 80, max: 150 },
'nuit': { min: 30, max: 70 }
};
const expected = expectedLength[temporalStyle.period];
if (expected) {
if (avgLength < expected.min || avgLength > expected.max) {
score *= 0.7;
}
}
return Math.max(0, Math.min(1, score));
}
// ============= EXPORTS =============
module.exports = {
getTemporalStyle,
applyTemporalStyle,
adjustSentenceLength,
adaptVocabulary,
adjustConnectors,
adjustRhythm,
analyzeTemporalCoherence,
calculateCoherenceScore,
buildVocabularyReplacements,
TEMPORAL_STYLES
};