11 KiB
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.mddefiniert Lifecycle (🆕 Neu → 📌 Dauerläufer → ✅ Archiv / ⏸️ Postponed) - Node-Scripts
scripts/lib/o365-calendar.jsmit 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: <Name>— kein Link zum Outlook-Serientermin
Neu erforderlich:
o365_series_idFrontmatter-Feld in Agenda.md (=seriesMasterId)- Node-Skript
fetch-meeting-artifacts.jsfür Recap + Transkript - Erweiterung
/meeting-endum 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 |
| Sprecher-Mapping | Transkript-Sprecher gegen 00 Kontext/Personen/ matchen, Wikilinks setzen |
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 <eventId> \
[--include-transcript] [--include-recap] \
[--out <file.json>]
# 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 aufAZURE_USER_EMAILOnlineMeetingTranscript.Read.All(neu)- ggf.
OnlineMeetingArtifact.Read.Allfür Recap
Ablauf:
- Lade Calendar-Event via
o365_id→ extrahierejoinUrl/joinWebUrl - Resolve OnlineMeeting via
GET /users/{userId}/onlineMeetings?$filter=JoinWebUrl eq '...' - Fetch Transkripte:
GET /users/{userId}/onlineMeetings/{id}/transcripts - Lade Inhalte (.vtt) → parse zu plain text mit Sprecher-Tags
- Versuche Recap (
aiInsightsBeta-Endpoint) — bei 403/404 →recap: null(kein Fehler) - Output 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 nachseriesMasterIdfür die Serie (Match perserie:-Feld + bekanntem Titel-Pattern), trägt ein. - Alternativ: lazy beim ersten
/meetingeiner Serie — wenno365_series_idleer, Claude liestseriesMasterIdaus aktuellem Event und schreibt es ein.
Verwendung bei /meeting-end:
- Aus Meeting-Notiz
o365_id→ Calendar-Event lesen →seriesMasterIdextrahieren - Match gegen
o365_series_idaller Agenda.md-Dateien → eindeutige Serie identifizieren
3. /meeting-end Command (überarbeitet)
Neue Schritte (vor bestehender Logik):
-
Auto-Import: a. Identifiziere Meeting-Notiz (current_note / Args). b. Lies
o365_idaus Frontmatter. c. Falls vorhanden →fetch-meeting-artifacts.js --o365-id <id>. d. Falls leer → Auto-Match per Datum+Serie, sonst Frage an User. e. Fallsrecap === null→ User-Prompt: "Kein Recap aus Graph. Bitte Teams-Recap einfügen oder mit nur Transkript fortfahren?" -
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,## Aufgabender Meeting-Notiz.
-
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 -
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.
-
Ausführung (wie bisheriger jour-fixe-Skill): Historie schreiben, Archivieren, Postponed verschieben, neue Topics zu Dauerläufern.
-
Status:
status: abgeschlossen. -
Bericht: "Meeting [Serie] vom [Datum] abgeschlossen. Recap: ✓/✗. Transkript: ✓. X Topics aktualisiert (auto), Y archiviert (auto), Z postponed (auto), W brauchten Bestätigung."
4. Sprecher-Mapping (scripts/lib/speaker-matcher.js)
Input: Sprecher-Liste aus Transkript (Display-Name + E-Mail aus Graph-Metadaten).
Match-Strategie (analog person-matcher.js):
- E-Mail-Match: Suche
emailim Frontmatter aller00 Kontext/Personen/*.md. - Name-Fallback: Vergleiche Display-Name mit
vorname+nachname. - Kein Match: Display-Name als Plain Text behalten, in offene Fragen aufnehmen ("Person X anlegen?").
Output: Map { "Christian Kauer" → "[[00 Kontext/Personen/Christian Kauer (KRAH)]]", "Frank Herberg" → "[[00 Kontext/Personen/Frank Herberg]]", ... }.
Verwendung:
- In Notizen-Merge: Sprecher-Erwähnungen durch Wikilinks ersetzen ("Frank hat berichtet..." → "Frank Herberg hat berichtet...").
- In Aufgaben: Owner-Namen zu Wikilinks.
- Bestehende
person-matcher.jsLogik wiederverwenden.
Tests:
- E-Mail-Match
- Name-Match mit Kurzformen (Christian → Christian Kauer (KRAH))
- Kein Match → Plain Text + Hinweis
5. Merge-Heuristik Details
Notizen-Merge:
- Live-Notizen aus existierenden
## Notizen/## Entscheidungen/## Aufgabenwerden 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
Bestätigt
- Graph-Permissions: User ergänzt im Azure Portal.
- Teams Premium / Copilot-Lizenz vorhanden →
aiInsightssollte funktionieren. - Sprecher-Mapping ist Teil des Scopes.
Backfill o365_series_id
Gezielt für IT Team und LANdata beim Aufsetzen eintragen (manuell via Skript-Aufruf oder beim ersten Test). Andere Serien: lazy beim nächsten /meeting der jeweiligen Serie automatisch eintragen.
Recording-Link
Falls Aufzeichnung verfügbar (/users/{id}/onlineMeetings/{id}/recordings):
- URL als
recording_urlim Frontmatter der Meeting-Notiz speichern. - Kein Download, nur Verlinkung — Zugriff erfolgt via Teams.