# Meeting-End Auto-Import — Design Spec **Datum:** 2026-05-06 **Status:** Draft → Review ## Ziel `/meeting-end` soll Recap (Teams/Copilot) und Transkript automatisch via MS Graph holen, mit der Meeting-Notiz mergen, und die Agenda fortschreiben. Manueller Trigger, semi-automatische Agenda-Rückführung mit Konfidenz-Schwelle. ## Kontext **Bestehend:** - Slash-Command `/meeting-end` (interaktiv, ohne Auto-Import) - Skill `jour-fixe.md` definiert Lifecycle (🆕 Neu → 📌 Dauerläufer → ✅ Archiv / ⏸️ Postponed) - Node-Scripts `scripts/lib/o365-calendar.js` mit MSAL Client-Credentials Flow, MS Graph Client, Calendar.Read - Meeting-Notizen tragen `o365_id` (Einzel-Event-ID) im Frontmatter - Agenda.md trägt nur `serie: ` — kein Link zum Outlook-Serientermin **Neu erforderlich:** - `o365_series_id` Frontmatter-Feld in Agenda.md (= `seriesMasterId`) - Node-Skript `fetch-meeting-artifacts.js` für Recap + Transkript - Erweiterung `/meeting-end` um Auto-Import-Schritt ## Entscheidungen (aus Brainstorming) | Punkt | Entscheidung | |-------|--------------| | Quelle | Doppel-Pass: Copilot-Recap + Rohtranskript | | Trigger | Manuell (`/meeting-end`) | | Meeting-ID | Primär `o365_id` aus Frontmatter, Fallback Auto-Match | | Recap-API | `aiInsights` (Beta) testen, sonst manueller Paste-Fallback | | Agenda-Rückführung | Hybrid: eindeutig auto, Grauzone zur Bestätigung | | Notiz-Merge | Mergen oder Skippen bei Duplikaten (Live-Notizen behalten) | | Serien-Link | Neues Frontmatter-Feld `o365_series_id` in Agenda.md | ## Architektur ``` [/meeting-end] ↓ [Claude] identifiziert Meeting-Notiz (current_note / Argument) ↓ [fetch-meeting-artifacts.js] ├─ liest o365_id (oder Args) ├─ MS Graph: resolve OnlineMeeting ├─ MS Graph: GET .../transcripts (.vtt) ├─ MS Graph (Beta): GET .../aiInsights └─ Output JSON: { transcript, recap?, meeting } ↓ [Claude] Doppel-Pass-Synthese ├─ Recap → Struktur (Topics, Entscheidungen, Tasks) ├─ Transkript → Validierung + Detail-Lücken └─ Merge mit Live-Notizen aus Notiz, Dedup ↓ [Claude] Agenda-Rückführung mit Konfidenz-Heuristik ├─ klar erledigt → archivieren (auto) ├─ klar offen → Historie ergänzen (auto) ├─ klar postponed → verschieben (auto) └─ unklar → Liste zur Bestätigung ↓ [Bericht] Zusammenfassung der Aktionen ``` ## Komponenten ### 1. `scripts/fetch-meeting-artifacts.js` (neu) **CLI:** ``` node fetch-meeting-artifacts.js \ --o365-id \ [--include-transcript] [--include-recap] \ [--out ] # Fallback ohne o365-id: node fetch-meeting-artifacts.js \ --date 2026-05-06 --title-regex "Jour Fixe IT Team" ``` **Auth:** bestehender MSAL-Client-Credentials-Flow. **Erforderliche Graph-Permissions** (Application): - `Calendars.Read` (vorhanden) - `OnlineMeetings.Read.All` (neu) — über Application Access Policy auf `AZURE_USER_EMAIL` - `OnlineMeetingTranscript.Read.All` (neu) - ggf. `OnlineMeetingArtifact.Read.All` für Recap **Ablauf:** 1. Lade Calendar-Event via `o365_id` → extrahiere `joinUrl`/`joinWebUrl` 2. Resolve OnlineMeeting via `GET /users/{userId}/onlineMeetings?$filter=JoinWebUrl eq '...'` 3. Fetch Transkripte: `GET /users/{userId}/onlineMeetings/{id}/transcripts` 4. Lade Inhalte (.vtt) → parse zu plain text mit Sprecher-Tags 5. Versuche Recap (`aiInsights` Beta-Endpoint) — bei 403/404 → `recap: null` (kein Fehler) 6. Output JSON: ```json { "meeting": { "id", "subject", "start", "end", "organizer", "attendees" }, "transcript": "speaker: text\nspeaker: text\n...", "recap": { "decisions": [], "openQuestions": [], "topics": [...], "tasks": [] } | null } ``` **Tests** (`scripts/test/fetch-meeting-artifacts.test.js`): - vtt-Parser: zeitstempel-zu-plain mit Sprechern - Resolve OnlineMeeting via JoinUrl: Mock-Response - Recap-Fallback bei 404 - CLI-Args-Parsing ### 2. `o365_series_id` Frontmatter in Agenda.md **Migration:** - Einmal-Skript `scripts/backfill-series-ids.js` (optional): liest jede Agenda.md, sucht im Calendar nach `seriesMasterId` für die Serie (Match per `serie:`-Feld + bekanntem Titel-Pattern), trägt ein. - Alternativ: lazy beim ersten `/meeting` einer Serie — wenn `o365_series_id` leer, Claude liest `seriesMasterId` aus aktuellem Event und schreibt es ein. **Verwendung bei `/meeting-end`:** - Aus Meeting-Notiz `o365_id` → Calendar-Event lesen → `seriesMasterId` extrahieren - Match gegen `o365_series_id` aller Agenda.md-Dateien → eindeutige Serie identifizieren ### 3. `/meeting-end` Command (überarbeitet) **Neue Schritte (vor bestehender Logik):** 0. **Auto-Import:** a. Identifiziere Meeting-Notiz (current_note / Args). b. Lies `o365_id` aus Frontmatter. c. Falls vorhanden → `fetch-meeting-artifacts.js --o365-id `. d. Falls leer → Auto-Match per Datum+Serie, sonst Frage an User. e. Falls `recap === null` → User-Prompt: "Kein Recap aus Graph. Bitte Teams-Recap einfügen oder mit nur Transkript fortfahren?" 1. **Doppel-Pass-Synthese:** - Lies bestehende Meeting-Notiz (Live-Notizen). - Aus Recap: extrahiere Topics, Entscheidungen, offene Fragen, Tasks. - Aus Transkript: pro Recap-Aussage Validierung; ergänze Details, die im Recap fehlen aber wichtig sind (Schwelle: 2+ Sprecher zum Thema). - Merge mit Live-Notizen: Duplikate skippen (Heuristik: gleicher Aktor + gleiche Kernaussage = Duplikat). Im Zweifel beibehalten. - Schreibe Sektionen `## Notizen`, `## Entscheidungen`, `## Offene Fragen`, `## Aufgaben` der Meeting-Notiz. 2. **Agenda-Rückführung mit Konfidenz-Heuristik:** Pro Topic im Meeting-Notiz Agenda-Block: | Signal im Recap/Transkript | Konfidenz | Aktion | |---|---|---| | "erledigt" / "abgeschlossen" / "✅" + alle Tasks done | HOCH | auto-archive | | "verschoben" / "postponed" / "vertagt" | HOCH | auto-postpone | | Topic erwähnt, kein Abschluss-Marker | HOCH | auto-update Historie | | Topic nicht erwähnt | HOCH | unverändert lassen | | Mehrdeutig | NIEDRIG | zur Bestätigung listen | 3. **Sammel-Bestätigung:** Liste aller HOCH-Konfidenz-Aktionen + Liste der NIEDRIG-Konfidenz-Topics zur Entscheidung. User bestätigt einmal für alle HOCH + entscheidet pro NIEDRIG. 4. **Ausführung** (wie bisheriger jour-fixe-Skill): Historie schreiben, Archivieren, Postponed verschieben, neue Topics zu Dauerläufern. 5. **Status:** `status: abgeschlossen`. 6. **Bericht:** "Meeting [Serie] vom [Datum] abgeschlossen. Recap: ✓/✗. Transkript: ✓. X Topics aktualisiert (auto), Y archiviert (auto), Z postponed (auto), W brauchten Bestätigung." ### 4. Merge-Heuristik Details **Notizen-Merge:** - Live-Notizen aus existierenden `## Notizen`/`## Entscheidungen`/`## Aufgaben` werden zuerst eingelesen. - Recap+Transkript-Output wird Item-weise verglichen: - **Skip** wenn Live-Eintrag mit gleichem Aktor + gleicher Kernaussage existiert. - **Mergen** wenn Live-Eintrag stub ist (z.B. nur Stichwort), Recap-Detail ergänzt. - **Anhängen** wenn neu. - Reihenfolge erhalten: Live-Reihenfolge bleibt, neue Recap-Items hinten dran. **Aufgaben-Merge:** - Match per Owner + Kernhandlung. Bei Match: bestehender Eintrag, ggf. Owner ergänzen. - Tasks ohne Owner aus Recap → fragen oder in `(unzugeordnet)` parken. ## Datenfluss-Beispiel ``` User: /meeting-end → current_note = 03 Bereiche/Meetings/2026-05-06 Jour Fixe IT Team.md → Read frontmatter: o365_id = AAMk... Claude → node scripts/fetch-meeting-artifacts.js --o365-id AAMk... Output: { meeting, transcript: "...", recap: {...} } Claude → liest existing Notiz (Live-Notizen vorhanden in ## Notizen) → Doppel-Pass-Synthese → schreibt aktualisierte Notiz Claude → liest 03 Bereiche/Jour Fixe/IT Team/Agenda IT Team.md → matcht Topics aus Notiz vs Agenda → klassifiziert Konfidenz → präsentiert Sammel-Liste: "Auto: 5 Topics aktualisiert, 2 archiviert, 1 postponed. Bestätigung nötig: 'NAC-Projekt' — erledigt oder offen?" → wartet auf User-Input für unklare → schreibt Agenda + Archiv Claude → setzt status: abgeschlossen → Bericht. ``` ## Error Handling | Fehler | Verhalten | |---|---| | MSAL-Auth fehlgeschlagen | Abbruch, klare Fehlermeldung mit Hinweis auf .env | | `o365_id` fehlt + kein Match | User fragen: o365-id manuell eintragen oder ohne Auto-Import fortfahren | | Transkript nicht verfügbar (Meeting läuft noch / nicht aufgezeichnet) | Wenn Recap da: nur Recap nutzen. Sonst: Hinweis + manuelle Fortführung | | `aiInsights` 403/404 | Recap=null, weiter mit Transkript-only oder User-Paste | | Graph-Rate-Limit | Backoff + Retry (3x, exponential) | | Mehrere Transkripte | nimm das längste / aktuellste | | User-Abbruch bei Sammel-Bestätigung | Notiz schon gespeichert, Agenda unverändert, Status nicht gesetzt | ## Testplan **Unit:** - vtt-Parser - OnlineMeeting-Resolver per JoinUrl - Konfidenz-Heuristik (Topic-Klassifikation aus Recap-Text) - Merge-Dedup für Notizen + Aufgaben **Integration:** - Echtes Meeting (read-only): fetch + parse, kein Schreiben - Dry-Run Mode (`--dry-run`): zeigt was geschrieben würde, ohne Datei-Änderungen **E2E manuell:** - Echtes Jour Fixe IT Team durchlaufen, dann `/meeting-end` - Überprüfen: Notiz-Merge sauber, Agenda korrekt fortgeschrieben, Archiv enthält erledigte Topics ## Offene Fragen - **Graph-Permissions:** Hat die bestehende Azure-App `OnlineMeetings.Read.All` + `OnlineMeetingTranscript.Read.All` und Application Access Policy auf `AZURE_USER_EMAIL`? Falls nein → Tenant-Admin Aktion erforderlich. - **`aiInsights`-Verfügbarkeit im Tenant:** muss empirisch getestet werden (Beta-Endpoint, abhängig von Teams Premium/Copilot-Lizenz). - **Backfill von `o365_series_id`:** Einmal-Skript bauen oder lazy beim nächsten `/meeting` jeder Serie? - **Sprecher-Mapping:** Transkript liefert E-Mail/Display-Name — sollen wir gleich gegen `00 Kontext/Personen/` matchen und Wikilinks setzen? - **Recordings:** sollen Aufzeichnungen ebenfalls verlinkt werden (Anhang im Frontmatter), oder reine Transkript+Recap-Lösung?