Übersicht: Schüler-Drilldown (Klick → aktuelle Simulationsparameter, cockpit-artig)

live.php: student_runs=<id> liefert letzte Läufe (detail_json = Parameter) + Live-State
+ Recency. teacher.html: Klick auf Triage-Zeile öffnet Modal mit Erfolg/Zuletzt, Live-
Tätigkeit und den letzten Durchläufen samt geparsten Sim-Parametern (CO₂, Fehlklicks …).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-20 15:20:40 +02:00
parent 4cd6302f0a
commit 2db99c37ee
2 changed files with 115 additions and 2 deletions
+50
View File
@@ -443,6 +443,56 @@ if ($method === 'GET') {
]);
}
// ===================================================================
// SCHÜLER-DRILLDOWN — student_runs=<id>
// Aktuelle Sim-Parameter: letzte Läufe (detail_json) + Live-State + Recency.
// ===================================================================
if (isset($_GET['student_runs'])) {
$studentId = (int)$_GET['student_runs'];
$stu = $db->fetchOne(
'SELECT s.id, s.display_name, s.username, s.emoji_avatar
FROM students s JOIN classes c ON c.id = s.class_id
WHERE s.id = ? AND c.teacher_id = ?',
[$studentId, $teacherId]);
if (!$stu) Response::error('Schüler:in nicht gefunden', 404);
$runs = $db->fetchAll(
'SELECT module_id, score, duration_sec, detail_json, completed_at
FROM student_results WHERE student_id = ? ORDER BY completed_at DESC LIMIT 12',
[$studentId]);
$live = $db->fetchOne(
'SELECT module_id, state_json, UNIX_TIMESTAMP(last_seen) AS last_seen_ts
FROM live_sessions WHERE student_id = ? ORDER BY last_seen DESC LIMIT 1',
[$studentId]);
$active = $live && ((int)$live['last_seen_ts'] >= time() - LIVE_ACTIVE_WINDOW_SEC);
// Recency-Kennzahlen aus den Läufen
$recentScores = []; $daysSince = null;
foreach ($runs as $i => $r) {
if ($daysSince === null && $r['completed_at']) $daysSince = (int)floor((time() - strtotime($r['completed_at'])) / 86400);
if ($i < 3 && $r['score'] !== null) $recentScores[] = (float)$r['score'];
}
$recentErfolg = $recentScores ? (int)round(array_sum($recentScores) / count($recentScores)) : null;
Response::ok([
'studentId' => $studentId,
'displayName' => $stu['display_name'] ?: $stu['username'],
'emoji' => $stu['emoji_avatar'] ?: '🧑‍🎓',
'recentErfolg' => $recentErfolg,
'daysSince' => $daysSince,
'live' => $active ? ['moduleId' => $live['module_id'], 'state' => json_decode($live['state_json'], true)] : null,
'runs' => array_map(function ($r) {
return [
'moduleId' => $r['module_id'],
'score' => $r['score'] !== null ? (float)$r['score'] : null,
'durationSec' => (int)$r['duration_sec'],
'completedAt' => $r['completed_at'],
'params' => $r['detail_json'] ? json_decode($r['detail_json'], true) : null,
];
}, $runs),
]);
}
// Klassen-Liste: alle aktiven Sessions
$classId = (int)($_GET['class_id'] ?? 0);
if (!$classId) Response::error('class_id erforderlich');