false, 'error' => 'Accès refusé']); exit; } $id_evaluation = isset($_GET['id_evaluation']) ? (int)$_GET['id_evaluation'] : 0; if ($id_evaluation <= 0) { echo json_encode(['success' => false, 'error' => 'ID évaluation invalide']); exit; } try { $db = Database::getInstance()->getConnection(); // ======================================================================== // 1. RÉCUPÉRER INFORMATIONS ÉVALUATION // ======================================================================== $stmt = $db->prepare(" SELECT titre, duree_minutes, note_totale, actif FROM evaluations WHERE id_evaluation = ? "); $stmt->execute([$id_evaluation]); $evaluation = $stmt->fetch(PDO::FETCH_ASSOC); if (!$evaluation) { echo json_encode(['success' => false, 'error' => 'Évaluation introuvable']); exit; } // ======================================================================== // 2. RÉCUPÉRER QUESTIONS AVEC POINTS // ======================================================================== $stmt = $db->prepare(" SELECT id_question, ordre, type_question, enonce, points, reponse_correcte_json, options_json FROM questions WHERE id_evaluation = ? ORDER BY ordre ASC "); $stmt->execute([$id_evaluation]); $questions = $stmt->fetchAll(PDO::FETCH_ASSOC); $nb_questions = count($questions); // ======================================================================== // 3. RÉCUPÉRER DERNIÈRES TENTATIVES PAR ÉLÈVE // ======================================================================== $stmt = $db->prepare(" SELECT te.id_tentative, te.id_eleve, te.note, te.note_sur, te.pourcentage, te.temps_passe, te.statut, te.date_fin, te.reponses_json, u.nom, u.prenom, u.login, c.nom_classe, c.id_classe FROM ( SELECT id_eleve, MAX(id_tentative) as max_id FROM tentatives_eleves WHERE id_evaluation = ? GROUP BY id_eleve ) dernieres JOIN tentatives_eleves te ON te.id_tentative = dernieres.max_id JOIN utilisateurs u ON te.id_eleve = u.id_utilisateur LEFT JOIN classes c ON u.id_classe = c.id_classe ORDER BY te.note DESC, te.date_fin ASC "); $stmt->execute([$id_evaluation]); $tentatives = $stmt->fetchAll(PDO::FETCH_ASSOC); if (empty($tentatives)) { echo json_encode([ 'success' => true, 'evaluation' => $evaluation, 'stats' => [ 'total_eleves' => 0, 'termines' => 0, 'en_cours' => 0, 'taux_participation' => 0, 'moyenne' => 0, 'mediane' => 0, 'min' => 0, 'max' => 0, 'ecart_type' => 0, 'taux_reussite' => 0, 'temps_moyen' => '00:00:00' ], 'stats_classes' => [], 'stats_questions' => [], 'tentatives' => [], 'graphiques' => [ 'histogramme_classes' => ['labels' => [], 'datasets' => []], 'progression_eleves' => ['labels' => [], 'datasets' => []] ] ]); exit; } // ======================================================================== // 4. CALCUL STATISTIQUES GÉNÉRALES // ======================================================================== $notes = []; $temps_total = 0; $nb_termines = 0; $nb_en_cours = 0; foreach ($tentatives as $t) { if ($t['statut'] === 'terminee') { $notes[] = (float)$t['note']; $temps_total += (int)$t['temps_passe']; $nb_termines++; } else { $nb_en_cours++; } } $nb_total = count($tentatives); // Moyenne $moyenne = !empty($notes) ? array_sum($notes) / count($notes) : 0; // Médiane $mediane = 0; if (!empty($notes)) { sort($notes); $count = count($notes); $mediane = ($count % 2 === 0) ? ($notes[$count/2 - 1] + $notes[$count/2]) / 2 : $notes[floor($count/2)]; } // Min / Max $min = !empty($notes) ? min($notes) : 0; $max = !empty($notes) ? max($notes) : 0; // Écart-type $ecart_type = 0; if (count($notes) > 1) { $variance = 0; foreach ($notes as $note) { $variance += pow($note - $moyenne, 2); } $ecart_type = sqrt($variance / count($notes)); } // Taux réussite (≥10/20) $reussites = count(array_filter($notes, fn($n) => $n >= 10)); $taux_reussite = !empty($notes) ? ($reussites / count($notes)) * 100 : 0; // Temps moyen $temps_moyen_sec = $nb_termines > 0 ? $temps_total / $nb_termines : 0; $heures = floor($temps_moyen_sec / 3600); $minutes = floor(($temps_moyen_sec % 3600) / 60); $secondes = $temps_moyen_sec % 60; $temps_moyen = sprintf("%02d:%02d:%02d", $heures, $minutes, $secondes); $stats = [ 'total_eleves' => $nb_total, 'termines' => $nb_termines, 'en_cours' => $nb_en_cours, 'taux_participation' => $nb_total > 0 ? round(($nb_termines / $nb_total) * 100, 1) : 0, 'moyenne' => round($moyenne, 2), 'mediane' => round($mediane, 2), 'min' => round($min, 2), 'max' => round($max, 2), 'ecart_type' => round($ecart_type, 2), 'taux_reussite' => round($taux_reussite, 1), 'temps_moyen' => $temps_moyen ]; // ======================================================================== // 5. STATISTIQUES PAR CLASSE // ======================================================================== $par_classe = []; foreach ($tentatives as $t) { if ($t['statut'] !== 'terminee') continue; $classe = $t['nom_classe'] ?? 'Sans classe'; if (!isset($par_classe[$classe])) { $par_classe[$classe] = [ 'nom' => $classe, 'id_classe' => $t['id_classe'], 'notes' => [], 'nb_eleves' => 0 ]; } $par_classe[$classe]['notes'][] = (float)$t['note']; $par_classe[$classe]['nb_eleves']++; } // Calcul moyennes par classe $stats_classes = []; foreach ($par_classe as $classe => $data) { $moyenne_classe = array_sum($data['notes']) / count($data['notes']); $stats_classes[] = [ 'nom' => $data['nom'], 'id_classe' => $data['id_classe'], 'nb_eleves' => $data['nb_eleves'], 'moyenne' => round($moyenne_classe, 2) ]; } // Trier par moyenne décroissante usort($stats_classes, fn($a, $b) => $b['moyenne'] <=> $a['moyenne']); // ======================================================================== // 6. ANALYSE QUESTIONS (% réussite) // ======================================================================== $stats_questions = []; foreach ($questions as $q) { $id_q = $q['id_question']; $nb_reponses = 0; $nb_correctes = 0; foreach ($tentatives as $t) { if ($t['statut'] !== 'terminee') continue; $reponses = json_decode($t['reponses_json'], true) ?: []; $reponse_eleve = $reponses[$id_q] ?? null; if ($reponse_eleve === null || $reponse_eleve === '') continue; $nb_reponses++; // Vérifier si correcte (fonction du monitoring) if (verifierReponseCorrecte($q, $reponse_eleve)) { $nb_correctes++; } } $taux_reussite_q = $nb_reponses > 0 ? ($nb_correctes / $nb_reponses) * 100 : 0; $stats_questions[] = [ 'ordre' => $q['ordre'], 'enonce' => substr($q['enonce'], 0, 80) . (strlen($q['enonce']) > 80 ? '...' : ''), 'type' => $q['type_question'], 'points' => (float)$q['points'], 'nb_reponses' => $nb_reponses, 'nb_correctes' => $nb_correctes, 'taux_reussite' => round($taux_reussite_q, 1) ]; } // ======================================================================== // 7. DONNÉES GRAPHIQUE : HISTOGRAMME PAR CLASSE // ======================================================================== $tranches = [ '0-5' => ['min' => 0, 'max' => 5], '5-10' => ['min' => 5, 'max' => 10], '10-15' => ['min' => 10, 'max' => 15], '15-20' => ['min' => 15, 'max' => 20] ]; $couleurs_classes = [ 'rgba(59, 130, 246, 0.6)', // Bleu 'rgba(34, 197, 94, 0.6)', // Vert 'rgba(249, 115, 22, 0.6)', // Orange 'rgba(168, 85, 247, 0.6)', // Violet 'rgba(236, 72, 153, 0.6)', // Rose 'rgba(20, 184, 166, 0.6)', // Teal 'rgba(251, 191, 36, 0.6)', // Jaune 'rgba(239, 68, 68, 0.6)' // Rouge ]; $datasets_histo = []; $couleur_index = 0; foreach ($par_classe as $classe => $data) { $distribution = array_fill(0, count($tranches), 0); foreach ($data['notes'] as $note) { $tranche_index = 0; foreach ($tranches as $t) { if ($note >= $t['min'] && $note < $t['max']) { $distribution[$tranche_index]++; break; } // Cas note = 20 exactement if ($note == 20 && $t['max'] == 20) { $distribution[$tranche_index]++; break; } $tranche_index++; } } $datasets_histo[] = [ 'label' => $classe, 'data' => $distribution, 'backgroundColor' => $couleurs_classes[$couleur_index % count($couleurs_classes)] ]; $couleur_index++; } $graphique_histo = [ 'labels' => array_keys($tranches), 'datasets' => $datasets_histo ]; // ======================================================================== // 8. DONNÉES GRAPHIQUE : COURBE PROGRESSION PAR ÉLÈVE // ======================================================================== // Top 5 élèves (notes les plus élevées terminées) $top_eleves = array_filter($tentatives, fn($t) => $t['statut'] === 'terminee'); usort($top_eleves, fn($a, $b) => $b['note'] <=> $a['note']); $top_eleves = array_slice($top_eleves, 0, 5); $couleurs_eleves = [ 'rgb(59, 130, 246)', // Bleu 'rgb(34, 197, 94)', // Vert 'rgb(249, 115, 22)', // Orange 'rgb(168, 85, 247)', // Violet 'rgb(236, 72, 153)' // Rose ]; $labels_questions = []; foreach ($questions as $q) { $labels_questions[] = 'Q' . $q['ordre']; } $datasets_progression = []; $couleur_index = 0; foreach ($top_eleves as $eleve) { $reponses = json_decode($eleve['reponses_json'], true) ?: []; $points_cumules = []; $total = 0; foreach ($questions as $q) { $id_q = $q['id_question']; $reponse_eleve = $reponses[$id_q] ?? null; // Ajouter points si réponse correcte if ($reponse_eleve !== null && $reponse_eleve !== '' && verifierReponseCorrecte($q, $reponse_eleve)) { $total += (float)$q['points']; } $points_cumules[] = round($total, 2); } $datasets_progression[] = [ 'label' => $eleve['nom'] . ' ' . substr($eleve['prenom'], 0, 1) . '. (' . round($eleve['note'], 1) . '/' . $eleve['note_sur'] . ')', 'data' => $points_cumules, 'borderColor' => $couleurs_eleves[$couleur_index % count($couleurs_eleves)], 'backgroundColor' => 'rgba(0,0,0,0)', 'tension' => 0.3, 'borderWidth' => 2 ]; $couleur_index++; } // Ajouter courbe moyenne classe $moyenne_cumules = []; $points_moyens = array_fill(0, $nb_questions, 0); $nb_eleves_valides = 0; foreach ($tentatives as $t) { if ($t['statut'] !== 'terminee') continue; $reponses = json_decode($t['reponses_json'], true) ?: []; $total = 0; $q_index = 0; foreach ($questions as $q) { $id_q = $q['id_question']; $reponse_eleve = $reponses[$id_q] ?? null; if ($reponse_eleve !== null && $reponse_eleve !== '' && verifierReponseCorrecte($q, $reponse_eleve)) { $total += (float)$q['points']; } $points_moyens[$q_index] += $total; $q_index++; } $nb_eleves_valides++; } if ($nb_eleves_valides > 0) { foreach ($points_moyens as $pm) { $moyenne_cumules[] = round($pm / $nb_eleves_valides, 2); } $datasets_progression[] = [ 'label' => 'Moyenne classe', 'data' => $moyenne_cumules, 'borderColor' => 'rgb(156, 163, 175)', 'backgroundColor' => 'rgba(0,0,0,0)', 'borderDash' => [5, 5], 'tension' => 0.3, 'borderWidth' => 2 ]; } $graphique_progression = [ 'labels' => $labels_questions, 'datasets' => $datasets_progression ]; // ======================================================================== // 9. FORMATER TENTATIVES POUR TABLEAU // ======================================================================== $tentatives_formatted = []; $rang = 1; foreach ($tentatives as $t) { // Formater temps $temps_sec = (int)$t['temps_passe']; $h = floor($temps_sec / 3600); $m = floor(($temps_sec % 3600) / 60); $s = $temps_sec % 60; $temps_fmt = sprintf("%02d:%02d:%02d", $h, $m, $s); // Formater date $date_fmt = $t['date_fin'] ? date('d/m/Y H:i', strtotime($t['date_fin'])) : '-'; $tentatives_formatted[] = [ 'rang' => $t['statut'] === 'terminee' ? $rang : '-', 'nom' => $t['nom'], 'prenom' => $t['prenom'], 'classe' => $t['nom_classe'] ?? 'Sans classe', 'note' => round($t['note'], 2), 'note_sur' => round($t['note_sur'], 2), 'pourcentage' => round($t['pourcentage'], 1), 'temps' => $temps_fmt, 'statut' => $t['statut'], 'date_fin' => $date_fmt, 'id_tentative' => $t['id_tentative'] ]; if ($t['statut'] === 'terminee') { $rang++; } } // ======================================================================== // 10. RÉPONSE JSON FINALE // ======================================================================== echo json_encode([ 'success' => true, 'evaluation' => [ 'titre' => $evaluation['titre'], 'duree_minutes' => (int)$evaluation['duree_minutes'], 'note_totale' => (float)$evaluation['note_totale'], 'nb_questions' => $nb_questions ], 'stats' => $stats, 'stats_classes' => $stats_classes, 'stats_questions' => $stats_questions, 'tentatives' => $tentatives_formatted, 'graphiques' => [ 'histogramme_classes' => $graphique_histo, 'progression_eleves' => $graphique_progression ], 'timestamp' => date('Y-m-d H:i:s') ], JSON_UNESCAPED_UNICODE); } catch (Exception $e) { echo json_encode([ 'success' => false, 'error' => $e->getMessage() ]); } /** * Fonction vérification réponse correcte * (Copie depuis monitoring_ajax.php V3 - logique alignée sur correction) */ function verifierReponseCorrecte($question, $reponse_eleve) { if (empty($reponse_eleve) && $reponse_eleve !== '0' && $reponse_eleve !== 0) { return false; } $type = $question['type_question']; $options = json_decode($question['options_json'] ?? '{}', true); $reponse_correcte_data = json_decode($question['reponse_correcte_json'] ?? '{}', true); // Type NUMBER if ($type === 'number') { $reponse_correcte = $reponse_correcte_data['reponse'] ?? null; if ($reponse_correcte === null) return false; return abs((float)$reponse_eleve - (float)$reponse_correcte) < 0.01; } // Type TEXT if ($type === 'text') { $reponse_correcte = $reponse_correcte_data['reponse'] ?? null; if ($reponse_correcte === null) return false; return strtolower(trim($reponse_eleve)) === strtolower(trim($reponse_correcte)); } // Type SELECT if ($type === 'select') { $id_reponse_correcte = $reponse_correcte_data['id'] ?? null; if (!$id_reponse_correcte || !isset($options['options'])) return false; $texte_correct = null; foreach ($options['options'] as $opt) { if (($opt['id'] ?? '') === $id_reponse_correcte) { $texte_correct = $opt['texte'] ?? null; break; } } return ($reponse_eleve === $id_reponse_correcte || $reponse_eleve === $texte_correct); } // Type QCM if ($type === 'qcm') { if (!isset($options['reponses'])) return false; $reponse_correcte = null; foreach ($options['reponses'] as $rep) { if (isset($rep['est_correcte']) && $rep['est_correcte'] === true) { $reponse_correcte = $rep['texte']; break; } } return $reponse_eleve === $reponse_correcte; } // Type CHECKBOX if ($type === 'checkbox') { if (!isset($options['reponses'])) return false; $reponses_correctes = []; foreach ($options['reponses'] as $rep) { if (isset($rep['est_correcte']) && $rep['est_correcte'] === true) { $reponses_correctes[] = $rep['texte']; } } $reponses_eleve_array = is_array($reponse_eleve) ? $reponse_eleve : [$reponse_eleve]; sort($reponses_correctes); sort($reponses_eleve_array); return $reponses_correctes === $reponses_eleve_array; } return false; } ?>