# V7.1.0 — Calendar sync + Meeting Notes > **Statut** : ✅ Livré en v7.1.0 (2026-09-28) — `services/calendar_sync.py`, `services/meetings.py`, `routers/meetings.py`, migration 27, 15 tests verts · **Roadmap** : `ROADMAP.md § v7.1.0` > Notes d'implémentation : pas de lib CalDAV/Google (REST + XML bruts) ; refresh OAuth hors scope (relink explicite sur 401) ; conflit = LWW auto + notif (pas de picker « garder ma version ») ; free/busy au jour (pas de créneaux horaires). > **Référence Notion** : Notion Calendar (Google sync) + AI Meeting Notes → trigger Custom Agents (07/2026). > **Existant** : vues jour/semaine/mois, moteur `recurrence.py`, `reminders.py` (scan 60s, dédup), timezones, `notify_assignment()`, template Meeting Notes. --- ## 1. Sync externe bidirectionnelle ```sql CREATE TABLE calendar_links ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, provider TEXT NOT NULL, -- google|caldav tokens_enc TEXT NOT NULL DEFAULT '', -- Fernet (refresh + access) calendar_id TEXT NOT NULL DEFAULT 'primary', sync_token TEXT DEFAULT '', last_sync TIMESTAMP, UNIQUE(user_id, provider, calendar_id) ); ALTER TABLE collection_pages ADD COLUMN external_event_id TEXT; CREATE TABLE meeting_transcripts ( id INTEGER PRIMARY KEY AUTOINCREMENT, page_id INTEGER NOT NULL REFERENCES pages(id) ON DELETE CASCADE, audio_path TEXT NOT NULL DEFAULT '', transcript TEXT NOT NULL DEFAULT '', summary TEXT NOT NULL DEFAULT '', language TEXT DEFAULT 'fr', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); ``` - OAuth Google (`app/auth/providers/google_calendar.py`, scope `calendar.events`) + CalDAV générique (URL/user/pass chiffrés). - Pull 15 min : event externe → page (titre + `date` + `__external_id__`) ; push : occurrence FlowDeck → event (si `sync_enabled` par collection). - Conflit (modifié des 2 côtés depuis `last_sync`) → LWW + notif `calendar.conflict` + lien « garder ma version / prendre externe ». - `GET /db/{id}/calendar/freebusy?attendees=&from=&to=` : créneaux libres (jour ouvré 9h-18h, fuseau user). ## 2. Meeting Notes v2 Upload `.mp3/.wav/.m4a` (100 MB) sur page Meeting → `meeting_transcripts` → transcription (Whisper local si binaire présent, sinon API via `llm_config`, timeout 10 min, job fond + polling) → résumé `ai_writing.py` (`Décisions`, `Next steps [[person]]`, `Action items`) inséré en blocs + `Agenda/Notes` existants complétés. Émet `meeting.summarized` → automations v7.0 (update tracker, post Slack, file tickets). ## 3. Tests (`tests/test_v71_calendar_meetings.py`, ~15) OAuth mock (tokens chiffrés roundtrip), pull/push, conflit LWW + notif, freebusy, transcription mock → résumé blocs, trigger émis, quota 100 MB refusé.