| name | bmi-reporting-decks |
| description | Lese Reporting-Daten vom MCP-Server 'bmi-reporting' aus, bereite sie auf und erzeuge daraus Slidedecks im FWU-Design. Triggert IMMER, wenn der User Reportings, KPIs, Snapshots, Status-Updates oder Auswertungen erwähnt — auch wenn kein konkreter Tool-Name fällt. Triggert ebenso bei Begriffen wie 'BMI Reporting', 'Reportings auslesen', 'KPI-Übersicht', 'KPI-Dashboard', 'Statusbericht aus Reportings', 'Reporting-Snapshot' oder beim Wunsch, aus Reporting-Daten ein Deck/eine Präsentation zu bauen. Nutzt die MCP-Tools `get_kpi_summary` und `get_reporting_snapshot` und kombiniert sich mit dem Skill 'fwu-design' für die Slide-Erstellung. |
BMI Reporting → FWU Slidedeck
Dieser Skill verbindet den MCP-Server bmi-reporting (Reporting-Datenquelle) mit dem Skill fwu-design (Folien-Erstellung im FWU-Corporate-Design). Typische Aufgabe: aus aktuellen KPI-Daten ein Statusbericht-Deck erzeugen, das visuell zum bestehenden FWU-Statusbericht-Format passt.
⚠️ Stolperfalle #1: endDate niemals als null senden
Beide MCP-Tools (get_kpi_summary, get_reporting_snapshot) haben endDate als Pflicht-String im Format YYYY-MM-DD. Wird null gesendet:
get_kpi_summary → manchmal SQL-Fehler column "event_type" does not exist, manchmal 4-Minuten-Timeout, selten zufällig erfolgreich
get_reporting_snapshot → saubere Schema-Validierung schlägt fehl, aber danach ist der Server oft für die nächsten Calls im Timeout-Zustand
Konkret in jedem Aufruf: endDate="2026-03-31" explizit setzen, auch wenn die Intuition nahelegt, dass null = „bis heute" bedeutet. Das tut es nicht.
Bei Quartalen: Enddatum ist immer der letzte Tag des letzten Monats im Quartal (31.03., 30.06., 30.09., 31.12.). Bei Monaten: letzter Tag des Monats.
Wann diesen Skill nutzen
Immer, wenn Reporting-Daten gelesen, aufbereitet und in einem Deck visualisiert werden sollen. Auslöser:
- "Mach mir ein Slidedeck aus den aktuellen Reporting-Daten"
- "Hol die KPIs vom letzten Monat und bau einen Statusbericht draus"
- "Erstelle einen Reporting-Snapshot als Präsentation"
- "Wie sieht die KPI-Übersicht aktuell aus? Pack das in ein FWU-Deck"
- "Update den telli-Statusbericht mit den neuesten Nutzendenzahlen"
- "Vergleich Q1 vs Q2 KPIs und visualisier das"
Auch dann, wenn nur ein Schritt erwähnt wird — z.B. nur "Hol mir das KPI-Summary" — diesen Skill nutzen, weil das Tool-Wissen über den bmi-reporting-MCP hier dokumentiert ist.
Voraussetzungen
⚠️ Stolperfalle #2: MCP-Server nicht verbunden. Wenn die Tools get_kpi_summary und get_reporting_snapshot nicht in der verfügbaren Tool-Liste auftauchen: Claude Desktop neu starten, damit der Server geladen wird.
- MCP-Server
bmi-reporting muss verbunden sein.
- Der Skill
fwu-design muss verfügbar sein, wenn ein Deck erzeugt werden soll. Dieser Skill ruft ihn implizit auf.
Verfügbare MCP-Tools
Detaillierte Parameter-Beschreibung: siehe references/mcp_tools.md.
| Tool | Zweck |
|---|
get_kpi_summary | Zusammenfassung der wichtigsten KPIs für einen Zeitraum/Bereich. Gibt aggregierte Zahlen zurück (Nutzendenzahlen, Kosten, Tagesnutzung etc.). |
get_reporting_snapshot | Kompletter Reporting-Snapshot zu einem bestimmten Zeitpunkt — enthält Rohdaten, Zeitreihen und Metadaten. Detaillierter als get_kpi_summary. |
Die Parameter der beiden Tools sind in der aktuellen Schema-Version strikt validiert — Pflicht sind startDate, endDate, interval, role und userState. Es gibt keinen argumentlosen Aufruf. Details in references/mcp_tools.md.
Workflow
Schritt 0 — Briefing (IMMER zuerst, vor jedem Tool-Aufruf)
Bevor irgendein MCP-Tool aufgerufen wird, dem User genau diese zwei Fragen in einer einzigen Nachricht stellen:
Zeitraum — welchen Zeitraum soll der Report abdecken?
- Letzter Monat
- Letztes Quartal
- Letztes Jahr (Kalenderjahr)
- Anderer Zeitraum (dann bitte konkret nennen)
Produkte — welche Produkte sollen enthalten sein?
- VIDIS
- telli
- Mundo
- Alle (VIDIS + telli + Mundo)
Antworten abwarten. Dann die API-Parameter wie folgt ableiten:
Zeitraum → startDate / endDate
Basisreferenz: das heutige Datum (per datetime.date.today()).
| Auswahl | startDate | endDate |
|---|
| Letzter Monat | Erster Tag des Vormonats | Letzter Tag des Vormonats |
| Letztes Quartal | Erster Tag des letzten vollständigen Quartals | Letzter Tag dieses Quartals |
| Letztes Jahr | {heute.year - 1}-01-01 | {heute.year - 1}-12-31 |
| Anderer Zeitraum | Nutzereingabe parsen | Nutzereingabe parsen |
import datetime
today = datetime.date.today()
first_of_this_month = today.replace(day=1)
last_month_end = first_of_this_month - datetime.timedelta(days=1)
last_month_start = last_month_end.replace(day=1)
q_end_month = ((today.month - 1) // 3) * 3
if q_end_month == 0:
q_end_month = 12
q_year = today.year - 1
else:
q_year = today.year
import calendar
q_end = datetime.date(q_year, q_end_month, calendar.monthrange(q_year, q_end_month)[1])
q_start = datetime.date(q_year, q_end_month - 2, 1)
year_start = datetime.date(today.year - 1, 1, 1)
year_end = datetime.date(today.year - 1, 12, 31)
Zur Erinnerung: endDate muss immer als konkreter YYYY-MM-DD-String gesetzt sein — niemals null (→ Stolperfalle #1 oben).
Produkte → modules / focus
| Auswahl | modules | focus (nur get_kpi_summary) |
|---|
| VIDIS | "vidis" | "vidis" |
| telli | "telli" | "telli" |
| Mundo | "mundo" | "mundo" |
| Alle | "all" | weglassen |
Bei Mundo: Feldnamen im Snapshot beginnen mit mundo… analog zu vidis… / telli…. Falls die API mundo als modules-Wert nicht kennt, beim ersten Aufruf ohne modules-Filter probieren und die Antwort-Struktur inspizieren.
Kurz-Bestätigung vor dem API-Call
Nach dem Briefing dem User kurz spiegeln, was jetzt gemacht wird:
„Ich hole die Daten für [Zeitraum] ([startDate] – [endDate]), Modul [Produkt]. Starte die API-Aufrufe…"
Dann erst mit Schritt 1 weitermachen.
Schritt 1 — Daten holen
1. get_kpi_summary aufrufen (für die Headline-Zahlen)
2. get_reporting_snapshot aufrufen (für Detail-Folien mit Zeitreihen)
3. Optional: pro Schulart einen KPI-Aufruf mit schoolType="…" (siehe unten)
4. Antwort-Struktur lesen und in einem Plan ablegen, welche Zahlen auf welche Folie kommen
Pflicht-Parameter für beide Tools: startDate (YYYY-MM-DD), endDate (YYYY-MM-DD), interval (day/week/month), role (FWU/VERWALT), userState (z.B. DE). Details und optionale Filter in references/mcp_tools.md — die Tools nehmen keine leeren Aufrufe entgegen.
Wenn der User einen spezifischen Zeitraum/Bereich nennt, diesen als startDate/endDate mitgeben. Wenn nicht: Default ist der laufende Monat mit interval="day".
Schritt 2 — Aufbereiten
Bevor du Folien baust, immer kurz die Datenstruktur prüfen und entscheiden:
-
Welche KPIs sind die "Headline-Zahlen"? (Typischerweise 3–5 große Stat-Callouts auf einer Übersichtsfolie)
-
Welche Zeitreihen lohnen ein Diagramm? (Verlauf Nutzendenzahlen, Kostenentwicklung, etc.)
-
Welche Vergleiche sind interessant? (Vor-/Nachher, Soll/Ist, Bundesländer-Vergleich, Schulart-Vergleich)
-
Gibt es Status-Indikatoren? (Ampelsystem 🟢🟡🔴 für Budget/Zeit/Scope/Risiken — siehe Original-Statusbericht-Folie 3)
-
Schulart-Breakdown gewünscht? Dann pro Schulart einen Aufruf mit schoolType="…" absetzen (Grundschule, Gymnasium, Berufsschule, …) — das Tool filtert, aggregiert aber nicht selbst über alle Schularten. Siehe references/mcp_tools.md → Abschnitt Schulart-Filter.
-
Durchschnitte NIE selbst berechnen — aber nur die Werte des aktivierten Moduls nutzen. Wenn get_reporting_snapshot das Feld dayTypeStats zurückgibt, enthält es vier Werte:
vidisAvgLoginsPerWorkday / vidisAvgLoginsPerNonWorkday — gelten nur bei modules="vidis" oder "all". Bei modules="telli" stehen hier 0 — nicht als "echte Null" interpretieren, sondern als "nicht aktiviert".
telliAvgMessagesPerWorkday / telliAvgMessagesPerNonWorkday — analog nur bei modules="telli" oder "all".
workdayCount / weekendCount / holidayCount — gelten immer, modulunabhängig.
Die MUSS-Regel (laut API-_note) bezieht sich nur auf die befüllten Felder des aktivierten Moduls. Gleiches gilt für previousPeriodKpis — nur die Felder des aktivierten Moduls sind valide, die anderen stehen auf 0.
Halte den Aufbereitungs-Plan dem User kurz vor, bevor du das Deck baust — gerade bei größeren Datenmengen lohnt sich eine schnelle Abstimmung über Reihenfolge und Schwerpunkte.
Schritt 3 — Deck erzeugen
Nutze den Skill fwu-design für das eigentliche Slide-Building. Standardstruktur eines Reporting-Decks:
| # | Layout | Inhalt |
|---|
| 1 | [0] Titelfolie orange | Titel ("Statusbericht …"), Datum/Periode |
| 2 | [2] Kapiteltrennseite | "01 — KPI-Übersicht" |
| 3 | [7] Standard-Inhalt | Headline-KPIs als Stat-Callouts |
| 4 | [7] | Zeitreihe Diagramm (Nutzendenzahlen o.ä.) |
| 5 | [7] | Zeitreihe Diagramm (Kosten o.ä.) |
| 6 | [9] Zweispalter | Gewinner/Verlierer vs. Vorperiode — erste Zeile als Überschrift mit Grün (Gewinner) bzw. Rot (Verlierer), danach leere 6pt-Zeile als Abstand, dann Bullets in 14pt |
| 7 | [7] | Nutzung nach Schulart (horizontales Balkendiagramm, sortiert nach Nutzendenzahl) |
| 8 | [2] Kapiteltrennseite | "02 — Detail" (optional) |
| ... | ... | Detail-Folien |
| n | [12] Abschluss | "Vielen Dank / Fragen?" |
Für Status-Indikatoren (Ampel-Tabelle wie auf Folie 3 des Original-Statusberichts): manuelle Tabelle in einer Standard-Inhaltsfolie mit den Emojis 🟢🟡🔴 und je einer Spalte für "Vorher" und "Tendenz".
Schritt 4 — Visualisierungen
| KPI-Typ | Empfohlene Darstellung |
|---|
| Einzelne Headline-Zahl | Stat-Callout (Font-Size lt. Daumenregel in fwu-design/SKILL.md, Helper: stat_callout()) |
| Zeitreihe (Tage/Monate) | Liniendiagramm in Orange #FE7235 |
| Zeitreihe Schulen pro Tag | Säulendiagramm in Orange |
| Anteilsverteilung | Donut oder gestapeltes Balkendiagramm |
| Produkt-Vergleich aktuell vs. Vorperiode | Balkendiagramm clustered: aktuell = Orange #FE7235, Vorperiode = Grau #D8D8D8. Zwei Orange-Töne sind in Legenden schwer unterscheidbar — immer Grau für Vorperiode. |
| Bundesland-Vergleich | Balkendiagramm horizontal, sortiert |
| Schulart-Vergleich | Horizontales Balkendiagramm, absteigend nach Nutzendenzahl sortiert; alternativ gestapeltes Balkendiagramm für Modul-Aufteilung (telli/vidis) je Schulart |
| Status (4 Dimensionen) | Tabelle mit 🟢🟡🔴 + "Vorher / Tendenz" |
Diagramme über python-pptx's add_chart() mit XL_CHART_TYPE.LINE/BAR_CLUSTERED/DOUGHNUT. Theme-Farben werden automatisch aus dem FWU-Template übernommen — Orange ist Accent 1.
Schritt 4a — Produkt-Namen aus Slugs mappen
Die API liefert Slugs (digitales-buecherregal, ctf-schule). In Charts/Folien immer Display-Namen verwenden. Mapping siehe references/product_names.md.
Schritt 4b — Anti-Pattern: gemischte Skalen
Nicht in einem einzigen Chart darstellen:
Werte, die in ihrer Größenordnung mehr als ~2 Zehnerpotenzen auseinander liegen, gehören nicht in einen gemeinsamen Bar-/Line-Chart. Beispiel aus Q1-telli: Nachrichten (2.128.296) vs. Power User (56). Im clustered Bar-Chart verschwinden die kleineren Werte zu unsichtbaren Strichen am Nullpunkt, der Chart liefert keinen Mehrwert.
Stattdessen:
- Separate Stat-Callouts pro Metrik. Eine Card pro Kennzahl mit eigener Skala. Für Vorher/Nachher-Vergleich: Wachstumsrate als
% groß, absolute Zahlen klein darunter.
- Hero-Stat + Grid. Eine prominente Story-Zahl links (z.B. „2,7× so viele Nachrichten"), daneben ein 2×2-Grid mit den vier Begleit-Wachstumsraten als kleine Cards.
- Nur normalisierte Werte. Wenn es unbedingt ein gemeinsamer Chart sein soll: alle Werte als „% Veränderung" oder „Index (Vorperiode = 100)" darstellen — das macht die Skalen vergleichbar.
Schnellcheck vor dem Chart: Ist max(values) / min(values) > 100? Wenn ja: nicht zusammenwerfen. Helper: scale_check() in fwu-design/scripts/helpers.py.
Schritt 5 — Quellen / Stand der Daten
Immer auf einer Folie (z.B. Footer oder eigene Quellenfolie) das Datenstand-Datum vermerken. Das ist im Original-Statusbericht zentral — siehe Folien 13–15 mit "Anm.: Märzzahlen enthalten nur Zahlen bis 18.3."
Schritt 6 — QA + Auslieferung
Vor Auslieferung:
python /mnt/skills/public/pptx/scripts/office/soffice.py --headless --convert-to pdf output.pptx
pdftoppm -jpeg -r 150 output.pdf slide
extract-text output.pptx | grep -iE "lorem|ipsum|TODO|\[insert"
Visuelle Inspektion mit Subagent oder direkt — siehe QA-Workflow im pptx-Skill.
Code-Beispiel: Vollständiger Reporting-zu-Deck-Pipeline
import subprocess
out = "Statusbericht_April_2026.pptx"
subprocess.run([
"python",
"/mnt/skills/user/fwu-design/scripts/new_deck.py",
out
], check=True)
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.chart.data import CategoryChartData
from pptx.enum.chart import XL_CHART_TYPE
prs = Presentation(out)
def set_ph(slide, idx, text):
for ph in slide.placeholders:
if ph.placeholder_format.idx == idx:
ph.text = text
return ph
return None
s = prs.slides.add_slide(prs.slide_layouts[0])
set_ph(s, 11, "Statusbericht")
set_ph(s, 12, "AIS / telli\nApril 2026")
s = prs.slides.add_slide(prs.slide_layouts[2])
set_ph(s, 20, "01")
set_ph(s, 12, "KPI Übersicht")
set_ph(s, 11, "Nutzendenzahlen, Kosten und Tagesnutzung")
s = prs.slides.add_slide(prs.slide_layouts[7])
set_ph(s, 0, "Headline-Zahlen telli")
set_ph(s, 20, "01")
set_ph(s, 21, "Statusbericht\nApril 2026")
ph = next(p for p in s.placeholders if p.placeholder_format.idx == 22)
tf = ph.text_frame
tf.text = "30.000+"
tf.paragraphs[0].runs[0].font.size = Pt(72)
p2 = tf.add_paragraph()
p2.text = "Nutzende pro Monat"
p2.runs[0].font.size = Pt(16)
s = prs.slides.add_slide(prs.slide_layouts[7])
set_ph(s, 0, "Entwicklung Nutzendenzahlen")
set_ph(s, 20, "02")
set_ph(s, 21, "Statusbericht\nApril 2026")
chart_data = CategoryChartData()
chart_data.categories = ["Aug", "Sep", "Okt", "Nov", "Dez", "Jan", "Feb", "Mär", "Apr"]
chart_data.add_series("Nutzende", (5000, 8000, 12000, 16000, 20000, 24000, 28000, 30000, 32000))
s.shapes.add_chart(
XL_CHART_TYPE.LINE,
Inches(2.4), Inches(2.0), Inches(10.5), Inches(4.8),
chart_data,
)
s = prs.slides.add_slide(prs.slide_layouts[7])
set_ph(s, 0, "Nutzung nach Schulart")
set_ph(s, 20, "03")
set_ph(s, 21, "Statusbericht\nApril 2026")
rows = [
("Gymnasium", 0), ("Grundschule", 0), ("Realschule", 0),
("Gesamtschule", 0), ("Berufsschule", 0), ("Hauptschule", 0),
("Förderschule", 0),
]
chart_data = CategoryChartData()
chart_data.categories = [r[0] for r in rows]
chart_data.add_series("Nutzende", [r[1] for r in rows])
s.shapes.add_chart(
XL_CHART_TYPE.BAR_CLUSTERED,
Inches(2.4), Inches(2.0), Inches(10.5), Inches(4.8),
chart_data,
)
s = prs.slides.add_slide(prs.slide_layouts[12])
set_ph(s, 12, "Vielen Dank")
set_ph(s, 13, "Fragen?")
prs.save(out)
Häufige Fragen / Edge Cases
Q: Der MCP-Server gibt einen Fehler zurück oder die Daten sind leer.
A: Zuerst prüfen, ob alle fünf Pflicht-Parameter gesetzt sind (startDate, endDate, interval, role, userState) — fehlt einer, schlägt die Schema-Validierung fehl. Bei leeren Ergebnissen trotz gültigem Aufruf: einen kürzeren, sicher-abgedeckten Zeitraum probieren (z.B. letzten abgeschlossenen Monat) und Filter-Parameter wie schoolType / selectedState weglassen, um zu sehen, woran es liegt. Wenn die Tools get_kpi_summary / get_reporting_snapshot gar nicht in der Tool-Liste auftauchen: Server ist nicht verbunden — User auf Claude-Desktop-Neustart hinweisen.
Q: Wie viel Granularität ist sinnvoll?
A: Default-mäßig 1 Folie pro Hauptkennzahl plus eine Übersichtsfolie. Bei mehr als 8 KPIs: in Kapitel gruppieren und mit Kapiteltrennern (Layout [2]) arbeiten.
Q: Soll ich für jede einzelne Detailzahl eine Folie machen?
A: Nein. Lieber wenige starke Folien mit großen Stat-Callouts und Diagrammen, als viele textlastige Folien. Im Original-Statusbericht hat jede telli-KPI genau eine Folie (Folien 13, 14, 15 für Nutzendenzahlen, Kosten, Tagesnutzung).
Q: Wie gehe ich mit Vergleichsperioden um (z.B. April vs. März)?
A: Beide Perioden mit get_reporting_snapshot einzeln holen und in einem Zweispalter (Layout [9]) oder als zwei Reihen im selben Diagramm darstellen.
Q: Wie bekomme ich die Aufschlüsselung „Nutzung nach Schulart"?
A: Der schoolType-Parameter ist ein Filter, kein Breakdown — pro Schulart muss ein eigener Aufruf abgesetzt werden. Ergebnisse in ein Dict sammeln und als horizontales, absteigend sortiertes Balkendiagramm auf einer eigenen Folie darstellen. Code-Pattern siehe Code-Beispiel oben und references/mcp_tools.md → Schulart-Filter. Immer zusätzlich den Gesamtwert ohne schoolType holen, um Anteile oder „nicht zugeordnet"-Reste sichtbar zu machen.
Q: Was, wenn der User nur die Daten will, kein Deck?
A: Dann nur Schritt 1+2 ausführen und die aufbereiteten Zahlen als Tabelle/Text im Chat zurückgeben. Kein Deck erzeugen, das nicht gewünscht ist.