"""Pusht Team-Reorganisation als Confluence-Seite.""" import urllib.request, json, ssl, urllib.parse from pathlib import Path secrets = {} with open("project-audit/.secrets", "r", encoding="utf-8-sig") as f: for line in f: if "=" in line and not line.startswith("#"): k, v = line.strip().split("=", 1) secrets[k.strip()] = v.strip() TOKEN = secrets.get("CONFLUENCE_TOKEN", "") BASE = "https://arija-confluence.jaas.service.deutschebahn.com/rest/api" SPACE = "BES" PARENT = "581013135" TITLE = "25. Team-Reorganisation (Optionen)" ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE # Read the markdown and convert key sections to HTML html = """

Team-Reorganisation pathOS — Optionen und Bewertung

Stand: 2026-04-30 | Status: Entwurf zur Diskussion

Dieses Dokument analysiert Optionen fuer die Neuaufstellung der pathOS-Teams. Ziele: Schnellere Umsetzung, weniger Komplexitaet, bessere Qualitaet und Verantwortlichkeit.

Ausgangslage

TeamPersonenVerantwortungServicesProblem
Team 404~10Portal UI + Middleware2 (68K LoC)33% Bug-Rate, steigend
Team CIB~13Prozesse, Backend, Camunda~15 (122K LoC)SV-Monolith (93K, 2214 Smells)
Team Zero~9TAF/TAP Schnittstellen~9 (38K LoC)OPs-Rotation belastet
OPs Squad2 fest + rot.Infrastruktur, BetriebInfra-ReposNur 2 feste Mitglieder
DevOps~7CI/CD, ToolingPipelinesJan Lubenow = 39% SPOF

Ziele

PrimaerNachrangig
Schnelle UmsetzungsgeschwindigkeitStaerkenorientierter Einsatz
Weniger KomplexitaetWissen breiter verteilen
Weniger AbhaengigkeitenFlexibilitaet bei Prioritaetswechsel
Bessere Qualitaet
Bessere Verantwortlichkeit

Option A: OpsDev + Feature-Pool

Modell

OpsDev Team (permanent, ~8-10): Betrieb, Bugs, kleine Features. Lead OpsDev ohne PO.

Feature-Pool (~20-25): Entwickler + BAs + Feature-POs. Bilden temporaere Feature-Teams (3-5 Pers.) fuer grosse Features. Nach Abschluss: 1 Dev → OpsDev (Hypercare).

VorteileNachteile
Feature-Team ownt end-to-endKontextwechsel, Onboarding-Aufwand
Keine Cross-Team-Deps fuer FeaturesKein stabiles Team, Teambildung leidet
Flexibel nach PrioritaetOpsDev wird Muellhalde fuer Bugs
Wissenstransfer durch HypercarePO-Overhead (3-4 POs parallel)

Option B: Fachlicher Schnitt (4 Varianten)

B1: Bestellen vs. Abwickeln

Team "Bestellen"Team "Abwickeln"
Portal UI + MW, Common Interface, Stammdaten, Kundendaten, TAF/TAP-KonverterSteuerung Vertrieb (Camunda), Auftrags-Verwaltung, IFP-Connector, Vertragsdaten, Archivierung, Abrechnung
Fokus: Was der Kunde siehtFokus: Was nach der Bestellung passiert

✔ Klarer Kundenfokus | ✘ NAÄ betrifft beide, SV ist Monolith

B4: Trasse vs. Vertrag (bester fachlicher Schnitt)

Team "Trasse"Team "Vertrag"
Trassenanmeldung (Portal+CI), Trassenkonstruktion (IFP), Stammdaten, TAF/TAPAngebot + Vertrag (SV), Abrechnung, Stornierung, Rahmenvertraege, Vertragsdaten
Vom Kundenwunsch bis KonstruktionsauftragVom Angebot bis zur Rechnung

✔ Sauberster Schnitt entlang Geschaeftsprozess, Abrechnung hat eigenes Team | ✘ SV muesste aufgeteilt werden, Portal zeigt beides

Option C: Hybrid — EMPFOHLEN

Empfohlenes Modell: OpsDev (permanent) + 2 fachliche Teams (permanent) + temporaere Feature-Squads fuer grosse Themen.

OpsDev (~6 Pers.)Team "Bestellen" (~12)Team "Verarbeiten" (~12)
Betrieb, Deployment
Monitoring, Infrastruktur
Bug-Triage
Lead: OpsDev-Lead
Portal UI + MW
Common Interface
Stammdaten, Kundendaten
TAF/TAP Konverter
Click&Ride
PO + BA + Devs
Steuerung Vertrieb
Auftrags-Verwaltung
IFP-Connector
Archivierung
Vertragsdaten, Abrechnung
PO + BA + Devs
Bugs: InfrastrukturBugs: Portal, CI, STBBugs: SV, AV, IFP

Fuer grosse Features (GelV, ujBau, NAÄ): Temporaer 2-3 Personen aus beiden Teams zusammenziehen. Nach Abschluss: zurueck + 1 Person Hypercare in OpsDev.

Gesamtbewertung

OptionSpeedKomplexitaetDepsQualitaetOwnership
A: OpsDev + Pool⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
B1: Bestellen/Abwickeln⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
B4: Trasse/Vertrag⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
C: Hybrid (empfohlen)⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
D: Spotify⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

Noch zu klaeren

  1. SV aufteilen? — Ist der Monolith (93K LoC) technisch teilbar?
  2. NAÄ als Querschnitt — Dediziertes Feature-Team oder feste Zuordnung?
  3. Portal-Ownership — Portal zeigt Daten aus allen Services. Eigener Querschnitt?
  4. Abrechnung — Eigenes Team wert? Oder bei "Verarbeiten"?
  5. Personelle Passung — Staerken-Mapping der 35 Personen
  6. Uebergangsphase — Dauer, Produktivitaetsverlust?
  7. PO-Struktur — 1 PO pro Team oder uebergreifend + Feature-POs?
  8. Metriken — Bug-Rate, Durchlaufzeit, Deployment-Frequenz als Erfolgsmessung

Fehlende Daten fuer Entscheidung

""" # Push to Confluence def api_request(url, method="GET", data=None): req = urllib.request.Request(url, method=method) req.add_header("Authorization", f"Bearer {TOKEN}") req.add_header("Content-Type", "application/json") if data: req.data = json.dumps(data).encode("utf-8") return json.loads(urllib.request.urlopen(req, context=ctx, timeout=30).read().decode("utf-8")) # Check if page exists search_url = f"{BASE}/content?spaceKey={SPACE}&title={urllib.parse.quote(TITLE)}&type=page" req = urllib.request.Request(search_url) req.add_header("Authorization", f"Bearer {TOKEN}") resp = urllib.request.urlopen(req, context=ctx, timeout=30) search_data = json.loads(resp.read().decode("utf-8")) if search_data.get("results"): # Update page_id = search_data["results"][0]["id"] ver_url = f"{BASE}/content/{page_id}?expand=version" req2 = urllib.request.Request(ver_url) req2.add_header("Authorization", f"Bearer {TOKEN}") ver_data = json.loads(urllib.request.urlopen(req2, context=ctx, timeout=30).read().decode("utf-8")) version = ver_data["version"]["number"] + 1 body = json.dumps({"version": {"number": version}, "title": TITLE, "type": "page", "body": {"storage": {"value": html, "representation": "storage"}}}).encode("utf-8") req3 = urllib.request.Request(f"{BASE}/content/{page_id}", method="PUT", data=body) req3.add_header("Authorization", f"Bearer {TOKEN}") req3.add_header("Content-Type", "application/json") result = json.loads(urllib.request.urlopen(req3, context=ctx, timeout=30).read().decode("utf-8")) print(f"Aktualisiert: https://arija-confluence.jaas.service.deutschebahn.com/pages/viewpage.action?pageId={result['id']}") else: # Create body = json.dumps({"type": "page", "title": TITLE, "space": {"key": SPACE}, "ancestors": [{"id": PARENT}], "body": {"storage": {"value": html, "representation": "storage"}}}).encode("utf-8") req3 = urllib.request.Request(f"{BASE}/content", method="POST", data=body) req3.add_header("Authorization", f"Bearer {TOKEN}") req3.add_header("Content-Type", "application/json") result = json.loads(urllib.request.urlopen(req3, context=ctx, timeout=30).read().decode("utf-8")) print(f"Erstellt: https://arija-confluence.jaas.service.deutschebahn.com/pages/viewpage.action?pageId={result['id']}")