diff --git a/.DS_Store b/.DS_Store
index befb89f..62ee9d4 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/limbach/.$RollsRoyce - Architektur - cad zentral.drawio.bkp b/limbach/.$RollsRoyce - Architektur - cad zentral.drawio.bkp
new file mode 100644
index 0000000..733dad4
--- /dev/null
+++ b/limbach/.$RollsRoyce - Architektur - cad zentral.drawio.bkp
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/limbach/.$RollsRoyce - Architektur.drawio.bkp b/limbach/.$RollsRoyce - Architektur.drawio.bkp
new file mode 100644
index 0000000..13d2222
--- /dev/null
+++ b/limbach/.$RollsRoyce - Architektur.drawio.bkp
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/limbach/.DS_Store b/limbach/.DS_Store
new file mode 100644
index 0000000..5008ddf
Binary files /dev/null and b/limbach/.DS_Store differ
diff --git a/limbach/.claude/settings.local.json b/limbach/.claude/settings.local.json
index 73a400e..6971739 100644
--- a/limbach/.claude/settings.local.json
+++ b/limbach/.claude/settings.local.json
@@ -5,7 +5,8 @@
"mcp__outline__update_document",
"mcp__outline__create_document",
"mcp__outline__list_documents",
- "mcp__outline__move_document"
+ "mcp__outline__move_document",
+ "Bash(python3 *)"
]
}
}
diff --git a/limbach/Anforderungskatalog_LIS_Fit-Gap v2.xlsx b/limbach/Anforderungskatalog_LIS_Fit-Gap v2.xlsx
new file mode 100644
index 0000000..c0caca3
Binary files /dev/null and b/limbach/Anforderungskatalog_LIS_Fit-Gap v2.xlsx differ
diff --git a/limbach/Folie10.pptx b/limbach/Folie10.pptx
new file mode 100644
index 0000000..0d37167
Binary files /dev/null and b/limbach/Folie10.pptx differ
diff --git a/limbach/Folie5.pptx b/limbach/Folie5.pptx
new file mode 100644
index 0000000..87efe3c
Binary files /dev/null and b/limbach/Folie5.pptx differ
diff --git a/limbach/Folie6.pptx b/limbach/Folie6.pptx
new file mode 100644
index 0000000..b25e8c1
Binary files /dev/null and b/limbach/Folie6.pptx differ
diff --git a/limbach/Folie7.pptx b/limbach/Folie7.pptx
new file mode 100644
index 0000000..a36ffd8
Binary files /dev/null and b/limbach/Folie7.pptx differ
diff --git a/limbach/Folie8.pptx b/limbach/Folie8.pptx
new file mode 100644
index 0000000..b907b44
Binary files /dev/null and b/limbach/Folie8.pptx differ
diff --git a/limbach/Folie9.pptx b/limbach/Folie9.pptx
new file mode 100644
index 0000000..7a46ce8
Binary files /dev/null and b/limbach/Folie9.pptx differ
diff --git a/limbach/aufwand.pptx b/limbach/aufwand.pptx
new file mode 100644
index 0000000..043867a
Binary files /dev/null and b/limbach/aufwand.pptx differ
diff --git a/limbach/folie10.py b/limbach/folie10.py
new file mode 100644
index 0000000..53242d1
--- /dev/null
+++ b/limbach/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/limbach/folie5.py b/limbach/folie5.py
new file mode 100644
index 0000000..cfc515a
--- /dev/null
+++ b/limbach/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/limbach/folie6.py b/limbach/folie6.py
new file mode 100644
index 0000000..ca460e4
--- /dev/null
+++ b/limbach/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/limbach/folie7.py b/limbach/folie7.py
new file mode 100644
index 0000000..2335fb0
--- /dev/null
+++ b/limbach/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/limbach/folie8.py b/limbach/folie8.py
new file mode 100644
index 0000000..8d5e94f
--- /dev/null
+++ b/limbach/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/limbach/folie9.py b/limbach/folie9.py
new file mode 100644
index 0000000..8dc763e
--- /dev/null
+++ b/limbach/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/limbach/integration-server-new.drawio b/limbach/integration-server-new.drawio
new file mode 100644
index 0000000..ed42931
--- /dev/null
+++ b/limbach/integration-server-new.drawio
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/limbach/integration-server_single.pdf b/limbach/integration-server_single.pdf
new file mode 100644
index 0000000..a2a6fea
Binary files /dev/null and b/limbach/integration-server_single.pdf differ
diff --git a/limbach/memory/MEMORY.md b/limbach/memory/MEMORY.md
index fbb5730..ca25fa5 100644
--- a/limbach/memory/MEMORY.md
+++ b/limbach/memory/MEMORY.md
@@ -1,3 +1,4 @@
- [Limbach Workshop / Outline](limbach-workshop-outline.md) — Wissensaufbau in Outline für Workshop 15./16.06.2026
- [adesso Styling](adesso-styling.md) — Corporate-Template-Quelle & Design-Tokens für Folien 2–4 (adesso_style.py)
- [Verbotene Wörter](verbotene-woerter.md) — „Pointe", „der Hit", „der Clou" & reißerische Wörter nie verwenden
+- [Integrationsserver Aufwand](integrationsserver-aufwand.md) — grobe Aufwandsschätzung Event-Sourcing-Integrationsserver (~300–530 PT)
diff --git a/limbach/memory/integrationsserver-aufwand.md b/limbach/memory/integrationsserver-aufwand.md
new file mode 100644
index 0000000..b7c2ec4
--- /dev/null
+++ b/limbach/memory/integrationsserver-aufwand.md
@@ -0,0 +1,34 @@
+---
+name: integrationsserver-aufwand
+description: "Grobe Aufwandsschätzung für den Event-Sourcing-Integrationsserver (Limbach, Stand 2026-06-18)"
+metadata:
+ node_type: memory
+ type: project
+ originSessionId: 3a4207d2-1bbd-40f0-9637-4230d2a92919
+---
+
+Grobe Order-of-Magnitude-Schätzung (KEIN Angebot) für das Aufsetzen eines **Event-Sourcing-Integrationsservers** (Kafka + Kafka Streams → PostgreSQL) mit den Systemen aus [[limbach-workshop-outline]] / Folie 7 (Order Entry über CLAB, Probeneingang [Atras SL + PVT], Geräte, Middleware, MiBi/Hybase, Stammdaten; Konsumenten SinaBI/Power BI, Archivierung/Vaultbox).
+
+**Gesamt grob: ~300–530 PT (≈ 15–27 Personenmonate; mit Team 4–6 ≈ 5–8 Monate Laufzeit).**
+
+Bausteine (PT):
+- Plattform-Foundation (Kafka, Schema Registry, Infra, CI/CD, Monitoring): 25–45
+- Core (Mapping/Transformation, Routing Engine, Projektions-Framework): 25–50
+- Adapter CLAB (LDT, bidirektional, zentraler Order-Pfad): 25–40
+- Adapter Probeneingang (Atras SL + PVT): 15–30
+- Adapter Geräte (Sysmex/Analyzer, HL7/ASTM, bidirektional): 20–35
+- Adapter Middleware (Myla/Maestria, LabOps, Liason): 25–45
+- Adapter MiBi/Hybase: 15–25
+- Adapter Stammdaten (SinaCRM, OTS/Znooni) + MDM/Dedup: 20–35
+- Projektionen/Read-Models (Auftragsstatus, Audit Trail, Reporting): 15–30
+- Konsumenten (SinaBI/Power BI, Archivierung/Vaultbox): 12–25
+- Golden-Master-Testharness: 10–20
+- Security/Auth/Compliance (Audit-Trail, DSGVO, Rollen, RiliBÄK-Doku): 15–30
+- + Architektur/PM/Test-Mgmt/Doku/Rollout (~25–30 %)
+
+**MVP-Schnitt** (Plattform + CLAB + Geräte + 1 Projektion + Golden Master): ~100–160 PT (~3–4 Monate).
+
+Größte Kostentreiber/Risiken: Verfügbarkeit & Qualität der Schnittstellen je System (LDT/HL7/ASTM/native, API vorhanden?); Bidirektionalität (Order-Rückweg); Stammdaten-Dedup; Parallelbetrieb/Migration/Replay; Regulatorik (RiliBÄK, ISO 15189, IfSG, DSGVO); Abhängigkeit von Limbach-/Drittanbieter-Ressourcen (CLAB-LDT, Vaultbox/LDT2).
+
+**Why:** Im Workshop kam die Frage nach dem ungefähren Aufwand; die Zahlen sind die abgestimmte grobe Hausnummer.
+**How to apply:** Als Ausgangspunkt für Aufwands-/Roadmap-Diskussionen nutzen; bei Bedarf in PT, Personenmonaten oder (mit Tagessatz) als €-Spanne darstellen. Vor jeder Nutzung an konkreten Schnittstellen-Infos je System schärfen. Nutzer wollte daraus ausdrücklich KEINE Folie.