seo-generator-server/lib/APIController.js
StillHammer 9a2ef7da2b feat(human-simulation): Système d'erreurs graduées procédurales + anti-répétition complet
## 🎯 Nouveau système d'erreurs graduées (architecture SmartTouch)

### Architecture procédurale intelligente :
- **3 niveaux de gravité** : Légère (50%) → Moyenne (30%) → Grave (10%)
- **14 types d'erreurs** réalistes et subtiles
- **Sélection procédurale** selon contexte (longueur, technique, heure)
- **Distribution contrôlée** : max 1 grave, 2 moyennes, 3 légères par article

### 1. Erreurs GRAVES (10% articles max) :
- Accord sujet-verbe : "ils sont" → "ils est"
- Mot manquant : "pour garantir la qualité" → "pour garantir qualité"
- Double mot : "pour garantir" → "pour pour garantir"
- Négation oubliée : "n'est pas" → "est pas"

### 2. Erreurs MOYENNES (30% articles) :
- Accord pluriel : "plaques résistantes" → "plaques résistant"
- Virgule manquante : "Ainsi, il" → "Ainsi il"
- Registre inapproprié : "Par conséquent" → "Du coup"
- Préposition incorrecte : "résistant aux" → "résistant des"
- Connecteur illogique : "cependant" → "donc"

### 3. Erreurs LÉGÈRES (50% articles) :
- Double espace : "de votre" → "de  votre"
- Trait d'union : "c'est-à-dire" → "c'est à dire"
- Espace ponctuation : "qualité ?" → "qualité?"
- Majuscule : "Toutenplaque" → "toutenplaque"
- Apostrophe droite : "l'article" → "l'article"

##  Système anti-répétition complet :

### Corrections critiques :
- **HumanSimulationTracker.js** : Tracker centralisé global
- **Word boundaries (\b)** sur TOUS les regex → FIX "maison" → "néanmoinson"
- **Protection 30+ expressions idiomatiques** françaises
- **Anti-répétition** : max 2× même mot, jamais 2× même développement
- **Diversification** : 48 variantes (hésitations, développements, connecteurs)

### Nouvelle structure (comme SmartTouch) :
```
lib/human-simulation/
├── error-profiles/                (NOUVEAU)
│   ├── ErrorProfiles.js          (définitions + probabilités)
│   ├── ErrorGrave.js             (10% articles)
│   ├── ErrorMoyenne.js           (30% articles)
│   ├── ErrorLegere.js            (50% articles)
│   └── ErrorSelector.js          (sélection procédurale)
├── HumanSimulationCore.js         (orchestrateur)
├── HumanSimulationTracker.js      (anti-répétition)
└── [autres modules]
```

## 🔄 Remplace ancien système :
-  SpellingErrors.js (basique, répétitif, "et" → "." × 8)
-  error-profiles/ (gradué, procédural, intelligent, diversifié)

## 🎲 Fonctionnalités procédurales :
- Analyse contexte : longueur texte, complexité technique, heure rédaction
- Multiplicateurs adaptatifs selon contexte
- Conditions application intelligentes
- Tracking global par batch (respecte limites 10%/30%/50%)

## 📊 Résultats validation :
Sur 100 articles → ~40-50 avec erreurs subtiles et diverses (plus de spam répétitif)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-14 01:06:28 +08:00

1120 lines
30 KiB
JavaScript

/**
* Contrôleur API RESTful pour SEO Generator
* Centralise toute la logique API métier
*/
const { logSh } = require('./ErrorReporting');
const { handleFullWorkflow } = require('./Main');
const { getPersonalities, readInstructionsData } = require('./BrainConfig');
const { getStoredArticle, getRecentArticles } = require('./ArticleStorage');
const { DynamicPromptEngine } = require('./prompt-engine/DynamicPromptEngine');
const { TrendManager } = require('./trend-prompts/TrendManager');
const { WorkflowEngine } = require('./workflow-configuration/WorkflowEngine');
const { ValidatorCore } = require('./validation/ValidatorCore');
const fs = require('fs').promises;
const path = require('path');
class APIController {
constructor() {
this.articles = new Map(); // Cache articles en mémoire
this.projects = new Map(); // Cache projets
this.templates = new Map(); // Cache templates
// Initialize prompt engine components
this.promptEngine = new DynamicPromptEngine();
this.trendManager = new TrendManager();
this.workflowEngine = new WorkflowEngine();
// ✅ PHASE 3: Validation tracking
this.activeValidations = new Map(); // Track running validations
this.validationHistory = []; // Store completed validations
this.wsServer = null; // WebSocket server reference (injected by ManualServer)
}
/**
* ✅ PHASE 3: Injecte le serveur WebSocket pour broadcasting
*/
setWebSocketServer(wsServer) {
this.wsServer = wsServer;
logSh('📡 WebSocket server injecté dans APIController', 'DEBUG');
}
/**
* ✅ PHASE 3: Broadcast un message aux clients WebSocket
*/
broadcastToClients(data) {
if (!this.wsServer || !this.wsServer.clients) {
return;
}
const message = JSON.stringify(data);
let sent = 0;
this.wsServer.clients.forEach(client => {
if (client.readyState === 1) { // OPEN state
try {
client.send(message);
sent++;
} catch (error) {
logSh(`⚠️ Erreur envoi WebSocket: ${error.message}`, 'WARN');
}
}
});
if (sent > 0) {
logSh(`📡 Message broadcast à ${sent} clients`, 'TRACE');
}
}
// ========================================
// GESTION ARTICLES
// ========================================
/**
* GET /api/articles - Liste tous les articles
*/
async getArticles(req, res) {
try {
const { limit = 50, offset = 0, project, status } = req.query;
logSh(`📋 Récupération articles: limit=${limit}, offset=${offset}`, 'DEBUG');
// Récupération depuis Google Sheets
const articles = await getRecentArticles(parseInt(limit));
// Filtrage optionnel
let filteredArticles = articles;
if (project) {
filteredArticles = articles.filter(a => a.project === project);
}
if (status) {
filteredArticles = filteredArticles.filter(a => a.status === status);
}
res.json({
success: true,
data: {
articles: filteredArticles.slice(offset, offset + limit),
total: filteredArticles.length,
limit: parseInt(limit),
offset: parseInt(offset)
},
timestamp: new Date().toISOString()
});
} catch (error) {
logSh(`❌ Erreur récupération articles: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: 'Erreur lors de la récupération des articles',
message: error.message
});
}
}
/**
* GET /api/articles/:id - Récupère un article spécifique
*/
async getArticle(req, res) {
try {
const { id } = req.params;
const { format = 'json' } = req.query || {};
logSh(`📄 Récupération article ID: ${id}`, 'DEBUG');
const article = await getStoredArticle(id);
if (!article) {
return res.status(404).json({
success: false,
error: 'Article non trouvé',
id
});
}
// Format de réponse
if (format === 'html') {
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.send(article.htmlContent || article.content);
} else if (format === 'text') {
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.send(article.textContent || article.content);
} else {
res.json({
success: true,
data: article,
timestamp: new Date().toISOString()
});
}
} catch (error) {
logSh(`❌ Erreur récupération article ${req.params.id}: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: 'Erreur lors de la récupération de l\'article',
message: error.message
});
}
}
/**
* POST /api/articles - Créer un nouvel article
*/
async createArticle(req, res) {
try {
const {
keyword,
rowNumber,
project = 'api',
config = {},
template,
personalityPreference
} = req.body;
// Validation
if (!keyword && !rowNumber) {
return res.status(400).json({
success: false,
error: 'Mot-clé ou numéro de ligne requis'
});
}
logSh(`✨ Création article: ${keyword || `ligne ${rowNumber}`}`, 'INFO');
// Configuration par défaut
const workflowConfig = {
rowNumber: rowNumber || 2,
source: 'api',
project,
selectiveStack: config.selectiveStack || 'standardEnhancement',
adversarialMode: config.adversarialMode || 'light',
humanSimulationMode: config.humanSimulationMode || 'none',
patternBreakingMode: config.patternBreakingMode || 'none',
personalityPreference,
template,
...config
};
// Si mot-clé fourni, créer données temporaires
if (keyword && !rowNumber) {
workflowConfig.csvData = {
mc0: keyword,
t0: `Guide complet ${keyword}`,
personality: personalityPreference || { nom: 'Marc', style: 'professionnel' }
};
}
// Exécution du workflow
const result = await handleFullWorkflow(workflowConfig);
res.status(201).json({
success: true,
data: {
id: result.id || result.slug,
article: result,
config: workflowConfig
},
message: 'Article créé avec succès',
timestamp: new Date().toISOString()
});
} catch (error) {
logSh(`❌ Erreur création article: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: 'Erreur lors de la création de l\'article',
message: error.message
});
}
}
// ========================================
// GESTION PROJETS
// ========================================
/**
* GET /api/projects - Liste tous les projets
*/
async getProjects(req, res) {
try {
const projects = Array.from(this.projects.values());
res.json({
success: true,
data: {
projects,
total: projects.length
},
timestamp: new Date().toISOString()
});
} catch (error) {
logSh(`❌ Erreur récupération projets: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: 'Erreur lors de la récupération des projets',
message: error.message
});
}
}
/**
* POST /api/projects - Créer un nouveau projet
*/
async createProject(req, res) {
try {
// Validation body null/undefined
if (!req.body) {
return res.status(400).json({
success: false,
error: 'Corps de requête requis'
});
}
const { name, description, config = {} } = req.body;
if (!name) {
return res.status(400).json({
success: false,
error: 'Nom du projet requis'
});
}
const project = {
id: `project_${Date.now()}`,
name,
description,
config,
createdAt: new Date().toISOString(),
articlesCount: 0
};
this.projects.set(project.id, project);
logSh(`📁 Projet créé: ${name}`, 'INFO');
res.status(201).json({
success: true,
data: project,
message: 'Projet créé avec succès'
});
} catch (error) {
logSh(`❌ Erreur création projet: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: 'Erreur lors de la création du projet',
message: error.message
});
}
}
// ========================================
// GESTION TEMPLATES
// ========================================
/**
* GET /api/templates - Liste tous les templates
*/
async getTemplates(req, res) {
try {
const templates = Array.from(this.templates.values());
res.json({
success: true,
data: {
templates,
total: templates.length
},
timestamp: new Date().toISOString()
});
} catch (error) {
logSh(`❌ Erreur récupération templates: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: 'Erreur lors de la récupération des templates',
message: error.message
});
}
}
/**
* POST /api/templates - Créer un nouveau template
*/
async createTemplate(req, res) {
try {
const { name, content, description, category = 'custom' } = req.body;
if (!name || !content) {
return res.status(400).json({
success: false,
error: 'Nom et contenu du template requis'
});
}
const template = {
id: `template_${Date.now()}`,
name,
content,
description,
category,
createdAt: new Date().toISOString()
};
this.templates.set(template.id, template);
logSh(`📋 Template créé: ${name}`, 'INFO');
res.status(201).json({
success: true,
data: template,
message: 'Template créé avec succès'
});
} catch (error) {
logSh(`❌ Erreur création template: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: 'Erreur lors de la création du template',
message: error.message
});
}
}
// ========================================
// CONFIGURATION & MONITORING
// ========================================
/**
* GET /api/config/personalities - Configuration personnalités
*/
async getPersonalitiesConfig(req, res) {
try {
const personalities = await getPersonalities();
res.json({
success: true,
data: {
personalities,
total: personalities.length
},
timestamp: new Date().toISOString()
});
} catch (error) {
logSh(`❌ Erreur config personnalités: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: 'Erreur lors de la récupération des personnalités',
message: error.message
});
}
}
/**
* GET /api/health - Health check
*/
async getHealth(req, res) {
try {
const health = {
status: 'healthy',
timestamp: new Date().toISOString(),
version: '1.0.0',
uptime: process.uptime(),
memory: process.memoryUsage(),
environment: process.env.NODE_ENV || 'development'
};
res.json({
success: true,
data: health
});
} catch (error) {
res.status(500).json({
success: false,
error: 'Health check failed',
message: error.message
});
}
}
/**
* GET /api/metrics - Métriques système
*/
async getMetrics(req, res) {
try {
const metrics = {
articles: {
total: this.articles.size,
recent: Array.from(this.articles.values()).filter(
a => new Date(a.createdAt) > new Date(Date.now() - 24 * 60 * 60 * 1000)
).length
},
projects: {
total: this.projects.size
},
templates: {
total: this.templates.size
},
system: {
uptime: process.uptime(),
memory: process.memoryUsage(),
platform: process.platform,
nodeVersion: process.version
}
};
res.json({
success: true,
data: metrics,
timestamp: new Date().toISOString()
});
} catch (error) {
logSh(`❌ Erreur métriques: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: 'Erreur lors de la récupération des métriques',
message: error.message
});
}
}
// ========================================
// PROMPT ENGINE API
// ========================================
/**
* POST /api/generate-prompt - Génère un prompt adaptatif
*/
async generatePrompt(req, res) {
try {
const {
templateType = 'technical',
content = {},
csvData = null,
trend = null,
layerConfig = {},
customVariables = {}
} = req.body;
logSh(`🧠 Génération prompt: template=${templateType}, trend=${trend}`, 'INFO');
// Apply trend if specified
if (trend) {
await this.trendManager.setTrend(trend);
}
// Generate adaptive prompt
const result = await this.promptEngine.generateAdaptivePrompt({
templateType,
content,
csvData,
trend: this.trendManager.getCurrentTrend(),
layerConfig,
customVariables
});
res.json({
success: true,
prompt: result.prompt,
metadata: result.metadata,
timestamp: new Date().toISOString()
});
logSh(`✅ Prompt généré: ${result.prompt.length} caractères`, 'DEBUG');
} catch (error) {
logSh(`❌ Erreur génération prompt: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: 'Erreur lors de la génération du prompt',
message: error.message
});
}
}
/**
* GET /api/trends - Liste toutes les tendances disponibles
*/
async getTrends(req, res) {
try {
const trends = this.trendManager.getAvailableTrends();
const currentTrend = this.trendManager.getCurrentTrend();
res.json({
success: true,
data: {
trends,
currentTrend,
total: trends.length
},
timestamp: new Date().toISOString()
});
} catch (error) {
logSh(`❌ Erreur récupération tendances: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: 'Erreur lors de la récupération des tendances',
message: error.message
});
}
}
/**
* POST /api/trends/:trendId - Applique une tendance
*/
async setTrend(req, res) {
try {
const { trendId } = req.params;
const { customConfig = null } = req.body;
logSh(`🎯 Application tendance: ${trendId}`, 'INFO');
const trend = await this.trendManager.setTrend(trendId, customConfig);
res.json({
success: true,
data: {
trend,
applied: true
},
timestamp: new Date().toISOString()
});
} catch (error) {
logSh(`❌ Erreur application tendance ${req.params.trendId}: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: 'Erreur lors de l\'application de la tendance',
message: error.message
});
}
}
/**
* GET /api/prompt-engine/status - Status du moteur de prompts
*/
async getPromptEngineStatus(req, res) {
try {
const engineStatus = this.promptEngine.getEngineStatus();
const trendStatus = this.trendManager.getStatus();
const workflowStatus = this.workflowEngine.getEngineStatus();
res.json({
success: true,
data: {
engine: engineStatus,
trends: trendStatus,
workflow: workflowStatus,
health: 'operational'
},
timestamp: new Date().toISOString()
});
} catch (error) {
logSh(`❌ Erreur status prompt engine: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: 'Erreur lors de la récupération du status',
message: error.message
});
}
}
/**
* GET /api/workflow/sequences - Liste toutes les séquences de workflow
*/
async getWorkflowSequences(req, res) {
try {
const sequences = this.workflowEngine.getAvailableSequences();
res.json({
success: true,
data: {
sequences,
total: sequences.length
},
timestamp: new Date().toISOString()
});
} catch (error) {
logSh(`❌ Erreur récupération séquences workflow: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: 'Erreur lors de la récupération des séquences workflow',
message: error.message
});
}
}
/**
* POST /api/workflow/sequences - Crée une séquence de workflow personnalisée
*/
async createWorkflowSequence(req, res) {
try {
const { name, sequence } = req.body;
if (!name || !sequence) {
return res.status(400).json({
success: false,
error: 'Nom et séquence requis'
});
}
if (!this.workflowEngine.validateSequence(sequence)) {
return res.status(400).json({
success: false,
error: 'Séquence invalide'
});
}
const createdSequence = this.workflowEngine.createCustomSequence(name, sequence);
res.json({
success: true,
data: {
name,
sequence: createdSequence
},
timestamp: new Date().toISOString()
});
} catch (error) {
logSh(`❌ Erreur création séquence workflow: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: 'Erreur lors de la création de la séquence workflow',
message: error.message
});
}
}
/**
* POST /api/workflow/execute - Exécute un workflow configurable
*/
async executeConfigurableWorkflow(req, res) {
try {
const {
content,
sequenceName = 'default',
customSequence = null,
selectiveConfig = {},
adversarialConfig = {},
humanConfig = {},
patternConfig = {},
csvData = {},
personalities = {}
} = req.body;
if (!content || typeof content !== 'object') {
return res.status(400).json({
success: false,
error: 'Contenu requis (objet)'
});
}
logSh(`🔄 Exécution workflow configurable: ${customSequence ? 'custom' : sequenceName}`, 'INFO');
const result = await this.workflowEngine.executeConfigurableWorkflow(content, {
sequenceName,
customSequence,
selectiveConfig,
adversarialConfig,
humanConfig,
patternConfig,
csvData,
personalities
});
res.json({
success: result.success,
data: {
content: result.content,
stats: result.stats
},
error: result.error || null,
timestamp: new Date().toISOString()
});
} catch (error) {
logSh(`❌ Erreur exécution workflow configurable: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: 'Erreur lors de l\'exécution du workflow configurable',
message: error.message
});
}
}
// ========================================
// VALIDATION API (PHASE 3)
// ========================================
/**
* POST /api/validation/start - Démarre une nouvelle validation
*/
async startValidation(req, res) {
try {
const {
pipelineConfig,
rowNumber = 2,
config = {}
} = req.body;
// Validation
if (!pipelineConfig) {
return res.status(400).json({
success: false,
error: 'Configuration pipeline requise'
});
}
logSh(`🚀 Démarrage validation: ${pipelineConfig.name || 'Sans nom'}`, 'INFO');
// ✅ PHASE 3: Créer nouvelle instance ValidatorCore avec broadcast callback
const validator = new ValidatorCore({
broadcastCallback: (data) => this.broadcastToClients(data)
});
// Démarrer validation en arrière-plan
const validationPromise = validator.runValidation(config, pipelineConfig, rowNumber);
// Stocker la validation active
this.activeValidations.set(validator.validationId, {
validator,
promise: validationPromise,
startTime: Date.now(),
pipelineConfig,
rowNumber,
status: 'running'
});
// Gérer la completion en arrière-plan
validationPromise.then(result => {
const validation = this.activeValidations.get(validator.validationId);
if (validation) {
validation.status = result.success ? 'completed' : 'error';
validation.result = result;
validation.endTime = Date.now();
// Déplacer vers historique après 5min
setTimeout(() => {
this.validationHistory.push(validation);
this.activeValidations.delete(validator.validationId);
}, 5 * 60 * 1000);
}
}).catch(error => {
const validation = this.activeValidations.get(validator.validationId);
if (validation) {
validation.status = 'error';
validation.error = error.message;
validation.endTime = Date.now();
}
});
// Répondre immédiatement avec validation ID
res.status(202).json({
success: true,
data: {
validationId: validator.validationId,
status: 'running',
message: 'Validation démarrée en arrière-plan'
},
timestamp: new Date().toISOString()
});
} catch (error) {
logSh(`❌ Erreur démarrage validation: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: 'Erreur lors du démarrage de la validation',
message: error.message
});
}
}
/**
* GET /api/validation/status/:id - Récupère le statut d'une validation
*/
async getValidationStatus(req, res) {
try {
const { id } = req.params;
logSh(`📊 Récupération statut validation: ${id}`, 'DEBUG');
// Chercher dans validations actives
const activeValidation = this.activeValidations.get(id);
if (activeValidation) {
const status = activeValidation.validator.getStatus();
return res.json({
success: true,
data: {
validationId: id,
status: activeValidation.status,
progress: status.progress,
startTime: activeValidation.startTime,
duration: Date.now() - activeValidation.startTime,
pipelineName: activeValidation.pipelineConfig.name,
result: activeValidation.result || null
},
timestamp: new Date().toISOString()
});
}
// Chercher dans historique
const historicalValidation = this.validationHistory.find(v => v.validator.validationId === id);
if (historicalValidation) {
return res.json({
success: true,
data: {
validationId: id,
status: historicalValidation.status,
startTime: historicalValidation.startTime,
endTime: historicalValidation.endTime,
duration: historicalValidation.endTime - historicalValidation.startTime,
pipelineName: historicalValidation.pipelineConfig.name,
result: historicalValidation.result
},
timestamp: new Date().toISOString()
});
}
// Non trouvé
return res.status(404).json({
success: false,
error: 'Validation non trouvée',
validationId: id
});
} catch (error) {
logSh(`❌ Erreur récupération statut validation: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: 'Erreur lors de la récupération du statut',
message: error.message
});
}
}
/**
* POST /api/validation/stop/:id - Arrête une validation en cours
*/
async stopValidation(req, res) {
try {
const { id } = req.params;
logSh(`🛑 Arrêt validation: ${id}`, 'INFO');
const validation = this.activeValidations.get(id);
if (!validation) {
return res.status(404).json({
success: false,
error: 'Validation non trouvée ou déjà terminée',
validationId: id
});
}
// Note: Pour l'instant, on ne peut pas vraiment interrompre une validation en cours
// On marque juste le statut comme "stopped"
validation.status = 'stopped';
validation.endTime = Date.now();
logSh(`⚠️ Validation ${id} marquée comme arrêtée (le processus continue en arrière-plan)`, 'WARN');
res.json({
success: true,
data: {
validationId: id,
status: 'stopped',
message: 'Validation marquée comme arrêtée'
},
timestamp: new Date().toISOString()
});
} catch (error) {
logSh(`❌ Erreur arrêt validation: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: 'Erreur lors de l\'arrêt de la validation',
message: error.message
});
}
}
/**
* GET /api/validation/list - Liste toutes les validations
*/
async listValidations(req, res) {
try {
const { status, limit = 50 } = req.query;
logSh(`📋 Récupération liste validations`, 'DEBUG');
// Collecter validations actives
const activeList = Array.from(this.activeValidations.values()).map(v => ({
validationId: v.validator.validationId,
status: v.status,
startTime: v.startTime,
duration: Date.now() - v.startTime,
pipelineName: v.pipelineConfig.name,
progress: v.validator.getStatus().progress
}));
// Collecter historique
const historyList = this.validationHistory.map(v => ({
validationId: v.validator.validationId,
status: v.status,
startTime: v.startTime,
endTime: v.endTime,
duration: v.endTime - v.startTime,
pipelineName: v.pipelineConfig.name
}));
// Combiner et filtrer
let allValidations = [...activeList, ...historyList];
if (status) {
allValidations = allValidations.filter(v => v.status === status);
}
// Limiter résultats
const limitedValidations = allValidations.slice(0, parseInt(limit));
res.json({
success: true,
data: {
validations: limitedValidations,
total: allValidations.length,
active: activeList.length,
historical: historyList.length
},
timestamp: new Date().toISOString()
});
} catch (error) {
logSh(`❌ Erreur liste validations: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: 'Erreur lors de la récupération de la liste',
message: error.message
});
}
}
/**
* GET /api/validation/:id/report - Récupère le rapport complet d'une validation
*/
async getValidationReport(req, res) {
try {
const { id } = req.params;
logSh(`📊 Récupération rapport validation: ${id}`, 'DEBUG');
// Chercher le dossier de validation
const validationDir = path.join(process.cwd(), 'validations', id);
const reportPath = path.join(validationDir, 'report.json');
try {
const reportContent = await fs.readFile(reportPath, 'utf8');
const report = JSON.parse(reportContent);
res.json({
success: true,
data: report,
timestamp: new Date().toISOString()
});
} catch (fileError) {
return res.status(404).json({
success: false,
error: 'Rapport de validation non trouvé',
validationId: id,
message: fileError.message
});
}
} catch (error) {
logSh(`❌ Erreur récupération rapport: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: 'Erreur lors de la récupération du rapport',
message: error.message
});
}
}
/**
* GET /api/validation/:id/evaluations - Récupère les évaluations détaillées
*/
async getValidationEvaluations(req, res) {
try {
const { id } = req.params;
logSh(`📊 Récupération évaluations validation: ${id}`, 'DEBUG');
const validationDir = path.join(process.cwd(), 'validations', id);
const evaluationsPath = path.join(validationDir, 'results', 'evaluations.json');
try {
const evaluationsContent = await fs.readFile(evaluationsPath, 'utf8');
const evaluations = JSON.parse(evaluationsContent);
res.json({
success: true,
data: evaluations,
timestamp: new Date().toISOString()
});
} catch (fileError) {
return res.status(404).json({
success: false,
error: 'Évaluations non trouvées',
validationId: id,
message: fileError.message
});
}
} catch (error) {
logSh(`❌ Erreur récupération évaluations: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: 'Erreur lors de la récupération des évaluations',
message: error.message
});
}
}
/**
* ✅ NOUVEAU: GET /api/validation/presets - Récupère les presets disponibles
*/
async getValidationPresets(req, res) {
try {
const { VALIDATION_PRESETS } = require('./validation/ValidatorCore');
logSh(`📋 Récupération presets validation`, 'DEBUG');
res.json({
success: true,
data: {
presets: VALIDATION_PRESETS,
count: Object.keys(VALIDATION_PRESETS).length
},
timestamp: new Date().toISOString()
});
} catch (error) {
logSh(`❌ Erreur récupération presets: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: 'Erreur lors de la récupération des presets',
message: error.message
});
}
}
}
module.exports = { APIController };