docs: add meeting-end auto-import design spec

This commit is contained in:
beo3000 2026-05-06 20:11:37 +02:00
parent 867c1c679d
commit 210112beb7
1 changed files with 232 additions and 0 deletions

View File

@ -0,0 +1,232 @@
# 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: <Name>` — 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 <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 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 <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?