Document Stundenplan + Schulkalender end-of-session state
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 50s
CI / test-go-edu-search (push) Successful in 45s
CI / test-python-klausur (push) Failing after 3m50s
CI / test-python-agent-core (push) Successful in 36s
CI / test-nodejs-website (push) Successful in 49s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 50s
CI / test-go-edu-search (push) Successful in 45s
CI / test-python-klausur (push) Failing after 3m50s
CI / test-python-agent-core (push) Successful in 36s
CI / test-nodejs-website (push) Successful in 49s
- CLAUDE.md gets a new section summarising the two feature strands, pitfalls (Timefold name, JSX quotes, LOC budget), the auth/messaging outsourcing, and pointers to the three memory files for next session. - docs-src/services/schulkalender/ — 5 MkDocs pages mirroring the stundenplan structure: index, architecture, holidays, parent-flow, notifications. Each with DB tables, endpoints, and the dispatch payload contract for the colleague's Matrix/Email services. - mkdocs.yml gains the Schulkalender nav entry under Services. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
# Notifications
|
||||
|
||||
## Pipeline
|
||||
|
||||
```
|
||||
06:00 Uhr (Berlin-Zeit, Container TZ=Europe/Berlin)
|
||||
│
|
||||
▼
|
||||
NotificationService.RunForDate(today)
|
||||
│
|
||||
▼
|
||||
dueEvents() findet cal_school_event mit
|
||||
(start_date - today) ∈ notification_lead_days
|
||||
│
|
||||
▼
|
||||
Pro Event: fuer jede Audience (parents/students) und jeden Channel
|
||||
(matrix für alle, email zusaetzlich nur fuer parents):
|
||||
│
|
||||
▼
|
||||
dispatchOne()
|
||||
1. Idempotenz-Check (UNIQUE notification_log)
|
||||
2. recipientsFor() — JOIN parent_account+parent_child fuer
|
||||
betroffene Klassen, gibt Email-Liste + bevorzugte Sprache zurueck
|
||||
3. Render-Template (templates.go, 8 Sprachen)
|
||||
4. POST {MATRIX,EMAIL}_SERVICE_URL mit DispatchPayload
|
||||
5. notification_log writeLog (sent/failed/skipped)
|
||||
```
|
||||
|
||||
## Cron-Mechanik
|
||||
|
||||
`main.go` startet einen Goroutine-Ticker mit 1h-Intervall. Sobald `time.Now().Hour() == 6` wird `RunForDate` aufgerufen. Idempotent — die UNIQUE auf notification_log filtert Doppel-Calls am selben Tag.
|
||||
|
||||
Bei Container-Restart vor 06:00 läuft trotzdem alles korrekt: der naechste 06-Tick fired bis spaetestens 06:59:59. Bei Restart nach 06:00: erste Notification erst am Folgetag (acceptable trade-off gegen einen 1-Min-Ticker).
|
||||
|
||||
## Manueller Trigger
|
||||
|
||||
```bash
|
||||
# Heute jetzt scannen
|
||||
curl -X POST http://localhost:8084/api/v1/school/calendar/notifications/run-now
|
||||
|
||||
# Backfill (z.B. nach langem Container-Down)
|
||||
curl -X POST 'http://localhost:8084/api/v1/school/calendar/notifications/run-now?date=2026-05-20'
|
||||
```
|
||||
|
||||
Antwort: `{"date":"2026-05-22","sent":N,"failed":N,"skipped":N,"already_logged":N}`.
|
||||
|
||||
## Template-Engine
|
||||
|
||||
Datei: `school-service/internal/notifications/templates.go`. Schema:
|
||||
|
||||
```
|
||||
templates[lang][event_type][audience][bucket] → {Subject, Body}
|
||||
```
|
||||
|
||||
- `lang` ∈ de/en/tr/ar/uk/ru/pl/fr (Fallback `de`)
|
||||
- `event_type` ∈ fortbildung/schulfeier/klassenfahrt/projekttag/eltern_info/andere (Fallback `andere`)
|
||||
- `audience` ∈ parents/students (Fallback `parents`)
|
||||
- `bucket` ∈ today/tomorrow/days (Fallback `days`)
|
||||
|
||||
Placeholders: `{{title}}`, `{{date}}`, `{{date_pretty}}`, `{{class_name}}`, `{{class_suffix}}`, `{{teacher_name}}`, `{{lead}}`.
|
||||
|
||||
Beispiel-Render (TR / schulfeier / parents / 1-Tag-Vorlauf):
|
||||
```
|
||||
Subject: Yarın: Sommerfest (5a)
|
||||
Body: Sayın veliler, yarın (15.06.2026) Sommerfest gerçekleşiyor (5a).
|
||||
```
|
||||
|
||||
## DispatchPayload (Endpoint-Vertrag mit Matrix/Email Service)
|
||||
|
||||
```json
|
||||
{
|
||||
"channel": "matrix",
|
||||
"recipient": "mama@example.de",
|
||||
"language": "tr",
|
||||
"subject": "Yarın: Sommerfest",
|
||||
"body": "Sayın veliler, ...",
|
||||
"event_id": "uuid-…",
|
||||
"lead_days": 1
|
||||
}
|
||||
```
|
||||
|
||||
Erwartete Antwort vom Upstream: HTTP 2xx = sent. 4xx/5xx = failed. Wir leiten **keine** Empfaenger-Identifier-Aufloesung weiter ans Upstream — die Matrix-Bridge mapt Email → Matrix-Handle in der eigenen Logik.
|
||||
|
||||
Bei `MATRIX_SERVICE_URL` oder `EMAIL_SERVICE_URL` leer: status='skipped', kein Versandversuch. Erlaubt lokales Testen ohne Upstream.
|
||||
|
||||
## Status-Anzeige im Lehrer-UI
|
||||
|
||||
`DayDetail` mountet `NotificationStatus` fuer jedes Event mit `notify_parents` oder `notify_students`. Lädt `GET /api/v1/school/calendar/events/:id/notifications` und zeigt Badges:
|
||||
|
||||
- ✓ gruen = sent
|
||||
- ✗ rot = failed (Hover zeigt error_message)
|
||||
- ⏱ amber = skipped (Upstream noch nicht konfiguriert)
|
||||
|
||||
## Privacy
|
||||
|
||||
`notification_log` ist nur über JOIN cal_school_event sichtbar — Lehrer sieht nur Logs seiner eigenen Events. Eltern haben gar keine UI fuer Logs.
|
||||
Reference in New Issue
Block a user