From 9ae750d6bd52499fc7079bfeb78546ad4d96511b Mon Sep 17 00:00:00 2001 From: Floke Date: Wed, 23 Apr 2025 05:36:57 +0000 Subject: [PATCH] bugfix --- brancheneinstufung.py | 182 +++++++++++++++++++++++++++--------------- 1 file changed, 119 insertions(+), 63 deletions(-) diff --git a/brancheneinstufung.py b/brancheneinstufung.py index a513a17d..a62b6b23 100644 --- a/brancheneinstufung.py +++ b/brancheneinstufung.py @@ -359,18 +359,112 @@ def serp_wikipedia_lookup(company_name, website=None, min_score=0.4): logging.error(f"Allgemeiner Fehler bei der SerpAPI Wikipedia Suche für '{company_name}': {e}") return None # Bei unerwarteten Fehlern None zurückgeben -# Kann als eigenständige Funktion oder Methode in DataProcessor implementiert werden # Annahme: COLUMN_MAP ist global definiert und enthält mindestens: # "CRM Name" (B), "CRM Anzahl Mitarbeiter" (K), "CRM Umsatz" (J), # "Wiki URL" (M), "ReEval Flag" (A), "Wiki Absatz" (N), ..., "Wiki Verif. Timestamp" (AX), "SerpAPI Wiki Search Timestamp" (AY) # Annahme: serp_wikipedia_lookup und simple_normalize_url sind definiert und nutzen logging/retry # Annahme: clean_text und normalize_company_name sind definiert -# Annahme: get_valid_numeric (oder ähnliche Logik zur numerischen Extraktion) ist verfügbar/implementiert. + +# Die extract_numeric_value Funktion wird für die Extraktion aus Wikipedia-Daten verwendet. +# Wir benötigen hier eine ähnliche Logik, die aber 0 statt 'k.A.' zurückgibt. + +# --- Hilfsfunktion zur sicheren numerischen Extraktion FÜR DIE FILTERLOGIK --- +def get_numeric_filter_value(value_str, is_umsatz=False): + """ + Extrahiert und normalisiert Zahlenwerte für die Filterlogik (Umsatz in Mio, Mitarbeiter int). + Gibt 0 zurück, wenn der Wert leer, k.A., nicht numerisch ist, oder 0 ergibt. + Beachtet Einheiten (Tsd, Mio, Mrd) für Umsatz. + """ + if value_str is None or pd.isna(value_str) or str(value_str).strip() == '': + return 0 # Leer oder k.A. -> 0 + + raw_value_str = str(value_str).strip() + if raw_value_str.lower() in ['k.a.', 'n/a', '-']: + return 0 + + try: + # Bereinigung ähnlich wie in clean_text und extract_numeric_value + processed_value = clean_text(raw_value_str) # Annahme: clean_text existiert + if processed_value == "k.A.": return 0 + + processed_value = re.sub(r'(?i)^\s*(ca\.?|circa|rund|etwa|über|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() + processed_value_no_thousands = processed_value.replace('.', '').replace("'", "") # Entferne Punkte UND Apostrophe als Tausendertrenner + processed_value_final = processed_value_no_thousands.replace(',', '.') # Ersetze Komma durch Punkt für Dezimaltrennung + + match = re.search(r'([\d.]+)', processed_value_final) + if not match: + # logging.debug(f"get_numeric_filter_value: Keine numerischen Zeichen gefunden in '{processed_value_final}'") + return 0 # Keine numerischen Zeichen gefunden + + num_str = match.group(1) + if not num_str or num_str == '.': return 0 # Leerer oder nur Punkt String + + num = float(num_str) # Konvertiere zum float + + # --- Einheiten-Skalierung basierend auf ORIGINALSTRING --- + # Dies ist der kritische Teil für den Umsatz aus Spalte J, wenn er Einheiten enthalten kann + original_lower = raw_value_str.lower() + scale_factor = 1.0 # Skalierungsfaktor, um alles in die Basiseinheit zu bringen (z.B. 1 für Tsd, 1000 für Mio, 1000000 für Mrd) + is_scaled = False # Flag, ob eine Einheit gefunden wurde + + if re.search(r'\bmrd\s*\b|\bmilliarden\s*\b|\bbillion\s*\b', original_lower): + scale_factor = 1000000.0 # 1 Mrd = 1000 Mio, also 1000*1000 = 1.000.000 Base units (assuming base is 1) + is_scaled = True + elif re.search(r'\bmio\s*\b|\bmillionen\s*\b|\bmill\.\s*\b', original_lower): + # Wenn der Wert bereits in Millionen angegeben ist (wie Spalte J), + # und die Einheit 'Mio' gefunden wird, muss keine Skalierung angewendet werden, + # da wir das Ergebnis in Millionen benötigen. + # ABER: Wir müssen prüfen, ob der *Schwellenwert* in Millionen ist. + # Der min_umsatz ist in Millionen (z.B. 200). Wir wollen den Wert in Spalte J + # auch in Millionen haben für den Vergleich. + # Wenn Original "6815", Einheit "Mio" -> num=6815, scale_factor=1, Ergebnis=6815 (Mio) + # Wenn Original "6.8 Mrd", Einheit "Mrd" -> num=6.8, scale_factor=1000 (Mio), Ergebnis=6800 (Mio) + # Das ist komplex. Vereinfachte Annahme: Wenn "Mrd" gefunden, multipliziere mit 1000 für Mio. + # Wenn "Tsd" gefunden, teile durch 1000 für Mio. + # Wenn "Mio" oder keine Einheit gefunden, nehme Zahl direkt als Mio. + if is_umsatz: + if re.search(r'\bmrd\s*\b|\bmilliarden\s*\b|\bbillion\s*\b', original_lower): # Wenn Mrd gefunden + num = num * 1000.0 # Skaliere zu Millionen + is_scaled = True + elif re.search(r'\btsd\s*\b|\btausend\s*\b', original_lower): # Wenn Tsd gefunden + num = num / 1000.0 # Skaliere zu Millionen + is_scaled = True + # Wenn "Mio" gefunden oder keine Einheit und is_umsatz=True, + # nehmen wir an, der Wert ist bereits in Millionen, keine weitere Skalierung nötig. + elif re.search(r'\btsd\s*\b|\btausend\s*\b', original_lower): # Wenn Tsd gefunden (für MA) + num = num * 1000.0 # Skaliere zu Tausendern + is_scaled = True + # Andere Einheiten (Mio, Mrd für MA) werden hier ignoriert, was bei MA OK ist. + + + # Das Ergebnis muss 0 oder positiv sein für die Filterlogik + result_num = num if num > 0 else 0 # Werte <= 0 zählen nicht + + if is_umsatz: + # Rückgabe als Wert in Millionen + # Wenn keine Einheit gefunden wurde (und is_umsatz), gehen wir davon aus, dass es Millionen sind. + # Wenn eine Einheit (Tsd, Mrd) gefunden und skaliert wurde, ist es auch in Millionen. + return result_num # Der Wert sollte jetzt in Millionen sein, bereit für Vergleich mit min_umsatz + + else: # Mitarbeiterzahl + # Rückgabe als ganze Zahl + return round(result_num) # Mitarbeiterzahl runden + + except Exception as e: + logging.debug(f"Fehler in get_numeric_filter_value für Wert '{raw_value_str}': {e}") + return 0 + +# --- Ende Hilfsfunktion für Filter --- + + +# Nun die process_find_wiki_with_serp Funktion mit der neuen Logik def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500, min_umsatz=200): """ Sucht fehlende Wikipedia-URLs (Spalte M = k.A.) für Unternehmen mit - (Umsatz CRM > min_umsatz ODER Mitarbeiter CRM > min_employees) + (Umsatz CRM > min_umsatz MIO € ODER Mitarbeiter CRM > min_employees) über SerpAPI und trägt gefundene URLs in Spalte M ein. Setzt ReEval-Flag (A) und löscht abhängige Wiki-Spalten (N-V, AN, AO, AP, AX). Merkt sich in Spalte AY, wann die Suche durchgeführt wurde. @@ -379,9 +473,9 @@ def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500 sheet_handler (GoogleSheetHandler): Initialisierte Instanz. row_limit (int, optional): Maximale Anzahl zu prüfender Zeilen. Defaults to None. min_employees (int, optional): Mindestanzahl Mitarbeiter (Spalte K) als Teilfilter. Defaults to 500. - min_umsatz (int, optional): Mindestumsatz (Spalte J) als Teilfilter. Defaults to 200. + min_umsatz (int, optional): Mindestumsatz in MIO € (Spalte J) als Teilfilter. Defaults to 200. """ - logging.info(f"Starte Modus 'find_wiki_serp': Suche fehlende Wiki-URLs für Firmen mit (Umsatz CRM > {min_umsatz} ODER Mitarbeiter CRM > {min_employees})...") + logging.info(f"Starte Modus 'find_wiki_serp': Suche fehlende Wiki-URLs für Firmen mit (Umsatz CRM > {min_umsatz} MIO € ODER Mitarbeiter CRM > {min_employees})...") if not sheet_handler.load_data(): return all_data = sheet_handler.get_all_data_with_headers() @@ -396,7 +490,7 @@ def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500 # Verwenden Sie hier das COLUMN_MAP robust col_indices = {} required_keys = [ - "ReEval Flag", "CRM Anzahl Mitarbeiter", "CRM Umsatz", "Wiki URL", "CRM Name", + "ReEval Flag", "CRM Anzahl Mitarbeiter", "CRM Umsatz", "Wiki URL", "CRM Name", "CRM Website", # Website wird jetzt für SerpAPI Suche benötigt "Wiki Absatz", "Wiki Branche", "Wiki Umsatz", "Wiki Mitarbeiter", "Wiki Kategorien", "Chat Wiki Konsistenzprüfung", "Chat Begründung Wiki Inkonsistenz", "Chat Vorschlag Wiki Artikel", "Begründung bei Abweichung", "Wikipedia Timestamp", "Timestamp letzte Prüfung", @@ -426,48 +520,6 @@ def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500 now_timestamp_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - # --- Hilfsfunktion zur sicheren numerischen Extraktion (adaptiert von prepare_data_for_modeling) --- - def safe_numeric_extract(value_str): - if value_str is None or pd.isna(value_str) or str(value_str).strip() == '': return 0 # Return 0, nicht NaN, für die Vergleichslogik - try: - # Annahme: clean_text existiert - processed_value = clean_text(str(value_str)) - if processed_value == "k.A.": return 0 - - processed_value = re.sub(r'(?i)^\s*(ca\.?|circa|rund|etwa|über|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 Punkte UND Apostrophe als Tausendertrenner, ersetze Komma durch Punkt - processed_value = processed_value.replace('.', '').replace("'", "").replace(',', '.') - - match = re.search(r'([\d.]+)', processed_value) - if not match: return 0 # Keine numerischen Zeichen gefunden - - num_str = match.group(1) - if not num_str or num_str == '.': return 0 - - num = float(num_str) - - # Einheiten-Multiplikatoren (Mrd, Mio, Tsd) - Wichtig für Umsatz - multiplier = 1.0 - original_lower = str(value_str).lower() # Nutze den Originalstring für Einheiten - if "mrd" in original_lower or "milliarden" in original_lower or "billion" in original_lower: multiplier = 1000000000.0 - elif "mio" in original_lower or "millionen" in original_lower or "mill." in original_lower: multiplier = 1000000.0 - elif "tsd" in original_lower or "tausend" in original_lower: multiplier = 1000.0 - - num = num * multiplier - - # Für den Vergleich (Umsatz in Mio, Mitarbeiter int) - # Umsatz soll > 200 MIO sein, also num direkt mit 200,000,000 vergleichen - # Mitarbeiter > 500, num direkt mit 500 vergleichen - # Geben Sie den Rohwert nach Multiplikator zurück - return num if num > 0 else 0 # Nur positive Werte zählen - - except Exception as e: - logging.debug(f"Fehler in safe_numeric_extract für Wert '{str(value_str)[:50]}...': {e}") - return 0 - # --- Ende Hilfsfunktion --- - # Iteriere durch die Datenzeilen for idx, row in enumerate(data_rows): @@ -493,7 +545,7 @@ def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500 # Prüfe Wiki URL (M): Überspringe, wenn bereits gefüllt (nicht k.A. oder leer) m_value = row[col_indices["Wiki URL"]] - if m_value and str(m_value).strip().lower() != "k.a.": # Jetzt auch 'leer' abfangen? Nein, '' ist schon abgedeckt + if m_value and str(m_value).strip().lower() != "k.a.": skipped_m_filled_count += 1 continue @@ -501,20 +553,21 @@ def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500 umsatz_val_str = row[col_indices["CRM Umsatz"]] ma_val_str = row[col_indices["CRM Anzahl Mitarbeiter"]] - umsatz_val_num = safe_numeric_extract(umsatz_val_str) - ma_val_num = safe_numeric_extract(ma_val_str) + # Nutze die neue Hilfsfunktion, um die Werte für den Vergleich zu bekommen + umsatz_val_mio = get_numeric_filter_value(umsatz_val_str, is_umsatz=True) # Ergebnis in Mio € + ma_val_num = get_numeric_filter_value(ma_val_str, is_umsatz=False) # Ergebnis als Integer - # Filterlogik: Umsatz > min_umsatz ODER Mitarbeiter > min_employees + # Filterlogik: Umsatz > min_umsatz MIO € ODER Mitarbeiter > min_employees # Wenn *nicht* (Umsatz > min_umsatz ODER Mitarbeiter > min_employees), dann überspringe - if not (umsatz_val_num > min_umsatz * 1000000 or ma_val_num > min_employees): - logging.debug(f"Zeile {row_num_in_sheet}: Übersprungen (Größe nicht ausreichend. Umsatz: {umsatz_val_num:.0f}, MA: {ma_val_num}).") + if not (umsatz_val_mio > min_umsatz or ma_val_num > min_employees): + logging.debug(f"Zeile {row_num_in_sheet}: Übersprungen (Größe nicht ausreichend. Umsatz (Mio): {umsatz_val_mio:.2f}, MA: {ma_val_num}). Schwellen: Umsatz > {min_umsatz} Mio, MA > {min_employees}.") skipped_size_count += 1 continue # --- Ende Größenprüfung --- # Kandidat gefunden: M leer/k.A., AY leer, und Größe passt - company_name = row[col_indices["CRM Name"]] if len(row) > col_indices["CRM Name"] else "" + company_name = row[col_indices["CRM Name"]] if not company_name or str(company_name).strip() == "": logging.warning(f"Zeile {row_num_in_sheet}: Übersprungen, kein Firmenname für Suche vorhanden.") # Setze AY Timestamp, damit wir nicht immer wieder versuchen @@ -523,12 +576,13 @@ def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500 continue # SerpAPI Suche - logging.info(f"Zeile {row_num_in_sheet}: Suche Wiki-URL für '{company_name}' (Umsatz: {umsatz_val_num:.0f}, MA: {ma_val_num})...") + logging.info(f"Zeile {row_num_in_sheet}: Suche Wiki-URL für '{company_name}' (Umsatz (Mio): {umsatz_val_mio:.2f}, MA: {ma_val_num})...") processed_rows_count += 1 # Zähle VOR dem Call, dass ein Versuch gestartet wird + # Hole Website für SerpAPI Kontext + website_url = row[col_indices["CRM Website"]] if col_indices["CRM Website"] is not None and len(row) > col_indices["CRM Website"] else None + # Annahme: serp_wikipedia_lookup existiert und nutzt logging/retry - # website_url wird nicht direkt im SerpAPI Lookup verwendet, kann aber als Kontext hilfreich sein - website_url = row[COLUMN_MAP.get("CRM Website", -1)] if COLUMN_MAP.get("CRM Website", -1) != -1 and len(row) > COLUMN_MAP.get("CRM Website", -1) else None wiki_url_found = serp_wikipedia_lookup(company_name, website=website_url) # Updates vorbereiten @@ -541,12 +595,14 @@ def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500 logging.info(f" -> URL gefunden: {wiki_url_found}. Bereite Update vor (Setze M, A; Lösche N-V, AN, AO, AP, AX).") found_urls_count += 1 - # Zusätzliche Updates für gefundene URL + # Indizes und Buchstaben für Updates holen m_l = col_letters["Wiki URL"] a_l = col_letters["ReEval Flag"] # Spalten N-V leeren - n_l = col_letters["Wiki Absatz"] - v_l = col_letters["Begründung bei Abweichung"] + n_idx = col_indices["Wiki Absatz"] + v_idx = col_indices["Begründung bei Abweichung"] + n_l=sheet_handler._get_col_letter(n_idx+1) + v_l=sheet_handler._get_col_letter(v_idx+1) # Timestamps AN, AO, AX, Version AP leeren an_l = col_letters["Wikipedia Timestamp"] ao_l = col_letters["Timestamp letzte Prüfung"] @@ -557,9 +613,9 @@ def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500 all_sheet_updates.extend([ {'range': f'{m_l}{row_num_in_sheet}', 'values': [[wiki_url_found]]}, # URL setzen in M {'range': f'{a_l}{row_num_in_sheet}', 'values': [['x']]}, # ReEval Flag setzen in A - # --- Spalten leeren, damit sie neu befüllt werden --- + # --- Spalten N-V leeren, damit sie neu befüllt werden --- # Range N:V leeren - {'range': f'{n_l}{row_num_in_sheet}:{v_l}{row_num_in_sheet}', 'values': [[''] * (col_indices["Begründung bei Abweichung"] - col_indices["Wiki Absatz"] + 1)]}, + {'range': f'{n_l}{row_num_in_sheet}:{v_l}{row_num_in_sheet}', 'values': [[''] * (v_idx - n_idx + 1)]}, {'range': f'{an_l}{row_num_in_sheet}', 'values': [['']]}, # AN leeren {'range': f'{ao_l}{row_num_in_sheet}', 'values': [['']]}, # AO leeren {'range': f'{ap_l}{row_num_in_sheet}', 'values': [['']]}, # AP leeren