#!/usr/bin/env python3 """Generiert die Board-Layout-Skizze (SVG) fuer den SLC-Workshop. Lineares Phasen-Swimlane-Layout: jede Phase eine Zeile, Pucks links->rechts. Exakt 40 Pucks (37 Aktivitaeten + 3 Gate-Pucks). Reproduzierbar: bei Aenderungen einfach erneut ausfuehren -> board-layout.svg. """ import math # (id, kurzname, is_gate) PHASES = [ ("DESIGN", "#2F80C9", [ ("ds_01", "Eigenschaften definieren", False), ("ds_02", "Komponenten designen", False), ("ds_03", "Vorgehen beschreiben", False), ("ds_04", "Implementierung vorbereiten", False), ]), ("TRANSITION", "#E8893B", [ ("tr_01", "Entw. / Konfig.?", True), ("tr_02", "Entwicklung koordinieren", False), ("tr_03", "Anwendungen entwickeln", False), ("tr_04", "Komponenten annehmen", False), ("tr_05", "Komponenten konfigurieren", False), ("tr_06", "Betriebsdoku erstellen", False), ("tr_07", "Komponenten testen", False), ("tr_08", "Formale Uebergabe", False), ("tr_09", "Entry-Pruefung", True), ("tr_10", "Ausrollen", False), ("tr_11", "Aktivierung vorbereiten", False), ("tr_12", "Go-Live-Freigabe", True), ]), ("OPERATION", "#5BAE5B", [ ("op_01", "Early Life Support", False), ("op_02", "Betriebs-Leitlinien", False), ("op_03", "Laufender Betrieb", False), ("op_04", "Ressourcen & Budget", False), ("op_05", "Services ueberwachen", False), ("op_06", "Qualitaetsbericht", False), ("op_07", "Proaktive Problemerkennung", False), ]), ("SUPPORT", "#3FB5B5", [ ("sp_01", "Support-Leitlinien", False), ("sp_02", "Wissensdatenbank", False), ("sp_03", "Incidents/Requests verteilen", False), ("sp_04", "Requests bearbeiten", False), ("sp_05", "Incident 1st Level", False), ("sp_06", "Incident 2nd Level", False), ("sp_07", "Record geloest", False), ("sp_08", "Schliessen", False), ("sp_09", "Problem Record anlegen", False), ("sp_10", "Wiederk. Incidents -> Problem", False), ("sp_11", "RCA & Workaround", False), ]), ("REVIEW", "#8E63B5", [ ("rv_01", "Service-Reviews durchf.", False), ("rv_02", "Bewertung d. Ergebnisse", False), ("rv_03", "Aenderungen definieren", False), ("rv_04", "Aenderungen starten", False), ("rv_05", "Aenderungen umsetzen", False), ]), # Arbeitsstand Frank (Change-Enablement); nicht im YAML/Konzept ] # Layout-Parameter TILE_W, TILE_H = 86, 86 # Zelle je Puck (rund, inscribed) GAP_X, GAP_Y = 12, 40 PUCK_R = 35 # Puck-Radius in px (= Ø100 mm) LABEL_W = 150 X0 = 30 + LABEL_W + 20 Y0 = 96 MAX_TILES = max(len(t) for _, _, t in PHASES) WIDTH = X0 + MAX_TILES * (TILE_W + GAP_X) + 200 HEIGHT = Y0 + len(PHASES) * (TILE_H + GAP_Y) + 120 TILE_MM = 100 # ein Puck = Ø100 mm def esc(s): return s.replace("&", "&").replace("<", "<").replace(">", ">") def lighten(hexcol, f=0.85): h = hexcol.lstrip("#") r, g, b = int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16) r = int(r + (255 - r) * f) g = int(g + (255 - g) * f) b = int(b + (255 - b) * f) return f"#{r:02x}{g:02x}{b:02x}" def tile_svg(x, y, tid, name, color, is_gate): """Zeichnet einen runden Puck: Aussenring, 7 Figurenmulden, zentrales Etikett.""" cx, cy = x + TILE_W / 2.0, y + TILE_H / 2.0 fill = color if is_gate else lighten(color, 0.90) stroke = color sw = 3 if is_gate else 2 parts = [] # Puck-Koerper parts.append(f'') # 7 Figurenmulden im Ring for k in range(7): a = math.radians(360.0 / 7 * k - 90) wx = cx + (PUCK_R - 8) * math.cos(a) wy = cy + (PUCK_R - 8) * math.sin(a) parts.append(f'') # zentrales Etikett-Feld parts.append(f'') parts.append(f'{esc(tid)}') # Name unter dem Puck parts.append(f'{esc(name)}') if is_gate: parts.append(f'GATE') return "\n".join(parts) def arrow(x1, y1, x2, y2, color="#666", w=2.2): return (f'') svg = [] svg.append(f'') svg.append(f'') svg.append('' '') # Titel svg.append(f'' f'Service-Lifecycle — Board-Layout (39 Pucks)') svg.append(f'' f'36 Aktivitaeten + 3 Gate-Pucks · 1 Puck = Ø{TILE_MM} mm · ' f'lose Bahn, Sequenz links nach rechts') row_y = {} for ri, (pname, color, tiles) in enumerate(PHASES): y = Y0 + ri * (TILE_H + GAP_Y) row_y[pname] = y # Phasen-Label svg.append(f'') svg.append(f'{esc(pname)}') svg.append(f'{len(tiles)} Pucks') # Tiles prev = None for ti, (tid, name, is_gate) in enumerate(tiles): x = X0 + ti * (TILE_W + GAP_X) if prev is not None: svg.append(arrow(prev + 8, y + TILE_H/2, x - 2, y + TILE_H/2)) svg.append(tile_svg(x, y, tid, name, color, is_gate)) prev = x + TILE_W # Connector zur naechsten Phase (von letztem Tile runter zur naechsten Zeile Start) if ri < len(PHASES) - 1: lastx = X0 + (len(tiles) - 1) * (TILE_W + GAP_X) + TILE_W/2 ny = y + TILE_H + GAP_Y svg.append(f'') # Operation <-> Support Loop (links neben den Labels) oy = row_y["OPERATION"] + TILE_H/2 sy = row_y["SUPPORT"] + TILE_H/2 svg.append(f'') svg.append(f'Betriebs-Loop') # Exit nach Review (DPM-Ruecklauf) ry = row_y["REVIEW"] + TILE_H/2 rx = X0 + (len(PHASES[-1][2]) - 1) * (TILE_W + GAP_X) + TILE_W svg.append(arrow(rx + 6, ry, rx + 70, ry, color="#8E63B5", w=2.6)) svg.append(f'zurueck in DPM') svg.append(f'' f'rv_05 Redesign / rv_06 Retirement') # Legende / Massstab ly = HEIGHT - 64 svg.append(f'') svg.append(f'Gate-Puck (rot, Etikett G1/G2/G3 + Icon)') svg.append(f'') svg.append(f'Station-Puck (Ø100, 7 Figurenmulden + Etikett)') # Gesamtbreite-Hinweis total_mm = MAX_TILES * (TILE_MM + 10) svg.append(f'' f'Breiteste Phase: {MAX_TILES} Pucks ~ {total_mm/10:.0f} cm ' f'(bei Ø{TILE_MM} mm Pucks + ~10 mm Abstand). Bahn bei Platzmangel maeandrierend.') svg.append('') out = "board-layout.svg" with open(out, "w", encoding="utf-8") as f: f.write("\n".join(svg)) total = sum(len(t) for _, _, t in PHASES) gates = sum(1 for _, _, t in PHASES for _, _, g in t if g) print(f"geschrieben: {out}") print(f"Pucks gesamt: {total} (Aktivitaeten: {total-gates}, Gate-Pucks: {gates})")