commit 93bb19abf07480088586a4287420c4b5b79f6fb9 Author: marcus hinz Date: Sat Jun 20 14:40:33 2026 +0200 initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a60baf --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.bkp +.$* diff --git a/Anforderungskatalog_LIS_Fit-Gap v2.xlsx b/Anforderungskatalog_LIS_Fit-Gap v2.xlsx new file mode 100644 index 0000000..c0caca3 Binary files /dev/null and b/Anforderungskatalog_LIS_Fit-Gap v2.xlsx differ diff --git a/Folie10.pptx b/Folie10.pptx new file mode 100644 index 0000000..0d37167 Binary files /dev/null and b/Folie10.pptx differ diff --git a/Folie2.pptx b/Folie2.pptx new file mode 100644 index 0000000..00bd175 Binary files /dev/null and b/Folie2.pptx differ diff --git a/Folie3.pptx b/Folie3.pptx new file mode 100644 index 0000000..35afb03 Binary files /dev/null and b/Folie3.pptx differ diff --git a/Folie3b.pptx b/Folie3b.pptx new file mode 100644 index 0000000..8051a42 Binary files /dev/null and b/Folie3b.pptx differ diff --git a/Folie4.pptx b/Folie4.pptx new file mode 100644 index 0000000..f280531 Binary files /dev/null and b/Folie4.pptx differ diff --git a/Folie5.pptx b/Folie5.pptx new file mode 100644 index 0000000..87efe3c Binary files /dev/null and b/Folie5.pptx differ diff --git a/Folie6.pptx b/Folie6.pptx new file mode 100644 index 0000000..b25e8c1 Binary files /dev/null and b/Folie6.pptx differ diff --git a/Folie7.pptx b/Folie7.pptx new file mode 100644 index 0000000..a36ffd8 Binary files /dev/null and b/Folie7.pptx differ diff --git a/Folie8.pptx b/Folie8.pptx new file mode 100644 index 0000000..b907b44 Binary files /dev/null and b/Folie8.pptx differ diff --git a/Folie9.pptx b/Folie9.pptx new file mode 100644 index 0000000..7a46ce8 Binary files /dev/null and b/Folie9.pptx differ diff --git a/Vault_Impuls_Workshop.pptx b/Vault_Impuls_Workshop.pptx new file mode 100644 index 0000000..43d1cb8 Binary files /dev/null and b/Vault_Impuls_Workshop.pptx differ diff --git a/Workshop_LIS.pptx b/Workshop_LIS.pptx new file mode 100644 index 0000000..7342471 Binary files /dev/null and b/Workshop_LIS.pptx differ diff --git a/adesso_style.py b/adesso_style.py new file mode 100644 index 0000000..dbfe85f --- /dev/null +++ b/adesso_style.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +"""Gemeinsames adesso-Styling für die Workshop-Folien. +Teal->Blau-Verlauf, Fira Sans, weiße Shapes + Coral-Akzente, +Header (Pill + Titel) und schlanke Fußzeile (Seitenzahl + adesso).""" + +from pptx.util import Inches, Pt +from pptx.dml.color import RGBColor +from pptx.enum.text import PP_ALIGN, MSO_ANCHOR +from pptx.enum.shapes import MSO_SHAPE +from pptx.oxml.ns import qn + +# ---- adesso Palette -------------------------------------------------------- +WHITE = RGBColor(0xFF, 0xFF, 0xFF) +INK = RGBColor(0x0E, 0x2A, 0x47) # tiefes Navy – Text auf hellen Boxen / dunkle Füllungen +BLUE = RGBColor(0x00, 0x6E, 0xC7) +TEAL = RGBColor(0x28, 0xDC, 0xAA) +GREEN = RGBColor(0x76, 0xC8, 0x00) +CORAL = RGBColor(0xFF, 0x98, 0x68) +PINK = RGBColor(0xF5, 0x66, 0xBA) +TAUPE = RGBColor(0x88, 0x7D, 0x75) +LIGHT = RGBColor(0xD7, 0xE6, 0xF4) # helle, gedämpfte Schrift/Linien auf dem Verlauf +MUTE = RGBColor(0x6B, 0x77, 0x85) # mittleres Grau – Text auf weißen Flächen +RECF = RGBColor(0xE6, 0xEF, 0xF8) # helle Kästchen in weißen Boxen + +F = "Fira Sans" +FC = "Fira Sans Condensed" + + +def alpha(shape, opacity_pct): + """Deckkraft (0-100) auf die Solid-Füllung setzen.""" + srgb = shape._element.spPr.find(qn('a:solidFill')).find(qn('a:srgbClr')) + srgb.append(srgb.makeelement(qn('a:alpha'), {'val': str(int(opacity_pct * 1000))})) + + +def line_alpha(shape, opacity_pct): + ln = shape._element.spPr.find(qn('a:ln')) + srgb = ln.find(qn('a:solidFill')).find(qn('a:srgbClr')) + srgb.append(srgb.makeelement(qn('a:alpha'), {'val': str(int(opacity_pct * 1000))})) + + +def paint_background(s, width, height): + """Teal->Blau-Verlauf als Folienhintergrund.""" + bgr = s.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, width, height) + bgr.line.fill.background(); bgr.shadow.inherit = False + bgr.fill.gradient() + try: + bgr.fill.gradient_angle = 90.0 + except Exception: + pass + st = bgr.fill.gradient_stops + st[0].position = 0.0; st[0].color.rgb = TEAL + st[1].position = 1.0; st[1].color.rgb = BLUE + return bgr + + +def chrome(s, title, page): + """Header (Pill + Titel) + schlanke Fußzeile (Seitenzahl + adesso).""" + pill = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, + Inches(0.55), Inches(0.46), Inches(0.5), Inches(0.26)) + pill.fill.background(); pill.line.color.rgb = WHITE; pill.line.width = Pt(1.5) + pill.shadow.inherit = False + try: pill.adjustments[0] = 0.5 + except Exception: pass + + def _t(x, y, w, h, t, size, color, bold=False, align=PP_ALIGN.LEFT, font=FC): + tb = s.shapes.add_textbox(x, y, w, h); tf = tb.text_frame + tf.word_wrap = True; tf.margin_left = tf.margin_right = Pt(0) + tf.margin_top = tf.margin_bottom = Pt(0) + p = tf.paragraphs[0]; p.alignment = align + r = p.add_run(); r.text = t + r.font.size = Pt(size); r.font.bold = bold; r.font.name = font; r.font.color.rgb = color + + _t(Inches(1.25), Inches(0.34), Inches(11), Inches(0.7), title, 30, WHITE, bold=True) + _t(Inches(0.55), Inches(7.06), Inches(1.0), Inches(0.3), str(page), 11, LIGHT) + _t(Inches(10.95), Inches(6.99), Inches(1.85), Inches(0.4), "adesso", 17, WHITE, + bold=True, align=PP_ALIGN.RIGHT) diff --git a/aufwand.pptx b/aufwand.pptx new file mode 100644 index 0000000..043867a Binary files /dev/null and b/aufwand.pptx differ diff --git a/build_deck.py b/build_deck.py new file mode 100644 index 0000000..d9743d3 --- /dev/null +++ b/build_deck.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python3 +"""Workshop-Folien 'Vault – Technischer Impuls': ruhig, visuell, wenig Text.""" + +import os +from pptx import Presentation +from pptx.util import Inches, Pt +from pptx.dml.color import RGBColor +from pptx.enum.text import PP_ALIGN, MSO_ANCHOR +from pptx.enum.shapes import MSO_SHAPE + +# ---- Palette --------------------------------------------------------------- +INK = RGBColor(0x2A, 0x2F, 0x3A) +NAVY = RGBColor(0x1F, 0x3A, 0x5F) +ACCENT = RGBColor(0xC0, 0x49, 0x2F) +MUTE = RGBColor(0x8A, 0x93, 0xA0) +SOFT = RGBColor(0xE9, 0xEC, 0xEF) # heller Kasten +SOFT2 = RGBColor(0xDD, 0xE5, 0xEC) # heller Kasten blau +LINEC = RGBColor(0xC3, 0xC9, 0xD1) +BG = RGBColor(0xFF, 0xFF, 0xFF) +WHITE = RGBColor(0xFF, 0xFF, 0xFF) +F = "Calibri" + +prs = Presentation() +prs.slide_width = Inches(13.333) +prs.slide_height = Inches(7.5) +SW, SH = prs.slide_width, prs.slide_height +BLANK = prs.slide_layouts[6] +MX = Inches(0.95) + + +def slide(bg=BG): + s = prs.slides.add_slide(BLANK) + r = s.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, SW, SH) + r.fill.solid(); r.fill.fore_color.rgb = bg; r.line.fill.background() + r.shadow.inherit = False + s.shapes._spTree.remove(r._element); s.shapes._spTree.insert(2, r._element) + return s + + +def shape(s, kind, x, y, w, h, fill=None, line=None, lw=Pt(1)): + sh = s.shapes.add_shape(kind, x, y, w, h) + if fill is None: sh.fill.background() + else: sh.fill.solid(); sh.fill.fore_color.rgb = fill + if line is None: sh.line.fill.background() + else: sh.line.color.rgb = line; sh.line.width = lw + sh.shadow.inherit = False + return sh + + +def box(s, x, y, w, h, text="", fill=SOFT, tc=INK, size=15, bold=False, + rounded=True, line=None, sub=None, sub_c=None): + kind = MSO_SHAPE.ROUNDED_RECTANGLE if rounded else MSO_SHAPE.RECTANGLE + sh = shape(s, kind, x, y, w, h, fill=fill, line=line) + tf = sh.text_frame; tf.word_wrap = True + tf.vertical_anchor = MSO_ANCHOR.MIDDLE + tf.margin_top = tf.margin_bottom = Pt(2) + p = tf.paragraphs[0]; p.alignment = PP_ALIGN.CENTER + r = p.add_run(); r.text = text + r.font.size = Pt(size); r.font.bold = bold; r.font.name = F; r.font.color.rgb = tc + if sub: + p2 = tf.add_paragraph(); p2.alignment = PP_ALIGN.CENTER; p2.space_before = Pt(2) + r2 = p2.add_run(); r2.text = sub + r2.font.size = Pt(11); r2.font.name = F; r2.font.color.rgb = sub_c or MUTE + return sh + + +def text(s, x, y, w, h, runs, align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.TOP, + leading=1.1, space=0): + tb = s.shapes.add_textbox(x, y, w, h); tf = tb.text_frame + tf.word_wrap = True; tf.vertical_anchor = anchor + tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0) + p = tf.paragraphs[0]; p.alignment = align; p.line_spacing = leading; p.space_after = Pt(space) + for t, kw in runs: + r = p.add_run(); r.text = t + r.font.size = Pt(kw.get("size", 16)); r.font.bold = kw.get("bold", False) + r.font.italic = kw.get("italic", False); r.font.name = F + r.font.color.rgb = kw.get("color", INK) + return tb + + +def kicker(s, t): + tb = text(s, MX, Inches(0.62), Inches(11), Inches(0.4), + [(t.upper(), {"size": 12.5, "color": ACCENT, "bold": True})]) + tb.text_frame.paragraphs[0].runs[0].font._rPr.set('spc', '240') + + +def head(s, t): + text(s, MX, Inches(1.0), Inches(11.4), Inches(0.9), + [(t, {"size": 32, "color": NAVY, "bold": True})]) + shape(s, MSO_SHAPE.RECTANGLE, MX, Inches(1.78), Inches(0.6), Pt(4), fill=ACCENT) + + +def caption(s, t): + text(s, MX, Inches(6.62), Inches(11.4), Inches(0.6), + [(t, {"size": 16, "color": MUTE})], leading=1.15) + + +def vline(s, x, y, h, color=LINEC): + shape(s, MSO_SHAPE.RECTANGLE, x, y, Pt(1.6), h, fill=color) + + +# =========================================================================== +# TITEL +# =========================================================================== +s = slide(NAVY) +shape(s, MSO_SHAPE.RECTANGLE, 0, 0, Inches(0.24), SH, fill=ACCENT) +shape(s, MSO_SHAPE.RECTANGLE, MX, Inches(2.7), Inches(0.85), Pt(5), fill=ACCENT) +text(s, MX, Inches(1.7), Inches(10), Inches(0.5), + [("TECHNISCHER IMPULS", {"size": 14, "color": RGBColor(0xC6,0xD1,0xDE), "bold": True})] + ).text_frame.paragraphs[0].runs[0].font._rPr.set('spc', '280') +text(s, MX, Inches(3.05), Inches(11.4), Inches(2.0), + [("LIS – wohin", {"size": 50, "color": WHITE, "bold": True})]) +text(s, MX, Inches(4.05), Inches(11.4), Inches(2.0), + [("technisch denken?", {"size": 50, "color": WHITE, "bold": True})]) + +# =========================================================================== +# FOLIE 1 – Produkt oder Projekt (Balken-Bild) +# =========================================================================== +s = slide() +kicker(s, "Diagnose") +head(s, "Produkt oder Projekt?") + +base = Inches(5.45) # Grundlinie +bw, gap = Inches(0.52), Inches(0.16) + + +def bars(cx_start, heights, colors, label): + n = len(heights) + for i, (hh, col) in enumerate(zip(heights, colors)): + x = cx_start + i * (bw + gap) + h = Inches(hh) + shape(s, MSO_SHAPE.ROUNDED_RECTANGLE, x, base - h, bw, h, fill=col) + total = n * bw + (n - 1) * gap + # Grundlinie + shape(s, MSO_SHAPE.RECTANGLE, cx_start, base, total, Pt(2), fill=LINEC) + text(s, cx_start, base + Inches(0.18), total, Inches(0.4), + [(label, {"size": 18, "color": NAVY, "bold": True})], align=PP_ALIGN.CENTER) + + +bars(Inches(1.5), [0.45, 0.5, 2.7, 0.5, 0.45], + [MUTE, MUTE, ACCENT, MUTE, MUTE], "Projekt") +bars(Inches(8.0), [1.7, 2.0, 1.8, 2.1, 1.7], + [NAVY, NAVY, NAVY, NAVY, NAVY], "Produkt") + +# kleine neutrale Unterschriften +text(s, Inches(1.5), base + Inches(0.6), Inches(3.42), Inches(0.4), + [("tief in einer Dimension", {"size": 13, "color": MUTE})], align=PP_ALIGN.CENTER) +text(s, Inches(8.0), base + Inches(0.6), Inches(3.42), Inches(0.4), + [("breit über viele", {"size": 13, "color": MUTE})], align=PP_ALIGN.CENTER) + +# =========================================================================== +# FOLIE 2 – Eine Schicht dazwischen (Layer-Diagramm) +# =========================================================================== +s = slide() +kicker(s, "Architektur") +head(s, "Eine Schicht dazwischen") + +cxL, cxR = MX, Inches(12.38) +full_w = cxR - cxL + +# oben: Gruppe / Prozesse +top_y = Inches(2.2) +chips = ["Reporting", "DFÜ / Anlieferung", "Fusion"] +cw = Inches(3.2); cg = (full_w - 3 * cw) / 2 +for i, c in enumerate(chips): + box(s, cxL + i * (cw + cg), top_y, cw, Inches(0.72), c, + fill=BG, tc=NAVY, size=15, bold=True, line=LINEC) + +# Mitte: die Schicht (Hero) +mid_y = Inches(3.55) +box(s, cxL, mid_y, full_w, Inches(0.95), + "Integrations- & Abstraktionsschicht", + fill=NAVY, tc=WHITE, size=20, bold=True) + +# unten: Systeme +bot_y = Inches(5.15) +labs = [("Labor A", "LIS"), ("Labor B", "LIS"), ("Vault", "LIS"), ("Labor C", "eigenes LIS")] +lw = Inches(2.65); lg = (full_w - 4 * lw) / 3 +for i, (nm, sub) in enumerate(labs): + x = cxL + i * (lw + lg) + box(s, x, bot_y, lw, Inches(0.95), nm, fill=SOFT, tc=INK, size=16, bold=True, + sub=sub, sub_c=MUTE) + # Verbinder Schicht -> System + vline(s, x + lw / 2, mid_y + Inches(0.95), bot_y - (mid_y + Inches(0.95))) + +# Verbinder oben -> Schicht +for i in range(3): + cx = cxL + i * (cw + cg) + cw / 2 + vline(s, cx, top_y + Inches(0.72), mid_y - (top_y + Inches(0.72))) + +caption(s, "Labore docken an und behalten ihr LIS. Die Schicht verbindet.") + +# =========================================================================== +# FOLIE 3 – Zustand oder Ereignis (Event Sourcing, visuell) +# =========================================================================== +s = slide() +kicker(s, "Modell") +head(s, "Zustand oder Ereignis") + +cxL, cxR = MX, Inches(12.38) +full_w = cxR - cxL + +# --- obere Spur: Zustand --- +ay = Inches(2.35) +text(s, cxL, ay - Inches(0.05), Inches(3), Inches(0.4), + [("Zustand", {"size": 15, "color": MUTE, "bold": True})]) +zy = ay + Inches(0.42) +box(s, cxL, zy, Inches(2.4), Inches(0.95), "Probe", fill=SOFT, tc=INK, size=16, bold=True) +# Pfeil "überschreiben" +shape(s, MSO_SHAPE.RIGHT_ARROW, cxL + Inches(2.65), zy + Inches(0.27), + Inches(1.5), Inches(0.42), fill=LINEC) +text(s, cxL + Inches(2.55), zy - Inches(0.18), Inches(1.7), Inches(0.3), + [("überschreiben", {"size": 11, "color": MUTE, "italic": True})], align=PP_ALIGN.CENTER) +box(s, cxL + Inches(4.4), zy, Inches(2.7), Inches(0.95), "1 Abrechnung", + fill=BG, tc=NAVY, size=16, bold=True, line=LINEC) + +# Trennlinie +shape(s, MSO_SHAPE.RECTANGLE, cxL, Inches(4.25), full_w, Pt(1.2), fill=LINEC) + +# --- untere Spur: Ereignisse --- +by = Inches(4.55) +text(s, cxL, by - Inches(0.05), Inches(3), Inches(0.4), + [("Ereignisse", {"size": 15, "color": MUTE, "bold": True})]) +ey = by + Inches(0.42) +ev = ["Eingang", "Messung", "Abrechnung", "Abrechnung"] +ew = Inches(1.85); eg = Inches(0.32) +for i, e in enumerate(ev): + x = cxL + i * (ew + eg) + col = ACCENT if e == "Abrechnung" else NAVY + box(s, x, ey, ew, Inches(0.85), e, fill=col, tc=WHITE, size=14, bold=True) + if i < len(ev) - 1: + shape(s, MSO_SHAPE.CHEVRON, x + ew + Inches(0.02), ey + Inches(0.24), + Inches(0.28), Inches(0.38), fill=LINEC) +# Pfeil zum abgeleiteten Zustand +last_x = cxL + 4 * (ew + eg) +shape(s, MSO_SHAPE.RIGHT_ARROW, last_x - Inches(0.05), ey + Inches(0.22), + Inches(0.7), Inches(0.42), fill=LINEC) +box(s, last_x + Inches(0.75), ey - Inches(0.05), Inches(2.7), Inches(0.95), + "Zustand", fill=SOFT2, tc=NAVY, size=15, bold=True, + sub="abgeleitet", sub_c=MUTE) + +caption(s, "Der Zustand entsteht aus den Ereignissen – Mehrfaches inklusive.") + +out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Vault_Impuls_Workshop.pptx") +prs.save(out) +print("saved", out) diff --git a/folie10.py b/folie10.py new file mode 100644 index 0000000..53242d1 --- /dev/null +++ b/folie10.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +"""Folie 10 – Was bringt der Integrationsserver? Wichtige Funktionen (adesso-Stil, deutsch).""" + +import os +from pptx import Presentation +from pptx.util import Inches, Pt +from pptx.dml.color import RGBColor +from pptx.enum.text import PP_ALIGN, MSO_ANCHOR +from pptx.enum.shapes import MSO_SHAPE +from adesso_style import (WHITE, INK, BLUE, TEAL, GREEN, CORAL, LIGHT, MUTE, RECF, + F, FC, chrome) + +BG = RGBColor(0x0A, 0x3D, 0x6B) +SUBC = RGBColor(0x3C, 0x4A, 0x5A) + +prs = Presentation(); prs.slide_width = Inches(13.333); prs.slide_height = Inches(7.5) +SW, SH = prs.slide_width, prs.slide_height +s = prs.slides.add_slide(prs.slide_layouts[6]) + + +def shp(kind, x, y, w, h, fill=None, line=None, lw=Pt(1), adj=None): + sh = s.shapes.add_shape(kind, Inches(x), Inches(y), Inches(w), Inches(h)) + if fill is None: sh.fill.background() + else: sh.fill.solid(); sh.fill.fore_color.rgb = fill + if line is None: sh.line.fill.background() + else: sh.line.color.rgb = line; sh.line.width = lw + if adj is not None: + try: sh.adjustments[0] = adj + except Exception: pass + sh.shadow.inherit = False + return sh + + +def txt(x, y, w, h, t, size=12, color=WHITE, bold=False, italic=False, + align=PP_ALIGN.LEFT, font=F, anchor=MSO_ANCHOR.MIDDLE): + tb = s.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h)); tf = tb.text_frame + tf.word_wrap = True; tf.vertical_anchor = anchor + tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0) + p = tf.paragraphs[0]; p.alignment = align + r = p.add_run(); r.text = t + r.font.size = Pt(size); r.font.bold = bold; r.font.italic = italic + r.font.name = font; r.font.color.rgb = color + return tb + + +def card(x, y, w, h, title, body): + shp(MSO_SHAPE.ROUNDED_RECTANGLE, x, y, w, h, fill=WHITE, adj=0.06) + shp(MSO_SHAPE.RECTANGLE, x, y + 0.16, 0.12, h - 0.32, fill=CORAL) + txt(x + 0.32, y + 0.18, w - 0.48, 0.78, title, size=13.5, color=INK, bold=True, + anchor=MSO_ANCHOR.TOP) + txt(x + 0.32, y + 0.92, w - 0.52, h - 1.06, body, size=11, color=SUBC, + anchor=MSO_ANCHOR.TOP) + + +# ---- Hintergrund + Header -------------------------------------------------- +shp(MSO_SHAPE.RECTANGLE, 0, 0, SW.inches, SH.inches, fill=BG) +chrome(s, "Was bringt der Integrationsserver?", 11) + +shp(MSO_SHAPE.RECTANGLE, 0.45, 1.12, 0.16, 0.22, fill=CORAL) +txt(0.71, 1.05, 9.0, 0.34, "Wichtige Funktionen", size=15, color=WHITE, bold=True, + anchor=MSO_ANCHOR.MIDDLE) + +cards = [ + ("Zentrale Anbindung (Adapter)", + "Jedes System über einen Adapter — neues System = neuer Adapter, ohne Eingriff in den Bestand."), + ("Routing der Aufträge", + "Integriert mehrere LIS- und Abrechnungs-Instanzen — Aufträge werden gezielt an das passende LIS bzw. die richtige Abrechnung verteilt."), + ("Routing Engine", + "Verteilt Events regelbasiert an die richtigen Ziele — z. B. Proben-Routing."), + ("Event Store (append-only)", + "Kanonische Wahrheit, vollständige Historie, jederzeit Replay-fähig."), + ("Projektionen / Read-Models", + "Bedarfsgerechte Sichten: Auftragsstatus, Reporting, Leistungsverzeichnis …"), + ("Stammdaten / MDM", + "Single Point of Truth — Daten einmal pflegen, doppelte Stammdatenpflege entfällt."), + ("Monitoring & Überwachung", + "Status, Fehler-Queues und Alerting für alle Schnittstellen an einer Stelle."), + ("Audit-Trail, Replay & Archiv", + "Revisionssicher & manipulationssicher; Wiederaufsetzen per Replay; Langzeit-Archiv."), +] + +cw, ch = 2.92, 2.45 +cols = [0.45, 3.62, 6.79, 9.96] +rows = [1.58, 4.22] +for i, (title, body) in enumerate(cards): + card(cols[i % 4], rows[i // 4], cw, ch, title, body) + +out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Folie10.pptx") +prs.save(out) +print("saved", out) diff --git a/folie2.py b/folie2.py new file mode 100644 index 0000000..28f6e8f --- /dev/null +++ b/folie2.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +"""Folie 2 – Grenzen der Vault-Integration (adesso-Styling). +Kette: Auftrag -> Auftragsannahme -> Schattenbuchhaltung (SPOT) -> Vault -> +Zusammenführung.""" + +import os +from pptx import Presentation +from pptx.util import Inches, Pt +from pptx.dml.color import RGBColor +from pptx.enum.text import PP_ALIGN, MSO_ANCHOR +from pptx.enum.shapes import MSO_SHAPE +from adesso_style import (WHITE, INK, CORAL, LIGHT, RECF, F, FC, + alpha, line_alpha, paint_background, chrome) + +prs = Presentation() +prs.slide_width = Inches(13.333) +prs.slide_height = Inches(7.5) +s = prs.slides.add_slide(prs.slide_layouts[6]) + + +def shp(kind, x, y, w, h, fill=None, line=None, lw=Pt(1), rot=0): + sh = s.shapes.add_shape(kind, x, y, w, h) + if fill is None: sh.fill.background() + else: sh.fill.solid(); sh.fill.fore_color.rgb = fill + if line is None: sh.line.fill.background() + else: sh.line.color.rgb = line; sh.line.width = lw + if rot: sh.rotation = rot + sh.shadow.inherit = False + return sh + + +def boxtext(sh, t, size=14, color=INK, bold=False, font=F): + tf = sh.text_frame; tf.word_wrap = True + tf.vertical_anchor = MSO_ANCHOR.MIDDLE + tf.margin_left = tf.margin_right = Pt(4); tf.margin_top = tf.margin_bottom = Pt(1) + p = tf.paragraphs[0]; p.alignment = PP_ALIGN.CENTER + r = p.add_run(); r.text = t + r.font.size = Pt(size); r.font.bold = bold; r.font.name = font; r.font.color.rgb = color + + +def txt(x, y, w, h, t, size=14, color=INK, bold=False, italic=False, + align=PP_ALIGN.LEFT, spc=None, lead=1.1, anchor=MSO_ANCHOR.TOP, font=F): + tb = s.shapes.add_textbox(x, y, w, h); tf = tb.text_frame + tf.word_wrap = True; tf.vertical_anchor = anchor + tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0) + p = tf.paragraphs[0]; p.alignment = align; p.line_spacing = lead + r = p.add_run(); r.text = t + r.font.size = Pt(size); r.font.bold = bold; r.font.italic = italic + r.font.name = font; r.font.color.rgb = color + if spc: r.font._rPr.set('spc', str(spc)) + return tb + + +paint_background(s, prs.slide_width, prs.slide_height) +chrome(s, "Grenzen der Vault-Integration?", 2) + +# ---- Linke Spalte ---------------------------------------------------------- +def claim(y, t, bold=False, sub=None): + shp(MSO_SHAPE.RECTANGLE, Inches(0.95), y + Inches(0.08), Inches(0.14), Inches(0.14), fill=CORAL) + txt(Inches(1.28), y, Inches(4.5), Inches(0.4), t, size=16.5, color=WHITE, bold=bold) + if sub: + txt(Inches(1.28), y + Inches(0.34), Inches(4.5), Inches(0.32), sub, + size=12.5, color=LIGHT, italic=True) + + +claim(Inches(2.2), "Middleware kapselt den Rand – den Kern nicht", bold=True) +claim(Inches(2.95), "Mischaufträge nur als Schattenmodell", + sub="aufwändig · fragil · fehleranfällig") +claim(Inches(3.95), "die Fachwahrheit verlässt den Kern") + +shp(MSO_SHAPE.RECTANGLE, Inches(0.95), Inches(4.95), Inches(0.5), Pt(3.5), fill=CORAL) +txt(Inches(0.95), Inches(5.12), Inches(4.85), Inches(1.1), + "Die Schattenbuchhaltung wird zum Single Point of Truth – nicht Vault.", + size=19, color=WHITE, bold=True, lead=1.08, font=FC) +txt(Inches(0.95), Inches(6.45), Inches(4.85), Inches(0.4), + "Genau die Rolle, die Vault halten müsste.", size=13.5, color=LIGHT, italic=True) + +# =========================================================================== +# RECHTS: Kette Auftrag -> Auftragsannahme -> SPOT -> Vault -> Zusammenführung +# =========================================================================== +fx, fy, fw, fh = Inches(6.25), Inches(2.1), Inches(5.9), Inches(4.72) +panel = shp(MSO_SHAPE.ROUNDED_RECTANGLE, fx, fy, fw, fh, fill=WHITE) +alpha(panel, 10) +panel.line.color.rgb = WHITE; panel.line.width = Pt(1) +line_alpha(panel, 35) +txt(fx + Inches(0.3), fy + Inches(0.12), fw - Inches(0.6), Inches(0.3), + "Auftragsweg – hin und zurück durch den Layer", size=12.5, color=WHITE, bold=True) + +cxc = fx + Inches(2.05) +bw = Inches(2.7) +bx = cxc - bw / 2 + + +def down(y, label=None): + shp(MSO_SHAPE.DOWN_ARROW, cxc - Inches(0.11), y, Inches(0.22), Inches(0.22), fill=LIGHT) + if label: + txt(cxc + Inches(0.22), y - Inches(0.02), Inches(1.7), Inches(0.3), label, + size=11, color=LIGHT, italic=True, anchor=MSO_ANCHOR.MIDDLE) + + +# 1) Auftrag +b = shp(MSO_SHAPE.ROUNDED_RECTANGLE, bx, fy + Inches(0.44), bw, Inches(0.40), fill=WHITE) +boxtext(b, "Auftrag · Mischauftrag", size=12.5, color=INK, bold=True) +down(fy + Inches(0.86)) + +# 2) Auftragsannahme (ersetzt altes LIS) +b = shp(MSO_SHAPE.ROUNDED_RECTANGLE, bx, fy + Inches(1.06), bw, Inches(0.40), fill=WHITE) +boxtext(b, "Auftragsannahme", size=13, color=INK, bold=True) +down(fy + Inches(1.48), "Magic") + +# 3) Schattenbuchhaltung = Rahmen um den Vault-Kasten +frx, fry = bx - Inches(0.22), fy + Inches(1.72) +frw, frh = bw + Inches(0.50), Inches(2.22) +frame = shp(MSO_SHAPE.ROUNDED_RECTANGLE, frx, fry, frw, frh, + fill=RGBColor(0xFF, 0xE3, 0x9B)) +alpha(frame, 22) +frame.line.color.rgb = CORAL; frame.line.width = Pt(2) + +# Überschrift + Positions-Mapping (oben im Rahmen) +txt(frx, fy + Inches(1.78), frw, Inches(0.28), "Schattenbuchhaltung", + size=13, color=CORAL, bold=True, align=PP_ALIGN.CENTER) +txt(frx, fy + Inches(2.07), frw, Inches(0.24), "Positions-Mapping", + size=11.5, color=WHITE, align=PP_ALIGN.CENTER) + +# aktiver SPOT-Stern (rechts neben dem Rahmen) +shp(MSO_SHAPE.STAR_5_POINT, Inches(10.75), fy + Inches(1.80), Inches(0.5), Inches(0.5), fill=CORAL) +txt(Inches(9.95), fy + Inches(2.32), Inches(2.1), Inches(0.28), "Single Point of Truth", + size=10.5, color=CORAL, bold=True, align=PP_ALIGN.CENTER) +down(fy + Inches(2.33)) + +# 4) Vault speichert (aufgesplittet) – im Rahmen +b = shp(MSO_SHAPE.ROUNDED_RECTANGLE, bx, fy + Inches(2.50), bw, Inches(0.70), fill=WHITE) +txt(bx, fy + Inches(2.55), bw, Inches(0.3), "Vault · 1:1 · Speicher", + size=13, color=INK, bold=True, align=PP_ALIGN.CENTER) +for k in range(3): + shp(MSO_SHAPE.ROUNDED_RECTANGLE, cxc - Inches(1.08) + k * Inches(0.78), + fy + Inches(2.87), Inches(0.62), Inches(0.3), fill=RECF) + txt(cxc - Inches(1.08) + k * Inches(0.78), fy + Inches(2.87), Inches(0.62), Inches(0.3), + "Rec", size=9.5, color=INK, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE) +# Geist-Stern (durchgestrichen) – die Wahrheit müsste hier liegen +gx = Inches(10.79) +gs = shp(MSO_SHAPE.STAR_5_POINT, gx, fy + Inches(2.55), Inches(0.42), Inches(0.42)) +gs.fill.background(); gs.line.color.rgb = LIGHT; gs.line.width = Pt(1.25) +shp(MSO_SHAPE.RECTANGLE, gx - Inches(0.02), fy + Inches(2.73), Inches(0.46), Pt(2), + fill=CORAL, rot=20) +txt(Inches(9.95), fy + Inches(3.02), Inches(2.1), Inches(0.3), "müsste hier liegen", + size=10, color=LIGHT, align=PP_ALIGN.CENTER) +down(fy + Inches(3.22)) + +# 5) Daten zusammenführen (unten im Rahmen) +txt(frx, fy + Inches(3.42), frw, Inches(0.28), "Daten Zusammenführen", + size=12.5, color=WHITE, bold=True, align=PP_ALIGN.CENTER) + +# 6) Weiterverarbeitung – grauer Pfeil + weißer Kasten unter dem Rahmen +shp(MSO_SHAPE.DOWN_ARROW, cxc - Inches(0.11), fy + Inches(4.00), Inches(0.22), Inches(0.22), + fill=RGBColor(0x8C, 0x8C, 0x8C)) +b = shp(MSO_SHAPE.ROUNDED_RECTANGLE, bx, fy + Inches(4.26), bw, Inches(0.40), fill=WHITE) +boxtext(b, "Weiterverarbeitung", size=12.5, color=INK, bold=True) + +# Rückweg (Lesen) – Pfeil links nach oben, Label vertikal daneben +ua = shp(MSO_SHAPE.UP_ARROW, fx + Inches(0.08), fy + Inches(1.55), Inches(0.26), Inches(2.30), + fill=LIGHT) +alpha(ua, 55) +lt = s.shapes.add_textbox(fx - Inches(0.95), fy + Inches(2.40), Inches(2.2), Inches(0.3)) +ltf = lt.text_frame; ltf.word_wrap = False +ltf.margin_left = ltf.margin_right = ltf.margin_top = ltf.margin_bottom = Pt(0) +lp = ltf.paragraphs[0]; lp.alignment = PP_ALIGN.CENTER +lr = lp.add_run(); lr.text = "Lesen: zurück durch den Layer" +lr.font.size = Pt(9.5); lr.font.name = F; lr.font.color.rgb = CORAL +lt.rotation = 270 + +out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Folie2.pptx") +prs.save(out) +print("saved", out) diff --git a/folie3.py b/folie3.py new file mode 100644 index 0000000..829bc44 --- /dev/null +++ b/folie3.py @@ -0,0 +1,283 @@ +#!/usr/bin/env python3 +"""Folie 3 – LIS mit Umsystemen (Adapter, links/rechts) und andockenden Modulen +(unten), unter einer Steuerungs-/Governance-Schicht; weitere LIS angedeutet.""" + +import os +from pptx import Presentation +from pptx.util import Inches, Pt +from pptx.dml.color import RGBColor +from pptx.enum.text import PP_ALIGN, MSO_ANCHOR +from pptx.enum.shapes import MSO_SHAPE +from adesso_style import (WHITE, INK, BLUE, TEAL, GREEN, CORAL, LIGHT, MUTE, RECF, + F, FC, paint_background, chrome) + +# Rollen-Mapping auf die adesso-Palette +NAVY = INK # dunkle Strukturelemente (Bar, LIS-Kern) +ACCENT = CORAL # Adapter-Linien +AMBER = GREEN # zweites Modul +LINEC = LIGHT # Verbinder / feine Linien +HINT = RGBColor(0xE6, 0xEF, 0xF8) # helle "weitere LIS"-Karten +HINTL = RGBColor(0xC4, 0xD6, 0xE8) +TINT_T = WHITE # Modul-Boxen weiß +TINT_A = WHITE +UMS = WHITE # Umsystem-Boxen weiß +BG = WHITE + +prs = Presentation() +prs.slide_width = Inches(13.333) +prs.slide_height = Inches(7.5) +s = prs.slides.add_slide(prs.slide_layouts[6]) +paint_background(s, prs.slide_width, prs.slide_height) + + +def shp(kind, x, y, w, h, fill=None, line=None, lw=Pt(1), rot=0): + sh = s.shapes.add_shape(kind, x, y, w, h) + if fill is None: sh.fill.background() + else: sh.fill.solid(); sh.fill.fore_color.rgb = fill + if line is None: sh.line.fill.background() + else: sh.line.color.rgb = line; sh.line.width = lw + if rot: sh.rotation = rot + sh.shadow.inherit = False + return sh + + +def boxtext(sh, t, size=14, color=INK, bold=False): + tf = sh.text_frame; tf.word_wrap = True + tf.vertical_anchor = MSO_ANCHOR.MIDDLE + tf.margin_left = tf.margin_right = Pt(3); tf.margin_top = tf.margin_bottom = Pt(1) + p = tf.paragraphs[0]; p.alignment = PP_ALIGN.CENTER + r = p.add_run(); r.text = t + r.font.size = Pt(size); r.font.bold = bold; r.font.name = F; r.font.color.rgb = color + + +def txt(x, y, w, h, t, size=14, color=INK, bold=False, italic=False, + align=PP_ALIGN.LEFT, spc=None): + tb = s.shapes.add_textbox(x, y, w, h); tf = tb.text_frame + tf.word_wrap = True; tf.vertical_anchor = MSO_ANCHOR.MIDDLE + tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0) + p = tf.paragraphs[0]; p.alignment = align + r = p.add_run(); r.text = t + r.font.size = Pt(size); r.font.bold = bold; r.font.italic = italic + r.font.name = F; r.font.color.rgb = color + if spc: r.font._rPr.set('spc', str(spc)) + return tb + + +def hline(x, y, w, color=LINEC, weight=Pt(1.4)): + shp(MSO_SHAPE.RECTANGLE, x, y - weight / 2, w, weight, fill=color) + + +def vline(x, y, h, color=LINEC, weight=Pt(1.4)): + shp(MSO_SHAPE.RECTANGLE, x - weight / 2, y, weight, h, fill=color) + + +# ---- Kopf ------------------------------------------------------------------ +chrome(s, "Offen – und gesteuert", 3) + +x0, x1 = Inches(0.95), Inches(12.38) + + +# =========================================================================== +# GOVERNANCE – Icon-Karten + Strich +# =========================================================================== +def card(cx, label): + cw_, ch_ = Inches(2.05), Inches(1.25) + x, y = cx - cw_ / 2, Inches(1.5) + shp(MSO_SHAPE.ROUNDED_RECTANGLE, x, y, cw_, ch_, fill=BG, line=LINEC, lw=Pt(1.25)) + txt(x, y + ch_ - Inches(0.4), cw_, Inches(0.34), label, + size=14, color=NAVY, bold=True, align=PP_ALIGN.CENTER) + return cx, y + Inches(0.42) + + +def regler(cx, cy): + tw = Inches(0.62) + left = cx - tw / 2 + for j, frac in enumerate([0.30, 0.68, 0.48]): + ty = cy - Inches(0.18) + j * Inches(0.18) + shp(MSO_SHAPE.ROUNDED_RECTANGLE, left, ty - Inches(0.022), tw, Inches(0.045), fill=LINEC) + kx = left + Inches(0.62 * frac) + shp(MSO_SHAPE.OVAL, kx - Inches(0.07), ty - Inches(0.07), + Inches(0.14), Inches(0.14), fill=NAVY) + + +cx1, cy1 = card(Inches(4.75), "Steuerung") +cx2, cy2 = card(Inches(6.95), "Reporting") +cx3, cy3 = card(Inches(9.15), "Governance") + +regler(cx1, cy1) + +# Reporting – Zahlenreihen +nb = s.shapes.add_textbox(cx2 - Inches(0.85), cy2 - Inches(0.36), Inches(1.7), Inches(0.72)) +tf = nb.text_frame; tf.word_wrap = False; tf.vertical_anchor = MSO_ANCHOR.MIDDLE +tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0) +for i, row in enumerate(["3 8 1 4 7", "6 0 9 2 5", "5 7 2 8 1"]): + p = tf.paragraphs[0] if i == 0 else tf.add_paragraph() + p.alignment = PP_ALIGN.CENTER; p.line_spacing = 0.95 + r = p.add_run(); r.text = row + r.font.size = Pt(11); r.font.name = "Consolas"; r.font.color.rgb = MUTE + +# Governance – § +gtb = s.shapes.add_textbox(cx3 - Inches(0.6), cy3 - Inches(0.42), Inches(1.2), Inches(0.8)) +gtf = gtb.text_frame; gtf.vertical_anchor = MSO_ANCHOR.MIDDLE +gp = gtf.paragraphs[0]; gp.alignment = PP_ALIGN.CENTER +gr = gp.add_run(); gr.text = "§"; gr.font.size = Pt(34); gr.font.bold = True +gr.font.name = F; gr.font.color.rgb = NAVY + +gov_y = Inches(3.05) +shp(MSO_SHAPE.ROUNDED_RECTANGLE, x0, gov_y - Inches(0.07), x1 - x0, Inches(0.14), fill=NAVY) +for cc in (cx1, cx2, cx3): + vline(cc, Inches(2.75), gov_y - Inches(0.07) - Inches(2.75), color=LINEC, weight=Pt(1)) + + +# =========================================================================== +# LIS + Umsysteme (Adapter links/rechts) + Module (andocken, unten) +# =========================================================================== +core_x, core_w = Inches(3.7), Inches(2.3) +core_y, core_h = Inches(4.3), Inches(1.2) +core_cx = core_x + core_w / 2 +core_by = core_y + core_h + +# Adapter-Schicht: echte Schicht als Hülle um den Kern (Trennung) +lay_pad_x, lay_pad_t = Inches(0.24), Inches(0.30) +lay_x = core_x - lay_pad_x +lay_w = core_w + lay_pad_x * 2 +lay_y = core_y - lay_pad_t +lay_h = core_h + lay_pad_t +lay_right = lay_x + lay_w + + +def adapter(xc, yc): + shp(MSO_SHAPE.ROUNDED_RECTANGLE, xc - Inches(0.10), yc - Inches(0.11), + Inches(0.16), Inches(0.22), fill=ACCENT) + shp(MSO_SHAPE.RECTANGLE, xc + Inches(0.06), yc - Inches(0.055), Inches(0.1), Pt(2.4), fill=ACCENT) + shp(MSO_SHAPE.RECTANGLE, xc + Inches(0.06), yc + Inches(0.03), Inches(0.1), Pt(2.4), fill=ACCENT) + + +def dock(xc, yc): + shp(MSO_SHAPE.ROUNDED_RECTANGLE, xc - Inches(0.1), yc - Inches(0.1), + Inches(0.2), Inches(0.2), fill=NAVY) + shp(MSO_SHAPE.OVAL, xc - Inches(0.055), yc - Inches(0.055), Inches(0.11), Inches(0.11), + fill=ACCENT) + + +def icon_truck(ix, iy): # Anlieferung + shp(MSO_SHAPE.ROUNDED_RECTANGLE, ix - Inches(0.20), iy - Inches(0.10), + Inches(0.24), Inches(0.16), fill=NAVY) + shp(MSO_SHAPE.ROUNDED_RECTANGLE, ix + Inches(0.05), iy - Inches(0.035), + Inches(0.12), Inches(0.095), fill=NAVY) + for wx in (ix - Inches(0.11), ix + Inches(0.08)): + shp(MSO_SHAPE.OVAL, wx - Inches(0.038), iy + Inches(0.05), + Inches(0.076), Inches(0.076), fill=NAVY) + + +def icon_device(ix, iy): # Gerät + shp(MSO_SHAPE.ROUNDED_RECTANGLE, ix - Inches(0.16), iy - Inches(0.14), + Inches(0.32), Inches(0.28), fill=NAVY) + shp(MSO_SHAPE.RECTANGLE, ix - Inches(0.11), iy - Inches(0.10), + Inches(0.22), Inches(0.085), fill=RGBColor(0x34, 0x4E, 0x70)) + shp(MSO_SHAPE.OVAL, ix - Inches(0.085), iy + Inches(0.03), Inches(0.055), Inches(0.055), fill=TEAL) + shp(MSO_SHAPE.OVAL, ix + Inches(0.03), iy + Inches(0.03), Inches(0.055), Inches(0.055), fill=AMBER) + + +def icon_dfu(ix, iy): # DFÜ + shp(MSO_SHAPE.RIGHT_ARROW, ix - Inches(0.16), iy - Inches(0.12), Inches(0.32), Inches(0.095), + fill=NAVY) + shp(MSO_SHAPE.LEFT_ARROW, ix - Inches(0.16), iy + Inches(0.025), Inches(0.32), Inches(0.095), + fill=ACCENT) + + +g = Inches(0.10) # Abstand Adapter-Linie <-> LIS +th = Inches(0.08) # Dicke der roten Adapter-Linie + +# Governance -> obere Adapter-Linie am Kern +vline(core_cx, gov_y + Inches(0.07), (core_y - g - th) - (gov_y + Inches(0.07)), + color=LINEC, weight=Pt(1)) + +uw, uh = Inches(1.55), Inches(0.6) + + +def ums_box(bx, yc, name, icon_fn): + shp(MSO_SHAPE.ROUNDED_RECTANGLE, bx, yc - uh / 2, uw, uh, fill=UMS, line=LINEC) + icon_fn(bx + Inches(0.32), yc) + txt(bx + Inches(0.58), yc - Inches(0.16), uw - Inches(0.64), Inches(0.32), name, + size=12, color=INK, align=PP_ALIGN.LEFT) + + +# Umsysteme links -> Adapter-Schichten am linken Kernrand +lx = Inches(0.8) +ums_box(lx, Inches(4.6), "Anlieferung", icon_truck) +ums_box(lx, Inches(5.2), "DFÜ", icon_dfu) +for yc in (Inches(4.6), Inches(5.2)): + hline(lx + uw, yc, (core_x - g - th) - (lx + uw)) + +# Umsystem rechts -> Adapter-Linie am rechten Kernrand +gx = Inches(6.65) +ums_box(gx, Inches(4.9), "Geräte", icon_device) +hline(core_x + core_w + g + th, Inches(4.9), gx - (core_x + core_w + g + th)) + +# ---- LIS-Kern ------------------------------------------------------------- +shp(MSO_SHAPE.ROUNDED_RECTANGLE, core_x, core_y, core_w, core_h, fill=NAVY) +sh = shp(MSO_SHAPE.ROUNDED_RECTANGLE, core_x, core_y, core_w, core_h, fill=NAVY) +boxtext(sh, "LIS", size=22, color=WHITE, bold=True) + + +# ---- 5 Adapter-Linien – je Schnittstelle eine, mit Abstand zum Kern ------- +def rbar_v(outer_x, yc, h=Inches(0.55)): # vertikal, linker/rechter Rand + shp(MSO_SHAPE.RECTANGLE, outer_x, yc - h / 2, th, h, fill=ACCENT) + + +def rbar_h(xc, outer_y, w): # horizontal, oben/unten + shp(MSO_SHAPE.RECTANGLE, xc - w / 2, outer_y, w, th, fill=ACCENT) + + +rbar_v(core_x - g - th, Inches(4.6)) # 1) Anlieferung +rbar_v(core_x - g - th, Inches(5.2)) # 2) DFÜ +rbar_h(core_cx, core_y - g - th, Inches(1.15)) # 3) nach oben (Steuerung) +rbar_v(core_x + core_w + g, Inches(4.9)) # 4) Geräte +rbar_h(core_cx, core_by + g, core_w - Inches(0.2)) # 5) Fachliche Verarbeitung + +# Fachliche Verarbeitung (unten): Module an der unteren Adapter-Linie +mw, mh = Inches(1.7), Inches(0.6) +my = Inches(6.05) +for nm, fill, tc, mcx in [("Hochdurchsatz", TINT_T, TEAL, Inches(3.85)), + ("Spezial", TINT_A, AMBER, Inches(5.85))]: + vline(mcx, core_by + g + th, my - (core_by + g + th), color=LINEC, weight=Pt(1)) + sh = shp(MSO_SHAPE.ROUNDED_RECTANGLE, mcx - mw / 2, my, mw, mh, fill=fill, line=tc, lw=Pt(1.5)) + boxtext(sh, nm, size=12.5, color=INK, bold=True) + +txt(Inches(2.9), my + mh + Inches(0.12), Inches(4.0), Inches(0.3), "Fachliche Verarbeitung", + size=12, color=LIGHT, bold=True, align=PP_ALIGN.CENTER) + + +# =========================================================================== +# WEITERE LIS – überlappend, nur angedeutet +# =========================================================================== +hw_, hh_ = Inches(1.8), Inches(2.0) + +def mini_lis(hx, hy, faded=False): + fill = RGBColor(0xF4, 0xF6, 0xF8) if faded else HINT + shp(MSO_SHAPE.ROUNDED_RECTANGLE, hx, hy, hw_, hh_, fill=fill, line=HINTL, lw=Pt(1.25)) + hdc = shp(MSO_SHAPE.ROUNDED_RECTANGLE, hx, hy, hw_, Inches(0.46), + fill=HINTL if not faded else RGBColor(0xDF, 0xE4, 0xEA)) + boxtext(hdc, "LIS", size=14, color=NAVY, bold=True) + for k in range(3): + yy = hy + Inches(0.66) + k * Inches(0.4) + shp(MSO_SHAPE.ROUNDED_RECTANGLE, hx + Inches(0.2), yy, hw_ - Inches(0.4), + Inches(0.28), fill=WHITE, line=HINTL, lw=Pt(0.75)) + +mini_lis(Inches(10.5), Inches(4.35), faded=True) +mini_lis(Inches(10.0), Inches(4.75), faded=False) +vline(Inches(10.0) + hw_ / 2, gov_y + Inches(0.07), Inches(4.75) - (gov_y + Inches(0.07)), + color=LINEC, weight=Pt(1)) +txt(Inches(9.9), Inches(4.35) + hh_ + Inches(0.2), Inches(2.4), Inches(0.35), "weitere LIS", + size=13, color=LIGHT, align=PP_ALIGN.CENTER) + +# ---- Mini-Legende ---------------------------------------------------------- +ly = Inches(7.05) +shp(MSO_SHAPE.RECTANGLE, Inches(7.55), ly - Inches(0.11), th, Inches(0.22), fill=ACCENT) +txt(Inches(7.8), ly - Inches(0.16), Inches(3.2), Inches(0.32), + "Adapter-Schicht – je Schnittstelle eine", size=11.5, color=LIGHT) + +out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Folie3.pptx") +prs.save(out) +print("saved", out) diff --git a/folie3b.py b/folie3b.py new file mode 100644 index 0000000..2ba128c --- /dev/null +++ b/folie3b.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +"""Folie 4 (eingehängt nach Folie 3) – Architektur der Modularisierung: +Modularer Monolith vs. Microservices, anschaulich im Containerumfeld. +Links: 1 Container, Module mit In-Process-Calls, 1 geteilte DB. +Rechts: N Container, Services über Netzwerk, DB je Service. +Unten: kompakte Gegenüberstellung der wesentlichen Unterschiede.""" + +import os +from pptx import Presentation +from pptx.util import Inches, Pt +from pptx.dml.color import RGBColor +from pptx.enum.text import PP_ALIGN, MSO_ANCHOR +from pptx.enum.shapes import MSO_SHAPE +from adesso_style import (WHITE, INK, BLUE, TEAL, GREEN, CORAL, LIGHT, MUTE, RECF, + F, FC, paint_background, chrome) + +NAVY = INK +ACCENT = CORAL +LINEC = LIGHT + +prs = Presentation() +prs.slide_width = Inches(13.333) +prs.slide_height = Inches(7.5) +s = prs.slides.add_slide(prs.slide_layouts[6]) +paint_background(s, prs.slide_width, prs.slide_height) + + +def shp(kind, x, y, w, h, fill=None, line=None, lw=Pt(1), rot=0): + sh = s.shapes.add_shape(kind, x, y, w, h) + if fill is None: sh.fill.background() + else: sh.fill.solid(); sh.fill.fore_color.rgb = fill + if line is None: sh.line.fill.background() + else: sh.line.color.rgb = line; sh.line.width = lw + if rot: sh.rotation = rot + sh.shadow.inherit = False + return sh + + +def boxtext(sh, t, size=14, color=INK, bold=False): + tf = sh.text_frame; tf.word_wrap = True + tf.vertical_anchor = MSO_ANCHOR.MIDDLE + tf.margin_left = tf.margin_right = Pt(3); tf.margin_top = tf.margin_bottom = Pt(1) + p = tf.paragraphs[0]; p.alignment = PP_ALIGN.CENTER + r = p.add_run(); r.text = t + r.font.size = Pt(size); r.font.bold = bold; r.font.name = F; r.font.color.rgb = color + + +def txt(x, y, w, h, t, size=14, color=INK, bold=False, italic=False, + align=PP_ALIGN.LEFT, spc=None): + tb = s.shapes.add_textbox(x, y, w, h); tf = tb.text_frame + tf.word_wrap = True; tf.vertical_anchor = MSO_ANCHOR.MIDDLE + tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0) + p = tf.paragraphs[0]; p.alignment = align + r = p.add_run(); r.text = t + r.font.size = Pt(size); r.font.bold = bold; r.font.italic = italic + r.font.name = F; r.font.color.rgb = color + if spc: r.font._rPr.set('spc', str(spc)) + return tb + + +def vline(x, y, h, color=LINEC, weight=Pt(1.4)): + shp(MSO_SHAPE.RECTANGLE, x - weight / 2, y, weight, h, fill=color) + + +def hline(x, y, w, color=LINEC, weight=Pt(1.4)): + shp(MSO_SHAPE.RECTANGLE, x, y - weight / 2, w, weight, fill=color) + + +# ---- Kopf + Kernaussage ---------------------------------------------------- +chrome(s, "Architektur der Modularisierung", 4) +txt(Inches(1.25), Inches(0.98), Inches(11.6), Inches(0.34), + "Gleiche fachlichen Schnitte – der Unterschied ist die Laufzeit- und Deployment-Grenze.", + size=14, color=LIGHT, italic=True) + +DOT = [TEAL, GREEN, BLUE] # gleiche Module/Services beidseitig farblich verankert +LET = ["A", "B", "C"] + +# =========================================================================== +# Spalten-Titel +# =========================================================================== +cL = Inches(3.6) # Mittelpunkt linke Spalte +cR = Inches(9.75) # Mittelpunkt rechte Spalte + +txt(Inches(0.95), Inches(1.5), Inches(5.3), Inches(0.32), "Modularer Monolith", + size=18, color=WHITE, bold=True, align=PP_ALIGN.CENTER) +txt(Inches(0.95), Inches(1.84), Inches(5.3), Inches(0.26), "1 Deployable · 1 Prozess", + size=11.5, color=LIGHT, align=PP_ALIGN.CENTER) + +txt(Inches(7.0), Inches(1.5), Inches(5.5), Inches(0.32), "Microservices", + size=18, color=WHITE, bold=True, align=PP_ALIGN.CENTER) +txt(Inches(7.0), Inches(1.84), Inches(5.5), Inches(0.26), "N Deployables · N Prozesse", + size=11.5, color=LIGHT, align=PP_ALIGN.CENTER) + +# =========================================================================== +# LINKS – Modularer Monolith: 1 Container, Module, geteilte DB +# =========================================================================== +cx_x, cx_w, cx_y, cx_h = Inches(0.95), Inches(5.3), Inches(2.2), Inches(1.6) +shp(MSO_SHAPE.ROUNDED_RECTANGLE, cx_x, cx_y, cx_w, cx_h, line=WHITE, lw=Pt(1.5)) +txt(cx_x + Inches(0.15), cx_y + Inches(0.05), Inches(2.0), Inches(0.26), "1 Container", + size=11, color=LIGHT, bold=True) + +mw, mh, my = Inches(1.35), Inches(0.8), Inches(2.62) +mcx = [Inches(2.05), Inches(3.6), Inches(5.15)] +for i, mx in enumerate(mcx): + sh = shp(MSO_SHAPE.ROUNDED_RECTANGLE, mx - mw / 2, my, mw, mh, fill=WHITE, line=LINEC, lw=Pt(1)) + boxtext(sh, "Modul " + LET[i], size=13, color=INK, bold=True) + shp(MSO_SHAPE.OVAL, mx - Inches(0.07), my + Inches(0.12), Inches(0.14), Inches(0.14), fill=DOT[i]) +# In-Process-Pfeile zwischen den Modulen +for ax in (Inches(2.725), Inches(4.275)): + shp(MSO_SHAPE.RIGHT_ARROW, ax, my + mh / 2 - Inches(0.09), Inches(0.2), Inches(0.18), fill=ACCENT) +txt(cx_x, cx_y + cx_h - Inches(0.34), cx_w, Inches(0.26), "In-Process-Aufrufe · Nanosekunden", + size=11, color=LIGHT, align=PP_ALIGN.CENTER) + +# geteilte Datenbank +db_w, db_h, db_y = Inches(1.5), Inches(0.95), Inches(4.0) +vline(cL, cx_y + cx_h, db_y - (cx_y + cx_h), color=LINEC, weight=Pt(1.4)) +sh = shp(MSO_SHAPE.CAN, cL - db_w / 2, db_y, db_w, db_h, fill=WHITE, line=LINEC, lw=Pt(1)) +boxtext(sh, "DB", size=14, color=INK, bold=True) +txt(cx_x, db_y + db_h + Inches(0.06), cx_w, Inches(0.28), "1 geteilte Datenbank", + size=12.5, color=WHITE, bold=True, align=PP_ALIGN.CENTER) + +# =========================================================================== +# RECHTS – Microservices: Netzwerk-Bus, N Container, DB je Service +# =========================================================================== +bar_x, bar_w, bar_y, bar_h = Inches(7.1), Inches(5.3), Inches(2.2), Inches(0.26) +bar = shp(MSO_SHAPE.ROUNDED_RECTANGLE, bar_x, bar_y, bar_w, bar_h, fill=NAVY) +boxtext(bar, "Netzwerk-Grenze · HTTP / gRPC / Events", size=11, color=WHITE, bold=True) + +scx = [Inches(7.95), Inches(9.75), Inches(11.55)] +sc_w, sc_h, sc_y = Inches(1.5), Inches(1.2), Inches(2.62) +sdb_w, sdb_h, sdb_y = Inches(0.85), Inches(0.7), Inches(3.97) +for i, cx in enumerate(scx): + vline(cx, bar_y + bar_h, sc_y - (bar_y + bar_h), color=LINEC, weight=Pt(1.1)) + shp(MSO_SHAPE.ROUNDED_RECTANGLE, cx - sc_w / 2, sc_y, sc_w, sc_h, line=WHITE, lw=Pt(1.5)) + sh = shp(MSO_SHAPE.ROUNDED_RECTANGLE, cx - Inches(0.55), sc_y + Inches(0.3), + Inches(1.1), Inches(0.62), fill=WHITE, line=LINEC, lw=Pt(1)) + boxtext(sh, "Svc " + LET[i], size=12.5, color=INK, bold=True) + shp(MSO_SHAPE.OVAL, cx - Inches(0.06), sc_y + Inches(0.4), Inches(0.12), Inches(0.12), fill=DOT[i]) + # eigene DB je Service + vline(cx, sc_y + sc_h, sdb_y - (sc_y + sc_h), color=LINEC, weight=Pt(1.1)) + sh = shp(MSO_SHAPE.CAN, cx - sdb_w / 2, sdb_y, sdb_w, sdb_h, fill=WHITE, line=LINEC, lw=Pt(1)) + boxtext(sh, "DB", size=11, color=INK, bold=True) +txt(bar_x, sdb_y + sdb_h + Inches(0.06), bar_w, Inches(0.28), "Datenbank je Service", + size=12.5, color=WHITE, bold=True, align=PP_ALIGN.CENTER) + +# =========================================================================== +# Trenner + vs. +# =========================================================================== +vline(Inches(6.65), Inches(1.55), Inches(3.5), color=LINEC, weight=Pt(1)) +vs = shp(MSO_SHAPE.OVAL, Inches(6.65) - Inches(0.31), Inches(3.05), Inches(0.62), Inches(0.46), + line=WHITE, lw=Pt(1.5)) +vs.fill.background() +boxtext(vs, "vs.", size=13, color=WHITE, bold=True) + +# =========================================================================== +# Gegenüberstellung – wesentliche Unterschiede +# =========================================================================== +rows = [ + ("Skalierung", "als Ganzes (mehr Replicas)", "je Service gezielt"), + ("Datenhaltung", "1 geteilte DB", "DB je Service"), + ("Deployment", "gemeinsam, 1 Pipeline", "unabhängig je Service"), + ("Konsistenz", "ACID, lokal", "eventual / Saga"), +] +ry = Inches(5.5) +rh = Inches(0.33) +chip_w = Inches(1.95) +for i, (dim, mono, ms) in enumerate(rows): + y = ry + rh * i + chip = shp(MSO_SHAPE.ROUNDED_RECTANGLE, Inches(6.65) - chip_w / 2, y, chip_w, Inches(0.28), + line=LINEC, lw=Pt(0.75)) + chip.fill.background() + boxtext(chip, dim, size=11, color=LIGHT, bold=True) + txt(Inches(2.5), y - Inches(0.02), Inches(3.1), Inches(0.32), mono, + size=12, color=WHITE, align=PP_ALIGN.RIGHT) + txt(Inches(7.72), y - Inches(0.02), Inches(3.1), Inches(0.32), ms, + size=12, color=WHITE, align=PP_ALIGN.LEFT) + +out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Folie3b.pptx") +prs.save(out) +print("saved", out) diff --git a/folie4.py b/folie4.py new file mode 100644 index 0000000..a090e9c --- /dev/null +++ b/folie4.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +"""Folie 4 – Eventstream als Quelle; Aussagen links, Lupen rechts, Strecke unten +(adesso-Styling).""" + +import os +from pptx import Presentation +from pptx.util import Inches, Pt +from pptx.dml.color import RGBColor +from pptx.enum.text import PP_ALIGN, MSO_ANCHOR +from pptx.enum.shapes import MSO_SHAPE +from adesso_style import (WHITE, INK, BLUE, TEAL, GREEN, CORAL, LIGHT, RECF, F, FC, + paint_background, chrome) + +# Rollen-Mapping auf die adesso-Palette +NAVY = INK # dunkle Füllungen (Lupe, Maschine) +ACCENT = CORAL +AMBER = GREEN +GLASS = RECF +BELT = RECF +DARK = INK +LINEC = LIGHT +THIN = LIGHT +STREAM = INK +MID = RGBColor(0x34, 0x4E, 0x70) + +prs = Presentation() +prs.slide_width = Inches(13.333) +prs.slide_height = Inches(7.5) +SW, SH = prs.slide_width, prs.slide_height +s = prs.slides.add_slide(prs.slide_layouts[6]) + + +def shp(kind, x, y, w, h, fill=None, line=None, lw=Pt(1), rot=0): + sh = s.shapes.add_shape(kind, x, y, w, h) + if fill is None: sh.fill.background() + else: sh.fill.solid(); sh.fill.fore_color.rgb = fill + if line is None: sh.line.fill.background() + else: sh.line.color.rgb = line; sh.line.width = lw + if rot: sh.rotation = rot + sh.shadow.inherit = False + return sh + + +def txt(x, y, w, h, t, size=16, color=WHITE, bold=False, italic=False, + align=PP_ALIGN.LEFT, spc=None, font=F): + tb = s.shapes.add_textbox(x, y, w, h); tf = tb.text_frame + tf.word_wrap = True; tf.vertical_anchor = MSO_ANCHOR.MIDDLE + tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0) + p = tf.paragraphs[0]; p.alignment = align + r = p.add_run(); r.text = t + r.font.size = Pt(size); r.font.bold = bold; r.font.italic = italic + r.font.name = font; r.font.color.rgb = color + if spc: r.font._rPr.set('spc', str(spc)) + return tb + + +paint_background(s, SW, SH) +chrome(s, "Zustand oder Ereignis", 5) + +x0, x1 = Inches(1.0), Inches(12.33) +beltw = x1 - x0 + +# ---- Aussagen links -------------------------------------------------------- +claims = [ + "Eventstream = einzige Quelle der Wahrheit", + "kann nicht überschrieben werden", + "vollständige 3D-Sicht auf Maschine / Strang", +] +cy = Inches(1.95) +for i, c in enumerate(claims): + y = cy + Inches(0.6) * i + shp(MSO_SHAPE.RECTANGLE, Inches(0.95), y + Inches(0.1), Inches(0.14), Inches(0.14), fill=ACCENT) + txt(Inches(1.28), y, Inches(4.6), Inches(0.45), c, size=16.5, color=WHITE, bold=(i == 0)) + +# =========================================================================== +# LUPEN (Projektionen) +# =========================================================================== +def lupe(cx, label, label_w): + cyy = Inches(2.45) + d = Inches(0.9) + shp(MSO_SHAPE.OVAL, cx - d / 2, cyy - d / 2, d, d, fill=WHITE, line=NAVY, lw=Pt(4)) + shp(MSO_SHAPE.OVAL, cx - d / 2 + Inches(0.13), cyy - d / 2 + Inches(0.12), + Inches(0.17), Inches(0.13), fill=RECF) + hw, hh = Inches(0.48), Inches(0.15) + hcx, hcy = cx + d * 0.52, cyy + d * 0.52 + shp(MSO_SHAPE.ROUNDED_RECTANGLE, hcx - hw / 2, hcy - hh / 2, hw, hh, fill=NAVY, rot=45) + txt(cx - label_w / 2, cyy + d / 2 + Inches(0.16), label_w, Inches(0.6), + label, size=14, color=WHITE, bold=True, align=PP_ALIGN.CENTER) + return cyy + d / 2 + + +lupes = [ + (Inches(7.05), "Gruppen-Reporting", Inches(2.3)), + (Inches(9.45), "Optimierung der Auslastung", Inches(2.3)), + (Inches(11.55), "Abrechnungen", Inches(1.9)), +] +lens_bottoms = [lupe(cx, lb, lw) for cx, lb, lw in lupes] + +# =========================================================================== +# EVENTSTREAM (Pfeil) +# =========================================================================== +ay, ah = Inches(4.45), Inches(0.46) +shp(MSO_SHAPE.RIGHT_ARROW, x0, ay, beltw, ah, fill=STREAM) +acx = ay + ah / 2 +txt(Inches(5.65), ay + Inches(0.04), Inches(2.0), ah - Inches(0.08), + "Eventstream", size=14, color=WHITE, bold=True, align=PP_ALIGN.CENTER) + +# Verbinder Lupe -> Strom +for (cx, _, _), lb_y in zip(lupes, lens_bottoms): + shp(MSO_SHAPE.RECTANGLE, cx - Pt(0.5), lb_y, Pt(1), ay - lb_y, fill=LINEC) + +# =========================================================================== +# RÖHRCHEN-STRECKE durch Maschine – unten +# =========================================================================== +belt_top = Inches(6.1) +belt_h = Inches(0.30) + +for rx in (x0 - Inches(0.18), x1 - Inches(0.32)): + shp(MSO_SHAPE.OVAL, rx, belt_top - Inches(0.10), Inches(0.5), Inches(0.5), + fill=BELT, line=LINEC) +shp(MSO_SHAPE.ROUNDED_RECTANGLE, x0, belt_top, beltw, belt_h, fill=BELT) + +mx, mw = Inches(5.45), Inches(2.45) +my, mh = Inches(5.2), Inches(1.5) +shp(MSO_SHAPE.ROUNDED_RECTANGLE, mx, my, mw, mh, fill=NAVY) +shp(MSO_SHAPE.ROUNDED_RECTANGLE, mx + Inches(0.3), my + Inches(0.22), + mw - Inches(0.6), Inches(0.32), fill=MID) +shp(MSO_SHAPE.OVAL, mx + Inches(0.35), my + Inches(0.74), Inches(0.15), Inches(0.15), fill=TEAL) +shp(MSO_SHAPE.OVAL, mx + Inches(0.6), my + Inches(0.74), Inches(0.15), Inches(0.15), fill=AMBER) +shp(MSO_SHAPE.RECTANGLE, mx - Inches(0.02), belt_top - Inches(0.02), + Inches(0.14), belt_h + Inches(0.04), fill=DARK) +shp(MSO_SHAPE.RECTANGLE, mx + mw - Inches(0.12), belt_top - Inches(0.02), + Inches(0.14), belt_h + Inches(0.04), fill=DARK) + + +def tube(cx, liquid): + bw, bh = Inches(0.22), Inches(0.6) + bx, by = cx - bw / 2, belt_top - bh + shp(MSO_SHAPE.ROUNDED_RECTANGLE, bx, by, bw, bh, fill=GLASS, line=LINEC, lw=Pt(0.75)) + lh = Inches(0.25) + shp(MSO_SHAPE.ROUNDED_RECTANGLE, bx + Pt(1), belt_top - lh, bw - Pt(2), lh, fill=liquid) + cw, ch = Inches(0.26), Inches(0.12) + shp(MSO_SHAPE.ROUNDED_RECTANGLE, cx - cw / 2, by - Inches(0.06), cw, ch, fill=DARK) + + +cols = [ACCENT, TEAL, AMBER] +pre_x = [1.55, 2.1, 2.65, 3.2, 3.75, 4.3, 4.85] +post_x = [8.25, 8.8, 9.35, 9.9, 10.45, 11.0] +for i, xv in enumerate(pre_x + post_x): + tube(Inches(xv), cols[i % 3]) + +tube_top = belt_top - Inches(0.6) +for xv in pre_x + post_x: + shp(MSO_SHAPE.RECTANGLE, Inches(xv) - Pt(0.4), ay + ah, + Pt(0.8), tube_top - (ay + ah), fill=THIN) + shp(MSO_SHAPE.OVAL, Inches(xv) - Inches(0.05), acx - Inches(0.05), + Inches(0.1), Inches(0.1), fill=AMBER) + +out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Folie4.pptx") +prs.save(out) +print("saved", out) diff --git a/folie5.py b/folie5.py new file mode 100644 index 0000000..cfc515a --- /dev/null +++ b/folie5.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +"""Folie 5 – Event Sourcing: das groessere Bild (DEUTSCH). +Quellen differenziert (Publisher / Subsysteme), Event-Prozessoren als Teil des Stores, +Hot Store & Cold Store als Kernfunktion (Coral, CORE-Tag), Projektionen dazwischen, +darunter Cold Store / Monitoring / Reporting. Tech-Impls als Chips. (adesso-Styling) +Hintergrund: solides dunkles Blau (BG) – Farbe frei anpassbar.""" + +import os +from pptx import Presentation +from pptx.util import Inches, Pt +from pptx.dml.color import RGBColor +from pptx.enum.text import PP_ALIGN, MSO_ANCHOR +from pptx.enum.shapes import MSO_SHAPE +from adesso_style import (WHITE, INK, BLUE, TEAL, GREEN, CORAL, LIGHT, MUTE, RECF, + F, FC, chrome) + +# ---- Hintergrundfarbe (solid, keine Transparenz) – frei anpassbar ---------- +BG = RGBColor(0x0A, 0x3D, 0x6B) # dunkles Blau + +prs = Presentation() +prs.slide_width = Inches(13.333) +prs.slide_height = Inches(7.5) +SW, SH = prs.slide_width, prs.slide_height +s = prs.slides.add_slide(prs.slide_layouts[6]) + + +def shp(kind, x, y, w, h, fill=None, line=None, lw=Pt(1), rot=0, adj=None): + sh = s.shapes.add_shape(kind, x, y, w, h) + if fill is None: sh.fill.background() + else: sh.fill.solid(); sh.fill.fore_color.rgb = fill + if line is None: sh.line.fill.background() + else: sh.line.color.rgb = line; sh.line.width = lw + if rot: sh.rotation = rot + if adj is not None: + try: sh.adjustments[0] = adj + except Exception: pass + sh.shadow.inherit = False + return sh + + +def txt(x, y, w, h, t, size=16, color=WHITE, bold=False, italic=False, + align=PP_ALIGN.LEFT, font=F, anchor=MSO_ANCHOR.MIDDLE): + tb = s.shapes.add_textbox(x, y, w, h); tf = tb.text_frame + tf.word_wrap = True; tf.vertical_anchor = anchor + tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0) + first = True + for line in t.split("\n"): + p = tf.paragraphs[0] if first else tf.add_paragraph() + first = False + p.alignment = align + r = p.add_run(); r.text = line + r.font.size = Pt(size); r.font.bold = bold; r.font.italic = italic + r.font.name = font; r.font.color.rgb = color + return tb + + +def chip(cx, y, text, fill=INK, fg=WHITE, size=11): + """Tech-Impl als kleine Pill, zentriert um cx.""" + w = Inches(0.34 + 0.092 * len(text)); h = Inches(0.30) + x = cx - w / 2 + shp(MSO_SHAPE.ROUNDED_RECTANGLE, x, y, w, h, fill=fill, adj=0.5) + txt(x, y, w, h, text, size=size, color=fg, bold=False, + align=PP_ALIGN.CENTER, font=FC) + + +def core_tag(x, y): + w, h = Inches(0.62), Inches(0.26) + shp(MSO_SHAPE.ROUNDED_RECTANGLE, x, y, w, h, fill=INK, adj=0.5) + txt(x, y, w, h, "CORE", size=9.5, color=TEAL, bold=True, + align=PP_ALIGN.CENTER, font=FC) + + +# ---- solider Hintergrund (zuerst -> liegt hinten) -------------------------- +bg = shp(MSO_SHAPE.RECTANGLE, 0, 0, SW, SH, fill=BG) + +chrome(s, "System Landscape", 6) + +CX = Inches(6.665) # Folienmitte (x) + +# ---- Quellen: differenziert ------------------------------------------------ +src_y, src_h = Inches(1.36), Inches(0.92) + + +def source_box(x, w, title, examples): + shp(MSO_SHAPE.ROUNDED_RECTANGLE, x, src_y, w, src_h, fill=WHITE, adj=0.10) + txt(x + Inches(0.28), src_y + Inches(0.10), w - Inches(0.56), Inches(0.32), + title, size=14.5, color=INK, bold=True, anchor=MSO_ANCHOR.TOP) + txt(x + Inches(0.28), src_y + Inches(0.46), w - Inches(0.56), Inches(0.36), + examples, size=12, color=MUTE, anchor=MSO_ANCHOR.TOP) + + +pub_x, pub_w = Inches(0.9), Inches(5.6) +sub_x, sub_w = Inches(6.83), Inches(5.6) +pub_cx = pub_x + pub_w / 2 +sub_cx = sub_x + sub_w / 2 + +source_box(pub_x, pub_w, "Publisher (nur publizieren)", + "z. B. AB+M · LabAccess · MDN · Stammdaten — proprietär") +source_box(sub_x, sub_w, "Subsysteme (konsumieren & publizieren)", + "z. B. LIS · Geräte / Analyzer — proprietär") + +# ---- Pfeile Quellen -> Hot Store ------------------------------------------ +ay, ah = src_y + src_h + Inches(0.02), Inches(0.30) +shp(MSO_SHAPE.DOWN_ARROW, pub_cx - Inches(0.16), ay, Inches(0.32), ah, fill=CORAL) +txt(pub_cx + Inches(0.28), ay - Inches(0.01), Inches(1.6), ah + Inches(0.02), + "publizieren", size=11, color=LIGHT, italic=True) +shp(MSO_SHAPE.UP_DOWN_ARROW, sub_cx - Inches(0.11), ay - Inches(0.05), Inches(0.22), + ah + Inches(0.10), fill=CORAL) +txt(sub_cx - Inches(2.85), ay - Inches(0.01), Inches(2.5), ah + Inches(0.02), + "konsumieren & publizieren", size=11, color=LIGHT, italic=True, align=PP_ALIGN.RIGHT) + +# ---- HOT STORE (Kern) + Event-Prozessoren (Teil des Stores) ---------------- +hx, hy, hw, hh = Inches(3.4), Inches(2.62), Inches(6.53), Inches(1.50) +shp(MSO_SHAPE.ROUNDED_RECTANGLE, hx, hy, hw, hh, fill=CORAL, line=INK, lw=Pt(3.5), adj=0.07) +core_tag(hx + Inches(0.16), hy + Inches(0.13)) +txt(hx, hy + Inches(0.05), hw, Inches(0.34), + "Event Store — Hot Store", size=20, color=INK, bold=True, align=PP_ALIGN.CENTER) +txt(hx, hy + Inches(0.40), hw, Inches(0.20), + "append-only · kanonische Wahrheit", size=12.5, color=INK, align=PP_ALIGN.CENTER) +chip(CX, hy + Inches(0.62), "z. B. Kafka") + +# Event-Prozessoren als eingebetteter Bestandteil des Stores +ep_x, ep_y, ep_w, ep_h = hx + Inches(0.28), hy + Inches(0.98), hw - Inches(0.56), Inches(0.44) +shp(MSO_SHAPE.ROUNDED_RECTANGLE, ep_x, ep_y, ep_w, ep_h, fill=WHITE, adj=0.22) +txt(ep_x, ep_y, ep_w, ep_h, + "Event-Prozessoren (konsumieren & publizieren) · z. B. Proben-Routing", + size=11.5, color=INK, bold=True, align=PP_ALIGN.CENTER) + +# ---- Hot -> Projektionen --------------------------------------------------- +shp(MSO_SHAPE.DOWN_ARROW, CX - Inches(0.16), hy + hh + Inches(0.03), + Inches(0.32), Inches(0.28), fill=CORAL) + +# ---- PROJEKTIONEN ---------------------------------------------------------- +px, py, pw, ph = Inches(3.4), Inches(4.46), Inches(6.53), Inches(0.66) +shp(MSO_SHAPE.ROUNDED_RECTANGLE, px, py, pw, ph, fill=WHITE, adj=0.14) +txt(px + Inches(0.3), py, Inches(3.0), ph, "Projektionen", size=16, color=INK, bold=True) +chip(px + pw - Inches(2.0), py + ph / 2 - Inches(0.15), "Kafka Streams → PostgreSQL") + +# ---- Verteiler (Projektionen -> 3 Bloecke) -------------------------------- +boxes_y = Inches(5.34) +bw = Inches(3.7); bh = Inches(1.56) +cold_x = Inches(0.9); cold_cx = cold_x + bw / 2 +mon_x = Inches(4.815); mon_cx = mon_x + bw / 2 +rep_x = Inches(8.73); rep_cx = rep_x + bw / 2 + +bus_y = py + ph + Inches(0.10) +shp(MSO_SHAPE.RECTANGLE, CX - Pt(0.75), py + ph, Pt(1.5), bus_y - (py + ph), fill=LIGHT) +shp(MSO_SHAPE.RECTANGLE, cold_cx, bus_y, rep_cx - cold_cx, Pt(1.5), fill=LIGHT) +for cx in (cold_cx, mon_cx, rep_cx): + shp(MSO_SHAPE.DOWN_ARROW, cx - Inches(0.11), bus_y, Inches(0.22), boxes_y - bus_y, + fill=LIGHT) + + +def block(x, title, body, tech, core=False): + fill = CORAL if core else WHITE + line = INK if core else None + lw = Pt(3.5) if core else Pt(1) + shp(MSO_SHAPE.ROUNDED_RECTANGLE, x, boxes_y, bw, bh, fill=fill, line=line, lw=lw, adj=0.07) + ty = boxes_y + (Inches(0.30) if core else Inches(0.20)) + if core: + core_tag(x + Inches(0.16), boxes_y + Inches(0.16)) + txt(x, ty, bw, Inches(0.42), title, size=17, color=INK, bold=True, align=PP_ALIGN.CENTER) + txt(x + Inches(0.25), ty + Inches(0.46), bw - Inches(0.5), Inches(0.5), body, + size=12.5, color=(INK if core else MUTE), align=PP_ALIGN.CENTER) + chip(x + bw / 2, boxes_y + bh - Inches(0.42), tech) + + +block(cold_x, "Cold Store", + "Archiv · Audit-Trail · Dokumente", "z. B. PostgreSQL", core=True) +block(mon_x, "Monitoring", + "Auftragsstatus / Auslastung", "z. B. Grafana") +block(rep_x, "Reporting", + "Bericht an Gruppe · KPIs", "z. B. Power BI") + +out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Folie5.pptx") +prs.save(out) +print("saved", out) diff --git a/folie6.py b/folie6.py new file mode 100644 index 0000000..ca460e4 --- /dev/null +++ b/folie6.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +"""Folie 6 – Core / Rationale (DEUTSCH): kompakte Landscape links, Argumente als Karten rechts. +(adesso-Styling). Hintergrund: solides dunkles Blau (BG) – Farbe frei anpassbar.""" + +import os +from pptx import Presentation +from pptx.util import Inches, Pt +from pptx.dml.color import RGBColor +from pptx.enum.text import PP_ALIGN, MSO_ANCHOR +from pptx.enum.shapes import MSO_SHAPE +from adesso_style import (WHITE, INK, BLUE, TEAL, GREEN, CORAL, LIGHT, MUTE, RECF, + F, FC, chrome) + +# ---- Hintergrundfarbe (solid, keine Transparenz) – frei anpassbar ---------- +BG = RGBColor(0x0A, 0x3D, 0x6B) # dunkles Blau + +prs = Presentation() +prs.slide_width = Inches(13.333) +prs.slide_height = Inches(7.5) +SW, SH = prs.slide_width, prs.slide_height +s = prs.slides.add_slide(prs.slide_layouts[6]) + + +def shp(kind, x, y, w, h, fill=None, line=None, lw=Pt(1), rot=0, adj=None): + sh = s.shapes.add_shape(kind, x, y, w, h) + if fill is None: sh.fill.background() + else: sh.fill.solid(); sh.fill.fore_color.rgb = fill + if line is None: sh.line.fill.background() + else: sh.line.color.rgb = line; sh.line.width = lw + if rot: sh.rotation = rot + if adj is not None: + try: sh.adjustments[0] = adj + except Exception: pass + sh.shadow.inherit = False + return sh + + +def txt(x, y, w, h, t, size=16, color=WHITE, bold=False, italic=False, + align=PP_ALIGN.LEFT, font=F, anchor=MSO_ANCHOR.MIDDLE, sp=None): + tb = s.shapes.add_textbox(x, y, w, h); tf = tb.text_frame + tf.word_wrap = True; tf.vertical_anchor = anchor + tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0) + first = True + for line in t.split("\n"): + p = tf.paragraphs[0] if first else tf.add_paragraph() + first = False + p.alignment = align + if sp is not None: p.space_after = Pt(sp) + r = p.add_run(); r.text = line + r.font.size = Pt(size); r.font.bold = bold; r.font.italic = italic + r.font.name = font; r.font.color.rgb = color + return tb + + +def chip(cx, y, text, size=10): + w = Inches(0.30 + 0.085 * len(text)); h = Inches(0.27) + x = cx - w / 2 + shp(MSO_SHAPE.ROUNDED_RECTANGLE, x, y, w, h, fill=INK, adj=0.5) + txt(x, y, w, h, text, size=size, color=WHITE, align=PP_ALIGN.CENTER, font=FC) + + +def core_tag(x, y): + w, h = Inches(0.54), Inches(0.23) + shp(MSO_SHAPE.ROUNDED_RECTANGLE, x, y, w, h, fill=INK, adj=0.5) + txt(x, y, w, h, "CORE", size=8.5, color=TEAL, bold=True, align=PP_ALIGN.CENTER, font=FC) + + +# ---- solider Hintergrund (zuerst -> liegt hinten) -------------------------- +bg = shp(MSO_SHAPE.RECTANGLE, 0, 0, SW, SH, fill=BG) + +chrome(s, "Warum Event Sourcing passt", 7) + +# =========================================================================== +# LINKS – kompakte Landscape +# =========================================================================== +LCX = Inches(3.35) + +# Quellen +shp(MSO_SHAPE.ROUNDED_RECTANGLE, Inches(0.7), Inches(1.58), Inches(5.3), Inches(0.7), + fill=WHITE, adj=0.12) +txt(Inches(0.92), Inches(1.66), Inches(4.9), Inches(0.26), "Quellen", + size=12.5, color=INK, bold=True, anchor=MSO_ANCHOR.TOP) +txt(Inches(0.92), Inches(1.94), Inches(4.9), Inches(0.28), + "Publisher (publizieren) · Subsysteme (konsumieren & publizieren)", + size=10.5, color=MUTE, anchor=MSO_ANCHOR.TOP) + +shp(MSO_SHAPE.DOWN_ARROW, LCX - Inches(0.13), Inches(2.30), Inches(0.26), Inches(0.26), + fill=CORAL) + +# Hot Store (CORE) inkl. Event-Prozessoren +hy, hh = Inches(2.58), Inches(0.98) +shp(MSO_SHAPE.ROUNDED_RECTANGLE, Inches(1.35), hy, Inches(4.0), hh, + fill=CORAL, line=INK, lw=Pt(3), adj=0.08) +core_tag(Inches(1.50), hy + Inches(0.12)) +txt(Inches(1.35), hy + Inches(0.08), Inches(4.0), Inches(0.34), + "Event Store — Hot Store", size=15, color=INK, bold=True, align=PP_ALIGN.CENTER) +txt(Inches(1.35), hy + Inches(0.40), Inches(4.0), Inches(0.24), + "inkl. Event-Prozessoren", size=10.5, color=INK, align=PP_ALIGN.CENTER) +chip(LCX, hy + hh - Inches(0.34), "Kafka") + +shp(MSO_SHAPE.DOWN_ARROW, LCX - Inches(0.13), hy + hh + Inches(0.02), + Inches(0.26), Inches(0.24), fill=CORAL) + +# Projektionen +py, ph = Inches(3.82), Inches(0.56) +shp(MSO_SHAPE.ROUNDED_RECTANGLE, Inches(1.35), py, Inches(4.0), ph, fill=WHITE, adj=0.16) +txt(Inches(1.55), py, Inches(2.0), ph, "Projektionen", size=12.5, color=INK, bold=True) +chip(Inches(4.25), py + ph / 2 - Inches(0.135), "Kafka Streams → PostgreSQL") + +# Verteiler -> 3 Bloecke +boxes_y = Inches(4.72); mbh = Inches(1.28) +mb_w = Inches(1.65) +cold_x = Inches(0.7); cold_cx = cold_x + mb_w / 2 +mon_x = Inches(2.55); mon_cx = mon_x + mb_w / 2 +rep_x = Inches(4.40); rep_cx = rep_x + mb_w / 2 +bus_y = py + ph + Inches(0.10) +shp(MSO_SHAPE.RECTANGLE, LCX - Pt(0.75), py + ph, Pt(1.5), bus_y - (py + ph), fill=LIGHT) +shp(MSO_SHAPE.RECTANGLE, cold_cx, bus_y, rep_cx - cold_cx, Pt(1.5), fill=LIGHT) +for cx in (cold_cx, mon_cx, rep_cx): + shp(MSO_SHAPE.DOWN_ARROW, cx - Inches(0.10), bus_y, Inches(0.20), boxes_y - bus_y, fill=LIGHT) + + +def minibox(x, title, tech, core=False): + fill = CORAL if core else WHITE + line = INK if core else None + lw = Pt(3) if core else Pt(1) + shp(MSO_SHAPE.ROUNDED_RECTANGLE, x, boxes_y, mb_w, mbh, fill=fill, line=line, lw=lw, adj=0.10) + if core: + core_tag(x + Inches(0.12), boxes_y + Inches(0.12)) + txt(x, boxes_y + (Inches(0.42) if core else Inches(0.26)), mb_w, Inches(0.5), title, + size=12, color=INK, bold=True, align=PP_ALIGN.CENTER) + chip(x + mb_w / 2, boxes_y + mbh - Inches(0.36), tech) + + +minibox(cold_x, "Cold\nStore", "PostgreSQL", core=True) +minibox(mon_x, "Monitoring", "Grafana") +minibox(rep_x, "Reporting", "Power BI") + +# =========================================================================== +# RECHTS – Argumente als Karten +# =========================================================================== +cards = [ + ("Bewährte Technologie", + "Erprobt in regulierten Branchen mit hohem Volumen — z. B. ING setzt auf Kafka / Event-Streaming."), + ("Flexibel", + "Neues System = neuer Adapter, ohne Eingriff in den Rest. Neue Event-Folgen & Projektionen jederzeit baubar."), + ("Skalierbar", + "Selbst massive Lastzunahme wird durch mehr Hardware aufgefangen — horizontal, ohne Redesign."), + ("Kosteneffizient", + "Wiederverwendbare Adapter statt Punkt-zu-Punkt. Open Source auf Standard-Hardware — kein Lock-in."), + ("Auditierbar & revisionssicher", + "Append-only, vollständige & manipulationssichere Historie — vollständig replay-fähig."), + ("Resilient & entkoppelt", + "Systeme fallen aus und holen per Replay auf; ein Log statt N² Schnittstellen."), +] + +cw, ch = Inches(3.0), Inches(1.42) +col_x = [Inches(6.55), Inches(9.8)] +row_y = [Inches(1.62), Inches(3.30), Inches(4.98)] +for i, (title, body) in enumerate(cards): + x = col_x[i % 2]; y = row_y[i // 2] + shp(MSO_SHAPE.ROUNDED_RECTANGLE, x, y, cw, ch, fill=WHITE, adj=0.08) + shp(MSO_SHAPE.RECTANGLE, x + Inches(0.0), y + Inches(0.14), Inches(0.11), ch - Inches(0.28), + fill=CORAL) + txt(x + Inches(0.32), y + Inches(0.15), cw - Inches(0.5), Inches(0.36), title, + size=13.5, color=INK, bold=True, anchor=MSO_ANCHOR.TOP) + txt(x + Inches(0.32), y + Inches(0.55), cw - Inches(0.52), ch - Inches(0.68), body, + size=10.5, color=MUTE, anchor=MSO_ANCHOR.TOP) + +out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Folie6.pptx") +prs.save(out) +print("saved", out) diff --git a/folie7.py b/folie7.py new file mode 100644 index 0000000..2335fb0 --- /dev/null +++ b/folie7.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +"""Folie 7 – Integrationsserver / Event-Sourcing-Anbindung (DEUTSCH, adesso-Stil). +Eingaenge (Order Entry/CLAB, Probeneingang [Atras+PVT], Geraete, Middleware, MiBi/Hybase, +Stammdaten) -> Adapter-Ebene -> Event Store -> unten Event-Prozessoren, Projektionen, +Konsumenten (SinaBI, Archivierung). Hintergrund: solides dunkles Blau (BG).""" + +import os +from pptx import Presentation +from pptx.util import Inches, Pt +from pptx.dml.color import RGBColor +from pptx.enum.text import PP_ALIGN, MSO_ANCHOR +from pptx.enum.shapes import MSO_SHAPE, MSO_CONNECTOR +from pptx.oxml.ns import qn +from adesso_style import (WHITE, INK, BLUE, TEAL, GREEN, CORAL, LIGHT, MUTE, RECF, + F, FC, chrome) + +BG = RGBColor(0x0A, 0x3D, 0x6B) +GRAY = RGBColor(0xAE, 0xB7, 0xC2) +ADC = RGBColor(0xCF, 0xE2, 0xF7) +SUBC = RGBColor(0x3C, 0x4A, 0x5A) # zweite Zeile – dunkel & gut lesbar + +prs = Presentation(); prs.slide_width = Inches(13.333); prs.slide_height = Inches(7.5) +SW, SH = prs.slide_width, prs.slide_height +s = prs.slides.add_slide(prs.slide_layouts[6]) + + +def shp(kind, x, y, w, h, fill=None, line=None, lw=Pt(1), adj=None): + sh = s.shapes.add_shape(kind, Inches(x), Inches(y), Inches(w), Inches(h)) + if fill is None: sh.fill.background() + else: sh.fill.solid(); sh.fill.fore_color.rgb = fill + if line is None: sh.line.fill.background() + else: sh.line.color.rgb = line; sh.line.width = lw + if adj is not None: + try: sh.adjustments[0] = adj + except Exception: pass + sh.shadow.inherit = False + return sh + + +def txt(x, y, w, h, t, size=11, color=WHITE, bold=False, italic=False, + align=PP_ALIGN.LEFT, font=F, anchor=MSO_ANCHOR.MIDDLE): + tb = s.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h)); tf = tb.text_frame + tf.word_wrap = True; tf.vertical_anchor = anchor + tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0) + first = True + for line in t.split("\n"): + p = tf.paragraphs[0] if first else tf.add_paragraph(); first = False + p.alignment = align + r = p.add_run(); r.text = line + r.font.size = Pt(size); r.font.bold = bold; r.font.italic = italic + r.font.name = font; r.font.color.rgb = color + return tb + + +def conn(x1, y1, x2, y2, color=LIGHT, w=1.6, both=False): + c = s.shapes.add_connector(MSO_CONNECTOR.STRAIGHT, Inches(x1), Inches(y1), + Inches(x2), Inches(y2)) + c.line.color.rgb = color; c.line.width = Pt(w); c.shadow.inherit = False + ln = c.line._get_or_add_ln() + if both: + ln.append(ln.makeelement(qn('a:headEnd'), {'type': 'triangle'})) + ln.append(ln.makeelement(qn('a:tailEnd'), {'type': 'triangle'})) + return c + + +def group(x, y, w, h, title, tsize=11.5): + shp(MSO_SHAPE.ROUNDED_RECTANGLE, x, y, w, h, fill=None, line=LIGHT, lw=Pt(1.25), adj=0.04) + txt(x, y + 0.05, w, 0.26, title, size=tsize, color=WHITE, bold=True, + align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.TOP) + + +def box(x, y, w, h, title, sub=None, fill=WHITE, tcol=INK, tsize=10.5, line=None, lw=Pt(1)): + shp(MSO_SHAPE.ROUNDED_RECTANGLE, x, y, w, h, fill=fill, line=line, lw=lw, adj=0.16) + tb = s.shapes.add_textbox(Inches(x + 0.05), Inches(y), Inches(w - 0.10), Inches(h)) + tf = tb.text_frame; tf.word_wrap = True; tf.vertical_anchor = MSO_ANCHOR.MIDDLE + tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0) + p = tf.paragraphs[0]; p.alignment = PP_ALIGN.CENTER; p.line_spacing = 0.98 + r = p.add_run(); r.text = title; r.font.size = Pt(tsize); r.font.bold = True + r.font.name = F; r.font.color.rgb = tcol + if sub is not None: + p.space_after = Pt(2) + p2 = tf.add_paragraph(); p2.alignment = PP_ALIGN.CENTER; p2.line_spacing = 0.98 + r2 = p2.add_run(); r2.text = sub; r2.font.size = Pt(tsize - 0.5) + r2.font.name = F; r2.font.color.rgb = SUBC + return tb + + +AD_Y, AD_H = 3.50, 0.48 +def adapter(x, w, title): + box(x, AD_Y, w, AD_H, title, fill=ADC, tcol=INK, tsize=9, line=BLUE, lw=Pt(1.1)) + + +# ---- Hintergrund + Header -------------------------------------------------- +shp(MSO_SHAPE.RECTANGLE, 0, 0, SW.inches, SH.inches, fill=BG) +chrome(s, "Event Sourcing — Integrationsserver", 8) + +# ---- Legende --------------------------------------------------------------- +ly = 0.95 +def legsq(x, color, label, lw_txt): + shp(MSO_SHAPE.RECTANGLE, x, ly + 0.02, 0.15, 0.15, fill=color) + txt(x + 0.21, ly - 0.03, lw_txt, 0.26, label, size=10, color=WHITE, anchor=MSO_ANCHOR.MIDDLE) +legsq(0.40, CORAL, "Event Store", 1.2) +legsq(1.76, GREEN, "Prop-Limbach", 1.3) +legsq(3.28, ADC, "Adapter (own)", 1.4) +legsq(4.90, WHITE, "System", 0.9) +txt(6.30, 0.92, 6.63, 0.3, + "↕ publizieren & abonnieren ↓ publizieren grau = Kommunikation", + size=10, color=LIGHT, italic=True, anchor=MSO_ANCHOR.MIDDLE, align=PP_ALIGN.RIGHT) + +# ---- EVENT STORE ----------------------------------------------------------- +ST_X, ST_Y, ST_W, ST_H = 0.40, 4.18, 12.53, 0.60 +shp(MSO_SHAPE.ROUNDED_RECTANGLE, ST_X, ST_Y, ST_W, ST_H, fill=CORAL, line=INK, lw=Pt(3.5), adj=0.10) +txt(ST_X, ST_Y + 0.06, ST_W, 0.30, "Event Store — append-only Event-Log", + size=15, color=INK, bold=True, align=PP_ALIGN.CENTER) +txt(ST_X, ST_Y + 0.35, ST_W, 0.22, "kanonische Wahrheit · Replay-fähig · (Kafka)", + size=10.5, color=INK, align=PP_ALIGN.CENTER) + +# =========================================================================== +# OBEN – Systeme + Adapter-Ebene +# =========================================================================== +TGY, TGH = 1.28, 2.78 + +# --- 1: Order Entry ueber CLAB --------------------------------------------- +group(0.40, TGY, 2.70, TGH, "Order Entry über CLAB") +box(0.52, 1.62, 1.16, 0.42, "LabAccess") +box(1.82, 1.62, 1.16, 0.42, "MDN") +box(1.18, 2.22, 1.14, 0.44, "AB+M") +box(1.05, 2.84, 1.40, 0.42, "CLAB (LIS)", fill=GREEN) +adapter(0.78, 1.94, "CLAB-Adapter") +conn(1.10, 2.04, 1.55, 2.22); conn(2.40, 2.04, 1.95, 2.22) +conn(1.75, 2.66, 1.75, 2.84); conn(1.75, 3.26, 1.75, AD_Y) +conn(1.75, AD_Y + AD_H, 1.75, ST_Y, both=True) + +# --- 2: Probeneingang (Atras SL + PVT) ------------------------------------- +group(3.26, TGY, 1.95, TGH, "Probeneingang") +box(3.34, 2.30, 0.86, 0.52, "Atras SL") +box(4.27, 2.30, 0.86, 0.52, "PVT") +adapter(3.34, 1.79, "Adapter (Probe → Event)") +conn(2.32, 2.50, 3.34, 2.56, color=GRAY, both=True) # Praeanalytik-Kommunikation +txt(2.28, 2.04, 1.30, 0.34, "Präanalytik-\nKomm.", size=8, color=GRAY, italic=True, + align=PP_ALIGN.CENTER) +conn(3.77, 2.82, 3.95, AD_Y); conn(4.70, 2.82, 4.55, AD_Y) +conn(4.235, AD_Y + AD_H, 4.235, ST_Y) + +# --- 3: Geraete ------------------------------------------------------------- +group(5.37, TGY, 1.78, TGH, "Geräte-Adapter") +box(5.49, 2.10, 1.54, 0.70, "Geräte", "Sysmex · Analyzer") +adapter(5.45, 1.62, "Adapter (Device → Event)") +conn(6.26, 2.80, 6.26, AD_Y); conn(6.26, AD_Y + AD_H, 6.26, ST_Y, both=True) + +# --- 4: Middleware ---------------------------------------------------------- +group(7.31, TGY, 1.78, TGH, "Middleware") +box(7.43, 2.10, 1.54, 0.70, "Middleware-Systeme", "Myla/Maestria · LabOps · Liason") +adapter(7.39, 1.62, "Adapter (Middleware → Event)") +conn(8.20, 2.80, 8.20, AD_Y); conn(8.20, AD_Y + AD_H, 8.20, ST_Y, both=True) + +# --- 5: MiBi (Hybase) ------------------------------------------------------- +group(9.25, TGY, 1.70, TGH, "MiBi") +box(9.37, 2.10, 1.46, 0.70, "Hybase", "Resistenz / ID-Daten") +adapter(9.33, 1.54, "Adapter (MiBi → Event)") +conn(10.10, 2.80, 10.10, AD_Y); conn(10.10, AD_Y + AD_H, 10.10, ST_Y, both=True) + +# --- 6: Stammdaten ---------------------------------------------------------- +group(11.11, TGY, 1.82, TGH, "Stammdaten") +box(11.23, 2.10, 1.58, 0.70, "Stammdaten-Quellen", "SinaCRM · OTS/Znooni") +adapter(11.19, 1.66, "Adapter (MasterData → Event)") +conn(12.02, 2.80, 12.02, AD_Y); conn(12.02, AD_Y + AD_H, 12.02, ST_Y) + +# =========================================================================== +# UNTEN – Event-Prozessoren · Projektionen · Konsumenten +# =========================================================================== +BGY, BGH = 4.96, 1.96 + +group(0.40, BGY, 2.95, BGH, "Event-Prozessoren") +box(0.56, 5.42, 2.63, 0.55, "Mapping / Transformation") +box(0.56, 6.12, 2.63, 0.55, "Routing Engine") +conn(1.85, ST_Y + ST_H, 1.85, BGY, both=True) + +group(3.50, BGY, 5.85, BGH, "Projektionen") +box(3.66, 5.46, 1.78, 1.02, "Auftragsstatus") +box(5.56, 5.46, 1.78, 1.02, "Audit Trail") +box(7.46, 5.46, 1.73, 1.02, "Reporting") +conn(6.40, ST_Y + ST_H, 6.40, BGY) + +group(9.50, BGY, 3.43, BGH, "Konsumenten") +box(9.66, 5.38, 3.11, 0.68, "SinaBI (Power BI)", "Reporting / BI") +box(9.66, 6.12, 3.11, 0.68, "Archivierung · Vaultbox", "Roche Navify · LDT2") +conn(11.21, ST_Y + ST_H, 11.21, BGY) + +out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Folie7.pptx") +prs.save(out) +print("saved", out) diff --git a/folie8.py b/folie8.py new file mode 100644 index 0000000..8d5e94f --- /dev/null +++ b/folie8.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +"""Folie 8 – Funktionszuordnung je System (aus Anforderungskatalog_LIS_Fit-Gap v2.xlsx). +Pro System eine Kachel; Funktionen NUR dort = kraeftig/dunkel, auch woanders = grau mit +'· auch: ...'. Einspaltig, Fira Sans Condensed. (adesso-Stil, deutsch, dunkelblau.)""" + +import os +from pptx import Presentation +from pptx.util import Inches, Pt +from pptx.dml.color import RGBColor +from pptx.enum.text import PP_ALIGN, MSO_ANCHOR +from pptx.enum.shapes import MSO_SHAPE +from openpyxl import load_workbook +from openpyxl.utils import column_index_from_string as ci +from adesso_style import (WHITE, INK, BLUE, TEAL, GREEN, CORAL, LIGHT, MUTE, RECF, + F, FC, chrome) + +BG = RGBColor(0x0A, 0x3D, 0x6B) +SHARE = RGBColor(0x6B, 0x77, 0x85) # grau fuer "auch woanders" + +# ---- Daten aus dem Excel lesen -------------------------------------------- +HERE = os.path.dirname(os.path.abspath(__file__)) +wb = load_workbook(os.path.join(HERE, "Anforderungskatalog_LIS_Fit-Gap v2.xlsx"), data_only=True) +ws = wb["Anforderungen & Fit-Gap"] +SYS = [('H', 'Order Entry'), ('N', 'AB+M'), ('O', 'Integrationsserver'), ('P', 'PVT'), + ('Q', 'R. LabOps'), ('R', 'Sysmex'), ('S', 'SAP'), ('T', 'Consens')] +ABBR = {'Order Entry': 'OE', 'AB+M': 'AB+M', 'Integrationsserver': 'Int.-Server', + 'PVT': 'PVT', 'R. LabOps': 'R.LabOps', 'Sysmex': 'Sysmex', 'SAP': 'SAP', + 'Consens': 'Consens'} + +reqs = []; cur = None +for r in range(5, ws.max_row + 1): + b = ws.cell(r, 2).value; c = ws.cell(r, 3).value + marks = {d: (ws.cell(r, ci(col)).value not in (None, '')) for col, d in SYS} + if b: + cur = {'name': (str(c).strip() if c else str(b)), 'm': dict(marks)} + reqs.append(cur) + elif cur: + for d, v in marks.items(): + if v: cur['m'][d] = True + if c: cur['name'] = (cur['name'] + ' / ' + str(c).strip()) + +persys = {d: [] for _, d in SYS} +for req in reqs: + active = [d for _, d in SYS if req['m'][d]] + for d in active: + persys[d].append((req['name'], [o for o in active if o != d])) +for d in persys: + persys[d].sort(key=lambda it: (1 if it[1] else 0)) + +# ---- PPTX ------------------------------------------------------------------ +prs = Presentation(); prs.slide_width = Inches(13.333); prs.slide_height = Inches(7.5) +SW, SH = prs.slide_width, prs.slide_height +s = prs.slides.add_slide(prs.slide_layouts[6]) + + +def shp(kind, x, y, w, h, fill=None, line=None, lw=Pt(1), adj=None): + sh = s.shapes.add_shape(kind, Inches(x), Inches(y), Inches(w), Inches(h)) + if fill is None: sh.fill.background() + else: sh.fill.solid(); sh.fill.fore_color.rgb = fill + if line is None: sh.line.fill.background() + else: sh.line.color.rgb = line; sh.line.width = lw + if adj is not None: + try: sh.adjustments[0] = adj + except Exception: pass + sh.shadow.inherit = False + return sh + + +def txt(x, y, w, h, t, size=12, color=WHITE, bold=False, align=PP_ALIGN.LEFT, + font=F, anchor=MSO_ANCHOR.MIDDLE): + tb = s.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h)); tf = tb.text_frame + tf.word_wrap = True; tf.vertical_anchor = anchor + tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0) + p = tf.paragraphs[0]; p.alignment = align + r = p.add_run(); r.text = t + r.font.size = Pt(size); r.font.bold = bold; r.font.name = font; r.font.color.rgb = color + return tb + + +def cut(t, n): + return t if len(t) <= n else t[:n].rstrip() + "…" + + +def tile(x, y, w, h, title, items, accent, isize, sa, name_max): + shp(MSO_SHAPE.ROUNDED_RECTANGLE, x, y, w, h, fill=WHITE, adj=0.05) + txt(x + 0.22, y + 0.14, w - 0.44, 0.32, title, size=14, color=INK, bold=True, + anchor=MSO_ANCHOR.MIDDLE) + n = len([i for i in items if not i[1]]) + txt(x + w - 1.7, y + 0.18, 1.5, 0.24, "%d · %d nur hier" % (len(items), n), + size=8.5, color=SHARE, align=PP_ALIGN.RIGHT, font=FC, anchor=MSO_ANCHOR.MIDDLE) + shp(MSO_SHAPE.RECTANGLE, x + 0.22, y + 0.50, w - 0.44, 0.030, fill=accent) + + tb = s.shapes.add_textbox(Inches(x + 0.22), Inches(y + 0.62), Inches(w - 0.40), + Inches(h - 0.74)) + tf = tb.text_frame; tf.word_wrap = True; tf.vertical_anchor = MSO_ANCHOR.TOP + tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0) + for i, (name, others) in enumerate(items): + p = tf.paragraphs[0] if i == 0 else tf.add_paragraph() + p.alignment = PP_ALIGN.LEFT; p.line_spacing = 0.96; p.space_after = Pt(sa) + r = p.add_run(); r.font.name = FC; r.font.size = Pt(isize) + if others: + r.text = cut(name, name_max); r.font.bold = False; r.font.color.rgb = SHARE + r2 = p.add_run(); r2.text = " · auch: " + ", ".join(ABBR[o] for o in others) + r2.font.name = FC; r2.font.size = Pt(isize - 1); r2.font.color.rgb = SHARE + else: + r.text = cut(name, name_max + 4); r.font.bold = True; r.font.color.rgb = INK + + +# ---- Hintergrund + Header -------------------------------------------------- +shp(MSO_SHAPE.RECTANGLE, 0, 0, SW.inches, SH.inches, fill=BG) +chrome(s, "Funktionszuordnung je System", 9) +txt(0.42, 0.96, 12.5, 0.3, + "Dunkel/fett = nur dieses System · grau „· auch: …" + chr(34) + " = Funktion liegt auch in anderen Systemen", + size=11, color=LIGHT, anchor=MSO_ANCHOR.MIDDLE, font=FC) + +CY, CH = 1.46, 5.42 +COLS = [0.40, 3.58, 6.76, 9.94]; CW = 2.98 + +# Spalte 1: Integrationsserver (hoch) +tile(COLS[0], CY, CW, CH, "Integrationsserver", persys['Integrationsserver'], + CORAL, isize=9, sa=1.5, name_max=30) +# Spalte 2: Order Entry (hoch) +tile(COLS[1], CY, CW, CH, "Order Entry", persys['Order Entry'], + BLUE, isize=10.5, sa=5, name_max=30) + +# Spalten 3 & 4: je 3 kleine Kacheln +SH3 = 1.68; GAP = 0.19 +ys = [CY, CY + SH3 + GAP, CY + 2 * (SH3 + GAP)] +small3 = ['AB+M', 'Consens', 'R. LabOps'] +small4 = ['PVT', 'Sysmex', 'SAP'] +for name, y in zip(small3, ys): + tile(COLS[2], y, CW, SH3, name, persys[name], TEAL, isize=9.5, sa=3, name_max=30) +for name, y in zip(small4, ys): + tile(COLS[3], y, CW, SH3, name, persys[name], TEAL, isize=9.5, sa=3, name_max=30) + +out = os.path.join(HERE, "Folie8.pptx") +prs.save(out) +print("saved", out) diff --git a/folie9.py b/folie9.py new file mode 100644 index 0000000..8dc763e --- /dev/null +++ b/folie9.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +"""Folie 9 – Golden Master (adesso-Stil, deutsch, dunkelblau). +Was ist das? · Vorgehen (3 Schritte) · Nutzen 1 + Nutzen 2.""" + +import os +from pptx import Presentation +from pptx.util import Inches, Pt +from pptx.dml.color import RGBColor +from pptx.enum.text import PP_ALIGN, MSO_ANCHOR +from pptx.enum.shapes import MSO_SHAPE, MSO_CONNECTOR +from pptx.oxml.ns import qn +from adesso_style import (WHITE, INK, BLUE, TEAL, GREEN, CORAL, LIGHT, MUTE, RECF, + F, FC, chrome) + +BG = RGBColor(0x0A, 0x3D, 0x6B) + +prs = Presentation(); prs.slide_width = Inches(13.333); prs.slide_height = Inches(7.5) +SW, SH = prs.slide_width, prs.slide_height +s = prs.slides.add_slide(prs.slide_layouts[6]) + + +def shp(kind, x, y, w, h, fill=None, line=None, lw=Pt(1), adj=None): + sh = s.shapes.add_shape(kind, Inches(x), Inches(y), Inches(w), Inches(h)) + if fill is None: sh.fill.background() + else: sh.fill.solid(); sh.fill.fore_color.rgb = fill + if line is None: sh.line.fill.background() + else: sh.line.color.rgb = line; sh.line.width = lw + if adj is not None: + try: sh.adjustments[0] = adj + except Exception: pass + sh.shadow.inherit = False + return sh + + +def txt(x, y, w, h, t, size=12, color=WHITE, bold=False, italic=False, + align=PP_ALIGN.LEFT, font=F, anchor=MSO_ANCHOR.MIDDLE): + tb = s.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h)); tf = tb.text_frame + tf.word_wrap = True; tf.vertical_anchor = anchor + tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0) + first = True + for line in t.split("\n"): + p = tf.paragraphs[0] if first else tf.add_paragraph(); first = False + p.alignment = align + r = p.add_run(); r.text = line + r.font.size = Pt(size); r.font.bold = bold; r.font.italic = italic + r.font.name = font; r.font.color.rgb = color + return tb + + +def conn(x1, y1, x2, y2, color=LIGHT, w=2.0): + c = s.shapes.add_connector(MSO_CONNECTOR.STRAIGHT, Inches(x1), Inches(y1), + Inches(x2), Inches(y2)) + c.line.color.rgb = color; c.line.width = Pt(w); c.shadow.inherit = False + ln = c.line._get_or_add_ln() + ln.append(ln.makeelement(qn('a:tailEnd'), {'type': 'triangle'})) + return c + + +def heading(x, y, t): + shp(MSO_SHAPE.RECTANGLE, x, y + 0.05, 0.16, 0.20, fill=CORAL) + txt(x + 0.26, y - 0.04, 8.0, 0.34, t, size=15, color=WHITE, bold=True, + anchor=MSO_ANCHOR.MIDDLE) + + +def step(x, y, w, h, num, title, sub): + shp(MSO_SHAPE.ROUNDED_RECTANGLE, x, y, w, h, fill=WHITE, adj=0.08) + shp(MSO_SHAPE.OVAL, x + 0.16, y + 0.16, 0.42, 0.42, fill=BLUE) + txt(x + 0.16, y + 0.16, 0.42, 0.42, str(num), size=15, color=WHITE, bold=True, + align=PP_ALIGN.CENTER) + txt(x + 0.70, y + 0.14, w - 0.85, 0.34, title, size=13, color=INK, bold=True, + anchor=MSO_ANCHOR.TOP) + txt(x + 0.70, y + 0.50, w - 0.85, h - 0.60, sub, size=10.5, color=MUTE, + anchor=MSO_ANCHOR.TOP) + + +def nutzen(x, y, w, h, title, body): + shp(MSO_SHAPE.ROUNDED_RECTANGLE, x, y, w, h, fill=WHITE, adj=0.05) + shp(MSO_SHAPE.RECTANGLE, x, y + 0.16, 0.13, h - 0.32, fill=CORAL) + txt(x + 0.34, y + 0.20, w - 0.55, 0.40, title, size=15, color=INK, bold=True, + anchor=MSO_ANCHOR.TOP) + txt(x + 0.34, y + 0.70, w - 0.6, h - 0.85, body, size=12, color=INK, + anchor=MSO_ANCHOR.TOP) + + +# ---- Hintergrund + Header -------------------------------------------------- +shp(MSO_SHAPE.RECTANGLE, 0, 0, SW.inches, SH.inches, fill=BG) +chrome(s, "Golden Master", 10) + +# ---- Was ist das? ---------------------------------------------------------- +heading(0.50, 1.22, "Was ist das?") +shp(MSO_SHAPE.ROUNDED_RECTANGLE, 0.50, 1.58, 12.33, 0.82, fill=WHITE, adj=0.06) +txt(0.78, 1.58, 11.8, 0.82, + "Ein umfassender Acceptance-/Approval-Test der Software: Das System wird als Ganzes gegen einen " + "einmal erfassten, freigegebenen Referenz-Stand („Golden Master“) geprüft — weicht ein " + "Ergebnis ab, schlägt der Test an.", + size=12.5, color=INK) + +# ---- Vorgehen -------------------------------------------------------------- +heading(0.50, 2.64, "Vorgehen") +SY, SH_ = 3.00, 1.06 +step(0.50, SY, 3.70, SH_, 1, "Code- & Ergebnis-Analyse", + "Prüfen, wie die Software arbeitet und wie/wo Ergebnisse abgelegt werden.") +step(4.82, SY, 3.70, SH_, 2, "Testtool aufbauen", + "Werkzeug, das Referenz-Ergebnisse erfasst und bei jedem Lauf vergleicht.") +step(9.13, SY, 3.70, SH_, 3, "Test-Suite (Fachseite)", + "Die FS baut die fachlichen Testfälle / Szenarien auf.") +conn(4.24, SY + SH_ / 2, 4.78, SY + SH_ / 2) +conn(8.56, SY + SH_ / 2, 9.09, SY + SH_ / 2) + +# ---- Nutzen ---------------------------------------------------------------- +heading(0.50, 4.34, "Nutzen") +NY, NH = 4.70, 2.02 +nutzen(0.50, NY, 6.00, NH, "Nutzen 1 · Funktionssicherung CLAB", + "Nach jeder Änderung lässt sich die Funktionsfähigkeit der Software prüfen — " + "schnelle, wiederholbare Regressionssicherheit ohne manuelles Nachtesten.") +nutzen(6.83, NY, 6.00, NH, "Nutzen 2 · Vollständigkeit bei LIS-Migration", + "Ein Ziel-LIS, auf das migriert werden soll, lässt sich hart gegen die eigenen " + "Anforderungen testen.\n\n⚠ Setzt ein neues Aufsetzen des Testtools voraus.") + +out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Folie9.pptx") +prs.save(out) +print("saved", out) diff --git a/integration-server-new.drawio b/integration-server-new.drawio new file mode 100644 index 0000000..ed42931 --- /dev/null +++ b/integration-server-new.drawio @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/integration-server.drawio b/integration-server.drawio new file mode 100644 index 0000000..7924336 --- /dev/null +++ b/integration-server.drawio @@ -0,0 +1,474 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/integration-server_single.pdf b/integration-server_single.pdf new file mode 100644 index 0000000..a2a6fea Binary files /dev/null and b/integration-server_single.pdf differ diff --git a/integration-server_sngle.drawio b/integration-server_sngle.drawio new file mode 100644 index 0000000..cd502a8 --- /dev/null +++ b/integration-server_sngle.drawio @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/merge.py b/merge.py new file mode 100644 index 0000000..5f56f20 --- /dev/null +++ b/merge.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +"""Fügt Folie2, Folie3, Folie4 zu einer Datei zusammen (Reihenfolge 2, 3, 4).""" + +import copy +import os +from pptx import Presentation + +base = os.path.dirname(os.path.abspath(__file__)) + "/" +dst = Presentation(base + "Folie2.pptx") # Seite 1 = Folie 2 +blank = dst.slide_layouts[6] + +for fname in ("Folie3.pptx", "Folie3b.pptx", "Folie4.pptx"): # Seiten 2, 3, 4, 5 + src = Presentation(base + fname) + new_slide = dst.slides.add_slide(blank) + for shape in src.slides[0].shapes: + new_slide.shapes._spTree.append(copy.deepcopy(shape._element)) + +out = base + "Workshop_LIS.pptx" +dst.save(out) +print("saved", out)