fetchOne('SELECT class_id FROM student_sessions WHERE id = ?', [$sessionId]); $classId = $session ? $session['class_id'] : null; // Cap defensiv: nie >120 min in die DB (siehe GGS_MAX_SESSION_MS). if (!defined('GGS_MAX_SESSION_MS')) define('GGS_MAX_SESSION_MS', 7200000); $rawDuration = (int)($body['duration'] ?? 0); $durationMs = max(0, min($rawDuration, GGS_MAX_SESSION_MS)); // Zwischen-Telemetrie eines laufenden Durchgangs NICHT in assessments persistieren. // Der Live-Zustand für das Lehrer-Cockpit kommt über den Heartbeat (live_sessions); // diese 30-s-Pings (phase 'running'/'started') haben früher die Tabelle zugemüllt // (~96 % der Zeilen). Nur echte Abschlüsse/Ergebnisse werden gespeichert. $pl = $body['processLog'] ?? []; $phase = (is_array($pl) && isset($pl['phase'])) ? $pl['phase'] : null; if ($phase === 'running' || $phase === 'started') { Response::ok(['skipped' => true]); } $db->execute( 'INSERT INTO assessments (session_id, sim_id, class_id, process_log, predictions, results, reflections, duration_ms, completed_phases) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', [ $sessionId, $body['simId'], $classId, json_encode($body['processLog'] ?? []), json_encode($body['predictions'] ?? []), json_encode($body['results'] ?? []), json_encode($body['reflections'] ?? []), $durationMs, json_encode($body['completedPhases'] ?? []), ] ); Response::ok(['id' => $db->lastInsertId()]);