Files
geograsim/App/php/api/logistik-analytics.php
T
Adminator 1e51ef7def Nachtrag: alle bisher untracked Ordner + hängende Änderungen mit-committen
- Konzept/, didaktik_geografie/, didaktik_simulation/, v2-modules/, v2-platform/
- 12 code-workspace-Files
- STATUS-*.md
- viele M/D/R-Änderungen an bereits getrackten Files
- .gitignore verstärkt: **/.humaninput/, **/secret_keys.txt

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-08 02:27:02 +02:00

153 lines
5.8 KiB
PHP

<?php
/**
* API: Logistik-Analytics (Einzelauftrags-Logging)
*
* POST /api/logistik-analytics
* Body: {
* levelId: int — Pflicht
* contractCode: string — Pflicht, max 32 Zeichen
* assignedVehicle: string — optional, max 32
* state: string — Pflicht: DELIVERED|LATE|FAILED|CANCELLED
* finalBalance: int — optional (Kontostand des Durchgangs beim Abschluss)
* lateMinutes: int — optional, default 0
* hintUsages: int — optional, default 0
* intermodal: bool — optional, default false
* routeDistanceKm: int — optional
* routeMode: string — optional: TRUCK_SMALL|TRUCK_LARGE|TRAIN
* }
* -> schreibt Eintrag in lg_contracts_log
*
* GET /api/logistik-analytics?scope=me|class|level
* Liefert aggregierte Kennzahlen:
* scope=me (default) -> eigene letzten 50 Auftraege
* scope=class (Lehrer only) -> pro Schuelersession aggregiert
* scope=level (Lehrer only) -> pro Level aggregiert (state-Verteilung)
*
* Security:
* - POST erfordert gueltige Schueler-Session (Cookie gegen student_sessions)
* - Keine Fremd-Session-IDs schreibbar (session_id kommt IMMER aus Cookie)
* - class/level-Scopes erfordern Lehrer-Login
*/
$method = $_SERVER['REQUEST_METHOD'];
$db = Database::get();
function lga_requireValidStudent(): string {
$sid = Session::requireStudent();
$db = Database::get();
$row = $db->fetchOne('SELECT id FROM student_sessions WHERE id = ?', [$sid]);
if (!$row) {
http_response_code(401);
echo json_encode(['error' => 'Session ungueltig']);
exit;
}
return $sid;
}
$VALID_STATES = ['DELIVERED', 'LATE', 'FAILED', 'CANCELLED'];
$VALID_MODES = ['TRUCK_SMALL', 'TRUCK_LARGE', 'TRAIN'];
if ($method === 'POST') {
$sid = lga_requireValidStudent();
$body = json_decode(file_get_contents('php://input'), true);
if (!is_array($body)) Response::error('Body fehlt oder kein JSON', 400);
$levelId = (int)($body['levelId'] ?? 0);
$contractCode = substr(trim($body['contractCode'] ?? ''), 0, 32);
$state = strtoupper(trim($body['state'] ?? ''));
if ($levelId < 1) Response::error('levelId fehlt', 400);
if ($contractCode === '') Response::error('contractCode fehlt', 400);
if (!in_array($state, $VALID_STATES, true)) Response::error('state ungueltig', 400);
$assignedVehicle = isset($body['assignedVehicle']) ? substr((string)$body['assignedVehicle'], 0, 32) : null;
$finalBalance = isset($body['finalBalance']) ? (int)$body['finalBalance'] : null;
$lateMinutes = max(0, (int)($body['lateMinutes'] ?? 0));
$hintUsages = max(0, (int)($body['hintUsages'] ?? 0));
$intermodal = !empty($body['intermodal']) ? 1 : 0;
$routeDistanceKm = isset($body['routeDistanceKm']) ? max(0, (int)$body['routeDistanceKm']) : null;
$routeMode = null;
if (isset($body['routeMode'])) {
$m = strtoupper((string)$body['routeMode']);
if (in_array($m, $VALID_MODES, true)) $routeMode = $m;
}
$db->execute(
'INSERT INTO lg_contracts_log
(session_id, level_id, contract_code, assigned_vehicle, state,
final_balance, late_minutes, hint_usages, intermodal,
route_distance_km, route_mode)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
[$sid, $levelId, $contractCode, $assignedVehicle, $state,
$finalBalance, $lateMinutes, $hintUsages, $intermodal,
$routeDistanceKm, $routeMode]
);
Response::ok(['logged' => true]);
}
if ($method === 'GET') {
$scope = $_GET['scope'] ?? 'me';
if ($scope === 'me') {
$sid = lga_requireValidStudent();
$rows = $db->fetchAll(
'SELECT level_id, contract_code, assigned_vehicle, state,
final_balance, late_minutes, hint_usages, intermodal,
route_distance_km, route_mode, completed_at
FROM lg_contracts_log
WHERE session_id = ?
ORDER BY completed_at DESC
LIMIT 50',
[$sid]
);
Response::ok(['scope' => 'me', 'rows' => $rows]);
}
if ($scope === 'class') {
Session::requireTeacher();
$levelId = (int)($_GET['levelId'] ?? 0);
$where = $levelId > 0 ? 'WHERE level_id = ?' : '';
$params = $levelId > 0 ? [$levelId] : [];
$rows = $db->fetchAll(
"SELECT session_id,
COUNT(*) AS total,
SUM(state = 'DELIVERED') AS delivered,
SUM(state = 'LATE') AS late,
SUM(state = 'FAILED') AS failed,
SUM(state = 'CANCELLED') AS cancelled,
AVG(late_minutes) AS avg_late_min,
SUM(intermodal) AS intermodal_count
FROM lg_contracts_log
$where
GROUP BY session_id
ORDER BY total DESC
LIMIT 200",
$params
);
Response::ok(['scope' => 'class', 'rows' => $rows]);
}
if ($scope === 'level') {
Session::requireTeacher();
$rows = $db->fetchAll(
"SELECT level_id,
COUNT(*) AS total,
SUM(state = 'DELIVERED') AS delivered,
SUM(state = 'LATE') AS late,
SUM(state = 'FAILED') AS failed,
SUM(state = 'CANCELLED') AS cancelled,
AVG(late_minutes) AS avg_late_min,
AVG(route_distance_km) AS avg_km
FROM lg_contracts_log
GROUP BY level_id
ORDER BY level_id"
);
Response::ok(['scope' => 'level', 'rows' => $rows]);
}
Response::error('Unbekannter scope', 400);
}
Response::error('Methode nicht erlaubt', 405);