Tag-Ansicht: echte Spieldauer + Ampelfarbe nach Erfolg + Sim-Symbol

Bug: fertig gespielte Laeufe (Farmer-Endbericht = 1 Assessment) erschienen als
kurzer "Abbruch"-Balken (Dauer 0). Fix:
- live.php summary: nutzt duration_ms -> Balken decken die echte Spielzeit ab und
  fluchten am Start; kein Falsch-Abbruch mehr. Neues Feld `score` (0-100) je
  Session (Score/Sterne); laufende Farmer-Sessions bekommen hit_rate als Live-Score.
- teacher.html Tag-Balken: einheitliche Hoehe (Hoehe raus), Farbe = Ampel nach
  Erfolg (gruen->rot), App-Farbe ersetzt durch Sim-Symbol; Klick = Details.
  Legende angepasst.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-22 12:56:47 +02:00
parent 85ee58c56d
commit 4e38b27c64
2 changed files with 69 additions and 27 deletions
+28
View File
@@ -141,6 +141,14 @@ if ($method === 'GET') {
if (($pl['phase'] ?? '') === 'completed') return true;
return null;
};
// Erfolgs-Score 0100 (für die Ampelfarbe der Tages-Balken). Sterne → %,
// sonst der Sim-Score (Farmer/Weltküche liefern `score`).
$extractScore = function($row) {
$r = $row['results'] ? json_decode($row['results'], true) : [];
foreach ([$r['score'] ?? null, $r['results']['score'] ?? null] as $c) if (is_numeric($c)) return max(0, min(100, (int)round($c)));
if (isset($r['stars']) && is_numeric($r['stars'])) return (int)round($r['stars'] / 5 * 100);
return null;
};
// Gruppieren: student_id × sim_id × lesson_key → eine ODER MEHRERE Sessions.
// Wichtig: bei Lücke > SESSION_GAP_SEC zwischen aufeinanderfolgenden Submits
@@ -197,6 +205,8 @@ if ($method === 'GET') {
'startMs' => $ts * 1000,
'endMs' => $ts * 1000,
'stars' => null,
'score' => null,
'durationMs' => 0,
'completed' => null,
'rowCount' => 0,
];
@@ -205,6 +215,10 @@ if ($method === 'GET') {
$currentSess['rowCount']++;
$st = $extractStars($row);
if ($st !== null && ($currentSess['stars'] === null || $st > $currentSess['stars'])) $currentSess['stars'] = $st;
$scv = $extractScore($row);
if ($scv !== null && ($currentSess['score'] === null || $scv > $currentSess['score'])) $currentSess['score'] = $scv;
$dmv = (int)($row['duration_ms'] ?? 0);
if ($dmv > $currentSess['durationMs']) $currentSess['durationMs'] = $dmv;
$cp = $extractCompleted($row);
if ($cp !== null) $currentSess['completed'] = $cp;
$prevTs = $ts;
@@ -227,6 +241,14 @@ if ($method === 'GET') {
];
}
$durSec = max(0, intval(($s['endMs'] - $s['startMs']) / 1000));
// Echte Spieldauer aus duration_ms — ein einzelnes Assessment (z. B. der
// Farmer-Endbericht) hätte sonst Dauer ~0 und würde faelschlich als
// „Abbruch" gewertet. Startzeit entsprechend zurückdatieren, damit der
// Balken die ganze Spielzeit abdeckt (und gleich startende Läufe fluchten).
if (!empty($s['durationMs']) && $s['durationMs'] > $durSec * 1000) {
$durSec = intval($s['durationMs'] / 1000);
$s['startMs'] = $s['endMs'] - $s['durationMs'];
}
// Success-Klassifikation
$success = 'unknown';
if ($s['completed'] === false) $success = 'aborted';
@@ -234,6 +256,8 @@ if ($method === 'GET') {
if ($s['stars'] >= 4) $success = 'good';
elseif ($s['stars'] >= 2) $success = 'mid';
else $success = 'bad';
} elseif ($s['score'] !== null) {
$success = $s['score'] >= 65 ? 'good' : ($s['score'] >= 40 ? 'mid' : 'bad');
} elseif ($s['completed'] === true) $success = 'good';
elseif ($durSec < 60) $success = 'aborted';
else $success = 'running';
@@ -247,6 +271,7 @@ if ($method === 'GET') {
'endMs' => $s['endMs'],
'durationSec' => $durSec,
'stars' => $s['stars'],
'score' => $s['score'],
'completed' => $s['completed'],
'success' => $success,
'rowCount' => $s['rowCount'],
@@ -314,6 +339,8 @@ if ($method === 'GET') {
$startMs = ((int)$lr['started_ts']) * 1000;
$endMs = ((int)$lr['last_seen_ts']) * 1000;
$durSec = max(0, intval(($endMs - $startMs) / 1000));
// Live-Erfolgs-Score für die Ampelfarbe: Farmer = aktuelle Trefferquote.
$liveScore = ($simId === 'farmer' && isset($state['hit_rate'])) ? max(0, min(100, (int)$state['hit_rate'])) : null;
$byStudent[$sid]['sessions'][] = [
'simId' => $simId,
'simTitle' => $mi['title'] ?? $simId,
@@ -324,6 +351,7 @@ if ($method === 'GET') {
'endMs' => $endMs,
'durationSec' => $durSec,
'stars' => null,
'score' => $liveScore,
'completed' => null,
'success' => 'running',
'rowCount' => 1,