Files
Orchestrator/bahn/Analyse-O2C-C2S/scripts/extract_it_fuben.py
T
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

313 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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()