Commit Graph

6 Commits

Author SHA1 Message Date
Benjamin Admin 8311b33fb3 Phase 9d: Notification cron + multilingual templates + status badges
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 1m10s
CI / test-go-edu-search (push) Successful in 43s
CI / test-python-klausur (push) Failing after 4m4s
CI / test-python-agent-core (push) Successful in 44s
CI / test-nodejs-website (push) Successful in 51s
Backend (school-service):
  - notification_log table with UNIQUE(event_id, lead_days, audience,
    channel) for idempotent re-runs. Status enum sent/failed/skipped.
  - internal/notifications/templates.go: per-event-type × audience ×
    lead-day-bucket × language templates in 8 languages (de/en/tr/ar/
    uk/ru/pl/fr). Fallback chain (lang→de, eventType→andere) so we
    never miss a render.
  - service.go scans cal_school_event for events whose
    (start_date - runDate) appears in notification_lead_days. For each
    due (audience, channel) tuple it dispatches via POST to the
    Matrix/Email upstreams owned by the colleague's services.
    Empty URL → status='skipped', logged for visibility.
  - dispatcher.go handles the POST, parent-recipient lookup (joins
    parent_account + parent_child + cal_school_event.affected_class_ids),
    and writeLog with the unique constraint dropping duplicate runs.
  - main.go runs a 1-hour ticker; when time.Hour()==6 it invokes the
    scanner for today. Idempotent so transient restarts don't double-
    send.
  - POST /calendar/notifications/run-now for manual trigger + backfill
    (?date=YYYY-MM-DD).
  - GET /calendar/events/:id/notifications returns notification_log
    rows scoped to the owning teacher.
  - MATRIX_SERVICE_URL + EMAIL_SERVICE_URL env vars added (default
    empty = stub mode).

Frontend (studio-v2):
  - NotificationStatus component fetches /events/:id/notifications and
    renders coloured badges per (lead, audience, channel, status).
  - DayDetail mounts NotificationStatus inside each event card when
    notify_parents or notify_students is set.

Tests:
  - 6 new Go unit tests for bucketFor + Render (de/tr/fallback paths)
    + substitute(class_suffix). 89 subtests gesamt.
  - 2 new Playwright tests: badge render with mocked log, hidden when
    notifications are off.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 18:12:39 +02:00
Benjamin Admin d9858084dd Phase 9c: Parent accounts, magic-link login + parent timetable view
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 31s
CI / test-go-edu-search (push) Successful in 30s
CI / test-python-klausur (push) Failing after 2m36s
CI / test-python-agent-core (push) Successful in 21s
CI / test-nodejs-website (push) Successful in 26s
Backend (school-service):
  - parent_account, parent_child, parent_magic_link, parent_session
    tables. Tokens are sha256-hashed in DB; raw goes back exactly
    once to the inviting teacher.
  - InviteParent upserts the parent account, links a child to a tt_
    class, mints a 7-day magic link. Returns the link path so the
    teacher can paste it into Matrix/Email.
  - RedeemMagicLink validates + marks used + mints a 30-day session,
    sets HttpOnly bp_parent_session cookie.
  - ParentSessionMiddleware reads the cookie and resolves the parent.
    Lives in its own router group /api/v1/parent — totally separate
    from the teacher JWT path.
  - ParentMe returns the account + list of children (with class name).
  - ParentTimetable returns the latest completed tt_solution's lessons
    for the requested child's class, with full authorization check
    (parent must own a child in that class).

Frontend (studio-v2):
  - lib/calendar/subject-i18n.ts maps 22 German subject names to 8
    parent locales (de/en/tr/ar/uk/ru/pl/fr). Falls back to German
    for custom subjects.
  - ParentManager component on the Schulkalender page lets the teacher
    invite parents via email + child name + class + language. Newly
    minted magic-link is shown with a copy-to-clipboard button.
  - app/api/parent/[...path]/route.ts proxies parent-side endpoints
    via the cookie so HttpOnly survives the Next.js round-trip.
  - /eltern/login?token=… redeems and redirects to /eltern.
  - /eltern shows a Wochengrid with German days + translated subject
    names in the parent's preferred language. Headings and weekday
    labels also localised (de/en/tr/ar/uk/ru/pl/fr).

Tests:
  - 3 new Go unit tests (random token, hash stability, invite-request
    validator). 83 subtests gesamt.
  - studio-v2: e2e/eltern.spec.ts mit 7 tests across ParentManager,
    /eltern/login, /eltern overview, subject-i18n end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 11:50:35 +02:00
Benjamin Admin 97e37837ee Phase 9a: Schulkalender — Bundesland-Auswahl + Monatsansicht mit Ferien
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 29s
CI / test-go-edu-search (push) Successful in 27s
CI / test-python-klausur (push) Failing after 2m50s
CI / test-python-agent-core (push) Successful in 18s
CI / test-nodejs-website (push) Successful in 21s
Backend (school-service):
  - cal_public_event (region, event_type, name_de, name_en, start/end,
    UNIQUE(region, event_type, name_de, start_date)) — global snapshot.
  - cal_school_config (user_id PRIMARY KEY, bundesland, school year dates).
  - cal_school_event — Schul-eigene Termine; CRUD folgt in 9b.
  - GET /calendar/holidays?region=&from=&to= — Range-Query against
    cal_public_event, ordered by start_date.
  - GET / PUT /calendar/config — upsert Bundesland per User.
  - SeedFromSnapshot reads internal/seed/calendar_holidays.json on every
    boot; idempotent via the unique constraint. Async goroutine so the
    HTTP server starts immediately even if the seed file is large.

Data source:
  - scripts/calendar-snapshot.sh ruft openholidaysapi.org fuer alle 16
    Bundeslaender x 3 Schuljahre und schreibt
    school-service/internal/seed/calendar_holidays.json (854 Events,
    Stand Schuljahre 2026-2028).
  - Dockerfile kopiert das seed/-Verzeichnis ins Image, damit die
    Container-Datenbank beim ersten Start gefuellt wird.

Frontend (studio-v2):
  - /schulkalender Page mit Gradient + Blobs wie /stundenplan und
    /korrektur — gleicher Visual-Style.
  - BundeslandWizard: zeigt alle 16 Laender als Dropdown, speichert
    bei Klick die Config und switcht zur Monatsansicht.
  - MonthView: 6-Wochen-Grid Mo-So, Feiertage rose-toned, Schulferien
    amber-toned, heutiges Datum mit Indigo-Ring. Prev/Next/Heute
    Navigation.
  - lib/schulkalender/api.ts re-uses the stundenplan JWT helper so
    auth-mode wechselt nicht.
  - Sidebar bekommt einen Schulkalender-Eintrag (Icon mit Datum-Dots,
    Pfad /schulkalender) in allen 26 Sprachen.

Tests:
  - Go: 3 neue Validator-Tests (Bundesland len=5, EventType oneof,
    Pflichtfelder). 77 Tests gesamt, alle gruen.
  - Playwright: e2e/schulkalender.spec.ts mit Wizard, Save-Flow,
    MonthView-Render, Heute-Button, Sidebar-Link. Hermetisch via
    mockCalendarApi.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 09:46:39 +02:00
Benjamin Admin f042f2896b Phase 5: Timefold timetable-solver-service + solution persistence
school-service additions:
  - tt_solution + tt_lesson migration. tt_lesson carries three UNIQUEs
    (solution+class, solution+teacher, solution+room per slot) so the
    DB itself rejects any double-booking the solver might emit by
    mistake.
  - Solution CRUD + GET solutions/:id/lessons endpoint with joined
    class/subject/teacher/room names for display.
  - POST /timetable/solutions creates the row then fires off the
    solver-service via HTTP (5s timeout, mark failed if unreachable).
  - SOLVER_SERVICE_URL config wired through main.go/handlers.

New service timetable-solver-service:
  - Python 3.11 + FastAPI + Timefold Solver 1.21 (Apache-2.0). Dockerfile
    bundles OpenJDK 17 since Timefold for Python is a JPype bridge.
  - app/domain.py — Timefold @planning_entity Lesson with timeslot+room
    as PlanningVariables; @planning_solution Timetable holds problem
    facts (rooms/teachers/etc.) AND rule-fact collections.
  - app/rules.py — frozen dataclasses mirroring 6 of the 15 tt_
    constraint_* tables initially.
  - app/constraints.py — ConstraintProvider with 3 universal hard
    constraints (no double-booking) + 5 DB-driven constraints
    (teacher_unavailable_day/window, teacher_excluded_room,
    room_unavailable, room_requires_type) + 1 quality soft constraint
    (subject_preferred_period). Remaining 9 constraint types ready to
    plug in via the same join pattern.
  - app/repository.py — async loaders for stammdaten + rules; builds
    one Lesson per (curriculum row × weekly_hours), skipping rows
    without a tt_assignment teacher.
  - app/runner.py — runs solver in ThreadPoolExecutor so the FastAPI
    event loop stays responsive. Updates tt_solution status
    pending→running→completed|infeasible|failed.
  - app/main.py — POST /api/v1/solve (202 Accepted, background task),
    GET /api/v1/jobs/{id}, /health. School-service polls tt_solution
    directly instead of GET /jobs for the typical case.
  - docker-compose.yml adds the service on port 8095, depending on
    core-health-check.

Tests:
  - school-service: validator test for CreateTimetableSolutionRequest
    (allows empty name).
  - solver-service: tests/test_domain.py + tests/test_rules.py cover
    construction + hashability of the planning facts. Full solve flow
    deferred to Phase 8 integration with seed data.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 00:16:52 +02:00
Benjamin Admin e958f88a2d Add timetable scheduler Phases 1 + 2 to school-service
Phase 1 — Stammdaten (7 tables):
  tt_class, tt_period, tt_room, tt_subject, tt_teacher,
  tt_curriculum, tt_assignment with CRUD endpoints.

Phase 2 — Constraints (15 typed tables):
  Teacher (6): unavailable_day, unavailable_window, max_hours_day,
    max_hours_week, excluded_subject, excluded_room
  Subject (5): min_day_gap, max_consecutive, contiguous_when_repeated,
    preferred_period, double_lesson
  Class (2): max_hours_day, no_gaps
  Room (2): requires_type, unavailable

Each constraint row carries is_hard / weight / active / note /
created_by_user_id; ownership enforced via WHERE EXISTS against the
parent tt_teacher/tt_class/tt_subject/tt_room row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:12:23 +02:00
Benjamin Boenisch 5a31f52310 Initial commit: breakpilot-lehrer - Lehrer KI Platform
Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website,
Klausur-Service, School-Service, Voice-Service, Geo-Service,
BreakPilot Drive, Agent-Core

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:26 +01:00