Files
breakpilot-lehrer/docs-src/services/schulkalender/notifications.md
T
Benjamin Admin 77c720e2df
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
Document Stundenplan + Schulkalender end-of-session state
- 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>
2026-05-22 18:41:31 +02:00

3.4 KiB
Raw Blame History

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

# 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)

{
  "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.