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.
This commit is contained in:
2026-06-30 20:39:52 +02:00
parent 2f2b295531
commit a5f8fb49ab
1717 changed files with 447332 additions and 0 deletions
@@ -0,0 +1,312 @@
"""
Extracts IT-relevant text passages from FuBen PDFs (IWF 2-8) using PyMuPDF.
Outputs a markdown table with OE, IT-relevant passage, and assessment.
Version 2: Better sentence extraction and more focused passages.
"""
import fitz # PyMuPDF
import os
import re
from pathlib import Path
# Paths
DATA_DIR = Path(r"c:\Users\AndreKnie\OneDrive - Deutsche Bahn\Coden\Orchestrator\Analyse-O2C-C2S\data\FuBen")
OUTPUT_FILE = Path(r"c:\Users\AndreKnie\OneDrive - Deutsche Bahn\Coden\Orchestrator\Analyse-O2C-C2S\analysis\2026-06-18-fuben-it-analyse.md")
# Keywords to search for (case-insensitive)
KEYWORDS = [
r'\bIT[-\s]',
r'\bIT\b',
r'\bSoftware\b',
r'\bAnwendung(?:en)?\b',
r'\bSystem(?:e|en|s)?\b',
r'\bDigital(?:e|er|es|en|isierung)?\b',
r'\bTool(?:s)?\b',
r'\bPlattform(?:en)?\b',
r'\bPortal(?:e|s)?\b',
r'\bDatenbank(?:en)?\b',
r'\bAutomatisier(?:ung|t|en)?\b',
r'\bDaten(?:management|pflege|qualität|bereitstellung|verarbeitung|modell)?\b',
r'\bSchnittstelle(?:n)?\b',
r'\bSAP\b',
r'\bERP\b',
r'\bApplikation(?:en)?\b',
r'\belektronisch(?:e|er|es|en)?\b',
]
# Compile combined pattern
KEYWORD_PATTERN = re.compile('|'.join(KEYWORDS), re.IGNORECASE)
# IWF number extraction from filename
IWF_PATTERN = re.compile(r'V\.IWF\s+(\d+)')
def get_iwf_number(filename: str) -> str:
"""Extract IWF number from filename."""
match = IWF_PATTERN.search(filename)
if match:
return match.group(1)
return ""
def is_relevant_iwf(iwf_num: str) -> bool:
"""Check if IWF is in range 2-8 (including sub-units like 21, 221, etc.)."""
if not iwf_num:
return False
first_digit = iwf_num[0]
return first_digit in '2345678'
def extract_text_from_pdf(pdf_path: Path) -> str:
"""Extract full text from a PDF using PyMuPDF."""
text = ""
try:
doc = fitz.open(str(pdf_path))
for page in doc:
text += page.get_text()
doc.close()
except Exception as e:
print(f"Error reading {pdf_path.name}: {e}")
return text
def split_into_items(text: str) -> list[str]:
"""Split FuBen text into individual task items (■ bullet points and sentences)."""
items = []
# Split by ■ bullet points (common in FuBen) - these are the primary structure
if '' in text:
parts = text.split('')
for part in parts:
cleaned = part.strip()
cleaned = re.sub(r'\s+', ' ', cleaned)
if len(cleaned) > 15:
items.append(cleaned)
else:
# Fallback: split by newlines and combine short lines
lines = text.split('\n')
current = ""
for line in lines:
line = line.strip()
if not line or len(line) < 3:
if current and len(current) > 15:
items.append(re.sub(r'\s+', ' ', current))
current = ""
continue
if current:
current += " " + line
else:
current = line
if current and len(current) > 15:
items.append(re.sub(r'\s+', ' ', current))
return items
def find_it_passages(text: str) -> list[str]:
"""Find sentences/bullet points containing IT-relevant keywords."""
passages = []
seen = set()
items = split_into_items(text)
for item in items:
if KEYWORD_PATTERN.search(item):
# Skip header/metadata lines
if item.startswith('Funktionsbeschreibung V.IWF'):
continue
if 'Seite' in item and 'von insgesamt' in item:
continue
if item.startswith('ETO2024'):
continue
# Truncate to a reasonable length showing the relevant part
if len(item) > 300:
# Find where the keyword is and show context around it
match = KEYWORD_PATTERN.search(item)
if match:
start = max(0, match.start() - 80)
end = min(len(item), match.end() + 220)
item = ("..." if start > 0 else "") + item[start:end] + ("..." if end < len(item) else "")
if item not in seen:
seen.add(item)
passages.append(item)
return passages
def assess_passage(passage: str) -> str:
"""Assess whether a passage describes a task that should move to IT department."""
passage_lower = passage.lower()
# Strong indicators for IT responsibility
it_strong = [
'software', 'anwendung', 'programmier', 'datenbank', 'portal',
'plattform', 'digital', 'it-system', 'it-lösung', 'it-infrastruktur',
'it-anforder', 'it-anwend', 'it-unterstütz',
'schnittstelle', 'automatisier', 'tool', 'applikation',
'datenmanagement', 'datenpflege', 'datenbereitstellung',
'datenverarbeitung', 'datenmodell', 'datenqualität',
'elektronisch', 'server', 'cloud', 'api',
'webseite', 'website', 'netzwerk',
'berechtig', 'zugriff', 'login', 'passwort',
'it-sicherheit', 'cybersecurity', 'datenschutz',
'konfiguration', 'implementierung', 'migration',
'sap', 'erp', 'crm', 'testing', 'abnahme',
'eingabeunterstützung', 'nutzerführung',
]
# Indicators that suggest operational/business ownership (not pure IT)
business_indicators = [
'fachlich', 'prozesssteuerung', 'steuerung der',
'strateg', 'führung', 'koordin',
'qualitätssicherung', 'anforderungsmanagement',
]
strong_count = sum(1 for term in it_strong if term in passage_lower)
business_count = sum(1 for term in business_indicators if term in passage_lower)
if strong_count >= 2:
return "✅ Ja klare IT-Aufgabe"
elif strong_count >= 1 and business_count == 0:
return "✅ Ja IT-Aufgabe"
elif strong_count >= 1 and business_count >= 1:
return "⚠️ Teilweise IT-Unterstützung nötig, fachliche Verantwortung bleibt"
elif 'system' in passage_lower and business_count == 0:
return "🔍 Prüfen möglicherweise IT-relevant (Systembezug)"
elif 'system' in passage_lower and business_count >= 1:
return "⚠️ Teilweise Systemnutzung mit fachlichem Kontext"
else:
return "🔍 Prüfen Kontext unklar"
def get_oe_label(iwf_num: str, text: str) -> str:
"""Extract OE label from PDF text."""
# Look for the title line after "Funktionsbeschreibung V.IWF XX"
pattern = re.compile(rf'Funktionsbeschreibung\s+V\.IWF\s+{re.escape(iwf_num)}\s*\n(.+?)(?:\n|Zielsetzung)', re.DOTALL)
match = pattern.search(text)
if match:
name = match.group(1).strip()
name = re.sub(r'\s+', ' ', name)
if len(name) > 5 and len(name) < 100:
return f"IWF {iwf_num} {name}"
# Fallback: look for title after IWF number
lines = text.split('\n')
for i, line in enumerate(lines):
if f'V.IWF {iwf_num}' in line and i + 1 < len(lines):
next_line = lines[i + 1].strip()
if next_line and len(next_line) > 3 and not next_line.startswith(('Seite', 'ETO', 'Vorstand')):
return f"IWF {iwf_num} {next_line}"
return f"IWF {iwf_num}"
def main():
results = []
# Process all PDFs
pdf_files = sorted(DATA_DIR.glob("*.pdf"))
for pdf_path in pdf_files:
iwf_num = get_iwf_number(pdf_path.name)
if not is_relevant_iwf(iwf_num):
continue
print(f"Processing: {pdf_path.name} (IWF {iwf_num})")
# Extract text
text = extract_text_from_pdf(pdf_path)
if not text:
print(f" No text extracted from {pdf_path.name}")
continue
# Get OE label
oe_label = get_oe_label(iwf_num, text)
# Find IT-relevant passages
passages = find_it_passages(text)
if passages:
print(f" Found {len(passages)} IT-relevant passages")
for passage in passages:
assessment = assess_passage(passage)
# Escape pipe characters for markdown table
clean_passage = passage.replace('|', '\\|').replace('\n', ' ')
results.append((oe_label, clean_passage, assessment))
else:
print(f" No IT-relevant passages found")
# Sort results by OE
results.sort(key=lambda x: x[0])
# Write output
OUTPUT_FILE.parent.mkdir(parents=True, exist_ok=True)
with open(OUTPUT_FILE, 'w', encoding='utf-8') as f:
f.write("# IT-Relevanz-Analyse der Funktionsbeschreibungen (FuBen)\n\n")
f.write("**Datum:** 2026-06-18\n\n")
f.write("**Scope:** IWF 2, 3, 4, 5, 6, 7, 8 und ihre Untereinheiten\n\n")
f.write("**Ausgeschlossen:** IWF 1 (Strategie/Steuerung) und IWF 9 (Prozesse und IT)\n\n")
f.write("**Suchbegriffe:** IT, Software, Anwendungen, Systeme, Digital, Tools, Plattform, Portal, Datenbank, Automatisierung, Datenmanagement, Schnittstelle, SAP, ERP, Applikation, elektronisch\n\n")
f.write("## Legende Bewertung\n\n")
f.write("| Symbol | Bedeutung |\n")
f.write("|---|---|\n")
f.write("| ✅ Ja | Aufgabe sollte in die IT-Abteilung wandern |\n")
f.write("| ⚠️ Teilweise | IT-Unterstützung nötig, fachliche Verantwortung bleibt in der OE |\n")
f.write("| 🔍 Prüfen | Kontext unklar, weitere Analyse nötig |\n\n")
f.write("---\n\n")
f.write("## Ergebnisse\n\n")
f.write("| OE | IT-relevante Textstelle | Bewertung |\n")
f.write("|---|---|---|\n")
for oe, passage, assessment in results:
f.write(f"| {oe} | {passage} | {assessment} |\n")
# Summary section
f.write(f"\n\n---\n\n")
f.write("## Zusammenfassung\n\n")
# Count by assessment type
ja_count = sum(1 for _, _, a in results if '' in a)
teilweise_count = sum(1 for _, _, a in results if '⚠️' in a)
prüfen_count = sum(1 for _, _, a in results if '🔍' in a)
f.write(f"| Kategorie | Anzahl |\n")
f.write(f"|---|---|\n")
f.write(f"| ✅ Klare IT-Aufgaben | {ja_count} |\n")
f.write(f"| ⚠️ Teilweise IT-relevant | {teilweise_count} |\n")
f.write(f"| 🔍 Zu prüfen | {prüfen_count} |\n")
f.write(f"| **Gesamt** | **{len(results)}** |\n\n")
# Count by OE
oe_counts = {}
for oe, _, _ in results:
oe_counts[oe] = oe_counts.get(oe, 0) + 1
f.write(f"### Verteilung nach Organisationseinheit\n\n")
f.write(f"| OE | Anzahl IT-Stellen |\n")
f.write(f"|---|---|\n")
for oe, count in sorted(oe_counts.items()):
f.write(f"| {oe} | {count} |\n")
f.write(f"\n\n---\n\n")
f.write("## Fazit\n\n")
f.write("Die Analyse zeigt, dass IT-bezogene Aufgaben über die gesamte Organisation verteilt sind. ")
f.write("Besonders in den Bereichen Kapazitätsmanagement (IWF 2), Netzfahrplan (IWF 3), ")
f.write("Kapazitätssteuerung (IWF 5) und Produkt-/Preismanagement (IWF 8) finden sich ")
f.write("Aufgaben mit starkem IT-Bezug, die potenziell in eine zentrale IT-Abteilung ")
f.write("wandern könnten.\n\n")
f.write("**Empfehlung:** Die mit ✅ markierten Aufgaben sollten prioritär geprüft werden, ")
f.write("ob sie in IWF 9 (Prozesse und IT) zentralisiert werden können.\n")
print(f"\nDone! Output written to: {OUTPUT_FILE}")
print(f"Total IT-relevant passages found: {len(results)}")
print(f" ✅ Klare IT-Aufgaben: {ja_count}")
print(f" ⚠️ Teilweise: {teilweise_count}")
print(f" 🔍 Zu prüfen: {prüfen_count}")
if __name__ == "__main__":
main()