Files
breakpilot-lehrer/docs-src/services/stundenplan/architecture.md
T
Benjamin Admin 306886a42b Phase 8: CSV + ICS export, print view, MkDocs docs, SBOM + dev-mode auth
Auth (Test-Mode):
  - middleware.AuthMiddleware now takes a devMode flag. In dev,
    requests without Authorization fall back to a deterministic dev
    UUID (00000000-...-001) and role=teacher. ENVIRONMENT=production
    re-enables the strict 401 path.
  - main.go wires devMode = cfg.Environment != "production".
  - page.tsx replaces the red 'Anmeldung noch nicht integriert' banner
    with a softer Testumgebung notice; the manual-token form moves
    behind a nested details block.

Export endpoints (school-service):
  - LoadExportLessons joins tt_lesson with tt_period for wall-clock
    times; one query feeds both CSV and ICS.
  - WriteCSV streams 10 columns including pinned flag.
  - WriteICS emits one VEVENT per lesson anchored to a Monday — caller
    overridable via ?start=YYYY-MM-DD. RFC 5545 escapes for ',', ';',
    '\n' in icsEscape().
  - NextMonday helper for the default anchor.
  - GET /timetable/solutions/:id/export.{csv,ics} handlers attach
    Content-Disposition: attachment so browsers download instead of
    rendering.

Frontend:
  - lib/stundenplan/api.ts downloadSolutionExport() fetches as blob,
    triggers a synthetic <a download> click, and forwards the JWT when
    present.
  - PlanView gains CSV / ICS / Drucken buttons next to the perspective
    selector. The toolbar carries class 'no-print' so window.print()
    yields only the grid.
  - globals.css @media print rule hides chrome, forces white
    background, gives the table proper borders for A4.

Docs:
  - docs-src/services/stundenplan/{index,architecture,constraints,
    solver-tuning,export}.md with nav entry in mkdocs.yml under
    Services → Stundenplaner.
  - sbom/stundenplan/README.md lists manually-verified key dependencies
    and the policy reference. scripts/stundenplan-sbom.sh generates
    full machine-readable inventories via go-licenses + pip-licenses
    + license-checker when those tools are available.

Tests:
  - internal/services/timetable_exports_test.go: 4 unit tests covering
    CSV column layout + quoting, ICS structure + DTSTART formatting,
    icsEscape special chars, NextMonday weekday math.
  - studio-v2/e2e/stundenplan-export.spec.ts split out of the main spec
    file (LOC budget) — 3 tests for button render, CSV download,
    ICS download.
  - mockSchoolApi extended with export.csv + export.ics routes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 08:57:07 +02:00

77 lines
2.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Architektur + Datenmodell
## Verantwortung pro Service
### school-service (Go/Gin)
- Persistenz fuer alle Stammdaten + Constraints + Solutions
- API-Gateway fuer studio-v2: validiert, ownership-checked, faked Auth im
Dev-Mode
- Trigger-Aufruf an solver-service nach POST /solutions
### timetable-solver-service (Python/FastAPI + Timefold)
- Liest Problem aus PG via asyncpg
- Baut Timefold-Domain (Lessons, Timeslots, Rooms, Rules)
- Loest im ThreadPoolExecutor (Solver ist CPU-gebunden)
- Schreibt Loesung direkt nach tt_lesson, updated tt_solution.status
### studio-v2 (Next.js)
- `/stundenplan` Tab-Page mit 9 Tabs
- Next.js API-Route `/api/school/*` proxied zu school-service
- Solution-Polling alle 4 s wenn Solve laeuft
## Datenmodell
### Stammdaten (7 Tabellen)
| Tabelle | Inhalt | Owner |
|---------|--------|-------|
| tt_class | Klassen (Name, Klassenstufe) | created_by_user_id |
| tt_period | Zeitraster (Mo-So × Stunde × Start/Ende) | created_by_user_id |
| tt_room | Raeume (Name, Typ, Kapazitaet, Aufzug) | created_by_user_id |
| tt_subject | Faecher (Name, Kuerzel, Farbe, RoomType) | created_by_user_id |
| tt_teacher | Lehrer als planbare Ressource | created_by_user_id |
| tt_curriculum | Klasse × Fach → Wochenstunden | indirect via tt_class |
| tt_assignment | Lehrer × Klasse × Fach | indirect via tt_teacher |
### Constraints (15 Tabellen)
Pro Tabelle die Felder: `is_hard` (bool), `weight` (0-100), `active` (bool),
`note` (TEXT), `created_by_user_id`. Aufgeteilt nach Parent-Entitaet:
- Lehrer: `unavailable_day`, `unavailable_window`, `max_hours_day`,
`max_hours_week`, `excluded_subject`, `excluded_room`
- Fach: `min_day_gap`, `max_consecutive`, `contiguous_when_repeated`,
`preferred_period`, `double_lesson`
- Klasse: `max_hours_day`, `no_gaps`
- Raum: `requires_type`, `unavailable`
### Solutions (2 Tabellen, Phase 5+7)
| Tabelle | Inhalt |
|---------|--------|
| tt_solution | Solve-Run: Status, hard/soft Score, parent_solution_id, seconds_limit |
| tt_lesson | Eine Stunde im Plan (class, subject, teacher, room, day, period, pinned) |
`tt_lesson` hat drei `UNIQUE`-Constraints, die der DB-Layer selbst Konflikt-
Lessons ablehnen laesst:
- `(solution_id, class_id, day, period)` — Klasse nicht doppelt
- `(solution_id, teacher_id, day, period)` — Lehrer nicht doppelt
- `(solution_id, room_id, day, period)` — Raum nicht doppelt
Damit kann ein fehlerhafter Solver-Output nicht in Daten landen, die das UI
inkonsistent darstellt.
## Ownership-Modell
Alles ist single-tenant pro `created_by_user_id`. CRUD-Endpoints filtern via
`WHERE EXISTS (SELECT 1 FROM tt_<parent> WHERE id = $X AND created_by_user_id
= $user)`. Cross-Tenant-Zugriff ist auf SQL-Ebene ausgeschlossen.
Im Dev-Mode injiziert `AuthMiddleware` einen festen UUID, damit Tests ohne
JWT laufen koennen. Production-Build (`ENVIRONMENT=production`) deaktiviert
den Bypass — JWT wird Pflicht.