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>
186 lines
5.9 KiB
Markdown
186 lines
5.9 KiB
Markdown
# Vertrag 3: Output-API
|
||
|
||
Was ein Modul am Ende einer Sitzung an die Plattform liefert. Plus
|
||
Endpoint zum Zwischenspeichern des Resume-States.
|
||
|
||
## Endgültiges Ergebnis bei Modul-Abschluss
|
||
|
||
Wenn Schüler*in das Modul abschließt (egal ob bestanden oder
|
||
abgebrochen), pusht das Modul **genau einen** Result-Call:
|
||
|
||
```http
|
||
POST /v2beta/api/result
|
||
Authorization: Bearer <session_token>
|
||
Content-Type: application/json
|
||
|
||
{
|
||
"moduleSlug": "logistik",
|
||
"moduleVersion": "2.0.0",
|
||
"specVersion": "1.0",
|
||
"completed": true,
|
||
"abandoned": false,
|
||
"score": 78,
|
||
"scoreMax": 100,
|
||
"durationS": 1452,
|
||
"difficulty": "L1",
|
||
"mode": "teacher_started",
|
||
"milestonesReached": [
|
||
"first-order-accepted",
|
||
"level-1-cleared"
|
||
],
|
||
"lehrplanCoverage": {
|
||
"AT-GW-1.4.2": "full",
|
||
"AT-GW-2.1.1": "partial",
|
||
"AT-GW-3.5.4": "none"
|
||
},
|
||
"freeTextAnswers": [
|
||
{
|
||
"promptId": "reflexion-end",
|
||
"promptText": "Warum hast du dich für die Bahnverbindung entschieden?",
|
||
"answerText": "Weil sie billiger war und nicht im Stau steht."
|
||
}
|
||
],
|
||
"artifacts": [
|
||
{
|
||
"type": "image/png",
|
||
"url": "/v2beta/sims/logistik/uploads/student-4711/route-final.png",
|
||
"label": "Letzte gewählte Route",
|
||
"thumbnail": "/v2beta/sims/logistik/uploads/student-4711/route-final-thumb.png"
|
||
}
|
||
],
|
||
"moduleSpecificData": {
|
||
"ordersCompleted": 6,
|
||
"ordersFailed": 2,
|
||
"totalCO2Kg": 142,
|
||
"totalKm": 1845
|
||
},
|
||
"ts": "2026-05-15T14:48:00Z"
|
||
}
|
||
```
|
||
|
||
### Pflicht-Felder
|
||
|
||
| Feld | Typ | Beschreibung |
|
||
|---|---|---|
|
||
| `moduleSlug` | String | Aus Manifest |
|
||
| `moduleVersion` | String | Aus Manifest |
|
||
| `specVersion` | String | Welche Spec-Version genutzt wurde |
|
||
| `completed` | Boolean | Modul ehrlich abgeschlossen (nicht abgebrochen) |
|
||
| `abandoned` | Boolean | Schüler*in hat Tab geschlossen / Stunde zu Ende ohne Abschluss |
|
||
| `score` | Integer 0–N | Punktzahl |
|
||
| `scoreMax` | Integer | Maximal mögliche Punktzahl |
|
||
| `durationS` | Integer | Reine Bearbeitungszeit (ohne Pausen >5min) |
|
||
| `difficulty` | String | Aus Input-Param |
|
||
| `mode` | String | Aus Input-Param |
|
||
| `lehrplanCoverage` | Object | Pro Anker aus Manifest: `full` / `partial` / `none` |
|
||
| `ts` | ISO-Timestamp | Zeitpunkt des Abschlusses |
|
||
|
||
### Optionale Felder
|
||
|
||
- `milestonesReached` — alle erreichten Milestone-IDs (Subset von `manifest.telemetryMilestones`)
|
||
- `freeTextAnswers` — Reflexionsfragen-Antworten der Schüler*in (für Lehrperson-Auswertung)
|
||
- `artifacts` — Modul-Output (Bilder, Karten, Audio) als URLs
|
||
- `moduleSpecificData` — Frei strukturiertes JSON mit Modul-eigenen Kennzahlen für die Auswertung
|
||
|
||
### Lehrplan-Coverage
|
||
|
||
Modul muss pro deklariertem Lehrplan-Anker entscheiden:
|
||
- `full` — Schüler*in hat zu diesem Anker gearbeitet UND erfolgreich
|
||
abgeschlossen
|
||
- `partial` — hat zu diesem Anker gearbeitet, aber nicht voll geschafft
|
||
- `none` — hat diesen Anker in dieser Sitzung nicht berührt
|
||
(z.B. weil bestimmtes Level nicht erreicht)
|
||
|
||
## Resume-State zwischenspeichern
|
||
|
||
Während der Sitzung kann (und soll) das Modul regelmäßig den State
|
||
speichern, damit Schüler*in nach Crash / Logout fortsetzen kann:
|
||
|
||
```http
|
||
PUT /v2beta/api/module/state
|
||
Authorization: Bearer <session_token>
|
||
Content-Type: application/json
|
||
|
||
{
|
||
"moduleSlug": "logistik",
|
||
"state": {
|
||
"<beliebiges Modul-JSON>": "..."
|
||
},
|
||
"summary": "Tag 3, Level 2, 4 von 8 Aufträgen erledigt"
|
||
}
|
||
```
|
||
|
||
**Response 200:**
|
||
```json
|
||
{ "ok": true, "savedAt": "2026-05-15T14:23:00Z" }
|
||
```
|
||
|
||
**Empfohlene Frequenz**: jede 30s wenn State sich verändert hat,
|
||
oder bei jedem Milestone. Nicht öfter (Server-Last).
|
||
|
||
**Achtung**: `state` ist ein opakes Modul-internes Format. Plattform
|
||
speichert es als JSON-Blob in `module_state_v2`-Tabelle, ohne es zu
|
||
parsen. Das Feld `summary` ist für die Lehrperson-Anzeige.
|
||
|
||
## Artifacts hochladen
|
||
|
||
Wenn das Modul Bilder/Audio/Karten produziert, die in der
|
||
Lehrer-Auswertung sichtbar sein sollen:
|
||
|
||
```http
|
||
POST /v2beta/api/artifact
|
||
Authorization: Bearer <session_token>
|
||
Content-Type: multipart/form-data
|
||
|
||
[file]
|
||
moduleSlug=logistik
|
||
label=Letzte gewählte Route
|
||
```
|
||
|
||
**Response 201:**
|
||
```json
|
||
{
|
||
"id": 8842,
|
||
"url": "/v2beta/sims/logistik/uploads/student-4711/route-final.png",
|
||
"thumbnail": "/v2beta/sims/logistik/uploads/student-4711/route-final-thumb.png"
|
||
}
|
||
```
|
||
|
||
URL und Thumbnail dann in `artifacts`-Array des Result-Calls referenzieren.
|
||
|
||
## Was die Plattform mit dem Result macht
|
||
|
||
1. Speichert in `assessments_v2` (mit `created_by_version='v2'`)
|
||
2. Aktualisiert `student_progress_v2` (Modul-Fortschritt für
|
||
Lerngeschichte-Anzeige)
|
||
3. Aktualisiert `lehrplan_progress_v2` (welche Anker hat Schüler*in
|
||
abgedeckt)
|
||
4. Pushed an Lehrer-Dashboard (Live-Update via Server-Sent-Events)
|
||
5. Macht Result für die Klassen-Auswertung sichtbar
|
||
|
||
## Level-Unlock-Update
|
||
|
||
Beim Result-Empfang aktualisiert die Plattform **automatisch** den
|
||
Level-Unlock-Status der Schüler*in (in `student_level_unlock_v2`):
|
||
|
||
- `attempts_per_level[difficulty] += 1`
|
||
- `best_score_per_level[difficulty]` = max von alt und neu
|
||
- Wenn `completed = true` UND `score / scoreMax >= 0.6` UND
|
||
Schüler*in war auf `highest_unlocked_level`:
|
||
- `highest_completed_level = difficulty`
|
||
- `highest_unlocked_level` = nächster Level aus `manifest.difficultyLevels` (falls vorhanden)
|
||
|
||
Modul muss sich darum nicht kümmern — passiert serverseitig.
|
||
|
||
## ✅ Geklärt mit Thomas (2026-05-17)
|
||
|
||
- **`freeTextAnswers`**: mit Klarnamen an die Lehrperson (wie V1).
|
||
Pädagogisch nötig — Lehrperson kann gezielt auf einzelne Schüler*in
|
||
eingehen. Andere Schüler*innen sehen die Antworten nicht.
|
||
- **Artifacts löschen (DSGVO)**: per Lehrperson-Cockpit „Sitzung
|
||
komplett löschen" — eine Aktion löscht Assessment + zugeordnete
|
||
Artifacts. Separater DELETE-Endpoint kommt mit dem Cockpit.
|
||
- **`moduleSpecificData`**: bleibt opakes JSON. Cross-Modul-Vergleiche
|
||
laufen über `score`, `scoreMax`, `lehrplanCoverage` — Modul-eigene
|
||
Kennzahlen sind Modul-Sache, kein Standardisierungs-Druck.
|