diff --git a/brancheneinstufung.py b/brancheneinstufung.py index e48c0593..2ccd4a57 100644 --- a/brancheneinstufung.py +++ b/brancheneinstufung.py @@ -7895,56 +7895,69 @@ class DataProcessor: def _get_numeric_value_for_plausi(self, value_str, is_umsatz=False): - """ - Hilfsfunktion, um einen String in einen numerischen Wert (float) für Plausi-Checks umzuwandeln. - Gibt np.nan zurück, wenn der Wert nicht numerisch oder 'k.A.' ist. - Unterscheidet sich von get_numeric_filter_value, da es float/nan für interne Berechnungen liefert. - """ - if value_str is None or pd.isna(value_str) or str(value_str).strip().lower() in ['', 'k.a.', 'n/a', '-']: + if value_str is None or pd.isna(value_str): return np.nan - # Nutze die bestehende Logik von extract_numeric_value, aber sorge für float/nan Output - # extract_numeric_value gibt Strings oder "k.A." zurück. Wir brauchen hier Zahlen. - - # Temporäre (vereinfachte) Konvertierung, die verbessert werden könnte, - # um die volle Logik von extract_numeric_value (Einheiten etc.) zu nutzen, - # aber sicherstellt, dass ein float oder np.nan zurückkommt. - - # Erster Versuch, die Logik von extract_numeric_value zu adaptieren: raw_value_str_clean = str(value_str).strip() - if not raw_value_str_clean: return np.nan - processed_value = clean_text(raw_value_str_clean) # Globale Funktion - if processed_value.lower() in ['k.a.', 'n/a', '-']: return np.nan + # 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.") + 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 + if temp_val.lower() in ['k.a.', 'n/a', '-']: return np.nan - 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() + # Entferne Nicht-Zahlen-Zeichen außer Punkt und Komma und Minus am Anfang + temp_val = re.sub(r'[^\d.,\-]', '', temp_val) - processed_value_no_thousands = processed_value.replace('.', '').replace("'", "") - processed_value_final = processed_value_no_thousands.replace(',', '.') - - match = re.search(r'([\d.]+)', processed_value_final) - if not match: return np.nan - - num_str = match.group(1) + # 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('.', '') + try: - if not num_str or num_str == '.' or num_str.count('.') > 1: raise ValueError - num = float(num_str) - + num = float(temp_val) + + # Einheiten-Skalierung (vereinfacht aus extract_numeric_value) 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 - num = num * multiplier - return num if num > 0 else np.nan # Nur positive Werte, sonst NaN + final_num = num * multiplier + + # 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 + # 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") + except ValueError: + self.logger.debug(f"_get_numeric_value_for_plausi: Konnte '{raw_value_str_clean}' (verarbeitet als '{temp_val}') nicht zu float konvertieren.") return np.nan + def _check_financial_plausibility(self, row_data_dict): """ Prüft die Plausibilität der finalen Umsatz- und Mitarbeiterzahlen @@ -7973,19 +7986,22 @@ class DataProcessor: umsatz_num = self._get_numeric_value_for_plausi(final_umsatz_str, is_umsatz=True) ma_num = self._get_numeric_value_for_plausi(final_ma_str, is_umsatz=False) - # 1. Format-Plausibilität (wird durch _get_numeric_value_for_plausi schon behandelt) - if pd.isna(umsatz_num) and final_umsatz_str.lower() not in ['k.a.', '', None]: + # 1. Format-Plausibilität + # Wird gesetzt, wenn der Originalstring nicht k.A./leer/explizit "0" war, aber die Konvertierung trotzdem zu NaN führte. + if pd.isna(umsatz_num) and final_umsatz_str.lower().strip() not in ['k.a.', '', 'n/a', '-', '0', '0.0', '0,00', '0.000']: results["plaus_umsatz_flag"] = "FEHLER_FORMAT" - results["begruendungen"].append(f"Finaler Umsatz ('{final_umsatz_str}') nicht numerisch.") - if pd.isna(ma_num) and final_ma_str.lower() not in ['k.a.', '', None]: + results["begruendungen"].append(f"Finaler Umsatz ('{final_umsatz_str}') konnte nicht als gültige Zahl interpretiert werden.") + + if pd.isna(ma_num) and final_ma_str.lower().strip() not in ['k.a.', '', 'n/a', '-', '0', '0.0', '0,00', '0.000']: results["plaus_ma_flag"] = "FEHLER_FORMAT" - results["begruendungen"].append(f"Finale MA-Zahl ('{final_ma_str}') nicht numerisch.") + results["begruendungen"].append(f"Finale MA-Zahl ('{final_ma_str}') konnte nicht als gültige Zahl interpretiert werden.") # Nur weiter prüfen, wenn Werte numerisch sind if not pd.isna(umsatz_num): - if umsatz_num < getattr(Config, 'PLAUSI_UMSATZ_MIN_WARNUNG', 50000): - results["plaus_umsatz_flag"] = "WARNUNG_NIEDRIG" - results["begruendungen"].append(f"Finaler Umsatz ({umsatz_num:,.0f} €) sehr niedrig (< {getattr(Config, 'PLAUSI_UMSATZ_MIN_WARNUNG', 50000):,.0f} €).") + if umsatz_num == 0: # Gilt für Fälle, wo z.B. "0 Tsd" zu 0.0 wird + results["plaus_umsatz_flag"] = "WARNUNG_NULL_WERT" + results["begruendungen"].append(f"Finaler Umsatz ist numerisch 0 (ursprünglicher Wert: '{final_umsatz_str}').") + elif umsatz_num < getattr(Config, 'PLAUSI_UMSATZ_MIN_WARNUNG', 50000): if umsatz_num > getattr(Config, 'PLAUSI_UMSATZ_MAX_WARNUNG', 200000000000): results["plaus_umsatz_flag"] = "WARNUNG_HOCH" results["begruendungen"].append(f"Finaler Umsatz ({umsatz_num:,.0f} €) sehr hoch (> {getattr(Config, 'PLAUSI_UMSATZ_MAX_WARNUNG', 200000000000):,.0f} €).")