Compare commits

...

2 Commits

Author SHA1 Message Date
beo3000 18e7737db5 upd 2026-05-06 22:10:46 +02:00
beo3000 a401f4fb55 docs(it-management): document meeting auto-import via graph 2026-05-06 22:05:48 +02:00
5 changed files with 1673 additions and 24 deletions

View File

@ -43,7 +43,41 @@
"Bash(git -C \"D:/projects/chrka/brain\" commit -m \"docs: clarify backfill scope and recording link in meeting-end spec\")",
"Bash(git -C \"D:/projects/chrka/brain\" add \"docs/superpowers/plans/2026-05-06-meeting-end-auto-import.md\")",
"Bash(git -C \"D:/projects/chrka/brain\" commit -m \"docs: add meeting-end auto-import implementation plan\")",
"Bash(git -C \"D:/projects/chrka/brain\" status -sb)"
"Bash(git -C \"D:/projects/chrka/brain\" status -sb)",
"Bash(node --test test/vtt-parser.test.js)",
"Bash(npm test *)",
"Bash(node --test scripts/test/vtt-parser.test.js)",
"Bash(node --test test/speaker-matcher.test.js)",
"Bash(node -e ' *)",
"Bash(node --test test/graph-meetings.test.js)",
"Bash(node --test test/o365-calendar.test.js)",
"Bash(cd /d/projects/chrka/brain && git show --stat 997e941)",
"Bash(cd /d/projects/chrka/brain && git show 997e941)",
"Read(//d/d/projects/chrka/brain/**)",
"Bash(cd /d D:\\\\projects\\\\chrka\\\\brain\\\\scripts && node --test test/o365-calendar.test.js)",
"Bash(cd /D/projects/chrka/brain && git add scripts/lib/o365-calendar.js scripts/test/o365-calendar.test.js && git commit -m \"$\\(cat <<'EOF'\nrefactor\\(o365\\): extract authenticated graph client helper, add getEventById validation\n\nExtract buildAuthenticatedClient\\(\\) helper to eliminate duplicate MSAL token\nacquisition logic in getCalendarEvents and getEventById. Add eventId validation\nand error wrapping with cause chain in getEventById.\n\nNew tests verify eventId type and empty-string checks.\n\nCo-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>\nEOF\n\\)\")",
"Bash(cd /d/projects/chrka/brain && git show 51613ff)",
"Bash(node --test test/fetch-meeting-artifacts.test.js)",
"Bash(cd \"D:\\\\projects\\\\chrka\\\\brain\" && git add scripts/fetch-meeting-artifacts.js scripts/test/fetch-meeting-artifacts.test.js && git commit -m \"$\\(cat <<'EOF'\nfeat\\(scripts\\): add fetch-meeting-artifacts orchestrator cli\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>\nEOF\n\\)\")",
"Bash(cd /d/projects/chrka/brain && git show 15499cf --stat)",
"Bash(node --test scripts/test/fetch-meeting-artifacts.test.js)",
"Bash(cd /d/projects/chrka/brain && git show 15499cf)",
"Bash(node --test test/backfill-series-id.test.js)",
"Bash(cd \"D:/projects/chrka/brain\" && git add scripts/backfill-series-id.js scripts/test/backfill-series-id.test.js && git commit -m \"$\\(cat <<'EOF'\nfeat\\(scripts\\): add backfill-series-id script for jour-fixe agendas\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>\nEOF\n\\)\")",
"Bash(cd /d/projects/chrka/brain && git show 0679aa0 --stat)",
"Bash(node --test scripts/test/backfill-series-id.test.js)",
"Bash(node -e \"const m = require\\('./scripts/backfill-series-id.js'\\); console.log\\('Exports:', Object.keys\\(m\\)\\); console.log\\('upsertFrontmatterField:', typeof m.upsertFrontmatterField\\); console.log\\('backfill:', typeof m.backfill\\);\")",
"Bash(node backfill-series-id.js \"../03 Bereiche/Jour Fixe/IT Team/Agenda IT Team.md\" \"Jour\\\\\\\\s*Fixe.*IT\\\\\\\\s*Team\")",
"Bash(node -e \"const {getCalendarEvents} = require\\('./lib/o365-calendar.js'\\); getCalendarEvents\\(30\\).then\\(es => { console.log\\(JSON.stringify\\(es.map\\(e => \\({title:e.title, isRecurring:e.isRecurring, start:e.start}\\)\\), null, 2\\)\\); }\\).catch\\(e => console.error\\(e.message\\)\\)\")",
"Bash(node -e \"const {getCalendarEvents} = require\\('./lib/o365-calendar.js'\\); getCalendarEvents\\(30\\).then\\(es => { const matches = es.filter\\(e => /jour.*fix|IT.Team|landata/i.test\\(e.title\\)\\); console.log\\(JSON.stringify\\(matches.map\\(e => \\({title:e.title, isRecurring:e.isRecurring, id:e.id}\\)\\), null, 2\\)\\); }\\).catch\\(e => console.error\\(e.message\\)\\)\")",
"Bash(node -e \"const {getCalendarEvents} = require\\('./lib/o365-calendar.js'\\); getCalendarEvents\\(60\\).then\\(es => { const matches = es.filter\\(e => /IT.*Team|landata|losch|brauer|kara|stieve|klein/i.test\\(e.title\\)\\); console.log\\(JSON.stringify\\(matches.map\\(e => \\({title:e.title, isRecurring:e.isRecurring}\\)\\), null, 2\\)\\); console.error\\('total events:', es.length\\); }\\).catch\\(e => console.error\\(e.message\\)\\)\")",
"Bash(node backfill-series-id.js \"../03 Bereiche/Jour Fixe/LANdata/Agenda LANdata.md\" \"Status.*Meeting.*KRAH.*LANdata\")",
"Bash(cd D:/projects/chrka/brain && git status && git diff --stat)",
"Edit(D:\\\\projects\\\\chrka\\\\brain\\\\.claude\\\\commands\\\\meeting.md)",
"Bash(node backfill-series-id.js \"../03 Bereiche/Jour Fixe/IT Team/Agenda IT Team.md\" \"diverse.IT.Themen\")",
"Bash(cd D:/projects/chrka/brain && git add \"03 Bereiche/Jour Fixe/IT Team/Agenda IT Team.md\" && git commit -m \"feat\\(jour-fixe\\): backfill o365_series_id for IT Team\" && git push origin main 2>&1)",
"Bash(node fetch-meeting-artifacts.js --o365-id \"AAMkADM1ZWEzODc3LTRjMTEtNGExYi04MGMwLWNhZTE0ZjkyNDVjOAFRAAgI3qsCaprAAEYAAAAAMySJViLo9EWl32JswSRPzgcA4E3NSW0FuEieq6kPTtaG_AAAAAABDQAADcgZwJ68IkWODv1ql-eWvQAGRrSRngAAEA==\" --out ./test-fetch.json)",
"Bash(node -e \"const j=require\\('./test-fetch.json'\\); console.log\\('transcript:', j.transcript?j.transcript.slice\\(0,200\\)+'...':'null'\\); console.log\\('recap:', j.recap?'YES \\(id='+j.recap.id+'\\)':'null'\\); console.log\\('recordingUrl:', j.recordingUrl\\); console.log\\('warnings:', j.warnings\\);\")"
]
},
"ms365-calendar": {

File diff suppressed because one or more lines are too long

View File

@ -90,9 +90,23 @@
"icon": "lucide-file",
"title": "2026-05-06-meeting-end-auto-import-design"
}
},
{
"id": "bc2720cfcb2da10f",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "03 Bereiche/IT-Management/Meeting Auto-Import via MS Graph.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Meeting Auto-Import via MS Graph"
}
}
],
"currentTab": 5
"currentTab": 6
}
],
"direction": "vertical"
@ -268,23 +282,27 @@
"bases:Neue Base erstellen": false
}
},
"active": "8a0d370769f861bf",
"active": "bc2720cfcb2da10f",
"lastOpenFiles": [
"docs/superpowers/plans/2026-05-06-meeting-end-auto-import.md",
"docs/superpowers/specs/2026-05-06-meeting-end-auto-import-design.md.tmp.83524.1778091651807",
"docs/superpowers/specs/2026-05-06-meeting-end-auto-import-design.md.tmp.83524.1778091588387",
"docs/superpowers/specs/2026-05-06-meeting-end-auto-import-design.md.tmp.83524.1778091573851",
"03 Bereiche/Meetings/2026-05-06 KI-Richtlinie - Absprache zur Umsetzung im LMS.md",
"docs/superpowers/specs/2026-05-06-meeting-end-auto-import-design.md",
"docs/superpowers/specs/2026-05-06-meeting-end-auto-import-design.md.tmp.83524.1778085768316",
"03 Bereiche/Meetings/2026-05-06 Jour Fixe IT Team.md.tmp.83524.1778084675533",
"03 Bereiche/IT-Management/Meeting Auto-Import via MS Graph.md",
"03 Bereiche/IT-Management/Meeting Auto-Import via MS Graph.md.tmp.83524.1778097941776",
"scripts/test-fetch.json",
"scripts/lib/o365-calendar.js.tmp.83524.1778094722205",
"scripts/backfill-series-id.js",
"scripts/backfill-series-id.js.tmp.83524.1778094476219",
"scripts/test/backfill-series-id.test.js",
"scripts/test/backfill-series-id.test.js.tmp.83524.1778094458381",
"scripts/fetch-meeting-artifacts.js",
"scripts/fetch-meeting-artifacts.js.tmp.83524.1778094337399",
"scripts/test/fetch-meeting-artifacts.test.js",
"docs/superpowers/plans/2026-05-06-meeting-end-auto-import.md",
"03 Bereiche/Meetings/2026-05-06 KI-Richtlinie - Absprache zur Umsetzung im LMS.md",
"00 Kontext/Personen/Thomas Sondermann.md",
"00 Kontext/Personen/Philip Hermann.md",
"03 Bereiche/Jour Fixe/IT Team/Agenda IT Team.md.tmp.83524.1778084518954",
"00 Kontext/Personen/Nadia Bieker (KRAH).md",
"03 Bereiche/Meetings/2026-05-05 Dunkelbuchung SAP.md",
"01 Inbox/Dunkelbuchungen Rechnungen SAP.md",
"03 Bereiche/Meetings/2026-05-05 Dunkelbuchung SAP.md.tmp.57940.1778057881564",
"01 Inbox/Unbenannt.md",
"03 Bereiche/Jour Fixe/Christian Hermann/Agenda.md",
"03 Bereiche/Meetings/2026-05-06 Jour Fixe IT Team.md",
@ -292,8 +310,6 @@
"03 Bereiche/Meetings/2026-05-06 Jour Fixe LANdata.md",
"03 Bereiche/Jour Fixe/LANdata/Archiv LANdata.md",
"03 Bereiche/KIT/Prozessdaten PDV Datenbank.md",
"03 Bereiche/Meetings/2026-05-06 Jour Fixe IT Team.md.tmp.71940.1778049959221",
"03 Bereiche/Meetings/2026-05-06 Jour Fixe LANdata.md.tmp.71940.1778049959179",
"03 Bereiche/KIT/HYDRA Schichtuebergabeprotokoll Webservice Mapping.md",
"03 Bereiche/KIT/OPEX Analyse KRAH-App.md",
"03 Bereiche/Meetings/2026-04-20 Jour Fixe Christian Hermann.md",
@ -304,8 +320,6 @@
"01 Inbox/Brain Dump.md",
"01 Inbox/Erstellen und Verwalten offizieller Markenkits in der Microsoft 365 Copilot-App - Microsoft-Support.md",
"03 Bereiche/Jour Fixe/NTT/Agenda.md",
"03 Bereiche/Meetings/2026-04-22 Selbetti vpn, Qlik licenses and local server installation.md",
"03 Bereiche/Meetings/2026-04-15 Jour Fixe IT Team.md.tmp.35776.1776844681771",
"07 Anhänge/Pasted image 20260415084134.png",
"07 Anhänge/inventar/Philips Hue Bridge - cover.jpg",
"07 Anhänge/inventar/Panasonic Blu-ray-Player - cover.jpg",

View File

@ -0,0 +1,138 @@
---
tags: [it-management, integration, teams, graph]
status: aktiv
date: 2026-05-06
---
# Meeting Auto-Import via MS Graph
## Was ist das?
Erweiterung von [[Obsidian O365 Kalender Integration]]: Nach Ende eines Jour-Fixe-Meetings zieht der Befehl `/meeting-end` automatisch **Transkript** und **Copilot-Recap** aus Teams via Microsoft Graph, mergt sie mit den live mitgeschriebenen Notizen und führt die Ergebnisse in die Agenda zurück (Historie, Archiv, Postponed).
## Workflow
1. Meeting läuft in Teams (mit aktivem Facilitator → Aufzeichnung + Copilot-Recap)
2. Nach Meeting: `/meeting-end` in der Meeting-Notiz
3. Script `fetch-meeting-artifacts.js` ruft Graph API:
- `getEventById` → Event mit `seriesMasterId` + JoinUrl
- `resolveOnlineMeeting` → Teams-OnlineMeeting via JoinUrl
- `fetchTranscriptVtt` → letztes Transkript (.vtt)
- `fetchAiInsights` (Beta) → Copilot-Recap (Topics, Decisions, Action Items)
- `fetchRecordingUrl` → Recording-Link
4. Claude führt Doppel-Pass-Synthese aus: Recap + Transkript validiert
5. Sprecher-Mapping: Speaker-Namen → Personen-Wikilinks via `00 Kontext/Personen/`
6. Merge mit Live-Notizen (Live-Order bleibt, neue Items hinten)
7. Agenda-Rückführung mit Konfidenz-Heuristik:
- Topic abgeschlossen → Archiv (mit Historie)
- Topic postponed → `## ⏸️ Postponed`
- Topic offen → datierter Historie-Eintrag in Agenda
- Mehrdeutig → User-Bestätigung pro Topic
8. `recording_url` ins Frontmatter, `status: abgeschlossen`
## Komponenten
**Scripts** (`scripts/`):
- `fetch-meeting-artifacts.js` — CLI-Orchestrator (`--o365-id <id> --out <file>`)
- `lib/graph-meetings.js` — Graph-Helper für OnlineMeetings/Transcripts/aiInsights/Recordings
- `lib/vtt-parser.js` — VTT-Parser mit `<v Speaker>`-Tags, optional Cue-Merge
- `lib/speaker-matcher.js` — Speaker-Name → Person-Wikilink (wraps `person-matcher.js`)
- `lib/o365-calendar.js` — erweitert um `getEventById` + `extractJoinUrlFromBody`
- `backfill-series-id.js` — One-Shot: schreibt `o365_series_id` ins Agenda-Frontmatter
**Commands** (`.claude/commands/`):
- `meeting-end.md` — komplett neu: Auto-Import-Flow mit Konfidenz-Klassifikation
- `meeting.md` — erweitert um Lazy-Backfill von `o365_series_id`
**Frontmatter-Erweiterungen:**
- Agenda: `o365_series_id: <seriesMasterId>` (verbindet Agenda fest mit Outlook-Serie)
- Meeting-Notiz: `recording_url: <url>` (gesetzt wenn Recording vorhanden)
## Backfill-Status
| Serie | O365-Titel | `o365_series_id` |
|---|---|---|
| IT Team | "diverse IT-Themen" | ✅ gesetzt |
| LANdata | "Status Meeting KRAH / LANdata" | ✅ gesetzt |
| Andere | — | Lazy-Backfill beim ersten `/meeting <Serie>` |
## Azure-Setup
**App Registration:** OfficeAssist (`93f9f15c-b566-4ca1-9145-c23f87c7f5c7`)
Authentication: OAuth2 Client Credentials Flow (`@azure/msal-node`)
### Application Permissions (Microsoft Graph, Admin Consent)
Ergänzt am 2026-05-06:
- `OnlineMeetings.Read.All` — OnlineMeeting per JoinUrl auflösen
- `OnlineMeetingTranscript.Read.All``.vtt` lesen
- `OnlineMeetingArtifact.Read.All` — Recording-URL lesen
- `OnlineMeetingAiInsight.Read.All` — Copilot-Recap (Beta-Endpoint)
Bereits vorhanden: `Calendars.Read`, `User.Read.All`, plus Mail/Sites.
### Application Access Policy (Teams PowerShell)
**Wichtig:** Ohne diese Policy → 404 trotz korrekter Permissions. Microsoft-spezifisches Gate für Service-Apps gegen OnlineMeetings. **In Teams Admin Center UI nicht sichtbar** — nur PowerShell.
Erstellt am 2026-05-06:
```powershell
Connect-MicrosoftTeams
New-CsApplicationAccessPolicy -Identity "obsidian-graph" `
-AppIds "93f9f15c-b566-4ca1-9145-c23f87c7f5c7" `
-Description "OfficeAssist meeting auto-import"
Grant-CsApplicationAccessPolicy -PolicyName "obsidian-graph" `
-Identity "c.kauer@krah-gruppe.de"
```
Verifizierung:
```powershell
Get-CsApplicationAccessPolicy -Identity "obsidian-graph"
# erwartet: AppIds enthält 93f9f15c-...
```
Replikationszeit nach `Grant`: typisch 30-60 Min, manchmal mehrere Stunden.
### Lizenzen
- E3 + Copilot for Microsoft 365 (für Recap via aiInsights Beta-Endpoint)
- Ohne Copilot: Transkript + Recording funktionieren, Recap fällt weg
## Daten-Flow
```
Meeting endet
└─> /meeting-end
└─> fetch-meeting-artifacts.js --o365-id <id>
├─> getEventById ── Calendars.Read
├─> resolveOnlineMeeting ── OnlineMeetings.Read.All + AccessPolicy
├─> fetchTranscriptVtt ── OnlineMeetingTranscript.Read.All
├─> fetchAiInsights ── OnlineMeetingAiInsight.Read.All (Beta)
└─> fetchRecordingUrl ── OnlineMeetingArtifact.Read.All
── JSON ──>
Claude: Doppel-Pass + Speaker-Mapping + Merge + Agenda-Rückführung
└─> Meeting-Notiz + Agenda.md aktualisiert
```
## Robustheit
- Per-Artefakt-Fallback: schlägt Transcript fehl → Warning, Recap+Recording trotzdem holen
- Fehlende Permissions / fehlende Policy → graceful: Warning + leere Felder, kein Crash
- Recap fehlt (kein Copilot) → User-Prompt zum manuellen Einfügen
- Kein O365-ID → User kann manuell weitermachen ohne Auto-Import
## Bekannte Einschränkungen
- Nur Meetings, bei denen `AZURE_USER_EMAIL` der Organizer ist (Graph-Restriktion)
- Recap-Qualität abhängig von Copilot — keine Kontrolle über Inhalte
- Konfidenz-Heuristik bei mehrdeutigen Topics → User-Bestätigung nötig
- Speaker-Mapping nur bei korrekt gepflegten Personen-Notizen mit Email/Name
## Siehe auch
- [[docs/superpowers/specs/2026-05-06-meeting-end-auto-import-design]] — Design Spec
- [[docs/superpowers/plans/2026-05-06-meeting-end-auto-import]] — Implementation Plan
- [[Obsidian O365 Kalender Integration]] — Basis-Integration (Kalender-Lookup)
- [[Jour Fixe System]] — Lifecycle der Agenda-Topics

49
scripts/test-fetch.json Normal file
View File

@ -0,0 +1,49 @@
{
"meeting": {
"id": "AAMkADM1ZWEzODc3LTRjMTEtNGExYi04MGMwLWNhZTE0ZjkyNDVjOAFRAAgI3qsCaprAAEYAAAAAMySJViLo9EWl32JswSRPzgcA4E3NSW0FuEieq6kPTtaG_AAAAAABDQAADcgZwJ68IkWODv1ql-eWvQAGRrSRngAAEA==",
"subject": "diverse IT-Themen",
"start": "2026-05-06T08:00:00.0000000",
"end": "2026-05-06T09:00:00.0000000",
"seriesMasterId": "AAMkADM1ZWEzODc3LTRjMTEtNGExYi04MGMwLWNhZTE0ZjkyNDVjOABGAAAAAAAzJIlWIuj0RaXfYmzBJE-OBwDgTc1JbQW4SJ6rqQ9O1ob4AAAAAAENAAANyBnAnrwiRY4O-WqX95a9AAZGtJGeAAA=",
"attendees": [
{
"name": "Steffen Brauer (HKR)",
"email": "s.brauer@hkr-automotive.de"
},
{
"name": "Philip Losch (KRAH)",
"email": "p.losch@krah-gruppe.de"
},
{
"name": "Florian Fiala (KRAH)",
"email": "f.fiala@krah-gruppe.de"
},
{
"name": "Steffen Ackerschott (KRAH)",
"email": "s.ackerschott@krah-gruppe.de"
},
{
"name": "Eray Kara (KRAH)",
"email": "e.kara@krah-gruppe.de"
},
{
"name": "Stefan Fiebrich (KRAH)",
"email": "s.fiebrich@krah-gruppe.de"
},
{
"name": "Christopher Klein (KRAH)",
"email": "c.klein@krah-gruppe.de"
},
{
"name": "Josiah Stiewe (KRAH)",
"email": "j.stiewe@krah-gruppe.de"
}
]
},
"transcript": null,
"recap": null,
"recordingUrl": null,
"warnings": [
"onlineMeeting not found for joinUrl=https://teams.microsoft.com/l/meetup-join/19%3ameeting_OGNjZmYwMTItY2NlMC00NTgxLTlkMDQtZGY1MmM4MWJmMjlj%40thread.v2/0?context=%7b%22Tid%22%3a%2294cf90d7-e9ff-49a1-bc3b-a5b94d3cc8ca%22%2c%22Oid%22%3a%2274b2ede0-08d8-4551-a59c-239714edc0e9%22%7d"
]
}