This commit is contained in:
2025-05-12 19:32:36 +00:00
parent 75de66550b
commit 0d54c59ae3

View File

@@ -802,93 +802,63 @@ def extract_numeric_value(raw_value, is_umsatz=False):
# Extrahiert und normalisiert Zahlenwerte fuer Vergleichslogik.
# Nutzt globale Helfer: clean_text, re.
def get_numeric_filter_value(value_str, is_umsatz=False):
"""
Extrahiert und normalisiert Zahlenwerte fuer die Filterlogik (Umsatz in Mio, Mitarbeiter int).
Gibt 0.0 (fuer Umsatz) oder 0 (fuer Mitarbeiter) zurueck, wenn der Wert leer, k.A., nicht numerisch ist, oder 0 ergibt.
Beachtet Einheiten (Tsd, Mio, Mrd) fuer Umsatz.
"""
# Verwenden Sie logger, da das Logging jetzt konfiguriert ist
logger = logging.getLogger(__name__) # <<< DIESE ZEILE HINZUFÜGEN
logger = logging.getLogger(__name__)
if value_str is None or pd.isna(value_str) or str(value_str).strip() == '':
# Gibt 0 (int/float) zurueck, nicht "k.A." fuer Filterlogik
return 0.0 if is_umsatz else 0
return 0.0 if is_umsatz else 0 # Für Filterlogik 0, wenn unbekannt
raw_value_str = str(value_str).strip()
# Pruefe auf bekannte "keine Angabe" Strings
if raw_value_str.lower() in ['k.a.', 'n/a', '-']:
if raw_value_str.lower() in ['k.a.', 'n/a', '-','0','0.0','0,00','0,000']: # "0" als expliziter String auch als "unbekannt" für Filter
return 0.0 if is_umsatz else 0
try:
processed_value = clean_text(raw_value_str)
if processed_value == "k.A." or processed_value.lower() in ['k.a.', 'n/a', '-']:
return 0.0 if is_umsatz else 0 # Pruefe erneut nach clean_text
if processed_value.lower() in ['k.a.', 'n/a', '-']: return 0.0 if is_umsatz else 0
# Entferne gaengige Praefixe/Suffixe und Spannen-Trennzeichen
processed_value = re.sub(r'(?i)^\s*(ca\.?|circa|rund|etwa|ueber|unter|mehr als|weniger als|bis zu)\s+', '', processed_value) # Beruecksichtige Umlaute (ue)
processed_value = re.sub(r'(?i)^\s*(ca\.?|circa|rund|etwa|ueber|unter|mehr als|weniger als|bis zu)\s+', '', processed_value)
processed_value = re.sub(r'[€$£¥]', '', processed_value).strip()
processed_value = re.split(r'\s*(-||bis)\s*', processed_value, 1)[0].strip() # Nimm nur den ersten Teil bei Spannen
# Entferne Tausendertrenner (Punkt, Apostroph) und ersetze Komma durch Punkt fuer Dezimal
processed_value_no_thousands = processed_value.replace('.', '').replace("'", "")
processed_value_final = processed_value_no_thousands.replace(',', '.')
# Finde die erste Sequenz von Ziffern und Punkten (die Zahl)
match = re.search(r'([\d.]+)', processed_value_final)
if not match:
# Wenn nach der Bereinigung keine Ziffern oder Punkte gefunden werden
# logger.debug(f"get_numeric_filter_value: Keine numerischen Zeichen gefunden nach Bereinigung von: '{raw_value_str}'") # Zu viel Laerm im Debug
return 0.0 if is_umsatz else 0
processed_value = re.split(r'\s*(-||bis)\s*', processed_value, 1)[0].strip()
# Tausendertrenner-Logik (vereinfacht, muss ggf. robuster werden)
temp_val_for_num = processed_value.replace("'", "")
if '.' in temp_val_for_num and ',' in temp_val_for_num:
if temp_val_for_num.rfind('.') > temp_val_for_num.rfind(','):
temp_val_for_num = temp_val_for_num.replace(',', '')
else:
temp_val_for_num = temp_val_for_num.replace('.', '').replace(',', '.')
elif ',' in temp_val_for_num:
temp_val_for_num = temp_val_for_num.replace(',', '.')
elif temp_val_for_num.count('.') > 1: # Mehrere Punkte -> Tausendertrenner
temp_val_for_num = temp_val_for_num.replace('.', '')
match = re.search(r'([\d.\-]+)', temp_val_for_num) # Erlaube auch negative Zahlen am Anfang
if not match: return 0.0 if is_umsatz else 0
num_str = match.group(1)
try:
# Zusaetzliche Pruefungen auf ungueltige Zahlenstrings
if not num_str or num_str == '.' or num_str.count('.') > 1:
raise ValueError("Leerer oder ungueltiger Zahlenstring gefunden nach Regex Match")
# Konvertiere zu Float
num = float(num_str)
except ValueError as e:
# Wenn die Konvertierung zu Float fehlschlaegt
# logger.debug(f"Fehler bei Float-Umwandlung des extrahierten Strings '{num_str}' (aus '{raw_value_str}'): {e}") # Zu viel Laerm im Debug
return 0.0 if is_umsatz else 0
# --- Einheiten-Skalierung basierend auf ORIGINALSTRING ---
# Ziel: Den Wert in die Einheit des Schwellenwerts konvertieren (Mio fuer Umsatz, Integer fuer MA).
original_lower = raw_value_str.lower(); multiplier = 1.0
if is_umsatz: # Umsatz (Schwellenwert in Mio)
if re.search(r'\bmrd\s*\b|\bmilliarden\s*\b|\bbillion\s*\b', original_lower):
num = num * 1000.0 # Konvertiere von Mrd zu Mio
elif re.search(r'\btsd\s*\b|\btausend\s*\b', original_lower):
num = num / 1000.0 # Konvertiere von Tsd zu Mio
# Wenn "Mio" oder keine Einheit, nehme num direkt (wird als Mio interpretiert)
else: # Mitarbeiterzahl (Schwellenwert ist Integer)
if re.search(r'\bmrd\s*\b|\bmilliarden\s*\b|\bbillion\s*\b', original_lower):
num = num * 1000000000.0 # Konvertiere von Mrd zu Integer
elif re.search(r'\bmio\s*\b|\bmillionen\s*\b|\bmill[.]?\s*\b', original_lower): # Beruecksichtige "mill."
num = num * 1000000.0 # Konvertiere von Mio zu Integer
elif re.search(r'\btsd\s*\b|\btausend\s*\b', original_lower):
num = num * 1000.0 # Konvertiere von Tsd zu Integer
# Wenn keine Einheit, nehme num direkt (wird als Integer interpretiert)
# Das Ergebnis muss 0 oder positiv sein fuer die Filterlogik
result_num = num if num > 0 else 0 # Werte <= 0 zaehlen nicht
if not num_str or num_str == '.' or (num_str.count('.') > 1 and not num_str.endswith('.')):
raise ValueError("Ungültiger Zahlenstring")
num = float(num_str)
# Einheiten-Skalierung:
# WENN is_umsatz=True, gehen wir davon aus, der Wert im Sheet ist BEREITS in Mio,
# AUSSER es steht explizit Mrd oder Tsd dabei.
original_lower = raw_value_str.lower()
if is_umsatz:
# Rueckgabe als Wert in Millionen (Float)
return result_num / 1000000.0
else: # Mitarbeiterzahl
# Rueckgabe als ganze Zahl
return round(result_num)
if re.search(r'\bmrd\s*\b|\bmilliarden\s*\b|\bbillion\s*\b', original_lower):
num = num * 1000.0 # von Mrd zu Mio
elif re.search(r'\btsd\s*\b|\btausend\s*\b', original_lower):
num = num / 1000.0 # von Tsd zu Mio
# ANSONSTEN (keine Einheit oder "Mio" explizit): num ist bereits in Mio
else: # Mitarbeiter
if re.search(r'\bmrd\s*\b|\bmilliarden\s*\b|\bbillion\s*\b', original_lower): num = num * 1000000000.0
elif re.search(r'\bmio\s*\b|\bmillionen\s*\b|\bmill[.]?\s*\b', original_lower): num = num * 1000000.0
elif re.search(r'\btsd\s*\b|\btausend\s*\b', original_lower): num = num * 1000.0
return num if num > 0 else (0.0 if is_umsatz else 0) # Nur positive Werte, sonst 0 für Filter
except Exception as e:
# Fange unerwartete Fehler ab und logge sie
logger.debug(f"Fehler in get_numeric_filter_value fuer Wert '{raw_value_str[:50]}...': {e}")
# Rueckgabe 0 bei Fehler fuer Filterlogik
logger.debug(f"Fehler in get_numeric_filter_value für Wert '{raw_value_str[:50]}...': {e}")
return 0.0 if is_umsatz else 0
@@ -7906,65 +7876,69 @@ class DataProcessor:
def _get_numeric_value_for_plausi(self, value_str, is_umsatz=False):
if value_str is None or pd.isna(value_str):
return np.nan
if value_str is None or pd.isna(value_str): return np.nan
raw_value_str_clean = str(value_str).strip()
# Fall 1: Explizit "unbekannt" oder leer
if raw_value_str_clean.lower() in ['', 'k.a.', 'n/a', '-']:
self.logger.debug(f"_get_numeric_value_for_plausi: Input '{raw_value_str_clean}' als 'unbekannt/leer' (NaN) interpretiert.")
if raw_value_str_clean.lower() in ['', 'k.a.', 'n/a', '-', '0', '0.0', '0,00', '0.000']:
self.logger.debug(f"_get_numeric_value_for_plausi: Input '{raw_value_str_clean}' als 'unbekannt' (NaN) interpretiert.")
return np.nan
# Fall 2: Versuche, als Zahl zu interpretieren
# (Hier sollte die volle Logik von extract_numeric_value für Einheiten etc. stehen)
# Für dieses Beispiel eine vereinfachte Version, die Tausendertrenner und Komma als Dezimal behandelt:
temp_val = clean_text(raw_value_str_clean) # Bereinigen
temp_val = clean_text(raw_value_str_clean)
if temp_val.lower() in ['k.a.', 'n/a', '-']: return np.nan
# Entferne Nicht-Zahlen-Zeichen außer Punkt und Komma und Minus am Anfang
temp_val = re.sub(r'[^\d.,\-]', '', temp_val)
# Wenn nach Bereinigung leer
if not temp_val: return np.nan
# Behandlung von Tausenderpunkten und Dezimalkomma
if '.' in temp_val and ',' in temp_val: # z.B. 1.234,56 oder 1,234.56
if temp_val.rfind('.') > temp_val.rfind(','): # US-Stil: 1,234.56
temp_val = temp_val.replace(',', '')
else: # EU-Stil: 1.234,56
temp_val = temp_val.replace('.', '').replace(',', '.')
elif ',' in temp_val: # Nur Komma: 1234,56 -> 1234.56
temp_val = temp_val.replace(',', '.')
# Wenn nur Punkte: 1.234.567 -> 1234567 (Annahme: Tausendertrenner)
# Dies ist heikel, wenn es sich um IP-Adressen oder Versionsnummern handelt.
# Aber für Finanzdaten ist es oft so.
elif temp_val.count('.') > 1:
temp_val = temp_val.replace('.', '')
temp_val = re.sub(r'(?i)^\s*(ca\.?|circa|...)\s+', '', temp_val) # Gekürzt für Lesbarkeit
temp_val = re.sub(r'[€$£¥]', '', temp_val).strip()
temp_val = re.split(r'\s*(-||bis)\s*', temp_val, 1)[0].strip()
# Tausendertrenner-Logik (wie oben)
temp_val_for_num = temp_val.replace("'", "")
if '.' in temp_val_for_num and ',' in temp_val_for_num:
if temp_val_for_num.rfind('.') > temp_val_for_num.rfind(','): temp_val_for_num = temp_val_for_num.replace(',', '')
else: temp_val_for_num = temp_val_for_num.replace('.', '').replace(',', '.')
elif ',' in temp_val_for_num: temp_val_for_num = temp_val_for_num.replace(',', '.')
elif temp_val_for_num.count('.') > 1: temp_val_for_num = temp_val_for_num.replace('.', '')
match = re.search(r'([\d.\-]+)', temp_val_for_num)
if not match:
self.logger.debug(f"_get_numeric_value_for_plausi: Kein numerischer Match in '{temp_val_for_num}' (von '{raw_value_str_clean}')")
return np.nan
num_str = match.group(1)
try:
num = float(temp_val)
# Einheiten-Skalierung (vereinfacht aus extract_numeric_value)
if not num_str or num_str == '.' or (num_str.count('.') > 1 and not num_str.endswith('.')):
raise ValueError("Ungültiger Zahlenstring")
num = float(num_str) # Das ist die Zahl, wie sie im String steht (ggf. schon Mio)
original_lower = raw_value_str_clean.lower()
multiplier = 1.0
if re.search(r'\bmrd\s*\b|\bmilliarden\s*\b|\bbillion\s*\b', original_lower): multiplier = 1000000000.0
elif re.search(r'\bmio\s*\b|\bmillionen\s*\b|\bmill[.]?\s*\b', original_lower): multiplier = 1000000.0
elif re.search(r'\btsd\s*\b|\btausend\s*\b', original_lower): multiplier = 1000.0
final_num = num * multiplier
final_num_absolute = num # Initialannahme: Zahl ist in der Grundeinheit (außer wenn Umsatz und keine Einheit)
# Gemäß Ihrer Regel: Wenn der *ursprüngliche String* "0" (oder Ähnliches) war, soll es als "unbekannt" (NaN) gelten.
if final_num == 0.0 and raw_value_str_clean.strip() in ['0', '0.0', '0,0', '0.00', '0,000', '0.000']:
self.logger.debug(f"_get_numeric_value_for_plausi: Input '{raw_value_str_clean}' ist explizit '0', als 'unbekannt' (NaN) interpretiert.")
return np.nan
if is_umsatz:
# WENN der String NUR eine Zahl ist (z.B. "173"), NEHMEN WIR AN, ES SIND BEREITS MILLIONEN
# und wir wollen den absoluten Wert in Euro für die Plausi-Checks.
if not any(kw in original_lower for kw in ['mrd', 'milliarden', 'billion', 'tsd', 'tausend']):
# Keine explizite Einheit gefunden, und es ist Umsatz -> interpretiere als Mio
final_num_absolute = num * 1000000.0
elif re.search(r'\bmrd\s*\b|\bmilliarden\s*\b|\bbillion\s*\b', original_lower):
final_num_absolute = num * 1000000000.0
elif re.search(r'\btsd\s*\b|\btausend\s*\b', original_lower):
final_num_absolute = num * 1000.0
# Wenn "mio" oder "millionen" explizit drinsteht, ist `num` bereits der Mio-Wert,
# also multiplizieren wir mit 1 Mio, um auf Euro zu kommen.
elif re.search(r'\bmio\s*\b|\bmillionen\s*\b|\bmill[.]?\s*\b', original_lower):
final_num_absolute = num * 1000000.0
# Sonst (falls es eine andere, nicht behandelte Einheit war), bleibt es `num`
# Dies sollte aber durch die erste Bedingung (nur Zahl -> Mio) abgedeckt sein.
else: # Mitarbeiter (bereits absolute Zahl erwartet, außer bei expliziten Einheiten)
if re.search(r'\bmrd\s*\b|\bmilliarden\s*\b|\bbillion\s*\b', original_lower): final_num_absolute = num * 1000000000.0
elif re.search(r'\bmio\s*\b|\bmillionen\s*\b|\bmill[.]?\s*\b', original_lower): final_num_absolute = num * 1000000.0
elif re.search(r'\btsd\s*\b|\btausend\s*\b', original_lower): final_num_absolute = num * 1000.0
# self.logger.debug(f"_get_numeric_value_for_plausi: Input '{raw_value_str_clean}' erfolgreich zu {final_num} konvertiert.")
return final_num # Gibt die Zahl zurück, auch wenn es 0.0 ist (aus z.B. "0 Tsd")
# Ihre Regel: String "0" wurde oben zu NaN.
# Wenn final_num_absolute jetzt 0 ist, war es eine Berechnung wie "0 Tsd".
return final_num_absolute
except ValueError:
self.logger.debug(f"_get_numeric_value_for_plausi: Konnte '{raw_value_str_clean}' (verarbeitet als '{temp_val}') nicht zu float konvertieren.")
except ValueError as e:
self.logger.debug(f"_get_numeric_value_for_plausi: ValueError bei Konvertierung von '{num_str}' (von '{raw_value_str_clean}'): {e}")
return np.nan