1e51ef7def
- 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>
171 lines
6.3 KiB
PHP
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;
|
|
}
|