// ================= VARIABLES GLOBALES ================= let allEvaluations = []; let scenariosData = []; let filteredEvaluations = []; let reviewsData = []; let currentReviewEvalId = null; let chart1 = null; let chart2 = null; // ================= INITIALISATION ================= document.addEventListener('DOMContentLoaded', function() { console.log('Initialisation de l\'analyse croisée...'); loadData(); }); // ================= CHARGEMENT DES DONNÉES ================= function loadData() { console.log('Début du chargement des données...'); // Afficher le statut de chargement showLoadingMessage('Chargement des données...'); // Masquer la section d'aide document.getElementById('helpSection').style.display = 'none'; // Charger les évaluations fetch('/api/evaluations') .then(response => { if (!response.ok) { console.warn('API /api/evaluations non disponible, utilisation des données locales'); // Charger depuis le fichier local return fetch('trust_stats.json').then(r => r.json()); } return response.json(); }) .then(data => { allEvaluations = data.evaluations || []; console.log(`${allEvaluations.length} évaluations chargées`); if (allEvaluations.length === 0) { console.warn('Aucune évaluation trouvée'); showWarningMessage('Aucune donnée d\'évaluation trouvée. Générer des données de test pour commencer.'); document.getElementById('helpSection').style.display = 'block'; } else { document.getElementById('warningMessage').style.display = 'none'; } // Charger les scénarios return fetch('/api/scenarios') .then(r => r.ok ? r.json() : fetch('scenarios.json').then(r2 => r2.json())) .catch(() => fetch('scenarios.json').then(r => r.json())); }) .then(data => { scenariosData = data.scenarios || []; console.log(`${scenariosData.length} scénarios chargés`); // Charger les reviews return fetch('/api/reviews') .then(r => r.ok ? r.json() : fetch('review.json').then(r2 => r2.json())) .catch(() => fetch('review.json').then(r => r.json())); }) .then(data => { reviewsData = data.reviews || []; console.log(`${reviewsData.length} reviews chargés`); // Traiter les données processEvaluations(); showSuccessMessage('Données chargées avec succès !'); }) .catch(error => { console.error('Erreur de chargement:', error); showErrorMessage(`Erreur de chargement: ${error.message}

Solutions possibles :
1. Vérifiez que les fichiers JSON existent
2. Générez des données de test avec le bouton ci-dessous
3. Vérifiez la console du navigateur (F12) pour plus de détails

`); // Afficher la section d'aide document.getElementById('helpSection').style.display = 'block'; }); } // ================= GÉNÉRATION DE DONNÉES DE TEST ================= function generateTestData() { console.log('Génération de données de test...'); showLoadingMessage('Génération des données de test...'); // Essayer d'abord l'API fetch('/api/generate_test_data') .then(response => { if (!response.ok) { // Si l'API n'est pas disponible, générer localement return generateLocalTestData(); } return response.json(); }) .then(data => { console.log('Données de test générées:', data); showSuccessMessage(`✅ Données de test générées avec succès !
- ${data.evaluations || 5} évaluations
- ${data.scenarios || scenariosData.length} scénarios
- ${data.reviews || 3} reviews`); // Recharger les données setTimeout(() => { location.reload(); }, 2000); }) .catch(error => { console.error('Erreur génération données test:', error); generateLocalTestData(); }); } function generateLocalTestData() { // Générer des évaluations de test const testEvaluations = []; const evaluators = ['Expert A', 'Expert B', 'Expert C', 'Anonyme']; const roles = ['Professeur', 'Psychologue', 'Tuteur', 'Chercheur']; for (let i = 0; i < 10; i++) { const scenarioId = Math.floor(Math.random() * 16) + 1; const evaluatorIndex = Math.floor(Math.random() * evaluators.length); const metrics = { AR: Math.floor(Math.random() * 6), AE: Math.floor(Math.random() * 6), ESR: Math.floor(Math.random() * 6), SDM: Math.floor(Math.random() * 6), SM: Math.floor(Math.random() * 6) }; const totalScore = (metrics.AR + metrics.AE + metrics.ESR + metrics.SDM + metrics.SM) * 4; testEvaluations.push({ id: `test_eval_${Date.now()}_${i}`, scenario_id: scenarioId, evaluator_name: evaluators[evaluatorIndex], evaluator_role: roles[evaluatorIndex], trust_metrics: metrics, trust_score: totalScore, comment: 'Évaluation générée automatiquement pour test', timestamp: new Date(Date.now() - Math.random() * 10000000000).toISOString(), cross_reading: Math.random() > 0.7 }); } // Générer des reviews de test const testReviews = []; for (let i = 0; i < 5; i++) { if (testEvaluations[i]) { const decisions = ['approved', 'needs_revision', 'rejected']; const decision = decisions[Math.floor(Math.random() * decisions.length)]; testReviews.push({ id: `test_review_${Date.now()}_${i}`, evaluation_id: testEvaluations[i].id, scenario_id: testEvaluations[i].scenario_id, evaluator_name: testEvaluations[i].evaluator_name, evaluator_role: testEvaluations[i].evaluator_role, original_score: testEvaluations[i].trust_score, original_comment: testEvaluations[i].comment, decision: decision, review_comment: `Review automatique - ${decision === 'approved' ? 'Approuvé' : decision === 'needs_revision' ? 'Nécessite révision' : 'Rejeté'}`, reviewer: 'Système de test', timestamp: new Date().toISOString() }); } } // Sauvegarder localement try { // Ajouter aux évaluations existantes allEvaluations = [...allEvaluations, ...testEvaluations]; // Ajouter aux reviews existants reviewsData = [...reviewsData, ...testReviews]; // Mettre à jour l'affichage processEvaluations(); filterAllEvaluations(); showSuccessMessage(`✅ Données de test générées localement !
- ${testEvaluations.length} nouvelles évaluations
- ${testReviews.length} nouveaux reviews`); return { evaluations: testEvaluations.length, reviews: testReviews.length, scenarios: scenariosData.length }; } catch (error) { console.error('Erreur génération locale:', error); showErrorMessage('Erreur lors de la génération des données de test'); throw error; } } // ================= TRAITEMENT DES DONNÉES ================= function processEvaluations() { console.log('Traitement des évaluations...'); // Vérifier s'il y a des données if (allEvaluations.length === 0) { showWarningMessage('Aucune évaluation à afficher. Utilisez le bouton "Générer des données de test" pour commencer.'); document.getElementById('helpSection').style.display = 'block'; return; } // Nettoyer les données allEvaluations.forEach((eval, index) => { // Assurer un ID unique if (!eval.id) { eval.id = `eval_${Date.now()}_${index}_${Math.random().toString(36).substr(2, 9)}`; } // Normaliser les noms de métriques if (eval.trust_metrics) { const normalizedMetrics = {}; Object.keys(eval.trust_metrics).forEach(key => { const normalizedKey = key.trim().toUpperCase(); if (normalizedKey === 'RA' || normalizedKey === 'AR') normalizedMetrics.AR = eval.trust_metrics[key]; else if (normalizedKey === 'EA' || normalizedKey === 'AE') normalizedMetrics.AE = eval.trust_metrics[key]; else if (normalizedKey === 'RE' || normalizedKey === 'ESR') normalizedMetrics.ESR = eval.trust_metrics[key]; else if (normalizedKey === 'MCS' || normalizedKey === 'SDM') normalizedMetrics.SDM = eval.trust_metrics[key]; else if (normalizedKey === 'MS' || normalizedKey === 'SM') normalizedMetrics.SM = eval.trust_metrics[key]; else normalizedMetrics[key] = eval.trust_metrics[key]; }); eval.trust_metrics = normalizedMetrics; } // Assurer que les métriques existent if (!eval.trust_metrics) { eval.trust_metrics = { AR: 0, AE: 0, ESR: 0, SDM: 0, SM: 0 }; } // Assurer un score par défaut if (eval.trust_score === undefined || eval.trust_score === null) { // Calculer le score moyen des métriques const metrics = eval.trust_metrics; const sum = (metrics.AR || 0) + (metrics.AE || 0) + (metrics.ESR || 0) + (metrics.SDM || 0) + (metrics.SM || 0); eval.trust_score = Math.round((sum / 5) * 20); // Convertir 0-5 en 0-100 } // Assurer un nom d'évaluateur if (!eval.evaluator_name) { eval.evaluator_name = 'Anonyme'; } // Assurer une date if (!eval.timestamp) { eval.timestamp = new Date().toISOString(); } // Assurer un scénario ID if (!eval.scenario_id && eval.scenario_id !== 0) { eval.scenario_id = Math.floor(Math.random() * 16) + 1; } }); // Calculer les statistiques globales updateGlobalStats(); // Créer la liste des scénarios pour le filtre populateScenarioFilter(); // Créer la liste des évaluateurs pour le filtre populateEvaluatorFilter(); // Afficher les données initiales filterData(); filterAllEvaluations(); } // ================= STATISTIQUES GLOBALES ================= function updateGlobalStats() { const totalEvaluations = allEvaluations.length; // Compter les scénarios uniques const uniqueScenarios = [...new Set(allEvaluations.map(e => e.scenario_id))].filter(id => id !== undefined && id !== null); // Compter les évaluateurs uniques const uniqueEvaluators = [...new Set(allEvaluations.map(e => e.evaluator_name || 'Anonyme'))]; // Calculer le taux de conflit global let totalConflict = 0; let analyzedScenarios = 0; uniqueScenarios.forEach(scenarioId => { const scenarioEvals = allEvaluations.filter(e => e.scenario_id === scenarioId); if (scenarioEvals.length > 1) { const avgScore = scenarioEvals.reduce((sum, e) => sum + (e.trust_score || 0), 0) / scenarioEvals.length; const variance = scenarioEvals.reduce((sum, e) => sum + Math.pow((e.trust_score || 0) - avgScore, 2), 0) / scenarioEvals.length; const stdDev = Math.sqrt(variance); totalConflict += stdDev; analyzedScenarios++; } }); const avgConflict = analyzedScenarios > 0 ? (totalConflict / analyzedScenarios) : 0; const conflictRate = Math.min(100, Math.round(avgConflict)); // Mettre à jour l'interface document.getElementById('totalEvaluations').textContent = totalEvaluations; document.getElementById('totalScenarios').textContent = uniqueScenarios.length; document.getElementById('totalEvaluators').textContent = uniqueEvaluators.length; document.getElementById('conflictRate').textContent = `${conflictRate}%`; } // ================= FILTRES ================= function populateScenarioFilter() { const filter = document.getElementById('scenarioFilter'); filter.innerHTML = ''; // Trier les scénarios par ID const uniqueScenarios = [...new Set(allEvaluations.map(e => e.scenario_id))] .filter(id => id !== undefined && id !== null) .sort((a, b) => a - b); // Ajouter les options uniqueScenarios.forEach(scenarioId => { const scenario = scenariosData.find(s => s.id === scenarioId); const option = document.createElement('option'); option.value = scenarioId; option.textContent = scenario ? `Scénario ${scenarioId} - ${scenario.title}` : `Scénario ${scenarioId}`; filter.appendChild(option); }); } function populateEvaluatorFilter() { const filter = document.getElementById('evaluatorFilter'); filter.innerHTML = ''; // Récupérer tous les évaluateurs uniques const uniqueEvaluators = [...new Set(allEvaluations.map(e => e.evaluator_name || 'Anonyme'))] .filter(name => name && name.trim() !== '') .sort(); // Ajouter les options uniqueEvaluators.forEach(evaluator => { const option = document.createElement('option'); option.value = evaluator; option.textContent = evaluator; filter.appendChild(option); }); } function filterData() { const scenarioFilter = document.getElementById('scenarioFilter').value; const confidenceFilter = document.getElementById('confidenceFilter').value; const sortBy = document.getElementById('sortBy').value; // Filtrer par scénario if (scenarioFilter === 'all') { filteredEvaluations = allEvaluations; } else { filteredEvaluations = allEvaluations.filter(e => e.scenario_id === parseInt(scenarioFilter)); } // Grouper les évaluations par scénario const groupedByScenario = {}; filteredEvaluations.forEach(eval => { if (eval.scenario_id === undefined) return; if (!groupedByScenario[eval.scenario_id]) { groupedByScenario[eval.scenario_id] = []; } groupedByScenario[eval.scenario_id].push(eval); }); // Calculer les métriques pour chaque scénario const scenarioMetrics = Object.keys(groupedByScenario).map(scenarioId => { const evaluations = groupedByScenario[scenarioId]; const scenario = scenariosData.find(s => s.id === parseInt(scenarioId)); // Calculer la confiance moyenne const avgScore = evaluations.reduce((sum, e) => sum + (e.trust_score || 0), 0) / evaluations.length; // Calculer l'écart-type (niveau de conflit) let stdDev = 0; if (evaluations.length > 1) { const variance = evaluations.reduce((sum, e) => sum + Math.pow((e.trust_score || 0) - avgScore, 2), 0) / evaluations.length; stdDev = Math.sqrt(variance); } // Déterminer le niveau de conflit let conflictLevel = 'low'; if (stdDev > 40) conflictLevel = 'high'; else if (stdDev > 20) conflictLevel = 'medium'; // Filtrer par niveau de conflit if (confidenceFilter !== 'all' && conflictLevel !== confidenceFilter) { return null; } return { scenarioId: parseInt(scenarioId), scenario: scenario, evaluations: evaluations, avgScore: avgScore, stdDev: stdDev, conflictLevel: conflictLevel, minScore: Math.min(...evaluations.map(e => e.trust_score || 0)), maxScore: Math.max(...evaluations.map(e => e.trust_score || 0)), dateRange: { min: new Date(Math.min(...evaluations.map(e => new Date(e.timestamp).getTime()))), max: new Date(Math.max(...evaluations.map(e => new Date(e.timestamp).getTime()))) } }; }).filter(metric => metric !== null); // Trier selon le critère scenarioMetrics.sort((a, b) => { switch(sortBy) { case 'scenario': return a.scenarioId - b.scenarioId; case 'conflict': return b.stdDev - a.stdDev; case 'evaluations': return b.evaluations.length - a.evaluations.length; case 'confidence': return b.avgScore - a.avgScore; default: return a.scenarioId - b.scenarioId; } }); // Mettre à jour l'affichage displayScenarioTable(scenarioMetrics); updateCharts(scenarioMetrics); } // ================= AFFICHAGE DU TABLEAU PAR SCÉNARIO ================= function displayScenarioTable(scenarioMetrics) { const tableContainer = document.getElementById('evaluationsTable'); if (scenarioMetrics.length === 0) { tableContainer.innerHTML = `

Aucune évaluation correspondant aux filtres sélectionnés

Essayez de modifier vos critères de recherche

`; return; } let html = ` `; scenarioMetrics.forEach(metric => { const conflictClass = `conflict-${metric.conflictLevel}`; const conflictText = metric.conflictLevel === 'high' ? 'Élevé' : metric.conflictLevel === 'medium' ? 'Moyen' : 'Faible'; html += ` `; }); html += `
Scénario Évaluations Confiance moyenne Niveau de conflit Plage de scores Période Actions
Scénario ${metric.scenarioId}
${metric.scenario ? metric.scenario.title : 'Non trouvé'}
${metric.evaluations.length}
${metric.evaluations.slice(0, 3).map(e => e.evaluator_name || 'Anonyme').join(', ')}${metric.evaluations.length > 3 ? '...' : ''}
${Math.round(metric.avgScore)}%
${conflictText}
Écart-type: ${metric.stdDev.toFixed(1)}%
${Math.round(metric.minScore)}% - ${Math.round(metric.maxScore)}%
Différence: ${Math.round(metric.maxScore - metric.minScore)}%
${formatDate(metric.dateRange.min)}
à ${formatDate(metric.dateRange.max)}
`; tableContainer.innerHTML = html; } // ================= TOUTES LES ÉVALUATIONS DÉTAILLÉES ================= function filterAllEvaluations() { const evaluatorFilter = document.getElementById('evaluatorFilter').value; const scoreFilter = document.getElementById('scoreFilter').value; const sortBy = document.getElementById('sortEvaluations').value; let filteredEvals = [...allEvaluations]; // Filtrer par évaluateur if (evaluatorFilter !== 'all') { filteredEvals = filteredEvals.filter(e => (e.evaluator_name || 'Anonyme') === evaluatorFilter ); } // Filtrer par score if (scoreFilter !== 'all') { filteredEvals = filteredEvals.filter(e => { const score = e.trust_score || 0; if (scoreFilter === 'high') return score >= 70; if (scoreFilter === 'medium') return score >= 40 && score < 70; if (scoreFilter === 'low') return score < 40; return true; }); } // Trier filteredEvals.sort((a, b) => { switch(sortBy) { case 'date_desc': return new Date(b.timestamp) - new Date(a.timestamp); case 'date_asc': return new Date(a.timestamp) - new Date(b.timestamp); case 'score_desc': return (b.trust_score || 0) - (a.trust_score || 0); case 'score_asc': return (a.trust_score || 0) - (b.trust_score || 0); case 'scenario': return (a.scenario_id || 0) - (b.scenario_id || 0); default: return new Date(b.timestamp) - new Date(a.timestamp); } }); // Mettre à jour l'affichage displayAllEvaluations(filteredEvals); } function displayAllEvaluations(evaluations) { const container = document.getElementById('allEvaluationsTable'); if (evaluations.length === 0) { container.innerHTML = `

Aucune évaluation correspondant aux filtres

Essayez de modifier les critères de filtrage

`; return; } let html = ` `; evaluations.forEach(eval => { const scenario = scenariosData.find(s => s.id === eval.scenario_id); const review = reviewsData.find(r => r.evaluation_id === eval.id); // Vérifier si c'est une évaluation standard ou cross-reading const isCrossReading = eval.cross_reading === true; html += ` `; }); html += `
Évaluateur Scénario Score Métriques Commentaire Review Actions
${eval.evaluator_name || 'Anonyme'}
${eval.evaluator_role || ''}
${formatDate(new Date(eval.timestamp))}
Scénario ${eval.scenario_id || 'N/A'}
${scenario ? scenario.title : 'Non trouvé'} ${isCrossReading ? '
Cross-Reading
' : ''}
${eval.trust_score || 0}%
${displayDetailedMetrics(eval.trust_metrics || {})} ${eval.comment || 'Aucun commentaire'} ${displayReviewStatus(review)}
`; container.innerHTML = html; } function displayDetailedMetrics(metrics) { const metricDefinitions = { 'AR': { name: 'AR', fullName: 'Authenticité Réflexive', question: 'L\'analyse reflète-t-elle fidèlement la situation réelle de l\'étudiant ?' }, 'AE': { name: 'AE', fullName: 'Alignement Empathique', question: 'L\'agent comprend-il les émotions, motivations et difficultés de l\'étudiant ?' }, 'ESR': { name: 'ESR', fullName: 'Encouragement Sensible au Risque', question: 'Les encouragements tiennent-ils compte des risques et difficultés identifiés ?' }, 'SDM': { name: 'SDM', fullName: 'Support aux Défis Mentaux', question: 'L\'agent aide-t-il l\'étudiant à développer sa pensée critique et ses compétences cognitives ?' }, 'SM': { name: 'SM', fullName: 'Support Métacognitif', question: 'L\'agent aide-t-il l\'étudiant à réfléchir sur ses propres stratégies d\'apprentissage ?' } }; let html = ''; Object.entries(metricDefinitions).forEach(([code, def]) => { const value = metrics[code] || 0; const stars = getStarsHTML(value); html += `
${def.fullName} (${def.name})
${stars}
${def.question}
Score: ${value}/5
`; }); return html; } function getStarsHTML(value) { let stars = ''; const normalizedValue = Math.min(5, Math.max(0, value)); // S'assurer que c'est entre 0 et 5 for (let i = 1; i <= 5; i++) { stars += ``; } return stars; } function displayReviewStatus(review) { if (!review) { return 'Non reviewé'; } const decisionText = { 'approved': '✅ Approuvé', 'needs_revision': '⚠️ Révision', 'rejected': '❌ Rejeté' }; const decisionClass = { 'approved': 'review-approved', 'needs_revision': 'review-revision', 'rejected': 'review-rejected' }; return `
${decisionText[review.decision]}
${formatDate(new Date(review.timestamp))}
${review.reviewer || 'Anonyme'}
`; } // ================= GESTION DES REVIEWS ================= function openReviewModal(evaluationId) { const evaluation = allEvaluations.find(e => e.id === evaluationId); if (!evaluation) { alert('Évaluation non trouvée'); return; } const scenario = scenariosData.find(s => s.id === evaluation.scenario_id); currentReviewEvalId = evaluationId; document.getElementById('modalEvaluator').textContent = `${evaluation.evaluator_name || 'Anonyme'} (${evaluation.evaluator_role || ''})`; document.getElementById('modalScenario').textContent = `Scénario ${evaluation.scenario_id || 'N/A'} - ${scenario ? scenario.title : 'Non trouvé'}`; document.getElementById('modalScore').textContent = `${evaluation.trust_score || 0}%`; // Charger le review existant s'il existe const existingReview = reviewsData.find(r => r.evaluation_id === evaluationId); if (existingReview) { document.getElementById('reviewDecision').value = existingReview.decision; document.getElementById('reviewComment').value = existingReview.review_comment || ''; } else { document.getElementById('reviewDecision').value = 'approved'; document.getElementById('reviewComment').value = ''; } document.getElementById('reviewModal').style.display = 'flex'; } function closeReviewModal() { document.getElementById('reviewModal').style.display = 'none'; currentReviewEvalId = null; } function saveReview() { if (!currentReviewEvalId) return; const decision = document.getElementById('reviewDecision').value; const reviewComment = document.getElementById('reviewComment').value.trim(); const evaluation = allEvaluations.find(e => e.id === currentReviewEvalId); if (!evaluation) return; // Créer l'objet review const reviewData = { evaluation_id: currentReviewEvalId, scenario_id: evaluation.scenario_id, evaluator_name: evaluation.evaluator_name, evaluator_role: evaluation.evaluator_role, original_score: evaluation.trust_score, original_comment: evaluation.comment, decision: decision, review_comment: reviewComment, reviewer: 'Expert Reviewer', timestamp: new Date().toISOString() }; // Envoyer au serveur via API fetch('/api/save_review', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(reviewData) }) .then(response => { // Vérifier si la réponse est en HTML (erreur) au lieu de JSON const contentType = response.headers.get("content-type"); if (!contentType || !contentType.includes("application/json")) { return response.text().then(text => { throw new Error(`Le serveur a renvoyé du HTML au lieu du JSON. Vérifiez que l'API /api/save_review existe.`); }); } return response.json(); }) .then(data => { if (data.status === 'ok') { // Ajouter le review localement const reviewId = `review_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; reviewsData.push({ id: reviewId, ...reviewData }); // Mettre à jour l'affichage filterAllEvaluations(); // Fermer la modale closeReviewModal(); showSuccessMessage(`✅ Review sauvegardé avec succès ! (${reviewsData.length} reviews au total)`); } else { throw new Error(data.error || 'Erreur inconnue'); } }) .catch(error => { console.error('Erreur sauvegarde review:', error); // Sauvegarde locale en cas d'erreur API const reviewId = `review_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const localReview = { id: reviewId, ...reviewData }; reviewsData.push(localReview); // Mettre à jour l'affichage filterAllEvaluations(); // Fermer la modale closeReviewModal(); showSuccessMessage(`✅ Review sauvegardé localement (API non disponible). (${reviewsData.length} reviews)`); // Afficher un avertissement showWarningMessage(`Le review a été sauvegardé localement. Pour une sauvegarde permanente, ajoutez l'endpoint /api/save_review à votre backend Flask.`); }); } // ================= GRAPHIQUES ================= function updateCharts(scenarioMetrics) { // Détruire les anciens graphiques s'ils existent if (chart1) chart1.destroy(); if (chart2) chart2.destroy(); if (scenarioMetrics.length === 0) { document.querySelectorAll('.chart-container').forEach(container => { container.innerHTML = '

Aucune donnée à afficher

'; }); return; } // Données pour les graphiques const labels = scenarioMetrics.map(m => `Scénario ${m.scenarioId}`); const avgScores = scenarioMetrics.map(m => Math.round(m.avgScore)); const stdDevs = scenarioMetrics.map(m => Math.round(m.stdDev)); const evaluationCounts = scenarioMetrics.map(m => m.evaluations.length); // Graphique 1 : Scores moyens par scénario const ctx1 = document.getElementById('confidenceChart').getContext('2d'); chart1 = new Chart(ctx1, { type: 'bar', data: { labels: labels, datasets: [{ label: 'Confiance moyenne (%)', data: avgScores, backgroundColor: avgScores.map(score => score >= 70 ? 'rgba(67, 160, 71, 0.7)' : score >= 40 ? 'rgba(251, 140, 0, 0.7)' : 'rgba(229, 57, 53, 0.7)' ), borderColor: avgScores.map(score => score >= 70 ? '#43A047' : score >= 40 ? '#FB8C00' : '#E53935' ), borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { title: { display: true, text: 'Confiance moyenne par scénario', font: { size: 16 } }, tooltip: { callbacks: { label: function(context) { const scenario = scenarioMetrics[context.dataIndex]; return [ `Confiance: ${context.parsed.y}%`, `Évaluations: ${scenario.evaluations.length}`, `Min: ${Math.round(scenario.minScore)}%, Max: ${Math.round(scenario.maxScore)}%`, `Conflit: ${scenario.conflictLevel === 'high' ? 'Élevé' : scenario.conflictLevel === 'medium' ? 'Moyen' : 'Faible'}` ]; } } } }, scales: { y: { beginAtZero: true, max: 100, title: { display: true, text: 'Score de confiance (%)' } } } } }); // Graphique 2 : Niveau de conflit const ctx2 = document.getElementById('conflictChart').getContext('2d'); chart2 = new Chart(ctx2, { type: 'line', data: { labels: labels, datasets: [ { label: 'Écart-type (conflit)', data: stdDevs, borderColor: '#E53935', backgroundColor: 'rgba(229, 57, 53, 0.1)', borderWidth: 2, fill: true, tension: 0.4 }, { label: 'Nombre d\'évaluations', data: evaluationCounts, borderColor: '#1976D2', backgroundColor: 'rgba(25, 118, 210, 0.1)', borderWidth: 2, fill: true, tension: 0.4, yAxisID: 'y1' } ] }, options: { responsive: true, maintainAspectRatio: false, plugins: { title: { display: true, text: 'Niveau de conflit et nombre d\'évaluations', font: { size: 16 } } }, scales: { y: { beginAtZero: true, title: { display: true, text: 'Écart-type (%)' } }, y1: { beginAtZero: true, position: 'right', title: { display: true, text: 'Nombre d\'évaluations' }, grid: { drawOnChartArea: false } } } } }); } // ================= DÉTAILS D'UN SCÉNARIO ================= function showScenarioDetails(scenarioId) { const scenario = scenariosData.find(s => s.id === scenarioId); const evaluations = allEvaluations.filter(e => e.scenario_id === scenarioId); if (!scenario) { alert('Scénario non trouvé'); return; } // Mettre à jour le titre document.getElementById('detailScenarioTitle').textContent = `${scenarioId} - ${scenario.title}`; // Calculer les métriques détaillées const avgScore = evaluations.reduce((sum, e) => sum + (e.trust_score || 0), 0) / evaluations.length; const metricsAvg = {}; const metricsStdDev = {}; // Initialiser les moyennes const metricKeys = ['AR', 'AE', 'ESR', 'SDM', 'SM']; metricKeys.forEach(key => { const values = evaluations.map(e => e.trust_metrics[key] || 0); metricsAvg[key] = values.reduce((sum, v) => sum + v, 0) / values.length; // Calculer l'écart-type if (values.length > 1) { const variance = values.reduce((sum, v) => sum + Math.pow(v - metricsAvg[key], 2), 0) / values.length; metricsStdDev[key] = Math.sqrt(variance); } else { metricsStdDev[key] = 0; } }); // Préparer le contenu HTML let html = `

${scenario.manifest?.manifeste || 'Pas de manifeste'}

Type: ${scenario.manifest?.type || 'Non spécifié'}

Description: ${scenario.manifest?.description || 'Pas de description'}

📊 Résumé des évaluations

Nombre d'évaluations: ${evaluations.length}

Confiance moyenne: ${Math.round(avgScore)}%

Plage de scores: ${Math.round(Math.min(...evaluations.map(e => e.trust_score || 0)))}% - ${Math.round(Math.max(...evaluations.map(e => e.trust_score || 0)))}%

Période: ${formatDate(new Date(Math.min(...evaluations.map(e => new Date(e.timestamp).getTime()))))} à ${formatDate(new Date(Math.max(...evaluations.map(e => new Date(e.timestamp).getTime()))))}

🎯 Métriques moyennes

${metricKeys.map(key => `
${getMetricName(key)}: ${metricsAvg[key].toFixed(1)}/5
`).join('')}

📋 Détail des évaluations

`; // Tableau des évaluations individuelles html += ` ${metricKeys.map(key => ``).join('')} `; evaluations.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp)).forEach(eval => { html += ` ${metricKeys.map(key => ` `).join('')} `; }); html += `
Évaluateur Date Score global${getMetricName(key)}Commentaire
${eval.evaluator_name || 'Anonyme'}
${eval.evaluator_role || ''}
${formatDate(new Date(eval.timestamp))}
${eval.trust_score || 0}%
${eval.trust_metrics[key] || 0}/5
${[1,2,3,4,5].map(i => `
`).join('')}
${eval.comment || 'Aucun commentaire'}
`; // Analyse des divergences if (evaluations.length > 1) { html += `

🔍 Analyse des divergences

`; metricKeys.forEach(key => { const values = evaluations.map(e => e.trust_metrics[key] || 0); const maxDiff = Math.max(...values) - Math.min(...values); html += `

${getMetricName(key)}

${maxDiff} pts

Différence maximale

Moyenne: ${metricsAvg[key].toFixed(1)}/5

Écart-type: ${metricsStdDev[key].toFixed(1)}

`; }); html += `
`; } // Mettre à jour le contenu et afficher document.getElementById('scenarioContent').innerHTML = html; document.getElementById('scenarioDetails').classList.add('active'); // Scroll vers les détails document.getElementById('scenarioDetails').scrollIntoView({ behavior: 'smooth' }); } // ================= FONCTIONS UTILITAIRES ================= function getMetricName(code) { const names = { 'AR': 'Authenticité Réflexive', 'AE': 'Alignement Empathique', 'ESR': 'Encouragement Sensible au Risque', 'SDM': 'Support aux Défis Mentaux', 'SM': 'Support Métacognitif' }; return names[code] || code; } function getConfidenceClass(score) { if (score >= 70) return 'high-confidence'; if (score >= 40) return 'medium-confidence'; return 'low-confidence'; } function formatDate(date) { if (!date || isNaN(date.getTime())) { return 'Date invalide'; } return date.toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }); } // ================= GESTION DES MESSAGES ================= function showLoadingMessage(message) { const loadingDiv = document.querySelector('.loading p') || document.createElement('p'); loadingDiv.textContent = message; } function showSuccessMessage(message) { const successDiv = document.getElementById('successMessage'); if (successDiv) { successDiv.innerHTML = `✅ ${message}`; successDiv.style.display = 'block'; setTimeout(() => { successDiv.style.display = 'none'; }, 5000); } } function showErrorMessage(message) { const tableContainer = document.getElementById('evaluationsTable'); if (tableContainer) { tableContainer.innerHTML = `

⚠️ Erreur de chargement

${message}
`; } } function showWarningMessage(message) { const warningDiv = document.getElementById('warningMessage'); if (warningDiv) { warningDiv.innerHTML = ` ⚠️ Aucune donnée trouvée

${message}

`; warningDiv.style.display = 'block'; } } // ================= EXPOSITION DES FONCTIONS GLOBALES ================= window.filterData = filterData; window.filterAllEvaluations = filterAllEvaluations; window.showScenarioDetails = showScenarioDetails; window.openReviewModal = openReviewModal; window.closeReviewModal = closeReviewModal; window.saveReview = saveReview; window.generateTestData = generateTestData; window.loadData = loadData;