bugfix
This commit is contained in:
@@ -726,87 +726,107 @@ def extract_numeric_value(raw_value, is_umsatz=False):
|
|||||||
if not text_to_parse or text_to_parse.lower() in ['k.a.', 'n/a', '-']:
|
if not text_to_parse or text_to_parse.lower() in ['k.a.', 'n/a', '-']:
|
||||||
return "k.A."
|
return "k.A."
|
||||||
|
|
||||||
|
# Vereinheitliche typografische Apostrophe zu geraden
|
||||||
|
text_to_parse = text_to_parse.replace("’", "'").replace("‘", "'")
|
||||||
|
|
||||||
# Vor dem Parsen prüfen, ob der Input-String selbst "0" oder eine Variation davon ist.
|
# Vor dem Parsen prüfen, ob der Input-String selbst "0" oder eine Variation davon ist.
|
||||||
# Wenn ja, "0" zurückgeben. Die Logik in _get_numeric_value_for_plausi
|
test_val_for_zero = text_to_parse.replace(',', '.').replace(' ', '') # Leerzeichen hier entfernen für den Test
|
||||||
# wird dies dann korrekt als "unbekannt" (np.nan) interpretieren.
|
|
||||||
# Leerzeichen werden hier noch nicht entfernt, um "0 . 0" nicht fälschlich als "0" zu erkennen.
|
|
||||||
test_val_for_zero = text_to_parse.replace(',', '.')
|
|
||||||
if test_val_for_zero in ['0', '0.0', '0.00', '0.000']:
|
if test_val_for_zero in ['0', '0.0', '0.00', '0.000']:
|
||||||
|
logger.debug(f"Input '{raw_value_str_original_for_debug}' direkt als '0' interpretiert.")
|
||||||
return "0"
|
return "0"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Schritt 1: Grobe Vorreinigung
|
# Schritt 1: Erweiterte Vorreinigung für Präfixe, Suffixe und Währungssymbole
|
||||||
# text_cleaned_for_units wird für die reine Einheitensuche verwendet, daher früh cleanen.
|
text_processed = text_to_parse
|
||||||
text_cleaned_for_units = clean_text(text_to_parse).lower()
|
|
||||||
|
|
||||||
text_processed = text_to_parse # Arbeitskopie für Zahlenextraktion
|
# Präfixe entfernen (Groß-/Kleinschreibung ignorieren)
|
||||||
text_processed = re.sub(r'(?i)^\s*(ca\.?|circa|rund|etwa|über|unter|mehr als|weniger als|bis zu)\s+', '', text_processed)
|
prefixes_to_remove = [
|
||||||
text_processed = re.sub(r'\(.*?\)|\[.*?\]', '', text_processed) # Klammern und eckige Klammern entfernen
|
r'ca\.?\s*', r'circa\s*', r'rund\s*', r'etwa\s*', r'über\s*', r'unter\s*',
|
||||||
text_processed = re.sub(r'[€$£¥]\s*|[Cc][Hh][Ff]\s*', '', text_processed, flags=re.IGNORECASE).strip()
|
r'mehr als\s*', r'weniger als\s*', r'bis zu\s*', r'about\s*', r'over\s*',
|
||||||
|
r'approx\.?\s*', r'around\s*', r'up to\s*', r'~\s*', r'rd\.?\s*'
|
||||||
|
]
|
||||||
|
for prefix_pattern in prefixes_to_remove:
|
||||||
|
text_processed = re.sub(f'(?i)^{prefix_pattern}', '', text_processed).strip()
|
||||||
|
|
||||||
|
# Währungssymbole und -codes entfernen (umfassender)
|
||||||
|
# Reihenfolge ist wichtig: spezifischere (z.B. "US$") vor allgemeineren ($)
|
||||||
|
currency_patterns = [
|
||||||
|
r'(?:US|USD)\$\s*', r'US\$\s*', r'EUR\s*€?\s*', r'€\s*', r'CHF\s*', r'GBP\s*£?\s*', r'£\s*',
|
||||||
|
r'JPY\s*¥?\s*', r'¥\s*', r'₹\s*', r'[Cc][Hh][Ff]\s*'
|
||||||
|
]
|
||||||
|
for curr_pattern in currency_patterns:
|
||||||
|
text_processed = re.sub(curr_pattern, '', text_processed, flags=re.IGNORECASE).strip()
|
||||||
|
|
||||||
|
# Klammern und eckige Klammern und ihren Inhalt entfernen
|
||||||
|
text_processed = re.sub(r'\(.*?\)|\[.*?\]', '', text_processed).strip()
|
||||||
|
# Bereiche ("X - Y" oder "X bis Y") - nur den ersten Teil nehmen
|
||||||
text_processed = re.split(r'\s*(-|–|bis)\s*', text_processed, 1)[0].strip()
|
text_processed = re.split(r'\s*(-|–|bis)\s*', text_processed, 1)[0].strip()
|
||||||
|
|
||||||
if not text_processed:
|
if not text_processed:
|
||||||
logger.debug(f"Text nach Vorreinigung leer für '{raw_value_str_original_for_debug}'")
|
logger.debug(f"Text nach erweiterter Vorreinigung leer für '{raw_value_str_original_for_debug}'")
|
||||||
return "k.A."
|
return "k.A."
|
||||||
|
|
||||||
# Schritt 2: Zahl und Einheit trennen.
|
# Der globale bereinigte Text für die Einheitensuche (ohne Zahl)
|
||||||
match = re.match(r'([\d.,\'\s]+)(.*)', text_processed)
|
text_cleaned_for_units = clean_text(text_processed).lower()
|
||||||
|
|
||||||
|
# Schritt 2: Zahl und direkt folgende Einheit suchen (flexibler)
|
||||||
|
# Suche nach der ERSTEN Zahlenfolge im String.
|
||||||
|
# Erlaubt optionalen Text davor, dann die Zahl, dann optionalen Text danach (für Einheit)
|
||||||
|
# Diese Regex versucht, die Zahl so gut wie möglich zu isolieren.
|
||||||
|
# \D*? am Anfang, um nicht-numerischen Text vor der Zahl zu überspringen (non-greedy)
|
||||||
|
# ([\d.,\'\s]+) fängt die Zahl selbst
|
||||||
|
# (.*) fängt den Rest als potentielle Einheit
|
||||||
|
num_match = re.search(r'([\d.,\'\s]+)', text_processed) # Finde die erste Zahlenfolge
|
||||||
|
|
||||||
num_str_candidate = ""
|
num_str_candidate = ""
|
||||||
unit_part_str = ""
|
unit_part_str = "" # Einheit, die direkt NACH der Zahl steht
|
||||||
|
|
||||||
if match:
|
if num_match:
|
||||||
num_str_candidate = match.group(1).strip()
|
num_str_candidate = num_match.group(1).strip()
|
||||||
unit_part_str = match.group(2).strip()
|
# Was nach der Zahl kommt, könnte eine Einheit sein
|
||||||
|
potential_unit_start_index = num_match.end()
|
||||||
|
unit_part_str = text_processed[potential_unit_start_index:].strip()
|
||||||
else:
|
else:
|
||||||
match_num_only = re.match(r'([\d.,\'\s]+)', text_processed)
|
logger.debug(f"Kein Zahlen-Match in '{text_processed}' (Original: '{raw_value_str_original_for_debug}')")
|
||||||
if match_num_only:
|
|
||||||
num_str_candidate = match_num_only.group(1).strip()
|
|
||||||
else:
|
|
||||||
logger.debug(f"Kein initialer Zahlen-Match in '{text_processed}' (Original: '{raw_value_str_original_for_debug}')")
|
|
||||||
return "k.A."
|
return "k.A."
|
||||||
|
|
||||||
if not num_str_candidate: # Falls num_str_candidate leer ist nach strip()
|
if not num_str_candidate:
|
||||||
logger.debug(f"Zahlenkandidat war leer oder nur Whitespace für '{raw_value_str_original_for_debug}'")
|
logger.debug(f"Zahlenkandidat war leer oder nur Whitespace für '{raw_value_str_original_for_debug}' nach match in '{text_processed}'")
|
||||||
return "k.A."
|
return "k.A."
|
||||||
|
|
||||||
# Schritt 3: Bereinige den extrahierten Zahlen-String
|
# Schritt 3: Bereinige den extrahierten Zahlen-String
|
||||||
cleaned_num_str = num_str_candidate.replace("'", "").replace(" ", "")
|
cleaned_num_str = num_str_candidate.replace("'", "").replace(" ", "") # Apostrophe und alle Leerzeichen entfernen
|
||||||
if not cleaned_num_str:
|
if not cleaned_num_str:
|
||||||
logger.debug(f"Zahlenkandidat '{num_str_candidate}' wurde zu leerem String nach Entfernung von ' und Leerraum.")
|
logger.debug(f"Zahlenkandidat '{num_str_candidate}' wurde zu leerem String nach Bereinigung.")
|
||||||
return "k.A."
|
return "k.A."
|
||||||
|
|
||||||
has_dot = '.' in cleaned_num_str
|
has_dot = '.' in cleaned_num_str
|
||||||
has_comma = ',' in cleaned_num_str
|
has_comma = ',' in cleaned_num_str
|
||||||
|
|
||||||
|
# Verbesserte Trennzeichenlogik
|
||||||
if has_dot and has_comma:
|
if has_dot and has_comma:
|
||||||
last_dot_pos = cleaned_num_str.rfind('.')
|
last_dot_pos = cleaned_num_str.rfind('.')
|
||||||
last_comma_pos = cleaned_num_str.rfind(',')
|
last_comma_pos = cleaned_num_str.rfind(',')
|
||||||
if last_dot_pos > last_comma_pos:
|
if last_dot_pos > last_comma_pos: # US: 1,234.56
|
||||||
cleaned_num_str = cleaned_num_str.replace(',', '')
|
cleaned_num_str = cleaned_num_str.replace(',', '') # Komma ist Tausender
|
||||||
else:
|
else: # EU: 1.234,56
|
||||||
cleaned_num_str = cleaned_num_str.replace('.', '')
|
cleaned_num_str = cleaned_num_str.replace('.', '') # Punkt ist Tausender
|
||||||
cleaned_num_str = cleaned_num_str.replace(',', '.')
|
cleaned_num_str = cleaned_num_str.replace(',', '.') # Komma wird Dezimal
|
||||||
elif has_comma:
|
elif has_comma:
|
||||||
# Fall: "1,234,567" -> 1234567
|
# Wenn es nur Kommas gibt:
|
||||||
# Fall: "1,234" -> 1234
|
# "1,23" -> "1.23" (letztes Komma, 1-2 Ziffern danach)
|
||||||
# Fall: "1,23" -> 1.23
|
# "1,234" -> "1.234" (letztes Komma, 3 Ziffern danach, aber es ist das einzige Komma)
|
||||||
# Fall: "1," -> k.A. (wird später abgefangen)
|
# "1,234,567" -> "1234567" (mehrere Kommas -> Tausender)
|
||||||
# Nur das *letzte* Komma kann ein Dezimaltrennzeichen sein, wenn es von 1 oder 2 Ziffern gefolgt wird.
|
if cleaned_num_str.count(',') == 1 and re.search(r',\d+$', cleaned_num_str):
|
||||||
if re.search(r',\d{1,2}$', cleaned_num_str):
|
# Einziges Komma, gefolgt von Ziffern -> Dezimaltrennzeichen
|
||||||
# Das letzte Komma ist wahrscheinlich ein Dezimaltrennzeichen.
|
cleaned_num_str = cleaned_num_str.replace(',', '.', 1)
|
||||||
# Ersetze nur dieses letzte Komma durch einen Punkt.
|
else:
|
||||||
# Alle anderen Kommas (falls vorhanden) sind Tausendertrenner.
|
# Mehrere Kommas oder Format passt nicht zu Dezimal -> Tausendertrenner
|
||||||
parts = cleaned_num_str.rsplit(',', 1)
|
|
||||||
integer_part = parts[0].replace(',', '')
|
|
||||||
cleaned_num_str = f"{integer_part}.{parts[1]}"
|
|
||||||
else: # Alle Kommas sind Tausendertrenner
|
|
||||||
cleaned_num_str = cleaned_num_str.replace(',', '')
|
cleaned_num_str = cleaned_num_str.replace(',', '')
|
||||||
elif has_dot:
|
elif has_dot:
|
||||||
# Analog zu Kommas
|
# Analoge Logik für Punkte
|
||||||
if re.search(r'\.\d{1,2}$', cleaned_num_str):
|
if cleaned_num_str.count('.') == 1 and re.search(r'\.\d+$', cleaned_num_str):
|
||||||
parts = cleaned_num_str.rsplit('.', 1)
|
cleaned_num_str = cleaned_num_str.replace('.', '.', 1) # Ist schon ein Punkt
|
||||||
integer_part = parts[0].replace('.', '')
|
|
||||||
cleaned_num_str = f"{integer_part}.{parts[1]}"
|
|
||||||
else:
|
else:
|
||||||
cleaned_num_str = cleaned_num_str.replace('.', '')
|
cleaned_num_str = cleaned_num_str.replace('.', '')
|
||||||
|
|
||||||
@@ -818,25 +838,41 @@ def extract_numeric_value(raw_value, is_umsatz=False):
|
|||||||
|
|
||||||
# Schritt 4: Einheiten-Skalierung
|
# Schritt 4: Einheiten-Skalierung
|
||||||
scaled_num = num_as_float
|
scaled_num = num_as_float
|
||||||
|
|
||||||
|
# Priorisiere die Einheit, die direkt nach der Zahl kommt.
|
||||||
|
# Falls diese leer ist, verwende den globaleren, bereinigten Text.
|
||||||
string_for_unit_search = unit_part_str.lower() if unit_part_str else text_cleaned_for_units
|
string_for_unit_search = unit_part_str.lower() if unit_part_str else text_cleaned_for_units
|
||||||
|
logger.debug(f"String für Einheitensuche: '{string_for_unit_search}' (num_as_float: {num_as_float})")
|
||||||
|
|
||||||
if is_umsatz:
|
if is_umsatz:
|
||||||
multiplikator = 1.0
|
multiplikator = 1.0
|
||||||
|
einheit_gefunden = False
|
||||||
if re.search(r'\b(mrd\.?|milliarden|billion|mia\.?)\b', string_for_unit_search):
|
if re.search(r'\b(mrd\.?|milliarden|billion|mia\.?)\b', string_for_unit_search):
|
||||||
multiplikator = 1000.0
|
multiplikator = 1000.0
|
||||||
elif re.search(r'\b(mio\.?|mill\.?|millionen)\b', string_for_unit_search):
|
einheit_gefunden = True
|
||||||
|
elif re.search(r'\bcrore\b', string_for_unit_search): # 1 crore = 10 Mio
|
||||||
|
multiplikator = 10.0
|
||||||
|
einheit_gefunden = True
|
||||||
|
elif re.search(r'\b(mio\.?|mill\.?|millionen|mn)\b', string_for_unit_search):
|
||||||
multiplikator = 1.0
|
multiplikator = 1.0
|
||||||
|
einheit_gefunden = True
|
||||||
elif re.search(r'\b(tsd\.?|tausend|k\b(?!\w))\b', string_for_unit_search):
|
elif re.search(r'\b(tsd\.?|tausend|k\b(?!\w))\b', string_for_unit_search):
|
||||||
multiplikator = 0.001
|
multiplikator = 0.001
|
||||||
|
einheit_gefunden = True
|
||||||
|
|
||||||
|
# Wenn keine explizite Einheit (Mrd, Mio, Tsd, Crore) gefunden wurde,
|
||||||
|
# wird die Zahl als bereits in Millionen interpretiert (multiplikator bleibt 1.0).
|
||||||
|
logger.debug(f"Umsatz: num_as_float={num_as_float}, gefundene Einheit? {einheit_gefunden}, Multiplikator={multiplikator}")
|
||||||
scaled_num = num_as_float * multiplikator
|
scaled_num = num_as_float * multiplikator
|
||||||
else:
|
else: # Mitarbeiter (absolute Zahl)
|
||||||
multiplikator = 1.0
|
multiplikator = 1.0
|
||||||
if re.search(r'\b(mrd\.?|milliarden|billion|mia\.?)\b', string_for_unit_search):
|
if re.search(r'\b(mrd\.?|milliarden|billion|mia\.?)\b', string_for_unit_search):
|
||||||
multiplikator = 1000000000.0
|
multiplikator = 1000000000.0
|
||||||
elif re.search(r'\b(mio\.?|mill\.?|millionen)\b', string_for_unit_search):
|
elif re.search(r'\b(mio\.?|mill\.?|millionen|mn)\b', string_for_unit_search):
|
||||||
multiplikator = 1000000.0
|
multiplikator = 1000000.0
|
||||||
elif re.search(r'\b(tsd\.?|tausend|k\b(?!\w))\b', string_for_unit_search):
|
elif re.search(r'\b(tsd\.?|tausend|k\b(?!\w))\b', string_for_unit_search):
|
||||||
multiplikator = 1000.0
|
multiplikator = 1000.0
|
||||||
|
logger.debug(f"Mitarbeiter: num_as_float={num_as_float}, Multiplikator={multiplikator}")
|
||||||
scaled_num = num_as_float * multiplikator
|
scaled_num = num_as_float * multiplikator
|
||||||
|
|
||||||
if pd.isna(scaled_num):
|
if pd.isna(scaled_num):
|
||||||
|
|||||||
Reference in New Issue
Block a user