133 lines
6.3 KiB
Markdown
133 lines
6.3 KiB
Markdown
# 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.
|