Files
Orchestrator/bahn/project-audit/scripts/confluence-push-teamreorg.py
ankn a5f8fb49ab Migrate all repos into monorepo context folders
Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
      Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
      Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)

Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
2026-06-30 20:39:52 +02:00

162 lines
10 KiB
Python

"""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 = """<h2>Team-Reorganisation pathOS &#8212; Optionen und Bewertung</h2>
<p><strong>Stand:</strong> 2026-04-30 | Status: Entwurf zur Diskussion</p>
<ac:structured-macro ac:name="info"><ac:rich-text-body>
<p>Dieses Dokument analysiert Optionen fuer die Neuaufstellung der pathOS-Teams. Ziele: Schnellere Umsetzung, weniger Komplexitaet, bessere Qualitaet und Verantwortlichkeit.</p>
</ac:rich-text-body></ac:structured-macro>
<h3>Ausgangslage</h3>
<table>
<tr><th>Team</th><th>Personen</th><th>Verantwortung</th><th>Services</th><th>Problem</th></tr>
<tr><td>Team 404</td><td>~10</td><td>Portal UI + Middleware</td><td>2 (68K LoC)</td><td>33% Bug-Rate, steigend</td></tr>
<tr><td>Team CIB</td><td>~13</td><td>Prozesse, Backend, Camunda</td><td>~15 (122K LoC)</td><td>SV-Monolith (93K, 2214 Smells)</td></tr>
<tr><td>Team Zero</td><td>~9</td><td>TAF/TAP Schnittstellen</td><td>~9 (38K LoC)</td><td>OPs-Rotation belastet</td></tr>
<tr><td>OPs Squad</td><td>2 fest + rot.</td><td>Infrastruktur, Betrieb</td><td>Infra-Repos</td><td>Nur 2 feste Mitglieder</td></tr>
<tr><td>DevOps</td><td>~7</td><td>CI/CD, Tooling</td><td>Pipelines</td><td>Jan Lubenow = 39% SPOF</td></tr>
</table>
<h3>Ziele</h3>
<table>
<tr><th>Primaer</th><th>Nachrangig</th></tr>
<tr><td>Schnelle Umsetzungsgeschwindigkeit</td><td>Staerkenorientierter Einsatz</td></tr>
<tr><td>Weniger Komplexitaet</td><td>Wissen breiter verteilen</td></tr>
<tr><td>Weniger Abhaengigkeiten</td><td>Flexibilitaet bei Prioritaetswechsel</td></tr>
<tr><td>Bessere Qualitaet</td><td></td></tr>
<tr><td>Bessere Verantwortlichkeit</td><td></td></tr>
</table>
<h3>Option A: OpsDev + Feature-Pool</h3>
<ac:structured-macro ac:name="panel"><ac:parameter ac:name="title">Modell</ac:parameter><ac:rich-text-body>
<p><strong>OpsDev Team</strong> (permanent, ~8-10): Betrieb, Bugs, kleine Features. Lead OpsDev ohne PO.</p>
<p><strong>Feature-Pool</strong> (~20-25): Entwickler + BAs + Feature-POs. Bilden temporaere Feature-Teams (3-5 Pers.) fuer grosse Features. Nach Abschluss: 1 Dev &#8594; OpsDev (Hypercare).</p>
</ac:rich-text-body></ac:structured-macro>
<table>
<tr><th>Vorteile</th><th>Nachteile</th></tr>
<tr><td>Feature-Team ownt end-to-end</td><td>Kontextwechsel, Onboarding-Aufwand</td></tr>
<tr><td>Keine Cross-Team-Deps fuer Features</td><td>Kein stabiles Team, Teambildung leidet</td></tr>
<tr><td>Flexibel nach Prioritaet</td><td>OpsDev wird Muellhalde fuer Bugs</td></tr>
<tr><td>Wissenstransfer durch Hypercare</td><td>PO-Overhead (3-4 POs parallel)</td></tr>
</table>
<h3>Option B: Fachlicher Schnitt (4 Varianten)</h3>
<h4>B1: Bestellen vs. Abwickeln</h4>
<table>
<tr><th>Team "Bestellen"</th><th>Team "Abwickeln"</th></tr>
<tr><td>Portal UI + MW, Common Interface, Stammdaten, Kundendaten, TAF/TAP-Konverter</td><td>Steuerung Vertrieb (Camunda), Auftrags-Verwaltung, IFP-Connector, Vertragsdaten, Archivierung, Abrechnung</td></tr>
<tr><td><em>Fokus: Was der Kunde sieht</em></td><td><em>Fokus: Was nach der Bestellung passiert</em></td></tr>
</table>
<p>&#10004; Klarer Kundenfokus | &#10008; NAÄ betrifft beide, SV ist Monolith</p>
<h4>B4: Trasse vs. Vertrag (bester fachlicher Schnitt)</h4>
<table>
<tr><th>Team "Trasse"</th><th>Team "Vertrag"</th></tr>
<tr><td>Trassenanmeldung (Portal+CI), Trassenkonstruktion (IFP), Stammdaten, TAF/TAP</td><td>Angebot + Vertrag (SV), Abrechnung, Stornierung, Rahmenvertraege, Vertragsdaten</td></tr>
<tr><td><em>Vom Kundenwunsch bis Konstruktionsauftrag</em></td><td><em>Vom Angebot bis zur Rechnung</em></td></tr>
</table>
<p>&#10004; Sauberster Schnitt entlang Geschaeftsprozess, Abrechnung hat eigenes Team | &#10008; SV muesste aufgeteilt werden, Portal zeigt beides</p>
<h3>Option C: Hybrid &#8212; EMPFOHLEN</h3>
<ac:structured-macro ac:name="tip"><ac:rich-text-body>
<p><strong>Empfohlenes Modell:</strong> OpsDev (permanent) + 2 fachliche Teams (permanent) + temporaere Feature-Squads fuer grosse Themen.</p>
</ac:rich-text-body></ac:structured-macro>
<table>
<tr><th>OpsDev (~6 Pers.)</th><th>Team "Bestellen" (~12)</th><th>Team "Verarbeiten" (~12)</th></tr>
<tr><td>Betrieb, Deployment<br/>Monitoring, Infrastruktur<br/>Bug-Triage<br/>Lead: OpsDev-Lead</td><td>Portal UI + MW<br/>Common Interface<br/>Stammdaten, Kundendaten<br/>TAF/TAP Konverter<br/>Click&amp;Ride<br/>PO + BA + Devs</td><td>Steuerung Vertrieb<br/>Auftrags-Verwaltung<br/>IFP-Connector<br/>Archivierung<br/>Vertragsdaten, Abrechnung<br/>PO + BA + Devs</td></tr>
<tr><td>Bugs: Infrastruktur</td><td>Bugs: Portal, CI, STB</td><td>Bugs: SV, AV, IFP</td></tr>
</table>
<p><strong>Fuer grosse Features</strong> (GelV, ujBau, NAÄ): Temporaer 2-3 Personen aus beiden Teams zusammenziehen. Nach Abschluss: zurueck + 1 Person Hypercare in OpsDev.</p>
<h3>Gesamtbewertung</h3>
<table>
<tr><th>Option</th><th>Speed</th><th>Komplexitaet</th><th>Deps</th><th>Qualitaet</th><th>Ownership</th></tr>
<tr><td>A: OpsDev + Pool</td><td>&#11088;&#11088;&#11088;&#11088;</td><td>&#11088;&#11088;</td><td>&#11088;&#11088;&#11088;&#11088;&#11088;</td><td>&#11088;&#11088;&#11088;</td><td>&#11088;&#11088;&#11088;</td></tr>
<tr><td>B1: Bestellen/Abwickeln</td><td>&#11088;&#11088;&#11088;</td><td>&#11088;&#11088;&#11088;</td><td>&#11088;&#11088;&#11088;</td><td>&#11088;&#11088;&#11088;</td><td>&#11088;&#11088;&#11088;&#11088;</td></tr>
<tr><td>B4: Trasse/Vertrag</td><td>&#11088;&#11088;&#11088;</td><td>&#11088;&#11088;&#11088;&#11088;</td><td>&#11088;&#11088;&#11088;&#11088;</td><td>&#11088;&#11088;&#11088;&#11088;</td><td>&#11088;&#11088;&#11088;&#11088;</td></tr>
<tr style="background:#d5e8d4;"><td><strong>C: Hybrid (empfohlen)</strong></td><td>&#11088;&#11088;&#11088;&#11088;</td><td>&#11088;&#11088;&#11088;&#11088;</td><td>&#11088;&#11088;&#11088;</td><td>&#11088;&#11088;&#11088;&#11088;</td><td>&#11088;&#11088;&#11088;&#11088;&#11088;</td></tr>
<tr><td>D: Spotify</td><td>&#11088;&#11088;&#11088;</td><td>&#11088;</td><td>&#11088;&#11088;&#11088;</td><td>&#11088;&#11088;&#11088;</td><td>&#11088;&#11088;&#11088;</td></tr>
</table>
<h3>Noch zu klaeren</h3>
<ol>
<li><strong>SV aufteilen?</strong> &#8212; Ist der Monolith (93K LoC) technisch teilbar?</li>
<li><strong>NAÄ als Querschnitt</strong> &#8212; Dediziertes Feature-Team oder feste Zuordnung?</li>
<li><strong>Portal-Ownership</strong> &#8212; Portal zeigt Daten aus allen Services. Eigener Querschnitt?</li>
<li><strong>Abrechnung</strong> &#8212; Eigenes Team wert? Oder bei "Verarbeiten"?</li>
<li><strong>Personelle Passung</strong> &#8212; Staerken-Mapping der 35 Personen</li>
<li><strong>Uebergangsphase</strong> &#8212; Dauer, Produktivitaetsverlust?</li>
<li><strong>PO-Struktur</strong> &#8212; 1 PO pro Team oder uebergreifend + Feature-POs?</li>
<li><strong>Metriken</strong> &#8212; Bug-Rate, Durchlaufzeit, Deployment-Frequenz als Erfolgsmessung</li>
</ol>
<h3>Fehlende Daten fuer Entscheidung</h3>
<ul>
<li>Git-Contributions pro Person/Service (wer kennt welchen Code?)</li>
<li>Abhaengigkeits-Graph zwischen Services (Kafka-Topics, REST-Calls)</li>
<li>Kundenfeedback: Welche Features werden am meisten nachgefragt?</li>
<li>Camunda-Prozess-Instanzen: Wie oft laeuft welcher Prozess?</li>
</ul>
"""
# 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']}")