Files
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

171 lines
6.3 KiB
PHP

<?php
/**
* POST /api/result
* Endergebnis bei Modul-Abschluss.
*/
declare(strict_types=1);
require_once __DIR__ . '/../../bootstrap.php';
v2_validate_method('POST');
$auth = v2_auth_require('student');
$studentId = (int) ($auth['sub'] ?? 0);
$classId = (int) ($auth['class_id'] ?? 0);
$body = v2_request_body_json_required();
v2_validate_required($body, [
'moduleSlug', 'moduleVersion', 'specVersion',
'completed', 'score', 'scoreMax', 'durationS',
'difficulty', 'mode', 'lehrplanCoverage', 'ts',
]);
v2_validate_enum((string) $body['mode'], ['free', 'teacher_started', 'assessment', 'demo'], 'mode');
$pdo = v2_db();
$pdo->beginTransaction();
try {
// Hauptdatensatz
$stmt = $pdo->prepare("
INSERT INTO assessments_v2
(student_id, class_id, module_slug, module_version, spec_version,
completed, abandoned, score, score_max, duration_s,
difficulty, mode, module_specific_data, created_at)
VALUES
(:sid, :cid, :slug, :mv, :sv,
:comp, :aban, :score, :scoremax, :dur,
:diff, :mode, :msd, :ts)
");
$stmt->execute([
':sid' => $studentId,
':cid' => $classId,
':slug' => $body['moduleSlug'],
':mv' => $body['moduleVersion'],
':sv' => $body['specVersion'],
':comp' => (int) (bool) $body['completed'],
':aban' => (int) (bool) ($body['abandoned'] ?? false),
':score' => (int) $body['score'],
':scoremax' => (int) $body['scoreMax'],
':dur' => (int) $body['durationS'],
':diff' => $body['difficulty'],
':mode' => $body['mode'],
':msd' => isset($body['moduleSpecificData'])
? json_encode($body['moduleSpecificData'], JSON_UNESCAPED_UNICODE)
: null,
':ts' => date('Y-m-d H:i:s', strtotime($body['ts']) ?: time()),
]);
$assessmentId = (int) $pdo->lastInsertId();
// Milestones
if (!empty($body['milestonesReached']) && is_array($body['milestonesReached'])) {
$stmtMs = $pdo->prepare("
INSERT IGNORE INTO assessment_milestones_v2 (assessment_id, milestone_id)
VALUES (:aid, :mid)
");
foreach ($body['milestonesReached'] as $mid) {
$stmtMs->execute([':aid' => $assessmentId, ':mid' => (string) $mid]);
}
}
// Lehrplan-Coverage
if (!empty($body['lehrplanCoverage']) && is_array($body['lehrplanCoverage'])) {
$stmtLp = $pdo->prepare("
INSERT INTO assessment_lehrplan_v2 (assessment_id, anker_code, coverage)
VALUES (:aid, :code, :cov)
ON DUPLICATE KEY UPDATE coverage = VALUES(coverage)
");
$stmtUpdLp = $pdo->prepare("
INSERT INTO lehrplan_progress_v2 (student_id, anker_code, best_coverage, reached_via_module)
VALUES (:sid, :code, :cov, :slug)
ON DUPLICATE KEY UPDATE
best_coverage = CASE
WHEN VALUES(best_coverage) = 'full' THEN 'full'
WHEN best_coverage = 'full' THEN best_coverage
WHEN VALUES(best_coverage) = 'partial' THEN 'partial'
ELSE best_coverage
END,
reached_via_module = VALUES(reached_via_module),
reached_at = CURRENT_TIMESTAMP
");
foreach ($body['lehrplanCoverage'] as $code => $cov) {
v2_validate_enum((string) $cov, ['full', 'partial', 'none'], "lehrplanCoverage.$code");
$stmtLp->execute([':aid' => $assessmentId, ':code' => $code, ':cov' => $cov]);
if ($cov !== 'none') {
$stmtUpdLp->execute([
':sid' => $studentId, ':code' => $code,
':cov' => $cov, ':slug' => $body['moduleSlug'],
]);
}
}
}
// Freitext
if (!empty($body['freeTextAnswers']) && is_array($body['freeTextAnswers'])) {
$stmtFt = $pdo->prepare("
INSERT INTO assessment_freetext_v2
(assessment_id, prompt_id, prompt_text, answer_text)
VALUES (:aid, :pid, :ptx, :atx)
");
foreach ($body['freeTextAnswers'] as $ft) {
if (!is_array($ft) || empty($ft['promptId'])) continue;
$stmtFt->execute([
':aid' => $assessmentId,
':pid' => (string) $ft['promptId'],
':ptx' => (string) ($ft['promptText'] ?? ''),
':atx' => (string) ($ft['answerText'] ?? ''),
]);
}
}
// Artefakte verknüpfen (URLs aus separatem Upload, IDs aus body)
if (!empty($body['artifacts']) && is_array($body['artifacts'])) {
$stmtArt = $pdo->prepare("
INSERT IGNORE INTO assessment_artifacts_v2 (assessment_id, artifact_id)
VALUES (:aid, :artid)
");
foreach ($body['artifacts'] as $art) {
if (is_array($art) && !empty($art['id'])) {
$stmtArt->execute([':aid' => $assessmentId, ':artid' => (int) $art['id']]);
}
}
}
// Schüler-Modul-Fortschritt aktualisieren
$stmt = $pdo->prepare("
INSERT INTO student_progress_v2
(student_id, module_slug, total_attempts, best_score, best_score_max,
last_completed_at, total_duration_s)
VALUES
(:sid, :slug, 1, :score, :scoremax,
CASE WHEN :comp THEN CURRENT_TIMESTAMP ELSE NULL END, :dur)
ON DUPLICATE KEY UPDATE
total_attempts = total_attempts + 1,
best_score = GREATEST(best_score, VALUES(best_score)),
best_score_max = GREATEST(best_score_max, VALUES(best_score_max)),
last_completed_at = CASE
WHEN :comp2 THEN CURRENT_TIMESTAMP
ELSE last_completed_at
END,
total_duration_s = total_duration_s + VALUES(total_duration_s)
");
$stmt->execute([
':sid' => $studentId,
':slug' => $body['moduleSlug'],
':score' => (int) $body['score'],
':scoremax' => (int) $body['scoreMax'],
':comp' => (int) (bool) $body['completed'],
':comp2' => (int) (bool) $body['completed'],
':dur' => (int) $body['durationS'],
]);
$pdo->commit();
v2_response_created([
'ok' => true,
'assessmentId' => $assessmentId,
'savedAt' => date('c'),
]);
} catch (Throwable $e) {
$pdo->rollBack();
throw $e;
}