diff --git a/brancheneinstufung.py b/brancheneinstufung.py index 970945a5..4e1248ba 100644 --- a/brancheneinstufung.py +++ b/brancheneinstufung.py @@ -29,6 +29,8 @@ import csv import gender_guesser.detector as gender from urllib.parse import urlparse, urlencode import argparse +import pandas as pd +import numpy as np # Für NaN # Optional: tiktoken für Token-Zählung (Modus 8) try: @@ -2190,6 +2192,9 @@ def process_contact_research(sheet_handler): debug_print("Contact Research abgeschlossen.") # ==================== ALIGNMENT DEMO (Hauptblatt) ==================== +# ==================== ALIGNMENT DEMO (Hauptblatt) ==================== +# Diese Funktion ist bereits im Code vorhanden (Zeile ~1230 in der vorherigen Version) +# Sie bleibt unverändert: def alignment_demo(sheet): """Schreibt die Header-Struktur (Zeilen 1-5) ins angegebene Sheet.""" # Definition der Header wie im Original-Code @@ -2215,10 +2220,11 @@ def alignment_demo(sheet): try: sheet.update(values=new_headers, range_name=header_range) - print(f"Alignment-Demo abgeschlossen: Header in Bereich {header_range} geschrieben.") + print(f"Alignment-Demo abgeschlossen: Header in Bereich {header_range} geschrieben.") # Geändert zu print für Sichtbarkeit + debug_print(f"Alignment-Demo: Header in Bereich {header_range} geschrieben.") except Exception as e: - print(f"Fehler beim Schreiben der Alignment-Demo Header: {e}") - + print(f"FEHLER beim Schreiben der Alignment-Demo Header: {e}") # Geändert zu print + debug_print(f"FEHLER beim Schreiben der Alignment-Demo Header: {e}") # ==================== DATA PROCESSOR ==================== class DataProcessor: @@ -2502,18 +2508,16 @@ class DataProcessor: debug_print(f"Modus 22 abgeschlossen. {rows_processed} Websites ergänzt.") -# ==================== MAIN FUNCTION ==================== # ==================== MAIN FUNCTION ==================== def main(): - # global MODE, LOG_FILE # MODE wird nicht mehr global benötigt, aber LOG_FILE schon - # -> MODE wird unten neu gesetzt, kein global nötig. LOG_FILE wird global gesetzt. - global LOG_FILE + global LOG_FILE # LOG_FILE wird global benötigt # --- Initialisierung --- # Argument Parser parser = argparse.ArgumentParser(description="Firmen-Datenanreicherungs-Skript") - parser.add_argument("--mode", type=str, help="Betriebsmodus (z.B. combined, wiki, website, branch, reeval, website_lookup, website_details, contacts, full_run)") - # HIER KORRIGIERT: --limit statt --row_limit + # Modus Argument + parser.add_argument("--mode", type=str, help="Betriebsmodus (z.B. combined, wiki, website, branch, reeval, website_lookup, website_details, contacts, full_run, alignment)") + # Limit Argument parser.add_argument("--limit", type=int, help="Maximale Anzahl zu verarbeitender Zeilen (für Batch/sequentielle Modi)", default=None) args = parser.parse_args() @@ -2521,7 +2525,8 @@ def main(): Config.load_api_keys() # Betriebsmodus ermitteln - valid_modes = ["combined", "wiki", "website", "branch", "reeval", "website_lookup", "website_details", "contacts", "full_run"] + # HIER NEU: 'alignment' hinzugefügt + valid_modes = ["combined", "wiki", "website", "branch", "reeval", "website_lookup", "website_details", "contacts", "full_run", "alignment"] mode = None # Priorisiere Kommandozeilenargumente if args.mode and args.mode.lower() in valid_modes: @@ -2539,14 +2544,16 @@ def main(): print(" website_details:Extrahiert Title/Desc/H-Tags für Zeilen mit 'x' in Spalte A") print(" contacts: Sucht LinkedIn Kontakte via SERP API und schreibt in 'Contacts' Blatt") print(" full_run: Verarbeitet alle Zeilen sequentiell ab der ersten ohne Zeitstempel (AO)") + print(" alignment: Schreibt die Definitions-Header (Zeilen 1-5) ins Hauptblatt (Überschreibt A1:AS5!)") # NEUE Beschreibung try: + # HIER NEU: valid_modes für die Hilfsanzeige verwendet mode_input = input(f"Geben Sie den Modus ein ({', '.join(valid_modes)}): ").strip().lower() if mode_input in valid_modes: mode = mode_input else: print("Ungültige Eingabe. Standardmodus 'combined' wird verwendet.") mode = "combined" # Standardmodus - # Füge eine Sicherheitsprüfung hinzu, falls input() in nohup aufgerufen wird + # Fehlerbehandlung für input() im Hintergrund except OSError as e: if e.errno == 9: # Bad file descriptor print("Fehler: Interaktive Modus-Abfrage nicht möglich (läuft im Hintergrund?). Standardmodus 'combined' wird verwendet.") @@ -2555,24 +2562,21 @@ def main(): print(f"Unerwarteter OS-Fehler bei Modus-Abfrage: {e}") print("Standardmodus 'combined' wird verwendet.") mode = "combined" - except EOFError: # Kann auftreten, wenn stdin geschlossen ist (z.B. bei nohup) + except EOFError: print("Fehler: Interaktive Modus-Abfrage nicht möglich (EOF). Standardmodus 'combined' wird verwendet.") mode = "combined" - # Zeilenlimit ermitteln + # Zeilenlimit ermitteln (Logik bleibt unverändert, fragt nur wenn nötig) row_limit = None - # Priorisiere Kommandozeilenargumente - if args.limit is not None: # Prüfe, ob --limit explizit gesetzt wurde (auch wenn 0) - # Erlaube auch 0 als Limit (obwohl es nichts tut, ist es eine gültige Angabe) + if args.limit is not None: if args.limit >= 0: row_limit = args.limit print(f"Zeilenlimit (aus Kommandozeile): {row_limit}") else: print("Warnung: Negatives Zeilenlimit ignoriert. Kein Limit gesetzt.") row_limit = None - # Nur wenn KEIN Limit über die Kommandozeile kam UND es ein Modus ist, der ein Limit brauchen könnte, FRAGE interaktiv - elif mode in ["combined", "wiki", "website", "branch", "full_run"]: + elif mode in ["combined", "wiki", "website", "branch", "full_run"]: # Limit nur für diese Modi relevant try: limit_input = input("Wie viele Zeilen sollen maximal bearbeitet werden? (Enter für alle) ") if limit_input.strip(): @@ -2584,21 +2588,20 @@ def main(): print("Warnung: Negatives Zeilenlimit ignoriert. Kein Limit gesetzt.") row_limit = None else: - row_limit = None # Alle verarbeiten + row_limit = None print("Kein Zeilenlimit gesetzt.") except ValueError: print("Ungültige Eingabe für Zeilenlimit. Kein Limit gesetzt.") row_limit = None - # Füge eine Sicherheitsprüfung hinzu, falls input() in nohup aufgerufen wird except OSError as e: - if e.errno == 9: # Bad file descriptor + if e.errno == 9: print("Warnung: Interaktive Abfrage des Limits nicht möglich (läuft im Hintergrund?). Kein Limit gesetzt.") row_limit = None else: print(f"Unerwarteter OS-Fehler bei Limit-Abfrage: {e}") print("Kein Limit gesetzt.") row_limit = None - except EOFError: # Kann auftreten, wenn stdin geschlossen ist + except EOFError: print("Warnung: Interaktive Abfrage des Limits nicht möglich (EOF). Kein Limit gesetzt.") row_limit = None @@ -2608,35 +2611,39 @@ def main(): debug_print(f"===== Skript gestartet =====") debug_print(f"Version: {Config.VERSION}") debug_print(f"Betriebsmodus: {mode}") - debug_print(f"Zeilenlimit: {row_limit if row_limit is not None else 'Unbegrenzt'}") + # Korrigiere Logging für row_limit, wenn es 0 ist + limit_log_text = str(row_limit) if row_limit is not None else 'Unbegrenzt' + if row_limit == 0 and mode in ["combined", "wiki", "website", "branch", "full_run"]: + limit_log_text = '0 (Keine Verarbeitung geplant)' + debug_print(f"Zeilenlimit: {limit_log_text}") debug_print(f"Logdatei: {LOG_FILE}") # --- Vorbereitung --- # Lade Branchenschema - load_target_schema() # Stellt sicher, dass globale Variablen gesetzt sind + load_target_schema() # Initialisiere Google Sheet Handler try: sheet_handler = GoogleSheetHandler() except Exception as e: debug_print(f"FATAL: Konnte Google Sheet Handler nicht initialisieren: {e}") + print(f"FEHLER: Verbindung zu Google Sheets fehlgeschlagen. Siehe Logdatei: {LOG_FILE}") # Direkte Ausgabe für den User return # Abbruch # Initialisiere DataProcessor data_processor = DataProcessor(sheet_handler) - # Optional: Alignment Demo für Hauptblatt ausführen? (Bleibt auskommentiert) - # run_alignment = input("Sollen die Header im Hauptblatt aktualisiert werden (Alignment Demo)? (j/N): ").lower() - # if run_alignment == 'j': - # alignment_demo(sheet_handler.sheet) - # --- Modusausführung --- start_time = time.time() debug_print(f"Starte Verarbeitung um {datetime.now().strftime('%H:%M:%S')}...") try: if mode in ["wiki", "website", "branch", "combined"]: - run_dispatcher(mode, sheet_handler, row_limit) + # Stelle sicher, dass row_limit nicht 0 ist, sonst startet der Dispatcher umsonst + if row_limit == 0: + debug_print("Zeilenlimit ist 0. Überspringe Dispatcher-Aufruf.") + else: + run_dispatcher(mode, sheet_handler, row_limit) elif mode == "reeval": data_processor.process_reevaluation_rows() elif mode == "website_lookup": @@ -2644,30 +2651,58 @@ def main(): elif mode == "website_details": data_processor.process_website_details_for_marked_rows() elif mode == "contacts": - process_contact_research(sheet_handler) # Übergib den Handler + process_contact_research(sheet_handler) elif mode == "full_run": - start_index = sheet_handler.get_start_row_index() # Index in Datenliste (0-basiert) - if start_index < len(sheet_handler.get_data()): # Nur starten, wenn Startindex valide ist - num_available = len(sheet_handler.get_data()) - start_index - # Berechne Anzahl zu verarbeitender Zeilen basierend auf Limit - if row_limit is not None and row_limit >= 0: - num_to_process = min(row_limit, num_available) - else: # Kein Limit oder negatives Limit -> alle verfügbaren - num_to_process = num_available - - if num_to_process > 0: - data_processor.process_rows_sequentially(start_index, num_to_process, process_wiki=True, process_chatgpt=True, process_website=True) - else: - debug_print("Keine Zeilen für 'full_run' zu verarbeiten (Limit 0 oder Startindex am Ende).") + # Behandlung von Limit 0 direkt hier + if row_limit == 0: + debug_print("Zeilenlimit ist 0. Überspringe sequenzielle Verarbeitung.") else: - debug_print(f"Startindex {start_index} liegt hinter der letzten Datenzeile. Keine Verarbeitung für 'full_run'.") + start_index = sheet_handler.get_start_row_index() + if start_index < len(sheet_handler.get_data()): + num_available = len(sheet_handler.get_data()) - start_index + if row_limit is not None and row_limit >= 0: + num_to_process = min(row_limit, num_available) + else: + num_to_process = num_available + + if num_to_process > 0: + data_processor.process_rows_sequentially(start_index, num_to_process, process_wiki=True, process_chatgpt=True, process_website=True) + else: + debug_print("Keine Zeilen für 'full_run' zu verarbeiten (Limit 0 oder Startindex am Ende).") + else: + debug_print(f"Startindex {start_index} liegt hinter der letzten Datenzeile. Keine Verarbeitung für 'full_run'.") + # HIER NEU: elif für den alignment Modus + elif mode == "alignment": + print("\nACHTUNG: Dieser Modus überschreibt die Zellen A1:AS5 im Haupt-Sheet!") + print("Diese Zellen enthalten die Spaltendefinitionen (Alignment Demo).") + try: + confirm = input("Möchten Sie wirklich fortfahren? (j/N): ").strip().lower() + if confirm == 'j': + debug_print("Bestätigung erhalten. Starte Alignment Demo...") + alignment_demo(sheet_handler.sheet) # Rufe die Funktion auf + debug_print("Alignment Demo Aufruf beendet.") + else: + print("Vorgang abgebrochen.") + debug_print("Alignment Demo vom Benutzer abgebrochen.") + # Fehlerbehandlung für input() im Hintergrund + except OSError as e: + if e.errno == 9: # Bad file descriptor + print("Fehler: Interaktive Bestätigung nicht möglich (läuft im Hintergrund?). Vorgang abgebrochen.") + debug_print("Alignment Demo abgebrochen (keine interaktive Bestätigung möglich).") + else: + print(f"Unerwarteter OS-Fehler bei Bestätigung: {e}. Vorgang abgebrochen.") + debug_print(f"Alignment Demo abgebrochen (OS-Fehler: {e}).") + except EOFError: + print("Fehler: Interaktive Bestätigung nicht möglich (EOF). Vorgang abgebrochen.") + debug_print("Alignment Demo abgebrochen (EOF).") + else: debug_print(f"Unbekannter Modus '{mode}' - keine Aktion ausgeführt.") except Exception as e: debug_print(f"FATAL: Unerwarteter Fehler auf oberster Ebene während der Modusausführung: {e}") import traceback - debug_print(traceback.format_exc()) # Detaillierten Stacktrace loggen + debug_print(traceback.format_exc()) # --- Abschluss --- end_time = time.time() @@ -2680,7 +2715,7 @@ def main(): try: with open(LOG_FILE, "a", encoding="utf-8") as f: f.write(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] ===== Skript wirklich beendet =====\n") - except: pass # Ignoriere Fehler beim letzten Schreiben + except: pass print(f"Verarbeitung abgeschlossen. Logfile: {LOG_FILE}")