This commit is contained in:
2025-05-13 10:27:44 +00:00
parent 648f8e41dd
commit 90bdc7edfb

View File

@@ -804,61 +804,101 @@ def extract_numeric_value(raw_value, is_umsatz=False):
def get_numeric_filter_value(value_str, is_umsatz=False):
logger = logging.getLogger(__name__)
if value_str is None or pd.isna(value_str) or str(value_str).strip() == '':
return 0.0 if is_umsatz else 0 # Für Filterlogik 0, wenn unbekannt
return 0.0 if is_umsatz else 0
raw_value_str = str(value_str).strip()
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
raw_value_str_original = str(value_str).strip() # Original für Einheiten-Keywords behalten
# "0" und "k.A." als expliziter String wird für Filterzwecke wie 0 behandelt
if raw_value_str_original.lower() in ['k.a.', 'n/a', '-', '0', '0.0', '0,00', '0,000']:
return 0.0 if is_umsatz else 0
try:
processed_value = clean_text(raw_value_str)
if processed_value.lower() in ['k.a.', 'n/a', '-']: return 0.0 if is_umsatz else 0
# Bereinigung für die Zahlenextraktion
processed_value = clean_text(raw_value_str_original) # clean_text ist Ihre globale Funktion
if processed_value.lower() in ['k.a.', 'n/a', '-']:
return 0.0 if is_umsatz else 0
# Präfixe und Währungssymbole entfernen
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()
# Nur den ersten Teil bei Spannen nehmen (z.B. "100 - 200" -> "100")
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('.', '')
# Umgang mit Tausendertrennern (Punkt) und Dezimalkomma
# Ziel: Einen String erhalten, der von float() korrekt als Zahl interpretiert wird (Dezimaltrenner ist Punkt)
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_extraction_str = processed_value.replace("'", "") # Apostroph als Tausendertrenner entfernen
num_str = match.group(1)
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)
# Wenn Punkt und Komma vorkommen -> versuche Format zu erkennen
if '.' in num_extraction_str and ',' in num_extraction_str:
if num_extraction_str.rfind('.') > num_extraction_str.rfind(','): # US-Format: 1,234.56 -> Kommas entfernen
num_extraction_str = num_extraction_str.replace(',', '')
else: # EU-Format: 1.234,56 -> Punkte entfernen, Komma zu Punkt
num_extraction_str = num_extraction_str.replace('.', '').replace(',', '.')
elif ',' in num_extraction_str: # Nur Kommas: 1234,56 -> 1234.56
num_extraction_str = num_extraction_str.replace(',', '.')
elif '.' in num_extraction_str: # Nur Punkte
# Wenn der String ein typisches Tausenderformat hat (z.B. 1.234 oder 1.234.567), Punkte entfernen
if re.fullmatch(r'\d{1,3}(\.\d{3})+', num_extraction_str): # z.B. 4.380 oder 17.800
num_extraction_str = num_extraction_str.replace('.', '')
# Ansonsten (z.B. "123.45") ist der Punkt wahrscheinlich ein Dezimalpunkt und bleibt.
# Wenn mehrere Punkte und es ist kein Tausenderformat (z.B. 1.2.3), wird es problematisch
# Hier ist eine einfache Annahme: Wenn mehr als ein Punkt und kein Tausenderformat, ist es ungültig.
elif num_extraction_str.count('.') > 1:
logger.debug(f"get_numeric_filter_value: Mehrere Punkte in '{num_extraction_str}', die nicht als Tausendertrenner erkannt wurden. Interpretiere als ungültig.")
return 0.0 if is_umsatz else 0
# 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()
# Extrahiere die Zahl (kann jetzt einen Dezimalpunkt enthalten)
match = re.search(r'([\d.\-]+)', num_extraction_str) # Erlaube Dezimalpunkt und optionales Minus
if not match:
# logger.debug(f"get_numeric_filter_value: Kein numerischer Match in '{num_extraction_str}' (aus '{raw_value_str_original}')")
return 0.0 if is_umsatz else 0
num_str_for_float = match.group(1)
# Finale Prüfung des extrahierten Zahlenstrings
if not num_str_for_float or \
num_str_for_float == '.' or \
num_str_for_float == '-' or \
(num_str_for_float.count('.') > 1) or \
(num_str_for_float.count('-') > 1) or \
(num_str_for_float.count('-') == 1 and not num_str_for_float.startswith('-')):
# logger.debug(f"get_numeric_filter_value: Ungültiger Zahlenstring '{num_str_for_float}' nach Regex.")
return 0.0 if is_umsatz else 0
num_as_float = float(num_str_for_float)
# Einheiten-Skalierung basierend auf dem ORIGINALSTRING raw_value_str_original
# Annahme: Wenn is_umsatz=True, ist num_as_float bereits in Millionen, es sei denn,
# es gibt explizite Einheiten wie "Mrd" oder "Tsd" im Originalstring.
scaled_num = num_as_float
original_lower = raw_value_str_original.lower()
if is_umsatz:
if re.search(r'\bmrd\s*\b|\bmilliarden\s*\b|\bbillion\s*\b', original_lower):
num = num * 1000.0 # von Mrd zu Mio
scaled_num = num_as_float * 1000.0 # Der num_as_float war die Zahl vor Mrd, jetzt ist es 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
scaled_num = num_as_float / 1000.0 # Der num_as_float war die Zahl vor Tsd, jetzt ist es Mio.
# Ansonsten ist num_as_float bereits der Wert in Millionen
else: # Mitarbeiter (absolute Zahl erwartet, außer bei expliziten Einheiten)
if re.search(r'\bmrd\s*\b|\bmilliarden\s*\b|\bbillion\s*\b', original_lower):
scaled_num = num_as_float * 1000000000.0
elif re.search(r'\bmio\s*\b|\bmillionen\s*\b|\bmill[.]?\s*\b', original_lower):
scaled_num = num_as_float * 1000000.0
elif re.search(r'\btsd\s*\b|\btausend\s*\b', original_lower):
scaled_num = num_as_float * 1000.0
return num if num > 0 else (0.0 if is_umsatz else 0) # Nur positive Werte, sonst 0 für Filter
# Für die Filterlogik nur positive Werte zurückgeben, sonst 0
return scaled_num if scaled_num > 0 else (0.0 if is_umsatz else 0)
except Exception as e:
logger.debug(f"Fehler in get_numeric_filter_value für Wert '{raw_value_str[:50]}...': {e}")
except ValueError as e: # Fehler bei float() Konvertierung
logger.debug(f"get_numeric_filter_value: ValueError '{e}' bei Konvertierung von '{num_str_for_float if 'num_str_for_float' in locals() else raw_value_str_original[:30]}...'")
return 0.0 if is_umsatz else 0
except Exception as e_general: # Andere unerwartete Fehler
logger.error(f"Unerwarteter Fehler in get_numeric_filter_value für '{raw_value_str_original[:50]}...': {e_general}")
logger.debug(traceback.format_exc())
return 0.0 if is_umsatz else 0
@@ -7879,66 +7919,76 @@ class DataProcessor:
if value_str is None or pd.isna(value_str): return np.nan
raw_value_str_clean = str(value_str).strip()
# Explizit "0" und andere "unbekannt" Strings als NaN
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.")
self.logger.debug(f"PlausiParse: Input '{raw_value_str_clean}' -> NaN (als 'unbekannt' interpretiert).")
return np.nan
temp_val = clean_text(raw_value_str_clean)
if temp_val.lower() in ['k.a.', 'n/a', '-']: return np.nan
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()
temp_val = re.sub(r'(?i)^\s*(ca\.?|circa|etwa|rund|ueber|unter|mehr als|weniger als|bis zu)\s+', '', temp_val)
temp_val = re.sub(r'[€$£¥]', '', temp_val).strip() # Währungssymbole entfernen
temp_val = re.split(r'\s*(-||bis)\s*', temp_val, 1)[0].strip() # Nur ersten Teil bei Spannen
# 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('.', '')
# Robuste Behandlung von Tausendertrennern (Punkte) und Dezimalkomma
# Entferne zuerst alle Punkte, die als Tausendertrenner dienen könnten
# Ein Punkt ist Tausendertrenner, wenn rechts davon 3 Ziffern und dann ein weiteres Nicht-Ziffer-Zeichen (oder Ende) oder ein weiteres Komma steht
# Oder wenn mehrere Punkte vorhanden sind und kein Komma.
match = re.search(r'([\d.\-]+)', temp_val_for_num)
# Vereinfachte Tausendertrenner-Logik, die für Fälle wie "4.380" und "17.800" funktionieren sollte:
# Annahme: Wenn ein Punkt vorkommt und kein Komma, oder das letzte Komma vor dem letzten Punkt steht,
# dann sind Punkte Tausendertrenner.
cleaned_num_str = temp_val
if '.' in cleaned_num_str and ',' in cleaned_num_str:
if cleaned_num_str.rfind('.') > cleaned_num_str.rfind(','): # US-Stil: 1,234.56 -> Kommas entfernen
cleaned_num_str = cleaned_num_str.replace(',', '')
else: # EU-Stil: 1.234,56 -> Punkte entfernen, Komma zu Punkt
cleaned_num_str = cleaned_num_str.replace('.', '').replace(',', '.')
elif ',' in cleaned_num_str: # Nur Komma: 1234,56 -> 1234.56
cleaned_num_str = cleaned_num_str.replace(',', '.')
elif '.' in cleaned_num_str: # Nur Punkte:
# Wenn es aussieht wie x.xxx (z.B. 4.380, 17.800), entferne den Punkt
if re.match(r'^\d{1,3}(\.\d{3})+$', cleaned_num_str): # Matches 1.234 or 1.234.567 etc.
cleaned_num_str = cleaned_num_str.replace('.', '')
# Ansonsten ist es ein Dezimalpunkt (z.B. 123.45) - bleibt bestehen
match = re.search(r'([\d.\-]+)', cleaned_num_str) # Erlaube Dezimalpunkt und Minus
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}')")
self.logger.debug(f"PlausiParse: Kein numerischer Match in '{cleaned_num_str}' (von '{raw_value_str_clean}') -> NaN.")
return np.nan
num_str = match.group(1)
num_str_from_regex = match.group(1)
try:
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)
# Vermeide Interpretation von "...." als Zahl
if not num_str_from_regex or num_str_from_regex == '.' or (num_str_from_regex.count('.') > 1 and not num_str_from_regex.endswith('.')):
raise ValueError("Ungültiger Zahlenstring nach Regex")
num = float(num_str_from_regex) # Das ist die Zahl, wie sie im String steht (z.B. 173, 4380, 17800)
original_lower = raw_value_str_clean.lower()
final_num_absolute = num # Initialannahme: Zahl ist in der Grundeinheit (außer wenn Umsatz und keine Einheit)
final_num_absolute = num
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
# Input ist bereits in Mio. €, es sei denn, es gibt explizite andere Einheiten
if re.search(r'\bmrd\s*\b|\bmilliarden\s*\b|\bbillion\s*\b', original_lower):
final_num_absolute = num * 1000000000.0 # z.B. "2 Mrd" -> 2 * 10^9
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)
final_num_absolute = num * 1000.0 # z.B. "500 Tsd" -> 500 * 10^3
else: # Annahme: num ist bereits in Mio, konvertiere zu absolutem Euro-Wert
final_num_absolute = num * 1000000.0
else: # Mitarbeiter (absolute Zahl, es sei denn, explizite 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
# Ihre Regel: String "0" wurde oben zu NaN.
# Wenn final_num_absolute jetzt 0 ist, war es eine Berechnung wie "0 Tsd".
# "0" (als String) wurde oben schon zu NaN.
# Wenn final_num_absolute jetzt numerisch 0 ist, war es z.B. "0 Tsd".
return final_num_absolute
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}")
self.logger.debug(f"PlausiParse: ValueError '{e}' bei Konvertierung von '{num_str_from_regex}' (von '{raw_value_str_clean}') -> NaN.")
return np.nan