docs: add meeting-end auto-import design spec
This commit is contained in:
parent
867c1c679d
commit
210112beb7
|
|
@ -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?
|
||||||
Loading…
Reference in New Issue