From dcc2413f9b0f7fd8e1ff6954e5fd42940fd2f425 Mon Sep 17 00:00:00 2001 From: Floke Date: Tue, 22 Apr 2025 06:12:55 +0000 Subject: [PATCH] bugfix --- brancheneinstufung.py | 132 +++++++++++++++++++++++------------------- 1 file changed, 74 insertions(+), 58 deletions(-) diff --git a/brancheneinstufung.py b/brancheneinstufung.py index f9b3a751..067c75b6 100644 --- a/brancheneinstufung.py +++ b/brancheneinstufung.py @@ -192,6 +192,7 @@ COLUMN_MAP = { "Finaler Umsatz (Wiki>CRM)": 47,# AV "Finaler Mitarbeiter (Wiki>CRM)": 48, # AW "Wiki Verif. Timestamp": 49 # AX (NEU) + "SerpAPI Wiki Search Timestamp": 50 # AY (NEU) } # Hinweis: Index ist 0-basiert, Spaltenbuchstaben sind 1-basiert (A=1, AW=49) @@ -319,11 +320,7 @@ def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500 Sucht fehlende Wikipedia-URLs (Spalte M = k.A.) für Unternehmen mit > min_employees über SerpAPI und trägt gefundene URLs in Spalte M ein. Setzt ReEval-Flag (A) und löscht Timestamps (AN, AO) für gefundene Einträge. - - Args: - 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 Filter. Defaults to 500. + Merkt sich in Spalte AY, wann die Suche durchgeführt wurde. """ logging.info(f"Starte Modus 'find_wiki_serp': Suche fehlende Wiki-URLs für Firmen > {min_employees} MA...") @@ -335,7 +332,7 @@ def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500 header_rows = 5 data_rows = all_data[header_rows:] - # Benötigte Spaltenindizes holen + # Benötigte Spaltenindizes holen (inkl. neuer Spalte AY) try: col_indices = { "A": COLUMN_MAP["ReEval Flag"], @@ -343,7 +340,8 @@ def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500 "M": COLUMN_MAP["Wiki URL"], "B": COLUMN_MAP["CRM Name"], "AN": COLUMN_MAP["Wikipedia Timestamp"], - "AO": COLUMN_MAP["Timestamp letzte Prüfung"] + "AO": COLUMN_MAP["Timestamp letzte Prüfung"], + "AY": COLUMN_MAP["SerpAPI Wiki Search Timestamp"] # <-- NEU } col_letters = {key: sheet_handler._get_col_letter(idx + 1) for key, idx in col_indices.items()} except KeyError as e: @@ -353,43 +351,49 @@ def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500 logging.critical(f"FEHLER beim Holen der Spaltenbuchstaben: {e}") return - all_sheet_updates = [] - processed_rows = 0 - found_urls = 0 + processed_rows = 0 # Zählt Zeilen, für die eine Suche durchgeführt wurde + found_urls = 0 # Zählt Zeilen, wo eine URL gefunden wurde + skipped_timestamp_ay = 0 # Zählt Zeilen, die übersprungen wurden, weil AY gesetzt war skipped_employee_count = 0 skipped_m_filled_count = 0 + now_timestamp_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S") # Einmaliger Timestamp für diesen Lauf for idx, row in enumerate(data_rows): row_num_in_sheet = idx + header_rows + 1 if row_limit is not None and processed_rows >= row_limit: - logging.info(f"Zeilenlimit ({row_limit}) erreicht.") + logging.info(f"Zeilenlimit ({row_limit}) für durchgeführte Suchen erreicht.") break - # Prüfe, ob Zeile überhaupt verarbeitet werden soll + # --- NEUE PRÜFUNG: Wurde diese Zeile schon via SerpAPI geprüft? --- + ts_ay_val = row[col_indices["AY"]] if len(row) > col_indices["AY"] else "" + if ts_ay_val and ts_ay_val.strip(): + skipped_timestamp_ay += 1 + # Optional: Debug Log + # logging.debug(f"Zeile {row_num_in_sheet}: Übersprungen (SerpAPI Wiki Search Timestamp AY bereits gesetzt: '{ts_ay_val}').") + continue # Nächste Zeile, diese wurde schon geprüft + # --- ENDE NEUE PRÜFUNG --- + try: # 1. Mitarbeiterzahl prüfen ma_val_str = row[col_indices["K"]] if len(row) > col_indices["K"] else "0" try: - # Versuche, die Zahl zu extrahieren (vereinfacht, ohne extract_numeric_value) - ma_val_str_cleaned = re.sub(r"[^\d]", "", ma_val_str) # Nur Ziffern + ma_val_str_cleaned = re.sub(r"[^\d]", "", ma_val_str) ma_val = int(ma_val_str_cleaned) if ma_val_str_cleaned else 0 - except ValueError: - ma_val = 0 # Im Zweifel als 0 werten + except ValueError: ma_val = 0 if ma_val <= min_employees: skipped_employee_count += 1 - continue # Nächste Zeile + continue # 2. Prüfen, ob Wiki URL (M) leer oder "k.A." ist m_value = row[col_indices["M"]] if len(row) > col_indices["M"] else "" if m_value and m_value.strip().lower() != "k.a.": skipped_m_filled_count += 1 - continue # Nächste Zeile + continue - # Wenn wir hier sind, ist die Zeile ein Kandidat - processed_rows += 1 + # Wenn wir hier sind, ist die Zeile ein Kandidat FÜR DIESEN LAUF company_name = row[col_indices["B"]] if len(row) > col_indices["B"] else "" if not company_name: logging.warning(f"Zeile {row_num_in_sheet}: Übersprungen, kein Firmenname für Suche vorhanden.") @@ -397,42 +401,50 @@ def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500 # --- SerpAPI Suche durchführen --- logging.info(f"Zeile {row_num_in_sheet}: Suche Wiki-URL für '{company_name}' (MA: {ma_val})...") - wiki_url_found = serp_wikipedia_lookup(company_name) # Annahme: nutzt logging - time.sleep(1.5) # Pause zwischen SerpAPI-Aufrufen + wiki_url_found = serp_wikipedia_lookup(company_name) + processed_rows += 1 # Zähle die durchgeführte Suche + time.sleep(1.5) # Pause + + # --- Updates vorbereiten --- + # Timestamp AY IMMER setzen, um die Suche zu markieren + row_updates = [{'range': f'{col_letters["AY"]}{row_num_in_sheet}', 'values': [[now_timestamp_str]]}] if wiki_url_found: logging.info(f" -> URL gefunden: {wiki_url_found}. Bereite Update vor.") found_urls += 1 - # Updates für diese Zeile sammeln - row_updates = [ - {'range': f'{col_letters["M"]}{row_num_in_sheet}', 'values': [[wiki_url_found]]}, # URL in M schreiben - {'range': f'{col_letters["A"]}{row_num_in_sheet}', 'values': [['x']]}, # ReEval Flag setzen - {'range': f'{col_letters["AN"]}{row_num_in_sheet}', 'values': [['']]}, # AN löschen - {'range': f'{col_letters["AO"]}{row_num_in_sheet}', 'values': [['']]} # AO löschen - ] - all_sheet_updates.extend(row_updates) + # Zusätzliche Updates nur, wenn URL gefunden wurde + row_updates.extend([ + {'range': f'{col_letters["M"]}{row_num_in_sheet}', 'values': [[wiki_url_found]]}, + {'range': f'{col_letters["A"]}{row_num_in_sheet}', 'values': [['x']]}, + {'range': f'{col_letters["AN"]}{row_num_in_sheet}', 'values': [['']]}, + {'range': f'{col_letters["AO"]}{row_num_in_sheet}', 'values': [['']]} + ]) else: logging.info(f" -> Keine Wiki-URL für '{company_name}' via SerpAPI gefunden.") - # Optional: Status in eine separate Spalte schreiben? Vorerst nicht. + # Optional: Marker in M schreiben? Vorerst nicht, AY reicht. + # row_updates.append({'range': f'{col_letters["M"]}{row_num_in_sheet}', 'values': [['k.A. (SerpAPI Searched)']]}) + + all_sheet_updates.extend(row_updates) except Exception as e: logging.exception(f"Unerwarteter Fehler bei Verarbeitung von Zeile {row_num_in_sheet}: {e}") - # Mache mit der nächsten Zeile weiter - # --- Batch Update am Ende --- - if all_sheet_updates: - logging.info(f"Sende Batch-Update für {found_urls} gefundene Wiki-URLs ({len(all_sheet_updates)} Zellen)...") - success = sheet_handler.batch_update_cells(all_sheet_updates) - if success: - logging.info(f"Sheet-Update für 'find_wiki_serp' erfolgreich.") - else: - logging.info("Keine neuen Wiki-URLs gefunden zum Eintragen.") +# --- Batch Update am Ende --- +if all_sheet_updates: + logging.info(f"Sende Batch-Update für {processed_rows} geprüfte Zeilen ({found_urls} URLs gefunden, {len(all_sheet_updates)} Zellen)...") + success = sheet_handler.batch_update_cells(all_sheet_updates) + if success: + logging.info(f"Sheet-Update für 'find_wiki_serp' erfolgreich.") + # Fehler wird von batch_update_cells geloggt +else: + logging.info("Keine Zeilen gefunden, für die eine SerpAPI Wiki-Suche durchgeführt werden musste/konnte.") - logging.info(f"Modus 'find_wiki_serp' abgeschlossen.") - logging.info(f" Geprüfte Kandidaten (MA>{min_employees}, M leer): {processed_rows}") - logging.info(f" Gefundene & eingetragene URLs: {found_urls}") - logging.info(f" Übersprungen (MA <= {min_employees}): {skipped_employee_count}") - logging.info(f" Übersprungen (M bereits gefüllt): {skipped_m_filled_count}") +logging.info(f"Modus 'find_wiki_serp' abgeschlossen.") +logging.info(f" Durchgeführte Suchen in diesem Lauf: {processed_rows}") +logging.info(f" Gefundene & eingetragene URLs: {found_urls}") +logging.info(f" Übersprungen (AY bereits gesetzt): {skipped_timestamp_ay}") +logging.info(f" Übersprungen (MA <= {min_employees}): {skipped_employee_count}") +logging.info(f" Übersprungen (M bereits gefüllt): {skipped_m_filled_count}") def prepare_data_for_modeling(sheet_handler): """ @@ -3714,7 +3726,8 @@ def alignment_demo(sheet): "Geschätzter Techniker Bucket", # AU "Finaler Umsatz (Wiki>CRM)", # AV "Finaler Mitarbeiter (Wiki>CRM)", # AW - "Wiki Verif. Timestamp" # AX (NEU) + "Wiki Verif. Timestamp", # AX + "SerpAPI Wiki Search Timestamp" # AY (NEU) ], [ # Quelle der Daten (Zeile 2) "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", "Wikipediascraper", "Wikipediascraper", "Wikipediascraper", "Wikipediascraper", "Wikipediascraper", "Wikipediascraper", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "LinkedIn (via SerpApi)", "LinkedIn (via SerpApi)", "LinkedIn (via SerpApi)", "LinkedIn (via SerpApi)", "System", "System", "System", "System", "System", "Web Scraper", "Chat GPT API", @@ -3722,7 +3735,8 @@ def alignment_demo(sheet): "ML Modell / Skript", # AU "Skript (Wiki/CRM)", # AV "Skript (Wiki/CRM)", # AW - "System" # AX (NEU) - Timestamp vom Wiki-Verifizierungs-Prozess + "System", # AX + "System" # AY (NEU) ], [ # Feldkategorie (Zeile 3) "Prozess", "Firmenname", "Firmenname", "Website", "Ort", "Beschreibung (Text)", "Branche", "Branche", "Anzahl Servicetechniker", "Umsatz", "Anzahl Mitarbeiter", "Wikipedia Artikel URL", "Wikipedia Artikel", "Beschreibung (Text)", "Branche", "Umsatz", "Anzahl Mitarbeiter", "Kategorien (Text)", "Verifizierung", "Begründung bei Abweichung", "Wikipedia Artikel", "Wikipedia Artikel", "Branche", "Branche", "Branche", "FSM Relevanz", "FSM Relevanz", "Anzahl Mitarbeiter", "Anzahl Mitarbeiter", "Anzahl Mitarbeiter", "Anzahl Servicetechniker", "Anzahl Servicetechniker", "Umsatz", "Umsatz", "Kontakte zur Firma", "Kontakte zur Firma", "Kontakte zur Firma", "Kontakte zur Firma", "Timestamp", "Timestamp", "Timestamp", "Version des Skripts die verwendet wurde", "ChatGPT Tokens", "Website-Content", "Website Zusammenfassung", @@ -3730,7 +3744,8 @@ def alignment_demo(sheet): "Anzahl Servicetechniker Bucket", # AU "Umsatz", # AV "Anzahl Mitarbeiter", # AW - "Timestamp" # AX (NEU) + "Timestamp", # AX + "Timestamp" # AY (NEU) ], [ # Kurze Beschreibung (Zeile 4) "Systemspalte...", "Enthält den Firmennamen...", "Manuell gepflegte Kurzform...", "Website des Unternehmens.", "Ort des Unternehmens.", "Kurze Beschreibung...", "Aktuelle Branchenzuweisung...", "Externe Branchenbeschreibung...", "Recherchierte Anzahl...", "Umsatz in Mio. € (CRM).", "Anzahl Mitarbeiter (CRM).", "Vorgeschlagene Wikipedia URL...", "Wikipedia URL...", "Erster Absatz...", "Wikipedia-Branche...", "Wikipedia-Umsatz...", "Wikipedia-Mitarbeiterzahl...", "Liste der Wikipedia-Kategorien.", "\"OK\" oder \"X\" – Ergebnis...", "Begründung bei Inkonsistenz...", "Chat-Vorschlag Wiki Artikel...", "Nicht genutzt...", "Branchenvorschlag via ChatGPT...", "Vergleich: Übereinstimmung CRM vs. ...", "Begründung bei abweichender...", "FSM-Relevanz: Bewertung...", "Begründung zur FSM-Bewertung.", "Schätzung Anzahl Mitarbeiter...", "Vergleich CRM vs. Wiki vs. ...", "Begründung bei Mitarbeiterabweichung...", "Schätzung Servicetechniker...", "Begründung bei Abweichung...", "Schätzung Umsatz via ChatGPT.", "Begründung bei Umsatzabweichung.", "Anzahl Kontakte (Serviceleiter)...", "Anzahl Kontakte (IT-Leiter)...", "Anzahl Kontakte (Management)...", "Anzahl Kontakte (Disponent)...", "Timestamp der Kontaktsuche.", "Timestamp der Wikipedia-Suche/Extraktion.", "Timestamp der ChatGPT-Bewertung / Letzte Prüfung der Zeile.", "Ausgabe der Skriptversion...", "Token-Zählung...", "Roh extrahierter Text...", "Zusammenfassung des Webseiteninhalts...", @@ -3738,7 +3753,8 @@ def alignment_demo(sheet): "Geschätzter Bucket (1-7) für Servicetechniker...", # AU "Konsolidierter Umsatz (Mio €) nach Priorität Wiki > CRM.", # AV "Konsolidierte Mitarbeiterzahl nach Priorität Wiki > CRM.", # AW - "Timestamp der letzten Wiki-Verifikation (Spalten S-Y)." # AX (NEU) + "Timestamp der letzten Wiki-Verifikation (Spalten S-Y).", # AX + "Timestamp der letzten SerpAPI-Suche nach fehlender Wiki-URL (Modus find_wiki_serp)." # AY (NEU) ], [ # Aufgabe / Funktion (Zeile 5) - Ergänzt um AX "Datenquelle", "Datenquelle", "Datenquelle", "Datenquelle", "Datenquelle", "Datenquelle", "Datenquelle", "Datenquelle", "Datenquelle", "Datenquelle", "Datenquelle", "Datenquelle", "Wird durch Wikipedia Scraper bereitgestellt", "Wird zunächst nicht verwendet...", "Wird u.a. zur finalen Ermittlung...", "Wird u.a. mit CRM-Umsatz...", "Wird u.a. mit CRM-Anzahl...", "Wenn Website-Daten fehlen...", "\"Es soll durch ChatGPT geprüft werden...", "\"Liegt eine Inkonsistenz...", "\"Sollte durch die Wikipedia-Suche...", "XXX derzeit nicht verwendet...", "\"ChatGPT soll anhand der vorliegenden...", "Die in Spalte CRM festgelegte...", "Weicht die von ChatGPT ermittelte...", "ChatGPT soll anhand der vorliegenden Daten prüfen...", "Die in 'Chat Begründung für FSM Relevanz'...", "Nur wenn kein Wikipedia-Eintrag...", "Entspricht die durch ChatGPT ermittelte...", "Weicht die von ChatGPT geschätzte...", "ChatGPT soll auf Basis öffentlich...", "Weicht die von ChatGPT geschätzte...", "Nur wenn kein Wikipedia-Eintrag...", "ChatGPT soll signifikante Umsatzabweichungen...", "Über SerpAPI wird zusammen...", "Über SerpAPI wird zusammen...", "Über SerpAPI wird zusammen...", "Über SerpAPI wird zusammen...", "Wenn die Kontaktsuche gestartet wird...", "Wenn die Wikipedia-Suche gestartet wird...", "Wenn die ChatGPT-Bewertung gestartet wird...", "Wird durch das System befüllt", "Wird durch tiktoken berechnet", "Wird durch Web Scraper...", "Wird durch ChatGPT API...", @@ -3746,23 +3762,23 @@ def alignment_demo(sheet): "Ergebnis der Schätzung durch das trainierte ML-Modell.", # AU "Vom Skript berechneter Wert, priorisiert Wiki > CRM...", # AV "Vom Skript berechneter Wert, priorisiert Wiki > CRM...", # AW - "Timestamp wird gesetzt, wenn Wiki-Verifikation (S-Y) durchgeführt wurde." # AX (NEU) + "Timestamp wird gesetzt, wenn Wiki-Verifikation (S-Y) durchgeführt wurde.", # AX + "Timestamp wird gesetzt, nachdem versucht wurde, eine fehlende Wiki-URL via SerpAPI zu finden." # AY (NEU) ] ] num_cols = len(new_headers[0]) - def colnum_string(n): - string = "" - while n > 0: n, remainder = divmod(n - 1, 26); string = chr(65 + remainder) + string - return string + # ... (Rest der Funktion zum Schreiben der Header bleibt gleich, verwendet jetzt AY als Endspalte) ... + def colnum_string(n): # Hilfsfunktion bleibt + string = "" + while n > 0: n, remainder = divmod(n - 1, 26); string = chr(65 + remainder) + string + return string end_col_letter = colnum_string(num_cols) header_range = f"A1:{end_col_letter}{len(new_headers)}" try: sheet.update(values=new_headers, range_name=header_range) - print(f"Alignment-Demo abgeschlossen: Header in Bereich {header_range} geschrieben.") - debug_print(f"Alignment-Demo: Header in Bereich {header_range} geschrieben.") + logging.info(f"Alignment-Demo abgeschlossen: Header in Bereich {header_range} geschrieben.") except Exception as e: - print(f"FEHLER beim Schreiben der Alignment-Demo Header: {e}") - debug_print(f"FEHLER beim Schreiben der Alignment-Demo Header: {e}") + logging.error(f"FEHLER beim Schreiben der Alignment-Demo Header: {e}") # ==================== DATA PROCESSOR ==================== class DataProcessor: