# JARVIS Nextcloud-Kalender-Integration (Phase 4c) - Design **Datum:** 2026-09-13 **Status:** Approved, bereit fuer Implementierungsplan ## Kontext Naechste Erweiterung nach dem Wetter-Widget: Zugriff auf einen bestehenden Nextcloud-Kalender ("FFW-Onza-Alle", ein geteilter Organisations-Kalender der Freiwilligen Feuerwehr Onza) via CalDAV. JARVIS soll Termine anzeigen, im Chat abfragbar machen und - nach Bestaetigung im Chat - neue Termine anlegen koennen. **Verbindung verifiziert (13.09.2026):** - CalDAV-URL: `https://cloud.ffw-onza.de/remote.php/dav/calendars/jonny/ffw-onza-alle/` - Auth: HTTP Basic mit Nextcloud-App-Passwort (User `jonny`) - Python-Library `caldav` (+ `icalendar` als Abhaengigkeit) liest und schreibt erfolgreich gegen diesen Kalender (Testtermin angelegt und wieder geloescht) - Kalender-Timezone: Europe/Berlin - Enthaelt sowohl Termine mit Uhrzeit (`dtstart` als `datetime`) als auch ganztaegige Termine (`dtstart` als `date` ohne Uhrzeit, z.B. "Volksfestwache") ## Sicherheit: Schreibzugriff nur nach Bestaetigung Der Kalender ist ein geteilter Organisations-Kalender, kein privater. Direkte, unbestaetigte Termin-Erstellung durch ein LLM (Halluzinationsrisiko) waere in einem geteilten Kalender riskanter als in einem privaten. Deshalb: - Der Chat-System-Prompt weist Claude explizit an, vor jedem Aufruf von `create_calendar_event` im Klartext nachzufragen ("Soll ich den Termin XY am [Datum] anlegen?") und das Tool erst aufzurufen, nachdem der Nutzer im naechsten Chat-Turn zugestimmt hat. - Das ist Prompt-Steuerung, kein hartes technisches Gate (kein separates Freigabe-UI) - Standardmuster fuer LLM-Tool-Use mit Human-in-the-loop bei diesem Umfang. Ein hartes Gate (z.B. ein Bestaetigungs-Endpoint) waere fuer diese Phase Overengineering (YAGNI) und ist ein moegliches Follow-up, falls sich in der Praxis zeigt, dass die Prompt-Steuerung nicht ausreicht. ## Architektur Neues Modul-internes CalDAV-Setup in `main.py` (kein eigenes File noetig bei diesem Umfang - passt zur bestehenden Ein-Datei-Struktur des Projekts): ``` NEXTCLOUD_CALDAV_URL = os.getenv("NEXTCLOUD_CALDAV_URL") NEXTCLOUD_USER = os.getenv("NEXTCLOUD_USER") NEXTCLOUD_APP_PASSWORD = os.getenv("NEXTCLOUD_APP_PASSWORD") ``` Zwei synchrone Helper-Funktionen (die `caldav`-Library ist nicht async-nativ, daher ueber `asyncio.to_thread` aufgerufen - gleiches Muster wie die bestehenden Postgres-Helper via `db_query`): - `list_upcoming_events(days_ahead: int) -> list[dict]` - liest Termine der naechsten N Tage, sortiert nach Start, gibt `{summary, start, end, description, all_day}` pro Termin zurueck (Datumswerte als ISO-Strings). - `create_event(summary: str, start: str, end: str, description: str = "") -> dict` - legt einen neuen Termin an (ISO-8601-Datums-/Zeitstrings als Input, vom Aufrufer bzw. von Claude im Tool-Call geliefert), gibt die gespeicherten Werte zurueck. ## Backend-Endpoint (fuer das Dashboard-Widget) `GET /api/v1/calendar/events?days=14` (geschuetzt wie alle anderen Routen) ruft `list_upcoming_events` auf und gibt `{"events": [...]}` zurueck. Bei Verbindungs-/Auth-Fehler zu Nextcloud: `503` mit Fehlermeldung statt stillem leerem Array - ein leeres Array waere nicht von "wirklich keine Termine" unterscheidbar. ## Chat-Integration: Claude Tool Use `/api/v1/chat` bekommt einen `tools`-Parameter mit zwei Tool-Definitionen: 1. `list_calendar_events` - Parameter `days_ahead` (Ganzzahl). Ruft `list_upcoming_events` auf. 2. `create_calendar_event` - Parameter `summary`, `start` (ISO-8601), `end` (ISO-8601), optional `description`. Ruft `create_event` auf. Ablauf pro Chat-Request: Claude wird mit den Tools aufgerufen. Antwortet Claude mit einem `tool_use`-Block, fuehrt das Backend die entsprechende Python-Funktion aus, haengt das Ergebnis als `tool_result` an die Konversation an und ruft Claude ein zweites Mal auf, um die finale, natuerlichsprachige Antwort zu bekommen (ein Tool-Call-Zyklus reicht fuer die hier vorgesehenen Anwendungsfaelle - keine mehrstufige Tool-Verkettung noetig, YAGNI). Der System-Prompt wird um die Bestaetigungs-Regel (siehe oben) ergaenzt. Die vom Tool-Use erzeugten Zwischennachrichten (der `tool_use`- und `tool_result`-Turn) werden **nicht** in der `messages`-Tabelle gespeichert - nur die sichtbare finale Nutzer-/Assistant-Nachricht, wie bisher. Das haelt die Persistenz-Logik unveraendert und die Chat-Historie fuer das Frontend weiterhin einfach (reine user/assistant-Paare). ## Frontend: "Nächste Termine"-Widget im Dashboard Neue Sektion in `Dashboard.tsx` (oder eigene Komponente `CalendarWidget`, analog zu `WeatherWidget`): laedt `GET /api/v1/calendar/events?days=14` und zeigt eine einfache Liste (Datum + Uhrzeit falls vorhanden + Titel). Kein eigenes Erstellen/Bearbeiten im Dashboard (das laeuft ueber den Chat) - YAGNI fuer diese Phase. ## Fehlerbehandlung - Nextcloud nicht erreichbar/Auth ungueltig: `list_upcoming_events`/ `create_event` werfen eine Exception mit klarer Meldung. Im Dashboard-Endpoint wird daraus ein `503`. Im Chat-Tool-Use-Pfad wird die Fehlermeldung als `tool_result` mit `is_error: true` an Claude zurueckgegeben, das sie dann verstaendlich im Chat formuliert (kein roher Stacktrace fuer den Nutzer). - Ganztaegige Termine (kein `dtstart`-Zeitanteil): werden mit `all_day: true` markiert, Frontend zeigt dafuer nur das Datum ohne Uhrzeit. ## Testing - Backend: Unit-Tests fuer `list_upcoming_events` und `create_event` mit gemocktem `caldav.DAVClient` (keine echten Netzwerkaufrufe im Test). - Manueller End-to-End-Test nach Deployment: Dashboard-Widget zeigt echte Termine (bereits verifiziert: 11 vorhandene Termine im Kalender), im Chat nach "was steht diese Woche an?" fragen, dann testweise einen Termin anlegen lassen inkl. Bestaetigungs-Nachfrage - danach den Test-Termin wieder aus dem echten Kalender loeschen (manuell oder per Skript), damit der Organisations-Kalender sauber bleibt. ## Out of Scope (bewusst nicht Teil dieser Phase) - Bearbeiten/Loeschen bestehender Termine ueber Chat oder Dashboard. - Mehrere Kalender/Kalenderauswahl (nur der eine angegebene Kalender). - Wiederkehrende Termine anlegen (nur einmalige Termine ueber `create_calendar_event`). - Hartes technisches Freigabe-Gate fuer Termin-Erstellung (siehe "Sicherheit" oben) - Prompt-Steuerung reicht fuer den Start.