feat: Anhänge je Stunde, Klausur-Sitzplan mischen, Gefährdungsbeurteilungs-Assistent
Lesson bekommt dieselbe Anhang-Infrastruktur wie Documentation (Material, Arbeitsblätter, Experimentunterlagen), samt Fix einer Sync-Lücke, die Anhang- Dateibytes bisher nur für Documentation statt generisch übertragen hat (IHasAttachments). Sitzplan-Tab bekommt einen "Plätze mischen"-Button für Klausursitzpläne. Neu: mehrschrittiger Gefährdungsbeurteilungs-Assistent mit optionalem KI-Entwurf (ai-backend/gbu.php) und PDF-Export, Format bewusst als JSON-Anhang statt eigener Datenbank-Entität. Details und Architekturentscheidungen in TODO.md (4.2, 7.1.5, 10.1.8). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -518,6 +518,83 @@ leer ist) und rollt von dort vorwärts auf den nächsten passenden Wochentag. Fe
|
||||
Stundenplan-Eintrag für die Gruppe, bleibt "heute" als Fallback erhalten (keine Verhaltens-
|
||||
änderung für Gruppen ohne Stundenplan).
|
||||
|
||||
**Nachtrag — Anhänge je Stunde (Nutzerwunsch, fachspezifisch):** Chemieunterricht plant mit
|
||||
Experimenten, zu denen eine schriftliche Gefährdungsbeurteilung gehört; außerdem gab es bisher
|
||||
gar keine Möglichkeit, ein Arbeitsblatt/Material als Datei an eine Stunde zu hängen (die
|
||||
`Material`-Spalte im Verlaufsplan ist bewusst nur Freitext, siehe 4.2.2). Statt zweier separater
|
||||
Felder ein einziges, generisches `Lesson.Attachments`
|
||||
(`List<DocumentAttachment>`) — dieselbe Anhang-Infrastruktur, die `Documentation` (5.1) bereits
|
||||
nutzt (`IAttachmentStorage`, `LiteAttachmentStorage`), deckt beides ab: Gefährdungsbeurteilungen
|
||||
genauso wie normale Arbeitsblätter/Scans. Im Stunden-Dialog
|
||||
([LessonDialog.axaml](LehrerApp.Desktop/Views/Groups/LessonDialog.axaml)) neuer Abschnitt
|
||||
"Anhänge" nach demselben Muster wie im Dokumentationsdialog (Button "+ Datei" über den
|
||||
Avalonia-Dateiauswahldialog, Liste mit Datei/Größe/"Speichern"/"×"). `LessonRepository.Delete`
|
||||
räumt beim (harten) Löschen einer Stunde die zugehörigen Anhänge aus der Attachment-Ablage auf,
|
||||
analog zu `LiteDbContext.CascadeHardDeleteDocumentation`. Neue Tests: `LessonDialogViewModelTests`
|
||||
(Hochladen inkl. Größenlimit, Entfernen, Vorbelegung beim Bearbeiten, Übernahme ins
|
||||
Speicherergebnis), `RepositoryTests.LessonRepository_DeleteRaeumtAnhaengeAusDerAttachmentAblageAuf`.
|
||||
Eine KI-gestützte Vorformulierung der Gefährdungsbeurteilung (ebenfalls Nutzerwunsch) ist bewusst
|
||||
zurückgestellt — dafür bräuchte es einen neuen `ai-backend`-Endpunkt mit eigenem Systemprompt
|
||||
(analog zu `explain.php`, 4.5.21) und eine sorgfältig formulierte, gut sichtbare
|
||||
Rechtssicherheits-Einschränkung im Dialog; als eigener, separat zu planender Schritt vorgesehen.
|
||||
|
||||
**Nachtrag — Gefährdungsbeurteilungs-Assistent (Umsetzung des oben zurückgestellten Schritts):**
|
||||
Vor der Umsetzung stand eine Architekturfrage: eigene LiteDB-Entität mit Repository/Sync-Wiring,
|
||||
oder etwas Leichtgewichtigeres? Entschieden für Letzteres — die Prüfung, ob Anhang-Dateibytes
|
||||
überhaupt zwischen Geräten synchronisieren (siehe Nachtrag zu 10.1.8, dort im selben Zug behoben),
|
||||
ergab, dass die eigentliche Lücke eine Ebene tiefer lag, nicht am Fehlen einer eigenen Entität.
|
||||
|
||||
**Format:** [`HazardAssessment`](LehrerApp.Core/Models/HazardAssessment.cs) ist ein reines,
|
||||
JSON-serialisierbares Modell (Titel, Klassenstufe/Kurs, Datum, Art des Versuchs
|
||||
[Lehrer-/Schüler-/Demonstrationsversuch], Durchführung, Gefahrstoffe mit GHS-Piktogrammen/H-/
|
||||
P-Sätzen/Menge, Gefährdungen, Schutzmaßnahmen, Erste Hilfe, Entsorgung) — **keine** eigene
|
||||
LiteDB-Collection/Repository. Wird als JSON serialisiert und über die bestehende
|
||||
Anhang-Infrastruktur an die `Lesson` gehängt, Dateiname endet auf `.gbu.json`
|
||||
(`AttachmentItem.IsHazardAssessment`/`HazardAssessmentSuffix`). Bewusst **kein** eingebauter
|
||||
Katalog amtlicher H-/P-Satz-Texte im Code — Fehlerrisiko bei sicherheitsrelevanten Angaben, die
|
||||
Lehrkraft trägt die Prüfung gegen das Sicherheitsdatenblatt.
|
||||
|
||||
**Wizard:** neuer mehrschrittiger Assistent
|
||||
([HazardAssessmentWizardDialog.axaml](LehrerApp.Desktop/Views/Groups/HazardAssessmentWizardDialog.axaml),
|
||||
`HazardAssessmentWizardViewModel`) mit 5 Schritten (Basisdaten, Gefahrstoffe, Gefährdungen &
|
||||
Schutzmaßnahmen, Erste Hilfe & Entsorgung, Zusammenfassung). Persistiert nicht selbst — liefert
|
||||
nur `Result`, der Aufrufer (`LessonDialog`) serialisiert zu JSON und hängt es über die bereits
|
||||
vorhandenen `AddAttachment`/`RemoveAttachmentCommand` an. Im Stunden-Dialog neuer Button
|
||||
"🧪 Gefährdungsbeurteilung" neben "+ Datei"; `.gbu.json`-Anhänge zeigen in der Anhangliste
|
||||
"Öffnen" (Wizard im Bearbeitungsmodus, ersetzt den Anhang beim Speichern) und "PDF" statt des
|
||||
generischen "Speichern".
|
||||
|
||||
**KI-Entwurf:** neuer Endpunkt `ai-backend/gbu.php` (gleicher Aufbau wie `explain.php`: Auth,
|
||||
Guthabenprüfung, fester Systemprompt, `ai_backend_call_and_charge`) erkennt aus Thema und
|
||||
Verlaufsplan der Stunde das Experiment und liefert einen strukturierten Entwurf. Systemprompt
|
||||
weist die KI ausdrücklich an, unsichere H-/P-Sätze/Mengenangaben **nicht zu erfinden**, sondern
|
||||
mit einem Prüfhinweis zu kennzeichnen, und bei der Einstufung Lehrer-/Schülerversuch im Zweifel
|
||||
vorsichtig (Lehrerversuch) zu sein. `AiPlanningService.RequestHazardAssessmentDraftAsync` (neue
|
||||
DTOs `AiHazardAssessmentRequest`/`AiHazardAssessmentResponse` in `AiPlanningDtos.cs`) baut den
|
||||
Kontext lokal aus dem gerade im Dialog bearbeiteten (auch noch nicht gespeicherten) Stand, nicht
|
||||
erst nach dem Speichern. Button "🤖 KI-Entwurf erstellen" im Wizard, direkt daneben ein **fest
|
||||
sichtbarer** Rechtssicherheits-Hinweis (nicht erst nach Fehlern eingeblendet): "Kein
|
||||
rechtssicheres Dokument — bitte jede Angabe eigenverantwortlich prüfen." `HazardAssessment.
|
||||
IsAiAssisted` wird gesetzt und erscheint als deutlich hervorgehobener Warnhinweis im PDF-Export,
|
||||
sobald KI-Anteile beteiligt waren.
|
||||
|
||||
Deployment-Hinweis: `ai-backend/` hat **kein** Auto-Deploy (anders als `LehrerApp.Api`, siehe
|
||||
[docker/README.md](docker/README.md)) — nach diesem Änderungsdurchgang muss `gbu.php` manuell auf
|
||||
den PHP-Server hochgeladen werden (siehe [ai-backend/README.md](ai-backend/README.md), Abschnitt
|
||||
"Update für bereits deployte Installationen").
|
||||
|
||||
**PDF-Export:** `PdfExportService.BuildHazardAssessmentPdf` (neues `HazardAssessmentPrintData`)
|
||||
druckt Durchführung, Gefahrstofftabelle, Gefährdungen/Schutzmaßnahmen als Liste, Erste
|
||||
Hilfe/Entsorgung/Hinweise — bei `IsAiAssisted` zusätzlich ein auffälliges Warnbanner oben im
|
||||
Dokument.
|
||||
|
||||
Neue Tests: [HazardAssessmentWizardViewModelTests.cs](LehrerApp.Desktop.Tests/HazardAssessmentWizardViewModelTests.cs)
|
||||
(Navigation, Validierung, Gefahrstoff-/Gefährdungs-/Schutzmaßnahmen-Verwaltung, Vorbelegung beim
|
||||
Bearbeiten, `ExperimentKindDisplay`/`GhsPictogramDisplay`), zwei neue Fälle in
|
||||
`PdfExportServiceTests.cs`. Die HTTP-Anfrage von `RequestHazardAssessmentDraftAsync` selbst ist
|
||||
wie bei `RequestExplanationAsync`/`RequestPlanAsync` nicht Teil der automatisierten Tests (braucht
|
||||
einen echten Endpunkt, siehe Kommentar in `AiPlanningServiceTests.cs`).
|
||||
|
||||
### 4.3 Stundenplan
|
||||
- [x] **4.3.1** Neues Modell `TimetableSlot` (Gruppe, Wochentag, Stunde, Raum) + Repository —
|
||||
[Planning.cs](LehrerApp.Core/Models/Planning.cs),
|
||||
@@ -1388,6 +1465,17 @@ Hinweis in Kapitel 1 — betrifft auch Kurse, nicht nur Klassen.
|
||||
(Laden, Umschalten inkl. Persistieren, Sperre für belegte Plätze, verhinderte Zuweisung auf
|
||||
ausgeblendete Plätze, `ShowSeat`/`CanToggleHidden` je nach Modus).
|
||||
|
||||
**Nachtrag (Nutzerwunsch): Klausur-Sitzplan mischen.** Neuer Button "🎲 Plätze für Klausur
|
||||
mischen" neben Bearbeiten/Löschen — erzeugt aus dem aktuell gewählten Plan einen neuen,
|
||||
unabhängigen Sitzplan mit gleichem Raster/Raum, aber zufällig vertauschten Insassen der
|
||||
belegten Plätze (Fisher-Yates über `Random.Shared.Shuffle`, Platzkoordinaten bleiben
|
||||
unverändert, nur wer wo sitzt wird neu gewürfelt). Der Ursprungsplan bleibt unangetastet
|
||||
erhalten, damit er für den nächsten regulären Unterricht weiter nutzbar ist; der neu
|
||||
angelegte Plan ist wie jeder andere `SeatingPlan`-Datensatz sofort über den bestehenden
|
||||
Sitzplan-PDF-Export (11.4) druckbar, ohne weitere Anpassung. Test:
|
||||
`SeatingPlanViewModelTests.ShuffleSeats_ErzeugtNeuenPlanMitVertauschtenPlaetzenUndBehaeltDenUrsprungsplan`
|
||||
plus ein `CanExecute`-Test für die Bearbeitungsmodus-Sperre.
|
||||
|
||||
### 7.2 Gruppen
|
||||
- [x] **7.2.1** Gruppe bearbeiten und löschen — bereits vorhanden (`EditGroupCommand`/`DeleteGroupCommand`/
|
||||
`ToggleArchiveCommand` in `GroupListViewModel`), nicht Teil der aktuellen Klausuren-Arbeit,
|
||||
@@ -1764,6 +1852,26 @@ die Docker-Verifikation unter 10.2.4 (kein Docker im Entwicklungsstand verfügba
|
||||
Anhänge nach Anwenden eines `Documentation`-Ereignisses). Original-`StorageId` bleibt beim
|
||||
Download erhalten (roher `db.Attachments.Upload`-Aufruf statt `IAttachmentStorage.Upload`,
|
||||
das immer eine neue Id vergäbe).
|
||||
|
||||
**Nachtrag — Verallgemeinerung auf `IHasAttachments` (Bug, gefunden bei 4.2-Nachtrag
|
||||
"Anhänge je Stunde"):** `SyncEventPublisher.Publish` und `EventApplier` (Gate +
|
||||
Deserialisierung in `DownloadMissingAttachmentsAsync`) waren hart auf den Modelltyp
|
||||
`Documentation` verdrahtet (`if (payload is Documentation doc)` bzw.
|
||||
`evt.EntityType == nameof(Documentation)`). Als `Lesson` ein eigenes `Attachments`-Feld
|
||||
bekam, hätte das bedeutet: die Metadaten synchronisieren (Lesson selbst synchronisiert
|
||||
ohnehin vollständig), aber die eigentlichen Datei-Bytes nie — ein an einem Gerät
|
||||
angehängtes Arbeitsblatt/eine Gefährdungsbeurteilung wäre auf dem zweiten Gerät nur ein
|
||||
Verweis auf eine nicht existierende `StorageId` gewesen. Neues Marker-Interface
|
||||
`IHasAttachments` ([Workload.cs](LehrerApp.Core/Models/Workload.cs), Property
|
||||
`List<DocumentAttachment> Attachments`), implementiert von `Documentation` und `Lesson`.
|
||||
`SyncEventPublisher.Publish` prüft jetzt `payload is IHasAttachments` statt des konkreten
|
||||
Typs; `EventApplier` bekommt eine kleine, pro Entitätstyp erweiterbare
|
||||
`AttachmentDeserializers`-Tabelle (analog zur bestehenden `Handlers`-Tabelle) statt der
|
||||
festen `Documentation`-Deserialisierung. `AttachmentSyncer` (Upload-Seite) war bereits
|
||||
generisch (kennt nur `StorageId`s, keinen Entitätstyp) und musste nicht geändert werden.
|
||||
Neue Tests: `EventApplierTests.ApplyAsync_LessonMitFehlendemAnhang_LaedtIhnUeberHttpNach`,
|
||||
neue Datei [SyncEventPublisherTests.cs](LehrerApp.Sync.Tests/SyncEventPublisherTests.cs)
|
||||
(Documentation, Lesson, Entität ohne Anhänge).
|
||||
- [x] **10.1.9** Schärfere Kollisionskontrolle beim Push (Konzeptgespräch nach dem
|
||||
Pull-Watermark-Bugfix, siehe Nachtrag zu 10.1.7): statt der bisherigen 30-Sekunden-
|
||||
Heuristik ("hat ein anderes Gerät kürzlich dieselbe Entität angefasst") trägt jedes
|
||||
|
||||
Reference in New Issue
Block a user