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